chore(aiproxy-froent): adjust code format style (#6088)

* chore format

* chore
This commit is contained in:
limbo
2025-10-14 20:05:01 +08:00
committed by GitHub
parent a8d299659a
commit 739972f3a8
59 changed files with 2042 additions and 2020 deletions
+2 -1
View File
@@ -28,4 +28,5 @@ pnpm-lock.yaml
public/
.git/
.vscode/
.idea/
.idea/
deploy/
@@ -11,6 +11,18 @@
"jsxBracketSameLine": false,
"embeddedLanguageFormatting": "auto",
"overrides": [
{
"files": "*.tsx",
"options": {
"semi": true
}
},
{
"files": "*.jsx",
"options": {
"semi": true
}
},
{
"files": ["*.json", "*.jsonc"],
"options": {
@@ -1,5 +1,5 @@
'use client'
import { useEffect, useMemo, useState } from 'react'
'use client';
import { useEffect, useMemo, useState } from 'react';
import {
Box,
Checkbox,
@@ -18,51 +18,51 @@ import {
Thead,
Tr,
useDisclosure,
} from '@chakra-ui/react'
import { useMessage } from '@sealos/ui'
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
} from '@chakra-ui/react';
import { useMessage } from '@sealos/ui';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import {
Column,
createColumnHelper,
flexRender,
getCoreRowModel,
useReactTable,
} from '@tanstack/react-table'
} from '@tanstack/react-table';
import {
deleteChannel,
getChannels,
getChannelTypeNames,
updateChannelStatus,
} from '@/api/platform'
import { useTranslationClientSide } from '@/app/i18n/client'
import SwitchPage from '@/components/common/SwitchPage'
import { useI18n } from '@/providers/i18n/i18nContext'
import { ChannelInfo, ChannelStatus, ChannelType } from '@/types/admin/channels/channelInfo'
import { QueryKey } from '@/types/query-key'
import { downloadJson } from '@/utils/common'
} from '@/api/platform';
import { useTranslationClientSide } from '@/app/i18n/client';
import SwitchPage from '@/components/common/SwitchPage';
import { useI18n } from '@/providers/i18n/i18nContext';
import { ChannelInfo, ChannelStatus, ChannelType } from '@/types/admin/channels/channelInfo';
import { QueryKey } from '@/types/query-key';
import { downloadJson } from '@/utils/common';
import UpdateChannelModal from './UpdateChannelModal'
import UpdateChannelModal from './UpdateChannelModal';
export default function ChannelTable({
exportData,
}: {
exportData: (data: ChannelInfo[]) => void
exportData: (data: ChannelInfo[]) => void;
}) {
const { isOpen, onOpen, onClose } = useDisclosure()
const { lng } = useI18n()
const { t } = useTranslationClientSide(lng, 'common')
const [operationType, setOperationType] = useState<'create' | 'update'>('update')
const [channelInfo, setChannelInfo] = useState<ChannelInfo | undefined>(undefined)
const [total, setTotal] = useState(0)
const [page, setPage] = useState(1)
const [pageSize, setPageSize] = useState(10)
const { isOpen, onOpen, onClose } = useDisclosure();
const { lng } = useI18n();
const { t } = useTranslationClientSide(lng, 'common');
const [operationType, setOperationType] = useState<'create' | 'update'>('update');
const [channelInfo, setChannelInfo] = useState<ChannelInfo | undefined>(undefined);
const [total, setTotal] = useState(0);
const [page, setPage] = useState(1);
const [pageSize, setPageSize] = useState(10);
const [selectedChannels, setSelectedChannels] = useState<ChannelInfo[]>([])
const [selectedChannels, setSelectedChannels] = useState<ChannelInfo[]>([]);
useEffect(() => {
exportData(selectedChannels)
}, [selectedChannels])
exportData(selectedChannels);
}, [selectedChannels]);
const { message } = useMessage({
warningBoxBg: '#FFFAEB',
@@ -71,23 +71,23 @@ export default function ChannelTable({
successBoxBg: '#EDFBF3',
successIconBg: '#039855',
successIconFill: 'white',
})
});
const queryClient = useQueryClient()
const queryClient = useQueryClient();
const { isLoading: isChannelTypeNamesLoading, data: channelTypeNames } = useQuery({
queryKey: [QueryKey.GetChannelTypeNames],
queryFn: () => getChannelTypeNames(),
})
});
const { data, isLoading: isChannelsLoading } = useQuery({
queryKey: [QueryKey.GetChannels, page, pageSize],
queryFn: () => getChannels({ page, perPage: pageSize }),
refetchOnReconnect: true,
onSuccess(data) {
setTotal(data?.total || 0)
setTotal(data?.total || 0);
},
})
});
const updateChannelStatusMutation = useMutation(
({ id, status }: { id: string; status: number }) => updateChannelStatus(id, status),
@@ -99,9 +99,9 @@ export default function ChannelTable({
isClosable: true,
duration: 2000,
position: 'top',
})
queryClient.invalidateQueries([QueryKey.GetChannels])
queryClient.invalidateQueries([QueryKey.GetChannelTypeNames])
});
queryClient.invalidateQueries([QueryKey.GetChannels]);
queryClient.invalidateQueries([QueryKey.GetChannelTypeNames]);
},
onError(err: any) {
message({
@@ -110,10 +110,10 @@ export default function ChannelTable({
description: err?.message || t('channel.updateFailed'),
isClosable: true,
position: 'top',
})
});
},
}
)
);
const deleteChannelMutation = useMutation(({ id }: { id: string }) => deleteChannel(id), {
onSuccess() {
message({
@@ -122,9 +122,9 @@ export default function ChannelTable({
isClosable: true,
duration: 2000,
position: 'top',
})
queryClient.invalidateQueries([QueryKey.GetChannels])
queryClient.invalidateQueries([QueryKey.GetChannelTypeNames])
});
queryClient.invalidateQueries([QueryKey.GetChannels]);
queryClient.invalidateQueries([QueryKey.GetChannelTypeNames]);
},
onError(err: any) {
message({
@@ -133,42 +133,42 @@ export default function ChannelTable({
description: err?.message || t('channel.deleteFailed'),
isClosable: true,
position: 'top',
})
});
},
})
});
// Update the button click handlers in the table actions column:
const handleStatusUpdate = (id: string, currentStatus: number) => {
const newStatus =
currentStatus === ChannelStatus.ChannelStatusDisabled
? ChannelStatus.ChannelStatusEnabled
: ChannelStatus.ChannelStatusDisabled
updateChannelStatusMutation.mutate({ id, status: newStatus })
}
: ChannelStatus.ChannelStatusDisabled;
updateChannelStatusMutation.mutate({ id, status: newStatus });
};
const columnHelper = createColumnHelper<ChannelInfo>()
const columnHelper = createColumnHelper<ChannelInfo>();
const handleHeaderCheckboxChange = (isChecked: boolean) => {
if (isChecked) {
setSelectedChannels(data?.channels || [])
setSelectedChannels(data?.channels || []);
} else {
setSelectedChannels([])
setSelectedChannels([]);
}
}
};
const handleRowCheckboxChange = (channel: ChannelInfo, isChecked: boolean) => {
if (isChecked) {
setSelectedChannels([...selectedChannels, channel])
setSelectedChannels([...selectedChannels, channel]);
} else {
setSelectedChannels(selectedChannels.filter((c) => c.id !== channel.id))
setSelectedChannels(selectedChannels.filter((c) => c.id !== channel.id));
}
}
};
const handleExportRow = (channel: ChannelInfo) => {
const channels = [channel]
const filename = `channel_${channels[0].id}_${new Date().toISOString()}.json`
downloadJson(channels, filename)
}
const channels = [channel];
const filename = `channel_${channels[0].id}_${new Date().toISOString()}.json`;
downloadJson(channels, filename);
};
const columns = [
columnHelper.accessor((row) => row.id, {
@@ -345,26 +345,26 @@ export default function ChannelTable({
</Text>
),
cell: (info) => {
const status = info.getValue()
let statusText = ''
let statusColor = ''
const status = info.getValue();
let statusText = '';
let statusColor = '';
switch (status) {
case ChannelStatus.ChannelStatusEnabled:
statusText = t('keystatus.enabled')
statusColor = 'green.600'
break
statusText = t('keystatus.enabled');
statusColor = 'green.600';
break;
case ChannelStatus.ChannelStatusDisabled:
statusText = t('keystatus.disabled')
statusColor = 'red.600'
break
statusText = t('keystatus.disabled');
statusColor = 'red.600';
break;
case ChannelStatus.ChannelStatusAutoDisabled:
statusText = t('channelStatus.autoDisabled')
statusColor = 'orange.500'
break
statusText = t('channelStatus.autoDisabled');
statusColor = 'orange.500';
break;
default:
statusText = t('keystatus.unknown')
statusColor = 'gray.500'
statusText = t('keystatus.unknown');
statusColor = 'gray.500';
}
return (
@@ -378,7 +378,7 @@ export default function ChannelTable({
>
{statusText}
</Text>
)
);
},
}),
@@ -570,9 +570,9 @@ export default function ChannelTable({
color: 'brightBlue.600',
}}
onClick={() => {
setOperationType('update')
setChannelInfo(info.row.original)
onOpen()
setOperationType('update');
setChannelInfo(info.row.original);
onOpen();
}}
>
<svg
@@ -689,15 +689,15 @@ export default function ChannelTable({
</Menu>
),
}),
]
];
const tableData = useMemo(() => data?.channels || [], [data])
const tableData = useMemo(() => data?.channels || [], [data]);
const table = useReactTable({
data: tableData,
columns,
getCoreRowModel: getCoreRowModel(),
})
});
return (
<Box
@@ -780,5 +780,5 @@ export default function ChannelTable({
channelInfo={channelInfo}
/>
</Box>
)
);
}
@@ -1,6 +1,6 @@
'use client'
import { Dispatch, SetStateAction, useEffect } from 'react'
import { Controller, FieldErrors, useForm } from 'react-hook-form'
'use client';
import { Dispatch, SetStateAction, useEffect } from 'react';
import { Controller, FieldErrors, useForm } from 'react-hook-form';
import {
Badge,
Button,
@@ -20,30 +20,30 @@ import {
Spinner,
Text,
VStack,
} from '@chakra-ui/react'
import { zodResolver } from '@hookform/resolvers/zod'
import { useMessage } from '@sealos/ui'
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import { z } from 'zod'
} from '@chakra-ui/react';
import { zodResolver } from '@hookform/resolvers/zod';
import { useMessage } from '@sealos/ui';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { z } from 'zod';
import {
getChannelBuiltInSupportModels,
getChannelDefaultModelAndDefaultModeMapping,
getChannelTypeNames,
} from '@/api/platform'
import { createChannel, updateChannel } from '@/api/platform'
import { useTranslationClientSide } from '@/app/i18n/client'
import ConstructModeMappingComponent from '@/components/common/ConstructModeMappingComponent'
import { MultiSelectCombobox } from '@/components/common/MultiSelectCombobox'
import { SingleSelectCombobox } from '@/components/common/SingleSelectCombobox'
import { useI18n } from '@/providers/i18n/i18nContext'
import { ChannelInfo, ChannelStatus, ChannelType } from '@/types/admin/channels/channelInfo'
import { QueryKey } from '@/types/query-key'
} from '@/api/platform';
import { createChannel, updateChannel } from '@/api/platform';
import { useTranslationClientSide } from '@/app/i18n/client';
import ConstructModeMappingComponent from '@/components/common/ConstructModeMappingComponent';
import { MultiSelectCombobox } from '@/components/common/MultiSelectCombobox';
import { SingleSelectCombobox } from '@/components/common/SingleSelectCombobox';
import { useI18n } from '@/providers/i18n/i18nContext';
import { ChannelInfo, ChannelStatus, ChannelType } from '@/types/admin/channels/channelInfo';
import { QueryKey } from '@/types/query-key';
type Model = {
name: string
isDefault: boolean
}
name: string;
isDefault: boolean;
};
export const UpdateChannelModal = function ({
isOpen,
@@ -51,14 +51,14 @@ export const UpdateChannelModal = function ({
operationType,
channelInfo,
}: {
isOpen: boolean
onClose: () => void
operationType: 'create' | 'update'
channelInfo?: ChannelInfo
isOpen: boolean;
onClose: () => void;
operationType: 'create' | 'update';
channelInfo?: ChannelInfo;
}): JSX.Element {
const { lng } = useI18n()
const { t } = useTranslationClientSide(lng, 'common')
const queryClient = useQueryClient()
const { lng } = useI18n();
const { t } = useTranslationClientSide(lng, 'common');
const queryClient = useQueryClient();
const { message } = useMessage({
warningBoxBg: '#FFFAEB',
@@ -68,30 +68,30 @@ export const UpdateChannelModal = function ({
successBoxBg: '#EDFBF3',
successIconBg: '#039855',
successIconFill: 'white',
})
});
const { isLoading: isChannelTypeNamesLoading, data: channelTypeNames } = useQuery({
queryKey: [QueryKey.GetChannelTypeNames],
queryFn: () => getChannelTypeNames(),
})
});
const { isLoading: isBuiltInSupportModelsLoading, data: builtInSupportModels } = useQuery({
queryKey: [QueryKey.GetAllChannelModes],
queryFn: () => getChannelBuiltInSupportModels(),
})
});
const { isLoading: isDefaultEnabledModelsLoading, data: defaultEnabledModels } = useQuery({
queryKey: [QueryKey.GetDefaultModelAndModeMapping],
queryFn: () => getChannelDefaultModelAndDefaultModeMapping(),
})
});
// model type select combobox
const handleModelTypeDropdownItemFilter = (dropdownItems: string[], inputValue: string) => {
const lowerCasedInput = inputValue.toLowerCase()
const lowerCasedInput = inputValue.toLowerCase();
return dropdownItems.filter(
(item) => !inputValue || item.toLowerCase().includes(lowerCasedInput)
)
}
);
};
const handleModelTypeDropdownItemDisplay = (dropdownItem: string) => {
return (
@@ -106,8 +106,8 @@ export const UpdateChannelModal = function ({
>
{dropdownItem}
</Text>
)
}
);
};
// model select combobox
const handleModelFilteredDropdownItems = (
@@ -115,17 +115,17 @@ export const UpdateChannelModal = function ({
selectedItems: Model[],
inputValue: string
) => {
const lowerCasedInputValue = inputValue.toLowerCase()
const lowerCasedInputValue = inputValue.toLowerCase();
// First filter out items that are already selected
const unselectedItems = dropdownItems.filter(
(dropdownItem) =>
!selectedItems.some((selectedItem) => selectedItem.name === dropdownItem.name)
)
);
// Then filter by input value
return unselectedItems.filter((item) => item.name.toLowerCase().includes(lowerCasedInputValue))
}
return unselectedItems.filter((item) => item.name.toLowerCase().includes(lowerCasedInputValue));
};
const handleModelDropdownItemDisplay = (dropdownItem: Model) => {
if (dropdownItem.isDefault) {
@@ -190,7 +190,7 @@ export const UpdateChannelModal = function ({
</Text>
</Badge>
</Flex>
)
);
}
return (
<Text
@@ -203,8 +203,8 @@ export const UpdateChannelModal = function ({
>
{dropdownItem.name}
</Text>
)
}
);
};
const handleModelSelectedItemDisplay = (selectedItem: Model) => {
if (selectedItem.isDefault) {
@@ -259,7 +259,7 @@ export const UpdateChannelModal = function ({
</svg>
</Badge>
</Flex>
)
);
}
return (
<Text
@@ -273,8 +273,8 @@ export const UpdateChannelModal = function ({
>
{selectedItem.name}
</Text>
)
}
);
};
const handleSetCustomModel = (
selectedItems: Model[],
@@ -286,16 +286,16 @@ export const UpdateChannelModal = function ({
const newModel: Model = {
name: customModeName.trim(),
isDefault: false,
}
};
const exists = selectedItems.some((item) => item.name === customModeName.trim())
const exists = selectedItems.some((item) => item.name === customModeName.trim());
if (!exists) {
setSelectedItems([...selectedItems, newModel])
setCustomModeName('')
setSelectedItems([...selectedItems, newModel]);
setCustomModeName('');
}
}
}
};
// form schema
const schema = z.object({
@@ -306,10 +306,10 @@ export const UpdateChannelModal = function ({
base_url: z.string(),
models: z.array(z.string()).default([]),
model_mapping: z.record(z.string(), z.any()).default({}),
})
});
const id = channelInfo?.id
type FormData = z.infer<typeof schema>
const id = channelInfo?.id;
type FormData = z.infer<typeof schema>;
const {
register,
@@ -332,18 +332,18 @@ export const UpdateChannelModal = function ({
},
mode: 'onChange',
reValidateMode: 'onChange',
})
});
useEffect(() => {
if (channelInfo) {
const { id, type, name, key, base_url, models, model_mapping } = channelInfo
reset({ id, type, name, key, base_url, models, model_mapping })
const { id, type, name, key, base_url, models, model_mapping } = channelInfo;
reset({ id, type, name, key, base_url, models, model_mapping });
}
}, [channelInfo])
}, [channelInfo]);
const resetModalState = () => {
reset()
}
reset();
};
const createChannelMutation = useMutation({
mutationFn: createChannel,
@@ -351,9 +351,9 @@ export const UpdateChannelModal = function ({
message({
title: t('channels.createSuccess'),
status: 'success',
})
});
},
})
});
const updateChannelMutation = useMutation({
mutationFn: (data: FormData) =>
@@ -372,9 +372,9 @@ export const UpdateChannelModal = function ({
message({
title: t('channels.updateSuccess'),
status: 'success',
})
});
},
})
});
const onValidate = async (data: FormData) => {
try {
@@ -387,18 +387,18 @@ export const UpdateChannelModal = function ({
base_url: data.base_url,
models: data.models,
model_mapping: data.model_mapping,
})
break
});
break;
case 'update':
await updateChannelMutation.mutateAsync(data)
break
await updateChannelMutation.mutateAsync(data);
break;
}
queryClient.invalidateQueries({ queryKey: [QueryKey.GetChannels] })
queryClient.invalidateQueries({ queryKey: [QueryKey.GetDefaultModelAndModeMapping] })
queryClient.invalidateQueries({ queryKey: [QueryKey.GetChannelTypeNames] })
queryClient.invalidateQueries({ queryKey: [QueryKey.GetAllChannelModes] })
resetModalState()
onClose()
queryClient.invalidateQueries({ queryKey: [QueryKey.GetChannels] });
queryClient.invalidateQueries({ queryKey: [QueryKey.GetDefaultModelAndModeMapping] });
queryClient.invalidateQueries({ queryKey: [QueryKey.GetChannelTypeNames] });
queryClient.invalidateQueries({ queryKey: [QueryKey.GetAllChannelModes] });
resetModalState();
onClose();
} catch (error) {
switch (operationType) {
case 'create':
@@ -409,8 +409,8 @@ export const UpdateChannelModal = function ({
duration: 2000,
isClosable: true,
description: error instanceof Error ? error.message : t('channels.createFailed'),
})
break
});
break;
case 'update':
message({
title: t('channels.updateFailed'),
@@ -419,14 +419,14 @@ export const UpdateChannelModal = function ({
duration: 2000,
isClosable: true,
description: error instanceof Error ? error.message : t('channels.updateFailed'),
})
break
});
break;
}
}
}
};
const onInvalid = (errors: FieldErrors<FormData>): void => {
const firstErrorMessage = Object.values(errors)[0]?.message
const firstErrorMessage = Object.values(errors)[0]?.message;
if (firstErrorMessage) {
message({
title: firstErrorMessage as string,
@@ -434,11 +434,11 @@ export const UpdateChannelModal = function ({
position: 'top',
duration: 2000,
isClosable: true,
})
});
}
}
};
const onSubmit = handleSubmit(onValidate, onInvalid)
const onSubmit = handleSubmit(onValidate, onInvalid);
return (
<Modal isOpen={isOpen} onClose={onClose} isCentered>
@@ -611,11 +611,11 @@ export const UpdateChannelModal = function ({
render={({ field }) => {
const availableChannels = Object.entries(channelTypeNames)
.filter(([channel]) => channel in builtInSupportModels)
.map(([_, name]) => name)
.map(([_, name]) => name);
const initSelectedItem = field.value
? channelTypeNames[String(field.value) as ChannelType]
: undefined
: undefined;
return (
<SingleSelectCombobox<string>
@@ -625,20 +625,20 @@ export const UpdateChannelModal = function ({
if (channelName) {
const channelType = Object.entries(channelTypeNames).find(
([_, name]) => name === channelName
)?.[0]
)?.[0];
if (channelType) {
const numericChannel = Number(channelType)
field.onChange(numericChannel)
setValue('models', [])
setValue('model_mapping', {})
const numericChannel = Number(channelType);
field.onChange(numericChannel);
setValue('models', []);
setValue('model_mapping', {});
}
}
}}
handleDropdownItemFilter={handleModelTypeDropdownItemFilter}
handleDropdownItemDisplay={handleModelTypeDropdownItemDisplay}
/>
)
);
}}
/>
{errors.type && <FormErrorMessage>{errors.type.message}</FormErrorMessage>}
@@ -649,35 +649,35 @@ export const UpdateChannelModal = function ({
name="models"
control={control}
render={({ field }) => {
const channelType = String(watch('type')) as ChannelType
const channelType = String(watch('type')) as ChannelType;
const builtInModes =
builtInSupportModels[channelType]?.map((mode) => mode.model) || []
const defaultModes = defaultEnabledModels.models[channelType] || []
builtInSupportModels[channelType]?.map((mode) => mode.model) || [];
const defaultModes = defaultEnabledModels.models[channelType] || [];
const allModes: Model[] = builtInModes.map((modeName) => ({
name: modeName,
isDefault: defaultModes.includes(modeName),
}))
}));
const selectedModels: Model[] = field.value.map((modeName) => ({
name: modeName,
isDefault: defaultModes.includes(modeName),
}))
}));
return (
<MultiSelectCombobox<Model>
dropdownItems={allModes}
selectedItems={selectedModels}
setSelectedItems={(models) => {
field.onChange((models as Model[]).map((m) => m.name))
field.onChange((models as Model[]).map((m) => m.name));
}}
handleFilteredDropdownItems={handleModelFilteredDropdownItems}
handleDropdownItemDisplay={handleModelDropdownItemDisplay}
handleSelectedItemDisplay={handleModelSelectedItemDisplay}
handleSetCustomSelectedItem={handleSetCustomModel}
/>
)
);
}}
/>
{errors.models && <FormErrorMessage>{errors.models.message}</FormErrorMessage>}
@@ -688,24 +688,24 @@ export const UpdateChannelModal = function ({
name="model_mapping"
control={control}
render={({ field }) => {
const channelType = String(watch('type')) as ChannelType
const channelType = String(watch('type')) as ChannelType;
const selectedModels = watch('models')
const defaultModes = defaultEnabledModels.models[channelType] || []
const selectedModels = watch('models');
const defaultModes = defaultEnabledModels.models[channelType] || [];
const covertedSelectedModels: Model[] = selectedModels.map((modeName) => ({
name: modeName,
isDefault: defaultModes.includes(modeName),
}))
}));
return (
<ConstructModeMappingComponent
mapKeys={covertedSelectedModels}
mapData={field.value}
setMapData={(mapping) => {
field.onChange(mapping)
field.onChange(mapping);
}}
/>
)
);
}}
/>
{errors.model_mapping?.message && (
@@ -849,7 +849,7 @@ export const UpdateChannelModal = function ({
</>
))}
</Modal>
)
}
);
};
export default UpdateChannelModal
export default UpdateChannelModal;
@@ -1,26 +1,26 @@
'use client'
import { useRef, useState } from 'react'
import { Button, Flex, Text, useDisclosure, useToast } from '@chakra-ui/react'
import { useMessage } from '@sealos/ui'
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
'use client';
import { useRef, useState } from 'react';
import { Button, Flex, Text, useDisclosure, useToast } from '@chakra-ui/react';
import { useMessage } from '@sealos/ui';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { getAllChannels, uploadChannels } from '@/api/platform'
import { useTranslationClientSide } from '@/app/i18n/client'
import { useI18n } from '@/providers/i18n/i18nContext'
import { ChannelInfo } from '@/types/admin/channels/channelInfo'
import { QueryKey } from '@/types/query-key'
import { downloadJson } from '@/utils/common'
import { getAllChannels, uploadChannels } from '@/api/platform';
import { useTranslationClientSide } from '@/app/i18n/client';
import { useI18n } from '@/providers/i18n/i18nContext';
import { ChannelInfo } from '@/types/admin/channels/channelInfo';
import { QueryKey } from '@/types/query-key';
import { downloadJson } from '@/utils/common';
import ChannelTable from './components/ChannelTable'
import UpdateChannelModal from './components/UpdateChannelModal'
import ChannelTable from './components/ChannelTable';
import UpdateChannelModal from './components/UpdateChannelModal';
export default function DashboardPage() {
const { isOpen, onOpen, onClose } = useDisclosure()
const [operationType, setOperationType] = useState<'create' | 'update'>('create')
const { lng } = useI18n()
const { t } = useTranslationClientSide(lng, 'common')
const [exportData, setExportData] = useState<ChannelInfo[]>([])
const fileInputRef = useRef<HTMLInputElement>(null)
const { isOpen, onOpen, onClose } = useDisclosure();
const [operationType, setOperationType] = useState<'create' | 'update'>('create');
const { lng } = useI18n();
const { t } = useTranslationClientSide(lng, 'common');
const [exportData, setExportData] = useState<ChannelInfo[]>([]);
const fileInputRef = useRef<HTMLInputElement>(null);
const { message } = useMessage({
warningBoxBg: 'var(--Yellow-50, #FFFAEB)',
@@ -30,9 +30,9 @@ export default function DashboardPage() {
successBoxBg: 'var(--Green-50, #EDFBF3)',
successIconBg: 'var(--Green-600, #039855)',
successIconFill: 'white',
})
});
const queryClient = useQueryClient()
const queryClient = useQueryClient();
const {
data: allChannels,
@@ -43,57 +43,57 @@ export default function DashboardPage() {
queryFn: getAllChannels,
refetchOnReconnect: false,
enabled: false,
})
});
const uploadMutation = useMutation({
mutationFn: uploadChannels,
})
});
const handleExport = async () => {
if (exportData.length === 0) {
const result = await refetch()
const dataToExport = result.data || []
downloadJson(dataToExport, 'channels')
const result = await refetch();
const dataToExport = result.data || [];
downloadJson(dataToExport, 'channels');
} else {
downloadJson(exportData, 'channels')
downloadJson(exportData, 'channels');
}
}
};
const handleImport = () => {
fileInputRef.current?.click()
}
fileInputRef.current?.click();
};
const handleFileChange = async (event: React.ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0]
if (!file) return
const file = event.target.files?.[0];
if (!file) return;
const formData = new FormData()
formData.append('file', file)
const formData = new FormData();
formData.append('file', file);
try {
await uploadMutation.mutateAsync(formData)
await uploadMutation.mutateAsync(formData);
message({
title: t('dashboard.importSuccess'),
status: 'success',
duration: 3000,
isClosable: true,
})
queryClient.invalidateQueries([QueryKey.GetChannels])
queryClient.invalidateQueries([QueryKey.GetChannelTypeNames])
});
queryClient.invalidateQueries([QueryKey.GetChannels]);
queryClient.invalidateQueries([QueryKey.GetChannelTypeNames]);
} catch (error) {
console.error('Import error:', error)
console.error('Import error:', error);
message({
title: t('dashboard.importError'),
status: 'error',
duration: 3000,
isClosable: true,
})
});
} finally {
if (fileInputRef.current) {
fileInputRef.current.value = ''
fileInputRef.current.value = '';
}
}
}
};
return (
<Flex pt="4px" pb="12px" pr="12px" h="full" width="full">
@@ -158,8 +158,8 @@ export default function DashboardPage() {
},
}}
onClick={() => {
setOperationType('create')
onOpen()
setOperationType('create');
onOpen();
}}
>
<svg
@@ -298,5 +298,5 @@ export default function DashboardPage() {
<UpdateChannelModal isOpen={isOpen} onClose={onClose} operationType={operationType} />
</Flex>
</Flex>
)
);
}
@@ -1,17 +1,17 @@
'use client'
import { useState } from 'react'
import { Button, Flex, Text, useDisclosure } from '@chakra-ui/react'
import { Switch } from '@chakra-ui/react'
import { useMessage } from '@sealos/ui'
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import { produce } from 'immer'
'use client';
import { useState } from 'react';
import { Button, Flex, Text, useDisclosure } from '@chakra-ui/react';
import { Switch } from '@chakra-ui/react';
import { useMessage } from '@sealos/ui';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { produce } from 'immer';
import { getOption, updateOption } from '@/api/platform'
import { useTranslationClientSide } from '@/app/i18n/client'
import { useI18n } from '@/providers/i18n/i18nContext'
import { QueryKey } from '@/types/query-key'
import { getOption, updateOption } from '@/api/platform';
import { useTranslationClientSide } from '@/app/i18n/client';
import { useI18n } from '@/providers/i18n/i18nContext';
import { QueryKey } from '@/types/query-key';
import { EditableText } from './EditableText'
import { EditableText } from './EditableText';
export enum CommonConfigKey {
GlobalApiRateLimitNum = 'GlobalApiRateLimitNum',
@@ -21,25 +21,25 @@ export enum CommonConfigKey {
}
type CommonConfig = {
[CommonConfigKey.GlobalApiRateLimitNum]: string
[CommonConfigKey.DisableServe]: string
[CommonConfigKey.RetryTimes]: string
[CommonConfigKey.GroupMaxTokenNum]: string
}
[CommonConfigKey.GlobalApiRateLimitNum]: string;
[CommonConfigKey.DisableServe]: string;
[CommonConfigKey.RetryTimes]: string;
[CommonConfigKey.GroupMaxTokenNum]: string;
};
const CommonConfig = () => {
const { lng } = useI18n()
const { t } = useTranslationClientSide(lng, 'common')
const queryClient = useQueryClient()
const { lng } = useI18n();
const { t } = useTranslationClientSide(lng, 'common');
const queryClient = useQueryClient();
const [commonConfig, setCommonConfig] = useState<CommonConfig>(() =>
produce({} as CommonConfig, (draft) => {
draft.GlobalApiRateLimitNum = ''
draft.DisableServe = ''
draft.RetryTimes = ''
draft.GroupMaxTokenNum = ''
draft.GlobalApiRateLimitNum = '';
draft.DisableServe = '';
draft.RetryTimes = '';
draft.GroupMaxTokenNum = '';
})
)
);
const { message } = useMessage({
warningBoxBg: '#FFFAEB',
@@ -48,24 +48,24 @@ const CommonConfig = () => {
successBoxBg: '#EDFBF3',
successIconBg: '#039855',
successIconFill: 'white',
})
});
const { isLoading: isOptionLoading, data: optionData } = useQuery({
queryKey: [QueryKey.GetCommonConfig],
queryFn: () => getOption(),
onSuccess: (data) => {
if (!data) return
if (!data) return;
setCommonConfig(
produce(commonConfig, (draft) => {
draft.GlobalApiRateLimitNum = data.GlobalApiRateLimitNum || ''
draft.DisableServe = data.DisableServe || ''
draft.RetryTimes = data.RetryTimes || ''
draft.GroupMaxTokenNum = data.GroupMaxTokenNum || ''
draft.GlobalApiRateLimitNum = data.GlobalApiRateLimitNum || '';
draft.DisableServe = data.DisableServe || '';
draft.RetryTimes = data.RetryTimes || '';
draft.GroupMaxTokenNum = data.GroupMaxTokenNum || '';
})
)
);
},
})
});
const updateOptionMutation = useMutation({
mutationFn: (params: { key: string; value: string }) => updateOption(params),
@@ -73,30 +73,30 @@ const CommonConfig = () => {
message({
title: t('globalConfigs.saveCommonConfigSuccess'),
status: 'success',
})
queryClient.invalidateQueries({ queryKey: [QueryKey.GetCommonConfig] })
});
queryClient.invalidateQueries({ queryKey: [QueryKey.GetCommonConfig] });
},
onError: () => {
message({
title: t('globalConfigs.saveCommonConfigFailed'),
status: 'error',
})
});
},
})
});
const updateConfigField = (field: CommonConfigKey, value: string) => {
setCommonConfig(
produce((draft) => {
draft[field] = value
draft[field] = value;
})
)
updateOptionMutation.mutate({ key: field, value })
}
);
updateOptionMutation.mutate({ key: field, value });
};
const handleDisableServeChange = (checked: boolean) => {
const value = checked ? 'true' : 'false'
updateConfigField(CommonConfigKey.DisableServe, value)
}
const value = checked ? 'true' : 'false';
updateConfigField(CommonConfigKey.DisableServe, value);
};
return (
/*
@@ -163,7 +163,7 @@ const CommonConfig = () => {
{/* -- config end */}
</Flex>
)
}
);
};
export default CommonConfig
export default CommonConfig;
@@ -1,6 +1,6 @@
'use client'
import React, { useState } from 'react'
import { CheckIcon, CloseIcon } from '@chakra-ui/icons'
'use client';
import React, { useState } from 'react';
import { CheckIcon, CloseIcon } from '@chakra-ui/icons';
import {
Box,
Button,
@@ -14,29 +14,29 @@ import {
PopoverTrigger,
Text,
useDisclosure,
} from '@chakra-ui/react'
} from '@chakra-ui/react';
interface EditableTextProps {
value: string | number
label: string
onSubmit: (value: string) => void
flexProps?: FlexProps
value: string | number;
label: string;
onSubmit: (value: string) => void;
flexProps?: FlexProps;
}
export const EditableText = ({ value, label, onSubmit, flexProps }: EditableTextProps) => {
const [editValue, setEditValue] = useState(String(value))
const { isOpen, onOpen, onClose } = useDisclosure()
const [editValue, setEditValue] = useState(String(value));
const { isOpen, onOpen, onClose } = useDisclosure();
const handleSubmit = () => {
onSubmit(editValue)
onClose()
}
onSubmit(editValue);
onClose();
};
const handleCancel = () => {
// 关闭时 恢复到传递来的初始值
setEditValue(String(value))
onClose()
}
setEditValue(String(value));
onClose();
};
return (
<Flex alignItems="center" gap="16px" justifyContent="space-between" {...flexProps}>
@@ -187,5 +187,5 @@ export const EditableText = ({ value, label, onSubmit, flexProps }: EditableText
</PopoverContent>
</Popover>
</Flex>
)
}
);
};
@@ -1,32 +1,32 @@
'use client'
import { Dispatch, SetStateAction, useEffect, useState } from 'react'
import { Controller, FieldError, FieldErrors, FieldErrorsImpl, useForm } from 'react-hook-form'
import { Button, Flex, FormControl, Skeleton, Text, VStack } from '@chakra-ui/react'
import { zodResolver } from '@hookform/resolvers/zod'
import { useMessage } from '@sealos/ui'
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import { z } from 'zod'
'use client';
import { Dispatch, SetStateAction, useEffect, useState } from 'react';
import { Controller, FieldError, FieldErrors, FieldErrorsImpl, useForm } from 'react-hook-form';
import { Button, Flex, FormControl, Skeleton, Text, VStack } from '@chakra-ui/react';
import { zodResolver } from '@hookform/resolvers/zod';
import { useMessage } from '@sealos/ui';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { z } from 'zod';
import {
batchOption,
getChannelBuiltInSupportModels,
getChannelTypeNames,
getOption,
} from '@/api/platform'
import { useTranslationClientSide } from '@/app/i18n/client'
import ConstructMappingComponent from '@/components/common/ConstructMappingComponent'
import { MultiSelectCombobox } from '@/components/common/MultiSelectCombobox'
import { SingleSelectCombobox } from '@/components/common/SingleSelectCombobox'
import { useI18n } from '@/providers/i18n/i18nContext'
import { ChannelType } from '@/types/admin/channels/channelInfo'
import { DefaultChannelModel, DefaultChannelModelMapping } from '@/types/admin/option'
import { BatchOptionData } from '@/types/admin/option'
import { QueryKey } from '@/types/query-key'
} from '@/api/platform';
import { useTranslationClientSide } from '@/app/i18n/client';
import ConstructMappingComponent from '@/components/common/ConstructMappingComponent';
import { MultiSelectCombobox } from '@/components/common/MultiSelectCombobox';
import { SingleSelectCombobox } from '@/components/common/SingleSelectCombobox';
import { useI18n } from '@/providers/i18n/i18nContext';
import { ChannelType } from '@/types/admin/channels/channelInfo';
import { DefaultChannelModel, DefaultChannelModelMapping } from '@/types/admin/option';
import { BatchOptionData } from '@/types/admin/option';
import { QueryKey } from '@/types/query-key';
const ModelConfig = () => {
const { lng } = useI18n()
const { t } = useTranslationClientSide(lng, 'common')
const queryClient = useQueryClient()
const { lng } = useI18n();
const { t } = useTranslationClientSide(lng, 'common');
const queryClient = useQueryClient();
const { message } = useMessage({
warningBoxBg: '#FFFAEB',
@@ -36,51 +36,51 @@ const ModelConfig = () => {
successBoxBg: '#EDFBF3',
successIconBg: '#039855',
successIconFill: 'white',
})
});
const [allSupportChannel, setAllSupportChannel] = useState<string[]>([])
const [allSupportChannel, setAllSupportChannel] = useState<string[]>([]);
const [allSupportChannelWithMode, setAllSupportChannelWithMode] = useState<{
[key in ChannelType]: string[]
}>({})
[key in ChannelType]: string[];
}>({});
const [defaultModel, setDefaultModel] = useState<DefaultChannelModel>({})
const [defaultModelMapping, setDefaultModelMapping] = useState<DefaultChannelModelMapping>({})
const [defaultModel, setDefaultModel] = useState<DefaultChannelModel>({});
const [defaultModelMapping, setDefaultModelMapping] = useState<DefaultChannelModelMapping>({});
const { isLoading: isChannelTypeNamesLoading, data: channelTypeNames } = useQuery({
queryKey: [QueryKey.GetChannelTypeNames],
queryFn: () => getChannelTypeNames(),
})
});
const { isLoading: isBuiltInSupportModelsLoading, data: builtInSupportModels } = useQuery({
queryKey: [QueryKey.GetAllChannelModes],
queryFn: () => getChannelBuiltInSupportModels(),
})
});
const { isLoading: isOptionLoading, data: optionData } = useQuery({
queryKey: [QueryKey.GetOption],
queryFn: () => getOption(),
onSuccess: (data) => {
if (!data) return
if (!data) return;
const defaultModels: DefaultChannelModel = JSON.parse(data.DefaultChannelModels)
const defaultModels: DefaultChannelModel = JSON.parse(data.DefaultChannelModels);
const defaultModelMappings: DefaultChannelModelMapping = JSON.parse(
data.DefaultChannelModelMapping
)
);
setDefaultModel(defaultModels)
setDefaultModelMapping(defaultModelMappings)
setDefaultModel(defaultModels);
setDefaultModelMapping(defaultModelMappings);
},
})
});
useEffect(() => {
if (!channelTypeNames || !builtInSupportModels) return
if (!channelTypeNames || !builtInSupportModels) return;
// 1. 处理 allSupportChannel
const supportedChannels = Object.entries(channelTypeNames)
.filter(([channel]) => channel in builtInSupportModels)
.map(([_, name]) => name)
.map(([_, name]) => name);
setAllSupportChannel(supportedChannels)
setAllSupportChannel(supportedChannels);
// 2. 处理 allSupportChannelWithMode
// 渠道类型可能出现在 channelTypeNames 中,但不在 builtInSupportModels 中,所以需要过滤
@@ -88,17 +88,17 @@ const ModelConfig = () => {
const channelWithModes = Object.entries(channelTypeNames)
.filter(([channelType, _]) => channelType in builtInSupportModels)
.reduce((acc, [channelType, channelName]) => {
const modelInfos = builtInSupportModels[channelType as ChannelType] || []
const models = [...new Set(modelInfos.map((info) => info.model))]
const modelInfos = builtInSupportModels[channelType as ChannelType] || [];
const models = [...new Set(modelInfos.map((info) => info.model))];
return {
...acc,
[channelType]: models,
}
}, {} as { [key in ChannelType]: string[] })
};
}, {} as { [key in ChannelType]: string[] });
setAllSupportChannelWithMode(channelWithModes)
}, [channelTypeNames, builtInSupportModels])
setAllSupportChannelWithMode(channelWithModes);
}, [channelTypeNames, builtInSupportModels]);
// form schema
const itemSchema = z.object({
@@ -108,15 +108,15 @@ const ModelConfig = () => {
.record(z.string(), z.string())
.refine((mapping) => {
// 检查所有值不能为空字符串
return Object.values(mapping).every((value) => value.trim() !== '')
return Object.values(mapping).every((value) => value.trim() !== '');
})
.default({}),
})
});
const schema = z.array(itemSchema)
const schema = z.array(itemSchema);
type ConfigItem = z.infer<typeof itemSchema>
type FormData = ConfigItem[]
type ConfigItem = z.infer<typeof itemSchema>;
type FormData = ConfigItem[];
const {
register,
@@ -131,12 +131,12 @@ const ModelConfig = () => {
defaultValues: [],
mode: 'onChange',
reValidateMode: 'onChange',
})
});
useEffect(() => {
if (!defaultModel || !defaultModelMapping) return
if (!defaultModel || !defaultModelMapping) return;
// Only proceed if both defaultModel and defaultModelMapping are available
if (Object.keys(defaultModel).length === 0) return
if (Object.keys(defaultModel).length === 0) return;
// Transform the data into form format
const formData: FormData = Object.entries(defaultModel).map(([channelType, modes]) => {
@@ -144,33 +144,33 @@ const ModelConfig = () => {
type: Number(channelType),
defaultMode: modes || [], // Using first mode as default
defaultModeMapping: defaultModelMapping[channelType as ChannelType] || {},
}
})
};
});
// Reset form with the new values
reset(formData)
}, [defaultModel, defaultModelMapping, reset])
reset(formData);
}, [defaultModel, defaultModelMapping, reset]);
const handleAddDefaultModel = () => {
const newItem = {
type: undefined, // Default type value
defaultMode: [],
defaultModeMapping: {},
}
};
// Get current form values
const currentValues = watch()
const currentValues = watch();
// Create new array with new item at the beginning
const newValues = [newItem, ...Object.values(currentValues)]
const newValues = [newItem, ...Object.values(currentValues)];
// Reset form with new values
reset(newValues)
}
reset(newValues);
};
const formValues = watch()
const formValues = watch();
const formValuesArray: FormData = Array.isArray(formValues)
? formValues
: Object.values(formValues)
: Object.values(formValues);
const batchOptionMutation = useMutation({
mutationFn: batchOption,
@@ -178,66 +178,66 @@ const ModelConfig = () => {
message({
title: t('globalConfigs.saveDefaultModelSuccess'),
status: 'success',
})
});
},
})
});
const transformFormDataToConfig = (formData: FormData): BatchOptionData => {
// 初始化两个对象
const defaultChannelModelMapping: Record<string, Record<string, string>> = {}
const defaultChannelModels: Record<string, string[]> = {}
const defaultChannelModelMapping: Record<string, Record<string, string>> = {};
const defaultChannelModels: Record<string, string[]> = {};
// 遍历 FormData
formData.forEach((item) => {
const type = item.type.toString()
const type = item.type.toString();
// 处理 DefaultChannelModelMapping
if (Object.keys(item.defaultModeMapping).length > 0) {
defaultChannelModelMapping[type] = item.defaultModeMapping
defaultChannelModelMapping[type] = item.defaultModeMapping;
}
// 处理 DefaultChannelModels
if (item.defaultMode.length > 0) {
defaultChannelModels[type] = item.defaultMode
defaultChannelModels[type] = item.defaultMode;
}
})
});
return {
// 转换为 JSON 字符串
DefaultChannelModelMapping: JSON.stringify(defaultChannelModelMapping),
DefaultChannelModels: JSON.stringify(defaultChannelModels),
}
}
};
};
const resetForm = () => {
reset()
}
reset();
};
type FieldErrorType =
| FieldError
| FieldErrorsImpl<{
type: number
defaultMode: string[]
defaultModeMapping: Record<string, string>
}>
type: number;
defaultMode: string[];
defaultModeMapping: Record<string, string>;
}>;
const getFirstErrorMessage = (errors: FieldErrors<FormData>): string => {
// Iterate through top-level errors
for (const index in errors) {
const fieldError = errors[index] as FieldErrorType
if (!fieldError) continue
const fieldError = errors[index] as FieldErrorType;
if (!fieldError) continue;
// Check if error is an object
if (typeof fieldError === 'object') {
// If it has a direct message property
if ('message' in fieldError && fieldError.message) {
return `Item ${Number(index) + 1}: ${fieldError.message}`
return `Item ${Number(index) + 1}: ${fieldError.message}`;
}
// Iterate through nested field errors
const errorKeys = Object.keys(fieldError) as Array<keyof typeof fieldError>
const errorKeys = Object.keys(fieldError) as Array<keyof typeof fieldError>;
for (const fieldName of errorKeys) {
const nestedError = fieldError[fieldName]
const nestedError = fieldError[fieldName];
if (nestedError && typeof nestedError === 'object' && 'message' in nestedError) {
// Map field names to their display labels
const fieldLabel =
@@ -245,25 +245,25 @@ const ModelConfig = () => {
type: 'Type',
defaultMode: 'Default Mode',
defaultModeMapping: 'Model Mapping',
}[fieldName as string] || fieldName
}[fieldName as string] || fieldName;
return `Item ${Number(index) + 1} ${fieldLabel}: ${nestedError.message}`
return `Item ${Number(index) + 1} ${fieldLabel}: ${nestedError.message}`;
}
}
}
}
return 'Form validation failed'
}
return 'Form validation failed';
};
const onValidate = async (data: FormData) => {
try {
const batchOptionData: BatchOptionData = transformFormDataToConfig(data)
await batchOptionMutation.mutateAsync(batchOptionData)
const batchOptionData: BatchOptionData = transformFormDataToConfig(data);
await batchOptionMutation.mutateAsync(batchOptionData);
queryClient.invalidateQueries({ queryKey: [QueryKey.GetOption] })
queryClient.invalidateQueries({ queryKey: [QueryKey.GetChannelTypeNames] })
queryClient.invalidateQueries({ queryKey: [QueryKey.GetAllChannelModes] })
resetForm()
queryClient.invalidateQueries({ queryKey: [QueryKey.GetOption] });
queryClient.invalidateQueries({ queryKey: [QueryKey.GetChannelTypeNames] });
queryClient.invalidateQueries({ queryKey: [QueryKey.GetAllChannelModes] });
resetForm();
} catch (error) {
message({
title: t('globalConfigs.saveDefaultModelFailed'),
@@ -273,15 +273,15 @@ const ModelConfig = () => {
isClosable: true,
description:
error instanceof Error ? error.message : t('globalConfigs.saveDefaultModelFailed'),
})
console.error(error)
});
console.error(error);
}
}
};
const onInvalid = (errors: FieldErrors<FormData>): void => {
console.error('errors', errors)
console.error('errors', errors);
const errorMessage = getFirstErrorMessage(errors)
const errorMessage = getFirstErrorMessage(errors);
message({
title: errorMessage,
@@ -289,10 +289,10 @@ const ModelConfig = () => {
position: 'top',
duration: 2000,
isClosable: true,
})
}
});
};
const onSubmit = handleSubmit(onValidate, onInvalid)
const onSubmit = handleSubmit(onValidate, onInvalid);
return (
/*
顶级 Flex 容器的高度: calc(100vh - 16px - 24px - 12px - 32px - 36px)
@@ -471,9 +471,9 @@ const ModelConfig = () => {
color: '#D92D20',
}}
onClick={() => {
const currentValues = watch()
const newValues = Object.values(currentValues).filter((_, i) => i !== index)
reset(newValues)
const currentValues = watch();
const newValues = Object.values(currentValues).filter((_, i) => i !== index);
reset(newValues);
}}
>
<svg
@@ -511,7 +511,7 @@ const ModelConfig = () => {
(type): type is ChannelType =>
type !== undefined && type in channelTypeNames
)
.map((type) => channelTypeNames[type])
.map((type) => channelTypeNames[type]);
// Filter available types
const availableTypes = allSupportChannel.filter(
@@ -521,11 +521,11 @@ const ModelConfig = () => {
(field.value &&
channelTypeNames[String(field.value) as ChannelType] ===
channelType)
)
);
const initSelectedItem = field.value
? channelTypeNames[String(field.value) as ChannelType]
: undefined
: undefined;
return (
<SingleSelectCombobox<string>
@@ -535,20 +535,20 @@ const ModelConfig = () => {
if (channelName) {
const channelType = Object.entries(channelTypeNames).find(
([_, name]) => name === channelName
)?.[0]
)?.[0];
if (channelType) {
const defaultModelField =
defaultModel[channelType as ChannelType]
defaultModel[channelType as ChannelType];
const defaultModelMappingField =
defaultModelMapping[channelType as ChannelType]
defaultModelMapping[channelType as ChannelType];
field.onChange(Number(channelType))
setValue(`${index}.defaultMode`, defaultModelField || [])
field.onChange(Number(channelType));
setValue(`${index}.defaultMode`, defaultModelField || []);
setValue(
`${index}.defaultModeMapping`,
defaultModelMappingField || {}
)
);
}
}
}}
@@ -556,11 +556,11 @@ const ModelConfig = () => {
dropdownItems: string[],
inputValue: string
) => {
const lowerCasedInput = inputValue.toLowerCase()
const lowerCasedInput = inputValue.toLowerCase();
return dropdownItems.filter(
(item) =>
!inputValue || item.toLowerCase().includes(lowerCasedInput)
)
);
}}
handleDropdownItemDisplay={(item: string) => (
<Text
@@ -576,7 +576,7 @@ const ModelConfig = () => {
</Text>
)}
/>
)
);
}}
/>
</FormControl>
@@ -587,10 +587,10 @@ const ModelConfig = () => {
control={control}
render={({ field }) => {
// Get the current type value from the form
const currentType = watch(`${index}.type`)
const currentType = watch(`${index}.type`);
const dropdownItems = currentType
? allSupportChannelWithMode[String(currentType) as ChannelType] || []
: []
: [];
const handleSetCustomModel = (
selectedItems: string[],
@@ -601,35 +601,35 @@ const ModelConfig = () => {
if (customModeName.trim()) {
const exists = field.value.some(
(item) => item === customModeName.trim()
)
);
if (!exists) {
field.onChange([...field.value, customModeName.trim()])
setCustomModeName('')
field.onChange([...field.value, customModeName.trim()]);
setCustomModeName('');
}
}
}
};
const handleModelFilteredDropdownItems = (
dropdownItems: string[],
selectedItems: string[],
inputValue: string
) => {
const lowerCasedInputValue = inputValue.toLowerCase()
const lowerCasedInputValue = inputValue.toLowerCase();
return dropdownItems.filter(
(item) =>
!selectedItems.includes(item) &&
item.toLowerCase().includes(lowerCasedInputValue)
)
}
);
};
return (
<MultiSelectCombobox<string>
dropdownItems={dropdownItems || []}
selectedItems={field.value || []} // Use field.value for selected items
setSelectedItems={(models) => {
field.onChange(models)
field.onChange(models);
}}
handleFilteredDropdownItems={handleModelFilteredDropdownItems}
handleDropdownItemDisplay={(item) => (
@@ -659,7 +659,7 @@ const ModelConfig = () => {
)}
handleSetCustomSelectedItem={handleSetCustomModel}
/>
)
);
}}
/>
</FormControl>
@@ -669,31 +669,31 @@ const ModelConfig = () => {
name={`${index}.defaultModeMapping`}
control={control}
render={({ field }) => {
const defaultMode = watch(`${index}.defaultMode`)
const defaultModeMapping = watch(`${index}.defaultModeMapping`)
const defaultMode = watch(`${index}.defaultMode`);
const defaultModeMapping = watch(`${index}.defaultModeMapping`);
return (
<ConstructMappingComponent
mapKeys={defaultMode}
mapData={defaultModeMapping}
setMapData={(mapping) => {
field.onChange(mapping)
field.onChange(mapping);
}}
/>
)
);
}}
/>
</FormControl>
</VStack>
</Flex>
)
);
})
)}
</Flex>
</Flex>
{/* -- config end */}
</Flex>
)
}
);
};
export default ModelConfig
export default ModelConfig;
@@ -1,23 +1,23 @@
'use client'
import { useRef } from 'react'
import { Button, Divider, Flex, Text, useDisclosure } from '@chakra-ui/react'
import { useMessage } from '@sealos/ui/src/components'
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
'use client';
import { useRef } from 'react';
import { Button, Divider, Flex, Text, useDisclosure } from '@chakra-ui/react';
import { useMessage } from '@sealos/ui/src/components';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { getOption, uploadOptions } from '@/api/platform'
import { useTranslationClientSide } from '@/app/i18n/client'
import { useI18n } from '@/providers/i18n/i18nContext'
import { QueryKey } from '@/types/query-key'
import { downloadJson } from '@/utils/common'
import { getOption, uploadOptions } from '@/api/platform';
import { useTranslationClientSide } from '@/app/i18n/client';
import { useI18n } from '@/providers/i18n/i18nContext';
import { QueryKey } from '@/types/query-key';
import { downloadJson } from '@/utils/common';
import CommonConfig from './components/CommonConfig'
import ModelConfig from './components/ModelConfig'
import CommonConfig from './components/CommonConfig';
import ModelConfig from './components/ModelConfig';
export default function GlobalConfigPage() {
const { lng } = useI18n()
const { t } = useTranslationClientSide(lng, 'common')
const { lng } = useI18n();
const { t } = useTranslationClientSide(lng, 'common');
const fileInputRef = useRef<HTMLInputElement>(null)
const fileInputRef = useRef<HTMLInputElement>(null);
const { message } = useMessage({
warningBoxBg: '#FFFAEB',
@@ -27,9 +27,9 @@ export default function GlobalConfigPage() {
successBoxBg: '#EDFBF3',
successIconBg: '#039855',
successIconFill: 'white',
})
});
const queryClient = useQueryClient()
const queryClient = useQueryClient();
const {
isFetching: isOptionFetching,
@@ -40,53 +40,53 @@ export default function GlobalConfigPage() {
queryFn: () => getOption(),
refetchOnReconnect: false,
enabled: false,
})
});
const uploadMutation = useMutation({
mutationFn: uploadOptions,
})
});
const handleExport = async () => {
const result = await refetch()
const dataToExport = result.data || []
downloadJson(dataToExport, 'global_configs')
}
const result = await refetch();
const dataToExport = result.data || [];
downloadJson(dataToExport, 'global_configs');
};
const handleImport = () => {
fileInputRef.current?.click()
}
fileInputRef.current?.click();
};
const handleFileChange = async (event: React.ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0]
if (!file) return
const file = event.target.files?.[0];
if (!file) return;
const formData = new FormData()
formData.append('file', file)
const formData = new FormData();
formData.append('file', file);
try {
await uploadMutation.mutateAsync(formData)
await uploadMutation.mutateAsync(formData);
message({
title: t('dashboard.importSuccess'),
status: 'success',
duration: 3000,
isClosable: true,
})
queryClient.invalidateQueries([QueryKey.GetOption])
queryClient.invalidateQueries([QueryKey.GetCommonConfig])
});
queryClient.invalidateQueries([QueryKey.GetOption]);
queryClient.invalidateQueries([QueryKey.GetCommonConfig]);
} catch (error) {
console.error('Import error:', error)
console.error('Import error:', error);
message({
title: t('dashboard.importError'),
status: 'error',
duration: 3000,
isClosable: true,
})
});
} finally {
if (fileInputRef.current) {
fileInputRef.current.value = ''
fileInputRef.current.value = '';
}
}
}
};
return (
<Flex pt="4px" pb="12px" pr="12px" pl="0px" h="100vh" width="full">
@@ -265,5 +265,5 @@ export default function GlobalConfigPage() {
{/* -- config end */}
</Flex>
</Flex>
)
);
}
@@ -1,42 +1,42 @@
'use client'
'use client';
import { useMemo, useState } from 'react'
import { Box, Button, Flex, Icon, Input, Text } from '@chakra-ui/react'
import { CurrencySymbol, MyTooltip } from '@sealos/ui'
import { useQuery } from '@tanstack/react-query'
import { ColumnDef, getCoreRowModel, useReactTable } from '@tanstack/react-table'
import { useMemo, useState } from 'react';
import { Box, Button, Flex, Icon, Input, Text } from '@chakra-ui/react';
import { CurrencySymbol, MyTooltip } from '@sealos/ui';
import { useQuery } from '@tanstack/react-query';
import { ColumnDef, getCoreRowModel, useReactTable } from '@tanstack/react-table';
import { getEnabledMode, getGlobalLogs } from '@/api/platform'
import { useTranslationClientSide } from '@/app/i18n/client'
import SelectDateRange from '@/components/common/SelectDateRange'
import { SingleSelectComboboxUnstyle } from '@/components/common/SingleSelectComboboxUnStyle'
import SwitchPage from '@/components/common/SwitchPage'
import { BaseTable } from '@/components/table/BaseTable'
import { useI18n } from '@/providers/i18n/i18nContext'
import { useBackendStore } from '@/store/backend'
import { QueryKey } from '@/types/query-key'
import { GlobalLogItem } from '@/types/user/logs'
import { getEnabledMode, getGlobalLogs } from '@/api/platform';
import { useTranslationClientSide } from '@/app/i18n/client';
import SelectDateRange from '@/components/common/SelectDateRange';
import { SingleSelectComboboxUnstyle } from '@/components/common/SingleSelectComboboxUnStyle';
import SwitchPage from '@/components/common/SwitchPage';
import { BaseTable } from '@/components/table/BaseTable';
import { useI18n } from '@/providers/i18n/i18nContext';
import { useBackendStore } from '@/store/backend';
import { QueryKey } from '@/types/query-key';
import { GlobalLogItem } from '@/types/user/logs';
export default function Home(): React.JSX.Element {
const { lng } = useI18n()
const { t } = useTranslationClientSide(lng, 'common')
const { currencySymbol } = useBackendStore()
const { lng } = useI18n();
const { t } = useTranslationClientSide(lng, 'common');
const { currencySymbol } = useBackendStore();
const [startTime, setStartTime] = useState(() => {
const currentDate = new Date()
currentDate.setMonth(currentDate.getMonth() - 1)
return currentDate
})
const [endTime, setEndTime] = useState(new Date())
const [groupId, setGroupId] = useState('')
const [name, setName] = useState('')
const [modelName, setModelName] = useState<string>('')
const [page, setPage] = useState(1)
const [pageSize, setPageSize] = useState(10)
const [logData, setLogData] = useState<GlobalLogItem[]>([])
const [total, setTotal] = useState(0)
const currentDate = new Date();
currentDate.setMonth(currentDate.getMonth() - 1);
return currentDate;
});
const [endTime, setEndTime] = useState(new Date());
const [groupId, setGroupId] = useState('');
const [name, setName] = useState('');
const [modelName, setModelName] = useState<string>('');
const [page, setPage] = useState(1);
const [pageSize, setPageSize] = useState(10);
const [logData, setLogData] = useState<GlobalLogItem[]>([]);
const [total, setTotal] = useState(0);
const { data: models = [] } = useQuery([QueryKey.GetEnabledModels], () => getEnabledMode())
const { data: models = [] } = useQuery([QueryKey.GetEnabledModels], () => getEnabledMode());
const { isLoading } = useQuery(
[QueryKey.GetGlobalLogs, page, pageSize, name, modelName, startTime, endTime, groupId],
@@ -53,15 +53,15 @@ export default function Home(): React.JSX.Element {
{
onSuccess: (data) => {
if (!data?.logs) {
setLogData([])
setTotal(0)
return
setLogData([]);
setTotal(0);
return;
}
setLogData(data?.logs || [])
setTotal(data?.total || 0)
setLogData(data?.logs || []);
setTotal(data?.total || 0);
},
}
)
);
const columns = useMemo<ColumnDef<GlobalLogItem>[]>(() => {
return [
@@ -94,7 +94,7 @@ export default function Home(): React.JSX.Element {
header: t('logs.status'),
accessorFn: (row) => (row.code === 200 ? t('logs.success') : t('logs.failed')),
cell: ({ getValue }) => {
const value = getValue() as string
const value = getValue() as string;
return (
<Text
color={
@@ -110,7 +110,7 @@ export default function Home(): React.JSX.Element {
>
{value}
</Text>
)
);
},
id: 'status',
},
@@ -142,17 +142,17 @@ export default function Home(): React.JSX.Element {
</Flex>
</MyTooltip>
</Box>
)
);
},
},
]
}, [])
];
}, []);
const table = useReactTable({
data: logData,
columns,
getCoreRowModel: getCoreRowModel(),
})
});
return (
<Flex
@@ -223,8 +223,8 @@ export default function Home(): React.JSX.Element {
bg="white"
boxShadow="0px 1px 2px 0px rgba(19, 51, 107, 0.05), 0px 0px 1px 0px rgba(19, 51, 107, 0.08)"
onClick={() => {
setName('')
setModelName('')
setName('');
setModelName('');
}}
>
<Icon
@@ -315,16 +315,16 @@ export default function Home(): React.JSX.Element {
dropdownItems={['all', ...models.map((item) => item.model)]}
setSelectedItem={(value) => {
if (value === 'all') {
setModelName('')
setModelName('');
} else {
setModelName(value)
setModelName(value);
}
}}
handleDropdownItemFilter={(dropdownItems, inputValue) => {
const lowerCasedInput = inputValue.toLowerCase()
const lowerCasedInput = inputValue.toLowerCase();
return dropdownItems.filter(
(item) => !inputValue || item.toLowerCase().includes(lowerCasedInput)
)
);
}}
handleDropdownItemDisplay={(dropdownItem) => {
return (
@@ -339,7 +339,7 @@ export default function Home(): React.JSX.Element {
>
{dropdownItem}
</Text>
)
);
}}
flexProps={{ w: '500px' }}
placeholder={t('GlobalLogs.selectModel')}
@@ -447,5 +447,5 @@ export default function Home(): React.JSX.Element {
{/* -- table end */}
</Flex>
</Flex>
)
);
}
@@ -1,6 +1,6 @@
import { Box, Flex } from '@chakra-ui/react'
import { Box, Flex } from '@chakra-ui/react';
import SideBar from '@/components/admin/Sidebar'
import SideBar from '@/components/admin/Sidebar';
export default function UserLayout({ children }: { children: React.ReactNode }) {
return (
@@ -13,5 +13,5 @@ export default function UserLayout({ children }: { children: React.ReactNode })
{children}
</Box>
</Flex>
)
);
}
@@ -1,6 +1,6 @@
'use client'
'use client';
import { useMemo, useState } from 'react'
import { useMemo, useState } from 'react';
import {
Box,
Button,
@@ -12,31 +12,31 @@ import {
MenuItem,
MenuList,
Text,
} from '@chakra-ui/react'
import { CurrencySymbol } from '@sealos/ui'
import { useMessage } from '@sealos/ui'
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
} from '@chakra-ui/react';
import { CurrencySymbol } from '@sealos/ui';
import { useMessage } from '@sealos/ui';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import {
ColumnDef,
createColumnHelper,
getCoreRowModel,
useReactTable,
} from '@tanstack/react-table'
} from '@tanstack/react-table';
import { deleteGroup, getGroups, updateGroupQpm, updateGroupStatus } from '@/api/platform'
import { useTranslationClientSide } from '@/app/i18n/client'
import { EditableTextNoLable } from '@/components/common/EditableTextNoLable'
import SwitchPage from '@/components/common/SwitchPage'
import { BaseTable } from '@/components/table/BaseTable'
import { useI18n } from '@/providers/i18n/i18nContext'
import { useBackendStore } from '@/store/backend'
import { GroupInfo, GroupStatus } from '@/types/admin/group'
import { QueryKey } from '@/types/query-key'
import { deleteGroup, getGroups, updateGroupQpm, updateGroupStatus } from '@/api/platform';
import { useTranslationClientSide } from '@/app/i18n/client';
import { EditableTextNoLable } from '@/components/common/EditableTextNoLable';
import SwitchPage from '@/components/common/SwitchPage';
import { BaseTable } from '@/components/table/BaseTable';
import { useI18n } from '@/providers/i18n/i18nContext';
import { useBackendStore } from '@/store/backend';
import { GroupInfo, GroupStatus } from '@/types/admin/group';
import { QueryKey } from '@/types/query-key';
export default function Home(): React.JSX.Element {
const { lng } = useI18n()
const { t } = useTranslationClientSide(lng, 'common')
const { currencySymbol } = useBackendStore()
const { lng } = useI18n();
const { t } = useTranslationClientSide(lng, 'common');
const { currencySymbol } = useBackendStore();
const { message } = useMessage({
warningBoxBg: '#FFFAEB',
warningIconBg: '#F79009',
@@ -44,15 +44,15 @@ export default function Home(): React.JSX.Element {
successBoxBg: '#EDFBF3',
successIconBg: '#039855',
successIconFill: 'white',
})
});
const queryClient = useQueryClient()
const queryClient = useQueryClient();
const [groupId, setGroupId] = useState('')
const [page, setPage] = useState(1)
const [pageSize, setPageSize] = useState(10)
const [groupData, setGroupData] = useState<GroupInfo[]>([])
const [total, setTotal] = useState(0)
const [groupId, setGroupId] = useState('');
const [page, setPage] = useState(1);
const [pageSize, setPageSize] = useState(10);
const [groupData, setGroupData] = useState<GroupInfo[]>([]);
const [total, setTotal] = useState(0);
const { isLoading } = useQuery(
[QueryKey.GetGroups, page, pageSize, groupId],
@@ -65,15 +65,15 @@ export default function Home(): React.JSX.Element {
{
onSuccess: (data) => {
if (!data?.groups) {
setGroupData([])
setTotal(0)
return
setGroupData([]);
setTotal(0);
return;
}
setGroupData(data?.groups || [])
setTotal(data?.total || 0)
setGroupData(data?.groups || []);
setTotal(data?.total || 0);
},
}
)
);
const deleteGroupMutation = useMutation(({ id }: { id: string }) => deleteGroup(id), {
onSuccess() {
@@ -83,8 +83,8 @@ export default function Home(): React.JSX.Element {
isClosable: true,
duration: 2000,
position: 'top',
})
queryClient.invalidateQueries([QueryKey.GetGroups])
});
queryClient.invalidateQueries([QueryKey.GetGroups]);
},
onError(err: any) {
message({
@@ -93,9 +93,9 @@ export default function Home(): React.JSX.Element {
description: err?.message || t('nsManager.deleteGroupFailed'),
isClosable: true,
position: 'top',
})
});
},
})
});
const updateGroupStatusMutation = useMutation(
({ id, status }: { id: string; status: number }) => updateGroupStatus(id, status),
@@ -107,8 +107,8 @@ export default function Home(): React.JSX.Element {
isClosable: true,
duration: 2000,
position: 'top',
})
queryClient.invalidateQueries([QueryKey.GetGroups])
});
queryClient.invalidateQueries([QueryKey.GetGroups]);
},
onError(err: any) {
message({
@@ -117,10 +117,10 @@ export default function Home(): React.JSX.Element {
description: err?.message || t('nsManager.updateGroupStatusFailed'),
isClosable: true,
position: 'top',
})
});
},
}
)
);
const updateGroupQpmMutation = useMutation(
({ id, qpm }: { id: string; qpm: number }) => updateGroupQpm(id, qpm),
@@ -132,8 +132,8 @@ export default function Home(): React.JSX.Element {
isClosable: true,
duration: 2000,
position: 'top',
})
queryClient.invalidateQueries([QueryKey.GetGroups])
});
queryClient.invalidateQueries([QueryKey.GetGroups]);
},
onError(err: any) {
message({
@@ -142,18 +142,18 @@ export default function Home(): React.JSX.Element {
description: err?.message || t('nsManager.updateGroupQpmFailed'),
isClosable: true,
position: 'top',
})
});
},
}
)
);
// Update the button click handlers in the table actions column:
const handleStatusUpdate = (id: string, currentStatus: number) => {
const newStatus =
currentStatus === GroupStatus.DISABLED ? GroupStatus.ENABLED : GroupStatus.DISABLED
updateGroupStatusMutation.mutate({ id, status: newStatus })
}
const columnHelper = createColumnHelper<GroupInfo>()
currentStatus === GroupStatus.DISABLED ? GroupStatus.ENABLED : GroupStatus.DISABLED;
updateGroupStatusMutation.mutate({ id, status: newStatus });
};
const columnHelper = createColumnHelper<GroupInfo>();
const columns = useMemo<ColumnDef<GroupInfo>[]>(() => {
return [
@@ -186,10 +186,10 @@ export default function Home(): React.JSX.Element {
header: t('nsManager.accessed_at'),
accessorFn: (row) => {
if (row.accessed_at && row.accessed_at < 0) {
return t('key.unused')
return t('key.unused');
}
return new Date(row.accessed_at).toLocaleString()
return new Date(row.accessed_at).toLocaleString();
},
id: 'accessed_at',
},
@@ -202,7 +202,7 @@ export default function Home(): React.JSX.Element {
accessorFn: (row) =>
row.status === GroupStatus.ENABLED ? t('nsManager.enabled') : t('nsManager.disabled'),
cell: ({ getValue }) => {
const value = getValue() as string
const value = getValue() as string;
return (
<Text
color={
@@ -218,7 +218,7 @@ export default function Home(): React.JSX.Element {
>
{value}
</Text>
)
);
},
id: 'status',
},
@@ -243,7 +243,7 @@ export default function Home(): React.JSX.Element {
<CurrencySymbol type={currencySymbol} />
</Flex>
</Box>
)
);
},
},
columnHelper.display({
@@ -409,14 +409,14 @@ export default function Home(): React.JSX.Element {
</Menu>
),
}),
]
}, [])
];
}, []);
const table = useReactTable({
data: groupData,
columns,
getCoreRowModel: getCoreRowModel(),
})
});
return (
<Flex
@@ -487,7 +487,7 @@ export default function Home(): React.JSX.Element {
bg="white"
boxShadow="0px 1px 2px 0px rgba(19, 51, 107, 0.05), 0px 0px 1px 0px rgba(19, 51, 107, 0.08)"
onClick={() => {
setGroupId('')
setGroupId('');
}}
>
<Icon
@@ -574,5 +574,5 @@ export default function Home(): React.JSX.Element {
{/* -- table end */}
</Flex>
</Flex>
)
);
}
@@ -1,52 +1,52 @@
import { useEffect, useRef } from 'react'
import { Box, Flex, Text } from '@chakra-ui/react'
import * as echarts from 'echarts'
import { useEffect, useRef } from 'react';
import { Box, Flex, Text } from '@chakra-ui/react';
import * as echarts from 'echarts';
import { useTranslationClientSide } from '@/app/i18n/client'
import { useI18n } from '@/providers/i18n/i18nContext'
import { useBackendStore } from '@/store/backend'
import { ChartDataItem } from '@/types/user/dashboard'
import { useTranslationClientSide } from '@/app/i18n/client';
import { useI18n } from '@/providers/i18n/i18nContext';
import { useBackendStore } from '@/store/backend';
import { ChartDataItem } from '@/types/user/dashboard';
export default function RequestDataChart({ data }: { data: ChartDataItem[] }): React.JSX.Element {
const costChartRef = useRef<HTMLDivElement>(null)
const requestChartRef = useRef<HTMLDivElement>(null)
const costChartInstance = useRef<echarts.ECharts>()
const requestChartInstance = useRef<echarts.ECharts>()
const { lng } = useI18n()
const { t } = useTranslationClientSide(lng, 'common')
const { currencySymbol } = useBackendStore()
const costChartRef = useRef<HTMLDivElement>(null);
const requestChartRef = useRef<HTMLDivElement>(null);
const costChartInstance = useRef<echarts.ECharts>();
const requestChartInstance = useRef<echarts.ECharts>();
const { lng } = useI18n();
const { t } = useTranslationClientSide(lng, 'common');
const { currencySymbol } = useBackendStore();
// Add helper function to determine date format
const getDateFormat = (timestamps: number[]) => {
if (timestamps.length < 2) return 'detailed'
if (timestamps.length < 2) return 'detailed';
const timeDiff = timestamps[timestamps.length - 1] - timestamps[0]
const timeDiff = timestamps[timestamps.length - 1] - timestamps[0];
// If time difference is more than 15 days (1296000 seconds), show daily format
return timeDiff > 1296000 ? 'daily' : 'detailed'
}
return timeDiff > 1296000 ? 'daily' : 'detailed';
};
// 初始化图表
useEffect(() => {
if (costChartRef.current && requestChartRef.current) {
costChartInstance.current = echarts.init(costChartRef.current, undefined, {
renderer: 'svg',
})
});
requestChartInstance.current = echarts.init(requestChartRef.current, undefined, {
renderer: 'svg',
})
});
}
return () => {
costChartInstance.current?.dispose()
requestChartInstance.current?.dispose()
costChartInstance.current = undefined
requestChartInstance.current = undefined
}
}, [])
costChartInstance.current?.dispose();
requestChartInstance.current?.dispose();
costChartInstance.current = undefined;
requestChartInstance.current = undefined;
};
}, []);
// 配置图表选项
useEffect(() => {
if (!costChartInstance.current || !requestChartInstance.current) return
if (!costChartInstance.current || !requestChartInstance.current) return;
const commonTooltipStyle: echarts.EChartsOption['tooltip'] = {
trigger: 'axis',
@@ -63,7 +63,7 @@ export default function RequestDataChart({ data }: { data: ChartDataItem[] }): R
color: '#111824',
fontSize: 12,
},
}
};
const commonXAxis: echarts.EChartsOption['xAxis'] = {
type: 'time',
@@ -94,8 +94,8 @@ export default function RequestDataChart({ data }: { data: ChartDataItem[] }): R
show: true,
color: '#667085',
formatter: (value: number) => {
const date = new Date(value * 1000)
const format = getDateFormat(data.map((item) => item.timestamp))
const date = new Date(value * 1000);
const format = getDateFormat(data.map((item) => item.timestamp));
return date
.toLocaleString(lng, {
@@ -107,12 +107,12 @@ export default function RequestDataChart({ data }: { data: ChartDataItem[] }): R
hour12: false,
}),
})
.replace(/\//g, '-')
.replace(/\//g, '-');
},
margin: 14,
align: 'left',
},
}
};
// 成本图表配置
const costOption: echarts.EChartsOption = {
@@ -123,22 +123,22 @@ export default function RequestDataChart({ data }: { data: ChartDataItem[] }): R
| echarts.DefaultLabelFormatterCallbackParams
| echarts.DefaultLabelFormatterCallbackParams[]
) {
if (!params) return ''
const paramArray = Array.isArray(params) ? params : [params]
if (paramArray.length === 0) return ''
if (!params) return '';
const paramArray = Array.isArray(params) ? params : [params];
if (paramArray.length === 0) return '';
const time = new Date((paramArray[0].value as [number, number])[0] * 1000)
const time = new Date((paramArray[0].value as [number, number])[0] * 1000);
const timeStr = time.toLocaleString(lng, {
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
})
});
let result = `
<div style="font-weight: 500; margin-bottom: 4px; margin-top: 4px; color: #667085; font-size: 12px">${timeStr}</div>
<div style="height: 1px; background: #DFE2EA; margin: 8px 0;"></div>
`
`;
const currency =
currencySymbol === 'shellCoin'
@@ -200,14 +200,14 @@ export default function RequestDataChart({ data }: { data: ChartDataItem[] }): R
</svg>`
: currencySymbol === 'cny'
? '¥'
: '$'
: '$';
paramArray.forEach((param) => {
const value = (param.value as [number, number])[1]
const value = (param.value as [number, number])[1];
const formattedValue = Number(value).toLocaleString(lng, {
minimumFractionDigits: 0,
maximumFractionDigits: 4,
})
});
result += `
<div style="display: flex; align-items: center; margin: 4px 0; min-width: 150px">
<div style="display: flex; align-items: center; flex: 1">
@@ -217,10 +217,10 @@ export default function RequestDataChart({ data }: { data: ChartDataItem[] }): R
</div>
<div style="font-weight: 500; color: #667085; font-size: 12px">${formattedValue}</div>
</div>
`
})
`;
});
return result
return result;
},
},
legend: {
@@ -269,32 +269,32 @@ export default function RequestDataChart({ data }: { data: ChartDataItem[] }): R
},
},
],
}
};
// 请求数图表配置
const requestOption: echarts.EChartsOption = {
tooltip: {
...commonTooltipStyle,
formatter: function (params) {
if (!params) return ''
const paramArray = Array.isArray(params) ? params : [params]
if (paramArray.length === 0) return ''
if (!params) return '';
const paramArray = Array.isArray(params) ? params : [params];
if (paramArray.length === 0) return '';
const time = new Date((paramArray[0].value as [number, number])[0] * 1000)
const time = new Date((paramArray[0].value as [number, number])[0] * 1000);
const timeStr = time.toLocaleString(lng, {
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
})
});
let result = `
<div style="font-weight: 500; margin-bottom: 4px; margin-top: 4px; color: #667085; font-size: 12px">${timeStr}</div>
<div style="height: 1px; background: #DFE2EA; margin: 8px 0;"></div>
`
`;
paramArray.forEach((param) => {
const value = (param.value as [number, number])[1]
const value = (param.value as [number, number])[1];
result += `
<div style="display: flex; align-items: center; margin: 4px 0; min-width: 150px">
<div style="display: flex; align-items: center; flex: 1">
@@ -303,10 +303,10 @@ export default function RequestDataChart({ data }: { data: ChartDataItem[] }): R
</div>
<div style="font-weight: 500; color: #667085; font-size: 12px">${value}</div>
</div>
`
})
`;
});
return result
return result;
},
},
legend: {
@@ -363,31 +363,31 @@ export default function RequestDataChart({ data }: { data: ChartDataItem[] }): R
},
},
],
}
};
// 设置图表选项
costChartInstance.current.setOption(costOption)
requestChartInstance.current.setOption(requestOption)
costChartInstance.current.setOption(costOption);
requestChartInstance.current.setOption(requestOption);
// 图表联动
costChartInstance.current.group = 'request-data'
requestChartInstance.current.group = 'request-data'
echarts.connect('request-data')
}, [data, t, lng])
costChartInstance.current.group = 'request-data';
requestChartInstance.current.group = 'request-data';
echarts.connect('request-data');
}, [data, t, lng]);
// 处理窗口大小变化
useEffect(() => {
const handleResize = () => {
costChartInstance.current?.resize()
requestChartInstance.current?.resize()
}
costChartInstance.current?.resize();
requestChartInstance.current?.resize();
};
window.addEventListener('resize', handleResize)
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize)
}
}, [])
window.removeEventListener('resize', handleResize);
};
}, []);
return (
<Box
@@ -435,5 +435,5 @@ export default function RequestDataChart({ data }: { data: ChartDataItem[] }): R
<Box ref={requestChartRef} w="full" h="full" position="relative" minH="160px" />
</Flex>
</Box>
)
);
}
@@ -1,27 +1,27 @@
'use client'
'use client';
import { useState } from 'react'
import { Box, Button, Center, Flex, Text } from '@chakra-ui/react'
import { CurrencySymbol, MySelect } from '@sealos/ui'
import { useQuery, UseQueryResult } from '@tanstack/react-query'
import { useState } from 'react';
import { Box, Button, Center, Flex, Text } from '@chakra-ui/react';
import { CurrencySymbol, MySelect } from '@sealos/ui';
import { useQuery, UseQueryResult } from '@tanstack/react-query';
import { getDashboardData } from '@/api/platform'
import { useTranslationClientSide } from '@/app/i18n/client'
import { useI18n } from '@/providers/i18n/i18nContext'
import { useBackendStore } from '@/store/backend'
import { QueryKey } from '@/types/query-key'
import { DashboardResponse } from '@/types/user/dashboard'
import { getDashboardData } from '@/api/platform';
import { useTranslationClientSide } from '@/app/i18n/client';
import { useI18n } from '@/providers/i18n/i18nContext';
import { useBackendStore } from '@/store/backend';
import { QueryKey } from '@/types/query-key';
import { DashboardResponse } from '@/types/user/dashboard';
import RequestDataChart from './components/RequestDataChart'
import RequestDataChart from './components/RequestDataChart';
export default function Home(): React.JSX.Element {
const { lng } = useI18n()
const { t } = useTranslationClientSide(lng, 'common')
const { currencySymbol } = useBackendStore()
const { lng } = useI18n();
const { t } = useTranslationClientSide(lng, 'common');
const { currencySymbol } = useBackendStore();
const [tokenName, setTokenName] = useState<string>('')
const [model, setModel] = useState<string>('')
const [type, setType] = useState<'week' | 'day' | 'two_week' | 'month'>('week') // default is week
const [tokenName, setTokenName] = useState<string>('');
const [model, setModel] = useState<string>('');
const [type, setType] = useState<'week' | 'day' | 'two_week' | 'month'>('week'); // default is week
const { data: dashboardData, isLoading }: UseQueryResult<DashboardResponse['data']> = useQuery(
[QueryKey.GetDashboardData, type, tokenName, model],
@@ -31,7 +31,7 @@ export default function Home(): React.JSX.Element {
...(tokenName && { token_name: tokenName }),
...(model && { model }),
})
)
);
return (
<Flex
@@ -98,9 +98,9 @@ export default function Home(): React.JSX.Element {
placeholder={t('dataDashboard.selectToken')}
onchange={(token: string) => {
if (token === 'all') {
setTokenName('')
setTokenName('');
} else {
setTokenName(token)
setTokenName(token);
}
}}
/>
@@ -125,9 +125,9 @@ export default function Home(): React.JSX.Element {
]}
onchange={(model: string) => {
if (model === 'all') {
setModel('')
setModel('');
} else {
setModel(model)
setModel(model);
}
}}
/>
@@ -734,5 +734,5 @@ export default function Home(): React.JSX.Element {
</Flex>
</Flex>
</Flex>
)
);
}
@@ -1,24 +1,24 @@
'use client'
'use client';
import { Button, Flex, Text } from '@chakra-ui/react'
import { Button, Flex, Text } from '@chakra-ui/react';
import { useTranslationClientSide } from '@/app/i18n/client'
import { MyTooltip } from '@/components/common/MyTooltip'
import KeyList from '@/components/user/KeyList'
import { useI18n } from '@/providers/i18n/i18nContext'
import { useBackendStore } from '@/store/backend'
import { useSessionStore } from '@/store/session'
import { useTranslationClientSide } from '@/app/i18n/client';
import { MyTooltip } from '@/components/common/MyTooltip';
import KeyList from '@/components/user/KeyList';
import { useI18n } from '@/providers/i18n/i18nContext';
import { useBackendStore } from '@/store/backend';
import { useSessionStore } from '@/store/session';
export default function Key(): JSX.Element {
const { isInvitationActive } = useBackendStore()
const { lng } = useI18n()
const { t } = useTranslationClientSide(lng, 'common')
const { session } = useSessionStore.getState()
const { invitationUrl } = useBackendStore()
let userInvitationUrl = ''
const { isInvitationActive } = useBackendStore();
const { lng } = useI18n();
const { t } = useTranslationClientSide(lng, 'common');
const { session } = useSessionStore.getState();
const { invitationUrl } = useBackendStore();
let userInvitationUrl = '';
if (isInvitationActive && invitationUrl) {
const userId = session?.user.id
const baseUrl = new URL(invitationUrl).origin
userInvitationUrl = `${baseUrl}/?uid=${userId}`
const userId = session?.user.id;
const baseUrl = new URL(invitationUrl).origin;
userInvitationUrl = `${baseUrl}/?uid=${userId}`;
}
return (
@@ -115,7 +115,7 @@ export default function Key(): JSX.Element {
textUnderlineOffset="auto"
cursor="pointer"
onClick={() => {
navigator.clipboard.writeText(userInvitationUrl)
navigator.clipboard.writeText(userInvitationUrl);
}}
>
{userInvitationUrl}
@@ -139,7 +139,7 @@ export default function Key(): JSX.Element {
bgGradient="linear(90deg, #1058FF 0%, #838AF1 57.5%, #FF80E6 100%)"
color="white"
onClick={() => {
window.open(invitationUrl, '_blank')
window.open(invitationUrl, '_blank');
}}
>
<Text
@@ -191,5 +191,5 @@ export default function Key(): JSX.Element {
</Flex>
)}
</Flex>
)
);
}
@@ -1,6 +1,6 @@
import { Box, Flex } from '@chakra-ui/react'
import { Box, Flex } from '@chakra-ui/react';
import SideBar from '@/components/user/Sidebar'
import SideBar from '@/components/user/Sidebar';
export default function UserLayout({ children }: { children: React.ReactNode }) {
return (
@@ -13,5 +13,5 @@ export default function UserLayout({ children }: { children: React.ReactNode })
{children}
</Box>
</Flex>
)
);
}
@@ -1,6 +1,6 @@
'use client'
'use client';
import ReactJson, { OnCopyProps } from 'react-json-view'
import ReactJson, { OnCopyProps } from 'react-json-view';
import {
Box,
Center,
@@ -14,45 +14,45 @@ import {
ModalOverlay,
Spinner,
Text,
} from '@chakra-ui/react'
import { CurrencySymbol } from '@sealos/ui'
import { useMessage } from '@sealos/ui'
import { useQuery } from '@tanstack/react-query'
} from '@chakra-ui/react';
import { CurrencySymbol } from '@sealos/ui';
import { useMessage } from '@sealos/ui';
import { useQuery } from '@tanstack/react-query';
import { getUserLogDetail } from '@/api/platform'
import { useTranslationClientSide } from '@/app/i18n/client'
import { MyTooltip } from '@/components/common/MyTooltip'
import { useI18n } from '@/providers/i18n/i18nContext'
import { useBackendStore } from '@/store/backend'
import { QueryKey } from '@/types/query-key'
import { LogItem } from '@/types/user/logs'
import { getTranslationWithFallback } from '@/utils/common'
import { getUserLogDetail } from '@/api/platform';
import { useTranslationClientSide } from '@/app/i18n/client';
import { MyTooltip } from '@/components/common/MyTooltip';
import { useI18n } from '@/providers/i18n/i18nContext';
import { useBackendStore } from '@/store/backend';
import { QueryKey } from '@/types/query-key';
import { LogItem } from '@/types/user/logs';
import { getTranslationWithFallback } from '@/utils/common';
import { getTimeDiff } from '../tools/handleTime'
import { getTimeDiff } from '../tools/handleTime';
export default function LogDetailModal({
isOpen,
onClose,
rowData,
}: {
isOpen: boolean
onClose: () => void
rowData: LogItem | null
isOpen: boolean;
onClose: () => void;
rowData: LogItem | null;
}): React.JSX.Element {
const { lng } = useI18n()
const { t } = useTranslationClientSide(lng, 'common')
const { currencySymbol } = useBackendStore()
const { lng } = useI18n();
const { t } = useTranslationClientSide(lng, 'common');
const { currencySymbol } = useBackendStore();
const { data: logDetail, isLoading } = useQuery({
queryKey: [QueryKey.GetUserLogDetail, rowData?.request_detail?.log_id],
queryFn: () => {
if (!rowData?.request_detail?.log_id) throw new Error('No log ID')
return getUserLogDetail(rowData.request_detail.log_id)
if (!rowData?.request_detail?.log_id) throw new Error('No log ID');
return getUserLogDetail(rowData.request_detail.log_id);
},
enabled: !!rowData?.request_detail?.log_id,
})
});
const isDetailLoading = !!rowData?.request_detail?.log_id && isLoading
const isDetailLoading = !!rowData?.request_detail?.log_id && isLoading;
const { message } = useMessage({
warningBoxBg: '#FFFAEB',
@@ -61,14 +61,14 @@ export default function LogDetailModal({
successBoxBg: '#EDFBF3',
successIconBg: '#039855',
successIconFill: 'white',
})
});
// 定义默认的网格配置
const gridConfig = {
labelWidth: '153px',
rowHeight: '48px',
jsonContentHeight: '122px',
}
};
const renderDetailRow = (
leftLabel: string | React.ReactNode | null,
@@ -76,15 +76,15 @@ export default function LogDetailModal({
rightLabel?: string | React.ReactNode | null,
rightValue?: string | number | React.ReactNode | undefined,
options?: {
labelWidth?: string
rowHeight?: string
isFirst?: boolean
isLast?: boolean
labelWidth?: string;
rowHeight?: string;
isFirst?: boolean;
isLast?: boolean;
}
) => {
// 辅助函数:渲染标签
const renderLabel = (label: string | React.ReactNode | null) => {
if (label === null) return null
if (label === null) return null;
if (typeof label === 'string') {
return (
<Text
@@ -96,10 +96,10 @@ export default function LogDetailModal({
>
{label}
</Text>
)
);
}
return label
}
return label;
};
// 辅助函数:渲染值
const renderValue = (value: string | number | React.ReactNode | undefined) => {
@@ -115,17 +115,17 @@ export default function LogDetailModal({
>
{value}
</Text>
)
);
}
return value
}
return value;
};
// 判断是否显示左侧或右侧列
const showLeftSection = leftLabel !== null && leftLabel !== undefined
const showRightSection = rightLabel !== null && rightLabel !== undefined
const showLeftSection = leftLabel !== null && leftLabel !== undefined;
const showRightSection = rightLabel !== null && rightLabel !== undefined;
// 根据显示的列数设置模板
const gridTemplateColumns = showLeftSection && showRightSection ? '1fr 1fr' : '1fr'
const gridTemplateColumns = showLeftSection && showRightSection ? '1fr 1fr' : '1fr';
return (
<Grid
@@ -206,35 +206,35 @@ export default function LogDetailModal({
</Grid>
)}
</Grid>
)
}
);
};
const renderJsonContent = (
label: string,
content: string | undefined,
options?: {
labelWidth?: string
contentHeight?: string
isFirst?: boolean
isLast?: boolean
labelWidth?: string;
contentHeight?: string;
isFirst?: boolean;
isLast?: boolean;
}
) => {
if (!content) return null
if (!content) return null;
const handleCopy = (copy: OnCopyProps) => {
if (typeof window === 'undefined') return
if (typeof window === 'undefined') return;
const copyText =
typeof copy.src === 'object' ? JSON.stringify(copy.src, null, 2) : String(copy.src)
typeof copy.src === 'object' ? JSON.stringify(copy.src, null, 2) : String(copy.src);
navigator.clipboard.writeText(copyText)
}
navigator.clipboard.writeText(copyText);
};
let parsed = null
let parsed = null;
try {
parsed = JSON.parse(content)
parsed = JSON.parse(content);
} catch {
parsed = content
parsed = content;
}
return (
<Grid
@@ -325,8 +325,8 @@ export default function LogDetailModal({
)}
</Box>
</Grid>
)
}
);
};
return isDetailLoading ? (
<Modal isOpen={isOpen} onClose={onClose} isCentered>
@@ -522,7 +522,7 @@ export default function LogDetailModal({
isClosable: true,
duration: 2000,
position: 'top',
})
});
},
(err) => {
message({
@@ -531,9 +531,9 @@ export default function LogDetailModal({
description: err?.message || t('copyFailed'),
isClosable: true,
position: 'top',
})
});
}
)
);
}}
>
{rowData.content}
@@ -771,5 +771,5 @@ export default function LogDetailModal({
</ModalBody>
</ModalContent>
</Modal>
)
);
}
@@ -1,6 +1,6 @@
'use client'
'use client';
import { useMemo, useState } from 'react'
import { useMemo, useState } from 'react';
import {
Box,
Button,
@@ -11,51 +11,51 @@ import {
InputRightElement,
Text,
useDisclosure,
} from '@chakra-ui/react'
import { CurrencySymbol, MySelect, MyTooltip } from '@sealos/ui'
import { useQuery } from '@tanstack/react-query'
import { createColumnHelper, getCoreRowModel, useReactTable } from '@tanstack/react-table'
import dynamic from 'next/dynamic'
} from '@chakra-ui/react';
import { CurrencySymbol, MySelect, MyTooltip } from '@sealos/ui';
import { useQuery } from '@tanstack/react-query';
import { createColumnHelper, getCoreRowModel, useReactTable } from '@tanstack/react-table';
import dynamic from 'next/dynamic';
import { getEnabledMode, getTokens, getUserLogs } from '@/api/platform'
import { useTranslationClientSide } from '@/app/i18n/client'
import SelectDateRange from '@/components/common/SelectDateRange'
import { SingleSelectComboboxUnstyle } from '@/components/common/SingleSelectComboboxUnStyle'
import SwitchPage from '@/components/common/SwitchPage'
import { BaseTable } from '@/components/table/BaseTable'
import { useDebounce } from '@/hooks/useDebounce'
import { useI18n } from '@/providers/i18n/i18nContext'
import { useBackendStore } from '@/store/backend'
import { QueryKey } from '@/types/query-key'
import { LogItem } from '@/types/user/logs'
import { getEnabledMode, getTokens, getUserLogs } from '@/api/platform';
import { useTranslationClientSide } from '@/app/i18n/client';
import SelectDateRange from '@/components/common/SelectDateRange';
import { SingleSelectComboboxUnstyle } from '@/components/common/SingleSelectComboboxUnStyle';
import SwitchPage from '@/components/common/SwitchPage';
import { BaseTable } from '@/components/table/BaseTable';
import { useDebounce } from '@/hooks/useDebounce';
import { useI18n } from '@/providers/i18n/i18nContext';
import { useBackendStore } from '@/store/backend';
import { QueryKey } from '@/types/query-key';
import { LogItem } from '@/types/user/logs';
import { getTimeDiff } from './tools/handleTime'
import { getTimeDiff } from './tools/handleTime';
const LogDetailModal = dynamic(
() => import('./components/LogDetailModal'),
{ ssr: false } // 禁用服务端渲染
)
);
export default function Logs(): React.JSX.Element {
const { lng } = useI18n()
const { t } = useTranslationClientSide(lng, 'common')
const { currencySymbol } = useBackendStore()
const { isOpen, onOpen, onClose } = useDisclosure()
const [selectedRow, setSelectedRow] = useState<LogItem | null>(null)
const { lng } = useI18n();
const { t } = useTranslationClientSide(lng, 'common');
const { currencySymbol } = useBackendStore();
const { isOpen, onOpen, onClose } = useDisclosure();
const [selectedRow, setSelectedRow] = useState<LogItem | null>(null);
const [startTime, setStartTime] = useState(() => {
const currentDate = new Date()
currentDate.setDate(currentDate.getDate() - 3)
return currentDate
})
const [endTime, setEndTime] = useState(new Date())
const [keyName, setKeyName] = useState('')
const [codeType, setCodeType] = useState('all')
const [modelName, setModelName] = useState('')
const [page, setPage] = useState(1)
const [pageSize, setPageSize] = useState(10)
const [inputKeyword, setInputKeyword] = useState('')
const debouncedKeyword = useDebounce(inputKeyword, 500) // 500ms 延迟 0.5s
const currentDate = new Date();
currentDate.setDate(currentDate.getDate() - 3);
return currentDate;
});
const [endTime, setEndTime] = useState(new Date());
const [keyName, setKeyName] = useState('');
const [codeType, setCodeType] = useState('all');
const [modelName, setModelName] = useState('');
const [page, setPage] = useState(1);
const [pageSize, setPageSize] = useState(10);
const [inputKeyword, setInputKeyword] = useState('');
const debouncedKeyword = useDebounce(inputKeyword, 500); // 500ms 延迟 0.5s
const { data: logData, isLoading } = useQuery(
[
@@ -80,9 +80,9 @@ export default function Logs(): React.JSX.Element {
start_timestamp: startTime.getTime().toString(),
end_timestamp: endTime.getTime().toString(),
})
)
);
const columnHelper = createColumnHelper<LogItem>()
const columnHelper = createColumnHelper<LogItem>();
const columns = useMemo(
() => [
@@ -189,7 +189,7 @@ export default function Logs(): React.JSX.Element {
</Text>
),
cell: ({ getValue, row }) => {
const code = getValue()
const code = getValue();
return (
<Flex alignItems="center" gap="4px" w="fit-content">
<Text
@@ -221,7 +221,7 @@ export default function Logs(): React.JSX.Element {
</MyTooltip>
)}
</Flex>
)
);
},
id: 'status',
}),
@@ -256,7 +256,7 @@ export default function Logs(): React.JSX.Element {
</Flex>
</MyTooltip>
</Box>
)
);
},
id: 'used_amount',
}),
@@ -277,8 +277,8 @@ export default function Logs(): React.JSX.Element {
cell: ({ row }) => (
<Button
onClick={() => {
setSelectedRow(row.original)
onOpen()
setSelectedRow(row.original);
onOpen();
}}
h="28px"
variant="unstyled"
@@ -338,13 +338,13 @@ export default function Logs(): React.JSX.Element {
}),
],
[t, currencySymbol]
)
);
const table = useReactTable({
data: logData?.logs || [],
columns,
getCoreRowModel: getCoreRowModel(),
})
});
return (
<Flex
@@ -414,7 +414,7 @@ export default function Logs(): React.JSX.Element {
}}
value={inputKeyword}
onChange={(e) => {
setInputKeyword(e.target.value)
setInputKeyword(e.target.value);
}}
/>
<InputRightElement w="32px" h="32px">
@@ -472,9 +472,9 @@ export default function Logs(): React.JSX.Element {
bg="white"
boxShadow="0px 1px 2px 0px rgba(19, 51, 107, 0.05), 0px 0px 1px 0px rgba(19, 51, 107, 0.08)"
onClick={() => {
setKeyName('')
setModelName('')
setCodeType('all')
setKeyName('');
setModelName('');
setCodeType('all');
}}
>
<Icon
@@ -524,16 +524,16 @@ export default function Logs(): React.JSX.Element {
dropdownItems={['all', ...(logData?.token_names || [])]}
setSelectedItem={(value) => {
if (value === 'all') {
setKeyName('')
setKeyName('');
} else {
setKeyName(value)
setKeyName(value);
}
}}
handleDropdownItemFilter={(dropdownItems, inputValue) => {
const lowerCasedInput = inputValue.toLowerCase()
const lowerCasedInput = inputValue.toLowerCase();
return dropdownItems.filter(
(item) => !inputValue || item.toLowerCase().includes(lowerCasedInput)
)
);
}}
handleDropdownItemDisplay={(dropdownItem) => {
return (
@@ -548,7 +548,7 @@ export default function Logs(): React.JSX.Element {
>
{dropdownItem}
</Text>
)
);
}}
flexProps={{ w: '280px' }}
placeholder={t('logs.select_token_name')}
@@ -572,16 +572,16 @@ export default function Logs(): React.JSX.Element {
dropdownItems={['all', ...(logData?.models || [])]}
setSelectedItem={(value) => {
if (value === 'all') {
setModelName('')
setModelName('');
} else {
setModelName(value)
setModelName(value);
}
}}
handleDropdownItemFilter={(dropdownItems, inputValue) => {
const lowerCasedInput = inputValue.toLowerCase()
const lowerCasedInput = inputValue.toLowerCase();
return dropdownItems.filter(
(item) => !inputValue || item.toLowerCase().includes(lowerCasedInput)
)
);
}}
handleDropdownItemDisplay={(dropdownItem) => {
return (
@@ -596,7 +596,7 @@ export default function Logs(): React.JSX.Element {
>
{dropdownItem}
</Text>
)
);
}}
flexProps={{ w: '280px' }}
placeholder={t('logs.select_modal')}
@@ -639,7 +639,7 @@ export default function Logs(): React.JSX.Element {
},
]}
onchange={(val: string) => {
setCodeType(val)
setCodeType(val);
}}
/>
</Flex>
@@ -688,5 +688,5 @@ export default function Logs(): React.JSX.Element {
</Flex>
<LogDetailModal isOpen={isOpen} onClose={onClose} rowData={selectedRow} />
</Flex>
)
);
}
@@ -1,32 +1,32 @@
'use client'
import { Badge, Box, Center, Flex, Icon, Skeleton, SkeletonText, Text } from '@chakra-ui/react'
import { useQuery } from '@tanstack/react-query'
import { useParams, useRouter } from 'next/navigation'
'use client';
import { Badge, Box, Center, Flex, Icon, Skeleton, SkeletonText, Text } from '@chakra-ui/react';
import { useQuery } from '@tanstack/react-query';
import { useParams, useRouter } from 'next/navigation';
import { getMcpDetail } from '@/api/platform'
import { useTranslationClientSide } from '@/app/i18n/client'
import { useI18n } from '@/providers/i18n/i18nContext'
import { QueryKey } from '@/types/query-key'
import { getMcpDetail } from '@/api/platform';
import { useTranslationClientSide } from '@/app/i18n/client';
import { useI18n } from '@/providers/i18n/i18nContext';
import { QueryKey } from '@/types/query-key';
import McpDetailBody from '../components/McpDetailBody'
import McpDetailHeader from '../components/McpDetailHeader'
import McpDetailBody from '../components/McpDetailBody';
import McpDetailHeader from '../components/McpDetailHeader';
export default function McpDetailPage() {
const { lng } = useI18n()
const { t } = useTranslationClientSide(lng, 'common')
const params = useParams()
const router = useRouter()
const id = params.id as string
const { lng } = useI18n();
const { t } = useTranslationClientSide(lng, 'common');
const params = useParams();
const router = useRouter();
const id = params.id as string;
const { data: mcpDetail, isLoading } = useQuery({
queryKey: [QueryKey.mcpDetail, id],
queryFn: () => getMcpDetail(id),
enabled: !!id,
})
});
const handleNavigateToHub = () => {
router.push(`/${lng}/mcp-hub`)
}
router.push(`/${lng}/mcp-hub`);
};
if (isLoading) {
return (
@@ -115,7 +115,7 @@ export default function McpDetailPage() {
</Flex>
</Box>
</Box>
)
);
}
if (!mcpDetail) {
@@ -127,7 +127,7 @@ export default function McpDetailPage() {
</Text>
</Box>
</Center>
)
);
}
return (
@@ -205,5 +205,5 @@ export default function McpDetailPage() {
</Flex>
</Box>
</Box>
)
);
}
@@ -1,29 +1,29 @@
'use client'
import { Badge, Box, Flex, Icon, Text } from '@chakra-ui/react'
import Image from 'next/image'
import { useRouter } from 'next/navigation'
'use client';
import { Badge, Box, Flex, Icon, Text } from '@chakra-ui/react';
import Image from 'next/image';
import { useRouter } from 'next/navigation';
import { useTranslationClientSide } from '@/app/i18n/client'
import { useI18n } from '@/providers/i18n/i18nContext'
import { Mcp } from '@/types/mcp'
import { getMcpIcon } from '@/ui/icons/mcp-icons'
import { useTranslationClientSide } from '@/app/i18n/client';
import { useI18n } from '@/providers/i18n/i18nContext';
import { Mcp } from '@/types/mcp';
import { getMcpIcon } from '@/ui/icons/mcp-icons';
export interface McpCardProps {
mcp: Mcp
mcp: Mcp;
}
export default function McpCard({ mcp }: McpCardProps) {
const { lng } = useI18n()
const { t } = useTranslationClientSide(lng, 'common')
const router = useRouter()
const { lng } = useI18n();
const { t } = useTranslationClientSide(lng, 'common');
const router = useRouter();
const mcpName = lng === 'zh' ? mcp.name_cn || mcp.name : mcp.name
const mcpDescription = lng === 'zh' ? mcp.description_cn || mcp.description : mcp.description
const mcpName = lng === 'zh' ? mcp.name_cn || mcp.name : mcp.name;
const mcpDescription = lng === 'zh' ? mcp.description_cn || mcp.description : mcp.description;
const handleCardClick = () => {
router.push(`/${lng}/mcp-hub/${mcp.id}`)
}
const iconSrc = getMcpIcon(mcp.id)
router.push(`/${lng}/mcp-hub/${mcp.id}`);
};
const iconSrc = getMcpIcon(mcp.id);
return (
<Box
bg="white"
@@ -132,5 +132,5 @@ export default function McpCard({ mcp }: McpCardProps) {
</Flex>
</Flex>
</Box>
)
);
}
@@ -1,4 +1,4 @@
'use client'
'use client';
import {
Alert,
AlertIcon,
@@ -12,33 +12,33 @@ import {
TabPanels,
Tabs,
Text,
} from '@chakra-ui/react'
import { useMessage } from '@sealos/ui'
import { useRouter } from 'next/navigation'
} from '@chakra-ui/react';
import { useMessage } from '@sealos/ui';
import { useRouter } from 'next/navigation';
import { useTranslationClientSide } from '@/app/i18n/client'
import { useI18n } from '@/providers/i18n/i18nContext'
import { McpDetail } from '@/types/mcp'
import { useTranslationClientSide } from '@/app/i18n/client';
import { useI18n } from '@/providers/i18n/i18nContext';
import { McpDetail } from '@/types/mcp';
export interface McpConnectionInfoProps {
mcpDetail: McpDetail
setViewMode?: () => void
mcpDetail: McpDetail;
setViewMode?: () => void;
}
// 自定义配置渲染组件
const ConfigRenderer = ({ config, lng }: { config: any; lng: string }) => {
const router = useRouter()
const router = useRouter();
const handleTokenClick = () => {
router.push(`/${lng}/key`)
}
router.push(`/${lng}/key`);
};
const renderConfigContent = () => {
const configString = JSON.stringify(config, null, 2)
const parts = configString.split('your_token')
const configString = JSON.stringify(config, null, 2);
const parts = configString.split('your_token');
if (parts.length === 1) {
return <span>{configString}</span>
return <span>{configString}</span>;
}
return (
@@ -61,8 +61,8 @@ const ConfigRenderer = ({ config, lng }: { config: any; lng: string }) => {
</span>
))}
</>
)
}
);
};
return (
<Code
@@ -77,20 +77,20 @@ const ConfigRenderer = ({ config, lng }: { config: any; lng: string }) => {
>
{renderConfigContent()}
</Code>
)
}
);
};
export default function McpConnectionInfo({ mcpDetail, setViewMode }: McpConnectionInfoProps) {
const { lng } = useI18n()
const { t } = useTranslationClientSide(lng, 'common')
const { message } = useMessage()
const { lng } = useI18n();
const { t } = useTranslationClientSide(lng, 'common');
const { message } = useMessage();
const hasSSE = mcpDetail.endpoints?.sse
const hasHTTP = mcpDetail.endpoints?.streamable_http
const hasSSE = mcpDetail.endpoints?.sse;
const hasHTTP = mcpDetail.endpoints?.streamable_http;
const generateConfig = (type: 'sse' | 'streamable_http') => {
const endpoint = type === 'sse' ? mcpDetail.endpoints.sse : mcpDetail.endpoints.streamable_http
const url = `https://${mcpDetail.endpoints.host}${endpoint}?key=your_token`
const endpoint = type === 'sse' ? mcpDetail.endpoints.sse : mcpDetail.endpoints.streamable_http;
const url = `https://${mcpDetail.endpoints.host}${endpoint}?key=your_token`;
return {
mcpServers: {
@@ -99,11 +99,11 @@ export default function McpConnectionInfo({ mcpDetail, setViewMode }: McpConnect
url: url,
},
},
}
}
};
};
const copyToClipboard = (config: object) => {
const configString = JSON.stringify(config, null, 2)
const configString = JSON.stringify(config, null, 2);
navigator.clipboard
.writeText(configString)
.then(() => {
@@ -111,20 +111,20 @@ export default function McpConnectionInfo({ mcpDetail, setViewMode }: McpConnect
status: 'success',
title: t('copySuccess'),
duration: 2000,
})
});
})
.catch(() => {
message({
status: 'error',
title: t('copyFailed'),
duration: 2000,
})
})
}
});
});
};
// 如果只有一种连接方式,就不显示Tab
if (hasSSE && !hasHTTP) {
const config = generateConfig('sse')
const config = generateConfig('sse');
return (
<Box h="full" overflow="hidden">
<Flex direction="column" gap="16px" h="full">
@@ -150,11 +150,11 @@ export default function McpConnectionInfo({ mcpDetail, setViewMode }: McpConnect
</Box>
</Flex>
</Box>
)
);
}
if (!hasSSE && hasHTTP) {
const config = generateConfig('streamable_http')
const config = generateConfig('streamable_http');
return (
<Box h="full" overflow="hidden">
<Flex direction="column" gap="16px" h="full">
@@ -180,7 +180,7 @@ export default function McpConnectionInfo({ mcpDetail, setViewMode }: McpConnect
</Box>
</Flex>
</Box>
)
);
}
// 有两种连接方式,显示Tab
@@ -254,5 +254,5 @@ export default function McpConnectionInfo({ mcpDetail, setViewMode }: McpConnect
</TabPanels>
</Tabs>
</Box>
)
);
}
@@ -1,13 +1,13 @@
'use client'
import { Box, Flex } from '@chakra-ui/react'
'use client';
import { Box, Flex } from '@chakra-ui/react';
import { McpDetail } from '@/types/mcp'
import { McpDetail } from '@/types/mcp';
import McpParamsConfig from './McpParamsConfig'
import McpReadme from './McpReadme'
import McpParamsConfig from './McpParamsConfig';
import McpReadme from './McpReadme';
export interface McpDetailBodyProps {
mcpDetail: McpDetail
mcpDetail: McpDetail;
}
export default function McpDetailBody({ mcpDetail }: McpDetailBodyProps) {
@@ -27,5 +27,5 @@ export default function McpDetailBody({ mcpDetail }: McpDetailBodyProps) {
</Box>
)}
</Flex>
)
);
}
@@ -1,25 +1,25 @@
'use client'
import { Badge, Box, Flex, IconButton, Text } from '@chakra-ui/react'
import Image from 'next/image'
'use client';
import { Badge, Box, Flex, IconButton, Text } from '@chakra-ui/react';
import Image from 'next/image';
import { useTranslationClientSide } from '@/app/i18n/client'
import { useI18n } from '@/providers/i18n/i18nContext'
import { McpDetail } from '@/types/mcp'
import { getMcpIcon } from '@/ui/icons/mcp-icons'
import { useTranslationClientSide } from '@/app/i18n/client';
import { useI18n } from '@/providers/i18n/i18nContext';
import { McpDetail } from '@/types/mcp';
import { getMcpIcon } from '@/ui/icons/mcp-icons';
export interface McpDetailHeaderProps {
mcpDetail: McpDetail
mcpDetail: McpDetail;
}
export default function McpDetailHeader({ mcpDetail }: McpDetailHeaderProps) {
const { lng } = useI18n()
const { t } = useTranslationClientSide(lng, 'common')
const { lng } = useI18n();
const { t } = useTranslationClientSide(lng, 'common');
const mcpName = lng === 'zh' ? mcpDetail.name_cn || mcpDetail.name : mcpDetail.name
const mcpName = lng === 'zh' ? mcpDetail.name_cn || mcpDetail.name : mcpDetail.name;
const mcpDescription =
lng === 'zh' ? mcpDetail.description_cn || mcpDetail.description : mcpDetail.description
lng === 'zh' ? mcpDetail.description_cn || mcpDetail.description : mcpDetail.description;
const iconSrc = getMcpIcon(mcpDetail.id)
const iconSrc = getMcpIcon(mcpDetail.id);
return (
<Flex direction="column" gap="16px">
@@ -100,5 +100,5 @@ export default function McpDetailHeader({ mcpDetail }: McpDetailHeaderProps) {
</Flex>
</Flex>
</Flex>
)
);
}
@@ -1,15 +1,15 @@
'use client'
import { Box, Center, Grid, Skeleton, SkeletonText, Text } from '@chakra-ui/react'
'use client';
import { Box, Center, Grid, Skeleton, SkeletonText, Text } from '@chakra-ui/react';
import { useTranslationClientSide } from '@/app/i18n/client'
import { useI18n } from '@/providers/i18n/i18nContext'
import { Mcp } from '@/types/mcp'
import { useTranslationClientSide } from '@/app/i18n/client';
import { useI18n } from '@/providers/i18n/i18nContext';
import { Mcp } from '@/types/mcp';
import McpCard from './McpCard'
import McpCard from './McpCard';
export interface McpListProps {
mcps: Mcp[]
isLoading: boolean
mcps: Mcp[];
isLoading: boolean;
}
// Skeleton Card Component
@@ -39,12 +39,12 @@ function McpCardSkeleton() {
</Box>
</Box>
</Box>
)
);
}
export default function McpList({ mcps, isLoading }: McpListProps) {
const { lng } = useI18n()
const { t } = useTranslationClientSide(lng, 'common')
const { lng } = useI18n();
const { t } = useTranslationClientSide(lng, 'common');
if (isLoading) {
return (
@@ -61,7 +61,7 @@ export default function McpList({ mcps, isLoading }: McpListProps) {
<McpCardSkeleton key={index} />
))}
</Grid>
)
);
}
if (mcps.length === 0) {
@@ -73,7 +73,7 @@ export default function McpList({ mcps, isLoading }: McpListProps) {
</Text>
</Box>
</Center>
)
);
}
return (
@@ -90,5 +90,5 @@ export default function McpList({ mcps, isLoading }: McpListProps) {
<McpCard key={mcp.id} mcp={mcp} />
))}
</Grid>
)
);
}
@@ -1,28 +1,28 @@
'use client'
import { useState } from 'react'
import { Box, Flex, Text } from '@chakra-ui/react'
'use client';
import { useState } from 'react';
import { Box, Flex, Text } from '@chakra-ui/react';
import { useTranslationClientSide } from '@/app/i18n/client'
import { useI18n } from '@/providers/i18n/i18nContext'
import { McpDetail } from '@/types/mcp'
import { useTranslationClientSide } from '@/app/i18n/client';
import { useI18n } from '@/providers/i18n/i18nContext';
import { McpDetail } from '@/types/mcp';
import McpConnectionInfo from './McpConnectionInfo'
import McpParamsForm from './McpParamsForm'
import McpConnectionInfo from './McpConnectionInfo';
import McpParamsForm from './McpParamsForm';
export interface McpParamsConfigProps {
mcpDetail: McpDetail
mcpDetail: McpDetail;
}
type ViewMode = 'info' | 'config'
type ViewMode = 'info' | 'config';
export default function McpParamsConfig({ mcpDetail }: McpParamsConfigProps) {
const { lng } = useI18n()
const { t } = useTranslationClientSide(lng, 'common')
const [viewMode, setViewMode] = useState<ViewMode>('info')
const { lng } = useI18n();
const { t } = useTranslationClientSide(lng, 'common');
const [viewMode, setViewMode] = useState<ViewMode>('info');
// 检查是否有连接信息
const hasConnectionInfo = !!mcpDetail.endpoints?.host
const hasReusing = Object.keys(mcpDetail.reusing || {}).length > 0
const hasConnectionInfo = !!mcpDetail.endpoints?.host;
const hasReusing = Object.keys(mcpDetail.reusing || {}).length > 0;
return (
<Box minH="0px" overflow="hidden" bg="grayModern.25" borderRadius="8px" p="20px">
@@ -56,5 +56,5 @@ export default function McpParamsConfig({ mcpDetail }: McpParamsConfigProps) {
</Box>
</Flex>
</Box>
)
);
}
@@ -1,5 +1,5 @@
'use client'
import { Dispatch, SetStateAction, useState } from 'react'
'use client';
import { Dispatch, SetStateAction, useState } from 'react';
import {
Badge,
Box,
@@ -11,20 +11,20 @@ import {
Input,
Text,
VStack,
} from '@chakra-ui/react'
import { useMessage } from '@sealos/ui'
import { useMutation, useQueryClient } from '@tanstack/react-query'
} from '@chakra-ui/react';
import { useMessage } from '@sealos/ui';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { updateMcpParams } from '@/api/platform'
import { useTranslationClientSide } from '@/app/i18n/client'
import { useI18n } from '@/providers/i18n/i18nContext'
import { McpDetail } from '@/types/mcp'
import { QueryKey } from '@/types/query-key'
import { updateMcpParams } from '@/api/platform';
import { useTranslationClientSide } from '@/app/i18n/client';
import { useI18n } from '@/providers/i18n/i18nContext';
import { McpDetail } from '@/types/mcp';
import { QueryKey } from '@/types/query-key';
export interface McpParamsFormProps {
mcpDetail: McpDetail
showBackButton?: boolean
setViewMode?: Dispatch<SetStateAction<'info' | 'config'>>
mcpDetail: McpDetail;
showBackButton?: boolean;
setViewMode?: Dispatch<SetStateAction<'info' | 'config'>>;
}
export default function McpParamsForm({
@@ -32,21 +32,21 @@ export default function McpParamsForm({
showBackButton,
setViewMode,
}: McpParamsFormProps) {
const { lng } = useI18n()
const { t } = useTranslationClientSide(lng, 'common')
const { message } = useMessage()
const queryClient = useQueryClient()
const { lng } = useI18n();
const { t } = useTranslationClientSide(lng, 'common');
const { message } = useMessage();
const queryClient = useQueryClient();
// 初始化表单数据
const [formData, setFormData] = useState<Record<string, string>>(() => {
const initialData: Record<string, string> = {}
const initialData: Record<string, string> = {};
Object.keys(mcpDetail.reusing || {}).forEach((key) => {
initialData[key] = mcpDetail.params?.[key] || ''
})
return initialData
})
initialData[key] = mcpDetail.params?.[key] || '';
});
return initialData;
});
const [errors, setErrors] = useState<Record<string, string>>({})
const [errors, setErrors] = useState<Record<string, string>>({});
const updateParamsMutation = useMutation(
(params: Record<string, string>) => updateMcpParams(mcpDetail.id, params),
@@ -56,9 +56,9 @@ export default function McpParamsForm({
status: 'success',
title: t('mcpHub.paramsSaved'),
duration: 2000,
})
queryClient.invalidateQueries([QueryKey.mcpDetail, mcpDetail.id])
setViewMode?.('info')
});
queryClient.invalidateQueries([QueryKey.mcpDetail, mcpDetail.id]);
setViewMode?.('info');
},
onError: (error: any) => {
message({
@@ -66,49 +66,49 @@ export default function McpParamsForm({
title: t('mcpHub.paramsSaveFailed'),
description: error?.message || t('mcpHub.paramsSaveFailed'),
duration: 3000,
})
});
},
}
)
);
const handleInputChange = (key: string, value: string) => {
setFormData((prev) => ({ ...prev, [key]: value }))
setFormData((prev) => ({ ...prev, [key]: value }));
// 清除错误
if (errors[key]) {
setErrors((prev) => ({ ...prev, [key]: '' }))
setErrors((prev) => ({ ...prev, [key]: '' }));
}
}
};
const validateForm = () => {
const newErrors: Record<string, string> = {}
const newErrors: Record<string, string> = {};
Object.entries(mcpDetail.reusing || {}).forEach(([key, config]) => {
if (config.required && !formData[key]?.trim()) {
newErrors[key] = t('mcpHub.required')
newErrors[key] = t('mcpHub.required');
}
})
});
setErrors(newErrors)
return Object.keys(newErrors).length === 0
}
setErrors(newErrors);
return Object.keys(newErrors).length === 0;
};
const handleSubmit = () => {
if (!validateForm()) {
return
return;
}
// 只提交有值的参数
const paramsToSubmit: Record<string, string> = {}
const paramsToSubmit: Record<string, string> = {};
Object.entries(formData).forEach(([key, value]) => {
if (value.trim()) {
paramsToSubmit[key] = value.trim()
paramsToSubmit[key] = value.trim();
}
})
});
updateParamsMutation.mutate(paramsToSubmit)
}
updateParamsMutation.mutate(paramsToSubmit);
};
const reusingParams = mcpDetail.reusing || {}
const reusingParams = mcpDetail.reusing || {};
return (
<Flex flex="1" direction="column" h="full" justifyContent="space-between">
@@ -174,5 +174,5 @@ export default function McpParamsForm({
</Button>
</Flex>
</Flex>
)
);
}
@@ -1,21 +1,21 @@
'use client'
import Markdown from 'react-markdown'
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'
import { oneLight } from 'react-syntax-highlighter/dist/esm/styles/prism'
import { Box, Text } from '@chakra-ui/react'
import remarkGfm from 'remark-gfm'
'use client';
import Markdown from 'react-markdown';
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
import { oneLight } from 'react-syntax-highlighter/dist/esm/styles/prism';
import { Box, Text } from '@chakra-ui/react';
import remarkGfm from 'remark-gfm';
import { useI18n } from '@/providers/i18n/i18nContext'
import { McpDetail } from '@/types/mcp'
import { useI18n } from '@/providers/i18n/i18nContext';
import { McpDetail } from '@/types/mcp';
export interface McpReadmeProps {
mcpDetail: McpDetail
mcpDetail: McpDetail;
}
export default function McpReadme({ mcpDetail }: McpReadmeProps) {
const { lng } = useI18n()
const { lng } = useI18n();
const readmeContent = lng === 'zh' ? mcpDetail.readme_cn || mcpDetail.readme : mcpDetail.readme
const readmeContent = lng === 'zh' ? mcpDetail.readme_cn || mcpDetail.readme : mcpDetail.readme;
return (
<Box h="full" overflow="hidden" w="100%" maxW="100%" position="relative">
@@ -276,8 +276,8 @@ export default function McpReadme({ mcpDetail }: McpReadmeProps) {
remarkPlugins={[remarkGfm]}
components={{
code(props) {
const { children, className, node, inline, ...rest } = props
const match = /language-(\w+)/.exec(className || '')
const { children, className, node, inline, ...rest } = props;
const match = /language-(\w+)/.exec(className || '');
return !inline && match ? (
<Box
className="code-block-wrapper"
@@ -329,7 +329,7 @@ export default function McpReadme({ mcpDetail }: McpReadmeProps) {
<code {...rest} className={className}>
{children}
</code>
)
);
},
}}
>
@@ -369,5 +369,5 @@ export default function McpReadme({ mcpDetail }: McpReadmeProps) {
)}
</Box>
</Box>
)
);
}
@@ -1,15 +1,15 @@
'use client'
import { useEffect, useState } from 'react'
import { Button, Flex, HStack, Input, InputGroup, InputLeftElement, Text } from '@chakra-ui/react'
'use client';
import { useEffect, useState } from 'react';
import { Button, Flex, HStack, Input, InputGroup, InputLeftElement, Text } from '@chakra-ui/react';
import { useTranslationClientSide } from '@/app/i18n/client'
import { useI18n } from '@/providers/i18n/i18nContext'
import { useTranslationClientSide } from '@/app/i18n/client';
import { useI18n } from '@/providers/i18n/i18nContext';
export interface SearchFilterProps {
searchTerm: string
serviceType: 'hosted' | 'local' | ''
onSearchChange: (value: string) => void
onServiceTypeChange: (value: string) => void
searchTerm: string;
serviceType: 'hosted' | 'local' | '';
onSearchChange: (value: string) => void;
onServiceTypeChange: (value: string) => void;
}
export default function SearchFilter({
@@ -18,58 +18,58 @@ export default function SearchFilter({
onSearchChange,
onServiceTypeChange,
}: SearchFilterProps) {
const { lng } = useI18n()
const { t } = useTranslationClientSide(lng, 'common')
const { lng } = useI18n();
const { t } = useTranslationClientSide(lng, 'common');
// 本地输入状态
const [inputValue, setInputValue] = useState(searchTerm)
const [inputValue, setInputValue] = useState(searchTerm);
// 本地多选状态管理 - 完全独立的视觉状态
const [selectedTypes, setSelectedTypes] = useState<Set<'hosted' | 'local'>>(() => {
return new Set<'hosted' | 'local'>() // 默认都不选
})
return new Set<'hosted' | 'local'>(); // 默认都不选
});
// 当外部searchTerm变化时,同步到本地状态
useEffect(() => {
setInputValue(searchTerm)
}, [searchTerm])
setInputValue(searchTerm);
}, [searchTerm]);
const handleKeyPress = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === 'Enter') {
onSearchChange(inputValue)
onSearchChange(inputValue);
}
}
};
const handleInputBlur = () => {
// 当失去焦点时也触发搜索,提供更好的用户体验
if (inputValue !== searchTerm) {
onSearchChange(inputValue)
onSearchChange(inputValue);
}
}
};
const handleServiceTypeToggle = (type: 'hosted' | 'local') => {
const newSelectedTypes = new Set(selectedTypes)
const newSelectedTypes = new Set(selectedTypes);
if (newSelectedTypes.has(type)) {
// 取消选择该类型
newSelectedTypes.delete(type)
newSelectedTypes.delete(type);
} else {
// 选择该类型
newSelectedTypes.add(type)
newSelectedTypes.add(type);
}
setSelectedTypes(newSelectedTypes)
setSelectedTypes(newSelectedTypes);
// 根据选择状态决定传递给父组件的值
if (newSelectedTypes.size === 0 || newSelectedTypes.size === 2) {
// 没有选择任何类型或选择了所有类型,都表示显示全部
onServiceTypeChange('')
onServiceTypeChange('');
} else if (newSelectedTypes.has('hosted')) {
onServiceTypeChange('hosted')
onServiceTypeChange('hosted');
} else if (newSelectedTypes.has('local')) {
onServiceTypeChange('local')
onServiceTypeChange('local');
}
}
};
return (
<Flex gap="24px" flexWrap="wrap" alignItems="center">
@@ -212,5 +212,5 @@ export default function SearchFilter({
</HStack>
</HStack>
</Flex>
)
);
}
@@ -1,25 +1,25 @@
'use client'
import { useState } from 'react'
import { Box, Flex, Text } from '@chakra-ui/react'
import { useQuery } from '@tanstack/react-query'
'use client';
import { useState } from 'react';
import { Box, Flex, Text } from '@chakra-ui/react';
import { useQuery } from '@tanstack/react-query';
import { getMcpList } from '@/api/platform'
import { useTranslationClientSide } from '@/app/i18n/client'
import SwitchPage from '@/components/common/SwitchPage'
import { useI18n } from '@/providers/i18n/i18nContext'
import { QueryKey } from '@/types/query-key'
import { getMcpList } from '@/api/platform';
import { useTranslationClientSide } from '@/app/i18n/client';
import SwitchPage from '@/components/common/SwitchPage';
import { useI18n } from '@/providers/i18n/i18nContext';
import { QueryKey } from '@/types/query-key';
import McpList from './components/McpList'
import SearchFilter from './components/SearchFilter'
import McpList from './components/McpList';
import SearchFilter from './components/SearchFilter';
export default function McpHubPage() {
const { lng } = useI18n()
const { t } = useTranslationClientSide(lng, 'common')
const { lng } = useI18n();
const { t } = useTranslationClientSide(lng, 'common');
const [searchTerm, setSearchTerm] = useState('')
const [serviceType, setServiceType] = useState<'hosted' | 'local' | ''>('')
const [page, setPage] = useState(1)
const [pageSize] = useState(28) // 固定每页28个
const [searchTerm, setSearchTerm] = useState('');
const [serviceType, setServiceType] = useState<'hosted' | 'local' | ''>('');
const [page, setPage] = useState(1);
const [pageSize] = useState(28); // 固定每页28个
const { data: mcpListData, isLoading } = useQuery({
queryKey: [
@@ -33,11 +33,11 @@ export default function McpHubPage() {
type: serviceType,
keyword: searchTerm,
}),
})
});
const mcps = mcpListData?.mcps || []
const total = mcpListData?.total || 0
const totalPages = Math.ceil(total / pageSize)
const mcps = mcpListData?.mcps || [];
const total = mcpListData?.total || 0;
const totalPages = Math.ceil(total / pageSize);
return (
<Box w="full" h="full" display="inline-flex" pt="4px" pb="12px" pr="12px" alignItems="center">
@@ -62,12 +62,12 @@ export default function McpHubPage() {
searchTerm={searchTerm}
serviceType={serviceType}
onSearchChange={(value) => {
setSearchTerm(value)
setPage(1) // 搜索时重置到第一页
setSearchTerm(value);
setPage(1); // 搜索时重置到第一页
}}
onServiceTypeChange={(type) => {
setServiceType(type as 'hosted' | 'local' | '')
setPage(1) // 筛选类型时重置到第一页
setServiceType(type as 'hosted' | 'local' | '');
setPage(1); // 筛选类型时重置到第一页
}}
/>
@@ -105,5 +105,5 @@ export default function McpHubPage() {
</Flex>
</Box>
</Box>
)
);
}
@@ -1,5 +1,5 @@
'use client'
import React from 'react'
'use client';
import React from 'react';
import {
Badge,
Box,
@@ -11,38 +11,38 @@ import {
DrawerOverlay,
Flex,
Text,
} from '@chakra-ui/react'
import { useMessage } from '@sealos/ui'
} from '@chakra-ui/react';
import { useMessage } from '@sealos/ui';
import { useTranslationClientSide } from '@/app/i18n/client'
import { useI18n } from '@/providers/i18n/i18nContext'
import { useBackendStore } from '@/store/backend'
import { ModelConfig } from '@/types/models/model'
import { useTranslationClientSide } from '@/app/i18n/client';
import { useI18n } from '@/providers/i18n/i18nContext';
import { useBackendStore } from '@/store/backend';
import { ModelConfig } from '@/types/models/model';
import CodeBlock from './CodeHight'
import { ModelComponent } from './Model'
import CodeBlock from './CodeHight';
import { ModelComponent } from './Model';
interface ApiDocContent {
title: string
endpoint: string
method: string
requestExample: string
responseExample: string
responseFormat: string
title: string;
endpoint: string;
method: string;
requestExample: string;
responseExample: string;
responseFormat: string;
requestAdditionalInfo?: {
voices?: string[]
formats?: string[]
}
voices?: string[];
formats?: string[];
};
responseAdditionalInfo?: {
voices?: string[]
formats?: string[]
}
voices?: string[];
formats?: string[];
};
}
interface ApiDocDrawerProps {
isOpen: boolean
onClose: () => void
modelConfig: ModelConfig
isOpen: boolean;
onClose: () => void;
modelConfig: ModelConfig;
}
const getApiDocContent = (
@@ -93,7 +93,7 @@ const getApiDocContent = (
"total_tokens": 70
}
}`,
}
};
case 3:
return {
title: t('modeType.3'),
@@ -129,7 +129,7 @@ const getApiDocContent = (
"total_tokens": 4
}
}`,
}
};
case 5:
return {
title: t('modeType.5'),
@@ -162,7 +162,7 @@ const getApiDocContent = (
}
}
}`,
}
};
case 7:
return {
title: t('modeType.7'),
@@ -192,7 +192,7 @@ ${
voices: modelConfig?.config?.support_voices,
formats: modelConfig?.config?.support_formats,
},
}
};
case 8:
return {
title: t('modeType.8'),
@@ -208,7 +208,7 @@ ${
responseExample: `{
"text": "<string>"
}`,
}
};
case 10:
return {
title: t('modeType.10'),
@@ -258,7 +258,7 @@ ${
}
}
}`,
}
};
case 11:
return {
title: t('modeType.11'),
@@ -275,7 +275,7 @@ ${
"pages": 1,
"markdown": "sf ad fda daf da \\\\( f \\\\) ds f sd fs d afdas fsd asfad f\\n\\n\\n\\n![img](data:image/jpeg;base64,/9...)\\n\\n| sadsa | | |\\n| --- | --- | --- |\\n| | sadasdsa | sad |\\n| | | dsadsadsa |\\n| | | |\\n\\n\\n\\na fda"
}`,
}
};
default:
return {
title: t('modeType.0'),
@@ -284,16 +284,16 @@ ${
responseFormat: 'json',
requestExample: 'unknown',
responseExample: 'unknown',
}
};
}
}
};
const ApiDocDrawer: React.FC<ApiDocDrawerProps> = ({ isOpen, onClose, modelConfig }) => {
const { lng } = useI18n()
const { t } = useTranslationClientSide(lng, 'common')
const aiproxyBackend = useBackendStore((state) => state.aiproxyBackend)
const { docUrl } = useBackendStore()
const apiDoc = getApiDocContent(modelConfig, aiproxyBackend, t)
const { lng } = useI18n();
const { t } = useTranslationClientSide(lng, 'common');
const aiproxyBackend = useBackendStore((state) => state.aiproxyBackend);
const { docUrl } = useBackendStore();
const apiDoc = getApiDocContent(modelConfig, aiproxyBackend, t);
const { message } = useMessage({
warningBoxBg: '#FFFAEB',
@@ -302,7 +302,7 @@ const ApiDocDrawer: React.FC<ApiDocDrawerProps> = ({ isOpen, onClose, modelConfi
successBoxBg: '#EDFBF3',
successIconBg: '#039855',
successIconFill: 'white',
})
});
return (
<Drawer isOpen={isOpen} onClose={onClose} placement="right" size="full">
@@ -363,7 +363,7 @@ const ApiDocDrawer: React.FC<ApiDocDrawerProps> = ({ isOpen, onClose, modelConfi
<ModelComponent modelConfig={modelConfig} displayType={true} />
<Button
onClick={() => {
window.open(docUrl, '_blank')
window.open(docUrl, '_blank');
}}
variant="unstyled"
position="absolute"
@@ -525,7 +525,7 @@ const ApiDocDrawer: React.FC<ApiDocDrawerProps> = ({ isOpen, onClose, modelConfi
isClosable: true,
duration: 2000,
position: 'top',
})
});
},
(err) => {
message({
@@ -534,9 +534,9 @@ const ApiDocDrawer: React.FC<ApiDocDrawerProps> = ({ isOpen, onClose, modelConfi
description: err?.message || t('copyFailed'),
isClosable: true,
position: 'top',
})
});
}
)
);
}}
variant="unstyled"
display="inline-flex"
@@ -876,7 +876,7 @@ const ApiDocDrawer: React.FC<ApiDocDrawerProps> = ({ isOpen, onClose, modelConfi
isClosable: true,
duration: 2000,
position: 'top',
})
});
},
(err) => {
message({
@@ -885,9 +885,9 @@ const ApiDocDrawer: React.FC<ApiDocDrawerProps> = ({ isOpen, onClose, modelConfi
description: err?.message || t('copyFailed'),
isClosable: true,
position: 'top',
})
});
}
)
);
}}
variant="unstyled"
display="inline-flex"
@@ -931,7 +931,7 @@ const ApiDocDrawer: React.FC<ApiDocDrawerProps> = ({ isOpen, onClose, modelConfi
</DrawerBody>
</DrawerContent>
</Drawer>
)
}
);
};
export default ApiDocDrawer
export default ApiDocDrawer;
@@ -1,7 +1,7 @@
import React from 'react'
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'
import { atomDark } from 'react-syntax-highlighter/dist/esm/styles/prism'
import { Box } from '@chakra-ui/react'
import React from 'react';
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
import { atomDark } from 'react-syntax-highlighter/dist/esm/styles/prism';
import { Box } from '@chakra-ui/react';
const CodeBlock = ({ code, language = 'bash' }: { code: string; language?: string }) => {
const customizedStyle = {
@@ -12,7 +12,7 @@ const CodeBlock = ({ code, language = 'bash' }: { code: string; language?: strin
margin: 0,
padding: 0,
},
}
};
return (
<Box
@@ -63,7 +63,7 @@ const CodeBlock = ({ code, language = 'bash' }: { code: string; language?: strin
{code}
</SyntaxHighlighter>
</Box>
)
}
);
};
export default CodeBlock
export default CodeBlock;
@@ -1,19 +1,19 @@
'use client'
import { Badge, Flex, Text } from '@chakra-ui/react'
import { useMessage } from '@sealos/ui'
import Image, { StaticImageData } from 'next/image'
'use client';
import { Badge, Flex, Text } from '@chakra-ui/react';
import { useMessage } from '@sealos/ui';
import Image, { StaticImageData } from 'next/image';
import { useTranslationClientSide } from '@/app/i18n/client'
import { MyTooltip } from '@/components/common/MyTooltip'
import { useI18n } from '@/providers/i18n/i18nContext'
import { ModelConfig } from '@/types/models/model'
import { modelIcons } from '@/ui/icons/mode-icons'
import { getTranslationWithFallback } from '@/utils/common'
import { useTranslationClientSide } from '@/app/i18n/client';
import { MyTooltip } from '@/components/common/MyTooltip';
import { useI18n } from '@/providers/i18n/i18nContext';
import { ModelConfig } from '@/types/models/model';
import { modelIcons } from '@/ui/icons/mode-icons';
import { getTranslationWithFallback } from '@/utils/common';
export const getModelIcon = (modelOwner: string): StaticImageData => {
const icon = modelIcons[modelOwner as keyof typeof modelIcons] || modelIcons['default']
return icon
}
const icon = modelIcons[modelOwner as keyof typeof modelIcons] || modelIcons['default'];
return icon;
};
// 在组件外部定义样式配置
const MODEL_TYPE_STYLES = {
@@ -61,22 +61,22 @@ const MODEL_TYPE_STYLES = {
background: '#F4F4F7',
color: '#383F50',
},
} as const
} as const;
// 在组件中使用
export const getTypeStyle = (type: number) => {
return MODEL_TYPE_STYLES[type as keyof typeof MODEL_TYPE_STYLES] || MODEL_TYPE_STYLES.default
}
return MODEL_TYPE_STYLES[type as keyof typeof MODEL_TYPE_STYLES] || MODEL_TYPE_STYLES.default;
};
export const ModelComponent = ({
modelConfig,
displayType = false,
}: {
modelConfig: ModelConfig
displayType?: boolean
modelConfig: ModelConfig;
displayType?: boolean;
}) => {
const { lng } = useI18n()
const { t } = useTranslationClientSide(lng, 'common')
const { lng } = useI18n();
const { t } = useTranslationClientSide(lng, 'common');
const { message } = useMessage({
warningBoxBg: '#FFFAEB',
warningIconBg: '#F79009',
@@ -84,9 +84,9 @@ export const ModelComponent = ({
successBoxBg: '#EDFBF3',
successIconBg: '#039855',
successIconFill: 'white',
})
});
const iconSrc = getModelIcon(modelConfig.owner)
const iconSrc = getModelIcon(modelConfig.owner);
return (
<Flex alignItems="center" justifyContent="flex-start" gap="8px" h="42px">
@@ -120,7 +120,7 @@ export const ModelComponent = ({
isClosable: true,
duration: 2000,
position: 'top',
})
});
},
(err) => {
message({
@@ -129,7 +129,7 @@ export const ModelComponent = ({
description: err?.message || t('copyFailed'),
isClosable: true,
position: 'top',
})
});
}
)
}
@@ -343,5 +343,5 @@ export const ModelComponent = ({
</Flex>
</Flex>
</Flex>
)
}
);
};
@@ -1,5 +1,5 @@
'use client'
import { useMemo, useState } from 'react'
'use client';
import { useMemo, useState } from 'react';
import {
Badge,
Box,
@@ -16,48 +16,48 @@ import {
Thead,
Tr,
useDisclosure,
} from '@chakra-ui/react'
import { CurrencySymbol } from '@sealos/ui'
} from '@chakra-ui/react';
import { CurrencySymbol } from '@sealos/ui';
import {
createColumnHelper,
flexRender,
getCoreRowModel,
useReactTable,
} from '@tanstack/react-table'
} from '@tanstack/react-table';
import { useTranslationClientSide } from '@/app/i18n/client'
import { MyTooltip } from '@/components/common/MyTooltip'
import { useI18n } from '@/providers/i18n/i18nContext'
import { useBackendStore } from '@/store/backend'
import { ModelConfig } from '@/types/models/model'
import { getTranslationWithFallback } from '@/utils/common'
import { useTranslationClientSide } from '@/app/i18n/client';
import { MyTooltip } from '@/components/common/MyTooltip';
import { useI18n } from '@/providers/i18n/i18nContext';
import { useBackendStore } from '@/store/backend';
import { ModelConfig } from '@/types/models/model';
import { getTranslationWithFallback } from '@/utils/common';
import ApiDocDrawer from './ApiDoc'
import { ModelComponent } from './Model'
import { getTypeStyle } from './Model'
import ApiDocDrawer from './ApiDoc';
import { ModelComponent } from './Model';
import { getTypeStyle } from './Model';
type SortDirection = 'asc' | 'desc' | false
type SortDirection = 'asc' | 'desc' | false;
export function PriceTable({
modelConfigs,
isLoading,
}: {
modelConfigs: ModelConfig[]
isLoading: boolean
modelConfigs: ModelConfig[];
isLoading: boolean;
}) {
const { lng } = useI18n()
const { t } = useTranslationClientSide(lng, 'common')
const [selectedModel, setSelectedModel] = useState<ModelConfig | null>(null)
const { isOpen, onOpen, onClose } = useDisclosure()
const { lng } = useI18n();
const { t } = useTranslationClientSide(lng, 'common');
const [selectedModel, setSelectedModel] = useState<ModelConfig | null>(null);
const { isOpen, onOpen, onClose } = useDisclosure();
const handleOpenApiDoc = (modelConfig: ModelConfig) => {
setSelectedModel(modelConfig)
onOpen()
}
setSelectedModel(modelConfig);
onOpen();
};
const { currencySymbol } = useBackendStore()
const { currencySymbol } = useBackendStore();
const columnHelper = createColumnHelper<ModelConfig>()
const columnHelper = createColumnHelper<ModelConfig>();
const columns = [
columnHelper.accessor((row) => row.model, {
id: 'model',
@@ -217,7 +217,7 @@ export function PriceTable({
letterSpacing="0.5px"
/>
</Flex>
)
);
}
return (
<Flex direction="column" gap="8px">
@@ -363,7 +363,7 @@ export function PriceTable({
</Flex>
)}
</Flex>
)
);
},
}),
columnHelper.display({
@@ -380,7 +380,7 @@ export function PriceTable({
</Text>
),
cell: ({ row }) => {
const modelConfig = row.original
const modelConfig = row.original;
return (
<Button
onClick={() => handleOpenApiDoc(modelConfig)}
@@ -437,21 +437,21 @@ export function PriceTable({
{t('logs.detail')}
</Text>
</Button>
)
);
},
id: 'detail',
}),
]
];
const tableData = useMemo(() => {
return modelConfigs
}, [modelConfigs])
return modelConfigs;
}, [modelConfigs]);
const table = useReactTable({
data: tableData,
columns,
getCoreRowModel: getCoreRowModel(),
})
});
return (
<>
@@ -514,5 +514,5 @@ export function PriceTable({
<ApiDocDrawer isOpen={isOpen} onClose={onClose} modelConfig={selectedModel} />
)}
</>
)
);
}
@@ -1,5 +1,5 @@
'use client'
import { useMemo, useState } from 'react'
'use client';
import { useMemo, useState } from 'react';
import {
Box,
Button,
@@ -9,36 +9,36 @@ import {
InputGroup,
InputRightElement,
Text,
} from '@chakra-ui/react'
import { useQuery, UseQueryResult } from '@tanstack/react-query'
import Image from 'next/image'
} from '@chakra-ui/react';
import { useQuery, UseQueryResult } from '@tanstack/react-query';
import Image from 'next/image';
import { getEnabledMode } from '@/api/platform'
import { useTranslationClientSide } from '@/app/i18n/client'
import { SingleSelectComboboxUnstyle } from '@/components/common/SingleSelectComboboxUnStyle'
import { useDebounce } from '@/hooks/useDebounce'
import { useI18n } from '@/providers/i18n/i18nContext'
import { ModelConfig } from '@/types/models/model'
import { QueryKey } from '@/types/query-key'
import { getTranslationWithFallback } from '@/utils/common'
import { getEnabledMode } from '@/api/platform';
import { useTranslationClientSide } from '@/app/i18n/client';
import { SingleSelectComboboxUnstyle } from '@/components/common/SingleSelectComboboxUnStyle';
import { useDebounce } from '@/hooks/useDebounce';
import { useI18n } from '@/providers/i18n/i18nContext';
import { ModelConfig } from '@/types/models/model';
import { QueryKey } from '@/types/query-key';
import { getTranslationWithFallback } from '@/utils/common';
import { getModelIcon } from './component/Model'
import { PriceTable } from './component/PriceTable'
import { getModelIcon } from './component/Model';
import { PriceTable } from './component/PriceTable';
export default function Price() {
const { lng } = useI18n()
const { t } = useTranslationClientSide(lng, 'common')
const { lng } = useI18n();
const { t } = useTranslationClientSide(lng, 'common');
const [modelOwner, setModelOwner] = useState<string>('')
const [modelType, setModelType] = useState<string>('')
const [modelOwner, setModelOwner] = useState<string>('');
const [modelType, setModelType] = useState<string>('');
const [searchInput, setSearchInput] = useState('')
const debouncedSearch = useDebounce(searchInput, 500)
const [searchInput, setSearchInput] = useState('');
const debouncedSearch = useDebounce(searchInput, 500);
interface FilterParams {
owner: string
type: string
name: string
owner: string;
type: string;
name: string;
}
const filterModels = (modelConfigs: ModelConfig[], filterParams: FilterParams): ModelConfig[] => {
@@ -50,34 +50,34 @@ export default function Price() {
`modeOwner.${String(config.owner)}`,
'modeOwner.unknown',
t as any
) === filterParams.owner
) === filterParams.owner;
const typeMatch =
!filterParams.type ||
filterParams.type === t('price.all') ||
getTranslationWithFallback(`modeType.${String(config.type)}`, 'modeType.0', t as any) ===
filterParams.type
filterParams.type;
const nameMatch =
!filterParams.name || config.model.toLowerCase().includes(filterParams.name.toLowerCase())
!filterParams.name || config.model.toLowerCase().includes(filterParams.name.toLowerCase());
return ownerMatch && typeMatch && nameMatch
})
}
return ownerMatch && typeMatch && nameMatch;
});
};
const {
isLoading,
data: modelConfigs = [] as ModelConfig[],
refetch,
}: UseQueryResult<ModelConfig[]> = useQuery([QueryKey.GetEnabledModels], () => getEnabledMode())
}: UseQueryResult<ModelConfig[]> = useQuery([QueryKey.GetEnabledModels], () => getEnabledMode());
const filteredModelConfigs = useMemo(() => {
return filterModels(modelConfigs, {
owner: modelOwner,
type: modelType,
name: debouncedSearch,
})
}, [modelConfigs, modelOwner, modelType, debouncedSearch])
});
}, [modelConfigs, modelOwner, modelType, debouncedSearch]);
return (
<Box w="full" h="full" display="inline-flex" pt="4px" pb="12px" pr="12px" alignItems="center">
@@ -129,7 +129,7 @@ export default function Price() {
bg="white"
boxShadow="0px 1px 2px 0px rgba(19, 51, 107, 0.05), 0px 0px 1px 0px rgba(19, 51, 107, 0.08)"
onClick={() => {
refetch()
refetch();
}}
>
<Icon
@@ -189,16 +189,16 @@ export default function Price() {
),
]}
setSelectedItem={(modelOwner) => {
setModelOwner(modelOwner.name)
setModelOwner(modelOwner.name);
}}
handleDropdownItemFilter={(dropdownItems, inputValue) => {
const lowerCasedInput = inputValue.toLowerCase()
const lowerCasedInput = inputValue.toLowerCase();
return dropdownItems.filter(
(item) => !inputValue || item.name.toLowerCase().includes(lowerCasedInput)
)
);
}}
handleDropdownItemDisplay={(dropdownItem) => {
const iconSrc = getModelIcon(dropdownItem.icon)
const iconSrc = getModelIcon(dropdownItem.icon);
if (dropdownItem.name === t('price.all')) {
return (
<Flex alignItems="center" gap="8px">
@@ -215,7 +215,7 @@ export default function Price() {
{dropdownItem.name}
</Text>
</Flex>
)
);
}
return (
<Flex alignItems="center" gap="8px">
@@ -232,7 +232,7 @@ export default function Price() {
{dropdownItem.name}
</Text>
</Flex>
)
);
}}
flexProps={{ w: '240px' }}
initSelectedItem={{ icon: '', name: t('price.all') }}
@@ -266,13 +266,13 @@ export default function Price() {
),
]}
setSelectedItem={(modelType) => {
setModelType(modelType)
setModelType(modelType);
}}
handleDropdownItemFilter={(dropdownItems, inputValue) => {
const lowerCasedInput = inputValue.toLowerCase()
const lowerCasedInput = inputValue.toLowerCase();
return dropdownItems.filter(
(item) => !inputValue || item.toLowerCase().includes(lowerCasedInput)
)
);
}}
handleDropdownItemDisplay={(dropdownItem) => {
return (
@@ -287,7 +287,7 @@ export default function Price() {
>
{dropdownItem}
</Text>
)
);
}}
flexProps={{ w: '240px' }}
initSelectedItem={t('price.all')}
@@ -320,7 +320,7 @@ export default function Price() {
}}
value={searchInput}
onChange={(e) => {
setSearchInput(e.target.value)
setSearchInput(e.target.value);
}}
/>
<InputRightElement w="32px" h="32px">
@@ -364,5 +364,5 @@ export default function Price() {
</Flex>
</Box>
</Box>
)
);
}
+23 -23
View File
@@ -1,52 +1,52 @@
import { dir } from 'i18next'
import type { Metadata } from 'next'
import Script from 'next/script'
import { dir } from 'i18next';
import type { Metadata } from 'next';
import Script from 'next/script';
import { useTranslationServerSide } from '@/app/i18n/server'
import { fallbackLng, languages } from '@/app/i18n/settings'
import InitializeApp from '@/components/InitializeApp'
import ChakraProviders from '@/providers/chakra/providers'
import { I18nProvider } from '@/providers/i18n/i18nContext'
import QueryProvider from '@/providers/tanstack-query/QueryProvider'
import { useTranslationServerSide } from '@/app/i18n/server';
import { fallbackLng, languages } from '@/app/i18n/settings';
import InitializeApp from '@/components/InitializeApp';
import ChakraProviders from '@/providers/chakra/providers';
import { I18nProvider } from '@/providers/i18n/i18nContext';
import QueryProvider from '@/providers/tanstack-query/QueryProvider';
import './globals.css'
import 'react-day-picker/dist/style.css'
import './globals.css';
import 'react-day-picker/dist/style.css';
export async function generateStaticParams(): Promise<{ lng: string }[]> {
return languages.map((lng) => ({ lng }))
return languages.map((lng) => ({ lng }));
}
export async function generateMetadata({
params,
}: {
params: {
lng: string
}
lng: string;
};
}): Promise<Metadata> {
let { lng } = await params
if (languages.indexOf(lng) < 0) lng = fallbackLng
let { lng } = await params;
if (languages.indexOf(lng) < 0) lng = fallbackLng;
// eslint-disable-next-line react-hooks/rules-of-hooks
const { t } = await useTranslationServerSide(lng, 'common')
const { t } = await useTranslationServerSide(lng, 'common');
return {
icons: {
icon: '/favicon.svg',
},
title: t('title'),
description: t('description'),
}
};
}
export default async function RootLayout({
children,
params,
}: Readonly<{
children: React.ReactNode
params: Promise<{ lng: string }>
children: React.ReactNode;
params: Promise<{ lng: string }>;
}>): Promise<React.JSX.Element> {
const lng = (await params).lng
const lng = (await params).lng;
const scripts: { [key: string]: string }[] = JSON.parse(
process.env.NEXT_PUBLIC_CUSTOM_SCRIPTS ?? '[]'
)
);
return (
<html lang={lng} dir={dir(lng)}>
<body>
@@ -63,5 +63,5 @@ export default async function RootLayout({
))}
</body>
</html>
)
);
}
@@ -1,4 +1,4 @@
import { Metadata } from 'next'
import { Metadata } from 'next';
export const metadata: Metadata = {
title: 'AI Proxy API Doc',
@@ -7,12 +7,12 @@ export const metadata: Metadata = {
index: process.env.NODE_ENV === 'development',
follow: false,
},
}
};
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>{children}</body>
</html>
)
);
}
@@ -1,14 +1,14 @@
import ReactSwagger from '@/components/SwaggerUi'
import { getApiDocs } from '@/lib/swagger'
import ReactSwagger from '@/components/SwaggerUi';
import { getApiDocs } from '@/lib/swagger';
async function ApiDocsPage() {
const spec = await getApiDocs()
const spec = await getApiDocs();
return (
<section className="container">
<ReactSwagger spec={spec} />
</section>
)
);
}
export default ApiDocsPage
export default ApiDocsPage;
@@ -1,135 +1,135 @@
'use client'
'use client';
import { useCallback, useEffect } from 'react'
import { usePathname } from 'next/navigation'
import { useRouter } from 'next/navigation'
import { EVENT_NAME } from 'sealos-desktop-sdk'
import { createSealosApp, sealosApp } from 'sealos-desktop-sdk/app'
import { useCallback, useEffect } from 'react';
import { usePathname } from 'next/navigation';
import { useRouter } from 'next/navigation';
import { EVENT_NAME } from 'sealos-desktop-sdk';
import { createSealosApp, sealosApp } from 'sealos-desktop-sdk/app';
import { initAppConfig } from '@/api/platform'
import { useTranslationClientSide } from '@/app/i18n/client'
import { useI18n } from '@/providers/i18n/i18nContext'
import { useBackendStore } from '@/store/backend'
import { useSessionStore } from '@/store/session'
import { initAppConfig } from '@/api/platform';
import { useTranslationClientSide } from '@/app/i18n/client';
import { useI18n } from '@/providers/i18n/i18nContext';
import { useBackendStore } from '@/store/backend';
import { useSessionStore } from '@/store/session';
export default function InitializeApp() {
const router = useRouter()
const pathname = usePathname()
const { lng } = useI18n()
const { i18n } = useTranslationClientSide(lng)
const router = useRouter();
const pathname = usePathname();
const { lng } = useI18n();
const { i18n } = useTranslationClientSide(lng);
const {
setAiproxyBackend,
setCurrencySymbol,
setDocUrl,
setIsInvitationActive,
setInvitationUrl,
} = useBackendStore()
} = useBackendStore();
const handleI18nChange = useCallback(
(data: { currentLanguage: string }) => {
const currentLng = i18n.resolvedLanguage // get the latest resolvedLanguage
const newLng = data.currentLanguage
const currentLng = i18n.resolvedLanguage; // get the latest resolvedLanguage
const newLng = data.currentLanguage;
if (currentLng !== newLng) {
const currentPath = window.location.pathname
const pathWithoutLang = currentPath.split('/').slice(2).join('/')
router.push(`/${newLng}/${pathWithoutLang}`)
const currentPath = window.location.pathname;
const pathWithoutLang = currentPath.split('/').slice(2).join('/');
router.push(`/${newLng}/${pathWithoutLang}`);
}
},
[i18n.resolvedLanguage]
)
);
useEffect(() => {
const cleanupApp = createSealosApp()
let cleanupEventListener: (() => void) | undefined
const cleanupApp = createSealosApp();
let cleanupEventListener: (() => void) | undefined;
const initApp = async () => {
try {
await initLanguage()
await initLanguage();
await initSession()
await initSession();
await initConfig()
await initConfig();
cleanupEventListener = sealosApp?.addAppEventListen(
EVENT_NAME.CHANGE_I18N,
handleI18nChange
)
);
} catch (error) {
console.error('aiproxy: init app error:', error)
console.error('aiproxy: init app error:', error);
}
}
};
initApp()
initApp();
return () => {
if (cleanupEventListener && typeof cleanupEventListener === 'function') {
cleanupEventListener()
cleanupEventListener();
}
if (cleanupApp && typeof cleanupApp === 'function') {
cleanupApp()
cleanupApp();
}
}
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
}, []);
// init language
const initLanguage = async () => {
const pathLng = pathname.split('/')[1]
const pathLng = pathname.split('/')[1];
try {
const lang = await sealosApp.getLanguage()
const lang = await sealosApp.getLanguage();
if (pathLng !== lang.lng) {
const pathParts = pathname.split('/')
pathParts[1] = lang.lng
router.push(pathParts.join('/'))
router.refresh()
const pathParts = pathname.split('/');
pathParts[1] = lang.lng;
router.push(pathParts.join('/'));
router.refresh();
}
console.info('aiproxy: init language success')
console.info('aiproxy: init language success');
} catch (error) {
if (error instanceof Error) {
console.debug('aiproxy: init language error:', error.message)
console.debug('aiproxy: init language error:', error.message);
} else {
console.debug('aiproxy: unknown init language error:', error)
console.debug('aiproxy: unknown init language error:', error);
}
}
}
};
// init session
const initSession = async () => {
const { setSession } = useSessionStore.getState()
const { setSession } = useSessionStore.getState();
try {
const newSession = await sealosApp.getSession()
const currentSession = useSessionStore.getState().session
const newSession = await sealosApp.getSession();
const currentSession = useSessionStore.getState().session;
// Compare token from persisted session with new session token
if (newSession?.token !== currentSession?.token) {
setSession(newSession)
window.location.reload()
setSession(newSession);
window.location.reload();
}
console.info('aiproxy: init session success')
console.info('aiproxy: init session success');
} catch (err) {
console.info('aiproxy: app is not running in desktop')
console.info('aiproxy: app is not running in desktop');
if (!process.env.NEXT_PUBLIC_MOCK_USER) {
setSession(null)
setSession(null);
}
}
}
};
// init config
const initConfig = async () => {
try {
const { aiproxyBackend, currencySymbol, docUrl, isInvitationActive, invitationUrl } =
await initAppConfig()
setAiproxyBackend(aiproxyBackend)
setCurrencySymbol(currencySymbol)
setDocUrl(docUrl)
setIsInvitationActive(isInvitationActive)
setInvitationUrl(invitationUrl)
console.info('aiproxy: init config success')
await initAppConfig();
setAiproxyBackend(aiproxyBackend);
setCurrencySymbol(currencySymbol);
setDocUrl(docUrl);
setIsInvitationActive(isInvitationActive);
setInvitationUrl(invitationUrl);
console.info('aiproxy: init config success');
} catch (error) {
console.error('aiproxy: init config error:', error)
console.error('aiproxy: init config error:', error);
}
}
};
return null
return null;
}
@@ -1,12 +1,12 @@
'use client'
'use client';
import { memo } from 'react'
import SwaggerUI from 'swagger-ui-react'
import { memo } from 'react';
import SwaggerUI from 'swagger-ui-react';
import 'swagger-ui-react/swagger-ui.css'
import 'swagger-ui-react/swagger-ui.css';
interface ReactSwaggerProps {
spec: object | string
spec: object | string;
}
const ReactSwagger = memo(({ spec }: ReactSwaggerProps) => {
@@ -50,9 +50,9 @@ const ReactSwagger = memo(({ spec }: ReactSwaggerProps) => {
}}
/>
</div>
)
})
);
});
ReactSwagger.displayName = 'ReactSwagger'
ReactSwagger.displayName = 'ReactSwagger';
export default ReactSwagger
export default ReactSwagger;
@@ -1,33 +1,33 @@
'use client'
import { Flex, Text } from '@chakra-ui/react'
import Image, { StaticImageData } from 'next/image'
import Link from 'next/link'
import { usePathname } from 'next/navigation'
'use client';
import { Flex, Text } from '@chakra-ui/react';
import Image, { StaticImageData } from 'next/image';
import Link from 'next/link';
import { usePathname } from 'next/navigation';
import { useTranslationClientSide } from '@/app/i18n/client'
import { useI18n } from '@/providers/i18n/i18nContext'
import configIcon from '@/ui/svg/icons/admin-sidebar/config.svg'
import configIcon_a from '@/ui/svg/icons/admin-sidebar/config_a.svg'
import homeIcon from '@/ui/svg/icons/admin-sidebar/home.svg'
import homeIcon_a from '@/ui/svg/icons/admin-sidebar/home_a.svg'
import logsIcon from '@/ui/svg/icons/admin-sidebar/logs.svg'
import logsIcon_a from '@/ui/svg/icons/admin-sidebar/logs_a.svg'
import nsManagerIcon from '@/ui/svg/icons/admin-sidebar/nsManager.svg'
import nsManagerIcon_a from '@/ui/svg/icons/admin-sidebar/nsManager_a.svg'
import { useTranslationClientSide } from '@/app/i18n/client';
import { useI18n } from '@/providers/i18n/i18nContext';
import configIcon from '@/ui/svg/icons/admin-sidebar/config.svg';
import configIcon_a from '@/ui/svg/icons/admin-sidebar/config_a.svg';
import homeIcon from '@/ui/svg/icons/admin-sidebar/home.svg';
import homeIcon_a from '@/ui/svg/icons/admin-sidebar/home_a.svg';
import logsIcon from '@/ui/svg/icons/admin-sidebar/logs.svg';
import logsIcon_a from '@/ui/svg/icons/admin-sidebar/logs_a.svg';
import nsManagerIcon from '@/ui/svg/icons/admin-sidebar/nsManager.svg';
import nsManagerIcon_a from '@/ui/svg/icons/admin-sidebar/nsManager_a.svg';
type Menu = {
id: string
url: string
value: string
icon: StaticImageData
activeIcon: StaticImageData
display: boolean
}
id: string;
url: string;
value: string;
icon: StaticImageData;
activeIcon: StaticImageData;
display: boolean;
};
const SideBar = (): JSX.Element => {
const pathname = usePathname()
const { lng } = useI18n()
const { t } = useTranslationClientSide(lng, 'common')
const pathname = usePathname();
const { lng } = useI18n();
const { t } = useTranslationClientSide(lng, 'common');
const menus: Menu[] = [
{
@@ -62,7 +62,7 @@ const SideBar = (): JSX.Element => {
activeIcon: nsManagerIcon_a,
display: true,
},
]
];
return (
<Flex
@@ -76,8 +76,8 @@ const SideBar = (): JSX.Element => {
{menus
.filter((menu) => menu.display)
.map((menu) => {
const fullUrl = `/${lng}${menu.url}`
const isActive = pathname === fullUrl
const fullUrl = `/${lng}${menu.url}`;
const isActive = pathname === fullUrl;
return (
<Link href={fullUrl} key={menu.id} style={{ textDecoration: 'none' }}>
@@ -115,10 +115,10 @@ const SideBar = (): JSX.Element => {
</Text>
</Flex>
</Link>
)
);
})}
</Flex>
)
}
);
};
export default SideBar
export default SideBar;
@@ -1,11 +1,11 @@
import React, { useEffect, useMemo, useState } from 'react'
import { Box, Button, Flex, FormLabel, Input, Text, VStack } from '@chakra-ui/react'
import React, { useEffect, useMemo, useState } from 'react';
import { Box, Button, Flex, FormLabel, Input, Text, VStack } from '@chakra-ui/react';
import { useTranslationClientSide } from '@/app/i18n/client'
import { useI18n } from '@/providers/i18n/i18nContext'
import { useTranslationClientSide } from '@/app/i18n/client';
import { useI18n } from '@/providers/i18n/i18nContext';
import { CustomSelect } from './Select'
type MapKeyValuePair = { key: string; value: string }
import { CustomSelect } from './Select';
type MapKeyValuePair = { key: string; value: string };
// mapKeys determines the available selection options
export const ConstructMappingComponent = function ({
@@ -13,30 +13,30 @@ export const ConstructMappingComponent = function ({
mapData,
setMapData,
}: {
mapKeys: string[]
mapData: Record<string, string>
setMapData: (mapping: Record<string, string>) => void
mapKeys: string[];
mapData: Record<string, string>;
setMapData: (mapping: Record<string, string>) => void;
}) {
const { lng } = useI18n()
const { t } = useTranslationClientSide(lng, 'common')
const { lng } = useI18n();
const { t } = useTranslationClientSide(lng, 'common');
const [mapKeyValuePairs, setMapkeyValuePairs] = useState<Array<MapKeyValuePair>>([])
const [mapKeyValuePairs, setMapkeyValuePairs] = useState<Array<MapKeyValuePair>>([]);
// 组件初步渲染时,isInternalUpdate 为 false,可以保证每次重新渲染时,mapData 变化时,mapKeyValuePairs 重新初始化
const [isInternalUpdate, setIsInternalUpdate] = useState(false)
const [isInternalUpdate, setIsInternalUpdate] = useState(false);
// 初始化并在 mapData 变化时更新 mapKeyValuePairs
useEffect(() => {
if (!isInternalUpdate) {
const entries = Object.entries(mapData)
const entries = Object.entries(mapData);
setMapkeyValuePairs(
entries.length > 0
? entries.map(([key, value]) => ({ key, value }))
: [{ key: '', value: '' }]
)
);
}
setIsInternalUpdate(false)
}, [mapData])
setIsInternalUpdate(false);
}, [mapData]);
const handleDropdownItemDisplay = (dropdownItem: string) => {
if (dropdownItem === t('channelsFormPlaceholder.modelMappingInput')) {
@@ -52,7 +52,7 @@ export const ConstructMappingComponent = function ({
>
{t('channelsFormPlaceholder.modelMappingInput')}
</Text>
)
);
}
return (
<Text
@@ -66,8 +66,8 @@ export const ConstructMappingComponent = function ({
>
{dropdownItem}
</Text>
)
}
);
};
const handleSeletedItemDisplay = (selectedItem: string) => {
if (selectedItem === t('channelsFormPlaceholder.modelMappingInput')) {
@@ -83,7 +83,7 @@ export const ConstructMappingComponent = function ({
>
{t('channelsFormPlaceholder.modelMappingInput')}
</Text>
)
);
}
return (
<Box
@@ -108,101 +108,101 @@ export const ConstructMappingComponent = function ({
{selectedItem}
</Text>
</Box>
)
}
);
};
// Handling mapData and mapKeyValuePairs cleanup when map keys change.
useEffect(() => {
// 1. Handle mapData cleanup
const removedKeysFromMapData = Object.keys(mapData).filter((key) => !mapKeys.includes(key))
const removedKeysFromMapData = Object.keys(mapData).filter((key) => !mapKeys.includes(key));
if (removedKeysFromMapData.length > 0) {
const newMapData = { ...mapData }
const newMapData = { ...mapData };
removedKeysFromMapData.forEach((key) => {
delete newMapData[key]
})
setIsInternalUpdate(true)
setMapData(newMapData)
delete newMapData[key];
});
setIsInternalUpdate(true);
setMapData(newMapData);
}
// 2. Handle mapKeyValuePairs cleanup
const removedPairs = mapKeyValuePairs.filter((pair) => pair.key && !mapKeys.includes(pair.key))
const removedPairs = mapKeyValuePairs.filter((pair) => pair.key && !mapKeys.includes(pair.key));
if (removedPairs.length > 0) {
const newMapKeyValuePairs = mapKeyValuePairs.filter(
(pair) => !pair.key || mapKeys.includes(pair.key)
)
setMapkeyValuePairs(newMapKeyValuePairs)
);
setMapkeyValuePairs(newMapKeyValuePairs);
}
}, [mapKeys])
}, [mapKeys]);
// Get the keys that have been selected
const getSelectedMapKeys = (currentIndex: number) => {
const selected = new Set<string>()
const selected = new Set<string>();
mapKeyValuePairs.forEach((mapKeyValuePair, idx) => {
if (idx !== currentIndex && mapKeyValuePair.key) {
selected.add(mapKeyValuePair.key)
selected.add(mapKeyValuePair.key);
}
})
return selected
}
});
return selected;
};
// Handling adding a new row
const handleAddNewMapKeyPair = () => {
setMapkeyValuePairs([...mapKeyValuePairs, { key: '', value: '' }])
}
setMapkeyValuePairs([...mapKeyValuePairs, { key: '', value: '' }]);
};
// Handling deleting a row
const handleRemoveMapKeyPair = (index: number) => {
const mapKeyValuePair = mapKeyValuePairs[index]
const newMapData = { ...mapData }
const mapKeyValuePair = mapKeyValuePairs[index];
const newMapData = { ...mapData };
if (mapKeyValuePair.key) {
delete newMapData[mapKeyValuePair.key]
delete newMapData[mapKeyValuePair.key];
}
setIsInternalUpdate(true)
setMapData(newMapData)
setIsInternalUpdate(true);
setMapData(newMapData);
const newMapKeyValuePairs = mapKeyValuePairs.filter((_, idx) => idx !== index)
setMapkeyValuePairs(newMapKeyValuePairs)
}
const newMapKeyValuePairs = mapKeyValuePairs.filter((_, idx) => idx !== index);
setMapkeyValuePairs(newMapKeyValuePairs);
};
// Handling selection/input changes
const handleInputChange = (index: number, field: 'key' | 'value', value: string) => {
const newMapKeyValuePairs = [...mapKeyValuePairs]
const oldValue = newMapKeyValuePairs[index][field]
newMapKeyValuePairs[index][field] = value
const newMapKeyValuePairs = [...mapKeyValuePairs];
const oldValue = newMapKeyValuePairs[index][field];
newMapKeyValuePairs[index][field] = value;
// Update the mapping relationship
const newMapData = { ...mapData }
const newMapData = { ...mapData };
if (field === 'key') {
if (oldValue) delete newMapData[oldValue]
if (oldValue) delete newMapData[oldValue];
if (!value) {
newMapKeyValuePairs[index].value = ''
newMapKeyValuePairs[index].value = '';
}
if (value && newMapKeyValuePairs[index].value) {
newMapData[value] = newMapKeyValuePairs[index].value
newMapData[value] = newMapKeyValuePairs[index].value;
}
} else {
if (newMapKeyValuePairs[index].key) {
newMapData[newMapKeyValuePairs[index].key] = value
newMapData[newMapKeyValuePairs[index].key] = value;
}
}
setMapkeyValuePairs(newMapKeyValuePairs)
setIsInternalUpdate(true)
setMapData(newMapData)
}
setMapkeyValuePairs(newMapKeyValuePairs);
setIsInternalUpdate(true);
setMapData(newMapData);
};
// Check if there are still keys that can be selected
const hasAvailableKeys = useMemo(() => {
const usedKeys = new Set(
mapKeyValuePairs.map((mapKeyValuePair) => mapKeyValuePair.key).filter(Boolean)
)
);
// Ensure mapKeyValuePairs length does not exceed mapKeys length
return (
mapKeyValuePairs.length < mapKeys.length && mapKeys.some((mapKey) => !usedKeys.has(mapKey))
)
}, [mapKeys, mapKeyValuePairs])
);
}, [mapKeys, mapKeyValuePairs]);
return (
<VStack w="full" align="stretch" alignItems="flex-start" spacing="8px">
@@ -339,6 +339,6 @@ export const ConstructMappingComponent = function ({
</Button>
)}
</VStack>
)
}
export default ConstructMappingComponent
);
};
export default ConstructMappingComponent;
@@ -1,16 +1,16 @@
import React, { useEffect, useMemo, useState } from 'react'
import { Badge, Box, Button, Flex, FormLabel, Input, Text, VStack } from '@chakra-ui/react'
import React, { useEffect, useMemo, useState } from 'react';
import { Badge, Box, Button, Flex, FormLabel, Input, Text, VStack } from '@chakra-ui/react';
import { useTranslationClientSide } from '@/app/i18n/client'
import { useI18n } from '@/providers/i18n/i18nContext'
import { useTranslationClientSide } from '@/app/i18n/client';
import { useI18n } from '@/providers/i18n/i18nContext';
import { CustomSelect } from './Select'
type MapKeyValuePair = { key: string; value: string }
import { CustomSelect } from './Select';
type MapKeyValuePair = { key: string; value: string };
type Model = {
name: string
isDefault: boolean
}
name: string;
isDefault: boolean;
};
// mapKeys determines the available selection options
export const ConstructModeMappingComponent = function ({
@@ -18,30 +18,30 @@ export const ConstructModeMappingComponent = function ({
mapData,
setMapData,
}: {
mapKeys: Model[]
mapData: Record<string, string>
setMapData: (mapping: Record<string, string>) => void
mapKeys: Model[];
mapData: Record<string, string>;
setMapData: (mapping: Record<string, string>) => void;
}) {
const { lng } = useI18n()
const { t } = useTranslationClientSide(lng, 'common')
const { lng } = useI18n();
const { t } = useTranslationClientSide(lng, 'common');
const [mapKeyValuePairs, setMapkeyValuePairs] = useState<Array<MapKeyValuePair>>([
{ key: '', value: '' },
])
]);
const [isInternalUpdate, setIsInternalUpdate] = useState(false)
const [isInternalUpdate, setIsInternalUpdate] = useState(false);
useEffect(() => {
if (!isInternalUpdate) {
const entries = Object.entries(mapData)
const entries = Object.entries(mapData);
setMapkeyValuePairs(
entries.length > 0
? entries.map(([key, value]) => ({ key, value }))
: [{ key: '', value: '' }]
)
);
}
setIsInternalUpdate(false)
}, [mapData])
setIsInternalUpdate(false);
}, [mapData]);
const handleDropdownItemDisplay = (dropdownItem: Model | string) => {
if (dropdownItem === t('channelsFormPlaceholder.modelMappingInput')) {
@@ -57,7 +57,7 @@ export const ConstructModeMappingComponent = function ({
>
{t('channelsFormPlaceholder.modelMappingInput')}
</Text>
)
);
}
if ((dropdownItem as Model).isDefault) {
@@ -112,7 +112,7 @@ export const ConstructModeMappingComponent = function ({
</svg>
</Badge>
</Flex>
)
);
}
return (
<Text
@@ -126,8 +126,8 @@ export const ConstructModeMappingComponent = function ({
>
{(dropdownItem as Model).name}
</Text>
)
}
);
};
const handleSeletedItemDisplay = (selectedItem: Model | string) => {
if (selectedItem === t('channelsFormPlaceholder.modelMappingInput')) {
@@ -143,7 +143,7 @@ export const ConstructModeMappingComponent = function ({
>
{t('channelsFormPlaceholder.modelMappingInput')}
</Text>
)
);
}
if ((selectedItem as Model).isDefault) {
@@ -215,7 +215,7 @@ export const ConstructModeMappingComponent = function ({
</svg>
</Badge>
</Flex>
)
);
}
return (
<Box
@@ -240,106 +240,106 @@ export const ConstructModeMappingComponent = function ({
{(selectedItem as Model).name}
</Text>
</Box>
)
}
);
};
// Handling mapData and mapKeyValuePairs cleanup when map keys change.
useEffect(() => {
// 1. Handle mapData cleanup
const removedKeys = Object.keys(mapData).filter(
(key) => !mapKeys.some((model) => model.name === key)
)
);
if (removedKeys.length > 0) {
// If there are mappings with removed keys, delete them
const newMapData = { ...mapData }
const newMapData = { ...mapData };
removedKeys.forEach((key) => {
delete newMapData[key]
})
setIsInternalUpdate(true)
setMapData(newMapData)
delete newMapData[key];
});
setIsInternalUpdate(true);
setMapData(newMapData);
}
// 2. Handle mapKeyValuePairs cleanup
const removedPairs = mapKeyValuePairs.filter(
(pair) => pair.key && !mapKeys.some((model) => model.name === pair.key)
)
);
if (removedPairs.length > 0) {
const newMapKeyValuePairs = mapKeyValuePairs.filter(
(pair) => !pair.key || mapKeys.some((model) => model.name === pair.key)
)
setMapkeyValuePairs(newMapKeyValuePairs)
);
setMapkeyValuePairs(newMapKeyValuePairs);
}
}, [mapKeys])
}, [mapKeys]);
// Get the keys that have been selected
const getSelectedMapKeys = (currentIndex: number) => {
const selected = new Set<string>()
const selected = new Set<string>();
mapKeyValuePairs.forEach((mapKeyValuePair, idx) => {
if (idx !== currentIndex && mapKeyValuePair.key) {
selected.add(mapKeyValuePair.key)
selected.add(mapKeyValuePair.key);
}
})
return selected
}
});
return selected;
};
// Handling adding a new row
const handleAddNewMapKeyPair = () => {
setMapkeyValuePairs([...mapKeyValuePairs, { key: '', value: '' }])
}
setMapkeyValuePairs([...mapKeyValuePairs, { key: '', value: '' }]);
};
// Handling deleting a row
const handleRemoveMapKeyPair = (index: number) => {
const mapKeyValuePair = mapKeyValuePairs[index]
const newMapData = { ...mapData }
const mapKeyValuePair = mapKeyValuePairs[index];
const newMapData = { ...mapData };
if (mapKeyValuePair.key) {
delete newMapData[mapKeyValuePair.key]
delete newMapData[mapKeyValuePair.key];
}
setIsInternalUpdate(true)
setMapData(newMapData)
setIsInternalUpdate(true);
setMapData(newMapData);
const newMapKeyValuePairs = mapKeyValuePairs.filter((_, idx) => idx !== index)
setMapkeyValuePairs(newMapKeyValuePairs)
}
const newMapKeyValuePairs = mapKeyValuePairs.filter((_, idx) => idx !== index);
setMapkeyValuePairs(newMapKeyValuePairs);
};
// Handling selection/input changes
const handleInputChange = (index: number, field: 'key' | 'value', value: string) => {
const newMapKeyValuePairs = [...mapKeyValuePairs]
const oldValue = newMapKeyValuePairs[index][field]
newMapKeyValuePairs[index][field] = value
const newMapKeyValuePairs = [...mapKeyValuePairs];
const oldValue = newMapKeyValuePairs[index][field];
newMapKeyValuePairs[index][field] = value;
// Update the mapping relationship
const newMapData = { ...mapData }
const newMapData = { ...mapData };
if (field === 'key') {
if (oldValue) delete newMapData[oldValue]
if (oldValue) delete newMapData[oldValue];
if (!value) {
newMapKeyValuePairs[index].value = ''
newMapKeyValuePairs[index].value = '';
}
if (value && newMapKeyValuePairs[index].value) {
newMapData[value] = newMapKeyValuePairs[index].value
newMapData[value] = newMapKeyValuePairs[index].value;
}
} else {
if (newMapKeyValuePairs[index].key) {
newMapData[newMapKeyValuePairs[index].key] = value
newMapData[newMapKeyValuePairs[index].key] = value;
}
}
setMapkeyValuePairs(newMapKeyValuePairs)
setIsInternalUpdate(true)
setMapData(newMapData)
}
setMapkeyValuePairs(newMapKeyValuePairs);
setIsInternalUpdate(true);
setMapData(newMapData);
};
// Check if there are still keys that can be selected
const hasAvailableKeys = useMemo(() => {
const usedKeys = new Set(
mapKeyValuePairs.map((mapKeyValuePair) => mapKeyValuePair.key).filter(Boolean)
)
);
return (
mapKeyValuePairs.length < mapKeys.length &&
mapKeys.some((mapKey) => !usedKeys.has(mapKey.name))
)
}, [mapKeys, mapKeyValuePairs])
);
}, [mapKeys, mapKeyValuePairs]);
return (
<VStack w="full" align="stretch" alignItems="flex-start" spacing="8px">
@@ -370,9 +370,9 @@ export const ConstructModeMappingComponent = function ({
// when select placeholder, the newSelectedItem is null
handleSelectedItemChange={(newSelectedItem) => {
if (newSelectedItem) {
handleInputChange(index, 'key', newSelectedItem.name)
handleInputChange(index, 'key', newSelectedItem.name);
} else {
handleInputChange(index, 'key', '')
handleInputChange(index, 'key', '');
}
}}
handleDropdownItemDisplay={handleDropdownItemDisplay}
@@ -489,6 +489,6 @@ export const ConstructModeMappingComponent = function ({
</Button>
)}
</VStack>
)
}
export default ConstructModeMappingComponent
);
};
export default ConstructModeMappingComponent;
@@ -1,6 +1,6 @@
'use client'
import React, { useState } from 'react'
import { CheckIcon, CloseIcon } from '@chakra-ui/icons'
'use client';
import React, { useState } from 'react';
import { CheckIcon, CloseIcon } from '@chakra-ui/icons';
import {
Box,
Button,
@@ -14,28 +14,28 @@ import {
PopoverTrigger,
Text,
useDisclosure,
} from '@chakra-ui/react'
} from '@chakra-ui/react';
interface EditableTextProps {
value: string | number
onSubmit: (value: string) => void
flexProps?: FlexProps
value: string | number;
onSubmit: (value: string) => void;
flexProps?: FlexProps;
}
export const EditableTextNoLable = ({ value, onSubmit, flexProps }: EditableTextProps) => {
const [editValue, setEditValue] = useState(String(value))
const { isOpen, onOpen, onClose } = useDisclosure()
const [editValue, setEditValue] = useState(String(value));
const { isOpen, onOpen, onClose } = useDisclosure();
const handleSubmit = () => {
onSubmit(editValue)
onClose()
}
onSubmit(editValue);
onClose();
};
const handleCancel = () => {
// 关闭时 恢复到传递来的初始值
setEditValue(String(value))
onClose()
}
setEditValue(String(value));
onClose();
};
return (
<Popover isOpen={isOpen} onClose={handleCancel} placement="bottom-end">
@@ -173,5 +173,5 @@ export const EditableTextNoLable = ({ value, onSubmit, flexProps }: EditableText
</PopoverBody>
</PopoverContent>
</Popover>
)
}
);
};
@@ -1,5 +1,5 @@
'use client'
import { Dispatch, ReactNode, SetStateAction, useMemo, useState } from 'react'
'use client';
import { Dispatch, ReactNode, SetStateAction, useMemo, useState } from 'react';
import {
Box,
Button,
@@ -11,11 +11,11 @@ import {
ListItem,
Text,
VStack,
} from '@chakra-ui/react'
import { useCombobox, useMultipleSelection } from 'downshift'
} from '@chakra-ui/react';
import { useCombobox, useMultipleSelection } from 'downshift';
import { useTranslationClientSide } from '@/app/i18n/client'
import { useI18n } from '@/providers/i18n/i18nContext'
import { useTranslationClientSide } from '@/app/i18n/client';
import { useI18n } from '@/providers/i18n/i18nContext';
export const MultiSelectCombobox = function <T>({
dropdownItems,
@@ -26,30 +26,30 @@ export const MultiSelectCombobox = function <T>({
handleSelectedItemDisplay,
handleSetCustomSelectedItem,
}: {
dropdownItems: T[]
selectedItems: T[]
setSelectedItems: Dispatch<SetStateAction<T[]>>
handleFilteredDropdownItems: (dropdownItems: T[], selectedItems: T[], inputValue: string) => T[]
handleDropdownItemDisplay: (dropdownItem: T) => ReactNode
handleSelectedItemDisplay: (selectedItem: T) => ReactNode
dropdownItems: T[];
selectedItems: T[];
setSelectedItems: Dispatch<SetStateAction<T[]>>;
handleFilteredDropdownItems: (dropdownItems: T[], selectedItems: T[], inputValue: string) => T[];
handleDropdownItemDisplay: (dropdownItem: T) => ReactNode;
handleSelectedItemDisplay: (selectedItem: T) => ReactNode;
handleSetCustomSelectedItem?: (
selectedItems: T[],
setSelectedItems: Dispatch<SetStateAction<T[]>>,
customSelectedItemName: string,
setCustomSelectedItemName: Dispatch<SetStateAction<string>>
) => void
) => void;
}): JSX.Element {
const { lng } = useI18n()
const { t } = useTranslationClientSide(lng, 'common')
const { lng } = useI18n();
const { t } = useTranslationClientSide(lng, 'common');
const [inputValue, setInputValue] = useState<string>('')
const [customSelectedItemName, setCustomSelectedItemName] = useState('')
const [inputValue, setInputValue] = useState<string>('');
const [customSelectedItemName, setCustomSelectedItemName] = useState('');
// Dropdown list excludes already selected options and includes those matching the input.
const items = useMemo(
() => handleFilteredDropdownItems(dropdownItems, selectedItems, inputValue),
[inputValue, selectedItems, dropdownItems, handleFilteredDropdownItems]
)
);
// 对已经选中的项目 添加处理事件
const { getSelectedItemProps, getDropdownProps, removeSelectedItem } = useMultipleSelection({
@@ -61,14 +61,14 @@ export const MultiSelectCombobox = function <T>({
case useMultipleSelection.stateChangeTypes.DropdownKeyDownBackspace:
case useMultipleSelection.stateChangeTypes.FunctionRemoveSelectedItem:
if (newSelectedItems) {
setSelectedItems(newSelectedItems)
setSelectedItems(newSelectedItems);
}
break
break;
default:
break
break;
}
},
})
});
const {
isOpen,
getToggleButtonProps,
@@ -84,7 +84,7 @@ export const MultiSelectCombobox = function <T>({
selectedItem: null,
inputValue,
stateReducer(state, actionAndChanges) {
const { changes, type } = actionAndChanges
const { changes, type } = actionAndChanges;
switch (type) {
case useCombobox.stateChangeTypes.InputKeyDownEnter:
@@ -93,9 +93,9 @@ export const MultiSelectCombobox = function <T>({
...changes,
isOpen: true, // keep the menu open after selection.
highlightedIndex: 0, // with the first option highlighted.
}
};
default:
return changes
return changes;
}
},
onStateChange({ inputValue: newInputValue, type, selectedItem: newSelectedItem }) {
@@ -104,20 +104,20 @@ export const MultiSelectCombobox = function <T>({
case useCombobox.stateChangeTypes.ItemClick:
case useCombobox.stateChangeTypes.InputBlur:
if (newSelectedItem) {
setSelectedItems([...selectedItems, newSelectedItem])
setInputValue('')
setSelectedItems([...selectedItems, newSelectedItem]);
setInputValue('');
}
break
break;
case useCombobox.stateChangeTypes.InputChange:
setInputValue(newInputValue ?? '')
setInputValue(newInputValue ?? '');
break
break;
default:
break
break;
}
},
})
});
return (
<Box w="full">
@@ -215,7 +215,7 @@ export const MultiSelectCombobox = function <T>({
setSelectedItems,
customSelectedItemName,
setCustomSelectedItemName
)
);
}}
_hover={{
transform: 'scale(1.05)',
@@ -282,8 +282,8 @@ export const MultiSelectCombobox = function <T>({
justifyContent="center"
cursor="pointer"
onClick={(e) => {
e.stopPropagation()
removeSelectedItem(selectedItemForRender)
e.stopPropagation();
removeSelectedItem(selectedItemForRender);
}}
>
<svg
@@ -417,5 +417,5 @@ export const MultiSelectCombobox = function <T>({
))}
</List>
</Box>
)
}
);
};
@@ -1,6 +1,6 @@
'use client'
'use client';
import { Tooltip, TooltipProps } from '@chakra-ui/react'
import { Tooltip, TooltipProps } from '@chakra-ui/react';
export const MyTooltip = ({ children, ...tooltipProps }: TooltipProps) => {
const tooltipStyles = {
@@ -42,11 +42,11 @@ export const MyTooltip = ({ children, ...tooltipProps }: TooltipProps) => {
},
},
},
}
};
return (
<Tooltip {...tooltipStyles} {...tooltipProps}>
{children}
</Tooltip>
)
}
);
};
@@ -1,10 +1,10 @@
'use client'
import { ReactNode } from 'react'
import { Box, List, ListItem, Text } from '@chakra-ui/react'
import { useSelect } from 'downshift'
'use client';
import { ReactNode } from 'react';
import { Box, List, ListItem, Text } from '@chakra-ui/react';
import { useSelect } from 'downshift';
import { useTranslationClientSide } from '@/app/i18n/client'
import { useI18n } from '@/providers/i18n/i18nContext'
import { useTranslationClientSide } from '@/app/i18n/client';
import { useI18n } from '@/providers/i18n/i18nContext';
export const CustomSelect = function <T>({
listItems,
@@ -14,16 +14,16 @@ export const CustomSelect = function <T>({
placeholder,
initSelectedItem,
}: {
listItems: T[]
handleSelectedItemChange: (selectedItem: T) => void
handleDropdownItemDisplay: (dropdownItem: T) => ReactNode
handleSelectedItemDisplay: (selectedItem: T) => ReactNode
placeholder?: string
initSelectedItem?: T
listItems: T[];
handleSelectedItemChange: (selectedItem: T) => void;
handleDropdownItemDisplay: (dropdownItem: T) => ReactNode;
handleSelectedItemDisplay: (selectedItem: T) => ReactNode;
placeholder?: string;
initSelectedItem?: T;
}) {
const { lng } = useI18n()
const { t } = useTranslationClientSide(lng, 'common')
const items = [placeholder, ...listItems]
const { lng } = useI18n();
const { t } = useTranslationClientSide(lng, 'common');
const items = [placeholder, ...listItems];
const {
isOpen,
@@ -37,12 +37,12 @@ export const CustomSelect = function <T>({
initialSelectedItem: initSelectedItem,
onSelectedItemChange: ({ selectedItem: newSelectedItem }) => {
if (newSelectedItem === placeholder) {
handleSelectedItemChange(undefined as T)
handleSelectedItemChange(undefined as T);
} else {
handleSelectedItemChange(newSelectedItem as T)
handleSelectedItemChange(newSelectedItem as T);
}
},
})
});
return (
<Box w="full" position="relative" flex={1}>
@@ -143,5 +143,5 @@ export const CustomSelect = function <T>({
))}
</List>
</Box>
)
}
);
};
@@ -1,5 +1,5 @@
import { ChangeEventHandler, Dispatch, SetStateAction, useState } from 'react'
import { DateRange, DayPicker, SelectRangeEventHandler } from 'react-day-picker'
import { ChangeEventHandler, Dispatch, SetStateAction, useState } from 'react';
import { DateRange, DayPicker, SelectRangeEventHandler } from 'react-day-picker';
import {
Box,
Button,
@@ -10,16 +10,16 @@ import {
Popover,
PopoverContent,
PopoverTrigger,
} from '@chakra-ui/react'
import { endOfDay, format, isAfter, isBefore, isValid, parse, startOfDay } from 'date-fns'
} from '@chakra-ui/react';
import { endOfDay, format, isAfter, isBefore, isValid, parse, startOfDay } from 'date-fns';
type SelectDateRangeProps = {
isDisabled?: boolean
startTime: Date
setStartTime: Dispatch<SetStateAction<Date>>
endTime: Date
setEndTime: Dispatch<SetStateAction<Date>>
}
isDisabled?: boolean;
startTime: Date;
setStartTime: Dispatch<SetStateAction<Date>>;
endTime: Date;
setEndTime: Dispatch<SetStateAction<Date>>;
};
export default function SelectDateRange({
isDisabled,
@@ -29,146 +29,146 @@ export default function SelectDateRange({
setEndTime,
...props
}: SelectDateRangeProps & FlexProps): JSX.Element {
const initState = { from: startTime, to: endTime }
const initState = { from: startTime, to: endTime };
const [selectedRange, setSelectedRange] = useState<DateRange | undefined>(initState)
const [fromValue, setFromValue] = useState<string>(format(initState.from, 'y-MM-dd'))
const [toValue, setToValue] = useState<string>(format(initState.to, 'y-MM-dd'))
const [inputState, setInputState] = useState<0 | 1>(0)
const [selectedRange, setSelectedRange] = useState<DateRange | undefined>(initState);
const [fromValue, setFromValue] = useState<string>(format(initState.from, 'y-MM-dd'));
const [toValue, setToValue] = useState<string>(format(initState.to, 'y-MM-dd'));
const [inputState, setInputState] = useState<0 | 1>(0);
const onClose = () => {
selectedRange?.from && setStartTime(startOfDay(selectedRange.from))
selectedRange?.to && setEndTime(endOfDay(selectedRange.to))
}
selectedRange?.from && setStartTime(startOfDay(selectedRange.from));
selectedRange?.to && setEndTime(endOfDay(selectedRange.to));
};
const handleFromChange: ChangeEventHandler<HTMLInputElement> = (e) => {
setFromValue(e.target.value)
const date = parse(e.target.value, 'y-MM-dd', new Date())
setFromValue(e.target.value);
const date = parse(e.target.value, 'y-MM-dd', new Date());
if (!isValid(date)) {
return setSelectedRange({ from: undefined, to: selectedRange?.to })
return setSelectedRange({ from: undefined, to: selectedRange?.to });
}
if (selectedRange?.to) {
if (isAfter(date, selectedRange.to)) {
setSelectedRange({ from: selectedRange.to, to: date })
setSelectedRange({ from: selectedRange.to, to: date });
} else {
setSelectedRange({ from: date, to: selectedRange?.to })
setSelectedRange({ from: date, to: selectedRange?.to });
}
} else {
setSelectedRange({ from: date, to: date })
setSelectedRange({ from: date, to: date });
}
}
};
const handleToChange: ChangeEventHandler<HTMLInputElement> = (e) => {
setToValue(e.target.value)
const date = parse(e.target.value, 'y-MM-dd', new Date())
setToValue(e.target.value);
const date = parse(e.target.value, 'y-MM-dd', new Date());
if (!isValid(date)) {
return setSelectedRange({ from: selectedRange?.from, to: undefined })
return setSelectedRange({ from: selectedRange?.from, to: undefined });
}
if (selectedRange?.from) {
if (isBefore(date, selectedRange.from)) {
setSelectedRange({ from: date, to: selectedRange.from })
setSelectedRange({ from: date, to: selectedRange.from });
} else {
setSelectedRange({ from: selectedRange?.from, to: date })
setSelectedRange({ from: selectedRange?.from, to: date });
}
} else {
setSelectedRange({ from: date, to: date })
setSelectedRange({ from: date, to: date });
}
}
};
const handleRangeSelect: SelectRangeEventHandler = (range: DateRange | undefined) => {
if (range) {
let { from, to } = range
let { from, to } = range;
if (inputState === 0) {
if (from === selectedRange?.from) {
from = to
from = to;
} else {
to = from
to = from;
}
setInputState(1)
setInputState(1);
} else {
setInputState(0)
setInputState(0);
}
setSelectedRange({
from,
to,
})
});
if (from) {
setFromValue(format(from, 'y-MM-dd'))
setFromValue(format(from, 'y-MM-dd'));
} else {
setFromValue('')
setFromValue('');
}
if (to) {
setToValue(format(to, 'y-MM-dd'))
setToValue(format(to, 'y-MM-dd'));
} else {
setToValue(from ? format(from, 'y-MM-dd') : '')
setToValue(from ? format(from, 'y-MM-dd') : '');
}
} else {
if (fromValue && selectedRange?.from) {
setToValue(fromValue)
setToValue(fromValue);
setSelectedRange({
...selectedRange,
to: selectedRange.from,
})
setInputState(1)
});
setInputState(1);
}
}
}
};
const handleRangeSelectFrom: SelectRangeEventHandler = (range: DateRange | undefined) => {
if (range) {
let { from, to } = range
let { from, to } = range;
if (selectedRange?.to) {
if (from) {
if (!to) {
to = from
to = from;
} else if (from === selectedRange?.from) {
from = to
to = selectedRange.to
from = to;
to = selectedRange.to;
}
if (isBefore(from, selectedRange.to)) {
setSelectedRange({
...selectedRange,
from,
})
setFromValue(format(from, 'y-MM-dd'))
});
setFromValue(format(from, 'y-MM-dd'));
}
}
}
}
}
};
const handleRangeSelectTo: SelectRangeEventHandler = (range: DateRange | undefined) => {
if (range) {
let { from, to } = range
let { from, to } = range;
if (selectedRange?.from) {
if (to) {
if (!from) {
from = to
from = to;
} else if (to === selectedRange?.to) {
to = from
from = selectedRange.from
to = from;
from = selectedRange.from;
}
if (isAfter(to, selectedRange.from)) {
setSelectedRange({
...selectedRange,
to,
})
setToValue(format(to, 'y-MM-dd'))
});
setToValue(format(to, 'y-MM-dd'));
}
}
}
} else {
if (fromValue && selectedRange?.from) {
setToValue(fromValue)
setToValue(fromValue);
setSelectedRange({
...selectedRange,
to: selectedRange.from,
})
setInputState(1)
});
setInputState(1);
}
}
}
};
return (
<Flex
@@ -196,7 +196,7 @@ export default function SelectDateRange({
minW="50px"
onChange={handleFromChange}
onBlur={() => {
selectedRange?.from && setStartTime(startOfDay(selectedRange.from))
selectedRange?.from && setStartTime(startOfDay(selectedRange.from));
}}
/>
</Button>
@@ -229,7 +229,7 @@ export default function SelectDateRange({
minW="90px"
onChange={handleToChange}
onBlur={() => {
selectedRange?.to && setEndTime(endOfDay(selectedRange.to))
selectedRange?.to && setEndTime(endOfDay(selectedRange.to));
}}
/>
</Button>
@@ -251,8 +251,8 @@ export default function SelectDateRange({
</Popover>
<Popover
onClose={() => {
setInputState(0)
onClose()
setInputState(0);
onClose();
}}
>
<PopoverTrigger>
@@ -286,5 +286,5 @@ export default function SelectDateRange({
</PopoverContent>
</Popover>
</Flex>
)
);
}
@@ -1,17 +1,26 @@
'use client'
import { ReactNode, useEffect, useState } from 'react'
import { Box, Button, FormLabel, Input, InputGroup, List, ListItem, VStack } from '@chakra-ui/react'
import { useCombobox, UseComboboxReturnValue } from 'downshift'
'use client';
import { ReactNode, useEffect, useState } from 'react';
import {
Box,
Button,
FormLabel,
Input,
InputGroup,
List,
ListItem,
VStack,
} from '@chakra-ui/react';
import { useCombobox, UseComboboxReturnValue } from 'downshift';
import { useTranslationClientSide } from '@/app/i18n/client'
import { useI18n } from '@/providers/i18n/i18nContext'
import { useTranslationClientSide } from '@/app/i18n/client';
import { useI18n } from '@/providers/i18n/i18nContext';
export const SingleSelectCombobox: <T>(props: {
dropdownItems: T[]
setSelectedItem: (value: T) => void
handleDropdownItemFilter: (dropdownItems: T[], inputValue: string) => T[]
handleDropdownItemDisplay: (dropdownItem: T) => ReactNode
initSelectedItem?: T
dropdownItems: T[];
setSelectedItem: (value: T) => void;
handleDropdownItemFilter: (dropdownItems: T[], inputValue: string) => T[];
handleDropdownItemDisplay: (dropdownItem: T) => ReactNode;
initSelectedItem?: T;
}) => JSX.Element = function <T>({
dropdownItems,
setSelectedItem,
@@ -19,18 +28,18 @@ export const SingleSelectCombobox: <T>(props: {
handleDropdownItemDisplay,
initSelectedItem,
}: {
dropdownItems: T[]
setSelectedItem: (value: T) => void
handleDropdownItemFilter: (dropdownItems: T[], inputValue: string) => T[]
handleDropdownItemDisplay: (dropdownItem: T) => ReactNode
initSelectedItem?: T
dropdownItems: T[];
setSelectedItem: (value: T) => void;
handleDropdownItemFilter: (dropdownItems: T[], inputValue: string) => T[];
handleDropdownItemDisplay: (dropdownItem: T) => ReactNode;
initSelectedItem?: T;
}) {
const { lng } = useI18n()
const { t } = useTranslationClientSide(lng, 'common')
const [getFilteredDropdownItems, setGetFilteredDropdownItems] = useState<T[]>(dropdownItems)
const { lng } = useI18n();
const { t } = useTranslationClientSide(lng, 'common');
const [getFilteredDropdownItems, setGetFilteredDropdownItems] = useState<T[]>(dropdownItems);
useEffect(() => {
setGetFilteredDropdownItems(dropdownItems)
}, [dropdownItems])
setGetFilteredDropdownItems(dropdownItems);
}, [dropdownItems]);
const {
isOpen: isComboboxOpen,
@@ -44,18 +53,18 @@ export const SingleSelectCombobox: <T>(props: {
}: UseComboboxReturnValue<T> = useCombobox({
items: getFilteredDropdownItems,
onInputValueChange: ({ inputValue }) => {
setGetFilteredDropdownItems(handleDropdownItemFilter(dropdownItems, inputValue))
setGetFilteredDropdownItems(handleDropdownItemFilter(dropdownItems, inputValue));
},
initialSelectedItem: initSelectedItem || undefined,
onSelectedItemChange: ({ selectedItem }) => {
const selectedDropdownItem = dropdownItems.find((item) => item === selectedItem)
const selectedDropdownItem = dropdownItems.find((item) => item === selectedItem);
if (selectedDropdownItem) {
setSelectedItem(selectedDropdownItem)
setSelectedItem(selectedDropdownItem);
}
},
})
});
return (
<Box w="full">
<VStack w="full" align="stretch" alignItems="flex-start" spacing="8px">
@@ -197,5 +206,5 @@ export const SingleSelectCombobox: <T>(props: {
))}
</List>
</Box>
)
}
);
};
@@ -1,20 +1,20 @@
'use client'
import { ReactNode, useEffect, useState } from 'react'
import { Box, Button, Flex, FlexProps, Input, InputGroup, List, ListItem } from '@chakra-ui/react'
import { useCombobox, UseComboboxReturnValue } from 'downshift'
'use client';
import { ReactNode, useEffect, useState } from 'react';
import { Box, Button, Flex, FlexProps, Input, InputGroup, List, ListItem } from '@chakra-ui/react';
import { useCombobox, UseComboboxReturnValue } from 'downshift';
import { useTranslationClientSide } from '@/app/i18n/client'
import { useI18n } from '@/providers/i18n/i18nContext'
import { useTranslationClientSide } from '@/app/i18n/client';
import { useI18n } from '@/providers/i18n/i18nContext';
export const SingleSelectComboboxUnstyle: <T>(props: {
dropdownItems: T[]
setSelectedItem: (value: T) => void
handleDropdownItemFilter: (dropdownItems: T[], inputValue: string) => T[]
handleDropdownItemDisplay: (dropdownItem: T) => ReactNode
handleInputDisplay?: (item: T) => string
initSelectedItem?: T
flexProps?: FlexProps
placeholder?: string
dropdownItems: T[];
setSelectedItem: (value: T) => void;
handleDropdownItemFilter: (dropdownItems: T[], inputValue: string) => T[];
handleDropdownItemDisplay: (dropdownItem: T) => ReactNode;
handleInputDisplay?: (item: T) => string;
initSelectedItem?: T;
flexProps?: FlexProps;
placeholder?: string;
}) => JSX.Element = function <T>({
dropdownItems,
setSelectedItem,
@@ -25,21 +25,21 @@ export const SingleSelectComboboxUnstyle: <T>(props: {
flexProps,
placeholder,
}: {
dropdownItems: T[]
setSelectedItem: (value: T) => void
handleDropdownItemFilter: (dropdownItems: T[], inputValue: string) => T[]
handleDropdownItemDisplay: (dropdownItem: T) => ReactNode
handleInputDisplay?: (item: T) => string
initSelectedItem?: T
flexProps?: FlexProps
placeholder?: string
dropdownItems: T[];
setSelectedItem: (value: T) => void;
handleDropdownItemFilter: (dropdownItems: T[], inputValue: string) => T[];
handleDropdownItemDisplay: (dropdownItem: T) => ReactNode;
handleInputDisplay?: (item: T) => string;
initSelectedItem?: T;
flexProps?: FlexProps;
placeholder?: string;
}) {
const { lng } = useI18n()
const { t } = useTranslationClientSide(lng, 'common')
const [getFilteredDropdownItems, setGetFilteredDropdownItems] = useState<T[]>(dropdownItems)
const { lng } = useI18n();
const { t } = useTranslationClientSide(lng, 'common');
const [getFilteredDropdownItems, setGetFilteredDropdownItems] = useState<T[]>(dropdownItems);
useEffect(() => {
setGetFilteredDropdownItems(dropdownItems)
}, [dropdownItems])
setGetFilteredDropdownItems(dropdownItems);
}, [dropdownItems]);
const {
isOpen: isComboboxOpen,
@@ -52,22 +52,22 @@ export const SingleSelectComboboxUnstyle: <T>(props: {
}: UseComboboxReturnValue<T> = useCombobox({
items: getFilteredDropdownItems,
onInputValueChange: ({ inputValue }) => {
setGetFilteredDropdownItems(handleDropdownItemFilter(dropdownItems, inputValue))
setGetFilteredDropdownItems(handleDropdownItemFilter(dropdownItems, inputValue));
},
initialSelectedItem: initSelectedItem || undefined,
itemToString: (item) => {
if (!item) return ''
return handleInputDisplay ? handleInputDisplay(item) : String(item)
if (!item) return '';
return handleInputDisplay ? handleInputDisplay(item) : String(item);
},
onSelectedItemChange: ({ selectedItem }) => {
const selectedDropdownItem = dropdownItems.find((item) => item === selectedItem)
const selectedDropdownItem = dropdownItems.find((item) => item === selectedItem);
if (selectedDropdownItem) {
setSelectedItem(selectedDropdownItem)
setSelectedItem(selectedDropdownItem);
}
},
})
});
return (
<Flex flexDirection="column" w="full" {...flexProps} position="relative">
@@ -195,5 +195,5 @@ export const SingleSelectComboboxUnstyle: <T>(props: {
))}
</List>
</Flex>
)
}
);
};
@@ -1,7 +1,7 @@
import { Button, ButtonProps, Flex, FlexProps, Text } from '@chakra-ui/react'
import { useTranslation } from 'next-i18next'
import { Button, ButtonProps, Flex, FlexProps, Text } from '@chakra-ui/react';
import { useTranslation } from 'next-i18next';
import { RightFirstIcon, ToLeftIcon } from '@/ui/icons'
import { RightFirstIcon, ToLeftIcon } from '@/ui/icons';
export default function SwitchPage({
totalPage,
@@ -12,14 +12,14 @@ export default function SwitchPage({
isPreviousData,
...props
}: {
currentPage: number
totalPage: number
totalItem: number
pageSize: number
isPreviousData?: boolean
setCurrentPage: (idx: number) => void
currentPage: number;
totalPage: number;
totalItem: number;
pageSize: number;
isPreviousData?: boolean;
setCurrentPage: (idx: number) => void;
} & FlexProps) {
const { t } = useTranslation()
const { t } = useTranslation();
const switchStyle: ButtonProps = {
width: '24px',
height: '24px',
@@ -38,7 +38,7 @@ export default function SwitchPage({
cursor: 'not-allowed',
minW: '0',
},
}
};
return (
<Flex minW="370px" h="32px" align={'center'} mb={'8px'} fontSize="14px" {...props}>
<Text fontSize="14px" color={'grayModern.500'}>
@@ -56,8 +56,8 @@ export default function SwitchPage({
minW="0"
boxSize="24px"
onClick={(e) => {
e.preventDefault()
setCurrentPage(1)
e.preventDefault();
setCurrentPage(1);
}}
>
<RightFirstIcon transform={'rotate(-180deg)'} />
@@ -70,8 +70,8 @@ export default function SwitchPage({
minW="0"
boxSize="24px"
onClick={(e) => {
e.preventDefault()
setCurrentPage(currentPage - 1)
e.preventDefault();
setCurrentPage(currentPage - 1);
}}
>
<ToLeftIcon />
@@ -88,8 +88,8 @@ export default function SwitchPage({
minW="0"
borderRadius={'50%'}
onClick={(e) => {
e.preventDefault()
setCurrentPage(currentPage + 1)
e.preventDefault();
setCurrentPage(currentPage + 1);
}}
>
<ToLeftIcon transform={'rotate(180deg)'} />
@@ -104,8 +104,8 @@ export default function SwitchPage({
borderRadius={'50%'}
mr={'10px'}
onClick={(e) => {
e.preventDefault()
setCurrentPage(totalPage)
e.preventDefault();
setCurrentPage(totalPage);
}}
>
<RightFirstIcon />
@@ -118,5 +118,5 @@ export default function SwitchPage({
/{t('Page')}
</Text>
</Flex>
)
);
}
@@ -8,8 +8,8 @@ import {
Th,
Thead,
Tr,
} from '@chakra-ui/react'
import { flexRender, Table as ReactTable } from '@tanstack/react-table'
} from '@chakra-ui/react';
import { flexRender, Table as ReactTable } from '@tanstack/react-table';
export function BaseTable<T extends unknown>({
table,
@@ -38,10 +38,10 @@ export function BaseTable<T extends unknown>({
>
{flexRender(header.column.columnDef.header, header.getContext())}
</Th>
)
);
})}
</Tr>
)
);
})}
</Thead>
<Tbody>
@@ -72,14 +72,14 @@ export function BaseTable<T extends unknown>({
<Td py="10px" key={cell.id} px="24px">
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</Td>
)
);
})}
</Tr>
)
);
})
)}
</Tbody>
</Table>
</TableContainer>
)
);
}
@@ -1,5 +1,5 @@
'use client'
import React, { useMemo, useState } from 'react'
'use client';
import React, { useMemo, useState } from 'react';
import {
Box,
Button,
@@ -28,33 +28,33 @@ import {
Thead,
Tr,
useDisclosure,
} from '@chakra-ui/react'
import { CurrencySymbol } from '@sealos/ui'
import { useMessage } from '@sealos/ui'
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
} from '@chakra-ui/react';
import { CurrencySymbol } from '@sealos/ui';
import { useMessage } from '@sealos/ui';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import {
Column,
createColumnHelper,
flexRender,
getCoreRowModel,
useReactTable,
} from '@tanstack/react-table'
import { TFunction } from 'i18next'
} from '@tanstack/react-table';
import { TFunction } from 'i18next';
import { createToken, deleteToken, getTokens, updateToken } from '@/api/platform'
import { useTranslationClientSide } from '@/app/i18n/client'
import { MyTooltip } from '@/components/common/MyTooltip'
import SwitchPage from '@/components/common/SwitchPage'
import { useI18n } from '@/providers/i18n/i18nContext'
import { useBackendStore } from '@/store/backend'
import { QueryKey } from '@/types/query-key'
import { TokenInfo } from '@/types/user/token'
import { ChainIcon } from '@/ui/icons/index'
import { createToken, deleteToken, getTokens, updateToken } from '@/api/platform';
import { useTranslationClientSide } from '@/app/i18n/client';
import { MyTooltip } from '@/components/common/MyTooltip';
import SwitchPage from '@/components/common/SwitchPage';
import { useI18n } from '@/providers/i18n/i18nContext';
import { useBackendStore } from '@/store/backend';
import { QueryKey } from '@/types/query-key';
import { TokenInfo } from '@/types/user/token';
import { ChainIcon } from '@/ui/icons/index';
export function KeyList(): JSX.Element {
const { lng } = useI18n()
const { t } = useTranslationClientSide(lng, 'common')
const { isOpen, onOpen, onClose } = useDisclosure()
const { lng } = useI18n();
const { t } = useTranslationClientSide(lng, 'common');
const { isOpen, onOpen, onClose } = useDisclosure();
return (
<>
@@ -78,7 +78,7 @@ export function KeyList(): JSX.Element {
{/* modal */}
<CreateKeyModal isOpen={isOpen} onClose={onClose} t={t} />
</>
)
);
}
export enum TableHeaderId {
@@ -100,7 +100,7 @@ enum KeyStatus {
}
const CustomHeader = ({ column, t }: { column: Column<TokenInfo>; t: TFunction }) => {
const { currencySymbol } = useBackendStore()
const { currencySymbol } = useBackendStore();
if (column.id === TableHeaderId.USED_AMOUNT) {
return (
<Flex alignItems={'center'} gap={'4px'}>
@@ -116,7 +116,7 @@ const CustomHeader = ({ column, t }: { column: Column<TokenInfo>; t: TFunction }
</Text>
<CurrencySymbol type={currencySymbol} />
</Flex>
)
);
}
return (
<Text
@@ -129,15 +129,15 @@ const CustomHeader = ({ column, t }: { column: Column<TokenInfo>; t: TFunction }
>
{t(column.id as TableHeaderId)}
</Text>
)
}
);
};
const ModelKeyTable = ({ t, onOpen }: { t: TFunction; onOpen: () => void }) => {
const aiproxyBackend = useBackendStore((state) => state.aiproxyBackend)
const [total, setTotal] = useState(0)
const [page, setPage] = useState(1)
const [pageSize, setPageSize] = useState(10)
const [openPopoverId, setOpenPopoverId] = useState<number | null>(null)
const aiproxyBackend = useBackendStore((state) => state.aiproxyBackend);
const [total, setTotal] = useState(0);
const [page, setPage] = useState(1);
const [pageSize, setPageSize] = useState(10);
const [openPopoverId, setOpenPopoverId] = useState<number | null>(null);
const { message } = useMessage({
warningBoxBg: '#FFFAEB',
@@ -146,28 +146,28 @@ const ModelKeyTable = ({ t, onOpen }: { t: TFunction; onOpen: () => void }) => {
successBoxBg: '#EDFBF3',
successIconBg: '#039855',
successIconFill: 'white',
})
const queryClient = useQueryClient()
});
const queryClient = useQueryClient();
const { data, isLoading } = useQuery({
queryKey: [QueryKey.GetTokens, page, pageSize],
queryFn: () => getTokens({ page, perPage: pageSize }),
refetchOnReconnect: true,
onSuccess(data) {
setTotal(data?.total || 0)
setTotal(data?.total || 0);
},
})
});
const deleteKeyMutation = useMutation((id: number) => deleteToken(id), {
onSuccess() {
queryClient.invalidateQueries([QueryKey.GetTokens])
queryClient.invalidateQueries([QueryKey.GetTokens]);
message({
status: 'success',
title: t('key.deleteSuccess'),
isClosable: true,
duration: 2000,
position: 'top',
})
});
},
onError(err: any) {
message({
@@ -176,22 +176,22 @@ const ModelKeyTable = ({ t, onOpen }: { t: TFunction; onOpen: () => void }) => {
description: err?.message || t('key.deleteFailed'),
isClosable: true,
position: 'top',
})
});
},
})
});
const updateKeyMutation = useMutation(
({ id, status }: { id: number; status: number }) => updateToken(id, status),
{
onSuccess() {
queryClient.invalidateQueries([QueryKey.GetTokens])
queryClient.invalidateQueries([QueryKey.GetTokens]);
message({
status: 'success',
title: t('key.updateSuccess'),
isClosable: true,
duration: 2000,
position: 'top',
})
});
},
onError(err: any) {
message({
@@ -200,22 +200,22 @@ const ModelKeyTable = ({ t, onOpen }: { t: TFunction; onOpen: () => void }) => {
description: err?.message ? t(err.message) : t('key.updateFailed'),
isClosable: true,
position: 'top',
})
});
},
}
)
);
// Update the button click handlers in the table actions column:
const handleStatusUpdate = (id: number, currentStatus: number) => {
const newStatus = currentStatus === KeyStatus.DISABLED ? KeyStatus.ENABLED : KeyStatus.DISABLED
updateKeyMutation.mutate({ id, status: newStatus })
}
const newStatus = currentStatus === KeyStatus.DISABLED ? KeyStatus.ENABLED : KeyStatus.DISABLED;
updateKeyMutation.mutate({ id, status: newStatus });
};
const handleDelete = (id: number) => {
deleteKeyMutation.mutate(id)
}
deleteKeyMutation.mutate(id);
};
const columnHelper = createColumnHelper<TokenInfo>()
const columnHelper = createColumnHelper<TokenInfo>();
const columns = [
columnHelper.accessor((row) => row.name, {
@@ -247,7 +247,7 @@ const ModelKeyTable = ({ t, onOpen }: { t: TFunction; onOpen: () => void }) => {
letterSpacing="0.5px"
cursor="pointer"
onClick={() => {
const key = 'sk-' + info.getValue()
const key = 'sk-' + info.getValue();
navigator.clipboard.writeText(key).then(
() => {
message({
@@ -256,7 +256,7 @@ const ModelKeyTable = ({ t, onOpen }: { t: TFunction; onOpen: () => void }) => {
isClosable: true,
duration: 2000,
position: 'top',
})
});
},
(err) => {
message({
@@ -265,9 +265,9 @@ const ModelKeyTable = ({ t, onOpen }: { t: TFunction; onOpen: () => void }) => {
description: err?.message || t('copyFailed'),
isClosable: true,
position: 'top',
})
});
}
)
);
}}
>
<MyTooltip label={t('copy')}>
@@ -296,7 +296,7 @@ const ModelKeyTable = ({ t, onOpen }: { t: TFunction; onOpen: () => void }) => {
id: TableHeaderId.LAST_USED_AT,
header: (props) => <CustomHeader column={props.column} t={t} />,
cell: (info) => {
const timestamp = info.getValue()
const timestamp = info.getValue();
if (timestamp && timestamp < 0) {
return (
@@ -310,7 +310,7 @@ const ModelKeyTable = ({ t, onOpen }: { t: TFunction; onOpen: () => void }) => {
>
{t('key.unused')}
</Text>
)
);
}
return (
@@ -324,37 +324,37 @@ const ModelKeyTable = ({ t, onOpen }: { t: TFunction; onOpen: () => void }) => {
>
{new Date(timestamp).toLocaleString()}
</Text>
)
);
},
}),
columnHelper.accessor((row) => row.status, {
id: TableHeaderId.STATUS,
header: (props) => <CustomHeader column={props.column} t={t} />,
cell: (info) => {
const status = info.getValue()
let statusText = ''
let statusColor = ''
const status = info.getValue();
let statusText = '';
let statusColor = '';
switch (status) {
case KeyStatus.ENABLED:
statusText = t('keystatus.enabled')
statusColor = 'green.600'
break
statusText = t('keystatus.enabled');
statusColor = 'green.600';
break;
case KeyStatus.DISABLED:
statusText = t('keystatus.disabled')
statusColor = 'red.600'
break
statusText = t('keystatus.disabled');
statusColor = 'red.600';
break;
case KeyStatus.EXPIRED:
statusText = t('keystatus.expired')
statusColor = 'orange.500'
break
statusText = t('keystatus.expired');
statusColor = 'orange.500';
break;
case KeyStatus.EXHAUSTED:
statusText = t('keystatus.exhausted')
statusColor = 'gray.500'
break
statusText = t('keystatus.exhausted');
statusColor = 'gray.500';
break;
default:
statusText = t('keystatus.unknown')
statusColor = 'gray.500'
statusText = t('keystatus.unknown');
statusColor = 'gray.500';
}
return (
@@ -368,7 +368,7 @@ const ModelKeyTable = ({ t, onOpen }: { t: TFunction; onOpen: () => void }) => {
>
{statusText}
</Text>
)
);
},
}),
@@ -393,11 +393,11 @@ const ModelKeyTable = ({ t, onOpen }: { t: TFunction; onOpen: () => void }) => {
id: TableHeaderId.USED_AMOUNT,
header: (props) => <CustomHeader column={props.column} t={t} />,
cell: (info) => {
const value = Number(info.getValue())
const value = Number(info.getValue());
// 获取小数部分的长度
const decimalLength = value.toString().split('.')[1]?.length || 0
const decimalLength = value.toString().split('.')[1]?.length || 0;
// 如果小数位超过6位则保留6位,否则保持原样
const formattedValue = decimalLength > 6 ? value.toFixed(6) : value
const formattedValue = decimalLength > 6 ? value.toFixed(6) : value;
return (
<Text
@@ -410,7 +410,7 @@ const ModelKeyTable = ({ t, onOpen }: { t: TFunction; onOpen: () => void }) => {
>
{formattedValue}
</Text>
)
);
},
}),
@@ -485,8 +485,8 @@ const ModelKeyTable = ({ t, onOpen }: { t: TFunction; onOpen: () => void }) => {
color: 'brightBlue.600',
}}
onClick={() => {
handleStatusUpdate(info.row.original.id, info.row.original.status)
setOpenPopoverId(null)
handleStatusUpdate(info.row.original.id, info.row.original.status);
setOpenPopoverId(null);
}}
>
<svg
@@ -532,8 +532,8 @@ const ModelKeyTable = ({ t, onOpen }: { t: TFunction; onOpen: () => void }) => {
color: 'brightBlue.600',
}}
onClick={() => {
setOpenPopoverId(null)
handleStatusUpdate(info.row.original.id, info.row.original.status)
setOpenPopoverId(null);
handleStatusUpdate(info.row.original.id, info.row.original.status);
}}
>
<svg
@@ -579,8 +579,8 @@ const ModelKeyTable = ({ t, onOpen }: { t: TFunction; onOpen: () => void }) => {
color: '#D92D20',
}}
onClick={() => {
handleDelete(info.row.original.id)
setOpenPopoverId(null)
handleDelete(info.row.original.id);
setOpenPopoverId(null);
}}
>
<svg
@@ -614,19 +614,19 @@ const ModelKeyTable = ({ t, onOpen }: { t: TFunction; onOpen: () => void }) => {
</Popover>
),
}),
]
];
const sortTableData = (data: TokenInfo[]) => {
return data.sort((a, b) => b.created_at - a.created_at)
}
return data.sort((a, b) => b.created_at - a.created_at);
};
const sortedData = useMemo(() => sortTableData(data?.tokens || []), [data])
const sortedData = useMemo(() => sortTableData(data?.tokens || []), [data]);
const table = useReactTable({
data: sortedData,
columns,
getCoreRowModel: getCoreRowModel(),
})
});
return (
<>
@@ -767,7 +767,7 @@ const ModelKeyTable = ({ t, onOpen }: { t: TFunction; onOpen: () => void }) => {
cursor="pointer"
whiteSpace="nowrap"
onClick={() => {
const endpoint = aiproxyBackend
const endpoint = aiproxyBackend;
navigator.clipboard.writeText(endpoint).then(
() => {
message({
@@ -776,7 +776,7 @@ const ModelKeyTable = ({ t, onOpen }: { t: TFunction; onOpen: () => void }) => {
isClosable: true,
duration: 2000,
position: 'top',
})
});
},
(err) => {
message({
@@ -785,9 +785,9 @@ const ModelKeyTable = ({ t, onOpen }: { t: TFunction; onOpen: () => void }) => {
description: err?.message || t('copyFailed'),
isClosable: true,
position: 'top',
})
});
}
)
);
}}
>
{aiproxyBackend}
@@ -857,10 +857,10 @@ const ModelKeyTable = ({ t, onOpen }: { t: TFunction; onOpen: () => void }) => {
>
{flexRender(header.column.columnDef.header, header.getContext())}
</Th>
)
);
})}
</Tr>
)
);
})}
</Thead>
<Tbody>
@@ -878,10 +878,10 @@ const ModelKeyTable = ({ t, onOpen }: { t: TFunction; onOpen: () => void }) => {
<Td key={cell.id}>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</Td>
)
);
})}
</Tr>
)
);
})}
</Tbody>
</Table>
@@ -900,22 +900,22 @@ const ModelKeyTable = ({ t, onOpen }: { t: TFunction; onOpen: () => void }) => {
</Flex>
)}
</>
)
}
);
};
function CreateKeyModal({
isOpen,
onClose,
t,
}: {
isOpen: boolean
onClose: () => void
t: TFunction
isOpen: boolean;
onClose: () => void;
t: TFunction;
}) {
const initialRef = React.useRef(null)
const [name, setName] = useState('')
const [error, setError] = useState('')
const queryClient = useQueryClient()
const initialRef = React.useRef(null);
const [name, setName] = useState('');
const [error, setError] = useState('');
const queryClient = useQueryClient();
const { message } = useMessage({
warningBoxBg: '#FFFAEB',
warningIconBg: '#F79009',
@@ -924,58 +924,58 @@ function CreateKeyModal({
successBoxBg: '#EDFBF3',
successIconBg: '#039855',
successIconFill: 'white',
})
});
const createKeyMutation = useMutation((name: string) => createToken(name), {
onSuccess(data) {
createKeyMutation.reset()
setName('')
queryClient.invalidateQueries([QueryKey.GetTokens])
createKeyMutation.reset();
setName('');
queryClient.invalidateQueries([QueryKey.GetTokens]);
message({
status: 'success',
title: t('key.createSuccess'),
isClosable: true,
duration: 2000,
position: 'top',
})
onClose()
});
onClose();
},
onError(err) {
console.error(err)
console.error(err);
message({
status: 'warning',
title: t('key.createFailed'),
description: err instanceof Error ? t(err.message as any) : t('key.createFailed'),
isClosable: true,
position: 'top',
})
});
},
})
});
const validateName = (value: string) => {
if (!value) {
setError(t('key.nameRequired'))
setError(t('key.nameRequired'));
} else if (value.length >= 32) {
setError(t('key.nameMaxLength'))
setError(t('key.nameMaxLength'));
} else if (!/^[A-Za-z0-9-]+$/.test(value)) {
setError(t('key.nameOnlyLettersAndNumbers'))
setError(t('key.nameOnlyLettersAndNumbers'));
} else {
setError('')
setError('');
}
}
};
const handleNameChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const newName = e.target.value
validateName(newName)
setName(newName)
}
const newName = e.target.value;
validateName(newName);
setName(newName);
};
const handleConfirm = () => {
if (error === '' && name !== '') {
createKeyMutation.mutate(name)
return
createKeyMutation.mutate(name);
return;
}
}
};
return (
<Modal isOpen={isOpen} onClose={onClose} isCentered initialFocusRef={initialRef}>
@@ -1098,7 +1098,7 @@ function CreateKeyModal({
</Flex>
</Flex>
</Modal>
)
);
}
export default KeyList
export default KeyList;
@@ -1,38 +1,38 @@
'use client'
import { Button, Flex, Text } from '@chakra-ui/react'
import Image, { StaticImageData } from 'next/image'
import Link from 'next/link'
import { usePathname } from 'next/navigation'
'use client';
import { Button, Flex, Text } from '@chakra-ui/react';
import Image, { StaticImageData } from 'next/image';
import Link from 'next/link';
import { usePathname } from 'next/navigation';
import { useTranslationClientSide } from '@/app/i18n/client'
import { useI18n } from '@/providers/i18n/i18nContext'
import { useBackendStore } from '@/store/backend'
import homeIcon from '@/ui/svg/icons/sidebar/home.svg'
import homeIcon_a from '@/ui/svg/icons/sidebar/home_a.svg'
import keysIcon from '@/ui/svg/icons/sidebar/key.svg'
import keysIcon_a from '@/ui/svg/icons/sidebar/key_a.svg'
import logsIcon from '@/ui/svg/icons/sidebar/logs.svg'
import logsIcon_a from '@/ui/svg/icons/sidebar/logs_a.svg'
import mcpIcon from '@/ui/svg/icons/sidebar/mcp.svg'
import mcpIcon_a from '@/ui/svg/icons/sidebar/mcp_a.svg'
import priceIcon from '@/ui/svg/icons/sidebar/price.svg'
import priceIcon_a from '@/ui/svg/icons/sidebar/price_a.svg'
import { useTranslationClientSide } from '@/app/i18n/client';
import { useI18n } from '@/providers/i18n/i18nContext';
import { useBackendStore } from '@/store/backend';
import homeIcon from '@/ui/svg/icons/sidebar/home.svg';
import homeIcon_a from '@/ui/svg/icons/sidebar/home_a.svg';
import keysIcon from '@/ui/svg/icons/sidebar/key.svg';
import keysIcon_a from '@/ui/svg/icons/sidebar/key_a.svg';
import logsIcon from '@/ui/svg/icons/sidebar/logs.svg';
import logsIcon_a from '@/ui/svg/icons/sidebar/logs_a.svg';
import mcpIcon from '@/ui/svg/icons/sidebar/mcp.svg';
import mcpIcon_a from '@/ui/svg/icons/sidebar/mcp_a.svg';
import priceIcon from '@/ui/svg/icons/sidebar/price.svg';
import priceIcon_a from '@/ui/svg/icons/sidebar/price_a.svg';
type Menu = {
id: string
url: string
value: string
icon: StaticImageData
activeIcon: StaticImageData
display: boolean
}
id: string;
url: string;
value: string;
icon: StaticImageData;
activeIcon: StaticImageData;
display: boolean;
};
const SideBar = (): JSX.Element => {
const pathname = usePathname()
const { lng } = useI18n()
const { t } = useTranslationClientSide(lng, 'common')
const pathname = usePathname();
const { lng } = useI18n();
const { t } = useTranslationClientSide(lng, 'common');
const { invitationUrl, docUrl } = useBackendStore()
const { invitationUrl, docUrl } = useBackendStore();
const menus: Menu[] = [
{
@@ -75,7 +75,7 @@ const SideBar = (): JSX.Element => {
activeIcon: mcpIcon_a,
display: true,
},
]
];
return (
<Flex
@@ -92,8 +92,8 @@ const SideBar = (): JSX.Element => {
{menus
.filter((menu) => menu.display)
.map((menu) => {
const fullUrl = `/${lng}${menu.url}`
const isActive = pathname === fullUrl || pathname.startsWith(fullUrl + '/')
const fullUrl = `/${lng}${menu.url}`;
const isActive = pathname === fullUrl || pathname.startsWith(fullUrl + '/');
return (
<Link href={fullUrl} key={menu.id} style={{ textDecoration: 'none' }}>
@@ -132,7 +132,7 @@ const SideBar = (): JSX.Element => {
</Text>
</Flex>
</Link>
)
);
})}
</Flex>
{/* doc */}
@@ -154,7 +154,7 @@ const SideBar = (): JSX.Element => {
bg="rgba(150, 153, 180, 0.10)"
_hover={{ bg: 'rgba(150, 153, 180, 0.15)' }}
onClick={() => {
window.open(docUrl, '_blank')
window.open(docUrl, '_blank');
}}
>
<Text
@@ -195,7 +195,7 @@ const SideBar = (): JSX.Element => {
bg="rgba(150, 153, 180, 0.10)"
_hover={{ bg: 'rgba(150, 153, 180, 0.15)' }}
onClick={() => {
window.open(invitationUrl, '_blank')
window.open(invitationUrl, '_blank');
}}
>
<Text
@@ -227,7 +227,7 @@ const SideBar = (): JSX.Element => {
)}
</Flex>
</Flex>
)
}
);
};
export default SideBar
export default SideBar;
@@ -1,12 +1,12 @@
'use client'
'use client';
import { ReactNode } from 'react'
import { ChakraProvider } from '@chakra-ui/react'
import { ReactNode } from 'react';
import { ChakraProvider } from '@chakra-ui/react';
import { theme } from '@/ui/chakraTheme'
import { theme } from '@/ui/chakraTheme';
export function ChakraProviders({ children }: { children: ReactNode }): JSX.Element {
return <ChakraProvider theme={theme}>{children}</ChakraProvider>
return <ChakraProvider theme={theme}>{children}</ChakraProvider>;
}
export default ChakraProviders
export default ChakraProviders;
@@ -1,16 +1,16 @@
'use client'
import { createContext, useContext } from 'react'
'use client';
import { createContext, useContext } from 'react';
const I18nContext = createContext<{ lng: string }>({ lng: 'en' })
const I18nContext = createContext<{ lng: string }>({ lng: 'en' });
export const useI18n = (): { lng: string } => useContext(I18nContext)
export const useI18n = (): { lng: string } => useContext(I18nContext);
export function I18nProvider({
children,
lng,
}: {
children: React.ReactNode
lng: string
children: React.ReactNode;
lng: string;
}): JSX.Element {
return <I18nContext.Provider value={{ lng }}>{children}</I18nContext.Provider>
return <I18nContext.Provider value={{ lng }}>{children}</I18nContext.Provider>;
}
@@ -1,7 +1,7 @@
'use client'
'use client';
import { useState } from 'react'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { useState } from 'react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
const QueryProvider = ({ children }: { children: React.ReactNode }) => {
const [queryClient] = useState(
@@ -15,9 +15,9 @@ const QueryProvider = ({ children }: { children: React.ReactNode }) => {
},
},
})
)
);
return <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
}
return <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>;
};
export default QueryProvider
export default QueryProvider;
@@ -1,4 +1,4 @@
import { Icon, IconProps } from '@chakra-ui/react'
import { Icon, IconProps } from '@chakra-ui/react';
export function ToLeftIcon(props: IconProps) {
return (
@@ -15,7 +15,7 @@ export function ToLeftIcon(props: IconProps) {
fill="#111824"
/>
</Icon>
)
);
}
export function RightFirstIcon(props: IconProps) {
@@ -33,7 +33,7 @@ export function RightFirstIcon(props: IconProps) {
fill="#111824"
/>
</Icon>
)
);
}
export const ChainIcon = (props: IconProps) => (
@@ -45,7 +45,7 @@ export const ChainIcon = (props: IconProps) => (
fill="currentColor"
/>
</Icon>
)
);
export const ListIcon = (props: IconProps) => (
<Icon viewBox="0 0 18 18" width="18" height="18" {...props}>
@@ -74,4 +74,4 @@ export const ListIcon = (props: IconProps) => (
fill="currentColor"
/>
</Icon>
)
);
@@ -1,53 +1,54 @@
import { StaticImageData } from 'next/image'
import DefaultIcon from '@/ui/svg/icons/mcp/default.svg'
import alipayIcon from '@/ui/svg/icons/mcp/alipay.svg'
import baiduMapIcon from '@/ui/svg/icons/mcp/baiduMap.svg'
import one2306Icon from '@/ui/svg/icons/mcp/12306.svg'
import bingIcon from '@/ui/svg/icons/mcp/bing.svg'
import amapIcon from '@/ui/svg/icons/mcp/amap.svg'
import searchIcon from '@/ui/svg/icons/mcp/search.svg'
import firecrawlIcon from '@/ui/svg/icons/mcp/firecrawl.svg'
import flomoIcon from '@/ui/svg/icons/mcp/flomo.svg'
import vis from '@/ui/svg/icons/mcp/vis.svg'
import hefengIcon from '@/ui/svg/icons/mcp/hefeng.svg'
import gezheIcon from '@/ui/svg/icons/mcp/gezhe.svg'
import tavilyIcon from '@/ui/svg/icons/mcp/tavily.svg'
import jinaIcon from '@/ui/svg/icons/mcp/jina.svg'
import howtocookIcon from '@/ui/svg/icons/mcp/howtocook.svg'
import timeIcon from '@/ui/svg/icons/mcp/time.svg'
import blenderIcon from '@/ui/svg/icons/mcp/blender.svg'
import browserbaseIcon from '@/ui/svg/icons/mcp/browserbase.svg'
import excelIcon from '@/ui/svg/icons/mcp/excel.svg'
import magicIcon from '@/ui/svg/icons/mcp/21dev.png'
import alibabacloudDmsIcon from '@/ui/svg/icons/mcp/alibabacloud-dms.svg'
import academicSearchIcon from '@/ui/svg/icons/mcp/academic-search.svg'
import allvoicelabIcon from '@/ui/svg/icons/mcp/allvoicelab.png'
import cfworkerIcon from '@/ui/svg/icons/mcp/cfworker.svg'
import elasticsearchIcon from '@/ui/svg/icons/mcp/elasticsearch.svg'
import everythingSearchIcon from '@/ui/svg/icons/mcp/everything-search.svg'
import filesystemIcon from '@/ui/svg/icons/mcp/filesystem.svg'
import gitIcon from '@/ui/svg/icons/mcp/git.svg'
import knowledgeGraphMemoryIcon from '@/ui/svg/icons/mcp/knowledge-graph-memory.svg'
import mysqlIcon from '@/ui/svg/icons/mcp/mysql.svg'
import oceanbaseIcon from '@/ui/svg/icons/mcp/oceanbase.svg'
import openmemoryIcon from '@/ui/svg/icons/mcp/openmemory.svg'
import officeWordIcon from '@/ui/svg/icons/mcp/office-word.svg'
import redbookSearchCommentIcon from '@/ui/svg/icons/mcp/redbook-search-comment.svg'
import puppeteerIcon from '@/ui/svg/icons/mcp/puppeteer.svg'
import playwrightIcon from '@/ui/svg/icons/mcp/playwright.svg'
import markdownifyIcon from '@/ui/svg/icons/mcp/markdownify.svg'
import redisIcon from '@/ui/svg/icons/mcp/redis.svg'
import sqliteIcon from '@/ui/svg/icons/mcp/sqlite.svg'
import stockAnalysisIcon from '@/ui/svg/icons/mcp/stock-analysis.svg'
import queryTableIcon from '@/ui/svg/icons/mcp/query-table.svg'
import unionpayIcon from '@/ui/svg/icons/mcp/unionpay.svg'
import wecomBotIcon from '@/ui/svg/icons/mcp/wecom-bot.svg'
import chatIcon from '@/ui/svg/icons/mcp/chat.svg'
import webcontentIcon from '@/ui/svg/icons/mcp/webcontent.svg'
import githubIcon from '@/ui/svg/icons/mcp/github.svg'
import notionIcon from '@/ui/svg/icons/mcp/notion.svg'
import zhipuaiIcon from '@/ui/svg/icons/mcp/zhipuai.svg'
import weatherIcon from '@/ui/svg/icons/mcp/weather.svg'
import { StaticImageData } from 'next/image';
import magicIcon from '@/ui/svg/icons/mcp/21dev.png';
import one2306Icon from '@/ui/svg/icons/mcp/12306.svg';
import academicSearchIcon from '@/ui/svg/icons/mcp/academic-search.svg';
import alibabacloudDmsIcon from '@/ui/svg/icons/mcp/alibabacloud-dms.svg';
import alipayIcon from '@/ui/svg/icons/mcp/alipay.svg';
import allvoicelabIcon from '@/ui/svg/icons/mcp/allvoicelab.png';
import amapIcon from '@/ui/svg/icons/mcp/amap.svg';
import baiduMapIcon from '@/ui/svg/icons/mcp/baiduMap.svg';
import bingIcon from '@/ui/svg/icons/mcp/bing.svg';
import blenderIcon from '@/ui/svg/icons/mcp/blender.svg';
import browserbaseIcon from '@/ui/svg/icons/mcp/browserbase.svg';
import cfworkerIcon from '@/ui/svg/icons/mcp/cfworker.svg';
import chatIcon from '@/ui/svg/icons/mcp/chat.svg';
import DefaultIcon from '@/ui/svg/icons/mcp/default.svg';
import elasticsearchIcon from '@/ui/svg/icons/mcp/elasticsearch.svg';
import everythingSearchIcon from '@/ui/svg/icons/mcp/everything-search.svg';
import excelIcon from '@/ui/svg/icons/mcp/excel.svg';
import filesystemIcon from '@/ui/svg/icons/mcp/filesystem.svg';
import firecrawlIcon from '@/ui/svg/icons/mcp/firecrawl.svg';
import flomoIcon from '@/ui/svg/icons/mcp/flomo.svg';
import gezheIcon from '@/ui/svg/icons/mcp/gezhe.svg';
import gitIcon from '@/ui/svg/icons/mcp/git.svg';
import githubIcon from '@/ui/svg/icons/mcp/github.svg';
import hefengIcon from '@/ui/svg/icons/mcp/hefeng.svg';
import howtocookIcon from '@/ui/svg/icons/mcp/howtocook.svg';
import jinaIcon from '@/ui/svg/icons/mcp/jina.svg';
import knowledgeGraphMemoryIcon from '@/ui/svg/icons/mcp/knowledge-graph-memory.svg';
import markdownifyIcon from '@/ui/svg/icons/mcp/markdownify.svg';
import mysqlIcon from '@/ui/svg/icons/mcp/mysql.svg';
import notionIcon from '@/ui/svg/icons/mcp/notion.svg';
import oceanbaseIcon from '@/ui/svg/icons/mcp/oceanbase.svg';
import officeWordIcon from '@/ui/svg/icons/mcp/office-word.svg';
import openmemoryIcon from '@/ui/svg/icons/mcp/openmemory.svg';
import playwrightIcon from '@/ui/svg/icons/mcp/playwright.svg';
import puppeteerIcon from '@/ui/svg/icons/mcp/puppeteer.svg';
import queryTableIcon from '@/ui/svg/icons/mcp/query-table.svg';
import redbookSearchCommentIcon from '@/ui/svg/icons/mcp/redbook-search-comment.svg';
import redisIcon from '@/ui/svg/icons/mcp/redis.svg';
import searchIcon from '@/ui/svg/icons/mcp/search.svg';
import sqliteIcon from '@/ui/svg/icons/mcp/sqlite.svg';
import stockAnalysisIcon from '@/ui/svg/icons/mcp/stock-analysis.svg';
import tavilyIcon from '@/ui/svg/icons/mcp/tavily.svg';
import timeIcon from '@/ui/svg/icons/mcp/time.svg';
import unionpayIcon from '@/ui/svg/icons/mcp/unionpay.svg';
import vis from '@/ui/svg/icons/mcp/vis.svg';
import weatherIcon from '@/ui/svg/icons/mcp/weather.svg';
import webcontentIcon from '@/ui/svg/icons/mcp/webcontent.svg';
import wecomBotIcon from '@/ui/svg/icons/mcp/wecom-bot.svg';
import zhipuaiIcon from '@/ui/svg/icons/mcp/zhipuai.svg';
export const mcpIcons = {
default: DefaultIcon,
@@ -100,9 +101,9 @@ export const mcpIcons = {
notion: notionIcon,
zhipuai: zhipuaiIcon,
weather: weatherIcon,
}
};
export const getMcpIcon = (mcpId: string): StaticImageData => {
const icon = mcpIcons[mcpId as keyof typeof mcpIcons] || mcpIcons['default']
return icon
}
const icon = mcpIcons[mcpId as keyof typeof mcpIcons] || mcpIcons['default'];
return icon;
};
@@ -1,36 +1,35 @@
import OpenAIIcon from '@/ui/svg/icons/modelist/openai.svg'
import QwenIcon from '@/ui/svg/icons/modelist/qianwen.svg'
import ChatglmIcon from '@/ui/svg/icons/modelist/chatglm.svg'
import DeepseekIcon from '@/ui/svg/icons/modelist/deepseek.svg'
import MoonshotIcon from '@/ui/svg/icons/modelist/moonshot.svg'
import SparkdeskIcon from '@/ui/svg/icons/modelist/sparkdesk.svg'
import DoubaoIcon from '@/ui/svg/icons/modelist/doubao.svg'
import BaaiIcon from '@/ui/svg/icons/modelist/baai.svg'
import HunyuanIcon from '@/ui/svg/icons/modelist/hunyuan.svg'
import MiniMaxIcon from '@/ui/svg/icons/modelist/minimax.svg'
import BaiduIcon from '@/ui/svg/icons/modelist/baidu.svg'
import GoogleGeminiIcon from '@/ui/svg/icons/modelist/google.svg'
import AlibabaIcon from '@/ui/svg/icons/modelist/alibaba.svg'
import FishAudioIcon from '@/ui/svg/icons/modelist/fishaudio.svg'
import StabilityAIIcon from '@/ui/svg/icons/modelist/stabilityai.svg'
import NeteaseIcon from '@/ui/svg/icons/modelist/netease.svg'
import AI360Icon from '@/ui/svg/icons/modelist/ai360.svg'
import AnthropicIcon from '@/ui/svg/icons/modelist/anthropic.svg'
import BaichuanIcon from '@/ui/svg/icons/modelist/baichuan.svg'
import MetaIcon from '@/ui/svg/icons/modelist/meta.svg'
import MistralIcon from '@/ui/svg/icons/modelist/mistral.svg'
import OpenChatIcon from '@/ui/svg/icons/modelist/openchat.svg'
import DefogIcon from '@/ui/svg/icons/modelist/defog.svg'
import NexusFlowIcon from '@/ui/svg/icons/modelist/nexusflow.svg'
import MicrosoftIcon from '@/ui/svg/icons/modelist/microsoft.svg'
import CohereIcon from '@/ui/svg/icons/modelist/cohere.svg'
import HuggingFaceIcon from '@/ui/svg/icons/modelist/huggingface.svg'
import LingyiWanwuIcon from '@/ui/svg/icons/modelist/lingyiwanwu.svg'
import StepFunIcon from '@/ui/svg/icons/modelist/stepfun.svg'
import DefaultIcon from '@/ui/svg/icons/modelist/default.svg'
import AbabIcon from '@/ui/svg/icons/modelist/minimax.svg'
import ErnieIcon from '@/ui/svg/icons/modelist/ernie.svg'
import AI360Icon from '@/ui/svg/icons/modelist/ai360.svg';
import AlibabaIcon from '@/ui/svg/icons/modelist/alibaba.svg';
import AnthropicIcon from '@/ui/svg/icons/modelist/anthropic.svg';
import BaaiIcon from '@/ui/svg/icons/modelist/baai.svg';
import BaichuanIcon from '@/ui/svg/icons/modelist/baichuan.svg';
import BaiduIcon from '@/ui/svg/icons/modelist/baidu.svg';
import ChatglmIcon from '@/ui/svg/icons/modelist/chatglm.svg';
import CohereIcon from '@/ui/svg/icons/modelist/cohere.svg';
import DeepseekIcon from '@/ui/svg/icons/modelist/deepseek.svg';
import DefaultIcon from '@/ui/svg/icons/modelist/default.svg';
import DefogIcon from '@/ui/svg/icons/modelist/defog.svg';
import DoubaoIcon from '@/ui/svg/icons/modelist/doubao.svg';
import ErnieIcon from '@/ui/svg/icons/modelist/ernie.svg';
import FishAudioIcon from '@/ui/svg/icons/modelist/fishaudio.svg';
import GoogleGeminiIcon from '@/ui/svg/icons/modelist/google.svg';
import HuggingFaceIcon from '@/ui/svg/icons/modelist/huggingface.svg';
import HunyuanIcon from '@/ui/svg/icons/modelist/hunyuan.svg';
import LingyiWanwuIcon from '@/ui/svg/icons/modelist/lingyiwanwu.svg';
import MetaIcon from '@/ui/svg/icons/modelist/meta.svg';
import MicrosoftIcon from '@/ui/svg/icons/modelist/microsoft.svg';
import MiniMaxIcon from '@/ui/svg/icons/modelist/minimax.svg';
import AbabIcon from '@/ui/svg/icons/modelist/minimax.svg';
import MistralIcon from '@/ui/svg/icons/modelist/mistral.svg';
import MoonshotIcon from '@/ui/svg/icons/modelist/moonshot.svg';
import NeteaseIcon from '@/ui/svg/icons/modelist/netease.svg';
import NexusFlowIcon from '@/ui/svg/icons/modelist/nexusflow.svg';
import OpenAIIcon from '@/ui/svg/icons/modelist/openai.svg';
import OpenChatIcon from '@/ui/svg/icons/modelist/openchat.svg';
import QwenIcon from '@/ui/svg/icons/modelist/qianwen.svg';
import SparkdeskIcon from '@/ui/svg/icons/modelist/sparkdesk.svg';
import StabilityAIIcon from '@/ui/svg/icons/modelist/stabilityai.svg';
import StepFunIcon from '@/ui/svg/icons/modelist/stepfun.svg';
export const modelIcons = {
openai: OpenAIIcon,
@@ -63,4 +62,4 @@ export const modelIcons = {
lingyiwanwu: LingyiWanwuIcon,
stepfun: StepFunIcon,
default: DefaultIcon,
}
};