import { Show, createEffect, createMemo, onCleanup } from "solid-js" import { createStore } from "solid-js/store" import { useSpring } from "@opencode-ai/ui/motion-spring" import { PromptInput } from "@/components/prompt-input" import { useLanguage } from "@/context/language" import { usePrompt } from "@/context/prompt" import { getSessionHandoff, setSessionHandoff } from "@/pages/session/handoff" import { useSessionKey } from "@/pages/session/session-layout" import { SessionPermissionDock } from "@/pages/session/composer/session-permission-dock" import { SessionQuestionDock } from "@/pages/session/composer/session-question-dock" import { SessionFollowupDock } from "@/pages/session/composer/session-followup-dock" import { SessionRevertDock } from "@/pages/session/composer/session-revert-dock" import type { SessionComposerState } from "@/pages/session/composer/session-composer-state" import { SessionTodoDock } from "@/pages/session/composer/session-todo-dock" import type { FollowupDraft } from "@/components/prompt-input/submit" import { createResizeObserver } from "@solid-primitives/resize-observer" export function SessionComposerRegion(props: { state: SessionComposerState ready: boolean centered: boolean inputRef: (el: HTMLDivElement) => void newSessionWorktree: string onNewSessionWorktreeReset: () => void onSubmit: () => void onResponseSubmit: () => void followup?: { queue: () => boolean items: { id: string; text: string }[] sending?: string edit?: { id: string; prompt: FollowupDraft["prompt"]; context: FollowupDraft["context"] } onQueue: (draft: FollowupDraft) => void onAbort: () => void onSend: (id: string) => void onEdit: (id: string) => void onEditLoaded: () => void } revert?: { items: { id: string; text: string }[] restoring?: string disabled?: boolean onRestore: (id: string) => void } setPromptDockRef: (el: HTMLDivElement) => void }) { const prompt = usePrompt() const language = useLanguage() const route = useSessionKey() const handoffPrompt = createMemo(() => getSessionHandoff(route.sessionKey())?.prompt) const previewPrompt = () => prompt .current() .map((part) => { if (part.type === "file") return `[file:${part.path}]` if (part.type === "agent") return `@${part.name}` if (part.type === "image") return `[image:${part.filename}]` return part.content }) .join("") .trim() createEffect(() => { if (!prompt.ready()) return setSessionHandoff(route.sessionKey(), { prompt: previewPrompt() }) }) const [store, setStore] = createStore({ ready: false, height: 320, body: undefined as HTMLDivElement | undefined, }) let timer: number | undefined let frame: number | undefined const clear = () => { if (timer !== undefined) { window.clearTimeout(timer) timer = undefined } if (frame !== undefined) { cancelAnimationFrame(frame) frame = undefined } } createEffect(() => { route.sessionKey() const ready = props.ready const delay = 140 clear() setStore("ready", false) if (!ready) return frame = requestAnimationFrame(() => { frame = undefined timer = window.setTimeout(() => { setStore("ready", true) timer = undefined }, delay) }) }) onCleanup(clear) const open = createMemo(() => store.ready && props.state.dock() && !props.state.closing()) const progress = useSpring(() => (open() ? 1 : 0), { visualDuration: 0.3, bounce: 0 }) const value = createMemo(() => Math.max(0, Math.min(1, progress()))) const dock = createMemo(() => (store.ready && props.state.dock()) || value() > 0.001) const rolled = createMemo(() => (props.revert?.items.length ? props.revert : undefined)) const lift = createMemo(() => (rolled() ? 18 : 36 * value())) const full = createMemo(() => Math.max(78, store.height)) createEffect(() => { const el = store.body if (!el) return const update = () => setStore("height", el.getBoundingClientRect().height) createResizeObserver(store.body, update) update() }) return (