From aa6b74d450b65d5d7441ca5e231f0cb20003cc7b Mon Sep 17 00:00:00 2001 From: YuanShiJiLoong <2450829984@qq.com> Date: Tue, 3 Mar 2026 15:42:19 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=20.env.example=20?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E7=A4=BA=E4=BE=8B=20&=20=E5=85=A8=E7=8E=AF?= =?UTF-8?q?=E5=A2=83=E5=8F=98=E9=87=8F=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 .env.example 配置模板,含使用教程和所有供应商参考 - 所有 API 路由支持环境变量 fallback (API_KEY/GEMINI_API_KEY/CLAUDE_API_KEY/OPENAI_API_KEY 等) - 支持自定义端口 (PORT 环境变量) - Electron 启动时自动加载 .env.local - .gitignore 排除 .env.example 以确保模板被追踪 --- .env.example | 110 ++++++++++++++++++++++++++++++++++ .gitignore | Bin 526 -> 540 bytes app/api/ai/claude/route.js | 6 +- app/api/ai/gemini/route.js | 4 +- app/api/ai/responses/route.js | 6 +- app/api/ai/route.js | 6 +- electron/main.js | 27 ++++++++- 7 files changed, 147 insertions(+), 12 deletions(-) create mode 100644 .env.example diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..2d9c42e --- /dev/null +++ b/.env.example @@ -0,0 +1,110 @@ +# ============================================ +# Author App — 环境变量配置 +# ============================================ +# +# 📖 使用教程: +# +# 1. 复制本文件并重命名: +# 将 .env.example 复制一份,命名为 .env.local +# (Windows: 复制粘贴后重命名; 命令行: copy .env.example .env.local) +# +# 2. 编辑 .env.local: +# 取消你需要的配置项的注释(删掉行首的 #),填入你自己的值 +# 例如想用智谱AI,只需修改 API_KEY 那一行 +# +# 3. 启动应用: +# 配置会自动生效,无需其他操作 +# +# ⚠️ 注意事项: +# - 不要直接编辑 .env.example,它只是模板 +# - .env.local 已被 .gitignore 忽略,不会泄露你的 Key +# - 如果同时在应用内 ⚙️ 设置了配置,应用内设置优先 +# - 只需配置你使用的那一个供应商,不需要全部填写 +# +# ============================================ + + +# ==================== 服务端口 ==================== + +# 自定义服务端口(默认 3000,被占用时自动递增查找) +# PORT=3000 + + +# ==================== OpenAI 兼容 API ==================== +# 适用于: 智谱AI / DeepSeek / SiliconFlow / 火山引擎 / Moonshot 等 +# 所有兼容 OpenAI /v1/chat/completions 格式的供应商均可使用 + +# API_KEY=你的Key +# API_BASE_URL=https://open.bigmodel.cn/api/paas/v4 +# API_MODEL=glm-4-flash + +# 各供应商 Base URL 参考: +# 智谱AI: https://open.bigmodel.cn/api/paas/v4 +# DeepSeek: https://api.deepseek.com/v1 +# OpenAI: https://api.openai.com/v1 +# SiliconFlow: https://api.siliconflow.cn/v1 +# 火山引擎: https://ark.cn-beijing.volces.com/api/v3 +# Moonshot: https://api.moonshot.cn/v1 + +# 各供应商推荐模型: +# 智谱AI: glm-4-flash (免费) / glm-4-plus / glm-4-long / glm-4 +# DeepSeek: deepseek-chat / deepseek-reasoner +# SiliconFlow: deepseek-ai/DeepSeek-V3 / Qwen/Qwen2.5-72B-Instruct +# Moonshot: moonshot-v1-8k / moonshot-v1-32k / moonshot-v1-128k + +# 兼容旧版变量名(优先使用 API_KEY) +# ZHIPU_API_KEY= + + +# ==================== Google Gemini ==================== +# 官网: https://aistudio.google.com + +# GEMINI_API_KEY=你的Key +# GEMINI_BASE_URL=https://generativelanguage.googleapis.com/v1beta +# GEMINI_MODEL=gemini-2.0-flash + +# 可选模型: gemini-2.0-flash-lite / gemini-1.5-flash / gemini-1.5-pro +# OpenAI 兼容模式也可用,将 Base URL 改为: +# https://generativelanguage.googleapis.com/v1beta/openai +# 然后使用上方 API_KEY/API_BASE_URL/API_MODEL 配置即可 + + +# ==================== Claude (Anthropic) ==================== +# 官网: https://console.anthropic.com + +# CLAUDE_API_KEY=你的Key +# CLAUDE_BASE_URL=https://api.anthropic.com +# CLAUDE_MODEL=claude-sonnet-4-20250514 + +# 可选模型: claude-3-7-sonnet-20250219 / claude-3-5-haiku-20241022 + + +# ==================== OpenAI Responses API ==================== +# 适用于 OpenAI /v1/responses 格式 + +# OPENAI_API_KEY=你的Key +# OPENAI_BASE_URL=https://api.openai.com/v1 +# OPENAI_MODEL=gpt-4o-mini + +# 可选模型: gpt-4o / gpt-4-turbo / gpt-3.5-turbo + + +# ==================== 快速配置示例 ==================== +# +# 🔹 示例1: 用智谱AI免费模型(最简配置,只需一行) +# API_KEY=你的智谱Key +# +# 🔹 示例2: 用DeepSeek +# API_KEY=你的DeepSeek_Key +# API_BASE_URL=https://api.deepseek.com/v1 +# API_MODEL=deepseek-chat +# +# 🔹 示例3: 用Gemini +# GEMINI_API_KEY=你的Gemini_Key +# +# 🔹 示例4: 用Claude +# CLAUDE_API_KEY=你的Claude_Key +# +# 🔹 示例5: 自定义端口 + 智谱AI +# PORT=8080 +# API_KEY=你的智谱Key diff --git a/.gitignore b/.gitignore index 71c312e547d7d592fca814e7980278de6ee3f3a0..33cd8ff22429279f0b325fa317eaee135326c2dd 100644 GIT binary patch delta 25 gcmeBUnZvSS2_vtfUTR*MUTQ^RZb44!Px# delta 11 ScmbQk(#Nu43FG8Vj9CB~fCM-I diff --git a/app/api/ai/claude/route.js b/app/api/ai/claude/route.js index 47d57aa..eb0a028 100644 --- a/app/api/ai/claude/route.js +++ b/app/api/ai/claude/route.js @@ -7,9 +7,9 @@ export async function POST(request) { try { const { systemPrompt, userPrompt, apiConfig, maxTokens, temperature, topP, reasoningEffort } = await request.json(); - const apiKey = apiConfig?.apiKey; - const baseUrl = (apiConfig?.baseUrl || 'https://api.anthropic.com').replace(/\/$/, ''); - const model = apiConfig?.model || 'claude-sonnet-4-20250514'; + const apiKey = apiConfig?.apiKey || process.env.CLAUDE_API_KEY; + const baseUrl = (apiConfig?.baseUrl || process.env.CLAUDE_BASE_URL || 'https://api.anthropic.com').replace(/\/$/, ''); + const model = apiConfig?.model || process.env.CLAUDE_MODEL || 'claude-sonnet-4-20250514'; if (!apiKey) { return new Response( diff --git a/app/api/ai/gemini/route.js b/app/api/ai/gemini/route.js index 49ad649..e2974e3 100644 --- a/app/api/ai/gemini/route.js +++ b/app/api/ai/gemini/route.js @@ -10,10 +10,10 @@ export async function POST(request) { const apiKey = apiConfig?.apiKey || process.env.GEMINI_API_KEY; let rawBaseUrl = apiConfig?.baseUrl; if (!rawBaseUrl || rawBaseUrl.includes('open.bigmodel.cn')) { - rawBaseUrl = 'https://generativelanguage.googleapis.com/v1beta'; + rawBaseUrl = process.env.GEMINI_BASE_URL || 'https://generativelanguage.googleapis.com/v1beta'; } const baseUrl = rawBaseUrl.replace(/\/$/, ''); - const model = apiConfig?.model || 'gemini-2.0-flash'; + const model = apiConfig?.model || process.env.GEMINI_MODEL || 'gemini-2.0-flash'; if (!apiKey) { return new Response( diff --git a/app/api/ai/responses/route.js b/app/api/ai/responses/route.js index 8b9c3da..c503fac 100644 --- a/app/api/ai/responses/route.js +++ b/app/api/ai/responses/route.js @@ -7,9 +7,9 @@ export async function POST(request) { try { const { systemPrompt, userPrompt, apiConfig, maxTokens, temperature, topP } = await request.json(); - const apiKey = apiConfig?.apiKey; - const baseUrl = (apiConfig?.baseUrl || 'https://api.openai.com/v1').replace(/\/$/, ''); - const model = apiConfig?.model || 'gpt-4o-mini'; + const apiKey = apiConfig?.apiKey || process.env.OPENAI_API_KEY; + const baseUrl = (apiConfig?.baseUrl || process.env.OPENAI_BASE_URL || 'https://api.openai.com/v1').replace(/\/$/, ''); + const model = apiConfig?.model || process.env.OPENAI_MODEL || 'gpt-4o-mini'; if (!apiKey) { return new Response( diff --git a/app/api/ai/route.js b/app/api/ai/route.js index 4d60c69..9f6a925 100644 --- a/app/api/ai/route.js +++ b/app/api/ai/route.js @@ -7,9 +7,9 @@ export async function POST(request) { try { const { systemPrompt, userPrompt, apiConfig, maxTokens, temperature, topP, reasoningEffort } = await request.json(); - const apiKey = apiConfig?.apiKey || process.env.ZHIPU_API_KEY; - const baseUrl = apiConfig?.baseUrl || 'https://open.bigmodel.cn/api/paas/v4'; - const model = apiConfig?.model || 'glm-4-flash'; + 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( diff --git a/electron/main.js b/electron/main.js index 513fb08..c71c03b 100644 --- a/electron/main.js +++ b/electron/main.js @@ -7,6 +7,31 @@ const net = require('net'); const fs = require('fs'); const os = require('os'); +// 加载 .env.local(轻量实现,无需 dotenv 依赖) +(function loadEnvFile() { + const envPaths = [ + path.join(__dirname, '..', '.env.local'), + path.join(__dirname, '..', '.env'), + ]; + for (const envPath of envPaths) { + if (fs.existsSync(envPath)) { + const lines = fs.readFileSync(envPath, 'utf8').split('\n'); + for (const line of lines) { + const trimmed = line.trim(); + if (!trimmed || trimmed.startsWith('#')) continue; + const eqIdx = trimmed.indexOf('='); + if (eqIdx === -1) continue; + const key = trimmed.slice(0, eqIdx).trim(); + const value = trimmed.slice(eqIdx + 1).trim(); + if (key && !process.env[key]) { + process.env[key] = value; + } + } + break; // 只加载第一个找到的文件 + } + } +})(); + // 日志文件 - 写到用户桌面方便查看 const logFile = path.join(app.getPath('desktop'), 'author-debug.log'); function log(msg) { @@ -27,7 +52,7 @@ let mainWindow; let serverProcess; const isDev = process.argv.includes('--dev'); -const BASE_PORT = 3000; +const BASE_PORT = parseInt(process.env.PORT, 10) || 3000; let actualPort = BASE_PORT; let loadRetries = 0; const MAX_LOAD_RETRIES = 10;