106 lines
5.3 KiB
JavaScript
106 lines
5.3 KiB
JavaScript
import React, { useEffect, useState } from 'react';
|
|
import { Wrench, Database } from 'lucide-react';
|
|
import { api } from '../api/client';
|
|
import { NavigationHeader } from '../components/NavigationHeader';
|
|
|
|
/*
|
|
* The admin panel is the project overview and nothing else.
|
|
*
|
|
* It used to carry three more sub-tabs behind a local `activeTab` switch -
|
|
* train/test dataset upload, a stock-remaining discount allocator, and a
|
|
* trigger for the vector ingestion pipeline. They were retired from the UI;
|
|
* with one view left there is nothing to switch between, so the tab bar and
|
|
* its state went too.
|
|
*
|
|
* Only the UI was removed. The endpoints behind those panels are still
|
|
* registered and still work - ingestion in particular is reachable from
|
|
* `python cli/ingest_brand.py "<brand>"` and from POST /api/catalog/generate.
|
|
*/
|
|
export function AdminPage() {
|
|
const [projectDetails, setProjectDetails] = useState(null);
|
|
const [loadingProject, setLoadingProject] = useState(true);
|
|
|
|
const fetchProjectDetails = async () => {
|
|
try {
|
|
setProjectDetails(await api.getProjectDetails());
|
|
} catch (e) {
|
|
console.warn('Could not fetch project details', e);
|
|
} finally {
|
|
setLoadingProject(false);
|
|
}
|
|
};
|
|
|
|
// This effect also drove a 3-second poll of ingestion job status; that went
|
|
// with the ingest panel, leaving a single fetch on mount.
|
|
useEffect(() => {
|
|
fetchProjectDetails();
|
|
}, []);
|
|
|
|
return (
|
|
<div className="flex h-screen w-full flex-col overflow-y-auto bg-paper-100 text-ink-950 font-sans">
|
|
<NavigationHeader
|
|
title="Admin Intelligence Panel"
|
|
subtitle="Brand catalogs, vector database tables & S3 storage overview"
|
|
icon={Wrench}
|
|
/>
|
|
|
|
<div className="mx-auto w-full max-w-6xl px-6 py-8 space-y-6">
|
|
<div className="space-y-6">
|
|
<div className="rounded-2xl border border-ink-900/10 bg-paper-50 p-6 shadow-xs">
|
|
<h2 className="font-display text-lg font-bold text-ink-950 flex items-center gap-2 mb-2">
|
|
<Database className="h-5 w-5 text-amber-500" /> Existing Project Details & System Architecture
|
|
</h2>
|
|
<p className="text-xs text-slate-500 leading-relaxed max-w-3xl">
|
|
High-level overview of active brand catalogs, PostgreSQL vector database tables, S3 image storage pipeline, and store intelligence ML models.
|
|
</p>
|
|
|
|
{loadingProject ? (
|
|
<div className="py-8 text-center text-xs text-slate-400">Loading project details...</div>
|
|
) : projectDetails ? (
|
|
<div className="mt-6 grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
|
|
<div className="rounded-xl border border-amber-500/20 bg-amber-500/5 p-4">
|
|
<p className="text-[11px] font-semibold uppercase text-amber-700">Total Brands</p>
|
|
<p className="font-mono text-3xl font-extrabold text-ink-950 mt-1">{projectDetails.total_brands}</p>
|
|
<p className="text-[10px] text-slate-400 mt-1">Ingested brand tables in pgvector</p>
|
|
</div>
|
|
|
|
<div className="rounded-xl border border-emerald-500/20 bg-emerald-500/5 p-4">
|
|
<p className="text-[11px] font-semibold uppercase text-emerald-700">Total Products</p>
|
|
<p className="font-mono text-3xl font-extrabold text-ink-950 mt-1">{projectDetails.total_products}</p>
|
|
<p className="text-[10px] text-slate-400 mt-1">Unique products with vector embeddings</p>
|
|
</div>
|
|
|
|
<div className="rounded-xl border border-blue-500/20 bg-blue-500/5 p-4">
|
|
<p className="text-[11px] font-semibold uppercase text-blue-700">S3 Storage Status</p>
|
|
<p className="font-mono text-xl font-bold text-ink-950 mt-2 uppercase">{projectDetails.s3_image_status}</p>
|
|
<p className="text-[10px] text-slate-400 mt-1">Bucket: {projectDetails.storage_bucket}</p>
|
|
</div>
|
|
|
|
<div className="rounded-xl border border-purple-500/20 bg-purple-500/5 p-4">
|
|
<p className="text-[11px] font-semibold uppercase text-purple-700">System Version</p>
|
|
<p className="font-mono text-xl font-bold text-ink-950 mt-2">{projectDetails.version}</p>
|
|
<p className="text-[10px] text-slate-400 mt-1">Multi-Store & RAG Engine</p>
|
|
</div>
|
|
</div>
|
|
) : null}
|
|
</div>
|
|
|
|
{projectDetails?.brand_product_counts && (
|
|
<div className="rounded-2xl border border-ink-900/10 bg-paper-50 p-6 shadow-xs">
|
|
<h3 className="font-display text-sm font-bold text-ink-950 mb-4">Ingested Brand Database Breakdown</h3>
|
|
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-3">
|
|
{Object.entries(projectDetails.brand_product_counts).map(([bName, count]) => (
|
|
<div key={bName} className="flex items-center justify-between p-3 rounded-xl border border-ink-900/10 bg-white">
|
|
<span className="text-xs font-bold text-ink-950">{bName}</span>
|
|
<span className="font-mono text-xs font-bold bg-amber-100 text-amber-800 px-2 py-0.5 rounded-full">{count} items</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|