feat(next-search): Added AI summary functionality #3221 (#9402)

### 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:
chanx
2025-08-12 12:27:00 +08:00
committed by GitHub
parent da68f541b6
commit 735570486f
7 changed files with 490 additions and 152 deletions

View File

@ -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],

View File

@ -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',

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

View File

@ -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>

View File

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

View File

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

View File

@ -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`,
}; };