From d66d806700de9ad9fb0f3997a076ad23a815e6ea Mon Sep 17 00:00:00 2001 From: Adam <2363879+adamdotdevin@users.noreply.github.com> Date: Mon, 15 Dec 2025 04:37:14 -0600 Subject: wip(desktop): progress --- packages/desktop/src/pages/session.tsx | 184 ++++++++++++++++++++++----------- 1 file changed, 124 insertions(+), 60 deletions(-) (limited to 'packages/desktop/src/pages') diff --git a/packages/desktop/src/pages/session.tsx b/packages/desktop/src/pages/session.tsx index e3cac4842..48e01239c 100644 --- a/packages/desktop/src/pages/session.tsx +++ b/packages/desktop/src/pages/session.tsx @@ -27,22 +27,91 @@ import { import type { DragEvent, Transformer } from "@thisbeyond/solid-dnd" import type { JSX } from "solid-js" import { useSync } from "@/context/sync" -import { useSession, type LocalPTY } from "@/context/session" +import { useTerminal, type LocalPTY } from "@/context/terminal" import { useLayout } from "@/context/layout" +import { usePrompt } from "@/context/prompt" import { getDirectory, getFilename } from "@opencode-ai/util/path" import { Terminal } from "@/components/terminal" import { checksum } from "@opencode-ai/util/encode" import { useDialog } from "@opencode-ai/ui/context/dialog" import { DialogSelectFile } from "@/components/dialog-select-file" import { useCommand } from "@/context/command" +import { useParams } from "@solidjs/router" +import { pipe, sumBy } from "remeda" +import { AssistantMessage, UserMessage } from "@opencode-ai/sdk/v2" export default function Page() { const layout = useLayout() const local = useLocal() const sync = useSync() - const session = useSession() + const terminal = useTerminal() + const prompt = usePrompt() const dialog = useDialog() const command = useCommand() + const params = useParams() + + // Session-specific derived state + const sessionKey = createMemo(() => `${params.dir}${params.id ? "/" + params.id : ""}`) + const tabs = createMemo(() => layout.tabs(sessionKey())) + + const info = createMemo(() => (params.id ? sync.session.get(params.id) : undefined)) + const messages = createMemo(() => (params.id ? (sync.data.message[params.id] ?? []) : [])) + const userMessages = createMemo(() => + messages() + .filter((m) => m.role === "user") + .sort((a, b) => a.id.localeCompare(b.id)), + ) + const lastUserMessage = createMemo(() => userMessages()?.at(-1)) + + const [messageStore, setMessageStore] = createStore<{ messageId?: string }>({}) + const activeMessage = createMemo(() => { + if (!messageStore.messageId) return lastUserMessage() + return userMessages()?.find((m) => m.id === messageStore.messageId) + }) + const setActiveMessage = (message: UserMessage | undefined) => { + setMessageStore("messageId", message?.id) + } + + const status = createMemo( + () => + sync.data.session_status[params.id ?? ""] ?? { + type: "idle", + }, + ) + const working = createMemo(() => status()?.type !== "idle") + + const cost = createMemo(() => { + const total = pipe( + messages(), + sumBy((x) => (x.role === "assistant" ? x.cost : 0)), + ) + return new Intl.NumberFormat("en-US", { + style: "currency", + currency: "USD", + }).format(total) + }) + + const last = createMemo( + () => messages().findLast((x) => x.role === "assistant" && x.tokens.output > 0) as AssistantMessage, + ) + const model = createMemo(() => + last() ? sync.data.provider.all.find((x) => x.id === last().providerID)?.models[last().modelID] : undefined, + ) + const diffs = createMemo(() => (params.id ? (sync.data.session_diff[params.id] ?? []) : [])) + + const tokens = createMemo(() => { + if (!last()) return + const t = last().tokens + return t.input + t.output + t.reasoning + t.cache.read + t.cache.write + }) + + const context = createMemo(() => { + const total = tokens() + const limit = model()?.limit.context + if (!total || !limit) return 0 + return Math.round((total / limit) * 100) + }) + const [store, setStore] = createStore({ clickTimer: undefined as number | undefined, activeDraggable: undefined as string | undefined, @@ -50,10 +119,15 @@ export default function Page() { }) let inputRef!: HTMLDivElement + createEffect(() => { + if (!params.id) return + sync.session.sync(params.id) + }) + createEffect(() => { if (layout.terminal.opened()) { - if (session.terminal.all().length === 0) { - session.terminal.new() + if (terminal.all().length === 0) { + terminal.new() } } }) @@ -99,7 +173,7 @@ export default function Page() { description: "Create a new terminal tab", category: "Terminal", keybind: "ctrl+shift+`", - onSelect: () => session.terminal.new(), + onSelect: () => terminal.new(), }, ]) @@ -166,11 +240,11 @@ export default function Page() { const handleDragOver = (event: DragEvent) => { const { draggable, droppable } = event if (draggable && droppable) { - const currentTabs = session.layout.tabs.all + const currentTabs = tabs().all() const fromIndex = currentTabs?.indexOf(draggable.id.toString()) const toIndex = currentTabs?.indexOf(droppable.id.toString()) if (fromIndex !== toIndex && toIndex !== undefined) { - session.layout.moveTab(draggable.id.toString(), toIndex) + tabs().move(draggable.id.toString(), toIndex) } } } @@ -188,11 +262,11 @@ export default function Page() { const handleTerminalDragOver = (event: DragEvent) => { const { draggable, droppable } = event if (draggable && droppable) { - const terminals = session.terminal.all() - const fromIndex = terminals.findIndex((t) => t.id === draggable.id.toString()) - const toIndex = terminals.findIndex((t) => t.id === droppable.id.toString()) + const terminals = terminal.all() + const fromIndex = terminals.findIndex((t: LocalPTY) => t.id === draggable.id.toString()) + const toIndex = terminals.findIndex((t: LocalPTY) => t.id === droppable.id.toString()) if (fromIndex !== -1 && toIndex !== -1 && fromIndex !== toIndex) { - session.terminal.move(draggable.id.toString(), toIndex) + terminal.move(draggable.id.toString(), toIndex) } } } @@ -210,8 +284,8 @@ export default function Page() { 1 && ( - session.terminal.close(props.terminal.id)} /> + terminal.all().length > 1 && ( + terminal.close(props.terminal.id)} /> ) } > @@ -326,7 +400,7 @@ export default function Page() { return typeof draggable.id === "string" ? draggable.id : undefined } - const wide = createMemo(() => layout.review.state() === "tab" || !session.diffs().length) + const wide = createMemo(() => layout.review.state() === "tab" || !diffs().length) return (
@@ -339,7 +413,7 @@ export default function Page() { > - +
@@ -349,15 +423,15 @@ export default function Page() { value={`${new Intl.NumberFormat("en-US", { notation: "compact", compactDisplay: "short", - }).format(session.usage.tokens() ?? 0)} Tokens`} + }).format(tokens() ?? 0)} Tokens`} class="flex items-center gap-1.5" > - -
{session.usage.context() ?? 0}%
+ +
{context() ?? 0}%
- +
- - + +
Review
- +
- {session.info()?.summary?.files ?? 0} + {info()?.summary?.files ?? 0}
- - - {(tab) => ( - - )} + + + {(tab) => }
@@ -415,27 +487,23 @@ export default function Page() { }} > - +
1 - ? "pr-6 pl-18" - : "px-6"), + (wide() ? "max-w-146 mx-auto px-6" : userMessages().length > 1 ? "pr-6 pl-18" : "px-6"), }} />
@@ -476,7 +544,7 @@ export default function Page() {
- +
{ layout.review.tab() - session.layout.setActiveTab("review") + tabs().setActive("review") }} /> @@ -506,7 +574,7 @@ export default function Page() {
- +
- + {(tab) => { const [file] = createResource( () => tab, @@ -579,7 +647,7 @@ export default function Page() {
- +
{ @@ -639,25 +707,21 @@ export default function Page() { > - + - t.id)}> - {(terminal) => } + t.id)}> + {(pty) => }
- +
- - {(terminal) => ( - - session.terminal.clone(terminal.id)} - /> + + {(pty) => ( + + terminal.clone(pty.id)} /> )} @@ -665,9 +729,9 @@ export default function Page() { {(draggedId) => { - const terminal = createMemo(() => session.terminal.all().find((t) => t.id === draggedId())) + const pty = createMemo(() => terminal.all().find((t: LocalPTY) => t.id === draggedId())) return ( - + {(t) => (
{t().title} -- cgit v1.2.3