|
@@ -1,4 +1,4 @@
|
|
|
-import type { Part, AssistantMessage, ReasoningPart, TextPart, ToolPart } from "@opencode-ai/sdk"
|
|
|
|
|
|
|
+import type { Part, AssistantMessage, ReasoningPart, TextPart, ToolPart, Message } from "@opencode-ai/sdk"
|
|
|
import { children, Component, createMemo, For, Match, Show, Switch, type JSX } from "solid-js"
|
|
import { children, Component, createMemo, For, Match, Show, Switch, type JSX } from "solid-js"
|
|
|
import { Dynamic } from "solid-js/web"
|
|
import { Dynamic } from "solid-js/web"
|
|
|
import { Markdown } from "./markdown"
|
|
import { Markdown } from "./markdown"
|
|
@@ -17,47 +17,44 @@ import type { WriteTool } from "opencode/tool/write"
|
|
|
import type { TodoWriteTool } from "opencode/tool/todo"
|
|
import type { TodoWriteTool } from "opencode/tool/todo"
|
|
|
import { DiffChanges } from "./diff-changes"
|
|
import { DiffChanges } from "./diff-changes"
|
|
|
|
|
|
|
|
-export function AssistantMessage(props: { message: AssistantMessage; parts: Part[]; lastToolOnly?: boolean }) {
|
|
|
|
|
|
|
+export function AssistantMessage(props: { message: AssistantMessage; parts: Part[] }) {
|
|
|
const filteredParts = createMemo(() => {
|
|
const filteredParts = createMemo(() => {
|
|
|
- let tool = false
|
|
|
|
|
return props.parts?.filter((x) => {
|
|
return props.parts?.filter((x) => {
|
|
|
- if (x.type === "tool" && props.lastToolOnly && tool) return false
|
|
|
|
|
- if (x.type === "tool") tool = true
|
|
|
|
|
|
|
+ if (x.type === "reasoning") return false
|
|
|
return x.type !== "tool" || x.tool !== "todoread"
|
|
return x.type !== "tool" || x.tool !== "todoread"
|
|
|
})
|
|
})
|
|
|
})
|
|
})
|
|
|
return (
|
|
return (
|
|
|
<div class="w-full flex flex-col items-start gap-4">
|
|
<div class="w-full flex flex-col items-start gap-4">
|
|
|
- <For each={filteredParts()}>
|
|
|
|
|
- {(part) => {
|
|
|
|
|
- const component = createMemo(() => PART_MAPPING[part.type as keyof typeof PART_MAPPING])
|
|
|
|
|
- return (
|
|
|
|
|
- <Show when={component()}>
|
|
|
|
|
- <Dynamic component={component()} part={part as any} message={props.message} />
|
|
|
|
|
- </Show>
|
|
|
|
|
- )
|
|
|
|
|
- }}
|
|
|
|
|
- </For>
|
|
|
|
|
|
|
+ <For each={filteredParts()}>{(part) => <Part part={part} message={props.message} />}</For>
|
|
|
</div>
|
|
</div>
|
|
|
)
|
|
)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+export function Part(props: { part: Part; message: Message; readonly?: boolean }) {
|
|
|
|
|
+ const component = createMemo(() => PART_MAPPING[props.part.type as keyof typeof PART_MAPPING])
|
|
|
|
|
+ return (
|
|
|
|
|
+ <Show when={component()}>
|
|
|
|
|
+ <Dynamic component={component()} part={props.part as any} message={props.message} readonly={props.readonly} />
|
|
|
|
|
+ </Show>
|
|
|
|
|
+ )
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
const PART_MAPPING = {
|
|
const PART_MAPPING = {
|
|
|
text: TextPart,
|
|
text: TextPart,
|
|
|
tool: ToolPart,
|
|
tool: ToolPart,
|
|
|
reasoning: ReasoningPart,
|
|
reasoning: ReasoningPart,
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-function ReasoningPart(props: { part: ReasoningPart; message: AssistantMessage }) {
|
|
|
|
|
- return null
|
|
|
|
|
- // return (
|
|
|
|
|
- // <Show when={props.part.text.trim()}>
|
|
|
|
|
- // <div>{props.part.text}</div>
|
|
|
|
|
- // </Show>
|
|
|
|
|
- // )
|
|
|
|
|
|
|
+function ReasoningPart(props: { part: ReasoningPart; message: Message }) {
|
|
|
|
|
+ return (
|
|
|
|
|
+ <Show when={props.part.text.trim()}>
|
|
|
|
|
+ <Markdown text={props.part.text.trim()} />
|
|
|
|
|
+ </Show>
|
|
|
|
|
+ )
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-function TextPart(props: { part: TextPart; message: AssistantMessage }) {
|
|
|
|
|
|
|
+function TextPart(props: { part: TextPart; message: Message }) {
|
|
|
return (
|
|
return (
|
|
|
<Show when={props.part.text.trim()}>
|
|
<Show when={props.part.text.trim()}>
|
|
|
<Markdown text={props.part.text.trim()} />
|
|
<Markdown text={props.part.text.trim()} />
|
|
@@ -65,17 +62,11 @@ function TextPart(props: { part: TextPart; message: AssistantMessage }) {
|
|
|
)
|
|
)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-function ToolPart(props: { part: ToolPart; message: AssistantMessage }) {
|
|
|
|
|
- // const sync = useSync()
|
|
|
|
|
-
|
|
|
|
|
|
|
+function ToolPart(props: { part: ToolPart; message: Message; readonly?: boolean }) {
|
|
|
const component = createMemo(() => {
|
|
const component = createMemo(() => {
|
|
|
const render = ToolRegistry.render(props.part.tool) ?? GenericTool
|
|
const render = ToolRegistry.render(props.part.tool) ?? GenericTool
|
|
|
-
|
|
|
|
|
const metadata = props.part.state.status === "pending" ? {} : (props.part.state.metadata ?? {})
|
|
const metadata = props.part.state.status === "pending" ? {} : (props.part.state.metadata ?? {})
|
|
|
const input = props.part.state.status === "completed" ? props.part.state.input : {}
|
|
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 (
|
|
return (
|
|
|
<Dynamic
|
|
<Dynamic
|
|
@@ -83,8 +74,8 @@ function ToolPart(props: { part: ToolPart; message: AssistantMessage }) {
|
|
|
input={input}
|
|
input={input}
|
|
|
tool={props.part.tool}
|
|
tool={props.part.tool}
|
|
|
metadata={metadata}
|
|
metadata={metadata}
|
|
|
- // permission={permission?.metadata ?? {}}
|
|
|
|
|
output={props.part.state.status === "completed" ? props.part.state.output : undefined}
|
|
output={props.part.state.status === "completed" ? props.part.state.output : undefined}
|
|
|
|
|
+ readonly={props.readonly}
|
|
|
/>
|
|
/>
|
|
|
)
|
|
)
|
|
|
})
|
|
})
|
|
@@ -106,7 +97,12 @@ const isTriggerTitle = (val: any): val is TriggerTitle => {
|
|
|
return typeof val === "object" && val !== null && "title" in val && !(val instanceof Node)
|
|
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 }) {
|
|
|
|
|
|
|
+function BasicTool(props: {
|
|
|
|
|
+ icon: IconProps["name"]
|
|
|
|
|
+ trigger: TriggerTitle | JSX.Element
|
|
|
|
|
+ children?: JSX.Element
|
|
|
|
|
+ readonly?: boolean
|
|
|
|
|
+}) {
|
|
|
const resolved = children(() => props.children)
|
|
const resolved = children(() => props.children)
|
|
|
return (
|
|
return (
|
|
|
<Collapsible>
|
|
<Collapsible>
|
|
@@ -161,13 +157,13 @@ function BasicTool(props: { icon: IconProps["name"]; trigger: TriggerTitle | JSX
|
|
|
</Switch>
|
|
</Switch>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
- <Show when={resolved()}>
|
|
|
|
|
|
|
+ <Show when={resolved() && !props.readonly}>
|
|
|
<Collapsible.Arrow />
|
|
<Collapsible.Arrow />
|
|
|
</Show>
|
|
</Show>
|
|
|
</div>
|
|
</div>
|
|
|
</Collapsible.Trigger>
|
|
</Collapsible.Trigger>
|
|
|
- <Show when={props.children}>
|
|
|
|
|
- <Collapsible.Content>{props.children}</Collapsible.Content>
|
|
|
|
|
|
|
+ <Show when={resolved() && !props.readonly}>
|
|
|
|
|
+ <Collapsible.Content>{resolved()}</Collapsible.Content>
|
|
|
</Show>
|
|
</Show>
|
|
|
</Collapsible>
|
|
</Collapsible>
|
|
|
// <>
|
|
// <>
|
|
@@ -177,15 +173,15 @@ function BasicTool(props: { icon: IconProps["name"]; trigger: TriggerTitle | JSX
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
function GenericTool(props: ToolProps<any>) {
|
|
function GenericTool(props: ToolProps<any>) {
|
|
|
- return <BasicTool icon="mcp" trigger={{ title: props.tool }} />
|
|
|
|
|
|
|
+ return <BasicTool icon="mcp" trigger={{ title: props.tool }} readonly={props.readonly} />
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
type ToolProps<T extends Tool.Info> = {
|
|
type ToolProps<T extends Tool.Info> = {
|
|
|
input: Partial<Tool.InferParameters<T>>
|
|
input: Partial<Tool.InferParameters<T>>
|
|
|
metadata: Partial<Tool.InferMetadata<T>>
|
|
metadata: Partial<Tool.InferMetadata<T>>
|
|
|
- // permission: Record<string, any>
|
|
|
|
|
tool: string
|
|
tool: string
|
|
|
output?: string
|
|
output?: string
|
|
|
|
|
+ readonly?: boolean
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
const ToolRegistry = (() => {
|
|
const ToolRegistry = (() => {
|