Browse Source

feat(desktop): show write tool output

Adam 2 months ago
parent
commit
ff690350b1

+ 39 - 35
packages/desktop/src/app.tsx

@@ -5,7 +5,9 @@ import { MetaProvider } from "@solidjs/meta"
 import { Font } from "@opencode-ai/ui/font"
 import { MarkedProvider } from "@opencode-ai/ui/context/marked"
 import { DiffComponentProvider } from "@opencode-ai/ui/context/diff"
+import { CodeComponentProvider } from "@opencode-ai/ui/context/code"
 import { Diff } from "@opencode-ai/ui/diff"
+import { Code } from "@opencode-ai/ui/code"
 import { GlobalSyncProvider } from "@/context/global-sync"
 import { LayoutProvider } from "@/context/layout"
 import { GlobalSDKProvider } from "@/context/global-sdk"
@@ -39,41 +41,43 @@ export function App() {
     <DialogProvider>
       <MarkedProvider>
         <DiffComponentProvider component={Diff}>
-          <GlobalSDKProvider url={url}>
-            <GlobalSyncProvider>
-              <LayoutProvider>
-                <NotificationProvider>
-                  <MetaProvider>
-                    <Font />
-                    <Router
-                      root={(props) => (
-                        <CommandProvider>
-                          <Layout>{props.children}</Layout>
-                        </CommandProvider>
-                      )}
-                    >
-                      <Route path="/" component={Home} />
-                      <Route path="/:dir" component={DirectoryLayout}>
-                        <Route path="/" component={() => <Navigate href="session" />} />
-                        <Route
-                          path="/session/:id?"
-                          component={(p) => (
-                            <Show when={p.params.id || true} keyed>
-                              <TerminalProvider>
-                                <PromptProvider>
-                                  <Session />
-                                </PromptProvider>
-                              </TerminalProvider>
-                            </Show>
-                          )}
-                        />
-                      </Route>
-                    </Router>
-                  </MetaProvider>
-                </NotificationProvider>
-              </LayoutProvider>
-            </GlobalSyncProvider>
-          </GlobalSDKProvider>
+          <CodeComponentProvider component={Code}>
+            <GlobalSDKProvider url={url}>
+              <GlobalSyncProvider>
+                <LayoutProvider>
+                  <NotificationProvider>
+                    <MetaProvider>
+                      <Font />
+                      <Router
+                        root={(props) => (
+                          <CommandProvider>
+                            <Layout>{props.children}</Layout>
+                          </CommandProvider>
+                        )}
+                      >
+                        <Route path="/" component={Home} />
+                        <Route path="/:dir" component={DirectoryLayout}>
+                          <Route path="/" component={() => <Navigate href="session" />} />
+                          <Route
+                            path="/session/:id?"
+                            component={(p) => (
+                              <Show when={p.params.id || true} keyed>
+                                <TerminalProvider>
+                                  <PromptProvider>
+                                    <Session />
+                                  </PromptProvider>
+                                </TerminalProvider>
+                              </Show>
+                            )}
+                          />
+                        </Route>
+                      </Router>
+                    </MetaProvider>
+                  </NotificationProvider>
+                </LayoutProvider>
+              </GlobalSyncProvider>
+            </GlobalSDKProvider>
+          </CodeComponentProvider>
         </DiffComponentProvider>
       </MarkedProvider>
     </DialogProvider>

+ 5 - 2
packages/desktop/src/pages/session.tsx

@@ -1,4 +1,5 @@
 import { For, onCleanup, onMount, Show, Match, Switch, createResource, createMemo, createEffect, on } from "solid-js"
+import { Dynamic } from "solid-js/web"
 import { useLocal, type LocalFile } from "@/context/local"
 import { createStore } from "solid-js/store"
 import { PromptInput } from "@/components/prompt-input"
@@ -11,7 +12,7 @@ import { DiffChanges } from "@opencode-ai/ui/diff-changes"
 import { ProgressCircle } from "@opencode-ai/ui/progress-circle"
 import { ResizeHandle } from "@opencode-ai/ui/resize-handle"
 import { Tabs } from "@opencode-ai/ui/tabs"
-import { Code } from "@opencode-ai/ui/code"
+import { useCodeComponent } from "@opencode-ai/ui/context/code"
 import { SessionTurn } from "@opencode-ai/ui/session-turn"
 import { SessionMessageRail } from "@opencode-ai/ui/session-message-rail"
 import { SessionReview } from "@opencode-ai/ui/session-review"
@@ -48,6 +49,7 @@ export default function Page() {
   const sync = useSync()
   const terminal = useTerminal()
   const dialog = useDialog()
+  const codeComponent = useCodeComponent()
   const command = useCommand()
   const params = useParams()
   const navigate = useNavigate()
@@ -764,7 +766,8 @@ export default function Page() {
                     <Switch>
                       <Match when={file()}>
                         {(f) => (
-                          <Code
+                          <Dynamic
+                            component={codeComponent}
                             file={{
                               name: f().path,
                               contents: f().content?.content ?? "",

+ 226 - 209
packages/enterprise/src/routes/share/[shareID].tsx

@@ -3,6 +3,7 @@ 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 { CodeComponentProvider } from "@opencode-ai/ui/context/code"
 import { createAsync, query, useParams } from "@solidjs/router"
 import { createEffect, createMemo, ErrorBoundary, For, Match, Show, Switch } from "solid-js"
 import { Share } from "~/core/share"
@@ -27,6 +28,7 @@ import { Meta } from "@solidjs/meta"
 import { Base64 } from "js-base64"
 
 const ClientOnlyDiff = clientOnly(() => import("@opencode-ai/ui/diff").then((m) => ({ default: m.Diff })))
+const ClientOnlyCode = clientOnly(() => import("@opencode-ai/ui/code").then((m) => ({ default: m.Code })))
 
 const SessionDataMissingError = NamedError.create(
   "SessionDataMissingError",
@@ -196,239 +198,254 @@ export default function () {
               <Meta property="og:image" content={ogImage()} />
               <Meta name="twitter:image" content={ogImage()} />
               <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) => a.time.created - b.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)
+                <CodeComponentProvider component={ClientOnlyCode}>
+                  <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) => a.time.created - b.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">
-                            <ProviderIcon id={provider() as IconName} class="size-3.5 shrink-0 text-icon-strong-base" />
-                            <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">
+                              <ProviderIcon
+                                id={provider() as IconName}
+                                class="size-3.5 shrink-0 text-icon-strong-base"
+                              />
+                              <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",
-                                }}
-                              />
-                            )}
-                          </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(),
+                                "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,
                                   }}
                                 >
