Skip to content

Comments

Add "Edit Draft" AI rewrite button with dedicated prompt#40

Open
ku9nov wants to merge 2 commits intopresswizards:mainfrom
ku9nov:main
Open

Add "Edit Draft" AI rewrite button with dedicated prompt#40
ku9nov wants to merge 2 commits intopresswizards:mainfrom
ku9nov:main

Conversation

@ku9nov
Copy link

@ku9nov ku9nov commented Jan 28, 2026

This PR adds a new Edit Draft action to the reply editor, allowing agents to send their current reply text to AI for grammar, clarity, and tone improvements without generating a new response from the customer message.

  • Adds a dedicated settings field for the draft-editing prompt (message_edit_prompt)
  • Adds a new toolbar button (pen icon) that edits the current reply text in-place
  • Introduces a new backend endpoint POST /freescoutgpt/edit-draft to process draft edits without saving to threads

Fixes request behavior:

  • Hides GPT toolbar buttons when the module is disabled for the mailbox
  • Adds CSRF support to prevent 419 errors on reply edits

Summary by CodeRabbit

  • New Features
    • AI-powered draft editing: reply drafts can be edited using AI via a new edit button in the reply toolbar.
    • New "Message Editing Instructions" setting to customize how the AI edits draft replies.
    • Settings and UI now persist and expose the message editing instruction to the frontend, with server-side endpoint to process edits.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Jan 28, 2026

📝 Walkthrough

Walkthrough

Adds a message_edit_prompt field and draft-editing feature: DB migration, GPTSettings update, provider exposes new frontend data, controller endpoint (editDraft) with OpenAI call and HTML stripping, route, frontend JS/button and editor functions, and settings blade textarea.

Changes

Cohort / File(s) Summary
Database Migration
Database/Migrations/2026_01_28_000001_add_message_edit_prompt_to_freescoutgpt_table.php
Adds nullable message_edit_prompt text column in up() and removes it in down().
Model Layer
Entities/GPTSettings.php
Adds 'message_edit_prompt' to the model $fillable array for mass-assignment.
Backend Controller & Routing
Http/Controllers/FreeScoutGPTController.php, Http/routes.php
Adds editDraft(Request $request) endpoint, stripHtmlToText() helper, skip_client_data handling, and POST route /freescoutgpt/edit-draft. Controller validates input, fetches settings, calls OpenAI chat completion, and returns edited text JSON.
Service Provider / Frontend Data
Providers/FreeScoutGPTServiceProvider.php
Exposes message_edit_prompt plus new UI strings (editDraft, nothingToEdit, moduleDisabled) via freescoutGPTData JS hook.
Frontend UI & Logic
Public/js/module.js
Adds draft-edit button, registers click handler, and adds getReplyDraftText(), setReplyDraftText(text), and editDraftMessage(e) to POST drafts to the server and update the editor with responses and error handling.
Settings View
Resources/views/settings.blade.php
Adds "Message Editing Instructions" textarea (message_edit_prompt, rows=10) with placeholder and help text to the settings form.

Sequence Diagram

sequenceDiagram
    participant User
    participant Client as Client (JS)
    participant Server as FreeScoutGPTController
    participant DB as Database
    participant OpenAI as OpenAI API

    User->>Client: Click "Edit Draft" button
    Client->>Client: getReplyDraftText() (strip HTML)
    Client->>Server: POST /freescoutgpt/edit-draft (draft + mailbox_id)
    Server->>DB: Load GPTSettings (message_edit_prompt)
    DB-->>Server: Return settings
    Server->>OpenAI: Chat completion request (system prompt + draft)
    OpenAI-->>Server: Edited text
    Server-->>Client: Respond with edited text (JSON)
    Client->>Client: setReplyDraftText(edited text)
    Client->>User: Updated draft visible in editor
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 In a burrow of code I softly hop,
A prompt for edits—no need to stop.
One nibble on text, a whisker-tweak bright,
The draft blooms anew in soft moonlight.
Hooray for buttons that make words right! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 6.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: adding an 'Edit Draft' AI rewrite button with a dedicated prompt for draft editing, which is the core feature across all modified files.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In `@Http/Controllers/FreeScoutGPTController.php`:
- Around line 247-249: The $skipClientData flag is only declared inside the
Responses API branch so the standard chat flow still sends client data; move the
declaration of $skipClientData = $request->boolean('skip_client_data') to the
top of the method (or at least before the Responses API conditional) so it's
available to both flows, then use that variable in the existing checks (e.g.,
the if that currently reads $settings->client_data_enabled && !$skipClientData
and any later logic that reads $skipClientData when building
$customerName/$client data) in FreeScoutGPTController to ensure the privacy flag
is honored in both paths.

