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