fix(chat): 优化权限请求折叠状态
Build Push and Deploy / docker-image (push) Successful in 2m28s
Build Push and Deploy / deploy-fallback-log (push) Has been skipped

This commit is contained in:
2026-06-08 13:44:23 +08:00
parent e32823e4b5
commit d31565d52c
+68 -53
View File
@@ -20,6 +20,7 @@ import {
alpha, alpha,
useTheme, useTheme,
} from "@mui/material"; } from "@mui/material";
import type { Theme } from "@mui/material/styles";
import ContentCopyRounded from "@mui/icons-material/ContentCopyRounded"; import ContentCopyRounded from "@mui/icons-material/ContentCopyRounded";
import RefreshRounded from "@mui/icons-material/RefreshRounded"; import RefreshRounded from "@mui/icons-material/RefreshRounded";
import EditRounded from "@mui/icons-material/EditRounded"; import EditRounded from "@mui/icons-material/EditRounded";
@@ -149,6 +150,27 @@ const getPermissionStatusLabel = (status: NonNullable<Message["permissions"]>[nu
}; };
const pendingPermissionColor = "#f9a825"; const pendingPermissionColor = "#f9a825";
const approvedOncePermissionColor = "#00838f";
const getPermissionStatusColor = (
status: NonNullable<Message["permissions"]>[number]["status"],
theme: Theme,
) => {
if (status === "approved_once") return approvedOncePermissionColor;
if (status === "approved_always") return theme.palette.success.main;
if (status === "rejected" || status === "error") return theme.palette.error.main;
return pendingPermissionColor;
};
const getPermissionStatusTextColor = (
status: NonNullable<Message["permissions"]>[number]["status"],
theme: Theme,
) => {
if (status === "approved_once") return "#006c78";
if (status === "approved_always") return theme.palette.success.dark;
if (status === "rejected" || status === "error") return theme.palette.error.main;
return "#8a5a00";
};
const PermissionRequestCard = ({ const PermissionRequestCard = ({
permission, permission,
@@ -162,19 +184,9 @@ const PermissionRequestCard = ({
const isSubmitting = permission.status === "submitting"; const isSubmitting = permission.status === "submitting";
const primaryValue = getPermissionPrimaryValue(permission); const primaryValue = getPermissionPrimaryValue(permission);
const metadataText = formatMetadata(permission.metadata); const metadataText = formatMetadata(permission.metadata);
const accentColor = const accentColor = getPermissionStatusColor(permission.status, theme);
permission.status === "rejected" || permission.status === "error" const statusTextColor = getPermissionStatusTextColor(permission.status, theme);
? theme.palette.error.main
: permission.status === "pending" || permission.status === "submitting"
? pendingPermissionColor
: theme.palette.success.main;
const statusLabel = getPermissionStatusLabel(permission.status); const statusLabel = getPermissionStatusLabel(permission.status);
const statusColor =
permission.status === "rejected" || permission.status === "error"
? "error"
: permission.status === "pending" || permission.status === "submitting"
? "warning"
: "success";
return ( return (
<Box <Box
@@ -229,25 +241,14 @@ const PermissionRequestCard = ({
</Box> </Box>
<Chip <Chip
size="small" size="small"
color={statusColor}
label={statusLabel} label={statusLabel}
sx={{ sx={{
height: 24, height: 24,
fontSize: "0.7rem", fontSize: "0.7rem",
fontWeight: 800, fontWeight: 800,
borderRadius: "12px", borderRadius: "12px",
bgcolor: bgcolor: alpha(accentColor, 0.12),
statusColor === "success" color: statusTextColor,
? alpha(theme.palette.success.main, 0.12)
: statusColor === "error"
? alpha(theme.palette.error.main, 0.1)
: alpha(pendingPermissionColor, 0.14),
color:
statusColor === "success"
? theme.palette.success.dark
: statusColor === "error"
? theme.palette.error.main
: "#8a5a00",
"& .MuiChip-label": { px: 1 }, "& .MuiChip-label": { px: 1 },
}} }}
/> />
@@ -401,9 +402,11 @@ const PermissionRequestCard = ({
const PermissionRequestGroup = ({ const PermissionRequestGroup = ({
permissions, permissions,
isRunning,
onReply, onReply,
}: { }: {
permissions: NonNullable<Message["permissions"]>; permissions: NonNullable<Message["permissions"]>;
isRunning: boolean;
onReply: (requestId: string, reply: PermissionReply) => void; onReply: (requestId: string, reply: PermissionReply) => void;
}) => { }) => {
const theme = useTheme(); const theme = useTheme();
@@ -422,11 +425,12 @@ const PermissionRequestGroup = ({
); );
const summaryItems = [ const summaryItems = [
{ label: "共", value: permissions.length, color: theme.palette.text.secondary }, { label: "共", value: permissions.length, color: theme.palette.text.secondary },
{ label: "允许一次", value: onceCount, color: "#00838f" }, { label: "允许一次", value: onceCount, color: getPermissionStatusColor("approved_once", theme), textColor: getPermissionStatusTextColor("approved_once", theme) },
{ label: "始终允许", value: alwaysCount, color: theme.palette.success.main }, { label: "始终允许", value: alwaysCount, color: getPermissionStatusColor("approved_always", theme), textColor: getPermissionStatusTextColor("approved_always", theme) },
{ label: "拒绝", value: rejectedCount, color: theme.palette.error.main }, { label: "拒绝", value: rejectedCount, color: getPermissionStatusColor("rejected", theme), textColor: getPermissionStatusTextColor("rejected", theme) },
]; ];
const chipColor = pendingCount > 0 ? pendingPermissionColor : rejectedCount > 0 ? theme.palette.error.main : theme.palette.success.main; const chipColor = pendingCount > 0 ? getPermissionStatusColor("pending", theme) : rejectedCount > 0 ? getPermissionStatusColor("rejected", theme) : getPermissionStatusColor("approved_always", theme);
const chipTextColor = pendingCount > 0 ? getPermissionStatusTextColor("pending", theme) : rejectedCount > 0 ? getPermissionStatusTextColor("rejected", theme) : getPermissionStatusTextColor("approved_always", theme);
return ( return (
<Box <Box
@@ -498,14 +502,20 @@ const PermissionRequestGroup = ({
borderRadius: "11px", borderRadius: "11px",
bgcolor: alpha(item.color, 0.08), bgcolor: alpha(item.color, 0.08),
border: `1px solid ${alpha(item.color, 0.12)}`, border: `1px solid ${alpha(item.color, 0.12)}`,
color: item.color, color: "textColor" in item ? item.textColor : item.color,
fontSize: "0.7rem", fontSize: "0.7rem",
fontWeight: 800, fontWeight: 800,
lineHeight: 1, lineHeight: 1,
whiteSpace: "nowrap", whiteSpace: "nowrap",
}} }}
> >
<Box component="span" sx={{ color: alpha(item.color, 0.82), fontWeight: 700 }}> <Box
component="span"
sx={{
color: "textColor" in item ? item.textColor : item.color,
fontWeight: 700,
}}
>
{item.label} {item.label}
</Box> </Box>
<Box component="span">{item.value} </Box> <Box component="span">{item.value} </Box>
@@ -513,19 +523,21 @@ const PermissionRequestGroup = ({
))} ))}
</Stack> </Stack>
</Box> </Box>
<Chip {isRunning && pendingCount > 0 ? (
size="small" <Chip
label={`待确认 ${pendingCount}`} size="small"
sx={{ label={`待确认 ${pendingCount}`}
height: 24, sx={{
borderRadius: "12px", height: 24,
fontSize: "0.7rem", borderRadius: "12px",
fontWeight: 800, fontSize: "0.7rem",
color: chipColor, fontWeight: 800,
bgcolor: alpha(chipColor, 0.1), color: chipTextColor,
"& .MuiChip-label": { px: 1 }, bgcolor: alpha(chipColor, 0.1),
}} "& .MuiChip-label": { px: 1 },
/> }}
/>
) : null}
<IconButton <IconButton
size="small" size="small"
aria-label={expanded ? "收起权限请求" : "展开权限请求"} aria-label={expanded ? "收起权限请求" : "展开权限请求"}
@@ -545,17 +557,13 @@ const PermissionRequestGroup = ({
</IconButton> </IconButton>
</Stack> </Stack>
{!expanded && !hasPendingPermissions && latestPermissions.length > 0 ? ( {!expanded && isRunning && !hasPendingPermissions && latestPermissions.length > 0 ? (
<Stack spacing={0} sx={{ px: 1.5, pb: 1.25 }}> <Stack spacing={0} sx={{ px: 1.5, pb: 1.25 }}>
{latestPermissions.map((permission, index) => { {latestPermissions.map((permission, index) => {
const primaryValue = getPermissionPrimaryValue(permission); const primaryValue = getPermissionPrimaryValue(permission);
const isLast = index === latestPermissions.length - 1; const isLast = index === latestPermissions.length - 1;
const itemColor = const itemColor = getPermissionStatusColor(permission.status, theme);
permission.status === "rejected" || permission.status === "error" const itemTextColor = getPermissionStatusTextColor(permission.status, theme);
? theme.palette.error.main
: permission.status === "approved_once" || permission.status === "approved_always"
? theme.palette.success.main
: pendingPermissionColor;
return ( return (
<Stack <Stack
@@ -607,7 +615,7 @@ const PermissionRequestGroup = ({
borderRadius: "11px", borderRadius: "11px",
fontSize: "0.68rem", fontSize: "0.68rem",
fontWeight: 800, fontWeight: 800,
color: itemColor, color: itemTextColor,
bgcolor: alpha(itemColor, 0.08), bgcolor: alpha(itemColor, 0.08),
"& .MuiChip-label": { px: 0.85 }, "& .MuiChip-label": { px: 0.85 },
}} }}
@@ -619,7 +627,7 @@ const PermissionRequestGroup = ({
) : null} ) : null}
<AnimatePresence initial={false}> <AnimatePresence initial={false}>
{!expanded && hasPendingPermissions ? ( {!expanded && isRunning && hasPendingPermissions ? (
<motion.div <motion.div
key="pending-permissions" key="pending-permissions"
initial={{ opacity: 0, y: -10, height: 0 }} initial={{ opacity: 0, y: -10, height: 0 }}
@@ -678,6 +686,12 @@ export const AgentTurn = React.memo(
const [isEditing, setIsEditing] = React.useState(false); const [isEditing, setIsEditing] = React.useState(false);
const [editDraft, setEditDraft] = React.useState(message.content); const [editDraft, setEditDraft] = React.useState(message.content);
const rootMessageId = message.branchRootId ?? message.id; const rootMessageId = message.branchRootId ?? message.id;
const isProgressComplete = message.progress?.some(
(item) => item.phase === "complete" && item.status === "completed",
) ?? false;
const isProgressRunning = !isErrorMessage && !isProgressComplete && (
message.progress?.some((item) => item.status === "running") ?? false
);
const parsedAssistantSections = useMemo( const parsedAssistantSections = useMemo(
() => () =>
@@ -956,6 +970,7 @@ export const AgentTurn = React.memo(
{message.permissions?.length ? ( {message.permissions?.length ? (
<PermissionRequestGroup <PermissionRequestGroup
permissions={message.permissions} permissions={message.permissions}
isRunning={isProgressRunning}
onReply={onReplyPermission} onReply={onReplyPermission}
/> />
) : null} ) : null}