Bläddra i källkod

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)
tags/v0.20.2
chanx 2 månader sedan
förälder
incheckning
735570486f
Inget konto är kopplat till bidragsgivarens mejladress

+ 4
- 0
web/src/components/ui/multi-select.tsx Visa fil

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

+ 1
- 1
web/src/locales/en.ts Visa fil

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
- 0
web/src/pages/next-search/search-setting-aisummery-config.tsx Visa fil

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

+ 240
- 150
web/src/pages/next-search/search-setting.tsx Visa fil

// 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';
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 {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import { RAGFlowSelect } 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 { useForm } from 'react-hook-form';
import { ISearchAppDetailProps } from '../next-searches/hooks';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useForm, useWatch } from 'react-hook-form';
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;
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,
data, data,
}) => { }) => {
const [width0, setWidth0] = useState('w-[440px]'); const [width0, setWidth0] = useState('w-[440px]');
// "avatar": null,
// "created_by": "c3fb861af27a11efa69751e139332ced",
// "description": "My first search app",
// "id": "22e874584b4511f0aa1ac57b9ea5a68b",
// "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 { search_config } = data || {};
const { llm_setting } = search_config || {};
const formMethods = useForm<SearchSettingFormData>({
resolver: zodResolver(SearchSettingFormSchema),
}); });

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(() => {
})(); })();
} }
}, [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) => {
} 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(
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>
</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>
</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 (
<FormItem> <FormItem>
<FormLabel>Description</FormLabel> <FormLabel>Description</FormLabel>
<FormControl> <FormControl>
<Input
placeholder="Description"
{...field}
defaultValue="You are an intelligent assistant."
/>
<Input placeholder="Description" {...field} />
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
{/* 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>
placeholder={t('chat.knowledgeBasesMessage')} placeholder={t('chat.knowledgeBasesMessage')}
variant="inverted" variant="inverted"
maxCount={10} maxCount={10}
defaultValue={field.value}
{...field} {...field}
/> />
</FormControl> </FormControl>
{/* 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
{/* 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>
</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>
/> />
</FormControl> </FormControl>
<FormLabel>AI Summary</FormLabel> <FormLabel>AI Summary</FormLabel>
<Label className="text-sm text-muted-foreground">
默认不打开
</Label>
</FormItem> </FormItem>
)} )}
/> />


{/* Top K */}
<FormField
control={formMethods.control}
name="topK"
render={({ field }) => (
<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>
)}
/>
{aiSummaryDisabled && (
<LlmSettingFieldItems
prefix="search_config.llm_setting"
options={aiSummeryModelOptions}
></LlmSettingFieldItems>
)}


{/* 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>


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


<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>
)} )}
/> />
{/* 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>

+ 51
- 0
web/src/pages/next-searches/hooks.ts Visa fil

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


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

+ 11
- 1
web/src/services/search-service.ts Visa fil

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



+ 1
- 0
web/src/utils/api.ts Visa fil

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

Laddar…
Avbryt
Spara