perf: usages list;perf: move components (#3654)
* perf: usages list * team sub plan load * perf: usage dashboard code * perf: dashboard ui * perf: move components
This commit is contained in:
@@ -101,7 +101,7 @@ const DateRangePicker = ({
|
||||
date.to = date.from;
|
||||
}
|
||||
setRange(date);
|
||||
onChange && onChange(date);
|
||||
onChange?.(date);
|
||||
}}
|
||||
footer={
|
||||
<Flex justifyContent={'flex-end'}>
|
||||
@@ -116,7 +116,7 @@ const DateRangePicker = ({
|
||||
<Button
|
||||
size={'sm'}
|
||||
onClick={() => {
|
||||
onSuccess && onSuccess(range || defaultDate);
|
||||
onSuccess?.(range || defaultDate);
|
||||
setShowSelected(false);
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -10,29 +10,30 @@ import {
|
||||
MenuList,
|
||||
useDisclosure
|
||||
} from '@chakra-ui/react';
|
||||
import React, { useMemo, useRef } from 'react';
|
||||
import React, { useCallback, useMemo, useRef, useState } from 'react';
|
||||
import MyTag from '../Tag/index';
|
||||
import MyIcon from '../Icon';
|
||||
import MyAvatar from '../Avatar';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useScrollPagination } from '../../../hooks/useScrollPagination';
|
||||
import MyDivider from '../MyDivider';
|
||||
|
||||
export type SelectProps<T = any> = {
|
||||
value: T[];
|
||||
placeholder?: string;
|
||||
list: {
|
||||
icon?: string;
|
||||
label: string | React.ReactNode;
|
||||
value: T;
|
||||
}[];
|
||||
value: T[];
|
||||
isSelectAll: boolean;
|
||||
setIsSelectAll: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
|
||||
placeholder?: string;
|
||||
maxH?: number;
|
||||
itemWrap?: boolean;
|
||||
onSelect: (val: T[]) => void;
|
||||
closeable?: boolean;
|
||||
showCheckedIcon?: boolean;
|
||||
ScrollData?: ReturnType<typeof useScrollPagination>['ScrollData'];
|
||||
isSelectAll?: boolean;
|
||||
setIsSelectAll?: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
} & Omit<ButtonProps, 'onSelect'>;
|
||||
|
||||
const MultipleSelect = <T = any,>({
|
||||
@@ -42,7 +43,6 @@ const MultipleSelect = <T = any,>({
|
||||
maxH = 400,
|
||||
onSelect,
|
||||
closeable = false,
|
||||
showCheckedIcon = true,
|
||||
itemWrap = true,
|
||||
ScrollData,
|
||||
isSelectAll,
|
||||
@@ -65,79 +65,66 @@ const MultipleSelect = <T = any,>({
|
||||
}
|
||||
};
|
||||
|
||||
const onclickItem = (val: T) => {
|
||||
if (value.includes(val)) {
|
||||
onSelect(value.filter((i) => i !== val));
|
||||
} else {
|
||||
onSelect([...value, val]);
|
||||
}
|
||||
};
|
||||
const onclickItem = useCallback(
|
||||
(val: T) => {
|
||||
// 全选状态下,value 实际上上空。
|
||||
if (isSelectAll) {
|
||||
onSelect(list.map((item) => item.value).filter((i) => i !== val));
|
||||
setIsSelectAll(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const onSelectAll = () => {
|
||||
if (!setIsSelectAll) {
|
||||
onSelect(value.length === list.length ? [] : list.map((item) => item.value));
|
||||
return;
|
||||
}
|
||||
if (value.includes(val)) {
|
||||
onSelect(value.filter((i) => i !== val));
|
||||
} else {
|
||||
onSelect([...value, val]);
|
||||
}
|
||||
},
|
||||
[value, isSelectAll, onSelect, setIsSelectAll]
|
||||
);
|
||||
|
||||
const onSelectAll = useCallback(() => {
|
||||
const hasSelected = isSelectAll || value.length > 0;
|
||||
onSelect(hasSelected ? [] : list.map((item) => item.value));
|
||||
|
||||
if (isSelectAll) {
|
||||
onSelect([]);
|
||||
}
|
||||
setIsSelectAll((state) => !state);
|
||||
};
|
||||
}, [value, list, setIsSelectAll, onSelect]);
|
||||
|
||||
const ListRender = useMemo(() => {
|
||||
return (
|
||||
<>
|
||||
{list.map((item, i) => (
|
||||
<MenuItem
|
||||
key={i}
|
||||
{...menuItemStyles}
|
||||
{...((isSelectAll && !value.includes(item.value)) ||
|
||||
(!isSelectAll && value.includes(item.value))
|
||||
? {
|
||||
color: 'primary.600'
|
||||
}
|
||||
: {
|
||||
color: 'myGray.900'
|
||||
})}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
onclickItem(item.value);
|
||||
}}
|
||||
whiteSpace={'pre-wrap'}
|
||||
fontSize={'sm'}
|
||||
gap={2}
|
||||
>
|
||||
{!showCheckedIcon && (
|
||||
<Checkbox
|
||||
isChecked={
|
||||
(isSelectAll && !value.includes(item.value)) ||
|
||||
(!isSelectAll && value.includes(item.value))
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{item.icon && <MyAvatar src={item.icon} w={'1rem'} borderRadius={'0'} />}
|
||||
<Box flex={'1 0 0'}>{item.label}</Box>
|
||||
{showCheckedIcon && (
|
||||
<Box w={'0.8rem'} lineHeight={1}>
|
||||
{(isSelectAll && !value.includes(item.value)) ||
|
||||
(!isSelectAll && value.includes(item.value) && (
|
||||
<MyIcon name={'price/right'} w={'1rem'} />
|
||||
))}
|
||||
</Box>
|
||||
)}
|
||||
</MenuItem>
|
||||
))}
|
||||
{list.map((item, i) => {
|
||||
const isSelected = isSelectAll || value.includes(item.value);
|
||||
return (
|
||||
<MenuItem
|
||||
key={i}
|
||||
{...menuItemStyles}
|
||||
{...(isSelected
|
||||
? {
|
||||
color: 'primary.600'
|
||||
}
|
||||
: {
|
||||
color: 'myGray.900'
|
||||
})}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
onclickItem(item.value);
|
||||
}}
|
||||
whiteSpace={'pre-wrap'}
|
||||
fontSize={'sm'}
|
||||
gap={2}
|
||||
>
|
||||
<Checkbox isChecked={isSelected} />
|
||||
{item.icon && <MyAvatar src={item.icon} w={'1rem'} borderRadius={'0'} />}
|
||||
<Box flex={'1 0 0'}>{item.label}</Box>
|
||||
</MenuItem>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
}, [value, list, isSelectAll]);
|
||||
|
||||
const isAllSelected = useMemo(
|
||||
() => (isSelectAll && value.length === 0) || (!isSelectAll && value.length === list.length),
|
||||
[isSelectAll, value, list]
|
||||
);
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Menu
|
||||
@@ -186,44 +173,43 @@ const MultipleSelect = <T = any,>({
|
||||
overflow={'hidden'}
|
||||
flex={1}
|
||||
>
|
||||
{isAllSelected ? (
|
||||
{isSelectAll ? (
|
||||
<Box fontSize={'mini'} color={'myGray.900'}>
|
||||
{t('common:common.All')}
|
||||
</Box>
|
||||
) : (
|
||||
(isSelectAll
|
||||
? list.filter((item) => !value.includes(item.value))
|
||||
: list.filter((item) => value.includes(item.value))
|
||||
).map((item, i) => (
|
||||
<MyTag
|
||||
className="tag-icon"
|
||||
key={i}
|
||||
bg={'primary.100'}
|
||||
color={'primary.700'}
|
||||
type={'fill'}
|
||||
borderRadius={'full'}
|
||||
px={2}
|
||||
py={0.5}
|
||||
flexShrink={0}
|
||||
>
|
||||
{item.label}
|
||||
{closeable && (
|
||||
<MyIcon
|
||||
name={'common/closeLight'}
|
||||
ml={1}
|
||||
w="0.8rem"
|
||||
cursor={'pointer'}
|
||||
_hover={{
|
||||
color: 'red.500'
|
||||
}}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onclickItem(item.value);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</MyTag>
|
||||
))
|
||||
list
|
||||
.filter((item) => value.includes(item.value))
|
||||
.map((item, i) => (
|
||||
<MyTag
|
||||
className="tag-icon"
|
||||
key={i}
|
||||
bg={'primary.100'}
|
||||
color={'primary.700'}
|
||||
type={'fill'}
|
||||
borderRadius={'full'}
|
||||
px={2}
|
||||
py={0.5}
|
||||
flexShrink={0}
|
||||
>
|
||||
{item.label}
|
||||
{closeable && (
|
||||
<MyIcon
|
||||
name={'common/closeLight'}
|
||||
ml={1}
|
||||
w="0.8rem"
|
||||
cursor={'pointer'}
|
||||
_hover={{
|
||||
color: 'red.500'
|
||||
}}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onclickItem(item.value);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</MyTag>
|
||||
))
|
||||
)}
|
||||
</Flex>
|
||||
<MyIcon name={'core/chat/chevronDown'} color={'myGray.600'} w={4} h={4} />
|
||||
@@ -245,7 +231,7 @@ const MultipleSelect = <T = any,>({
|
||||
>
|
||||
<MenuItem
|
||||
{...menuItemStyles}
|
||||
color={isAllSelected ? 'primary.600' : 'myGray.900'}
|
||||
color={isSelectAll ? 'primary.600' : 'myGray.900'}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
@@ -256,15 +242,12 @@ const MultipleSelect = <T = any,>({
|
||||
gap={2}
|
||||
mb={1}
|
||||
>
|
||||
{!showCheckedIcon && <Checkbox isChecked={isAllSelected} />}
|
||||
<Checkbox isChecked={isSelectAll} />
|
||||
<Box flex={'1 0 0'}>{t('common:common.All')}</Box>
|
||||
{showCheckedIcon && (
|
||||
<Box w={'0.8rem'} lineHeight={1}>
|
||||
{isAllSelected && <MyIcon name={'price/right'} w={'1rem'} />}
|
||||
</Box>
|
||||
)}
|
||||
</MenuItem>
|
||||
|
||||
<MyDivider my={1} />
|
||||
|
||||
{ScrollData ? <ScrollData>{ListRender}</ScrollData> : ListRender}
|
||||
</MenuList>
|
||||
</Menu>
|
||||
@@ -273,3 +256,9 @@ const MultipleSelect = <T = any,>({
|
||||
};
|
||||
|
||||
export default MultipleSelect;
|
||||
|
||||
export const useMultipleSelect = <T = any,>(defaultValue: T[] = [], defaultSelectAll = false) => {
|
||||
const [value, setValue] = useState<T[]>(defaultValue);
|
||||
const [isSelectAll, setIsSelectAll] = useState<boolean>(defaultSelectAll);
|
||||
return { value, setValue, isSelectAll, setIsSelectAll };
|
||||
};
|
||||
|
||||
24
packages/web/components/common/String/CopyBox.tsx
Normal file
24
packages/web/components/common/String/CopyBox.tsx
Normal file
@@ -0,0 +1,24 @@
|
||||
import { useCopyData } from '../../../hooks/useCopyData';
|
||||
import React from 'react';
|
||||
import MyTooltip from '../MyTooltip';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { Box, BoxProps } from '@chakra-ui/react';
|
||||
|
||||
const CopyBox = ({
|
||||
value,
|
||||
children,
|
||||
...props
|
||||
}: { value: string; children: React.ReactNode } & BoxProps) => {
|
||||
const { copyData } = useCopyData();
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<MyTooltip label={t('common:click_to_copy')}>
|
||||
<Box cursor={'pointer'} onClick={() => copyData(value)} {...props}>
|
||||
{children}
|
||||
</Box>
|
||||
</MyTooltip>
|
||||
);
|
||||
};
|
||||
|
||||
export default CopyBox;
|
||||
64
packages/web/hooks/useCopyData.tsx
Normal file
64
packages/web/hooks/useCopyData.tsx
Normal file
@@ -0,0 +1,64 @@
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useToast } from './useToast';
|
||||
import { useCallback } from 'react';
|
||||
import { hasHttps } from '../common/system/utils';
|
||||
import { isProduction } from '@fastgpt/global/common/system/constants';
|
||||
|
||||
/**
|
||||
* copy text data
|
||||
*/
|
||||
export const useCopyData = () => {
|
||||
const { t } = useTranslation();
|
||||
const { toast } = useToast();
|
||||
|
||||
const copyData = useCallback(
|
||||
async (
|
||||
data: string,
|
||||
title: string | null = t('common:common.Copy Successful'),
|
||||
duration = 1000
|
||||
) => {
|
||||
data = data.trim();
|
||||
|
||||
try {
|
||||
if ((hasHttps() || !isProduction) && navigator.clipboard) {
|
||||
await navigator.clipboard.writeText(data);
|
||||
} else {
|
||||
throw new Error('');
|
||||
}
|
||||
} catch (error) {
|
||||
// console.log(error);
|
||||
|
||||
const textarea = document.createElement('textarea');
|
||||
textarea.value = data;
|
||||
textarea.style.position = 'absolute';
|
||||
textarea.style.opacity = '0';
|
||||
document.body.appendChild(textarea);
|
||||
|
||||
textarea.select();
|
||||
const res = document.execCommand('copy');
|
||||
document.body.removeChild(textarea);
|
||||
|
||||
if (!res) {
|
||||
return toast({
|
||||
title: t('common:common.Copy_failed'),
|
||||
status: 'error',
|
||||
duration
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (title) {
|
||||
toast({
|
||||
title,
|
||||
status: 'success',
|
||||
duration
|
||||
});
|
||||
}
|
||||
},
|
||||
[t, toast]
|
||||
);
|
||||
|
||||
return {
|
||||
copyData
|
||||
};
|
||||
};
|
||||
@@ -9,7 +9,10 @@
|
||||
"details": "Details",
|
||||
"dingtalk": "DingTalk",
|
||||
"duration_seconds": "Duration (seconds)",
|
||||
"every_day": "Day",
|
||||
"every_month": "Moon",
|
||||
"export_confirm": "Export confirmation",
|
||||
"export_confirm_tip": "There are currently {{total}} usage records in total. Are you sure to export?",
|
||||
"feishu": "Feishu",
|
||||
"generation_time": "Generation time",
|
||||
"input_token_length": "input tokens",
|
||||
@@ -26,7 +29,6 @@
|
||||
"select_member_and_source_first": "Please select members and types first",
|
||||
"share": "Share Link",
|
||||
"source": "source",
|
||||
"start_export": "Export started",
|
||||
"text_length": "text length",
|
||||
"token_length": "token length",
|
||||
"total_points": "AI points consumption",
|
||||
|
||||
@@ -37,9 +37,12 @@
|
||||
"chose_condition": "Choose Condition",
|
||||
"chosen": "Chosen",
|
||||
"classification": "Classification",
|
||||
"click_to_copy": "Click to copy",
|
||||
"click_to_resume": "Click to Resume",
|
||||
"code_editor": "Code Editor",
|
||||
"code_error.account_error": "Incorrect account name or password",
|
||||
"code_error.account_exist": "Account has been registered",
|
||||
"code_error.account_not_found": "User is not registered",
|
||||
"code_error.app_error.invalid_app_type": "Invalid Application Type",
|
||||
"code_error.app_error.invalid_owner": "Unauthorized Application Owner",
|
||||
"code_error.app_error.not_exist": "Application Does Not Exist",
|
||||
@@ -932,14 +935,14 @@
|
||||
"model_doubao": "Doubao",
|
||||
"model_ernie": "Ernie",
|
||||
"model_hunyuan": "Hunyuan",
|
||||
"model_intern": "Intern",
|
||||
"model_moka": "Moka-AI",
|
||||
"model_moonshot": "Moonshot",
|
||||
"model_other": "Other",
|
||||
"model_qwen": "Qwen",
|
||||
"model_sparkdesk": "SprkDesk",
|
||||
"model_stepfun": "StepFun",
|
||||
"model_yi": "Yi",
|
||||
"model_intern": "Intern",
|
||||
"model_moka": "Moka-AI",
|
||||
"move.confirm": "Confirm move",
|
||||
"navbar.Account": "Account",
|
||||
"navbar.Chat": "Chat",
|
||||
|
||||
@@ -9,10 +9,11 @@
|
||||
"details": "详情",
|
||||
"dingtalk": "钉钉",
|
||||
"duration_seconds": "时长(秒)",
|
||||
"every_day": "每天",
|
||||
"every_month": "每月",
|
||||
"every_day": "天",
|
||||
"every_month": "月",
|
||||
"every_week": "每周",
|
||||
"export_confirm": "导出确认",
|
||||
"export_confirm_tip": "当前共 {{total}} 条使用记录,确认导出?",
|
||||
"export_success": "导出成功",
|
||||
"feishu": "飞书",
|
||||
"generation_time": "生成时间",
|
||||
@@ -30,7 +31,6 @@
|
||||
"select_member_and_source_first": "请先选中成员和类型",
|
||||
"share": "分享链接",
|
||||
"source": "来源",
|
||||
"start_export": "已开始导出",
|
||||
"text_length": "文本长度",
|
||||
"token_length": "token 长度",
|
||||
"total_points": "AI 积分消耗",
|
||||
|
||||
@@ -41,9 +41,12 @@
|
||||
"chose_condition": "选择条件",
|
||||
"chosen": "已选",
|
||||
"classification": "分类",
|
||||
"click_to_copy": "点击复制",
|
||||
"click_to_resume": "点击恢复",
|
||||
"code_editor": "代码编辑",
|
||||
"code_error.account_error": "账号名或密码错误",
|
||||
"code_error.account_exist": "账号已注册",
|
||||
"code_error.account_not_found": "用户未注册",
|
||||
"code_error.app_error.invalid_app_type": "错误的应用类型",
|
||||
"code_error.app_error.invalid_owner": "非法的应用所有者",
|
||||
"code_error.app_error.not_exist": "应用不存在",
|
||||
@@ -935,14 +938,14 @@
|
||||
"model_doubao": "豆包",
|
||||
"model_ernie": "文心一言",
|
||||
"model_hunyuan": "腾讯混元",
|
||||
"model_intern": "书生",
|
||||
"model_moka": "Moka-AI",
|
||||
"model_moonshot": "月之暗面",
|
||||
"model_other": "其他",
|
||||
"model_qwen": "阿里千问",
|
||||
"model_sparkdesk": "讯飞星火",
|
||||
"model_stepfun": "阶跃星辰",
|
||||
"model_yi": "零一万物",
|
||||
"model_intern": "书生",
|
||||
"model_moka": "Moka-AI",
|
||||
"move.confirm": "确认移动",
|
||||
"navbar.Account": "账号",
|
||||
"navbar.Chat": "聊天",
|
||||
|
||||
@@ -9,7 +9,10 @@
|
||||
"details": "詳情",
|
||||
"dingtalk": "釘釘",
|
||||
"duration_seconds": "時長(秒)",
|
||||
"every_day": "天",
|
||||
"every_month": "月",
|
||||
"export_confirm": "導出確認",
|
||||
"export_confirm_tip": "當前共 {{total}} 筆使用記錄,確認導出?",
|
||||
"feishu": "飛書",
|
||||
"generation_time": "生成時間",
|
||||
"input_token_length": "輸入 tokens",
|
||||
@@ -26,7 +29,6 @@
|
||||
"select_member_and_source_first": "請先選取成員和類型",
|
||||
"share": "分享連結",
|
||||
"source": "來源",
|
||||
"start_export": "已開始匯出",
|
||||
"text_length": "文字長度",
|
||||
"token_length": "token 長度",
|
||||
"total_points": "AI 積分消耗",
|
||||
|
||||
@@ -37,9 +37,11 @@
|
||||
"chose_condition": "選擇條件",
|
||||
"chosen": "已選擇",
|
||||
"classification": "分類",
|
||||
"click_to_copy": "點選複製",
|
||||
"click_to_resume": "點選繼續",
|
||||
"code_editor": "程式碼編輯器",
|
||||
"code_error.account_error": "帳號名稱或密碼錯誤",
|
||||
"code_error.account_not_found": "用戶未註冊",
|
||||
"code_error.app_error.invalid_app_type": "無效的應用程式類型",
|
||||
"code_error.app_error.invalid_owner": "非法的應用程式擁有者",
|
||||
"code_error.app_error.not_exist": "應用程式不存在",
|
||||
@@ -932,14 +934,14 @@
|
||||
"model_doubao": "豆包",
|
||||
"model_ernie": "文心一言",
|
||||
"model_hunyuan": "騰訊混元",
|
||||
"model_intern": "書生",
|
||||
"model_moka": "Moka-AI",
|
||||
"model_moonshot": "月之暗面",
|
||||
"model_other": "其他",
|
||||
"model_qwen": "阿里千問",
|
||||
"model_sparkdesk": "訊飛星火",
|
||||
"model_stepfun": "階躍星辰",
|
||||
"model_yi": "零一萬物",
|
||||
"model_intern": "書生",
|
||||
"model_moka": "Moka-AI",
|
||||
"move.confirm": "確認移動",
|
||||
"navbar.Account": "帳戶",
|
||||
"navbar.Chat": "對話",
|
||||
|
||||
Reference in New Issue
Block a user