feat: webUI2.0 前端介面更新

1. 剩余登陆注册未完成
2. 剩余插件列表&市场未完成
This commit is contained in:
HYana
2025-03-26 17:01:22 +08:00
committed by Junyan Qin
parent 8511432dee
commit 453237aef8
97 changed files with 9757 additions and 9332 deletions

BIN
web_ui/src/app/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

37
web_ui/src/app/global.css Normal file
View File

@@ -0,0 +1,37 @@
* {
margin: 0;
padding: 0;
}
:root {
/* 适用于 Firefox 的滚动条 */
scrollbar-color: rgba(0, 0, 0, 0.2) transparent; /* 滑块颜色 + 轨道颜色 */
scrollbar-width: thin; /* auto | thin | none */
}
/* WebKit 内核浏览器定制 */
::-webkit-scrollbar {
width: 6px; /* 垂直滚动条宽度 */
height: 6px; /* 水平滚动条高度 */
}
::-webkit-scrollbar-track {
background: transparent; /* 隐藏轨道背景 */
}
::-webkit-scrollbar-thumb {
background: rgba(0, 0, 0, 0.2); /* 半透明黑色 */
border-radius: 3px;
transition: background 0.3s;
}
::-webkit-scrollbar-thumb:hover {
background: rgba(0, 0, 0, 0.35); /* 悬停加深 */
}
/* 兼容 Edge */
@supports (-ms-ime-align:auto) {
body {
-ms-overflow-style: -ms-autohiding-scrollbar; /* 自动隐藏滚动条 */
}
}

View File

@@ -0,0 +1,3 @@
export interface ICreateBotField {
}

View File

@@ -0,0 +1,23 @@
.configPageContainer {
width: 100%;
height: 100%;
}
.cardContainer {
width: 420px;
height: 220px;
border: 1px solid black;
}
.botListContainer {
align-self: flex-start;
justify-self: flex-start;
width: calc(100% - 60px);
margin: auto;
display: grid;
grid-template-rows: repeat(auto-fill, minmax(220px, 1fr));
grid-template-columns: repeat(auto-fill, minmax(360px, 1fr));
gap: 15px;
justify-items: center;
align-items: center;
}

View File

@@ -0,0 +1,39 @@
import {BotCardVO} from "@/app/home/bot-config/components/bot-card/BotCardVO";
import styles from "./botCard.module.css";
export default function BotCard({
botCardVO
}: {
botCardVO: BotCardVO;
}) {
return (
<div className={`${styles.cardContainer}`}>
{/* icon和基本信息 */}
<div className={`${styles.iconBasicInfoContainer}`}>
{/* icon */}
<div className={`${styles.icon}`}>
ICO
</div>
{/* bot基本信息 */}
<div className={`${styles.basicInfoContainer}`}>
<div className={`${styles.basicInfoText} ${styles.bigText}`}>
{botCardVO.name}
</div>
<div className={`${styles.basicInfoText}`}>
{botCardVO.adapter}
</div>
<div className={`${styles.basicInfoText}`}>
线{botCardVO.pipelineName}
</div>
</div>
</div>
{/* 描述和创建时间 */}
<div className={`${styles.urlAndUpdateText}`}>
{botCardVO.description}
</div>
<div className={`${styles.urlAndUpdateText}`}>
{botCardVO.updateTime}
</div>
</div>
)
}

View File

@@ -0,0 +1,28 @@
export interface IBotCardVO {
id: string;
name: string;
adapter: string;
description: string;
updateTime: string;
pipelineName: string;
}
export class BotCardVO implements IBotCardVO {
id: string;
adapter: string;
description: string;
name: string;
updateTime: string;
pipelineName: string;
constructor(props: IBotCardVO) {
this.id = props.id;
this.name = props.name;
this.adapter = props.adapter;
this.description = props.description;
this.updateTime = props.updateTime;
this.pipelineName = props.pipelineName;
}
}

View File

@@ -0,0 +1,67 @@
.iconBasicInfoContainer {
width: 300px;
height: 100px;
margin-left: 20px;
display: flex;
flex-direction: row;
}
.cardContainer {
width: 360px;
height: 200px;
background-color: #FFF;
border-radius: 9px;
box-shadow: rgba(0, 0, 0, 0.4) 0 1px 1px -1px;
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: space-evenly;
}
.iconBasicInfoContainer {
width: 300px;
height: 100px;
margin-left: 20px;
display: flex;
flex-direction: row;
}
.icon {
width: 90px;
height: 90px;
border-radius: 5px;
font-size: 40px;
line-height: 90px;
text-align: center;
color: #ffffff;
background: rgba(96, 149, 209, 0.31);
border: 1px solid rgba(96, 149, 209, 0.31);
}
.basicInfoContainer {
width: 200px;
height: 90px;
padding-left: 20px;
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: space-between;
}
.basicInfoText {
}
.bigText {
font-size: 20px;
}
.urlAndUpdateText {
margin-left: 20px;
}
.createCardContainer {
font-size: 90px;
background: #6062E7;
color: white;
}

View File

