AI Assistant 是 TinyMCE 首次将 AI 功能集成到编辑器中的版本。TinyMCE AI 是当前的 AI 解决方案,通过 Chat、Quick Actions 和 Review 模式提供深入的文档感知 AI 功能。TinyMCE AI 适用于 Tiny Cloud 和自托管的 TinyMCE 编辑器,并且可以连接到托管的 Tiny Cloud AI 服务或自托管 AI 服务。

AI Assistant 插件允许用户在 TinyMCE 编辑器对话框中发送查询并查看响应,从而与注册的 AI API 进行交互。

一旦响应生成并在对话框中显示,用户可以选择:

  1. 将其插入到编辑器当前的选区中;

  2. 创建另一个查询以进一步优化 AI 生成的响应;或者

  3. 关闭对话框并丢弃返回的响应。

用户可以使用 getThreadLog API 检索与 AI 的对话历史,包括任何被丢弃的响应。

交互示例

此示例使用代理端点与 OpenAI API 进行通信。这样做是为了避免在客户端代码中暴露 API 密钥。有关使用代理服务器与 AI Assistant 插件的更多信息,请参阅 AI 代理服务器参考指南。

HTML

<textarea id="ai">
  <h1 class="p1"><span class="s1">🤖</span><span class="s2"><strong> Try out AI Assistant!</strong></span></h1>
  <p class="p2"><span class="s2">Below are just a few of the ways you can use AI Assistant within your app. Since you can define your own custom prompts, the sky really is the limit!</span></p>
  <p class="p2"><span class="s2"><strong>&nbsp;</strong></span><span class="s3">🎭</span><span class="s2"><strong> Changing tone </strong>&ndash;<strong>&nbsp;</strong>Lighten up the sentence below by selecting the text, clicking <img src="../_images/ai-plugin/wand-icon.svg" width="20" height="20"/>,&nbsp;and choosing <em>Change tone &gt; Friendly</em>.</span></p>
  <blockquote>
  <p class="p2"><span class="s2">The 3Q23 financial results followed a predictable trend, reflecting the status quo from previous years.</span></p>
  </blockquote>
  <p class="p2"><span class="s3">📝</span><span class="s2"><strong> Summarizing&nbsp;</strong>&ndash; Below is a long paragraph that people may not want to read from start to finish. Get a quick summary by selecting the text, clicking <img src="../_images/ai-plugin/wand-icon.svg" width="20" height="20"/>,&nbsp;and choosing <em>Summarize content</em>.</span></p>
  <blockquote>
  <p class="p2"><span class="s2">Population growth in the 17th century was marked by significant increment in the number of people around the world. Various factors contributed to this demographic trend. Firstly, advancements in agriculture and technology resulted in increased food production and improved living conditions. This led to decreased mortality rates and better overall health, allowing for more individuals to survive and thrive. Additionally, the exploration and expansion of European powers, such as colonization efforts, fostered migration and settlement in new territories.</span></p>
  </blockquote>
  <p class="p2"><span class="s3">💡</span><span class="s2"><strong> Writing from scratch</strong> &ndash; Ask AI Assistant to generate content from scratch by clicking <img src="../_images/ai-plugin/ai-icon.svg" width="20" height="20"/>, and typing&nbsp;<em>Write a marketing email announcing TinyMCE's new AI Assistant plugin</em>.</span></p>
</textarea>

JS

const fetchApi = import('https://cdn.skypack.dev/@microsoft/fetch-event-source@2.0.1')
  .then((module) => module.fetchEventSource);
 

// 此示例将 API 密钥存储在客户端集成中。不建议出于任何目的这样做。
// 应使用其他方法来检索 API 密钥。
const api_key = '<INSERT_API_KEY_HERE>';

