mirror of
https://github.com/labring/FastGPT.git
synced 2025-12-25 20:02:47 +00:00
fix: document deploy (#5868)
* mcp memory * Editor space parse error * fix: ts * fix: debug interactive * fix: templateId * fix: editor in debug * doc
This commit is contained in:
parent
89ca81d1a4
commit
7aaa28ed08
|
|
@ -0,0 +1,384 @@
|
|||
# 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 系统插件设计方案
|
|||
- **lib**: 库文件,提供工具函数和类库
|
||||
- **test**: 测试相关
|
||||
|
||||
系统工具的结构可以参考 [如何开发系统工具](/docs/introduction/guide/plugins/dev_system_tool)。
|
||||
系统工具的结构可以参考 [如何开发系统插件](/docs/introduction/guide/plugins/dev_system_tool)。
|
||||
|
||||
## 技术细节
|
||||
|
||||
|
|
|
|||
|
|
@ -1,14 +1,17 @@
|
|||
---
|
||||
title: 如何开发系统工具
|
||||
description: FastGPT 系统工具开发指南
|
||||
title: 如何开发系统插件
|
||||
description: FastGPT 系统插件开发指南(工具篇)
|
||||
---
|
||||
|
||||
## 介绍
|
||||
|
||||
FastGPT 系统工具项目从 4.10.0 版本后移动到独立的`fastgpt-plugin`项目中,采用纯代码的模式进行工具编写。
|
||||
FastGPT 系统插件项目从 4.10.0 版本后移动到独立的`fastgpt-plugin`项目中,采用纯代码的模式进行工具编写。
|
||||
在 4.14.0 版本插件市场更新后,系统工具开发流程有所改变,请依照最新文档贡献代码。
|
||||
|
||||
你可以在`fastgpt-plugin`项目中进行独立开发和调试好插件后,直接向 FastGPT 官方提交 PR 即可,无需运行 FastGPT 主服务。
|
||||
|
||||
目前系统插件仅支持“工具”这一种类型。
|
||||
|
||||
## 概念
|
||||
|
||||
- 工具(Tool):最小的运行单元,每个工具都有唯一 ID 和特定的输入和输出。
|
||||
|
|
|
|||
|
|
@ -111,6 +111,7 @@ description: FastGPT 文档目录
|
|||
- [/docs/upgrading/4-13/4131](/docs/upgrading/4-13/4131)
|
||||
- [/docs/upgrading/4-13/4132](/docs/upgrading/4-13/4132)
|
||||
- [/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/41](/docs/upgrading/4-8/41)
|
||||
- [/docs/upgrading/4-8/42](/docs/upgrading/4-8/42)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
---
|
||||
title: 'V4.14.0(包含升级脚本)'
|
||||
description: 'FastGPT V4.14.0 更新说明'
|
||||
---
|
||||
|
||||
|
||||
|
||||
## 🚀 新增内容
|
||||
|
||||
|
||||
## ⚙️ 优化
|
||||
|
||||
1. 在同一轮对话中,MCP Client 会持久化实例,不会销毁。
|
||||
|
||||
## 🐛 修复
|
||||
|
||||
1. Debug 模式下,交互节点无法正常使用。
|
||||
2. 富文本编辑器 tab 空格未对齐。
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"title": "4.14.x",
|
||||
"description": "",
|
||||
"pages": ["4141", "4140"]
|
||||
}
|
||||
|
|
@ -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/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/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/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",
|
||||
|
|
@ -101,7 +101,7 @@
|
|||
"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/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/4101.mdx": "2025-09-08T20:07:20+08:00",
|
||||
"document/content/docs/upgrading/4-11/4110.mdx": "2025-08-05T23:20:39+08:00",
|
||||
|
|
@ -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/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-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/41.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}
|
|||
if (chunkLength > maxSize) {
|
||||
const newChunks = commonSplit({
|
||||
...props,
|
||||
text: chunk
|
||||
text: chunk.replace(defaultChunk, '').trim()
|
||||
}).chunks;
|
||||
chunks.push(...newChunks);
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -89,6 +89,8 @@ export type ModuleDispatchProps<T> = ChatDispatchProps & {
|
|||
runtimeNodes: RuntimeNodeItemType[];
|
||||
runtimeEdges: RuntimeEdgeItemType[];
|
||||
params: T;
|
||||
|
||||
mcpClientMemory: Record<string, MCPClient>; // key: url
|
||||
};
|
||||
|
||||
export type SystemVariablesType = {
|
||||
|
|
|
|||
|
|
@ -59,9 +59,10 @@ export class MCPClient {
|
|||
}
|
||||
|
||||
// 内部方法:关闭连接
|
||||
private async closeConnection() {
|
||||
async closeConnection() {
|
||||
try {
|
||||
await retryFn(() => this.client.close(), 3);
|
||||
addLog.debug(`[MCP Client] Closed connection:${this.url}`);
|
||||
} catch (error) {
|
||||
addLog.error('[MCP Client] Failed to close connection:', error);
|
||||
}
|
||||
|
|
@ -110,7 +111,15 @@ export class MCPClient {
|
|||
* @param params Parameters
|
||||
* @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 {
|
||||
const client = await this.getConnection();
|
||||
addLog.debug(`[MCP Client] Call tool: ${toolName}`, params);
|
||||
|
|
@ -129,7 +138,9 @@ export class MCPClient {
|
|||
addLog.error(`[MCP Client] Failed to call tool ${toolName}:`, error);
|
||||
return Promise.reject(error);
|
||||
} finally {
|
||||
await this.closeConnection();
|
||||
if (closeConnection) {
|
||||
await this.closeConnection();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@ import { getNodeErrResponse } from '../utils';
|
|||
import { splitCombineToolId } from '@fastgpt/global/core/app/tool/utils';
|
||||
import { getAppVersionById } from '../../../../core/app/version/controller';
|
||||
import { runHTTPTool } from '../../../app/http';
|
||||
import { i18nT } from '../../../../../web/i18n/utils';
|
||||
|
||||
type SystemInputConfigType = {
|
||||
type: SystemToolSecretInputTypeEnum;
|
||||
|
|
@ -50,6 +49,7 @@ export const dispatchRunTool = async (props: RunToolProps): Promise<RunToolRespo
|
|||
runningAppInfo,
|
||||
variables,
|
||||
workflowStreamResponse,
|
||||
|
||||
node: { name, avatar, toolConfig, version, catchError }
|
||||
} = props;
|
||||
|
||||
|
|
@ -201,14 +201,19 @@ export const dispatchRunTool = async (props: RunToolProps): Promise<RunToolRespo
|
|||
|
||||
const { headerSecret, url } =
|
||||
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 {
|
||||
data: { [NodeOutputKeyEnum.rawResponse]: result },
|
||||
[DispatchNodeResponseKeyEnum.nodeResponse]: {
|
||||
|
|
@ -285,7 +290,7 @@ export const dispatchRunTool = async (props: RunToolProps): Promise<RunToolRespo
|
|||
storeSecret: headerSecret
|
||||
})
|
||||
});
|
||||
const result = await mcpClient.toolCall(toolName, restParams);
|
||||
const result = await mcpClient.toolCall({ toolName, params: restParams });
|
||||
|
||||
return {
|
||||
data: {
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ import { createChatUsageRecord, pushChatItemUsage } from '../../../support/walle
|
|||
import type { RequireOnlyOne } from '@fastgpt/global/common/type/utils';
|
||||
import { getS3ChatSource } from '../../../common/s3/sources/chat';
|
||||
import { addPreviewUrlToChatItems } from '../../chat/utils';
|
||||
import type { MCPClient } from '../../app/mcp';
|
||||
|
||||
type Props = Omit<ChatDispatchProps, 'workflowDispatchDeep' | 'timezone' | 'externalProvider'> & {
|
||||
runtimeNodes: RuntimeNodeItemType[];
|
||||
|
|
@ -152,6 +153,8 @@ export async function dispatchWorkFlow({
|
|||
}))
|
||||
};
|
||||
|
||||
let mcpClientMemory = {} as Record<string, MCPClient>;
|
||||
|
||||
// Init some props
|
||||
return runWorkflow({
|
||||
...data,
|
||||
|
|
@ -163,17 +166,24 @@ export async function dispatchWorkFlow({
|
|||
variables: defaultVariables,
|
||||
workflowDispatchDeep: 0,
|
||||
usageId: newUsageId,
|
||||
concatUsage
|
||||
concatUsage,
|
||||
mcpClientMemory
|
||||
}).finally(() => {
|
||||
if (streamCheckTimer) {
|
||||
clearInterval(streamCheckTimer);
|
||||
}
|
||||
|
||||
// Close mcpClient connections
|
||||
Object.values(mcpClientMemory).forEach((client) => {
|
||||
client.closeConnection();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
type RunWorkflowProps = ChatDispatchProps & {
|
||||
runtimeNodes: RuntimeNodeItemType[];
|
||||
runtimeEdges: RuntimeEdgeItemType[];
|
||||
mcpClientMemory: Record<string, MCPClient>;
|
||||
defaultSkipNodeQueue?: WorkflowDebugResponse['skipNodeQueue'];
|
||||
concatUsage?: (points: number) => any;
|
||||
};
|
||||
|
|
@ -192,7 +202,8 @@ export const runWorkflow = async (data: RunWorkflowProps): Promise<DispatchFlowR
|
|||
responseAllData = true,
|
||||
usageId,
|
||||
concatUsage,
|
||||
runningUserInfo: { teamId }
|
||||
runningUserInfo: { teamId },
|
||||
mcpClientMemory
|
||||
} = data;
|
||||
|
||||
// Over max depth
|
||||
|
|
@ -458,7 +469,8 @@ export const runWorkflow = async (data: RunWorkflowProps): Promise<DispatchFlowR
|
|||
|
||||
const dispatchData: ModuleDispatchProps<Record<string, any>> = {
|
||||
...data,
|
||||
lastInteractive: data.lastInteractive?.entryNodeIds.includes(node.nodeId)
|
||||
mcpClientMemory,
|
||||
lastInteractive: data.lastInteractive?.entryNodeIds?.includes(node.nodeId)
|
||||
? data.lastInteractive
|
||||
: undefined,
|
||||
variables,
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ import FocusPlugin from '../../Textarea/PromptEditor/plugins/FocusPlugin';
|
|||
import VariableLabelPlugin from '../../Textarea/PromptEditor/plugins/VariableLabelPlugin';
|
||||
import { VariableLabelNode } from '../../Textarea/PromptEditor/plugins/VariableLabelPlugin/node';
|
||||
import VariableLabelPickerPlugin from '../../Textarea/PromptEditor/plugins/VariableLabelPickerPlugin';
|
||||
import { useDeepCompareEffect } from 'ahooks';
|
||||
|
||||
export default function Editor({
|
||||
h = 40,
|
||||
|
|
@ -45,7 +46,7 @@ export default function Editor({
|
|||
h?: number;
|
||||
variables: EditorVariablePickerType[];
|
||||
variableLabels: EditorVariableLabelPickerType[];
|
||||
onChange?: (editorState: EditorState, editor: LexicalEditor) => void;
|
||||
onChange?: (editor: LexicalEditor) => void;
|
||||
onBlur?: (editor: LexicalEditor) => void;
|
||||
value?: string;
|
||||
currentValue?: string;
|
||||
|
|
@ -61,11 +62,11 @@ export default function Editor({
|
|||
nodes: [VariableNode, VariableLabelNode],
|
||||
editorState: textToEditorState(value),
|
||||
onError: (error: Error) => {
|
||||
throw error;
|
||||
console.error('Lexical errror', error);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
useDeepCompareEffect(() => {
|
||||
if (focus) return;
|
||||
setKey(getNanoid(6));
|
||||
}, [value, variables.length]);
|
||||
|
|
@ -119,7 +120,7 @@ export default function Editor({
|
|||
<OnChangePlugin
|
||||
onChange={(editorState: EditorState, editor: LexicalEditor) => {
|
||||
startSts(() => {
|
||||
onChange?.(editorState, editor);
|
||||
onChange?.(editor);
|
||||
});
|
||||
}}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ const HttpInput = ({
|
|||
const [currentValue, setCurrentValue] = React.useState(value);
|
||||
|
||||
const onChangeInput = useCallback(
|
||||
(editorState: EditorState, editor: LexicalEditor) => {
|
||||
(editor: LexicalEditor) => {
|
||||
const text = editorStateToText(editor);
|
||||
setCurrentValue(text);
|
||||
onChange?.(text);
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
*/
|
||||
|
||||
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 { PlainTextPlugin } from '@lexical/react/LexicalPlainTextPlugin';
|
||||
import { RichTextPlugin } from '@lexical/react/LexicalRichTextPlugin';
|
||||
|
|
@ -72,7 +72,7 @@ export type EditorProps = {
|
|||
isRichText?: boolean;
|
||||
variables?: EditorVariablePickerType[];
|
||||
variableLabels?: EditorVariableLabelPickerType[];
|
||||
value?: string;
|
||||
value: string;
|
||||
showOpenModal?: boolean;
|
||||
minH?: number;
|
||||
maxH?: number;
|
||||
|
|
@ -100,7 +100,7 @@ export default function Editor({
|
|||
onChange,
|
||||
onChangeText,
|
||||
onBlur,
|
||||
value,
|
||||
value = '',
|
||||
placeholder = '',
|
||||
placeholderPadding = '12px 14px',
|
||||
bg = 'white',
|
||||
|
|
@ -111,7 +111,7 @@ export default function Editor({
|
|||
}: EditorProps &
|
||||
FormPropsType & {
|
||||
onOpenModal?: () => void;
|
||||
onChange: (editorState: EditorState, editor: LexicalEditor) => void;
|
||||
onChange: (editor: LexicalEditor) => void;
|
||||
onChangeText?: ((text: string) => void) | undefined;
|
||||
onBlur?: (editor: LexicalEditor) => void;
|
||||
}) {
|
||||
|
|
@ -132,7 +132,7 @@ export default function Editor({
|
|||
],
|
||||
editorState: textToEditorState(value, isRichText),
|
||||
onError: (error: Error) => {
|
||||
throw error;
|
||||
console.error('Lexical errror', error);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -185,8 +185,6 @@ export default function Editor({
|
|||
maxHeight: `${maxH}px`,
|
||||
...boxStyle
|
||||
}}
|
||||
onFocus={() => setFocus(true)}
|
||||
onBlur={() => setFocus(false)}
|
||||
/>
|
||||
}
|
||||
placeholder={<Placeholder padding={placeholderPadding}>{placeholder}</Placeholder>}
|
||||
|
|
@ -234,7 +232,7 @@ export default function Editor({
|
|||
const rootElement = editor.getRootElement();
|
||||
setScrollHeight(rootElement?.scrollHeight || 0);
|
||||
startSts(() => {
|
||||
onChange?.(editorState, editor);
|
||||
onChange?.(editor);
|
||||
});
|
||||
}}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
export const TabStr = ' ';
|
||||
|
|
@ -5,7 +5,7 @@ import type { EditorProps } from './Editor';
|
|||
import Editor from './Editor';
|
||||
import MyModal from '../../MyModal';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import type { EditorState, LexicalEditor } from 'lexical';
|
||||
import type { LexicalEditor } from 'lexical';
|
||||
import type { FormPropsType } from './type';
|
||||
|
||||
const PromptEditor = ({
|
||||
|
|
@ -18,7 +18,8 @@ const PromptEditor = ({
|
|||
isDisabled,
|
||||
...props
|
||||
}: FormPropsType &
|
||||
EditorProps & {
|
||||
Omit<EditorProps, 'value'> & {
|
||||
value?: string;
|
||||
title?: string;
|
||||
isDisabled?: boolean;
|
||||
onChange?: (text: string) => void;
|
||||
|
|
@ -28,7 +29,7 @@ const PromptEditor = ({
|
|||
const { t } = useTranslation();
|
||||
|
||||
const onChangeInput = useCallback(
|
||||
(editorState: EditorState, editor: LexicalEditor) => {
|
||||
(editor: LexicalEditor) => {
|
||||
const text = editorStateToText(editor);
|
||||
onChange?.(text);
|
||||
},
|
||||
|
|
@ -37,10 +38,8 @@ const PromptEditor = ({
|
|||
|
||||
const onBlurInput = useCallback(
|
||||
(editor: LexicalEditor) => {
|
||||
if (onBlur) {
|
||||
const text = editorStateToText(editor);
|
||||
onBlur(text);
|
||||
}
|
||||
const text = editorStateToText(editor);
|
||||
onBlur?.(text);
|
||||
},
|
||||
[onBlur]
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
|
||||
import {
|
||||
KEY_TAB_COMMAND,
|
||||
COMMAND_PRIORITY_EDITOR,
|
||||
COMMAND_PRIORITY_HIGH,
|
||||
$getSelection,
|
||||
$isRangeSelection,
|
||||
$isTextNode
|
||||
|
|
@ -62,19 +62,15 @@ export default function TabToSpacesPlugin(): null {
|
|||
// Handle Shift+Tab (outdent)
|
||||
if (isShiftTab) {
|
||||
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 {
|
||||
const selectedText = selection.getTextContent();
|
||||
const lines = selectedText.split('\n');
|
||||
|
||||
const outdentedText = lines
|
||||
.map((line) => {
|
||||
// Remove up to 4 spaces from the beginning of the line
|
||||
if (line.startsWith(' ')) {
|
||||
return line.slice(4);
|
||||
} else if (line.startsWith(' ')) {
|
||||
return line.slice(3);
|
||||
} else if (line.startsWith(' ')) {
|
||||
// Remove up to 2 spaces from the beginning of the line
|
||||
if (line.startsWith(' ')) {
|
||||
return line.slice(2);
|
||||
} else if (line.startsWith(' ')) {
|
||||
return line.slice(1);
|
||||
|
|
@ -128,7 +124,7 @@ export default function TabToSpacesPlugin(): null {
|
|||
|
||||
// Check if there are spaces before cursor to remove
|
||||
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] === ' ') {
|
||||
spacesToRemove++;
|
||||
} else {
|
||||
|
|
@ -159,7 +155,7 @@ export default function TabToSpacesPlugin(): null {
|
|||
try {
|
||||
const selectedText = selection.getTextContent();
|
||||
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
|
||||
selection.insertText(indentedText);
|
||||
|
|
@ -191,13 +187,13 @@ export default function TabToSpacesPlugin(): null {
|
|||
return true;
|
||||
} catch (e) {
|
||||
// If selection operation fails, fall back to simple space insertion
|
||||
const textNode = $createTextNode(' ');
|
||||
const textNode = $createTextNode(' ');
|
||||
selection.insertNodes([textNode]);
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
// For cursor position (no selection), insert 4 spaces
|
||||
const textNode = $createTextNode(' '); // 4 spaces
|
||||
// For cursor position (no selection), insert 2 spaces
|
||||
const textNode = $createTextNode(' '); // 2 spaces
|
||||
selection.insertNodes([textNode]);
|
||||
return true;
|
||||
}
|
||||
|
|
@ -208,7 +204,7 @@ export default function TabToSpacesPlugin(): null {
|
|||
return false;
|
||||
}
|
||||
},
|
||||
COMMAND_PRIORITY_EDITOR
|
||||
COMMAND_PRIORITY_HIGH
|
||||
);
|
||||
}, [editor]);
|
||||
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import type {
|
|||
ListItemInfo,
|
||||
ChildEditorNode
|
||||
} from './type';
|
||||
import { TabStr } from './constants';
|
||||
|
||||
export function registerLexicalTextEntity<T extends TextNode | VariableLabelNode | VariableNode>(
|
||||
editor: LexicalEditor,
|
||||
|
|
@ -186,7 +187,8 @@ export function registerLexicalTextEntity<T extends TextNode | VariableLabelNode
|
|||
// text to editor state
|
||||
const parseTextLine = (line: string) => {
|
||||
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(/^- (.*)$/);
|
||||
if (bulletMatch) {
|
||||
|
|
@ -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[]) => {
|
||||
|
|
@ -326,7 +329,7 @@ export const textToEditorState = (text = '', isRichText = false) => {
|
|||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: parsed.indent,
|
||||
indent: 0, // Always use 0 for paragraphs, spaces are in text content
|
||||
type: 'paragraph',
|
||||
version: 1
|
||||
});
|
||||
|
|
@ -426,7 +429,7 @@ const processListItem = ({
|
|||
} else if (child.type === 'text') {
|
||||
itemText.push(child.text);
|
||||
} else if (child.type === 'tab') {
|
||||
itemText.push(' ');
|
||||
itemText.push(TabStr);
|
||||
} else if (child.type === 'variableLabel' || child.type === 'Variable') {
|
||||
itemText.push(child.variableKey);
|
||||
} else if (child.type === 'list') {
|
||||
|
|
@ -434,9 +437,9 @@ const processListItem = ({
|
|||
}
|
||||
});
|
||||
|
||||
// Add prefix and indent
|
||||
const itemTextString = itemText.join('').trim();
|
||||
const indent = ' '.repeat(indentLevel);
|
||||
// Add prefix and indent (using TabStr for consistency)
|
||||
const itemTextString = itemText.join('');
|
||||
const indent = TabStr.repeat(indentLevel);
|
||||
const prefix = listType === 'bullet' ? '- ' : `${index + 1}. `;
|
||||
results.push(indent + prefix + itemTextString);
|
||||
|
||||
|
|
@ -483,7 +486,7 @@ export const editorStateToText = (editor: LexicalEditor) => {
|
|||
|
||||
// Handle tab nodes
|
||||
if (node.type === 'tab') {
|
||||
return ' ';
|
||||
return TabStr;
|
||||
}
|
||||
|
||||
// Handle text nodes
|
||||
|
|
@ -549,15 +552,14 @@ export const editorStateToText = (editor: LexicalEditor) => {
|
|||
const children = paragraph.children;
|
||||
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) => {
|
||||
const val = extractText(child);
|
||||
paragraphText.push(val);
|
||||
});
|
||||
|
||||
const finalText = paragraphText.join('');
|
||||
editorStateTextString.push(indentSpaces + finalText);
|
||||
editorStateTextString.push(finalText);
|
||||
} else {
|
||||
const text = extractText(paragraph);
|
||||
editorStateTextString.push(text);
|
||||
|
|
|
|||
|
|
@ -70,6 +70,7 @@ const InputRender = (props: InputRenderProps) => {
|
|||
maxLength={props.maxLength}
|
||||
minH={40}
|
||||
maxH={120}
|
||||
isRichText={props.isRichText}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -218,7 +218,7 @@ const RenderUserFormInteractive = React.memo(function RenderFormInput({
|
|||
const defaultValues = useMemo(() => {
|
||||
if (interactive.type === 'userInput') {
|
||||
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;
|
||||
}, {});
|
||||
}
|
||||
|
|
@ -229,9 +229,8 @@ const RenderUserFormInteractive = React.memo(function RenderFormInput({
|
|||
(data: Record<string, any>) => {
|
||||
const finalData: Record<string, any> = {};
|
||||
interactive.params.inputForm?.forEach((item, index) => {
|
||||
const fieldName = `field_${index}`;
|
||||
if (fieldName in data) {
|
||||
finalData[item.label] = data[fieldName];
|
||||
if (item.key in data) {
|
||||
finalData[item.key] = data[item.key];
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
import React, { useCallback } from 'react';
|
||||
import React from 'react';
|
||||
import { Box, Flex } from '@chakra-ui/react';
|
||||
import { Controller, useForm, type UseFormHandleSubmit } from 'react-hook-form';
|
||||
import Markdown from '@/components/Markdown';
|
||||
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
|
||||
import {
|
||||
type UserInputFormItemType,
|
||||
type UserInputInteractive,
|
||||
type UserSelectInteractive,
|
||||
type UserSelectOptionItemType
|
||||
|
|
@ -64,7 +63,7 @@ export const SelectOptionsComponent = React.memo(function SelectOptionsComponent
|
|||
});
|
||||
|
||||
export const FormInputComponent = React.memo(function FormInputComponent({
|
||||
interactiveParams,
|
||||
interactiveParams: { description, inputForm, submitted },
|
||||
defaultValues = {},
|
||||
SubmitButton
|
||||
}: {
|
||||
|
|
@ -72,58 +71,49 @@ export const FormInputComponent = React.memo(function FormInputComponent({
|
|||
defaultValues?: Record<string, any>;
|
||||
SubmitButton: (e: { onSubmit: UseFormHandleSubmit<Record<string, any>> }) => React.JSX.Element;
|
||||
}) {
|
||||
const { description, inputForm, submitted } = interactiveParams;
|
||||
|
||||
const { handleSubmit, control } = useForm({
|
||||
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 (
|
||||
<Box>
|
||||
<DescriptionBox description={description} />
|
||||
<Flex flexDirection={'column'} gap={3}>
|
||||
{inputForm.map((input, index) => (
|
||||
<Box key={input.label}>
|
||||
<Flex alignItems={'center'} mb={1}>
|
||||
{input.required && <Box color={'red.500'}>*</Box>}
|
||||
<FormLabel>{input.label}</FormLabel>
|
||||
{input.description && <QuestionTip ml={1} label={input.description} />}
|
||||
</Flex>
|
||||
<RenderFormInput input={input} index={index} />
|
||||
</Box>
|
||||
))}
|
||||
{inputForm.map((input) => {
|
||||
const inputType = nodeInputTypeToInputType([input.type]);
|
||||
|
||||
return (
|
||||
<Box key={input.key}>
|
||||
<Flex alignItems={'center'} mb={1}>
|
||||
{input.required && <Box color={'red.500'}>*</Box>}
|
||||
<FormLabel>{input.label}</FormLabel>
|
||||
{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>
|
||||
|
||||
{!submitted && (
|
||||
|
|
|
|||
|
|
@ -11,14 +11,9 @@ import {
|
|||
SelectOptionsComponent
|
||||
} from '@/components/core/chat/components/Interactive/InteractiveComponents';
|
||||
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 { ChatItemValueTypeEnum, ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
|
||||
import PopoverConfirm from '@fastgpt/web/components/common/MyPopover/PopoverConfirm';
|
||||
import MyIconButton from '@fastgpt/web/components/common/Icon/button';
|
||||
import { WorkflowActionsContext } from '../../../../context/workflowActionsContext';
|
||||
import { WorkflowDebugContext } from '../../../../context/workflowDebugContext';
|
||||
|
||||
|
|
@ -27,7 +22,7 @@ type NodeDebugResponseProps = {
|
|||
debugResult: FlowNodeItemType['debugResult'];
|
||||
};
|
||||
|
||||
const RenderUserFormInteractive = React.memo(function RenderFormInput({
|
||||
const RenderUserFormInteractive = function RenderFormInput({
|
||||
interactive,
|
||||
onNext
|
||||
}: {
|
||||
|
|
@ -38,13 +33,13 @@ const RenderUserFormInteractive = React.memo(function RenderFormInput({
|
|||
|
||||
const defaultValues = useMemo(() => {
|
||||
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;
|
||||
}, {});
|
||||
}, [interactive.params.inputForm]);
|
||||
|
||||
return (
|
||||
<Box px={4} py={4} bg="white" borderRadius="md">
|
||||
<Box className="nodrag" px={4} py={4} bg="white" borderRadius="md">
|
||||
<FormInputComponent
|
||||
defaultValues={defaultValues}
|
||||
interactiveParams={interactive.params}
|
||||
|
|
@ -63,7 +58,7 @@ const RenderUserFormInteractive = React.memo(function RenderFormInput({
|
|||
/>
|
||||
</Box>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const NodeDebugResponse = ({ nodeId, debugResult }: NodeDebugResponseProps) => {
|
||||
const { t } = useTranslation();
|
||||
|
|
@ -120,8 +115,8 @@ const NodeDebugResponse = ({ nodeId, debugResult }: NodeDebugResponseProps) => {
|
|||
type: ChatItemValueTypeEnum.interactive,
|
||||
interactive: {
|
||||
...interactive,
|
||||
entryNodeIds: workflowDebugData.entryNodeIds || [],
|
||||
memoryEdges: interactive.memoryEdges || [],
|
||||
entryNodeIds: interactive.entryNodeIds || [],
|
||||
nodeOutputs: interactive.nodeOutputs || []
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -94,7 +94,8 @@ async function handler(req: ApiRequestProps<CreateAppBody>) {
|
|||
teamId,
|
||||
tmbId,
|
||||
userAvatar: tmb?.avatar,
|
||||
username: tmb?.user?.username
|
||||
username: tmb?.user?.username,
|
||||
templateId
|
||||
});
|
||||
|
||||
pushTrack.createApp({
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ async function handler(
|
|||
})
|
||||
});
|
||||
|
||||
return mcpClient.toolCall(toolName, params);
|
||||
return mcpClient.toolCall({ toolName, params });
|
||||
}
|
||||
|
||||
export default NextAPI(handler);
|
||||
|
|
|
|||
Loading…
Reference in New Issue