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

View File

@@ -2,7 +2,7 @@
* @Author: ZhaoYing
* @Date: 2026-03-13 17:27:52
* @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 { useTranslation } from 'react-i18next'
@@ -109,7 +109,7 @@ const TestChat: FC<TestChatProps> = ({
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, {
role: 'assistant',
created_at: Date.now(),

View File

@@ -562,83 +562,88 @@ const KnowledgeBaseManagement: FC = () => {
{data.length === 0 && !loading ? (
<Empty size={200} />
) : (
<div style={{ columns: '3 280px', columnGap: 12, marginBottom: 8 }}>
{data.map((item) => {
const modelInfo = modelMenus[item.id];
const hasModelInfo = modelInfo && modelInfo.menu.length > 1;
return (
<div key={item.id} className="rb:break-inside-avoid rb:mb-3">
<RbCard
title={item.name}
headerType="borderless"
headerClassName="rb:py-3!"
extra={
<div onClick={(e) => e.stopPropagation()}>
<Dropdown
menu={{ items: getOptMenuItems(item) }}
placement="bottomRight"
>
<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>
}
>
<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>
);
})}
<Flex align="flex-start" gap={12} className="rb:mb-2!">
{[0, 1, 2].map(colIdx => (
<div key={colIdx} style={{ flex: 1, minWidth: 0, display: 'flex', flexDirection: 'column', gap: 12 }}>
{data.filter((_, i) => i % 3 === colIdx).map((item) => {
const modelInfo = modelMenus[item.id];
const hasModelInfo = modelInfo && modelInfo.menu.length > 1;
return (
<div key={item.id}>
<RbCard
title={item.name}
headerType="borderless"
headerClassName="rb:py-3!"
extra={
<div onClick={(e) => e.stopPropagation()}>
<Dropdown
menu={{ items: getOptMenuItems(item) }}
placement="bottomRight"
>
<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>
)}
</div>
)}
</div>
</RbCard>
}
>
<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>
</RbCard>
</div>
)
})}
</div>
)})}
</div>
))}
</Flex>
)}
</InfiniteScroll>

View File

@@ -2,7 +2,7 @@
* @Author: ZhaoYing
* @Date: 2025-12-30 13:59:36
* @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 { Form, Input, Select, InputNumber, Button, Row, Col, Flex, Spin } from 'antd';
@@ -135,7 +135,10 @@ const ChatVariableModal = forwardRef<ChatVariableModalRef, ChatVariableModalProp
const handleSave = () => {
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();
});
};

View File

@@ -2,7 +2,7 @@
* @Author: ZhaoYing
* @Date: 2026-02-03 15:40:13
* @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 { 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>
)
) : selectedSuggestion ? (
<span className="rb:flex rb:flex-1 rb:min-w-0">
<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">
<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 rb:overflow-hidden">
{!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>}
<span className="rb:text-[#171719]">
{!isConversation && nodeData?.name && <span className="rb:text-[#5B6167] rb:shrink rb:min-w-0 rb:truncate rb:max-w-[40%]">{nodeData.name}</span>}
{!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}
</span>
</span>
</span>
</div>
) : (
<span className="rb:text-[#bfbfbf] rb:flex-1">{placeholder}</span>
)}
@@ -264,7 +265,7 @@ const VariableSelect: FC<VariableSelectProps> = ({
{open && createPortal(
<div
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 }}
>
<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;
return (
<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]">
{nd.icon && <div className={`rb:size-3 rb:bg-cover ${nd.icon}`} />}
<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-4 rb:bg-cover ${nd.icon}`} />}
{nd.name}
</Flex>
{suggestions.map(s => {
@@ -286,14 +287,15 @@ const VariableSelect: FC<VariableSelectProps> = ({
<Flex
key={s.key}
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"
justify="space-between"
style={{
cursor: s.disabled ? 'not-allowed' : 'pointer',
background: isSelected || isExpanded ? '#f0f8ff' : 'white',
opacity: s.disabled ? 0.5 : 1,
}}
onClick={() => {
if (s.disabled) return;
if (hasChildren) {

View File

@@ -2,7 +2,7 @@
* @Author: ZhaoYing
* @Date: 2026-01-19 17:00:26
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-04-02 16:58:40
* @Last Modified time: 2026-04-07 20:33:26
*/
/**
* useVariableList Hook
@@ -125,7 +125,7 @@ const processNodeVariables = (
if (type in NODE_VARIABLES) {
if (type === 'list-operator') {
// Determine output type from the first variable in config
const variableValue = config?.variable;
const variableValue = config?.input_list?.defaultValue;
let itemType = 'string';
if (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);
});
}
// Add code node variables
if (type === 'code') {
(nodeData.config?.output_variables?.defaultValue || []).forEach((p: any) => {
@@ -393,8 +392,18 @@ export const useVariableList = (
// 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' }));
// Process each relevant node
// Process each relevant node: non-list-operator first, then list-operator
const listOperatorIds: string[] = [];
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);
if (node) processNodeVariables(node.getData(), node.getData().id, list, keys);
});

View File

@@ -2,7 +2,7 @@
* @Author: ZhaoYing
* @Date: 2026-02-03 15:06:18
* @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 NormalNode from './components/Nodes/NormalNode';
@@ -579,7 +579,7 @@ export const noteNode = {
export const nodeWidth = 240;
export const conditionNodePortItemArgsY = 60;
export const conditionNodePortItemArgsY = 56.5;
export const conditionNodeItemHeight = 26;
export const conditionNodeHeight = 110;
/**
@@ -703,7 +703,7 @@ export const portTextAttrs = { fontSize: 12, fill: '#5B6167' }
/**
* Port position arguments
*/
export const portItemArgsY = 26;
export const portItemArgsY = 26.5;
export const portArgs = { x: nodeWidth, y: portItemArgsY }
const defaultPortGroup = {

View File

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