mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat(site): add transcript tool icons (#25724)
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import { BrainIcon } from "lucide-react";
|
||||
import { type FC, useEffect, useState } from "react";
|
||||
import { Alert, AlertDescription, AlertTitle } from "#/components/Alert/Alert";
|
||||
import { Link } from "#/components/Link/Link";
|
||||
@@ -18,14 +19,18 @@ type ReconnectingStatus = Extract<LiveStatusModel, { phase: "reconnecting" }>;
|
||||
const StatusPlaceholder: FC<{
|
||||
text: string;
|
||||
shimmer?: boolean;
|
||||
}> = ({ text, shimmer = false }) => {
|
||||
showThinkingIcon?: boolean;
|
||||
}> = ({ text, shimmer = false, showThinkingIcon = false }) => {
|
||||
return (
|
||||
<div className="relative min-h-6">
|
||||
{/* Reserve the final response height without exposing a selectable copy. */}
|
||||
<Response aria-hidden className="invisible select-none">
|
||||
{text}
|
||||
</Response>
|
||||
<div className="pointer-events-none absolute inset-0 flex items-baseline gap-2">
|
||||
<div className="pointer-events-none absolute inset-0 flex items-center gap-2 text-content-secondary">
|
||||
{showThinkingIcon && (
|
||||
<BrainIcon className="size-4 shrink-0 stroke-[1.5] text-current" />
|
||||
)}
|
||||
{shimmer ? (
|
||||
<Shimmer as="div" className="text-[13px] leading-relaxed">
|
||||
{text}
|
||||
@@ -54,6 +59,7 @@ const StartingPlaceholder: FC = () => {
|
||||
<StatusPlaceholder
|
||||
text={isDelayed ? DELAYED_STARTUP_TEXT : THINKING_TEXT}
|
||||
shimmer={!isDelayed}
|
||||
showThinkingIcon={!isDelayed}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
import { ChevronLeftIcon, ChevronRightIcon, PencilIcon } from "lucide-react";
|
||||
import {
|
||||
BrainIcon,
|
||||
ChevronLeftIcon,
|
||||
ChevronRightIcon,
|
||||
PencilIcon,
|
||||
} from "lucide-react";
|
||||
import {
|
||||
type FC,
|
||||
Fragment,
|
||||
@@ -160,13 +165,16 @@ const ReasoningDisclosure = memo<{
|
||||
expanded={expanded}
|
||||
onExpandedChange={(open) => setManualToggle(open)}
|
||||
header={
|
||||
isStreaming ? (
|
||||
<Shimmer as="span" className="text-[13px]">
|
||||
{title}
|
||||
</Shimmer>
|
||||
) : (
|
||||
<span className="text-[13px]">{title}</span>
|
||||
)
|
||||
<>
|
||||
<BrainIcon className="size-4 shrink-0 stroke-[1.5] text-current" />
|
||||
{isStreaming ? (
|
||||
<Shimmer as="span" className="text-[13px] leading-6">
|
||||
{title}
|
||||
</Shimmer>
|
||||
) : (
|
||||
<span className="text-[13px] leading-6">{title}</span>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
>
|
||||
{hasText && (
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { BrainIcon } from "lucide-react";
|
||||
import type { FC } from "react";
|
||||
import type { UrlTransform } from "streamdown";
|
||||
import type * as TypesGen from "#/api/typesGenerated";
|
||||
@@ -36,7 +37,8 @@ const hasTextOrReasoningBlock = (blocks: readonly RenderBlock[]): boolean =>
|
||||
const StreamingThinkingPlaceholder: FC = () => (
|
||||
<div data-transcript-row="" className="text-content-secondary">
|
||||
<TranscriptRow className="w-full gap-2">
|
||||
<Shimmer as="span" className="text-[13px] leading-relaxed">
|
||||
<BrainIcon className="size-4 shrink-0 stroke-[1.5] text-current" />
|
||||
<Shimmer as="span" className="text-[13px] leading-6">
|
||||
Thinking
|
||||
</Shimmer>
|
||||
</TranscriptRow>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { BrainIcon } from "lucide-react";
|
||||
import type { ComponentPropsWithRef } from "react";
|
||||
import { cn } from "#/utils/cn";
|
||||
|
||||
@@ -8,10 +9,13 @@ export const Thinking = ({ className, ref, ...props }: ThinkingProps) => {
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"rounded-lg border border-border bg-surface-primary px-3 py-2 text-xs text-content-secondary",
|
||||
"flex items-start gap-2 rounded-lg border border-border bg-surface-primary px-3 py-2 text-xs text-content-secondary",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
>
|
||||
<BrainIcon className="mt-0.5 size-4 shrink-0 stroke-[1.5] text-current" />
|
||||
<div className="min-w-0 flex-1">{props.children}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -53,55 +53,55 @@ export const AdvisorTool: React.FC<AdvisorToolProps> = ({
|
||||
defaultExpanded
|
||||
headerClassName="items-start"
|
||||
header={(expanded) => (
|
||||
<>
|
||||
<div className="flex min-w-0 flex-1 flex-col gap-0.5">
|
||||
<div className="flex min-w-0 items-center gap-2 leading-4">
|
||||
<ToolIcon
|
||||
name="advisor"
|
||||
isError={showError}
|
||||
isRunning={isRunning}
|
||||
/>
|
||||
<ToolLabel
|
||||
name="advisor"
|
||||
args={{ question: questionText }}
|
||||
result={resultType ? { type: resultType } : undefined}
|
||||
/>
|
||||
{isRunning && (
|
||||
<span className="shrink-0 rounded-full border border-solid border-border-default px-2 text-[13px] leading-4 text-content-secondary">
|
||||
{RUNNING_MESSAGE}
|
||||
</span>
|
||||
)}
|
||||
{advisorModelText && (
|
||||
<span className="min-w-0 truncate rounded-full border border-solid border-border-default px-2 text-[13px] leading-4 text-content-secondary">
|
||||
{advisorModelText}
|
||||
</span>
|
||||
)}
|
||||
{remainingUses !== undefined && (
|
||||
<span className="shrink-0 rounded-full border border-solid border-border-default px-2 text-[13px] leading-4 text-content-secondary">
|
||||
{remainingUses.toLocaleString("en-US")} uses left
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<span
|
||||
className={cn(
|
||||
"ml-6 block whitespace-normal break-words text-[13px]",
|
||||
"font-normal leading-5 text-content-primary",
|
||||
"[overflow-wrap:anywhere]",
|
||||
!expanded && "line-clamp-2",
|
||||
)}
|
||||
>
|
||||
{questionText}
|
||||
</span>
|
||||
<div className="flex min-w-0 flex-1 flex-col gap-0.5">
|
||||
<div className="flex min-w-0 items-center gap-2 leading-4">
|
||||
<ToolIcon
|
||||
name="advisor"
|
||||
isError={showError}
|
||||
isRunning={isRunning}
|
||||
/>
|
||||
<ToolLabel
|
||||
name="advisor"
|
||||
args={{ question: questionText }}
|
||||
result={resultType ? { type: resultType } : undefined}
|
||||
/>
|
||||
{isRunning && (
|
||||
<span className="shrink-0 rounded-full border border-solid border-border-default px-2 text-[13px] leading-4 text-content-secondary">
|
||||
{RUNNING_MESSAGE}
|
||||
</span>
|
||||
)}
|
||||
{advisorModelText && (
|
||||
<span className="min-w-0 truncate rounded-full border border-solid border-border-default px-2 text-[13px] leading-4 text-content-secondary">
|
||||
{advisorModelText}
|
||||
</span>
|
||||
)}
|
||||
{remainingUses !== undefined && (
|
||||
<span className="shrink-0 rounded-full border border-solid border-border-default px-2 text-[13px] leading-4 text-content-secondary">
|
||||
{remainingUses.toLocaleString("en-US")} uses left
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{showLimitReached ? (
|
||||
<TriangleAlertIcon className="mt-0.5 size-3.5 shrink-0 text-content-warning" />
|
||||
) : showError ? (
|
||||
<CircleAlertIcon className="mt-0.5 size-3.5 shrink-0 text-content-destructive" />
|
||||
) : isRunning ? (
|
||||
<LoaderIcon className="mt-0.5 size-3.5 shrink-0 animate-spin motion-reduce:animate-none text-content-secondary" />
|
||||
) : null}
|
||||
</>
|
||||
<span
|
||||
className={cn(
|
||||
"ml-6 block whitespace-normal break-words text-[13px]",
|
||||
"font-normal leading-5 text-content-primary",
|
||||
"[overflow-wrap:anywhere]",
|
||||
!expanded && "line-clamp-2",
|
||||
)}
|
||||
>
|
||||
{questionText}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
headerStatus={
|
||||
showLimitReached ? (
|
||||
<TriangleAlertIcon className="mt-0.5 size-3.5 shrink-0 text-content-warning" />
|
||||
) : showError ? (
|
||||
<CircleAlertIcon className="mt-0.5 size-3.5 shrink-0 text-content-destructive" />
|
||||
) : isRunning ? (
|
||||
<LoaderIcon className="mt-0.5 size-3.5 shrink-0 animate-spin motion-reduce:animate-none text-content-secondary" />
|
||||
) : null
|
||||
}
|
||||
>
|
||||
<ScrollArea
|
||||
className="mt-1.5 rounded-md border border-solid border-border-default bg-surface-primary"
|
||||
|
||||
@@ -10,6 +10,7 @@ import { Input } from "#/components/Input/Input";
|
||||
import { RadioGroup, RadioGroupItem } from "#/components/RadioGroup/RadioGroup";
|
||||
import { cn } from "#/utils/cn";
|
||||
import { TranscriptRow } from "../TranscriptRow";
|
||||
import { ToolIcon } from "./ToolIcon";
|
||||
import type { ToolStatus } from "./utils";
|
||||
|
||||
export type AskUserQuestion = {
|
||||
@@ -539,8 +540,9 @@ export const AskUserQuestionTool: FC<AskUserQuestionToolProps> = ({
|
||||
<div className="w-full">
|
||||
<TranscriptRow
|
||||
role="alert"
|
||||
className="gap-1.5 text-[13px] text-content-secondary"
|
||||
className="gap-2 text-[13px] text-content-secondary"
|
||||
>
|
||||
<ToolIcon name="ask_user_question" isError={isError} />
|
||||
<TriangleAlertIcon
|
||||
aria-label="Error"
|
||||
className="size-3.5 shrink-0 text-content-secondary"
|
||||
@@ -555,7 +557,16 @@ export const AskUserQuestionTool: FC<AskUserQuestionToolProps> = ({
|
||||
return (
|
||||
<div className="w-full">
|
||||
{isRunning ? (
|
||||
<TranscriptRow role="status" aria-live="polite" className="gap-1.5">
|
||||
<TranscriptRow
|
||||
role="status"
|
||||
aria-live="polite"
|
||||
className="gap-2 text-content-secondary"
|
||||
>
|
||||
<ToolIcon
|
||||
name="ask_user_question"
|
||||
isError={false}
|
||||
isRunning={isRunning}
|
||||
/>
|
||||
<span className="text-[13px] text-content-secondary">
|
||||
Asking for clarification...
|
||||
</span>
|
||||
@@ -677,7 +688,16 @@ export const AskUserQuestionTool: FC<AskUserQuestionToolProps> = ({
|
||||
return (
|
||||
<div className="w-full">
|
||||
{isRunning && (
|
||||
<TranscriptRow role="status" aria-live="polite" className="gap-1.5">
|
||||
<TranscriptRow
|
||||
role="status"
|
||||
aria-live="polite"
|
||||
className="gap-2 text-content-secondary"
|
||||
>
|
||||
<ToolIcon
|
||||
name="ask_user_question"
|
||||
isError={false}
|
||||
isRunning={isRunning}
|
||||
/>
|
||||
<span className="text-[13px] text-content-secondary">
|
||||
Asking for clarification...
|
||||
</span>
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
} from "#/components/Tooltip/Tooltip";
|
||||
import { Response } from "../Response";
|
||||
import { ToolCollapsible } from "./ToolCollapsible";
|
||||
import { ToolIcon } from "./ToolIcon";
|
||||
import type { ToolStatus } from "./utils";
|
||||
|
||||
/**
|
||||
@@ -29,9 +30,18 @@ export const ChatSummarizedTool: React.FC<{
|
||||
hasContent={hasSummary}
|
||||
header={
|
||||
<>
|
||||
<span className="text-[13px]">
|
||||
<ToolIcon
|
||||
name="chat_summarized"
|
||||
isError={isError}
|
||||
isRunning={isRunning}
|
||||
/>
|
||||
<span className="text-[13px] leading-6">
|
||||
{isRunning ? "Summarizing…" : "Summarized"}
|
||||
</span>
|
||||
</>
|
||||
}
|
||||
headerStatus={
|
||||
<>
|
||||
{isError && (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
} from "#/components/Tooltip/Tooltip";
|
||||
import { ImageLightbox } from "../../ImageLightbox";
|
||||
import { ToolCollapsible } from "./ToolCollapsible";
|
||||
import { ToolIcon } from "./ToolIcon";
|
||||
import type { ToolStatus } from "./utils";
|
||||
|
||||
/**
|
||||
@@ -39,9 +40,14 @@ export const ComputerTool: React.FC<{
|
||||
defaultExpanded={hasImage}
|
||||
header={
|
||||
<>
|
||||
<span className="text-[13px]">
|
||||
<ToolIcon name="computer" isError={isError} isRunning={isRunning} />
|
||||
<span className="text-[13px] leading-6">
|
||||
{isRunning ? "Taking screenshot…" : "Screenshot"}
|
||||
</span>
|
||||
</>
|
||||
}
|
||||
headerStatus={
|
||||
<>
|
||||
{isError && (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
|
||||
@@ -1,9 +1,4 @@
|
||||
import {
|
||||
ExternalLinkIcon,
|
||||
LoaderIcon,
|
||||
MonitorIcon,
|
||||
TriangleAlertIcon,
|
||||
} from "lucide-react";
|
||||
import { ExternalLinkIcon, LoaderIcon, TriangleAlertIcon } from "lucide-react";
|
||||
import type React from "react";
|
||||
import { Link } from "react-router";
|
||||
import {
|
||||
@@ -12,6 +7,7 @@ import {
|
||||
TooltipTrigger,
|
||||
} from "#/components/Tooltip/Tooltip";
|
||||
import { ToolCollapsible } from "./ToolCollapsible";
|
||||
import { ToolIcon } from "./ToolIcon";
|
||||
import { asRecord, asString, type ToolStatus } from "./utils";
|
||||
import { WorkspaceBuildLogSection } from "./WorkspaceBuildLogSection";
|
||||
|
||||
@@ -72,8 +68,26 @@ export const CreateWorkspaceTool: React.FC<{
|
||||
|
||||
const header = (
|
||||
<>
|
||||
<MonitorIcon className="size-4 shrink-0 text-current" />
|
||||
<span className="text-[13px]">{label}</span>
|
||||
<ToolIcon
|
||||
name="create_workspace"
|
||||
isError={isError}
|
||||
isRunning={isRunning}
|
||||
/>
|
||||
<span className="text-[13px] leading-6">{label}</span>
|
||||
{workspaceLink && !isRunning && (
|
||||
<Link
|
||||
to={workspaceLink}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
className="ml-1 inline-flex align-middle text-content-secondary opacity-50 transition-opacity hover:opacity-100"
|
||||
aria-label="View workspace"
|
||||
>
|
||||
<ExternalLinkIcon className="size-3" />
|
||||
</Link>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
const headerStatus = (
|
||||
<>
|
||||
{isError && (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
@@ -87,16 +101,6 @@ export const CreateWorkspaceTool: React.FC<{
|
||||
{isRunning && (
|
||||
<LoaderIcon className="size-3.5 shrink-0 animate-spin motion-reduce:animate-none text-current" />
|
||||
)}
|
||||
{workspaceLink && !isRunning && (
|
||||
<Link
|
||||
to={workspaceLink}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
className="ml-1 inline-flex align-middle text-content-secondary opacity-50 transition-opacity hover:opacity-100"
|
||||
aria-label="View workspace"
|
||||
>
|
||||
<ExternalLinkIcon className="size-3" />
|
||||
</Link>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -104,6 +108,7 @@ export const CreateWorkspaceTool: React.FC<{
|
||||
<div className="w-full">
|
||||
<ToolCollapsible
|
||||
header={header}
|
||||
headerStatus={headerStatus}
|
||||
hasContent={hasBuildLogs}
|
||||
defaultExpanded={isRunning}
|
||||
>
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
resolveAgentDisplayState,
|
||||
} from "./displayMode";
|
||||
import { AgentDisplayModeToolCollapsible } from "./ToolCollapsible";
|
||||
import { ToolIcon } from "./ToolIcon";
|
||||
import {
|
||||
DIFFS_FONT_STYLE,
|
||||
type EditFilesFileEntry,
|
||||
@@ -69,7 +70,12 @@ export const EditFilesTool: React.FC<{
|
||||
autoDisplayState={EDIT_FILES_AUTO_DISPLAY_STATE}
|
||||
header={
|
||||
<>
|
||||
<span className="text-[13px]">{label}</span>
|
||||
<ToolIcon name="edit_files" isError={isError} isRunning={isRunning} />
|
||||
<span className="text-[13px] leading-6">{label}</span>
|
||||
</>
|
||||
}
|
||||
headerStatus={
|
||||
<>
|
||||
{isError && (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
isAgentDisplayOpen,
|
||||
resolveAgentDisplayState,
|
||||
} from "./displayMode";
|
||||
import { ToolIcon } from "./ToolIcon";
|
||||
import {
|
||||
formatShellDurationMs,
|
||||
sanitizeExecuteModelIntent,
|
||||
@@ -190,7 +191,8 @@ const ShellCommandLine: React.FC<{
|
||||
const commandDisplay = summary || command;
|
||||
return (
|
||||
<>
|
||||
<span className="block min-w-0 truncate text-[13px] font-normal text-current">
|
||||
<ToolIcon name="execute" isError={false} />
|
||||
<span className="block min-w-0 truncate text-[13px] font-normal leading-6 text-current">
|
||||
{intentLabel ? (
|
||||
<>
|
||||
{intentLabel} using {commandDisplay}
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
TooltipTrigger,
|
||||
} from "#/components/Tooltip/Tooltip";
|
||||
import { ToolCollapsible } from "./ToolCollapsible";
|
||||
import { ToolIcon } from "./ToolIcon";
|
||||
import { asRecord, asString, type ToolStatus } from "./utils";
|
||||
|
||||
/**
|
||||
@@ -36,7 +37,16 @@ export const ListTemplatesTool: React.FC<{
|
||||
hasContent={hasContent}
|
||||
header={
|
||||
<>
|
||||
<span className="text-[13px]">{label}</span>
|
||||
<ToolIcon
|
||||
name="list_templates"
|
||||
isError={isError}
|
||||
isRunning={isRunning}
|
||||
/>
|
||||
<span className="text-[13px] leading-6">{label}</span>
|
||||
</>
|
||||
}
|
||||
headerStatus={
|
||||
<>
|
||||
{isError && (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
resolveAgentDisplayState,
|
||||
} from "./displayMode";
|
||||
import { AgentDisplayModeToolCollapsible } from "./ToolCollapsible";
|
||||
import { ToolIcon } from "./ToolIcon";
|
||||
import { COLLAPSED_OUTPUT_HEIGHT, signalTooltipLabel } from "./utils";
|
||||
|
||||
type ProcessOutputToolProps = {
|
||||
@@ -77,8 +78,7 @@ const ProcessOutputToolInner: React.FC<ProcessOutputToolInnerProps> = ({
|
||||
const toggleOutputExpansion = () => {
|
||||
setOutputFullyExpanded((expanded) => !expanded);
|
||||
};
|
||||
const hasHeaderActions =
|
||||
isRunning || Boolean(killedBySignal) || showExitCode || hasOutput;
|
||||
const hasHeaderActions = Boolean(killedBySignal) || showExitCode || hasOutput;
|
||||
|
||||
return (
|
||||
<AgentDisplayModeToolCollapsible
|
||||
@@ -89,13 +89,24 @@ const ProcessOutputToolInner: React.FC<ProcessOutputToolInnerProps> = ({
|
||||
ariaLabel={(expanded) =>
|
||||
expanded ? "Collapse process output" : "Expand process output"
|
||||
}
|
||||
header={<span className="text-[13px]">Process output</span>}
|
||||
header={
|
||||
<>
|
||||
<ToolIcon
|
||||
name="process_output"
|
||||
isError={isError}
|
||||
isRunning={isRunning}
|
||||
/>
|
||||
<span className="text-[13px] leading-6">Process output</span>
|
||||
</>
|
||||
}
|
||||
headerStatus={
|
||||
isRunning ? (
|
||||
<LoaderIcon className="size-3.5 shrink-0 animate-spin motion-reduce:animate-none text-content-secondary" />
|
||||
) : undefined
|
||||
}
|
||||
headerActions={
|
||||
hasHeaderActions ? (
|
||||
<>
|
||||
{isRunning && (
|
||||
<LoaderIcon className="size-3.5 shrink-0 animate-spin motion-reduce:animate-none text-content-secondary" />
|
||||
)}
|
||||
{killedBySignal && !isRunning && (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
} from "#/components/Tooltip/Tooltip";
|
||||
import { Response } from "../Response";
|
||||
import { TranscriptRow } from "../TranscriptRow";
|
||||
import { ToolIcon } from "./ToolIcon";
|
||||
import type { ToolStatus } from "./utils";
|
||||
|
||||
export const ProposePlanTool: React.FC<{
|
||||
@@ -73,8 +74,13 @@ export const ProposePlanTool: React.FC<{
|
||||
|
||||
return (
|
||||
<div className="w-full">
|
||||
<TranscriptRow className="gap-1.5 text-content-secondary">
|
||||
<span className="text-[13px]">
|
||||
<TranscriptRow className="gap-2 text-content-secondary">
|
||||
<ToolIcon
|
||||
name="propose_plan"
|
||||
isError={effectiveError}
|
||||
isRunning={isRunning}
|
||||
/>
|
||||
<span className="text-[13px] leading-6">
|
||||
{isRunning ? `Proposing ${filename}…` : `Proposed ${filename}`}
|
||||
</span>
|
||||
{effectiveError && (
|
||||
@@ -138,7 +144,7 @@ export const ProposePlanTool: React.FC<{
|
||||
)
|
||||
)}
|
||||
{fetchLoading && (
|
||||
<TranscriptRow className="gap-1.5 text-[13px] text-content-secondary">
|
||||
<TranscriptRow className="gap-2 text-[13px] text-content-secondary">
|
||||
<LoaderIcon className="size-3.5 animate-spin motion-reduce:animate-none" />
|
||||
Loading plan…
|
||||
</TranscriptRow>
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
} from "#/components/Tooltip/Tooltip";
|
||||
import { asRecord, asString } from "../runtimeTypeUtils";
|
||||
import { ToolCollapsible } from "./ToolCollapsible";
|
||||
import { ToolIcon } from "./ToolIcon";
|
||||
import {
|
||||
DIFFS_FONT_STYLE,
|
||||
getFileViewerOptionsMinimal,
|
||||
@@ -96,7 +97,12 @@ export const ReadFileTool: React.FC<{
|
||||
onExpandedChange={onExpandedChange}
|
||||
header={
|
||||
<>
|
||||
<span className="text-[13px]">{label}</span>
|
||||
<ToolIcon name="read_file" isError={isError} isRunning={isRunning} />
|
||||
<span className="text-[13px] leading-6">{label}</span>
|
||||
</>
|
||||
}
|
||||
headerStatus={
|
||||
<>
|
||||
{isError && (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
import type { MergedTool } from "../../ChatConversation/types";
|
||||
import { getReadFileToolData, ReadFileTool } from "./ReadFileTool";
|
||||
import { ToolCollapsible } from "./ToolCollapsible";
|
||||
import { ToolIcon } from "./ToolIcon";
|
||||
|
||||
type ReadFileItem = {
|
||||
id: string;
|
||||
@@ -50,7 +51,12 @@ export const ReadFilesTool: FC<{
|
||||
onExpandedChange={onExpandedChange}
|
||||
header={
|
||||
<>
|
||||
<span className="text-[13px]">{label}</span>
|
||||
<ToolIcon
|
||||
name="read_file"
|
||||
isError={isError}
|
||||
isRunning={isRunning}
|
||||
/>
|
||||
<span className="text-[13px] leading-6">{label}</span>
|
||||
{isError && (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { BookOpenIcon, LoaderIcon, TriangleAlertIcon } from "lucide-react";
|
||||
import { LoaderIcon, TriangleAlertIcon } from "lucide-react";
|
||||
import type React from "react";
|
||||
import { ScrollArea } from "#/components/ScrollArea/ScrollArea";
|
||||
import {
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
} from "#/components/Tooltip/Tooltip";
|
||||
import { Response } from "../Response";
|
||||
import { ToolCollapsible } from "./ToolCollapsible";
|
||||
import { ToolIcon } from "./ToolIcon";
|
||||
import type { ToolStatus } from "./utils";
|
||||
|
||||
export const ReadSkillTool: React.FC<{
|
||||
@@ -26,10 +27,14 @@ export const ReadSkillTool: React.FC<{
|
||||
hasContent={hasContent}
|
||||
header={
|
||||
<>
|
||||
<BookOpenIcon className="size-4 shrink-0 text-current" />
|
||||
<span className="text-[13px]">
|
||||
<ToolIcon name="read_skill" isError={isError} isRunning={isRunning} />
|
||||
<span className="text-[13px] leading-6">
|
||||
{isRunning ? `Reading ${label}…` : `Read ${label}`}
|
||||
</span>
|
||||
</>
|
||||
}
|
||||
headerStatus={
|
||||
<>
|
||||
{isError && (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
TooltipTrigger,
|
||||
} from "#/components/Tooltip/Tooltip";
|
||||
import { TranscriptRow } from "../TranscriptRow";
|
||||
import { ToolIcon } from "./ToolIcon";
|
||||
import type { ToolStatus } from "./utils";
|
||||
|
||||
/**
|
||||
@@ -27,8 +28,9 @@ export const ReadTemplateTool: React.FC<{
|
||||
: "Read template";
|
||||
|
||||
return (
|
||||
<TranscriptRow className="gap-1.5 text-content-secondary">
|
||||
<span className="text-[13px]">{label}</span>
|
||||
<TranscriptRow className="gap-2 text-content-secondary">
|
||||
<ToolIcon name="read_template" isError={isError} isRunning={isRunning} />
|
||||
<span className="text-[13px] leading-6">{label}</span>
|
||||
{isError && (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { LoaderIcon, MonitorPlayIcon, TriangleAlertIcon } from "lucide-react";
|
||||
import { LoaderIcon, TriangleAlertIcon } from "lucide-react";
|
||||
import type { FC } from "react";
|
||||
import {
|
||||
Tooltip,
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
TooltipTrigger,
|
||||
} from "#/components/Tooltip/Tooltip";
|
||||
import { ToolCollapsible } from "./ToolCollapsible";
|
||||
import { ToolIcon } from "./ToolIcon";
|
||||
import type { ToolStatus } from "./utils";
|
||||
import { WorkspaceBuildLogSection } from "./WorkspaceBuildLogSection";
|
||||
|
||||
@@ -42,8 +43,16 @@ export const StartWorkspaceTool: FC<StartWorkspaceToolProps> = ({
|
||||
|
||||
const header = (
|
||||
<>
|
||||
<MonitorPlayIcon className="size-4 shrink-0 text-current" />
|
||||
<span className="text-[13px]">{label}</span>
|
||||
<ToolIcon
|
||||
name="start_workspace"
|
||||
isError={isError}
|
||||
isRunning={isRunning}
|
||||
/>
|
||||
<span className="text-[13px] leading-6">{label}</span>
|
||||
</>
|
||||
);
|
||||
const headerStatus = (
|
||||
<>
|
||||
{isError && (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
@@ -67,6 +76,7 @@ export const StartWorkspaceTool: FC<StartWorkspaceToolProps> = ({
|
||||
<div className="w-full">
|
||||
<ToolCollapsible
|
||||
header={header}
|
||||
headerStatus={headerStatus}
|
||||
hasContent={hasBuildLogs}
|
||||
defaultExpanded={isRunning}
|
||||
>
|
||||
|
||||
@@ -69,7 +69,7 @@ function getSubagentLabel(
|
||||
): React.ReactNode {
|
||||
if (showDesktopPreview && toolStatus === "running") {
|
||||
return (
|
||||
<Shimmer as="span" className="text-[13px]">
|
||||
<Shimmer as="span" className="text-[13px] leading-6">
|
||||
Using the computer...
|
||||
</Shimmer>
|
||||
);
|
||||
@@ -125,20 +125,22 @@ const SubagentStatusIcon: React.FC<{
|
||||
const subagentCompleted = isSubagentSuccessStatus(subagentStatus);
|
||||
const DefaultIcon = iconKind === "monitor" ? MonitorIcon : BotIcon;
|
||||
if (isTimeout && !subagentCompleted) {
|
||||
return <ClockIcon className="size-4 shrink-0 text-current" />;
|
||||
return <ClockIcon className="size-4 shrink-0 stroke-[1.5] text-current" />;
|
||||
}
|
||||
if ((isError && !subagentCompleted) || toolStatus === "error") {
|
||||
return <CircleXIcon className="size-4 shrink-0 text-current" />;
|
||||
}
|
||||
if (toolStatus === "running") {
|
||||
if (showDesktopPreview) {
|
||||
return <MonitorIcon className="size-4 shrink-0 text-current" />;
|
||||
return (
|
||||
<MonitorIcon className="size-4 shrink-0 stroke-[1.5] text-current" />
|
||||
);
|
||||
}
|
||||
return (
|
||||
<LoaderIcon className="size-4 shrink-0 animate-spin motion-reduce:animate-none text-content-link" />
|
||||
);
|
||||
}
|
||||
return <DefaultIcon className="size-4 shrink-0 text-current" />;
|
||||
return <DefaultIcon className="size-4 shrink-0 stroke-[1.5] text-current" />;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
} from "storybook/test";
|
||||
import { reactRouterParameters } from "storybook-addon-remix-react-router";
|
||||
import { ChatWorkspaceContext } from "../../../context/ChatWorkspaceContext";
|
||||
import { BlockList } from "../../ChatConversation/ConversationTimeline";
|
||||
import { DesktopPanelContext } from "./DesktopPanelContext";
|
||||
import { Tool } from "./Tool";
|
||||
|
||||
@@ -18,6 +19,10 @@ const executeIntentCommand = "npm test";
|
||||
const longExecuteCommand =
|
||||
"docker build --no-cache --build-arg NODE_ENV=production --build-arg API_URL=https://coder.example.com/api --build-arg SENTRY_DSN=https://example.com/sentry --build-arg FEATURE_FLAGS=agents,shell-tools --tag coder-agent:latest .";
|
||||
|
||||
// 1x1 solid coral (#FF6B6B) PNG encoded as base64.
|
||||
const TEST_PNG_B64 =
|
||||
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR4nGP4n539HwAHFwLVF8kc1wAAAABJRU5ErkJggg==";
|
||||
|
||||
const getDiffsText = (element: HTMLElement) =>
|
||||
Array.from(element.querySelectorAll("diffs-container"))
|
||||
.map((container) => container.shadowRoot?.textContent ?? "")
|
||||
@@ -48,6 +53,212 @@ const meta: Meta<typeof Tool> = {
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof Tool>;
|
||||
|
||||
type ToolShowcaseItem = {
|
||||
name: string;
|
||||
status?: React.ComponentProps<typeof Tool>["status"];
|
||||
args?: unknown;
|
||||
result?: unknown;
|
||||
isError?: boolean;
|
||||
killedBySignal?: "kill" | "terminate";
|
||||
modelIntent?: string;
|
||||
parsedCommands?: readonly string[][];
|
||||
subagentVariants?: Map<string, "general" | "explore" | "computer_use">;
|
||||
};
|
||||
|
||||
const allToolShowcaseItems: ToolShowcaseItem[] = [
|
||||
{
|
||||
name: "execute",
|
||||
args: { command: "pnpm check", model_intent: "Checking frontend" },
|
||||
modelIntent: "Checking frontend",
|
||||
parsedCommands: [["pnpm", "check"]],
|
||||
result: {
|
||||
output: "Checked 1799 files.",
|
||||
wall_duration_ms: 2400,
|
||||
exit_code: 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "process_output",
|
||||
args: { process_id: "storybook-process" },
|
||||
result: { output: "dev server ready on :6006" },
|
||||
},
|
||||
{
|
||||
name: "process_list",
|
||||
args: {},
|
||||
result: {
|
||||
processes: [
|
||||
{
|
||||
id: "storybook-process",
|
||||
command: "pnpm storybook",
|
||||
status: "running",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "process_signal",
|
||||
args: { process_id: "storybook-process", signal: "terminate" },
|
||||
result: { success: true },
|
||||
},
|
||||
{
|
||||
name: "wait_for_external_auth",
|
||||
args: { provider: "github" },
|
||||
result: { provider_display_name: "GitHub", authenticated: true },
|
||||
},
|
||||
{
|
||||
name: "read_file",
|
||||
args: { path: "site/src/pages/AgentsPage/AgentChatPage.tsx" },
|
||||
result: { content: "export const AgentChatPage = () => null;" },
|
||||
},
|
||||
{
|
||||
name: "write_file",
|
||||
args: { path: "docs/example.md", content: "# Example\n" },
|
||||
result: { path: "docs/example.md" },
|
||||
},
|
||||
{
|
||||
name: "edit_files",
|
||||
args: {
|
||||
files: [
|
||||
{
|
||||
path: "site/src/example.ts",
|
||||
edits: [{ old_text: "foo", new_text: "bar" }],
|
||||
},
|
||||
],
|
||||
},
|
||||
result: { files: [{ path: "site/src/example.ts", status: "edited" }] },
|
||||
},
|
||||
{
|
||||
name: "list_templates",
|
||||
result: {
|
||||
templates: [
|
||||
{
|
||||
id: "template-1",
|
||||
name: "go-template",
|
||||
display_name: "Go Development",
|
||||
},
|
||||
],
|
||||
count: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "read_template",
|
||||
args: { template_id: "template-1" },
|
||||
result: {
|
||||
template: { name: "go-template", display_name: "Go Development" },
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "create_workspace",
|
||||
result: {
|
||||
created: true,
|
||||
workspace_name: "agent-icons",
|
||||
build_id: "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "start_workspace",
|
||||
result: {
|
||||
started: true,
|
||||
workspace_name: "agent-icons",
|
||||
agent_status: "ready",
|
||||
build_id: "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "chat_summarized",
|
||||
result: { summary: "Earlier transcript content was compacted." },
|
||||
},
|
||||
{
|
||||
name: "propose_plan",
|
||||
args: { path: "/home/coder/.coder/plans/PLAN-example.md" },
|
||||
result: { path: "/home/coder/.coder/plans/PLAN-example.md" },
|
||||
},
|
||||
{
|
||||
name: "ask_user_question",
|
||||
args: { questions: [] },
|
||||
status: "running",
|
||||
},
|
||||
{
|
||||
name: "advisor",
|
||||
args: { question: "Which icon family should represent transcript tools?" },
|
||||
result: { answer: "Use category-level icons for better scanning." },
|
||||
},
|
||||
{
|
||||
name: "computer",
|
||||
args: { action: "screenshot" },
|
||||
result: { output: { type: "image", data: TEST_PNG_B64 } },
|
||||
},
|
||||
{
|
||||
name: "read_skill",
|
||||
args: { name: "deep-review" },
|
||||
result: {
|
||||
name: "deep-review",
|
||||
content: "# Deep Review\nReview code carefully.",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "read_skill_file",
|
||||
args: { name: "deep-review", path: "roles/security-reviewer.md" },
|
||||
result: { content: "# Security Reviewer Role\nCheck auth boundaries." },
|
||||
},
|
||||
{
|
||||
name: "spawn_agent",
|
||||
args: { title: "Repository review", prompt: "Review the code." },
|
||||
result: {
|
||||
chat_id: "bot-child",
|
||||
title: "Repository review",
|
||||
status: "completed",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "wait_agent",
|
||||
args: { chat_id: "bot-child" },
|
||||
result: {
|
||||
chat_id: "bot-child",
|
||||
title: "Repository review",
|
||||
status: "completed",
|
||||
report: "No issues found.",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "message_agent",
|
||||
args: { chat_id: "bot-child", message: "Check icon consistency." },
|
||||
result: { chat_id: "bot-child", status: "completed" },
|
||||
},
|
||||
{
|
||||
name: "close_agent",
|
||||
args: { chat_id: "bot-child" },
|
||||
result: { chat_id: "bot-child", status: "completed" },
|
||||
},
|
||||
{
|
||||
name: "spawn_computer_use_agent",
|
||||
args: { prompt: "Inspect the UI." },
|
||||
result: { chat_id: "desktop-child", status: "completed" },
|
||||
subagentVariants: new Map([["desktop-child", "computer_use"]]),
|
||||
},
|
||||
{
|
||||
name: "read_file",
|
||||
args: { path: "site/src/pages/AgentsPage/Missing.tsx" },
|
||||
status: "error",
|
||||
isError: true,
|
||||
result: { error: "File not found" },
|
||||
},
|
||||
{
|
||||
name: "create_workspace",
|
||||
status: "running",
|
||||
args: { workspace_name: "agent-icons" },
|
||||
result: {
|
||||
workspace_name: "agent-icons",
|
||||
build_id: "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "unknown_tool",
|
||||
args: { example: true },
|
||||
result: { ok: true },
|
||||
},
|
||||
];
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Execute stories
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -2578,3 +2789,65 @@ export const CreateWorkspaceBuildFailed: Story = {
|
||||
expect(canvas.getByText("Failed to create workspace")).toBeInTheDocument();
|
||||
},
|
||||
};
|
||||
|
||||
export const AllToolIconsTranscript: Story = {
|
||||
render: () => (
|
||||
<ChatWorkspaceContext value={{ workspaceId: "test-workspace-id" }}>
|
||||
<DesktopPanelContext.Provider
|
||||
value={{ desktopChatId: "desktop-child", onOpenDesktop: fn() }}
|
||||
>
|
||||
<div className="flex flex-col gap-2">
|
||||
<BlockList
|
||||
blocks={[
|
||||
{
|
||||
type: "thinking",
|
||||
text: "Thinking\nReviewing the available tools and grouping them by category.",
|
||||
},
|
||||
]}
|
||||
tools={[]}
|
||||
keyPrefix="all-tool-icons-thinking"
|
||||
/>
|
||||
{allToolShowcaseItems.map((tool, index) => (
|
||||
<Tool
|
||||
key={`${tool.name}-${index}`}
|
||||
name={tool.name}
|
||||
status={tool.status ?? "completed"}
|
||||
args={tool.args}
|
||||
result={tool.result}
|
||||
isError={tool.isError}
|
||||
killedBySignal={tool.killedBySignal}
|
||||
modelIntent={tool.modelIntent}
|
||||
parsedCommands={tool.parsedCommands}
|
||||
subagentVariants={tool.subagentVariants}
|
||||
shellToolDisplayMode="always_collapsed"
|
||||
codeDiffDisplayMode="always_collapsed"
|
||||
showDesktopPreviews={false}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</DesktopPanelContext.Provider>
|
||||
</ChatWorkspaceContext>
|
||||
),
|
||||
parameters: {
|
||||
queries: [
|
||||
{
|
||||
key: ["workspace", "test-workspace-id"],
|
||||
data: {
|
||||
id: "test-workspace-id",
|
||||
latest_build: {
|
||||
id: "test-build-id",
|
||||
status: "running",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
key: [
|
||||
"workspaceBuilds",
|
||||
"a1b2c3d4-e5f6-7890-abcd-ef1234567890",
|
||||
"logs",
|
||||
],
|
||||
data: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
@@ -938,6 +938,10 @@ const GenericToolRenderer: FC<ToolRendererProps> = ({
|
||||
mcpSlug={mcpServer?.slug}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
const toolHeaderStatus = (
|
||||
<>
|
||||
{isError && (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
@@ -963,7 +967,11 @@ const GenericToolRenderer: FC<ToolRendererProps> = ({
|
||||
);
|
||||
|
||||
return (
|
||||
<ToolCollapsible hasContent={hasContent} header={toolHeader}>
|
||||
<ToolCollapsible
|
||||
hasContent={hasContent}
|
||||
header={toolHeader}
|
||||
headerStatus={toolHeaderStatus}
|
||||
>
|
||||
{toolContent}
|
||||
</ToolCollapsible>
|
||||
);
|
||||
|
||||
@@ -17,6 +17,7 @@ interface ToolCollapsibleProps {
|
||||
children: ReactNode;
|
||||
header: ToolCollapsibleHeader;
|
||||
headerActions?: ReactNode;
|
||||
headerStatus?: ReactNode;
|
||||
hasContent?: boolean;
|
||||
defaultExpanded?: boolean;
|
||||
expanded?: boolean;
|
||||
@@ -50,6 +51,7 @@ export const ToolCollapsible: FC<ToolCollapsibleProps> = ({
|
||||
children,
|
||||
header,
|
||||
headerActions,
|
||||
headerStatus,
|
||||
hasContent = true,
|
||||
defaultExpanded = false,
|
||||
expanded: expandedProp,
|
||||
@@ -88,6 +90,7 @@ export const ToolCollapsible: FC<ToolCollapsibleProps> = ({
|
||||
onClick={toggleExpanded}
|
||||
>
|
||||
{renderedHeader}
|
||||
{headerStatus}
|
||||
<ChevronDownIcon
|
||||
className={cn(
|
||||
"size-3 shrink-0 text-current transition-transform",
|
||||
@@ -105,6 +108,7 @@ export const ToolCollapsible: FC<ToolCollapsibleProps> = ({
|
||||
)}
|
||||
>
|
||||
{renderedHeader}
|
||||
{headerStatus}
|
||||
</TranscriptRow>
|
||||
);
|
||||
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import {
|
||||
BookOpenIcon,
|
||||
BadgeQuestionMarkIcon,
|
||||
BotIcon,
|
||||
ClipboardListIcon,
|
||||
FileIcon,
|
||||
FilePenIcon,
|
||||
LightbulbIcon,
|
||||
CompassIcon,
|
||||
FilePenLineIcon,
|
||||
FileTextIcon,
|
||||
LogInIcon,
|
||||
MonitorIcon,
|
||||
PlayIcon,
|
||||
PlusCircleIcon,
|
||||
PowerIcon,
|
||||
RouteIcon,
|
||||
ServerIcon,
|
||||
TerminalIcon,
|
||||
WrenchIcon,
|
||||
} from "lucide-react";
|
||||
@@ -35,7 +36,12 @@ export const ToolIcon: React.FC<{
|
||||
}> = ({ name, iconUrl, isRunning, serverName, subagentIconKind }) => {
|
||||
const [imgError, setImgError] = useState(false);
|
||||
const color = "text-current";
|
||||
const base = cn("size-4 shrink-0", color, isRunning && "grayscale");
|
||||
const base = cn(
|
||||
"size-4 shrink-0",
|
||||
color,
|
||||
"stroke-[1.5]",
|
||||
isRunning && "grayscale",
|
||||
);
|
||||
|
||||
// If an MCP icon URL is provided and hasn't failed, render it.
|
||||
// Strip colour so external icons match the monochrome lucide
|
||||
@@ -93,28 +99,31 @@ export const ToolIcon: React.FC<{
|
||||
case "process_list":
|
||||
case "process_signal":
|
||||
return <TerminalIcon className={base} />;
|
||||
case "wait_for_external_auth":
|
||||
return <LogInIcon className={base} />;
|
||||
case "read_file":
|
||||
case "list_templates":
|
||||
case "read_template":
|
||||
return <FileIcon className={base} />;
|
||||
case "read_skill":
|
||||
case "read_skill_file":
|
||||
return <FileTextIcon className={base} />;
|
||||
case "write_file":
|
||||
case "edit_files":
|
||||
return <FilePenIcon className={base} />;
|
||||
return <FilePenLineIcon className={base} />;
|
||||
case "list_templates":
|
||||
case "read_template":
|
||||
case "create_workspace":
|
||||
return <PlusCircleIcon className={base} />;
|
||||
return <ServerIcon className={base} />;
|
||||
case "start_workspace":
|
||||
return <PowerIcon className={base} />;
|
||||
case "chat_summarized":
|
||||
return <BotIcon className={base} />;
|
||||
case "propose_plan":
|
||||
return <ClipboardListIcon className={base} />;
|
||||
return <RouteIcon className={base} />;
|
||||
case "ask_user_question":
|
||||
return <BadgeQuestionMarkIcon className={base} />;
|
||||
case "advisor":
|
||||
return <LightbulbIcon className={base} />;
|
||||
return <CompassIcon className={base} />;
|
||||
case "computer":
|
||||
return <MonitorIcon className={base} />;
|
||||
case "read_skill":
|
||||
case "read_skill_file":
|
||||
return <BookOpenIcon className={base} />;
|
||||
case "start_workspace":
|
||||
return <PlayIcon className={base} />;
|
||||
|
||||
default:
|
||||
return <WrenchIcon className={base} />;
|
||||
|
||||
@@ -36,8 +36,8 @@ const WebSearchSources: FC<WebSearchSourcesProps> = ({ sources }) => {
|
||||
hasContent={unique.length > 0}
|
||||
header={
|
||||
<>
|
||||
<GlobeIcon className="size-4 shrink-0 text-current" />
|
||||
<span className="text-[13px]">
|
||||
<GlobeIcon className="size-4 shrink-0 stroke-[1.5] text-current" />
|
||||
<span className="text-[13px] leading-6">
|
||||
Searched <span className="text-content-secondary/60">{detail}</span>
|
||||
</span>
|
||||
</>
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
resolveAgentDisplayState,
|
||||
} from "./displayMode";
|
||||
import { AgentDisplayModeToolCollapsible } from "./ToolCollapsible";
|
||||
import { ToolIcon } from "./ToolIcon";
|
||||
import {
|
||||
DIFFS_FONT_STYLE,
|
||||
getDiffViewerOptions,
|
||||
@@ -53,7 +54,12 @@ export const WriteFileTool: React.FC<{
|
||||
autoDisplayState={WRITE_FILE_AUTO_DISPLAY_STATE}
|
||||
header={
|
||||
<>
|
||||
<span className="text-[13px]">{label}</span>
|
||||
<ToolIcon name="write_file" isError={isError} isRunning={isRunning} />
|
||||
<span className="text-[13px] leading-6">{label}</span>
|
||||
</>
|
||||
}
|
||||
headerStatus={
|
||||
<>
|
||||
{isError && (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
|
||||
Reference in New Issue
Block a user