Add error handling for dynamic variables in DebugDrawer component. Introduce state for dynamic variable errors and implement validation logic to manage required keys. Update methods to reset error state and handle errors during dynamic variable operations, enhancing user feedback and session management.

This commit is contained in:
Xin Wang
2026-02-27 11:39:53 +08:00
parent 3272a7a68a
commit 6a9b5fcff4

View File

@@ -1643,6 +1643,7 @@ export const DebugDrawer: React.FC<{
const [captureConfigView, setCaptureConfigView] = useState<string>('');
const [settingsDrawerOpen, setSettingsDrawerOpen] = useState(false);
const [dynamicVariables, setDynamicVariables] = useState<DynamicVariableEntry[]>([]);
const [dynamicVariablesError, setDynamicVariablesError] = useState('');
const dynamicVariableSeqRef = useRef(0);
const [wsUrl, setWsUrl] = useState<string>(() => {
const fromStorage = localStorage.getItem('debug_ws_url');
@@ -1666,6 +1667,18 @@ export const DebugDrawer: React.FC<{
extractDynamicTemplateKeys(String(assistant.opener || '')).forEach((key) => keys.add(key));
return Array.from(keys).sort();
}, [assistant.opener, assistant.prompt]);
const missingRequiredDynamicVariableKeys = useMemo(() => {
const valuesByKey = new Map<string, string>();
for (const row of dynamicVariables) {
const key = row.key.trim();
if (!key || valuesByKey.has(key)) continue;
valuesByKey.set(key, row.value);
}
return requiredTemplateVariableKeys.filter((key) => {
if (!valuesByKey.has(key)) return true;
return valuesByKey.get(key) === '';
});
}, [dynamicVariables, requiredTemplateVariableKeys]);
// Media State
const videoRef = useRef<HTMLVideoElement>(null);
@@ -1758,6 +1771,7 @@ export const DebugDrawer: React.FC<{
useEffect(() => {
dynamicVariableSeqRef.current = 0;
setDynamicVariables([]);
setDynamicVariablesError('');
}, [assistant.id, isOpen]);
useEffect(() => {
@@ -2059,6 +2073,7 @@ export const DebugDrawer: React.FC<{
setMessages([]);
lastUserFinalRef.current = '';
setWsError('');
setDynamicVariablesError('');
closeWs();
if (textTtsEnabled) await ensureAudioContext();
await ensureWsSession();
@@ -2068,8 +2083,13 @@ export const DebugDrawer: React.FC<{
console.error(e);
stopVoiceCapture();
setCallStatus('idle');
const err = e as Error & { __dynamicVariables?: boolean };
if (err.__dynamicVariables) {
setWsStatus('disconnected');
return;
}
setWsStatus('error');
setWsError((e as Error)?.message || 'Failed to start voice call');
setWsError(err?.message || 'Failed to start voice call');
}
};
void launchVoice();
@@ -2094,6 +2114,7 @@ export const DebugDrawer: React.FC<{
setIsLoading(true);
try {
setDynamicVariablesError('');
if (mode === 'text') {
if (textTtsEnabled) await ensureAudioContext();
await ensureWsSession();
@@ -2114,7 +2135,12 @@ export const DebugDrawer: React.FC<{
}
} catch (e) {
console.error(e);
const errMessage = (e as Error)?.message || 'Failed to connect to AI service.';
const err = e as Error & { __dynamicVariables?: boolean };
if (err.__dynamicVariables) {
setIsLoading(false);
return;
}
const errMessage = err?.message || 'Failed to connect to AI service.';
setMessages(prev => [...prev, { role: 'model', text: `Error: ${errMessage}` }]);
setWsError(errMessage);
setIsLoading(false);
@@ -2126,6 +2152,7 @@ export const DebugDrawer: React.FC<{
const handleTextLaunch = async () => {
try {
setWsError('');
setDynamicVariablesError('');
// Start every text debug run as a fresh session transcript.
setMessages([]);
lastUserFinalRef.current = '';
@@ -2138,14 +2165,21 @@ export const DebugDrawer: React.FC<{
setTextSessionStarted(true);
} catch (e) {
console.error(e);
const err = e as Error & { __dynamicVariables?: boolean };
if (err.__dynamicVariables) {
setWsStatus('disconnected');
setTextSessionStarted(false);
return;
}
setWsStatus('error');
setWsError((e as Error)?.message || 'Failed to connect');
setWsError(err?.message || 'Failed to connect');
setTextSessionStarted(false);
}
};
const addDynamicVariableRow = () => {
if (isDynamicVariablesLocked) return;
setDynamicVariablesError('');
setDynamicVariables((prev) => {
if (prev.length >= DYNAMIC_VARIABLE_MAX_ITEMS) return prev;
return [...prev, { id: nextDynamicVariableId(), key: '', value: '' }];
@@ -2154,14 +2188,49 @@ export const DebugDrawer: React.FC<{
const updateDynamicVariableRow = (rowId: string, field: 'key' | 'value', value: string) => {
if (isDynamicVariablesLocked) return;
setDynamicVariablesError('');
setDynamicVariables((prev) => prev.map((item) => (item.id === rowId ? { ...item, [field]: value } : item)));
};
const removeDynamicVariableRow = (rowId: string) => {
if (isDynamicVariablesLocked) return;
setDynamicVariablesError('');
setDynamicVariables((prev) => prev.filter((item) => item.id !== rowId));
};
const importDynamicVariablesFromPlaceholders = () => {
if (isDynamicVariablesLocked) return;
setDynamicVariablesError('');
setDynamicVariables((prev) => {
if (!requiredTemplateVariableKeys.length || prev.length >= DYNAMIC_VARIABLE_MAX_ITEMS) {
return prev;
}
const existingKeys = new Set(
prev
.map((item) => item.key.trim())
.filter((key) => key !== '')
);
const next = [...prev];
for (const key of requiredTemplateVariableKeys) {
if (next.length >= DYNAMIC_VARIABLE_MAX_ITEMS) break;
if (existingKeys.has(key)) continue;
next.push({
id: nextDynamicVariableId(),
key,
value: '',
});
existingKeys.add(key);
}
return next;
});
};
const createDynamicVariablesError = (message: string): Error & { __dynamicVariables?: boolean } => {
const error = new Error(message) as Error & { __dynamicVariables?: boolean };
error.__dynamicVariables = true;
return error;
};
const buildDynamicVariablesPayload = (): { variables: Record<string, string>; error?: string } => {
const variables: Record<string, string> = {};
const nonEmptyRows = dynamicVariables
@@ -2329,8 +2398,10 @@ export const DebugDrawer: React.FC<{
const fetchRuntimeMetadata = async (): Promise<Record<string, any>> => {
const dynamicVariablesResult = buildDynamicVariablesPayload();
if (dynamicVariablesResult.error) {
throw new Error(dynamicVariablesResult.error);
setDynamicVariablesError(dynamicVariablesResult.error);
throw createDynamicVariablesError(dynamicVariablesResult.error);
}
setDynamicVariablesError('');
const localResolved = buildLocalResolvedRuntime();
const mergedMetadata: Record<string, any> = {
...localResolved.sessionStartMetadata,
@@ -2788,16 +2859,28 @@ export const DebugDrawer: React.FC<{
<div className="rounded-md border border-white/10 bg-black/20 p-2 space-y-2">
<div className="flex items-center justify-between gap-2">
<p className="text-[10px] uppercase tracking-widest text-muted-foreground">Dynamic Variables</p>
<Button
size="icon"
variant="ghost"
className="h-6 w-6"
onClick={addDynamicVariableRow}
disabled={isDynamicVariablesLocked || dynamicVariables.length >= DYNAMIC_VARIABLE_MAX_ITEMS}
title={isDynamicVariablesLocked ? 'Disable editing while session is active' : 'Add variable'}
>
<Plus className="h-3.5 w-3.5" />
</Button>
<div className="flex items-center gap-1.5">
<Button
size="sm"
variant="ghost"
className="h-6 px-2 text-[10px]"
onClick={importDynamicVariablesFromPlaceholders}
disabled={isDynamicVariablesLocked || requiredTemplateVariableKeys.length === 0 || dynamicVariables.length >= DYNAMIC_VARIABLE_MAX_ITEMS}
title="Import keys from {{placeholder}} in prompt/opener"
>
Import
</Button>
<Button
size="icon"
variant="ghost"
className="h-6 w-6"
onClick={addDynamicVariableRow}
disabled={isDynamicVariablesLocked || dynamicVariables.length >= DYNAMIC_VARIABLE_MAX_ITEMS}
title={isDynamicVariablesLocked ? 'Disable editing while session is active' : 'Add variable'}
>
<Plus className="h-3.5 w-3.5" />
</Button>
</div>
</div>
<p className="text-[11px] text-muted-foreground">
Use placeholders like {'{{customer_name}}'} in prompt/opener.
@@ -2807,6 +2890,16 @@ export const DebugDrawer: React.FC<{
Required: {requiredTemplateVariableKeys.join(', ')}
</p>
)}
{missingRequiredDynamicVariableKeys.length > 0 && (
<p className="text-[11px] text-red-300/90">
Missing required dynamic variable: {missingRequiredDynamicVariableKeys.join(', ')}
</p>
)}
{dynamicVariablesError && (
<p className="text-[11px] text-red-300/90">
{dynamicVariablesError}
</p>
)}
{dynamicVariables.length === 0 ? (
<div className="text-[11px] text-muted-foreground/80 border border-dashed border-white/10 rounded-md px-2 py-2">
No variables added.