summaryrefslogtreecommitdiffhomepage
path: root/packages/web/src
diff options
context:
space:
mode:
authorDax <[email protected]>2025-07-13 17:22:11 -0400
committerGitHub <[email protected]>2025-07-13 17:22:11 -0400
commit90d6c4ab41bb097d7db354109e3616ff16778f0b (patch)
tree303861ce5789f6e0e8e843cb8184dea829b4885d /packages/web/src
parent736396fc70ab05204b886634ffbcd1318d82eca8 (diff)
downloadopencode-90d6c4ab41bb097d7db354109e3616ff16778f0b.tar.gz
opencode-90d6c4ab41bb097d7db354109e3616ff16778f0b.zip
Part data model (#950)
Diffstat (limited to 'packages/web/src')
-rw-r--r--packages/web/src/components/Share.tsx60
1 files changed, 46 insertions, 14 deletions
diff --git a/packages/web/src/components/Share.tsx b/packages/web/src/components/Share.tsx
index ad23f188b..56f218fd8 100644
--- a/packages/web/src/components/Share.tsx
+++ b/packages/web/src/components/Share.tsx
@@ -1,6 +1,6 @@
-import { For, Show, onMount, Suspense, onCleanup, createMemo, createSignal, SuspenseList } from "solid-js"
+import { For, Show, onMount, Suspense, onCleanup, createMemo, createSignal, SuspenseList, createEffect } from "solid-js"
import { DateTime } from "luxon"
-import { createStore, reconcile } from "solid-js/store"
+import { createStore, reconcile, unwrap } from "solid-js/store"
import { IconArrowDown } from "./icons"
import { IconOpencode } from "./icons/custom"
import styles from "./share.module.css"
@@ -9,6 +9,8 @@ import type { Message } from "opencode/session/message"
import type { Session } from "opencode/session/index"
import { Part, ProviderIcon } from "./share/part"
+type MessageWithParts = MessageV2.Info & { parts: MessageV2.Part[] }
+
type Status = "disconnected" | "connecting" | "connected" | "error" | "reconnecting"
function scrollToAnchor(id: string) {
@@ -39,7 +41,7 @@ export default function Share(props: {
id: string
api: string
info: Session.Info
- messages: Record<string, MessageV2.Info>
+ messages: Record<string, MessageWithParts>
}) {
let lastScrollY = 0
let hasScrolledToAnchor = false
@@ -57,10 +59,13 @@ export default function Share(props: {
const [store, setStore] = createStore<{
info?: Session.Info
- messages: Record<string, MessageV2.Info | Message.Info>
+ messages: Record<string, MessageWithParts>
}>({ info: props.info, messages: props.messages })
const messages = createMemo(() => Object.values(store.messages).toSorted((a, b) => a.id?.localeCompare(b.id)))
const [connectionStatus, setConnectionStatus] = createSignal<[Status, string?]>(["disconnected", "Disconnected"])
+ createEffect(() => {
+ console.log(unwrap(store))
+ })
onMount(() => {
const apiUrl = props.api
@@ -115,8 +120,22 @@ export default function Share(props: {
}
if (type === "message") {
const [, messageID] = splits
+ if ("metadata" in d.content) {
+ d.content = fromV1(d.content)
+ }
+ d.content.parts = d.content.parts ?? store.messages[messageID]?.parts ?? []
setStore("messages", messageID, reconcile(d.content))
}
+ if (type === "part") {
+ setStore("messages", d.content.messageID, "parts", arr => {
+ const index = arr.findIndex((x) => x.id === d.content.id)
+ if (index === -1)
+ arr.push(d.content)
+ if (index > -1)
+ arr[index] = d.content
+ return [...arr]
+ })
+ }
} catch (error) {
console.error("Error parsing WebSocket message:", error)
}
@@ -233,7 +252,7 @@ export default function Share(props: {
rootDir: undefined as string | undefined,
created: undefined as number | undefined,
completed: undefined as number | undefined,
- messages: [] as MessageV2.Info[],
+ messages: [] as MessageWithParts[],
models: {} as Record<string, string[]>,
cost: 0,
tokens: {
@@ -247,7 +266,7 @@ export default function Share(props: {
const msgs = messages()
for (let i = 0; i < msgs.length; i++) {
- const msg = "metadata" in msgs[i] ? fromV1(msgs[i] as Message.Info) : (msgs[i] as MessageV2.Info)
+ const msg = msgs[i]
result.messages.push(msg)
@@ -464,9 +483,9 @@ export default function Share(props: {
)
}
-export function fromV1(v1: Message.Info): MessageV2.Info {
+export function fromV1(v1: Message.Info): MessageWithParts {
if (v1.role === "assistant") {
- const result: MessageV2.Assistant = {
+ return {
id: v1.id,
sessionID: v1.metadata.sessionID,
role: "assistant",
@@ -482,10 +501,16 @@ export function fromV1(v1: Message.Info): MessageV2.Info {
providerID: v1.metadata.assistant!.providerID,
system: v1.metadata.assistant!.system,
error: v1.metadata.error,
- parts: v1.parts.flatMap((part): MessageV2.AssistantPart[] => {
+ parts: v1.parts.flatMap((part, index): MessageV2.Part[] => {
+ const base = {
+ id: index.toString(),
+ messageID: v1.id,
+ sessionID: v1.metadata.sessionID,
+ }
if (part.type === "text") {
return [
{
+ ...base,
type: "text",
text: part.text,
},
@@ -494,6 +519,7 @@ export function fromV1(v1: Message.Info): MessageV2.Info {
if (part.type === "step-start") {
return [
{
+ ...base,
type: "step-start",
},
]
@@ -501,8 +527,9 @@ export function fromV1(v1: Message.Info): MessageV2.Info {
if (part.type === "tool-invocation") {
return [
{
+ ...base,
type: "tool",
- id: part.toolInvocation.toolCallId,
+ callID: part.toolInvocation.toolCallId,
tool: part.toolInvocation.toolName,
state: (() => {
if (part.toolInvocation.state === "partial-call") {
@@ -540,21 +567,26 @@ export function fromV1(v1: Message.Info): MessageV2.Info {
return []
}),
}
- return result
}
if (v1.role === "user") {
- const result: MessageV2.User = {
+ return {
id: v1.id,
sessionID: v1.metadata.sessionID,
role: "user",
time: {
created: v1.metadata.time.created,
},
- parts: v1.parts.flatMap((part): MessageV2.UserPart[] => {
+ parts: v1.parts.flatMap((part, index): MessageV2.Part[] => {
+ const base = {
+ id: index.toString(),
+ messageID: v1.id,
+ sessionID: v1.metadata.sessionID,
+ }
if (part.type === "text") {
return [
{
+ ...base,
type: "text",
text: part.text,
},
@@ -563,6 +595,7 @@ export function fromV1(v1: Message.Info): MessageV2.Info {
if (part.type === "file") {
return [
{
+ ...base,
type: "file",
mime: part.mediaType,
filename: part.filename,
@@ -573,7 +606,6 @@ export function fromV1(v1: Message.Info): MessageV2.Info {
return []
}),
}
- return result
}
throw new Error("unknown message type")