From 35f28a6e7733675813b34a3e6e2bda5907cdc393 Mon Sep 17 00:00:00 2001 From: kaitranntt Date: Thu, 8 Jan 2026 16:56:05 -0500 Subject: [PATCH] fix(ui): add provider indicator, retry button, and optimistic locking - U4: add blue info box showing supported providers - W1: add inline retry button in error alert - W4: track lastModified, handle 409 conflict with auto-refresh --- .../settings/hooks/use-thinking-config.ts | 27 +++++++++++++-- .../settings/sections/thinking/index.tsx | 33 +++++++++++++++++-- 2 files changed, 54 insertions(+), 6 deletions(-) diff --git a/ui/src/pages/settings/hooks/use-thinking-config.ts b/ui/src/pages/settings/hooks/use-thinking-config.ts index b4571ff3..9c59bbb7 100644 --- a/ui/src/pages/settings/hooks/use-thinking-config.ts +++ b/ui/src/pages/settings/hooks/use-thinking-config.ts @@ -1,9 +1,10 @@ /** * Thinking Config Hook * Manages thinking budget configuration with direct API calls + * Includes W4 optimistic locking via lastModified timestamps */ -import { useCallback, useState } from 'react'; +import { useCallback, useState, useRef } from 'react'; import type { ThinkingConfig, ThinkingMode } from '../types'; const DEFAULT_THINKING_CONFIG: ThinkingConfig = { @@ -24,6 +25,8 @@ export function useThinkingConfig() { const [saving, setSaving] = useState(false); const [error, setError] = useState(null); const [success, setSuccess] = useState(false); + // W4: Track lastModified for optimistic locking + const lastModifiedRef = useRef(undefined); const fetchConfig = useCallback(async () => { const controller = new AbortController(); @@ -37,6 +40,8 @@ export function useThinkingConfig() { if (!res.ok) throw new Error('Failed to load Thinking config'); const data = await res.json(); setConfig(data.config || DEFAULT_THINKING_CONFIG); + // W4: Store lastModified for optimistic locking + lastModifiedRef.current = data.lastModified; } catch (err) { clearTimeout(timeoutId); if ((err as Error).name === 'AbortError') { @@ -63,10 +68,16 @@ export function useThinkingConfig() { setSaving(true); setError(null); + // W4: Include lastModified for optimistic locking + const payload = { + ...optimisticConfig, + lastModified: lastModifiedRef.current, + }; + const res = await fetch('/api/thinking', { method: 'PUT', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(optimisticConfig), + body: JSON.stringify(payload), signal: controller.signal, }); @@ -74,11 +85,17 @@ export function useThinkingConfig() { if (!res.ok) { const data = await res.json(); + // W4: Handle conflict (409) with user-friendly message + if (res.status === 409) { + throw new Error('Config changed by another session. Refreshing...'); + } throw new Error(data.error || 'Failed to save'); } const data = await res.json(); setConfig(data.config); + // W4: Update lastModified after successful save + lastModifiedRef.current = data.lastModified; setSuccess(true); setTimeout(() => setSuccess(false), 1500); } catch (err) { @@ -88,12 +105,16 @@ export function useThinkingConfig() { setError('Request timeout - please try again'); } else { setError((err as Error).message); + // W4: On conflict, auto-refresh to get latest + if ((err as Error).message.includes('another session')) { + setTimeout(() => fetchConfig(), 1000); + } } } finally { setSaving(false); } }, - [config] + [config, fetchConfig] ); const setMode = useCallback( diff --git a/ui/src/pages/settings/sections/thinking/index.tsx b/ui/src/pages/settings/sections/thinking/index.tsx index fb5d5c17..e4754d2c 100644 --- a/ui/src/pages/settings/sections/thinking/index.tsx +++ b/ui/src/pages/settings/sections/thinking/index.tsx @@ -16,7 +16,7 @@ import { SelectTrigger, SelectValue, } from '@/components/ui/select'; -import { RefreshCw, CheckCircle2, AlertCircle, Brain } from 'lucide-react'; +import { RefreshCw, CheckCircle2, AlertCircle, Brain, Info } from 'lucide-react'; import { useThinkingConfig } from '../../hooks'; import type { ThinkingMode } from '../../types'; @@ -69,8 +69,22 @@ export default function ThinkingSection() { > {error && ( - - {error} +
+
+ + {error} +
+ {/* W1: Retry button on error */} + +
)} {success && ( @@ -91,6 +105,19 @@ export default function ThinkingSection() {

+ {/* U4: Provider support indicator */} +
+ +
+

Supported Providers

+

+ Thinking budget works with: agy (Antigravity),{' '} + gemini (with thinking models). Other providers may ignore this + setting. +

+
+
+ {/* Mode Selection */}

Thinking Mode