summaryrefslogtreecommitdiffhomepage
path: root/packages/ui/src/components/message-part.tsx
diff options
context:
space:
mode:
authorAdam <[email protected]>2025-12-15 09:34:00 -0600
committerAdam <[email protected]>2025-12-15 10:22:04 -0600
commit5cf6a1343c6ca088bd2b586197faf7fe58961290 (patch)
treed8001631005d2f4791bfe3a0dd3a0b21003a2516 /packages/ui/src/components/message-part.tsx
parent44d6c5780d41616bf29a749020c9d7f98895407f (diff)
downloadopencode-5cf6a1343c6ca088bd2b586197faf7fe58961290.tar.gz
opencode-5cf6a1343c6ca088bd2b586197faf7fe58961290.zip
wip(desktop): progress
Diffstat (limited to 'packages/ui/src/components/message-part.tsx')
-rw-r--r--packages/ui/src/components/message-part.tsx96
1 files changed, 88 insertions, 8 deletions
diff --git a/packages/ui/src/components/message-part.tsx b/packages/ui/src/components/message-part.tsx
index f00c43bd8..5ea3cd19c 100644
--- a/packages/ui/src/components/message-part.tsx
+++ b/packages/ui/src/components/message-part.tsx
@@ -2,6 +2,7 @@ import { Component, createMemo, For, Match, Show, Switch } from "solid-js"
import { Dynamic } from "solid-js/web"
import {
AssistantMessage,
+ FilePart,
Message as MessageType,
Part as PartType,
TextPart,
@@ -74,13 +75,93 @@ export function AssistantMessageDisplay(props: { message: AssistantMessage; 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(""),
+ const textPart = createMemo(
+ () => props.parts?.find((p) => p.type === "text" && !(p as TextPart).synthetic) as TextPart | undefined,
+ )
+
+ const text = createMemo(() => textPart()?.text || "")
+
+ const files = createMemo(() => (props.parts?.filter((p) => p.type === "file") as FilePart[]) ?? [])
+
+ const attachments = createMemo(() =>
+ files()?.filter((f) => {
+ const mime = f.mime
+ return mime.startsWith("image/") || mime === "application/pdf"
+ }),
+ )
+
+ const inlineFiles = createMemo(() =>
+ files().filter((f) => {
+ const mime = f.mime
+ return !mime.startsWith("image/") && mime !== "application/pdf" && f.source?.text?.start !== undefined
+ }),
+ )
+
+ return (
+ <div data-component="user-message">
+ <Show when={attachments().length > 0}>
+ <div data-slot="user-message-attachments">
+ <For each={attachments()}>
+ {(file) => (
+ <div data-slot="user-message-attachment" data-type={file.mime.startsWith("image/") ? "image" : "file"}>
+ <Show
+ when={file.mime.startsWith("image/") && file.url}
+ fallback={
+ <div data-slot="user-message-attachment-icon">
+ <Icon name="folder" />
+ </div>
+ }
+ >
+ <img data-slot="user-message-attachment-image" src={file.url} alt={file.filename ?? "attachment"} />
+ </Show>
+ </div>
+ )}
+ </For>
+ </div>
+ </Show>
+ <Show when={text()}>
+ <div data-slot="user-message-text">
+ <HighlightedText text={text()} references={inlineFiles()} />
+ </div>
+ </Show>
+ </div>
+ )
+}
+
+function HighlightedText(props: { text: string; references: FilePart[] }) {
+ const segments = createMemo(() => {
+ const text = props.text
+ const refs = [...props.references].sort((a, b) => (a.source?.text?.start ?? 0) - (b.source?.text?.start ?? 0))
+
+ const result: { text: string; highlight?: boolean }[] = []
+ let lastIndex = 0
+
+ for (const ref of refs) {
+ const start = ref.source?.text?.start
+ const end = ref.source?.text?.end
+
+ if (start === undefined || end === undefined || start < lastIndex) continue
+
+ if (start > lastIndex) {
+ result.push({ text: text.slice(lastIndex, start) })
+ }
+
+ result.push({ text: text.slice(start, end), highlight: true })
+ lastIndex = end
+ }
+
+ if (lastIndex < text.length) {
+ result.push({ text: text.slice(lastIndex) })
+ }
+
+ return result
+ })
+
+ return (
+ <For each={segments()}>
+ {(segment) => <span classList={{ "text-text-strong font-medium": segment.highlight }}>{segment.text}</span>}
+ </For>
)
- return <div data-component="user-message">{text()}</div>
}
export function Part(props: MessagePartProps) {
@@ -303,9 +384,8 @@ ToolRegistry.register({
<BasicTool
icon="task"
trigger={{
- title: `${props.input.subagent_type || props.tool} Agent`,
+ title: `${props.input.subagent_type || props.tool} Agent: ${props.input.description || ""}`,
titleClass: "capitalize",
- subtitle: props.input.description,
}}
>
<Show when={false && props.output}>