Adam 3 месяцев назад
Родитель
Сommit
4f604b3839
2 измененных файлов с 101 добавлено и 31 удалено
  1. 95 30
      packages/desktop/src/context/session.tsx
  2. 6 1
      packages/ui/src/components/diff.tsx

+ 95 - 30
packages/desktop/src/context/session.tsx

@@ -1,6 +1,6 @@
 import { createStore, produce } from "solid-js/store"
 import { createSimpleContext } from "./helper"
-import { batch, createEffect, createMemo } from "solid-js"
+import { batch, createEffect, createMemo, createSignal, on } from "solid-js"
 import { useSync } from "./sync"
 import { makePersisted } from "@solid-primitives/storage"
 import { TextSelection, useLocal } from "./local"
@@ -13,26 +13,74 @@ export const { use: useSession, provider: SessionProvider } = createSimpleContex
     const sync = useSync()
     const local = useLocal()
 
-    const [store, setStore] = makePersisted(
+    const seed = props.sessionId ?? "new-session"
+
+    const [persist, setPersist] = makePersisted(
       createStore<{
-        prompt: Prompt
-        cursorPosition?: number
         messageId?: string
         tabs: {
           active?: string
           opened: string[]
         }
       }>({
-        prompt: [{ type: "text", content: "", start: 0, end: 0 }],
         tabs: {
           opened: [],
         },
       }),
       {
-        name: props.sessionId ?? "new-session",
+        name: seed,
       },
     )
 
+    const [promptStore, setPromptStore] = createStore<{
+      prompt: Prompt
+      cursor?: number
+    }>({
+      prompt: clonePrompt(DEFAULT_PROMPT),
+    })
+
+    const key = createMemo(() => props.sessionId ?? "new-session")
+    const [ready, setReady] = createSignal(false)
+    const prefix = "session-prompt:"
+
+    createEffect(
+      on(
+        key,
+        (value) => {
+          setReady(false)
+          const record = localStorage.getItem(prefix + value)
+          if (!record) {
+            setPromptStore("prompt", clonePrompt(DEFAULT_PROMPT))
+            setPromptStore("cursor", undefined)
+            setReady(true)
+            return
+          }
+          const payload = JSON.parse(record) as { prompt?: Prompt; cursor?: number }
+          const parts = payload.prompt ?? DEFAULT_PROMPT
+          const cursor = typeof payload.cursor === "number" ? payload.cursor : undefined
+          setPromptStore("prompt", clonePrompt(parts))
+          setPromptStore("cursor", cursor)
+          setReady(true)
+        },
+        { defer: true },
+      ),
+    )
+
+    createEffect(() => {
+      if (!ready()) return
+      const value = key()
+      const isDefault = isPromptEqual(promptStore.prompt, DEFAULT_PROMPT)
+      if (isDefault && (promptStore.cursor === undefined || promptStore.cursor <= 0)) {
+        localStorage.removeItem(prefix + value)
+        return
+      }
+      const next = JSON.stringify({
+        prompt: clonePrompt(promptStore.prompt),
+        cursor: promptStore.cursor,
+      })
+      localStorage.setItem(prefix + value, next)
+    })
+
     createEffect(() => {
       if (!props.sessionId) return
       sync.session.sync(props.sessionId)
@@ -49,8 +97,8 @@ export const { use: useSession, provider: SessionProvider } = createSimpleContex
       return userMessages()?.at(0)
     })
     const activeMessage = createMemo(() => {
-      if (!store.messageId) return lastUserMessage()
-      return userMessages()?.find((m) => m.id === store.messageId)
+      if (!persist.messageId) return lastUserMessage()
+      return userMessages()?.find((m) => m.id === persist.messageId)
     })
     const working = createMemo(() => {
       if (!props.sessionId) return false
@@ -101,13 +149,14 @@ export const { use: useSession, provider: SessionProvider } = createSimpleContex
       working,
       diffs,
       prompt: {
-        current: createMemo(() => store.prompt),
-        cursor: createMemo(() => store.cursorPosition),
-        dirty: createMemo(() => !isPromptEqual(store.prompt, DEFAULT_PROMPT)),
+        current: createMemo(() => promptStore.prompt),
+        cursor: createMemo(() => promptStore.cursor),
+        dirty: createMemo(() => !isPromptEqual(promptStore.prompt, DEFAULT_PROMPT)),
         set(prompt: Prompt, cursorPosition?: number) {
+          const next = clonePrompt(prompt)
           batch(() => {
-            setStore("prompt", prompt)
-            if (cursorPosition !== undefined) setStore("cursorPosition", cursorPosition)
+            setPromptStore("prompt", next)
+            if (cursorPosition !== undefined) setPromptStore("cursor", cursorPosition)
           })
         },
       },
@@ -117,7 +166,7 @@ export const { use: useSession, provider: SessionProvider } = createSimpleContex
         last: lastUserMessage,
         active: activeMessage,
         setActive(id: string | undefined) {
-          setStore("messageId", id)
+          setPersist("messageId", id)
         },
       },
       usage: {
@@ -126,53 +175,52 @@ export const { use: useSession, provider: SessionProvider } = createSimpleContex
         context,
       },
       layout: {
-        tabs: store.tabs,
+        tabs: persist.tabs,
         setActiveTab(tab: string | undefined) {
-          setStore("tabs", "active", tab)
+          setPersist("tabs", "active", tab)
         },
         setOpenedTabs(tabs: string[]) {
-          setStore("tabs", "opened", tabs)
+          setPersist("tabs", "opened", tabs)
         },
         async openTab(tab: string) {
           if (tab === "chat") {
-            setStore("tabs", "active", undefined)
+            setPersist("tabs", "active", undefined)
             return
           }
           if (tab.startsWith("file://")) {
             await local.file.open(tab.replace("file://", ""))
           }
           if (tab !== "review") {
-            if (!store.tabs.opened.includes(tab)) {
-              setStore("tabs", "opened", [...store.tabs.opened, tab])
+            if (!persist.tabs.opened.includes(tab)) {
+              setPersist("tabs", "opened", [...persist.tabs.opened, tab])
             }
           }
-          setStore("tabs", "active", tab)
+          setPersist("tabs", "active", tab)
         },
         closeTab(tab: string) {
           batch(() => {
-            setStore(
+            setPersist(
               "tabs",
               "opened",
-              store.tabs.opened.filter((x) => x !== tab),
+              persist.tabs.opened.filter((x) => x !== tab),
             )
-            if (store.tabs.active === tab) {
-              const index = store.tabs.opened.findIndex((f) => f === tab)
-              const previous = store.tabs.opened[Math.max(0, index - 1)]
-              setStore("tabs", "active", previous)
+            if (persist.tabs.active === tab) {
+              const index = persist.tabs.opened.findIndex((f) => f === tab)
+              const previous = persist.tabs.opened[Math.max(0, index - 1)]
+              setPersist("tabs", "active", previous)
             }
           })
         },
         moveTab(tab: string, to: number) {
-          const index = store.tabs.opened.findIndex((f) => f === tab)
+          const index = persist.tabs.opened.findIndex((f) => f === tab)
           if (index === -1) return
-          setStore(
+          setPersist(
             "tabs",
             "opened",
             produce((opened) => {
               opened.splice(to, 0, opened.splice(index, 1)[0])
             }),
           )
-          // setStore("node", path, "pinned", true)
         },
       },
     }
@@ -215,3 +263,20 @@ export function isPromptEqual(promptA: Prompt, promptB: Prompt): boolean {
   }
   return true
 }
+
+function cloneSelection(selection?: TextSelection) {
+  if (!selection) return undefined
+  return { ...selection }
+}
+
+function clonePart(part: ContentPart): ContentPart {
+  if (part.type === "text") return { ...part }
+  return {
+    ...part,
+    selection: cloneSelection(part.selection),
+  }
+}
+
+function clonePrompt(prompt: Prompt): Prompt {
+  return prompt.map(clonePart)
+}

+ 6 - 1
packages/ui/src/components/diff.tsx

@@ -231,6 +231,12 @@ registerCustomTheme("OpenCode", () => {
           foreground: "var(--syntax-type)",
         },
       },
+      {
+        scope: ["meta.object.member"],
+        settings: {
+          foreground: "var(--syntax-primitive)",
+        },
+      },
       {
         scope: [
           "variable.parameter.function",
@@ -238,7 +244,6 @@ registerCustomTheme("OpenCode", () => {
           "meta.block",
           "meta.tag.attributes",
           "entity.name.constant",
-          "meta.object.member",
           "meta.embedded.expression",
           "meta.template.expression",
           "string.other.begin.yaml",