mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-06-13 09:16:04 +00:00
Merge pull request #1375 from RockChinQ/feat/renderable-pipeline-config
feat: make pipeline config dynamic-form-renderable
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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
web/package-lock.json
generated
31
web/package-lock.json
generated
@@ -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",
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import BotForm from '@/app/home/bots/components/bot-form/BotForm';
|
|||||||
import BotCard from '@/app/home/bots/components/bot-card/BotCard';
|
import BotCard from '@/app/home/bots/components/bot-card/BotCard';
|
||||||
import CreateCardComponent from '@/app/infra/basic-component/create-card-component/CreateCardComponent';
|
import CreateCardComponent from '@/app/infra/basic-component/create-card-component/CreateCardComponent';
|
||||||
import { httpClient } from '@/app/infra/http/HttpClient';
|
import { httpClient } from '@/app/infra/http/HttpClient';
|
||||||
import { Bot, Adapter } from '@/app/infra/api/api-types';
|
import { Bot, Adapter } from '@/app/infra/entities/api';
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
@@ -20,27 +20,14 @@ import {
|
|||||||
} from "@/components/ui/dialog"
|
} from "@/components/ui/dialog"
|
||||||
|
|
||||||
export default function BotConfigPage() {
|
export default function BotConfigPage() {
|
||||||
const router = useRouter();
|
|
||||||
const [pageShowRule, setPageShowRule] = useState<BotConfigPageShowRule>(
|
|
||||||
BotConfigPageShowRule.NO_BOT,
|
|
||||||
);
|
|
||||||
const [modalOpen, setModalOpen] = useState<boolean>(false);
|
const [modalOpen, setModalOpen] = useState<boolean>(false);
|
||||||
const [botList, setBotList] = useState<BotCardVO[]>([]);
|
const [botList, setBotList] = useState<BotCardVO[]>([]);
|
||||||
const [isEditForm, setIsEditForm] = useState(false);
|
const [isEditForm, setIsEditForm] = useState(false);
|
||||||
const [nowSelectedBotCard, setNowSelectedBotCard] = useState<BotCardVO>();
|
const [nowSelectedBotCard, setNowSelectedBotCard] = useState<BotCardVO>();
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// TODO:补齐加载转圈逻辑
|
// TODO:补齐加载转圈逻辑
|
||||||
setIsLoading(true);
|
|
||||||
checkHasLLM().then((hasLLM) => {
|
|
||||||
if (hasLLM) {
|
|
||||||
getBotList();
|
|
||||||
} else {
|
|
||||||
setPageShowRule(BotConfigPageShowRule.NO_LLM);
|
|
||||||
setIsLoading(false);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
async function checkHasLLM(): Promise<boolean> {
|
async function checkHasLLM(): Promise<boolean> {
|
||||||
@@ -49,7 +36,6 @@ export default function BotConfigPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function getBotList() {
|
async function getBotList() {
|
||||||
setIsLoading(true);
|
|
||||||
|
|
||||||
const adapterListResp = await httpClient.getAdapters();
|
const adapterListResp = await httpClient.getAdapters();
|
||||||
const adapterList = adapterListResp.adapters.map((adapter: Adapter) => {
|
const adapterList = adapterListResp.adapters.map((adapter: Adapter) => {
|
||||||
@@ -72,11 +58,6 @@ export default function BotConfigPage() {
|
|||||||
usePipelineName: bot.use_pipeline_name || '',
|
usePipelineName: bot.use_pipeline_name || '',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
if (botList.length === 0) {
|
|
||||||
setPageShowRule(BotConfigPageShowRule.NO_BOT);
|
|
||||||
} else {
|
|
||||||
setPageShowRule(BotConfigPageShowRule.HAVE_BOT);
|
|
||||||
}
|
|
||||||
setBotList(botList);
|
setBotList(botList);
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
@@ -89,7 +70,7 @@ export default function BotConfigPage() {
|
|||||||
// });
|
// });
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
setIsLoading(false);
|
// setIsLoading(false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,46 +93,6 @@ export default function BotConfigPage() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.configPageContainer}>
|
<div className={styles.configPageContainer}>
|
||||||
{/* <Spin spinning={isLoading} tip="加载中..." size="large">
|
|
||||||
<Modal
|
|
||||||
title={isEditForm ? '编辑机器人' : '创建机器人'}
|
|
||||||
centered
|
|
||||||
open={modalOpen}
|
|
||||||
onOk={() => setModalOpen(false)}
|
|
||||||
onCancel={() => setModalOpen(false)}
|
|
||||||
width={700}
|
|
||||||
footer={null}
|
|
||||||
destroyOnClose={true}
|
|
||||||
>
|
|
||||||
<BotForm
|
|
||||||
initBotId={nowSelectedBotCard?.id}
|
|
||||||
onFormSubmit={() => {
|
|
||||||
getBotList();
|
|
||||||
setModalOpen(false);
|
|
||||||
}}
|
|
||||||
onFormCancel={() => setModalOpen(false)}
|
|
||||||
/>
|
|
||||||
</Modal>
|
|
||||||
{pageShowRule === BotConfigPageShowRule.NO_LLM && (
|
|
||||||
<EmptyAndCreateComponent
|
|
||||||
title={'需要先创建大模型才能配置机器人哦~'}
|
|
||||||
subTitle={'快去创建一个吧!'}
|
|
||||||
buttonText={'创建大模型 GO!'}
|
|
||||||
onButtonClick={() => {
|
|
||||||
router.push('/home/models');
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{pageShowRule === BotConfigPageShowRule.NO_BOT && (
|
|
||||||
<EmptyAndCreateComponent
|
|
||||||
title={'您还未配置机器人哦~'}
|
|
||||||
subTitle={'快去创建一个吧!'}
|
|
||||||
buttonText={'创建机器人 +'}
|
|
||||||
onButtonClick={handleCreateBotClick}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Spin> */}
|
|
||||||
|
|
||||||
<Dialog open={modalOpen} onOpenChange={setModalOpen}>
|
<Dialog open={modalOpen} onOpenChange={setModalOpen}>
|
||||||
<DialogContent className="w-[700px] max-h-[80vh] p-0 flex flex-col">
|
<DialogContent className="w-[700px] max-h-[80vh] p-0 flex flex-col">
|
||||||
@@ -176,7 +117,6 @@ export default function BotConfigPage() {
|
|||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
{/* 注意:其余的返回内容需要保持在Spin组件外部 */}
|
{/* 注意:其余的返回内容需要保持在Spin组件外部 */}
|
||||||
{pageShowRule === BotConfigPageShowRule.HAVE_BOT && (
|
|
||||||
<div className={`${styles.botListContainer}`}>
|
<div className={`${styles.botListContainer}`}>
|
||||||
|
|
||||||
<CreateCardComponent
|
<CreateCardComponent
|
||||||
@@ -198,13 +138,6 @@ export default function BotConfigPage() {
|
|||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
enum BotConfigPageShowRule {
|
|
||||||
NO_LLM,
|
|
||||||
NO_BOT,
|
|
||||||
HAVE_BOT,
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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>;
|
||||||
}) {
|
}) {
|
||||||
@@ -45,6 +45,15 @@ export default function DynamicFormComponent({
|
|||||||
case 'select':
|
case 'select':
|
||||||
fieldSchema = z.string();
|
fieldSchema = z.string();
|
||||||
break;
|
break;
|
||||||
|
case 'llm-model-selector':
|
||||||
|
fieldSchema = z.string();
|
||||||
|
break;
|
||||||
|
case 'prompt-editor':
|
||||||
|
fieldSchema = z.array(z.object({
|
||||||
|
content: z.string(),
|
||||||
|
role: z.string(),
|
||||||
|
}));
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
fieldSchema = z.string();
|
fieldSchema = z.string();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,35 @@
|
|||||||
// 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 { Switch } from "@/components/ui/switch"
|
||||||
import { ControllerRenderProps } from "react-hook-form";
|
import { ControllerRenderProps } from "react-hook-form";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { httpClient } from "@/app/infra/http/HttpClient";
|
||||||
|
import { LLMModel } from "@/app/infra/entities/api";
|
||||||
|
|
||||||
export default function DynamicFormItemComponent({
|
export default function DynamicFormItemComponent({
|
||||||
config,
|
config,
|
||||||
field,
|
field,
|
||||||
}: {
|
}: {
|
||||||
config: IDynamicFormItemConfig;
|
config: IDynamicFormItemSchema;
|
||||||
field: ControllerRenderProps<any, any>;
|
field: ControllerRenderProps<any, any>;
|
||||||
}) {
|
}) {
|
||||||
|
const [llmModels, setLlmModels] = useState<LLMModel[]>([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (config.type === DynamicFormItemType.LLM_MODEL_SELECTOR) {
|
||||||
|
httpClient.getProviderLLMModels().then((resp) => {
|
||||||
|
setLlmModels(resp.models);
|
||||||
|
}).catch((err) => {
|
||||||
|
console.error('获取 LLM 模型列表失败:', err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [config.type]);
|
||||||
|
|
||||||
switch (config.type) {
|
switch (config.type) {
|
||||||
case DynamicFormItemType.INT:
|
case DynamicFormItemType.INT:
|
||||||
case DynamicFormItemType.FLOAT:
|
case DynamicFormItemType.FLOAT:
|
||||||
@@ -31,7 +46,7 @@ export default function DynamicFormItemComponent({
|
|||||||
|
|
||||||
case DynamicFormItemType.BOOLEAN:
|
case DynamicFormItemType.BOOLEAN:
|
||||||
return (
|
return (
|
||||||
<Checkbox
|
<Switch
|
||||||
checked={field.value}
|
checked={field.value}
|
||||||
onCheckedChange={field.onChange}
|
onCheckedChange={field.onChange}
|
||||||
/>
|
/>
|
||||||
@@ -39,21 +54,42 @@ 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 items-center">
|
||||||
>
|
<Input
|
||||||
<SelectTrigger>
|
className="w-[200px]"
|
||||||
<SelectValue placeholder="请选择" />
|
value={item}
|
||||||
</SelectTrigger>
|
onChange={(e) => {
|
||||||
<SelectContent>
|
const newValue = [...field.value];
|
||||||
<SelectGroup>
|
newValue[index] = e.target.value;
|
||||||
{/* 这里需要根据实际情况添加选项 */}
|
field.onChange(newValue);
|
||||||
<SelectItem value="option1">选项1</SelectItem>
|
}}
|
||||||
<SelectItem value="option2">选项2</SelectItem>
|
/>
|
||||||
</SelectGroup>
|
<button
|
||||||
</SelectContent>
|
type="button"
|
||||||
</Select>
|
className="p-2 hover:bg-gray-100 rounded"
|
||||||
|
onClick={() => {
|
||||||
|
const newValue = field.value.filter((_: string, i: number) => i !== index);
|
||||||
|
field.onChange(newValue);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" className="w-5 h-5 text-red-500">
|
||||||
|
<path d="M7 4V2H17V4H22V6H20V21C20 21.5523 19.5523 22 19 22H5C4.44772 22 4 21.5523 4 21V6H2V4H7ZM6 6V20H18V6H6ZM9 9H11V17H9V9ZM13 9H15V17H13V9Z"></path>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => {
|
||||||
|
field.onChange([...field.value, '']);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
添加
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
case DynamicFormItemType.SELECT:
|
case DynamicFormItemType.SELECT:
|
||||||
@@ -67,14 +103,107 @@ 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>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
case DynamicFormItemType.LLM_MODEL_SELECTOR:
|
||||||
|
return (
|
||||||
|
<Select
|
||||||
|
value={field.value}
|
||||||
|
onValueChange={field.onChange}
|
||||||
|
>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="请选择模型" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectGroup>
|
||||||
|
{llmModels.map((model) => (
|
||||||
|
<SelectItem key={model.uuid} value={model.uuid}>
|
||||||
|
{model.name}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectGroup>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
);
|
||||||
|
|
||||||
|
case DynamicFormItemType.PROMPT_EDITOR:
|
||||||
|
return (
|
||||||
|
<div className="space-y-2">
|
||||||
|
{field.value.map((item: { role: string; content: string }, index: number) => (
|
||||||
|
<div key={index} className="flex gap-2 items-center">
|
||||||
|
{/* 角色选择 */}
|
||||||
|
{index === 0 ? (
|
||||||
|
<div className="w-[120px] px-3 py-2 border rounded bg-gray-50 text-gray-500">system</div>
|
||||||
|
) : (
|
||||||
|
<Select
|
||||||
|
value={item.role}
|
||||||
|
onValueChange={(value) => {
|
||||||
|
const newValue = [...field.value];
|
||||||
|
newValue[index] = { ...newValue[index], role: value };
|
||||||
|
field.onChange(newValue);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<SelectTrigger className="w-[120px]">
|
||||||
|
<SelectValue />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectGroup>
|
||||||
|
<SelectItem value="user">user</SelectItem>
|
||||||
|
<SelectItem value="assistant">assistant</SelectItem>
|
||||||
|
</SelectGroup>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
)}
|
||||||
|
{/* 内容输入 */}
|
||||||
|
<Input
|
||||||
|
className="w-[300px]"
|
||||||
|
value={item.content}
|
||||||
|
onChange={(e) => {
|
||||||
|
const newValue = [...field.value];
|
||||||
|
newValue[index] = { ...newValue[index], content: e.target.value };
|
||||||
|
field.onChange(newValue);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{/* 删除按钮,第一轮不显示 */}
|
||||||
|
{index !== 0 && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="p-2 hover:bg-gray-100 rounded"
|
||||||
|
onClick={() => {
|
||||||
|
const newValue = field.value.filter((_: any, i: number) => i !== index);
|
||||||
|
field.onChange(newValue);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" className="w-5 h-5 text-red-500">
|
||||||
|
<path d="M7 4V2H17V4H22V6H20V21C20 21.5523 19.5523 22 19 22H5C4.44772 22 4 21.5523 4 21V6H2V4H7ZM6 6V20H18V6H6ZM9 9H11V17H9V9ZM13 9H15V17H13V9Z"></path>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => {
|
||||||
|
field.onChange([
|
||||||
|
...field.value,
|
||||||
|
{ role: 'user', content: '' },
|
||||||
|
]);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
添加回合
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return <Input {...field} />;
|
return <Input {...field} />;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import { SelectProps } from 'antd';
|
|
||||||
import { ICreateLLMField } from '@/app/home/models/ICreateLLMField';
|
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"
|
||||||
@@ -91,7 +90,7 @@ export default function LLMForm({
|
|||||||
const [extraArgs, setExtraArgs] = useState<{key: string, type: 'string' | 'number' | 'boolean', value: string}[]>([]);
|
const [extraArgs, setExtraArgs] = useState<{key: string, type: 'string' | 'number' | 'boolean', value: string}[]>([]);
|
||||||
|
|
||||||
const [showDeleteConfirmModal, setShowDeleteConfirmModal] = useState(false);
|
const [showDeleteConfirmModal, setShowDeleteConfirmModal] = useState(false);
|
||||||
const abilityOptions: SelectProps['options'] = [
|
const abilityOptions: { label: string, value: string }[] = [
|
||||||
{
|
{
|
||||||
label: '视觉能力',
|
label: '视觉能力',
|
||||||
value: 'vision',
|
value: 'vision',
|
||||||
@@ -178,7 +177,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 '';
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import LLMCard from '@/app/home/models/component/llm-card/LLMCard';
|
|||||||
import LLMForm from '@/app/home/models/component/llm-form/LLMForm';
|
import LLMForm from '@/app/home/models/component/llm-form/LLMForm';
|
||||||
import CreateCardComponent from '@/app/infra/basic-component/create-card-component/CreateCardComponent';
|
import CreateCardComponent from '@/app/infra/basic-component/create-card-component/CreateCardComponent';
|
||||||
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 {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
@@ -98,43 +98,27 @@ export default function LLMConfigPage() {
|
|||||||
/>
|
/>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
<div className={`${styles.modelListContainer}`}>
|
||||||
|
|
||||||
{cardList.length > 0 && (
|
<CreateCardComponent
|
||||||
<div className={`${styles.modelListContainer}`}>
|
width={'24rem'}
|
||||||
|
height={'10rem'}
|
||||||
<CreateCardComponent
|
plusSize={'90px'}
|
||||||
width={'24rem'}
|
onClick={handleCreateModelClick}
|
||||||
height={'10rem'}
|
/>
|
||||||
plusSize={'90px'}
|
{cardList.map((cardVO) => {
|
||||||
onClick={handleCreateModelClick}
|
return (
|
||||||
/>
|
<div
|
||||||
{cardList.map((cardVO) => {
|
key={cardVO.id}
|
||||||
return (
|
onClick={() => {
|
||||||
<div
|
selectLLM(cardVO);
|
||||||
key={cardVO.id}
|
}}
|
||||||
onClick={() => {
|
>
|
||||||
selectLLM(cardVO);
|
<LLMCard cardVO={cardVO}></LLMCard>
|
||||||
}}
|
</div>
|
||||||
>
|
);
|
||||||
<LLMCard cardVO={cardVO}></LLMCard>
|
})}
|
||||||
</div>
|
</div>
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{cardList.length === 0 && (
|
|
||||||
<div className={`${styles.emptyContainer}`}>
|
|
||||||
<EmptyAndCreateComponent
|
|
||||||
title={'模型列表空空如也~'}
|
|
||||||
subTitle={'快去创建一个吧!'}
|
|
||||||
buttonText={'创建模型 +'}
|
|
||||||
onButtonClick={() => {
|
|
||||||
handleCreateModelClick();
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
import { DynamicFormItemConfig } from '@/app/home/components/dynamic-form/DynamicFormItemConfig';
|
|
||||||
|
|
||||||
export interface IPipelineChildFormEntity {
|
|
||||||
name: string;
|
|
||||||
label: string;
|
|
||||||
formItems: DynamicFormItemConfig[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export class PipelineChildFormEntity implements IPipelineChildFormEntity {
|
|
||||||
formItems: DynamicFormItemConfig[];
|
|
||||||
label: string;
|
|
||||||
name: string;
|
|
||||||
|
|
||||||
constructor(props: IPipelineChildFormEntity) {
|
|
||||||
this.label = props.label;
|
|
||||||
this.name = props.name;
|
|
||||||
this.formItems = props.formItems;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,23 +1,29 @@
|
|||||||
|
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { httpClient } from '@/app/infra/http/HttpClient';
|
||||||
|
import { Pipeline } from '@/app/infra/entities/api';
|
||||||
|
import { PipelineFormEntity, PipelineConfigTab, PipelineConfigStage } from '@/app/infra/entities/pipeline';
|
||||||
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
|
||||||
|
import DynamicFormComponent from '@/app/home/components/dynamic-form/DynamicFormComponent';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { useForm } from "react-hook-form"
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod"
|
||||||
|
import { z } from "zod"
|
||||||
|
import { Input } from "@/components/ui/input"
|
||||||
import {
|
import {
|
||||||
Form,
|
Form,
|
||||||
Button,
|
FormControl,
|
||||||
Switch,
|
FormField,
|
||||||
Select,
|
FormItem,
|
||||||
Input,
|
FormLabel,
|
||||||
InputNumber,
|
FormMessage,
|
||||||
SelectProps,
|
} from "@/components/ui/form"
|
||||||
} from 'antd';
|
|
||||||
import { CaretLeftOutlined, CaretRightOutlined } from '@ant-design/icons';
|
|
||||||
import { useEffect, useState } from 'react';
|
|
||||||
import styles from './pipelineFormStyle.module.css';
|
|
||||||
import { httpClient } from '@/app/infra/http/HttpClient';
|
|
||||||
import { LLMModel, Pipeline } from '@/app/infra/api/api-types';
|
|
||||||
import { UUID } from 'uuidjs';
|
|
||||||
import { PipelineFormEntity } from '@/app/home/pipelines/components/pipeline-form/PipelineFormEntity';
|
|
||||||
|
|
||||||
export default function PipelineFormComponent({
|
export default function PipelineFormComponent({
|
||||||
initValues,
|
initValues,
|
||||||
onFinish,
|
onFinish,
|
||||||
|
onNewPipelineCreated,
|
||||||
isEditMode,
|
isEditMode,
|
||||||
pipelineId,
|
pipelineId,
|
||||||
disableForm,
|
disableForm,
|
||||||
@@ -28,625 +34,287 @@ export default function PipelineFormComponent({
|
|||||||
// 这里的写法很不安全不规范,未来流水线需要重新整理
|
// 这里的写法很不安全不规范,未来流水线需要重新整理
|
||||||
initValues?: PipelineFormEntity;
|
initValues?: PipelineFormEntity;
|
||||||
onFinish: () => void;
|
onFinish: () => void;
|
||||||
|
onNewPipelineCreated: (pipelineId: string) => void;
|
||||||
}) {
|
}) {
|
||||||
const [nowFormIndex, setNowFormIndex] = useState<number>(0);
|
|
||||||
const [nowAIRunner, setNowAIRunner] = useState('');
|
const formSchema = isEditMode ? z.object({
|
||||||
const [llmModelList, setLlmModelList] = useState<SelectProps['options']>([]);
|
basic: z.object({
|
||||||
|
name: z.string().min(1, { message: '名称不能为空' }),
|
||||||
|
description: z.string().min(1, { message: '描述不能为空' }),
|
||||||
|
}),
|
||||||
|
ai: z.record(z.string(), z.any()),
|
||||||
|
trigger: z.record(z.string(), z.any()),
|
||||||
|
safety: z.record(z.string(), z.any()),
|
||||||
|
output: z.record(z.string(), z.any()),
|
||||||
|
})
|
||||||
|
: z.object({
|
||||||
|
basic: z.object({
|
||||||
|
name: z.string().min(1, { message: '名称不能为空' }),
|
||||||
|
description: z.string().min(1, { message: '描述不能为空' }),
|
||||||
|
}),
|
||||||
|
ai: z.record(z.string(), z.any()).optional(),
|
||||||
|
trigger: z.record(z.string(), z.any()).optional(),
|
||||||
|
safety: z.record(z.string(), z.any()).optional(),
|
||||||
|
output: z.record(z.string(), z.any()).optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
type FormValues = z.infer<typeof formSchema>;
|
||||||
// 这里不好,可以改成enum等
|
// 这里不好,可以改成enum等
|
||||||
const formLabelList: FormLabel[] = [
|
const formLabelList: FormLabel[] = isEditMode ? [
|
||||||
{ 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' },
|
||||||
|
] : [
|
||||||
|
{ label: '基础信息', name: 'basic' },
|
||||||
];
|
];
|
||||||
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>();
|
||||||
|
|
||||||
|
const form = useForm<FormValues>({
|
||||||
|
resolver: zodResolver(formSchema),
|
||||||
|
defaultValues: {
|
||||||
|
basic: {},
|
||||||
|
ai: {},
|
||||||
|
trigger: {},
|
||||||
|
safety: {},
|
||||||
|
output: {},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
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);
|
|
||||||
if (initValues) {
|
if (initValues) {
|
||||||
basicForm.setFieldsValue(initValues.basic);
|
form.reset(initValues);
|
||||||
aiForm.setFieldsValue(initValues.ai);
|
|
||||||
triggerForm.setFieldsValue(initValues.trigger);
|
|
||||||
safetyForm.setFieldsValue(initValues.safety);
|
|
||||||
outputForm.setFieldsValue(initValues.output);
|
|
||||||
}
|
}
|
||||||
}, [aiForm, basicForm, initValues, outputForm, safetyForm, triggerForm]);
|
|
||||||
|
|
||||||
function getLLMModelList() {
|
if (!isEditMode) {
|
||||||
httpClient
|
form.reset({
|
||||||
.getProviderLLMModels()
|
basic: {
|
||||||
.then((resp) => {
|
name: '',
|
||||||
setLlmModelList(
|
description: '',
|
||||||
resp.models.map((model: LLMModel) => {
|
},
|
||||||
return {
|
|
||||||
value: model.uuid,
|
|
||||||
label: model.name,
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
console.error('get LLM model list error', err);
|
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
function getNowFormLabel() {
|
|
||||||
return formLabelList[nowFormIndex];
|
|
||||||
}
|
|
||||||
|
|
||||||
function getPreFormLabel(): undefined | FormLabel {
|
|
||||||
if (nowFormIndex !== undefined && nowFormIndex > 0) {
|
|
||||||
return formLabelList[nowFormIndex - 1];
|
|
||||||
} else {
|
|
||||||
return undefined;
|
|
||||||
}
|
}
|
||||||
}
|
}, [initValues, form]);
|
||||||
|
|
||||||
function getNextFormLabel(): undefined | FormLabel {
|
function handleFormSubmit(values: FormValues) {
|
||||||
if (nowFormIndex !== undefined && nowFormIndex < formLabelList.length - 1) {
|
console.log('handleFormSubmit', values);
|
||||||
return formLabelList[nowFormIndex + 1];
|
|
||||||
} else {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function addFormLabelIndex() {
|
|
||||||
if (nowFormIndex < formLabelList.length - 1) {
|
|
||||||
setNowFormIndex(nowFormIndex + 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function reduceFormLabelIndex() {
|
|
||||||
if (nowFormIndex > 0) {
|
|
||||||
setNowFormIndex(nowFormIndex - 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleCommit() {
|
|
||||||
if (isEditMode) {
|
if (isEditMode) {
|
||||||
handleModify();
|
handleModify(values);
|
||||||
} else {
|
} else {
|
||||||
handleCreate();
|
handleCreate(values);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleCreate() {
|
function handleCreate(values: FormValues) {
|
||||||
Promise.all([
|
console.log('handleCreate', values);
|
||||||
basicForm.validateFields(),
|
const pipeline: Pipeline = {
|
||||||
aiForm.validateFields(),
|
description: values.basic.description,
|
||||||
triggerForm.validateFields(),
|
name: values.basic.name,
|
||||||
safetyForm.validateFields(),
|
};
|
||||||
outputForm.validateFields(),
|
httpClient.createPipeline(pipeline).then((resp) => {
|
||||||
])
|
onFinish();
|
||||||
.then(() => {
|
onNewPipelineCreated(resp.uuid);
|
||||||
const pipeline = assembleForm();
|
});
|
||||||
httpClient.createPipeline(pipeline).then(() => onFinish());
|
|
||||||
})
|
|
||||||
.catch((e) => {
|
|
||||||
console.error(e);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleModify() {
|
function handleModify(values: FormValues) {
|
||||||
Promise.all([
|
|
||||||
basicForm.validateFields(),
|
const realConfig = {
|
||||||
aiForm.validateFields(),
|
ai: values.ai,
|
||||||
triggerForm.validateFields(),
|
trigger: values.trigger,
|
||||||
safetyForm.validateFields(),
|
safety: values.safety,
|
||||||
outputForm.validateFields(),
|
output: values.output,
|
||||||
])
|
};
|
||||||
.then(() => {
|
|
||||||
const pipeline = assembleForm();
|
const pipeline: Pipeline = {
|
||||||
httpClient
|
config: realConfig,
|
||||||
.updatePipeline(pipelineId || '', pipeline)
|
// created_at: '',
|
||||||
.then(() => onFinish());
|
description: values.basic.description,
|
||||||
})
|
// for_version: '',
|
||||||
.catch((e) => {
|
name: values.basic.name,
|
||||||
console.error(e);
|
// stages: [],
|
||||||
});
|
// updated_at: '',
|
||||||
|
// uuid: pipelineId || '',
|
||||||
|
// is_default: false,
|
||||||
|
};
|
||||||
|
httpClient.updatePipeline(pipelineId || '', pipeline).then(() => onFinish());
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO 类型混乱,需要优化
|
function renderDynamicForms(stage: PipelineConfigStage, formName: keyof FormValues) {
|
||||||
function assembleForm(): Pipeline {
|
// 如果是 AI 配置,需要特殊处理
|
||||||
console.log('basicForm:', basicForm.getFieldsValue());
|
if (formName === 'ai') {
|
||||||
console.log('aiForm:', aiForm.getFieldsValue());
|
// 获取当前选择的 runner
|
||||||
console.log('triggerForm:', triggerForm.getFieldsValue());
|
const currentRunner = form.watch('ai.runner.runner');
|
||||||
console.log('safetyForm:', safetyForm.getFieldsValue());
|
|
||||||
console.log('outputForm:', outputForm.getFieldsValue());
|
|
||||||
const config: object = {
|
|
||||||
ai: aiForm.getFieldsValue(),
|
|
||||||
trigger: triggerForm.getFieldsValue(),
|
|
||||||
safety: safetyForm.getFieldsValue(),
|
|
||||||
output: outputForm.getFieldsValue(),
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
// 如果是 runner 配置项,直接渲染
|
||||||
config,
|
if (stage.name === 'runner') {
|
||||||
created_at: '',
|
return (
|
||||||
description: basicForm.getFieldsValue().description,
|
<div key={stage.name} className="space-y-4 mb-6">
|
||||||
for_version: '',
|
<div className="text-lg font-medium">{stage.label.zh_CN}</div>
|
||||||
name: basicForm.getFieldsValue().name,
|
{stage.description && (
|
||||||
stages: [],
|
<div className="text-sm text-gray-500">{stage.description.zh_CN}</div>
|
||||||
updated_at: '',
|
)}
|
||||||
uuid: UUID.generate(),
|
<DynamicFormComponent
|
||||||
};
|
itemConfigList={stage.config}
|
||||||
|
initialValues={(form.watch(formName) as Record<string, any>)?.[stage.name] || {}}
|
||||||
|
onSubmit={(values) => {
|
||||||
|
const currentValues = form.getValues(formName) as Record<string, any> || {};
|
||||||
|
form.setValue(formName, {
|
||||||
|
...currentValues,
|
||||||
|
[stage.name]: values,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果不是当前选择的 runner 对应的配置项,则不渲染
|
||||||
|
if (stage.name !== currentRunner) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div key={stage.name} className="space-y-4 mb-6">
|
||||||
|
<div className="text-lg font-medium">{stage.label.zh_CN}</div>
|
||||||
|
{stage.description && (
|
||||||
|
<div className="text-sm text-gray-500">{stage.description.zh_CN}</div>
|
||||||
|
)}
|
||||||
|
<DynamicFormComponent
|
||||||
|
itemConfigList={stage.config}
|
||||||
|
initialValues={(form.watch(formName) as Record<string, any>)?.[stage.name] || {}}
|
||||||
|
onSubmit={(values) => {
|
||||||
|
const currentValues = form.getValues(formName) as Record<string, any> || {};
|
||||||
|
form.setValue(formName, {
|
||||||
|
...currentValues,
|
||||||
|
[stage.name]: values,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ maxHeight: '70vh', overflowY: 'auto' }}>
|
<div style={{ maxHeight: '70vh', overflowY: 'auto' }}>
|
||||||
<h1>{getNowFormLabel().label}</h1>
|
<Form {...form}>
|
||||||
<Form
|
<form onSubmit={form.handleSubmit(handleFormSubmit)}>
|
||||||
layout={'vertical'}
|
<Tabs defaultValue={formLabelList[0].name}>
|
||||||
style={{
|
<TabsList>
|
||||||
display: getNowFormLabel().name === 'basic' ? 'block' : 'none',
|
{formLabelList.map((formLabel) => (
|
||||||
}}
|
<TabsTrigger key={formLabel.name} value={formLabel.name}>
|
||||||
form={basicForm}
|
{formLabel.label}
|
||||||
disabled={disableForm}
|
</TabsTrigger>
|
||||||
>
|
))}
|
||||||
<Form.Item
|
</TabsList>
|
||||||
label="流水线名称"
|
|
||||||
name={'name'}
|
|
||||||
rules={[
|
|
||||||
{
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Input />
|
|
||||||
</Form.Item>
|
|
||||||
|
|
||||||
<Form.Item
|
{formLabelList.map((formLabel) => (
|
||||||
label="流水线描述"
|
<TabsContent key={formLabel.name} value={formLabel.name} className='pr-6'>
|
||||||
name={'description'}
|
<h1 className="text-xl font-bold mb-4">{formLabel.label}</h1>
|
||||||
rules={[
|
|
||||||
{
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Input />
|
|
||||||
</Form.Item>
|
|
||||||
</Form>
|
|
||||||
{/* AI能力表单 ai */}
|
|
||||||
<Form
|
|
||||||
layout={'vertical'}
|
|
||||||
style={{ display: getNowFormLabel().name === 'ai' ? 'block' : 'none' }}
|
|
||||||
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 配置区块 */}
|
{formLabel.name === 'basic' && (
|
||||||
{nowAIRunner === 'local-agent' && (
|
<div className="space-y-6">
|
||||||
<>
|
<FormField
|
||||||
<div className={`${styles.formItemSubtitle}`}>配置内置Agent</div>
|
control={form.control}
|
||||||
<Form.Item
|
name="basic.name"
|
||||||
label="模型"
|
render={({ field }) => (
|
||||||
name={['local-agent', 'model']}
|
<FormItem>
|
||||||
rules={[{ required: true }]}
|
<FormLabel>名称<span className="text-red-500">*</span></FormLabel>
|
||||||
tooltip="从模型库中选择"
|
<FormControl>
|
||||||
>
|
<Input {...field} />
|
||||||
<Select
|
</FormControl>
|
||||||
options={llmModelList}
|
<FormMessage />
|
||||||
placeholder="请选择语言模型"
|
</FormItem>
|
||||||
showSearch
|
)}
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
|
||||||
<Form.Item
|
<FormField
|
||||||
label="最大回合数"
|
control={form.control}
|
||||||
name={['local-agent', 'max-round']}
|
name="basic.description"
|
||||||
rules={[
|
render={({ field }) => (
|
||||||
{
|
<FormItem>
|
||||||
required: true,
|
<FormLabel>描述<span className="text-red-500">*</span></FormLabel>
|
||||||
},
|
<FormControl>
|
||||||
]}
|
<Input {...field} />
|
||||||
>
|
</FormControl>
|
||||||
<InputNumber precision={0} />
|
<FormMessage />
|
||||||
</Form.Item>
|
</FormItem>
|
||||||
{/* TODO 这里要做转换处理 */}
|
)}
|
||||||
<Form.Item
|
/>
|
||||||
label="提示词"
|
</div>
|
||||||
name={['local-agent', 'prompt']}
|
)}
|
||||||
rules={[{ required: true }]}
|
|
||||||
tooltip="按JSON格式输入"
|
{isEditMode && (
|
||||||
>
|
<>
|
||||||
<Input.TextArea
|
{formLabel.name === 'ai' && aiConfigTabSchema && (
|
||||||
rows={4}
|
<div className="space-y-6">
|
||||||
placeholder={`示例结构:{ "role": "user", "content": "你好" } `}
|
{aiConfigTabSchema.stages.map((stage) => renderDynamicForms(stage, 'ai'))}
|
||||||
/>
|
</div>
|
||||||
</Form.Item>
|
)}
|
||||||
</>
|
|
||||||
)}
|
{formLabel.name === 'trigger' && triggerConfigTabSchema && (
|
||||||
{/* Dify 服务 API 区块 */}
|
<div className="space-y-6">
|
||||||
{nowAIRunner === 'dify-service-api' && (
|
{triggerConfigTabSchema.stages.map((stage) => renderDynamicForms(stage, 'trigger'))}
|
||||||
<>
|
</div>
|
||||||
<div className={`${styles.formItemSubtitle}`}>配置Dify服务API</div>
|
)}
|
||||||
<Form.Item
|
|
||||||
label="基础 URL"
|
{formLabel.name === 'safety' && safetyConfigTabSchema && (
|
||||||
name={['dify-service-api', 'base-url']}
|
<div className="space-y-6">
|
||||||
rules={[
|
{safetyConfigTabSchema.stages.map((stage) => renderDynamicForms(stage, 'safety'))}
|
||||||
{ required: true },
|
</div>
|
||||||
{ type: 'url', message: '请输入有效的URL地址' },
|
)}
|
||||||
]}
|
|
||||||
>
|
{formLabel.name === 'output' && outputConfigTabSchema && (
|
||||||
<Input />
|
<div className="space-y-6">
|
||||||
</Form.Item>
|
{outputConfigTabSchema.stages.map((stage) => renderDynamicForms(stage, 'output'))}
|
||||||
<Form.Item
|
</div>
|
||||||
label="应用类型"
|
)}
|
||||||
name={['dify-service-api', 'app-type']}
|
</>
|
||||||
initialValue={'chat'}
|
)}
|
||||||
rules={[{ required: true }]}
|
</TabsContent>
|
||||||
>
|
))}
|
||||||
<Select
|
</Tabs>
|
||||||
options={[
|
|
||||||
{ label: '聊天(包括Chatflow)', value: 'chat' },
|
<div className="sticky bottom-0 left-0 right-0 bg-background border-t p-4 mt-4">
|
||||||
{ label: 'Agent', value: 'agent' },
|
<div className="flex justify-end gap-2">
|
||||||
{ label: '工作流', value: 'workflow' },
|
<Button type="submit">
|
||||||
]}
|
{isEditMode ? '保存' : '提交'}
|
||||||
/>
|
</Button>
|
||||||
</Form.Item>
|
<Button type="button" variant="outline" onClick={onFinish}>
|
||||||
<Form.Item
|
取消
|
||||||
label="API 密钥"
|
</Button>
|
||||||
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>
|
</div>
|
||||||
<Form.Item
|
</div>
|
||||||
label="应用类型"
|
</form>
|
||||||
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>
|
</Form>
|
||||||
|
|
||||||
{/* 触发条件表单 trigger */}
|
|
||||||
<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
|
|
||||||
type="primary"
|
|
||||||
icon={<CaretLeftOutlined />}
|
|
||||||
onClick={reduceFormLabelIndex}
|
|
||||||
disabled={!getPreFormLabel()}
|
|
||||||
>
|
|
||||||
{getPreFormLabel()?.label || '暂无更多'}
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
type="primary"
|
|
||||||
icon={<CaretRightOutlined />}
|
|
||||||
onClick={addFormLabelIndex}
|
|
||||||
disabled={!getNextFormLabel()}
|
|
||||||
iconPosition={'end'}
|
|
||||||
>
|
|
||||||
{getNextFormLabel()?.label || '暂无更多'}
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Button type="primary" onClick={handleCommit}>
|
|
||||||
提交
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
export interface PipelineFormEntity {
|
|
||||||
basic: object;
|
|
||||||
ai: object;
|
|
||||||
trigger: object;
|
|
||||||
safety: object;
|
|
||||||
output: object;
|
|
||||||
}
|
|
||||||
@@ -1,13 +1,20 @@
|
|||||||
'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';
|
||||||
import { httpClient } from '@/app/infra/http/HttpClient';
|
import { httpClient } from '@/app/infra/http/HttpClient';
|
||||||
import { PipelineCardVO } from '@/app/home/pipelines/components/pipeline-card/PipelineCardVO';
|
import { PipelineCardVO } from '@/app/home/pipelines/components/pipeline-card/PipelineCardVO';
|
||||||
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/infra/entities/pipeline';
|
||||||
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() {
|
||||||
@@ -35,16 +42,16 @@ export default function PluginConfigPage() {
|
|||||||
.then((value) => {
|
.then((value) => {
|
||||||
let currentTime = new Date();
|
let currentTime = new Date();
|
||||||
const pipelineList = value.pipelines.map((pipeline) => {
|
const pipelineList = value.pipelines.map((pipeline) => {
|
||||||
let lastUpdatedTimeAgo = Math.floor((currentTime.getTime() - new Date(pipeline.updated_at).getTime()) / 1000 / 60 / 60 / 24);
|
let lastUpdatedTimeAgo = Math.floor((currentTime.getTime() - new Date(pipeline.updated_at ?? currentTime.getTime()).getTime()) / 1000 / 60 / 60 / 24);
|
||||||
|
|
||||||
let lastUpdatedTimeAgoText = lastUpdatedTimeAgo > 0 ? ` ${lastUpdatedTimeAgo} 天前` : '今天';
|
let lastUpdatedTimeAgoText = lastUpdatedTimeAgo > 0 ? ` ${lastUpdatedTimeAgo} 天前` : '今天';
|
||||||
|
|
||||||
return new PipelineCardVO({
|
return new PipelineCardVO({
|
||||||
lastUpdatedTimeAgo: lastUpdatedTimeAgoText,
|
lastUpdatedTimeAgo: lastUpdatedTimeAgoText,
|
||||||
description: pipeline.description,
|
description: pipeline.description,
|
||||||
id: pipeline.uuid,
|
id: pipeline.uuid ?? '',
|
||||||
name: pipeline.name,
|
name: pipeline.name,
|
||||||
isDefault: pipeline.is_default,
|
isDefault: pipeline.is_default ?? false,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
setPipelineList(pipelineList);
|
setPipelineList(pipelineList);
|
||||||
@@ -73,58 +80,64 @@ export default function PluginConfigPage() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.configPageContainer}>
|
<div className={styles.configPageContainer}>
|
||||||
|
|
||||||
<Modal
|
<Dialog open={modalOpen} onOpenChange={setModalOpen}>
|
||||||
title={isEditForm ? '编辑流水线' : '创建流水线'}
|
<DialogContent className="w-[700px] max-h-[80vh] p-0 flex flex-col">
|
||||||
centered
|
<DialogHeader className="px-6 pt-6 pb-4">
|
||||||
open={modalOpen}
|
<DialogTitle>
|
||||||
destroyOnClose={true}
|
{isEditForm ? '编辑流水线' : '创建流水线'}
|
||||||
onOk={() => setModalOpen(false)}
|
</DialogTitle>
|
||||||
onCancel={() => setModalOpen(false)}
|
</DialogHeader>
|
||||||
width={700}
|
<div className="flex-1 overflow-y-auto px-6">
|
||||||
footer={null}
|
<PipelineFormComponent
|
||||||
>
|
onNewPipelineCreated={(pipelineId) => {
|
||||||
<PipelineFormComponent
|
setDisableForm(true);
|
||||||
onFinish={() => {
|
setIsEditForm(true);
|
||||||
getPipelines();
|
setModalOpen(true);
|
||||||
setModalOpen(false);
|
setSelectedPipelineId(pipelineId);
|
||||||
|
getSelectedPipelineForm(pipelineId);
|
||||||
|
}}
|
||||||
|
onFinish={() => {
|
||||||
|
getPipelines();
|
||||||
|
setModalOpen(false);
|
||||||
|
}}
|
||||||
|
isEditMode={isEditForm}
|
||||||
|
pipelineId={selectedPipelineId}
|
||||||
|
disableForm={disableForm}
|
||||||
|
initValues={selectedPipelineFormValue}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
|
||||||
|
<div className={styles.pipelineListContainer}>
|
||||||
|
<CreateCardComponent
|
||||||
|
width={'24rem'}
|
||||||
|
height={'10rem'}
|
||||||
|
plusSize={'90px'}
|
||||||
|
onClick={() => {
|
||||||
|
setIsEditForm(false);
|
||||||
|
setModalOpen(true);
|
||||||
}}
|
}}
|
||||||
isEditMode={isEditForm}
|
|
||||||
pipelineId={selectedPipelineId}
|
|
||||||
disableForm={disableForm}
|
|
||||||
initValues={selectedPipelineFormValue}
|
|
||||||
/>
|
/>
|
||||||
</Modal>
|
|
||||||
|
|
||||||
{pipelineList.length > 0 && (
|
{pipelineList.map((pipeline) => {
|
||||||
<div className={styles.pipelineListContainer}>
|
return (
|
||||||
<CreateCardComponent
|
<div
|
||||||
width={'24rem'}
|
key={pipeline.id}
|
||||||
height={'10rem'}
|
onClick={() => {
|
||||||
plusSize={'90px'}
|
setDisableForm(true);
|
||||||
onClick={() => {
|
setIsEditForm(true);
|
||||||
setModalOpen(true);
|
setModalOpen(true);
|
||||||
}}
|
setSelectedPipelineId(pipeline.id);
|
||||||
/>
|
getSelectedPipelineForm(pipeline.id);
|
||||||
|
}}
|
||||||
{pipelineList.map((pipeline) => {
|
>
|
||||||
return (
|
<PipelineCard cardVO={pipeline} />
|
||||||
<div
|
</div>
|
||||||
key={pipeline.id}
|
);
|
||||||
onClick={() => {
|
})}
|
||||||
setDisableForm(true);
|
</div>
|
||||||
setIsEditForm(true);
|
|
||||||
setModalOpen(true);
|
|
||||||
setSelectedPipelineId(pipeline.id);
|
|
||||||
getSelectedPipelineForm(pipeline.id);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<PipelineCard cardVO={pipeline} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
@@ -58,15 +64,15 @@ export interface ApiRespPipelines {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface Pipeline {
|
export interface Pipeline {
|
||||||
uuid: string;
|
uuid?: string;
|
||||||
name: string;
|
name: string;
|
||||||
description: string;
|
description: string;
|
||||||
for_version: string;
|
for_version?: string;
|
||||||
config: object;
|
config: object;
|
||||||
stages: string[];
|
stages?: string[];
|
||||||
is_default: boolean;
|
is_default?: boolean;
|
||||||
created_at: string;
|
created_at?: string;
|
||||||
updated_at: string;
|
updated_at?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ApiRespPlatformAdapters {
|
export interface ApiRespPlatformAdapters {
|
||||||
@@ -302,3 +308,7 @@ interface GetPipeline {
|
|||||||
export interface GetPipelineResponseData {
|
export interface GetPipelineResponseData {
|
||||||
pipeline: GetPipeline;
|
pipeline: GetPipeline;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface GetPipelineMetadataResponseData {
|
||||||
|
configs: PipelineConfigTab[];
|
||||||
|
}
|
||||||
5
web/src/app/infra/entities/common.ts
Normal file
5
web/src/app/infra/entities/common.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
export interface I18nLabel {
|
||||||
|
en_US: string;
|
||||||
|
zh_CN: string;
|
||||||
|
ja_JP?: string;
|
||||||
|
}
|
||||||
29
web/src/app/infra/entities/form/dynamic.ts
Normal file
29
web/src/app/infra/entities/form/dynamic.ts
Normal file
@@ -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;
|
||||||
|
}
|
||||||
23
web/src/app/infra/entities/pipeline/index.ts
Normal file
23
web/src/app/infra/entities/pipeline/index.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
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;
|
||||||
|
description?: I18nLabel;
|
||||||
|
config: IDynamicFormItemSchema[];
|
||||||
|
}
|
||||||
@@ -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
web/src/components/ui/tabs.tsx
Normal file
66
web/src/components/ui/tabs.tsx
Normal 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 }
|
||||||
Reference in New Issue
Block a user