feat: add model info get data action

This commit is contained in:
Mahen 2026-02-06 11:26:57 +07:00
parent 7a1e82806e
commit 441d6f69e3
23 changed files with 1073 additions and 144 deletions

View File

@ -1,110 +0,0 @@
import { useState } from "react";
import { Badge } from "@/components/ui/badge";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { modelData } from "@/src/app/dashboard/lib/data";
import { cn } from "@/lib/utils";
export function ModelInfo() {
const [selectedModel, setSelectedModel] =
useState<keyof typeof modelData>("optimized");
const currentModel = modelData[selectedModel];
return (
<div className="rounded-xl border bg-card p-6 ">
<div className="mb-4 flex items-center justify-between gap-4">
<Select
value={selectedModel}
onValueChange={(value) =>
setSelectedModel(value as keyof typeof modelData)
}
>
<SelectTrigger className="w-fit justify-start gap-3 text-md font-semibold border-border bg-card shadow-sm transition-all focus:ring-primary/20">
<SelectValue placeholder="Pilih Model" />
</SelectTrigger>
<SelectContent
position="popper"
sideOffset={5}
className="min-w-65 bg-card border-border shadow-lg animate-in fade-in zoom-in-95 duration-200"
>
<SelectItem
value="baseline"
className="cursor-pointer px-4 py-2.5 transition-colors focus:bg-secondary focus:text-primary data-[state=checked]:text-primary data-[state=checked]:font-md"
>
Model XGBoost (Baseline)
</SelectItem>
<SelectItem
value="tuned"
className="cursor-pointer px-4 py-2.5 transition-colors focus:bg-secondary focus:text-primary data-[state=checked]:text-primary data-[state=checked]:font-md"
>
Model XGBoost (Tuned)
</SelectItem>
<SelectItem
value="optimized"
className="cursor-pointer px-4 py-2.5 transition-colors focus:bg-secondary focus:text-primary data-[state=checked]:text-primary data-[state=checked]:font-md"
>
Model XGBoost (Optimized)
</SelectItem>
</SelectContent>
</Select>
<Badge
variant="secondary"
className="bg-sentiment-positive-light text-sentiment-positive"
>
Active
</Badge>
</div>
<p className="mb-6 text-sm text-muted-foreground">
{currentModel.description}
</p>
<div className="grid grid-cols-2 gap-4">
{currentModel.metrics.map((metric) => (
<div
key={metric.label}
className={cn(
"flex items-center gap-3 rounded-lg p-3 transition-colors",
"bg-secondary/50 border border-border/40",
)}
>
<div className="rounded-lg bg-primary/10 p-2">
<metric.icon className="h-4 w-4 text-primary" />
</div>
<div>
<p className="text-xs text-muted-foreground">{metric.label}</p>
<p className="font-semibold">{metric.value}</p>
</div>
</div>
))}
</div>
<div className="mt-6 space-y-2 text-sm text-muted-foreground">
<div className="flex justify-between">
<span>Preprocessing</span>
<span className="text-foreground">
Case Folding, Stopwords, Stemming
</span>
</div>
<div className="flex justify-between">
<span>Feature Extraction</span>
<span className="text-foreground">TF-IDF Vectorization</span>
</div>
<div className="flex justify-between">
<span>Training Data</span>
<span className="text-foreground">3.445 ulasan</span>
</div>
</div>
</div>
);
}

View File

