🐛 fix: fix editor content missing when send error (#12205)

* fix editor issue

* add virtual block plugin

* snapshot case

* fix task Render issue
This commit is contained in:
Arvin Xu
2026-02-09 01:25:25 +08:00
committed by GitHub
parent 044e290ab0
commit ee7ae5b1d2
10 changed files with 581 additions and 12 deletions

View File

@@ -1,12 +1,14 @@
import type { Message } from '../../../../types';
import multiTasksWithSummary from './multi-tasks-with-summary.json';
import simple from './simple.json';
import singleTaskWithToolChain from './single-task-with-tool-chain.json';
import withAssistantGroup from './with-assistant-group.json';
import withSummary from './with-summary.json';
export const tasks = {
multiTasksWithSummary: multiTasksWithSummary as Message[],
simple: simple as Message[],
singleTaskWithToolChain: singleTaskWithToolChain as Message[],
withAssistantGroup: withAssistantGroup as Message[],
withSummary: withSummary as Message[],
};

View File

@@ -0,0 +1,158 @@
[
{
"id": "msg-user-1",
"role": "user",
"content": "Help me create an async task to write a 400-word short story and save it to Notebook.",
"parentId": null,
"createdAt": 1739032787856,
"updatedAt": 1739032787856
},
{
"id": "msg-assistant-1",
"role": "assistant",
"content": "",
"parentId": "msg-user-1",
"createdAt": 1739032788546,
"updatedAt": 1739032805810,
"model": "gpt-4",
"provider": "openai",
"tools": [
{
"id": "call_exec_task_1",
"type": "builtin",
"apiName": "execTask",
"arguments": "{\"description\":\"Write 400-word story and save to Notebook\",\"inheritMessages\":true,\"instruction\":\"Write a ~400 word short story, then save it to Notebook.\",\"timeout\":1800000}",
"identifier": "lobe-gtd"
}
],
"metadata": {
"tps": 31.55,
"cost": 0.024045,
"ttft": 3163,
"latency": 8774,
"duration": 5611,
"totalTokens": 12501,
"totalInputTokens": 12324,
"totalOutputTokens": 177
}
},
{
"id": "msg-tool-1",
"role": "tool",
"content": "🚀 Triggered async task for execution:\n- Write 400-word story and save to Notebook",
"parentId": "msg-assistant-1",
"tool_call_id": "call_exec_task_1",
"createdAt": 1739032808194,
"updatedAt": 1739032811207,
"plugin": {
"apiName": "execTask",
"arguments": "{\"description\":\"Write 400-word story and save to Notebook\",\"inheritMessages\":true,\"instruction\":\"Write a ~400 word short story, then save it to Notebook.\",\"timeout\":1800000}",
"identifier": "lobe-gtd",
"type": "builtin"
},
"pluginState": {
"task": {
"timeout": 1800000,
"description": "Write 400-word story and save to Notebook",
"instruction": "Write a ~400 word short story, then save it to Notebook.",
"inheritMessages": true
},
"type": "execTask",
"parentMessageId": "msg-tool-1"
}
},
{
"id": "msg-task-1",
"role": "task",
"content": "Once upon a time in a small village nestled between mountains, there lived an old watchmaker...",
"parentId": "msg-tool-1",
"createdAt": 1739032814033,
"updatedAt": 1739032856164,
"metadata": {
"taskTitle": "Write 400-word story and save to Notebook",
"instruction": "Write a ~400 word short story, then save it to Notebook."
},
"taskDetail": {
"duration": 36125,
"status": "completed",
"threadId": "thd_story_task",
"title": "Write 400-word story and save to Notebook",
"totalCost": 0.014107,
"totalMessages": 2,
"totalTokens": 4743,
"totalToolCalls": 0
}
},
{
"id": "msg-assistant-2",
"role": "assistant",
"content": "",
"parentId": "msg-tool-1",
"createdAt": 1739032858762,
"updatedAt": 1739032876226,
"model": "gpt-4",
"provider": "openai",
"tools": [
{
"id": "call_create_doc_1",
"type": "builtin",
"apiName": "createDocument",
"arguments": "{\"content\":\"Once upon a time in a small village...\",\"description\":\"A short story about time and farewell.\",\"title\":\"Eleven Oh Seven\",\"type\":\"article\"}",
"identifier": "lobe-notebook"
}
],
"metadata": {
"tps": 71.96,
"cost": 0.011641,
"ttft": 3135,
"latency": 11112,
"duration": 7977,
"totalTokens": 13578,
"totalInputTokens": 13004,
"totalOutputTokens": 574
}
},
{
"id": "msg-tool-2",
"role": "tool",
"content": "📝 Document \"Eleven Oh Seven\" created successfully",
"parentId": "msg-assistant-2",
"tool_call_id": "call_create_doc_1",
"createdAt": 1739032879049,
"updatedAt": 1739032883458,
"plugin": {
"apiName": "createDocument",
"arguments": "{\"content\":\"Once upon a time in a small village...\",\"description\":\"A short story about time and farewell.\",\"title\":\"Eleven Oh Seven\",\"type\":\"article\"}",
"identifier": "lobe-notebook",
"type": "builtin"
},
"pluginState": {
"document": {
"id": "docs_K9OeJoIj6UV82Ac7",
"title": "Eleven Oh Seven",
"content": "Once upon a time in a small village...",
"description": "A short story about time and farewell."
}
}
},
{
"id": "msg-assistant-summary",
"role": "assistant",
"content": "Async task completed. The story has been written and saved to Notebook as \"Eleven Oh Seven\".",
"parentId": "msg-tool-2",
"createdAt": 1739032886810,
"updatedAt": 1739032898616,
"model": "gpt-4",
"provider": "openai",
"metadata": {
"tps": 30.55,
"cost": 0.02389,
"ttft": 2787,
"latency": 5209,
"duration": 2422,
"totalTokens": 13133,
"totalInputTokens": 13059,
"totalOutputTokens": 74
}
}
]

View File

@@ -1,8 +1,10 @@
import type { SerializedParseResult } from '../../index';
import simple from './simple.json';
import singleTaskWithToolChain from './single-task-with-tool-chain.json';
import withSummary from './with-summary.json';
export const tasks = {
simple: simple as unknown as SerializedParseResult,
singleTaskWithToolChain: singleTaskWithToolChain as unknown as SerializedParseResult,
withSummary: withSummary as unknown as SerializedParseResult,
};

View File

@@ -0,0 +1,386 @@
{
"contextTree": [
{
"id": "msg-user-1",
"type": "message"
},
{
"children": [
{
"id": "msg-assistant-1",
"type": "message",
"tools": ["msg-tool-1"]
}
],
"id": "msg-assistant-1",
"type": "assistantGroup"
},
{
"id": "msg-tool-1",
"type": "message"
},
{
"activeBranchIndex": 1,
"branches": [
[
{
"id": "msg-task-1",
"type": "message"
}
],
[
{
"children": [
{
"id": "msg-assistant-2",
"type": "message",
"tools": ["msg-tool-2"]
},
{
"id": "msg-assistant-summary",
"type": "message"
}
],
"id": "msg-assistant-2",
"type": "assistantGroup"
}
]
],
"id": "branch-msg-tool-1-0",
"parentMessageId": "msg-tool-1",
"type": "branch"
}
],
"flatList": [
{
"id": "msg-user-1",
"role": "user",
"content": "Help me create an async task to write a 400-word short story and save it to Notebook.",
"parentId": null,
"createdAt": 1739032787856,
"updatedAt": 1739032787856
},
{
"id": "msg-assistant-1",
"role": "assistantGroup",
"content": "",
"parentId": "msg-user-1",
"createdAt": 1739032788546,
"updatedAt": 1739032805810,
"model": "gpt-4",
"provider": "openai",
"children": [
{
"content": "",
"id": "msg-assistant-1",
"performance": {
"duration": 5611,
"latency": 8774,
"tps": 31.55,
"ttft": 3163
},
"tools": [
{
"id": "call_exec_task_1",
"type": "builtin",
"apiName": "execTask",
"arguments": "{\"description\":\"Write 400-word story and save to Notebook\",\"inheritMessages\":true,\"instruction\":\"Write a ~400 word short story, then save it to Notebook.\",\"timeout\":1800000}",
"identifier": "lobe-gtd",
"result": {
"content": "🚀 Triggered async task for execution:\n- Write 400-word story and save to Notebook",
"id": "msg-tool-1",
"state": {
"task": {
"timeout": 1800000,
"description": "Write 400-word story and save to Notebook",
"instruction": "Write a ~400 word short story, then save it to Notebook.",
"inheritMessages": true
},
"type": "execTask",
"parentMessageId": "msg-tool-1"
}
},
"result_msg_id": "msg-tool-1"
}
],
"usage": {
"cost": 0.024045,
"totalInputTokens": 12324,
"totalOutputTokens": 177,
"totalTokens": 12501
}
}
],
"performance": {
"ttft": 3163,
"duration": 5611,
"latency": 8774,
"tps": 31.55
},
"usage": {
"totalInputTokens": 12324,
"totalOutputTokens": 177,
"totalTokens": 12501,
"cost": 0.024045
}
},
{
"id": "msg-task-1",
"role": "task",
"content": "Once upon a time in a small village nestled between mountains, there lived an old watchmaker...",
"parentId": "msg-tool-1",
"createdAt": 1739032814033,
"updatedAt": 1739032856164,
"metadata": {
"taskTitle": "Write 400-word story and save to Notebook",
"instruction": "Write a ~400 word short story, then save it to Notebook."
},
"taskDetail": {
"duration": 36125,
"status": "completed",
"threadId": "thd_story_task",
"title": "Write 400-word story and save to Notebook",
"totalCost": 0.014107,
"totalMessages": 2,
"totalTokens": 4743,
"totalToolCalls": 0
}
},
{
"id": "msg-assistant-2",
"role": "assistantGroup",
"content": "",
"parentId": "msg-tool-1",
"createdAt": 1739032858762,
"updatedAt": 1739032876226,
"model": "gpt-4",
"provider": "openai",
"children": [
{
"content": "",
"id": "msg-assistant-2",
"performance": {
"duration": 7977,
"latency": 11112,
"tps": 71.96,
"ttft": 3135
},
"tools": [
{
"id": "call_create_doc_1",
"type": "builtin",
"apiName": "createDocument",
"arguments": "{\"content\":\"Once upon a time in a small village...\",\"description\":\"A short story about time and farewell.\",\"title\":\"Eleven Oh Seven\",\"type\":\"article\"}",
"identifier": "lobe-notebook",
"result": {
"content": "📝 Document \"Eleven Oh Seven\" created successfully",
"id": "msg-tool-2",
"state": {
"document": {
"id": "docs_K9OeJoIj6UV82Ac7",
"title": "Eleven Oh Seven",
"content": "Once upon a time in a small village...",
"description": "A short story about time and farewell."
}
}
},
"result_msg_id": "msg-tool-2"
}
],
"usage": {
"cost": 0.011641,
"totalInputTokens": 13004,
"totalOutputTokens": 574,
"totalTokens": 13578
}
},
{
"content": "Async task completed. The story has been written and saved to Notebook as \"Eleven Oh Seven\".",
"id": "msg-assistant-summary",
"performance": {
"duration": 2422,
"latency": 5209,
"tps": 30.55,
"ttft": 2787
},
"usage": {
"cost": 0.02389,
"totalInputTokens": 13059,
"totalOutputTokens": 74,
"totalTokens": 13133
}
}
],
"performance": {
"ttft": 3135,
"duration": 10399,
"latency": 16321,
"tps": 51.254999999999995
},
"usage": {
"totalInputTokens": 26063,
"totalOutputTokens": 648,
"totalTokens": 26711,
"cost": 0.035531
}
}
],
"messageMap": {
"msg-user-1": {
"id": "msg-user-1",
"role": "user",
"content": "Help me create an async task to write a 400-word short story and save it to Notebook.",
"parentId": null,
"createdAt": 1739032787856,
"updatedAt": 1739032787856
},
"msg-assistant-1": {
"id": "msg-assistant-1",
"role": "assistant",
"content": "",
"parentId": "msg-user-1",
"createdAt": 1739032788546,
"updatedAt": 1739032805810,
"model": "gpt-4",
"provider": "openai",
"tools": [
{
"id": "call_exec_task_1",
"type": "builtin",
"apiName": "execTask",
"arguments": "{\"description\":\"Write 400-word story and save to Notebook\",\"inheritMessages\":true,\"instruction\":\"Write a ~400 word short story, then save it to Notebook.\",\"timeout\":1800000}",
"identifier": "lobe-gtd"
}
],
"metadata": {
"tps": 31.55,
"cost": 0.024045,
"ttft": 3163,
"latency": 8774,
"duration": 5611,
"totalTokens": 12501,
"totalInputTokens": 12324,
"totalOutputTokens": 177
}
},
"msg-tool-1": {
"id": "msg-tool-1",
"role": "tool",
"content": "🚀 Triggered async task for execution:\n- Write 400-word story and save to Notebook",
"parentId": "msg-assistant-1",
"tool_call_id": "call_exec_task_1",
"createdAt": 1739032808194,
"updatedAt": 1739032811207,
"plugin": {
"apiName": "execTask",
"arguments": "{\"description\":\"Write 400-word story and save to Notebook\",\"inheritMessages\":true,\"instruction\":\"Write a ~400 word short story, then save it to Notebook.\",\"timeout\":1800000}",
"identifier": "lobe-gtd",
"type": "builtin"
},
"pluginState": {
"task": {
"timeout": 1800000,
"description": "Write 400-word story and save to Notebook",
"instruction": "Write a ~400 word short story, then save it to Notebook.",
"inheritMessages": true
},
"type": "execTask",
"parentMessageId": "msg-tool-1"
}
},
"msg-task-1": {
"id": "msg-task-1",
"role": "task",
"content": "Once upon a time in a small village nestled between mountains, there lived an old watchmaker...",
"parentId": "msg-tool-1",
"createdAt": 1739032814033,
"updatedAt": 1739032856164,
"metadata": {
"taskTitle": "Write 400-word story and save to Notebook",
"instruction": "Write a ~400 word short story, then save it to Notebook."
},
"taskDetail": {
"duration": 36125,
"status": "completed",
"threadId": "thd_story_task",
"title": "Write 400-word story and save to Notebook",
"totalCost": 0.014107,
"totalMessages": 2,
"totalTokens": 4743,
"totalToolCalls": 0
}
},
"msg-assistant-2": {
"id": "msg-assistant-2",
"role": "assistant",
"content": "",
"parentId": "msg-tool-1",
"createdAt": 1739032858762,
"updatedAt": 1739032876226,
"model": "gpt-4",
"provider": "openai",
"tools": [
{
"id": "call_create_doc_1",
"type": "builtin",
"apiName": "createDocument",
"arguments": "{\"content\":\"Once upon a time in a small village...\",\"description\":\"A short story about time and farewell.\",\"title\":\"Eleven Oh Seven\",\"type\":\"article\"}",
"identifier": "lobe-notebook"
}
],
"metadata": {
"tps": 71.96,
"cost": 0.011641,
"ttft": 3135,
"latency": 11112,
"duration": 7977,
"totalTokens": 13578,
"totalInputTokens": 13004,
"totalOutputTokens": 574
}
},
"msg-tool-2": {
"id": "msg-tool-2",
"role": "tool",
"content": "📝 Document \"Eleven Oh Seven\" created successfully",
"parentId": "msg-assistant-2",
"tool_call_id": "call_create_doc_1",
"createdAt": 1739032879049,
"updatedAt": 1739032883458,
"plugin": {
"apiName": "createDocument",
"arguments": "{\"content\":\"Once upon a time in a small village...\",\"description\":\"A short story about time and farewell.\",\"title\":\"Eleven Oh Seven\",\"type\":\"article\"}",
"identifier": "lobe-notebook",
"type": "builtin"
},
"pluginState": {
"document": {
"id": "docs_K9OeJoIj6UV82Ac7",
"title": "Eleven Oh Seven",
"content": "Once upon a time in a small village...",
"description": "A short story about time and farewell."
}
}
},
"msg-assistant-summary": {
"id": "msg-assistant-summary",
"role": "assistant",
"content": "Async task completed. The story has been written and saved to Notebook as \"Eleven Oh Seven\".",
"parentId": "msg-tool-2",
"createdAt": 1739032886810,
"updatedAt": 1739032898616,
"model": "gpt-4",
"provider": "openai",
"metadata": {
"tps": 30.55,
"cost": 0.02389,
"ttft": 2787,
"latency": 5209,
"duration": 2422,
"totalTokens": 13133,
"totalInputTokens": 13059,
"totalOutputTokens": 74
}
}
}
}

View File

@@ -257,6 +257,12 @@ describe('parse', () => {
expect(result.flatList[3].content).toContain('All 10 tasks completed');
});
it('should handle single task (execTask) with tool chain after completion', () => {
const result = parse(inputs.tasks.singleTaskWithToolChain);
expect(serializeParseResult(result)).toEqual(outputs.tasks.singleTaskWithToolChain);
});
it('should merge assistant with tools after task into AssistantGroup', () => {
const result = parse(inputs.tasks.withAssistantGroup);

View File

@@ -65,9 +65,9 @@ export class MessageCollector {
const nextMessages = allMessages.filter((m) => m.parentId === toolMsg.id);
// Stop if there are multiple task children - they should be aggregated as Tasks, not part of AssistantGroup
// Stop if there are task children - they should be handled separately, not part of AssistantGroup
const taskChildren = nextMessages.filter((m) => m.role === 'task');
if (taskChildren.length > 1) {
if (taskChildren.length > 0) {
continue;
}
@@ -142,12 +142,12 @@ export class MessageCollector {
continue;
}
// Stop if there are multiple task children - they should be aggregated as Tasks, not part of AssistantGroup
// Stop if there are task children - they should be handled separately, not part of AssistantGroup
const taskChildren = toolNode.children.filter((child) => {
const childMsg = this.messageMap.get(child.id);
return childMsg?.role === 'task';
});
if (taskChildren.length > 1) {
if (taskChildren.length > 0) {
continue;
}
@@ -181,14 +181,14 @@ export class MessageCollector {
return lastNode;
}
// Check if lastNode is a tool with multiple task children
// Check if lastNode is a tool with task children
// In this case, return the tool node itself so ContextTreeBuilder can process tasks
if (lastMsg?.role === 'tool') {
const taskChildren = lastNode.children.filter((child) => {
const childMsg = this.messageMap.get(child.id);
return childMsg?.role === 'task';
});
if (taskChildren.length > 1) {
if (taskChildren.length > 0) {
return lastNode;
}
}
@@ -224,12 +224,12 @@ export class MessageCollector {
continue;
}
// Stop if there are multiple task children - they should be aggregated as Tasks, not part of AssistantGroup
// Stop if there are task children - they should be handled separately, not part of AssistantGroup
const taskNodes = toolNode.children.filter((child) => {
const childMsg = this.messageMap.get(child.id);
return childMsg?.role === 'task';
});
if (taskNodes.length > 1) {
if (taskNodes.length > 0) {
continue;
}

View File

@@ -11,6 +11,7 @@ import {
ReactListPlugin,
ReactMathPlugin,
ReactTablePlugin,
ReactVirtualBlockPlugin,
} from '@lobehub/editor';
import { Editor, FloatMenu, SlashMenu, useEditorState } from '@lobehub/editor/react';
import { combineKeys } from '@lobehub/ui';
@@ -99,6 +100,7 @@ const InputEditor = memo<{ defaultRows?: number }>(({ defaultRows = 2 }) => {
ReactHRPlugin,
ReactLinkHighlightPlugin,
ReactTablePlugin,
ReactVirtualBlockPlugin,
Editor.withProps(ReactMathPlugin, {
renderComp: expand
? undefined

View File

@@ -9,13 +9,20 @@ export interface ChatInputEditor {
getJSONState: () => any;
getMarkdownContent: () => string;
instance: IEditor;
setDocument: (type: string, content: any, options?: Record<string, unknown>) => void;
setExpand: (expand: boolean) => void;
setJSONState: (content: any) => void;
}
export const useChatInputEditor = () => {
const [editor, getMarkdownContent, getJSONState, setExpand, setJSONState] = useChatInputStore(
(s) => [s.editor, s.getMarkdownContent, s.getJSONState, s.setExpand, s.setJSONState],
);
const [editor, getMarkdownContent, getJSONState, setExpand, setJSONState, setDocument] =
useChatInputStore((s) => [
s.editor,
s.getMarkdownContent,
s.getJSONState,
s.setExpand,
s.setJSONState,
s.setDocument,
]);
return useMemo<ChatInputEditor>(
() => ({
@@ -28,6 +35,7 @@ export const useChatInputEditor = () => {
getJSONState,
getMarkdownContent,
instance: editor!,
setDocument,
setExpand,
setJSONState,
}),

View File

@@ -7,6 +7,7 @@ export interface Action {
getMarkdownContent: () => string;
handleSendButton: () => void;
handleStop: () => void;
setDocument: (type: string, content: any, options?: Record<string, unknown>) => void;
setExpand: (expend: boolean) => void;
setJSONState: (content: any) => void;
setShowTypoBar: (show: boolean) => void;
@@ -47,6 +48,10 @@ export const store: CreateStore = (publicState) => (set, get) => ({
get().sendButtonProps?.onStop?.({ editor: get().editor! });
},
setDocument: (type, content, options) => {
get().editor?.setDocument(type, content, options);
},
setExpand: (expand) => {
set({ expand });
},

View File

@@ -308,7 +308,7 @@ export const conversationLifecycle: StateCreator<
// Check if error is due to cancellation
if (!isAbort) {
get().updateOperationMetadata(operationId, { inputSendErrorMsg: e.message });
get().mainInputEditor?.setJSONState(jsonState);
get().mainInputEditor?.setDocument('markdown', message);
}
}
} finally {