const ai_request = (request, respondWith) => {
    respondWith.stream((signal, streamMessage) => {
        // 将之前的每个查询和响应作为单独的消息添加
        const conversation = request.thread.flatMap((event) => {
            if (event.response) {
                return [
                    { role: 'user', content: event.request.query },
                    { role: 'assistant', content: event.response.data },
                ];
            } else {
                return [];
            }
        });

        // 插件提供的系统消息,用于将输出格式化为 HTML 内容。
        const systemMessages = request.system.map((content) => ({
            role: 'system',
            content,
        }));

        // 构建发送到 API 的新查询
        const content =
            request.context.length === 0 || conversation.length > 0
                ? request.query
                : `Question: ${request.query} Context: """${request.context}"""`;

        const messages = [
            ...conversation,
            ...systemMessages,
            { role: 'user', content },
        ];

        let hasHead = false;
        let markdownHead = '';

        const hasMarkdown = (message) => {
            if (message.includes('`') && markdownHead !== '```') {
                const numBackticks = message.split('`').length - 1;
                markdownHead += '`'.repeat(numBackticks);
                if (hasHead && markdownHead === '```') {
                    markdownHead = '';
                    hasHead = false;
                }
                return true;
            } else if (message.includes('html') && markdownHead === '```') {
                markdownHead = '';
                hasHead = true;
                return true;
            }
            return false;
        };

        const requestBody = {
            model: 'gpt-4o',
            temperature: 0.7,
            max_tokens: 4000,
            messages,
            stream: true,
        };

        const openAiOptions = {
            signal,
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'Authorization': `Bearer ${api_key}`,
            },
            body: JSON.stringify(requestBody),
        };

        const onopen = async (response) => {
            if (response) {
                const contentType = response.headers.get('content-type');
                if (response.ok && contentType?.includes('text/event-stream')) {
                    return;
                } else if (contentType?.includes('application/json')) {
                    const data = await response.json();
                    if (data.error) {
                        throw new Error(
                            `${data.error.type}: ${data.error.message}`
                        );
                    }
                }
            } else {
                throw new Error('Failed to communicate with the ChatGPT API');
            }
        };

        // 此函数通过 `streamMessage` 回调将每条新消息传递给插件。
        const onmessage = (ev) => {
            const data = ev.data;
            if (data !== '[DONE]') {
                const parsedData = JSON.parse(data);
                const firstChoice = parsedData?.choices[0];
                const message = firstChoice?.delta?.content;
                if (message && message !== '') {
                    if (!hasMarkdown(message)) {
                        streamMessage(message);
                    }
                }
            }
        };

        const onerror = (error) => {
            // 停止操作,且不通过 fetch-event-source 进行重试
            throw error;
        };

        // 使用 Microsoft 的 fetch-event-source 库来绕过浏览器 `EventSource` API 的 2000 字符限制,该 API 需要查询字符串
        return fetchApi
            .then((fetchEventSource) =>
                fetchEventSource('https://api.openai.com/v1/chat/completions', {
                    ...openAiOptions,
                    openWhenHidden: true,
                    onopen,
                    onmessage,
                    onerror,
                })
            )
            .then(async (response) => {
                if (response && !response.ok) {
                    const data = await response.json();
                    if (data.error) {
                        throw new Error(
                            `${data.error.type}: ${data.error.message}`
                        );
                    }
                }
            })
            .catch(onerror);
    });
};

tinymce.init({
    selector: 'textarea', // 根据你的 HTML 更改此值
    plugins: 'ai advlist anchor autolink charmap advcode emoticons fullscreen help image link lists media preview searchreplace table',
    toolbar: 'undo redo | aidialog aishortcuts | styles fontsizeinput | bold italic | align bullist numlist | table link image | code',
    height: 650,
    ai_request,
});

基本设置

要将 AI Assistant 插件添加到编辑器中,请按照以下步骤操作:

  • 在编辑器配置的 plugins 选项中添加 ai。

  • 在编辑器配置中添加 ai_request 函数。

例如:

tinymce.init({
  selector: 'textarea',  // 根据您的 HTML 更改此值
  plugins: 'ai',
  toolbar: 'aidialog aishortcuts',
  ai_request: <AI_REQUEST_FUNCTION>,
});

有关如何使用模块打包器来打包此插件的信息,请参阅 Bundling TinyMCE - Overview。

使用代理服务器配合 AI Assistant

根据 OpenAI 关于API 密钥安全性的最佳实践,在客户端环境中部署 API 密钥是明确不推荐的。

使用代理服务器可以避免这个问题,减少财务和服务正常运行时间方面的风险。

代理服务器还可以通过在请求发送到 LLM AI 端点之前以及将响应返回给用户之前允许额外的处理来提供灵活性。

有关如何为 AI Assistant 设置代理服务器的信息,请参阅AI 代理服务器参考指南。

