ERP-node/frontend/app/(main)/admin/aiAssistant/history/page.tsx

158 lines
5.9 KiB
TypeScript

"use client";
import { useState, useEffect } from "react";
import { aiAssistantApi } from "@/lib/api/aiAssistant";
import type { UsageLogItem } from "@/lib/api/aiAssistant";
import { History, Loader2, MessageSquare, Clock, Zap, CheckCircle, XCircle } from "lucide-react";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
import { Button } from "@/components/ui/button";
import { toast } from "sonner";
export default function AiAssistantHistoryPage() {
const [loading, setLoading] = useState(true);
const [logs, setLogs] = useState<UsageLogItem[]>([]);
const [page, setPage] = useState(1);
const [totalPages, setTotalPages] = useState(1);
useEffect(() => {
loadLogs();
}, [page]);
const loadLogs = async () => {
setLoading(true);
try {
const res = await aiAssistantApi.get(`/usage/logs?page=${page}&limit=20`);
const data = res.data?.data as { logs?: UsageLogItem[]; pagination?: { totalPages?: number } };
setLogs(data?.logs ?? []);
setTotalPages(data?.pagination?.totalPages ?? 1);
} catch {
toast.error("대화 이력을 불러오는데 실패했습니다.");
} finally {
setLoading(false);
}
};
if (loading && logs.length === 0) {
return (
<div className="flex h-64 items-center justify-center">
<Loader2 className="text-primary h-8 w-8 animate-spin" />
</div>
);
}
return (
<div className="space-y-6">
<div>
<h1 className="text-3xl font-bold tracking-tight"> </h1>
<p className="text-muted-foreground mt-1">AI Assistant와의 .</p>
</div>
<Card>
<CardHeader>
<CardTitle>API </CardTitle>
<CardDescription> API </CardDescription>
</CardHeader>
<CardContent>
{logs.length === 0 ? (
<div className="flex flex-col items-center justify-center py-12 text-center">
<History className="text-muted-foreground mb-4 h-12 w-12" />
<h3 className="text-lg font-medium"> </h3>
<p className="text-muted-foreground mt-1 text-sm">AI .</p>
</div>
) : (
<>
<Table>
<TableHeader>
<TableRow>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{logs.map((log) => (
<TableRow key={log.id}>
<TableCell>
{log.success ? (
<Badge variant="success" className="gap-1">
<CheckCircle className="h-3 w-3" />
</Badge>
) : (
<Badge variant="destructive" className="gap-1">
<XCircle className="h-3 w-3" />
</Badge>
)}
</TableCell>
<TableCell>
<Badge variant="outline">{log.providerName}</Badge>
</TableCell>
<TableCell className="font-mono text-sm">{log.modelName}</TableCell>
<TableCell>
<div className="flex items-center gap-1">
<Zap className="text-muted-foreground h-3 w-3" />
<span>{(log.totalTokens ?? 0).toLocaleString()}</span>
</div>
<div className="text-muted-foreground text-xs">
: {log.promptTokens ?? 0} / : {log.completionTokens ?? 0}
</div>
</TableCell>
<TableCell>${(log.costUsd ?? 0).toFixed(6)}</TableCell>
<TableCell>
<div className="flex items-center gap-1">
<Clock className="text-muted-foreground h-3 w-3" />
<span>{log.responseTimeMs ?? 0}ms</span>
</div>
</TableCell>
<TableCell className="text-muted-foreground">
{new Date(log.createdAt).toLocaleString("ko-KR")}
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
{totalPages > 1 && (
<div className="mt-4 flex items-center justify-center gap-2">
<Button
variant="outline"
size="sm"
onClick={() => setPage((p) => Math.max(1, p - 1))}
disabled={page === 1}
>
</Button>
<span className="text-muted-foreground text-sm">
{page} / {totalPages}
</span>
<Button
variant="outline"
size="sm"
onClick={() => setPage((p) => Math.min(totalPages, p + 1))}
disabled={page === totalPages}
>
</Button>
</div>
)}
</>
)}
</CardContent>
</Card>
</div>
);
}