ソースを参照

Fix:Optimize Agent template page, fix bugs in knowledge base (#9009)

### 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
chanx 3ヶ月前
コミット
03e39ca9be
コミッターのメールアドレスに関連付けられたアカウントが存在しません

+ 0
- 8
web/src/components/entity-types-form-field.tsx ファイルの表示

type EntityTypesFormFieldProps = { type EntityTypesFormFieldProps = {
name?: string; name?: string;
}; };
const initialEntityTypes = [
'organization',
'person',
'geo',
'event',
'category',
];
export function EntityTypesFormField({ export function EntityTypesFormField({
name = 'parser_config.entity_types', name = 'parser_config.entity_types',
}: EntityTypesFormFieldProps) { }: EntityTypesFormFieldProps) {
<FormField <FormField
control={form.control} control={form.control}
name={name} name={name}
defaultValue={initialEntityTypes}
render={({ field }) => { render={({ field }) => {
return ( return (
<FormItem className=" items-center space-y-0 "> <FormItem className=" items-center space-y-0 ">

+ 2
- 8
web/src/components/knowledge-base-item.tsx ファイルの表示

import { useFetchKnowledgeList } from '@/hooks/knowledge-hooks'; import { useFetchKnowledgeList } from '@/hooks/knowledge-hooks';
import { UserOutlined } from '@ant-design/icons'; import { UserOutlined } from '@ant-design/icons';
import { Avatar as AntAvatar, Form, Select, Space } from 'antd'; import { Avatar as AntAvatar, Form, Select, Space } from 'antd';
import { Book } from 'lucide-react';
import { useFormContext } from 'react-hook-form'; 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 { FormControl, FormField, FormItem, FormLabel } from './ui/form';
import { MultiSelect } from './ui/multi-select'; import { MultiSelect } from './ui/multi-select';


label: x.name, label: x.name,
value: x.id, value: x.id,
icon: () => ( 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} />
), ),
})); }));



+ 15
- 9
web/src/components/parse-configuration/raptor-form-fields.tsx ファイルの表示

import { useTranslate } from '@/hooks/common-hooks'; import { useTranslate } from '@/hooks/common-hooks';
import random from 'lodash/random'; import random from 'lodash/random';
import { Plus } from 'lucide-react'; import { Plus } from 'lucide-react';
import { useCallback } from 'react';
import { useCallback, useEffect } from 'react';
import { useFormContext, useWatch } from 'react-hook-form'; import { useFormContext, useWatch } from 'react-hook-form';
import { SliderInputFormField } from '../slider-input-form-field'; import { SliderInputFormField } from '../slider-input-form-field';
import { Button } from '../ui/button'; import { Button } from '../ui/button';


const UseRaptorField = 'parser_config.raptor.use_raptor'; const UseRaptorField = 'parser_config.raptor.use_raptor';
const RandomSeedField = 'parser_config.raptor.random_seed'; 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. // The three types "table", "resume" and "one" do not display this configuration.


const form = useFormContext(); const form = useFormContext();
const { t } = useTranslate('knowledgeConfiguration'); const { t } = useTranslate('knowledgeConfiguration');
const useRaptor = useWatch({ name: UseRaptorField }); 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(() => { const handleGenerate = useCallback(() => {
form.setValue(RandomSeedField, random(10000)); form.setValue(RandomSeedField, random(10000));
</FormLabel> </FormLabel>
<div className="w-3/4"> <div className="w-3/4">
<FormControl> <FormControl>
<Textarea
{...field}
rows={8}
defaultValue={t('promptText')}
/>
<Textarea {...field} rows={8} />
</FormControl> </FormControl>
</div> </div>
</div> </div>
name={'parser_config.raptor.max_token'} name={'parser_config.raptor.max_token'}
label={t('maxToken')} label={t('maxToken')}
tooltip={t('maxTokenTip')} tooltip={t('maxTokenTip')}
defaultValue={256}
max={2048} max={2048}
min={0} min={0}
layout={FormLayout.Horizontal} layout={FormLayout.Horizontal}
name={'parser_config.raptor.threshold'} name={'parser_config.raptor.threshold'}
label={t('threshold')} label={t('threshold')}
tooltip={t('thresholdTip')} tooltip={t('thresholdTip')}
defaultValue={0.1}
step={0.01} step={0.01}
max={1} max={1}
min={0} min={0}
name={'parser_config.raptor.max_cluster'} name={'parser_config.raptor.max_cluster'}
label={t('maxCluster')} label={t('maxCluster')}
tooltip={t('maxClusterTip')} tooltip={t('maxClusterTip')}
defaultValue={64}
max={1024} max={1024}
min={1} min={1}
layout={FormLayout.Horizontal} layout={FormLayout.Horizontal}

+ 10
- 6
web/src/hooks/llm-hooks.tsx ファイルの表示



return modelTypes.reduce< return modelTypes.reduce<
(DefaultOptionType & { (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) => { >((pre, cur) => {
const options = allOptions[cur]; const options = allOptions[cur];
return { data, loading }; return { data, loading };
}; };



export const useSelectLlmList = () => { export const useSelectLlmList = () => {
const { data: myLlmList, loading: myLlmListLoading } = useFetchMyLlmList(); const { data: myLlmList, loading: myLlmListLoading } = useFetchMyLlmList();
const { data: factoryList, loading: factoryListLoading } = const { data: factoryList, loading: factoryListLoading } =
if (data.code === 0) { if (data.code === 0) {
message.success(t('message.modified')); message.success(t('message.modified'));
queryClient.invalidateQueries({ queryKey: ['myLlmList'] }); queryClient.invalidateQueries({ queryKey: ['myLlmList'] });
queryClient.invalidateQueries({ queryKey: ['myLlmListDetailed'] });
queryClient.invalidateQueries({ queryKey: ['myLlmListDetailed'] });
queryClient.invalidateQueries({ queryKey: ['factoryList'] }); queryClient.invalidateQueries({ queryKey: ['factoryList'] });
} }
return data.code; return data.code;
const { data } = await userService.add_llm(params); const { data } = await userService.add_llm(params);
if (data.code === 0) { if (data.code === 0) {
queryClient.invalidateQueries({ queryKey: ['myLlmList'] }); queryClient.invalidateQueries({ queryKey: ['myLlmList'] });
queryClient.invalidateQueries({ queryKey: ['myLlmListDetailed'] });
queryClient.invalidateQueries({ queryKey: ['myLlmListDetailed'] });
queryClient.invalidateQueries({ queryKey: ['factoryList'] }); queryClient.invalidateQueries({ queryKey: ['factoryList'] });
message.success(t('message.modified')); message.success(t('message.modified'));
} }
const { data } = await userService.delete_llm(params); const { data } = await userService.delete_llm(params);
if (data.code === 0) { if (data.code === 0) {
queryClient.invalidateQueries({ queryKey: ['myLlmList'] }); queryClient.invalidateQueries({ queryKey: ['myLlmList'] });
queryClient.invalidateQueries({ queryKey: ['myLlmListDetailed'] });
queryClient.invalidateQueries({ queryKey: ['myLlmListDetailed'] });
queryClient.invalidateQueries({ queryKey: ['factoryList'] }); queryClient.invalidateQueries({ queryKey: ['factoryList'] });
message.success(t('message.deleted')); message.success(t('message.deleted'));
} }
const { data } = await userService.deleteFactory(params); const { data } = await userService.deleteFactory(params);
if (data.code === 0) { if (data.code === 0) {
queryClient.invalidateQueries({ queryKey: ['myLlmList'] }); queryClient.invalidateQueries({ queryKey: ['myLlmList'] });
queryClient.invalidateQueries({ queryKey: ['myLlmListDetailed'] });
queryClient.invalidateQueries({ queryKey: ['myLlmListDetailed'] });
queryClient.invalidateQueries({ queryKey: ['factoryList'] }); queryClient.invalidateQueries({ queryKey: ['factoryList'] });
message.success(t('message.deleted')); message.success(t('message.deleted'));
} }

+ 1
- 0
web/src/locales/en.ts ファイルの表示

agentDescription: agentDescription:
'Builds agent components equipped with reasoning, tool usage, and multi-agent collaboration. ', 'Builds agent components equipped with reasoning, tool usage, and multi-agent collaboration. ',
maxRecords: 'Max records', maxRecords: 'Max records',
createAgent: 'Create Agent',
stringTransform: 'String transform', stringTransform: 'String transform',
userFillUp: 'Input', userFillUp: 'Input',
codeExec: 'Code', codeExec: 'Code',

+ 1
- 0
web/src/locales/zh.ts ファイルの表示

agent: 'Agent', agent: 'Agent',
agentDescription: '构建具备推理、工具调用和多智能体协同的智能体组件。', agentDescription: '构建具备推理、工具调用和多智能体协同的智能体组件。',
maxRecords: '最大记录数', maxRecords: '最大记录数',
createAgent: 'Create Agent',
stringTransform: '文本处理', stringTransform: '文本处理',
userFillUp: '等待输入', userFillUp: '等待输入',
codeExec: '代码', codeExec: '代码',

+ 7
- 6
web/src/pages/agent/canvas/node/retrieval-node.tsx ファイルの表示

import { RAGFlowAvatar } from '@/components/ragflow-avatar';
import { useFetchKnowledgeList } from '@/hooks/knowledge-hooks'; import { useFetchKnowledgeList } from '@/hooks/knowledge-hooks';
import { IRetrievalNode } from '@/interfaces/database/flow'; import { IRetrievalNode } from '@/interfaces/database/flow';
import { UserOutlined } from '@ant-design/icons';
import { NodeProps, Position } from '@xyflow/react'; import { NodeProps, Position } from '@xyflow/react';
import { Avatar, Flex } from 'antd';
import { Flex } from 'antd';
import classNames from 'classnames'; import classNames from 'classnames';
import { get } from 'lodash'; import { get } from 'lodash';
import { memo, useMemo } from 'react'; import { memo, useMemo } from 'react';
return ( return (
<div className={styles.nodeText} key={knowledge.id}> <div className={styles.nodeText} key={knowledge.id}>
<Flex align={'center'} gap={6}> <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}> <Flex className={styles.knowledgeNodeName} flex={1}>
{knowledge.name} {knowledge.name}

+ 13
- 2
web/src/pages/agents/agent-templates.tsx ファイルの表示

import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks'; import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks';
import { useFetchAgentTemplates, useSetAgent } from '@/hooks/use-agent-request'; import { useFetchAgentTemplates, useSetAgent } from '@/hooks/use-agent-request';
import { IFlowTemplate } from '@/interfaces/database/flow'; import { IFlowTemplate } from '@/interfaces/database/flow';
import { useCallback, useState } from 'react';
import { useCallback, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { CreateAgentDialog } from './create-agent-dialog'; import { CreateAgentDialog } from './create-agent-dialog';
import { TemplateCard } from './template-card'; import { TemplateCard } from './template-card';
const { t } = useTranslation(); const { t } = useTranslation();
const list = useFetchAgentTemplates(); const list = useFetchAgentTemplates();
const { loading, setAgent } = useSetAgent(); const { loading, setAgent } = useSetAgent();
const [templateList, setTemplateList] = useState<IFlowTemplate[]>([]);


useEffect(() => {
setTemplateList(list);
}, [list]);
const { const {
visible: creatingVisible, visible: creatingVisible,
hideModal: hideCreatingModal, hideModal: hideCreatingModal,
template?.dsl, template?.dsl,
], ],
); );

const handleSiderBarChange = (keyword: string) => {
const tempList = list.filter(
(item, index) =>
item.title.toLocaleLowerCase().includes(keyword?.toLocaleLowerCase()) ||
index === 0,
);
setTemplateList(tempList);
};
return ( return (
<section> <section>
<PageHeader> <PageHeader>

+ 38
- 26
web/src/pages/agents/template-card.tsx ファイルの表示

import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
import { RAGFlowAvatar } from '@/components/ragflow-avatar';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Card, CardContent } from '@/components/ui/card'; import { Card, CardContent } from '@/components/ui/card';
import { IFlowTemplate } from '@/interfaces/database/flow'; import { IFlowTemplate } from '@/interfaces/database/flow';
import { Plus } from 'lucide-react';
import { useCallback } from 'react'; import { useCallback } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';


interface IProps { interface IProps {
data: IFlowTemplate; data: IFlowTemplate;
isCreate?: boolean;
showModal(record: IFlowTemplate): void; showModal(record: IFlowTemplate): void;
} }


export function TemplateCard({ data, showModal }: IProps) {
export function TemplateCard({ data, showModal, isCreate = false }: IProps) {
const { t } = useTranslation(); const { t } = useTranslation();


const handleClick = useCallback(() => { const handleClick = useCallback(() => {
showModal(data); showModal(data);
}, [data, showModal]); }, [data, showModal]);

return ( 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 "> <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> </CardContent>
</Card> </Card>
); );

+ 57
- 0
web/src/pages/agents/template-sidebar.tsx ファイルの表示

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>
);
}

+ 17
- 6
web/src/pages/dataset/dataset/parsing-status-cell.tsx ファイルの表示

DropdownMenuItem, DropdownMenuItem,
DropdownMenuTrigger, DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu'; } from '@/components/ui/dropdown-menu';
import {
HoverCard,
HoverCardContent,
HoverCardTrigger,
} from '@/components/ui/hover-card';
import { Progress } from '@/components/ui/progress'; import { Progress } from '@/components/ui/progress';
import { Separator } from '@/components/ui/separator'; import { Separator } from '@/components/ui/separator';
import { IDocumentInfo } from '@/interfaces/database/document'; import { IDocumentInfo } from '@/interfaces/database/document';
import { useCallback } from 'react'; import { useCallback } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { RunningStatus } from './constant'; import { RunningStatus } from './constant';
import { ParsingCard } from './parsing-card';
import { ParsingCard, PopoverContent } from './parsing-card';
import { UseChangeDocumentParserShowType } from './use-change-document-parser'; import { UseChangeDocumentParserShowType } from './use-change-document-parser';
import { useHandleRunDocumentByIds } from './use-run-document'; import { useHandleRunDocumentByIds } from './use-run-document';
import { UseSaveMetaShowType } from './use-save-meta'; import { UseSaveMetaShowType } from './use-save-meta';
import { isParserRunning } from './utils'; import { isParserRunning } from './utils';

const IconMap = { const IconMap = {
[RunningStatus.UNSTART]: <Play />, [RunningStatus.UNSTART]: <Play />,
[RunningStatus.RUNNING]: <CircleX />, [RunningStatus.RUNNING]: <CircleX />,
</Button> </Button>
</ConfirmDeleteDialog> </ConfirmDeleteDialog>
{isParserRunning(run) ? ( {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> <ParsingCard record={record}></ParsingCard>
)} )}

+ 10
- 3
web/src/pages/dataset/setting/hooks.ts ファイルの表示

knowledgeDetails.avatar, 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', 'description',
'name', 'name',
'permission', 'permission',
'parser_config', 'parser_config',
'pagerank', 'pagerank',
]), ]),
};
form.reset({
...formValues,
avatar: fileList, avatar: fileList,
}); });
}, [form, knowledgeDetails]); }, [form, knowledgeDetails]);