@@ -0,0 +1,222 @@
import {BotFormEntity, IBotFormEntity} from "@/app/home/bot-config/components/bot-form/BotFormEntity";
import {fetchAdapterList} from "@/app/home/mock-api/index"
import {Button, Form, Input, Select, Space} from "antd";
import {useEffect, useState} from "react";
import {IChooseAdapterEntity} from "@/app/home/bot-config/components/bot-form/ChooseAdapterEntity";
import {
DynamicFormItemConfig,
IDynamicFormItemConfig,
parseDynamicFormItemType
} from "@/app/home/components/dynamic-form/DynamicFormItemConfig";
import {UUID} from 'uuidjs'
import DynamicFormComponent from "@/app/home/components/dynamic-form/DynamicFormComponent";
import {ICreateLLMField} from "@/app/home/llm-config/ICreateLLMField";
export default function BotForm({
initBotId,
onFormSubmit,
onFormCancel,
}: {
initBotId?: string;
onFormSubmit: (value: IBotFormEntity) => void;
onFormCancel: (value: IBotFormEntity) => void;
}) {
const [adapterNameToDynamicConfigMap, setAdapterNameToDynamicConfigMap] = useState(new Map<string, IDynamicFormItemConfig[]>())
const [form] = Form.useForm<IBotFormEntity>();
const [showDynamicForm, setShowDynamicForm] = useState<boolean>(false)
const [dynamicForm] = Form.useForm();
const [adapterNameList, setAdapterNameList] = useState<IChooseAdapterEntity[]>([])
const [dynamicFormConfigList, setDynamicFormConfigList] = useState<IDynamicFormItemConfig[]>([])
useEffect(() => {
initBotFormComponent()
if (initBotId) {
onEditMode()
} else {
onCreateMode()
}
}, [])
async function initBotFormComponent() {
// 拉取adapter
const rawAdapterList = await fetchAdapterList()
// 初始化适配器选择列表
setAdapterNameList(
rawAdapterList.map(item => {
return {
label: item.label.zh_CN,
value: item.name
}
})
)
// 初始化适配器表单map
rawAdapterList.forEach(rawAdapter => {
adapterNameToDynamicConfigMap.set(
rawAdapter.name,
rawAdapter.spec.config.map(item =>
new DynamicFormItemConfig({
default: item.default,
id: UUID.generate(),
label: item.label,
name: item.name,
required: item.required,
type: parseDynamicFormItemType(item.type)
})
)
)
})
// 拉取初始化表单信息
if (initBotId) {
getBotFieldById(initBotId).then(val => {
form.setFieldsValue(val)
handleAdapterSelect(val.adapter)
})
} else {
form.resetFields()
}
setAdapterNameToDynamicConfigMap(adapterNameToDynamicConfigMap)
}
async function onCreateMode() {
}
function onEditMode() {
}
async function getBotFieldById(botId: string): Promise<IBotFormEntity> {
return new BotFormEntity({
adapter: "telegram",
description: "模拟拉取bot",
name: "模拟电报bot",
adapter_config: {
token: "aaabbbccc"
},
})
}
function handleAdapterSelect(adapterName: string) {
if (adapterName) {
console.log(adapterNameToDynamicConfigMap)
const dynamicFormConfigList = adapterNameToDynamicConfigMap.get(adapterName)
if (dynamicFormConfigList) {
console.log(dynamicFormConfigList)
setDynamicFormConfigList(dynamicFormConfigList)
}
setShowDynamicForm(true)
} else {
setShowDynamicForm(false)
}
}
function handleSubmitButton() {
form.submit()
}
function handleFormFinish(value: IBotFormEntity) {
dynamicForm.submit()
}
// 只有通过外层固定表单验证才会走到这里,真正的提交逻辑在这里
function onDynamicFormSubmit(value: object) {
if (initBotId) {
// 编辑提交
console.log('submit edit', form.getFieldsValue() ,value)
} else {
// 创建提交
console.log('submit create', form.getFieldsValue() ,value)
}
onFormSubmit(form.getFieldsValue())
setShowDynamicForm(false)
form.resetFields()
dynamicForm.resetFields()
}
function handleSaveButton() {
}
return (
<div>
<Form
form={form}
labelCol={{span: 5}}
wrapperCol={{span: 18}}
layout='vertical'
onFinish={handleFormFinish}
>
<Form.Item<IBotFormEntity>
label={"机器人名称"}
name={"name"}
rules={[{required: true, message: "该项为必填项哦~"}]}
>
<Input
placeholder="为机器人取个好听的名字吧~"
style={{width: 260}}
></Input>
</Form.Item>
<Form.Item<IBotFormEntity>
label={"描述"}
name={"description"}
rules={[{required: true, message: "该项为必填项哦~"}]}
>
<Input
placeholder="简单描述一下这个机器人"
></Input>
</Form.Item>
<Form.Item<IBotFormEntity>
label={"平台/适配器选择"}
name={"adapter"}
rules={[{required: true, message: "该项为必填项哦~"}]}
>
<Select
style={{width: 220}}
onChange={(value) => {
handleAdapterSelect(value)
}}
options={adapterNameList}
/>
</Form.Item>
</Form>
{
showDynamicForm &&
<DynamicFormComponent
form={dynamicForm}
itemConfigList={dynamicFormConfigList}
onSubmit={onDynamicFormSubmit}
/>
}
<Space>
{
!initBotId &&
<Button
type="primary"
htmlType="button"
onClick={handleSubmitButton}
>
</Button>
}
{
initBotId &&
<Button
type="primary"
htmlType="submit"
onClick={handleSaveButton}
>
</Button>
}
<Button htmlType="button" onClick={() => {
onFormCancel(form.getFieldsValue())
}}>
</Button>
</Space>
</div>
)
}

View File

@@ -0,0 +1,20 @@
export interface IBotFormEntity {
name: string,
description: string,
adapter: string,
adapter_config: object;
}
export class BotFormEntity implements IBotFormEntity {
adapter: string;
description: string;
name: string;
adapter_config: object;
constructor(props: IBotFormEntity) {
this.adapter = props.adapter;
this.description = props.description;
this.name = props.name;
this.adapter_config = props.adapter_config;
}
}

View File

@@ -0,0 +1,4 @@
export interface IChooseAdapterEntity {
label: string
value: string
}

View File

