webui: add tool calls display to mobile chat timeline
Co-Authored-By: sketch <hello@sketch.dev>
Change-ID: s654197301b832e70k
diff --git a/webui/src/web-components/mobile-chat.ts b/webui/src/web-components/mobile-chat.ts
index c746686..74381cc 100644
--- a/webui/src/web-components/mobile-chat.ts
+++ b/webui/src/web-components/mobile-chat.ts
@@ -241,6 +241,53 @@
.markdown-content i {
font-style: italic;
}
+
+ /* Tool calls styling for mobile */
+ .tool-calls {
+ margin-top: 12px;
+ display: flex;
+ flex-direction: column;
+ gap: 6px;
+ }
+
+ .tool-call-item {
+ background-color: rgba(0, 0, 0, 0.04);
+ border-radius: 8px;
+ padding: 6px 8px;
+ font-size: 12px;
+ font-family: monospace;
+ line-height: 1.3;
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ }
+
+ .tool-status-icon {
+ flex-shrink: 0;
+ font-size: 14px;
+ }
+
+ .tool-name {
+ font-weight: bold;
+ color: #333;
+ flex-shrink: 0;
+ margin-right: 2px;
+ }
+
+ .tool-summary {
+ color: #555;
+ flex-grow: 1;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ }
+
+ .tool-duration {
+ font-size: 10px;
+ color: #888;
+ flex-shrink: 0;
+ margin-left: 4px;
+ }
`;
updated(changedProperties: Map<string, any>) {
@@ -359,6 +406,116 @@
}
}
+ private renderToolCalls(message: AgentMessage) {
+ if (!message.tool_calls || message.tool_calls.length === 0) {
+ return "";
+ }
+
+ return html`
+ <div class="tool-calls">
+ ${message.tool_calls.map((toolCall) => {
+ const statusIcon = this.getToolStatusIcon(toolCall);
+ const summary = this.getToolSummary(toolCall);
+ const duration = this.getToolDuration(toolCall);
+
+ return html`
+ <div class="tool-call-item ${toolCall.name}">
+ <span class="tool-name">${toolCall.name}</span>
+ <span class="tool-summary">${summary}</span>
+ </div>
+ `;
+ })}
+ </div>
+ `;
+ }
+
+ private getToolStatusIcon(toolCall: any): string {
+ // Don't show status icons for mobile
+ return "";
+ }
+
+ private getToolSummary(toolCall: any): string {
+ try {
+ const input = JSON.parse(toolCall.input || "{}");
+
+ switch (toolCall.name) {
+ case "bash":
+ const command = input.command || "";
+ const isBackground = input.background === true;
+ const bgPrefix = isBackground ? "[bg] " : "";
+ return (
+ bgPrefix +
+ (command.length > 40 ? command.substring(0, 40) + "..." : command)
+ );
+
+ case "patch":
+ const path = input.path || "unknown";
+ const patchCount = (input.patches || []).length;
+ return `${path}: ${patchCount} edit${patchCount > 1 ? "s" : ""}`;
+
+ case "think":
+ const thoughts = input.thoughts || "";
+ const firstLine = thoughts.split("\n")[0] || "";
+ return firstLine.length > 50
+ ? firstLine.substring(0, 50) + "..."
+ : firstLine;
+
+ case "keyword_search":
+ const query = input.query || "";
+ return query.length > 50 ? query.substring(0, 50) + "..." : query;
+
+ case "browser_navigate":
+ return input.url || "";
+
+ case "browser_take_screenshot":
+ return "Taking screenshot";
+
+ case "browser_click":
+ return `Click: ${input.selector || ""}`;
+
+ case "browser_type":
+ const text = input.text || "";
+ return `Type: ${text.length > 30 ? text.substring(0, 30) + "..." : text}`;
+
+ case "todo_write":
+ const tasks = input.tasks || [];
+ return `${tasks.length} task${tasks.length > 1 ? "s" : ""}`;
+
+ case "todo_read":
+ return "Read todo list";
+
+ case "set-slug":
+ return `Slug: "${input.slug || ""}"`;
+
+ case "multiplechoice":
+ const question = input.question || "Multiple choice question";
+ const options = input.responseOptions || [];
+ if (options.length > 0) {
+ const optionsList = options.map((opt) => opt.caption).join(", ");
+ return `${question} [${optionsList}]`;
+ }
+ return question;
+
+ case "done":
+ return "Task completion checklist";
+
+ default:
+ // For unknown tools, show first part of input
+ const inputStr = JSON.stringify(input);
+ return inputStr.length > 50
+ ? inputStr.substring(0, 50) + "..."
+ : inputStr;
+ }
+ } catch (e) {
+ return "Tool call";
+ }
+ }
+
+ private getToolDuration(toolCall: any): string {
+ // Don't show duration for mobile
+ return "";
+ }
+
render() {
const displayMessages = this.messages.filter((msg) =>
this.shouldShowMessage(msg),
@@ -383,6 +540,7 @@
${unsafeHTML(this.renderMarkdown(text))}
</div>`
: text}
+ ${this.renderToolCalls(message)}
</div>
</div>
`;