正如其名称所指出的,AI 代理服务器参考指南只是一份参考文档。并没有适用于所有情况的单一代理服务器设置,其他设置可能更适合您的使用场景。

选项

以下配置选项会影响 AI Assistant 插件的行为。

ai_request

AI Assistant 使用 ai_request 函数将提示发送到 AI 端点,并显示响应。

每次用户提交提示时,都会调用 ai_request 函数。

仅当 AI Assistant 对话框打开时,这些提示才会被提交,无论是从对话框输入字段中输入,还是从使用 AI Assistant 快捷方式。

一旦提供了响应,在 ai_request 函数内返回的内容将在对话框中显示。

使用 AI Assistant 插件时,此选项是必需的。

类型: 函数

示例:使用 ai_request 与 OpenAI Completions API 进行接口交互

// 此示例将 API 密钥存储在客户端集成中。不建议用于任何目的。
// 应使用其他方法来获取 API 密钥。
const api_key = '<INSERT_API_KEY_HERE>';

const ai_request = (request, respondWith) => {
  const openAiOptions = {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${api_key}`
    },
    body: JSON.stringify({
      model: 'gpt-4o',
      temperature: 0.7,
      max_tokens: 800,
      messages: [{ role: 'user', content: request.prompt }],
    })
  };
  respondWith.string((signal) => window.fetch('https://api.openai.com/v1/chat/completions', { signal, ...openAiOptions })
    .then(async (response) => {
      if (response) {
        const data = await response.json();
        if (data.error) {
          throw new Error(`${data.error.type}: ${data.error.message}`);
        } else if (response.ok) {
          // 从 API 返回的数据中提取响应内容
          return data?.choices[0]?.message?.content?.trim();
        }
      } else {
        throw new Error('与 ChatGPT API 通信失败');
      }
    })
  );
};

tinymce.init({
  selector: 'textarea',  // 根据您的 HTML 更改此值
  plugins: 'ai',
  toolbar: 'aidialog aishortcuts',
  ai_request
});

request 对象

ai_request 函数以 request 对象作为第一个参数,该对象具有以下字段:

query

用户提交的提示字符串,不包含任何上下文。这是用户在 AI Assistant 对话框中输入的文本,或者是从快捷方式菜单中选择快捷方式对象时编写的 prompt。

context

当前的选择内容(如果有)作为字符串,或者对话框中当前显示的响应。集成商可以以自定义的方式将其与 `query` 组合以形成一个请求。当前的选择内容将以 HTML 格式提供,任何显示的 HTML 响应也是如此,并且会增加 token 的使用量。

thread

一个包含对话框内请求和响应历史的数组,以对象数组的形式提供。对于 AI Assistant 对话框的当前实例,此 thread 数组与 getThreadLog API 中记录的相同。

system

一个消息数组,提供处理用户提示的指令。system 数组:

[ '根据下面的上下文回答问题。',
  '响应应采用 HTML 格式。',
  '响应应保留上下文中的任何 HTML 格式、链接和样式。' ]
prompt

提交的提示字符串,与任何当前选择内容(首次打开对话框时)或先前的响应组合在一起。AI Assistant 插件提供了一个自定义格式来组合这些字符串,尽管集成商可以根据 request 对象中提供的任何其他字段自由构建自己的格式。

默认提示和 token 使用量。

AI Assistant 会自动将 system 值作为字符串添加到 prompt 值之前。提示还将 query 和 context 值组合成一个字符串。

根据以下上下文回答问题。
响应应为 HTML 格式。
响应应保留上下文中的任何 HTML 格式、链接和样式。

上下文:"""<the-selected-text>"""

问题:"""<the-prompt>"""

答案:

此字符串旨在改善用户体验,提高响应准确性,并简化 AI Assistant 插件的初始集成。

然而,此字符串使用的 token 数量比 query 和 context 组合使用的还要多。

respondWith 对象

ai_request 函数提供的对象作为第二个参数包含两个独立的回调函数。这些回调函数允许集成人员选择如何将来自 API 的响应显示在 AI Assistant 对话框中。

这两个回调函数都期望一个 Promise,该 Promise 表示响应已完成(当 resolve 时)或中断(当 reject 时)。Promise 的返回类型因回调函数而异。

两个回调函数都提供一个 signal 参数。

signal

如果用户关闭对话框或中止流式响应,signal 参数可以中止请求。

respondWith.string 回调函数

respondWith.string 回调函数提供用于显示来自 AI 的完整响应的功能。

最终响应应使用 Promise.resolve() 作为字符串返回。此字符串将显示在 AI Assistant 对话框中。

respondWith.stream 回调函数

respondWith.stream 回调函数提供用于显示来自 AI 的流式响应的功能。

此回调函数期望一个 Promise,当 AI 完成流式响应后,该 Promise 会 resolve。

此回调函数将 streamMessage 回调函数作为第二个参数提供,当收到每个新的部分消息时应调用此参数,以便立即在 AI Assistant 对话框中显示消息。

streamMessage

接收一个字符串并将其追加到 AI Assistant 对话框中显示的内容。

ai_shortcuts

ai_shortcuts 选项控制 AI Shortcuts 工具栏按钮和菜单项中可用的 AI Assistant 快捷方式列表。

可以使用数组配置此选项,以呈现一组自定义的 AI Assistant 快捷方式。

此外,可以将其设置为 Boolean 值,以控制默认的 AI Assistant 快捷方式列表的使用。

当未指定或设置为 true 时,AI Assistant 快捷方式工具栏按钮和菜单项会显示 AI Assistant 附带的默认快捷方式集。

当设置为 [](空数组)或 false 时,AI Assistant 快捷方式工具栏按钮和菜单项不会在 TinyMCE 实例中显示。

当配置了特定于实例的对象数组时,AI Assistant 快捷方式工具栏按钮和菜单项会显示,并在激活时呈现配置的快捷方式。

类型:Array 的 Objects,或 Boolean

默认值:

[
  { title: 'Summarize content', prompt: 'Provide the key points and concepts in this content in a succinct summary.', selection: true },
  { title: 'Improve writing', prompt: 'Rewrite this content with no spelling mistakes, proper grammar, and with more descriptive language, using best writing practices without losing the original meaning.', selection: true },
  { title: 'Simplify language', prompt: 'Rewrite this content with simplified language and reduce the complexity of the writing, so that the content is easier to understand.', selection: true },
  { title: 'Expand upon', prompt: 'Expand upon this content with descriptive language and more detailed explanations, to make the writing easier to understand and increase the length of the content.', selection: true },
  { title: 'Trim content', prompt: 'Remove any repetitive, redundant, or non-essential writing in this content without changing the meaning or losing any key information.', selection: true },
  {
    title: 'Change tone', subprompts: [
      { title: 'Professional', prompt: 'Rewrite this content using polished, formal, and respectful language to convey professional expertise and competence.', selection: true },
      { title: 'Casual', prompt: 'Rewrite this content with casual, informal language to convey a casual conversation with a real person.', selection: true },
      { title: 'Direct', prompt: 'Rewrite this content with direct language using only the essential information.', selection: true },
      { title: 'Confident', prompt: 'Rewrite this content using compelling, optimistic language to convey confidence in the writing.', selection: true },
      { title: 'Friendly', prompt: 'Rewrite this content using friendly, comforting language, to convey understanding and empathy.', selection: true },
    ]
  },
  {
    title: 'Change style', subprompts: [
      { title: 'Business', prompt: 'Rewrite this content as a business professional with formal language.', selection: true },
      { title: 'Legal', prompt: 'Rewrite this content as a legal professional using valid legal terminology.', selection: true },
      { title: 'Journalism', prompt: 'Rewrite this content as a journalist using engaging language to convey the importance of the information.', selection: true },
      { title: 'Medical', prompt: 'Rewrite this content as a medical professional using valid medical terminology.', selection: true },
      { title: 'Poetic', prompt: 'Rewrite this content as a poem using poetic techniques without losing the original meaning.', selection: true },
    ]
  },
  {
    title: 'Translate', subprompts: [
      { title: 'Translate to English', prompt: 'Translate this content to English language.', selection: true },
      { title: 'Translate to Spanish', prompt: 'Translate this content to Spanish language.', selection: true },
      { title: 'Translate to Portuguese', prompt: 'Translate this content to Portuguese language.', selection: true },
      { title: 'Translate to German', prompt: 'Translate this content to German language.', selection: true },
      { title: 'Translate to French', prompt: 'Translate this content to French language.', selection: true },
      { title: 'Translate to Norwegian', prompt: 'Translate this content to Norwegian language.', selection: true },
      { title: 'Translate to Ukrainian', prompt: 'Translate this content to Ukrainian language.', selection: true },
      { title: 'Translate to Japanese', prompt: 'Translate this content to Japanese language.', selection: true },
      { title: 'Translate to Korean', prompt: 'Translate this content to Korean language.', selection: true },
      { title: 'Translate to Simplified Chinese', prompt: 'Translate this content to Simplified Chinese language.', selection: true },
      { title: 'Translate to Hebrew', prompt: 'Translate this content to Hebrew language.', selection: true },
      { title: 'Translate to Hindi', prompt: 'Translate this content to Hindi language.', selection: true },
      { title: 'Translate to Arabic', prompt: 'Translate this content to Arabic language.', selection: true },
    ]
  },
]
翻译与更改

默认的 AI Assistant 快捷方式仅提供英文版本。这些快捷方式未被翻译成任何其他语言,将 TinyMCE 切换为非英语语言不会改变默认的 AI Assistant 快捷方式。

此外,默认的 AI Assistant 快捷方式可能会发生变化。如果您希望保留这些快捷方式,请将其包含在您的集成中。

示例:使用 ai_shortcuts 呈现自定义的 AI Assistant 快捷方式集

tinymce.init({
  selector: 'textarea',  // change this value according to your html
  plugins: 'ai',
  toolbar: 'aidialog aishortcuts',
  ai_request: (request, respondWith) => respondWith.string(() => Promise.reject("See docs to implement AI Assistant")),
  ai_shortcuts: [
    { title: 'Screenplay', prompt: 'Convert this to screenplay format.', selection: true },
    { title: 'Stage play', prompt: 'Convert this to stage play format.', selection: true },
    { title: 'Classical', subprompts:
      [
        { title: 'Dialogue', prompt: 'Convert this to a Socratic dialogue.', selection: true },
        { title: 'Homeric', prompt: 'Convert this to a Classical Epic.', selection: true }
      ]
    },
    { title: 'Celtic', subprompts:
      [
        { title: 'Bardic', prompt: 'Convert this to Bardic verse.', selection: true },
        { title: 'Filí', prompt: 'Convert this to Filí-an verse.', selection: true }
      ]
    },
  ]
});

示例:禁用 ai_shortcuts

若要禁用 AI Assistant 快捷方式菜单和工具栏选项,请将 ai_shortcuts 设置为 false(或设置为 [],即空数组)。

tinymce.init({
  selector: 'textarea',  // 根据您的 HTML 更改此值
  ai_shortcuts: false
});
tinymce.init({
  selector: 'textarea',  // 根据您的 HTML 更改此值
  ai_shortcuts: []
});

有效的快捷方式

有效的快捷方式对象包含以下属性。

title

一个字符串,显示在 aishortcuts 工具栏按钮和菜单项中。这将表明使用的是哪个快捷方式,或此菜单中包含哪些类别的快捷方式。

以及以下之一

subprompts

一个包含更多有效快捷方式对象的数组。这允许将快捷方式分组到 AI Assistant 快捷方式工具栏按钮和菜单项中的类别。

或

prompt

一个字符串,包含使用快捷方式时提供给 ai_request 函数的查询。

selection 属性

具有 prompt 属性的快捷方式对象也可能包含以下可选属性。

selection

一个布尔值,将匹配当前选择并设置快捷方式的启用状态。当 selection 为:

  • true:快捷方式将仅在用户在编辑器中进行了选择时启用。

  • false:快捷方式将仅在用户在编辑器中未进行选择时启用。

  • undefined 或未设置:快捷方式将始终启用。

此属性允许定义仅在用户选择了内容时才应操作的快捷方式,当属性为 true 时,需要选择作为 AI 的上下文。此外,当属性为 false 时,旨在生成特定内容的快捷方式在任何选择下都不会启用。

工具栏按钮

AI Assistant 插件提供以下工具栏按钮:

工具栏按钮标识符描述

aidialog

打开 AI Assistant 对话框。

aishortcuts

打开 AI 快捷方式菜单,显示可用于查询 AI API 的快捷方式提示。

这些工具栏按钮可以通过以下方式添加到编辑器中:

  • toolbar 配置选项。

  • quickbars_insert_toolbar 配置选项。

  • 自定义上下文工具栏。

菜单项

AI Assistant 插件提供以下菜单项:

菜单项标识符默认菜单位置描述

aidialog

工具

打开 AI Assistant 对话框。

aishortcuts

工具

打开 AI Assistant 快捷方式子菜单,显示可用于查询 AI API 的快捷方式提示。

这些菜单项可以通过以下方式添加到编辑器中:

  • menu 配置选项。

  • contextmenu 配置选项。

  • 自定义菜单工具栏按钮。

命令

AI Assistant 插件提供以下 TinyMCE 命令。

命令描述

mceAiDialog

此命令打开 AI Assistant 对话框。有关详细信息,请参阅使用 mceAiDialog。

mceAiDialogClose

此命令关闭 AI Assistant 对话框。

示例
tinymce.activeEditor.execCommand('mceAiDialog');
tinymce.activeEditor.execCommand('mceAiDialog', true|false, { prompt: '<value1>', generate: true, display: false });
tinymce.activeEditor.execCommand('mceAiDialogClose');

使用 mceAiDialog

mceAiDialog 接受一个包含以下任何键值对的对象:

名称值要求描述

prompt

string

非必需

首次打开对话框时要预填充到输入字段中的提示。

generate

boolean

非必需

首次打开对话框时是否应发送请求。

display

boolean

非必需

首次打开对话框时是否在对话框中显示输入字段和生成按钮。

事件

AI Assistant 插件提供以下事件。

AI Assistant 插件提供以下事件。

名称数据描述

AIRequest

{ request: Request }

当向 ai_request 函数发送请求时触发。

AIResponse

{ response: string }

当 ai_request Promise 被解决(resolved)时触发。

AIError

{ error: Error or string }

当 ai_request Promise 被拒绝(rejected)时触发。

AIDialogOpen

不适用

当 AI Assistant 对话框打开时触发。

AIDialogClose

不适用

当 AI Assistant 对话框关闭时触发。

APIs

AI Assistant 插件提供以下 API。

名称参数说明

getThreadLog

不适用

检索使用该插件时生成的每个会话线程的历史记录。

示例
// 检索在当前编辑器中使用插件生成的每个会话线程的历史记录。
tinymce.activeEditor.plugins.ai.getThreadLog();

getThreadLog API

用户或集成商可以在启用了 AI Assistant 插件的编辑器实例上调用 editor.ai.getThreadLog(),以检索每个会话线程的历史记录。

每次 AI 对话框打开时,一个新的线程会以唯一的 ID 记录到线程日志中。当请求返回响应或错误时,会在当前线程中记录一个事件,该事件包含以下字段:

eventUid

事件的唯一标识符。

timestamp

事件在线程中被记录的时间戳日期,格式为 ISO-8601。

request

提供给 ai_request 函数集成的 request 对象,不包括当前线程。

以及以下之一:

response

由集成提供的 response 对象,包含一个 type 字段,用于表示所使用的 ai_request 回调(string 或 stream),以及一个包含整个响应数据的 data 字段;或者

error

一个包含集成返回的任何错误的字符串。

线程日志可以包含任意数量的线程,每个线程中也可以有任意数量的事件。下面的示例仅显示一个包含单个事件的单个线程。返回的对象格式如下:

{
    "mce-aithread_123456": [
        {
            "eventUid": "mce-aithreadevent_654321",
            "timestamp": "2023-03-15T09:00:00.000Z",
            "request": {
                "prompt": "Answer the question based on the context below.\nThe response should be in HTML format.\nThe response should preserve any HTML formatting, links, and styles in the context.\n\nContext: \"\"\"Some selection\"\"\"\n\nQuestion: \"\"\"A user query\"\"\"\n\nAnswer:",
                "query": "A user query",
                "context": "Some selection",
                "system": [
                    "Answer the question based on the context below.",
                    "The response should be in HTML format.",
                    "The response should preserve any HTML formatting, links, and styles in the context."
                ]
            },
            "response": {
                "type": "string",
                "data": "Sorry, there is not enough information to provide an answer to your query,"
            }
        }
    ]
}
一旦 TinyMCE 编辑器实例关闭,所有临时存储的结果都将丢失,因此请使用 getThreadLog() 来检索并存储不应丢失的任何响应。