package engine import ( "context" "errors" "fmt" "log/slog" "strings" "time" "github.com/local/glpi-neural-brain/internal/model" "github.com/local/glpi-neural-brain/internal/research" ) type ResearchTestResult struct { OK bool `json:"ok"` Query string `json:"query"` Diagnostic research.Diagnostic `json:"diagnostic"` Results []model.ResearchResult `json:"results"` } func (e *Engine) ResearchStatus() research.Diagnostic { if e == nil || e.Research == nil { return research.Diagnostic{Configured: false, OK: false, ErrorKind: "not_configured", Error: "SearXNG ist nicht konfiguriert"} } status := e.Research.Status() if !e.ResearchEnabledForRuntime() { status.OK = false status.ErrorKind = "disabled" status.Error = "BRAIN_RESEARCH_ENABLED und Autonomous Research sind deaktiviert" } return status } func (e *Engine) TestResearch(ctx context.Context, query string, limit int) (ResearchTestResult, error) { query = sanitizeSearchQuerySiteFilters(strings.TrimSpace(query)) if query == "" { query = "Btrfs Snapshots und ZFS History Unterschiede Timeline" } if limit < 1 { limit = 4 } if limit > 10 { limit = 10 } if !e.ResearchEnabledForRuntime() { err := errors.New("SearXNG-Recherche ist deaktiviert") return ResearchTestResult{Query: query, Diagnostic: e.ResearchStatus()}, err } if e.Research == nil { err := errors.New("SearXNG-Client ist nicht konfiguriert") return ResearchTestResult{Query: query, Diagnostic: e.ResearchStatus()}, err } researchID := newResearchRunID("research-test", query) started := time.Now() startMetadata := map[string]any{ "trigger": "diagnostic", "research_id": researchID, "research_query": query, "animation_min_ms": 2000, "test_only": true, "searxng_base_url": e.Research.Status().BaseURL, } e.Broker.Publish(model.Activity{Type: "research.test.started", Source: "searxng", Phase: "diagnostic", Message: "SearXNG-Verbindung und JSON-Suche werden direkt getestet", Strength: .92, Metadata: startMetadata}) var results []model.ResearchResult var diagnostic research.Diagnostic err := e.withSharedResearchWork(ctx, "searxng.diagnostic", func() error { var searchErr error results, diagnostic, searchErr = e.Research.SearchDetailed(ctx, query, limit) return searchErr }) if err != nil { metadata := mergeResearchMetadata(startMetadata, researchDiagnosticMetadata(diagnostic)) metadata["duration_ms"] = time.Since(started).Milliseconds() metadata["error"] = err.Error() slog.Warn("SearXNG diagnostic search failed", "base_url", diagnostic.BaseURL, "kind", diagnostic.ErrorKind, "duration_ms", diagnostic.DurationMS, "error", err) e.Broker.Publish(model.Activity{Type: "research.test.failed", Source: "searxng", Phase: "diagnostic", Message: "SearXNG-Test fehlgeschlagen", Strength: .4, Metadata: metadata}) return ResearchTestResult{OK: false, Query: query, Diagnostic: diagnostic}, err } metadata := mergeResearchMetadata(researchEventMetadata("diagnostic", researchID, query, results, time.Since(started)), researchDiagnosticMetadata(diagnostic)) metadata["test_only"] = true message := fmt.Sprintf("SearXNG-Test erfolgreich · %d verwertbare Treffer", len(results)) if len(results) == 0 { message = "SearXNG-Test erfolgreich · JSON-Antwort erhalten, aber keine verwertbaren Treffer" } e.Broker.Publish(model.Activity{Type: "research.test.results", Source: "searxng", Phase: "diagnostic", Message: message, Strength: 1, Metadata: metadata}) return ResearchTestResult{OK: true, Query: query, Diagnostic: diagnostic, Results: results}, nil } func researchDiagnosticMetadata(diagnostic research.Diagnostic) map[string]any { metadata := map[string]any{ "searxng_configured": diagnostic.Configured, "searxng_ok": diagnostic.OK, "searxng_base_url": diagnostic.BaseURL, "searxng_http_status": diagnostic.HTTPStatus, "searxng_content_type": diagnostic.ContentType, "error_kind": diagnostic.ErrorKind, } if diagnostic.DurationMS > 0 { metadata["duration_ms"] = diagnostic.DurationMS } if diagnostic.Error != "" { metadata["error"] = diagnostic.Error } return metadata }