From 067ccb481bf8fe017864bf23c748325e301e5274 Mon Sep 17 00:00:00 2001 From: sijinhui Date: Fri, 29 Dec 2023 23:49:26 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E7=AE=A1=E7=90=86=E5=91=98?= =?UTF-8?q?=E7=95=8C=E9=9D=A2=E7=9B=B8=E5=85=B3=E9=97=AE=E9=A2=98=EF=BC=8C?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E9=A1=B5=E9=9D=A2=E6=98=BE=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/api/logs/[...path]/route.ts | 5 + app/app/(admin)/admin/usage-by-model.tsx | 293 ++++++++++++----------- lib/auth.ts | 4 +- 3 files changed, 158 insertions(+), 144 deletions(-) diff --git a/app/api/logs/[...path]/route.ts b/app/api/logs/[...path]/route.ts index f9a885247..c5c5a8e3b 100644 --- a/app/api/logs/[...path]/route.ts +++ b/app/api/logs/[...path]/route.ts @@ -6,6 +6,7 @@ import { insertUser } from "@/lib/auth"; // import cl100k_base from "tiktoken/encoders/cl100k_base.json" import "tiktoken"; import { get_encoding } from "tiktoken"; +import { addHours, subMinutes } from "date-fns"; function getTokenLength(input: string): number { // const { Tiktoken } = require("tiktoken/lite"); @@ -45,6 +46,10 @@ async function handle( console.log("[LOG]", "logToken", e); request_data.logToken = 0; } + // 默认时间不准,还是手动获取一下吧。 + // 转换时区太麻烦,我还是直接减去时差 + // const current_time = new Date(); + // request_data.createdAt = subMinutes(current_time, current_time.getTimezoneOffset()) await prisma.logEntry.create({ data: request_data, diff --git a/app/app/(admin)/admin/usage-by-model.tsx b/app/app/(admin)/admin/usage-by-model.tsx index a3495eec0..af1aa575a 100644 --- a/app/app/(admin)/admin/usage-by-model.tsx +++ b/app/app/(admin)/admin/usage-by-model.tsx @@ -2,7 +2,9 @@ import * as echarts from "echarts"; import { EChartsOption } from "echarts"; import dynamic from "next/dynamic"; import prisma from "@/lib/prisma"; -import { addHours } from "date-fns"; +import { addHours, subMinutes } from "date-fns"; +import { log } from "util"; +import { use } from "react"; // import { getTokenLength } from "@/app/utils/token"; @@ -10,53 +12,60 @@ const UsageByModelChart = dynamic(() => import("./usage-by-model-chart"), { ssr: false, }); +interface StringKeyedObject { + [key: string]: { [key: string]: number }; +} +// interface StringArray { +// strings: string[]; +// } + +type StringSet = Set; +type StringArray = string[]; + function HandleLogData( - todayLog: [{ userName: string; logEntry: string; logToken: number }], + todayLog: [{ userName: string; logToken: number; model: string }], ) { - const data1 = todayLog.map((log) => { + // 先遍历一遍,获取所有的模型和名字。 + let all_models: StringSet = new Set(); + let all_names: StringSet = new Set(); + // 拼接数据结构 + let data_by_name: StringKeyedObject = {}; + todayLog.map((log) => { + all_models.add(log.model); + all_names.add(log.userName); + data_by_name[log.userName] = data_by_name[log.userName] ?? {}; + data_by_name[log.userName][log.model] = + (data_by_name[log.userName][log.model] || 0) + log.logToken; + // 这么顺利,顺便加个总数吧。 + data_by_name[log.userName]["all_token"] = + (data_by_name[log.userName]["all_token"] || 0) + log.logToken; + }); + // + // 然后遍历并以all_token,排序。 + const userNameList: StringArray = Array.from(all_names).sort((a, b) => { + return data_by_name[a]["all_token"] - data_by_name[b]["all_token"]; + }); + // 将值按模型分为两个序列 + const modelNameList = Array.from(all_models).map((model) => { return { - name: log.userName ?? "unknown", - value: log.logToken, + name: model, + data: userNameList.map((userName) => { + return data_by_name[userName][model] ?? null; + }), }; }); - - type Accumulator = { - [key: string]: number; + console.log("看看", modelNameList); + return { + modelNameList, + userNameList, + data_by_name, }; - 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() { // 今天日期的开始和结束 var today = new Date(); - today = addHours(today, +8); + // today = subMinutes(today, today.getTimezoneOffset()) const startOfTheDayInTimeZone = new Date( today.getFullYear(), today.getMonth(), @@ -65,19 +74,7 @@ export default async function UsageByModel() { 0, 0, ); - // const endOfTheDayInTimeZone = new Date( - // today.getFullYear(), - // today.getMonth(), - // today.getDate(), - // 23, - // 59, - // 59, - // ); // 当天的结束时间 const endOfTheDayInTimeZone = addHours(startOfTheDayInTimeZone, +24); // 当天的结束时间 - - // const startDate = addHours(startOfTheDayInTimeZone, -8); - // const endDate = addHours(endOfTheDayInTimeZone, -8); - console.log("===", today, startOfTheDayInTimeZone, endOfTheDayInTimeZone); const todayLog = await prisma.logEntry.findMany({ where: { createdAt: { @@ -89,13 +86,9 @@ export default async function UsageByModel() { user: true, }, }); - - // console.log("========", todayLog[todayLog.length - 1]); // @ts-ignore const log_data = HandleLogData(todayLog); - console.log("[log_data]====---==", todayLog); - const usageByModelOption: EChartsOption = { tooltip: { trigger: "axis", @@ -122,12 +115,12 @@ export default async function UsageByModel() { yAxis: [ { type: "category", - data: log_data.name, + data: log_data.userNameList, }, ], - series: [ - { - name: "总量", + series: log_data.modelNameList.map((item) => { + return { + ...item, type: "bar", emphasis: { focus: "series", @@ -136,91 +129,107 @@ export default async function UsageByModel() { 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] - // } - ], + colorBy: "series", + stack: "model", + }; + }), + + // [ + // { + // name: "总量", + // type: "bar", + // emphasis: { + // 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 ( <> diff --git a/lib/auth.ts b/lib/auth.ts index de93680dc..5df57af6e 100644 --- a/lib/auth.ts +++ b/lib/auth.ts @@ -252,7 +252,7 @@ function isPinYin(input: string): boolean { export function isName(input: string): boolean { const denyList = [ - "suibian", + "suibian", "某某", "张三", "李四" ] if (denyList.includes(input)) { return false; @@ -300,4 +300,4 @@ function cleanUpString(input: string): string { catch { return ''; } -} \ No newline at end of file +}