feat: ai proxy v1 (#3898)
* feat: ai proxy v1 * perf: ai proxy channel crud * feat: ai proxy logs * feat: channel test * doc * update lock
This commit is contained in:
722
projects/app/src/pageComponents/account/model/AddModelBox.tsx
Normal file
722
projects/app/src/pageComponents/account/model/AddModelBox.tsx
Normal file
@@ -0,0 +1,722 @@
|
||||
import {
|
||||
Box,
|
||||
Flex,
|
||||
HStack,
|
||||
Table,
|
||||
TableContainer,
|
||||
Tbody,
|
||||
Td,
|
||||
Th,
|
||||
Thead,
|
||||
Tr,
|
||||
Switch,
|
||||
ModalBody,
|
||||
Input,
|
||||
ModalFooter,
|
||||
Button,
|
||||
ButtonProps
|
||||
} from '@chakra-ui/react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import React, { useMemo, useRef, useState } from 'react';
|
||||
import {
|
||||
ModelProviderList,
|
||||
ModelProviderIdType,
|
||||
getModelProvider
|
||||
} from '@fastgpt/global/core/ai/provider';
|
||||
import MySelect from '@fastgpt/web/components/common/MySelect';
|
||||
import { ModelTypeEnum } from '@fastgpt/global/core/ai/model';
|
||||
import Avatar from '@fastgpt/web/components/common/Avatar';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import { getSystemModelDefaultConfig, putSystemModel } from '@/web/core/ai/config';
|
||||
import { SystemModelItemType } from '@fastgpt/service/core/ai/type';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import MyNumberInput from '@fastgpt/web/components/common/Input/NumberInput';
|
||||
import MyTextarea from '@/components/common/Textarea/MyTextarea';
|
||||
import JsonEditor from '@fastgpt/web/components/common/Textarea/JsonEditor';
|
||||
import MyMenu from '@fastgpt/web/components/common/MyMenu';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
|
||||
import { Prompt_CQJson, Prompt_ExtractJson } from '@fastgpt/global/core/ai/prompt/agent';
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
|
||||
export const AddModelButton = ({
|
||||
onCreate,
|
||||
...props
|
||||
}: { onCreate: (type: ModelTypeEnum) => void } & ButtonProps) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<MyMenu
|
||||
trigger="hover"
|
||||
size="sm"
|
||||
Button={<Button {...props}>{t('account:create_model')}</Button>}
|
||||
menuList={[
|
||||
{
|
||||
children: [
|
||||
{
|
||||
label: t('common:model.type.chat'),
|
||||
onClick: () => onCreate(ModelTypeEnum.llm)
|
||||
},
|
||||
{
|
||||
label: t('common:model.type.embedding'),
|
||||
onClick: () => onCreate(ModelTypeEnum.embedding)
|
||||
},
|
||||
{
|
||||
label: t('common:model.type.tts'),
|
||||
onClick: () => onCreate(ModelTypeEnum.tts)
|
||||
},
|
||||
{
|
||||
label: t('common:model.type.stt'),
|
||||
onClick: () => onCreate(ModelTypeEnum.stt)
|
||||
},
|
||||
{
|
||||
label: t('common:model.type.reRank'),
|
||||
onClick: () => onCreate(ModelTypeEnum.rerank)
|
||||
}
|
||||
]
|
||||
}
|
||||
]}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const InputStyles = {
|
||||
maxW: '300px',
|
||||
bg: 'myGray.50',
|
||||
w: '100%',
|
||||
rows: 3
|
||||
};
|
||||
export const ModelEditModal = ({
|
||||
modelData,
|
||||
onSuccess,
|
||||
onClose
|
||||
}: {
|
||||
modelData: SystemModelItemType;
|
||||
onSuccess: () => void;
|
||||
onClose: () => void;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { feConfigs } = useSystemStore();
|
||||
|
||||
const { register, getValues, setValue, handleSubmit, watch, reset } =
|
||||
useForm<SystemModelItemType>({
|
||||
defaultValues: modelData
|
||||
});
|
||||
|
||||
const isCustom = !!modelData.isCustom;
|
||||
const isLLMModel = modelData?.type === ModelTypeEnum.llm;
|
||||
const isEmbeddingModel = modelData?.type === ModelTypeEnum.embedding;
|
||||
const isTTSModel = modelData?.type === ModelTypeEnum.tts;
|
||||
const isSTTModel = modelData?.type === ModelTypeEnum.stt;
|
||||
const isRerankModel = modelData?.type === ModelTypeEnum.rerank;
|
||||
|
||||
const provider = watch('provider');
|
||||
const providerData = useMemo(() => getModelProvider(provider), [provider]);
|
||||
|
||||
const providerList = useRef<{ label: any; value: ModelProviderIdType }[]>(
|
||||
ModelProviderList.map((item) => ({
|
||||
label: (
|
||||
<HStack>
|
||||
<Avatar src={item.avatar} w={'1rem'} />
|
||||
<Box>{t(item.name as any)}</Box>
|
||||
</HStack>
|
||||
),
|
||||
value: item.id
|
||||
}))
|
||||
);
|
||||
|
||||
const priceUnit = useMemo(() => {
|
||||
if (isLLMModel || isEmbeddingModel) return '/ 1k Tokens';
|
||||
if (isTTSModel) return `/ 1k ${t('common:unit.character')}`;
|
||||
if (isSTTModel) return `/ 60 ${t('common:unit.seconds')}`;
|
||||
return '';
|
||||
}, [isLLMModel, isEmbeddingModel, isTTSModel, t, isSTTModel]);
|
||||
|
||||
const { runAsync: updateModel, loading: updatingModel } = useRequest2(
|
||||
async (data: SystemModelItemType) => {
|
||||
return putSystemModel({
|
||||
model: data.model,
|
||||
metadata: data
|
||||
}).then(onSuccess);
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
onClose();
|
||||
},
|
||||
successToast: t('common:common.Success')
|
||||
}
|
||||
);
|
||||
|
||||
const [key, setKey] = useState(0);
|
||||
const { runAsync: loadDefaultConfig, loading: loadingDefaultConfig } = useRequest2(
|
||||
getSystemModelDefaultConfig,
|
||||
{
|
||||
onSuccess(res) {
|
||||
reset({
|
||||
...getValues(),
|
||||
...res
|
||||
});
|
||||
setTimeout(() => {
|
||||
setKey((prev) => prev + 1);
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return (
|
||||
<MyModal
|
||||
iconSrc={'modal/edit'}
|
||||
title={t('account:model.edit_model')}
|
||||
isOpen
|
||||
onClose={onClose}
|
||||
maxW={['90vw', '80vw']}
|
||||
w={'100%'}
|
||||
h={'100%'}
|
||||
>
|
||||
<ModalBody>
|
||||
<Flex gap={4} key={key}>
|
||||
<TableContainer flex={'1'}>
|
||||
<Table>
|
||||
<Thead>
|
||||
<Tr color={'myGray.600'}>
|
||||
<Th fontSize={'xs'}>{t('account:model.param_name')}</Th>
|
||||
<Th fontSize={'xs'}></Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
<Tr>
|
||||
<Td>
|
||||
<HStack spacing={1}>
|
||||
<Box>{t('account:model.model_id')}</Box>
|
||||
<QuestionTip label={t('account:model.model_id_tip')} />
|
||||
</HStack>
|
||||
</Td>
|
||||
<Td textAlign={'right'}>
|
||||
{isCustom ? (
|
||||
<Input {...register('model', { required: true })} {...InputStyles} />
|
||||
) : (
|
||||
modelData?.model
|
||||
)}
|
||||
</Td>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<Td>{t('common:model.provider')}</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<MySelect
|
||||
value={provider}
|
||||
onchange={(value) => setValue('provider', value)}
|
||||
list={providerList.current}
|
||||
{...InputStyles}
|
||||
/>
|
||||
</Td>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<Td>
|
||||
<HStack spacing={1}>
|
||||
<Box>{t('account:model.alias')}</Box>
|
||||
<QuestionTip label={t('account:model.alias_tip')} />
|
||||
</HStack>
|
||||
</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<Input {...register('name', { required: true })} {...InputStyles} />
|
||||
</Td>
|
||||
</Tr>
|
||||
{priceUnit && feConfigs?.isPlus && (
|
||||
<>
|
||||
<Tr>
|
||||
<Td>
|
||||
<HStack spacing={1}>
|
||||
<Box>{t('account:model.charsPointsPrice')}</Box>
|
||||
<QuestionTip label={t('account:model.charsPointsPrice_tip')} />
|
||||
</HStack>
|
||||
</Td>
|
||||
<Td>
|
||||
<Flex justify="flex-end">
|
||||
<HStack w={'100%'} maxW={'300px'}>
|
||||
<MyNumberInput
|
||||
flex={'1 0 0'}
|
||||
register={register}
|
||||
name={'charsPointsPrice'}
|
||||
step={0.01}
|
||||
/>
|
||||
<Box fontSize={'sm'}>{priceUnit}</Box>
|
||||
</HStack>
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
{isLLMModel && (
|
||||
<>
|
||||
<Tr>
|
||||
<Td>
|
||||
<HStack spacing={1}>
|
||||
<Box>{t('account:model.input_price')}</Box>
|
||||
<QuestionTip label={t('account:model.input_price_tip')} />
|
||||
</HStack>
|
||||
</Td>
|
||||
<Td>
|
||||
<Flex justify="flex-end">
|
||||
<HStack w={'100%'} maxW={'300px'}>
|
||||
<MyNumberInput
|
||||
flex={'1 0 0'}
|
||||
register={register}
|
||||
name={'inputPrice'}
|
||||
step={0.01}
|
||||
/>
|
||||
<Box fontSize={'sm'}>{priceUnit}</Box>
|
||||
</HStack>
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<Td>
|
||||
<HStack spacing={1}>
|
||||
<Box>{t('account:model.output_price')}</Box>
|
||||
<QuestionTip label={t('account:model.output_price_tip')} />
|
||||
</HStack>
|
||||
</Td>
|
||||
<Td>
|
||||
<Flex justify="flex-end">
|
||||
<HStack w={'100%'} maxW={'300px'}>
|
||||
<MyNumberInput
|
||||
flex={'1 0 0'}
|
||||
register={register}
|
||||
name={'outputPrice'}
|
||||
step={0.01}
|
||||
/>
|
||||
<Box fontSize={'sm'}>{priceUnit}</Box>
|
||||
</HStack>
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{isLLMModel && (
|
||||
<>
|
||||
<Tr>
|
||||
<Td>{t('common:core.ai.Max context')}</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<Flex justifyContent={'flex-end'}>
|
||||
<MyNumberInput
|
||||
register={register}
|
||||
isRequired
|
||||
name="maxContext"
|
||||
{...InputStyles}
|
||||
/>
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<Td>{t('account:model.max_quote')}</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<Flex justifyContent={'flex-end'}>
|
||||
<MyNumberInput
|
||||
register={register}
|
||||
isRequired
|
||||
name="quoteMaxToken"
|
||||
{...InputStyles}
|
||||
/>
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<Td>{t('common:core.chat.response.module maxToken')}</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<Flex justifyContent={'flex-end'}>
|
||||
<MyNumberInput register={register} name="maxResponse" {...InputStyles} />
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<Td>{t('account:model.max_temperature')}</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<Flex justifyContent={'flex-end'}>
|
||||
<MyNumberInput
|
||||
register={register}
|
||||
name="maxTemperature"
|
||||
step={0.1}
|
||||
{...InputStyles}
|
||||
/>
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<Td>
|
||||
<HStack spacing={1}>
|
||||
<Box>{t('account:model.show_top_p')}</Box>
|
||||
</HStack>
|
||||
</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<Flex justifyContent={'flex-end'}>
|
||||
<Switch {...register('showTopP')} />
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<Td>
|
||||
<HStack spacing={1}>
|
||||
<Box>{t('account:model.show_stop_sign')}</Box>
|
||||
</HStack>
|
||||
</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<Flex justifyContent={'flex-end'}>
|
||||
<Switch {...register('showStopSign')} />
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<Td>{t('account:model.response_format')}</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<JsonEditor
|
||||
value={JSON.stringify(getValues('responseFormatList'), null, 2)}
|
||||
resize
|
||||
onChange={(e) => {
|
||||
if (!e) {
|
||||
setValue('responseFormatList', []);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
setValue('responseFormatList', JSON.parse(e));
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}}
|
||||
{...InputStyles}
|
||||
/>
|
||||
</Td>
|
||||
</Tr>
|
||||
</>
|
||||
)}
|
||||
{isEmbeddingModel && (
|
||||
<>
|
||||
<Tr>
|
||||
<Td>
|
||||
<HStack spacing={1}>
|
||||
<Box>{t('account:model.normalization')}</Box>
|
||||
<QuestionTip label={t('account:model.normalization_tip')} />
|
||||
</HStack>
|
||||
</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<Flex justifyContent={'flex-end'}>
|
||||
<Switch {...register('normalization')} />
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<Td>
|
||||
<HStack spacing={1}>
|
||||
<Box>{t('account:model.default_token')}</Box>
|
||||
<QuestionTip label={t('account:model.default_token_tip')} />
|
||||
</HStack>
|
||||
</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<Flex justifyContent={'flex-end'}>
|
||||
<MyNumberInput
|
||||
register={register}
|
||||
isRequired
|
||||
name="defaultToken"
|
||||
{...InputStyles}
|
||||
/>
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<Td>{t('common:core.ai.Max context')}</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<Flex justifyContent={'flex-end'}>
|
||||
<MyNumberInput
|
||||
register={register}
|
||||
isRequired
|
||||
name="maxToken"
|
||||
{...InputStyles}
|
||||
/>
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<Td>
|
||||
<HStack spacing={1}>
|
||||
<Box>{t('account:model.defaultConfig')}</Box>
|
||||
<QuestionTip label={t('account:model.defaultConfig_tip')} />
|
||||
</HStack>
|
||||
</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<Flex justifyContent={'flex-end'}>
|
||||
<JsonEditor
|
||||
value={JSON.stringify(getValues('defaultConfig'), null, 2)}
|
||||
onChange={(e) => {
|
||||
if (!e) {
|
||||
setValue('defaultConfig', {});
|
||||
return;
|
||||
}
|
||||
try {
|
||||
setValue('defaultConfig', JSON.parse(e));
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}}
|
||||
{...InputStyles}
|
||||
/>
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
</>
|
||||
)}
|
||||
{isTTSModel && (
|
||||
<>
|
||||
<Tr>
|
||||
<Td>
|
||||
<HStack spacing={1}>
|
||||
<Box>{t('account:model.voices')}</Box>
|
||||
<QuestionTip label={t('account:model.voices_tip')} />
|
||||
</HStack>
|
||||
</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<Flex justifyContent={'flex-end'}>
|
||||
<JsonEditor
|
||||
value={JSON.stringify(getValues('voices'), null, 2)}
|
||||
onChange={(e) => {
|
||||
try {
|
||||
setValue('voices', JSON.parse(e));
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}}
|
||||
{...InputStyles}
|
||||
/>
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
</>
|
||||
)}
|
||||
<Tr>
|
||||
<Td>
|
||||
<HStack spacing={1}>
|
||||
<Box>{t('account:model.request_url')}</Box>
|
||||
<QuestionTip label={t('account:model.request_url_tip')} />
|
||||
</HStack>
|
||||
</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<Input {...register('requestUrl')} {...InputStyles} />
|
||||
</Td>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<Td>
|
||||
<HStack spacing={1}>
|
||||
<Box>{t('account:model.request_auth')}</Box>
|
||||
<QuestionTip label={t('account:model.request_auth_tip')} />
|
||||
</HStack>
|
||||
</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<Input {...register('requestAuth')} {...InputStyles} />
|
||||
</Td>
|
||||
</Tr>
|
||||
</Tbody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
{isLLMModel && (
|
||||
<TableContainer flex={'1'}>
|
||||
<Table>
|
||||
<Thead>
|
||||
<Tr color={'myGray.600'}>
|
||||
<Th fontSize={'xs'}>{t('account:model.param_name')}</Th>
|
||||
<Th fontSize={'xs'}></Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
<Tr>
|
||||
<Td>
|
||||
<HStack spacing={1}>
|
||||
<Box>{t('account:model.tool_choice')}</Box>
|
||||
<QuestionTip label={t('account:model.tool_choice_tip')} />
|
||||
</HStack>
|
||||
</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<Flex justifyContent={'flex-end'}>
|
||||
<Switch {...register('toolChoice')} />
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<Td>
|
||||
<HStack spacing={1}>
|
||||
<Box>{t('account:model.function_call')}</Box>
|
||||
<QuestionTip label={t('account:model.function_call_tip')} />
|
||||
</HStack>
|
||||
</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<Flex justifyContent={'flex-end'}>
|
||||
<Switch {...register('functionCall')} />
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<Td>
|
||||
<HStack spacing={1}>
|
||||
<Box>{t('account:model.vision')}</Box>
|
||||
<QuestionTip label={t('account:model.vision_tip')} />
|
||||
</HStack>
|
||||
</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<Flex justifyContent={'flex-end'}>
|
||||
<Switch {...register('vision')} />
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<Td>
|
||||
<HStack spacing={1}>
|
||||
<Box>{t('account:model.reasoning')}</Box>
|
||||
<QuestionTip label={t('account:model.reasoning_tip')} />
|
||||
</HStack>
|
||||
</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<Flex justifyContent={'flex-end'}>
|
||||
<Switch {...register('reasoning')} />
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
{feConfigs?.isPlus && (
|
||||
<Tr>
|
||||
<Td>
|
||||
<HStack spacing={1}>
|
||||
<Box>{t('account:model.censor')}</Box>
|
||||
<QuestionTip label={t('account:model.censor_tip')} />
|
||||
</HStack>
|
||||
</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<Flex justifyContent={'flex-end'}>
|
||||
<Switch {...register('censor')} />
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
)}
|
||||
<Tr>
|
||||
<Td>{t('account:model.dataset_process')}</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<Flex justifyContent={'flex-end'}>
|
||||
<Switch {...register('datasetProcess')} />
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<Td>{t('account:model.used_in_classify')}</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<Flex justifyContent={'flex-end'}>
|
||||
<Switch {...register('usedInClassify')} />
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<Td>{t('account:model.used_in_extract_fields')}</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<Flex justifyContent={'flex-end'}>
|
||||
<Switch {...register('usedInExtractFields')} />
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<Td>{t('account:model.used_in_tool_call')}</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<Flex justifyContent={'flex-end'}>
|
||||
<Switch {...register('usedInToolCall')} />
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<Td>
|
||||
<HStack spacing={1}>
|
||||
<Box>{t('account:model.default_system_chat_prompt')}</Box>
|
||||
<QuestionTip label={t('account:model.default_system_chat_prompt_tip')} />
|
||||
</HStack>
|
||||
</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<MyTextarea {...register('defaultSystemChatPrompt')} {...InputStyles} />
|
||||
</Td>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<Td>
|
||||
<HStack spacing={1}>
|
||||
<Box>{t('account:model.custom_cq_prompt')}</Box>
|
||||
<QuestionTip
|
||||
label={t('account:model.custom_cq_prompt_tip', { prompt: Prompt_CQJson })}
|
||||
/>
|
||||
</HStack>
|
||||
</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<MyTextarea {...register('customCQPrompt')} {...InputStyles} />
|
||||
</Td>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<Td>
|
||||
<HStack spacing={1}>
|
||||
<Box>{t('account:model.custom_extract_prompt')}</Box>
|
||||
<QuestionTip
|
||||
label={t('account:model.custom_extract_prompt_tip', {
|
||||
prompt: Prompt_ExtractJson
|
||||
})}
|
||||
/>
|
||||
</HStack>
|
||||
</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<MyTextarea {...register('customExtractPrompt')} {...InputStyles} />
|
||||
</Td>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<Td>
|
||||
<HStack spacing={1}>
|
||||
<Box>{t('account:model.default_config')}</Box>
|
||||
<QuestionTip label={t('account:model.default_config_tip')} />
|
||||
</HStack>
|
||||
</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<JsonEditor
|
||||
value={JSON.stringify(getValues('defaultConfig'), null, 2)}
|
||||
resize
|
||||
onChange={(e) => {
|
||||
if (!e) {
|
||||
setValue('defaultConfig', {});
|
||||
return;
|
||||
}
|
||||
try {
|
||||
setValue('defaultConfig', JSON.parse(e));
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}}
|
||||
{...InputStyles}
|
||||
/>
|
||||
</Td>
|
||||
</Tr>
|
||||
</Tbody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
)}
|
||||
</Flex>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
{!modelData.isCustom && (
|
||||
<Button
|
||||
isLoading={loadingDefaultConfig}
|
||||
variant={'whiteBase'}
|
||||
mr={4}
|
||||
onClick={() => loadDefaultConfig(modelData.model)}
|
||||
>
|
||||
{t('account:reset_default')}
|
||||
</Button>
|
||||
)}
|
||||
<Button variant={'whiteBase'} mr={4} onClick={onClose}>
|
||||
{t('common:common.Cancel')}
|
||||
</Button>
|
||||
<Button isLoading={updatingModel} onClick={handleSubmit(updateModel)}>
|
||||
{t('common:common.Confirm')}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</MyModal>
|
||||
);
|
||||
};
|
||||
|
||||
export default function Dom() {
|
||||
return <></>;
|
||||
}
|
||||
@@ -0,0 +1,499 @@
|
||||
import { aiproxyIdMap } from '@/global/aiproxy/constants';
|
||||
import { ChannelInfoType } from '@/global/aiproxy/type';
|
||||
import {
|
||||
Box,
|
||||
BoxProps,
|
||||
Button,
|
||||
Flex,
|
||||
Input,
|
||||
MenuItemProps,
|
||||
ModalBody,
|
||||
ModalFooter,
|
||||
useDisclosure,
|
||||
Menu,
|
||||
MenuButton,
|
||||
MenuList,
|
||||
MenuItem,
|
||||
HStack,
|
||||
useOutsideClick
|
||||
} from '@chakra-ui/react';
|
||||
import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
import MySelect from '@fastgpt/web/components/common/MySelect';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import React, { useCallback, useMemo, useRef, useState } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { AddModelButton } from '../AddModelBox';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { SystemModelItemType } from '@fastgpt/service/core/ai/type';
|
||||
import { ModelTypeEnum } from '@fastgpt/global/core/ai/model';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import { getSystemModelList } from '@/web/core/ai/config';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import { getModelProvider } from '@fastgpt/global/core/ai/provider';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import MyAvatar from '@fastgpt/web/components/common/Avatar';
|
||||
import MyTag from '@fastgpt/web/components/common/Tag/index';
|
||||
import { useCopyData } from '@fastgpt/web/hooks/useCopyData';
|
||||
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
|
||||
import JsonEditor from '@fastgpt/web/components/common/Textarea/JsonEditor';
|
||||
import { getChannelProviders, postCreateChannel, putChannel } from '@/web/core/ai/channel';
|
||||
import CopyBox from '@fastgpt/web/components/common/String/CopyBox';
|
||||
|
||||
const ModelEditModal = dynamic(() => import('../AddModelBox').then((mod) => mod.ModelEditModal));
|
||||
|
||||
const LabelStyles: BoxProps = {
|
||||
fontSize: 'sm',
|
||||
color: 'myGray.900',
|
||||
flex: '0 0 70px'
|
||||
};
|
||||
const EditChannelModal = ({
|
||||
defaultConfig,
|
||||
onClose,
|
||||
onSuccess
|
||||
}: {
|
||||
defaultConfig: ChannelInfoType;
|
||||
onClose: () => void;
|
||||
onSuccess: () => void;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { defaultModels } = useSystemStore();
|
||||
const isEdit = defaultConfig.id !== 0;
|
||||
|
||||
const { register, handleSubmit, watch, setValue } = useForm({
|
||||
defaultValues: defaultConfig
|
||||
});
|
||||
|
||||
const providerType = watch('type');
|
||||
const { data: providerList = [] } = useRequest2(
|
||||
() =>
|
||||
getChannelProviders().then((res) => {
|
||||
return Object.entries(res)
|
||||
.map(([key, value]) => {
|
||||
const mapData = aiproxyIdMap[key as any] ?? {
|
||||
label: value.name,
|
||||
provider: 'Other'
|
||||
};
|
||||
const provider = getModelProvider(mapData.provider);
|
||||
return {
|
||||
order: provider.order,
|
||||
defaultBaseUrl: value.defaultBaseUrl,
|
||||
keyHelp: value.keyHelp,
|
||||
icon: provider.avatar,
|
||||
label: t(mapData.label as any),
|
||||
value: Number(key)
|
||||
};
|
||||
})
|
||||
.sort((a, b) => a.order - b.order);
|
||||
}),
|
||||
{
|
||||
manual: false
|
||||
}
|
||||
);
|
||||
const selectedProvider = useMemo(() => {
|
||||
const res = providerList.find((item) => item.value === providerType);
|
||||
return res;
|
||||
}, [providerList, providerType]);
|
||||
|
||||
const [editModelData, setEditModelData] = useState<SystemModelItemType>();
|
||||
const onCreateModel = (type: ModelTypeEnum) => {
|
||||
const defaultModel = defaultModels[type];
|
||||
|
||||
setEditModelData({
|
||||
...defaultModel,
|
||||
model: '',
|
||||
name: '',
|
||||
charsPointsPrice: 0,
|
||||
inputPrice: undefined,
|
||||
outputPrice: undefined,
|
||||
|
||||
isCustom: true,
|
||||
isActive: true,
|
||||
// @ts-ignore
|
||||
type
|
||||
});
|
||||
};
|
||||
|
||||
const models = watch('models');
|
||||
const {
|
||||
data: systemModelList = [],
|
||||
runAsync: refreshSystemModelList,
|
||||
loading: loadingModels
|
||||
} = useRequest2(getSystemModelList, {
|
||||
manual: false
|
||||
});
|
||||
const modelList = useMemo(() => {
|
||||
const currentProvider = aiproxyIdMap[providerType]?.provider;
|
||||
return systemModelList
|
||||
.map((item) => {
|
||||
const provider = getModelProvider(item.provider);
|
||||
|
||||
return {
|
||||
provider: item.provider,
|
||||
icon: provider.avatar,
|
||||
label: item.model,
|
||||
value: item.model
|
||||
};
|
||||
})
|
||||
.sort((a, b) => {
|
||||
// sort by provider, same provider first
|
||||
if (a.provider === currentProvider && b.provider !== currentProvider) return -1;
|
||||
if (a.provider !== currentProvider && b.provider === currentProvider) return 1;
|
||||
return 0;
|
||||
});
|
||||
}, [providerType, systemModelList]);
|
||||
|
||||
const modelMapping = watch('model_mapping');
|
||||
|
||||
const { runAsync: onSubmit, loading: loadingCreate } = useRequest2(
|
||||
(data: ChannelInfoType) => {
|
||||
if (data.models.length === 0) {
|
||||
return Promise.reject(t('account_model:selected_model_empty'));
|
||||
}
|
||||
return isEdit ? putChannel(data) : postCreateChannel(data);
|
||||
},
|
||||
{
|
||||
onSuccess() {
|
||||
onSuccess();
|
||||
onClose();
|
||||
},
|
||||
successToast: isEdit ? t('common:common.Update Success') : t('common:common.Create Success'),
|
||||
manual: true
|
||||
}
|
||||
);
|
||||
|
||||
const isLoading = loadingModels || loadingCreate;
|
||||
|
||||
return (
|
||||
<>
|
||||
<MyModal
|
||||
isLoading={isLoading}
|
||||
iconSrc={'modal/setting'}
|
||||
title={t('account_model:edit_channel')}
|
||||
onClose={onClose}
|
||||
w={'100%'}
|
||||
maxW={['90vw', '800px']}
|
||||
>
|
||||
<ModalBody>
|
||||
{/* Chnnel name */}
|
||||
<Box>
|
||||
<FormLabel required {...LabelStyles}>
|
||||
{t('account_model:channel_name')}
|
||||
</FormLabel>
|
||||
<Input mt={1} {...register('name', { required: true })} />
|
||||
</Box>
|
||||
{/* Provider */}
|
||||
<Box alignItems={'center'} mt={4}>
|
||||
<FormLabel required {...LabelStyles}>
|
||||
{t('account_model:channel_type')}
|
||||
</FormLabel>
|
||||
<Box mt={1}>
|
||||
<MySelect
|
||||
list={providerList}
|
||||
placeholder={t('account_model:select_provider_placeholder')}
|
||||
value={providerType}
|
||||
isSearch
|
||||
onchange={(val) => {
|
||||
setValue('type', val);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
{/* Model */}
|
||||
<Box mt={4}>
|
||||
<Flex alignItems={'center'}>
|
||||
<FormLabel required flex={'1 0 0'}>
|
||||
{t('account_model:model')}({models.length})
|
||||
</FormLabel>
|
||||
|
||||
<AddModelButton onCreate={onCreateModel} size={'sm'} variant={'outline'} />
|
||||
<Button ml={2} size={'sm'} variant={'outline'} onClick={() => setValue('models', [])}>
|
||||
{t('account_model:clear_model')}
|
||||
</Button>
|
||||
</Flex>
|
||||
<Box mt={2}>
|
||||
<MultipleSelect
|
||||
value={models}
|
||||
list={modelList}
|
||||
onSelect={(val) => {
|
||||
setValue('models', val);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
{/* Mapping */}
|
||||
<Box mt={4}>
|
||||
<HStack>
|
||||
<FormLabel>{t('account_model:mapping')}</FormLabel>
|
||||
<QuestionTip label={t('account_model:mapping_tip')} />
|
||||
</HStack>
|
||||
<Box mt={2}>
|
||||
<JsonEditor
|
||||
value={JSON.stringify(modelMapping, null, 2)}
|
||||
onChange={(val) => {
|
||||
if (!val) {
|
||||
setValue('model_mapping', {});
|
||||
} else {
|
||||
try {
|
||||
setValue('model_mapping', JSON.parse(val));
|
||||
} catch (error) {}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
{/* url and key */}
|
||||
<Box mt={4}>
|
||||
<Flex alignItems={'center'}>
|
||||
<FormLabel>{t('account_model:base_url')}</FormLabel>
|
||||
{selectedProvider && (
|
||||
<Flex alignItems={'center'} fontSize={'xs'}>
|
||||
<Box>{'('}</Box>
|
||||
<Box mr={1}>{t('account_model:default_url')}:</Box>
|
||||
<CopyBox value={selectedProvider?.defaultBaseUrl || ''}>
|
||||
{selectedProvider?.defaultBaseUrl || ''}
|
||||
</CopyBox>
|
||||
<Box>{')'}</Box>
|
||||
</Flex>
|
||||
)}
|
||||
</Flex>
|
||||
<Input
|
||||
mt={1}
|
||||
{...register('base_url')}
|
||||
placeholder={selectedProvider?.defaultBaseUrl || 'https://api.openai.com/v1'}
|
||||
/>
|
||||
</Box>
|
||||
<Box mt={4}>
|
||||
<Flex alignItems={'center'}>
|
||||
<FormLabel>{t('account_model:api_key')}</FormLabel>
|
||||
{selectedProvider?.keyHelp && (
|
||||
<Flex alignItems={'center'} fontSize={'xs'}>
|
||||
<Box>{'('}</Box>
|
||||
<Box mr={1}>{t('account_model:key_type')}</Box>
|
||||
<Box>{selectedProvider.keyHelp}</Box>
|
||||
<Box>{')'}</Box>
|
||||
</Flex>
|
||||
)}
|
||||
</Flex>
|
||||
<Input
|
||||
mt={1}
|
||||
{...register('key')}
|
||||
placeholder={selectedProvider?.keyHelp || 'sk-1234567890'}
|
||||
/>
|
||||
</Box>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button variant={'outline'} onClick={onClose} mr={4}>
|
||||
{t('common:common.Cancel')}
|
||||
</Button>
|
||||
<Button variant={'primary'} onClick={handleSubmit(onSubmit)}>
|
||||
{isEdit ? t('common:common.Update') : t('common:new_create')}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</MyModal>
|
||||
{!!editModelData && (
|
||||
<ModelEditModal
|
||||
modelData={editModelData}
|
||||
onSuccess={refreshSystemModelList}
|
||||
onClose={() => setEditModelData(undefined)}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default EditChannelModal;
|
||||
|
||||
type SelectProps = {
|
||||
list: {
|
||||
icon?: string;
|
||||
label: string;
|
||||
value: string;
|
||||
}[];
|
||||
value: string[];
|
||||
onSelect: (val: string[]) => void;
|
||||
};
|
||||
const menuItemStyles: MenuItemProps = {
|
||||
borderRadius: 'sm',
|
||||
py: 2,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
_hover: {
|
||||
backgroundColor: 'myGray.100'
|
||||
},
|
||||
_notLast: {
|
||||
mb: 0.5
|
||||
}
|
||||
};
|
||||
const MultipleSelect = ({ value = [], list = [], onSelect }: SelectProps) => {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const BoxRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const { t } = useTranslation();
|
||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||
const { copyData } = useCopyData();
|
||||
|
||||
const onclickItem = useCallback(
|
||||
(val: string) => {
|
||||
if (value.includes(val)) {
|
||||
onSelect(value.filter((i) => i !== val));
|
||||
} else {
|
||||
onSelect([...value, val]);
|
||||
BoxRef.current?.scrollTo({
|
||||
top: BoxRef.current.scrollHeight
|
||||
});
|
||||
}
|
||||
},
|
||||
[value, onSelect]
|
||||
);
|
||||
|
||||
const [search, setSearch] = useState('');
|
||||
|
||||
const filterUnSelected = useMemo(() => {
|
||||
return list
|
||||
.filter((item) => !value.includes(item.value))
|
||||
.filter((item) => {
|
||||
if (!search) return true;
|
||||
const regx = new RegExp(search, 'i');
|
||||
return regx.test(item.label);
|
||||
});
|
||||
}, [list, value, search]);
|
||||
|
||||
useOutsideClick({
|
||||
ref,
|
||||
handler: () => {
|
||||
onClose();
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<Box ref={ref}>
|
||||
<Menu autoSelect={false} isOpen={isOpen} strategy={'fixed'} matchWidth closeOnSelect={false}>
|
||||
<Box
|
||||
position={'relative'}
|
||||
py={2}
|
||||
borderRadius={'md'}
|
||||
border={'base'}
|
||||
userSelect={'none'}
|
||||
cursor={'pointer'}
|
||||
_active={{
|
||||
transform: 'none'
|
||||
}}
|
||||
_hover={{
|
||||
borderColor: 'primary.300'
|
||||
}}
|
||||
{...(isOpen
|
||||
? {
|
||||
boxShadow: '0px 0px 4px #A8DBFF',
|
||||
borderColor: 'primary.500',
|
||||
onClick: onClose
|
||||
}
|
||||
: {
|
||||
onClick: () => {
|
||||
onOpen();
|
||||
setSearch('');
|
||||
}
|
||||
})}
|
||||
>
|
||||
<MenuButton zIndex={0} position={'absolute'} bottom={0} left={0} right={0} top={0} />
|
||||
<Flex
|
||||
ref={BoxRef}
|
||||
position={'relative'}
|
||||
alignItems={value.length === 0 ? 'center' : 'flex-start'}
|
||||
gap={2}
|
||||
px={2}
|
||||
pb={0}
|
||||
overflowY={'auto'}
|
||||
maxH={'200px'}
|
||||
>
|
||||
{value.length === 0 ? (
|
||||
<Box flex={'1 0 0'} color={'myGray.500'} fontSize={'xs'}>
|
||||
{t('account_model:select_model_placeholder')}
|
||||
</Box>
|
||||
) : (
|
||||
<Flex flex={'1 0 0'} alignItems={'center'} gap={2} flexWrap={'wrap'}>
|
||||
{value.map((item) => (
|
||||
<MyTag
|
||||
key={item}
|
||||
type="borderSolid"
|
||||
colorSchema="gray"
|
||||
bg={'myGray.150'}
|
||||
color={'myGray.900'}
|
||||
_hover={{
|
||||
bg: 'myGray.250'
|
||||
}}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
copyData(item, t('account_model:copy_model_id_success'));
|
||||
}}
|
||||
>
|
||||
<Box>{item}</Box>
|
||||
<MyIcon
|
||||
ml={0.5}
|
||||
name={'common/closeLight'}
|
||||
w={'14px'}
|
||||
h={'14px'}
|
||||
_hover={{
|
||||
color: 'red.600'
|
||||
}}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onclickItem(item);
|
||||
}}
|
||||
/>
|
||||
</MyTag>
|
||||
))}
|
||||
{isOpen && (
|
||||
<Input
|
||||
key={'search'}
|
||||
variant={'unstyled'}
|
||||
w={'150px'}
|
||||
h={'24px'}
|
||||
autoFocus
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
placeholder={t('account_model:search_model')}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
)}
|
||||
<MyIcon name={'core/chat/chevronDown'} color={'myGray.600'} w={4} h={4} />
|
||||
</Flex>
|
||||
</Box>
|
||||
|
||||
<MenuList
|
||||
px={'6px'}
|
||||
py={'6px'}
|
||||
border={'1px solid #fff'}
|
||||
boxShadow={
|
||||
'0px 4px 10px 0px rgba(19, 51, 107, 0.10), 0px 0px 1px 0px rgba(19, 51, 107, 0.10);'
|
||||
}
|
||||
zIndex={99}
|
||||
maxH={'40vh'}
|
||||
overflowY={'auto'}
|
||||
>
|
||||
{filterUnSelected.map((item, i) => {
|
||||
return (
|
||||
<MenuItem
|
||||
key={i}
|
||||
color={'myGray.900'}
|
||||
onClick={(e) => {
|
||||
onclickItem(item.value);
|
||||
}}
|
||||
whiteSpace={'pre-wrap'}
|
||||
fontSize={'sm'}
|
||||
gap={2}
|
||||
{...menuItemStyles}
|
||||
>
|
||||
{item.icon && <MyAvatar src={item.icon} w={'1rem'} borderRadius={'0'} />}
|
||||
<Box flex={'1 0 0'}>{item.label}</Box>
|
||||
</MenuItem>
|
||||
);
|
||||
})}
|
||||
</MenuList>
|
||||
</Menu>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,196 @@
|
||||
import { getSystemModelList, getTestModel } from '@/web/core/ai/config';
|
||||
import {
|
||||
Table,
|
||||
Thead,
|
||||
Tbody,
|
||||
Tr,
|
||||
Th,
|
||||
Td,
|
||||
TableContainer,
|
||||
Box,
|
||||
Flex,
|
||||
Button,
|
||||
HStack,
|
||||
ModalBody,
|
||||
ModalFooter
|
||||
} from '@chakra-ui/react';
|
||||
import { getModelProvider } from '@fastgpt/global/core/ai/provider';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import React, { useRef, useState } from 'react';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
import MyTag from '@fastgpt/web/components/common/Tag/index';
|
||||
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
|
||||
import { getErrText } from '@fastgpt/global/common/error/utils';
|
||||
import { batchRun } from '@fastgpt/global/common/fn/utils';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
|
||||
type ModelTestItem = {
|
||||
label: React.ReactNode;
|
||||
model: string;
|
||||
status: 'waiting' | 'running' | 'success' | 'error';
|
||||
message?: string;
|
||||
duration?: number;
|
||||
};
|
||||
|
||||
const ModelTest = ({ models, onClose }: { models: string[]; onClose: () => void }) => {
|
||||
const { t } = useTranslation();
|
||||
const { toast } = useToast();
|
||||
const [testModelList, setTestModelList] = useState<ModelTestItem[]>([]);
|
||||
|
||||
const statusMap = useRef({
|
||||
waiting: {
|
||||
label: t('account_model:waiting_test'),
|
||||
colorSchema: 'gray'
|
||||
},
|
||||
running: {
|
||||
label: t('account_model:running_test'),
|
||||
colorSchema: 'blue'
|
||||
},
|
||||
success: {
|
||||
label: t('common:common.Success'),
|
||||
colorSchema: 'green'
|
||||
},
|
||||
error: {
|
||||
label: t('common:common.failed'),
|
||||
colorSchema: 'red'
|
||||
}
|
||||
});
|
||||
const { loading: loadingModels } = useRequest2(getSystemModelList, {
|
||||
manual: false,
|
||||
refreshDeps: [models],
|
||||
onSuccess(res) {
|
||||
const list = models
|
||||
.map((model) => {
|
||||
const modelData = res.find((item) => item.model === model);
|
||||
if (!modelData) return null;
|
||||
const provider = getModelProvider(modelData.provider);
|
||||
|
||||
return {
|
||||
label: (
|
||||
<HStack>
|
||||
<MyIcon name={provider.avatar as any} w={'1rem'} />
|
||||
<Box>{t(modelData.name as any)}</Box>
|
||||
</HStack>
|
||||
),
|
||||
model: modelData.model,
|
||||
status: 'waiting'
|
||||
};
|
||||
})
|
||||
.filter(Boolean) as ModelTestItem[];
|
||||
setTestModelList(list);
|
||||
}
|
||||
});
|
||||
|
||||
const { runAsync: onStartTest, loading: isTesting } = useRequest2(
|
||||
async () => {
|
||||
{
|
||||
let errorNum = 0;
|
||||
const testModel = async (model: string) => {
|
||||
setTestModelList((prev) =>
|
||||
prev.map((item) =>
|
||||
item.model === model ? { ...item, status: 'running', message: '' } : item
|
||||
)
|
||||
);
|
||||
const start = Date.now();
|
||||
try {
|
||||
await getTestModel(model);
|
||||
const duration = Date.now() - start;
|
||||
setTestModelList((prev) =>
|
||||
prev.map((item) =>
|
||||
item.model === model
|
||||
? { ...item, status: 'success', duration: duration / 1000 }
|
||||
: item
|
||||
)
|
||||
);
|
||||
} catch (error) {
|
||||
setTestModelList((prev) =>
|
||||
prev.map((item) =>
|
||||
item.model === model
|
||||
? { ...item, status: 'error', message: getErrText(error) }
|
||||
: item
|
||||
)
|
||||
);
|
||||
errorNum++;
|
||||
}
|
||||
};
|
||||
|
||||
await batchRun(
|
||||
testModelList.map((item) => item.model),
|
||||
testModel,
|
||||
5
|
||||
);
|
||||
|
||||
if (errorNum > 0) {
|
||||
toast({
|
||||
status: 'warning',
|
||||
title: t('account_model:test_failed', { num: errorNum })
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
refreshDeps: [testModelList]
|
||||
}
|
||||
);
|
||||
|
||||
console.log(testModelList);
|
||||
return (
|
||||
<MyModal
|
||||
iconSrc={'core/chat/sendLight'}
|
||||
isLoading={loadingModels}
|
||||
title={t('account_model:model_test')}
|
||||
w={'600px'}
|
||||
isOpen
|
||||
>
|
||||
<ModalBody>
|
||||
<TableContainer h={'100%'} overflowY={'auto'} fontSize={'sm'} maxH={'60vh'}>
|
||||
<Table>
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th>{t('account_model:model')}</Th>
|
||||
<Th>{t('account_model:channel_status')}</Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{testModelList.map((item) => {
|
||||
const data = statusMap.current[item.status];
|
||||
return (
|
||||
<Tr key={item.model}>
|
||||
<Td>{item.label}</Td>
|
||||
<Td>
|
||||
<Flex alignItems={'center'}>
|
||||
<MyTag mr={1} type="borderSolid" colorSchema={data.colorSchema as any}>
|
||||
{data.label}
|
||||
</MyTag>
|
||||
{item.message && <QuestionTip label={item.message} />}
|
||||
{item.status === 'success' && item.duration && (
|
||||
<Box fontSize={'sm'} color={'myGray.500'}>
|
||||
{t('account_model:request_duration', {
|
||||
duration: item.duration.toFixed(2)
|
||||
})}
|
||||
</Box>
|
||||
)}
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
);
|
||||
})}
|
||||
</Tbody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button mr={4} variant={'whiteBase'} onClick={onClose}>
|
||||
{t('common:common.Cancel')}
|
||||
</Button>
|
||||
<Button isLoading={isTesting} variant={'primary'} onClick={onStartTest}>
|
||||
{t('account_model:start_test', { num: testModelList.length })}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</MyModal>
|
||||
);
|
||||
};
|
||||
|
||||
export default ModelTest;
|
||||
230
projects/app/src/pageComponents/account/model/Channel/index.tsx
Normal file
230
projects/app/src/pageComponents/account/model/Channel/index.tsx
Normal file
@@ -0,0 +1,230 @@
|
||||
import { deleteChannel, getChannelList, putChannel, putChannelStatus } from '@/web/core/ai/channel';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
Table,
|
||||
Thead,
|
||||
Tbody,
|
||||
Tr,
|
||||
Th,
|
||||
Td,
|
||||
TableContainer,
|
||||
Box,
|
||||
Flex,
|
||||
Button,
|
||||
HStack
|
||||
} from '@chakra-ui/react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import MyBox from '@fastgpt/web/components/common/MyBox';
|
||||
import MyIconButton from '@fastgpt/web/components/common/Icon/button';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import { ChannelInfoType } from '@/global/aiproxy/type';
|
||||
import MyTag from '@fastgpt/web/components/common/Tag/index';
|
||||
import {
|
||||
aiproxyIdMap,
|
||||
ChannelStatusEnum,
|
||||
ChannelStautsMap,
|
||||
defaultChannel
|
||||
} from '@/global/aiproxy/constants';
|
||||
import MyMenu from '@fastgpt/web/components/common/MyMenu';
|
||||
import dynamic from 'next/dynamic';
|
||||
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
|
||||
import MyNumberInput from '@fastgpt/web/components/common/Input/NumberInput';
|
||||
import { getModelProvider } from '@fastgpt/global/core/ai/provider';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
|
||||
const EditChannelModal = dynamic(() => import('./EditChannelModal'), { ssr: false });
|
||||
const ModelTest = dynamic(() => import('./ModelTest'), { ssr: false });
|
||||
|
||||
const ChannelTable = ({ Tab }: { Tab: React.ReactNode }) => {
|
||||
const { t } = useTranslation();
|
||||
const { userInfo } = useUserStore();
|
||||
|
||||
const isRoot = userInfo?.username === 'root';
|
||||
|
||||
const {
|
||||
data: channelList = [],
|
||||
runAsync: refreshChannelList,
|
||||
loading: loadingChannelList
|
||||
} = useRequest2(getChannelList, {
|
||||
manual: false
|
||||
});
|
||||
|
||||
const [editChannel, setEditChannel] = useState<ChannelInfoType>();
|
||||
|
||||
const { runAsync: updateChannel, loading: loadingUpdateChannel } = useRequest2(putChannel, {
|
||||
manual: true,
|
||||
onSuccess: () => {
|
||||
refreshChannelList();
|
||||
}
|
||||
});
|
||||
const { runAsync: updateChannelStatus, loading: loadingUpdateChannelStatus } = useRequest2(
|
||||
putChannelStatus,
|
||||
{
|
||||
onSuccess: () => {
|
||||
refreshChannelList();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const { runAsync: onDeleteChannel, loading: loadingDeleteChannel } = useRequest2(deleteChannel, {
|
||||
manual: true,
|
||||
onSuccess: () => {
|
||||
refreshChannelList();
|
||||
}
|
||||
});
|
||||
|
||||
const [testModels, setTestModels] = useState<string[]>();
|
||||
|
||||
const isLoading =
|
||||
loadingChannelList ||
|
||||
loadingUpdateChannel ||
|
||||
loadingDeleteChannel ||
|
||||
loadingUpdateChannelStatus;
|
||||
|
||||
return (
|
||||
<>
|
||||
{isRoot && (
|
||||
<Flex alignItems={'center'}>
|
||||
{Tab}
|
||||
<Box flex={1} />
|
||||
<Button variant={'whiteBase'} mr={2} onClick={() => setEditChannel(defaultChannel)}>
|
||||
{t('account_model:create_channel')}
|
||||
</Button>
|
||||
</Flex>
|
||||
)}
|
||||
<MyBox flex={'1 0 0'} h={0} isLoading={isLoading}>
|
||||
<TableContainer h={'100%'} overflowY={'auto'} fontSize={'sm'}>
|
||||
<Table>
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th>ID</Th>
|
||||
<Th>{t('account_model:channel_name')}</Th>
|
||||
<Th>{t('account_model:channel_type')}</Th>
|
||||
<Th>{t('account_model:channel_status')}</Th>
|
||||
<Th>
|
||||
{t('account_model:channel_priority')}
|
||||
<QuestionTip label={t('account_model:channel_priority_tip')} />
|
||||
</Th>
|
||||
<Th></Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{channelList.map((item) => {
|
||||
const providerData = aiproxyIdMap[item.type];
|
||||
const provider = getModelProvider(providerData?.provider);
|
||||
|
||||
return (
|
||||
<Tr key={item.id} _hover={{ bg: 'myGray.100' }}>
|
||||
<Td>{item.id}</Td>
|
||||
<Td>{item.name}</Td>
|
||||
<Td>
|
||||
{providerData ? (
|
||||
<HStack>
|
||||
<MyIcon name={provider?.avatar as any} w={'1rem'} />
|
||||
<Box>{t(providerData?.label as any)}</Box>
|
||||
</HStack>
|
||||
) : (
|
||||
'Invalid provider'
|
||||
)}
|
||||
</Td>
|
||||
<Td>
|
||||
<MyTag
|
||||
colorSchema={ChannelStautsMap[item.status]?.colorSchema as any}
|
||||
type="borderFill"
|
||||
>
|
||||
{t(ChannelStautsMap[item.status]?.label as any) ||
|
||||
t('account_model:channel_status_unknown')}
|
||||
</MyTag>
|
||||
</Td>
|
||||
<Td>
|
||||
<MyNumberInput
|
||||
defaultValue={item.priority || 0}
|
||||
min={0}
|
||||
max={100}
|
||||
h={'32px'}
|
||||
w={'80px'}
|
||||
onBlur={(e) => {
|
||||
const val = (() => {
|
||||
if (!e) return 0;
|
||||
return e;
|
||||
})();
|
||||
updateChannel({
|
||||
...item,
|
||||
priority: val
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Td>
|
||||
<Td>
|
||||
<MyMenu
|
||||
menuList={[
|
||||
{
|
||||
label: '',
|
||||
children: [
|
||||
{
|
||||
icon: 'core/chat/sendLight',
|
||||
label: t('account_model:model_test'),
|
||||
onClick: () => setTestModels(item.models)
|
||||
},
|
||||
...(item.status === ChannelStatusEnum.ChannelStatusEnabled
|
||||
? [
|
||||
{
|
||||
icon: 'common/disable',
|
||||
label: t('account_model:forbid_channel'),
|
||||
onClick: () =>
|
||||
updateChannelStatus(
|
||||
item.id,
|
||||
ChannelStatusEnum.ChannelStatusDisabled
|
||||
)
|
||||
}
|
||||
]
|
||||
: [
|
||||
{
|
||||
icon: 'common/enable',
|
||||
label: t('account_model:enable_channel'),
|
||||
onClick: () =>
|
||||
updateChannelStatus(
|
||||
item.id,
|
||||
ChannelStatusEnum.ChannelStatusEnabled
|
||||
)
|
||||
}
|
||||
]),
|
||||
{
|
||||
icon: 'common/settingLight',
|
||||
label: t('account_model:edit'),
|
||||
onClick: () => setEditChannel(item)
|
||||
},
|
||||
{
|
||||
type: 'danger',
|
||||
icon: 'delete',
|
||||
label: t('common:common.Delete'),
|
||||
onClick: () => onDeleteChannel(item.id)
|
||||
}
|
||||
]
|
||||
}
|
||||
]}
|
||||
Button={<MyIconButton icon={'more'} />}
|
||||
/>
|
||||
</Td>
|
||||
</Tr>
|
||||
);
|
||||
})}
|
||||
</Tbody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</MyBox>
|
||||
|
||||
{!!editChannel && (
|
||||
<EditChannelModal
|
||||
defaultConfig={editChannel}
|
||||
onClose={() => setEditChannel(undefined)}
|
||||
onSuccess={refreshChannelList}
|
||||
/>
|
||||
)}
|
||||
{!!testModels && <ModelTest models={testModels} onClose={() => setTestModels(undefined)} />}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ChannelTable;
|
||||
406
projects/app/src/pageComponents/account/model/Log/index.tsx
Normal file
406
projects/app/src/pageComponents/account/model/Log/index.tsx
Normal file
@@ -0,0 +1,406 @@
|
||||
import { getChannelList, getChannelLog, getLogDetail } from '@/web/core/ai/channel';
|
||||
import { getSystemModelList } from '@/web/core/ai/config';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import {
|
||||
Table,
|
||||
Thead,
|
||||
Tbody,
|
||||
Tr,
|
||||
Th,
|
||||
Td,
|
||||
TableContainer,
|
||||
Box,
|
||||
Flex,
|
||||
Button,
|
||||
HStack,
|
||||
ModalBody,
|
||||
Grid,
|
||||
GridItem,
|
||||
BoxProps
|
||||
} from '@chakra-ui/react';
|
||||
import { getModelProvider } from '@fastgpt/global/core/ai/provider';
|
||||
import DateRangePicker, { DateRangeType } from '@fastgpt/web/components/common/DateRangePicker';
|
||||
import MyBox from '@fastgpt/web/components/common/MyBox';
|
||||
import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
|
||||
import MySelect from '@fastgpt/web/components/common/MySelect';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import { useScrollPagination } from '@fastgpt/web/hooks/useScrollPagination';
|
||||
import { addDays } from 'date-fns';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { formatTime2YMDHMS } from '@fastgpt/global/common/string/time';
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
|
||||
|
||||
type LogDetailType = {
|
||||
id: number;
|
||||
request_id: string;
|
||||
channelName: string | number;
|
||||
model: React.JSX.Element;
|
||||
duration: number;
|
||||
request_at: string;
|
||||
code: number;
|
||||
prompt_tokens: number;
|
||||
completion_tokens: number;
|
||||
endpoint: string;
|
||||
|
||||
content?: string;
|
||||
request_body?: string;
|
||||
response_body?: string;
|
||||
};
|
||||
const ChannelLog = ({ Tab }: { Tab: React.ReactNode }) => {
|
||||
const { t } = useTranslation();
|
||||
const { userInfo } = useUserStore();
|
||||
|
||||
const isRoot = userInfo?.username === 'root';
|
||||
const [filterProps, setFilterProps] = useState<{
|
||||
channelId?: string;
|
||||
model?: string;
|
||||
code_type: 'all' | 'success' | 'error';
|
||||
dateRange: DateRangeType;
|
||||
}>({
|
||||
code_type: 'all',
|
||||
dateRange: {
|
||||
from: (() => {
|
||||
const today = addDays(new Date(), -1);
|
||||
today.setHours(0, 0, 0, 0);
|
||||
return today;
|
||||
})(),
|
||||
to: (() => {
|
||||
const today = new Date();
|
||||
today.setHours(23, 59, 59, 999);
|
||||
return today;
|
||||
})()
|
||||
}
|
||||
});
|
||||
|
||||
const { data: channelList = [] } = useRequest2(
|
||||
async () => {
|
||||
const res = await getChannelList().then((res) =>
|
||||
res.map((item) => ({
|
||||
label: item.name,
|
||||
value: `${item.id}`
|
||||
}))
|
||||
);
|
||||
return [
|
||||
{
|
||||
label: t('common:common.All'),
|
||||
value: ''
|
||||
},
|
||||
...res
|
||||
];
|
||||
},
|
||||
{
|
||||
manual: false
|
||||
}
|
||||
);
|
||||
|
||||
const { data: systemModelList = [] } = useRequest2(getSystemModelList, {
|
||||
manual: false
|
||||
});
|
||||
const modelList = useMemo(() => {
|
||||
const res = systemModelList
|
||||
.map((item) => {
|
||||
const provider = getModelProvider(item.provider);
|
||||
|
||||
return {
|
||||
order: provider.order,
|
||||
icon: provider.avatar,
|
||||
label: item.model,
|
||||
value: item.model
|
||||
};
|
||||
})
|
||||
.sort((a, b) => a.order - b.order);
|
||||
return [
|
||||
{
|
||||
label: t('common:common.All'),
|
||||
value: ''
|
||||
},
|
||||
...res
|
||||
];
|
||||
}, [systemModelList]);
|
||||
|
||||
const { data, isLoading, ScrollData } = useScrollPagination(getChannelLog, {
|
||||
pageSize: 20,
|
||||
refreshDeps: [filterProps],
|
||||
params: {
|
||||
channel: filterProps.channelId,
|
||||
model_name: filterProps.model,
|
||||
code_type: filterProps.code_type,
|
||||
start_timestamp: filterProps.dateRange.from?.getTime() || 0,
|
||||
end_timestamp: filterProps.dateRange.to?.getTime() || 0
|
||||
}
|
||||
});
|
||||
|
||||
const formatData = useMemo<LogDetailType[]>(() => {
|
||||
return data.map((item) => {
|
||||
const duration = item.created_at - item.request_at;
|
||||
const durationSecond = duration / 1000;
|
||||
|
||||
const channelName = channelList.find((channel) => channel.value === `${item.channel}`)?.label;
|
||||
|
||||
const model = systemModelList.find((model) => model.model === item.model);
|
||||
const provider = getModelProvider(model?.provider);
|
||||
|
||||
return {
|
||||
id: item.id,
|
||||
channelName: channelName || item.channel,
|
||||
model: (
|
||||
<HStack>
|
||||
<MyIcon name={provider?.avatar as any} w={'1rem'} />
|
||||
<Box>{model?.model}</Box>
|
||||
</HStack>
|
||||
),
|
||||
duration: durationSecond,
|
||||
request_at: formatTime2YMDHMS(item.request_at),
|
||||
code: item.code,
|
||||
prompt_tokens: item.prompt_tokens,
|
||||
completion_tokens: item.completion_tokens,
|
||||
request_id: item.request_id,
|
||||
endpoint: item.endpoint,
|
||||
content: item.content
|
||||
};
|
||||
});
|
||||
}, [data]);
|
||||
|
||||
const [logDetail, setLogDetail] = useState<LogDetailType>();
|
||||
|
||||
return (
|
||||
<>
|
||||
{isRoot && (
|
||||
<Flex alignItems={'center'}>
|
||||
{Tab}
|
||||
<Box flex={1} />
|
||||
</Flex>
|
||||
)}
|
||||
<HStack spacing={4}>
|
||||
<HStack>
|
||||
<FormLabel>{t('common:user.Time')}</FormLabel>
|
||||
<Box>
|
||||
<DateRangePicker
|
||||
defaultDate={filterProps.dateRange}
|
||||
dateRange={filterProps.dateRange}
|
||||
position="bottom"
|
||||
onSuccess={(e) => setFilterProps({ ...filterProps, dateRange: e })}
|
||||
/>
|
||||
</Box>
|
||||
</HStack>
|
||||
<HStack flex={'0 0 200px'}>
|
||||
<FormLabel>{t('account_model:channel_name')}</FormLabel>
|
||||
<Box flex={'1 0 0'}>
|
||||
<MySelect<string>
|
||||
bg={'myGray.50'}
|
||||
isSearch
|
||||
list={channelList}
|
||||
placeholder={t('account_model:select_channel')}
|
||||
value={filterProps.channelId}
|
||||
onchange={(val) => setFilterProps({ ...filterProps, channelId: val })}
|
||||
/>
|
||||
</Box>
|
||||
</HStack>
|
||||
<HStack flex={'0 0 200px'}>
|
||||
<FormLabel>{t('account_model:model_name')}</FormLabel>
|
||||
<Box flex={'1 0 0'}>
|
||||
<MySelect<string>
|
||||
bg={'myGray.50'}
|
||||
isSearch
|
||||
list={modelList}
|
||||
placeholder={t('account_model:select_model')}
|
||||
value={filterProps.model}
|
||||
onchange={(val) => setFilterProps({ ...filterProps, model: val })}
|
||||
/>
|
||||
</Box>
|
||||
</HStack>
|
||||
<HStack flex={'0 0 200px'}>
|
||||
<FormLabel>{t('account_model:log_status')}</FormLabel>
|
||||
<Box flex={'1 0 0'}>
|
||||
<MySelect<'all' | 'success' | 'error'>
|
||||
bg={'myGray.50'}
|
||||
list={[
|
||||
{ label: t('common:common.All'), value: 'all' },
|
||||
{ label: t('common:common.Success'), value: 'success' },
|
||||
{ label: t('common:common.failed'), value: 'error' }
|
||||
]}
|
||||
value={filterProps.code_type}
|
||||
onchange={(val) => setFilterProps({ ...filterProps, code_type: val })}
|
||||
/>
|
||||
</Box>
|
||||
</HStack>
|
||||
</HStack>
|
||||
<MyBox flex={'1 0 0'} h={0} isLoading={isLoading}>
|
||||
<ScrollData h={'100%'}>
|
||||
<TableContainer fontSize={'sm'}>
|
||||
<Table>
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th>{t('account_model:channel_name')}</Th>
|
||||
<Th>{t('account_model:model')}</Th>
|
||||
<Th>{t('account_model:model_tokens')}</Th>
|
||||
<Th>{t('account_model:duration')}</Th>
|
||||
<Th>{t('account_model:channel_status')}</Th>
|
||||
<Th>{t('account_model:request_at')}</Th>
|
||||
<Th></Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{formatData.map((item) => (
|
||||
<Tr key={item.id}>
|
||||
<Td>{item.channelName}</Td>
|
||||
<Td>{item.model}</Td>
|
||||
<Td>
|
||||
{item.prompt_tokens} / {item.completion_tokens}
|
||||
</Td>
|
||||
<Td color={item.duration > 10 ? 'red.600' : ''}>{item.duration.toFixed(2)}s</Td>
|
||||
<Td color={item.code === 200 ? 'green.600' : 'red.600'}>
|
||||
{item.code}
|
||||
{item.content && <QuestionTip label={item.content} />}
|
||||
</Td>
|
||||
<Td>{item.request_at}</Td>
|
||||
<Td>
|
||||
<Button
|
||||
leftIcon={<MyIcon name={'menu'} w={'1rem'} />}
|
||||
size={'sm'}
|
||||
variant={'outline'}
|
||||
onClick={() => setLogDetail(item)}
|
||||
>
|
||||
{t('account_model:detail')}
|
||||
</Button>
|
||||
</Td>
|
||||
</Tr>
|
||||
))}
|
||||
</Tbody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</ScrollData>
|
||||
</MyBox>
|
||||
|
||||
{!!logDetail && <LogDetail data={logDetail} onClose={() => setLogDetail(undefined)} />}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ChannelLog;
|
||||
|
||||
const LogDetail = ({ data, onClose }: { data: LogDetailType; onClose: () => void }) => {
|
||||
const { t } = useTranslation();
|
||||
const { data: detailData } = useRequest2(
|
||||
async () => {
|
||||
if (data.code === 200) return data;
|
||||
const res = await getLogDetail(data.id);
|
||||
return {
|
||||
...res,
|
||||
...data
|
||||
};
|
||||
},
|
||||
{
|
||||
manual: false
|
||||
}
|
||||
);
|
||||
|
||||
const Title = useCallback(({ children, ...props }: { children: React.ReactNode } & BoxProps) => {
|
||||
return (
|
||||
<Box
|
||||
bg={'myGray.50'}
|
||||
color="myGray.900 "
|
||||
borderRight={'base'}
|
||||
p={3}
|
||||
flex={'0 0 100px'}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</Box>
|
||||
);
|
||||
}, []);
|
||||
const Container = useCallback(
|
||||
({ children, ...props }: { children: React.ReactNode } & BoxProps) => {
|
||||
return (
|
||||
<Box p={3} flex={1} {...props}>
|
||||
{children}
|
||||
</Box>
|
||||
);
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
return (
|
||||
<MyModal
|
||||
isOpen
|
||||
iconSrc="support/bill/payRecordLight"
|
||||
title={t('account_model:log_detail')}
|
||||
onClose={onClose}
|
||||
maxW={['90vw', '800px']}
|
||||
w={'100%'}
|
||||
>
|
||||
{detailData && (
|
||||
<ModalBody>
|
||||
{/* 基本信息表格 */}
|
||||
<Grid
|
||||
templateColumns="repeat(2, 1fr)"
|
||||
gap={0}
|
||||
borderWidth="1px"
|
||||
borderRadius="md"
|
||||
fontSize={'sm'}
|
||||
overflow={'hidden'}
|
||||
>
|
||||
{/* 第一行 */}
|
||||
<GridItem display={'flex'} borderBottomWidth="1px" borderRightWidth="1px">
|
||||
<Title>RequestID</Title>
|
||||
<Container>{detailData?.request_id}</Container>
|
||||
</GridItem>
|
||||
<GridItem display={'flex'} borderBottomWidth="1px" borderRightWidth="1px">
|
||||
<Title>{t('account_model:channel_status')}</Title>
|
||||
<Container color={detailData.code === 200 ? 'green.600' : 'red.600'}>
|
||||
{detailData?.code}
|
||||
</Container>
|
||||
</GridItem>
|
||||
<GridItem display={'flex'} borderBottomWidth="1px" borderRightWidth="1px">
|
||||
<Title>Endpoint</Title>
|
||||
<Container>{detailData?.endpoint}</Container>
|
||||
</GridItem>
|
||||
<GridItem display={'flex'} borderBottomWidth="1px" borderRightWidth="1px">
|
||||
<Title>{t('account_model:channel_name')}</Title>
|
||||
<Container>{detailData?.channelName}</Container>
|
||||
</GridItem>
|
||||
<GridItem display={'flex'} borderBottomWidth="1px" borderRightWidth="1px">
|
||||
<Title>{t('account_model:request_at')}</Title>
|
||||
<Container>{detailData?.request_at}</Container>
|
||||
</GridItem>
|
||||
<GridItem display={'flex'} borderBottomWidth="1px" borderRightWidth="1px">
|
||||
<Title>{t('account_model:duration')}</Title>
|
||||
<Container>{detailData?.duration.toFixed(2)}s</Container>
|
||||
</GridItem>
|
||||
<GridItem display={'flex'} borderBottomWidth="1px" borderRightWidth="1px">
|
||||
<Title>{t('account_model:model')}</Title>
|
||||
<Container>{detailData?.model}</Container>
|
||||
</GridItem>
|
||||
<GridItem display={'flex'} borderBottomWidth="1px" borderRightWidth="1px">
|
||||
<Title flex={'0 0 150px'}>{t('account_model:model_tokens')}</Title>
|
||||
<Container>
|
||||
{detailData?.prompt_tokens} / {detailData?.completion_tokens}
|
||||
</Container>
|
||||
</GridItem>
|
||||
{detailData?.content && (
|
||||
<GridItem display={'flex'} borderBottomWidth="1px" borderRightWidth="1px" colSpan={2}>
|
||||
<Title>Content</Title>
|
||||
<Container>{detailData?.content}</Container>
|
||||
</GridItem>
|
||||
)}
|
||||
{detailData?.request_body && (
|
||||
<GridItem display={'flex'} borderBottomWidth="1px" borderRightWidth="1px" colSpan={2}>
|
||||
<Title>Request Body</Title>
|
||||
<Container userSelect={'all'}>{detailData?.request_body}</Container>
|
||||
</GridItem>
|
||||
)}
|
||||
{detailData?.response_body && (
|
||||
<GridItem display={'flex'} borderBottomWidth="1px" borderRightWidth="1px" colSpan={2}>
|
||||
<Title>Response Body</Title>
|
||||
<Container>{detailData?.response_body}</Container>
|
||||
</GridItem>
|
||||
)}
|
||||
</Grid>
|
||||
</ModalBody>
|
||||
)}
|
||||
</MyModal>
|
||||
);
|
||||
};
|
||||
@@ -33,7 +33,6 @@ import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import {
|
||||
deleteSystemModel,
|
||||
getModelConfigJson,
|
||||
getSystemModelDefaultConfig,
|
||||
getSystemModelDetail,
|
||||
getSystemModelList,
|
||||
getTestModel,
|
||||
@@ -44,24 +43,20 @@ import MyBox from '@fastgpt/web/components/common/MyBox';
|
||||
import { SystemModelItemType } from '@fastgpt/service/core/ai/type';
|
||||
import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
|
||||
import MyIconButton from '@fastgpt/web/components/common/Icon/button';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import MyNumberInput from '@fastgpt/web/components/common/Input/NumberInput';
|
||||
import MyTextarea from '@/components/common/Textarea/MyTextarea';
|
||||
import JsonEditor from '@fastgpt/web/components/common/Textarea/JsonEditor';
|
||||
import { clientInitData } from '@/web/common/system/staticData';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import MyMenu from '@fastgpt/web/components/common/MyMenu';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
|
||||
import { putUpdateWithJson } from '@/web/core/ai/config';
|
||||
import CopyBox from '@fastgpt/web/components/common/String/CopyBox';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import AIModelSelector from '@/components/Select/AIModelSelector';
|
||||
import { useRefresh } from '../../../../../../packages/web/hooks/useRefresh';
|
||||
import { Prompt_CQJson, Prompt_ExtractJson } from '@fastgpt/global/core/ai/prompt/agent';
|
||||
import MyDivider from '@fastgpt/web/components/common/MyDivider';
|
||||
import { AddModelButton } from './AddModelBox';
|
||||
|
||||
const MyModal = dynamic(() => import('@fastgpt/web/components/common/MyModal'));
|
||||
const ModelEditModal = dynamic(() => import('./AddModelBox').then((mod) => mod.ModelEditModal));
|
||||
|
||||
const ModelTable = ({ Tab }: { Tab: React.ReactNode }) => {
|
||||
const { t } = useTranslation();
|
||||
@@ -271,6 +266,7 @@ const ModelTable = ({ Tab }: { Tab: React.ReactNode }) => {
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const onCreateModel = (type: ModelTypeEnum) => {
|
||||
const defaultModel = defaultModels[type];
|
||||
|
||||
@@ -316,37 +312,7 @@ const ModelTable = ({ Tab }: { Tab: React.ReactNode }) => {
|
||||
<Button variant={'whiteBase'} mr={2} onClick={onOpenJsonConfig}>
|
||||
{t('account:model.json_config')}
|
||||
</Button>
|
||||
<MyMenu
|
||||
trigger="hover"
|
||||
size="sm"
|
||||
Button={<Button>{t('account:create_model')}</Button>}
|
||||
menuList={[
|
||||
{
|
||||
children: [
|
||||
{
|
||||
label: t('common:model.type.chat'),
|
||||
onClick: () => onCreateModel(ModelTypeEnum.llm)
|
||||
},
|
||||
{
|
||||
label: t('common:model.type.embedding'),
|
||||
onClick: () => onCreateModel(ModelTypeEnum.embedding)
|
||||
},
|
||||
{
|
||||
label: t('common:model.type.tts'),
|
||||
onClick: () => onCreateModel(ModelTypeEnum.tts)
|
||||
},
|
||||
{
|
||||
label: t('common:model.type.stt'),
|
||||
onClick: () => onCreateModel(ModelTypeEnum.stt)
|
||||
},
|
||||
{
|
||||
label: t('common:model.type.reRank'),
|
||||
onClick: () => onCreateModel(ModelTypeEnum.rerank)
|
||||
}
|
||||
]
|
||||
}
|
||||
]}
|
||||
/>
|
||||
<AddModelButton onCreate={onCreateModel} />
|
||||
</Flex>
|
||||
)}
|
||||
<MyBox flex={'1 0 0'} isLoading={isLoading}>
|
||||
@@ -512,650 +478,6 @@ const ModelTable = ({ Tab }: { Tab: React.ReactNode }) => {
|
||||
);
|
||||
};
|
||||
|
||||
const InputStyles = {
|
||||
maxW: '300px',
|
||||
bg: 'myGray.50',
|
||||
w: '100%',
|
||||
rows: 3
|
||||
};
|
||||
const ModelEditModal = ({
|
||||
modelData,
|
||||
onSuccess,
|
||||
onClose
|
||||
}: {
|
||||
modelData: SystemModelItemType;
|
||||
onSuccess: () => void;
|
||||
onClose: () => void;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { feConfigs } = useSystemStore();
|
||||
|
||||
const { register, getValues, setValue, handleSubmit, watch, reset } =
|
||||
useForm<SystemModelItemType>({
|
||||
defaultValues: modelData
|
||||
});
|
||||
|
||||
const isCustom = !!modelData.isCustom;
|
||||
const isLLMModel = modelData?.type === ModelTypeEnum.llm;
|
||||
const isEmbeddingModel = modelData?.type === ModelTypeEnum.embedding;
|
||||
const isTTSModel = modelData?.type === ModelTypeEnum.tts;
|
||||
const isSTTModel = modelData?.type === ModelTypeEnum.stt;
|
||||
const isRerankModel = modelData?.type === ModelTypeEnum.rerank;
|
||||
|
||||
const provider = watch('provider');
|
||||
const providerData = useMemo(() => getModelProvider(provider), [provider]);
|
||||
|
||||
const providerList = useRef<{ label: any; value: ModelProviderIdType }[]>(
|
||||
ModelProviderList.map((item) => ({
|
||||
label: (
|
||||
<HStack>
|
||||
<Avatar src={item.avatar} w={'1rem'} />
|
||||
<Box>{t(item.name as any)}</Box>
|
||||
</HStack>
|
||||
),
|
||||
value: item.id
|
||||
}))
|
||||
);
|
||||
|
||||
const priceUnit = useMemo(() => {
|
||||
if (isLLMModel || isEmbeddingModel) return '/ 1k Tokens';
|
||||
if (isTTSModel) return `/ 1k ${t('common:unit.character')}`;
|
||||
if (isSTTModel) return `/ 60 ${t('common:unit.seconds')}`;
|
||||
return '';
|
||||
return '';
|
||||
}, [isLLMModel, isEmbeddingModel, isTTSModel, t, isSTTModel]);
|
||||
|
||||
const { runAsync: updateModel, loading: updatingModel } = useRequest2(
|
||||
async (data: SystemModelItemType) => {
|
||||
return putSystemModel({
|
||||
model: data.model,
|
||||
metadata: data
|
||||
}).then(onSuccess);
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
onClose();
|
||||
},
|
||||
successToast: t('common:common.Success')
|
||||
}
|
||||
);
|
||||
|
||||
const [key, setKey] = useState(0);
|
||||
const { runAsync: loadDefaultConfig, loading: loadingDefaultConfig } = useRequest2(
|
||||
getSystemModelDefaultConfig,
|
||||
{
|
||||
onSuccess(res) {
|
||||
reset({
|
||||
...getValues(),
|
||||
...res
|
||||
});
|
||||
setTimeout(() => {
|
||||
setKey((prev) => prev + 1);
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return (
|
||||
<MyModal
|
||||
iconSrc={'modal/edit'}
|
||||
title={t('account:model.edit_model')}
|
||||
isOpen
|
||||
onClose={onClose}
|
||||
maxW={['90vw', '80vw']}
|
||||
w={'100%'}
|
||||
h={'100%'}
|
||||
>
|
||||
<ModalBody>
|
||||
<Flex gap={4} key={key}>
|
||||
<TableContainer flex={'1'}>
|
||||
<Table>
|
||||
<Thead>
|
||||
<Tr color={'myGray.600'}>
|
||||
<Th fontSize={'xs'}>{t('account:model.param_name')}</Th>
|
||||
<Th fontSize={'xs'}></Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
<Tr>
|
||||
<Td>
|
||||
<HStack spacing={1}>
|
||||
<Box>{t('account:model.model_id')}</Box>
|
||||
<QuestionTip label={t('account:model.model_id_tip')} />
|
||||
</HStack>
|
||||
</Td>
|
||||
<Td textAlign={'right'}>
|
||||
{isCustom ? (
|
||||
<Input {...register('model', { required: true })} {...InputStyles} />
|
||||
) : (
|
||||
modelData?.model
|
||||
)}
|
||||
</Td>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<Td>{t('common:model.provider')}</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<MySelect
|
||||
value={provider}
|
||||
onchange={(value) => setValue('provider', value)}
|
||||
list={providerList.current}
|
||||
{...InputStyles}
|
||||
/>
|
||||
</Td>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<Td>
|
||||
<HStack spacing={1}>
|
||||
<Box>{t('account:model.alias')}</Box>
|
||||
<QuestionTip label={t('account:model.alias_tip')} />
|
||||
</HStack>
|
||||
</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<Input {...register('name', { required: true })} {...InputStyles} />
|
||||
</Td>
|
||||
</Tr>
|
||||
{priceUnit && feConfigs?.isPlus && (
|
||||
<>
|
||||
<Tr>
|
||||
<Td>
|
||||
<HStack spacing={1}>
|
||||
<Box>{t('account:model.charsPointsPrice')}</Box>
|
||||
<QuestionTip label={t('account:model.charsPointsPrice_tip')} />
|
||||
</HStack>
|
||||
</Td>
|
||||
<Td>
|
||||
<Flex justify="flex-end">
|
||||
<HStack w={'100%'} maxW={'300px'}>
|
||||
<MyNumberInput
|
||||
flex={'1 0 0'}
|
||||
register={register}
|
||||
name={'charsPointsPrice'}
|
||||
step={0.01}
|
||||
/>
|
||||
<Box fontSize={'sm'}>{priceUnit}</Box>
|
||||
</HStack>
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
{isLLMModel && (
|
||||
<>
|
||||
<Tr>
|
||||
<Td>
|
||||
<HStack spacing={1}>
|
||||
<Box>{t('account:model.input_price')}</Box>
|
||||
<QuestionTip label={t('account:model.input_price_tip')} />
|
||||
</HStack>
|
||||
</Td>
|
||||
<Td>
|
||||
<Flex justify="flex-end">
|
||||
<HStack w={'100%'} maxW={'300px'}>
|
||||
<MyNumberInput
|
||||
flex={'1 0 0'}
|
||||
register={register}
|
||||
name={'inputPrice'}
|
||||
step={0.01}
|
||||
/>
|
||||
<Box fontSize={'sm'}>{priceUnit}</Box>
|
||||
</HStack>
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<Td>
|
||||
<HStack spacing={1}>
|
||||
<Box>{t('account:model.output_price')}</Box>
|
||||
<QuestionTip label={t('account:model.output_price_tip')} />
|
||||
</HStack>
|
||||
</Td>
|
||||
<Td>
|
||||
<Flex justify="flex-end">
|
||||
<HStack w={'100%'} maxW={'300px'}>
|
||||
<MyNumberInput
|
||||
flex={'1 0 0'}
|
||||
register={register}
|
||||
name={'outputPrice'}
|
||||
step={0.01}
|
||||
/>
|
||||
<Box fontSize={'sm'}>{priceUnit}</Box>
|
||||
</HStack>
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{isLLMModel && (
|
||||
<>
|
||||
<Tr>
|
||||
<Td>{t('common:core.ai.Max context')}</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<Flex justifyContent={'flex-end'}>
|
||||
<MyNumberInput
|
||||
register={register}
|
||||
isRequired
|
||||
name="maxContext"
|
||||
{...InputStyles}
|
||||
/>
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<Td>{t('account:model.max_quote')}</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<Flex justifyContent={'flex-end'}>
|
||||
<MyNumberInput
|
||||
register={register}
|
||||
isRequired
|
||||
name="quoteMaxToken"
|
||||
{...InputStyles}
|
||||
/>
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<Td>{t('common:core.chat.response.module maxToken')}</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<Flex justifyContent={'flex-end'}>
|
||||
<MyNumberInput
|
||||
register={register}
|
||||
isRequired
|
||||
name="maxResponse"
|
||||
{...InputStyles}
|
||||
/>
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<Td>{t('account:model.max_temperature')}</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<Flex justifyContent={'flex-end'}>
|
||||
<MyNumberInput
|
||||
register={register}
|
||||
isRequired
|
||||
name="maxTemperature"
|
||||
step={0.1}
|
||||
{...InputStyles}
|
||||
/>
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<Td>
|
||||
<HStack spacing={1}>
|
||||
<Box>{t('account:model.show_top_p')}</Box>
|
||||
</HStack>
|
||||
</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<Flex justifyContent={'flex-end'}>
|
||||
<Switch {...register('showTopP')} />
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<Td>
|
||||
<HStack spacing={1}>
|
||||
<Box>{t('account:model.show_stop_sign')}</Box>
|
||||
</HStack>
|
||||
</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<Flex justifyContent={'flex-end'}>
|
||||
<Switch {...register('showStopSign')} />
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<Td>{t('account:model.response_format')}</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<JsonEditor
|
||||
value={JSON.stringify(getValues('responseFormatList'), null, 2)}
|
||||
resize
|
||||
onChange={(e) => {
|
||||
if (!e) {
|
||||
setValue('responseFormatList', []);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
setValue('responseFormatList', JSON.parse(e));
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}}
|
||||
{...InputStyles}
|
||||
/>
|
||||
</Td>
|
||||
</Tr>
|
||||
</>
|
||||
)}
|
||||
{isEmbeddingModel && (
|
||||
<>
|
||||
<Tr>
|
||||
<Td>
|
||||
<HStack spacing={1}>
|
||||
<Box>{t('account:model.normalization')}</Box>
|
||||
<QuestionTip label={t('account:model.normalization_tip')} />
|
||||
</HStack>
|
||||
</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<Flex justifyContent={'flex-end'}>
|
||||
<Switch {...register('normalization')} />
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<Td>
|
||||
<HStack spacing={1}>
|
||||
<Box>{t('account:model.default_token')}</Box>
|
||||
<QuestionTip label={t('account:model.default_token_tip')} />
|
||||
</HStack>
|
||||
</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<Flex justifyContent={'flex-end'}>
|
||||
<MyNumberInput
|
||||
register={register}
|
||||
isRequired
|
||||
name="defaultToken"
|
||||
{...InputStyles}
|
||||
/>
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<Td>{t('common:core.ai.Max context')}</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<Flex justifyContent={'flex-end'}>
|
||||
<MyNumberInput
|
||||
register={register}
|
||||
isRequired
|
||||
name="maxToken"
|
||||
{...InputStyles}
|
||||
/>
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<Td>
|
||||
<HStack spacing={1}>
|
||||
<Box>{t('account:model.defaultConfig')}</Box>
|
||||
<QuestionTip label={t('account:model.defaultConfig_tip')} />
|
||||
</HStack>
|
||||
</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<Flex justifyContent={'flex-end'}>
|
||||
<JsonEditor
|
||||
value={JSON.stringify(getValues('defaultConfig'), null, 2)}
|
||||
onChange={(e) => {
|
||||
if (!e) {
|
||||
setValue('defaultConfig', {});
|
||||
return;
|
||||
}
|
||||
try {
|
||||
setValue('defaultConfig', JSON.parse(e));
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}}
|
||||
{...InputStyles}
|
||||
/>
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
</>
|
||||
)}
|
||||
{isTTSModel && (
|
||||
<>
|
||||
<Tr>
|
||||
<Td>
|
||||
<HStack spacing={1}>
|
||||
<Box>{t('account:model.voices')}</Box>
|
||||
<QuestionTip label={t('account:model.voices_tip')} />
|
||||
</HStack>
|
||||
</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<Flex justifyContent={'flex-end'}>
|
||||
<JsonEditor
|
||||
value={JSON.stringify(getValues('voices'), null, 2)}
|
||||
onChange={(e) => {
|
||||
try {
|
||||
setValue('voices', JSON.parse(e));
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}}
|
||||
{...InputStyles}
|
||||
/>
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
</>
|
||||
)}
|
||||
<Tr>
|
||||
<Td>
|
||||
<HStack spacing={1}>
|
||||
<Box>{t('account:model.request_url')}</Box>
|
||||
<QuestionTip label={t('account:model.request_url_tip')} />
|
||||
</HStack>
|
||||
</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<Input {...register('requestUrl')} {...InputStyles} />
|
||||
</Td>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<Td>
|
||||
<HStack spacing={1}>
|
||||
<Box>{t('account:model.request_auth')}</Box>
|
||||
<QuestionTip label={t('account:model.request_auth_tip')} />
|
||||
</HStack>
|
||||
</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<Input {...register('requestAuth')} {...InputStyles} />
|
||||
</Td>
|
||||
</Tr>
|
||||
</Tbody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
{isLLMModel && (
|
||||
<TableContainer flex={'1'}>
|
||||
<Table>
|
||||
<Thead>
|
||||
<Tr color={'myGray.600'}>
|
||||
<Th fontSize={'xs'}>{t('account:model.param_name')}</Th>
|
||||
<Th fontSize={'xs'}></Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
<Tr>
|
||||
<Td>
|
||||
<HStack spacing={1}>
|
||||
<Box>{t('account:model.tool_choice')}</Box>
|
||||
<QuestionTip label={t('account:model.tool_choice_tip')} />
|
||||
</HStack>
|
||||
</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<Flex justifyContent={'flex-end'}>
|
||||
<Switch {...register('toolChoice')} />
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<Td>
|
||||
<HStack spacing={1}>
|
||||
<Box>{t('account:model.function_call')}</Box>
|
||||
<QuestionTip label={t('account:model.function_call_tip')} />
|
||||
</HStack>
|
||||
</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<Flex justifyContent={'flex-end'}>
|
||||
<Switch {...register('functionCall')} />
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<Td>
|
||||
<HStack spacing={1}>
|
||||
<Box>{t('account:model.vision')}</Box>
|
||||
<QuestionTip label={t('account:model.vision_tip')} />
|
||||
</HStack>
|
||||
</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<Flex justifyContent={'flex-end'}>
|
||||
<Switch {...register('vision')} />
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<Td>
|
||||
<HStack spacing={1}>
|
||||
<Box>{t('account:model.reasoning')}</Box>
|
||||
<QuestionTip label={t('account:model.reasoning_tip')} />
|
||||
</HStack>
|
||||
</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<Flex justifyContent={'flex-end'}>
|
||||
<Switch {...register('reasoning')} />
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
{feConfigs?.isPlus && (
|
||||
<Tr>
|
||||
<Td>
|
||||
<HStack spacing={1}>
|
||||
<Box>{t('account:model.censor')}</Box>
|
||||
<QuestionTip label={t('account:model.censor_tip')} />
|
||||
</HStack>
|
||||
</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<Flex justifyContent={'flex-end'}>
|
||||
<Switch {...register('censor')} />
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
)}
|
||||
<Tr>
|
||||
<Td>{t('account:model.dataset_process')}</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<Flex justifyContent={'flex-end'}>
|
||||
<Switch {...register('datasetProcess')} />
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<Td>{t('account:model.used_in_classify')}</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<Flex justifyContent={'flex-end'}>
|
||||
<Switch {...register('usedInClassify')} />
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<Td>{t('account:model.used_in_extract_fields')}</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<Flex justifyContent={'flex-end'}>
|
||||
<Switch {...register('usedInExtractFields')} />
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<Td>{t('account:model.used_in_tool_call')}</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<Flex justifyContent={'flex-end'}>
|
||||
<Switch {...register('usedInToolCall')} />
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<Td>
|
||||
<HStack spacing={1}>
|
||||
<Box>{t('account:model.default_system_chat_prompt')}</Box>
|
||||
<QuestionTip label={t('account:model.default_system_chat_prompt_tip')} />
|
||||
</HStack>
|
||||
</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<MyTextarea {...register('defaultSystemChatPrompt')} {...InputStyles} />
|
||||
</Td>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<Td>
|
||||
<HStack spacing={1}>
|
||||
<Box>{t('account:model.custom_cq_prompt')}</Box>
|
||||
<QuestionTip
|
||||
label={t('account:model.custom_cq_prompt_tip', { prompt: Prompt_CQJson })}
|
||||
/>
|
||||
</HStack>
|
||||
</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<MyTextarea {...register('customCQPrompt')} {...InputStyles} />
|
||||
</Td>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<Td>
|
||||
<HStack spacing={1}>
|
||||
<Box>{t('account:model.custom_extract_prompt')}</Box>
|
||||
<QuestionTip
|
||||
label={t('account:model.custom_extract_prompt_tip', {
|
||||
prompt: Prompt_ExtractJson
|
||||
})}
|
||||
/>
|
||||
</HStack>
|
||||
</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<MyTextarea {...register('customExtractPrompt')} {...InputStyles} />
|
||||
</Td>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<Td>
|
||||
<HStack spacing={1}>
|
||||
<Box>{t('account:model.default_config')}</Box>
|
||||
<QuestionTip label={t('account:model.default_config_tip')} />
|
||||
</HStack>
|
||||
</Td>
|
||||
<Td textAlign={'right'}>
|
||||
<JsonEditor
|
||||
value={JSON.stringify(getValues('defaultConfig'), null, 2)}
|
||||
resize
|
||||
onChange={(e) => {
|
||||
if (!e) {
|
||||
setValue('defaultConfig', {});
|
||||
return;
|
||||
}
|
||||
try {
|
||||
setValue('defaultConfig', JSON.parse(e));
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}}
|
||||
{...InputStyles}
|
||||
/>
|
||||
</Td>
|
||||
</Tr>
|
||||
</Tbody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
)}
|
||||
</Flex>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
{!modelData.isCustom && (
|
||||
<Button
|
||||
isLoading={loadingDefaultConfig}
|
||||
variant={'whiteBase'}
|
||||
mr={4}
|
||||
onClick={() => loadDefaultConfig(modelData.model)}
|
||||
>
|
||||
{t('account:reset_default')}
|
||||
</Button>
|
||||
)}
|
||||
<Button variant={'whiteBase'} mr={4} onClick={onClose}>
|
||||
{t('common:common.Cancel')}
|
||||
</Button>
|
||||
<Button isLoading={updatingModel} onClick={handleSubmit(updateModel)}>
|
||||
{t('common:common.Confirm')}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</MyModal>
|
||||
);
|
||||
};
|
||||
|
||||
const JsonConfigModal = ({
|
||||
onClose,
|
||||
onSuccess
|
||||
|
||||
Reference in New Issue
Block a user