From e31bb1fcff115a442ec8fc0f800155c6380ec1a7 Mon Sep 17 00:00:00 2001 From: sijinhui Date: Thu, 21 Dec 2023 22:22:31 +0800 Subject: [PATCH 1/5] init --- app/app/(admin)/admin/page.tsx | 10 +++--- app/app/(admin)/admin/testchart.tsx | 53 +++++++++++++++++++++++++++++ app/app/(admin)/layout.tsx | 9 ++--- package.json | 1 + 4 files changed, 63 insertions(+), 10 deletions(-) create mode 100644 app/app/(admin)/admin/testchart.tsx diff --git a/app/app/(admin)/admin/page.tsx b/app/app/(admin)/admin/page.tsx index c32d89fa9..bc8f813d6 100644 --- a/app/app/(admin)/admin/page.tsx +++ b/app/app/(admin)/admin/page.tsx @@ -1,18 +1,16 @@ // "use client"; import { Grid, Col, Card, Text, AreaChart, Metric } from "@tremor/react"; import UsageAnalysis from "./usage-analysis"; +import EchartsComponent from "./testchart"; export default function AdminPage() { return ( - + - - - Title - KPI 2 - + + ); diff --git a/app/app/(admin)/admin/testchart.tsx b/app/app/(admin)/admin/testchart.tsx new file mode 100644 index 000000000..dedcb1c24 --- /dev/null +++ b/app/app/(admin)/admin/testchart.tsx @@ -0,0 +1,53 @@ +"use client"; +import React, { useEffect } from "react"; +import * as echarts from "echarts"; // 导入 echarts + +const EchartsComponent: React.FC = () => { + useEffect(() => { + var chartDom = document.getElementById("charts"); + var myChart = echarts.init(chartDom); + var option; + + option = { + title: { + text: "Referer of a Website", + subtext: "Fake Data", + left: "center", + }, + tooltip: { + trigger: "item", + }, + legend: { + orient: "vertical", + left: "left", + }, + series: [ + { + name: "Access From", + type: "pie", + 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: { + itemStyle: { + shadowBlur: 10, + shadowOffsetX: 0, + shadowColor: "rgba(0, 0, 0, 0.5)", + }, + }, + }, + ], + }; + + option && myChart.setOption(option); + }, []); // 空数组作为第二个参数,表示仅在组件挂载和卸载时执行 + + return
; +}; + +export default EchartsComponent; diff --git a/app/app/(admin)/layout.tsx b/app/app/(admin)/layout.tsx index 8b7376bc6..7f0708649 100644 --- a/app/app/(admin)/layout.tsx +++ b/app/app/(admin)/layout.tsx @@ -12,13 +12,14 @@ export default async function AdminLayout({ children: ReactNode; }) { return ( -
-
-

+
+
+
+

Admin Page

- {children}
+ {children}
); } diff --git a/package.json b/package.json index e03b839e4..b344344db 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "@svgr/webpack": "^8.1.0", "@tremor/react": "^3.12.1", "@vercel/analytics": "^1.1.1", + "echarts": "^5.4.3", "emoji-picker-react": "^4.5.15", "fuse.js": "^7.0.0", "html-to-image": "^1.11.11", From c1ba0be98bf9ead82805e42022a56e12b71e9c26 Mon Sep 17 00:00:00 2001 From: sijinhui Date: Thu, 21 Dec 2023 22:49:36 +0800 Subject: [PATCH 2/5] =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=B8=83=E5=B1=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/app/(admin)/admin/page.tsx | 18 ++++++++++-------- app/app/(admin)/admin/testchart.tsx | 2 +- app/app/(admin)/layout.tsx | 11 ++++++----- 3 files changed, 17 insertions(+), 14 deletions(-) diff --git a/app/app/(admin)/admin/page.tsx b/app/app/(admin)/admin/page.tsx index bc8f813d6..bd26de746 100644 --- a/app/app/(admin)/admin/page.tsx +++ b/app/app/(admin)/admin/page.tsx @@ -5,13 +5,15 @@ import EchartsComponent from "./testchart"; export default function AdminPage() { return ( - - - - - - - - + <> + + + + + + + + + ); } diff --git a/app/app/(admin)/admin/testchart.tsx b/app/app/(admin)/admin/testchart.tsx index dedcb1c24..45513dc13 100644 --- a/app/app/(admin)/admin/testchart.tsx +++ b/app/app/(admin)/admin/testchart.tsx @@ -47,7 +47,7 @@ const EchartsComponent: React.FC = () => { option && myChart.setOption(option); }, []); // 空数组作为第二个参数,表示仅在组件挂载和卸载时执行 - return
; + return
; }; export default EchartsComponent; diff --git a/app/app/(admin)/layout.tsx b/app/app/(admin)/layout.tsx index 7f0708649..240c0e232 100644 --- a/app/app/(admin)/layout.tsx +++ b/app/app/(admin)/layout.tsx @@ -12,14 +12,15 @@ export default async function AdminLayout({ children: ReactNode; }) { return ( -
-
-
-

+
+
+

Admin Page

+
+ {children} +
- {children}
); } From cca33ae16fa94975cdefe663f9f8e225607fcd50 Mon Sep 17 00:00:00 2001 From: sijinhui Date: Fri, 22 Dec 2023 13:46:40 +0800 Subject: [PATCH 3/5] =?UTF-8?q?=E8=A7=A3=E5=86=B3=E5=9B=BE=E8=A1=A8?= =?UTF-8?q?=E6=98=BE=E7=A4=BA=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/app/(admin)/admin/page.tsx | 9 +++++++-- .../admin/{testchart.tsx => usage-by-model.tsx} | 14 ++++++++++++-- 2 files changed, 19 insertions(+), 4 deletions(-) rename app/app/(admin)/admin/{testchart.tsx => usage-by-model.tsx} (75%) diff --git a/app/app/(admin)/admin/page.tsx b/app/app/(admin)/admin/page.tsx index bd26de746..bef4e9350 100644 --- a/app/app/(admin)/admin/page.tsx +++ b/app/app/(admin)/admin/page.tsx @@ -1,7 +1,12 @@ // "use client"; import { Grid, Col, Card, Text, AreaChart, Metric } from "@tremor/react"; import UsageAnalysis from "./usage-analysis"; -import EchartsComponent from "./testchart"; +// import EchartsComponent from "./testchart"; +import dynamic from "next/dynamic"; + +const UsageByModelChart = dynamic(() => import("./usage-by-model"), { + ssr: false, +}); export default function AdminPage() { return ( @@ -11,7 +16,7 @@ export default function AdminPage() { - + diff --git a/app/app/(admin)/admin/testchart.tsx b/app/app/(admin)/admin/usage-by-model.tsx similarity index 75% rename from app/app/(admin)/admin/testchart.tsx rename to app/app/(admin)/admin/usage-by-model.tsx index 45513dc13..a6c84ea02 100644 --- a/app/app/(admin)/admin/testchart.tsx +++ b/app/app/(admin)/admin/usage-by-model.tsx @@ -2,9 +2,14 @@ import React, { useEffect } from "react"; import * as echarts from "echarts"; // 导入 echarts +// const isBrowser = () => typeof window !== 'undefined'; //The approach recommended by Next.js + const EchartsComponent: React.FC = () => { useEffect(() => { - var chartDom = document.getElementById("charts"); + // if (!isBrowser()) return; + var chartDom = document.getElementById("usage-by-model-chart"); + // if (!chartDiv.current) return; + // var myChart = echarts.init(chartDiv.current) var myChart = echarts.init(chartDom); var option; @@ -47,7 +52,12 @@ const EchartsComponent: React.FC = () => { option && myChart.setOption(option); }, []); // 空数组作为第二个参数,表示仅在组件挂载和卸载时执行 - return
; + return ( +
+ ); }; export default EchartsComponent; From d5e05adbe49226d4122bda38d7343cd559a6ab57 Mon Sep 17 00:00:00 2001 From: sijinhui Date: Fri, 22 Dec 2023 16:11:37 +0800 Subject: [PATCH 4/5] =?UTF-8?q?=E8=A7=A3=E5=86=B3=E5=9B=BE=E8=A1=A8?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E4=BC=A0=E9=80=92=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/app/(admin)/admin/page.tsx | 9 +- .../(admin)/admin/usage-by-model-chart.tsx | 30 ++++++ app/app/(admin)/admin/usage-by-model.tsx | 101 ++++++++---------- 3 files changed, 76 insertions(+), 64 deletions(-) create mode 100644 app/app/(admin)/admin/usage-by-model-chart.tsx diff --git a/app/app/(admin)/admin/page.tsx b/app/app/(admin)/admin/page.tsx index bef4e9350..e653a425b 100644 --- a/app/app/(admin)/admin/page.tsx +++ b/app/app/(admin)/admin/page.tsx @@ -1,12 +1,7 @@ // "use client"; import { Grid, Col, Card, Text, AreaChart, Metric } from "@tremor/react"; import UsageAnalysis from "./usage-analysis"; -// import EchartsComponent from "./testchart"; -import dynamic from "next/dynamic"; - -const UsageByModelChart = dynamic(() => import("./usage-by-model"), { - ssr: false, -}); +import UsageByModel from "./usage-by-model"; export default function AdminPage() { return ( @@ -16,7 +11,7 @@ export default function AdminPage() { - + diff --git a/app/app/(admin)/admin/usage-by-model-chart.tsx b/app/app/(admin)/admin/usage-by-model-chart.tsx new file mode 100644 index 000000000..b327d2dbb --- /dev/null +++ b/app/app/(admin)/admin/usage-by-model-chart.tsx @@ -0,0 +1,30 @@ +"use client"; +import React, { useEffect, useState } from "react"; +import * as echarts from "echarts"; // 导入 echarts + +export default function UsageByModelChart({ + option, +}: { + option: echarts.EChartsOption; +}) { + // const EchartsComponent: React.FC = ({ data: any }) => { + // const [option, setOption] = useState({}); + console.log("======", option); + + useEffect(() => { + var chartDom = document.getElementById("usage-by-model-chart"); + // if (chartDom) echarts.dispose(chartDom); + var myChart = echarts.init(chartDom); + // console.log('=======[option]', option) + option && myChart.setOption(option); + }, []); // 空数组作为第二个参数,表示仅在组件挂载和卸载时执行 + + return ( +
+ ); +} + +// export default EchartsComponent; diff --git a/app/app/(admin)/admin/usage-by-model.tsx b/app/app/(admin)/admin/usage-by-model.tsx index a6c84ea02..ea432fec5 100644 --- a/app/app/(admin)/admin/usage-by-model.tsx +++ b/app/app/(admin)/admin/usage-by-model.tsx @@ -1,63 +1,50 @@ -"use client"; -import React, { useEffect } from "react"; -import * as echarts from "echarts"; // 导入 echarts +import * as echarts from "echarts"; +import { EChartsOption } from "echarts"; +import dynamic from "next/dynamic"; -// const isBrowser = () => typeof window !== 'undefined'; //The approach recommended by Next.js +const UsageByModelChart = dynamic(() => import("./usage-by-model-chart"), { + ssr: false, +}); -const EchartsComponent: React.FC = () => { - useEffect(() => { - // if (!isBrowser()) return; - var chartDom = document.getElementById("usage-by-model-chart"); - // if (!chartDiv.current) return; - // var myChart = echarts.init(chartDiv.current) - var myChart = echarts.init(chartDom); - var option; - - option = { - title: { - text: "Referer of a Website", - subtext: "Fake Data", - left: "center", - }, - tooltip: { - trigger: "item", - }, - legend: { - orient: "vertical", - left: "left", - }, - series: [ - { - name: "Access From", - type: "pie", - 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: { - itemStyle: { - shadowBlur: 10, - shadowOffsetX: 0, - shadowColor: "rgba(0, 0, 0, 0.5)", - }, +export default function UsageByModel() { + const usageByModelOption: EChartsOption = { + title: { + text: "Referer of a Website", + subtext: "Fake Data", + left: "center", + }, + tooltip: { + trigger: "item", + }, + legend: { + orient: "vertical", + left: "left", + }, + series: [ + { + name: "Access From", + type: "pie", + 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: { + itemStyle: { + shadowBlur: 10, + shadowOffsetX: 0, + shadowColor: "rgba(0, 0, 0, 0.5)", }, }, - ], - }; - - option && myChart.setOption(option); - }, []); // 空数组作为第二个参数,表示仅在组件挂载和卸载时执行 - + }, + ], + }; return ( -
+ <> + + ); -}; - -export default EchartsComponent; +} From 1227fb1882936f3a51cfe86f46aaf10a24bd964f Mon Sep 17 00:00:00 2001 From: sijinhui Date: Fri, 22 Dec 2023 16:53:06 +0800 Subject: [PATCH 5/5] =?UTF-8?q?=E6=9B=BF=E6=8D=A2=E4=BD=BF=E7=94=A8echarts?= =?UTF-8?q?=E6=98=BE=E7=A4=BA=E5=9B=BE=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/app/(admin)/admin/page.tsx | 5 +- app/app/(admin)/admin/usage-analysis.tsx | 98 --------- .../(admin)/admin/usage-by-model-chart.tsx | 10 +- app/app/(admin)/admin/usage-by-model.tsx | 207 +++++++++++++++--- 4 files changed, 186 insertions(+), 134 deletions(-) diff --git a/app/app/(admin)/admin/page.tsx b/app/app/(admin)/admin/page.tsx index e653a425b..29c830745 100644 --- a/app/app/(admin)/admin/page.tsx +++ b/app/app/(admin)/admin/page.tsx @@ -1,6 +1,6 @@ // "use client"; 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"; export default function AdminPage() { @@ -8,7 +8,8 @@ export default function AdminPage() { <> - + {/**/} + {/**/} diff --git a/app/app/(admin)/admin/usage-analysis.tsx b/app/app/(admin)/admin/usage-analysis.tsx index 345d49652..2349f74de 100644 --- a/app/app/(admin)/admin/usage-analysis.tsx +++ b/app/app/(admin)/admin/usage-analysis.tsx @@ -54,105 +54,7 @@ export default async function UsageAnalysis() { // @ts-ignore const log_data = HandleLogData(todayLog); - // console.log("======", log_data); - // const data = [ - // { - // name: "Twitter", - // value: 456, - // href: "https://twitter.com/tremorlabs", - // icon: function TwitterIcon() { - // return ( - // - // - // - // - // ); - // }, - // }, - // { - // name: "Google", - // value: 351, - // href: "https://google.com", - // icon: function GoogleIcon() { - // return ( - // - // - // - // - // ); - // }, - // }, - // { - // name: "GitHub", - // value: 271, - // href: "https://github.com/tremorlabs/tremor", - // icon: function GitHubIcon() { - // return ( - // - // - // - // - // ); - // }, - // }, - // { - // name: "Reddit", - // value: 191, - // href: "https://reddit.com", - // icon: function RedditIcon() { - // return ( - // - // - // - // - // ); - // }, - // }, - // { - // name: "Youtube", - // value: 91, - // href: "https://www.youtube.com/@tremorlabs3079", - // icon: function YouTubeIcon() { - // return ( - // - // - // - // - // ); - // }, - // }, - // ]; // @ts-ignore return ( diff --git a/app/app/(admin)/admin/usage-by-model-chart.tsx b/app/app/(admin)/admin/usage-by-model-chart.tsx index b327d2dbb..ba41dc53f 100644 --- a/app/app/(admin)/admin/usage-by-model-chart.tsx +++ b/app/app/(admin)/admin/usage-by-model-chart.tsx @@ -7,17 +7,11 @@ export default function UsageByModelChart({ }: { option: echarts.EChartsOption; }) { - // const EchartsComponent: React.FC = ({ data: any }) => { - // const [option, setOption] = useState({}); - console.log("======", option); - useEffect(() => { var chartDom = document.getElementById("usage-by-model-chart"); - // if (chartDom) echarts.dispose(chartDom); var myChart = echarts.init(chartDom); - // console.log('=======[option]', option) option && myChart.setOption(option); - }, []); // 空数组作为第二个参数,表示仅在组件挂载和卸载时执行 + }, [option]); // 空数组作为第二个参数,表示仅在组件挂载和卸载时执行 return (
); } - -// export default EchartsComponent; diff --git a/app/app/(admin)/admin/usage-by-model.tsx b/app/app/(admin)/admin/usage-by-model.tsx index ea432fec5..fbc17e74c 100644 --- a/app/app/(admin)/admin/usage-by-model.tsx +++ b/app/app/(admin)/admin/usage-by-model.tsx @@ -1,45 +1,202 @@ import * as echarts from "echarts"; import { EChartsOption } from "echarts"; import dynamic from "next/dynamic"; +import prisma from "@/lib/prisma"; +import { estimateTokenLength } from "@/app/utils/token"; const UsageByModelChart = dynamic(() => import("./usage-by-model-chart"), { 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((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 = { - title: { - text: "Referer of a Website", - subtext: "Fake Data", - left: "center", - }, 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: [ { - name: "Access From", - type: "pie", - 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" }, - ], + name: "总量", + type: "bar", emphasis: { - itemStyle: { - shadowBlur: 10, - shadowOffsetX: 0, - shadowColor: "rgba(0, 0, 0, 0.5)", - }, + focus: "series", }, + 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 (