feat: 支持多模型服务配置 (Key/Base URL/模型名)

- 后端 ai:chat 支持 baseURL 参数,可配置任意 OpenAI 兼容 API
- 前端 UI 增加 Base URL 输入框和模型选择下拉 (DeepSeek/GPT-4o/Claude/Moonshot)
- 配置通过 localStorage 持久化,支持从 .env 文件读取默认値
- 统一错误提示,移除 DeepSeek 硬编码引用

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
电摇小子
2026-06-01 10:11:25 +08:00
co-authored by Claude Opus 4.7
parent 9fb1d14c41
commit a4dfa542d1
3 changed files with 37 additions and 13 deletions
+3 -1
View File
@@ -1,8 +1,10 @@
# WeChat Database Key (Optional, can be entered in UI)
VITE_DB_KEY=
# DeepSeek API Key (Optional, can be entered in UI)
# AI API Configuration (Optional, can be entered in UI)
VITE_DEEPSEEK_API_KEY=
VITE_AI_BASE_URL=https://api.deepseek.com
VITE_AI_MODEL=deepseek-chat
# Message types to filter out (comma separated)
VITE_FILTER_MSG_TYPES=分享消息,图片,表情包,视频
+8 -6
View File
@@ -181,22 +181,24 @@ app.whenReady().then(() => {
async (
_,
messages: { role: string; content: string }[],
options?: { apiKey?: string; model?: string }
options?: { apiKey?: string; model?: string; baseURL?: string }
) => {
// @ts-ignore: vite env
const apiKey = options?.apiKey || import.meta.env.VITE_DEEPSEEK_API_KEY
const model = options?.model || 'deepseek-chat'
const model = options?.model || import.meta.env.VITE_AI_MODEL || 'deepseek-chat'
const baseURL =
options?.baseURL || import.meta.env.VITE_AI_BASE_URL || 'https://api.deepseek.com'
if (!apiKey) {
return { success: false, error: '未配置 DeepSeek API Key' }
return { success: false, error: '未配置 API Key' }
}
// 动态导入以避免如果未安装或初始类型缺失的问题
const { OpenAI } = await import('openai')
const openai = new OpenAI({
baseURL: 'https://api.deepseek.com',
apiKey: apiKey
baseURL,
apiKey
})
try {
const completion = await openai.chat.completions.create({
@@ -206,7 +208,7 @@ app.whenReady().then(() => {
})
return { success: true, data: completion.choices[0].message.content }
} catch (error: unknown) {
console.error('DeepSeek API Error:', error)
console.error('AI API Error:', error)
const errorMessage = error instanceof Error ? error.message : 'Unknown error'
return { success: false, error: errorMessage }
}
+26 -6
View File
@@ -52,11 +52,16 @@ const ChatWindow: React.FC<ChatWindowProps> = ({
// AI Settings
const [showSettingsModal, setShowSettingsModal] = useState(false)
const [apiKey, setApiKey] = useState(() => localStorage.getItem('deepseek_api_key') || '')
const [model, setModel] = useState('deepseek-chat')
const [apiKey, setApiKey] = useState(() => localStorage.getItem('ai_api_key') || '')
const [baseURL, setBaseURL] = useState(
() => localStorage.getItem('ai_base_url') || 'https://api.deepseek.com'
)
const [model, setModel] = useState(() => localStorage.getItem('ai_model') || 'deepseek-chat')
const handleSaveSettings = (): void => {
localStorage.setItem('deepseek_api_key', apiKey)
localStorage.setItem('ai_api_key', apiKey)
localStorage.setItem('ai_base_url', baseURL)
localStorage.setItem('ai_model', model)
setShowSettingsModal(false)
AIChat()
}
@@ -186,7 +191,7 @@ const ChatWindow: React.FC<ChatWindowProps> = ({
{ role: 'system', content: systemPrompt },
{ role: 'user', content: prompt }
],
{ apiKey, model }
{ apiKey, model, baseURL }
)
if (result.success && result.data) {
@@ -482,22 +487,37 @@ const ChatWindow: React.FC<ChatWindowProps> = ({
<div className="modal-content" onClick={(e) => e.stopPropagation()}>
<h3>AI </h3>
<div className="form-group" style={{ marginBottom: '15px' }}>
<label style={{ display: 'block', marginBottom: '5px' }}>:</label>
<label style={{ display: 'block', marginBottom: '5px' }}>:</label>
<select
value={model}
onChange={(e) => setModel(e.target.value)}
style={{ width: '100%', padding: '8px' }}
>
<option value="deepseek-chat">DeepSeek Chat</option>
<option value="gpt-4o">GPT-4o</option>
<option value="gpt-4o-mini">GPT-4o Mini</option>
<option value="gpt-4-turbo">GPT-4 Turbo</option>
<option value="claude-3-5-sonnet-20240620">Claude 3.5 Sonnet</option>
<option value="moonshot-v1-8k">Moonshot V1</option>
</select>
</div>
<div className="form-group" style={{ marginBottom: '15px' }}>
<label style={{ display: 'block', marginBottom: '5px' }}>Base URL:</label>
<input
type="text"
value={baseURL}
onChange={(e) => setBaseURL(e.target.value)}
placeholder="https://api.deepseek.com"
style={{ width: '95%', padding: '8px' }}
/>
</div>
<div className="form-group" style={{ marginBottom: '20px' }}>
<label style={{ display: 'block', marginBottom: '5px' }}>API Key:</label>
<input
type="password"
value={apiKey}
onChange={(e) => setApiKey(e.target.value)}
placeholder="Enter your DeepSeek API Key"
placeholder="Enter your API Key"
style={{ width: '95%', padding: '8px' }}
/>
</div>