diff options
| author | Adam <[email protected]> | 2025-12-12 07:07:54 -0600 |
|---|---|---|
| committer | Adam <[email protected]> | 2025-12-12 07:07:54 -0600 |
| commit | feb8c4f3c60e1ada28bd24abd09d534375a6bc08 (patch) | |
| tree | fc55e3bfe53c2707944a12c234e54287e7b1ff2f /packages/desktop/src | |
| parent | 3f5cd2c4a810168ed03094cbf1d3e51a4b62f261 (diff) | |
| download | opencode-feb8c4f3c60e1ada28bd24abd09d534375a6bc08.tar.gz opencode-feb8c4f3c60e1ada28bd24abd09d534375a6bc08.zip | |
feat(desktop): archive sessions
Diffstat (limited to 'packages/desktop/src')
| -rw-r--r-- | packages/desktop/src/context/global-sync.tsx | 31 | ||||
| -rw-r--r-- | packages/desktop/src/context/layout.tsx | 15 | ||||
| -rw-r--r-- | packages/desktop/src/context/sync.tsx | 9 | ||||
| -rw-r--r-- | packages/desktop/src/pages/layout.tsx | 106 |
4 files changed, 107 insertions, 54 deletions
diff --git a/packages/desktop/src/context/global-sync.tsx b/packages/desktop/src/context/global-sync.tsx index 39e0fd34a..4c461a4e9 100644 --- a/packages/desktop/src/context/global-sync.tsx +++ b/packages/desktop/src/context/global-sync.tsx @@ -69,8 +69,19 @@ export const { use: useGlobalSync, provider: GlobalSyncProvider } = createSimple children: {}, }) + async function loadSessions(directory: string) { + globalSDK.client.session.list({ directory }).then((x) => { + const sessions = (x.data ?? []) + .slice() + .filter((s) => !s.time.archived) + .sort((a, b) => a.id.localeCompare(b.id)) + .slice(0, 5) + setGlobalStore("children", directory, "session", sessions) + }) + } + async function bootstrapInstance(directory: string) { - const [store, setStore] = child(directory) + const [, setStore] = child(directory) const sdk = createOpencodeClient({ baseUrl: globalSDK.url, directory, @@ -80,14 +91,7 @@ export const { use: useGlobalSync, provider: GlobalSyncProvider } = createSimple provider: () => sdk.provider.list().then((x) => setStore("provider", x.data!)), path: () => sdk.path.get().then((x) => setStore("path", x.data!)), agent: () => sdk.app.agents().then((x) => setStore("agent", x.data ?? [])), - session: () => - sdk.session.list().then((x) => { - const sessions = (x.data ?? []) - .slice() - .sort((a, b) => a.id.localeCompare(b.id)) - .slice(0, store.limit) - setStore("session", sessions) - }), + session: () => loadSessions(directory), status: () => sdk.session.status().then((x) => setStore("session_status", x.data!)), config: () => sdk.config.get().then((x) => setStore("config", x.data!)), changes: () => sdk.file.status().then((x) => setStore("changes", x.data!)), @@ -158,6 +162,12 @@ export const { use: useGlobalSync, provider: GlobalSyncProvider } = createSimple } case "session.updated": { const result = Binary.search(store.session, event.properties.info.id, (s) => s.id) + if (event.properties.info.time.archived) { + if (result.found) { + setStore("session", (s) => s.filter((x) => x.id !== event.properties.info.id)) + } + break + } if (result.found) { setStore("session", result.index, reconcile(event.properties.info)) break @@ -257,6 +267,9 @@ export const { use: useGlobalSync, provider: GlobalSyncProvider } = createSimple }, child, bootstrap, + project: { + loadSessions, + }, } }, }) diff --git a/packages/desktop/src/context/layout.tsx b/packages/desktop/src/context/layout.tsx index 4ec0af601..3d5cad761 100644 --- a/packages/desktop/src/context/layout.tsx +++ b/packages/desktop/src/context/layout.tsx @@ -92,21 +92,10 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext( const enriched = createMemo(() => store.projects.flatMap(enrich)) const list = createMemo(() => enriched().flatMap(colorize)) - async function loadProjectSessions(directory: string) { - const [, setStore] = globalSync.child(directory) - globalSdk.client.session.list({ directory }).then((x) => { - const sessions = (x.data ?? []) - .slice() - .sort((a, b) => a.id.localeCompare(b.id)) - .slice(0, 5) - setStore("session", sessions) - }) - } - onMount(() => { Promise.all( store.projects.map((project) => { - return loadProjectSessions(project.worktree) + return globalSync.project.loadSessions(project.worktree) }), ) }) @@ -116,7 +105,7 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext( list, open(directory: string) { if (store.projects.find((x) => x.worktree === directory)) return - loadProjectSessions(directory) + globalSync.project.loadSessions(directory) setStore("projects", (x) => [{ worktree: directory, expanded: true }, ...x]) }, close(directory: string) { diff --git a/packages/desktop/src/context/sync.tsx b/packages/desktop/src/context/sync.tsx index 85758c5b6..2ab54b3ae 100644 --- a/packages/desktop/src/context/sync.tsx +++ b/packages/desktop/src/context/sync.tsx @@ -65,6 +65,15 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({ }) }, more: createMemo(() => store.session.length >= store.limit), + archive: async (sessionID: string) => { + await sdk.client.session.update({ sessionID, time: { archived: Date.now() } }) + setStore( + produce((draft) => { + const match = Binary.search(draft.session, sessionID, (s) => s.id) + if (match.found) draft.session.splice(match.index, 1) + }), + ) + }, }, absolute, get directory() { diff --git a/packages/desktop/src/pages/layout.tsx b/packages/desktop/src/pages/layout.tsx index e7ae83162..c34904e11 100644 --- a/packages/desktop/src/pages/layout.tsx +++ b/packages/desktop/src/pages/layout.tsx @@ -55,6 +55,7 @@ import { showToast, Toast } from "@opencode-ai/ui/toast" import { useGlobalSDK } from "@/context/global-sdk" import { Spinner } from "@opencode-ai/ui/spinner" import { useNotification } from "@/context/notification" +import { Binary } from "@opencode-ai/util/binary" export default function Layout(props: ParentProps) { const [store, setStore] = createStore({ @@ -258,7 +259,8 @@ export default function Layout(props: ParentProps) { const SortableProject = (props: { project: Project & { expanded: boolean } }): JSX.Element => { const notification = useNotification() const sortable = createSortable(props.project.worktree) - const [projectStore] = globalSync.child(props.project.worktree) + const [projectStore, setProjectStore] = globalSync.child(props.project.worktree) + const sessions = createMemo(() => projectStore.session.filter((s) => !s.time.archived)) const slug = createMemo(() => base64Encode(props.project.worktree)) const name = createMemo(() => getFilename(props.project.worktree)) const [expanded, setExpanded] = createSignal(true) @@ -300,21 +302,33 @@ export default function Layout(props: ParentProps) { </Button> <Collapsible.Content> <nav class="hidden @[4rem]:flex w-full flex-col gap-1.5"> - <For each={projectStore.session}> + <For each={sessions()}> {(session) => { const updated = createMemo(() => DateTime.fromMillis(session.time.updated)) const notifications = createMemo(() => notification.session.unseen(session.id)) const hasError = createMemo(() => notifications().some((n) => n.type === "error")) + async function archive(session: Session) { + await globalSDK.client.session.update({ + directory: session.directory, + sessionID: session.id, + time: { archived: Date.now() }, + }) + setProjectStore( + produce((draft) => { + const match = Binary.search(draft.session, session.id, (s) => s.id) + if (match.found) draft.session.splice(match.index, 1) + }), + ) + } return ( <A - data-active={session.id === params.id} href={`${slug()}/session/${session.id}`} class="group/session focus:outline-none cursor-default" > <Tooltip placement="right" value={session.title}> <div - class="relative w-full pl-4 pr-2 py-1 rounded-md - group-data-[active=true]/session:bg-surface-raised-base-hover + class="relative w-full pl-4 pr-1 py-1 rounded-md + group-[.active]/session:bg-surface-raised-base-hover group-hover/session:bg-surface-raised-base-hover group-focus/session:bg-surface-raised-base-hover" > @@ -322,40 +336,68 @@ export default function Layout(props: ParentProps) { <span class="text-14-regular text-text-strong overflow-hidden text-ellipsis truncate"> {session.title} </span> - <Switch> - <Match when={hasError()}> - <div class="size-1.5 shrink-0 mr-1 rounded-full bg-text-diff-delete-base" /> - </Match> - <Match when={notifications().length > 0}> - <div class="size-1.5 shrink-0 mr-1 rounded-full bg-text-interactive-base" /> - </Match> - <Match when={true}> - <span class="text-12-regular text-text-weak text-right whitespace-nowrap"> - {Math.abs(updated().diffNow().as("seconds")) < 60 - ? "Now" - : updated() - .toRelative({ - style: "short", - unit: ["days", "hours", "minutes"], - }) - ?.replace(" ago", "") - ?.replace(/ days?/, "d") - ?.replace(" min.", "m") - ?.replace(" hr.", "h")} - </span> - </Match> - </Switch> - </div> - <div class="hidden _flex justify-between items-center self-stretch"> - <span class="text-12-regular text-text-weak">{`${session.summary?.files || "No"} file${session.summary?.files !== 1 ? "s" : ""} changed`}</span> - <Show when={session.summary}>{(summary) => <DiffChanges changes={summary()} />}</Show> + <div class="shrink-0 group-hover/session:hidden mr-1"> + <Switch> + <Match when={hasError()}> + <div class="size-1.5 mr-1.5 rounded-full bg-text-diff-delete-base" /> + </Match> + <Match when={notifications().length > 0}> + <div class="size-1.5 mr-1.5 rounded-full bg-text-interactive-base" /> + </Match> + <Match when={true}> + <span class="text-12-regular text-text-weak text-right whitespace-nowrap"> + {Math.abs(updated().diffNow().as("seconds")) < 60 + ? "Now" + : updated() + .toRelative({ + style: "short", + unit: ["days", "hours", "minutes"], + }) + ?.replace(" ago", "") + ?.replace(/ days?/, "d") + ?.replace(" min.", "m") + ?.replace(" hr.", "h")} + </span> + </Match> + </Switch> + </div> + <div class="hidden group-hover/session:flex group-active/session:flex text-text-base gap-1"> + {/* <IconButton icon="dot-grid" variant="ghost" /> */} + <Tooltip placement="right" value="Archive session"> + <IconButton icon="archive" variant="ghost" onClick={() => archive(session)} /> + </Tooltip> + </div> </div> + <Show when={session.summary?.files}> + <div class="flex justify-between items-center self-stretch"> + <span class="text-12-regular text-text-weak">{`${session.summary?.files || "No"} file${session.summary?.files !== 1 ? "s" : ""} changed`}</span> + <Show when={session.summary}>{(summary) => <DiffChanges changes={summary()} />}</Show> + </div> + </Show> </div> </Tooltip> </A> ) }} </For> + <Show when={sessions().length === 0}> + <A href={`${slug()}/session`} class="group/session focus:outline-none cursor-default"> + <Tooltip placement="right" value="New session"> + <div + class="relative w-full pl-4 pr-1 py-1 rounded-md + group-[.active]/session:bg-surface-raised-base-hover + group-hover/session:bg-surface-raised-base-hover + group-focus/session:bg-surface-raised-base-hover" + > + <div class="flex items-center self-stretch gap-6 justify-between"> + <span class="text-14-regular text-text-strong overflow-hidden text-ellipsis truncate"> + New session + </span> + </div> + </div> + </Tooltip> + </A> + </Show> </nav> </Collapsible.Content> </Collapsible> |
