Merge pull request #292 from SuanmoSuanyangTechnology/docs/web_zy
style(web): translate the comments in the src/views directory into En…
This commit is contained in:
@@ -2154,6 +2154,7 @@ Memory Bear: After the rebellion, regional warlordism intensified for several re
|
|||||||
resilience: 'Resilience',
|
resilience: 'Resilience',
|
||||||
suggestions: 'Personalized Suggestions',
|
suggestions: 'Personalized Suggestions',
|
||||||
suggestionLoading: 'Your personalized suggestions are being generated',
|
suggestionLoading: 'Your personalized suggestions are being generated',
|
||||||
|
item: 'item',
|
||||||
},
|
},
|
||||||
reflectionEngine: {
|
reflectionEngine: {
|
||||||
reflectionEngineConfig: 'Reflection Engine Configuration',
|
reflectionEngineConfig: 'Reflection Engine Configuration',
|
||||||
|
|||||||
@@ -2243,6 +2243,7 @@ export const zh = {
|
|||||||
resilience: '恢复力',
|
resilience: '恢复力',
|
||||||
suggestions: '个性化建议',
|
suggestions: '个性化建议',
|
||||||
suggestionLoading: '您的个性化建议正在生成中',
|
suggestionLoading: '您的个性化建议正在生成中',
|
||||||
|
item: '个',
|
||||||
},
|
},
|
||||||
reflectionEngine: {
|
reflectionEngine: {
|
||||||
reflectionEngineConfig: '反思引擎配置',
|
reflectionEngineConfig: '反思引擎配置',
|
||||||
|
|||||||
@@ -1,7 +1,14 @@
|
|||||||
|
/*
|
||||||
|
* @Author: ZhaoYing
|
||||||
|
* @Date: 2026-02-03 15:52:44
|
||||||
|
* @Last Modified by: ZhaoYing
|
||||||
|
* @Last Modified time: 2026-02-04 10:00:02
|
||||||
|
*/
|
||||||
import { forwardRef, useImperativeHandle, useState } from 'react';
|
import { forwardRef, useImperativeHandle, useState } from 'react';
|
||||||
import { Switch, Button, Tooltip } from 'antd';
|
import { Switch, Button, Tooltip } from 'antd';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import type { ApiKey, ApiKeyModalRef } from '../types';
|
import type { ApiKey, ApiKeyModalRef } from '../types';
|
||||||
import RbModal from '@/components/RbModal'
|
import RbModal from '@/components/RbModal'
|
||||||
import { getApiKey } from '@/api/apiKey';
|
import { getApiKey } from '@/api/apiKey';
|
||||||
@@ -9,16 +16,29 @@ import { formatDateTime } from '@/utils/format'
|
|||||||
import Tag from '@/components/Tag'
|
import Tag from '@/components/Tag'
|
||||||
import { maskApiKeys } from '@/utils/apiKeyReplacer';
|
import { maskApiKeys } from '@/utils/apiKeyReplacer';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modal component for viewing API key details
|
||||||
|
* Displays read-only information about an API key
|
||||||
|
*/
|
||||||
const ApiKeyDetailModal = forwardRef<ApiKeyModalRef, { handleCopy: (content: string) => void }>(({ handleCopy }, ref) => {
|
const ApiKeyDetailModal = forwardRef<ApiKeyModalRef, { handleCopy: (content: string) => void }>(({ handleCopy }, ref) => {
|
||||||
|
// Hooks
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
// State
|
||||||
const [visible, setVisible] = useState(false);
|
const [visible, setVisible] = useState(false);
|
||||||
const [data, setData] = useState<ApiKey>({} as ApiKey)
|
const [data, setData] = useState<ApiKey>({} as ApiKey)
|
||||||
|
|
||||||
// 封装取消方法,添加关闭弹窗逻辑
|
/**
|
||||||
|
* Close the modal
|
||||||
|
*/
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
setVisible(false);
|
setVisible(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open modal and fetch API key details
|
||||||
|
* @param apiKey - API key item to view
|
||||||
|
*/
|
||||||
const handleOpen = (apiKey?: ApiKey) => {
|
const handleOpen = (apiKey?: ApiKey) => {
|
||||||
if (apiKey?.id) {
|
if (apiKey?.id) {
|
||||||
getApiKey(apiKey.id)
|
getApiKey(apiKey.id)
|
||||||
@@ -29,7 +49,9 @@ const ApiKeyDetailModal = forwardRef<ApiKeyModalRef, { handleCopy: (content: str
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 暴露给父组件的方法
|
/**
|
||||||
|
* Expose methods to parent component via ref
|
||||||
|
*/
|
||||||
useImperativeHandle(ref, () => ({
|
useImperativeHandle(ref, () => ({
|
||||||
handleOpen,
|
handleOpen,
|
||||||
handleClose
|
handleClose
|
||||||
@@ -84,7 +106,6 @@ const ApiKeyDetailModal = forwardRef<ApiKeyModalRef, { handleCopy: (content: str
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 高级设置 */}
|
|
||||||
{data.expires_at && <>
|
{data.expires_at && <>
|
||||||
<div className="rb:text-[#5B6167] rb:font-medium rb:leading-5 rb:my-4">{t('apiKey.advancedSettings')}</div>
|
<div className="rb:text-[#5B6167] rb:font-medium rb:leading-5 rb:my-4">{t('apiKey.advancedSettings')}</div>
|
||||||
|
|
||||||
|
|||||||
@@ -1,28 +1,48 @@
|
|||||||
|
/*
|
||||||
|
* @Author: ZhaoYing
|
||||||
|
* @Date: 2026-02-03 15:52:47
|
||||||
|
* @Last Modified by: ZhaoYing
|
||||||
|
* @Last Modified time: 2026-02-04 10:00:01
|
||||||
|
*/
|
||||||
import { forwardRef, useImperativeHandle, useState } from 'react';
|
import { forwardRef, useImperativeHandle, useState } from 'react';
|
||||||
import { Form, Input, Switch, App, DatePicker } from 'antd';
|
import { Form, Input, Switch, App, DatePicker } from 'antd';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import dayjs from 'dayjs'
|
||||||
|
|
||||||
import type { ApiKey, ApiKeyModalRef } from '../types';
|
import type { ApiKey, ApiKeyModalRef } from '../types';
|
||||||
import RbModal from '@/components/RbModal'
|
import RbModal from '@/components/RbModal'
|
||||||
import dayjs from 'dayjs'
|
|
||||||
import { createApiKey, updateApiKey } from '@/api/apiKey';
|
import { createApiKey, updateApiKey } from '@/api/apiKey';
|
||||||
|
|
||||||
const FormItem = Form.Item;
|
const FormItem = Form.Item;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Props for ApiKeyModal component
|
||||||
|
*/
|
||||||
interface CreateModalProps {
|
interface CreateModalProps {
|
||||||
|
/** Callback to refresh parent list after save */
|
||||||
refresh: () => void;
|
refresh: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modal component for creating or editing API keys
|
||||||
|
* Handles API key configuration including permissions and expiration
|
||||||
|
*/
|
||||||
const ApiKeyModal = forwardRef<ApiKeyModalRef, CreateModalProps>(({
|
const ApiKeyModal = forwardRef<ApiKeyModalRef, CreateModalProps>(({
|
||||||
refresh,
|
refresh,
|
||||||
}, ref) => {
|
}, ref) => {
|
||||||
|
// Hooks
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { message } = App.useApp();
|
const { message } = App.useApp();
|
||||||
const [visible, setVisible] = useState(false);
|
|
||||||
const [form] = Form.useForm<ApiKey>();
|
const [form] = Form.useForm<ApiKey>();
|
||||||
|
|
||||||
|
// State
|
||||||
|
const [visible, setVisible] = useState(false);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [editVo, setEditVo] = useState<ApiKey | null>(null);
|
const [editVo, setEditVo] = useState<ApiKey | null>(null);
|
||||||
|
|
||||||
// 封装取消方法,添加关闭弹窗逻辑
|
/**
|
||||||
|
* Close modal and reset form state
|
||||||
|
*/
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
setVisible(false);
|
setVisible(false);
|
||||||
form.resetFields();
|
form.resetFields();
|
||||||
@@ -30,10 +50,14 @@ const ApiKeyModal = forwardRef<ApiKeyModalRef, CreateModalProps>(({
|
|||||||
setEditVo(null);
|
setEditVo(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open modal for creating or editing
|
||||||
|
* @param apiKey - Optional API key data for edit mode
|
||||||
|
*/
|
||||||
const handleOpen = (apiKey?: ApiKey) => {
|
const handleOpen = (apiKey?: ApiKey) => {
|
||||||
if (apiKey?.id) {
|
if (apiKey?.id) {
|
||||||
const { scopes = [], expires_at } = apiKey
|
const { scopes = [], expires_at } = apiKey
|
||||||
// 编辑模式,填充表单
|
// Edit mode - populate form with existing data
|
||||||
form.setFieldsValue({
|
form.setFieldsValue({
|
||||||
name: apiKey.name,
|
name: apiKey.name,
|
||||||
description: apiKey.description,
|
description: apiKey.description,
|
||||||
@@ -46,7 +70,10 @@ const ApiKeyModal = forwardRef<ApiKeyModalRef, CreateModalProps>(({
|
|||||||
setVisible(true);
|
setVisible(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 封装保存方法,添加提交逻辑
|
/**
|
||||||
|
* Validate and submit form data
|
||||||
|
* Creates new API key or updates existing one
|
||||||
|
*/
|
||||||
const handleSave = async () => {
|
const handleSave = async () => {
|
||||||
form.validateFields()
|
form.validateFields()
|
||||||
.then((values) => {
|
.then((values) => {
|
||||||
@@ -59,7 +86,7 @@ const ApiKeyModal = forwardRef<ApiKeyModalRef, CreateModalProps>(({
|
|||||||
if (rag) {
|
if (rag) {
|
||||||
scopes.push('rag')
|
scopes.push('rag')
|
||||||
}
|
}
|
||||||
// 准备新的/更新的API Key数据
|
// Prepare new/updated API key data
|
||||||
const apiKeyData = {
|
const apiKeyData = {
|
||||||
...rest,
|
...rest,
|
||||||
scopes,
|
scopes,
|
||||||
@@ -78,7 +105,9 @@ const ApiKeyModal = forwardRef<ApiKeyModalRef, CreateModalProps>(({
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 暴露给父组件的方法
|
/**
|
||||||
|
* Expose methods to parent component via ref
|
||||||
|
*/
|
||||||
useImperativeHandle(ref, () => ({
|
useImperativeHandle(ref, () => ({
|
||||||
handleOpen,
|
handleOpen,
|
||||||
handleClose
|
handleClose
|
||||||
@@ -133,7 +162,6 @@ const ApiKeyModal = forwardRef<ApiKeyModalRef, CreateModalProps>(({
|
|||||||
<Switch />
|
<Switch />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
|
||||||
{/* 高级设置 */}
|
|
||||||
<div className="rb:text-[#5B6167] rb:font-medium rb:leading-5 rb:mb-4">{t('apiKey.advancedSettings')}</div>
|
<div className="rb:text-[#5B6167] rb:font-medium rb:leading-5 rb:mb-4">{t('apiKey.advancedSettings')}</div>
|
||||||
|
|
||||||
<FormItem
|
<FormItem
|
||||||
|
|||||||
@@ -1,8 +1,16 @@
|
|||||||
|
/*
|
||||||
|
* @Author: ZhaoYing
|
||||||
|
* @Date: 2026-02-03 15:52:50
|
||||||
|
* @Last Modified by: ZhaoYing
|
||||||
|
* @Last Modified time: 2026-02-03 15:52:50
|
||||||
|
*/
|
||||||
import React, { useRef } from 'react';
|
import React, { useRef } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Button, App, Space } from 'antd';
|
import { Button, App, Space } from 'antd';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import { DeleteOutlined, EditOutlined, EyeOutlined } from '@ant-design/icons';
|
import { DeleteOutlined, EditOutlined, EyeOutlined } from '@ant-design/icons';
|
||||||
|
import copy from 'copy-to-clipboard'
|
||||||
|
|
||||||
import type { ApiKey, ApiKeyModalRef } from './types';
|
import type { ApiKey, ApiKeyModalRef } from './types';
|
||||||
import ApiKeyModal from './components/ApiKeyModal';
|
import ApiKeyModal from './components/ApiKeyModal';
|
||||||
import ApiKeyDetailModal from './components/ApiKeyDetailModal';
|
import ApiKeyDetailModal from './components/ApiKeyDetailModal';
|
||||||
@@ -11,26 +19,49 @@ import { getApiKeyListUrl, deleteApiKey } from '@/api/apiKey';
|
|||||||
import PageScrollList, { type PageScrollListRef } from '@/components/PageScrollList'
|
import PageScrollList, { type PageScrollListRef } from '@/components/PageScrollList'
|
||||||
import { formatDateTime } from '@/utils/format';
|
import { formatDateTime } from '@/utils/format';
|
||||||
import Tag from '@/components/Tag'
|
import Tag from '@/components/Tag'
|
||||||
import copy from 'copy-to-clipboard'
|
|
||||||
import { maskApiKeys } from '@/utils/apiKeyReplacer';
|
import { maskApiKeys } from '@/utils/apiKeyReplacer';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API Key Management page component
|
||||||
|
* Manages service API keys with CRUD operations
|
||||||
|
*/
|
||||||
const ApiKeyManagement: React.FC = () => {
|
const ApiKeyManagement: React.FC = () => {
|
||||||
|
// Hooks
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { modal, message } = App.useApp();
|
const { modal, message } = App.useApp();
|
||||||
|
|
||||||
|
// Refs
|
||||||
const apiKeyModalRef = useRef<ApiKeyModalRef>(null);
|
const apiKeyModalRef = useRef<ApiKeyModalRef>(null);
|
||||||
const apiKeyDetailModalRef = useRef<ApiKeyModalRef>(null)
|
const apiKeyDetailModalRef = useRef<ApiKeyModalRef>(null)
|
||||||
const scrollListRef = useRef<PageScrollListRef>(null)
|
const scrollListRef = useRef<PageScrollListRef>(null)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refresh the API key list
|
||||||
|
*/
|
||||||
const refresh = () => {
|
const refresh = () => {
|
||||||
scrollListRef.current?.refresh();
|
scrollListRef.current?.refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open modal to create or edit API key
|
||||||
|
* @param item - Optional API key item for edit mode
|
||||||
|
*/
|
||||||
const handleEdit = (item?: ApiKey) => {
|
const handleEdit = (item?: ApiKey) => {
|
||||||
apiKeyModalRef.current?.handleOpen(item);
|
apiKeyModalRef.current?.handleOpen(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open modal to view API key details
|
||||||
|
* @param item - API key item to view
|
||||||
|
*/
|
||||||
const handleView = (item: ApiKey) => {
|
const handleView = (item: ApiKey) => {
|
||||||
apiKeyDetailModalRef.current?.handleOpen(item);
|
apiKeyDetailModalRef.current?.handleOpen(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete API key with confirmation
|
||||||
|
* @param item - API key item to delete
|
||||||
|
*/
|
||||||
const handleDelete = (item: ApiKey) => {
|
const handleDelete = (item: ApiKey) => {
|
||||||
modal.confirm({
|
modal.confirm({
|
||||||
title: t('common.confirmDeleteDesc', { name: item.name }),
|
title: t('common.confirmDeleteDesc', { name: item.name }),
|
||||||
@@ -46,6 +77,10 @@ const ApiKeyManagement: React.FC = () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Copy content to clipboard
|
||||||
|
* @param content - Content to copy
|
||||||
|
*/
|
||||||
const handleCopy = (content: string) => {
|
const handleCopy = (content: string) => {
|
||||||
copy(content)
|
copy(content)
|
||||||
message.success(t('common.copySuccess'))
|
message.success(t('common.copySuccess'))
|
||||||
|
|||||||
@@ -1,39 +1,76 @@
|
|||||||
|
/*
|
||||||
|
* @Author: ZhaoYing
|
||||||
|
* @Date: 2026-02-03 15:52:53
|
||||||
|
* @Last Modified by: ZhaoYing
|
||||||
|
* @Last Modified time: 2026-02-03 15:52:53
|
||||||
|
*/
|
||||||
import type { Dayjs } from 'dayjs'
|
import type { Dayjs } from 'dayjs'
|
||||||
import { maskApiKeys } from '@/utils/apiKeyReplacer'
|
import { maskApiKeys } from '@/utils/apiKeyReplacer'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API Key data structure
|
||||||
|
*/
|
||||||
export interface ApiKey {
|
export interface ApiKey {
|
||||||
|
/** Unique identifier */
|
||||||
id: string;
|
id: string;
|
||||||
|
/** API key name */
|
||||||
name: string;
|
name: string;
|
||||||
|
/** Optional description */
|
||||||
description?: string;
|
description?: string;
|
||||||
|
/** API key type */
|
||||||
type: 'agent' | 'multi_agent' | 'workflow' | 'service';
|
type: 'agent' | 'multi_agent' | 'workflow' | 'service';
|
||||||
scopes?: string[]; // 'memory' | 'rag' | 'app'
|
/** Permission scopes: 'memory' | 'rag' | 'app' */
|
||||||
|
scopes?: string[];
|
||||||
|
|
||||||
|
/** The actual API key string */
|
||||||
api_key: string;
|
api_key: string;
|
||||||
|
/** Whether the key is active */
|
||||||
is_active: boolean;
|
is_active: boolean;
|
||||||
|
/** Whether the key has expired */
|
||||||
is_expired: boolean;
|
is_expired: boolean;
|
||||||
|
/** Creation timestamp */
|
||||||
created_at: number;
|
created_at: number;
|
||||||
|
/** Expiration timestamp or Dayjs object */
|
||||||
expires_at?: number | Dayjs;
|
expires_at?: number | Dayjs;
|
||||||
|
/** Memory engine permission flag */
|
||||||
memory?: boolean;
|
memory?: boolean;
|
||||||
|
/** RAG/Knowledge base permission flag */
|
||||||
rag?: boolean;
|
rag?: boolean;
|
||||||
|
|
||||||
|
/** Last update timestamp */
|
||||||
updated_at: string;
|
updated_at: string;
|
||||||
|
/** Queries per second limit */
|
||||||
qps_limit?: number;
|
qps_limit?: number;
|
||||||
|
/** Daily request limit */
|
||||||
daily_request_limit?: number;
|
daily_request_limit?: number;
|
||||||
|
|
||||||
|
/** Rate limit */
|
||||||
rate_limit?: number;
|
rate_limit?: number;
|
||||||
|
/** Total number of requests made */
|
||||||
total_requests: number;
|
total_requests: number;
|
||||||
|
/** Quota used */
|
||||||
quota_used: number;
|
quota_used: number;
|
||||||
|
/** Quota limit */
|
||||||
quota_limit: number;
|
quota_limit: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ref methods exposed by API Key modal components
|
||||||
|
*/
|
||||||
export interface ApiKeyModalRef {
|
export interface ApiKeyModalRef {
|
||||||
|
/**
|
||||||
|
* Open the modal
|
||||||
|
* @param apiKey - Optional API key data for edit mode
|
||||||
|
*/
|
||||||
handleOpen: (apiKey?: ApiKey) => void;
|
handleOpen: (apiKey?: ApiKey) => void;
|
||||||
|
/** Close the modal */
|
||||||
handleClose: () => void;
|
handleClose: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取掩码后的API密钥
|
* Get masked API key for display
|
||||||
|
* @param apiKey - The API key to mask
|
||||||
|
* @returns Masked API key string
|
||||||
*/
|
*/
|
||||||
export const getMaskedApiKey = (apiKey: string): string => {
|
export const getMaskedApiKey = (apiKey: string): string => {
|
||||||
return maskApiKeys(apiKey)
|
return maskApiKeys(apiKey)
|
||||||
|
|||||||
@@ -1,75 +0,0 @@
|
|||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { type FC, useEffect, useState } from 'react';
|
|
||||||
import { Row, Col, Skeleton } from 'antd'
|
|
||||||
import CodeBlock from '@/components/Markdown/CodeBlock';
|
|
||||||
import { getMemoryApi } from '@/api/memory';
|
|
||||||
import RbCard from '@/components/RbCard/Card';
|
|
||||||
import type {
|
|
||||||
Data,
|
|
||||||
Section
|
|
||||||
} from './types';
|
|
||||||
import Empty from '@/components/Empty'
|
|
||||||
|
|
||||||
|
|
||||||
const ApiParameters: FC = () => {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const [loading, setLoading] = useState<boolean>(false)
|
|
||||||
// const [data, setData] = useState<Data | null>(null)
|
|
||||||
const [apiData, setApiData] = useState<Section[]>([])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
getApiData()
|
|
||||||
}, [])
|
|
||||||
const getApiData = () => {
|
|
||||||
setLoading(true)
|
|
||||||
getMemoryApi().then((res) => {
|
|
||||||
const resp = res as Data || {}
|
|
||||||
// setData(resp)
|
|
||||||
setApiData(resp.sections || [])
|
|
||||||
})
|
|
||||||
.finally(() => setLoading(false))
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="rb:pb-[24px]">
|
|
||||||
<h1 className="rb:text-2xl rb:font-semibold rb:mb-[8px]">{t('api.pageTitle')}</h1>
|
|
||||||
<p className="rb:text-[#5B6167] rb:text-[14px] rb:mb-[24px] rb:leading-[20px]">{t('api.pageSubTitle')}</p>
|
|
||||||
|
|
||||||
{loading
|
|
||||||
? <Skeleton />
|
|
||||||
: apiData.length === 0
|
|
||||||
? <Empty />
|
|
||||||
: <Row gutter={[24, 24]}>
|
|
||||||
{apiData.map((api, index) => (
|
|
||||||
<Col span={24} key={index}>
|
|
||||||
<RbCard
|
|
||||||
title={`${index + 1}. ${api.name}`}
|
|
||||||
>
|
|
||||||
<>
|
|
||||||
<div className="rb:mb-[24px] rb:bg-[#F0F3F8] rb:rounded-[8px] rb:shadow-[inset_4px_0px_0px_0px_#155EEF] rb:p-[16px_24px] rb:text-sm">
|
|
||||||
<span className="rb:bg-[#155EEF] rb:p-[2px_8px] rb:rounded-[6px] rb:text-[#fff] rb:mr-[16px]">{api.method}</span>
|
|
||||||
{api.path}
|
|
||||||
</div>
|
|
||||||
{api.desc &&<>
|
|
||||||
<div className="rb:text-base rb:font-medium rb:mb-[8px]">{t('api.desc')}</div>
|
|
||||||
<div className="rb:mb-[24px] rb:text-sm rb:text-[#5B6167]">{api.desc}</div>
|
|
||||||
</>}
|
|
||||||
|
|
||||||
{typeof api.input === 'string' && api.input !== '无' && <>
|
|
||||||
<div className="rb:text-base rb:font-medium rb:mb-[12px] rb:mt-[24px]">{t('api.input')}</div>
|
|
||||||
<CodeBlock value={api.input} />
|
|
||||||
</>}
|
|
||||||
{typeof api.output === 'string' && api.output !== '无' && <>
|
|
||||||
<div className="rb:text-base rb:font-medium rb:mb-[12px] rb:mt-[24px]">{t('api.output')}</div>
|
|
||||||
<CodeBlock value={api.output} />
|
|
||||||
</>}
|
|
||||||
</>
|
|
||||||
</RbCard>
|
|
||||||
</Col>
|
|
||||||
))}
|
|
||||||
</Row>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
export default ApiParameters;
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
export interface Section {
|
|
||||||
name: string;
|
|
||||||
path: string;
|
|
||||||
method: string;
|
|
||||||
input: string;
|
|
||||||
output: string;
|
|
||||||
desc: string;
|
|
||||||
}
|
|
||||||
export interface Data {
|
|
||||||
title: string;
|
|
||||||
meta: {
|
|
||||||
search_switch: {
|
|
||||||
value: string;
|
|
||||||
desc: string;
|
|
||||||
}[];
|
|
||||||
status_code: {
|
|
||||||
code: string;
|
|
||||||
desc: string;
|
|
||||||
}[];
|
|
||||||
}
|
|
||||||
sections: Section[]
|
|
||||||
}
|
|
||||||
@@ -1,8 +1,15 @@
|
|||||||
|
/*
|
||||||
|
* @Author: ZhaoYing
|
||||||
|
* @Date: 2026-02-03 16:29:21
|
||||||
|
* @Last Modified by: ZhaoYing
|
||||||
|
* @Last Modified time: 2026-02-03 16:29:21
|
||||||
|
*/
|
||||||
import { type FC, type ReactNode, useEffect, useRef, useState, forwardRef, useImperativeHandle } from 'react';
|
import { type FC, type ReactNode, useEffect, useRef, useState, forwardRef, useImperativeHandle } from 'react';
|
||||||
import clsx from 'clsx'
|
import clsx from 'clsx'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
import { Row, Col, Space, Form, Input, Switch, Button, App, Spin } from 'antd'
|
import { Row, Col, Space, Form, Input, Switch, Button, App, Spin } from 'antd'
|
||||||
|
|
||||||
import Chat from './components/Chat'
|
import Chat from './components/Chat'
|
||||||
import RbCard from '@/components/RbCard/Card'
|
import RbCard from '@/components/RbCard/Card'
|
||||||
import Card from './components/Card'
|
import Card from './components/Card'
|
||||||
@@ -33,6 +40,11 @@ import AiPromptModal from './components/AiPromptModal'
|
|||||||
import ToolList from './components/ToolList/ToolList'
|
import ToolList from './components/ToolList/ToolList'
|
||||||
import ChatVariableConfigModal from './components/ChatVariableConfigModal';
|
import ChatVariableConfigModal from './components/ChatVariableConfigModal';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Description wrapper component
|
||||||
|
* @param desc - Description text
|
||||||
|
* @param className - Additional CSS classes
|
||||||
|
*/
|
||||||
const DescWrapper: FC<{desc: string, className?: string}> = ({desc, className}) => {
|
const DescWrapper: FC<{desc: string, className?: string}> = ({desc, className}) => {
|
||||||
return (
|
return (
|
||||||
<div className={clsx(className, "rb:text-[12px] rb:text-[#5B6167] rb:font-regular rb:leading-4 ")}>
|
<div className={clsx(className, "rb:text-[12px] rb:text-[#5B6167] rb:font-regular rb:leading-4 ")}>
|
||||||
@@ -40,6 +52,12 @@ const DescWrapper: FC<{desc: string, className?: string}> = ({desc, className})
|
|||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Label wrapper component
|
||||||
|
* @param title - Label title
|
||||||
|
* @param className - Additional CSS classes
|
||||||
|
* @param children - Child elements
|
||||||
|
*/
|
||||||
const LabelWrapper: FC<{title: string, className?: string; children?: ReactNode}> = ({title, className, children}) => {
|
const LabelWrapper: FC<{title: string, className?: string; children?: ReactNode}> = ({title, className, children}) => {
|
||||||
return (
|
return (
|
||||||
<div className={clsx(className, "rb:text-[14px] rb:font-medium rb:leading-5")}>
|
<div className={clsx(className, "rb:text-[14px] rb:font-medium rb:leading-5")}>
|
||||||
@@ -48,6 +66,13 @@ const LabelWrapper: FC<{title: string, className?: string; children?: ReactNode}
|
|||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Switch wrapper component with label and description
|
||||||
|
* @param title - Switch title
|
||||||
|
* @param desc - Optional description
|
||||||
|
* @param name - Form field name
|
||||||
|
* @param needTransition - Whether to translate text
|
||||||
|
*/
|
||||||
const SwitchWrapper: FC<{ title: string, desc?: string, name: string | string[]; needTransition?: boolean; }> = ({ title, desc, name, needTransition = true }) => {
|
const SwitchWrapper: FC<{ title: string, desc?: string, name: string | string[]; needTransition?: boolean; }> = ({ title, desc, name, needTransition = true }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
@@ -65,6 +90,13 @@ const SwitchWrapper: FC<{ title: string, desc?: string, name: string | string[];
|
|||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Select wrapper component with label and description
|
||||||
|
* @param title - Select title
|
||||||
|
* @param desc - Description text
|
||||||
|
* @param name - Form field name
|
||||||
|
* @param url - API URL for options
|
||||||
|
*/
|
||||||
const SelectWrapper: FC<{ title: string, desc: string, name: string | string[], url: string }> = ({ title, desc, name, url }) => {
|
const SelectWrapper: FC<{ title: string, desc: string, name: string | string[], url: string }> = ({ title, desc, name, url }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
@@ -88,6 +120,10 @@ const SelectWrapper: FC<{ title: string, desc: string, name: string | string[],
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Agent configuration component
|
||||||
|
* Manages single agent configuration including prompts, knowledge, memory, variables, and tools
|
||||||
|
*/
|
||||||
const Agent = forwardRef<AgentRef>((_props, ref) => {
|
const Agent = forwardRef<AgentRef>((_props, ref) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { id } = useParams();
|
const { id } = useParams();
|
||||||
@@ -103,7 +139,7 @@ const Agent = forwardRef<AgentRef>((_props, ref) => {
|
|||||||
const [isSave, setIsSave] = useState(false)
|
const [isSave, setIsSave] = useState(false)
|
||||||
const initialized = useRef(false)
|
const initialized = useRef(false)
|
||||||
|
|
||||||
// 初始化完成标记
|
// Initialization flag
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (data) {
|
if (data) {
|
||||||
initialized.current = true
|
initialized.current = true
|
||||||
@@ -121,6 +157,9 @@ const Agent = forwardRef<AgentRef>((_props, ref) => {
|
|||||||
getData()
|
getData()
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch agent configuration data
|
||||||
|
*/
|
||||||
const getData = () => {
|
const getData = () => {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
getApplicationConfig(id as string).then(res => {
|
getApplicationConfig(id as string).then(res => {
|
||||||
@@ -147,6 +186,11 @@ const Agent = forwardRef<AgentRef>((_props, ref) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refresh configuration after model changes
|
||||||
|
* @param vo - Model configuration
|
||||||
|
* @param type - Source type (model or chat)
|
||||||
|
*/
|
||||||
const refresh = (vo: ModelConfig, type: Source) => {
|
const refresh = (vo: ModelConfig, type: Source) => {
|
||||||
if (type === 'model') {
|
if (type === 'model') {
|
||||||
const { default_model_config_id, ...rest } = vo
|
const { default_model_config_id, ...rest } = vo
|
||||||
@@ -188,20 +232,30 @@ const Agent = forwardRef<AgentRef>((_props, ref) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open model configuration modal
|
||||||
|
*/
|
||||||
const handleModelConfig = () => {
|
const handleModelConfig = () => {
|
||||||
modelConfigModalRef.current?.handleOpen('model')
|
modelConfigModalRef.current?.handleOpen('model')
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Clear all debugging chat sessions
|
||||||
|
*/
|
||||||
const handleClearDebugging = () => {
|
const handleClearDebugging = () => {
|
||||||
setChatList([])
|
setChatList([])
|
||||||
}
|
}
|
||||||
|
|
||||||
// 保存Agent配置
|
/**
|
||||||
|
* Save agent configuration
|
||||||
|
* @param flag - Whether to show success message
|
||||||
|
* @returns Promise that resolves when save is complete
|
||||||
|
*/
|
||||||
const handleSave = (flag = true) => {
|
const handleSave = (flag = true) => {
|
||||||
if (!isSave || !data) return Promise.resolve()
|
if (!isSave || !data) return Promise.resolve()
|
||||||
const { memory, knowledge_retrieval, tools, ...rest } = values
|
const { memory, knowledge_retrieval, tools, ...rest } = values
|
||||||
const { knowledge_bases = [], ...knowledgeRest } = knowledge_retrieval || {}
|
const { knowledge_bases = [], ...knowledgeRest } = knowledge_retrieval || {}
|
||||||
const { memory_content } = memory || {}
|
const { memory_content } = memory || {}
|
||||||
// 从原数据中获取memory的其他必要属性
|
// Get other necessary properties of memory from original data
|
||||||
const originalMemory = data.memory || ({} as MemoryConfig)
|
const originalMemory = data.memory || ({} as MemoryConfig)
|
||||||
|
|
||||||
const params: Config = {
|
const params: Config = {
|
||||||
@@ -240,6 +294,9 @@ const Agent = forwardRef<AgentRef>((_props, ref) => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Fetch available models list
|
||||||
|
*/
|
||||||
const getModels = () => {
|
const getModels = () => {
|
||||||
getModelList({ type: 'llm,chat', pagesize: 100, page: 1, is_active: true })
|
getModelList({ type: 'llm,chat', pagesize: 100, page: 1, is_active: true })
|
||||||
.then(res => {
|
.then(res => {
|
||||||
@@ -247,6 +304,9 @@ const Agent = forwardRef<AgentRef>((_props, ref) => {
|
|||||||
setModelList(response.items)
|
setModelList(response.items)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Add new model for debugging
|
||||||
|
*/
|
||||||
const handleAddModel = () => {
|
const handleAddModel = () => {
|
||||||
modelConfigModalRef.current?.handleOpen('chat')
|
modelConfigModalRef.current?.handleOpen('chat')
|
||||||
}
|
}
|
||||||
@@ -268,9 +328,16 @@ const Agent = forwardRef<AgentRef>((_props, ref) => {
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
const aiPromptModalRef = useRef<AiPromptModalRef>(null)
|
const aiPromptModalRef = useRef<AiPromptModalRef>(null)
|
||||||
|
/**
|
||||||
|
* Open AI prompt generation modal
|
||||||
|
*/
|
||||||
const handlePrompt = () => {
|
const handlePrompt = () => {
|
||||||
aiPromptModalRef.current?.handleOpen()
|
aiPromptModalRef.current?.handleOpen()
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Update prompt and extract variables
|
||||||
|
* @param value - New prompt value
|
||||||
|
*/
|
||||||
const updatePrompt = (value: string) => {
|
const updatePrompt = (value: string) => {
|
||||||
form.setFieldValue('system_prompt', value)
|
form.setFieldValue('system_prompt', value)
|
||||||
const variables = value.match(/\{\{([^}]+)\}\}/g)?.map(match => match.slice(2, -2)) || []
|
const variables = value.match(/\{\{([^}]+)\}\}/g)?.map(match => match.slice(2, -2)) || []
|
||||||
@@ -285,15 +352,26 @@ const Agent = forwardRef<AgentRef>((_props, ref) => {
|
|||||||
updateVariableList(newVariableList)
|
updateVariableList(newVariableList)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update variable list
|
||||||
|
* @param list - New variable list
|
||||||
|
*/
|
||||||
const updateVariableList = (list: Variable[]) => {
|
const updateVariableList = (list: Variable[]) => {
|
||||||
form.setFieldValue('variables', [...list])
|
form.setFieldValue('variables', [...list])
|
||||||
setChatVariables([...list])
|
setChatVariables([...list])
|
||||||
}
|
}
|
||||||
const chatVariableConfigModalRef = useRef<ChatVariableConfigModalRef>(null)
|
const chatVariableConfigModalRef = useRef<ChatVariableConfigModalRef>(null)
|
||||||
const [chatVariables, setChatVariables] = useState<Variable[]>([])
|
const [chatVariables, setChatVariables] = useState<Variable[]>([])
|
||||||
|
/**
|
||||||
|
* Open chat variable configuration modal
|
||||||
|
*/
|
||||||
const handleOpenVariableConfig = () => {
|
const handleOpenVariableConfig = () => {
|
||||||
chatVariableConfigModalRef.current?.handleOpen(chatVariables)
|
chatVariableConfigModalRef.current?.handleOpen(chatVariables)
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Save chat variable configuration
|
||||||
|
* @param values - Variable values
|
||||||
|
*/
|
||||||
const handleSaveChatVariable = (values: Variable[]) => {
|
const handleSaveChatVariable = (values: Variable[]) => {
|
||||||
setChatVariables(values)
|
setChatVariables(values)
|
||||||
}
|
}
|
||||||
@@ -347,7 +425,7 @@ const Agent = forwardRef<AgentRef>((_props, ref) => {
|
|||||||
<Knowledge />
|
<Knowledge />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
{/* 记忆配置 */}
|
{/* Memory Configuration */}
|
||||||
<Card title={t('application.memoryConfiguration')}>
|
<Card title={t('application.memoryConfiguration')}>
|
||||||
<Space size={24} direction='vertical' style={{ width: '100%' }}>
|
<Space size={24} direction='vertical' style={{ width: '100%' }}>
|
||||||
<SwitchWrapper title="dialogueHistoricalMemory" desc="dialogueHistoricalMemoryDesc" name={['memory', 'enabled']} />
|
<SwitchWrapper title="dialogueHistoricalMemory" desc="dialogueHistoricalMemoryDesc" name={['memory', 'enabled']} />
|
||||||
@@ -364,7 +442,7 @@ const Agent = forwardRef<AgentRef>((_props, ref) => {
|
|||||||
<VariableList />
|
<VariableList />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
{/* 工具配置 */}
|
{/* Tool Configuration */}
|
||||||
<Form.Item name="tools">
|
<Form.Item name="tools">
|
||||||
<ToolList />
|
<ToolList />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
/*
|
||||||
|
* @Author: ZhaoYing
|
||||||
|
* @Date: 2026-02-03 16:29:29
|
||||||
|
* @Last Modified by: ZhaoYing
|
||||||
|
* @Last Modified time: 2026-02-03 16:29:29
|
||||||
|
*/
|
||||||
import { type FC, useState, useRef, useEffect } from 'react';
|
import { type FC, useState, useRef, useEffect } from 'react';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@@ -14,6 +20,11 @@ import Tag from '@/components/Tag'
|
|||||||
import { getApiKeyList, getApiKeyStats, deleteApiKey } from '@/api/apiKey';
|
import { getApiKeyList, getApiKeyStats, deleteApiKey } from '@/api/apiKey';
|
||||||
import { maskApiKeys } from '@/utils/apiKeyReplacer'
|
import { maskApiKeys } from '@/utils/apiKeyReplacer'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API configuration page component
|
||||||
|
* Manages API endpoints and API keys for the application
|
||||||
|
* @param application - Current application data
|
||||||
|
*/
|
||||||
const Api: FC<{ application: Application | null }> = ({ application }) => {
|
const Api: FC<{ application: Application | null }> = ({ application }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const activeMethods = ['POST'];
|
const activeMethods = ['POST'];
|
||||||
@@ -23,6 +34,10 @@ const Api: FC<{ application: Application | null }> = ({ application }) => {
|
|||||||
const apiKeyConfigModalRef = useRef<ApiKeyConfigModalRef>(null);
|
const apiKeyConfigModalRef = useRef<ApiKeyConfigModalRef>(null);
|
||||||
const [apiKeyList, setApiKeyList] = useState<ApiKey[]>([])
|
const [apiKeyList, setApiKeyList] = useState<ApiKey[]>([])
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copy content to clipboard
|
||||||
|
* @param content - Content to copy
|
||||||
|
*/
|
||||||
const handleCopy = (content: string) => {
|
const handleCopy = (content: string) => {
|
||||||
copy(content)
|
copy(content)
|
||||||
message.success(t('common.copySuccess'))
|
message.success(t('common.copySuccess'))
|
||||||
@@ -31,6 +46,9 @@ const Api: FC<{ application: Application | null }> = ({ application }) => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getApiList()
|
getApiList()
|
||||||
}, [])
|
}, [])
|
||||||
|
/**
|
||||||
|
* Fetch API key list for the application
|
||||||
|
*/
|
||||||
const getApiList = () => {
|
const getApiList = () => {
|
||||||
if (!application) {
|
if (!application) {
|
||||||
return
|
return
|
||||||
@@ -48,6 +66,10 @@ const Api: FC<{ application: Application | null }> = ({ application }) => {
|
|||||||
getAllStats([...list])
|
getAllStats([...list])
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Fetch statistics for all API keys
|
||||||
|
* @param list - List of API keys
|
||||||
|
*/
|
||||||
const getAllStats = (list: ApiKey[]) => {
|
const getAllStats = (list: ApiKey[]) => {
|
||||||
const allList: ApiKey[] = []
|
const allList: ApiKey[] = []
|
||||||
list.forEach(async item => {
|
list.forEach(async item => {
|
||||||
@@ -66,12 +88,23 @@ const Api: FC<{ application: Application | null }> = ({ application }) => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Open modal to add new API key
|
||||||
|
*/
|
||||||
const handleAdd = () => {
|
const handleAdd = () => {
|
||||||
apiKeyModalRef.current?.handleOpen()
|
apiKeyModalRef.current?.handleOpen()
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Open modal to edit API key
|
||||||
|
* @param vo - API key to edit
|
||||||
|
*/
|
||||||
const handleEdit = (vo: ApiKey) => {
|
const handleEdit = (vo: ApiKey) => {
|
||||||
apiKeyConfigModalRef.current?.handleOpen(vo)
|
apiKeyConfigModalRef.current?.handleOpen(vo)
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Delete API key with confirmation
|
||||||
|
* @param vo - API key to delete
|
||||||
|
*/
|
||||||
const handleDelete = (vo: ApiKey) => {
|
const handleDelete = (vo: ApiKey) => {
|
||||||
modal.confirm({
|
modal.confirm({
|
||||||
title: t('common.confirmDeleteDesc', { name: vo.name }),
|
title: t('common.confirmDeleteDesc', { name: vo.name }),
|
||||||
@@ -89,7 +122,7 @@ const Api: FC<{ application: Application | null }> = ({ application }) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 计算total_requests总数
|
// Calculate total requests across all API keys
|
||||||
const totalRequests = apiKeyList.reduce((total, item) => total + item.total_requests, 0);
|
const totalRequests = apiKeyList.reduce((total, item) => total + item.total_requests, 0);
|
||||||
return (
|
return (
|
||||||
<div className="rb:w-250 rb:mt-5 rb:pb-5 rb:mx-auto">
|
<div className="rb:w-250 rb:mt-5 rb:pb-5 rb:mx-auto">
|
||||||
@@ -129,7 +162,7 @@ const Api: FC<{ application: Application | null }> = ({ application }) => {
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div className="rb:text-[#5B6167] rb:text-[12px] rb:mb-2">{t('application.apiKeySubTitle')}</div>
|
<div className="rb:text-[#5B6167] rb:text-[12px] rb:mb-2">{t('application.apiKeySubTitle')}</div>
|
||||||
{/* 总览数据 */}
|
{/* Overview Data */}
|
||||||
<Row>
|
<Row>
|
||||||
<Col span={6}>
|
<Col span={6}>
|
||||||
<Statistic title={t('application.apiKeyTotal')} value={apiKeyList.length} />
|
<Statistic title={t('application.apiKeyTotal')} value={apiKeyList.length} />
|
||||||
@@ -138,7 +171,7 @@ const Api: FC<{ application: Application | null }> = ({ application }) => {
|
|||||||
<Statistic title={t('application.apiKeyRequestTotal')} value={totalRequests} />
|
<Statistic title={t('application.apiKeyRequestTotal')} value={totalRequests} />
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
{/* API Key 列表 */}
|
{/* API Key List */}
|
||||||
{apiKeyList.sort((a, b) => b.created_at - a.created_at).map(item => (
|
{apiKeyList.sort((a, b) => b.created_at - a.created_at).map(item => (
|
||||||
<div key={item.id} className="rb:mt-4 rb:p-[10px_12px] rb:bg-[#F0F3F8] rb:border rb:border-[#DFE4ED] rb:rounded-lg">
|
<div key={item.id} className="rb:mt-4 rb:p-[10px_12px] rb:bg-[#F0F3F8] rb:border rb:border-[#DFE4ED] rb:rounded-lg">
|
||||||
<div className="rb:flex rb:items-center rb:justify-between">
|
<div className="rb:flex rb:items-center rb:justify-between">
|
||||||
|
|||||||
@@ -1,8 +1,15 @@
|
|||||||
|
/*
|
||||||
|
* @Author: ZhaoYing
|
||||||
|
* @Date: 2026-02-03 16:29:33
|
||||||
|
* @Last Modified by: ZhaoYing
|
||||||
|
* @Last Modified time: 2026-02-03 16:29:33
|
||||||
|
*/
|
||||||
import { useEffect, useState, useRef, forwardRef, useImperativeHandle } from 'react'
|
import { useEffect, useState, useRef, forwardRef, useImperativeHandle } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
import Card from './components/Card'
|
|
||||||
import { Form, Space, Row, Col, Button, Flex, App, Select } from 'antd'
|
import { Form, Space, Row, Col, Button, Flex, App, Select } from 'antd'
|
||||||
|
|
||||||
|
import Card from './components/Card'
|
||||||
import Tag, { type TagProps } from './components/Tag'
|
import Tag, { type TagProps } from './components/Tag'
|
||||||
import CustomSelect from '@/components/CustomSelect';
|
import CustomSelect from '@/components/CustomSelect';
|
||||||
import { getMultiAgentConfig, saveMultiAgentConfig, getApplicationList } from '@/api/application';
|
import { getMultiAgentConfig, saveMultiAgentConfig, getApplicationList } from '@/api/application';
|
||||||
@@ -26,6 +33,10 @@ import type { Application } from '@/views/ApplicationManagement/types'
|
|||||||
|
|
||||||
const tagColors = ['processing', 'warning', 'default']
|
const tagColors = ['processing', 'warning', 'default']
|
||||||
const MAX_LENGTH = 5;
|
const MAX_LENGTH = 5;
|
||||||
|
/**
|
||||||
|
* Multi-agent cluster configuration component
|
||||||
|
* Manages multi-agent orchestration, sub-agents, and collaboration modes
|
||||||
|
*/
|
||||||
const Cluster = forwardRef<ClusterRef>((_props, ref) => {
|
const Cluster = forwardRef<ClusterRef>((_props, ref) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { message } = App.useApp()
|
const { message } = App.useApp()
|
||||||
@@ -41,6 +52,11 @@ const Cluster = forwardRef<ClusterRef>((_props, ref) => {
|
|||||||
},
|
},
|
||||||
])
|
])
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save cluster configuration
|
||||||
|
* @param flag - Whether to show success message
|
||||||
|
* @returns Promise that resolves when save is complete
|
||||||
|
*/
|
||||||
const handleSave = (flag = true) => {
|
const handleSave = (flag = true) => {
|
||||||
if (!data) return Promise.resolve()
|
if (!data) return Promise.resolve()
|
||||||
if (!values.default_model_config_id && values.orchestration_mode === 'supervisor') {
|
if (!values.default_model_config_id && values.orchestration_mode === 'supervisor') {
|
||||||
@@ -80,6 +96,9 @@ const Cluster = forwardRef<ClusterRef>((_props, ref) => {
|
|||||||
getData()
|
getData()
|
||||||
}, [id])
|
}, [id])
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch cluster configuration data
|
||||||
|
*/
|
||||||
const getData = () => {
|
const getData = () => {
|
||||||
if (!id) {
|
if (!id) {
|
||||||
return
|
return
|
||||||
@@ -113,9 +132,17 @@ const Cluster = forwardRef<ClusterRef>((_props, ref) => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Open sub-agent modal for add or edit
|
||||||
|
* @param agent - Optional agent data for edit mode
|
||||||
|
*/
|
||||||
const handleSubAgentModal = (agent?: SubAgentItem) => {
|
const handleSubAgentModal = (agent?: SubAgentItem) => {
|
||||||
subAgentModalRef.current?.handleOpen(agent)
|
subAgentModalRef.current?.handleOpen(agent)
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Refresh sub-agents list after add or edit
|
||||||
|
* @param agent - Agent data to add or update
|
||||||
|
*/
|
||||||
const refreshSubAgents = (agent: SubAgentItem) => {
|
const refreshSubAgents = (agent: SubAgentItem) => {
|
||||||
const index = subAgents.findIndex(item => item.agent_id === agent.agent_id)
|
const index = subAgents.findIndex(item => item.agent_id === agent.agent_id)
|
||||||
const newSubAgents = [...subAgents]
|
const newSubAgents = [...subAgents]
|
||||||
@@ -130,6 +157,10 @@ const Cluster = forwardRef<ClusterRef>((_props, ref) => {
|
|||||||
setSubAgents(newSubAgents)
|
setSubAgents(newSubAgents)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Delete sub-agent from list
|
||||||
|
* @param agent - Agent to delete
|
||||||
|
*/
|
||||||
const handleDeleteSubAgent = (agent: SubAgentItem) => {
|
const handleDeleteSubAgent = (agent: SubAgentItem) => {
|
||||||
setSubAgents(prev => prev.filter(item => item.agent_id !== agent.agent_id))
|
setSubAgents(prev => prev.filter(item => item.agent_id !== agent.agent_id))
|
||||||
}
|
}
|
||||||
@@ -138,9 +169,16 @@ const Cluster = forwardRef<ClusterRef>((_props, ref) => {
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
const modelConfigModalRef = useRef<ModelConfigModalRef>(null)
|
const modelConfigModalRef = useRef<ModelConfigModalRef>(null)
|
||||||
|
/**
|
||||||
|
* Open model configuration modal
|
||||||
|
*/
|
||||||
const handleEditModelConfig = () => {
|
const handleEditModelConfig = () => {
|
||||||
modelConfigModalRef.current?.handleOpen('multi_agent', values.model_parameters)
|
modelConfigModalRef.current?.handleOpen('multi_agent', values.model_parameters)
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Save model configuration
|
||||||
|
* @param values - Model parameters
|
||||||
|
*/
|
||||||
const handleSaveModelConfig = (values: Config['model_parameters']) => {
|
const handleSaveModelConfig = (values: Config['model_parameters']) => {
|
||||||
form.setFieldsValue({
|
form.setFieldsValue({
|
||||||
model_parameters: values
|
model_parameters: values
|
||||||
|
|||||||
@@ -1,7 +1,14 @@
|
|||||||
|
/*
|
||||||
|
* @Author: ZhaoYing
|
||||||
|
* @Date: 2026-02-03 16:29:41
|
||||||
|
* @Last Modified by: ZhaoYing
|
||||||
|
* @Last Modified time: 2026-02-03 16:29:41
|
||||||
|
*/
|
||||||
import { type FC, useState, useEffect, useRef } from 'react';
|
import { type FC, useState, useEffect, useRef } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import { Button, Space, Input, Form, App } from 'antd';
|
import { Button, Space, Input, Form, App } from 'antd';
|
||||||
|
|
||||||
import Tag, { type TagProps } from './components/Tag'
|
import Tag, { type TagProps } from './components/Tag'
|
||||||
import RbCard from '@/components/RbCard/Card'
|
import RbCard from '@/components/RbCard/Card'
|
||||||
import { getReleaseList, rollbackRelease } from '@/api/application'
|
import { getReleaseList, rollbackRelease } from '@/api/application'
|
||||||
@@ -12,12 +19,21 @@ import type { Application } from '@/views/ApplicationManagement/types'
|
|||||||
import Empty from '@/components/Empty'
|
import Empty from '@/components/Empty'
|
||||||
import { formatDateTime } from '@/utils/format';
|
import { formatDateTime } from '@/utils/format';
|
||||||
import Markdown from '@/components/Markdown'
|
import Markdown from '@/components/Markdown'
|
||||||
|
/**
|
||||||
|
* Tag color mapping for release versions
|
||||||
|
*/
|
||||||
const tagColors: Record<Release['tagKey'], TagProps['color']> = {
|
const tagColors: Record<Release['tagKey'], TagProps['color']> = {
|
||||||
current: 'processing',
|
current: 'processing',
|
||||||
rolledBack: 'warning',
|
rolledBack: 'warning',
|
||||||
history: 'default',
|
history: 'default',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Release page component
|
||||||
|
* Manages application version releases, rollbacks, and version history
|
||||||
|
* @param data - Application data
|
||||||
|
* @param refresh - Function to refresh application data
|
||||||
|
*/
|
||||||
const ReleasePage: FC<{data: Application; refresh: () => void}> = ({data, refresh}) => {
|
const ReleasePage: FC<{data: Application; refresh: () => void}> = ({data, refresh}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { message } = App.useApp()
|
const { message } = App.useApp()
|
||||||
@@ -30,6 +46,9 @@ const ReleasePage: FC<{data: Application; refresh: () => void}> = ({data, refres
|
|||||||
getData()
|
getData()
|
||||||
}, [data.id])
|
}, [data.id])
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch release list data
|
||||||
|
*/
|
||||||
const getData = () => {
|
const getData = () => {
|
||||||
refresh()
|
refresh()
|
||||||
getReleaseList(data.id).then(res => {
|
getReleaseList(data.id).then(res => {
|
||||||
@@ -38,6 +57,9 @@ const ReleasePage: FC<{data: Application; refresh: () => void}> = ({data, refres
|
|||||||
setSelectedVersion(response?.[0])
|
setSelectedVersion(response?.[0])
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Rollback to selected version
|
||||||
|
*/
|
||||||
const handleRollback = () => {
|
const handleRollback = () => {
|
||||||
if (!selectedVersion) return
|
if (!selectedVersion) return
|
||||||
rollbackRelease(data.id, selectedVersion.version).then(() => {
|
rollbackRelease(data.id, selectedVersion.version).then(() => {
|
||||||
@@ -124,7 +146,7 @@ const ReleasePage: FC<{data: Application; refresh: () => void}> = ({data, refres
|
|||||||
</div>
|
</div>
|
||||||
</RbCard>
|
</RbCard>
|
||||||
|
|
||||||
{/* 日志 */}
|
{/* Logs */}
|
||||||
<RbCard title={t('application.changeLog')} headerType="borderless">
|
<RbCard title={t('application.changeLog')} headerType="borderless">
|
||||||
<Space size={16} direction="vertical" style={{ width: '100%' }}>
|
<Space size={16} direction="vertical" style={{ width: '100%' }}>
|
||||||
{selectedVersion && (
|
{selectedVersion && (
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
/*
|
||||||
|
* @Author: ZhaoYing
|
||||||
|
* @Date: 2026-02-03 16:29:45
|
||||||
|
* @Last Modified by: ZhaoYing
|
||||||
|
* @Last Modified time: 2026-02-03 16:29:45
|
||||||
|
*/
|
||||||
import { type FC, useState, useEffect } from 'react';
|
import { type FC, useState, useEffect } from 'react';
|
||||||
import { Row, Col, Flex, DatePicker } from 'antd';
|
import { Row, Col, Flex, DatePicker } from 'antd';
|
||||||
import type { Dayjs } from 'dayjs'
|
import type { Dayjs } from 'dayjs'
|
||||||
@@ -10,12 +16,21 @@ import { getAppStatistics } from '@/api/application';
|
|||||||
import LineCard from './components/LineCard'
|
import LineCard from './components/LineCard'
|
||||||
import type { StatisticsData, StatisticsItem } from './types'
|
import type { StatisticsData, StatisticsItem } from './types'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mapping of daily statistics keys to total statistics keys
|
||||||
|
*/
|
||||||
const TotalObj: Record<string, keyof StatisticsData> = {
|
const TotalObj: Record<string, keyof StatisticsData> = {
|
||||||
daily_conversations: 'total_conversations',
|
daily_conversations: 'total_conversations',
|
||||||
daily_new_users: 'total_new_users',
|
daily_new_users: 'total_new_users',
|
||||||
daily_api_calls: 'total_api_calls',
|
daily_api_calls: 'total_api_calls',
|
||||||
daily_tokens: 'total_tokens',
|
daily_tokens: 'total_tokens',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Statistics page component
|
||||||
|
* Displays application usage statistics with charts and date range filtering
|
||||||
|
* @param application - Application data
|
||||||
|
*/
|
||||||
const Statistics: FC<{ application: Application | null }> = ({ application }) => {
|
const Statistics: FC<{ application: Application | null }> = ({ application }) => {
|
||||||
const [data, setData] = useState<StatisticsData>({
|
const [data, setData] = useState<StatisticsData>({
|
||||||
daily_conversations: [],
|
daily_conversations: [],
|
||||||
@@ -35,6 +50,9 @@ const Statistics: FC<{ application: Application | null }> = ({ application }) =>
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getData()
|
getData()
|
||||||
}, [application, query])
|
}, [application, query])
|
||||||
|
/**
|
||||||
|
* Fetch statistics data
|
||||||
|
*/
|
||||||
const getData = () => {
|
const getData = () => {
|
||||||
if (!application?.id) {
|
if (!application?.id) {
|
||||||
return
|
return
|
||||||
@@ -49,6 +67,10 @@ const Statistics: FC<{ application: Application | null }> = ({ application }) =>
|
|||||||
setData(res as StatisticsData)
|
setData(res as StatisticsData)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Handle date range change
|
||||||
|
* @param date - Selected date range
|
||||||
|
*/
|
||||||
const handleChange = (date: [Dayjs | null, Dayjs | null] | null) => {
|
const handleChange = (date: [Dayjs | null, Dayjs | null] | null) => {
|
||||||
if (!date || !date[0] || !date[1]) return
|
if (!date || !date[0] || !date[1]) return
|
||||||
setQuery({
|
setQuery({
|
||||||
|
|||||||
@@ -1,3 +1,15 @@
|
|||||||
|
/*
|
||||||
|
* @Author: ZhaoYing
|
||||||
|
* @Date: 2026-02-03 16:26:44
|
||||||
|
* @Last Modified by: ZhaoYing
|
||||||
|
* @Last Modified time: 2026-02-03 16:26:44
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* AI Prompt Assistant Modal
|
||||||
|
* Provides an interactive chat interface to help users optimize their prompts using AI
|
||||||
|
* Features model selection, chat history, and variable insertion
|
||||||
|
*/
|
||||||
|
|
||||||
import { forwardRef, useImperativeHandle, useState, useRef } from 'react';
|
import { forwardRef, useImperativeHandle, useState, useRef } from 'react';
|
||||||
import { Button, Form, Input, App, Row, Col } from 'antd';
|
import { Button, Form, Input, App, Row, Col } from 'antd';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@@ -19,11 +31,20 @@ import AiPromptVariableModal from './AiPromptVariableModal'
|
|||||||
import { type SSEMessage } from '@/utils/stream'
|
import { type SSEMessage } from '@/utils/stream'
|
||||||
import Editor from './Editor'
|
import Editor from './Editor'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component props
|
||||||
|
*/
|
||||||
interface AiPromptModalProps {
|
interface AiPromptModalProps {
|
||||||
|
/** Callback to refresh prompt with optimized value */
|
||||||
refresh: (value: string) => void;
|
refresh: (value: string) => void;
|
||||||
|
/** Default model to pre-select */
|
||||||
defaultModel: ModelListItem | null;
|
defaultModel: ModelListItem | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AI Prompt Assistant Modal Component
|
||||||
|
* Helps users create and optimize prompts through AI-powered conversation
|
||||||
|
*/
|
||||||
const AiPromptModal = forwardRef<AiPromptModalRef, AiPromptModalProps>(({
|
const AiPromptModal = forwardRef<AiPromptModalRef, AiPromptModalProps>(({
|
||||||
refresh,
|
refresh,
|
||||||
defaultModel,
|
defaultModel,
|
||||||
@@ -42,7 +63,7 @@ const AiPromptModal = forwardRef<AiPromptModalRef, AiPromptModalProps>(({
|
|||||||
|
|
||||||
const values = Form.useWatch([], form)
|
const values = Form.useWatch([], form)
|
||||||
|
|
||||||
// 封装取消方法,添加关闭弹窗逻辑
|
/** Close modal and reset state */
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
setVisible(false);
|
setVisible(false);
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
@@ -54,6 +75,7 @@ const AiPromptModal = forwardRef<AiPromptModalRef, AiPromptModalProps>(({
|
|||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** Open modal and create new prompt session */
|
||||||
const handleOpen = () => {
|
const handleOpen = () => {
|
||||||
createPromptSessions()
|
createPromptSessions()
|
||||||
.then(res => {
|
.then(res => {
|
||||||
@@ -66,6 +88,7 @@ const AiPromptModal = forwardRef<AiPromptModalRef, AiPromptModalProps>(({
|
|||||||
setVisible(true);
|
setVisible(true);
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
/** Send user message and get AI response */
|
||||||
const handleSend = () => {
|
const handleSend = () => {
|
||||||
if (!promptSession) return
|
if (!promptSession) return
|
||||||
if (!values.model_id) {
|
if (!values.model_id) {
|
||||||
@@ -115,7 +138,7 @@ const AiPromptModal = forwardRef<AiPromptModalRef, AiPromptModalProps>(({
|
|||||||
break;
|
break;
|
||||||
case 'end':
|
case 'end':
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
// 流结束时同步表单值
|
// Sync form value when stream ends
|
||||||
form.setFieldsValue({ current_prompt: currentPromptValueRef.current })
|
form.setFieldsValue({ current_prompt: currentPromptValueRef.current })
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -134,14 +157,17 @@ const AiPromptModal = forwardRef<AiPromptModalRef, AiPromptModalProps>(({
|
|||||||
setLoading(false)
|
setLoading(false)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
/** Copy current prompt to clipboard */
|
||||||
const handleCopy = () => {
|
const handleCopy = () => {
|
||||||
if (!values.current_prompt || values?.current_prompt?.trim() === '') return
|
if (!values.current_prompt || values?.current_prompt?.trim() === '') return
|
||||||
copy(values.current_prompt)
|
copy(values.current_prompt)
|
||||||
message.success(t('common.copySuccess'))
|
message.success(t('common.copySuccess'))
|
||||||
}
|
}
|
||||||
|
/** Open variable selection modal */
|
||||||
const handleAdd = () => {
|
const handleAdd = () => {
|
||||||
aiPromptVariableModalRef.current?.handleOpen()
|
aiPromptVariableModalRef.current?.handleOpen()
|
||||||
}
|
}
|
||||||
|
/** Insert variable into prompt editor */
|
||||||
const handleVariableApply = (value: string) => {
|
const handleVariableApply = (value: string) => {
|
||||||
if (editorRef.current?.insertText) {
|
if (editorRef.current?.insertText) {
|
||||||
editorRef.current.insertText(value)
|
editorRef.current.insertText(value)
|
||||||
@@ -149,6 +175,7 @@ const AiPromptModal = forwardRef<AiPromptModalRef, AiPromptModalProps>(({
|
|||||||
form.setFieldValue('current_prompt', (values.current_prompt || '') + value)
|
form.setFieldValue('current_prompt', (values.current_prompt || '') + value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/** Apply optimized prompt and close modal */
|
||||||
const handleApply = () => {
|
const handleApply = () => {
|
||||||
if (!values.current_prompt) {
|
if (!values.current_prompt) {
|
||||||
return
|
return
|
||||||
@@ -157,7 +184,7 @@ const AiPromptModal = forwardRef<AiPromptModalRef, AiPromptModalProps>(({
|
|||||||
handleClose()
|
handleClose()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 暴露给父组件的方法
|
/** Expose methods to parent component */
|
||||||
useImperativeHandle(ref, () => ({
|
useImperativeHandle(ref, () => ({
|
||||||
handleOpen,
|
handleOpen,
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -1,25 +1,42 @@
|
|||||||
|
/*
|
||||||
|
* @Author: ZhaoYing
|
||||||
|
* @Date: 2026-02-03 16:27:14
|
||||||
|
* @Last Modified by: ZhaoYing
|
||||||
|
* @Last Modified time: 2026-02-03 16:27:14
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* AI Prompt Variable Modal
|
||||||
|
* Allows users to insert variables into AI-generated prompts
|
||||||
|
* Supports autocomplete with existing variables
|
||||||
|
*/
|
||||||
|
|
||||||
import { forwardRef, useEffect, useImperativeHandle, useState } from 'react';
|
import { forwardRef, useEffect, useImperativeHandle, useState } from 'react';
|
||||||
import { Form, Input, App, Select, AutoComplete, type AutoCompleteProps } from 'antd';
|
import { Form, AutoComplete, type AutoCompleteProps } from 'antd';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import type { Application } from '@/views/ApplicationManagement/types'
|
|
||||||
import type { AiPromptVariableModalRef } from '../types'
|
import type { AiPromptVariableModalRef } from '../types'
|
||||||
import { createApiKey } from '@/api/apiKey';
|
|
||||||
import RbModal from '@/components/RbModal'
|
import RbModal from '@/components/RbModal'
|
||||||
|
|
||||||
const FormItem = Form.Item;
|
const FormItem = Form.Item;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component props
|
||||||
|
*/
|
||||||
interface AiPromptVariableModalProps {
|
interface AiPromptVariableModalProps {
|
||||||
|
/** Callback to insert variable into prompt */
|
||||||
refresh: (value: string) => void;
|
refresh: (value: string) => void;
|
||||||
|
/** List of available variables */
|
||||||
variables: string[];
|
variables: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Variable selection modal for AI prompt assistant
|
||||||
|
*/
|
||||||
const AiPromptVariableModal = forwardRef<AiPromptVariableModalRef, AiPromptVariableModalProps>(({
|
const AiPromptVariableModal = forwardRef<AiPromptVariableModalRef, AiPromptVariableModalProps>(({
|
||||||
refresh,
|
refresh,
|
||||||
variables
|
variables
|
||||||
}, ref) => {
|
}, ref) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { message } = App.useApp();
|
|
||||||
const [visible, setVisible] = useState(false);
|
const [visible, setVisible] = useState(false);
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false)
|
||||||
@@ -31,6 +48,7 @@ const AiPromptVariableModal = forwardRef<AiPromptVariableModalRef, AiPromptVaria
|
|||||||
label: `{{${key}}}`
|
label: `{{${key}}}`
|
||||||
})))
|
})))
|
||||||
}, [variables])
|
}, [variables])
|
||||||
|
/** Search and filter variables */
|
||||||
const handleSearch = (value: string) => {
|
const handleSearch = (value: string) => {
|
||||||
const filterKeys = variables?.filter(key => key.includes(value))
|
const filterKeys = variables?.filter(key => key.includes(value))
|
||||||
|
|
||||||
@@ -47,18 +65,19 @@ const AiPromptVariableModal = forwardRef<AiPromptVariableModalRef, AiPromptVaria
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 封装取消方法,添加关闭弹窗逻辑
|
/** Close modal and reset form */
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
setVisible(false);
|
setVisible(false);
|
||||||
form.resetFields();
|
form.resetFields();
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** Open modal */
|
||||||
const handleOpen = () => {
|
const handleOpen = () => {
|
||||||
setVisible(true);
|
setVisible(true);
|
||||||
form.resetFields();
|
form.resetFields();
|
||||||
};
|
};
|
||||||
// 封装保存方法,添加提交逻辑
|
/** Apply selected variable */
|
||||||
const handleSave = () => {
|
const handleSave = () => {
|
||||||
const variableName = form.getFieldValue('variableName')
|
const variableName = form.getFieldValue('variableName')
|
||||||
|
|
||||||
@@ -68,7 +87,7 @@ const AiPromptVariableModal = forwardRef<AiPromptVariableModalRef, AiPromptVaria
|
|||||||
handleClose()
|
handleClose()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 暴露给父组件的方法
|
/** Expose methods to parent component */
|
||||||
useImperativeHandle(ref, () => ({
|
useImperativeHandle(ref, () => ({
|
||||||
handleOpen,
|
handleOpen,
|
||||||
handleClose
|
handleClose
|
||||||
|
|||||||
@@ -1,3 +1,14 @@
|
|||||||
|
/*
|
||||||
|
* @Author: ZhaoYing
|
||||||
|
* @Date: 2026-02-03 16:27:22
|
||||||
|
* @Last Modified by: ZhaoYing
|
||||||
|
* @Last Modified time: 2026-02-03 16:27:22
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* API Key Configuration Modal
|
||||||
|
* Allows configuring rate limits and daily usage limits for API keys
|
||||||
|
*/
|
||||||
|
|
||||||
import { forwardRef, useImperativeHandle, useState } from 'react';
|
import { forwardRef, useImperativeHandle, useState } from 'react';
|
||||||
import { Form, Slider } from 'antd';
|
import { Form, Slider } from 'antd';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@@ -7,10 +18,17 @@ import RbModal from '@/components/RbModal'
|
|||||||
import { updateApiKey } from '@/api/apiKey';
|
import { updateApiKey } from '@/api/apiKey';
|
||||||
import type { ApiKey } from '@/views/ApiKeyManagement/types'
|
import type { ApiKey } from '@/views/ApiKeyManagement/types'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component props
|
||||||
|
*/
|
||||||
interface ApiKeyConfigModalProps {
|
interface ApiKeyConfigModalProps {
|
||||||
|
/** Callback to refresh API key list */
|
||||||
refresh: () => void;
|
refresh: () => void;
|
||||||
}
|
}
|
||||||
const ApiKeyConfigModal = forwardRef<ApiKeyConfigModalRef, ApiKeyConfigModalProps>(({
|
|
||||||
|
/**
|
||||||
|
* Modal for configuring API key limits
|
||||||
|
*/const ApiKeyConfigModal = forwardRef<ApiKeyConfigModalRef, ApiKeyConfigModalProps>(({
|
||||||
refresh
|
refresh
|
||||||
}, ref) => {
|
}, ref) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -20,7 +38,7 @@ const ApiKeyConfigModal = forwardRef<ApiKeyConfigModalRef, ApiKeyConfigModalProp
|
|||||||
const values = Form.useWatch<ApiKey>([], form)
|
const values = Form.useWatch<ApiKey>([], form)
|
||||||
const [editVo, setEditVo] = useState<ApiKey | null>(null)
|
const [editVo, setEditVo] = useState<ApiKey | null>(null)
|
||||||
|
|
||||||
// 封装取消方法,添加关闭弹窗逻辑
|
/** Close modal and reset state */
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
form.resetFields();
|
form.resetFields();
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
@@ -28,6 +46,7 @@ const ApiKeyConfigModal = forwardRef<ApiKeyConfigModalRef, ApiKeyConfigModalProp
|
|||||||
setVisible(false);
|
setVisible(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** Open modal with API key data */
|
||||||
const handleOpen = (apiKey: ApiKey) => {
|
const handleOpen = (apiKey: ApiKey) => {
|
||||||
setVisible(true);
|
setVisible(true);
|
||||||
setEditVo(apiKey)
|
setEditVo(apiKey)
|
||||||
@@ -36,7 +55,7 @@ const ApiKeyConfigModal = forwardRef<ApiKeyConfigModalRef, ApiKeyConfigModalProp
|
|||||||
rate_limit: apiKey.rate_limit
|
rate_limit: apiKey.rate_limit
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
// 封装保存方法,添加提交逻辑
|
/** Save API key configuration */
|
||||||
const handleSave = () => {
|
const handleSave = () => {
|
||||||
if (!editVo?.id) return
|
if (!editVo?.id) return
|
||||||
form.validateFields()
|
form.validateFields()
|
||||||
@@ -52,7 +71,7 @@ const ApiKeyConfigModal = forwardRef<ApiKeyConfigModalRef, ApiKeyConfigModalProp
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 暴露给父组件的方法
|
/** Expose methods to parent component */
|
||||||
useImperativeHandle(ref, () => ({
|
useImperativeHandle(ref, () => ({
|
||||||
handleOpen,
|
handleOpen,
|
||||||
handleClose
|
handleClose
|
||||||
@@ -73,7 +92,7 @@ const ApiKeyConfigModal = forwardRef<ApiKeyConfigModalRef, ApiKeyConfigModalProp
|
|||||||
className="rb:px-2.5!"
|
className="rb:px-2.5!"
|
||||||
scrollToFirstError={{ behavior: 'instant', block: 'end', focus: true }}
|
scrollToFirstError={{ behavior: 'instant', block: 'end', focus: true }}
|
||||||
>
|
>
|
||||||
{/* QPS 限制(每秒请求数) */}
|
{/* QPS limit (requests per second) */}
|
||||||
<>
|
<>
|
||||||
<div className="rb:text-[14px] rb:font-medium rb:leading-5 rb:mb-2">
|
<div className="rb:text-[14px] rb:font-medium rb:leading-5 rb:mb-2">
|
||||||
{t(`application.qpsLimit`)}({t('application.qpsLimitTip')})
|
{t(`application.qpsLimit`)}({t('application.qpsLimitTip')})
|
||||||
@@ -98,7 +117,7 @@ const ApiKeyConfigModal = forwardRef<ApiKeyConfigModalRef, ApiKeyConfigModalProp
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
{/* 日调用量限制 */}
|
{/* Daily usage limit */}
|
||||||
<>
|
<>
|
||||||
<div className="rb:text-[14px] rb:font-medium rb:leading-5 rb:mt-6 rb:mb-2">
|
<div className="rb:text-[14px] rb:font-medium rb:leading-5 rb:mt-6 rb:mb-2">
|
||||||
{t(`application.dailyUsageLimit`)}
|
{t(`application.dailyUsageLimit`)}
|
||||||
|
|||||||
@@ -1,3 +1,14 @@
|
|||||||
|
/*
|
||||||
|
* @Author: ZhaoYing
|
||||||
|
* @Date: 2026-02-03 16:27:25
|
||||||
|
* @Last Modified by: ZhaoYing
|
||||||
|
* @Last Modified time: 2026-02-03 16:27:25
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* API Key Creation Modal
|
||||||
|
* Allows creating new API keys for application access
|
||||||
|
*/
|
||||||
|
|
||||||
import { forwardRef, useImperativeHandle, useState } from 'react';
|
import { forwardRef, useImperativeHandle, useState } from 'react';
|
||||||
import { Form, Input, App } from 'antd';
|
import { Form, Input, App } from 'antd';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@@ -9,11 +20,19 @@ import RbModal from '@/components/RbModal'
|
|||||||
|
|
||||||
const FormItem = Form.Item;
|
const FormItem = Form.Item;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component props
|
||||||
|
*/
|
||||||
interface ApiKeyModalProps {
|
interface ApiKeyModalProps {
|
||||||
|
/** Callback to refresh API key list */
|
||||||
refresh: () => void;
|
refresh: () => void;
|
||||||
|
/** Application data */
|
||||||
application?: Application | null;
|
application?: Application | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modal for creating new API keys
|
||||||
|
*/
|
||||||
const ApiKeyModal = forwardRef<ApiKeyModalRef, ApiKeyModalProps>(({
|
const ApiKeyModal = forwardRef<ApiKeyModalRef, ApiKeyModalProps>(({
|
||||||
refresh,
|
refresh,
|
||||||
application
|
application
|
||||||
@@ -24,18 +43,19 @@ const ApiKeyModal = forwardRef<ApiKeyModalRef, ApiKeyModalProps>(({
|
|||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false)
|
||||||
|
|
||||||
// 封装取消方法,添加关闭弹窗逻辑
|
/** Close modal and reset form */
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
setVisible(false);
|
setVisible(false);
|
||||||
form.resetFields();
|
form.resetFields();
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** Open modal */
|
||||||
const handleOpen = () => {
|
const handleOpen = () => {
|
||||||
setVisible(true);
|
setVisible(true);
|
||||||
form.resetFields();
|
form.resetFields();
|
||||||
};
|
};
|
||||||
// 封装保存方法,添加提交逻辑
|
/** Create new API key */
|
||||||
const handleSave = () => {
|
const handleSave = () => {
|
||||||
if (!application) return
|
if (!application) return
|
||||||
form.validateFields()
|
form.validateFields()
|
||||||
@@ -58,7 +78,7 @@ const ApiKeyModal = forwardRef<ApiKeyModalRef, ApiKeyModalProps>(({
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 暴露给父组件的方法
|
/** Expose methods to parent component */
|
||||||
useImperativeHandle(ref, () => ({
|
useImperativeHandle(ref, () => ({
|
||||||
handleOpen,
|
handleOpen,
|
||||||
handleClose
|
handleClose
|
||||||
@@ -78,7 +98,7 @@ const ApiKeyModal = forwardRef<ApiKeyModalRef, ApiKeyModalProps>(({
|
|||||||
layout="vertical"
|
layout="vertical"
|
||||||
scrollToFirstError={{ behavior: 'instant', block: 'end', focus: true }}
|
scrollToFirstError={{ behavior: 'instant', block: 'end', focus: true }}
|
||||||
>
|
>
|
||||||
{/* Key 名称 */}
|
{/* Key name */}
|
||||||
<FormItem
|
<FormItem
|
||||||
name="name"
|
name="name"
|
||||||
label={t('application.apiKeyName')}
|
label={t('application.apiKeyName')}
|
||||||
@@ -89,7 +109,7 @@ const ApiKeyModal = forwardRef<ApiKeyModalRef, ApiKeyModalProps>(({
|
|||||||
>
|
>
|
||||||
<Input placeholder={t('application.apiKeyNamePlaceholder')} />
|
<Input placeholder={t('application.apiKeyNamePlaceholder')} />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
{/* 描述 */}
|
{/* Description */}
|
||||||
<FormItem
|
<FormItem
|
||||||
name="description"
|
name="description"
|
||||||
label={t('application.description')}
|
label={t('application.description')}
|
||||||
|
|||||||
@@ -1,13 +1,31 @@
|
|||||||
|
/*
|
||||||
|
* @Author: ZhaoYing
|
||||||
|
* @Date: 2026-02-03 16:27:31
|
||||||
|
* @Last Modified by: ZhaoYing
|
||||||
|
* @Last Modified time: 2026-02-03 16:27:31
|
||||||
|
*/
|
||||||
import { type FC, type ReactNode } from 'react'
|
import { type FC, type ReactNode } from 'react'
|
||||||
|
|
||||||
import RbCard from '@/components/RbCard/Card'
|
import RbCard from '@/components/RbCard/Card'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Props for Card component
|
||||||
|
*/
|
||||||
interface CardProps {
|
interface CardProps {
|
||||||
|
/** Card title */
|
||||||
title?: string | ReactNode;
|
title?: string | ReactNode;
|
||||||
|
/** Card subtitle */
|
||||||
subTitle?: string | ReactNode;
|
subTitle?: string | ReactNode;
|
||||||
|
/** Card content */
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
|
/** Extra content in header */
|
||||||
extra?: ReactNode;
|
extra?: ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Card component wrapper
|
||||||
|
* Styled card with left border accent for application configuration sections
|
||||||
|
*/
|
||||||
const Card: FC<CardProps> = ({
|
const Card: FC<CardProps> = ({
|
||||||
title,
|
title,
|
||||||
subTitle,
|
subTitle,
|
||||||
|
|||||||
@@ -1,7 +1,20 @@
|
|||||||
|
/*
|
||||||
|
* @Author: ZhaoYing
|
||||||
|
* @Date: 2026-02-03 16:27:39
|
||||||
|
* @Last Modified by: ZhaoYing
|
||||||
|
* @Last Modified time: 2026-02-03 16:27:39
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Chat debugging component for application testing
|
||||||
|
* Supports both single agent and multi-agent cluster modes
|
||||||
|
* Provides real-time streaming responses and conversation history
|
||||||
|
*/
|
||||||
|
|
||||||
import { type FC, useEffect, useState } from 'react';
|
import { type FC, useEffect, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import clsx from 'clsx'
|
import clsx from 'clsx'
|
||||||
import { Input, Form } from 'antd'
|
import { Input, Form } from 'antd'
|
||||||
|
|
||||||
import ChatIcon from '@/assets/images/application/chat.png'
|
import ChatIcon from '@/assets/images/application/chat.png'
|
||||||
import ChatSendIcon from '@/assets/images/application/chatSend.svg'
|
import ChatSendIcon from '@/assets/images/application/chatSend.svg'
|
||||||
import DebuggingEmpty from '@/assets/images/application/debuggingEmpty.png'
|
import DebuggingEmpty from '@/assets/images/application/debuggingEmpty.png'
|
||||||
@@ -12,13 +25,26 @@ import ChatContent from '@/components/Chat/ChatContent'
|
|||||||
import type { ChatItem } from '@/components/Chat/types'
|
import type { ChatItem } from '@/components/Chat/types'
|
||||||
import { type SSEMessage } from '@/utils/stream'
|
import { type SSEMessage } from '@/utils/stream'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component props
|
||||||
|
*/
|
||||||
interface ChatProps {
|
interface ChatProps {
|
||||||
|
/** List of chat configurations for comparison */
|
||||||
chatList: ChatData[];
|
chatList: ChatData[];
|
||||||
|
/** Application configuration data */
|
||||||
data: Config;
|
data: Config;
|
||||||
|
/** Update chat list state */
|
||||||
updateChatList: React.Dispatch<React.SetStateAction<ChatData[]>>;
|
updateChatList: React.Dispatch<React.SetStateAction<ChatData[]>>;
|
||||||
|
/** Save configuration before running */
|
||||||
handleSave: (flag?: boolean) => Promise<unknown>;
|
handleSave: (flag?: boolean) => Promise<unknown>;
|
||||||
|
/** Source type: multi-agent cluster or single agent */
|
||||||
source?: 'multi_agent' | 'agent';
|
source?: 'multi_agent' | 'agent';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Chat debugging component
|
||||||
|
* Allows testing application with different model configurations side-by-side
|
||||||
|
*/
|
||||||
const Chat: FC<ChatProps> = ({ chatList, data, updateChatList, handleSave, source = 'agent' }) => {
|
const Chat: FC<ChatProps> = ({ chatList, data, updateChatList, handleSave, source = 'agent' }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [form] = Form.useForm<{ message: string }>()
|
const [form] = Form.useForm<{ message: string }>()
|
||||||
@@ -31,6 +57,7 @@ const Chat: FC<ChatProps> = ({ chatList, data, updateChatList, handleSave, sourc
|
|||||||
setIsCluster(source === 'multi_agent')
|
setIsCluster(source === 'multi_agent')
|
||||||
}, [source])
|
}, [source])
|
||||||
|
|
||||||
|
/** Add user message to all chat lists */
|
||||||
const addUserMessage = (message: string) => {
|
const addUserMessage = (message: string) => {
|
||||||
const newUserMessage: ChatItem = {
|
const newUserMessage: ChatItem = {
|
||||||
role: 'user',
|
role: 'user',
|
||||||
@@ -42,6 +69,7 @@ const Chat: FC<ChatProps> = ({ chatList, data, updateChatList, handleSave, sourc
|
|||||||
list: [...(item.list || []), newUserMessage]
|
list: [...(item.list || []), newUserMessage]
|
||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
|
/** Add empty assistant message placeholder */
|
||||||
const addAssistantMessage = () => {
|
const addAssistantMessage = () => {
|
||||||
const assistantMessage: ChatItem = {
|
const assistantMessage: ChatItem = {
|
||||||
role: 'assistant',
|
role: 'assistant',
|
||||||
@@ -65,6 +93,7 @@ const Chat: FC<ChatProps> = ({ chatList, data, updateChatList, handleSave, sourc
|
|||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/** Update assistant message with streaming content */
|
||||||
const updateAssistantMessage = (content?: string, model_config_id?: string, conversation_id?: string) => {
|
const updateAssistantMessage = (content?: string, model_config_id?: string, conversation_id?: string) => {
|
||||||
if (!content || !model_config_id) return
|
if (!content || !model_config_id) return
|
||||||
updateChatList(prev => {
|
updateChatList(prev => {
|
||||||
@@ -92,6 +121,7 @@ const Chat: FC<ChatProps> = ({ chatList, data, updateChatList, handleSave, sourc
|
|||||||
return prev;
|
return prev;
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
/** Update assistant message when error occurs */
|
||||||
const updateErrorAssistantMessage = (message_length: number, model_config_id?: string) => {
|
const updateErrorAssistantMessage = (message_length: number, model_config_id?: string) => {
|
||||||
if (message_length > 0 || !model_config_id) return
|
if (message_length > 0 || !model_config_id) return
|
||||||
|
|
||||||
@@ -120,6 +150,7 @@ const Chat: FC<ChatProps> = ({ chatList, data, updateChatList, handleSave, sourc
|
|||||||
return prev
|
return prev
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
/** Send message for agent comparison mode */
|
||||||
const handleSend = () => {
|
const handleSend = () => {
|
||||||
if (loading) return
|
if (loading) return
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
@@ -176,6 +207,7 @@ const Chat: FC<ChatProps> = ({ chatList, data, updateChatList, handleSave, sourc
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Add assistant message for cluster mode */
|
||||||
const addClusterAssistantMessage = () => {
|
const addClusterAssistantMessage = () => {
|
||||||
const assistantMessage: ChatItem = {
|
const assistantMessage: ChatItem = {
|
||||||
role: 'assistant',
|
role: 'assistant',
|
||||||
@@ -187,6 +219,7 @@ const Chat: FC<ChatProps> = ({ chatList, data, updateChatList, handleSave, sourc
|
|||||||
list: [...(item.list || []), assistantMessage]
|
list: [...(item.list || []), assistantMessage]
|
||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
|
/** Update cluster assistant message with content */
|
||||||
const updateClusterAssistantMessage = (content?: string) => {
|
const updateClusterAssistantMessage = (content?: string) => {
|
||||||
if (!content) return
|
if (!content) return
|
||||||
updateChatList(prev => {
|
updateChatList(prev => {
|
||||||
@@ -209,6 +242,7 @@ const Chat: FC<ChatProps> = ({ chatList, data, updateChatList, handleSave, sourc
|
|||||||
return [...modelChatList]
|
return [...modelChatList]
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
/** Update cluster message when error occurs */
|
||||||
const updateClusterErrorAssistantMessage = (message_length: number) => {
|
const updateClusterErrorAssistantMessage = (message_length: number) => {
|
||||||
if (message_length > 0) return
|
if (message_length > 0) return
|
||||||
|
|
||||||
@@ -232,6 +266,7 @@ const Chat: FC<ChatProps> = ({ chatList, data, updateChatList, handleSave, sourc
|
|||||||
return [...modelChatList]
|
return [...modelChatList]
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
/** Send message for cluster mode */
|
||||||
const handleClusterSend = () => {
|
const handleClusterSend = () => {
|
||||||
if (loading) return
|
if (loading) return
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
@@ -291,6 +326,7 @@ const Chat: FC<ChatProps> = ({ chatList, data, updateChatList, handleSave, sourc
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Delete chat configuration from list */
|
||||||
const handleDelete = (index: number) => {
|
const handleDelete = (index: number) => {
|
||||||
updateChatList(chatList.filter((_, voIndex) => voIndex !== index))
|
updateChatList(chatList.filter((_, voIndex) => voIndex !== index))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,14 @@
|
|||||||
|
/*
|
||||||
|
* @Author: ZhaoYing
|
||||||
|
* @Date: 2026-02-03 16:27:44
|
||||||
|
* @Last Modified by: ZhaoYing
|
||||||
|
* @Last Modified time: 2026-02-03 16:27:44
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Chat Variable Configuration Modal
|
||||||
|
* Allows users to configure variable values before starting a chat session
|
||||||
|
*/
|
||||||
|
|
||||||
import { forwardRef, useImperativeHandle, useState } from 'react';
|
import { forwardRef, useImperativeHandle, useState } from 'react';
|
||||||
import { Form, Input, InputNumber } from 'antd';
|
import { Form, Input, InputNumber } from 'antd';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@@ -6,10 +17,17 @@ import type { ChatVariableConfigModalRef } from '../types'
|
|||||||
import type { Variable } from './VariableList/types'
|
import type { Variable } from './VariableList/types'
|
||||||
import RbModal from '@/components/RbModal'
|
import RbModal from '@/components/RbModal'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component props
|
||||||
|
*/
|
||||||
interface VariableEditModalProps {
|
interface VariableEditModalProps {
|
||||||
|
/** Callback to update variables */
|
||||||
refresh: (values: Variable[]) => void;
|
refresh: (values: Variable[]) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modal for configuring chat variables
|
||||||
|
*/
|
||||||
const ChatVariableConfigModal = forwardRef<ChatVariableConfigModalRef, VariableEditModalProps>(({
|
const ChatVariableConfigModal = forwardRef<ChatVariableConfigModalRef, VariableEditModalProps>(({
|
||||||
refresh,
|
refresh,
|
||||||
}, ref) => {
|
}, ref) => {
|
||||||
@@ -19,20 +37,21 @@ const ChatVariableConfigModal = forwardRef<ChatVariableConfigModalRef, VariableE
|
|||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false)
|
||||||
const [initialValues, setInitialValues] = useState<Variable[]>([])
|
const [initialValues, setInitialValues] = useState<Variable[]>([])
|
||||||
|
|
||||||
// 封装取消方法,添加关闭弹窗逻辑
|
/** Close modal and reset form */
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
setVisible(false);
|
setVisible(false);
|
||||||
form.resetFields();
|
form.resetFields();
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** Open modal with variable list */
|
||||||
const handleOpen = (values: Variable[]) => {
|
const handleOpen = (values: Variable[]) => {
|
||||||
console.log('values', values)
|
console.log('values', values)
|
||||||
setVisible(true);
|
setVisible(true);
|
||||||
form.setFieldsValue({variables: values})
|
form.setFieldsValue({variables: values})
|
||||||
setInitialValues([...values])
|
setInitialValues([...values])
|
||||||
};
|
};
|
||||||
// 封装保存方法,添加提交逻辑
|
/** Save variable configuration */
|
||||||
const handleSave = () => {
|
const handleSave = () => {
|
||||||
form.validateFields().then((values) => {
|
form.validateFields().then((values) => {
|
||||||
refresh([
|
refresh([
|
||||||
@@ -42,7 +61,7 @@ const ChatVariableConfigModal = forwardRef<ChatVariableConfigModalRef, VariableE
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 暴露给父组件的方法
|
/** Expose methods to parent component */
|
||||||
useImperativeHandle(ref, () => ({
|
useImperativeHandle(ref, () => ({
|
||||||
handleOpen,
|
handleOpen,
|
||||||
handleClose
|
handleClose
|
||||||
|
|||||||
@@ -1,8 +1,15 @@
|
|||||||
|
/*
|
||||||
|
* @Author: ZhaoYing
|
||||||
|
* @Date: 2026-02-03 16:27:52
|
||||||
|
* @Last Modified by: ZhaoYing
|
||||||
|
* @Last Modified time: 2026-02-03 16:27:52
|
||||||
|
*/
|
||||||
import { type FC, useRef } from 'react';
|
import { type FC, useRef } from 'react';
|
||||||
import { useNavigate, useParams } from 'react-router-dom';
|
import { useNavigate, useParams } from 'react-router-dom';
|
||||||
import { Layout, Tabs, Dropdown, Button, Flex } from 'antd';
|
import { Layout, Tabs, Dropdown, Button, Flex } from 'antd';
|
||||||
import type { MenuProps } from 'antd';
|
import type { MenuProps } from 'antd';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import styles from '../index.module.css'
|
import styles from '../index.module.css'
|
||||||
import logoutIcon from '@/assets/images/logout.svg'
|
import logoutIcon from '@/assets/images/logout.svg'
|
||||||
import editIcon from '@/assets/images/edit_hover.svg'
|
import editIcon from '@/assets/images/edit_hover.svg'
|
||||||
@@ -17,21 +24,43 @@ import CopyModal from './CopyModal'
|
|||||||
|
|
||||||
const { Header } = Layout;
|
const { Header } = Layout;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tab keys for application configuration
|
||||||
|
*/
|
||||||
const tabKeys = ['arrangement', 'api', 'release', 'statistics']
|
const tabKeys = ['arrangement', 'api', 'release', 'statistics']
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Menu icon mapping
|
||||||
|
*/
|
||||||
const menuIcons: Record<string, string> = {
|
const menuIcons: Record<string, string> = {
|
||||||
edit: editIcon,
|
edit: editIcon,
|
||||||
copy: copyIcon,
|
copy: copyIcon,
|
||||||
export: exportIcon,
|
export: exportIcon,
|
||||||
delete: deleteIcon
|
delete: deleteIcon
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Props for ConfigHeader component
|
||||||
|
*/
|
||||||
interface ConfigHeaderProps {
|
interface ConfigHeaderProps {
|
||||||
|
/** Application data */
|
||||||
application?: Application;
|
application?: Application;
|
||||||
|
/** Active tab key */
|
||||||
activeTab: string;
|
activeTab: string;
|
||||||
|
/** Tab change handler */
|
||||||
handleChangeTab: (key: string) => void;
|
handleChangeTab: (key: string) => void;
|
||||||
|
/** Refresh application data */
|
||||||
refresh: () => void;
|
refresh: () => void;
|
||||||
|
/** Workflow component ref */
|
||||||
workflowRef: React.RefObject<WorkflowRef>
|
workflowRef: React.RefObject<WorkflowRef>
|
||||||
|
/** App component ref (Agent/Cluster/Workflow) */
|
||||||
appRef?: React.RefObject<AgentRef | ClusterRef | WorkflowRef>
|
appRef?: React.RefObject<AgentRef | ClusterRef | WorkflowRef>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configuration header component
|
||||||
|
* Displays application name, tabs, and action buttons
|
||||||
|
*/
|
||||||
const ConfigHeader: FC<ConfigHeaderProps> = ({
|
const ConfigHeader: FC<ConfigHeaderProps> = ({
|
||||||
application, activeTab, handleChangeTab, refresh,
|
application, activeTab, handleChangeTab, refresh,
|
||||||
workflowRef,
|
workflowRef,
|
||||||
@@ -42,12 +71,18 @@ const ConfigHeader: FC<ConfigHeaderProps> = ({
|
|||||||
const applicationModalRef = useRef<ApplicationModalRef>(null);
|
const applicationModalRef = useRef<ApplicationModalRef>(null);
|
||||||
const copyModalRef = useRef<CopyModalRef>(null);
|
const copyModalRef = useRef<CopyModalRef>(null);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format tab items for display
|
||||||
|
*/
|
||||||
const formatTabItems = () => {
|
const formatTabItems = () => {
|
||||||
return tabKeys.map(key => ({
|
return tabKeys.map(key => ({
|
||||||
key,
|
key,
|
||||||
label: t(`application.${key}`),
|
label: t(`application.${key}`),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Format dropdown menu items
|
||||||
|
*/
|
||||||
const formatMenuItems = () => {
|
const formatMenuItems = () => {
|
||||||
const items = ['edit', 'copy', 'export', 'delete'].map(key => ({
|
const items = ['edit', 'copy', 'export', 'delete'].map(key => ({
|
||||||
key,
|
key,
|
||||||
@@ -59,6 +94,9 @@ const ConfigHeader: FC<ConfigHeaderProps> = ({
|
|||||||
onClick: handleClick
|
onClick: handleClick
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Handle menu item click
|
||||||
|
*/
|
||||||
const handleClick: MenuProps['onClick'] = ({ key }) => {
|
const handleClick: MenuProps['onClick'] = ({ key }) => {
|
||||||
switch (key) {
|
switch (key) {
|
||||||
case 'edit':
|
case 'edit':
|
||||||
@@ -74,6 +112,9 @@ const ConfigHeader: FC<ConfigHeaderProps> = ({
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Delete application with confirmation
|
||||||
|
*/
|
||||||
const handleDelete = () => {
|
const handleDelete = () => {
|
||||||
if (!id) {
|
if (!id) {
|
||||||
return
|
return
|
||||||
@@ -86,21 +127,36 @@ const ConfigHeader: FC<ConfigHeaderProps> = ({
|
|||||||
console.error('Failed to delete application');
|
console.error('Failed to delete application');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Navigate to application list
|
||||||
|
*/
|
||||||
const goToApplication = () => {
|
const goToApplication = () => {
|
||||||
navigate('/application', { replace: true })
|
navigate('/application', { replace: true })
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Save workflow configuration
|
||||||
|
*/
|
||||||
const save = () => {
|
const save = () => {
|
||||||
workflowRef.current?.handleSave()
|
workflowRef.current?.handleSave()
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Run workflow
|
||||||
|
*/
|
||||||
const run = () => {
|
const run = () => {
|
||||||
workflowRef.current?.handleSave(false)
|
workflowRef.current?.handleSave(false)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
workflowRef.current?.handleRun()
|
workflowRef.current?.handleRun()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Clear workflow canvas
|
||||||
|
*/
|
||||||
const clear = () => {
|
const clear = () => {
|
||||||
workflowRef?.current?.graphRef?.current?.clearCells()
|
workflowRef?.current?.graphRef?.current?.clearCells()
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Add variable to workflow
|
||||||
|
*/
|
||||||
const addvariable = () => {
|
const addvariable = () => {
|
||||||
workflowRef?.current?.addVariable()
|
workflowRef?.current?.addVariable()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,14 @@
|
|||||||
|
/*
|
||||||
|
* @Author: ZhaoYing
|
||||||
|
* @Date: 2026-02-03 16:27:56
|
||||||
|
* @Last Modified by: ZhaoYing
|
||||||
|
* @Last Modified time: 2026-02-03 16:27:56
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Copy Application Modal
|
||||||
|
* Allows users to duplicate an existing application with a new name
|
||||||
|
*/
|
||||||
|
|
||||||
import { forwardRef, useImperativeHandle, useState } from 'react';
|
import { forwardRef, useImperativeHandle, useState } from 'react';
|
||||||
import { Form, Input } from 'antd';
|
import { Form, Input } from 'antd';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@@ -10,10 +21,17 @@ import type { Application } from '@/views/ApplicationManagement/types'
|
|||||||
|
|
||||||
const FormItem = Form.Item;
|
const FormItem = Form.Item;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component props
|
||||||
|
*/
|
||||||
interface CopyModalProps {
|
interface CopyModalProps {
|
||||||
|
/** Application data to copy */
|
||||||
data: Application
|
data: Application
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modal for copying applications
|
||||||
|
*/
|
||||||
const CopyModal = forwardRef<CopyModalRef, CopyModalProps>(({
|
const CopyModal = forwardRef<CopyModalRef, CopyModalProps>(({
|
||||||
data
|
data
|
||||||
}, ref) => {
|
}, ref) => {
|
||||||
@@ -23,17 +41,18 @@ const CopyModal = forwardRef<CopyModalRef, CopyModalProps>(({
|
|||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false)
|
||||||
|
|
||||||
// 封装取消方法,添加关闭弹窗逻辑
|
/** Close modal and reset form */
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
setVisible(false);
|
setVisible(false);
|
||||||
form.resetFields();
|
form.resetFields();
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** Open modal */
|
||||||
const handleOpen = () => {
|
const handleOpen = () => {
|
||||||
setVisible(true);
|
setVisible(true);
|
||||||
};
|
};
|
||||||
// 封装保存方法,添加提交逻辑
|
/** Copy application with new name */
|
||||||
const handleSave = () => {
|
const handleSave = () => {
|
||||||
setVisible(false);
|
setVisible(false);
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
@@ -48,7 +67,7 @@ const CopyModal = forwardRef<CopyModalRef, CopyModalProps>(({
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 暴露给父组件的方法
|
/** Expose methods to parent component */
|
||||||
useImperativeHandle(ref, () => ({
|
useImperativeHandle(ref, () => ({
|
||||||
handleOpen,
|
handleOpen,
|
||||||
handleClose
|
handleClose
|
||||||
@@ -68,7 +87,7 @@ const CopyModal = forwardRef<CopyModalRef, CopyModalProps>(({
|
|||||||
form={form}
|
form={form}
|
||||||
layout="vertical"
|
layout="vertical"
|
||||||
>
|
>
|
||||||
{/* 应用名 */}
|
{/* Application name */}
|
||||||
<FormItem
|
<FormItem
|
||||||
name="new_name"
|
name="new_name"
|
||||||
label={t('application.applicationName')}
|
label={t('application.applicationName')}
|
||||||
|
|||||||
@@ -1,3 +1,14 @@
|
|||||||
|
/*
|
||||||
|
* @Author: ZhaoYing
|
||||||
|
* @Date: 2026-02-03 16:25:17
|
||||||
|
* @Last Modified by: ZhaoYing
|
||||||
|
* @Last Modified time: 2026-02-03 16:25:17
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Rich text editor component using Lexical framework
|
||||||
|
* Provides text editing with insert, append, clear, and scroll capabilities
|
||||||
|
*/
|
||||||
|
|
||||||
import {forwardRef, useImperativeHandle } from 'react';
|
import {forwardRef, useImperativeHandle } from 'react';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import { LexicalComposer } from '@lexical/react/LexicalComposer';
|
import { LexicalComposer } from '@lexical/react/LexicalComposer';
|
||||||
@@ -6,25 +17,44 @@ import { ContentEditable } from '@lexical/react/LexicalContentEditable';
|
|||||||
import { LexicalErrorBoundary } from '@lexical/react/LexicalErrorBoundary';
|
import { LexicalErrorBoundary } from '@lexical/react/LexicalErrorBoundary';
|
||||||
import { $getSelection, $getRoot, $createParagraphNode, $createTextNode, $isParagraphNode, $isTextNode } from 'lexical';
|
import { $getSelection, $getRoot, $createParagraphNode, $createTextNode, $isParagraphNode, $isTextNode } from 'lexical';
|
||||||
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
|
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
|
||||||
|
|
||||||
import InitialValuePlugin from './plugin/InitialValuePlugin'
|
import InitialValuePlugin from './plugin/InitialValuePlugin'
|
||||||
import LineBreakPlugin from './plugin/LineBreakPlugin';
|
import LineBreakPlugin from './plugin/LineBreakPlugin';
|
||||||
import InsertTextPlugin from './plugin/InsertTextPlugin';
|
import InsertTextPlugin from './plugin/InsertTextPlugin';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Editor ref methods exposed to parent components
|
||||||
|
*/
|
||||||
export interface EditorRef {
|
export interface EditorRef {
|
||||||
|
/** Insert text at current cursor position */
|
||||||
insertText: (text: string) => void;
|
insertText: (text: string) => void;
|
||||||
|
/** Append text to the end of content */
|
||||||
appendText: (text: string) => void;
|
appendText: (text: string) => void;
|
||||||
|
/** Clear all editor content */
|
||||||
clear: () => void;
|
clear: () => void;
|
||||||
|
/** Scroll editor to bottom */
|
||||||
scrollToBottom: () => void;
|
scrollToBottom: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Editor component props
|
||||||
|
*/
|
||||||
interface LexicalEditorProps {
|
interface LexicalEditorProps {
|
||||||
|
/** Additional CSS class names */
|
||||||
className?: string;
|
className?: string;
|
||||||
|
/** Placeholder text when editor is empty */
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
|
/** Initial editor value */
|
||||||
value?: string;
|
value?: string;
|
||||||
|
/** Callback when content changes */
|
||||||
onChange?: (value: string) => void;
|
onChange?: (value: string) => void;
|
||||||
|
/** Editor height in pixels */
|
||||||
height?: number;
|
height?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lexical editor theme configuration
|
||||||
|
*/
|
||||||
const theme = {
|
const theme = {
|
||||||
paragraph: 'editor-paragraph',
|
paragraph: 'editor-paragraph',
|
||||||
text: {
|
text: {
|
||||||
@@ -33,14 +63,24 @@ const theme = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Editor content component with Lexical context
|
||||||
|
*/
|
||||||
const EditorContent = forwardRef<EditorRef, LexicalEditorProps>(({
|
const EditorContent = forwardRef<EditorRef, LexicalEditorProps>(({
|
||||||
className = '',
|
className = '',
|
||||||
value,
|
value,
|
||||||
placeholder = "请输入内容...",
|
placeholder = "Please enter content...",
|
||||||
onChange,
|
onChange,
|
||||||
}, ref) => {
|
}, ref) => {
|
||||||
const [editor] = useLexicalComposerContext();
|
const [editor] = useLexicalComposerContext();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expose editor methods to parent component
|
||||||
|
* - insertText: Insert at cursor position
|
||||||
|
* - appendText: Append to end of content
|
||||||
|
* - clear: Clear all content
|
||||||
|
* - scrollToBottom: Scroll to bottom
|
||||||
|
*/
|
||||||
useImperativeHandle(ref, () => ({
|
useImperativeHandle(ref, () => ({
|
||||||
insertText: (text: string) => {
|
insertText: (text: string) => {
|
||||||
editor.update(() => {
|
editor.update(() => {
|
||||||
@@ -109,6 +149,10 @@ const EditorContent = forwardRef<EditorRef, LexicalEditorProps>(({
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main editor wrapper component
|
||||||
|
* Initializes Lexical composer with configuration
|
||||||
|
*/
|
||||||
const Editor = forwardRef<EditorRef, LexicalEditorProps>((props, ref) => {
|
const Editor = forwardRef<EditorRef, LexicalEditorProps>((props, ref) => {
|
||||||
const initialConfig = {
|
const initialConfig = {
|
||||||
namespace: 'Editor',
|
namespace: 'Editor',
|
||||||
|
|||||||
@@ -1,20 +1,34 @@
|
|||||||
|
/*
|
||||||
|
* @Author: ZhaoYing
|
||||||
|
* @Date: 2026-02-03 16:24:59
|
||||||
|
* @Last Modified by: ZhaoYing
|
||||||
|
* @Last Modified time: 2026-02-03 16:24:59
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Initial Value Plugin
|
||||||
|
* Sets the initial content of the Lexical editor
|
||||||
|
* Only updates when the value prop changes
|
||||||
|
*/
|
||||||
|
|
||||||
import { type FC, useEffect, useRef } from 'react';
|
import { type FC, useEffect, useRef } from 'react';
|
||||||
import { $getRoot, $createParagraphNode, $createTextNode } from 'lexical';
|
import { $getRoot, $createParagraphNode, $createTextNode } from 'lexical';
|
||||||
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
|
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
|
||||||
|
|
||||||
// 设置初始值的插件
|
/**
|
||||||
|
* Plugin to set initial editor value
|
||||||
|
*/
|
||||||
const InitialValuePlugin: FC<{ value?: string }> = ({ value }) => {
|
const InitialValuePlugin: FC<{ value?: string }> = ({ value }) => {
|
||||||
const [editor] = useLexicalComposerContext();
|
const [editor] = useLexicalComposerContext();
|
||||||
const lastValueRef = useRef<string | undefined>(undefined);
|
const lastValueRef = useRef<string | undefined>(undefined);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// 只有当value真正发生变化时才更新
|
// Only update when value actually changes
|
||||||
if (lastValueRef.current !== value) {
|
if (lastValueRef.current !== value) {
|
||||||
editor.update(() => {
|
editor.update(() => {
|
||||||
const root = $getRoot();
|
const root = $getRoot();
|
||||||
const currentText = root.getTextContent();
|
const currentText = root.getTextContent();
|
||||||
|
|
||||||
// 如果当前内容和新值相同,则不更新
|
// If current content matches new value, don't update
|
||||||
if (currentText === (value || '')) {
|
if (currentText === (value || '')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -26,7 +40,7 @@ const InitialValuePlugin: FC<{ value?: string }> = ({ value }) => {
|
|||||||
paragraph.append(textNode);
|
paragraph.append(textNode);
|
||||||
root.append(paragraph);
|
root.append(paragraph);
|
||||||
} else {
|
} else {
|
||||||
// 当value为undefined或空时,创建一个空段落
|
// When value is undefined or empty, create an empty paragraph
|
||||||
const paragraph = $createParagraphNode();
|
const paragraph = $createParagraphNode();
|
||||||
root.append(paragraph);
|
root.append(paragraph);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,22 @@
|
|||||||
|
/*
|
||||||
|
* @Author: ZhaoYing
|
||||||
|
* @Date: 2026-02-03 16:25:05
|
||||||
|
* @Last Modified by: ZhaoYing
|
||||||
|
* @Last Modified time: 2026-02-03 16:25:05
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Insert Text Plugin
|
||||||
|
* Provides functionality to insert text at the current cursor position
|
||||||
|
*/
|
||||||
|
|
||||||
import { forwardRef, useImperativeHandle } from 'react';
|
import { forwardRef, useImperativeHandle } from 'react';
|
||||||
import { $getSelection } from 'lexical';
|
import { $getSelection } from 'lexical';
|
||||||
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
|
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
|
||||||
import type { EditorRef } from '../index'
|
|
||||||
|
|
||||||
// 插入文本的插件
|
/**
|
||||||
const InsertTextPlugin = forwardRef<EditorRef>((_, ref) => {
|
* Plugin to insert text at cursor position
|
||||||
|
*/
|
||||||
|
const InsertTextPlugin = forwardRef<{ insertText: (text: string) => void; }>((_, ref) => {
|
||||||
const [editor] = useLexicalComposerContext();
|
const [editor] = useLexicalComposerContext();
|
||||||
|
|
||||||
useImperativeHandle(ref, () => ({
|
useImperativeHandle(ref, () => ({
|
||||||
|
|||||||
@@ -1,8 +1,22 @@
|
|||||||
|
/*
|
||||||
|
* @Author: ZhaoYing
|
||||||
|
* @Date: 2026-02-03 16:25:09
|
||||||
|
* @Last Modified by: ZhaoYing
|
||||||
|
* @Last Modified time: 2026-02-03 16:25:09
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Line Break Plugin
|
||||||
|
* Handles line breaks and triggers onChange callback when editor content changes
|
||||||
|
* Converts \n escape sequences to actual line breaks
|
||||||
|
*/
|
||||||
|
|
||||||
import { type FC, useEffect } from 'react';
|
import { type FC, useEffect } from 'react';
|
||||||
import { $getRoot } from 'lexical';
|
import { $getRoot } from 'lexical';
|
||||||
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
|
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
|
||||||
|
|
||||||
// 处理换行的插件
|
/**
|
||||||
|
* Plugin to handle line breaks and content changes
|
||||||
|
*/
|
||||||
const LineBreakPlugin: FC<{ onChange?: (value: string) => void }> = ({ onChange }) => {
|
const LineBreakPlugin: FC<{ onChange?: (value: string) => void }> = ({ onChange }) => {
|
||||||
const [editor] = useLexicalComposerContext();
|
const [editor] = useLexicalComposerContext();
|
||||||
|
|
||||||
@@ -11,7 +25,7 @@ const LineBreakPlugin: FC<{ onChange?: (value: string) => void }> = ({ onChange
|
|||||||
editorState.read(() => {
|
editorState.read(() => {
|
||||||
const root = $getRoot();
|
const root = $getRoot();
|
||||||
const textContent = root.getTextContent();
|
const textContent = root.getTextContent();
|
||||||
// 将\n转换为实际换行
|
// Convert \n to actual line breaks
|
||||||
const processedContent = textContent.replace(/\\n/g, '\n');
|
const processedContent = textContent.replace(/\\n/g, '\n');
|
||||||
onChange?.(processedContent);
|
onChange?.(processedContent);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,19 @@
|
|||||||
|
/*
|
||||||
|
* @Author: ZhaoYing
|
||||||
|
* @Date: 2026-02-03 16:25:32
|
||||||
|
* @Last Modified by: ZhaoYing
|
||||||
|
* @Last Modified time: 2026-02-03 16:25:32
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Knowledge Base Component
|
||||||
|
* Manages knowledge base associations for the application
|
||||||
|
* Allows adding, configuring, and removing knowledge bases
|
||||||
|
*/
|
||||||
|
|
||||||
import { type FC, useRef, useState, useEffect } from 'react'
|
import { type FC, useRef, useState, useEffect } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { Space, Button, List } from 'antd'
|
import { Space, Button, List } from 'antd'
|
||||||
|
|
||||||
import knowledgeEmpty from '@/assets/images/application/knowledgeEmpty.svg'
|
import knowledgeEmpty from '@/assets/images/application/knowledgeEmpty.svg'
|
||||||
import type {
|
import type {
|
||||||
KnowledgeConfigForm,
|
KnowledgeConfigForm,
|
||||||
@@ -19,6 +32,11 @@ import Tag from '@/components/Tag'
|
|||||||
import { getKnowledgeBaseList } from '@/api/knowledgeBase'
|
import { getKnowledgeBaseList } from '@/api/knowledgeBase'
|
||||||
import Card from '../Card'
|
import Card from '../Card'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Knowledge base management component
|
||||||
|
* @param value - Current knowledge configuration
|
||||||
|
* @param onChange - Callback when configuration changes
|
||||||
|
*/
|
||||||
const Knowledge: FC<{value?: KnowledgeConfig; onChange?: (config: KnowledgeConfig) => void}> = ({value = {knowledge_bases: []}, onChange}) => {
|
const Knowledge: FC<{value?: KnowledgeConfig; onChange?: (config: KnowledgeConfig) => void}> = ({value = {knowledge_bases: []}, onChange}) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const knowledgeModalRef = useRef<KnowledgeModalRef>(null)
|
const knowledgeModalRef = useRef<KnowledgeModalRef>(null)
|
||||||
@@ -32,10 +50,10 @@ const Knowledge: FC<{value?: KnowledgeConfig; onChange?: (config: KnowledgeConfi
|
|||||||
setEditConfig({ ...(value || {}) })
|
setEditConfig({ ...(value || {}) })
|
||||||
const knowledge_bases = [...(value.knowledge_bases || [])]
|
const knowledge_bases = [...(value.knowledge_bases || [])]
|
||||||
|
|
||||||
// 检查是否有knowledge_bases缺少name字段
|
// Check if knowledge_bases are missing name field
|
||||||
const basesWithoutName = knowledge_bases.filter(base => !base.name)
|
const basesWithoutName = knowledge_bases.filter(base => !base.name)
|
||||||
if (basesWithoutName.length > 0) {
|
if (basesWithoutName.length > 0) {
|
||||||
// 调用接口获取完整的知识库信息
|
// Call API to get complete knowledge base information
|
||||||
getKnowledgeBaseList().then(res => {
|
getKnowledgeBaseList().then(res => {
|
||||||
const fullBases = knowledge_bases.map(base => {
|
const fullBases = knowledge_bases.map(base => {
|
||||||
if (!base.name) {
|
if (!base.name) {
|
||||||
@@ -54,12 +72,15 @@ const Knowledge: FC<{value?: KnowledgeConfig; onChange?: (config: KnowledgeConfi
|
|||||||
}
|
}
|
||||||
}, [value])
|
}, [value])
|
||||||
|
|
||||||
|
/** Open global knowledge configuration modal */
|
||||||
const handleKnowledgeConfig = () => {
|
const handleKnowledgeConfig = () => {
|
||||||
knowledgeGlobalConfigModalRef.current?.handleOpen()
|
knowledgeGlobalConfigModalRef.current?.handleOpen()
|
||||||
}
|
}
|
||||||
|
/** Open knowledge base selection modal */
|
||||||
const handleAddKnowledge = () => {
|
const handleAddKnowledge = () => {
|
||||||
knowledgeModalRef.current?.handleOpen()
|
knowledgeModalRef.current?.handleOpen()
|
||||||
}
|
}
|
||||||
|
/** Remove knowledge base from list */
|
||||||
const handleDeleteKnowledge = (id: string) => {
|
const handleDeleteKnowledge = (id: string) => {
|
||||||
const list = knowledgeList.filter(item => item.id !== id)
|
const list = knowledgeList.filter(item => item.id !== id)
|
||||||
setKnowledgeList([...list])
|
setKnowledgeList([...list])
|
||||||
@@ -68,9 +89,11 @@ const Knowledge: FC<{value?: KnowledgeConfig; onChange?: (config: KnowledgeConfi
|
|||||||
knowledge_bases: [...list],
|
knowledge_bases: [...list],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
/** Open knowledge base configuration modal */
|
||||||
const handleEditKnowledge = (item: KnowledgeBase) => {
|
const handleEditKnowledge = (item: KnowledgeBase) => {
|
||||||
knowledgeConfigModalRef.current?.handleOpen(item)
|
knowledgeConfigModalRef.current?.handleOpen(item)
|
||||||
}
|
}
|
||||||
|
/** Update knowledge configuration */
|
||||||
const refresh = (values: KnowledgeBase[] | KnowledgeConfigForm | RerankerConfig, type: 'knowledge' | 'knowledgeConfig' | 'rerankerConfig') => {
|
const refresh = (values: KnowledgeBase[] | KnowledgeConfigForm | RerankerConfig, type: 'knowledge' | 'knowledgeConfig' | 'rerankerConfig') => {
|
||||||
if (type === 'knowledge') {
|
if (type === 'knowledge') {
|
||||||
let list = [...knowledgeList]
|
let list = [...knowledgeList]
|
||||||
|
|||||||
@@ -1,3 +1,15 @@
|
|||||||
|
/*
|
||||||
|
* @Author: ZhaoYing
|
||||||
|
* @Date: 2026-02-03 16:25:37
|
||||||
|
* @Last Modified by: ZhaoYing
|
||||||
|
* @Last Modified time: 2026-02-03 16:25:37
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Knowledge Configuration Modal
|
||||||
|
* Configures retrieval settings for individual knowledge bases
|
||||||
|
* Supports different retrieval modes: participle, semantic, and hybrid
|
||||||
|
*/
|
||||||
|
|
||||||
import { forwardRef, useEffect, useImperativeHandle, useState } from 'react';
|
import { forwardRef, useEffect, useImperativeHandle, useState } from 'react';
|
||||||
import { Form, Select, InputNumber } from 'antd';
|
import { Form, Select, InputNumber } from 'antd';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@@ -9,11 +21,22 @@ import { formatDateTime } from '@/utils/format';
|
|||||||
|
|
||||||
const FormItem = Form.Item;
|
const FormItem = Form.Item;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component props
|
||||||
|
*/
|
||||||
interface KnowledgeConfigModalProps {
|
interface KnowledgeConfigModalProps {
|
||||||
|
/** Callback to update knowledge configuration */
|
||||||
refresh: (values: KnowledgeConfigForm, type: 'knowledgeConfig') => void;
|
refresh: (values: KnowledgeConfigForm, type: 'knowledgeConfig') => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Available retrieval types
|
||||||
|
*/
|
||||||
const retrieveTypes: RetrieveType[] = ['participle', 'semantic', 'hybrid']
|
const retrieveTypes: RetrieveType[] = ['participle', 'semantic', 'hybrid']
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modal for configuring knowledge base retrieval settings
|
||||||
|
*/
|
||||||
const KnowledgeConfigModal = forwardRef<KnowledgeConfigModalRef, KnowledgeConfigModalProps>(({
|
const KnowledgeConfigModal = forwardRef<KnowledgeConfigModalRef, KnowledgeConfigModalProps>(({
|
||||||
refresh,
|
refresh,
|
||||||
}, ref) => {
|
}, ref) => {
|
||||||
@@ -24,13 +47,14 @@ const KnowledgeConfigModal = forwardRef<KnowledgeConfigModalRef, KnowledgeConfig
|
|||||||
|
|
||||||
const values = Form.useWatch<KnowledgeConfigForm>([], form);
|
const values = Form.useWatch<KnowledgeConfigForm>([], form);
|
||||||
|
|
||||||
// 封装取消方法,添加关闭弹窗逻辑
|
/** Close modal and reset form */
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
setVisible(false);
|
setVisible(false);
|
||||||
form.resetFields();
|
form.resetFields();
|
||||||
setData(null)
|
setData(null)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** Open modal with knowledge base data */
|
||||||
const handleOpen = (data: KnowledgeBase) => {
|
const handleOpen = (data: KnowledgeBase) => {
|
||||||
form.setFieldsValue({
|
form.setFieldsValue({
|
||||||
retrieve_type: data?.config?.retrieve_type || retrieveTypes[0],
|
retrieve_type: data?.config?.retrieve_type || retrieveTypes[0],
|
||||||
@@ -44,7 +68,7 @@ const KnowledgeConfigModal = forwardRef<KnowledgeConfigModalRef, KnowledgeConfig
|
|||||||
setData({...data})
|
setData({...data})
|
||||||
setVisible(true);
|
setVisible(true);
|
||||||
};
|
};
|
||||||
// 封装保存方法,添加提交逻辑
|
/** Save knowledge configuration */
|
||||||
const handleSave = () => {
|
const handleSave = () => {
|
||||||
form
|
form
|
||||||
.validateFields()
|
.validateFields()
|
||||||
@@ -57,7 +81,7 @@ const KnowledgeConfigModal = forwardRef<KnowledgeConfigModalRef, KnowledgeConfig
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 暴露给父组件的方法
|
/** Expose methods to parent component */
|
||||||
useImperativeHandle(ref, () => ({
|
useImperativeHandle(ref, () => ({
|
||||||
handleOpen,
|
handleOpen,
|
||||||
handleClose
|
handleClose
|
||||||
@@ -94,7 +118,7 @@ const KnowledgeConfigModal = forwardRef<KnowledgeConfigModalRef, KnowledgeConfig
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<FormItem name="kb_id" hidden />
|
<FormItem name="kb_id" hidden />
|
||||||
{/* 检索模式 */}
|
{/* Retrieval mode */}
|
||||||
<FormItem
|
<FormItem
|
||||||
name="retrieve_type"
|
name="retrieve_type"
|
||||||
label={t('application.retrieve_type')}
|
label={t('application.retrieve_type')}
|
||||||
@@ -124,7 +148,7 @@ const KnowledgeConfigModal = forwardRef<KnowledgeConfigModalRef, KnowledgeConfig
|
|||||||
onChange={(value) => form.setFieldValue('top_k', value)}
|
onChange={(value) => form.setFieldValue('top_k', value)}
|
||||||
/>
|
/>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
{/* 语义相似度阈值 similarity_threshold */}
|
{/* Semantic similarity threshold */}
|
||||||
{values?.retrieve_type === 'semantic' && (
|
{values?.retrieve_type === 'semantic' && (
|
||||||
<FormItem
|
<FormItem
|
||||||
name="similarity_threshold"
|
name="similarity_threshold"
|
||||||
@@ -139,7 +163,7 @@ const KnowledgeConfigModal = forwardRef<KnowledgeConfigModalRef, KnowledgeConfig
|
|||||||
/>
|
/>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
{/* 分词匹配度阈值 vector_similarity_weight */}
|
{/* Word segmentation matching threshold */}
|
||||||
{values?.retrieve_type === 'participle' && (
|
{values?.retrieve_type === 'participle' && (
|
||||||
<FormItem
|
<FormItem
|
||||||
name="vector_similarity_weight"
|
name="vector_similarity_weight"
|
||||||
@@ -154,7 +178,7 @@ const KnowledgeConfigModal = forwardRef<KnowledgeConfigModalRef, KnowledgeConfig
|
|||||||
/>
|
/>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
{/* 混合检索权重 */}
|
{/* Hybrid retrieval weight */}
|
||||||
{values?.retrieve_type === 'hybrid' && (
|
{values?.retrieve_type === 'hybrid' && (
|
||||||
<>
|
<>
|
||||||
<FormItem
|
<FormItem
|
||||||
|
|||||||
@@ -1,3 +1,14 @@
|
|||||||
|
/*
|
||||||
|
* @Author: ZhaoYing
|
||||||
|
* @Date: 2026-02-03 16:25:42
|
||||||
|
* @Last Modified by: ZhaoYing
|
||||||
|
* @Last Modified time: 2026-02-03 16:25:42
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Knowledge Global Configuration Modal
|
||||||
|
* Configures global reranker settings for all knowledge bases
|
||||||
|
*/
|
||||||
|
|
||||||
import { forwardRef, useImperativeHandle, useState, useEffect } from 'react';
|
import { forwardRef, useImperativeHandle, useState, useEffect } from 'react';
|
||||||
import { Form, InputNumber, Switch } from 'antd';
|
import { Form, InputNumber, Switch } from 'antd';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@@ -9,11 +20,19 @@ import { getModelListUrl } from '@/api/models'
|
|||||||
|
|
||||||
const FormItem = Form.Item;
|
const FormItem = Form.Item;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component props
|
||||||
|
*/
|
||||||
interface KnowledgeGlobalConfigModalProps {
|
interface KnowledgeGlobalConfigModalProps {
|
||||||
|
/** Current reranker configuration */
|
||||||
data: RerankerConfig;
|
data: RerankerConfig;
|
||||||
|
/** Callback to update reranker configuration */
|
||||||
refresh: (values: RerankerConfig, type: 'rerankerConfig') => void;
|
refresh: (values: RerankerConfig, type: 'rerankerConfig') => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modal for configuring global reranker settings
|
||||||
|
*/
|
||||||
const KnowledgeGlobalConfigModal = forwardRef<KnowledgeGlobalConfigModalRef, KnowledgeGlobalConfigModalProps>(({
|
const KnowledgeGlobalConfigModal = forwardRef<KnowledgeGlobalConfigModalRef, KnowledgeGlobalConfigModalProps>(({
|
||||||
refresh,
|
refresh,
|
||||||
data,
|
data,
|
||||||
@@ -23,17 +42,18 @@ const KnowledgeGlobalConfigModal = forwardRef<KnowledgeGlobalConfigModalRef, Kno
|
|||||||
const [form] = Form.useForm<RerankerConfig>();
|
const [form] = Form.useForm<RerankerConfig>();
|
||||||
const values = Form.useWatch<RerankerConfig>([], form);
|
const values = Form.useWatch<RerankerConfig>([], form);
|
||||||
|
|
||||||
// 封装取消方法,添加关闭弹窗逻辑
|
/** Close modal and reset form */
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
setVisible(false);
|
setVisible(false);
|
||||||
form.resetFields();
|
form.resetFields();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** Open modal with current configuration */
|
||||||
const handleOpen = () => {
|
const handleOpen = () => {
|
||||||
form.setFieldsValue({ ...data, rerank_model: !!data?.reranker_id })
|
form.setFieldsValue({ ...data, rerank_model: !!data?.reranker_id })
|
||||||
setVisible(true);
|
setVisible(true);
|
||||||
};
|
};
|
||||||
// 封装保存方法,添加提交逻辑
|
/** Save reranker configuration */
|
||||||
const handleSave = () => {
|
const handleSave = () => {
|
||||||
form
|
form
|
||||||
.validateFields()
|
.validateFields()
|
||||||
@@ -54,7 +74,7 @@ const KnowledgeGlobalConfigModal = forwardRef<KnowledgeGlobalConfigModalRef, Kno
|
|||||||
}
|
}
|
||||||
}, [values?.rerank_model])
|
}, [values?.rerank_model])
|
||||||
|
|
||||||
// 暴露给父组件的方法
|
/** Expose methods to parent component */
|
||||||
useImperativeHandle(ref, () => ({
|
useImperativeHandle(ref, () => ({
|
||||||
handleOpen,
|
handleOpen,
|
||||||
}));
|
}));
|
||||||
@@ -73,7 +93,7 @@ const KnowledgeGlobalConfigModal = forwardRef<KnowledgeGlobalConfigModalRef, Kno
|
|||||||
>
|
>
|
||||||
<div className="rb:text-[#5B6167] rb:mb-6">{t('application.globalConfigDesc')}</div>
|
<div className="rb:text-[#5B6167] rb:mb-6">{t('application.globalConfigDesc')}</div>
|
||||||
|
|
||||||
{/* 结果重排 */}
|
{/* Result reranking */}
|
||||||
<div className="rb:flex rb:items-center rb:justify-between rb:my-6">
|
<div className="rb:flex rb:items-center rb:justify-between rb:my-6">
|
||||||
<div className="rb:text-[14px] rb:font-medium rb:leading-5">
|
<div className="rb:text-[14px] rb:font-medium rb:leading-5">
|
||||||
{t('application.rerankModel')}
|
{t('application.rerankModel')}
|
||||||
|
|||||||
@@ -1,7 +1,19 @@
|
|||||||
|
/*
|
||||||
|
* @Author: ZhaoYing
|
||||||
|
* @Date: 2026-02-03 16:25:49
|
||||||
|
* @Last Modified by: ZhaoYing
|
||||||
|
* @Last Modified time: 2026-02-03 16:25:49
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Knowledge List Modal
|
||||||
|
* Displays and allows selection of knowledge bases to associate with the application
|
||||||
|
*/
|
||||||
|
|
||||||
import { forwardRef, useEffect, useImperativeHandle, useState } from 'react';
|
import { forwardRef, useEffect, useImperativeHandle, useState } from 'react';
|
||||||
import { Space, List } from 'antd';
|
import { Space, List } from 'antd';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import clsx from 'clsx'
|
import clsx from 'clsx'
|
||||||
|
|
||||||
import type { KnowledgeModalRef, KnowledgeBase } from './types'
|
import type { KnowledgeModalRef, KnowledgeBase } from './types'
|
||||||
import type { KnowledgeBaseListItem } from '@/views/KnowledgeBase/types'
|
import type { KnowledgeBaseListItem } from '@/views/KnowledgeBase/types'
|
||||||
import RbModal from '@/components/RbModal'
|
import RbModal from '@/components/RbModal'
|
||||||
@@ -9,11 +21,19 @@ import { getKnowledgeBaseList } from '@/api/knowledgeBase'
|
|||||||
import SearchInput from '@/components/SearchInput'
|
import SearchInput from '@/components/SearchInput'
|
||||||
import Empty from '@/components/Empty'
|
import Empty from '@/components/Empty'
|
||||||
import { formatDateTime } from '@/utils/format';
|
import { formatDateTime } from '@/utils/format';
|
||||||
|
/**
|
||||||
|
* Component props
|
||||||
|
*/
|
||||||
interface KnowledgeModalProps {
|
interface KnowledgeModalProps {
|
||||||
|
/** Callback to add selected knowledge bases */
|
||||||
refresh: (rows: KnowledgeBase[], type: 'knowledge') => void;
|
refresh: (rows: KnowledgeBase[], type: 'knowledge') => void;
|
||||||
|
/** Currently selected knowledge bases */
|
||||||
selectedList: KnowledgeBase[];
|
selectedList: KnowledgeBase[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modal for selecting knowledge bases
|
||||||
|
*/
|
||||||
const KnowledgeListModal = forwardRef<KnowledgeModalRef, KnowledgeModalProps>(({
|
const KnowledgeListModal = forwardRef<KnowledgeModalRef, KnowledgeModalProps>(({
|
||||||
refresh,
|
refresh,
|
||||||
selectedList
|
selectedList
|
||||||
@@ -26,7 +46,7 @@ const KnowledgeListModal = forwardRef<KnowledgeModalRef, KnowledgeModalProps>(({
|
|||||||
const [selectedIds, setSelectedIds] = useState<string[]>([])
|
const [selectedIds, setSelectedIds] = useState<string[]>([])
|
||||||
const [selectedRows, setSelectedRows] = useState<KnowledgeBase[]>([])
|
const [selectedRows, setSelectedRows] = useState<KnowledgeBase[]>([])
|
||||||
|
|
||||||
// 封装取消方法,添加关闭弹窗逻辑
|
/** Close modal and reset state */
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
setVisible(false);
|
setVisible(false);
|
||||||
setQuery({})
|
setQuery({})
|
||||||
@@ -34,6 +54,7 @@ const KnowledgeListModal = forwardRef<KnowledgeModalRef, KnowledgeModalProps>(({
|
|||||||
setSelectedRows([])
|
setSelectedRows([])
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** Open modal */
|
||||||
const handleOpen = () => {
|
const handleOpen = () => {
|
||||||
setVisible(true);
|
setVisible(true);
|
||||||
setQuery({})
|
setQuery({})
|
||||||
@@ -46,6 +67,7 @@ const KnowledgeListModal = forwardRef<KnowledgeModalRef, KnowledgeModalProps>(({
|
|||||||
getList()
|
getList()
|
||||||
}
|
}
|
||||||
}, [query.keywords, visible])
|
}, [query.keywords, visible])
|
||||||
|
/** Fetch knowledge base list */
|
||||||
const getList = () => {
|
const getList = () => {
|
||||||
getKnowledgeBaseList(undefined, {
|
getKnowledgeBaseList(undefined, {
|
||||||
...query,
|
...query,
|
||||||
@@ -58,7 +80,7 @@ const KnowledgeListModal = forwardRef<KnowledgeModalRef, KnowledgeModalProps>(({
|
|||||||
setList(response.items || [])
|
setList(response.items || [])
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
// 封装保存方法,添加提交逻辑
|
/** Save selected knowledge bases */
|
||||||
const handleSave = () => {
|
const handleSave = () => {
|
||||||
refresh(selectedRows.map(item => ({
|
refresh(selectedRows.map(item => ({
|
||||||
...item,
|
...item,
|
||||||
@@ -72,16 +94,18 @@ const KnowledgeListModal = forwardRef<KnowledgeModalRef, KnowledgeModalProps>(({
|
|||||||
setVisible(false);
|
setVisible(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 暴露给父组件的方法
|
/** Expose methods to parent component */
|
||||||
useImperativeHandle(ref, () => ({
|
useImperativeHandle(ref, () => ({
|
||||||
handleOpen,
|
handleOpen,
|
||||||
handleClose
|
handleClose
|
||||||
}));
|
}));
|
||||||
|
/** Search knowledge bases */
|
||||||
const handleSearch = (value?: string) => {
|
const handleSearch = (value?: string) => {
|
||||||
setQuery({keywords: value})
|
setQuery({keywords: value})
|
||||||
setSelectedIds([])
|
setSelectedIds([])
|
||||||
setSelectedRows([])
|
setSelectedRows([])
|
||||||
}
|
}
|
||||||
|
/** Toggle knowledge base selection */
|
||||||
const handleSelect = (item: KnowledgeBase) => {
|
const handleSelect = (item: KnowledgeBase) => {
|
||||||
const index = selectedIds.indexOf(item.id)
|
const index = selectedIds.indexOf(item.id)
|
||||||
if (index === -1) {
|
if (index === -1) {
|
||||||
|
|||||||
@@ -1,30 +1,87 @@
|
|||||||
|
/*
|
||||||
|
* @Author: ZhaoYing
|
||||||
|
* @Date: 2026-02-03 16:25:53
|
||||||
|
* @Last Modified by: ZhaoYing
|
||||||
|
* @Last Modified time: 2026-02-03 16:25:53
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Type definitions for knowledge base configuration in application settings
|
||||||
|
*/
|
||||||
|
|
||||||
import type { KnowledgeBaseListItem } from '@/views/KnowledgeBase/types'
|
import type { KnowledgeBaseListItem } from '@/views/KnowledgeBase/types'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reranker configuration for knowledge retrieval
|
||||||
|
*/
|
||||||
export interface RerankerConfig {
|
export interface RerankerConfig {
|
||||||
|
/** Whether to enable rerank model */
|
||||||
rerank_model?: boolean | undefined;
|
rerank_model?: boolean | undefined;
|
||||||
|
/** Reranker model ID */
|
||||||
reranker_id?: string | undefined;
|
reranker_id?: string | undefined;
|
||||||
|
/** Top K results for reranking */
|
||||||
reranker_top_k?: number | undefined;
|
reranker_top_k?: number | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Knowledge retrieval type
|
||||||
|
* - participle: Word segmentation based retrieval
|
||||||
|
* - semantic: Semantic similarity based retrieval
|
||||||
|
* - hybrid: Combination of both methods
|
||||||
|
*/
|
||||||
export type RetrieveType = 'participle' | 'semantic' | 'hybrid'
|
export type RetrieveType = 'participle' | 'semantic' | 'hybrid'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Knowledge base configuration form data
|
||||||
|
*/
|
||||||
export interface KnowledgeConfigForm {
|
export interface KnowledgeConfigForm {
|
||||||
|
/** Knowledge base ID */
|
||||||
kb_id?: string;
|
kb_id?: string;
|
||||||
|
/** Similarity threshold for retrieval (0-1) */
|
||||||
similarity_threshold?: number;
|
similarity_threshold?: number;
|
||||||
|
/** Weight for vector similarity in hybrid mode (0-1) */
|
||||||
vector_similarity_weight?: number;
|
vector_similarity_weight?: number;
|
||||||
|
/** Number of top results to retrieve */
|
||||||
top_k?: number;
|
top_k?: number;
|
||||||
|
/** Retrieval strategy type */
|
||||||
retrieve_type?: RetrieveType;
|
retrieve_type?: RetrieveType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Knowledge base with configuration
|
||||||
|
*/
|
||||||
export interface KnowledgeBase extends KnowledgeBaseListItem, KnowledgeConfigForm {
|
export interface KnowledgeBase extends KnowledgeBaseListItem, KnowledgeConfigForm {
|
||||||
|
/** Additional configuration object */
|
||||||
config?: KnowledgeConfigForm
|
config?: KnowledgeConfigForm
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Complete knowledge configuration including reranker settings
|
||||||
|
*/
|
||||||
export interface KnowledgeConfig extends RerankerConfig {
|
export interface KnowledgeConfig extends RerankerConfig {
|
||||||
|
/** List of configured knowledge bases */
|
||||||
knowledge_bases: KnowledgeBase[];
|
knowledge_bases: KnowledgeBase[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modal ref for individual knowledge base configuration
|
||||||
|
*/
|
||||||
export interface KnowledgeConfigModalRef {
|
export interface KnowledgeConfigModalRef {
|
||||||
|
/** Open modal with knowledge base data */
|
||||||
handleOpen: (data: KnowledgeBase) => void;
|
handleOpen: (data: KnowledgeBase) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modal ref for global knowledge configuration
|
||||||
|
*/
|
||||||
export interface KnowledgeGlobalConfigModalRef {
|
export interface KnowledgeGlobalConfigModalRef {
|
||||||
|
/** Open global configuration modal */
|
||||||
handleOpen: () => void;
|
handleOpen: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modal ref for knowledge base selection
|
||||||
|
*/
|
||||||
export interface KnowledgeModalRef {
|
export interface KnowledgeModalRef {
|
||||||
|
/** Open modal with optional existing configuration */
|
||||||
handleOpen: (config?: KnowledgeConfig[]) => void;
|
handleOpen: (config?: KnowledgeConfig[]) => void;
|
||||||
}
|
}
|
||||||
@@ -1,18 +1,38 @@
|
|||||||
|
/*
|
||||||
|
* @Author: ZhaoYing
|
||||||
|
* @Date: 2026-02-03 16:28:03
|
||||||
|
* @Last Modified by: ZhaoYing
|
||||||
|
* @Last Modified time: 2026-02-03 16:28:03
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Line chart card component for displaying statistics
|
||||||
|
* Uses ECharts to render time-series data with gradient area fill
|
||||||
|
*/
|
||||||
|
|
||||||
import { type FC, useEffect, useRef } from 'react'
|
import { type FC, useEffect, useRef } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import ReactEcharts from 'echarts-for-react';
|
import ReactEcharts from 'echarts-for-react';
|
||||||
import * as echarts from 'echarts';
|
import * as echarts from 'echarts';
|
||||||
import Empty from '@/components/Empty'
|
|
||||||
|
|
||||||
|
import Empty from '@/components/Empty'
|
||||||
import Card from './Card'
|
import Card from './Card'
|
||||||
import type { StatisticsItem } from '../types'
|
import type { StatisticsItem } from '../types'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component props
|
||||||
|
*/
|
||||||
interface LineCardProps {
|
interface LineCardProps {
|
||||||
|
/** Chart data points */
|
||||||
chartData: StatisticsItem[];
|
chartData: StatisticsItem[];
|
||||||
|
/** Statistics type key */
|
||||||
type: string;
|
type: string;
|
||||||
|
/** Total count to display */
|
||||||
total: number;
|
total: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ECharts series configuration
|
||||||
|
*/
|
||||||
const SeriesConfig = {
|
const SeriesConfig = {
|
||||||
type: 'line',
|
type: 'line',
|
||||||
stack: 'Total',
|
stack: 'Total',
|
||||||
@@ -30,6 +50,9 @@ const SeriesConfig = {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Color mapping for different statistic types
|
||||||
|
*/
|
||||||
const ColorObj: Record<string, string> = {
|
const ColorObj: Record<string, string> = {
|
||||||
daily_conversations: '#FFB048',
|
daily_conversations: '#FFB048',
|
||||||
daily_new_users: '#4DA8FF',
|
daily_new_users: '#4DA8FF',
|
||||||
@@ -37,6 +60,10 @@ const ColorObj: Record<string, string> = {
|
|||||||
daily_tokens: '#AD88FF'
|
daily_tokens: '#AD88FF'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Line chart card component
|
||||||
|
* Displays time-series statistics with gradient area chart
|
||||||
|
*/
|
||||||
const LineCard: FC<LineCardProps> = ({ chartData, type, total }) => {
|
const LineCard: FC<LineCardProps> = ({ chartData, type, total }) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const chartRef = useRef<ReactEcharts>(null);
|
const chartRef = useRef<ReactEcharts>(null);
|
||||||
|
|||||||
@@ -1,3 +1,15 @@
|
|||||||
|
/*
|
||||||
|
* @Author: ZhaoYing
|
||||||
|
* @Date: 2026-02-03 16:28:07
|
||||||
|
* @Last Modified by: ZhaoYing
|
||||||
|
* @Last Modified time: 2026-02-03 16:28:07
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Model Configuration Modal
|
||||||
|
* Allows configuring model parameters like temperature, max_tokens, top_p, etc.
|
||||||
|
* Supports different sources: model, chat, and multi_agent
|
||||||
|
*/
|
||||||
|
|
||||||
import { forwardRef, useImperativeHandle, useState, useEffect } from 'react';
|
import { forwardRef, useImperativeHandle, useState, useEffect } from 'react';
|
||||||
import { Form, Select } from 'antd';
|
import { Form, Select } from 'antd';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@@ -9,12 +21,24 @@ import RbSlider from '@/components/RbSlider'
|
|||||||
|
|
||||||
const FormItem = Form.Item;
|
const FormItem = Form.Item;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component props
|
||||||
|
*/
|
||||||
interface ModelConfigModalProps {
|
interface ModelConfigModalProps {
|
||||||
|
/** List of available models */
|
||||||
modelList?: ModelListItem[];
|
modelList?: ModelListItem[];
|
||||||
|
/** Callback to update model configuration */
|
||||||
refresh: (values: ModelConfig, type: Source) => void;
|
refresh: (values: ModelConfig, type: Source) => void;
|
||||||
|
/** Application configuration data */
|
||||||
data: Config;
|
data: Config;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modal for configuring model parameters
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Model parameter configuration fields
|
||||||
|
*/
|
||||||
const configFields = [
|
const configFields = [
|
||||||
{ key: 'temperature', max: 2, min: 0, step: 0.1, defaultValue: 0.7 },
|
{ key: 'temperature', max: 2, min: 0, step: 0.1, defaultValue: 0.7 },
|
||||||
{ key: 'max_tokens', max: 32000, min: 256, step: 1, defaultValue: 2000 },
|
{ key: 'max_tokens', max: 32000, min: 256, step: 1, defaultValue: 2000 },
|
||||||
@@ -36,12 +60,13 @@ const ModelConfigModal = forwardRef<ModelConfigModalRef, ModelConfigModalProps>(
|
|||||||
|
|
||||||
const values = Form.useWatch([], form);
|
const values = Form.useWatch([], form);
|
||||||
|
|
||||||
// 封装取消方法,添加关闭弹窗逻辑
|
/** Close modal and reset form */
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
setVisible(false);
|
setVisible(false);
|
||||||
form.resetFields();
|
form.resetFields();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** Open modal with configuration source */
|
||||||
const handleOpen = (source: Source, model?: any) => {
|
const handleOpen = (source: Source, model?: any) => {
|
||||||
setSource(source)
|
setSource(source)
|
||||||
if (source === 'model') {
|
if (source === 'model') {
|
||||||
@@ -64,7 +89,7 @@ const ModelConfigModal = forwardRef<ModelConfigModalRef, ModelConfigModalProps>(
|
|||||||
}
|
}
|
||||||
setVisible(true);
|
setVisible(true);
|
||||||
};
|
};
|
||||||
// 封装保存方法,添加提交逻辑
|
/** Save model configuration */
|
||||||
const handleSave = () => {
|
const handleSave = () => {
|
||||||
form
|
form
|
||||||
.validateFields()
|
.validateFields()
|
||||||
@@ -76,13 +101,14 @@ const ModelConfigModal = forwardRef<ModelConfigModalRef, ModelConfigModalProps>(
|
|||||||
console.log('err', err)
|
console.log('err', err)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
/** Handle model selection change */
|
||||||
const handleChange = (_value: string, option: ModelListItem | ModelListItem[] | undefined) => {
|
const handleChange = (_value: string, option: ModelListItem | ModelListItem[] | undefined) => {
|
||||||
if (source === 'chat') {
|
if (source === 'chat') {
|
||||||
form.setFieldValue('label', (option as ModelListItem).name)
|
form.setFieldValue('label', (option as ModelListItem).name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 暴露给父组件的方法
|
/** Expose methods to parent component */
|
||||||
useImperativeHandle(ref, () => ({
|
useImperativeHandle(ref, () => ({
|
||||||
handleOpen,
|
handleOpen,
|
||||||
handleClose
|
handleClose
|
||||||
|
|||||||
@@ -1,3 +1,14 @@
|
|||||||
|
/*
|
||||||
|
* @Author: ZhaoYing
|
||||||
|
* @Date: 2026-02-03 16:28:11
|
||||||
|
* @Last Modified by: ZhaoYing
|
||||||
|
* @Last Modified time: 2026-02-03 16:28:11
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Release Modal
|
||||||
|
* Allows publishing a new version of the application
|
||||||
|
*/
|
||||||
|
|
||||||
import { forwardRef, useImperativeHandle, useState } from 'react';
|
import { forwardRef, useImperativeHandle, useState } from 'react';
|
||||||
import { Form, Input } from 'antd';
|
import { Form, Input } from 'antd';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@@ -9,11 +20,19 @@ import type { Application } from '@/views/ApplicationManagement/types'
|
|||||||
|
|
||||||
const FormItem = Form.Item;
|
const FormItem = Form.Item;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component props
|
||||||
|
*/
|
||||||
interface ReleaseModalProps {
|
interface ReleaseModalProps {
|
||||||
|
/** Callback to refresh release list */
|
||||||
refreshTable: () => void;
|
refreshTable: () => void;
|
||||||
|
/** Application data */
|
||||||
data: Application
|
data: Application
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modal for publishing new application versions
|
||||||
|
*/
|
||||||
const ReleaseModal = forwardRef<ReleaseModalRef, ReleaseModalProps>(({
|
const ReleaseModal = forwardRef<ReleaseModalRef, ReleaseModalProps>(({
|
||||||
refreshTable,
|
refreshTable,
|
||||||
data
|
data
|
||||||
@@ -23,17 +42,18 @@ const ReleaseModal = forwardRef<ReleaseModalRef, ReleaseModalProps>(({
|
|||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false)
|
||||||
|
|
||||||
// 封装取消方法,添加关闭弹窗逻辑
|
/** Close modal and reset form */
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
setVisible(false);
|
setVisible(false);
|
||||||
form.resetFields();
|
form.resetFields();
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** Open modal */
|
||||||
const handleOpen = () => {
|
const handleOpen = () => {
|
||||||
setVisible(true);
|
setVisible(true);
|
||||||
};
|
};
|
||||||
// 封装保存方法,添加提交逻辑
|
/** Publish new release */
|
||||||
const handleSave = () => {
|
const handleSave = () => {
|
||||||
form.validateFields().then(() => {
|
form.validateFields().then(() => {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
@@ -49,7 +69,7 @@ const ReleaseModal = forwardRef<ReleaseModalRef, ReleaseModalProps>(({
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 暴露给父组件的方法
|
/** Expose methods to parent component */
|
||||||
useImperativeHandle(ref, () => ({
|
useImperativeHandle(ref, () => ({
|
||||||
handleOpen,
|
handleOpen,
|
||||||
handleClose
|
handleClose
|
||||||
@@ -69,7 +89,7 @@ const ReleaseModal = forwardRef<ReleaseModalRef, ReleaseModalProps>(({
|
|||||||
form={form}
|
form={form}
|
||||||
layout="vertical"
|
layout="vertical"
|
||||||
>
|
>
|
||||||
{/* 版本名 */}
|
{/* Version name */}
|
||||||
<FormItem
|
<FormItem
|
||||||
name="version_name"
|
name="version_name"
|
||||||
label={t('application.versionName')}
|
label={t('application.versionName')}
|
||||||
@@ -80,7 +100,7 @@ const ReleaseModal = forwardRef<ReleaseModalRef, ReleaseModalProps>(({
|
|||||||
>
|
>
|
||||||
<Input placeholder={t('common.enter')} />
|
<Input placeholder={t('common.enter')} />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
{/* 版本描述 */}
|
{/* Version description */}
|
||||||
<FormItem
|
<FormItem
|
||||||
name="release_notes"
|
name="release_notes"
|
||||||
label={t('application.versionDescription')}
|
label={t('application.versionDescription')}
|
||||||
|
|||||||
@@ -1,3 +1,14 @@
|
|||||||
|
/*
|
||||||
|
* @Author: ZhaoYing
|
||||||
|
* @Date: 2026-02-03 16:28:46
|
||||||
|
* @Last Modified by: ZhaoYing
|
||||||
|
* @Last Modified time: 2026-02-03 16:28:46
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Release Share Modal
|
||||||
|
* Generates and displays a shareable link for a specific application version
|
||||||
|
*/
|
||||||
|
|
||||||
import { forwardRef, useImperativeHandle, useState } from 'react';
|
import { forwardRef, useImperativeHandle, useState } from 'react';
|
||||||
import { Button, App } from 'antd';
|
import { Button, App } from 'antd';
|
||||||
import { ExclamationCircleFilled } from '@ant-design/icons';
|
import { ExclamationCircleFilled } from '@ant-design/icons';
|
||||||
@@ -9,10 +20,17 @@ import RbModal from '@/components/RbModal'
|
|||||||
import { shareRelease } from '@/api/application'
|
import { shareRelease } from '@/api/application'
|
||||||
import RbAlert from '@/components/RbAlert'
|
import RbAlert from '@/components/RbAlert'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component props
|
||||||
|
*/
|
||||||
interface ReleaseShareModalProps {
|
interface ReleaseShareModalProps {
|
||||||
|
/** Version to share */
|
||||||
version: Release | null
|
version: Release | null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modal for sharing application versions
|
||||||
|
*/
|
||||||
const ReleaseShareModal = forwardRef<ReleaseShareModalRef, ReleaseShareModalProps>(({
|
const ReleaseShareModal = forwardRef<ReleaseShareModalRef, ReleaseShareModalProps>(({
|
||||||
version
|
version
|
||||||
}, ref) => {
|
}, ref) => {
|
||||||
@@ -22,12 +40,13 @@ const ReleaseShareModal = forwardRef<ReleaseShareModalRef, ReleaseShareModalProp
|
|||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false)
|
||||||
const [shareLink, setShareLink] = useState<string | null>(null)
|
const [shareLink, setShareLink] = useState<string | null>(null)
|
||||||
|
|
||||||
// 封装取消方法,添加关闭弹窗逻辑
|
/** Close modal */
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
setVisible(false);
|
setVisible(false);
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** Open modal and generate share link */
|
||||||
const handleOpen = () => {
|
const handleOpen = () => {
|
||||||
if (!version) {
|
if (!version) {
|
||||||
return
|
return
|
||||||
@@ -45,13 +64,14 @@ const ReleaseShareModal = forwardRef<ReleaseShareModalRef, ReleaseShareModalProp
|
|||||||
setLoading(false)
|
setLoading(false)
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
/** Copy share link to clipboard */
|
||||||
const handleCopy = () => {
|
const handleCopy = () => {
|
||||||
if (!shareLink) return
|
if (!shareLink) return
|
||||||
copy(shareLink)
|
copy(shareLink)
|
||||||
message.success(t('common.copySuccess'))
|
message.success(t('common.copySuccess'))
|
||||||
}
|
}
|
||||||
|
|
||||||
// 暴露给父组件的方法
|
/** Expose methods to parent component */
|
||||||
useImperativeHandle(ref, () => ({
|
useImperativeHandle(ref, () => ({
|
||||||
handleOpen,
|
handleOpen,
|
||||||
handleClose
|
handleClose
|
||||||
@@ -65,9 +85,9 @@ const ReleaseShareModal = forwardRef<ReleaseShareModalRef, ReleaseShareModalProp
|
|||||||
footer={false}
|
footer={false}
|
||||||
>
|
>
|
||||||
<>
|
<>
|
||||||
<div className="rb:leading-[20px] rb:mb-[8px]">{t('application.shareLink')}</div>
|
<div className="rb:leading-5 rb:mb-2">{t('application.shareLink')}</div>
|
||||||
<div className="rb:mb-[12px] rb:flex rb:items-center rb:gap-[10px] rb:justify-between">
|
<div className="rb:mb-3 rb:flex rb:items-center rb:gap-2.5 rb:justify-between">
|
||||||
<div className="rb:overflow-hidden rb:whitespace-nowrap rb:text-ellipsis rb:cursor-pointer rb:h-[32px] rb:p-[6px_10px] rb:bg-[#FFFFFF] rb:border rb:border-[#EBEBEB] rb:rounded-[6px] rb:leading-[20px]">{shareLink}</div>
|
<div className="rb:overflow-hidden rb:whitespace-nowrap rb:text-ellipsis rb:cursor-pointer rb:h-8 rb:p-[6px_10px] rb:bg-[#FFFFFF] rb:border rb:border-[#EBEBEB] rb:rounded-md rb:leading-5">{shareLink}</div>
|
||||||
|
|
||||||
<Button type="primary" loading={loading} disabled={!shareLink} onClick={handleCopy}>
|
<Button type="primary" loading={loading} disabled={!shareLink} onClick={handleCopy}>
|
||||||
{t('common.copy')}
|
{t('common.copy')}
|
||||||
|
|||||||
@@ -1,3 +1,14 @@
|
|||||||
|
/*
|
||||||
|
* @Author: ZhaoYing
|
||||||
|
* @Date: 2026-02-03 16:28:51
|
||||||
|
* @Last Modified by: ZhaoYing
|
||||||
|
* @Last Modified time: 2026-02-03 16:28:51
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Sub-Agent Modal
|
||||||
|
* Allows adding or editing sub-agents in multi-agent cluster configuration
|
||||||
|
*/
|
||||||
|
|
||||||
import { forwardRef, useImperativeHandle, useState, type Key } from 'react';
|
import { forwardRef, useImperativeHandle, useState, type Key } from 'react';
|
||||||
import { Form, Select, Input } from 'antd';
|
import { Form, Select, Input } from 'antd';
|
||||||
import type { DefaultOptionType } from 'antd/es/select'
|
import type { DefaultOptionType } from 'antd/es/select'
|
||||||
@@ -10,10 +21,17 @@ import { getApplicationListUrl } from '@/api/application';
|
|||||||
|
|
||||||
const FormItem = Form.Item;
|
const FormItem = Form.Item;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component props
|
||||||
|
*/
|
||||||
interface SubAgentModalProps {
|
interface SubAgentModalProps {
|
||||||
|
/** Callback to update sub-agent */
|
||||||
refresh: (agent: SubAgentItem) => void;
|
refresh: (agent: SubAgentItem) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modal for managing sub-agents
|
||||||
|
*/
|
||||||
const SubAgentModal = forwardRef<SubAgentModalRef, SubAgentModalProps>(({
|
const SubAgentModal = forwardRef<SubAgentModalRef, SubAgentModalProps>(({
|
||||||
refresh,
|
refresh,
|
||||||
}, ref) => {
|
}, ref) => {
|
||||||
@@ -24,19 +42,20 @@ const SubAgentModal = forwardRef<SubAgentModalRef, SubAgentModalProps>(({
|
|||||||
const [editVo, setEditVo] = useState<SubAgentItem>()
|
const [editVo, setEditVo] = useState<SubAgentItem>()
|
||||||
const values = Form.useWatch([], form)
|
const values = Form.useWatch([], form)
|
||||||
|
|
||||||
// 封装取消方法,添加关闭弹窗逻辑
|
/** Close modal and reset form */
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
setVisible(false);
|
setVisible(false);
|
||||||
form.resetFields();
|
form.resetFields();
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** Open modal with optional agent data */
|
||||||
const handleOpen = (agent?: SubAgentItem) => {
|
const handleOpen = (agent?: SubAgentItem) => {
|
||||||
setVisible(true);
|
setVisible(true);
|
||||||
form.setFieldsValue(agent)
|
form.setFieldsValue(agent)
|
||||||
setEditVo(agent)
|
setEditVo(agent)
|
||||||
};
|
};
|
||||||
// 封装保存方法,添加提交逻辑
|
/** Save sub-agent configuration */
|
||||||
const handleSave = () => {
|
const handleSave = () => {
|
||||||
form.validateFields().then(() => {
|
form.validateFields().then(() => {
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
@@ -47,6 +66,7 @@ const SubAgentModal = forwardRef<SubAgentModalRef, SubAgentModalProps>(({
|
|||||||
handleClose()
|
handleClose()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
/** Handle agent selection change */
|
||||||
const handleChange = (value: Key, option?: DefaultOptionType | DefaultOptionType[] | undefined) => {
|
const handleChange = (value: Key, option?: DefaultOptionType | DefaultOptionType[] | undefined) => {
|
||||||
console.log(value, option)
|
console.log(value, option)
|
||||||
if (option && !Array.isArray(option)) {
|
if (option && !Array.isArray(option)) {
|
||||||
@@ -54,7 +74,7 @@ const SubAgentModal = forwardRef<SubAgentModalRef, SubAgentModalProps>(({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 暴露给父组件的方法
|
/** Expose methods to parent component */
|
||||||
useImperativeHandle(ref, () => ({
|
useImperativeHandle(ref, () => ({
|
||||||
handleOpen,
|
handleOpen,
|
||||||
handleClose
|
handleClose
|
||||||
@@ -73,7 +93,7 @@ const SubAgentModal = forwardRef<SubAgentModalRef, SubAgentModalProps>(({
|
|||||||
form={form}
|
form={form}
|
||||||
layout="vertical"
|
layout="vertical"
|
||||||
>
|
>
|
||||||
{/* Agent名称 */}
|
{/* Agent name */}
|
||||||
<FormItem
|
<FormItem
|
||||||
name="agent_id"
|
name="agent_id"
|
||||||
label={t('application.agentName')}
|
label={t('application.agentName')}
|
||||||
@@ -93,14 +113,14 @@ const SubAgentModal = forwardRef<SubAgentModalRef, SubAgentModalProps>(({
|
|||||||
/>
|
/>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
<FormItem name="name" hidden />
|
<FormItem name="name" hidden />
|
||||||
{/* 描述 */}
|
{/* Description */}
|
||||||
<FormItem
|
<FormItem
|
||||||
name="role"
|
name="role"
|
||||||
label={t('application.description')}
|
label={t('application.description')}
|
||||||
>
|
>
|
||||||
<Input.TextArea placeholder={t('common.pleaseEnter')} />
|
<Input.TextArea placeholder={t('common.pleaseEnter')} />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
{/* 关键词 */}
|
{/* Keywords */}
|
||||||
<FormItem
|
<FormItem
|
||||||
name="capabilities"
|
name="capabilities"
|
||||||
label={t('application.capabilities')}
|
label={t('application.capabilities')}
|
||||||
|
|||||||
@@ -1,11 +1,31 @@
|
|||||||
|
/*
|
||||||
|
* @Author: ZhaoYing
|
||||||
|
* @Date: 2026-02-03 16:29:17
|
||||||
|
* @Last Modified by: ZhaoYing
|
||||||
|
* @Last Modified time: 2026-02-03 16:29:17
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Tag Component
|
||||||
|
* Displays colored status tags with different color schemes
|
||||||
|
*/
|
||||||
|
|
||||||
import { type FC, type ReactNode } from 'react'
|
import { type FC, type ReactNode } from 'react'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tag component props
|
||||||
|
*/
|
||||||
export interface TagProps {
|
export interface TagProps {
|
||||||
|
/** Tag color scheme */
|
||||||
color?: 'processing' | 'warning' | 'default' | 'success';
|
color?: 'processing' | 'warning' | 'default' | 'success';
|
||||||
|
/** Tag content */
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
|
/** Additional CSS classes */
|
||||||
className?: string;
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Color scheme mapping
|
||||||
|
*/
|
||||||
const colors = {
|
const colors = {
|
||||||
processing: 'rb:text-[#155EEF] rb:border-[rgba(21,94,239,0.25)] rb:bg-[rgba(21,94,239,0.06)]',
|
processing: 'rb:text-[#155EEF] rb:border-[rgba(21,94,239,0.25)] rb:bg-[rgba(21,94,239,0.06)]',
|
||||||
warning: 'rb:text-[#FF5D34] rb:border-[rgba(255,93,52,0.08)] rb:bg-[rgba(255,93,52,0.08)]',
|
warning: 'rb:text-[#FF5D34] rb:border-[rgba(255,93,52,0.08)] rb:bg-[rgba(255,93,52,0.08)]',
|
||||||
@@ -13,9 +33,12 @@ const colors = {
|
|||||||
success: 'rb:text-[#369F21] rb:border-[rgba(54,159,33,0.30)] rb:bg-[rgba(54,159,33,0.08)]',
|
success: 'rb:text-[#369F21] rb:border-[rgba(54,159,33,0.30)] rb:bg-[rgba(54,159,33,0.08)]',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tag component for displaying status labels
|
||||||
|
*/
|
||||||
const Tag: FC<TagProps> = ({ color = 'processing', children, className }) => {
|
const Tag: FC<TagProps> = ({ color = 'processing', children, className }) => {
|
||||||
return (
|
return (
|
||||||
<span className={`rb:inline-block rb:px-[8px] rb:py-[2px] rb:rounded-[11px] rb:text-[12px] rb:font-regular rb:leading-[16px] rb:border-[1px] ${colors[color]} ${className || ''}`}>
|
<span className={`rb:inline-block rb:px-2 rb:py-0.5 rb:rounded-[11px] rb:text-[12px] rb:font-regular rb:leading-4 rb:border ${colors[color]} ${className || ''}`}>
|
||||||
{children}
|
{children}
|
||||||
</span>
|
</span>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,6 +1,19 @@
|
|||||||
|
/*
|
||||||
|
* @Author: ZhaoYing
|
||||||
|
* @Date: 2026-02-03 16:26:03
|
||||||
|
* @Last Modified by: ZhaoYing
|
||||||
|
* @Last Modified time: 2026-02-03 16:26:03
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Tool List Component
|
||||||
|
* Manages tool configurations for the application
|
||||||
|
* Allows adding, removing, and enabling/disabling tools
|
||||||
|
*/
|
||||||
|
|
||||||
import { type FC, useRef, useState, useEffect } from 'react'
|
import { type FC, useRef, useState, useEffect } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { Space, Button, List, Switch } from 'antd'
|
import { Space, Button, List, Switch } from 'antd'
|
||||||
|
|
||||||
import Card from '../Card'
|
import Card from '../Card'
|
||||||
import type {
|
import type {
|
||||||
ToolModalRef,
|
ToolModalRef,
|
||||||
@@ -10,6 +23,11 @@ import Empty from '@/components/Empty'
|
|||||||
import ToolModal from './ToolModal'
|
import ToolModal from './ToolModal'
|
||||||
import { getToolMethods, getToolDetail } from '@/api/tools'
|
import { getToolMethods, getToolDetail } from '@/api/tools'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tool list management component
|
||||||
|
* @param value - Current tool configurations
|
||||||
|
* @param onChange - Callback when tools change
|
||||||
|
*/
|
||||||
const ToolList: FC<{ value?: ToolOption[]; onChange?: (config: ToolOption[]) => void}> = ({value, onChange}) => {
|
const ToolList: FC<{ value?: ToolOption[]; onChange?: (config: ToolOption[]) => void}> = ({value, onChange}) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const toolModalRef = useRef<ToolModalRef>(null)
|
const toolModalRef = useRef<ToolModalRef>(null)
|
||||||
@@ -79,19 +97,23 @@ const ToolList: FC<{ value?: ToolOption[]; onChange?: (config: ToolOption[]) =>
|
|||||||
}
|
}
|
||||||
}, [value])
|
}, [value])
|
||||||
|
|
||||||
|
/** Open tool selection modal */
|
||||||
const handleAddTool = () => {
|
const handleAddTool = () => {
|
||||||
toolModalRef.current?.handleOpen()
|
toolModalRef.current?.handleOpen()
|
||||||
}
|
}
|
||||||
|
/** Add new tool to list */
|
||||||
const updateTools = (tool: ToolOption) => {
|
const updateTools = (tool: ToolOption) => {
|
||||||
const list = [...toolList, tool]
|
const list = [...toolList, tool]
|
||||||
setToolList(list)
|
setToolList(list)
|
||||||
onChange && onChange(list)
|
onChange && onChange(list)
|
||||||
}
|
}
|
||||||
|
/** Remove tool from list */
|
||||||
const handleDeleteTool = (index: number) => {
|
const handleDeleteTool = (index: number) => {
|
||||||
const list = toolList.filter((_item, idx) => idx !== index)
|
const list = toolList.filter((_item, idx) => idx !== index)
|
||||||
setToolList([...list])
|
setToolList([...list])
|
||||||
onChange && onChange(list)
|
onChange && onChange(list)
|
||||||
}
|
}
|
||||||
|
/** Toggle tool enabled state */
|
||||||
const handleChangeEnabled = (index: number) => {
|
const handleChangeEnabled = (index: number) => {
|
||||||
const list = toolList.map((item, idx) => {
|
const list = toolList.map((item, idx) => {
|
||||||
if (idx === index) {
|
if (idx === index) {
|
||||||
|
|||||||
@@ -1,18 +1,37 @@
|
|||||||
|
/*
|
||||||
|
* @Author: ZhaoYing
|
||||||
|
* @Date: 2026-02-03 16:26:06
|
||||||
|
* @Last Modified by: ZhaoYing
|
||||||
|
* @Last Modified time: 2026-02-03 16:26:06
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Tool Selection Modal
|
||||||
|
* Provides cascading selection of tools by type, tool, and method
|
||||||
|
* Supports MCP, builtin, and custom tool types
|
||||||
|
*/
|
||||||
|
|
||||||
import { forwardRef, useImperativeHandle, useState } from 'react';
|
import { forwardRef, useImperativeHandle, useState } from 'react';
|
||||||
import { Form, Cascader, type CascaderProps } from 'antd';
|
import { Form, Cascader, type CascaderProps } from 'antd';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import type { ToolModalRef, ToolOption } from '../types'
|
import type { ToolModalRef, ToolOption } from './types'
|
||||||
import RbModal from '@/components/RbModal'
|
import RbModal from '@/components/RbModal'
|
||||||
import { getToolMethods, getTools } from '@/api/tools'
|
import { getToolMethods, getTools } from '@/api/tools'
|
||||||
import type { ToolType, ToolItem } from '@/views/ToolManagement/types'
|
import type { ToolType, ToolItem } from '@/views/ToolManagement/types'
|
||||||
|
|
||||||
const FormItem = Form.Item;
|
const FormItem = Form.Item;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component props
|
||||||
|
*/
|
||||||
interface ToolModalProps {
|
interface ToolModalProps {
|
||||||
|
/** Callback to add selected tool */
|
||||||
refresh: (tool: ToolOption) => void;
|
refresh: (tool: ToolOption) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modal for selecting tools
|
||||||
|
*/
|
||||||
const ToolModal = forwardRef<ToolModalRef, ToolModalProps>(({
|
const ToolModal = forwardRef<ToolModalRef, ToolModalProps>(({
|
||||||
refresh,
|
refresh,
|
||||||
}, ref) => {
|
}, ref) => {
|
||||||
@@ -27,7 +46,7 @@ const ToolModal = forwardRef<ToolModalRef, ToolModalProps>(({
|
|||||||
])
|
])
|
||||||
const [selectdTools, setSelectedTools] = useState<ToolOption[]>([])
|
const [selectdTools, setSelectedTools] = useState<ToolOption[]>([])
|
||||||
|
|
||||||
// 封装取消方法,添加关闭弹窗逻辑
|
/** Close modal and reset state */
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
setVisible(false);
|
setVisible(false);
|
||||||
form.resetFields();
|
form.resetFields();
|
||||||
@@ -35,12 +54,13 @@ const ToolModal = forwardRef<ToolModalRef, ToolModalProps>(({
|
|||||||
setSelectedTools([])
|
setSelectedTools([])
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** Open modal */
|
||||||
const handleOpen = () => {
|
const handleOpen = () => {
|
||||||
setVisible(true);
|
setVisible(true);
|
||||||
form.resetFields();
|
form.resetFields();
|
||||||
setSelectedTools([])
|
setSelectedTools([])
|
||||||
};
|
};
|
||||||
// 封装保存方法,添加提交逻辑
|
/** Save selected tool */
|
||||||
const handleSave = () => {
|
const handleSave = () => {
|
||||||
form.validateFields().then(() => {
|
form.validateFields().then(() => {
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
@@ -64,6 +84,7 @@ const ToolModal = forwardRef<ToolModalRef, ToolModalProps>(({
|
|||||||
handleClose()
|
handleClose()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
/** Load cascader options dynamically */
|
||||||
const loadData = (selectedOptions: ToolOption[]) => {
|
const loadData = (selectedOptions: ToolOption[]) => {
|
||||||
const targetOption = selectedOptions[selectedOptions.length - 1];
|
const targetOption = selectedOptions[selectedOptions.length - 1];
|
||||||
if (selectedOptions.length === 1) {
|
if (selectedOptions.length === 1) {
|
||||||
@@ -98,12 +119,13 @@ const ToolModal = forwardRef<ToolModalRef, ToolModalProps>(({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** Handle cascader selection change */
|
||||||
const handleChange: CascaderProps<ToolOption>['onChange'] = (_value, selectedOptions) => {
|
const handleChange: CascaderProps<ToolOption>['onChange'] = (_value, selectedOptions) => {
|
||||||
console.log('selectedOptions', selectedOptions)
|
console.log('selectedOptions', selectedOptions)
|
||||||
setSelectedTools(selectedOptions)
|
setSelectedTools(selectedOptions)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 暴露给父组件的方法
|
/** Expose methods to parent component */
|
||||||
useImperativeHandle(ref, () => ({
|
useImperativeHandle(ref, () => ({
|
||||||
handleOpen,
|
handleOpen,
|
||||||
handleClose
|
handleClose
|
||||||
|
|||||||
@@ -1,26 +1,67 @@
|
|||||||
|
/*
|
||||||
|
* @Author: ZhaoYing
|
||||||
|
* @Date: 2026-02-03 16:26:10
|
||||||
|
* @Last Modified by: ZhaoYing
|
||||||
|
* @Last Modified time: 2026-02-03 16:26:10
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Type definitions for tool configuration in application settings
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tool option for cascader selection
|
||||||
|
*/
|
||||||
export interface ToolOption {
|
export interface ToolOption {
|
||||||
|
/** Option value */
|
||||||
value?: string | number | null;
|
value?: string | number | null;
|
||||||
|
/** Display label */
|
||||||
label?: React.ReactNode;
|
label?: React.ReactNode;
|
||||||
|
/** Tool description */
|
||||||
description?: string;
|
description?: string;
|
||||||
|
/** Child options for nested selection */
|
||||||
children?: ToolOption[];
|
children?: ToolOption[];
|
||||||
|
/** Whether this is a leaf node */
|
||||||
isLeaf?: boolean;
|
isLeaf?: boolean;
|
||||||
|
/** Method ID for API operations */
|
||||||
method_id?: string;
|
method_id?: string;
|
||||||
|
/** Operation name */
|
||||||
operation?: string;
|
operation?: string;
|
||||||
|
/** Method parameters */
|
||||||
parameters?: Parameter[];
|
parameters?: Parameter[];
|
||||||
|
/** Tool ID */
|
||||||
tool_id?: string;
|
tool_id?: string;
|
||||||
|
/** Whether tool is enabled */
|
||||||
enabled?: boolean;
|
enabled?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parameter definition for tool methods
|
||||||
|
*/
|
||||||
export interface Parameter {
|
export interface Parameter {
|
||||||
|
/** Parameter name */
|
||||||
name: string;
|
name: string;
|
||||||
|
/** Parameter data type */
|
||||||
type: string;
|
type: string;
|
||||||
|
/** Parameter description */
|
||||||
description: string;
|
description: string;
|
||||||
|
/** Whether parameter is required */
|
||||||
required: boolean;
|
required: boolean;
|
||||||
|
/** Default value */
|
||||||
default: any;
|
default: any;
|
||||||
|
/** Enum values if applicable */
|
||||||
enum: null | string[];
|
enum: null | string[];
|
||||||
|
/** Minimum value for numeric types */
|
||||||
minimum: number;
|
minimum: number;
|
||||||
|
/** Maximum value for numeric types */
|
||||||
maximum: number;
|
maximum: number;
|
||||||
|
/** Regex pattern for validation */
|
||||||
pattern: null | string;
|
pattern: null | string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modal ref for tool selection
|
||||||
|
*/
|
||||||
export interface ToolModalRef {
|
export interface ToolModalRef {
|
||||||
|
/** Open tool selection modal */
|
||||||
handleOpen: () => void;
|
handleOpen: () => void;
|
||||||
}
|
}
|
||||||
@@ -1,3 +1,14 @@
|
|||||||
|
/*
|
||||||
|
* @Author: ZhaoYing
|
||||||
|
* @Date: 2026-02-03 16:26:18
|
||||||
|
* @Last Modified by: ZhaoYing
|
||||||
|
* @Last Modified time: 2026-02-03 16:26:18
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* API Extension Modal
|
||||||
|
* Allows configuring external API endpoints for dynamic variable options
|
||||||
|
*/
|
||||||
|
|
||||||
import { forwardRef, useImperativeHandle, useState } from 'react';
|
import { forwardRef, useImperativeHandle, useState } from 'react';
|
||||||
import { Form, Input } from 'antd';
|
import { Form, Input } from 'antd';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@@ -7,10 +18,17 @@ import RbModal from '@/components/RbModal'
|
|||||||
|
|
||||||
const FormItem = Form.Item;
|
const FormItem = Form.Item;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component props
|
||||||
|
*/
|
||||||
interface ApiExtensionModalProps {
|
interface ApiExtensionModalProps {
|
||||||
|
/** Callback to refresh API extension list */
|
||||||
refresh: () => void;
|
refresh: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modal for adding API extensions
|
||||||
|
*/
|
||||||
const ApiExtensionModal = forwardRef<ApiExtensionModalRef, ApiExtensionModalProps>(({
|
const ApiExtensionModal = forwardRef<ApiExtensionModalRef, ApiExtensionModalProps>(({
|
||||||
refresh
|
refresh
|
||||||
}, ref) => {
|
}, ref) => {
|
||||||
@@ -21,18 +39,19 @@ const ApiExtensionModal = forwardRef<ApiExtensionModalRef, ApiExtensionModalProp
|
|||||||
|
|
||||||
const values = Form.useWatch([], form);
|
const values = Form.useWatch([], form);
|
||||||
|
|
||||||
// 封装取消方法,添加关闭弹窗逻辑
|
/** Close modal and reset form */
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
setVisible(false);
|
setVisible(false);
|
||||||
form.resetFields();
|
form.resetFields();
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** Open modal */
|
||||||
const handleOpen = () => {
|
const handleOpen = () => {
|
||||||
form.resetFields();
|
form.resetFields();
|
||||||
setVisible(true);
|
setVisible(true);
|
||||||
};
|
};
|
||||||
// 封装保存方法,添加提交逻辑
|
/** Save API extension configuration */
|
||||||
const handleSave = () => {
|
const handleSave = () => {
|
||||||
form
|
form
|
||||||
.validateFields()
|
.validateFields()
|
||||||
@@ -46,7 +65,7 @@ const ApiExtensionModal = forwardRef<ApiExtensionModalRef, ApiExtensionModalProp
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 暴露给父组件的方法
|
/** Expose methods to parent component */
|
||||||
useImperativeHandle(ref, () => ({
|
useImperativeHandle(ref, () => ({
|
||||||
handleOpen,
|
handleOpen,
|
||||||
handleClose
|
handleClose
|
||||||
|
|||||||
@@ -1,3 +1,15 @@
|
|||||||
|
/*
|
||||||
|
* @Author: ZhaoYing
|
||||||
|
* @Date: 2026-02-03 16:26:27
|
||||||
|
* @Last Modified by: ZhaoYing
|
||||||
|
* @Last Modified time: 2026-02-03 16:26:27
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Variable Edit Modal
|
||||||
|
* Allows creating and editing application input variables
|
||||||
|
* Supports multiple variable types: text, paragraph, number, dropdown, checkbox, API variable
|
||||||
|
*/
|
||||||
|
|
||||||
import { forwardRef, useImperativeHandle, useState, useRef } from 'react';
|
import { forwardRef, useImperativeHandle, useState, useRef } from 'react';
|
||||||
import { Form, Input, Select, InputNumber, Checkbox, Tag, Divider, Button } from 'antd';
|
import { Form, Input, Select, InputNumber, Checkbox, Tag, Divider, Button } from 'antd';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@@ -9,10 +21,17 @@ import ApiExtensionModal from './ApiExtensionModal'
|
|||||||
|
|
||||||
const FormItem = Form.Item;
|
const FormItem = Form.Item;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component props
|
||||||
|
*/
|
||||||
interface VariableEditModalProps {
|
interface VariableEditModalProps {
|
||||||
|
/** Callback to update variable */
|
||||||
refreshTable: (values: Variable) => void;
|
refreshTable: (values: Variable) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Supported variable types
|
||||||
|
*/
|
||||||
const types = [
|
const types = [
|
||||||
'text',
|
'text',
|
||||||
'paragraph',
|
'paragraph',
|
||||||
@@ -21,6 +40,9 @@ const types = [
|
|||||||
// 'checkbox',
|
// 'checkbox',
|
||||||
// 'apiVariable'
|
// 'apiVariable'
|
||||||
]
|
]
|
||||||
|
/**
|
||||||
|
* Variable type to data type mapping
|
||||||
|
*/
|
||||||
const variableType = {
|
const variableType = {
|
||||||
text: 'string',
|
text: 'string',
|
||||||
paragraph: 'string',
|
paragraph: 'string',
|
||||||
@@ -30,6 +52,9 @@ const variableType = {
|
|||||||
apiVariable: 'string',
|
apiVariable: 'string',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modal for editing variables
|
||||||
|
*/
|
||||||
const VariableEditModal = forwardRef<VariableEditModalRef, VariableEditModalProps>(({
|
const VariableEditModal = forwardRef<VariableEditModalRef, VariableEditModalProps>(({
|
||||||
refreshTable
|
refreshTable
|
||||||
}, ref) => {
|
}, ref) => {
|
||||||
@@ -42,7 +67,7 @@ const VariableEditModal = forwardRef<VariableEditModalRef, VariableEditModalProp
|
|||||||
|
|
||||||
const values = Form.useWatch([], form);
|
const values = Form.useWatch([], form);
|
||||||
|
|
||||||
// 封装取消方法,添加关闭弹窗逻辑
|
/** Close modal and reset form */
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
setVisible(false);
|
setVisible(false);
|
||||||
form.resetFields();
|
form.resetFields();
|
||||||
@@ -50,6 +75,7 @@ const VariableEditModal = forwardRef<VariableEditModalRef, VariableEditModalProp
|
|||||||
setEditVo(null)
|
setEditVo(null)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** Open modal with optional variable data */
|
||||||
const handleOpen = (variable?: Variable) => {
|
const handleOpen = (variable?: Variable) => {
|
||||||
setVisible(true);
|
setVisible(true);
|
||||||
if (variable) {
|
if (variable) {
|
||||||
@@ -59,7 +85,7 @@ const VariableEditModal = forwardRef<VariableEditModalRef, VariableEditModalProp
|
|||||||
form.resetFields();
|
form.resetFields();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
// 封装保存方法,添加提交逻辑
|
/** Save variable configuration */
|
||||||
const handleSave = () => {
|
const handleSave = () => {
|
||||||
form.validateFields().then((values) => {
|
form.validateFields().then((values) => {
|
||||||
refreshTable({
|
refreshTable({
|
||||||
@@ -70,12 +96,12 @@ const VariableEditModal = forwardRef<VariableEditModalRef, VariableEditModalProp
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 暴露给父组件的方法
|
/** Expose methods to parent component */
|
||||||
useImperativeHandle(ref, () => ({
|
useImperativeHandle(ref, () => ({
|
||||||
handleOpen,
|
handleOpen,
|
||||||
handleClose
|
handleClose
|
||||||
}));
|
}));
|
||||||
// 变量类型改变时,更新初始化其他字段值
|
/** Handle variable type change */
|
||||||
const handleChangeType = (value: Variable['type']) => {
|
const handleChangeType = (value: Variable['type']) => {
|
||||||
if (value) {
|
if (value) {
|
||||||
form.setFieldsValue({
|
form.setFieldsValue({
|
||||||
@@ -90,7 +116,7 @@ const VariableEditModal = forwardRef<VariableEditModalRef, VariableEditModalProp
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 添加 API 扩展
|
/** Add API extension */
|
||||||
const addApiExtension = () => {
|
const addApiExtension = () => {
|
||||||
apiExtensionModalRef.current?.handleOpen()
|
apiExtensionModalRef.current?.handleOpen()
|
||||||
}
|
}
|
||||||
@@ -111,7 +137,7 @@ const VariableEditModal = forwardRef<VariableEditModalRef, VariableEditModalProp
|
|||||||
layout="vertical"
|
layout="vertical"
|
||||||
scrollToFirstError={{ behavior: 'instant', block: 'end', focus: true }}
|
scrollToFirstError={{ behavior: 'instant', block: 'end', focus: true }}
|
||||||
>
|
>
|
||||||
{/* 变量类型 */}
|
{/* Variable Type */}
|
||||||
<FormItem
|
<FormItem
|
||||||
name="type"
|
name="type"
|
||||||
label={t('application.variableType')}
|
label={t('application.variableType')}
|
||||||
@@ -128,7 +154,7 @@ const VariableEditModal = forwardRef<VariableEditModalRef, VariableEditModalProp
|
|||||||
optionRender={(props) => <div className="rb:flex rb:justify-between rb:items-center">{props.label} <Tag color="blue">{variableType[props.value as keyof typeof variableType]}</Tag></div>}
|
optionRender={(props) => <div className="rb:flex rb:justify-between rb:items-center">{props.label} <Tag color="blue">{variableType[props.value as keyof typeof variableType]}</Tag></div>}
|
||||||
/>
|
/>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
{/* 变量名称 */}
|
{/* Variable Name */}
|
||||||
<FormItem
|
<FormItem
|
||||||
name="name"
|
name="name"
|
||||||
label={t('application.variableName')}
|
label={t('application.variableName')}
|
||||||
@@ -146,7 +172,7 @@ const VariableEditModal = forwardRef<VariableEditModalRef, VariableEditModalProp
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
{/* 显示名称 */}
|
{/* Display Name */}
|
||||||
<FormItem
|
<FormItem
|
||||||
name="display_name"
|
name="display_name"
|
||||||
label={t('application.displayName')}
|
label={t('application.displayName')}
|
||||||
@@ -154,14 +180,14 @@ const VariableEditModal = forwardRef<VariableEditModalRef, VariableEditModalProp
|
|||||||
>
|
>
|
||||||
<Input placeholder={t('common.enter')} />
|
<Input placeholder={t('common.enter')} />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
{/* 描述 */}
|
{/* Description */}
|
||||||
<FormItem
|
<FormItem
|
||||||
name="description"
|
name="description"
|
||||||
label={t('application.description')}
|
label={t('application.description')}
|
||||||
>
|
>
|
||||||
<Input placeholder={t('common.enter')} />
|
<Input placeholder={t('common.enter')} />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
{/* 最大长度 */}
|
{/* Max Length */}
|
||||||
{['text', 'paragraph'].includes(values?.type) && (
|
{['text', 'paragraph'].includes(values?.type) && (
|
||||||
<FormItem
|
<FormItem
|
||||||
name="max_length"
|
name="max_length"
|
||||||
@@ -170,7 +196,7 @@ const VariableEditModal = forwardRef<VariableEditModalRef, VariableEditModalProp
|
|||||||
<InputNumber placeholder={t('common.enter')} style={{ width: '100%' }} />
|
<InputNumber placeholder={t('common.enter')} style={{ width: '100%' }} />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
{/* 默认值 */}
|
{/* Default Value */}
|
||||||
{/* {['text', 'paragraph', 'number', 'checkbox'].includes(values?.type) && (
|
{/* {['text', 'paragraph', 'number', 'checkbox'].includes(values?.type) && (
|
||||||
<FormItem
|
<FormItem
|
||||||
name="default_value"
|
name="default_value"
|
||||||
@@ -182,7 +208,7 @@ const VariableEditModal = forwardRef<VariableEditModalRef, VariableEditModalProp
|
|||||||
{['checkbox'].includes(values.type) && <Select options={[{ value: true, label: t('application.defaultChecked') }, { value: false, label: t('application.notDefaultChecked') }]} />}
|
{['checkbox'].includes(values.type) && <Select options={[{ value: true, label: t('application.defaultChecked') }, { value: false, label: t('application.notDefaultChecked') }]} />}
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)} */}
|
)} */}
|
||||||
{/* 选项 */}
|
{/* Options */}
|
||||||
{['dropdownOptions'].includes(values?.type) && (
|
{['dropdownOptions'].includes(values?.type) && (
|
||||||
<FormItem
|
<FormItem
|
||||||
name="options"
|
name="options"
|
||||||
@@ -191,7 +217,7 @@ const VariableEditModal = forwardRef<VariableEditModalRef, VariableEditModalProp
|
|||||||
<SortableList />
|
<SortableList />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
{/* API 扩展 */}
|
{/* API Extension */}
|
||||||
{['apiVariable'].includes(values?.type) && (
|
{['apiVariable'].includes(values?.type) && (
|
||||||
<FormItem
|
<FormItem
|
||||||
name="api_extension"
|
name="api_extension"
|
||||||
@@ -211,14 +237,14 @@ const VariableEditModal = forwardRef<VariableEditModalRef, VariableEditModalProp
|
|||||||
/>
|
/>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
{/* 是否必填 */}
|
{/* Required */}
|
||||||
<FormItem
|
<FormItem
|
||||||
name="required"
|
name="required"
|
||||||
valuePropName="checked"
|
valuePropName="checked"
|
||||||
>
|
>
|
||||||
<Checkbox>{t('application.required')}</Checkbox>
|
<Checkbox>{t('application.required')}</Checkbox>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
{/* 是否隐藏 */}
|
{/* Hidden */}
|
||||||
{/* <FormItem
|
{/* <FormItem
|
||||||
name="hidden"
|
name="hidden"
|
||||||
valuePropName="checked"
|
valuePropName="checked"
|
||||||
|
|||||||
@@ -1,6 +1,19 @@
|
|||||||
|
/*
|
||||||
|
* @Author: ZhaoYing
|
||||||
|
* @Date: 2026-02-03 16:26:32
|
||||||
|
* @Last Modified by: ZhaoYing
|
||||||
|
* @Last Modified time: 2026-02-03 16:26:32
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Variable List Component
|
||||||
|
* Manages application input variables configuration
|
||||||
|
* Allows adding, editing, and removing variables
|
||||||
|
*/
|
||||||
|
|
||||||
import { type FC, useRef } from 'react'
|
import { type FC, useRef } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { Space, Button, Switch, Form } from 'antd'
|
import { Space, Button, Switch, Form } from 'antd'
|
||||||
|
|
||||||
import variablesEmpty from '@/assets/images/application/variablesEmpty.svg'
|
import variablesEmpty from '@/assets/images/application/variablesEmpty.svg'
|
||||||
import Card from '../Card'
|
import Card from '../Card'
|
||||||
import Table from '@/components/Table';
|
import Table from '@/components/Table';
|
||||||
@@ -8,17 +21,27 @@ import type { Variable, VariableEditModalRef } from './types'
|
|||||||
import Empty from '@/components/Empty'
|
import Empty from '@/components/Empty'
|
||||||
import VariableEditModal from './VariableEditModal'
|
import VariableEditModal from './VariableEditModal'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component props
|
||||||
|
*/
|
||||||
interface VariableListProps {
|
interface VariableListProps {
|
||||||
|
/** Current variable list */
|
||||||
value?: Variable[];
|
value?: Variable[];
|
||||||
|
/** Callback when variables change */
|
||||||
onChange?: (value: Variable[]) => void;
|
onChange?: (value: Variable[]) => void;
|
||||||
}
|
}
|
||||||
const VariableList: FC<VariableListProps> = ({value = [], onChange}) => {
|
|
||||||
|
/**
|
||||||
|
* Variable list management component
|
||||||
|
*/const VariableList: FC<VariableListProps> = ({value = [], onChange}) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const variableEditModalRef = useRef<VariableEditModalRef>(null)
|
const variableEditModalRef = useRef<VariableEditModalRef>(null)
|
||||||
|
|
||||||
|
/** Open variable edit modal */
|
||||||
const handleAddVariable = () => {
|
const handleAddVariable = () => {
|
||||||
variableEditModalRef.current?.handleOpen()
|
variableEditModalRef.current?.handleOpen()
|
||||||
}
|
}
|
||||||
|
/** Save variable changes */
|
||||||
const handleSaveVariable = (variable: Variable) => {
|
const handleSaveVariable = (variable: Variable) => {
|
||||||
const newList = [...(value || [])]
|
const newList = [...(value || [])]
|
||||||
if (variable.index !== undefined && variable.index >= 0) {
|
if (variable.index !== undefined && variable.index >= 0) {
|
||||||
|
|||||||
@@ -1,28 +1,69 @@
|
|||||||
export interface Variable {
|
/*
|
||||||
index?: number;
|
* @Author: ZhaoYing
|
||||||
name: string;
|
* @Date: 2026-02-03 16:26:21
|
||||||
display_name: string;
|
* @Last Modified by: ZhaoYing
|
||||||
type: string;
|
* @Last Modified time: 2026-02-03 16:26:21
|
||||||
required: boolean;
|
*/
|
||||||
max_length?: number;
|
/**
|
||||||
description?: string;
|
* Type definitions for variable configuration in application settings
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Variable definition for application input
|
||||||
|
*/
|
||||||
|
export interface Variable {
|
||||||
|
/** Variable index in list */
|
||||||
|
index?: number;
|
||||||
|
/** Variable name (identifier) */
|
||||||
|
name: string;
|
||||||
|
/** Display name shown to users */
|
||||||
|
display_name: string;
|
||||||
|
/** Variable data type (string, number, select, etc.) */
|
||||||
|
type: string;
|
||||||
|
/** Whether variable is required */
|
||||||
|
required: boolean;
|
||||||
|
/** Maximum length for string types */
|
||||||
|
max_length?: number;
|
||||||
|
/** Variable description */
|
||||||
|
description?: string;
|
||||||
|
/** Unique key for React rendering */
|
||||||
key?: string;
|
key?: string;
|
||||||
|
/** Default value */
|
||||||
default_value?: string;
|
default_value?: string;
|
||||||
|
/** Options for select type variables */
|
||||||
options?: string[];
|
options?: string[];
|
||||||
|
/** API extension for dynamic options */
|
||||||
api_extension?: string;
|
api_extension?: string;
|
||||||
|
/** Whether variable is hidden from UI */
|
||||||
hidden?: boolean;
|
hidden?: boolean;
|
||||||
|
/** Current value */
|
||||||
value?: any;
|
value?: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modal ref for variable editing
|
||||||
|
*/
|
||||||
export interface VariableEditModalRef {
|
export interface VariableEditModalRef {
|
||||||
|
/** Open modal with optional existing variable data */
|
||||||
handleOpen: (values?: Variable) => void;
|
handleOpen: (values?: Variable) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API extension configuration data
|
||||||
|
*/
|
||||||
export interface ApiExtensionModalData {
|
export interface ApiExtensionModalData {
|
||||||
|
/** Extension name */
|
||||||
name: string;
|
name: string;
|
||||||
|
/** API endpoint URL */
|
||||||
apiEndpoint: string;
|
apiEndpoint: string;
|
||||||
|
/** API authentication key */
|
||||||
apiKey: string;
|
apiKey: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modal ref for API extension configuration
|
||||||
|
*/
|
||||||
export interface ApiExtensionModalRef {
|
export interface ApiExtensionModalRef {
|
||||||
|
/** Open API extension modal */
|
||||||
handleOpen: () => void;
|
handleOpen: () => void;
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,12 @@
|
|||||||
|
/*
|
||||||
|
* @Author: ZhaoYing
|
||||||
|
* @Date: 2026-02-03 16:29:37
|
||||||
|
* @Last Modified by: ZhaoYing
|
||||||
|
* @Last Modified time: 2026-02-03 16:29:37
|
||||||
|
*/
|
||||||
import React, { useEffect, useState, useRef } from 'react';
|
import React, { useEffect, useState, useRef } from 'react';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
|
|
||||||
import ConfigHeader from './components/ConfigHeader'
|
import ConfigHeader from './components/ConfigHeader'
|
||||||
import type { AgentRef, ClusterRef, WorkflowRef } from './types'
|
import type { AgentRef, ClusterRef, WorkflowRef } from './types'
|
||||||
import type { Application } from '@/views/ApplicationManagement/types'
|
import type { Application } from '@/views/ApplicationManagement/types'
|
||||||
@@ -11,14 +18,28 @@ import { getApplication } from '@/api/application'
|
|||||||
import Workflow from '@/views/Workflow';
|
import Workflow from '@/views/Workflow';
|
||||||
import Statistics from './Statistics'
|
import Statistics from './Statistics'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Application configuration page component
|
||||||
|
* Main container for configuring agents, workflows, multi-agent clusters
|
||||||
|
* Manages tabs for arrangement, API, release, and statistics
|
||||||
|
*/
|
||||||
const ApplicationConfig: React.FC = () => {
|
const ApplicationConfig: React.FC = () => {
|
||||||
|
// Hooks
|
||||||
const { id } = useParams();
|
const { id } = useParams();
|
||||||
|
|
||||||
|
// Refs for different application types
|
||||||
const agentRef = useRef<AgentRef>(null)
|
const agentRef = useRef<AgentRef>(null)
|
||||||
const clusterRef = useRef<ClusterRef>(null)
|
const clusterRef = useRef<ClusterRef>(null)
|
||||||
const workflowRef = useRef<WorkflowRef>(null)
|
const workflowRef = useRef<WorkflowRef>(null)
|
||||||
|
|
||||||
|
// State
|
||||||
const [application, setApplication] = useState<Application | null>(null);
|
const [application, setApplication] = useState<Application | null>(null);
|
||||||
const [activeTab, setActiveTab] = useState('arrangement');
|
const [activeTab, setActiveTab] = useState('arrangement');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle tab change with auto-save for arrangement tab
|
||||||
|
* @param key - New tab key
|
||||||
|
*/
|
||||||
const handleChangeTab = async (key: string) => {
|
const handleChangeTab = async (key: string) => {
|
||||||
if (activeTab === 'arrangement' && application?.type === 'agent' && agentRef.current) {
|
if (activeTab === 'arrangement' && application?.type === 'agent' && agentRef.current) {
|
||||||
agentRef.current.handleSave(false)
|
agentRef.current.handleSave(false)
|
||||||
@@ -44,6 +65,9 @@ const ApplicationConfig: React.FC = () => {
|
|||||||
getApplicationInfo()
|
getApplicationInfo()
|
||||||
}, [id])
|
}, [id])
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch application information
|
||||||
|
*/
|
||||||
const getApplicationInfo = () => {
|
const getApplicationInfo = () => {
|
||||||
if (!id) {
|
if (!id) {
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
/*
|
||||||
|
* @Author: ZhaoYing
|
||||||
|
* @Date: 2026-02-03 16:29:49
|
||||||
|
* @Last Modified by: ZhaoYing
|
||||||
|
* @Last Modified time: 2026-02-03 16:29:49
|
||||||
|
*/
|
||||||
import type { KnowledgeConfig } from './components/Knowledge/types'
|
import type { KnowledgeConfig } from './components/Knowledge/types'
|
||||||
import type { Variable } from './components/VariableList/types'
|
import type { Variable } from './components/VariableList/types'
|
||||||
import type { ToolOption } from './components/ToolList/types'
|
import type { ToolOption } from './components/ToolList/types'
|
||||||
@@ -5,164 +11,389 @@ import type { ChatItem } from '@/components/Chat/types'
|
|||||||
import type { GraphRef } from '@/views/Workflow/types';
|
import type { GraphRef } from '@/views/Workflow/types';
|
||||||
import type { ApiKey } from '@/views/ApiKeyManagement/types'
|
import type { ApiKey } from '@/views/ApiKeyManagement/types'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Model configuration parameters
|
||||||
|
*/
|
||||||
export interface ModelConfig {
|
export interface ModelConfig {
|
||||||
|
/** Model label */
|
||||||
label?: string;
|
label?: string;
|
||||||
|
/** Default model configuration ID */
|
||||||
default_model_config_id?: string;
|
default_model_config_id?: string;
|
||||||
|
/** Temperature for response randomness (0-2) */
|
||||||
temperature: number;
|
temperature: number;
|
||||||
|
/** Maximum tokens in response */
|
||||||
max_tokens: number;
|
max_tokens: number;
|
||||||
|
/** Top-p sampling parameter */
|
||||||
top_p: number;
|
top_p: number;
|
||||||
|
/** Frequency penalty */
|
||||||
frequency_penalty: number;
|
frequency_penalty: number;
|
||||||
|
/** Presence penalty */
|
||||||
presence_penalty: number;
|
presence_penalty: number;
|
||||||
|
/** Number of completions to generate */
|
||||||
n: number;
|
n: number;
|
||||||
|
/** Stop sequences */
|
||||||
stop?: string;
|
stop?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Memory configuration
|
||||||
|
*/
|
||||||
export interface MemoryConfig {
|
export interface MemoryConfig {
|
||||||
|
/** Whether memory is enabled */
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
|
/** Memory content */
|
||||||
memory_content?: string;
|
memory_content?: string;
|
||||||
|
/** Maximum history length */
|
||||||
max_history?: number | string;
|
max_history?: number | string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Application configuration
|
||||||
|
*/
|
||||||
export interface Config extends MultiAgentConfig {
|
export interface Config extends MultiAgentConfig {
|
||||||
|
/** Configuration ID */
|
||||||
id: string;
|
id: string;
|
||||||
|
/** Application ID */
|
||||||
app_id: string;
|
app_id: string;
|
||||||
|
/** System prompt */
|
||||||
system_prompt: string;
|
system_prompt: string;
|
||||||
|
/** Default model configuration ID */
|
||||||
default_model_config_id?: string;
|
default_model_config_id?: string;
|
||||||
|
/** Model parameters */
|
||||||
model_parameters: ModelConfig;
|
model_parameters: ModelConfig;
|
||||||
|
/** Knowledge retrieval configuration */
|
||||||
knowledge_retrieval: KnowledgeConfig | null;
|
knowledge_retrieval: KnowledgeConfig | null;
|
||||||
|
/** Memory configuration */
|
||||||
memory?: MemoryConfig;
|
memory?: MemoryConfig;
|
||||||
|
/** Variables list */
|
||||||
variables: Variable[];
|
variables: Variable[];
|
||||||
|
/** Tools list */
|
||||||
tools: ToolOption[];
|
tools: ToolOption[];
|
||||||
|
/** Whether configuration is active */
|
||||||
is_active: boolean;
|
is_active: boolean;
|
||||||
|
/** Creation timestamp */
|
||||||
created_at: number;
|
created_at: number;
|
||||||
|
/** Last update timestamp */
|
||||||
updated_at: number;
|
updated_at: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Multi-agent configuration
|
||||||
|
*/
|
||||||
export interface MultiAgentConfig {
|
export interface MultiAgentConfig {
|
||||||
|
/** Configuration ID */
|
||||||
id: string;
|
id: string;
|
||||||
|
/** Application ID */
|
||||||
app_id: string;
|
app_id: string;
|
||||||
|
/** Default model configuration ID */
|
||||||
default_model_config_id?: string;
|
default_model_config_id?: string;
|
||||||
|
/** Model parameters */
|
||||||
model_parameters: ModelConfig;
|
model_parameters: ModelConfig;
|
||||||
|
/** Sub-agents list */
|
||||||
sub_agents?: SubAgentItem[];
|
sub_agents?: SubAgentItem[];
|
||||||
|
/** Routing rules */
|
||||||
routing_rules: null;
|
routing_rules: null;
|
||||||
|
/** Orchestration mode */
|
||||||
orchestration_mode: 'supervisor' | 'collaboration';
|
orchestration_mode: 'supervisor' | 'collaboration';
|
||||||
|
/** Execution configuration */
|
||||||
execution_config: {
|
execution_config: {
|
||||||
|
/** Sub-agent execution mode */
|
||||||
sub_agent_execution_mode: 'sequential' | 'parallel';
|
sub_agent_execution_mode: 'sequential' | 'parallel';
|
||||||
};
|
};
|
||||||
|
/** Aggregation strategy */
|
||||||
aggregation_strategy: 'merge' | 'vote' | 'priority'
|
aggregation_strategy: 'merge' | 'vote' | 'priority'
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建表单数据类型
|
/**
|
||||||
|
* Application modal form data
|
||||||
|
*/
|
||||||
export interface ApplicationModalData {
|
export interface ApplicationModalData {
|
||||||
|
/** Application name */
|
||||||
name: string;
|
name: string;
|
||||||
|
/** Application type */
|
||||||
type: string;
|
type: string;
|
||||||
|
/** Application icon */
|
||||||
icon: string;
|
icon: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 定义组件暴露的方法接口
|
/**
|
||||||
|
* Agent component ref methods
|
||||||
|
*/
|
||||||
export interface AgentRef {
|
export interface AgentRef {
|
||||||
|
/**
|
||||||
|
* Save agent configuration
|
||||||
|
* @param flag - Whether to show success message
|
||||||
|
*/
|
||||||
handleSave: (flag?: boolean) => Promise<unknown>;
|
handleSave: (flag?: boolean) => Promise<unknown>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cluster component ref methods
|
||||||
|
*/
|
||||||
export interface ClusterRef {
|
export interface ClusterRef {
|
||||||
|
/**
|
||||||
|
* Save cluster configuration
|
||||||
|
* @param flag - Whether to show success message
|
||||||
|
*/
|
||||||
handleSave: (flag?: boolean) => Promise<unknown>;
|
handleSave: (flag?: boolean) => Promise<unknown>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Workflow component ref methods
|
||||||
|
*/
|
||||||
export interface WorkflowRef {
|
export interface WorkflowRef {
|
||||||
|
/**
|
||||||
|
* Save workflow configuration
|
||||||
|
* @param flag - Whether to show success message
|
||||||
|
*/
|
||||||
handleSave: (flag?: boolean) => Promise<unknown>;
|
handleSave: (flag?: boolean) => Promise<unknown>;
|
||||||
|
/** Run workflow */
|
||||||
handleRun: () => void;
|
handleRun: () => void;
|
||||||
|
/** Graph reference */
|
||||||
graphRef: GraphRef;
|
graphRef: GraphRef;
|
||||||
|
/** Add variable */
|
||||||
addVariable: () => void;
|
addVariable: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Application modal ref methods
|
||||||
|
*/
|
||||||
export interface ApplicationModalRef {
|
export interface ApplicationModalRef {
|
||||||
|
/**
|
||||||
|
* Open application modal
|
||||||
|
* @param application - Optional application data for edit mode
|
||||||
|
*/
|
||||||
handleOpen: (application?: Config) => void;
|
handleOpen: (application?: Config) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Model configuration source type
|
||||||
|
*/
|
||||||
export type Source = 'chat' | 'model' | 'multi_agent'
|
export type Source = 'chat' | 'model' | 'multi_agent'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Model configuration modal ref methods
|
||||||
|
*/
|
||||||
export interface ModelConfigModalRef {
|
export interface ModelConfigModalRef {
|
||||||
|
/**
|
||||||
|
* Open model configuration modal
|
||||||
|
* @param source - Configuration source
|
||||||
|
* @param model - Optional model data
|
||||||
|
*/
|
||||||
handleOpen: (source: Source, model?: any) => void;
|
handleOpen: (source: Source, model?: any) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Model configuration modal form data
|
||||||
|
*/
|
||||||
export interface ModelConfigModalData {
|
export interface ModelConfigModalData {
|
||||||
|
/** Model identifier */
|
||||||
model: string;
|
model: string;
|
||||||
|
/** Additional configuration fields */
|
||||||
[key: string]: string;
|
[key: string]: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AI prompt modal ref methods
|
||||||
|
*/
|
||||||
export interface AiPromptModalRef {
|
export interface AiPromptModalRef {
|
||||||
handleOpen: () => void;
|
/** Open AI prompt modal */
|
||||||
}
|
|
||||||
export interface ChatData {
|
|
||||||
label?: string;
|
|
||||||
model_config_id?: string;
|
|
||||||
model_parameters?: ModelConfig;
|
|
||||||
list?: ChatItem[];
|
|
||||||
conversation_id?: string | null;
|
|
||||||
}
|
|
||||||
export interface Release {
|
|
||||||
id: string;
|
|
||||||
app_id: string;
|
|
||||||
version: string;
|
|
||||||
release_notes: string;
|
|
||||||
name: string;
|
|
||||||
description?: string;
|
|
||||||
icon: string;
|
|
||||||
icon_type?: string;
|
|
||||||
type: string;
|
|
||||||
visibility: string;
|
|
||||||
config: Config;
|
|
||||||
default_model_config_id?: string;
|
|
||||||
published_by?: string;
|
|
||||||
published_at: number;
|
|
||||||
publisher_name?: string;
|
|
||||||
is_active?: boolean;
|
|
||||||
created_at?: number;
|
|
||||||
updated_at?: number;
|
|
||||||
status?: string;
|
|
||||||
version_name?: string;
|
|
||||||
tagKey: 'current' | 'rolledBack' | 'history';
|
|
||||||
}
|
|
||||||
export interface ReleaseModalRef {
|
|
||||||
handleOpen: () => void;
|
|
||||||
}
|
|
||||||
export interface ReleaseShareModalRef {
|
|
||||||
handleOpen: () => void;
|
|
||||||
}
|
|
||||||
export interface CopyModalRef {
|
|
||||||
handleOpen: () => void;
|
|
||||||
}
|
|
||||||
export interface SubAgentItem {
|
|
||||||
agent_id: string;
|
|
||||||
name: string;
|
|
||||||
role: string;
|
|
||||||
capabilities: string[];
|
|
||||||
is_active?: boolean;
|
|
||||||
}
|
|
||||||
export interface SubAgentModalRef {
|
|
||||||
handleOpen: (agent?: SubAgentItem) => void;
|
|
||||||
}
|
|
||||||
export interface ApiKeyModalRef {
|
|
||||||
handleOpen: () => void;
|
|
||||||
}
|
|
||||||
export interface ApiKeyConfigModalRef {
|
|
||||||
handleOpen: (apiKey: ApiKey) => void;
|
|
||||||
}
|
|
||||||
export interface AiPromptVariableModalRef {
|
|
||||||
handleOpen: () => void;
|
handleOpen: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Chat data structure
|
||||||
|
*/
|
||||||
|
export interface ChatData {
|
||||||
|
/** Chat label */
|
||||||
|
label?: string;
|
||||||
|
/** Model configuration ID */
|
||||||
|
model_config_id?: string;
|
||||||
|
/** Model parameters */
|
||||||
|
model_parameters?: ModelConfig;
|
||||||
|
/** Chat messages list */
|
||||||
|
list?: ChatItem[];
|
||||||
|
/** Conversation ID */
|
||||||
|
conversation_id?: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Release version data
|
||||||
|
*/
|
||||||
|
export interface Release {
|
||||||
|
/** Release ID */
|
||||||
|
id: string;
|
||||||
|
/** Application ID */
|
||||||
|
app_id: string;
|
||||||
|
/** Version number */
|
||||||
|
version: string;
|
||||||
|
/** Release notes */
|
||||||
|
release_notes: string;
|
||||||
|
/** Release name */
|
||||||
|
name: string;
|
||||||
|
/** Release description */
|
||||||
|
description?: string;
|
||||||
|
/** Application icon */
|
||||||
|
icon: string;
|
||||||
|
/** Icon type */
|
||||||
|
icon_type?: string;
|
||||||
|
/** Application type */
|
||||||
|
type: string;
|
||||||
|
/** Visibility setting */
|
||||||
|
visibility: string;
|
||||||
|
/** Configuration snapshot */
|
||||||
|
config: Config;
|
||||||
|
/** Default model configuration ID */
|
||||||
|
default_model_config_id?: string;
|
||||||
|
/** Publisher user ID */
|
||||||
|
published_by?: string;
|
||||||
|
/** Publication timestamp */
|
||||||
|
published_at: number;
|
||||||
|
/** Publisher name */
|
||||||
|
publisher_name?: string;
|
||||||
|
/** Whether release is active */
|
||||||
|
is_active?: boolean;
|
||||||
|
/** Creation timestamp */
|
||||||
|
created_at?: number;
|
||||||
|
/** Last update timestamp */
|
||||||
|
updated_at?: number;
|
||||||
|
/** Release status */
|
||||||
|
status?: string;
|
||||||
|
/** Version name */
|
||||||
|
version_name?: string;
|
||||||
|
/** Tag key for UI display */
|
||||||
|
tagKey: 'current' | 'rolledBack' | 'history';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Release modal ref methods
|
||||||
|
*/
|
||||||
|
export interface ReleaseModalRef {
|
||||||
|
/** Open release modal */
|
||||||
|
handleOpen: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Release share modal ref methods
|
||||||
|
*/
|
||||||
|
export interface ReleaseShareModalRef {
|
||||||
|
/** Open release share modal */
|
||||||
|
handleOpen: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copy modal ref methods
|
||||||
|
*/
|
||||||
|
export interface CopyModalRef {
|
||||||
|
/** Open copy modal */
|
||||||
|
handleOpen: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sub-agent item data
|
||||||
|
*/
|
||||||
|
export interface SubAgentItem {
|
||||||
|
/** Agent ID */
|
||||||
|
agent_id: string;
|
||||||
|
/** Agent name */
|
||||||
|
name: string;
|
||||||
|
/** Agent role */
|
||||||
|
role: string;
|
||||||
|
/** Agent capabilities */
|
||||||
|
capabilities: string[];
|
||||||
|
/** Whether agent is active */
|
||||||
|
is_active?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sub-agent modal ref methods
|
||||||
|
*/
|
||||||
|
export interface SubAgentModalRef {
|
||||||
|
/**
|
||||||
|
* Open sub-agent modal
|
||||||
|
* @param agent - Optional agent data for edit mode
|
||||||
|
*/
|
||||||
|
handleOpen: (agent?: SubAgentItem) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API key modal ref methods
|
||||||
|
*/
|
||||||
|
export interface ApiKeyModalRef {
|
||||||
|
/** Open API key modal */
|
||||||
|
handleOpen: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API key configuration modal ref methods
|
||||||
|
*/
|
||||||
|
export interface ApiKeyConfigModalRef {
|
||||||
|
/**
|
||||||
|
* Open API key configuration modal
|
||||||
|
* @param apiKey - API key data
|
||||||
|
*/
|
||||||
|
handleOpen: (apiKey: ApiKey) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AI prompt variable modal ref methods
|
||||||
|
*/
|
||||||
|
export interface AiPromptVariableModalRef {
|
||||||
|
/** Open AI prompt variable modal */
|
||||||
|
handleOpen: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AI prompt form data
|
||||||
|
*/
|
||||||
export interface AiPromptForm {
|
export interface AiPromptForm {
|
||||||
|
/** Model ID */
|
||||||
model_id?: string;
|
model_id?: string;
|
||||||
|
/** Message content */
|
||||||
message?: string;
|
message?: string;
|
||||||
|
/** Current prompt */
|
||||||
current_prompt?: string;
|
current_prompt?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Chat variable configuration modal ref methods
|
||||||
|
*/
|
||||||
export interface ChatVariableConfigModalRef {
|
export interface ChatVariableConfigModalRef {
|
||||||
|
/**
|
||||||
|
* Open chat variable configuration modal
|
||||||
|
* @param values - Variables list
|
||||||
|
*/
|
||||||
handleOpen: (values: Variable[]) => void;
|
handleOpen: (values: Variable[]) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Statistics item data
|
||||||
|
*/
|
||||||
export interface StatisticsItem {
|
export interface StatisticsItem {
|
||||||
|
/** Count value */
|
||||||
count: number;
|
count: number;
|
||||||
|
/** Date string */
|
||||||
date: string;
|
date: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Statistics data structure
|
||||||
|
*/
|
||||||
export interface StatisticsData {
|
export interface StatisticsData {
|
||||||
|
/** Daily conversations statistics */
|
||||||
daily_conversations: StatisticsItem[];
|
daily_conversations: StatisticsItem[];
|
||||||
|
/** Daily new users statistics */
|
||||||
daily_new_users: StatisticsItem[];
|
daily_new_users: StatisticsItem[];
|
||||||
|
/** Daily API calls statistics */
|
||||||
daily_api_calls: StatisticsItem[];
|
daily_api_calls: StatisticsItem[];
|
||||||
|
/** Daily tokens usage statistics */
|
||||||
daily_tokens: StatisticsItem[];
|
daily_tokens: StatisticsItem[];
|
||||||
|
/** Total conversations count */
|
||||||
total_conversations: number;
|
total_conversations: number;
|
||||||
|
/** Total new users count */
|
||||||
total_new_users: number;
|
total_new_users: number;
|
||||||
|
/** Total API calls count */
|
||||||
total_api_calls: number;
|
total_api_calls: number;
|
||||||
|
/** Total tokens used */
|
||||||
total_tokens: number;
|
total_tokens: number;
|
||||||
}
|
}
|
||||||
@@ -1,32 +1,57 @@
|
|||||||
|
/*
|
||||||
|
* @Author: ZhaoYing
|
||||||
|
* @Date: 2026-02-03 16:34:09
|
||||||
|
* @Last Modified by: ZhaoYing
|
||||||
|
* @Last Modified time: 2026-02-03 16:35:30
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Application Modal
|
||||||
|
* Modal for creating and editing applications
|
||||||
|
* Supports three application types: agent, multi_agent, and workflow
|
||||||
|
*/
|
||||||
|
|
||||||
import { forwardRef, useImperativeHandle, useState } from 'react';
|
import { forwardRef, useImperativeHandle, useState } from 'react';
|
||||||
import { Form, Input } from 'antd';
|
import { Form, Input } from 'antd';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import RadioGroupCard from '@/components/RadioGroupCard'
|
import RadioGroupCard from '@/components/RadioGroupCard'
|
||||||
import AgentIcon from '@/assets/images/application/agent.svg'
|
import AgentIcon from '@/assets/images/application/agent.svg'
|
||||||
import ClusterIcon from '@/assets/images/application/cluster.svg'
|
import ClusterIcon from '@/assets/images/application/cluster.svg'
|
||||||
import WorkflowIcon from '@/assets/images/application/workflow.svg'
|
import WorkflowIcon from '@/assets/images/application/workflow.svg'
|
||||||
|
|
||||||
import type { ApplicationModalData, ApplicationModalRef, Application } from '../types'
|
import type { ApplicationModalData, ApplicationModalRef, Application } from '../types'
|
||||||
import RbModal from '@/components/RbModal'
|
import RbModal from '@/components/RbModal'
|
||||||
import { addApplication, updateApplication } from '@/api/application'
|
import { addApplication, updateApplication } from '@/api/application'
|
||||||
|
|
||||||
const FormItem = Form.Item;
|
const FormItem = Form.Item;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component props
|
||||||
|
*/
|
||||||
interface ApplicationModalProps {
|
interface ApplicationModalProps {
|
||||||
|
/** Callback to refresh application list */
|
||||||
refresh: () => void;
|
refresh: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Supported application types
|
||||||
|
*/
|
||||||
const types = [
|
const types = [
|
||||||
'agent',
|
'agent',
|
||||||
'multi_agent',
|
'multi_agent',
|
||||||
'workflow'
|
'workflow'
|
||||||
]
|
]
|
||||||
|
/**
|
||||||
|
* Application type icon mapping
|
||||||
|
*/
|
||||||
const typeIcons: Record<string, string> = {
|
const typeIcons: Record<string, string> = {
|
||||||
agent: AgentIcon,
|
agent: AgentIcon,
|
||||||
multi_agent: ClusterIcon,
|
multi_agent: ClusterIcon,
|
||||||
workflow: WorkflowIcon
|
workflow: WorkflowIcon
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modal for creating and editing applications
|
||||||
|
*/
|
||||||
const ApplicationModal = forwardRef<ApplicationModalRef, ApplicationModalProps>(({
|
const ApplicationModal = forwardRef<ApplicationModalRef, ApplicationModalProps>(({
|
||||||
refresh
|
refresh
|
||||||
}, ref) => {
|
}, ref) => {
|
||||||
@@ -38,7 +63,7 @@ const ApplicationModal = forwardRef<ApplicationModalRef, ApplicationModalProps>(
|
|||||||
|
|
||||||
const values = Form.useWatch([], form);
|
const values = Form.useWatch([], form);
|
||||||
|
|
||||||
// 封装取消方法,添加关闭弹窗逻辑
|
/** Close modal and reset form */
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
setVisible(false);
|
setVisible(false);
|
||||||
form.resetFields();
|
form.resetFields();
|
||||||
@@ -46,6 +71,7 @@ const ApplicationModal = forwardRef<ApplicationModalRef, ApplicationModalProps>(
|
|||||||
setEditVo(null)
|
setEditVo(null)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** Open modal with optional application data for editing */
|
||||||
const handleOpen = (application?: Application) => {
|
const handleOpen = (application?: Application) => {
|
||||||
if (application) {
|
if (application) {
|
||||||
setEditVo(application || null)
|
setEditVo(application || null)
|
||||||
@@ -59,7 +85,7 @@ const ApplicationModal = forwardRef<ApplicationModalRef, ApplicationModalProps>(
|
|||||||
}
|
}
|
||||||
setVisible(true);
|
setVisible(true);
|
||||||
};
|
};
|
||||||
// 封装保存方法,添加提交逻辑
|
/** Save application (create or update) */
|
||||||
const handleSave = () => {
|
const handleSave = () => {
|
||||||
form
|
form
|
||||||
.validateFields()
|
.validateFields()
|
||||||
@@ -83,7 +109,7 @@ const ApplicationModal = forwardRef<ApplicationModalRef, ApplicationModalProps>(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 暴露给父组件的方法
|
/** Expose methods to parent component */
|
||||||
useImperativeHandle(ref, () => ({
|
useImperativeHandle(ref, () => ({
|
||||||
handleOpen,
|
handleOpen,
|
||||||
handleClose
|
handleClose
|
||||||
|
|||||||
@@ -1,8 +1,21 @@
|
|||||||
|
/*
|
||||||
|
* @Author: ZhaoYing
|
||||||
|
* @Date: 2026-02-03 16:34:12
|
||||||
|
* @Last Modified by: ZhaoYing
|
||||||
|
* @Last Modified time: 2026-02-03 16:34:12
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Application Management Page
|
||||||
|
* Displays and manages all applications in the workspace
|
||||||
|
* Supports creating, editing, and deleting applications
|
||||||
|
*/
|
||||||
|
|
||||||
import React, { useState, useRef } from 'react';
|
import React, { useState, useRef } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Button, Row, Col, App } from 'antd';
|
import { Button, Row, Col, App } from 'antd';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import { DeleteOutlined } from '@ant-design/icons';
|
import { DeleteOutlined } from '@ant-design/icons';
|
||||||
|
|
||||||
import type { Application, ApplicationModalRef, Query } from './types';
|
import type { Application, ApplicationModalRef, Query } from './types';
|
||||||
import ApplicationModal from './components/ApplicationModal';
|
import ApplicationModal from './components/ApplicationModal';
|
||||||
import SearchInput from '@/components/SearchInput'
|
import SearchInput from '@/components/SearchInput'
|
||||||
@@ -11,6 +24,9 @@ import { getApplicationListUrl, deleteApplication } from '@/api/application'
|
|||||||
import PageScrollList, { type PageScrollListRef } from '@/components/PageScrollList'
|
import PageScrollList, { type PageScrollListRef } from '@/components/PageScrollList'
|
||||||
import { formatDateTime } from '@/utils/format';
|
import { formatDateTime } from '@/utils/format';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Application management main component
|
||||||
|
*/
|
||||||
const ApplicationManagement: React.FC = () => {
|
const ApplicationManagement: React.FC = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { modal } = App.useApp();
|
const { modal } = App.useApp();
|
||||||
@@ -18,16 +34,20 @@ const ApplicationManagement: React.FC = () => {
|
|||||||
const applicationModalRef = useRef<ApplicationModalRef>(null);
|
const applicationModalRef = useRef<ApplicationModalRef>(null);
|
||||||
const scrollListRef = useRef<PageScrollListRef>(null)
|
const scrollListRef = useRef<PageScrollListRef>(null)
|
||||||
|
|
||||||
|
/** Refresh application list */
|
||||||
const refresh = () => {
|
const refresh = () => {
|
||||||
scrollListRef.current?.refresh();
|
scrollListRef.current?.refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Open create application modal */
|
||||||
const handleCreate = () => {
|
const handleCreate = () => {
|
||||||
applicationModalRef.current?.handleOpen();
|
applicationModalRef.current?.handleOpen();
|
||||||
}
|
}
|
||||||
|
/** Navigate to application configuration page */
|
||||||
const handleEdit = (item: Application) => {
|
const handleEdit = (item: Application) => {
|
||||||
window.open(`/#/application/config/${item.id}`);
|
window.open(`/#/application/config/${item.id}`);
|
||||||
}
|
}
|
||||||
|
/** Delete application with confirmation */
|
||||||
const handleDelete = (item: Application) => {
|
const handleDelete = (item: Application) => {
|
||||||
modal.confirm({
|
modal.confirm({
|
||||||
title: t('common.confirmDeleteDesc', { name: item.name }),
|
title: t('common.confirmDeleteDesc', { name: item.name }),
|
||||||
|
|||||||
@@ -1,76 +1,174 @@
|
|||||||
// 应用数据类型
|
/*
|
||||||
|
* @Author: ZhaoYing
|
||||||
|
* @Date: 2026-02-03 16:34:15
|
||||||
|
* @Last Modified by: ZhaoYing
|
||||||
|
* @Last Modified time: 2026-02-03 16:34:15
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Type definitions for Application Management
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search query parameters
|
||||||
|
*/
|
||||||
export interface Query {
|
export interface Query {
|
||||||
|
/** Search keyword */
|
||||||
search: string;
|
search: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Application data structure
|
||||||
|
*/
|
||||||
export interface Application {
|
export interface Application {
|
||||||
|
/** Application ID */
|
||||||
id: string;
|
id: string;
|
||||||
|
/** Workspace ID */
|
||||||
workspace_id: string;
|
workspace_id: string;
|
||||||
|
/** Creator user ID */
|
||||||
created_by: string;
|
created_by: string;
|
||||||
|
/** Application name */
|
||||||
name: string;
|
name: string;
|
||||||
|
/** Application description */
|
||||||
description?: string;
|
description?: string;
|
||||||
|
/** Icon URL */
|
||||||
icon?: string;
|
icon?: string;
|
||||||
|
/** Icon type */
|
||||||
icon_type?: string;
|
icon_type?: string;
|
||||||
|
/** Application type: agent, multi_agent, or workflow */
|
||||||
type: 'agent' | 'multi_agent' | 'workflow';
|
type: 'agent' | 'multi_agent' | 'workflow';
|
||||||
|
/** Visibility setting */
|
||||||
visibility: string;
|
visibility: string;
|
||||||
|
/** Application status */
|
||||||
status: string;
|
status: string;
|
||||||
|
/** Application tags */
|
||||||
tags: string[];
|
tags: string[];
|
||||||
|
/** Current release version ID */
|
||||||
current_release_id?: string;
|
current_release_id?: string;
|
||||||
|
/** Whether application is active */
|
||||||
is_active: boolean;
|
is_active: boolean;
|
||||||
|
/** Whether application is shared */
|
||||||
is_shared: boolean;
|
is_shared: boolean;
|
||||||
|
/** Creation timestamp */
|
||||||
created_at: number;
|
created_at: number;
|
||||||
|
/** Last update timestamp */
|
||||||
updated_at: number;
|
updated_at: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建表单数据类型
|
/**
|
||||||
|
* Application creation/edit form data
|
||||||
|
*/
|
||||||
export interface ApplicationModalData {
|
export interface ApplicationModalData {
|
||||||
|
/** Application name */
|
||||||
name: string;
|
name: string;
|
||||||
|
/** Application type */
|
||||||
type: string;
|
type: string;
|
||||||
|
/** Application description */
|
||||||
description?: string;
|
description?: string;
|
||||||
|
/** Icon upload data */
|
||||||
icon: {
|
icon: {
|
||||||
url: string;
|
url: string;
|
||||||
uid: string | number;
|
uid: string | number;
|
||||||
}[];
|
}[];
|
||||||
}
|
}
|
||||||
|
|
||||||
// 定义组件暴露的方法接口
|
/**
|
||||||
|
* Application modal ref interface
|
||||||
|
*/
|
||||||
export interface ApplicationModalRef {
|
export interface ApplicationModalRef {
|
||||||
|
/** Open modal with optional application data for editing */
|
||||||
handleOpen: (application?: Application) => void;
|
handleOpen: (application?: Application) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Model configuration modal ref interface
|
||||||
|
*/
|
||||||
export interface ModelConfigModalRef {
|
export interface ModelConfigModalRef {
|
||||||
|
/** Open modal with application data */
|
||||||
handleOpen: (application?: Application) => void;
|
handleOpen: (application?: Application) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Model configuration form data
|
||||||
|
*/
|
||||||
export interface ModelConfigModalData {
|
export interface ModelConfigModalData {
|
||||||
|
/** Selected model */
|
||||||
model: string;
|
model: string;
|
||||||
|
/** Additional configuration fields */
|
||||||
[key: string]: string;
|
[key: string]: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AI prompt modal ref interface
|
||||||
|
*/
|
||||||
export interface AiPromptModalRef {
|
export interface AiPromptModalRef {
|
||||||
|
/** Open modal with application data */
|
||||||
handleOpen: (application?: Application) => void;
|
handleOpen: (application?: Application) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Variable modal ref interface
|
||||||
|
*/
|
||||||
export interface VariableModalRef {
|
export interface VariableModalRef {
|
||||||
|
/** Open modal with application data */
|
||||||
handleOpen: (application?: Application) => void;
|
handleOpen: (application?: Application) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Variable modal props
|
||||||
|
*/
|
||||||
export interface VariableModalProps {
|
export interface VariableModalProps {
|
||||||
|
/** Callback to refresh variable list */
|
||||||
refresh: () => void;
|
refresh: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Variable edit modal ref interface
|
||||||
|
*/
|
||||||
export interface VariableEditModalRef {
|
export interface VariableEditModalRef {
|
||||||
|
/** Open modal with optional variable data */
|
||||||
handleOpen: (values?: Variable) => void;
|
handleOpen: (values?: Variable) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Variable data structure
|
||||||
|
*/
|
||||||
export interface Variable {
|
export interface Variable {
|
||||||
|
/** Variable index */
|
||||||
index?: number;
|
index?: number;
|
||||||
|
/** Variable type */
|
||||||
type: string;
|
type: string;
|
||||||
|
/** Variable key */
|
||||||
key: string;
|
key: string;
|
||||||
|
/** Variable name */
|
||||||
name: string;
|
name: string;
|
||||||
|
/** Maximum length for string types */
|
||||||
maxLength?: number;
|
maxLength?: number;
|
||||||
|
/** Default value */
|
||||||
defaultValue?: string;
|
defaultValue?: string;
|
||||||
|
/** Options for select type */
|
||||||
options?: string[];
|
options?: string[];
|
||||||
|
/** Whether variable is required */
|
||||||
required: boolean;
|
required: boolean;
|
||||||
|
/** Whether variable is hidden */
|
||||||
hidden?: boolean;
|
hidden?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API extension configuration data
|
||||||
|
*/
|
||||||
export interface ApiExtensionModalData {
|
export interface ApiExtensionModalData {
|
||||||
|
/** Extension name */
|
||||||
name: string;
|
name: string;
|
||||||
|
/** API endpoint URL */
|
||||||
apiEndpoint: string;
|
apiEndpoint: string;
|
||||||
|
/** API authentication key */
|
||||||
apiKey: string;
|
apiKey: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API extension modal ref interface
|
||||||
|
*/
|
||||||
export interface ApiExtensionModalRef {
|
export interface ApiExtensionModalRef {
|
||||||
|
/** Open API extension modal */
|
||||||
handleOpen: () => void;
|
handleOpen: () => void;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,23 @@
|
|||||||
|
/*
|
||||||
|
* @Author: ZhaoYing
|
||||||
|
* @Date: 2026-02-03 16:58:03
|
||||||
|
* @Last Modified by: ZhaoYing
|
||||||
|
* @Last Modified time: 2026-02-03 16:58:35
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Conversation Page
|
||||||
|
* Public conversation interface for shared applications
|
||||||
|
* Supports conversation history, streaming responses, and memory/web search features
|
||||||
|
*/
|
||||||
|
|
||||||
import { type FC, useState, useEffect, useRef } from 'react'
|
import { type FC, useState, useEffect, useRef } from 'react'
|
||||||
import { useParams, useLocation } from 'react-router-dom'
|
import { useParams, useLocation } from 'react-router-dom'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import InfiniteScroll from 'react-infinite-scroll-component';
|
import InfiniteScroll from 'react-infinite-scroll-component';
|
||||||
import { Flex, Skeleton, Form } from 'antd'
|
import { Flex, Skeleton, Form } from 'antd'
|
||||||
import clsx from 'clsx'
|
import clsx from 'clsx'
|
||||||
import AnalysisEmptyIcon from '@/assets/images/conversation/analysisEmpty.svg'
|
import dayjs from 'dayjs'
|
||||||
|
|
||||||
import { getConversationHistory, sendConversation, getConversationDetail, getShareToken } from '@/api/application'
|
import { getConversationHistory, sendConversation, getConversationDetail, getShareToken } from '@/api/application'
|
||||||
import type { HistoryItem, QueryParams } from './types'
|
import type { HistoryItem, QueryParams } from './types'
|
||||||
import Empty from '@/components/Empty'
|
import Empty from '@/components/Empty'
|
||||||
@@ -19,9 +32,11 @@ import MemoryFunctionIcon from '@/assets/images/conversation/memoryFunction.svg'
|
|||||||
import OnlineIcon from '@/assets/images/conversation/online.svg'
|
import OnlineIcon from '@/assets/images/conversation/online.svg'
|
||||||
import OnlineCheckedIcon from '@/assets/images/conversation/onlineChecked.svg'
|
import OnlineCheckedIcon from '@/assets/images/conversation/onlineChecked.svg'
|
||||||
import MemoryFunctionCheckedIcon from '@/assets/images/conversation/memoryFunctionChecked.svg'
|
import MemoryFunctionCheckedIcon from '@/assets/images/conversation/memoryFunctionChecked.svg'
|
||||||
import dayjs from 'dayjs'
|
|
||||||
import { type SSEMessage } from '@/utils/stream'
|
import { type SSEMessage } from '@/utils/stream'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Conversation component for shared applications
|
||||||
|
*/
|
||||||
const Conversation: FC = () => {
|
const Conversation: FC = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { token } = useParams()
|
const { token } = useParams()
|
||||||
@@ -61,7 +76,7 @@ const Conversation: FC = () => {
|
|||||||
}
|
}
|
||||||
}, [token, shareToken, page, hasMore, historyList])
|
}, [token, shareToken, page, hasMore, historyList])
|
||||||
|
|
||||||
// 按日期分组历史记录
|
/** Group conversation history by date */
|
||||||
const groupHistoryByDate = (items: HistoryItem[]): Record<string, HistoryItem[]> => {
|
const groupHistoryByDate = (items: HistoryItem[]): Record<string, HistoryItem[]> => {
|
||||||
return items.reduce((groups: Record<string, HistoryItem[]>, item) => {
|
return items.reduce((groups: Record<string, HistoryItem[]>, item) => {
|
||||||
const date = formatDateTime(item.created_at, 'YYYY-MM-DD')
|
const date = formatDateTime(item.created_at, 'YYYY-MM-DD')
|
||||||
@@ -74,6 +89,7 @@ const Conversation: FC = () => {
|
|||||||
}, {});
|
}, {});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Fetch conversation history with pagination */
|
||||||
const getHistory = (flag: boolean = false) => {
|
const getHistory = (flag: boolean = false) => {
|
||||||
if (!token || (pageLoading || !hasMore) && !flag) {
|
if (!token || (pageLoading || !hasMore) && !flag) {
|
||||||
return
|
return
|
||||||
@@ -104,6 +120,7 @@ const Conversation: FC = () => {
|
|||||||
setPageLoading(false);
|
setPageLoading(false);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
/** Switch to different conversation or start new one */
|
||||||
const handleChangeHistory = (id: string | null) => {
|
const handleChangeHistory = (id: string | null) => {
|
||||||
if (id !== conversation_id) {
|
if (id !== conversation_id) {
|
||||||
setConversationId(id)
|
setConversationId(id)
|
||||||
@@ -124,6 +141,7 @@ const Conversation: FC = () => {
|
|||||||
}
|
}
|
||||||
}, [conversation_id])
|
}, [conversation_id])
|
||||||
|
|
||||||
|
/** Add user message to chat */
|
||||||
const addUserMessage = (message: string = '') => {
|
const addUserMessage = (message: string = '') => {
|
||||||
const newUserMessage: ChatItem = {
|
const newUserMessage: ChatItem = {
|
||||||
conversation_id,
|
conversation_id,
|
||||||
@@ -133,6 +151,7 @@ const Conversation: FC = () => {
|
|||||||
};
|
};
|
||||||
setChatList(prev => [...prev, newUserMessage])
|
setChatList(prev => [...prev, newUserMessage])
|
||||||
}
|
}
|
||||||
|
/** Add empty assistant message placeholder */
|
||||||
const addAssistantMessage = () => {
|
const addAssistantMessage = () => {
|
||||||
const newAssistantMessage: ChatItem = {
|
const newAssistantMessage: ChatItem = {
|
||||||
created_at: Date.now(),
|
created_at: Date.now(),
|
||||||
@@ -141,6 +160,7 @@ const Conversation: FC = () => {
|
|||||||
}
|
}
|
||||||
setChatList(prev => [...prev, newAssistantMessage])
|
setChatList(prev => [...prev, newAssistantMessage])
|
||||||
}
|
}
|
||||||
|
/** Update assistant message with streaming content */
|
||||||
const updateAssistantMessage = (content: string = '') => {
|
const updateAssistantMessage = (content: string = '') => {
|
||||||
if (!content) return
|
if (!content) return
|
||||||
if (streamLoading) {
|
if (streamLoading) {
|
||||||
@@ -164,6 +184,7 @@ const Conversation: FC = () => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Send message and handle streaming response */
|
||||||
const handleSend = () => {
|
const handleSend = () => {
|
||||||
if (!token || !shareToken) {
|
if (!token || !shareToken) {
|
||||||
return
|
return
|
||||||
@@ -261,7 +282,7 @@ const Conversation: FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="rb:relative rb:h-screen rb:px-4 rb:flex-[1_1_auto]">
|
<div className="rb:relative rb:h-screen rb:px-4 rb:flex-[1_1_auto]">
|
||||||
<div className='rb:w-[760px] rb:h-screen rb:mx-auto rb:pt-10'>
|
<div className='rb:w-190 rb:h-screen rb:mx-auto rb:pt-10'>
|
||||||
<Chat
|
<Chat
|
||||||
empty={<Empty url={ChatEmpty} className="rb:h-full" size={[320,180]} title={t('memoryConversation.chatEmpty')} subTitle={t('memoryConversation.emptyDesc')} />}
|
empty={<Empty url={ChatEmpty} className="rb:h-full" size={[320,180]} title={t('memoryConversation.chatEmpty')} subTitle={t('memoryConversation.emptyDesc')} />}
|
||||||
contentClassName="rb:h-[calc(100%-152px)] "
|
contentClassName="rb:h-[calc(100%-152px)] "
|
||||||
|
|||||||
@@ -1,21 +1,53 @@
|
|||||||
|
/*
|
||||||
|
* @Author: ZhaoYing
|
||||||
|
* @Date: 2026-02-03 16:57:46
|
||||||
|
* @Last Modified by: ZhaoYing
|
||||||
|
* @Last Modified time: 2026-02-03 16:57:46
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Type definitions for Conversation
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Conversation history item
|
||||||
|
*/
|
||||||
export interface HistoryItem {
|
export interface HistoryItem {
|
||||||
|
/** Conversation ID */
|
||||||
id: string;
|
id: string;
|
||||||
|
/** Application ID */
|
||||||
app_id: string;
|
app_id: string;
|
||||||
|
/** Workspace ID */
|
||||||
workspace_id: string;
|
workspace_id: string;
|
||||||
|
/** User ID */
|
||||||
user_id: string | null;
|
user_id: string | null;
|
||||||
|
/** Conversation title */
|
||||||
title: string;
|
title: string;
|
||||||
|
/** Conversation summary */
|
||||||
summary?: string
|
summary?: string
|
||||||
|
/** Whether conversation is draft */
|
||||||
is_draft: boolean;
|
is_draft: boolean;
|
||||||
|
/** Number of messages */
|
||||||
message_count: number;
|
message_count: number;
|
||||||
|
/** Whether conversation is active */
|
||||||
is_active: boolean;
|
is_active: boolean;
|
||||||
|
/** Creation timestamp */
|
||||||
created_at: number;
|
created_at: number;
|
||||||
|
/** Update timestamp */
|
||||||
updated_at: number;
|
updated_at: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Query parameters for sending messages
|
||||||
|
*/
|
||||||
export interface QueryParams {
|
export interface QueryParams {
|
||||||
|
/** Message content */
|
||||||
message?: string;
|
message?: string;
|
||||||
|
/** Whether to enable web search */
|
||||||
web_search?: boolean;
|
web_search?: boolean;
|
||||||
|
/** Whether to enable memory */
|
||||||
memory?: boolean;
|
memory?: boolean;
|
||||||
|
/** Whether to use streaming response */
|
||||||
stream: boolean;
|
stream: boolean;
|
||||||
|
/** Current conversation ID */
|
||||||
conversation_id?: string | null;
|
conversation_id?: string | null;
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,20 @@
|
|||||||
|
/*
|
||||||
|
* @Author: ZhaoYing
|
||||||
|
* @Date: 2026-02-03 16:56:54
|
||||||
|
* @Last Modified by: ZhaoYing
|
||||||
|
* @Last Modified time: 2026-02-03 16:57:17
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Emotion Engine Configuration Page
|
||||||
|
* Configures emotion analysis settings for memory system
|
||||||
|
* Includes model selection, intensity threshold, and feature toggles
|
||||||
|
*/
|
||||||
|
|
||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { Row, Col, Form, Slider, Button, Alert, message, Space } from 'antd';
|
import { Row, Col, Form, Slider, Button, Alert, message, Space } from 'antd';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import RbCard from '@/components/RbCard/Card';
|
import RbCard from '@/components/RbCard/Card';
|
||||||
import strategyImpactSimulator from '@/assets/images/memory/strategyImpactSimulator.svg'
|
import strategyImpactSimulator from '@/assets/images/memory/strategyImpactSimulator.svg'
|
||||||
import { getMemoryEmotionConfig, updateMemoryEmotionConfig } from '@/api/memory'
|
import { getMemoryEmotionConfig, updateMemoryEmotionConfig } from '@/api/memory'
|
||||||
@@ -11,6 +24,9 @@ import { getModelListUrl } from '@/api/models'
|
|||||||
import Tag from '@/components/Tag'
|
import Tag from '@/components/Tag'
|
||||||
import SwitchFormItem from '@/components/FormItem/SwitchFormItem'
|
import SwitchFormItem from '@/components/FormItem/SwitchFormItem'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configuration field definitions
|
||||||
|
*/
|
||||||
const configList = [
|
const configList = [
|
||||||
{
|
{
|
||||||
key: 'emotion_enabled',
|
key: 'emotion_enabled',
|
||||||
@@ -41,6 +57,9 @@ const configList = [
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emotion engine configuration component
|
||||||
|
*/
|
||||||
const EmotionEngine: React.FC = () => {
|
const EmotionEngine: React.FC = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { id } = useParams();
|
const { id } = useParams();
|
||||||
@@ -55,6 +74,7 @@ const EmotionEngine: React.FC = () => {
|
|||||||
getConfigData()
|
getConfigData()
|
||||||
}, [id])
|
}, [id])
|
||||||
|
|
||||||
|
/** Fetch emotion engine configuration */
|
||||||
const getConfigData = () => {
|
const getConfigData = () => {
|
||||||
if (!id) {
|
if (!id) {
|
||||||
return
|
return
|
||||||
@@ -72,9 +92,11 @@ const EmotionEngine: React.FC = () => {
|
|||||||
console.error('Failed to load data');
|
console.error('Failed to load data');
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
/** Reset form to saved configuration */
|
||||||
const handleReset = () => {
|
const handleReset = () => {
|
||||||
form.setFieldsValue(configData);
|
form.setFieldsValue(configData);
|
||||||
}
|
}
|
||||||
|
/** Save emotion engine configuration */
|
||||||
const handleSave = () => {
|
const handleSave = () => {
|
||||||
if (!id) {
|
if (!id) {
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -1,48 +1,99 @@
|
|||||||
// 标签表单数据类型
|
/*
|
||||||
|
* @Author: ZhaoYing
|
||||||
|
* @Date: 2026-02-03 16:57:37
|
||||||
|
* @Last Modified by: ZhaoYing
|
||||||
|
* @Last Modified time: 2026-02-03 16:57:37
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Type definitions for Emotion Engine
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tag form data (legacy, not used in current implementation)
|
||||||
|
*/
|
||||||
export interface TagFormData {
|
export interface TagFormData {
|
||||||
|
/** Tag name */
|
||||||
tagName: string;
|
tagName: string;
|
||||||
|
/** Tag type */
|
||||||
type: string;
|
type: string;
|
||||||
|
/** Tag color */
|
||||||
color: string;
|
color: string;
|
||||||
|
/** Tag description */
|
||||||
description?: string;
|
description?: string;
|
||||||
|
/** Applicable scope */
|
||||||
applicableScope?: string[];
|
applicableScope?: string[];
|
||||||
|
/** Semantic expansion */
|
||||||
semanticExpansion?: string;
|
semanticExpansion?: string;
|
||||||
|
/** Whether tag is active */
|
||||||
isActive?: boolean;
|
isActive?: boolean;
|
||||||
// 扩展字段用于区分编辑和新增操作
|
/** Whether in editing mode */
|
||||||
isEditing?: boolean;
|
isEditing?: boolean;
|
||||||
|
/** Tag ID */
|
||||||
tagId?: string;
|
tagId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 记忆总览数据类型
|
/**
|
||||||
|
* Memory overview record (legacy, not used in current implementation)
|
||||||
|
*/
|
||||||
export interface MemoryOverviewRecord {
|
export interface MemoryOverviewRecord {
|
||||||
|
/** Record ID */
|
||||||
id: number;
|
id: number;
|
||||||
|
/** Memory ID */
|
||||||
memoryID: string,
|
memoryID: string,
|
||||||
|
/** Content summary */
|
||||||
contentSummary: string;
|
contentSummary: string;
|
||||||
|
/** Memory type */
|
||||||
type: string;
|
type: string;
|
||||||
|
/** Creation time */
|
||||||
createTime: string;
|
createTime: string;
|
||||||
|
/** Last call time */
|
||||||
lastCallTime: string;
|
lastCallTime: string;
|
||||||
|
/** Retention degree */
|
||||||
retentionDegree: string;
|
retentionDegree: string;
|
||||||
|
/** Status */
|
||||||
status: string;
|
status: string;
|
||||||
}
|
}
|
||||||
// 定义组件暴露的方法接口
|
|
||||||
|
/**
|
||||||
|
* Memory overview form ref interface (legacy)
|
||||||
|
*/
|
||||||
export interface MemoryOverviewFormRef {
|
export interface MemoryOverviewFormRef {
|
||||||
|
/** Open form with optional record */
|
||||||
handleOpen: (memoryOverview?: MemoryOverviewRecord | null) => void;
|
handleOpen: (memoryOverview?: MemoryOverviewRecord | null) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 遗忘曲线数据类型
|
/**
|
||||||
|
* Forgetting curve record (legacy, not used in current implementation)
|
||||||
|
*/
|
||||||
export interface CurveRecord {
|
export interface CurveRecord {
|
||||||
|
/** Memory ID */
|
||||||
memoryID: string;
|
memoryID: string;
|
||||||
|
/** Memory type */
|
||||||
type: string;
|
type: string;
|
||||||
|
/** Current retention rate */
|
||||||
currentRetentionRate: string;
|
currentRetentionRate: string;
|
||||||
|
/** Finally activated time */
|
||||||
finallyActivated: string;
|
finallyActivated: string;
|
||||||
|
/** Expected forgetting time */
|
||||||
expectedForgettingTime: string;
|
expectedForgettingTime: string;
|
||||||
|
/** Reinforcement count */
|
||||||
reinforcementCount: string;
|
reinforcementCount: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emotion engine configuration form
|
||||||
|
*/
|
||||||
export interface ConfigForm {
|
export interface ConfigForm {
|
||||||
|
/** Configuration ID */
|
||||||
config_id: number | string;
|
config_id: number | string;
|
||||||
|
/** Whether emotion engine is enabled */
|
||||||
emotion_enabled: boolean;
|
emotion_enabled: boolean;
|
||||||
|
/** Emotion analysis model ID */
|
||||||
emotion_model_id: string;
|
emotion_model_id: string;
|
||||||
|
/** Whether to extract keywords */
|
||||||
emotion_extract_keywords: boolean;
|
emotion_extract_keywords: boolean;
|
||||||
|
/** Minimum emotion intensity threshold (0-1) */
|
||||||
emotion_min_intensity: number;
|
emotion_min_intensity: number;
|
||||||
|
/** Whether to enable subject extraction */
|
||||||
emotion_enable_subject: boolean;
|
emotion_enable_subject: boolean;
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,24 @@
|
|||||||
|
/*
|
||||||
|
* @Author: ZhaoYing
|
||||||
|
* @Date: 2026-02-03 17:00:20
|
||||||
|
* @Last Modified by: ZhaoYing
|
||||||
|
* @Last Modified time: 2026-02-04 10:03:35
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Line Chart Component
|
||||||
|
* Visualizes forgetting curves based on different configurations
|
||||||
|
* Compares current config with quick/slow forgetting presets
|
||||||
|
*/
|
||||||
|
|
||||||
import { type FC, useRef, useEffect, useState, useMemo, useCallback } from 'react'
|
import { type FC, useRef, useEffect, useState, useMemo, useCallback } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import ReactEcharts from 'echarts-for-react';
|
import ReactEcharts from 'echarts-for-react';
|
||||||
|
|
||||||
import type { ConfigForm } from '../types'
|
import type { ConfigForm } from '../types'
|
||||||
|
|
||||||
// 定义当前数据类型
|
/**
|
||||||
|
* Current data item type
|
||||||
|
*/
|
||||||
type CurrentDataItem = {
|
type CurrentDataItem = {
|
||||||
name: string;
|
name: string;
|
||||||
data: number[];
|
data: number[];
|
||||||
@@ -16,7 +31,9 @@ type CurrentDataItem = {
|
|||||||
emphasis: { focus: string };
|
emphasis: { focus: string };
|
||||||
};
|
};
|
||||||
|
|
||||||
// 定义图表系列数据类型
|
/**
|
||||||
|
* Chart series data item type
|
||||||
|
*/
|
||||||
type SeriesDataItem = {
|
type SeriesDataItem = {
|
||||||
name: string;
|
name: string;
|
||||||
data: number[];
|
data: number[];
|
||||||
@@ -29,17 +46,26 @@ type SeriesDataItem = {
|
|||||||
emphasis: { focus: string };
|
emphasis: { focus: string };
|
||||||
};
|
};
|
||||||
|
|
||||||
// 定义简化的配置类型,只包含图表计算需要的属性
|
/**
|
||||||
|
* Simplified config type for chart calculations
|
||||||
|
*/
|
||||||
interface ChartConfig {
|
interface ChartConfig {
|
||||||
lambda_mem: number;
|
lambda_mem: number;
|
||||||
lambda_time: number;
|
lambda_time: number;
|
||||||
offset: number;
|
offset: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component props
|
||||||
|
*/
|
||||||
interface LineCardProps {
|
interface LineCardProps {
|
||||||
|
/** Forgetting engine configuration */
|
||||||
config: ConfigForm
|
config: ConfigForm
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ECharts series configuration
|
||||||
|
*/
|
||||||
const SeriesConfig = {
|
const SeriesConfig = {
|
||||||
type: 'line',
|
type: 'line',
|
||||||
smooth: true,
|
smooth: true,
|
||||||
@@ -55,10 +81,18 @@ const SeriesConfig = {
|
|||||||
focus: 'series'
|
focus: 'series'
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Chart color palette
|
||||||
|
*/
|
||||||
const Colors = ['#155EEF', '#4DA8FF', '#FFB048']
|
const Colors = ['#155EEF', '#4DA8FF', '#FFB048']
|
||||||
|
|
||||||
|
|
||||||
// 快速遗忘:lambda_mem=0.3,lambda_time=1,offset=0.05; 慢速遗忘:lambda_mem=1,lambda_time=0.3,offset=0.2
|
/**
|
||||||
|
* Line chart component for forgetting curves
|
||||||
|
* Formula: R = offset + (1 - offset) × e^(-λ_time × t / λ_mem)
|
||||||
|
* Quick forgetting: λ_mem=0.3, λ_time=1, offset=0.05
|
||||||
|
* Slow forgetting: λ_mem=1, λ_time=0.3, offset=0.2
|
||||||
|
*/
|
||||||
const LineChart: FC<LineCardProps> = ({ config }) => {
|
const LineChart: FC<LineCardProps> = ({ config }) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const chartRef = useRef<ReactEcharts>(null);
|
const chartRef = useRef<ReactEcharts>(null);
|
||||||
@@ -92,7 +126,6 @@ const LineChart: FC<LineCardProps> = ({ config }) => {
|
|||||||
}, [t])
|
}, [t])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// 语言切换时重新生成数据
|
|
||||||
if (config) {
|
if (config) {
|
||||||
getCaculateData(config)
|
getCaculateData(config)
|
||||||
}
|
}
|
||||||
@@ -131,8 +164,7 @@ const LineChart: FC<LineCardProps> = ({ config }) => {
|
|||||||
}
|
}
|
||||||
}, [config])
|
}, [config])
|
||||||
|
|
||||||
// 快速遗忘:lambda_mem=0.3,lambda_time=1,offset=0.05;
|
/** Initialize preset forgetting curves */
|
||||||
// 慢速遗忘:lambda_mem=1,lambda_time=0.3,offset=0.2
|
|
||||||
const getInitData = useCallback(() => {
|
const getInitData = useCallback(() => {
|
||||||
const list = seriesData.map(item => ({
|
const list = seriesData.map(item => ({
|
||||||
...item,
|
...item,
|
||||||
@@ -141,6 +173,7 @@ const LineChart: FC<LineCardProps> = ({ config }) => {
|
|||||||
setInitialData(list)
|
setInitialData(list)
|
||||||
}, [seriesData])
|
}, [seriesData])
|
||||||
|
|
||||||
|
/** Calculate retention rate for given days and config */
|
||||||
const calculateSeriesData = useCallback((days: number, data: ChartConfig | ConfigForm) => {
|
const calculateSeriesData = useCallback((days: number, data: ChartConfig | ConfigForm) => {
|
||||||
const offset = Number(data.offset)
|
const offset = Number(data.offset)
|
||||||
const lambda_time = Number(data.lambda_time)
|
const lambda_time = Number(data.lambda_time)
|
||||||
@@ -148,10 +181,12 @@ const LineChart: FC<LineCardProps> = ({ config }) => {
|
|||||||
// R = offset + (1 - offset) × e^(-λtime × t / (λmem × S))
|
// R = offset + (1 - offset) × e^(-λtime × t / (λmem × S))
|
||||||
return +(offset + (1 - offset) * Math.exp(-lambda_time * days / lambda_mem)).toFixed(4)
|
return +(offset + (1 - offset) * Math.exp(-lambda_time * days / lambda_mem)).toFixed(4)
|
||||||
}, [])
|
}, [])
|
||||||
|
/** Format data for all x-axis points */
|
||||||
const formatData = useCallback((data: ChartConfig | ConfigForm) => {
|
const formatData = useCallback((data: ChartConfig | ConfigForm) => {
|
||||||
return xAxisData.map(days => Number(calculateSeriesData(days, data)))
|
return xAxisData.map(days => Number(calculateSeriesData(days, data)))
|
||||||
}, [calculateSeriesData])
|
}, [calculateSeriesData])
|
||||||
|
|
||||||
|
/** Calculate current configuration curve data */
|
||||||
const getCaculateData = useCallback((data: ConfigForm) => {
|
const getCaculateData = useCallback((data: ConfigForm) => {
|
||||||
if (!data) {
|
if (!data) {
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -1,7 +1,20 @@
|
|||||||
|
/*
|
||||||
|
* @Author: ZhaoYing
|
||||||
|
* @Date: 2026-02-03 17:00:12
|
||||||
|
* @Last Modified by: ZhaoYing
|
||||||
|
* @Last Modified time: 2026-02-03 17:00:12
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Forgetting Engine Configuration Page
|
||||||
|
* Configures memory forgetting curve parameters
|
||||||
|
* Uses Ebbinghaus forgetting curve formula: R = offset + (1 - offset) × e^(-λ_time × t / λ_mem)
|
||||||
|
*/
|
||||||
|
|
||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { Row, Col, Form, Slider, Button, Space, message } from 'antd';
|
import { Row, Col, Form, Slider, Button, Space, message } from 'antd';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import RbCard from '@/components/RbCard/Card';
|
import RbCard from '@/components/RbCard/Card';
|
||||||
import strategyImpactSimulator from '@/assets/images/memory/strategyImpactSimulator.svg'
|
import strategyImpactSimulator from '@/assets/images/memory/strategyImpactSimulator.svg'
|
||||||
import LineChart from './components/LineChart'
|
import LineChart from './components/LineChart'
|
||||||
@@ -9,6 +22,9 @@ import { getMemoryForgetConfig, updateMemoryForgetConfig } from '@/api/memory'
|
|||||||
import type { ConfigForm } from './types'
|
import type { ConfigForm } from './types'
|
||||||
import SwitchFormItem from '@/components/FormItem/SwitchFormItem'
|
import SwitchFormItem from '@/components/FormItem/SwitchFormItem'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configuration field definitions
|
||||||
|
*/
|
||||||
const configList = [
|
const configList = [
|
||||||
{
|
{
|
||||||
key: 'minimumRetention',
|
key: 'minimumRetention',
|
||||||
@@ -82,6 +98,9 @@ const configList = [
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Forgetting engine configuration component
|
||||||
|
*/
|
||||||
const ForgettingEngine: React.FC = () => {
|
const ForgettingEngine: React.FC = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { id } = useParams();
|
const { id } = useParams();
|
||||||
@@ -96,6 +115,7 @@ const ForgettingEngine: React.FC = () => {
|
|||||||
getConfigData()
|
getConfigData()
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
/** Fetch forgetting engine configuration */
|
||||||
const getConfigData = () => {
|
const getConfigData = () => {
|
||||||
getMemoryForgetConfig(id as string)
|
getMemoryForgetConfig(id as string)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
@@ -113,9 +133,11 @@ const ForgettingEngine: React.FC = () => {
|
|||||||
console.error('Failed to load data');
|
console.error('Failed to load data');
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
/** Reset form to saved configuration */
|
||||||
const handleReset = () => {
|
const handleReset = () => {
|
||||||
form.setFieldsValue(configData || {});
|
form.setFieldsValue(configData || {});
|
||||||
}
|
}
|
||||||
|
/** Save forgetting engine configuration */
|
||||||
const handleSave = () => {
|
const handleSave = () => {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
updateMemoryForgetConfig({
|
updateMemoryForgetConfig({
|
||||||
|
|||||||
@@ -1,56 +1,111 @@
|
|||||||
// 标签表单数据类型
|
/*
|
||||||
|
* @Author: ZhaoYing
|
||||||
|
* @Date: 2026-02-03 17:00:08
|
||||||
|
* @Last Modified by: ZhaoYing
|
||||||
|
* @Last Modified time: 2026-02-03 17:00:08
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Type definitions for Forgetting Engine
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tag form data (legacy, not used in current implementation)
|
||||||
|
*/
|
||||||
export interface TagFormData {
|
export interface TagFormData {
|
||||||
|
/** Tag name */
|
||||||
tagName: string;
|
tagName: string;
|
||||||
|
/** Tag type */
|
||||||
type: string;
|
type: string;
|
||||||
|
/** Tag color */
|
||||||
color: string;
|
color: string;
|
||||||
|
/** Tag description */
|
||||||
description?: string;
|
description?: string;
|
||||||
|
/** Applicable scope */
|
||||||
applicableScope?: string[];
|
applicableScope?: string[];
|
||||||
|
/** Semantic expansion */
|
||||||
semanticExpansion?: string;
|
semanticExpansion?: string;
|
||||||
|
/** Whether tag is active */
|
||||||
isActive?: boolean;
|
isActive?: boolean;
|
||||||
// 扩展字段用于区分编辑和新增操作
|
/** Whether in editing mode */
|
||||||
isEditing?: boolean;
|
isEditing?: boolean;
|
||||||
|
/** Tag ID */
|
||||||
tagId?: string;
|
tagId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 记忆总览数据类型
|
/**
|
||||||
|
* Memory overview record (legacy, not used in current implementation)
|
||||||
|
*/
|
||||||
export interface MemoryOverviewRecord {
|
export interface MemoryOverviewRecord {
|
||||||
|
/** Record ID */
|
||||||
id: number;
|
id: number;
|
||||||
|
/** Memory ID */
|
||||||
memoryID: string,
|
memoryID: string,
|
||||||
|
/** Content summary */
|
||||||
contentSummary: string;
|
contentSummary: string;
|
||||||
|
/** Memory type */
|
||||||
type: string;
|
type: string;
|
||||||
|
/** Creation time */
|
||||||
createTime: string;
|
createTime: string;
|
||||||
|
/** Last call time */
|
||||||
lastCallTime: string;
|
lastCallTime: string;
|
||||||
|
/** Retention degree */
|
||||||
retentionDegree: string;
|
retentionDegree: string;
|
||||||
|
/** Status */
|
||||||
status: string;
|
status: string;
|
||||||
}
|
}
|
||||||
// 定义组件暴露的方法接口
|
|
||||||
|
/**
|
||||||
|
* Memory overview form ref interface (legacy)
|
||||||
|
*/
|
||||||
export interface MemoryOverviewFormRef {
|
export interface MemoryOverviewFormRef {
|
||||||
|
/** Open form with optional record */
|
||||||
handleOpen: (memoryOverview?: MemoryOverviewRecord | null) => void;
|
handleOpen: (memoryOverview?: MemoryOverviewRecord | null) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 遗忘曲线数据类型
|
/**
|
||||||
|
* Forgetting curve record (legacy, not used in current implementation)
|
||||||
|
*/
|
||||||
export interface CurveRecord {
|
export interface CurveRecord {
|
||||||
|
/** Memory ID */
|
||||||
memoryID: string;
|
memoryID: string;
|
||||||
|
/** Memory type */
|
||||||
type: string;
|
type: string;
|
||||||
|
/** Current retention rate */
|
||||||
currentRetentionRate: string;
|
currentRetentionRate: string;
|
||||||
|
/** Finally activated time */
|
||||||
finallyActivated: string;
|
finallyActivated: string;
|
||||||
|
/** Expected forgetting time */
|
||||||
expectedForgettingTime: string;
|
expectedForgettingTime: string;
|
||||||
|
/** Reinforcement count */
|
||||||
reinforcementCount: string;
|
reinforcementCount: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Forgetting engine configuration form
|
||||||
|
*/
|
||||||
export interface ConfigForm {
|
export interface ConfigForm {
|
||||||
|
/** Configuration ID */
|
||||||
config_id?: string;
|
config_id?: string;
|
||||||
|
/** Time decay factor (λ_time) */
|
||||||
lambda_time: string | number;
|
lambda_time: string | number;
|
||||||
|
/** Memory strength factor (λ_mem) */
|
||||||
lambda_mem: string | number;
|
lambda_mem: string | number;
|
||||||
|
/** Minimum retention offset */
|
||||||
offset: string | number;
|
offset: string | number;
|
||||||
|
/** Decay constant */
|
||||||
decay_constant: string | number;
|
decay_constant: string | number;
|
||||||
|
/** Maximum history length */
|
||||||
max_history_length: string | number;
|
max_history_length: string | number;
|
||||||
|
/** Forgetting threshold */
|
||||||
forgetting_threshold: string | number;
|
forgetting_threshold: string | number;
|
||||||
|
/** Minimum days since last access */
|
||||||
min_days_since_access: string | number;
|
min_days_since_access: string | number;
|
||||||
|
/** Whether to enable LLM summary */
|
||||||
enable_llm_summary: boolean;
|
enable_llm_summary: boolean;
|
||||||
|
/** Maximum merge batch size */
|
||||||
max_merge_batch_size: string | number;
|
max_merge_batch_size: string | number;
|
||||||
|
/** Forgetting interval in hours */
|
||||||
forgetting_interval_hours: string | number;
|
forgetting_interval_hours: string | number;
|
||||||
|
/** Additional dynamic fields */
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,21 @@
|
|||||||
|
/*
|
||||||
|
* @Author: ZhaoYing
|
||||||
|
* @Date: 2026-02-03 17:27:28
|
||||||
|
* @Last Modified by: ZhaoYing
|
||||||
|
* @Last Modified time: 2026-02-03 17:27:28
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Card Component
|
||||||
|
* Reusable card wrapper for dashboard sections
|
||||||
|
*/
|
||||||
|
|
||||||
import { type FC, type ReactNode } from 'react'
|
import { type FC, type ReactNode } from 'react'
|
||||||
|
|
||||||
import RbCard from '@/components/RbCard/Card'
|
import RbCard from '@/components/RbCard/Card'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component props
|
||||||
|
*/
|
||||||
interface CardProps {
|
interface CardProps {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
title: string;
|
title: string;
|
||||||
|
|||||||
@@ -1,13 +1,28 @@
|
|||||||
|
/*
|
||||||
|
* @Author: ZhaoYing
|
||||||
|
* @Date: 2026-02-03 17:17:05
|
||||||
|
* @Last Modified by: ZhaoYing
|
||||||
|
* @Last Modified time: 2026-02-03 17:18:32
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Line Chart Card Component
|
||||||
|
* Displays time-series data with ECharts line chart
|
||||||
|
* Supports multiple series and date range selection
|
||||||
|
*/
|
||||||
|
|
||||||
import { type FC, useRef } from 'react'
|
import { type FC, useRef } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { Select } from 'antd'
|
import { Select } from 'antd'
|
||||||
import ReactEcharts from 'echarts-for-react';
|
import ReactEcharts from 'echarts-for-react';
|
||||||
import * as echarts from 'echarts';
|
import * as echarts from 'echarts';
|
||||||
|
|
||||||
import { formatDateTime } from '@/utils/format';
|
import { formatDateTime } from '@/utils/format';
|
||||||
import Empty from '@/components/Empty'
|
import Empty from '@/components/Empty'
|
||||||
|
|
||||||
import Card from './Card'
|
import Card from './Card'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component props
|
||||||
|
*/
|
||||||
interface LineCardProps {
|
interface LineCardProps {
|
||||||
chartData: Array<Record<string, string | number>>;
|
chartData: Array<Record<string, string | number>>;
|
||||||
limit: number;
|
limit: number;
|
||||||
@@ -17,6 +32,7 @@ interface LineCardProps {
|
|||||||
seriesList: string[];
|
seriesList: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** ECharts series configuration */
|
||||||
const SeriesConfig = {
|
const SeriesConfig = {
|
||||||
type: 'line',
|
type: 'line',
|
||||||
stack: 'Total',
|
stack: 'Total',
|
||||||
@@ -34,6 +50,7 @@ const SeriesConfig = {
|
|||||||
},
|
},
|
||||||
data: [220, 302, 181, 234, 210, 290, 150]
|
data: [220, 302, 181, 234, 210, 290, 150]
|
||||||
}
|
}
|
||||||
|
/** Chart color palette */
|
||||||
const Colors = ['#FFB048', '#4DA8FF', '#155EEF']
|
const Colors = ['#FFB048', '#4DA8FF', '#155EEF']
|
||||||
|
|
||||||
const LineCard: FC<LineCardProps> = ({ chartData, limit, onChange, type, className, seriesList }) => {
|
const LineCard: FC<LineCardProps> = ({ chartData, limit, onChange, type, className, seriesList }) => {
|
||||||
@@ -47,6 +64,7 @@ const LineCard: FC<LineCardProps> = ({ chartData, limit, onChange, type, classNa
|
|||||||
{ label: t('dashboard.lastYear'), value: 365 },
|
{ label: t('dashboard.lastYear'), value: 365 },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
/** Generate series data with gradient colors */
|
||||||
const getSeries = () => {
|
const getSeries = () => {
|
||||||
const list = seriesList.map((key, index) => {
|
const list = seriesList.map((key, index) => {
|
||||||
return {
|
return {
|
||||||
@@ -71,6 +89,7 @@ const LineCard: FC<LineCardProps> = ({ chartData, limit, onChange, type, classNa
|
|||||||
|
|
||||||
return list
|
return list
|
||||||
}
|
}
|
||||||
|
/** Format series list for legend */
|
||||||
const formatSeriesList = () => {
|
const formatSeriesList = () => {
|
||||||
return seriesList.map(key => ({
|
return seriesList.map(key => ({
|
||||||
...SeriesConfig,
|
...SeriesConfig,
|
||||||
@@ -85,11 +104,11 @@ const LineCard: FC<LineCardProps> = ({ chartData, limit, onChange, type, classNa
|
|||||||
<Select
|
<Select
|
||||||
value={limit}
|
value={limit}
|
||||||
options={options}
|
options={options}
|
||||||
onChange={(value) => onChange(value, type)}
|
onChange={(value) => onChange(String(value), type)}
|
||||||
style={{ width: '150px' }}
|
style={{ width: '150px' }}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
className={`rb:pb-[24px] ${className}`}
|
className={`rb:pb-6 ${className}`}
|
||||||
>
|
>
|
||||||
{chartData && chartData.length > 0 ? (
|
{chartData && chartData.length > 0 ? (
|
||||||
<ReactEcharts
|
<ReactEcharts
|
||||||
@@ -157,7 +176,7 @@ const LineCard: FC<LineCardProps> = ({ chartData, limit, onChange, type, classNa
|
|||||||
notMerge={true}
|
notMerge={true}
|
||||||
lazyUpdate={true}
|
lazyUpdate={true}
|
||||||
/>
|
/>
|
||||||
) : <Empty size={120} className="rb:mt-[48px] rb:mb-[81px]" />}
|
) : <Empty size={120} className="rb:mt-12 rb:mb-20.25" />}
|
||||||
</Card>
|
</Card>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,30 @@
|
|||||||
|
/*
|
||||||
|
* @Author: ZhaoYing
|
||||||
|
* @Date: 2026-02-03 17:16:45
|
||||||
|
* @Last Modified by: ZhaoYing
|
||||||
|
* @Last Modified time: 2026-02-03 17:16:45
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Pie Chart Card Component
|
||||||
|
* Displays knowledge base type distribution with ECharts donut chart
|
||||||
|
*/
|
||||||
|
|
||||||
import { type FC, useRef, useEffect } from 'react'
|
import { type FC, useRef, useEffect } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import ReactEcharts from 'echarts-for-react';
|
import ReactEcharts from 'echarts-for-react';
|
||||||
|
|
||||||
import Card from './Card'
|
import Card from './Card'
|
||||||
import Loading from '@/components/Empty/Loading'
|
import Loading from '@/components/Empty/Loading'
|
||||||
import Empty from '@/components/Empty'
|
import Empty from '@/components/Empty'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component props
|
||||||
|
*/
|
||||||
interface PieCardProps {
|
interface PieCardProps {
|
||||||
chartData: Array<Record<string, string | number>>;
|
chartData: Array<Record<string, string | number>>;
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
}
|
}
|
||||||
|
/** Chart color palette */
|
||||||
const Colors = ['#155EEF', '#31E8FF', '#AD88FF', '#FFB048', '#4DA8FF', '#03BDFF']
|
const Colors = ['#155EEF', '#31E8FF', '#AD88FF', '#FFB048', '#4DA8FF', '#03BDFF']
|
||||||
|
|
||||||
const PieCard: FC<PieCardProps> = ({ chartData, loading }) => {
|
const PieCard: FC<PieCardProps> = ({ chartData, loading }) => {
|
||||||
@@ -45,7 +61,7 @@ const PieCard: FC<PieCardProps> = ({ chartData, loading }) => {
|
|||||||
{loading
|
{loading
|
||||||
? <Loading size={249} />
|
? <Loading size={249} />
|
||||||
: !chartData || chartData.length === 0
|
: !chartData || chartData.length === 0
|
||||||
? <Empty size={120} className="rb:mt-[48px] rb:mb-[81px]" />
|
? <Empty size={120} className="rb:mt-12 rb:mb-20.25" />
|
||||||
: <ReactEcharts
|
: <ReactEcharts
|
||||||
option={{
|
option={{
|
||||||
color: Colors,
|
color: Colors,
|
||||||
|
|||||||
@@ -1,14 +1,19 @@
|
|||||||
/*
|
/*
|
||||||
* @Description:
|
* @Author: ZhaoYing
|
||||||
* @Version: 0.0.1
|
* @Date: 2026-02-03 17:16:38
|
||||||
* @Author: yujiangping
|
* @Last Modified by: ZhaoYing
|
||||||
* @Date: 2026-01-05 17:22:23
|
* @Last Modified time: 2026-02-03 17:16:38
|
||||||
* @LastEditors: yujiangping
|
|
||||||
* @LastEditTime: 2026-01-15 14:55:51
|
|
||||||
*/
|
*/
|
||||||
|
/**
|
||||||
|
* Quick Operation Component
|
||||||
|
* Displays shortcut cards for common operations
|
||||||
|
* Includes navigation to application, knowledge base, memory conversation, and help center
|
||||||
|
*/
|
||||||
|
|
||||||
import { type FC } from 'react'
|
import { type FC } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
import Card from './Card';
|
import Card from './Card';
|
||||||
import applicationIcon from '@/assets/images/menu/application_active.svg';
|
import applicationIcon from '@/assets/images/menu/application_active.svg';
|
||||||
import knowledgeIcon from '@/assets/images/menu/knowledge_active.svg';
|
import knowledgeIcon from '@/assets/images/menu/knowledge_active.svg';
|
||||||
@@ -16,6 +21,7 @@ import memoryConversationIcon from '@/assets/images/menu/memoryConversation_acti
|
|||||||
import helpCenterIcon from '@/assets/images/menu/helpCenter_active.svg'
|
import helpCenterIcon from '@/assets/images/menu/helpCenter_active.svg'
|
||||||
import arrowTopRight from '@/assets/images/home/arrow_top_right.svg';
|
import arrowTopRight from '@/assets/images/home/arrow_top_right.svg';
|
||||||
|
|
||||||
|
/** Quick operation items configuration */
|
||||||
const quickOperations = [
|
const quickOperations = [
|
||||||
{ key: 'createNewApplication', url: '/application' },
|
{ key: 'createNewApplication', url: '/application' },
|
||||||
{ key: 'createNewKnowledge', url: '/knowledge-base' },
|
{ key: 'createNewKnowledge', url: '/knowledge-base' },
|
||||||
@@ -23,6 +29,7 @@ const quickOperations = [
|
|||||||
{ key: 'helpCenter', url: '' },
|
{ key: 'helpCenter', url: '' },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
/** Icon mapping for quick operations */
|
||||||
const quickOperationIcons: {[key: string]: string | undefined} = {
|
const quickOperationIcons: {[key: string]: string | undefined} = {
|
||||||
createNewApplication: applicationIcon,
|
createNewApplication: applicationIcon,
|
||||||
createNewKnowledge: knowledgeIcon,
|
createNewKnowledge: knowledgeIcon,
|
||||||
@@ -33,6 +40,7 @@ const QuickOperation:FC = () => {
|
|||||||
const { t, i18n } = useTranslation()
|
const { t, i18n } = useTranslation()
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
/** Handle navigation or external link */
|
||||||
const handleJump = (url: string | null) => {
|
const handleJump = (url: string | null) => {
|
||||||
if (url) {
|
if (url) {
|
||||||
navigate(url)
|
navigate(url)
|
||||||
@@ -41,7 +49,7 @@ const QuickOperation:FC = () => {
|
|||||||
const lang = currentLang === 'zh' ? 'zh' : 'en';
|
const lang = currentLang === 'zh' ? 'zh' : 'en';
|
||||||
const helpUrl = `https://docs.redbearai.com/s/${lang}-memorybear`;
|
const helpUrl = `https://docs.redbearai.com/s/${lang}-memorybear`;
|
||||||
|
|
||||||
// 创建隐藏的 a 标签来避免弹窗拦截
|
/** Create hidden link to avoid popup blocking */
|
||||||
const link = document.createElement('a');
|
const link = document.createElement('a');
|
||||||
link.href = helpUrl;
|
link.href = helpUrl;
|
||||||
link.target = '_blank';
|
link.target = '_blank';
|
||||||
@@ -55,15 +63,15 @@ const QuickOperation:FC = () => {
|
|||||||
<Card
|
<Card
|
||||||
title={t('dashboard.quickOperation')}
|
title={t('dashboard.quickOperation')}
|
||||||
>
|
>
|
||||||
<div className="rb:grid rb:grid-cols-4 rb:gap-[16px]">
|
<div className="rb:grid rb:grid-cols-4 rb:gap-4">
|
||||||
{quickOperations.map(item => (
|
{quickOperations.map(item => (
|
||||||
<div key={item.key} className="rb:rounded-[8px] rb:p-[20px_16px] rb:border-1 rb:border-[#DFE4ED] rb:cursor-pointer rb:hover:border-[#155EEF]" onClick={() => handleJump(item.url)}>
|
<div key={item.key} className="rb:rounded-lg rb:p-[20px_16px] rb:border rb:border-[#DFE4ED] rb:cursor-pointer rb:hover:border-[#155EEF]" onClick={() => handleJump(item.url)}>
|
||||||
<div className="rb:flex rb:justify-between">
|
<div className="rb:flex rb:justify-between">
|
||||||
<img className="rb:w-[32px] rb:h-[32px]" src={quickOperationIcons[item.key]} />
|
<img className="rb:w-8 rb:h-8" src={quickOperationIcons[item.key]} />
|
||||||
<img className="rb:w-[16px] rb:h-[16px]" src={arrowTopRight} />
|
<img className="rb:w-4 rb:h-4" src={arrowTopRight} />
|
||||||
</div>
|
</div>
|
||||||
<div className="rb:mt-[24px] rb:text-[#212332] rb:text-[16px] rb:leading-[20px] rb:font-medium">{t(`dashboard.${item.key}`)}</div>
|
<div className="rb:mt-6 rb:text-[#212332] rb:text-[16px] rb:leading-5 rb:font-medium">{t(`dashboard.${item.key}`)}</div>
|
||||||
<div className="rb:mt-[8px] rb:text-[#5B6167] rb:text-[12px] rb:font-regular">{t(`dashboard.${item.key}Desc`)}</div>
|
<div className="rb:mt-2 rb:text-[#5B6167] rb:text-[12px] rb:font-regular">{t(`dashboard.${item.key}Desc`)}</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,7 +1,20 @@
|
|||||||
|
/*
|
||||||
|
* @Author: ZhaoYing
|
||||||
|
* @Date: 2026-02-03 17:15:33
|
||||||
|
* @Last Modified by: ZhaoYing
|
||||||
|
* @Last Modified time: 2026-02-03 17:15:33
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Recent Activity Component
|
||||||
|
* Displays recent memory processing activities with statistics
|
||||||
|
* Shows chunk count, statements, entity relations, and temporal data
|
||||||
|
*/
|
||||||
|
|
||||||
import { type FC, useEffect, useState } from 'react'
|
import { type FC, useEffect, useState } from 'react'
|
||||||
import clsx from 'clsx'
|
import clsx from 'clsx'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { Skeleton } from 'antd';
|
import { Skeleton } from 'antd';
|
||||||
|
|
||||||
import chunkCountIcon from '@/assets/images/home/chunk_count.svg';
|
import chunkCountIcon from '@/assets/images/home/chunk_count.svg';
|
||||||
import statementsCountIcon from '@/assets/images/home/statements_count.svg';
|
import statementsCountIcon from '@/assets/images/home/statements_count.svg';
|
||||||
import tripletCountIcon from '@/assets/images/home/triplet_count.svg';
|
import tripletCountIcon from '@/assets/images/home/triplet_count.svg';
|
||||||
@@ -11,18 +24,30 @@ import Empty from '@/components/Empty';
|
|||||||
import Card from './Card';
|
import Card from './Card';
|
||||||
import { getRecentActivityStats } from '@/api/memory';
|
import { getRecentActivityStats } from '@/api/memory';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API response data structure
|
||||||
|
*/
|
||||||
interface Data {
|
interface Data {
|
||||||
latest_relative: string;
|
latest_relative: string;
|
||||||
stats: RecentActivities;
|
stats: RecentActivities;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Recent activity statistics
|
||||||
|
*/
|
||||||
interface RecentActivities {
|
interface RecentActivities {
|
||||||
"chunk_count": number; // 数据分块
|
/** Data chunk count */
|
||||||
"statements_count": number; // 语句提取
|
"chunk_count": number;
|
||||||
"triplet_entities_count": number; // 实体关系萃取-实体节点
|
/** Statement extraction count */
|
||||||
"triplet_relations_count": number; // 实体关系萃取 - 关系连接
|
"statements_count": number;
|
||||||
"temporal_count": number; // 时间萃取
|
/** Entity node count */
|
||||||
|
"triplet_entities_count": number;
|
||||||
|
/** Relation connection count */
|
||||||
|
"triplet_relations_count": number;
|
||||||
|
/** Temporal extraction count */
|
||||||
|
"temporal_count": number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Activity list configuration */
|
||||||
const activityList = [
|
const activityList = [
|
||||||
{ key: 'chunk_count', icon: chunkCountIcon },
|
{ key: 'chunk_count', icon: chunkCountIcon },
|
||||||
{ key: 'statements_count', icon: statementsCountIcon },
|
{ key: 'statements_count', icon: statementsCountIcon },
|
||||||
@@ -40,7 +65,7 @@ const RecentActivity:FC = () => {
|
|||||||
getRecentActivityList()
|
getRecentActivityList()
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
// 最近活动统计
|
/** Fetch recent activity statistics */
|
||||||
const getRecentActivityList = () => {
|
const getRecentActivityList = () => {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
getRecentActivityStats().then(res => {
|
getRecentActivityStats().then(res => {
|
||||||
@@ -58,13 +83,13 @@ const RecentActivity:FC = () => {
|
|||||||
{loading
|
{loading
|
||||||
? <Skeleton />
|
? <Skeleton />
|
||||||
: !recentActivities || Object.keys(recentActivities).length === 0
|
: !recentActivities || Object.keys(recentActivities).length === 0
|
||||||
? <Empty url={activityEmpty} subTitle={t('dashboard.activityEmpty')} size={120} className="rb:mt-[45px] rb:mb-[81px]" />
|
? <Empty url={activityEmpty} subTitle={t('dashboard.activityEmpty')} size={120} className="rb:mt-11.25 rb:mb-20.25" />
|
||||||
: activityList.map((item, index) => (
|
: activityList.map((item, index) => (
|
||||||
<div key={item.key} className={clsx("rb:flex rb:justify-between rb:items-center rb:not-italic", {
|
<div key={item.key} className={clsx("rb:flex rb:justify-between rb:items-center rb:not-italic", {
|
||||||
'rb:mt-[24px]': index !== 0
|
'rb:mt-6': index !== 0
|
||||||
})}>
|
})}>
|
||||||
<div className="rb:flex rb:items-center rb:text-[#060419] rb:text-[16px] rb:font-medium">
|
<div className="rb:flex rb:items-center rb:text-[#060419] rb:text-[16px] rb:font-medium">
|
||||||
<img className="rb:w-[40px] rb:h-[40px] rb:mr-[16px]" src={item.icon} />
|
<img className="rb:w-10 rb:h-10 rb:mr-4" src={item.icon} />
|
||||||
<div>
|
<div>
|
||||||
{t(`dashboard.${item.key}`)}
|
{t(`dashboard.${item.key}`)}
|
||||||
<div className="rb:text-[#5B6167] rb:text-[14px] rb:font-normal">
|
<div className="rb:text-[#5B6167] rb:text-[14px] rb:font-normal">
|
||||||
|
|||||||
@@ -1,7 +1,19 @@
|
|||||||
|
/*
|
||||||
|
* @Author: ZhaoYing
|
||||||
|
* @Date: 2026-02-03 17:15:04
|
||||||
|
* @Last Modified by: ZhaoYing
|
||||||
|
* @Last Modified time: 2026-02-03 17:15:04
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Tag List Component
|
||||||
|
* Displays popular memory tags with frequency counts
|
||||||
|
*/
|
||||||
|
|
||||||
import { type FC, useEffect, useState } from 'react'
|
import { type FC, useEffect, useState } from 'react'
|
||||||
import clsx from 'clsx'
|
import clsx from 'clsx'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { Skeleton } from 'antd';
|
import { Skeleton } from 'antd';
|
||||||
|
|
||||||
import tagEmpty from '@/assets/images/home/tagEmpty.svg'
|
import tagEmpty from '@/assets/images/home/tagEmpty.svg'
|
||||||
import Empty from '@/components/Empty';
|
import Empty from '@/components/Empty';
|
||||||
import Card from './Card';
|
import Card from './Card';
|
||||||
@@ -16,7 +28,7 @@ const TagList:FC = () => {
|
|||||||
getTagList()
|
getTagList()
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
// 热门记忆标签
|
/** Fetch popular memory tags */
|
||||||
const getTagList = () => {
|
const getTagList = () => {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
getHotMemoryTags().then(res => {
|
getHotMemoryTags().then(res => {
|
||||||
@@ -30,12 +42,12 @@ const TagList:FC = () => {
|
|||||||
{loading
|
{loading
|
||||||
? <Skeleton />
|
? <Skeleton />
|
||||||
: !tagList || tagList.length === 0
|
: !tagList || tagList.length === 0
|
||||||
? <Empty url={tagEmpty} title={t('dashboard.activityEmpty')} size={120} className="rb:mt-[36px] rb:mb-[81px]" />
|
? <Empty url={tagEmpty} title={t('dashboard.activityEmpty')} size={120} className="rb:mt-9 rb:mb-20.25" />
|
||||||
: <div className="rb:gap-[12px] rb:flex rb:flex-wrap">
|
: <div className="rb:gap-3 rb:flex rb:flex-wrap">
|
||||||
{tagList.map((item, index) => (
|
{tagList.map((item, index) => (
|
||||||
<div
|
<div
|
||||||
key={item.name}
|
key={item.name}
|
||||||
className={clsx("rb:pt-[6px] rb:pb-[6px] rb:pr-[23px] rb:pl-[20px] rb:border-1 rb:leading-[20px] rb:bg-white rb:rounded-[17px]", {
|
className={clsx("rb:pt-1.5 rb:pb-1.5 rb:pr-5.75 rb:pl-5 rb:border rb:leading-5 rb:bg-white rb:rounded-[17px]", {
|
||||||
'rb:border-[rgba(21,94,239,0.4)] rb:text-[#155EEF]': index % 3 === 0,
|
'rb:border-[rgba(21,94,239,0.4)] rb:text-[#155EEF]': index % 3 === 0,
|
||||||
'rb:border-[rgba(255,138,76,0.4)] rb:text-[#FF5D34]': index % 3 === 1,
|
'rb:border-[rgba(255,138,76,0.4)] rb:text-[#FF5D34]': index % 3 === 1,
|
||||||
'rb:border-[rgba(54,159,33,0.4)] rb:text-[#369F21]': index % 3 === 2,
|
'rb:border-[rgba(54,159,33,0.4)] rb:text-[#369F21]': index % 3 === 2,
|
||||||
|
|||||||
@@ -1,13 +1,26 @@
|
|||||||
|
/*
|
||||||
|
* @Author: ZhaoYing
|
||||||
|
* @Date: 2026-02-03 17:28:07
|
||||||
|
* @Last Modified by: ZhaoYing
|
||||||
|
* @Last Modified time: 2026-02-03 17:28:07
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Top Card List Component
|
||||||
|
* Displays dashboard summary cards for key metrics
|
||||||
|
* Shows total memory capacity, applications, knowledge bases, and API calls
|
||||||
|
*/
|
||||||
|
|
||||||
import { type FC } from 'react'
|
import { type FC } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
import totalMemoryCapacity from '@/assets/images/home/totalMemoryCapacity.svg';
|
import totalMemoryCapacity from '@/assets/images/home/totalMemoryCapacity.svg';
|
||||||
import userMemory from '@/assets/images/home/userMemory.svg';
|
import userMemory from '@/assets/images/home/userMemory.svg';
|
||||||
import knowledgeBaseCount from '@/assets/images/home/knowledgeBaseCount.svg';
|
import knowledgeBaseCount from '@/assets/images/home/knowledgeBaseCount.svg';
|
||||||
import apiCallCount from '@/assets/images/home/apiCallCount.svg';
|
import apiCallCount from '@/assets/images/home/apiCallCount.svg';
|
||||||
import styles from './index.module.css'
|
import styles from './index.module.css'
|
||||||
import clsx from 'clsx';
|
|
||||||
import type { DashboardData } from '../../index'
|
import type { DashboardData } from '../../index'
|
||||||
|
|
||||||
|
/** Card configuration with styling */
|
||||||
const list = [
|
const list = [
|
||||||
{
|
{
|
||||||
key: 'totalMemoryCapacity',
|
key: 'totalMemoryCapacity',
|
||||||
@@ -46,10 +59,14 @@ const list = [
|
|||||||
background: 'linear-gradient( 180deg, #F8F6F5 0%, #FAFDFF 100%)',
|
background: 'linear-gradient( 180deg, #F8F6F5 0%, #FAFDFF 100%)',
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
/**
|
||||||
|
* Component props
|
||||||
|
* @param data - Dashboard statistics data
|
||||||
|
*/
|
||||||
const TopCardList: FC<{data?: DashboardData}> = ({ data }) => {
|
const TopCardList: FC<{data?: DashboardData}> = ({ data }) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
return (
|
return (
|
||||||
<div className="rb:grid rb:grid-cols-4 rb:gap-[16px]">
|
<div className="rb:grid rb:grid-cols-4 rb:gap-4">
|
||||||
{list.map((item) => {
|
{list.map((item) => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -65,11 +82,7 @@ const TopCardList: FC<{data?: DashboardData}> = ({ data }) => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles.content}>
|
<div className={styles.content}>
|
||||||
{data?.[item.key] || item.value || 0}
|
{data?.[item.key as keyof DashboardData] || 0}
|
||||||
<div className={styles.contentRight}>
|
|
||||||
{item.trendValue && <div className={clsx(styles.trend, styles[item.trend])}>{item.trendValue}</div>}
|
|
||||||
{item.trendDesc && <div>{t(`dashboard.${item.trendDesc}`)}</div>}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,5 +1,16 @@
|
|||||||
|
/*
|
||||||
|
* @Author: ZhaoYing
|
||||||
|
* @Date: 2026-02-03 17:12:43
|
||||||
|
* @Last Modified by: ZhaoYing
|
||||||
|
* @Last Modified time: 2026-02-03 17:26:04
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Home Dashboard Page
|
||||||
|
* Main dashboard displaying memory statistics, charts, activities, and quick operations
|
||||||
|
*/
|
||||||
|
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { Row, Col, Space } from 'antd';
|
import { Row, Col } from 'antd';
|
||||||
|
|
||||||
import TopCardList from './components/TopCardList'
|
import TopCardList from './components/TopCardList'
|
||||||
import LineCard from './components/LineCard'
|
import LineCard from './components/LineCard'
|
||||||
@@ -9,6 +20,9 @@ import RecentActivity from './components/RecentActivity'
|
|||||||
import TagList from './components/TagList'
|
import TagList from './components/TagList'
|
||||||
import QuickOperation from './components/QuickOperation'
|
import QuickOperation from './components/QuickOperation'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dashboard statistics data
|
||||||
|
*/
|
||||||
export interface DashboardData {
|
export interface DashboardData {
|
||||||
totalMemoryCapacity?: number;
|
totalMemoryCapacity?: number;
|
||||||
application?: number;
|
application?: number;
|
||||||
@@ -25,12 +39,12 @@ const Home = () => {
|
|||||||
const [memoryIncrement, setMemoryIncrement] = useState<Array<{ updated_at: string; total_num: number; }>>([]);
|
const [memoryIncrement, setMemoryIncrement] = useState<Array<{ updated_at: string; total_num: number; }>>([]);
|
||||||
const [limit, setLimit] = useState(7);
|
const [limit, setLimit] = useState(7);
|
||||||
|
|
||||||
// 模拟API获取数据
|
/** Simulate API data fetch */
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getData()
|
getData()
|
||||||
getKnowledgeTypeDistribution()
|
getKnowledgeTypeDistribution()
|
||||||
}, []);
|
}, []);
|
||||||
// 记忆总量 / 应用数量 / 知识库数量 / API调用次数
|
/** Fetch memory total, app count, knowledge base count, API call count */
|
||||||
const getData = () => {
|
const getData = () => {
|
||||||
getDashboardData().then(res => {
|
getDashboardData().then(res => {
|
||||||
const response = res as {
|
const response = res as {
|
||||||
@@ -49,16 +63,16 @@ const Home = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
const { storage_type = 'neo4j' } = response || {}
|
const { storage_type = 'neo4j' } = response || {}
|
||||||
const responseData = response[storage_type + '_data'] || {}
|
const responseData = storage_type === 'neo4j' ? response.neo4j_data : response.rag_data
|
||||||
setDashboardData({
|
setDashboardData({
|
||||||
totalMemoryCapacity: responseData.total_memory || 0,
|
totalMemoryCapacity: responseData?.total_memory || 0,
|
||||||
application: responseData.total_app || 0,
|
application: responseData?.total_app || 0,
|
||||||
knowledgeBaseCount: responseData.total_knowledge || 0,
|
knowledgeBaseCount: responseData?.total_knowledge || 0,
|
||||||
apiCallCount: responseData.total_api_call || 0
|
apiCallCount: responseData?.total_api_call || 0
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
// 知识库类型分布 / 知识库数量
|
/** Fetch knowledge base type distribution */
|
||||||
const getKnowledgeTypeDistribution = () => {
|
const getKnowledgeTypeDistribution = () => {
|
||||||
setLoading({
|
setLoading({
|
||||||
...loading,
|
...loading,
|
||||||
@@ -86,7 +100,7 @@ const Home = () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
// 记忆增长趋势
|
/** Fetch memory growth trend data */
|
||||||
const getMemoryIncrementData = () => {
|
const getMemoryIncrementData = () => {
|
||||||
getMemoryIncrement(limit).then(res => {
|
getMemoryIncrement(limit).then(res => {
|
||||||
const response = res as { updated_at: string; total_num: number; }[]
|
const response = res as { updated_at: string; total_num: number; }[]
|
||||||
@@ -106,12 +120,10 @@ const Home = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="rb:pb-[24px]">
|
<div className="rb:pb-6">
|
||||||
{/* 统计卡片 */}
|
|
||||||
<TopCardList data={dashboardData} />
|
<TopCardList data={dashboardData} />
|
||||||
|
|
||||||
<Row className="rb:mt-[16px]" gutter={16}>
|
<Row className="rb:mt-4" gutter={16}>
|
||||||
{/* 记忆增长趋势 */}
|
|
||||||
<Col span={12}>
|
<Col span={12}>
|
||||||
<LineCard
|
<LineCard
|
||||||
chartData={memoryIncrement}
|
chartData={memoryIncrement}
|
||||||
@@ -121,7 +133,6 @@ const Home = () => {
|
|||||||
seriesList={['total_num']}
|
seriesList={['total_num']}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
{/* 知识库类型分布 */}
|
|
||||||
<Col span={12}>
|
<Col span={12}>
|
||||||
<PieCard
|
<PieCard
|
||||||
loading={loading.knowledgeTypeDistribution}
|
loading={loading.knowledgeTypeDistribution}
|
||||||
@@ -130,20 +141,17 @@ const Home = () => {
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<Row className="rb:mt-[16px]" gutter={16}>
|
<Row className="rb:mt-4" gutter={16}>
|
||||||
<Col span={12}>
|
<Col span={12}>
|
||||||
{/* 最近记忆活动 */}
|
|
||||||
<RecentActivity />
|
<RecentActivity />
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={12}>
|
<Col span={12}>
|
||||||
{/* 热门记忆标签 */}
|
|
||||||
<TagList />
|
<TagList />
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<Row className="rb:mt-[16px]" gutter={16}>
|
<Row className="rb:mt-4" gutter={16}>
|
||||||
<Col span={24}>
|
<Col span={24}>
|
||||||
{/* 快速操作 */}
|
|
||||||
<QuickOperation />
|
<QuickOperation />
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|||||||
@@ -1,10 +1,23 @@
|
|||||||
|
/*
|
||||||
|
* @Author: ZhaoYing
|
||||||
|
* @Date: 2026-02-03 16:37:12
|
||||||
|
* @Last Modified by: ZhaoYing
|
||||||
|
* @Last Modified time: 2026-02-04 10:05:39
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Invite Register Page
|
||||||
|
* Handles user registration via workspace invitation link
|
||||||
|
* Validates invite token and allows new users to set up their account
|
||||||
|
*/
|
||||||
|
|
||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useNavigate, useParams } from 'react-router-dom';
|
import { useNavigate, useParams } from 'react-router-dom';
|
||||||
import { Button, Input, Form, Progress, App } from 'antd';
|
import { Button, Input, Form, Progress, App } from 'antd';
|
||||||
import { ExclamationCircleFilled } from '@ant-design/icons';
|
import { ExclamationCircleFilled } from '@ant-design/icons';
|
||||||
import { useUser, type LoginInfo } from '@/store/user';
|
|
||||||
import type { FormProps } from 'antd';
|
import type { FormProps } from 'antd';
|
||||||
|
|
||||||
|
import { useUser, type LoginInfo } from '@/store/user';
|
||||||
import { login } from '@/api/user'
|
import { login } from '@/api/user'
|
||||||
import inviteBg from '@/assets/images/login/inviteBg.png'
|
import inviteBg from '@/assets/images/login/inviteBg.png'
|
||||||
import checkBg from '@/assets/images/login/checkBg.png'
|
import checkBg from '@/assets/images/login/checkBg.png'
|
||||||
@@ -13,13 +26,19 @@ import { validateInviteToken } from '@/api/member'
|
|||||||
import RbAlert from '@/components/RbAlert'
|
import RbAlert from '@/components/RbAlert'
|
||||||
import styles from './index.module.css'
|
import styles from './index.module.css'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alert extra content wrapper
|
||||||
|
*/
|
||||||
const Extra = ({ children }: { children: React.ReactNode }) => (
|
const Extra = ({ children }: { children: React.ReactNode }) => (
|
||||||
<div className="rb:flex rb:items-start">
|
<div className="rb:flex rb:items-start">
|
||||||
<ExclamationCircleFilled className="rb:mr-[4px] rb:mt-[3px]" />
|
<ExclamationCircleFilled className="rb:mr-1 rb:mt-0.75" />
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invite registration component
|
||||||
|
*/
|
||||||
const InviteRegister: React.FC = () => {
|
const InviteRegister: React.FC = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -36,6 +55,7 @@ const InviteRegister: React.FC = () => {
|
|||||||
getInitalData()
|
getInitalData()
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
/** Fetch and validate invite token */
|
||||||
const getInitalData = () => {
|
const getInitalData = () => {
|
||||||
if (!token) {
|
if (!token) {
|
||||||
message.warning(t('user.inviteLinkInvalid'))
|
message.warning(t('user.inviteLinkInvalid'))
|
||||||
@@ -49,39 +69,39 @@ const InviteRegister: React.FC = () => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 密码强度校验函数
|
/** Validate password strength and return score */
|
||||||
const validatePasswordStrength = (password: string): { strength: 'weak' | 'medium' | 'strong', error: string } => {
|
const validatePasswordStrength = (password: string): { strength: 'weak' | 'medium' | 'strong', error: string } => {
|
||||||
let strength: 'weak' | 'medium' | 'strong' = 'weak';
|
let strength: 'weak' | 'medium' | 'strong' = 'weak';
|
||||||
let score = 0;
|
let score = 0;
|
||||||
let error = '';
|
let error = '';
|
||||||
|
|
||||||
// 密码长度检查
|
// Password length check
|
||||||
if (password.length < 8) {
|
if (password.length < 8) {
|
||||||
error = t('login.lengthDesc');
|
error = t('login.lengthDesc');
|
||||||
return { strength, error };
|
return { strength, error };
|
||||||
}
|
}
|
||||||
score += 1;
|
score += 1;
|
||||||
|
|
||||||
// 包含数字
|
// Contains number
|
||||||
if (/\d/.test(password)) score += 1;
|
if (/\d/.test(password)) score += 1;
|
||||||
|
|
||||||
// 包含小写字母
|
// Contains lowercase letter
|
||||||
if (/[a-z]/.test(password)) score += 1;
|
if (/[a-z]/.test(password)) score += 1;
|
||||||
|
|
||||||
// 包含大写字母
|
// Contains uppercase letter
|
||||||
if (/[A-Z]/.test(password)) score += 1;
|
if (/[A-Z]/.test(password)) score += 1;
|
||||||
|
|
||||||
// 包含特殊字符
|
// Contains special character
|
||||||
if (/[^A-Za-z0-9]/.test(password)) score += 1;
|
if (/[^A-Za-z0-9]/.test(password)) score += 1;
|
||||||
|
|
||||||
// 判断强度
|
// Determine strength
|
||||||
if (score >= 4) {
|
if (score >= 4) {
|
||||||
strength = 'strong';
|
strength = 'strong';
|
||||||
} else if (score >= 3) {
|
} else if (score >= 3) {
|
||||||
strength = 'medium';
|
strength = 'medium';
|
||||||
}
|
}
|
||||||
|
|
||||||
// 根据强度返回提示
|
// Return message based on strength
|
||||||
if (strength === 'weak' && score >= 1) {
|
if (strength === 'weak' && score >= 1) {
|
||||||
error = t('login.weakDesc');
|
error = t('login.weakDesc');
|
||||||
} else if (strength === 'medium') {
|
} else if (strength === 'medium') {
|
||||||
@@ -91,7 +111,7 @@ const InviteRegister: React.FC = () => {
|
|||||||
return { strength, error };
|
return { strength, error };
|
||||||
};
|
};
|
||||||
|
|
||||||
// 监听密码变化,更新强度
|
/** Update password strength indicator on change */
|
||||||
const handlePasswordChange = (value: string) => {
|
const handlePasswordChange = (value: string) => {
|
||||||
if (!value) {
|
if (!value) {
|
||||||
setPasswordStrength(null);
|
setPasswordStrength(null);
|
||||||
@@ -101,19 +121,19 @@ const InviteRegister: React.FC = () => {
|
|||||||
setPasswordStrength(strength);
|
setPasswordStrength(strength);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 密码一致性校验
|
/** Validate password confirmation matches */
|
||||||
const validateConfirmPassword = (_: unknown, value: string) => {
|
const validateConfirmPassword = (_: unknown, value: string) => {
|
||||||
const password = values.password;
|
const password = values.password;
|
||||||
if (!value) {
|
if (!value) {
|
||||||
return Promise.reject(new Error('请确认密码'));
|
return Promise.reject(new Error('Please confirm password'));
|
||||||
}
|
}
|
||||||
if (value !== password) {
|
if (value !== password) {
|
||||||
return Promise.reject(new Error('两次输入的密码不一致'));
|
return Promise.reject(new Error('Passwords do not match'));
|
||||||
}
|
}
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
};
|
};
|
||||||
|
|
||||||
// 处理注册提交
|
/** Handle registration form submission */
|
||||||
const handleRegister: FormProps<LoginForm>['onFinish'] = async (values) => {
|
const handleRegister: FormProps<LoginForm>['onFinish'] = async (values) => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
login({
|
login({
|
||||||
@@ -133,12 +153,12 @@ const InviteRegister: React.FC = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="rb:w-screen rb:h-screen rb:flex rb:items-center rb:justify-center">
|
<div className="rb:w-screen rb:h-screen rb:flex rb:items-center rb:justify-center">
|
||||||
<img src={inviteBg} className="rb:w-screen rb:h-screen rb:fixed rb:top-0 rb:left-0 rb:z-[0]" />
|
<img src={inviteBg} className="rb:w-screen rb:h-screen rb:fixed rb:top-0 rb:left-0 rb:z-0" />
|
||||||
|
|
||||||
<div className="rb:relative rb:z-[1] rb:w-[480px] rb:max-h-full rb:overflow-y-auto rb:bg-[#FFFFFF] rb:rounded-[12px] rb:shadow-[0px_2px_10px_0px_rgba(11,49,124,0.2)]">
|
<div className="rb:relative rb:z-1 rb:w-120 rb:max-h-full rb:overflow-y-auto rb:bg-[#FFFFFF] rb:rounded-xl rb:shadow-[0px_2px_10px_0px_rgba(11,49,124,0.2)]">
|
||||||
<div className="rb:bg-[url('@/assets/images/login/inviteForm.png')] rb:bg-cover rb:bg-no-repeat rb:text-[24px] rb:font-bold rb:leading-[32px] rb:p-[28px_24px]">
|
<div className="rb:bg-[url('@/assets/images/login/inviteForm.png')] rb:bg-cover rb:bg-no-repeat rb:text-[24px] rb:font-bold rb:leading-8 rb:p-[28px_24px]">
|
||||||
{t('login.welcomeTeam')}
|
{t('login.welcomeTeam')}
|
||||||
<div className="rb:text-[#5B6167] rb:text-[12px] rb:font-regular rb:leading-[16px] rb:mt-[10px]">{t('login.welcomeTeamSubTitle')}</div>
|
<div className="rb:text-[#5B6167] rb:text-[12px] rb:font-regular rb:leading-4 rb:mt-2.5">{t('login.welcomeTeamSubTitle')}</div>
|
||||||
</div>
|
</div>
|
||||||
<Form
|
<Form
|
||||||
form={form}
|
form={form}
|
||||||
@@ -146,10 +166,10 @@ const InviteRegister: React.FC = () => {
|
|||||||
layout="vertical"
|
layout="vertical"
|
||||||
className={styles.form}
|
className={styles.form}
|
||||||
>
|
>
|
||||||
<RbAlert icon={<img src={checkBg} className="rb:w-[24px] rb:h-[24px]" />} className="rb:mb-[24px]">
|
<RbAlert icon={<img src={checkBg} className="rb:w-6 rb:h-6" />} className="rb:mb-6">
|
||||||
<div className="rb:text-[14px] rb:font-medium rb:leading-[20px]">
|
<div className="rb:text-[14px] rb:font-medium rb:leading-5">
|
||||||
{t('login.invitationVerified')}
|
{t('login.invitationVerified')}
|
||||||
<div className="rb:text-[12px] rb:font-regular rb:leading-[16px] rb:mt-[4px]">{t('login.account')}: {values?.email || '-'}</div>
|
<div className="rb:text-[12px] rb:font-regular rb:leading-4 rb:mt-1">{t('login.account')}: {values?.email || '-'}</div>
|
||||||
</div>
|
</div>
|
||||||
</RbAlert>
|
</RbAlert>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
@@ -164,14 +184,14 @@ const InviteRegister: React.FC = () => {
|
|||||||
label={t('login.setPassword')}
|
label={t('login.setPassword')}
|
||||||
extra={
|
extra={
|
||||||
<div>
|
<div>
|
||||||
<div className="rb:mb-[12px]">
|
<div className="rb:mb-3">
|
||||||
<Progress
|
<Progress
|
||||||
percent={passwordStrength === 'weak' ? 33 : passwordStrength === 'medium' ? 66 : passwordStrength === 'strong' ? 100 : 0}
|
percent={passwordStrength === 'weak' ? 33 : passwordStrength === 'medium' ? 66 : passwordStrength === 'strong' ? 100 : 0}
|
||||||
steps={3}
|
steps={3}
|
||||||
showInfo={false}
|
showInfo={false}
|
||||||
style={{width: '100%'}}
|
style={{width: '100%'}}
|
||||||
/>
|
/>
|
||||||
<div className="rb:font-medium rb:mt-[8px]">
|
<div className="rb:font-medium rb:mt-2">
|
||||||
{t('login.passwordStrength')}:
|
{t('login.passwordStrength')}:
|
||||||
{passwordStrength
|
{passwordStrength
|
||||||
? <span className="rb:text-[#155EEF]">{t(`login.${passwordStrength}`)}</span>
|
? <span className="rb:text-[#155EEF]">{t(`login.${passwordStrength}`)}</span>
|
||||||
@@ -191,7 +211,7 @@ const InviteRegister: React.FC = () => {
|
|||||||
}
|
}
|
||||||
const { error } = validatePasswordStrength(value);
|
const { error } = validatePasswordStrength(value);
|
||||||
if (error && value.length >= 8) {
|
if (error && value.length >= 8) {
|
||||||
return Promise.resolve(); // 强度提示但不阻止提交
|
return Promise.resolve();
|
||||||
} else if (error) {
|
} else if (error) {
|
||||||
return Promise.reject(new Error(error));
|
return Promise.reject(new Error(error));
|
||||||
}
|
}
|
||||||
@@ -221,7 +241,7 @@ const InviteRegister: React.FC = () => {
|
|||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="username"
|
name="username"
|
||||||
label={<>{t('login.name')}<span className="rb:text-[12px] rb:text-[#5B6167] rb:font-regular rb:leading-[20px]"> {t('login.nameSubTitle')}</span></>}
|
label={<>{t('login.name')}<span className="rb:text-[12px] rb:text-[#5B6167] rb:font-regular rb:leading-5"> {t('login.nameSubTitle')}</span></>}
|
||||||
>
|
>
|
||||||
<Input placeholder={t('login.namePlaceholder')} />
|
<Input placeholder={t('login.namePlaceholder')} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
@@ -230,7 +250,7 @@ const InviteRegister: React.FC = () => {
|
|||||||
block
|
block
|
||||||
loading={loading}
|
loading={loading}
|
||||||
htmlType="submit"
|
htmlType="submit"
|
||||||
className="rb:h-[40px]! rb:rounded-[8px]!"
|
className="rb:h-10! rb:rounded-lg!"
|
||||||
>
|
>
|
||||||
{t('login.register')}
|
{t('login.register')}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -1,14 +1,41 @@
|
|||||||
|
/*
|
||||||
|
* @Author: ZhaoYing
|
||||||
|
* @Date: 2026-02-03 16:37:20
|
||||||
|
* @Last Modified by: ZhaoYing
|
||||||
|
* @Last Modified time: 2026-02-03 16:37:20
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Type definitions for Invite Register
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registration form data
|
||||||
|
*/
|
||||||
export interface LoginForm {
|
export interface LoginForm {
|
||||||
|
/** User email address */
|
||||||
email: string;
|
email: string;
|
||||||
|
/** User password */
|
||||||
password: string;
|
password: string;
|
||||||
|
/** Password confirmation */
|
||||||
confirmPassword: string;
|
confirmPassword: string;
|
||||||
|
/** User display name */
|
||||||
username: string;
|
username: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invite token validation response
|
||||||
|
*/
|
||||||
export interface ValidateToken {
|
export interface ValidateToken {
|
||||||
|
/** Workspace name */
|
||||||
workspace_name: string;
|
workspace_name: string;
|
||||||
|
/** Workspace ID */
|
||||||
workspace_id: string;
|
workspace_id: string;
|
||||||
|
/** Invited user email */
|
||||||
email: string;
|
email: string;
|
||||||
|
/** User role in workspace */
|
||||||
role: string;
|
role: string;
|
||||||
|
/** Whether token is expired */
|
||||||
is_expired: boolean;
|
is_expired: boolean;
|
||||||
|
/** Whether token is valid */
|
||||||
is_valid: boolean;
|
is_valid: boolean;
|
||||||
}
|
}
|
||||||
@@ -1,8 +1,21 @@
|
|||||||
|
/*
|
||||||
|
* @Author: ZhaoYing
|
||||||
|
* @Date: 2026-02-03 16:40:01
|
||||||
|
* @Last Modified by: ZhaoYing
|
||||||
|
* @Last Modified time: 2026-02-03 16:40:32
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Login Page
|
||||||
|
* Handles user authentication and login
|
||||||
|
* Features split-screen design with branding and login form
|
||||||
|
*/
|
||||||
|
|
||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Button, Input, Form, App } from 'antd';
|
import { Button, Input, Form, App } from 'antd';
|
||||||
import { useUser, type LoginInfo } from '@/store/user';
|
|
||||||
import type { FormProps } from 'antd';
|
import type { FormProps } from 'antd';
|
||||||
|
|
||||||
|
import { useUser, type LoginInfo } from '@/store/user';
|
||||||
import { login } from '@/api/user'
|
import { login } from '@/api/user'
|
||||||
import loginBg from '@/assets/images/login/loginBg.png'
|
import loginBg from '@/assets/images/login/loginBg.png'
|
||||||
import check from '@/assets/images/login/check.png'
|
import check from '@/assets/images/login/check.png'
|
||||||
@@ -10,8 +23,14 @@ import email from '@/assets/images/login/email.svg'
|
|||||||
import lock from '@/assets/images/login/lock.svg'
|
import lock from '@/assets/images/login/lock.svg'
|
||||||
import type { LoginForm } from './types';
|
import type { LoginForm } from './types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Input field styling
|
||||||
|
*/
|
||||||
const inputClassName = "rb:rounded-[8px]! rb:p-[12px]! rb:h-[44px]!"
|
const inputClassName = "rb:rounded-[8px]! rb:p-[12px]! rb:h-[44px]!"
|
||||||
const LoginPage: React.FC = () => {
|
|
||||||
|
/**
|
||||||
|
* Login page component
|
||||||
|
*/const LoginPage: React.FC = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { clearUserInfo, updateLoginInfo, getUserInfo } = useUser();
|
const { clearUserInfo, updateLoginInfo, getUserInfo } = useUser();
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
@@ -22,7 +41,7 @@ const LoginPage: React.FC = () => {
|
|||||||
clearUserInfo();
|
clearUserInfo();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// 处理登录提交
|
/** Handle login form submission */
|
||||||
const handleLogin: FormProps<LoginForm>['onFinish'] = async (values) => {
|
const handleLogin: FormProps<LoginForm>['onFinish'] = async (values) => {
|
||||||
if (!values.email) {
|
if (!values.email) {
|
||||||
message.warning(t('login.emailPlaceholder'));
|
message.warning(t('login.emailPlaceholder'));
|
||||||
@@ -48,18 +67,18 @@ const LoginPage: React.FC = () => {
|
|||||||
<div className="rb:min-h-screen rb:flex rb:h-screen">
|
<div className="rb:min-h-screen rb:flex rb:h-screen">
|
||||||
<div className="rb:relative rb:w-1/2 rb:h-screen rb:overflow-hidden">
|
<div className="rb:relative rb:w-1/2 rb:h-screen rb:overflow-hidden">
|
||||||
<img src={loginBg} alt="loginBg" className="rb:w-full rb:h-full rb:object-cover rb:absolute rb:top-1/2 rb:-translate-y-1/2 rb:left-0" />
|
<img src={loginBg} alt="loginBg" className="rb:w-full rb:h-full rb:object-cover rb:absolute rb:top-1/2 rb:-translate-y-1/2 rb:left-0" />
|
||||||
<div className="rb:absolute rb:top-[56px] rb:left-[64px]">
|
<div className="rb:absolute rb:top-14 rb:left-16">
|
||||||
<div className="rb:text-[28px] rb:leading-[33px] rb:font-bold rb:font-[AlimamaShuHeiTi,AlimamaShuHeiTi] rb:mb-[16px]">{t('login.title')}</div>
|
<div className="rb:text-[28px] rb:leading-8.25 rb:font-bold rb:font-[AlimamaShuHeiTi,AlimamaShuHeiTi] rb:mb-4">{t('login.title')}</div>
|
||||||
<div className="rb:text-[18px] rb:leading-[25px] rb:font-regular">{t('login.subTitle')}</div>
|
<div className="rb:text-[18px] rb:leading-6.25 rb:font-regular">{t('login.subTitle')}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="rb:absolute rb:bottom-[81px] rb:left-[64px] rb:grid rb:grid-cols-2 rb:gap-x-[120px] rb:gap-y-[43px]">
|
<div className="rb:absolute rb:bottom-20.25 rb:left-16 rb:grid rb:grid-cols-2 rb:gap-x-30 rb:gap-y-10.75">
|
||||||
{['intelligentMemory', 'instantRecall', 'knowledgeAssociation'].map(key => (
|
{['intelligentMemory', 'instantRecall', 'knowledgeAssociation'].map(key => (
|
||||||
<div key={key} className="rb:flex">
|
<div key={key} className="rb:flex">
|
||||||
<img src={check} className="rb:w-[16px] rb:h-[16px] rb:mr-[8px] rb:mt-[3px]" />
|
<img src={check} className="rb:w-4 rb:h-4 rb:mr-2 rb:mt-0.75" />
|
||||||
<div className="rb:text-[16px] rb:leading-[22px]">
|
<div className="rb:text-[16px] rb:leading-5.5">
|
||||||
<div className="rb:font-medium">{t(`login.${key}`)}</div>
|
<div className="rb:font-medium">{t(`login.${key}`)}</div>
|
||||||
<div className="rb:text-[#5B6167] rb:text-[14px] rb:leading-[20px] rb:font-regular! rb:mt-[8px]">{t(`login.${key}Desc`)}</div>
|
<div className="rb:text-[#5B6167] rb:text-[14px] rb:leading-5 rb:font-regular! rb:mt-2">{t(`login.${key}Desc`)}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
@@ -67,22 +86,22 @@ const LoginPage: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="rb:bg-[#FFFFFF] rb:flex rb:items-center rb:justify-center rb:flex-[1_1_auto]">
|
<div className="rb:bg-[#FFFFFF] rb:flex rb:items-center rb:justify-center rb:flex-[1_1_auto]">
|
||||||
<div className="rb:w-[400px] rb:mx-auto">
|
<div className="rb:w-100 rb:mx-auto">
|
||||||
<div className="rb:text-center rb:text-[28px] rb:font-semibold rb:leading-[32px] rb:mb-[48px]">{t('login.welcome')}</div>
|
<div className="rb:text-center rb:text-[28px] rb:font-semibold rb:leading-8 rb:mb-12">{t('login.welcome')}</div>
|
||||||
<Form
|
<Form
|
||||||
form={form}
|
form={form}
|
||||||
onFinish={handleLogin}
|
onFinish={handleLogin}
|
||||||
>
|
>
|
||||||
<Form.Item name="email" className="rb:mb-[20px]!">
|
<Form.Item name="email" className="rb:mb-5!">
|
||||||
<Input
|
<Input
|
||||||
prefix={<img src={email} className="rb:w-[20px] rb:h-[20px] rb:mr-[8px]" />}
|
prefix={<img src={email} className="rb:w-5 rb:h-5 rb:mr-2" />}
|
||||||
placeholder={t('login.emailPlaceholder')}
|
placeholder={t('login.emailPlaceholder')}
|
||||||
className={inputClassName}
|
className={inputClassName}
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item name="password">
|
<Form.Item name="password">
|
||||||
<Input.Password
|
<Input.Password
|
||||||
prefix={<img src={lock} className="rb:w-[20px] rb:h-[20px] rb:mr-[8px]" />}
|
prefix={<img src={lock} className="rb:w-5 rb:h-5 rb:mr-2" />}
|
||||||
placeholder={t('login.passwordPlaceholder')}
|
placeholder={t('login.passwordPlaceholder')}
|
||||||
className={inputClassName}
|
className={inputClassName}
|
||||||
/>
|
/>
|
||||||
@@ -92,7 +111,7 @@ const LoginPage: React.FC = () => {
|
|||||||
block
|
block
|
||||||
loading={loading}
|
loading={loading}
|
||||||
htmlType="submit"
|
htmlType="submit"
|
||||||
className="rb:h-[40px]! rb:rounded-[8px]! rb:mt-[16px]"
|
className="rb:h-10! rb:rounded-lg! rb:mt-4"
|
||||||
>
|
>
|
||||||
{t('login.loginIn')}
|
{t('login.loginIn')}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -1,4 +1,19 @@
|
|||||||
|
/*
|
||||||
|
* @Author: ZhaoYing
|
||||||
|
* @Date: 2026-02-03 16:40:06
|
||||||
|
* @Last Modified by: ZhaoYing
|
||||||
|
* @Last Modified time: 2026-02-03 16:40:06
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Type definitions for Login
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Login form data
|
||||||
|
*/
|
||||||
export interface LoginForm {
|
export interface LoginForm {
|
||||||
|
/** User email address */
|
||||||
email: string;
|
email: string;
|
||||||
|
/** User password */
|
||||||
password: string;
|
password: string;
|
||||||
}
|
}
|
||||||
@@ -1,3 +1,15 @@
|
|||||||
|
/*
|
||||||
|
* @Author: ZhaoYing
|
||||||
|
* @Date: 2026-02-03 16:42:17
|
||||||
|
* @Last Modified by: ZhaoYing
|
||||||
|
* @Last Modified time: 2026-02-03 16:42:17
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Member Modal
|
||||||
|
* Modal for inviting new members or editing existing member roles
|
||||||
|
* Generates invitation links for new members
|
||||||
|
*/
|
||||||
|
|
||||||
import { forwardRef, useImperativeHandle, useState } from 'react';
|
import { forwardRef, useImperativeHandle, useState } from 'react';
|
||||||
import { Form, Input, Select, Modal, App } from 'antd';
|
import { Form, Input, Select, Modal, App } from 'antd';
|
||||||
import type { SelectProps } from 'antd';
|
import type { SelectProps } from 'antd';
|
||||||
@@ -12,10 +24,17 @@ const FormItem = Form.Item;
|
|||||||
const { Option } = Select;
|
const { Option } = Select;
|
||||||
type LabelRender = SelectProps['labelRender'];
|
type LabelRender = SelectProps['labelRender'];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component props
|
||||||
|
*/
|
||||||
interface MemberModalProps {
|
interface MemberModalProps {
|
||||||
|
/** Callback to refresh member list */
|
||||||
refreshTable: () => void;
|
refreshTable: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modal for member invitation and editing
|
||||||
|
*/
|
||||||
const MemberModal = forwardRef<MemberModalRef, MemberModalProps>(({
|
const MemberModal = forwardRef<MemberModalRef, MemberModalProps>(({
|
||||||
refreshTable
|
refreshTable
|
||||||
}, ref) => {
|
}, ref) => {
|
||||||
@@ -36,7 +55,7 @@ const MemberModal = forwardRef<MemberModalRef, MemberModalProps>(({
|
|||||||
]
|
]
|
||||||
const values: MemberModalData = Form.useWatch([], form);
|
const values: MemberModalData = Form.useWatch([], form);
|
||||||
|
|
||||||
// 封装取消方法,添加关闭弹窗逻辑
|
/** Close modal and reset form */
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
setVisible(false);
|
setVisible(false);
|
||||||
setEditingUser(null);
|
setEditingUser(null);
|
||||||
@@ -44,10 +63,11 @@ const MemberModal = forwardRef<MemberModalRef, MemberModalProps>(({
|
|||||||
setLoading(false)
|
setLoading(false)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** Open modal with optional member data for editing */
|
||||||
const handleOpen = (member?: Member | null) => {
|
const handleOpen = (member?: Member | null) => {
|
||||||
if (member) {
|
if (member) {
|
||||||
setEditingUser(member);
|
setEditingUser(member);
|
||||||
// 设置表单值
|
// Set form values
|
||||||
form.setFieldsValue({
|
form.setFieldsValue({
|
||||||
email: member.account,
|
email: member.account,
|
||||||
role: member.role
|
role: member.role
|
||||||
@@ -57,7 +77,7 @@ const MemberModal = forwardRef<MemberModalRef, MemberModalProps>(({
|
|||||||
}
|
}
|
||||||
setVisible(true);
|
setVisible(true);
|
||||||
};
|
};
|
||||||
// 封装保存方法,添加提交逻辑
|
/** Save member (invite or update) */
|
||||||
const handleSave = () => {
|
const handleSave = () => {
|
||||||
form
|
form
|
||||||
.validateFields()
|
.validateFields()
|
||||||
@@ -100,11 +120,12 @@ const MemberModal = forwardRef<MemberModalRef, MemberModalProps>(({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 暴露给父组件的方法
|
/** Expose methods to parent component */
|
||||||
useImperativeHandle(ref, () => ({
|
useImperativeHandle(ref, () => ({
|
||||||
handleOpen,
|
handleOpen,
|
||||||
handleClose
|
handleClose
|
||||||
}));
|
}));
|
||||||
|
/** Custom label renderer for role select */
|
||||||
const labelRender: LabelRender = (props) => {
|
const labelRender: LabelRender = (props) => {
|
||||||
const { label, value } = props;
|
const { label, value } = props;
|
||||||
|
|
||||||
|
|||||||
@@ -1,35 +1,49 @@
|
|||||||
|
/*
|
||||||
|
* @Author: ZhaoYing
|
||||||
|
* @Date: 2026-02-03 16:42:12
|
||||||
|
* @Last Modified by: ZhaoYing
|
||||||
|
* @Last Modified time: 2026-02-03 16:42:12
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Member Management Page
|
||||||
|
* Manages workspace members with invite, edit, and delete functionality
|
||||||
|
*/
|
||||||
|
|
||||||
import React, { useRef } from 'react';
|
import React, { useRef } from 'react';
|
||||||
import { App, Button, Space } from 'antd';
|
import { App, Button, Space } from 'antd';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import type { ColumnsType } from 'antd/es/table';
|
import type { ColumnsType } from 'antd/es/table';
|
||||||
import type { AnyObject } from 'antd/es/_util/type';
|
import type { AnyObject } from 'antd/es/_util/type';
|
||||||
import { deleteMember, memberListUrl } from '@/api/member';
|
|
||||||
|
|
||||||
|
import { deleteMember, memberListUrl } from '@/api/member';
|
||||||
import MemberModal from './components/MemberModal';
|
import MemberModal from './components/MemberModal';
|
||||||
import type { Member, MemberModalRef } from './types'
|
import type { Member, MemberModalRef } from './types'
|
||||||
import Tag from '@/components/Tag';
|
import Tag from '@/components/Tag';
|
||||||
import Table, { type TableRef } from '@/components/Table'
|
import Table, { type TableRef } from '@/components/Table'
|
||||||
import { formatDateTime } from '@/utils/format';
|
import { formatDateTime } from '@/utils/format';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Member management main component
|
||||||
|
*/
|
||||||
const MemberManagement: React.FC = () => {
|
const MemberManagement: React.FC = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { message, modal } = App.useApp();
|
const { message, modal } = App.useApp();
|
||||||
const memberFormRef = useRef<MemberModalRef>(null);
|
const memberFormRef = useRef<MemberModalRef>(null);
|
||||||
const tableRef = useRef<TableRef>(null);
|
const tableRef = useRef<TableRef>(null);
|
||||||
|
|
||||||
// 打开新增用户弹窗
|
/** Open member modal for create or edit */
|
||||||
const handleEdit = (member?: Member) => {
|
const handleEdit = (member?: Member) => {
|
||||||
if (memberFormRef.current) {
|
if (memberFormRef.current) {
|
||||||
memberFormRef.current.handleOpen(member);
|
memberFormRef.current.handleOpen(member);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 刷新列表数据
|
/** Refresh member list */
|
||||||
const refreshTable = () => {
|
const refreshTable = () => {
|
||||||
tableRef.current?.loadData()
|
tableRef.current?.loadData()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 单个删除用户
|
/** Delete member with confirmation */
|
||||||
const handleDelete = async (member: Member) => {
|
const handleDelete = async (member: Member) => {
|
||||||
modal.confirm({
|
modal.confirm({
|
||||||
title: t('common.confirmDeleteDesc', { name: member.username }),
|
title: t('common.confirmDeleteDesc', { name: member.username }),
|
||||||
@@ -46,7 +60,7 @@ const MemberManagement: React.FC = () => {
|
|||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
// 表格列配置
|
/** Table column configuration */
|
||||||
const columns: ColumnsType = [
|
const columns: ColumnsType = [
|
||||||
{
|
{
|
||||||
title: t('member.username'),
|
title: t('member.username'),
|
||||||
|
|||||||
@@ -1,17 +1,43 @@
|
|||||||
// 用户数据类型
|
/*
|
||||||
|
* @Author: ZhaoYing
|
||||||
|
* @Date: 2026-02-03 16:42:00
|
||||||
|
* @Last Modified by: ZhaoYing
|
||||||
|
* @Last Modified time: 2026-02-03 16:42:00
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Type definitions for Member Management
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Member data structure
|
||||||
|
*/
|
||||||
export interface Member {
|
export interface Member {
|
||||||
|
/** Member ID */
|
||||||
id: string;
|
id: string;
|
||||||
|
/** Member username */
|
||||||
username: string;
|
username: string;
|
||||||
|
/** Member account (email) */
|
||||||
account: string;
|
account: string;
|
||||||
|
/** Member role */
|
||||||
role: string;
|
role: string;
|
||||||
|
/** Last login timestamp */
|
||||||
last_login_at: string | number;
|
last_login_at: string | number;
|
||||||
}
|
}
|
||||||
// 用户表单数据类型
|
|
||||||
|
/**
|
||||||
|
* Member invitation/edit form data
|
||||||
|
*/
|
||||||
export interface MemberModalData {
|
export interface MemberModalData {
|
||||||
|
/** Member email address */
|
||||||
email: string;
|
email: string;
|
||||||
|
/** Member role */
|
||||||
role: string;
|
role: string;
|
||||||
}
|
}
|
||||||
// 定义组件暴露的方法接口
|
|
||||||
|
/**
|
||||||
|
* Member modal ref interface
|
||||||
|
*/
|
||||||
export interface MemberModalRef {
|
export interface MemberModalRef {
|
||||||
|
/** Open modal with optional member data for editing */
|
||||||
handleOpen: (user?: Member | null) => void;
|
handleOpen: (user?: Member | null) => void;
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,15 @@
|
|||||||
|
/**
|
||||||
|
* Card Component
|
||||||
|
* Styled wrapper for conversation and analysis panels
|
||||||
|
* Provides consistent layout and styling
|
||||||
|
*/
|
||||||
|
|
||||||
import { Card } from 'antd'
|
import { Card } from 'antd'
|
||||||
import { type FC, type ReactNode } from 'react'
|
import { type FC, type ReactNode } from 'react'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component props
|
||||||
|
*/
|
||||||
interface RbCardProps {
|
interface RbCardProps {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
title: string;
|
title: string;
|
||||||
|
|||||||
@@ -1,6 +1,20 @@
|
|||||||
|
/*
|
||||||
|
* @Author: ZhaoYing
|
||||||
|
* @Date: 2026-02-03 17:09:03
|
||||||
|
* @Last Modified by: ZhaoYing
|
||||||
|
* @Last Modified time: 2026-02-03 17:09:03
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Memory Conversation Page
|
||||||
|
* Interactive conversation interface with memory analysis
|
||||||
|
* Supports deep thinking, normal reply, and quick reply modes
|
||||||
|
*/
|
||||||
|
|
||||||
import { type FC, type ReactNode, useState, useEffect } from 'react'
|
import { type FC, type ReactNode, useState, useEffect } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { Col, Row, App, Skeleton, Space, Select, Flex } from 'antd'
|
import { Col, Row, App, Skeleton, Space, Select, Flex } from 'antd'
|
||||||
|
import dayjs from 'dayjs'
|
||||||
|
import type { AnyObject } from 'antd/es/_util/type';
|
||||||
import clsx from 'clsx'
|
import clsx from 'clsx'
|
||||||
|
|
||||||
import ConversationEmptyIcon from '@/assets/images/conversation/conversationEmpty.svg'
|
import ConversationEmptyIcon from '@/assets/images/conversation/conversationEmpty.svg'
|
||||||
@@ -19,46 +33,61 @@ import DeepThinkingCheckedIcon from '@/assets/images/conversation/deepThinkingCh
|
|||||||
import OnlineCheckedIcon from '@/assets/images/conversation/onlineChecked.svg'
|
import OnlineCheckedIcon from '@/assets/images/conversation/onlineChecked.svg'
|
||||||
import MemoryFunctionCheckedIcon from '@/assets/images/conversation/memoryFunctionChecked.svg'
|
import MemoryFunctionCheckedIcon from '@/assets/images/conversation/memoryFunctionChecked.svg'
|
||||||
import type { ChatItem } from '@/components/Chat/types'
|
import type { ChatItem } from '@/components/Chat/types'
|
||||||
import dayjs from 'dayjs'
|
|
||||||
import type { AnyObject } from 'antd/es/_util/type';
|
|
||||||
|
|
||||||
|
|
||||||
|
/** Search mode configuration */
|
||||||
const searchSwitchList = [
|
const searchSwitchList = [
|
||||||
{
|
{
|
||||||
icon: DeepThinkingIcon,
|
icon: DeepThinkingIcon,
|
||||||
checkedIcon: DeepThinkingCheckedIcon,
|
checkedIcon: DeepThinkingCheckedIcon,
|
||||||
value: '0',
|
value: '0',
|
||||||
label: 'deepThinking' // 深度思考
|
label: 'deepThinking'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: MemoryFunctionIcon,
|
icon: MemoryFunctionIcon,
|
||||||
checkedIcon: MemoryFunctionCheckedIcon,
|
checkedIcon: MemoryFunctionCheckedIcon,
|
||||||
value: '1',
|
value: '1',
|
||||||
label: 'normalReply' // 普通回复
|
label: 'normalReply'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: OnlineIcon,
|
icon: OnlineIcon,
|
||||||
checkedIcon: OnlineCheckedIcon,
|
checkedIcon: OnlineCheckedIcon,
|
||||||
value: '2',
|
value: '2',
|
||||||
label: 'quickReply' // 快速回复
|
label: 'quickReply'
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test parameters for conversation API
|
||||||
|
*/
|
||||||
export interface TestParams {
|
export interface TestParams {
|
||||||
|
/** End user identifier */
|
||||||
end_user_id: string;
|
end_user_id: string;
|
||||||
|
/** User message content */
|
||||||
message: string;
|
message: string;
|
||||||
|
/** Search mode switch (0: deep thinking, 1: normal, 2: quick) */
|
||||||
search_switch: string;
|
search_switch: string;
|
||||||
|
/** Conversation history */
|
||||||
history: { role: string; content: string }[];
|
history: { role: string; content: string }[];
|
||||||
|
/** Enable web search */
|
||||||
web_search?: boolean;
|
web_search?: boolean;
|
||||||
|
/** Enable memory function */
|
||||||
memory?: boolean;
|
memory?: boolean;
|
||||||
|
/** Conversation ID */
|
||||||
conversation_id?: string;
|
conversation_id?: string;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Data item in analysis logs
|
||||||
|
*/
|
||||||
interface DataItem {
|
interface DataItem {
|
||||||
id: string;
|
id: string;
|
||||||
question: string;
|
question: string;
|
||||||
type: string;
|
type: string;
|
||||||
reason: string;
|
reason: string;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Log item for conversation analysis
|
||||||
|
*/
|
||||||
export interface LogItem {
|
export interface LogItem {
|
||||||
type: string;
|
type: string;
|
||||||
title: string;
|
title: string;
|
||||||
@@ -72,6 +101,9 @@ export interface LogItem {
|
|||||||
index?: number;
|
index?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Content wrapper component for analysis items
|
||||||
|
*/
|
||||||
const ContentWrapper: FC<{ children: ReactNode }> = ({ children }) => (
|
const ContentWrapper: FC<{ children: ReactNode }> = ({ children }) => (
|
||||||
<div className="rb:p-3 rb:bg-[#F6F8FC] rb:border rb:border-[#DFE4ED] rb:rounded-lg">
|
<div className="rb:p-3 rb:bg-[#F6F8FC] rb:border rb:border-[#DFE4ED] rb:rounded-lg">
|
||||||
{children}
|
{children}
|
||||||
@@ -89,6 +121,7 @@ const MemoryConversation: FC = () => {
|
|||||||
const [search_switch, setSearchSwitch] = useState('0')
|
const [search_switch, setSearchSwitch] = useState('0')
|
||||||
const [msg, setMsg] = useState<string>('')
|
const [msg, setMsg] = useState<string>('')
|
||||||
|
|
||||||
|
/** Load user list on mount */
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getUserMemoryList().then(res => {
|
getUserMemoryList().then(res => {
|
||||||
setUserList((res as Data[] || []).map(item => ({
|
setUserList((res as Data[] || []).map(item => ({
|
||||||
@@ -98,6 +131,7 @@ const MemoryConversation: FC = () => {
|
|||||||
})
|
})
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
/** Handle message send */
|
||||||
const handleSend = () => {
|
const handleSend = () => {
|
||||||
if(!userId) {
|
if(!userId) {
|
||||||
message.warning(t('common.inputPlaceholder', { title: t('memoryConversation.userID') }))
|
message.warning(t('common.inputPlaceholder', { title: t('memoryConversation.userID') }))
|
||||||
@@ -121,6 +155,7 @@ const MemoryConversation: FC = () => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Handle search mode change */
|
||||||
const handleChange = (value: string) => {
|
const handleChange = (value: string) => {
|
||||||
setSearchSwitch(value)
|
setSearchSwitch(value)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,24 @@
|
|||||||
|
/*
|
||||||
|
* @Author: ZhaoYing
|
||||||
|
* @Date: 2026-02-03 17:30:51
|
||||||
|
* @Last Modified by: ZhaoYing
|
||||||
|
* @Last Modified time: 2026-02-03 17:30:51
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Card Component
|
||||||
|
* Collapsible card wrapper for configuration sections
|
||||||
|
*/
|
||||||
|
|
||||||
import { type FC, type ReactNode } from 'react'
|
import { type FC, type ReactNode } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
|
|
||||||
import RbCard from '@/components/RbCard/Card'
|
import RbCard from '@/components/RbCard/Card'
|
||||||
import down from '@/assets/images/userMemory/down.svg'
|
import down from '@/assets/images/userMemory/down.svg'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component props
|
||||||
|
*/
|
||||||
interface CardProps {
|
interface CardProps {
|
||||||
type?: string;
|
type?: string;
|
||||||
title: string | ReactNode;
|
title: string | ReactNode;
|
||||||
@@ -35,7 +50,7 @@ const Card: FC<CardProps> = ({
|
|||||||
headerType="borderless"
|
headerType="borderless"
|
||||||
extra={type && handleExpand && (
|
extra={type && handleExpand && (
|
||||||
<div
|
<div
|
||||||
className="rb:flex rb:items-center rb:text-[14px] rb:text-[#5B6167] rb:cursor-pointer rb:font-regular rb:leading-[20px]"
|
className="rb:flex rb:items-center rb:text-[14px] rb:text-[#5B6167] rb:cursor-pointer rb:font-regular rb:leading-5"
|
||||||
onClick={() => handleExpand(type)}
|
onClick={() => handleExpand(type)}
|
||||||
>
|
>
|
||||||
{expanded ? t('common.foldUp') : t('common.expanded')}
|
{expanded ? t('common.foldUp') : t('common.expanded')}
|
||||||
|
|||||||
@@ -1,9 +1,23 @@
|
|||||||
|
/*
|
||||||
|
* @Author: ZhaoYing
|
||||||
|
* @Date: 2026-02-03 17:30:11
|
||||||
|
* @Last Modified by: ZhaoYing
|
||||||
|
* @Last Modified time: 2026-02-04 10:08:49
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Result Component
|
||||||
|
* Displays real-time extraction results with progress tracking
|
||||||
|
* Shows text preprocessing, knowledge extraction, node/edge creation, and deduplication
|
||||||
|
*/
|
||||||
|
|
||||||
import { type FC, useState } from 'react'
|
import { type FC, useState } from 'react'
|
||||||
import { useParams } from 'react-router-dom'
|
import { useParams } from 'react-router-dom'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { Space, Button, Progress } from 'antd'
|
import { Space, Button, Progress } from 'antd'
|
||||||
import { ExclamationCircleFilled, CheckCircleFilled, ClockCircleOutlined, LoadingOutlined } from '@ant-design/icons'
|
import { ExclamationCircleFilled, CheckCircleFilled, ClockCircleOutlined, LoadingOutlined } from '@ant-design/icons'
|
||||||
import clsx from 'clsx'
|
import clsx from 'clsx'
|
||||||
|
import type { AnyObject } from 'antd/es/_util/type';
|
||||||
|
|
||||||
import Card from './Card'
|
import Card from './Card'
|
||||||
import RbCard from '@/components/RbCard/Card'
|
import RbCard from '@/components/RbCard/Card'
|
||||||
import RbAlert from '@/components/RbAlert'
|
import RbAlert from '@/components/RbAlert'
|
||||||
@@ -13,18 +27,24 @@ import { type SSEMessage } from '@/utils/stream'
|
|||||||
import Tag, { type TagProps } from '@/components/Tag'
|
import Tag, { type TagProps } from '@/components/Tag'
|
||||||
import Markdown from '@/components/Markdown'
|
import Markdown from '@/components/Markdown'
|
||||||
import { groupDataByType } from '../constant'
|
import { groupDataByType } from '../constant'
|
||||||
import type { AnyObject } from 'antd/es/_util/type';
|
|
||||||
|
|
||||||
|
/** Result metric mapping */
|
||||||
const resultObj = {
|
const resultObj = {
|
||||||
extractTheNumberOfEntities: 'entities.extracted_count',
|
extractTheNumberOfEntities: 'entities.extracted_count',
|
||||||
numberOfEntityDisambiguation: 'disambiguation.block_count',
|
numberOfEntityDisambiguation: 'disambiguation.block_count',
|
||||||
memoryFragments: 'memory.chunks',
|
memoryFragments: 'memory.chunks',
|
||||||
numberOfRelationalTriples: 'triplets.count'
|
numberOfRelationalTriples: 'triplets.count'
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Component props
|
||||||
|
*/
|
||||||
interface ResultProps {
|
interface ResultProps {
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
handleSave: () => void;
|
handleSave: () => void;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Module processing item
|
||||||
|
*/
|
||||||
interface ModuleItem {
|
interface ModuleItem {
|
||||||
status: 'pending' | 'processing' | 'completed' | 'failed';
|
status: 'pending' | 'processing' | 'completed' | 'failed';
|
||||||
data: any[],
|
data: any[],
|
||||||
@@ -32,6 +52,7 @@ interface ModuleItem {
|
|||||||
start_at?: number;
|
start_at?: number;
|
||||||
end_at?: number;
|
end_at?: number;
|
||||||
}
|
}
|
||||||
|
/** Tag color mapping by status */
|
||||||
const tagColors: {
|
const tagColors: {
|
||||||
[key: string]: TagProps['color']
|
[key: string]: TagProps['color']
|
||||||
} = {
|
} = {
|
||||||
@@ -40,6 +61,7 @@ const tagColors: {
|
|||||||
completed: 'success',
|
completed: 'success',
|
||||||
failed: 'error'
|
failed: 'error'
|
||||||
}
|
}
|
||||||
|
/** Initial module state */
|
||||||
const initObj = {
|
const initObj = {
|
||||||
data: [],
|
data: [],
|
||||||
status: 'pending',
|
status: 'pending',
|
||||||
@@ -57,6 +79,7 @@ const Result: FC<ResultProps> = ({ loading, handleSave }) => {
|
|||||||
const [creatingNodesEdges, setCreatingNodesEdges] = useState<ModuleItem>(initObj as ModuleItem)
|
const [creatingNodesEdges, setCreatingNodesEdges] = useState<ModuleItem>(initObj as ModuleItem)
|
||||||
const [deduplication, setDeduplication] = useState<ModuleItem>(initObj as ModuleItem)
|
const [deduplication, setDeduplication] = useState<ModuleItem>(initObj as ModuleItem)
|
||||||
|
|
||||||
|
/** Run pilot test */
|
||||||
const handleRun = () => {
|
const handleRun = () => {
|
||||||
if(!id) return
|
if(!id) return
|
||||||
setTextPreprocessing({...initObj} as ModuleItem)
|
setTextPreprocessing({...initObj} as ModuleItem)
|
||||||
@@ -68,20 +91,20 @@ const Result: FC<ResultProps> = ({ loading, handleSave }) => {
|
|||||||
|
|
||||||
list.forEach((data: AnyObject) => {
|
list.forEach((data: AnyObject) => {
|
||||||
switch(data.event) {
|
switch(data.event) {
|
||||||
case 'text_preprocessing': // 开始预处理文本
|
case 'text_preprocessing': // Start text preprocessing
|
||||||
setTextPreprocessing(prev => ({
|
setTextPreprocessing(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
status: 'processing',
|
status: 'processing',
|
||||||
start_at: data.data.time
|
start_at: data.data.time
|
||||||
}))
|
}))
|
||||||
break
|
break
|
||||||
case 'text_preprocessing_result': // 预处理文本分块中
|
case 'text_preprocessing_result': // Text preprocessing in progress
|
||||||
setTextPreprocessing(prev => ({
|
setTextPreprocessing(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
data: [...prev.data, data.data?.data]
|
data: [...prev.data, data.data?.data]
|
||||||
}))
|
}))
|
||||||
break
|
break
|
||||||
case 'text_preprocessing_complete': // 预处理文本完成
|
case 'text_preprocessing_complete': // Text preprocessing complete
|
||||||
setTextPreprocessing(prev => ({
|
setTextPreprocessing(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
result: data.data?.data,
|
result: data.data?.data,
|
||||||
@@ -89,20 +112,20 @@ const Result: FC<ResultProps> = ({ loading, handleSave }) => {
|
|||||||
end_at: data.data.time
|
end_at: data.data.time
|
||||||
}))
|
}))
|
||||||
break
|
break
|
||||||
case 'knowledge_extraction': // 开始知识抽取
|
case 'knowledge_extraction': // Start knowledge extraction
|
||||||
setKnowledgeExtraction(prev => ({
|
setKnowledgeExtraction(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
status: 'processing',
|
status: 'processing',
|
||||||
start_at: data.data.time
|
start_at: data.data.time
|
||||||
}))
|
}))
|
||||||
break
|
break
|
||||||
case 'knowledge_extraction_result': // 知识抽取中
|
case 'knowledge_extraction_result': // Knowledge extraction in progress
|
||||||
setKnowledgeExtraction(prev => ({
|
setKnowledgeExtraction(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
data: [...prev.data, data.data?.data]
|
data: [...prev.data, data.data?.data]
|
||||||
}))
|
}))
|
||||||
break
|
break
|
||||||
case 'knowledge_extraction_complete': // 知识抽取完成
|
case 'knowledge_extraction_complete': // Knowledge extraction complete
|
||||||
setKnowledgeExtraction(prev => ({
|
setKnowledgeExtraction(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
result: data.data?.data,
|
result: data.data?.data,
|
||||||
@@ -110,20 +133,20 @@ const Result: FC<ResultProps> = ({ loading, handleSave }) => {
|
|||||||
end_at: data.data.time
|
end_at: data.data.time
|
||||||
}))
|
}))
|
||||||
break
|
break
|
||||||
case 'creating_nodes_edges': // 开始创建节点和边
|
case 'creating_nodes_edges': // Start creating nodes and edges
|
||||||
setCreatingNodesEdges(prev => ({
|
setCreatingNodesEdges(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
status: 'processing',
|
status: 'processing',
|
||||||
start_at: data.data.time
|
start_at: data.data.time
|
||||||
}))
|
}))
|
||||||
break
|
break
|
||||||
case 'creating_nodes_edges_result': // 创建节点和边中
|
case 'creating_nodes_edges_result': // Creating nodes and edges in progress
|
||||||
setCreatingNodesEdges(prev => ({
|
setCreatingNodesEdges(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
data: [...prev.data, data.data?.data]
|
data: [...prev.data, data.data?.data]
|
||||||
}))
|
}))
|
||||||
break
|
break
|
||||||
case 'creating_nodes_edges_complete': // 创建节点和边完成
|
case 'creating_nodes_edges_complete': // Creating nodes and edges complete
|
||||||
setCreatingNodesEdges(prev => ({
|
setCreatingNodesEdges(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
result: data.data?.data,
|
result: data.data?.data,
|
||||||
@@ -131,20 +154,20 @@ const Result: FC<ResultProps> = ({ loading, handleSave }) => {
|
|||||||
end_at: data.data.time
|
end_at: data.data.time
|
||||||
}))
|
}))
|
||||||
break
|
break
|
||||||
case 'deduplication': // 开始去重消歧
|
case 'deduplication': // Start deduplication and disambiguation
|
||||||
setDeduplication(prev => ({
|
setDeduplication(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
status: 'processing',
|
status: 'processing',
|
||||||
start_at: data.data.time
|
start_at: data.data.time
|
||||||
}))
|
}))
|
||||||
break
|
break
|
||||||
case 'dedup_disambiguation_result': // 去重消歧中
|
case 'dedup_disambiguation_result': // Deduplication and disambiguation in progress
|
||||||
setDeduplication(prev => ({
|
setDeduplication(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
data: [...prev.data, data.data.data]
|
data: [...prev.data, data.data.data]
|
||||||
}))
|
}))
|
||||||
break
|
break
|
||||||
case 'dedup_disambiguation_complete': // 去重消歧完成
|
case 'dedup_disambiguation_complete': // Deduplication and disambiguation complete
|
||||||
setDeduplication(prev => ({
|
setDeduplication(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
result: data.data?.data,
|
result: data.data?.data,
|
||||||
@@ -152,9 +175,9 @@ const Result: FC<ResultProps> = ({ loading, handleSave }) => {
|
|||||||
end_at: data.data.time
|
end_at: data.data.time
|
||||||
}))
|
}))
|
||||||
break
|
break
|
||||||
case 'generating_results': // 开始生成结果
|
case 'generating_results': // Generating results
|
||||||
break
|
break
|
||||||
case 'result': // 结果
|
case 'result': // Result
|
||||||
setTestResult(data.data?.extracted_result)
|
setTestResult(data.data?.extracted_result)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -172,6 +195,7 @@ const Result: FC<ResultProps> = ({ loading, handleSave }) => {
|
|||||||
const completedNum = [textPreprocessing, knowledgeExtraction, creatingNodesEdges, deduplication].filter(item => item.status === 'completed').length
|
const completedNum = [textPreprocessing, knowledgeExtraction, creatingNodesEdges, deduplication].filter(item => item.status === 'completed').length
|
||||||
const deduplicationData = groupDataByType(deduplication.data, 'result_type')
|
const deduplicationData = groupDataByType(deduplication.data, 'result_type')
|
||||||
|
|
||||||
|
/** Format status tag */
|
||||||
const formatTag = (status: string) => {
|
const formatTag = (status: string) => {
|
||||||
return (
|
return (
|
||||||
<Tag color={tagColors[status]}>
|
<Tag color={tagColors[status]}>
|
||||||
@@ -181,12 +205,14 @@ const Result: FC<ResultProps> = ({ loading, handleSave }) => {
|
|||||||
</Tag>
|
</Tag>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
/** Format processing time */
|
||||||
const formatTime = (data: ModuleItem, color?: string) => {
|
const formatTime = (data: ModuleItem, color?: string) => {
|
||||||
if (typeof data.end_at === 'number' && typeof data.start_at === 'number') {
|
if (typeof data.end_at === 'number' && typeof data.start_at === 'number') {
|
||||||
return <div className={`rb:mt-3 rb:text-[${color ?? '#155EEF'}]`}>{t('memoryExtractionEngine.time')}{data.end_at - data.start_at}ms</div>
|
return <div className={`rb:mt-3 rb:text-[${color ?? '#155EEF'}]`}>{t('memoryExtractionEngine.time')}{data.end_at - data.start_at}ms</div>
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
/** Convert first character to lowercase */
|
||||||
const lowercaseFirst = (str: string) => str.charAt(0).toLowerCase() + str.slice(1)
|
const lowercaseFirst = (str: string) => str.charAt(0).toLowerCase() + str.slice(1)
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
@@ -202,7 +228,7 @@ const Result: FC<ResultProps> = ({ loading, handleSave }) => {
|
|||||||
<RbAlert color="blue" icon={<ExclamationCircleFilled />} className="rb:mb-3.5">
|
<RbAlert color="blue" icon={<ExclamationCircleFilled />} className="rb:mb-3.5">
|
||||||
{t('memoryExtractionEngine.processing')}
|
{t('memoryExtractionEngine.processing')}
|
||||||
</RbAlert>
|
</RbAlert>
|
||||||
{/* 整体进度 */}
|
{/* Overall Progress */}
|
||||||
<div className="rb:mb-2">
|
<div className="rb:mb-2">
|
||||||
<div className="rb:flex rb:items-center rb:justify-between rb:text-[12px] rb:leading-4 rb:font-regular">
|
<div className="rb:flex rb:items-center rb:justify-between rb:text-[12px] rb:leading-4 rb:font-regular">
|
||||||
{t('memoryExtractionEngine.overallProgress')}
|
{t('memoryExtractionEngine.overallProgress')}
|
||||||
@@ -220,7 +246,7 @@ const Result: FC<ResultProps> = ({ loading, handleSave }) => {
|
|||||||
</RbAlert>
|
</RbAlert>
|
||||||
}
|
}
|
||||||
<Space size={16} direction="vertical" style={{ width: '100%' }}>
|
<Space size={16} direction="vertical" style={{ width: '100%' }}>
|
||||||
{/* 文本预处理 */}
|
{/* Text Preprocessing */}
|
||||||
<RbCard
|
<RbCard
|
||||||
title={t(`memoryExtractionEngine.text_preprocessing`)}
|
title={t(`memoryExtractionEngine.text_preprocessing`)}
|
||||||
extra={formatTag(textPreprocessing.status)}
|
extra={formatTag(textPreprocessing.status)}
|
||||||
@@ -240,7 +266,7 @@ const Result: FC<ResultProps> = ({ loading, handleSave }) => {
|
|||||||
</RbAlert>
|
</RbAlert>
|
||||||
}
|
}
|
||||||
</RbCard>
|
</RbCard>
|
||||||
{/* 知识抽取 */}
|
{/* Knowledge Extraction */}
|
||||||
<RbCard
|
<RbCard
|
||||||
title={t(`memoryExtractionEngine.knowledge_extraction`)}
|
title={t(`memoryExtractionEngine.knowledge_extraction`)}
|
||||||
extra={formatTag(knowledgeExtraction.status)}
|
extra={formatTag(knowledgeExtraction.status)}
|
||||||
@@ -260,7 +286,7 @@ const Result: FC<ResultProps> = ({ loading, handleSave }) => {
|
|||||||
})}
|
})}
|
||||||
</RbAlert>}
|
</RbAlert>}
|
||||||
</RbCard>
|
</RbCard>
|
||||||
{/* 创建实体关系 */}
|
{/* Creating Entity Relationships */}
|
||||||
<RbCard
|
<RbCard
|
||||||
title={t(`memoryExtractionEngine.creating_nodes_edges`)}
|
title={t(`memoryExtractionEngine.creating_nodes_edges`)}
|
||||||
extra={formatTag(creatingNodesEdges.status)}
|
extra={formatTag(creatingNodesEdges.status)}
|
||||||
@@ -280,7 +306,7 @@ const Result: FC<ResultProps> = ({ loading, handleSave }) => {
|
|||||||
{t('memoryExtractionEngine.creating_nodes_edges_desc', {num: creatingNodesEdges.result.entity_entity_edges_count})}
|
{t('memoryExtractionEngine.creating_nodes_edges_desc', {num: creatingNodesEdges.result.entity_entity_edges_count})}
|
||||||
</RbAlert>}
|
</RbAlert>}
|
||||||
</RbCard>
|
</RbCard>
|
||||||
{/* 去重消歧 */}
|
{/* Deduplication and Disambiguation */}
|
||||||
<RbCard
|
<RbCard
|
||||||
title={t(`memoryExtractionEngine.deduplication`)}
|
title={t(`memoryExtractionEngine.deduplication`)}
|
||||||
extra={formatTag(deduplication.status)}
|
extra={formatTag(deduplication.status)}
|
||||||
@@ -307,7 +333,7 @@ const Result: FC<ResultProps> = ({ loading, handleSave }) => {
|
|||||||
const keys = (resultObj as Record<string, string>)[key].split('.')
|
const keys = (resultObj as Record<string, string>)[key].split('.')
|
||||||
return (
|
return (
|
||||||
<div key={index}>
|
<div key={index}>
|
||||||
<div className="rb:text-[24px] rb:leading-[30px] rb:font-extrabold">{(testResult?.[keys[0] as keyof TestResult] as any)?.[keys[1]]}</div>
|
<div className="rb:text-[24px] rb:leading-7.5 rb:font-extrabold">{(testResult?.[keys[0] as keyof TestResult] as any)?.[keys[1]]}</div>
|
||||||
<div className="rb:text-[12px] rb:text-[#5B6167] rb:leading-4 rb:font-regular">{t(`memoryExtractionEngine.${key}`)}</div>
|
<div className="rb:text-[12px] rb:text-[#5B6167] rb:leading-4 rb:font-regular">{t(`memoryExtractionEngine.${key}`)}</div>
|
||||||
<div className="rb:mt-1 rb:text-[12px] rb:text-[#369F21] rb:leading-3.5 rb:font-regular">
|
<div className="rb:mt-1 rb:text-[12px] rb:text-[#369F21] rb:leading-3.5 rb:font-regular">
|
||||||
{}
|
{}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,3 +1,15 @@
|
|||||||
|
/*
|
||||||
|
* @Author: ZhaoYing
|
||||||
|
* @Date: 2026-02-03 17:30:02
|
||||||
|
* @Last Modified by: ZhaoYing
|
||||||
|
* @Last Modified time: 2026-02-03 17:30:02
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Memory Extraction Engine Configuration Page
|
||||||
|
* Configures entity deduplication, disambiguation, semantic anchoring, and pruning
|
||||||
|
* Supports real-time testing with example data
|
||||||
|
*/
|
||||||
|
|
||||||
import { type FC, useState, useEffect } from 'react'
|
import { type FC, useState, useEffect } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { useParams } from 'react-router-dom'
|
import { useParams } from 'react-router-dom'
|
||||||
@@ -14,12 +26,15 @@ import Result from './components/Result'
|
|||||||
import SwitchFormItem from '@/components/FormItem/SwitchFormItem'
|
import SwitchFormItem from '@/components/FormItem/SwitchFormItem'
|
||||||
import CustomSelect from '@/components/CustomSelect'
|
import CustomSelect from '@/components/CustomSelect'
|
||||||
|
|
||||||
|
/** Available configuration section keys */
|
||||||
const keys = [
|
const keys = [
|
||||||
// 'example',
|
|
||||||
'storageLayerModule',
|
'storageLayerModule',
|
||||||
'arrangementLayerModule'
|
'arrangementLayerModule'
|
||||||
]
|
]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configuration description component
|
||||||
|
*/
|
||||||
const ConfigDesc: FC<{ config: Variable, className?: string }> = ({config, className}) => {
|
const ConfigDesc: FC<{ config: Variable, className?: string }> = ({config, className}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
@@ -54,6 +69,7 @@ const MemoryExtractionEngine: FC = () => {
|
|||||||
}
|
}
|
||||||
}, [values])
|
}, [values])
|
||||||
|
|
||||||
|
/** Fetch configuration data */
|
||||||
const getConfig = () => {
|
const getConfig = () => {
|
||||||
if (!id) {
|
if (!id) {
|
||||||
return
|
return
|
||||||
@@ -79,11 +95,13 @@ const MemoryExtractionEngine: FC = () => {
|
|||||||
}
|
}
|
||||||
}, [id])
|
}, [id])
|
||||||
|
|
||||||
|
/** Toggle section expansion */
|
||||||
const handleExpand = (key: string) => {
|
const handleExpand = (key: string) => {
|
||||||
const newKeys = expandedKeys.includes(key) ? expandedKeys.filter(item => item !== key) : [...expandedKeys, key]
|
const newKeys = expandedKeys.includes(key) ? expandedKeys.filter(item => item !== key) : [...expandedKeys, key]
|
||||||
|
|
||||||
setExpandedKeys(newKeys)
|
setExpandedKeys(newKeys)
|
||||||
}
|
}
|
||||||
|
/** Save configuration */
|
||||||
const handleSave = () => {
|
const handleSave = () => {
|
||||||
if (!id) {
|
if (!id) {
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -1,3 +1,12 @@
|
|||||||
|
/*
|
||||||
|
* @Author: ZhaoYing
|
||||||
|
* @Date: 2026-02-03 17:29:55
|
||||||
|
* @Last Modified by: ZhaoYing
|
||||||
|
* @Last Modified time: 2026-02-03 17:29:55
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Memory Extraction Engine Configuration Form Types
|
||||||
|
*/
|
||||||
export interface ConfigForm {
|
export interface ConfigForm {
|
||||||
llm_id: string;
|
llm_id: string;
|
||||||
config_id?: number | string;
|
config_id?: number | string;
|
||||||
@@ -19,6 +28,9 @@ export interface ConfigForm {
|
|||||||
baseline: string;
|
baseline: string;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Configuration variable definition
|
||||||
|
*/
|
||||||
export interface Variable {
|
export interface Variable {
|
||||||
label: string;
|
label: string;
|
||||||
variableName: string;
|
variableName: string;
|
||||||
@@ -33,6 +45,9 @@ export interface Variable {
|
|||||||
max?: number;
|
max?: number;
|
||||||
step?: number;
|
step?: number;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Configuration section structure
|
||||||
|
*/
|
||||||
export interface ConfigVo {
|
export interface ConfigVo {
|
||||||
type: string;
|
type: string;
|
||||||
data: {
|
data: {
|
||||||
@@ -41,6 +56,9 @@ export interface ConfigVo {
|
|||||||
}[]
|
}[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test result data structure
|
||||||
|
*/
|
||||||
export interface TestResult {
|
export interface TestResult {
|
||||||
generated_at: string;
|
generated_at: string;
|
||||||
entities: Record<string, number>;
|
entities: Record<string, number>;
|
||||||
|
|||||||
@@ -1,3 +1,14 @@
|
|||||||
|
/*
|
||||||
|
* @Author: ZhaoYing
|
||||||
|
* @Date: 2026-02-03 17:33:22
|
||||||
|
* @Last Modified by: ZhaoYing
|
||||||
|
* @Last Modified time: 2026-02-03 17:33:22
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Memory Form Component
|
||||||
|
* Modal form for creating and editing memory configurations
|
||||||
|
*/
|
||||||
|
|
||||||
import { forwardRef, useImperativeHandle, useState } from 'react';
|
import { forwardRef, useImperativeHandle, useState } from 'react';
|
||||||
import { Form, Input, App } from 'antd';
|
import { Form, Input, App } from 'antd';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@@ -10,6 +21,9 @@ import CustomSelect from '@/components/CustomSelect';
|
|||||||
|
|
||||||
const FormItem = Form.Item;
|
const FormItem = Form.Item;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component props
|
||||||
|
*/
|
||||||
interface MemoryFormProps {
|
interface MemoryFormProps {
|
||||||
refresh: () => void;
|
refresh: () => void;
|
||||||
}
|
}
|
||||||
@@ -26,7 +40,7 @@ const MemoryForm = forwardRef<MemoryFormRef, MemoryFormProps>(({
|
|||||||
|
|
||||||
const values = Form.useWatch([], form);
|
const values = Form.useWatch([], form);
|
||||||
|
|
||||||
// 封装取消方法,添加关闭弹窗逻辑
|
/** Close modal and reset form */
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
setVisible(false);
|
setVisible(false);
|
||||||
setEditingMemory(null);
|
setEditingMemory(null);
|
||||||
@@ -34,10 +48,11 @@ const MemoryForm = forwardRef<MemoryFormRef, MemoryFormProps>(({
|
|||||||
setLoading(false);
|
setLoading(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** Open modal with optional data */
|
||||||
const handleOpen = (memory?: Memory | null) => {
|
const handleOpen = (memory?: Memory | null) => {
|
||||||
if (memory) {
|
if (memory) {
|
||||||
setEditingMemory(memory);
|
setEditingMemory(memory);
|
||||||
// 设置表单值
|
/** Set form values */
|
||||||
form.setFieldsValue({
|
form.setFieldsValue({
|
||||||
config_name: memory.config_name,
|
config_name: memory.config_name,
|
||||||
config_desc: memory.config_desc,
|
config_desc: memory.config_desc,
|
||||||
@@ -48,7 +63,7 @@ const MemoryForm = forwardRef<MemoryFormRef, MemoryFormProps>(({
|
|||||||
}
|
}
|
||||||
setVisible(true);
|
setVisible(true);
|
||||||
};
|
};
|
||||||
// 封装保存方法,添加提交逻辑
|
/** Save configuration */
|
||||||
const handleSave = () => {
|
const handleSave = () => {
|
||||||
form
|
form
|
||||||
.validateFields()
|
.validateFields()
|
||||||
@@ -73,7 +88,7 @@ const MemoryForm = forwardRef<MemoryFormRef, MemoryFormProps>(({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 暴露给父组件的方法
|
/** Expose methods to parent component */
|
||||||
useImperativeHandle(ref, () => ({
|
useImperativeHandle(ref, () => ({
|
||||||
handleOpen,
|
handleOpen,
|
||||||
handleClose
|
handleClose
|
||||||
|
|||||||
@@ -1,15 +1,27 @@
|
|||||||
|
/*
|
||||||
|
* @Author: ZhaoYing
|
||||||
|
* @Date: 2026-02-03 17:33:15
|
||||||
|
* @Last Modified by: ZhaoYing
|
||||||
|
* @Last Modified time: 2026-02-03 17:33:15
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Memory Management Page
|
||||||
|
* Manages memory configurations with extraction, forgetting, emotion, and reflection engines
|
||||||
|
* Displays configuration cards with navigation to engine settings
|
||||||
|
*/
|
||||||
|
|
||||||
import React, { useState, useEffect, useRef } from 'react';
|
import React, { useState, useEffect, useRef } from 'react';
|
||||||
import { List, Button, Space, App, Tooltip } from 'antd';
|
import { List, Button, Space, App, Tooltip } from 'antd';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import clsx from 'clsx'
|
||||||
|
|
||||||
import MemoryForm from './components/MemoryForm';
|
import MemoryForm from './components/MemoryForm';
|
||||||
import type { Memory, MemoryFormRef } from '@/views/MemoryManagement/types'
|
import type { Memory, MemoryFormRef } from '@/views/MemoryManagement/types'
|
||||||
import RbCard from '@/components/RbCard/Card'
|
import RbCard from '@/components/RbCard/Card'
|
||||||
// import StatusTag from '@/components/StatusTag'
|
|
||||||
import { getMemoryConfigList, deleteMemoryConfig } from '@/api/memory'
|
import { getMemoryConfigList, deleteMemoryConfig } from '@/api/memory'
|
||||||
import BodyWrapper from '@/components/Empty/BodyWrapper'
|
import BodyWrapper from '@/components/Empty/BodyWrapper'
|
||||||
import { formatDateTime } from '@/utils/format';
|
import { formatDateTime } from '@/utils/format';
|
||||||
import clsx from 'clsx'
|
|
||||||
import RbAlert from '@/components/RbAlert'
|
import RbAlert from '@/components/RbAlert'
|
||||||
|
|
||||||
const MemoryManagement: React.FC = () => {
|
const MemoryManagement: React.FC = () => {
|
||||||
@@ -25,6 +37,7 @@ const MemoryManagement: React.FC = () => {
|
|||||||
loadMoreData()
|
loadMoreData()
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
/** Load configuration list */
|
||||||
const loadMoreData = () => {
|
const loadMoreData = () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
getMemoryConfigList()
|
getMemoryConfigList()
|
||||||
@@ -41,10 +54,11 @@ const MemoryManagement: React.FC = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// 打开新增标签弹窗
|
/** Open create/edit modal */
|
||||||
const handleEdit = (config?: Memory) => {
|
const handleEdit = (config?: Memory) => {
|
||||||
memoryFormRef.current?.handleOpen(config);
|
memoryFormRef.current?.handleOpen(config);
|
||||||
}
|
}
|
||||||
|
/** Delete configuration */
|
||||||
const handleDelete = (item: Memory) => {
|
const handleDelete = (item: Memory) => {
|
||||||
modal.confirm({
|
modal.confirm({
|
||||||
title: t('common.confirmDeleteDesc', { name: item.config_name }),
|
title: t('common.confirmDeleteDesc', { name: item.config_name }),
|
||||||
@@ -61,6 +75,7 @@ const MemoryManagement: React.FC = () => {
|
|||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** Navigate to engine configuration page */
|
||||||
const handleClick = (id: number, type: string) => {
|
const handleClick = (id: number, type: string) => {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'memoryExtractionEngine':
|
case 'memoryExtractionEngine':
|
||||||
|
|||||||
@@ -1,4 +1,12 @@
|
|||||||
// 内存管理表单数据类型
|
/*
|
||||||
|
* @Author: ZhaoYing
|
||||||
|
* @Date: 2026-02-03 17:33:01
|
||||||
|
* @Last Modified by: ZhaoYing
|
||||||
|
* @Last Modified time: 2026-02-03 17:33:24
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Memory management form data type
|
||||||
|
*/
|
||||||
export interface MemoryFormData {
|
export interface MemoryFormData {
|
||||||
config_id?: number;
|
config_id?: number;
|
||||||
config_name: string;
|
config_name: string;
|
||||||
@@ -6,7 +14,9 @@ export interface MemoryFormData {
|
|||||||
scene_id?: string;
|
scene_id?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 内存数据类型
|
/**
|
||||||
|
* Memory configuration data type
|
||||||
|
*/
|
||||||
export interface Memory {
|
export interface Memory {
|
||||||
config_id: number;
|
config_id: number;
|
||||||
config_name: string;
|
config_name: string;
|
||||||
@@ -34,7 +44,9 @@ export interface Memory {
|
|||||||
scene_name: string;
|
scene_name: string;
|
||||||
[key: string]: string | number | boolean;
|
[key: string]: string | number | boolean;
|
||||||
}
|
}
|
||||||
// 定义组件暴露的方法接口
|
/**
|
||||||
|
* Component exposed methods interface
|
||||||
|
*/
|
||||||
export interface MemoryFormRef {
|
export interface MemoryFormRef {
|
||||||
handleOpen: (memory?: Memory | null) => void;
|
handleOpen: (memory?: Memory | null) => void;
|
||||||
}
|
}
|
||||||
@@ -1,3 +1,15 @@
|
|||||||
|
/*
|
||||||
|
* @Author: ZhaoYing
|
||||||
|
* @Date: 2026-02-03 16:50:00
|
||||||
|
* @Last Modified by: ZhaoYing
|
||||||
|
* @Last Modified time: 2026-02-03 16:50:00
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Group Model View
|
||||||
|
* Displays composite/group models in card grid layout
|
||||||
|
* Supports filtering and configuration
|
||||||
|
*/
|
||||||
|
|
||||||
import { useState, useEffect, forwardRef, useImperativeHandle } from 'react';
|
import { useState, useEffect, forwardRef, useImperativeHandle } from 'react';
|
||||||
import clsx from 'clsx'
|
import clsx from 'clsx'
|
||||||
import { Button } from 'antd'
|
import { Button } from 'antd'
|
||||||
@@ -9,12 +21,16 @@ import { getModelNewList } from '@/api/models'
|
|||||||
import PageEmpty from '@/components/Empty/PageEmpty';
|
import PageEmpty from '@/components/Empty/PageEmpty';
|
||||||
import { formatDateTime } from '@/utils/format';
|
import { formatDateTime } from '@/utils/format';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Group model list component
|
||||||
|
*/
|
||||||
const Group = forwardRef <BaseRef,{ query: any; handleEdit: (data: ModelListItem) => void; }>(({ query, handleEdit }, ref) => {
|
const Group = forwardRef <BaseRef,{ query: any; handleEdit: (data: ModelListItem) => void; }>(({ query, handleEdit }, ref) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [list, setList] = useState<ModelListItem[]>([])
|
const [list, setList] = useState<ModelListItem[]>([])
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getList()
|
getList()
|
||||||
}, [query])
|
}, [query])
|
||||||
|
/** Fetch group model list */
|
||||||
const getList = () => {
|
const getList = () => {
|
||||||
getModelNewList({
|
getModelNewList({
|
||||||
...query,
|
...query,
|
||||||
@@ -26,6 +42,7 @@ const Group = forwardRef <BaseRef,{ query: any; handleEdit: (data: ModelListItem
|
|||||||
setList(response[0]?.models || [])
|
setList(response[0]?.models || [])
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
/** Format model data for display */
|
||||||
const formatData = (data: ModelListItem) => {
|
const formatData = (data: ModelListItem) => {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
@@ -46,6 +63,7 @@ const Group = forwardRef <BaseRef,{ query: any; handleEdit: (data: ModelListItem
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Expose methods to parent component */
|
||||||
useImperativeHandle(ref, () => ({
|
useImperativeHandle(ref, () => ({
|
||||||
getList,
|
getList,
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -1,3 +1,15 @@
|
|||||||
|
/*
|
||||||
|
* @Author: ZhaoYing
|
||||||
|
* @Date: 2026-02-03 16:50:10
|
||||||
|
* @Last Modified by: ZhaoYing
|
||||||
|
* @Last Modified time: 2026-02-03 16:50:10
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Model List View
|
||||||
|
* Displays models grouped by provider with key configuration
|
||||||
|
* Shows model tags and allows viewing model details
|
||||||
|
*/
|
||||||
|
|
||||||
import { useRef, useState, useEffect, type FC } from 'react';
|
import { useRef, useState, useEffect, type FC } from 'react';
|
||||||
import { Button, Flex, Row, Col } from 'antd'
|
import { Button, Flex, Row, Col } from 'antd'
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@@ -11,6 +23,9 @@ import KeyConfigModal from './components/KeyConfigModal'
|
|||||||
import ModelListDetail from './components/ModelListDetail'
|
import ModelListDetail from './components/ModelListDetail'
|
||||||
import { getLogoUrl } from './utils'
|
import { getLogoUrl } from './utils'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Model list component
|
||||||
|
*/
|
||||||
const ModelList: FC<{ query: any }> = ({ query }) => {
|
const ModelList: FC<{ query: any }> = ({ query }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const keyConfigModalRef = useRef<KeyConfigModalRef>(null)
|
const keyConfigModalRef = useRef<KeyConfigModalRef>(null)
|
||||||
@@ -19,6 +34,7 @@ const ModelList: FC<{ query: any }> = ({ query }) => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getList()
|
getList()
|
||||||
}, [query])
|
}, [query])
|
||||||
|
/** Fetch model list grouped by provider */
|
||||||
const getList = () => {
|
const getList = () => {
|
||||||
getModelNewList({
|
getModelNewList({
|
||||||
...query,
|
...query,
|
||||||
@@ -29,9 +45,11 @@ const ModelList: FC<{ query: any }> = ({ query }) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Open model detail drawer */
|
||||||
const handleShowModel = (vo: ProviderModelItem) => {
|
const handleShowModel = (vo: ProviderModelItem) => {
|
||||||
modelListDetailRef.current?.handleOpen(vo)
|
modelListDetailRef.current?.handleOpen(vo)
|
||||||
}
|
}
|
||||||
|
/** Open key configuration modal */
|
||||||
const handleKeyConfig = (vo: ProviderModelItem) => {
|
const handleKeyConfig = (vo: ProviderModelItem) => {
|
||||||
keyConfigModalRef.current?.handleOpen(vo)
|
keyConfigModalRef.current?.handleOpen(vo)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,15 @@
|
|||||||
|
/*
|
||||||
|
* @Author: ZhaoYing
|
||||||
|
* @Date: 2026-02-03 16:50:14
|
||||||
|
* @Last Modified by: ZhaoYing
|
||||||
|
* @Last Modified time: 2026-02-03 16:50:14
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Model Square View
|
||||||
|
* Displays public model marketplace grouped by provider
|
||||||
|
* Allows adding models and viewing details
|
||||||
|
*/
|
||||||
|
|
||||||
import { useRef, useState, useEffect, forwardRef, useImperativeHandle } from 'react';
|
import { useRef, useState, useEffect, forwardRef, useImperativeHandle } from 'react';
|
||||||
import { Button, Space, App, Divider, Flex, Tooltip } from 'antd'
|
import { Button, Space, App, Divider, Flex, Tooltip } from 'antd'
|
||||||
import { UsergroupAddOutlined } from '@ant-design/icons';
|
import { UsergroupAddOutlined } from '@ant-design/icons';
|
||||||
@@ -11,6 +23,9 @@ import Tag from '@/components/Tag';
|
|||||||
import ModelSquareDetail from './components/ModelSquareDetail'
|
import ModelSquareDetail from './components/ModelSquareDetail'
|
||||||
import { getLogoUrl } from './utils'
|
import { getLogoUrl } from './utils'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Model square component
|
||||||
|
*/
|
||||||
const ModelSquare = forwardRef <BaseRef, { query: any; handleEdit: (vo?: ModelPlazaItem) => void; }>(({ query, handleEdit }, ref) => {
|
const ModelSquare = forwardRef <BaseRef, { query: any; handleEdit: (vo?: ModelPlazaItem) => void; }>(({ query, handleEdit }, ref) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { message } = App.useApp()
|
const { message } = App.useApp()
|
||||||
@@ -19,6 +34,7 @@ const ModelSquare = forwardRef <BaseRef, { query: any; handleEdit: (vo?: ModelPl
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getList()
|
getList()
|
||||||
}, [query])
|
}, [query])
|
||||||
|
/** Fetch model plaza list */
|
||||||
const getList = () => {
|
const getList = () => {
|
||||||
getModelPlaza(query)
|
getModelPlaza(query)
|
||||||
.then(res => {
|
.then(res => {
|
||||||
@@ -26,9 +42,11 @@ const ModelSquare = forwardRef <BaseRef, { query: any; handleEdit: (vo?: ModelPl
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Open model detail drawer */
|
||||||
const handleMore = (vo: ModelPlaza) => {
|
const handleMore = (vo: ModelPlaza) => {
|
||||||
modelSquareDetailRef.current?.handleOpen(vo)
|
modelSquareDetailRef.current?.handleOpen(vo)
|
||||||
}
|
}
|
||||||
|
/** Add model to workspace */
|
||||||
const handleAdd = (item: ModelPlazaItem) => {
|
const handleAdd = (item: ModelPlazaItem) => {
|
||||||
addModelPlaza(item.id)
|
addModelPlaza(item.id)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
@@ -37,6 +55,7 @@ const ModelSquare = forwardRef <BaseRef, { query: any; handleEdit: (vo?: ModelPl
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Expose methods to parent component */
|
||||||
useImperativeHandle(ref, () => ({
|
useImperativeHandle(ref, () => ({
|
||||||
getList,
|
getList,
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -1,3 +1,15 @@
|
|||||||
|
/*
|
||||||
|
* @Author: ZhaoYing
|
||||||
|
* @Date: 2026-02-03 16:49:28
|
||||||
|
* @Last Modified by: ZhaoYing
|
||||||
|
* @Last Modified time: 2026-02-03 16:49:28
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Custom Model Modal
|
||||||
|
* Modal for creating and editing custom models in the model square
|
||||||
|
* Supports logo upload, type/provider selection, and tagging
|
||||||
|
*/
|
||||||
|
|
||||||
import { forwardRef, useImperativeHandle, useState } from 'react';
|
import { forwardRef, useImperativeHandle, useState } from 'react';
|
||||||
import { Form, Input, App, Select } from 'antd';
|
import { Form, Input, App, Select } from 'antd';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@@ -9,6 +21,9 @@ import UploadImages from '@/components/Upload/UploadImages'
|
|||||||
import { updateCustomModel, addCustomModel, modelTypeUrl, modelProviderUrl } from '@/api/models'
|
import { updateCustomModel, addCustomModel, modelTypeUrl, modelProviderUrl } from '@/api/models'
|
||||||
import { getFileLink } from '@/api/fileStorage'
|
import { getFileLink } from '@/api/fileStorage'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom model modal component
|
||||||
|
*/
|
||||||
const CustomModelModal = forwardRef<CustomModelModalRef, CustomModelModalProps>(({
|
const CustomModelModal = forwardRef<CustomModelModalRef, CustomModelModalProps>(({
|
||||||
refresh
|
refresh
|
||||||
}, ref) => {
|
}, ref) => {
|
||||||
@@ -21,6 +36,7 @@ const CustomModelModal = forwardRef<CustomModelModalRef, CustomModelModalProps>(
|
|||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false)
|
||||||
const formValues = Form.useWatch([], form)
|
const formValues = Form.useWatch([], form)
|
||||||
|
|
||||||
|
/** Close modal and reset state */
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
setModel({} as ModelPlazaItem);
|
setModel({} as ModelPlazaItem);
|
||||||
form.resetFields();
|
form.resetFields();
|
||||||
@@ -28,6 +44,7 @@ const CustomModelModal = forwardRef<CustomModelModalRef, CustomModelModalProps>(
|
|||||||
setVisible(false);
|
setVisible(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** Open modal with optional model data for editing */
|
||||||
const handleOpen = (model?: ModelPlazaItem) => {
|
const handleOpen = (model?: ModelPlazaItem) => {
|
||||||
if (model) {
|
if (model) {
|
||||||
setIsEdit(true);
|
setIsEdit(true);
|
||||||
@@ -42,6 +59,7 @@ const CustomModelModal = forwardRef<CustomModelModalRef, CustomModelModalProps>(
|
|||||||
}
|
}
|
||||||
setVisible(true);
|
setVisible(true);
|
||||||
};
|
};
|
||||||
|
/** Update or create custom model */
|
||||||
const handleUpdate = (data: CustomModelForm) => {
|
const handleUpdate = (data: CustomModelForm) => {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
const { type, provider, ...rest} = data
|
const { type, provider, ...rest} = data
|
||||||
@@ -56,6 +74,7 @@ const CustomModelModal = forwardRef<CustomModelModalRef, CustomModelModalProps>(
|
|||||||
setLoading(false)
|
setLoading(false)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
/** Validate and save custom model */
|
||||||
const handleSave = () => {
|
const handleSave = () => {
|
||||||
form
|
form
|
||||||
.validateFields()
|
.validateFields()
|
||||||
@@ -87,6 +106,7 @@ const CustomModelModal = forwardRef<CustomModelModalRef, CustomModelModalProps>(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Expose methods to parent component */
|
||||||
useImperativeHandle(ref, () => ({
|
useImperativeHandle(ref, () => ({
|
||||||
handleOpen,
|
handleOpen,
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -1,3 +1,15 @@
|
|||||||
|
/*
|
||||||
|
* @Author: ZhaoYing
|
||||||
|
* @Date: 2026-02-03 16:49:33
|
||||||
|
* @Last Modified by: ZhaoYing
|
||||||
|
* @Last Modified time: 2026-02-03 16:49:33
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Group Model Modal
|
||||||
|
* Modal for creating and editing composite/group models
|
||||||
|
* Supports multiple API key configuration and load balancing
|
||||||
|
*/
|
||||||
|
|
||||||
import { forwardRef, useImperativeHandle, useState } from 'react';
|
import { forwardRef, useImperativeHandle, useState } from 'react';
|
||||||
import { Form, Input, App, Select } from 'antd';
|
import { Form, Input, App, Select } from 'antd';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@@ -10,6 +22,9 @@ import UploadImages from '@/components/Upload/UploadImages'
|
|||||||
import ModelImplement from './ModelImplement'
|
import ModelImplement from './ModelImplement'
|
||||||
import { getFileLink } from '@/api/fileStorage'
|
import { getFileLink } from '@/api/fileStorage'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Group model modal component
|
||||||
|
*/
|
||||||
const GroupModelModal = forwardRef<GroupModelModalRef, GroupModelModalProps>(({
|
const GroupModelModal = forwardRef<GroupModelModalRef, GroupModelModalProps>(({
|
||||||
refresh
|
refresh
|
||||||
}, ref) => {
|
}, ref) => {
|
||||||
@@ -22,6 +37,7 @@ const GroupModelModal = forwardRef<GroupModelModalRef, GroupModelModalProps>(({
|
|||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false)
|
||||||
const type = Form.useWatch(['type'], form)
|
const type = Form.useWatch(['type'], form)
|
||||||
|
|
||||||
|
/** Close modal and reset state */
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
setModel({} as ModelListItem);
|
setModel({} as ModelListItem);
|
||||||
form.resetFields();
|
form.resetFields();
|
||||||
@@ -29,6 +45,7 @@ const GroupModelModal = forwardRef<GroupModelModalRef, GroupModelModalProps>(({
|
|||||||
setVisible(false);
|
setVisible(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** Open modal with optional model data for editing */
|
||||||
const handleOpen = (model?: ModelListItem) => {
|
const handleOpen = (model?: ModelListItem) => {
|
||||||
if (model) {
|
if (model) {
|
||||||
setIsEdit(true);
|
setIsEdit(true);
|
||||||
@@ -44,6 +61,7 @@ const GroupModelModal = forwardRef<GroupModelModalRef, GroupModelModalProps>(({
|
|||||||
}
|
}
|
||||||
setVisible(true);
|
setVisible(true);
|
||||||
};
|
};
|
||||||
|
/** Validate and save group model */
|
||||||
const handleSave = () => {
|
const handleSave = () => {
|
||||||
form
|
form
|
||||||
.validateFields()
|
.validateFields()
|
||||||
@@ -73,6 +91,7 @@ const GroupModelModal = forwardRef<GroupModelModalRef, GroupModelModalProps>(({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Update or create group model */
|
||||||
const handleUpdate = (data: CompositeModelForm) => {
|
const handleUpdate = (data: CompositeModelForm) => {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
const { type, ...rest } = data
|
const { type, ...rest } = data
|
||||||
@@ -90,6 +109,7 @@ const GroupModelModal = forwardRef<GroupModelModalRef, GroupModelModalProps>(({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Expose methods to parent component */
|
||||||
useImperativeHandle(ref, () => ({
|
useImperativeHandle(ref, () => ({
|
||||||
handleOpen,
|
handleOpen,
|
||||||
handleClose
|
handleClose
|
||||||
|
|||||||
@@ -1,10 +1,26 @@
|
|||||||
|
/*
|
||||||
|
* @Author: ZhaoYing
|
||||||
|
* @Date: 2026-02-03 16:49:40
|
||||||
|
* @Last Modified by: ZhaoYing
|
||||||
|
* @Last Modified time: 2026-02-03 16:49:40
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Key Configuration Modal
|
||||||
|
* Modal for configuring API keys for model providers
|
||||||
|
* Allows setting API key and base URL
|
||||||
|
*/
|
||||||
|
|
||||||
import { forwardRef, useImperativeHandle, useState } from 'react';
|
import { forwardRef, useImperativeHandle, useState } from 'react';
|
||||||
import { Form, Input, App } from 'antd';
|
import { Form, Input, App } from 'antd';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import type { KeyConfigModalForm, ProviderModelItem, KeyConfigModalRef, KeyConfigModalProps } from '../types';
|
import type { KeyConfigModalForm, ProviderModelItem, KeyConfigModalRef, KeyConfigModalProps } from '../types';
|
||||||
import RbModal from '@/components/RbModal'
|
import RbModal from '@/components/RbModal'
|
||||||
import { updateProviderApiKeys } from '@/api/models'
|
import { updateProviderApiKeys } from '@/api/models'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Key configuration modal component
|
||||||
|
*/
|
||||||
const KeyConfigModal = forwardRef<KeyConfigModalRef, KeyConfigModalProps>(({
|
const KeyConfigModal = forwardRef<KeyConfigModalRef, KeyConfigModalProps>(({
|
||||||
refresh
|
refresh
|
||||||
}, ref) => {
|
}, ref) => {
|
||||||
@@ -15,6 +31,7 @@ const KeyConfigModal = forwardRef<KeyConfigModalRef, KeyConfigModalProps>(({
|
|||||||
const [form] = Form.useForm<KeyConfigModalForm>();
|
const [form] = Form.useForm<KeyConfigModalForm>();
|
||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false)
|
||||||
|
|
||||||
|
/** Close modal and reset state */
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
setModel({} as ProviderModelItem);
|
setModel({} as ProviderModelItem);
|
||||||
form.resetFields();
|
form.resetFields();
|
||||||
@@ -22,10 +39,12 @@ const KeyConfigModal = forwardRef<KeyConfigModalRef, KeyConfigModalProps>(({
|
|||||||
setVisible(false);
|
setVisible(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** Open modal with provider model data */
|
||||||
const handleOpen = (vo: ProviderModelItem) => {
|
const handleOpen = (vo: ProviderModelItem) => {
|
||||||
setVisible(true);
|
setVisible(true);
|
||||||
setModel(vo);
|
setModel(vo);
|
||||||
};
|
};
|
||||||
|
/** Save API key configuration */
|
||||||
const handleSave = () => {
|
const handleSave = () => {
|
||||||
form
|
form
|
||||||
.validateFields()
|
.validateFields()
|
||||||
@@ -51,6 +70,7 @@ const KeyConfigModal = forwardRef<KeyConfigModalRef, KeyConfigModalProps>(({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Expose methods to parent component */
|
||||||
useImperativeHandle(ref, () => ({
|
useImperativeHandle(ref, () => ({
|
||||||
handleOpen,
|
handleOpen,
|
||||||
handleClose
|
handleClose
|
||||||
|
|||||||
@@ -1,3 +1,15 @@
|
|||||||
|
/*
|
||||||
|
* @Author: ZhaoYing
|
||||||
|
* @Date: 2026-02-03 16:49:20
|
||||||
|
* @Last Modified by: ZhaoYing
|
||||||
|
* @Last Modified time: 2026-02-03 16:54:54
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Sub-Model Modal
|
||||||
|
* Modal for selecting models and API keys to add to group model
|
||||||
|
* Uses cascader for hierarchical selection
|
||||||
|
*/
|
||||||
|
|
||||||
import { forwardRef, useImperativeHandle, useState, useEffect } from 'react';
|
import { forwardRef, useImperativeHandle, useState, useEffect } from 'react';
|
||||||
import { Form, Cascader, App, type CascaderProps } from 'antd';
|
import { Form, Cascader, App, type CascaderProps } from 'antd';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@@ -10,12 +22,19 @@ import type { ProviderModelItem } from '../../types'
|
|||||||
|
|
||||||
const { SHOW_CHILD } = Cascader;
|
const { SHOW_CHILD } = Cascader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cascader option interface
|
||||||
|
*/
|
||||||
interface Option {
|
interface Option {
|
||||||
value: string | number;
|
value: string | number;
|
||||||
label: string;
|
label: string;
|
||||||
children?: Option[];
|
children?: Option[];
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sub-model modal component
|
||||||
|
*/
|
||||||
const SubModelModal = forwardRef<SubModelModalRef, SubModelModalProps>(({
|
const SubModelModal = forwardRef<SubModelModalRef, SubModelModalProps>(({
|
||||||
refresh,
|
refresh,
|
||||||
type,
|
type,
|
||||||
@@ -38,7 +57,7 @@ const SubModelModal = forwardRef<SubModelModalRef, SubModelModalProps>(({
|
|||||||
}
|
}
|
||||||
}, [groupedByProvider, provider])
|
}, [groupedByProvider, provider])
|
||||||
|
|
||||||
// 封装取消方法,添加关闭弹窗逻辑
|
/** Close modal and reset state */
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
form.resetFields();
|
form.resetFields();
|
||||||
setVisible(false);
|
setVisible(false);
|
||||||
@@ -46,11 +65,12 @@ const SubModelModal = forwardRef<SubModelModalRef, SubModelModalProps>(({
|
|||||||
setModelList([])
|
setModelList([])
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** Open modal */
|
||||||
const handleOpen = () => {
|
const handleOpen = () => {
|
||||||
form.resetFields()
|
form.resetFields()
|
||||||
setVisible(true);
|
setVisible(true);
|
||||||
};
|
};
|
||||||
// 封装保存方法,添加提交逻辑
|
/** Save selected models and API keys */
|
||||||
const handleSave = () => {
|
const handleSave = () => {
|
||||||
form
|
form
|
||||||
.validateFields()
|
.validateFields()
|
||||||
@@ -65,6 +85,7 @@ const SubModelModal = forwardRef<SubModelModalRef, SubModelModalProps>(({
|
|||||||
handleClose()
|
handleClose()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
/** Handle cascader selection change */
|
||||||
const handleChange = (value: (string | number)[][], selectedOptions: Option[][]) => {
|
const handleChange = (value: (string | number)[][], selectedOptions: Option[][]) => {
|
||||||
const filterList = selectedOptions.filter(vo => vo.length === 1).map(item => item[0])
|
const filterList = selectedOptions.filter(vo => vo.length === 1).map(item => item[0])
|
||||||
const lastFilterLit = value.filter(vo => vo.length !== 1)
|
const lastFilterLit = value.filter(vo => vo.length !== 1)
|
||||||
@@ -75,6 +96,7 @@ const SubModelModal = forwardRef<SubModelModalRef, SubModelModalProps>(({
|
|||||||
setSelecteds(selectedOptions)
|
setSelecteds(selectedOptions)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Handle provider change and load models */
|
||||||
const handleChangeProvider = (provider: string, api_key_ids?: any[]) => {
|
const handleChangeProvider = (provider: string, api_key_ids?: any[]) => {
|
||||||
form.setFieldValue('api_key_ids', undefined)
|
form.setFieldValue('api_key_ids', undefined)
|
||||||
if (provider) {
|
if (provider) {
|
||||||
@@ -110,6 +132,7 @@ const SubModelModal = forwardRef<SubModelModalRef, SubModelModalProps>(({
|
|||||||
setModelList([])
|
setModelList([])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/** Custom display renderer for cascader */
|
||||||
const displayRender: CascaderProps<Option>['displayRender'] = (labels, selectedOptions = []) =>
|
const displayRender: CascaderProps<Option>['displayRender'] = (labels, selectedOptions = []) =>
|
||||||
labels.map((label, i) => {
|
labels.map((label, i) => {
|
||||||
const option = selectedOptions[i];
|
const option = selectedOptions[i];
|
||||||
@@ -123,7 +146,7 @@ const SubModelModal = forwardRef<SubModelModalRef, SubModelModalProps>(({
|
|||||||
return <span key={option?.value || i}>{label} / </span>;
|
return <span key={option?.value || i}>{label} / </span>;
|
||||||
});
|
});
|
||||||
|
|
||||||
// 暴露给父组件的方法
|
/** Expose methods to parent component */
|
||||||
useImperativeHandle(ref, () => ({
|
useImperativeHandle(ref, () => ({
|
||||||
handleOpen,
|
handleOpen,
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -1,3 +1,15 @@
|
|||||||
|
/*
|
||||||
|
* @Author: ZhaoYing
|
||||||
|
* @Date: 2026-02-03 16:49:12
|
||||||
|
* @Last Modified by: ZhaoYing
|
||||||
|
* @Last Modified time: 2026-02-03 16:49:12
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Model Implementation Component
|
||||||
|
* Manages model implementations with API keys for group models
|
||||||
|
* Allows adding and removing model-API key associations
|
||||||
|
*/
|
||||||
|
|
||||||
import { type FC, useRef } from "react";
|
import { type FC, useRef } from "react";
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Flex, Button, Space, App } from 'antd'
|
import { Flex, Button, Space, App } from 'antd'
|
||||||
@@ -7,16 +19,27 @@ import SubModelModal from './SubModelModal'
|
|||||||
import Empty from '@/components/Empty'
|
import Empty from '@/components/Empty'
|
||||||
import Tag from '@/components/Tag'
|
import Tag from '@/components/Tag'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component props
|
||||||
|
*/
|
||||||
interface ModelImplementProps {
|
interface ModelImplementProps {
|
||||||
|
/** Model type */
|
||||||
type?: string;
|
type?: string;
|
||||||
|
/** Current model list value */
|
||||||
value?: any;
|
value?: any;
|
||||||
|
/** Callback when value changes */
|
||||||
onChange?: (value: any) => void;
|
onChange?: (value: any) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Model implementation management component
|
||||||
|
*/
|
||||||
const ModelImplement: FC<ModelImplementProps> = ({ type, value, onChange }) => {
|
const ModelImplement: FC<ModelImplementProps> = ({ type, value, onChange }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { modal, message } = App.useApp();
|
const { modal, message } = App.useApp();
|
||||||
const subModelModalRef = useRef<SubModelModalRef>(null)
|
const subModelModalRef = useRef<SubModelModalRef>(null)
|
||||||
|
|
||||||
|
/** Open add implementation modal */
|
||||||
const handleAdd = () => {
|
const handleAdd = () => {
|
||||||
if (!type || type.trim() === '') {
|
if (!type || type.trim() === '') {
|
||||||
message.warning(t('common.selectPlaceholder', { title: t('modelNew.type') }))
|
message.warning(t('common.selectPlaceholder', { title: t('modelNew.type') }))
|
||||||
@@ -24,6 +47,7 @@ const ModelImplement: FC<ModelImplementProps> = ({ type, value, onChange }) => {
|
|||||||
}
|
}
|
||||||
subModelModalRef.current?.handleOpen()
|
subModelModalRef.current?.handleOpen()
|
||||||
}
|
}
|
||||||
|
/** Delete model implementation */
|
||||||
const handleDelete = (vo: any) => {
|
const handleDelete = (vo: any) => {
|
||||||
modal.confirm({
|
modal.confirm({
|
||||||
title: t('common.confirmDeleteDesc', { name: [vo.model_name, vo.api_key].join(' / ') }),
|
title: t('common.confirmDeleteDesc', { name: [vo.model_name, vo.api_key].join(' / ') }),
|
||||||
@@ -35,6 +59,7 @@ const ModelImplement: FC<ModelImplementProps> = ({ type, value, onChange }) => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
/** Refresh model list after adding implementations */
|
||||||
const handleRefresh = (list: ModelList[]) => {
|
const handleRefresh = (list: ModelList[]) => {
|
||||||
const existingModels = value || [];
|
const existingModels = value || [];
|
||||||
let updatedModels = [...existingModels];
|
let updatedModels = [...existingModels];
|
||||||
@@ -47,6 +72,7 @@ const ModelImplement: FC<ModelImplementProps> = ({ type, value, onChange }) => {
|
|||||||
onChange?.([...updatedModels]);
|
onChange?.([...updatedModels]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Group models by provider */
|
||||||
const groupedByProvider: Record<string, ModelList[]> = (value || []).reduce((acc: Record<string, ModelList[]>, item: ModelList) => {
|
const groupedByProvider: Record<string, ModelList[]> = (value || []).reduce((acc: Record<string, ModelList[]>, item: ModelList) => {
|
||||||
const provider = item.provider || 'unknown';
|
const provider = item.provider || 'unknown';
|
||||||
if (!acc[provider]) acc[provider] = [];
|
if (!acc[provider]) acc[provider] = [];
|
||||||
|
|||||||
@@ -1,17 +1,49 @@
|
|||||||
|
/*
|
||||||
|
* @Author: ZhaoYing
|
||||||
|
* @Date: 2026-02-03 16:49:24
|
||||||
|
* @Last Modified by: ZhaoYing
|
||||||
|
* @Last Modified time: 2026-02-03 16:49:24
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Type definitions for Model Implementation
|
||||||
|
*/
|
||||||
|
|
||||||
import type { ModelListItem } from '../../types'
|
import type { ModelListItem } from '../../types'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Model list item with API key ID
|
||||||
|
*/
|
||||||
export interface ModelList extends ModelListItem {
|
export interface ModelList extends ModelListItem {
|
||||||
|
/** Associated API key ID */
|
||||||
api_key_id: string;
|
api_key_id: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sub-model modal form data
|
||||||
|
*/
|
||||||
export interface SubModelModalForm {
|
export interface SubModelModalForm {
|
||||||
|
/** Model provider */
|
||||||
provider: string;
|
provider: string;
|
||||||
|
/** Selected API key IDs (nested array for cascader) */
|
||||||
api_key_ids: string[][];
|
api_key_ids: string[][];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sub-model modal ref interface
|
||||||
|
*/
|
||||||
export interface SubModelModalRef {
|
export interface SubModelModalRef {
|
||||||
|
/** Open modal */
|
||||||
handleOpen: () => void;
|
handleOpen: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sub-model modal props
|
||||||
|
*/
|
||||||
export interface SubModelModalProps {
|
export interface SubModelModalProps {
|
||||||
|
/** Model type filter */
|
||||||
type?: string;
|
type?: string;
|
||||||
|
/** Callback to update model list */
|
||||||
refresh?: (vo: ModelList[]) => void;
|
refresh?: (vo: ModelList[]) => void;
|
||||||
|
/** Existing models grouped by provider */
|
||||||
groupedByProvider?: Record<string, ModelList[]>
|
groupedByProvider?: Record<string, ModelList[]>
|
||||||
}
|
}
|
||||||
@@ -1,3 +1,15 @@
|
|||||||
|
/*
|
||||||
|
* @Author: ZhaoYing
|
||||||
|
* @Date: 2026-02-03 16:49:45
|
||||||
|
* @Last Modified by: ZhaoYing
|
||||||
|
* @Last Modified time: 2026-02-03 16:49:45
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Model List Detail Drawer
|
||||||
|
* Displays detailed list of models from a specific provider
|
||||||
|
* Allows filtering by type and configuring API keys
|
||||||
|
*/
|
||||||
|
|
||||||
import { useState, useImperativeHandle, forwardRef, useRef, useMemo } from 'react';
|
import { useState, useImperativeHandle, forwardRef, useRef, useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Button, Switch, Row, Col, Space, Tooltip } from 'antd'
|
import { Button, Switch, Row, Col, Space, Tooltip } from 'antd'
|
||||||
@@ -12,10 +24,17 @@ import { getModelNewList, updateModelStatus, modelTypeUrl } from '@/api/models'
|
|||||||
import { getLogoUrl } from '../utils'
|
import { getLogoUrl } from '../utils'
|
||||||
import CustomSelect from '@/components/CustomSelect'
|
import CustomSelect from '@/components/CustomSelect'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component props
|
||||||
|
*/
|
||||||
interface ModelListDetailProps {
|
interface ModelListDetailProps {
|
||||||
|
/** Callback to refresh parent list */
|
||||||
refresh?: () => void;
|
refresh?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Model list detail drawer component
|
||||||
|
*/
|
||||||
const ModelListDetail = forwardRef<ModelListDetailRef, ModelListDetailProps>(({ refresh }, ref) => {
|
const ModelListDetail = forwardRef<ModelListDetailRef, ModelListDetailProps>(({ refresh }, ref) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
@@ -25,12 +44,14 @@ const ModelListDetail = forwardRef<ModelListDetailRef, ModelListDetailProps>(({
|
|||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false)
|
||||||
const [type, setType] = useState<string | undefined | null>(null)
|
const [type, setType] = useState<string | undefined | null>(null)
|
||||||
|
|
||||||
|
/** Open drawer with provider model data */
|
||||||
const handleOpen = (vo: ProviderModelItem) => {
|
const handleOpen = (vo: ProviderModelItem) => {
|
||||||
setType(null)
|
setType(null)
|
||||||
setOpen(true)
|
setOpen(true)
|
||||||
getData(vo)
|
getData(vo)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Fetch model data for provider */
|
||||||
const getData = (vo: ProviderModelItem) => {
|
const getData = (vo: ProviderModelItem) => {
|
||||||
if (!vo.provider) return
|
if (!vo.provider) return
|
||||||
|
|
||||||
@@ -43,9 +64,11 @@ const ModelListDetail = forwardRef<ModelListDetailRef, ModelListDetailProps>(({
|
|||||||
setList(response[0].models)
|
setList(response[0].models)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
/** Open key configuration modal */
|
||||||
const handleKeyConfig = (vo: ModelListItem) => {
|
const handleKeyConfig = (vo: ModelListItem) => {
|
||||||
multiKeyConfigModalRef.current?.handleOpen(vo, data.provider)
|
multiKeyConfigModalRef.current?.handleOpen(vo, data.provider)
|
||||||
}
|
}
|
||||||
|
/** Toggle model active status */
|
||||||
const handleChange = (vo: ModelListItem) => {
|
const handleChange = (vo: ModelListItem) => {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
updateModelStatus(vo.id, { is_active: !vo.is_active })
|
updateModelStatus(vo.id, { is_active: !vo.is_active })
|
||||||
@@ -55,22 +78,27 @@ const ModelListDetail = forwardRef<ModelListDetailRef, ModelListDetailProps>(({
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Close drawer */
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
setType(null)
|
setType(null)
|
||||||
setOpen(false)
|
setOpen(false)
|
||||||
refresh?.()
|
refresh?.()
|
||||||
}
|
}
|
||||||
|
/** Refresh model list */
|
||||||
const handleRefresh = () => {
|
const handleRefresh = () => {
|
||||||
getData(data)
|
getData(data)
|
||||||
}
|
}
|
||||||
|
/** Handle type filter change */
|
||||||
const handleTypeChange = (value: string) => {
|
const handleTypeChange = (value: string) => {
|
||||||
setType(value)
|
setType(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Expose methods to parent component */
|
||||||
useImperativeHandle(ref, () => ({
|
useImperativeHandle(ref, () => ({
|
||||||
handleOpen,
|
handleOpen,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
/** Filter models by selected type */
|
||||||
const filterList = useMemo(() => {
|
const filterList = useMemo(() => {
|
||||||
if (!type) return list
|
if (!type) return list
|
||||||
return list.filter(vo => vo.type === type)
|
return list.filter(vo => vo.type === type)
|
||||||
|
|||||||
@@ -1,3 +1,15 @@
|
|||||||
|
/*
|
||||||
|
* @Author: ZhaoYing
|
||||||
|
* @Date: 2026-02-03 16:49:49
|
||||||
|
* @Last Modified by: ZhaoYing
|
||||||
|
* @Last Modified time: 2026-02-03 16:54:26
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Model Square Detail Drawer
|
||||||
|
* Displays all models from a specific provider in the model square
|
||||||
|
* Allows adding models and editing custom models
|
||||||
|
*/
|
||||||
|
|
||||||
import { useState, useImperativeHandle, forwardRef } from 'react';
|
import { useState, useImperativeHandle, forwardRef } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Button, Space, App, Flex, Tooltip, Divider } from 'antd'
|
import { Button, Space, App, Flex, Tooltip, Divider } from 'antd'
|
||||||
@@ -11,10 +23,19 @@ import Tag from '@/components/Tag';
|
|||||||
import PageEmpty from '@/components/Empty/PageEmpty';
|
import PageEmpty from '@/components/Empty/PageEmpty';
|
||||||
import { getLogoUrl } from '../utils'
|
import { getLogoUrl } from '../utils'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component props
|
||||||
|
*/
|
||||||
interface ModelSquareDetailProps {
|
interface ModelSquareDetailProps {
|
||||||
|
/** Callback to refresh parent list */
|
||||||
refresh: () => void;
|
refresh: () => void;
|
||||||
|
/** Callback to edit model */
|
||||||
handleEdit: (vo: ModelPlazaItem) => void;
|
handleEdit: (vo: ModelPlazaItem) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Model square detail drawer component
|
||||||
|
*/
|
||||||
const ModelSquareDetail = forwardRef<ModelSquareDetailRef, ModelSquareDetailProps>(({ refresh, handleEdit }, ref) => {
|
const ModelSquareDetail = forwardRef<ModelSquareDetailRef, ModelSquareDetailProps>(({ refresh, handleEdit }, ref) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { message } = App.useApp()
|
const { message } = App.useApp()
|
||||||
@@ -23,15 +44,18 @@ const ModelSquareDetail = forwardRef<ModelSquareDetailRef, ModelSquareDetailProp
|
|||||||
|
|
||||||
const [list, setList] = useState<ModelPlazaItem[]>([])
|
const [list, setList] = useState<ModelPlazaItem[]>([])
|
||||||
|
|
||||||
|
/** Open drawer with model plaza data */
|
||||||
const handleOpen = (vo: ModelPlaza) => {
|
const handleOpen = (vo: ModelPlaza) => {
|
||||||
setModel(vo)
|
setModel(vo)
|
||||||
setOpen(true)
|
setOpen(true)
|
||||||
getList(vo)
|
getList(vo)
|
||||||
}
|
}
|
||||||
|
/** Close drawer */
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
setOpen(false)
|
setOpen(false)
|
||||||
refresh()
|
refresh()
|
||||||
}
|
}
|
||||||
|
/** Fetch model list for provider */
|
||||||
const getList = (vo: ModelPlaza) => {
|
const getList = (vo: ModelPlaza) => {
|
||||||
getModelPlaza({ provider: vo.provider })
|
getModelPlaza({ provider: vo.provider })
|
||||||
.then(res => {
|
.then(res => {
|
||||||
@@ -39,6 +63,7 @@ const ModelSquareDetail = forwardRef<ModelSquareDetailRef, ModelSquareDetailProp
|
|||||||
setList(response.length > 0 ? response[0].models : [])
|
setList(response.length > 0 ? response[0].models : [])
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
/** Add model to workspace */
|
||||||
const handleAdd = (item: ModelPlazaItem) => {
|
const handleAdd = (item: ModelPlazaItem) => {
|
||||||
addModelPlaza(item.id)
|
addModelPlaza(item.id)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
@@ -47,6 +72,7 @@ const ModelSquareDetail = forwardRef<ModelSquareDetailRef, ModelSquareDetailProp
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Expose methods to parent component */
|
||||||
useImperativeHandle(ref, () => ({
|
useImperativeHandle(ref, () => ({
|
||||||
handleOpen,
|
handleOpen,
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -1,10 +1,26 @@
|
|||||||
|
/*
|
||||||
|
* @Author: ZhaoYing
|
||||||
|
* @Date: 2026-02-03 16:49:55
|
||||||
|
* @Last Modified by: ZhaoYing
|
||||||
|
* @Last Modified time: 2026-02-03 16:49:55
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Multi-Key Configuration Modal
|
||||||
|
* Modal for managing multiple API keys for a single model
|
||||||
|
* Allows adding and removing API keys
|
||||||
|
*/
|
||||||
|
|
||||||
import { forwardRef, useImperativeHandle, useState } from 'react';
|
import { forwardRef, useImperativeHandle, useState } from 'react';
|
||||||
import { Form, Input, App, Button } from 'antd';
|
import { Form, Input, App, Button } from 'antd';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import type { ModelListItem, MultiKeyForm, MultiKeyConfigModalRef, MultiKeyConfigModalProps } from '../types';
|
import type { ModelListItem, MultiKeyForm, MultiKeyConfigModalRef, MultiKeyConfigModalProps } from '../types';
|
||||||
import RbModal from '@/components/RbModal'
|
import RbModal from '@/components/RbModal'
|
||||||
import { addModelApiKey, deleteModelApiKey, getModelInfo } from '@/api/models'
|
import { addModelApiKey, deleteModelApiKey, getModelInfo } from '@/api/models'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Multi-key configuration modal component
|
||||||
|
*/
|
||||||
const MultiKeyConfigModal = forwardRef<MultiKeyConfigModalRef, MultiKeyConfigModalProps>(({ refresh }, ref) => {
|
const MultiKeyConfigModal = forwardRef<MultiKeyConfigModalRef, MultiKeyConfigModalProps>(({ refresh }, ref) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { message } = App.useApp();
|
const { message } = App.useApp();
|
||||||
@@ -13,6 +29,7 @@ const MultiKeyConfigModal = forwardRef<MultiKeyConfigModalRef, MultiKeyConfigMod
|
|||||||
const [form] = Form.useForm<MultiKeyForm>();
|
const [form] = Form.useForm<MultiKeyForm>();
|
||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false)
|
||||||
|
|
||||||
|
/** Close modal and refresh parent */
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
setModel({} as ModelListItem);
|
setModel({} as ModelListItem);
|
||||||
refresh?.()
|
refresh?.()
|
||||||
@@ -22,11 +39,13 @@ const MultiKeyConfigModal = forwardRef<MultiKeyConfigModalRef, MultiKeyConfigMod
|
|||||||
setVisible(false);
|
setVisible(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** Open modal with model data */
|
||||||
const handleOpen = (vo: ModelListItem) => {
|
const handleOpen = (vo: ModelListItem) => {
|
||||||
setVisible(true);
|
setVisible(true);
|
||||||
getData(vo)
|
getData(vo)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** Fetch model information */
|
||||||
const getData = (vo: ModelListItem) => {
|
const getData = (vo: ModelListItem) => {
|
||||||
if (!vo.id) return
|
if (!vo.id) return
|
||||||
|
|
||||||
@@ -35,6 +54,7 @@ const MultiKeyConfigModal = forwardRef<MultiKeyConfigModalRef, MultiKeyConfigMod
|
|||||||
setModel(res as ModelListItem)
|
setModel(res as ModelListItem)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
/** Add new API key */
|
||||||
const handleSave = () => {
|
const handleSave = () => {
|
||||||
form
|
form
|
||||||
.validateFields()
|
.validateFields()
|
||||||
@@ -58,6 +78,7 @@ const MultiKeyConfigModal = forwardRef<MultiKeyConfigModalRef, MultiKeyConfigMod
|
|||||||
console.log('err', err)
|
console.log('err', err)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
/** Delete API key */
|
||||||
const handleDelete = (api_key_id: string) => {
|
const handleDelete = (api_key_id: string) => {
|
||||||
deleteModelApiKey(api_key_id)
|
deleteModelApiKey(api_key_id)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
@@ -66,6 +87,7 @@ const MultiKeyConfigModal = forwardRef<MultiKeyConfigModalRef, MultiKeyConfigMod
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Expose methods to parent component */
|
||||||
useImperativeHandle(ref, () => ({
|
useImperativeHandle(ref, () => ({
|
||||||
handleOpen,
|
handleOpen,
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -1,3 +1,15 @@
|
|||||||
|
/*
|
||||||
|
* @Author: ZhaoYing
|
||||||
|
* @Date: 2026-02-03 16:50:05
|
||||||
|
* @Last Modified by: ZhaoYing
|
||||||
|
* @Last Modified time: 2026-02-03 16:50:05
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Model Management Main Page
|
||||||
|
* Manages AI models with three views: group models, model list, and model square
|
||||||
|
* Supports filtering, searching, and CRUD operations
|
||||||
|
*/
|
||||||
|
|
||||||
import { useState, useRef, type FC } from 'react';
|
import { useState, useRef, type FC } from 'react';
|
||||||
import { Button, Flex, Space, type SegmentedProps, Form } from 'antd'
|
import { Button, Flex, Space, type SegmentedProps, Form } from 'antd'
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@@ -13,8 +25,14 @@ import CustomModelModal from './components/CustomModelModal'
|
|||||||
import CustomSelect from '@/components/CustomSelect'
|
import CustomSelect from '@/components/CustomSelect'
|
||||||
import { modelTypeUrl, modelProviderUrl } from '@/api/models'
|
import { modelTypeUrl, modelProviderUrl } from '@/api/models'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Available tab keys
|
||||||
|
*/
|
||||||
const tabKeys = ['group', 'list', 'square']
|
const tabKeys = ['group', 'list', 'square']
|
||||||
const ModelManagement: FC = () => {
|
|
||||||
|
/**
|
||||||
|
* Model management main component
|
||||||
|
*/const ModelManagement: FC = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [activeTab, setActiveTab] = useState('group');
|
const [activeTab, setActiveTab] = useState('group');
|
||||||
const configModalRef = useRef<GroupModelModalRef>(null)
|
const configModalRef = useRef<GroupModelModalRef>(null)
|
||||||
@@ -24,17 +42,20 @@ const ModelManagement: FC = () => {
|
|||||||
const [form] = Form.useForm<Query>()
|
const [form] = Form.useForm<Query>()
|
||||||
const query = Form.useWatch([], form)
|
const query = Form.useWatch([], form)
|
||||||
|
|
||||||
|
/** Format tab items with translations */
|
||||||
const formatTabItems = () => {
|
const formatTabItems = () => {
|
||||||
return tabKeys.map(value => ({
|
return tabKeys.map(value => ({
|
||||||
value,
|
value,
|
||||||
label: t(`modelNew.${value}`),
|
label: t(`modelNew.${value}`),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
/** Handle tab change */
|
||||||
const handleChangeTab = (value: SegmentedProps['value']) => {
|
const handleChangeTab = (value: SegmentedProps['value']) => {
|
||||||
setActiveTab(value as string);
|
setActiveTab(value as string);
|
||||||
form.resetFields()
|
form.resetFields()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Open edit modal based on active tab */
|
||||||
const handleEdit = (vo?: ModelListItem | ModelPlazaItem) => {
|
const handleEdit = (vo?: ModelListItem | ModelPlazaItem) => {
|
||||||
switch(activeTab) {
|
switch(activeTab) {
|
||||||
case 'group':
|
case 'group':
|
||||||
@@ -45,6 +66,7 @@ const ModelManagement: FC = () => {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/** Refresh list based on active tab */
|
||||||
const handleRefresh = () => {
|
const handleRefresh = () => {
|
||||||
switch (activeTab) {
|
switch (activeTab) {
|
||||||
case 'group':
|
case 'group':
|
||||||
|
|||||||
@@ -1,139 +1,315 @@
|
|||||||
export interface Query {
|
/*
|
||||||
type?: string;
|
* @Author: ZhaoYing
|
||||||
provider?: string;
|
* @Date: 2026-02-03 16:50:18
|
||||||
is_active?: boolean;
|
* @Last Modified by: ZhaoYing
|
||||||
is_public?: boolean;
|
* @Last Modified time: 2026-02-03 16:50:18
|
||||||
is_composite?: boolean;
|
*/
|
||||||
search?: string;
|
/**
|
||||||
|
* Type definitions for Model Management
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Query parameters for model filtering
|
||||||
|
*/
|
||||||
|
export interface Query {
|
||||||
|
/** Model type filter */
|
||||||
|
type?: string;
|
||||||
|
/** Model provider filter */
|
||||||
|
provider?: string;
|
||||||
|
/** Active status filter */
|
||||||
|
is_active?: boolean;
|
||||||
|
/** Public status filter */
|
||||||
|
is_public?: boolean;
|
||||||
|
/** Composite model filter */
|
||||||
|
is_composite?: boolean;
|
||||||
|
/** Search keyword */
|
||||||
|
search?: string;
|
||||||
|
/** Page size */
|
||||||
pagesize?: number;
|
pagesize?: number;
|
||||||
|
/** Page number */
|
||||||
page?: number;
|
page?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Description item for model details
|
||||||
|
*/
|
||||||
export interface DescriptionItem {
|
export interface DescriptionItem {
|
||||||
|
/** Item key */
|
||||||
key: string;
|
key: string;
|
||||||
|
/** Item label */
|
||||||
label: string;
|
label: string;
|
||||||
|
/** Item content */
|
||||||
children: string;
|
children: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Composite model form data
|
||||||
|
*/
|
||||||
export interface CompositeModelForm {
|
export interface CompositeModelForm {
|
||||||
|
/** Model logo */
|
||||||
logo?: any;
|
logo?: any;
|
||||||
|
/** Model name */
|
||||||
name: string;
|
name: string;
|
||||||
|
/** Model type */
|
||||||
type?: string;
|
type?: string;
|
||||||
|
/** Model description */
|
||||||
description: string;
|
description: string;
|
||||||
|
/** Associated API key IDs */
|
||||||
api_key_ids: ModelApiKey[] | string[];
|
api_key_ids: ModelApiKey[] | string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Group model modal ref interface
|
||||||
|
*/
|
||||||
export interface GroupModelModalRef {
|
export interface GroupModelModalRef {
|
||||||
|
/** Open modal with optional model data */
|
||||||
handleOpen: (model?: ModelListItem) => void;
|
handleOpen: (model?: ModelListItem) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Group model modal props
|
||||||
|
*/
|
||||||
export interface GroupModelModalProps {
|
export interface GroupModelModalProps {
|
||||||
|
/** Callback to refresh model list */
|
||||||
refresh?: () => void;
|
refresh?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Model list detail ref interface
|
||||||
|
*/
|
||||||
export interface ModelListDetailRef {
|
export interface ModelListDetailRef {
|
||||||
|
/** Open detail drawer with provider model data */
|
||||||
handleOpen: (vo: ProviderModelItem) => void;
|
handleOpen: (vo: ProviderModelItem) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Model API key configuration
|
||||||
|
*/
|
||||||
export interface ModelApiKey {
|
export interface ModelApiKey {
|
||||||
|
/** Model name */
|
||||||
model_name: string;
|
model_name: string;
|
||||||
|
/** API key description */
|
||||||
description: string | null;
|
description: string | null;
|
||||||
|
/** Model provider */
|
||||||
provider: string;
|
provider: string;
|
||||||
|
/** API key value */
|
||||||
api_key: string;
|
api_key: string;
|
||||||
|
/** API base URL */
|
||||||
api_base: string;
|
api_base: string;
|
||||||
|
/** Additional configuration */
|
||||||
config: any;
|
config: any;
|
||||||
|
/** Whether API key is active */
|
||||||
is_active: boolean;
|
is_active: boolean;
|
||||||
|
/** Priority level */
|
||||||
priority: string;
|
priority: string;
|
||||||
|
/** API key ID */
|
||||||
id: string;
|
id: string;
|
||||||
|
/** Usage count */
|
||||||
usage_count: string;
|
usage_count: string;
|
||||||
|
/** Last used timestamp */
|
||||||
last_used_at: number;
|
last_used_at: number;
|
||||||
|
/** Creation timestamp */
|
||||||
created_at: number;
|
created_at: number;
|
||||||
|
/** Update timestamp */
|
||||||
updated_at: number;
|
updated_at: number;
|
||||||
|
/** Associated model config IDs */
|
||||||
model_config_ids: string[];
|
model_config_ids: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Model list item data structure
|
||||||
|
*/
|
||||||
export interface ModelListItem {
|
export interface ModelListItem {
|
||||||
|
/** Model name */
|
||||||
model_name?: string;
|
model_name?: string;
|
||||||
|
/** Associated model config IDs */
|
||||||
model_config_ids: string[];
|
model_config_ids: string[];
|
||||||
|
/** Display name */
|
||||||
name: string;
|
name: string;
|
||||||
|
/** Model type */
|
||||||
type: string;
|
type: string;
|
||||||
|
/** Model logo URL */
|
||||||
logo: string;
|
logo: string;
|
||||||
|
/** Model description */
|
||||||
description: string;
|
description: string;
|
||||||
|
/** Model provider */
|
||||||
provider: string;
|
provider: string;
|
||||||
|
/** Model configuration */
|
||||||
config: any;
|
config: any;
|
||||||
|
/** Whether model is active */
|
||||||
is_active: boolean;
|
is_active: boolean;
|
||||||
|
/** Whether model is public */
|
||||||
is_public: boolean;
|
is_public: boolean;
|
||||||
|
/** Model ID */
|
||||||
id: string;
|
id: string;
|
||||||
|
/** Creation timestamp */
|
||||||
created_at: number;
|
created_at: number;
|
||||||
|
/** Update timestamp */
|
||||||
updated_at: number;
|
updated_at: number;
|
||||||
|
/** Associated API keys */
|
||||||
api_keys: ModelApiKey[]
|
api_keys: ModelApiKey[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provider model item grouping
|
||||||
|
*/
|
||||||
export interface ProviderModelItem {
|
export interface ProviderModelItem {
|
||||||
|
/** Provider name */
|
||||||
provider: string;
|
provider: string;
|
||||||
|
/** Provider logo URL */
|
||||||
logo?: string;
|
logo?: string;
|
||||||
|
/** Provider tags */
|
||||||
tags: string[];
|
tags: string[];
|
||||||
|
/** Models from this provider */
|
||||||
models: ModelListItem[];
|
models: ModelListItem[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Key configuration modal form data
|
||||||
|
*/
|
||||||
export interface KeyConfigModalForm {
|
export interface KeyConfigModalForm {
|
||||||
|
/** Model provider */
|
||||||
provider: string;
|
provider: string;
|
||||||
|
/** API key value */
|
||||||
api_key: string;
|
api_key: string;
|
||||||
|
/** API base URL */
|
||||||
api_base: string;
|
api_base: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Key configuration modal ref interface
|
||||||
|
*/
|
||||||
export interface KeyConfigModalRef {
|
export interface KeyConfigModalRef {
|
||||||
|
/** Open modal with provider model data */
|
||||||
handleOpen: (vo: ProviderModelItem) => void;
|
handleOpen: (vo: ProviderModelItem) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Key configuration modal props
|
||||||
|
*/
|
||||||
export interface KeyConfigModalProps {
|
export interface KeyConfigModalProps {
|
||||||
|
/** Callback to refresh model list */
|
||||||
refresh?: () => void;
|
refresh?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Multi-key configuration form data
|
||||||
|
*/
|
||||||
export interface MultiKeyForm {
|
export interface MultiKeyForm {
|
||||||
|
/** Model config ID */
|
||||||
model_config_id?: string;
|
model_config_id?: string;
|
||||||
|
/** Model name */
|
||||||
model_name: string;
|
model_name: string;
|
||||||
|
/** Model provider */
|
||||||
provider: string;
|
provider: string;
|
||||||
|
/** API key value */
|
||||||
api_key: string;
|
api_key: string;
|
||||||
|
/** API base URL */
|
||||||
api_base: string;
|
api_base: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Multi-key configuration modal ref interface
|
||||||
|
*/
|
||||||
export interface MultiKeyConfigModalRef {
|
export interface MultiKeyConfigModalRef {
|
||||||
|
/** Open modal with model data */
|
||||||
handleOpen: (vo: ModelListItem, provider?: string) => void;
|
handleOpen: (vo: ModelListItem, provider?: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Multi-key configuration modal props
|
||||||
|
*/
|
||||||
export interface MultiKeyConfigModalProps {
|
export interface MultiKeyConfigModalProps {
|
||||||
|
/** Callback to refresh model list */
|
||||||
refresh?: () => void;
|
refresh?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Model plaza grouping by provider
|
||||||
|
*/
|
||||||
export interface ModelPlaza {
|
export interface ModelPlaza {
|
||||||
|
/** Provider name */
|
||||||
provider: string;
|
provider: string;
|
||||||
|
/** Models from this provider */
|
||||||
models: ModelPlazaItem[];
|
models: ModelPlazaItem[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Model plaza item data structure
|
||||||
|
*/
|
||||||
export interface ModelPlazaItem {
|
export interface ModelPlazaItem {
|
||||||
|
/** Model ID */
|
||||||
id: string;
|
id: string;
|
||||||
|
/** Model name */
|
||||||
name: string;
|
name: string;
|
||||||
|
/** Model type */
|
||||||
type: string;
|
type: string;
|
||||||
|
/** Model provider */
|
||||||
provider: string;
|
provider: string;
|
||||||
|
/** Model logo URL */
|
||||||
logo: string;
|
logo: string;
|
||||||
|
/** Model description */
|
||||||
description: string;
|
description: string;
|
||||||
|
/** Whether model is deprecated */
|
||||||
is_deprecated: boolean;
|
is_deprecated: boolean;
|
||||||
|
/** Whether model is official */
|
||||||
is_official: boolean;
|
is_official: boolean;
|
||||||
|
/** Model tags */
|
||||||
tags: string[];
|
tags: string[];
|
||||||
|
/** Number of times added */
|
||||||
add_count: number;
|
add_count: number;
|
||||||
|
/** Whether user has added this model */
|
||||||
is_added: boolean;
|
is_added: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Model square detail ref interface
|
||||||
|
*/
|
||||||
export interface ModelSquareDetailRef {
|
export interface ModelSquareDetailRef {
|
||||||
|
/** Open detail drawer with model plaza data */
|
||||||
handleOpen: (vo: ModelPlaza) => void;
|
handleOpen: (vo: ModelPlaza) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom model form data
|
||||||
|
*/
|
||||||
export interface CustomModelForm {
|
export interface CustomModelForm {
|
||||||
|
/** Model name */
|
||||||
name: string;
|
name: string;
|
||||||
|
/** Model type */
|
||||||
type?: string;
|
type?: string;
|
||||||
|
/** Model provider */
|
||||||
provider?: string;
|
provider?: string;
|
||||||
|
/** Model logo */
|
||||||
logo?: any;
|
logo?: any;
|
||||||
|
/** Model description */
|
||||||
description: string;
|
description: string;
|
||||||
|
/** Whether model is official */
|
||||||
is_official: boolean;
|
is_official: boolean;
|
||||||
|
/** Model tags */
|
||||||
tags: string[];
|
tags: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom model modal ref interface
|
||||||
|
*/
|
||||||
export interface CustomModelModalRef {
|
export interface CustomModelModalRef {
|
||||||
|
/** Open modal with optional model plaza item */
|
||||||
handleOpen: (vo?: ModelPlazaItem) => void;
|
handleOpen: (vo?: ModelPlazaItem) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom model modal props
|
||||||
|
*/
|
||||||
export interface CustomModelModalProps {
|
export interface CustomModelModalProps {
|
||||||
|
/** Callback to refresh model list */
|
||||||
refresh?: () => void;
|
refresh?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base ref interface for list components
|
||||||
|
*/
|
||||||
export interface BaseRef {
|
export interface BaseRef {
|
||||||
|
/** Refresh list data */
|
||||||
getList: () => void;
|
getList: () => void;
|
||||||
}
|
}
|
||||||
@@ -1,3 +1,13 @@
|
|||||||
|
/*
|
||||||
|
* @Author: ZhaoYing
|
||||||
|
* @Date: 2026-02-03 16:50:22
|
||||||
|
* @Last Modified by: ZhaoYing
|
||||||
|
* @Last Modified time: 2026-02-03 16:50:22
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Utility functions for Model Management
|
||||||
|
*/
|
||||||
|
|
||||||
import bedrockIcon from '@/assets/images/model/bedrock.svg'
|
import bedrockIcon from '@/assets/images/model/bedrock.svg'
|
||||||
import dashscopeIcon from '@/assets/images/model/dashscope.png'
|
import dashscopeIcon from '@/assets/images/model/dashscope.png'
|
||||||
import gpustackIcon from '@/assets/images/model/gpustack.png'
|
import gpustackIcon from '@/assets/images/model/gpustack.png'
|
||||||
@@ -5,6 +15,9 @@ import ollamaIcon from '@/assets/images/model/ollama.svg'
|
|||||||
import openaiIcon from '@/assets/images/model/openai.svg'
|
import openaiIcon from '@/assets/images/model/openai.svg'
|
||||||
import xinferenceIcon from '@/assets/images/model/xinference.svg'
|
import xinferenceIcon from '@/assets/images/model/xinference.svg'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provider icon mapping
|
||||||
|
*/
|
||||||
export const ICONS = {
|
export const ICONS = {
|
||||||
bedrock: bedrockIcon,
|
bedrock: bedrockIcon,
|
||||||
dashscope: dashscopeIcon,
|
dashscope: dashscopeIcon,
|
||||||
@@ -14,6 +27,11 @@ export const ICONS = {
|
|||||||
xinference: xinferenceIcon
|
xinference: xinferenceIcon
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get logo URL from provider name or URL
|
||||||
|
* @param logo - Provider name or logo URL
|
||||||
|
* @returns Logo URL or undefined
|
||||||
|
*/
|
||||||
export const getLogoUrl = (logo?: string) => {
|
export const getLogoUrl = (logo?: string) => {
|
||||||
if (!logo) {
|
if (!logo) {
|
||||||
return undefined
|
return undefined
|
||||||
|
|||||||
@@ -1,4 +1,16 @@
|
|||||||
|
/*
|
||||||
|
* @Author: ZhaoYing
|
||||||
|
* @Date: 2026-02-03 17:34:18
|
||||||
|
* @Last Modified by: ZhaoYing
|
||||||
|
* @Last Modified time: 2026-02-03 17:34:18
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* No Permission Page
|
||||||
|
* Displays when user lacks access rights to a resource
|
||||||
|
*/
|
||||||
|
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import noPermission from '@/assets/images/empty/noPermission.png';
|
import noPermission from '@/assets/images/empty/noPermission.png';
|
||||||
import Empty from '@/components/Empty';
|
import Empty from '@/components/Empty';
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,16 @@
|
|||||||
|
/*
|
||||||
|
* @Author: ZhaoYing
|
||||||
|
* @Date: 2026-02-03 17:35:01
|
||||||
|
* @Last Modified by: ZhaoYing
|
||||||
|
* @Last Modified time: 2026-02-03 17:35:01
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Not Found Page (404)
|
||||||
|
* Displays when requested route or resource does not exist
|
||||||
|
*/
|
||||||
|
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import notFoundImg from '@/assets/images/empty/404.png';
|
import notFoundImg from '@/assets/images/empty/404.png';
|
||||||
import Empty from '@/components/Empty';
|
import Empty from '@/components/Empty';
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,14 @@
|
|||||||
|
/*
|
||||||
|
* @Author: ZhaoYing
|
||||||
|
* @Date: 2026-02-03 17:35:49
|
||||||
|
* @Last Modified by: ZhaoYing
|
||||||
|
* @Last Modified time: 2026-02-03 17:35:49
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Order Detail Component
|
||||||
|
* Modal displaying detailed order information including payment details
|
||||||
|
*/
|
||||||
|
|
||||||
import { forwardRef, useImperativeHandle, useState, useCallback } from 'react';
|
import { forwardRef, useImperativeHandle, useState, useCallback } from 'react';
|
||||||
import { Descriptions } from 'antd';
|
import { Descriptions } from 'antd';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@@ -14,11 +25,12 @@ const OrderDetail = forwardRef<OrderDetailRef, { getProductType: (type: string)
|
|||||||
const [visible, setVisible] = useState(false);
|
const [visible, setVisible] = useState(false);
|
||||||
const [data, setData] = useState({})
|
const [data, setData] = useState({})
|
||||||
|
|
||||||
// 封装取消方法,添加关闭弹窗逻辑
|
/** Close modal */
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
setVisible(false);
|
setVisible(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** Open modal and fetch order details */
|
||||||
const handleOpen = (order: Order) => {
|
const handleOpen = (order: Order) => {
|
||||||
setVisible(true);
|
setVisible(true);
|
||||||
getOrderDetail(order.order_no)
|
getOrderDetail(order.order_no)
|
||||||
@@ -26,6 +38,7 @@ const OrderDetail = forwardRef<OrderDetailRef, { getProductType: (type: string)
|
|||||||
setData(res as Order)
|
setData(res as Order)
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
/** Format order information items */
|
||||||
const formatItems = useCallback(() => {
|
const formatItems = useCallback(() => {
|
||||||
if (!data) return []
|
if (!data) return []
|
||||||
return ['order_no', 'product_type', 'payable_amount', 'status', 'pay_time', 'create_time'].map(key => {
|
return ['order_no', 'product_type', 'payable_amount', 'status', 'pay_time', 'create_time'].map(key => {
|
||||||
@@ -43,6 +56,7 @@ const OrderDetail = forwardRef<OrderDetailRef, { getProductType: (type: string)
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}, [data])
|
}, [data])
|
||||||
|
/** Format payment information items */
|
||||||
const formatPayItems = useCallback(() => {
|
const formatPayItems = useCallback(() => {
|
||||||
if (!data) return []
|
if (!data) return []
|
||||||
return ['pay_txn_id', 'payer'].map(key => ({
|
return ['pay_txn_id', 'payer'].map(key => ({
|
||||||
@@ -52,13 +66,12 @@ const OrderDetail = forwardRef<OrderDetailRef, { getProductType: (type: string)
|
|||||||
}))
|
}))
|
||||||
}, [data])
|
}, [data])
|
||||||
|
|
||||||
// 暴露给父组件的方法
|
/** Expose methods to parent component */
|
||||||
useImperativeHandle(ref, () => ({
|
useImperativeHandle(ref, () => ({
|
||||||
handleOpen,
|
handleOpen,
|
||||||
handleClose
|
handleClose
|
||||||
}));
|
}));
|
||||||
// ['pay_txn_id', 'payer']
|
|
||||||
// ['pay_txn_id', 'payer']
|
|
||||||
return (
|
return (
|
||||||
<RbModal
|
<RbModal
|
||||||
title={t('pricing.orderDetail')}
|
title={t('pricing.orderDetail')}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user