@@ -0,0 +1,150 @@
"use client"
import {useEffect, useState} from "react";
import styles from "./botConfig.module.css";
import EmptyAndCreateComponent from "@/app/home/components/empty-and-create-component/EmptyAndCreateComponent";
import {useRouter} from "next/navigation";
import {BotCardVO} from "@/app/home/bot-config/components/bot-card/BotCardVO";
import {Modal} from "antd";
import BotForm from "@/app/home/bot-config/components/bot-form/BotForm";
import BotCard from "@/app/home/bot-config/components/bot-card/BotCard";
import CreateCardComponent from "@/app/infra/basic-component/create-card-component/CreateCardComponent"
export default function BotConfigPage() {
const router = useRouter();
const [pageShowRule, setPageShowRule] = useState<BotConfigPageShowRule>(BotConfigPageShowRule.NO_BOT)
const [modalOpen, setModalOpen] = useState<boolean>(false);
const [botList, setBotList] = useState<BotCardVO[]>([])
const [isEditForm, setIsEditForm] = useState(false)
const [nowSelectedBotCard, setNowSelectedBotCard] = useState<BotCardVO>()
useEffect(() => {
// TODO补齐加载转圈逻辑
checkHasLLM().then((hasLLM) => {
if (hasLLM) {
const botList = getBotList()
if (botList.length === 0) {
setPageShowRule(BotConfigPageShowRule.NO_BOT)
} else {
setPageShowRule(BotConfigPageShowRule.HAVE_BOT)
}
setBotList(botList)
} else {
setPageShowRule(BotConfigPageShowRule.NO_LLM)
}
})
}, [])
async function checkHasLLM(): Promise<boolean> {
// NOT IMPL
return true
}
function getBotList(): BotCardVO[] {
let botList: BotCardVO[] = [
new BotCardVO({
adapter: "QQ bot",
description: "1111",
id: "1111",
name: "第一个bot",
updateTime: "202300001111",
pipelineName: "默认流水线",
}),
new BotCardVO({
adapter: "WX bot",
description: "22211",
id: "2222",
name: "第2个bot",
updateTime: "2025011011",
pipelineName: "默认流水线",
}),
]
// botList = []
return botList
}
function handleCreateBotClick() {
setIsEditForm(false)
setNowSelectedCard(undefined)
setModalOpen(true);
}
function setNowSelectedCard(cardVO: BotCardVO | undefined) {
setNowSelectedBotCard(cardVO)
}
function selectBot(cardVO: BotCardVO) {
setIsEditForm(true)
setNowSelectedCard(cardVO)
console.log("set now vo", cardVO)
setModalOpen(true)
}
return (
<div className={styles.configPageContainer}>
<Modal
title={isEditForm ? "编辑机器人" : "创建机器人"}
centered
open={modalOpen}
onOk={() => setModalOpen(false)}
onCancel={() => setModalOpen(false)}
width={700}
footer={null}
destroyOnClose={true}
>
<BotForm
initBotId={nowSelectedBotCard?.id}
onFormSubmit={() => setIsEditForm(false)}
onFormCancel={() => setModalOpen(false)}
/>
</Modal>
{pageShowRule === BotConfigPageShowRule.NO_LLM &&
<EmptyAndCreateComponent
title={"需要先创建大模型才能配置机器人哦~"}
subTitle={"快去创建一个吧!"}
buttonText={"创建大模型 GO"}
onButtonClick={() => {
router.push("/home/llm-config");
}}
/>
}
{pageShowRule === BotConfigPageShowRule.NO_BOT &&
<EmptyAndCreateComponent
title={"您还未配置机器人哦~"}
subTitle={"快去创建一个吧!"}
buttonText={"创建机器人 +"}
onButtonClick={handleCreateBotClick}
/>
}
{pageShowRule === BotConfigPageShowRule.HAVE_BOT &&
<div className={`${styles.botListContainer}`}
>
{botList.map(cardVO => {
return (
<div
key={cardVO.id}
onClick={() => {selectBot(cardVO)}}
>
<BotCard botCardVO={cardVO} />
</div>)
})}
<CreateCardComponent
width={360}
height={200}
plusSize={90}
onClick={handleCreateBotClick}
/>
</div>
}
</div>
)
}
enum BotConfigPageShowRule {
NO_LLM,
NO_BOT,
HAVE_BOT,
}

View File

@@ -0,0 +1,30 @@
import {IDynamicFormItemConfig} from "@/app/home/components/dynamic-form/DynamicFormItemConfig";
import {Form, FormInstance} from "antd";
import DynamicFormItemComponent from "@/app/home/components/dynamic-form/DynamicFormItemComponent";
export default function DynamicFormComponent({
form,
itemConfigList,
onSubmit,
}: {
form: FormInstance<object>
itemConfigList: IDynamicFormItemConfig[]
onSubmit?: (val: object) => unknown
}) {
return (
<Form
form={form}
onFinish={onSubmit}
layout={"vertical"}
>
{
itemConfigList.map(config =>
<DynamicFormItemComponent
key={config.id}
config={config}
/>
)
}
</Form>
)
}

View File

@@ -0,0 +1,38 @@
import {Form, Input, InputNumber, Select, Switch} from "antd";
import {DynamicFormItemType, IDynamicFormItemConfig} from "@/app/home/components/dynamic-form/DynamicFormItemConfig";
export default function DynamicFormItemComponent({
config
}: {
config: IDynamicFormItemConfig
}) {
return (
<Form.Item
label={config.label.zh_CN}
name={config.name}
rules={[{required: config.required, message: "该项为必填项哦~"}]}
initialValue={config.default}
>
{
config.type === DynamicFormItemType.INT &&
<InputNumber/>
}
{
config.type === DynamicFormItemType.STRING &&
<Input/>
}
{
config.type === DynamicFormItemType.BOOLEAN &&
<Switch defaultChecked/>
}
{
config.type === DynamicFormItemType.STRING_ARRAY &&
<Select options={[]}/>
}
</Form.Item>
)
}

View File

