feat(ui): add visual feedback for WebSearch model auto-save

- Add 'Saved' indicator with checkmark for Gemini/OpenCode model inputs

- Show indicator for 2 seconds after successful save on blur

- Uses fade-in animation for smooth UX

Closes #164
This commit is contained in:
kaitranntt
2025-12-21 01:47:18 -05:00
parent 69d4316821
commit eaf566beac
+31 -4
View File
@@ -107,6 +107,9 @@ export function SettingsPage() {
const [showGeminiHint, setShowGeminiHint] = useState(false); const [showGeminiHint, setShowGeminiHint] = useState(false);
const [showOpencodeHint, setShowOpencodeHint] = useState(false); const [showOpencodeHint, setShowOpencodeHint] = useState(false);
const [showGrokHint, setShowGrokHint] = useState(false); const [showGrokHint, setShowGrokHint] = useState(false);
// Model save indicators (brief visual feedback)
const [geminiModelSaved, setGeminiModelSaved] = useState(false);
const [opencodeModelSaved, setOpencodeModelSaved] = useState(false);
// Global Env state // Global Env state
const [globalEnvConfig, setGlobalEnvConfig] = useState<GlobalEnvConfig | null>(null); const [globalEnvConfig, setGlobalEnvConfig] = useState<GlobalEnvConfig | null>(null);
const [globalEnvLoading, setGlobalEnvLoading] = useState(true); const [globalEnvLoading, setGlobalEnvLoading] = useState(true);
@@ -332,11 +335,11 @@ export function SettingsPage() {
}; };
// Save Gemini model on blur (only if changed) // Save Gemini model on blur (only if changed)
const saveGeminiModel = () => { const saveGeminiModel = async () => {
const currentModel = config?.providers?.gemini?.model ?? 'gemini-2.5-flash'; const currentModel = config?.providers?.gemini?.model ?? 'gemini-2.5-flash';
if (geminiModelInput !== currentModel) { if (geminiModelInput !== currentModel) {
const providers = config?.providers || {}; const providers = config?.providers || {};
saveConfig({ await saveConfig({
providers: { providers: {
...providers, ...providers,
gemini: { gemini: {
@@ -345,15 +348,18 @@ export function SettingsPage() {
}, },
}, },
}); });
// Show saved indicator briefly
setGeminiModelSaved(true);
setTimeout(() => setGeminiModelSaved(false), 2000);
} }
}; };
// Save OpenCode model on blur (only if changed) // Save OpenCode model on blur (only if changed)
const saveOpencodeModel = () => { const saveOpencodeModel = async () => {
const currentModel = config?.providers?.opencode?.model ?? 'opencode/grok-code'; const currentModel = config?.providers?.opencode?.model ?? 'opencode/grok-code';
if (opencodeModelInput !== currentModel) { if (opencodeModelInput !== currentModel) {
const providers = config?.providers || {}; const providers = config?.providers || {};
saveConfig({ await saveConfig({
providers: { providers: {
...providers, ...providers,
opencode: { opencode: {
@@ -362,6 +368,9 @@ export function SettingsPage() {
}, },
}, },
}); });
// Show saved indicator briefly
setOpencodeModelSaved(true);
setTimeout(() => setOpencodeModelSaved(false), 2000);
} }
}; };
@@ -551,6 +560,8 @@ export function SettingsPage() {
isOpenCodeEnabled={isOpenCodeEnabled} isOpenCodeEnabled={isOpenCodeEnabled}
geminiModelInput={geminiModelInput} geminiModelInput={geminiModelInput}
opencodeModelInput={opencodeModelInput} opencodeModelInput={opencodeModelInput}
geminiModelSaved={geminiModelSaved}
opencodeModelSaved={opencodeModelSaved}
showGeminiHint={showGeminiHint} showGeminiHint={showGeminiHint}
showOpencodeHint={showOpencodeHint} showOpencodeHint={showOpencodeHint}
showGrokHint={showGrokHint} showGrokHint={showGrokHint}
@@ -703,6 +714,8 @@ interface WebSearchContentProps {
isOpenCodeEnabled: boolean; isOpenCodeEnabled: boolean;
geminiModelInput: string; geminiModelInput: string;
opencodeModelInput: string; opencodeModelInput: string;
geminiModelSaved: boolean;
opencodeModelSaved: boolean;
showGeminiHint: boolean; showGeminiHint: boolean;
showOpencodeHint: boolean; showOpencodeHint: boolean;
showGrokHint: boolean; showGrokHint: boolean;
@@ -733,6 +746,8 @@ function WebSearchContent({
isOpenCodeEnabled, isOpenCodeEnabled,
geminiModelInput, geminiModelInput,
opencodeModelInput, opencodeModelInput,
geminiModelSaved,
opencodeModelSaved,
showGeminiHint, showGeminiHint,
showOpencodeHint, showOpencodeHint,
showGrokHint, showGrokHint,
@@ -857,6 +872,12 @@ function WebSearchContent({
className="h-8 text-sm font-mono" className="h-8 text-sm font-mono"
disabled={saving} disabled={saving}
/> />
{geminiModelSaved && (
<span className="flex items-center gap-1 text-green-600 dark:text-green-400 text-xs animate-in fade-in duration-200">
<Check className="w-3.5 h-3.5" />
Saved
</span>
)}
</div> </div>
</div> </div>
)} )}
@@ -948,6 +969,12 @@ function WebSearchContent({
className="h-8 text-sm font-mono" className="h-8 text-sm font-mono"
disabled={saving} disabled={saving}
/> />
{opencodeModelSaved && (
<span className="flex items-center gap-1 text-green-600 dark:text-green-400 text-xs animate-in fade-in duration-200">
<Check className="w-3.5 h-3.5" />
Saved
</span>
)}
</div> </div>
</div> </div>
)} )}