diff --git a/web/src/components/Header/index.tsx b/web/src/components/Header/index.tsx index 1de2e038..49988223 100644 --- a/web/src/components/Header/index.tsx +++ b/web/src/components/Header/index.tsx @@ -91,10 +91,10 @@ const AppHeader: FC<{source?: 'space' | 'manage';}> = ({source = 'manage'}) => { }, { key: '3', - icon:
, + icon:
, label: {t('header.userInfo')} -
+
, className: 'rb:text-[#212332]!', onClick: () => { @@ -103,10 +103,10 @@ const AppHeader: FC<{source?: 'space' | 'manage';}> = ({source = 'manage'}) => { }, { key: '4', - icon:
, + icon:
, label: {t('header.settings')} -
+
, className: 'rb:text-[#212332]!', onClick: () => { @@ -120,7 +120,7 @@ const AppHeader: FC<{source?: 'space' | 'manage';}> = ({source = 'manage'}) => { }, { key: '6', - icon:
, + icon:
, label: t('header.logout'), danger: true, className: 'rb:hover:rb:bg-transparent rb:hover:text-[#FF5D34]!', diff --git a/web/src/views/ApplicationConfig/components/ConfigHeader.tsx b/web/src/views/ApplicationConfig/components/ConfigHeader.tsx index ece533c1..d77ae27c 100644 --- a/web/src/views/ApplicationConfig/components/ConfigHeader.tsx +++ b/web/src/views/ApplicationConfig/components/ConfigHeader.tsx @@ -2,7 +2,7 @@ * @Author: ZhaoYing * @Date: 2026-02-03 16:27:52 * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-03-27 19:07:24 + * @Last Modified time: 2026-04-07 16:28:33 */ import { type FC, useRef, useMemo, useCallback } from 'react'; import { useNavigate, useParams } from 'react-router-dom'; @@ -18,7 +18,6 @@ import type { CopyModalRef, AgentRef, ClusterRef, WorkflowRef, FeaturesConfigFor import { deleteApplication, appExport } from '@/api/application' import CopyModal from './CopyModal' import PageHeader from '@/components/Layout/PageHeader' -import FeaturesConfig from './FeaturesConfig' /** * Tab keys for application configuration @@ -70,7 +69,6 @@ const ConfigHeader: FC = ({ application, activeTab, handleChangeTab, refresh, workflowRef, appRef, - features, onFeaturesChange, }) => { const { t } = useTranslation(); @@ -175,10 +173,9 @@ const ConfigHeader: FC = ({ return items }, [t, handleClick, application]) - const handleSaveFeaturesConfig = useCallback((value: FeaturesConfigForm) => { - appRef?.current?.handleSaveFeaturesConfig?.(value) - onFeaturesChange?.(value) - }, [appRef, onFeaturesChange]) + const handleFeaturesConfig = () => { + workflowRef.current?.handleFeaturesConfig?.() + } return ( <> @@ -209,12 +206,12 @@ const ConfigHeader: FC = ({ } extra={application?.type === 'workflow' && source !== 'sharing' && activeTab === 'arrangement' ? - ({ ...v, display_name: v.name }))} - /> + +
+
diff --git a/web/src/views/ApplicationConfig/components/FeaturesConfig/OpenStatementSettingModal.tsx b/web/src/views/ApplicationConfig/components/FeaturesConfig/OpenStatementSettingModal.tsx index 67501255..91d0d19f 100644 --- a/web/src/views/ApplicationConfig/components/FeaturesConfig/OpenStatementSettingModal.tsx +++ b/web/src/views/ApplicationConfig/components/FeaturesConfig/OpenStatementSettingModal.tsx @@ -2,7 +2,7 @@ * @Author: ZhaoYing * @Date: 2026-03-05 * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-04-07 14:22:40 + * @Last Modified time: 2026-04-07 16:58:10 */ import { forwardRef, useImperativeHandle, useState } from 'react'; import { Button, Form, Input, Flex, App } from 'antd'; @@ -12,6 +12,8 @@ import RbModal from '@/components/RbModal'; import type { FeaturesConfigForm } from '../../types' import type { Variable } from '../VariableList/types' import Tag from '@/components/Tag' +import type { Application } from '@/views/ApplicationManagement/types'; +import Editor from '@/views/Workflow/components/Editor'; export interface OpenStatementSettingModalRef { handleOpen: (values?: FeaturesConfigForm['opening_statement']) => void; @@ -21,17 +23,21 @@ export interface OpenStatementSettingModalRef { interface OpenStatementSettingModalProps { onSave: (values: FeaturesConfigForm['opening_statement']) => void; chatVariables?: Variable[]; + source?: Application['type']; } const OpenStatementSettingModal = forwardRef(({ onSave, - chatVariables = [] + chatVariables = [], + source }, ref) => { const { t } = useTranslation(); const { modal } = App.useApp() const [visible, setVisible] = useState(false); const [form] = Form.useForm(); + console.log('chatVariables', chatVariables) + const handleClose = () => { setVisible(false); form.resetFields(); @@ -49,6 +55,7 @@ const OpenStatementSettingModal = forwardRef m[1]))] + console.log('usedVars', usedVars, chatVariables) const validNames = new Set(chatVariables.map(v => v.name)) const invalid = usedVars.filter(v => !validNames.has(v)) if (invalid.length > 0) { @@ -100,9 +107,12 @@ const OpenStatementSettingModal = forwardRef - + {source === 'workflow' + ? + : + } diff --git a/web/src/views/ApplicationConfig/types.ts b/web/src/views/ApplicationConfig/types.ts index 1d6e9e57..61d8d9be 100644 --- a/web/src/views/ApplicationConfig/types.ts +++ b/web/src/views/ApplicationConfig/types.ts @@ -2,7 +2,7 @@ * @Author: ZhaoYing * @Date: 2026-02-03 16:29:49 * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-03-31 15:45:17 + * @Last Modified time: 2026-04-07 15:46:19 */ import type { KnowledgeConfig } from './components/Knowledge/types' import type { Variable } from './components/VariableList/types' @@ -168,6 +168,7 @@ export interface WorkflowRef { chatVariables: ChatVariable[]; config: WorkflowConfig | null; features: WorkflowConfig['features']; + handleFeaturesConfig?: () => void; handleSaveFeaturesConfig?: (value: FeaturesConfigForm) => void; } diff --git a/web/src/views/UserMemoryDetail/components/ConversationMemory.tsx b/web/src/views/UserMemoryDetail/components/ConversationMemory.tsx index c209274b..cd080cc6 100644 --- a/web/src/views/UserMemoryDetail/components/ConversationMemory.tsx +++ b/web/src/views/UserMemoryDetail/components/ConversationMemory.tsx @@ -48,8 +48,8 @@ const ConversationMemory: FC = () => { gap={12} >
(({ appId, graphRef, features @@ -419,6 +420,22 @@ const Chat = forwardRef { + const opening_statement = features?.opening_statement + + if (opening_statement?.enabled && opening_statement?.statement && opening_statement?.statement.trim() !== '') { + const assistantMsg: ChatItem = { + role: 'assistant', + content: replaceVariables(opening_statement.statement, variables as any), + meta_data: { + suggested_questions: opening_statement?.suggested_questions + } + } + console.log('variables', assistantMsg) + setChatList(prev => [assistantMsg, ...prev.slice(1)]) + } + }, [chatList.length, features?.opening_statement, variables]) + return ( diff --git a/web/src/views/Workflow/components/Editor/index.tsx b/web/src/views/Workflow/components/Editor/index.tsx index 5f6844df..a631c9a3 100644 --- a/web/src/views/Workflow/components/Editor/index.tsx +++ b/web/src/views/Workflow/components/Editor/index.tsx @@ -2,7 +2,7 @@ * @Author: ZhaoYing * @Date: 2025-12-23 16:22:51 * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-04-03 20:44:16 + * @Last Modified time: 2026-04-07 16:29:36 */ import { type FC, useState, useMemo } from 'react'; import { LexicalComposer } from '@lexical/react/LexicalComposer'; @@ -57,7 +57,6 @@ const Editor: FC =({ language = 'string', height, className, - waitForInit = false, }) => { console.log('Editor value', value) const [_count, setCount] = useState(0); @@ -149,10 +148,10 @@ const Editor: FC =({ /> - + - - + +
); diff --git a/web/src/views/Workflow/components/Editor/nodes/VariableNode.tsx b/web/src/views/Workflow/components/Editor/nodes/VariableNode.tsx index 24560395..5688342c 100644 --- a/web/src/views/Workflow/components/Editor/nodes/VariableNode.tsx +++ b/web/src/views/Workflow/components/Editor/nodes/VariableNode.tsx @@ -33,6 +33,18 @@ const VariableComponent: React.FC<{ nodeKey: NodeKey; data: Suggestion }> = ({ setSelected(!isSelected); }; + if (!data.nodeData?.name) { + return ( + + {data.value} + + ); + } + return ( = ({ options }) => { // Group suggestions by node ID const groupedSuggestions = options.reduce((groups: Record, suggestion) => { const { nodeData } = suggestion - const nodeId = nodeData.id as string; + const nodeId = nodeData?.id as string; if (!groups[nodeId]) { groups[nodeId] = []; } @@ -291,67 +291,67 @@ const AutocompletePlugin: FC<{ options: Suggestion[] }> = ({ options }) => { }} >
- - {Object.entries(groupedSuggestions).map(([nodeId, nodeOptions]) => { - const nodeName = nodeOptions[0]?.nodeData?.name || nodeId; - const nodeIcon = nodeOptions[0]?.nodeData?.icon; - return ( -
- - {nodeIcon &&
} - {nodeName} - - {nodeOptions.map((option) => { - const globalIndex = flatOptions.indexOf(option); - const isExpanded = expandedParent?.key === option.key; - const hasChildren = !!option.children?.length; - return ( - { if (el) itemRefs.current.set(option.key, el); }} - data-selected={selectedIndex === globalIndex} - className="rb:pl-6! rb:pr-3! rb:py-2!" - align="center" - justify="space-between" - style={{ - cursor: option.disabled ? 'not-allowed' : 'pointer', - background: (selectedIndex === globalIndex || isExpanded) ? '#f0f8ff' : 'white', - opacity: option.disabled ? 0.5 : 1, - }} - onClick={() => { - if (option.disabled) return; - insertMention(option); - }} - onMouseEnter={() => { - setSelectedIndex(globalIndex); - if (hasChildren) { - const el = itemRefs.current.get(option.key); - if (el && popupRef.current) { - const elRect = el.getBoundingClientRect(); - const popupRect = popupRef.current.getBoundingClientRect(); - setChildPanelTop(calcChildPanelTop(elRect, popupRect)); + + {Object.entries(groupedSuggestions).map(([nodeId, nodeOptions]) => { + const nodeName = nodeOptions[0]?.nodeData?.name || nodeId; + const nodeIcon = nodeOptions[0]?.nodeData?.icon; + return ( +
+ {nodeName !== 'undefined' && + {nodeIcon &&
} + {nodeName} + } + {nodeOptions.map((option) => { + const globalIndex = flatOptions.indexOf(option); + const isExpanded = expandedParent?.key === option.key; + const hasChildren = !!option.children?.length; + return ( + { if (el) itemRefs.current.set(option.key, el); }} + data-selected={selectedIndex === globalIndex} + className="rb:pl-6! rb:pr-3! rb:py-2!" + align="center" + justify="space-between" + style={{ + cursor: option.disabled ? 'not-allowed' : 'pointer', + background: (selectedIndex === globalIndex || isExpanded) ? '#f0f8ff' : 'white', + opacity: option.disabled ? 0.5 : 1, + }} + onClick={() => { + if (option.disabled) return; + insertMention(option); + }} + onMouseEnter={() => { + setSelectedIndex(globalIndex); + if (hasChildren) { + const el = itemRefs.current.get(option.key); + if (el && popupRef.current) { + const elRect = el.getBoundingClientRect(); + const popupRect = popupRef.current.getBoundingClientRect(); + setChildPanelTop(calcChildPanelTop(elRect, popupRect)); + } + setExpandedParent(option); + } else { + setExpandedParent(null); } - setExpandedParent(option); - } else { - setExpandedParent(null); - } - }} - > - - {option.isContext ? '📄' : `{x}`} - {option.label} - - - {option.dataType && {option.dataType}} - {hasChildren && ›} - - - ); - })} -
- ); - })} -
+ }} + > + {option.label && + {option.isContext ? '📄' : `{x}`} + {option.label} + } + + {option.dataType && {option.dataType}} + {hasChildren && ›} + + + ); + })} +
+ ); + })} +
{/* Child variables panel - floats to the left */} {expandedParent?.children?.length && ( diff --git a/web/src/views/Workflow/hooks/useWorkflowGraph.ts b/web/src/views/Workflow/hooks/useWorkflowGraph.ts index e42fae01..86d6007f 100644 --- a/web/src/views/Workflow/hooks/useWorkflowGraph.ts +++ b/web/src/views/Workflow/hooks/useWorkflowGraph.ts @@ -2,7 +2,7 @@ * @Author: ZhaoYing * @Date: 2026-02-03 15:17:48 * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-03-31 11:13:23 + * @Last Modified time: 2026-04-07 16:47:09 */ import { useRef, useEffect, useState } from 'react'; import { useParams } from 'react-router-dom'; @@ -18,6 +18,7 @@ import { getWorkflowConfig, saveWorkflowConfig } from '@/api/application' import { useUser } from '@/store/user'; import type { FeaturesConfigForm } from '@/views/ApplicationConfig/types' import { calcConditionNodeTotalHeight, getConditionNodeCasePortY } from '../utils' +import type { Suggestion } from '../components/Editor/plugin/AutocompletePlugin'; /** * Props for useWorkflowGraph hook @@ -73,6 +74,8 @@ export interface UseWorkflowGraphReturn { handleAddNotes: () => void; handleSaveFeaturesConfig: (value: FeaturesConfigForm) => void; features?: FeaturesConfigForm; + /** Get start node output variable list (user-defined + system variables) */ + getStartNodeVariables: () => Array<{ name: string; type: string; readonly?: boolean }>; } /** @@ -1363,9 +1366,49 @@ export const useWorkflowGraph = ({ data: { ...cleanNodeData }, }); } + const getStartNodeVariables = (): Array<{ name: string; type: string; readonly?: boolean }> => { + const startNode = graphRef.current?.getNodes().find(n => n.getData()?.type === 'start') + if (!startNode) return [] + const data = startNode.getData() + const userVars: Array<{ name: string; type: string; readonly?: boolean }> = + (data?.config?.variables?.defaultValue ?? []).map((v: any) => ({ name: v.name, type: v.type })) + return userVars + } + const handleSaveFeaturesConfig = (value?: FeaturesConfigForm) => { + const { statement = '' } = value?.opening_statement || {} featuresRef.current = value onFeaturesLoad?.(value) + + const usedVars = [...new Set([...(statement?.matchAll(/\{\{(\w+)\}\}/g) ?? [])].map(m => m[1]))] + const startVars = getStartNodeVariables() + const validNames = new Set(startVars.map(v => v.name)) + const invalid = usedVars.filter(v => !validNames.has(v)) + if (invalid.length > 0) { + const newVars = invalid.map(name => ({ + name, + description: name, + type: 'string', + required: true, + defaultValue: '', + })) + + const startNode = graphRef.current?.getNodes().find(n => n.getData()?.type === 'start') + if (startNode) { + const data = startNode.getData() + console.log('startNode', [...startVars, ...newVars]) + startNode.setData({ + ...data, + config: { + ...data.config, + variables: { + ...data.config.variables, + defaultValue: [...startVars, ...newVars], + }, + }, + }) + } + } } return { @@ -1389,5 +1432,6 @@ export const useWorkflowGraph = ({ handleAddNotes, handleSaveFeaturesConfig, features: featuresRef.current, + getStartNodeVariables, }; }; diff --git a/web/src/views/Workflow/index.tsx b/web/src/views/Workflow/index.tsx index 34ef91a7..db3fe352 100644 --- a/web/src/views/Workflow/index.tsx +++ b/web/src/views/Workflow/index.tsx @@ -6,10 +6,11 @@ import Properties from './components/Properties'; import CanvasToolbar from './components/CanvasToolbar'; import PortClickHandler from './components/PortClickHandler'; import { useWorkflowGraph } from './hooks/useWorkflowGraph'; -import type { WorkflowRef, FeaturesConfigForm } from '@/views/ApplicationConfig/types' +import type { WorkflowRef, FeaturesConfigForm, FeaturesConfigModalRef } from '@/views/ApplicationConfig/types' import Chat from './components/Chat/Chat'; import type { ChatRef, AddChatVariableRef } from './types' import AddChatVariable from './components/AddChatVariable'; +import FeaturesConfigModal from '@/views/ApplicationConfig/components/FeaturesConfig/FeaturesConfigModal' const Workflow = forwardRef void }>(({ onFeaturesLoad }, ref) => { const containerRef = useRef(null); @@ -35,7 +36,8 @@ const Workflow = forwardRef { @@ -51,6 +53,15 @@ const Workflow = forwardRef(null) + + /** Open the feature config modal pre-populated with the current values */ + const handleFeaturesConfig = () => { + blankClick() + funConfigModalRef.current?.handleOpen(features as FeaturesConfigForm) + } + useImperativeHandle(ref, () => ({ handleSave, handleRun, @@ -59,6 +70,7 @@ const Workflow = forwardRef + {/* Modal for editing feature settings; calls refresh on save */} + ({ name: v.name, key: `start_${v.name}`, label: v.name, type: 'variable', dataType: v.type, value:`{{${v.name}}}` })) as any} + />
); });