Files
YuanShiJiLoong aa6b74d450 feat: 添加 .env.example 配置示例 & 全环境变量支持
- 新增 .env.example 配置模板,含使用教程和所有供应商参考
- 所有 API 路由支持环境变量 fallback (API_KEY/GEMINI_API_KEY/CLAUDE_API_KEY/OPENAI_API_KEY 等)
- 支持自定义端口 (PORT 环境变量)
- Electron 启动时自动加载 .env.local
- .gitignore 排除 .env.example 以确保模板被追踪
2026-03-03 15:42:19 +09:00

148 lines
6.4 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// OpenAI 兼容 API — SSE 流式转发(Edge Runtime 确保流式不被缓冲)
// 支持智谱、DeepSeek、OpenAI、Moonshot 等所有兼容 OpenAI 格式的 API
export const runtime = 'edge';
export async function POST(request) {
try {
const { systemPrompt, userPrompt, apiConfig, maxTokens, temperature, topP, reasoningEffort } = await request.json();
const apiKey = apiConfig?.apiKey || process.env.API_KEY || process.env.ZHIPU_API_KEY;
const baseUrl = apiConfig?.baseUrl || process.env.API_BASE_URL || 'https://open.bigmodel.cn/api/paas/v4';
const model = apiConfig?.model || process.env.API_MODEL || 'glm-4-flash';
if (!apiKey) {
return new Response(
JSON.stringify({ error: '请先配置 API Key。点击左下角 ⚙️ → API配置,填入你的 Key' }),
{ status: 400, headers: { 'Content-Type': 'application/json' } }
);
}
const url = `${baseUrl.replace(/\/$/, '')}/chat/completions`;
// 请求上游 API,开启 stream 模式
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${apiKey}`,
},
body: JSON.stringify({
model,
messages: [
{ role: 'system', content: systemPrompt },
{ role: 'user', content: userPrompt }
],
...(temperature != null ? { temperature } : {}),
...(topP != null ? { top_p: topP } : {}),
...(maxTokens ? { max_tokens: maxTokens } : {}),
...(reasoningEffort && reasoningEffort !== 'auto' ? { reasoning_effort: reasoningEffort } : {}),
stream: true,
}),
});
if (!response.ok) {
const errorText = await response.text();
console.error('API错误:', response.status, errorText);
const errorMessages = {
401: 'API Key 无效或已过期,请检查后重新填写',
429: '请求频率过高或额度不足,请稍后再试',
};
const errMsg = errorMessages[response.status]
|| `AI服务返回错误(${response.status}),请检查 API 配置`;
return new Response(
JSON.stringify({ error: errMsg }),
{ status: response.status, headers: { 'Content-Type': 'application/json' } }
);
}
// 将上游 SSE 流透传给前端
const encoder = new TextEncoder();
const decoder = new TextDecoder();
const stream = new ReadableStream({
async start(controller) {
const reader = response.body.getReader();
let buffer = '';
try {
while (true) {
const { done, value } = 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) {
const trimmed = line.trim();
if (!trimmed || trimmed.startsWith(':')) continue;
if (trimmed === 'data: [DONE]') {
controller.enqueue(encoder.encode('data: [DONE]\n\n'));
continue;
}
if (trimmed.startsWith('data: ')) {
try {
const json = JSON.parse(trimmed.slice(6));
const delta = json.choices?.[0]?.delta;
// 转发思维链内容(DeepSeek reasoning_content)
const reasoning = delta?.reasoning_content;
if (reasoning) {
controller.enqueue(encoder.encode(`data: ${JSON.stringify({ thinking: reasoning })}\n\n`));
}
// 转发文本 delta
const content = delta?.content;
if (content) {
controller.enqueue(encoder.encode(`data: ${JSON.stringify({ text: content })}\n\n`));
}
// 检查是否包含 usage 信息(某些 API 在最后一个事件中返回)
if (json.usage) {
controller.enqueue(encoder.encode(`data: ${JSON.stringify({
usage: {
promptTokens: json.usage.prompt_tokens || 0,
completionTokens: json.usage.completion_tokens || 0,
totalTokens: json.usage.total_tokens || 0,
}
})}\n\n`));
}
} catch {
// 解析失败的行直接跳过
}
}
}
}
} catch (err) {
// 客户端断开等情况
console.error('Stream 读取错误:', err.message);
} finally {
controller.close();
reader.releaseLock();
}
}
});
return new Response(stream, {
headers: {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
},
});
} catch (error) {
console.error('AI接口错误:', error);
return new Response(
JSON.stringify({ error: '网络连接失败,请检查 API 地址是否正确' }),
{ status: 500, headers: { 'Content-Type': 'application/json' } }
);
}
}