diff --git a/web/src/styles/index.css b/web/src/styles/index.css index 479740d9..2c2425cf 100644 --- a/web/src/styles/index.css +++ b/web/src/styles/index.css @@ -311,4 +311,7 @@ body { .ant-select-dropdown .ant-select-item-option-selected:not(.ant-select-item-option-disabled) { color: #212332; background: #F6F6F6; +} +.ant-checkbox .ant-checkbox-inner { + border-radius: 6px !important; } \ No newline at end of file diff --git a/web/src/views/ApplicationConfig/Agent.tsx b/web/src/views/ApplicationConfig/Agent.tsx index 237c3373..3b66adc6 100644 --- a/web/src/views/ApplicationConfig/Agent.tsx +++ b/web/src/views/ApplicationConfig/Agent.tsx @@ -2,13 +2,12 @@ * @Author: ZhaoYing * @Date: 2026-02-03 16:29:21 * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-03-03 14:24:34 + * @Last Modified time: 2026-03-04 10:28:59 */ -import { type FC, type ReactNode, useEffect, useRef, useState, forwardRef, useImperativeHandle } from 'react'; -import clsx from 'clsx' +import { useEffect, useRef, useState, forwardRef, useImperativeHandle } from 'react'; import { useTranslation } from 'react-i18next' import { useParams } from 'react-router-dom'; -import { Row, Col, Space, Form, Input, Switch, Button, App, Spin } from 'antd' +import { Row, Col, Space, Form, Input, Button, App, Spin, Flex } from 'antd' import Chat from './components/Chat' import RbCard from '@/components/RbCard/Card' @@ -35,92 +34,13 @@ import VariableList from './components/VariableList/VariableList' import { getApplicationConfig } from '@/api/application' import { memoryConfigListUrl } from '@/api/memory' import CustomSelect from '@/components/CustomSelect' -import aiPrompt from '@/assets/images/application/aiPrompt.png' import AiPromptModal from './components/AiPromptModal' import ToolList from './components/ToolList/ToolList' import SkillList from './components/Skill' import ChatVariableConfigModal from './components/ChatVariableConfigModal'; import type { Skill } from '@/views/Skills/types' - -/** - * Description wrapper component - * @param desc - Description text - * @param className - Additional CSS classes - */ -const DescWrapper: FC<{desc: string, className?: string}> = ({desc, className}) => { - return ( -
- {desc} -
- ) -} -/** - * 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}) => { - return ( -
- {title} - {children} -
- ) -} -/** - * 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 { t } = useTranslation(); - return ( -
- - {desc && } - - - - -
- ) -} -/** - * 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 { t } = useTranslation(); - return ( - <> - - - - - - - - ) -} +import SwitchFormItem from '@/components/FormItem/SwitchFormItem' +import DescWrapper from '@/components/FormItem/DescWrapper' /** * Agent configuration component @@ -172,8 +92,8 @@ const Agent = forwardRef((_props, ref) => { const allSkills = Array.isArray(skills?.skill_ids) ? skills?.skill_ids.map(vo => ({ id: vo })) : [] const allTools = Array.isArray(response.tools) ? response.tools : [] const memoryContent = response.memory?.memory_config_id - const parsedMemoryContent = memoryContent === null || memoryContent === '' - ? undefined + const parsedMemoryContent = memoryContent === null || memoryContent === '' + ? undefined : !isNaN(Number(memoryContent)) ? Number(memoryContent) : memoryContent const variableList = variables?.map((item, index) => ({ ...item, @@ -410,46 +330,48 @@ const Agent = forwardRef((_props, ref) => { return ( <> {loading && } - - -
- - - - -
+ +
- - - - -
-
- {t('application.configuration')} - ({t('application.configurationDesc')}) -
- + + + + + + + + + +
+ {t('application.aiPrompt')} + + } + > +
+ {t('application.configuration')} + ({t('application.configurationDesc')})
- + @@ -462,15 +384,28 @@ const Agent = forwardRef((_props, ref) => { {/* Memory Configuration */} - - - + - + } + layout="vertical" + className="rb:mb-0!" + > + + +
@@ -485,26 +420,29 @@ const Agent = forwardRef((_props, ref) => { - + - -
- {t('application.debuggingAndPreview')} - - - {chatVariables.length > 0 && + + - } - -
-
-
- + +
+ + } + headerType="borderless" + headerClassName="rb:h-[56px]! rb:leading-[22px]!" + titleClassName="rb:font-[MiSans-Bold] rb:font-bold" + bodyClassName="rb:p-4! rb:pt-0!" + className="rb:h-full" + > = ({ application }) => { // Calculate total requests across all API keys const totalRequests = apiKeyList.reduce((total, item) => total + item.total_requests, 0); return ( -
- - + + + {t('application.endpointConfiguration')} + ({t('application.endpointConfigurationSubTitle')}) + } + headerType="borderless" + headerClassName="rb:min-h-13.5!" > -
{t('application.endpointConfigurationSubTitle')}
-
- - {['GET', 'POST', 'PUT', 'DELETE'].map((method) => ( -
- {method} -
- ))} -
+ + {['GET', 'POST', 'PUT', 'DELETE'].map((method) => ( +
+ {method} +
+ ))} +
-
- {copyContent} - - -
-
-
- + {copyContent} + + + + + + {t('application.apiKeys')} + ({t('application.apiKeySubTitle')}) + } extra={ } + headerType="borderless" + headerClassName="rb:min-h-13.5!" > -
{t('application.apiKeySubTitle')}
{/* Overview Data */} - + - +
{apiKeyList.length}
+
{t('application.apiKeyTotal')}
- +
{totalRequests}
+
{t('application.apiKeyRequestTotal')}
{/* API Key List */} {apiKeyList.sort((a, b) => b.created_at - a.created_at).map(item => ( -
-
-
-
{item.name}
- ID: {item.id} -
- +
+ + +
{item.name}
+
ID: {item.id}
+
+
handleEdit(item)} @@ -189,30 +196,38 @@ const Api: FC<{ application: Application | null }> = ({ application }) => { onClick={() => handleDelete(item)} >
-
-
- {maskApiKeys(item.api_key)} - - -
- - + + + - + + +
{item.total_requests}
+
{t('application.apiKeyRequestTotal')}
+ + +
{item.rate_limit}
+
{t('application.qpsLimit')}
+ +
- - + + + {maskApiKeys(item.api_key)} + + +
))} - - + + ((_props, ref) => { list: [] }, ]) + const [loading, setLoading] = useState(false) /** * Save cluster configuration @@ -103,6 +102,7 @@ const Cluster = forwardRef((_props, ref) => { if (!id) { return } + setLoading(true) getMultiAgentConfig(id as string).then(res => { const response = res as Config setData(response) @@ -131,6 +131,9 @@ const Cluster = forwardRef((_props, ref) => { setSubAgents(sub_agents) } }) + .finally(() => { + setLoading(false) + }) } /** * Open sub-agent modal for add or edit @@ -186,80 +189,84 @@ const Cluster = forwardRef((_props, ref) => { } return ( - - -
- -
-
- - - - ({ - value: type, - label: t(`application.${type}`), - labelDesc: t(`application.${type}Desc`), - }))} - allowClear={false} - /> - - - - - -
{t('application.added')}: {subAgents.length}/{MAX_LENGTH}
- + <> + {loading && } + + + + + + + + + ({ + value: type, + label: t(`application.${type}`), + labelDesc: t(`application.${type}Desc`), + }))} + allowClear={false} + block={true} + /> + + - {subAgents.length === 0 - ? - : subAgents.map((agent, index) => ( - - -
- {agent.name?.[0]} -
-
- -
{agent.name} - - {agent.is_active ? t('common.enable') : t('common.deleted')} - -
- {agent.role &&
{agent.role || '-'}
} - {agent.capabilities && {agent.capabilities.map((tag, tagIndex) => {tag})}} -
-
- - -
handleSubAgentModal(agent)} - >
-
handleDeleteSubAgent(agent)} - >
-
-
- ))} -
- - {values?.orchestration_mode !== 'collaboration' && - + {t('application.subAgentsManagement')} + ({subAgents.length}/{MAX_LENGTH}) + } + extra={} > - - + {subAgents.length === 0 + ?
+ : + {subAgents.map((agent, index) => ( + + +
+ {agent.name} + + {agent.is_active ? t('common.enable') : t('common.deleted')} + +
+ {agent.role &&
{agent.role || '-'}
} + {agent.capabilities && + {agent.capabilities.map((tag, tagIndex) => {tag})} + } +
+ + +
handleSubAgentModal(agent)} + >
+
handleDeleteSubAgent(agent)} + >
+
+
+ ))} +
+ } +
+ + {values?.orchestration_mode !== 'collaboration' && + {t('application.model')}} + required={true} + className="rb:mb-4!" + > + ((_props, ref) => { style={{ width: '100%' }} /> - - - +
} + onClick={handleEditModelConfig} + >{t('application.modelConfig')} - -
- - - ({ - value: type, - label: t(`application.${type}`), - }))} - /> - -
} -
- - - - - - - + + + {t('application.orchestrationMode')}} + className="rb:mb-4!" + > + ({ + value: type, + label: t(`application.${type}`), + }))} + /> + + } + + + + + + + + - - - + + + + ) }) diff --git a/web/src/views/ApplicationConfig/ReleasePage.tsx b/web/src/views/ApplicationConfig/ReleasePage.tsx index 63f6df71..b3dd5639 100644 --- a/web/src/views/ApplicationConfig/ReleasePage.tsx +++ b/web/src/views/ApplicationConfig/ReleasePage.tsx @@ -1,13 +1,13 @@ /* * @Author: ZhaoYing * @Date: 2026-02-03 16:29:41 - * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-02-03 16:29:41 + * @Last Modified by: ZhaoYing + * @Last Modified time: 2026-03-03 19:02:43 */ import { type FC, useState, useEffect, useRef } from 'react'; import { useTranslation } from 'react-i18next'; import clsx from 'clsx'; -import { Button, Space, Input, Form, App } from 'antd'; +import { Space, Input, Form, App, Flex } from 'antd'; import Tag, { type TagProps } from './components/Tag' import RbCard from '@/components/RbCard/Card' @@ -19,6 +19,7 @@ import type { Application } from '@/views/ApplicationManagement/types' import Empty from '@/components/Empty' import { formatDateTime } from '@/utils/format'; import Markdown from '@/components/Markdown' +import RbButton from '@/components/RbButton'; /** * Tag color mapping for release versions */ @@ -28,6 +29,7 @@ const tagColors: Record = { history: 'default', } +const heightClass = 'rb:h-[calc(100vh-88px)]' /** * Release page component * Manages application version releases, rollbacks, and version history @@ -68,12 +70,12 @@ const ReleasePage: FC<{data: Application; refresh: () => void}> = ({data, refres }) } return ( -
-
- -
- {t('application.versionList')} -
{t('application.versionListDesc')}
+ +
+ +
+
{t('application.versionList')}
+
{t('application.versionListDesc')}
{releaseList.length === 0 ? @@ -91,9 +93,9 @@ const ReleasePage: FC<{data: Application; refresh: () => void}> = ({data, refres {tagKey} } } - className={clsx("rb:hover:border-[#155EEF]! rb:cursor-pointer", { - 'rb:bg-[rgba(21,94,239,0.06)]! rb:border-[#155EEF]!': version.id === selectedVersion.id, - 'rb:border-[#DFE4ED] rb:bg-[#FBFDFF]': version.id !== selectedVersion.id + className={clsx("rb:hover:shadow-[0px_2px_8px_0px_rgba(0,0,0,0.2)]! rb:cursor-pointer rb:bg-white", { + 'rb:border-[#171719]!': version.id === selectedVersion.id, + 'rb:border-[#DFE4ED] ': version.id !== selectedVersion.id })} headerType="borderless" onClick={() => setSelectedVersion(version)} @@ -101,38 +103,41 @@ const ReleasePage: FC<{data: Application; refresh: () => void}> = ({data, refres
-
+
{t('application.publishedOn')} {formatDateTime(version.published_at, 'YYYY-MM-DD HH:mm:ss')}
-
+
{t('application.publisher')}: {version.publisher_name}
) }) } - +
-
+
-
- {selectedVersion && t('application.DetailsOfVersion', { version: selectedVersion.version_name && selectedVersion.version_name[0].toLocaleLowerCase() === 'v' ? selectedVersion.version_name : selectedVersion.version_name ? `v${selectedVersion.version_name}` : `v${selectedVersion.version}` || '-' })} + {selectedVersion && t('application.detailsOfVersion', { version: selectedVersion.version_name && selectedVersion.version_name[0].toLocaleLowerCase() === 'v' ? selectedVersion.version_name : selectedVersion.version_name ? `v${selectedVersion.version_name}` : `v${selectedVersion.version}` || '-' })} {selectedVersion && <> - {/* */} - {data.current_release_id !== selectedVersion.id && } - + {/* {t('application.exportDSLFile')} */} + {data.current_release_id !== selectedVersion.id && {t('application.willRollToThisVersion')}} + releaseShareModalRef.current?.handleOpen()}>{t('application.share')} } - + releaseModalRef.current?.handleOpen()}>{t('application.release')} -
+ {selectedVersion && - - + +
@@ -147,22 +152,26 @@ const ReleasePage: FC<{data: Application; refresh: () => void}> = ({data, refres {/* Logs */} - - + + {selectedVersion && ( {formatDateTime(selectedVersion.published_at, 'YYYY-MM-DD HH:mm:ss')}
} extra={{selectedVersion.publisher_name}} + bodyClassName="rb:pt-0! rb:pb-3! rb:px-4!" > -
+
)} - + - + }
@@ -175,7 +184,7 @@ const ReleasePage: FC<{data: Application; refresh: () => void}> = ({data, refres ref={releaseShareModalRef} version={selectedVersion} /> -
+ ); } export default ReleasePage; \ No newline at end of file diff --git a/web/src/views/ApplicationConfig/Statistics.tsx b/web/src/views/ApplicationConfig/Statistics.tsx index 0c2c4b54..5115c4c7 100644 --- a/web/src/views/ApplicationConfig/Statistics.tsx +++ b/web/src/views/ApplicationConfig/Statistics.tsx @@ -1,8 +1,8 @@ /* * @Author: ZhaoYing * @Date: 2026-02-03 16:29:45 - * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-02-03 16:29:45 + * @Last Modified by: ZhaoYing + * @Last Modified time: 2026-03-03 18:57:44 */ import { type FC, useState, useEffect } from 'react'; import { Row, Col, Flex, DatePicker } from 'antd'; @@ -13,7 +13,7 @@ const { RangePicker } = DatePicker; import type { Application } from '@/views/ApplicationManagement/types' import { getAppStatistics } from '@/api/application'; -import LineCard from './components/LineCard' +import ChartCard from './components/ChartCard' import type { StatisticsData, StatisticsItem } from './types' /** @@ -79,11 +79,11 @@ const Statistics: FC<{ application: Application | null }> = ({ application }) => }) } return ( -
+
- + {Object.entries(data).map(([key, value]) => { @@ -93,7 +93,7 @@ const Statistics: FC<{ application: Application | null }> = ({ application }) => const totalKey = TotalObj[key]; return ( - void; /** Default model to pre-select */ defaultModel?: ModelListItem | null; - source?: 'app' | 'skills' + source?: 'application' | 'skills' } /** @@ -185,6 +186,13 @@ const AiPromptModal = forwardRef(({ useImperativeHandle(ref, () => ({ handleOpen, })); + const [isFocus, setIsFocus] = useState(false) + const handleFocus = () => { + setIsFocus(true) + } + const handleBlur = () => { + setIsFocus(false) + } console.log(values) return ( @@ -194,69 +202,107 @@ const AiPromptModal = forwardRef(({ onCancel={handleClose} footer={null} width={1000} + classNames={{ + content: 'rb:p-0!', + header: 'rb:p-6! rb:mb-0!', + body: 'rb:p-0! rb:border-t rb:border-t-[#EBEBEB]' + }} > -
-
-
+ +
+
{ + return data.map(option => ({ + ...data, + value: option.id, + label: (
+ {getLogoUrl(option.logo as string) && } + {option.name as string} +
+ ) + })) + }} + className="rb:w-full" />
} + classNames="rb:h-105.5 rb:pb-[15px]!" + contentClassNames="rb:max-w-75!" + empty={} data={chatList || []} streamLoading={false} labelPosition="top" labelFormat={(item) => item.role === 'user' ? t(`${source}.you`) : t(`${source}.ai`)} /> - -
- + + - -
+ {loading + ?
+ : !values || !values?.message || values?.message?.trim() === '' + ?
+ :
+ } +
-
- - - - - {source === 'application' && - - } - - - form.setFieldValue('current_prompt', value)} - /> +
+ +
+ {t(`${source}.conversationOptimizationPrompt`)} +
+ +
} + onClick={handleCopy}>{t('common.copy')} +
} + onClick={handleApply} + >{t(`${source}.apply`)} + {source === 'application' && +
} + onClick={handleAdd} + > + } + + + + + {values?.current_prompt + ? form.setFieldValue('current_prompt', value)} + /> + : + } -
- - -
diff --git a/web/src/views/ApplicationConfig/components/ApiKeyConfigModal.tsx b/web/src/views/ApplicationConfig/components/ApiKeyConfigModal.tsx index f4751c88..5269c008 100644 --- a/web/src/views/ApplicationConfig/components/ApiKeyConfigModal.tsx +++ b/web/src/views/ApplicationConfig/components/ApiKeyConfigModal.tsx @@ -1,8 +1,8 @@ /* * @Author: ZhaoYing * @Date: 2026-02-03 16:27:22 - * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-02-03 16:27:22 + * @Last Modified by: ZhaoYing + * @Last Modified time: 2026-02-11 11:52:32 */ /** * API Key Configuration Modal @@ -10,7 +10,7 @@ */ import { forwardRef, useImperativeHandle, useState } from 'react'; -import { Form, Slider } from 'antd'; +import { Form, Slider, Flex } from 'antd'; import { useTranslation } from 'react-i18next'; import type { ApiKeyConfigModalRef } from '../types' @@ -111,10 +111,10 @@ interface ApiKeyConfigModalProps { step={1} /> -
+ 1 - {t('application.currentValue')}: {values?.rate_limit}{t('application.qpsLimitUnit')} -
+ {t('application.currentValue')}: {values?.rate_limit} {t('application.qpsLimitUnit')} +
{/* Daily usage limit */} @@ -136,10 +136,10 @@ interface ApiKeyConfigModalProps { step={100} /> -
+ 100 - {t('application.currentValue')}: {values?.daily_request_limit}{t('application.dailyUsageLimitUnit')} -
+ {t('application.currentValue')}: {values?.daily_request_limit} {t('application.dailyUsageLimitUnit')} +
diff --git a/web/src/views/ApplicationConfig/components/Card.tsx b/web/src/views/ApplicationConfig/components/Card.tsx index bf67d970..43f36ac5 100644 --- a/web/src/views/ApplicationConfig/components/Card.tsx +++ b/web/src/views/ApplicationConfig/components/Card.tsx @@ -2,7 +2,7 @@ * @Author: ZhaoYing * @Date: 2026-02-03 16:27:31 * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-02-04 13:50:47 + * @Last Modified time: 2026-03-04 10:25:35 */ import { type FC, type ReactNode } from 'react' @@ -40,8 +40,9 @@ const Card: FC = ({ subTitle={subTitle} extra={extra} variant={variant} - headerType="borderL" - headerClassName={variant ? '' : "rb:before:bg-[#155EEF]! rb:before:h-[19px]"} + headerType="borderless" + headerClassName="rb:h-11.5! rb:py-3! rb:leading-5.5!" + titleClassName="rb:font-[MiSans-Bold] rb:font-bold" > {children} diff --git a/web/src/views/ApplicationConfig/components/ChartCard.tsx b/web/src/views/ApplicationConfig/components/ChartCard.tsx new file mode 100644 index 00000000..3c24ef69 --- /dev/null +++ b/web/src/views/ApplicationConfig/components/ChartCard.tsx @@ -0,0 +1,98 @@ +/* + * @Author: ZhaoYing + * @Date: 2026-02-03 16:28:03 + * @Last Modified by: ZhaoYing + * @Last Modified time: 2026-02-25 13:55:11 + */ +/** + * Line chart card component for displaying statistics + * Uses ECharts to render time-series data with gradient area fill + */ + +import { type FC } from 'react' +import { useTranslation } from 'react-i18next' + +import type { StatisticsItem } from '../types' +import RbCard from '@/components/RbCard/Card'; +import AreaLineChart from '@/components/Charts/AreaLineChart' +import BarChart from '@/components/Charts/BarChart' + +/** + * Component props + */ +interface ChartCardProps { + /** Chart data points */ + chartData: StatisticsItem[]; + /** Statistics type key */ + type: string; + /** Total count to display */ + total: number; +} + +/** + * Color mapping for different statistic types + */ +const ColorObj: Record = { + daily_conversations: '#155EEF', + daily_new_users: '#9C6FFF', + daily_api_calls: '#155EEF', + daily_tokens: '#FF8A4C' +} + +/** + * Line chart card component + * Displays time-series statistics with gradient area chart + */ +const ChartCard: FC = ({ chartData, type, total }) => { + const { t } = useTranslation() + + return ( + {total}} + headerType="borderless" + headerClassName="rb:min-h-26!" + > +
+ {type === 'daily_conversations' || type === 'daily_tokens' ? ( + + ) + : } +
+
+ ) +} + +export default ChartCard diff --git a/web/src/views/ApplicationConfig/components/Chat.tsx b/web/src/views/ApplicationConfig/components/Chat.tsx index 17af7613..be40496c 100644 --- a/web/src/views/ApplicationConfig/components/Chat.tsx +++ b/web/src/views/ApplicationConfig/components/Chat.tsx @@ -10,7 +10,7 @@ * Provides real-time streaming responses and conversation history */ -import { type FC, useEffect, useState, useRef } from 'react'; +import { type FC, useEffect, useState, useRef, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import clsx from 'clsx' import { Flex, Dropdown, type MenuProps, App, Divider } from 'antd' @@ -143,18 +143,16 @@ const Chat: FC = ({ chatList, data, updateChatList, handleSave, sourc const modelChatList = [...prev] const curModelChat = modelChatList[targetIndex] const curChatMsgList = curModelChat.list || [] - const lastMsg = curChatMsgList[curChatMsgList.length - 1] - if (lastMsg.role === 'assistant') { - modelChatList[targetIndex] = { - ...modelChatList[targetIndex], - list: [ - ...curChatMsgList.slice(0, curChatMsgList.length - 1), - { - ...lastMsg, - content: null - } - ] - } + const lastMsg = curChatMsgList[curChatMsgList.length - 2] + modelChatList[targetIndex] = { + ...modelChatList[targetIndex], + list: [ + ...curChatMsgList.slice(0, curChatMsgList.length - 2), + { + ...lastMsg, + ...(lastMsg.role === 'user' ? { status: 'error' } : { content: null }) + } + ] } return [...modelChatList] } @@ -434,62 +432,71 @@ const Chat: FC = ({ chatList, data, updateChatList, handleSave, sourc const updateFileList = (list?: any[]) => { setFileList([...list || []]) } + const isHasLabel = useMemo(() => chatList.some(item => item.label), [chatList]) return ( -
+ {chatList.length === 0 ? - : <> -
- {chatList.map((chat, index) => ( -
1, - })}> - {chat.label && -
-
-
{chat.label}
-
handleDelete(index)} - >
-
+ : <> +
+ {chatList.map((chat, index) => ( + 1, + })}> + {chat.label && +
+
+
{chat.label}
+
handleDelete(index)} + >
- } - } - data={chat.list || []} - streamLoading={compareLoading} - labelPosition="top" - labelFormat={(item) => item.role === 'user' ? t('application.you') : chat.label} - errorDesc={t('application.ReplyException')} - /> -
- ))} -
+
+ } + 1, + "rb:pl-4": index !== 0 && chatList.length > 1, + }} + contentClassNames={{ + 'rb:max-w-100!': chatList.length === 1, + 'rb:max-w-70!': chatList.length === 2, + 'rb:max-w-45!': chatList.length === 3, + 'rb:max-w-24!': chatList.length === 4, + }} + empty={} + data={chat.list || []} + streamLoading={compareLoading} + labelPosition="top" + labelFormat={(item) => item.role === 'user' ? t('application.you') : chat.label || t(`application.ai`)} + errorDesc={t('application.ReplyException')} + /> + + ))} +
= ({ chatList, data, updateChatList, handleSave, sourc - +
- + } -
+
) } diff --git a/web/src/views/ApplicationConfig/components/ConfigHeader.tsx b/web/src/views/ApplicationConfig/components/ConfigHeader.tsx index 42031d85..d6fe315a 100644 --- a/web/src/views/ApplicationConfig/components/ConfigHeader.tsx +++ b/web/src/views/ApplicationConfig/components/ConfigHeader.tsx @@ -2,16 +2,16 @@ * @Author: ZhaoYing * @Date: 2026-02-03 16:27:52 * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-02-28 16:48:52 + * @Last Modified time: 2026-03-04 10:31:08 */ import { type FC, useRef, useMemo } from 'react'; import { useNavigate, useParams } from 'react-router-dom'; -import { Layout, Tabs, Dropdown, Button, Flex } from 'antd'; +import { Tabs, Dropdown, Button, Flex } from 'antd'; import type { MenuProps } from 'antd'; import { useTranslation } from 'react-i18next'; +import clsx from 'clsx'; import styles from '../index.module.css' -import logoutIcon from '@/assets/images/logout.svg' import editIcon from '@/assets/images/edit_hover.svg' import copyIcon from '@/assets/images/copy_hover.svg' import exportIcon from '@/assets/images/export_hover.svg' @@ -21,10 +21,9 @@ import ApplicationModal from '@/views/ApplicationManagement/components/Applicati import type { CopyModalRef, AgentRef, ClusterRef, WorkflowRef } from '../types' import { deleteApplication } from '@/api/application' import CopyModal from './CopyModal' +import PageHeader from '@/components/Layout/PageHeader' import { exportToYaml } from '@/utils/yamlExport'; -const { Header } = Layout; - /** * Tab keys for application configuration */ @@ -93,8 +92,7 @@ const ConfigHeader: FC = ({ copyModalRef.current?.handleOpen() break; case 'export': - console.log('export', workflowRef?.current?.config) - exportToYaml(workflowRef?.current?.config, application?.name ?`${application?.name}.yml`: undefined) + exportToYaml(workflowRef?.current?.config, application?.name ? `${application?.name}.yml` : undefined) break; case 'delete': handleDelete() @@ -153,7 +151,7 @@ const ConfigHeader: FC = ({ * Format dropdown menu items */ const formatMenuItems = useMemo(() => { - const items = (application?.type === 'workflow' ? ['edit', 'copy', 'export', 'delete'] : ['edit', 'copy', 'delete']).map(key => ({ + const items = (application?.type === 'workflow' ? ['edit', 'copy', 'export', 'delete'] : ['edit', 'copy', 'delete']).map(key => ({ key, icon: , label: t(`common.${key}`), @@ -164,49 +162,53 @@ const ConfigHeader: FC = ({ console.log('formatMenuItems', formatMenuItems) return ( <> -
-
-
- {application?.name[0]} -
- -
{application?.name}
- -
-
-
- -
- +
+ } + centerContent={ + -
- {application?.type === 'workflow' - ?
+ } + extra={application?.type === 'workflow' + ? - {/* */} - -
+
+ : -
- - {t('application.returnToApplicationList')} -
+ +
+ {t('common.return')} +
} -
+ > + (({ contentEditable={ } placeholder={ -
+
{placeholder}
} diff --git a/web/src/views/ApplicationConfig/components/Knowledge/Knowledge.tsx b/web/src/views/ApplicationConfig/components/Knowledge/Knowledge.tsx index 7fdf1ab2..7ad3073d 100644 --- a/web/src/views/ApplicationConfig/components/Knowledge/Knowledge.tsx +++ b/web/src/views/ApplicationConfig/components/Knowledge/Knowledge.tsx @@ -1,8 +1,8 @@ /* * @Author: ZhaoYing * @Date: 2026-02-03 16:25:32 - * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-02-03 16:25:32 + * @Last Modified by: ZhaoYing + * @Last Modified time: 2026-03-04 10:34:43 */ /** * Knowledge Base Component @@ -12,7 +12,7 @@ import { type FC, useRef, useState, useEffect } from 'react' import { useTranslation } from 'react-i18next' -import { Space, Button, List } from 'antd' +import { Space, Button, Flex } from 'antd' import knowledgeEmpty from '@/assets/images/application/knowledgeEmpty.svg' import type { @@ -140,44 +140,48 @@ const Knowledge: FC<{value?: KnowledgeConfig; onChange?: (config: KnowledgeConfi title={t('application.knowledgeBaseAssociation')} extra={ - - +
} + onClick={handleKnowledgeConfig} + >{t('application.globalConfig')} + } > +
+ {t('application.associatedKnowledgeBase')} +
+ {knowledgeList.length === 0 - ? - : - { + ?
+ +
+ : + {knowledgeList.map(item => { if (!item.id) return null return ( - -
-
- {item.name} - - {item.status === 1 ? t('common.enable') : item.status === 0 ? t('common.disabled') : t('common.deleted')} - -
{t('application.contains', {include_count: item.doc_num})}
-
- -
handleEditKnowledge(item)} - >
-
handleDeleteKnowledge(item.id)} - >
-
+ +
+ {item.name} + + {item.status === 1 ? t('common.enable') : item.status === 0 ? t('common.disabled') : t('common.deleted')} + +
{t('application.contains', {include_count: item.doc_num})}
- + +
handleEditKnowledge(item)} + >
+
handleDeleteKnowledge(item.id)} + >
+
+
) - }} - /> + })} + } {data && ( -
+
{data.name}
{t('application.contains', {include_count: data.doc_num})}
{formatDateTime(data.updated_at, 'YYYY-MM-DD HH:mm:ss')}
-
+ )}
{/* Result reranking */} -
+
{t('application.rerankModel')}
{t('application.rerankModelDesc')}
@@ -106,7 +106,7 @@ const KnowledgeGlobalConfigModal = forwardRef -
+
{values?.rerank_model && <> (({ const [visible, setVisible] = useState(false); const [list, setList] = useState([]) const [filterList, setFilterList] = useState([]) - const [query, setQuery] = useState<{keywords?: string}>({}) const [selectedIds, setSelectedIds] = useState([]) const [selectedRows, setSelectedRows] = useState([]) + const [form] = Form.useForm() + const query = Form.useWatch([], form) + /** Close modal and reset state */ const handleClose = () => { setVisible(false); - setQuery({}) + form.resetFields() setSelectedIds([]) setSelectedRows([]) }; @@ -57,7 +59,7 @@ const KnowledgeListModal = forwardRef(({ /** Open modal */ const handleOpen = () => { setVisible(true); - setQuery({}) + form.resetFields() setSelectedIds([]) setSelectedRows([]) }; @@ -66,7 +68,7 @@ const KnowledgeListModal = forwardRef(({ if (visible) { getList() } - }, [query.keywords, visible]) + }, [query?.keywords, visible]) /** Fetch knowledge base list */ const getList = () => { getKnowledgeBaseList(undefined, { @@ -77,7 +79,9 @@ const KnowledgeListModal = forwardRef(({ }) .then(res => { const response = res as { items: KnowledgeBaseListItem[] } - setList(response.items || []) + setList([...(response.items || [])]) + setSelectedIds([]) + setSelectedRows([]) }) } /** Save selected knowledge bases */ @@ -99,12 +103,6 @@ const KnowledgeListModal = forwardRef(({ handleOpen, handleClose })); - /** Search knowledge bases */ - const handleSearch = (value?: string) => { - setQuery({keywords: value}) - setSelectedIds([]) - setSelectedRows([]) - } /** Toggle knowledge base selection */ const handleSelect = (item: KnowledgeBase) => { const index = selectedIds.indexOf(item.id) @@ -121,7 +119,7 @@ const KnowledgeListModal = forwardRef(({ if (list.length && selectedList.length) { const unSelectedList = list.filter(item => selectedList.findIndex(vo => vo.id === item.id) < 0) setFilterList([...unSelectedList]) - } else if (list.length) { + } else { setFilterList([...list]) } }, [list, selectedList]) @@ -136,12 +134,15 @@ const KnowledgeListModal = forwardRef(({ onOk={handleSave} width={1000} > - - + +
+ + + +
{filterList.length === 0 ? : (({ dataSource={filterList} renderItem={(item: KnowledgeBase) => ( -
handleSelect(item)}> @@ -158,12 +159,12 @@ const KnowledgeListModal = forwardRef(({
{t('application.contains', {include_count: item.doc_num})}
{formatDateTime(item.created_at, 'YYYY-MM-DD HH:mm:ss')}
-
+
)} /> } - + ); diff --git a/web/src/views/ApplicationConfig/components/LineCard.tsx b/web/src/views/ApplicationConfig/components/LineCard.tsx deleted file mode 100644 index 7a2b7d3c..00000000 --- a/web/src/views/ApplicationConfig/components/LineCard.tsx +++ /dev/null @@ -1,154 +0,0 @@ -/* - * @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 { useTranslation } from 'react-i18next' -import ReactEcharts from 'echarts-for-react'; -import * as echarts from 'echarts'; - -import Empty from '@/components/Empty' -import Card from './Card' -import type { StatisticsItem } from '../types' - -/** - * Component props - */ -interface LineCardProps { - /** Chart data points */ - chartData: StatisticsItem[]; - /** Statistics type key */ - type: string; - /** Total count to display */ - total: number; -} - -/** - * ECharts series configuration - */ -const SeriesConfig = { - type: 'line', - stack: 'Total', - smooth: true, - lineStyle: { - width: 3 - }, - showSymbol: true, - label: { - show: false, - position: 'top' - }, - emphasis: { - focus: 'series' - }, -} - -/** - * Color mapping for different statistic types - */ -const ColorObj: Record = { - daily_conversations: '#FFB048', - daily_new_users: '#4DA8FF', - daily_api_calls: '#155EEF', - daily_tokens: '#AD88FF' -} - -/** - * Line chart card component - * Displays time-series statistics with gradient area chart - */ -const LineCard: FC = ({ chartData, type, total }) => { - const { t } = useTranslation() - const chartRef = useRef(null); - - useEffect(() => { - - }, [chartData]) - - const getSeries = () => { - return [{ - ...SeriesConfig, - name: t(`application.${type}`), - data: chartData.map(vo => vo.count), - areaStyle: { - opacity: 0.8, - color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ - { offset: 0, color: ColorObj[type] }, - { offset: 1, color: '#FFFFFF' } - ]) - }, - }] - } - - return ( - {t(`application.${type}`)} {total}
} - > - {chartData && chartData.length > 0 ? ( - item.date), - boundaryGap: false, - }, - yAxis: { - type: 'value', - axisLabel: { - color: '#A8A9AA', - fontFamily: 'PingFangSC, PingFang SC', - align: 'right', - lineHeight: 17, - }, - axisLine: { - lineStyle: { - color: '#EBEBEB', - } - }, - }, - series: getSeries() - }} - style={{ height: '265px', width: '100%', minWidth: '100%', boxSizing: 'border-box' }} - opts={{ renderer: 'canvas' }} - notMerge={true} - lazyUpdate={true} - /> - ) : } - - ) -} - -export default LineCard diff --git a/web/src/views/ApplicationConfig/components/ReleaseShareModal.tsx b/web/src/views/ApplicationConfig/components/ReleaseShareModal.tsx index f26441fd..b3c8aaa1 100644 --- a/web/src/views/ApplicationConfig/components/ReleaseShareModal.tsx +++ b/web/src/views/ApplicationConfig/components/ReleaseShareModal.tsx @@ -2,7 +2,7 @@ * @Author: ZhaoYing * @Date: 2026-02-03 16:28:46 * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-03-03 14:03:44 + * @Last Modified time: 2026-03-04 10:32:29 */ /** * Release Share Modal @@ -10,7 +10,7 @@ */ import { forwardRef, useImperativeHandle, useState } from 'react'; -import { Button, App } from 'antd'; +import { Button, App, Flex } from 'antd'; import { ExclamationCircleFilled } from '@ant-design/icons'; import { useTranslation } from 'react-i18next'; import copy from 'copy-to-clipboard' @@ -86,13 +86,15 @@ const ReleaseShareModal = forwardRef <>
{t('application.shareLink')}
-
-
{shareLink}
+ +
+ {shareLink} +
-
+ }> {t('application.shareLinkTip')} diff --git a/web/src/views/ApplicationConfig/components/Skill/SkillListModal.tsx b/web/src/views/ApplicationConfig/components/Skill/SkillListModal.tsx index 0a56b82f..667e175b 100644 --- a/web/src/views/ApplicationConfig/components/Skill/SkillListModal.tsx +++ b/web/src/views/ApplicationConfig/components/Skill/SkillListModal.tsx @@ -2,10 +2,10 @@ * @Author: ZhaoYing * @Date: 2026-02-05 10:45:08 * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-02-10 17:59:37 + * @Last Modified time: 2026-03-04 10:41:35 */ import { forwardRef, useEffect, useImperativeHandle, useState } from 'react'; -import { Space, List, Flex, Tooltip } from 'antd'; +import { List, Flex, Tooltip, Form } from 'antd'; import { useTranslation } from 'react-i18next'; import clsx from 'clsx' @@ -31,7 +31,7 @@ interface SkillModalProps { * * A modal dialog for selecting skills from a searchable list. * Features: - * - Search functionality to filter skills by search + * - Search functionality to filter skills by keywords * - Grid layout displaying skill cards with icons and descriptions * - Multi-select capability with visual feedback * - Excludes already selected skills from the list @@ -49,17 +49,19 @@ const SkillListModal = forwardRef(({ const [visible, setVisible] = useState(false); const [list, setList] = useState([]) const [filterList, setFilterList] = useState([]) - const [query, setQuery] = useState<{search?: string}>({}) const [selectedIds, setSelectedIds] = useState([]) const [selectedRows, setSelectedRows] = useState([]) + const [form] = Form.useForm() + const query = Form.useWatch([], form) + /** * Closes the modal and resets all state * Clears search query, selected IDs, and selected rows */ const handleClose = () => { setVisible(false); - setQuery({}) + form.resetFields() setSelectedIds([]) setSelectedRows([]) }; @@ -70,7 +72,7 @@ const SkillListModal = forwardRef(({ */ const handleOpen = () => { setVisible(true); - setQuery({}) + form.resetFields() setSelectedIds([]) setSelectedRows([]) }; @@ -82,7 +84,7 @@ const SkillListModal = forwardRef(({ if (visible) { getList() } - }, [query.search, visible]) + }, [query?.search, visible]) /** * Fetches the skill list from API with current search parameters @@ -96,6 +98,8 @@ const SkillListModal = forwardRef(({ .then(res => { const response = res as { items: Skill[] } setList(response.items || []) + setSelectedIds([]) + setSelectedRows([]) }) } @@ -117,17 +121,6 @@ const SkillListModal = forwardRef(({ handleClose })); - /** - * Handles search input changes and resets selection - * Clears current selections when search query changes - * @param value - Search keyword - */ - const handleSearch = (value?: string) => { - setQuery({search: value}) - setSelectedIds([]) - setSelectedRows([]) - } - /** * Toggles skill selection state * Adds skill to selection if not selected, removes if already selected @@ -154,7 +147,7 @@ const SkillListModal = forwardRef(({ if (list.length && selectedList.length) { const unSelectedList = list.filter(item => selectedList.findIndex(vo => vo.id === item.id) < 0) setFilterList([...unSelectedList]) - } else if (list.length) { + } else { setFilterList([...list]) } }, [list, selectedList]) @@ -169,13 +162,16 @@ const SkillListModal = forwardRef(({ onOk={handleSave} width={1000} > - + {/* Search input for filtering skills */} - +
+ + + +
{/* Display empty state or skill grid */} {filterList.length === 0 ? @@ -191,9 +187,9 @@ const SkillListModal = forwardRef(({ })} onClick={() => handleSelect(item)}> {/* Skill avatar showing first letter of name */} -
+ {item.name[0]} -
+
{/* Skill name and description */}
{item.name}
@@ -207,7 +203,7 @@ const SkillListModal = forwardRef(({ )} /> } - + ); diff --git a/web/src/views/ApplicationConfig/components/Skill/SkillsItem.tsx b/web/src/views/ApplicationConfig/components/Skill/SkillsItem.tsx index 35dee5ec..cbf33e55 100644 --- a/web/src/views/ApplicationConfig/components/Skill/SkillsItem.tsx +++ b/web/src/views/ApplicationConfig/components/Skill/SkillsItem.tsx @@ -2,12 +2,12 @@ * @Author: ZhaoYing * @Date: 2026-02-05 10:43:03 * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-02-05 11:10:01 + * @Last Modified time: 2026-02-25 15:36:14 */ import { type FC, useEffect, useRef } from 'react' import { useTranslation } from 'react-i18next' import { Space, Button, Form, Flex, Tooltip, Checkbox } from 'antd' -import { CloseOutlined, CheckCircleFilled } from '@ant-design/icons' +import { CheckCircleFilled } from '@ant-design/icons' import type { SkillConfigForm, @@ -15,7 +15,6 @@ import type { } from './types' import Empty from '@/components/Empty' import SkillListModal from './SkillListModal' -import Card from '../Card' import RbAlert from '@/components/RbAlert' import type { Skill } from '@/views/Skills/types' @@ -86,20 +85,19 @@ const SkillsItem: FC = ({ }, [allSkills]) return ( - +
+ +
{title}
+ + {/* "Allow all skills" checkbox - only shown if supportAll is true */} {supportAll && - {t('application.allSkill')} + {t('application.allSkill')} } {/* Add skill button - disabled when all skills are enabled */} - + - } - variant="borderL" - > +
{/* Show alert when all skills enabled, otherwise show skill list */} {allSkills ? }>{t('application.allSkillIntro')} @@ -111,39 +109,29 @@ const SkillsItem: FC = ({ ) : ( /* Render list of configured skills */ - + {fields.map((field) => { const skill = form.getFieldValue([...parentName, 'skill_ids', field.name]) return ( /* Individual skill card */ -
+ - {/* Skill icon or fallback initial */} - {skill.icon - ? - :
- {skill.name?.[0]} -
- } {/* Skill name and description */}
-
{skill.name}
+
{skill.name}
-
{skill.description}
+
{skill.description}
- - {/* Remove skill button */} - remove(field.name)} - /> - -
+
remove(field.name)} + >
+
) })} -
+ ) )} @@ -155,7 +143,7 @@ const SkillsItem: FC = ({ selectedList={form.getFieldValue([...parentName, 'skill_ids']) || []} refresh={refresh} /> - +
) } export default SkillsItem \ No newline at end of file diff --git a/web/src/views/ApplicationConfig/components/Skill/index.tsx b/web/src/views/ApplicationConfig/components/Skill/index.tsx index d42edd3d..aac7bb72 100644 --- a/web/src/views/ApplicationConfig/components/Skill/index.tsx +++ b/web/src/views/ApplicationConfig/components/Skill/index.tsx @@ -1,17 +1,18 @@ /* * @Author: ZhaoYing * @Date: 2026-02-05 10:42:56 - * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-02-05 10:42:56 + * @Last Modified by: ZhaoYing + * @Last Modified time: 2026-02-26 10:18:56 */ import { useEffect, type FC } from 'react' import { useTranslation } from 'react-i18next' -import { Space, Button, Switch, Form, Flex } from 'antd' +import { Space, Switch, Form, Flex } from 'antd' +import clsx from 'clsx' import type { SkillConfigForm, } from './types' -import Card from '../Card' +import RbCard from '@/components/RbCard/Card' import SkillsItem from './SkillsItem' import { getSkillList } from '@/api/skill' import type { Skill } from '@/views/Skills/types' @@ -94,15 +95,15 @@ const SkillList: FC<{value?: SkillConfigForm; onChange?: (config: SkillConfigFor return ( - - {t('application.skill')} - ({t('application.skillTitle')}) +
{t('application.skill')}
+
{t('application.skillTitle')}
} extra={ {/* Help button for skill configuration guidance */} - + {/* */} {/* Toggle switch to enable/disable skill functionality */} } + headerType="borderless" + headerClassName={clsx("rb:py-[16px]! rb:leading-[22px]! rb:font-regular", { + 'rb:h-[76px]! rb:py-[16px]!': !skillConfig?.enabled, + 'rb:h-[68px]! rb:pb-2!': skillConfig?.enabled, + })} > {/* Render skill configuration UI only when enabled */} - {skillConfig?.enabled && + {skillConfig?.enabled && +
{t('application.executeProcessPreview')}
+ + {/* Render each step in the process flow with numbered badges */} + {processObj.map((key, index) => (<> + + {/* Step number badge */} + {index + 1} + {/* Step label */} + {t(`application.${key}`)} + + {/* Arrow separator between steps (except after last step) */} + {index !== processObj.length - 1 &&
} + ))} +
{/* Dynamic skill binding configuration section */} - {/* Execution process preview card showing workflow steps */} - - - {/* Render each step in the process flow with numbered badges */} - {processObj.map((key, index) => (<> - - {/* Step number badge */} -
{index + 1}
- {/* Step label */} - {t(`application.${key}`)} -
- {/* Arrow separator between steps (except after last step) */} - {index !== processObj.length - 1 &&
} - ))} -
-
} -
+ ) } export default SkillList \ No newline at end of file diff --git a/web/src/views/ApplicationConfig/components/Tag.tsx b/web/src/views/ApplicationConfig/components/Tag.tsx index 16659c8b..ba043baf 100644 --- a/web/src/views/ApplicationConfig/components/Tag.tsx +++ b/web/src/views/ApplicationConfig/components/Tag.tsx @@ -16,7 +16,7 @@ import { type FC, type ReactNode } from 'react' */ export interface TagProps { /** Tag color scheme */ - color?: 'processing' | 'warning' | 'default' | 'success'; + color?: 'processing' | 'warning' | 'default' | 'success' | 'dark'; /** Tag content */ children: ReactNode; /** Additional CSS classes */ @@ -31,6 +31,7 @@ const colors = { warning: 'rb:text-[#FF5D34] rb:border-[rgba(255,93,52,0.08)] rb:bg-[rgba(255,93,52,0.08)]', default: 'rb:text-[#5B6167] rb:border-[rgba(91,97,103,0.30)] rb:bg-[rgba(91,97,103,0.08)]', success: 'rb:text-[#369F21] rb:border-[rgba(54,159,33,0.30)] rb:bg-[rgba(54,159,33,0.08)]', + dark: 'rb:text-[#171719] rb:border-[#171719] rb:bg-transparent' } /** diff --git a/web/src/views/ApplicationConfig/components/ToolList/ToolList.tsx b/web/src/views/ApplicationConfig/components/ToolList/ToolList.tsx index 1a093b6e..f48af77a 100644 --- a/web/src/views/ApplicationConfig/components/ToolList/ToolList.tsx +++ b/web/src/views/ApplicationConfig/components/ToolList/ToolList.tsx @@ -1,8 +1,8 @@ /* * @Author: ZhaoYing * @Date: 2026-02-03 16:26:03 - * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-02-03 16:26:03 + * @Last Modified by: ZhaoYing + * @Last Modified time: 2026-03-04 10:15:39 */ /** * Tool List Component @@ -12,7 +12,7 @@ import { type FC, useRef, useState, useEffect } from 'react' import { useTranslation } from 'react-i18next' -import { Space, Button, List, Switch } from 'antd' +import { Space, Button, Switch, Flex } from 'antd' import Card from '../Card' import type { @@ -131,32 +131,30 @@ const ToolList: FC<{ value?: ToolOption[]; onChange?: (config: ToolOption[]) => + {t('application.addTool')} + } > +
+ {t('application.toolManagement')} +
{toolList.length === 0 - ? - : - ( - -
-
- {item.label} -
- -
handleDeleteTool(index)} - >
- handleChangeEnabled(index)} /> -
-
-
- )} - /> + ?
+ : + {toolList.map((item, index) => ( + +
+ {item.label} +
+ + handleChangeEnabled(index)} /> +
handleDeleteTool(index)} + >
+
+
+ ))} +
}
{props.label} {variableType[props.value as keyof typeof variableType]}
} - optionRender={(props) =>
{props.label} {variableType[props.value as keyof typeof variableType]}
} + labelRender={(props) => {props.label} {variableType[props.value as keyof typeof variableType]}} + optionRender={(props) => {props.label} {variableType[props.value as keyof typeof variableType]}} /> {/* Variable Name */} diff --git a/web/src/views/ApplicationConfig/index.module.css b/web/src/views/ApplicationConfig/index.module.css index 2e782dd5..3b3a5316 100644 --- a/web/src/views/ApplicationConfig/index.module.css +++ b/web/src/views/ApplicationConfig/index.module.css @@ -7,8 +7,8 @@ } .tabs :global(.ant-tabs-tab) { line-height: 20px; - padding-bottom: 18px; - padding-top: 10px; + padding-bottom: 22px; + padding-top: 22px; } .tabs :global(.ant-tabs-tab-active) { font-weight: 500; diff --git a/web/src/views/ApplicationConfig/index.tsx b/web/src/views/ApplicationConfig/index.tsx index df5dbd59..f72ea294 100644 --- a/web/src/views/ApplicationConfig/index.tsx +++ b/web/src/views/ApplicationConfig/index.tsx @@ -1,8 +1,8 @@ /* * @Author: ZhaoYing * @Date: 2026-02-03 16:29:37 - * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-02-03 16:29:37 + * @Last Modified by: ZhaoYing + * @Last Modified time: 2026-03-03 18:57:36 */ import React, { useEffect, useState, useRef } from 'react'; import { useParams } from 'react-router-dom'; @@ -88,12 +88,14 @@ const ApplicationConfig: React.FC = () => { appRef={application?.type === 'agent' ? agentRef : application?.type === 'multi_agent' ? clusterRef : application?.type === 'workflow' ? workflowRef : undefined} workflowRef={workflowRef} /> - {activeTab === 'arrangement' && application?.type === 'agent' && } - {activeTab === 'arrangement' && application?.type === 'multi_agent' && } - {activeTab === 'arrangement' && application?.type === 'workflow' && } - {activeTab === 'api' && } - {activeTab === 'release' && } - {activeTab === 'statistics' && } +
+ {activeTab === 'arrangement' && application?.type === 'agent' && } + {activeTab === 'arrangement' && application?.type === 'multi_agent' && } + {activeTab === 'arrangement' && application?.type === 'workflow' && } + {activeTab === 'api' && } + {activeTab === 'release' && } + {activeTab === 'statistics' && } +
); }; diff --git a/web/src/views/ApplicationConfig/types.ts b/web/src/views/ApplicationConfig/types.ts index 36d40a40..62232cbc 100644 --- a/web/src/views/ApplicationConfig/types.ts +++ b/web/src/views/ApplicationConfig/types.ts @@ -1,8 +1,8 @@ /* * @Author: ZhaoYing * @Date: 2026-02-03 16:29:49 - * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-02-28 16:40:30 + * @Last Modified by: ZhaoYing + * @Last Modified time: 2026-03-03 18:55:57 */ import type { KnowledgeConfig } from './components/Knowledge/types' import type { Variable } from './components/VariableList/types' @@ -378,6 +378,8 @@ export interface StatisticsItem { count: number; /** Date string */ date: string; + /** Index signature for compatibility with ChartData */ + [key: string]: string | number; } /** diff --git a/web/src/views/ApplicationManagement/index.tsx b/web/src/views/ApplicationManagement/index.tsx index 055c0c8f..c8708656 100644 --- a/web/src/views/ApplicationManagement/index.tsx +++ b/web/src/views/ApplicationManagement/index.tsx @@ -2,7 +2,7 @@ * @Author: ZhaoYing * @Date: 2026-02-03 16:34:12 * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-03-02 17:48:51 + * @Last Modified time: 2026-03-04 10:44:29 */ /** * Application Management Page @@ -10,21 +10,22 @@ * Supports creating, editing, and deleting applications */ -import React, { useState, useRef, useEffect } from 'react'; +import React, { useRef, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; -import { Button, Row, Col, App, Select, Space, Dropdown } from 'antd'; +import { App, Select, Space, Form, Flex, Dropdown, Button } from 'antd'; import clsx from 'clsx'; -import { DeleteOutlined } from '@ant-design/icons'; import { useSearchParams } from 'react-router-dom' import ApplicationModal, { types } from './components/ApplicationModal'; import type { Application, ApplicationModalRef, Query, UploadWorkflowModalRef } from './types'; import SearchInput from '@/components/SearchInput' -import RbCard from '@/components/RbCard/Card' import { getApplicationListUrl, deleteApplication } from '@/api/application' import PageScrollList, { type PageScrollListRef } from '@/components/PageScrollList' import { formatDateTime } from '@/utils/format'; import UploadWorkflowModal from './components/UploadWorkflowModal' +import RbCard from '@/components/RbCard' +import RbButton from '@/components/RbButton' +import RbDescriptions from '@/components/RbDescriptions' /** * Application management main component @@ -33,19 +34,19 @@ const ApplicationManagement: React.FC = () => { const { t } = useTranslation(); const { modal } = App.useApp(); const [searchParams] = useSearchParams() - const [query, setQuery] = useState({} as Query); const applicationModalRef = useRef(null); const scrollListRef = useRef(null) const uploadWorkflowModalRef = useRef(null); + const [form] = Form.useForm() + const query = Form.useWatch([], form) + useEffect(() => { // Convert URLSearchParams to a plain object for easier access const data = Object.fromEntries(searchParams) const { type } = data - setQuery(prev => ({ - ...prev, - type: type || undefined - })) + + form.setFieldValue('type', type || null) }, [searchParams]) /** Refresh application list */ @@ -79,14 +80,11 @@ const ApplicationManagement: React.FC = () => { } }) } - const handleChangeType = (value?: string) => { - setQuery(prev => ({...prev, type: value})) - } const handleImport = () => { uploadWorkflowModalRef.current?.handleOpen() } - const handleClick = ({ key }: { key: string } ) => { + const handleClick = ({ key }: { key: string }) => { switch (key) { case 'thirdParty': handleImport() @@ -95,45 +93,48 @@ const ApplicationManagement: React.FC = () => { } return ( <> - - - ({ + value: type, + label: t(`application.${type}`), + })) + ]} + className="rb:w-30!" + /> + + + + + + -
} onClick={handleCreate}> {t('application.createApplication')} - +
- - + + ref={scrollListRef} @@ -142,20 +143,23 @@ const ApplicationManagement: React.FC = () => { renderItem={(item) => ( - {item.name[0]} -
- } + avatarText={item.name.trim()[0]} + avatarClassName={clsx({ + 'rb:bg-[#155EEF]': item.type === 'agent', + 'rb:bg-[#9C6FFF]!': item.type === 'multi_agent', + 'rb:bg-[#171719]': item.type === 'workflow', + })} + footer={ + handleDelete(item)}>{t('common.delete')} + handleEdit(item)}>{t('application.configuration')} + } > - {['type', 'source', 'created_at'].map((key, index) => ( -
- {t(`application.${key}`)} - ({ + key, + label: t(`application.${key}`), + children: {key === 'source' && item.is_shared ? t('application.shared') @@ -166,13 +170,8 @@ const ApplicationManagement: React.FC = () => { : t(`application.${item[key as keyof Application]}`) } -
- ))} - -
- - -
+ }))} + /> )} />