@ -0,0 +1,100 @@
"use client";
import { useState } from "react";
import { Badge } from "@/components/ui/badge";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { ModelDB } from "@/src/types";
import { useModelInfo } from "@/src/hooks/useModelInfo";
export function ModelInfo({ data }: { data: ModelDB[] }) {
const { selectedIndex, metrics, setSelectedIndex, currentModel } =
useModelInfo({ data });
if (!data || data.length === 0) {
return (
<div className="rounded-xl border bg-card p-6 flex items-center justify-center h-[350px]">
<p className="text-muted-foreground text-sm">
Data model tidak tersedia.
</p>
</div>
);
}
return (
<div className="rounded-xl border bg-card p-6">
<div className="mb-4 flex items-center justify-between gap-4">
<Select
value={selectedIndex.toString()}
onValueChange={(val) => setSelectedIndex(parseInt(val))}
>
<SelectTrigger className="w-fit justify-start gap-3 text-md font-semibold border-border bg-card shadow-sm">
<SelectValue placeholder="Pilih Model" />
</SelectTrigger>
<SelectContent className="bg-card border-border shadow-lg">
{data.map((model, index) => (
<SelectItem key={model.modelName} value={index.toString()}>
{model.modelName}
</SelectItem>
))}
</SelectContent>
</Select>
<Badge
variant="secondary"
className="bg-sentiment-positive-light text-sentiment-positive"
>
Active
</Badge>
</div>
<p className="mb-6 text-sm text-muted-foreground min-h-[40px]">
{currentModel.description}
</p>
<div className="grid grid-cols-2 gap-4">
{metrics.map((metric) => (
<div
key={metric.label}
className="flex items-center gap-3 rounded-lg p-3 bg-secondary/50 border border-border/40"
>
<div className="rounded-lg bg-primary/10 p-2">
<metric.icon className="h-4 w-4 text-primary" />
</div>
<div>
<p className="text-xs text-muted-foreground">{metric.label}</p>
<p className="font-semibold">
{typeof metric.value === "number"
? `${(metric.value * 100).toFixed(1)}%`
: metric.value}
</p>
</div>
</div>
))}
</div>
<div className="mt-6 space-y-2 text-sm text-muted-foreground border-t pt-4">
<div className="flex justify-between">
<span>Preprocessing</span>
<span className="text-foreground">
Case Folding, Stopwords, Stemming
</span>
</div>
<div className="flex justify-between">
<span>Feature Extraction</span>
<span className="text-foreground">TF-IDF Vectorization</span>
</div>
<div className="flex justify-between">
<span>Training Data</span>
<span className="text-foreground">3.445 Ulasan</span>
</div>
</div>
</div>
);
}

View File

