diff options
| author | Adam <[email protected]> | 2025-12-15 09:34:00 -0600 |
|---|---|---|
| committer | Adam <[email protected]> | 2025-12-15 10:22:04 -0600 |
| commit | 5cf6a1343c6ca088bd2b586197faf7fe58961290 (patch) | |
| tree | d8001631005d2f4791bfe3a0dd3a0b21003a2516 /packages/ui/src/components/message-part.tsx | |
| parent | 44d6c5780d41616bf29a749020c9d7f98895407f (diff) | |
| download | opencode-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.tsx | 96 |
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}> |
