diff options
| author | Adam <[email protected]> | 2025-12-07 21:21:13 -0600 |
|---|---|---|
| committer | Adam <[email protected]> | 2025-12-07 21:21:18 -0600 |
| commit | 9a90939ac410e3ebffb741994383d7a434069682 (patch) | |
| tree | f3621d491af6eb97e0bf9039c7f82f0451ebd9d7 | |
| parent | 123a136093968376935e7166efd26b17a3f68b39 (diff) | |
| download | opencode-9a90939ac410e3ebffb741994383d7a434069682.tar.gz opencode-9a90939ac410e3ebffb741994383d7a434069682.zip | |
fix: use diff context instead of prop drilling
| -rw-r--r-- | packages/desktop/src/index.tsx | 64 | ||||
| -rw-r--r-- | packages/desktop/src/pages/session.tsx | 6 | ||||
| -rw-r--r-- | packages/enterprise/src/routes/share/[shareID].tsx | 415 | ||||
| -rw-r--r-- | packages/ui/src/components/message-part.tsx | 32 | ||||
| -rw-r--r-- | packages/ui/src/components/message-progress.tsx | 21 | ||||
| -rw-r--r-- | packages/ui/src/components/session-review.tsx | 7 | ||||
| -rw-r--r-- | packages/ui/src/components/session-turn.tsx | 36 | ||||
| -rw-r--r-- | packages/ui/src/context/diff.tsx | 13 | ||||
| -rw-r--r-- | packages/ui/src/context/index.ts | 1 |
9 files changed, 280 insertions, 315 deletions
diff --git a/packages/desktop/src/index.tsx b/packages/desktop/src/index.tsx index 149b907bc..3c21b9a37 100644 --- a/packages/desktop/src/index.tsx +++ b/packages/desktop/src/index.tsx @@ -6,6 +6,8 @@ import { MetaProvider } from "@solidjs/meta" import { Font } from "@opencode-ai/ui/font" import { Favicon } from "@opencode-ai/ui/favicon" import { MarkedProvider } from "@opencode-ai/ui/context/marked" +import { DiffComponentProvider } from "@opencode-ai/ui/context/diff" +import { Diff } from "@opencode-ai/ui/diff" import { GlobalSyncProvider, useGlobalSync } from "./context/global-sync" import Layout from "@/pages/layout" import DirectoryLayout from "@/pages/directory-layout" @@ -35,38 +37,40 @@ if (import.meta.env.DEV && !(root instanceof HTMLElement)) { render( () => ( <MarkedProvider> - <GlobalSDKProvider url={url}> - <GlobalSyncProvider> - <LayoutProvider> - <MetaProvider> - <Font /> - <Router root={Layout}> - <Route - path="/" - component={() => { - const globalSync = useGlobalSync() - const slug = createMemo(() => base64Encode(globalSync.data.defaultProject!.worktree)) - return <Navigate href={`${slug()}/session`} /> - }} - /> - <Route path="/:dir" component={DirectoryLayout}> - <Route path="/" component={() => <Navigate href="session" />} /> + <DiffComponentProvider component={Diff}> + <GlobalSDKProvider url={url}> + <GlobalSyncProvider> + <LayoutProvider> + <MetaProvider> + <Font /> + <Router root={Layout}> <Route - path="/session/:id?" - component={(p) => ( - <Show when={p.params.id || true} keyed> - <SessionProvider> - <Session /> - </SessionProvider> - </Show> - )} + path="/" + component={() => { + const globalSync = useGlobalSync() + const slug = createMemo(() => base64Encode(globalSync.data.defaultProject!.worktree)) + return <Navigate href={`${slug()}/session`} /> + }} /> - </Route> - </Router> - </MetaProvider> - </LayoutProvider> - </GlobalSyncProvider> - </GlobalSDKProvider> + <Route path="/:dir" component={DirectoryLayout}> + <Route path="/" component={() => <Navigate href="session" />} /> + <Route + path="/session/:id?" + component={(p) => ( + <Show when={p.params.id || true} keyed> + <SessionProvider> + <Session /> + </SessionProvider> + </Show> + )} + /> + </Route> + </Router> + </MetaProvider> + </LayoutProvider> + </GlobalSyncProvider> + </GlobalSDKProvider> + </DiffComponentProvider> </MarkedProvider> ), root!, diff --git a/packages/desktop/src/pages/session.tsx b/packages/desktop/src/pages/session.tsx index 8cd9e9d65..3cc6d307c 100644 --- a/packages/desktop/src/pages/session.tsx +++ b/packages/desktop/src/pages/session.tsx @@ -30,7 +30,6 @@ import { useSync } from "@/context/sync" import { useSession } from "@/context/session" import { useLayout } from "@/context/layout" import { getDirectory, getFilename } from "@opencode-ai/util/path" -import { Diff } from "@opencode-ai/ui/diff" import { Terminal } from "@/components/terminal" export default function Page() { @@ -282,7 +281,7 @@ export default function Page() { const wide = createMemo(() => layout.review.state() === "tab" || !session.diffs().length) return ( - <div class="relative bg-background-base size-full overflow-x-hidden flex flex-col items-start"> + <div class="relative bg-background-base size-full overflow-x-hidden flex flex-col"> <div class="min-h-0 grow w-full"> <DragDropProvider onDragStart={handleDragStart} @@ -389,7 +388,6 @@ export default function Page() { ? "pr-6 pl-18" : "px-6"), }} - diffComponent={Diff} /> </div> </Match> @@ -438,7 +436,6 @@ export default function Page() { container: "px-6", }} diffs={session.diffs()} - diffComponent={Diff} actions={ <Tooltip value="Open in tab"> <IconButton @@ -470,7 +467,6 @@ export default function Page() { container: "px-6", }} diffs={session.diffs()} - diffComponent={Diff} split /> </div> diff --git a/packages/enterprise/src/routes/share/[shareID].tsx b/packages/enterprise/src/routes/share/[shareID].tsx index 5f6a09486..c3392489d 100644 --- a/packages/enterprise/src/routes/share/[shareID].tsx +++ b/packages/enterprise/src/routes/share/[shareID].tsx @@ -2,6 +2,7 @@ import { FileDiff, Message, Model, Part, Session, SessionStatus, UserMessage } f import { SessionTurn } from "@opencode-ai/ui/session-turn" import { SessionReview } from "@opencode-ai/ui/session-review" import { DataProvider } from "@opencode-ai/ui/context" +import { DiffComponentProvider } from "@opencode-ai/ui/context/diff" import { createAsync, query, useParams } from "@solidjs/router" import { createEffect, createMemo, ErrorBoundary, For, Match, Show, Switch } from "solid-js" import { Share } from "~/core/share" @@ -18,7 +19,7 @@ import z from "zod" import NotFound from "../[...404]" import { Tabs } from "@opencode-ai/ui/tabs" import { preloadMultiFileDiff, PreloadMultiFileDiffResult } from "@pierre/precision-diffs/ssr" -import { Diff } from "@opencode-ai/ui/diff-ssr" +import { Diff as SSRDiff } from "@opencode-ai/ui/diff-ssr" import { clientOnly } from "@solidjs/start" const ClientOnlyDiff = clientOnly(() => import("@opencode-ai/ui/diff").then((m) => ({ default: m.Diff }))) @@ -157,234 +158,240 @@ export default function () { const info = createMemo(() => data().session[match().index]) return ( - <DataProvider data={data()} directory={info().directory}> - {iife(() => { - const [store, setStore] = createStore({ - messageId: undefined as string | undefined, - }) - const messages = createMemo(() => - data().sessionID - ? (data().message[data().sessionID]?.filter((m) => m.role === "user") ?? []).sort( - (a, b) => b.time.created - a.time.created, - ) - : [], - ) - const firstUserMessage = createMemo(() => messages().at(0)) - const activeMessage = createMemo( - () => messages().find((m) => m.id === store.messageId) ?? firstUserMessage(), - ) - function setActiveMessage(message: UserMessage | undefined) { - if (message) { - setStore("messageId", message.id) - } else { - setStore("messageId", undefined) + <DiffComponentProvider component={ClientOnlyDiff}> + <DataProvider data={data()} directory={info().directory}> + {iife(() => { + const [store, setStore] = createStore({ + messageId: undefined as string | undefined, + }) + const messages = createMemo(() => + data().sessionID + ? (data().message[data().sessionID]?.filter((m) => m.role === "user") ?? []).sort( + (a, b) => b.time.created - a.time.created, + ) + : [], + ) + const firstUserMessage = createMemo(() => messages().at(0)) + const activeMessage = createMemo( + () => messages().find((m) => m.id === store.messageId) ?? firstUserMessage(), + ) + function setActiveMessage(message: UserMessage | undefined) { + if (message) { + setStore("messageId", message.id) + } else { + setStore("messageId", undefined) + } } - } - const provider = createMemo(() => activeMessage()?.model?.providerID) - const modelID = createMemo(() => activeMessage()?.model?.modelID) - const model = createMemo(() => data().model[data().sessionID]?.find((m) => m.id === modelID())) - const diffs = createMemo(() => { - const diffs = data().session_diff[data().sessionID] ?? [] - const preloaded = data().session_diff_preload[data().sessionID] ?? [] - return diffs.map((diff) => ({ - ...diff, - preloaded: preloaded.find((d) => d.newFile.name === diff.file), - })) - }) - const splitDiffs = createMemo(() => { - const diffs = data().session_diff[data().sessionID] ?? [] - const preloaded = data().session_diff_preload_split[data().sessionID] ?? [] - return diffs.map((diff) => ({ - ...diff, - preloaded: preloaded.find((d) => d.newFile.name === diff.file), - })) - }) + const provider = createMemo(() => activeMessage()?.model?.providerID) + const modelID = createMemo(() => activeMessage()?.model?.modelID) + const model = createMemo(() => data().model[data().sessionID]?.find((m) => m.id === modelID())) + const diffs = createMemo(() => { + const diffs = data().session_diff[data().sessionID] ?? [] + const preloaded = data().session_diff_preload[data().sessionID] ?? [] + return diffs.map((diff) => ({ + ...diff, + preloaded: preloaded.find((d) => d.newFile.name === diff.file), + })) + }) + const splitDiffs = createMemo(() => { + const diffs = data().session_diff[data().sessionID] ?? [] + const preloaded = data().session_diff_preload_split[data().sessionID] ?? [] + return diffs.map((diff) => ({ + ...diff, + preloaded: preloaded.find((d) => d.newFile.name === diff.file), + })) + }) - const title = () => ( - <div class="flex flex-col gap-4"> - <div class="h-8 flex gap-4 items-center justify-start self-stretch"> - <div class="pl-[2.5px] pr-2 flex items-center gap-1.75 bg-surface-strong shadow-xs-border-base"> - <Mark class="shrink-0 w-3 my-0.5" /> - <div class="text-12-mono text-text-base">v{info().version}</div> - </div> - <div class="flex gap-2 items-center"> - <img src={`https://models.dev/logos/${provider()}.svg`} class="size-3.5 shrink-0 dark:invert" /> - <div class="text-12-regular text-text-base">{model()?.name ?? modelID()}</div> - </div> - <div class="text-12-regular text-text-weaker"> - {DateTime.fromMillis(info().time.created).toFormat("dd MMM yyyy, HH:mm")} + const title = () => ( + <div class="flex flex-col gap-4"> + <div class="h-8 flex gap-4 items-center justify-start self-stretch"> + <div class="pl-[2.5px] pr-2 flex items-center gap-1.75 bg-surface-strong shadow-xs-border-base"> + <Mark class="shrink-0 w-3 my-0.5" /> + <div class="text-12-mono text-text-base">v{info().version}</div> + </div> + <div class="flex gap-2 items-center"> + <img + src={`https://models.dev/logos/${provider()}.svg`} + class="size-3.5 shrink-0 dark:invert" + /> + <div class="text-12-regular text-text-base">{model()?.name ?? modelID()}</div> + </div> + <div class="text-12-regular text-text-weaker"> + {DateTime.fromMillis(info().time.created).toFormat("dd MMM yyyy, HH:mm")} + </div> </div> + <div class="text-left text-16-medium text-text-strong">{info().title}</div> </div> - <div class="text-left text-16-medium text-text-strong">{info().title}</div> - </div> - ) + ) - const turns = () => ( - <div class="relative mt-2 pt-6 pb-8 min-w-0 w-full h-full overflow-y-auto no-scrollbar"> - <div class="px-4">{title()}</div> - <div class="flex flex-col gap-15 items-start justify-start mt-4"> - <For each={messages()}> - {(message) => ( - <SessionTurn - sessionID={data().sessionID} - messageID={message.id} - classes={{ - root: "min-w-0 w-full relative", - content: - "flex flex-col justify-between !overflow-visible [&_[data-slot=session-turn-message-header]]:top-[-32px]", - container: "px-4", - }} - diffComponent={ClientOnlyDiff} - /> - )} - </For> - </div> - <div class="px-4 flex items-center justify-center pt-20 pb-8 shrink-0"> - <Logo class="w-58.5 opacity-12" /> + const turns = () => ( + <div class="relative mt-2 pt-6 pb-8 min-w-0 w-full h-full overflow-y-auto no-scrollbar"> + <div class="px-4">{title()}</div> + <div class="flex flex-col gap-15 items-start justify-start mt-4"> + <For each={messages()}> + {(message) => ( + <SessionTurn + sessionID={data().sessionID} + messageID={message.id} + classes={{ + root: "min-w-0 w-full relative", + content: + "flex flex-col justify-between !overflow-visible [&_[data-slot=session-turn-message-header]]:top-[-32px]", + container: "px-4", + }} + /> + )} + </For> + </div> + <div class="px-4 flex items-center justify-center pt-20 pb-8 shrink-0"> + <Logo class="w-58.5 opacity-12" /> + </div> </div> - </div> - ) + ) - const wide = createMemo(() => diffs().length === 0) + const wide = createMemo(() => diffs().length === 0) - return ( - <div class="relative bg-background-stronger w-screen h-screen overflow-hidden flex flex-col"> - <header class="h-12 px-6 py-2 flex items-center justify-between self-stretch bg-background-base border-b border-border-weak-base"> - <div class=""> - <a href="https://opencode.ai"> - <Mark /> - </a> - </div> - <div class="flex gap-3 items-center"> - <IconButton - as={"a"} - href="https://github.com/sst/opencode" - target="_blank" - icon="github" - variant="ghost" - /> - <IconButton - as={"a"} - href="https://opencode.ai/discord" - target="_blank" - icon="discord" - variant="ghost" - /> - </div> - </header> - <div class="select-text flex flex-col flex-1 min-h-0"> - <div classList={{ "hidden w-full flex-1 min-h-0": true, "md:flex": wide(), "lg:flex": !wide() }}> + return ( + <div class="relative bg-background-stronger w-screen h-screen overflow-hidden flex flex-col"> + <header class="h-12 px-6 py-2 flex items-center justify-between self-stretch bg-background-base border-b border-border-weak-base"> + <div class=""> + <a href="https://opencode.ai"> + <Mark /> + </a> + </div> + <div class="flex gap-3 items-center"> + <IconButton + as={"a"} + href="https://github.com/sst/opencode" + target="_blank" + icon="github" + variant="ghost" + /> + <IconButton + as={"a"} + href="https://opencode.ai/discord" + target="_blank" + icon="discord" + variant="ghost" + /> + </div> + </header> + <div class="select-text flex flex-col flex-1 min-h-0"> <div - classList={{ - "@container relative shrink-0 pt-14 flex flex-col gap-10 min-h-0 w-full": true, - "mx-auto max-w-146": !wide(), - }} + classList={{ "hidden w-full flex-1 min-h-0": true, "md:flex": wide(), "lg:flex": !wide() }} > <div classList={{ - "w-full flex justify-start items-start min-w-0": true, - "max-w-146 mx-auto px-6": wide(), - "pr-6 pl-18": !wide() && messages().length > 1, - "px-6": !wide() && messages().length === 1, + "@container relative shrink-0 pt-14 flex flex-col gap-10 min-h-0 w-full": true, + "mx-auto max-w-146": !wide(), }} > - {title()} - </div> - <div class="flex items-start justify-start h-full min-h-0"> - <SessionMessageRail - messages={messages()} - current={activeMessage()} - onMessageSelect={setActiveMessage} - wide={wide()} - /> - <SessionTurn - sessionID={data().sessionID} - messageID={store.messageId ?? firstUserMessage()!.id!} - classes={{ - root: "grow", - content: "flex flex-col justify-between items-start", - container: - "w-full pb-20 " + - (wide() ? "max-w-146 mx-auto px-6" : messages().length > 1 ? "pr-6 pl-18" : "px-6"), + <div + classList={{ + "w-full flex justify-start items-start min-w-0": true, + "max-w-146 mx-auto px-6": wide(), + "pr-6 pl-18": !wide() && messages().length > 1, + "px-6": !wide() && messages().length === 1, }} - diffComponent={ClientOnlyDiff} > - <div classList={{ "w-full flex items-center justify-center pb-8 shrink-0": true }}> - <Logo class="w-58.5 opacity-12" /> - </div> - </SessionTurn> - </div> - </div> - <Show when={diffs().length > 0}> - <div class="@container relative grow pt-14 flex-1 min-h-0 border-l border-border-weak-base"> - <SessionReview - class="@4xl:hidden" - diffs={diffs()} - diffComponent={Diff} - classes={{ - root: "pb-20", - header: "px-6", - container: "px-6", - }} - /> - <SessionReview - split - class="hidden @4xl:flex" - diffs={splitDiffs()} - diffComponent={Diff} - classes={{ - root: "pb-20", - header: "px-6", - container: "px-6", - }} - /> + {title()} + </div> + <div class="flex items-start justify-start h-full min-h-0"> + <SessionMessageRail + messages={messages()} + current={activeMessage()} + onMessageSelect={setActiveMessage} + wide={wide()} + /> + <SessionTurn + sessionID={data().sessionID} + messageID={store.messageId ?? firstUserMessage()!.id!} + classes={{ + root: "grow", + content: "flex flex-col justify-between items-start", + container: + "w-full pb-20 " + + (wide() ? "max-w-146 mx-auto px-6" : messages().length > 1 ? "pr-6 pl-18" : "px-6"), + }} + > + <div classList={{ "w-full flex items-center justify-center pb-8 shrink-0": true }}> + <Logo class="w-58.5 opacity-12" /> + </div> + </SessionTurn> + </div> </div> - </Show> - </div> - <Switch> - <Match when={diffs().length > 0}> - <Tabs classList={{ "md:hidden": wide(), "lg:hidden": !wide() }}> - <Tabs.List> - <Tabs.Trigger value="session" class="w-1/2" classes={{ button: "w-full" }}> - Session - </Tabs.Trigger> - <Tabs.Trigger value="review" class="w-1/2 !border-r-0" classes={{ button: "w-full" }}> - 5 Files Changed - </Tabs.Trigger> - </Tabs.List> - <Tabs.Content value="session" class="!overflow-hidden"> - {turns()} - </Tabs.Content> - <Tabs.Content - forceMount - value="review" - class="!overflow-hidden hidden data-[selected]:block" - > - <div class="relative h-full pt-8 overflow-y-auto no-scrollbar"> + <Show when={diffs().length > 0}> + <DiffComponentProvider component={SSRDiff}> + <div class="@container relative grow pt-14 flex-1 min-h-0 border-l border-border-weak-base"> <SessionReview + class="@4xl:hidden" diffs={diffs()} - diffComponent={Diff} classes={{ root: "pb-20", - header: "px-4", - container: "px-4", + header: "px-6", + container: "px-6", + }} + /> + <SessionReview + split + class="hidden @4xl:flex" + diffs={splitDiffs()} + classes={{ + root: "pb-20", + header: "px-6", + container: "px-6", }} /> </div> - </Tabs.Content> - </Tabs> - </Match> - <Match when={true}> - <div classList={{ "!overflow-hidden": true, "md:hidden": wide(), "lg:hidden": !wide() }}> - {turns()} - </div> - </Match> - </Switch> + </DiffComponentProvider> + </Show> + </div> + <Switch> + <Match when={diffs().length > 0}> + <Tabs classList={{ "md:hidden": wide(), "lg:hidden": !wide() }}> + <Tabs.List> + <Tabs.Trigger value="session" class="w-1/2" classes={{ button: "w-full" }}> + Session + </Tabs.Trigger> + <Tabs.Trigger value="review" class="w-1/2 !border-r-0" classes={{ button: "w-full" }}> + 5 Files Changed + </Tabs.Trigger> + </Tabs.List> + <Tabs.Content value="session" class="!overflow-hidden"> + {turns()} + </Tabs.Content> + <Tabs.Content + forceMount + value="review" + class="!overflow-hidden hidden data-[selected]:block" + > + <div class="relative h-full pt-8 overflow-y-auto no-scrollbar"> + <DiffComponentProvider component={SSRDiff}> + <SessionReview + diffs={diffs()} + classes={{ + root: "pb-20", + header: "px-4", + container: "px-4", + }} + /> + </DiffComponentProvider> + </div> + </Tabs.Content> + </Tabs> + </Match> + <Match when={true}> + <div classList={{ "!overflow-hidden": true, "md:hidden": wide(), "lg:hidden": !wide() }}> + {turns()} + </div> + </Match> + </Switch> + </div> </div> - </div> - ) - })} - </DataProvider> + ) + })} + </DataProvider> + </DiffComponentProvider> ) }} </Show> diff --git a/packages/ui/src/components/message-part.tsx b/packages/ui/src/components/message-part.tsx index a0e6e91b6..2da4387fd 100644 --- a/packages/ui/src/components/message-part.tsx +++ b/packages/ui/src/components/message-part.tsx @@ -1,4 +1,4 @@ -import { Component, createMemo, For, Match, Show, Switch, ValidComponent } from "solid-js" +import { Component, createMemo, For, Match, Show, Switch } from "solid-js" import { Dynamic } from "solid-js/web" import { AssistantMessage, @@ -8,6 +8,7 @@ import { ToolPart, UserMessage, } from "@opencode-ai/sdk" +import { useDiffComponent } from "../context/diff" import { BasicTool } from "./basic-tool" import { GenericTool } from "./basic-tool" import { Card } from "./card" @@ -22,14 +23,12 @@ import { unwrap } from "solid-js/store" export interface MessageProps { message: MessageType parts: PartType[] - diffComponent: ValidComponent sanitize?: RegExp } export interface MessagePartProps { part: PartType message: MessageType - diffComponent: ValidComponent hideDetails?: boolean sanitize?: RegExp } @@ -54,7 +53,6 @@ export function Message(props: MessageProps) { message={assistantMessage() as AssistantMessage} parts={props.parts} sanitize={props.sanitize} - diffComponent={props.diffComponent} /> )} </Match> @@ -62,12 +60,7 @@ export function Message(props: MessageProps) { ) } -export function AssistantMessageDisplay(props: { - message: AssistantMessage - parts: PartType[] - sanitize?: RegExp - diffComponent: ValidComponent -}) { +export function AssistantMessageDisplay(props: { message: AssistantMessage; parts: PartType[]; sanitize?: RegExp }) { const filteredParts = createMemo(() => { return props.parts?.filter((x) => { if (x.type === "reasoning") return false @@ -75,11 +68,7 @@ export function AssistantMessageDisplay(props: { }) }) return ( - <For each={filteredParts()}> - {(part) => ( - <Part part={part} message={props.message} sanitize={props.sanitize} diffComponent={props.diffComponent} /> - )} - </For> + <For each={filteredParts()}>{(part) => <Part part={part} message={props.message} sanitize={props.sanitize} />}</For> ) } @@ -98,13 +87,7 @@ export function Part(props: MessagePartProps) { const part = createMemo(() => sanitizePart(unwrap(props.part), props.sanitize)) return ( <Show when={component()}> - <Dynamic - component={component()} - part={part()} - message={props.message} - diffComponent={props.diffComponent} - hideDetails={props.hideDetails} - /> + <Dynamic component={component()} part={part()} message={props.message} hideDetails={props.hideDetails} /> </Show> ) } @@ -113,7 +96,6 @@ export interface ToolProps { input: Record<string, any> metadata: Record<string, any> tool: string - diffComponent: ValidComponent output?: string hideDetails?: boolean } @@ -180,7 +162,6 @@ PART_MAPPING["tool"] = function ToolPartDisplay(props) { component={render} input={input} tool={part.tool} - diffComponent={props.diffComponent} metadata={metadata} output={part.state.status === "completed" ? part.state.output : undefined} hideDetails={props.hideDetails} @@ -356,6 +337,7 @@ ToolRegistry.register({ ToolRegistry.register({ name: "edit", render(props) { + const diffComponent = useDiffComponent() return ( <BasicTool icon="code-lines" @@ -381,7 +363,7 @@ ToolRegistry.register({ <Show when={props.metadata.filediff}> <div data-component="edit-content"> <Dynamic - component={props.diffComponent} + component={diffComponent} before={{ name: getFilename(props.metadata.filediff.path), contents: props.metadata.filediff.before, diff --git a/packages/ui/src/components/message-progress.tsx b/packages/ui/src/components/message-progress.tsx index afd7f754a..bbcdd3096 100644 --- a/packages/ui/src/components/message-progress.tsx +++ b/packages/ui/src/components/message-progress.tsx @@ -1,15 +1,4 @@ -import { - For, - JSXElement, - Match, - Show, - Switch, - ValidComponent, - createEffect, - createMemo, - createSignal, - onCleanup, -} from "solid-js" +import { For, JSXElement, Match, Show, Switch, createEffect, createMemo, createSignal, onCleanup } from "solid-js" import { Part } from "./message-part" import { Spinner } from "./spinner" import { useData } from "../context/data" @@ -17,7 +6,6 @@ import type { AssistantMessage as AssistantMessageType, ToolPart } from "@openco export interface MessageProgressProps { assistantMessages: () => AssistantMessageType[] - diffComponent: ValidComponent done?: boolean } @@ -172,12 +160,7 @@ export function MessageProgress(props: MessageProgressProps) { ) return ( <div data-slot="message-progress-item"> - <Part - message={message()!} - part={part} - sanitize={sanitizer()} - diffComponent={props.diffComponent} - /> + <Part message={message()!} part={part} sanitize={sanitizer()} /> </div> ) }} diff --git a/packages/ui/src/components/session-review.tsx b/packages/ui/src/components/session-review.tsx index ea5871b95..6dbaaec18 100644 --- a/packages/ui/src/components/session-review.tsx +++ b/packages/ui/src/components/session-review.tsx @@ -4,8 +4,9 @@ import { DiffChanges } from "./diff-changes" import { FileIcon } from "./file-icon" import { Icon } from "./icon" import { StickyAccordionHeader } from "./sticky-accordion-header" +import { useDiffComponent } from "../context/diff" import { getDirectory, getFilename } from "@opencode-ai/util/path" -import { For, Match, Show, Switch, ValidComponent, type JSX } from "solid-js" +import { For, Match, Show, Switch, type JSX } from "solid-js" import { createStore } from "solid-js/store" import { type FileDiff } from "@opencode-ai/sdk" import { PreloadMultiFileDiffResult } from "@pierre/precision-diffs/ssr" @@ -18,10 +19,10 @@ export interface SessionReviewProps { classes?: { root?: string; header?: string; container?: string } actions?: JSX.Element diffs: (FileDiff & { preloaded?: PreloadMultiFileDiffResult<any> })[] - diffComponent: ValidComponent } export const SessionReview = (props: SessionReviewProps) => { + const diffComponent = useDiffComponent() const [store, setStore] = createStore({ open: props.diffs.map((d) => d.file), }) @@ -98,7 +99,7 @@ export const SessionReview = (props: SessionReviewProps) => { </StickyAccordionHeader> <Accordion.Content data-slot="session-review-accordion-content"> <Dynamic - component={props.diffComponent} + component={diffComponent} preloadedDiff={diff.preloaded} diffStyle={props.split ? "split" : "unified"} before={{ diff --git a/packages/ui/src/components/session-turn.tsx b/packages/ui/src/components/session-turn.tsx index 963713a22..44ecc3a14 100644 --- a/packages/ui/src/components/session-turn.tsx +++ b/packages/ui/src/components/session-turn.tsx @@ -1,19 +1,9 @@ import { AssistantMessage } from "@opencode-ai/sdk" import { useData } from "../context" +import { useDiffComponent } from "../context/diff" import { Binary } from "@opencode-ai/util/binary" import { getDirectory, getFilename } from "@opencode-ai/util/path" -import { - createEffect, - createMemo, - createSignal, - For, - Match, - onMount, - ParentProps, - Show, - Switch, - ValidComponent, -} from "solid-js" +import { createEffect, createMemo, createSignal, For, Match, onMount, ParentProps, Show, Switch } from "solid-js" import { DiffChanges } from "./diff-changes" import { Typewriter } from "./typewriter" import { Message } from "./message-part" @@ -36,10 +26,10 @@ export function SessionTurn( content?: string container?: string } - diffComponent: ValidComponent }>, ) { const data = useData() + const diffComponent = useDiffComponent() const match = Binary.search(data.store.session, props.sessionID, (s) => s.id) if (!match.found) throw new Error(`Session ${props.sessionID} not found`) @@ -129,7 +119,7 @@ export function SessionTurn( </div> </div> <div data-slot="session-turn-message-content"> - <Message message={msg()} parts={parts()} sanitize={sanitizer()} diffComponent={props.diffComponent} /> + <Message message={msg()} parts={parts()} sanitize={sanitizer()} /> </div> {/* Summary */} <Show when={completed()}> @@ -180,7 +170,7 @@ export function SessionTurn( </StickyAccordionHeader> <Accordion.Content data-slot="session-turn-accordion-content"> <Dynamic - component={props.diffComponent} + component={diffComponent} before={{ name: diff.file!, contents: diff.before!, @@ -206,11 +196,7 @@ export function SessionTurn( <div data-slot="session-turn-response-section"> <Switch> <Match when={!completed()}> - <MessageProgress - assistantMessages={assistantMessages} - done={!messageWorking()} - diffComponent={props.diffComponent} - /> + <MessageProgress assistantMessages={assistantMessages} done={!messageWorking()} /> </Match> <Match when={completed() && hasToolPart()}> <Collapsible variant="ghost" open={detailsExpanded()} onOpenChange={setDetailsExpanded}> @@ -241,18 +227,10 @@ export function SessionTurn( message={assistantMessage} parts={parts().filter((p) => p?.id !== last()?.id)} sanitize={sanitizer()} - diffComponent={props.diffComponent} /> ) } - return ( - <Message - message={assistantMessage} - parts={parts()} - sanitize={sanitizer()} - diffComponent={props.diffComponent} - /> - ) + return <Message message={assistantMessage} parts={parts()} sanitize={sanitizer()} /> }} </For> <Show when={error()}> diff --git a/packages/ui/src/context/diff.tsx b/packages/ui/src/context/diff.tsx new file mode 100644 index 000000000..630437de6 --- /dev/null +++ b/packages/ui/src/context/diff.tsx @@ -0,0 +1,13 @@ +import { createContext, useContext, type ParentProps, type ValidComponent } from "solid-js" + +const DiffComponentContext = createContext<ValidComponent>() + +export function DiffComponentProvider(props: ParentProps<{ component: ValidComponent }>) { + return <DiffComponentContext.Provider value={props.component}>{props.children}</DiffComponentContext.Provider> +} + +export function useDiffComponent() { + const component = useContext(DiffComponentContext) + if (!component) throw new Error("DiffComponentProvider must be used to provide a diff component") + return component +} diff --git a/packages/ui/src/context/index.ts b/packages/ui/src/context/index.ts index fdff32bf2..3e0f5de74 100644 --- a/packages/ui/src/context/index.ts +++ b/packages/ui/src/context/index.ts @@ -1,2 +1,3 @@ export * from "./helper" export * from "./data" +export * from "./diff" |
