feat: Add base project structure with API and web components

This commit is contained in:
Ke Sun
2025-12-02 20:28:01 +08:00
parent f3de6d6cc9
commit c1adc62ec6
817 changed files with 111226 additions and 106 deletions

View File

@@ -0,0 +1,139 @@
import { forwardRef, useImperativeHandle, useState } from 'react';
import { Form, Input } from 'antd';
import { useTranslation } from 'react-i18next';
import RadioGroupCard from '@/components/RadioGroupCard'
import AgentIcon from '@/assets/images/application/agent.svg'
import ClusterIcon from '@/assets/images/application/cluster.svg'
import WorkflowIcon from '@/assets/images/application/workflow.svg'
import type { ApplicationModalData, ApplicationModalRef, Application } from '../types'
import RbModal from '@/components/RbModal'
import { addApplication, updateApplication } from '@/api/application'
const FormItem = Form.Item;
interface ApplicationModalProps {
refresh: () => void;
}
const types = [
'agent',
'multi_agent',
'workflow'
]
const typeIcons: Record<string, string> = {
agent: AgentIcon,
multi_agent: ClusterIcon,
workflow: WorkflowIcon
}
const ApplicationModal = forwardRef<ApplicationModalRef, ApplicationModalProps>(({
refresh
}, ref) => {
const { t } = useTranslation();
const [visible, setVisible] = useState(false);
const [form] = Form.useForm<ApplicationModalData>();
const [loading, setLoading] = useState(false)
const [editVo, setEditVo] = useState<Application | null>(null)
const values = Form.useWatch([], form);
// 封装取消方法,添加关闭弹窗逻辑
const handleClose = () => {
setVisible(false);
form.resetFields();
setLoading(false)
setEditVo(null)
};
const handleOpen = (application?: Application) => {
if (application) {
setEditVo(application || null)
form.setFieldsValue({
name: application.name,
type: application.type,
description: application.description,
})
} else {
form.resetFields();
}
setVisible(true);
};
// 封装保存方法,添加提交逻辑
const handleSave = () => {
form
.validateFields()
.then(() => {
setLoading(true)
const response = editVo?.id ? updateApplication(editVo.id, {
...editVo,
...values,
} as Application) : addApplication(values as Application)
response.then(() => {
refresh()
handleClose()
})
.finally(() => {
setLoading(false)
});
})
.catch((err) => {
console.log('err', err)
});
}
// 暴露给父组件的方法
useImperativeHandle(ref, () => ({
handleOpen,
handleClose
}));
return (
<RbModal
title={t(`application.${editVo?.id ? 'editApplication' : 'createApplication'}`)}
open={visible}
onCancel={handleClose}
okText={t('common.save')}
onOk={handleSave}
confirmLoading={loading}
>
<Form
form={form}
layout="vertical"
>
<FormItem
name="name"
label={t('application.applicationName')}
rules={[{ required: true, message: t('common.pleaseEnter') }]}
>
<Input placeholder={t('common.enter')} />
</FormItem>
<FormItem
name="description"
label={t('application.description')}
>
<Input.TextArea placeholder={t('common.enter')} />
</FormItem>
<FormItem
name="type"
label={t('application.applicationType')}
rules={[{ required: true, message: t('common.pleaseSelect') }]}
>
<RadioGroupCard
options={types.map((type) => ({
value: type,
label: t(`application.${type}`),
labelDesc: t(`application.${type}Desc`),
icon: typeIcons[type],
disabled: editVo?.id || type === 'workflow'
}))}
/>
</FormItem>
</Form>
</RbModal>
);
});
export default ApplicationModal;

View File

