diff --git a/public/static/favicon.ico b/public/static/favicon.ico new file mode 100644 index 00000000..c3fe7ef1 Binary files /dev/null and b/public/static/favicon.ico differ diff --git a/src/components/echarts/h-bar.tsx b/src/components/echarts/h-bar.tsx index 1bcf1398..e4a6786c 100644 --- a/src/components/echarts/h-bar.tsx +++ b/src/components/echarts/h-bar.tsx @@ -53,7 +53,7 @@ const BarChart: React.FC = (props) => { axisLabel: { ...yAxis.axisLabel, overflow: 'truncate', - width: 60, + width: 75, ellipsis: '...' } }, diff --git a/src/components/echarts/mix-line-bar.tsx b/src/components/echarts/mix-line-bar.tsx index d0a31b8e..12eb8f3e 100644 --- a/src/components/echarts/mix-line-bar.tsx +++ b/src/components/echarts/mix-line-bar.tsx @@ -101,6 +101,7 @@ const MixLineBarChart: React.FC< stack: 'total', yAxisIndex: 0, itemStyle: { + ...item.itemStyle, color: item.color } }; diff --git a/src/components/logs-viewer/parse-worker.ts b/src/components/logs-viewer/parse-worker.ts index ebe8da02..05a7a22a 100644 --- a/src/components/logs-viewer/parse-worker.ts +++ b/src/components/logs-viewer/parse-worker.ts @@ -30,6 +30,8 @@ class AnsiParser { private percent: number = 0; private isComplete: boolean = false; private chunked: boolean = true; // true: send data in chunks, false: send all data at once + private reminder: string = ''; + private lines: string[] = []; private pageSize: number = 500; private colorMap = { '30': 'black', @@ -52,6 +54,8 @@ class AnsiParser { this.screen = [['']]; this.rawDataRows = 0; this.uid = this.uid + 1; + this.lines = []; + this.reminder = ''; this.page = 1; } @@ -109,7 +113,6 @@ class AnsiParser { const n = parseInt(match[1] || '1', 10); const m = parseInt(match[2] || '1', 10); const command = match[3]; - switch (command) { case 'A': this.cursorRow = Math.max(0, this.cursorRow - n); @@ -183,6 +186,28 @@ class AnsiParser { return result; } + private processInputByLine(input: string): { + data: string[]; + lines: number; + remainder: string; + } { + const lines = input?.split(/\r?\n/) || []; + const remainder = lines.pop() || ''; + + // const data = lines.join('\n'); + this.rawDataRows += lines.length; + this.lines.push(...lines); + return { + data: this.lines, + lines: this.rawDataRows, + remainder + }; + } + + private getAllLines() { + return this.lines.join('\n'); + } + private async processQueue(): Promise { if (this.isProcessing) { return; @@ -191,11 +216,12 @@ class AnsiParser { this.isProcessing = true; while (this.taskQueue.length > 0) { - const input = this.taskQueue.shift(); + const input = this.reminder + this.taskQueue.shift(); if (input) { try { - const result = this.processInput(input); + const result = this.processInputByLine(input); + this.reminder = result.remainder; if (this.chunked) { self.postMessage({ result: result.data, lines: result.lines }); } else if (!this.isComplete) { @@ -220,7 +246,7 @@ class AnsiParser { this.processQueue(); } else if (this.isComplete && !this.chunked) { self.postMessage({ - result: this.getScreenText(), + result: this.getAllLines(), percent: this.percent, isComplete: true }); diff --git a/src/components/logs-viewer/virtual-log-list.tsx b/src/components/logs-viewer/virtual-log-list.tsx index ed2e46d6..5f947828 100644 --- a/src/components/logs-viewer/virtual-log-list.tsx +++ b/src/components/logs-viewer/virtual-log-list.tsx @@ -44,7 +44,7 @@ const LogsViewer: React.FC = forwardRef((props, ref) => { const pageRef = useRef(page); const totalPageRef = useRef(totalPage); const isLoadingMoreRef = useRef(false); - const [currentData, setCurrentData] = useState([]); + const [currentData, setCurrentPageData] = useState([]); const scrollPosRef = useRef({ pos: 'bottom', page: 1 @@ -59,6 +59,21 @@ const LogsViewer: React.FC = forwardRef((props, ref) => { } })); + const removeBracketsFromLine = (row: string) => { + return row.startsWith('(…)') ? row.slice(3) : row; + }; + + const setCurrentData = (lines: string[]) => { + const dataList = lines.map((line, index) => { + return { + content: removeBracketsFromLine(line), + uid: `${pageRef.current}-${index}` + }; + }); + + setCurrentPageData(dataList); + }; + const debounceLoading = _.debounce(() => { setLoading(false); isLoadingMoreRef.current = false; diff --git a/src/pages/dashboard/components/usage-inner/export-data.tsx b/src/pages/dashboard/components/usage-inner/export-data.tsx index e7c996d7..c1147a9c 100644 --- a/src/pages/dashboard/components/usage-inner/export-data.tsx +++ b/src/pages/dashboard/components/usage-inner/export-data.tsx @@ -119,7 +119,7 @@ const ExportData: React.FC<{ init(); } else { setQuery({ - start_date: dayjs().subtract(30, 'days').format('YYYY-MM-DD'), + start_date: dayjs().subtract(29, 'days').format('YYYY-MM-DD'), end_date: dayjs().format('YYYY-MM-DD'), model_ids: [], user_ids: [] diff --git a/src/pages/dashboard/components/usage-inner/index.tsx b/src/pages/dashboard/components/usage-inner/index.tsx index 4bd6be92..edca6cad 100644 --- a/src/pages/dashboard/components/usage-inner/index.tsx +++ b/src/pages/dashboard/components/usage-inner/index.tsx @@ -50,11 +50,19 @@ const UsageInner: FC<{ maxWidth: number }> = ({ maxWidth }) => { const topUserNames = topUsers.map((item: any) => { topUserPrompt.data.push({ name: item.username, - value: item.prompt_token_count + value: item.prompt_token_count, + itemStyle: { + borderRadius: !item.completion_token_count + ? [2, 2, 2, 2] + : [0, 2, 2, 0] + } }); topUserCompletion.data.push({ name: item.username, - value: item.completion_token_count + value: item.completion_token_count, + itemStyle: { + borderRadius: !item.prompt_token_count ? [2, 2, 2, 2] : [2, 0, 0, 2] + } }); return item.username; }); diff --git a/src/pages/dashboard/components/usage-inner/use-usage-data.tsx b/src/pages/dashboard/components/usage-inner/use-usage-data.tsx index c35e5df7..55b58560 100644 --- a/src/pages/dashboard/components/usage-inner/use-usage-data.tsx +++ b/src/pages/dashboard/components/usage-inner/use-usage-data.tsx @@ -85,7 +85,7 @@ const generateValueMap = (list: { timestamp: number; value: number }[]) => { }; const generateData = (dateRage: string[], valueMap: Map) => { - return dateRage.map((date) => { + return dateRage.map((date, index) => { const value = valueMap.get(date) || 0; return { time: date, @@ -201,15 +201,40 @@ export default function useUseageData(config: { }; // =========== token usage data ============== + const completionDataList = generateData( + dateRange, + generateValueMap(completionTokenHistory) + ); + const promptDataList = generateData( + dateRange, + generateValueMap(promptTokenHistory) + ); + const completionData: any = { name: 'Completion tokens', color: baseColorMap.base, - data: generateData(dateRange, generateValueMap(completionTokenHistory)) + data: completionDataList.map((item, index) => { + return { + ...item, + itemStyle: { + borderRadius: !promptDataList[index].value + ? [2, 2, 0, 0] + : [0, 0, 0, 0] + } + }; + }) }; const promptData: any = { name: 'Prompt tokens', color: baseColorMap.baseR3, - data: generateData(dateRange, generateValueMap(promptTokenHistory)) + data: promptDataList.map((item, index) => { + return { + ...item, + itemStyle: { + borderRadius: [2, 2, 0, 0] + } + }; + }) }; return {