Buckets:
GHHG10/CodeServer / opencode /packages /app /src /pages /session /composer /session-composer-state.ts
| import { createEffect, createMemo, on, onCleanup } from "solid-js" | |
| import { createStore } from "solid-js/store" | |
| import type { PermissionRequest, QuestionRequest, Todo } from "@opencode-ai/sdk/v2" | |
| import { useParams } from "@solidjs/router" | |
| import { showToast } from "@/utils/toast" | |
| import { useServerSync } from "@/context/server-sync" | |
| import { useLanguage } from "@/context/language" | |
| import { usePermission } from "@/context/permission" | |
| import { useSDK } from "@/context/sdk" | |
| import { useSync } from "@/context/sync" | |
| import { sessionPermissionRequest, sessionQuestionRequest } from "./session-request-tree" | |
| export const todoState = (input: { | |
| count: number | |
| done: boolean | |
| live: boolean | |
| }): "hide" | "clear" | "open" | "close" => { | |
| if (input.count === 0) return "hide" | |
| if (!input.live) return "clear" | |
| if (!input.done) return "open" | |
| return "close" | |
| } | |
| const idle = { type: "idle" as const } | |
| export function createSessionComposerState(options?: { closeMs?: number | (() => number) }) { | |
| const params = useParams() | |
| const sdk = useSDK() | |
| const sync = useSync() | |
| const serverSync = useServerSync() | |
| const language = useLanguage() | |
| const permission = usePermission() | |
| const questionRequest = createMemo((): QuestionRequest | undefined => { | |
| return sessionQuestionRequest(sync.data.session, sync.data.question, params.id) | |
| }) | |
| const permissionRequest = createMemo((): PermissionRequest | undefined => { | |
| return sessionPermissionRequest(sync.data.session, sync.data.permission, params.id, (item) => { | |
| return !permission.autoResponds(item, sdk.directory) | |
| }) | |
| }) | |
| const blocked = createMemo(() => { | |
| const id = params.id | |
| if (!id) return false | |
| return !!permissionRequest() || !!questionRequest() | |
| }) | |
| const todos = createMemo((): Todo[] => { | |
| const id = params.id | |
| if (!id) return [] | |
| return serverSync.data.session_todo[id] ?? [] | |
| }) | |
| const done = createMemo( | |
| () => todos().length > 0 && todos().every((todo) => todo.status === "completed" || todo.status === "cancelled"), | |
| ) | |
| const live = createMemo(() => sync.data.session_working(params.id ?? "") || blocked()) | |
| const [store, setStore] = createStore({ | |
| responding: undefined as string | undefined, | |
| dock: todos().length > 0 && live(), | |
| closing: false, | |
| opening: false, | |
| }) | |
| const permissionResponding = createMemo(() => { | |
| const perm = permissionRequest() | |
| if (!perm) return false | |
| return store.responding === perm.id | |
| }) | |
| const decide = (response: "once" | "always" | "reject") => { | |
| const perm = permissionRequest() | |
| if (!perm) return | |
| if (store.responding === perm.id) return | |
| setStore("responding", perm.id) | |
| sdk.client.permission | |
| .respond({ sessionID: perm.sessionID, permissionID: perm.id, response }) | |
| .catch((err: unknown) => { | |
| const description = err instanceof Error ? err.message : String(err) | |
| showToast({ title: language.t("common.requestFailed"), description }) | |
| }) | |
| .finally(() => { | |
| setStore("responding", (id) => (id === perm.id ? undefined : id)) | |
| }) | |
| } | |
| let timer: number | undefined | |
| let raf: number | undefined | |
| const closeMs = () => { | |
| const value = options?.closeMs | |
| if (typeof value === "function") return Math.max(0, value()) | |
| if (typeof value === "number") return Math.max(0, value) | |
| return 400 | |
| } | |
| const scheduleClose = () => { | |
| if (timer) window.clearTimeout(timer) | |
| timer = window.setTimeout(() => { | |
| setStore({ dock: false, closing: false }) | |
| timer = undefined | |
| }, closeMs()) | |
| } | |
| // Keep stale turn todos from reopening if the model never clears them. | |
| const clear = () => { | |
| const id = params.id | |
| if (!id) return | |
| serverSync.todo.set(id, []) | |
| sync.set("todo", id, []) | |
| } | |
| createEffect( | |
| on( | |
| () => [todos().length, done(), live()] as const, | |
| ([count, complete, active]) => { | |
| if (raf) cancelAnimationFrame(raf) | |
| raf = undefined | |
| const next = todoState({ | |
| count, | |
| done: complete, | |
| live: active, | |
| }) | |
| if (next === "hide") { | |
| if (timer) window.clearTimeout(timer) | |
| timer = undefined | |
| setStore({ dock: false, closing: false, opening: false }) | |
| return | |
| } | |
| if (next === "clear") { | |
| if (timer) window.clearTimeout(timer) | |
| timer = undefined | |
| clear() | |
| return | |
| } | |
| if (next === "open") { | |
| if (timer) window.clearTimeout(timer) | |
| timer = undefined | |
| const hidden = !store.dock || store.closing | |
| setStore({ dock: true, closing: false }) | |
| if (hidden) { | |
| setStore("opening", true) | |
| raf = requestAnimationFrame(() => { | |
| setStore("opening", false) | |
| raf = undefined | |
| }) | |
| return | |
| } | |
| setStore("opening", false) | |
| return | |
| } | |
| setStore({ dock: true, opening: false, closing: true }) | |
| if (!timer) scheduleClose() | |
| }, | |
| ), | |
| ) | |
| onCleanup(() => { | |
| if (!timer) return | |
| window.clearTimeout(timer) | |
| }) | |
| onCleanup(() => { | |
| if (!raf) return | |
| cancelAnimationFrame(raf) | |
| }) | |
| return { | |
| blocked, | |
| questionRequest, | |
| permissionRequest, | |
| permissionResponding, | |
| decide, | |
| todos, | |
| dock: () => store.dock, | |
| closing: () => store.closing, | |
| opening: () => store.opening, | |
| } | |
| } | |
| export type SessionComposerState = ReturnType<typeof createSessionComposerState> | |
Xet Storage Details
- Size:
- 5.51 kB
- Xet hash:
- 1a0059feb65524dedd274776f811f9b75be9a9479effc7ae3ee771a367773a55
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.