mirror of
https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web.git
synced 2025-10-03 00:26:40 +08:00
替换使用echarts显示图表
This commit is contained in:
parent
d5e05adbe4
commit
1227fb1882
@ -1,6 +1,6 @@
|
|||||||
// "use client";
|
// "use client";
|
||||||
import { Grid, Col, Card, Text, AreaChart, Metric } from "@tremor/react";
|
import { Grid, Col, Card, Text, AreaChart, Metric } from "@tremor/react";
|
||||||
import UsageAnalysis from "./usage-analysis";
|
// import UsageAnalysis from "./usage-analysis";
|
||||||
import UsageByModel from "./usage-by-model";
|
import UsageByModel from "./usage-by-model";
|
||||||
|
|
||||||
export default function AdminPage() {
|
export default function AdminPage() {
|
||||||
@ -8,7 +8,8 @@ export default function AdminPage() {
|
|||||||
<>
|
<>
|
||||||
<Grid numItems={1} numItemsSm={2} numItemsLg={3} className="gap-2">
|
<Grid numItems={1} numItemsSm={2} numItemsLg={3} className="gap-2">
|
||||||
<Col numColSpan={1} numColSpanLg={1}>
|
<Col numColSpan={1} numColSpanLg={1}>
|
||||||
<UsageAnalysis />
|
{/*<UsageAnalysis />*/}
|
||||||
|
{/*<Card></Card>*/}
|
||||||
</Col>
|
</Col>
|
||||||
<Col numColSpan={1} numColSpanLg={2}>
|
<Col numColSpan={1} numColSpanLg={2}>
|
||||||
<UsageByModel />
|
<UsageByModel />
|
||||||
|
@ -54,105 +54,7 @@ export default async function UsageAnalysis() {
|
|||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const log_data = HandleLogData(todayLog);
|
const log_data = HandleLogData(todayLog);
|
||||||
// console.log("======", log_data);
|
|
||||||
|
|
||||||
// const data = [
|
|
||||||
// {
|
|
||||||
// name: "Twitter",
|
|
||||||
// value: 456,
|
|
||||||
// href: "https://twitter.com/tremorlabs",
|
|
||||||
// icon: function TwitterIcon() {
|
|
||||||
// return (
|
|
||||||
// <svg
|
|
||||||
// xmlns="http://www.w3.org/2000/svg"
|
|
||||||
// className="mr-2.5 fill-blue-500"
|
|
||||||
// viewBox="0 0 24 24"
|
|
||||||
// width="20"
|
|
||||||
// height="20"
|
|
||||||
// >
|
|
||||||
// <path fill="none" d="M0 0h24v24H0z" />
|
|
||||||
// <path d="M22.162 5.656a8.384 8.384 0 0 1-2.402.658A4.196 4.196 0 0 0 21.6 4c-.82.488-1.719.83-2.656 1.015a4.182 4.182 0 0 0-7.126 3.814 11.874 11.874 0 0 1-8.62-4.37 4.168 4.168 0 0 0-.566 2.103c0 1.45.738 2.731 1.86 3.481a4.168 4.168 0 0 1-1.894-.523v.052a4.185 4.185 0 0 0 3.355 4.101 4.21 4.21 0 0 1-1.89.072A4.185 4.185 0 0 0 7.97 16.65a8.394 8.394 0 0 1-6.191 1.732 11.83 11.83 0 0 0 6.41 1.88c7.693 0 11.9-6.373 11.9-11.9 0-.18-.005-.362-.013-.54a8.496 8.496 0 0 0 2.087-2.165z" />
|
|
||||||
// </svg>
|
|
||||||
// );
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// name: "Google",
|
|
||||||
// value: 351,
|
|
||||||
// href: "https://google.com",
|
|
||||||
// icon: function GoogleIcon() {
|
|
||||||
// return (
|
|
||||||
// <svg
|
|
||||||
// xmlns="http://www.w3.org/2000/svg"
|
|
||||||
// className="mr-2.5 fill-slate-500"
|
|
||||||
// viewBox="0 0 24 24"
|
|
||||||
// width="20"
|
|
||||||
// height="20"
|
|
||||||
// >
|
|
||||||
// <path fill="none" d="M0 0h24v24H0z" />
|
|
||||||
// <path d="M3.064 7.51A9.996 9.996 0 0 1 12 2c2.695 0 4.959.99 6.69 2.605l-2.867 2.868C14.786 6.482 13.468 5.977 12 5.977c-2.605 0-4.81 1.76-5.595 4.123-.2.6-.314 1.24-.314 1.9 0 .66.114 1.3.314 1.9.786 2.364 2.99 4.123 5.595 4.123 1.345 0 2.49-.355 3.386-.955a4.6 4.6 0 0 0 1.996-3.018H12v-3.868h9.418c.118.654.182 1.336.182 2.045 0 3.046-1.09 5.61-2.982 7.35C16.964 21.105 14.7 22 12 22A9.996 9.996 0 0 1 2 12c0-1.614.386-3.14 1.064-4.49z" />
|
|
||||||
// </svg>
|
|
||||||
// );
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// name: "GitHub",
|
|
||||||
// value: 271,
|
|
||||||
// href: "https://github.com/tremorlabs/tremor",
|
|
||||||
// icon: function GitHubIcon() {
|
|
||||||
// return (
|
|
||||||
// <svg
|
|
||||||
// xmlns="http://www.w3.org/2000/svg"
|
|
||||||
// className="mr-2.5 fill-slate-900"
|
|
||||||
// viewBox="0 0 24 24"
|
|
||||||
// width="20"
|
|
||||||
// height="20"
|
|
||||||
// >
|
|
||||||
// <path fill="none" d="M0 0h24v24H0z" />
|
|
||||||
// <path d="M12 2C6.475 2 2 6.475 2 12a9.994 9.994 0 0 0 6.838 9.488c.5.087.687-.213.687-.476 0-.237-.013-1.024-.013-1.862-2.512.463-3.162-.612-3.362-1.175-.113-.288-.6-1.175-1.025-1.413-.35-.187-.85-.65-.013-.662.788-.013 1.35.725 1.538 1.025.9 1.512 2.338 1.087 2.912.825.088-.65.35-1.087.638-1.337-2.225-.25-4.55-1.113-4.55-4.938 0-1.088.387-1.987 1.025-2.688-.1-.25-.45-1.275.1-2.65 0 0 .837-.262 2.75 1.026a9.28 9.28 0 0 1 2.5-.338c.85 0 1.7.112 2.5.337 1.912-1.3 2.75-1.024 2.75-1.024.55 1.375.2 2.4.1 2.65.637.7 1.025 1.587 1.025 2.687 0 3.838-2.337 4.688-4.562 4.938.362.312.675.912.675 1.85 0 1.337-.013 2.412-.013 2.75 0 .262.188.574.688.474A10.016 10.016 0 0 0 22 12c0-5.525-4.475-10-10-10z" />
|
|
||||||
// </svg>
|
|
||||||
// );
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// name: "Reddit",
|
|
||||||
// value: 191,
|
|
||||||
// href: "https://reddit.com",
|
|
||||||
// icon: function RedditIcon() {
|
|
||||||
// return (
|
|
||||||
// <svg
|
|
||||||
// xmlns="http://www.w3.org/2000/svg"
|
|
||||||
// className="mr-2.5 fill-orange-500"
|
|
||||||
// viewBox="0 0 24 24"
|
|
||||||
// width="20"
|
|
||||||
// height="20"
|
|
||||||
// >
|
|
||||||
// <path fill="none" d="M0 0h24v24H0z" />
|
|
||||||
// <path d="M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10-4.477 10-10 10zm6.67-10a1.46 1.46 0 0 0-2.47-1 7.12 7.12 0 0 0-3.85-1.23L13 6.65l2.14.45a1 1 0 1 0 .13-.61L12.82 6a.31.31 0 0 0-.37.24l-.74 3.47a7.14 7.14 0 0 0-3.9 1.23 1.46 1.46 0 1 0-1.61 2.39 2.87 2.87 0 0 0 0 .44c0 2.24 2.61 4.06 5.83 4.06s5.83-1.82 5.83-4.06a2.87 2.87 0 0 0 0-.44 1.46 1.46 0 0 0 .81-1.33zm-10 1a1 1 0 1 1 2 0 1 1 0 0 1-2 0zm5.81 2.75a3.84 3.84 0 0 1-2.47.77 3.84 3.84 0 0 1-2.47-.77.27.27 0 0 1 .38-.38A3.27 3.27 0 0 0 12 16a3.28 3.28 0 0 0 2.09-.61.28.28 0 1 1 .39.4v-.04zm-.18-1.71a1 1 0 1 1 1-1 1 1 0 0 1-1.01 1.04l.01-.04z" />
|
|
||||||
// </svg>
|
|
||||||
// );
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// name: "Youtube",
|
|
||||||
// value: 91,
|
|
||||||
// href: "https://www.youtube.com/@tremorlabs3079",
|
|
||||||
// icon: function YouTubeIcon() {
|
|
||||||
// return (
|
|
||||||
// <svg
|
|
||||||
// xmlns="http://www.w3.org/2000/svg"
|
|
||||||
// className="mr-2.5 fill-red-500"
|
|
||||||
// viewBox="0 0 24 24"
|
|
||||||
// width="20"
|
|
||||||
// height="20"
|
|
||||||
// >
|
|
||||||
// <path fill="none" d="M0 0h24v24H0z" />
|
|
||||||
// <path d="M21.543 6.498C22 8.28 22 12 22 12s0 3.72-.457 5.502c-.254.985-.997 1.76-1.938 2.022C17.896 20 12 20 12 20s-5.893 0-7.605-.476c-.945-.266-1.687-1.04-1.938-2.022C2 15.72 2 12 2 12s0-3.72.457-5.502c.254-.985.997-1.76 1.938-2.022C6.107 4 12 4 12 4s5.896 0 7.605.476c.945.266 1.687 1.04 1.938 2.022zM10 15.5l6-3.5-6-3.5v7z" />
|
|
||||||
// </svg>
|
|
||||||
// );
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
// ];
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
return (
|
return (
|
||||||
<Card className="max-w-lg">
|
<Card className="max-w-lg">
|
||||||
|
@ -7,17 +7,11 @@ export default function UsageByModelChart({
|
|||||||
}: {
|
}: {
|
||||||
option: echarts.EChartsOption;
|
option: echarts.EChartsOption;
|
||||||
}) {
|
}) {
|
||||||
// const EchartsComponent: React.FC = ({ data: any }) => {
|
|
||||||
// const [option, setOption] = useState({});
|
|
||||||
console.log("======", option);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
var chartDom = document.getElementById("usage-by-model-chart");
|
var chartDom = document.getElementById("usage-by-model-chart");
|
||||||
// if (chartDom) echarts.dispose(chartDom);
|
|
||||||
var myChart = echarts.init(chartDom);
|
var myChart = echarts.init(chartDom);
|
||||||
// console.log('=======[option]', option)
|
|
||||||
option && myChart.setOption(option);
|
option && myChart.setOption(option);
|
||||||
}, []); // 空数组作为第二个参数,表示仅在组件挂载和卸载时执行
|
}, [option]); // 空数组作为第二个参数,表示仅在组件挂载和卸载时执行
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@ -26,5 +20,3 @@ export default function UsageByModelChart({
|
|||||||
></div>
|
></div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// export default EchartsComponent;
|
|
||||||
|
@ -1,45 +1,202 @@
|
|||||||
import * as echarts from "echarts";
|
import * as echarts from "echarts";
|
||||||
import { EChartsOption } from "echarts";
|
import { EChartsOption } from "echarts";
|
||||||
import dynamic from "next/dynamic";
|
import dynamic from "next/dynamic";
|
||||||
|
import prisma from "@/lib/prisma";
|
||||||
|
import { estimateTokenLength } from "@/app/utils/token";
|
||||||
|
|
||||||
const UsageByModelChart = dynamic(() => import("./usage-by-model-chart"), {
|
const UsageByModelChart = dynamic(() => import("./usage-by-model-chart"), {
|
||||||
ssr: false,
|
ssr: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
export default function UsageByModel() {
|
function HandleLogData(todayLog: [{ userName: string; logEntry: string }]) {
|
||||||
|
const data1 = todayLog.map((log) => {
|
||||||
|
return {
|
||||||
|
name: log.userName ?? "unknown",
|
||||||
|
value: estimateTokenLength(log.logEntry ?? ""),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
type Accumulator = {
|
||||||
|
[key: string]: number;
|
||||||
|
};
|
||||||
|
const data2 = data1.reduce<Accumulator>((acc, item) => {
|
||||||
|
if (acc[item.name]) {
|
||||||
|
acc[item.name] += item.value;
|
||||||
|
} else {
|
||||||
|
acc[item.name] = item.value;
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
const data3 = Object.entries(data2)
|
||||||
|
.map(([name, value]) => ({
|
||||||
|
name,
|
||||||
|
value,
|
||||||
|
}))
|
||||||
|
.sort((a, b) => {
|
||||||
|
return a.value - b.value;
|
||||||
|
});
|
||||||
|
|
||||||
|
const data4 = data3.reduce(
|
||||||
|
(acc, cur) => {
|
||||||
|
// @ts-ignore
|
||||||
|
acc.name.push(cur.name);
|
||||||
|
// @ts-ignore
|
||||||
|
acc.value.push(cur.value);
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{ name: [], value: [] },
|
||||||
|
);
|
||||||
|
return data4;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function UsageByModel() {
|
||||||
|
// 今天日期的开始和结束
|
||||||
|
const startDate = new Date();
|
||||||
|
startDate.setHours(0, 0, 0, 0);
|
||||||
|
|
||||||
|
const endDate = new Date();
|
||||||
|
endDate.setHours(23, 59, 59, 999);
|
||||||
|
const todayLog = await prisma.logEntry.findMany({
|
||||||
|
where: {
|
||||||
|
createdAt: {
|
||||||
|
gte: startDate, // gte 表示 '大于等于'
|
||||||
|
lte: endDate, // lte 表示 '小于等于'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
user: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
const log_data = HandleLogData(todayLog);
|
||||||
|
|
||||||
|
// console.log('[log_data]====---==', log_data)
|
||||||
|
|
||||||
const usageByModelOption: EChartsOption = {
|
const usageByModelOption: EChartsOption = {
|
||||||
title: {
|
|
||||||
text: "Referer of a Website",
|
|
||||||
subtext: "Fake Data",
|
|
||||||
left: "center",
|
|
||||||
},
|
|
||||||
tooltip: {
|
tooltip: {
|
||||||
trigger: "item",
|
trigger: "axis",
|
||||||
|
axisPointer: {
|
||||||
|
type: "shadow",
|
||||||
},
|
},
|
||||||
legend: {
|
|
||||||
orient: "vertical",
|
|
||||||
left: "left",
|
|
||||||
},
|
},
|
||||||
|
title: {
|
||||||
|
show: true,
|
||||||
|
text: "token使用分析",
|
||||||
|
},
|
||||||
|
legend: {},
|
||||||
|
grid: {
|
||||||
|
left: "3%",
|
||||||
|
right: "4%",
|
||||||
|
bottom: "3%",
|
||||||
|
containLabel: true,
|
||||||
|
},
|
||||||
|
xAxis: [
|
||||||
|
{
|
||||||
|
type: "value",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
yAxis: [
|
||||||
|
{
|
||||||
|
type: "category",
|
||||||
|
data: log_data.name,
|
||||||
|
},
|
||||||
|
],
|
||||||
series: [
|
series: [
|
||||||
{
|
{
|
||||||
name: "Access From",
|
name: "总量",
|
||||||
type: "pie",
|
type: "bar",
|
||||||
radius: "50%",
|
|
||||||
data: [
|
|
||||||
{ value: 1048, name: "Search Engine" },
|
|
||||||
{ value: 735, name: "Direct" },
|
|
||||||
{ value: 580, name: "Email" },
|
|
||||||
{ value: 484, name: "Union Ads" },
|
|
||||||
{ value: 300, name: "Video Ads" },
|
|
||||||
],
|
|
||||||
emphasis: {
|
emphasis: {
|
||||||
itemStyle: {
|
focus: "series",
|
||||||
shadowBlur: 10,
|
|
||||||
shadowOffsetX: 0,
|
|
||||||
shadowColor: "rgba(0, 0, 0, 0.5)",
|
|
||||||
},
|
},
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
position: "right",
|
||||||
},
|
},
|
||||||
|
colorBy: "data",
|
||||||
|
// progress: {
|
||||||
|
// show: true
|
||||||
|
// },
|
||||||
|
data: log_data.value,
|
||||||
},
|
},
|
||||||
|
// {
|
||||||
|
// name: 'Email',
|
||||||
|
// type: 'bar',
|
||||||
|
// stack: 'Ad',
|
||||||
|
// emphasis: {
|
||||||
|
// focus: 'series'
|
||||||
|
// },
|
||||||
|
// data: [120, 132, 101, 134, 90, 230, 210]
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// name: 'Union Ads',
|
||||||
|
// type: 'bar',
|
||||||
|
// stack: 'Ad',
|
||||||
|
// emphasis: {
|
||||||
|
// focus: 'series'
|
||||||
|
// },
|
||||||
|
// data: [220, 182, 191, 234, 290, 330, 310]
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// name: 'Video Ads',
|
||||||
|
// type: 'bar',
|
||||||
|
// stack: 'Ad',
|
||||||
|
// emphasis: {
|
||||||
|
// focus: 'series'
|
||||||
|
// },
|
||||||
|
// data: [150, 232, 201, 154, 190, 330, 410]
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// name: 'Search Engine',
|
||||||
|
// type: 'bar',
|
||||||
|
// data: [862, 1018, 964, 1026, 1679, 1600, 1570],
|
||||||
|
// emphasis: {
|
||||||
|
// focus: 'series'
|
||||||
|
// },
|
||||||
|
// // markLine: {
|
||||||
|
// // lineStyle: {
|
||||||
|
// // type: 'dashed'
|
||||||
|
// // },
|
||||||
|
// // data: [[{ type: 'min' }, { type: 'max' }]]
|
||||||
|
// // }
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// name: 'Baidu',
|
||||||
|
// type: 'bar',
|
||||||
|
// barWidth: 5,
|
||||||
|
// stack: 'Search Engine',
|
||||||
|
// emphasis: {
|
||||||
|
// focus: 'series'
|
||||||
|
// },
|
||||||
|
// data: [620, 732, 701, 734, 1090, 1130, 1120]
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// name: 'Google',
|
||||||
|
// type: 'bar',
|
||||||
|
// stack: 'Search Engine',
|
||||||
|
// emphasis: {
|
||||||
|
// focus: 'series'
|
||||||
|
// },
|
||||||
|
// data: [120, 132, 101, 134, 290, 230, 220]
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// name: 'Bing',
|
||||||
|
// type: 'bar',
|
||||||
|
// stack: 'Search Engine',
|
||||||
|
// emphasis: {
|
||||||
|
// focus: 'series'
|
||||||
|
// },
|
||||||
|
// data: [60, 72, 71, 74, 190, 130, 110]
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// name: 'Others',
|
||||||
|
// type: 'bar',
|
||||||
|
// stack: 'Search Engine',
|
||||||
|
// emphasis: {
|
||||||
|
// focus: 'series'
|
||||||
|
// },
|
||||||
|
// data: [62, 82, 91, 84, 109, 110, 120]
|
||||||
|
// }
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
|
Loading…
Reference in New Issue
Block a user