+
{t('application.publishedOn')} {formatDateTime(version.published_at, 'YYYY-MM-DD HH:mm:ss')}
-
+
{t('application.publisher')}: {version.publisher_name}
)
})
}
-
+
-
+
+
);
}
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]'
+ }}
>
-
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 (
<>
-
+ >
+
(({
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')}
-
+
)}
{/* Retrieval mode */}
diff --git a/web/src/views/ApplicationConfig/components/Knowledge/KnowledgeGlobalConfigModal.tsx b/web/src/views/ApplicationConfig/components/Knowledge/KnowledgeGlobalConfigModal.tsx
index a09f1d05..0ceb13ff 100644
--- a/web/src/views/ApplicationConfig/components/Knowledge/KnowledgeGlobalConfigModal.tsx
+++ b/web/src/views/ApplicationConfig/components/Knowledge/KnowledgeGlobalConfigModal.tsx
@@ -1,8 +1,8 @@
/*
* @Author: ZhaoYing
* @Date: 2026-02-03 16:25:42
- * @Last Modified by: ZhaoYing
- * @Last Modified time: 2026-02-03 16:25:42
+ * @Last Modified by: ZhaoYing
+ * @Last Modified time: 2026-02-24 11:47:32
*/
/**
* Knowledge Global Configuration Modal
@@ -10,7 +10,7 @@
*/
import { forwardRef, useImperativeHandle, useState, useEffect } from 'react';
-import { Form, InputNumber, Switch } from 'antd';
+import { Form, InputNumber, Switch, Flex } from 'antd';
import { useTranslation } from 'react-i18next';
import type { RerankerConfig, KnowledgeGlobalConfigModalRef } from './types'
@@ -94,7 +94,7 @@ const KnowledgeGlobalConfigModal = forwardRef{t('application.globalConfigDesc')}
{/* 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 (
<>
-
-
-
} 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]}`)
}
-
- ))}
-
-
-
- } onClick={() => handleDelete(item)}>
-
+ }))}
+ />
)}
/>