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}
+ />
);
});