@@ -0,0 +1,51 @@
export interface IDynamicFormItemConfig {
id: string;
default: string | number | boolean | Array<unknown>;
label: IDynamicFormItemLabel;
name: string;
required: boolean;
type: DynamicFormItemType
description?: IDynamicFormItemLabel;
}
export class DynamicFormItemConfig implements IDynamicFormItemConfig {
id: string;
name: string;
default: string | number | boolean | Array<unknown>;
label: IDynamicFormItemLabel;
required: boolean;
type: DynamicFormItemType;
description?: IDynamicFormItemLabel;
constructor(params: IDynamicFormItemConfig) {
this.id = params.id;
this.name = params.name;
this.default = params.default;
this.label = params.label;
this.required = params.required;
this.type = params.type;
this.description = params.description;
}
}
export interface IDynamicFormItemLabel {
en_US: string,
zh_CN: string,
}
export enum DynamicFormItemType {
INT = "integer",
STRING = "string",
BOOLEAN = "boolean",
STRING_ARRAY = "array[string]",
UNKNOWN = "unknown",
}
export function isDynamicFormItemType(value: string): value is DynamicFormItemType {
return Object.values(DynamicFormItemType).includes(value as DynamicFormItemType);
}
export function parseDynamicFormItemType(value: string): DynamicFormItemType {
return isDynamicFormItemType(value) ? value : DynamicFormItemType.UNKNOWN;
}

View File

@@ -0,0 +1,41 @@
import {
DynamicFormItemConfig,
DynamicFormItemType,
IDynamicFormItemConfig
} from "@/app/home/components/dynamic-form/DynamicFormItemConfig";
export const testDynamicConfigList: IDynamicFormItemConfig[] = [
new DynamicFormItemConfig({
default: "",
id: "111",
label: {
zh_CN: "测试字段string",
en_US: "eng test"
},
name: "string_test",
required: false,
type: DynamicFormItemType.STRING
}),
new DynamicFormItemConfig({
default: "",
id: "222",
label: {
zh_CN: "测试字段int",
en_US: "int eng test"
},
name: "int_test",
required: true,
type: DynamicFormItemType.INT
}),
new DynamicFormItemConfig({
default: "",
id: "333",
label: {
zh_CN: "测试字段boolean",
en_US: "boolean eng test"
},
name: "boolean_test",
required: false,
type: DynamicFormItemType.BOOLEAN
}),
]

View File

@@ -0,0 +1,35 @@
import styles from "./emptyAndCreate.module.css";
export default function EmptyAndCreateComponent({
title,
subTitle,
buttonText,
onButtonClick,
}: {
title: string,
subTitle: string,
buttonText: string,
onButtonClick: () => void,
}) {
return (
<div className={`${styles.emptyPageContainer}`}>
<div className={`${styles.emptyContainer}`}>
<div className={`${styles.emptyInfoContainer}`}>
<div className={`${styles.emptyInfoText}`}>
{title}
</div>
<div className={`${styles.emptyInfoSubText}`}>
{subTitle}
</div>
</div>
<div
className={`${styles.emptyCreateButton}`}
onClick={onButtonClick}
>
{buttonText}
</div>
</div>
</div>
)
}

View File

@@ -0,0 +1,54 @@
.emptyPageContainer {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background: #FFF;
border: 1px solid #c5c5c5;
border-radius: 10px;
}
.emptyContainer {
width: 100%;
height: 50%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-evenly;
}
.emptyCreateButton {
width: 200px;
height: 50px;
border-radius: 20px;
background-color: #6062E7;
color: #FFF;
font-size: 20px;
font-weight: bold;
text-align: center;
line-height: 50px;
user-select: none;
}
.emptyCreateButton:hover {
background-color: #4b4de3;
}
.emptyInfoContainer {
width: 100%;
height: 60px;
display: flex;
flex-direction: column;
align-items: center;
color: #353535;
}
.emptyInfoText {
font-size: 30px;
}
.emptyInfoSubText {
font-size: 28px;
}

View File

@@ -0,0 +1,67 @@
.sidebarContainer {
box-sizing: border-box;
width: 240px;
height: 100vh;
background-color: #FFF;
display: flex;
flex-direction: column;
align-items: center;
}
.langbotIconContainer {
width: 240px;
height: 70px;
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-evenly;
.langbotIcon {
width: 54px;
height: 54px;
border-radius: 12px;
background: #6062E7;
color: #fbfbfb;
font-weight: 600;
font-size: 36px;
line-height: 54px;
text-align: center;
}
.langbotText {
font-size: 26px;
}
}
.sidebarChildContainer {
box-sizing: border-box;
width: 198px;
height: 48px;
margin: 12px 0;
font-size: 16px;
background-color: #fff;
border-radius: 12px;
display: flex;
flex-direction: row;
align-items: center;
cursor: pointer;
}
.sidebarSelected {
background-color: #6062E7;
color: white;
}
.sidebarUnselected {
background-color: white;
color: #6C6C6C;
}
.sidebarChildIcon {
width: 28px;
height: 28px;
margin-left: 16px;
margin-right: 6px;
background-color: rgba(96, 149, 209, 0);
}

View File

