claude-code-cchui/src/app/api/conversations/[id]/export/route.ts
gaoziman 2c292b0a8f feat(导出功能): 添加对话导出 API 路由
- 实现 GET /api/conversations/[id]/export 接口
- 支持 markdown/json/html/pdf 四种导出格式
- 添加用户身份验证和权限检查
- PDF 格式返回数据供客户端生成
2025-12-24 09:40:28 +08:00

168 lines
5.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.

/**
* 对话导出 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<ExportData | null> {
// 获取对话
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;
}