505 lines
19 KiB
TypeScript
505 lines
19 KiB
TypeScript
"use client"
|
|
|
|
import { useMemo, useState } from "react"
|
|
import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card"
|
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
|
|
import {
|
|
BarChart3,
|
|
FileText,
|
|
Search,
|
|
AlertCircle,
|
|
CheckCircle2,
|
|
Download,
|
|
Database,
|
|
Code,
|
|
MessageSquare,
|
|
TrendingUp,
|
|
} from "lucide-react"
|
|
|
|
interface DashboardData {
|
|
missingManuals: any
|
|
qaData: any[]
|
|
metadata: any[]
|
|
structuredData: any[]
|
|
semanticIndex: any
|
|
acquisitionList: any
|
|
nameMapping: any[]
|
|
}
|
|
|
|
interface ManualsDashboardClientProps {
|
|
data: DashboardData
|
|
}
|
|
|
|
export function ManualsDashboardClient({ data }: ManualsDashboardClientProps) {
|
|
const [searchTerm, setSearchTerm] = useState("")
|
|
|
|
// Calculate statistics
|
|
const stats = useMemo(() => {
|
|
const missing = data.missingManuals?.summary || {}
|
|
const qaCategories = data.qaData.reduce((acc: any, item: any) => {
|
|
const cat = item.category || "unknown"
|
|
acc[cat] = (acc[cat] || 0) + 1
|
|
return acc
|
|
}, {})
|
|
|
|
const metadataWithSpecs = data.metadata.filter(
|
|
(m: any) =>
|
|
m.specifications?.dimensions &&
|
|
Object.keys(m.specifications.dimensions).length > 0
|
|
).length
|
|
|
|
const metadataWithParts = data.metadata.filter(
|
|
(m: any) => m.parts_list && m.parts_list.length > 0
|
|
).length
|
|
|
|
const metadataWithTroubleshooting = data.metadata.filter(
|
|
(m: any) => m.troubleshooting && m.troubleshooting.length > 0
|
|
).length
|
|
|
|
const schemaTypes = data.structuredData.reduce((acc: any, item: any) => {
|
|
item.schemas?.forEach((schema: any) => {
|
|
const type = schema["@type"] || "unknown"
|
|
acc[type] = (acc[type] || 0) + 1
|
|
})
|
|
return acc
|
|
}, {})
|
|
|
|
return {
|
|
totalModels: missing.total_expected_models || 0,
|
|
missingAll: missing.models_missing_all || 0,
|
|
partial: missing.models_partial || 0,
|
|
totalQAPairs: data.qaData.length,
|
|
qaCategories,
|
|
totalManuals: data.metadata.length,
|
|
metadataWithSpecs,
|
|
metadataWithParts,
|
|
metadataWithTroubleshooting,
|
|
totalChunks: data.semanticIndex?.total_chunks || 0,
|
|
schemaTypes,
|
|
highPriority: data.acquisitionList?.high_priority || 0,
|
|
mediumPriority: data.acquisitionList?.medium_priority || 0,
|
|
lowPriority: data.acquisitionList?.low_priority || 0,
|
|
}
|
|
}, [data])
|
|
|
|
// Filter Q&A data
|
|
const filteredQA = useMemo(() => {
|
|
if (!searchTerm) return data.qaData.slice(0, 50)
|
|
const term = searchTerm.toLowerCase()
|
|
return data.qaData
|
|
.filter(
|
|
(item: any) =>
|
|
item.question?.toLowerCase().includes(term) ||
|
|
item.answer?.toLowerCase().includes(term)
|
|
)
|
|
.slice(0, 50)
|
|
}, [data.qaData, searchTerm])
|
|
|
|
// Get high priority missing manuals
|
|
const highPriorityMissing = useMemo(() => {
|
|
return (data.acquisitionList?.acquisition_list || [])
|
|
.filter((item: any) => item.priority === "high")
|
|
.slice(0, 20)
|
|
}, [data.acquisitionList])
|
|
|
|
return (
|
|
<div className="space-y-8">
|
|
{/* Statistics Cards */}
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="text-sm font-medium flex items-center gap-2">
|
|
<Database className="h-4 w-4" />
|
|
Total Models
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="text-3xl font-bold">{stats.totalModels}</div>
|
|
<p className="text-xs text-muted-foreground mt-1">In database</p>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="text-sm font-medium flex items-center gap-2">
|
|
<FileText className="h-4 w-4" />
|
|
Processed Manuals
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="text-3xl font-bold">{stats.totalManuals}</div>
|
|
<p className="text-xs text-muted-foreground mt-1">With metadata</p>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="text-sm font-medium flex items-center gap-2">
|
|
<MessageSquare className="h-4 w-4" />
|
|
Q&A Pairs
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="text-3xl font-bold">
|
|
{stats.totalQAPairs.toLocaleString()}
|
|
</div>
|
|
<p className="text-xs text-muted-foreground mt-1">Generated</p>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="text-sm font-medium flex items-center gap-2">
|
|
<AlertCircle className="h-4 w-4" />
|
|
Missing Manuals
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="text-3xl font-bold">{stats.missingAll}</div>
|
|
<p className="text-xs text-muted-foreground mt-1">
|
|
No manuals found
|
|
</p>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
|
|
{/* Main Content Tabs */}
|
|
<Tabs defaultValue="overview" className="w-full">
|
|
<TabsList className="grid w-full grid-cols-5">
|
|
<TabsTrigger value="overview">Overview</TabsTrigger>
|
|
<TabsTrigger value="missing">Missing Manuals</TabsTrigger>
|
|
<TabsTrigger value="qa">Q&A Dataset</TabsTrigger>
|
|
<TabsTrigger value="metadata">Metadata</TabsTrigger>
|
|
<TabsTrigger value="optimization">Optimization</TabsTrigger>
|
|
</TabsList>
|
|
|
|
{/* Overview Tab */}
|
|
<TabsContent value="overview" className="space-y-4">
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="flex items-center gap-2">
|
|
<BarChart3 className="h-5 w-5" />
|
|
Processing Statistics
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="space-y-4">
|
|
<div className="space-y-2">
|
|
<div className="flex justify-between">
|
|
<span className="text-sm">Models Analyzed</span>
|
|
<span className="font-semibold">{stats.totalModels}</span>
|
|
</div>
|
|
<div className="flex justify-between">
|
|
<span className="text-sm">Manuals Processed</span>
|
|
<span className="font-semibold">{stats.totalManuals}</span>
|
|
</div>
|
|
<div className="flex justify-between">
|
|
<span className="text-sm">Q&A Pairs Generated</span>
|
|
<span className="font-semibold">
|
|
{stats.totalQAPairs.toLocaleString()}
|
|
</span>
|
|
</div>
|
|
<div className="flex justify-between">
|
|
<span className="text-sm">Semantic Chunks</span>
|
|
<span className="font-semibold">{stats.totalChunks}</span>
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="flex items-center gap-2">
|
|
<TrendingUp className="h-5 w-5" />
|
|
Gap Analysis
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="space-y-4">
|
|
<div className="space-y-2">
|
|
<div className="flex justify-between">
|
|
<span className="text-sm">Missing All Manuals</span>
|
|
<span className="font-semibold text-destructive">
|
|
{stats.missingAll}
|
|
</span>
|
|
</div>
|
|
<div className="flex justify-between">
|
|
<span className="text-sm">Partial Manuals</span>
|
|
<span className="font-semibold text-yellow-600">
|
|
{stats.partial}
|
|
</span>
|
|
</div>
|
|
<div className="flex justify-between">
|
|
<span className="text-sm">High Priority</span>
|
|
<span className="font-semibold">{stats.highPriority}</span>
|
|
</div>
|
|
<div className="flex justify-between">
|
|
<span className="text-sm">Medium Priority</span>
|
|
<span className="font-semibold">
|
|
{stats.mediumPriority}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="flex items-center gap-2">
|
|
<Code className="h-5 w-5" />
|
|
Q&A Categories
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="space-y-2">
|
|
{Object.entries(stats.qaCategories)
|
|
.sort(([, a]: any, [, b]: any) => b - a)
|
|
.map(([category, count]: [string, any]) => (
|
|
<div key={category} className="flex justify-between">
|
|
<span className="text-sm capitalize">{category}</span>
|
|
<span className="font-semibold">{count}</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="flex items-center gap-2">
|
|
<CheckCircle2 className="h-5 w-5" />
|
|
Metadata Extraction
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="space-y-4">
|
|
<div className="space-y-2">
|
|
<div className="flex justify-between">
|
|
<span className="text-sm">With Specifications</span>
|
|
<span className="font-semibold">
|
|
{stats.metadataWithSpecs}
|
|
</span>
|
|
</div>
|
|
<div className="flex justify-between">
|
|
<span className="text-sm">With Parts Lists</span>
|
|
<span className="font-semibold">
|
|
{stats.metadataWithParts}
|
|
</span>
|
|
</div>
|
|
<div className="flex justify-between">
|
|
<span className="text-sm">With Troubleshooting</span>
|
|
<span className="font-semibold">
|
|
{stats.metadataWithTroubleshooting}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
</TabsContent>
|
|
|
|
{/* Missing Manuals Tab */}
|
|
<TabsContent value="missing" className="space-y-4">
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>High Priority Missing Manuals</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="space-y-4">
|
|
{highPriorityMissing.map((item: any, index: number) => (
|
|
<div key={index} className="border rounded-lg p-4">
|
|
<div className="flex justify-between items-start mb-2">
|
|
<div>
|
|
<h3 className="font-semibold">
|
|
{item.manufacturer} {item.model_number}
|
|
</h3>
|
|
<p className="text-sm text-muted-foreground">
|
|
{item.model_name}
|
|
</p>
|
|
</div>
|
|
<span className="px-2 py-1 bg-red-100 text-red-800 text-xs rounded">
|
|
High Priority
|
|
</span>
|
|
</div>
|
|
<div className="mt-2">
|
|
<p className="text-sm">
|
|
<strong>Missing:</strong>{" "}
|
|
{item.missing_types?.join(", ")}
|
|
</p>
|
|
<p className="text-sm text-muted-foreground mt-1">
|
|
<strong>Sources:</strong>{" "}
|
|
{item.source_urls?.length || 0} URLs found
|
|
</p>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</TabsContent>
|
|
|
|
{/* Q&A Dataset Tab */}
|
|
<TabsContent value="qa" className="space-y-4">
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="flex items-center gap-2">
|
|
<Search className="h-5 w-5" />
|
|
Q&A Dataset ({stats.totalQAPairs.toLocaleString()} pairs)
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="mb-4">
|
|
<input
|
|
type="text"
|
|
placeholder="Search Q&A pairs..."
|
|
value={searchTerm}
|
|
onChange={(e) => setSearchTerm(e.target.value)}
|
|
className="w-full px-4 py-2 border rounded-lg"
|
|
/>
|
|
</div>
|
|
<div className="space-y-4 max-h-[600px] overflow-y-auto">
|
|
{filteredQA.map((item: any, index: number) => (
|
|
<div key={index} className="border rounded-lg p-4">
|
|
<div className="flex items-start justify-between mb-2">
|
|
<h3 className="font-semibold text-sm flex-1">
|
|
{item.question}
|
|
</h3>
|
|
{item.category && (
|
|
<span className="px-2 py-1 bg-blue-100 text-blue-800 text-xs rounded ml-2">
|
|
{item.category}
|
|
</span>
|
|
)}
|
|
</div>
|
|
<p className="text-sm text-muted-foreground">
|
|
{item.answer}
|
|
</p>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</TabsContent>
|
|
|
|
{/* Metadata Tab */}
|
|
<TabsContent value="metadata" className="space-y-4">
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Metadata Statistics</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="space-y-2">
|
|
<div className="flex justify-between">
|
|
<span>Total Manuals</span>
|
|
<span className="font-semibold">{stats.totalManuals}</span>
|
|
</div>
|
|
<div className="flex justify-between">
|
|
<span>With Specifications</span>
|
|
<span className="font-semibold">
|
|
{stats.metadataWithSpecs}
|
|
</span>
|
|
</div>
|
|
<div className="flex justify-between">
|
|
<span>With Parts Lists</span>
|
|
<span className="font-semibold">
|
|
{stats.metadataWithParts}
|
|
</span>
|
|
</div>
|
|
<div className="flex justify-between">
|
|
<span>With Troubleshooting</span>
|
|
<span className="font-semibold">
|
|
{stats.metadataWithTroubleshooting}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Sample Manual Metadata</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
{data.metadata.length > 0 && (
|
|
<div className="space-y-2 text-sm">
|
|
<p>
|
|
<strong>Manufacturer:</strong>{" "}
|
|
{data.metadata[0].manufacturer}
|
|
</p>
|
|
<p>
|
|
<strong>Model:</strong>{" "}
|
|
{data.metadata[0].model_number || "N/A"}
|
|
</p>
|
|
<p>
|
|
<strong>Type:</strong> {data.metadata[0].manual_type}
|
|
</p>
|
|
{data.metadata[0].specifications?.interfaces && (
|
|
<p>
|
|
<strong>Interfaces:</strong>{" "}
|
|
{data.metadata[0].specifications.interfaces.join(", ")}
|
|
</p>
|
|
)}
|
|
</div>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
</TabsContent>
|
|
|
|
{/* Optimization Tab */}
|
|
<TabsContent value="optimization" className="space-y-4">
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Structured Data (JSON-LD)</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="space-y-2">
|
|
<p className="text-sm">
|
|
<strong>Total Schemas:</strong> {data.structuredData.length}
|
|
</p>
|
|
<div className="mt-4">
|
|
<p className="text-sm font-semibold mb-2">Schema Types:</p>
|
|
{Object.entries(stats.schemaTypes).map(
|
|
([type, count]: [string, any]) => (
|
|
<div
|
|
key={type}
|
|
className="flex justify-between text-sm"
|
|
>
|
|
<span>{type}</span>
|
|
<span>{count}</span>
|
|
</div>
|
|
)
|
|
)}
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Semantic Index</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="space-y-2">
|
|
<div className="flex justify-between">
|
|
<span>Total Chunks</span>
|
|
<span className="font-semibold">{stats.totalChunks}</span>
|
|
</div>
|
|
<div className="flex justify-between">
|
|
<span>Manuals Indexed</span>
|
|
<span className="font-semibold">
|
|
{data.semanticIndex?.total_manuals || 0}
|
|
</span>
|
|
</div>
|
|
<p className="text-xs text-muted-foreground mt-4">
|
|
Note: Using placeholder embeddings. Ready for production
|
|
integration.
|
|
</p>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
</TabsContent>
|
|
</Tabs>
|
|
</div>
|
|
)
|
|
}
|