feat: fix eslint limits to build

This commit is contained in:
HYana
2025-04-28 21:35:26 +08:00
parent 32f138bff5
commit 5c74bb41c9
15 changed files with 1886 additions and 1887 deletions

View File

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

View File

@@ -1,281 +1,292 @@
import {BotFormEntity, IBotFormEntity} from "@/app/home/bots/components/bot-form/BotFormEntity";
import {Button, Form, Input, Select, Space} from "antd";
import {useEffect, useState} from "react";
import {IChooseAdapterEntity} from "@/app/home/bots/components/bot-form/ChooseAdapterEntity";
import { import {
DynamicFormItemConfig, BotFormEntity,
IDynamicFormItemConfig, IBotFormEntity
parseDynamicFormItemType } from "@/app/home/bots/components/bot-form/BotFormEntity";
import { Button, Form, Input, notification, Select, Space } from "antd";
import { useEffect, useState } from "react";
import { IChooseAdapterEntity } from "@/app/home/bots/components/bot-form/ChooseAdapterEntity";
import {
DynamicFormItemConfig,
IDynamicFormItemConfig,
parseDynamicFormItemType
} from "@/app/home/components/dynamic-form/DynamicFormItemConfig"; } from "@/app/home/components/dynamic-form/DynamicFormItemConfig";
import {UUID} from 'uuidjs' import { UUID } from "uuidjs";
import DynamicFormComponent from "@/app/home/components/dynamic-form/DynamicFormComponent"; import DynamicFormComponent from "@/app/home/components/dynamic-form/DynamicFormComponent";
import {httpClient} from "@/app/infra/http/HttpClient"; import { httpClient } from "@/app/infra/http/HttpClient";
import { Bot } from "@/app/infra/api/api-types"; import { Bot } from "@/app/infra/api/api-types";
import { notification } from "antd";
export default function BotForm({ export default function BotForm({
initBotId, initBotId,
onFormSubmit, onFormSubmit,
onFormCancel, onFormCancel
}: { }: {
initBotId?: string; initBotId?: string;
onFormSubmit: (value: IBotFormEntity) => void; onFormSubmit: (value: IBotFormEntity) => void;
onFormCancel: (value: IBotFormEntity) => void; onFormCancel: (value: IBotFormEntity) => void;
}) { }) {
const [adapterNameToDynamicConfigMap, setAdapterNameToDynamicConfigMap] = useState(new Map<string, IDynamicFormItemConfig[]>()) const [adapterNameToDynamicConfigMap, setAdapterNameToDynamicConfigMap] =
const [form] = Form.useForm<IBotFormEntity>(); useState(new Map<string, IDynamicFormItemConfig[]>());
const [showDynamicForm, setShowDynamicForm] = useState<boolean>(false) const [form] = Form.useForm<IBotFormEntity>();
const [dynamicForm] = Form.useForm(); const [showDynamicForm, setShowDynamicForm] = useState<boolean>(false);
const [adapterNameList, setAdapterNameList] = useState<IChooseAdapterEntity[]>([]) const [dynamicForm] = Form.useForm();
const [dynamicFormConfigList, setDynamicFormConfigList] = useState<IDynamicFormItemConfig[]>([]) const [adapterNameList, setAdapterNameList] = useState<
const [isLoading, setIsLoading] = useState<boolean>(false) IChooseAdapterEntity[]
>([]);
const [dynamicFormConfigList, setDynamicFormConfigList] = useState<
IDynamicFormItemConfig[]
>([]);
const [isLoading, setIsLoading] = useState<boolean>(false);
useEffect(() => { useEffect(() => {
initBotFormComponent() initBotFormComponent();
if (initBotId) { if (initBotId) {
onEditMode() onEditMode();
} else { } else {
onCreateMode() onCreateMode();
} }
}, []) // eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
async function initBotFormComponent() { async function initBotFormComponent() {
// 拉取adapter // 拉取adapter
const rawAdapterList = await httpClient.getAdapters() const rawAdapterList = await httpClient.getAdapters();
// 初始化适配器选择列表 // 初始化适配器选择列表
setAdapterNameList( setAdapterNameList(
rawAdapterList.adapters.map(item => { rawAdapterList.adapters.map((item) => {
return { return {
label: item.label.zh_CN, label: item.label.zh_CN,
value: item.name value: item.name
} };
})
);
// 初始化适配器表单map
rawAdapterList.adapters.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)
}) })
) )
// 初始化适配器表单map );
rawAdapterList.adapters.forEach(rawAdapter => { });
adapterNameToDynamicConfigMap.set( // 拉取初始化表单信息
rawAdapter.name, if (initBotId) {
rawAdapter.spec.config.map(item => getBotFieldById(initBotId).then((val) => {
new DynamicFormItemConfig({ form.setFieldsValue(val);
default: item.default, handleAdapterSelect(val.adapter);
id: UUID.generate(), dynamicForm.setFieldsValue(val.adapter_config);
label: item.label, });
name: item.name, } else {
required: item.required, form.resetFields();
type: parseDynamicFormItemType(item.type) }
}) setAdapterNameToDynamicConfigMap(adapterNameToDynamicConfigMap);
) }
)
async function onCreateMode() {}
function onEditMode() {}
async function getBotFieldById(botId: string): Promise<IBotFormEntity> {
const bot = (await httpClient.getBot(botId)).bot;
return new BotFormEntity({
adapter: bot.adapter,
description: bot.description,
name: bot.name,
adapter_config: bot.adapter_config
});
}
function handleAdapterSelect(adapterName: string) {
console.log("Select adapter: ", adapterName);
if (adapterName) {
const dynamicFormConfigList =
adapterNameToDynamicConfigMap.get(adapterName);
console.log(dynamicFormConfigList);
if (dynamicFormConfigList) {
setDynamicFormConfigList(dynamicFormConfigList);
}
setShowDynamicForm(true);
} else {
setShowDynamicForm(false);
}
}
function handleSubmitButton() {
form.submit();
}
function handleFormFinish() {
dynamicForm.submit();
}
// 只有通过外层固定表单验证才会走到这里,真正的提交逻辑在这里
function onDynamicFormSubmit(value: object) {
setIsLoading(true);
console.log("set loading", true);
if (initBotId) {
// 编辑提交
console.log("submit edit", form.getFieldsValue(), value);
const updateBot: Bot = {
uuid: initBotId,
name: form.getFieldsValue().name,
description: form.getFieldsValue().description,
adapter: form.getFieldsValue().adapter,
adapter_config: value
};
httpClient
.updateBot(initBotId, updateBot)
.then((res) => {
// TODO success toast
console.log("update bot success", res);
onFormSubmit(form.getFieldsValue());
notification.success({
message: "更新成功",
description: "机器人更新成功"
});
}) })
// 拉取初始化表单信息 .catch(() => {
if (initBotId) { // TODO error toast
getBotFieldById(initBotId).then(val => { notification.error({
form.setFieldsValue(val) message: "更新失败",
handleAdapterSelect(val.adapter) description: "机器人更新失败"
dynamicForm.setFieldsValue(val.adapter_config) });
})
} else {
form.resetFields()
}
setAdapterNameToDynamicConfigMap(adapterNameToDynamicConfigMap)
}
async function onCreateMode() {
}
function onEditMode() {
}
async function getBotFieldById(botId: string): Promise<IBotFormEntity> {
const bot = (await httpClient.getBot(botId)).bot
let botFormEntity = new BotFormEntity({
adapter: bot.adapter,
description: bot.description,
name: bot.name,
adapter_config: bot.adapter_config
}) })
return botFormEntity .finally(() => {
setIsLoading(false);
form.resetFields();
dynamicForm.resetFields();
});
} else {
// 创建提交
console.log("submit create", form.getFieldsValue(), value);
const newBot: Bot = {
name: form.getFieldsValue().name,
description: form.getFieldsValue().description,
adapter: form.getFieldsValue().adapter,
adapter_config: value
};
httpClient
.createBot(newBot)
.then((res) => {
// TODO success toast
notification.success({
message: "创建成功",
description: "机器人创建成功"
});
console.log(res);
onFormSubmit(form.getFieldsValue());
})
.catch(() => {
// TODO error toast
notification.error({
message: "创建失败",
description: "机器人创建失败"
});
})
.finally(() => {
setIsLoading(false);
form.resetFields();
dynamicForm.resetFields();
});
} }
setShowDynamicForm(false);
console.log("set loading", false);
// TODO 刷新bot列表
// TODO 关闭当前弹窗 Already closed @setShowDynamicForm(false)?
}
function handleAdapterSelect(adapterName: string) { function handleSaveButton() {
console.log("Select adapter: ", adapterName) form.submit();
if (adapterName) { }
const dynamicFormConfigList = adapterNameToDynamicConfigMap.get(adapterName)
console.log(dynamicFormConfigList)
if (dynamicFormConfigList) {
setDynamicFormConfigList(dynamicFormConfigList)
}
setShowDynamicForm(true)
} else {
setShowDynamicForm(false)
}
}
function handleSubmitButton() { return (
form.submit() <div>
} <Form
form={form}
labelCol={{ span: 5 }}
wrapperCol={{ span: 18 }}
layout="vertical"
onFinish={handleFormFinish}
disabled={isLoading}
>
<Form.Item<IBotFormEntity>
label={"机器人名称"}
name={"name"}
rules={[{ required: true, message: "该项为必填项哦~" }]}
>
<Input
placeholder="为机器人取个好听的名字吧~"
style={{ width: 260 }}
></Input>
</Form.Item>
function handleFormFinish(value: IBotFormEntity) { <Form.Item<IBotFormEntity>
dynamicForm.submit() label={"描述"}
} name={"description"}
rules={[{ required: true, message: "该项为必填项哦~" }]}
>
<Input placeholder="简单描述一下这个机器人"></Input>
</Form.Item>
// 只有通过外层固定表单验证才会走到这里,真正的提交逻辑在这里 <Form.Item<IBotFormEntity>
function onDynamicFormSubmit(value: object) { label={"平台/适配器选择"}
setIsLoading(true) name={"adapter"}
console.log('setloading', true) rules={[{ required: true, message: "该项为必填项哦~" }]}
if (initBotId) { >
// 编辑提交 <Select
console.log('submit edit', form.getFieldsValue() ,value) style={{ width: 220 }}
let updateBot: Bot = { onChange={(value) => {
uuid: initBotId, handleAdapterSelect(value);
name: form.getFieldsValue().name, }}
description: form.getFieldsValue().description, options={adapterNameList}
adapter: form.getFieldsValue().adapter, />
adapter_config: value </Form.Item>
} </Form>
httpClient.updateBot(initBotId, updateBot).then(res => { {showDynamicForm && (
// TODO success toast <DynamicFormComponent
console.log("update bot success", res) form={dynamicForm}
onFormSubmit(form.getFieldsValue()) itemConfigList={dynamicFormConfigList}
notification.success({ onSubmit={onDynamicFormSubmit}
message: "更新成功", />
description: "机器人更新成功" )}
}) <Space>
}).catch(err => { {!initBotId && (
// TODO error toast <Button
notification.error({ type="primary"
message: "更新失败", htmlType="button"
description: "机器人更新失败" onClick={handleSubmitButton}
}) loading={isLoading}
}).finally(() => { >
setIsLoading(false)
form.resetFields() </Button>
dynamicForm.resetFields() )}
}) {initBotId && (
} else { <Button
// 创建提交 type="primary"
console.log('submit create', form.getFieldsValue() ,value) htmlType="submit"
let newBot: Bot = { onClick={handleSaveButton}
name: form.getFieldsValue().name, loading={isLoading}
description: form.getFieldsValue().description, >
adapter: form.getFieldsValue().adapter,
adapter_config: value </Button>
} )}
httpClient.createBot(newBot).then(res => { <Button
// TODO success toast htmlType="button"
notification.success({ onClick={() => {
message: "创建成功", onFormCancel(form.getFieldsValue());
description: "机器人创建成功" }}
}) disabled={isLoading}
console.log(res) >
onFormSubmit(form.getFieldsValue())
}).catch(err => { </Button>
// TODO error toast </Space>
notification.error({ </div>
message: "创建失败", );
description: "机器人创建失败" }
})
}).finally(() => {
setIsLoading(false)
form.resetFields()
dynamicForm.resetFields()
})
}
setShowDynamicForm(false)
console.log('setloading', false)
// TODO 刷新bot列表
// TODO 关闭当前弹窗 Already closed @setShowDynamicForm(false)?
}
function handleSaveButton() {
form.submit()
}
return (
<div>
<Form
form={form}
labelCol={{span: 5}}
wrapperCol={{span: 18}}
layout='vertical'
onFinish={handleFormFinish}
disabled={isLoading}
>
<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}
loading={isLoading}
>
</Button>
}
{
initBotId &&
<Button
type="primary"
htmlType="submit"
onClick={handleSaveButton}
loading={isLoading}
>
</Button>
}
<Button htmlType="button" onClick={() => {
onFormCancel(form.getFieldsValue())
}} disabled={isLoading}>
</Button>
</Space>
</div>
)
}

