From fc115ea367dd034c7b989819d4f547c5d7519253 Mon Sep 17 00:00:00 2001 From: Adam <2363879+adamdotdevin@users.noreply.github.com> Date: Mon, 27 Oct 2025 15:35:47 -0500 Subject: wip: desktop work --- .../desktop/src/components/assistant-message.tsx | 362 +++++++++++++++++++++ packages/desktop/src/components/diff-changes.tsx | 20 ++ 2 files changed, 382 insertions(+) create mode 100644 packages/desktop/src/components/assistant-message.tsx create mode 100644 packages/desktop/src/components/diff-changes.tsx (limited to 'packages/desktop/src/components') diff --git a/packages/desktop/src/components/assistant-message.tsx b/packages/desktop/src/components/assistant-message.tsx new file mode 100644 index 000000000..2e3d659aa --- /dev/null +++ b/packages/desktop/src/components/assistant-message.tsx @@ -0,0 +1,362 @@ +import type { Part, AssistantMessage, ReasoningPart, TextPart, ToolPart } from "@opencode-ai/sdk" +import type { Tool } from "opencode/tool/tool" +import type { ReadTool } from "opencode/tool/read" +import { children, Component, createMemo, For, Match, Show, Switch, type JSX } from "solid-js" +import { Dynamic } from "solid-js/web" +import { Markdown } from "./markdown" +import { Collapsible, Icon, IconProps } from "@opencode-ai/ui" +import { getDirectory, getFilename } from "@/utils" +import { ListTool } from "opencode/tool/ls" +import { GlobTool } from "opencode/tool/glob" +import { GrepTool } from "opencode/tool/grep" +import { WebFetchTool } from "opencode/tool/webfetch" +import { TaskTool } from "opencode/tool/task" +import { BashTool } from "opencode/tool/bash" +import { EditTool } from "opencode/tool/edit" +import { DiffChanges } from "./diff-changes" +import { WriteTool } from "opencode/tool/write" + +export function AssistantMessage(props: { message: AssistantMessage; parts: Part[] }) { + return ( +
+ + {(part) => { + const component = createMemo(() => PART_MAPPING[part.type as keyof typeof PART_MAPPING]) + return ( + + + + ) + }} + +
+ ) +} + +const PART_MAPPING = { + text: TextPart, + tool: ToolPart, + reasoning: ReasoningPart, +} + +function ReasoningPart(props: { part: ReasoningPart; message: AssistantMessage }) { + return null + // return ( + // + //
{props.part.text}
+ //
+ // ) +} + +function TextPart(props: { part: TextPart; message: AssistantMessage }) { + return ( + + + + ) +} + +function ToolPart(props: { part: ToolPart; message: AssistantMessage }) { + // const sync = useSync() + + const component = createMemo(() => { + const render = ToolRegistry.render(props.part.tool) ?? GenericTool + + const metadata = props.part.state.status === "pending" ? {} : (props.part.state.metadata ?? {}) + const input = props.part.state.status === "completed" ? props.part.state.input : {} + // const permissions = sync.data.permission[props.message.sessionID] ?? [] + // const permissionIndex = permissions.findIndex((x) => x.callID === props.part.callID) + // const permission = permissions[permissionIndex] + + return ( + <> + + {/* {props.part.state.error.replace("Error: ", "")} */} + + ) + }) + + return {component()} +} + +type TriggerTitle = { + title: string + subtitle?: string + args?: string[] + action?: JSX.Element +} + +const isTriggerTitle = (val: any): val is TriggerTitle => { + return typeof val === "object" && val !== null && "title" in val && !(val instanceof Node) +} + +function BasicTool(props: { icon: IconProps["name"]; trigger: TriggerTitle | JSX.Element; children?: JSX.Element }) { + const resolved = children(() => props.children) + + return ( + + +
+
+ + + +
+
+ + {(props.trigger as TriggerTitle).title} + + + {(props.trigger as TriggerTitle).subtitle} + + + + {(arg) => {arg}} + + +
+ {(props.trigger as TriggerTitle).action} +
+
+ {props.trigger as JSX.Element} +
+
+ + + +
+
+ + {props.children} + +
+ ) +} + +function GenericTool(props: ToolProps) { + return +} + +type ToolProps = { + input: Partial> + metadata: Partial> + // permission: Record + tool: string + output?: string +} + +const ToolRegistry = (() => { + const state: Record< + string, + { + name: string + render?: Component> + } + > = {} + function register(input: { name: string; render?: Component> }) { + state[input.name] = input + return input + } + return { + register, + render(name: string) { + return state[name]?.render + }, + } +})() + +ToolRegistry.register({ + name: "read", + render(props) { + return ( + + ) + }, +}) + +ToolRegistry.register({ + name: "list", + render(props) { + return ( + + +
{props.output}
+
+
+ ) + }, +}) + +ToolRegistry.register({ + name: "glob", + render(props) { + return ( + {props.output} + + + ) + }, +}) + +ToolRegistry.register({ + name: "grep", + render(props) { + const args = [] + if (props.input.pattern) args.push("pattern=" + props.input.pattern) + if (props.input.include) args.push("include=" + props.input.include) + return ( + + +
{props.output}
+
+
+ ) + }, +}) + +ToolRegistry.register({ + name: "webfetch", + render(props) { + return ( + + + + ), + }} + > + +
{props.output}
+
+
+ ) + }, +}) + +ToolRegistry.register({ + name: "task", + render(props) { + return ( + + +
{props.output}
+
+
+ ) + }, +}) + +ToolRegistry.register({ + name: "bash", + render(props) { + return ( + + +
{props.output}
+
+
+ ) + }, +}) + +ToolRegistry.register({ + name: "edit", + render(props) { + return ( + +
+
Edit
+
+ + {getDirectory(props.input.filePath!)}/ + + {getFilename(props.input.filePath ?? "")} +
+
+
{/* */}
+ + } + > + +
{props.output}
+
+
+ ) + }, +}) + +ToolRegistry.register({ + name: "write", + render(props) { + return ( + +
+
Write
+
+ + {getDirectory(props.input.filePath!)}/ + + {getFilename(props.input.filePath ?? "")} +
+
+
{/* */}
+ + } + > + +
{props.output}
+
+
+ ) + }, +}) diff --git a/packages/desktop/src/components/diff-changes.tsx b/packages/desktop/src/components/diff-changes.tsx new file mode 100644 index 000000000..3b633f70f --- /dev/null +++ b/packages/desktop/src/components/diff-changes.tsx @@ -0,0 +1,20 @@ +import { FileDiff } from "@opencode-ai/sdk" +import { createMemo, Show } from "solid-js" + +export function DiffChanges(props: { diff: FileDiff | FileDiff[] }) { + const additions = createMemo(() => + Array.isArray(props.diff) ? props.diff.reduce((acc, diff) => acc + (diff.additions ?? 0), 0) : props.diff.additions, + ) + const deletions = createMemo(() => + Array.isArray(props.diff) ? props.diff.reduce((acc, diff) => acc + (diff.deletions ?? 0), 0) : props.diff.deletions, + ) + const total = createMemo(() => additions() + deletions()) + return ( + 0}> +
+ {`+${additions()}`} + {`-${deletions()}`} +
+
+ ) +} -- cgit v1.2.3