@ -1,3 +1,5 @@
"use client";
import { useEffect, useState } from "react";
import { import {
AreaChart, AreaChart,
Area, Area,
@ -21,6 +23,11 @@ interface TrendChartProps {
} }
export function TrendChart({ data }: TrendChartProps) { export function TrendChart({ data }: TrendChartProps) {
const [isMounted, setIsMounted] = useState(false);
useEffect(() => {
setIsMounted(true);
}, []);
const CustomTooltip = ({ active, payload, label }: any) => { const CustomTooltip = ({ active, payload, label }: any) => {
if (active && payload && payload.length) { if (active && payload && payload.length) {
return ( return (
@ -42,8 +49,12 @@ export function TrendChart({ data }: TrendChartProps) {
return null; return null;
}; };
if (!isMounted) {
return <div className="h-[350px] w-full bg-transparent" />;
}
return ( return (
<div className="h-[350px] w-full"> <div className="h-[350px] min-h-[350px] w-full">
<ResponsiveContainer width="100%" height="100%"> <ResponsiveContainer width="100%" height="100%">
<AreaChart <AreaChart
data={data} data={data}
@ -68,11 +79,7 @@ export function TrendChart({ data }: TrendChartProps) {
stopColor="hsl(0, 72%, 51%)" stopColor="hsl(0, 72%, 51%)"
stopOpacity={0.3} stopOpacity={0.3}
/> />
<stop <stop offset="95%" stopColor="hsl(0, 72%, 51%)" stopOpacity={0} />
offset="95%"
stopColor="hsl(0, 72%, 51%)"
stopOpacity={0}
/>
</linearGradient> </linearGradient>
<linearGradient id="colorNetral" x1="0" y1="0" x2="0" y2="1"> <linearGradient id="colorNetral" x1="0" y1="0" x2="0" y2="1">
<stop <stop
@ -110,7 +117,9 @@ export function TrendChart({ data }: TrendChartProps) {
verticalAlign="top" verticalAlign="top"
height={36} height={36}
formatter={(value: string) => ( formatter={(value: string) => (
<span className="text-sm capitalize text-foreground">{value}</span> <span className="text-sm capitalize text-foreground">
{value}
</span>
)} )}
/> />
<Area <Area

View File

@ -0,0 +1,52 @@
import { ArrowDown, ChevronDown } from "lucide-react";
export function ModelInfoSkeleton() {
return (
<div className="rounded-xl border bg-card p-6">
<div className="mb-4 flex items-center justify-between gap-4">
<div className="flex items-center gap-3">
<div className="flex items-center pr-3 h-9 w-64 rounded-md border border-border">
<ChevronDown className="ml-auto h-4 w-4 text-gray-400" />
</div>
</div>
<div className="h-4.5 w-13.5 rounded-full bg-gray-200" />
</div>
<div className="mb-6 min-h-[40px] space-y-2">
<div className="h-4 w-3/4 rounded bg-gray-100" />
<div className="h-4 w-1/2 rounded bg-gray-100" />
</div>
<div className="grid grid-cols-2 gap-4 mb-2">
{[1, 2, 3, 4].map((i) => (
<div
key={i}
className="flex items-center gap-3 rounded-lg border border-border/40 bg-secondary/50 p-3"
>
<div className="flex h-9 w-9 items-center justify-center rounded-lg bg-muted/60">
<div className="h-7 w-7 rounded bg-gray-200" />
</div>
<div className="flex-1 space-y-2">
<div className="h-3 w-20 rounded bg-gray-100" />
<div className="h-4 w-15 rounded bg-gray-200" />
</div>
</div>
))}
</div>
{/* Footer info */}
<div className="mt-8 space-y-3 border-t pt-4">
{[1, 2, 3].map((i) => (
<div key={i} className="flex items-center justify-between">
{/* label */}
<div className="h-4 w-32 rounded bg-gray-100" />
{/* value */}
<div className="h-4 w-44 rounded bg-gray-100" />
</div>
))}
</div>
</div>
);
}

656
package-lock.json generated
View File

@ -25,7 +25,10 @@
"react": "19.2.3", "react": "19.2.3",
"react-dom": "19.2.3", "react-dom": "19.2.3",
"recharts": "^3.7.0", "recharts": "^3.7.0",
"tailwind-merge": "^3.4.0" "tailwind-merge": "^3.4.0",
"tailwindcss-animate": "^1.0.7",
"ts-node": "^10.9.2",
"tsx": "^4.21.0"
}, },
"devDependencies": { "devDependencies": {
"@prisma/adapter-neon": "^7.3.0", "@prisma/adapter-neon": "^7.3.0",
@ -395,6 +398,28 @@
"devOptional": true, "devOptional": true,
"license": "Apache-2.0" "license": "Apache-2.0"
}, },
"node_modules/@cspotcode/source-map-support": {
"version": "0.8.1",
"resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
"integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==",
"license": "MIT",
"dependencies": {
"@jridgewell/trace-mapping": "0.3.9"
},
"engines": {
"node": ">=12"
}
},
"node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": {
"version": "0.3.9",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz",
"integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==",
"license": "MIT",
"dependencies": {
"@jridgewell/resolve-uri": "^3.0.3",
"@jridgewell/sourcemap-codec": "^1.4.10"
}
},
"node_modules/@electric-sql/pglite": { "node_modules/@electric-sql/pglite": {
"version": "0.3.15", "version": "0.3.15",
"resolved": "https://registry.npmjs.org/@electric-sql/pglite/-/pglite-0.3.15.tgz", "resolved": "https://registry.npmjs.org/@electric-sql/pglite/-/pglite-0.3.15.tgz",
@ -458,6 +483,422 @@
"tslib": "^2.4.0" "tslib": "^2.4.0"
} }
}, },
"node_modules/@esbuild/aix-ppc64": {
"version": "0.27.3",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz",
"integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==",
"cpu": [
"ppc64"
],
"license": "MIT",
"optional": true,
"os": [
"aix"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/android-arm": {
"version": "0.27.3",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz",
"integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==",
"cpu": [
"arm"
],
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/android-arm64": {
"version": "0.27.3",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz",
"integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/android-x64": {
"version": "0.27.3",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz",
"integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/darwin-arm64": {
"version": "0.27.3",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz",
"integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/darwin-x64": {
"version": "0.27.3",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz",
"integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/freebsd-arm64": {
"version": "0.27.3",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz",
"integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/freebsd-x64": {
"version": "0.27.3",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz",
"integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-arm": {
"version": "0.27.3",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz",
"integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==",
"cpu": [
"arm"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-arm64": {
"version": "0.27.3",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz",
"integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-ia32": {
"version": "0.27.3",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz",
"integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==",
"cpu": [
"ia32"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-loong64": {
"version": "0.27.3",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz",
"integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==",
"cpu": [
"loong64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-mips64el": {
"version": "0.27.3",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz",
"integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==",
"cpu": [
"mips64el"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-ppc64": {
"version": "0.27.3",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz",
"integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==",
"cpu": [
"ppc64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-riscv64": {
"version": "0.27.3",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz",
"integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==",
"cpu": [
"riscv64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-s390x": {
"version": "0.27.3",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz",
"integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==",
"cpu": [
"s390x"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-x64": {
"version": "0.27.3",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz",
"integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/netbsd-arm64": {
"version": "0.27.3",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz",
"integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/netbsd-x64": {
"version": "0.27.3",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz",
"integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/openbsd-arm64": {
"version": "0.27.3",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz",
"integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/openbsd-x64": {
"version": "0.27.3",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz",
"integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/openharmony-arm64": {
"version": "0.27.3",
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz",
"integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"openharmony"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/sunos-x64": {
"version": "0.27.3",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz",
"integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"sunos"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/win32-arm64": {
"version": "0.27.3",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz",
"integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/win32-ia32": {
"version": "0.27.3",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz",
"integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==",
"cpu": [
"ia32"
],
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/win32-x64": {
"version": "0.27.3",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz",
"integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@eslint-community/eslint-utils": { "node_modules/@eslint-community/eslint-utils": {
"version": "4.9.1", "version": "4.9.1",
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz",
@ -1197,7 +1638,6 @@
"version": "3.1.2", "version": "3.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=6.0.0" "node": ">=6.0.0"
@ -1207,7 +1647,6 @@
"version": "1.5.5", "version": "1.5.5",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@jridgewell/trace-mapping": { "node_modules/@jridgewell/trace-mapping": {
@ -5056,6 +5495,30 @@
"tailwindcss": "4.1.18" "tailwindcss": "4.1.18"
} }
}, },
"node_modules/@tsconfig/node10": {
"version": "1.0.12",
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz",
"integrity": "sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==",
"license": "MIT"
},
"node_modules/@tsconfig/node12": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz",
"integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==",
"license": "MIT"
},
"node_modules/@tsconfig/node14": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz",
"integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==",
"license": "MIT"
},
"node_modules/@tsconfig/node16": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz",
"integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==",
"license": "MIT"
},
"node_modules/@tybys/wasm-util": { "node_modules/@tybys/wasm-util": {
"version": "0.10.1", "version": "0.10.1",
"resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz",
@ -5155,7 +5618,6 @@
"version": "20.19.30", "version": "20.19.30",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.30.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.30.tgz",
"integrity": "sha512-WJtwWJu7UdlvzEAUm484QNg5eAoq5QR08KDNx7g45Usrs2NtOPiX8ugDqmKdXkyL03rBqU5dYNYVQetEpBHq2g==", "integrity": "sha512-WJtwWJu7UdlvzEAUm484QNg5eAoq5QR08KDNx7g45Usrs2NtOPiX8ugDqmKdXkyL03rBqU5dYNYVQetEpBHq2g==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"undici-types": "~6.21.0" "undici-types": "~6.21.0"
@ -5741,7 +6203,6 @@
"version": "8.15.0", "version": "8.15.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"dev": true,
"license": "MIT", "license": "MIT",
"bin": { "bin": {
"acorn": "bin/acorn" "acorn": "bin/acorn"
@ -5760,6 +6221,18 @@
"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
} }
}, },
"node_modules/acorn-walk": {
"version": "8.3.4",
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz",
"integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==",
"license": "MIT",
"dependencies": {
"acorn": "^8.11.0"
},
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/ajv": { "node_modules/ajv": {
"version": "6.12.6", "version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
@ -5793,6 +6266,12 @@
"url": "https://github.com/chalk/ansi-styles?sponsor=1" "url": "https://github.com/chalk/ansi-styles?sponsor=1"
} }
}, },
"node_modules/arg": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
"integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
"license": "MIT"
},
"node_modules/argparse": { "node_modules/argparse": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
@ -6377,6 +6856,12 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/create-require": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
"integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
"license": "MIT"
},
"node_modules/cross-spawn": { "node_modules/cross-spawn": {
"version": "7.0.6", "version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
@ -6698,6 +7183,15 @@
"integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/diff": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz",
"integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==",
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.3.1"
}
},
"node_modules/doctrine": { "node_modules/doctrine": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz",
@ -6974,6 +7468,47 @@
"benchmarks" "benchmarks"
] ]
}, },
"node_modules/esbuild": {
"version": "0.27.3",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz",
"integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==",
"hasInstallScript": true,
"license": "MIT",
"bin": {
"esbuild": "bin/esbuild"
},
"engines": {
"node": ">=18"
},
"optionalDependencies": {
"@esbuild/aix-ppc64": "0.27.3",
"@esbuild/android-arm": "0.27.3",
"@esbuild/android-arm64": "0.27.3",
"@esbuild/android-x64": "0.27.3",
"@esbuild/darwin-arm64": "0.27.3",
"@esbuild/darwin-x64": "0.27.3",
"@esbuild/freebsd-arm64": "0.27.3",
"@esbuild/freebsd-x64": "0.27.3",
"@esbuild/linux-arm": "0.27.3",
"@esbuild/linux-arm64": "0.27.3",
"@esbuild/linux-ia32": "0.27.3",
"@esbuild/linux-loong64": "0.27.3",
"@esbuild/linux-mips64el": "0.27.3",
"@esbuild/linux-ppc64": "0.27.3",
"@esbuild/linux-riscv64": "0.27.3",
"@esbuild/linux-s390x": "0.27.3",
"@esbuild/linux-x64": "0.27.3",
"@esbuild/netbsd-arm64": "0.27.3",
"@esbuild/netbsd-x64": "0.27.3",
"@esbuild/openbsd-arm64": "0.27.3",
"@esbuild/openbsd-x64": "0.27.3",
"@esbuild/openharmony-arm64": "0.27.3",
"@esbuild/sunos-x64": "0.27.3",
"@esbuild/win32-arm64": "0.27.3",
"@esbuild/win32-ia32": "0.27.3",
"@esbuild/win32-x64": "0.27.3"
}
},
"node_modules/escalade": { "node_modules/escalade": {
"version": "3.2.0", "version": "3.2.0",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
@ -7642,6 +8177,20 @@
} }
} }
}, },
"node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/function-bind": { "node_modules/function-bind": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
@ -7790,7 +8339,6 @@
"version": "4.13.1", "version": "4.13.1",
"resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.1.tgz", "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.1.tgz",
"integrity": "sha512-EoY1N2xCn44xU6750Sx7OjOIT59FkmstNc3X6y5xpz7D5cBtZRe/3pSlTkDJgqsOk3WwZPkWfonhhUJfttQo3w==", "integrity": "sha512-EoY1N2xCn44xU6750Sx7OjOIT59FkmstNc3X6y5xpz7D5cBtZRe/3pSlTkDJgqsOk3WwZPkWfonhhUJfttQo3w==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"resolve-pkg-maps": "^1.0.0" "resolve-pkg-maps": "^1.0.0"
@ -9066,6 +9614,12 @@
"@jridgewell/sourcemap-codec": "^1.5.5" "@jridgewell/sourcemap-codec": "^1.5.5"
} }
}, },
"node_modules/make-error": {
"version": "1.3.6",
"resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
"integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
"license": "ISC"
},
"node_modules/math-intrinsics": { "node_modules/math-intrinsics": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
@ -10391,7 +10945,6 @@
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
"integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
"dev": true,
"license": "MIT", "license": "MIT",
"funding": { "funding": {
"url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
@ -11006,9 +11559,17 @@
"version": "4.1.18", "version": "4.1.18",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz",
"integrity": "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==", "integrity": "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/tailwindcss-animate": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/tailwindcss-animate/-/tailwindcss-animate-1.0.7.tgz",
"integrity": "sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==",
"license": "MIT",
"peerDependencies": {
"tailwindcss": ">=3.0.0 || insiders"
}
},
"node_modules/tapable": { "node_modules/tapable": {
"version": "2.3.0", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz",
@ -11113,6 +11674,49 @@
"typescript": ">=4.8.4" "typescript": ">=4.8.4"
} }
}, },
"node_modules/ts-node": {
"version": "10.9.2",
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz",
"integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==",
"license": "MIT",
"dependencies": {
"@cspotcode/source-map-support": "^0.8.0",
"@tsconfig/node10": "^1.0.7",
"@tsconfig/node12": "^1.0.7",
"@tsconfig/node14": "^1.0.0",
"@tsconfig/node16": "^1.0.2",
"acorn": "^8.4.1",
"acorn-walk": "^8.1.1",
"arg": "^4.1.0",
"create-require": "^1.1.0",
"diff": "^4.0.1",
"make-error": "^1.1.1",
"v8-compile-cache-lib": "^3.0.1",
"yn": "3.1.1"
},
"bin": {
"ts-node": "dist/bin.js",
"ts-node-cwd": "dist/bin-cwd.js",
"ts-node-esm": "dist/bin-esm.js",
"ts-node-script": "dist/bin-script.js",
"ts-node-transpile-only": "dist/bin-transpile.js",
"ts-script": "dist/bin-script-deprecated.js"
},
"peerDependencies": {
"@swc/core": ">=1.2.50",
"@swc/wasm": ">=1.2.50",
"@types/node": "*",
"typescript": ">=2.7"
},
"peerDependenciesMeta": {
"@swc/core": {
"optional": true
},
"@swc/wasm": {
"optional": true
}
}
},
"node_modules/tsconfig-paths": { "node_modules/tsconfig-paths": {
"version": "3.15.0", "version": "3.15.0",
"resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz",
@ -11145,6 +11749,25 @@
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"license": "0BSD" "license": "0BSD"
}, },
"node_modules/tsx": {
"version": "4.21.0",
"resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz",
"integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==",
"license": "MIT",
"dependencies": {
"esbuild": "~0.27.0",
"get-tsconfig": "^4.7.5"
},
"bin": {
"tsx": "dist/cli.mjs"
},
"engines": {
"node": ">=18.0.0"
},
"optionalDependencies": {
"fsevents": "~2.3.3"
}
},
"node_modules/tw-animate-css": { "node_modules/tw-animate-css": {
"version": "1.4.0", "version": "1.4.0",
"resolved": "https://registry.npmjs.org/tw-animate-css/-/tw-animate-css-1.4.0.tgz", "resolved": "https://registry.npmjs.org/tw-animate-css/-/tw-animate-css-1.4.0.tgz",
@ -11250,7 +11873,6 @@
"version": "5.9.3", "version": "5.9.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"devOptional": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"bin": { "bin": {
"tsc": "bin/tsc", "tsc": "bin/tsc",
@ -11307,7 +11929,6 @@
"version": "6.21.0", "version": "6.21.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/unrs-resolver": { "node_modules/unrs-resolver": {
@ -11438,6 +12059,12 @@
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
} }
}, },
"node_modules/v8-compile-cache-lib": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
"integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==",
"license": "MIT"
},
"node_modules/valibot": { "node_modules/valibot": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/valibot/-/valibot-1.2.0.tgz", "resolved": "https://registry.npmjs.org/valibot/-/valibot-1.2.0.tgz",
@ -11606,6 +12233,15 @@
"dev": true, "dev": true,
"license": "ISC" "license": "ISC"
}, },
"node_modules/yn": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
"integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/yocto-queue": { "node_modules/yocto-queue": {
"version": "0.1.0", "version": "0.1.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",

View File

@ -6,7 +6,8 @@
"dev": "next dev", "dev": "next dev",
"build": "next build", "build": "next build",
"start": "next start", "start": "next start",
"lint": "eslint" "lint": "eslint",
"seed": "prisma db seed"
}, },
"dependencies": { "dependencies": {
"@base-ui/react": "^1.1.0", "@base-ui/react": "^1.1.0",
@ -26,7 +27,10 @@
"react": "19.2.3", "react": "19.2.3",
"react-dom": "19.2.3", "react-dom": "19.2.3",
"recharts": "^3.7.0", "recharts": "^3.7.0",
"tailwind-merge": "^3.4.0" "tailwind-merge": "^3.4.0",
"tailwindcss-animate": "^1.0.7",
"ts-node": "^10.9.2",
"tsx": "^4.21.0"
}, },
"devDependencies": { "devDependencies": {
"@prisma/adapter-neon": "^7.3.0", "@prisma/adapter-neon": "^7.3.0",

View File

@ -1,5 +1,3 @@
// This file was generated by Prisma, and assumes you have installed the following:
// npm install --save-dev prisma dotenv
import "dotenv/config"; import "dotenv/config";
import { defineConfig } from "prisma/config"; import { defineConfig } from "prisma/config";
@ -7,6 +5,7 @@ export default defineConfig({
schema: "prisma/schema.prisma", schema: "prisma/schema.prisma",
migrations: { migrations: {
path: "prisma/migrations", path: "prisma/migrations",
seed: "tsx prisma/seed.ts",
}, },
datasource: { datasource: {
url: process.env["DATABASE_URL"], url: process.env["DATABASE_URL"],

View File

@ -5,3 +5,82 @@ generator client {
datasource db { datasource db {
provider = "postgresql" provider = "postgresql"
} }
enum UserGender {
male
female
other
}
enum Sentiment {
positive
negative
neutral
}
model User {
id Int @id @default(autoincrement())
email String @unique
name String?
gender UserGender?
productReference String?
password String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
analysis Analysis[]
}
model Analysis {
id Int @id @default(autoincrement())
userId Int
reviewId Int
productId Int
modelId Int
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id])
review Review @relation(fields: [reviewId], references: [id])
product Product @relation(fields: [productId], references: [id])
model Model @relation(fields: [modelId], references: [id])
}
model Product {
id Int @id @default(autoincrement())
name String
brand String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
reviews Review[]
analysis Analysis[]
}
model Review {
id Int @id @default(autoincrement())
productId Int
content String
keywords String[]
sentiment Sentiment
confidenceScore Float
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
product Product @relation(fields: [productId], references: [id])
analysis Analysis[]
}
model Model {
id Int @id @default(autoincrement())
modelName String
description String
accuracy Float
macroF1 Float
f1Negative Float
f1Neutral Float
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
analysis Analysis[]
}

53
prisma/seed.ts Normal file
View File

@ -0,0 +1,53 @@
import prisma from "@/lib/prisma";
async function main() {
console.log("Sedang memulai proses seeding...");
const modelData = [
{
modelName: "Model XGBoost (Baseline)",
description:
"Model awal menggunakan parameter default XGBoost (learning_rate=0.3, max_depth=6) pada dataset yang tidak seimbang.",
accuracy: 0.8,
macroF1: 0.56,
f1Negative: 0.61,
f1Neutral: 0.16,
},
{
modelName: "Model XGBoost (Tuned)",
description:
"Model dengan optimasi Hyperparameter menggunakan Grid Search untuk mencari kombinasi learning_rate dan max_depth terbaik.",
accuracy: 0.81,
macroF1: 0.58,
f1Negative: 0.65,
f1Neutral: 0.17,
},
{
modelName: "Model XGBoost (Optimized)",
description:
"Model final menggunakan teknik SMOTE untuk menyeimbangkan kelas, seleksi fitur Chi-Square, dan optimasi Grid Search.",
accuracy: 0.82,
macroF1: 0.61,
f1Negative: 0.65,
f1Neutral: 0.27,
},
];
for (const data of modelData) {
const model = await prisma.model.create({
data: data,
});
console.log(`Berhasil membuat model: ${model.modelName}`);
}
console.log("Proses seeding selesai.");
}
main()
.catch((e) => {
console.error(e);
process.exit(1);
})
.finally(async () => {
await prisma.$disconnect();
});

View File

@ -0,0 +1,30 @@
"use server";
import prisma from "@/lib/prisma";
import { notFound } from "next/navigation";
export const getClassificationReport = async () => {
try {
const response = await prisma.model.findMany({
select: {
modelName: true,
description: true,
accuracy: true,
macroF1: true,
f1Negative: true,
f1Neutral: true,
},
orderBy: {
createdAt: "asc",
},
});
if (!response || response.length === 0) {
return notFound();
}
return response;
} catch (error) {
console.error("Error fetching classification report:", error);
throw error;
}
};

View File

@ -1,5 +1,5 @@
"use client"; "use client";
import { useState } from "react"; import { Suspense, useEffect, useState } from "react";
import { import {
brandData, brandData,
reviewData, reviewData,
@ -7,7 +7,7 @@ import {
trendData, trendData,
wordCloudData, wordCloudData,
} from "./lib/data"; } from "./lib/data";
import { Header } from "@/components/dashboard/Header"; import { Header } from "@/components/dashboards/Header";
import { import {
MessageSquareText, MessageSquareText,
Minus, Minus,
@ -15,18 +15,22 @@ import {
ThumbsUp, ThumbsUp,
TrendingUp, TrendingUp,
} from "lucide-react"; } from "lucide-react";
import { StatCard } from "@/components/dashboard/StatCard"; import { StatCard } from "@/components/dashboards/StatCard";
import { TrendChart } from "@/components/dashboard/TrendChart"; import { TrendChart } from "@/components/dashboards/TrendChart";
import { SentimentChart } from "@/components/dashboard/SentimentChart"; import { SentimentChart } from "@/components/dashboards/SentimentChart";
import { WordCloud } from "@/components/dashboard/WordCloud"; import { WordCloud } from "@/components/dashboards/WordCloud";
import { ModelInfo } from "@/components/dashboard/ModelInfo"; import { ModelInfo } from "@/components/dashboards/ModelInfo";
import { SentimentAnalyzer } from "@/components/dashboard/SentimentAnalyzer"; import { SentimentAnalyzer } from "@/components/dashboards/SentimentAnalyzer";
import { BrandFilter } from "@/components/dashboard/BrandFilter"; import { BrandFilter } from "@/components/dashboards/BrandFilter";
import { ReviewTable } from "@/components/dashboard/ReviewTable"; import { ReviewTable } from "@/components/dashboards/ReviewTable";
import { getClassificationReport } from "./lib/actions";
import { ModelDB } from "@/src/types";
import { ModelInfoSkeleton } from "@/components/skeletons/ModelInfoSkeleton";
export default function DashboardPage() { export default function DashboardPage() {
const [selectedBrand, setSelectedBrand] = useState<string | null>(null); const [selectedBrand, setSelectedBrand] = useState<string | null>(null);
const [loading, setLoading] = useState(true);
const [modelData, setModelData] = useState<ModelDB[]>([]);
const totalReviews = sentimentDistribution.reduce( const totalReviews = sentimentDistribution.reduce(
(sum, s) => sum + s.value, (sum, s) => sum + s.value,
0, 0,
@ -42,6 +46,20 @@ export default function DashboardPage() {
? reviewData.filter((r) => r.brand === selectedBrand) ? reviewData.filter((r) => r.brand === selectedBrand)
: reviewData; : reviewData;
useEffect(() => {
async function fetchData() {
try {
const data = await getClassificationReport();
setModelData(data);
} catch (error) {
console.error("Failed to fetch model data", error);
} finally {
setLoading(false);
}
}
fetchData();
}, []);
return ( return (
<div className="min-h-screen bg-background"> <div className="min-h-screen bg-background">
<Header /> <Header />
@ -122,6 +140,7 @@ export default function DashboardPage() {
{/* Word Cloud & Model Info */} {/* Word Cloud & Model Info */}
<div className="mb-8 grid gap-6 lg:grid-cols-2"> <div className="mb-8 grid gap-6 lg:grid-cols-2">
{/* Slot Kata Kunci */}
<div className="rounded-xl border bg-card p-6"> <div className="rounded-xl border bg-card p-6">
<h3 className="mb-4 text-lg font-semibold">Kata Kunci Populer</h3> <h3 className="mb-4 text-lg font-semibold">Kata Kunci Populer</h3>
<p className="mb-4 text-sm text-muted-foreground"> <p className="mb-4 text-sm text-muted-foreground">
@ -130,7 +149,16 @@ export default function DashboardPage() {
</p> </p>
<WordCloud words={wordCloudData} /> <WordCloud words={wordCloudData} />
</div> </div>
<ModelInfo />
{loading ? (
<ModelInfoSkeleton />
) : modelData.length > 0 ? (
<ModelInfo data={modelData} />
) : (
<div className="rounded-xl border bg-card p-6 text-center text-muted-foreground">
Data model tidak tersedia.
</div>
)}
</div> </div>
{/* Sentiment Analyzer */} {/* Sentiment Analyzer */}

View File

@ -115,4 +115,10 @@
@apply backdrop-blur-lg; @apply backdrop-blur-lg;
background: hsl(var(--card) / 0.8); background: hsl(var(--card) / 0.8);
} }
@keyframes shimmer {
100% {
transform: translateX(100%);
}
}
} }

View File

@ -27,7 +27,7 @@ export default function RootLayout({
}>) { }>) {
return ( return (
<html lang="en"> <html lang="en">
<body className={`${inter.variable} font-sans`}>{children}</body> <body className={`${inter.className} font-sans`}>{children}</body>
</html> </html>
); );
} }

View File

@ -1,3 +1,4 @@
'use client";';
import { LoginPage } from "./auth/login/page"; import { LoginPage } from "./auth/login/page";
export default function Home() { export default function Home() {

34
src/hooks/useModelInfo.ts Normal file
View File

@ -0,0 +1,34 @@
import { useState } from "react";
import { ModelDB } from "../types";
import { BarChart3, CircleDot, Target, TrendingDown } from "lucide-react";
export const useModelInfo = ({ data }: { data: ModelDB[] }) => {
const [selectedIndex, setSelectedIndex] = useState(0);
const currentModel = data[selectedIndex];
const metrics = [
{
label: "Accuracy",
value: currentModel?.accuracy ?? 0,
icon: Target,
},
{
label: "Macro F1",
value: currentModel?.macroF1 ?? 0,
icon: BarChart3,
},
{
label: "F1 Negative",
value: currentModel?.f1Negative ?? 0,
icon: TrendingDown,
},
{
label: "F1 Neutral",
value: currentModel?.f1Neutral ?? 0,
icon: CircleDot,
},
];
return { selectedIndex, metrics, setSelectedIndex, currentModel };
};

8
src/types/index.ts Normal file
View File

@ -0,0 +1,8 @@
export interface ModelDB {
modelName: string;
description: string;
accuracy: number;
macroF1: number;
f1Negative: number;
f1Neutral: number;
}