Feat: Use data pipeline to visualize the parsing configuration of the knowledge base (#10423)

### What problem does this PR solve?

#9869

### Type of change

- [x] New Feature (non-breaking change which adds functionality)

---------

Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: jinhai <haijin.chn@gmail.com>
Signed-off-by: Jin Hai <haijin.chn@gmail.com>
Co-authored-by: chanx <1243304602@qq.com>
Co-authored-by: balibabu <cike8899@users.noreply.github.com>
Co-authored-by: Lynn <lynn_inf@hotmail.com>
Co-authored-by: 纷繁下的无奈 <zhileihuang@126.com>
Co-authored-by: huangzl <huangzl@shinemo.com>
Co-authored-by: writinwaters <93570324+writinwaters@users.noreply.github.com>
Co-authored-by: Wilmer <33392318@qq.com>
Co-authored-by: Adrian Weidig <adrianweidig@gmx.net>
Co-authored-by: Zhichang Yu <yuzhichang@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Yongteng Lei <yongtengrey@outlook.com>
Co-authored-by: Liu An <asiro@qq.com>
Co-authored-by: buua436 <66937541+buua436@users.noreply.github.com>
Co-authored-by: BadwomanCraZY <511528396@qq.com>
Co-authored-by: cucusenok <31804608+cucusenok@users.noreply.github.com>
Co-authored-by: Russell Valentine <russ@coldstonelabs.org>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Billy Bao <newyorkupperbay@gmail.com>
Co-authored-by: Zhedong Cen <cenzhedong2@126.com>
Co-authored-by: TensorNull <129579691+TensorNull@users.noreply.github.com>
Co-authored-by: TensorNull <tensor.null@gmail.com>
Co-authored-by: TeslaZY <TeslaZY@outlook.com>
Co-authored-by: Ajay <160579663+aybanda@users.noreply.github.com>
Co-authored-by: AB <aj@Ajays-MacBook-Air.local>
Co-authored-by: 天海蒼灆 <huangaoqin@tecpie.com>
Co-authored-by: He Wang <wanghechn@qq.com>
Co-authored-by: Atsushi Hatakeyama <atu729@icloud.com>
Co-authored-by: Jin Hai <haijin.chn@gmail.com>
Co-authored-by: Mohamed Mathari <155896313+melmathari@users.noreply.github.com>
Co-authored-by: Mohamed Mathari <nocodeventure@Mac-mini-van-Mohamed.fritz.box>
Co-authored-by: Stephen Hu <stephenhu@seismic.com>
Co-authored-by: Shaun Zhang <zhangwfjh@users.noreply.github.com>
Co-authored-by: zhimeng123 <60221886+zhimeng123@users.noreply.github.com>
Co-authored-by: mxc <mxc@example.com>
Co-authored-by: Dominik Novotný <50611433+SgtMarmite@users.noreply.github.com>
Co-authored-by: EVGENY M <168018528+rjohny55@users.noreply.github.com>
Co-authored-by: mcoder6425 <mcoder64@gmail.com>
Co-authored-by: lemsn <lemsn@msn.com>
Co-authored-by: lemsn <lemsn@126.com>
Co-authored-by: Adrian Gora <47756404+adagora@users.noreply.github.com>
Co-authored-by: Womsxd <45663319+Womsxd@users.noreply.github.com>
Co-authored-by: FatMii <39074672+FatMii@users.noreply.github.com>
This commit is contained in:
Kevin Hu
2025-10-09 12:36:19 +08:00
committed by GitHub
parent ef0aecea3b
commit cbf04ee470
490 changed files with 10630 additions and 30688 deletions

View File

@ -18,8 +18,11 @@ import { useFetchKnowledgeBaseConfiguration } from '@/hooks/use-knowledge-reques
import { IModalProps } from '@/interfaces/common';
import { IParserConfig } from '@/interfaces/database/document';
import { IChangeParserConfigRequestBody } from '@/interfaces/request/document';
import {
ChunkMethodItem,
ParseTypeItem,
} from '@/pages/dataset/dataset-setting/configuration/common-item';
import { zodResolver } from '@hookform/resolvers/zod';
import get from 'lodash/get';
import omit from 'lodash/omit';
import {} from 'module';
import { useEffect, useMemo } from 'react';
@ -30,24 +33,17 @@ import {
AutoKeywordsFormField,
AutoQuestionsFormField,
} from '../auto-keywords-form-field';
import { DataFlowSelect } from '../data-pipeline-select';
import { DelimiterFormField } from '../delimiter-form-field';
import { EntityTypesFormField } from '../entity-types-form-field';
import { ExcelToHtmlFormField } from '../excel-to-html-form-field';
import { FormContainer } from '../form-container';
import { LayoutRecognizeFormField } from '../layout-recognize-form-field';
import { MaxTokenNumberFormField } from '../max-token-number-from-field';
import {
UseGraphRagFormField,
showGraphRagItems,
} from '../parse-configuration/graph-rag-form-fields';
import RaptorFormFields, {
showRaptorParseConfiguration,
} from '../parse-configuration/raptor-form-fields';
import { ButtonLoading } from '../ui/button';
import { Input } from '../ui/input';
import { RAGFlowSelect } from '../ui/select';
import { DynamicPageRange } from './dynamic-page-range';
import { useFetchParserListOnMount, useShowAutoKeywords } from './hooks';
import { useShowAutoKeywords } from './hooks';
import {
useDefaultParserValues,
useFillDefaultValueOnMount,
@ -62,6 +58,7 @@ interface IProps
}> {
loading: boolean;
parserId: string;
pipelineId?: string;
parserConfig: IParserConfig;
documentExtension: string;
documentId: string;
@ -80,6 +77,7 @@ export function ChunkMethodDialog({
hideModal,
onOk,
parserId,
pipelineId,
documentExtension,
visible,
parserConfig,
@ -87,8 +85,6 @@ export function ChunkMethodDialog({
}: IProps) {
const { t } = useTranslation();
const { parserList } = useFetchParserListOnMount(documentExtension);
const { data: knowledgeDetails } = useFetchKnowledgeBaseConfiguration();
const useGraphRag = useMemo(() => {
@ -99,46 +95,59 @@ export function ChunkMethodDialog({
const fillDefaultParserValue = useFillDefaultValueOnMount();
const FormSchema = z.object({
parser_id: z
.string()
.min(1, {
message: t('common.pleaseSelect'),
})
.trim(),
parser_config: z.object({
task_page_size: z.coerce.number().optional(),
layout_recognize: z.string().optional(),
chunk_token_num: z.coerce.number().optional(),
delimiter: z.string().optional(),
auto_keywords: z.coerce.number().optional(),
auto_questions: z.coerce.number().optional(),
html4excel: z.boolean().optional(),
raptor: z
.object({
use_raptor: z.boolean().optional(),
prompt: z.string().optional().optional(),
max_token: z.coerce.number().optional(),
threshold: z.coerce.number().optional(),
max_cluster: z.coerce.number().optional(),
random_seed: z.coerce.number().optional(),
const FormSchema = z
.object({
parseType: z.number(),
parser_id: z
.string()
.min(1, {
message: t('common.pleaseSelect'),
})
.optional(),
graphrag: z.object({
use_graphrag: z.boolean().optional(),
.trim(),
pipeline_id: z.string().optional(),
parser_config: z.object({
task_page_size: z.coerce.number().optional(),
layout_recognize: z.string().optional(),
chunk_token_num: z.coerce.number().optional(),
delimiter: z.string().optional(),
auto_keywords: z.coerce.number().optional(),
auto_questions: z.coerce.number().optional(),
html4excel: z.boolean().optional(),
// raptor: z
// .object({
// use_raptor: z.boolean().optional(),
// prompt: z.string().optional().optional(),
// max_token: z.coerce.number().optional(),
// threshold: z.coerce.number().optional(),
// max_cluster: z.coerce.number().optional(),
// random_seed: z.coerce.number().optional(),
// })
// .optional(),
// graphrag: z.object({
// use_graphrag: z.boolean().optional(),
// }),
entity_types: z.array(z.string()).optional(),
pages: z
.array(z.object({ from: z.coerce.number(), to: z.coerce.number() }))
.optional(),
}),
entity_types: z.array(z.string()).optional(),
pages: z
.array(z.object({ from: z.coerce.number(), to: z.coerce.number() }))
.optional(),
}),
});
})
.superRefine((data, ctx) => {
if (data.parseType === 2 && !data.pipeline_id) {
ctx.addIssue({
path: ['pipeline_id'],
message: t('common.pleaseSelect'),
code: 'custom',
});
}
});
const form = useForm<z.infer<typeof FormSchema>>({
resolver: zodResolver(FormSchema),
defaultValues: {
parser_id: parserId,
parser_id: parserId || '',
pipeline_id: pipelineId || '',
parseType: pipelineId ? 2 : 1,
parser_config: defaultParserValues,
},
});
@ -200,17 +209,19 @@ export function ChunkMethodDialog({
const pages =
parserConfig?.pages?.map((x) => ({ from: x[0], to: x[1] })) ?? [];
form.reset({
parser_id: parserId,
parser_id: parserId || '',
pipeline_id: pipelineId || '',
parseType: pipelineId ? 2 : 1,
parser_config: fillDefaultParserValue({
pages: pages.length > 0 ? pages : [{ from: 1, to: 1024 }],
...omit(parserConfig, 'pages'),
graphrag: {
use_graphrag: get(
parserConfig,
'graphrag.use_graphrag',
useGraphRag,
),
},
// graphrag: {
// use_graphrag: get(
// parserConfig,
// 'graphrag.use_graphrag',
// useGraphRag,
// ),
// },
}),
});
}
@ -220,10 +231,20 @@ export function ChunkMethodDialog({
knowledgeDetails.parser_config,
parserConfig,
parserId,
pipelineId,
useGraphRag,
visible,
]);
const parseType = useWatch({
control: form.control,
name: 'parseType',
defaultValue: pipelineId ? 2 : 1,
});
useEffect(() => {
if (parseType === 1) {
form.setValue('pipeline_id', '');
}
}, [parseType, form]);
return (
<Dialog open onOpenChange={hideModal}>
<DialogContent className="max-w-[50vw]">
@ -237,7 +258,17 @@ export function ChunkMethodDialog({
id={FormId}
>
<FormContainer>
<FormField
<ParseTypeItem />
{parseType === 1 && <ChunkMethodItem></ChunkMethodItem>}
{parseType === 2 && (
<DataFlowSelect
isMult={false}
// toDataPipeline={navigateToAgents}
formFieldName="pipeline_id"
/>
)}
{/* <FormField
control={form.control}
name="parser_id"
render={({ field }) => (
@ -252,9 +283,11 @@ export function ChunkMethodDialog({
<FormMessage />
</FormItem>
)}
/>
{showPages && <DynamicPageRange></DynamicPageRange>}
{showPages && layoutRecognize && (
/> */}
{showPages && parseType === 1 && (
<DynamicPageRange></DynamicPageRange>
)}
{showPages && parseType === 1 && layoutRecognize && (
<FormField
control={form.control}
name="parser_config.task_page_size"
@ -279,50 +312,60 @@ export function ChunkMethodDialog({
/>
)}
</FormContainer>
<FormContainer
show={showOne || showMaxTokenNumber}
className="space-y-3"
>
{showOne && <LayoutRecognizeFormField></LayoutRecognizeFormField>}
{showMaxTokenNumber && (
<>
<MaxTokenNumberFormField
max={
selectedTag === DocumentParserType.KnowledgeGraph
? 8192 * 2
: 2048
}
></MaxTokenNumberFormField>
<DelimiterFormField></DelimiterFormField>
</>
)}
</FormContainer>
<FormContainer
show={showAutoKeywords(selectedTag) || showExcelToHtml}
className="space-y-3"
>
{showAutoKeywords(selectedTag) && (
<>
<AutoKeywordsFormField></AutoKeywordsFormField>
<AutoQuestionsFormField></AutoQuestionsFormField>
</>
)}
{showExcelToHtml && <ExcelToHtmlFormField></ExcelToHtmlFormField>}
</FormContainer>
{showRaptorParseConfiguration(
selectedTag as DocumentParserType,
) && (
<FormContainer>
<RaptorFormFields></RaptorFormFields>
</FormContainer>
)}
{showGraphRagItems(selectedTag as DocumentParserType) &&
useGraphRag && (
<FormContainer>
<UseGraphRagFormField></UseGraphRagFormField>
{parseType === 1 && (
<>
<FormContainer
show={showOne || showMaxTokenNumber}
className="space-y-3"
>
{showOne && (
<LayoutRecognizeFormField></LayoutRecognizeFormField>
)}
{showMaxTokenNumber && (
<>
<MaxTokenNumberFormField
max={
selectedTag === DocumentParserType.KnowledgeGraph
? 8192 * 2
: 2048
}
></MaxTokenNumberFormField>
<DelimiterFormField></DelimiterFormField>
</>
)}
</FormContainer>
)}
{showEntityTypes && <EntityTypesFormField></EntityTypesFormField>}
<FormContainer
show={showAutoKeywords(selectedTag) || showExcelToHtml}
className="space-y-3"
>
{showAutoKeywords(selectedTag) && (
<>
<AutoKeywordsFormField></AutoKeywordsFormField>
<AutoQuestionsFormField></AutoQuestionsFormField>
</>
)}
{showExcelToHtml && (
<ExcelToHtmlFormField></ExcelToHtmlFormField>
)}
</FormContainer>
{/* {showRaptorParseConfiguration(
selectedTag as DocumentParserType,
) && (
<FormContainer>
<RaptorFormFields></RaptorFormFields>
</FormContainer>
)} */}
{/* {showGraphRagItems(selectedTag as DocumentParserType) &&
useGraphRag && (
<FormContainer>
<UseGraphRagFormField></UseGraphRagFormField>
</FormContainer>
)} */}
{showEntityTypes && (
<EntityTypesFormField></EntityTypesFormField>
)}
</>
)}
</form>
</Form>
<DialogFooter>

View File

@ -1,7 +1,7 @@
import { IParserConfig } from '@/interfaces/database/document';
import { useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { DocumentType } from '../layout-recognize-form-field';
import { ParseDocumentType } from '../layout-recognize-form-field';
export function useDefaultParserValues() {
const { t } = useTranslation();
@ -9,23 +9,23 @@ export function useDefaultParserValues() {
const defaultParserValues = useMemo(() => {
const defaultParserValues = {
task_page_size: 12,
layout_recognize: DocumentType.DeepDOC,
layout_recognize: ParseDocumentType.DeepDOC,
chunk_token_num: 512,
delimiter: '\n',
auto_keywords: 0,
auto_questions: 0,
html4excel: false,
raptor: {
use_raptor: false,
prompt: t('knowledgeConfiguration.promptText'),
max_token: 256,
threshold: 0.1,
max_cluster: 64,
random_seed: 0,
},
graphrag: {
use_graphrag: false,
},
// raptor: {
// use_raptor: false,
// prompt: t('knowledgeConfiguration.promptText'),
// max_token: 256,
// threshold: 0.1,
// max_cluster: 64,
// random_seed: 0,
// },
// graphrag: {
// use_graphrag: false,
// },
entity_types: [],
pages: [],
};

View File

@ -8,7 +8,7 @@ import {
AlertDialogTitle,
AlertDialogTrigger,
} from '@/components/ui/alert-dialog';
import { PropsWithChildren } from 'react';
import { DialogProps } from '@radix-ui/react-dialog';
import { useTranslation } from 'react-i18next';
interface IProps {
@ -24,7 +24,10 @@ export function ConfirmDeleteDialog({
onOk,
onCancel,
hidden = false,
}: IProps & PropsWithChildren) {
onOpenChange,
open,
defaultOpen,
}: IProps & DialogProps) {
const { t } = useTranslation();
if (hidden) {
@ -32,7 +35,11 @@ export function ConfirmDeleteDialog({
}
return (
<AlertDialog>
<AlertDialog
onOpenChange={onOpenChange}
open={open}
defaultOpen={defaultOpen}
>
<AlertDialogTrigger asChild>{children}</AlertDialogTrigger>
<AlertDialogContent
onSelect={(e) => e.preventDefault()}

View File

@ -22,7 +22,7 @@ const Languages = [
'Vietnamese',
];
const options = Languages.map((x) => ({
export const crossLanguageOptions = Languages.map((x) => ({
label: t('language.' + toLower(x)),
value: x,
}));
@ -30,11 +30,13 @@ const options = Languages.map((x) => ({
type CrossLanguageItemProps = {
name?: string;
vertical?: boolean;
label?: string;
};
export const CrossLanguageFormField = ({
name = 'prompt_config.cross_languages',
vertical = true,
label,
}: CrossLanguageItemProps) => {
const { t } = useTranslation();
const form = useFormContext();
@ -53,11 +55,11 @@ export const CrossLanguageFormField = ({
})}
>
<FormLabel tooltip={t('chat.crossLanguageTip')}>
{t('chat.crossLanguage')}
{label || t('chat.crossLanguage')}
</FormLabel>
<FormControl>
<MultiSelect
options={options}
options={crossLanguageOptions}
placeholder={t('fileManager.pleaseSelect')}
maxCount={100}
{...field}

View File

@ -0,0 +1,120 @@
import { AgentCategory } from '@/constants/agent';
import { useTranslate } from '@/hooks/common-hooks';
import { useFetchAgentList } from '@/hooks/use-agent-request';
import { buildSelectOptions } from '@/utils/component-util';
import { ArrowUpRight } from 'lucide-react';
import { useEffect, useMemo } from 'react';
import { useFormContext } from 'react-hook-form';
import { SelectWithSearch } from '../originui/select-with-search';
import {
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '../ui/form';
import { MultiSelect } from '../ui/multi-select';
export interface IDataPipelineSelectNode {
id?: string;
name?: string;
avatar?: string;
}
interface IProps {
toDataPipeline?: () => void;
formFieldName: string;
isMult?: boolean;
setDataList?: (data: IDataPipelineSelectNode[]) => void;
}
export function DataFlowSelect(props: IProps) {
const { toDataPipeline, formFieldName, isMult = false, setDataList } = props;
const { t } = useTranslate('knowledgeConfiguration');
const form = useFormContext();
const toDataPipLine = () => {
toDataPipeline?.();
};
const { data: dataPipelineOptions } = useFetchAgentList({
canvas_category: AgentCategory.DataflowCanvas,
});
const options = useMemo(() => {
const option = buildSelectOptions(
dataPipelineOptions?.canvas,
'id',
'title',
);
return option || [];
}, [dataPipelineOptions]);
const nodes = useMemo(() => {
return (
dataPipelineOptions?.canvas?.map((item) => {
return {
id: item?.id,
name: item?.title,
avatar: item?.avatar,
};
}) || []
);
}, [dataPipelineOptions]);
useEffect(() => {
setDataList?.(nodes);
}, [nodes, setDataList]);
return (
<FormField
control={form.control}
name={formFieldName}
render={({ field }) => (
<FormItem className=" items-center space-y-0 ">
<div className="flex flex-col gap-1">
<div className="flex gap-2 justify-between ">
<FormLabel
tooltip={t('dataFlowTip')}
className="text-sm text-text-primary whitespace-wrap "
>
{t('dataPipeline')}
</FormLabel>
{toDataPipeline && (
<div
className="text-sm flex text-text-primary cursor-pointer"
onClick={toDataPipLine}
>
{t('buildItFromScratch')}
<ArrowUpRight size={14} />
</div>
)}
</div>
<div className="text-muted-foreground">
<FormControl>
<>
{!isMult && (
<SelectWithSearch
{...field}
placeholder={t('dataFlowPlaceholder')}
options={options}
/>
)}
{isMult && (
<MultiSelect
{...field}
onValueChange={field.onChange}
placeholder={t('dataFlowPlaceholder')}
options={options}
/>
)}
</>
</FormControl>
</div>
</div>
<div className="flex pt-1">
<FormMessage />
</div>
</FormItem>
)}
/>
);
}

View File

@ -16,11 +16,17 @@ interface IProps {
}
export const DelimiterInput = forwardRef<HTMLInputElement, InputProps & IProps>(
({ value, onChange, maxLength, defaultValue }, ref) => {
const nextValue = value?.replaceAll('\n', '\\n');
({ value, onChange, maxLength, defaultValue, ...props }, ref) => {
const nextValue = value
?.replaceAll('\n', '\\n')
.replaceAll('\t', '\\t')
.replaceAll('\r', '\\r');
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const val = e.target.value;
const nextValue = val.replaceAll('\\n', '\n');
const nextValue = val
.replaceAll('\\n', '\n')
.replaceAll('\\t', '\t')
.replaceAll('\\r', '\r');
onChange?.(nextValue);
};
return (
@ -30,6 +36,7 @@ export const DelimiterInput = forwardRef<HTMLInputElement, InputProps & IProps>(
maxLength={maxLength}
defaultValue={defaultValue}
ref={ref}
{...props}
></Input>
);
},

View File

@ -26,7 +26,7 @@ export function EntityTypesFormField({
return (
<FormItem className=" items-center space-y-0 ">
<div className="flex items-center">
<FormLabel className="text-sm text-muted-foreground whitespace-nowrap w-1/4">
<FormLabel className="text-sm whitespace-nowrap w-1/4">
<span className="text-red-600">*</span> {t('entityTypes')}
</FormLabel>
<div className="w-3/4">

View File

@ -1,24 +1,29 @@
// src/pages/dataset/file-logs/file-status-badge.tsx
import { RunningStatus } from '@/pages/dataset/dataset/constant';
import { FC } from 'react';
/**
* params: status: 0 not run yet 1 running, 2 cancel, 3 success, 4 fail
*/
interface StatusBadgeProps {
status: 'Success' | 'Failed' | 'Running' | 'Pending';
// status: 'Success' | 'Failed' | 'Running' | 'Pending';
status: RunningStatus;
name?: string;
}
const FileStatusBadge: FC<StatusBadgeProps> = ({ status }) => {
const FileStatusBadge: FC<StatusBadgeProps> = ({ status, name }) => {
const getStatusColor = () => {
// #3ba05c → rgb(59, 160, 92) // state-success
// #d8494b → rgb(216, 73, 75) // state-error
// #00beb4 → rgb(0, 190, 180) // accent-primary
// #faad14 → rgb(250, 173, 20) // state-warning
switch (status) {
case 'Success':
case RunningStatus.DONE:
return `bg-[rgba(59,160,92,0.1)] text-state-success`;
case 'Failed':
case RunningStatus.FAIL:
return `bg-[rgba(216,73,75,0.1)] text-state-error`;
case 'Running':
case RunningStatus.RUNNING:
return `bg-[rgba(0,190,180,0.1)] text-accent-primary`;
case 'Pending':
case RunningStatus.UNSTART:
return `bg-[rgba(250,173,20,0.1)] text-state-warning`;
default:
return 'bg-gray-500/10 text-white';
@ -31,13 +36,13 @@ const FileStatusBadge: FC<StatusBadgeProps> = ({ status }) => {
// #00beb4 → rgb(0, 190, 180) // accent-primary
// #faad14 → rgb(250, 173, 20) // state-warning
switch (status) {
case 'Success':
case RunningStatus.DONE:
return `bg-[rgba(59,160,92,1)] text-state-success`;
case 'Failed':
case RunningStatus.FAIL:
return `bg-[rgba(216,73,75,1)] text-state-error`;
case 'Running':
case RunningStatus.RUNNING:
return `bg-[rgba(0,190,180,1)] text-accent-primary`;
case 'Pending':
case RunningStatus.UNSTART:
return `bg-[rgba(250,173,20,1)] text-state-warning`;
default:
return 'bg-gray-500/10 text-white';
@ -46,10 +51,10 @@ const FileStatusBadge: FC<StatusBadgeProps> = ({ status }) => {
return (
<span
className={`inline-flex items-center w-[75px] px-2 py-1 rounded-full text-xs font-medium ${getStatusColor(0.1)}`}
className={`inline-flex items-center w-[75px] px-2 py-1 rounded-full text-xs font-medium ${getStatusColor()}`}
>
<div className={`w-1 h-1 mr-1 rounded-full ${getBgStatusColor()}`}></div>
{status}
{name || ''}
</span>
);
};

View File

@ -13,8 +13,15 @@ interface IProps {
onClick?: () => void;
moreDropdown: React.ReactNode;
sharedBadge?: ReactNode;
icon?: React.ReactNode;
}
export function HomeCard({ data, onClick, moreDropdown, sharedBadge }: IProps) {
export function HomeCard({
data,
onClick,
moreDropdown,
sharedBadge,
icon,
}: IProps) {
return (
<Card
className="bg-bg-card border-colors-outline-neutral-standard"
@ -32,10 +39,13 @@ export function HomeCard({ data, onClick, moreDropdown, sharedBadge }: IProps) {
/>
</div>
<div className="flex flex-col justify-between gap-1 flex-1 h-full w-[calc(100%-50px)]">
<section className="flex justify-between">
<div className="text-[20px] font-bold w-80% leading-5 text-ellipsis overflow-hidden">
{data.name}
</div>
<section className="flex justify-between w-full">
<section className="flex gap-1 items-center w-full">
<div className="text-[20px] font-bold w-80% leading-5 text-ellipsis overflow-hidden">
{data.name}
</div>
{icon}
</section>
{moreDropdown}
</section>

View File

@ -4,6 +4,7 @@ import { getExtension } from '@/utils/document-util';
type IconFontType = {
name: string;
className?: string;
};
@ -13,6 +14,23 @@ export const IconFont = ({ name, className }: IconFontType) => (
</svg>
);
export function IconFontFill({
name,
className,
isFill = true,
}: IconFontType & { isFill?: boolean }) {
return (
<span className={cn('size-4', className)}>
<svg
className={cn('size-4', className)}
style={{ fill: isFill ? 'currentColor' : '' }}
>
<use xlinkHref={`#icon-${name}`} />
</svg>
</span>
);
}
export function FileIcon({
name,
className,

View File

@ -1,9 +1,11 @@
import { LlmModelType } from '@/constants/knowledge';
import { useTranslate } from '@/hooks/common-hooks';
import { useSelectLlmOptionsByModelType } from '@/hooks/llm-hooks';
import { cn } from '@/lib/utils';
import { camelCase } from 'lodash';
import { useMemo } from 'react';
import { ReactNode, useMemo } from 'react';
import { useFormContext } from 'react-hook-form';
import { SelectWithSearch } from './originui/select-with-search';
import {
FormControl,
FormField,
@ -11,24 +13,36 @@ import {
FormLabel,
FormMessage,
} from './ui/form';
import { RAGFlowSelect } from './ui/select';
export const enum DocumentType {
export const enum ParseDocumentType {
DeepDOC = 'DeepDOC',
PlainText = 'Plain Text',
}
export function LayoutRecognizeFormField() {
export function LayoutRecognizeFormField({
name = 'parser_config.layout_recognize',
horizontal = true,
optionsWithoutLLM,
label,
}: {
name?: string;
horizontal?: boolean;
optionsWithoutLLM?: { value: string; label: string }[];
label?: ReactNode;
}) {
const form = useFormContext();
const { t } = useTranslate('knowledgeDetails');
const allOptions = useSelectLlmOptionsByModelType();
const options = useMemo(() => {
const list = [DocumentType.DeepDOC, DocumentType.PlainText].map((x) => ({
label: x === DocumentType.PlainText ? t(camelCase(x)) : 'DeepDoc',
value: x,
}));
const list = optionsWithoutLLM
? optionsWithoutLLM
: [ParseDocumentType.DeepDOC, ParseDocumentType.PlainText].map((x) => ({
label:
x === ParseDocumentType.PlainText ? t(camelCase(x)) : 'DeepDoc',
value: x,
}));
const image2TextList = allOptions[LlmModelType.Image2text].map((x) => {
return {
@ -48,38 +62,40 @@ export function LayoutRecognizeFormField() {
});
return [...list, ...image2TextList];
}, [allOptions, t]);
}, [allOptions, optionsWithoutLLM, t]);
return (
<FormField
control={form.control}
name="parser_config.layout_recognize"
name={name}
render={({ field }) => {
if (typeof field.value === 'undefined') {
// default value set
form.setValue(
'parser_config.layout_recognize',
form.formState.defaultValues?.parser_config?.layout_recognize ??
'DeepDOC',
);
}
return (
<FormItem className=" items-center space-y-0 ">
<div className="flex items-center">
<FormItem className={'items-center space-y-0 '}>
<div
className={cn('flex', {
'flex-col ': !horizontal,
'items-center': horizontal,
})}
>
<FormLabel
tooltip={t('layoutRecognizeTip')}
className="text-sm text-muted-foreground whitespace-wrap w-1/4"
className={cn('text-sm text-muted-foreground whitespace-wrap', {
['w-1/4']: horizontal,
})}
>
{t('layoutRecognize')}
{label || t('layoutRecognize')}
</FormLabel>
<div className="w-3/4">
<div className={horizontal ? 'w-3/4' : 'w-full'}>
<FormControl>
<RAGFlowSelect {...field} options={options}></RAGFlowSelect>
<SelectWithSearch
{...field}
options={options}
></SelectWithSearch>
</FormControl>
</div>
</div>
<div className="flex pt-1">
<div className="w-1/4"></div>
<div className={horizontal ? 'w-1/4' : 'w-full'}></div>
<FormMessage />
</div>
</FormItem>

View File

@ -0,0 +1,25 @@
import { LlmModelType } from '@/constants/knowledge';
import { useComposeLlmOptionsByModelTypes } from '@/hooks/llm-hooks';
import { useTranslation } from 'react-i18next';
import { SelectWithSearch } from '../originui/select-with-search';
import { RAGFlowFormItem } from '../ragflow-form';
type LLMFormFieldProps = {
options?: any[];
name?: string;
};
export function LLMFormField({ options, name }: LLMFormFieldProps) {
const { t } = useTranslation();
const modelOptions = useComposeLlmOptionsByModelTypes([
LlmModelType.Chat,
LlmModelType.Image2text,
]);
return (
<RAGFlowFormItem name={name || 'llm_id'} label={t('chat.model')}>
<SelectWithSearch options={options || modelOptions}></SelectWithSearch>
</RAGFlowFormItem>
);
}

View File

@ -1,11 +1,9 @@
import { LlmModelType, ModelVariableType } from '@/constants/knowledge';
import { ModelVariableType } 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';
import { SelectWithSearch } from '../originui/select-with-search';
import {
FormControl,
FormField,
@ -20,6 +18,7 @@ import {
SelectTrigger,
SelectValue,
} from '../ui/select';
import { LLMFormField } from './llm-form-field';
import { SliderInputSwitchFormField } from './slider';
import { useHandleFreedomChange } from './use-watch-change';
@ -61,11 +60,6 @@ export function LlmSettingFieldItems({
const form = useFormContext();
const { t } = useTranslate('chat');
const modelOptions = useComposeLlmOptionsByModelTypes([
LlmModelType.Chat,
LlmModelType.Image2text,
]);
const getFieldWithPrefix = useCallback(
(name: string) => {
return prefix ? `${prefix}.${name}` : name;
@ -82,22 +76,7 @@ export function LlmSettingFieldItems({
return (
<div className="space-y-5">
<FormField
control={form.control}
name={'llm_id'}
render={({ field }) => (
<FormItem>
<FormLabel>{t('model')}</FormLabel>
<FormControl>
<SelectWithSearch
options={options || modelOptions}
{...field}
></SelectWithSearch>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<LLMFormField options={options}></LLMFormField>
<FormField
control={form.control}
name={'parameter'}

View File

@ -45,8 +45,26 @@ export type SelectWithSearchFlagProps = {
onChange?(value: string): void;
triggerClassName?: string;
allowClear?: boolean;
disabled?: boolean;
placeholder?: string;
};
function findLabelWithoutOptions(
options: SelectWithSearchFlagOptionType[],
value: string,
) {
return options.find((opt) => opt.value === value)?.label || '';
}
function findLabelWithOptions(
options: SelectWithSearchFlagOptionType[],
value: string,
) {
return options
.map((group) => group?.options?.find((item) => item.value === value))
.filter(Boolean)[0]?.label;
}
export const SelectWithSearch = forwardRef<
React.ElementRef<typeof Button>,
SelectWithSearchFlagProps
@ -58,6 +76,8 @@ export const SelectWithSearch = forwardRef<
options = [],
triggerClassName,
allowClear = false,
disabled = false,
placeholder = t('common.selectPlaceholder'),
},
ref,
) => {
@ -65,6 +85,28 @@ export const SelectWithSearch = forwardRef<
const [open, setOpen] = useState<boolean>(false);
const [value, setValue] = useState<string>('');
const selectLabel = useMemo(() => {
if (options.every((x) => x.options === undefined)) {
return findLabelWithoutOptions(options, value);
} else if (options.every((x) => Array.isArray(x.options))) {
return findLabelWithOptions(options, value);
} else {
// Some have options, some don't
const optionsWithOptions = options.filter((x) =>
Array.isArray(x.options),
);
const optionsWithoutOptions = options.filter(
(x) => x.options === undefined,
);
const label = findLabelWithOptions(optionsWithOptions, value);
if (label) {
return label;
}
return findLabelWithoutOptions(optionsWithoutOptions, value);
}
}, [options, value]);
const handleSelect = useCallback(
(val: string) => {
setValue(val);
@ -86,16 +128,7 @@ export const SelectWithSearch = forwardRef<
useEffect(() => {
setValue(val);
}, [val]);
const selectLabel = useMemo(() => {
const optionTemp = options[0];
if (optionTemp?.options) {
return options
.map((group) => group?.options?.find((item) => item.value === value))
.filter(Boolean)[0]?.label;
} else {
return options.find((opt) => opt.value === value)?.label || '';
}
}, [options, value]);
return (
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
@ -105,6 +138,7 @@ export const SelectWithSearch = forwardRef<
role="combobox"
aria-expanded={open}
ref={ref}
disabled={disabled}
className={cn(
'bg-background hover:bg-background border-input w-full justify-between px-3 font-normal outline-offset-0 outline-none focus-visible:outline-[3px] [&_svg]:pointer-events-auto',
triggerClassName,
@ -115,9 +149,7 @@ export const SelectWithSearch = forwardRef<
<span className="leading-none truncate">{selectLabel}</span>
</span>
) : (
<span className="text-muted-foreground">
{t('common.selectPlaceholder')}
</span>
<span className="text-muted-foreground">{placeholder}</span>
)}
<div className="flex items-center justify-between">
{value && allowClear && (

View File

@ -1,7 +1,8 @@
'use client';
import { cn } from '@/lib/utils';
import { parseColorToRGBA } from '@/utils/common-util';
import { TimelineNodeType } from '@/pages/dataflow-result/constant';
import { parseColorToRGB } from '@/utils/common-util';
import { Slot } from '@radix-ui/react-slot';
import * as React from 'react';
@ -220,6 +221,8 @@ interface TimelineNode
completed?: boolean;
clickable?: boolean;
activeStyle?: TimelineIndicatorNodeProps;
detail?: any;
type?: TimelineNodeType;
}
interface CustomTimelineProps extends React.HTMLAttributes<HTMLDivElement> {
@ -243,7 +246,7 @@ const CustomTimeline = ({
orientation = 'horizontal',
lineStyle = 'solid',
lineColor = 'var(--text-secondary)',
indicatorColor = 'var(--accent-primary)',
indicatorColor = 'rgb(var(--accent-primary))',
defaultValue = 1,
className,
activeStyle,
@ -251,8 +254,7 @@ const CustomTimeline = ({
}: CustomTimelineProps) => {
const [internalActiveStep, setInternalActiveStep] =
React.useState(defaultValue);
const _lineColor = `rgb(${parseColorToRGBA(lineColor)})`;
console.log(lineColor, _lineColor);
const _lineColor = `rgb(${parseColorToRGB(lineColor)})`;
const currentActiveStep = activeStep ?? internalActiveStep;
const handleStepChange = (step: number, id: string | number) => {
@ -261,7 +263,7 @@ const CustomTimeline = ({
}
onStepChange?.(step, id);
};
const [r, g, b] = parseColorToRGBA(indicatorColor);
const [r, g, b] = parseColorToRGB(indicatorColor);
return (
<Timeline
value={currentActiveStep}
@ -284,8 +286,6 @@ const CustomTimeline = ({
typeof _nodeSizeTemp === 'number'
? `${_nodeSizeTemp}px`
: _nodeSizeTemp;
console.log('icon-size', nodeSize, node.nodeSize, _nodeSize);
// const activeStyle = _activeStyle || {};
return (
<TimelineItem
@ -372,11 +372,10 @@ const CustomTimeline = ({
)}
</TimelineIndicator>
<TimelineHeader>
{node.date && <TimelineDate>{node.date}</TimelineDate>}
<TimelineHeader className="transform -translate-x-[40%] text-center">
<TimelineTitle
className={cn(
'text-sm font-medium',
'text-sm font-medium -ml-1',
isActive && _activeStyle.textColor
? `text-${_activeStyle.textColor}`
: '',
@ -387,6 +386,7 @@ const CustomTimeline = ({
>
{node.title}
</TimelineTitle>
{node.date && <TimelineDate>{node.date}</TimelineDate>}
</TimelineHeader>
{node.content && <TimelineContent>{node.content}</TimelineContent>}
</TimelineItem>

View File

@ -1,6 +1,11 @@
import { DocumentParserType } from '@/constants/knowledge';
import { useTranslate } from '@/hooks/common-hooks';
import { cn } from '@/lib/utils';
import {
GenerateLogButton,
GenerateType,
IGenerateLogButtonProps,
} from '@/pages/dataset/dataset/generate-button/generate';
import { upperFirst } from 'lodash';
import { useCallback, useMemo } from 'react';
import { useFormContext, useWatch } from 'react-hook-form';
@ -47,9 +52,17 @@ export const showGraphRagItems = (parserId: DocumentParserType | undefined) => {
type GraphRagItemsProps = {
marginBottom?: boolean;
className?: string;
data: IGenerateLogButtonProps;
onDelete?: () => void;
};
export function UseGraphRagFormField() {
export function UseGraphRagFormField({
data,
onDelete,
}: {
data: IGenerateLogButtonProps;
onDelete?: () => void;
}) {
const form = useFormContext();
const { t } = useTranslate('knowledgeConfiguration');
@ -62,16 +75,23 @@ export function UseGraphRagFormField() {
<div className="flex items-center gap-1">
<FormLabel
tooltip={t('useGraphRagTip')}
className="text-sm text-muted-foreground whitespace-break-spaces w-1/4"
className="text-sm whitespace-break-spaces w-1/4"
>
{t('useGraphRag')}
</FormLabel>
<div className="w-3/4">
<FormControl>
<Switch
{/* <Switch
checked={field.value}
onCheckedChange={field.onChange}
></Switch>
></Switch> */}
<GenerateLogButton
{...data}
onDelete={onDelete}
className="w-full text-text-secondary"
status={1}
type={GenerateType.KnowledgeGraph}
/>
</FormControl>
</div>
</div>
@ -89,6 +109,8 @@ export function UseGraphRagFormField() {
const GraphRagItems = ({
marginBottom = false,
className = 'p-10',
data,
onDelete,
}: GraphRagItemsProps) => {
const { t } = useTranslate('knowledgeConfiguration');
const form = useFormContext();
@ -114,7 +136,10 @@ const GraphRagItems = ({
return (
<FormContainer className={cn({ 'mb-4': marginBottom }, className)}>
<UseGraphRagFormField></UseGraphRagFormField>
<UseGraphRagFormField
data={data}
onDelete={onDelete}
></UseGraphRagFormField>
{useRaptor && (
<>
<EntityTypesFormField name="parser_config.graphrag.entity_types"></EntityTypesFormField>
@ -125,7 +150,7 @@ const GraphRagItems = ({
<FormItem className=" items-center space-y-0 ">
<div className="flex items-center">
<FormLabel
className="text-sm text-muted-foreground whitespace-nowrap w-1/4"
className="text-sm whitespace-nowrap w-1/4"
tooltip={renderWideTooltip(
<div
dangerouslySetInnerHTML={{
@ -161,7 +186,7 @@ const GraphRagItems = ({
<div className="flex items-center">
<FormLabel
tooltip={renderWideTooltip('resolutionTip')}
className="text-sm text-muted-foreground whitespace-nowrap w-1/4"
className="text-sm whitespace-nowrap w-1/4"
>
{t('resolution')}
</FormLabel>
@ -190,7 +215,7 @@ const GraphRagItems = ({
<div className="flex items-center">
<FormLabel
tooltip={renderWideTooltip('communityTip')}
className="text-sm text-muted-foreground whitespace-nowrap w-1/4"
className="text-sm whitespace-nowrap w-1/4"
>
{t('community')}
</FormLabel>
@ -210,6 +235,18 @@ const GraphRagItems = ({
</FormItem>
)}
/>
{/* {showGenerateItem && (
<div className="w-full flex items-center">
<div className="text-sm whitespace-nowrap w-1/4">
{t('extractKnowledgeGraph')}
</div>
<GenerateLogButton
className="w-3/4 text-text-secondary"
status={1}
type={GenerateType.KnowledgeGraph}
/>
</div>
)} */}
</>
)}
</FormContainer>

View File

@ -1,12 +1,16 @@
import { FormLayout } from '@/constants/form';
import { DocumentParserType } from '@/constants/knowledge';
import { useTranslate } from '@/hooks/common-hooks';
import {
GenerateLogButton,
GenerateType,
IGenerateLogButtonProps,
} from '@/pages/dataset/dataset/generate-button/generate';
import random from 'lodash/random';
import { Plus } from 'lucide-react';
import { Shuffle } from 'lucide-react';
import { useCallback } from 'react';
import { useFormContext, useWatch } from 'react-hook-form';
import { SliderInputFormField } from '../slider-input-form-field';
import { Button } from '../ui/button';
import {
FormControl,
FormField,
@ -14,8 +18,7 @@ import {
FormLabel,
FormMessage,
} from '../ui/form';
import { Input } from '../ui/input';
import { Switch } from '../ui/switch';
import { ExpandedInput } from '../ui/input';
import { Textarea } from '../ui/textarea';
export const excludedParseMethods = [
@ -53,7 +56,13 @@ const Prompt = 'parser_config.raptor.prompt';
// The three types "table", "resume" and "one" do not display this configuration.
const RaptorFormFields = () => {
const RaptorFormFields = ({
data,
onDelete,
}: {
data: IGenerateLogButtonProps;
onDelete: () => void;
}) => {
const form = useFormContext();
const { t } = useTranslate('knowledgeConfiguration');
const useRaptor = useWatch({ name: UseRaptorField });
@ -93,7 +102,7 @@ const RaptorFormFields = () => {
<div className="flex items-center gap-1">
<FormLabel
tooltip={t('useRaptorTip')}
className="text-sm text-muted-foreground w-1/4 whitespace-break-spaces"
className="text-sm w-1/4 whitespace-break-spaces"
>
<div className="w-auto xl:w-20 2xl:w-24 3xl:w-28 4xl:w-auto ">
{t('useRaptor')}
@ -101,13 +110,13 @@ const RaptorFormFields = () => {
</FormLabel>
<div className="w-3/4">
<FormControl>
<Switch
checked={field.value}
onCheckedChange={(e) => {
changeRaptor(e);
field.onChange(e);
}}
></Switch>
<GenerateLogButton
{...data}
onDelete={onDelete}
className="w-full text-text-secondary"
status={1}
type={GenerateType.Raptor}
/>
</FormControl>
</div>
</div>
@ -130,7 +139,7 @@ const RaptorFormFields = () => {
<div className="flex items-start">
<FormLabel
tooltip={t('promptTip')}
className="text-sm text-muted-foreground whitespace-nowrap w-1/4"
className="text-sm whitespace-nowrap w-1/4"
>
{t('prompt')}
</FormLabel>
@ -185,21 +194,23 @@ const RaptorFormFields = () => {
render={({ field }) => (
<FormItem className=" items-center space-y-0 ">
<div className="flex items-center">
<FormLabel className="text-sm text-muted-foreground whitespace-wrap w-1/4">
<FormLabel className="text-sm whitespace-wrap w-1/4">
{t('randomSeed')}
</FormLabel>
<div className="w-3/4">
<FormControl defaultValue={0}>
<div className="flex gap-4 items-center">
<Input {...field} defaultValue={0} type="number" />
<Button
size={'sm'}
onClick={handleGenerate}
type={'button'}
>
<Plus />
</Button>
</div>
<ExpandedInput
{...field}
className="w-full"
defaultValue={0}
type="number"
suffix={
<Shuffle
className="size-3.5 cursor-pointer"
onClick={handleGenerate}
/>
}
/>
</FormControl>
</div>
</div>

View File

@ -11,11 +11,12 @@ import { ControllerRenderProps, useFormContext } from 'react-hook-form';
type RAGFlowFormItemProps = {
name: string;
label: ReactNode;
label?: ReactNode;
tooltip?: ReactNode;
children: ReactNode | ((field: ControllerRenderProps) => ReactNode);
horizontal?: boolean;
required?: boolean;
labelClassName?: string;
};
export function RAGFlowFormItem({
@ -25,6 +26,7 @@ export function RAGFlowFormItem({
children,
horizontal = false,
required = false,
labelClassName,
}: RAGFlowFormItemProps) {
const form = useFormContext();
return (
@ -37,13 +39,15 @@ export function RAGFlowFormItem({
'flex items-center': horizontal,
})}
>
<FormLabel
required={required}
tooltip={tooltip}
className={cn({ 'w-1/4': horizontal })}
>
{label}
</FormLabel>
{label && (
<FormLabel
required={required}
tooltip={tooltip}
className={cn({ 'w-1/4': horizontal }, labelClassName)}
>
{label}
</FormLabel>
)}
<FormControl>
{typeof children === 'function'
? children(field)

View File

@ -54,8 +54,7 @@ export function SliderInputFormField({
<FormLabel
tooltip={tooltip}
className={cn({
'text-sm text-muted-foreground whitespace-break-spaces w-1/4':
isHorizontal,
'text-sm whitespace-break-spaces w-1/4': isHorizontal,
})}
>
{label}

View File

@ -28,7 +28,7 @@ const DualRangeSlider = React.forwardRef<
)}
{...props}
>
<SliderPrimitive.Track className="relative h-2 w-full grow overflow-hidden rounded-full bg-secondary">
<SliderPrimitive.Track className="relative h-2 w-full grow overflow-hidden rounded-full bg-border-button">
<SliderPrimitive.Range className="absolute h-full bg-accent-primary" />
</SliderPrimitive.Track>
{initialValue.map((value, index) => (

View File

@ -31,6 +31,7 @@ export interface ModalProps {
export interface ModalType extends FC<ModalProps> {
show: typeof modalIns.show;
hide: typeof modalIns.hide;
destroy: typeof modalIns.destroy;
}
const Modal: ModalType = ({
@ -76,20 +77,20 @@ const Modal: ModalType = ({
const handleCancel = useCallback(() => {
onOpenChange?.(false);
onCancel?.();
}, [onOpenChange, onCancel]);
}, [onCancel, onOpenChange]);
const handleOk = useCallback(() => {
onOpenChange?.(true);
onOk?.();
}, [onOpenChange, onOk]);
}, [onOk, onOpenChange]);
const handleChange = (open: boolean) => {
onOpenChange?.(open);
console.log('open', open, onOpenChange);
if (open) {
handleOk();
onOk?.();
}
if (!open) {
handleCancel();
onCancel?.();
}
};
const footEl = useMemo(() => {
@ -177,7 +178,7 @@ const Modal: ModalType = ({
<DialogPrimitive.Close asChild>
<button
type="button"
className="flex h-7 w-7 items-center justify-center rounded-full hover:bg-muted"
className="flex h-7 w-7 items-center justify-center rounded-full hover:bg-muted focus-visible:outline-none"
>
{closeIcon}
</button>
@ -187,7 +188,7 @@ const Modal: ModalType = ({
)}
{/* content */}
<div className="py-2 px-6 overflow-y-auto max-h-[80vh] focus-visible:!outline-none">
<div className="py-2 px-6 overflow-y-auto scrollbar-auto max-h-[80vh] focus-visible:!outline-none">
{destroyOnClose && !open ? null : children}
</div>
@ -208,5 +209,6 @@ Modal.show = modalIns
return modalIns.show;
};
Modal.hide = modalIns.hide;
Modal.destroy = modalIns.destroy;
export { Modal };

View File

@ -49,7 +49,7 @@ function Radio({ value, checked, disabled, onChange, children }: RadioProps) {
>
<span
className={cn(
'flex h-4 w-4 items-center justify-center rounded-full border border-input transition-colors',
'flex h-4 w-4 items-center justify-center rounded-full border border-border transition-colors',
'peer ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
isChecked && 'border-primary bg-primary/10',
mergedDisabled && 'border-muted',

View File

@ -33,7 +33,12 @@ export { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger };
export const FormTooltip = ({ tooltip }: { tooltip: React.ReactNode }) => {
return (
<Tooltip>
<TooltipTrigger tabIndex={-1}>
<TooltipTrigger
tabIndex={-1}
onClick={(e) => {
e.preventDefault(); // Prevent clicking the tooltip from triggering form save
}}
>
<Info className="size-3 ml-2" />
</TooltipTrigger>
<TooltipContent>
@ -107,7 +112,7 @@ export const AntToolTip: React.FC<AntToolTipProps> = ({
{visible && title && (
<div
className={cn(
'absolute z-50 px-2.5 py-2 text-xs text-text-primary bg-muted rounded-sm shadow-sm whitespace-wrap',
'absolute z-50 px-2.5 py-2 text-xs text-text-primary bg-muted rounded-sm shadow-sm whitespace-wrap w-max',
getPlacementClasses(),
className,
)}