From 99ca472dd2a86f96bf4b8f1cf5562b4a6fe17b58 Mon Sep 17 00:00:00 2001 From: gaoziman <2942894660@qq.com> Date: Sun, 21 Dec 2025 21:14:41 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E8=AE=BE=E7=BD=AE):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=20API=20=E6=A0=BC=E5=BC=8F=E9=80=89=E6=8B=A9=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 支持在 Claude 原生格式和 OpenAI 兼容格式之间切换: - 新增 api_format 数据库字段和迁移脚本 - 更新设置 Hook 类型定义 - 扩展设置 API 支持 apiFormat 读写 - 添加设置页面 API 格式选择 UI 组件 --- src/app/api/settings/route.ts | 10 +- src/app/settings/page.tsx | 80 ++ .../migrations/0008_flat_star_brand.sql | 1 + .../migrations/meta/0008_snapshot.json | 1128 +++++++++++++++++ src/drizzle/migrations/meta/_journal.json | 7 + src/drizzle/schema.ts | 4 +- src/hooks/useSettings.ts | 4 +- 7 files changed, 1231 insertions(+), 3 deletions(-) create mode 100644 src/drizzle/migrations/0008_flat_star_brand.sql create mode 100644 src/drizzle/migrations/meta/0008_snapshot.json diff --git a/src/app/api/settings/route.ts b/src/app/api/settings/route.ts index 6fa0000..675621c 100644 --- a/src/app/api/settings/route.ts +++ b/src/app/api/settings/route.ts @@ -9,7 +9,8 @@ import { encryptApiKey } from '@/lib/crypto'; const DEFAULT_SETTINGS = { cchUrl: process.env.CCH_DEFAULT_URL || 'https://claude.leocoder.cn/', cchApiKeyConfigured: false, - defaultModel: 'claude-sonnet-4-20250514', + apiFormat: 'claude' as 'claude' | 'openai', // API 格式:claude(原生)| openai(兼容) + defaultModel: 'claude-sonnet-4-5-20250929', defaultTools: ['web_search', 'code_execution', 'web_fetch'], systemPrompt: '', temperature: '0.7', @@ -29,6 +30,7 @@ function formatSettingsResponse(settings: typeof userSettings.$inferSelect | nul return { cchUrl: settings.cchUrl || DEFAULT_SETTINGS.cchUrl, cchApiKeyConfigured: settings.cchApiKeyConfigured || false, + apiFormat: (settings.apiFormat as 'claude' | 'openai') || DEFAULT_SETTINGS.apiFormat, defaultModel: settings.defaultModel || DEFAULT_SETTINGS.defaultModel, defaultTools: settings.defaultTools || DEFAULT_SETTINGS.defaultTools, systemPrompt: settings.systemPrompt || '', @@ -102,6 +104,7 @@ export async function PUT(request: Request) { const { cchUrl, cchApiKey, + apiFormat, defaultModel, defaultTools, systemPrompt, @@ -135,6 +138,11 @@ export async function PUT(request: Request) { } } + // API 格式类型 + if (apiFormat !== undefined) { + updateData.apiFormat = apiFormat; + } + if (defaultModel !== undefined) { updateData.defaultModel = defaultModel; } diff --git a/src/app/settings/page.tsx b/src/app/settings/page.tsx index 61eb91b..9e7344c 100644 --- a/src/app/settings/page.tsx +++ b/src/app/settings/page.tsx @@ -52,6 +52,7 @@ export default function SettingsPage() { // CCH 配置状态 const [cchUrl, setCchUrl] = useState(''); const [cchApiKey, setCchApiKey] = useState(''); + const [apiFormat, setApiFormat] = useState<'claude' | 'openai'>('claude'); const [showApiKey, setShowApiKey] = useState(false); const [saveStatus, setSaveStatus] = useState<'idle' | 'saving' | 'saved' | 'error'>('idle'); @@ -70,6 +71,7 @@ export default function SettingsPage() { useEffect(() => { if (settings) { setCchUrl(settings.cchUrl || ''); + setApiFormat((settings.apiFormat as 'claude' | 'openai') || 'claude'); setSystemPrompt(settings.systemPrompt || ''); setTemperature(settings.temperature || '0.7'); } @@ -82,6 +84,7 @@ export default function SettingsPage() { const updates: Record = {}; if (cchUrl) updates.cchUrl = cchUrl; if (cchApiKey) updates.cchApiKey = cchApiKey; + updates.apiFormat = apiFormat; if (Object.keys(updates).length > 0) { await updateSettings(updates); @@ -333,6 +336,83 @@ export default function SettingsPage() { /> + {/* API 格式选择 */} +
+
+ API 格式 +
+
+ 选择中转站的 API 接口格式 +
+
+ {/* Claude 原生选项 */} + + + {/* OpenAI 兼容选项 */} + +
+
+ ().default(['web_search', 'code_execution', 'web_fetch']), // AI 行为设置 systemPrompt: text('system_prompt'), // 系统提示词 diff --git a/src/hooks/useSettings.ts b/src/hooks/useSettings.ts index 5b76f28..94ae9b5 100644 --- a/src/hooks/useSettings.ts +++ b/src/hooks/useSettings.ts @@ -5,6 +5,7 @@ import { useState, useEffect, useCallback } from 'react'; export interface Settings { cchUrl: string; cchApiKeyConfigured: boolean; + apiFormat: 'claude' | 'openai'; defaultModel: string; defaultTools: string[]; systemPrompt: string; @@ -50,7 +51,8 @@ export interface Model { const defaultSettings: Settings = { cchUrl: 'http://localhost:13500', cchApiKeyConfigured: false, - defaultModel: 'claude-sonnet-4-20250514', + apiFormat: 'claude', + defaultModel: 'claude-sonnet-4-5-20250929', defaultTools: ['web_search', 'code_execution', 'web_fetch'], systemPrompt: '', temperature: '0.7',