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:
@@ -1,7 +1,9 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
FROM node:22-alpine AS builder
|
||||
WORKDIR /app
|
||||
COPY package*.json ./
|
||||
RUN npm install
|
||||
COPY --from=npm-cache / /tmp/npm-cache
|
||||
RUN npm install --cache /tmp/npm-cache --prefer-offline --no-audit
|
||||
COPY tsconfig.json ./
|
||||
COPY src ./src
|
||||
RUN npm run build
|
||||
@@ -9,7 +11,8 @@ RUN npm run build
|
||||
FROM node:22-alpine
|
||||
WORKDIR /app
|
||||
COPY package*.json ./
|
||||
RUN npm install --omit=dev
|
||||
COPY --from=npm-cache / /tmp/npm-cache
|
||||
RUN npm install --omit=dev --cache /tmp/npm-cache --prefer-offline --no-audit
|
||||
COPY --from=builder /app/dist ./dist
|
||||
EXPOSE 3005
|
||||
ENV DATA_DIR=/app/data
|
||||
|
||||
@@ -40,6 +40,7 @@ const PORT = parseInt(process.env.PORT ?? '3005', 10);
|
||||
const MEMORY_SVC_URL = process.env.MEMORY_SVC_URL ?? '';
|
||||
const LLM_SVC_URL = process.env.LLM_SVC_URL ?? '';
|
||||
const MASTER_AGENTS_SVC_URL = process.env.MASTER_AGENTS_SVC_URL?.trim() ?? '';
|
||||
const DISCOVER_SVC_URL = process.env.DISCOVER_SVC_URL?.trim() ?? '';
|
||||
|
||||
const messageSchema = z.object({
|
||||
messageId: z.string().min(1),
|
||||
@@ -237,7 +238,33 @@ app.post<{ Body: unknown }>('/api/v1/chat', async (req, reply) => {
|
||||
}
|
||||
}
|
||||
|
||||
const isDiscoverSummary =
|
||||
body.message.content.startsWith('Summary: ') &&
|
||||
body.message.content.length > 9;
|
||||
const summaryUrl = isDiscoverSummary
|
||||
? body.message.content.slice(9).trim()
|
||||
: '';
|
||||
|
||||
if (isDiscoverSummary) {
|
||||
req.log.info(
|
||||
{ summaryUrl: summaryUrl.slice(0, 80), hasDiscoverSvc: !!DISCOVER_SVC_URL },
|
||||
'Discover summary request'
|
||||
);
|
||||
|
||||
if (DISCOVER_SVC_URL) {
|
||||
fetch(`${DISCOVER_SVC_URL.replace(/\/$/, '')}/api/v1/discover/queue`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ url: summaryUrl, priority: Date.now() }),
|
||||
signal: AbortSignal.timeout(3000),
|
||||
}).catch(() => {});
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// Саммари статьи: всегда идём в master-agents (поиск по теме + саммари), не отдаём кэш.
|
||||
// Кэш сохраняется после ответа для перегенерации; при нажатии «перегенерировать» кэш очищается в web-svc.
|
||||
|
||||
if (!MASTER_AGENTS_SVC_URL) {
|
||||
return reply.status(503).send({ message: 'MASTER_AGENTS_SVC_URL not configured. master-agents-svc required for chat.' });
|
||||
}
|
||||
@@ -269,6 +296,85 @@ app.post<{ Body: unknown }>('/api/v1/chat', async (req, reply) => {
|
||||
const errText = await proxyRes.text();
|
||||
return reply.status(proxyRes.status).send({ message: errText || 'master-agents-svc error' });
|
||||
}
|
||||
|
||||
if (!proxyRes.body) {
|
||||
return reply.status(502).send({ message: 'No response body' });
|
||||
}
|
||||
|
||||
if (isDiscoverSummary && summaryUrl && DISCOVER_SVC_URL) {
|
||||
const collected: string[] = [];
|
||||
const reader = proxyRes.body.getReader();
|
||||
const decoder = new TextDecoder();
|
||||
const encoder = new TextEncoder();
|
||||
const discoverBase = DISCOVER_SVC_URL.replace(/\/$/, '');
|
||||
const stream = new ReadableStream({
|
||||
async start(controller) {
|
||||
let buffer = '';
|
||||
try {
|
||||
while (true) {
|
||||
const { value, done } = await reader.read();
|
||||
if (done) break;
|
||||
buffer += decoder.decode(value, { stream: true });
|
||||
const lines = buffer.split('\n');
|
||||
buffer = lines.pop() ?? '';
|
||||
for (const line of lines) {
|
||||
if (line.trim()) {
|
||||
collected.push(line);
|
||||
controller.enqueue(encoder.encode(line + '\n'));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (buffer.trim()) {
|
||||
collected.push(buffer);
|
||||
controller.enqueue(encoder.encode(buffer + '\n'));
|
||||
}
|
||||
} finally {
|
||||
if (collected.length > 0) {
|
||||
const urlToSave = summaryUrl;
|
||||
const eventsToSave = [...collected];
|
||||
req.log.info({ url: urlToSave.slice(0, 60), events: eventsToSave.length }, 'article-summary saving full payload to discover-svc');
|
||||
const maxRetries = 5;
|
||||
const retryDelayMs = 2000;
|
||||
let lastRes: Response | null = null;
|
||||
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
||||
try {
|
||||
const res = await fetch(`${discoverBase}/api/v1/discover/article-summary`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ url: urlToSave, events: eventsToSave }),
|
||||
signal: AbortSignal.timeout(120000),
|
||||
});
|
||||
lastRes = res;
|
||||
if (res.ok) {
|
||||
req.log.info({ url: urlToSave.slice(0, 60) }, 'article-summary saved');
|
||||
break;
|
||||
}
|
||||
if (res.status === 413) {
|
||||
req.log.warn({ url: urlToSave.slice(0, 60), attempt }, 'article-summary 413, retry full payload');
|
||||
} else {
|
||||
req.log.warn({ url: urlToSave.slice(0, 60), status: res.status, attempt }, 'article-summary save failed, retry');
|
||||
}
|
||||
} catch (e) {
|
||||
req.log.warn({ err: e, attempt }, 'article-summary save error, retry');
|
||||
}
|
||||
if (attempt < maxRetries) {
|
||||
await new Promise((r) => setTimeout(r, retryDelayMs));
|
||||
}
|
||||
}
|
||||
if (lastRes && !lastRes.ok) {
|
||||
req.log.warn({ url: urlToSave.slice(0, 60), status: lastRes.status }, 'article-summary save failed after all retries');
|
||||
}
|
||||
}
|
||||
controller.close();
|
||||
}
|
||||
},
|
||||
});
|
||||
return reply
|
||||
.header('Content-Type', 'application/x-ndjson')
|
||||
.header('Cache-Control', 'no-cache')
|
||||
.send(stream);
|
||||
}
|
||||
|
||||
return reply
|
||||
.header('Content-Type', 'application/x-ndjson')
|
||||
.header('Cache-Control', 'no-cache')
|
||||
|
||||
Reference in New Issue
Block a user