View File

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

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

View File

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

View File

@@ -1,296 +1,287 @@
import styles from "@/app/home/models/LLMConfig.module.css"; import styles from "@/app/home/models/LLMConfig.module.css";
import {Button, Form, Input, Select, SelectProps, Space, Modal} from "antd"; import { Button, Form, Input, Select, SelectProps, Space, Modal } from "antd";
import {ICreateLLMField} from "@/app/home/models/ICreateLLMField"; import { ICreateLLMField } from "@/app/home/models/ICreateLLMField";
import {useEffect, useState} from "react"; import { useEffect, useState } from "react";
import {IChooseRequesterEntity} from "@/app/home/models/component/llm-form/ChooseAdapterEntity"; import { IChooseRequesterEntity } from "@/app/home/models/component/llm-form/ChooseAdapterEntity";
import { httpClient } from "@/app/infra/http/HttpClient"; import { httpClient } from "@/app/infra/http/HttpClient";
import {LLMModel} from "@/app/infra/api/api-types"; import { LLMModel } from "@/app/infra/api/api-types";
import {UUID} from "uuidjs"; import { UUID } from "uuidjs";
export default function LLMForm({ export default function LLMForm({
editMode, editMode,
initLLMId, initLLMId,
onFormSubmit, onFormSubmit,
onFormCancel, onFormCancel,
onLLMDeleted, onLLMDeleted
}: { }: {
editMode: boolean; editMode: boolean;
initLLMId?: string; initLLMId?: string;
onFormSubmit: (value: ICreateLLMField) => void; onFormSubmit: (value: ICreateLLMField) => void;
onFormCancel: (value: ICreateLLMField) => void; onFormCancel: (value: ICreateLLMField) => void;
onLLMDeleted: () => void; onLLMDeleted: () => void;
}) { }) {
const [form] = Form.useForm<ICreateLLMField>(); const [form] = Form.useForm<ICreateLLMField>();
const extraOptions: SelectProps['options'] = [] const extraOptions: SelectProps["options"] = [];
const [initValue, setInitValue] = useState<ICreateLLMField>() const [initValue] = useState<ICreateLLMField>();
const [showDeleteConfirmModal, setShowDeleteConfirmModal] = useState(false) const [showDeleteConfirmModal, setShowDeleteConfirmModal] = useState(false);
const abilityOptions: SelectProps['options'] = [ const abilityOptions: SelectProps["options"] = [
{ {
label: '函数调用', label: "函数调用",
value: 'func_call', value: "func_call"
}, },
{ {
label: '图像识别', label: "图像识别",
value: 'vision', value: "vision"
},
];
const [requesterNameList, setRequesterNameList] = useState<IChooseRequesterEntity[]>([])
useEffect(() => {
initLLMModelFormComponent()
if (editMode && initLLMId) {
getLLMConfig(initLLMId).then(val => {
form.setFieldsValue(val)
})
} else {
form.resetFields()
}
}, [])
async function initLLMModelFormComponent() {
const requesterNameList = await httpClient.getProviderRequesters()
setRequesterNameList(requesterNameList.requesters.map(item => {
return {
label: item.label.zh_CN,
value: item.name
}
}))
} }
];
const [requesterNameList, setRequesterNameList] = useState<
IChooseRequesterEntity[]
>([]);
async function getLLMConfig(id: string): Promise<ICreateLLMField> { useEffect(() => {
initLLMModelFormComponent();
if (editMode && initLLMId) {
getLLMConfig(initLLMId).then((val) => {
form.setFieldsValue(val);
});
} else {
form.resetFields();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const llmModel = await httpClient.getProviderLLMModel(id) async function initLLMModelFormComponent() {
const requesterNameList = await httpClient.getProviderRequesters();
let fakeExtraArgs = [] setRequesterNameList(
const extraArgs = llmModel.model.extra_args as Record<string, string> requesterNameList.requesters.map((item) => {
for (const key in extraArgs) {
fakeExtraArgs.push(`${key}:${extraArgs[key]}`)
}
return { return {
name: llmModel.model.name, label: item.label.zh_CN,
model_provider: llmModel.model.requester, value: item.name
url: llmModel.model.requester_config?.base_url,
api_key: llmModel.model.api_keys[0],
abilities: llmModel.model.abilities,
extra_args: fakeExtraArgs,
}
}
function handleFormSubmit(value: ICreateLLMField) {
if (editMode) {
// 暂不支持更改模型
// onSaveEdit(value)
} else {
onCreateLLM(value)
}
form.resetFields()
}
function onSaveEdit(value: ICreateLLMField) {
const requestParam: LLMModel = {
uuid: UUID.generate(),
name: value.name,
description: "",
requester: value.model_provider,
requester_config: {
"base_url": value.url,
"timeout": 120
},
extra_args: value.extra_args,
api_keys: [value.api_key],
abilities: value.abilities,
// created_at: 'Sun Apr 27 2025 21:56:35 GMT+0800',
// updated_at: 'Sun Apr 27 2025 21:56:35 GMT+0800',
}; };
httpClient.createProviderLLMModel(requestParam).then(r => console.log(r)) })
);
}
async function getLLMConfig(id: string): Promise<ICreateLLMField> {
const llmModel = await httpClient.getProviderLLMModel(id);
const fakeExtraArgs = [];
const extraArgs = llmModel.model.extra_args as Record<string, string>;
for (const key in extraArgs) {
fakeExtraArgs.push(`${key}:${extraArgs[key]}`);
} }
return {
name: llmModel.model.name,
model_provider: llmModel.model.requester,
url: llmModel.model.requester_config?.base_url,
api_key: llmModel.model.api_keys[0],
abilities: llmModel.model.abilities,
extra_args: fakeExtraArgs
};
}
function onCreateLLM(value: ICreateLLMField) { function handleFormSubmit(value: ICreateLLMField) {
console.log("create llm", value) if (editMode) {
const requestParam: LLMModel = { // 暂不支持更改模型
uuid: UUID.generate(), // onSaveEdit(value)
name: value.name, } else {
description: "", onCreateLLM(value);
requester: value.model_provider,
requester_config: {
"base_url": value.url,
"timeout": 120
},
extra_args: value.extra_args,
api_keys: [value.api_key],
abilities: value.abilities,
// created_at: 'Sun Apr 27 2025 21:56:35 GMT+0800',
// updated_at: 'Sun Apr 27 2025 21:56:35 GMT+0800',
};
httpClient.createProviderLLMModel(requestParam).then(r => {
onFormSubmit(value)
})
} }
form.resetFields();
}
function handleAbilitiesChange() { // function onSaveEdit(value: ICreateLLMField) {
// const requestParam: LLMModel = {
// uuid: UUID.generate(),
// name: value.name,
// description: "",
// requester: value.model_provider,
// requester_config: {
// "base_url": value.url,
// "timeout": 120
// },
// extra_args: value.extra_args,
// api_keys: [value.api_key],
// abilities: value.abilities,
// // created_at: 'Sun Apr 27 2025 21:56:35 GMT+0800',
// // updated_at: 'Sun Apr 27 2025 21:56:35 GMT+0800',
// };
// httpClient.createProviderLLMModel(requestParam).then(r => console.log(r))
// }
function onCreateLLM(value: ICreateLLMField) {
console.log("create llm", value);
const requestParam: LLMModel = {
uuid: UUID.generate(),
name: value.name,
description: "",
requester: value.model_provider,
requester_config: {
base_url: value.url,
timeout: 120
},
extra_args: value.extra_args,
api_keys: [value.api_key],
abilities: value.abilities
// created_at: 'Sun Apr 27 2025 21:56:35 GMT+0800',
// updated_at: 'Sun Apr 27 2025 21:56:35 GMT+0800',
};
httpClient.createProviderLLMModel(requestParam).then(() => {
onFormSubmit(value);
});
}
function handleAbilitiesChange() {}
function deleteModel() {
if (initLLMId) {
httpClient.deleteProviderLLMModel(initLLMId).then(() => {
onLLMDeleted();
});
} }
}
function deleteModel() { return (
if (initLLMId) { <div className={styles.modalContainer}>
httpClient.deleteProviderLLMModel(initLLMId).then(res => { <Modal
onLLMDeleted() open={showDeleteConfirmModal}
}) title={"删除确认"}
} onCancel={() => setShowDeleteConfirmModal(false)}
} footer={
<div
return ( style={{
<div className={styles.modalContainer}> width: "170px",
<Modal display: "flex",
open={showDeleteConfirmModal} flexDirection: "row",
title={"删除确认"} justifyContent: "space-between"
onCancel={() => setShowDeleteConfirmModal(false)} }}
footer={ >
<div <Button
style={{ danger
width: "170px", onClick={() => {
display: "flex", deleteModel();
flexDirection: "row", setShowDeleteConfirmModal(false);
justifyContent: "space-between" }}
}}
>
<Button
danger
onClick={() => {
deleteModel()
setShowDeleteConfirmModal(false)
}}
></Button>
<Button
onClick={() => {
setShowDeleteConfirmModal(false)
}}
></Button>
</div>
}
> >
</Modal> </Button>
<Form <Button
form={form} onClick={() => {
labelCol={{span: 4}} setShowDeleteConfirmModal(false);
wrapperCol={{span: 14}} }}
layout='horizontal' >
initialValues={{
...initValue </Button>
</div>
}
>
</Modal>
<Form
form={form}
labelCol={{ span: 4 }}
wrapperCol={{ span: 14 }}
layout="horizontal"
initialValues={{
...initValue
}}
onFinish={handleFormSubmit}
clearOnDestroy={true}
disabled={editMode}
>
<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={requesterNameList}
/>
</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={"API Key"}
name={"api_key"}
rules={[{ required: true, message: "该项为必填项哦~" }]}
>
<Input placeholder="你的API Key" 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
color="danger"
variant="solid"
onClick={() => {
setShowDeleteConfirmModal(true);
}} }}
onFinish={handleFormSubmit} disabled={false}
clearOnDestroy={true} >
disabled={editMode}
</Button>
)}
<Button
htmlType="button"
onClick={() => {
onFormCancel(form.getFieldsValue());
}}
disabled={false}
> >
<Form.Item<ICreateLLMField>
label={"模型名称"} </Button>
name={"name"} </Space>
rules={[{required: true, message: "该项为必填项哦~"}]} </Form.Item>
> </Form>
<Input </div>
placeholder={"为自己的大模型取个好听的名字~"} );
style={{width: 260}} }
></Input>
</Form.Item>
<Form.Item<ICreateLLMField>
label={"模型供应商"}
name={"model_provider"}
rules={[{required: true, message: "该项为必填项哦~"}]}
>
<Select
style={{width: 120}}
onChange={() => {
}}
options={requesterNameList}
/>
</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={"API Key"}
name={"api_key"}
rules={[{required: true, message: "该项为必填项哦~"}]}
>
<Input
placeholder="你的API Key"
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
color="danger"
variant="solid"
onClick={() => {setShowDeleteConfirmModal(true)}}
disabled={false}
>
</Button>
}
<Button
htmlType="button"
onClick={() => {
onFormCancel(form.getFieldsValue())
}}
disabled={false}
>
</Button>
</Space>
</Form.Item>
</Form>
</div>
)
}

