claude-code-cchui/src/app/api/settings/route.ts
gaoziman 5a6a147bd8 feat(API): 集成秘塔AI工具和工具追踪功能
聊天API (chat/route.ts):
- 添加秘塔API Key解密和传递
- 集成工具使用追踪,记录每次对话使用的工具
- 支持图片搜索结果的流式返回
- 添加 tool_used 和 tool_search_images 事件类型

设置API (settings/route.ts):
- 支持秘塔API Key的加密存储和清除
- 更新默认工具列表包含秘塔工具

消息API (messages/route.ts):
- 支持搜索图片数据的追加保存
2025-12-22 12:22:34 +08:00

240 lines
7.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { NextResponse } from 'next/server';
import { db } from '@/drizzle/db';
import { userSettings } from '@/drizzle/schema';
import { eq } from 'drizzle-orm';
import { getCurrentUser } from '@/lib/auth';
import { encryptApiKey } from '@/lib/crypto';
// 默认设置值
const DEFAULT_SETTINGS = {
cchUrl: process.env.CCH_DEFAULT_URL || 'https://claude.leocoder.cn/',
cchApiKeyConfigured: false,
metasoApiKeyConfigured: false,
apiFormat: 'claude' as 'claude' | 'openai', // API 格式claude原生| openai兼容
defaultModel: 'claude-sonnet-4-5-20250929',
defaultTools: ['web_search', 'web_fetch', 'mita_search', 'mita_reader'],
systemPrompt: '',
temperature: '0.7',
theme: 'light',
language: 'zh-CN',
fontSize: 15,
enableThinking: false,
saveChatHistory: true,
};
// 格式化设置响应(不返回 API Key 本身)
function formatSettingsResponse(settings: typeof userSettings.$inferSelect | null) {
if (!settings) {
return DEFAULT_SETTINGS;
}
return {
cchUrl: settings.cchUrl || DEFAULT_SETTINGS.cchUrl,
cchApiKeyConfigured: settings.cchApiKeyConfigured || false,
metasoApiKeyConfigured: settings.metasoApiKeyConfigured || 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 || '',
temperature: settings.temperature || '0.7',
theme: settings.theme || DEFAULT_SETTINGS.theme,
language: settings.language || DEFAULT_SETTINGS.language,
fontSize: settings.fontSize || 15,
enableThinking: settings.enableThinking || false,
saveChatHistory: settings.saveChatHistory ?? true,
};
}
// GET /api/settings - 获取当前用户的设置
export async function GET() {
try {
// 1. 获取当前登录用户
const user = await getCurrentUser();
if (!user) {
// 未登录用户返回默认设置(允许查看但无法保存)
return NextResponse.json(DEFAULT_SETTINGS);
}
// 2. 查询该用户的设置
const settings = await db.query.userSettings.findFirst({
where: eq(userSettings.userId, user.userId),
});
// 3. 如果没有设置记录,创建默认设置
if (!settings) {
const newSettings = await db
.insert(userSettings)
.values({
userId: user.userId,
cchUrl: DEFAULT_SETTINGS.cchUrl,
defaultModel: DEFAULT_SETTINGS.defaultModel,
defaultTools: DEFAULT_SETTINGS.defaultTools,
theme: DEFAULT_SETTINGS.theme,
language: DEFAULT_SETTINGS.language,
fontSize: DEFAULT_SETTINGS.fontSize,
enableThinking: DEFAULT_SETTINGS.enableThinking,
saveChatHistory: DEFAULT_SETTINGS.saveChatHistory,
})
.returning();
return NextResponse.json(formatSettingsResponse(newSettings[0]));
}
return NextResponse.json(formatSettingsResponse(settings));
} catch (error) {
console.error('Failed to get settings:', error);
return NextResponse.json(
{ error: 'Failed to get settings' },
{ status: 500 }
);
}
}
// PUT /api/settings - 更新当前用户的设置
export async function PUT(request: Request) {
try {
// 1. 获取当前登录用户
const user = await getCurrentUser();
if (!user) {
return NextResponse.json(
{ error: '请先登录后再修改设置' },
{ status: 401 }
);
}
const body = await request.json();
const {
cchUrl,
cchApiKey,
metasoApiKey,
apiFormat,
defaultModel,
defaultTools,
systemPrompt,
temperature,
theme,
language,
fontSize,
enableThinking,
saveChatHistory,
} = body;
// 2. 构建更新对象
const updateData: Record<string, unknown> = {
updatedAt: new Date(),
};
if (cchUrl !== undefined) {
updateData.cchUrl = cchUrl;
}
// 如果提供了 API Key加密后存储
if (cchApiKey !== undefined) {
if (cchApiKey === '') {
// 清除 API Key
updateData.cchApiKey = null;
updateData.cchApiKeyConfigured = false;
} else {
// 加密存储 API Key
updateData.cchApiKey = encryptApiKey(cchApiKey);
updateData.cchApiKeyConfigured = true;
}
}
// 如果提供了秘塔 API Key加密后存储
if (metasoApiKey !== undefined) {
if (metasoApiKey === '') {
// 清除秘塔 API Key
updateData.metasoApiKey = null;
updateData.metasoApiKeyConfigured = false;
} else {
// 加密存储秘塔 API Key
updateData.metasoApiKey = encryptApiKey(metasoApiKey);
updateData.metasoApiKeyConfigured = true;
}
}
// API 格式类型
if (apiFormat !== undefined) {
updateData.apiFormat = apiFormat;
}
if (defaultModel !== undefined) {
updateData.defaultModel = defaultModel;
}
if (defaultTools !== undefined) {
updateData.defaultTools = defaultTools;
}
if (systemPrompt !== undefined) {
updateData.systemPrompt = systemPrompt;
}
if (temperature !== undefined) {
updateData.temperature = temperature;
}
if (theme !== undefined) {
updateData.theme = theme;
}
if (language !== undefined) {
updateData.language = language;
}
if (fontSize !== undefined) {
// 限制字体大小在 12-20 之间
updateData.fontSize = Math.min(20, Math.max(12, fontSize));
}
if (enableThinking !== undefined) {
updateData.enableThinking = enableThinking;
}
if (saveChatHistory !== undefined) {
updateData.saveChatHistory = saveChatHistory;
}
// 3. 检查是否存在该用户的设置记录
const existing = await db.query.userSettings.findFirst({
where: eq(userSettings.userId, user.userId),
});
if (!existing) {
// 创建新的设置记录
await db.insert(userSettings).values({
userId: user.userId,
cchUrl: DEFAULT_SETTINGS.cchUrl,
defaultModel: DEFAULT_SETTINGS.defaultModel,
defaultTools: DEFAULT_SETTINGS.defaultTools,
theme: DEFAULT_SETTINGS.theme,
language: DEFAULT_SETTINGS.language,
fontSize: DEFAULT_SETTINGS.fontSize,
enableThinking: DEFAULT_SETTINGS.enableThinking,
saveChatHistory: DEFAULT_SETTINGS.saveChatHistory,
...updateData,
});
} else {
// 更新现有记录
await db
.update(userSettings)
.set(updateData)
.where(eq(userSettings.userId, user.userId));
}
// 4. 返回更新后的设置(不包含 API Key
const updatedSettings = await db.query.userSettings.findFirst({
where: eq(userSettings.userId, user.userId),
});
return NextResponse.json(formatSettingsResponse(updatedSettings ?? null));
} catch (error) {
console.error('Failed to update settings:', error);
return NextResponse.json(
{ error: 'Failed to update settings' },
{ status: 500 }
);
}
}