diff --git a/src/routes/ai.ts b/src/routes/ai.ts index 5ca68b9..bbef803 100644 --- a/src/routes/ai.ts +++ b/src/routes/ai.ts @@ -5,6 +5,7 @@ import type { FastifyInstance } from "fastify"; import { isAvailable, loadConfig } from "../llm/client.js"; import { createTask, getTask, type TaskType } from "../task-queue.js"; +import { logStore } from "../admin/store.js"; export async function aiRoutes(app: FastifyInstance): Promise { const config = loadConfig(); @@ -39,4 +40,48 @@ export async function aiRoutes(app: FastifyInstance): Promise { } return task; }); + + /** 抓取网页标题 */ + app.post<{ Body: { url: string } }>("/fetch-title", async (request, reply) => { + const { url } = request.body ?? {}; + const t0 = Date.now(); + + if (!url || typeof url !== "string" || (!url.startsWith("http://") && !url.startsWith("https://"))) { + return reply.status(400).send({ error: "无效的 URL" }); + } + + try { + const controller = new AbortController(); + const timer = setTimeout(() => controller.abort(), 10_000); + const response = await fetch(url, { signal: controller.signal }); + clearTimeout(timer); + + const html = await response.text(); + const match = html.match(/]*>([^<]+)<\/title>/i); + const title = match ? match[1].trim() : null; + + logStore.add({ + timestamp: new Date().toISOString(), + endpoint: "/fetch-title", + model: "", + durationMs: Date.now() - t0, + status: 200, + requestBody: { url }, + responseBody: { url, title }, + }); + return { url, title }; + } catch (err) { + logStore.add({ + timestamp: new Date().toISOString(), + endpoint: "/fetch-title", + model: "", + durationMs: Date.now() - t0, + status: 502, + requestBody: { url }, + responseBody: { url, title: null }, + error: err instanceof Error ? err.message : String(err), + }); + return { url, title: null }; + } + }); }