diff options
| author | Adam <[email protected]> | 2026-03-12 15:17:36 -0500 |
|---|---|---|
| committer | GitHub <[email protected]> | 2026-03-12 20:17:36 +0000 |
| commit | 42a5af6c8f6998277cf69270ad12e2a64edac5d3 (patch) | |
| tree | eca5dff51dc694cce1e783425fc11b4bea1e6a12 /packages/app/src/pages/session/composer | |
| parent | f0542fae7a917fabb9e943c3112a3d0b4b03302d (diff) | |
| download | opencode-42a5af6c8f6998277cf69270ad12e2a64edac5d3.tar.gz opencode-42a5af6c8f6998277cf69270ad12e2a64edac5d3.zip | |
feat(app): follow-up behavior (#17233)
Diffstat (limited to 'packages/app/src/pages/session/composer')
3 files changed, 137 insertions, 2 deletions
diff --git a/packages/app/src/pages/session/composer/session-composer-region.tsx b/packages/app/src/pages/session/composer/session-composer-region.tsx index 0cca90180..2034fbead 100644 --- a/packages/app/src/pages/session/composer/session-composer-region.tsx +++ b/packages/app/src/pages/session/composer/session-composer-region.tsx @@ -8,9 +8,11 @@ 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" export function SessionComposerRegion(props: { state: SessionComposerState @@ -21,6 +23,17 @@ export function SessionComposerRegion(props: { 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 @@ -214,10 +227,23 @@ export function SessionComposerRegion(props: { "margin-top": `${-lift()}px`, }} > + <Show when={props.followup?.items.length}> + <SessionFollowupDock + items={props.followup!.items} + sending={props.followup!.sending} + onSend={props.followup!.onSend} + onEdit={props.followup!.onEdit} + /> + </Show> <PromptInput ref={props.inputRef} newSessionWorktree={props.newSessionWorktree} onNewSessionWorktreeReset={props.onNewSessionWorktreeReset} + edit={props.followup?.edit} + onEditLoaded={props.followup?.onEditLoaded} + shouldQueue={props.followup?.queue} + onQueue={props.followup?.onQueue} + onAbort={props.followup?.onAbort} onSubmit={props.onSubmit} /> </div> diff --git a/packages/app/src/pages/session/composer/session-followup-dock.tsx b/packages/app/src/pages/session/composer/session-followup-dock.tsx new file mode 100644 index 000000000..7d744f4e6 --- /dev/null +++ b/packages/app/src/pages/session/composer/session-followup-dock.tsx @@ -0,0 +1,109 @@ +import { For, Show, createMemo } from "solid-js" +import { createStore } from "solid-js/store" +import { Button } from "@opencode-ai/ui/button" +import { DockTray } from "@opencode-ai/ui/dock-surface" +import { IconButton } from "@opencode-ai/ui/icon-button" +import { useLanguage } from "@/context/language" + +export function SessionFollowupDock(props: { + items: { id: string; text: string }[] + sending?: string + onSend: (id: string) => void + onEdit: (id: string) => void +}) { + const language = useLanguage() + const [store, setStore] = createStore({ + collapsed: false, + }) + + const toggle = () => setStore("collapsed", (value) => !value) + const total = createMemo(() => props.items.length) + const label = createMemo(() => + language.t(total() === 1 ? "session.followupDock.summary.one" : "session.followupDock.summary.other", { + count: total(), + }), + ) + const preview = createMemo(() => props.items[0]?.text ?? "") + + return ( + <DockTray + data-component="session-followup-dock" + style={{ + "margin-bottom": "-0.875rem", + "border-bottom-left-radius": 0, + "border-bottom-right-radius": 0, + }} + > + <div + class="pl-3 pr-2 py-2 flex items-center gap-2" + role="button" + tabIndex={0} + onClick={toggle} + onKeyDown={(event) => { + if (event.key !== "Enter" && event.key !== " ") return + event.preventDefault() + toggle() + }} + > + <span class="shrink-0 text-13-medium text-text-strong cursor-default">{label()}</span> + <Show when={store.collapsed && preview()}> + <span class="min-w-0 flex-1 truncate text-13-regular text-text-base cursor-default">{preview()}</span> + </Show> + <div class="ml-auto shrink-0"> + <IconButton + data-collapsed={store.collapsed ? "true" : "false"} + icon="chevron-down" + size="normal" + variant="ghost" + style={{ transform: `rotate(${store.collapsed ? 180 : 0}deg)` }} + onMouseDown={(event) => { + event.preventDefault() + event.stopPropagation() + }} + onClick={(event) => { + event.stopPropagation() + toggle() + }} + aria-label={ + store.collapsed ? language.t("session.followupDock.expand") : language.t("session.followupDock.collapse") + } + /> + </div> + </div> + + <Show when={store.collapsed}> + <div class="h-5" aria-hidden="true" /> + </Show> + + <Show when={!store.collapsed}> + <div class="px-3 pb-7 flex flex-col gap-1.5 max-h-42 overflow-y-auto no-scrollbar"> + <For each={props.items}> + {(item) => ( + <div class="flex items-center gap-2 min-w-0 py-1"> + <span class="min-w-0 flex-1 truncate text-13-regular text-text-strong">{item.text}</span> + <Button + size="small" + variant="secondary" + class="shrink-0" + disabled={!!props.sending} + onClick={() => props.onSend(item.id)} + > + {language.t("session.followupDock.sendNow")} + </Button> + <Button + size="small" + variant="ghost" + class="shrink-0" + disabled={!!props.sending} + onClick={() => props.onEdit(item.id)} + > + {language.t("session.followupDock.edit")} + </Button> + </div> + )} + </For> + </div> + </Show> + </DockTray> + ) +} diff --git a/packages/app/src/pages/session/composer/session-revert-dock.tsx b/packages/app/src/pages/session/composer/session-revert-dock.tsx index fd4b39ee7..5a5b8a9cd 100644 --- a/packages/app/src/pages/session/composer/session-revert-dock.tsx +++ b/packages/app/src/pages/session/composer/session-revert-dock.tsx @@ -75,10 +75,10 @@ export function SessionRevertDock(props: { </Show> <Show when={!store.collapsed}> - <div class="px-3 pb-11 flex flex-col gap-1.5 max-h-42 overflow-y-auto no-scrollbar"> + <div class="px-3 pb-7 flex flex-col gap-1.5 max-h-42 overflow-y-auto no-scrollbar"> <For each={props.items}> {(item) => ( - <div class="flex items-center gap-2 min-w-0 rounded-[10px] border border-border-weak-base bg-background-stronger px-2.5 py-2"> + <div class="flex items-center gap-2 min-w-0 py-1"> <span class="min-w-0 flex-1 truncate text-13-regular text-text-strong">{item.text}</span> <Button size="small" |
