diff --git a/src/app/api/prompt/history/route.ts b/src/app/api/prompt/history/route.ts new file mode 100644 index 0000000..c788c13 --- /dev/null +++ b/src/app/api/prompt/history/route.ts @@ -0,0 +1,97 @@ +import { NextResponse } from 'next/server'; +import { db } from '@/drizzle/db'; +import { promptOptimizations } from '@/drizzle/schema'; +import { eq, desc } from 'drizzle-orm'; +import { getCurrentUser } from '@/lib/auth'; + +// GET /api/prompt/history - 获取用户的优化历史 +export async function GET() { + try { + // 获取当前用户 + const user = await getCurrentUser(); + if (!user) { + return NextResponse.json( + { error: '请先登录' }, + { status: 401 } + ); + } + + // 查询该用户的优化历史(最近 50 条) + const history = await db + .select() + .from(promptOptimizations) + .where(eq(promptOptimizations.userId, user.userId)) + .orderBy(desc(promptOptimizations.createdAt)) + .limit(50); + + return NextResponse.json({ + success: true, + data: history, + }); + } catch (error) { + console.error('获取优化历史失败:', error); + return NextResponse.json( + { error: '获取历史记录失败' }, + { status: 500 } + ); + } +} + +// DELETE /api/prompt/history - 删除指定的优化历史 +export async function DELETE(request: Request) { + try { + // 获取当前用户 + const user = await getCurrentUser(); + if (!user) { + return NextResponse.json( + { error: '请先登录' }, + { status: 401 } + ); + } + + const { searchParams } = new URL(request.url); + const id = searchParams.get('id'); + + if (!id) { + return NextResponse.json( + { error: '缺少记录 ID' }, + { status: 400 } + ); + } + + // 验证记录属于当前用户后删除 + const record = await db + .select() + .from(promptOptimizations) + .where(eq(promptOptimizations.id, parseInt(id))) + .limit(1); + + if (record.length === 0) { + return NextResponse.json( + { error: '记录不存在' }, + { status: 404 } + ); + } + + if (record[0].userId !== user.userId) { + return NextResponse.json( + { error: '无权删除此记录' }, + { status: 403 } + ); + } + + await db + .delete(promptOptimizations) + .where(eq(promptOptimizations.id, parseInt(id))); + + return NextResponse.json({ + success: true, + }); + } catch (error) { + console.error('删除优化历史失败:', error); + return NextResponse.json( + { error: '删除失败' }, + { status: 500 } + ); + } +} diff --git a/src/app/api/prompt/optimize/route.ts b/src/app/api/prompt/optimize/route.ts new file mode 100644 index 0000000..b0e7164 --- /dev/null +++ b/src/app/api/prompt/optimize/route.ts @@ -0,0 +1,166 @@ +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 || ''; + } + + // 清理 标签(如果有) + optimizedPrompt = optimizedPrompt.replace(/[\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 } + ); + } +}