mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-06-12 08:46:02 +00:00
feat: webUI2.0 前端介面更新
1. 剩余登陆注册未完成 2. 剩余插件列表&市场未完成
This commit is contained in:
BIN
web_ui/src/app/favicon.ico
Normal file
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
37
web_ui/src/app/global.css
Normal 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; /* 自动隐藏滚动条 */
|
||||
}
|
||||
}
|
||||
3
web_ui/src/app/home/bot-config/ICreateBotField.ts
Normal file
3
web_ui/src/app/home/bot-config/ICreateBotField.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export interface ICreateBotField {
|
||||
|
||||
}
|
||||
23
web_ui/src/app/home/bot-config/botConfig.module.css
Normal file
23
web_ui/src/app/home/bot-config/botConfig.module.css
Normal 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;
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
222
web_ui/src/app/home/bot-config/components/bot-form/BotForm.tsx
Normal file
222
web_ui/src/app/home/bot-config/components/bot-form/BotForm.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export interface IChooseAdapterEntity {
|
||||
label: string
|
||||
value: string
|
||||
}
|
||||
150
web_ui/src/app/home/bot-config/page.tsx
Normal file
150
web_ui/src/app/home/bot-config/page.tsx
Normal 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,
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
}),
|
||||
]
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
107
web_ui/src/app/home/components/home-sidebar/HomeSidebar.tsx
Normal file
107
web_ui/src/app/home/components/home-sidebar/HomeSidebar.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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",
|
||||
})
|
||||
]
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
18
web_ui/src/app/home/layout.module.css
Normal file
18
web_ui/src/app/home/layout.module.css
Normal 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;
|
||||
}
|
||||
36
web_ui/src/app/home/layout.tsx
Normal file
36
web_ui/src/app/home/layout.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
8
web_ui/src/app/home/llm-config/ICreateLLMField.ts
Normal file
8
web_ui/src/app/home/llm-config/ICreateLLMField.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
export interface ICreateLLMField {
|
||||
name: string;
|
||||
model_provider: string;
|
||||
url: string;
|
||||
api_key: string;
|
||||
abilities: string[];
|
||||
extra_args: string[];
|
||||
}
|
||||
90
web_ui/src/app/home/llm-config/LLMConfig.module.css
Normal file
90
web_ui/src/app/home/llm-config/LLMConfig.module.css
Normal 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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
177
web_ui/src/app/home/llm-config/component/llm-form/LLMForm.tsx
Normal file
177
web_ui/src/app/home/llm-config/component/llm-form/LLMForm.tsx
Normal 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>
|
||||
|
||||
)
|
||||
}
|
||||
126
web_ui/src/app/home/llm-config/page.tsx
Normal file
126
web_ui/src/app/home/llm-config/page.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
1374
web_ui/src/app/home/mock-api/index.ts
Normal file
1374
web_ui/src/app/home/mock-api/index.ts
Normal file
File diff suppressed because it is too large
Load Diff
6
web_ui/src/app/home/page.tsx
Normal file
6
web_ui/src/app/home/page.tsx
Normal file
@@ -0,0 +1,6 @@
|
||||
export default function Home() {
|
||||
return (
|
||||
<div className={``}>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
30
web_ui/src/app/home/pipeline-config/page.tsx
Normal file
30
web_ui/src/app/home/pipeline-config/page.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
.formItemSubTitle {
|
||||
font-size: 30px;
|
||||
font-weight: bold;
|
||||
}
|
||||
7
web_ui/src/app/home/plugin-config/page.tsx
Normal file
7
web_ui/src/app/home/plugin-config/page.tsx
Normal file
@@ -0,0 +1,7 @@
|
||||
export default function pluginConfigPage() {
|
||||
return (
|
||||
<div className={``}>
|
||||
<h1>PluginConfigPage</h1>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
11
web_ui/src/app/infra/api/api-types/index.ts
Normal file
11
web_ui/src/app/infra/api/api-types/index.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
export interface ApiResponse<T> {
|
||||
code: number;
|
||||
data: T;
|
||||
msg: string;
|
||||
}
|
||||
|
||||
export interface I18nText {
|
||||
en_US: string;
|
||||
zh_CN: string;
|
||||
}
|
||||
|
||||
@@ -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[];
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
4
web_ui/src/app/infra/basic-types/I18N.ts
Normal file
4
web_ui/src/app/infra/basic-types/I18N.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export interface I18NText {
|
||||
en_US: string;
|
||||
zh_CN: string;
|
||||
}
|
||||
22
web_ui/src/app/layout.tsx
Normal file
22
web_ui/src/app/layout.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
13
web_ui/src/app/login/layout.tsx
Normal file
13
web_ui/src/app/login/layout.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import React from "react";
|
||||
|
||||
export default function LoginLayout({
|
||||
children
|
||||
}: Readonly<{
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
return (
|
||||
<div>
|
||||
<main>{children}</main>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
7
web_ui/src/app/login/page.tsx
Normal file
7
web_ui/src/app/login/page.tsx
Normal file
@@ -0,0 +1,7 @@
|
||||
export default function Home() {
|
||||
return (
|
||||
<div className={``}>
|
||||
<h1>loginpage</h1>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
8
web_ui/src/app/not-found.tsx
Normal file
8
web_ui/src/app/not-found.tsx
Normal 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
7
web_ui/src/app/page.tsx
Normal file
@@ -0,0 +1,7 @@
|
||||
export default function Home() {
|
||||
return (
|
||||
<div className={``}>
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user