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)
This commit is contained in:
chanx
2025-07-24 09:30:05 +08:00
committed by GitHub
parent ad177951e9
commit 03e39ca9be
17 changed files with 222 additions and 103 deletions

View File

@ -12,13 +12,6 @@ import {
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) {
@ -29,7 +22,6 @@ export function EntityTypesFormField({
<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 ">

View File

@ -3,9 +3,8 @@ import { useTranslate } from '@/hooks/common-hooks';
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';
@ -81,12 +80,7 @@ export function KnowledgeBaseFormField() {
label: x.name, label: x.name,
value: x.id, value: x.id,
icon: () => ( icon: () => (
<Avatar className="size-4 mr-2"> <RAGFlowAvatar className="size-4 mr-2" avatar={x.avatar} name={x.name} />
<AvatarImage src={x.avatar} />
<AvatarFallback>
<Book />
</AvatarFallback>
</Avatar>
), ),
})); }));

View File

@ -3,7 +3,7 @@ import { DocumentParserType } from '@/constants/knowledge';
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';
@ -46,6 +46,10 @@ export const showTagItems = (parserId: DocumentParserType) => {
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.
@ -53,6 +57,15 @@ const RaptorFormFields = () => {
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));
@ -114,11 +127,7 @@ const RaptorFormFields = () => {
</FormLabel> </FormLabel>
<div className="w-3/4"> <div className="w-3/4">
<FormControl> <FormControl>
<Textarea <Textarea {...field} rows={8} />
{...field}
rows={8}
defaultValue={t('promptText')}
/>
</FormControl> </FormControl>
</div> </div>
</div> </div>
@ -134,7 +143,6 @@ const RaptorFormFields = () => {
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}
@ -143,7 +151,6 @@ const RaptorFormFields = () => {
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}
@ -153,7 +160,6 @@ const RaptorFormFields = () => {
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}

View File

@ -143,7 +143,12 @@ export const useComposeLlmOptionsByModelTypes = (
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];
@ -211,7 +216,6 @@ export const useFetchMyLlmListDetailed = (): ResponseGetType<
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 } =
@ -262,7 +266,7 @@ export const useSaveApiKey = () => {
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;
@ -314,7 +318,7 @@ export const useAddLlm = () => {
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'));
} }
@ -338,7 +342,7 @@ export const useDeleteLlm = () => {
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'));
} }
@ -362,7 +366,7 @@ export const useDeleteFactory = () => {
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'));
} }

View File

@ -1296,6 +1296,7 @@ This delimiter is used to split the input text into several text pieces echo of
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',

View File

@ -1248,6 +1248,7 @@ General实体和关系提取提示来自 GitHub - microsoft/graphrag基于
agent: 'Agent', agent: 'Agent',
agentDescription: '构建具备推理、工具调用和多智能体协同的智能体组件。', agentDescription: '构建具备推理、工具调用和多智能体协同的智能体组件。',
maxRecords: '最大记录数', maxRecords: '最大记录数',
createAgent: 'Create Agent',
stringTransform: '文本处理', stringTransform: '文本处理',
userFillUp: '等待输入', userFillUp: '等待输入',
codeExec: '代码', codeExec: '代码',

View File

