import { useI18n } from "../i18n"; import { useLayoutEffect, useRef } from "react"; import { Settings2, X } from "lucide-react"; import ReactMarkdown from "react-markdown"; import remarkGfm from "remark-gfm"; import type { ActivityEntry, ActivityKind } from "../api/types"; import { useSessionStore } from "../store/sessionStore"; import { Button } from "../components/ui/button"; import { isNearBottom } from "./activityScroll"; const VISIBLE_MODEL_ACTIVITY_KINDS: ReadonlySet = new Set([ "prompt", "thinking", "assistant", ]); const ACTIVITY_LABELS: Readonly>> = { prompt: "Question", thinking: "Reasoning", assistant: "Response", }; export function isVisibleModelActivity(entry: ActivityEntry): boolean { return VISIBLE_MODEL_ACTIVITY_KINDS.has(entry.kind); } export function formatModelActivity(text: string): string { return text .replace(/[\s\S]*?<\/think>/g, "") .replace(/<\/?think>/g, "") .replace(/\r\n?/g, "\n") .replace(/([.!?])(?=[A-ZÀ-ÖØ-Þ])/g, "$1\n\n") .replace(/^[\t ]*[•‣–]\s+/gm, "- ") .replace(/^(\d+)\)\s+/gm, (_, n) => `${n}. `) .replace(/\n(?=\S)(?![-*+] |\d+\. |>|`|\|)/g, "\n\n") .replace(/\n{3,}/g, "\n\n") .trim(); } function MarkdownBody({ entry }: { entry: ActivityEntry }) { const tone = entry.kind === "thinking" ? "text-muted-foreground" : "text-foreground"; return (
{formatModelActivity(entry.text)}
); } function ActivityRow({ entry }: { entry: ActivityEntry }) { const { t: translate } = useI18n(); return (
{translate(ACTIVITY_LABELS[entry.kind] ?? entry.kind)} {entry.phase && {entry.phase}}
); } /** Left drawer showing the user-facing projection of the in-memory model activity timeline. */ export function ModelActivityPanel({ desktopSplit = false, onClose, onOpenWorkspaceManager, hidden = false, }: { desktopSplit?: boolean; onClose: () => void; onOpenWorkspaceManager?: () => void; hidden?: boolean; }) { const { t: translate } = useI18n(); const activityLog = useSessionStore((s) => s.activityLog); const visibleActivity = activityLog.filter(isVisibleModelActivity); const visibleCount = visibleActivity.length; const visibleTail = visibleActivity.at(-1); const scrollRef = useRef(null); const followRef = useRef(true); useLayoutEffect(() => { const viewport = scrollRef.current; if (viewport && followRef.current) viewport.scrollTop = viewport.scrollHeight; }, [visibleCount, visibleTail]); return ( ); }