first-update

This commit is contained in:
2026-03-17 14:36:31 +08:00
parent 72f08aee7c
commit 4eddf05e79
516 changed files with 115270 additions and 1 deletions

View File

@@ -0,0 +1,139 @@
'use client';
import {
Container,
Box,
Typography,
Alert,
Dialog,
DialogTitle,
DialogContent,
DialogActions,
Button,
Paper
} from '@mui/material';
import ConversationHeader from '@/components/conversations/ConversationHeader';
import ConversationMetadata from '@/components/conversations/ConversationMetadata';
import ConversationContent from '@/components/conversations/ConversationContent';
import ConversationRatingSection from '@/components/conversations/ConversationRatingSection';
import useConversationDetails from './useConversationDetails';
import { useTranslation } from 'react-i18next';
/**
* 多轮对话详情页面
*/
export default function ConversationDetailPage({ params }) {
const { projectId, conversationId } = params;
const { t } = useTranslation();
// 使用自定义Hook管理状态和逻辑
const {
conversation,
messages,
loading,
editMode,
saving,
editData,
setEditData,
deleteDialogOpen,
setDeleteDialogOpen,
handleEdit,
handleSave,
handleCancel,
handleDelete,
handleNavigate,
updateMessageContent
} = useConversationDetails(projectId, conversationId);
// 加载状态
if (loading) {
return (
<Container maxWidth="xl" sx={{ py: 4 }}>
<Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '70vh' }}>
<Alert severity="info">{t('datasets.loadingDataset')}</Alert>
</Box>
</Container>
);
}
// 无数据状态
if (!conversation) {
return (
<Container maxWidth="xl" sx={{ py: 4 }}>
<Alert severity="error">{t('datasets.conversationNotFound')}</Alert>
</Container>
);
}
return (
<Container maxWidth="xl" sx={{ py: 4 }}>
{/* 顶部导航栏 */}
<ConversationHeader
projectId={projectId}
conversationId={conversationId}
conversation={conversation}
editMode={editMode}
saving={saving}
onEdit={handleEdit}
onSave={handleSave}
onCancel={handleCancel}
onDelete={() => setDeleteDialogOpen(true)}
onNavigate={handleNavigate}
/>
{/* 主要布局:左右分栏 */}
<Box sx={{ display: 'flex', gap: 3, alignItems: 'flex-start' }}>
{/* 左侧主要内容区域 */}
<Box sx={{ flex: 1, minWidth: 0 }}>
<Paper sx={{ p: 3 }}>
{/* 对话内容 */}
<ConversationContent
messages={editMode ? editData.messages : messages}
editMode={editMode}
onMessageChange={updateMessageContent}
conversation={conversation}
/>
</Paper>
</Box>
{/* 右侧固定侧边栏 */}
<Box
sx={{
width: 360,
position: 'sticky',
top: 24,
maxHeight: 'calc(100vh - 48px)',
overflowY: 'auto'
}}
>
{/* 元数据展示 */}
<ConversationMetadata conversation={conversation} />
{/* 评分、标签、备注区域 */}
<ConversationRatingSection
conversation={conversation}
projectId={projectId}
onUpdate={() => {
// 更新成功后刷新数据,保持页面状态同步
// 这里可以调用 useConversationDetails 的刷新逻辑
}}
/>
</Box>
</Box>
{/* 删除确认对话框 */}
<Dialog open={deleteDialogOpen} onClose={() => setDeleteDialogOpen(false)}>
<DialogTitle>{t('datasets.confirmDelete')}</DialogTitle>
<DialogContent>
<Typography>{t('datasets.confirmDeleteConversation')}</Typography>
</DialogContent>
<DialogActions>
<Button onClick={() => setDeleteDialogOpen(false)}>{t('common.cancel')}</Button>
<Button color="error" onClick={handleDelete}>
{t('common.delete')}
</Button>
</DialogActions>
</Dialog>
</Container>
);
}

View File

