feat: Go backend, enhanced search, new widgets, Docker deploy

Major changes:
- Add Go backend (backend/) with microservices architecture
- Enhanced master-agents-svc: reranker, content-classifier, stealth-crawler,
  proxy-manager, media-search, fastClassifier, language detection
- New web-svc widgets: KnowledgeCard, ProductCard, ProfileCard, VideoCard,
  UnifiedCard, CardGallery, InlineImageGallery, SourcesPanel, RelatedQuestions
- Improved discover-svc with discover-db integration
- Docker deployment improvements (Caddyfile, vendor.sh, BUILD.md)
- Library-svc: project_id schema migration
- Remove deprecated finance-svc and travel-svc
- Localization improvements across services

Made-with: Cursor
This commit is contained in:
home
2026-02-27 04:15:32 +03:00
parent 328d968f3f
commit 06fe57c765
285 changed files with 53132 additions and 1871 deletions

View File

@@ -0,0 +1,128 @@
package agent
import (
"context"
"github.com/gooseek/backend/internal/llm"
"github.com/gooseek/backend/internal/search"
"github.com/gooseek/backend/internal/session"
"github.com/gooseek/backend/internal/types"
"github.com/google/uuid"
)
type ResearchInput struct {
ChatHistory []llm.Message
FollowUp string
Classification *ClassificationResult
Mode Mode
Sources []string
Locale string
DetectedLang string
IsArticleSummary bool
}
func research(
ctx context.Context,
sess *session.Session,
llmClient llm.Client,
searchClient *search.SearXNGClient,
input ResearchInput,
) ([]types.Chunk, error) {
maxIterations := 1
switch input.Mode {
case ModeBalanced:
maxIterations = 3
case ModeQuality:
maxIterations = 10
}
researchBlockID := uuid.New().String()
sess.EmitBlock(types.NewResearchBlock(researchBlockID))
allResults := make([]types.Chunk, 0)
seenURLs := make(map[string]bool)
searchQuery := input.Classification.StandaloneFollowUp
if searchQuery == "" {
searchQuery = input.FollowUp
}
for i := 0; i < maxIterations; i++ {
queries := generateSearchQueries(searchQuery)
sess.UpdateBlock(researchBlockID, []session.Patch{
{
Op: "replace",
Path: "/data/subSteps",
Value: []types.ResearchSubStep{
{
ID: uuid.New().String(),
Type: "searching",
Searching: queries,
},
},
},
})
for _, q := range queries {
resp, err := searchClient.Search(ctx, q, &search.SearchOptions{
Categories: categoriesToSearch(input.Sources),
PageNo: 1,
})
if err != nil {
continue
}
for _, r := range resp.Results {
if r.URL != "" && !seenURLs[r.URL] {
seenURLs[r.URL] = true
allResults = append(allResults, r.ToChunk())
}
}
}
if input.Mode == ModeSpeed {
break
}
if len(allResults) >= 20 && input.Mode == ModeBalanced {
break
}
if len(allResults) >= 50 {
break
}
}
return allResults, nil
}
func categoriesToSearch(sources []string) []string {
if len(sources) == 0 {
return []string{"general", "news"}
}
categories := make([]string, 0)
for _, s := range sources {
switch s {
case "web":
categories = append(categories, "general")
case "discussions":
categories = append(categories, "social media")
case "academic":
categories = append(categories, "science")
case "news":
categories = append(categories, "news")
case "images":
categories = append(categories, "images")
case "videos":
categories = append(categories, "videos")
}
}
if len(categories) == 0 {
return []string{"general"}
}
return categories
}