91 lines
2.1 KiB
Go
91 lines
2.1 KiB
Go
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()
|
|
}
|
|
}
|
|
}()
|
|
}
|