| | |
| | | import { clearAiSessionMemory, getAiRuntime, getAiSessions, pinAiSession, removeAiSession, renameAiSession, retainAiSessionLatestRound, streamAiChat } from "@/api/ai/chat"; |
| | | |
| | | const DEFAULT_PROMPT_CODE = "home.default"; |
| | | const AI_CHAT_DRAWER_Z_INDEX = 1400; |
| | | const AI_CHAT_DIALOG_Z_INDEX = AI_CHAT_DRAWER_Z_INDEX + 20; |
| | | const THINKING_PHASE_ORDER = { |
| | | ANALYZE: 0, |
| | | TOOL_CALL: 1, |
| | |
| | | const [drawerError, setDrawerError] = useState(""); |
| | | const [sessionKeyword, setSessionKeyword] = useState(""); |
| | | const [renameDialog, setRenameDialog] = useState({ open: false, sessionId: null, title: "" }); |
| | | const [runtimePanelExpanded, setRuntimePanelExpanded] = useState(false); |
| | | |
| | | const quickLinks = useMemo(() => ([ |
| | | { label: translate("menu.aiParam"), path: "/aiParam", icon: <SettingsSuggestOutlinedIcon fontSize="small" /> }, |
| | |
| | | |
| | | useEffect(() => { |
| | | if (open) { |
| | | setRuntimePanelExpanded(false); |
| | | initializeDrawer(); |
| | | } else { |
| | | stopStream(false); |
| | |
| | | onClose={onClose} |
| | | ModalProps={{ keepMounted: true }} |
| | | sx={{ |
| | | zIndex: 1400, |
| | | zIndex: AI_CHAT_DRAWER_Z_INDEX, |
| | | "& .MuiDrawer-paper": { |
| | | top: 0, |
| | | height: "100vh", |
| | |
| | | |
| | | <Box flex={1} display="flex" flexDirection="column" minHeight={0}> |
| | | <Box px={2} py={1.5}> |
| | | <Stack direction="row" spacing={1} flexWrap="wrap" useFlexGap> |
| | | <Chip size="small" label={translate("ai.drawer.requestMetric", { value: runtimeSummary.requestId })} /> |
| | | <Chip size="small" label={translate("ai.drawer.sessionMetric", { id: sessionId || "--" })} /> |
| | | <Chip size="small" label={translate("ai.drawer.promptMetric", { value: runtimeSummary.promptName })} /> |
| | | <Chip size="small" label={translate("ai.drawer.modelMetric", { value: runtimeSummary.model })} /> |
| | | <Chip size="small" label={translate("ai.drawer.mcpMetric", { value: runtimeSummary.mountedMcpCount })} /> |
| | | <Chip size="small" label={translate("ai.drawer.historyMetric", { value: persistedMessages.length })} /> |
| | | <Chip size="small" label={translate("ai.drawer.recentMetric", { value: runtimeSummary.recentMessageCount })} /> |
| | | <Chip size="small" color={runtimeSummary.hasSummary ? "success" : "default"} label={translate(runtimeSummary.hasSummary ? "ai.drawer.hasSummary" : "ai.drawer.noSummary")} /> |
| | | <Chip size="small" color={runtimeSummary.hasFacts ? "info" : "default"} label={translate(runtimeSummary.hasFacts ? "ai.drawer.hasFacts" : "ai.drawer.noFacts")} /> |
| | | <Stack direction="row" alignItems="center" justifyContent="space-between" spacing={1}> |
| | | <Typography variant="subtitle2"> |
| | | {translate("ai.drawer.runtimeOverview")} |
| | | </Typography> |
| | | <Button |
| | | size="small" |
| | | onClick={() => setRuntimePanelExpanded((prev) => !prev)} |
| | | endIcon={runtimePanelExpanded |
| | | ? <ExpandLessOutlinedIcon fontSize="small" /> |
| | | : <ExpandMoreOutlinedIcon fontSize="small" />} |
| | | sx={{ minWidth: "auto", px: 1 }} |
| | | > |
| | | {translate(runtimePanelExpanded ? "ai.drawer.runtimeCollapse" : "ai.drawer.runtimeExpand")} |
| | | </Button> |
| | | </Stack> |
| | | <Stack direction="row" spacing={1} mt={1.5} flexWrap="wrap" useFlexGap> |
| | | {quickLinks.map((item) => ( |
| | | <Collapse in={runtimePanelExpanded} timeout="auto" unmountOnExit> |
| | | <Stack direction="row" spacing={1} flexWrap="wrap" useFlexGap sx={{ mt: 1.5 }}> |
| | | <Chip size="small" label={translate("ai.drawer.requestMetric", { value: runtimeSummary.requestId })} /> |
| | | <Chip size="small" label={translate("ai.drawer.sessionMetric", { id: sessionId || "--" })} /> |
| | | <Chip size="small" label={translate("ai.drawer.promptMetric", { value: runtimeSummary.promptName })} /> |
| | | <Chip size="small" label={translate("ai.drawer.modelMetric", { value: runtimeSummary.model })} /> |
| | | <Chip size="small" label={translate("ai.drawer.mcpMetric", { value: runtimeSummary.mountedMcpCount })} /> |
| | | <Chip size="small" label={translate("ai.drawer.historyMetric", { value: persistedMessages.length })} /> |
| | | <Chip size="small" label={translate("ai.drawer.recentMetric", { value: runtimeSummary.recentMessageCount })} /> |
| | | <Chip size="small" color={runtimeSummary.hasSummary ? "success" : "default"} label={translate(runtimeSummary.hasSummary ? "ai.drawer.hasSummary" : "ai.drawer.noSummary")} /> |
| | | <Chip size="small" color={runtimeSummary.hasFacts ? "info" : "default"} label={translate(runtimeSummary.hasFacts ? "ai.drawer.hasFacts" : "ai.drawer.noFacts")} /> |
| | | </Stack> |
| | | <Stack direction="row" spacing={1} mt={1.5} flexWrap="wrap" useFlexGap> |
| | | {quickLinks.map((item) => ( |
| | | <Button |
| | | key={item.path} |
| | | size="small" |
| | | variant="outlined" |
| | | startIcon={item.icon} |
| | | onClick={() => navigate(item.path)} |
| | | > |
| | | {item.label} |
| | | </Button> |
| | | ))} |
| | | <Button |
| | | key={item.path} |
| | | size="small" |
| | | variant="outlined" |
| | | startIcon={item.icon} |
| | | onClick={() => navigate(item.path)} |
| | | startIcon={<HistoryOutlinedIcon />} |
| | | onClick={handleRetainLatestRound} |
| | | disabled={!sessionId || streaming} |
| | | > |
| | | {item.label} |
| | | {translate("ai.drawer.retainLatestRound")} |
| | | </Button> |
| | | ))} |
| | | <Button |
| | | size="small" |
| | | variant="outlined" |
| | | startIcon={<HistoryOutlinedIcon />} |
| | | onClick={handleRetainLatestRound} |
| | | disabled={!sessionId || streaming} |
| | | > |
| | | {translate("ai.drawer.retainLatestRound")} |
| | | </Button> |
| | | <Button |
| | | size="small" |
| | | variant="outlined" |
| | | color="warning" |
| | | startIcon={<AutoDeleteOutlinedIcon />} |
| | | onClick={handleClearMemory} |
| | | disabled={!sessionId || streaming} |
| | | > |
| | | {translate("ai.drawer.clearMemory")} |
| | | </Button> |
| | | </Stack> |
| | | {!!runtime?.memorySummary && ( |
| | | <Alert severity="info" sx={{ mt: 1.5 }}> |
| | | <Typography variant="body2" sx={{ whiteSpace: "pre-wrap" }}> |
| | | {runtime.memorySummary} |
| | | </Typography> |
| | | </Alert> |
| | | )} |
| | | {!!runtime?.memoryFacts && ( |
| | | <Alert severity="success" sx={{ mt: 1.5 }}> |
| | | <Typography variant="body2" sx={{ whiteSpace: "pre-wrap" }}> |
| | | {runtime.memoryFacts} |
| | | </Typography> |
| | | </Alert> |
| | | )} |
| | | <Button |
| | | size="small" |
| | | variant="outlined" |
| | | color="warning" |
| | | startIcon={<AutoDeleteOutlinedIcon />} |
| | | onClick={handleClearMemory} |
| | | disabled={!sessionId || streaming} |
| | | > |
| | | {translate("ai.drawer.clearMemory")} |
| | | </Button> |
| | | </Stack> |
| | | {!!runtime?.memorySummary && ( |
| | | <Alert severity="info" sx={{ mt: 1.5 }}> |
| | | <Typography variant="body2" sx={{ whiteSpace: "pre-wrap" }}> |
| | | {runtime.memorySummary} |
| | | </Typography> |
| | | </Alert> |
| | | )} |
| | | {!!runtime?.memoryFacts && ( |
| | | <Alert severity="success" sx={{ mt: 1.5 }}> |
| | | <Typography variant="body2" sx={{ whiteSpace: "pre-wrap" }}> |
| | | {runtime.memoryFacts} |
| | | </Typography> |
| | | </Alert> |
| | | )} |
| | | </Collapse> |
| | | {loadingRuntime && ( |
| | | <Typography variant="body2" color="text.secondary" mt={1}> |
| | | {translate("ai.drawer.loadingRuntime")} |
| | |
| | | </Box> |
| | | </Box> |
| | | </Box> |
| | | <Dialog open={renameDialog.open} onClose={closeRenameDialog} fullWidth maxWidth="xs"> |
| | | <Dialog |
| | | open={renameDialog.open} |
| | | onClose={closeRenameDialog} |
| | | fullWidth |
| | | maxWidth="xs" |
| | | sx={{ |
| | | zIndex: AI_CHAT_DIALOG_Z_INDEX, |
| | | }} |
| | | > |
| | | <DialogTitle>{translate("ai.drawer.renameDialogTitle")}</DialogTitle> |
| | | <DialogContent> |
| | | <TextField |