包含前端源码、预构建产物(dist/)、零依赖 Node.js 服务器(server.mjs)、 Cloudflare Worker 版本(worker/)、部署文档(DEPLOY.md)和交接文档。 技术总监可直接 clone 后运行 node server.mjs 启动,无需安装 React 或 build。 DEEPSEEK_API_KEY 通过环境变量注入,未纳入仓库。
79 lines
3.9 KiB
TypeScript
79 lines
3.9 KiB
TypeScript
import { defineConfig, loadEnv, type Plugin } from "vite";
|
||
import react from "@vitejs/plugin-react";
|
||
|
||
const SYSTEM_PROMPT = `你是一名数字脑健康表现报告助手。根据用户完成游戏时产生的结构化数据,生成通俗、克制、非医疗化的中文报告。
|
||
必须遵守:
|
||
1. 不诊断疾病,不使用确诊、患病、痴呆、障碍等医疗结论;
|
||
2. 只描述本次任务表现,明确设备、环境、熟悉程度可能影响结果;禁止使用“提示存在问题或挑战”“异常”“受损”等暗示医学结论的说法;
|
||
3. 建议必须低风险、可执行,不推荐药物或医疗处置;
|
||
4. 优先使用“本次反应相对偏慢”“注意表现有波动”“容易遗漏部分信息”“仍有提升空间”等表达;
|
||
5. 仅输出合法 JSON,不要 Markdown。
|
||
JSON 结构:{"summary":"80字内总结","strength":"60字内相对优势","opportunity":"60字内提升空间","recommendations":["建议1","建议2","建议3"]}`;
|
||
|
||
function localDeepSeekApi(apiKey: string): Plugin {
|
||
return {
|
||
name: "braincheck-deepseek-local-api",
|
||
configureServer(server) {
|
||
server.middlewares.use("/api/analyze", async (request, response) => {
|
||
if (request.method !== "POST") {
|
||
response.statusCode = 405;
|
||
response.end("Method not allowed");
|
||
return;
|
||
}
|
||
if (!apiKey) {
|
||
response.statusCode = 503;
|
||
response.setHeader("content-type", "application/json");
|
||
response.end(JSON.stringify({ error: "DEEPSEEK_API_KEY is not configured" }));
|
||
return;
|
||
}
|
||
try {
|
||
let raw = "";
|
||
for await (const chunk of request) raw += chunk;
|
||
const assessment = JSON.parse(raw || "{}");
|
||
const upstream = await fetch("https://api.deepseek.com/chat/completions", {
|
||
method: "POST",
|
||
headers: { "content-type": "application/json", authorization: `Bearer ${apiKey}` },
|
||
body: JSON.stringify({
|
||
model: "deepseek-chat",
|
||
temperature: 0.35,
|
||
response_format: { type: "json_object" },
|
||
messages: [
|
||
{ role: "system", content: SYSTEM_PROMPT },
|
||
{ role: "user", content: JSON.stringify({ profile: assessment.profile, metrics: assessment.metrics, eventCount: assessment.events?.length ?? 0 }) },
|
||
],
|
||
}),
|
||
signal: AbortSignal.timeout(15000),
|
||
});
|
||
if (!upstream.ok) throw new Error(`DeepSeek returned ${upstream.status}`);
|
||
const payload = await upstream.json();
|
||
const content = payload?.choices?.[0]?.message?.content;
|
||
if (!content) throw new Error("DeepSeek returned an empty response");
|
||
const rawReport = JSON.parse(content.replace(/^```json\s*|\s*```$/g, ""));
|
||
const parsed = {
|
||
summary: String(rawReport.summary || "本次四项任务已经完成。"),
|
||
strength: String(rawReport.strength || "部分任务表现相对稳定。"),
|
||
opportunity: String(rawReport.opportunity || "仍有可以通过规律练习提升的空间。"),
|
||
recommendations: Array.isArray(rawReport.recommendations) ? rawReport.recommendations.slice(0, 3).map(String) : [],
|
||
};
|
||
response.statusCode = 200;
|
||
response.setHeader("content-type", "application/json; charset=utf-8");
|
||
response.end(JSON.stringify(parsed));
|
||
} catch (error) {
|
||
response.statusCode = 502;
|
||
response.setHeader("content-type", "application/json");
|
||
response.end(JSON.stringify({ error: error instanceof Error ? error.message : "AI request failed" }));
|
||
}
|
||
});
|
||
},
|
||
};
|
||
}
|
||
|
||
export default defineConfig(({ mode }) => {
|
||
const env = loadEnv(mode, process.cwd(), "");
|
||
return {
|
||
build: { outDir: "dist/client" },
|
||
server: { host: "0.0.0.0", allowedHosts: ["terminal.local"] },
|
||
plugins: [react(), localDeepSeekApi(env.DEEPSEEK_API_KEY)],
|
||
};
|
||
});
|