From a81a07d6d8df93e5c793ffe5557dac42d1abf458 Mon Sep 17 00:00:00 2001 From: Felipe Cardoso Date: Thu, 23 Jan 2025 14:12:12 +0100 Subject: [PATCH] Major restyling of frontend Signed-off-by: Felipe Cardoso --- frontend/src/app/page.tsx | 30 ++-- frontend/src/app/samples/page.tsx | 171 ++++++++++++--------- frontend/src/components/SamplesGallery.tsx | 57 +++++-- frontend/src/contexts/TrainingContext.tsx | 17 +- 4 files changed, 173 insertions(+), 102 deletions(-) diff --git a/frontend/src/app/page.tsx b/frontend/src/app/page.tsx index 85df6e5..5461e41 100644 --- a/frontend/src/app/page.tsx +++ b/frontend/src/app/page.tsx @@ -2,45 +2,47 @@ import {TrainingProgress} from '@/components/TrainingProgress' import {SamplesGallery} from '@/components/SamplesGallery' import {LogViewer} from '@/components/LogViewer' import {Suspense} from 'react' +import {TrainingInfo} from "@/components/TrainingInfo"; export default function DashboardPage() { return ( -
+
-

Training Monitor

-
+

Training Monitor

+
Live monitoring
{/* Training Status Section */} -
-

Training Progress

- Loading training status...
}> +
+

Training Progress

+ Loading training status...
}>
{/* Latest Samples Section */} -
+
-

Latest Samples

- Auto-updating +

Latest Samples

+ Auto-updating
- Loading samples...
}> + Loading samples...
}>
{/* Log Viewer Section - Full Width */} -
+
-

Training Logs

- Real-time updates +

Training Logs