@@ -0,0 +1,107 @@
"use client"
import styles from "./HomeSidebar.module.css"
import {useEffect, useState} from "react";
import {SidebarChild, SidebarChildVO} from "@/app/home/components/home-sidebar/HomeSidebarChild";
import {useRouter, usePathname, useSearchParams} from "next/navigation";
import {sidebarConfigList} from "@/app/home/components/home-sidebar/sidbarConfigList";
// TODO 侧边导航栏要加动画
export default function HomeSidebar({
onSelectedChange
}: {
onSelectedChange: (sidebarChild: SidebarChildVO) => void
}) {
// 路由相关
const router = useRouter()
const pathname = usePathname();
const searchParams = useSearchParams();
// 路由被动变化时处理
useEffect(() => {
handleRouteChange(pathname)
}, [pathname, searchParams]);
const [selectedChild, setSelectedChild] = useState<SidebarChildVO>(sidebarConfigList[0])
useEffect(() => {
console.log('HomeSidebar挂载完成');
initSelect()
return () => console.log('HomeSidebar卸载');
}, []);
function handleChildClick(child: SidebarChildVO) {
setSelectedChild(child)
handleRoute(child)
onSelectedChange(child)
}
function initSelect() {
handleChildClick(sidebarConfigList[0])
}
function handleRoute(child: SidebarChildVO) {
console.log(child)
router.push(`${child.route}`)
}
function handleRouteChange(pathname: string) {
// TODO 这段逻辑并不好未来router封装好后改掉
// 判断在home下并且路由更改的是自己的路由子组件则更新UI
const routeList = pathname.split('/')
if (
routeList[1] === "home" &&
sidebarConfigList.find(childConfig =>
childConfig.route === pathname
)
) {
console.log("find success")
const routeSelectChild = sidebarConfigList.find(childConfig =>
childConfig.route === pathname
)
if (routeSelectChild) {
setSelectedChild(routeSelectChild)
}
}
}
return (
<div className={`${styles.sidebarContainer}`}>
{/* LangBot、ICON区域 */}
<div className={`${styles.langbotIconContainer}`}>
{/* icon */}
<div className={`${styles.langbotIcon}`}>
L
</div>
<div className={`${styles.langbotText}`}>
Langbot
</div>
</div>
{/* 菜单列表,后期可升级成配置驱动 */}
<div>
{
sidebarConfigList.map(config => {
return (
<div
key={config.id}
onClick={() => {
console.log('click:', config.id)
handleChildClick(config)
}}
>
<SidebarChild
isSelected={selectedChild.id === config.id}
icon={config.icon}
name={config.name}
/>
</div>
)
})
}
</div>
</div>
);
}

View File

@@ -0,0 +1,41 @@
import styles from "./HomeSidebar.module.css";
export interface ISidebarChildVO {
id: string;
icon: string;
name: string;
route: string;
}
export class SidebarChildVO {
id: string;
icon: string;
name: string;
route: string;
constructor(props: ISidebarChildVO) {
this.id = props.id;
this.icon = props.icon;
this.name = props.name;
this.route = props.route;
}
}
export function SidebarChild({
icon,
name,
isSelected,
}: {
icon: string;
name: string;
isSelected: boolean;
}) {
return (
<div className={`${styles.sidebarChildContainer} ${isSelected ? styles.sidebarSelected : styles.sidebarUnselected}`}>
<div className={`${styles.sidebarChildIcon}`}/>
<div>{name}</div>
</div>
);
}

View File

@@ -0,0 +1,28 @@
import {SidebarChildVO} from "@/app/home/components/home-sidebar/HomeSidebarChild";
export const sidebarConfigList = [
new SidebarChildVO({
id: "llm-config",
name: "大模型配置",
icon: "",
route: "/home/llm-config",
}),
new SidebarChildVO({
id: "platform-config",
name: "机器人配置",
icon: "",
route: "/home/bot-config",
}),
new SidebarChildVO({
id: "plugin-config",
name: "插件配置",
icon: "",
route: "/home/plugin-config",
}),
new SidebarChildVO({
id: "pipeline-config",
name: "流水线配置",
icon: "",
route: "/home/pipeline-config",
})
]

View File

@@ -0,0 +1,16 @@
import styles from "./HomeTittleBar.module.css"
export default function HomeTitleBar({
title,
}: {
title: string
}) {
return (
<div className={`${styles.titleBarContainer}`}>
<div
className={`${styles.titleText}`}
>{title}</div>
</div>
);
}

View File

@@ -0,0 +1,15 @@
.titleBarContainer {
width: 100%;
height: 70px;
background-color: #FFF;
font-size: 20px;
display: flex;
flex-direction: row;
align-items: center;
}
.titleText {
margin-left: 10px;
font-size: 20px;
font-weight: bold;
}

View File

@@ -0,0 +1,18 @@
.homeLayoutContainer {
width: 100vw;
height: 100vh;
display: flex;
flex-direction: row;
}
.main {
width: 100%;
height: 100%;
background-color: #FAFBFB;
}
.mainContent {
width: calc(100% - 40px);
height: calc(100% - 110px);
margin: 20px;
}

View File

@@ -0,0 +1,36 @@
"use client"
import '@ant-design/v5-patch-for-react-19';
import styles from "./layout.module.css"
import HomeSidebar from "@/app/home/components/home-sidebar/HomeSidebar";
import HomeTitleBar from "@/app/home/components/home-titlebar/HomeTitleBar";
import React, {useState} from "react";
import {SidebarChildVO} from "@/app/home/components/home-sidebar/HomeSidebarChild";
import { useRouter } from 'next/navigation';
export default function HomeLayout({
children
}: Readonly<{
children: React.ReactNode;
}>) {
const router = useRouter();
const [title, setTitle] = useState<string>("")
const onSelectedChange = (child: SidebarChildVO) => {
setTitle(child.name)
}
return (
<div className={`${styles.homeLayoutContainer}`}>
<HomeSidebar
onSelectedChange={onSelectedChange}
/>
<div className={`${styles.main}`}>
<HomeTitleBar title={title}/>
{/* 主页面 */}
<div className={`${styles.mainContent}`}>
{children}
</div>
</div>
</div>
)
}

View File

@@ -0,0 +1,8 @@
export interface ICreateLLMField {
name: string;
model_provider: string;
url: string;
api_key: string;
abilities: string[];
extra_args: string[];
}

View File