-                                  <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}>
-                              <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()}
-                                    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()}
                                   />
-                                  <SessionReview
-                                    split
-                                    class="hidden @4xl:flex"
-                                    diffs={splitDiffs()}
+                                  <SessionTurn
+                                    sessionID={data().sessionID}
+                                    messageID={store.messageId ?? firstUserMessage()!.id!}
                                     classes={{
-                                      root: "pb-20",
-                                      header: "px-6",
-                                      container: "px-6",
+                                      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>
-                              </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" }}>
-                                    {diffs().length} 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>
+                              <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()}
+                                      classes={{
+                                        root: "pb-20",
+                                        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>
+                                </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" }}
+                                    >
+                                      {diffs().length} 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>
+                </CodeComponentProvider>
               </DiffComponentProvider>
             </>
           )

+ 17 - 14
packages/ui/src/components/message-part.tsx

@@ -11,6 +11,7 @@ import {
 } from "@opencode-ai/sdk/v2"
 import { useData } from "../context"
 import { useDiffComponent } from "../context/diff"
+import { useCodeComponent } from "../context/code"
 import { BasicTool } from "./basic-tool"
 import { GenericTool } from "./basic-tool"
 import { Card } from "./card"
@@ -19,6 +20,7 @@ import { Checkbox } from "./checkbox"
 import { DiffChanges } from "./diff-changes"
 import { Markdown } from "./markdown"
 import { getDirectory as _getDirectory, getFilename } from "@opencode-ai/util/path"
+import { checksum } from "@opencode-ai/util/encode"
 
 export interface MessageProps {
   message: MessageType
@@ -488,9 +490,10 @@ ToolRegistry.register({
 ToolRegistry.register({
   name: "write",
   render(props) {
-    console.log(props)
+    const codeComponent = useCodeComponent()
     return (
       <BasicTool
+        defaultOpen
         icon="code-lines"
         trigger={
           <div data-component="write-trigger">
@@ -507,19 +510,19 @@ ToolRegistry.register({
           </div>
         }
       >
-        {/* <Show when={props.input.content}> */}
-        {/*   <div data-component="write-content"> */}
-        {/*     <Code */}
-        {/*       file={{ */}
-        {/*         name: props.input.filePath, */}
-        {/*         contents: props.input.content, */}
-        {/*         cacheKey: checksum(props.input.content), */}
-        {/*       }} */}
-        {/*       overflow="scroll" */}
-        {/*       class="pb-40" */}
-        {/*     /> */}
-        {/*   </div> */}
-        {/* </Show> */}
+        <Show when={props.input.content}>
+          <div data-component="write-content">
+            <Dynamic
+              component={codeComponent}
+              file={{
+                name: props.input.filePath,
+                contents: props.input.content,
+                cacheKey: checksum(props.input.content),
+              }}
+              overflow="scroll"
+            />
+          </div>
+        </Show>
       </BasicTool>
     )
   },

+ 10 - 0
packages/ui/src/context/code.tsx

@@ -0,0 +1,10 @@
+import type { ValidComponent } from "solid-js"
+import { createSimpleContext } from "./helper"
+
+const ctx = createSimpleContext<ValidComponent, { component: ValidComponent }>({
+  name: "CodeComponent",
+  init: (props) => props.component,
+})
+
+export const CodeComponentProvider = ctx.provider
+export const useCodeComponent = ctx.use

+ 8 - 11
packages/ui/src/context/diff.tsx

@@ -1,13 +1,10 @@
-import { createContext, useContext, type ParentProps, type ValidComponent } from "solid-js"
+import type { ValidComponent } from "solid-js"
+import { createSimpleContext } from "./helper"
 
-const DiffComponentContext = createContext<ValidComponent>()
+const ctx = createSimpleContext<ValidComponent, { component: ValidComponent }>({
+  name: "DiffComponent",
+  init: (props) => props.component,
+})
 
-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
-}
+export const DiffComponentProvider = ctx.provider
+export const useDiffComponent = ctx.use