Commit 7aaa28ed by Archer Committed by GitHub

fix: document deploy (#5868)

* mcp memory

* Editor space parse error

* fix: ts

* fix: debug interactive

* fix: templateId

* fix: editor in debug

* doc
parent 89ca81d1
# Lexical Editor 文本解析一致性分析报告
## 执行摘要
通过对 `textToEditorState``editorStateToText` 函数的全面分析,发现了 **3 个确认的不一致性问题**,会导致用户保存后重新加载时看到与编辑器显示不同的内容。
### 严重问题总览
| 问题 | 严重性 | 影响 | 位置 |
|------|--------|------|------|
| 列表项尾部空格丢失 | 🔴 高 | 用户有意添加的空格被删除 | utils.ts:255 |
| 有序列表序号重置 | 🔴 高 | 自定义序号变成连续序号 | utils.ts:257 |
| 列表项内换行不对称 | 🟡 中 | 编辑器支持但无法往返 | processListItem |
---
## 问题 1: 列表项尾部空格丢失 🔴(已处理)
### 问题描述
`processListItem` 函数中使用了 `trim()` 处理列表项文本:
```typescript
// utils.ts:255
const itemTextString = itemText.join('').trim();
```
### 不一致性演示
**用户输入:**
```
- hello world
```
(注意 "world" 后面有 2 个空格)
**EditorState:**
```json
{
"type": "listitem",
"children": [
{ "type": "text", "text": "hello world " }
]
}
```
**输出文本:**
```
- hello world
```
(尾部空格被 trim 删除)
**重新加载:**
```
- hello world
```
(用户的空格永久丢失)
### 影响分析
- **用户体验**: 用户有意添加的尾部空格(可能用于格式对齐)会丢失
- **数据完整性**: 每次保存/加载循环都会丢失尾部空格
- **严重程度**: 高 - 直接影响用户输入的完整性
### 解决方案
**方案 1: 移除 trim()**
```typescript
const itemTextString = itemText.join(''); // 不使用 trim
```
**方案 2: 只移除前导空格**
```typescript
const itemTextString = itemText.join('').trimStart(); // 只移除开头空格
```
**推荐**: 方案 1,完全保留用户输入的空格
---
## 问题 2: 有序列表序号重置 🔴
### 问题描述
在输出有序列表时,使用 `index + 1` 而不是列表项自身的 `value`:
```typescript
// utils.ts:257
const prefix = listType === 'bullet' ? '- ' : `${index + 1}. `;
```
但在解析时,`numberValue` 被正确提取并存储到 `listItem.value`
### 不一致性演示
**用户输入:**
```
1. first
2. second
5. fifth
10. tenth
```
**解析 (textToEditorState):**
```javascript
items = [
{ numberValue: 1, text: "first" },
{ numberValue: 2, text: "second" },
{ numberValue: 5, text: "fifth" },
{ numberValue: 10, text: "tenth" }
]
```
**EditorState:**
```json
[
{ "value": 1, "text": "first" },
{ "value": 2, "text": "second" },
{ "value": 5, "text": "fifth" },
{ "value": 10, "text": "tenth" }
]
```
**输出文本 (editorStateToText):**
```
1. first (index=0, 0+1=1) ✓
2. second (index=1, 1+1=2) ✓
3. fifth (index=2, 2+1=3) ✗ 应该是 5
4. tenth (index=3, 3+1=4) ✗ 应该是 10
```
**重新加载:**
用户的自定义序号 5 和 10 永久丢失,变成连续的 3 和 4。
### 影响分析
- **用户体验**: 用户有意设置的序号被强制改为连续序号
- **数据完整性**: 有序列表的语义丢失(如章节编号 1.1, 1.2, 2.1)
- **严重程度**: 高 - 改变了用户的语义表达
### 解决方案
```typescript
// utils.ts:257
const prefix = listType === 'bullet'
? '- '
: `${listItem.value || index + 1}. `;
```
使用 `listItem.value` 而不是 `index + 1`,保留原始序号。
---
## 问题 3: 列表项内换行不对称 🟡
### 问题描述
Lexical 编辑器允许在列表项内插入换行符 (`linebreak` 节点),但 `textToEditorState` 无法将包含换行的文本重新解析为列表项内换行。
### 不一致性演示
**用户在编辑器中操作:**
```
1. 输入: "- item1"
2. 按 Shift+Enter (插入软换行)
3. 继续输入: "continued content"
```
**EditorState:**
```json
{
"type": "listitem",
"children": [
{ "type": "text", "text": "item1" },
{ "type": "linebreak" },
{ "type": "text", "text": "continued content" }
]
}
```
**输出文本 (editorStateToText):**
```
- item1
continued content
```
**重新加载 (textToEditorState):**
```
行1: "- item1" → 列表项
行2: "continued content" → 段落 (不再是列表项的一部分!)
```
**最终结构变化:**
```
原来: 1个列表项(包含换行)
现在: 1个列表项 + 1个段落
```
### 影响分析
- **结构完整性**: 列表项的内部结构在保存/加载后改变
- **语义丢失**: 原本属于列表项的内容变成了独立段落
- **严重程度**: 中 - 影响文档结构,但可能符合 Markdown 语义
### 解决方案
**方案 1: 在输出时将换行转为空格**
```typescript
if (child.type === 'linebreak') {
itemText.push(' '); // 使用空格而不是 \n
}
```
**方案 2: 在编辑器中禁止列表项内换行**
- 配置 Lexical 不允许在列表项内插入 linebreak
- 用户只能通过创建新列表项来换行
**方案 3: 支持 Markdown 风格的列表项多行**
```typescript
// 识别缩进的行为列表项的继续内容
parseTextLine:
if (line.startsWith(' ') && prevLine.wasListItem) {
// 作为列表项的继续内容
}
```
**推荐**: 方案 1 (最简单) 或方案 2 (最明确)
---
## 其他潜在问题
### 问题 4: 变量节点保存后变成普通文本 🟡
**现象**:
```
EditorState: { type: 'variableLabel', variableKey: '{{var1}}' }
输出文本: "{{var1}}"
重新加载: { type: 'text', text: "{{var1}}" }
```
**影响**: 变量节点的功能性丢失
**分析**: 这可能是设计决策 - 变量只在编辑会话中有效,保存到文本后变成普通占位符。如果需要保持变量功能,应该使用其他存储格式(如 JSON)而不是纯文本。
### 问题 5: 非连续缩进级别可能导致结构错误 🟡
**现象**:
```
输入:
- level 0
- level 2 (跳过 level 1)
- level 1
```
**问题**: `buildListStructure` 可能无法正确处理非连续的缩进级别
**影响**: 列表嵌套结构可能不符合预期
**建议**: 规范化缩进级别,或在文档中说明只支持连续缩进
---
## 正常运作的部分 ✅
经过分析,以下功能**正常运作**,不存在一致性问题:
1. **空行处理** - 空行被正确保留和还原
2. **段落前导空格** - 修复后完全保留
3. **列表和段落边界** - 正确识别和分离
4. **特殊字符在段落中** - 只有行首的 `- ``\d+. ` 被识别为列表
5. **混合列表类型** - bullet 和 number 列表正确分离
6. **列表缩进** - 使用 TabStr 统一为 2 个空格
---
## 测试用例建议
### 测试用例 1: 列表项尾部空格
```typescript
const input = "- hello world "; // 2个尾部空格
const state = textToEditorState(input, true);
const editor = createEditorWithState(state);
const output = editorStateToText(editor);
expect(output).toBe("- hello world "); // 应保留空格
```
### 测试用例 2: 自定义列表序号
```typescript
const input = "1. first\n5. fifth\n10. tenth";
const state = textToEditorState(input, true);
const editor = createEditorWithState(state);
const output = editorStateToText(editor);
expect(output).toBe("1. first\n5. fifth\n10. tenth"); // 应保留序号
```
### 测试用例 3: 列表项换行
```typescript
// 在编辑器中创建列表项并插入 linebreak
const editor = createEditor();
// ... 创建列表项
// ... 插入 linebreak
const output = editorStateToText(editor);
const reloadedState = textToEditorState(output, true);
const reloadedEditor = createEditorWithState(reloadedState);
// 验证结构是否一致
expect(getStructure(editor)).toEqual(getStructure(reloadedEditor));
```
### 测试用例 4: 往返对称性
```typescript
const testCases = [
"simple text",
" indented text",
"- bullet list\n - nested",
"1. first\n2. second\n5. fifth",
"text\n\n\nwith\n\nempty\n\nlines",
"- item ", // 尾部空格
];
testCases.forEach(input => {
const state = textToEditorState(input, true);
const editor = createEditorWithState(state);
const output = editorStateToText(editor);
expect(output).toBe(input); // 应完全一致
});
```
---
## 修复优先级建议
### P0 - 立即修复
1.**列表项尾部空格丢失** - 影响数据完整性
2.**有序列表序号重置** - 影响语义表达
### P1 - 高优先级
3. ⚠️ **列表项内换行不对称** - 影响结构一致性
### P2 - 按需修复
4. 📝 **变量节点** - 根据产品需求决定
5. 📝 **非连续缩进** - 文档说明或规范化处理
---
## 代码修改建议
### 修改 1: 保留列表项空格
```diff
// utils.ts:255
- const itemTextString = itemText.join('').trim();
+ const itemTextString = itemText.join('');
```
### 修改 2: 使用原始列表序号
```diff
// utils.ts:257
- const prefix = listType === 'bullet' ? '- ' : `${index + 1}. `;
+ const prefix = listType === 'bullet' ? '- ' : `${listItem.value || index + 1}. `;
```
### 修改 3: 处理列表项换行(方案1)
```diff
// utils.ts:242
if (child.type === 'linebreak') {
- itemText.push('\n');
+ itemText.push(' '); // 转为空格而不是换行
}
```
---
## 总结
通过全面分析,确认了 **3 个会导致编辑器显示与解析文本不一致的问题**:
1. 🔴 列表项尾部空格丢失 → 修复: 移除 trim()
2. 🔴 有序列表序号重置 → 修复: 使用 listItem.value
3. 🟡 列表项内换行不对称 → 修复: 转换为空格或禁止
其他方面(空行、前导空格、边界处理)都运作正常。
建议优先修复前两个 P0 问题,确保用户数据的完整性和语义准确性。
...@@ -52,7 +52,7 @@ description: FastGPT 系统插件设计方案 ...@@ -52,7 +52,7 @@ description: FastGPT 系统插件设计方案
- **lib**: 库文件,提供工具函数和类库 - **lib**: 库文件,提供工具函数和类库
- **test**: 测试相关 - **test**: 测试相关
系统工具的结构可以参考 [如何开发系统工具](/docs/introduction/guide/plugins/dev_system_tool)。 系统工具的结构可以参考 [如何开发系统插件](/docs/introduction/guide/plugins/dev_system_tool)。
## 技术细节 ## 技术细节
......
--- ---
title: 如何开发系统工具 title: 如何开发系统插件
description: FastGPT 系统工具开发指南 description: FastGPT 系统插件开发指南(工具篇)
--- ---
## 介绍 ## 介绍
FastGPT 系统工具项目从 4.10.0 版本后移动到独立的`fastgpt-plugin`项目中,采用纯代码的模式进行工具编写。 FastGPT 系统插件项目从 4.10.0 版本后移动到独立的`fastgpt-plugin`项目中,采用纯代码的模式进行工具编写。
在 4.14.0 版本插件市场更新后,系统工具开发流程有所改变,请依照最新文档贡献代码。 在 4.14.0 版本插件市场更新后,系统工具开发流程有所改变,请依照最新文档贡献代码。
你可以在`fastgpt-plugin`项目中进行独立开发和调试好插件后,直接向 FastGPT 官方提交 PR 即可,无需运行 FastGPT 主服务。 你可以在`fastgpt-plugin`项目中进行独立开发和调试好插件后,直接向 FastGPT 官方提交 PR 即可,无需运行 FastGPT 主服务。
目前系统插件仅支持“工具”这一种类型。
## 概念 ## 概念
- 工具(Tool):最小的运行单元,每个工具都有唯一 ID 和特定的输入和输出。 - 工具(Tool):最小的运行单元,每个工具都有唯一 ID 和特定的输入和输出。
......
...@@ -111,6 +111,7 @@ description: FastGPT 文档目录 ...@@ -111,6 +111,7 @@ description: FastGPT 文档目录
- [/docs/upgrading/4-13/4131](/docs/upgrading/4-13/4131) - [/docs/upgrading/4-13/4131](/docs/upgrading/4-13/4131)
- [/docs/upgrading/4-13/4132](/docs/upgrading/4-13/4132) - [/docs/upgrading/4-13/4132](/docs/upgrading/4-13/4132)
- [/docs/upgrading/4-14/4140](/docs/upgrading/4-14/4140) - [/docs/upgrading/4-14/4140](/docs/upgrading/4-14/4140)
- [/docs/upgrading/4-14/4141](/docs/upgrading/4-14/4141)
- [/docs/upgrading/4-8/40](/docs/upgrading/4-8/40) - [/docs/upgrading/4-8/40](/docs/upgrading/4-8/40)
- [/docs/upgrading/4-8/41](/docs/upgrading/4-8/41) - [/docs/upgrading/4-8/41](/docs/upgrading/4-8/41)
- [/docs/upgrading/4-8/42](/docs/upgrading/4-8/42) - [/docs/upgrading/4-8/42](/docs/upgrading/4-8/42)
......
---
title: 'V4.14.0(包含升级脚本)'
description: 'FastGPT V4.14.0 更新说明'
---
## 🚀 新增内容
## ⚙️ 优化
1. 在同一轮对话中,MCP Client 会持久化实例,不会销毁。
## 🐛 修复
1. Debug 模式下,交互节点无法正常使用。
2. 富文本编辑器 tab 空格未对齐。
{
"title": "4.14.x",
"description": "",
"pages": ["4141", "4140"]
}
...@@ -20,7 +20,7 @@ ...@@ -20,7 +20,7 @@
"document/content/docs/introduction/development/custom-models/xinference.mdx": "2025-08-05T23:20:39+08:00", "document/content/docs/introduction/development/custom-models/xinference.mdx": "2025-08-05T23:20:39+08:00",
"document/content/docs/introduction/development/design/dataset.mdx": "2025-07-23T21:35:03+08:00", "document/content/docs/introduction/development/design/dataset.mdx": "2025-07-23T21:35:03+08:00",
"document/content/docs/introduction/development/design/design_plugin.mdx": "2025-11-04T16:58:12+08:00", "document/content/docs/introduction/development/design/design_plugin.mdx": "2025-11-04T16:58:12+08:00",
"document/content/docs/introduction/development/docker.mdx": "2025-11-05T13:57:12+08:00", "document/content/docs/introduction/development/docker.mdx": "2025-11-05T14:16:14+08:00",
"document/content/docs/introduction/development/faq.mdx": "2025-08-12T22:22:18+08:00", "document/content/docs/introduction/development/faq.mdx": "2025-08-12T22:22:18+08:00",
"document/content/docs/introduction/development/intro.mdx": "2025-09-29T11:34:11+08:00", "document/content/docs/introduction/development/intro.mdx": "2025-09-29T11:34:11+08:00",
"document/content/docs/introduction/development/migration/docker_db.mdx": "2025-07-23T21:35:03+08:00", "document/content/docs/introduction/development/migration/docker_db.mdx": "2025-07-23T21:35:03+08:00",
...@@ -101,7 +101,7 @@ ...@@ -101,7 +101,7 @@
"document/content/docs/protocol/terms.en.mdx": "2025-08-03T22:37:45+08:00", "document/content/docs/protocol/terms.en.mdx": "2025-08-03T22:37:45+08:00",
"document/content/docs/protocol/terms.mdx": "2025-08-03T22:37:45+08:00", "document/content/docs/protocol/terms.mdx": "2025-08-03T22:37:45+08:00",
"document/content/docs/toc.en.mdx": "2025-08-04T13:42:36+08:00", "document/content/docs/toc.en.mdx": "2025-08-04T13:42:36+08:00",
"document/content/docs/toc.mdx": "2025-11-04T16:58:12+08:00", "document/content/docs/toc.mdx": "2025-11-05T21:56:32+08:00",
"document/content/docs/upgrading/4-10/4100.mdx": "2025-08-02T19:38:37+08:00", "document/content/docs/upgrading/4-10/4100.mdx": "2025-08-02T19:38:37+08:00",
"document/content/docs/upgrading/4-10/4101.mdx": "2025-09-08T20:07:20+08:00", "document/content/docs/upgrading/4-10/4101.mdx": "2025-09-08T20:07:20+08:00",
"document/content/docs/upgrading/4-11/4110.mdx": "2025-08-05T23:20:39+08:00", "document/content/docs/upgrading/4-11/4110.mdx": "2025-08-05T23:20:39+08:00",
...@@ -114,7 +114,8 @@ ...@@ -114,7 +114,8 @@
"document/content/docs/upgrading/4-13/4130.mdx": "2025-11-04T15:06:39+08:00", "document/content/docs/upgrading/4-13/4130.mdx": "2025-11-04T15:06:39+08:00",
"document/content/docs/upgrading/4-13/4131.mdx": "2025-09-30T15:47:06+08:00", "document/content/docs/upgrading/4-13/4131.mdx": "2025-09-30T15:47:06+08:00",
"document/content/docs/upgrading/4-13/4132.mdx": "2025-10-21T11:46:53+08:00", "document/content/docs/upgrading/4-13/4132.mdx": "2025-10-21T11:46:53+08:00",
"document/content/docs/upgrading/4-14/4140.mdx": "2025-11-05T13:57:12+08:00", "document/content/docs/upgrading/4-14/4140.mdx": "2025-11-05T14:16:14+08:00",
"document/content/docs/upgrading/4-14/4141.mdx": "2025-11-06T13:25:49+08:00",
"document/content/docs/upgrading/4-8/40.mdx": "2025-08-02T19:38:37+08:00", "document/content/docs/upgrading/4-8/40.mdx": "2025-08-02T19:38:37+08:00",
"document/content/docs/upgrading/4-8/41.mdx": "2025-08-02T19:38:37+08:00", "document/content/docs/upgrading/4-8/41.mdx": "2025-08-02T19:38:37+08:00",
"document/content/docs/upgrading/4-8/42.mdx": "2025-08-02T19:38:37+08:00", "document/content/docs/upgrading/4-8/42.mdx": "2025-08-02T19:38:37+08:00",
......
...@@ -97,7 +97,7 @@ ${mdSplitString} ...@@ -97,7 +97,7 @@ ${mdSplitString}
if (chunkLength > maxSize) { if (chunkLength > maxSize) {
const newChunks = commonSplit({ const newChunks = commonSplit({
...props, ...props,
text: chunk text: chunk.replace(defaultChunk, '').trim()
}).chunks; }).chunks;
chunks.push(...newChunks); chunks.push(...newChunks);
} else { } else {
......
...@@ -89,6 +89,8 @@ export type ModuleDispatchProps<T> = ChatDispatchProps & { ...@@ -89,6 +89,8 @@ export type ModuleDispatchProps<T> = ChatDispatchProps & {
runtimeNodes: RuntimeNodeItemType[]; runtimeNodes: RuntimeNodeItemType[];
runtimeEdges: RuntimeEdgeItemType[]; runtimeEdges: RuntimeEdgeItemType[];
params: T; params: T;
mcpClientMemory: Record<string, MCPClient>; // key: url
}; };
export type SystemVariablesType = { export type SystemVariablesType = {
......
...@@ -59,9 +59,10 @@ export class MCPClient { ...@@ -59,9 +59,10 @@ export class MCPClient {
} }
// 内部方法:关闭连接 // 内部方法:关闭连接
private async closeConnection() { async closeConnection() {
try { try {
await retryFn(() => this.client.close(), 3); await retryFn(() => this.client.close(), 3);
addLog.debug(`[MCP Client] Closed connection:${this.url}`);
} catch (error) { } catch (error) {
addLog.error('[MCP Client] Failed to close connection:', error); addLog.error('[MCP Client] Failed to close connection:', error);
} }
...@@ -110,7 +111,15 @@ export class MCPClient { ...@@ -110,7 +111,15 @@ export class MCPClient {
* @param params Parameters * @param params Parameters
* @returns Tool execution result * @returns Tool execution result
*/ */
public async toolCall(toolName: string, params: Record<string, any>): Promise<any> { public async toolCall({
toolName,
params,
closeConnection = true
}: {
toolName: string;
params: Record<string, any>;
closeConnection?: boolean;
}): Promise<any> {
try { try {
const client = await this.getConnection(); const client = await this.getConnection();
addLog.debug(`[MCP Client] Call tool: ${toolName}`, params); addLog.debug(`[MCP Client] Call tool: ${toolName}`, params);
...@@ -129,7 +138,9 @@ export class MCPClient { ...@@ -129,7 +138,9 @@ export class MCPClient {
addLog.error(`[MCP Client] Failed to call tool ${toolName}:`, error); addLog.error(`[MCP Client] Failed to call tool ${toolName}:`, error);
return Promise.reject(error); return Promise.reject(error);
} finally { } finally {
await this.closeConnection(); if (closeConnection) {
await this.closeConnection();
}
} }
} }
} }
......
...@@ -22,7 +22,6 @@ import { getNodeErrResponse } from '../utils'; ...@@ -22,7 +22,6 @@ import { getNodeErrResponse } from '../utils';
import { splitCombineToolId } from '@fastgpt/global/core/app/tool/utils'; import { splitCombineToolId } from '@fastgpt/global/core/app/tool/utils';
import { getAppVersionById } from '../../../../core/app/version/controller'; import { getAppVersionById } from '../../../../core/app/version/controller';
import { runHTTPTool } from '../../../app/http'; import { runHTTPTool } from '../../../app/http';
import { i18nT } from '../../../../../web/i18n/utils';
type SystemInputConfigType = { type SystemInputConfigType = {
type: SystemToolSecretInputTypeEnum; type: SystemToolSecretInputTypeEnum;
...@@ -50,6 +49,7 @@ export const dispatchRunTool = async (props: RunToolProps): Promise<RunToolRespo ...@@ -50,6 +49,7 @@ export const dispatchRunTool = async (props: RunToolProps): Promise<RunToolRespo
runningAppInfo, runningAppInfo,
variables, variables,
workflowStreamResponse, workflowStreamResponse,
node: { name, avatar, toolConfig, version, catchError } node: { name, avatar, toolConfig, version, catchError }
} = props; } = props;
...@@ -201,14 +201,19 @@ export const dispatchRunTool = async (props: RunToolProps): Promise<RunToolRespo ...@@ -201,14 +201,19 @@ export const dispatchRunTool = async (props: RunToolProps): Promise<RunToolRespo
const { headerSecret, url } = const { headerSecret, url } =
tool.nodes[0].toolConfig?.mcpToolSet ?? tool.nodes[0].inputs[0].value; tool.nodes[0].toolConfig?.mcpToolSet ?? tool.nodes[0].inputs[0].value;
const mcpClient = new MCPClient({
url,
headers: getSecretValue({
storeSecret: headerSecret
})
});
const result = await mcpClient.toolCall(toolName, params); // Buffer mcpClient in this workflow
const mcpClient =
props.mcpClientMemory?.[url] ??
new MCPClient({
url,
headers: getSecretValue({
storeSecret: headerSecret
})
});
props.mcpClientMemory[url] = mcpClient;
const result = await mcpClient.toolCall({ toolName, params, closeConnection: false });
return { return {
data: { [NodeOutputKeyEnum.rawResponse]: result }, data: { [NodeOutputKeyEnum.rawResponse]: result },
[DispatchNodeResponseKeyEnum.nodeResponse]: { [DispatchNodeResponseKeyEnum.nodeResponse]: {
...@@ -285,7 +290,7 @@ export const dispatchRunTool = async (props: RunToolProps): Promise<RunToolRespo ...@@ -285,7 +290,7 @@ export const dispatchRunTool = async (props: RunToolProps): Promise<RunToolRespo
storeSecret: headerSecret storeSecret: headerSecret
}) })
}); });
const result = await mcpClient.toolCall(toolName, restParams); const result = await mcpClient.toolCall({ toolName, params: restParams });
return { return {
data: { data: {
......
...@@ -54,6 +54,7 @@ import { createChatUsageRecord, pushChatItemUsage } from '../../../support/walle ...@@ -54,6 +54,7 @@ import { createChatUsageRecord, pushChatItemUsage } from '../../../support/walle
import type { RequireOnlyOne } from '@fastgpt/global/common/type/utils'; import type { RequireOnlyOne } from '@fastgpt/global/common/type/utils';
import { getS3ChatSource } from '../../../common/s3/sources/chat'; import { getS3ChatSource } from '../../../common/s3/sources/chat';
import { addPreviewUrlToChatItems } from '../../chat/utils'; import { addPreviewUrlToChatItems } from '../../chat/utils';
import type { MCPClient } from '../../app/mcp';
type Props = Omit<ChatDispatchProps, 'workflowDispatchDeep' | 'timezone' | 'externalProvider'> & { type Props = Omit<ChatDispatchProps, 'workflowDispatchDeep' | 'timezone' | 'externalProvider'> & {
runtimeNodes: RuntimeNodeItemType[]; runtimeNodes: RuntimeNodeItemType[];
...@@ -152,6 +153,8 @@ export async function dispatchWorkFlow({ ...@@ -152,6 +153,8 @@ export async function dispatchWorkFlow({
})) }))
}; };
let mcpClientMemory = {} as Record<string, MCPClient>;
// Init some props // Init some props
return runWorkflow({ return runWorkflow({
...data, ...data,
...@@ -163,17 +166,24 @@ export async function dispatchWorkFlow({ ...@@ -163,17 +166,24 @@ export async function dispatchWorkFlow({
variables: defaultVariables, variables: defaultVariables,
workflowDispatchDeep: 0, workflowDispatchDeep: 0,
usageId: newUsageId, usageId: newUsageId,
concatUsage concatUsage,
mcpClientMemory
}).finally(() => { }).finally(() => {
if (streamCheckTimer) { if (streamCheckTimer) {
clearInterval(streamCheckTimer); clearInterval(streamCheckTimer);
} }
// Close mcpClient connections
Object.values(mcpClientMemory).forEach((client) => {
client.closeConnection();
});
}); });
} }
type RunWorkflowProps = ChatDispatchProps & { type RunWorkflowProps = ChatDispatchProps & {
runtimeNodes: RuntimeNodeItemType[]; runtimeNodes: RuntimeNodeItemType[];
runtimeEdges: RuntimeEdgeItemType[]; runtimeEdges: RuntimeEdgeItemType[];
mcpClientMemory: Record<string, MCPClient>;
defaultSkipNodeQueue?: WorkflowDebugResponse['skipNodeQueue']; defaultSkipNodeQueue?: WorkflowDebugResponse['skipNodeQueue'];
concatUsage?: (points: number) => any; concatUsage?: (points: number) => any;
}; };
...@@ -192,7 +202,8 @@ export const runWorkflow = async (data: RunWorkflowProps): Promise<DispatchFlowR ...@@ -192,7 +202,8 @@ export const runWorkflow = async (data: RunWorkflowProps): Promise<DispatchFlowR
responseAllData = true, responseAllData = true,
usageId, usageId,
concatUsage, concatUsage,
runningUserInfo: { teamId } runningUserInfo: { teamId },
mcpClientMemory
} = data; } = data;
// Over max depth // Over max depth
...@@ -458,7 +469,8 @@ export const runWorkflow = async (data: RunWorkflowProps): Promise<DispatchFlowR ...@@ -458,7 +469,8 @@ export const runWorkflow = async (data: RunWorkflowProps): Promise<DispatchFlowR
const dispatchData: ModuleDispatchProps<Record<string, any>> = { const dispatchData: ModuleDispatchProps<Record<string, any>> = {
...data, ...data,
lastInteractive: data.lastInteractive?.entryNodeIds.includes(node.nodeId) mcpClientMemory,
lastInteractive: data.lastInteractive?.entryNodeIds?.includes(node.nodeId)
? data.lastInteractive ? data.lastInteractive
: undefined, : undefined,
variables, variables,
......
...@@ -30,6 +30,7 @@ import FocusPlugin from '../../Textarea/PromptEditor/plugins/FocusPlugin'; ...@@ -30,6 +30,7 @@ import FocusPlugin from '../../Textarea/PromptEditor/plugins/FocusPlugin';
import VariableLabelPlugin from '../../Textarea/PromptEditor/plugins/VariableLabelPlugin'; import VariableLabelPlugin from '../../Textarea/PromptEditor/plugins/VariableLabelPlugin';
import { VariableLabelNode } from '../../Textarea/PromptEditor/plugins/VariableLabelPlugin/node'; import { VariableLabelNode } from '../../Textarea/PromptEditor/plugins/VariableLabelPlugin/node';
import VariableLabelPickerPlugin from '../../Textarea/PromptEditor/plugins/VariableLabelPickerPlugin'; import VariableLabelPickerPlugin from '../../Textarea/PromptEditor/plugins/VariableLabelPickerPlugin';
import { useDeepCompareEffect } from 'ahooks';
export default function Editor({ export default function Editor({
h = 40, h = 40,
...@@ -45,7 +46,7 @@ export default function Editor({ ...@@ -45,7 +46,7 @@ export default function Editor({
h?: number; h?: number;
variables: EditorVariablePickerType[]; variables: EditorVariablePickerType[];
variableLabels: EditorVariableLabelPickerType[]; variableLabels: EditorVariableLabelPickerType[];
onChange?: (editorState: EditorState, editor: LexicalEditor) => void; onChange?: (editor: LexicalEditor) => void;
onBlur?: (editor: LexicalEditor) => void; onBlur?: (editor: LexicalEditor) => void;
value?: string; value?: string;
currentValue?: string; currentValue?: string;
...@@ -61,11 +62,11 @@ export default function Editor({ ...@@ -61,11 +62,11 @@ export default function Editor({
nodes: [VariableNode, VariableLabelNode], nodes: [VariableNode, VariableLabelNode],
editorState: textToEditorState(value), editorState: textToEditorState(value),
onError: (error: Error) => { onError: (error: Error) => {
throw error; console.error('Lexical errror', error);
} }
}; };
useEffect(() => { useDeepCompareEffect(() => {
if (focus) return; if (focus) return;
setKey(getNanoid(6)); setKey(getNanoid(6));
}, [value, variables.length]); }, [value, variables.length]);
...@@ -119,7 +120,7 @@ export default function Editor({ ...@@ -119,7 +120,7 @@ export default function Editor({
<OnChangePlugin <OnChangePlugin
onChange={(editorState: EditorState, editor: LexicalEditor) => { onChange={(editorState: EditorState, editor: LexicalEditor) => {
startSts(() => { startSts(() => {
onChange?.(editorState, editor); onChange?.(editor);
}); });
}} }}
/> />
......
...@@ -30,7 +30,7 @@ const HttpInput = ({ ...@@ -30,7 +30,7 @@ const HttpInput = ({
const [currentValue, setCurrentValue] = React.useState(value); const [currentValue, setCurrentValue] = React.useState(value);
const onChangeInput = useCallback( const onChangeInput = useCallback(
(editorState: EditorState, editor: LexicalEditor) => { (editor: LexicalEditor) => {
const text = editorStateToText(editor); const text = editorStateToText(editor);
setCurrentValue(text); setCurrentValue(text);
onChange?.(text); onChange?.(text);
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
*/ */
import type { CSSProperties } from 'react'; import type { CSSProperties } from 'react';
import { useMemo, useState, useTransition } from 'react'; import { useEffect, useMemo, useState, useTransition } from 'react';
import { LexicalComposer } from '@lexical/react/LexicalComposer'; import { LexicalComposer } from '@lexical/react/LexicalComposer';
import { PlainTextPlugin } from '@lexical/react/LexicalPlainTextPlugin'; import { PlainTextPlugin } from '@lexical/react/LexicalPlainTextPlugin';
import { RichTextPlugin } from '@lexical/react/LexicalRichTextPlugin'; import { RichTextPlugin } from '@lexical/react/LexicalRichTextPlugin';
...@@ -72,7 +72,7 @@ export type EditorProps = { ...@@ -72,7 +72,7 @@ export type EditorProps = {
isRichText?: boolean; isRichText?: boolean;
variables?: EditorVariablePickerType[]; variables?: EditorVariablePickerType[];
variableLabels?: EditorVariableLabelPickerType[]; variableLabels?: EditorVariableLabelPickerType[];
value?: string; value: string;
showOpenModal?: boolean; showOpenModal?: boolean;
minH?: number; minH?: number;
maxH?: number; maxH?: number;
...@@ -100,7 +100,7 @@ export default function Editor({ ...@@ -100,7 +100,7 @@ export default function Editor({
onChange, onChange,
onChangeText, onChangeText,
onBlur, onBlur,
value, value = '',
placeholder = '', placeholder = '',
placeholderPadding = '12px 14px', placeholderPadding = '12px 14px',
bg = 'white', bg = 'white',
...@@ -111,7 +111,7 @@ export default function Editor({ ...@@ -111,7 +111,7 @@ export default function Editor({
}: EditorProps & }: EditorProps &
FormPropsType & { FormPropsType & {
onOpenModal?: () => void; onOpenModal?: () => void;
onChange: (editorState: EditorState, editor: LexicalEditor) => void; onChange: (editor: LexicalEditor) => void;
onChangeText?: ((text: string) => void) | undefined; onChangeText?: ((text: string) => void) | undefined;
onBlur?: (editor: LexicalEditor) => void; onBlur?: (editor: LexicalEditor) => void;
}) { }) {
...@@ -132,7 +132,7 @@ export default function Editor({ ...@@ -132,7 +132,7 @@ export default function Editor({
], ],
editorState: textToEditorState(value, isRichText), editorState: textToEditorState(value, isRichText),
onError: (error: Error) => { onError: (error: Error) => {
throw error; console.error('Lexical errror', error);
} }
}; };
...@@ -185,8 +185,6 @@ export default function Editor({ ...@@ -185,8 +185,6 @@ export default function Editor({
maxHeight: `${maxH}px`, maxHeight: `${maxH}px`,
...boxStyle ...boxStyle
}} }}
onFocus={() => setFocus(true)}
onBlur={() => setFocus(false)}
/> />
} }
placeholder={<Placeholder padding={placeholderPadding}>{placeholder}</Placeholder>} placeholder={<Placeholder padding={placeholderPadding}>{placeholder}</Placeholder>}
...@@ -234,7 +232,7 @@ export default function Editor({ ...@@ -234,7 +232,7 @@ export default function Editor({
const rootElement = editor.getRootElement(); const rootElement = editor.getRootElement();
setScrollHeight(rootElement?.scrollHeight || 0); setScrollHeight(rootElement?.scrollHeight || 0);
startSts(() => { startSts(() => {
onChange?.(editorState, editor); onChange?.(editor);
}); });
}} }}
/> />
......
...@@ -5,7 +5,7 @@ import type { EditorProps } from './Editor'; ...@@ -5,7 +5,7 @@ import type { EditorProps } from './Editor';
import Editor from './Editor'; import Editor from './Editor';
import MyModal from '../../MyModal'; import MyModal from '../../MyModal';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import type { EditorState, LexicalEditor } from 'lexical'; import type { LexicalEditor } from 'lexical';
import type { FormPropsType } from './type'; import type { FormPropsType } from './type';
const PromptEditor = ({ const PromptEditor = ({
...@@ -18,7 +18,8 @@ const PromptEditor = ({ ...@@ -18,7 +18,8 @@ const PromptEditor = ({
isDisabled, isDisabled,
...props ...props
}: FormPropsType & }: FormPropsType &
EditorProps & { Omit<EditorProps, 'value'> & {
value?: string;
title?: string; title?: string;
isDisabled?: boolean; isDisabled?: boolean;
onChange?: (text: string) => void; onChange?: (text: string) => void;
...@@ -28,7 +29,7 @@ const PromptEditor = ({ ...@@ -28,7 +29,7 @@ const PromptEditor = ({
const { t } = useTranslation(); const { t } = useTranslation();
const onChangeInput = useCallback( const onChangeInput = useCallback(
(editorState: EditorState, editor: LexicalEditor) => { (editor: LexicalEditor) => {
const text = editorStateToText(editor); const text = editorStateToText(editor);
onChange?.(text); onChange?.(text);
}, },
...@@ -37,10 +38,8 @@ const PromptEditor = ({ ...@@ -37,10 +38,8 @@ const PromptEditor = ({
const onBlurInput = useCallback( const onBlurInput = useCallback(
(editor: LexicalEditor) => { (editor: LexicalEditor) => {
if (onBlur) { const text = editorStateToText(editor);
const text = editorStateToText(editor); onBlur?.(text);
onBlur(text);
}
}, },
[onBlur] [onBlur]
); );
......
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'; import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
import { import {
KEY_TAB_COMMAND, KEY_TAB_COMMAND,
COMMAND_PRIORITY_EDITOR, COMMAND_PRIORITY_HIGH,
$getSelection, $getSelection,
$isRangeSelection, $isRangeSelection,
$isTextNode $isTextNode
...@@ -62,19 +62,15 @@ export default function TabToSpacesPlugin(): null { ...@@ -62,19 +62,15 @@ export default function TabToSpacesPlugin(): null {
// Handle Shift+Tab (outdent) // Handle Shift+Tab (outdent)
if (isShiftTab) { if (isShiftTab) {
if (!selection.isCollapsed()) { if (!selection.isCollapsed()) {
// For selected text, remove 4 spaces from the beginning of each line // For selected text, remove 2 spaces from the beginning of each line
try { try {
const selectedText = selection.getTextContent(); const selectedText = selection.getTextContent();
const lines = selectedText.split('\n'); const lines = selectedText.split('\n');
const outdentedText = lines const outdentedText = lines
.map((line) => { .map((line) => {
// Remove up to 4 spaces from the beginning of the line // Remove up to 2 spaces from the beginning of the line
if (line.startsWith(' ')) { if (line.startsWith(' ')) {
return line.slice(4);
} else if (line.startsWith(' ')) {
return line.slice(3);
} else if (line.startsWith(' ')) {
return line.slice(2); return line.slice(2);
} else if (line.startsWith(' ')) { } else if (line.startsWith(' ')) {
return line.slice(1); return line.slice(1);
...@@ -128,7 +124,7 @@ export default function TabToSpacesPlugin(): null { ...@@ -128,7 +124,7 @@ export default function TabToSpacesPlugin(): null {
// Check if there are spaces before cursor to remove // Check if there are spaces before cursor to remove
let spacesToRemove = 0; let spacesToRemove = 0;
for (let i = beforeCursor.length - 1; i >= 0 && spacesToRemove < 4; i--) { for (let i = beforeCursor.length - 1; i >= 0 && spacesToRemove < 2; i--) {
if (beforeCursor[i] === ' ') { if (beforeCursor[i] === ' ') {
spacesToRemove++; spacesToRemove++;
} else { } else {
...@@ -159,7 +155,7 @@ export default function TabToSpacesPlugin(): null { ...@@ -159,7 +155,7 @@ export default function TabToSpacesPlugin(): null {
try { try {
const selectedText = selection.getTextContent(); const selectedText = selection.getTextContent();
const lines = selectedText.split('\n'); const lines = selectedText.split('\n');
const indentedText = lines.map((line) => ' ' + line).join('\n'); const indentedText = lines.map((line) => ' ' + line).join('\n');
// Insert the indented text and let Lexical handle cursor positioning // Insert the indented text and let Lexical handle cursor positioning
selection.insertText(indentedText); selection.insertText(indentedText);
...@@ -191,13 +187,13 @@ export default function TabToSpacesPlugin(): null { ...@@ -191,13 +187,13 @@ export default function TabToSpacesPlugin(): null {
return true; return true;
} catch (e) { } catch (e) {
// If selection operation fails, fall back to simple space insertion // If selection operation fails, fall back to simple space insertion
const textNode = $createTextNode(' '); const textNode = $createTextNode(' ');
selection.insertNodes([textNode]); selection.insertNodes([textNode]);
return true; return true;
} }
} else { } else {
// For cursor position (no selection), insert 4 spaces // For cursor position (no selection), insert 2 spaces
const textNode = $createTextNode(' '); // 4 spaces const textNode = $createTextNode(' '); // 2 spaces
selection.insertNodes([textNode]); selection.insertNodes([textNode]);
return true; return true;
} }
...@@ -208,7 +204,7 @@ export default function TabToSpacesPlugin(): null { ...@@ -208,7 +204,7 @@ export default function TabToSpacesPlugin(): null {
return false; return false;
} }
}, },
COMMAND_PRIORITY_EDITOR COMMAND_PRIORITY_HIGH
); );
}, [editor]); }, [editor]);
......
...@@ -20,6 +20,7 @@ import type { ...@@ -20,6 +20,7 @@ import type {
ListItemInfo, ListItemInfo,
ChildEditorNode ChildEditorNode
} from './type'; } from './type';
import { TabStr } from './constants';
export function registerLexicalTextEntity<T extends TextNode | VariableLabelNode | VariableNode>( export function registerLexicalTextEntity<T extends TextNode | VariableLabelNode | VariableNode>(
editor: LexicalEditor, editor: LexicalEditor,
...@@ -186,7 +187,8 @@ export function registerLexicalTextEntity<T extends TextNode | VariableLabelNode ...@@ -186,7 +187,8 @@ export function registerLexicalTextEntity<T extends TextNode | VariableLabelNode
// text to editor state // text to editor state
const parseTextLine = (line: string) => { const parseTextLine = (line: string) => {
const trimmed = line.trimStart(); const trimmed = line.trimStart();
const indentLevel = Math.floor((line.length - trimmed.length) / 2); const leadingSpaces = line.length - trimmed.length;
const indentLevel = Math.floor(leadingSpaces / TabStr.length);
const bulletMatch = trimmed.match(/^- (.*)$/); const bulletMatch = trimmed.match(/^- (.*)$/);
if (bulletMatch) { if (bulletMatch) {
...@@ -203,7 +205,8 @@ const parseTextLine = (line: string) => { ...@@ -203,7 +205,8 @@ const parseTextLine = (line: string) => {
}; };
} }
return { type: 'paragraph', text: trimmed, indent: indentLevel }; // For paragraphs, preserve original leading spaces in text (don't use indent)
return { type: 'paragraph', text: line, indent: 0 };
}; };
const buildListStructure = (items: ListItemInfo[]) => { const buildListStructure = (items: ListItemInfo[]) => {
...@@ -326,7 +329,7 @@ export const textToEditorState = (text = '', isRichText = false) => { ...@@ -326,7 +329,7 @@ export const textToEditorState = (text = '', isRichText = false) => {
], ],
direction: 'ltr', direction: 'ltr',
format: '', format: '',
indent: parsed.indent, indent: 0, // Always use 0 for paragraphs, spaces are in text content
type: 'paragraph', type: 'paragraph',
version: 1 version: 1
}); });
...@@ -426,7 +429,7 @@ const processListItem = ({ ...@@ -426,7 +429,7 @@ const processListItem = ({
} else if (child.type === 'text') { } else if (child.type === 'text') {
itemText.push(child.text); itemText.push(child.text);
} else if (child.type === 'tab') { } else if (child.type === 'tab') {
itemText.push(' '); itemText.push(TabStr);
} else if (child.type === 'variableLabel' || child.type === 'Variable') { } else if (child.type === 'variableLabel' || child.type === 'Variable') {
itemText.push(child.variableKey); itemText.push(child.variableKey);
} else if (child.type === 'list') { } else if (child.type === 'list') {
...@@ -434,9 +437,9 @@ const processListItem = ({ ...@@ -434,9 +437,9 @@ const processListItem = ({
} }
}); });
// Add prefix and indent // Add prefix and indent (using TabStr for consistency)
const itemTextString = itemText.join('').trim(); const itemTextString = itemText.join('');
const indent = ' '.repeat(indentLevel); const indent = TabStr.repeat(indentLevel);
const prefix = listType === 'bullet' ? '- ' : `${index + 1}. `; const prefix = listType === 'bullet' ? '- ' : `${index + 1}. `;
results.push(indent + prefix + itemTextString); results.push(indent + prefix + itemTextString);
...@@ -483,7 +486,7 @@ export const editorStateToText = (editor: LexicalEditor) => { ...@@ -483,7 +486,7 @@ export const editorStateToText = (editor: LexicalEditor) => {
// Handle tab nodes // Handle tab nodes
if (node.type === 'tab') { if (node.type === 'tab') {
return ' '; return TabStr;
} }
// Handle text nodes // Handle text nodes
...@@ -549,15 +552,14 @@ export const editorStateToText = (editor: LexicalEditor) => { ...@@ -549,15 +552,14 @@ export const editorStateToText = (editor: LexicalEditor) => {
const children = paragraph.children; const children = paragraph.children;
const paragraphText: string[] = []; const paragraphText: string[] = [];
const indentSpaces = ' '.repeat(paragraph.indent || 0); // Don't add indent prefix for paragraphs, spaces are already in text content
children.forEach((child) => { children.forEach((child) => {
const val = extractText(child); const val = extractText(child);
paragraphText.push(val); paragraphText.push(val);
}); });
const finalText = paragraphText.join(''); const finalText = paragraphText.join('');
editorStateTextString.push(indentSpaces + finalText); editorStateTextString.push(finalText);
} else { } else {
const text = extractText(paragraph); const text = extractText(paragraph);
editorStateTextString.push(text); editorStateTextString.push(text);
......
...@@ -70,6 +70,7 @@ const InputRender = (props: InputRenderProps) => { ...@@ -70,6 +70,7 @@ const InputRender = (props: InputRenderProps) => {
maxLength={props.maxLength} maxLength={props.maxLength}
minH={40} minH={40}
maxH={120} maxH={120}
isRichText={props.isRichText}
/> />
); );
} }
......
...@@ -218,7 +218,7 @@ const RenderUserFormInteractive = React.memo(function RenderFormInput({ ...@@ -218,7 +218,7 @@ const RenderUserFormInteractive = React.memo(function RenderFormInput({
const defaultValues = useMemo(() => { const defaultValues = useMemo(() => {
if (interactive.type === 'userInput') { if (interactive.type === 'userInput') {
return interactive.params.inputForm?.reduce((acc: Record<string, any>, item, index) => { return interactive.params.inputForm?.reduce((acc: Record<string, any>, item, index) => {
acc[`field_${index}`] = !!item.value ? item.value : item.defaultValue; acc[item.key] = !!item.value ? item.value : item.defaultValue;
return acc; return acc;
}, {}); }, {});
} }
...@@ -229,9 +229,8 @@ const RenderUserFormInteractive = React.memo(function RenderFormInput({ ...@@ -229,9 +229,8 @@ const RenderUserFormInteractive = React.memo(function RenderFormInput({
(data: Record<string, any>) => { (data: Record<string, any>) => {
const finalData: Record<string, any> = {}; const finalData: Record<string, any> = {};
interactive.params.inputForm?.forEach((item, index) => { interactive.params.inputForm?.forEach((item, index) => {
const fieldName = `field_${index}`; if (item.key in data) {
if (fieldName in data) { finalData[item.key] = data[item.key];
finalData[item.label] = data[fieldName];
} }
}); });
......
import React, { useCallback } from 'react'; import React from 'react';
import { Box, Flex } from '@chakra-ui/react'; import { Box, Flex } from '@chakra-ui/react';
import { Controller, useForm, type UseFormHandleSubmit } from 'react-hook-form'; import { Controller, useForm, type UseFormHandleSubmit } from 'react-hook-form';
import Markdown from '@/components/Markdown'; import Markdown from '@/components/Markdown';
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip'; import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
import { import {
type UserInputFormItemType,
type UserInputInteractive, type UserInputInteractive,
type UserSelectInteractive, type UserSelectInteractive,
type UserSelectOptionItemType type UserSelectOptionItemType
...@@ -64,7 +63,7 @@ export const SelectOptionsComponent = React.memo(function SelectOptionsComponent ...@@ -64,7 +63,7 @@ export const SelectOptionsComponent = React.memo(function SelectOptionsComponent
}); });
export const FormInputComponent = React.memo(function FormInputComponent({ export const FormInputComponent = React.memo(function FormInputComponent({
interactiveParams, interactiveParams: { description, inputForm, submitted },
defaultValues = {}, defaultValues = {},
SubmitButton SubmitButton
}: { }: {
...@@ -72,58 +71,49 @@ export const FormInputComponent = React.memo(function FormInputComponent({ ...@@ -72,58 +71,49 @@ export const FormInputComponent = React.memo(function FormInputComponent({
defaultValues?: Record<string, any>; defaultValues?: Record<string, any>;
SubmitButton: (e: { onSubmit: UseFormHandleSubmit<Record<string, any>> }) => React.JSX.Element; SubmitButton: (e: { onSubmit: UseFormHandleSubmit<Record<string, any>> }) => React.JSX.Element;
}) { }) {
const { description, inputForm, submitted } = interactiveParams;
const { handleSubmit, control } = useForm({ const { handleSubmit, control } = useForm({
defaultValues defaultValues
}); });
const RenderFormInput = useCallback(
({ input, index }: { input: UserInputFormItemType; index: number }) => {
return (
<Controller
key={input.label}
control={control}
name={`field_${index}`}
rules={{ required: input.required }}
render={({ field: { onChange, value }, fieldState: { error } }) => {
const inputType = nodeInputTypeToInputType([input.type]);
return (
<InputRender
inputType={inputType}
value={value}
onChange={onChange}
isDisabled={submitted}
isInvalid={!!error}
maxLength={input.maxLength}
min={input.min}
max={input.max}
list={input.list}
isRichText={false}
/>
);
}}
/>
);
},
[control, submitted]
);
return ( return (
<Box> <Box>
<DescriptionBox description={description} /> <DescriptionBox description={description} />
<Flex flexDirection={'column'} gap={3}> <Flex flexDirection={'column'} gap={3}>
{inputForm.map((input, index) => ( {inputForm.map((input) => {
<Box key={input.label}> const inputType = nodeInputTypeToInputType([input.type]);
<Flex alignItems={'center'} mb={1}>
{input.required && <Box color={'red.500'}>*</Box>} return (
<FormLabel>{input.label}</FormLabel> <Box key={input.key}>
{input.description && <QuestionTip ml={1} label={input.description} />} <Flex alignItems={'center'} mb={1}>
</Flex> {input.required && <Box color={'red.500'}>*</Box>}
<RenderFormInput input={input} index={index} /> <FormLabel>{input.label}</FormLabel>
</Box> {input.description && <QuestionTip ml={1} label={input.description} />}
))} </Flex>
<Controller
key={input.key} // 添加 key
control={control}
name={input.key}
rules={{ required: input.required }}
render={({ field: { onChange, value }, fieldState: { error } }) => {
return (
<InputRender
inputType={inputType}
value={value}
onChange={onChange}
isDisabled={submitted}
isInvalid={!!error}
maxLength={input.maxLength}
min={input.min}
max={input.max}
list={input.list}
isRichText={false}
/>
);
}}
/>
</Box>
);
})}
</Flex> </Flex>
{!submitted && ( {!submitted && (
......
...@@ -11,14 +11,9 @@ import { ...@@ -11,14 +11,9 @@ import {
SelectOptionsComponent SelectOptionsComponent
} from '@/components/core/chat/components/Interactive/InteractiveComponents'; } from '@/components/core/chat/components/Interactive/InteractiveComponents';
import { type UserInputInteractive } from '@fastgpt/global/core/workflow/template/system/interactive/type'; import { type UserInputInteractive } from '@fastgpt/global/core/workflow/template/system/interactive/type';
import {
getLastInteractiveValue,
storeEdges2RuntimeEdges
} from '@fastgpt/global/core/workflow/runtime/utils';
import { type ChatItemType, type UserChatItemValueItemType } from '@fastgpt/global/core/chat/type'; import { type ChatItemType, type UserChatItemValueItemType } from '@fastgpt/global/core/chat/type';
import { ChatItemValueTypeEnum, ChatRoleEnum } from '@fastgpt/global/core/chat/constants'; import { ChatItemValueTypeEnum, ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
import PopoverConfirm from '@fastgpt/web/components/common/MyPopover/PopoverConfirm'; import PopoverConfirm from '@fastgpt/web/components/common/MyPopover/PopoverConfirm';
import MyIconButton from '@fastgpt/web/components/common/Icon/button';
import { WorkflowActionsContext } from '../../../../context/workflowActionsContext'; import { WorkflowActionsContext } from '../../../../context/workflowActionsContext';
import { WorkflowDebugContext } from '../../../../context/workflowDebugContext'; import { WorkflowDebugContext } from '../../../../context/workflowDebugContext';
...@@ -27,7 +22,7 @@ type NodeDebugResponseProps = { ...@@ -27,7 +22,7 @@ type NodeDebugResponseProps = {
debugResult: FlowNodeItemType['debugResult']; debugResult: FlowNodeItemType['debugResult'];
}; };
const RenderUserFormInteractive = React.memo(function RenderFormInput({ const RenderUserFormInteractive = function RenderFormInput({
interactive, interactive,
onNext onNext
}: { }: {
...@@ -38,13 +33,13 @@ const RenderUserFormInteractive = React.memo(function RenderFormInput({ ...@@ -38,13 +33,13 @@ const RenderUserFormInteractive = React.memo(function RenderFormInput({
const defaultValues = useMemo(() => { const defaultValues = useMemo(() => {
return interactive.params.inputForm?.reduce((acc: Record<string, any>, item) => { return interactive.params.inputForm?.reduce((acc: Record<string, any>, item) => {
acc[item.label] = !!item.value ? item.value : item.defaultValue; acc[item.key] = item.value !== undefined ? item.value : item.defaultValue;
return acc; return acc;
}, {}); }, {});
}, [interactive.params.inputForm]); }, [interactive.params.inputForm]);
return ( return (
<Box px={4} py={4} bg="white" borderRadius="md"> <Box className="nodrag" px={4} py={4} bg="white" borderRadius="md">
<FormInputComponent <FormInputComponent
defaultValues={defaultValues} defaultValues={defaultValues}
interactiveParams={interactive.params} interactiveParams={interactive.params}
...@@ -63,7 +58,7 @@ const RenderUserFormInteractive = React.memo(function RenderFormInput({ ...@@ -63,7 +58,7 @@ const RenderUserFormInteractive = React.memo(function RenderFormInput({
/> />
</Box> </Box>
); );
}); };
const NodeDebugResponse = ({ nodeId, debugResult }: NodeDebugResponseProps) => { const NodeDebugResponse = ({ nodeId, debugResult }: NodeDebugResponseProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
...@@ -120,8 +115,8 @@ const NodeDebugResponse = ({ nodeId, debugResult }: NodeDebugResponseProps) => { ...@@ -120,8 +115,8 @@ const NodeDebugResponse = ({ nodeId, debugResult }: NodeDebugResponseProps) => {
type: ChatItemValueTypeEnum.interactive, type: ChatItemValueTypeEnum.interactive,
interactive: { interactive: {
...interactive, ...interactive,
entryNodeIds: workflowDebugData.entryNodeIds || [],
memoryEdges: interactive.memoryEdges || [], memoryEdges: interactive.memoryEdges || [],
entryNodeIds: interactive.entryNodeIds || [],
nodeOutputs: interactive.nodeOutputs || [] nodeOutputs: interactive.nodeOutputs || []
} }
} }
......
...@@ -94,7 +94,8 @@ async function handler(req: ApiRequestProps<CreateAppBody>) { ...@@ -94,7 +94,8 @@ async function handler(req: ApiRequestProps<CreateAppBody>) {
teamId, teamId,
tmbId, tmbId,
userAvatar: tmb?.avatar, userAvatar: tmb?.avatar,
username: tmb?.user?.username username: tmb?.user?.username,
templateId
}); });
pushTrack.createApp({ pushTrack.createApp({
......
...@@ -27,7 +27,7 @@ async function handler( ...@@ -27,7 +27,7 @@ async function handler(
}) })
}); });
return mcpClient.toolCall(toolName, params); return mcpClient.toolCall({ toolName, params });
} }
export default NextAPI(handler); export default NextAPI(handler);
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment