diff --git a/.gitignore b/.gitignore index 41d72c3ea9..6f46f0d97f 100644 --- a/.gitignore +++ b/.gitignore @@ -79,3 +79,7 @@ profile.txt src/version.ts OPENAI_FIX_SUMMARY.md docs/vercel/ +TESTING.md +FEATURE_SUMMARY.md +CODE_CHANGES.md +README_COMPACT_HERE.md diff --git a/src/components/AIView.tsx b/src/components/AIView.tsx index 01e60f3e8c..12597516ef 100644 --- a/src/components/AIView.tsx +++ b/src/components/AIView.tsx @@ -376,7 +376,11 @@ const AIViewInner: React.FC = ({ return ( - + {isAtCutoff && ( ⚠️ Messages below this line will be removed when you submit the edit diff --git a/src/components/Messages/MessageRenderer.tsx b/src/components/Messages/MessageRenderer.tsx index a2918980e7..40695d3099 100644 --- a/src/components/Messages/MessageRenderer.tsx +++ b/src/components/Messages/MessageRenderer.tsx @@ -11,11 +11,12 @@ interface MessageRendererProps { message: DisplayedMessage; className?: string; onEditUserMessage?: (messageId: string, content: string) => void; + workspaceId?: string; } // Memoized to prevent unnecessary re-renders when parent (AIView) updates export const MessageRenderer = React.memo( - ({ message, className, onEditUserMessage }) => { + ({ message, className, onEditUserMessage, workspaceId }) => { // Route based on message type switch (message.type) { case "user": @@ -23,7 +24,7 @@ export const MessageRenderer = React.memo( case "assistant": return ; case "tool": - return ; + return ; case "reasoning": return ; case "stream-error": diff --git a/src/components/Messages/ToolMessage.tsx b/src/components/Messages/ToolMessage.tsx index 607a1e3977..1a90d4460e 100644 --- a/src/components/Messages/ToolMessage.tsx +++ b/src/components/Messages/ToolMessage.tsx @@ -21,6 +21,7 @@ import type { interface ToolMessageProps { message: DisplayedMessage & { type: "tool" }; className?: string; + workspaceId?: string; } // Type guard for bash tool @@ -71,7 +72,7 @@ function isProposePlanTool(toolName: string, args: unknown): args is ProposePlan return toolName === "propose_plan" && typeof args === "object" && args !== null && "plan" in args; } -export const ToolMessage: React.FC = ({ message, className }) => { +export const ToolMessage: React.FC = ({ message, className, workspaceId }) => { // Route to specialized components based on tool name if (isBashTool(message.toolName, message.args)) { return ( @@ -130,6 +131,7 @@ export const ToolMessage: React.FC = ({ message, className }) args={message.args} result={message.result as ProposePlanToolResult | undefined} status={message.status} + workspaceId={workspaceId} /> ); diff --git a/src/components/tools/ProposePlanToolCall.tsx b/src/components/tools/ProposePlanToolCall.tsx index 3560c2c8a0..9f0b5f6de8 100644 --- a/src/components/tools/ProposePlanToolCall.tsx +++ b/src/components/tools/ProposePlanToolCall.tsx @@ -12,6 +12,7 @@ import { import { useToolExpansion, getStatusDisplay, type ToolStatus } from "./shared/toolUtils"; import { MarkdownRenderer } from "../Messages/MarkdownRenderer"; import { formatKeybind, KEYBINDS } from "@/utils/ui/keybinds"; +import { createCmuxMessage } from "@/types/message"; const PlanContainer = styled.div` padding: 12px; @@ -238,16 +239,19 @@ interface ProposePlanToolCallProps { args: ProposePlanToolArgs; result?: ProposePlanToolResult; status?: ToolStatus; + workspaceId?: string; } export const ProposePlanToolCall: React.FC = ({ args, result: _result, status = "pending", + workspaceId, }) => { const { expanded, toggleExpanded } = useToolExpansion(true); // Expand by default const [showRaw, setShowRaw] = useState(false); const [copied, setCopied] = useState(false); + const [isCompacting, setIsCompacting] = useState(false); const statusDisplay = getStatusDisplay(status); @@ -261,6 +265,37 @@ export const ProposePlanToolCall: React.FC = ({ } }; + const handleCompactHere = async () => { + if (!workspaceId || isCompacting) return; + + setIsCompacting(true); + try { + // Create a compacted message with the plan content + // Format: Title as H1 + plan content + const compactedContent = `# ${args.title}\n\n${args.plan}`; + + const summaryMessage = createCmuxMessage( + `compact-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`, + "assistant", + compactedContent, + { + timestamp: Date.now(), + compacted: true, + } + ); + + const result = await window.api.workspace.replaceChatHistory(workspaceId, summaryMessage); + + if (!result.success) { + console.error("Failed to compact:", result.error); + } + } catch (err) { + console.error("Compact error:", err); + } finally { + setIsCompacting(false); + } + }; + return ( @@ -278,6 +313,11 @@ export const ProposePlanToolCall: React.FC = ({ {args.title} + {workspaceId && ( + void handleCompactHere()} disabled={isCompacting}> + {isCompacting ? "Compacting..." : "📦 Compact Here"} + + )} void handleCopy()}> {copied ? "✓ Copied" : "Copy"}