Merge pull request #803 from SuanmoSuanyangTechnology/feature/ui_upgrade_zy

Feature/UI upgrade zy
This commit is contained in:
yingzhao
2026-04-07 20:40:16 +08:00
committed by GitHub
8 changed files with 127 additions and 104 deletions

View File

@@ -2,7 +2,7 @@
* @Author: ZhaoYing * @Author: ZhaoYing
* @Date: 2026-02-03 16:29:33 * @Date: 2026-02-03 16:29:33
* @Last Modified by: ZhaoYing * @Last Modified by: ZhaoYing
* @Last Modified time: 2026-03-27 18:14:25 * @Last Modified time: 2026-04-07 20:37:43
*/ */
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'
@@ -115,7 +115,8 @@ const Cluster = forwardRef<ClusterRef, { onFeaturesLoad?: (features: FeaturesCon
console.log({ ids: sub_agents?.map(item => item.agent_id) }) console.log({ ids: sub_agents?.map(item => item.agent_id) })
getApplicationList({ ids: sub_agents?.map(item => item.agent_id).join(',')}) getApplicationList({ ids: sub_agents?.map(item => item.agent_id).join(',')})
.then(res => { .then(res => {
const applicationList = (res as Application[]) || [] const applicationList = ((res as { items: Application[] }).items) || []
setSubAgents(sub_agents.map(vo => { setSubAgents(sub_agents.map(vo => {
const filterVO = applicationList.find(item => item.id === vo.agent_id) const filterVO = applicationList.find(item => item.id === vo.agent_id)
if (filterVO) { if (filterVO) {
@@ -194,6 +195,9 @@ const Cluster = forwardRef<ClusterRef, { onFeaturesLoad?: (features: FeaturesCon
// form.setFieldValue('features', value) // form.setFieldValue('features', value)
// } // }
console.log('subAgents', subAgents)
return ( return (
<> <>
{loading && <Spin fullscreen></Spin>} {loading && <Spin fullscreen></Spin>}

View File

@@ -2,7 +2,7 @@
* @Author: ZhaoYing * @Author: ZhaoYing
* @Date: 2026-03-13 17:27:52 * @Date: 2026-03-13 17:27:52
* @Last Modified by: ZhaoYing * @Last Modified by: ZhaoYing
* @Last Modified time: 2026-04-07 18:08:18 * @Last Modified time: 2026-04-07 20:25:45
*/ */
import { type FC, useState, useRef, useEffect } from 'react' import { type FC, useState, useRef, useEffect } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
@@ -109,7 +109,7 @@ const TestChat: FC<TestChatProps> = ({
setFeatures(config?.features || {} as FeaturesConfigForm) setFeatures(config?.features || {} as FeaturesConfigForm)
if (config?.features?.opening_statement.enabled && config?.features?.opening_statement?.statement && config?.features?.opening_statement?.statement.trim() !== '') { if (config?.features?.opening_statement?.enabled && config?.features?.opening_statement?.statement && config?.features?.opening_statement?.statement.trim() !== '') {
setChatList(prev => [...prev, { setChatList(prev => [...prev, {
role: 'assistant', role: 'assistant',
created_at: Date.now(), created_at: Date.now(),

View File

@@ -562,83 +562,88 @@ const KnowledgeBaseManagement: FC = () => {
{data.length === 0 && !loading ? ( {data.length === 0 && !loading ? (
<Empty size={200} /> <Empty size={200} />
) : ( ) : (
<div style={{ columns: '3 280px', columnGap: 12, marginBottom: 8 }}> <Flex align="flex-start" gap={12} className="rb:mb-2!">
{data.map((item) => { {[0, 1, 2].map(colIdx => (
const modelInfo = modelMenus[item.id]; <div key={colIdx} style={{ flex: 1, minWidth: 0, display: 'flex', flexDirection: 'column', gap: 12 }}>
const hasModelInfo = modelInfo && modelInfo.menu.length > 1; {data.filter((_, i) => i % 3 === colIdx).map((item) => {
return ( const modelInfo = modelMenus[item.id];
<div key={item.id} className="rb:break-inside-avoid rb:mb-3"> const hasModelInfo = modelInfo && modelInfo.menu.length > 1;
<RbCard return (
title={item.name} <div key={item.id}>
headerType="borderless" <RbCard
headerClassName="rb:py-3!" title={item.name}
extra={ headerType="borderless"
<div onClick={(e) => e.stopPropagation()}> headerClassName="rb:py-3!"
<Dropdown extra={
menu={{ items: getOptMenuItems(item) }} <div onClick={(e) => e.stopPropagation()}>
placement="bottomRight" <Dropdown
> menu={{ items: getOptMenuItems(item) }}
<div onClick={(e) => e.stopPropagation()} className="rb:cursor-pointer rb:size-5.5 rb:bg-[url('@/assets/images/common/more.svg')] rb:hover:bg-[url('@/assets/images/common/more_hover.svg')]"></div> placement="bottomRight"
</Dropdown> >
</div> <div onClick={(e) => e.stopPropagation()} className="rb:cursor-pointer rb:size-5.5 rb:bg-[url('@/assets/images/common/more.svg')] rb:hover:bg-[url('@/assets/images/common/more_hover.svg')]"></div>
} </Dropdown>
>
<div className='' onClick={() => handleToDetail(item)}>
<div className="rb:flex rb:text-[#5B6167] rb:h-5 rb:line-clamp-1 rb:text-sm rb:leading-5 rb:mb-3">
{/* <div className="rb:font-medium rb:w-20">{t('knowledgeBase.description')} </div> */}
<Tooltip title={item.description}>
<div className='rb:flex-1 rb:text-left rb:leading-5 rb:text-gray-800 rb:wrap-break-word rb:line-clamp-2'>{(item.description && item.description != '') ? item.description : t('knowledgeBase.noDescription')}</div>
</Tooltip>
</div>
<Flex vertical gap={4} className='rb:min-h-15 rb:py-2.5! rb:px-3! rb:bg-[#F6F6F6] rb:rounded-lg rb:mb-3'>
{item.descriptionItems?.map((description: Record<string, unknown>) => (
<div
key={description.key as string}
className="rb:grid rb:grid-cols-2 rb:text-[#5B6167] rb:text-[14px] rb:leading-5"
>
<div className={clsx('rb:whitespace-nowrap rb:w-20', {"rb:text-gray-800 rb:font-medium" : (description.key as string) === 'permission_id'})}>{(description.label as string)}</div>
<div className={clsx('rb:flex-inline rb:text-left rb:py-px rb:rounded',{
"rb:text-[#155eef] rb:font-medium": (description.key as string) === 'permission_id' && (description.children as string) === t('knowledgeBase.private'),
"rb:text-[#FF8A4C] rb:font-medium": (description.key as string) === 'permission_id' && (description.children as string) === t('knowledgeBase.share'),
})}>{(description.children as string)}</div>
</div>
))}
</Flex>
{hasModelInfo && (
<div onClick={(e) => e.stopPropagation()}>
<div
className="rb:flex rb:items-center rb:pt-2 rb:px-2 rb:text-[12px] rb:leading-5 rb:cursor-pointer rb:rounded rb:transition-colors"
onClick={() => {
setData(prev => prev.map(d => d.id === item.id ? { ...d, _expanded: !d._expanded } : d));
}}
>
{/* <span className='rb:text-gray-500'>{t('knowledgeBase.models')}:</span> */}
<span className="rb:ml-1 rb:truncate rb:flex-1 rb:text-gray-500">
{modelInfo.summary[0].split(':')[0]}:<span className="rb:text-gray-900">{modelInfo.summary[0].split(':').slice(1).join(':')}</span>
</span>
<span className="rb:ml-auto rb:text-gray-400 rb:text-[10px]">
{item._expanded ? <DownOutlined /> : <RightOutlined />}
</span>
</div>
{item._expanded && (
<div className="rb:py-1 rb:px-2 rb:text-[12px]">
{modelInfo.summary.slice(1).map((text, idx) => {
const [label, value] = text.split(':');
return (
<div key={idx} className="rb:py-1 rb:text-gray-500">
{label}:<span className="rb:text-gray-900">{value}</span>
</div>
);
})}
</div> </div>
)} }
</div> >
)} <div className='' onClick={() => handleToDetail(item)}>
</div> <div className="rb:flex rb:text-[#5B6167] rb:h-5 rb:line-clamp-1 rb:text-sm rb:leading-5 rb:mb-3">
</RbCard> {/* <div className="rb:font-medium rb:w-20">{t('knowledgeBase.description')} </div> */}
<Tooltip title={item.description}>
<div className='rb:flex-1 rb:text-left rb:leading-5 rb:text-gray-800 rb:wrap-break-word rb:line-clamp-2'>{(item.description && item.description != '') ? item.description : t('knowledgeBase.noDescription')}</div>
</Tooltip>
</div>
<Flex vertical gap={4} className='rb:min-h-15 rb:py-2.5! rb:px-3! rb:bg-[#F6F6F6] rb:rounded-lg rb:mb-3'>
{item.descriptionItems?.map((description: Record<string, unknown>) => (
<div
key={description.key as string}
className="rb:grid rb:grid-cols-2 rb:text-[#5B6167] rb:text-[14px] rb:leading-5"
>
<div className={clsx('rb:whitespace-nowrap rb:w-20', {"rb:text-gray-800 rb:font-medium" : (description.key as string) === 'permission_id'})}>{(description.label as string)}</div>
<div className={clsx('rb:flex-inline rb:text-left rb:py-px rb:rounded',{
"rb:text-[#155eef] rb:font-medium": (description.key as string) === 'permission_id' && (description.children as string) === t('knowledgeBase.private'),
"rb:text-[#FF8A4C] rb:font-medium": (description.key as string) === 'permission_id' && (description.children as string) === t('knowledgeBase.share'),
})}>{(description.children as string)}</div>
</div>
))}
</Flex>
{hasModelInfo && (
<div onClick={(e) => e.stopPropagation()}>
<div
className="rb:flex rb:items-center rb:pt-2 rb:px-2 rb:text-[12px] rb:leading-5 rb:cursor-pointer rb:rounded rb:transition-colors"
onClick={() => {
setData(prev => prev.map(d => d.id === item.id ? { ...d, _expanded: !d._expanded } : d));
}}
>
{/* <span className='rb:text-gray-500'>{t('knowledgeBase.models')}:</span> */}
<span className="rb:ml-1 rb:truncate rb:flex-1 rb:text-gray-500">
{modelInfo.summary[0].split(':')[0]}:<span className="rb:text-gray-900">{modelInfo.summary[0].split(':').slice(1).join(':')}</span>
</span>
<span className="rb:ml-auto rb:text-gray-400 rb:text-[10px]">
{item._expanded ? <DownOutlined /> : <RightOutlined />}
</span>
</div>
{item._expanded && (
<div className="rb:py-1 rb:px-2 rb:text-[12px]">
{modelInfo.summary.slice(1).map((text, idx) => {
const [label, value] = text.split(':');
return (
<div key={idx} className="rb:py-1 rb:text-gray-500">
{label}:<span className="rb:text-gray-900">{value}</span>
</div>
);
})}
</div>
)}
</div>
)}
</div>
</RbCard>
</div>
)
})}
</div> </div>
)})} ))}
</div> </Flex>
)} )}
</InfiniteScroll> </InfiniteScroll>

View File

@@ -2,7 +2,7 @@
* @Author: ZhaoYing * @Author: ZhaoYing
* @Date: 2025-12-30 13:59:36 * @Date: 2025-12-30 13:59:36
* @Last Modified by: ZhaoYing * @Last Modified by: ZhaoYing
* @Last Modified time: 2026-04-02 19:01:12 * @Last Modified time: 2026-04-07 20:17:59
*/ */
import { forwardRef, useImperativeHandle, useState, useRef, useMemo } from 'react'; import { forwardRef, useImperativeHandle, useState, useRef, useMemo } from 'react';
import { Form, Input, Select, InputNumber, Button, Row, Col, Flex, Spin } from 'antd'; import { Form, Input, Select, InputNumber, Button, Row, Col, Flex, Spin } from 'antd';
@@ -135,7 +135,10 @@ const ChatVariableModal = forwardRef<ChatVariableModalRef, ChatVariableModalProp
const handleSave = () => { const handleSave = () => {
form.validateFields().then((values) => { form.validateFields().then((values) => {
refresh({ ...values, default: values.defaultValue }, editIndex); const defaultValue = Array.isArray(values.defaultValue)
? values.defaultValue.filter((v: any) => v !== undefined && v !== null && v !== '')
: values.defaultValue;
refresh({ ...values, defaultValue }, editIndex);
handleClose(); handleClose();
}); });
}; };

View File

@@ -2,7 +2,7 @@
* @Author: ZhaoYing * @Author: ZhaoYing
* @Date: 2026-02-03 15:40:13 * @Date: 2026-02-03 15:40:13
* @Last Modified by: ZhaoYing * @Last Modified by: ZhaoYing
* @Last Modified time: 2026-04-03 20:19:34 * @Last Modified time: 2026-04-07 19:40:27
*/ */
import { useState, useRef, useEffect, useLayoutEffect, type FC } from 'react' import { useState, useRef, useEffect, useLayoutEffect, type FC } from 'react'
import { createPortal } from 'react-dom' import { createPortal } from 'react-dom'
@@ -232,15 +232,16 @@ const VariableSelect: FC<VariableSelectProps> = ({
<span className="rb:text-[#bfbfbf] rb:flex-1 rb:text-[12px]">{placeholder}</span> <span className="rb:text-[#bfbfbf] rb:flex-1 rb:text-[12px]">{placeholder}</span>
) )
) : selectedSuggestion ? ( ) : selectedSuggestion ? (
<span className="rb:flex rb:flex-1 rb:min-w-0"> <div className="rb:flex rb:flex-1 rb:min-w-0 rb:max-w-full">
<span className="rb:inline-flex rb:items-center rb:gap-0.5 rb:bg-[#f0f8ff] rb:rounded rb:px-1 rb:py-0.5 rb:text-[11px] rb:max-w-full"> <span className="rb:inline-flex rb:items-center rb:gap-0.5 rb:bg-[#f0f8ff] rb:rounded rb:px-1 rb:py-0.5 rb:text-[11px] rb:max-w-full rb:overflow-hidden">
{!isConversation && nodeData?.icon && <div className={`rb:size-3 rb:shrink-0 rb:bg-cover ${nodeData.icon}`} />} {!isConversation && nodeData?.icon && <div className={`rb:size-3 rb:shrink-0 rb:bg-cover ${nodeData.icon}`} />}
{!isConversation && nodeData?.name && <span className="rb:text-[#5B6167]">{nodeData.name}{sep}</span>} {!isConversation && nodeData?.name && <span className="rb:text-[#5B6167] rb:shrink rb:min-w-0 rb:truncate rb:max-w-[40%]">{nodeData.name}</span>}
<span className="rb:text-[#171719]"> {!isConversation && nodeData?.name && <span className="rb:text-[#5B6167]">{sep}</span>}
<span className="rb:text-[#171719] rb:shrink rb:min-w-0 rb:truncate">
{parentOfSelected ? <>{parentOfSelected.label}{sep}{selectedSuggestion.label}</> : selectedSuggestion.label} {parentOfSelected ? <>{parentOfSelected.label}{sep}{selectedSuggestion.label}</> : selectedSuggestion.label}
</span> </span>
</span> </span>
</span> </div>
) : ( ) : (
<span className="rb:text-[#bfbfbf] rb:flex-1">{placeholder}</span> <span className="rb:text-[#bfbfbf] rb:flex-1">{placeholder}</span>
)} )}
@@ -264,7 +265,7 @@ const VariableSelect: FC<VariableSelectProps> = ({
{open && createPortal( {open && createPortal(
<div <div
ref={dropdownRef} ref={dropdownRef}
className="rb:fixed rb:z-9999 rb:bg-white rb:text-[14px] rb:rounded-xl rb:shadow-[0px_2px_12px_0px_rgba(23,23,25,0.12)]" className="rb:fixed rb:z-9999 rb:bg-white rb:text-[14px] rb:rounded-lg rb:shadow-[0px_2px_12px_0px_rgba(23,23,25,0.12)] rb:p-1"
style={{ top: dropdownPos.top, left: dropdownPos.left, minWidth: dropdownPos.width }} style={{ top: dropdownPos.top, left: dropdownPos.left, minWidth: dropdownPos.width }}
> >
<div className="rb:min-w-70 rb:max-h-60 rb:overflow-y-auto rb:py-1"> <div className="rb:min-w-70 rb:max-h-60 rb:overflow-y-auto rb:py-1">
@@ -272,8 +273,8 @@ const VariableSelect: FC<VariableSelectProps> = ({
const nd = suggestions[0].nodeData; const nd = suggestions[0].nodeData;
return ( return (
<div key={nodeId}> <div key={nodeId}>
<Flex align="center" gap={4} className="rb:px-3! rb:py-1.25! rb:text-[12px] rb:font-medium rb:text-[#5B6167]"> <Flex align="center" gap={4} className="rb:px-3! rb:py-1.25! rb:text-[12px] rb:text-[#5B6167]">
{nd.icon && <div className={`rb:size-3 rb:bg-cover ${nd.icon}`} />} {nd.icon && <div className={`rb:size-4 rb:bg-cover ${nd.icon}`} />}
{nd.name} {nd.name}
</Flex> </Flex>
{suggestions.map(s => { {suggestions.map(s => {
@@ -286,14 +287,15 @@ const VariableSelect: FC<VariableSelectProps> = ({
<Flex <Flex
key={s.key} key={s.key}
ref={(el) => { if (el) itemRefs.current.set(s.key, el); }} ref={(el) => { if (el) itemRefs.current.set(s.key, el); }}
className="rb:mx-3! rb:pl-3! rb:pr-3! rb:py-1.5! rb:rounded-lg!" className={clsx("rb:pl-6! rb:pr-3! rb:py-1.25! rb:rounded-lg!", {
'rb:bg-[#e6f4ff]': isSelected || isExpanded,
'rb:bg-white rb:hover:bg-[#F6F6F6]!': !(isSelected || isExpanded),
'rb:opacity-60': s.disabled,
'rb:cursor-not-allowed': s.disabled,
'rb:cursor-pointer': !s.disabled,
})}
align="center" align="center"
justify="space-between" justify="space-between"
style={{
cursor: s.disabled ? 'not-allowed' : 'pointer',
background: isSelected || isExpanded ? '#f0f8ff' : 'white',
opacity: s.disabled ? 0.5 : 1,
}}
onClick={() => { onClick={() => {
if (s.disabled) return; if (s.disabled) return;
if (hasChildren) { if (hasChildren) {

View File

@@ -2,7 +2,7 @@
* @Author: ZhaoYing * @Author: ZhaoYing
* @Date: 2026-01-19 17:00:26 * @Date: 2026-01-19 17:00:26
* @Last Modified by: ZhaoYing * @Last Modified by: ZhaoYing
* @Last Modified time: 2026-04-02 16:58:40 * @Last Modified time: 2026-04-07 20:33:26
*/ */
/** /**
* useVariableList Hook * useVariableList Hook
@@ -125,7 +125,7 @@ const processNodeVariables = (
if (type in NODE_VARIABLES) { if (type in NODE_VARIABLES) {
if (type === 'list-operator') { if (type === 'list-operator') {
// Determine output type from the first variable in config // Determine output type from the first variable in config
const variableValue = config?.variable; const variableValue = config?.input_list?.defaultValue;
let itemType = 'string'; let itemType = 'string';
if (variableValue) { if (variableValue) {
const refVar = variableList.find(v => `{{${v.value}}}` === variableValue); const refVar = variableList.find(v => `{{${v.value}}}` === variableValue);
@@ -321,7 +321,6 @@ export const getChildNodeVariables = (
if (p?.name) addVariable(list, keys, `${nodeId}_${p.name}`, p.name, p.type || 'string', `${nodeId}.${p.name}`, nodeData); if (p?.name) addVariable(list, keys, `${nodeId}_${p.name}`, p.name, p.type || 'string', `${nodeId}.${p.name}`, nodeData);
}); });
} }
// Add code node variables // Add code node variables
if (type === 'code') { if (type === 'code') {
(nodeData.config?.output_variables?.defaultValue || []).forEach((p: any) => { (nodeData.config?.output_variables?.defaultValue || []).forEach((p: any) => {
@@ -393,8 +392,18 @@ export const useVariableList = (
// Add chat variables // Add chat variables
chatVariables?.forEach(v => addVariable(list, keys, `CONVERSATION_${v.name}`, v.name, v.type, `conv.${v.name}`, { type: 'CONVERSATION', name: 'CONVERSATION', icon: '' }, { group: 'CONVERSATION' })); chatVariables?.forEach(v => addVariable(list, keys, `CONVERSATION_${v.name}`, v.name, v.type, `conv.${v.name}`, { type: 'CONVERSATION', name: 'CONVERSATION', icon: '' }, { group: 'CONVERSATION' }));
// Process each relevant node // Process each relevant node: non-list-operator first, then list-operator
const listOperatorIds: string[] = [];
relevantIds.forEach(id => { relevantIds.forEach(id => {
const node = nodes.find(n => n.id === id);
if (!node) return;
if (node.getData()?.type === 'list-operator') {
listOperatorIds.push(id);
} else {
processNodeVariables(node.getData(), node.getData().id, list, keys);
}
});
listOperatorIds.forEach(id => {
const node = nodes.find(n => n.id === id); const node = nodes.find(n => n.id === id);
if (node) processNodeVariables(node.getData(), node.getData().id, list, keys); if (node) processNodeVariables(node.getData(), node.getData().id, list, keys);
}); });

View File

@@ -2,7 +2,7 @@
* @Author: ZhaoYing * @Author: ZhaoYing
* @Date: 2026-02-03 15:06:18 * @Date: 2026-02-03 15:06:18
* @Last Modified by: ZhaoYing * @Last Modified by: ZhaoYing
* @Last Modified time: 2026-04-03 20:28:08 * @Last Modified time: 2026-04-07 19:56:56
*/ */
import LoopNode from './components/Nodes/LoopNode'; import LoopNode from './components/Nodes/LoopNode';
import NormalNode from './components/Nodes/NormalNode'; import NormalNode from './components/Nodes/NormalNode';
@@ -579,7 +579,7 @@ export const noteNode = {
export const nodeWidth = 240; export const nodeWidth = 240;
export const conditionNodePortItemArgsY = 60; export const conditionNodePortItemArgsY = 56.5;
export const conditionNodeItemHeight = 26; export const conditionNodeItemHeight = 26;
export const conditionNodeHeight = 110; export const conditionNodeHeight = 110;
/** /**
@@ -703,7 +703,7 @@ export const portTextAttrs = { fontSize: 12, fill: '#5B6167' }
/** /**
* Port position arguments * Port position arguments
*/ */
export const portItemArgsY = 26; export const portItemArgsY = 26.5;
export const portArgs = { x: nodeWidth, y: portItemArgsY } export const portArgs = { x: nodeWidth, y: portItemArgsY }
const defaultPortGroup = { const defaultPortGroup = {

View File

@@ -108,7 +108,7 @@ export interface ChatVariable {
required: boolean; required: boolean;
description: string; description: string;
default?: string; default?: string;
defaultValue: string; defaultValue: string | any[];
} }
export interface AddChatVariableRef { export interface AddChatVariableRef {
handleOpen: (value?: ChatVariable) => void; handleOpen: (value?: ChatVariable) => void;