Files
teknav-bak/app/backend/src/routes/rag-gateway.ts
T
arsam dd3a702c37 Import source: app/backend/src/routes/rag-gateway
Import the existing local project snapshot. This commit organizes existing files; it does not represent newly developed functionality.
2026-09-18 23:20:36 +03:30

52 lines
2.0 KiB
TypeScript

import type { FastifyPluginAsync, FastifyRequest } from 'fastify';
import { z } from 'zod';
import { prisma } from '../db.js';
import { checkRateLimit, rateLimitResponse } from '../lib/rate-limit.js';
function stripHtmlToMarkdown(html: string): string {
if (!html) return '';
return html
.replace(/<h[1-6][^>]*>(.*?)<\/h[1-6]>/gi, '\n## $1\n')
.replace(/<p[^>]*>(.*?)<\/p>/gi, '\n$1\n')
.replace(/<a[^>]*href="([^"]+)"[^>]*>(.*?)<\/a>/gi, '[$2]($1)')
.replace(/<strong[^>]*>(.*?)<\/strong>/gi, '**$1**')
.replace(/<em[^>]*>(.*?)<\/em>/gi, '*$1*')
.replace(/<[^>]+>/g, '')
.replace(/\n\s*\n/g, '\n\n')
.trim();
}
export const ragRoutes: FastifyPluginAsync = async (app) => {
app.get('/api/v1/rag/:slug', async (req, reply) => {
const { slug } = req.params as { slug: string };
const article = await prisma.article.findUnique({
where: { slug },
select: { title: true, summary: true, content: true, dateModified: true }
});
if (!article) return reply.code(404).send('# Not Found');
reply.header('Content-Type', 'text/markdown; charset=utf-8');
let markdown = `# ${article.title}\n\n`;
if (article.summary) markdown += `> ${article.summary}\n\n`;
markdown += `Last Modified: ${article.dateModified?.toISOString()}\n\n`;
markdown += stripHtmlToMarkdown(article.content);
return markdown;
});
app.post('/api/v1/search/semantic', async (req: FastifyRequest, reply) => {
const rl = await checkRateLimit(req.ip, 'semantic', 10, 60);
if (rl.limited) return rateLimitResponse(reply, rl.retryAfter);
const schema = z.object({ query: z.string().min(1), limit: z.number().max(20).optional() });
const parsed = schema.safeParse(req.body);
if (!parsed.success) return reply.code(400).send({ error: 'invalid_input' });
const { semanticSearch } = await import('../lib/embeddings.js');
const results = await semanticSearch(parsed.data.query, parsed.data.limit || 5);
return { results };
});
};