/** * 对话导出 API * GET /api/conversations/[id]/export?format=markdown|json|html|pdf */ import { NextRequest, NextResponse } from 'next/server'; import { db } from '@/drizzle/db'; import { conversations, messages } from '@/drizzle/schema'; import { eq, asc } from 'drizzle-orm'; import { getCurrentUser } from '@/lib/auth'; import { exportConversation, getExportContentType, generateExportFilename, DEFAULT_EXPORT_OPTIONS, type ExportFormat, type ExportData, type ExportMessageData, } from '@/lib/export'; export async function GET( request: NextRequest, { params }: { params: Promise<{ id: string }> } ) { try { // 验证用户身份 const user = await getCurrentUser(); if (!user) { return NextResponse.json({ error: '未登录' }, { status: 401 }); } const { id: conversationId } = await params; // 获取导出参数 const searchParams = request.nextUrl.searchParams; const format = (searchParams.get('format') || 'markdown') as ExportFormat; const includeThinking = searchParams.get('includeThinking') !== 'false'; const includeToolCalls = searchParams.get('includeToolCalls') !== 'false'; const includeImages = searchParams.get('includeImages') !== 'false'; // 验证格式 if (!['markdown', 'json', 'html', 'pdf'].includes(format)) { return NextResponse.json( { error: '不支持的导出格式' }, { status: 400 } ); } // PDF 导出需要在客户端执行,服务端只返回数据 if (format === 'pdf') { // 对于 PDF,返回 JSON 数据让客户端生成 const exportData = await getExportData(conversationId, user.userId); if (!exportData) { return NextResponse.json( { error: '对话不存在或无权访问' }, { status: 404 } ); } return NextResponse.json({ ...exportData, exportFormat: 'pdf', message: '请在客户端生成 PDF', }); } // 获取对话和消息数据 const exportData = await getExportData(conversationId, user.userId); if (!exportData) { return NextResponse.json( { error: '对话不存在或无权访问' }, { status: 404 } ); } // 执行导出 const content = await exportConversation(exportData, { format, includeThinking, includeToolCalls, includeImages, }); // 生成文件名 const filename = generateExportFilename(exportData.conversation.title, format); const contentType = getExportContentType(format); // 返回文件 return new NextResponse(content as string, { headers: { 'Content-Type': contentType, 'Content-Disposition': `attachment; filename*=UTF-8''${encodeURIComponent(filename)}`, }, }); } catch (error) { console.error('Export error:', error); return NextResponse.json( { error: '导出失败' }, { status: 500 } ); } } /** * 获取导出数据 */ async function getExportData( conversationId: string, userId: string ): Promise { // 获取对话 const conversation = await db.query.conversations.findFirst({ where: eq(conversations.conversationId, conversationId), }); if (!conversation) { return null; } // 验证权限 if (conversation.userId !== userId) { return null; } // 获取消息 const messageList = await db.query.messages.findMany({ where: eq(messages.conversationId, conversationId), orderBy: [asc(messages.createdAt)], }); // 构建导出数据 const exportData: ExportData = { exportInfo: { exportedAt: new Date().toISOString(), format: 'json', // 将在实际导出时更新 version: '1.0', }, conversation: { id: conversation.conversationId, title: conversation.title, model: conversation.model, enableThinking: conversation.enableThinking || false, tools: (conversation.tools as string[]) || [], messageCount: conversation.messageCount || 0, totalTokens: conversation.totalTokens || 0, createdAt: conversation.createdAt?.toISOString() || new Date().toISOString(), updatedAt: conversation.updatedAt?.toISOString() || new Date().toISOString(), }, messages: messageList.map((msg): ExportMessageData => ({ id: msg.messageId, role: msg.role as 'user' | 'assistant' | 'system', content: msg.content, thinkingContent: msg.thinkingContent, toolCalls: msg.toolCalls as ExportMessageData['toolCalls'], toolResults: msg.toolResults as ExportMessageData['toolResults'], images: msg.images as string[] | null, uploadedImages: msg.uploadedImages as string[] | null, uploadedDocuments: msg.uploadedDocuments as ExportMessageData['uploadedDocuments'], usedTools: msg.usedTools as string[] | null, searchImages: msg.searchImages as ExportMessageData['searchImages'], inputTokens: msg.inputTokens, outputTokens: msg.outputTokens, createdAt: msg.createdAt?.toISOString() || new Date().toISOString(), })), }; return exportData; }