View File

@@ -1,20 +1,19 @@
import {DynamicFormItemConfig} from "@/app/home/components/dynamic-form/DynamicFormItemConfig"; import { DynamicFormItemConfig } from "@/app/home/components/dynamic-form/DynamicFormItemConfig";
export interface IPipelineChildFormEntity { export interface IPipelineChildFormEntity {
name: string; name: string;
label: string; label: string;
formItems: DynamicFormItemConfig[] formItems: DynamicFormItemConfig[];
} }
export class PipelineChildFormEntity implements IPipelineChildFormEntity { export class PipelineChildFormEntity implements IPipelineChildFormEntity {
formItems: DynamicFormItemConfig[]; formItems: DynamicFormItemConfig[];
label: string; label: string;
name: string; name: string;
constructor(props: IPipelineChildFormEntity) { constructor(props: IPipelineChildFormEntity) {
this.form = props.form; this.label = props.label;
this.label = props.label; this.name = props.name;
this.name = props.name; this.formItems = props.formItems;
this.formItems = props.formItems; }
} }
}

View File

@@ -1,68 +1,78 @@
"use client" "use client";
import {Modal} from "antd"; import { Modal } from "antd";
import {useState, useEffect} from "react"; import { useState, useEffect } from "react";
import CreateCardComponent from "@/app/infra/basic-component/create-card-component/CreateCardComponent"; import CreateCardComponent from "@/app/infra/basic-component/create-card-component/CreateCardComponent";
import PipelineFormComponent from "./components/pipeline-form/PipelineFormComponent"; import PipelineFormComponent from "./components/pipeline-form/PipelineFormComponent";
import {httpClient} from "@/app/infra/http/HttpClient"; import { httpClient } from "@/app/infra/http/HttpClient";
import {PipelineCardVO} from "@/app/home/pipelines/components/pipeline-card/PipelineCardVO"; import { PipelineCardVO } from "@/app/home/pipelines/components/pipeline-card/PipelineCardVO";
import PipelineCardComponent from "@/app/home/pipelines/components/pipeline-card/PipelineCardComponent"; import PipelineCardComponent from "@/app/home/pipelines/components/pipeline-card/PipelineCardComponent";
export default function PluginConfigPage() { export default function PluginConfigPage() {
const [modalOpen, setModalOpen] = useState<boolean>(false); const [modalOpen, setModalOpen] = useState<boolean>(false);
const [isEditForm, setIsEditForm] = useState(false) const [isEditForm] = useState(false);
const [pipelineList, setPipelineList] = useState<PipelineCardVO[]>([]) const [pipelineList, setPipelineList] = useState<PipelineCardVO[]>([]);
useEffect(() => { useEffect(() => {
getPipelines() getPipelines();
}, []) }, []);
function getPipelines() {
httpClient
.getPipelines()
.then((value) => {
const pipelineList = value.pipelines.map((pipeline) => {
return new PipelineCardVO({
createTime: pipeline.created_at,
description: pipeline.description,
id: pipeline.uuid,
name: pipeline.name,
version: pipeline.for_version
});
});
setPipelineList(pipelineList);
})
.catch((error) => {
// TODO toast
console.log(error);
});
}
function getPipelines() { return (
httpClient.getPipelines().then(value => { <div className={``}>
const pipelineList = value.pipelines.map(pipeline => { <Modal
return new PipelineCardVO({ title={isEditForm ? "编辑流水线" : "创建流水线"}
createTime: pipeline.created_at, centered
description: pipeline.description, open={modalOpen}
id: pipeline.uuid, onOk={() => setModalOpen(false)}
name: pipeline.name, onCancel={() => setModalOpen(false)}
version: pipeline.for_version width={700}
}) footer={null}
}) >
setPipelineList(pipelineList) <PipelineFormComponent
}).catch(error => { onFinish={() => {
// TODO toast getPipelines();
console.log(error) setModalOpen(false);
}) }}
} />
</Modal>
return ( {pipelineList.length > 0 && (
<div className={``}> <div className={``}>
<Modal {pipelineList.map((pipeline) => {
title={isEditForm ? "编辑流水线" : "创建流水线"} return (
centered <PipelineCardComponent key={pipeline.id} cardVO={pipeline} />
open={modalOpen} );
onOk={() => setModalOpen(false)} })}
onCancel={() => setModalOpen(false)}
width={700}
footer={null}
>
<PipelineFormComponent
onFinish={() => {
getPipelines()
setModalOpen(false)
}}
onCancel={() => {}}/>
</Modal>
{
pipelineList.length > 0 &&
<div className={``}>
{pipelineList.map(pipeline => {
return <PipelineCardComponent cardVO={pipeline}/>
})}
</div>
}
<CreateCardComponent width={360} height={200} plusSize={90} onClick={() => {setModalOpen(true)}}/>
</div> </div>
); )}
<CreateCardComponent
width={360}
height={200}
plusSize={90}
onClick={() => {
setModalOpen(true);
}}
/>
</div>
);
} }

