chore: rename web_ui dir to web

This commit is contained in:
Junyan Qin
2025-04-28 21:41:03 +08:00
parent 5c74bb41c9
commit 2eaac168dc
81 changed files with 0 additions and 1 deletions
@@ -0,0 +1,33 @@
import styles from "./pipelineCard.module.css";
import {PipelineCardVO} from "@/app/home/pipelines/components/pipeline-card/PipelineCardVO";
export default function PipelineCardComponent({
cardVO
}: {
cardVO: PipelineCardVO
}) {
return (
<div className={`${styles.cardContainer}`}>
{/* icon和基本信息 */}
<div className={`${styles.iconBasicInfoContainer}`}>
{/* icon */}
<div className={`${styles.icon}`}>
ICO
</div>
{/* 基本信息 */}
<div className={`${styles.basicInfoContainer}`}>
<div className={`${styles.basicInfoText} ${styles.bigText}`}>
{cardVO.name}
</div>
<div className={`${styles.basicInfoText}`}>
{cardVO.description}
</div>
</div>
</div>
{/* URL和创建时间 */}
<div className={`${styles.urlAndUpdateText}`}>
{cardVO.version}
</div>
</div>
);
}
@@ -0,0 +1,23 @@
export interface IPipelineCardVO {
id: string;
name: string;
description: string;
createTime: string;
version: string;
}
export class PipelineCardVO implements IPipelineCardVO {
createTime: string;
description: string;
id: string;
name: string;
version: string;
constructor(props: IPipelineCardVO) {
this.id = props.id;
this.name = props.name;
this.description = props.description;
this.createTime = props.createTime;
this.version = props.version;
}
}
@@ -0,0 +1,59 @@
.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;
}
.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;
}
@@ -0,0 +1,19 @@
import { DynamicFormItemConfig } from "@/app/home/components/dynamic-form/DynamicFormItemConfig";
export interface IPipelineChildFormEntity {
name: string;
label: string;
formItems: DynamicFormItemConfig[];
}
export class PipelineChildFormEntity implements IPipelineChildFormEntity {
formItems: DynamicFormItemConfig[];
label: string;
name: string;
constructor(props: IPipelineChildFormEntity) {
this.label = props.label;
this.name = props.name;
this.formItems = props.formItems;
}
}
@@ -0,0 +1,604 @@
import {
Form,
Button,
Switch,
Select,
Input,
InputNumber,
SelectProps
} from "antd";
import { CaretLeftOutlined, CaretRightOutlined } from "@ant-design/icons";
import { useEffect, useState } from "react";
import styles from "./pipelineFormStyle.module.css";
import { httpClient } from "@/app/infra/http/HttpClient";
import { LLMModel, Pipeline } from "@/app/infra/api/api-types";
import { UUID } from "uuidjs";
export default function PipelineFormComponent({
onFinish
}: {
onFinish: () => void;
}) {
const [nowFormIndex, setNowFormIndex] = useState<number>(0);
const [nowAIRunner, setNowAIRunner] = useState("");
const [llmModelList, setLlmModelList] = useState<SelectProps["options"]>([]);
// 这里不好,可以改成enum等
const formLabelList: FormLabel[] = [
{ label: "基础", name: "basic" },
{ label: "AI能力", name: "ai" },
{ label: "触发条件", name: "trigger" },
{ label: "安全能力", name: "safety" },
{ label: "输出处理", name: "output" }
];
const [basicForm] = Form.useForm();
const [aiForm] = Form.useForm();
const [triggerForm] = Form.useForm();
const [safetyForm] = Form.useForm();
const [outputForm] = Form.useForm();
useEffect(() => {
getLLMModelList();
}, []);
function getLLMModelList() {
httpClient
.getProviderLLMModels()
.then((resp) => {
setLlmModelList(
resp.models.map((model: LLMModel) => {
return {
value: model.uuid,
label: model.name
};
})
);
})
.catch((err) => {
console.error("get LLM model list error", err);
});
}
function getNowFormLabel() {
return formLabelList[nowFormIndex];
}
function getPreFormLabel(): undefined | FormLabel {
if (nowFormIndex !== undefined && nowFormIndex > 0) {
return formLabelList[nowFormIndex - 1];
} else {
return undefined;
}
}
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);
}
}
function handleCommit() {
Promise.all([
basicForm.validateFields(),
aiForm.validateFields(),
triggerForm.validateFields(),
safetyForm.validateFields(),
outputForm.validateFields()
])
.then(() => {
const pipeline = assembleForm();
httpClient.createPipeline(pipeline).then(() => onFinish());
})
.catch((e) => {
console.error(e);
});
}
// TODO 类型混乱,需要优化
function assembleForm(): Pipeline {
console.log("basicForm:", basicForm.getFieldsValue());
console.log("aiForm:", aiForm.getFieldsValue());
console.log("triggerForm:", triggerForm.getFieldsValue());
console.log("safetyForm:", safetyForm.getFieldsValue());
console.log("outputForm:", outputForm.getFieldsValue());
const config: object = {
ai: aiForm.getFieldsValue(),
trigger: triggerForm.getFieldsValue(),
safety: safetyForm.getFieldsValue(),
output: outputForm.getFieldsValue()
};
return {
config,
created_at: "",
description: basicForm.getFieldsValue().description,
for_version: "",
name: basicForm.getFieldsValue().name,
stages: [],
updated_at: "",
uuid: UUID.generate()
};
}
return (
<div style={{ maxHeight: "70vh", overflowY: "auto" }}>
<h1>{getNowFormLabel().label}</h1>
<Form
layout={"vertical"}
style={{
display: getNowFormLabel().name === "basic" ? "block" : "none"
}}
form={basicForm}
>
<Form.Item
label="流水线名称"
name={"name"}
rules={[
{
required: true
}
]}
>
<Input />
</Form.Item>
<Form.Item
label="流水线描述"
name={"description"}
rules={[
{
required: true
}
]}
>
<Input />
</Form.Item>
</Form>
{/* AI能力表单 ai */}
<Form
layout={"vertical"}
style={{ display: getNowFormLabel().name === "ai" ? "block" : "none" }}
form={aiForm}
>
{/* Runner 配置区块 */}
<div className={`${styles.formItemSubtitle}`}></div>
<Form.Item
label="运行器"
name={["runner", "runner"]}
rules={[{ required: true }]}
>
<Select
options={[
{ label: "内置 Agent", value: "local-agent" },
{ label: "Dify 服务 API", value: "dify-service-api" },
{ label: "阿里云百炼平台 API", value: "dashscope-app-api" }
]}
onChange={(value) => setNowAIRunner(value)}
/>
</Form.Item>
{/* 内置 Agent 配置区块 */}
{nowAIRunner === "local-agent" && (
<>
<div className={`${styles.formItemSubtitle}`}>Agent</div>
<Form.Item
label="模型"
name={["local-agent", "model"]}
rules={[{ required: true }]}
tooltip="从模型库中选择"
>
<Select
options={llmModelList}
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 区块 */}
{nowAIRunner === "dify-service-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>
</>
)}
{/* 阿里云百炼区块 */}
{nowAIRunner === "dashscope-app-api" && (
<>
<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={triggerForm}
>
{/* 群响应规则块 */}
<div className={`${styles.formItemSubtitle}`}> </div>
<Form.Item
label={"是否在消息@机器人时触发"}
name={["group-respond-rules", "at"]}
rules={[{ required: true }]}
>
<Switch />
</Form.Item>
<Form.Item
label={"消息前缀"}
name={["group-respond-rules", "prefix"]}
rules={[{ required: true }]}
>
<Select
options={[{ value: '"type": "string"', label: '"type": "string"' }]}
/>
</Form.Item>
<Form.Item
label={"正则表达式"}
name={["group-respond-rules", "regexp"]}
rules={[{ required: true }]}
>
<Select mode="tags" options={[]} />
</Form.Item>
<Form.Item
label={"随机"}
name={["group-respond-rules", "random"]}
rules={[{ required: false }]}
>
<InputNumber max={1} min={0} step={0.05} />
</Form.Item>
<div className={`${styles.formItemSubtitle}`}> 访 </div>
<Form.Item
label={"模式"}
name={["access-control", "mode"]}
rules={[{ required: true }]}
tooltip={"访问控制模式"}
>
<Select
options={[
{ label: "黑名单", value: "blacklist" },
{ label: "白名单", value: "Whitelist" }
]}
/>
</Form.Item>
<Form.Item
label={"黑名单"}
name={["access-control", "blacklist"]}
rules={[{ required: true }]}
>
<Select mode={"tags"} options={[]} />
</Form.Item>
<Form.Item
label={"白名单"}
name={["access-control", "whitelist"]}
rules={[{ required: true }]}
>
<Select mode={"tags"} options={[]} />
</Form.Item>
<div className={`${styles.formItemSubtitle}`}> </div>
<Form.Item
label={"前缀"}
name={["ignore-rules", "whitelist"]}
rules={[{ required: true }]}
tooltip={"消息前缀"}
>
<Select mode={"tags"} options={[]} />
</Form.Item>
<Form.Item
label={"正则表达式"}
name={["ignore-rules", "regexp"]}
rules={[{ required: true }]}
tooltip={"消息正则表达式"}
>
<Select mode={"tags"} options={[]} />
</Form.Item>
</Form>
{/* 安全控制表单 safety */}
<Form
layout={"vertical"}
style={{
display: getNowFormLabel().name === "safety" ? "block" : "none"
}}
form={safetyForm}
>
{/* 内容过滤块 content-filter */}
<div className={`${styles.formItemSubtitle}`}> </div>
<Form.Item
label={"检查范围"}
name={["content-filter", "scope"]}
rules={[{ required: true }]}
>
<Select
options={[
{ label: "全部", value: "all" },
{ label: "传入消息(用户消息)", value: "income-msg" },
{ label: "传出消息(机器人消息)", value: "output-msg" }
]}
/>
</Form.Item>
<Form.Item
label={"检查敏感词"}
name={["content-filter", "check-sensitive-words"]}
rules={[{ required: true }]}
>
<Switch />
</Form.Item>
{/* 速率限制块 rate-limit */}
<div className={`${styles.formItemSubtitle}`}> </div>
<Form.Item
label={"窗口长度(秒)"}
name={["rate-limit", "window-length"]}
rules={[{ required: true }]}
initialValue={60}
>
<InputNumber></InputNumber>
</Form.Item>
<Form.Item
label={"限制次数"}
name={["rate-limit", "limitation"]}
rules={[{ required: true }]}
initialValue={60}
>
<InputNumber />
</Form.Item>
<Form.Item
label={"策略"}
name={["rate-limit", "strategy"]}
rules={[{ required: true }]}
initialValue={"drop"}
>
<Select
options={[
{ label: "丢弃", value: "drop" },
{ label: "等待", value: "wait" }
]}
/>
</Form.Item>
</Form>
{/* 输出处理控制表单 output */}
<Form
layout={"vertical"}
style={{
display: getNowFormLabel().name === "output" ? "block" : "none"
}}
form={outputForm}
>
{/* 长文本处理区块 */}
<div className={`${styles.formItemSubtitle}`}> </div>
<Form.Item
label="阈值"
name={["long-text-processing", "threshold"]}
rules={[{ required: true }]}
>
<InputNumber />
</Form.Item>
<Form.Item
label="策略"
name={["long-text-processing", "strategy"]}
rules={[{ required: true }]}
>
<Select
options={[
{ label: "转发消息组件", value: "forward" },
{ label: "转换为图片", value: "image" }
]}
/>
</Form.Item>
<Form.Item
label="字体路径"
name={["long-text-processing", "font-path"]}
rules={[{ required: true }]}
>
<Input />
</Form.Item>
{/* 强制延迟区块 */}
<div className={`${styles.formItemSubtitle}`}> </div>
<Form.Item
label="最小秒数"
name={["force-delay", "min"]}
rules={[{ required: true }]}
>
<InputNumber />
</Form.Item>
<Form.Item
label="最大秒数"
name={["force-delay", "max"]}
rules={[{ required: true }]}
>
<InputNumber />
</Form.Item>
{/* 杂项区块 */}
<div className={`${styles.formItemSubtitle}`}> </div>
<Form.Item
label="不输出异常信息给用户"
name={["misc", "hide-exception"]}
rules={[{ required: true }]}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
label="在回复中@发送者"
name={["misc", "at-sender"]}
rules={[{ required: true }]}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
label="引用原文"
name={["misc", "quote-origin"]}
rules={[{ required: true }]}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
label="跟踪函数调用"
name={["misc", "track-function-calls"]}
rules={[{ required: true }]}
valuePropName="checked"
>
<Switch />
</Form.Item>
</Form>
<div className={`${styles.changeFormButtonGroupContainer}`}>
<Button
type="primary"
icon={<CaretLeftOutlined />}
onClick={reduceFormLabelIndex}
disabled={!getPreFormLabel()}
>
{getPreFormLabel()?.label || "暂无更多"}
</Button>
<Button
type="primary"
icon={<CaretRightOutlined />}
onClick={addFormLabelIndex}
disabled={!getNextFormLabel()}
iconPosition={"end"}
>
{getNextFormLabel()?.label || "暂无更多"}
</Button>
<Button type="primary" onClick={handleCommit}>
</Button>
</div>
</div>
);
}
interface FormLabel {
label: string;
name: string;
}
@@ -0,0 +1,12 @@
.formItemSubtitle {
font-size: 18px;
font-weight: bold;
margin-bottom: 10px;
}
.changeFormButtonGroupContainer {
width: 320px;
display: flex;
flex-direction: row;
justify-content: space-between;
}
+78
View File
@@ -0,0 +1,78 @@
"use client";
import { Modal } from "antd";
import { useState, useEffect } from "react";
import CreateCardComponent from "@/app/infra/basic-component/create-card-component/CreateCardComponent";
import PipelineFormComponent from "./components/pipeline-form/PipelineFormComponent";
import { httpClient } from "@/app/infra/http/HttpClient";
import { PipelineCardVO } from "@/app/home/pipelines/components/pipeline-card/PipelineCardVO";
import PipelineCardComponent from "@/app/home/pipelines/components/pipeline-card/PipelineCardComponent";
export default function PluginConfigPage() {
const [modalOpen, setModalOpen] = useState<boolean>(false);
const [isEditForm] = useState(false);
const [pipelineList, setPipelineList] = useState<PipelineCardVO[]>([]);
useEffect(() => {
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);
});
}
return (
<div className={``}>
<Modal
title={isEditForm ? "编辑流水线" : "创建流水线"}
centered
open={modalOpen}
onOk={() => setModalOpen(false)}
onCancel={() => setModalOpen(false)}
width={700}
footer={null}
>
<PipelineFormComponent
onFinish={() => {
getPipelines();
setModalOpen(false);
}}
/>
</Modal>
{pipelineList.length > 0 && (
<div className={``}>
{pipelineList.map((pipeline) => {
return (
<PipelineCardComponent key={pipeline.id} cardVO={pipeline} />
);
})}
</div>
)}
<CreateCardComponent
width={360}
height={200}
plusSize={90}
onClick={() => {
setModalOpen(true);
}}
/>
</div>
);
}
@@ -0,0 +1,4 @@
.formItemSubTitle {
font-size: 30px;
font-weight: bold;
}