mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-07-14 17:08:31 +08:00
Feat: Added support for session graph and session essence templates. (#16851)
### Summary Feat: Added support for session graph and session essence templates.
This commit is contained in:
@@ -59,6 +59,16 @@ function ArtifactForceGraph<TNodeValue = IArtifactGraphEntity>({
|
||||
[onNodeClick, mapNodeToValue],
|
||||
);
|
||||
|
||||
const getLinkColor = useCallback(() => {
|
||||
if (typeof window === 'undefined' || !containerRef.current) {
|
||||
return '#b2b5b7';
|
||||
}
|
||||
return window
|
||||
.getComputedStyle(containerRef.current)
|
||||
.getPropertyValue('--text-disabled')
|
||||
.trim();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={containerRef}
|
||||
@@ -77,6 +87,7 @@ function ArtifactForceGraph<TNodeValue = IArtifactGraphEntity>({
|
||||
onNodeClick={handleNodeClick}
|
||||
nodeCanvasObject={renderNodeLabel}
|
||||
nodeCanvasObjectMode={() => 'after'}
|
||||
linkColor={getLinkColor}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -20,7 +20,7 @@ export interface IChunkListResult {
|
||||
searchString?: string;
|
||||
handleInputChange?: React.ChangeEventHandler<HTMLInputElement>;
|
||||
pagination: PaginationProps;
|
||||
setPagination?: (pagination: { page: number; pageSize: number }) => void;
|
||||
setPagination?: (pagination: { page: number; pageSize?: number }) => void;
|
||||
available: number | undefined;
|
||||
handleSetAvailable: (available: number | undefined) => void;
|
||||
dataUpdatedAt?: number; // Timestamp when data was last updated - useful for cache busting
|
||||
|
||||
@@ -12,9 +12,11 @@ import {
|
||||
getCompilationTemplateGroup,
|
||||
updateCompilationTemplateGroup,
|
||||
} from '@/services/compilation-template-group-service';
|
||||
import { isCreateCompilationTemplateGroup } from '@/utils/compilation-template-util';
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { useDebounce } from 'ahooks';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useParams } from 'react-router';
|
||||
|
||||
import {
|
||||
useGetPaginationWithRouter,
|
||||
@@ -98,15 +100,18 @@ export const useFetchCompilationTemplateGroupsByPage = () => {
|
||||
};
|
||||
};
|
||||
|
||||
export const useFetchCompilationTemplateGroup = (id?: string) => {
|
||||
export const useFetchCompilationTemplateGroup = () => {
|
||||
const { id } = useParams<{ id: string }>();
|
||||
const isCreate = isCreateCompilationTemplateGroup(id);
|
||||
|
||||
const { data, isFetching: loading } = useQuery<
|
||||
ICompilationTemplateGroup | undefined
|
||||
>({
|
||||
queryKey: CompilationTemplateGroupKeys.detail(id),
|
||||
enabled: !!id && id !== 'create',
|
||||
enabled: !isCreate,
|
||||
gcTime: 0,
|
||||
queryFn: async () => {
|
||||
if (!id || id === 'create') return undefined;
|
||||
if (isCreate) return undefined;
|
||||
const { data } = await getCompilationTemplateGroup(id);
|
||||
return data?.data as ICompilationTemplateGroup | undefined;
|
||||
},
|
||||
|
||||
@@ -27,6 +27,7 @@ export interface ICompilationTemplateConfig {
|
||||
[section: string]:
|
||||
| ICompilationTemplateSection
|
||||
| ICompilationTemplateRaptorConfig
|
||||
| Record<string, unknown>
|
||||
| string
|
||||
| boolean
|
||||
| undefined;
|
||||
|
||||
@@ -27,6 +27,7 @@ export interface ICompilationTemplateConfigRequest {
|
||||
[section: string]:
|
||||
| ICompilationTemplateSectionRequest
|
||||
| ICompilationTemplateRaptorConfigRequest
|
||||
| Record<string, unknown>
|
||||
| string
|
||||
| boolean
|
||||
| undefined;
|
||||
|
||||
@@ -103,6 +103,7 @@ export default function ToolbarPlugin({ onToggleSource, showSource }: Props) {
|
||||
Source
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setShowToc(!showToc);
|
||||
}}
|
||||
|
||||
@@ -517,7 +517,7 @@ Example: A 1 KB message with 1024-dim embedding uses ~9 KB. The 5 MB default lim
|
||||
graphPlaceholder: 'Graph view placeholder',
|
||||
llmWiki: 'LLM Wiki',
|
||||
skills: 'Skills',
|
||||
contents: 'Contents',
|
||||
contents: 'Navigation',
|
||||
topics: 'Topics',
|
||||
concept: 'Concept',
|
||||
entity: 'Entity',
|
||||
@@ -1650,6 +1650,7 @@ Example: Virtual Hosted Style`,
|
||||
"Select or customize a specific structure from the Blueprint library on the left to define your Wiki's content framework and visual presentation.",
|
||||
blueprintsPlaceholderSkip:
|
||||
'If you have no special requirements, feel free to skip this step, and the system will automatically apply the default structure.',
|
||||
useBlueprint: 'Use this blueprint',
|
||||
channelEmptyTip:
|
||||
'No chat channels added yet. Select one below to connect.',
|
||||
availableChannels: 'Available channels',
|
||||
@@ -2055,15 +2056,6 @@ Example: Virtual Hosted Style`,
|
||||
},
|
||||
knowledgeCompilation: {
|
||||
builtinTemplates: 'Built-in template',
|
||||
kind: {
|
||||
knowledge_graph: 'Knowledge graph',
|
||||
artifacts: 'Artifacts',
|
||||
timeline: 'Timeline',
|
||||
page_index: 'Page index',
|
||||
tree: 'Tree',
|
||||
mind_map: 'Mind map',
|
||||
empty: 'Empty',
|
||||
},
|
||||
},
|
||||
datasetSkill: {
|
||||
folders: 'Skills',
|
||||
|
||||
@@ -463,7 +463,7 @@ export default {
|
||||
graphPlaceholder: '图谱视图占位',
|
||||
llmWiki: 'LLM Wiki',
|
||||
skills: '技能',
|
||||
contents: '目录',
|
||||
contents: '导航',
|
||||
topics: '主题',
|
||||
concept: '概念',
|
||||
createDirectoryFolder: '创建目录',
|
||||
@@ -1350,6 +1350,7 @@ NER:使用 spaCy NER 和基于规则的关键词提取来抽取实体和关系
|
||||
'从左侧的 Blueprint 库中选择或自定义特定结构,以定义您的 Wiki 内容框架和视觉呈现。',
|
||||
blueprintsPlaceholderSkip:
|
||||
'如果您没有特殊要求,可以跳过此步骤,系统将自动应用默认结构。',
|
||||
useBlueprint: '使用此蓝图',
|
||||
channelEmptyTip: '暂未添加任何聊天渠道,请从下方选择一个进行连接。',
|
||||
availableChannels: '可用渠道',
|
||||
availableChannelsDescription: '选择要添加的聊天渠道',
|
||||
@@ -1714,15 +1715,6 @@ NER:使用 spaCy NER 和基于规则的关键词提取来抽取实体和关系
|
||||
},
|
||||
knowledgeCompilation: {
|
||||
builtinTemplates: '内置模板',
|
||||
kind: {
|
||||
knowledge_graph: '知识图谱',
|
||||
artifacts: 'Artifacts',
|
||||
timeline: 'Timeline',
|
||||
page_index: 'Page index',
|
||||
tree: '树',
|
||||
mind_map: '思维导图',
|
||||
empty: '空',
|
||||
},
|
||||
},
|
||||
datasetSkill: {
|
||||
folders: '技能',
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
import { CirclePause, WandSparkles } from 'lucide-react';
|
||||
import { CirclePause, Logs, WandSparkles } from 'lucide-react';
|
||||
import { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { IconFontFill } from '@/components/icon-font';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { cn } from '@/lib/utils';
|
||||
import {
|
||||
GenerateType,
|
||||
IconKeyMap,
|
||||
} from '@/pages/dataset/dataset/generate-button/constants';
|
||||
import { GenerateType } from '@/pages/dataset/dataset/generate-button/constants';
|
||||
import {
|
||||
ITraceInfo,
|
||||
useDatasetGenerate,
|
||||
@@ -79,10 +76,7 @@ export function CompilationEmptyState({
|
||||
) : (
|
||||
<div className="w-full max-w-md p-6 flex flex-col gap-4">
|
||||
<div className="flex items-center gap-2 text-text-primary">
|
||||
<IconFontFill
|
||||
name={IconKeyMap[generateType]}
|
||||
className="text-accent-primary"
|
||||
/>
|
||||
<Logs className="size-5" />
|
||||
<span>{t(LabelKeyMap[type])}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
|
||||
@@ -15,7 +15,6 @@ import {
|
||||
useFetchKnowledgeBaseConfiguration,
|
||||
useFetchKnowledgeGraph,
|
||||
} from '@/hooks/use-knowledge-request';
|
||||
import { Routes } from '@/routes';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useParams } from 'react-router';
|
||||
|
||||
@@ -83,10 +82,7 @@ export default function Compilation() {
|
||||
return (
|
||||
<section className="flex flex-col p-4 gap-4 h-full">
|
||||
<header className="space-y-5">
|
||||
<BackButton
|
||||
to={`${Routes.DatasetBase}${Routes.Files}/${id}`}
|
||||
onClick={navigateToDataFile(id!)}
|
||||
>
|
||||
<BackButton onClick={navigateToDataFile(id!)}>
|
||||
{t('common.back')}
|
||||
</BackButton>
|
||||
|
||||
|
||||
@@ -27,10 +27,3 @@ export const GenerateTypeMap = {
|
||||
[GenerateType.Artifact]: ProcessingType.artifact,
|
||||
[GenerateType.ToSkills]: ProcessingType.skill,
|
||||
};
|
||||
|
||||
export const IconKeyMap = {
|
||||
KnowledgeGraph: 'knowledgegraph',
|
||||
Raptor: 'dataflow-01',
|
||||
Artifact: 'book-open-01',
|
||||
ToSkills: 'spark',
|
||||
};
|
||||
|
||||
@@ -9,7 +9,7 @@ import { startCase } from 'lodash';
|
||||
import { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { FieldLabelKeyMap } from '@/pages/user-setting/compilation-templates/edit-template/utils';
|
||||
import { FieldLabelKeyMap } from '@/pages/user-setting/compilation-templates/create-next/utils';
|
||||
|
||||
import { useAddFieldForm } from '../hooks/use-add-field-form';
|
||||
|
||||
@@ -50,6 +50,14 @@ export function AddFieldModal({
|
||||
onOpenChange(false);
|
||||
}, [onOpenChange]);
|
||||
|
||||
const handleConfirm = useCallback(
|
||||
(field: Record<string, string>) => {
|
||||
onAdd(field);
|
||||
onOpenChange(false);
|
||||
},
|
||||
[onAdd, onOpenChange],
|
||||
);
|
||||
|
||||
const getFieldLabel = useCallback(
|
||||
(key: string) => {
|
||||
return FieldLabelKeyMap[key] ? t(FieldLabelKeyMap[key]) : startCase(key);
|
||||
@@ -68,7 +76,7 @@ export function AddFieldModal({
|
||||
<Button type="button" variant="outline" onClick={handleClose}>
|
||||
{t('common.cancel')}
|
||||
</Button>
|
||||
<Button type="button" onClick={handleSubmit(onAdd)}>
|
||||
<Button type="button" onClick={handleSubmit(handleConfirm)}>
|
||||
{t('common.confirm')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -5,12 +5,13 @@ import { Textarea } from '@/components/ui/textarea';
|
||||
import { TreeDataItem, TreeView } from '@/components/ui/tree-view';
|
||||
import { useFetchWikiPresets } from '@/hooks/use-compilation-template-request';
|
||||
import { IWikiPreset } from '@/interfaces/database/compilation-template';
|
||||
import { FormSchemaType } from '@/pages/user-setting/compilation-templates/create-next/schema';
|
||||
import { groupBy } from 'lodash';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { UseFormReturn, useWatch } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { FormSchemaType } from '@/pages/user-setting/compilation-templates/edit-template/schema';
|
||||
import { Checkbox } from '@/components/ui/checkbox';
|
||||
|
||||
type WikiPresetTreeItem = TreeDataItem & {
|
||||
_preset?: IWikiPreset;
|
||||
@@ -73,12 +74,34 @@ export function BlueprintsStep({
|
||||
`templates.${selectedTemplateIndex}.config.instruction` as const;
|
||||
const pageExamplePath =
|
||||
`templates.${selectedTemplateIndex}.config.page_example` as const;
|
||||
const useBlueprintPath =
|
||||
`templates.${selectedTemplateIndex}.config.use_blueprint` as const;
|
||||
|
||||
const pageExample = useWatch({
|
||||
control: form.control,
|
||||
name: pageExamplePath,
|
||||
});
|
||||
|
||||
const instruction = useWatch({
|
||||
control: form.control,
|
||||
name: instructionPath,
|
||||
});
|
||||
|
||||
const useBlueprint = useWatch({
|
||||
control: form.control,
|
||||
name: useBlueprintPath,
|
||||
});
|
||||
|
||||
const selectedTemplateName = useMemo(() => {
|
||||
if (!selectedItemId) return '';
|
||||
const selectedItem = treeData
|
||||
.flatMap((node) => node.children ?? [])
|
||||
.find((child) => child.id === selectedItemId);
|
||||
return selectedItem?.name ?? '';
|
||||
}, [treeData, selectedItemId]);
|
||||
|
||||
const hasTemplateData = Boolean(selectedItemId || instruction || pageExample);
|
||||
|
||||
const handleSelect = useCallback(
|
||||
(item?: TreeDataItem) => {
|
||||
if (!item || item.children) return;
|
||||
@@ -97,6 +120,14 @@ export function BlueprintsStep({
|
||||
[form, instructionPath, pageExamplePath],
|
||||
);
|
||||
|
||||
const handleToggleBlueprint = useCallback(
|
||||
(checked: boolean | 'indeterminate') => {
|
||||
if (checked === 'indeterminate') return;
|
||||
form.setValue(useBlueprintPath, checked, { shouldValidate: false });
|
||||
},
|
||||
[form, useBlueprintPath],
|
||||
);
|
||||
|
||||
const handlePageExampleChange = useCallback(
|
||||
(value: string) => {
|
||||
form.setValue(pageExamplePath, value, { shouldValidate: false });
|
||||
@@ -120,7 +151,20 @@ export function BlueprintsStep({
|
||||
</div>
|
||||
|
||||
<div className="flex-1 min-h-0 flex flex-col">
|
||||
{selectedItemId ? (
|
||||
{hasTemplateData && (
|
||||
<section className="flex justify-between p-5">
|
||||
<span>{selectedTemplateName}</span>
|
||||
<div className="space-x-2">
|
||||
<Checkbox
|
||||
id="use-blueprint-checkbox"
|
||||
checked={Boolean(useBlueprint)}
|
||||
onCheckedChange={handleToggleBlueprint}
|
||||
/>
|
||||
<span>{t('setting.useBlueprint')}</span>
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
{hasTemplateData ? (
|
||||
<div className="flex-1 min-h-0 flex flex-col p-5 gap-4 overflow-y-auto">
|
||||
<RAGFlowFormItem
|
||||
name={instructionPath}
|
||||
@@ -140,7 +184,7 @@ export function BlueprintsStep({
|
||||
<BlueprintsEmptyState />
|
||||
)}
|
||||
|
||||
<footer className="shrink-0 px-5 py-4 border-t border-border-button flex items-center justify-between">
|
||||
<footer className="shrink-0 px-5 py-4 border-t border-border-button flex items-center justify-end gap-5">
|
||||
<Button type="button" variant="outline" onClick={onBack}>
|
||||
{t('common.back')}
|
||||
</Button>
|
||||
|
||||
@@ -11,8 +11,8 @@ import {
|
||||
} from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { FormSchemaType } from '@/pages/user-setting/compilation-templates/edit-template/schema';
|
||||
import { getTypeOptionsFromBuiltinSection } from '@/pages/user-setting/compilation-templates/edit-template/utils';
|
||||
import { FormSchemaType } from '@/pages/user-setting/compilation-templates/create-next/schema';
|
||||
import { getTypeOptionsFromBuiltinSection } from '@/pages/user-setting/compilation-templates/create-next/utils';
|
||||
|
||||
import { FieldCard } from './field-card';
|
||||
|
||||
|
||||
@@ -12,10 +12,10 @@ import { UseFormReturn, useWatch } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { CompilationTemplateKind } from '@/constants/compilation';
|
||||
import { TreeTemplateFields } from '@/pages/user-setting/compilation-templates/edit-template/components/tree-template-fields';
|
||||
import { useTemplateKindChange } from '@/pages/user-setting/compilation-templates/edit-template/hooks/use-template-kind-change';
|
||||
import { FormSchemaType } from '@/pages/user-setting/compilation-templates/edit-template/schema';
|
||||
import { SectionTitleKeyMap } from '@/pages/user-setting/compilation-templates/edit-template/utils';
|
||||
import { TreeTemplateFields } from '@/pages/user-setting/compilation-templates/create-next/components/tree-template-fields';
|
||||
import { useTemplateKindChange } from '@/pages/user-setting/compilation-templates/create-next/hooks/use-template-kind-change';
|
||||
import { FormSchemaType } from '@/pages/user-setting/compilation-templates/create-next/schema';
|
||||
import { SectionTitleKeyMap } from '@/pages/user-setting/compilation-templates/create-next/utils';
|
||||
|
||||
import { useActiveSectionTab } from '../hooks/use-active-section-tab';
|
||||
import { useAvailableKindOptions } from '../hooks/use-available-kind-options';
|
||||
@@ -233,7 +233,7 @@ export function TemplateConfiguration({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer className="shrink-0 px-5 py-4 border-t border-border-button flex items-center justify-between">
|
||||
<footer className="shrink-0 px-5 py-4 border-t border-border-button flex items-center justify-end gap-5">
|
||||
<Button type="button" variant="outline" onClick={onBack}>
|
||||
{t('common.back')}
|
||||
</Button>
|
||||
|
||||
@@ -9,8 +9,10 @@ import {
|
||||
} from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { FormSchemaType } from '@/pages/user-setting/compilation-templates/edit-template/schema';
|
||||
import { DefaultTemplateValues } from '@/pages/user-setting/compilation-templates/edit-template/utils';
|
||||
import { formatKindLabel } from '@/utils/compilation-template-util';
|
||||
|
||||
import { FormSchemaType } from '@/pages/user-setting/compilation-templates/create-next/schema';
|
||||
import { DefaultTemplateValues } from '@/pages/user-setting/compilation-templates/create-next/utils';
|
||||
|
||||
import { useTemplateAddButton } from '../hooks/use-template-add-button';
|
||||
|
||||
@@ -86,7 +88,7 @@ function TemplateSidebarItem({
|
||||
</span>
|
||||
{template?.kind && (
|
||||
<span className="ml-2 shrink-0 text-text-secondary">
|
||||
{t(`knowledgeCompilation.kind.${template.kind}`)}
|
||||
{formatKindLabel(template.kind)}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -3,7 +3,7 @@ import {
|
||||
createEmptyField,
|
||||
getFieldKeyOrder,
|
||||
getTypeOptionsFromBuiltinSection,
|
||||
} from '@/pages/user-setting/compilation-templates/edit-template/utils';
|
||||
} from '@/pages/user-setting/compilation-templates/create-next/utils';
|
||||
import { useCallback, useEffect, useMemo } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { CompilationTemplateKind } from '@/constants/compilation';
|
||||
import { FormSchemaType } from '@/pages/user-setting/compilation-templates/edit-template/schema';
|
||||
import { FormSchemaType } from '@/pages/user-setting/compilation-templates/create-next/schema';
|
||||
import { useMemo } from 'react';
|
||||
import { UseFormReturn, useWatch } from 'react-hook-form';
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
import {
|
||||
isConfigMetaKey,
|
||||
sortSectionNames,
|
||||
} from '@/pages/user-setting/compilation-templates/edit-template/utils';
|
||||
} from '@/pages/user-setting/compilation-templates/create-next/utils';
|
||||
|
||||
export const useBuiltinTemplate = (
|
||||
builtins: ICompilationTemplateBuiltin[],
|
||||
|
||||
@@ -6,21 +6,22 @@ import {
|
||||
} from '@/hooks/use-compilation-template-group-request';
|
||||
import { useFetchBuiltinCompilationTemplates } from '@/hooks/use-compilation-template-request';
|
||||
import { useFetchDefaultModelDictionary } from '@/hooks/use-llm-request';
|
||||
import { isCreateCompilationTemplateGroup } from '@/utils/compilation-template-util';
|
||||
import { useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useParams } from 'react-router';
|
||||
|
||||
import { useCompilationTemplateGroupForm } from '@/pages/user-setting/compilation-templates/edit-template/hooks/use-compilation-template-group-form';
|
||||
import { useCompilationTemplateGroupSubmit } from '@/pages/user-setting/compilation-templates/edit-template/hooks/use-compilation-template-group-submit';
|
||||
import { formatKindLabel } from '@/utils/compilation-template-util';
|
||||
|
||||
import { useCompilationTemplateGroupForm } from '@/pages/user-setting/compilation-templates/create-next/hooks/use-compilation-template-group-form';
|
||||
import { useCompilationTemplateGroupSubmit } from '@/pages/user-setting/compilation-templates/create-next/hooks/use-compilation-template-group-submit';
|
||||
|
||||
export const useCreateNextCompilationTemplateGroup = () => {
|
||||
const { t } = useTranslation();
|
||||
const { id } = useParams<{ id: string }>();
|
||||
const { navigateToCompilationTemplates } = useNavigatePage();
|
||||
|
||||
const isCreate = !id || id === 'create';
|
||||
const isCreate = isCreateCompilationTemplateGroup(id);
|
||||
|
||||
const { data: detail } = useFetchCompilationTemplateGroup(id);
|
||||
const { data: detail } = useFetchCompilationTemplateGroup();
|
||||
const { data: builtins, kindOptions: builtinKindOptions } =
|
||||
useFetchBuiltinCompilationTemplates();
|
||||
const defaultModelDictionary = useFetchDefaultModelDictionary();
|
||||
@@ -34,9 +35,9 @@ export const useCreateNextCompilationTemplateGroup = () => {
|
||||
() =>
|
||||
builtinKindOptions.map((option) => ({
|
||||
...option,
|
||||
label: t(`knowledgeCompilation.kind.${option.value}`),
|
||||
label: formatKindLabel(option.value),
|
||||
})),
|
||||
[builtinKindOptions, t],
|
||||
[builtinKindOptions],
|
||||
);
|
||||
|
||||
const { form } = useCompilationTemplateGroupForm({
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useCallback } from 'react';
|
||||
import { ArrayPath, UseFormReturn } from 'react-hook-form';
|
||||
|
||||
import { FormSchemaType } from '@/pages/user-setting/compilation-templates/edit-template/schema';
|
||||
import { FormSchemaType } from '@/pages/user-setting/compilation-templates/create-next/schema';
|
||||
|
||||
export const useFieldArrayHandlers = (
|
||||
form: UseFormReturn<FormSchemaType>,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { CompilationTemplateKind } from '@/constants/compilation';
|
||||
import { FormSchemaType } from '@/pages/user-setting/compilation-templates/edit-template/schema';
|
||||
import { FormSchemaType } from '@/pages/user-setting/compilation-templates/create-next/schema';
|
||||
import { UseFormReturn, useWatch } from 'react-hook-form';
|
||||
|
||||
export const useTemplateAddButton = (
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useState } from 'react';
|
||||
import { UseFormReturn, useWatch } from 'react-hook-form';
|
||||
|
||||
import { FormSchemaType } from '@/pages/user-setting/compilation-templates/edit-template/schema';
|
||||
import { FormSchemaType } from '@/pages/user-setting/compilation-templates/create-next/schema';
|
||||
|
||||
export function useTemplatePreviewSheets(
|
||||
form: UseFormReturn<FormSchemaType>,
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
ICompilationTemplateBuiltin,
|
||||
ICompilationTemplateSection,
|
||||
} from '@/interfaces/database/compilation-template';
|
||||
import { FormSchemaType } from '@/pages/user-setting/compilation-templates/edit-template/schema';
|
||||
import { FormSchemaType } from '@/pages/user-setting/compilation-templates/create-next/schema';
|
||||
|
||||
export const useTemplateSectionData = (
|
||||
form: UseFormReturn<FormSchemaType>,
|
||||
|
||||
@@ -12,6 +12,7 @@ import { useCallback, useState } from 'react';
|
||||
import { useFieldArray, useWatch } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { useFetchCompilationTemplateGroup } from '@/hooks/use-compilation-template-group-request';
|
||||
import { BasicInfoStep } from './components/basic-info-step';
|
||||
import { BlueprintsStep } from './components/blueprints-step';
|
||||
import { TemplateConfiguration } from './components/template-configuration';
|
||||
@@ -26,6 +27,7 @@ export default function CreateNextCompilationTemplate() {
|
||||
|
||||
const { form, kindOptions, builtins, onSubmit, isCreate, isLoading } =
|
||||
useCreateNextCompilationTemplateGroup();
|
||||
const { data: group } = useFetchCompilationTemplateGroup();
|
||||
|
||||
const { fields, append, remove } = useFieldArray({
|
||||
control: form.control,
|
||||
@@ -77,7 +79,7 @@ export default function CreateNextCompilationTemplate() {
|
||||
<h2 className="font-medium text-text-secondary">
|
||||
{isCreate
|
||||
? t('setting.addTemplateGroup')
|
||||
: t('setting.editTemplateGroup')}
|
||||
: group?.name || t('setting.editTemplateGroup')}
|
||||
</h2>
|
||||
</header>
|
||||
|
||||
|
||||
@@ -16,6 +16,15 @@ export const buildRaptorConfigSchema = (t: (key: string) => string) =>
|
||||
rechunk: z.boolean().optional(),
|
||||
});
|
||||
|
||||
export const buildSynthesisSchema = () =>
|
||||
z
|
||||
.object({
|
||||
compile_kwd: z.string().optional(),
|
||||
enabled: z.boolean().optional(),
|
||||
example: z.string().optional(),
|
||||
})
|
||||
.passthrough();
|
||||
|
||||
export const buildTemplateSchema = (t: (key: string) => string) =>
|
||||
z.object({
|
||||
id: z.string().optional(),
|
||||
@@ -24,7 +33,13 @@ export const buildTemplateSchema = (t: (key: string) => string) =>
|
||||
llm_id: z.string().min(1, t('setting.llmForExtractionRequired')),
|
||||
kind: z.string().min(1, t('setting.templateKindRequired')),
|
||||
config: z.record(
|
||||
z.union([buildRaptorConfigSchema(t), buildSectionSchema(t), z.string()]),
|
||||
z.union([
|
||||
buildRaptorConfigSchema(t),
|
||||
buildSectionSchema(t),
|
||||
buildSynthesisSchema(),
|
||||
z.string(),
|
||||
z.boolean(),
|
||||
]),
|
||||
),
|
||||
});
|
||||
|
||||
@@ -15,6 +15,20 @@ import { FormSchemaType, TemplateSchemaType } from './schema';
|
||||
|
||||
export const DefaultFieldKeys = ['type', 'description', 'rule'];
|
||||
|
||||
export const splitExampleToBlueprintFields = (
|
||||
example: string,
|
||||
): { instruction: string; page_example: string } => {
|
||||
const trimmed = example.trim();
|
||||
const separatorIndex = trimmed.indexOf('\n\n');
|
||||
if (separatorIndex === -1) {
|
||||
return { instruction: trimmed, page_example: '' };
|
||||
}
|
||||
return {
|
||||
instruction: trimmed.slice(0, separatorIndex).trim(),
|
||||
page_example: trimmed.slice(separatorIndex + 2).trim(),
|
||||
};
|
||||
};
|
||||
|
||||
export const FieldKeyOrders = [
|
||||
DefaultFieldKeys,
|
||||
['statement', 'subject'],
|
||||
@@ -42,6 +56,7 @@ export const DefaultTemplateValues: TemplateSchemaType = {
|
||||
example: '',
|
||||
instruction: '',
|
||||
page_example: '',
|
||||
use_blueprint: false,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -60,6 +75,8 @@ export const isConfigMetaKey = (key: string) =>
|
||||
'example',
|
||||
'instruction',
|
||||
'page_example',
|
||||
'synthesis',
|
||||
'use_blueprint',
|
||||
].includes(key);
|
||||
|
||||
export const createEmptyField = (keys: string[]) =>
|
||||
@@ -87,6 +104,10 @@ export const buildConfigFromBuiltin = (
|
||||
kind: string,
|
||||
llmId: string,
|
||||
): TemplateSchemaType['config'] => {
|
||||
const example =
|
||||
typeof builtinTemplate.config?.example === 'string'
|
||||
? builtinTemplate.config.example
|
||||
: '';
|
||||
const sections: TemplateSchemaType['config'] = {
|
||||
kind,
|
||||
llm_id: llmId,
|
||||
@@ -98,8 +119,24 @@ export const buildConfigFromBuiltin = (
|
||||
typeof builtinTemplate.config?.example === 'string'
|
||||
? builtinTemplate.config.example
|
||||
: '',
|
||||
...(typeof builtinTemplate.config?.synthesis === 'object' &&
|
||||
builtinTemplate.config?.synthesis !== null
|
||||
? {
|
||||
synthesis: builtinTemplate.config
|
||||
.synthesis as TemplateSchemaType['config']['synthesis'],
|
||||
}
|
||||
: {}),
|
||||
use_blueprint:
|
||||
kind === CompilationTemplateKind.Artifacts && example.length > 0,
|
||||
};
|
||||
|
||||
if (kind === CompilationTemplateKind.Artifacts && example.length > 0) {
|
||||
const { instruction, page_example } =
|
||||
splitExampleToBlueprintFields(example);
|
||||
sections.instruction = instruction;
|
||||
sections.page_example = page_example;
|
||||
}
|
||||
|
||||
if (kind === CompilationTemplateKind.Tree) {
|
||||
const builtinRaptor: ICompilationTemplateRaptorConfig =
|
||||
builtinTemplate.config?.raptor ?? {};
|
||||
@@ -128,13 +165,29 @@ export const transformDetailToForm = (
|
||||
detail: ICompilationTemplate,
|
||||
): TemplateSchemaType => {
|
||||
const config = detail.config ?? {};
|
||||
const example = typeof config.example === 'string' ? config.example : '';
|
||||
const base: TemplateSchemaType['config'] = {
|
||||
kind: config.kind ?? '',
|
||||
llm_id: config.llm_id ?? '',
|
||||
global_rules: config.global_rules ?? '',
|
||||
example: typeof config.example === 'string' ? config.example : '',
|
||||
...(typeof config.synthesis === 'object' && config.synthesis !== null
|
||||
? {
|
||||
synthesis:
|
||||
config.synthesis as TemplateSchemaType['config']['synthesis'],
|
||||
}
|
||||
: {}),
|
||||
use_blueprint:
|
||||
detail.kind === CompilationTemplateKind.Artifacts && example.length > 0,
|
||||
};
|
||||
|
||||
if (detail.kind === CompilationTemplateKind.Artifacts && example.length > 0) {
|
||||
const { instruction, page_example } =
|
||||
splitExampleToBlueprintFields(example);
|
||||
base.instruction = instruction;
|
||||
base.page_example = page_example;
|
||||
}
|
||||
|
||||
if (detail.kind === CompilationTemplateKind.Tree) {
|
||||
const raptor: ICompilationTemplateRaptorConfig = config.raptor ?? {};
|
||||
return {
|
||||
@@ -196,6 +249,10 @@ export const transformTemplateToPayload = (template: TemplateSchemaType) => {
|
||||
Object.entries(template.config).forEach(([key, value]) => {
|
||||
if (key === 'kind' || key === 'llm_id') return;
|
||||
if (key === 'instruction' || key === 'page_example') return;
|
||||
if (key === 'synthesis') {
|
||||
config[key] = value as ICompilationTemplateConfigRequest[string];
|
||||
return;
|
||||
}
|
||||
if (isConfigMetaKey(key)) {
|
||||
if (typeof value === 'string') config[key] = value;
|
||||
} else {
|
||||
@@ -204,9 +261,13 @@ export const transformTemplateToPayload = (template: TemplateSchemaType) => {
|
||||
});
|
||||
|
||||
if (template.kind === CompilationTemplateKind.Artifacts) {
|
||||
const instruction = String(template.config.instruction ?? '').trim();
|
||||
const pageExample = String(template.config.page_example ?? '').trim();
|
||||
config.example = [instruction, pageExample].filter(Boolean).join('\n\n');
|
||||
if (template.config.use_blueprint) {
|
||||
const instruction = String(template.config.instruction ?? '').trim();
|
||||
const pageExample = String(template.config.page_example ?? '').trim();
|
||||
config.example = [instruction, pageExample].filter(Boolean).join('\n\n');
|
||||
} else {
|
||||
config.example = '';
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -1,174 +0,0 @@
|
||||
import { Collapse } from '@/components/collapse';
|
||||
import { SelectWithSearch } from '@/components/originui/select-with-search';
|
||||
import { RAGFlowFormItem } from '@/components/ragflow-form';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card, CardContent } from '@/components/ui/card';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import { ICompilationTemplateSection } from '@/interfaces/database/compilation-template';
|
||||
import { startCase } from 'lodash';
|
||||
import { Plus, Trash2 } from 'lucide-react';
|
||||
import { useMemo } from 'react';
|
||||
import {
|
||||
ArrayPath,
|
||||
Path,
|
||||
useFieldArray,
|
||||
useFormContext,
|
||||
} from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { useFieldTypeChange } from '../hooks/use-field-type-change';
|
||||
import { FormSchemaType } from '../schema';
|
||||
import { createEmptyField, FieldLabelKeyMap } from '../utils';
|
||||
|
||||
type FieldsSectionProps = {
|
||||
name: Path<FormSchemaType>;
|
||||
title: string;
|
||||
fieldKeys: string[];
|
||||
defaultOpen?: boolean;
|
||||
builtinSection?: ICompilationTemplateSection;
|
||||
};
|
||||
|
||||
export function FieldsSection({
|
||||
name,
|
||||
title,
|
||||
fieldKeys,
|
||||
defaultOpen = false,
|
||||
builtinSection,
|
||||
}: FieldsSectionProps) {
|
||||
const { t } = useTranslation();
|
||||
const form = useFormContext<FormSchemaType>();
|
||||
const fieldsPath = `${name}.fields` as ArrayPath<FormSchemaType>;
|
||||
|
||||
const { fields, append, remove } = useFieldArray({
|
||||
name: fieldsPath,
|
||||
control: form.control,
|
||||
});
|
||||
|
||||
const typeOptions = useMemo(() => {
|
||||
const typeSet = new Set<string>();
|
||||
builtinSection?.fields?.forEach((field) => {
|
||||
if (field.type) typeSet.add(field.type);
|
||||
});
|
||||
return Array.from(typeSet)
|
||||
.sort()
|
||||
.map((value) => ({ label: value, value }));
|
||||
}, [builtinSection]);
|
||||
|
||||
return (
|
||||
<Collapse defaultOpen={defaultOpen} title={title}>
|
||||
<section className="space-y-4">
|
||||
<RAGFlowFormItem
|
||||
name={`${name}.description`}
|
||||
label={t('setting.description')}
|
||||
>
|
||||
<Textarea
|
||||
placeholder={t('setting.descriptionPlaceholder')}
|
||||
rows={3}
|
||||
/>
|
||||
</RAGFlowFormItem>
|
||||
|
||||
<section className="space-y-3">
|
||||
{fields.map((field, index) => (
|
||||
<Card
|
||||
key={field.id}
|
||||
className="border-border-button bg-transparent transition-colors has-[button:hover]:border-state-error"
|
||||
>
|
||||
<CardContent className="p-4 space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-sm text-text-secondary">
|
||||
{t('setting.field')} #{index + 1}
|
||||
</span>
|
||||
{fields.length > 1 && (
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="icon-xs"
|
||||
onClick={() => remove(index)}
|
||||
className="text-text-secondary hover:text-state-error"
|
||||
>
|
||||
<Trash2 className="size-4" />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{fieldKeys.map((key) => (
|
||||
<FieldFormItem
|
||||
key={key}
|
||||
fieldKey={key}
|
||||
fieldName={`${fieldsPath}.${index}.${key}`}
|
||||
typeOptions={typeOptions}
|
||||
builtinSection={builtinSection}
|
||||
fieldsPath={fieldsPath}
|
||||
index={index}
|
||||
/>
|
||||
))}
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</section>
|
||||
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
onClick={() => append(createEmptyField(fieldKeys))}
|
||||
>
|
||||
<Plus className="size-4 mr-2" />
|
||||
{t('setting.addField')}
|
||||
</Button>
|
||||
</section>
|
||||
</Collapse>
|
||||
);
|
||||
}
|
||||
|
||||
type FieldFormItemProps = {
|
||||
fieldKey: string;
|
||||
fieldName: string;
|
||||
typeOptions: { label: string; value: string }[];
|
||||
builtinSection?: ICompilationTemplateSection;
|
||||
fieldsPath: ArrayPath<FormSchemaType>;
|
||||
index: number;
|
||||
};
|
||||
|
||||
function FieldFormItem({
|
||||
fieldKey,
|
||||
fieldName,
|
||||
typeOptions,
|
||||
builtinSection,
|
||||
fieldsPath,
|
||||
index,
|
||||
}: FieldFormItemProps) {
|
||||
const { t } = useTranslation();
|
||||
const form = useFormContext<FormSchemaType>();
|
||||
const handleTypeChange = useFieldTypeChange({
|
||||
form,
|
||||
builtinSection,
|
||||
fieldsPath: fieldsPath as `templates.${number}.config.${string}.fields`,
|
||||
index,
|
||||
});
|
||||
|
||||
return (
|
||||
<RAGFlowFormItem
|
||||
name={fieldName}
|
||||
label={
|
||||
FieldLabelKeyMap[fieldKey]
|
||||
? t(FieldLabelKeyMap[fieldKey])
|
||||
: startCase(fieldKey)
|
||||
}
|
||||
required
|
||||
>
|
||||
{fieldKey === 'type' ? (
|
||||
(field) => (
|
||||
<SelectWithSearch
|
||||
options={typeOptions}
|
||||
value={field.value}
|
||||
onChange={(value) => handleTypeChange(field, value)}
|
||||
disabled={field.disabled}
|
||||
placeholder={t('common.selectPlaceholder')}
|
||||
/>
|
||||
)
|
||||
) : (
|
||||
<Textarea placeholder={t('setting.descriptionPlaceholder')} rows={2} />
|
||||
)}
|
||||
</RAGFlowFormItem>
|
||||
);
|
||||
}
|
||||
@@ -1,213 +0,0 @@
|
||||
import { Collapse } from '@/components/collapse';
|
||||
import { ModelTreeSelectFormField } from '@/components/model-tree-select';
|
||||
import { SelectWithSearch } from '@/components/originui/select-with-search';
|
||||
import { RAGFlowFormItem } from '@/components/ragflow-form';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card, CardContent } from '@/components/ui/card';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import {
|
||||
ICompilationTemplateBuiltin,
|
||||
ICompilationTemplateSection,
|
||||
} from '@/interfaces/database/compilation-template';
|
||||
import { Trash2 } from 'lucide-react';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { UseFormReturn, useWatch } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { CompilationTemplateKind } from '@/constants/compilation';
|
||||
import { useTemplateKindChange } from '../hooks/use-template-kind-change';
|
||||
import { FormSchemaType } from '../schema';
|
||||
import {
|
||||
DefaultFieldKeys,
|
||||
getFieldKeyOrder,
|
||||
isConfigMetaKey,
|
||||
SectionTitleKeyMap,
|
||||
sortSectionNames,
|
||||
} from '../utils';
|
||||
import { FieldsSection } from './fields-section';
|
||||
import { TreeTemplateFields } from './tree-template-fields';
|
||||
|
||||
type TemplateCardProps = {
|
||||
index: number;
|
||||
form: UseFormReturn<FormSchemaType>;
|
||||
kindOptions: { label: string; value: string }[];
|
||||
builtins: ICompilationTemplateBuiltin[];
|
||||
onRemove: (index: number) => void;
|
||||
canRemove: boolean;
|
||||
};
|
||||
|
||||
export function TemplateCard({
|
||||
index,
|
||||
form,
|
||||
kindOptions,
|
||||
builtins,
|
||||
onRemove,
|
||||
canRemove,
|
||||
}: TemplateCardProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const kind = useWatch({
|
||||
control: form.control,
|
||||
name: `templates.${index}.kind`,
|
||||
});
|
||||
|
||||
const builtinTemplate = useMemo(
|
||||
() => builtins.find((template) => template.kind === kind),
|
||||
[builtins, kind],
|
||||
);
|
||||
|
||||
const sectionNames = useMemo(() => {
|
||||
const names = Object.keys(builtinTemplate?.config ?? {}).filter(
|
||||
(key) => !isConfigMetaKey(key),
|
||||
);
|
||||
return sortSectionNames(names);
|
||||
}, [builtinTemplate]);
|
||||
|
||||
const handleRemove = useCallback(() => {
|
||||
onRemove(index);
|
||||
}, [index, onRemove]);
|
||||
|
||||
const handleRemoveClick = useCallback(
|
||||
(e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
e.stopPropagation();
|
||||
handleRemove();
|
||||
},
|
||||
[handleRemove],
|
||||
);
|
||||
|
||||
const handleKindChange = useTemplateKindChange({
|
||||
form,
|
||||
index,
|
||||
builtins,
|
||||
});
|
||||
|
||||
return (
|
||||
<Card className="border-border-button">
|
||||
<CardContent className="p-4">
|
||||
<Collapse
|
||||
title={
|
||||
<div>
|
||||
<span className="text-sm font-medium text-text-primary">
|
||||
{t('setting.template')} #{index + 1}
|
||||
</span>
|
||||
<span className="ml-2 text-xs text-text-secondary">
|
||||
{kindOptions.find((option) => option.value === kind)?.label}
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
rightContent={
|
||||
canRemove && (
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="icon-xs"
|
||||
onClick={handleRemoveClick}
|
||||
className="text-text-secondary hover:text-state-error"
|
||||
>
|
||||
<Trash2 className="size-4" />
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
defaultOpen
|
||||
>
|
||||
<div className="space-y-4">
|
||||
{kind !== CompilationTemplateKind.Tree && (
|
||||
<RAGFlowFormItem
|
||||
name={`templates.${index}.name`}
|
||||
label={t('setting.templateName')}
|
||||
>
|
||||
<Input placeholder={t('common.namePlaceholder')} />
|
||||
</RAGFlowFormItem>
|
||||
)}
|
||||
|
||||
<RAGFlowFormItem
|
||||
name={`templates.${index}.description`}
|
||||
label={t('setting.templateDescription')}
|
||||
>
|
||||
<Textarea
|
||||
placeholder={t('common.descriptionPlaceholder')}
|
||||
rows={2}
|
||||
/>
|
||||
</RAGFlowFormItem>
|
||||
|
||||
<ModelTreeSelectFormField
|
||||
name={`templates.${index}.llm_id`}
|
||||
label={t('setting.llmForExtraction')}
|
||||
required
|
||||
/>
|
||||
|
||||
<RAGFlowFormItem
|
||||
name={`templates.${index}.kind`}
|
||||
label={t('knowledgeCompilation.builtinTemplates')}
|
||||
required
|
||||
>
|
||||
{(field) => (
|
||||
<SelectWithSearch
|
||||
value={field.value}
|
||||
onChange={(value) => handleKindChange(field, value)}
|
||||
disabled={field.disabled}
|
||||
options={kindOptions}
|
||||
placeholder={t('common.selectPlaceholder')}
|
||||
/>
|
||||
)}
|
||||
</RAGFlowFormItem>
|
||||
|
||||
{kind === CompilationTemplateKind.Artifacts && (
|
||||
<RAGFlowFormItem
|
||||
name={`templates.${index}.config.example`}
|
||||
label={t('setting.example')}
|
||||
>
|
||||
<Textarea
|
||||
placeholder={t('setting.examplePlaceholder')}
|
||||
rows={4}
|
||||
/>
|
||||
</RAGFlowFormItem>
|
||||
)}
|
||||
|
||||
{kind === CompilationTemplateKind.Tree ? (
|
||||
<TreeTemplateFields index={index} />
|
||||
) : (
|
||||
sectionNames.map((sectionName, sectionIndex) => {
|
||||
const builtinSection = builtinTemplate?.config?.[sectionName];
|
||||
|
||||
const firstField = (
|
||||
builtinSection as ICompilationTemplateSection | undefined
|
||||
)?.fields?.[0];
|
||||
const firstFieldKeys = firstField
|
||||
? Object.keys(firstField)
|
||||
: DefaultFieldKeys;
|
||||
const fieldKeys = getFieldKeyOrder(firstFieldKeys);
|
||||
|
||||
return (
|
||||
<FieldsSection
|
||||
key={sectionName}
|
||||
name={
|
||||
`templates.${index}.config.${sectionName}` as `templates.${number}.config.${string}`
|
||||
}
|
||||
title={t(SectionTitleKeyMap[sectionName] ?? sectionName)}
|
||||
fieldKeys={fieldKeys}
|
||||
defaultOpen={sectionIndex === 0}
|
||||
builtinSection={
|
||||
builtinSection as ICompilationTemplateSection | undefined
|
||||
}
|
||||
/>
|
||||
);
|
||||
})
|
||||
)}
|
||||
|
||||
<RAGFlowFormItem
|
||||
name={`templates.${index}.config.global_rules`}
|
||||
label={t('setting.globalRules')}
|
||||
>
|
||||
<Textarea
|
||||
placeholder={t('setting.globalRulesPlaceholder')}
|
||||
rows={4}
|
||||
/>
|
||||
</RAGFlowFormItem>
|
||||
</div>
|
||||
</Collapse>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
import {
|
||||
useCreateCompilationTemplateGroup,
|
||||
useFetchCompilationTemplateGroup,
|
||||
useUpdateCompilationTemplateGroup,
|
||||
} from '@/hooks/use-compilation-template-group-request';
|
||||
import { useFetchBuiltinCompilationTemplates } from '@/hooks/use-compilation-template-request';
|
||||
import { useFetchDefaultModelDictionary } from '@/hooks/use-llm-request';
|
||||
import { useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useParams } from 'react-router';
|
||||
|
||||
export const useCompilationTemplateGroupData = () => {
|
||||
const { id } = useParams<{ id: string }>();
|
||||
const isCreate = id === 'create';
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { data: detail } = useFetchCompilationTemplateGroup(id);
|
||||
const { data: builtins, kindOptions: builtinKindOptions } =
|
||||
useFetchBuiltinCompilationTemplates();
|
||||
const defaultModelDictionary = useFetchDefaultModelDictionary();
|
||||
|
||||
const { createGroup, loading: createLoading } =
|
||||
useCreateCompilationTemplateGroup();
|
||||
const { updateGroup, loading: updateLoading } =
|
||||
useUpdateCompilationTemplateGroup();
|
||||
|
||||
const kindOptions = useMemo(
|
||||
() =>
|
||||
builtinKindOptions.map((option) => ({
|
||||
...option,
|
||||
label: t(`knowledgeCompilation.kind.${option.value}`),
|
||||
})),
|
||||
[builtinKindOptions, t],
|
||||
);
|
||||
|
||||
const isLoading = createLoading || updateLoading;
|
||||
|
||||
return {
|
||||
id,
|
||||
isCreate,
|
||||
detail,
|
||||
builtins,
|
||||
kindOptions,
|
||||
defaultModelDictionary,
|
||||
createGroup,
|
||||
updateGroup,
|
||||
isLoading,
|
||||
};
|
||||
};
|
||||
@@ -1,51 +0,0 @@
|
||||
import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks';
|
||||
import { useWatch } from 'react-hook-form';
|
||||
|
||||
import { DefaultValues } from '../utils';
|
||||
import { useCompilationTemplateGroupData } from './use-compilation-template-group-data';
|
||||
import { useCompilationTemplateGroupForm } from './use-compilation-template-group-form';
|
||||
import { useCompilationTemplateGroupSubmit } from './use-compilation-template-group-submit';
|
||||
|
||||
export const useEditCompilationTemplateGroup = () => {
|
||||
const {
|
||||
id,
|
||||
isCreate,
|
||||
detail,
|
||||
builtins,
|
||||
kindOptions,
|
||||
defaultModelDictionary,
|
||||
createGroup,
|
||||
updateGroup,
|
||||
isLoading,
|
||||
} = useCompilationTemplateGroupData();
|
||||
|
||||
const { navigateToCompilationTemplates } = useNavigatePage();
|
||||
|
||||
const { form } = useCompilationTemplateGroupForm({
|
||||
detail,
|
||||
defaultLlmId: defaultModelDictionary.llm_id,
|
||||
isCreate,
|
||||
});
|
||||
|
||||
const watchedValues = useWatch({ control: form.control }) ?? DefaultValues;
|
||||
|
||||
const { onSubmit } = useCompilationTemplateGroupSubmit({
|
||||
isCreate,
|
||||
id,
|
||||
createGroup,
|
||||
updateGroup,
|
||||
onSuccess: navigateToCompilationTemplates,
|
||||
});
|
||||
|
||||
return {
|
||||
id,
|
||||
isCreate,
|
||||
form,
|
||||
watchedValues,
|
||||
kindOptions,
|
||||
builtins,
|
||||
onSubmit,
|
||||
isLoading,
|
||||
navigateToCompilationTemplates,
|
||||
};
|
||||
};
|
||||
@@ -1,66 +0,0 @@
|
||||
import {
|
||||
ICompilationTemplateField,
|
||||
ICompilationTemplateSection,
|
||||
} from '@/interfaces/database/compilation-template';
|
||||
import { useCallback } from 'react';
|
||||
import { UseFormReturn } from 'react-hook-form';
|
||||
|
||||
import { FormSchemaType } from '../schema';
|
||||
|
||||
type FieldLike = {
|
||||
value: string;
|
||||
onChange: (value: string) => void;
|
||||
};
|
||||
|
||||
type UseFieldTypeChangeOptions = {
|
||||
form: UseFormReturn<FormSchemaType>;
|
||||
builtinSection?: ICompilationTemplateSection;
|
||||
fieldsPath: `templates.${number}.config.${string}.fields`;
|
||||
index: number;
|
||||
};
|
||||
|
||||
const FIELD_SYNC_KEYS: Array<keyof ICompilationTemplateField> = [
|
||||
'description',
|
||||
'rule',
|
||||
];
|
||||
|
||||
export const useFieldTypeChange = ({
|
||||
form,
|
||||
builtinSection,
|
||||
fieldsPath,
|
||||
index,
|
||||
}: UseFieldTypeChangeOptions) => {
|
||||
return useCallback(
|
||||
(field: FieldLike, value: string) => {
|
||||
if (!value || value === field.value) {
|
||||
field.onChange(value);
|
||||
return;
|
||||
}
|
||||
|
||||
const matchedField = builtinSection?.fields?.find(
|
||||
(builtinField) => builtinField.type === value,
|
||||
);
|
||||
|
||||
const currentField = form.getValues(`${fieldsPath}.${index}`);
|
||||
const nextField: Record<string, string> = {
|
||||
...currentField,
|
||||
type: value,
|
||||
};
|
||||
|
||||
if (matchedField) {
|
||||
FIELD_SYNC_KEYS.forEach((key) => {
|
||||
if (key in matchedField) {
|
||||
nextField[key] = matchedField[key] ?? '';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
form.setValue(`${fieldsPath}.${index}`, nextField, {
|
||||
shouldValidate: false,
|
||||
shouldDirty: true,
|
||||
shouldTouch: true,
|
||||
});
|
||||
},
|
||||
[builtinSection, fieldsPath, form, index],
|
||||
);
|
||||
};
|
||||
@@ -1,183 +0,0 @@
|
||||
import BackButton from '@/components/back-button';
|
||||
import { RAGFlowFormItem } from '@/components/ragflow-form';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Form } from '@/components/ui/form';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import { Routes } from '@/routes';
|
||||
import { Plus } from 'lucide-react';
|
||||
import { useMemo } from 'react';
|
||||
import { useFieldArray, useWatch } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import JsonView from 'react18-json-view';
|
||||
|
||||
import { CompilationTemplateKind } from '@/constants/compilation';
|
||||
import { TemplateCard } from './components/template-card';
|
||||
import { useEditCompilationTemplateGroup } from './hooks/use-edit-compilation-template-group';
|
||||
import { DefaultTemplateValues } from './utils';
|
||||
|
||||
export default function EditCompilationTemplate() {
|
||||
const { t } = useTranslation();
|
||||
const {
|
||||
isCreate,
|
||||
form,
|
||||
watchedValues,
|
||||
kindOptions,
|
||||
builtins,
|
||||
onSubmit,
|
||||
isLoading,
|
||||
navigateToCompilationTemplates,
|
||||
} = useEditCompilationTemplateGroup();
|
||||
|
||||
const { fields, append, remove } = useFieldArray({
|
||||
control: form.control,
|
||||
name: 'templates',
|
||||
});
|
||||
|
||||
const watchedTemplates = useWatch({
|
||||
control: form.control,
|
||||
name: 'templates',
|
||||
});
|
||||
|
||||
const isAddTemplateDisabled = watchedTemplates?.some(
|
||||
(template) => template.kind === CompilationTemplateKind.Artifacts,
|
||||
);
|
||||
|
||||
const hasNonArtifactsTemplate = watchedTemplates?.some(
|
||||
(template) =>
|
||||
template.kind && template.kind !== CompilationTemplateKind.Artifacts,
|
||||
);
|
||||
|
||||
const availableKindOptions = useMemo(
|
||||
() =>
|
||||
hasNonArtifactsTemplate
|
||||
? kindOptions.filter(
|
||||
(option) => option.value !== CompilationTemplateKind.Artifacts,
|
||||
)
|
||||
: kindOptions,
|
||||
[hasNonArtifactsTemplate, kindOptions],
|
||||
);
|
||||
|
||||
const handleAddTemplate = () => {
|
||||
const firstTemplateLlmId = form.getValues('templates.0.llm_id');
|
||||
append({
|
||||
...DefaultTemplateValues,
|
||||
llm_id: firstTemplateLlmId || '',
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<section className="h-full flex flex-col">
|
||||
<header className="shrink-0 px-5 py-4 border-b border-border-button flex flex-col items-start gap-2">
|
||||
<BackButton
|
||||
to={`${Routes.UserSetting}${Routes.CompilationTemplates}`}
|
||||
/>
|
||||
<h2 className="text-xl font-medium text-text-primary">
|
||||
{isCreate
|
||||
? t('setting.addTemplateGroup')
|
||||
: t('setting.editTemplateGroup')}
|
||||
</h2>
|
||||
</header>
|
||||
|
||||
<div className="flex-1 min-h-0 flex">
|
||||
<Form {...form}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
className="flex-1 min-w-0 flex flex-col min-h-0"
|
||||
>
|
||||
<div className="flex-1 min-h-0 overflow-y-auto p-5">
|
||||
<div className="max-w-2xl space-y-6 mx-auto">
|
||||
<RAGFlowFormItem
|
||||
name="name"
|
||||
label={t('setting.groupName')}
|
||||
required
|
||||
>
|
||||
<Input placeholder={t('common.namePlaceholder')} />
|
||||
</RAGFlowFormItem>
|
||||
|
||||
<RAGFlowFormItem
|
||||
name="description"
|
||||
label={t('setting.groupDescription')}
|
||||
>
|
||||
<Textarea
|
||||
placeholder={t('common.descriptionPlaceholder')}
|
||||
rows={3}
|
||||
/>
|
||||
</RAGFlowFormItem>
|
||||
|
||||
<section className="space-y-4">
|
||||
{fields.map((field, index) => (
|
||||
<TemplateCard
|
||||
key={field.id}
|
||||
index={index}
|
||||
form={form}
|
||||
kindOptions={availableKindOptions}
|
||||
builtins={builtins}
|
||||
onRemove={remove}
|
||||
canRemove={fields.length > 1}
|
||||
/>
|
||||
))}
|
||||
</section>
|
||||
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
onClick={handleAddTemplate}
|
||||
disabled={isAddTemplateDisabled}
|
||||
>
|
||||
<Plus className="size-4 mr-2" />
|
||||
{t('setting.addTemplate')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="shrink-0 px-5 py-4 border-t border-border-button flex items-center justify-end gap-3">
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
onClick={navigateToCompilationTemplates}
|
||||
disabled={isLoading}
|
||||
>
|
||||
{t('common.cancel')}
|
||||
</Button>
|
||||
<Button type="submit" loading={isLoading} disabled={isLoading}>
|
||||
{t('common.confirm')}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
|
||||
<aside className="flex-1 min-w-0 border-l border-border-button p-5 hidden lg:flex flex-col">
|
||||
<Tabs defaultValue="json" className="h-full flex flex-col min-h-0">
|
||||
<TabsList className="mb-3 self-start shrink-0">
|
||||
<TabsTrigger value="json">JSON</TabsTrigger>
|
||||
<TabsTrigger value="processFlow">
|
||||
{t('setting.processFlow')}
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent
|
||||
value="json"
|
||||
className="mt-0 flex-1 min-h-0 overflow-y-auto"
|
||||
>
|
||||
<JsonView
|
||||
src={watchedValues}
|
||||
displaySize
|
||||
collapseStringsAfterLength={100000000000}
|
||||
className="w-full h-full break-words p-2 bg-muted rounded-md"
|
||||
/>
|
||||
</TabsContent>
|
||||
<TabsContent
|
||||
value="processFlow"
|
||||
className="mt-0 flex-1 min-h-0 overflow-y-auto"
|
||||
>
|
||||
<div className="w-full h-full flex items-center justify-center text-sm text-text-secondary">
|
||||
{t('setting.processFlowComingSoon')}
|
||||
</div>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</aside>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -6,7 +6,8 @@ import { CompilationTemplateScope } from '@/constants/compilation';
|
||||
import { ICompilationTemplateGroup } from '@/interfaces/database/compilation-template';
|
||||
import { Database, FileText, LucideIcon } from 'lucide-react';
|
||||
import { useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { formatKindLabel } from '@/utils/compilation-template-util';
|
||||
import { TemplateDropdown } from './template-dropdown';
|
||||
|
||||
type TemplateCardProps = {
|
||||
@@ -27,8 +28,6 @@ function ScopeIcon({ scope }: { scope?: string }) {
|
||||
}
|
||||
|
||||
export function TemplateCard({ data, onClick, onDelete }: TemplateCardProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const kinds = useMemo(
|
||||
() => Array.from(new Set((data.templates ?? []).map((item) => item.kind))),
|
||||
[data.templates],
|
||||
@@ -64,7 +63,7 @@ export function TemplateCard({ data, onClick, onDelete }: TemplateCardProps) {
|
||||
<div className="flex flex-wrap gap-2 mt-2">
|
||||
{kinds.map((kind) => (
|
||||
<Badge key={kind} variant="secondary">
|
||||
{t(`knowledgeCompilation.kind.${kind}`, kind)}
|
||||
{formatKindLabel(kind)}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -44,7 +44,6 @@ export enum Routes {
|
||||
Model = '/model',
|
||||
Prompt = '/prompt',
|
||||
CompilationTemplates = '/compilation-templates',
|
||||
CompilationTemplate = `${CompilationTemplates}/:id`,
|
||||
CompilationTemplatesCreateNext = '/compilation-templates/create-next',
|
||||
DataSource = '/data-source',
|
||||
DataSourceDetailPage = '/data-source-detail-page',
|
||||
@@ -318,12 +317,6 @@ const routeConfigOptions = [
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: Routes.CompilationTemplate,
|
||||
layout: false,
|
||||
Component: () =>
|
||||
import('@/pages/user-setting/compilation-templates/edit-template'),
|
||||
},
|
||||
{
|
||||
path: Routes.CompilationTemplatesCreateNext,
|
||||
layout: false,
|
||||
|
||||
10
web/src/utils/compilation-template-util.ts
Normal file
10
web/src/utils/compilation-template-util.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { capitalize, lowerCase } from 'lodash';
|
||||
|
||||
export function formatKindLabel(kind: string): string {
|
||||
return capitalize(lowerCase(kind));
|
||||
}
|
||||
export const isCreateCompilationTemplateGroup = (
|
||||
id?: string,
|
||||
): id is undefined => {
|
||||
return !id;
|
||||
};
|
||||
Reference in New Issue
Block a user