@@ -0,0 +1,114 @@
import React, { useState, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import { Button, Row, Col, App } from 'antd';
import clsx from 'clsx';
import { DeleteOutlined } from '@ant-design/icons';
import type { Application, ApplicationModalRef } from './types';
import ApplicationModal from './components/ApplicationModal';
import SearchInput from '@/components/SearchInput'
import RbCard from '@/components/RbCard/Card'
import { getApplicationListUrl, deleteApplication } from '@/api/application'
import PageScrollList, { type PageScrollListRef } from '@/components/PageScrollList'
import { formatDateTime } from '@/utils/format';
const ApplicationManagement: React.FC = () => {
const { t } = useTranslation();
const { modal } = App.useApp();
const [query, setQuery] = useState({});
const applicationModalRef = useRef<ApplicationModalRef>(null);
const scrollListRef = useRef<PageScrollListRef>(null)
const refresh = () => {
scrollListRef.current?.refresh();
}
const handleCreate = () => {
applicationModalRef.current?.handleOpen();
}
const handleEdit = (item: Application) => {
window.open(`/#/application/config/${item.id}`);
}
const handleDelete = (item: Application) => {
modal.confirm({
title: t('common.confirmDeleteDesc', { name: item.name }),
okText: t('common.delete'),
okType: 'danger',
onOk: () => {
deleteApplication(item.id)
.then(() => {
refresh();
})
.catch(() => {
console.error('Failed to delete application');
});
}
})
}
return (
<>
<Row gutter={16} className="rb:mb-[16px]">
<Col span={12}>
<SearchInput
placeholder={t('application.searchPlaceholder')}
onSearch={(value) => setQuery({ search: value })}
style={{width: '100%'}}
/>
</Col>
<Col span={12} className="rb:text-right">
<Button type="primary" onClick={handleCreate}>
{t('application.createApplication')}
</Button>
</Col>
</Row>
<PageScrollList
ref={scrollListRef}
url={getApplicationListUrl}
query={query}
renderItem={(item: Application) => (
<RbCard
title={item.name}
avatar={
<div className="rb:w-[48px] rb:h-[48px] rb:rounded-[8px] rb:mr-[13px] rb:bg-[#155eef] rb:flex rb:items-center rb:justify-center rb:text-[28px] rb:text-[#ffffff]">
{item.name[0]}
</div>
}
>
{['type', 'source', 'created_at'].map((key, index) => (
<div key={key} className={clsx("rb:flex rb:justify-between rb:gap-[20px] rb:font-regular rb:text-[14px]", {
'rb:mt-[12px]': index !== 0
})}>
<span className="rb:text-[#5B6167]">{t(`application.${key}`)}</span>
<span className={clsx({
'rb:text-[#155EEF] rb:font-medium': key === 'type' && item[key] === 'agent',
'rb:text-[#369F21] rb:font-medium': key === 'type' && item[key] === 'multi_agent',
})}>
{key === 'source' && item.is_shared
? t('application.shared')
: key === 'source' && !item.is_shared
? t('application.configuration')
: key === 'created_at'
? formatDateTime(item[key as keyof Application], 'YYYY-MM-DD HH:mm:ss')
: t(`application.${item[key as keyof Application]}`)
}
</span>
</div>
))}
<div className="rb:mt-[20px] rb:flex rb:justify-between rb:gap-[10px]">
<Button type="primary" ghost className="rb:w-[calc(100%-46px)]" onClick={() => handleEdit(item)}>{t('application.configuration')}</Button>
<Button icon={<DeleteOutlined />} onClick={() => handleDelete(item)}></Button>
</div>
</RbCard>
)}
/>
<ApplicationModal
ref={applicationModalRef}
refresh={refresh}
/>
</>
);
};
export default ApplicationManagement;

View File

@@ -0,0 +1,73 @@
// 应用数据类型
export interface Application {
id: string;
workspace_id: string;
created_by: string;
name: string;
description?: string;
icon?: string;
icon_type?: string;
type: string;
visibility: string;
status: string;
tags: string[];
current_release_id?: string;
is_active: boolean;
is_shared: boolean;
created_at: number;
updated_at: number;
}
// 创建表单数据类型
export interface ApplicationModalData {
name: string;
type: string;
description?: string;
icon: {
url: string;
uid: string | number;
}[];
}
// 定义组件暴露的方法接口
export interface ApplicationModalRef {
handleOpen: (application?: Application) => void;
}
export interface ModelConfigModalRef {
handleOpen: (application?: Application) => void;
}
export interface ModelConfigModalData {
model: string;
[key: string]: string;
}
export interface AiPromptModalRef {
handleOpen: (application?: Application) => void;
}
export interface VariableModalRef {
handleOpen: (application?: Application) => void;
}
export interface VariableModalProps {
refresh: () => void;
}
export interface VariableEditModalRef {
handleOpen: (values?: Variable) => void;
}
export interface Variable {
index?: number;
type: string;
key: string;
name: string;
maxLength?: number;
defaultValue?: string;
options?: string[];
required: boolean;
hidden?: boolean;
}
export interface ApiExtensionModalData {
name: string;
apiEndpoint: string;
apiKey: string;
}
export interface ApiExtensionModalRef {
handleOpen: () => void;
}