mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-08-03 06:17:29 +08:00
Feat: Search for knowledge-base-level graph nodes. (#17444)
This commit is contained in:
@@ -91,7 +91,7 @@ function TemplateSidebarItem({
|
||||
</span>
|
||||
{template?.kind && (
|
||||
<span className="ml-2 shrink-0 text-text-secondary">
|
||||
{formatKindLabel(template.kind)}
|
||||
{formatKindLabel(t, template.kind)}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -8,6 +8,7 @@ import { useFetchBuiltinCompilationTemplates } from '@/hooks/use-compilation-tem
|
||||
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 { formatKindLabel } from '@/utils/compilation-template-util';
|
||||
@@ -18,6 +19,7 @@ import { useCompilationTemplateGroupSubmit } from '@/pages/user-setting/compilat
|
||||
export const useCreateNextCompilationTemplateGroup = () => {
|
||||
const { id } = useParams<{ id: string }>();
|
||||
const { navigateToCompilationTemplates } = useNavigatePage();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const isCreate = isCreateCompilationTemplateGroup(id);
|
||||
|
||||
@@ -35,9 +37,9 @@ export const useCreateNextCompilationTemplateGroup = () => {
|
||||
() =>
|
||||
builtinKindOptions.map((option) => ({
|
||||
...option,
|
||||
label: formatKindLabel(option.value),
|
||||
label: formatKindLabel(t, option.value),
|
||||
})),
|
||||
[builtinKindOptions],
|
||||
[builtinKindOptions, t],
|
||||
);
|
||||
|
||||
const { form } = useCompilationTemplateGroupForm({
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
import { SelectWithSearch } from '@/components/originui/select-with-search';
|
||||
import { RAGFlowFormItem } from '@/components/ragflow-form';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Form } from '@/components/ui/form';
|
||||
import { Modal } from '@/components/ui/modal/modal';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import { ICompilationTemplateSection } from '@/interfaces/database/compilation-template';
|
||||
import { startCase } from 'lodash';
|
||||
import { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { FieldLabelKeyMap } from '../utils';
|
||||
|
||||
import { useAddFieldForm } from '../hooks/use-add-field-form';
|
||||
|
||||
type AddFieldModalProps = {
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
sectionName: string;
|
||||
builtinSection?: ICompilationTemplateSection;
|
||||
initialField?: Record<string, string>;
|
||||
onAdd: (field: Record<string, string>) => void;
|
||||
};
|
||||
|
||||
export function AddFieldModal({
|
||||
open,
|
||||
onOpenChange,
|
||||
sectionName,
|
||||
builtinSection,
|
||||
initialField,
|
||||
onAdd,
|
||||
}: AddFieldModalProps) {
|
||||
const { t } = useTranslation();
|
||||
const {
|
||||
form,
|
||||
fieldKeys,
|
||||
hasTypeField,
|
||||
typeOptions,
|
||||
handleTypeChange,
|
||||
handleSubmit,
|
||||
} = useAddFieldForm({
|
||||
open,
|
||||
builtinSection,
|
||||
initialField,
|
||||
});
|
||||
|
||||
const nonTypeKeys = fieldKeys.filter((key) => key !== 'type');
|
||||
|
||||
const handleClose = useCallback(() => {
|
||||
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);
|
||||
},
|
||||
[t],
|
||||
);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
open={open}
|
||||
onOpenChange={onOpenChange}
|
||||
title={`${initialField ? t('setting.editFieldModalTitle') : t('setting.addFieldModalTitle')} - ${startCase(sectionName)}`}
|
||||
size="default"
|
||||
footer={
|
||||
<div className="flex justify-end gap-2">
|
||||
<Button type="button" variant="outline" onClick={handleClose}>
|
||||
{t('common.cancel')}
|
||||
</Button>
|
||||
<Button type="button" onClick={handleSubmit(handleConfirm)}>
|
||||
{t('common.confirm')}
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<Form {...form}>
|
||||
<div className="space-y-4">
|
||||
{hasTypeField && (
|
||||
<RAGFlowFormItem name="type" label={getFieldLabel('type')}>
|
||||
{(field) => (
|
||||
<SelectWithSearch
|
||||
{...field}
|
||||
options={typeOptions}
|
||||
allowClear
|
||||
onChange={(value) => {
|
||||
field.onChange(value);
|
||||
handleTypeChange(value);
|
||||
}}
|
||||
placeholder={t('setting.selectFieldType')}
|
||||
allowCustomValue
|
||||
/>
|
||||
)}
|
||||
</RAGFlowFormItem>
|
||||
)}
|
||||
|
||||
{nonTypeKeys.map((key) => (
|
||||
<RAGFlowFormItem key={key} name={key} label={getFieldLabel(key)}>
|
||||
<Textarea
|
||||
placeholder={t('setting.descriptionPlaceholder')}
|
||||
rows={key === 'description' ? 4 : 10}
|
||||
resize="vertical"
|
||||
/>
|
||||
</RAGFlowFormItem>
|
||||
))}
|
||||
</div>
|
||||
</Form>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
import { Collapse } from '@/components/collapse';
|
||||
import MarkdownEditor from '@/components/markdown-editor';
|
||||
import { SelectWithSearch } from '@/components/originui/select-with-search';
|
||||
import { RAGFlowFormItem } from '@/components/ragflow-form';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import { useFetchWikiPresets } from '@/hooks/use-compilation-template-request';
|
||||
import { UseFormReturn } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { useBlueprintSelection } from '../hooks/use-blueprint-selection';
|
||||
import { FormSchemaType } from '../schema';
|
||||
|
||||
type BlueprintSectionProps = {
|
||||
form: UseFormReturn<FormSchemaType>;
|
||||
selectedTemplateIndex: number;
|
||||
};
|
||||
|
||||
export function BlueprintSection({
|
||||
form,
|
||||
selectedTemplateIndex,
|
||||
}: BlueprintSectionProps) {
|
||||
const { t } = useTranslation();
|
||||
const { data: presets } = useFetchWikiPresets();
|
||||
const {
|
||||
selectedValue,
|
||||
options,
|
||||
handleSelect,
|
||||
instructionPath,
|
||||
pageExample,
|
||||
handlePageExampleChange,
|
||||
} = useBlueprintSelection({ form, selectedTemplateIndex, presets });
|
||||
|
||||
if (presets.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<section className="space-y-4 pt-4">
|
||||
<Collapse
|
||||
defaultOpen
|
||||
title={
|
||||
<h3 className="text-base font-medium">{t('setting.blueprints')}</h3>
|
||||
}
|
||||
>
|
||||
<div className="space-y-4">
|
||||
<SelectWithSearch
|
||||
value={selectedValue}
|
||||
onChange={handleSelect}
|
||||
options={options}
|
||||
placeholder={t('common.selectPlaceholder')}
|
||||
/>
|
||||
|
||||
<div className="space-y-4">
|
||||
<RAGFlowFormItem
|
||||
name={instructionPath}
|
||||
label={t('setting.instruction')}
|
||||
>
|
||||
<Textarea rows={6} />
|
||||
</RAGFlowFormItem>
|
||||
|
||||
<div className="flex h-[50vh] min-h-0 flex-col">
|
||||
<MarkdownEditor
|
||||
content={String(pageExample ?? '')}
|
||||
onChange={handlePageExampleChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Collapse>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card, CardContent } from '@/components/ui/card';
|
||||
import { Pencil, Trash2 } from 'lucide-react';
|
||||
|
||||
type FieldCardProps = {
|
||||
title?: string;
|
||||
field: Record<string, string>;
|
||||
onEdit: () => void;
|
||||
onDelete: () => void;
|
||||
};
|
||||
|
||||
export function FieldCard({ title, field, onEdit, onDelete }: FieldCardProps) {
|
||||
return (
|
||||
<Card className="border-border-button bg-transparent group">
|
||||
<CardContent className="p-4 space-y-3">
|
||||
<div className="flex items-start justify-between gap-2">
|
||||
<div className="space-y-2 space-x-2 flex-1 min-w-0">
|
||||
{title && (
|
||||
<span className="text-sm font-medium text-text-primary">
|
||||
{title}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center gap-1 opacity-0 group-hover:opacity-100 transition-opacity shrink-0">
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="icon-xs"
|
||||
onClick={onEdit}
|
||||
className="text-text-secondary hover:text-text-primary"
|
||||
>
|
||||
<Pencil className="size-4" />
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="icon-xs"
|
||||
onClick={onDelete}
|
||||
className="text-text-secondary hover:text-state-error"
|
||||
>
|
||||
<Trash2 className="size-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{field.description && (
|
||||
<p className="text-sm text-text-secondary line-clamp-3">
|
||||
{field.description}
|
||||
</p>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import JsonEditor from '@/components/json-edit';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
Sheet,
|
||||
SheetContent,
|
||||
SheetHeader,
|
||||
SheetTitle,
|
||||
SheetTrigger,
|
||||
} from '@/components/ui/sheet';
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from '@/components/ui/tooltip';
|
||||
import { Braces } from 'lucide-react';
|
||||
|
||||
interface JsonPreviewSheetProps {
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
value: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export function JsonPreviewSheet({
|
||||
open,
|
||||
onOpenChange,
|
||||
value,
|
||||
}: JsonPreviewSheetProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Sheet open={open} onOpenChange={onOpenChange} modal={false}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<SheetTrigger asChild>
|
||||
<Button variant="ghost" size="icon" className="size-8">
|
||||
<Braces className="size-4" />
|
||||
</Button>
|
||||
</SheetTrigger>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>{t('setting.jsonPreview')}</TooltipContent>
|
||||
</Tooltip>
|
||||
<SheetContent
|
||||
className="w-1/2 max-w-[700px] flex flex-col"
|
||||
onInteractOutside={(e) => e.preventDefault()}
|
||||
>
|
||||
<SheetHeader>
|
||||
<SheetTitle>{t('setting.jsonPreview')}</SheetTitle>
|
||||
</SheetHeader>
|
||||
<div className="flex-1 min-h-0 mt-4">
|
||||
<JsonEditor
|
||||
value={value}
|
||||
height="100%"
|
||||
options={{ mode: 'tree', modes: ['tree', 'code'] }}
|
||||
defaultExpanded
|
||||
/>
|
||||
</div>
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
import { Card, CardContent } from '@/components/ui/card';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { Plus } from 'lucide-react';
|
||||
import { useFieldArray, useFormContext, useWatch } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { FieldCard } from './field-card';
|
||||
|
||||
type SectionFieldGridProps = {
|
||||
fieldsPath: string;
|
||||
sectionName: string;
|
||||
onOpenAddField: () => void;
|
||||
onEditField: (index: number) => void;
|
||||
};
|
||||
|
||||
export function SectionFieldGrid({
|
||||
fieldsPath,
|
||||
sectionName,
|
||||
onOpenAddField,
|
||||
onEditField,
|
||||
}: SectionFieldGridProps) {
|
||||
const { t } = useTranslation();
|
||||
const form = useFormContext();
|
||||
const { fields, remove } = useFieldArray({
|
||||
control: form.control,
|
||||
name: fieldsPath,
|
||||
});
|
||||
|
||||
const isTypedSection = sectionName === 'entity' || sectionName === 'relation';
|
||||
|
||||
const currentFields = useWatch({
|
||||
control: form.control,
|
||||
name: fieldsPath,
|
||||
}) as Record<string, string>[] | undefined;
|
||||
|
||||
return (
|
||||
<section className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4">
|
||||
{fields.map((field, index) => {
|
||||
const fieldValue = currentFields?.[index] ?? {};
|
||||
return (
|
||||
<FieldCard
|
||||
key={field.id}
|
||||
title={isTypedSection ? fieldValue.type : undefined}
|
||||
field={fieldValue}
|
||||
onEdit={() => onEditField(index)}
|
||||
onDelete={() => remove(index)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
<Card
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onClick={onOpenAddField}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
onOpenAddField();
|
||||
}
|
||||
}}
|
||||
className={cn(
|
||||
'border-border-button bg-transparent border-dashed flex flex-col items-center justify-center gap-2 min-h-[140px] cursor-pointer',
|
||||
'hover:border-border-accent hover:text-text-primary text-text-secondary',
|
||||
)}
|
||||
>
|
||||
<CardContent className="flex flex-col items-center justify-center gap-2 p-4">
|
||||
<Plus className="size-6" />
|
||||
<span className="text-sm font-medium">{t('setting.addField')}</span>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,242 @@
|
||||
import { ModelTreeSelectFormField } from '@/components/model-tree-select';
|
||||
import { SelectWithSearch } from '@/components/originui/select-with-search';
|
||||
import { RAGFlowFormItem } from '@/components/ragflow-form';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import { ICompilationTemplateBuiltin } from '@/interfaces/database/compilation-template';
|
||||
import { startCase } from 'lodash';
|
||||
import { ReactNode, useCallback } from 'react';
|
||||
import { UseFormReturn, useWatch } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { CompilationTemplateKind } from '@/constants/compilation';
|
||||
import { TreeTemplateFields } from './tree-template-fields';
|
||||
import { useTemplateKindChange } from '../hooks/use-template-kind-change';
|
||||
import { FormSchemaType } from '../schema';
|
||||
import { SectionTitleKeyMap } from '../utils';
|
||||
|
||||
import { useActiveSectionTab } from '../hooks/use-active-section-tab';
|
||||
import { useAvailableKindOptions } from '../hooks/use-available-kind-options';
|
||||
import { useBuiltinTemplate } from '../hooks/use-builtin-template';
|
||||
import { useFieldArrayHandlers } from '../hooks/use-field-array-handlers';
|
||||
import { useFieldModal } from '../hooks/use-field-modal';
|
||||
import { useTemplatePreviewSheets } from '../hooks/use-template-preview-sheets';
|
||||
import { useTemplateSectionData } from '../hooks/use-template-section-data';
|
||||
|
||||
import { AddFieldModal } from './add-field-modal';
|
||||
import { SectionFieldGrid } from './section-field-grid';
|
||||
import { TemplatePreviewHeader } from './template-preview-header';
|
||||
|
||||
type TemplateConfigurationProps = {
|
||||
form: UseFormReturn<FormSchemaType>;
|
||||
builtins: ICompilationTemplateBuiltin[];
|
||||
kindOptions: { label: string; value: string }[];
|
||||
selectedTemplateIndex: number;
|
||||
children?: ReactNode;
|
||||
};
|
||||
|
||||
export function TemplateConfiguration({
|
||||
form,
|
||||
builtins,
|
||||
kindOptions,
|
||||
selectedTemplateIndex,
|
||||
children,
|
||||
}: TemplateConfigurationProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const {
|
||||
addFieldModalOpen,
|
||||
editingFieldIndex,
|
||||
setEditingFieldIndex,
|
||||
handleModalOpenChange,
|
||||
handleOpenAddField,
|
||||
handleOpenEditField,
|
||||
} = useFieldModal();
|
||||
|
||||
const kind = useWatch({
|
||||
control: form.control,
|
||||
name: `templates.${selectedTemplateIndex}.kind`,
|
||||
});
|
||||
|
||||
const availableKindOptions = useAvailableKindOptions(
|
||||
form,
|
||||
kindOptions,
|
||||
selectedTemplateIndex,
|
||||
);
|
||||
|
||||
const { builtinTemplate, sectionNames } = useBuiltinTemplate(builtins, kind);
|
||||
|
||||
const {
|
||||
jsonSheetOpen,
|
||||
setJsonSheetOpen,
|
||||
workflowSheetOpen,
|
||||
setWorkflowSheetOpen,
|
||||
allFormValues,
|
||||
templateName,
|
||||
} = useTemplatePreviewSheets(form, selectedTemplateIndex);
|
||||
|
||||
const { activeSectionTab, setActiveSectionTab } =
|
||||
useActiveSectionTab(sectionNames);
|
||||
|
||||
const handleKindChange = useTemplateKindChange({
|
||||
form,
|
||||
index: selectedTemplateIndex,
|
||||
builtins,
|
||||
});
|
||||
|
||||
const { activeFieldsPath, builtinSection, editingField } =
|
||||
useTemplateSectionData(
|
||||
form,
|
||||
selectedTemplateIndex,
|
||||
activeSectionTab,
|
||||
builtinTemplate,
|
||||
editingFieldIndex,
|
||||
);
|
||||
|
||||
const { handleAddField } = useFieldArrayHandlers(
|
||||
form,
|
||||
activeFieldsPath,
|
||||
editingFieldIndex,
|
||||
setEditingFieldIndex,
|
||||
);
|
||||
|
||||
const renderSectionTabs = useCallback(
|
||||
(sectionName: string) => {
|
||||
return (
|
||||
sectionName === activeSectionTab && (
|
||||
<SectionFieldGrid
|
||||
key={activeFieldsPath}
|
||||
fieldsPath={activeFieldsPath}
|
||||
sectionName={sectionName}
|
||||
onOpenAddField={handleOpenAddField}
|
||||
onEditField={handleOpenEditField}
|
||||
/>
|
||||
)
|
||||
);
|
||||
},
|
||||
[
|
||||
activeFieldsPath,
|
||||
activeSectionTab,
|
||||
handleOpenAddField,
|
||||
handleOpenEditField,
|
||||
],
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<TemplatePreviewHeader
|
||||
templateName={templateName}
|
||||
jsonSheetOpen={jsonSheetOpen}
|
||||
onJsonSheetOpenChange={setJsonSheetOpen}
|
||||
workflowSheetOpen={workflowSheetOpen}
|
||||
onWorkflowSheetOpenChange={setWorkflowSheetOpen}
|
||||
allFormValues={allFormValues}
|
||||
/>
|
||||
<div className="flex-1 min-h-0 overflow-y-auto p-5">
|
||||
<div className="max-w-4xl mx-auto space-y-6">
|
||||
<RAGFlowFormItem
|
||||
name={`templates.${selectedTemplateIndex}.name`}
|
||||
label={t('setting.templateName')}
|
||||
required
|
||||
>
|
||||
<Input placeholder={t('common.namePlaceholder')} />
|
||||
</RAGFlowFormItem>
|
||||
|
||||
<RAGFlowFormItem
|
||||
name={`templates.${selectedTemplateIndex}.description`}
|
||||
label={t('setting.templateDescription')}
|
||||
>
|
||||
<Textarea
|
||||
placeholder={t('common.descriptionPlaceholder')}
|
||||
rows={2}
|
||||
resize="vertical"
|
||||
/>
|
||||
</RAGFlowFormItem>
|
||||
|
||||
<ModelTreeSelectFormField
|
||||
name={`templates.${selectedTemplateIndex}.llm_id`}
|
||||
label={t('setting.llmForExtraction')}
|
||||
required
|
||||
/>
|
||||
|
||||
<RAGFlowFormItem
|
||||
name={`templates.${selectedTemplateIndex}.kind`}
|
||||
label={t('knowledgeCompilation.builtinTemplates')}
|
||||
required
|
||||
>
|
||||
{(field) => (
|
||||
<SelectWithSearch
|
||||
value={field.value}
|
||||
onChange={(value) => handleKindChange(field, value)}
|
||||
disabled={field.disabled}
|
||||
options={availableKindOptions}
|
||||
placeholder={t('common.selectPlaceholder')}
|
||||
/>
|
||||
)}
|
||||
</RAGFlowFormItem>
|
||||
|
||||
<RAGFlowFormItem
|
||||
name={`templates.${selectedTemplateIndex}.config.global_rules`}
|
||||
label={t('setting.globalRules')}
|
||||
>
|
||||
<Textarea
|
||||
placeholder={t('setting.globalRulesPlaceholder')}
|
||||
rows={8}
|
||||
resize="vertical"
|
||||
/>
|
||||
</RAGFlowFormItem>
|
||||
|
||||
{kind === CompilationTemplateKind.Tree ? (
|
||||
<TreeTemplateFields index={selectedTemplateIndex} />
|
||||
) : (
|
||||
sectionNames.length > 0 &&
|
||||
activeSectionTab && (
|
||||
<Tabs
|
||||
value={activeSectionTab}
|
||||
onValueChange={setActiveSectionTab}
|
||||
className="w-full"
|
||||
>
|
||||
<TabsList className="w-full justify-start">
|
||||
{sectionNames.map((sectionName) => (
|
||||
<TabsTrigger
|
||||
key={sectionName}
|
||||
value={sectionName}
|
||||
className="flex-1"
|
||||
>
|
||||
{t(
|
||||
SectionTitleKeyMap[sectionName] ??
|
||||
startCase(sectionName),
|
||||
)}
|
||||
</TabsTrigger>
|
||||
))}
|
||||
</TabsList>
|
||||
|
||||
{sectionNames.map((sectionName) => (
|
||||
<TabsContent
|
||||
key={sectionName}
|
||||
value={sectionName}
|
||||
className="mt-4"
|
||||
>
|
||||
{renderSectionTabs(sectionName)}
|
||||
</TabsContent>
|
||||
))}
|
||||
</Tabs>
|
||||
)
|
||||
)}
|
||||
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<AddFieldModal
|
||||
open={addFieldModalOpen}
|
||||
onOpenChange={handleModalOpenChange}
|
||||
sectionName={activeSectionTab}
|
||||
builtinSection={builtinSection}
|
||||
initialField={editingField}
|
||||
onAdd={handleAddField}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { JsonPreviewSheet } from './json-preview-sheet';
|
||||
import { WorkflowPreviewSheet } from './workflow-preview-sheet';
|
||||
|
||||
interface TemplatePreviewHeaderProps {
|
||||
templateName: string | undefined;
|
||||
jsonSheetOpen: boolean;
|
||||
onJsonSheetOpenChange: (open: boolean) => void;
|
||||
workflowSheetOpen: boolean;
|
||||
onWorkflowSheetOpenChange: (open: boolean) => void;
|
||||
allFormValues: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export function TemplatePreviewHeader({
|
||||
templateName,
|
||||
jsonSheetOpen,
|
||||
onJsonSheetOpenChange,
|
||||
workflowSheetOpen,
|
||||
onWorkflowSheetOpenChange,
|
||||
allFormValues,
|
||||
}: TemplatePreviewHeaderProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<section className="shrink-0 flex justify-between items-center px-5 py-4 border-b border-border-button">
|
||||
<span className="text-lg font-medium text-text-primary">
|
||||
{templateName || t('setting.templateName')}
|
||||
</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<JsonPreviewSheet
|
||||
open={jsonSheetOpen}
|
||||
onOpenChange={onJsonSheetOpenChange}
|
||||
value={allFormValues}
|
||||
/>
|
||||
<WorkflowPreviewSheet
|
||||
open={workflowSheetOpen}
|
||||
onOpenChange={onWorkflowSheetOpenChange}
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
import { Collapse } from '@/components/collapse';
|
||||
import { RAGFlowFormItem } from '@/components/ragflow-form';
|
||||
import { SliderInputFormField } from '@/components/slider-input-form-field';
|
||||
import { SwitchFormField } from '@/components/switch-fom-field';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
type TreeTemplateFieldsProps = {
|
||||
index: number;
|
||||
};
|
||||
|
||||
export function TreeTemplateFields({ index }: TreeTemplateFieldsProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Collapse defaultOpen title={t('setting.raptorTreeSettings')}>
|
||||
<div className="space-y-4">
|
||||
<RAGFlowFormItem
|
||||
name={`templates.${index}.config.raptor.prompt`}
|
||||
label={t('setting.summarizationPrompt')}
|
||||
>
|
||||
<Textarea
|
||||
placeholder={t('setting.descriptionPlaceholder')}
|
||||
rows={6}
|
||||
/>
|
||||
</RAGFlowFormItem>
|
||||
|
||||
<SliderInputFormField
|
||||
name={`templates.${index}.config.raptor.max_token`}
|
||||
label={t('setting.maxToken')}
|
||||
max={2048}
|
||||
min={0}
|
||||
step={1}
|
||||
/>
|
||||
<SliderInputFormField
|
||||
name={`templates.${index}.config.raptor.threshold`}
|
||||
label={t('setting.threshold')}
|
||||
step={0.01}
|
||||
max={1}
|
||||
min={0}
|
||||
/>
|
||||
|
||||
<SwitchFormField
|
||||
name={`templates.${index}.config.raptor.rechunk`}
|
||||
label={t('setting.rechunkByTreeLeaves')}
|
||||
tooltip={t('setting.rechunkByTreeLeavesTip')}
|
||||
vertical={false}
|
||||
/>
|
||||
</div>
|
||||
</Collapse>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
Sheet,
|
||||
SheetContent,
|
||||
SheetHeader,
|
||||
SheetTitle,
|
||||
SheetTrigger,
|
||||
} from '@/components/ui/sheet';
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from '@/components/ui/tooltip';
|
||||
import { Workflow } from 'lucide-react';
|
||||
|
||||
interface WorkflowPreviewSheetProps {
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
}
|
||||
|
||||
export function WorkflowPreviewSheet({
|
||||
open,
|
||||
onOpenChange,
|
||||
}: WorkflowPreviewSheetProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Sheet open={open} onOpenChange={onOpenChange} modal={false}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<SheetTrigger asChild>
|
||||
<Button variant="ghost" size="icon" className="size-8">
|
||||
<Workflow className="size-4" />
|
||||
</Button>
|
||||
</SheetTrigger>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>{t('setting.processFlow')}</TooltipContent>
|
||||
</Tooltip>
|
||||
<SheetContent
|
||||
className="w-1/2 max-w-[700px] flex flex-col"
|
||||
onInteractOutside={(e) => e.preventDefault()}
|
||||
>
|
||||
<SheetHeader>
|
||||
<SheetTitle>{t('setting.processFlow')}</SheetTitle>
|
||||
</SheetHeader>
|
||||
<div className="flex-1 min-h-0 mt-4 flex items-center justify-center">
|
||||
<span className="text-text-disabled">
|
||||
{t('setting.processFlowComingSoon')}
|
||||
</span>
|
||||
</div>
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
export const useActiveSectionTab = (sectionNames: string[]) => {
|
||||
const [activeSectionTab, setActiveSectionTab] = useState<string>('');
|
||||
|
||||
useEffect(() => {
|
||||
setActiveSectionTab((prev) =>
|
||||
sectionNames.includes(prev) ? prev : (sectionNames[0] ?? ''),
|
||||
);
|
||||
}, [sectionNames]);
|
||||
|
||||
return { activeSectionTab, setActiveSectionTab };
|
||||
};
|
||||
@@ -0,0 +1,115 @@
|
||||
import { ICompilationTemplateSection } from '@/interfaces/database/compilation-template';
|
||||
import {
|
||||
createEmptyField,
|
||||
getFieldKeyOrder,
|
||||
getTypeOptionsFromBuiltinSection,
|
||||
} from '../utils';
|
||||
import { useCallback, useEffect, useMemo } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
|
||||
type UseAddFieldFormOptions = {
|
||||
open: boolean;
|
||||
builtinSection?: ICompilationTemplateSection;
|
||||
initialField?: Record<string, string>;
|
||||
};
|
||||
|
||||
export const useAddFieldForm = ({
|
||||
open,
|
||||
builtinSection,
|
||||
initialField,
|
||||
}: UseAddFieldFormOptions) => {
|
||||
const form = useForm<Record<string, string>>({
|
||||
defaultValues: {},
|
||||
});
|
||||
|
||||
const fieldKeys = useMemo(() => {
|
||||
const firstField = builtinSection?.fields?.[0];
|
||||
const keys = firstField
|
||||
? Object.keys(firstField)
|
||||
: ['type', 'description', 'rule'];
|
||||
return getFieldKeyOrder(keys);
|
||||
}, [builtinSection]);
|
||||
|
||||
const hasTypeField = fieldKeys.includes('type');
|
||||
|
||||
const typeOptions = useMemo(
|
||||
() => getTypeOptionsFromBuiltinSection(builtinSection),
|
||||
[builtinSection],
|
||||
);
|
||||
|
||||
const buildField = useCallback(
|
||||
(typeValue: string) => {
|
||||
const matched = builtinSection?.fields?.find(
|
||||
(field) => field.type === typeValue,
|
||||
);
|
||||
if (matched) {
|
||||
const normalized: Record<string, string> = {};
|
||||
fieldKeys.forEach((key) => {
|
||||
normalized[key] = (matched as Record<string, string>)[key] ?? '';
|
||||
});
|
||||
return normalized;
|
||||
}
|
||||
const empty = createEmptyField(fieldKeys);
|
||||
if (hasTypeField) {
|
||||
empty.type = typeValue;
|
||||
}
|
||||
return empty;
|
||||
},
|
||||
[builtinSection, fieldKeys, hasTypeField],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
|
||||
if (initialField) {
|
||||
const normalized: Record<string, string> = {};
|
||||
fieldKeys.forEach((key) => {
|
||||
normalized[key] = initialField[key] ?? '';
|
||||
});
|
||||
form.reset(normalized);
|
||||
return;
|
||||
}
|
||||
|
||||
const firstType = typeOptions[0]?.value ?? '';
|
||||
form.reset(buildField(firstType));
|
||||
}, [buildField, fieldKeys, form, initialField, open, typeOptions]);
|
||||
|
||||
const handleTypeChange = useCallback(
|
||||
(value: string) => {
|
||||
if (initialField) {
|
||||
form.setValue('type', value, {
|
||||
shouldDirty: true,
|
||||
shouldTouch: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const nextField = buildField(value);
|
||||
Object.entries(nextField).forEach(([key, val]) => {
|
||||
form.setValue(key, val, {
|
||||
shouldDirty: true,
|
||||
shouldTouch: true,
|
||||
});
|
||||
});
|
||||
},
|
||||
[buildField, form, initialField],
|
||||
);
|
||||
|
||||
const handleSubmit = useCallback(
|
||||
(onAdd: (field: Record<string, string>) => void) => {
|
||||
return form.handleSubmit((values) => {
|
||||
onAdd(values);
|
||||
});
|
||||
},
|
||||
[form],
|
||||
);
|
||||
|
||||
return {
|
||||
form,
|
||||
fieldKeys,
|
||||
hasTypeField,
|
||||
typeOptions,
|
||||
handleTypeChange,
|
||||
handleSubmit,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,53 @@
|
||||
import { CompilationTemplateKind } from '@/constants/compilation';
|
||||
import { FormSchemaType } from '../schema';
|
||||
import { useMemo } from 'react';
|
||||
import { UseFormReturn, useWatch } from 'react-hook-form';
|
||||
|
||||
export const useAvailableKindOptions = (
|
||||
form: UseFormReturn<FormSchemaType>,
|
||||
kindOptions: { label: string; value: string }[],
|
||||
selectedTemplateIndex: number,
|
||||
) => {
|
||||
const kind = useWatch({
|
||||
control: form.control,
|
||||
name: `templates.${selectedTemplateIndex}.kind`,
|
||||
});
|
||||
|
||||
const templates = useWatch({ control: form.control, name: 'templates' });
|
||||
|
||||
const availableKindOptions = useMemo(() => {
|
||||
const otherSelectedKinds = new Set(
|
||||
templates
|
||||
?.filter((_, index) => index !== selectedTemplateIndex)
|
||||
.map((template) => template.kind)
|
||||
.filter((value): value is string => Boolean(value)) ?? [],
|
||||
);
|
||||
|
||||
const hasOtherNonArtifactsKind = Array.from(otherSelectedKinds).some(
|
||||
(value) => value !== CompilationTemplateKind.Artifacts,
|
||||
);
|
||||
const hasOtherArtifactsKind = otherSelectedKinds.has(
|
||||
CompilationTemplateKind.Artifacts,
|
||||
);
|
||||
|
||||
return kindOptions.filter((option) => {
|
||||
if (option.value === kind) return true;
|
||||
if (otherSelectedKinds.has(option.value)) return false;
|
||||
if (
|
||||
hasOtherNonArtifactsKind &&
|
||||
option.value === CompilationTemplateKind.Artifacts
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
hasOtherArtifactsKind &&
|
||||
option.value !== CompilationTemplateKind.Artifacts
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}, [kindOptions, kind, selectedTemplateIndex, templates]);
|
||||
|
||||
return availableKindOptions;
|
||||
};
|
||||
@@ -0,0 +1,132 @@
|
||||
import { SelectWithSearchFlagOptionType } from '@/components/originui/select-with-search';
|
||||
import { IWikiPreset } from '@/interfaces/database/compilation-template';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { UseFormReturn, useWatch } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { FormSchemaType } from '../schema';
|
||||
|
||||
export const CustomBlueprintValue = '__custom__';
|
||||
|
||||
type UseBlueprintSelectionParams = {
|
||||
form: UseFormReturn<FormSchemaType>;
|
||||
selectedTemplateIndex: number;
|
||||
presets: IWikiPreset[];
|
||||
};
|
||||
|
||||
const isSameBlueprintContent = (
|
||||
preset: IWikiPreset,
|
||||
instruction: string,
|
||||
pageExample: string,
|
||||
) =>
|
||||
preset.instruction.trim() === instruction.trim() &&
|
||||
preset.page_example.trim() === pageExample.trim();
|
||||
|
||||
export function useBlueprintSelection({
|
||||
form,
|
||||
selectedTemplateIndex,
|
||||
presets,
|
||||
}: UseBlueprintSelectionParams) {
|
||||
const { t } = useTranslation();
|
||||
const [explicitValue, setExplicitValue] = useState<string>();
|
||||
|
||||
const instructionPath =
|
||||
`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 instruction = useWatch({
|
||||
control: form.control,
|
||||
name: instructionPath,
|
||||
});
|
||||
const pageExample = useWatch({
|
||||
control: form.control,
|
||||
name: pageExamplePath,
|
||||
});
|
||||
|
||||
const matchedPresetId = useMemo(() => {
|
||||
const currentInstruction = String(instruction ?? '');
|
||||
const currentPageExample = String(pageExample ?? '');
|
||||
if (!currentInstruction.trim() && !currentPageExample.trim()) {
|
||||
return undefined;
|
||||
}
|
||||
return presets.find((preset) =>
|
||||
isSameBlueprintContent(preset, currentInstruction, currentPageExample),
|
||||
)?.id;
|
||||
}, [instruction, pageExample, presets]);
|
||||
|
||||
const selectedValue =
|
||||
explicitValue ?? matchedPresetId ?? CustomBlueprintValue;
|
||||
|
||||
const options = useMemo<SelectWithSearchFlagOptionType[]>(
|
||||
() => [
|
||||
...presets.map((preset) => ({
|
||||
label: preset.id,
|
||||
value: preset.id,
|
||||
})),
|
||||
{ label: t('setting.custom'), value: CustomBlueprintValue },
|
||||
],
|
||||
[presets, t],
|
||||
);
|
||||
|
||||
const handleSelect = useCallback(
|
||||
(value: string) => {
|
||||
if (value === selectedValue) return;
|
||||
setExplicitValue(value);
|
||||
|
||||
if (value === CustomBlueprintValue) {
|
||||
const defaultConfig =
|
||||
form.formState.defaultValues?.templates?.[selectedTemplateIndex]
|
||||
?.config;
|
||||
form.setValue(
|
||||
instructionPath,
|
||||
String(defaultConfig?.instruction ?? ''),
|
||||
{ shouldValidate: false },
|
||||
);
|
||||
form.setValue(
|
||||
pageExamplePath,
|
||||
String(defaultConfig?.page_example ?? ''),
|
||||
{ shouldValidate: false },
|
||||
);
|
||||
} else {
|
||||
const preset = presets.find((item) => item.id === value);
|
||||
if (!preset) return;
|
||||
form.setValue(instructionPath, preset.instruction, {
|
||||
shouldValidate: false,
|
||||
});
|
||||
form.setValue(pageExamplePath, preset.page_example, {
|
||||
shouldValidate: false,
|
||||
});
|
||||
}
|
||||
|
||||
form.setValue(useBlueprintPath, true, { shouldValidate: false });
|
||||
},
|
||||
[
|
||||
form,
|
||||
instructionPath,
|
||||
pageExamplePath,
|
||||
presets,
|
||||
selectedTemplateIndex,
|
||||
selectedValue,
|
||||
useBlueprintPath,
|
||||
],
|
||||
);
|
||||
|
||||
const handlePageExampleChange = useCallback(
|
||||
(value: string) => {
|
||||
form.setValue(pageExamplePath, value, { shouldValidate: false });
|
||||
},
|
||||
[form, pageExamplePath],
|
||||
);
|
||||
|
||||
return {
|
||||
selectedValue,
|
||||
options,
|
||||
handleSelect,
|
||||
instructionPath,
|
||||
pageExample,
|
||||
handlePageExampleChange,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import {
|
||||
ICompilationTemplateBuiltin,
|
||||
ICompilationTemplateSection,
|
||||
} from '@/interfaces/database/compilation-template';
|
||||
import { isConfigMetaKey, sortSectionNames } from '../utils';
|
||||
|
||||
export const useBuiltinTemplate = (
|
||||
builtins: ICompilationTemplateBuiltin[],
|
||||
kind: string,
|
||||
) => {
|
||||
const builtinTemplate = useMemo(
|
||||
() => builtins.find((template) => template.kind === kind),
|
||||
[builtins, kind],
|
||||
);
|
||||
|
||||
const sectionNames = useMemo(() => {
|
||||
const names = Object.keys(builtinTemplate?.config ?? {}).filter((key) => {
|
||||
if (isConfigMetaKey(key)) return false;
|
||||
const section = builtinTemplate?.config?.[key];
|
||||
return (
|
||||
section &&
|
||||
typeof section === 'object' &&
|
||||
Array.isArray((section as ICompilationTemplateSection).fields)
|
||||
);
|
||||
});
|
||||
return sortSectionNames(names);
|
||||
}, [builtinTemplate]);
|
||||
|
||||
return { builtinTemplate, sectionNames };
|
||||
};
|
||||
@@ -0,0 +1,43 @@
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { useEffect } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { ICompilationTemplateGroup } from '@/interfaces/database/compilation-template';
|
||||
|
||||
import { buildFormSchema, FormSchemaType } from '../schema';
|
||||
import { DefaultValues, transformGroupDetailToForm } from '../utils';
|
||||
|
||||
type UseCompilationTemplateGroupFormOptions = {
|
||||
detail?: ICompilationTemplateGroup;
|
||||
defaultLlmId?: string;
|
||||
isCreate: boolean;
|
||||
};
|
||||
|
||||
export const useCompilationTemplateGroupForm = ({
|
||||
detail,
|
||||
defaultLlmId,
|
||||
isCreate,
|
||||
}: UseCompilationTemplateGroupFormOptions) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const form = useForm<FormSchemaType>({
|
||||
resolver: zodResolver(buildFormSchema(t)),
|
||||
defaultValues: DefaultValues,
|
||||
mode: 'onChange',
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (detail) {
|
||||
form.reset(transformGroupDetailToForm(detail));
|
||||
} else if (
|
||||
isCreate &&
|
||||
defaultLlmId &&
|
||||
!form.getValues('templates.0.llm_id')
|
||||
) {
|
||||
form.setValue('templates.0.llm_id', defaultLlmId);
|
||||
}
|
||||
}, [defaultLlmId, detail, form, isCreate]);
|
||||
|
||||
return { form };
|
||||
};
|
||||
@@ -0,0 +1,48 @@
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import {
|
||||
ICreateCompilationTemplateGroupRequestBody,
|
||||
IUpdateCompilationTemplateGroupRequestBody,
|
||||
} from '@/interfaces/request/compilation-template';
|
||||
|
||||
import { FormSchemaType } from '../schema';
|
||||
import { transformFormToPayload } from '../utils';
|
||||
|
||||
type UseCompilationTemplateGroupSubmitOptions = {
|
||||
isCreate: boolean;
|
||||
id?: string;
|
||||
createGroup: (
|
||||
params: ICreateCompilationTemplateGroupRequestBody,
|
||||
) => Promise<{ code: number } & Record<string, unknown>>;
|
||||
updateGroup: (
|
||||
id: string,
|
||||
params: IUpdateCompilationTemplateGroupRequestBody,
|
||||
) => Promise<{ code: number } & Record<string, unknown>>;
|
||||
onSuccess: () => void;
|
||||
};
|
||||
|
||||
export const useCompilationTemplateGroupSubmit = ({
|
||||
isCreate,
|
||||
id,
|
||||
createGroup,
|
||||
updateGroup,
|
||||
onSuccess,
|
||||
}: UseCompilationTemplateGroupSubmitOptions) => {
|
||||
const onSubmit = useCallback(
|
||||
async (values: FormSchemaType) => {
|
||||
const payload = transformFormToPayload(values);
|
||||
let result;
|
||||
if (isCreate) {
|
||||
result = await createGroup(payload);
|
||||
} else if (id) {
|
||||
result = await updateGroup(id, payload);
|
||||
}
|
||||
if (result?.code === 0) {
|
||||
onSuccess();
|
||||
}
|
||||
},
|
||||
[createGroup, id, isCreate, onSuccess, updateGroup],
|
||||
);
|
||||
|
||||
return { onSubmit };
|
||||
};
|
||||
@@ -0,0 +1,75 @@
|
||||
import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks';
|
||||
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 { isCreateCompilationTemplateGroup } from '@/utils/compilation-template-util';
|
||||
import { useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useParams } from 'react-router';
|
||||
|
||||
import { formatKindLabel } from '@/utils/compilation-template-util';
|
||||
|
||||
import { useCompilationTemplateGroupForm } from './use-compilation-template-group-form';
|
||||
import { useCompilationTemplateGroupSubmit } from './use-compilation-template-group-submit';
|
||||
|
||||
type UseEditNextCompilationTemplateGroupOptions = {
|
||||
onSuccess?: () => void;
|
||||
};
|
||||
|
||||
export const useEditNextCompilationTemplateGroup = ({
|
||||
onSuccess,
|
||||
}: UseEditNextCompilationTemplateGroupOptions = {}) => {
|
||||
const { id } = useParams<{ id: string }>();
|
||||
const { navigateToCompilationTemplates } = useNavigatePage();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const isCreate = isCreateCompilationTemplateGroup(id);
|
||||
|
||||
const { data: detail } = useFetchCompilationTemplateGroup();
|
||||
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: formatKindLabel(t, option.value),
|
||||
})),
|
||||
[builtinKindOptions, t],
|
||||
);
|
||||
|
||||
const { form } = useCompilationTemplateGroupForm({
|
||||
detail,
|
||||
defaultLlmId: defaultModelDictionary.llm_id,
|
||||
isCreate,
|
||||
});
|
||||
|
||||
const { onSubmit } = useCompilationTemplateGroupSubmit({
|
||||
isCreate,
|
||||
id,
|
||||
createGroup,
|
||||
updateGroup,
|
||||
onSuccess: onSuccess ?? navigateToCompilationTemplates,
|
||||
});
|
||||
|
||||
return {
|
||||
id,
|
||||
form,
|
||||
kindOptions,
|
||||
builtins,
|
||||
onSubmit,
|
||||
isCreate,
|
||||
isLoading: isCreate ? createLoading : updateLoading,
|
||||
navigateToCompilationTemplates,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,39 @@
|
||||
import { useCallback } from 'react';
|
||||
import { ArrayPath, UseFormReturn } from 'react-hook-form';
|
||||
|
||||
import { FormSchemaType } from '../schema';
|
||||
|
||||
export const useFieldArrayHandlers = (
|
||||
form: UseFormReturn<FormSchemaType>,
|
||||
activeFieldsPath: ArrayPath<FormSchemaType>,
|
||||
editingFieldIndex: number | null,
|
||||
setEditingFieldIndex: (index: number | null) => void,
|
||||
) => {
|
||||
const handleAddField = useCallback(
|
||||
(field: Record<string, string>) => {
|
||||
const currentFields =
|
||||
(form.getValues(activeFieldsPath) as
|
||||
| Record<string, string>[]
|
||||
| undefined) ?? [];
|
||||
if (editingFieldIndex !== null) {
|
||||
const newFields = [...currentFields];
|
||||
newFields[editingFieldIndex] = field;
|
||||
form.setValue(activeFieldsPath, newFields, {
|
||||
shouldValidate: false,
|
||||
shouldDirty: true,
|
||||
shouldTouch: true,
|
||||
});
|
||||
} else {
|
||||
form.setValue(activeFieldsPath, [...currentFields, field], {
|
||||
shouldValidate: false,
|
||||
shouldDirty: true,
|
||||
shouldTouch: true,
|
||||
});
|
||||
}
|
||||
setEditingFieldIndex(null);
|
||||
},
|
||||
[activeFieldsPath, editingFieldIndex, form, setEditingFieldIndex],
|
||||
);
|
||||
|
||||
return { handleAddField };
|
||||
};
|
||||
@@ -0,0 +1,35 @@
|
||||
import { useCallback, useState } from 'react';
|
||||
|
||||
export const useFieldModal = () => {
|
||||
const [addFieldModalOpen, setAddFieldModalOpen] = useState(false);
|
||||
const [editingFieldIndex, setEditingFieldIndex] = useState<number | null>(
|
||||
null,
|
||||
);
|
||||
|
||||
const handleModalOpenChange = useCallback(
|
||||
(open: boolean) => {
|
||||
setAddFieldModalOpen(open);
|
||||
if (!open) setEditingFieldIndex(null);
|
||||
},
|
||||
[setAddFieldModalOpen],
|
||||
);
|
||||
|
||||
const handleOpenAddField = useCallback(() => {
|
||||
setEditingFieldIndex(null);
|
||||
setAddFieldModalOpen(true);
|
||||
}, []);
|
||||
|
||||
const handleOpenEditField = useCallback((index: number) => {
|
||||
setEditingFieldIndex(index);
|
||||
setAddFieldModalOpen(true);
|
||||
}, []);
|
||||
|
||||
return {
|
||||
addFieldModalOpen,
|
||||
editingFieldIndex,
|
||||
setEditingFieldIndex,
|
||||
handleModalOpenChange,
|
||||
handleOpenAddField,
|
||||
handleOpenEditField,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,42 @@
|
||||
import { ICompilationTemplateBuiltin } from '@/interfaces/database/compilation-template';
|
||||
import { UseFormReturn } from 'react-hook-form';
|
||||
|
||||
import { FormSchemaType } from '../schema';
|
||||
import { buildConfigFromBuiltin } from '../utils';
|
||||
|
||||
type FieldLike = {
|
||||
value: string;
|
||||
onChange: (value: string) => void;
|
||||
};
|
||||
|
||||
type UseTemplateKindChangeOptions = {
|
||||
form: UseFormReturn<FormSchemaType>;
|
||||
index: number;
|
||||
builtins: ICompilationTemplateBuiltin[];
|
||||
};
|
||||
|
||||
export const useTemplateKindChange = ({
|
||||
form,
|
||||
index,
|
||||
builtins,
|
||||
}: UseTemplateKindChangeOptions) => {
|
||||
return (field: FieldLike, value: string) => {
|
||||
if (value && value !== field.value) {
|
||||
const builtinTemplate = builtins.find(
|
||||
(template) => template.kind === value,
|
||||
);
|
||||
if (builtinTemplate) {
|
||||
form.setValue(
|
||||
`templates.${index}.config`,
|
||||
buildConfigFromBuiltin(
|
||||
builtinTemplate,
|
||||
value,
|
||||
form.getValues(`templates.${index}.llm_id`),
|
||||
),
|
||||
{ shouldValidate: false },
|
||||
);
|
||||
}
|
||||
}
|
||||
field.onChange(value);
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,28 @@
|
||||
import { useState } from 'react';
|
||||
import { UseFormReturn, useWatch } from 'react-hook-form';
|
||||
|
||||
import { FormSchemaType } from '../schema';
|
||||
|
||||
export function useTemplatePreviewSheets(
|
||||
form: UseFormReturn<FormSchemaType>,
|
||||
selectedTemplateIndex: number,
|
||||
) {
|
||||
const [jsonSheetOpen, setJsonSheetOpen] = useState(false);
|
||||
const [workflowSheetOpen, setWorkflowSheetOpen] = useState(false);
|
||||
|
||||
const allFormValues = useWatch({ control: form.control });
|
||||
|
||||
const templateName = useWatch({
|
||||
control: form.control,
|
||||
name: `templates.${selectedTemplateIndex}.name`,
|
||||
});
|
||||
|
||||
return {
|
||||
jsonSheetOpen,
|
||||
setJsonSheetOpen,
|
||||
workflowSheetOpen,
|
||||
setWorkflowSheetOpen,
|
||||
allFormValues,
|
||||
templateName,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import { useMemo } from 'react';
|
||||
import { ArrayPath, UseFormReturn } from 'react-hook-form';
|
||||
|
||||
import {
|
||||
ICompilationTemplateBuiltin,
|
||||
ICompilationTemplateSection,
|
||||
} from '@/interfaces/database/compilation-template';
|
||||
import { FormSchemaType } from '../schema';
|
||||
|
||||
export const useTemplateSectionData = (
|
||||
form: UseFormReturn<FormSchemaType>,
|
||||
selectedTemplateIndex: number,
|
||||
activeSectionTab: string,
|
||||
builtinTemplate: ICompilationTemplateBuiltin | undefined,
|
||||
editingFieldIndex: number | null,
|
||||
) => {
|
||||
const activeSectionPath = `templates.${selectedTemplateIndex}.config.${activeSectionTab}`;
|
||||
const activeFieldsPath =
|
||||
`${activeSectionPath}.fields` as ArrayPath<FormSchemaType>;
|
||||
|
||||
const builtinSection = useMemo(() => {
|
||||
return builtinTemplate?.config?.[activeSectionTab] as
|
||||
| ICompilationTemplateSection
|
||||
| undefined;
|
||||
}, [activeSectionTab, builtinTemplate?.config]);
|
||||
|
||||
const editingField = useMemo(() => {
|
||||
if (editingFieldIndex === null) return undefined;
|
||||
return ((form.getValues(activeFieldsPath) as
|
||||
| Record<string, string>[]
|
||||
| undefined) ?? [])[editingFieldIndex];
|
||||
}, [activeFieldsPath, editingFieldIndex, form]);
|
||||
|
||||
return {
|
||||
activeSectionPath,
|
||||
activeFieldsPath,
|
||||
builtinSection,
|
||||
editingField,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,85 @@
|
||||
import BackButton from '@/components/back-button';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Form } from '@/components/ui/form';
|
||||
import { CompilationTemplateKind } from '@/constants/compilation';
|
||||
import { useFetchCompilationTemplateGroup } from '@/hooks/use-compilation-template-group-request';
|
||||
import { Routes } from '@/routes';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useWatch } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useNavigate } from 'react-router';
|
||||
|
||||
import { BlueprintSection } from './components/blueprint-section';
|
||||
import { TemplateConfiguration } from './components/template-configuration';
|
||||
import { useEditNextCompilationTemplateGroup } from './hooks/use-edit-next-compilation-template-group';
|
||||
|
||||
const SelectedTemplateIndex = 0;
|
||||
|
||||
const agentsUrl = Routes.Agents;
|
||||
|
||||
export default function EditNextCompilationTemplate() {
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const navigateToAgents = useCallback(() => {
|
||||
navigate(agentsUrl);
|
||||
}, [navigate]);
|
||||
|
||||
const { form, kindOptions, builtins, onSubmit, isCreate, isLoading } =
|
||||
useEditNextCompilationTemplateGroup({
|
||||
onSuccess: navigateToAgents,
|
||||
});
|
||||
const { data: group } = useFetchCompilationTemplateGroup();
|
||||
|
||||
const selectedKind = useWatch({
|
||||
control: form.control,
|
||||
name: `templates.${SelectedTemplateIndex}.kind`,
|
||||
});
|
||||
|
||||
const isArtifacts = selectedKind === CompilationTemplateKind.Artifacts;
|
||||
|
||||
const handleSave = useMemo(
|
||||
() => form.handleSubmit(onSubmit),
|
||||
[form, onSubmit],
|
||||
);
|
||||
|
||||
return (
|
||||
<section className="h-full flex flex-col bg-bg-base">
|
||||
<header className="shrink-0 px-5 py-4 border-b border-border-button flex gap-3 items-center">
|
||||
<BackButton to={agentsUrl} />
|
||||
<h2 className="font-medium text-text-secondary">
|
||||
{isCreate
|
||||
? t('setting.addTemplateGroup')
|
||||
: group?.name || t('setting.editTemplateGroup')}
|
||||
</h2>
|
||||
</header>
|
||||
|
||||
<Form {...form}>
|
||||
<form className="flex-1 min-h-0 flex flex-col">
|
||||
<TemplateConfiguration
|
||||
form={form}
|
||||
builtins={builtins}
|
||||
kindOptions={kindOptions}
|
||||
selectedTemplateIndex={SelectedTemplateIndex}
|
||||
>
|
||||
{isArtifacts && (
|
||||
<BlueprintSection
|
||||
form={form}
|
||||
selectedTemplateIndex={SelectedTemplateIndex}
|
||||
/>
|
||||
)}
|
||||
</TemplateConfiguration>
|
||||
|
||||
<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={navigateToAgents}>
|
||||
{t('common.back')}
|
||||
</Button>
|
||||
<Button type="button" loading={isLoading} onClick={handleSave}>
|
||||
{t('common.save')}
|
||||
</Button>
|
||||
</footer>
|
||||
</form>
|
||||
</Form>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const buildSectionSchema = (t: (key: string) => string) =>
|
||||
z.object({
|
||||
description: z.string().optional(),
|
||||
fields: z
|
||||
.array(z.record(z.string().min(1, t('setting.fieldDescriptionRequired'))))
|
||||
.min(1),
|
||||
});
|
||||
|
||||
export const buildRaptorConfigSchema = (t: (key: string) => string) =>
|
||||
z.object({
|
||||
prompt: z.string().optional(),
|
||||
max_token: z.number().min(1, t('setting.maxTokenRequired')),
|
||||
threshold: z.number().min(0).max(1),
|
||||
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(),
|
||||
name: z.string().min(1, t('setting.templateNameRequired')),
|
||||
description: z.string().optional(),
|
||||
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),
|
||||
buildSynthesisSchema(),
|
||||
z.string(),
|
||||
z.boolean(),
|
||||
]),
|
||||
),
|
||||
});
|
||||
|
||||
export const buildFormSchema = (t: (key: string) => string) =>
|
||||
z.object({
|
||||
name: z.string().optional(),
|
||||
description: z.string().optional(),
|
||||
avatar: z.string().optional(),
|
||||
templates: z.array(buildTemplateSchema(t)).min(1),
|
||||
});
|
||||
|
||||
export type TemplateSchemaType = z.infer<
|
||||
ReturnType<typeof buildTemplateSchema>
|
||||
>;
|
||||
export type FormSchemaType = z.infer<ReturnType<typeof buildFormSchema>>;
|
||||
@@ -0,0 +1,325 @@
|
||||
import { isEqual } from 'lodash';
|
||||
|
||||
import {
|
||||
ICompilationTemplate,
|
||||
ICompilationTemplateBuiltin,
|
||||
ICompilationTemplateGroup,
|
||||
ICompilationTemplateRaptorConfig,
|
||||
ICompilationTemplateSection,
|
||||
} from '@/interfaces/database/compilation-template';
|
||||
import { ICompilationTemplateConfigRequest } from '@/interfaces/request/compilation-template';
|
||||
|
||||
import { CompilationTemplateKind } from '@/constants/compilation';
|
||||
|
||||
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'],
|
||||
['definition_excerpt', 'term'],
|
||||
];
|
||||
|
||||
export const getFieldKeyOrder = (keys: string[]): string[] => {
|
||||
const sortedKeys = [...keys].sort();
|
||||
return (
|
||||
FieldKeyOrders.find((order) => isEqual([...order].sort(), sortedKeys)) ??
|
||||
keys
|
||||
);
|
||||
};
|
||||
|
||||
export const DefaultTemplateValues: TemplateSchemaType = {
|
||||
id: undefined,
|
||||
name: '',
|
||||
description: '',
|
||||
llm_id: '',
|
||||
kind: '',
|
||||
config: {
|
||||
kind: '',
|
||||
llm_id: '',
|
||||
global_rules: '',
|
||||
example: '',
|
||||
instruction: '',
|
||||
page_example: '',
|
||||
use_blueprint: false,
|
||||
},
|
||||
};
|
||||
|
||||
export const DefaultValues: FormSchemaType = {
|
||||
name: '',
|
||||
description: '',
|
||||
avatar: '',
|
||||
templates: [DefaultTemplateValues],
|
||||
};
|
||||
|
||||
export const isConfigMetaKey = (key: string) =>
|
||||
[
|
||||
'kind',
|
||||
'llm_id',
|
||||
'global_rules',
|
||||
'example',
|
||||
'instruction',
|
||||
'page_example',
|
||||
'synthesis',
|
||||
'use_blueprint',
|
||||
].includes(key);
|
||||
|
||||
export const createEmptyField = (keys: string[]) =>
|
||||
Object.fromEntries(keys.map((key) => [key, '']));
|
||||
|
||||
export const normalizeSection = (
|
||||
section?: ICompilationTemplateSection,
|
||||
): ICompilationTemplateSection => {
|
||||
const fields = section?.fields ?? [];
|
||||
return {
|
||||
description: section?.description ?? '',
|
||||
fields:
|
||||
fields.length > 0
|
||||
? fields.map((field) =>
|
||||
Object.fromEntries(
|
||||
Object.entries(field).map(([key, value]) => [key, value ?? '']),
|
||||
),
|
||||
)
|
||||
: [createEmptyField(DefaultFieldKeys)],
|
||||
};
|
||||
};
|
||||
|
||||
export const buildConfigFromBuiltin = (
|
||||
builtinTemplate: ICompilationTemplateBuiltin,
|
||||
kind: string,
|
||||
llmId: string,
|
||||
): TemplateSchemaType['config'] => {
|
||||
const example =
|
||||
typeof builtinTemplate.config?.example === 'string'
|
||||
? builtinTemplate.config.example
|
||||
: '';
|
||||
const sections: TemplateSchemaType['config'] = {
|
||||
kind,
|
||||
llm_id: llmId,
|
||||
global_rules:
|
||||
typeof builtinTemplate.config?.global_rules === 'string'
|
||||
? builtinTemplate.config.global_rules
|
||||
: '',
|
||||
example:
|
||||
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 ?? {};
|
||||
return {
|
||||
...sections,
|
||||
raptor: {
|
||||
prompt: builtinRaptor.prompt ?? '',
|
||||
max_token: builtinRaptor.max_token ?? 512,
|
||||
threshold: builtinRaptor.threshold ?? 0.1,
|
||||
rechunk: builtinRaptor.rechunk ?? false,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
Object.entries(builtinTemplate.config ?? {}).forEach(([key, value]) => {
|
||||
if (isConfigMetaKey(key)) return;
|
||||
sections[key] = normalizeSection(
|
||||
value as ICompilationTemplateSection,
|
||||
) as TemplateSchemaType['config'][string];
|
||||
});
|
||||
|
||||
return sections;
|
||||
};
|
||||
|
||||
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 {
|
||||
id: detail.id,
|
||||
name: detail.name ?? '',
|
||||
description: detail.description ?? '',
|
||||
llm_id: config.llm_id ?? '',
|
||||
kind: detail.kind ?? '',
|
||||
config: {
|
||||
...base,
|
||||
raptor: {
|
||||
prompt: raptor.prompt ?? '',
|
||||
max_token: raptor.max_token ?? 512,
|
||||
threshold: raptor.threshold ?? 0.1,
|
||||
rechunk: raptor.rechunk ?? false,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
Object.entries(config).forEach(([key, value]) => {
|
||||
if (isConfigMetaKey(key)) return;
|
||||
base[key] = normalizeSection(
|
||||
value as ICompilationTemplateSection,
|
||||
) as TemplateSchemaType['config'][string];
|
||||
});
|
||||
|
||||
return {
|
||||
id: detail.id,
|
||||
name: detail.name ?? '',
|
||||
description: detail.description ?? '',
|
||||
llm_id: config.llm_id ?? '',
|
||||
kind: detail.kind ?? '',
|
||||
config: base,
|
||||
};
|
||||
};
|
||||
|
||||
export const transformGroupDetailToForm = (
|
||||
detail: ICompilationTemplateGroup,
|
||||
): FormSchemaType => {
|
||||
const templates = (detail.templates ?? []).map((template) =>
|
||||
transformDetailToForm(template),
|
||||
);
|
||||
|
||||
return {
|
||||
name: detail.name ?? '',
|
||||
description: detail.description ?? '',
|
||||
avatar: detail.avatar ?? '',
|
||||
templates: templates.length > 0 ? templates : [DefaultTemplateValues],
|
||||
};
|
||||
};
|
||||
|
||||
export const transformTemplateToPayload = (template: TemplateSchemaType) => {
|
||||
const config: ICompilationTemplateConfigRequest = {
|
||||
kind: template.kind,
|
||||
llm_id: template.llm_id,
|
||||
};
|
||||
|
||||
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 {
|
||||
config[key] = value as ICompilationTemplateConfigRequest[string];
|
||||
}
|
||||
});
|
||||
|
||||
if (template.kind === CompilationTemplateKind.Artifacts) {
|
||||
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 {
|
||||
id: template.id,
|
||||
name: template.name,
|
||||
description: template.description,
|
||||
kind: template.kind,
|
||||
config,
|
||||
};
|
||||
};
|
||||
|
||||
export const transformFormToPayload = (values: FormSchemaType) => {
|
||||
const template = values.templates[0];
|
||||
return {
|
||||
name: template?.name ?? values.name ?? '',
|
||||
description: template?.description ?? values.description,
|
||||
avatar: values.avatar || undefined,
|
||||
templates: values.templates.map((template) =>
|
||||
transformTemplateToPayload(template),
|
||||
),
|
||||
};
|
||||
};
|
||||
|
||||
export const SectionTitleKeyMap: Record<string, string> = {
|
||||
entity: 'setting.entitySpecification',
|
||||
relation: 'setting.relationSpecification',
|
||||
concept: 'setting.conceptSpecification',
|
||||
claim: 'setting.claimSpecification',
|
||||
};
|
||||
|
||||
export const SectionPriority = ['entity', 'relation'];
|
||||
|
||||
export const sortSectionNames = (names: string[]): string[] => {
|
||||
const priority = SectionPriority.filter((name) => names.includes(name));
|
||||
const rest = names.filter((name) => !SectionPriority.includes(name));
|
||||
return [...priority, ...rest];
|
||||
};
|
||||
|
||||
export const FieldLabelKeyMap: Record<string, string> = {
|
||||
type: 'setting.fieldType',
|
||||
description: 'setting.fieldDescription',
|
||||
rule: 'setting.fieldRule',
|
||||
};
|
||||
|
||||
export const getTypeOptionsFromBuiltinSection = (
|
||||
builtinSection?: ICompilationTemplateSection,
|
||||
) => {
|
||||
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 }));
|
||||
};
|
||||
@@ -27,7 +27,10 @@ export default function CompilationTemplates() {
|
||||
} = useFetchCompilationTemplateGroupsByPage();
|
||||
|
||||
const { deleteGroup } = useDeleteCompilationTemplateGroup();
|
||||
const { navigateToCompilationTemplate } = useNavigatePage();
|
||||
const {
|
||||
navigateToCompilationTemplate,
|
||||
navigateToCompilationTemplateEditNext,
|
||||
} = useNavigatePage();
|
||||
|
||||
const handlePageChange = useCallback(
|
||||
(page: number, pageSize?: number) => {
|
||||
@@ -40,6 +43,10 @@ export default function CompilationTemplates() {
|
||||
navigateToCompilationTemplate('create')();
|
||||
}, [navigateToCompilationTemplate]);
|
||||
|
||||
const handleAddNext = useCallback(() => {
|
||||
navigateToCompilationTemplateEditNext()();
|
||||
}, [navigateToCompilationTemplateEditNext]);
|
||||
|
||||
const handleDelete = useCallback(
|
||||
async (id: string) => {
|
||||
await deleteGroup(id);
|
||||
@@ -73,6 +80,10 @@ export default function CompilationTemplates() {
|
||||
<Plus />
|
||||
{t('setting.addTemplateGroup')}
|
||||
</Button>
|
||||
<Button onClick={handleAddNext}>
|
||||
<Plus />
|
||||
Next
|
||||
</Button>
|
||||
</div>
|
||||
</header>
|
||||
}
|
||||
@@ -85,7 +96,7 @@ export default function CompilationTemplates() {
|
||||
<TemplateCard
|
||||
key={item.id}
|
||||
data={item}
|
||||
onClick={navigateToCompilationTemplate(item.id)}
|
||||
onClick={navigateToCompilationTemplateEditNext(item.id)}
|
||||
onDelete={handleDelete}
|
||||
/>
|
||||
))}
|
||||
|
||||
@@ -6,6 +6,7 @@ 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';
|
||||
@@ -28,6 +29,7 @@ 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],
|
||||
@@ -63,7 +65,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">
|
||||
{formatKindLabel(kind)}
|
||||
{formatKindLabel(t, kind)}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -12,7 +12,6 @@ import { cn } from '@/lib/utils';
|
||||
import { Routes } from '@/routes';
|
||||
import { TFunction } from 'i18next';
|
||||
import {
|
||||
Columns3Cog,
|
||||
LucideBox,
|
||||
LucideLogOut,
|
||||
LucideMessagesSquare,
|
||||
@@ -47,11 +46,6 @@ const menuItems = (t: TFunction) => [
|
||||
label: 'MCP',
|
||||
key: Routes.Mcp,
|
||||
},
|
||||
{
|
||||
icon: <Columns3Cog className="size-[1em]" />,
|
||||
label: t('setting.compilationTemplates'),
|
||||
key: Routes.CompilationTemplates,
|
||||
},
|
||||
{
|
||||
icon: <LucideUsers className="size-[1em]" />,
|
||||
label: t('setting.team'),
|
||||
|
||||
Reference in New Issue
Block a user