diff --git a/components/ChatWindow.tsx b/components/ChatWindow.tsx index 794a34cf3..8f6eda692 100644 --- a/components/ChatWindow.tsx +++ b/components/ChatWindow.tsx @@ -9,6 +9,7 @@ import { MessageView } from "./MessageView"; import { ChatInput, type ChatInputHandle } from "./ChatInput"; import { ChatMinimap, useMessageRefs } from "./ChatMinimap"; import { ExtensionStatusBar } from "./ExtensionStatusBar"; +import { ExtensionWidgets } from "./ExtensionWidgets"; import { useI18n } from "@/hooks/useI18n"; import { useAgentSession, type AgentPhase, type NoticeItem } from "@/hooks/useAgentSession"; import { useAudio } from "@/hooks/useAudio"; @@ -486,7 +487,9 @@ export function ChatWindow({ session, newSessionCwd, onAgentEnd, onSessionCreate + {chatInputElement} + ) : ( @@ -510,8 +513,6 @@ export function ChatWindow({ session, newSessionCwd, onAgentEnd, onSessionCreate
- - {(() => { const toolResultsMap = new Map(); for (const msg of messages) { @@ -746,40 +747,24 @@ export function ChatWindow({ session, newSessionCwd, onAgentEnd, onSessionCreate }} >
- +
{chatInputElement} - -
- - )} -
- ); -} - -function ExtensionWidgets({ widgets }: { widgets: Array<{ key: string; lines: string[] }> }) { - if (widgets.length === 0) return null; - return ( -
- {widgets.map((widget) => (
-
- {widget.key} +
+
-
-            {widget.lines.join("\n")}
-          
- ))} + +
+ + )}
); } diff --git a/components/ExtensionWidgets.test.mjs b/components/ExtensionWidgets.test.mjs new file mode 100644 index 000000000..00c520b2f --- /dev/null +++ b/components/ExtensionWidgets.test.mjs @@ -0,0 +1,41 @@ +import assert from "node:assert/strict"; +import test from "node:test"; +import React from "react"; +import { renderToStaticMarkup } from "react-dom/server"; +import { createJiti } from "jiti"; + +const jiti = createJiti(import.meta.url, { + jsx: { runtime: "automatic" }, + tsconfigPaths: true, +}); +const { + ExtensionWidgets, + MAX_EXTENSION_WIDGET_LINES, +} = await jiti.import("./ExtensionWidgets.tsx"); + +test("renders short extension widgets without a truncation marker", () => { + const html = renderToStaticMarkup( + React.createElement(ExtensionWidgets, { + widgets: [{ key: "short", lines: ["first", "second"] }], + }), + ); + + assert.match(html, /first\nsecond/); + assert.doesNotMatch(html, /widget truncated/); +}); + +test("matches the Pi TUI extension widget line limit", () => { + const lines = Array.from( + { length: MAX_EXTENSION_WIDGET_LINES + 2 }, + (_, index) => `line-${index + 1}`, + ); + const html = renderToStaticMarkup( + React.createElement(ExtensionWidgets, { + widgets: [{ key: "long", lines }], + }), + ); + + assert.match(html, new RegExp(`line-${MAX_EXTENSION_WIDGET_LINES}`)); + assert.doesNotMatch(html, new RegExp(`line-${MAX_EXTENSION_WIDGET_LINES + 1}`)); + assert.match(html, /\.\.\. \(widget truncated\)/); +}); diff --git a/components/ExtensionWidgets.tsx b/components/ExtensionWidgets.tsx new file mode 100644 index 000000000..1e66c5739 --- /dev/null +++ b/components/ExtensionWidgets.tsx @@ -0,0 +1,37 @@ +"use client"; + +export const MAX_EXTENSION_WIDGET_LINES = 10; + +function getDisplayLines(lines: string[]): string[] { + if (lines.length <= MAX_EXTENSION_WIDGET_LINES) return lines; + return [ + ...lines.slice(0, MAX_EXTENSION_WIDGET_LINES), + "... (widget truncated)", + ]; +} + +export function ExtensionWidgets({ widgets }: { widgets: Array<{ key: string; lines: string[] }> }) { + if (widgets.length === 0) return null; + return ( +
+ {widgets.map((widget) => ( +
+
+ {widget.key} +
+
+            {getDisplayLines(widget.lines).join("\n")}
+          
+
+ ))} +
+ ); +}