blob: c78dae908ded37d7cbfc35e6200ec56e277d906a [file] [log] [blame]
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"bytes"
"context"
"fmt"
"io"
"net/http"
"net/url"
"github.com/google/safehtml/template"
"golang.org/x/oscar/internal/llm"
"golang.org/x/oscar/internal/storage"
)
func (g *Gaby) handleSearch(w http.ResponseWriter, r *http.Request) {
query := r.FormValue("q")
if query == "" {
_, _ = io.WriteString(w, searchForm)
return
}
results, err := g.search(query)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
data, err := searchResultsHTML(query, results)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
_, _ = w.Write(data)
}
type searchResult struct {
Title string
VResult storage.VectorResult
IDIsURL bool // VResult.ID can be parsed as a URL
}
func (g *Gaby) search(query string) ([]searchResult, error) {
vecs, err := g.embed.EmbedDocs(context.Background(), []llm.EmbedDoc{{Title: "", Text: query}})
if err != nil {
return nil, fmt.Errorf("EmbedDocs: %w", err)
}
vec := vecs[0]
var srs []searchResult
for _, r := range g.vector.Search(vec, 20) {
title := "?"
if d, ok := g.docs.Get(r.ID); ok {
title = d.Title
}
_, err := url.Parse(r.ID)
srs = append(srs, searchResult{title, r, err == nil})
}
return srs, nil
}
func searchResultsHTML(query string, results []searchResult) ([]byte, error) {
data := struct {
Query string
Results []searchResult
}{
query, results,
}
var buf bytes.Buffer
if err := searchResultsTmpl.Execute(&buf, data); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
const searchForm = `
<!doctype html>
<html>
<head>
<title>Oscar Search</title>
<!-- All links open in another tab. -->
<base target="_blank">
</head>
<body>
<h1>Vector search</h1>
<form action="/search" method="GET">
<input type="text" name="q" required autofocus />
<input type="submit" value="Search"/>
</form
</body>
</html>
`
var searchResultsTmpl = template.Must(template.New("").Parse(`
<!doctype html>
<html>
<head>
<title>Oscar Search Results</title>
</head>
<body>
<h1>Search results for "{{.Query}}"</h1>
{{- with .Results -}}
{{- range . }}
<p>{{with .Title}}{{.}}: {{end -}}
{{if .IDIsURL -}}
{{with .VResult}}<a href="{{.ID}}">{{.ID}}</a>{{end -}}
{{else -}}
{{.VResult.ID}}
{{end -}}
{{" "}}({{.VResult.Score}})</p>
{{end}}
{{- else -}}
No results.
{{- end}}
</body>
</html>
`))