diff --git a/README.md b/README.md index 538738876..2442ffdd3 100644 --- a/README.md +++ b/README.md @@ -78,6 +78,47 @@ | 11 | [Cloudinary](https://cloudinary.com/) | 配置 `Cloud Name`、`API Key`、`API Secret` 参数 | [如何使用 Cloudinary?](https://cloudinary.com/documentation/upload_images) | | 12 | 自定义上传 | 是 | [如何自定义上传?](/docs/custom-upload.md) | +## 🔑 配置 AI API Key(环境变量) + +支持通过环境变量预设各服务类型的 API Key,有以下几种方式: + +**方式 1:本地部署** + +在 `apps/web/.env.local` 文件中配置(该文件会被 git 忽略,不会提交到仓库): + +```bash +# .env.local +VITE_OPENAI_KEY_openai=sk-your-openai-key +VITE_OPENAI_KEY_deepseek=sk-your-deepseek-key +VITE_OPENAI_KEY_qwen=sk-your-qwen-key +``` + +**方式 2:Docker 部署** + +通过构建参数传递环境变量: + +```sh +# 配置单个服务的 API Key +docker build --build-arg VITE_OPENAI_KEY_openai=sk-your-openai-key -t md:latest . + +# 同时配置多个服务的 API Key +docker build \ + --build-arg VITE_OPENAI_KEY_openai=sk-openai-key \ + --build-arg VITE_OPENAI_KEY_deepseek=sk-deepseek-key \ + --build-arg VITE_OPENAI_KEY_qwen=sk-qwen-key \ + -t md:latest . +``` + +**支持的服务类型:** `openai`, `deepseek`, `qwen`, `moonshot`, `ernie`, `hunyuan`, `doubao`, `baichuan`, `bigmodel`, `siliconflow`, `302ai`, `lingyiwanwu`, `custom` + +**环境变量格式:** `VITE_OPENAI_KEY_<服务类型>`,例如 `VITE_OPENAI_KEY_openai`、`VITE_OPENAI_KEY_deepseek` + +**注意:** + +- 环境变量中的 API Key 会作为默认值使用 +- 如果用户在界面中手动配置了 API Key,会优先使用用户配置的值(保存到浏览器 localStorage) +- `.env.local` 文件需要放在 `apps/web/` 目录下,且变量名必须以 `VITE_` 开头 + ## 🎬 产品演示
diff --git a/apps/web/.env.local.example b/apps/web/.env.local.example new file mode 100644 index 000000000..c05c3689f --- /dev/null +++ b/apps/web/.env.local.example @@ -0,0 +1,18 @@ +# 开发配置,启动编辑器 +# https://github.com/yyx990803/launch-editor?tab=readme-ov-file#supported-editors +# VITE_LAUNCH_EDITOR=code + +# api key +# VITE_OPENAI_KEY_openai="" +# VITE_OPENAI_KEY_deepseek="" +# VITE_OPENAI_KEY_qwen="" +# VITE_OPENAI_KEY_moonshot="" +# VITE_OPENAI_KEY_ernie="" +# VITE_OPENAI_KEY_hunyuan="" +# VITE_OPENAI_KEY_doubao="" +# VITE_OPENAI_KEY_baichuan="" +# VITE_OPENAI_KEY_bigmodel="" +# VITE_OPENAI_KEY_siliconflow="" +# VITE_OPENAI_KEY_302ai="" +# VITE_OPENAI_KEY_lingyiwanwu="" +# VITE_OPENAI_KEY_custom="" diff --git a/apps/web/src/stores/aiConfig.ts b/apps/web/src/stores/aiConfig.ts index 37ee608d5..222565424 100644 --- a/apps/web/src/stores/aiConfig.ts +++ b/apps/web/src/stores/aiConfig.ts @@ -33,14 +33,35 @@ export const useAIConfigStore = defineStore(`AIConfig`, () => { // ==================== API Key 管理 ==================== + // 从环境变量获取默认 API Key + // 支持多种方式: + // 1. 本地开发:在 .env.local 或 .env 文件中配置 VITE_OPENAI_KEY_<服务类型> + // 2. Docker 构建:通过 --build-arg 传递环境变量 + // 3. 运行时环境变量:在运行环境中设置 + const getDefaultApiKey = (serviceType: string): string => { + // 使用服务特定的环境变量,例如 VITE_OPENAI_KEY_openai + const serviceSpecificKey = import.meta.env[`VITE_OPENAI_KEY_${serviceType}`] + if (serviceSpecificKey) + return serviceSpecificKey + + // 如果没有设置环境变量,使用常量默认值 + return DEFAULT_SERVICE_KEY + } + // API Key(按服务类型分别持久化) const apiKey = customRef((track, trigger) => { let cachedKey = `` + // 加载 API Key 的函数 + const loadApiKey = async (serviceType: string) => { + const storedValue = await store.get(`openai_key_${serviceType}`) + // 如果 localStorage 中有值,使用它;否则使用环境变量中的默认值 + cachedKey = storedValue || getDefaultApiKey(serviceType) + trigger() + } + // 异步加载初始值 - store.get(`openai_key_${type.value}`).then((value) => { - cachedKey = value || DEFAULT_SERVICE_KEY - }) + loadApiKey(type.value) return { get() { @@ -51,6 +72,8 @@ export const useAIConfigStore = defineStore(`AIConfig`, () => { cachedKey = val trigger() + // 保存到 localStorage(用户手动设置或环境变量初始化都会保存) + // 这样用户可以在界面中看到环境变量提供的默认值,也可以手动修改 if (type.value !== DEFAULT_SERVICE_TYPE) { store.set(`openai_key_${type.value}`, val) } @@ -75,6 +98,23 @@ export const useAIConfigStore = defineStore(`AIConfig`, () => { // 保存当前模型 await store.set(`openai_model_${newType}`, model.value) + + // 重新加载当前服务类型的 API Key + const storedKey = await store.get(`openai_key_${newType}`) + if (storedKey) { + // localStorage 中有值,使用它 + apiKey.value = storedKey + } + else { + // localStorage 中没有值,使用环境变量中的默认值 + const envKey = getDefaultApiKey(newType) + if (envKey) { + apiKey.value = envKey + } + else { + apiKey.value = DEFAULT_SERVICE_KEY + } + } }, { immediate: true }, // 首次加载时也执行 ) diff --git a/docker/latest/Dockerfile.base b/docker/latest/Dockerfile.base index 9c08bd088..82a43d9c9 100644 --- a/docker/latest/Dockerfile.base +++ b/docker/latest/Dockerfile.base @@ -7,6 +7,40 @@ RUN curl -L "https://github.com/doocs/md/archive/refs/heads/main.zip" -o "main.z WORKDIR /app RUN npm install -g pnpm ENV NODE_OPTIONS="--openssl-legacy-provider" + +# 支持构建时传递各服务类型的 API Key 环境变量 +# 格式:--build-arg VITE_OPENAI_KEY_<服务类型>=your-key +# 支持的服务类型:openai, deepseek, qwen, moonshot, ernie, hunyuan, doubao, baichuan, bigmodel, siliconflow, 302ai, lingyiwanwu, custom +# 示例:docker build --build-arg VITE_OPENAI_KEY_openai=sk-xxx -t md:latest . +ARG VITE_OPENAI_KEY_openai="" +ARG VITE_OPENAI_KEY_deepseek="" +ARG VITE_OPENAI_KEY_qwen="" +ARG VITE_OPENAI_KEY_moonshot="" +ARG VITE_OPENAI_KEY_ernie="" +ARG VITE_OPENAI_KEY_hunyuan="" +ARG VITE_OPENAI_KEY_doubao="" +ARG VITE_OPENAI_KEY_baichuan="" +ARG VITE_OPENAI_KEY_bigmodel="" +ARG VITE_OPENAI_KEY_siliconflow="" +ARG VITE_OPENAI_KEY_302ai="" +ARG VITE_OPENAI_KEY_lingyiwanwu="" +ARG VITE_OPENAI_KEY_custom="" + +# 设置环境变量供构建时使用 +ENV VITE_OPENAI_KEY_openai=${VITE_OPENAI_KEY_openai} +ENV VITE_OPENAI_KEY_deepseek=${VITE_OPENAI_KEY_deepseek} +ENV VITE_OPENAI_KEY_qwen=${VITE_OPENAI_KEY_qwen} +ENV VITE_OPENAI_KEY_moonshot=${VITE_OPENAI_KEY_moonshot} +ENV VITE_OPENAI_KEY_ernie=${VITE_OPENAI_KEY_ernie} +ENV VITE_OPENAI_KEY_hunyuan=${VITE_OPENAI_KEY_hunyuan} +ENV VITE_OPENAI_KEY_doubao=${VITE_OPENAI_KEY_doubao} +ENV VITE_OPENAI_KEY_baichuan=${VITE_OPENAI_KEY_baichuan} +ENV VITE_OPENAI_KEY_bigmodel=${VITE_OPENAI_KEY_bigmodel} +ENV VITE_OPENAI_KEY_siliconflow=${VITE_OPENAI_KEY_siliconflow} +ENV VITE_OPENAI_KEY_302ai=${VITE_OPENAI_KEY_302ai} +ENV VITE_OPENAI_KEY_lingyiwanwu=${VITE_OPENAI_KEY_lingyiwanwu} +ENV VITE_OPENAI_KEY_custom=${VITE_OPENAI_KEY_custom} + RUN pnpm install && pnpm web build:h5-netlify FROM scratch