All checks were successful
release-tag / release-image (push) Successful in 2m19s
135 lines
3.5 KiB
Diff
135 lines
3.5 KiB
Diff
diff --git a/cmd/server/app.go b/cmd/server/app.go
|
|
index 158e10b..a85413d 100644
|
|
--- a/cmd/server/app.go
|
|
+++ b/cmd/server/app.go
|
|
@@ -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) {
|
|
diff --git a/internal/brainactivity/client.go b/internal/brainactivity/client.go
|
|
new file mode 100644
|
|
index 0000000..fb16e0a
|
|
--- /dev/null
|
|
+++ b/internal/brainactivity/client.go
|
|
@@ -0,0 +1,96 @@
|
|
+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])
|
|
+ }
|
|
+ eventType := "knowledge.search"
|
|
+ message := "Wissenssuche aus " + source
|
|
+ if len(hits) == 0 {
|
|
+ eventType = "knowledge.search.empty"
|
|
+ message = "Wissenssuche ohne Treffer aus " + source
|
|
+ }
|
|
+ e := event{
|
|
+ Type: eventType, Source: source, Query: query,
|
|
+ Message: message,
|
|
+ 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()
|
|
+ }
|
|
+ }
|
|
+ }()
|
|
+}
|