fix: use diff context instead of prop drilling
This commit is contained in:
@@ -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,6 +37,7 @@ if (import.meta.env.DEV && !(root instanceof HTMLElement)) {
|
||||
render(
|
||||
() => (
|
||||
<MarkedProvider>
|
||||
<DiffComponentProvider component={Diff}>
|
||||
<GlobalSDKProvider url={url}>
|
||||
<GlobalSyncProvider>
|
||||
<LayoutProvider>
|
||||
@@ -67,6 +70,7 @@ render(
|
||||
</LayoutProvider>
|
||||
</GlobalSyncProvider>
|
||||
</GlobalSDKProvider>
|
||||
</DiffComponentProvider>
|
||||
</MarkedProvider>
|
||||
),
|
||||
root!,
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,6 +158,7 @@ export default function () {
|
||||
const info = createMemo(() => data().session[match().index])
|
||||
|
||||
return (
|
||||
<DiffComponentProvider component={ClientOnlyDiff}>
|
||||
<DataProvider data={data()} directory={info().directory}>
|
||||
{iife(() => {
|
||||
const [store, setStore] = createStore({
|
||||
@@ -208,7 +210,10 @@ export default function () {
|
||||
<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" />
|
||||
<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">
|
||||
@@ -234,7 +239,6 @@ export default function () {
|
||||
"flex flex-col justify-between !overflow-visible [&_[data-slot=session-turn-message-header]]:top-[-32px]",
|
||||
container: "px-4",
|
||||
}}
|
||||
diffComponent={ClientOnlyDiff}
|
||||
/>
|
||||
)}
|
||||
</For>
|
||||
@@ -273,7 +277,9 @@ export default function () {
|
||||
</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() }}>
|
||||
<div
|
||||
classList={{ "hidden w-full flex-1 min-h-0": true, "md:flex": wide(), "lg:flex": !wide() }}
|
||||
>
|
||||
<div
|
||||
classList={{
|
||||
"@container relative shrink-0 pt-14 flex flex-col gap-10 min-h-0 w-full": true,
|
||||
@@ -307,7 +313,6 @@ export default function () {
|
||||
"w-full pb-20 " +
|
||||
(wide() ? "max-w-146 mx-auto px-6" : messages().length > 1 ? "pr-6 pl-18" : "px-6"),
|
||||
}}
|
||||
diffComponent={ClientOnlyDiff}
|
||||
>
|
||||
<div classList={{ "w-full flex items-center justify-center pb-8 shrink-0": true }}>
|
||||
<Logo class="w-58.5 opacity-12" />
|
||||
@@ -316,11 +321,11 @@ export default function () {
|
||||
</div>
|
||||
</div>
|
||||
<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-6",
|
||||
@@ -331,7 +336,6 @@ export default function () {
|
||||
split
|
||||
class="hidden @4xl:flex"
|
||||
diffs={splitDiffs()}
|
||||
diffComponent={Diff}
|
||||
classes={{
|
||||
root: "pb-20",
|
||||
header: "px-6",
|
||||
@@ -339,6 +343,7 @@ export default function () {
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</DiffComponentProvider>
|
||||
</Show>
|
||||
</div>
|
||||
<Switch>
|
||||
@@ -361,15 +366,16 @@ export default function () {
|
||||
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()}
|
||||
diffComponent={Diff}
|
||||
classes={{
|
||||
root: "pb-20",
|
||||
header: "px-4",
|
||||
container: "px-4",
|
||||
}}
|
||||
/>
|
||||
</DiffComponentProvider>
|
||||
</div>
|
||||
</Tabs.Content>
|
||||
</Tabs>
|
||||
@@ -385,6 +391,7 @@ export default function () {
|
||||
)
|
||||
})}
|
||||
</DataProvider>
|
||||
</DiffComponentProvider>
|
||||
)
|
||||
}}
|
||||
</Show>
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}}
|
||||
|
||||
@@ -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={{
|
||||
|
||||
@@ -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()}>
|
||||
|
||||
13
packages/ui/src/context/diff.tsx
Normal file
13
packages/ui/src/context/diff.tsx
Normal file
@@ -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
|
||||
}
|
||||
@@ -1,2 +1,3 @@
|
||||
export * from "./helper"
|
||||
export * from "./data"
|
||||
export * from "./diff"
|
||||
|
||||
Reference in New Issue
Block a user