Build [v1.9.19]
This commit is contained in:
@@ -13,21 +13,12 @@ import {
|
||||
Plus,
|
||||
AlertTriangle,
|
||||
LogOut,
|
||||
Database,
|
||||
History,
|
||||
Lock,
|
||||
Edit2,
|
||||
X,
|
||||
Server,
|
||||
Globe,
|
||||
Wifi,
|
||||
WifiOff,
|
||||
Layers,
|
||||
ChevronDown,
|
||||
Clock,
|
||||
HardDrive,
|
||||
Download,
|
||||
RotateCcw
|
||||
RotateCcw,
|
||||
FileText,
|
||||
Upload,
|
||||
Brain
|
||||
} from 'lucide-react';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import { cn } from '@/lib/utils';
|
||||
@@ -61,6 +52,11 @@ export default function AdminPage() {
|
||||
const [dbStats, setDbStats] = useState({ backup_count: 0, total_size_bytes: 0 });
|
||||
const [dbSettings, setDbSettings] = useState({ retention_count: 10, schedule_hour: 3, schedule_freq_days: 1 });
|
||||
const [isBackingUp, setIsBackingUp] = useState(false);
|
||||
const [isImporting, setIsImporting] = useState(false);
|
||||
|
||||
// AI Prompt State
|
||||
const [aiPrompt, setAiPrompt] = useState("");
|
||||
const [isSavingPrompt, setIsSavingPrompt] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
loadData();
|
||||
@@ -83,6 +79,9 @@ export default function AdminPage() {
|
||||
setBackups(b);
|
||||
setDbStats(s);
|
||||
setDbSettings(st);
|
||||
|
||||
const p = await inventoryApi.getAiPrompt();
|
||||
setAiPrompt(p.value);
|
||||
} catch (err: any) {
|
||||
console.error(err);
|
||||
toast.error("Failed to load admin data");
|
||||
@@ -213,6 +212,60 @@ export default function AdminPage() {
|
||||
}
|
||||
};
|
||||
|
||||
const handleUpdatePrompt = async () => {
|
||||
setIsSavingPrompt(true);
|
||||
try {
|
||||
await inventoryApi.updateAiPrompt(aiPrompt);
|
||||
toast.success("AI Extraction Prompt updated");
|
||||
} catch (err: any) {
|
||||
toast.error("Failed to update AI prompt");
|
||||
} finally {
|
||||
setIsSavingPrompt(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleExportDb = async () => {
|
||||
try {
|
||||
const blob = await inventoryApi.exportDb();
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = `inventory_backup_${new Date().toISOString().split('T')[0]}.db`;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
window.URL.revokeObjectURL(url);
|
||||
toast.success("Database exported successfully");
|
||||
} catch (err: any) {
|
||||
toast.error("Export failed");
|
||||
}
|
||||
};
|
||||
|
||||
const handleImportDb = async (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const file = e.target.files?.[0];
|
||||
if (!file) return;
|
||||
|
||||
if (!confirm("DANGEROUS: You are about to REPLACE the entire database with this file. All current data will be lost. Proceed?")) {
|
||||
e.target.value = '';
|
||||
return;
|
||||
}
|
||||
|
||||
setIsImporting(true);
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
|
||||
const loadingToast = toast.loading("Importing database...");
|
||||
try {
|
||||
await inventoryApi.importDb(formData);
|
||||
toast.success("Database imported! Reloading system...", { id: loadingToast });
|
||||
setTimeout(() => window.location.reload(), 2000);
|
||||
} catch (err: any) {
|
||||
toast.error("Import failed: " + (err.response?.data?.detail || err.message), { id: loadingToast });
|
||||
} finally {
|
||||
setIsImporting(false);
|
||||
e.target.value = '';
|
||||
}
|
||||
};
|
||||
|
||||
const handleUpdateLdap = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
@@ -565,6 +618,48 @@ export default function AdminPage() {
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* AI Configuration Section */}
|
||||
<section className="glass-card p-6 md:p-8 rounded-[2.5rem] md:rounded-[3rem] space-y-8">
|
||||
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-6 px-2">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="p-3 bg-purple-500/10 rounded-2xl text-purple-500 border border-purple-500/20">
|
||||
<Brain size={24} />
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="text-xl font-black text-white">AI Vision Intelligence</h2>
|
||||
<p className="text-xs text-slate-500 font-bold mt-1">Configure extraction prompts and heuristic models</p>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={handleUpdatePrompt}
|
||||
disabled={isSavingPrompt}
|
||||
className="bg-purple-600 hover:bg-purple-500 text-white font-black text-xs px-6 py-3 rounded-xl shadow-xl shadow-purple-500/20 transition-all active:scale-95 disabled:opacity-50 flex items-center gap-2"
|
||||
>
|
||||
{isSavingPrompt ? <div className="w-3 h-3 border-2 border-white border-t-transparent animate-spin rounded-full" /> : <Edit2 size={14} />}
|
||||
Save AI Prompt
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center gap-2 px-1">
|
||||
<FileText size={14} className="text-purple-400" />
|
||||
<label className="text-xs font-black text-slate-300">Global Extraction Prompt</label>
|
||||
</div>
|
||||
<textarea
|
||||
value={aiPrompt}
|
||||
onChange={(e) => setAiPrompt(e.target.value)}
|
||||
placeholder="Enter the AI system prompt for label extraction..."
|
||||
className="w-full bg-slate-950/80 border border-slate-800 rounded-3xl p-6 text-sm text-slate-300 font-mono leading-relaxed min-h-[300px] outline-none focus:border-purple-500/30 transition-all custom-scrollbar"
|
||||
/>
|
||||
<div className="bg-purple-500/5 border border-purple-500/10 p-5 rounded-2xl">
|
||||
<p className="text-[10px] text-slate-500 leading-relaxed font-bold italic">
|
||||
Tip: Use specific instructions like "Return ONLY valid JSON" to ensure consistent results.
|
||||
Reference fields: <span className="text-purple-400/80">Item, Type, Description, Category, Connector, Size, Color, PartNr, OCR</span>.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Category Management Section */}
|
||||
<section className="glass-card p-6 md:p-8 rounded-[2.5rem] md:rounded-[3rem] space-y-8">
|
||||
<div className="flex items-center justify-between px-2">
|
||||
@@ -674,18 +769,38 @@ export default function AdminPage() {
|
||||
<p className="text-xs text-slate-500 font-bold mt-1">Snapshot management and disaster recovery tools</p>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={handleCreateBackup}
|
||||
disabled={isBackingUp}
|
||||
className="group flex items-center justify-center gap-2 bg-amber-500/10 hover:bg-amber-500 text-amber-500 hover:text-white font-black text-xs px-6 py-3 rounded-xl transition-all border border-amber-500/20 active:scale-95 disabled:opacity-50"
|
||||
>
|
||||
{isBackingUp ? (
|
||||
<div className="w-3 h-3 border-2 border-current border-t-transparent animate-spin rounded-full" />
|
||||
) : (
|
||||
<Download size={14} className="group-hover:-translate-y-0.5 transition-transform" />
|
||||
)}
|
||||
Create Manual Backup
|
||||
</button>
|
||||
<div className="flex items-center gap-3">
|
||||
<button
|
||||
onClick={handleExportDb}
|
||||
className="flex items-center justify-center gap-2 bg-slate-800 hover:bg-slate-700 text-slate-300 font-black text-xs px-5 py-3 rounded-xl transition-all border border-slate-700 active:scale-95"
|
||||
title="Download current database as .db file"
|
||||
>
|
||||
<Download size={14} /> Export
|
||||
</button>
|
||||
<label className="flex items-center justify-center gap-2 bg-slate-800 hover:bg-rose-900/20 text-slate-300 hover:text-rose-400 font-black text-xs px-5 py-3 rounded-xl transition-all border border-slate-700 hover:border-rose-500/30 cursor-pointer active:scale-95">
|
||||
<Upload size={14} /> Import
|
||||
<input
|
||||
type="file"
|
||||
accept=".db,.sqlite,.sqlite3"
|
||||
className="hidden"
|
||||
onChange={handleImportDb}
|
||||
disabled={isImporting}
|
||||
/>
|
||||
</label>
|
||||
<div className="w-px h-8 bg-slate-800 mx-2" />
|
||||
<button
|
||||
onClick={handleCreateBackup}
|
||||
disabled={isBackingUp}
|
||||
className="group flex items-center justify-center gap-2 bg-amber-500/10 hover:bg-amber-500 text-amber-500 hover:text-white font-black text-xs px-6 py-3 rounded-xl transition-all border border-amber-500/20 active:scale-95 disabled:opacity-50"
|
||||
>
|
||||
{isBackingUp ? (
|
||||
<div className="w-3 h-3 border-2 border-current border-t-transparent animate-spin rounded-full" />
|
||||
) : (
|
||||
<Download size={14} className="group-hover:-translate-y-0.5 transition-transform" />
|
||||
)}
|
||||
Snapshot
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid lg:grid-cols-3 gap-6">
|
||||
|
||||
Reference in New Issue
Block a user