Skip to content

Commit cd54548

Browse files
committed
fix: surface query errors, render regression metrics, persist profile name
- global query error toast so failed loads no longer look like empty states - optimize mutation now reports errors instead of failing silently - regression RMSE/MAE render from best-model metrics, including single-model runs - settings save persists the display name via updateUser
1 parent 34ba134 commit cd54548

7 files changed

Lines changed: 64 additions & 19 deletions

File tree

frontend/src/contexts/AuthContext.tsx

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ interface AuthContextType {
2323
signOut: () => Promise<void>;
2424
resetPassword: (email: string) => Promise<void>;
2525
updatePassword: (newPassword: string) => Promise<void>;
26+
updateProfile: (fullName: string) => Promise<void>;
2627
exitRecovery: () => void;
2728
}
2829

@@ -196,6 +197,24 @@ export function AuthProvider({ children }: { children: ReactNode }) {
196197
if (error) throw error;
197198
};
198199

200+
const updateProfile = async (fullName: string) => {
201+
if (AUTH_PROVIDER === 'local') {
202+
const current = localGetUser();
203+
if (current) {
204+
const updated: User = { ...current, user_metadata: { ...current.user_metadata, full_name: fullName } };
205+
localStorage.setItem(LOCAL_USER_KEY, JSON.stringify(updated));
206+
setUser(updated);
207+
}
208+
return;
209+
}
210+
if (!supabase) throw new Error('Supabase is not configured');
211+
const { data, error } = await supabase.auth.updateUser({ data: { full_name: fullName } });
212+
if (error) throw error;
213+
if (data.user) {
214+
setUser({ id: data.user.id, email: data.user.email!, user_metadata: data.user.user_metadata });
215+
}
216+
};
217+
199218
const exitRecovery = () => {
200219
setRecoveryMode(false);
201220
if (typeof window !== 'undefined') {
@@ -205,7 +224,7 @@ export function AuthProvider({ children }: { children: ReactNode }) {
205224

206225
return (
207226
<AuthContext.Provider
208-
value={{ user, loading, recoveryMode, signUp, signIn, signOut, resetPassword, updatePassword, exitRecovery }}
227+
value={{ user, loading, recoveryMode, signUp, signIn, signOut, resetPassword, updatePassword, updateProfile, exitRecovery }}
209228
>
210229
{children}
211230
</AuthContext.Provider>

frontend/src/freestyle/panels/EvaluationPanel.tsx

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ export default function EvaluationPanel({ projectId }: Props) {
2020
? Object.entries(data.feature_importance).sort((a, b) => b[1] - a[1]).slice(0, 8)
2121
: null
2222
const maxImp = featureImportance?.[0]?.[1] ?? 1
23+
const bestMetrics =
24+
(data?.results?.find((r) => r.model === data.best_model) ?? data?.results?.[0])?.metrics
2325

2426
return (
2527
<div className="flex flex-col flex-1 overflow-hidden">
@@ -140,15 +142,21 @@ export default function EvaluationPanel({ projectId }: Props) {
140142
</Section>
141143
)}
142144

143-
{isRegression && (data as any).rmse != null && (
145+
{isRegression && bestMetrics && (bestMetrics.rmse != null || bestMetrics.mae != null) && (
144146
<Section label="Regression Metrics">
145147
<div className="flex flex-col gap-1">
146-
{[['RMSE', (data as any).rmse?.toFixed(4)], ['MAE', (data as any).mae?.toFixed(4)]].filter(([, v]) => v != null).map(([label, value]) => (
147-
<div key={label} className="flex items-center justify-between px-2.5 py-2 bg-[#111827] border border-[#1e2a3a] rounded">
148-
<span className="text-[11px] text-[#64748b]">{label}</span>
149-
<span className="text-[11px] font-mono text-[#e2e8f0]">{value}</span>
148+
{bestMetrics.rmse != null && (
149+
<div className="flex items-center justify-between px-2.5 py-2 bg-[#111827] border border-[#1e2a3a] rounded">
150+
<span className="text-[11px] text-[#64748b]">RMSE</span>
151+
<span className="text-[11px] font-mono text-[#e2e8f0]">{bestMetrics.rmse.toFixed(4)}</span>
150152
</div>
151-
))}
153+
)}
154+
{bestMetrics.mae != null && (
155+
<div className="flex items-center justify-between px-2.5 py-2 bg-[#111827] border border-[#1e2a3a] rounded">
156+
<span className="text-[11px] text-[#64748b]">MAE</span>
157+
<span className="text-[11px] font-mono text-[#e2e8f0]">{bestMetrics.mae.toFixed(4)}</span>
158+
</div>
159+
)}
152160
</div>
153161
</Section>
154162
)}

frontend/src/freestyle/panels/OptimizationPanel.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { useState } from 'react'
22
import { useQuery, useMutation } from '@tanstack/react-query'
3+
import toast from 'react-hot-toast'
34
import { Play, TrendingUp, TrendingDown, Minus } from 'lucide-react'
45
import { modelApi, type OptimizeResponse } from '../../services/api/model'
56
import { Section, OptionCard } from './MissingValuesPanel'
@@ -109,6 +110,7 @@ export default function OptimizationPanel({ projectId }: Props) {
109110
param_choices: Object.fromEntries(categoricalParams.map(p => [p.key, getChoices(p)])),
110111
}),
111112
onSuccess: setResult,
113+
onError: (err: Error) => toast.error(err.message),
112114
})
113115

114116
const improvement = result?.improvement ?? 0

frontend/src/lib/queryClient.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
1-
import { QueryClient } from '@tanstack/react-query'
1+
import { QueryClient, QueryCache } from '@tanstack/react-query'
2+
import toast from 'react-hot-toast'
3+
4+
function errorMessage(error: unknown): string {
5+
return error instanceof Error && error.message ? error.message : 'Something went wrong'
6+
}
27

38
export const queryClient = new QueryClient({
9+
queryCache: new QueryCache({
10+
onError: (error) => toast.error(errorMessage(error)),
11+
}),
412
defaultOptions: {
513
queries: {
614
retry: 1,

frontend/src/pages/EvaluationPage.tsx

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ export default function EvaluationPage({ projectId, onNext }: EvaluationPageProp
3838

3939
const confusionMatrix = evalData?.confusion_matrix
4040
const classNames = evalData?.class_names ?? []
41+
const bestMetrics =
42+
(evalData?.results?.find((r) => r.model === evalData.best_model) ?? evalData?.results?.[0])?.metrics
4143

4244
return (
4345
<div className="animate-fade-in" style={{ paddingBottom: '64px' }}>
@@ -86,13 +88,11 @@ export default function EvaluationPage({ projectId, onNext }: EvaluationPageProp
8688
Regression task - showing R², RMSE and MAE.
8789
Precision / Recall / F1 are classification metrics and do not apply here.
8890
</p>
89-
{evalData && (
90-
<div className="mt-2 flex gap-6">
91-
{(evalData as any).r2 != null && <span className="text-xs text-[#64748b]">R²: <span className="text-[#22c55e] font-mono">{((evalData as any).r2 * 100).toFixed(2)}%</span></span>}
92-
{(evalData as any).rmse != null && <span className="text-xs text-[#64748b]">RMSE: <span className="text-[#f97316] font-mono">{(evalData as any).rmse?.toFixed(4)}</span></span>}
93-
{(evalData as any).mae != null && <span className="text-xs text-[#64748b]">MAE: <span className="text-[#94a3b8] font-mono">{(evalData as any).mae?.toFixed(4)}</span></span>}
94-
</div>
95-
)}
91+
<div className="mt-2 flex gap-6">
92+
<span className="text-xs text-[#64748b]">R²: <span className="text-[#22c55e] font-mono">{(evalData.accuracy * 100).toFixed(2)}%</span></span>
93+
{bestMetrics && bestMetrics.rmse != null && <span className="text-xs text-[#64748b]">RMSE: <span className="text-[#f97316] font-mono">{bestMetrics.rmse.toFixed(4)}</span></span>}
94+
{bestMetrics && bestMetrics.mae != null && <span className="text-xs text-[#64748b]">MAE: <span className="text-[#94a3b8] font-mono">{bestMetrics.mae.toFixed(4)}</span></span>}
95+
</div>
9696
</div>
9797
)}
9898

frontend/src/pages/OptimizationPage.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { useState } from 'react'
22
import { useQuery, useMutation } from '@tanstack/react-query'
3+
import toast from 'react-hot-toast'
34
import { ChevronRight, ChevronDown, Play, TrendingUp, TrendingDown, Minus } from 'lucide-react'
45
import { modelApi, type OptimizeResponse } from '../services/api/model'
56
import type { PipelineStep } from '../types'
@@ -115,6 +116,7 @@ export default function OptimizationPage({ projectId, onNext }: OptimizationPage
115116
param_choices: Object.fromEntries(categoricalParams.map(p => [p.key, getChoices(p)])),
116117
}),
117118
onSuccess: setResult,
119+
onError: (err: Error) => toast.error(err.message),
118120
})
119121

120122
const improvement = result?.improvement ?? 0

frontend/src/pages/SettingsPage.tsx

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import { useState } from 'react';
22
import { Check } from 'lucide-react';
3+
import toast from 'react-hot-toast';
34
import { useAuth } from '../contexts/AuthContext';
45

56
export default function SettingsPage() {
6-
const { user, updatePassword } = useAuth();
7+
const { user, updatePassword, updateProfile } = useAuth();
78
const [activeTab, setActiveTab] = useState<'profile' | 'api'>('profile');
89
const [name, setName] = useState(user?.user_metadata?.full_name ?? '');
910
const [saved, setSaved] = useState(false);
@@ -16,9 +17,14 @@ export default function SettingsPage() {
1617

1718
const canChangePassword = (import.meta.env.VITE_AUTH_PROVIDER ?? 'local') === 'supabase';
1819

19-
const handleSave = () => {
20-
setSaved(true);
21-
setTimeout(() => setSaved(false), 2500);
20+
const handleSave = async () => {
21+
try {
22+
await updateProfile(name.trim());
23+
setSaved(true);
24+
setTimeout(() => setSaved(false), 2500);
25+
} catch (err: any) {
26+
toast.error(err.message || 'Failed to save changes');
27+
}
2228
};
2329

2430
const handleChangePassword = async () => {

0 commit comments

Comments
 (0)