### What problem does this PR solve? Feat: Rendering recall test page #3221 ### Type of change - [x] New Feature (non-breaking change which adds functionality)tags/v0.19.0
| leftPanel?: ReactNode; | leftPanel?: ReactNode; | ||||
| } | } | ||||
| const FilterButton = React.forwardRef< | |||||
| export const FilterButton = React.forwardRef< | |||||
| HTMLButtonElement, | HTMLButtonElement, | ||||
| ButtonProps & { count?: number } | ButtonProps & { count?: number } | ||||
| >(({ count = 0, ...props }, ref) => { | >(({ count = 0, ...props }, ref) => { |
| import { Select as AntSelect, Form, message, Slider } from 'antd'; | import { Select as AntSelect, Form, message, Slider } from 'antd'; | ||||
| import { useCallback } from 'react'; | import { useCallback } from 'react'; | ||||
| import { useFormContext } from 'react-hook-form'; | import { useFormContext } from 'react-hook-form'; | ||||
| import { SingleFormSlider } from './ui/dual-range-slider'; | |||||
| import { z } from 'zod'; | |||||
| import { SliderInputFormField } from './slider-input-form-field'; | |||||
| import { | import { | ||||
| FormControl, | FormControl, | ||||
| FormField, | FormField, | ||||
| ); | ); | ||||
| }; | }; | ||||
| export const topKSchema = { | |||||
| top_k: z.number().optional(), | |||||
| }; | |||||
| export const initialTopKValue = { | |||||
| top_k: 1024, | |||||
| }; | |||||
| const Rerank = () => { | const Rerank = () => { | ||||
| const { t } = useTranslate('knowledgeDetails'); | const { t } = useTranslate('knowledgeDetails'); | ||||
| } | } | ||||
| export function RerankFormFields() { | export function RerankFormFields() { | ||||
| const { control, watch } = useFormContext(); | |||||
| const { watch } = useFormContext(); | |||||
| const { t } = useTranslate('knowledgeDetails'); | const { t } = useTranslate('knowledgeDetails'); | ||||
| const rerankId = watch(RerankId); | const rerankId = watch(RerankId); | ||||
| <> | <> | ||||
| <RerankFormField></RerankFormField> | <RerankFormField></RerankFormField> | ||||
| {rerankId && ( | {rerankId && ( | ||||
| <FormField | |||||
| control={control} | |||||
| <SliderInputFormField | |||||
| name={'top_k'} | name={'top_k'} | ||||
| render={({ field }) => ( | |||||
| <FormItem> | |||||
| <FormLabel tooltip={t('topKTip')}>{t('topK')}</FormLabel> | |||||
| <FormControl> | |||||
| <SingleFormSlider | |||||
| {...field} | |||||
| max={2048} | |||||
| min={1} | |||||
| ></SingleFormSlider> | |||||
| </FormControl> | |||||
| <FormMessage /> | |||||
| </FormItem> | |||||
| )} | |||||
| /> | |||||
| label={t('topK')} | |||||
| max={2048} | |||||
| min={1} | |||||
| tooltip={t('topKTip')} | |||||
| ></SliderInputFormField> | |||||
| )} | )} | ||||
| </> | </> | ||||
| ); | ); |
| import { useTranslate } from '@/hooks/common-hooks'; | import { useTranslate } from '@/hooks/common-hooks'; | ||||
| import { Form, Slider } from 'antd'; | import { Form, Slider } from 'antd'; | ||||
| import { useFormContext } from 'react-hook-form'; | |||||
| import { z } from 'zod'; | import { z } from 'zod'; | ||||
| import { SingleFormSlider } from '../ui/dual-range-slider'; | |||||
| import { | |||||
| FormControl, | |||||
| FormField, | |||||
| FormItem, | |||||
| FormLabel, | |||||
| FormMessage, | |||||
| } from '../ui/form'; | |||||
| import { SliderInputFormField } from '../slider-input-form-field'; | |||||
| type FieldType = { | type FieldType = { | ||||
| similarity_threshold?: number; | similarity_threshold?: number; | ||||
| vectorSimilarityWeightName = 'vector_similarity_weight', | vectorSimilarityWeightName = 'vector_similarity_weight', | ||||
| isTooltipShown, | isTooltipShown, | ||||
| }: SimilaritySliderFormFieldProps) { | }: SimilaritySliderFormFieldProps) { | ||||
| const form = useFormContext(); | |||||
| const { t } = useTranslate('knowledgeDetails'); | const { t } = useTranslate('knowledgeDetails'); | ||||
| return ( | return ( | ||||
| <> | <> | ||||
| <FormField | |||||
| control={form.control} | |||||
| <SliderInputFormField | |||||
| name={'similarity_threshold'} | name={'similarity_threshold'} | ||||
| render={({ field }) => ( | |||||
| <FormItem> | |||||
| <FormLabel tooltip={isTooltipShown && t('similarityThresholdTip')}> | |||||
| {t('similarityThreshold')} | |||||
| </FormLabel> | |||||
| <FormControl> | |||||
| <SingleFormSlider | |||||
| {...field} | |||||
| max={1} | |||||
| step={0.01} | |||||
| ></SingleFormSlider> | |||||
| </FormControl> | |||||
| <FormMessage /> | |||||
| </FormItem> | |||||
| )} | |||||
| /> | |||||
| <FormField | |||||
| control={form.control} | |||||
| label={t('similarityThreshold')} | |||||
| max={1} | |||||
| step={0.01} | |||||
| tooltip={isTooltipShown && t('similarityThresholdTip')} | |||||
| ></SliderInputFormField> | |||||
| <SliderInputFormField | |||||
| name={vectorSimilarityWeightName} | name={vectorSimilarityWeightName} | ||||
| render={({ field }) => ( | |||||
| <FormItem> | |||||
| <FormLabel | |||||
| tooltip={isTooltipShown && t('vectorSimilarityWeightTip')} | |||||
| > | |||||
| {t('vectorSimilarityWeight')} | |||||
| </FormLabel> | |||||
| <FormControl> | |||||
| <SingleFormSlider | |||||
| {...field} | |||||
| max={1} | |||||
| step={0.01} | |||||
| ></SingleFormSlider> | |||||
| </FormControl> | |||||
| <FormMessage /> | |||||
| </FormItem> | |||||
| )} | |||||
| /> | |||||
| label={t('vectorSimilarityWeight')} | |||||
| max={1} | |||||
| step={0.01} | |||||
| tooltip={isTooltipShown && t('vectorSimilarityWeightTip')} | |||||
| ></SliderInputFormField> | |||||
| </> | </> | ||||
| ); | ); | ||||
| } | } |
| import { cn } from '@/lib/utils'; | |||||
| import { ReactNode } from 'react'; | import { ReactNode } from 'react'; | ||||
| import { useFormContext } from 'react-hook-form'; | import { useFormContext } from 'react-hook-form'; | ||||
| import { SingleFormSlider } from './ui/dual-range-slider'; | import { SingleFormSlider } from './ui/dual-range-slider'; | ||||
| label: string; | label: string; | ||||
| tooltip?: ReactNode; | tooltip?: ReactNode; | ||||
| defaultValue?: number; | defaultValue?: number; | ||||
| className?: string; | |||||
| }; | }; | ||||
| export function SliderInputFormField({ | export function SliderInputFormField({ | ||||
| name, | name, | ||||
| tooltip, | tooltip, | ||||
| defaultValue, | defaultValue, | ||||
| className, | |||||
| }: SliderInputFormFieldProps) { | }: SliderInputFormFieldProps) { | ||||
| const form = useFormContext(); | const form = useFormContext(); | ||||
| render={({ field }) => ( | render={({ field }) => ( | ||||
| <FormItem> | <FormItem> | ||||
| <FormLabel tooltip={tooltip}>{label}</FormLabel> | <FormLabel tooltip={tooltip}>{label}</FormLabel> | ||||
| <div className="flex items-center gap-14 justify-between"> | |||||
| <div | |||||
| className={cn( | |||||
| 'flex items-center gap-14 justify-between', | |||||
| className, | |||||
| )} | |||||
| > | |||||
| <FormControl> | <FormControl> | ||||
| <SingleFormSlider | <SingleFormSlider | ||||
| {...field} | {...field} |
| import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; | import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; | ||||
| import { useDebounce } from 'ahooks'; | import { useDebounce } from 'ahooks'; | ||||
| import { message } from 'antd'; | import { message } from 'antd'; | ||||
| import { useCallback, useMemo, useState } from 'react'; | |||||
| import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; | |||||
| import { useParams } from 'umi'; | import { useParams } from 'umi'; | ||||
| import { | import { | ||||
| useGetPaginationWithRouter, | useGetPaginationWithRouter, | ||||
| useHandleSearchChange, | useHandleSearchChange, | ||||
| } from './logic-hooks'; | } from './logic-hooks'; | ||||
| import { useSetPaginationParams } from './route-hook'; | |||||
| export const enum KnowledgeApiAction { | export const enum KnowledgeApiAction { | ||||
| TestRetrieval = 'testRetrieval', | TestRetrieval = 'testRetrieval', | ||||
| export const useTestRetrieval = () => { | export const useTestRetrieval = () => { | ||||
| const knowledgeBaseId = useKnowledgeBaseId(); | const knowledgeBaseId = useKnowledgeBaseId(); | ||||
| const { page, size: pageSize } = useSetPaginationParams(); | |||||
| const [values, setValues] = useState<ITestRetrievalRequestBody>(); | const [values, setValues] = useState<ITestRetrievalRequestBody>(); | ||||
| const mountedRef = useRef(false); | |||||
| const { filterValue, handleFilterSubmit } = useHandleFilterSubmit(); | |||||
| const [page, setPage] = useState(1); | |||||
| const [pageSize, setPageSize] = useState(10); | |||||
| const onPaginationChange = useCallback((page: number, pageSize: number) => { | |||||
| setPage(page); | |||||
| setPageSize(pageSize); | |||||
| }, []); | |||||
| const queryParams = useMemo(() => { | const queryParams = useMemo(() => { | ||||
| return { | return { | ||||
| kb_id: values?.kb_id || knowledgeBaseId, | kb_id: values?.kb_id || knowledgeBaseId, | ||||
| page, | page, | ||||
| size: pageSize, | size: pageSize, | ||||
| doc_ids: filterValue.doc_ids, | |||||
| }; | }; | ||||
| }, [knowledgeBaseId, page, pageSize, values]); | |||||
| }, [filterValue, knowledgeBaseId, page, pageSize, values]); | |||||
| const { | const { | ||||
| data, | data, | ||||
| isFetching: loading, | isFetching: loading, | ||||
| refetch, | refetch, | ||||
| } = useQuery<INextTestingResult>({ | } = useQuery<INextTestingResult>({ | ||||
| queryKey: [KnowledgeApiAction.TestRetrieval, queryParams], | |||||
| queryKey: [KnowledgeApiAction.TestRetrieval, queryParams, page, pageSize], | |||||
| initialData: { | initialData: { | ||||
| chunks: [], | chunks: [], | ||||
| doc_aggs: [], | doc_aggs: [], | ||||
| gcTime: 0, | gcTime: 0, | ||||
| queryFn: async () => { | queryFn: async () => { | ||||
| const { data } = await kbService.retrieval_test(queryParams); | const { data } = await kbService.retrieval_test(queryParams); | ||||
| console.log('🚀 ~ queryFn: ~ data:', data); | |||||
| return data?.data ?? {}; | return data?.data ?? {}; | ||||
| }, | }, | ||||
| }); | }); | ||||
| return { data, loading, setValues, refetch }; | |||||
| useEffect(() => { | |||||
| if (mountedRef.current) { | |||||
| refetch(); | |||||
| } | |||||
| mountedRef.current = true; | |||||
| }, [page, pageSize, refetch, filterValue]); | |||||
| return { | |||||
| data, | |||||
| loading, | |||||
| setValues, | |||||
| refetch, | |||||
| onPaginationChange, | |||||
| page, | |||||
| pageSize, | |||||
| handleFilterSubmit, | |||||
| }; | |||||
| }; | }; | ||||
| export const useFetchNextKnowledgeListByPage = () => { | export const useFetchNextKnowledgeListByPage = () => { |
| const { setDocumentIds, data: fileThumbnails } = | const { setDocumentIds, data: fileThumbnails } = | ||||
| useFetchDocumentThumbnailsByIds(); | useFetchDocumentThumbnailsByIds(); | ||||
| const contentWithCursor = useMemo(() => { | const contentWithCursor = useMemo(() => { | ||||
| let text = DOMPurify.sanitize(content); | |||||
| // let text = DOMPurify.sanitize(content); | |||||
| let text = content; | |||||
| if (text === '') { | if (text === '') { | ||||
| text = t('chat.searching'); | text = t('chat.searching'); | ||||
| } | } |
| import { ReactNode } from 'react'; | |||||
| type TopTitleProps = { | |||||
| title: ReactNode; | |||||
| description: ReactNode; | |||||
| }; | |||||
| export function TopTitle({ title, description }: TopTitleProps) { | |||||
| return ( | |||||
| <div className="pb-5"> | |||||
| <div className="text-2xl font-semibold">{title}</div> | |||||
| <p className="text-text-sub-title pt-2">{description}</p> | |||||
| </div> | |||||
| ); | |||||
| } |
| import { zodResolver } from '@hookform/resolvers/zod'; | import { zodResolver } from '@hookform/resolvers/zod'; | ||||
| import { useForm, useWatch } from 'react-hook-form'; | import { useForm, useWatch } from 'react-hook-form'; | ||||
| import { z } from 'zod'; | import { z } from 'zod'; | ||||
| import { TopTitle } from '../dataset-title'; | |||||
| import CategoryPanel from './category-panel'; | import CategoryPanel from './category-panel'; | ||||
| import { ChunkMethodForm } from './chunk-method-form'; | import { ChunkMethodForm } from './chunk-method-form'; | ||||
| import { formSchema } from './form-schema'; | import { formSchema } from './form-schema'; | ||||
| return ( | return ( | ||||
| <section className="p-5 "> | <section className="p-5 "> | ||||
| <div className="pb-5"> | |||||
| <div className="text-2xl font-semibold">Configuration</div> | |||||
| <p className="text-text-sub-title pt-2"> | |||||
| Update your knowledge base configuration here, particularly the chunk | |||||
| method. | |||||
| </p> | |||||
| </div> | |||||
| <TopTitle | |||||
| title={'Configuration'} | |||||
| description={` Update your knowledge base configuration here, particularly the chunk | |||||
| method.`} | |||||
| ></TopTitle> | |||||
| <div className="flex gap-14"> | <div className="flex gap-14"> | ||||
| <Form {...form}> | <Form {...form}> | ||||
| <form | <form |
| import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; | |||||
| import { FormContainer } from '@/components/form-container'; | |||||
| import { FilterButton } from '@/components/list-filter-bar'; | |||||
| import { FilterPopover } from '@/components/list-filter-bar/filter-popover'; | |||||
| import { FilterCollection } from '@/components/list-filter-bar/interface'; | |||||
| import { Button } from '@/components/ui/button'; | |||||
| import { RAGFlowPagination } from '@/components/ui/ragflow-pagination'; | |||||
| import { useTranslate } from '@/hooks/common-hooks'; | import { useTranslate } from '@/hooks/common-hooks'; | ||||
| import { useTestRetrieval } from '@/hooks/use-knowledge-request'; | import { useTestRetrieval } from '@/hooks/use-knowledge-request'; | ||||
| import { ITestingChunk } from '@/interfaces/database/knowledge'; | import { ITestingChunk } from '@/interfaces/database/knowledge'; | ||||
| import { camelCase } from 'lodash'; | import { camelCase } from 'lodash'; | ||||
| import { Plus } from 'lucide-react'; | |||||
| import { useMemo } from 'react'; | |||||
| import { TopTitle } from '../dataset-title'; | |||||
| import TestingForm from './testing-form'; | import TestingForm from './testing-form'; | ||||
| const similarityList: Array<{ field: keyof ITestingChunk; label: string }> = [ | const similarityList: Array<{ field: keyof ITestingChunk; label: string }> = [ | ||||
| const ChunkTitle = ({ item }: { item: ITestingChunk }) => { | const ChunkTitle = ({ item }: { item: ITestingChunk }) => { | ||||
| const { t } = useTranslate('knowledgeDetails'); | const { t } = useTranslate('knowledgeDetails'); | ||||
| return ( | return ( | ||||
| <div className="flex gap-3 text-xs"> | |||||
| <div className="flex gap-3 text-xs text-text-sub-title-invert italic"> | |||||
| {similarityList.map((x) => ( | {similarityList.map((x) => ( | ||||
| <div key={x.field} className="space-x-1"> | <div key={x.field} className="space-x-1"> | ||||
| <span>{((item[x.field] as number) * 100).toFixed(2)}</span> | <span>{((item[x.field] as number) * 100).toFixed(2)}</span> | ||||
| }; | }; | ||||
| export default function RetrievalTesting() { | export default function RetrievalTesting() { | ||||
| const { loading, setValues, refetch, data } = useTestRetrieval(); | |||||
| const { | |||||
| loading, | |||||
| setValues, | |||||
| refetch, | |||||
| data, | |||||
| onPaginationChange, | |||||
| page, | |||||
| pageSize, | |||||
| handleFilterSubmit, | |||||
| } = useTestRetrieval(); | |||||
| const filters: FilterCollection[] = useMemo(() => { | |||||
| return [ | |||||
| { | |||||
| field: 'doc_ids', | |||||
| label: 'File', | |||||
| list: | |||||
| data.doc_aggs?.map((x) => ({ | |||||
| id: x.doc_id, | |||||
| label: x.doc_name, | |||||
| count: x.count, | |||||
| })) ?? [], | |||||
| }, | |||||
| ]; | |||||
| }, [data.doc_aggs]); | |||||
| return ( | return ( | ||||
| <section className="flex divide-x h-full"> | |||||
| <div className="p-4"> | |||||
| <TestingForm | |||||
| loading={loading} | |||||
| setValues={setValues} | |||||
| refetch={refetch} | |||||
| ></TestingForm> | |||||
| </div> | |||||
| <div className="p-4 flex-1 "> | |||||
| <h2 className="text-4xl font-bold mb-8 px-[10%]"> | |||||
| 15 Results from 3 files | |||||
| </h2> | |||||
| <section className="flex flex-col gap-4 overflow-auto h-[83vh] px-[10%]"> | |||||
| {data.chunks.map((x) => ( | |||||
| <Card | |||||
| key={x.chunk_id} | |||||
| className="bg-colors-background-neutral-weak border-colors-outline-neutral-strong" | |||||
| > | |||||
| <CardHeader> | |||||
| <CardTitle> | |||||
| <div className="flex gap-2 flex-wrap"> | |||||
| <ChunkTitle item={x}></ChunkTitle> | |||||
| </div> | |||||
| </CardTitle> | |||||
| </CardHeader> | |||||
| <CardContent> | |||||
| <p className="text-colors-text-neutral-strong"> | |||||
| {x.content_with_weight} | |||||
| </p> | |||||
| </CardContent> | |||||
| </Card> | |||||
| ))} | |||||
| </section> | |||||
| </div> | |||||
| </section> | |||||
| <div className="p-5"> | |||||
| <section className="flex justify-between items-center"> | |||||
| <TopTitle | |||||
| title={'Configuration'} | |||||
| description={` Update your knowledge base configuration here, particularly the chunk | |||||
| method.`} | |||||
| ></TopTitle> | |||||
| <Button>Save as Preset</Button> | |||||
| </section> | |||||
| <section className="flex divide-x h-full"> | |||||
| <div className="p-4 flex-1"> | |||||
| <div className="flex justify-between pb-2.5"> | |||||
| <span className="text-text-title font-semibold text-2xl"> | |||||
| Test setting | |||||
| </span> | |||||
| <Button variant={'outline'}> | |||||
| <Plus /> Add New Test | |||||
| </Button> | |||||
| </div> | |||||
| <TestingForm | |||||
| loading={loading} | |||||
| setValues={setValues} | |||||
| refetch={refetch} | |||||
| ></TestingForm> | |||||
| </div> | |||||
| <div className="p-4 flex-1"> | |||||
| <div className="flex justify-between pb-2.5"> | |||||
| <span className="text-text-title font-semibold text-2xl"> | |||||
| Test results | |||||
| </span> | |||||
| <FilterPopover filters={filters} onChange={handleFilterSubmit}> | |||||
| <FilterButton></FilterButton> | |||||
| </FilterPopover> | |||||
| </div> | |||||
| <section className="flex flex-col gap-5 overflow-auto h-[76vh] mb-5"> | |||||
| {data.chunks?.map((x) => ( | |||||
| <FormContainer key={x.chunk_id} className="px-5 py-2.5"> | |||||
| <ChunkTitle item={x}></ChunkTitle> | |||||
| <p className="!mt-2.5"> {x.content_with_weight}</p> | |||||
| </FormContainer> | |||||
| ))} | |||||
| </section> | |||||
| <RAGFlowPagination | |||||
| total={data.total} | |||||
| onChange={onPaginationChange} | |||||
| current={page} | |||||
| pageSize={pageSize} | |||||
| ></RAGFlowPagination> | |||||
| </div> | |||||
| </section> | |||||
| </div> | |||||
| ); | ); | ||||
| } | } |
| import { useForm, useWatch } from 'react-hook-form'; | import { useForm, useWatch } from 'react-hook-form'; | ||||
| import { z } from 'zod'; | import { z } from 'zod'; | ||||
| import { RerankFormFields } from '@/components/rerank'; | |||||
| import { FormContainer } from '@/components/form-container'; | |||||
| import { | |||||
| initialTopKValue, | |||||
| RerankFormFields, | |||||
| topKSchema, | |||||
| } from '@/components/rerank'; | |||||
| import { | import { | ||||
| initialKeywordsSimilarityWeightValue, | initialKeywordsSimilarityWeightValue, | ||||
| initialSimilarityThresholdValue, | initialSimilarityThresholdValue, | ||||
| SimilaritySliderFormField, | SimilaritySliderFormField, | ||||
| similarityThresholdSchema, | similarityThresholdSchema, | ||||
| } from '@/components/similarity-slider'; | } from '@/components/similarity-slider'; | ||||
| import { ButtonLoading } from '@/components/ui/button'; | |||||
| import { | import { | ||||
| Form, | Form, | ||||
| FormControl, | FormControl, | ||||
| FormLabel, | FormLabel, | ||||
| FormMessage, | FormMessage, | ||||
| } from '@/components/ui/form'; | } from '@/components/ui/form'; | ||||
| import { LoadingButton } from '@/components/ui/loading-button'; | |||||
| import { Textarea } from '@/components/ui/textarea'; | import { Textarea } from '@/components/ui/textarea'; | ||||
| import { UseKnowledgeGraphFormField } from '@/components/use-knowledge-graph-item'; | import { UseKnowledgeGraphFormField } from '@/components/use-knowledge-graph-item'; | ||||
| import { useTestRetrieval } from '@/hooks/use-knowledge-request'; | import { useTestRetrieval } from '@/hooks/use-knowledge-request'; | ||||
| }), | }), | ||||
| ...similarityThresholdSchema, | ...similarityThresholdSchema, | ||||
| ...keywordsSimilarityWeightSchema, | ...keywordsSimilarityWeightSchema, | ||||
| ...topKSchema, | |||||
| }); | }); | ||||
| const form = useForm<z.infer<typeof formSchema>>({ | const form = useForm<z.infer<typeof formSchema>>({ | ||||
| defaultValues: { | defaultValues: { | ||||
| ...initialSimilarityThresholdValue, | ...initialSimilarityThresholdValue, | ||||
| ...initialKeywordsSimilarityWeightValue, | ...initialKeywordsSimilarityWeightValue, | ||||
| ...initialTopKValue, | |||||
| }, | }, | ||||
| }); | }); | ||||
| return ( | return ( | ||||
| <Form {...form}> | <Form {...form}> | ||||
| <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8"> | <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8"> | ||||
| <SimilaritySliderFormField | |||||
| vectorSimilarityWeightName="keywords_similarity_weight" | |||||
| isTooltipShown | |||||
| ></SimilaritySliderFormField> | |||||
| <RerankFormFields></RerankFormFields> | |||||
| <UseKnowledgeGraphFormField name="use_kg"></UseKnowledgeGraphFormField> | |||||
| <FormContainer className="p-10"> | |||||
| <SimilaritySliderFormField | |||||
| vectorSimilarityWeightName="keywords_similarity_weight" | |||||
| isTooltipShown | |||||
| ></SimilaritySliderFormField> | |||||
| <RerankFormFields></RerankFormFields> | |||||
| <UseKnowledgeGraphFormField name="use_kg"></UseKnowledgeGraphFormField> | |||||
| </FormContainer> | |||||
| <FormField | <FormField | ||||
| control={form.control} | control={form.control} | ||||
| name="question" | name="question" | ||||
| </FormItem> | </FormItem> | ||||
| )} | )} | ||||
| /> | /> | ||||
| <LoadingButton | |||||
| variant={'tertiary'} | |||||
| size={'sm'} | |||||
| <ButtonLoading | |||||
| type="submit" | type="submit" | ||||
| className="w-full" | |||||
| disabled={!!!trim(question)} | disabled={!!!trim(question)} | ||||
| loading={loading} | loading={loading} | ||||
| > | > | ||||
| {t('knowledgeDetails.testingLabel')} | {t('knowledgeDetails.testingLabel')} | ||||
| </LoadingButton> | |||||
| </ButtonLoading> | |||||
| </form> | </form> | ||||
| </Form> | </Form> | ||||
| ); | ); |