summaryrefslogtreecommitdiffhomepage
path: root/packages/app/src/pages/session
diff options
context:
space:
mode:
authorAdam <[email protected]>2026-02-12 09:49:14 -0600
committerGitHub <[email protected]>2026-02-12 09:49:14 -0600
commitff4414bb152acfddb5c0eb073c38bedc1df4ae14 (patch)
tree78381c67d21ef6f089647f6b19e7aa2976840dbc /packages/app/src/pages/session
parent56ad2db02055955f926fda0e4a89055b22ead6f9 (diff)
downloadopencode-ff4414bb152acfddb5c0eb073c38bedc1df4ae14.tar.gz
opencode-ff4414bb152acfddb5c0eb073c38bedc1df4ae14.zip
chore: refactor packages/app files (#13236)
Co-authored-by: opencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com> Co-authored-by: Frank <[email protected]>
Diffstat (limited to 'packages/app/src/pages/session')
-rw-r--r--packages/app/src/pages/session/file-tabs.tsx94
-rw-r--r--packages/app/src/pages/session/message-timeline.tsx78
-rw-r--r--packages/app/src/pages/session/review-tab.tsx14
-rw-r--r--packages/app/src/pages/session/session-mobile-tabs.tsx14
-rw-r--r--packages/app/src/pages/session/session-prompt-dock.tsx9
-rw-r--r--packages/app/src/pages/session/session-side-panel.tsx24
-rw-r--r--packages/app/src/pages/session/terminal-panel.tsx5
-rw-r--r--packages/app/src/pages/session/use-session-commands.tsx152
8 files changed, 176 insertions, 214 deletions
diff --git a/packages/app/src/pages/session/file-tabs.tsx b/packages/app/src/pages/session/file-tabs.tsx
index 0c8281a66..c94c0ff35 100644
--- a/packages/app/src/pages/session/file-tabs.tsx
+++ b/packages/app/src/pages/session/file-tabs.tsx
@@ -12,6 +12,13 @@ import { useFile, type SelectedLineRange } from "@/context/file"
import { useComments } from "@/context/comments"
import { useLanguage } from "@/context/language"
+const formatCommentLabel = (range: SelectedLineRange) => {
+ const start = Math.min(range.start, range.end)
+ const end = Math.max(range.start, range.end)
+ if (start === end) return `line ${start}`
+ return `lines ${start}-${end}`
+}
+
export function FileTabContent(props: {
tab: string
activeTab: () => string
@@ -76,7 +83,6 @@ export function FileTabContent(props: {
showToast({
variant: "error",
title: props.language.t("toast.file.loadFailed.title"),
- description: "Invalid base64 content.",
})
})
const svgPreviewUrl = createMemo(() => {
@@ -116,34 +122,6 @@ export function FileTabContent(props: {
draftTop: undefined as number | undefined,
})
- const openedComment = () => note.openedComment
- const setOpenedComment = (
- value: typeof note.openedComment | ((value: typeof note.openedComment) => typeof note.openedComment),
- ) => setNote("openedComment", value)
-
- const commenting = () => note.commenting
- const setCommenting = (value: typeof note.commenting | ((value: typeof note.commenting) => typeof note.commenting)) =>
- setNote("commenting", value)
-
- const draft = () => note.draft
- const setDraft = (value: typeof note.draft | ((value: typeof note.draft) => typeof note.draft)) =>
- setNote("draft", value)
-
- const positions = () => note.positions
- const setPositions = (value: typeof note.positions | ((value: typeof note.positions) => typeof note.positions)) =>
- setNote("positions", value)
-
- const draftTop = () => note.draftTop
- const setDraftTop = (value: typeof note.draftTop | ((value: typeof note.draftTop) => typeof note.draftTop)) =>
- setNote("draftTop", value)
-
- const commentLabel = (range: SelectedLineRange) => {
- const start = Math.min(range.start, range.end)
- const end = Math.max(range.start, range.end)
- if (start === end) return `line ${start}`
- return `lines ${start}-${end}`
- }
-
const getRoot = () => {
const el = wrap
if (!el) return
@@ -174,8 +152,8 @@ export function FileTabContent(props: {
const el = wrap
const root = getRoot()
if (!el || !root) {
- setPositions({})
- setDraftTop(undefined)
+ setNote("positions", {})
+ setNote("draftTop", undefined)
return
}
@@ -186,21 +164,21 @@ export function FileTabContent(props: {
next[comment.id] = markerTop(el, marker)
}
- setPositions(next)
+ setNote("positions", next)
- const range = commenting()
+ const range = note.commenting
if (!range) {
- setDraftTop(undefined)
+ setNote("draftTop", undefined)
return
}
const marker = findMarker(root, range)
if (!marker) {
- setDraftTop(undefined)
+ setNote("draftTop", undefined)
return
}
- setDraftTop(markerTop(el, marker))
+ setNote("draftTop", markerTop(el, marker))
}
const scheduleComments = () => {
@@ -213,10 +191,10 @@ export function FileTabContent(props: {
})
createEffect(() => {
- const range = commenting()
+ const range = note.commenting
scheduleComments()
if (!range) return
- setDraft("")
+ setNote("draft", "")
})
createEffect(() => {
@@ -229,8 +207,8 @@ export function FileTabContent(props: {
const target = fileComments().find((comment) => comment.id === focus.id)
if (!target) return
- setOpenedComment(target.id)
- setCommenting(null)
+ setNote("openedComment", target.id)
+ setNote("commenting", null)
props.file.setSelectedLines(p, target.selection)
requestAnimationFrame(() => props.comments.clearFocus())
})
@@ -390,16 +368,16 @@ export function FileTabContent(props: {
const p = path()
if (!p) return
props.file.setSelectedLines(p, range)
- if (!range) setCommenting(null)
+ if (!range) setNote("commenting", null)
}}
onLineSelectionEnd={(range: SelectedLineRange | null) => {
if (!range) {
- setCommenting(null)
+ setNote("commenting", null)
return
}
- setOpenedComment(null)
- setCommenting(range)
+ setNote("openedComment", null)
+ setNote("commenting", range)
}}
overflow="scroll"
class="select-text"
@@ -408,10 +386,10 @@ export function FileTabContent(props: {
{(comment) => (
<LineCommentView
id={comment.id}
- top={positions()[comment.id]}
- open={openedComment() === comment.id}
+ top={note.positions[comment.id]}
+ open={note.openedComment === comment.id}
comment={comment.comment}
- selection={commentLabel(comment.selection)}
+ selection={formatCommentLabel(comment.selection)}
onMouseEnter={() => {
const p = path()
if (!p) return
@@ -420,22 +398,22 @@ export function FileTabContent(props: {
onClick={() => {
const p = path()
if (!p) return
- setCommenting(null)
- setOpenedComment((current) => (current === comment.id ? null : comment.id))
+ setNote("commenting", null)
+ setNote("openedComment", (current) => (current === comment.id ? null : comment.id))
props.file.setSelectedLines(p, comment.selection)
}}
/>
)}
</For>
- <Show when={commenting()}>
+ <Show when={note.commenting}>
{(range) => (
- <Show when={draftTop() !== undefined}>
+ <Show when={note.draftTop !== undefined}>
<LineCommentEditor
- top={draftTop()}
- value={draft()}
- selection={commentLabel(range())}
- onInput={(value) => setDraft(value)}
- onCancel={() => setCommenting(null)}
+ top={note.draftTop}
+ value={note.draft}
+ selection={formatCommentLabel(range())}
+ onInput={(value) => setNote("draft", value)}
+ onCancel={() => setNote("commenting", null)}
onSubmit={(value) => {
const p = path()
if (!p) return
@@ -445,7 +423,7 @@ export function FileTabContent(props: {
comment: value,
origin: "file",
})
- setCommenting(null)
+ setNote("commenting", null)
}}
onPopoverFocusOut={(e: FocusEvent) => {
const current = e.currentTarget as HTMLDivElement
@@ -454,7 +432,7 @@ export function FileTabContent(props: {
setTimeout(() => {
if (!document.activeElement || !current.contains(document.activeElement)) {
- setCommenting(null)
+ setNote("commenting", null)
}
}, 0)
}}
diff --git a/packages/app/src/pages/session/message-timeline.tsx b/packages/app/src/pages/session/message-timeline.tsx
index a4ca06dd5..d5f04ccf9 100644
--- a/packages/app/src/pages/session/message-timeline.tsx
+++ b/packages/app/src/pages/session/message-timeline.tsx
@@ -9,6 +9,37 @@ import { SessionTurn } from "@opencode-ai/ui/session-turn"
import type { UserMessage } from "@opencode-ai/sdk/v2"
import { shouldMarkBoundaryGesture, normalizeWheelDelta } from "@/pages/session/message-gesture"
+const boundaryTarget = (root: HTMLElement, target: EventTarget | null) => {
+ const current = target instanceof Element ? target : undefined
+ const nested = current?.closest("[data-scrollable]")
+ if (!nested || nested === root) return root
+ if (!(nested instanceof HTMLElement)) return root
+ return nested
+}
+
+const markBoundaryGesture = (input: {
+ root: HTMLDivElement
+ target: EventTarget | null
+ delta: number
+ onMarkScrollGesture: (target?: EventTarget | null) => void
+}) => {
+ const target = boundaryTarget(input.root, input.target)
+ if (target === input.root) {
+ input.onMarkScrollGesture(input.root)
+ return
+ }
+ if (
+ shouldMarkBoundaryGesture({
+ delta: input.delta,
+ scrollTop: target.scrollTop,
+ scrollHeight: target.scrollHeight,
+ clientHeight: target.clientHeight,
+ })
+ ) {
+ input.onMarkScrollGesture(input.root)
+ }
+}
+
export function MessageTimeline(props: {
mobileChanges: boolean
mobileFallback: JSX.Element
@@ -86,35 +117,13 @@ export function MessageTimeline(props: {
ref={props.setScrollRef}
onWheel={(e) => {
const root = e.currentTarget
- const target = e.target instanceof Element ? e.target : undefined
- const nested = target?.closest("[data-scrollable]")
- if (!nested || nested === root) {
- props.onMarkScrollGesture(root)
- return
- }
-
- if (!(nested instanceof HTMLElement)) {
- props.onMarkScrollGesture(root)
- return
- }
-
const delta = normalizeWheelDelta({
deltaY: e.deltaY,
deltaMode: e.deltaMode,
rootHeight: root.clientHeight,
})
if (!delta) return
-
- if (
- shouldMarkBoundaryGesture({
- delta,
- scrollTop: nested.scrollTop,
- scrollHeight: nested.scrollHeight,
- clientHeight: nested.clientHeight,
- })
- ) {
- props.onMarkScrollGesture(root)
- }
+ markBoundaryGesture({ root, target: e.target, delta, onMarkScrollGesture: props.onMarkScrollGesture })
}}
onTouchStart={(e) => {
touchGesture = e.touches[0]?.clientY
@@ -129,28 +138,7 @@ export function MessageTimeline(props: {
if (!delta) return
const root = e.currentTarget
- const target = e.target instanceof Element ? e.target : undefined
- const nested = target?.closest("[data-scrollable]")
- if (!nested || nested === root) {
- props.onMarkScrollGesture(root)
- return
- }
-
- if (!(nested instanceof HTMLElement)) {
- props.onMarkScrollGesture(root)
- return
- }
-
- if (
- shouldMarkBoundaryGesture({
- delta,
- scrollTop: nested.scrollTop,
- scrollHeight: nested.scrollHeight,
- clientHeight: nested.clientHeight,
- })
- ) {
- props.onMarkScrollGesture(root)
- }
+ markBoundaryGesture({ root, target: e.target, delta, onMarkScrollGesture: props.onMarkScrollGesture })
}}
onTouchEnd={() => {
touchGesture = undefined
diff --git a/packages/app/src/pages/session/review-tab.tsx b/packages/app/src/pages/session/review-tab.tsx
index 72518c68e..634491c72 100644
--- a/packages/app/src/pages/session/review-tab.tsx
+++ b/packages/app/src/pages/session/review-tab.tsx
@@ -1,4 +1,5 @@
-import { createEffect, on, onCleanup, createSignal, type JSX } from "solid-js"
+import { createEffect, on, onCleanup, type JSX } from "solid-js"
+import { createStore } from "solid-js/store"
import type { FileDiff } from "@opencode-ai/sdk/v2"
import { SessionReview } from "@opencode-ai/ui/session-review"
import type { SelectedLineRange } from "@/context/file"
@@ -30,7 +31,7 @@ export interface SessionReviewTabProps {
}
export function StickyAddButton(props: { children: JSX.Element }) {
- const [stuck, setStuck] = createSignal(false)
+ const [state, setState] = createStore({ stuck: false })
let button: HTMLDivElement | undefined
createEffect(() => {
@@ -43,7 +44,7 @@ export function StickyAddButton(props: { children: JSX.Element }) {
const handler = () => {
const rect = node.getBoundingClientRect()
const scrollRect = scroll.getBoundingClientRect()
- setStuck(rect.right >= scrollRect.right && scroll.scrollWidth > scroll.clientWidth)
+ setState("stuck", rect.right >= scrollRect.right && scroll.scrollWidth > scroll.clientWidth)
}
scroll.addEventListener("scroll", handler, { passive: true })
@@ -60,7 +61,7 @@ export function StickyAddButton(props: { children: JSX.Element }) {
<div
ref={button}
class="bg-background-base h-full shrink-0 sticky right-0 z-10 flex items-center justify-center border-b border-border-weak-base px-3"
- classList={{ "border-l": stuck() }}
+ classList={{ "border-l": state.stuck }}
>
{props.children}
</div>
@@ -78,7 +79,10 @@ export function SessionReviewTab(props: SessionReviewTabProps) {
return sdk.client.file
.read({ path })
.then((x) => x.data)
- .catch(() => undefined)
+ .catch((error) => {
+ console.debug("[session-review] failed to read file", { path, error })
+ return undefined
+ })
}
const restoreScroll = () => {
diff --git a/packages/app/src/pages/session/session-mobile-tabs.tsx b/packages/app/src/pages/session/session-mobile-tabs.tsx
index 41f058231..6afe8024a 100644
--- a/packages/app/src/pages/session/session-mobile-tabs.tsx
+++ b/packages/app/src/pages/session/session-mobile-tabs.tsx
@@ -1,8 +1,9 @@
-import { Match, Show, Switch } from "solid-js"
+import { Show } from "solid-js"
import { Tabs } from "@opencode-ai/ui/tabs"
export function SessionMobileTabs(props: {
open: boolean
+ mobileTab: "session" | "changes"
hasReview: boolean
reviewCount: number
onSession: () => void
@@ -11,7 +12,7 @@ export function SessionMobileTabs(props: {
}) {
return (
<Show when={props.open}>
- <Tabs class="h-auto">
+ <Tabs value={props.mobileTab} class="h-auto">
<Tabs.List>
<Tabs.Trigger value="session" class="w-1/2" classes={{ button: "w-full" }} onClick={props.onSession}>
{props.t("session.tab.session")}
@@ -22,12 +23,9 @@ export function SessionMobileTabs(props: {
classes={{ button: "w-full" }}
onClick={props.onChanges}
>
- <Switch>
- <Match when={props.hasReview}>
- {props.t("session.review.filesChanged", { count: props.reviewCount })}
- </Match>
- <Match when={true}>{props.t("session.review.change.other")}</Match>
- </Switch>
+ {props.hasReview
+ ? props.t("session.review.filesChanged", { count: props.reviewCount })
+ : props.t("session.review.change.other")}
</Tabs.Trigger>
</Tabs.List>
</Tabs>
diff --git a/packages/app/src/pages/session/session-prompt-dock.tsx b/packages/app/src/pages/session/session-prompt-dock.tsx
index eaf0564b2..8ec4f3b9f 100644
--- a/packages/app/src/pages/session/session-prompt-dock.tsx
+++ b/packages/app/src/pages/session/session-prompt-dock.tsx
@@ -1,15 +1,14 @@
-import { For, Show, type ComponentProps } from "solid-js"
+import { For, Show } from "solid-js"
+import type { QuestionRequest } 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"
-const questionDockRequest = (value: unknown) => value as ComponentProps<typeof QuestionDock>["request"]
-
export function SessionPromptDock(props: {
centered: boolean
- questionRequest: () => { questions: unknown[] } | undefined
+ questionRequest: () => QuestionRequest | undefined
permissionRequest: () => { patterns: string[]; permission: string } | undefined
blocked: boolean
promptReady: boolean
@@ -48,7 +47,7 @@ export function SessionPromptDock(props: {
subtitle,
}}
/>
- <QuestionDock request={questionDockRequest(req)} />
+ <QuestionDock request={req} />
</div>
)
}}
diff --git a/packages/app/src/pages/session/session-side-panel.tsx b/packages/app/src/pages/session/session-side-panel.tsx
index d9460cc1a..15ad90ffe 100644
--- a/packages/app/src/pages/session/session-side-panel.tsx
+++ b/packages/app/src/pages/session/session-side-panel.tsx
@@ -21,6 +21,14 @@ import { useFile, type SelectedLineRange } from "@/context/file"
import { useLanguage } from "@/context/language"
import { useLayout } from "@/context/layout"
import { useSync } from "@/context/sync"
+import type { Message, UserMessage } from "@opencode-ai/sdk/v2/client"
+
+type SessionSidePanelViewModel = {
+ messages: () => Message[]
+ visibleUserMessages: () => UserMessage[]
+ view: () => ReturnType<ReturnType<typeof useLayout>["view"]>
+ info: () => ReturnType<ReturnType<typeof useSync>["session"]["get"]>
+}
export function SessionSidePanel(props: {
open: boolean
@@ -31,7 +39,6 @@ export function SessionSidePanel(props: {
dialog: ReturnType<typeof useDialog>
file: ReturnType<typeof useFile>
comments: ReturnType<typeof useComments>
- sync: ReturnType<typeof useSync>
hasReview: boolean
reviewCount: number
reviewTab: boolean
@@ -43,10 +50,7 @@ export function SessionSidePanel(props: {
openTab: (value: string) => void
showAllFiles: () => void
reviewPanel: () => JSX.Element
- messages: () => unknown[]
- visibleUserMessages: () => unknown[]
- view: () => ReturnType<ReturnType<typeof useLayout>["view"]>
- info: () => unknown
+ vm: SessionSidePanelViewModel
handoffFiles: () => Record<string, SelectedLineRange | null> | undefined
codeComponent: NonNullable<ValidComponent>
addCommentToContext: (input: {
@@ -187,10 +191,10 @@ export function SessionSidePanel(props: {
<Show when={props.activeTab() === "context"}>
<div class="relative pt-2 flex-1 min-h-0 overflow-hidden">
<SessionContextTab
- messages={props.messages as never}
- visibleUserMessages={props.visibleUserMessages as never}
- view={props.view as never}
- info={props.info as never}
+ messages={props.vm.messages}
+ visibleUserMessages={props.vm.visibleUserMessages}
+ view={props.vm.view}
+ info={props.vm.info}
/>
</div>
</Show>
@@ -203,7 +207,7 @@ export function SessionSidePanel(props: {
tab={tab}
activeTab={props.activeTab}
tabs={props.tabs}
- view={props.view}
+ view={props.vm.view}
handoffFiles={props.handoffFiles}
file={props.file}
comments={props.comments}
diff --git a/packages/app/src/pages/session/terminal-panel.tsx b/packages/app/src/pages/session/terminal-panel.tsx
index 2e65fde0e..d3475c714 100644
--- a/packages/app/src/pages/session/terminal-panel.tsx
+++ b/packages/app/src/pages/session/terminal-panel.tsx
@@ -1,4 +1,4 @@
-import { createMemo, For, Show } from "solid-js"
+import { For, Show } from "solid-js"
import { Tabs } from "@opencode-ai/ui/tabs"
import { ResizeHandle } from "@opencode-ai/ui/resize-handle"
import { IconButton } from "@opencode-ai/ui/icon-button"
@@ -141,9 +141,8 @@ export function TerminalPanel(props: {
<DragOverlay>
<Show when={props.activeTerminalDraggable()}>
{(draggedId) => {
- const pty = createMemo(() => props.terminal.all().find((t: LocalPTY) => t.id === draggedId()))
return (
- <Show when={pty()}>
+ <Show when={props.terminal.all().find((t: LocalPTY) => t.id === draggedId())}>
{(t) => (
<div class="relative p-1 h-10 flex items-center bg-background-stronger text-14-regular">
{terminalTabLabel({
diff --git a/packages/app/src/pages/session/use-session-commands.tsx b/packages/app/src/pages/session/use-session-commands.tsx
index d52022d73..81c71133f 100644
--- a/packages/app/src/pages/session/use-session-commands.tsx
+++ b/packages/app/src/pages/session/use-session-commands.tsx
@@ -1,8 +1,8 @@
import { createMemo } from "solid-js"
import { useNavigate, useParams } from "@solidjs/router"
-import { useCommand } from "@/context/command"
+import { useCommand, type CommandOption } from "@/context/command"
import { useDialog } from "@opencode-ai/ui/context/dialog"
-import { useFile, selectionFromLines, type FileSelection } from "@/context/file"
+import { useFile, selectionFromLines, type FileSelection, type SelectedLineRange } from "@/context/file"
import { useLanguage } from "@/context/language"
import { useLayout } from "@/context/layout"
import { useLocal } from "@/context/local"
@@ -22,7 +22,7 @@ import { UserMessage } from "@opencode-ai/sdk/v2"
import { combineCommandSections } from "@/pages/session/helpers"
import { canAddSelectionContext } from "@/pages/session/session-command-helpers"
-export const useSessionCommands = (input: {
+export type SessionCommandContext = {
command: ReturnType<typeof useCommand>
dialog: ReturnType<typeof useDialog>
file: ReturnType<typeof useFile>
@@ -49,32 +49,48 @@ export const useSessionCommands = (input: {
setActiveMessage: (message: UserMessage | undefined) => void
addSelectionToContext: (path: string, selection: FileSelection) => void
focusInput: () => void
-}) => {
+}
+
+const withCategory = (category: string) => {
+ return (option: Omit<CommandOption, "category">): CommandOption => ({
+ ...option,
+ category,
+ })
+}
+
+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: input.language.t("command.session.new"),
- category: input.language.t("command.category.session"),
keybind: "mod+shift+s",
slash: "new",
onSelect: () => input.navigate(`/${input.params.dir}/session`),
- },
+ }),
])
const fileCommands = createMemo(() => [
- {
+ fileCommand({
id: "file.open",
title: input.language.t("command.file.open"),
description: input.language.t("palette.search.placeholder"),
- category: input.language.t("command.category.file"),
keybind: "mod+p",
slash: "open",
onSelect: () => input.dialog.show(() => <DialogSelectFile onOpenFile={input.showAllFiles} />),
- },
- {
+ }),
+ fileCommand({
id: "tab.close",
title: input.language.t("command.tab.close"),
- category: input.language.t("command.category.file"),
keybind: "mod+w",
disabled: !input.tabs().active(),
onSelect: () => {
@@ -82,15 +98,14 @@ export const useSessionCommands = (input: {
if (!active) return
input.tabs().close(active)
},
- },
+ }),
])
const contextCommands = createMemo(() => [
- {
+ contextCommand({
id: "context.addSelection",
title: input.language.t("command.context.addSelection"),
description: input.language.t("command.context.addSelection.description"),
- category: input.language.t("command.category.context"),
keybind: "mod+shift+l",
disabled: !canAddSelectionContext({
active: input.tabs().active(),
@@ -103,7 +118,7 @@ export const useSessionCommands = (input: {
const path = input.file.pathFromTab(active)
if (!path) return
- const range = input.file.selectedLines(path)
+ const range = input.file.selectedLines(path) as SelectedLineRange | null | undefined
if (!range) {
showToast({
title: input.language.t("toast.context.noLineSelection.title"),
@@ -114,58 +129,49 @@ export const useSessionCommands = (input: {
input.addSelectionToContext(path, selectionFromLines(range))
},
- },
+ }),
])
const viewCommands = createMemo(() => [
- {
+ viewCommand({
id: "terminal.toggle",
title: input.language.t("command.terminal.toggle"),
- description: "",
- category: input.language.t("command.category.view"),
keybind: "ctrl+`",
slash: "terminal",
onSelect: () => input.view().terminal.toggle(),
- },
- {
+ }),
+ viewCommand({
id: "review.toggle",
title: input.language.t("command.review.toggle"),
- description: "",
- category: input.language.t("command.category.view"),
keybind: "mod+shift+r",
onSelect: () => input.view().reviewPanel.toggle(),
- },
- {
+ }),
+ viewCommand({
id: "fileTree.toggle",
title: input.language.t("command.fileTree.toggle"),
- description: "",
- category: input.language.t("command.category.view"),
keybind: "mod+\\",
onSelect: () => input.layout.fileTree.toggle(),
- },
- {
+ }),
+ viewCommand({
id: "input.focus",
title: input.language.t("command.input.focus"),
- category: input.language.t("command.category.view"),
keybind: "ctrl+l",
onSelect: () => input.focusInput(),
- },
- {
+ }),
+ terminalCommand({
id: "terminal.new",
title: input.language.t("command.terminal.new"),
description: input.language.t("command.terminal.new.description"),
- category: input.language.t("command.category.terminal"),
keybind: "ctrl+alt+t",
onSelect: () => {
if (input.terminal.all().length > 0) input.terminal.new()
input.view().terminal.open()
},
- },
- {
+ }),
+ viewCommand({
id: "steps.toggle",
title: input.language.t("command.steps.toggle"),
description: input.language.t("command.steps.toggle.description"),
- category: input.language.t("command.category.view"),
keybind: "mod+e",
slash: "steps",
disabled: !input.params.id,
@@ -174,86 +180,78 @@ export const useSessionCommands = (input: {
if (!msg) return
input.setExpanded(msg.id, (open: boolean | undefined) => !open)
},
- },
+ }),
])
const messageCommands = createMemo(() => [
- {
+ sessionCommand({
id: "message.previous",
title: input.language.t("command.message.previous"),
description: input.language.t("command.message.previous.description"),
- category: input.language.t("command.category.session"),
keybind: "mod+arrowup",
disabled: !input.params.id,
onSelect: () => input.navigateMessageByOffset(-1),
- },
- {
+ }),
+ sessionCommand({
id: "message.next",
title: input.language.t("command.message.next"),
description: input.language.t("command.message.next.description"),
- category: input.language.t("command.category.session"),
keybind: "mod+arrowdown",
disabled: !input.params.id,
onSelect: () => input.navigateMessageByOffset(1),
- },
+ }),
])
const agentCommands = createMemo(() => [
- {
+ modelCommand({
id: "model.choose",
title: input.language.t("command.model.choose"),
description: input.language.t("command.model.choose.description"),
- category: input.language.t("command.category.model"),
keybind: "mod+'",
slash: "model",
onSelect: () => input.dialog.show(() => <DialogSelectModel />),
- },
- {
+ }),
+ mcpCommand({
id: "mcp.toggle",
title: input.language.t("command.mcp.toggle"),
description: input.language.t("command.mcp.toggle.description"),
- category: input.language.t("command.category.mcp"),
keybind: "mod+;",
slash: "mcp",
onSelect: () => input.dialog.show(() => <DialogSelectMcp />),
- },
- {
+ }),
+ agentCommand({
id: "agent.cycle",
title: input.language.t("command.agent.cycle"),
description: input.language.t("command.agent.cycle.description"),
- category: input.language.t("command.category.agent"),
keybind: "mod+.",
slash: "agent",
onSelect: () => input.local.agent.move(1),
- },
- {
+ }),
+ agentCommand({
id: "agent.cycle.reverse",
title: input.language.t("command.agent.cycle.reverse"),
description: input.language.t("command.agent.cycle.reverse.description"),
- category: input.language.t("command.category.agent"),
keybind: "shift+mod+.",
onSelect: () => input.local.agent.move(-1),
- },
- {
+ }),
+ modelCommand({
id: "model.variant.cycle",
title: input.language.t("command.model.variant.cycle"),
description: input.language.t("command.model.variant.cycle.description"),
- category: input.language.t("command.category.model"),
keybind: "shift+mod+d",
onSelect: () => {
input.local.model.variant.cycle()
},
- },
+ }),
])
const permissionCommands = createMemo(() => [
- {
+ permissionsCommand({
id: "permissions.autoaccept",
title:
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"),
- category: input.language.t("command.category.permissions"),
keybind: "mod+shift+a",
disabled: !input.params.id || !input.permission.permissionsEnabled(),
onSelect: () => {
@@ -269,15 +267,14 @@ export const useSessionCommands = (input: {
: input.language.t("toast.permissions.autoaccept.off.description"),
})
},
- },
+ }),
])
const sessionActionCommands = createMemo(() => [
- {
+ sessionCommand({
id: "session.undo",
title: input.language.t("command.session.undo"),
description: input.language.t("command.session.undo.description"),
- category: input.language.t("command.category.session"),
slash: "undo",
disabled: !input.params.id || input.visibleUserMessages().length === 0,
onSelect: async () => {
@@ -298,12 +295,11 @@ export const useSessionCommands = (input: {
const priorMessage = findLast(input.userMessages(), (x) => x.id < message.id)
input.setActiveMessage(priorMessage)
},
- },
- {
+ }),
+ sessionCommand({
id: "session.redo",
title: input.language.t("command.session.redo"),
description: input.language.t("command.session.redo.description"),
- category: input.language.t("command.category.session"),
slash: "redo",
disabled: !input.params.id || !input.info()?.revert?.messageID,
onSelect: async () => {
@@ -323,12 +319,11 @@ export const useSessionCommands = (input: {
const priorMsg = findLast(input.userMessages(), (x) => x.id < nextMessage.id)
input.setActiveMessage(priorMsg)
},
- },
- {
+ }),
+ sessionCommand({
id: "session.compact",
title: input.language.t("command.session.compact"),
description: input.language.t("command.session.compact.description"),
- category: input.language.t("command.category.session"),
slash: "compact",
disabled: !input.params.id || input.visibleUserMessages().length === 0,
onSelect: async () => {
@@ -348,22 +343,21 @@ export const useSessionCommands = (input: {
providerID: model.provider.id,
})
},
- },
- {
+ }),
+ sessionCommand({
id: "session.fork",
title: input.language.t("command.session.fork"),
description: input.language.t("command.session.fork.description"),
- category: input.language.t("command.category.session"),
slash: "fork",
disabled: !input.params.id || input.visibleUserMessages().length === 0,
onSelect: () => input.dialog.show(() => <DialogFork />),
- },
+ }),
])
const shareCommands = createMemo(() => {
if (input.sync.data.config.share === "disabled") return []
return [
- {
+ sessionCommand({
id: "session.share",
title: input.info()?.share?.url
? input.language.t("session.share.copy.copyLink")
@@ -371,7 +365,6 @@ export const useSessionCommands = (input: {
description: input.info()?.share?.url
? input.language.t("toast.session.share.success.description")
: input.language.t("command.session.share.description"),
- category: input.language.t("command.category.session"),
slash: "share",
disabled: !input.params.id,
onSelect: async () => {
@@ -441,12 +434,11 @@ export const useSessionCommands = (input: {
await copy(url, false)
},
- },
- {
+ }),
+ sessionCommand({
id: "session.unshare",
title: input.language.t("command.session.unshare"),
description: input.language.t("command.session.unshare.description"),
- category: input.language.t("command.category.session"),
slash: "unshare",
disabled: !input.params.id || !input.info()?.share?.url,
onSelect: async () => {
@@ -468,7 +460,7 @@ export const useSessionCommands = (input: {
}),
)
},
- },
+ }),
]
})