import { Component, createMemo, For, Match, Show, Switch } from "solid-js" import { Dynamic } from "solid-js/web" import { AssistantMessage, Message as MessageType, Part as PartType, TextPart, ToolPart, UserMessage, } from "@opencode-ai/sdk" import { BasicTool } from "./basic-tool" import { GenericTool } from "./basic-tool" import { Card } from "./card" import { Icon } from "./icon" import { Checkbox } from "./checkbox" import { Diff } from "./diff" import { DiffChanges } from "./diff-changes" import { Markdown } from "./markdown" export interface MessageProps { message: MessageType parts: PartType[] } export interface MessagePartProps { part: PartType message: MessageType hideDetails?: boolean } export type PartComponent = Component export const PART_MAPPING: Record = {} function getFilename(path: string) { if (!path) return "" const trimmed = path.replace(/[\/]+$/, "") const parts = trimmed.split("/") return parts[parts.length - 1] ?? "" } function getDirectory(path: string) { const parts = path.split("/") const dir = parts.slice(0, parts.length - 1).join("/") return dir ? dir + "/" : "" } export function registerPartComponent(type: string, component: PartComponent) { PART_MAPPING[type] = component } export function Message(props: MessageProps) { return ( {(userMessage) => ( )} {(assistantMessage) => ( )} ) } export function AssistantMessageDisplay(props: { message: AssistantMessage; parts: PartType[] }) { const filteredParts = createMemo(() => { return props.parts?.filter((x) => { if (x.type === "reasoning") return false return x.type !== "tool" || (x as ToolPart).tool !== "todoread" }) }) return {(part) => } } export function UserMessageDisplay(props: { message: UserMessage; parts: PartType[] }) { const text = createMemo(() => props.parts ?.filter((p) => p.type === "text" && !(p as TextPart).synthetic) ?.map((p) => (p as TextPart).text) ?.join(""), ) return
{text()}
} export function Part(props: MessagePartProps) { const component = createMemo(() => PART_MAPPING[props.part.type]) return ( ) } export interface ToolProps { input: Record metadata: Record tool: string output?: string hideDetails?: boolean } export type ToolComponent = Component const state: Record< string, { name: string render?: ToolComponent } > = {} export function registerTool(input: { name: string; render?: ToolComponent }) { state[input.name] = input return input } export function getTool(name: string) { return state[name]?.render } export const ToolRegistry = { register: registerTool, render: getTool, } PART_MAPPING["tool"] = function ToolPartDisplay(props) { const part = props.part as ToolPart const component = createMemo(() => { const render = ToolRegistry.render(part.tool) ?? GenericTool const metadata = part.state.status === "pending" ? {} : (part.state.metadata ?? {}) const input = part.state.status === "completed" ? part.state.input : {} return ( {(error) => { const cleaned = error().replace("Error: ", "") const [title, ...rest] = cleaned.split(": ") return (
{title}
{rest.join(": ")}
{cleaned}
) }}
) }) return {component()} } PART_MAPPING["text"] = function TextPartDisplay(props) { const part = props.part as TextPart return (
) } PART_MAPPING["reasoning"] = function ReasoningPartDisplay(props) { const part = props.part as any return (
) } 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 ?? "")}
} >
) }, }) ToolRegistry.register({ name: "write", render(props) { return (
Write
{getDirectory(props.input.filePath!)} {getFilename(props.input.filePath ?? "")}
{/* */}
} >
{props.output}
) }, }) ToolRegistry.register({ name: "todowrite", render(props) { return ( t.status === "completed").length}/${props.input.todos?.length}`, }} >
{(todo: any) => (
{todo.content}
)}
) }, })