diff --git a/.github/workflows/gitleaks.yaml b/.github/workflows/gitleaks.yaml
new file mode 100644
index 0000000..9ae62e1
--- /dev/null
+++ b/.github/workflows/gitleaks.yaml
@@ -0,0 +1,44 @@
+name: Secret Value found!!
+on:
+ push:
+ public:
+jobs:
+ scan:
+ name: gitleaks
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4.2.2
+ - name: Install the gitleaks
+ run: wget https://github.com/zricethezav/gitleaks/releases/download/v8.15.2/gitleaks_8.15.2_linux_x64.tar.gz
+ shell: pwsh
+ - name: Extract the tar file
+ run: tar xzvf gitleaks_8.15.2_linux_x64.tar.gz
+ - name: Generate the report
+ id: gitleaks
+ run: $GITHUB_WORKSPACE/gitleaks detect -s $GITHUB_WORKSPACE -f json -r $GITHUB_WORKSPACE/leaksreport.json
+ shell: bash
+ continue-on-error: true
+ - name: Setup NuGet.exe
+ if: steps.gitleaks.outcome != 'success'
+ uses: nuget/setup-nuget@v2
+ with:
+ nuget-version: latest
+ - name: Install Mono
+ if: steps.gitleaks.outcome != 'success'
+ run: |
+ sudo apt update
+ sudo apt install -y mono-complete
+ - name: Install the dotnet SDK to a custom directory
+ if: steps.gitleaks.outcome != 'success'
+ run: |
+ mkdir -p $GITHUB_WORKSPACE/dotnet
+ curl -sSL https://dot.net/v1/dotnet-install.sh | bash /dev/stdin --install-dir $GITHUB_WORKSPACE/dotnet --channel 6.0
+ - name: Install the report tool packages
+ if: steps.gitleaks.outcome != 'success'
+ run: |
+ export PATH=$GITHUB_WORKSPACE/dotnet:$PATH
+ nuget install "Syncfusion.Email" -source ${{ secrets.NexusFeedLink }} -ExcludeVersion
+ dir $GITHUB_WORKSPACE/Syncfusion.Email/lib/net6.0
+ dotnet $GITHUB_WORKSPACE/Syncfusion.Email/lib/net6.0/GitleaksReportMail.dll ${{ secrets.CITEAMCREDENTIALS }} "$GITHUB_REF_NAME" ${{ secrets.NETWORKCREDENTIALS }} ${{ secrets.NETWORKKEY }} "$GITHUB_WORKSPACE" ${{ secrets.ORGANIZATIONNAME }}
+ exit 1
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..4d29575
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,23 @@
+# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
+
+# dependencies
+/node_modules
+/.pnp
+.pnp.js
+
+# testing
+/coverage
+
+# production
+/build
+
+# misc
+.DS_Store
+.env.local
+.env.development.local
+.env.test.local
+.env.production.local
+
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
diff --git a/README.md b/README.md
index 5c9fecd..def2963 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,71 @@
-# ai-powered-docx-editor-sample
+# ai-powered-react-docx-editor-sample
A sample project showcasing the DOCX Editor with AI assistance for generating, refining, and summarizing content.
+
+## Getting Started
+
+To get started, clone the `ai-powered-react-docx-editor-sample` repository.
+
+```
+git clone https://github.com/SyncfusionExamples/ai-powered-react-docx-editor-sample.git
+```
+
+## Installation
+
+All required packages are pre-configured in the `package.json` file. Install the dependencies by running:
+
+```
+npm install
+```
+
+## License Registration
+
+Before using the Syncfusion DOCX Editor, register your license key in the `src/index.tsx` file:
+
+```
+import { registerLicense } from '@syncfusion/ej2-base';
+
+// Registering Syncfusion license key
+registerLicense('Replace your generated license key here');
+
+const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement);
+root.render(
+
+
+
+);
+```
+
+For more details on license registration, refer to the [official documentation](https://ej2.syncfusion.com/react/documentation/licensing/license-key-registration#register-syncfusion-license-key-in-the-project).
+
+## Configuring AI Services
+
+To set up the AI services, navigate to `src/ai-models.ts` and replace the placeholders with your actual credentials:
+
+```
+const azure = createAzure({
+ resourceName: 'YOUR_RESOURCE_NAME',
+ apiKey: 'YOUR_API_KEY',
+});
+const aiModel = azure('YOUR_MODEL_NAME');
+
+// for gemini model
+const google = createGoogleGenerativeAI({
+ baseURL: "https://generativelanguage.googleapis.com/v1beta",
+ apiKey: "API_KEY"
+});
+const aiModel = google('YOUR_MODEL_NAME');
+```
+
+Your Azure endpoint should resemble: `https://{resource_name}.openai.azure.com/`
+
+For more information on Azure OpenAI configuration, consult the [Vercel AI SDK documentation](https://sdk.vercel.ai/providers/ai-sdk-providers/azure).
+
+## Development Server
+
+To run the application, use the following npm script:
+
+```
+npm start
+```
+
+This command will start the DOCX Editor and open it in your default web browser.
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..3fa29d6
--- /dev/null
+++ b/package.json
@@ -0,0 +1,70 @@
+{
+ "name": "my-app",
+ "version": "0.1.0",
+ "private": true,
+ "dependencies": {
+ "@syncfusion/ej2": "*",
+ "@syncfusion/ej2-base": "*",
+ "@syncfusion/ej2-react-base": "*",
+ "@syncfusion/ej2-react-buttons": "*",
+ "@syncfusion/ej2-react-documenteditor": "*",
+ "@syncfusion/ej2-react-dropdowns": "*",
+ "@syncfusion/ej2-react-inputs": "*",
+ "@syncfusion/ej2-react-interactive-chat": "*",
+ "@syncfusion/ej2-react-layouts": "*",
+ "@syncfusion/ej2-react-lists": "*",
+ "@syncfusion/ej2-react-navigations": "*",
+ "@syncfusion/ej2-react-notifications": "*",
+ "@syncfusion/ej2-react-popups": "*",
+ "@syncfusion/ej2-react-splitbuttons": "*",
+ "@xenova/transformers": "^2.17.2",
+ "@ai-sdk/azure": "^3.0.6",
+ "@ai-sdk/google": "^3.0.6",
+ "@azure/openai": "^2.0.0-beta.1",
+ "@google/generative-ai": "^0.13.0",
+ "ai": "^6.0.27",
+ "form-data": "^4.0.0",
+ "groq-sdk": "^0.5.0",
+ "openai": "^4.56.0",
+ "@testing-library/dom": "^10.4.0",
+ "@testing-library/jest-dom": "^6.6.3",
+ "@testing-library/react": "^16.3.0",
+ "@testing-library/user-event": "^13.5.0",
+ "@types/jest": "^27.5.2",
+ "@types/node": "^16.18.126",
+ "@types/react": "^19.1.6",
+ "@types/react-dom": "^19.1.5",
+ "react": "^19.1.0",
+ "react-dom": "^19.1.0",
+ "react-scripts": "5.0.1",
+ "typescript": "^4.9.5",
+ "web-vitals": "^2.1.4"
+ },
+ "scripts": {
+ "start": "react-scripts start",
+ "build": "react-scripts build",
+ "test": "react-scripts test",
+ "eject": "react-scripts eject"
+ },
+ "eslintConfig": {
+ "extends": [
+ "react-app",
+ "react-app/jest"
+ ]
+ },
+ "browserslist": {
+ "production": [
+ ">0.2%",
+ "not dead",
+ "not op_mini all"
+ ],
+ "development": [
+ "last 1 chrome version",
+ "last 1 firefox version",
+ "last 1 safari version"
+ ]
+ },
+ "devDependencies": {
+ "@types/codemirror": "^5.60.16"
+ }
+}
diff --git a/public/favicon.ico b/public/favicon.ico
new file mode 100644
index 0000000..a11777c
Binary files /dev/null and b/public/favicon.ico differ
diff --git a/public/index.html b/public/index.html
new file mode 100644
index 0000000..3121d5c
--- /dev/null
+++ b/public/index.html
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ AI-Powered React DOCX Editor
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/public/logo192.png b/public/logo192.png
new file mode 100644
index 0000000..fc44b0a
Binary files /dev/null and b/public/logo192.png differ
diff --git a/public/logo512.png b/public/logo512.png
new file mode 100644
index 0000000..a4e47a6
Binary files /dev/null and b/public/logo512.png differ
diff --git a/src/AIPopup.css b/src/AIPopup.css
new file mode 100644
index 0000000..df79aba
--- /dev/null
+++ b/src/AIPopup.css
@@ -0,0 +1,351 @@
+.ai-assist-dialog,
+.e-stop-generating-dialog {
+ max-height: none !important;
+}
+
+.e-dlg-container.e-smart-editor-dialog,
+.ai-settings-menu .e-control.e-contextmenu,
+.ai-smart-menu ul {
+ position: fixed !important;
+}
+
+.ai-assist-dialog,
+.e-stop-generating-dialog,
+.e-smart-editor-dialog {
+ min-width: 280px !important;
+}
+
+.e-original-word .e-original-word {
+ color: #DC143C;
+ text-decoration: line-through;
+}
+
+.e-improved-word {
+ color: #228B22;
+}
+
+.ai-chat-btn.e-btn {
+ height: 40px;
+ width: 40px;
+ min-height: 40px;
+ min-width: 40px;
+}
+
+.ai-input-wrapper {
+ flex: 1;
+}
+
+.e-caret-hide .e-caret {
+ display: none;
+}
+
+.ai-input-wrapper .e-send.e-disabled {
+ pointer-events: none;
+}
+
+.ai-gc-row {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+}
+
+.e-smart-editor-dialog .pane-content-header {
+ display: flex;
+ height: 48px;
+ align-items: center;
+}
+
+.e-smart-editor-dialog .pane-text-area {
+ height: 280px;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ padding: 6px 10px !important;
+ min-height: 280px;
+ overflow-y: auto;
+}
+
+.e-smart-editor-dialog .e-split-bar.e-resizable-split-bar,
+.e-smart-editor-dialog .e-resizable-split-bar.e-split-bar-hover,
+.e-smart-editor-dialog .e-resize-handler.e-icons {
+ display: none !important;
+}
+
+.e-smart-editor-dialog .e-dlg-header-content {
+ height: 56px;
+ padding: 12px 16px;
+}
+
+.e-bigger .e-smart-editor-dialog .e-dlg-header-content {
+ height: 64px;
+ padding: 12px 20px;
+}
+
+.e-smart-editor-dialog .e-dlg-header-content .e-custom-header {
+ height: 32px;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.e-bigger .e-smart-editor-dialog .e-dlg-header-content .e-custom-header {
+ height: 42px;
+}
+
+.e-ai-assist-toolbar,
+.e-ai-assist-toolbar .e-toolbar-items,
+.e-ai-assist-toolbar .e-toolbar-item,
+.e-ai-assist-toolbar .e-toolbar-item:hover,
+.e-ai-assist-toolbar .e-toolbar-item:focus,
+.e-ai-assist-toolbar .e-toolbar-item .e-tbar-btn,
+.e-ai-assist-toolbar .e-dropdown-btn.settings-btn {
+ background-color: #fff !important;
+}
+
+.e-ai-assist-toolbar .e-toolbar-item .e-tbar-btn,
+.e-ai-assist-toolbar .settings-btn {
+ box-shadow: none !important;
+}
+
+.e-ai-assist-toolbar .e-toolbar-item .e-tbar-btn-text,
+.e-ai-assist-toolbar .e-toolbar-item .e-btn-icon.e-icons {
+ color: #212529 !important;
+}
+
+.e-smart-editor-dialog .e-footer-content {
+ padding: 14px 16px 12px 16px;
+}
+
+.e-bigger .e-smart-editor-dialog .e-footer-content {
+ padding: 16px 20px 16px 20px;
+}
+
+.e-smart-editor-dialog .e-splitter.e-splitter-vertical {
+ border: none;
+}
+
+.e-ai-chat-btn::before,
+.stop-popup-text-icon::before {
+ content: '\e903';
+}
+
+.ai-assist-btn .e-ai-assist-btn::before {
+ content: '\e950';
+}
+
+.e-bigger .ai-assist-btn .e-icons.e-ai-assist-btn {
+ height: 16px;
+ width: 16px;
+ margin: 10px;
+}
+
+.ai-assist-btn.e-btn {
+ background-color: #ffffff !important;
+ border-radius: 4px !important;
+ border: 1px solid #0F6CBD !important;
+ color: #0F6CBD !important;
+ min-height: 4px !important;
+ min-width: 4px !important;
+}
+
+.ai-generate-dialog .e-dlg-header-content {
+ display: none !important;
+}
+
+.settings-btn.e-dropdown-btn {
+ width: 36px;
+ height: 32px;
+ border: none;
+ color: #6b7280 !important;
+ box-shadow: none !important;
+ background: #fff !important;
+}
+
+.e-bigger .e-smart-editor-dialog .settings-btn {
+ height: 40px;
+ width: 40px;
+}
+
+.settings-btn .e-btn-icon {
+ color: #6b7280;
+}
+
+.e-smart-editor-dialog .e-dlg-header-content .e-dlg-closeicon-btn {
+ height: 32px !important;
+ width: 32px !important;
+}
+
+.e-smart-editor-dialog .e-dlg-header-content .e-icon-dlg-close.e-icons {
+ position: static !important;
+}
+
+.e-stop-generating-dialog {
+ max-height: 100%;
+}
+
+.ai-stop-popup {
+ padding: 12px;
+ border-radius: 8px;
+ box-shadow: 0 0 16px 0 rgba(60, 60, 119, 0.11);
+}
+
+.e-bigger .ai-stop-popup {
+ height: 72px;
+ padding: 16px;
+}
+
+.ai-stop-popup .stop-popup-text {
+ margin: 0 6px;
+}
+
+.ai-stop-popup .stop-popup-text-icon {
+ color: #0F6CBD;
+ font-size: 18px;
+ vertical-align: middle;
+}
+
+.stop-popup-text-icon,
+.stop-popup-text {
+ line-height: 32px;
+}
+
+.e-bigger .stop-popup-text-icon,
+.e-bigger .stop-popup-text {
+ line-height: 40px;
+}
+
+.ai-stop-popup .ai-stop-btn {
+ height: 32px;
+ min-width: 151px;
+ line-height: 14px;
+ float: right;
+ z-index: 1000000001;
+}
+
+.e-bigger .ai-stop-popup .ai-stop-btn {
+ height: 40px;
+ min-width: 151px;
+ padding: 9px 16px;
+}
+
+.ai-stop-popup .ai-stop-btn,
+.ai-stop-popup .stop-popup-text,
+.e-ai-assist-toolbar .e-icons.e-btn,
+.e-smart-editor-dialog .translate-label,
+.e-smart-editor-dialog .e-icon-dlg-close,
+.ai-assist-dialog .e-dlg-content .e-icons.e-settings,
+.e-ai-assist-toolbar .e-toolbar-item.page-count span,
+.ai-assist-dialog .e-dlg-content .e-icons.e-reference,
+.ai-assist-dialog .e-dlg-content .e-icons.e-send,
+.ai-assist-dialog .e-dlg-content .e-textbox.e-input,
+.ai-assist-dialog .e-dlg-header-content .e-popup-header,
+.ai-assist-btn .e-icons.e-ai-assist-btn {
+ font-size: 14px !important;
+}
+
+.e-bigger .ai-stop-popup .ai-stop-btn,
+.e-bigger .e-ai-assist-toolbar .e-icons.e-btn,
+.e-bigger .ai-stop-popup .stop-popup-text,
+.e-bigger .e-smart-editor-dialog .translate-label,
+.e-smart-editor-dialog .e-dlg-header-content .e-popup-header,
+.e-bigger .e-ai-assist-toolbar .e-toolbar-item.page-count span,
+.e-bigger .e-smart-editor-dialog .e-icon-dlg-close,
+.e-bigger .ai-assist-dialog .e-dlg-content .e-icons.e-settings,
+.e-bigger .ai-assist-dialog .e-dlg-content .e-textbox.e-input,
+.e-bigger .ai-assist-dialog .e-dlg-header-content .e-popup-header,
+.ai-chat-btn .e-icons.e-ai-chat-btn,
+.e-bigger .ai-assist-btn .e-icons.e-ai-assist-btn {
+ font-size: 16px !important;
+}
+
+.ai-stop-popup .stop-popup-text-icon,
+.e-ai-assist-toolbar .e-toolbar-item.page-count span,
+.e-ai-assist-toolbar .e-icons.e-btn,
+.e-smart-editor-dialog .e-icon-dlg-close,
+.ai-assist-dialog .e-dlg-content .e-icons.e-settings,
+.ai-assist-dialog .e-dlg-content .e-icons.e-reference,
+.ai-assist-dialog .e-dlg-content .e-icons.e-send,
+.e-bigger .ai-assist-dialog .e-dlg-content .e-icons.e-reference,
+.e-bigger .ai-assist-dialog .e-dlg-content .e-icons.e-send,
+.ai-assist-dialog .e-dlg-content .e-textbox.e-input,
+.ai-chat-btn .e-icons.e-ai-chat-btn {
+ font-weight: 400 !important;
+}
+
+.e-smart-editor-dialog .translate-label,
+.ai-stop-popup .stop-popup-text,
+.e-bigger .ai-stop-popup .stop-popup-text,
+.e-smart-editor-dialog .e-dlg-header-content .e-popup-header {
+ font-weight: 600 !important;
+}
+
+.ai-settings-menu .e-menu-parent.e-ul .e-menu-item:not(.e-selected) {
+ padding-left: 34px;
+}
+
+.ai-settings-menu .e-menu-parent.e-ul .e-menu-item.e-selected {
+ padding-left: 8px;
+}
+
+.e-smart-editor-dialog .e-multiselect.e-control-wrapper,
+.e-smart-editor-dialog .e-ddl.e-control-wrapper {
+ margin-left: 8px;
+}
+
+.e-bigger .e-smart-editor-dialog .e-ddl.e-control-wrapper {
+ margin-left: 12px;
+}
+
+.e-bigger .e-smart-editor-dialog .e-multiselect.e-control-wrapper {
+ margin-left: 9px;
+}
+
+.e-smart-editor-dialog .e-header-toolbar {
+ height: 39px;
+}
+
+.e-bigger .e-smart-editor-dialog .e-header-toolbar {
+ height: 48px;
+}
+
+.e-smart-editor-dialog .e-icons.e-settings {
+ vertical-align: baseline;
+}
+
+.e-bigger .e-smart-editor-dialog .e-icons.e-settings {
+ height: 16px;
+ width: 16px;
+}
+
+.e-bigger .e-smart-editor-dialog .e-dlg-closeicon-btn {
+ top: 1px;
+}
+
+.e-smart-editor-dialog .e-dlg-content {
+ padding: 0 16px !important;
+}
+
+.e-bigger .e-smart-editor-dialog .e-dlg-content {
+ padding: 0 20px !important;
+}
+
+.ai-settings-menu ul {
+ z-index: 1000000005 !important;
+}
+
+.e-ribbon-help-template.e-hide {
+ display: none !important;
+}
+
+.e-smart-editor-dialog .e-footer-content {
+ margin-top: 12px;
+}
+
+.ai-assist-dialog .settings-btn {
+ color: #6b7280;
+}
+
+.ai-assist-dialog .e-footer-content .e-discard-btn {
+ background-color: #fff !important;
+ border-color: #C50F1F !important;
+ color: #C50F1F !important;
+}
\ No newline at end of file
diff --git a/src/AIPopup.jsx b/src/AIPopup.jsx
new file mode 100644
index 0000000..8ac4bd2
--- /dev/null
+++ b/src/AIPopup.jsx
@@ -0,0 +1,1093 @@
+import React, { useEffect, useMemo, useRef, useState } from 'react';
+import { ButtonComponent } from '@syncfusion/ej2-react-buttons';
+import { DialogComponent } from '@syncfusion/ej2-react-popups';
+import { ContextMenuComponent } from '@syncfusion/ej2-react-navigations';
+import { ToolbarComponent, ItemsDirective, ItemDirective } from '@syncfusion/ej2-react-navigations';
+import { SplitterComponent, PanesDirective, PaneDirective } from '@syncfusion/ej2-react-layouts';
+import { TextBoxComponent } from '@syncfusion/ej2-react-inputs';
+import { ComboBoxComponent, MultiSelectComponent, CheckBoxSelection, Inject } from '@syncfusion/ej2-react-dropdowns';
+import { DropDownButtonComponent } from '@syncfusion/ej2-react-splitbuttons';
+import { FabComponent } from '@syncfusion/ej2-react-buttons';
+import { createSpinner, showSpinner, hideSpinner } from '@syncfusion/ej2-popups';
+import './editor-helpers.js';
+import './AIPopup.css';
+import { getAzureChatAIRequest } from './ai-models';
+
+const GrammarOptions = [
+ { Name: 'Subject-Verb Agreement' }, { Name: 'Tense Consistency' }, { Name: 'Pronoun Agreement' },
+ { Name: 'Comma Usage' }, { Name: 'Parallel Structure' }, { Name: 'Misplaced Modifiers' },
+ { Name: 'Dangling Modifiers' }, { Name: 'Word Choice' }, { Name: 'Redundancy' },
+ { Name: 'Use of Articles' }, { Name: 'Punctuation Marks' }, { Name: 'Apostrophes for Possessives and Contractions' },
+ { Name: 'Spelling Errors' },
+];
+
+const TranslateList = ['English', 'Simplified Chinese', 'Spanish', 'French', 'Arabic', 'Portuguese', 'Russian', 'Urdu', 'Indonesian', 'German', 'Japanese'];
+
+const AiTask = { Generate: 'Generate', Rephrase: 'Rephrase', Translate: 'Translate', Grammar: 'Grammar' };
+
+let aiResults = [];
+
+function levenshtein(a, b) {
+ const dp = Array(a.length + 1).fill(0).map(() => Array(b.length + 1).fill(0));
+ for (let i = 0; i <= a.length; i++) dp[i][0] = i;
+ for (let j = 0; j <= b.length; j++) dp[0][j] = j;
+ for (let i = 1; i <= a.length; i++) {
+ for (let j = 1; j <= b.length; j++) {
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1;
+ dp[i][j] = Math.min(dp[i - 1][j] + 1, dp[i][j - 1] + 1, dp[i - 1][j - 1] + cost);
+ }
+ }
+ return dp[a.length][b.length];
+}
+
+function isSimilar(a, b, threshold = 1) {
+ if (!a || !b) return false;
+ return levenshtein(a, b) <= threshold;
+}
+
+function highlightDifferences(original, modified) {
+ const oWords = (original || '').split(/\s+/);
+ const mWords = (modified || '').split(/\s+/);
+ const lcs = Array(oWords.length + 1).fill(0).map(() => Array(mWords.length + 1).fill(0));
+ for (let i = 0; i < oWords.length; i++) {
+ for (let j = 0; j < mWords.length; j++) {
+ if (oWords[i] === mWords[j] || isSimilar(oWords[i], mWords[j])) lcs[i + 1][j + 1] = lcs[i][j] + 1;
+ else lcs[i + 1][j + 1] = Math.max(lcs[i + 1][j], lcs[i][j + 1]);
+ }
+ }
+
+ const unchanged = new Set();
+ let x = oWords.length, y = mWords.length;
+
+ while (x > 0 && y > 0) {
+ if (oWords[x - 1] === mWords[y - 1] || isSimilar(oWords[x - 1], mWords[y - 1])) { unchanged.add(`${x - 1}|${y - 1}`); x--; y--; }
+ else if (lcs[x - 1][y] >= lcs[x][y - 1]) x--;
+ else y--;
+ }
+
+ const highlightedOriginal = oWords.map((w, i) =>
+ [...unchanged].some(k => k.startsWith(`${i}|`)) ? w : `${w}`).join(' ');
+
+ const highlightedModified = mWords.map((w, j) =>
+ [...unchanged].some(k => k.endsWith(`|${j}`)) ? w : `${w}`).join(' ');
+
+ return { highlightedOriginal, highlightedModified };
+}
+
+export default function AIPopup({ editorRef, onShowChatPane, chatOpen, assistInitialPos, isAIEnabled }) {
+ const [isSmartEditor, setIsSmartEditor] = useState(false);
+ const [popupType, setPopupType] = useState('');
+ const [tone, setTone] = useState('Professional');
+ const [format, setFormat] = useState('Paragraph');
+ const [length, setLength] = useState('Medium');
+ const [translateTo, setTranslateTo] = useState('French');
+ const [checks, setChecks] = useState([]);
+ const [isLoading, setIsLoading] = useState(false);
+ const [outHtml, setOutHtml] = useState('');
+ const [inHtml, setInHtml] = useState('');
+ const [suggestions, setSuggestions] = useState([]);
+ const [currentIndex, setCurrentIndex] = useState(0);
+ const [userPrompt, setUserPrompt] = useState('');
+ const [dialogPos, setDialogPos] = useState({ x: '200', y: '160' });
+ const cmAssistRef = useRef(null);
+ const gearRef = useRef(null);
+ const genDialogRef = useRef(null);
+ const smartDialogRef = useRef(null);
+ const textboxRef = useRef(null);
+ const stopDlgRef = useRef(null);
+ const [draftRange, setDraftRange] = useState({ start: null, end: null });
+ const [stopVisible, setStopVisible] = useState(false);
+ const [stopPos, setStopPos] = useState({ x: 0, y: 0 });
+ const canceledRef = useRef(false);
+ const gearHeaderRef = useRef(null);
+ const cmSettingsRef = useRef(null);
+ const [genVisible, setGenVisible] = useState(false);
+ const [smartVisible, setSmartVisible] = useState(false);
+ const assistFabRef = useRef(null);
+ const chatFabRef = useRef(null);
+ const [viewerHost, setViewerHost] = useState(null);
+ const [fabChatVisible, setFabChatVisible] = useState(false);
+ const [fabAssistVisible, setFabAssistVisible] = useState(false);
+
+ const [assistBtn, setAssistBtn] = useState({
+ left: 80,
+ top: 160,
+ width: 24,
+ height: 24,
+ visible: true
+ });
+
+ const isContentGenerated = popupType === AiTask.Generate && !isSmartEditor && !!outHtml;
+
+ const menuItems = [
+ { text: 'Rephrase', iconCss: 'e-icons e-rephrase' },
+ { text: 'Translate', iconCss: 'e-icons e-translate' },
+ { text: 'Grammar', iconCss: 'e-icons e-grammar-check' },
+ ];
+
+ const settingsMenuItems = [
+ {
+ id: 'parent-tone', text: 'Choose Tone', items: [
+ { id: 'child-tone-professional', text: 'Professional' },
+ { id: 'child-tone-friendly', text: 'Friendly' },
+ { id: 'child-tone-instructional', text: 'Instructional' },
+ { id: 'child-tone-marketing', text: 'Marketing' },
+ { id: 'child-tone-academic', text: 'Academic' },
+ { id: 'child-tone-legal', text: 'Legal' },
+ { id: 'child-tone-technical', text: 'Technical' },
+ { id: 'child-tone-narrative', text: 'Narrative' },
+ { id: 'child-tone-direct', text: 'Direct' }
+ ]
+ },
+ {
+ id: 'parent-format', text: 'Choose Format', items: [
+ { id: 'child-format-paragraph', text: 'Paragraph' },
+ { id: 'child-format-blog-post', text: 'Blog post' },
+ { id: 'child-format-technical-documentation', text: 'Technical Documentation' },
+ { id: 'child-format-report', text: 'Report' },
+ { id: 'child-format-research-papers', text: 'Research Papers' },
+ { id: 'child-format-tutorial', text: 'Tutorial' },
+ { id: 'child-format-meeting-notes', text: 'Meeting Notes' }
+ ]
+ },
+ {
+ id: 'parent-size', text: 'Choose Size', items: [
+ { id: 'child-size-short', text: 'Short' },
+ { id: 'child-size-medium', text: 'Medium' },
+ { id: 'child-size-long', text: 'Long' }
+ ]
+ }
+ ];
+
+ function buildPrompt(task, text, Regenerate, {
+ tone = 'Professional',
+ format = 'Paragraph',
+ length = 'Medium',
+ fromLang = 'English',
+ toLang = 'French',
+ checks = [],
+ userHint = ''
+ } = {}) {
+ const content = (text || '').trim().toLowerCase();
+ const toneValue = String(tone).toLowerCase();
+ const formatValue = String(format).toLowerCase();
+ const lengthValue = String(length).toLowerCase();
+ switch (task) {
+ case 'Generate': {
+ const currentResult = getSelectionText();
+ if (!Regenerate) {
+ return (currentResult.length > 0) ? {
+ messages: [
+ { role: "system", content: `You are a helpful assistant. Your task is to analyze the provided text and revise it based on the provided suggestion: '${content}'. Please adjust the text to reflect a tone of '${toneValue}', formatted in '${formatValue}' style, and maintain a length of '${lengthValue}'. Always respond in proper HTML format, excluding , , and tags.` },
+ { role: "user", content: currentResult }
+ ],
+ model: "gpt-4",
+ } : {
+ messages: [
+ { role: "system", content: `You are a helpful assistant. Your task is to generate content based on the provided text. Please adjust the text to reflect a tone of '${toneValue}', formatted in '${formatValue}' style, and maintain a length of '${lengthValue}'. Always respond in proper text format not a md format. Always respond in proper HTML format, excluding , , and tags.` },
+ { role: "user", content: content }
+ ],
+ model: "gpt-4",
+ };
+ } else {
+ return {
+ messages: [
+ { role: "system", content: `You are a helpful assistant. Your task is to analyze the provided text and rephrase it. Please adjust the text to reflect a tone of '${toneValue}', formatted in '${formatValue}' style, and maintain a length of '${lengthValue}'. Always respond in proper HTML format, excluding , , and tags.` },
+ { role: "user", content: currentResult }
+ ],
+ model: "gpt-4",
+ };
+ }
+ }
+
+ case 'Rephrase': {
+ if (!Regenerate) {
+ return {
+ messages: [
+ { role: "system", content: `You are a helpful assistant. Your task is to analyze the provided text and rephrase it. Please adjust the text to reflect a tone of '${toneValue}', formatted in '${formatValue}' style, and maintain a length of '${lengthValue}'. Always respond in proper HTML format, excluding , , and tags.` },
+ { role: "user", content: content }
+ ],
+ model: "gpt-4",
+ }
+ } else {
+ return {
+ messages: [
+ { role: "system", content: `You are a helpful assistant. Your task is to analyze the provided text and revise it based on the provided suggestion: '${aiResults}'. Please adjust the text to reflect a tone of '${toneValue}', formatted in '${formatValue}' style, and maintain a length of '${lengthValue}'. Always respond in proper HTML format, excluding , , and tags.` },
+ { role: "user", content: content }
+ ],
+ model: "gpt-4",
+ }
+ }
+ }
+
+ case 'Translate':
+ return {
+ messages: [
+ { role: "system", content: `You are a helpful assistant. Your task is to translate the provided text into '${toLang}'. Always respond in proper HTML format, excluding and tags.` },
+ { role: "user", content: content }
+ ],
+ model: "gpt-4",
+ };
+
+ case 'Grammar': {
+ let value = '';
+ let systemPrompt = '';
+ if (checks.length > 0) {
+ checks.forEach((item) => {
+ value += item + ', ';
+ });
+ systemPrompt = `You are a helpful assistant. Your task is to analyze the provided text and perform the following grammar checks: ${value}. Please ensure that the revised text reflects these corrections. Always respond in proper HTML format, but do not include , , or tags.`;
+ } else {
+ systemPrompt = "You are a helpful assistant. Your task is to analyze the provided text, check for and correct any grammatical errors, and rephrase it. Always respond in proper HTML format, but do not include , , or tags.";
+ }
+ return {
+ messages: [
+ { role: "system", content: systemPrompt },
+ { role: "user", content: content }
+ ],
+ model: "gpt-4",
+ };
+ }
+ default:
+ return content;
+ }
+ }
+
+ const generateContentOpen = (args) => {
+ if (isContentGenerated) {
+ const aiAssistBtnPosition = window?.getAIAssistPopupPosition?.();
+ const updatedPosition = window?.getRegeneratePopupPosition?.();
+ if (!aiAssistBtnPosition || !updatedPosition) return;
+ var x = String(Math.round(aiAssistBtnPosition.x));
+ var y = String(Math.round(updatedPosition.y));
+ genDialogRef.current.position.X = x;
+ genDialogRef.current.position.Y = y;
+ setDialogPos({ x: x, y: y });
+ }
+ }
+
+ const generateFooterTemplate = () => (
+
+
+ Keep it
+
+ runTask(AiTask.Generate, true)}>
+ Regenerate
+
+ Discard
+
+ );
+
+ useEffect(() => {
+ if (!assistInitialPos) return;
+ if (!isAIEnabled) {
+ setFabAssistVisible(false);
+ setFabChatVisible(false);
+ }
+ setAssistBtn(prev => ({
+ ...prev,
+ left: assistInitialPos.left ?? prev.left,
+ top: assistInitialPos.top ?? prev.top,
+ width: assistInitialPos.width ?? prev.width,
+ height: assistInitialPos.height ?? prev.height
+ }));
+ }, [
+ assistInitialPos?.left,
+ assistInitialPos?.top,
+ assistInitialPos?.width,
+ assistInitialPos?.height
+ ]);
+
+ useEffect(() => {
+ let mounted = true;
+ const pick = () => {
+ if (!mounted) return;
+ const el = document.querySelector('#document-editor #documentEditorDiv');
+ if (el && el !== viewerHost) {
+ setViewerHost(el);
+ }
+ };
+ pick();
+ const id = setInterval(pick, 300);
+ const onResize = () => pick();
+ window.addEventListener('resize', onResize);
+ return () => {
+ mounted = false;
+ clearInterval(id);
+ window.removeEventListener('resize', onResize);
+ };
+ }, [viewerHost]);
+
+
+ useEffect(() => {
+ if (isAIEnabled) {
+ setFabChatVisible(!chatOpen);
+ }
+ if (!chatOpen) {
+ requestAnimationFrame(positionChatFabByHelper);
+ }
+ }, [chatOpen]);
+
+ const getSelectionText = () => {
+ try {
+ return (editorRef?.current?.documentEditor?.selection?.text || '').trim();
+ } catch { return ''; }
+ };
+
+ const replaceSelectionWithPlainText = async (html) => {
+ const text = (html || '').replace(/<[^>]+>/g, ' ').replace(/\s+/g, ' ').trim();
+ try {
+ const editor = editorRef?.current?.documentEditor;
+ if (editor?.selection?.text) editor.editor.delete();
+ editor?.editor?.insertText(text);
+ } catch (e) { alert('Replace failed: ' + e.message); }
+ };
+
+ const insertContent = async (out) => {
+ if (canceledRef.current) return;
+ closeStopDialog();
+ setInHtml('');
+ setOutHtml(out);
+ try {
+ const ed = editorRef?.current?.documentEditor;
+ if (ed) {
+ await ed.editor.delete();
+ ed.focusIn();
+ const { end: caretEndBefore } = getOffsets();
+ if (caretEndBefore != null) selectOffsets(caretEndBefore, caretEndBefore);
+ const plain = htmlToPlain(out);
+ if (canceledRef.current) {
+ return;
+ }
+ ed.editor.insertText(plain);
+ ed.editor.insertText('\n');
+ const { end: endAfter } = getOffsets();
+ if (caretEndBefore != null && endAfter != null && endAfter >= caretEndBefore) {
+ selectOffsets(caretEndBefore, endAfter);
+ setDraftRange({ start: caretEndBefore, end: endAfter });
+ } else {
+ setDraftRange({ start: null, end: null });
+ }
+ }
+ } catch (e) {
+ alert('Insert failed: ' + (e?.message || e));
+ }
+ setUserPrompt('');
+ }
+
+ async function runTask(task, isRegenerate = false, toLanguage) {
+ setIsLoading(true);
+ canceledRef.current = false;
+ var out = '';
+ aiResults = [];
+ try {
+ let sourceText = '';
+ let options = '';
+ if (task === AiTask.Generate) {
+ sourceText = userPrompt?.trim() || textboxRef.current.value?.trim();
+ setUserPrompt('');
+ window?.toggleSendIcon?.(false);
+ openStopDialog();
+ if (!isRegenerate && !sourceText) { setIsLoading(false); return; }
+ options = buildPrompt(AiTask.Generate, sourceText, isRegenerate, { tone, format, length });
+ setTimeout(async () => {
+ out = await getAzureChatAIRequest(options);
+ out = out.replace("```html\n", "").replace("\n```", "");
+ insertContent(out, isRegenerate);
+ }, 1000);
+ } else {
+ sourceText = getSelectionText();
+ if (!sourceText || sourceText.trim().length < 3) { setIsLoading(false); return; }
+ if (task === AiTask.Rephrase) {
+ const userHint = isRegenerate ? '' : (userPrompt?.trim() || '');
+ for (var i = 0; i < 3; i++) {
+ options = buildPrompt(AiTask.Rephrase, sourceText, isRegenerate, { tone, format, length, userHint });
+ out = await getAzureChatAIRequest(options);
+ out = out.replace("```html\n", "").replace("\n```", "");
+ if (!aiResults.includes(out)) {
+ aiResults.push(out);
+ }
+ }
+ }
+ else if (task === AiTask.Grammar) {
+ options = buildPrompt(AiTask.Grammar, sourceText, isRegenerate, { checks: checks });
+ out = await getAzureChatAIRequest(options);
+ out = out.replace("```html\n", "").replace("\n```", "");
+ }
+ else {
+ var toLang = toLanguage || translateTo;
+ options = buildPrompt(AiTask.Translate, sourceText, false, { fromLang: 'English', toLang: toLang });
+ out = await getAzureChatAIRequest(options);
+ out = out.replace("```html\n", "").replace("\n```", "");
+ }
+ out = aiResults.length > 0 ? aiResults[0] : out;
+ const { highlightedOriginal, highlightedModified } = highlightDifferences(`${sourceText}
`, out);
+ setInHtml(highlightedOriginal);
+ setOutHtml(highlightedModified);
+ setSuggestions(prev => {
+ setCurrentIndex(0);
+ return aiResults;
+ });
+ hideSpinner(document.getElementById('spinner-container'));
+ }
+ } catch (e) {
+ if (!canceledRef.current) alert('AI error: ' + (e?.message || e));
+ } finally {
+ setIsLoading(false);
+ }
+ }
+
+ const onMenuSelect = (args) => {
+ const sel = args.item?.text;
+ const action = sel === 'Rephrase' ? AiTask.Rephrase : (sel === 'Translate' ? AiTask.Translate : AiTask.Grammar);
+ if (!sel) return;
+ setPopupType(action);
+ setSuggestions([]); setCurrentIndex(0); setUserPrompt('');
+ setIsSmartEditor(true);
+ setInHtml(`${getSelectionText()}
`);
+ setOutHtml('');
+ showSpinner(document.getElementById('spinner-container'));
+ setSmartVisible(true);
+ setTimeout(() => runTask(action), 100);
+ };
+
+
+ const openAssistMenu = (ev) => {
+ ev?.preventDefault?.();
+ ev?.stopPropagation?.();
+ const sel = getSelectionText();
+ if (!sel) {
+ if (stopVisible) return;
+ setPopupType(AiTask.Generate);
+ setIsSmartEditor(false);
+ setInHtml(''); setOutHtml(''); setUserPrompt(''); setSuggestions([]);
+ const pos = window.getAIAssistPopupPosition ? window?.getAIAssistPopupPosition?.() : { x: 200, y: 160 };
+ setDialogPos({ x: String(Math.round(pos.x)), y: String(Math.round(pos.y)) });
+ setGenVisible(true);
+ requestAnimationFrame(() => genDialogRef.current?.refreshPosition?.());
+ return;
+ }
+ if (genVisible || stopVisible) return;
+ const aiPos = window.getAIButtonPosition ? window.getAIButtonPosition() : null;
+ if (aiPos && cmAssistRef.current?.open) {
+ cmAssistRef.current.open(Math.round(aiPos.y), Math.round(aiPos.x + 24));
+ return;
+ }
+ const el = assistFabRef.current?.element || assistFabRef.current;
+ if (!el || !cmAssistRef.current?.open) return;
+ const r = el.getBoundingClientRect();
+ const x = Math.round(r.left + window.scrollX + 24);
+ const y = Math.round(r.top + window.scrollY);
+ cmAssistRef.current.open(x, y);
+ };
+
+ const openChat = () => {
+ document.querySelector('.e-ribbon-help-template')?.classList.add('e-hide');
+ document.querySelector('.e-fab.ai-assist-btn')?.classList.add('e-hide');
+ document.querySelector('.document-editor-container')?.classList.add('e-hide');
+ setFabChatVisible(false);
+ onShowChatPane?.();
+ };
+
+ const onReplace = async () => { await replaceSelectionWithPlainText(outHtml); setSmartVisible(false); };
+
+ const prevSuggestion = () => {
+ setCurrentIndex(i => {
+ const next = Math.max(i - 1, 0);
+ setOutHtml(suggestions[next] || outHtml);
+ return next;
+ });
+ };
+
+ const nextSuggestion = () => {
+ setCurrentIndex(i => {
+ const next = Math.min(i + 1, suggestions.length - 1);
+ setOutHtml(suggestions[next] || outHtml);
+ return next;
+ });
+ };
+
+ const headerText = useMemo(() => {
+ if (popupType === AiTask.Rephrase) return 'Rephrased Content';
+ if (popupType === AiTask.Translate) return 'Translate';
+ if (popupType === AiTask.Grammar) return 'Grammar Check';
+ return 'AI Assistant';
+ }, [popupType]);
+
+ const openHeaderSettingsMenu = () => {
+ const btn = gearHeaderRef.current;
+ if (!btn || !cmSettingsRef.current) return;
+ openMenuBesideButton(btn, cmSettingsRef);
+ };
+
+ const UpdateIconCss = (items) => {
+ items.forEach((item) => {
+ const id = item.id?.toLowerCase() || "";
+ var isChild = id.includes("child");
+
+ var match = id.includes(tone.toLowerCase()) ||
+ id.includes(format.split(" ").join('-').toLowerCase()) ||
+ id.includes(length.toLowerCase());
+
+ item.iconCss = (isChild && match) ? "e-icons e-check" : null;
+
+ if (Array.isArray(item.items) && item.items.length) {
+ UpdateIconCss(item.items);
+ }
+ })
+ }
+
+ const onSettingsMenuSelect = (args) => {
+ const id = args.item?.id || '';
+ const text = args.item?.text || '';
+ if (id.startsWith('child')) {
+ if (id.startsWith('child-tone-')) setTone(text);
+ else if (id.startsWith('child-format-')) setFormat(text);
+ else if (id.startsWith('child-size-')) setLength(text);
+ UpdateIconCss(settingsMenuItems);
+ }
+ };
+
+ const openGearMenu = (args) => {
+ const btn = gearRef.current;
+ if (!btn || !cmSettingsRef.current) return;
+ openMenuBesideButton(btn, cmSettingsRef);
+ };
+
+ const onSend = () => {
+ runTask(AiTask.Generate);
+ };
+
+ const changeLanguage = (e) => {
+ setTranslateTo(e.value);
+ setTimeout(() => {
+ runTask(AiTask.Translate, false, e.value);
+ }, 100);
+ };
+
+ const smartHeaderTemplate = () => (
+
+
{headerText}
+ {popupType === AiTask.Rephrase && (
+
+
+
+
+
+ = suggestions.length}
+ click={nextSuggestion}
+ />
+ (
+ { args.cancel = true; openHeaderSettingsMenu(); }}
+ />
+ )}
+ />
+
+
+
+ )}
+
+ );
+
+ const smartFooterTemplate = () => (
+
+
+ Replace
+
+ {popupType === AiTask.Rephrase && (
+ { showSpinner(document.getElementById('spinner-container')); setTimeout(() => runTask(AiTask.Rephrase, true), 10); }}
+ isPrimary
+ >
+ Regenerate
+
+ )}
+ {popupType === AiTask.Grammar && (
+ { showSpinner(document.getElementById('spinner-container')); setTimeout(() => runTask(AiTask.Grammar, true), 10); }}
+ isPrimary
+ >
+ Regenerate
+
+ )}
+ setSmartVisible(false)}>Cancel
+
+ );
+
+ const positionChatFabByHelper = () => {
+ const position = window?.getAIChatBtnPosition?.();
+ if (!position) return;
+ window?.setAiAssistBtnPosition?.(Math.round(position.x), Math.round(position.y));
+ };
+
+ const onChatFabCreated = () => {
+ requestAnimationFrame(positionChatFabByHelper);
+ window.addEventListener('resize', positionChatFabByHelper);
+ };
+
+ useEffect(() => {
+ createSpinner({
+ target: document.getElementById('spinner-container'),
+ });
+ return () => window.removeEventListener('resize', positionChatFabByHelper);
+ }, []);
+
+ const positionAssistFabInitial = () => {
+ try {
+ const pos = window?.getAIAssistBtnPosition?.();
+ if (!pos) return;
+ setAssistBtn(s => ({
+ ...s,
+ left: Math.round(pos.x),
+ top: Math.round(pos.y),
+ width: 24,
+ height: 24,
+ visible: true
+ }));
+ } catch { }
+ };
+
+ useEffect(() => {
+ if (viewerHost) positionAssistFabInitial();
+ }, [viewerHost]);
+
+ useEffect(() => {
+ if (editorRef && editorRef?.current) {
+ const ed = editorRef?.current?.documentEditor;
+ if (!ed) return;
+ const onSelectionChange = () => {
+ try {
+ const pos = window?.getAIAssistBtnPosition?.();
+ if (pos) {
+ setAssistBtn(prev => ({
+ ...prev,
+ left: Math.round(pos.x),
+ top: Math.round(pos.y)
+ }));
+ }
+ } catch { }
+ };
+ ed.selectionChange = onSelectionChange;
+
+ }
+ }, [editorRef, viewerHost]);
+
+ useEffect(() => {
+ const viewerEl =
+ viewerHost || document.querySelector('#document-editor #documentEditorDiv');
+ if (!viewerEl) return;
+
+ let tracking = false;
+
+ const onMouseDown = () => {
+ tracking = true;
+ try {
+ const sel = editorRef?.current?.documentEditor?.selection?.text || '';
+ if (sel && isSmartEditor) setIsSmartEditor(false);
+ } catch { }
+ };
+
+ const onMouseUp = () => {
+ if (!tracking) return;
+ tracking = false;
+
+ setTimeout(() => {
+ try {
+ const selText = editorRef?.current?.documentEditor?.selection?.text || '';
+ if (!!selText && selText.trim().length > 0) {
+ setIsSmartEditor(true);
+ assistFabRef.title = 'Refine the content';
+ }
+
+ const pos = window?.getAIAssistBtnPosition?.();
+ if (pos && typeof pos.x === 'number' && typeof pos.y === 'number') {
+ setAssistBtn(prev => ({
+ ...prev,
+ left: Math.round(pos.x),
+ top: Math.round(pos.y)
+ }));
+ }
+ } catch { }
+ }, 10);
+ };
+
+ viewerEl.addEventListener('mousedown', onMouseDown, false);
+ viewerEl.addEventListener('mouseup', onMouseUp, false);
+ return () => {
+ viewerEl.removeEventListener('mousedown', onMouseDown, false);
+ viewerEl.removeEventListener('mouseup', onMouseUp, false);
+ };
+ }, [viewerHost, isSmartEditor, editorRef]);
+
+ useEffect(() => {
+ if (!genVisible) return;
+
+ const isInside = (el, target) => {
+ if (!el || !target) return false;
+ if (el.contains(target)) return true;
+ const path = typeof target.composedPath === 'function' ? target.composedPath() : [];
+ return path.includes(el);
+ };
+
+ const onOutsidePress = (e) => {
+ try {
+ const dlgEl = genDialogRef.current?.element;
+ const settingsEl = document.querySelector('.ai-settings-menu');
+
+ const inDialog = isInside(dlgEl, e.target);
+ const inSettings = isInside(settingsEl, e.target);
+
+ if (!inDialog && !inSettings) {
+ setGenVisible(false);
+ }
+ } catch { }
+ };
+
+ document.addEventListener('pointerdown', onOutsidePress, true);
+ document.addEventListener('touchstart', onOutsidePress, { capture: true, passive: true });
+ document.addEventListener('mousedown', onOutsidePress, true);
+ return () => {
+ document.removeEventListener('pointerdown', onOutsidePress, true);
+ document.removeEventListener('touchstart', onOutsidePress, true);
+ document.removeEventListener('mousedown', onOutsidePress, true);
+ };
+ }, [genVisible]);
+
+ function openMenuBesideButton(btnRef, cmRef) {
+ if (!btnRef || !cmRef?.current?.open) return;
+ const rect = btnRef.element.getBoundingClientRect();
+ const y = rect.bottom;
+ const x = rect.left;
+ cmRef.current.open(Math.round(y), Math.round(x));
+ }
+
+ const TextBoxCreated = () => {
+ const inst = textboxRef.current;
+ if (!inst) return;
+ const isDisabled = userPrompt?.trim() ? '' : 'e-disabled'
+ const className = 'e-icons e-send ' + isDisabled;
+ inst.addIcon('append', className);
+ const wrapper = inst.element?.parentElement;
+ const icon = wrapper?.querySelector('.e-input-group-icon.e-send');
+ if (icon) {
+ icon.setAttribute('title', 'Generate');
+ icon.setAttribute('role', 'button');
+ icon.setAttribute('aria-label', 'Generate');
+ icon.addEventListener('click', onSend);
+ }
+ return;
+ }
+
+ const textboxValueChange = (e) => {
+ const value = e.value;
+ setUserPrompt(value);
+ window?.toggleSendIcon?.(value.length > 0);
+ }
+
+ const htmlToPlain = (html) =>
+ (html || '').replace(/<[^>]+>/g, ' ').replace(/\s+/g, ' ').trim();
+
+ const getDE = () => editorRef?.current?.documentEditor || null;
+
+ const getOffsets = () => {
+ const ed = getDE();
+ const sel = ed?.selection;
+ try {
+ const start = sel.startOffset;
+ const end = sel?.endOffset;
+ return { start, end };
+ } catch {
+ return { start: null, end: null };
+ }
+ };
+
+ const selectOffsets = (start, end) => {
+ const sel = getDE()?.selection;
+ if (sel?.select && start != null && end != null) sel.select(start, end);
+ };
+
+ const onKeepGenerated = () => {
+ const ed = getDE();
+ const { end } = draftRange || {};
+ if (ed && end != null) {
+ selectOffsets(end, end);
+ }
+ setDraftRange({ start: null, end: null });
+ setGenVisible(false);
+ };
+
+ const onDiscardGenerated = () => {
+ const ed = getDE();
+ const { start, end } = draftRange || {};
+ if (ed && start != null && end != null) {
+ selectOffsets(start, end);
+ ed.editor.delete();
+ }
+ setDraftRange({ start: null, end: null });
+ setGenVisible(false);
+ };
+
+ const openStopDialog = () => {
+ setGenVisible(false);
+ const pos = window?.getGeneratingDraftPosition?.() || { x: 200, y: 160 };
+ setStopPos({ x: Math.round(pos.x + 24), y: Math.round(pos.y) });
+ setStopVisible(true);
+ };
+
+ const closeStopDialog = () => {
+ setGenVisible(true);
+ setStopVisible(false);
+ }
+
+ const stopContentTemplate = () => (
+
+
+ Generating a draft...
+
+ );
+
+ useEffect(() => {
+ if (!isAIEnabled) {
+ setFabAssistVisible(false);
+ setFabChatVisible(false);
+ } else {
+ setFabAssistVisible(isAIEnabled);
+ setFabChatVisible(isAIEnabled);
+ }
+ }, [isAIEnabled]);
+
+ const shouldTick = (id) => {
+ const key = (id || '').toLowerCase();
+ const toneKey = (tone || '').toLowerCase();
+ const formatKey = (format || '').toLowerCase().replace(/\s+/g, '-');
+ const lengthKey = (length || '').toLowerCase();
+ const isChild = key.includes('child');
+ const match = key.includes(toneKey) || key.includes(formatKey) || key.includes(lengthKey);
+ return isChild && match;
+ };
+
+ const onSettingsBeforeItemRender = (args) => {
+ const id = args.item?.id || '';
+ const li = args.element;
+ const hasIcon = !!li.querySelector('.e-menu-icon');
+ if (shouldTick(id)) {
+ if (!hasIcon) {
+ const icon = document.createElement('span');
+ icon.className = 'e-menu-icon e-icons e-check';
+ li.insertBefore(icon, li.firstChild);
+ li.className = li.className + ' e-selected';
+ } else {
+ li.querySelector('.e-menu-icon').className = 'e-menu-icon e-icons e-check';
+ }
+ }
+ };
+
+ const floatFocus = (args) => {
+ args.target.parentElement.classList.add("e-input-focus");
+ };
+
+ const floatBlur = (args) => {
+ args.target.parentElement.classList.remove('e-input-focus');
+ };
+
+ return (
+
+
+
+
{ e.preventDefault(); e.stopPropagation(); }}
+ onClick={openAssistMenu}
+ style={{
+ position: 'absolute',
+ left: `${assistBtn.left}px`,
+ top: `${assistBtn.top}px`,
+ width: `${assistBtn.width}px`,
+ height: `${assistBtn.height}px`
+ }}
+ />
+
+
+
+
+
+
+
+ {
+ if (e.key === 'Enter' && userPrompt?.trim()) {
+ e.preventDefault();
+ onSend();
+ }
+ }}
+ />
+
+
{ args.cancel = true; openGearMenu(); }}
+ />
+
+
+
+
+
+ setSmartVisible(false)}
+ cssClass="e-smart-editor-dialog">
+
+
+
+
+
+
+
+
+
+
+ } />
+
+
+
+
+ {popupType === AiTask.Translate && (
+
+ )}
+ {popupType === AiTask.Grammar && (
+ setChecks(e.value || [])}
+ mode="CheckBox"
+ showSelectAll={true}
+ showDropDownIcon={true}
+ allowFiltering={true}
+ placeholder="e.g. Spelling Errors"
+ width="180px"
+ popupHeight="260px"
+ >
+
+
+ )}
+
+
+
+ } />
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/App.css b/src/App.css
new file mode 100644
index 0000000..1728e0d
--- /dev/null
+++ b/src/App.css
@@ -0,0 +1,650 @@
+body {
+ overflow: hidden !important;
+ margin: 8px 8px 0 8px !important;
+}
+
+body .col-lg-9 {
+ width: 100% !important;
+}
+
+#documenteditor_titlebar {
+ height: 36px;
+ line-height: 26px;
+ padding-left: 15px;
+ padding-right: 10px;
+ font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
+}
+
+#documenteditor_title_contentEditor {
+ height: 26px;
+ max-width: 85%;
+ width: auto;
+ overflow: hidden;
+ display: inline-block;
+ padding-left: 4px;
+ padding-right: 4px;
+ margin: 5px;
+}
+
+.single-line {
+ cursor: text !important;
+ outline: none;
+}
+
+.single-line:hover {
+ border-color: #e4e4e4 !important;
+}
+
+[contenteditable="true"].single-line {
+ white-space: nowrap;
+ border-color: #e4e4e4 !important;
+}
+
+[contenteditable="true"].single-line {
+ white-space: nowrap;
+ border-color: #e4e4e4 !important;
+}
+
+[contenteditable="true"].single-line * {
+ white-space: nowrap;
+}
+
+@font-face {
+ font-family: 'Sample brower icons';
+ src: url(data:application/x-font-ttf;charset=utf-8;base64,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bwBuAHQAIABnAGUAbgBlAHIAYQB0AGUAZAAgAHUAcwBpAG4AZwAgAFMAeQBuAGMAZgB1AHMAaQBvAG4AIABNAGUAdAByAG8AIABTAHQAdQBkAGkAbwB3AHcAdwAuAHMAeQBuAGMAZgB1AHMAaQBvAG4ALgBjAG8AbQAAAAACAAAAAAAAAAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEMBAgEDAQQBBQEGAQcBCAEJAQoBCwEMAQ0BDgEPARABEQESARMBFAEVARYBFwEYARkBGgEbARwBHQEeAR8BIAEhASIBIwEkASUBJgEnASgBKQEqASsBLAEtAS4BLwEwATEBMgEzATQBNQE2ATcBOAE5AToBOwE8AT0BPgE/AUABQQFCAUMBRAALU3Ryb2tlU3R5bGUIQm9va21hcmsHUGljdHVyZQRGaW5kDU91dHNpZGVCb3JkZXIHSnVzdGlmeQVDbG9zZQ5EZWNyZWFzZUluZGVudBVQaXhlbEFsaWduQ2VudGVyVGFibGUPQmFja2dyb3VuZENvbG9yC0FsaWduQm90dG9tCVBhZ2VTZXR1cA5IaWdobGlnaHRDb2xvcgtTdXBlcnNjcmlwdAVUYWJsZQRVbmRvC0luc2VydEJlbG93CVRvcEJvcmRlcgpQYWdlTnVtYmVyEEFsaWduQ2VudGVyVGFibGUOSW5jcmVhc2VJbmRlbnQEQm9sZAlBbGlnbkxlZnQGRm9vdGVyC0luc2VydFJpZ2h0CVVuZGVybGluZQpJbnNlcnRMZWZ0BExvY2sGSGVhZGVyDVN0cmlrZXRocm91Z2gIQ2xlYXJBbGwLUmlnaHRCb3JkZXIKQWxpZ25SaWdodARPcGVuClN0cm9rZVNpemUFUHJpbnQLRGVsZXRlVGFibGUJRm9udENvbG9yDUluc2lkZUJvcmRlcnMKRGVsZXRlUm93cwhEb3dubG9hZAtMaW5lU3BhY2luZxRJbnNpZGVWZXJ0aWNhbEJvcmRlcghBbGlnblRPcARSZWRvDEJvdHRvbUJvcmRlcgNOZXcFUGFzdGUHQnVsbGV0cwRDZWxsDURlbGV0ZUNvbHVtbnMKQWxsQm9yZGVycwlTdWJzY3JpcHQQU2hvd0hpZGVQcm9wZXJ0eQ5UYWJsZU9mQ29udGVudAZJdGFsaWMWSW5zaWRlSG9yaXpvbmRhbGJvcmRlcgtMZWZ0Qm9yZGVycwlOdW1iZXJpbmcETGluawtBbGlnbkNlbnRlcgtJbnNlcnRBYm92ZQZCcmVha3MITmV4dFBhZ2USU2VsZWN0ZnJvbUNvbXB1dGVyCVBhZ2VCcmVhawAAAAA=) format('truetype');
+ font-weight: normal;
+ font-style: normal;
+}
+
+[class^="e-de-icon-"],
+[class*=" e-de-icon-"] {
+ font-family: 'Sample brower icons' !important;
+}
+
+.e-de-icon-Print:before {
+ content: "\e723";
+}
+
+.e-de-icon-Download:before {
+ content: "\e70a";
+}
+
+#documenteditor_titlebar #de-print,
+#documenteditor_titlebar #documenteditor-share {
+ font-size: 12px !important;
+}
+
+.content-wrapper {
+ margin: 10px;
+}
+
+.center-vertical {
+ min-height: calc(100vh - 20px);
+ flex-direction: column;
+}
+
+.uploader-page {
+ margin: 100px 250px;
+}
+
+.upload-wrapper,
+.uploader-page-header,
+.create-new-button-section {
+ text-align: center;
+ max-width: 100%;
+}
+
+.upload-wrapper .e-file-select-wrap {
+ padding: 48px 28px !important;
+}
+
+.upload-wrapper .e-file-select-wrap .e-btn {
+ background: #fff;
+ border: 1px solid #D3D3D3;
+ color: #000;
+ font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
+ font-size: 16px !important;
+ height: 48px;
+ width: 160px;
+ border-radius: 10px;
+ font-weight: 600;
+ padding: 12px 20px;
+ line-height: 16px;
+}
+
+.upload-wrapper .e-upload {
+ border: 2px dashed #0F6CBD;
+ border-radius: 16px;
+}
+
+.uploader-page-header {
+ font-family: Segoe UI;
+ font-size: 28px;
+ line-height: 36px;
+ margin-bottom: 24px;
+}
+
+.e-upload .e-file-drop {
+ color: #6b7280;
+ font-size: 18px !important;
+ font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
+ display: block;
+ margin-top: 14px;
+ margin-left: 0px !important;
+}
+
+.create-new-button {
+ border-radius: 12px;
+ border: none;
+ font-size: 18px !important;
+ font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
+ height: 52px;
+ min-height: 48px;
+ width: 262px;
+ line-height: 54px;
+ padding: 14px 28px;
+}
+
+.create-new-button-section {
+ margin-top: 28px;
+}
+
+.create-new-button-section .e-new-icons.e-icons {
+ line-height: 28px;
+ margin-top: 0;
+}
+
+
+.e-back-button {
+ width: 32px !important;
+ height: 32px !important;
+}
+
+.e-new-icons,
+.e-new-icons::before {
+ width: 28px !important;
+ height: 28px !important;
+}
+
+.e-new-icons::before {
+ line-height: 26px;
+}
+
+.create-new-button,
+.create-new-button-section {
+ align-items: center;
+}
+
+.e-back-button {
+ border: none;
+}
+
+.center-vertical,
+.create-new-button,
+.create-new-button-section {
+ display: flex;
+}
+
+.back-button-container {
+ display: inline-block;
+}
+
+.e-back-button {
+ font-size: 12px;
+}
+
+.e-new-icons {
+ font-size: 14px !important;
+ font-weight: 900 !important;
+}
+
+.e-new-icons,
+.create-new-button {
+ font-weight: 400;
+}
+
+.uploader-page-header {
+ font-weight: 600;
+}
+
+.center-vertical,
+.create-new-button-section {
+ justify-content: center;
+}
+
+.e-back-button {
+ border-radius: 50%;
+ margin-right: 12px;
+}
+
+/* Ensure we target only this uploader instance */
+.e-upload.e-control-wrapper .e-file-drop::after {
+ content: "Supported formats: DOCX, DOC and RTF.";
+ display: block;
+ font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
+ margin-top: 14px;
+ font-size: 14px;
+ color: #7a7f88;
+ /* tweak color as needed */
+}
+
+/* Optional: center-align if your theme differs */
+.e-upload.e-control-wrapper .e-file-drop,
+.e-upload.e-control-wrapper .e-file-drop::after {
+ text-align: center;
+}
+
+.e-fe-upload:before {
+ content: "\e712";
+}
+
+.e-fe-upload {
+ margin-top: -4px !important;
+ margin-right: 8px;
+ font-weight: 600 !important;
+}
+
+.e-documenteditorcontainer .e-de-status-bar .e-de-pagenumber-input {
+ color: rgb(73, 80, 87) !important;
+ font-family: inherit !important;
+ font-size: 14px !important;
+ text-align: center !important;
+ width: 22px !important;
+ background: rgb(255, 255, 255) !important;
+ border: 1px solid rgb(222, 226, 230) !important;
+ border-radius: 2px !important;
+ padding: 0px !important;
+ height: 100% !important;
+}
+
+.e-documenteditorcontainer .e-de-status-bar .e-btn-pageweb-spellcheck {
+ margin-left: calc(100% - 400px) !important;
+}
+
+.e-ribbon .help-pane-content {
+ color: #0F172AE0;
+ display: flex;
+ align-items: center;
+ font-size: 14px;
+ font-weight: 500;
+}
+
+.e-ribbon .help-pane-content .syncfusion-logo {
+ margin-right: 10px;
+}
+
+.e-ribbon .help-pane-content a,
+.e-ribbon .help-pane-content a:hover {
+ text-decoration: none;
+}
+
+.help-pane-content a,
+.help-pane-content a:visited,
+.help-pane-content a:hover,
+.help-pane-content a:active {
+ color: #0d6efd;
+}
+
+.footer {
+ position: fixed;
+ left: 8px;
+ right: 8px;
+ bottom: 0;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ background: linear-gradient(90.4deg, #C5D9FF 0.03%, #E8F0FF 11.56%, #E8F0FF 85.56%, #C5D9FF 99.97%);
+ border: 1px solid #D8DEE9;
+ z-index: 100;
+ height: 74px;
+}
+
+.footer-container {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ gap: 16px;
+}
+
+.docx-icon {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ flex: 0 0 auto;
+ width: 40px;
+ height: 40px;
+}
+
+.footer-content {
+ display: flex;
+ align-items: center;
+ gap: 40px;
+}
+
+
+.footer-content .title span {
+ color: #000;
+ font-weight: 400;
+ font-size: 16px;
+ font-family: 'Open Sans';
+}
+
+.footer-content .title span .main-title {
+ font-weight: 700;
+ font-size: 16px;
+ font-family: 'Open Sans';
+}
+
+.footer-content .buttons {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+}
+
+.footer-content .e-trial-btn::before {
+ content: 'Start Free Trial';
+}
+
+.footer-content .e-trial-btn {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ background: #0057FF;
+ border-radius: 8px;
+ padding: 10px 16px;
+ font-size: 14px;
+ font-weight: 700;
+ font-family: 'Open Sans';
+ height: 40px;
+ box-shadow: none;
+}
+
+.footer-content .e-demo-btn {
+ display: flex;
+ align-items: center;
+ background-color: transparent;
+ border: transparent;
+ color: #0057FF;
+ border-radius: 6px;
+ gap: 10px;
+ padding: 10px 16px;
+ font-size: 14px;
+ font-weight: 700;
+ font-family: 'Open Sans';
+ height: 40px;
+ box-shadow: none;
+}
+
+.footer-content .e-demo-btn:hover {
+ background-color: #D4E2FF;
+ color: #0057FF;
+}
+
+.footer-content .e-trial-btn::after {
+ content: "\e7f9";
+ font-family: 'e-icons';
+}
+
+.footer-content .e-primary:hover {
+ background: #0050EB;
+}
+
+#documenteditor_titlebar #de-print::after {
+ content: 'Print';
+}
+
+#documenteditor_titlebar #documenteditor-share::after {
+ content: 'Download';
+}
+
+
+@media (max-width: 1140px) {
+ .footer-content .title {
+ display: flex;
+ flex-direction: column;
+ }
+
+ .footer-content .title span,
+ .footer-content .title span .main-title {
+ font-size: 14px;
+ }
+}
+
+/* To hide the footer content: "Try our DOCX Editor SDK — create, edit, and collaborate!" */
+@media (max-width: 900px) {
+
+ .e-ribbon .help-pane-content,
+ .footer-content .title span:last-child {
+ display: none;
+ }
+}
+
+/* To hide the "Request Demo" button */
+@media (max-width: 730px) {
+ .footer-content .buttons .e-demo-btn {
+ display: none;
+ }
+
+ .footer-content .buttons .e-trial-btn::before {
+ content: 'Free Trial';
+ }
+
+ .footer-content .buttons .e-trial-btn::after {
+ content: none;
+ }
+}
+
+@media (max-width: 767px) {
+ .uploader-page {
+ margin: 0 0 50px 0 !important;
+ }
+}
+
+@media (max-width: 600px) {
+ .ai-generate-dialog.ai-assist-dialog {
+ width: 300px !important;
+ }
+
+ #documenteditor_titlebar #de-print::after,
+ #documenteditor_titlebar #documenteditor-share::after {
+ content: '';
+ }
+
+ #documenteditor_titlebar #de-print,
+ #documenteditor_titlebar #documenteditor-share {
+ padding: 4px 8px;
+ }
+
+ .e-fab.ai-assist-btn.e-hide,
+ .document-editor-container.e-hide {
+ display: none !important;
+ }
+
+ .document-editor-container {
+ float: none !important;
+ }
+
+ .ai-chat-container {
+ float: none !important;
+ width: 100% !important;
+ }
+
+ .ai-chat-container .e-aiassist-chat {
+ border-left: 1px solid #ced4da !important;
+ }
+
+ .e-control.e-dialog.e-smart-editor-dialog {
+ width: 90% !important;
+ }
+
+ .e-smart-editor-dialog .e-dlg-header-content .e-toolbar-item.e-tbtn-align {
+ max-width: 32px !important;
+ }
+
+ .e-smart-editor-dialog .e-dlg-header-content .e-toolbar-item {
+ padding: 0 !important;
+ }
+
+ .e-smart-editor-dialog .e-header-toolbar {
+ height: 38px !important;
+ }
+}
+
+
+.e-ribbon-group-overflow-ddb .e-ribbon-overflow-target .e-ribbon-overflow-header {
+ font-size: 12px !important;
+}
+
+.e-ribbon-gallery-popup.e-popup .e-ribbon-gallery-header,
+.e-ribbon-gallery-dropdown.e-popup .e-ribbon-gallery-header,
+.e-ribbon-group-overflow-ddb .e-ribbon-overflow-target .e-ribbon-overflow-header {
+ font-weight: 600 !important;
+}
+
+.e-ribbon-gallery-popup.e-popup .e-ribbon-gallery-container .e-ribbon-gallery-item,
+.e-ribbon-gallery-dropdown.e-popup .e-ribbon-gallery-container .e-ribbon-gallery-item {
+ width: 96px !important;
+}
+
+.e-ribbon.e-rbn .e-ribbon-item .e-ribbon-gallery-button {
+ border-style: outset !important;
+}
+
+.e-switch-wrapper .e-switch-handle {
+ background-color: #21252940 !important;
+}
+
+.e-switch-wrapper .e-switch-handle.e-switch-active {
+ background-color: #0D6EFD !important;
+}
+
+#documenteditor_titlebar .e-switch-wrapper.titlebar-ai-switch .e-switch-inner.e-switch-active .e-switch-on,
+#documenteditor_titlebar .e-switch-wrapper.titlebar-ai-switch .e-switch-inner .e-switch-off {
+ background-color: #fff !important;
+}
+
+.e-aiassist-chat .e-view-header .e-tbar-btn-text {
+ font-weight: 600;
+ line-height: 20px;
+}
+
+.e-aiassist-chat .e-aiassist-chat-icon::before,
+.e-aiassist-chat .e-aiassist-page-icon::before {
+ content: '\e7b4';
+}
+
+.ai-chat-container .e-aiassist-chat {
+ border-left: 0;
+}
+
+.e-aiassist-chat .e-view-content .ai-assist-banner {
+ display: flex;
+ flex-direction: column;
+ height: 348px;
+ align-items: center;
+ justify-content: center;
+}
+
+.e-aiassist-chat .e-aiassist-page-icon {
+ color: #616161;
+ width: 40px;
+ height: 40px;
+ background-color: #e6e6e6;
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.e-aiassist-chat .e-aiassist-page-header {
+ font-size: 12px;
+ margin: 12px;
+ line-height: 16px;
+}
+
+.e-aiassist-chat .e-suggestions {
+ margin: 10px 0 0;
+}
+
+.e-aiassist-chat .e-suggestions li {
+ height: 36px;
+ font-weight: 400 !important;
+ line-height: 18px !important;
+ padding: 8px 12px !important;
+ margin-bottom: 12px !important;
+}
+
+.e-aiassist-chat .e-view-header .e-icons.e-aiassist-chat-header {
+ display: none;
+}
+
+.e-aiassist-chat .e-view-header .e-icons.e-close,
+.e-aiassist-chat .e-aiassist-page-icon::before,
+.e-aiassist-chat .e-aiassist-page-header {
+ font-weight: 400;
+}
+
+.e-aiassist-chat .e-footer,
+.e-aiassist-chat .e-views .e-view-container {
+ width: 96% !important;
+}
+
+
+.e-aiassist-chat .e-toolbar .e-close {
+ width: 30px;
+ font-weight: 400;
+ font-size: 15px;
+ text-align: center;
+}
+
+
+
+
+
+.e-bigger .e-aiassist-chat .e-view-header .e-icons.e-close {
+ font-size: 20px !important;
+}
+
+.e-bigger .e-aiassist-chat .e-view-header .e-tbar-btn-text,
+.e-aiassist-chat .e-view-header .e-icons.e-close,
+.e-aiassist-chat .e-aiassist-page-icon::before,
+.e-bigger .e-aiassist-chat .e-suggestions li {
+ font-size: 16px !important;
+}
+
+.e-aiassist-chat .e-view-header .e-tbar-btn-text,
+.e-aiassist-chat .e-suggestions li,
+.e-bigger .e-aiassist-chat .e-aiassist-page-header {
+ font-size: 14px !important;
+}
+
+.e-bigger .e-aiassist-chat .e-aiassist-page-icon::before {
+ height: 22px;
+ width: 22px;
+ font-size: 22px;
+}
+
+.e-bigger .e-aiassist-chat .e-aiassist-page-icon {
+ width: 56px;
+ height: 56px;
+}
+
+.e-bigger .e-aiassist-chat .e-aiassist-page-header {
+ line-height: 20px;
+}
+
+.e-bigger .e-aiassist-chat .e-suggestions li {
+ height: 40px;
+ line-height: 20px !important;
+ padding: 9px 16px !important;
+}
+
+.e-control.e-dialog {
+ max-height: 95%;
+}
+
+.e-ribbon-menu.e-menu-wrapper ul .e-menu-item,
+#documenteditor-share-popup ul .e-item,
+.e-smart-editor-dialog .e-original-word p {
+ font-size: inherit !important;
+}
\ No newline at end of file
diff --git a/src/App.jsx b/src/App.jsx
new file mode 100644
index 0000000..68788b3
--- /dev/null
+++ b/src/App.jsx
@@ -0,0 +1,401 @@
+import React, { useRef, useEffect, useState, useCallback } from 'react';
+import { DocumentEditorContainerComponent, Ribbon } from '@syncfusion/ej2-react-documenteditor';
+import { TitleBar } from './title-bar.jsx';
+import { UploaderComponent } from '@syncfusion/ej2-react-inputs';
+import { ButtonComponent } from '@syncfusion/ej2-react-buttons';
+import { AIAssistViewComponent, ViewsDirective, ViewDirective } from '@syncfusion/ej2-react-interactive-chat';
+import { getDocumentText } from './summarizer';
+import AIPopup from './AIPopup.jsx';
+import './editor-helpers.js';
+import './App.css';
+import { L10n } from "@syncfusion/ej2-base";
+import { getAzureChatAIRequest } from './ai-models';
+
+DocumentEditorContainerComponent.Inject(Ribbon);
+
+L10n.load({
+ 'en-US': {
+ uploader: {
+ dropFilesHint: "or drop file here"
+ }
+ }
+});
+
+const SERVICE_URL = 'https://document.syncfusion.com/web-services/docx-editor/api/documenteditor/';
+const UPLOADER_SAVE_URL = 'https://services.syncfusion.com/react/production/api/FileUploader/Save';
+const UPLOADER_REMOVE_URL = 'https://services.syncfusion.com/react/production/api/FileUploader/Remove';
+
+export const App = () => {
+ let container = useRef(null);
+ const uploaderRef = useRef(null);
+ let titleBar;
+ let documentName = "New Document";
+ const assistInstance = useRef(null);
+ const [initialized, setInitialized] = useState(false);
+ const [openChat, setOpenChat] = useState(false);
+ const [isAIEnabled, setIsAIEnabled] = useState(false);
+ const [assistBtnPos, setAssistBtnPos] = useState({
+ left: 80,
+ top: 160,
+ width: 24,
+ height: 24
+ });
+ const [aiSuggestions, setAiSuggestions] = useState(['Summarize this document']);
+
+ const onZoomFactorChange = useCallback(() => {
+ const editor = container.current?.documentEditor;
+ if (!editor) return;
+ setTimeout(() => {
+ editor.focusIn();
+ const zoom = editor.zoomFactor;
+ const pos = window.getAIAssistBtnPosition?.();
+ if (pos) {
+ setAssistBtnPos({
+ left: Math.round(pos.x),
+ top: Math.round(pos.y),
+ width: Math.round(24 * zoom),
+ height: Math.round(24 * zoom)
+ });
+ }
+ window.setAiAssistBtnIconSize?.(Math.round(14 * zoom));
+ }, 10);
+ }, []);
+
+ useEffect(() => {
+ container.current?.documentEditor?.resize?.();
+ container.current?.ribbon?.ribbon?.refreshLayout?.();
+ onZoomFactorChange();
+ }, [openChat, onZoomFactorChange]);
+
+ useEffect(() => {
+ if (!isAIEnabled) {
+ setOpenChat(false);
+ }
+ }, [isAIEnabled]);
+
+ const onLoadDefault = () => {
+ const editor = container.current?.documentEditor;
+ if (editor) {
+ editor.documentName = documentName;
+ if (titleBar) {
+ titleBar.updateDocumentTitle();
+ }
+ container.current.documentChange = () => {
+ if (titleBar) {
+ titleBar.updateDocumentTitle();
+ }
+ editor.focusIn();
+ };
+ }
+ };
+
+ const openNewDocument = useCallback(() => {
+ setInitialized(true);
+ documentName = "New Document";
+ }, []);
+
+ const onContainerCreated = useCallback(() => {
+ const editor = container.current?.documentEditor;
+ if (!editor) return;
+ try { editor.focusIn(); } catch { }
+ setTimeout(() => {
+ try {
+ const pos = window?.getAIAssistBtnPosition?.();
+ if (!pos) return;
+ setAssistBtnPos(prev => ({
+ ...prev,
+ left: Math.round(pos.x),
+ top: Math.round(pos.y),
+ width: 24,
+ height: 24
+ }));
+ } catch { }
+ }, 10);
+ window.onbeforeunload = function () {
+ return "Want to save your changes?";
+ };
+ editor.zoomFactorChange = () => {
+ onZoomFactorChange();
+ };
+ editor.pageOutline = "#E0E0E0";
+ editor.acceptTab = true;
+ container.current.documentEditorSettings.showRuler = true;
+ editor.resize();
+ titleBar = new TitleBar(
+ document.getElementById("documenteditor_titlebar"),
+ editor,
+ true,
+ null,
+ goBackToUploadPage,
+ (checked) => {
+ setIsAIEnabled(checked);
+ }
+ );
+ onLoadDefault();
+ let spellChecker = editor.spellChecker;
+ spellChecker.languageID = 1033;
+ spellChecker.removeUnderline = false;
+ spellChecker.allowSpellCheckAndSuggestion = true;
+ window.addEventListener('resize', onResize);
+ }, []);
+
+ useEffect(() => {
+ return () => window.removeEventListener('resize', onResize);
+ }, []);
+
+ const onResize = () => {
+ onZoomFactorChange();
+ }
+
+ const goBackToUploadPage = useCallback(() => {
+ const editor = container.current?.documentEditor;
+ try {
+ if (editor) {
+ editor.showRevisions = false;
+ editor.destroy();
+ setIsAIEnabled(false);
+ }
+ } catch { }
+ setInitialized(false);
+ }, []);
+
+ const onFileSelected = useCallback(async (args) => {
+ const files = args.filesData || [];
+ if (!files.length) return;
+ const file = files[0]?.rawFile;
+ if (!file) return;
+ setInitialized(true);
+ documentName = files[0].name ? files[0].name.split("." + files[0].type)[0] : 'Untitled Document';
+ try {
+ const formData = new FormData();
+ formData.append('files', file, file.name);
+ const response = await fetch(`${SERVICE_URL}Import`, {
+ method: 'POST',
+ body: formData
+ });
+ if (!response.ok) {
+ throw new Error(`Import failed: ${response.statusText}`);
+ }
+ const sfdt = await response.text();
+ setTimeout(() => {
+ const editor = container.current?.documentEditor;
+ if (editor) {
+ editor.open(sfdt);
+ }
+ }, 0);
+ } catch (err) {
+ alert('Failed to import and open the document.');
+ }
+ }, []);
+
+ const onUploaderCreated = () => {
+ const browseBtn = document.querySelector('.e-file-select-wrap .e-btn');
+ if (browseBtn && !browseBtn.querySelector('.e-btn-icon')) {
+ const icon = document.createElement('span');
+ icon.className = 'e-btn-icon e-icons e-fe-upload';
+ browseBtn.insertBefore(icon, browseBtn.firstChild);
+ }
+ };
+
+ const showChatPane = () => {
+ setOpenChat(true);
+ setAiSuggestions(['Summarize this document']);
+ }
+
+ const chatPanelCreated = () => {
+ assistInstance.current.toolbarSettings.itemClicked = (args) => {
+ const itemClass = String(args?.item?.iconCss || '');
+ if (itemClass.includes('e-close')) {
+ setOpenChat(false);
+ document.querySelector('.e-fab.ai-assist-btn')?.classList.remove('e-hide');
+ document.querySelector('.document-editor-container')?.classList.remove('e-hide');
+ }
+ };
+ setAiSuggestions(['Summarize this document']);
+ }
+
+ return (
+
+
+
+ {!initialized ? (
+
+
+
+
+
+
+ Create New Document
+
+
+
+ ) : (
+
+
+
+
+ {openChat && (
+
+
+
+
+
How can I help you?
+
+ }
+ promptRequest={async (args) => {
+ const prompt = String(args?.prompt || '').trim();
+ if (!prompt) { args.response = ''; return; }
+ try {
+ if (prompt === 'Summarize this document') {
+ await new Promise(resolve => {
+ requestAnimationFrame(() => setTimeout(resolve, 10));
+ });
+ const documentContent = await getDocumentText(container);
+ const options = {
+ messages: [
+ { role: "system", content: `You are a helpful assistant. Your task is to analyze the provided text and generate short summary. Always respond in proper HTML format, but do not include , , or tags.` },
+ { role: "user", content: documentContent }
+ ],
+ model: "gpt-4",
+ };
+ const summaryHtml = await getAzureChatAIRequest(options);
+ args.response = summaryHtml || 'No summary available.
';
+ assistInstance.current.addPromptResponse(args.response);
+ const suggestionsRaw = await getAzureChatAIRequest({
+ messages: [
+ { role: "system", content: `You are a helpful assistant. Your task is to analyze the provided text and generate 3 short diverse questions and each question should not exceed 10 words` },
+ { role: "user", content: documentContent }
+ ],
+ model: "gpt-4",
+ });
+ if (suggestionsRaw) {
+ const next = (suggestionsRaw.split(/\d+\.\s*/).filter(x => x.trim() !== "")).map((text, index) => {
+ return `${index + 1}. ${text.trim()}`;
+ });
+ if (next.length) setAiSuggestions(next);
+ }
+ } else {
+
+ await new Promise(resolve => {
+ requestAnimationFrame(() => setTimeout(resolve, 10));
+ });
+
+ const options = {
+ messages: [
+ { role: "system", content: `You are a helpful assistant. Use the provided context to answer the user question. Always respond in proper HTML format, but do not include , , or tags. Context:` },
+ { role: "user", content: prompt }
+ ],
+ model: "gpt-4",
+ };
+ const answerHtml = await getAzureChatAIRequest(options);
+ var response = String(answerHtml ?? 'No answer.
');
+ args.response = response;
+ assistInstance.current.addPromptResponse(response);
+ }
+ } catch (e) {
+ args.response = `AI error: ${e?.message || e}
`;
+ }
+ }}
+ responseToolbarSettings={{
+ items: [
+ { iconCss: 'e-icons e-copy', tooltip: 'copy' },
+ { iconCss: 'e-btn-icon e-de-ctnr-new', tooltip: 'insert' }
+ ],
+ itemClicked: async (e) => {
+ const idx = typeof e?.dataIndex === 'number'
+ ? e.dataIndex
+ : (assistInstance.current?.prompts?.length ?? 1) - 1;
+ const resHtml = assistInstance.current?.prompts?.[idx]?.response ?? '';
+ if (!resHtml) return;
+ const tmp = document.createElement('div');
+ tmp.innerHTML = resHtml;
+ const plainText = (tmp.innerText || '').trim();
+ const tip = (e?.item?.tooltip || '').toLowerCase();
+ if (tip === 'copy') {
+ if (navigator.clipboard && window.ClipboardItem) {
+ const blobHtml = new Blob([resHtml], { type: 'text/html' });
+ const blobText = new Blob([plainText], { type: 'text/plain' });
+ await navigator.clipboard.write([
+ new ClipboardItem({
+ 'text/html': blobHtml,
+ 'text/plain': blobText
+ })
+ ]);
+ } else {
+ await navigator.clipboard?.writeText(plainText);
+ }
+ } else if (tip === 'insert') {
+ const editor = container?.current?.documentEditor?.editor;
+ if (editor && plainText) {
+ editor.insertText(plainText);
+ }
+ }
+ }
+ }}
+ >
+
+
+
+
+
+ )}
+
+
+ )}
+
+
+
+ );
+}
+
+export default App;
\ No newline at end of file
diff --git a/src/ai-models.ts b/src/ai-models.ts
new file mode 100644
index 0000000..9c26d98
--- /dev/null
+++ b/src/ai-models.ts
@@ -0,0 +1,38 @@
+import { generateText } from "ai"
+import { createGoogleGenerativeAI } from '@ai-sdk/google';
+import { createAzure } from '@ai-sdk/azure';
+import { createOpenAI } from '@ai-sdk/openai';
+
+const google = createGoogleGenerativeAI({
+ baseURL: "https://generativelanguage.googleapis.com/v1beta",
+ apiKey: "API_KEY"
+});
+const azure = createAzure({
+ resourceName: 'RESOURCE_NAME',
+ apiKey: 'API_KEY',
+});
+const groq = createOpenAI({
+ baseURL: 'https://api.groq.com/openai/v1',
+ apiKey: 'API_KEY',
+});
+
+const aiModel = azure('MODEL_NAME'); // Update the model here
+
+export async function getAzureChatAIRequest(options: any) {
+ try {
+ const result = await generateText({
+ model: aiModel,
+ messages: options.messages,
+ topP: options.topP,
+ temperature: options.temperature,
+ maxOutputTokens: options.maxTokens,
+ frequencyPenalty: options.frequencyPenalty,
+ presencePenalty: options.presencePenalty,
+ stopSequences: options.stopSequences
+ });
+ return result.text;
+ } catch (err) {
+ console.error("Error occurred:", err);
+ return null;
+ }
+}
diff --git a/src/editor-helpers.js b/src/editor-helpers.js
new file mode 100644
index 0000000..45dfb78
--- /dev/null
+++ b/src/editor-helpers.js
@@ -0,0 +1,207 @@
+window.getAIAssistBtnPosition = function () {
+ var documnetEditor = document.querySelector(".control-section");
+ if (!documnetEditor) {
+ return;
+ }
+ var documnetEditorRect = documnetEditor.getBoundingClientRect();
+ var documnetEditorTop = documnetEditorRect.top;
+ var documnetEditorLeft = documnetEditorRect.left;
+ var viewerContainer = document.querySelector("#document-editor_editor_viewerContainer");
+ if (!viewerContainer) {
+ return;
+ }
+ var viewerContainerRect = viewerContainer.getBoundingClientRect();
+ var viewerContainerTop = viewerContainerRect.top;
+ var viewerContainerLeft = viewerContainerRect.left;
+ var cursor = document.querySelector('.e-de-blink-cursor');
+ if (!cursor) {
+ return;
+ }
+ var cursorRect = cursor.getBoundingClientRect();
+ var cursorTop = cursor.style.display === 'none' ? parseInt(cursor.style.top.split("px")[0], 10) : cursorRect.top;
+ var viewercontainer = document.querySelector("#document-editor_editor_viewerContainer .e-de-hRuler .e-de-hRuler");
+ if (!viewercontainer) {
+ return;
+ }
+ var rulerLeft = parseInt(viewercontainer.style.marginLeft.split("px")[0], 10);
+ var viewercontainerWidth = document.querySelector(".e-de-hRuler .e-de-hRuler").offsetWidth;
+ if (!viewercontainerWidth && viewercontainerWidth !== 0) {
+ return;
+ }
+ var AiButtonPosition = viewercontainerWidth / 20;
+ var markIndicator = document.querySelector("#document-editor_editor_markIndicator");
+ var vRuleIndicator = document.querySelector("#document-editor_editor_vRulerBottom");
+ if (!markIndicator || !vRuleIndicator) {
+ return;
+ }
+ var markIndicatorRect = markIndicator.getBoundingClientRect().top;
+ var vRuleIndicatorRect = vRuleIndicator.getBoundingClientRect().top;
+ var scrollDifference = markIndicatorRect - vRuleIndicatorRect;
+
+ var y = cursor.style.display === 'none' ? ((viewerContainerTop - documnetEditorTop) + cursorTop) - scrollDifference : (cursorTop - documnetEditorTop);
+ var x = (viewerContainerLeft - documnetEditorLeft) + rulerLeft + AiButtonPosition;
+ return { x: x, y: y };
+};
+
+window.getAIChatBtnPosition = function () {
+ var documnetEditor = document.querySelector("#document-editor");
+ if (!documnetEditor) {
+ return;
+ }
+ var documnetEditorRect = documnetEditor.getBoundingClientRect();
+ var documnetEditorHeight = documnetEditorRect.height;
+ var documnetEditorWidth = documnetEditorRect.width;
+ var x = documnetEditorWidth - 87;
+ var y = documnetEditorHeight - 81;
+ return { x: x, y: y };
+};
+
+window.setAiAssistBtnPosition = function (x, y) {
+ var el = document.getElementsByClassName('ai-chat-btn')[0];
+ if (!el) return;
+ el.style.position = 'absolute';
+ el.style.left = x + 'px';
+ el.style.top = y + 'px';
+};
+window.getAIAssistPopupPosition = function () {
+ var aiButton = document.getElementsByClassName('ai-assist-btn')[0];
+ if (!aiButton) return { x: 200, y: 160 };
+ var bRect = aiButton.getBoundingClientRect();
+ var sampleMargin = 8;
+ return { x: bRect.left - sampleMargin, y: (bRect.top + bRect.height) - sampleMargin };
+};
+
+window.setDialogDivHeight = (mode) => {
+ var q = document.getElementById('e-de-qus-pane');
+ var ans = document.getElementById('e-de-editableDiv');
+ if (!ans) return;
+ if (mode === 'Generate') ans.style.height = '100px';
+ else { if (q) q.style.height = '75px'; ans.style.height = '75px'; }
+};
+window.getTextContent = () => {
+ var el = document.getElementById('e-de-editableDiv');
+ return el ? (el.textContent || '').trim() : '';
+};
+window.getInputContent = () => {
+ var el = document.getElementById('e-de-editableDiv');
+ return el ? (el.value || '').trim() : '';
+};
+window.getHtmlContent = () => {
+ var el = document.getElementById('e-de-editableDiv');
+ return el ? el.innerHTML : '';
+};
+window.setTextContent = (text) => {
+ var el = document.getElementById('e-de-editableDiv');
+ if (el) el.textContent = text || '';
+};
+window.setHtmlContent = (html) => {
+ var el = document.getElementById('e-de-editableDiv');
+ if (el) el.innerHTML = html || '';
+};
+window.clearDivContent = () => {
+ var el = document.getElementById('e-de-editableDiv');
+ if (el) el.innerHTML = '';
+};
+window.setPlaceholder = (placeholderText) => {
+ var el = document.getElementById('e-de-editableDiv');
+ if (el && (el.innerText || '').trim() === '') {
+ el.innerText = placeholderText || '';
+ el.classList.add('placeHoldr');
+ }
+};
+window.removePlaceholder = (placeholderText) => {
+ var el = document.getElementById('e-de-editableDiv');
+ if (!el) return;
+ if (el.innerText === placeholderText) {
+ el.innerText = '';
+ el.classList.remove('placeHoldr');
+ }
+};
+
+
+window.getAIButtonPosition = function () {
+ var aiButton = document.getElementsByClassName('e-control e-btn ai-assist-btn e-fab')[0];
+ if (!aiButton) {
+ return;
+ }
+ var aiButtonRect = aiButton.getBoundingClientRect();
+ var x = aiButtonRect.left;
+ var y = aiButtonRect.top;
+ return { x: x, y: y };
+}
+
+window.toggleSendIcon = function (isEnabled) {
+ const sendElement = document.querySelector(".ai-assist-dialog .e-icons.e-send");
+ if (sendElement) {
+ if (isEnabled) {
+ sendElement.classList.remove('e-disabled');
+ } else {
+ sendElement.classList.add('e-disabled');
+ }
+ }
+};
+
+
+window.getGeneratingDraftPosition = function () {
+ var aiButton = document.getElementsByClassName('e-control ai-assist-btn e-fab')[0];
+ if (!aiButton) {
+ return;
+ }
+ var aiButtonRect = aiButton.getBoundingClientRect();
+ var aiButtonLeft = aiButtonRect.left;
+ var aiButtonTop = aiButtonRect.top;
+ var documnetEditor = document.querySelector(".control-section");
+ if (!documnetEditor) {
+ return;
+ }
+ var documnetEditorRect = documnetEditor.getBoundingClientRect();
+ var documnetEditorTop = documnetEditorRect.top;
+ var sampleMargin = 8;
+ var x = aiButtonLeft - sampleMargin;
+ var y = aiButtonTop - documnetEditorTop;
+ return { x: x, y: y };
+}
+
+window.setGeneratingDraftPosition = function (x, y) {
+ var element = document.getElementsByClassName('e-stop-generating-dialog')[0];
+ if (element) {
+ element.style.position = 'absolute';
+ element.style.left = x + 'px';
+ element.style.top = y + 'px';
+ }
+};
+window.showGeneratingDraft = function (isShow) {
+ var stopPopupElement = document.querySelector('.e-stop-generating-dialog');
+ if (stopPopupElement) {
+ if (isShow) {
+ stopPopupElement.style.display = "block";
+ }
+ else {
+ stopPopupElement.style.display = "none";
+ }
+ }
+}
+
+window.setAIAssistBtnIconSize = function (AIAssistBtnIconSize) {
+ var iconElement = document.querySelector(".ai-assist-btn .e-icons.e-ai-assist-btn");
+ if (iconElement) {
+ iconElement.style.fontSize = AIAssistBtnIconSize + "px";
+ iconElement.style.height = AIAssistBtnIconSize + "px";
+ iconElement.style.width = AIAssistBtnIconSize + "px";
+ iconElement.style.lineHeight = (AIAssistBtnIconSize + 1) + "px";
+ }
+}
+
+window.getRegeneratePopupPosition = function () {
+ var statusBar = document.querySelector(".e-de-status-bar");
+ if (!statusBar) {
+ return;
+ }
+ var statusBarRect = statusBar.getBoundingClientRect();
+ var statusBarTop = statusBarRect.top;
+ var regeneratePopupHeight = 175;
+ var sampleMargin = 8;
+ var x = 130;
+ var y = (statusBarTop - regeneratePopupHeight) - sampleMargin;
+ return { x: x, y: y };
+}
\ No newline at end of file
diff --git a/src/helpers.js b/src/helpers.js
new file mode 100644
index 0000000..5275489
--- /dev/null
+++ b/src/helpers.js
@@ -0,0 +1,86 @@
+export const AiTask = {
+ Generate: 'Generate',
+ Rephrase: 'Rephrase',
+ Translate: 'Translate',
+ Grammar: 'Grammar',
+};
+
+export function buildPrompt(task, text, {
+ tone = 'Professional',
+ format = 'Paragraph',
+ length = 'Medium',
+ fromLang = 'English',
+ toLang = 'French',
+ checks = [],
+ userHint = '',
+} = {}) {
+ const safeText = text || '';
+ switch (task) {
+ case AiTask.Generate:
+ return `generate ${format.toLowerCase()} in ${tone.toLowerCase()} tone, length ${length.toLowerCase()}:
+${safeText}
+
+Return only an HTML fragment (use ,
,