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

@@ -1,7 +1,9 @@
# syntax=docker/dockerfile:1
FROM node:22-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
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 ci --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 3006
CMD ["node", "dist/index.js"]

View File

@@ -33,11 +33,28 @@ const STUB_COLLECTIONS = [
{ id: 'sp-transcripts', title: 'S&P 500 Transcripts', category: 'finance', description: 'Earnings call transcripts' },
];
/** Popular Space templates — Perplexity-style coverage */
const STUB_TEMPLATES = [
{ id: 'research-assistant', title: 'Research Assistant', category: 'research', description: 'Deep research, synthesis, sources' },
{ id: 'writing-editing', title: 'Writing & Editing', category: 'writing', description: 'Drafts, proofreading, style' },
{ id: 'code-assistant', title: 'Code Assistant', category: 'coding', description: 'Debug, explain, refactor code' },
{ id: 'finance-research', title: 'Finance Research', category: 'finance', description: 'Market analysis, earnings, SEC filings' },
{ id: 'product-docs', title: 'Product Documentation', category: 'product', description: 'Technical docs, specs, roadmaps' },
{ id: 'marketing-campaigns', title: 'Marketing Campaigns', category: 'marketing', description: 'Campaign briefs, analytics, copy' },
{ id: 'travel-planning', title: 'Travel Planning', category: 'travel', description: 'Itineraries, hotels, destinations' },
{ id: 'academic-research', title: 'Academic Research', category: 'academic', description: 'Papers, citations, literature review' },
{ id: 'legal-research', title: 'Legal Research', category: 'legal', description: 'Case law, contracts, compliance' },
{ id: 'healthcare-assistant', title: 'Healthcare Assistant', category: 'healthcare', description: 'Medical info, summaries, protocols' },
{ id: 'hr-recruiting', title: 'HR & Recruiting', category: 'hr', description: 'Job descriptions, screening, onboarding' },
{ id: 'sales-assistant', title: 'Sales Assistant', category: 'sales', description: 'Pitch decks, outreach, CRM' },
{ id: 'data-analysis', title: 'Data Analysis', category: 'data', description: 'Stats, insights, visualizations' },
{ id: 'content-creation', title: 'Content Creation', category: 'content', description: 'Blog posts, social, SEO' },
{ id: 'job-search', title: 'Job Search', category: 'career', description: 'Resume, cover letters, interviews' },
{ id: 'competitive-intel', title: 'Competitive Intelligence', category: 'research', description: 'Competitors, market landscape' },
{ id: 'due-diligence', title: 'Due Diligence', category: 'finance', description: 'M&A, investments, valuations' },
{ id: 'grant-writing', title: 'Grant Writing', category: 'writing', description: 'Proposals, budgets, narratives' },
{ id: 'customer-support', title: 'Customer Support', category: 'support', description: 'Tickets, FAQs, escalation' },
{ id: 'project-management', title: 'Project Management', category: 'product', description: 'Roadmaps, timelines, standups' },
];
const app = Fastify({ logger: true });
@@ -69,19 +86,89 @@ app.get<{ Querystring: { category?: string } }>('/api/v1/templates', async (req,
return { items };
});
/** In-memory projects storage (userId -> Map<projectId, project>) */
const projectsStore = new Map<
string,
Map<
string,
{ id: string; userId: string; title: string; description: string; links: string[]; instructions: string; createdAt: string }
>
>();
function getOrCreateUserProjects(userId: string) {
if (!projectsStore.has(userId)) {
projectsStore.set(userId, new Map());
}
return projectsStore.get(userId)!;
}
app.get('/api/v1/projects', async (req, reply) => {
const userId = await getUserIdFromToken(req.headers.authorization);
if (!userId) return reply.status(401).send({ error: 'Authorization required' });
return reply.send({ items: [] });
const userProjects = getOrCreateUserProjects(userId);
const items = Array.from(userProjects.values()).sort(
(a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
);
return reply.send({ items });
});
app.post('/api/v1/projects', async (req, reply) => {
app.get<{ Params: { id: string } }>('/api/v1/projects/:id', async (req, reply) => {
const userId = await getUserIdFromToken(req.headers.authorization);
if (!userId) return reply.status(401).send({ error: 'Authorization required' });
return reply.status(501).send({
error: 'Not implemented',
message: 'Projects CRUD will be available in a future release',
});
const userProjects = getOrCreateUserProjects(userId);
const project = userProjects.get(req.params.id);
if (!project) return reply.status(404).send({ error: 'Project not found' });
const p = project as { instructions?: string };
if (p.instructions === undefined) p.instructions = '';
return reply.send(project);
});
app.post<{
Body: { id?: string; title: string; description?: string };
}>('/api/v1/projects', async (req, reply) => {
const userId = await getUserIdFromToken(req.headers.authorization);
if (!userId) return reply.status(401).send({ error: 'Authorization required' });
const { id, title, description = '' } = req.body ?? {};
const projectId = id ?? crypto.randomUUID();
const now = new Date().toISOString();
const project = {
id: projectId,
userId,
title: (title ?? 'Новый проект').trim(),
description: (description ?? '').trim(),
links: [] as string[],
instructions: '',
createdAt: now,
};
getOrCreateUserProjects(userId).set(projectId, project);
return reply.send(project);
});
app.patch<{
Params: { id: string };
Body: { title?: string; description?: string; links?: string[]; instructions?: string };
}>('/api/v1/projects/:id', async (req, reply) => {
const userId = await getUserIdFromToken(req.headers.authorization);
if (!userId) return reply.status(401).send({ error: 'Authorization required' });
const userProjects = getOrCreateUserProjects(userId);
const project = userProjects.get(req.params.id);
if (!project) return reply.status(404).send({ error: 'Project not found' });
if (!('instructions' in project)) (project as { instructions?: string }).instructions = '';
const { title, description, links, instructions } = req.body ?? {};
if (title !== undefined) project.title = title.trim();
if (description !== undefined) project.description = description.trim();
if (links !== undefined) project.links = Array.isArray(links) ? links : project.links;
if (instructions !== undefined) project.instructions = typeof instructions === 'string' ? instructions : project.instructions;
return reply.send(project);
});
app.delete<{ Params: { id: string } }>('/api/v1/projects/:id', async (req, reply) => {
const userId = await getUserIdFromToken(req.headers.authorization);
if (!userId) return reply.status(401).send({ error: 'Authorization required' });
const userProjects = getOrCreateUserProjects(userId);
if (!userProjects.has(req.params.id)) return reply.status(404).send({ error: 'Project not found' });
userProjects.delete(req.params.id);
return reply.send({ ok: true });
});
/**