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,177 @@
'use client';
import { useState, useCallback, useRef } from 'react';
import type { Message, Citation, Widget, StreamEvent } from '../types';
import { streamChat, generateId } from '../api';
interface UseChatOptions {
onError?: (error: Error) => void;
}
export function useChat(options: UseChatOptions = {}) {
const [messages, setMessages] = useState<Message[]>([]);
const [isLoading, setIsLoading] = useState(false);
const [citations, setCitations] = useState<Citation[]>([]);
const [widgets, setWidgets] = useState<Widget[]>([]);
const abortControllerRef = useRef<AbortController | null>(null);
const sendMessage = useCallback(async (content: string, mode: 'speed' | 'balanced' | 'quality' = 'balanced') => {
if (!content.trim() || isLoading) return;
const userMessage: Message = {
id: generateId(),
role: 'user',
content: content.trim(),
createdAt: new Date(),
};
const assistantMessage: Message = {
id: generateId(),
role: 'assistant',
content: '',
citations: [],
widgets: [],
isStreaming: true,
createdAt: new Date(),
};
setMessages((prev) => [...prev, userMessage, assistantMessage]);
setIsLoading(true);
setCitations([]);
setWidgets([]);
const history: [string, string][] = messages
.filter((m) => !m.isStreaming)
.reduce((acc, m, i, arr) => {
if (m.role === 'user' && arr[i + 1]?.role === 'assistant') {
acc.push([m.content, arr[i + 1].content]);
}
return acc;
}, [] as [string, string][]);
try {
const stream = streamChat({
message: {
messageId: assistantMessage.id,
chatId: userMessage.id,
content: content.trim(),
},
optimizationMode: mode,
history,
locale: 'ru',
});
let fullContent = '';
const collectedCitations: Citation[] = [];
const collectedWidgets: Widget[] = [];
for await (const event of stream) {
switch (event.type) {
case 'messageStart':
break;
case 'message':
if (event.data && typeof event.data === 'object' && 'content' in event.data) {
const chunk = (event.data as { content: string }).content;
fullContent += chunk;
setMessages((prev) =>
prev.map((m) =>
m.id === assistantMessage.id
? { ...m, content: fullContent }
: m
)
);
}
break;
case 'sources':
if (event.data && Array.isArray(event.data)) {
const sources = event.data as Array<{
url: string;
title?: string;
metadata?: { title?: string };
}>;
sources.forEach((source, idx) => {
const citation: Citation = {
index: idx + 1,
url: source.url,
title: source.title || source.metadata?.title || new URL(source.url).hostname,
domain: new URL(source.url).hostname,
};
collectedCitations.push(citation);
});
setCitations([...collectedCitations]);
}
break;
case 'widget':
if (event.data && typeof event.data === 'object') {
const widget = event.data as Widget;
collectedWidgets.push(widget);
setWidgets([...collectedWidgets]);
}
break;
case 'messageEnd':
setMessages((prev) =>
prev.map((m) =>
m.id === assistantMessage.id
? {
...m,
content: fullContent,
citations: collectedCitations,
widgets: collectedWidgets,
isStreaming: false,
}
: m
)
);
break;
case 'error':
throw new Error(
event.data && typeof event.data === 'object' && 'message' in event.data
? String((event.data as { message: string }).message)
: 'Unknown error'
);
}
}
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Ошибка при отправке сообщения';
setMessages((prev) =>
prev.map((m) =>
m.id === assistantMessage.id
? { ...m, content: `Ошибка: ${errorMessage}`, isStreaming: false }
: m
)
);
options.onError?.(error instanceof Error ? error : new Error(errorMessage));
} finally {
setIsLoading(false);
}
}, [isLoading, messages, options]);
const stopGeneration = useCallback(() => {
abortControllerRef.current?.abort();
setIsLoading(false);
setMessages((prev) =>
prev.map((m) => (m.isStreaming ? { ...m, isStreaming: false } : m))
);
}, []);
const clearMessages = useCallback(() => {
setMessages([]);
setCitations([]);
setWidgets([]);
}, []);
return {
messages,
isLoading,
citations,
widgets,
sendMessage,
stopGeneration,
clearMessages,
};
}