Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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_` 开头

## 🎬 产品演示

<div align="center">
Expand Down
18 changes: 18 additions & 0 deletions apps/web/.env.local.example
Original file line number Diff line number Diff line change
@@ -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=""
46 changes: 43 additions & 3 deletions apps/web/src/stores/aiConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string>((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() {
Expand All @@ -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)
}
Expand All @@ -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 }, // 首次加载时也执行
)
Expand Down
34 changes: 34 additions & 0 deletions docker/latest/Dockerfile.base
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down