Files
agent-desk-ng/internal/ai/rag/faq_content.go
mono 2f7a491666 feat: harden support, localize knowledge and add operator availability [skip ci]
Source-only publication of reviewed commit 64e96a7f423673dc29ad56485be9fc79bce8af1c.

Passed in isolation: focused frontend/backend tests, race checks, TypeScript typecheck, lint, Go compilation, Flowgram build, and deterministic SDK build/tests.

Not accepted as a production release: Next.js export/native assembly remains blocked by environment memory limits; browser interaction and MySQL staging acceptance remain unverified. No production server, database/schema, runtime configuration, deployment, release tag, or workflow execution was performed.

Expected source tree: 0a4a7528e9.
2026-10-16 15:21:42 +00:00

83 lines
3.0 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package rag
import (
"context"
"encoding/json"
"fmt"
"strings"
"agent-desk/internal/models"
"agent-desk/internal/pkg/i18nx"
)
// faqContent keeps application-owned labels separate from source text. Never
// translate, trim, or replace substrings in the source question or answer.
type faqContent struct {
question string
similarQuestions []string
answer string
}
func sourceFAQContent(faq models.KnowledgeFAQ) faqContent {
content := faqContent{question: faq.Question, answer: faq.Answer}
if faq.SimilarQuestions != "" {
_ = json.Unmarshal([]byte(faq.SimilarQuestions), &content.similarQuestions)
}
return content
}
func (content faqContent) render(locale string) string {
parts := []string{i18nx.Getf(locale, "knowledge.rag.faq.question", content.question)}
if len(content.similarQuestions) > 0 {
similar := joinNonEmptySimilarQuestions(content.similarQuestions, i18nx.Getf(locale, "knowledge.rag.faq.separator"))
parts = append(parts, i18nx.Getf(locale, "knowledge.rag.faq.similarQuestions", similar))
}
parts = append(parts, i18nx.Getf(locale, "knowledge.rag.faq.answer", content.answer))
return strings.Join(parts, "\n")
}
// Index generation deliberately uses the configured default, not the locale of
// whichever administrator requested a build. A locale read never re-embeds data.
func buildFAQChunkContent(faq models.KnowledgeFAQ) string {
return sourceFAQContent(faq).render(i18nx.DefaultLocale)
}
// legacyChinese renders the exact pre-localization serialization. This is a
// recognition format, not UI copy. Keep it frozen when catalog wording changes.
func (content faqContent) legacyChinese() string {
parts := []string{fmt.Sprintf("问题:%s", content.question)}
if len(content.similarQuestions) > 0 {
parts = append(parts, "相似问:"+joinNonEmptySimilarQuestions(content.similarQuestions, ";"))
}
parts = append(parts, "回答:"+content.answer)
return strings.Join(parts, "\n")
}
// Only a full byte-for-byte match against a generated representation of the
// current source proves which text belongs to us. Stale/manual/malformed chunks
// stay as recorded. In particular, do not infer labels by splitting on Chinese
// words: the source content may itself contain those words or arbitrary lines.
// This function is for live retrieval only, never historical log replay.
func renderVerifiedFAQChunkContent(ctx context.Context, stored string, faq models.KnowledgeFAQ) string {
content := sourceFAQContent(faq)
if stored == content.legacyChinese() {
return content.render(i18nx.LocaleFromContext(ctx))
}
for _, locale := range []string{i18nx.LocaleEnUS, i18nx.LocaleRuRU, i18nx.LocaleZhCN} {
if stored == content.render(locale) {
return content.render(i18nx.LocaleFromContext(ctx))
}
}
return stored
}
func joinNonEmptySimilarQuestions(items []string, separator string) string {
nonEmpty := make([]string, 0, len(items))
for _, item := range items {
if item != "" {
nonEmpty = append(nonEmpty, item)
}
}
return strings.Join(nonEmpty, separator)
}