mirror of
https://github.com/labring/FastGPT.git
synced 2025-10-18 17:51:24 +00:00

* add logs chart (#5352) * charts * chart data * log chart * delete * rename api * fix * move api * fix * fix * pro config * fix * feat: Repository interaction (#5356) * feat: 1好像功能没问题了,明天再测 * feat: 2 解决了昨天遗留的bug,但全选按钮又bug了 * feat: 3 第三版,解决了全选功能bug * feat: 4 第四版,下面改小细节 * feat: 5 我勒个痘 * feat: 6 * feat: 6 pr * feat: 7 * feat: 8 * feat: 9 * feat: 10 * feat: 11 * feat: 12 * perf: checkbox ui * refactor: tweak login loyout (#5357) Co-authored-by: Archer <545436317@qq.com> * login ui * app chat log chart pro display (#5392) * app chat log chart pro display * add canopen props * perf: pro tag tip * perf: pro tag tip * feat: openrouter provider (#5406) * perf: login ui * feat: openrouter provider * provider * perf: custom error throw * perf: emb batch (#5407) * perf: emb batch * perf: vector retry * doc * doc (#5411) * doc * fix: team folder will add to workflow * fix: generateToc shell * Tool price (#5376) * resolve conflicts for cherry-pick * fix i18n * Enhance system plugin template data structure and update ToolSelectModal to include CostTooltip component * refactor: update systemKeyCost type to support array of objects in plugin and workflow types * refactor: simplify systemKeyCost type across plugin and workflow types to a single number * refactor: streamline systemKeyCost handling in plugin and workflow components * fix * fix * perf: toolset price config;fix: workflow array selector ui (#5419) * fix: workflow array selector ui * update default model tip * perf: toolset price config * doc * fix: test * Refactor/chat (#5418) * refactor: add homepage configuration; add home chat page; add side bar animated collapse and layout * fix: fix lint rules * chore: improve logics and code * chore: more clearer logics * chore: adjust api --------- Co-authored-by: Archer <545436317@qq.com> * perf: chat setting code * del history * logo image * perf: home chat ui * feat: enhance chat response handling with external links and user info (#5427) * feat: enhance chat response handling with external links and user info * fix * cite code * perf: toolset add in workflow * fix: test * fix: search paraentId * Fix/chat (#5434) * wip: rebase了upstream * wip: adapt mobile UI * fix: fix chat page logic and UI * fix: fix UI and improve some logics * fix: model selector missing logo; vision model to retrieve file * perf: role selector * fix: chat ui * optimize export app chat log (#5436) * doc * chore: move components to proper directory; fix the api to get app list (#5437) * chore: improve team app panel display form (#5438) * feat: add home chat log tab * chore: improve team app panel display form * chore: improve log panel * fix: spec * doc * fix: log permission * fix: dataset schema required * add loading status * remove ui weight * manage log * fix: log detail per * doc * fix: log menu * rename permission * bg color * fix: app log per * fix: log key selector * fix: log * doc --------- Co-authored-by: heheer <zhiyu44@qq.com> Co-authored-by: colnii <1286949794@qq.com> Co-authored-by: 伍闲犬 <76519998+xqvvu@users.noreply.github.com> Co-authored-by: Ctrlz <143257420+ctrlz526@users.noreply.github.com> Co-authored-by: 伍闲犬 <whoeverimf5@gmail.com> Co-authored-by: heheer <heheer@sealos.io>
233 lines
6.5 KiB
TypeScript
233 lines
6.5 KiB
TypeScript
import React, { useCallback, useMemo } from 'react';
|
|
import { Box, Flex, HStack, useTheme } from '@chakra-ui/react';
|
|
import {
|
|
ResponsiveContainer,
|
|
XAxis,
|
|
YAxis,
|
|
CartesianGrid,
|
|
Tooltip,
|
|
type TooltipProps,
|
|
LineChart,
|
|
Line,
|
|
ReferenceLine
|
|
} from 'recharts';
|
|
import { type NameType, type ValueType } from 'recharts/types/component/DefaultTooltipContent';
|
|
import { formatNumber } from '@fastgpt/global/common/math/tools';
|
|
import { useTranslation } from 'next-i18next';
|
|
import QuestionTip from '../MyTooltip/QuestionTip';
|
|
|
|
type LineConfig = {
|
|
dataKey: string;
|
|
name: string;
|
|
color: string;
|
|
gradient?: boolean;
|
|
};
|
|
|
|
type TooltipItem = {
|
|
label: string;
|
|
dataKey: string;
|
|
color: string;
|
|
formatter?: (value: number) => string;
|
|
customValue?: (data: Record<string, any>) => number;
|
|
};
|
|
|
|
type LineChartComponentProps = {
|
|
data: Record<string, any>[];
|
|
title: string;
|
|
description?: string;
|
|
HeaderRightChildren?: React.ReactNode;
|
|
lines: LineConfig[];
|
|
tooltipItems?: TooltipItem[];
|
|
showAverage?: boolean;
|
|
averageKey?: string;
|
|
blur?: boolean;
|
|
};
|
|
|
|
const CustomTooltip = ({
|
|
active,
|
|
payload,
|
|
tooltipItems
|
|
}: TooltipProps<ValueType, NameType> & { tooltipItems?: TooltipItem[] }) => {
|
|
const data = payload?.[0]?.payload;
|
|
|
|
if (!active || !data || !tooltipItems) {
|
|
return null;
|
|
}
|
|
|
|
return (
|
|
<Box
|
|
bg="white"
|
|
p={3}
|
|
borderRadius="md"
|
|
border="base"
|
|
boxShadow="sm"
|
|
{...(tooltipItems.length > 8 ? { position: 'relative', top: '-30px' } : {})}
|
|
>
|
|
<Box fontSize="sm" color="myGray.900" mb={tooltipItems.length > 5 ? 1 : 2}>
|
|
{data.xLabel || data.x}
|
|
</Box>
|
|
{tooltipItems.map((item, index) => {
|
|
const value = item.customValue ? item.customValue(data) : data[item.dataKey];
|
|
const displayValue = item.formatter ? item.formatter(value) : formatNumber(value);
|
|
|
|
return (
|
|
<HStack key={index} fontSize="sm" _notLast={{ mb: tooltipItems.length > 5 ? 0 : 1 }}>
|
|
<Box w={2} h={2} borderRadius="full" bg={item.color} />
|
|
<Box>{item.label}</Box>
|
|
<Box>{displayValue.toLocaleString()}</Box>
|
|
</HStack>
|
|
);
|
|
})}
|
|
</Box>
|
|
);
|
|
};
|
|
|
|
const LineChartComponent = ({
|
|
data,
|
|
title,
|
|
description,
|
|
HeaderRightChildren,
|
|
lines,
|
|
tooltipItems,
|
|
showAverage = false,
|
|
averageKey,
|
|
blur = false
|
|
}: LineChartComponentProps) => {
|
|
const theme = useTheme();
|
|
|
|
// Y-axis number formatter function
|
|
const formatYAxisNumber = useCallback((value: number): string => {
|
|
if (value >= 1000000) {
|
|
return value / 1000000 + 'M';
|
|
} else if (value >= 1000) {
|
|
return value / 1000 + 'K';
|
|
}
|
|
return value.toString();
|
|
}, []);
|
|
|
|
// Calculate average value
|
|
const averageValue = useMemo(() => {
|
|
if (!showAverage || !averageKey || data.length === 0) return null;
|
|
|
|
const sum = data.reduce((acc, item) => acc + (item[averageKey] || 0), 0);
|
|
return sum / data.length;
|
|
}, [showAverage, averageKey, data]);
|
|
|
|
// Generate gradient definitions
|
|
const gradientDefs = useMemo(
|
|
() => (
|
|
<defs>
|
|
{lines.map((line) => (
|
|
<linearGradient
|
|
key={`gradient-${line.color}`}
|
|
id={`gradient-${line.color}`}
|
|
x1="0"
|
|
y1="0"
|
|
x2="0"
|
|
y2="1"
|
|
>
|
|
<stop offset="0%" stopColor={line.color} stopOpacity={0.25} />
|
|
<stop offset="100%" stopColor={line.color} stopOpacity={0.01} />
|
|
</linearGradient>
|
|
))}
|
|
</defs>
|
|
),
|
|
[lines]
|
|
);
|
|
|
|
return (
|
|
<Box
|
|
onMouseEnter={(e) => {
|
|
const chartElement = e.currentTarget.querySelector('.recharts-wrapper');
|
|
if (chartElement && showAverage && averageValue !== null) {
|
|
chartElement.classList.add('show-average');
|
|
}
|
|
}}
|
|
onMouseLeave={(e) => {
|
|
const chartElement = e.currentTarget.querySelector('.recharts-wrapper');
|
|
if (chartElement) {
|
|
chartElement.classList.remove('show-average');
|
|
}
|
|
}}
|
|
h="100%"
|
|
>
|
|
<style jsx global>{`
|
|
.recharts-wrapper .average-line {
|
|
opacity: 0;
|
|
transition: opacity 0.2s ease-in-out;
|
|
}
|
|
.recharts-wrapper.show-average .average-line {
|
|
opacity: 1;
|
|
}
|
|
`}</style>
|
|
<Flex mb={4} h={6}>
|
|
<Flex flex={1} alignItems={'center'} gap={1}>
|
|
<Box fontSize={'sm'} color={'myGray.900'} fontWeight={'medium'}>
|
|
{title}
|
|
</Box>
|
|
<QuestionTip label={description} />
|
|
</Flex>
|
|
<Box filter={blur ? 'blur(7.5px)' : 'none'} pointerEvents={blur ? 'none' : 'auto'}>
|
|
{HeaderRightChildren}
|
|
</Box>
|
|
</Flex>
|
|
<ResponsiveContainer
|
|
width="100%"
|
|
height={'100%'}
|
|
style={{ filter: blur ? 'blur(7.5px)' : 'none' }}
|
|
>
|
|
<LineChart
|
|
data={data}
|
|
margin={{ top: 5, right: 30, left: 0, bottom: HeaderRightChildren ? 20 : 15 }}
|
|
>
|
|
{gradientDefs}
|
|
<XAxis
|
|
dataKey="x"
|
|
tickMargin={10}
|
|
tick={{ fontSize: '12px', color: theme?.colors?.myGray['500'], fontWeight: '500' }}
|
|
interval="preserveStartEnd"
|
|
/>
|
|
<YAxis
|
|
axisLine={false}
|
|
tickSize={0}
|
|
tickMargin={10}
|
|
tick={{ fontSize: '12px', color: theme?.colors?.myGray['500'], fontWeight: '500' }}
|
|
interval="preserveStartEnd"
|
|
tickFormatter={formatYAxisNumber}
|
|
/>
|
|
<CartesianGrid strokeDasharray="3 3" horizontal={true} vertical={false} />
|
|
{tooltipItems && <Tooltip content={<CustomTooltip tooltipItems={tooltipItems} />} />}
|
|
{lines.map((line, index) => (
|
|
<Line
|
|
key={line.dataKey}
|
|
name={line.name}
|
|
dataKey={line.dataKey}
|
|
stroke={line.color}
|
|
strokeWidth={2}
|
|
fill={`url(#gradient-${line.color})`}
|
|
dot={false}
|
|
/>
|
|
))}
|
|
{showAverage && averageValue !== null && (
|
|
<ReferenceLine
|
|
y={averageValue}
|
|
stroke={theme.colors.primary?.['400']}
|
|
strokeDasharray="5 5"
|
|
strokeWidth={1}
|
|
className="average-line"
|
|
label={{
|
|
value: `${formatNumber(averageValue)}`,
|
|
position: 'insideTopRight',
|
|
fill: theme.colors.primary?.['400'],
|
|
fontSize: 12
|
|
}}
|
|
/>
|
|
)}
|
|
</LineChart>
|
|
</ResponsiveContainer>
|
|
</Box>
|
|
);
|
|
};
|
|
|
|
export default LineChartComponent;
|