diff options
| author | Adam <[email protected]> | 2026-02-17 07:16:23 -0600 |
|---|---|---|
| committer | GitHub <[email protected]> | 2026-02-17 07:16:23 -0600 |
| commit | 10985671ad9553e7ac594ede30981166f69ba3c5 (patch) | |
| tree | 0f501ccba5dcf90e86beb18a0732826f7a3b1d0a /packages/app/src/pages/session | |
| parent | 3dfbb7059345350fdcb3f45fe9a44697c08a040a (diff) | |
| download | opencode-10985671ad9553e7ac594ede30981166f69ba3c5.tar.gz opencode-10985671ad9553e7ac594ede30981166f69ba3c5.zip | |
feat(app): session timeline/turn rework (#13196)
Co-authored-by: David Hill <[email protected]>
Diffstat (limited to 'packages/app/src/pages/session')
| -rw-r--r-- | packages/app/src/pages/session/message-timeline.tsx | 54 | ||||
| -rw-r--r-- | packages/app/src/pages/session/session-prompt-dock.tsx | 138 | ||||
| -rw-r--r-- | packages/app/src/pages/session/use-session-commands.tsx | 368 |
3 files changed, 313 insertions, 247 deletions
diff --git a/packages/app/src/pages/session/message-timeline.tsx b/packages/app/src/pages/session/message-timeline.tsx index d5f04ccf9..53cf441d6 100644 --- a/packages/app/src/pages/session/message-timeline.tsx +++ b/packages/app/src/pages/session/message-timeline.tsx @@ -4,10 +4,10 @@ import { Icon } from "@opencode-ai/ui/icon" import { IconButton } from "@opencode-ai/ui/icon-button" import { DropdownMenu } from "@opencode-ai/ui/dropdown-menu" import { InlineInput } from "@opencode-ai/ui/inline-input" -import { Tooltip } from "@opencode-ai/ui/tooltip" import { SessionTurn } from "@opencode-ai/ui/session-turn" import type { UserMessage } from "@opencode-ai/sdk/v2" import { shouldMarkBoundaryGesture, normalizeWheelDelta } from "@/pages/session/message-gesture" +import { SessionContextUsage } from "@/components/session-context-usage" const boundaryTarget = (root: HTMLElement, target: EventTarget | null) => { const current = target instanceof Element ? target : undefined @@ -88,8 +88,6 @@ export function MessageTimeline(props: { onUnregisterMessage: (id: string) => void onFirstTurnMount?: () => void lastUserMessageID?: string - expanded: Record<string, boolean> - onToggleExpanded: (id: string) => void }) { let touchGesture: number | undefined @@ -100,7 +98,7 @@ export function MessageTimeline(props: { > <div class="relative w-full h-full min-w-0"> <div - class="absolute left-1/2 -translate-x-1/2 bottom-[calc(var(--prompt-height,8rem)+32px)] z-[60] pointer-events-none transition-all duration-200 ease-out" + class="absolute left-1/2 -translate-x-1/2 bottom-6 z-[60] pointer-events-none transition-all duration-200 ease-out" classList={{ "opacity-100 translate-y-0 scale-100": props.scroll.overflow && !props.scroll.bottom, "opacity-0 translate-y-2 scale-95 pointer-events-none": !props.scroll.overflow || props.scroll.bottom, @@ -164,14 +162,15 @@ export function MessageTimeline(props: { <Show when={props.showHeader}> <div classList={{ - "sticky top-0 z-30 bg-background-stronger": true, + "sticky top-0 z-30 bg-[linear-gradient(to_bottom,var(--background-stronger)_48px,transparent)]": true, "w-full": true, - "px-4 md:px-6": true, + "pb-4": true, + "pl-2 pr-4 md:pl-4 md:pr-6": true, "md:max-w-200 md:mx-auto 2xl:max-w-[1000px]": props.centered, }} > - <div class="h-10 w-full flex items-center justify-between gap-2"> - <div class="flex items-center gap-1 min-w-0 flex-1"> + <div class="h-12 w-full flex items-center justify-between gap-2"> + <div class="flex items-center gap-1 min-w-0 flex-1 pr-3"> <Show when={props.parentID}> <IconButton tabIndex={-1} @@ -185,7 +184,10 @@ export function MessageTimeline(props: { <Show when={props.titleState.editing} fallback={ - <h1 class="text-16-medium text-text-strong truncate min-w-0" onDblClick={props.openTitleEditor}> + <h1 + class="text-14-medium text-text-strong truncate grow-1 min-w-0 pl-2" + onDblClick={props.openTitleEditor} + > {props.title} </h1> } @@ -194,7 +196,8 @@ export function MessageTimeline(props: { ref={props.titleRef} value={props.titleState.draft} disabled={props.titleState.saving} - class="text-16-medium text-text-strong grow-1 min-w-0" + class="text-14-medium text-text-strong grow-1 min-w-0 pl-2 rounded-[6px]" + style={{ "--inline-input-shadow": "var(--shadow-xs-border-select)" }} onInput={(event) => props.onTitleDraft(event.currentTarget.value)} onKeyDown={(event) => { event.stopPropagation() @@ -215,19 +218,24 @@ export function MessageTimeline(props: { </div> <Show when={props.sessionID}> {(id) => ( - <div class="shrink-0 flex items-center"> - <DropdownMenu open={props.titleState.menuOpen} onOpenChange={props.onTitleMenuOpen}> - <Tooltip value={props.t("common.moreOptions")} placement="top"> - <DropdownMenu.Trigger - as={IconButton} - icon="dot-grid" - variant="ghost" - class="size-6 rounded-md data-[expanded]:bg-surface-base-active" - aria-label={props.t("common.moreOptions")} - /> - </Tooltip> + <div class="shrink-0 flex items-center gap-3"> + <SessionContextUsage placement="bottom" /> + <DropdownMenu + gutter={4} + placement="bottom-end" + open={props.titleState.menuOpen} + onOpenChange={props.onTitleMenuOpen} + > + <DropdownMenu.Trigger + as={IconButton} + icon="dot-grid" + variant="ghost" + class="size-6 rounded-md data-[expanded]:bg-surface-base-active" + aria-label={props.t("common.moreOptions")} + /> <DropdownMenu.Portal> <DropdownMenu.Content + style={{ "min-width": "104px" }} onCloseAutoFocus={(event) => { if (!props.titleState.pendingRename) return event.preventDefault() @@ -263,7 +271,7 @@ export function MessageTimeline(props: { <div ref={props.setContentRef} role="log" - class="flex flex-col gap-12 items-start justify-start pb-[calc(var(--prompt-height,8rem)+64px)] md:pb-[calc(var(--prompt-height,10rem)+64px)] transition-[margin]" + class="flex flex-col gap-12 items-start justify-start pb-16 transition-[margin]" classList={{ "w-full": true, "md:max-w-200 md:mx-auto 2xl:max-w-[1000px]": props.centered, @@ -316,8 +324,6 @@ export function MessageTimeline(props: { sessionID={props.sessionID} messageID={message.id} lastUserMessageID={props.lastUserMessageID} - stepsExpanded={props.expanded[message.id] ?? false} - onStepsExpandedToggle={() => props.onToggleExpanded(message.id)} classes={{ root: "min-w-0 w-full relative", content: "flex flex-col justify-between !overflow-visible", diff --git a/packages/app/src/pages/session/session-prompt-dock.tsx b/packages/app/src/pages/session/session-prompt-dock.tsx index 8ec4f3b9f..2b9571982 100644 --- a/packages/app/src/pages/session/session-prompt-dock.tsx +++ b/packages/app/src/pages/session/session-prompt-dock.tsx @@ -1,16 +1,17 @@ -import { For, Show } from "solid-js" -import type { QuestionRequest } from "@opencode-ai/sdk/v2" +import { For, Show, createEffect, createMemo, createSignal, on, onCleanup } from "solid-js" +import type { QuestionRequest, Todo } from "@opencode-ai/sdk/v2" import { Button } from "@opencode-ai/ui/button" import { BasicTool } from "@opencode-ai/ui/basic-tool" import { PromptInput } from "@/components/prompt-input" import { QuestionDock } from "@/components/question-dock" -import { questionSubtitle } from "@/pages/session/session-prompt-helpers" +import { SessionTodoDock } from "@/components/session-todo-dock" export function SessionPromptDock(props: { centered: boolean questionRequest: () => QuestionRequest | undefined permissionRequest: () => { patterns: string[]; permission: string } | undefined blocked: boolean + todos: Todo[] promptReady: boolean handoffPrompt?: string t: (key: string, vars?: Record<string, string | number | boolean>) => string @@ -22,10 +23,88 @@ export function SessionPromptDock(props: { onSubmit: () => void setPromptDockRef: (el: HTMLDivElement) => void }) { + const done = createMemo( + () => + props.todos.length > 0 && props.todos.every((todo) => todo.status === "completed" || todo.status === "cancelled"), + ) + + const [dock, setDock] = createSignal(props.todos.length > 0) + const [closing, setClosing] = createSignal(false) + const [opening, setOpening] = createSignal(false) + let timer: number | undefined + let raf: number | undefined + + const scheduleClose = () => { + if (timer) window.clearTimeout(timer) + timer = window.setTimeout(() => { + setDock(false) + setClosing(false) + timer = undefined + }, 400) + } + + createEffect( + on( + () => [props.todos.length, done()] as const, + ([count, complete], prev) => { + if (raf) cancelAnimationFrame(raf) + raf = undefined + + if (count === 0) { + if (timer) window.clearTimeout(timer) + timer = undefined + setDock(false) + setClosing(false) + setOpening(false) + return + } + + if (!complete) { + if (timer) window.clearTimeout(timer) + timer = undefined + const wasHidden = !dock() || closing() + setDock(true) + setClosing(false) + if (wasHidden) { + setOpening(true) + raf = requestAnimationFrame(() => { + setOpening(false) + raf = undefined + }) + return + } + setOpening(false) + return + } + + if (prev && prev[1]) { + if (closing() && !timer) scheduleClose() + return + } + + setDock(true) + setOpening(false) + setClosing(true) + scheduleClose() + }, + ), + ) + + onCleanup(() => { + if (!timer) return + window.clearTimeout(timer) + }) + + onCleanup(() => { + if (!raf) return + cancelAnimationFrame(raf) + }) + return ( <div ref={props.setPromptDockRef} - class="absolute inset-x-0 bottom-0 pt-12 pb-4 flex flex-col justify-center items-center z-50 bg-gradient-to-t from-background-stronger via-background-stronger to-transparent pointer-events-none" + data-component="session-prompt-dock" + class="shrink-0 w-full pb-4 flex flex-col justify-center items-center bg-background-stronger pointer-events-none" > <div classList={{ @@ -35,18 +114,8 @@ export function SessionPromptDock(props: { > <Show when={props.questionRequest()} keyed> {(req) => { - const subtitle = questionSubtitle(req.questions.length, (key) => props.t(key)) return ( - <div data-component="tool-part-wrapper" data-question="true" class="mb-3"> - <BasicTool - icon="bubble-5" - locked - defaultOpen - trigger={{ - title: props.t("ui.tool.questions"), - subtitle, - }} - /> + <div> <QuestionDock request={req} /> </div> ) @@ -122,12 +191,39 @@ export function SessionPromptDock(props: { </div> } > - <PromptInput - ref={props.inputRef} - newSessionWorktree={props.newSessionWorktree} - onNewSessionWorktreeReset={props.onNewSessionWorktreeReset} - onSubmit={props.onSubmit} - /> + <Show when={dock()}> + <div + classList={{ + "transition-[max-height,opacity,transform] duration-[400ms] ease-out overflow-hidden": true, + "max-h-[320px]": !closing(), + "max-h-0 pointer-events-none": closing(), + "opacity-0 translate-y-9": closing() || opening(), + "opacity-100 translate-y-0": !closing() && !opening(), + }} + > + <SessionTodoDock + todos={props.todos} + title={props.t("session.todo.title")} + collapseLabel={props.t("session.todo.collapse")} + expandLabel={props.t("session.todo.expand")} + /> + </div> + </Show> + <div + classList={{ + "relative z-10": true, + "transition-[margin] duration-[400ms] ease-out": true, + "-mt-9": dock() && !closing(), + "mt-0": !dock() || closing(), + }} + > + <PromptInput + ref={props.inputRef} + newSessionWorktree={props.newSessionWorktree} + onNewSessionWorktreeReset={props.onNewSessionWorktreeReset} + onSubmit={props.onSubmit} + /> + </div> </Show> </Show> </div> diff --git a/packages/app/src/pages/session/use-session-commands.tsx b/packages/app/src/pages/session/use-session-commands.tsx index d2f74288f..4d68afd66 100644 --- a/packages/app/src/pages/session/use-session-commands.tsx +++ b/packages/app/src/pages/session/use-session-commands.tsx @@ -22,11 +22,29 @@ import { UserMessage } from "@opencode-ai/sdk/v2" import { canAddSelectionContext } from "@/pages/session/session-command-helpers" export type SessionCommandContext = { - activeMessage: () => UserMessage | undefined + command: ReturnType<typeof useCommand> + dialog: ReturnType<typeof useDialog> + file: ReturnType<typeof useFile> + language: ReturnType<typeof useLanguage> + local: ReturnType<typeof useLocal> + permission: ReturnType<typeof usePermission> + prompt: ReturnType<typeof usePrompt> + sdk: ReturnType<typeof useSDK> + sync: ReturnType<typeof useSync> + terminal: ReturnType<typeof useTerminal> + layout: ReturnType<typeof useLayout> + params: ReturnType<typeof useParams> + navigate: ReturnType<typeof useNavigate> + tabs: () => ReturnType<ReturnType<typeof useLayout>["tabs"]> + view: () => ReturnType<ReturnType<typeof useLayout>["view"]> + info: () => { revert?: { messageID?: string }; share?: { url?: string } } | undefined + status: () => { type: string } + userMessages: () => UserMessage[] + visibleUserMessages: () => UserMessage[] showAllFiles: () => void navigateMessageByOffset: (offset: number) => void - setExpanded: (id: string, fn: (open: boolean | undefined) => boolean) => void setActiveMessage: (message: UserMessage | undefined) => void + addSelectionToContext: (path: string, selection: FileSelection) => void focusInput: () => void } @@ -37,88 +55,45 @@ const withCategory = (category: string) => { }) } -export const useSessionCommands = (args: SessionCommandContext) => { - const command = useCommand() - const dialog = useDialog() - const file = useFile() - const language = useLanguage() - const local = useLocal() - const permission = usePermission() - const prompt = usePrompt() - const sdk = useSDK() - const sync = useSync() - const terminal = useTerminal() - const layout = useLayout() - const params = useParams() - const navigate = useNavigate() - - const sessionKey = createMemo(() => `${params.dir}${params.id ? "/" + params.id : ""}`) - const tabs = createMemo(() => layout.tabs(sessionKey)) - const view = createMemo(() => layout.view(sessionKey)) - const info = createMemo(() => (params.id ? sync.session.get(params.id) : undefined)) - const idle = { type: "idle" as const } - const status = createMemo(() => sync.data.session_status[params.id ?? ""] ?? idle) - const messages = createMemo(() => (params.id ? (sync.data.message[params.id] ?? []) : [])) - const userMessages = createMemo(() => messages().filter((m) => m.role === "user") as UserMessage[]) - const visibleUserMessages = createMemo(() => { - const revert = info()?.revert?.messageID - if (!revert) return userMessages() - return userMessages().filter((m) => m.id < revert) - }) - - const selectionPreview = (path: string, selection: FileSelection) => { - const content = file.get(path)?.content?.content - if (!content) return undefined - const start = Math.max(1, Math.min(selection.startLine, selection.endLine)) - const end = Math.max(selection.startLine, selection.endLine) - const lines = content.split("\n").slice(start - 1, end) - if (lines.length === 0) return undefined - return lines.slice(0, 2).join("\n") - } - - const addSelectionToContext = (path: string, selection: FileSelection) => { - const preview = selectionPreview(path, selection) - prompt.context.add({ type: "file", path, selection, preview }) - } - - const sessionCommand = withCategory(language.t("command.category.session")) - const fileCommand = withCategory(language.t("command.category.file")) - const contextCommand = withCategory(language.t("command.category.context")) - const viewCommand = withCategory(language.t("command.category.view")) - const terminalCommand = withCategory(language.t("command.category.terminal")) - const modelCommand = withCategory(language.t("command.category.model")) - const mcpCommand = withCategory(language.t("command.category.mcp")) - const agentCommand = withCategory(language.t("command.category.agent")) - const permissionsCommand = withCategory(language.t("command.category.permissions")) +export const useSessionCommands = (input: SessionCommandContext) => { + const sessionCommand = withCategory(input.language.t("command.category.session")) + const fileCommand = withCategory(input.language.t("command.category.file")) + const contextCommand = withCategory(input.language.t("command.category.context")) + const viewCommand = withCategory(input.language.t("command.category.view")) + const terminalCommand = withCategory(input.language.t("command.category.terminal")) + const modelCommand = withCategory(input.language.t("command.category.model")) + const mcpCommand = withCategory(input.language.t("command.category.mcp")) + const agentCommand = withCategory(input.language.t("command.category.agent")) + const permissionsCommand = withCategory(input.language.t("command.category.permissions")) const sessionCommands = createMemo(() => [ sessionCommand({ id: "session.new", - title: language.t("command.session.new"), + title: input.language.t("command.session.new"), keybind: "mod+shift+s", slash: "new", - onSelect: () => navigate(`/${params.dir}/session`), + onSelect: () => input.navigate(`/${input.params.dir}/session`), }), ]) const fileCommands = createMemo(() => [ fileCommand({ id: "file.open", - title: language.t("command.file.open"), - description: language.t("palette.search.placeholder"), + title: input.language.t("command.file.open"), + description: input.language.t("palette.search.placeholder"), keybind: "mod+p", slash: "open", - onSelect: () => dialog.show(() => <DialogSelectFile onOpenFile={args.showAllFiles} />), + onSelect: () => input.dialog.show(() => <DialogSelectFile onOpenFile={input.showAllFiles} />), }), fileCommand({ id: "tab.close", - title: language.t("command.tab.close"), + title: input.language.t("command.tab.close"), keybind: "mod+w", - disabled: !tabs().active(), + disabled: !input.tabs().active(), onSelect: () => { - const active = tabs().active() + const active = input.tabs().active() if (!active) return - tabs().close(active) + input.tabs().close(active) }, }), ]) @@ -126,30 +101,30 @@ export const useSessionCommands = (args: SessionCommandContext) => { const contextCommands = createMemo(() => [ contextCommand({ id: "context.addSelection", - title: language.t("command.context.addSelection"), - description: language.t("command.context.addSelection.description"), + title: input.language.t("command.context.addSelection"), + description: input.language.t("command.context.addSelection.description"), keybind: "mod+shift+l", disabled: !canAddSelectionContext({ - active: tabs().active(), - pathFromTab: file.pathFromTab, - selectedLines: file.selectedLines, + active: input.tabs().active(), + pathFromTab: input.file.pathFromTab, + selectedLines: input.file.selectedLines, }), onSelect: () => { - const active = tabs().active() + const active = input.tabs().active() if (!active) return - const path = file.pathFromTab(active) + const path = input.file.pathFromTab(active) if (!path) return - const range = file.selectedLines(path) as SelectedLineRange | null | undefined + const range = input.file.selectedLines(path) as SelectedLineRange | null | undefined if (!range) { showToast({ - title: language.t("toast.context.noLineSelection.title"), - description: language.t("toast.context.noLineSelection.description"), + title: input.language.t("toast.context.noLineSelection.title"), + description: input.language.t("toast.context.noLineSelection.description"), }) return } - addSelectionToContext(path, selectionFromLines(range)) + input.addSelectionToContext(path, selectionFromLines(range)) }, }), ]) @@ -157,50 +132,37 @@ export const useSessionCommands = (args: SessionCommandContext) => { const viewCommands = createMemo(() => [ viewCommand({ id: "terminal.toggle", - title: language.t("command.terminal.toggle"), + title: input.language.t("command.terminal.toggle"), keybind: "ctrl+`", slash: "terminal", - onSelect: () => view().terminal.toggle(), + onSelect: () => input.view().terminal.toggle(), }), viewCommand({ id: "review.toggle", - title: language.t("command.review.toggle"), + title: input.language.t("command.review.toggle"), keybind: "mod+shift+r", - onSelect: () => view().reviewPanel.toggle(), + onSelect: () => input.view().reviewPanel.toggle(), }), viewCommand({ id: "fileTree.toggle", - title: language.t("command.fileTree.toggle"), + title: input.language.t("command.fileTree.toggle"), keybind: "mod+\\", - onSelect: () => layout.fileTree.toggle(), + onSelect: () => input.layout.fileTree.toggle(), }), viewCommand({ id: "input.focus", - title: language.t("command.input.focus"), + title: input.language.t("command.input.focus"), keybind: "ctrl+l", - onSelect: () => args.focusInput(), + onSelect: () => input.focusInput(), }), terminalCommand({ id: "terminal.new", - title: language.t("command.terminal.new"), - description: language.t("command.terminal.new.description"), + title: input.language.t("command.terminal.new"), + description: input.language.t("command.terminal.new.description"), keybind: "ctrl+alt+t", onSelect: () => { - if (terminal.all().length > 0) terminal.new() - view().terminal.open() - }, - }), - viewCommand({ - id: "steps.toggle", - title: language.t("command.steps.toggle"), - description: language.t("command.steps.toggle.description"), - keybind: "mod+e", - slash: "steps", - disabled: !params.id, - onSelect: () => { - const msg = args.activeMessage() - if (!msg) return - args.setExpanded(msg.id, (open: boolean | undefined) => !open) + if (input.terminal.all().length > 0) input.terminal.new() + input.view().terminal.open() }, }), ]) @@ -208,61 +170,61 @@ export const useSessionCommands = (args: SessionCommandContext) => { const messageCommands = createMemo(() => [ sessionCommand({ id: "message.previous", - title: language.t("command.message.previous"), - description: language.t("command.message.previous.description"), + title: input.language.t("command.message.previous"), + description: input.language.t("command.message.previous.description"), keybind: "mod+arrowup", - disabled: !params.id, - onSelect: () => args.navigateMessageByOffset(-1), + disabled: !input.params.id, + onSelect: () => input.navigateMessageByOffset(-1), }), sessionCommand({ id: "message.next", - title: language.t("command.message.next"), - description: language.t("command.message.next.description"), + title: input.language.t("command.message.next"), + description: input.language.t("command.message.next.description"), keybind: "mod+arrowdown", - disabled: !params.id, - onSelect: () => args.navigateMessageByOffset(1), + disabled: !input.params.id, + onSelect: () => input.navigateMessageByOffset(1), }), ]) const agentCommands = createMemo(() => [ modelCommand({ id: "model.choose", - title: language.t("command.model.choose"), - description: language.t("command.model.choose.description"), + title: input.language.t("command.model.choose"), + description: input.language.t("command.model.choose.description"), keybind: "mod+'", slash: "model", - onSelect: () => dialog.show(() => <DialogSelectModel />), + onSelect: () => input.dialog.show(() => <DialogSelectModel />), }), mcpCommand({ id: "mcp.toggle", - title: language.t("command.mcp.toggle"), - description: language.t("command.mcp.toggle.description"), + title: input.language.t("command.mcp.toggle"), + description: input.language.t("command.mcp.toggle.description"), keybind: "mod+;", slash: "mcp", - onSelect: () => dialog.show(() => <DialogSelectMcp />), + onSelect: () => input.dialog.show(() => <DialogSelectMcp />), }), agentCommand({ id: "agent.cycle", - title: language.t("command.agent.cycle"), - description: language.t("command.agent.cycle.description"), + title: input.language.t("command.agent.cycle"), + description: input.language.t("command.agent.cycle.description"), keybind: "mod+.", slash: "agent", - onSelect: () => local.agent.move(1), + onSelect: () => input.local.agent.move(1), }), agentCommand({ id: "agent.cycle.reverse", - title: language.t("command.agent.cycle.reverse"), - description: language.t("command.agent.cycle.reverse.description"), + title: input.language.t("command.agent.cycle.reverse"), + description: input.language.t("command.agent.cycle.reverse.description"), keybind: "shift+mod+.", - onSelect: () => local.agent.move(-1), + onSelect: () => input.local.agent.move(-1), }), modelCommand({ id: "model.variant.cycle", - title: language.t("command.model.variant.cycle"), - description: language.t("command.model.variant.cycle.description"), + title: input.language.t("command.model.variant.cycle"), + description: input.language.t("command.model.variant.cycle.description"), keybind: "shift+mod+d", onSelect: () => { - local.model.variant.cycle() + input.local.model.variant.cycle() }, }), ]) @@ -271,22 +233,22 @@ export const useSessionCommands = (args: SessionCommandContext) => { permissionsCommand({ id: "permissions.autoaccept", title: - params.id && permission.isAutoAccepting(params.id, sdk.directory) - ? language.t("command.permissions.autoaccept.disable") - : language.t("command.permissions.autoaccept.enable"), + input.params.id && input.permission.isAutoAccepting(input.params.id, input.sdk.directory) + ? input.language.t("command.permissions.autoaccept.disable") + : input.language.t("command.permissions.autoaccept.enable"), keybind: "mod+shift+a", - disabled: !params.id || !permission.permissionsEnabled(), + disabled: !input.params.id || !input.permission.permissionsEnabled(), onSelect: () => { - const sessionID = params.id + const sessionID = input.params.id if (!sessionID) return - permission.toggleAutoAccept(sessionID, sdk.directory) + input.permission.toggleAutoAccept(sessionID, input.sdk.directory) showToast({ - title: permission.isAutoAccepting(sessionID, sdk.directory) - ? language.t("toast.permissions.autoaccept.on.title") - : language.t("toast.permissions.autoaccept.off.title"), - description: permission.isAutoAccepting(sessionID, sdk.directory) - ? language.t("toast.permissions.autoaccept.on.description") - : language.t("toast.permissions.autoaccept.off.description"), + title: input.permission.isAutoAccepting(sessionID, input.sdk.directory) + ? input.language.t("toast.permissions.autoaccept.on.title") + : input.language.t("toast.permissions.autoaccept.off.title"), + description: input.permission.isAutoAccepting(sessionID, input.sdk.directory) + ? input.language.t("toast.permissions.autoaccept.on.description") + : input.language.t("toast.permissions.autoaccept.off.description"), }) }, }), @@ -295,71 +257,71 @@ export const useSessionCommands = (args: SessionCommandContext) => { const sessionActionCommands = createMemo(() => [ sessionCommand({ id: "session.undo", - title: language.t("command.session.undo"), - description: language.t("command.session.undo.description"), + title: input.language.t("command.session.undo"), + description: input.language.t("command.session.undo.description"), slash: "undo", - disabled: !params.id || visibleUserMessages().length === 0, + disabled: !input.params.id || input.visibleUserMessages().length === 0, onSelect: async () => { - const sessionID = params.id + const sessionID = input.params.id if (!sessionID) return - if (status()?.type !== "idle") { - await sdk.client.session.abort({ sessionID }).catch(() => {}) + if (input.status()?.type !== "idle") { + await input.sdk.client.session.abort({ sessionID }).catch(() => {}) } - const revert = info()?.revert?.messageID - const message = findLast(userMessages(), (x) => !revert || x.id < revert) + const revert = input.info()?.revert?.messageID + const message = findLast(input.userMessages(), (x) => !revert || x.id < revert) if (!message) return - await sdk.client.session.revert({ sessionID, messageID: message.id }) - const parts = sync.data.part[message.id] + await input.sdk.client.session.revert({ sessionID, messageID: message.id }) + const parts = input.sync.data.part[message.id] if (parts) { - const restored = extractPromptFromParts(parts, { directory: sdk.directory }) - prompt.set(restored) + const restored = extractPromptFromParts(parts, { directory: input.sdk.directory }) + input.prompt.set(restored) } - const priorMessage = findLast(userMessages(), (x) => x.id < message.id) - args.setActiveMessage(priorMessage) + const priorMessage = findLast(input.userMessages(), (x) => x.id < message.id) + input.setActiveMessage(priorMessage) }, }), sessionCommand({ id: "session.redo", - title: language.t("command.session.redo"), - description: language.t("command.session.redo.description"), + title: input.language.t("command.session.redo"), + description: input.language.t("command.session.redo.description"), slash: "redo", - disabled: !params.id || !info()?.revert?.messageID, + disabled: !input.params.id || !input.info()?.revert?.messageID, onSelect: async () => { - const sessionID = params.id + const sessionID = input.params.id if (!sessionID) return - const revertMessageID = info()?.revert?.messageID + const revertMessageID = input.info()?.revert?.messageID if (!revertMessageID) return - const nextMessage = userMessages().find((x) => x.id > revertMessageID) + const nextMessage = input.userMessages().find((x) => x.id > revertMessageID) if (!nextMessage) { - await sdk.client.session.unrevert({ sessionID }) - prompt.reset() - const lastMsg = findLast(userMessages(), (x) => x.id >= revertMessageID) - args.setActiveMessage(lastMsg) + await input.sdk.client.session.unrevert({ sessionID }) + input.prompt.reset() + const lastMsg = findLast(input.userMessages(), (x) => x.id >= revertMessageID) + input.setActiveMessage(lastMsg) return } - await sdk.client.session.revert({ sessionID, messageID: nextMessage.id }) - const priorMsg = findLast(userMessages(), (x) => x.id < nextMessage.id) - args.setActiveMessage(priorMsg) + await input.sdk.client.session.revert({ sessionID, messageID: nextMessage.id }) + const priorMsg = findLast(input.userMessages(), (x) => x.id < nextMessage.id) + input.setActiveMessage(priorMsg) }, }), sessionCommand({ id: "session.compact", - title: language.t("command.session.compact"), - description: language.t("command.session.compact.description"), + title: input.language.t("command.session.compact"), + description: input.language.t("command.session.compact.description"), slash: "compact", - disabled: !params.id || visibleUserMessages().length === 0, + disabled: !input.params.id || input.visibleUserMessages().length === 0, onSelect: async () => { - const sessionID = params.id + const sessionID = input.params.id if (!sessionID) return - const model = local.model.current() + const model = input.local.model.current() if (!model) { showToast({ - title: language.t("toast.model.none.title"), - description: language.t("toast.model.none.description"), + title: input.language.t("toast.model.none.title"), + description: input.language.t("toast.model.none.description"), }) return } - await sdk.client.session.summarize({ + await input.sdk.client.session.summarize({ sessionID, modelID: model.id, providerID: model.provider.id, @@ -368,27 +330,29 @@ export const useSessionCommands = (args: SessionCommandContext) => { }), sessionCommand({ id: "session.fork", - title: language.t("command.session.fork"), - description: language.t("command.session.fork.description"), + title: input.language.t("command.session.fork"), + description: input.language.t("command.session.fork.description"), slash: "fork", - disabled: !params.id || visibleUserMessages().length === 0, - onSelect: () => dialog.show(() => <DialogFork />), + disabled: !input.params.id || input.visibleUserMessages().length === 0, + onSelect: () => input.dialog.show(() => <DialogFork />), }), ]) const shareCommands = createMemo(() => { - if (sync.data.config.share === "disabled") return [] + if (input.sync.data.config.share === "disabled") return [] return [ sessionCommand({ id: "session.share", - title: info()?.share?.url ? language.t("session.share.copy.copyLink") : language.t("command.session.share"), - description: info()?.share?.url - ? language.t("toast.session.share.success.description") - : language.t("command.session.share.description"), + title: input.info()?.share?.url + ? input.language.t("session.share.copy.copyLink") + : input.language.t("command.session.share"), + description: input.info()?.share?.url + ? input.language.t("toast.session.share.success.description") + : input.language.t("command.session.share.description"), slash: "share", - disabled: !params.id, + disabled: !input.params.id, onSelect: async () => { - if (!params.id) return + if (!input.params.id) return const write = (value: string) => { const body = typeof document === "undefined" ? undefined : document.body @@ -418,7 +382,7 @@ export const useSessionCommands = (args: SessionCommandContext) => { const ok = await write(url) if (!ok) { showToast({ - title: language.t("toast.session.share.copyFailed.title"), + title: input.language.t("toast.session.share.copyFailed.title"), variant: "error", }) return @@ -426,27 +390,27 @@ export const useSessionCommands = (args: SessionCommandContext) => { showToast({ title: existing - ? language.t("session.share.copy.copied") - : language.t("toast.session.share.success.title"), - description: language.t("toast.session.share.success.description"), + ? input.language.t("session.share.copy.copied") + : input.language.t("toast.session.share.success.title"), + description: input.language.t("toast.session.share.success.description"), variant: "success", }) } - const existing = info()?.share?.url + const existing = input.info()?.share?.url if (existing) { await copy(existing, true) return } - const url = await sdk.client.session - .share({ sessionID: params.id }) + const url = await input.sdk.client.session + .share({ sessionID: input.params.id }) .then((res) => res.data?.share?.url) .catch(() => undefined) if (!url) { showToast({ - title: language.t("toast.session.share.failed.title"), - description: language.t("toast.session.share.failed.description"), + title: input.language.t("toast.session.share.failed.title"), + description: input.language.t("toast.session.share.failed.description"), variant: "error", }) return @@ -457,25 +421,25 @@ export const useSessionCommands = (args: SessionCommandContext) => { }), sessionCommand({ id: "session.unshare", - title: language.t("command.session.unshare"), - description: language.t("command.session.unshare.description"), + title: input.language.t("command.session.unshare"), + description: input.language.t("command.session.unshare.description"), slash: "unshare", - disabled: !params.id || !info()?.share?.url, + disabled: !input.params.id || !input.info()?.share?.url, onSelect: async () => { - if (!params.id) return - await sdk.client.session - .unshare({ sessionID: params.id }) + if (!input.params.id) return + await input.sdk.client.session + .unshare({ sessionID: input.params.id }) .then(() => showToast({ - title: language.t("toast.session.unshare.success.title"), - description: language.t("toast.session.unshare.success.description"), + title: input.language.t("toast.session.unshare.success.title"), + description: input.language.t("toast.session.unshare.success.description"), variant: "success", }), ) .catch(() => showToast({ - title: language.t("toast.session.unshare.failed.title"), - description: language.t("toast.session.unshare.failed.description"), + title: input.language.t("toast.session.unshare.failed.title"), + description: input.language.t("toast.session.unshare.failed.description"), variant: "error", }), ) @@ -484,7 +448,7 @@ export const useSessionCommands = (args: SessionCommandContext) => { ] }) - command.register("session", () => + input.command.register("session", () => [ sessionCommands(), fileCommands(), @@ -495,6 +459,6 @@ export const useSessionCommands = (args: SessionCommandContext) => { permissionCommands(), sessionActionCommands(), shareCommands(), - ].flatMap((section) => section), + ].flatMap((x) => x), ) } |
