- 新增 /api/prompt/optimize 接口用于调用 AI 优化提示词 - 支持简洁版和详细版两种优化模式 - 支持 Claude 原生格式和 OpenAI 兼容格式 - 新增 /api/prompt/history 接口管理优化历史记录 - 支持查询和删除历史记录
167 lines
5.1 KiB
TypeScript
167 lines
5.1 KiB
TypeScript
import { NextResponse } from 'next/server';
|
||
import { db } from '@/drizzle/db';
|
||
import { promptOptimizations, userSettings } from '@/drizzle/schema';
|
||
import { eq } from 'drizzle-orm';
|
||
import { getCurrentUser } from '@/lib/auth';
|
||
import { decryptApiKey } from '@/lib/crypto';
|
||
|
||
// 简洁版系统提示词
|
||
const CONCISE_SYSTEM_PROMPT = `你是一个提示词优化专家。用户会给你一个原始的想法或问题,请帮助优化成简洁明了的提示词。
|
||
|
||
要求:
|
||
1. 保持核心意图不变
|
||
2. 去除冗余和模糊的表达
|
||
3. 使用清晰、准确的语言
|
||
4. 输出应该简短精炼(不超过150字)
|
||
5. 直接输出优化后的提示词,不要解释或添加其他内容`;
|
||
|
||
// 详细版系统提示词
|
||
const DETAILED_SYSTEM_PROMPT = `你是一个提示词优化专家。用户会给你一个原始的想法或问题,请帮助优化成结构化的详细提示词。
|
||
|
||
优化后的提示词应包含以下要素(根据实际情况选择):
|
||
1. **任务目标**:明确说明要完成什么
|
||
2. **背景上下文**:提供必要的背景信息
|
||
3. **具体要求**:列出详细的要求和约束
|
||
4. **输出格式**:说明期望的输出格式
|
||
5. **示例**(如有必要):提供参考示例
|
||
|
||
要求:
|
||
- 使用清晰的结构和层次
|
||
- 语言准确、专业
|
||
- 保持原始意图的完整性
|
||
- 直接输出优化后的提示词,不要解释或添加额外的说明文字`;
|
||
|
||
// 规范化 URL
|
||
function normalizeBaseUrl(url: string): string {
|
||
return url.replace(/\/+$/, '');
|
||
}
|
||
|
||
// POST /api/prompt/optimize - 优化提示词
|
||
export async function POST(request: Request) {
|
||
try {
|
||
// 获取当前用户
|
||
const user = await getCurrentUser();
|
||
if (!user) {
|
||
return NextResponse.json(
|
||
{ error: '请先登录' },
|
||
{ status: 401 }
|
||
);
|
||
}
|
||
|
||
const body = await request.json();
|
||
const { originalPrompt, mode } = body;
|
||
|
||
if (!originalPrompt || !mode) {
|
||
return NextResponse.json(
|
||
{ error: '缺少必要参数' },
|
||
{ status: 400 }
|
||
);
|
||
}
|
||
|
||
if (!['concise', 'detailed'].includes(mode)) {
|
||
return NextResponse.json(
|
||
{ error: '无效的优化模式' },
|
||
{ status: 400 }
|
||
);
|
||
}
|
||
|
||
// 获取用户设置
|
||
const settings = await db.query.userSettings.findFirst({
|
||
where: eq(userSettings.userId, user.userId),
|
||
});
|
||
|
||
if (!settings?.cchApiKey) {
|
||
return NextResponse.json(
|
||
{ error: '请先在设置中配置 API Key' },
|
||
{ status: 400 }
|
||
);
|
||
}
|
||
|
||
// 解密 API Key
|
||
const apiKey = decryptApiKey(settings.cchApiKey);
|
||
const cchUrl = settings.cchUrl || process.env.CCH_DEFAULT_URL || 'https://claude.leocoder.cn/';
|
||
const apiFormat = (settings.apiFormat as 'claude' | 'openai') || 'claude';
|
||
|
||
// 选择系统提示词
|
||
const systemPrompt = mode === 'concise' ? CONCISE_SYSTEM_PROMPT : DETAILED_SYSTEM_PROMPT;
|
||
|
||
// 调用 AI 进行优化
|
||
let optimizedPrompt: string;
|
||
|
||
if (apiFormat === 'openai') {
|
||
// OpenAI 兼容格式
|
||
const response = await fetch(`${normalizeBaseUrl(cchUrl)}/v1/chat/completions`, {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'Authorization': `Bearer ${apiKey}`,
|
||
},
|
||
body: JSON.stringify({
|
||
model: 'claude-sonnet-4-5-20250929',
|
||
messages: [
|
||
{ role: 'system', content: systemPrompt },
|
||
{ role: 'user', content: `请优化以下提示词:\n\n${originalPrompt}` },
|
||
],
|
||
temperature: 0.7,
|
||
}),
|
||
});
|
||
|
||
if (!response.ok) {
|
||
const errorText = await response.text();
|
||
throw new Error(`API 调用失败: ${errorText}`);
|
||
}
|
||
|
||
const data = await response.json();
|
||
optimizedPrompt = data.choices?.[0]?.message?.content || '';
|
||
} else {
|
||
// Claude 原生格式
|
||
const response = await fetch(`${normalizeBaseUrl(cchUrl)}/v1/messages`, {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'x-api-key': apiKey,
|
||
'anthropic-version': '2023-06-01',
|
||
},
|
||
body: JSON.stringify({
|
||
model: 'claude-sonnet-4-5-20250929',
|
||
max_tokens: 2048,
|
||
system: systemPrompt,
|
||
messages: [
|
||
{ role: 'user', content: `请优化以下提示词:\n\n${originalPrompt}` },
|
||
],
|
||
}),
|
||
});
|
||
|
||
if (!response.ok) {
|
||
const errorText = await response.text();
|
||
throw new Error(`API 调用失败: ${errorText}`);
|
||
}
|
||
|
||
const data = await response.json();
|
||
optimizedPrompt = data.content?.[0]?.text || '';
|
||
}
|
||
|
||
// 清理 <think> 标签(如果有)
|
||
optimizedPrompt = optimizedPrompt.replace(/<think>[\s\S]*?<\/think>/g, '').trim();
|
||
|
||
// 保存到数据库
|
||
await db.insert(promptOptimizations).values({
|
||
userId: user.userId,
|
||
originalPrompt,
|
||
optimizedPrompt,
|
||
mode,
|
||
});
|
||
|
||
return NextResponse.json({
|
||
success: true,
|
||
optimizedPrompt,
|
||
});
|
||
} catch (error) {
|
||
console.error('提示词优化失败:', error);
|
||
return NextResponse.json(
|
||
{ error: error instanceof Error ? error.message : '优化失败,请稍后重试' },
|
||
{ status: 500 }
|
||
);
|
||
}
|
||
}
|