perf: unify entities

This commit is contained in:
Junyan Qin
2025-05-08 18:09:52 +08:00
parent ef6be4dfd9
commit e74de068ea
19 changed files with 400 additions and 632 deletions
+2 -11
View File
@@ -40,9 +40,8 @@ stages:
label: label:
en_US: Model en_US: Model
zh_CN: 模型 zh_CN: 模型
type: select type: llm-model-selector
required: true required: true
scope: /provider/models/llm
- name: max-round - name: max-round
label: label:
en_US: Max Round en_US: Max Round
@@ -54,16 +53,8 @@ stages:
label: label:
en_US: Prompt en_US: Prompt
zh_CN: 提示词 zh_CN: 提示词
type: array type: prompt-editor
required: true required: true
items:
type: object
properties:
role:
type: string
default: user
content:
type: string
- name: dify-service-api - name: dify-service-api
label: label:
en_US: Dify Service API en_US: Dify Service API
+6 -18
View File
@@ -28,11 +28,9 @@ stages:
description: description:
en_US: The prefix of the message en_US: The prefix of the message
zh_CN: 消息前缀 zh_CN: 消息前缀
type: array type: array[string]
required: true required: true
default: [] default: []
items:
type: string
- name: regexp - name: regexp
label: label:
en_US: Regexp en_US: Regexp
@@ -40,11 +38,9 @@ stages:
description: description:
en_US: The regexp of the message en_US: The regexp of the message
zh_CN: 消息正则表达式 zh_CN: 消息正则表达式
type: array type: array[string]
required: true required: true
default: [] default: []
items:
type: string
- name: random - name: random
label: label:
en_US: Random en_US: Random
@@ -83,20 +79,16 @@ stages:
label: label:
en_US: Blacklist en_US: Blacklist
zh_CN: 黑名单 zh_CN: 黑名单
type: array type: array[string]
required: true required: true
default: [] default: []
items:
type: string
- name: whitelist - name: whitelist
label: label:
en_US: Whitelist en_US: Whitelist
zh_CN: 白名单 zh_CN: 白名单
type: array type: array[string]
required: true required: true
default: [] default: []
items:
type: string
- name: ignore-rules - name: ignore-rules
label: label:
en_US: Ignore Rules en_US: Ignore Rules
@@ -109,11 +101,9 @@ stages:
description: description:
en_US: The prefix of the message en_US: The prefix of the message
zh_CN: 消息前缀 zh_CN: 消息前缀
type: array type: array[string]
required: true required: true
default: [] default: []
items:
type: string
- name: regexp - name: regexp
label: label:
en_US: Regexp en_US: Regexp
@@ -121,8 +111,6 @@ stages:
description: description:
en_US: The regexp of the message en_US: The regexp of the message
zh_CN: 消息正则表达式 zh_CN: 消息正则表达式
type: array type: array[string]
required: true required: true
default: [] default: []
items:
type: string
+31
View File
@@ -16,6 +16,7 @@
"@radix-ui/react-select": "^2.2.4", "@radix-ui/react-select": "^2.2.4",
"@radix-ui/react-slot": "^1.2.2", "@radix-ui/react-slot": "^1.2.2",
"@radix-ui/react-switch": "^1.2.4", "@radix-ui/react-switch": "^1.2.4",
"@radix-ui/react-tabs": "^1.1.11",
"@radix-ui/react-toggle": "^1.1.8", "@radix-ui/react-toggle": "^1.1.8",
"@radix-ui/react-toggle-group": "^1.1.9", "@radix-ui/react-toggle-group": "^1.1.9",
"@tailwindcss/postcss": "^4.1.5", "@tailwindcss/postcss": "^4.1.5",
@@ -1503,6 +1504,36 @@
} }
} }
}, },
"node_modules/@radix-ui/react-tabs": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.11.tgz",
"integrity": "sha512-4FiKSVoXqPP/KfzlB7lwwqoFV6EPwkrrqGp9cUYXjwDYHhvpnqq79P+EPHKcdoTE7Rl8w/+6s9rTlsfXHES9GA==",
"license": "MIT",
"dependencies": {
"@radix-ui/primitive": "1.1.2",
"@radix-ui/react-context": "1.1.2",
"@radix-ui/react-direction": "1.1.1",
"@radix-ui/react-id": "1.1.1",
"@radix-ui/react-presence": "1.1.4",
"@radix-ui/react-primitive": "2.1.2",
"@radix-ui/react-roving-focus": "1.1.9",
"@radix-ui/react-use-controllable-state": "1.2.2"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-toggle": { "node_modules/@radix-ui/react-toggle": {
"version": "1.1.8", "version": "1.1.8",
"resolved": "https://registry.npmjs.org/@radix-ui/react-toggle/-/react-toggle-1.1.8.tgz", "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle/-/react-toggle-1.1.8.tgz",
+1
View File
@@ -19,6 +19,7 @@
"@radix-ui/react-select": "^2.2.4", "@radix-ui/react-select": "^2.2.4",
"@radix-ui/react-slot": "^1.2.2", "@radix-ui/react-slot": "^1.2.2",
"@radix-ui/react-switch": "^1.2.4", "@radix-ui/react-switch": "^1.2.4",
"@radix-ui/react-tabs": "^1.1.11",
"@radix-ui/react-toggle": "^1.1.8", "@radix-ui/react-toggle": "^1.1.8",
"@radix-ui/react-toggle-group": "^1.1.9", "@radix-ui/react-toggle-group": "^1.1.9",
"@tailwindcss/postcss": "^4.1.5", "@tailwindcss/postcss": "^4.1.5",
@@ -2,10 +2,10 @@ import { useEffect, useState } from 'react';
import { IChooseAdapterEntity, IPipelineEntity } from '@/app/home/bots/components/bot-form/ChooseEntity'; import { IChooseAdapterEntity, IPipelineEntity } from '@/app/home/bots/components/bot-form/ChooseEntity';
import { import {
DynamicFormItemConfig, DynamicFormItemConfig,
IDynamicFormItemConfig,
getDefaultValues, getDefaultValues,
parseDynamicFormItemType, parseDynamicFormItemType,
} from '@/app/home/components/dynamic-form/DynamicFormItemConfig'; } from '@/app/home/components/dynamic-form/DynamicFormItemConfig';
import { IDynamicFormItemSchema } from '@/app/infra/entities/form/dynamic';
import { UUID } from 'uuidjs'; import { UUID } from 'uuidjs';
import DynamicFormComponent from '@/app/home/components/dynamic-form/DynamicFormComponent'; import DynamicFormComponent from '@/app/home/components/dynamic-form/DynamicFormComponent';
import { httpClient } from '@/app/infra/http/HttpClient'; import { httpClient } from '@/app/infra/http/HttpClient';
@@ -76,7 +76,7 @@ export default function BotForm({
const [showDeleteConfirmModal, setShowDeleteConfirmModal] = useState(false); const [showDeleteConfirmModal, setShowDeleteConfirmModal] = useState(false);
const [adapterNameToDynamicConfigMap, setAdapterNameToDynamicConfigMap] = const [adapterNameToDynamicConfigMap, setAdapterNameToDynamicConfigMap] =
useState(new Map<string, IDynamicFormItemConfig[]>()); useState(new Map<string, IDynamicFormItemSchema[]>());
// const [form] = Form.useForm<IBotFormEntity>(); // const [form] = Form.useForm<IBotFormEntity>();
const [showDynamicForm, setShowDynamicForm] = useState<boolean>(false); const [showDynamicForm, setShowDynamicForm] = useState<boolean>(false);
// const [dynamicForm] = Form.useForm(); // const [dynamicForm] = Form.useForm();
@@ -95,7 +95,7 @@ export default function BotForm({
>([]); >([]);
const [dynamicFormConfigList, setDynamicFormConfigList] = useState< const [dynamicFormConfigList, setDynamicFormConfigList] = useState<
IDynamicFormItemConfig[] IDynamicFormItemSchema[]
>([]); >([]);
const [isLoading, setIsLoading] = useState<boolean>(false); const [isLoading, setIsLoading] = useState<boolean>(false);
@@ -1,4 +1,4 @@
import { IDynamicFormItemConfig } from '@/app/home/components/dynamic-form/DynamicFormItemConfig'; import { IDynamicFormItemSchema } from '@/app/infra/entities/form/dynamic';
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod"; import { z } from "zod";
@@ -18,7 +18,7 @@ export default function DynamicFormComponent({
onSubmit, onSubmit,
initialValues, initialValues,
}: { }: {
itemConfigList: IDynamicFormItemConfig[]; itemConfigList: IDynamicFormItemSchema[];
onSubmit?: (val: object) => unknown; onSubmit?: (val: object) => unknown;
initialValues?: Record<string, any>; initialValues?: Record<string, any>;
}) { }) {
@@ -1,18 +1,18 @@
// import { Form, Input, InputNumber, Select, Switch } from 'antd';
import { import {
DynamicFormItemType, DynamicFormItemType,
IDynamicFormItemConfig, IDynamicFormItemSchema,
} from '@/app/home/components/dynamic-form/DynamicFormItemConfig'; } from '@/app/infra/entities/form/dynamic';
import { Input } from "@/components/ui/input" import { Input } from "@/components/ui/input"
import { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectTrigger, SelectValue } from "@/components/ui/select" import { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectTrigger, SelectValue } from "@/components/ui/select"
import { Checkbox } from "@/components/ui/checkbox" import { Checkbox } from "@/components/ui/checkbox"
import { ControllerRenderProps } from "react-hook-form"; import { ControllerRenderProps } from "react-hook-form";
import { Button } from "@/components/ui/button";
export default function DynamicFormItemComponent({ export default function DynamicFormItemComponent({
config, config,
field, field,
}: { }: {
config: IDynamicFormItemConfig; config: IDynamicFormItemSchema;
field: ControllerRenderProps<any, any>; field: ControllerRenderProps<any, any>;
}) { }) {
switch (config.type) { switch (config.type) {
@@ -39,21 +39,37 @@ export default function DynamicFormItemComponent({
case DynamicFormItemType.STRING_ARRAY: case DynamicFormItemType.STRING_ARRAY:
return ( return (
<Select <div className="space-y-2">
value={field.value} {field.value.map((item: string, index: number) => (
onValueChange={field.onChange} <div key={index} className="flex gap-2">
> <Input
<SelectTrigger> value={item}
<SelectValue placeholder="请选择" /> onChange={(e) => {
</SelectTrigger> const newValue = [...field.value];
<SelectContent> newValue[index] = e.target.value;
<SelectGroup> field.onChange(newValue);
{/* 这里需要根据实际情况添加选项 */} }}
<SelectItem value="option1">1</SelectItem> />
<SelectItem value="option2">2</SelectItem> <Button
</SelectGroup> variant="destructive"
</SelectContent> onClick={() => {
</Select> const newValue = field.value.filter((_: string, i: number) => i !== index);
field.onChange(newValue);
}}
>
</Button>
</div>
))}
<Button
variant="outline"
onClick={() => {
field.onChange([...field.value, '']);
}}
>
</Button>
</div>
); );
case DynamicFormItemType.SELECT: case DynamicFormItemType.SELECT:
@@ -67,9 +83,11 @@ export default function DynamicFormItemComponent({
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
<SelectGroup> <SelectGroup>
{/* 这里需要根据实际情况添加选项 */} {config.options?.map((option) => (
<SelectItem value="option1">1</SelectItem> <SelectItem key={option.name} value={option.name}>
<SelectItem value="option2">2</SelectItem> {option.label.zh_CN}
</SelectItem>
))}
</SelectGroup> </SelectGroup>
</SelectContent> </SelectContent>
</Select> </Select>
@@ -1,23 +1,17 @@
export interface IDynamicFormItemConfig { import { IDynamicFormItemSchema, DynamicFormItemType, IDynamicFormItemOption } from '@/app/infra/entities/form/dynamic';
id: string; import { I18nLabel } from '@/app/infra/entities/common';
default: string | number | boolean | Array<unknown>;
label: IDynamicFormItemLabel;
name: string;
required: boolean;
type: DynamicFormItemType;
description?: IDynamicFormItemLabel;
}
export class DynamicFormItemConfig implements IDynamicFormItemConfig { export class DynamicFormItemConfig implements IDynamicFormItemSchema {
id: string; id: string;
name: string; name: string;
default: string | number | boolean | Array<unknown>; default: string | number | boolean | Array<unknown>;
label: IDynamicFormItemLabel; label: I18nLabel;
required: boolean; required: boolean;
type: DynamicFormItemType; type: DynamicFormItemType;
description?: IDynamicFormItemLabel; description?: I18nLabel;
options?: IDynamicFormItemOption[];
constructor(params: IDynamicFormItemConfig) { constructor(params: IDynamicFormItemSchema) {
this.id = params.id; this.id = params.id;
this.name = params.name; this.name = params.name;
this.default = params.default; this.default = params.default;
@@ -25,23 +19,10 @@ export class DynamicFormItemConfig implements IDynamicFormItemConfig {
this.required = params.required; this.required = params.required;
this.type = params.type; this.type = params.type;
this.description = params.description; this.description = params.description;
this.options = params.options;
} }
} }
export interface IDynamicFormItemLabel {
en_US: string;
zh_CN: string;
}
export enum DynamicFormItemType {
INT = 'integer',
FLOAT = 'float',
BOOLEAN = 'boolean',
STRING = 'string',
STRING_ARRAY = 'array[string]',
SELECT = 'select',
UNKNOWN = 'unknown',
}
export function isDynamicFormItemType( export function isDynamicFormItemType(
value: string, value: string,
@@ -55,7 +36,7 @@ export function parseDynamicFormItemType(value: string): DynamicFormItemType {
return isDynamicFormItemType(value) ? value : DynamicFormItemType.UNKNOWN; return isDynamicFormItemType(value) ? value : DynamicFormItemType.UNKNOWN;
} }
export function getDefaultValues(itemConfigList: IDynamicFormItemConfig[]): Record<string, any> { export function getDefaultValues(itemConfigList: IDynamicFormItemSchema[]): Record<string, any> {
return itemConfigList.reduce((acc, item) => { return itemConfigList.reduce((acc, item) => {
acc[item.name] = item.default; acc[item.name] = item.default;
return acc; return acc;
@@ -1,10 +1,10 @@
import { import {
DynamicFormItemConfig,
DynamicFormItemType, DynamicFormItemType,
IDynamicFormItemConfig, IDynamicFormItemSchema,
} from '@/app/home/components/dynamic-form/DynamicFormItemConfig'; } from '@/app/infra/entities/form/dynamic';
import { DynamicFormItemConfig } from '@/app/home/components/dynamic-form/DynamicFormItemConfig';
export const testDynamicConfigList: IDynamicFormItemConfig[] = [ export const testDynamicConfigList: IDynamicFormItemSchema[] = [
new DynamicFormItemConfig({ new DynamicFormItemConfig({
default: '', default: '',
id: '111', id: '111',
@@ -3,7 +3,7 @@ import { ICreateLLMField } from '@/app/home/models/ICreateLLMField';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { IChooseRequesterEntity } from '@/app/home/models/component/llm-form/ChooseRequesterEntity'; import { IChooseRequesterEntity } from '@/app/home/models/component/llm-form/ChooseRequesterEntity';
import { httpClient } from '@/app/infra/http/HttpClient'; import { httpClient } from '@/app/infra/http/HttpClient';
import { LLMModel } from '@/app/infra/api/api-types'; import { LLMModel } from '@/app/infra/entities/api';
import { UUID } from 'uuidjs'; import { UUID } from 'uuidjs';
import { zodResolver } from "@hookform/resolvers/zod" import { zodResolver } from "@hookform/resolvers/zod"
@@ -178,7 +178,7 @@ export default function LLMForm({
const config = item.spec.config; const config = item.spec.config;
for (let i = 0; i < config.length; i++) { for (let i = 0; i < config.length; i++) {
if (config[i].name == 'base_url') { if (config[i].name == 'base_url') {
return config[i].default; return config[i].default?.toString() || '';
} }
} }
return ''; return '';
@@ -1,19 +1,27 @@
import { // import {
Form, // Form,
Button, // Button,
Switch, // Switch,
Select, // Select,
Input, // Input,
InputNumber, // InputNumber,
SelectProps, // SelectProps,
} from 'antd'; // } from 'antd';
import { CaretLeftOutlined, CaretRightOutlined } from '@ant-design/icons'; import { CaretLeftOutlined, CaretRightOutlined } from '@ant-design/icons';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import styles from './pipelineFormStyle.module.css'; import styles from './pipelineFormStyle.module.css';
import { httpClient } from '@/app/infra/http/HttpClient'; import { httpClient } from '@/app/infra/http/HttpClient';
import { LLMModel, Pipeline } from '@/app/infra/api/api-types'; import { LLMModel, Pipeline } from '@/app/infra/entities/api';
import { UUID } from 'uuidjs'; import { UUID } from 'uuidjs';
import { PipelineFormEntity } from '@/app/home/pipelines/components/pipeline-form/PipelineFormEntity'; import { PipelineFormEntity, PipelineConfigTab, PipelineConfigStage } from '@/app/infra/entities/pipeline';
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
import {
getDefaultValues,
parseDynamicFormItemType,
} from '@/app/home/components/dynamic-form/DynamicFormItemConfig';
import { IDynamicFormItemSchema } from '@/app/infra/entities/form/dynamic';
import DynamicFormComponent from '@/app/home/components/dynamic-form/DynamicFormComponent';
import { Button } from '@/components/ui/button';
export default function PipelineFormComponent({ export default function PipelineFormComponent({
initValues, initValues,
@@ -31,48 +39,67 @@ export default function PipelineFormComponent({
}) { }) {
const [nowFormIndex, setNowFormIndex] = useState<number>(0); const [nowFormIndex, setNowFormIndex] = useState<number>(0);
const [nowAIRunner, setNowAIRunner] = useState(''); const [nowAIRunner, setNowAIRunner] = useState('');
const [llmModelList, setLlmModelList] = useState<SelectProps['options']>([]); // const [llmModelList, setLlmModelList] = useState<SelectProps['options']>([]);
// 这里不好,可以改成enum等 // 这里不好,可以改成enum等
const formLabelList: FormLabel[] = [ const formLabelList: FormLabel[] = [
{ label: '基础', name: 'basic' }, { label: '基础信息', name: 'basic' },
{ label: 'AI能力', name: 'ai' }, { label: 'AI能力', name: 'ai' },
{ label: '触发条件', name: 'trigger' }, { label: '触发条件', name: 'trigger' },
{ label: '安全能力', name: 'safety' }, { label: '安全能力', name: 'safety' },
{ label: '输出处理', name: 'output' }, { label: '输出处理', name: 'output' },
]; ];
const [basicForm] = Form.useForm(); // const [basicForm] = Form.useForm();
const [aiForm] = Form.useForm(); // const [aiForm] = Form.useForm();
const [triggerForm] = Form.useForm(); // const [triggerForm] = Form.useForm();
const [safetyForm] = Form.useForm(); // const [safetyForm] = Form.useForm();
const [outputForm] = Form.useForm(); // const [outputForm] = Form.useForm();
const [aiConfigTabSchema, setAIConfigTabSchema] = useState<PipelineConfigTab>();
const [triggerConfigTabSchema, setTriggerConfigTabSchema] = useState<PipelineConfigTab>();
const [safetyConfigTabSchema, setSafetyConfigTabSchema] = useState<PipelineConfigTab>();
const [outputConfigTabSchema, setOutputConfigTabSchema] = useState<PipelineConfigTab>();
useEffect(() => { useEffect(() => {
getLLMModelList(); getLLMModelList();
// get config schema from metadata
httpClient.getGeneralPipelineMetadata().then((resp) => {
for (const config of resp.configs) {
if (config.name === 'ai') {
setAIConfigTabSchema(config);
} else if (config.name === 'trigger') {
setTriggerConfigTabSchema(config);
} else if (config.name === 'safety') {
setSafetyConfigTabSchema(config);
} else if (config.name === 'output') {
setOutputConfigTabSchema(config);
}
}
});
}, []); }, []);
useEffect(() => { // useEffect(() => {
console.log('initValues change: ', initValues); // console.log('initValues change: ', initValues);
if (initValues) { // if (initValues) {
basicForm.setFieldsValue(initValues.basic); // // basicForm.setFieldsValue(initValues.basic);
aiForm.setFieldsValue(initValues.ai); // // aiForm.setFieldsValue(initValues.ai);
triggerForm.setFieldsValue(initValues.trigger); // // triggerForm.setFieldsValue(initValues.trigger);
safetyForm.setFieldsValue(initValues.safety); // // safetyForm.setFieldsValue(initValues.safety);
outputForm.setFieldsValue(initValues.output); // // outputForm.setFieldsValue(initValues.output);
} // }
}, [aiForm, basicForm, initValues, outputForm, safetyForm, triggerForm]); // }, [aiForm, basicForm, initValues, outputForm, safetyForm, triggerForm]);
function getLLMModelList() { function getLLMModelList() {
httpClient httpClient
.getProviderLLMModels() .getProviderLLMModels()
.then((resp) => { .then((resp) => {
setLlmModelList( // setLlmModelList(
resp.models.map((model: LLMModel) => { // resp.models.map((model: LLMModel) => {
return { // return {
value: model.uuid, // value: model.uuid,
label: model.name, // label: model.name,
}; // };
}), // }),
); // );
}) })
.catch((err) => { .catch((err) => {
console.error('get LLM model list error', err); console.error('get LLM model list error', err);
@@ -120,511 +147,89 @@ export default function PipelineFormComponent({
} }
function handleCreate() { function handleCreate() {
Promise.all([ // Promise.all([
basicForm.validateFields(), // // basicForm.validateFields(),
aiForm.validateFields(), // // aiForm.validateFields(),
triggerForm.validateFields(), // // triggerForm.validateFields(),
safetyForm.validateFields(), // // safetyForm.validateFields(),
outputForm.validateFields(), // // outputForm.validateFields(),
]) // ])
.then(() => { // .then(() => {
const pipeline = assembleForm(); // const pipeline = assembleForm();
httpClient.createPipeline(pipeline).then(() => onFinish()); // httpClient.createPipeline(pipeline).then(() => onFinish());
}) // })
.catch((e) => { // .catch((e) => {
console.error(e); // console.error(e);
}); // });
} }
function handleModify() { function handleModify() {
Promise.all([ // Promise.all([
basicForm.validateFields(), // // basicForm.validateFields(),
aiForm.validateFields(), // // aiForm.validateFields(),
triggerForm.validateFields(), // // triggerForm.validateFields(),
safetyForm.validateFields(), // // safetyForm.validateFields(),
outputForm.validateFields(), // // outputForm.validateFields(),
]) // ])
.then(() => { // .then(() => {
const pipeline = assembleForm(); // const pipeline = assembleForm();
httpClient // httpClient
.updatePipeline(pipelineId || '', pipeline) // .updatePipeline(pipelineId || '', pipeline)
.then(() => onFinish()); // .then(() => onFinish());
}) // })
.catch((e) => { // .catch((e) => {
console.error(e); // console.error(e);
}); // });
} }
// TODO 类型混乱,需要优化 // TODO 类型混乱,需要优化
function assembleForm(): Pipeline { // function assembleForm(): Pipeline {
console.log('basicForm:', basicForm.getFieldsValue()); // console.log('basicForm:', basicForm.getFieldsValue());
console.log('aiForm:', aiForm.getFieldsValue()); // console.log('aiForm:', aiForm.getFieldsValue());
console.log('triggerForm:', triggerForm.getFieldsValue()); // console.log('triggerForm:', triggerForm.getFieldsValue());
console.log('safetyForm:', safetyForm.getFieldsValue()); // console.log('safetyForm:', safetyForm.getFieldsValue());
console.log('outputForm:', outputForm.getFieldsValue()); // console.log('outputForm:', outputForm.getFieldsValue());
const config: object = { // const config: object = {
ai: aiForm.getFieldsValue(), // ai: aiForm.getFieldsValue(),
trigger: triggerForm.getFieldsValue(), // trigger: triggerForm.getFieldsValue(),
safety: safetyForm.getFieldsValue(), // safety: safetyForm.getFieldsValue(),
output: outputForm.getFieldsValue(), // output: outputForm.getFieldsValue(),
}; // };
return { // return {
config, // config,
created_at: '', // created_at: '',
description: basicForm.getFieldsValue().description, // description: basicForm.getFieldsValue().description,
for_version: '', // for_version: '',
name: basicForm.getFieldsValue().name, // name: basicForm.getFieldsValue().name,
stages: [], // stages: [],
updated_at: '', // updated_at: '',
uuid: UUID.generate(), // uuid: UUID.generate(),
}; // };
} // }
return ( return (
<div style={{ maxHeight: '70vh', overflowY: 'auto' }}> <div style={{ maxHeight: '70vh', overflowY: 'auto' }}>
<h1>{getNowFormLabel().label}</h1>
<Form
layout={'vertical'}
style={{
display: getNowFormLabel().name === 'basic' ? 'block' : 'none',
}}
form={basicForm}
disabled={disableForm}
>
<Form.Item
label="流水线名称"
name={'name'}
rules={[
{
required: true,
},
]}
>
<Input />
</Form.Item>
<Form.Item <Tabs defaultValue={getNowFormLabel().name}>
label="流水线描述" <TabsList>
name={'description'} {formLabelList.map((formLabel) => (
rules={[ <TabsTrigger key={formLabel.name} value={formLabel.name}>
{ {formLabel.label}
required: true, </TabsTrigger>
}, ))}
]} </TabsList>
> {formLabelList.map((formLabel) => (
<Input /> <TabsContent key={formLabel.name} value={formLabel.name}>
</Form.Item> <h1>{formLabel.label}</h1>
</Form> <div>name: {formLabel.name}</div>
{/* AI能力表单 ai */}
<Form </TabsContent>
layout={'vertical'} ))}
style={{ display: getNowFormLabel().name === 'ai' ? 'block' : 'none' }} </Tabs>
form={aiForm}
disabled={disableForm}
>
{/* Runner 配置区块 */}
<div className={`${styles.formItemSubtitle}`}></div>
<Form.Item
label="运行器"
name={['runner', 'runner']}
rules={[{ required: true }]}
>
<Select
options={[
{ label: '内置 Agent', value: 'local-agent' },
{ label: 'Dify 服务 API', value: 'dify-service-api' },
{ label: '阿里云百炼平台 API', value: 'dashscope-app-api' },
]}
onChange={(value) => setNowAIRunner(value)}
/>
</Form.Item>
{/* 内置 Agent 配置区块 */}
{nowAIRunner === 'local-agent' && (
<>
<div className={`${styles.formItemSubtitle}`}>Agent</div>
<Form.Item
label="模型"
name={['local-agent', 'model']}
rules={[{ required: true }]}
tooltip="从模型库中选择"
>
<Select
options={llmModelList}
placeholder="请选择语言模型"
showSearch
/>
</Form.Item>
<Form.Item
label="最大回合数"
name={['local-agent', 'max-round']}
rules={[
{
required: true,
},
]}
>
<InputNumber precision={0} />
</Form.Item>
{/* TODO 这里要做转换处理 */}
<Form.Item
label="提示词"
name={['local-agent', 'prompt']}
rules={[{ required: true }]}
tooltip="按JSON格式输入"
>
<Input.TextArea
rows={4}
placeholder={`示例结构:{ "role": "user", "content": "你好" } `}
/>
</Form.Item>
</>
)}
{/* Dify 服务 API 区块 */}
{nowAIRunner === 'dify-service-api' && (
<>
<div className={`${styles.formItemSubtitle}`}>Dify服务API</div>
<Form.Item
label="基础 URL"
name={['dify-service-api', 'base-url']}
rules={[
{ required: true },
{ type: 'url', message: '请输入有效的URL地址' },
]}
>
<Input />
</Form.Item>
<Form.Item
label="应用类型"
name={['dify-service-api', 'app-type']}
initialValue={'chat'}
rules={[{ required: true }]}
>
<Select
options={[
{ label: '聊天(包括Chatflow', value: 'chat' },
{ label: 'Agent', value: 'agent' },
{ label: '工作流', value: 'workflow' },
]}
/>
</Form.Item>
<Form.Item
label="API 密钥"
name={['dify-service-api', 'api-key']}
rules={[{ required: true }]}
>
<Input.Password visibilityToggle={false} />
</Form.Item>
<Form.Item
label="思维链转换"
name={['dify-service-api', 'thinking-convert']}
rules={[{ required: true }]}
>
<Select
options={[
{ label: '转换成 \<think\>...\<\/think\>', value: 'plain' },
{ label: '原始', value: 'original' },
{ label: '移除', value: 'remove' },
]}
/>
</Form.Item>
</>
)}
{/* 阿里云百炼区块 */}
{nowAIRunner === 'dashscope-app-api' && (
<>
<div className={`${styles.formItemSubtitle}`}>
API
</div>
<Form.Item
label="应用类型"
name={['dashscope-app-api', 'app-type']}
rules={[{ required: true }]}
>
<Select
options={[
{ label: 'Agent', value: 'agent' },
{ label: '工作流', value: 'workflow' },
]}
/>
</Form.Item>
<Form.Item
label="API 密钥"
name={['dashscope-app-api', 'api-key']}
rules={[{ required: true }]}
>
<Input.Password visibilityToggle={false} />
</Form.Item>
<Form.Item
label="应用 ID"
name={['dashscope-app-api', 'app-id']}
rules={[{ required: true }]}
>
<Input />
</Form.Item>
<Form.Item
label="引用文本"
name={['dashscope-app-api', 'references_quote']}
initialValue={'参考资料来自:'}
>
<Input.TextArea rows={2} />
</Form.Item>
</>
)}
</Form>
{/* 触发条件表单 trigger */} {/* <div className={`${styles.changeFormButtonGroupContainer}`}>
<Form
layout={'vertical'}
style={{
display: getNowFormLabel().name === 'trigger' ? 'block' : 'none',
}}
form={triggerForm}
disabled={disableForm}
>
{/* 群响应规则块 */}
<div className={`${styles.formItemSubtitle}`}> </div>
<Form.Item
label={'是否在消息@机器人时触发'}
name={['group-respond-rules', 'at']}
rules={[{ required: true }]}
>
<Switch />
</Form.Item>
<Form.Item
label={'消息前缀'}
name={['group-respond-rules', 'prefix']}
rules={[{ required: true }]}
>
<Select
options={[{ value: '"type": "string"', label: '"type": "string"' }]}
/>
</Form.Item>
<Form.Item
label={'正则表达式'}
name={['group-respond-rules', 'regexp']}
rules={[{ required: true }]}
>
<Select mode="tags" options={[]} />
</Form.Item>
<Form.Item
label={'随机'}
name={['group-respond-rules', 'random']}
rules={[{ required: false }]}
>
<InputNumber max={1} min={0} step={0.05} />
</Form.Item>
<div className={`${styles.formItemSubtitle}`}> 访 </div>
<Form.Item
label={'模式'}
name={['access-control', 'mode']}
rules={[{ required: true }]}
tooltip={'访问控制模式'}
>
<Select
options={[
{ label: '黑名单', value: 'blacklist' },
{ label: '白名单', value: 'Whitelist' },
]}
/>
</Form.Item>
<Form.Item
label={'黑名单'}
name={['access-control', 'blacklist']}
rules={[{ required: true }]}
>
<Select mode={'tags'} options={[]} />
</Form.Item>
<Form.Item
label={'白名单'}
name={['access-control', 'whitelist']}
rules={[{ required: true }]}
>
<Select mode={'tags'} options={[]} />
</Form.Item>
<div className={`${styles.formItemSubtitle}`}> </div>
<Form.Item
label={'前缀'}
name={['ignore-rules', 'whitelist']}
rules={[{ required: true }]}
tooltip={'消息前缀'}
>
<Select mode={'tags'} options={[]} />
</Form.Item>
<Form.Item
label={'正则表达式'}
name={['ignore-rules', 'regexp']}
rules={[{ required: true }]}
tooltip={'消息正则表达式'}
>
<Select mode={'tags'} options={[]} />
</Form.Item>
</Form>
{/* 安全控制表单 safety */}
<Form
layout={'vertical'}
style={{
display: getNowFormLabel().name === 'safety' ? 'block' : 'none',
}}
form={safetyForm}
disabled={disableForm}
>
{/* 内容过滤块 content-filter */}
<div className={`${styles.formItemSubtitle}`}> </div>
<Form.Item
label={'检查范围'}
name={['content-filter', 'scope']}
rules={[{ required: true }]}
>
<Select
options={[
{ label: '全部', value: 'all' },
{ label: '传入消息(用户消息)', value: 'income-msg' },
{ label: '传出消息(机器人消息)', value: 'output-msg' },
]}
/>
</Form.Item>
<Form.Item
label={'检查敏感词'}
name={['content-filter', 'check-sensitive-words']}
rules={[{ required: true }]}
>
<Switch />
</Form.Item>
{/* 速率限制块 rate-limit */}
<div className={`${styles.formItemSubtitle}`}> </div>
<Form.Item
label={'窗口长度(秒)'}
name={['rate-limit', 'window-length']}
rules={[{ required: true }]}
initialValue={60}
>
<InputNumber></InputNumber>
</Form.Item>
<Form.Item
label={'限制次数'}
name={['rate-limit', 'limitation']}
rules={[{ required: true }]}
initialValue={60}
>
<InputNumber />
</Form.Item>
<Form.Item
label={'策略'}
name={['rate-limit', 'strategy']}
rules={[{ required: true }]}
initialValue={'drop'}
>
<Select
options={[
{ label: '丢弃', value: 'drop' },
{ label: '等待', value: 'wait' },
]}
/>
</Form.Item>
</Form>
{/* 输出处理控制表单 output */}
<Form
layout={'vertical'}
style={{
display: getNowFormLabel().name === 'output' ? 'block' : 'none',
}}
form={outputForm}
disabled={disableForm}
>
{/* 长文本处理区块 */}
<div className={`${styles.formItemSubtitle}`}> </div>
<Form.Item
label="阈值"
name={['long-text-processing', 'threshold']}
rules={[{ required: true }]}
>
<InputNumber />
</Form.Item>
<Form.Item
label="策略"
name={['long-text-processing', 'strategy']}
rules={[{ required: true }]}
>
<Select
options={[
{ label: '转发消息组件', value: 'forward' },
{ label: '转换为图片', value: 'image' },
]}
/>
</Form.Item>
<Form.Item
label="字体路径"
name={['long-text-processing', 'font-path']}
rules={[{ required: true }]}
>
<Input />
</Form.Item>
{/* 强制延迟区块 */}
<div className={`${styles.formItemSubtitle}`}> </div>
<Form.Item
label="最小秒数"
name={['force-delay', 'min']}
rules={[{ required: true }]}
>
<InputNumber />
</Form.Item>
<Form.Item
label="最大秒数"
name={['force-delay', 'max']}
rules={[{ required: true }]}
>
<InputNumber />
</Form.Item>
{/* 杂项区块 */}
<div className={`${styles.formItemSubtitle}`}> </div>
<Form.Item
label="不输出异常信息给用户"
name={['misc', 'hide-exception']}
rules={[{ required: true }]}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
label="在回复中@发送者"
name={['misc', 'at-sender']}
rules={[{ required: true }]}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
label="引用原文"
name={['misc', 'quote-origin']}
rules={[{ required: true }]}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
label="跟踪函数调用"
name={['misc', 'track-function-calls']}
rules={[{ required: true }]}
valuePropName="checked"
>
<Switch />
</Form.Item>
</Form>
<div className={`${styles.changeFormButtonGroupContainer}`}>
<Button <Button
type="primary" type="primary"
icon={<CaretLeftOutlined />} icon={<CaretLeftOutlined />}
@@ -646,7 +251,7 @@ export default function PipelineFormComponent({
<Button type="primary" onClick={handleCommit}> <Button type="primary" onClick={handleCommit}>
提交 提交
</Button> </Button>
</div> </div> */}
</div> </div>
); );
} }
@@ -1,7 +0,0 @@
export interface PipelineFormEntity {
basic: object;
ai: object;
trigger: object;
safety: object;
output: object;
}
+30 -3
View File
@@ -1,5 +1,4 @@
'use client'; 'use client';
import { Modal } from 'antd';
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import CreateCardComponent from '@/app/infra/basic-component/create-card-component/CreateCardComponent'; import CreateCardComponent from '@/app/infra/basic-component/create-card-component/CreateCardComponent';
import PipelineFormComponent from './components/pipeline-form/PipelineFormComponent'; import PipelineFormComponent from './components/pipeline-form/PipelineFormComponent';
@@ -8,6 +7,14 @@ import { PipelineCardVO } from '@/app/home/pipelines/components/pipeline-card/Pi
import PipelineCard from '@/app/home/pipelines/components/pipeline-card/PipelineCard'; import PipelineCard from '@/app/home/pipelines/components/pipeline-card/PipelineCard';
import { PipelineFormEntity } from '@/app/home/pipelines/components/pipeline-form/PipelineFormEntity'; import { PipelineFormEntity } from '@/app/home/pipelines/components/pipeline-form/PipelineFormEntity';
import styles from './pipelineConfig.module.css'; import styles from './pipelineConfig.module.css';
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog"
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
export default function PluginConfigPage() { export default function PluginConfigPage() {
@@ -74,7 +81,7 @@ export default function PluginConfigPage() {
return ( return (
<div className={styles.configPageContainer}> <div className={styles.configPageContainer}>
<Modal {/* <Modal
title={isEditForm ? '编辑流水线' : '创建流水线'} title={isEditForm ? '编辑流水线' : '创建流水线'}
centered centered
open={modalOpen} open={modalOpen}
@@ -94,7 +101,27 @@ export default function PluginConfigPage() {
disableForm={disableForm} disableForm={disableForm}
initValues={selectedPipelineFormValue} initValues={selectedPipelineFormValue}
/> />
</Modal> </Modal> */}
<Dialog open={modalOpen} onOpenChange={setModalOpen}>
<DialogContent>
<DialogHeader>
<DialogTitle>
{isEditForm ? '编辑流水线' : '创建流水线'}
</DialogTitle>
</DialogHeader>
<PipelineFormComponent
onFinish={() => {
getPipelines();
setModalOpen(false);
}}
isEditMode={isEditForm}
pipelineId={selectedPipelineId}
disableForm={disableForm}
initValues={selectedPipelineFormValue}
/>
</DialogContent>
</Dialog>
{pipelineList.length > 0 && ( {pipelineList.length > 0 && (
<div className={styles.pipelineListContainer}> <div className={styles.pipelineListContainer}>
@@ -1,3 +1,7 @@
import { IDynamicFormItemSchema } from '@/app/infra/entities/form/dynamic';
import { I18nLabel } from '@/app/infra/entities/common';
import { PipelineConfigTab } from '@/app/infra/entities/pipeline';
export interface ApiResponse<T> { export interface ApiResponse<T> {
code: number; code: number;
data: T; data: T;
@@ -26,7 +30,9 @@ export interface Requester {
label: I18nText; label: I18nText;
description: I18nText; description: I18nText;
icon?: string; icon?: string;
spec: object; spec: {
config: IDynamicFormItemSchema[];
}
} }
export interface ApiRespProviderLLMModels { export interface ApiRespProviderLLMModels {
@@ -302,3 +308,7 @@ interface GetPipeline {
export interface GetPipelineResponseData { export interface GetPipelineResponseData {
pipeline: GetPipeline; pipeline: GetPipeline;
} }
export interface GetPipelineMetadataResponseData {
configs: PipelineConfigTab[];
}
+5
View File
@@ -0,0 +1,5 @@
export interface I18nLabel {
en_US: string;
zh_CN: string;
ja_JP?: string;
}
@@ -0,0 +1,29 @@
import { I18nLabel } from '@/app/infra/entities/common';
export interface IDynamicFormItemSchema {
id: string;
default: string | number | boolean | Array<unknown>;
label: I18nLabel;
name: string;
required: boolean;
type: DynamicFormItemType;
description?: I18nLabel;
options?: IDynamicFormItemOption[];
}
export enum DynamicFormItemType {
INT = 'integer',
FLOAT = 'float',
BOOLEAN = 'boolean',
STRING = 'string',
STRING_ARRAY = 'array[string]',
SELECT = 'select',
LLM_MODEL_SELECTOR = 'llm-model-selector',
PROMPT_EDITOR = 'prompt-editor',
UNKNOWN = 'unknown',
}
export interface IDynamicFormItemOption {
name: string;
label: I18nLabel;
}
@@ -0,0 +1,22 @@
import { I18nLabel } from '@/app/infra/entities/common';
import { IDynamicFormItemSchema } from '@/app/infra/entities/form/dynamic';
export interface PipelineFormEntity {
basic: object;
ai: object;
trigger: object;
safety: object;
output: object;
}
export interface PipelineConfigTab {
name: string;
label: I18nLabel;
stages: PipelineConfigStage[];
}
export interface PipelineConfigStage {
name: string;
label: I18nLabel;
config: IDynamicFormItemSchema[];
}
+3 -2
View File
@@ -28,7 +28,8 @@ import {
ApiRespUserToken, ApiRespUserToken,
MarketPluginResponse, MarketPluginResponse,
GetPipelineResponseData, GetPipelineResponseData,
} from '../api/api-types'; GetPipelineMetadataResponseData
} from '@/app/infra/entities/api';
import { notification } from 'antd'; import { notification } from 'antd';
type JSONValue = string | number | boolean | JSONObject | JSONArray | null; type JSONValue = string | number | boolean | JSONObject | JSONArray | null;
@@ -249,7 +250,7 @@ class HttpClient {
} }
// ============ Pipeline API ============ // ============ Pipeline API ============
public getGeneralPipelineMetadata(): Promise<object> { public getGeneralPipelineMetadata(): Promise<GetPipelineMetadataResponseData> {
// as designed, this method will be deprecated, and only for developer to check the prefered config schema // as designed, this method will be deprecated, and only for developer to check the prefered config schema
return this.get('/api/v1/pipelines/_/metadata'); return this.get('/api/v1/pipelines/_/metadata');
} }
+66
View File
@@ -0,0 +1,66 @@
"use client"
import * as React from "react"
import * as TabsPrimitive from "@radix-ui/react-tabs"
import { cn } from "@/lib/utils"
function Tabs({
className,
...props
}: React.ComponentProps<typeof TabsPrimitive.Root>) {
return (
<TabsPrimitive.Root
data-slot="tabs"
className={cn("flex flex-col gap-2", className)}
{...props}
/>
)
}
function TabsList({
className,
...props
}: React.ComponentProps<typeof TabsPrimitive.List>) {
return (
<TabsPrimitive.List
data-slot="tabs-list"
className={cn(
"bg-muted text-muted-foreground inline-flex h-9 w-fit items-center justify-center rounded-lg p-[3px]",
className
)}
{...props}
/>
)
}
function TabsTrigger({
className,
...props
}: React.ComponentProps<typeof TabsPrimitive.Trigger>) {
return (
<TabsPrimitive.Trigger
data-slot="tabs-trigger"
className={cn(
"data-[state=active]:bg-background dark:data-[state=active]:text-foreground focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring dark:data-[state=active]:border-input dark:data-[state=active]:bg-input/30 text-foreground dark:text-muted-foreground inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:shadow-sm [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className
)}
{...props}
/>
)
}
function TabsContent({
className,
...props
}: React.ComponentProps<typeof TabsPrimitive.Content>) {
return (
<TabsPrimitive.Content
data-slot="tabs-content"
className={cn("flex-1 outline-none", className)}
{...props}
/>
)
}
export { Tabs, TabsList, TabsTrigger, TabsContent }