feat: add GET /api/search?q=...&format=md for AI/LLM consumption
New endpoint returns all matching documents as concatenated plain-text
markdown, one section per document separated by ---.
Format:
# Document: {title}
ID: {id} | Category: {category} | Date: {date} | Vendor: {vendor}
{full_text or summary}
---
Parameters:
q - search query (required)
format - must be 'md' (required; distinguishes from HTML search)
Uses same FTS5 search as existing endpoints, limit raised to 200.
Falls back to LIKE search if FTS5 fails. Returns text/markdown content type.
POST /api/search (HTML partial) unchanged.
This commit is contained in:
parent
63d4e5e5ca
commit
405a6f697f
72
main.go
72
main.go
|
|
@ -98,6 +98,7 @@ func main() {
|
|||
r.Get("/search", searchHandler)
|
||||
|
||||
// API endpoints
|
||||
r.Get("/api/search", apiSearchMDHandler)
|
||||
r.Post("/api/search", apiSearchHandler)
|
||||
r.Get("/api/documents", apiDocumentsHandler)
|
||||
r.Get("/api/processing", apiProcessingHandler)
|
||||
|
|
@ -372,6 +373,77 @@ func apiSearchHandler(w http.ResponseWriter, r *http.Request) {
|
|||
renderPartial(w, "document-list", docs)
|
||||
}
|
||||
|
||||
// apiSearchMDHandler handles GET /api/search?q={query}&format=md
|
||||
// Returns all matching documents as concatenated plain-text markdown,
|
||||
// one document per section separated by ---. Intended for AI/LLM consumption.
|
||||
func apiSearchMDHandler(w http.ResponseWriter, r *http.Request) {
|
||||
query := r.URL.Query().Get("q")
|
||||
format := r.URL.Query().Get("format")
|
||||
|
||||
// Only serve markdown format; anything else falls back to a 400
|
||||
if format != "md" {
|
||||
http.Error(w, "format=md required", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if query == "" {
|
||||
http.Error(w, "q parameter required", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
docs, err := SearchDocuments(query, 200)
|
||||
if err != nil {
|
||||
docs, _ = SearchDocumentsFallback(query, 200)
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "text/markdown; charset=utf-8")
|
||||
|
||||
if len(docs) == 0 {
|
||||
w.Write([]byte("No documents found matching: " + query + "\n"))
|
||||
return
|
||||
}
|
||||
|
||||
var sb strings.Builder
|
||||
for i, doc := range docs {
|
||||
sb.WriteString("# Document: ")
|
||||
if doc.Title != "" {
|
||||
sb.WriteString(doc.Title)
|
||||
} else {
|
||||
sb.WriteString("(untitled)")
|
||||
}
|
||||
sb.WriteString("\n")
|
||||
sb.WriteString("ID: ")
|
||||
sb.WriteString(doc.ID)
|
||||
if doc.Category != "" {
|
||||
sb.WriteString(" | Category: ")
|
||||
sb.WriteString(doc.Category)
|
||||
}
|
||||
if doc.Date != "" {
|
||||
sb.WriteString(" | Date: ")
|
||||
sb.WriteString(doc.Date)
|
||||
}
|
||||
if doc.Vendor != "" {
|
||||
sb.WriteString(" | Vendor: ")
|
||||
sb.WriteString(doc.Vendor)
|
||||
}
|
||||
sb.WriteString("\n\n")
|
||||
|
||||
text := doc.FullText
|
||||
if text == "" {
|
||||
text = doc.Summary
|
||||
}
|
||||
if text != "" {
|
||||
sb.WriteString(text)
|
||||
sb.WriteString("\n")
|
||||
}
|
||||
|
||||
if i < len(docs)-1 {
|
||||
sb.WriteString("\n---\n\n")
|
||||
}
|
||||
}
|
||||
|
||||
w.Write([]byte(sb.String()))
|
||||
}
|
||||
|
||||
func apiProcessingHandler(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(GetActiveJobs())
|
||||
|
|
|
|||
Loading…
Reference in New Issue