@@ -0,0 +1,90 @@
.configPageContainer {
width: 100%;
height: 100%;
}
.modalContainer {
width: 100%;
/*height: calc(100vh - 200px);*/
margin-top: 20px;
}
.modelListContainer {
align-self: flex-start;
justify-self: flex-start;
width: calc(100% - 60px);
margin: auto;
display: grid;
grid-template-rows: repeat(auto-fill, minmax(220px, 1fr));
grid-template-columns: repeat(auto-fill, minmax(360px, 1fr));
gap: 15px;
justify-items: center;
align-items: center;
}
.emptyContainer {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.cardContainer {
width: 360px;
height: 200px;
background-color: #FFF;
border-radius: 9px;
box-shadow: rgba(0, 0, 0, 0.4) 0 1px 1px -1px;
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: space-evenly;
}
.iconBasicInfoContainer {
width: 300px;
height: 100px;
margin-left: 20px;
display: flex;
flex-direction: row;
}
.icon {
width: 90px;
height: 90px;
border-radius: 5px;
font-size: 40px;
line-height: 90px;
text-align: center;
color: #ffffff;
background: rgba(96, 149, 209, 0.31);
border: 1px solid rgba(96, 149, 209, 0.31);
}
.basicInfoContainer {
width: 200px;
height: 90px;
padding-left: 20px;
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: space-between;
}
.basicInfoText {
}
.bigText {
font-size: 20px;
}
.urlAndUpdateText {
margin-left: 20px;
}

View File

@@ -0,0 +1,39 @@
import styles from "../../LLMConfig.module.css"
import {LLMCardVO} from "@/app/home/llm-config/component/llm-card/LLMCardVO";
export default function LLMCard({
cardVO
}: {
cardVO: LLMCardVO
}) {
return (
<div className={`${styles.cardContainer}`}>
{/* icon和基本信息 */}
<div className={`${styles.iconBasicInfoContainer}`}>
{/* icon */}
<div className={`${styles.icon}`}>
ICO
</div>
{/* bot基本信息 */}
<div className={`${styles.basicInfoContainer}`}>
<div className={`${styles.basicInfoText} ${styles.bigText}`}>
{cardVO.name}
</div>
<div className={`${styles.basicInfoText}`}>
使{cardVO.model}
</div>
<div className={`${styles.basicInfoText}`}>
{cardVO.company}
</div>
</div>
</div>
{/* URL和创建时间 */}
<div className={`${styles.urlAndUpdateText}`}>
URL{cardVO.URL}
</div>
<div className={`${styles.urlAndUpdateText}`}>
{cardVO.updateTime}
</div>
</div>
);
}

View File

@@ -0,0 +1,27 @@
export interface ILLMCardVO {
id: string;
name: string;
model: string;
company: string;
URL: string;
updateTime: string;
}
export class LLMCardVO implements ILLMCardVO {
id: string;
name: string;
model: string;
company: string;
URL: string;
updateTime: string;
constructor(props: ILLMCardVO) {
this.id = props.id;
this.name = props.name;
this.model = props.model;
this.company = props.company;
this.URL = props.URL;
this.updateTime = props.updateTime;
}
}

View File

@@ -0,0 +1,177 @@
import styles from "@/app/home/llm-config/LLMConfig.module.css";
import {Button, Form, Input, Select, SelectProps, Space} from "antd";
import {ICreateLLMField} from "@/app/home/llm-config/ICreateLLMField";
import {useEffect, useState} from "react";
export default function LLMForm({
editMode,
initLLMId,
onFormSubmit,
onFormCancel,
}: {
editMode: boolean;
initLLMId?: string;
onFormSubmit: (value: ICreateLLMField) => void;
onFormCancel: (value: ICreateLLMField) => void;
}) {
const [form] = Form.useForm<ICreateLLMField>();
const extraOptions: SelectProps['options'] = []
const [initValue, setInitValue] = useState<ICreateLLMField>()
const abilityOptions: SelectProps['options'] = [
{
label: '函数调用',
value: 'func_call',
},
{
label: '图像识别',
value: 'vision',
},
];
useEffect(() => {
if (editMode && initLLMId) {
getLLMConfig(initLLMId).then(val => {
form.setFieldsValue(val)
})
} else {
form.resetFields()
}
})
async function getLLMConfig(id: string): Promise<ICreateLLMField> {
return {
name: id,
model_provider: "OpenAI",
url: "www.aaa.com",
api_key: "",
abilities: [],
extra_args: [],
}
}
function handleFormSubmit(value: ICreateLLMField) {
if (editMode) {
onSaveEdit(value)
} else {
onCreateLLM(value)
}
onFormSubmit(value)
form.resetFields()
}
function onSaveEdit(value: ICreateLLMField) {
console.log("edit save", value)
}
function onCreateLLM(value: ICreateLLMField) {
console.log("create llm", value)
}
function handleAbilitiesChange() {
}
return (
<div className={styles.modalContainer}>
<Form
form={form}
labelCol={{span: 4}}
wrapperCol={{span: 14}}
layout='horizontal'
initialValues={{
...initValue
}}
onFinish={handleFormSubmit}
clearOnDestroy={true}
>
<Form.Item<ICreateLLMField>
label={"模型名称"}
name={"name"}
rules={[{required: true, message: "该项为必填项哦~"}]}
>
<Input
placeholder={"为自己的大模型取个好听的名字~"}
style={{width: 260}}
></Input>
</Form.Item>
<Form.Item<ICreateLLMField>
label={"模型供应商"}
name={"model_provider"}
rules={[{required: true, message: "该项为必填项哦~"}]}
>
<Select
style={{width: 120}}
onChange={() => {
}}
options={[
{value: 'OpenAI', label: 'OpenAI'},
{value: 'OLAMA', label: 'OLAMA'},
{value: 'DeepSeek', label: 'DeepSeek'},
]}
/>
</Form.Item>
<Form.Item<ICreateLLMField>
label={"请求URL"}
name={"url"}
rules={[{required: true, message: "该项为必填项哦~"}]}
>
<Input
placeholder="请求地址一般是API提供商提供的URL"
style={{width: 500}}
></Input>
</Form.Item>
<Form.Item<ICreateLLMField>
label={"开启能力"}
name={"abilities"}
>
<Select
mode="tags"
style={{width: 500}}
placeholder="选择模型能力,输入回车可自定义能力"
onChange={handleAbilitiesChange}
options={abilityOptions}
/>
</Form.Item>
<Form.Item<ICreateLLMField>
label={"其他参数"}
name={"extra_args"}
>
<Select
mode="tags"
style={{width: 500}}
placeholder="输入后回车可自定义其他参数,例 key:value"
onChange={handleAbilitiesChange}
options={extraOptions}
/>
</Form.Item>
<Form.Item
wrapperCol={{offset: 4, span: 14}}
>
<Space>
{
!editMode &&
<Button type="primary" htmlType="submit">
</Button>
}
{
editMode &&
<Button type="primary" htmlType="submit">
</Button>
}
<Button htmlType="button" onClick={() => {
onFormCancel(form.getFieldsValue())
}}>
</Button>
</Space>
</Form.Item>
</Form>
</div>
)
}

View File

@@ -0,0 +1,126 @@
"use client"
import {useState} from "react";
import {LLMCardVO} from "@/app/home/llm-config/component/llm-card/LLMCardVO";
import styles from "./LLMConfig.module.css"
import EmptyAndCreateComponent from "@/app/home/components/empty-and-create-component/EmptyAndCreateComponent";
import {Modal} from "antd";
import LLMCard from "@/app/home/llm-config/component/llm-card/LLMCard";
import LLMForm from "@/app/home/llm-config/component/llm-form/LLMForm";
import CreateCardComponent from "@/app/infra/basic-component/create-card-component/CreateCardComponent";
export default function LLMConfigPage() {
const [cardList, setCardList] = useState<LLMCardVO[]>([
new LLMCardVO({
id: "1",
name: "测试模型",
model: "GPT-4o",
URL: "www.openai.com",
company: "OpenAI",
updateTime: "2025.1.2"
}),
new LLMCardVO({
id: "2",
name: "测试模型",
model: "GPT-4o",
URL: "www.openai.com",
company: "OpenAI",
updateTime: "2025.1.2"
}),
new LLMCardVO({
id: "3",
name: "测试模型",
model: "GPT-4o",
URL: "www.openai.com",
company: "OpenAI",
updateTime: "2025.1.2"
}),
new LLMCardVO({
id: "4",
name: "测试模型",
model: "GPT-4o",
URL: "www.openai.com",
company: "OpenAI",
updateTime: "2025.1.2"
}),
new LLMCardVO({
id: "5",
name: "测试模型",
model: "GPT-4o",
URL: "www.openai.com",
company: "OpenAI",
updateTime: "2025.1.2"
}),
])
const [modalOpen, setModalOpen] = useState<boolean>(false);
const [isEditForm, setIsEditForm] = useState(false)
const [nowSelectedLLM, setNowSelectedLLM] = useState<LLMCardVO | null>(null)
function selectLLM(cardVO: LLMCardVO) {
setIsEditForm(true)
setNowSelectedLLM(cardVO)
console.log("set now vo", cardVO)
setModalOpen(true)
}
function handleCreateModelClick() {
setIsEditForm(false)
setNowSelectedLLM(null)
setModalOpen(true);
}
return (
<div className={styles.configPageContainer}>
<Modal
title={isEditForm ? "编辑模型" : "创建模型"}
centered
open={modalOpen}
onOk={() => setModalOpen(false)}
onCancel={() => setModalOpen(false)}
width={700}
footer={null}
>
<LLMForm
editMode={isEditForm}
initLLMId={nowSelectedLLM?.id}
onFormSubmit={() => {
setModalOpen(false);
}}
onFormCancel={() => {
setModalOpen(false);
}}
/>
</Modal>
{
cardList.length > 0 &&
<div className={`${styles.modelListContainer}`}
>
{cardList.map(cardVO => {
return <div key={cardVO.id} onClick={() => {selectLLM(cardVO)}}>
<LLMCard cardVO={cardVO}></LLMCard>
</div>
})}
<CreateCardComponent
width={360}
height={200}
plusSize={90}
onClick={handleCreateModelClick}
/>
</div>
}
{
cardList.length === 0 &&
<div className={`${styles.emptyContainer}`}>
<EmptyAndCreateComponent
title={"模型列表空空如也~"}
subTitle={"快去创建一个吧!"}
buttonText={"创建模型 +"}
onButtonClick={() => {
handleCreateModelClick()
}}
/>
</div>
}
</div>
)
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,6 @@
export default function Home() {
return (
<div className={``}>
</div>
);
}

View File

@@ -0,0 +1,20 @@
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.form = props.form;
this.label = props.label;
this.name = props.name;
this.formItems = props.formItems;
}
}

