From 614398e1b75c4127d3dcbad0f6f4ffdf2613fbf1 Mon Sep 17 00:00:00 2001 From: Karl Kemister-Sheppard Date: Thu, 2 Apr 2026 14:04:31 +1000 Subject: [PATCH 1/4] Docs: DOC-3404 - Update Full-Featured Premium Demo to use TinyMCE AI plugin Replace legacy AI Assistant plugin with the new TinyMCE AI plugin in the full-featured premium demo. Updates toolbar buttons, quickbar actions, sidebar default, translation configuration, and custom quick actions to align with the TinyMCE AI plugin documentation demos. --- .../live-demos/full-featured/example.js | 186 +++++------------ .../live-demos/full-featured/index.html | 2 +- .../live-demos/full-featured/index.js | 189 +++++------------- .../pages/full-featured-premium-demo.adoc | 3 + 4 files changed, 102 insertions(+), 278 deletions(-) diff --git a/modules/ROOT/examples/live-demos/full-featured/example.js b/modules/ROOT/examples/live-demos/full-featured/example.js index 45f1eb4be7..37ed48a5dd 100644 --- a/modules/ROOT/examples/live-demos/full-featured/example.js +++ b/modules/ROOT/examples/live-demos/full-featured/example.js @@ -1,6 +1,3 @@ -const fetchApi = import('https://cdn.skypack.dev/@microsoft/fetch-event-source@2.0.1') - .then((module) => module.fetchEventSource); - const API_URL = 'https://demouserdirectory.tiny.cloud/v1/users'; const user_id = 'james-wilson'; @@ -250,136 +247,6 @@ const tinycomments_fetch = (conversationUids, done) => { setTimeout(() => done({ conversations: fetchedConversations }), fakeDelay); }; -const ai_request = (request, respondWith) => { - respondWith.stream((signal, streamMessage) => { - // Adds each previous query and response as individual messages - const conversation = request.thread.flatMap((event) => { - if (event.response) { - return [ - { role: "user", content: event.request.query }, - { role: "assistant", content: event.response.data }, - ]; - } else { - return []; - } - }); - - // System messages provided by the plugin to format the output as HTML content. - const systemMessages = request.system.map((content) => ({ - role: "system", - content, - })); - - // Forms the new query sent to the 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 {{ openai_proxy_token }}`, - }, - 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"); - } - }; - - // This function passes each new message into the plugin via the `streamMessage` callback. - 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) => { - // Stop operation and do not retry by the fetch-event-source - throw error; - }; - - // Use microsoft's fetch-event-source library to work around the 2000 character limit - // of the browser `EventSource` API, which requires query strings - return fetchApi - .then((fetchEventSource) => - fetchEventSource("{{ openai_proxy_url }}", { - ...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); - }); -}; - const revisions = [ { revisionId: '3', @@ -537,7 +404,7 @@ const revisionhistory_fetch_revision = (_editor, revision) => new Promise((resol tinymce.init({ selector: 'textarea#full-featured', plugins: [ - 'ai', 'suggestededits', 'preview', 'powerpaste', 'casechange', 'importcss', 'searchreplace', + 'tinymceai', 'suggestededits', 'preview', 'powerpaste', 'casechange', 'importcss', 'searchreplace', 'autolink', 'autosave', 'save', 'directionality', 'advcode', 'visualblocks', 'visualchars', 'fullscreen', 'link', 'math', 'media', 'mediaembed', 'codesample', 'table', 'charmap', 'pagebreak', 'nonbreaking', 'anchor', 'tableofcontents', 'insertdatetime', 'advlist', 'lists', 'checklist', 'wordcount', 'tinymcespellchecker', @@ -553,9 +420,9 @@ tinymce.init({ }, menubar: 'file edit view insert format tools table tc help', // Note: if a toolbar item requires a plugin, the item will not present in the toolbar if the plugin is not also loaded. - toolbar: "undo redo | insertfile |importword exportword exportpdf | suggestededits | revisionhistory | aidialog aishortcuts | blocks fontsizeinput | bold italic | align numlist bullist | link uploadcare uploadcare-video | table math media pageembed | lineheight outdent indent | strikethrough forecolor backcolor formatpainter removeformat | charmap emoticons checklist | code fullscreen preview | save print | pagebreak anchor codesample footnotes mergetags | addtemplate inserttemplate | addcomment showcomments | ltr rtl casechange | spellcheckdialog a11ycheck", + toolbar: "undo redo | insertfile |importword exportword exportpdf | suggestededits | revisionhistory | tinymceai-chat tinymceai-review ai-quickactions-translate | blocks fontsizeinput | bold italic | align numlist bullist | link uploadcare uploadcare-video | table math media pageembed | lineheight outdent indent | strikethrough forecolor backcolor formatpainter removeformat | charmap emoticons checklist | code fullscreen preview | save print | pagebreak anchor codesample footnotes mergetags | addtemplate inserttemplate | addcomment showcomments | ltr rtl casechange | spellcheckdialog a11ycheck", mobile: { - plugins: 'ai suggestededits preview powerpaste casechange importcss searchreplace autolink autosave save directionality advcode visualblocks visualchars fullscreen link math media mediaembed codesample table charmap pagebreak nonbreaking anchor tableofcontents insertdatetime advlist lists checklist wordcount tinymcespellchecker a11ychecker help formatpainter pageembed charmap mentions quickbars emoticons advtable footnotes mergetags autocorrect typography advtemplate uploadcare' + plugins: 'tinymceai suggestededits preview powerpaste casechange importcss searchreplace autolink autosave save directionality advcode visualblocks visualchars fullscreen link math media mediaembed codesample table charmap pagebreak nonbreaking anchor tableofcontents insertdatetime advlist lists checklist wordcount tinymcespellchecker a11ychecker help formatpainter pageembed charmap mentions quickbars emoticons advtable footnotes mergetags autocorrect typography advtemplate uploadcare' }, autosave_ask_before_unload: true, @@ -628,7 +495,7 @@ tinymce.init({ ], importcss_append: true, height: 600, - quickbars_selection_toolbar: 'bold italic | quicklink h2 h3 blockquote quicktable | addcomment showcomments', + quickbars_selection_toolbar: 'tinymceai-quickactions ai-quickactions-translate | addcomment', noneditable_class: 'mceNonEditable', toolbar_mode: 'sliding', spellchecker_ignore_list: ['Ephox', 'Moxiecode', 'tinymce', 'TinyMCE'], @@ -636,11 +503,52 @@ tinymce.init({ 'body { font-family:Helvetica,Arial,sans-serif; font-size:16px }', contextmenu: 'link uploadcare uploadcare-video table spellchecker configurepermanentpen', - ai_request, + tinymceai_token_provider: async () => { + return fetch('/api/tinymceai-token', { credentials: 'include' }) + .then(resp => resp.text()) + .then(token => ({ token })); + }, + tinymceai_chat_fetch_sources: () => Promise.resolve([{ + label: 'TinyMCE resources', + sources: [ + { id: 'docs', label: 'TinyMCE Documentation', type: 'web-resource' }, + { id: 'blog', label: 'Tiny Blog', type: 'web-resource' }, + { id: 'survey-2023', label: 'State of rich text editing 2023', type: 'web-resource' }, + ] + }]), + tinymceai_chat_fetch_source: (id) => { + const urls = { + 'docs': 'https://www.tiny.cloud/docs/tinymce/latest/', + 'blog': 'https://www.tiny.cloud/blog/', + 'survey-2023': 'https://www.tiny.cloud/developer-survey-results-2023/', + }; + return Promise.resolve({ type: 'web-resource', url: urls[id] }); + }, + tinymceai_quickactions_custom: [ + { + type: 'chat', + title: 'Challenge', + prompt: 'Challenge statements, verify facts and identify assumptions' + } + ], + tinymceai_languages: [ + { title: 'English', language: 'english' }, + { title: 'Chinese (Simplified)', language: 'chinese' }, + { title: 'Spanish', language: 'spanish' }, + { title: 'German', language: 'german' }, + { title: 'Japanese', language: 'japanese' }, + { title: 'Portuguese', language: 'portuguese' }, + { title: 'Swedish', language: 'swedish' }, + { title: 'Korean', language: 'korean' }, + { title: 'Hindi (Devanagari)', language: 'hindi devanagari' }, + { title: 'Italian', language: 'italian' }, + { title: 'Klingon', language: 'klingon' }, + { title: 'Dothraki', language: 'dothraki' }, + ], tinycomments_mode: 'callback', tinycomments_mentions_enabled: true, - sidebar_show: 'showcomments', + sidebar_show: 'tinymceai-chat', tinycomments_create, tinycomments_reply, tinycomments_delete, diff --git a/modules/ROOT/examples/live-demos/full-featured/index.html b/modules/ROOT/examples/live-demos/full-featured/index.html index 2ae3bd329f..33a57d0466 100644 --- a/modules/ROOT/examples/live-demos/full-featured/index.html +++ b/modules/ROOT/examples/live-demos/full-featured/index.html @@ -1,6 +1,6 @@