"use client"; import { memo, useEffect, useMemo, useState, useCallback } from "react"; import { IconX, IconChevronDown, IconPlayerPlay, IconPlus } from "@tabler/icons-react"; import { TabsContent } from "@kandev/ui/tabs"; import { Button } from "@kandev/ui/button"; import { SessionPanel, SessionPanelContent } from "@kandev/ui/pannel-session"; import { Tooltip, TooltipContent, TooltipTrigger } from "@kandev/ui/tooltip"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from "@kandev/ui/dropdown-menu"; import { useAppStore } from "@/components/state-provider"; import { useLayoutStore } from "@/lib/state/layout-store"; // closeDocument kept for mobile/tablet backward compat import { getWebSocketClient } from "@/lib/ws/connection"; import { useTask } from "@/hooks/use-task"; import { SessionTabs, type SessionTab } from "@/components/session-tabs"; import { TaskPlanPanel } from "../task-plan-panel"; import { TaskCreateDialog } from "@/components/task-create-dialog"; import type { ActiveDocument } from "@/lib/state/slices/ui/types"; type DocumentPanelProps = { sessionId: string | null; }; export const DocumentPanel = memo(function DocumentPanel({ sessionId }: DocumentPanelProps) { const activeDocument = useAppStore((state) => sessionId ? (state.documentPanel.activeDocumentBySessionId[sessionId] ?? null) : null, ); const setActiveDocument = useAppStore((state) => state.setActiveDocument); const closeDocument = useLayoutStore((state) => state.closeDocument); const handleClose = () => { if (!sessionId) return; closeDocument(sessionId); setActiveDocument(sessionId, null); }; // Auto-close document layout when no document is active (e.g. stale localStorage) useEffect(() => { if (!activeDocument && sessionId) { closeDocument(sessionId); } }, [activeDocument, sessionId, closeDocument]); const tabs: SessionTab[] = useMemo(() => { if (!activeDocument) return []; if (activeDocument.type === "plan") { return [{ id: "plan", label: "Plan" }]; } return [{ id: "file", label: activeDocument.name }]; }, [activeDocument]); const activeTab = activeDocument?.type === "plan" ? "plan" : "file"; if (!activeDocument) { return null; } const isPlan = activeDocument.type === "plan"; const headerActions = (
{isPlan && } Close document panel
); return ( {}} className="flex-1 min-h-0 flex flex-col gap-2" rightContent={headerActions} > ); }); /** Dropdown button for implementing the plan */ const ImplementPlanDropdown = memo(function ImplementPlanDropdown({ sessionId, onClose, }: { sessionId: string | null; onClose: () => void; }) { const taskId = useAppStore((state) => state.tasks.activeTaskId); const task = useTask(taskId); const plan = useAppStore((state) => (taskId ? (state.taskPlans.byTaskId[taskId] ?? null) : null)); const [showNewSessionDialog, setShowNewSessionDialog] = useState(false); const handleImplement = useCallback(async () => { if (!taskId || !sessionId || !plan?.content) return; const client = getWebSocketClient(); if (!client) return; // Close plan mode first onClose(); // Send the plan as an implementation prompt (not in plan mode) try { await client.request( "message.add", { task_id: taskId, session_id: sessionId, content: `Implement the following plan:\n\n${plan.content}`, }, 10000, ); } catch (err) { console.error("Failed to send implement plan message:", err); } }, [taskId, sessionId, plan, onClose]); const handleImplementNewSession = useCallback(() => { // Close plan mode and open the new session dialog onClose(); setShowNewSessionDialog(true); }, [onClose]); return ( <>
Implement in a new session
Starts fresh with a clean context window
); }); const DocumentPlanContent = memo(function DocumentPlanContent() { const activeTaskId = useAppStore((state) => state.tasks.activeTaskId); return ; }); const DocumentFileContent = memo(function DocumentFileContent({ doc }: { doc: ActiveDocument }) { if (doc.type === "plan") return null; return (
File viewer: {doc.path}
); });