fix(web): harden typed SPA chat lifecycle
Co-Authored-By: Claude Haiku 4.5 <[email protected]>
This commit is contained in:
co-authored by
Claude Haiku 4.5
parent
b2e005f2b4
commit
caebf9ef70
@@ -1,41 +1,59 @@
|
||||
import type { ReactElement } from 'react';
|
||||
import type { SessionInfoPayload } from '@/lib/chat-contract';
|
||||
import { asString, asStringArray } from './runtime-guards';
|
||||
|
||||
interface SessionPanelProps {
|
||||
sessionInfo: SessionInfoPayload | null;
|
||||
onSetThinking: (level: string) => void;
|
||||
}
|
||||
|
||||
const THINKING_LEVEL_UNAVAILABLE = '';
|
||||
|
||||
export function SessionPanel({
|
||||
sessionInfo,
|
||||
onSetThinking,
|
||||
}: SessionPanelProps): ReactElement | null {
|
||||
if (!sessionInfo) return null;
|
||||
|
||||
const availableThinkingLevels = asStringArray(sessionInfo.availableThinkingLevels);
|
||||
const hasThinkingLevels = availableThinkingLevels.length > 0;
|
||||
|
||||
return (
|
||||
<section
|
||||
aria-label="Session info"
|
||||
className="flex flex-wrap items-center gap-3 border-b px-4 py-2 text-xs"
|
||||
>
|
||||
<span>{sessionInfo.provider}</span>
|
||||
<span>{sessionInfo.modelId}</span>
|
||||
<span>{asString(sessionInfo.provider, 'unknown')}</span>
|
||||
<span>{asString(sessionInfo.modelId, 'unknown')}</span>
|
||||
<label className="flex items-center gap-2">
|
||||
<span>Thinking level</span>
|
||||
<select
|
||||
aria-label="Thinking level"
|
||||
value={sessionInfo.thinkingLevel}
|
||||
onChange={(event) => onSetThinking(event.target.value)}
|
||||
value={
|
||||
hasThinkingLevels ? asString(sessionInfo.thinkingLevel) : THINKING_LEVEL_UNAVAILABLE
|
||||
}
|
||||
onChange={(event) => {
|
||||
// The placeholder option is not a real, settable level — a
|
||||
// malformed availableThinkingLevels list must never let the
|
||||
// client emit set:thinking for it.
|
||||
if (!hasThinkingLevels) return;
|
||||
onSetThinking(event.target.value);
|
||||
}}
|
||||
>
|
||||
{sessionInfo.availableThinkingLevels.map((level) => (
|
||||
<option key={level} value={level}>
|
||||
{level}
|
||||
</option>
|
||||
))}
|
||||
{hasThinkingLevels ? (
|
||||
availableThinkingLevels.map((level) => (
|
||||
<option key={level} value={level}>
|
||||
{level}
|
||||
</option>
|
||||
))
|
||||
) : (
|
||||
<option value={THINKING_LEVEL_UNAVAILABLE}>Thinking level unavailable</option>
|
||||
)}
|
||||
</select>
|
||||
</label>
|
||||
{sessionInfo.routingDecision ? (
|
||||
<span title={sessionInfo.routingDecision.ruleName}>
|
||||
{sessionInfo.routingDecision.reason}
|
||||
<span title={asString(sessionInfo.routingDecision.ruleName)}>
|
||||
{asString(sessionInfo.routingDecision.reason)}
|
||||
</span>
|
||||
) : null}
|
||||
</section>
|
||||
|
||||
Reference in New Issue
Block a user