Neural-Brain integriert

This commit is contained in:
2026-08-04 21:14:40 +02:00
parent dc369dab65
commit 5f870912a3
2 changed files with 100 additions and 1 deletions

View File

@@ -11,8 +11,10 @@ import (
"os"
"strconv"
"strings"
"time"
"kb-editor/internal/aifallback"
"kb-editor/internal/brainactivity"
"kb-editor/internal/staging"
"kb-editor/internal/store"
)
@@ -131,8 +133,15 @@ func (a *app) handleList(w http.ResponseWriter, r *http.Request) {
}
func (a *app) handleSearch(w http.ResponseWriter, r *http.Request) {
startedAt := time.Now()
q := queryFromURL(r)
writeJSON(w, http.StatusOK, a.store.Search(q))
result := a.store.Search(q)
hits := make([]brainactivity.Hit, 0, len(result.Items))
for _, hit := range result.Items {
hits = append(hits, brainactivity.Hit{ID: hit.ID, Score: float64(hit.Score) / 100})
}
brainactivity.EmitSearch("knowledgebase", q.Q, hits, time.Since(startedAt))
writeJSON(w, http.StatusOK, result)
}
func (a *app) handleFacets(w http.ResponseWriter, r *http.Request) {

View File

@@ -0,0 +1,90 @@
package brainactivity
import (
"bytes"
"encoding/json"
"net/http"
"os"
"strings"
"sync"
"time"
)
type Hit struct {
ID string `json:"id"`
Score float64 `json:"score,omitempty"`
}
type event struct {
Type string `json:"type"`
Source string `json:"source"`
Query string `json:"query,omitempty"`
Message string `json:"message,omitempty"`
Hits []Hit `json:"hits,omitempty"`
Metadata map[string]any `json:"metadata,omitempty"`
}
var sender = newSender()
type asyncSender struct {
once sync.Once
url string
key string
ch chan event
http *http.Client
}
func newSender() *asyncSender {
return &asyncSender{ch: make(chan event, 128), http: &http.Client{Timeout: 3 * time.Second}}
}
// EmitSearch is fail-open and has no effect unless BRAIN_ACTIVITY_URL is set.
// It never blocks the ticket-processing path and silently drops telemetry when
// the optional visualization is unavailable or the local queue is full.
func EmitSearch(source, query string, hits []Hit, duration time.Duration) {
sender.once.Do(sender.start)
if sender.url == "" {
return
}
query = strings.TrimSpace(query)
if len([]rune(query)) > 4000 {
query = string([]rune(query)[:4000])
}
e := event{
Type: "knowledge.search", Source: source, Query: query,
Message: "Wissenssuche aus " + source,
Hits: hits, Metadata: map[string]any{"duration_ms": duration.Milliseconds(), "result_count": len(hits)},
}
select {
case sender.ch <- e:
default:
}
}
func (s *asyncSender) start() {
s.url = strings.TrimSpace(os.Getenv("BRAIN_ACTIVITY_URL"))
s.key = strings.TrimSpace(os.Getenv("BRAIN_ACTIVITY_API_KEY"))
if s.url == "" {
return
}
go func() {
for e := range s.ch {
b, err := json.Marshal(e)
if err != nil {
continue
}
req, err := http.NewRequest(http.MethodPost, s.url, bytes.NewReader(b))
if err != nil {
continue
}
req.Header.Set("Content-Type", "application/json")
if s.key != "" {
req.Header.Set("Authorization", "Bearer "+s.key)
}
resp, err := s.http.Do(req)
if err == nil {
_ = resp.Body.Close()
}
}
}()
}