feat(settings): 实现数据导出和清除聊天功能
- 添加导出聊天数据功能,支持 JSON 格式下载 - 添加清除所有聊天功能,带确认对话框 - 显示待删除对话和消息数量统计 - 优化 SettingsSection 组件支持暗色主题
This commit is contained in:
parent
749247affa
commit
0b5b67174f
@ -2,10 +2,11 @@
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import Link from 'next/link';
|
||||
import { ArrowLeft, Download, Check, Loader2, Eye, EyeOff, RotateCcw, Moon, Sun, Sparkles, Trash2 } from 'lucide-react';
|
||||
import { ArrowLeft, Download, Check, Loader2, Eye, EyeOff, RotateCcw, Moon, Sun, Sparkles, Trash2, AlertTriangle } from 'lucide-react';
|
||||
import { Toggle } from '@/components/ui/Toggle';
|
||||
import { ModelCardSelector } from '@/components/ui/ModelCardSelector';
|
||||
import { FontSizePicker } from '@/components/ui/FontSizePicker';
|
||||
import { ConfirmDialog } from '@/components/ui/ConfirmDialog';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { useSettings, useModels, useTools } from '@/hooks/useSettings';
|
||||
|
||||
@ -59,6 +60,12 @@ export default function SettingsPage() {
|
||||
const [temperature, setTemperature] = useState('0.7');
|
||||
const [promptSaveStatus, setPromptSaveStatus] = useState<'idle' | 'saving' | 'saved' | 'error'>('idle');
|
||||
|
||||
// 导出和清除状态
|
||||
const [exportLoading, setExportLoading] = useState(false);
|
||||
const [clearDialogOpen, setClearDialogOpen] = useState(false);
|
||||
const [clearLoading, setClearLoading] = useState(false);
|
||||
const [chatStats, setChatStats] = useState<{ conversationCount: number; messageCount: number } | null>(null);
|
||||
|
||||
// 当设置加载完成后,更新本地状态
|
||||
useEffect(() => {
|
||||
if (settings) {
|
||||
@ -198,6 +205,70 @@ export default function SettingsPage() {
|
||||
}
|
||||
};
|
||||
|
||||
// 导出聊天数据
|
||||
const handleExportData = async () => {
|
||||
setExportLoading(true);
|
||||
try {
|
||||
const response = await fetch('/api/conversations/export');
|
||||
if (!response.ok) {
|
||||
throw new Error('Export failed');
|
||||
}
|
||||
const data = await response.json();
|
||||
|
||||
// 创建 Blob 并下载
|
||||
const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
const date = new Date().toISOString().split('T')[0];
|
||||
a.download = `cchcode-chat-export-${date}.json`;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
} catch (error) {
|
||||
console.error('Failed to export data:', error);
|
||||
alert('导出失败,请稍后重试');
|
||||
} finally {
|
||||
setExportLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 打开清除对话框(获取统计信息)
|
||||
const handleOpenClearDialog = async () => {
|
||||
try {
|
||||
const response = await fetch('/api/conversations/all');
|
||||
if (response.ok) {
|
||||
const stats = await response.json();
|
||||
setChatStats(stats);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to get stats:', error);
|
||||
}
|
||||
setClearDialogOpen(true);
|
||||
};
|
||||
|
||||
// 清除所有聊天
|
||||
const handleClearAllChats = async () => {
|
||||
setClearLoading(true);
|
||||
try {
|
||||
const response = await fetch('/api/conversations/all', {
|
||||
method: 'DELETE',
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error('Delete failed');
|
||||
}
|
||||
setClearDialogOpen(false);
|
||||
// 跳转到首页
|
||||
window.location.href = '/';
|
||||
} catch (error) {
|
||||
console.error('Failed to clear chats:', error);
|
||||
alert('清除失败,请稍后重试');
|
||||
} finally {
|
||||
setClearLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="flex min-h-screen items-center justify-center">
|
||||
@ -510,9 +581,17 @@ export default function SettingsPage() {
|
||||
label="导出数据"
|
||||
description="下载所有聊天历史"
|
||||
>
|
||||
<button className="btn-ghost inline-flex items-center gap-2">
|
||||
<Download size={16} />
|
||||
导出
|
||||
<button
|
||||
onClick={handleExportData}
|
||||
disabled={exportLoading}
|
||||
className="btn-ghost inline-flex items-center gap-2"
|
||||
>
|
||||
{exportLoading ? (
|
||||
<Loader2 size={16} className="animate-spin" />
|
||||
) : (
|
||||
<Download size={16} />
|
||||
)}
|
||||
{exportLoading ? '导出中...' : '导出'}
|
||||
</button>
|
||||
</SettingsItem>
|
||||
|
||||
@ -520,12 +599,49 @@ export default function SettingsPage() {
|
||||
label="清除所有聊天"
|
||||
description="删除所有对话历史"
|
||||
>
|
||||
<button className="btn-ghost text-red-600 hover:text-red-700 inline-flex items-center gap-2">
|
||||
<button
|
||||
onClick={handleOpenClearDialog}
|
||||
className="btn-ghost text-red-600 hover:text-red-700 inline-flex items-center gap-2"
|
||||
>
|
||||
<Trash2 size={16} />
|
||||
清除
|
||||
</button>
|
||||
</SettingsItem>
|
||||
</SettingsSection>
|
||||
|
||||
{/* 清除确认对话框 */}
|
||||
<ConfirmDialog
|
||||
isOpen={clearDialogOpen}
|
||||
onClose={() => setClearDialogOpen(false)}
|
||||
onConfirm={handleClearAllChats}
|
||||
title="确认清除所有聊天记录?"
|
||||
variant="danger"
|
||||
confirmText="确认清除"
|
||||
cancelText="取消"
|
||||
loading={clearLoading}
|
||||
>
|
||||
<div className="space-y-3">
|
||||
<p className="text-sm text-[var(--color-text-secondary)]">
|
||||
此操作将永久删除:
|
||||
</p>
|
||||
<ul className="text-sm text-[var(--color-text-primary)] space-y-1 ml-4">
|
||||
<li className="flex items-center gap-2">
|
||||
<span className="w-1.5 h-1.5 rounded-full bg-red-500" />
|
||||
<span><strong>{chatStats?.conversationCount || 0}</strong> 个对话</span>
|
||||
</li>
|
||||
<li className="flex items-center gap-2">
|
||||
<span className="w-1.5 h-1.5 rounded-full bg-red-500" />
|
||||
<span><strong>{chatStats?.messageCount || 0}</strong> 条消息</span>
|
||||
</li>
|
||||
</ul>
|
||||
<div className="flex items-start gap-2 p-3 rounded-md bg-red-500/10 border border-red-500/20">
|
||||
<AlertTriangle className="w-4 h-4 text-red-500 mt-0.5 flex-shrink-0" />
|
||||
<p className="text-xs text-red-500">
|
||||
此操作不可撤销!建议先导出数据备份。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</ConfirmDialog>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
@ -543,22 +659,22 @@ function SettingsSection({ title, description, children, variant = 'default' }:
|
||||
return (
|
||||
<section
|
||||
className={cn(
|
||||
'bg-white border rounded-md mb-6 overflow-hidden',
|
||||
variant === 'danger' ? 'border-red-200' : 'border-[var(--color-border)]'
|
||||
'bg-[var(--color-bg-primary)] border rounded-md mb-6 overflow-hidden',
|
||||
variant === 'danger' ? 'border-red-500/20' : 'border-[var(--color-border)]'
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
'px-5 py-4 border-b',
|
||||
variant === 'danger'
|
||||
? 'bg-red-50 border-red-200'
|
||||
? 'bg-red-500/10 border-red-500/20'
|
||||
: 'border-[var(--color-border-light)]'
|
||||
)}
|
||||
>
|
||||
<h2
|
||||
className={cn(
|
||||
'text-base font-semibold',
|
||||
variant === 'danger' ? 'text-red-600' : 'text-[var(--color-text-primary)]'
|
||||
variant === 'danger' ? 'text-red-500' : 'text-[var(--color-text-primary)]'
|
||||
)}
|
||||
>
|
||||
{title}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user