mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-06-11 08:16:03 +00:00
chore: rename web_ui dir to web
This commit is contained in:
8
web/src/app/home/models/ICreateLLMField.ts
Normal file
8
web/src/app/home/models/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/src/app/home/models/LLMConfig.module.css
Normal file
90
web/src/app/home/models/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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
33
web/src/app/home/models/component/llm-card/LLMCard.tsx
Normal file
33
web/src/app/home/models/component/llm-card/LLMCard.tsx
Normal file
@@ -0,0 +1,33 @@
|
||||
import styles from "../../LLMConfig.module.css"
|
||||
import {LLMCardVO} from "@/app/home/models/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.company}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* URL和创建时间 */}
|
||||
<div className={`${styles.urlAndUpdateText}`}>
|
||||
URL:{cardVO.URL}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
21
web/src/app/home/models/component/llm-card/LLMCardVO.ts
Normal file
21
web/src/app/home/models/component/llm-card/LLMCardVO.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
export interface ILLMCardVO {
|
||||
id: string;
|
||||
name: string;
|
||||
company: string;
|
||||
URL: string;
|
||||
}
|
||||
|
||||
export class LLMCardVO implements ILLMCardVO {
|
||||
id: string;
|
||||
name: string;
|
||||
company: string;
|
||||
URL: string;
|
||||
|
||||
constructor(props: ILLMCardVO) {
|
||||
this.id = props.id;
|
||||
this.name = props.name;
|
||||
this.company = props.company;
|
||||
this.URL = props.URL;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export interface IChooseRequesterEntity {
|
||||
label: string
|
||||
value: string
|
||||
}
|
||||
287
web/src/app/home/models/component/llm-form/LLMForm.tsx
Normal file
287
web/src/app/home/models/component/llm-form/LLMForm.tsx
Normal file
@@ -0,0 +1,287 @@
|
||||
import styles from "@/app/home/models/LLMConfig.module.css";
|
||||
import { Button, Form, Input, Select, SelectProps, Space, Modal } from "antd";
|
||||
import { ICreateLLMField } from "@/app/home/models/ICreateLLMField";
|
||||
import { useEffect, useState } from "react";
|
||||
import { IChooseRequesterEntity } from "@/app/home/models/component/llm-form/ChooseAdapterEntity";
|
||||
import { httpClient } from "@/app/infra/http/HttpClient";
|
||||
import { LLMModel } from "@/app/infra/api/api-types";
|
||||
import { UUID } from "uuidjs";
|
||||
|
||||
export default function LLMForm({
|
||||
editMode,
|
||||
initLLMId,
|
||||
onFormSubmit,
|
||||
onFormCancel,
|
||||
onLLMDeleted
|
||||
}: {
|
||||
editMode: boolean;
|
||||
initLLMId?: string;
|
||||
onFormSubmit: (value: ICreateLLMField) => void;
|
||||
onFormCancel: (value: ICreateLLMField) => void;
|
||||
onLLMDeleted: () => void;
|
||||
}) {
|
||||
const [form] = Form.useForm<ICreateLLMField>();
|
||||
const extraOptions: SelectProps["options"] = [];
|
||||
const [initValue] = useState<ICreateLLMField>();
|
||||
const [showDeleteConfirmModal, setShowDeleteConfirmModal] = useState(false);
|
||||
const abilityOptions: SelectProps["options"] = [
|
||||
{
|
||||
label: "函数调用",
|
||||
value: "func_call"
|
||||
},
|
||||
{
|
||||
label: "图像识别",
|
||||
value: "vision"
|
||||
}
|
||||
];
|
||||
const [requesterNameList, setRequesterNameList] = useState<
|
||||
IChooseRequesterEntity[]
|
||||
>([]);
|
||||
|
||||
useEffect(() => {
|
||||
initLLMModelFormComponent();
|
||||
if (editMode && initLLMId) {
|
||||
getLLMConfig(initLLMId).then((val) => {
|
||||
form.setFieldsValue(val);
|
||||
});
|
||||
} else {
|
||||
form.resetFields();
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
async function initLLMModelFormComponent() {
|
||||
const requesterNameList = await httpClient.getProviderRequesters();
|
||||
setRequesterNameList(
|
||||
requesterNameList.requesters.map((item) => {
|
||||
return {
|
||||
label: item.label.zh_CN,
|
||||
value: item.name
|
||||
};
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
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 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))
|
||||
// }
|
||||
|
||||
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();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.modalContainer}>
|
||||
<Modal
|
||||
open={showDeleteConfirmModal}
|
||||
title={"删除确认"}
|
||||
onCancel={() => setShowDeleteConfirmModal(false)}
|
||||
footer={
|
||||
<div
|
||||
style={{
|
||||
width: "170px",
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between"
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
danger
|
||||
onClick={() => {
|
||||
deleteModel();
|
||||
setShowDeleteConfirmModal(false);
|
||||
}}
|
||||
>
|
||||
确认删除
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setShowDeleteConfirmModal(false);
|
||||
}}
|
||||
>
|
||||
取消
|
||||
</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);
|
||||
}}
|
||||
disabled={false}
|
||||
>
|
||||
删除
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
htmlType="button"
|
||||
onClick={() => {
|
||||
onFormCancel(form.getFieldsValue());
|
||||
}}
|
||||
disabled={false}
|
||||
>
|
||||
取消
|
||||
</Button>
|
||||
</Space>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
116
web/src/app/home/models/page.tsx
Normal file
116
web/src/app/home/models/page.tsx
Normal file
@@ -0,0 +1,116 @@
|
||||
"use client"
|
||||
|
||||
import {useState, useEffect} from "react";
|
||||
import {LLMCardVO} from "@/app/home/models/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/models/component/llm-card/LLMCard";
|
||||
import LLMForm from "@/app/home/models/component/llm-form/LLMForm";
|
||||
import CreateCardComponent from "@/app/infra/basic-component/create-card-component/CreateCardComponent";
|
||||
import { httpClient } from "@/app/infra/http/HttpClient";
|
||||
import { LLMModel } from "@/app/infra/api/api-types";
|
||||
|
||||
export default function LLMConfigPage() {
|
||||
const [cardList, setCardList] = useState<LLMCardVO[]>([])
|
||||
const [modalOpen, setModalOpen] = useState<boolean>(false);
|
||||
const [isEditForm, setIsEditForm] = useState(false)
|
||||
const [nowSelectedLLM, setNowSelectedLLM] = useState<LLMCardVO | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
getLLMModelList()
|
||||
}, [])
|
||||
|
||||
function getLLMModelList() {
|
||||
httpClient.getProviderLLMModels().then((resp) => {
|
||||
const llmModelList: LLMCardVO[] = resp.models.map((model: LLMModel) => {
|
||||
console.log("model", model)
|
||||
return new LLMCardVO({
|
||||
id: model.uuid,
|
||||
name: model.name,
|
||||
company: model.requester,
|
||||
URL: model.requester_config?.base_url,
|
||||
})
|
||||
})
|
||||
console.log("get llmModelList", llmModelList)
|
||||
setCardList(llmModelList)
|
||||
}).catch((err) => {
|
||||
// TODO error toast
|
||||
console.error("get LLM model list error", err)
|
||||
})
|
||||
}
|
||||
|
||||
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}
|
||||
destroyOnClose={true}
|
||||
onOk={() => setModalOpen(false)}
|
||||
onCancel={() => setModalOpen(false)}
|
||||
width={700}
|
||||
footer={null}
|
||||
>
|
||||
<LLMForm
|
||||
editMode={isEditForm}
|
||||
initLLMId={nowSelectedLLM?.id}
|
||||
onFormSubmit={() => {
|
||||
setModalOpen(false);
|
||||
getLLMModelList()
|
||||
}}
|
||||
onFormCancel={() => {
|
||||
setModalOpen(false);
|
||||
}}
|
||||
onLLMDeleted={() => {
|
||||
setModalOpen(false)
|
||||
getLLMModelList()
|
||||
}}
|
||||
/>
|
||||
</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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user