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-app-shell.ts b/webui/src/web-components/sketch-app-shell.ts
index d351339..c9553b4 100644
--- a/webui/src/web-components/sketch-app-shell.ts
+++ b/webui/src/web-components/sketch-app-shell.ts
@@ -32,46 +32,15 @@
// Last commit information
@state()
- lastCommit: { hash: string; pushedBranch?: string } | null = null;
+
+ // Reference to the container status element
+ containerStatusElement: any = null;
// See https://lit.dev/docs/components/styles/ for how lit-element handles CSS.
// Note that these styles only apply to the scope of this web component's
// shadow DOM node, so they won't leak out or collide with CSS declared in
// other components or the containing web page (...unless you want it to do that).
static styles = css`
- /* Last commit display styling */
- .last-commit {
- display: flex;
- align-items: center;
- padding: 3px 8px;
- background: #f0f7ff;
- border: 1px solid #c8e1ff;
- border-radius: 4px;
- font-family: monospace;
- font-size: 12px;
- color: #0366d6;
- cursor: pointer;
- position: relative;
- margin: 0 10px;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- max-width: 180px;
- transition: background-color 0.2s ease;
- }
-
- .last-commit:hover {
- background-color: #dbedff;
- }
-
- .last-commit::before {
- content: "Last Commit: ";
- color: #666;
- margin-right: 4px;
- font-family: system-ui, sans-serif;
- font-size: 11px;
- }
-
.copied-indicator {
position: absolute;
top: -20px;
@@ -326,7 +295,7 @@
// Track if the last commit info has been copied
@state()
- lastCommitCopied: boolean = false;
+ // lastCommitCopied moved to sketch-container-status
// Track notification preferences
@state()
@@ -386,6 +355,9 @@
constructor() {
super();
+ // Reference to the container status element
+ this.containerStatusElement = null;
+
// Binding methods to this
this._handleViewModeSelect = this._handleViewModeSelect.bind(this);
this._handleShowCommitDiff = this._handleShowCommitDiff.bind(this);
@@ -412,6 +384,12 @@
connectedCallback() {
super.connectedCallback();
+ // Get reference to the container status element
+ setTimeout(() => {
+ this.containerStatusElement =
+ this.shadowRoot?.getElementById("container-status");
+ }, 0);
+
// Initialize client-side nav history.
const url = new URL(window.location.href);
const mode = url.searchParams.get("view") || "chat";
@@ -451,7 +429,12 @@
// Process existing messages for commit info
if (this.messages && this.messages.length > 0) {
- this.updateLastCommitInfo(this.messages);
+ // Update last commit info via container status component
+ setTimeout(() => {
+ if (this.containerStatusElement) {
+ this.containerStatusElement.updateLastCommitInfo(this.messages);
+ }
+ }, 100);
}
}
@@ -775,7 +758,10 @@
this.messages = aggregateAgentMessages(this.messages, newMessages);
// Process new messages to find commit messages
- this.updateLastCommitInfo(newMessages);
+ // Update last commit info via container status component
+ if (this.containerStatusElement) {
+ this.containerStatusElement.updateLastCommitInfo(newMessages);
+ }
// Check for agent messages with end_of_turn=true and show notifications
if (newMessages && newMessages.length > 0) {
@@ -803,54 +789,6 @@
this.updateDocumentTitle();
}
- // Update last commit information when new messages arrive
- private updateLastCommitInfo(newMessages: AgentMessage[]): void {
- if (!newMessages || newMessages.length === 0) return;
-
- // Process messages in chronological order (latest last)
- for (const message of newMessages) {
- if (
- message.type === "commit" &&
- message.commits &&
- message.commits.length > 0
- ) {
- // Get the first commit from the list
- const commit = message.commits[0];
- if (commit) {
- this.lastCommit = {
- hash: commit.hash,
- pushedBranch: commit.pushed_branch,
- };
- this.lastCommitCopied = false;
- }
- }
- }
- }
-
- // Copy commit info to clipboard
- private copyCommitInfo(event: MouseEvent): void {
- event.preventDefault();
- event.stopPropagation();
-
- if (!this.lastCommit) return;
-
- const textToCopy =
- this.lastCommit.pushedBranch || this.lastCommit.hash.substring(0, 8);
-
- navigator.clipboard
- .writeText(textToCopy)
- .then(() => {
- this.lastCommitCopied = true;
- // Reset the copied state after 2 seconds
- setTimeout(() => {
- this.lastCommitCopied = false;
- }, 2000);
- })
- .catch((err) => {
- console.error("Failed to copy commit info:", err);
- });
- }
-
private async _handleStopClick(): Promise<void> {
try {
const response = await fetch("cancel", {
@@ -936,31 +874,13 @@
<!-- Container status info moved above tabs -->
<sketch-container-status
.state=${this.containerState}
+ id="container-status"
></sketch-container-status>
- <!-- Views section with tabs - repositioned -->
- <sketch-view-mode-select></sketch-view-mode-select>
+ <!-- Last Commit section moved to sketch-container-status -->
- ${this.lastCommit
- ? html`
- <div
- class="last-commit"
- @click=${(e: MouseEvent) => this.copyCommitInfo(e)}
- title="Click to copy"
- >
- ${this.lastCommitCopied
- ? html`<span class="copied-indicator">Copied!</span>`
- : ""}
- ${this.lastCommit.pushedBranch
- ? html`<span class="commit-branch-indicator"
- >${this.lastCommit.pushedBranch}</span
- >`
- : html`<span class="commit-hash-indicator"
- >${this.lastCommit.hash.substring(0, 8)}</span
- >`}
- </div>
- `
- : ""}
+ <!-- Views section with tabs -->
+ <sketch-view-mode-select></sketch-view-mode-select>
<div class="refresh-control">
<button
@@ -1054,6 +974,9 @@
<sketch-timeline
.messages=${this.messages}
.scrollContainer=${this.scrollContainerRef}
+ .agentState=${this.containerState?.agent_state}
+ .llmCalls=${this.containerState?.outstanding_llm_calls || 0}
+ .toolCalls=${this.containerState?.outstanding_tool_calls || []}
></sketch-timeline>
</div>
<div
@@ -1124,7 +1047,10 @@
// Process any existing messages to find commit information
if (this.messages && this.messages.length > 0) {
- this.updateLastCommitInfo(this.messages);
+ // Update last commit info via container status component
+ if (this.containerStatusElement) {
+ this.containerStatusElement.updateLastCommitInfo(this.messages);
+ }
}
}
}