mirror of
https://github.com/infiniflow/ragflow.git
synced 2025-12-08 20:42:30 +08:00
### What problem does this PR solve? feat(next-search): Added AI summary functionality #3221 - Added the LlmSettingFieldItems component for AI summary settings - Updated the SearchSetting component to integrate AI summary functionality - Added the updateSearch hook and related service methods - Modified the ISearchAppDetailProps interface to add the llm_setting field ### Type of change - [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
@ -208,6 +208,10 @@ export const MultiSelect = React.forwardRef<
|
|||||||
const [isPopoverOpen, setIsPopoverOpen] = React.useState(false);
|
const [isPopoverOpen, setIsPopoverOpen] = React.useState(false);
|
||||||
const [isAnimating, setIsAnimating] = React.useState(false);
|
const [isAnimating, setIsAnimating] = React.useState(false);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
setSelectedValues(defaultValue);
|
||||||
|
}, [defaultValue]);
|
||||||
|
|
||||||
const flatOptions = React.useMemo(() => {
|
const flatOptions = React.useMemo(() => {
|
||||||
return options.flatMap((option) =>
|
return options.flatMap((option) =>
|
||||||
'options' in option ? option.options : [option],
|
'options' in option ? option.options : [option],
|
||||||
|
|||||||
@ -471,7 +471,7 @@ This auto-tagging feature enhances retrieval by adding another layer of domain-s
|
|||||||
modelEnabledTools: 'Enabled tools',
|
modelEnabledTools: 'Enabled tools',
|
||||||
modelEnabledToolsTip:
|
modelEnabledToolsTip:
|
||||||
'Please select one or more tools for the chat model to use. It takes no effect for models not supporting tool call.',
|
'Please select one or more tools for the chat model to use. It takes no effect for models not supporting tool call.',
|
||||||
freedom: 'Freedom',
|
freedom: 'Creativity',
|
||||||
improvise: 'Improvise',
|
improvise: 'Improvise',
|
||||||
precise: 'Precise',
|
precise: 'Precise',
|
||||||
balance: 'Balance',
|
balance: 'Balance',
|
||||||
|
|||||||
182
web/src/pages/next-search/search-setting-aisummery-config.tsx
Normal file
182
web/src/pages/next-search/search-setting-aisummery-config.tsx
Normal file
@ -0,0 +1,182 @@
|
|||||||
|
import { SliderInputSwitchFormField } from '@/components/llm-setting-items/slider';
|
||||||
|
import { SelectWithSearch } from '@/components/originui/select-with-search';
|
||||||
|
import {
|
||||||
|
FormControl,
|
||||||
|
FormField,
|
||||||
|
FormItem,
|
||||||
|
FormLabel,
|
||||||
|
FormMessage,
|
||||||
|
} from '@/components/ui/form';
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from '@/components/ui/select';
|
||||||
|
import {
|
||||||
|
LlmModelType,
|
||||||
|
ModelVariableType,
|
||||||
|
settledModelVariableMap,
|
||||||
|
} from '@/constants/knowledge';
|
||||||
|
import { useTranslate } from '@/hooks/common-hooks';
|
||||||
|
import { useComposeLlmOptionsByModelTypes } from '@/hooks/llm-hooks';
|
||||||
|
import { camelCase } from 'lodash';
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
import { useFormContext } from 'react-hook-form';
|
||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
interface LlmSettingFieldItemsProps {
|
||||||
|
prefix?: string;
|
||||||
|
options?: any[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const LlmSettingSchema = {
|
||||||
|
llm_id: z.string(),
|
||||||
|
temperature: z.coerce.number(),
|
||||||
|
top_p: z.string(),
|
||||||
|
presence_penalty: z.coerce.number(),
|
||||||
|
frequency_penalty: z.coerce.number(),
|
||||||
|
temperatureEnabled: z.boolean(),
|
||||||
|
topPEnabled: z.boolean(),
|
||||||
|
presencePenaltyEnabled: z.boolean(),
|
||||||
|
frequencyPenaltyEnabled: z.boolean(),
|
||||||
|
maxTokensEnabled: z.boolean(),
|
||||||
|
};
|
||||||
|
|
||||||
|
export function LlmSettingFieldItems({
|
||||||
|
prefix,
|
||||||
|
options,
|
||||||
|
}: LlmSettingFieldItemsProps) {
|
||||||
|
const form = useFormContext();
|
||||||
|
const { t } = useTranslate('chat');
|
||||||
|
|
||||||
|
const modelOptions = useComposeLlmOptionsByModelTypes([
|
||||||
|
LlmModelType.Chat,
|
||||||
|
LlmModelType.Image2text,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const handleChange = useCallback(
|
||||||
|
(parameter: string) => {
|
||||||
|
// const currentValues = { ...form.getValues() };
|
||||||
|
const values =
|
||||||
|
settledModelVariableMap[
|
||||||
|
parameter as keyof typeof settledModelVariableMap
|
||||||
|
];
|
||||||
|
|
||||||
|
// const nextValues = { ...currentValues, ...values };
|
||||||
|
|
||||||
|
for (const key in values) {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(values, key)) {
|
||||||
|
const element = values[key];
|
||||||
|
|
||||||
|
form.setValue(`${prefix}.${key}`, element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[form, prefix],
|
||||||
|
);
|
||||||
|
|
||||||
|
const parameterOptions = Object.values(ModelVariableType).map((x) => ({
|
||||||
|
label: t(camelCase(x)),
|
||||||
|
value: x,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const getFieldWithPrefix = useCallback(
|
||||||
|
(name: string) => {
|
||||||
|
return prefix ? `${prefix}.${name}` : name;
|
||||||
|
},
|
||||||
|
[prefix],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-5">
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name={getFieldWithPrefix('llm_id')}
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>
|
||||||
|
<span className="text-destructive mr-1"> *</span>
|
||||||
|
{t('model')}
|
||||||
|
</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<SelectWithSearch
|
||||||
|
options={options || modelOptions}
|
||||||
|
triggerClassName="bg-bg-card"
|
||||||
|
{...field}
|
||||||
|
></SelectWithSearch>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name={getFieldWithPrefix('parameter')}
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="flex justify-between gap-4 items-center">
|
||||||
|
<FormLabel>{t('freedom')}</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<div className="w-28">
|
||||||
|
<Select
|
||||||
|
{...field}
|
||||||
|
onValueChange={(val) => {
|
||||||
|
handleChange(val);
|
||||||
|
field.onChange(val);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{parameterOptions.map((x) => (
|
||||||
|
<SelectItem value={x.value} key={x.value}>
|
||||||
|
{x.label}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<SliderInputSwitchFormField
|
||||||
|
name={getFieldWithPrefix('temperature')}
|
||||||
|
checkName="temperatureEnabled"
|
||||||
|
label="temperature"
|
||||||
|
max={1}
|
||||||
|
step={0.01}
|
||||||
|
></SliderInputSwitchFormField>
|
||||||
|
<SliderInputSwitchFormField
|
||||||
|
name={getFieldWithPrefix('top_p')}
|
||||||
|
checkName="topPEnabled"
|
||||||
|
label="topP"
|
||||||
|
max={1}
|
||||||
|
step={0.01}
|
||||||
|
></SliderInputSwitchFormField>
|
||||||
|
<SliderInputSwitchFormField
|
||||||
|
name={getFieldWithPrefix('presence_penalty')}
|
||||||
|
checkName="presencePenaltyEnabled"
|
||||||
|
label="presencePenalty"
|
||||||
|
max={1}
|
||||||
|
step={0.01}
|
||||||
|
></SliderInputSwitchFormField>
|
||||||
|
<SliderInputSwitchFormField
|
||||||
|
name={getFieldWithPrefix('frequency_penalty')}
|
||||||
|
checkName="frequencyPenaltyEnabled"
|
||||||
|
label="frequencyPenalty"
|
||||||
|
max={1}
|
||||||
|
step={0.01}
|
||||||
|
></SliderInputSwitchFormField>
|
||||||
|
{/* <SliderInputSwitchFormField
|
||||||
|
name={getFieldWithPrefix('max_tokens')}
|
||||||
|
checkName="maxTokensEnabled"
|
||||||
|
label="maxTokens"
|
||||||
|
max={128000}
|
||||||
|
></SliderInputSwitchFormField> */}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -1,5 +1,6 @@
|
|||||||
// src/pages/next-search/search-setting.tsx
|
// src/pages/next-search/search-setting.tsx
|
||||||
|
|
||||||
|
import { Input } from '@/components/originui/input';
|
||||||
import { RAGFlowAvatar } from '@/components/ragflow-avatar';
|
import { RAGFlowAvatar } from '@/components/ragflow-avatar';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { SingleFormSlider } from '@/components/ui/dual-range-slider';
|
import { SingleFormSlider } from '@/components/ui/dual-range-slider';
|
||||||
@ -11,29 +12,35 @@ import {
|
|||||||
FormLabel,
|
FormLabel,
|
||||||
FormMessage,
|
FormMessage,
|
||||||
} from '@/components/ui/form';
|
} from '@/components/ui/form';
|
||||||
import { Input } from '@/components/ui/input';
|
|
||||||
import { Label } from '@/components/ui/label';
|
import { Label } from '@/components/ui/label';
|
||||||
import {
|
import {
|
||||||
MultiSelect,
|
MultiSelect,
|
||||||
MultiSelectOptionType,
|
MultiSelectOptionType,
|
||||||
} from '@/components/ui/multi-select';
|
} from '@/components/ui/multi-select';
|
||||||
import {
|
import { RAGFlowSelect } from '@/components/ui/select';
|
||||||
Select,
|
|
||||||
SelectContent,
|
|
||||||
SelectItem,
|
|
||||||
SelectTrigger,
|
|
||||||
SelectValue,
|
|
||||||
} from '@/components/ui/select';
|
|
||||||
import { Switch } from '@/components/ui/switch';
|
import { Switch } from '@/components/ui/switch';
|
||||||
import { useFetchKnowledgeList } from '@/hooks/knowledge-hooks';
|
import { useFetchKnowledgeList } from '@/hooks/knowledge-hooks';
|
||||||
|
import {
|
||||||
|
useComposeLlmOptionsByModelTypes,
|
||||||
|
useSelectLlmOptionsByModelType,
|
||||||
|
} from '@/hooks/llm-hooks';
|
||||||
|
import { useFetchTenantInfo } from '@/hooks/user-setting-hooks';
|
||||||
import { IKnowledge } from '@/interfaces/database/knowledge';
|
import { IKnowledge } from '@/interfaces/database/knowledge';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { transformFile2Base64 } from '@/utils/file-util';
|
import { transformFile2Base64 } from '@/utils/file-util';
|
||||||
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import { t } from 'i18next';
|
import { t } from 'i18next';
|
||||||
import { PanelRightClose, Pencil, Upload } from 'lucide-react';
|
import { PanelRightClose, Pencil, Upload } from 'lucide-react';
|
||||||
import { useEffect, useState } from 'react';
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm, useWatch } from 'react-hook-form';
|
||||||
import { ISearchAppDetailProps } from '../next-searches/hooks';
|
import { z } from 'zod';
|
||||||
|
import { LlmModelType, ModelVariableType } from '../dataset/dataset/constant';
|
||||||
|
import {
|
||||||
|
ISearchAppDetailProps,
|
||||||
|
IUpdateSearchProps,
|
||||||
|
useUpdateSearch,
|
||||||
|
} from '../next-searches/hooks';
|
||||||
|
import { LlmSettingFieldItems } from './search-setting-aisummery-config';
|
||||||
|
|
||||||
interface SearchSettingProps {
|
interface SearchSettingProps {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
@ -41,7 +48,52 @@ interface SearchSettingProps {
|
|||||||
className?: string;
|
className?: string;
|
||||||
data: ISearchAppDetailProps;
|
data: ISearchAppDetailProps;
|
||||||
}
|
}
|
||||||
|
const SearchSettingFormSchema = z
|
||||||
|
.object({
|
||||||
|
search_id: z.string().optional(),
|
||||||
|
name: z.string().min(1, 'Name is required'),
|
||||||
|
avatar: z.string().optional(),
|
||||||
|
description: z.string().optional(),
|
||||||
|
search_config: z.object({
|
||||||
|
kb_ids: z.array(z.string()).min(1, 'At least one dataset is required'),
|
||||||
|
vector_similarity_weight: z.number().min(0).max(100),
|
||||||
|
web_search: z.boolean(),
|
||||||
|
similarity_threshold: z.number(),
|
||||||
|
use_kg: z.boolean(),
|
||||||
|
rerank_id: z.string(),
|
||||||
|
use_rerank: z.boolean(),
|
||||||
|
top_k: z.number(),
|
||||||
|
summary: z.boolean(),
|
||||||
|
llm_setting: z.object({
|
||||||
|
llm_id: z.string(),
|
||||||
|
parameter: z.string(),
|
||||||
|
temperature: z.number(),
|
||||||
|
top_p: z.union([z.string(), z.number()]),
|
||||||
|
frequency_penalty: z.number(),
|
||||||
|
presence_penalty: z.number(),
|
||||||
|
}),
|
||||||
|
related_search: z.boolean(),
|
||||||
|
query_mindmap: z.boolean(),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
.superRefine((data, ctx) => {
|
||||||
|
if (data.search_config.use_rerank && !data.search_config.rerank_id) {
|
||||||
|
ctx.addIssue({
|
||||||
|
path: ['search_config', 'rerank_id'],
|
||||||
|
message: 'Rerank model is required when rerank is enabled',
|
||||||
|
code: z.ZodIssueCode.custom,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.search_config.summary && !data.search_config.llm_setting?.llm_id) {
|
||||||
|
ctx.addIssue({
|
||||||
|
path: ['search_config', 'llm_setting', 'llm_id'],
|
||||||
|
message: 'Model is required when AI Summary is enabled',
|
||||||
|
code: z.ZodIssueCode.custom,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
type SearchSettingFormData = z.infer<typeof SearchSettingFormSchema>;
|
||||||
const SearchSetting: React.FC<SearchSettingProps> = ({
|
const SearchSetting: React.FC<SearchSettingProps> = ({
|
||||||
open = false,
|
open = false,
|
||||||
setOpen,
|
setOpen,
|
||||||
@ -49,51 +101,56 @@ const SearchSetting: React.FC<SearchSettingProps> = ({
|
|||||||
data,
|
data,
|
||||||
}) => {
|
}) => {
|
||||||
const [width0, setWidth0] = useState('w-[440px]');
|
const [width0, setWidth0] = useState('w-[440px]');
|
||||||
// "avatar": null,
|
const { search_config } = data || {};
|
||||||
// "created_by": "c3fb861af27a11efa69751e139332ced",
|
const { llm_setting } = search_config || {};
|
||||||
// "description": "My first search app",
|
const formMethods = useForm<SearchSettingFormData>({
|
||||||
// "id": "22e874584b4511f0aa1ac57b9ea5a68b",
|
resolver: zodResolver(SearchSettingFormSchema),
|
||||||
// "name": "updated search app",
|
|
||||||
// "search_config": {
|
|
||||||
// "cross_languages": [],
|
|
||||||
// "doc_ids": [],
|
|
||||||
// "highlight": false,
|
|
||||||
// "kb_ids": [],
|
|
||||||
// "keyword": false,
|
|
||||||
// "query_mindmap": false,
|
|
||||||
// "related_search": false,
|
|
||||||
// "rerank_id": "",
|
|
||||||
// "similarity_threshold": 0.5,
|
|
||||||
// "summary": false,
|
|
||||||
// "top_k": 1024,
|
|
||||||
// "use_kg": true,
|
|
||||||
// "vector_similarity_weight": 0.3,
|
|
||||||
// "web_search": false
|
|
||||||
// },
|
|
||||||
// "tenant_id": "c3fb861af27a11efa69751e139332ced",
|
|
||||||
// "update_time": 1750144129641
|
|
||||||
const formMethods = useForm({
|
|
||||||
defaultValues: {
|
|
||||||
id: '',
|
|
||||||
name: '',
|
|
||||||
avatar: '',
|
|
||||||
description: 'You are an intelligent assistant.',
|
|
||||||
datasets: '',
|
|
||||||
keywordSimilarityWeight: 20,
|
|
||||||
rerankModel: false,
|
|
||||||
aiSummary: false,
|
|
||||||
topK: true,
|
|
||||||
searchMethod: '',
|
|
||||||
model: '',
|
|
||||||
enableWebSearch: false,
|
|
||||||
enableRelatedSearch: true,
|
|
||||||
showQueryMindmap: true,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const [avatarFile, setAvatarFile] = useState<File | null>(null);
|
const [avatarFile, setAvatarFile] = useState<File | null>(null);
|
||||||
const [avatarBase64Str, setAvatarBase64Str] = useState(''); // Avatar Image base64
|
const [avatarBase64Str, setAvatarBase64Str] = useState(''); // Avatar Image base64
|
||||||
const [datasetList, setDatasetList] = useState<MultiSelectOptionType[]>([]);
|
const [datasetList, setDatasetList] = useState<MultiSelectOptionType[]>([]);
|
||||||
const [datasetSelectEmbdId, setDatasetSelectEmbdId] = useState('');
|
const [datasetSelectEmbdId, setDatasetSelectEmbdId] = useState('');
|
||||||
|
|
||||||
|
const resetForm = useCallback(() => {
|
||||||
|
formMethods.reset({
|
||||||
|
search_id: data?.id,
|
||||||
|
name: data?.name || '',
|
||||||
|
avatar: data?.avatar || '',
|
||||||
|
description: data?.description || 'You are an intelligent assistant.',
|
||||||
|
search_config: {
|
||||||
|
kb_ids: search_config?.kb_ids || [],
|
||||||
|
vector_similarity_weight: search_config?.vector_similarity_weight || 20,
|
||||||
|
web_search: search_config?.web_search || false,
|
||||||
|
doc_ids: [],
|
||||||
|
similarity_threshold: 0.0,
|
||||||
|
use_kg: false,
|
||||||
|
rerank_id: search_config?.rerank_id || '',
|
||||||
|
use_rerank: search_config?.rerank_id ? true : false,
|
||||||
|
top_k: search_config?.top_k || 1024,
|
||||||
|
summary: search_config?.summary || false,
|
||||||
|
chat_id: '',
|
||||||
|
llm_setting: {
|
||||||
|
llm_id: llm_setting?.llm_id || '',
|
||||||
|
parameter: llm_setting?.parameter || ModelVariableType.Improvise,
|
||||||
|
temperature: llm_setting?.temperature || 0.8,
|
||||||
|
top_p: llm_setting?.top_p || 0.9,
|
||||||
|
frequency_penalty: llm_setting?.frequency_penalty || 0.1,
|
||||||
|
presence_penalty: llm_setting?.presence_penalty || 0.1,
|
||||||
|
},
|
||||||
|
chat_settingcross_languages: [],
|
||||||
|
highlight: false,
|
||||||
|
keyword: false,
|
||||||
|
related_search: search_config?.related_search || false,
|
||||||
|
query_mindmap: search_config?.query_mindmap || false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}, [data, search_config, llm_setting, formMethods]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
resetForm();
|
||||||
|
}, [resetForm]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!open) {
|
if (!open) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@ -116,7 +173,8 @@ const SearchSetting: React.FC<SearchSettingProps> = ({
|
|||||||
})();
|
})();
|
||||||
}
|
}
|
||||||
}, [avatarFile]);
|
}, [avatarFile]);
|
||||||
const { list: datasetListOrigin } = useFetchKnowledgeList();
|
const { list: datasetListOrigin, loading: datasetLoading } =
|
||||||
|
useFetchKnowledgeList();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const datasetListMap = datasetListOrigin.map((item: IKnowledge) => {
|
const datasetListMap = datasetListOrigin.map((item: IKnowledge) => {
|
||||||
@ -143,8 +201,45 @@ const SearchSetting: React.FC<SearchSettingProps> = ({
|
|||||||
} else {
|
} else {
|
||||||
setDatasetSelectEmbdId('');
|
setDatasetSelectEmbdId('');
|
||||||
}
|
}
|
||||||
|
formMethods.setValue('search_config.kb_ids', value);
|
||||||
onChange?.(value);
|
onChange?.(value);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const allOptions = useSelectLlmOptionsByModelType();
|
||||||
|
const rerankModelOptions = useMemo(() => {
|
||||||
|
return allOptions[LlmModelType.Rerank];
|
||||||
|
}, [allOptions]);
|
||||||
|
|
||||||
|
const aiSummeryModelOptions = useComposeLlmOptionsByModelTypes([
|
||||||
|
LlmModelType.Chat,
|
||||||
|
LlmModelType.Image2text,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const rerankModelDisabled = useWatch({
|
||||||
|
control: formMethods.control,
|
||||||
|
name: 'search_config.use_rerank',
|
||||||
|
});
|
||||||
|
const aiSummaryDisabled = useWatch({
|
||||||
|
control: formMethods.control,
|
||||||
|
name: 'search_config.summary',
|
||||||
|
});
|
||||||
|
|
||||||
|
const { updateSearch, isLoading: isUpdating } = useUpdateSearch();
|
||||||
|
const { data: systemSetting } = useFetchTenantInfo();
|
||||||
|
const onSubmit = async (
|
||||||
|
formData: IUpdateSearchProps & { tenant_id: string },
|
||||||
|
) => {
|
||||||
|
try {
|
||||||
|
await updateSearch({
|
||||||
|
...formData,
|
||||||
|
tenant_id: systemSetting.tenant_id,
|
||||||
|
avatar: avatarBase64Str,
|
||||||
|
});
|
||||||
|
setOpen(false); // 关闭弹窗
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to update search:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
@ -156,7 +251,7 @@ const SearchSetting: React.FC<SearchSettingProps> = ({
|
|||||||
width0,
|
width0,
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
style={{ height: 'calc(100dvh - 170px)' }}
|
style={{ maxHeight: 'calc(100dvh - 170px)' }}
|
||||||
>
|
>
|
||||||
<div className="flex justify-between items-center text-base mb-8">
|
<div className="flex justify-between items-center text-base mb-8">
|
||||||
<div className="text-text-primary">Search Settings</div>
|
<div className="text-text-primary">Search Settings</div>
|
||||||
@ -168,19 +263,26 @@ const SearchSetting: React.FC<SearchSettingProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
style={{ height: 'calc(100dvh - 270px)' }}
|
style={{ maxHeight: 'calc(100dvh - 270px)' }}
|
||||||
className="overflow-y-auto scrollbar-auto p-1 text-text-secondary"
|
className="overflow-y-auto scrollbar-auto p-1 text-text-secondary"
|
||||||
>
|
>
|
||||||
<Form {...formMethods}>
|
<Form {...formMethods}>
|
||||||
<form
|
<form
|
||||||
onSubmit={formMethods.handleSubmit((data) => console.log(data))}
|
onSubmit={formMethods.handleSubmit(
|
||||||
|
(data) => {
|
||||||
|
console.log('Form submitted with data:', data);
|
||||||
|
onSubmit(data as IUpdateSearchProps);
|
||||||
|
},
|
||||||
|
(errors) => {
|
||||||
|
console.log('Validation errors:', errors);
|
||||||
|
},
|
||||||
|
)}
|
||||||
className="space-y-6"
|
className="space-y-6"
|
||||||
>
|
>
|
||||||
{/* Name */}
|
{/* Name */}
|
||||||
<FormField
|
<FormField
|
||||||
control={formMethods.control}
|
control={formMethods.control}
|
||||||
name="name"
|
name="name"
|
||||||
rules={{ required: 'Name is required' }}
|
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>
|
<FormLabel>
|
||||||
@ -225,13 +327,13 @@ const SearchSetting: React.FC<SearchSettingProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<Input
|
<input
|
||||||
placeholder=""
|
placeholder=""
|
||||||
// {...field}
|
// {...field}
|
||||||
type="file"
|
type="file"
|
||||||
title=""
|
title=""
|
||||||
accept="image/*"
|
accept="image/*"
|
||||||
className="absolute top-0 left-0 w-full h-full opacity-0 cursor-pointer"
|
className="absolute w-[64px] top-0 left-0 h-full opacity-0 cursor-pointer"
|
||||||
onChange={(ev) => {
|
onChange={(ev) => {
|
||||||
const file = ev.target?.files?.[0];
|
const file = ev.target?.files?.[0];
|
||||||
if (
|
if (
|
||||||
@ -257,11 +359,7 @@ const SearchSetting: React.FC<SearchSettingProps> = ({
|
|||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Description</FormLabel>
|
<FormLabel>Description</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input
|
<Input placeholder="Description" {...field} />
|
||||||
placeholder="Description"
|
|
||||||
{...field}
|
|
||||||
defaultValue="You are an intelligent assistant."
|
|
||||||
/>
|
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
@ -271,7 +369,7 @@ const SearchSetting: React.FC<SearchSettingProps> = ({
|
|||||||
{/* Datasets */}
|
{/* Datasets */}
|
||||||
<FormField
|
<FormField
|
||||||
control={formMethods.control}
|
control={formMethods.control}
|
||||||
name="datasets"
|
name="search_config.kb_ids"
|
||||||
rules={{ required: 'Datasets is required' }}
|
rules={{ required: 'Datasets is required' }}
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
@ -288,6 +386,7 @@ const SearchSetting: React.FC<SearchSettingProps> = ({
|
|||||||
placeholder={t('chat.knowledgeBasesMessage')}
|
placeholder={t('chat.knowledgeBasesMessage')}
|
||||||
variant="inverted"
|
variant="inverted"
|
||||||
maxCount={10}
|
maxCount={10}
|
||||||
|
defaultValue={field.value}
|
||||||
{...field}
|
{...field}
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
@ -299,10 +398,13 @@ const SearchSetting: React.FC<SearchSettingProps> = ({
|
|||||||
{/* Keyword Similarity Weight */}
|
{/* Keyword Similarity Weight */}
|
||||||
<FormField
|
<FormField
|
||||||
control={formMethods.control}
|
control={formMethods.control}
|
||||||
name="keywordSimilarityWeight"
|
name="search_config.vector_similarity_weight"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem className="flex flex-col">
|
<FormItem className="flex flex-col">
|
||||||
<FormLabel>Keyword Similarity Weight</FormLabel>
|
<FormLabel>
|
||||||
|
<span className="text-destructive mr-1"> *</span>Keyword
|
||||||
|
Similarity Weight
|
||||||
|
</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex justify-between items-center">
|
||||||
<SingleFormSlider
|
<SingleFormSlider
|
||||||
@ -324,7 +426,7 @@ const SearchSetting: React.FC<SearchSettingProps> = ({
|
|||||||
{/* Rerank Model */}
|
{/* Rerank Model */}
|
||||||
<FormField
|
<FormField
|
||||||
control={formMethods.control}
|
control={formMethods.control}
|
||||||
name="rerankModel"
|
name="search_config.use_rerank"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem className="flex flex-row items-start space-x-3 space-y-0">
|
<FormItem className="flex flex-row items-start space-x-3 space-y-0">
|
||||||
<FormControl>
|
<FormControl>
|
||||||
@ -337,11 +439,60 @@ const SearchSetting: React.FC<SearchSettingProps> = ({
|
|||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
{rerankModelDisabled && (
|
||||||
|
<>
|
||||||
|
<FormField
|
||||||
|
control={formMethods.control}
|
||||||
|
name={'search_config.rerank_id'}
|
||||||
|
// rules={{ required: 'Model is required' }}
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="flex flex-col">
|
||||||
|
<FormLabel>
|
||||||
|
<span className="text-destructive mr-1"> *</span>Model
|
||||||
|
</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<RAGFlowSelect
|
||||||
|
{...field}
|
||||||
|
options={rerankModelOptions}
|
||||||
|
// disabled={disabled}
|
||||||
|
placeholder={'model'}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={formMethods.control}
|
||||||
|
name="search_config.top_k"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="flex flex-col">
|
||||||
|
<FormLabel>Top K</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<div className="flex justify-between items-center">
|
||||||
|
<SingleFormSlider
|
||||||
|
max={100}
|
||||||
|
step={1}
|
||||||
|
value={field.value as number}
|
||||||
|
onChange={(values) => field.onChange(values)}
|
||||||
|
></SingleFormSlider>
|
||||||
|
<Label className="w-10 h-6 bg-bg-card flex justify-center items-center rounded-lg ml-20">
|
||||||
|
{field.value}
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* AI Summary */}
|
{/* AI Summary */}
|
||||||
<FormField
|
<FormField
|
||||||
control={formMethods.control}
|
control={formMethods.control}
|
||||||
name="aiSummary"
|
name="search_config.summary"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem className="flex flex-row items-start space-x-3 space-y-0">
|
<FormItem className="flex flex-row items-start space-x-3 space-y-0">
|
||||||
<FormControl>
|
<FormControl>
|
||||||
@ -351,93 +502,21 @@ const SearchSetting: React.FC<SearchSettingProps> = ({
|
|||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormLabel>AI Summary</FormLabel>
|
<FormLabel>AI Summary</FormLabel>
|
||||||
<Label className="text-sm text-muted-foreground">
|
|
||||||
默认不打开
|
|
||||||
</Label>
|
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Top K */}
|
{aiSummaryDisabled && (
|
||||||
<FormField
|
<LlmSettingFieldItems
|
||||||
control={formMethods.control}
|
prefix="search_config.llm_setting"
|
||||||
name="topK"
|
options={aiSummeryModelOptions}
|
||||||
render={({ field }) => (
|
></LlmSettingFieldItems>
|
||||||
<FormItem className="flex flex-row items-start space-x-3 space-y-0">
|
|
||||||
<FormControl>
|
|
||||||
<Switch
|
|
||||||
checked={field.value}
|
|
||||||
onCheckedChange={field.onChange}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
<FormLabel>Top K</FormLabel>
|
|
||||||
</FormItem>
|
|
||||||
)}
|
)}
|
||||||
/>
|
|
||||||
|
|
||||||
{/* Search Method */}
|
|
||||||
<FormField
|
|
||||||
control={formMethods.control}
|
|
||||||
name="searchMethod"
|
|
||||||
rules={{ required: 'Search Method is required' }}
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>
|
|
||||||
<span className="text-destructive mr-1"> *</span>Search
|
|
||||||
Method
|
|
||||||
</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Select
|
|
||||||
onValueChange={field.onChange}
|
|
||||||
defaultValue={field.value}
|
|
||||||
>
|
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValue placeholder="Select search method..." />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value="method1">Method 1</SelectItem>
|
|
||||||
<SelectItem value="method2">Method 2</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* Model */}
|
|
||||||
<FormField
|
|
||||||
control={formMethods.control}
|
|
||||||
name="model"
|
|
||||||
rules={{ required: 'Model is required' }}
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>
|
|
||||||
<span className="text-destructive mr-1"> *</span>Model
|
|
||||||
</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Select
|
|
||||||
onValueChange={field.onChange}
|
|
||||||
defaultValue={field.value}
|
|
||||||
>
|
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValue placeholder="Select model..." />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value="model1">Model 1</SelectItem>
|
|
||||||
<SelectItem value="model2">Model 2</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* Feature Controls */}
|
{/* Feature Controls */}
|
||||||
<FormField
|
<FormField
|
||||||
control={formMethods.control}
|
control={formMethods.control}
|
||||||
name="enableWebSearch"
|
name="search_config.web_search"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem className="flex flex-row items-start space-x-3 space-y-0">
|
<FormItem className="flex flex-row items-start space-x-3 space-y-0">
|
||||||
<FormControl>
|
<FormControl>
|
||||||
@ -453,7 +532,7 @@ const SearchSetting: React.FC<SearchSettingProps> = ({
|
|||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
control={formMethods.control}
|
control={formMethods.control}
|
||||||
name="enableRelatedSearch"
|
name="search_config.related_search"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem className="flex flex-row items-start space-x-3 space-y-0">
|
<FormItem className="flex flex-row items-start space-x-3 space-y-0">
|
||||||
<FormControl>
|
<FormControl>
|
||||||
@ -469,7 +548,7 @@ const SearchSetting: React.FC<SearchSettingProps> = ({
|
|||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
control={formMethods.control}
|
control={formMethods.control}
|
||||||
name="showQueryMindmap"
|
name="search_config.query_mindmap"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem className="flex flex-row items-start space-x-3 space-y-0">
|
<FormItem className="flex flex-row items-start space-x-3 space-y-0">
|
||||||
<FormControl>
|
<FormControl>
|
||||||
@ -483,7 +562,18 @@ const SearchSetting: React.FC<SearchSettingProps> = ({
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
{/* Submit Button */}
|
{/* Submit Button */}
|
||||||
<div className="flex justify-end">
|
<div className="flex justify-end"></div>
|
||||||
|
<div className="flex justify-end gap-2">
|
||||||
|
<Button
|
||||||
|
type="reset"
|
||||||
|
variant={'transparent'}
|
||||||
|
onClick={() => {
|
||||||
|
resetForm();
|
||||||
|
setOpen(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
<Button type="submit">Confirm</Button>
|
<Button type="submit">Confirm</Button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@ -158,6 +158,15 @@ export const useDeleteSearch = () => {
|
|||||||
return { data, isLoading, isError, deleteSearch };
|
return { data, isLoading, isError, deleteSearch };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
interface IllmSettingProps {
|
||||||
|
llm_id: string;
|
||||||
|
parameter: string;
|
||||||
|
temperature: number;
|
||||||
|
top_p: number;
|
||||||
|
frequency_penalty: number;
|
||||||
|
presence_penalty: number;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ISearchAppDetailProps {
|
export interface ISearchAppDetailProps {
|
||||||
avatar: any;
|
avatar: any;
|
||||||
created_by: string;
|
created_by: string;
|
||||||
@ -175,10 +184,12 @@ export interface ISearchAppDetailProps {
|
|||||||
rerank_id: string;
|
rerank_id: string;
|
||||||
similarity_threshold: number;
|
similarity_threshold: number;
|
||||||
summary: boolean;
|
summary: boolean;
|
||||||
|
llm_setting: IllmSettingProps;
|
||||||
top_k: number;
|
top_k: number;
|
||||||
use_kg: boolean;
|
use_kg: boolean;
|
||||||
vector_similarity_weight: number;
|
vector_similarity_weight: number;
|
||||||
web_search: boolean;
|
web_search: boolean;
|
||||||
|
chat_settingcross_languages: string[];
|
||||||
};
|
};
|
||||||
tenant_id: string;
|
tenant_id: string;
|
||||||
update_time: number;
|
update_time: number;
|
||||||
@ -207,3 +218,43 @@ export const useFetchSearchDetail = () => {
|
|||||||
|
|
||||||
return { data: data?.data, isLoading, isError };
|
return { data: data?.data, isLoading, isError };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type IUpdateSearchProps = Omit<ISearchAppDetailProps, 'id'> & {
|
||||||
|
search_id: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useUpdateSearch = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const {
|
||||||
|
data,
|
||||||
|
isLoading,
|
||||||
|
isError,
|
||||||
|
mutateAsync: updateSearchMutation,
|
||||||
|
} = useMutation<any, Error, IUpdateSearchProps>({
|
||||||
|
mutationKey: ['updateSearch'],
|
||||||
|
mutationFn: async (formData) => {
|
||||||
|
const { data: response } =
|
||||||
|
await searchService.updateSearchSetting(formData);
|
||||||
|
if (response.code !== 0) {
|
||||||
|
throw new Error(response.message || 'Failed to update search');
|
||||||
|
}
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
onSuccess: () => {
|
||||||
|
message.success(t('message.updated'));
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
message.error(t('message.error', { error: error.message }));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const updateSearch = useCallback(
|
||||||
|
(formData: IUpdateSearchProps) => {
|
||||||
|
return updateSearchMutation(formData);
|
||||||
|
},
|
||||||
|
[updateSearchMutation],
|
||||||
|
);
|
||||||
|
|
||||||
|
return { data, isLoading, isError, updateSearch };
|
||||||
|
};
|
||||||
|
|||||||
@ -2,7 +2,13 @@ import api from '@/utils/api';
|
|||||||
import registerServer from '@/utils/register-server';
|
import registerServer from '@/utils/register-server';
|
||||||
import request from '@/utils/request';
|
import request from '@/utils/request';
|
||||||
|
|
||||||
const { createSearch, getSearchList, deleteSearch, getSearchDetail } = api;
|
const {
|
||||||
|
createSearch,
|
||||||
|
getSearchList,
|
||||||
|
deleteSearch,
|
||||||
|
getSearchDetail,
|
||||||
|
updateSearchSetting,
|
||||||
|
} = api;
|
||||||
const methods = {
|
const methods = {
|
||||||
createSearch: {
|
createSearch: {
|
||||||
url: createSearch,
|
url: createSearch,
|
||||||
@ -17,6 +23,10 @@ const methods = {
|
|||||||
url: getSearchDetail,
|
url: getSearchDetail,
|
||||||
method: 'get',
|
method: 'get',
|
||||||
},
|
},
|
||||||
|
updateSearchSetting: {
|
||||||
|
url: updateSearchSetting,
|
||||||
|
method: 'post',
|
||||||
|
},
|
||||||
} as const;
|
} as const;
|
||||||
const searchService = registerServer<keyof typeof methods>(methods, request);
|
const searchService = registerServer<keyof typeof methods>(methods, request);
|
||||||
|
|
||||||
|
|||||||
@ -181,4 +181,5 @@ export default {
|
|||||||
getSearchList: `${api_host}/search/list`,
|
getSearchList: `${api_host}/search/list`,
|
||||||
deleteSearch: `${api_host}/search/rm`,
|
deleteSearch: `${api_host}/search/rm`,
|
||||||
getSearchDetail: `${api_host}/search/detail`,
|
getSearchDetail: `${api_host}/search/detail`,
|
||||||
|
updateSearchSetting: `${api_host}/search/update`,
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user