+ 0
- 4
web/src/pages/dataset/setting/index.tsx ファイルの表示

topn_tags: 3, topn_tags: 3,
raptor: { raptor: {
use_raptor: false, use_raptor: false,
max_token: 256,
threshold: 0.1,
max_cluster: 64,
random_seed: 0,
}, },
graphrag: { graphrag: {
use_graphrag: false, use_graphrag: false,

+ 6
- 5
web/src/pages/datasets/dataset-card.tsx ファイルの表示

import { MoreButton } from '@/components/more-button'; 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 { Badge } from '@/components/ui/badge';
import { Card, CardContent } from '@/components/ui/card'; import { Card, CardContent } from '@/components/ui/card';
import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks'; import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks';
<CardContent className="p-2.5 pt-2 group"> <CardContent className="p-2.5 pt-2 group">
<section className="flex justify-between mb-2"> <section className="flex justify-between mb-2">
<div className="flex gap-2 items-center"> <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 && ( {owner && (
<Badge className="h-5 rounded-sm px-1 bg-background-badge text-text-badge"> <Badge className="h-5 rounded-sm px-1 bg-background-badge text-text-badge">
{owner} {owner}

+ 11
- 4
web/src/pages/user-setting/setting-model/hooks.ts ファイルの表示

export const useSubmitOllama = () => { export const useSubmitOllama = () => {
const [selectedLlmFactory, setSelectedLlmFactory] = useState<string>(''); const [selectedLlmFactory, setSelectedLlmFactory] = useState<string>('');
const [editMode, setEditMode] = useState(false); 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 [originalModelName, setOriginalModelName] = useState<string>('');
const { addLlm, loading } = useAddLlm(); const { addLlm, loading } = useAddLlm();
const { const {
if (!cleanedPayload.api_key || cleanedPayload.api_key.trim() === '') { if (!cleanedPayload.api_key || cleanedPayload.api_key.trim() === '') {
delete cleanedPayload.api_key; delete cleanedPayload.api_key;
} }
const ret = await addLlm(cleanedPayload); const ret = await addLlm(cleanedPayload);
if (ret === 0) { if (ret === 0) {
hideLlmAddingModal(); hideLlmAddingModal();
[hideLlmAddingModal, addLlm], [hideLlmAddingModal, addLlm],
); );


const handleShowLlmAddingModal = (llmFactory: string, isEdit = false, modelData?: any, detailedData?: any) => {
const handleShowLlmAddingModal = (
llmFactory: string,
isEdit = false,
modelData?: any,
detailedData?: any,
) => {
setSelectedLlmFactory(llmFactory); setSelectedLlmFactory(llmFactory);
setEditMode(isEdit); setEditMode(isEdit);
if (isEdit && detailedData) { if (isEdit && detailedData) {
const initialVals = { const initialVals = {
llm_name: getRealModelName(detailedData.name), llm_name: getRealModelName(detailedData.name),

+ 25
- 8
web/src/pages/user-setting/setting-model/index.tsx ファイルの表示

import { useTheme } from '@/components/theme-provider'; import { useTheme } from '@/components/theme-provider';
import { LLMFactory } from '@/constants/llm'; import { LLMFactory } from '@/constants/llm';
import { useSetModalState, useTranslate } from '@/hooks/common-hooks'; 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 { getRealModelName } from '@/utils/llm-util';
import { CloseCircleOutlined, EditOutlined, SettingOutlined } from '@ant-design/icons';
import {
CloseCircleOutlined,
EditOutlined,
SettingOutlined,
} from '@ant-design/icons';
import { import {
Button, Button,
Card, Card,
<Tag color="#b8b8b8">{model.type}</Tag> <Tag color="#b8b8b8">{model.type}</Tag>
{isLocalLlmFactory(item.name) && ( {isLocalLlmFactory(item.name) && (
<Tooltip title={t('edit', { keyPrefix: 'common' })}> <Tooltip title={t('edit', { keyPrefix: 'common' })}>
<Button type={'text'} onClick={() => handleEditModel(model, item)}>
<Button
type={'text'}
onClick={() => handleEditModel(model, item)}
>
<EditOutlined style={{ color: '#1890ff' }} /> <EditOutlined style={{ color: '#1890ff' }} />
</Button> </Button>
</Tooltip> </Tooltip>
(model: any, factory: LlmItem) => { (model: any, factory: LlmItem) => {
if (factory) { if (factory) {
const detailedFactory = detailedLlmList[factory.name]; 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 = { const editData = {
llm_factory: factory.name, llm_factory: factory.name,
llm_name: model.name, llm_name: model.name,
model_type: model.type
model_type: model.type,
}; };
if (isLocalLlmFactory(factory.name)) { if (isLocalLlmFactory(factory.name)) {
showLlmAddingModal(factory.name, true, editData, detailedModel); showLlmAddingModal(factory.name, true, editData, detailedModel);
} else if (factory.name in ModalMap) { } else if (factory.name in ModalMap) {
grid={{ gutter: 16, column: 1 }} grid={{ gutter: 16, column: 1 }}
dataSource={llmList} dataSource={llmList}
renderItem={(item) => ( renderItem={(item) => (
<ModelCard item={item} clickApiKey={handleAddModel} handleEditModel={handleEditModel}></ModelCard>
<ModelCard
item={item}
clickApiKey={handleAddModel}
handleEditModel={handleEditModel}
></ModelCard>
)} )}
/> />
), ),

+ 9
- 8
web/src/pages/user-setting/setting-model/ollama-modal/index.tsx ファイルの表示

llmFactory, llmFactory,
editMode = false, editMode = false,
initialValues, initialValues,
}: IModalProps<IAddLlmRequestBody> & {
llmFactory: string;
}: IModalProps<IAddLlmRequestBody> & {
llmFactory: string;
editMode?: boolean; editMode?: boolean;
initialValues?: Partial<IAddLlmRequestBody>; initialValues?: Partial<IAddLlmRequestBody>;
}) => { }) => {
form.resetFields(); form.resetFields();
} }
}, [visible, editMode, initialValues, form]); }, [visible, editMode, initialValues, form]);
const url = const url =
llmFactoryToUrlMap[llmFactory as LlmFactory] || llmFactoryToUrlMap[llmFactory as LlmFactory] ||
'https://github.com/infiniflow/ragflow/blob/main/docs/guides/models/deploy_local_llm.mdx'; 'https://github.com/infiniflow/ragflow/blob/main/docs/guides/models/deploy_local_llm.mdx';
}; };
return ( return (
<Modal <Modal
title={editMode ? t('editLlmTitle', { name: llmFactory }) : t('addLlmTitle', { name: llmFactory })}
title={
editMode
? t('editLlmTitle', { name: llmFactory })
: t('addLlmTitle', { name: llmFactory })
}
open={visible} open={visible}
onOk={handleOk} onOk={handleOk}
onCancel={hideModal} onCancel={hideModal}
name="api_key" name="api_key"
rules={[{ required: false, message: t('apiKeyMessage') }]} rules={[{ required: false, message: t('apiKeyMessage') }]}
> >
<Input
placeholder={t('apiKeyMessage')}
onKeyDown={handleKeyDown}
/>
<Input placeholder={t('apiKeyMessage')} onKeyDown={handleKeyDown} />
</Form.Item> </Form.Item>
<Form.Item<FieldType> <Form.Item<FieldType>
label={t('maxTokens')} label={t('maxTokens')}

読み込み中…
キャンセル
保存