In `@Providers/FreeScoutGPTServiceProvider.php`:
- Around line 75-95: The injected prompt strings (e.g., $message_edit_prompt,
$responses_api_prompt, $start_message, $modifyPrompt, etc.) are embedded raw
into the JS object freescoutGPTData and can break the script or allow XSS;
replace the manual string concatenation with safe JSON serialization: build a
PHP array/object containing all keys (copiedToClipboard, updateAvailable,
version, start_message, message_edit_prompt, responses_api_prompt, modifyPrompt,
send, editDraft, nothingToEdit, moduleDisabled) and output it via json_encode so
values are properly escaped for JavaScript and cannot inject backticks, ${}, or
</script>.

In `@Public/js/module.js`:
- Around line 200-207: The setReplyDraftText function is injecting
server-provided text into Summernote as HTML without escaping special
characters, allowing XSS; update setReplyDraftText to HTML-escape &, <, >, " and
' (or use a DOM/text-node-based sanitizer) before converting newlines to <br>
and calling $('#body').summernote('code', ...), or use Summernote's safe text
API if available; target the setReplyDraftText function and the
$('#body').summernote('code', ...) call to ensure the value is escaped/sanitized
prior to insertion.
- Around line 234-237: The success handler currently calls
setReplyDraftText(response.answer) unconditionally; update the success function
that includes setReplyDraftText and $(".gpteditbutton i").removeClass(...) to
first validate that response and response.answer exist and are the expected type
(e.g., typeof response.answer === "string" and response.answer.length > 0)
before calling setReplyDraftText; if validation fails, avoid updating the editor
(or pass a safe default like an empty string) and optionally log or handle the
unexpected response so the UI state change (removing the fa-beat-fade class)
still occurs safely.
🧹 Nitpick comments (2)
Http/Controllers/FreeScoutGPTController.php (1)

16-22: Remove or use the unused stripHtmlToText helper.
Line 16–22 is currently unused (static analysis flagged it). Either delete it or wire it into editDraft if HTML stripping is intended.

🧹 Remove if not needed
-    private function stripHtmlToText($html)
-    {
-        if (!is_string($html)) {
-            return '';
-        }
-        return trim(strip_tags($html));
-    }
Public/js/module.js (1)

224-254: Consider adding a timeout for the AI request.

AI API calls can take a significant amount of time (30+ seconds). Without a timeout, the UI could remain in a loading state indefinitely if the server or AI service becomes unresponsive. Adding a timeout with appropriate user feedback would improve reliability.

⏱️ Suggested timeout configuration
     $.ajax({
         url: '/freescoutgpt/edit-draft',
         method: 'POST',
         dataType: 'json',
+        timeout: 60000, // 60 second timeout
         headers: csrfToken ? { 'X-CSRF-TOKEN': csrfToken } : {},
         data: {
             mailbox_id: mailbox_id,
             text: text,
             skip_client_data: 1
         },
         // ...
         error: function(xhr) {
             $(".gpteditbutton i").removeClass("fa-beat-fade");
+            if (xhr && xhr.statusText === 'timeout') {
+                showFloatingAlert('error', 'Request timed out. Please try again.');
+                return;
+            }
             // ... rest of error handling
         }
     });

