feat: harden runtime readiness and session workflow

This commit is contained in:
User
2026-07-14 10:27:25 +02:00
parent 6d7738d538
commit 6dbf93fff9
52 changed files with 1464 additions and 169 deletions
+34 -9
View File
@@ -15,13 +15,24 @@ interface Props {
sessionId: string | null;
/** Called with the new session id when the box starts a fresh session. */
onSessionCreated?: (id: string) => void;
/** Paint the provisional session view before the create request completes. */
onSessionCreating?: (question: string) => void;
/** Revert the provisional view when session creation fails. */
onSessionCreateFailed?: () => void;
/** Interrupt the running session, persisting its state. */
onStop?: () => void;
/** Lets the parent focus the composer (e.g. on "New session"). */
inputRef?: React.Ref<HTMLTextAreaElement>;
}
export function SteerInput({ sessionId, onSessionCreated, onStop, inputRef }: Props) {
export function SteerInput({
sessionId,
onSessionCreated,
onSessionCreating,
onSessionCreateFailed,
onStop,
inputRef,
}: Props) {
const [text, setText] = useState("");
const [busy, setBusy] = useState(false);
const setLastUserEntry = useSessionStore((s) => s.setLastUserEntry);
@@ -48,16 +59,29 @@ export function SteerInput({ sessionId, onSessionCreated, onStop, inputRef }: Pr
const trimmed = text.trim();
if (!trimmed || busy) return;
setBusy(true);
try {
if (sessionId) {
if (sessionId) {
try {
await postSteer(sessionId, trimmed);
} else {
const { id } = await createSession({ question: trimmed });
setPhase("F1"); // optimistic: paint F1 yellow during the cold start, before the first gate
onSessionCreated?.(id);
setLastUserEntry({ kind: "input", text: trimmed });
setText("");
} finally {
setBusy(false);
}
setLastUserEntry({ kind: "input", text: trimmed });
return;
}
// Session creation can include cold-start work. Hand the UI state to the
// provisional session view before awaiting it so feedback is immediate.
setPhase("F1");
setLastUserEntry({ kind: "input", text: trimmed });
onSessionCreating?.(trimmed);
try {
const { id } = await createSession({ question: trimmed });
onSessionCreated?.(id);
setText("");
} catch {
// Keep the question in the composer so retrying does not require retyping.
onSessionCreateFailed?.();
} finally {
setBusy(false);
}
@@ -92,7 +116,8 @@ export function SteerInput({ sessionId, onSessionCreated, onStop, inputRef }: Pr
onKeyDown={handleKeyDown}
placeholder={sessionId ? "Free steering (!)…" : "Ask a question to start a new session…"}
aria-label={sessionId ? "Steering" : "New question"}
className="max-h-40 flex-1 resize-none bg-transparent px-1 py-1 text-sm leading-relaxed outline-none placeholder:text-muted-foreground"
data-awaiting-input={sessionId ? undefined : "true"}
className={["max-h-40 flex-1 resize-none rounded-lg px-1 py-1 text-sm leading-relaxed outline-none placeholder:text-muted-foreground", sessionId ? "bg-transparent" : "thot-awaiting-input"].join(" ")}
/>
<button
type="button"