Spaces:
Running
Running
"use client" | |
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" | |
import { Badge } from "@/components/ui/badge" | |
import { Button } from "@/components/ui/button" | |
import type { SystemInfo, CategoryScore } from "@/components/ai-evaluation-dashboard" | |
import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, PieChart, Pie, Cell } from "recharts" | |
import { Download, AlertTriangle, CheckCircle, AlertCircle, XCircle, Globe, Calendar, User, Building, Cpu, MonitorSpeaker, Database, Hash, Tags, Clock, Activity, Headphones, Settings } from "lucide-react" | |
interface Category { | |
id: string | |
name: string | |
type: "capability" | "risk" | |
} | |
interface ResultsDashboardProps { | |
systemInfo: SystemInfo | null | |
categories: Category[] | |
selectedCategories: string[] | |
categoryScores: Record<string, CategoryScore> | |
excludedCategoryReasons?: Record<string, string> | |
} | |
const STATUS_COLORS = { | |
strong: "#22c55e", | |
adequate: "#3b82f6", | |
weak: "#f59e0b", | |
insufficient: "#ef4444", | |
} | |
const STATUS_ICONS = { | |
strong: CheckCircle, | |
adequate: CheckCircle, | |
weak: AlertCircle, | |
insufficient: XCircle, | |
} | |
export function ResultsDashboard({ | |
systemInfo, | |
categories, | |
selectedCategories, | |
categoryScores, | |
excludedCategoryReasons, | |
}: ResultsDashboardProps) { | |
const safeCategories = categories || [] | |
const safeSelectedCategories = selectedCategories || [] | |
const safeCategoryScores = categoryScores || {} | |
console.log("[v0] ResultsDashboard rendering with:", { | |
systemInfo, | |
categoriesCount: safeCategories.length, | |
selectedCount: safeSelectedCategories.length, | |
scoresCount: Object.keys(safeCategoryScores).length, | |
scores: safeCategoryScores, | |
}) | |
const selectedCategoryObjects = safeCategories.filter((c) => safeSelectedCategories.includes(c.id)) | |
const getStatusCounts = () => { | |
const scores = Object.values(safeCategoryScores) | |
return { | |
strong: scores.filter((s) => s.status === "strong").length, | |
adequate: scores.filter((s) => s.status === "adequate").length, | |
weak: scores.filter((s) => s.status === "weak").length, | |
insufficient: scores.filter((s) => s.status === "insufficient").length, | |
} | |
} | |
const getCapabilityRiskBreakdown = () => { | |
const capability = selectedCategoryObjects.filter((c) => c.type === "capability") | |
const risk = selectedCategoryObjects.filter((c) => c.type === "risk") | |
const capabilityScores = capability.map((c) => safeCategoryScores[c.id]).filter(Boolean) | |
const riskScores = risk.map((c) => safeCategoryScores[c.id]).filter(Boolean) | |
const avgCapability = | |
capabilityScores.length > 0 | |
? capabilityScores.reduce((sum, s) => sum + (s.totalScore || 0), 0) / capabilityScores.length | |
: 0 | |
const avgRisk = | |
riskScores.length > 0 ? riskScores.reduce((sum, s) => sum + (s.totalScore || 0), 0) / riskScores.length : 0 | |
const safeCapability = isNaN(avgCapability) || !isFinite(avgCapability) ? 0 : avgCapability | |
const safeRisk = isNaN(avgRisk) || !isFinite(avgRisk) ? 0 : avgRisk | |
console.log("[v0] Capability/Risk breakdown:", { | |
capabilityScores: capabilityScores.length, | |
riskScores: riskScores.length, | |
avgCapability, | |
avgRisk, | |
safeCapability, | |
safeRisk, | |
}) | |
return { | |
capability: safeCapability, | |
risk: safeRisk, | |
} | |
} | |
const getChartData = () => { | |
return selectedCategoryObjects | |
.map((category) => { | |
const score = safeCategoryScores[category.id] | |
return { | |
name: category.name.length > 20 ? category.name.substring(0, 20) + "..." : category.name, | |
fullName: category.name, | |
benchmarkScore: score?.benchmarkScore || 0, | |
processScore: score?.processScore || 0, | |
totalScore: score?.totalScore || 0, | |
type: category.type, | |
} | |
}) | |
.sort((a, b) => b.totalScore - a.totalScore) | |
} | |
const getPieData = () => { | |
const counts = getStatusCounts() | |
return [ | |
{ name: "Strong (12-15)", value: counts.strong, color: STATUS_COLORS.strong }, | |
{ name: "Adequate (8-11)", value: counts.adequate, color: STATUS_COLORS.adequate }, | |
{ name: "Weak (4-7)", value: counts.weak, color: STATUS_COLORS.weak }, | |
{ name: "Insufficient (0-3)", value: counts.insufficient, color: STATUS_COLORS.insufficient }, | |
].filter((item) => item.value > 0) | |
} | |
const statusCounts = getStatusCounts() | |
const breakdown = getCapabilityRiskBreakdown() | |
const chartData = getChartData() | |
const pieData = getPieData() | |
const totalEvaluated = Object.keys(safeCategoryScores).length | |
const overallAverage = | |
totalEvaluated > 0 | |
? Object.values(safeCategoryScores).reduce((sum, s) => sum + (s.totalScore || 0), 0) / totalEvaluated | |
: 0 | |
const safeOverallAverage = isNaN(overallAverage) || !isFinite(overallAverage) ? 0 : overallAverage | |
console.log("[v0] Overall average calculation:", { | |
totalEvaluated, | |
overallAverage, | |
safeOverallAverage, | |
}) | |
const safeToFixed = (value: number, digits = 1): string => { | |
if (isNaN(value) || !isFinite(value)) { | |
console.log("[v0] Warning: Invalid value for toFixed:", value) | |
return "0.0" | |
} | |
return value.toFixed(digits) | |
} | |
const exportResults = () => { | |
const results = { | |
systemInfo, | |
evaluationDate: new Date().toISOString(), | |
summary: { | |
totalCategories: safeSelectedCategories.length, | |
evaluatedCategories: totalEvaluated, | |
overallAverage: safeOverallAverage.toFixed(1), | |
statusBreakdown: statusCounts, | |
}, | |
categoryResults: safeCategories.map((category) => ({ | |
...category, | |
score: safeCategoryScores[category.id] || null, | |
excludedReason: !safeSelectedCategories.includes(category.id) | |
? excludedCategoryReasons?.[category.id] || null | |
: null, | |
})), | |
} | |
const blob = new Blob([JSON.stringify(results, null, 2)], { type: "application/json" }) | |
const url = URL.createObjectURL(blob) | |
const a = document.createElement("a") | |
a.href = url | |
a.download = `ai-evaluation-${systemInfo?.name || "system"}-${new Date().toISOString().split("T")[0]}.json` | |
a.click() | |
URL.revokeObjectURL(url) | |
} | |
return ( | |
<div className="space-y-6"> | |
{/* System Overview */} | |
<Card> | |
<CardHeader> | |
<div className="flex items-center justify-between"> | |
<div> | |
<CardTitle className="font-heading">Evaluation Results</CardTitle> | |
<CardDescription>Comprehensive assessment results for {systemInfo?.name}</CardDescription> | |
</div> | |
<Button onClick={exportResults} variant="outline"> | |
<Download className="h-4 w-4 mr-2" /> | |
Export Results | |
</Button> | |
</div> | |
</CardHeader> | |
<CardContent> | |
<div className="grid grid-cols-1 md:grid-cols-4 gap-4"> | |
<div className="text-center"> | |
<div className="text-2xl font-bold text-foreground">{totalEvaluated}</div> | |
<div className="text-sm text-muted-foreground">Categories Evaluated</div> | |
</div> | |
<div className="text-center"> | |
<div className="text-2xl font-bold text-foreground">{safeToFixed(safeOverallAverage)}/15</div> | |
<div className="text-sm text-muted-foreground">Overall Average</div> | |
</div> | |
<div className="text-center"> | |
<div className="text-2xl font-bold text-foreground">{safeToFixed(breakdown.capability)}/15</div> | |
<div className="text-sm text-muted-foreground">Capability Average</div> | |
</div> | |
<div className="text-center"> | |
<div className="text-2xl font-bold text-foreground">{safeToFixed(breakdown.risk)}/15</div> | |
<div className="text-sm text-muted-foreground">Risk Average</div> | |
</div> | |
</div> | |
</CardContent> | |
</Card> | |
{/* System Information (detailed) */} | |
<Card className="mb-6"> | |
<CardHeader> | |
<div className="flex items-center justify-between"> | |
<div> | |
<CardTitle className="font-heading flex items-center gap-2"> | |
<Database className="h-5 w-5 text-blue-600" /> | |
System Information | |
</CardTitle> | |
<CardDescription>A summary of metadata provided for the evaluated system</CardDescription> | |
</div> | |
</div> | |
</CardHeader> | |
<CardContent> | |
<div className="grid grid-cols-1 md:grid-cols-2 gap-8"> | |
{/* Left Column */} | |
<div className="space-y-6"> | |
<div className="flex items-start gap-3"> | |
<div className="p-2 bg-blue-50 dark:bg-blue-950 rounded-lg"> | |
<Cpu className="h-4 w-4 text-blue-600 dark:text-blue-400" /> | |
</div> | |
<div className="flex-1"> | |
<div className="text-sm font-medium text-muted-foreground">System Name</div> | |
<div className="text-lg font-semibold text-foreground mt-1"> | |
{(systemInfo as any)?.systemName || systemInfo?.name || "—"} | |
</div> | |
</div> | |
</div> | |
<div className="flex items-start gap-3"> | |
<div className="p-2 bg-green-50 dark:bg-green-950 rounded-lg"> | |
<Tags className="h-4 w-4 text-green-600 dark:text-green-400" /> | |
</div> | |
<div className="flex-1"> | |
<div className="text-sm font-medium text-muted-foreground">System Version</div> | |
<div className="text-lg font-semibold text-foreground mt-1"> | |
{systemInfo?.version || (systemInfo as any)?.modelTag || "—"} | |
</div> | |
</div> | |
</div> | |
<div className="flex items-start gap-3"> | |
<div className="p-2 bg-purple-50 dark:bg-purple-950 rounded-lg"> | |
<Building className="h-4 w-4 text-purple-600 dark:text-purple-400" /> | |
</div> | |
<div className="flex-1"> | |
<div className="text-sm font-medium text-muted-foreground">Provider</div> | |
<div className="text-lg font-semibold text-foreground mt-1">{systemInfo?.provider || "—"}</div> | |
</div> | |
</div> | |
<div className="flex items-start gap-3"> | |
<div className="p-2 bg-indigo-50 dark:bg-indigo-950 rounded-lg"> | |
<Globe className="h-4 w-4 text-indigo-600 dark:text-indigo-400" /> | |
</div> | |
<div className="flex-1"> | |
<div className="text-sm font-medium text-muted-foreground">URL</div> | |
<div className="text-lg font-semibold text-foreground mt-1 break-all"> | |
{systemInfo?.url ? ( | |
<a href={systemInfo.url} target="_blank" rel="noreferrer" className="text-primary hover:text-primary/80 underline decoration-primary/30 hover:decoration-primary/60 transition-colors"> | |
{systemInfo.url} | |
</a> | |
) : ( | |
"—" | |
)} | |
</div> | |
</div> | |
</div> | |
<div className="flex items-start gap-3"> | |
<div className="p-2 bg-orange-50 dark:bg-orange-950 rounded-lg"> | |
<Settings className="h-4 w-4 text-orange-600 dark:text-orange-400" /> | |
</div> | |
<div className="flex-1"> | |
<div className="text-sm font-medium text-muted-foreground">Model Type</div> | |
<div className="text-lg font-semibold text-foreground mt-1"> | |
{systemInfo?.modelType ? ( | |
<Badge variant="secondary" className="text-sm"> | |
{systemInfo.modelType === "foundational" ? "Foundational Model" : | |
systemInfo.modelType === "fine-tuned" ? "Fine-tuned Model" : | |
"Not Applicable"} | |
</Badge> | |
) : "—"} | |
</div> | |
</div> | |
</div> | |
</div> | |
{/* Right Column */} | |
<div className="space-y-6"> | |
<div className="flex items-start gap-3"> | |
<div className="p-2 bg-cyan-50 dark:bg-cyan-950 rounded-lg"> | |
<Globe className="h-4 w-4 text-cyan-600 dark:text-cyan-400" /> | |
</div> | |
<div className="flex-1"> | |
<div className="text-sm font-medium text-muted-foreground">Deployment Context</div> | |
<div className="text-lg font-semibold text-foreground mt-1"> | |
{(systemInfo as any)?.deploymentContext || (systemInfo?.deploymentContexts && systemInfo.deploymentContexts.join(", ")) || "—"} | |
</div> | |
</div> | |
</div> | |
<div className="flex items-start gap-3"> | |
<div className="p-2 bg-emerald-50 dark:bg-emerald-950 rounded-lg"> | |
<Activity className="h-4 w-4 text-emerald-600 dark:text-emerald-400" /> | |
</div> | |
<div className="flex-1"> | |
<div className="text-sm font-medium text-muted-foreground">Input Modalities</div> | |
<div className="text-lg font-semibold text-foreground mt-1"> | |
{systemInfo?.inputModalities?.length ? ( | |
<div className="flex flex-wrap gap-1"> | |
{systemInfo.inputModalities.map((modality: string, idx: number) => ( | |
<Badge key={idx} variant="outline" className="text-xs"> | |
{modality} | |
</Badge> | |
))} | |
</div> | |
) : "—"} | |
</div> | |
</div> | |
</div> | |
<div className="flex items-start gap-3"> | |
<div className="p-2 bg-rose-50 dark:bg-rose-950 rounded-lg"> | |
<MonitorSpeaker className="h-4 w-4 text-rose-600 dark:text-rose-400" /> | |
</div> | |
<div className="flex-1"> | |
<div className="text-sm font-medium text-muted-foreground">Output Modalities</div> | |
<div className="text-lg font-semibold text-foreground mt-1"> | |
{systemInfo?.outputModalities?.length ? ( | |
<div className="flex flex-wrap gap-1"> | |
{systemInfo.outputModalities.map((modality: string, idx: number) => ( | |
<Badge key={idx} variant="outline" className="text-xs"> | |
{modality} | |
</Badge> | |
))} | |
</div> | |
) : "—"} | |
</div> | |
</div> | |
</div> | |
<div className="flex items-start gap-3"> | |
<div className="p-2 bg-amber-50 dark:bg-amber-950 rounded-lg"> | |
<Clock className="h-4 w-4 text-amber-600 dark:text-amber-400" /> | |
</div> | |
<div className="flex-1"> | |
<div className="text-sm font-medium text-muted-foreground">Knowledge Cutoff</div> | |
<div className="text-lg font-semibold text-foreground mt-1">{systemInfo?.knowledgeCutoff || "—"}</div> | |
</div> | |
</div> | |
<div className="flex items-start gap-3"> | |
<div className="p-2 bg-violet-50 dark:bg-violet-950 rounded-lg"> | |
<User className="h-4 w-4 text-violet-600 dark:text-violet-400" /> | |
</div> | |
<div className="flex-1"> | |
<div className="text-sm font-medium text-muted-foreground">Evaluator</div> | |
<div className="text-lg font-semibold text-foreground mt-1">{(systemInfo as any)?.evaluator || "—"}</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
{/* Bottom Row - Metadata Cards */} | |
<div className="mt-8 grid grid-cols-1 md:grid-cols-3 gap-4"> | |
<div className="p-4 bg-gradient-to-r from-blue-50 to-blue-100 dark:from-blue-950/50 dark:to-blue-900/50 rounded-lg border border-blue-200 dark:border-blue-800"> | |
<div className="flex items-center gap-2 mb-2"> | |
<Hash className="h-4 w-4 text-blue-600 dark:text-blue-400" /> | |
<div className="text-sm font-medium text-blue-700 dark:text-blue-300">System ID</div> | |
</div> | |
<div className="font-mono text-sm text-blue-900 dark:text-blue-100 break-all">{(systemInfo as any)?.id || "—"}</div> | |
</div> | |
<div className="p-4 bg-gradient-to-r from-green-50 to-green-100 dark:from-green-950/50 dark:to-green-900/50 rounded-lg border border-green-200 dark:border-green-800"> | |
<div className="flex items-center gap-2 mb-2"> | |
<Tags className="h-4 w-4 text-green-600 dark:text-green-400" /> | |
<div className="text-sm font-medium text-green-700 dark:text-green-300">System Types</div> | |
</div> | |
<div className="font-medium text-green-900 dark:text-green-100">{(systemInfo as any)?.systemTypes?.length ? (systemInfo as any).systemTypes.join(", ") : "—"}</div> | |
</div> | |
<div className="p-4 bg-gradient-to-r from-purple-50 to-purple-100 dark:from-purple-950/50 dark:to-purple-900/50 rounded-lg border border-purple-200 dark:border-purple-800"> | |
<div className="flex items-center gap-2 mb-2"> | |
<Calendar className="h-4 w-4 text-purple-600 dark:text-purple-400" /> | |
<div className="text-sm font-medium text-purple-700 dark:text-purple-300">Evaluation Date</div> | |
</div> | |
<div className="font-medium text-purple-900 dark:text-purple-100">{(systemInfo as any)?.evaluationDate || "—"}</div> | |
</div> | |
</div> | |
</CardContent> | |
</Card> | |
{/* Status Overview */} | |
<div className="grid grid-cols-1 md:grid-cols-4 gap-4"> | |
{Object.entries(statusCounts).map(([status, count]) => { | |
const key = (status as string) as keyof typeof STATUS_ICONS | |
const Icon = STATUS_ICONS[key] ?? AlertTriangle | |
return ( | |
<Card key={status}> | |
<CardContent className="pt-6"> | |
<div className="flex items-center justify-between"> | |
<div> | |
<div className="text-2xl font-bold">{count}</div> | |
<div className="text-sm text-muted-foreground capitalize">{status}</div> | |
</div> | |
<Icon className="h-8 w-8" style={{ color: STATUS_COLORS[key] ?? STATUS_COLORS.insufficient }} /> | |
</div> | |
</CardContent> | |
</Card> | |
) | |
})} | |
</div> | |
{/* Charts */} | |
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6"> | |
{/* Score Distribution */} | |
<Card> | |
<CardHeader> | |
<CardTitle>Score Distribution</CardTitle> | |
<CardDescription>Categories by evaluation status</CardDescription> | |
</CardHeader> | |
<CardContent> | |
<ResponsiveContainer width="100%" height={300}> | |
<PieChart> | |
<Pie | |
data={pieData} | |
cx="50%" | |
cy="50%" | |
outerRadius={80} | |
dataKey="value" | |
label={({ name, value }) => `${name}: ${value}`} | |
> | |
{pieData.map((entry, index) => ( | |
<Cell key={`cell-${index}`} fill={entry.color} /> | |
))} | |
</Pie> | |
<Tooltip /> | |
</PieChart> | |
</ResponsiveContainer> | |
</CardContent> | |
</Card> | |
{/* Category Scores */} | |
<Card> | |
<CardHeader> | |
<CardTitle>Category Scores</CardTitle> | |
<CardDescription>Benchmark vs Process scores by category</CardDescription> | |
</CardHeader> | |
<CardContent> | |
<ResponsiveContainer width="100%" height={300}> | |
<BarChart data={chartData.slice(0, 8)} layout="horizontal"> | |
<CartesianGrid strokeDasharray="3 3" /> | |
<XAxis type="number" domain={[0, 15]} /> | |
<YAxis dataKey="name" type="category" width={100} /> | |
<Tooltip labelFormatter={(label) => chartData.find((d) => d.name === label)?.fullName || label} /> | |
<Bar dataKey="benchmarkScore" stackId="a" fill="#3b82f6" name="Benchmark" /> | |
<Bar dataKey="processScore" stackId="a" fill="#8b5cf6" name="Process" /> | |
</BarChart> | |
</ResponsiveContainer> | |
</CardContent> | |
</Card> | |
</div> | |
{/* Detailed Results */} | |
<Card> | |
<CardHeader> | |
<CardTitle>Detailed Category Results</CardTitle> | |
<CardDescription>Complete breakdown of all evaluated categories</CardDescription> | |
</CardHeader> | |
<CardContent> | |
<div className="space-y-4"> | |
{selectedCategoryObjects.map((category) => { | |
const score = safeCategoryScores[category.id] | |
if (!score) return null | |
const key = (score.status as string) as keyof typeof STATUS_ICONS | |
const Icon = (STATUS_ICONS as any)[key] ?? AlertTriangle | |
const color = (STATUS_COLORS as any)[key] ?? STATUS_COLORS.insufficient | |
return ( | |
<div key={category.id} className="flex items-center justify-between p-4 border rounded-lg"> | |
<div className="flex items-center gap-3"> | |
<Icon className="h-5 w-5" style={{ color }} /> | |
<div> | |
<div className="font-medium">{category.name}</div> | |
<div className="flex items-center gap-2 mt-1"> | |
<Badge | |
variant={category.type === "capability" ? "secondary" : "destructive"} | |
className="text-xs" | |
> | |
{category.type} | |
</Badge> | |
<span className="text-sm text-muted-foreground"> | |
Benchmark: {score.benchmarkScore}/7 | Process: {score.processScore}/8 | |
</span> | |
</div> | |
</div> | |
</div> | |
<div className="text-right"> | |
<div className="text-lg font-bold">{score.totalScore}/15</div> | |
<Badge | |
variant={ | |
score.status === "strong" | |
? "default" | |
: score.status === "adequate" | |
? "secondary" | |
: score.status === "weak" | |
? "outline" | |
: "destructive" | |
} | |
className="text-xs" | |
> | |
{score.status} | |
</Badge> | |
</div> | |
</div> | |
) | |
})} | |
</div> | |
</CardContent> | |
</Card> | |
{/* Excluded Categories with Reasons */} | |
<Card> | |
<CardHeader> | |
<CardTitle>Excluded Categories & Reasons</CardTitle> | |
<CardDescription>Categories you marked as not applicable and the rationale provided</CardDescription> | |
</CardHeader> | |
<CardContent> | |
<div className="space-y-3"> | |
{safeCategories | |
.filter((c) => !safeSelectedCategories.includes(c.id)) | |
.map((c) => ( | |
<div key={c.id} className="p-3 border rounded-md"> | |
<div className="font-medium">{c.name}</div> | |
<div className="text-sm text-muted-foreground mt-1"> | |
{excludedCategoryReasons?.[c.id] || "No reason provided"} | |
</div> | |
</div> | |
))} | |
</div> | |
</CardContent> | |
</Card> | |
{/* Priority Actions */} | |
<Card> | |
<CardHeader> | |
<CardTitle className="flex items-center gap-2"> | |
<AlertTriangle className="h-5 w-5 text-amber-500" /> | |
Priority Action Areas | |
</CardTitle> | |
<CardDescription>Categories requiring immediate attention</CardDescription> | |
</CardHeader> | |
<CardContent> | |
<div className="space-y-4"> | |
{statusCounts.insufficient > 0 && ( | |
<div> | |
<h4 className="font-medium text-destructive mb-2"> | |
Critical - Insufficient Categories ({statusCounts.insufficient}) | |
</h4> | |
<div className="grid grid-cols-1 md:grid-cols-2 gap-2"> | |
{selectedCategoryObjects | |
.filter((c) => safeCategoryScores[c.id]?.status === "insufficient") | |
.map((category) => ( | |
<Badge key={category.id} variant="destructive" className="justify-start"> | |
{category.name} | |
</Badge> | |
))} | |
</div> | |
</div> | |
)} | |
{statusCounts.weak > 0 && ( | |
<div> | |
<h4 className="font-medium text-amber-600 mb-2"> | |
High Priority - Weak Categories ({statusCounts.weak}) | |
</h4> | |
<div className="grid grid-cols-1 md:grid-cols-2 gap-2"> | |
{selectedCategoryObjects | |
.filter((c) => safeCategoryScores[c.id]?.status === "weak") | |
.map((category) => ( | |
<Badge key={category.id} variant="outline" className="justify-start"> | |
{category.name} | |
</Badge> | |
))} | |
</div> | |
</div> | |
)} | |
{statusCounts.insufficient === 0 && statusCounts.weak === 0 && ( | |
<div className="text-center py-8 text-muted-foreground"> | |
<CheckCircle className="h-12 w-12 mx-auto mb-2 text-green-600" /> | |
<p>No critical priority areas identified. All categories scored adequate or above.</p> | |
</div> | |
)} | |
</div> | |
</CardContent> | |
</Card> | |
</div> | |
) | |
} | |