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

@@ -13,6 +13,7 @@
"@fastify/cors": "^9.0.1",
"@toolsycc/json-repair": "^0.1.22",
"fastify": "^4.28.1",
"franc": "^6.2.0",
"ollama": "^0.6.3",
"openai": "^6.9.0",
"zod": "^4.1.12"

View File

@@ -6,6 +6,13 @@ import type { ChatTurnMessage } from '../types.js';
type Input = { chatHistory: ChatTurnMessage[]; locale?: string };
function getLastUserContent(history: ChatTurnMessage[]): string {
for (let i = history.length - 1; i >= 0; i--) {
if (history[i].role === 'user') return history[i].content ?? '';
}
return '';
}
export async function generateSuggestions(input: Input, llm: BaseLLM): Promise<string[]> {
const schema = z.object({
suggestions: z.array(z.string()).describe('List of suggested questions or prompts'),
@@ -14,7 +21,7 @@ export async function generateSuggestions(input: Input, llm: BaseLLM): Promise<s
const res = await llm.generateObject<{ suggestions: string[] }>({
schema,
messages: [
{ role: 'system', content: getSuggestionGeneratorPrompt(input.locale) },
{ role: 'system', content: getSuggestionGeneratorPrompt(undefined, undefined) },
{
role: 'user',
content: `<chat_history>\n${formatChatHistoryAsString(input.chatHistory)}\n</chat_history>`,

View File

@@ -0,0 +1,29 @@
import { franc } from 'franc';
/** ISO 639-3 (franc) → human-readable language name */
const ISO6393_TO_LANGUAGE: Record<string, string> = {
rus: 'Russian', eng: 'English', deu: 'German', fra: 'French', spa: 'Spanish',
ita: 'Italian', por: 'Portuguese', ukr: 'Ukrainian', pol: 'Polish', cmn: 'Chinese',
jpn: 'Japanese', kor: 'Korean', arb: 'Arabic', tur: 'Turkish', bel: 'Belarusian',
kaz: 'Kazakh', swe: 'Swedish', nob: 'Norwegian', dan: 'Danish', fin: 'Finnish',
ces: 'Czech', slk: 'Slovak', hun: 'Hungarian', ron: 'Romanian', bul: 'Bulgarian',
hrv: 'Croatian', srp: 'Serbian', ell: 'Greek', hin: 'Hindi', tha: 'Thai',
vie: 'Vietnamese', ind: 'Indonesian', zlm: 'Malay', heb: 'Hebrew', pes: 'Persian',
nld: 'Dutch',
};
/** Fallback instruction when language cannot be determined */
export const FALLBACK_RESPONSE_LANGUAGE = `
<response_language>
Always respond in the same language as the user's query. Detect the language from the conversation and reply accordingly. If the user wrote in Russian, respond in Russian; if in English, respond in English; and so on.
</response_language>`;
/**
* Detect language of text using franc. Returns human-readable language name or undefined if undetermined.
*/
export function detectLanguage(text: string): string | undefined {
if (!text || text.trim().length < 2) return undefined;
const code = franc(text.trim(), { minLength: 2 });
if (code === 'und') return undefined;
return ISO6393_TO_LANGUAGE[code] ?? code;
}

View File

@@ -1,21 +1,17 @@
const LOCALE_TO_LANGUAGE: Record<string, string> = {
ru: 'Russian',
en: 'English',
de: 'German',
fr: 'French',
es: 'Spanish',
it: 'Italian',
pt: 'Portuguese',
uk: 'Ukrainian',
pl: 'Polish',
zh: 'Chinese',
ja: 'Japanese',
ko: 'Korean',
};
/** Принудительно русский язык везде */
const RUSSIAN_RESPONSE_LANGUAGE = `
<response_language>
Always respond ONLY in Russian. Regardless of the user's query language, format your entire response in Russian.
</response_language>`;
export function getLocaleInstruction(locale?: string): string {
if (!locale) return '';
const lang = locale.split('-')[0];
const name = LOCALE_TO_LANGUAGE[lang] ?? lang;
return `\n<response_language>\nUser's locale is ${locale}. Always format your response in ${name}.\n</response_language>`;
export const FALLBACK_RESPONSE_LANGUAGE = RUSSIAN_RESPONSE_LANGUAGE;
/**
* Build response language instruction. Always Russian.
*/
export function getLocaleInstruction(
_locale?: string,
_detectedLanguage?: string,
): string {
return RUSSIAN_RESPONSE_LANGUAGE;
}

View File

@@ -12,6 +12,6 @@ Rules:
Today's date is ${new Date().toISOString()}
`;
export function getSuggestionGeneratorPrompt(locale?: string): string {
return base + getLocaleInstruction(locale);
export function getSuggestionGeneratorPrompt(locale?: string, detectedLanguage?: string): string {
return base + getLocaleInstruction(locale, detectedLanguage);
}