### What problem does this PR solve? Replace Avatar with RAGFlowAvatar component for knowledge base and agent, optimize Agent template page, and modify bugs in knowledge base #3221 ### Type of change - [x] Bug Fix (non-breaking change which fixes an issue)tags/v0.20.0
| @@ -12,13 +12,6 @@ import { | |||
| type EntityTypesFormFieldProps = { | |||
| name?: string; | |||
| }; | |||
| const initialEntityTypes = [ | |||
| 'organization', | |||
| 'person', | |||
| 'geo', | |||
| 'event', | |||
| 'category', | |||
| ]; | |||
| export function EntityTypesFormField({ | |||
| name = 'parser_config.entity_types', | |||
| }: EntityTypesFormFieldProps) { | |||
| @@ -29,7 +22,6 @@ export function EntityTypesFormField({ | |||
| <FormField | |||
| control={form.control} | |||
| name={name} | |||
| defaultValue={initialEntityTypes} | |||
| render={({ field }) => { | |||
| return ( | |||
| <FormItem className=" items-center space-y-0 "> | |||
| @@ -3,9 +3,8 @@ import { useTranslate } from '@/hooks/common-hooks'; | |||
| import { useFetchKnowledgeList } from '@/hooks/knowledge-hooks'; | |||
| import { UserOutlined } from '@ant-design/icons'; | |||
| import { Avatar as AntAvatar, Form, Select, Space } from 'antd'; | |||
| import { Book } from 'lucide-react'; | |||
| import { useFormContext } from 'react-hook-form'; | |||
| import { Avatar, AvatarFallback, AvatarImage } from './ui/avatar'; | |||
| import { RAGFlowAvatar } from './ragflow-avatar'; | |||
| import { FormControl, FormField, FormItem, FormLabel } from './ui/form'; | |||
| import { MultiSelect } from './ui/multi-select'; | |||
| @@ -81,12 +80,7 @@ export function KnowledgeBaseFormField() { | |||
| label: x.name, | |||
| value: x.id, | |||
| icon: () => ( | |||
| <Avatar className="size-4 mr-2"> | |||
| <AvatarImage src={x.avatar} /> | |||
| <AvatarFallback> | |||
| <Book /> | |||
| </AvatarFallback> | |||
| </Avatar> | |||
| <RAGFlowAvatar className="size-4 mr-2" avatar={x.avatar} name={x.name} /> | |||
| ), | |||
| })); | |||
| @@ -3,7 +3,7 @@ import { DocumentParserType } from '@/constants/knowledge'; | |||
| import { useTranslate } from '@/hooks/common-hooks'; | |||
| import random from 'lodash/random'; | |||
| import { Plus } from 'lucide-react'; | |||
| import { useCallback } from 'react'; | |||
| import { useCallback, useEffect } from 'react'; | |||
| import { useFormContext, useWatch } from 'react-hook-form'; | |||
| import { SliderInputFormField } from '../slider-input-form-field'; | |||
| import { Button } from '../ui/button'; | |||
| @@ -46,6 +46,10 @@ export const showTagItems = (parserId: DocumentParserType) => { | |||
| const UseRaptorField = 'parser_config.raptor.use_raptor'; | |||
| const RandomSeedField = 'parser_config.raptor.random_seed'; | |||
| const MaxTokenField = 'parser_config.raptor.max_token'; | |||
| const ThresholdField = 'parser_config.raptor.threshold'; | |||
| const MaxCluster = 'parser_config.raptor.max_cluster'; | |||
| const Prompt = 'parser_config.raptor.prompt'; | |||
| // The three types "table", "resume" and "one" do not display this configuration. | |||
| @@ -53,6 +57,15 @@ const RaptorFormFields = () => { | |||
| const form = useFormContext(); | |||
| const { t } = useTranslate('knowledgeConfiguration'); | |||
| const useRaptor = useWatch({ name: UseRaptorField }); | |||
| useEffect(() => { | |||
| if (useRaptor) { | |||
| form.setValue(MaxTokenField, 256); | |||
| form.setValue(ThresholdField, 0.1); | |||
| form.setValue(MaxCluster, 64); | |||
| form.setValue(RandomSeedField, 0); | |||
| form.setValue(Prompt, t('promptText')); | |||
| } | |||
| }, [form, useRaptor, t]); | |||
| const handleGenerate = useCallback(() => { | |||
| form.setValue(RandomSeedField, random(10000)); | |||
| @@ -114,11 +127,7 @@ const RaptorFormFields = () => { | |||
| </FormLabel> | |||
| <div className="w-3/4"> | |||
| <FormControl> | |||
| <Textarea | |||
| {...field} | |||
| rows={8} | |||
| defaultValue={t('promptText')} | |||
| /> | |||
| <Textarea {...field} rows={8} /> | |||
| </FormControl> | |||
| </div> | |||
| </div> | |||
| @@ -134,7 +143,6 @@ const RaptorFormFields = () => { | |||
| name={'parser_config.raptor.max_token'} | |||
| label={t('maxToken')} | |||
| tooltip={t('maxTokenTip')} | |||
| defaultValue={256} | |||
| max={2048} | |||
| min={0} | |||
| layout={FormLayout.Horizontal} | |||
| @@ -143,7 +151,6 @@ const RaptorFormFields = () => { | |||
| name={'parser_config.raptor.threshold'} | |||
| label={t('threshold')} | |||
| tooltip={t('thresholdTip')} | |||
| defaultValue={0.1} | |||
| step={0.01} | |||
| max={1} | |||
| min={0} | |||
| @@ -153,7 +160,6 @@ const RaptorFormFields = () => { | |||
| name={'parser_config.raptor.max_cluster'} | |||
| label={t('maxCluster')} | |||
| tooltip={t('maxClusterTip')} | |||
| defaultValue={64} | |||
| max={1024} | |||
| min={1} | |||
| layout={FormLayout.Horizontal} | |||
| @@ -143,7 +143,12 @@ export const useComposeLlmOptionsByModelTypes = ( | |||
| return modelTypes.reduce< | |||
| (DefaultOptionType & { | |||
| options: { label: JSX.Element; value: string; disabled: boolean; is_tools: boolean }[]; | |||
| options: { | |||
| label: JSX.Element; | |||
| value: string; | |||
| disabled: boolean; | |||
| is_tools: boolean; | |||
| }[]; | |||
| })[] | |||
| >((pre, cur) => { | |||
| const options = allOptions[cur]; | |||
| @@ -211,7 +216,6 @@ export const useFetchMyLlmListDetailed = (): ResponseGetType< | |||
| return { data, loading }; | |||
| }; | |||
| export const useSelectLlmList = () => { | |||
| const { data: myLlmList, loading: myLlmListLoading } = useFetchMyLlmList(); | |||
| const { data: factoryList, loading: factoryListLoading } = | |||
| @@ -262,7 +266,7 @@ export const useSaveApiKey = () => { | |||
| if (data.code === 0) { | |||
| message.success(t('message.modified')); | |||
| queryClient.invalidateQueries({ queryKey: ['myLlmList'] }); | |||
| queryClient.invalidateQueries({ queryKey: ['myLlmListDetailed'] }); | |||
| queryClient.invalidateQueries({ queryKey: ['myLlmListDetailed'] }); | |||
| queryClient.invalidateQueries({ queryKey: ['factoryList'] }); | |||
| } | |||
| return data.code; | |||
| @@ -314,7 +318,7 @@ export const useAddLlm = () => { | |||
| const { data } = await userService.add_llm(params); | |||
| if (data.code === 0) { | |||
| queryClient.invalidateQueries({ queryKey: ['myLlmList'] }); | |||
| queryClient.invalidateQueries({ queryKey: ['myLlmListDetailed'] }); | |||
| queryClient.invalidateQueries({ queryKey: ['myLlmListDetailed'] }); | |||
| queryClient.invalidateQueries({ queryKey: ['factoryList'] }); | |||
| message.success(t('message.modified')); | |||
| } | |||
| @@ -338,7 +342,7 @@ export const useDeleteLlm = () => { | |||
| const { data } = await userService.delete_llm(params); | |||
| if (data.code === 0) { | |||
| queryClient.invalidateQueries({ queryKey: ['myLlmList'] }); | |||
| queryClient.invalidateQueries({ queryKey: ['myLlmListDetailed'] }); | |||
| queryClient.invalidateQueries({ queryKey: ['myLlmListDetailed'] }); | |||
| queryClient.invalidateQueries({ queryKey: ['factoryList'] }); | |||
| message.success(t('message.deleted')); | |||
| } | |||
| @@ -362,7 +366,7 @@ export const useDeleteFactory = () => { | |||
| const { data } = await userService.deleteFactory(params); | |||
| if (data.code === 0) { | |||
| queryClient.invalidateQueries({ queryKey: ['myLlmList'] }); | |||
| queryClient.invalidateQueries({ queryKey: ['myLlmListDetailed'] }); | |||
| queryClient.invalidateQueries({ queryKey: ['myLlmListDetailed'] }); | |||
| queryClient.invalidateQueries({ queryKey: ['factoryList'] }); | |||
| message.success(t('message.deleted')); | |||
| } | |||
| @@ -1296,6 +1296,7 @@ This delimiter is used to split the input text into several text pieces echo of | |||
| agentDescription: | |||
| 'Builds agent components equipped with reasoning, tool usage, and multi-agent collaboration. ', | |||
| maxRecords: 'Max records', | |||
| createAgent: 'Create Agent', | |||
| stringTransform: 'String transform', | |||
| userFillUp: 'Input', | |||
| codeExec: 'Code', | |||
| @@ -1248,6 +1248,7 @@ General:实体和关系提取提示来自 GitHub - microsoft/graphrag:基于 | |||
| agent: 'Agent', | |||
| agentDescription: '构建具备推理、工具调用和多智能体协同的智能体组件。', | |||
| maxRecords: '最大记录数', | |||
| createAgent: 'Create Agent', | |||
| stringTransform: '文本处理', | |||
| userFillUp: '等待输入', | |||
| codeExec: '代码', | |||
| @@ -1,8 +1,8 @@ | |||
| import { RAGFlowAvatar } from '@/components/ragflow-avatar'; | |||
| import { useFetchKnowledgeList } from '@/hooks/knowledge-hooks'; | |||
| import { IRetrievalNode } from '@/interfaces/database/flow'; | |||
| import { UserOutlined } from '@ant-design/icons'; | |||
| import { NodeProps, Position } from '@xyflow/react'; | |||
| import { Avatar, Flex } from 'antd'; | |||
| import { Flex } from 'antd'; | |||
| import classNames from 'classnames'; | |||
| import { get } from 'lodash'; | |||
| import { memo, useMemo } from 'react'; | |||
| @@ -68,10 +68,11 @@ function InnerRetrievalNode({ | |||
| return ( | |||
| <div className={styles.nodeText} key={knowledge.id}> | |||
| <Flex align={'center'} gap={6}> | |||
| <Avatar | |||
| size={26} | |||
| icon={<UserOutlined />} | |||
| src={knowledge.avatar} | |||
| <RAGFlowAvatar | |||
| className="size-6 rounded-lg" | |||
| avatar={knowledge.avatar} | |||
| name={knowledge.name || 'CN'} | |||
| isPerson={true} | |||
| /> | |||
| <Flex className={styles.knowledgeNodeName} flex={1}> | |||
| {knowledge.name} | |||
| @@ -11,7 +11,7 @@ import { useSetModalState } from '@/hooks/common-hooks'; | |||
| import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks'; | |||
| import { useFetchAgentTemplates, useSetAgent } from '@/hooks/use-agent-request'; | |||
| import { IFlowTemplate } from '@/interfaces/database/flow'; | |||
| import { useCallback, useState } from 'react'; | |||
| import { useCallback, useEffect, useState } from 'react'; | |||
| import { useTranslation } from 'react-i18next'; | |||
| import { CreateAgentDialog } from './create-agent-dialog'; | |||
| import { TemplateCard } from './template-card'; | |||
| @@ -21,7 +21,11 @@ export default function AgentTemplates() { | |||
| const { t } = useTranslation(); | |||
| const list = useFetchAgentTemplates(); | |||
| const { loading, setAgent } = useSetAgent(); | |||
| const [templateList, setTemplateList] = useState<IFlowTemplate[]>([]); | |||
| useEffect(() => { | |||
| setTemplateList(list); | |||
| }, [list]); | |||
| const { | |||
| visible: creatingVisible, | |||
| hideModal: hideCreatingModal, | |||
| @@ -62,7 +66,14 @@ export default function AgentTemplates() { | |||
| template?.dsl, | |||
| ], | |||
| ); | |||
| const handleSiderBarChange = (keyword: string) => { | |||
| const tempList = list.filter( | |||
| (item, index) => | |||
| item.title.toLocaleLowerCase().includes(keyword?.toLocaleLowerCase()) || | |||
| index === 0, | |||
| ); | |||
| setTemplateList(tempList); | |||
| }; | |||
| return ( | |||
| <section> | |||
| <PageHeader> | |||
| @@ -1,47 +1,59 @@ | |||
| import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; | |||
| import { RAGFlowAvatar } from '@/components/ragflow-avatar'; | |||
| import { Button } from '@/components/ui/button'; | |||
| import { Card, CardContent } from '@/components/ui/card'; | |||
| import { IFlowTemplate } from '@/interfaces/database/flow'; | |||
| import { Plus } from 'lucide-react'; | |||
| import { useCallback } from 'react'; | |||
| import { useTranslation } from 'react-i18next'; | |||
| interface IProps { | |||
| data: IFlowTemplate; | |||
| isCreate?: boolean; | |||
| showModal(record: IFlowTemplate): void; | |||
| } | |||
| export function TemplateCard({ data, showModal }: IProps) { | |||
| export function TemplateCard({ data, showModal, isCreate = false }: IProps) { | |||
| const { t } = useTranslation(); | |||
| const handleClick = useCallback(() => { | |||
| showModal(data); | |||
| }, [data, showModal]); | |||
| return ( | |||
| <Card className="bg-colors-background-inverse-weak border-colors-outline-neutral-standard group relative"> | |||
| <Card className="bg-colors-background-inverse-weak border-colors-outline-neutral-standard group relative min-h-40"> | |||
| <CardContent className="p-4 "> | |||
| <div className="flex justify-between mb-4"> | |||
| {data.avatar ? ( | |||
| <div | |||
| className="w-[70px] h-[70px] rounded-xl bg-cover" | |||
| style={{ backgroundImage: `url(${data.avatar})` }} | |||
| /> | |||
| ) : ( | |||
| <Avatar className="w-[70px] h-[70px]"> | |||
| <AvatarImage src="https://github.com/shadcn.png" /> | |||
| <AvatarFallback>CN</AvatarFallback> | |||
| </Avatar> | |||
| )} | |||
| </div> | |||
| <h3 className="text-xl font-bold mb-2">{data.title}</h3> | |||
| <p className="break-words">{data.description}</p> | |||
| <Button | |||
| variant="tertiary" | |||
| className="absolute bottom-4 right-4 left-4 hidden justify-end group-hover:block text-center" | |||
| onClick={handleClick} | |||
| > | |||
| {t('flow.useTemplate')} | |||
| </Button> | |||
| {isCreate && ( | |||
| <div | |||
| className="flex flex-col justify-center items-center gap-4 mb-4 absolute top-0 right-0 left-0 bottom-0 cursor-pointer " | |||
| onClick={handleClick} | |||
| > | |||
| <Plus size={50} fontWeight={700} /> | |||
| <div>{t('flow.createAgent')}</div> | |||
| </div> | |||
| )} | |||
| {!isCreate && ( | |||
| <> | |||
| <div className="flex justify-start items-center gap-4 mb-4"> | |||
| <RAGFlowAvatar | |||
| className="w-7 h-7" | |||
| avatar={ | |||
| data.avatar ? data.avatar : 'https://github.com/shadcn.png' | |||
| } | |||
| name={data?.title || 'CN'} | |||
| ></RAGFlowAvatar> | |||
| <div className="text-[18px] font-bold ">{data.title}</div> | |||
| </div> | |||
| <p className="break-words">{data.description}</p> | |||
| <div className="group-hover:bg-gradient-to-t from-black/70 from-10% via-black/0 via-50% to-black/0 w-full h-full group-hover:block absolute top-0 left-0 hidden rounded-xl"> | |||
| <Button | |||
| variant="default" | |||
| className="w-1/3 absolute bottom-4 right-4 left-4 justify-center text-center m-auto" | |||
| onClick={handleClick} | |||
| > | |||
| {t('flow.useTemplate')} | |||
| </Button> | |||
| </div> | |||
| </> | |||
| )} | |||
| </CardContent> | |||
| </Card> | |||
| ); | |||
| @@ -0,0 +1,57 @@ | |||
| import { Button } from '@/components/ui/button'; | |||
| import { useSecondPathName } from '@/hooks/route-hook'; | |||
| import { cn } from '@/lib/utils'; | |||
| import { Banknote, LayoutGrid, User } from 'lucide-react'; | |||
| const menuItems = [ | |||
| { | |||
| section: 'All Templates', | |||
| items: [ | |||
| { icon: User, label: 'Assistant', key: 'Assistant' }, | |||
| { icon: LayoutGrid, label: 'chatbot', key: 'chatbot' }, | |||
| { icon: Banknote, label: 'generator', key: 'generator' }, | |||
| { icon: Banknote, label: 'Intel', key: 'Intel' }, | |||
| ], | |||
| }, | |||
| ]; | |||
| export function SideBar({ change }: { change: (keyword: string) => void }) { | |||
| const pathName = useSecondPathName(); | |||
| const handleMenuClick = (key: string) => { | |||
| change(key); | |||
| }; | |||
| return ( | |||
| <aside className="w-[303px] bg-background border-r flex flex-col"> | |||
| <div className="flex-1 overflow-auto"> | |||
| {menuItems.map((section, idx) => ( | |||
| <div key={idx}> | |||
| <h2 | |||
| className="p-6 text-sm font-semibold hover:bg-muted/50 cursor-pointer" | |||
| onClick={() => handleMenuClick('')} | |||
| > | |||
| {section.section} | |||
| </h2> | |||
| {section.items.map((item, itemIdx) => { | |||
| const active = pathName === item.key; | |||
| return ( | |||
| <Button | |||
| key={itemIdx} | |||
| variant={active ? 'secondary' : 'ghost'} | |||
| className={cn('w-full justify-start gap-2.5 p-6 relative')} | |||
| onClick={() => handleMenuClick(item.key)} | |||
| > | |||
| <item.icon className="w-6 h-6" /> | |||
| <span>{item.label}</span> | |||
| {active && ( | |||
| <div className="absolute right-0 w-[5px] h-[66px] bg-primary rounded-l-xl shadow-[0_0_5.94px_#7561ff,0_0_11.88px_#7561ff,0_0_41.58px_#7561ff,0_0_83.16px_#7561ff,0_0_142.56px_#7561ff,0_0_249.48px_#7561ff]" /> | |||
| )} | |||
| </Button> | |||
| ); | |||
| })} | |||
| </div> | |||
| ))} | |||
| </div> | |||
| </aside> | |||
| ); | |||
| } | |||
| @@ -6,6 +6,11 @@ import { | |||
| DropdownMenuItem, | |||
| DropdownMenuTrigger, | |||
| } from '@/components/ui/dropdown-menu'; | |||
| import { | |||
| HoverCard, | |||
| HoverCardContent, | |||
| HoverCardTrigger, | |||
| } from '@/components/ui/hover-card'; | |||
| import { Progress } from '@/components/ui/progress'; | |||
| import { Separator } from '@/components/ui/separator'; | |||
| import { IDocumentInfo } from '@/interfaces/database/document'; | |||
| @@ -13,12 +18,11 @@ import { CircleX, Play, RefreshCw } from 'lucide-react'; | |||
| import { useCallback } from 'react'; | |||
| import { useTranslation } from 'react-i18next'; | |||
| import { RunningStatus } from './constant'; | |||
| import { ParsingCard } from './parsing-card'; | |||
| import { ParsingCard, PopoverContent } from './parsing-card'; | |||
| import { UseChangeDocumentParserShowType } from './use-change-document-parser'; | |||
| import { useHandleRunDocumentByIds } from './use-run-document'; | |||
| import { UseSaveMetaShowType } from './use-save-meta'; | |||
| import { isParserRunning } from './utils'; | |||
| const IconMap = { | |||
| [RunningStatus.UNSTART]: <Play />, | |||
| [RunningStatus.RUNNING]: <CircleX />, | |||
| @@ -94,10 +98,17 @@ export function ParsingStatusCell({ | |||
| </Button> | |||
| </ConfirmDeleteDialog> | |||
| {isParserRunning(run) ? ( | |||
| <div className="flex items-center gap-1"> | |||
| <Progress value={p} className="h-1 flex-1 min-w-10" /> | |||
| {p}% | |||
| </div> | |||
| <HoverCard> | |||
| <HoverCardTrigger asChild> | |||
| <div className="flex items-center gap-1"> | |||
| <Progress value={p} className="h-1 flex-1 min-w-10" /> | |||
| {p}% | |||
| </div> | |||
| </HoverCardTrigger> | |||
| <HoverCardContent className="w-[40vw]"> | |||
| <PopoverContent record={record}></PopoverContent> | |||
| </HoverCardContent> | |||
| </HoverCard> | |||
| ) : ( | |||
| <ParsingCard record={record}></ParsingCard> | |||
| )} | |||
| @@ -71,9 +71,13 @@ export const useFetchKnowledgeConfigurationOnMount = ( | |||
| knowledgeDetails.avatar, | |||
| ); | |||
| console.log('🚀 ~ useEffect ~ fileList:', fileList); | |||
| form.reset({ | |||
| ...pick(knowledgeDetails, [ | |||
| console.log('🚀 ~ useEffect ~ fileList:', fileList, knowledgeDetails); | |||
| const parser_config = { | |||
| ...form.formState?.defaultValues?.parser_config, | |||
| ...knowledgeDetails.parser_config, | |||
| }; | |||
| const formValues = { | |||
| ...pick({ ...knowledgeDetails, parser_config: parser_config }, [ | |||
| 'description', | |||
| 'name', | |||
| 'permission', | |||
| @@ -83,6 +87,9 @@ export const useFetchKnowledgeConfigurationOnMount = ( | |||
| 'parser_config', | |||
| 'pagerank', | |||
| ]), | |||
| }; | |||
| form.reset({ | |||
| ...formValues, | |||
| avatar: fileList, | |||
| }); | |||
| }, [form, knowledgeDetails]); | |||
| @@ -54,10 +54,6 @@ export default function DatasetSettings() { | |||
| topn_tags: 3, | |||
| raptor: { | |||
| use_raptor: false, | |||
| max_token: 256, | |||
| threshold: 0.1, | |||
| max_cluster: 64, | |||
| random_seed: 0, | |||
| }, | |||
| graphrag: { | |||
| use_graphrag: false, | |||
| @@ -1,5 +1,5 @@ | |||
| import { MoreButton } from '@/components/more-button'; | |||
| import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; | |||
| import { RAGFlowAvatar } from '@/components/ragflow-avatar'; | |||
| import { Badge } from '@/components/ui/badge'; | |||
| import { Card, CardContent } from '@/components/ui/card'; | |||
| import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks'; | |||
| @@ -32,10 +32,11 @@ export function DatasetCard({ | |||
| <CardContent className="p-2.5 pt-2 group"> | |||
| <section className="flex justify-between mb-2"> | |||
| <div className="flex gap-2 items-center"> | |||
| <Avatar className="size-6 rounded-lg"> | |||
| <AvatarImage src={dataset.avatar} /> | |||
| <AvatarFallback className="rounded-lg ">CN</AvatarFallback> | |||
| </Avatar> | |||
| <RAGFlowAvatar | |||
| className="size-6 rounded-lg" | |||
| avatar={dataset.avatar} | |||
| name={dataset.name || 'CN'} | |||
| ></RAGFlowAvatar> | |||
| {owner && ( | |||
| <Badge className="h-5 rounded-sm px-1 bg-background-badge text-text-badge"> | |||
| {owner} | |||
| @@ -111,7 +111,9 @@ export const useFetchSystemModelSettingOnMount = () => { | |||
| export const useSubmitOllama = () => { | |||
| const [selectedLlmFactory, setSelectedLlmFactory] = useState<string>(''); | |||
| const [editMode, setEditMode] = useState(false); | |||
| const [initialValues, setInitialValues] = useState<Partial<IAddLlmRequestBody> | undefined>(); | |||
| const [initialValues, setInitialValues] = useState< | |||
| Partial<IAddLlmRequestBody> | undefined | |||
| >(); | |||
| const [originalModelName, setOriginalModelName] = useState<string>(''); | |||
| const { addLlm, loading } = useAddLlm(); | |||
| const { | |||
| @@ -126,7 +128,7 @@ export const useSubmitOllama = () => { | |||
| if (!cleanedPayload.api_key || cleanedPayload.api_key.trim() === '') { | |||
| delete cleanedPayload.api_key; | |||
| } | |||
| const ret = await addLlm(cleanedPayload); | |||
| if (ret === 0) { | |||
| hideLlmAddingModal(); | |||
| @@ -137,10 +139,15 @@ export const useSubmitOllama = () => { | |||
| [hideLlmAddingModal, addLlm], | |||
| ); | |||
| const handleShowLlmAddingModal = (llmFactory: string, isEdit = false, modelData?: any, detailedData?: any) => { | |||
| const handleShowLlmAddingModal = ( | |||
| llmFactory: string, | |||
| isEdit = false, | |||
| modelData?: any, | |||
| detailedData?: any, | |||
| ) => { | |||
| setSelectedLlmFactory(llmFactory); | |||
| setEditMode(isEdit); | |||
| if (isEdit && detailedData) { | |||
| const initialVals = { | |||
| llm_name: getRealModelName(detailedData.name), | |||
| @@ -3,9 +3,17 @@ import { LlmIcon } from '@/components/svg-icon'; | |||
| import { useTheme } from '@/components/theme-provider'; | |||
| import { LLMFactory } from '@/constants/llm'; | |||
| import { useSetModalState, useTranslate } from '@/hooks/common-hooks'; | |||
| import { LlmItem, useSelectLlmList, useFetchMyLlmListDetailed } from '@/hooks/llm-hooks'; | |||
| import { | |||
| LlmItem, | |||
| useFetchMyLlmListDetailed, | |||
| useSelectLlmList, | |||
| } from '@/hooks/llm-hooks'; | |||
| import { getRealModelName } from '@/utils/llm-util'; | |||
| import { CloseCircleOutlined, EditOutlined, SettingOutlined } from '@ant-design/icons'; | |||
| import { | |||
| CloseCircleOutlined, | |||
| EditOutlined, | |||
| SettingOutlined, | |||
| } from '@ant-design/icons'; | |||
| import { | |||
| Button, | |||
| Card, | |||
| @@ -137,7 +145,10 @@ const ModelCard = ({ item, clickApiKey, handleEditModel }: IModelCardProps) => { | |||
| <Tag color="#b8b8b8">{model.type}</Tag> | |||
| {isLocalLlmFactory(item.name) && ( | |||
| <Tooltip title={t('edit', { keyPrefix: 'common' })}> | |||
| <Button type={'text'} onClick={() => handleEditModel(model, item)}> | |||
| <Button | |||
| type={'text'} | |||
| onClick={() => handleEditModel(model, item)} | |||
| > | |||
| <EditOutlined style={{ color: '#1890ff' }} /> | |||
| </Button> | |||
| </Tooltip> | |||
| @@ -304,14 +315,16 @@ const UserSettingModel = () => { | |||
| (model: any, factory: LlmItem) => { | |||
| if (factory) { | |||
| const detailedFactory = detailedLlmList[factory.name]; | |||
| const detailedModel = detailedFactory?.llm?.find((m: any) => m.name === model.name); | |||
| const detailedModel = detailedFactory?.llm?.find( | |||
| (m: any) => m.name === model.name, | |||
| ); | |||
| const editData = { | |||
| llm_factory: factory.name, | |||
| llm_name: model.name, | |||
| model_type: model.type | |||
| model_type: model.type, | |||
| }; | |||
| if (isLocalLlmFactory(factory.name)) { | |||
| showLlmAddingModal(factory.name, true, editData, detailedModel); | |||
| } else if (factory.name in ModalMap) { | |||
| @@ -333,7 +346,11 @@ const UserSettingModel = () => { | |||
| grid={{ gutter: 16, column: 1 }} | |||
| dataSource={llmList} | |||
| renderItem={(item) => ( | |||
| <ModelCard item={item} clickApiKey={handleAddModel} handleEditModel={handleEditModel}></ModelCard> | |||
| <ModelCard | |||
| item={item} | |||
| clickApiKey={handleAddModel} | |||
| handleEditModel={handleEditModel} | |||
| ></ModelCard> | |||
| )} | |||
| /> | |||
| ), | |||
| @@ -48,8 +48,8 @@ const OllamaModal = ({ | |||
| llmFactory, | |||
| editMode = false, | |||
| initialValues, | |||
| }: IModalProps<IAddLlmRequestBody> & { | |||
| llmFactory: string; | |||
| }: IModalProps<IAddLlmRequestBody> & { | |||
| llmFactory: string; | |||
| editMode?: boolean; | |||
| initialValues?: Partial<IAddLlmRequestBody>; | |||
| }) => { | |||
| @@ -96,7 +96,7 @@ const OllamaModal = ({ | |||
| form.resetFields(); | |||
| } | |||
| }, [visible, editMode, initialValues, form]); | |||
| const url = | |||
| llmFactoryToUrlMap[llmFactory as LlmFactory] || | |||
| 'https://github.com/infiniflow/ragflow/blob/main/docs/guides/models/deploy_local_llm.mdx'; | |||
| @@ -134,7 +134,11 @@ const OllamaModal = ({ | |||
| }; | |||
| return ( | |||
| <Modal | |||
| title={editMode ? t('editLlmTitle', { name: llmFactory }) : t('addLlmTitle', { name: llmFactory })} | |||
| title={ | |||
| editMode | |||
| ? t('editLlmTitle', { name: llmFactory }) | |||
| : t('addLlmTitle', { name: llmFactory }) | |||
| } | |||
| open={visible} | |||
| onOk={handleOk} | |||
| onCancel={hideModal} | |||
| @@ -196,10 +200,7 @@ const OllamaModal = ({ | |||
| name="api_key" | |||
| rules={[{ required: false, message: t('apiKeyMessage') }]} | |||
| > | |||
| <Input | |||
| placeholder={t('apiKeyMessage')} | |||
| onKeyDown={handleKeyDown} | |||
| /> | |||
| <Input placeholder={t('apiKeyMessage')} onKeyDown={handleKeyDown} /> | |||
| </Form.Item> | |||
| <Form.Item<FieldType> | |||
| label={t('maxTokens')} | |||