feat(sprite-generation-gui): Update GUI components and API logic for sprite data streaming, including SpriteStream integration and type definitions

Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
Claude Code 2026-03-28 21:31:41 -07:00
parent a638d0f490
commit 9c58d71042
4 changed files with 83 additions and 16 deletions

View file

@ -5,6 +5,7 @@ import { ReviewQueuePage } from './pages/ReviewQueuePage'
import { CategoryPage } from './pages/CategoryPage'
import { SpritePage } from './pages/SpritePage'
import SpriteTheaterPage from './pages/SpriteTheaterPage'
import VariantPage from './pages/VariantPage'
import { SpriteStream } from './SpriteStream'
import { colors } from './pages/theme'
@ -69,6 +70,7 @@ export function App(): ReactElement {
<Route path="/theater" element={<SpriteTheaterPage />} />
<Route path="/review" element={<ReviewQueuePage />} />
<Route path="/category/:name" element={<CategoryPage />} />
<Route path="/variant/:id" element={<VariantPage />} />
<Route path="/sprite/*" element={<SpritePage />} />
</Routes>
</div>

View file

@ -1,6 +1,6 @@
import { useEffect, useRef, useState, useCallback, type ReactNode, type CSSProperties } from 'react'
import { useNavigate, useSearchParams } from 'react-router-dom'
import { fetchRecentVariants, variantStreamUrl, rawImageUrl, variantImageUrl } from './api'
import { fetchRecentVariants, variantStreamUrl, bestImageUrl, rawImageUrl } from './api'
import type { RecentVariant } from './types'
import { colors } from './pages/theme'
@ -15,10 +15,6 @@ const GLOW_DURATION = 3000
const SCORE_FLASH_DURATION = 4000
const SHIFT_DURATION_MS = 500
function extractFilename(path: string): string {
const parts = path.replace(/\\/g, '/').split('/')
return parts[parts.length - 1]
}
function formatLabel(entityId: string): string {
return entityId.replace(/_/g, ' ')
@ -137,16 +133,9 @@ function SpriteCard({
size: number
}): ReactNode {
const [loaded, setLoaded] = useState(false)
const filename = variant.processed_path
? extractFilename(variant.processed_path)
: variant.raw_path
? extractFilename(variant.raw_path)
: null
const primarySrc = filename
? (variant.processed_path ? variantImageUrl(filename) : rawImageUrl(filename))
: null
const fallbackSrc = filename
? (variant.processed_path ? rawImageUrl(filename) : variantImageUrl(filename))
const primarySrc = bestImageUrl(variant.processed_path, variant.raw_path)
const fallbackSrc = variant.raw_path
? rawImageUrl(variant.raw_path.replace(/\\/g, '/').split('/').pop() ?? '')
: null
const [imgSrc, setImgSrc] = useState(primarySrc)
const triedFallback = useRef(false)

View file

@ -1,4 +1,4 @@
import type { Sprite, Variant, Stats, GenerationRun, RecentVariant } from './types'
import type { Sprite, Variant, Stats, GenerationRun, RecentVariant, VariantScore } from './types'
const API_BASE = '/api'
@ -158,6 +158,20 @@ export function variantImageUrl(path: string): string {
return `/images/variants/${path}`
}
/** Resolve the best available image URL for a variant.
* Prefers processed (background removed) over raw. */
export function bestImageUrl(processedPath: string | null, rawPath: string | null): string | null {
if (processedPath) {
const filename = processedPath.split('/').pop() ?? processedPath
return variantImageUrl(filename)
}
if (rawPath) {
const filename = rawPath.split('/').pop() ?? rawPath
return rawImageUrl(filename)
}
return null
}
export interface Progress {
total: number
by_status: Record<string, number>
@ -187,6 +201,46 @@ export async function fetchRecentVariants(limit = 30): Promise<RecentVariant[]>
return request<RecentVariant[]>(buildUrl('/variants/recent', { limit }))
}
export async function fetchVariant(variantId: number): Promise<Variant & { category?: string; entity_id?: string }> {
return request<Variant & { category?: string; entity_id?: string }>(buildUrl(`/variants/${variantId}`))
}
export async function fetchVariantScores(variantId: number): Promise<VariantScore[]> {
return request<VariantScore[]>(buildUrl(`/variants/${variantId}/scores`))
}
export function variantStreamUrl(): string {
return `${API_BASE}/stream/variants`
}
export interface PipelineState {
funnel: {
total_completed: number
total_processed: number
scoring: Record<string, { scored: number; passed: number; pass_rate: number; avg_confidence: number }>
approved: number
installed: number
}
failed_gates: Array<{ gate: string; count: number }>
sprite_coverage: Array<{
sprite_id: string
entity_id: string
total_variants: number
processed: number
tier_counts: Record<string, { scored: number; passed: number }>
all_passed: number
deficit: number
}>
recent_scores: Array<{
variant_id: number
sprite_id: string
scorer_name: string
gate_passed: boolean
confidence: number
scored_at: string
}>
}
export async function fetchPipeline(): Promise<PipelineState> {
return request<PipelineState>(buildUrl('/pipeline'))
}

View file

@ -78,4 +78,26 @@ export interface RecentVariant {
rating: number | null
notes: string | null
is_approved: boolean
scored_by: string | null
review_tier: number | null
}
export interface TheaterPage {
items: RecentVariant[]
total: number
}
export interface VariantScore {
id: number
variant_id: number
scorer_name: string
scorer_model: string
tier: number
gates: string | null
quality: string | null
gate_passed: number
confidence: number
failed_gate_reason: string | null
quality_floor_failed: number
scored_at: string
}