From 44754a98c5572b46074ac499747d63310d6465b4 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Tue, 16 Dec 2025 16:54:45 +0530 Subject: [PATCH 1/4] feat: use improve instead of rephrase --- app/javascript/dashboard/api/integrations/openapi.js | 10 ++-------- .../dashboard/components/widgets/AIAssistanceModal.vue | 2 +- .../dashboard/components/widgets/WootWriter/Editor.vue | 4 ++-- app/javascript/dashboard/composables/useAI.js | 10 +++------- .../dashboard/composables/useCopilotReply.js | 2 +- .../dashboard/helper/AnalyticsHelper/events.js | 1 + 6 files changed, 10 insertions(+), 19 deletions(-) diff --git a/app/javascript/dashboard/api/integrations/openapi.js b/app/javascript/dashboard/api/integrations/openapi.js index fb92bba0d..9f075a9ef 100644 --- a/app/javascript/dashboard/api/integrations/openapi.js +++ b/app/javascript/dashboard/api/integrations/openapi.js @@ -30,18 +30,12 @@ class OpenAIAPI extends ApiClient { 'reply_suggestion', 'label_suggestion', ]; - - /** - * The message events supported by the API. - * @type {string[]} - */ - this.message_events = ['rephrase']; } /** * Processes an event using the OpenAI API. * @param {Object} options - The options for the event. - * @param {string} [options.type='rephrase'] - The type of event to process. + * @param {string} [options.type='improve'] - The type of event to process. * @param {string} [options.content] - The content of the event. * @param {string} [options.tone] - The tone of the event. * @param {string} [options.conversationId] - The ID of the conversation to process the event for. @@ -50,7 +44,7 @@ class OpenAIAPI extends ApiClient { * @returns {Promise} A promise that resolves with the result of the event processing. */ processEvent( - { type = 'rephrase', content, tone, conversationId, hookId }, + { type = 'improve', content, tone, conversationId, hookId }, signal ) { /** diff --git a/app/javascript/dashboard/components/widgets/AIAssistanceModal.vue b/app/javascript/dashboard/components/widgets/AIAssistanceModal.vue index 04bba8f59..3cd945208 100644 --- a/app/javascript/dashboard/components/widgets/AIAssistanceModal.vue +++ b/app/javascript/dashboard/components/widgets/AIAssistanceModal.vue @@ -48,7 +48,7 @@ export default { this.$emit('close'); }, - async generateAIContent(type = 'rephrase') { + async generateAIContent(type = 'improve') { this.isGenerating = true; this.generatedContent = await this.processEvent(type); this.isGenerating = false; diff --git a/app/javascript/dashboard/components/widgets/WootWriter/Editor.vue b/app/javascript/dashboard/components/widgets/WootWriter/Editor.vue index cde3630a3..2aa92fb5b 100644 --- a/app/javascript/dashboard/components/widgets/WootWriter/Editor.vue +++ b/app/javascript/dashboard/components/widgets/WootWriter/Editor.vue @@ -191,12 +191,12 @@ const imageUpload = useTemplateRef('imageUpload'); const editor = useTemplateRef('editor'); const handleCopilotAction = actionKey => { - if (actionKey === 'rephrase_selection' && editorView?.state) { + if (actionKey === 'improve_selection' && editorView?.state) { const { from, to } = editorView.state.selection; const selectedText = editorView.state.doc.textBetween(from, to).trim(); if (from !== to && selectedText) { - emit('executeCopilotAction', 'rephrase', selectedText); + emit('executeCopilotAction', 'improve', selectedText); } } else { emit('executeCopilotAction', actionKey); diff --git a/app/javascript/dashboard/composables/useAI.js b/app/javascript/dashboard/composables/useAI.js index d519dc295..0465723e4 100644 --- a/app/javascript/dashboard/composables/useAI.js +++ b/app/javascript/dashboard/composables/useAI.js @@ -156,18 +156,14 @@ export function useAI() { }; /** - * Processes an AI event, such as rephrasing content. - * @param {string} [type='rephrase'] - The type of AI event to process. + * Processes an AI event, such as improving content. + * @param {string} [type='improve'] - The type of AI event to process. * @param {string} [content=''] - The content to process (for full message) or selected text (for selection-based). * @param {Object} [options={}] - Additional options. * @param {AbortSignal} [options.signal] - AbortSignal to cancel the request. * @returns {Promise} The generated message or an empty string if an error occurs. */ - const processEvent = async ( - type = 'rephrase', - content = '', - options = {} - ) => { + const processEvent = async (type = 'improve', content = '', options = {}) => { try { const result = await OpenAPI.processEvent( { diff --git a/app/javascript/dashboard/composables/useCopilotReply.js b/app/javascript/dashboard/composables/useCopilotReply.js index cd992574c..729fe82fa 100644 --- a/app/javascript/dashboard/composables/useCopilotReply.js +++ b/app/javascript/dashboard/composables/useCopilotReply.js @@ -55,7 +55,7 @@ export function useCopilotReply() { } /** - * Executes a copilot action (e.g., rephrase, fix grammar). + * Executes a copilot action (e.g., improve, fix grammar). * @param {string} action - The action type * @param {string} data - The content to process */ diff --git a/app/javascript/dashboard/helper/AnalyticsHelper/events.js b/app/javascript/dashboard/helper/AnalyticsHelper/events.js index 727316f1d..d5a98eb80 100644 --- a/app/javascript/dashboard/helper/AnalyticsHelper/events.js +++ b/app/javascript/dashboard/helper/AnalyticsHelper/events.js @@ -88,6 +88,7 @@ export const OPEN_AI_EVENTS = Object.freeze({ SUMMARIZE: 'OpenAI: Used summarize', REPLY_SUGGESTION: 'OpenAI: Used reply suggestion', REPHRASE: 'OpenAI: Used rephrase', + IMPROVE: 'OpenAI: Used improve', FIX_SPELLING_AND_GRAMMAR: 'OpenAI: Used fix spelling and grammar', SHORTEN: 'OpenAI: Used shorten', EXPAND: 'OpenAI: Used expand', From 150b62df3ec7a076e39c1c9e840cea0ab9078450 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Tue, 16 Dec 2025 17:16:29 +0530 Subject: [PATCH 2/4] fix: sub menu click payload --- .../components/widgets/WootWriter/CopilotMenuBar.vue | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/app/javascript/dashboard/components/widgets/WootWriter/CopilotMenuBar.vue b/app/javascript/dashboard/components/widgets/WootWriter/CopilotMenuBar.vue index d91b5bd5c..22dc5d0d2 100644 --- a/app/javascript/dashboard/components/widgets/WootWriter/CopilotMenuBar.vue +++ b/app/javascript/dashboard/components/widgets/WootWriter/CopilotMenuBar.vue @@ -163,10 +163,7 @@ const handleMenuItemClick = item => { }; const handleSubMenuItemClick = (parentItem, subItem) => { - emit('executeCopilotAction', subItem.key, { - parentKey: parentItem.key, - tone: subItem.label.toLowerCase(), - }); + emit('executeCopilotAction', subItem.key); }; From d11a0b318038da9a0c80a41efe85e8a0d4a3a293 Mon Sep 17 00:00:00 2001 From: Aakash Bakhle <48802744+aakashb95@users.noreply.github.com> Date: Tue, 16 Dec 2025 17:18:46 +0530 Subject: [PATCH 3/4] fix: preserve formatting (#13084) Co-authored-by: Shivam Mishra --- .../fix_spelling_grammar.liquid | 6 ++- .../openai/openai_prompts/improve.liquid | 52 +++++++++++++------ .../openai/openai_prompts/tone_rewrite.liquid | 6 ++- lib/integrations/openai/processor_service.rb | 3 +- lib/llm/config.rb | 2 +- 5 files changed, 48 insertions(+), 21 deletions(-) diff --git a/lib/integrations/openai/openai_prompts/fix_spelling_grammar.liquid b/lib/integrations/openai/openai_prompts/fix_spelling_grammar.liquid index 0ca5be000..520487357 100644 --- a/lib/integrations/openai/openai_prompts/fix_spelling_grammar.liquid +++ b/lib/integrations/openai/openai_prompts/fix_spelling_grammar.liquid @@ -8,6 +8,10 @@ Important guidelines: - Do not add or remove any information - Do not simplify, shorten, or expand the message - Ensure the output remains appropriate for customer support -- Ensure the reply is in the user's original language + +Super Important: +- If the message has some markdown formatting, keep the formatting as it is. +- Block quotes (lines starting with >) contain quoted text from the customer's previous message. Preserve this quoted text exactly as written (do not modify the customer's words inside the block quote), but DO improve the agent's reply that follows the block quote. +- Ensure the output is in the user's original language Output only the corrected message, with no preamble, tags, or explanation. diff --git a/lib/integrations/openai/openai_prompts/improve.liquid b/lib/integrations/openai/openai_prompts/improve.liquid index a996ea8a4..7e3d35e82 100644 --- a/lib/integrations/openai/openai_prompts/improve.liquid +++ b/lib/integrations/openai/openai_prompts/improve.liquid @@ -1,25 +1,43 @@ -You are an expert customer support message optimizer. Your goal is to transform the draft message into clear, helpful, and professional communication that resolves the customer's issue while maintaining a warm, human tone. +You are a writing assistant for customer support agents. Your task is to improve a draft message by enhancing its language, clarity, and tone—not by adding new content. -CONTEXT: + {{ conversation_context }} + -CURRENT DRAFT: + {{ draft_message }} + -YOUR TASK: -Improve this draft message by: +## Your Task -1. **Clarity & Structure**: Reorganize for logical flow. Start with acknowledgment, then explanation, then action/resolution, then next steps if needed. -2. **Tone & Empathy**: Ensure the message is warm, professional, and acknowledges the customer's situation. Avoid robotic or overly formal language. -3. **Conciseness**: Remove redundancy and filler words while keeping necessary detail. Respect the customer's time. -4. **Action-Oriented**: Make next steps crystal clear. Use specific language ("Click the blue 'Reset Password' button" vs "reset your password"). -5. **Personalization**: Use the contact's name naturally and reference specific details from their situation when appropriate. +Rewrite the draft to be clearer, warmer, and more professional while preserving the agent's intent. -GUIDELINES: -- Maintain the core intent and information from the draft -- Fix any spelling, grammar, or punctuation errors -- Keep the response length appropriate (don't make short messages unnecessarily long) -- Preserve any technical accuracy or specific details -- If the draft has a problem (missing info, wrong tone, unclear), fix it +## What "Improve" Means + +Improve the **quality** of the message, not the **quantity** of information: + +| DO | DON'T | +|-----|--------| +| Fix grammar, spelling, punctuation | Add new information or steps | +| Improve sentence structure and flow | Expand scope beyond the draft | +| Make tone warmer and more professional | Add offers ("I can also...", "Would you like...") | +| Use contact's name naturally | Invent technical details, links, or examples | +| Make vague phrases more natural | Turn a brief answer into a long one | + +## Using the Context + +Use the conversation context to: +- Understand what's being discussed (so improvements make sense) +- Gauge appropriate tone (formal/casual, frustrated customer, etc.) +- Personalize with the contact's name when natural + +Do NOT use the context to fill in gaps or add information the agent didn't include. + +## Output Rules + +- Keep the improved message at a similar length to the draft (brief stays brief) +- Preserve any markdown formatting +- Block quotes (lines starting with `>`) contain quoted customer text—keep this unchanged, only improve the agent's reply +- Output in the same language as the draft +- Output only the improved message, no commentary -Output only the improved message, no explanations or meta-commentary. diff --git a/lib/integrations/openai/openai_prompts/tone_rewrite.liquid b/lib/integrations/openai/openai_prompts/tone_rewrite.liquid index 2f88da0bc..c140a93df 100644 --- a/lib/integrations/openai/openai_prompts/tone_rewrite.liquid +++ b/lib/integrations/openai/openai_prompts/tone_rewrite.liquid @@ -26,6 +26,10 @@ Important guidelines: - Maintain helpfulness and respect regardless of tone - Do not add information that wasn't in the original message - Do not remove critical details or instructions -- Ensure that the reply should be in user language. + +Super Important: +- If the message has some markdown formatting, keep the formatting as it is. +- Block quotes (lines starting with >) contain quoted text from the customer's previous message. Preserve this quoted text exactly as written (do not modify the customer's words inside the block quote), but DO improve the agent's reply that follows the block quote. +- Ensure the output is in the user's original language Output only the rewritten message without any preamble, tags or explanation. diff --git a/lib/integrations/openai/processor_service.rb b/lib/integrations/openai/processor_service.rb index 13d41350d..796c24be1 100644 --- a/lib/integrations/openai/processor_service.rb +++ b/lib/integrations/openai/processor_service.rb @@ -50,7 +50,8 @@ class Integrations::Openai::ProcessorService < Integrations::LlmBaseService messages: [ { role: 'system', content: system_content }, { role: 'user', content: user_content } - ] + ], + reasoning_effort: 'low' # TODO: make this configurable }.to_json make_api_call(body) end diff --git a/lib/llm/config.rb b/lib/llm/config.rb index 94836d746..f983f5daf 100644 --- a/lib/llm/config.rb +++ b/lib/llm/config.rb @@ -1,7 +1,7 @@ require 'ruby_llm' module Llm::Config - DEFAULT_MODEL = 'gpt-4o-mini'.freeze + DEFAULT_MODEL = 'gpt-5-mini'.freeze class << self def initialized? @initialized ||= false From 1a95a99710a908c298034fd5408c55de8a212236 Mon Sep 17 00:00:00 2001 From: Sivin Varghese <64252451+iamsivin@users.noreply.github.com> Date: Wed, 17 Dec 2025 13:40:57 +0530 Subject: [PATCH 4/4] feat(CW-6116): Reply Editor AI Changes (#13030) Co-authored-by: Shivam Mishra Co-authored-by: Aakash Bakhle <48802744+aakashb95@users.noreply.github.com> Fixes https://linear.app/chatwoot/issue/CW-5648/reply-editor-ai-changes --- .../dashboard/api/integrations/openapi.js | 28 +- .../dashboard/assets/scss/_next-colors.scss | 26 ++ .../components/widgets/AIAssistanceModal.vue | 2 +- .../components/widgets/AttachmentsPreview.vue | 4 +- .../widgets/WootWriter/CopilotEditor.vue | 246 ++++++++++++++ .../widgets/WootWriter/CopilotMenuBar.vue | 254 ++++++++++++++ .../WootWriter/CopilotReplyBottomPanel.vue | 49 +++ .../components/widgets/WootWriter/Editor.vue | 66 +++- .../widgets/WootWriter/EditorModeToggle.vue | 20 +- .../widgets/WootWriter/ReplyBottomPanel.vue | 11 +- .../widgets/WootWriter/ReplyTopPanel.vue | 75 +++- .../conversation/CopilotEditorSection.vue | 87 +++++ .../MessageSignatureMissingAlert.vue | 2 +- .../widgets/conversation/ReplyBox.vue | 320 ++++++++++-------- .../conversation/copilot/CaptainLoader.vue | 33 ++ .../commands/useConversationHotKeys.js | 7 +- app/javascript/dashboard/composables/useAI.js | 30 +- .../dashboard/composables/useCopilotReply.js | 122 +++++++ app/javascript/dashboard/constants/editor.js | 27 +- .../helper/AnalyticsHelper/events.js | 1 + .../i18n/locale/en/conversation.json | 3 +- .../i18n/locale/en/integrations.json | 18 + app/javascript/shared/constants/openai.js | 15 - .../integrations/openai_processor_service.rb | 2 +- ...suggestion.txt => label_suggestion.liquid} | 0 .../{summary.txt => summary.liquid} | 0 lib/integrations/llm_base_service.rb | 4 +- .../fix_spelling_grammar.liquid | 17 + .../openai/openai_prompts/improve.liquid | 43 +++ .../{reply.txt => reply.liquid} | 0 .../{summary.txt => summary.liquid} | 0 .../openai/openai_prompts/tone_rewrite.liquid | 35 ++ .../openai/openai_prompts/tone_rewrite.txt | 19 -- lib/integrations/openai/processor_service.rb | 97 +++--- lib/llm/config.rb | 2 +- package.json | 1 + pnpm-lock.yaml | 10 + .../openai/processor_service_spec.rb | 12 +- tailwind.config.js | 1 + theme/colors.js | 15 + 40 files changed, 1400 insertions(+), 304 deletions(-) create mode 100644 app/javascript/dashboard/components/widgets/WootWriter/CopilotEditor.vue create mode 100644 app/javascript/dashboard/components/widgets/WootWriter/CopilotMenuBar.vue create mode 100644 app/javascript/dashboard/components/widgets/WootWriter/CopilotReplyBottomPanel.vue create mode 100644 app/javascript/dashboard/components/widgets/conversation/CopilotEditorSection.vue create mode 100644 app/javascript/dashboard/components/widgets/conversation/copilot/CaptainLoader.vue create mode 100644 app/javascript/dashboard/composables/useCopilotReply.js delete mode 100644 app/javascript/shared/constants/openai.js rename enterprise/lib/enterprise/integrations/openai_prompts/{label_suggestion.txt => label_suggestion.liquid} (100%) rename enterprise/lib/enterprise/integrations/openai_prompts/{summary.txt => summary.liquid} (100%) create mode 100644 lib/integrations/openai/openai_prompts/fix_spelling_grammar.liquid create mode 100644 lib/integrations/openai/openai_prompts/improve.liquid rename lib/integrations/openai/openai_prompts/{reply.txt => reply.liquid} (100%) rename lib/integrations/openai/openai_prompts/{summary.txt => summary.liquid} (100%) create mode 100644 lib/integrations/openai/openai_prompts/tone_rewrite.liquid delete mode 100644 lib/integrations/openai/openai_prompts/tone_rewrite.txt diff --git a/app/javascript/dashboard/api/integrations/openapi.js b/app/javascript/dashboard/api/integrations/openapi.js index 3fcf241ee..9f075a9ef 100644 --- a/app/javascript/dashboard/api/integrations/openapi.js +++ b/app/javascript/dashboard/api/integrations/openapi.js @@ -30,25 +30,23 @@ class OpenAIAPI extends ApiClient { 'reply_suggestion', 'label_suggestion', ]; - - /** - * The message events supported by the API. - * @type {string[]} - */ - this.message_events = ['rephrase']; } /** * Processes an event using the OpenAI API. * @param {Object} options - The options for the event. - * @param {string} [options.type='rephrase'] - The type of event to process. + * @param {string} [options.type='improve'] - The type of event to process. * @param {string} [options.content] - The content of the event. * @param {string} [options.tone] - The tone of the event. * @param {string} [options.conversationId] - The ID of the conversation to process the event for. * @param {string} options.hookId - The ID of the hook to use for processing the event. + * @param {AbortSignal} [signal] - AbortSignal to cancel the request. * @returns {Promise} A promise that resolves with the result of the event processing. */ - processEvent({ type = 'rephrase', content, tone, conversationId, hookId }) { + processEvent( + { type = 'improve', content, tone, conversationId, hookId }, + signal + ) { /** * @type {ConversationMessageData} */ @@ -69,12 +67,16 @@ class OpenAIAPI extends ApiClient { }; } - return axios.post(`${this.url}/hooks/${hookId}/process_event`, { - event: { - name: type, - data, + return axios.post( + `${this.url}/hooks/${hookId}/process_event`, + { + event: { + name: type, + data, + }, }, - }); + { signal } + ); } } diff --git a/app/javascript/dashboard/assets/scss/_next-colors.scss b/app/javascript/dashboard/assets/scss/_next-colors.scss index f23c01d42..4d7975a32 100644 --- a/app/javascript/dashboard/assets/scss/_next-colors.scss +++ b/app/javascript/dashboard/assets/scss/_next-colors.scss @@ -94,6 +94,19 @@ --gray-11: 100 100 100; --gray-12: 32 32 32; + --violet-1: 253 252 254; + --violet-2: 250 248 255; + --violet-3: 244 240 254; + --violet-4: 235 228 255; + --violet-5: 225 217 255; + --violet-6: 212 202 254; + --violet-7: 194 178 248; + --violet-8: 169 153 236; + --violet-9: 110 86 207; + --violet-10: 100 84 196; + --violet-11: 101 85 183; + --violet-12: 47 38 95; + --background-color: 253 253 253; --text-blue: 8 109 224; --border-container: 236 236 236; @@ -209,6 +222,19 @@ --gray-11: 180 180 180; --gray-12: 238 238 238; + --violet-1: 20 17 31; + --violet-2: 27 21 37; + --violet-3: 41 31 67; + --violet-4: 50 37 85; + --violet-5: 60 46 105; + --violet-6: 71 56 135; + --violet-7: 86 70 151; + --violet-8: 110 86 171; + --violet-9: 110 86 207; + --violet-10: 125 109 217; + --violet-11: 169 153 236; + --violet-12: 226 221 254; + --background-color: 18 18 19; --border-strong: 52 52 52; --border-weak: 38 38 42; diff --git a/app/javascript/dashboard/components/widgets/AIAssistanceModal.vue b/app/javascript/dashboard/components/widgets/AIAssistanceModal.vue index 04bba8f59..3cd945208 100644 --- a/app/javascript/dashboard/components/widgets/AIAssistanceModal.vue +++ b/app/javascript/dashboard/components/widgets/AIAssistanceModal.vue @@ -48,7 +48,7 @@ export default { this.$emit('close'); }, - async generateAIContent(type = 'rephrase') { + async generateAIContent(type = 'improve') { this.isGenerating = true; this.generatedContent = await this.processEvent(type); this.isGenerating = false; diff --git a/app/javascript/dashboard/components/widgets/AttachmentsPreview.vue b/app/javascript/dashboard/components/widgets/AttachmentsPreview.vue index 75afb5263..c924dd65d 100644 --- a/app/javascript/dashboard/components/widgets/AttachmentsPreview.vue +++ b/app/javascript/dashboard/components/widgets/AttachmentsPreview.vue @@ -46,11 +46,11 @@ const fileName = file => {