View File

@@ -0,0 +1,512 @@
import {Form, Button, Switch, Select, Input, InputNumber} from "antd";
import { CaretLeftOutlined, CaretRightOutlined } from '@ant-design/icons';
import {useState} from "react";
import styles from "./pipelineFormStyle.module.css"
export default function PipelineFormComponent({
onFinish,
onCancel,
}: {
onFinish: () => void;
onCancel: () => void;
}) {
const [nowFormIndex, setNowFormIndex] = useState<number>(0)
// 这里不好可以改成enum等
const formLabelList: FormLabel[] = [
{label: "AI能力", name: "ai"},
{label: "触发条件", name: "trigger"},
{label: "安全能力", name: "safety"},
{label: "输出处理", name: "output"},
]
function getNowFormLabel() {
return formLabelList[nowFormIndex]
}
function getPreFormLabel(): undefined | FormLabel {
if (nowFormIndex !== undefined && nowFormIndex > 0) {
return formLabelList[nowFormIndex - 1]
} else {
return undefined
}
}
function getNextFormLabel(): undefined | FormLabel {
if (nowFormIndex !== undefined && nowFormIndex < formLabelList.length - 1) {
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)
}
}
return (
<div
style={{ maxHeight: '70vh', overflowY: 'auto' }}
>
<h1>
{getNowFormLabel().label}
</h1>
{/* AI能力表单 ai */}
<Form
layout={"vertical"}
style={{ display: getNowFormLabel().name === "ai" ? 'block' : 'none' }}
>
{/* 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" }
]}
/>
</Form.Item>
{/* 内置 Agent 配置区块 */}
<div className={`${styles.formItemSubtitle}`}>Agent</div>
{/* TODO 这里要拉模型 */}
<Form.Item
label="模型"
name={["local-agent", "model"]}
rules={[{ required: true }]}
tooltip="从模型库中选择"
>
<Select
options={[]}
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 区块 */}
<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>
{/* 阿里云百炼区块 */}
<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 */}
<Form
layout={"vertical"}
style={{ display: getNowFormLabel().name === "trigger" ? 'block' : 'none' }}
>
{/* 群响应规则块 */}
<Form.Item>
</Form.Item>
<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>
<Form.Item>
访
</Form.Item>
<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>
<Form.Item
label={"消息忽略规则"}
>
</Form.Item>
<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' }}
>
{/* 内容过滤块 content-filter */}
<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 */}
<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.Item label="长文本处理" />
<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>
{/* 强制延迟区块 */}
<Form.Item label="强制延迟" />
<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>
{/* 杂项区块 */}
<Form.Item label="杂项" />
<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>
</div>
</div>
)
}
enum PipelineFormRoute {
}
interface FormPageLabel {
formIndex: number,
formName: string,
formLabel: string,
}
interface FormLabel {
label: string,
name: string,
}

