瀏覽代碼

wip(desktop): progress

Adam 2 月之前
父節點
當前提交
b9fa7d9163

+ 22 - 11
packages/desktop/src/context/layout.tsx

@@ -12,7 +12,7 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
     const globalSync = useGlobalSync()
     const [store, setStore] = makePersisted(
       createStore({
-        projects: [] as { directory: string; expanded: boolean }[],
+        projects: [] as { worktree: string; expanded: boolean }[],
         sidebar: {
           opened: false,
           width: 280,
@@ -26,7 +26,7 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
         },
       }),
       {
-        name: "default-layout.v4",
+        name: "default-layout.v6",
       },
     )
 
@@ -43,32 +43,43 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
 
     onMount(() => {
       Promise.all(
-        store.projects.map(({ directory }) => {
-          return loadProjectSessions(directory)
+        store.projects.map(({ worktree }) => {
+          return loadProjectSessions(worktree)
         }),
       )
     })
 
+    function enrich(project: { worktree: string; expanded: boolean }) {
+      const metadata = globalSync.data.projects.find((x) => x.worktree === project.worktree)
+      if (!metadata) return []
+      return [
+        {
+          ...project,
+          ...metadata,
+        },
+      ]
+    }
+
     return {
       projects: {
-        list: createMemo(() => store.projects),
+        list: createMemo(() => store.projects.flatMap(enrich)),
         open(directory: string) {
-          if (store.projects.find((x) => x.directory === directory)) return
+          if (store.projects.find((x) => x.worktree === directory)) return
           loadProjectSessions(directory)
-          setStore("projects", (x) => [...x, { directory, expanded: true }])
+          setStore("projects", (x) => [...x, { worktree: directory, expanded: true }])
         },
         close(directory: string) {
-          setStore("projects", (x) => x.filter((x) => x.directory !== directory))
+          setStore("projects", (x) => x.filter((x) => x.worktree !== directory))
         },
         expand(directory: string) {
-          setStore("projects", (x) => x.map((x) => (x.directory === directory ? { ...x, expanded: true } : x)))
+          setStore("projects", (x) => x.map((x) => (x.worktree === directory ? { ...x, expanded: true } : x)))
         },
         collapse(directory: string) {
-          setStore("projects", (x) => x.map((x) => (x.directory === directory ? { ...x, expanded: false } : x)))
+          setStore("projects", (x) => x.map((x) => (x.worktree === directory ? { ...x, expanded: false } : x)))
         },
         move(directory: string, toIndex: number) {
           setStore("projects", (projects) => {
-            const fromIndex = projects.findIndex((x) => x.directory === directory)
+            const fromIndex = projects.findIndex((x) => x.worktree === directory)
             if (fromIndex === -1 || fromIndex === toIndex) return projects
             const result = [...projects]
             const [item] = result.splice(fromIndex, 1)

+ 25 - 18
packages/desktop/src/pages/layout.tsx

@@ -16,7 +16,7 @@ import { DiffChanges } from "@opencode-ai/ui/diff-changes"
 import { getFilename } from "@opencode-ai/util/path"
 import { Select } from "@opencode-ai/ui/select"
 import { DropdownMenu } from "@opencode-ai/ui/dropdown-menu"
-import { Session } from "@opencode-ai/sdk/v2/client"
+import { Session, Project } from "@opencode-ai/sdk/v2/client"
 import { usePlatform } from "@/context/platform"
 import { createStore } from "solid-js/store"
 import {
@@ -106,8 +106,8 @@ export default function Layout(props: ParentProps) {
     const { draggable, droppable } = event
     if (draggable && droppable) {
       const projects = layout.projects.list()
-      const fromIndex = projects.findIndex((p) => p.directory === draggable.id.toString())
-      const toIndex = projects.findIndex((p) => p.directory === droppable.id.toString())
+      const fromIndex = projects.findIndex((p) => p.worktree === draggable.id.toString())
+      const toIndex = projects.findIndex((p) => p.worktree === droppable.id.toString())
       if (fromIndex !== toIndex && toIndex !== -1) {
         layout.projects.move(draggable.id.toString(), toIndex)
       }
@@ -176,11 +176,11 @@ export default function Layout(props: ParentProps) {
     )
   }
 
-  const SortableProject = (props: { project: { directory: string; expanded: boolean } }): JSX.Element => {
-    const sortable = createSortable(props.project.directory)
-    const [projectStore] = globalSync.child(props.project.directory)
-    const slug = createMemo(() => base64Encode(props.project.directory))
-    const name = createMemo(() => getFilename(props.project.directory))
+  const SortableProject = (props: { project: Project & { expanded: boolean } }): JSX.Element => {
+    const sortable = createSortable(props.project.worktree)
+    const [projectStore] = globalSync.child(props.project.worktree)
+    const slug = createMemo(() => base64Encode(props.project.worktree))
+    const name = createMemo(() => getFilename(props.project.worktree))
     return (
       // @ts-ignore
       <div use:sortable classList={{ "opacity-30": sortable.isActiveDraggable }}>
@@ -194,11 +194,18 @@ export default function Layout(props: ParentProps) {
               >
                 <Collapsible.Trigger class="group/trigger flex items-center gap-3 p-0 text-left min-w-0 grow border-none">
                   <div class="size-6 shrink-0">
-                    <Avatar
-                      fallback={name()}
-                      background="var(--surface-info-base)"
-                      class="size-full group-hover/session:hidden"
-                    />
+                    <Switch>
+                      <Match when={props.project.icon?.url}>
+                        {(url) => <img src={url()} class="size-full group-hover/session:hidden" />}
+                      </Match>
+                      <Match when={true}>
+                        <Avatar
+                          fallback={name()}
+                          background={props.project.icon?.color ?? "var(--surface-info-base)"}
+                          class="size-full group-hover/session:hidden"
+                        />
+                      </Match>
+                    </Switch>
                     <Icon
                       name="chevron-right"
                       size="large"
@@ -212,7 +219,7 @@ export default function Layout(props: ParentProps) {
                     <DropdownMenu.Trigger as={IconButton} icon="dot-grid" variant="ghost" />
                     <DropdownMenu.Portal>
                       <DropdownMenu.Content>
-                        <DropdownMenu.Item onSelect={() => closeProject(props.project.directory)}>
+                        <DropdownMenu.Item onSelect={() => closeProject(props.project.worktree)}>
                           <DropdownMenu.ItemLabel>Close Project</DropdownMenu.ItemLabel>
                         </DropdownMenu.Item>
                       </DropdownMenu.Content>
@@ -274,8 +281,8 @@ export default function Layout(props: ParentProps) {
             </Collapsible>
           </Match>
           <Match when={true}>
-            <Tooltip placement="right" value={props.project.directory}>
-              <ProjectVisual directory={props.project.directory} />
+            <Tooltip placement="right" value={props.project.worktree}>
+              <ProjectVisual directory={props.project.worktree} />
             </Tooltip>
           </Match>
         </Switch>
@@ -315,7 +322,7 @@ export default function Layout(props: ParentProps) {
             <div class="flex items-center gap-3">
               <div class="flex items-center gap-2">
                 <Select
-                  options={layout.projects.list().map((project) => project.directory)}
+                  options={layout.projects.list().map((project) => project.worktree)}
                   current={currentDirectory()}
                   label={(x) => getFilename(x)}
                   onSelect={(x) => (x ? navigateToProject(x) : undefined)}
@@ -443,7 +450,7 @@ export default function Layout(props: ParentProps) {
               <DragDropSensors />
               <ConstrainDragXAxis />
               <div class="w-full min-w-8 flex flex-col gap-2 min-h-0 overflow-y-auto no-scrollbar">
-                <SortableProvider ids={layout.projects.list().map((p) => p.directory)}>
+                <SortableProvider ids={layout.projects.list().map((p) => p.worktree)}>
                   <For each={layout.projects.list()}>{(project) => <SortableProject project={project} />}</For>
                 </SortableProvider>
               </div>

+ 1 - 1
packages/enterprise/src/core/share.ts

@@ -1,4 +1,4 @@
-import { FileDiff, Message, Model, Part, Session, SessionStatus } from "@opencode-ai/sdk"
+import { FileDiff, Message, Model, Part, Session } from "@opencode-ai/sdk/v2"
 import { fn } from "@opencode-ai/util/fn"
 import { iife } from "@opencode-ai/util/iife"
 import { Identifier } from "@opencode-ai/util/identifier"

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

@@ -1,4 +1,4 @@
-import { FileDiff, Message, Model, Part, Session, SessionStatus, UserMessage } from "@opencode-ai/sdk"
+import { FileDiff, Message, Model, Part, Session, SessionStatus, UserMessage } from "@opencode-ai/sdk/v2"
 import { SessionTurn } from "@opencode-ai/ui/session-turn"
 import { SessionReview } from "@opencode-ai/ui/session-review"
 import { DataProvider } from "@opencode-ai/ui/context"

+ 1 - 1
packages/ui/src/components/message-nav.tsx

@@ -1,4 +1,4 @@
-import { UserMessage } from "@opencode-ai/sdk"
+import { UserMessage } from "@opencode-ai/sdk/v2"
 import { ComponentProps, createMemo, For, Match, Show, splitProps, Switch } from "solid-js"
 import { DiffChanges } from "./diff-changes"
 import { Spinner } from "./spinner"

+ 1 - 1
packages/ui/src/components/message-part.tsx

@@ -7,7 +7,7 @@ import {
   TextPart,
   ToolPart,
   UserMessage,
-} from "@opencode-ai/sdk"
+} from "@opencode-ai/sdk/v2"
 import { useDiffComponent } from "../context/diff"
 import { BasicTool } from "./basic-tool"
 import { GenericTool } from "./basic-tool"

+ 1 - 1
packages/ui/src/components/message-progress.tsx

@@ -2,7 +2,7 @@ import { For, JSXElement, Match, Show, Switch, createEffect, createMemo, createS
 import { Part } from "./message-part"
 import { Spinner } from "./spinner"
 import { useData } from "../context/data"
-import type { AssistantMessage as AssistantMessageType, ToolPart } from "@opencode-ai/sdk"
+import type { AssistantMessage as AssistantMessageType, ToolPart } from "@opencode-ai/sdk/v2"
 
 export interface MessageProgressProps {
   assistantMessages: () => AssistantMessageType[]

+ 1 - 1
packages/ui/src/components/session-message-rail.tsx

@@ -1,4 +1,4 @@
-import { UserMessage } from "@opencode-ai/sdk"
+import { UserMessage } from "@opencode-ai/sdk/v2"
 import { ComponentProps, Show, splitProps } from "solid-js"
 import { MessageNav } from "./message-nav"
 import "./session-message-rail.css"

+ 1 - 1
packages/ui/src/components/session-review.tsx

@@ -8,7 +8,7 @@ import { useDiffComponent } from "../context/diff"
 import { getDirectory, getFilename } from "@opencode-ai/util/path"
 import { For, Match, Show, Switch, type JSX } from "solid-js"
 import { createStore } from "solid-js/store"
-import { type FileDiff } from "@opencode-ai/sdk"
+import { type FileDiff } from "@opencode-ai/sdk/v2"
 import { PreloadMultiFileDiffResult } from "@pierre/precision-diffs/ssr"
 import { Dynamic } from "solid-js/web"
 import { checksum } from "@opencode-ai/util/encode"

+ 1 - 1
packages/ui/src/components/session-turn.tsx

@@ -1,4 +1,4 @@
-import { AssistantMessage } from "@opencode-ai/sdk"
+import { AssistantMessage } from "@opencode-ai/sdk/v2"
 import { useData } from "../context"
 import { useDiffComponent } from "../context/diff"
 import { getDirectory, getFilename } from "@opencode-ai/util/path"

+ 1 - 1
packages/ui/src/context/data.tsx

@@ -1,4 +1,4 @@
-import type { Message, Session, Part, FileDiff, SessionStatus } from "@opencode-ai/sdk"
+import type { Message, Session, Part, FileDiff, SessionStatus } from "@opencode-ai/sdk/v2"
 import { createSimpleContext } from "./helper"
 import { PreloadMultiFileDiffResult } from "@pierre/precision-diffs/ssr"
 

+ 1 - 1
packages/util/src/sanitize.ts

@@ -1,4 +1,4 @@
-import type { Part } from "@opencode-ai/sdk/client"
+import type { Part } from "@opencode-ai/sdk/v2/client"
 
 export const sanitize = (text: string | undefined, remove?: RegExp) => (remove ? text?.replace(remove, "") : text) ?? ""