Overhaul UI with chat-like interface
Major UI improvements:
- Revamp timeline messages with chat-like interface
- User messages now on right with white text on blue background
- Agent/tool messages on left with black text on grey background
- Chat bubbles extend up to 80% of screen width
- Maintain left-aligned text for code readability
- Move metadata to outer gutters
- Show turn duration for end-of-turn messages
- Integrate tool calls within agent message bubbles
- Add thinking indicator with animated dots when LLM is processing
- Replace buttons with intuitive icons (copy, info, etc.)
- Improve tool call presentation
- Simplify to single row design with all essential info
- Add clear status indicators for success/pending/error
- Fix horizontal scrolling for long commands and outputs
- Prevent tool name truncation
- Improve spacing and alignment throughout
- Enhance header and status displays
- Move Last Commit to dedicated third column in header grid
- Add proper labeling with two-row structure
- Provide consistent styling across all status elements
- Other UI refinements
- Add root URL redirection to demo page
- Fix spacing throughout the interface
- Optimize CSS for better performance
- Ensure consistent styling across components
- Improve command output display and wrapping
Co-Authored-By: sketch <hello@sketch.dev>
diff --git a/webui/src/web-components/sketch-timeline-message.test.ts b/webui/src/web-components/sketch-timeline-message.test.ts
index 3d9b799..bdab45a 100644
--- a/webui/src/web-components/sketch-timeline-message.test.ts
+++ b/webui/src/web-components/sketch-timeline-message.test.ts
@@ -85,6 +85,7 @@
test("formats timestamps correctly", async ({ mount }) => {
const message = createMockMessage({
timestamp: "2023-05-15T12:00:00Z",
+ type: "agent",
});
const component = await mount(SketchTimelineMessage, {
@@ -93,15 +94,36 @@
},
});
- await expect(component.locator(".message-timestamp")).toBeVisible();
- // Should include a formatted date like "May 15, 2023"
- await expect(component.locator(".message-timestamp")).toContainText(
+ // Toggle the info panel to view timestamps
+ await component.locator(".info-icon").click();
+ await expect(component.locator(".message-info-panel")).toBeVisible();
+
+ // Find the timestamp in the info panel
+ const timeInfoRow = component.locator(".info-row", { hasText: "Time:" });
+ await expect(timeInfoRow).toBeVisible();
+ await expect(timeInfoRow.locator(".info-value")).toContainText(
"May 15, 2023",
);
- // Should include elapsed time
- await expect(component.locator(".message-timestamp")).toContainText(
- "(1.50s)",
- );
+ // For end-of-turn messages, duration is shown separately
+ const endOfTurnMessage = createMockMessage({
+ timestamp: "2023-05-15T12:00:00Z",
+ type: "agent",
+ end_of_turn: true,
+ });
+
+ const endOfTurnComponent = await mount(SketchTimelineMessage, {
+ props: {
+ message: endOfTurnMessage,
+ },
+ });
+
+ // For end-of-turn messages, duration is shown in the end-of-turn indicator
+ await expect(
+ endOfTurnComponent.locator(".end-of-turn-indicator"),
+ ).toBeVisible();
+ await expect(
+ endOfTurnComponent.locator(".end-of-turn-indicator"),
+ ).toContainText("1.5s");
});
test("renders markdown content correctly", async ({ mount }) => {
@@ -148,14 +170,24 @@
},
});
- await expect(component.locator(".message-usage")).toBeVisible();
- await expect(component.locator(".message-usage")).toContainText(
- "200".toLocaleString(),
- ); // In (150 + 50 cache)
- await expect(component.locator(".message-usage")).toContainText(
- "300".toLocaleString(),
- ); // Out
- await expect(component.locator(".message-usage")).toContainText("$0.03"); // Cost
+ // Toggle the info panel to view usage information
+ await component.locator(".info-icon").click();
+ await expect(component.locator(".message-info-panel")).toBeVisible();
+
+ // Find the tokens info in the info panel
+ const tokensInfoRow = component.locator(".info-row", { hasText: "Tokens:" });
+ await expect(tokensInfoRow).toBeVisible();
+ await expect(tokensInfoRow).toContainText("Input: " + "150".toLocaleString());
+ await expect(tokensInfoRow).toContainText(
+ "Cache read: " + "50".toLocaleString(),
+ );
+ // Check for output tokens
+ await expect(tokensInfoRow).toContainText(
+ "Output: " + "300".toLocaleString(),
+ );
+
+ // Check for cost
+ await expect(tokensInfoRow).toContainText("Cost: $0.03");
});
test("renders commit information correctly", async ({ mount }) => {
@@ -179,8 +211,10 @@
});
await expect(component.locator(".commits-container")).toBeVisible();
- await expect(component.locator(".commits-header")).toBeVisible();
- await expect(component.locator(".commits-header")).toContainText("1 new");
+ await expect(component.locator(".commit-notification")).toBeVisible();
+ await expect(component.locator(".commit-notification")).toContainText(
+ "1 new",
+ );
await expect(component.locator(".commit-hash")).toBeVisible();
await expect(component.locator(".commit-hash")).toHaveText("12345678"); // First 8 chars