docs: share page task tool

This commit is contained in:
Jay V 2025-07-15 17:12:09 -04:00
parent 73b46c2bf9
commit 749e7838a4
6 changed files with 86 additions and 39 deletions

View file

@ -340,6 +340,7 @@ export default function Share(props: {
const filteredParts = createMemo(() => const filteredParts = createMemo(() =>
msg.parts.filter((x, index) => { msg.parts.filter((x, index) => {
if (x.type === "step-start" && index > 0) return false if (x.type === "step-start" && index > 0) return false
if (x.type === "snapshot") return false
if (x.type === "step-finish") return false if (x.type === "step-finish") return false
if (x.type === "text" && x.synthetic === true) return false if (x.type === "text" && x.synthetic === true) return false
if (x.type === "tool" && x.tool === "todoread") return false if (x.type === "tool" && x.tool === "todoread") return false

View file

@ -58,3 +58,11 @@ export function IconMeta(props: JSX.SvgSVGAttributes<SVGSVGElement>) {
</svg> </svg>
) )
} }
// https://icones.js.org/collection/ri?s=robot&icon=ri:robot-2-line
export function IconRobot(props: JSX.SvgSVGAttributes<SVGSVGElement>) {
return (
<svg {...props} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path fill="currentColor" d="M13.5 2c0 .444-.193.843-.5 1.118V5h5a3 3 0 0 1 3 3v10a3 3 0 0 1-3 3H6a3 3 0 0 1-3-3V8a3 3 0 0 1 3-3h5V3.118A1.5 1.5 0 1 1 13.5 2M6 7a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V8a1 1 0 0 0-1-1zm-4 3H0v6h2zm20 0h2v6h-2zM9 14.5a1.5 1.5 0 1 0 0-3a1.5 1.5 0 0 0 0 3m6 0a1.5 1.5 0 1 0 0-3a1.5 1.5 0 0 0 0 3" /></svg>
)
}

View file

@ -1,21 +1,13 @@
.root { .root {
border: 1px solid var(--sl-color-blue-high);
padding: 0.5rem calc(0.5rem + 3px);
border-radius: 0.25rem;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: flex-start; align-items: flex-start;
gap: 1rem; gap: 1rem;
align-self: flex-start;
&[data-highlight="true"] {
background-color: var(--sl-color-blue-low);
}
[data-slot="expand-button"] { [data-slot="expand-button"] {
flex: 0 0 auto; flex: 0 0 auto;
padding: 2px 0; padding: 2px 0;
font-size: 0.75rem; font-size: 0.857em;
} }
[data-slot="markdown"] { [data-slot="markdown"] {
@ -29,7 +21,7 @@
display: block; display: block;
} }
font-size: 0.875rem; font-size: 1em;
line-height: 1.5; line-height: 1.5;
p, p,
@ -61,7 +53,7 @@
h4, h4,
h5, h5,
h6 { h6 {
font-size: 0.875rem; font-size: 1em;
font-weight: 600; font-weight: 600;
margin-bottom: 0.5rem; margin-bottom: 0.5rem;
} }
@ -75,7 +67,7 @@
background-color: var(--sl-color-bg-surface) !important; background-color: var(--sl-color-bg-surface) !important;
padding: 0.5rem 0.75rem; padding: 0.5rem 0.75rem;
line-height: 1.6; line-height: 1.6;
font-size: 0.75rem; font-size: 0.857em;
white-space: pre-wrap; white-space: pre-wrap;
word-break: break-word; word-break: break-word;

View file

@ -1,10 +1,10 @@
import style from "./content-markdown.module.css"
import { createResource, createSignal } from "solid-js"
import { createOverflow } from "./common"
import { transformerNotationDiff } from "@shikijs/transformers"
import { marked } from "marked" import { marked } from "marked"
import markedShiki from "marked-shiki"
import { codeToHtml } from "shiki" import { codeToHtml } from "shiki"
import markedShiki from "marked-shiki"
import { createOverflow } from "./common"
import { createResource, createSignal } from "solid-js"
import { transformerNotationDiff } from "@shikijs/transformers"
import style from "./content-markdown.module.css"
const markedWithShiki = marked.use( const markedWithShiki = marked.use(
markedShiki({ markedShiki({

View file

@ -103,7 +103,7 @@
[data-component="content"] { [data-component="content"] {
flex: 1 1 auto; flex: 1 1 auto;
min-width: 0; min-width: 0;
padding: 0 0 0.375rem; padding: 0 0 1rem;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 1rem; gap: 1rem;
@ -135,6 +135,14 @@
gap: 1rem; gap: 1rem;
flex-grow: 1; flex-grow: 1;
max-width: var(--md-tool-width); max-width: var(--md-tool-width);
& > [data-component="assistant-text-markdown"] {
align-self: flex-start;
font-size: 0.875rem;
border: 1px solid var(--sl-color-blue-high);
padding: 0.5rem calc(0.5rem + 3px);
border-radius: 0.25rem;
}
} }
[data-component="step-start"] { [data-component="step-start"] {
@ -239,6 +247,24 @@
max-width: var(--lg-tool-width); max-width: var(--lg-tool-width);
} }
} }
&[data-tool="task"] {
[data-component="tool-input"] {
font-size: 0.75rem;
line-height: 1.5;
max-width: var(--sm-tool-width);
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
}
[data-component="tool-output"] {
max-width: var(--sm-tool-width);
font-size: 0.75rem;
border: 1px solid var(--sl-color-divider);
padding: 0.5rem calc(0.5rem + 3px);
border-radius: 0.25rem;
}
}
} }
[data-component="tool-title"] { [data-component="tool-title"] {

View file

@ -19,25 +19,25 @@ import {
IconMagnifyingGlass, IconMagnifyingGlass,
IconDocumentMagnifyingGlass, IconDocumentMagnifyingGlass,
} from "../icons" } from "../icons"
import { IconMeta, IconOpenAI, IconGemini, IconAnthropic } from "../icons/custom" import { IconMeta, IconRobot, IconOpenAI, IconGemini, IconAnthropic } from "../icons/custom"
import { formatDuration } from "../share/common"
import { ContentCode } from "./content-code" import { ContentCode } from "./content-code"
import { ContentDiff } from "./content-diff" import { ContentDiff } from "./content-diff"
import { ContentText } from "./content-text" import { ContentText } from "./content-text"
import { ContentError } from "./content-error"
import { ContentMarkdown } from "./content-markdown"
import { ContentBash } from "./content-bash" import { ContentBash } from "./content-bash"
import { ContentError } from "./content-error"
import { formatDuration } from "../share/common"
import { ContentMarkdown } from "./content-markdown"
import type { MessageV2 } from "opencode/session/message-v2" import type { MessageV2 } from "opencode/session/message-v2"
import type { Diagnostic } from "vscode-languageserver-types" import type { Diagnostic } from "vscode-languageserver-types"
import styles from "./part.module.css" import styles from "./part.module.css"
const MIN_DURATION = 2 const MIN_DURATION = 2000
export interface PartProps { export interface PartProps {
index: number index: number
message: MessageV2.Info message: MessageV2.Info
part: MessageV2.AssistantPart | MessageV2.UserPart part: MessageV2.Part
last: boolean last: boolean
} }
@ -114,7 +114,7 @@ export function Part(props: PartProps) {
<IconGlobeAlt width={18} height={18} /> <IconGlobeAlt width={18} height={18} />
</Match> </Match>
<Match when={props.part.type === "tool" && props.part.tool === "task"}> <Match when={props.part.type === "tool" && props.part.tool === "task"}>
<IconRectangleStack width={18} height={18} /> <IconRobot width={18} height={18} />
</Match> </Match>
<Match when={true}> <Match when={true}>
<IconSparkles width={18} height={18} /> <IconSparkles width={18} height={18} />
@ -131,12 +131,13 @@ export function Part(props: PartProps) {
{props.message.role === "user" && props.part.type === "text" && ( {props.message.role === "user" && props.part.type === "text" && (
<div data-component="user-text"> <div data-component="user-text">
<ContentText text={props.part.text} expand={props.last} /> <ContentText text={props.part.text} expand={props.last} />
<Spacer />
</div> </div>
)} )}
{props.message.role === "assistant" && props.part.type === "text" && ( {props.message.role === "assistant" && props.part.type === "text" && (
<div data-component="assistant-text"> <div data-component="assistant-text">
<ContentMarkdown expand={props.last} text={props.part.text} /> <div data-component="assistant-text-markdown">
<ContentMarkdown expand={props.last} text={props.part.text} />
</div>
{props.last && props.message.role === "assistant" && props.message.time.completed && ( {props.last && props.message.role === "assistant" && props.message.time.completed && (
<Footer <Footer
title={DateTime.fromMillis(props.message.time.completed).toLocaleString( title={DateTime.fromMillis(props.message.time.completed).toLocaleString(
@ -146,7 +147,6 @@ export function Part(props: PartProps) {
{DateTime.fromMillis(props.message.time.completed).toLocaleString(DateTime.DATETIME_MED)} {DateTime.fromMillis(props.message.time.completed).toLocaleString(DateTime.DATETIME_MED)}
</Footer> </Footer>
)} )}
<Spacer />
</div> </div>
)} )}
{props.message.role === "user" && props.part.type === "file" && ( {props.message.role === "user" && props.part.type === "file" && (
@ -245,6 +245,14 @@ export function Part(props: PartProps) {
state={props.part.state} state={props.part.state}
/> />
</Match> </Match>
<Match when={props.part.tool === "task"}>
<TaskTool
id={props.part.id}
tool={props.part.tool}
message={props.message}
state={props.part.state}
/>
</Match>
<Match when={true}> <Match when={true}>
<FallbackTool <FallbackTool
message={props.message} message={props.message}
@ -256,11 +264,10 @@ export function Part(props: PartProps) {
</Switch> </Switch>
</div> </div>
<ToolFooter <ToolFooter
time={ time={DateTime.fromMillis(props.part.state.time.end)
DateTime.fromMillis(props.part.state.time.start) .diff(DateTime.fromMillis(props.part.state.time.start))
.diff(DateTime.fromMillis(props.part.state.time.end)) .toMillis()}
.toMillis() />
} />
</> </>
)} )}
</div> </div>
@ -636,12 +643,25 @@ function Footer(props: ParentProps<{ title: string }>) {
} }
function ToolFooter(props: { time: number }) { function ToolFooter(props: { time: number }) {
return props.time > MIN_DURATION ? ( return props.time > MIN_DURATION && <Footer title={`${props.time}ms`}>{formatDuration(props.time)}</Footer>
<Footer title={`${props.time}ms`}> }
{formatDuration(props.time)}
</Footer> function TaskTool(props: ToolProps) {
) : ( return (
<Spacer /> <>
<div data-component="tool-title">
<span data-slot="name">Task</span>
<span data-slot="target">{props.state.input.description}</span>
</div>
<div data-component="tool-input">
&ldquo;{props.state.input.prompt}&rdquo;
</div>
<ResultsButton showCopy="Show output" hideCopy="Hide output">
<div data-component="tool-output">
<ContentMarkdown expand text={props.state.output} />
</div>
</ResultsButton>
</>
) )
} }