View File

@@ -1,104 +1,107 @@
"use client" "use client";
import CreateCardComponent from "@/app/infra/basic-component/create-card-component/CreateCardComponent"; import CreateCardComponent from "@/app/infra/basic-component/create-card-component/CreateCardComponent";
import {PluginCardVO} from "@/app/home/plugins/plugin-installed/PluginCardVO"; import { PluginCardVO } from "@/app/home/plugins/plugin-installed/PluginCardVO";
import {useEffect, useState} from "react"; import { useEffect, useState } from "react";
import PluginCardComponent from "@/app/home/plugins/plugin-installed/plugin-card/PluginCardComponent"; import PluginCardComponent from "@/app/home/plugins/plugin-installed/plugin-card/PluginCardComponent";
import styles from "@/app/home/plugins/plugins.module.css"; import styles from "@/app/home/plugins/plugins.module.css";
import {Modal, Input} from "antd"; import { Modal, Input } from "antd";
import {GithubOutlined} from "@ant-design/icons"; import { GithubOutlined } from "@ant-design/icons";
import {httpClient} from "@/app/infra/http/HttpClient"; import { httpClient } from "@/app/infra/http/HttpClient";
export default function PluginInstalledComponent () { export default function PluginInstalledComponent() {
const [pluginList, setPluginList] = useState<PluginCardVO[]>([]) const [pluginList, setPluginList] = useState<PluginCardVO[]>([]);
const [modalOpen, setModalOpen] = useState(false) const [modalOpen, setModalOpen] = useState(false);
const [githubURL, setGithubURL] = useState("") const [githubURL, setGithubURL] = useState("");
useEffect(() => {
initData();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useEffect(() => { function initData() {
initData() getPluginList();
}, []) }
function initData() { function getPluginList() {
getPluginList() httpClient.getPlugins().then((value) => {
} setPluginList(
value.plugins.map((plugin) => {
function getPluginList() { return new PluginCardVO({
httpClient.getPlugins().then((value) => { author: plugin.author,
setPluginList(value.plugins.map(plugin => { description: plugin.description.zh_CN,
return new PluginCardVO({ handlerCount: 0,
author: plugin.author, name: plugin.name,
description: plugin.description.zh_CN, version: plugin.version,
handlerCount: 0, isInitialized: plugin.status === "initialized"
name: plugin.name, });
version: plugin.version,
isInitialized: plugin.status === "initialized",
})
}))
}) })
} );
});
}
function handleModalConfirm() { function handleModalConfirm() {
installPlugin(githubURL) installPlugin(githubURL);
setModalOpen(false) setModalOpen(false);
} }
function installPlugin(url: string) { function installPlugin(url: string) {
httpClient.installPluginFromGithub(url).then(res => { httpClient
// 安装后重新拉取 .installPluginFromGithub(url)
getPluginList() .then(() => {
}).catch(err => { // 安装后重新拉取
console.log("error when install plugin:", err) getPluginList();
}) })
} .catch((err) => {
return ( console.log("error when install plugin:", err);
<div className={`${styles.pluginListContainer}`}> });
<Modal }
title={ return (
<div className={`${styles.modalTitle}`}> <div className={`${styles.pluginListContainer}`}>
<GithubOutlined <Modal
style={{ title={
fontSize: '30px', <div className={`${styles.modalTitle}`}>
marginRight: '20px' <GithubOutlined
}} style={{
type="setting" fontSize: "30px",
/> marginRight: "20px"
<span> GitHub </span> }}
</div> type="setting"
}
centered
open={modalOpen}
onOk={() => handleModalConfirm()}
onCancel={() => setModalOpen(false)}
width={500}
destroyOnClose={true}
>
<div className={`${styles.modalBody}`}>
<div>
GitHub
</div>
<Input
placeholder="请输入插件的Github链接"
value={githubURL}
onChange={(e) => setGithubURL(e.target.value)}
/>
</div>
</Modal>
{
pluginList.map((vo, index) => {
return <div key={index}>
<PluginCardComponent cardVO={vo}/>
</div>
})
}
<CreateCardComponent
width={360}
height={140}
plusSize={90}
onClick={() => {
setModalOpen(true)
}}
/> />
<span> GitHub </span>
</div>
}
centered
open={modalOpen}
onOk={() => handleModalConfirm()}
onCancel={() => setModalOpen(false)}
width={500}
destroyOnClose={true}
>
<div className={`${styles.modalBody}`}>
<div> GitHub </div>
<Input
placeholder="请输入插件的Github链接"
value={githubURL}
onChange={(e) => setGithubURL(e.target.value)}
/>
</div> </div>
) </Modal>
{pluginList.map((vo, index) => {
return (
<div key={index}>
<PluginCardComponent cardVO={vo} />
</div>
);
})}
<CreateCardComponent
width={360}
height={140}
plusSize={90}
onClick={() => {
setModalOpen(true);
}}
/>
</div>
);
} }

View File

@@ -1,68 +1,65 @@
import styles from "./pluginCard.module.css" import styles from "./pluginCard.module.css";
import {PluginCardVO} from "@/app/home/plugins/plugin-installed/PluginCardVO"; import { PluginCardVO } from "@/app/home/plugins/plugin-installed/PluginCardVO";
import {GithubOutlined, LinkOutlined, ToolOutlined} from '@ant-design/icons'; import { GithubOutlined, LinkOutlined, ToolOutlined } from "@ant-design/icons";
import {Switch, Tag} from 'antd' import { Switch, Tag } from "antd";
import {useState} from "react"; import { useState } from "react";
import {httpClient} from "@/app/infra/http/HttpClient"; import { httpClient } from "@/app/infra/http/HttpClient";
export default function PluginCardComponent({ export default function PluginCardComponent({
cardVO cardVO
}: { }: {
cardVO: PluginCardVO cardVO: PluginCardVO;
}) { }) {
const [initialized, setInitialized] = useState(cardVO.isInitialized) const [initialized, setInitialized] = useState(cardVO.isInitialized);
const [switchEnable, setSwitchEnable] = useState(true) const [switchEnable, setSwitchEnable] = useState(true);
function handleEnable() { function handleEnable() {
setSwitchEnable(false) setSwitchEnable(false);
httpClient.togglePlugin(cardVO.author, cardVO.name, !initialized).then(result => { httpClient
setInitialized(!initialized) .togglePlugin(cardVO.author, cardVO.name, !initialized)
}).catch(err => { .then(() => {
console.log("error: ", err) setInitialized(!initialized);
}).finally(() => { })
setSwitchEnable(true) .catch((err) => {
}) console.log("error: ", err);
} })
return ( .finally(() => {
<div className={`${styles.cardContainer}`}> setSwitchEnable(true);
{/* header */} });
<div className={`${styles.cardHeader}`}> }
{/* left author */} return (
<div className={`${styles.fontGray}`}>{cardVO.author}</div> <div className={`${styles.cardContainer}`}>
{/* right icon & version */} {/* header */}
<div className={`${styles.iconVersionContainer}`}> <div className={`${styles.cardHeader}`}>
<GithubOutlined {/* left author */}
style={{fontSize: '26px'}} <div className={`${styles.fontGray}`}>{cardVO.author}</div>
type="setting" {/* right icon & version */}
/> <div className={`${styles.iconVersionContainer}`}>
<Tag color="#108ee9">v{cardVO.version}</Tag> <GithubOutlined style={{ fontSize: "26px" }} type="setting" />
</div> <Tag color="#108ee9">v{cardVO.version}</Tag>
</div>
{/* content */}
<div className={`${styles.cardContent}`}>
<div className={`${styles.boldFont}`}>{cardVO.name}</div>
<div className={`${styles.fontGray}`}>{cardVO.description}</div>
</div>
{/* footer */}
<div className={`${styles.cardFooter}`}>
<div className={`${styles.linkSettingContainer}`}>
<div className={`${styles.link}`}>
<LinkOutlined
style={{fontSize: '22px'}}
/>
<span>1</span>
</div>
<ToolOutlined
style={{fontSize: '22px'}}
/>
</div>
<Switch
value={initialized}
onClick={handleEnable}
disabled={!switchEnable}
/>
</div>
</div> </div>
); </div>
{/* content */}
<div className={`${styles.cardContent}`}>
<div className={`${styles.boldFont}`}>{cardVO.name}</div>
<div className={`${styles.fontGray}`}>{cardVO.description}</div>
</div>
{/* footer */}
<div className={`${styles.cardFooter}`}>
<div className={`${styles.linkSettingContainer}`}>
<div className={`${styles.link}`}>
<LinkOutlined style={{ fontSize: "22px" }} />
<span>1</span>
</div>
<ToolOutlined style={{ fontSize: "22px" }} />
</div>
<Switch
value={initialized}
onClick={handleEnable}
disabled={!switchEnable}
/>
</div>
</div>
);
} }

View File

@@ -1,79 +1,87 @@
"use client" "use client";
import {useCallback, useEffect, useState} from "react"; import { useEffect, useState } from "react";
import styles from "@/app/home/plugins/plugins.module.css"; import styles from "@/app/home/plugins/plugins.module.css";
import {PluginMarketCardVO} from "@/app/home/plugins/plugin-market/plugin-market-card/PluginMarketCardVO"; import { PluginMarketCardVO } from "@/app/home/plugins/plugin-market/plugin-market-card/PluginMarketCardVO";
import PluginMarketCardComponent from "@/app/home/plugins/plugin-market/plugin-market-card/PluginMarketCardComponent"; import PluginMarketCardComponent from "@/app/home/plugins/plugin-market/plugin-market-card/PluginMarketCardComponent";
import {Input, Pagination} from "antd"; import { Input, Pagination } from "antd";
import {debounce} from "lodash" import { spaceClient } from "@/app/infra/http/HttpClient";
import {httpClient, spaceClient} from "@/app/infra/http/HttpClient";
export default function PluginMarketComponent () { export default function PluginMarketComponent() {
const [marketPluginList, setMarketPluginList] = useState<PluginMarketCardVO[]>([]) const [marketPluginList, setMarketPluginList] = useState<
const [totalCount, setTotalCount] = useState(0) PluginMarketCardVO[]
const [nowPage, setNowPage] = useState(1) >([]);
const [searchKeyword, setSearchKeyword] = useState("") const [totalCount, setTotalCount] = useState(0);
const [nowPage, setNowPage] = useState(1);
const [searchKeyword, setSearchKeyword] = useState("");
useEffect(() => { useEffect(() => {
initData() initData();
}, []) // eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
function initData() { function initData() {
getPluginList() getPluginList();
} }
function onInputSearchKeyword(keyword: string) { function onInputSearchKeyword(keyword: string) {
// 这里记得加防抖,暂时没加 // 这里记得加防抖,暂时没加
setSearchKeyword(keyword) setSearchKeyword(keyword);
setNowPage(1) setNowPage(1);
getPluginList(1, keyword) getPluginList(1, keyword);
} }
function getPluginList(
page: number = nowPage,
keyword: string = searchKeyword
) {
spaceClient.getMarketPlugins(page, 10, keyword).then((res) => {
setMarketPluginList(
res.plugins.map(
(marketPlugin) =>
new PluginMarketCardVO({
author: marketPlugin.author,
description: marketPlugin.description,
githubURL: marketPlugin.repository,
name: marketPlugin.name,
pluginId: String(marketPlugin.ID),
starCount: marketPlugin.stars
})
)
);
setTotalCount(res.total);
console.log("market plugins:", res);
});
}
function getPluginList(page: number = nowPage, keyword: string = searchKeyword) { return (
spaceClient.getMarketPlugins(page, 10, keyword).then(res => { <div className={`${styles.marketComponentBody}`}>
setMarketPluginList(res.plugins.map(marketPlugin => new PluginMarketCardVO({ <Input
author: marketPlugin.author, style={{
description: marketPlugin.description, width: "300px",
githubURL: marketPlugin.repository, marginTop: "10px"
name: marketPlugin.name, }}
pluginId: String(marketPlugin.ID), value={searchKeyword}
starCount: marketPlugin.stars, placeholder="搜索插件"
}))) onChange={(e) => onInputSearchKeyword(e.target.value)}
setTotalCount(res.total) />
console.log("market plugins:", res) <div className={`${styles.pluginListContainer}`}>
}) {marketPluginList.map((vo, index) => {
} return (
<div key={index}>
return ( <PluginMarketCardComponent cardVO={vo} />
<div className={`${styles.marketComponentBody}`}>
<Input
style={{
width: '300px',
marginTop: '10px',
}}
value={searchKeyword}
placeholder="搜索插件"
onChange={(e) => onInputSearchKeyword(e.target.value)}
/>
<div className={`${styles.pluginListContainer}`}>
{
marketPluginList.map((vo, index) => {
return <div key={index}>
<PluginMarketCardComponent cardVO={vo}/>
</div>
})
}
</div> </div>
<Pagination );
defaultCurrent={1} })}
total={totalCount} </div>
onChange={(pageNumber) => { <Pagination
setNowPage(pageNumber) defaultCurrent={1}
getPluginList(pageNumber) total={totalCount}
}} onChange={(pageNumber) => {
/> setNowPage(pageNumber);
</div> getPluginList(pageNumber);
}}
) />
</div>
);
} }

View File

@@ -1,204 +1,217 @@
export interface ApiResponse<T> { export interface ApiResponse<T> {
code: number; code: number;
data: T; data: T;
msg: string; msg: string;
} }
export interface I18nText { export interface I18nText {
en_US: string; en_US: string;
zh_CN: string; zh_CN: string;
} }
export interface AsyncTaskCreatedResp { export interface AsyncTaskCreatedResp {
task_id: number; task_id: number;
} }
export interface ApiRespProviderRequesters { export interface ApiRespProviderRequesters {
requesters: Requester[]; requesters: Requester[];
} }
export interface ApiRespProviderRequester { export interface ApiRespProviderRequester {
requester: Requester; requester: Requester;
} }
export interface Requester { export interface Requester {
name: string; name: string;
label: I18nText; label: I18nText;
description: I18nText; description: I18nText;
icon?: string; icon?: string;
spec: object; spec: object;
} }
export interface ApiRespProviderLLMModels { export interface ApiRespProviderLLMModels {
models: LLMModel[]; models: LLMModel[];
} }
export interface ApiRespProviderLLMModel { export interface ApiRespProviderLLMModel {
model: LLMModel; model: LLMModel;
} }
export interface LLMModel { export interface LLMModel {
name: string; name: string;
description: string; description: string;
uuid: string; uuid: string;
requester: string; requester: string;
requester_config: object; requester_config: {
extra_args: object; base_url: string;
api_keys: string[]; timeout: number;
abilities: string[]; };
// created_at: string; extra_args: object;
// updated_at: string; api_keys: string[];
abilities: string[];
// created_at: string;
// updated_at: string;
} }
export interface ApiRespPipelines { export interface ApiRespPipelines {
pipelines: Pipeline[]; pipelines: Pipeline[];
} }
export interface ApiRespPipeline { export interface ApiRespPipeline {
pipeline: Pipeline; pipeline: Pipeline;
} }
export interface Pipeline { export interface Pipeline {
uuid: string; uuid: string;
name: string; name: string;
description: string; description: string;
for_version: string; for_version: string;
config: object; config: object;
stages: string[]; stages: string[];
created_at: string; created_at: string;
updated_at: string; updated_at: string;
} }
export interface ApiRespPlatformAdapters { export interface ApiRespPlatformAdapters {
adapters: Adapter[]; adapters: Adapter[];
} }
export interface ApiRespPlatformAdapter { export interface ApiRespPlatformAdapter {
adapter: Adapter; adapter: Adapter;
} }
export interface Adapter { export interface Adapter {
name: string; name: string;
label: I18nText; label: I18nText;
description: I18nText; description: I18nText;
icon?: string; icon?: string;
spec: object; spec: {
config: AdapterSpecConfig[];
};
}
export interface AdapterSpecConfig {
default: string | number | boolean | Array<unknown>;
label: I18nText;
name: string;
required: boolean;
type: string;
} }
export interface ApiRespPlatformBots { export interface ApiRespPlatformBots {
bots: Bot[]; bots: Bot[];
} }
export interface ApiRespPlatformBot { export interface ApiRespPlatformBot {
bot: Bot; bot: Bot;
} }
export interface Bot { export interface Bot {
uuid?: string; uuid?: string;
name: string; name: string;
description: string; description: string;
enable?: boolean; enable?: boolean;
adapter: string; adapter: string;
adapter_config: object; adapter_config: object;
use_pipeline_name?: string; use_pipeline_name?: string;
use_pipeline_uuid?: string; use_pipeline_uuid?: string;
created_at?: string; created_at?: string;
updated_at?: string; updated_at?: string;
} }
// plugins // plugins
export interface ApiRespPlugins { export interface ApiRespPlugins {
plugins: Plugin[]; plugins: Plugin[];
} }
export interface ApiRespPlugin { export interface ApiRespPlugin {
plugin: Plugin; plugin: Plugin;
} }
export interface Plugin { export interface Plugin {
author: string; author: string;
name: string; name: string;
description: I18nText; description: I18nText;
label: I18nText; label: I18nText;
version: string; version: string;
enabled: boolean; enabled: boolean;
priority: number; priority: number;
status: string; status: string;
tools: object[]; tools: object[];
event_handlers: object; event_handlers: object;
main_file: string; main_file: string;
pkg_path: string; pkg_path: string;
repository: string; repository: string;
config_schema: object; config_schema: object;
} }
export interface ApiRespPluginConfig { export interface ApiRespPluginConfig {
config: object; config: object;
} }
export interface PluginReorderElement { export interface PluginReorderElement {
author: string; author: string;
name: string; name: string;
priority: number; priority: number;
} }
// system // system
export interface ApiRespSystemInfo { export interface ApiRespSystemInfo {
debug: boolean; debug: boolean;
version: string; version: string;
} }
export interface ApiRespAsyncTasks { export interface ApiRespAsyncTasks {
tasks: AsyncTask[]; tasks: AsyncTask[];
} }
export interface ApiRespAsyncTask { export interface ApiRespAsyncTask {
task: AsyncTask; task: AsyncTask;
} }
export interface AsyncTaskRuntimeInfo { export interface AsyncTaskRuntimeInfo {
done: boolean; done: boolean;
exception?: string; exception?: string;
result?: object; result?: object;
state: string; state: string;
} }
export interface AsyncTaskTaskContext { export interface AsyncTaskTaskContext {
current_action: string; current_action: string;
log: string; log: string;
} }
export interface AsyncTask { export interface AsyncTask {
id: number; id: number;
kind: string; kind: string;
name: string; name: string;
task_type: string; // system or user task_type: string; // system or user
runtime: AsyncTaskRuntimeInfo; runtime: AsyncTaskRuntimeInfo;
task_context: AsyncTaskTaskContext; task_context: AsyncTaskTaskContext;
} }
export interface ApiRespUserToken { export interface ApiRespUserToken {
token: string; token: string;
} }
export interface MarketPlugin { export interface MarketPlugin {
ID: number ID: number;
CreatedAt: string // ISO 8601 格式日期 CreatedAt: string; // ISO 8601 格式日期
UpdatedAt: string UpdatedAt: string;
DeletedAt: string | null DeletedAt: string | null;
name: string name: string;
author: string author: string;
description: string description: string;
repository: string // GitHub 仓库路径 repository: string; // GitHub 仓库路径
artifacts_path: string artifacts_path: string;
stars: number stars: number;
downloads: number downloads: number;
status: "initialized" | "mounted" // 可根据实际状态值扩展联合类型 status: "initialized" | "mounted"; // 可根据实际状态值扩展联合类型
synced_at: string synced_at: string;
pushed_at: string // 最后一次代码推送时间 pushed_at: string; // 最后一次代码推送时间
} }
export interface MarketPluginResponse { export interface MarketPluginResponse {
plugins: MarketPlugin[] plugins: MarketPlugin[];
total: number total: number;
} }

View File

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

View File

@@ -1,194 +1,204 @@
'use client'; "use client";
import { Button, Input, Form, Checkbox, Divider } from 'antd'; import { Button, Input, Form, Checkbox, Divider } from "antd";
import {GoogleOutlined, AppleOutlined, LockOutlined, UserOutlined, QqCircleFilled, QqOutlined} from '@ant-design/icons'; import {
import styles from './login.module.css'; GoogleOutlined,
import {useEffect, useState} from 'react'; LockOutlined,
UserOutlined,
QqOutlined
import {httpClient} from "@/app/infra/http/HttpClient"; } from "@ant-design/icons";
import '@ant-design/v5-patch-for-react-19'; import styles from "./login.module.css";
import {useRouter} from "next/navigation"; import { useEffect, useState } from "react";
import { httpClient } from "@/app/infra/http/HttpClient";
import "@ant-design/v5-patch-for-react-19";
import { useRouter } from "next/navigation";
export default function Home() { export default function Home() {
const router = useRouter(); const router = useRouter();
const [form] = Form.useForm<LoginField>(); const [form] = Form.useForm<LoginField>();
const [rememberMe, setRememberMe] = useState(false); const [rememberMe, setRememberMe] = useState(false);
const [isRegisterMode, setIsRegisterMode] = useState(false); const [isRegisterMode, setIsRegisterMode] = useState(false);
const [isInitialized, setIsInitialized] = useState(false) const [isInitialized, setIsInitialized] = useState(false);
useEffect(() => { useEffect(() => {
getIsInitialized() getIsInitialized();
}, []) }, []);
// 检查是否为首次启动项目,只为首次启动的用户提供注册资格
function getIsInitialized() {
httpClient
.checkIfInited()
.then((res) => {
setIsInitialized(res.initialized);
})
.catch((err) => {
console.log("error at getIsInitialized: ", err);
});
}
// 检查是否为首次启动项目,只为首次启动的用户提供注册资格 function handleFormSubmit(formField: LoginField) {
function getIsInitialized() { if (isRegisterMode) {
httpClient.checkIfInited().then(res => { handleRegister(formField.email, formField.password);
setIsInitialized(res.initialized) } else {
}).catch(err => { handleLogin(formField.email, formField.password);
console.log("error at getIsInitialized: ", err)
})
} }
}
function handleFormSubmit(formField: LoginField) { function handleRegister(username: string, password: string) {
if (isRegisterMode) { httpClient
handleRegister(formField.email, formField.password); .initUser(username, password)
} else { .then((res) => {
handleLogin(formField.email, formField.password) console.log("init user success: ", res);
} })
} .catch((err) => {
console.log("init user error: ", err);
});
}
function handleRegister(username: string, password: string) { function handleLogin(username: string, password: string) {
httpClient.initUser(username, password).then(res => { httpClient
console.log("init user success: ", res) .authUser(username, password)
}).catch(err => { .then((res) => {
console.log("init user error: ", err) localStorage.setItem("token", res.token);
}) console.log("login success: ", res);
} router.push("/home");
})
.catch((err) => {
console.log("login error: ", err);
});
}
function handleLogin(username: string, password: string) { return (
httpClient.authUser(username, password).then(res => { // 使用 Ant Design 的组件库,使用 antd 的样式
localStorage.setItem("token", res.token) // 仅前端样式,无交互功能。
console.log("login success: ", res)
router.push("/home")
}).catch(err => {
console.log("login error: ", err)
})
}
<div className={styles.container}>
{/* login 类是整个 container使用 flex 左右布局 */}
<div className={styles.login}>
{/* left 为注册的表单,需要填入的内容有:邮箱,密码 */}
<div className={styles.left}>
<div className={styles.loginForm}>
{isRegisterMode && (
<h1 className={styles.title}> LangBot </h1>
)}
{!isRegisterMode && (
<h1 className={styles.title}> LangBot</h1>
)}
<Form
form={form}
layout="vertical"
onFinish={(values) => {
handleFormSubmit(values);
}}
>
<Form.Item
name="email"
rules={[
{ required: true, message: "请输入邮箱!" },
{ type: "email", message: "请输入有效的邮箱地址!" }
]}
>
<Input
placeholder="输入邮箱地址"
size="large"
prefix={<UserOutlined />}
/>
</Form.Item>
return ( <Form.Item
// 使用 Ant Design 的组件库,使用 antd 的样式 name="password"
// 仅前端样式,无交互功能。 rules={[{ required: true, message: "请输入密码!" }]}
>
<Input.Password
placeholder="输入密码"
size="large"
prefix={<LockOutlined />}
/>
</Form.Item>
<div className={styles.container}> <div className={styles.rememberMe}>
{/* login 类是整个 container使用 flex 左右布局 */} <Checkbox
<div className={styles.login}> checked={rememberMe}
{/* left 为注册的表单,需要填入的内容有:邮箱,密码 */} onChange={(e) => setRememberMe(e.target.checked)}
<div className={styles.left}> >
<div className={styles.loginForm}> 30
{ </Checkbox>
isRegisterMode && <span>
<h1 className={styles.title}> LangBot </h1> <a href="#" className={`${styles.forgetPassword}`}>
} ?
{ </a>
!isRegisterMode && {!isRegisterMode && (
<h1 className={styles.title}> LangBot</h1> <a
} href=""
<Form onClick={(event) => {
form={form} setIsRegisterMode(true);
layout="vertical" event.preventDefault();
onFinish={(values) => { }}
handleFormSubmit(values) >
}}
> </a>
<Form.Item )}
name="email" {isRegisterMode && (
rules={[ <a
{ required: true, message: '请输入邮箱!' }, href=""
{ type: 'email', message: '请输入有效的邮箱地址!' } onClick={(event) => {
]} setIsRegisterMode(false);
> event.preventDefault();
<Input }}
placeholder="输入邮箱地址" >
size="large"
prefix={<UserOutlined />} </a>
/> )}
</Form.Item> </span>
</div>
<Form.Item <Button
name="password" type="primary"
rules={[ size="large"
{ required: true, message: '请输入密码!' } className={styles.loginButton}
]} block
> htmlType="submit"
<Input.Password disabled={isRegisterMode && isInitialized}
placeholder="输入密码" >
size="large" {isRegisterMode
prefix={<LockOutlined />} ? isInitialized
/> ? "暂不提供注册"
</Form.Item> : "注册"
: "登录"}
</Button>
<div className={styles.rememberMe}> <Divider className={styles.divider}></Divider>
<Checkbox
checked={rememberMe}
onChange={(e) => setRememberMe(e.target.checked)}
>
30
</Checkbox>
<span>
<a href="#" className={`${styles.forgetPassword}`}>?</a>
{
!isRegisterMode &&
<a href=""
onClick={(event) => {
setIsRegisterMode(true)
event.preventDefault()
}}
></a>
}
{
isRegisterMode &&
<a href=""
onClick={(event) => {
setIsRegisterMode(false)
event.preventDefault()
}}
></a>
}
</span>
</div>
<div className={styles.socialLogin}>
<Button <Button
type="primary" className={styles.socialButton}
size="large" icon={<GoogleOutlined />}
className={styles.loginButton} size="large"
block disabled={true}
htmlType="submit" >
disabled={isRegisterMode && isInitialized} 使
> </Button>
{ </div>
isRegisterMode ? ( <div style={{ height: "10px" }}></div>
isInitialized ? "暂不提供注册" : "注册" <div className={styles.socialLogin}>
) : "登录" <Button
} className={styles.socialButton}
</Button> icon={<QqOutlined />}
size="large"
disabled={true}
<Divider className={styles.divider}></Divider> >
使QQ账号登录
<div className={styles.socialLogin}> </Button>
<Button </div>
className={styles.socialButton} </Form>
icon={<GoogleOutlined />} </div>
size="large"
disabled={true}
>
使
</Button>
</div>
<div style={{ height: '10px' }}></div>
<div className={styles.socialLogin}>
<Button
className={styles.socialButton}
icon={<QqOutlined />}
size="large"
disabled={true}
>
使QQ账号登录
</Button>
</div>
</Form>
</div>
</div>
</div>
</div> </div>
); </div>
</div>
);
} }
interface LoginField { interface LoginField {
email: string; email: string;
password: string; password: string;
} }