auto send prompt

This commit is contained in:
Diego Imbert
2026-07-09 14:04:32 +02:00
parent cd3138163f
commit 2a08aa59b4
4 changed files with 51 additions and 10 deletions
@@ -82,10 +82,11 @@
inputProps={{ rows: 4, placeholder, onkeydown: onKeydown }}
/>
<Button
endIcon={{ icon: ArrowRight }}
endIcon={starting ? {} : { icon: ArrowRight }}
wrapperClasses="absolute right-2 bottom-3.5"
variant={value.trim() ? 'accent' : 'default'}
iconOnly
loading={starting}
disabled={!value.trim() || starting}
onclick={start}
></Button>
@@ -40,7 +40,8 @@
selectSession,
sessionState,
setSessionArchived,
syncWorkspaceTo
syncWorkspaceTo,
takeAutoSendPrompt
} from './sessionState.svelte'
import { getOrCreateRuntime, removeSession } from './sessionRuntime.svelte'
import { goto } from '$lib/navigation'
@@ -73,6 +74,12 @@
// transient draft slot (script-init: AIChatInput reads it once at mount).
const restoredDraftPrompt = peekTransientDraftPrompt(sessionId)
// One-shot: a prompt this session was created to auto-send (home composer).
// Read once at init and cleared; the effect below fires it when the chat is
// ready. Sent through the manager, so the composer stays empty (no prefill) —
// which is why initialInstructions is suppressed when this is set.
const autoSendPrompt = takeAutoSendPrompt(sessionId)
// The workspace the session acts on, shown in the header "Acting on" strip via the shared
// WorkspaceScopeTrigger chip. `targetId` is also the workspace the chip's ellipsis menu targets.
const acting = $derived.by(() => {
@@ -239,6 +246,21 @@
setTimeout(() => chat.focusInput(), 0)
})
// Auto-send the prompt this session was created with, once the chat is ready
// (same readiness gate as the focus effect: mounted + copilot loaded). Latched
// so it fires exactly once; guarded on an empty conversation so it can never
// interleave with a message the user already sent.
let autoSent = false
$effect(() => {
if (!autoSendPrompt || autoSent) return
if (sessionState.currentSessionId !== sessionId) return
if (!aiChat || !$copilotInfo.enabled) return
if (hasFirstUserMessage) return
autoSent = true
const chat = aiChat
setTimeout(() => chat.sendRequest({ instructions: autoSendPrompt }), 0)
})
// True when the session committed to a workspace that's no longer in
// the user's list (deleted / archived / access revoked). The chat is
// disabled and SessionChangesBar shows a move/discard banner.
@@ -414,7 +436,7 @@
hideHeader
hideModeSelector
wideLayout
initialInstructions={restoredDraftPrompt}
initialInstructions={autoSendPrompt ? undefined : restoredDraftPrompt}
onDraftChange={(text) => queueTransientDraftPrompt(sessionId, text)}
forceDisabled={isUnavailable || !!session.archived}
forceDisabledMessage={isUnavailable
@@ -341,6 +341,24 @@ export function peekTransientDraftPrompt(sessionId: string): string | undefined
return transientPrompt?.sessionId === sessionId ? transientPrompt.text : undefined
}
// One-shot intent to auto-send a prompt as soon as a freshly-created session's
// chat is ready (set by startSessionWithPrompt for the home composer). Held in
// memory only — deliberately NOT persisted, so a reload never re-fires a send
// the user didn't just trigger. Consumed once via takeAutoSendPrompt.
let autoSendPrompt: { sessionId: string; text: string } | undefined
export function queueAutoSendPrompt(sessionId: string, text: string): void {
autoSendPrompt = { sessionId, text }
}
// Take (read-and-clear) the auto-send prompt for a session. One-shot: the second
// caller — a re-mount, or a different session — gets undefined.
export function takeAutoSendPrompt(sessionId: string): string | undefined {
if (autoSendPrompt?.sessionId !== sessionId) return undefined
const text = autoSendPrompt.text
autoSendPrompt = undefined
return text
}
// Write-behind a single session record. Transient (unsent) sessions are not
// written to IndexedDB — they live in memory plus a single localStorage draft
// slot until materializeTransient() promotes them at first send.
@@ -3,7 +3,7 @@ import { goto } from '$lib/navigation'
import { workspaceStore } from '$lib/stores'
import {
createSession,
queueTransientDraftPrompt,
queueAutoSendPrompt,
selectSession,
sessionInCurrentFamily,
sessionState,
@@ -50,15 +50,15 @@ export async function enterSessionMode(opts?: { replace?: boolean }): Promise<vo
})
}
// Start a fresh AI session seeded with a prompt composed elsewhere (e.g. the
// home page composer), then route into session mode. The prompt is queued onto
// the transient draft so the session's chat input restores it on mount — the
// same path a reload uses — leaving it composed-but-unsent for the user to
// review and send. No-op routing is fine for a blank prompt; callers guard.
// Start a fresh AI session from a prompt composed elsewhere (e.g. the home page
// composer) and auto-send it, then route into session mode. The prompt is queued
// as a one-shot auto-send intent that SessionWrapper fires once the session's
// chat is ready (mounted + copilot loaded). No-op routing is fine for a blank
// prompt; callers guard.
export async function startSessionWithPrompt(prompt: string): Promise<void> {
const session = createSession()
const text = prompt.trim()
if (text) queueTransientDraftPrompt(session.id, text)
if (text) queueAutoSendPrompt(session.id, text)
selectSession(session.id)
await goto(`/sessions?session_name=${encodeURIComponent(session.name)}`)
}