View File

@@ -0,0 +1,12 @@
.formItemSubtitle {
font-size: 18px;
font-weight: bold;
margin-bottom: 10px;
}
.changeFormButtonGroupContainer {
width: 250px;
display: flex;
flex-direction: row;
justify-content: space-between;
}

View File

@@ -0,0 +1,30 @@
"use client"
import {Modal} from "antd";
import {useState} from "react";
import CreateCardComponent from "@/app/infra/basic-component/create-card-component/CreateCardComponent";
import PipelineFormComponent from "./components/pipeline-form/PipelineFormComponent";
export default function PluginConfigPage() {
const [modalOpen, setModalOpen] = useState<boolean>(false);
const [isEditForm, setIsEditForm] = useState(false)
return (
<div className={``}>
<Modal
title={isEditForm ? "编辑流水线" : "创建流水线"}
centered
open={modalOpen}
onOk={() => setModalOpen(false)}
onCancel={() => setModalOpen(false)}
width={700}
footer={null}
>
<PipelineFormComponent onFinish={() => {}} onCancel={() => {}}/>
</Modal>
<CreateCardComponent width={360} height={200} plusSize={90} onClick={() => {setModalOpen(true)}}/>
</div>
);
}

View File

@@ -0,0 +1,4 @@
.formItemSubTitle {
font-size: 30px;
font-weight: bold;
}

View File

@@ -0,0 +1,7 @@
export default function pluginConfigPage() {
return (
<div className={``}>
<h1>PluginConfigPage</h1>
</div>
);
}

View File

@@ -0,0 +1,11 @@
export interface ApiResponse<T> {
code: number;
data: T;
msg: string;
}
export interface I18nText {
en_US: string;
zh_CN: string;
}

View File

@@ -0,0 +1,47 @@
export interface GetMetaDataResponse {
configs: Config[]
}
interface Label {
en_US: string;
zh_CN: string;
}
interface Option {
label: Label;
name: string;
}
interface ConfigItem {
default?: boolean | Array<unknown> | number | string;
description?: Label;
items?: {
type?: string;
properties?: {
[key: string]: {
type: string;
default?: any;
};
};
};
label: Label;
name: string;
options?: Option[];
required: boolean;
scope?: string;
type: string;
}
interface Stage {
config: ConfigItem[];
description?: Label;
label: Label;
name: string;
}
interface Config {
label: Label;
name: string;
stages: Stage[];
}

View File

@@ -0,0 +1,27 @@
import styles from "./createCartComponent.module.css";
export default function CreateCardComponent({
width,
height,
plusSize,
onClick,
}: {
width: number;
height: number;
plusSize: number;
onClick: () => void
}) {
return (
<div
className={`${styles.cardContainer} ${styles.createCardContainer} `}
style={{
width: `${width}px`,
height: `${height}px`,
fontSize: `${plusSize}px`
}}
onClick={onClick}
>
+
</div>
)
}

View File

@@ -0,0 +1,16 @@
.cardContainer {
width: 360px;
height: 200px;
background-color: #FFF;
border-radius: 9px;
box-shadow: rgba(0, 0, 0, 0.4) 0 1px 1px -1px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-evenly;
}
.createCardContainer {
font-size: 90px;
color: #acacac;
}

View File

@@ -0,0 +1,4 @@
export interface I18NText {
en_US: string;
zh_CN: string;
}

22
web_ui/src/app/layout.tsx Normal file
View File

@@ -0,0 +1,22 @@
import "./global.css"
import type { Metadata } from "next";
export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en">
<body className={``}>
{children}
</body>
</html>
);
}

View File

@@ -0,0 +1,13 @@
import React from "react";
export default function LoginLayout({
children
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<div>
<main>{children}</main>
</div>
)
}

View File

@@ -0,0 +1,7 @@
export default function Home() {
return (
<div className={``}>
<h1>loginpage</h1>
</div>
);
}

View File

@@ -0,0 +1,8 @@
export default function NotFound() {
return (
<div className={``}>
{/* TODO: @qidongrui 这里404页面有时间要更新*/}
<h1>Langbot没有找到该页面喔</h1>
</div>
);
}

7
web_ui/src/app/page.tsx Normal file
View File

@@ -0,0 +1,7 @@
export default function Home() {
return (
<div className={``}>
</div>
);
}