diff options
| author | Jay V <[email protected]> | 2025-06-05 13:52:39 -0400 |
|---|---|---|
| committer | Jay V <[email protected]> | 2025-06-05 13:52:44 -0400 |
| commit | 142056e9afa6913a44e65bf109eac0c857b49b02 (patch) | |
| tree | f893d98b071fa6650773f6fc59ee62bca52256d0 /packages/web/src | |
| parent | 241c366164496f1961a86ae02d2b51e9a9453b12 (diff) | |
| download | opencode-142056e9afa6913a44e65bf109eac0c857b49b02.tar.gz opencode-142056e9afa6913a44e65bf109eac0c857b49b02.zip | |
share page todos
Diffstat (limited to 'packages/web/src')
| -rw-r--r-- | packages/web/src/components/Share.tsx | 120 | ||||
| -rw-r--r-- | packages/web/src/components/share.module.css | 71 |
2 files changed, 191 insertions, 0 deletions
diff --git a/packages/web/src/components/Share.tsx b/packages/web/src/components/Share.tsx index b6bd7fd0d..9831504c8 100644 --- a/packages/web/src/components/Share.tsx +++ b/packages/web/src/components/Share.tsx @@ -16,6 +16,7 @@ import { IconOpenAI, IconGemini, IconAnthropic } from "./icons/custom" import { IconCpuChip, IconSparkles, + IconQueueList, IconUserCircle, IconChevronDown, IconCommandLine, @@ -75,6 +76,27 @@ type SessionInfo = { cost?: number } +type TodoStatus = "pending" | "in_progress" | "completed" + +interface Todo { + id: string + content: string + status: TodoStatus + priority: "low" | "medium" | "high" +} + +function sortTodosByStatus(todos: Todo[]) { + const statusPriority: Record<TodoStatus, number> = { + in_progress: 0, + pending: 1, + completed: 2, + } + + return todos + .slice() + .sort((a, b) => statusPriority[a.status] - statusPriority[b.status]) +} + function getFileType(path: string) { return path.split(".").pop() } @@ -1163,6 +1185,104 @@ export default function Share(props: { api: string }) { ) }} </Match> + {/* Todo read */} + <Match + when={ + msg.role === "assistant" && + part.type === "tool-invocation" && + part.toolInvocation.toolName === "opencode_todoread" && + part + } + > + {(part) => { + const metadata = createMemo(() => msg.metadata?.tool[part().toolInvocation.toolCallId]) + + const duration = createMemo(() => + DateTime.fromMillis(metadata()?.time.end || 0).diff( + DateTime.fromMillis(metadata()?.time.start || 0), + ).toMillis(), + ) + + return ( + <div + data-section="part" + data-part-type="tool-fallback" + > + <div data-section="decoration"> + <div title="Plan"> + <IconQueueList width={18} height={18} /> + </div> + <div></div> + </div> + <div data-section="content"> + <div data-part-tool-body> + <span data-part-title data-size="sm"> + Checking plan… + </span> + </div> + <ToolFooter time={duration()} /> + </div> + </div> + ) + }} + </Match> + {/* Todo write */} + <Match + when={ + msg.role === "assistant" && + part.type === "tool-invocation" && + part.toolInvocation.toolName === "opencode_todowrite" && + part + } + > + {(part) => { + const metadata = createMemo(() => msg.metadata?.tool[part().toolInvocation.toolCallId]) + + const todos = createMemo(() => sortTodosByStatus( + part().toolInvocation.args.todos + )) + + const duration = createMemo(() => + DateTime.fromMillis(metadata()?.time.end || 0).diff( + DateTime.fromMillis(metadata()?.time.start || 0), + ).toMillis(), + ) + + return ( + <div + data-section="part" + data-part-type="tool-fallback" + > + <div data-section="decoration"> + <div title="Plan"> + <IconQueueList width={18} height={18} /> + </div> + <div></div> + </div> + <div data-section="content"> + <div data-part-tool-body> + <span data-part-title data-size="sm"> + Planning… + </span> + <Show when={todos().length > 0}> + <ul class={styles.todos}> + <For each={todos()}> + {({ status, content }) => + <li data-status={status}> + <span></span> + {content} + </li> + } + </For> + </ul> + </Show> + </div> + <ToolFooter time={duration()} /> + </div> + </div> + ) + }} + </Match> {/* Tool call */} <Match when={ diff --git a/packages/web/src/components/share.module.css b/packages/web/src/components/share.module.css index ef4507340..895a38a95 100644 --- a/packages/web/src/components/share.module.css +++ b/packages/web/src/components/share.module.css @@ -535,3 +535,74 @@ font-size: 0.75rem; } } + +.todos { + list-style-type: none; + padding: 0; + margin: 0; + border: 1px solid var(--sl-color-divider); + border-radius: 0.25rem; + + li { + margin: 0; + position: relative; + padding-left: 1.5rem; + font-size: 0.75rem; + padding: 0.375rem 0.625rem 0.375rem 1.75rem; + border-bottom: 1px solid var(--sl-color-divider); + line-height: 1.5; + + &:last-child { + border-bottom: none; + } + + & > span { + position: absolute; + display: inline-block; + left: 0.5rem; + top: calc(0.5rem + 1px); + width: 0.75rem; + height: 0.75rem; + border: 1px solid var(--sl-color-divider); + border-radius: 0.15rem; + + &::before { + } + } + + &[data-status="pending"] { + color: var(--sl-color-text); + } + &[data-status="in_progress"] { + color: var(--sl-color-text); + + & > span { border-color: var(--sl-color-orange); } + & > span::before { + content: ""; + position: absolute; + top: 2px; + left: 2px; + width: calc(0.75rem - 2px - 4px); + height: calc(0.75rem - 2px - 4px); + box-shadow: inset 1rem 1rem var(--sl-color-orange-low); + } + } + &[data-status="completed"] { + color: var(--sl-color-text-dimmed); + + & > span { border-color: var(--sl-color-hairline); } + & > span::before { + content: ""; + position: absolute; + top: 2px; + left: 2px; + width: calc(0.75rem - 2px - 4px); + height: calc(0.75rem - 2px - 4px); + box-shadow: inset 1rem 1rem var(--sl-color-divider); + + transform-origin: bottom left; + clip-path: polygon(14% 44%, 0 65%, 50% 100%, 100% 16%, 80% 0%, 43% 62%); + } + } + } +} |