@ -1,8 +1,8 @@
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';
@ -68,10 +68,11 @@ function InnerRetrievalNode({
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 <RAGFlowAvatar
size={26} className="size-6 rounded-lg"
icon={<UserOutlined />} avatar={knowledge.avatar}
src={knowledge.avatar} name={knowledge.name || 'CN'}
isPerson={true}
/> />
<Flex className={styles.knowledgeNodeName} flex={1}> <Flex className={styles.knowledgeNodeName} flex={1}>
{knowledge.name} {knowledge.name}

View File

@ -11,7 +11,7 @@ import { useSetModalState } from '@/hooks/common-hooks';
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';
@ -21,7 +21,11 @@ export default function AgentTemplates() {
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,
@ -62,7 +66,14 @@ export default function AgentTemplates() {
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>

View File

@ -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 { 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"> {isCreate && (
{data.avatar ? ( <div
<div className="flex flex-col justify-center items-center gap-4 mb-4 absolute top-0 right-0 left-0 bottom-0 cursor-pointer "
className="w-[70px] h-[70px] rounded-xl bg-cover" onClick={handleClick}
style={{ backgroundImage: `url(${data.avatar})` }} >
/> <Plus size={50} fontWeight={700} />
) : ( <div>{t('flow.createAgent')}</div>
<Avatar className="w-[70px] h-[70px]"> </div>
<AvatarImage src="https://github.com/shadcn.png" /> )}
<AvatarFallback>CN</AvatarFallback> {!isCreate && (
</Avatar> <>
)} <div className="flex justify-start items-center gap-4 mb-4">
</div> <RAGFlowAvatar
<h3 className="text-xl font-bold mb-2">{data.title}</h3> className="w-7 h-7"
<p className="break-words">{data.description}</p> avatar={
<Button data.avatar ? data.avatar : 'https://github.com/shadcn.png'
variant="tertiary" }
className="absolute bottom-4 right-4 left-4 hidden justify-end group-hover:block text-center" name={data?.title || 'CN'}
onClick={handleClick} ></RAGFlowAvatar>
> <div className="text-[18px] font-bold ">{data.title}</div>
{t('flow.useTemplate')} </div>
</Button> <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>
); );

View File

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

View File

@ -6,6 +6,11 @@ import {
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';
@ -13,12 +18,11 @@ import { CircleX, Play, RefreshCw } from 'lucide-react';
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 />,
@ -94,10 +98,17 @@ export function ParsingStatusCell({
</Button> </Button>
</ConfirmDeleteDialog> </ConfirmDeleteDialog>
{isParserRunning(run) ? ( {isParserRunning(run) ? (
<div className="flex items-center gap-1"> <HoverCard>
<Progress value={p} className="h-1 flex-1 min-w-10" /> <HoverCardTrigger asChild>
{p}% <div className="flex items-center gap-1">
</div> <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>
)} )}

View File

@ -71,9 +71,13 @@ export const useFetchKnowledgeConfigurationOnMount = (
knowledgeDetails.avatar, knowledgeDetails.avatar,
); );
console.log('🚀 ~ useEffect ~ fileList:', fileList); console.log('🚀 ~ useEffect ~ fileList:', fileList, knowledgeDetails);
form.reset({ const parser_config = {
...pick(knowledgeDetails, [ ...form.formState?.defaultValues?.parser_config,
...knowledgeDetails.parser_config,
};
const formValues = {
...pick({ ...knowledgeDetails, parser_config: parser_config }, [
'description', 'description',
'name', 'name',
'permission', 'permission',
@ -83,6 +87,9 @@ export const useFetchKnowledgeConfigurationOnMount = (
'parser_config', 'parser_config',
'pagerank', 'pagerank',
]), ]),
};
form.reset({
...formValues,
avatar: fileList, avatar: fileList,
}); });
}, [form, knowledgeDetails]); }, [form, knowledgeDetails]);

View File

@ -54,10 +54,6 @@ export default function DatasetSettings() {
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,

View File

@ -1,5 +1,5 @@
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';
@ -32,10 +32,11 @@ export function DatasetCard({
<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"> <RAGFlowAvatar
<AvatarImage src={dataset.avatar} /> className="size-6 rounded-lg"
<AvatarFallback className="rounded-lg ">CN</AvatarFallback> avatar={dataset.avatar}
</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}

View File

@ -111,7 +111,9 @@ export const useFetchSystemModelSettingOnMount = () => {
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 {
@ -126,7 +128,7 @@ export const useSubmitOllama = () => {
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();
@ -137,10 +139,15 @@ export const useSubmitOllama = () => {
[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),

View File

@ -3,9 +3,17 @@ import { LlmIcon } from '@/components/svg-icon';
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,
@ -137,7 +145,10 @@ const ModelCard = ({ item, clickApiKey, handleEditModel }: IModelCardProps) => {
<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>
@ -304,14 +315,16 @@ const UserSettingModel = () => {
(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) {
@ -333,7 +346,11 @@ const UserSettingModel = () => {
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>
)} )}
/> />
), ),

View File

@ -48,8 +48,8 @@ const OllamaModal = ({
llmFactory, llmFactory,
editMode = false, editMode = false,
initialValues, initialValues,
}: IModalProps<IAddLlmRequestBody> & { }: IModalProps<IAddLlmRequestBody> & {
llmFactory: string; llmFactory: string;
editMode?: boolean; editMode?: boolean;
initialValues?: Partial<IAddLlmRequestBody>; initialValues?: Partial<IAddLlmRequestBody>;
}) => { }) => {
@ -96,7 +96,7 @@ const OllamaModal = ({
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';
@ -134,7 +134,11 @@ const OllamaModal = ({
}; };
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}
@ -196,10 +200,7 @@ const OllamaModal = ({
name="api_key" name="api_key"
rules={[{ required: false, message: t('apiKeyMessage') }]} rules={[{ required: false, message: t('apiKeyMessage') }]}
> >
<Input <Input placeholder={t('apiKeyMessage')} onKeyDown={handleKeyDown} />
placeholder={t('apiKeyMessage')}
onKeyDown={handleKeyDown}
/>
</Form.Item> </Form.Item>
<Form.Item<FieldType> <Form.Item<FieldType>
label={t('maxTokens')} label={t('maxTokens')}