+ Real-time updates
- Loading logs...
}> + Loading logs...
}> diff --git a/frontend/src/app/samples/page.tsx b/frontend/src/app/samples/page.tsx index 5d87e06..ad7acf5 100644 --- a/frontend/src/app/samples/page.tsx +++ b/frontend/src/app/samples/page.tsx @@ -3,94 +3,125 @@ import {useSamples} from '@/contexts/SamplesContext' import Image from 'next/image' +import Link from "next/link" import {useMemo} from 'react' -interface ParsedSample { - filename: string - timestamp: number - batch: number - index: number - url: string - created_at: string +// Helper function to parse sample information +const parseSampleInfo = (filename: string) => { + const [timestamp, info] = filename.split('__') + const [batch, index] = info.split('.')[0].split('_') + return { + timestamp: parseInt(timestamp), + batch: parseInt(batch), + index: parseInt(index) + } } export default function SamplesPage() { const {samples, isLoading, error} = useSamples() + // Group samples by batch number using a memoized calculation const groupedSamples = useMemo(() => { if (!samples?.length) return new Map() - const parsed: ParsedSample[] = samples.map(sample => { - console.debug('sample', sample) - const [timestamp, info] = sample.filename.split('__') - const [batch, index] = info.split('_') - return { + // Create groups based on batch numbers + const groups = samples.reduce((acc, sample) => { + const {batch} = parseSampleInfo(sample.filename) + const group = acc.get(batch) || [] + group.push({ ...sample, - timestamp: parseInt(timestamp), - batch: parseInt(batch), - index: parseInt(index.replace('.jpg', '')), - } - }) - - // Group by batch - const groups = parsed.reduce((acc, sample) => { - const group = acc.get(sample.batch) || [] - group.push(sample) - acc.set(sample.batch, group) + ...parseSampleInfo(sample.filename) + }) + acc.set(batch, group) return acc - }, new Map()) + }, new Map()) - // Sort within each group + // Sort samples within each group by index for (const [batch, items] of groups) { - groups.set(batch, items.sort((a, b) => b.index - a.index)) + groups.set(batch, items.sort((a: any, b: any) => a.index - b.index)) } - // return new Map([...groups].sort((a, b) => b[0] - a[0])) - return new Map([...groups]) + // Return groups sorted by batch number (descending) + return new Map([...groups].sort((a, b) => b[0] - a[0])) }, [samples]) - if (isLoading) return
Loading samples...
- if (error) return
Error: {error.message}
- - return ( -
-

Samples Gallery

- - {Array.from(groupedSamples).map(([batch, items]) => ( -
-

- Step {batch} -

-
-
- {items.sort((a: any, b: any) => a.index - b.index).map((sample: any) => ( -
- {`Sample -
-
- {new Date(sample.created_at).toLocaleString()} -
- - Sample {sample.index} - -
-
- ))} -
-
-
- ))} + // Handle loading and error states with appropriate styling + if (isLoading) return ( +
+
Loading samples...
) + + if (error) return ( +
+
Error: {error.message}
+
+ ) + + return ( +
+
+ {/* Header with navigation */} +
+

Sample Gallery

+ + ← Back to Dashboard + +
+ + {/* Sample groups */} +
+ {Array.from(groupedSamples).map(([batch, items]) => ( +
+

+ Step {batch} +

+ + {/* Scrollable container for samples */} +
+
+ {items.map((sample: any) => ( +
+ {/* Image container with hover effects */} +
+ {`Sample +
+ + {/* Sample information */} +
+
+ {new Date(sample.created_at).toLocaleString()} +
+ + Sample {sample.index} + +
+
+ ))} +
+
+
+ ))} +
+
+
+ ) } \ No newline at end of file diff --git a/frontend/src/components/SamplesGallery.tsx b/frontend/src/components/SamplesGallery.tsx index 0930086..96fad9b 100644 --- a/frontend/src/components/SamplesGallery.tsx +++ b/frontend/src/components/SamplesGallery.tsx @@ -1,28 +1,53 @@ "use client" import {useSamples} from '@/contexts/SamplesContext' import Image from 'next/image' +import Link from "next/link" export function SamplesGallery() { const {latestSamples, isLoading, error, refreshSamples} = useSamples() - if (isLoading) return
Loading samples...
- if (error) return
Error loading samples: {error.message}
- if (latestSamples.length === 0) return
No samples available
+ // First, let's handle our loading and error states with appropriate dark mode styling + if (isLoading) return
Loading samples...
+ if (error) return
Error loading samples: {error.message}
+ if (latestSamples.length === 0) return
No samples available
return ( -
- {latestSamples.map((sample) => ( -
- {sample.filename} -

{sample.url.split('__')[1]}

-
- ))} +
+ {/* Style the link to match our dark theme while maintaining accessibility */} + + Go to All samples + + + + {/* Update the grid layout with dark mode appropriate spacing and styling */} +
+ {latestSamples.map((sample) => ( +
+ {/* Add a subtle hover effect to the image container */} +
+ {sample.filename} +
+ + {/* Style the filename with appropriate dark mode colors */} +

+ {sample.url.split('__')[1]} +

+
+ ))} +
) } \ No newline at end of file diff --git a/frontend/src/contexts/TrainingContext.tsx b/frontend/src/contexts/TrainingContext.tsx index 73183cf..724e61c 100644 --- a/frontend/src/contexts/TrainingContext.tsx +++ b/frontend/src/contexts/TrainingContext.tsx @@ -1,9 +1,11 @@ "use client" import {createContext, useContext, useEffect, useState} from 'react' import type {TrainingStatus} from '@/types/api' +import {Config} from "@/types/config"; interface TrainingContextType { status: TrainingStatus | null + config: Config | null; logs: string[] isLoading: boolean error: Error | null @@ -13,6 +15,7 @@ const TrainingContext = createContext(undefined export function TrainingProvider({children}: { children: React.ReactNode }) { const [status, setStatus] = useState(null) + const [config, setConfig] = useState(null) const [logs, setLogs] = useState([]) const [isLoading, setIsLoading] = useState(true) const [error, setError] = useState(null) @@ -47,6 +50,16 @@ export function TrainingProvider({children}: { children: React.ReactNode }) { return result; }; + + const fetchConfig = async () => { + try { + const response = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/api/v1/config/`) + const data = await response.json() + setConfig(data) + } catch (err) { + setError(err as Error) + } + } const fetchStatus = async () => { try { const response = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/api/v1/training/status`) @@ -64,7 +77,6 @@ export function TrainingProvider({children}: { children: React.ReactNode }) { const data = await response.json() const filteredLogs = filterLogsKeepLatestProgress(data) setLogs(filteredLogs) - setIsLoading(false) } catch (err) { setError(err as Error) } @@ -74,6 +86,7 @@ export function TrainingProvider({children}: { children: React.ReactNode }) { // Initial fetch + fetchConfig() fetchStatus() fetchLogs() @@ -87,7 +100,7 @@ export function TrainingProvider({children}: { children: React.ReactNode }) { }, []) return ( - + {children} )