@@ -0,0 +1,211 @@
'use client';
import { useState, useEffect } from 'react';
import { useRouter } from 'next/navigation';
import { toast } from 'sonner';
import { useTranslation } from 'react-i18next';
/**
* 多轮对话详情页面的状态管理Hook
*/
export default function useConversationDetails(projectId, conversationId) {
const { t } = useTranslation();
const router = useRouter();
// 基础状态
const [conversation, setConversation] = useState(null);
const [messages, setMessages] = useState([]);
const [loading, setLoading] = useState(true);
// 编辑状态
const [editMode, setEditMode] = useState(false);
const [saving, setSaving] = useState(false);
const [editData, setEditData] = useState({
score: 0,
tags: '',
note: '',
confirmed: false,
messages: []
});
// 对话框状态
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
// 获取对话详情
const fetchConversation = async () => {
try {
setLoading(true);
const response = await fetch(`/api/projects/${projectId}/dataset-conversations/${conversationId}`);
if (!response.ok) {
if (response.status === 404) {
toast.error(t('datasets.conversationNotFound'));
router.push(`/projects/${projectId}/multi-turn`);
return;
}
throw new Error(t('datasets.fetchDataFailed'));
}
const data = await response.json();
setConversation(data);
// 解析对话消息
let parsedMessages = [];
try {
parsedMessages = JSON.parse(data.rawMessages || '[]');
setMessages(parsedMessages);
} catch (error) {
console.error('解析对话消息失败:', error);
setMessages([]);
}
// 设置编辑数据
setEditData({
score: data.score || 0,
tags: data.tags || '',
note: data.note || '',
confirmed: data.confirmed || false,
messages: parsedMessages
});
} catch (error) {
console.error('获取对话详情失败:', error);
toast.error(error.message || t('datasets.fetchDataFailed'));
} finally {
setLoading(false);
}
};
// 保存编辑
const handleSave = async () => {
try {
setSaving(true);
const response = await fetch(`/api/projects/${projectId}/dataset-conversations/${conversationId}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
score: editData.score,
tags: editData.tags,
note: editData.note,
confirmed: editData.confirmed,
messages: editData.messages
})
});
if (!response.ok) {
throw new Error(t('datasets.saveFailed'));
}
// 更新本地状态
setConversation({ ...conversation, ...editData });
setMessages(editData.messages);
setEditMode(false);
toast.success(t('datasets.saveSuccess'));
} catch (error) {
console.error('保存失败:', error);
toast.error(error.message || t('datasets.saveFailed'));
} finally {
setSaving(false);
}
};
// 开始编辑
const handleEdit = () => {
setEditMode(true);
};
// 取消编辑
const handleCancel = () => {
// 恢复到原始数据
setEditData({
score: conversation.score || 0,
tags: conversation.tags || '',
note: conversation.note || '',
confirmed: conversation.confirmed || false,
messages: messages
});
setEditMode(false);
};
// 删除对话
const handleDelete = async () => {
try {
const response = await fetch(`/api/projects/${projectId}/dataset-conversations/${conversationId}`, {
method: 'DELETE'
});
if (!response.ok) {
throw new Error(t('datasets.deleteFailed'));
}
toast.success(t('datasets.deleteSuccess'));
router.push(`/projects/${projectId}/multi-turn`);
} catch (error) {
console.error('删除失败:', error);
toast.error(error.message || t('datasets.deleteFailed'));
}
};
// 更新消息内容
const updateMessageContent = (index, newContent) => {
const updatedMessages = [...editData.messages];
updatedMessages[index] = { ...updatedMessages[index], content: newContent };
setEditData({ ...editData, messages: updatedMessages });
};
// 翻页导航
const handleNavigate = async direction => {
try {
const response = await fetch(
`/api/projects/${projectId}/dataset-conversations/${conversationId}?operateType=${direction}`
);
if (!response.ok) {
throw new Error('获取导航数据失败');
}
const data = await response.json();
if (data) {
router.push(`/projects/${projectId}/multi-turn/${data.id}`);
} else {
toast.warning(`已经是${direction === 'next' ? '最后' : '第'}一条对话了`);
}
} catch (error) {
console.error('导航失败:', error);
toast.error(error.message || '导航失败');
}
};
// 初始化
useEffect(() => {
fetchConversation();
}, [projectId, conversationId]);
return {
// 数据状态
conversation,
messages,
loading,
// 编辑状态
editMode,
saving,
editData,
setEditData,
// 对话框状态
deleteDialogOpen,
setDeleteDialogOpen,
// 操作方法
handleEdit,
handleSave,
handleCancel,
handleDelete,
handleNavigate,
updateMessageContent,
fetchConversation
};
}