mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
refactor(site/src/pages/AgentsPage): align tool-call and message styling (#24790)
Tighten visual rhythm and typography in the agent chat page so tool calls, reasoning, and assistant text share the same baseline. ## Highlights - Unify font size to **13px** across user messages, assistant `Response`, reasoning, and every tool-call label. - Reuse the `text-content-secondary → hover:text-content-primary` transition on tool-call rows so labels, chevrons, and lucide icons brighten together. Icons inside hover-aware headers switch to `text-current` so they inherit the parent transition; static icons in non-collapsible cards (`ExecuteTool`, `ProcessOutputTool`) keep the constant secondary color. - Collapse padding between adjacent tool/thinking blocks via a shared `data-tool-call` attribute and adjacent-sibling selectors (`[&:has(+[data-tool-call])]:pb-0` + `[[data-tool-call]+&]:pt-0`). First/last items keep their padding against text and reasoning siblings. - `read_file` now mirrors `write_file`: `Reading <name>…` while running, `Read <name>` once complete. - `ask_user_question` flips the inline label from `Asking:` to `Asked:` once answered. - Subagent row layout: status icon + label + chevron sit together at the start of the row, while the `Worked for <duration>` text uses `ml-auto` to anchor the right edge. - New Storybook story `WithEveryTool` (under `Pages / AgentsPage / AgentChatPage`) exercises every tool renderer plus subagent variants and the generic MCP fallback in a single completed-then-streaming turn. --- _Authored with help from a Coder Agent._
This commit is contained in:
@@ -201,6 +201,507 @@ const buildQueries = (
|
||||
];
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Every-tool showcase: a single completed assistant turn that exercises
|
||||
// every tool renderer registered in Tool.tsx, plus the SubagentRenderer
|
||||
// variants and the generic MCP fallback. Used by WithEveryTool
|
||||
// so the page demonstrates every tool card type at once.
|
||||
//
|
||||
// The generated `ChatToolCallPart`/`ChatToolResultPart` types declare
|
||||
// `args`/`result` as `Record<string, string>`, but the runtime accepts
|
||||
// arbitrary JSON. We cast through `unknown` here so the showcase can
|
||||
// use realistic numbers, booleans, arrays, and nested objects without
|
||||
// stringifying every field.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const EVERY_TOOL_ASSISTANT_TURN = {
|
||||
id: 4,
|
||||
chat_id: CHAT_ID,
|
||||
created_at: "2026-02-18T00:00:04.000Z",
|
||||
role: "assistant",
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: "Here is a recap of every tool I have at my disposal, exercised against this workspace so each card type renders.",
|
||||
},
|
||||
|
||||
// execute -- shell command output
|
||||
{
|
||||
type: "tool-call",
|
||||
tool_call_id: "every-execute",
|
||||
tool_name: "execute",
|
||||
args: { command: "go test ./coderd/httpmw/..." },
|
||||
},
|
||||
{
|
||||
type: "tool-result",
|
||||
tool_call_id: "every-execute",
|
||||
tool_name: "execute",
|
||||
result: {
|
||||
output: [
|
||||
"ok \tgithub.com/coder/coder/coderd/httpmw\t1.842s",
|
||||
"ok \tgithub.com/coder/coder/coderd/httpmw/auth\t0.612s",
|
||||
].join("\n"),
|
||||
exit_code: 0,
|
||||
},
|
||||
},
|
||||
|
||||
// process_output -- background process output
|
||||
{
|
||||
type: "tool-call",
|
||||
tool_call_id: "every-process-output",
|
||||
tool_name: "process_output",
|
||||
args: { process_id: "proc-42" },
|
||||
},
|
||||
{
|
||||
type: "tool-result",
|
||||
tool_call_id: "every-process-output",
|
||||
tool_name: "process_output",
|
||||
result: {
|
||||
output: [
|
||||
"[server] listening on :3000",
|
||||
"[server] handled GET / 200 in 4ms",
|
||||
"[server] handled GET /api/v2/users 200 in 12ms",
|
||||
].join("\n"),
|
||||
exit_code: null,
|
||||
},
|
||||
},
|
||||
|
||||
// process_signal -- signal sent to a background process
|
||||
{
|
||||
type: "tool-call",
|
||||
tool_call_id: "every-process-signal",
|
||||
tool_name: "process_signal",
|
||||
args: { process_id: "proc-42", signal: "terminate" },
|
||||
},
|
||||
{
|
||||
type: "tool-result",
|
||||
tool_call_id: "every-process-signal",
|
||||
tool_name: "process_signal",
|
||||
result: { success: true, signal: "terminate" },
|
||||
},
|
||||
|
||||
// read_file -- completed file read with content viewer
|
||||
{
|
||||
type: "tool-call",
|
||||
tool_call_id: "every-read-file",
|
||||
tool_name: "read_file",
|
||||
args: { path: "coderd/httpmw/apikey.go" },
|
||||
},
|
||||
{
|
||||
type: "tool-result",
|
||||
tool_call_id: "every-read-file",
|
||||
tool_name: "read_file",
|
||||
result: {
|
||||
content: [
|
||||
"package httpmw",
|
||||
"",
|
||||
"// ExtractAPIKeyMW will be split into a dedicated auth",
|
||||
"// package with three layers: transport, validation, authz.",
|
||||
"func ExtractAPIKeyMW(/* ... */) {}",
|
||||
].join("\n"),
|
||||
},
|
||||
},
|
||||
|
||||
// write_file -- completed file write with diff viewer
|
||||
{
|
||||
type: "tool-call",
|
||||
tool_call_id: "every-write-file",
|
||||
tool_name: "write_file",
|
||||
args: {
|
||||
path: "coderd/httpmw/auth/transport.go",
|
||||
content: [
|
||||
"package auth",
|
||||
"",
|
||||
"// ExtractCredentials reads credentials from the request.",
|
||||
"func ExtractCredentials(r *http.Request) (Credential, error) {",
|
||||
" return Credential{}, nil",
|
||||
"}",
|
||||
].join("\n"),
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "tool-result",
|
||||
tool_call_id: "every-write-file",
|
||||
tool_name: "write_file",
|
||||
result: { content: "wrote 6 lines" },
|
||||
},
|
||||
|
||||
// edit_files -- completed multi-file edit
|
||||
{
|
||||
type: "tool-call",
|
||||
tool_call_id: "every-edit-files",
|
||||
tool_name: "edit_files",
|
||||
args: {
|
||||
files: [
|
||||
{
|
||||
path: "coderd/coderd.go",
|
||||
edits: [
|
||||
{
|
||||
search: "httpmw.ExtractAPIKeyMW(opts)",
|
||||
replace: "auth.Middleware(opts)",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "tool-result",
|
||||
tool_call_id: "every-edit-files",
|
||||
tool_name: "edit_files",
|
||||
result: { applied: 1 },
|
||||
},
|
||||
|
||||
// list_templates -- completed template listing
|
||||
{
|
||||
type: "tool-call",
|
||||
tool_call_id: "every-list-templates",
|
||||
tool_name: "list_templates",
|
||||
args: {},
|
||||
},
|
||||
{
|
||||
type: "tool-result",
|
||||
tool_call_id: "every-list-templates",
|
||||
tool_name: "list_templates",
|
||||
result: {
|
||||
templates: [
|
||||
{
|
||||
id: "tpl-go",
|
||||
name: "go-template",
|
||||
display_name: "Go Development",
|
||||
description: "Workspace for Go services with VS Code.",
|
||||
},
|
||||
{
|
||||
id: "tpl-py",
|
||||
name: "python-template",
|
||||
description: "Python development environment.",
|
||||
},
|
||||
],
|
||||
count: 2,
|
||||
},
|
||||
},
|
||||
|
||||
// read_template -- completed single-template read
|
||||
{
|
||||
type: "tool-call",
|
||||
tool_call_id: "every-read-template",
|
||||
tool_name: "read_template",
|
||||
args: { name: "go-template" },
|
||||
},
|
||||
{
|
||||
type: "tool-result",
|
||||
tool_call_id: "every-read-template",
|
||||
tool_name: "read_template",
|
||||
result: {
|
||||
template: { name: "go-template", display_name: "Go Development" },
|
||||
},
|
||||
},
|
||||
|
||||
// read_skill -- completed skill load
|
||||
{
|
||||
type: "tool-call",
|
||||
tool_call_id: "every-read-skill",
|
||||
tool_name: "read_skill",
|
||||
args: { name: "deep-review" },
|
||||
},
|
||||
{
|
||||
type: "tool-result",
|
||||
tool_call_id: "every-read-skill",
|
||||
tool_name: "read_skill",
|
||||
result: {
|
||||
name: "deep-review",
|
||||
body: [
|
||||
"## Deep Review Skill",
|
||||
"",
|
||||
"Review the code changes thoroughly:",
|
||||
"",
|
||||
"1. Check for correctness",
|
||||
"2. Verify tests cover the new branches",
|
||||
"3. Ensure style consistency",
|
||||
].join("\n"),
|
||||
files: ["roles/security-reviewer.md"],
|
||||
},
|
||||
},
|
||||
|
||||
// read_skill_file -- completed skill file fetch
|
||||
{
|
||||
type: "tool-call",
|
||||
tool_call_id: "every-read-skill-file",
|
||||
tool_name: "read_skill_file",
|
||||
args: { name: "deep-review", path: "roles/security-reviewer.md" },
|
||||
},
|
||||
{
|
||||
type: "tool-result",
|
||||
tool_call_id: "every-read-skill-file",
|
||||
tool_name: "read_skill_file",
|
||||
result: {
|
||||
content: [
|
||||
"# Security Reviewer Role",
|
||||
"",
|
||||
"Focus on authentication, authorization, and input validation.",
|
||||
].join("\n"),
|
||||
},
|
||||
},
|
||||
|
||||
// chat_summarized -- compaction summary card
|
||||
{
|
||||
type: "tool-call",
|
||||
tool_call_id: "every-summarized",
|
||||
tool_name: "chat_summarized",
|
||||
args: {},
|
||||
},
|
||||
{
|
||||
type: "tool-result",
|
||||
tool_call_id: "every-summarized",
|
||||
tool_name: "chat_summarized",
|
||||
result: {
|
||||
summary: [
|
||||
"Summarized the previous turns to free up context: the agent",
|
||||
"split the auth middleware into transport and validation layers",
|
||||
"and is about to wire up the call sites.",
|
||||
].join(" "),
|
||||
},
|
||||
},
|
||||
|
||||
// ask_user_question -- completed clarification
|
||||
{
|
||||
type: "tool-call",
|
||||
tool_call_id: "every-ask-user",
|
||||
tool_name: "ask_user_question",
|
||||
args: {
|
||||
questions: [
|
||||
{
|
||||
header: "Migration approach",
|
||||
question:
|
||||
"How should we structure the database migration for the auth split?",
|
||||
options: [
|
||||
{
|
||||
label: "Single migration",
|
||||
description: "One migration with all changes.",
|
||||
},
|
||||
{
|
||||
label: "Incremental migrations",
|
||||
description: "Multiple sequential migrations.",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "tool-result",
|
||||
tool_call_id: "every-ask-user",
|
||||
tool_name: "ask_user_question",
|
||||
result: { questions: [{ answer: "Incremental migrations" }] },
|
||||
},
|
||||
|
||||
// propose_plan -- proposed plan with content
|
||||
{
|
||||
type: "tool-call",
|
||||
tool_call_id: "every-propose-plan",
|
||||
tool_name: "propose_plan",
|
||||
args: { path: "/home/coder/.coder/plans/AUTH_SPLIT.md" },
|
||||
},
|
||||
{
|
||||
type: "tool-result",
|
||||
tool_call_id: "every-propose-plan",
|
||||
tool_name: "propose_plan",
|
||||
result: {
|
||||
file_id: "plan-file-1",
|
||||
content: [
|
||||
"# Auth Split Plan",
|
||||
"",
|
||||
"1. Carve transport, validation, and authz packages.",
|
||||
"2. Update call sites in coderd and enterprise/coderd.",
|
||||
"3. Add an incremental migration for session lookups.",
|
||||
"4. Refresh the changelog and run the suite.",
|
||||
].join("\n"),
|
||||
},
|
||||
},
|
||||
|
||||
// computer -- screenshot tool result with text fallback
|
||||
{
|
||||
type: "tool-call",
|
||||
tool_call_id: "every-computer",
|
||||
tool_name: "computer",
|
||||
args: { action: "screenshot" },
|
||||
},
|
||||
{
|
||||
type: "tool-result",
|
||||
tool_call_id: "every-computer",
|
||||
tool_name: "computer",
|
||||
result: {
|
||||
data: "",
|
||||
text: "Screen resolution: 1920x1080\nActive window: Terminal",
|
||||
mime_type: "image/png",
|
||||
},
|
||||
},
|
||||
|
||||
// attach_file -- generic renderer with explicit attach label
|
||||
{
|
||||
type: "tool-call",
|
||||
tool_call_id: "every-attach-file",
|
||||
tool_name: "attach_file",
|
||||
args: { path: "docs/runbooks/auth-split.md" },
|
||||
},
|
||||
{
|
||||
type: "tool-result",
|
||||
tool_call_id: "every-attach-file",
|
||||
tool_name: "attach_file",
|
||||
result: {},
|
||||
},
|
||||
|
||||
// generic / MCP fallback -- unknown tool name with no server
|
||||
{
|
||||
type: "tool-call",
|
||||
tool_call_id: "every-generic",
|
||||
tool_name: "web_search",
|
||||
args: { query: "OAuth2 token rotation strategies" },
|
||||
},
|
||||
{
|
||||
type: "tool-result",
|
||||
tool_call_id: "every-generic",
|
||||
tool_name: "web_search",
|
||||
result: {
|
||||
results: [
|
||||
{
|
||||
title: "OAuth 2.0 RFC 6749",
|
||||
url: "https://datatracker.ietf.org/doc/html/rfc6749",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
// spawn_agent (general subagent variant)
|
||||
{
|
||||
type: "tool-call",
|
||||
tool_call_id: "every-spawn-general",
|
||||
tool_name: "spawn_agent",
|
||||
args: {
|
||||
type: "general",
|
||||
title: "Workspace diagnostics",
|
||||
prompt: "Collect logs and summarize why startup failed.",
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "tool-result",
|
||||
tool_call_id: "every-spawn-general",
|
||||
tool_name: "spawn_agent",
|
||||
result: {
|
||||
chat_id: "every-general-child",
|
||||
type: "general",
|
||||
title: "Workspace diagnostics",
|
||||
status: "completed",
|
||||
duration_ms: 3200,
|
||||
},
|
||||
},
|
||||
|
||||
// spawn_explore_agent (explore subagent variant)
|
||||
{
|
||||
type: "tool-call",
|
||||
tool_call_id: "every-spawn-explore",
|
||||
tool_name: "spawn_explore_agent",
|
||||
args: {
|
||||
prompt: "Read the repo and summarize the auth flow.",
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "tool-result",
|
||||
tool_call_id: "every-spawn-explore",
|
||||
tool_name: "spawn_explore_agent",
|
||||
result: {
|
||||
chat_id: "every-explore-child",
|
||||
type: "explore",
|
||||
status: "completed",
|
||||
duration_ms: 4100,
|
||||
},
|
||||
},
|
||||
|
||||
// spawn_computer_use_agent (desktop subagent variant)
|
||||
{
|
||||
type: "tool-call",
|
||||
tool_call_id: "every-spawn-desktop",
|
||||
tool_name: "spawn_computer_use_agent",
|
||||
args: {
|
||||
title: "Visual regression check",
|
||||
prompt: "Open the dashboard and look for visual regressions.",
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "tool-result",
|
||||
tool_call_id: "every-spawn-desktop",
|
||||
tool_name: "spawn_computer_use_agent",
|
||||
result: {
|
||||
chat_id: "every-desktop-child",
|
||||
type: "computer_use",
|
||||
title: "Visual regression check",
|
||||
status: "completed",
|
||||
duration_ms: 12400,
|
||||
},
|
||||
},
|
||||
|
||||
// message_agent -- send a follow-up to a subagent
|
||||
{
|
||||
type: "tool-call",
|
||||
tool_call_id: "every-message-agent",
|
||||
tool_name: "message_agent",
|
||||
args: { chat_id: "every-general-child", message: "continue" },
|
||||
},
|
||||
{
|
||||
type: "tool-result",
|
||||
tool_call_id: "every-message-agent",
|
||||
tool_name: "message_agent",
|
||||
result: {
|
||||
chat_id: "every-general-child",
|
||||
type: "general",
|
||||
status: "running",
|
||||
},
|
||||
},
|
||||
|
||||
// wait_agent -- wait for a subagent to finish
|
||||
{
|
||||
type: "tool-call",
|
||||
tool_call_id: "every-wait-agent",
|
||||
tool_name: "wait_agent",
|
||||
args: { chat_id: "every-general-child" },
|
||||
},
|
||||
{
|
||||
type: "tool-result",
|
||||
tool_call_id: "every-wait-agent",
|
||||
tool_name: "wait_agent",
|
||||
result: {
|
||||
chat_id: "every-general-child",
|
||||
type: "general",
|
||||
status: "completed",
|
||||
report: "Diagnostics finished; logs were uploaded.",
|
||||
},
|
||||
},
|
||||
|
||||
// close_agent -- terminate a subagent
|
||||
{
|
||||
type: "tool-call",
|
||||
tool_call_id: "every-close-agent",
|
||||
tool_name: "close_agent",
|
||||
args: { chat_id: "every-explore-child" },
|
||||
},
|
||||
{
|
||||
type: "tool-result",
|
||||
tool_call_id: "every-close-agent",
|
||||
tool_name: "close_agent",
|
||||
result: {
|
||||
chat_id: "every-explore-child",
|
||||
type: "explore",
|
||||
status: "completed",
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
type: "text",
|
||||
text: "With every renderer exercised, I'll continue the auth split below. Note the streaming flurry of file tool calls in the next turn.",
|
||||
},
|
||||
],
|
||||
} as unknown as TypesGen.ChatMessage;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Meta
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -1343,6 +1844,315 @@ export const StreamedReasoning: Story = {
|
||||
// This made the stories render empty chats and fail interaction
|
||||
// tests in both local and CI environments.
|
||||
|
||||
/**
|
||||
* Live agent turn with streaming reasoning and a back-to-back flurry of
|
||||
* in-progress file tool calls. The persisted history establishes context
|
||||
* (an earlier completed read+write pair); the WebSocket then streams an
|
||||
* assistant turn with reasoning followed by read/write/edit/read/write
|
||||
* tool calls that intentionally have no matching tool-result parts, so
|
||||
* each card stays in the "running" state and shows its loading spinner.
|
||||
*/
|
||||
export const WithEveryTool: Story = {
|
||||
parameters: {
|
||||
queries: buildQueries(
|
||||
{
|
||||
id: CHAT_ID,
|
||||
...baseChatFields,
|
||||
title: "Refactoring the auth module",
|
||||
status: "running",
|
||||
},
|
||||
{
|
||||
messages: [
|
||||
// -- Turn 1: user kicks off the task --
|
||||
{
|
||||
id: 1,
|
||||
chat_id: CHAT_ID,
|
||||
created_at: "2026-02-18T00:00:01.000Z",
|
||||
role: "user",
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: "Refactor the auth module: split httpmw/apikey.go into a transport, validation, and authorization layer. Update the imports and add a CHANGELOG entry while you are at it.",
|
||||
},
|
||||
],
|
||||
},
|
||||
// -- Turn 2: previous assistant turn (completed) --
|
||||
// Establishes that the agent already inspected and patched
|
||||
// a couple of files before the streaming turn begins.
|
||||
{
|
||||
id: 2,
|
||||
chat_id: CHAT_ID,
|
||||
created_at: "2026-02-18T00:00:02.000Z",
|
||||
role: "assistant",
|
||||
content: [
|
||||
{
|
||||
type: "reasoning",
|
||||
text: "I'll start by reading the existing middleware to understand its responsibilities, then sketch out the three-layer split before touching anything else.",
|
||||
},
|
||||
{
|
||||
type: "text",
|
||||
text: "Reading the existing middleware first so I can plan the split.",
|
||||
},
|
||||
{
|
||||
type: "tool-call",
|
||||
tool_call_id: "call-read-apikey",
|
||||
tool_name: "read_file",
|
||||
args: { path: "coderd/httpmw/apikey.go" },
|
||||
},
|
||||
{
|
||||
type: "tool-result",
|
||||
tool_call_id: "call-read-apikey",
|
||||
tool_name: "read_file",
|
||||
result: {
|
||||
content: [
|
||||
"package httpmw",
|
||||
"",
|
||||
"// ExtractAPIKeyMW does too many things: it parses",
|
||||
"// the token, validates the signature, looks up the",
|
||||
"// session, and authorizes the user. We will split",
|
||||
"// these into transport, validation, and authz.",
|
||||
"func ExtractAPIKeyMW(/* ... */) {}",
|
||||
].join("\n"),
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "tool-call",
|
||||
tool_call_id: "call-write-transport",
|
||||
tool_name: "write_file",
|
||||
args: {
|
||||
path: "coderd/httpmw/auth/transport.go",
|
||||
content: [
|
||||
"package auth",
|
||||
"",
|
||||
"// ExtractCredentials pulls the credential out of the",
|
||||
"// HTTP request without doing any validation.",
|
||||
"func ExtractCredentials(r *http.Request) (Credential, error) {",
|
||||
' if h := r.Header.Get("Authorization"); h != "" {',
|
||||
" return Credential{Kind: KindBearer, Value: h}, nil",
|
||||
" }",
|
||||
' if c, err := r.Cookie("coder_session_token"); err == nil {',
|
||||
" return Credential{Kind: KindCookie, Value: c.Value}, nil",
|
||||
" }",
|
||||
" return Credential{}, ErrNoCredential",
|
||||
"}",
|
||||
].join("\n"),
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "tool-result",
|
||||
tool_call_id: "call-write-transport",
|
||||
tool_name: "write_file",
|
||||
result: { content: "wrote 12 lines" },
|
||||
},
|
||||
{
|
||||
type: "text",
|
||||
text: "Transport layer is in place. Next I'll carve out validation and authorization, update the call sites, and bump the changelog.",
|
||||
},
|
||||
],
|
||||
},
|
||||
// -- Turn 3: user asks for a tool-by-tool tour --
|
||||
{
|
||||
id: 3,
|
||||
chat_id: CHAT_ID,
|
||||
created_at: "2026-02-18T00:00:03.000Z",
|
||||
role: "user",
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: "Before you keep going, can you take one quick pass and exercise every tool you have so I can confirm each one renders the way I expect?",
|
||||
},
|
||||
],
|
||||
},
|
||||
// -- Turn 4: assistant runs every tool exactly once --
|
||||
EVERY_TOOL_ASSISTANT_TURN,
|
||||
],
|
||||
queued_messages: [],
|
||||
has_more: false,
|
||||
},
|
||||
{ diffUrl: undefined },
|
||||
),
|
||||
// The streaming turn arrives over the WebSocket. None of the
|
||||
// tool-call parts have a matching tool-result, so each card
|
||||
// renders in the "running" state with its spinner.
|
||||
webSocket: {
|
||||
"/chats/": [
|
||||
{
|
||||
event: "message",
|
||||
data: JSON.stringify([
|
||||
// Streaming reasoning, delivered in two deltas so the
|
||||
// thinking block grows as the turn unfolds.
|
||||
{
|
||||
type: "message_part",
|
||||
chat_id: CHAT_ID,
|
||||
message_part: {
|
||||
part: {
|
||||
type: "reasoning",
|
||||
text: "Now I'll pull in the validation file and start carving out the JWT and session checks. ",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "message_part",
|
||||
chat_id: CHAT_ID,
|
||||
message_part: {
|
||||
part: {
|
||||
type: "reasoning",
|
||||
text: "After that I need to wire the new package into the existing call sites and refresh the changelog so reviewers can follow the split.",
|
||||
},
|
||||
},
|
||||
},
|
||||
// Some streaming response text before the tool calls.
|
||||
{
|
||||
type: "message_part",
|
||||
chat_id: CHAT_ID,
|
||||
message_part: {
|
||||
part: {
|
||||
type: "text",
|
||||
text: "Reading the validation helpers, then writing the new package, patching the consumers, and updating the changelog.",
|
||||
},
|
||||
},
|
||||
},
|
||||
// 1) read_file - in progress.
|
||||
{
|
||||
type: "message_part",
|
||||
chat_id: CHAT_ID,
|
||||
message_part: {
|
||||
part: {
|
||||
type: "tool-call",
|
||||
tool_call_id: "stream-read-validate",
|
||||
tool_name: "read_file",
|
||||
args_delta: JSON.stringify({
|
||||
path: "coderd/httpmw/validate.go",
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
// 2) write_file - in progress.
|
||||
{
|
||||
type: "message_part",
|
||||
chat_id: CHAT_ID,
|
||||
message_part: {
|
||||
part: {
|
||||
type: "tool-call",
|
||||
tool_call_id: "stream-write-validation",
|
||||
tool_name: "write_file",
|
||||
args_delta: JSON.stringify({
|
||||
path: "coderd/httpmw/auth/validation.go",
|
||||
content: [
|
||||
"package auth",
|
||||
"",
|
||||
"// Validate verifies the supplied credential and",
|
||||
"// returns the resolved subject. It does not make",
|
||||
"// authorization decisions.",
|
||||
"func Validate(ctx context.Context, c Credential) (Subject, error) {",
|
||||
" switch c.Kind {",
|
||||
" case KindBearer:",
|
||||
" return validateBearer(ctx, c.Value)",
|
||||
" case KindCookie:",
|
||||
" return validateSession(ctx, c.Value)",
|
||||
" default:",
|
||||
" return Subject{}, ErrUnsupportedCredential",
|
||||
" }",
|
||||
"}",
|
||||
].join("\n"),
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
// 3) edit_files - in progress (multi-file).
|
||||
{
|
||||
type: "message_part",
|
||||
chat_id: CHAT_ID,
|
||||
message_part: {
|
||||
part: {
|
||||
type: "tool-call",
|
||||
tool_call_id: "stream-edit-callsites",
|
||||
tool_name: "edit_files",
|
||||
args_delta: JSON.stringify({
|
||||
files: [
|
||||
{
|
||||
path: "coderd/coderd.go",
|
||||
edits: [
|
||||
{
|
||||
search: "httpmw.ExtractAPIKeyMW(opts)",
|
||||
replace: "auth.Middleware(opts)",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "enterprise/coderd/coderd.go",
|
||||
edits: [
|
||||
{
|
||||
search: "httpmw.ExtractAPIKeyMW(opts)",
|
||||
replace: "auth.Middleware(opts)",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
// 4) read_file - in progress.
|
||||
{
|
||||
type: "message_part",
|
||||
chat_id: CHAT_ID,
|
||||
message_part: {
|
||||
part: {
|
||||
type: "tool-call",
|
||||
tool_call_id: "stream-read-changelog",
|
||||
tool_name: "read_file",
|
||||
args_delta: JSON.stringify({
|
||||
path: "CHANGELOG.md",
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
// 5) write_file - in progress.
|
||||
{
|
||||
type: "message_part",
|
||||
chat_id: CHAT_ID,
|
||||
message_part: {
|
||||
part: {
|
||||
type: "tool-call",
|
||||
tool_call_id: "stream-write-changelog",
|
||||
tool_name: "write_file",
|
||||
args_delta: JSON.stringify({
|
||||
path: "CHANGELOG.md",
|
||||
content: [
|
||||
"# Changelog",
|
||||
"",
|
||||
"## Unreleased",
|
||||
"",
|
||||
"- Split the auth middleware into transport, validation,",
|
||||
" and authorization layers under coderd/httpmw/auth.",
|
||||
].join("\n"),
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
] satisfies TypesGen.ChatStreamEvent[]),
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
// All five streamed tool calls should appear simultaneously.
|
||||
// read_file, write_file, and edit_files all switch to a
|
||||
// progressive label ("Reading" / "Writing" / "Editing")
|
||||
// while running; the spinner conveys progress.
|
||||
await waitFor(() => {
|
||||
expect(canvas.getByText(/Reading validate\.go/)).toBeInTheDocument();
|
||||
expect(canvas.getByText(/Writing validation\.go/)).toBeInTheDocument();
|
||||
expect(canvas.getByText(/Editing 2 files/)).toBeInTheDocument();
|
||||
expect(canvas.getByText(/Reading CHANGELOG\.md/)).toBeInTheDocument();
|
||||
expect(canvas.getByText(/Writing CHANGELOG\.md/)).toBeInTheDocument();
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
/** wait_agent for a computer-use subagent renders the VNC preview card
|
||||
* (SubagentTool with computer-use variant) instead of the plain SubagentTool card. */
|
||||
export const WithWaitAgentComputerUseVNC: Story = {
|
||||
|
||||
@@ -147,52 +147,62 @@ const ReasoningDisclosure = memo<{
|
||||
}, [displayTextLength, isPreviewConstrained]);
|
||||
|
||||
return (
|
||||
<Collapsible
|
||||
open={expanded}
|
||||
onOpenChange={(open) => setManualToggle(open)}
|
||||
className="w-full"
|
||||
>
|
||||
<CollapsibleTrigger
|
||||
className={cn(
|
||||
"border-0 bg-transparent p-0 m-0 font-[inherit] text-[inherit] text-left",
|
||||
"flex w-full items-center gap-2 cursor-pointer",
|
||||
"text-content-secondary transition-colors hover:text-content-primary",
|
||||
)}
|
||||
>
|
||||
{isStreaming ? (
|
||||
<Shimmer as="span" className="text-sm">
|
||||
Thinking
|
||||
</Shimmer>
|
||||
) : (
|
||||
<span className="text-sm">Thinking</span>
|
||||
)}
|
||||
<ChevronDownIcon
|
||||
className={cn(
|
||||
"ml-auto h-3 w-3 shrink-0 text-current transition-transform",
|
||||
expanded ? "rotate-0" : "-rotate-90",
|
||||
)}
|
||||
/>
|
||||
</CollapsibleTrigger>
|
||||
{hasText && (
|
||||
<CollapsibleContent>
|
||||
<div
|
||||
ref={previewScrollRef}
|
||||
className={cn(
|
||||
"mt-1.5",
|
||||
isPreviewConstrained && "max-h-24 overflow-y-auto",
|
||||
)}
|
||||
>
|
||||
<Response
|
||||
className="text-[11px] text-content-secondary"
|
||||
urlTransform={urlTransform}
|
||||
streaming={isStreaming}
|
||||
>
|
||||
{displayText}
|
||||
</Response>
|
||||
</div>
|
||||
</CollapsibleContent>
|
||||
<div
|
||||
data-tool-call=""
|
||||
className={cn(
|
||||
"py-0.5",
|
||||
// Collapse padding between adjacent tool/thinking blocks.
|
||||
"[&:has(+[data-tool-call])]:pb-0",
|
||||
"[[data-tool-call]+&]:pt-0",
|
||||
)}
|
||||
</Collapsible>
|
||||
>
|
||||
<Collapsible
|
||||
open={expanded}
|
||||
onOpenChange={(open) => setManualToggle(open)}
|
||||
className="w-full"
|
||||
>
|
||||
<CollapsibleTrigger
|
||||
className={cn(
|
||||
"border-0 bg-transparent p-0 m-0 font-[inherit] text-[inherit] text-left",
|
||||
"flex w-full items-center gap-2 cursor-pointer",
|
||||
"text-content-secondary transition-colors hover:text-content-primary",
|
||||
)}
|
||||
>
|
||||
{isStreaming ? (
|
||||
<Shimmer as="span" className="text-[13px]">
|
||||
Thinking
|
||||
</Shimmer>
|
||||
) : (
|
||||
<span className="text-[13px]">Thinking</span>
|
||||
)}
|
||||
<ChevronDownIcon
|
||||
className={cn(
|
||||
"h-3 w-3 shrink-0 text-current transition-transform",
|
||||
expanded ? "rotate-0" : "-rotate-90",
|
||||
)}
|
||||
/>
|
||||
</CollapsibleTrigger>
|
||||
{hasText && (
|
||||
<CollapsibleContent>
|
||||
<div
|
||||
ref={previewScrollRef}
|
||||
className={cn(
|
||||
"mt-1.5",
|
||||
isPreviewConstrained && "max-h-24 overflow-y-auto",
|
||||
)}
|
||||
>
|
||||
<Response
|
||||
className="text-[11px] text-content-secondary"
|
||||
urlTransform={urlTransform}
|
||||
streaming={isStreaming}
|
||||
>
|
||||
{displayText}
|
||||
</Response>
|
||||
</div>
|
||||
</CollapsibleContent>
|
||||
)}
|
||||
</Collapsible>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -20,7 +20,7 @@ export const MessageContent = ({
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"whitespace-pre-wrap break-words text-sm leading-relaxed text-content-primary",
|
||||
"whitespace-pre-wrap break-words text-[13px] leading-relaxed text-content-primary",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import { LoaderIcon, TriangleAlertIcon } from "lucide-react";
|
||||
import {
|
||||
LoaderIcon,
|
||||
MessageCircleQuestionIcon,
|
||||
TriangleAlertIcon,
|
||||
} from "lucide-react";
|
||||
import { type FC, type FormEvent, useId, useState } from "react";
|
||||
import { useMutation } from "react-query";
|
||||
import { Button } from "#/components/Button/Button";
|
||||
@@ -161,8 +165,10 @@ const SelectableAnswerOption: FC<SelectableAnswerOptionProps> = ({
|
||||
id={id}
|
||||
value={value}
|
||||
/>
|
||||
<span className="text-sm font-medium text-content-primary">{label}</span>
|
||||
<p className="col-start-2 m-0 whitespace-pre-wrap text-sm text-content-secondary">
|
||||
<span className="text-[13px] font-medium text-content-primary">
|
||||
{label}
|
||||
</span>
|
||||
<p className="col-start-2 m-0 whitespace-pre-wrap text-[13px] text-content-secondary">
|
||||
{description}
|
||||
</p>
|
||||
</label>
|
||||
@@ -282,18 +288,20 @@ const QuestionStep: FC<QuestionStepProps> = ({
|
||||
Question {questionIndex + 1} of {questionCount}
|
||||
</p>
|
||||
)}
|
||||
<div className="space-y-1.5">
|
||||
<p
|
||||
id={questionHeaderId}
|
||||
className="text-xs font-medium text-content-secondary"
|
||||
>
|
||||
{questionHeader}
|
||||
</p>
|
||||
<div className="flex items-start gap-1.5 text-content-secondary">
|
||||
<MessageCircleQuestionIcon
|
||||
aria-hidden="true"
|
||||
className="mt-0.5 h-4 w-4 shrink-0"
|
||||
/>
|
||||
<p
|
||||
id={questionTextId}
|
||||
className="whitespace-pre-wrap text-sm text-content-primary"
|
||||
className="m-0 min-w-0 flex-1 whitespace-pre-wrap text-[13px]"
|
||||
>
|
||||
{questionText}
|
||||
<span className="sr-only" id={questionHeaderId}>
|
||||
{questionHeader}
|
||||
</span>
|
||||
<span aria-hidden="true">Asking: </span>
|
||||
<span>{questionText}</span>
|
||||
</p>
|
||||
</div>
|
||||
<div className="rounded-md border border-solid border-border-default px-3 py-1">
|
||||
@@ -343,12 +351,19 @@ const AnsweredQuestionText: FC<AnsweredQuestionTextProps> = ({
|
||||
idPrefix,
|
||||
}) => {
|
||||
return (
|
||||
<p
|
||||
id={`${idPrefix}-question-${questionIndex}-text`}
|
||||
className="whitespace-pre-wrap text-sm text-content-primary"
|
||||
>
|
||||
{getQuestionText(question)}
|
||||
</p>
|
||||
<div className="flex items-start gap-1.5 text-content-secondary">
|
||||
<MessageCircleQuestionIcon
|
||||
aria-hidden="true"
|
||||
className="mt-0.5 h-4 w-4 shrink-0"
|
||||
/>
|
||||
<p
|
||||
id={`${idPrefix}-question-${questionIndex}-text`}
|
||||
className="m-0 min-w-0 flex-1 whitespace-pre-wrap text-[13px]"
|
||||
>
|
||||
<span aria-hidden="true">Asked: </span>
|
||||
<span>{getQuestionText(question)}</span>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -523,7 +538,7 @@ export const AskUserQuestionTool: FC<AskUserQuestionToolProps> = ({
|
||||
<div className="w-full">
|
||||
<div
|
||||
role="alert"
|
||||
className="flex items-center gap-1.5 py-0.5 text-sm text-content-secondary"
|
||||
className="flex items-center gap-1.5 py-0.5 text-[13px] text-content-secondary"
|
||||
>
|
||||
<TriangleAlertIcon
|
||||
aria-label="Error"
|
||||
@@ -544,7 +559,7 @@ export const AskUserQuestionTool: FC<AskUserQuestionToolProps> = ({
|
||||
aria-live="polite"
|
||||
className="flex items-center gap-1.5 py-0.5"
|
||||
>
|
||||
<span className="text-sm text-content-secondary">
|
||||
<span className="text-[13px] text-content-secondary">
|
||||
Asking for clarification...
|
||||
</span>
|
||||
<LoaderIcon
|
||||
@@ -553,7 +568,7 @@ export const AskUserQuestionTool: FC<AskUserQuestionToolProps> = ({
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-sm italic text-content-secondary">
|
||||
<p className="text-[13px] italic text-content-secondary">
|
||||
No questions available.
|
||||
</p>
|
||||
)}
|
||||
@@ -606,7 +621,7 @@ export const AskUserQuestionTool: FC<AskUserQuestionToolProps> = ({
|
||||
<p className="text-xs font-medium text-content-secondary">
|
||||
Submitted answer
|
||||
</p>
|
||||
<p className="mt-1 whitespace-pre-wrap text-sm text-content-primary">
|
||||
<p className="mt-1 whitespace-pre-wrap text-[13px] text-content-primary">
|
||||
{displayedSubmittedResponseText || "No answer recorded."}
|
||||
</p>
|
||||
</div>
|
||||
@@ -615,7 +630,7 @@ export const AskUserQuestionTool: FC<AskUserQuestionToolProps> = ({
|
||||
{submitError && (
|
||||
<div
|
||||
role="alert"
|
||||
className="mt-3 flex items-center gap-1.5 text-sm text-content-destructive"
|
||||
className="mt-3 flex items-center gap-1.5 text-[13px] text-content-destructive"
|
||||
>
|
||||
<TriangleAlertIcon className="h-3.5 w-3.5 shrink-0" />
|
||||
<span>{submitError}</span>
|
||||
@@ -670,7 +685,7 @@ export const AskUserQuestionTool: FC<AskUserQuestionToolProps> = ({
|
||||
aria-live="polite"
|
||||
className="flex items-center gap-1.5 py-0.5"
|
||||
>
|
||||
<span className="text-sm text-content-secondary">
|
||||
<span className="text-[13px] text-content-secondary">
|
||||
Asking for clarification...
|
||||
</span>
|
||||
<LoaderIcon
|
||||
|
||||
@@ -6,7 +6,6 @@ import {
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from "#/components/Tooltip/Tooltip";
|
||||
import { cn } from "#/utils/cn";
|
||||
import { Response } from "../Response";
|
||||
import { ToolCollapsible } from "./ToolCollapsible";
|
||||
import type { ToolStatus } from "./utils";
|
||||
@@ -30,13 +29,13 @@ export const ChatSummarizedTool: React.FC<{
|
||||
hasContent={hasSummary}
|
||||
header={
|
||||
<>
|
||||
<span className={cn("text-sm", "text-content-secondary")}>
|
||||
<span className="text-[13px]">
|
||||
{isRunning ? "Summarizing…" : "Summarized"}
|
||||
</span>
|
||||
{isError && (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<TriangleAlertIcon className="h-3.5 w-3.5 shrink-0 text-content-secondary" />
|
||||
<TriangleAlertIcon className="h-3.5 w-3.5 shrink-0 text-current" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
{errorMessage || "Failed to summarize conversation"}
|
||||
@@ -44,7 +43,7 @@ export const ChatSummarizedTool: React.FC<{
|
||||
</Tooltip>
|
||||
)}
|
||||
{isRunning && (
|
||||
<LoaderIcon className="h-3.5 w-3.5 shrink-0 animate-spin motion-reduce:animate-none text-content-secondary" />
|
||||
<LoaderIcon className="h-3.5 w-3.5 shrink-0 animate-spin motion-reduce:animate-none text-current" />
|
||||
)}
|
||||
</>
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import {
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from "#/components/Tooltip/Tooltip";
|
||||
import { cn } from "#/utils/cn";
|
||||
import { ImageLightbox } from "../../ImageLightbox";
|
||||
import { ToolCollapsible } from "./ToolCollapsible";
|
||||
import type { ToolStatus } from "./utils";
|
||||
@@ -40,13 +39,13 @@ export const ComputerTool: React.FC<{
|
||||
defaultExpanded={hasImage}
|
||||
header={
|
||||
<>
|
||||
<span className={cn("text-sm", "text-content-secondary")}>
|
||||
<span className="text-[13px]">
|
||||
{isRunning ? "Taking screenshot…" : "Screenshot"}
|
||||
</span>
|
||||
{isError && (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<TriangleAlertIcon className="h-3.5 w-3.5 shrink-0 text-content-secondary" />
|
||||
<TriangleAlertIcon className="h-3.5 w-3.5 shrink-0 text-current" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
{errorMessage || "Failed to take screenshot"}
|
||||
@@ -54,7 +53,7 @@ export const ComputerTool: React.FC<{
|
||||
</Tooltip>
|
||||
)}
|
||||
{isRunning && (
|
||||
<LoaderIcon className="h-3.5 w-3.5 shrink-0 animate-spin motion-reduce:animate-none text-content-secondary" />
|
||||
<LoaderIcon className="h-3.5 w-3.5 shrink-0 animate-spin motion-reduce:animate-none text-current" />
|
||||
)}
|
||||
</>
|
||||
}
|
||||
|
||||
@@ -68,12 +68,12 @@ export const CreateWorkspaceTool: React.FC<{
|
||||
|
||||
const header = (
|
||||
<>
|
||||
<MonitorIcon className="h-4 w-4 shrink-0 text-content-secondary" />
|
||||
<span className="text-sm text-content-secondary">{label}</span>
|
||||
<MonitorIcon className="h-4 w-4 shrink-0 text-current" />
|
||||
<span className="text-[13px]">{label}</span>
|
||||
{isError && (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<TriangleAlertIcon className="h-3.5 w-3.5 shrink-0 text-content-secondary" />
|
||||
<TriangleAlertIcon className="h-3.5 w-3.5 shrink-0 text-current" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
{errorMessage || "Failed to create workspace"}
|
||||
@@ -81,7 +81,7 @@ export const CreateWorkspaceTool: React.FC<{
|
||||
</Tooltip>
|
||||
)}
|
||||
{isRunning && (
|
||||
<LoaderIcon className="h-3.5 w-3.5 shrink-0 animate-spin motion-reduce:animate-none text-content-secondary" />
|
||||
<LoaderIcon className="h-3.5 w-3.5 shrink-0 animate-spin motion-reduce:animate-none text-current" />
|
||||
)}
|
||||
{workspaceLink && !isRunning && (
|
||||
<Link
|
||||
|
||||
@@ -9,7 +9,6 @@ import {
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from "#/components/Tooltip/Tooltip";
|
||||
import { cn } from "#/utils/cn";
|
||||
import { ToolCollapsible } from "./ToolCollapsible";
|
||||
import {
|
||||
DIFFS_FONT_STYLE,
|
||||
@@ -61,13 +60,11 @@ export const EditFilesTool: React.FC<{
|
||||
defaultExpanded
|
||||
header={
|
||||
<>
|
||||
<span className={cn("text-sm", "text-content-secondary")}>
|
||||
{label}
|
||||
</span>
|
||||
<span className="text-[13px]">{label}</span>
|
||||
{isError && (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<TriangleAlertIcon className="h-3.5 w-3.5 shrink-0 text-content-secondary" />
|
||||
<TriangleAlertIcon className="h-3.5 w-3.5 shrink-0 text-current" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
{errorMessage || "Failed to edit files"}
|
||||
@@ -75,7 +72,7 @@ export const EditFilesTool: React.FC<{
|
||||
</Tooltip>
|
||||
)}
|
||||
{isRunning && (
|
||||
<LoaderIcon className="h-3.5 w-3.5 shrink-0 animate-spin motion-reduce:animate-none text-content-secondary" />
|
||||
<LoaderIcon className="h-3.5 w-3.5 shrink-0 animate-spin motion-reduce:animate-none text-current" />
|
||||
)}
|
||||
</>
|
||||
}
|
||||
|
||||
@@ -213,7 +213,7 @@ export const ExecuteAuthRequiredTool: React.FC<{
|
||||
<div className="w-full overflow-hidden rounded-md border border-solid border-border-default bg-surface-primary">
|
||||
<div className="flex flex-wrap items-center gap-2 px-3 py-2">
|
||||
<CircleAlertIcon className="h-4 w-4 shrink-0 text-content-warning" />
|
||||
<span className="text-sm text-content-primary">
|
||||
<span className="text-[13px] text-content-primary">
|
||||
Authenticate with {providerLabel} to continue this command.
|
||||
</span>
|
||||
</div>
|
||||
@@ -302,7 +302,7 @@ export const WaitForExternalAuthTool: React.FC<{
|
||||
<div className="w-full overflow-hidden rounded-md border border-solid border-border-default bg-surface-primary px-3 py-2">
|
||||
<div className="flex items-center gap-2">
|
||||
{icon}
|
||||
<span className="text-sm text-content-primary">{label}</span>
|
||||
<span className="text-[13px] text-content-primary">{label}</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -86,7 +86,7 @@ export const InlineDesktopPreview: React.FC<{
|
||||
aria-label="Open desktop tab"
|
||||
className="absolute inset-0 z-10 flex cursor-pointer items-center justify-center gap-1.5 border-0 bg-black/0 p-0 transition-colors group-hover/preview:bg-black/50"
|
||||
>
|
||||
<span className="text-sm font-medium text-white opacity-0 drop-shadow-md transition-opacity group-hover/preview:opacity-100">
|
||||
<span className="text-[13px] font-medium text-white opacity-0 drop-shadow-md transition-opacity group-hover/preview:opacity-100">
|
||||
View desktop
|
||||
</span>
|
||||
<PanelRightOpenIcon className="h-4 w-4 text-white opacity-0 drop-shadow-md transition-opacity group-hover/preview:opacity-100" />
|
||||
|
||||
@@ -6,7 +6,6 @@ import {
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from "#/components/Tooltip/Tooltip";
|
||||
import { cn } from "#/utils/cn";
|
||||
import { ToolCollapsible } from "./ToolCollapsible";
|
||||
import { asRecord, asString, type ToolStatus } from "./utils";
|
||||
|
||||
@@ -37,13 +36,11 @@ export const ListTemplatesTool: React.FC<{
|
||||
hasContent={hasContent}
|
||||
header={
|
||||
<>
|
||||
<span className={cn("text-sm", "text-content-secondary")}>
|
||||
{label}
|
||||
</span>
|
||||
<span className="text-[13px]">{label}</span>
|
||||
{isError && (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<TriangleAlertIcon className="h-3.5 w-3.5 shrink-0 text-content-secondary" />
|
||||
<TriangleAlertIcon className="h-3.5 w-3.5 shrink-0 text-current" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
{errorMessage || "Failed to list templates"}
|
||||
@@ -51,7 +48,7 @@ export const ListTemplatesTool: React.FC<{
|
||||
</Tooltip>
|
||||
)}
|
||||
{isRunning && (
|
||||
<LoaderIcon className="h-3.5 w-3.5 shrink-0 animate-spin motion-reduce:animate-none text-content-secondary" />
|
||||
<LoaderIcon className="h-3.5 w-3.5 shrink-0 animate-spin motion-reduce:animate-none text-current" />
|
||||
)}
|
||||
</>
|
||||
}
|
||||
@@ -68,7 +65,7 @@ export const ListTemplatesTool: React.FC<{
|
||||
|
||||
if (!name) {
|
||||
return (
|
||||
<div key={index} className="text-sm text-content-secondary">
|
||||
<div key={index} className="text-[13px] text-content-secondary">
|
||||
{templateName}
|
||||
</div>
|
||||
);
|
||||
@@ -79,7 +76,7 @@ export const ListTemplatesTool: React.FC<{
|
||||
<Link
|
||||
to={`/templates/${name}`}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
className="flex items-center gap-1.5 text-sm text-content-secondary opacity-50 transition-opacity hover:opacity-100"
|
||||
className="flex items-center gap-1.5 text-[13px] text-content-secondary opacity-50 transition-opacity hover:opacity-100"
|
||||
>
|
||||
<span>{templateName}</span>
|
||||
<ExternalLinkIcon className="h-3 w-3 shrink-0" />
|
||||
|
||||
@@ -9,7 +9,6 @@ import {
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from "#/components/Tooltip/Tooltip";
|
||||
import { cn } from "#/utils/cn";
|
||||
import { Response } from "../Response";
|
||||
import type { ToolStatus } from "./utils";
|
||||
|
||||
@@ -73,8 +72,8 @@ export const ProposePlanTool: React.FC<{
|
||||
|
||||
return (
|
||||
<div className="w-full">
|
||||
<div className="flex items-center gap-1.5 py-0.5">
|
||||
<span className={cn("text-sm", "text-content-secondary")}>
|
||||
<div className="flex items-center gap-1.5 py-0.5 text-content-secondary">
|
||||
<span className="text-[13px]">
|
||||
{isRunning ? `Proposing ${filename}…` : `Proposed ${filename}`}
|
||||
</span>
|
||||
{effectiveError && (
|
||||
@@ -91,7 +90,7 @@ export const ProposePlanTool: React.FC<{
|
||||
</Tooltip>
|
||||
)}
|
||||
{isRunning && (
|
||||
<LoaderIcon className="h-3.5 w-3.5 shrink-0 animate-spin motion-reduce:animate-none text-content-secondary" />
|
||||
<LoaderIcon className="h-3.5 w-3.5 shrink-0 animate-spin motion-reduce:animate-none text-current" />
|
||||
)}
|
||||
</div>
|
||||
{hasDisplayContent ? (
|
||||
@@ -132,13 +131,13 @@ export const ProposePlanTool: React.FC<{
|
||||
) : (
|
||||
!fetchLoading &&
|
||||
!effectiveError && (
|
||||
<p className="text-sm text-content-secondary italic">
|
||||
<p className="text-[13px] text-content-secondary italic">
|
||||
No plan content.
|
||||
</p>
|
||||
)
|
||||
)}
|
||||
{fetchLoading && (
|
||||
<div className="flex items-center gap-1.5 py-2 text-sm text-content-secondary">
|
||||
<div className="flex items-center gap-1.5 py-2 text-[13px] text-content-secondary">
|
||||
<LoaderIcon className="h-3.5 w-3.5 animate-spin motion-reduce:animate-none" />
|
||||
Loading plan…
|
||||
</div>
|
||||
|
||||
@@ -8,7 +8,6 @@ import {
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from "#/components/Tooltip/Tooltip";
|
||||
import { cn } from "#/utils/cn";
|
||||
import { ToolCollapsible } from "./ToolCollapsible";
|
||||
import {
|
||||
DIFFS_FONT_STYLE,
|
||||
@@ -31,6 +30,8 @@ export const ReadFileTool: React.FC<{
|
||||
const isDark = theme.palette.mode === "dark";
|
||||
const hasContent = content.length > 0;
|
||||
const isRunning = status === "running";
|
||||
const filename = path.split("/").pop() || path;
|
||||
const label = isRunning ? `Reading ${filename}…` : `Read ${filename}`;
|
||||
|
||||
return (
|
||||
<ToolCollapsible
|
||||
@@ -38,13 +39,11 @@ export const ReadFileTool: React.FC<{
|
||||
hasContent={hasContent}
|
||||
header={
|
||||
<>
|
||||
<span className={cn("text-sm", "text-content-secondary")}>
|
||||
Read {path.split("/").pop() || path}
|
||||
</span>
|
||||
<span className="text-[13px]">{label}</span>
|
||||
{isError && (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<TriangleAlertIcon className="h-3.5 w-3.5 shrink-0 text-content-secondary" />
|
||||
<TriangleAlertIcon className="h-3.5 w-3.5 shrink-0 text-current" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
{errorMessage || "Failed to read file"}
|
||||
@@ -52,7 +51,7 @@ export const ReadFileTool: React.FC<{
|
||||
</Tooltip>
|
||||
)}
|
||||
{isRunning && (
|
||||
<LoaderIcon className="h-3.5 w-3.5 shrink-0 animate-spin motion-reduce:animate-none text-content-secondary" />
|
||||
<LoaderIcon className="h-3.5 w-3.5 shrink-0 animate-spin motion-reduce:animate-none text-current" />
|
||||
)}
|
||||
</>
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import {
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from "#/components/Tooltip/Tooltip";
|
||||
import { cn } from "#/utils/cn";
|
||||
import { Response } from "../Response";
|
||||
import { ToolCollapsible } from "./ToolCollapsible";
|
||||
import type { ToolStatus } from "./utils";
|
||||
@@ -27,14 +26,14 @@ export const ReadSkillTool: React.FC<{
|
||||
hasContent={hasContent}
|
||||
header={
|
||||
<>
|
||||
<BookOpenIcon className="h-4 w-4 shrink-0 text-content-secondary" />
|
||||
<span className={cn("text-sm", "text-content-secondary")}>
|
||||
<BookOpenIcon className="h-4 w-4 shrink-0 text-current" />
|
||||
<span className="text-[13px]">
|
||||
{isRunning ? `Reading ${label}…` : `Read ${label}`}
|
||||
</span>
|
||||
{isError && (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<TriangleAlertIcon className="h-3.5 w-3.5 shrink-0 text-content-secondary" />
|
||||
<TriangleAlertIcon className="h-3.5 w-3.5 shrink-0 text-current" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
{errorMessage || "Failed to read skill"}
|
||||
@@ -42,7 +41,7 @@ export const ReadSkillTool: React.FC<{
|
||||
</Tooltip>
|
||||
)}
|
||||
{isRunning && (
|
||||
<LoaderIcon className="h-3.5 w-3.5 shrink-0 animate-spin motion-reduce:animate-none text-content-secondary" />
|
||||
<LoaderIcon className="h-3.5 w-3.5 shrink-0 animate-spin motion-reduce:animate-none text-current" />
|
||||
)}
|
||||
</>
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import {
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from "#/components/Tooltip/Tooltip";
|
||||
import { cn } from "#/utils/cn";
|
||||
import type { ToolStatus } from "./utils";
|
||||
|
||||
/**
|
||||
@@ -27,12 +26,12 @@ export const ReadTemplateTool: React.FC<{
|
||||
: "Read template";
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-1.5">
|
||||
<span className={cn("text-sm", "text-content-secondary")}>{label}</span>
|
||||
<div className="flex items-center gap-1.5 text-content-secondary">
|
||||
<span className="text-[13px]">{label}</span>
|
||||
{isError && (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<TriangleAlertIcon className="h-3.5 w-3.5 shrink-0 text-content-secondary" />
|
||||
<TriangleAlertIcon className="h-3.5 w-3.5 shrink-0 text-current" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
{errorMessage || "Failed to read template"}
|
||||
@@ -40,7 +39,7 @@ export const ReadTemplateTool: React.FC<{
|
||||
</Tooltip>
|
||||
)}
|
||||
{isRunning && (
|
||||
<LoaderIcon className="h-3.5 w-3.5 shrink-0 animate-spin motion-reduce:animate-none text-content-secondary" />
|
||||
<LoaderIcon className="h-3.5 w-3.5 shrink-0 animate-spin motion-reduce:animate-none text-current" />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -38,12 +38,12 @@ export const StartWorkspaceTool: FC<StartWorkspaceToolProps> = ({
|
||||
|
||||
const header = (
|
||||
<>
|
||||
<MonitorPlayIcon className="h-4 w-4 shrink-0 text-content-secondary" />
|
||||
<span className="text-sm text-content-secondary">{label}</span>
|
||||
<MonitorPlayIcon className="h-4 w-4 shrink-0 text-current" />
|
||||
<span className="text-[13px]">{label}</span>
|
||||
{isError && (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<TriangleAlertIcon className="h-3.5 w-3.5 shrink-0 text-content-secondary" />
|
||||
<TriangleAlertIcon className="h-3.5 w-3.5 shrink-0 text-current" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
{errorMessage || "Failed to start workspace"}
|
||||
@@ -51,7 +51,7 @@ export const StartWorkspaceTool: FC<StartWorkspaceToolProps> = ({
|
||||
</Tooltip>
|
||||
)}
|
||||
{isRunning && (
|
||||
<LoaderIcon className="h-3.5 w-3.5 shrink-0 animate-spin motion-reduce:animate-none text-content-secondary" />
|
||||
<LoaderIcon className="h-3.5 w-3.5 shrink-0 animate-spin motion-reduce:animate-none text-current" />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -67,7 +67,7 @@ function getSubagentLabel(
|
||||
): React.ReactNode {
|
||||
if (showDesktopPreview && toolStatus === "running") {
|
||||
return (
|
||||
<Shimmer as="span" className="text-sm">
|
||||
<Shimmer as="span" className="text-[13px]">
|
||||
Using the computer...
|
||||
</Shimmer>
|
||||
);
|
||||
@@ -79,8 +79,7 @@ function getSubagentLabel(
|
||||
) {
|
||||
return (
|
||||
<>
|
||||
Used the computer{" "}
|
||||
<span className="text-content-secondary opacity-60">{title}</span>
|
||||
Used the computer <span className="opacity-60">{title}</span>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -94,7 +93,7 @@ function getSubagentLabel(
|
||||
return (
|
||||
<>
|
||||
{SUBAGENT_VERBS[descriptor.action][phase]}
|
||||
<span className="text-content-secondary opacity-60">{title}</span>
|
||||
<span className="opacity-60">{title}</span>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -124,22 +123,20 @@ const SubagentStatusIcon: React.FC<{
|
||||
const subagentCompleted = isSubagentSuccessStatus(subagentStatus);
|
||||
const DefaultIcon = iconKind === "monitor" ? MonitorIcon : BotIcon;
|
||||
if (isTimeout && !subagentCompleted) {
|
||||
return <ClockIcon className="h-4 w-4 shrink-0 text-content-secondary" />;
|
||||
return <ClockIcon className="h-4 w-4 shrink-0 text-current" />;
|
||||
}
|
||||
if ((isError && !subagentCompleted) || toolStatus === "error") {
|
||||
return <CircleXIcon className="h-4 w-4 shrink-0 text-content-secondary" />;
|
||||
return <CircleXIcon className="h-4 w-4 shrink-0 text-current" />;
|
||||
}
|
||||
if (toolStatus === "running") {
|
||||
if (showDesktopPreview) {
|
||||
return (
|
||||
<MonitorIcon className="h-4 w-4 shrink-0 text-content-secondary" />
|
||||
);
|
||||
return <MonitorIcon className="h-4 w-4 shrink-0 text-current" />;
|
||||
}
|
||||
return (
|
||||
<LoaderIcon className="h-4 w-4 shrink-0 animate-spin motion-reduce:animate-none text-content-link" />
|
||||
);
|
||||
}
|
||||
return <DefaultIcon className="h-4 w-4 shrink-0 text-content-secondary" />;
|
||||
return <DefaultIcon className="h-4 w-4 shrink-0 text-current" />;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -199,7 +196,8 @@ export const SubagentTool: React.FC<{
|
||||
className={cn(
|
||||
"border-0 bg-transparent p-0 m-0 font-[inherit] text-[inherit] text-left",
|
||||
"flex w-full items-center gap-2",
|
||||
hasExpandableContent && "cursor-pointer",
|
||||
"text-content-secondary transition-colors",
|
||||
hasExpandableContent && "cursor-pointer hover:text-content-primary",
|
||||
)}
|
||||
>
|
||||
<SubagentStatusIcon
|
||||
@@ -210,7 +208,7 @@ export const SubagentTool: React.FC<{
|
||||
iconKind={descriptor.iconKind}
|
||||
showDesktopPreview={showDesktopPreview}
|
||||
/>{" "}
|
||||
<span className="min-w-0 flex-1 truncate text-sm text-content-secondary">
|
||||
<span className="min-w-0 truncate text-[13px]">
|
||||
{getSubagentLabel(
|
||||
showDesktopPreview,
|
||||
toolStatus,
|
||||
@@ -229,19 +227,19 @@ export const SubagentTool: React.FC<{
|
||||
</Link>
|
||||
)}
|
||||
</span>
|
||||
{durationLabel && (
|
||||
<span className="shrink-0 text-xs text-content-secondary">
|
||||
{`Worked for ${durationLabel}`}
|
||||
</span>
|
||||
)}
|
||||
{hasExpandableContent && (
|
||||
<ChevronDownIcon
|
||||
className={cn(
|
||||
"h-3 w-3 shrink-0 text-content-secondary transition-transform",
|
||||
"h-3 w-3 shrink-0 text-current transition-transform",
|
||||
expanded ? "rotate-0" : "-rotate-90",
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
{durationLabel && (
|
||||
<span className="ml-auto shrink-0 text-xs">
|
||||
{`Worked for ${durationLabel}`}
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
|
||||
{showDesktopPreview && desktopChatId && toolStatus !== "completed" && (
|
||||
|
||||
@@ -813,7 +813,7 @@ const GenericToolRenderer: FC<ToolRendererProps> = ({
|
||||
serverName={mcpServer?.display_name}
|
||||
/>
|
||||
{modelIntent ? (
|
||||
<span className="truncate text-sm text-content-secondary">
|
||||
<span className="truncate text-[13px]">
|
||||
{modelIntent.charAt(0).toUpperCase() + modelIntent.slice(1)}
|
||||
</span>
|
||||
) : (
|
||||
@@ -827,7 +827,7 @@ const GenericToolRenderer: FC<ToolRendererProps> = ({
|
||||
{isError && (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<TriangleAlertIcon className="h-3.5 w-3.5 shrink-0 text-content-secondary" />
|
||||
<TriangleAlertIcon className="h-3.5 w-3.5 shrink-0 text-current" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
{errorMessage || "Tool call failed"}
|
||||
@@ -835,7 +835,7 @@ const GenericToolRenderer: FC<ToolRendererProps> = ({
|
||||
</Tooltip>
|
||||
)}
|
||||
{isRunning && (
|
||||
<LoaderIcon className="h-3.5 w-3.5 shrink-0 animate-spin motion-reduce:animate-none text-content-secondary" />
|
||||
<LoaderIcon className="h-3.5 w-3.5 shrink-0 animate-spin motion-reduce:animate-none text-current" />
|
||||
)}
|
||||
</>
|
||||
}
|
||||
@@ -988,12 +988,18 @@ export const Tool = memo(
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
data-tool-call=""
|
||||
className={cn(
|
||||
name === "execute" ||
|
||||
name === "process_output" ||
|
||||
name === "propose_plan"
|
||||
? "w-full py-0.5"
|
||||
: "py-0.5",
|
||||
// Collapse padding between adjacent tool calls so they hug.
|
||||
// Bottom padding is removed on a tool followed by a tool, and
|
||||
// top padding is removed on a tool preceded by a tool.
|
||||
"[&:has(+[data-tool-call])]:pb-0",
|
||||
"[[data-tool-call]+&]:pt-0",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
|
||||
@@ -38,13 +38,18 @@ export const ToolCollapsible: FC<ToolCollapsibleProps> = ({
|
||||
{header}
|
||||
<ChevronDownIcon
|
||||
className={cn(
|
||||
"ml-auto h-3 w-3 shrink-0 text-current transition-transform",
|
||||
"h-3 w-3 shrink-0 text-current transition-transform",
|
||||
expanded ? "rotate-0" : "-rotate-90",
|
||||
)}
|
||||
/>
|
||||
</button>
|
||||
) : (
|
||||
<div className={cn("flex items-center gap-2", headerClassName)}>
|
||||
<div
|
||||
className={cn(
|
||||
"flex items-center gap-2 text-content-secondary",
|
||||
headerClassName,
|
||||
)}
|
||||
>
|
||||
{header}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -33,7 +33,7 @@ export const ToolIcon: React.FC<{
|
||||
subagentIconKind?: SubagentIconKind;
|
||||
}> = ({ name, iconUrl, isRunning, serverName, subagentIconKind }) => {
|
||||
const [imgError, setImgError] = useState(false);
|
||||
const color = "text-content-secondary";
|
||||
const color = "text-current";
|
||||
const base = cn("h-4 w-4 shrink-0", color, isRunning && "grayscale");
|
||||
|
||||
// If an MCP icon URL is provided and hasn't failed, render it.
|
||||
|
||||
@@ -45,9 +45,7 @@ const renderSubagentLabel = (
|
||||
}
|
||||
})();
|
||||
|
||||
return (
|
||||
<span className="truncate text-sm text-content-secondary">{text}</span>
|
||||
);
|
||||
return <span className="truncate text-[13px]">{text}</span>;
|
||||
};
|
||||
|
||||
export const ToolLabel: React.FC<{
|
||||
@@ -77,17 +75,11 @@ export const ToolLabel: React.FC<{
|
||||
</code>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<span className="truncate text-sm text-content-secondary">
|
||||
Running command
|
||||
</span>
|
||||
);
|
||||
return <span className="truncate text-[13px]">Running command</span>;
|
||||
}
|
||||
case "process_output":
|
||||
return (
|
||||
<span className="truncate text-sm text-content-secondary">
|
||||
Reading process output
|
||||
</span>
|
||||
<span className="truncate text-[13px]">Reading process output</span>
|
||||
);
|
||||
case "process_signal": {
|
||||
const signal = parsed ? asString(parsed.signal) : "";
|
||||
@@ -98,7 +90,7 @@ export const ToolLabel: React.FC<{
|
||||
if (hasResult && success) {
|
||||
const verb = signal === "kill" ? "Killed" : "Terminated";
|
||||
return (
|
||||
<span className="truncate text-sm text-content-secondary">
|
||||
<span className="truncate text-[13px]">
|
||||
{verb} process{shortId ? ` ${shortId}` : ""}
|
||||
</span>
|
||||
);
|
||||
@@ -111,13 +103,13 @@ export const ToolLabel: React.FC<{
|
||||
? "terminate"
|
||||
: "signal";
|
||||
return (
|
||||
<span className="truncate text-sm text-content-secondary">
|
||||
<span className="truncate text-[13px]">
|
||||
Failed to {verb} process{shortId ? ` ${shortId}` : ""}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<span className="truncate text-sm text-content-secondary">
|
||||
<span className="truncate text-[13px]">
|
||||
{signal === "kill"
|
||||
? "Killing process…"
|
||||
: signal === "terminate"
|
||||
@@ -127,17 +119,9 @@ export const ToolLabel: React.FC<{
|
||||
);
|
||||
}
|
||||
case "process_list":
|
||||
return (
|
||||
<span className="truncate text-sm text-content-secondary">
|
||||
Listing processes
|
||||
</span>
|
||||
);
|
||||
return <span className="truncate text-[13px]">Listing processes</span>;
|
||||
case "read_file":
|
||||
return (
|
||||
<span className="truncate text-sm text-content-secondary">
|
||||
Reading file…
|
||||
</span>
|
||||
);
|
||||
return <span className="truncate text-[13px]">Reading file…</span>;
|
||||
case "write_file": {
|
||||
const path = parsed ? asString(parsed.path) : "";
|
||||
if (path) {
|
||||
@@ -147,11 +131,7 @@ export const ToolLabel: React.FC<{
|
||||
</code>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<span className="truncate text-sm text-content-secondary">
|
||||
Writing file
|
||||
</span>
|
||||
);
|
||||
return <span className="truncate text-[13px]">Writing file</span>;
|
||||
}
|
||||
case "edit_files": {
|
||||
const files = parsed?.files;
|
||||
@@ -165,33 +145,21 @@ export const ToolLabel: React.FC<{
|
||||
);
|
||||
}
|
||||
}
|
||||
return (
|
||||
<span className="truncate text-sm text-content-secondary">
|
||||
Editing files
|
||||
</span>
|
||||
);
|
||||
return <span className="truncate text-[13px]">Editing files</span>;
|
||||
}
|
||||
case "create_workspace": {
|
||||
const wsName = parsedResult ? asString(parsedResult.workspace_name) : "";
|
||||
if (wsName) {
|
||||
return (
|
||||
<span className="truncate text-sm text-content-secondary">
|
||||
Created {wsName}
|
||||
</span>
|
||||
);
|
||||
return <span className="truncate text-[13px]">Created {wsName}</span>;
|
||||
}
|
||||
return (
|
||||
<span className="truncate text-sm text-content-secondary">
|
||||
Creating workspace
|
||||
</span>
|
||||
);
|
||||
return <span className="truncate text-[13px]">Creating workspace</span>;
|
||||
}
|
||||
case "list_templates": {
|
||||
const count = parsedResult
|
||||
? ((parsedResult.count as number | undefined) ?? 0)
|
||||
: 0;
|
||||
return (
|
||||
<span className="truncate text-sm text-content-secondary">
|
||||
<span className="truncate text-[13px]">
|
||||
{count === 0
|
||||
? "Listing templates…"
|
||||
: count === 1
|
||||
@@ -208,17 +176,13 @@ export const ToolLabel: React.FC<{
|
||||
? asString(templateRec.display_name) || asString(templateRec.name)
|
||||
: "";
|
||||
return (
|
||||
<span className="truncate text-sm text-content-secondary">
|
||||
<span className="truncate text-[13px]">
|
||||
{tmplName ? `Read template ${tmplName}` : "Reading template…"}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
case "chat_summarized":
|
||||
return (
|
||||
<span className="truncate text-sm text-content-secondary">
|
||||
Summarized
|
||||
</span>
|
||||
);
|
||||
return <span className="truncate text-[13px]">Summarized</span>;
|
||||
case "attach_file": {
|
||||
const attachedName =
|
||||
(parsedResult ? asString(parsedResult.name) : "") ||
|
||||
@@ -226,30 +190,20 @@ export const ToolLabel: React.FC<{
|
||||
(parsed ? asString(parsed.path).split("/").pop() : "") ||
|
||||
"file";
|
||||
return (
|
||||
<span className="truncate text-sm text-content-secondary">
|
||||
{`Attached ${attachedName}`}
|
||||
</span>
|
||||
<span className="truncate text-[13px]">{`Attached ${attachedName}`}</span>
|
||||
);
|
||||
}
|
||||
case "computer":
|
||||
return (
|
||||
<span className="truncate text-sm text-content-secondary">
|
||||
Screenshot
|
||||
</span>
|
||||
);
|
||||
return <span className="truncate text-[13px]">Screenshot</span>;
|
||||
case "propose_plan": {
|
||||
const path = parsed ? asString(parsed.path) || "PLAN.md" : "PLAN.md";
|
||||
const filename = path.split("/").pop() || "PLAN.md";
|
||||
return (
|
||||
<span className="truncate text-sm text-content-secondary">
|
||||
{filename}
|
||||
</span>
|
||||
);
|
||||
return <span className="truncate text-[13px]">{filename}</span>;
|
||||
}
|
||||
case "read_skill": {
|
||||
const skillName = parsed ? asString(parsed.name) : "";
|
||||
return (
|
||||
<span className="truncate text-sm text-content-secondary">
|
||||
<span className="truncate text-[13px]">
|
||||
{skillName
|
||||
? parsedResult
|
||||
? `Read skill ${skillName}`
|
||||
@@ -266,7 +220,7 @@ export const ToolLabel: React.FC<{
|
||||
? `${skillName}/${filePath}`
|
||||
: skillName || filePath || "skill file";
|
||||
return (
|
||||
<span className="truncate text-sm text-content-secondary">
|
||||
<span className="truncate text-[13px]">
|
||||
{parsedResult ? `Read ${label}` : `Reading ${label}…`}
|
||||
</span>
|
||||
);
|
||||
@@ -274,7 +228,7 @@ export const ToolLabel: React.FC<{
|
||||
case "start_workspace": {
|
||||
const wsName = parsedResult ? asString(parsedResult.workspace_name) : "";
|
||||
return (
|
||||
<span className="truncate text-sm text-content-secondary">
|
||||
<span className="truncate text-[13px]">
|
||||
{wsName ? `Started ${wsName}` : "Starting workspace…"}
|
||||
</span>
|
||||
);
|
||||
@@ -282,11 +236,7 @@ export const ToolLabel: React.FC<{
|
||||
|
||||
default: {
|
||||
const displayName = mcpSlug ? humanizeMCPToolName(mcpSlug, name) : name;
|
||||
return (
|
||||
<span className="truncate text-sm text-content-secondary">
|
||||
{displayName}
|
||||
</span>
|
||||
);
|
||||
return <span className="truncate text-[13px]">{displayName}</span>;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -36,8 +36,8 @@ const WebSearchSources: FC<WebSearchSourcesProps> = ({ sources }) => {
|
||||
hasContent={unique.length > 0}
|
||||
header={
|
||||
<>
|
||||
<GlobeIcon className="h-4 w-4 shrink-0 text-content-secondary" />
|
||||
<span className="text-sm text-content-secondary">
|
||||
<GlobeIcon className="h-4 w-4 shrink-0 text-current" />
|
||||
<span className="text-[13px]">
|
||||
Searched <span className="text-content-secondary/60">{detail}</span>
|
||||
</span>
|
||||
</>
|
||||
|
||||
@@ -9,7 +9,6 @@ import {
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from "#/components/Tooltip/Tooltip";
|
||||
import { cn } from "#/utils/cn";
|
||||
import { ToolCollapsible } from "./ToolCollapsible";
|
||||
import {
|
||||
DIFFS_FONT_STYLE,
|
||||
@@ -43,13 +42,11 @@ export const WriteFileTool: React.FC<{
|
||||
hasContent={hasDiff}
|
||||
header={
|
||||
<>
|
||||
<span className={cn("text-sm", "text-content-secondary")}>
|
||||
{label}
|
||||
</span>
|
||||
<span className="text-[13px]">{label}</span>
|
||||
{isError && (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<TriangleAlertIcon className="h-3.5 w-3.5 shrink-0 text-content-secondary" />
|
||||
<TriangleAlertIcon className="h-3.5 w-3.5 shrink-0 text-current" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
{errorMessage || "Failed to write file"}
|
||||
@@ -57,7 +54,7 @@ export const WriteFileTool: React.FC<{
|
||||
</Tooltip>
|
||||
)}
|
||||
{isRunning && (
|
||||
<LoaderIcon className="h-3.5 w-3.5 shrink-0 animate-spin motion-reduce:animate-none text-content-secondary" />
|
||||
<LoaderIcon className="h-3.5 w-3.5 shrink-0 animate-spin motion-reduce:animate-none text-current" />
|
||||
)}
|
||||
</>
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user