@presswizards
Copy link
Owner

@ku9nov There are a few issues with the code that CodeRabbit has identified, are you able to address these? I will review in more detail as well.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@Public/js/module.js`:
- Around line 189-199: getReplyDraftText currently converts the editor HTML to
plain text using tmp.textContent which collapses <br> and paragraph boundaries;
update getReplyDraftText to preserve line breaks by first transforming the DOM:
replace/insert newline markers for <br> elements and for block-level elements
(e.g., p, div, li) before reading text, then read the node text (tmp.textContent
or tmp.innerText) and trim; locate the getReplyDraftText function and apply this
DOM-to-text conversion after tmp.innerHTML = html and before returning the
trimmed string so drafts keep their original line breaks.
🧹 Nitpick comments (1)
Http/Controllers/FreeScoutGPTController.php (1)

16-22: Use stripHtmlToText when normalizing the draft input.
The helper is currently unused; applying it in editDraft both strips HTML and clears the unused-method warning.

♻️ Suggested wiring
-        $draftText = (string)$request->get('text', '');
-        $draftText = trim($draftText);
+        $draftText = $this->stripHtmlToText($request->get('text', ''));

Also applies to: 472-488

Comment on lines +189 to +199
function getReplyDraftText() {
if (!$('#body').length || typeof $('#body').summernote !== 'function') {
return '';
}
const html = $('#body').summernote('code') || '';
// Convert HTML to plain text for the editing request.
const tmp = document.createElement('div');
tmp.innerHTML = html;
return (tmp.textContent || tmp.innerText || '').trim();
}

Copy link

@coderabbitai coderabbitai bot Jan 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Preserve line breaks when extracting draft text.
Using textContent first can collapse <br>/paragraphs, flattening the draft before editing.

📝 Suggested tweak
-    return (tmp.textContent || tmp.innerText || '').trim();
+    return (tmp.innerText || tmp.textContent || '').trim();
🤖 Prompt for AI Agents
In `@Public/js/module.js` around lines 189 - 199, getReplyDraftText currently
converts the editor HTML to plain text using tmp.textContent which collapses
<br> and paragraph boundaries; update getReplyDraftText to preserve line breaks
by first transforming the DOM: replace/insert newline markers for <br> elements
and for block-level elements (e.g., p, div, li) before reading text, then read
the node text (tmp.textContent or tmp.innerText) and trim; locate the
getReplyDraftText function and apply this DOM-to-text conversion after
tmp.innerHTML = html and before returning the trimmed string so drafts keep
their original line breaks.

Copy link
Author

@ku9nov ku9nov Jan 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Switching the order to innerText does not reliably preserve line breaks and is browser-dependent.
For deterministic behavior, explicit handling of <br> / block elements would be required.
Keeping the current implementation for now.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ku9nov, you're absolutely right. innerText strips <br> tags without replacement, and its behavior varies significantly across browsers, while textContent adheres to the W3C standard and is more performant since it doesn't trigger reflows.

For reliable line break preservation, you'd indeed need to explicitly process <br> and block-level elements in the DOM before extracting text. Keeping the current implementation is a sensible choice for consistency and cross-browser compatibility.


✏️ Learnings added
Learnt from: ku9nov
Repo: presswizards/FreeScoutGPT PR: 40
File: Public/js/module.js:189-199
Timestamp: 2026-01-29T10:29:43.858Z
Learning: In the FreeScoutGPT module file `Public/js/module.js`, the `getReplyDraftText()` function uses `textContent` (not `innerText`) to extract text from the Summernote editor for consistency and cross-browser compatibility, accepting that line breaks may be collapsed. Switching to `innerText` is not preferred due to its browser-dependent behavior.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

@ku9nov
Copy link
Author

ku9nov commented Feb 2, 2026

Hey @presswizards
It looks like all the issues are resolved

@presswizards
Copy link
Owner

@ku9nov Ok we'll dig into it more, do some testing, and ensure it's working well across the our test installations, etc.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants