Skip to content

Commit 1dbb4a9

Browse files
authored
fix(ui): support draft prompt command sessions (#446)
## Summary - Show attachments added to the no-session draft prompt before a session exists. - Create and activate a real session before first-prompt slash commands or shell commands execute. - Keep existing session command behavior unchanged by adding an optional PromptInput command handler. ## Validation - npm run typecheck --workspace @codenomad/ui
1 parent d3950df commit 1dbb4a9

3 files changed

Lines changed: 53 additions & 3 deletions

File tree

packages/ui/src/components/instance/instance-shell2.tsx

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import PermissionNotificationBanner from "../permission-notification-banner"
2929
import PermissionApprovalModal from "../permission-approval-modal"
3030
import SessionView from "../session/session-view"
3131
import MessageSection from "../message-section"
32+
import PromptAttachmentsBar from "../prompt-input/PromptAttachmentsBar"
3233
import { formatTokenTotal } from "../../lib/formatters"
3334
import ContextMeter from "../context-meter"
3435
import { sseManager } from "../../lib/sse-manager"
@@ -50,7 +51,8 @@ import type { Attachment } from "../../types/attachment"
5051
import { setAgentModelPreference, useConfig } from "../../stores/preferences"
5152
import { showPromptDialog } from "../../stores/alerts"
5253
import { openSessionPreview, sessionPreviews, showSessionChat, showSessionPreview } from "../../stores/session-previews"
53-
import { createSession, getDefaultModel, providers, sendMessage, setActiveParentSession, updateSessionModel } from "../../stores/sessions"
54+
import { createSession, executeCustomCommand, getDefaultModel, providers, runShellCommand, sendMessage, setActiveParentSession, updateSessionModel } from "../../stores/sessions"
55+
import { getAttachments, removeAttachment } from "../../stores/attachments"
5456

5557
import type { LayoutMode } from "./shell/types"
5658
import {
@@ -123,6 +125,7 @@ const InstanceShell2: Component<InstanceShellProps> = (props) => {
123125
const [draftAgent, setDraftAgent] = createSignal("")
124126
const [draftModel, setDraftModel] = createSignal({ providerId: "", modelId: "" })
125127
const [draftModelManuallySelected, setDraftModelManuallySelected] = createSignal(false)
128+
const [draftPromptInputApi, setDraftPromptInputApi] = createSignal<PromptInputApi | null>(null)
126129

127130
// Worktree selector manages its own dialogs.
128131
const [showSessionSearch, setShowSessionSearch] = createSignal(false)
@@ -805,7 +808,16 @@ const InstanceShell2: Component<InstanceShellProps> = (props) => {
805808
setDraftModelManuallySelected(true)
806809
}
807810

808-
async function handleFirstPromptSend(prompt: string, attachments: Attachment[]) {
811+
const draftAttachments = createMemo(() => getAttachments(props.instance.id, NO_SESSION_DRAFT_SESSION_ID))
812+
813+
function registerDraftPromptInputApi(api: PromptInputApi) {
814+
setDraftPromptInputApi(api)
815+
return () => {
816+
setDraftPromptInputApi((current) => (current === api ? null : current))
817+
}
818+
}
819+
820+
async function createAndActivateDraftSession() {
809821
const agent = draftAgent()
810822
const model = draftModel()
811823
if (agent && model.providerId && model.modelId) {
@@ -816,9 +828,24 @@ const InstanceShell2: Component<InstanceShellProps> = (props) => {
816828
await updateSessionModel(props.instance.id, session.id, model)
817829
}
818830
setActiveParentSession(props.instance.id, session.id)
831+
return session
832+
}
833+
834+
async function handleFirstPromptSend(prompt: string, attachments: Attachment[]) {
835+
const session = await createAndActivateDraftSession()
819836
await sendMessage(props.instance.id, session.id, prompt, attachments)
820837
}
821838

839+
async function handleFirstPromptCommand(commandName: string, args: string) {
840+
const session = await createAndActivateDraftSession()
841+
await executeCustomCommand(props.instance.id, session.id, commandName, args)
842+
}
843+
844+
async function handleFirstPromptShell(command: string) {
845+
const session = await createAndActivateDraftSession()
846+
await runShellCommand(props.instance.id, session.id, command)
847+
}
848+
822849
const sessionLayout = (
823850
<div
824851
class="session-shell-panels flex flex-1 min-h-0 overflow-x-hidden"
@@ -1079,14 +1106,32 @@ const InstanceShell2: Component<InstanceShellProps> = (props) => {
10791106
forceCompactStatusLayout={showEmbeddedSidebarToggle()}
10801107
/>
10811108

1109+
<Show when={draftAttachments().length > 0}>
1110+
<PromptAttachmentsBar
1111+
attachments={draftAttachments()}
1112+
onRemoveAttachment={(attachmentId) => {
1113+
const api = draftPromptInputApi()
1114+
if (api) {
1115+
api.removeAttachment(attachmentId)
1116+
return
1117+
}
1118+
removeAttachment(props.instance.id, NO_SESSION_DRAFT_SESSION_ID, attachmentId)
1119+
}}
1120+
onExpandTextAttachment={(attachmentId) => draftPromptInputApi()?.expandTextAttachment(attachmentId)}
1121+
/>
1122+
</Show>
1123+
10821124
<PromptInput
10831125
instanceId={props.instance.id}
10841126
instanceFolder={props.instance.folder}
10851127
sessionId={NO_SESSION_DRAFT_SESSION_ID}
10861128
isActive={props.isActiveInstance}
10871129
compactLayout={compactPromptLayout()}
10881130
onSend={handleFirstPromptSend}
1131+
onCommand={handleFirstPromptCommand}
1132+
onRunShell={handleFirstPromptShell}
10891133
escapeInDebounce={props.escapeInDebounce}
1134+
registerPromptInputApi={registerDraftPromptInputApi}
10901135
/>
10911136
</div>
10921137
}

packages/ui/src/components/prompt-input.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -357,7 +357,11 @@ export default function PromptInput(props: PromptInputProps) {
357357
await props.onSend(resolvedPrompt, [])
358358
}
359359
} else if (isKnownSlashCommand) {
360-
await executeCustomCommand(props.instanceId, props.sessionId, commandName, resolvedCommandArgs)
360+
if (props.onCommand) {
361+
await props.onCommand(commandName, resolvedCommandArgs)
362+
} else {
363+
await executeCustomCommand(props.instanceId, props.sessionId, commandName, resolvedCommandArgs)
364+
}
361365
} else {
362366
await props.onSend(resolvedPrompt, currentAttachments)
363367
}

packages/ui/src/components/prompt-input/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export interface PromptInputProps {
2525
// Phone/tablet layouts should keep the expanded prompt more compact.
2626
compactLayout?: boolean
2727
onSend: (prompt: string, attachments: Attachment[]) => Promise<void>
28+
onCommand?: (commandName: string, args: string) => Promise<void>
2829
onRunShell?: (command: string) => Promise<void>
2930
disabled?: boolean
3031
escapeInDebounce?: boolean

0 commit comments

Comments
 (0)