增加会话标题重命名功能,优化历史面板交互
Build Push and Deploy / docker-image (push) Successful in 2m0s
Build Push and Deploy / deploy-fallback-log (push) Has been skipped

This commit is contained in:
2026-05-19 16:42:28 +08:00
parent 3800d73e85
commit 9106b8d4a9
8 changed files with 508 additions and 71 deletions
+186 -42
View File
@@ -18,7 +18,11 @@ import {
Tooltip,
Typography,
alpha,
useTheme,
} from "@mui/material";
import CheckRounded from "@mui/icons-material/CheckRounded";
import CloseRounded from "@mui/icons-material/CloseRounded";
import EditRounded from "@mui/icons-material/EditRounded";
import EditNoteRounded from "@mui/icons-material/EditNoteRounded";
import DeleteOutlineRounded from "@mui/icons-material/DeleteOutlineRounded";
import ChatBubbleOutlineRounded from "@mui/icons-material/ChatBubbleOutlineRounded";
@@ -31,6 +35,7 @@ type AgentHistoryPanelProps = {
activeSessionId?: string;
isHydrating?: boolean;
onNewSession: () => void;
onRenameSession: (sessionId: string, title: string) => void;
onSelectSession: (sessionId: string) => void;
onDeleteSession: (sessionId: string) => void;
};
@@ -68,14 +73,19 @@ const getSessionGroupLabel = (timestamp: number) => {
};
export const AgentHistoryPanel = ({
sessions,
activeSessionId,
isHydrating = false,
onNewSession,
onRenameSession,
onSelectSession,
onDeleteSession,
}: AgentHistoryPanelProps) => {
const theme = useTheme();
const [keyword, setKeyword] = React.useState("");
const [editingSessionId, setEditingSessionId] = React.useState<string | null>(null);
const [draftTitle, setDraftTitle] = React.useState("");
const [isDeleteDialogOpen, setIsDeleteDialogOpen] = React.useState(false);
const [pendingDeleteSessionId, setPendingDeleteSessionId] = React.useState<string | null>(null);
@@ -105,6 +115,23 @@ export const AgentHistoryPanel = ({
(session) => session.id === pendingDeleteSessionId,
);
const handleStartRename = (sessionId: string, title: string) => {
setEditingSessionId(sessionId);
setDraftTitle(title);
};
const handleCancelRename = () => {
setEditingSessionId(null);
setDraftTitle("");
};
const handleConfirmRename = (sessionId: string) => {
const normalizedTitle = draftTitle.trim();
if (!normalizedTitle) return;
onRenameSession(sessionId, normalizedTitle);
handleCancelRename();
};
return (
<>
<Paper
@@ -240,7 +267,10 @@ export const AgentHistoryPanel = ({
<Paper
key={session.id}
elevation={0}
onClick={() => onSelectSession(session.id)}
onClick={() => {
if (editingSessionId === session.id) return;
onSelectSession(session.id);
}}
sx={{
px: 1.25,
py: 1,
@@ -259,49 +289,163 @@ export const AgentHistoryPanel = ({
>
<Stack direction="row" spacing={1} alignItems="flex-start">
<Box sx={{ flex: 1, minWidth: 0 }}>
<Typography
variant="body2"
fontWeight={isActive ? 800 : 700}
color="text.primary"
sx={{
overflow: "hidden",
textOverflow: "ellipsis",
display: "-webkit-box",
WebkitLineClamp: 2,
WebkitBoxOrient: "vertical",
}}
>
{session.title}
</Typography>
<Typography variant="caption" color="text.secondary" sx={{ mt: 0.5, display: "block" }}>
{formatRelativeDate(session.updatedAt)}
</Typography>
{editingSessionId === session.id ? (
<Stack direction="row" spacing={0.5} alignItems="center" sx={{ minHeight: 46 }}>
<TextField
value={draftTitle}
onChange={(event) => setDraftTitle(event.target.value)}
size="small"
autoFocus
placeholder="请输入会话标题"
onClick={(event) => event.stopPropagation()}
onKeyDown={(event) => {
if (event.key === "Enter") {
event.preventDefault();
event.stopPropagation();
handleConfirmRename(session.id);
} else if (event.key === "Escape") {
event.preventDefault();
event.stopPropagation();
handleCancelRename();
}
}}
sx={{
flex: 1,
minWidth: 0,
"& .MuiOutlinedInput-root": {
height: 32,
bgcolor: alpha("#fff", 0.75),
borderRadius: 1.5,
transition: "all 0.2s ease-in-out",
"& fieldset": {
borderColor: alpha("#000", 0.08),
},
"&:hover fieldset": {
borderColor: alpha(theme.palette.primary.main, 0.4),
},
"&.Mui-focused fieldset": {
borderColor: theme.palette.primary.main,
borderWidth: "1.5px",
boxShadow: `0 0 0 3px ${alpha(theme.palette.primary.main, 0.1)}`,
},
},
"& .MuiInputBase-input": {
padding: "4px 10px",
fontSize: "0.85rem",
fontWeight: 700,
color: theme.palette.text.primary,
}
}}
/>
<IconButton
size="small"
aria-label="确认"
onClick={(event) => {
event.stopPropagation();
handleConfirmRename(session.id);
}}
disabled={!draftTitle.trim()}
sx={{
width: 28,
height: 28,
color: "success.main",
bgcolor: alpha(theme.palette.success.main, 0.1),
"&:hover": { bgcolor: alpha(theme.palette.success.main, 0.2) },
}}
>
<CheckRounded sx={{ fontSize: 16 }} />
</IconButton>
<IconButton
size="small"
aria-label="取消"
onClick={(event) => {
event.stopPropagation();
handleCancelRename();
}}
sx={{
width: 28,
height: 28,
color: "text.secondary",
bgcolor: alpha("#000", 0.05),
"&:hover": { bgcolor: alpha("#000", 0.1) },
}}
>
<CloseRounded sx={{ fontSize: 16 }} />
</IconButton>
</Stack>
) : (
<Box sx={{ minHeight: 46, display: "flex", flexDirection: "column", justifyContent: "center" }}>
<Typography
variant="body2"
fontWeight={isActive ? 800 : 700}
color="text.primary"
sx={{
overflow: "hidden",
textOverflow: "ellipsis",
display: "-webkit-box",
WebkitLineClamp: 2,
WebkitBoxOrient: "vertical",
}}
>
{session.title}
</Typography>
<Typography variant="caption" color="text.secondary" sx={{ mt: 0.5, display: "block" }}>
{formatRelativeDate(session.updatedAt)}
</Typography>
</Box>
)}
</Box>
<Tooltip title="删除会话">
<span>
<IconButton
size="small"
aria-label="删除会话"
onClick={(event) => {
event.stopPropagation();
setPendingDeleteSessionId(session.id);
setIsDeleteDialogOpen(true);
}}
sx={{
width: 24,
height: 24,
color: "text.secondary",
"&:hover": {
color: "error.main",
bgcolor: alpha("#ef5350", 0.08),
},
}}
>
<DeleteOutlineRounded sx={{ fontSize: 16 }} />
</IconButton>
</span>
</Tooltip>
<Stack direction="row" spacing={0.25} sx={{ display: editingSessionId === session.id ? 'none' : 'flex' }}>
<Tooltip title="修改会话标题">
<span>
<IconButton
size="small"
aria-label="修改会话标题"
onClick={(event) => {
event.stopPropagation();
handleStartRename(session.id, session.title);
}}
disabled={isHydrating || editingSessionId === session.id}
sx={{
width: 24,
height: 24,
color: "text.secondary",
"&:hover": {
color: "primary.main",
bgcolor: alpha("#00acc1", 0.08),
},
}}
>
<EditRounded sx={{ fontSize: 16 }} />
</IconButton>
</span>
</Tooltip>
<Tooltip title="删除会话">
<span>
<IconButton
size="small"
aria-label="删除会话"
onClick={(event) => {
event.stopPropagation();
setPendingDeleteSessionId(session.id);
setIsDeleteDialogOpen(true);
}}
sx={{
width: 24,
height: 24,
color: "text.secondary",
"&:hover": {
color: "error.main",
bgcolor: alpha("#ef5350", 0.08),
},
}}
>
<DeleteOutlineRounded sx={{ fontSize: 16 }} />
</IconButton>
</span>
</Tooltip>
</Stack>
</Stack>
</Paper>
);