| 1 |
/** |
| 2 |
* Appends a message to the chat UI. |
| 3 |
* @param {string} role - The role of the message sender (e.g., 'user', 'assistant'). |
| 4 |
* @param {string} content - The message content to display. |
| 5 |
*/ |
| 6 |
function appendMessageToChat(role, content) { |
| 7 |
var $model_body = jQuery(".wpsc-modal-body"); |
| 8 |
var $action_buttons = jQuery(".wpsc-ai-action-buttons"); |
| 9 |
var messageClass = |
| 10 |
role === "user" ? "wpsc-customer-reply-message" : "wpsc-ai-reply-message"; |
| 11 |
var div = jQuery("<div>") |
| 12 |
.addClass("wpsc-ai-message " + messageClass) |
| 13 |
.html(content); |
| 14 |
|
| 15 |
$model_body.append(div); |
| 16 |
|
| 17 |
if (role === "user") { |
| 18 |
var loaderDiv = jQuery("<div>").addClass("wpsc-loading-ai-response").html(supportcandy.ai_loader_html); |
| 19 |
$model_body.append(loaderDiv); |
| 20 |
$action_buttons.hide(); |
| 21 |
} |
| 22 |
|
| 23 |
if (role === "assistant") { |
| 24 |
if ($action_buttons.length) { |
| 25 |
$action_buttons.appendTo($model_body).show(); |
| 26 |
} |
| 27 |
} |
| 28 |
|
| 29 |
// Smooth scroll to bottom |
| 30 |
$model_body.scrollTop($model_body[0].scrollHeight); |
| 31 |
} |
| 32 |
|
| 33 |
/** |
| 34 |
* Ensures AI conversation history exists and is an array. |
| 35 |
* @returns {Array} |
| 36 |
*/ |
| 37 |
function ensureAIConversation() { |
| 38 |
if (!Array.isArray(window.wpsc_ai_conversation)) { |
| 39 |
window.wpsc_ai_conversation = []; |
| 40 |
} |
| 41 |
return window.wpsc_ai_conversation; |
| 42 |
} |
| 43 |
|
| 44 |
/** |
| 45 |
* Calls the AI assistant and gets an improved reply. |
| 46 |
* @param {HTMLElement} el - The element triggering the request. |
| 47 |
* @param {string|number} ticket_id - The ticket ID. |
| 48 |
* @param {string} nonce - The security nonce. |
| 49 |
*/ |
| 50 |
function wpsc_polish_reply_with_ai(el, ticket_id, nonce) { |
| 51 |
|
| 52 |
window.wpsc_ai_conversation = [ |
| 53 |
{ |
| 54 |
role: "system", |
| 55 |
content: |
| 56 |
"You are a customer support assistant. Improve the given reply to make it clear, professional, concise, and helpful. Preserve the original meaning and intent. Do not add new information or assumptions. Fix grammar, tone, and structure. Use a polite and friendly tone. Keep the response easy to read. Return only the improved reply, without any explanations or extra text.", |
| 57 |
}, |
| 58 |
]; |
| 59 |
|
| 60 |
var is_tinymce = |
| 61 |
typeof tinyMCE != "undefined" && |
| 62 |
tinyMCE.activeEditor && |
| 63 |
!tinyMCE.activeEditor.isHidden(); |
| 64 |
var description = |
| 65 |
is_tinymce && tinymce.get("description") |
| 66 |
? tinyMCE.get("description").getContent() |
| 67 |
: jQuery("#description").val().trim(); |
| 68 |
|
| 69 |
if (!description) { |
| 70 |
alert(supportcandy.translations.empty_desc_warning); |
| 71 |
return; |
| 72 |
} |
| 73 |
|
| 74 |
jQuery |
| 75 |
.post({ |
| 76 |
url: supportcandy.ajax_url, |
| 77 |
data: { |
| 78 |
action: "wpsc_refine_ticket_reply_with_ai", |
| 79 |
ticket_id, |
| 80 |
_ajax_nonce: nonce, |
| 81 |
}, |
| 82 |
}) |
| 83 |
.done(function (res) { |
| 84 |
jQuery(".wpsc-modal-header").html(res.title); |
| 85 |
jQuery(".wpsc-modal-body").html(res.body); |
| 86 |
jQuery(".wpsc-modal-footer").html(res.footer); |
| 87 |
|
| 88 |
wpsc_show_modal(); |
| 89 |
wpsc_show_modal_inner_container(); |
| 90 |
var $model_body = jQuery(".wpsc-modal-body"); |
| 91 |
var loaderDiv = jQuery("<div>").addClass("wpsc-loading-ai-response").html(supportcandy.ai_loader_html); |
| 92 |
$model_body.append(loaderDiv); |
| 93 |
jQuery(".wpsc-modal-footer").hide(); |
| 94 |
fetchAndAppendAIReply(ticket_id, description, nonce); |
| 95 |
}); |
| 96 |
} |
| 97 |
|
| 98 |
/** |
| 99 |
* Fetches the AI-generated reply and appends it to the chat UI. |
| 100 |
* @param {string|number} ticket_id - The ticket ID. |
| 101 |
* @param {string} description - The ticket description. |
| 102 |
* @param {string} nonce - The security nonce. |
| 103 |
*/ |
| 104 |
function fetchAndAppendAIReply(ticket_id, description, nonce) { |
| 105 |
jQuery |
| 106 |
.post({ |
| 107 |
url: supportcandy.ajax_url, |
| 108 |
data: { |
| 109 |
action: "wpsc_generate_ai_reply", |
| 110 |
ticket_id: ticket_id, |
| 111 |
is_system_call: true, |
| 112 |
description: description, |
| 113 |
_ajax_nonce: nonce, |
| 114 |
}, |
| 115 |
}) |
| 116 |
.done(function (ai) { |
| 117 |
jQuery(".wpsc-loading-ai-response").html(""); |
| 118 |
jQuery(".wpsc-modal-footer").show(); |
| 119 |
|
| 120 |
if (!ai.success) { |
| 121 |
appendMessageToChat("assistant", "Failed to generate AI reply."); |
| 122 |
return; |
| 123 |
} |
| 124 |
|
| 125 |
var $model_body = jQuery(".wpsc-modal-body"); |
| 126 |
var lastMsg = $model_body.find(".wpsc-ai-reply-message").last().text(); |
| 127 |
if (lastMsg !== ai.data.reply) { |
| 128 |
ensureAIConversation().push({ |
| 129 |
role: "assistant", |
| 130 |
content: ai.data.reply, |
| 131 |
}); |
| 132 |
appendMessageToChat("assistant", ai.data.reply); |
| 133 |
} |
| 134 |
}); |
| 135 |
} |
| 136 |
|
| 137 |
/** |
| 138 |
* Handles user prompt submission and AI response. |
| 139 |
* @param {string|number} ticket_id - The ticket ID. |
| 140 |
* @param {string} prompt - The user's prompt. |
| 141 |
* @param {string} nonce - The security nonce. |
| 142 |
* @param {function} callback - The callback function to execute after processing. |
| 143 |
* @param {string} type - The type of AI request. |
| 144 |
*/ |
| 145 |
function wpsc_refine_reply_using_agent_prompt(ticket_id, prompt, nonce, callback, type) { |
| 146 |
var conversation = ensureAIConversation(); |
| 147 |
conversation.push({ role: "user", content: prompt }); |
| 148 |
jQuery(".wpsc-modal-footer").hide(); |
| 149 |
// Append to UI |
| 150 |
appendMessageToChat("user", prompt); |
| 151 |
|
| 152 |
// If type is auto_draft_reply, and callback is wpsc_generate_ai_reply replace and action as wpsc_generate_ai_reply otherwise keep action as callback |
| 153 |
var action = "wpsc_generate_ai_reply"; |
| 154 |
if (type === "auto_draft_reply" && callback === "wpsc_generate_ai_reply") { |
| 155 |
action = callback; |
| 156 |
} else if (callback) { |
| 157 |
action = callback; |
| 158 |
} |
| 159 |
|
| 160 |
// get data from class wpsc-auto-draft-message > span |
| 161 |
var draftReply = jQuery(".wpsc-auto-draft-message > span").text().trim(); |
| 162 |
if (draftReply) { |
| 163 |
// Only add draftReply if not already present in conversation |
| 164 |
var alreadySeeded = conversation.some(function(msg) { |
| 165 |
return msg.role === "assistant" && msg.content === draftReply; |
| 166 |
}); |
| 167 |
if (!alreadySeeded) { |
| 168 |
conversation.push({ role: "assistant", content: draftReply }); |
| 169 |
} |
| 170 |
} |
| 171 |
|
| 172 |
// Call AI |
| 173 |
jQuery |
| 174 |
.post({ |
| 175 |
url: supportcandy.ajax_url, |
| 176 |
data: { |
| 177 |
action: action, |
| 178 |
ticket_id, |
| 179 |
is_system_call: false, |
| 180 |
description: JSON.stringify(conversation), |
| 181 |
_ajax_nonce: nonce, |
| 182 |
}, |
| 183 |
}) |
| 184 |
.done(function (ai) { |
| 185 |
jQuery(".wpsc-loading-ai-response").html(""); |
| 186 |
jQuery(".wpsc-modal-footer").show(); |
| 187 |
if (ai.success) { |
| 188 |
// Add AI reply to conversation and UI |
| 189 |
ensureAIConversation().push({ |
| 190 |
role: "assistant", |
| 191 |
content: ai.data.reply, |
| 192 |
}); |
| 193 |
appendMessageToChat("assistant", ai.data.reply); |
| 194 |
} else { |
| 195 |
appendMessageToChat("assistant", "Failed to generate AI reply."); |
| 196 |
} |
| 197 |
}); |
| 198 |
} |
| 199 |
|
| 200 |
/* |
| 201 |
* Handles Enter key press in the AI chat textarea to submit user prompt. |
| 202 |
*/ |
| 203 |
jQuery(document).on("keydown", ".wpsc-improve-auto-draft-reply", function (e) { |
| 204 |
if (e.key === "Enter" && !e.shiftKey) { |
| 205 |
var conversation = ensureAIConversation(); |
| 206 |
|
| 207 |
e.preventDefault(); |
| 208 |
var prompt = jQuery(this).val().trim(); |
| 209 |
if (prompt) { |
| 210 |
var pre_content = jQuery(".wpsc-auto-draft-message > span").text().trim(); |
| 211 |
|
| 212 |
if (pre_content) { |
| 213 |
// Add the draft reply as an assistant message to the conversation history |
| 214 |
conversation.push({ role: 'assistant', content: pre_content }); |
| 215 |
} |
| 216 |
var nonce = jQuery(this).data("nonce"); |
| 217 |
var ticket_id = jQuery(this).data("ticket-id"); |
| 218 |
var callback = jQuery(this).data("callback"); |
| 219 |
wpsc_refine_reply_using_agent_prompt(ticket_id, prompt, nonce, callback, "auto_draft_reply"); |
| 220 |
jQuery(this).val(""); // clear input |
| 221 |
} |
| 222 |
} |
| 223 |
}); |
| 224 |
|
| 225 |
/* |
| 226 |
* Handles Enter key press in the AI chat textarea to submit user prompt. |
| 227 |
*/ |
| 228 |
jQuery(document).on("keydown", ".wpsc-ai-chat-textarea", function (e) { |
| 229 |
if (e.key === "Enter" && !e.shiftKey) { |
| 230 |
e.preventDefault(); |
| 231 |
var prompt = jQuery(this).val().trim(); |
| 232 |
if (prompt) { |
| 233 |
var nonce = jQuery(this).data("nonce"); |
| 234 |
var ticket_id = jQuery(this).data("ticket-id"); |
| 235 |
var callback = jQuery(this).data("callback"); |
| 236 |
wpsc_refine_reply_using_agent_prompt(ticket_id, prompt, nonce, callback, "polish_reply"); |
| 237 |
jQuery(this).val(""); // clear input |
| 238 |
} |
| 239 |
} |
| 240 |
}); |
| 241 |
|
| 242 |
/** |
| 243 |
* Appends improved reply to the editor when "Append" button is clicked. |
| 244 |
*/ |
| 245 |
jQuery(document).off("click", ".wpsc-ai-append").on("click", ".wpsc-ai-append", function () { |
| 246 |
// Get the improved reply from the modal (adjust selector as needed) |
| 247 |
var improvedReply = jQuery(".wpsc-ai-reply-message").last().html() || ""; |
| 248 |
|
| 249 |
// TinyMCE editor |
| 250 |
var is_tinymce = |
| 251 |
typeof tinyMCE != "undefined" && |
| 252 |
tinyMCE.activeEditor && |
| 253 |
!tinyMCE.activeEditor.isHidden(); |
| 254 |
if (is_tinymce) { |
| 255 |
var currentContent = tinymce.activeEditor.getContent({ format: "html" }); |
| 256 |
tinymce.activeEditor.setContent(currentContent + "<br>" + improvedReply); |
| 257 |
} else { |
| 258 |
// Plain textarea: convert <br> to newlines |
| 259 |
var improvedReplyText = improvedReply.replace(/<br\s*\/?>(\r?\n)?/gi, "\n"); |
| 260 |
var $txt = jQuery(".wpsc_textarea"); |
| 261 |
var textAreaTxt = $txt.val(); |
| 262 |
$txt.val(textAreaTxt + "\n\n" + improvedReplyText); |
| 263 |
} |
| 264 |
wpsc_close_modal(); |
| 265 |
}); |
| 266 |
|
| 267 |
/** |
| 268 |
* Replaces editor content with improved reply when "Replace" button is clicked. |
| 269 |
*/ |
| 270 |
jQuery(document).off("click", ".wpsc-ai-replace").on("click", ".wpsc-ai-replace", function () { |
| 271 |
// Get the improved reply from the modal (adjust selector as needed) |
| 272 |
var improvedReply = jQuery(".wpsc-ai-reply-message").last().html() || ""; |
| 273 |
|
| 274 |
// TinyMCE editor |
| 275 |
var is_tinymce = |
| 276 |
typeof tinyMCE != "undefined" && |
| 277 |
tinyMCE.activeEditor && |
| 278 |
!tinyMCE.activeEditor.isHidden(); |
| 279 |
if (is_tinymce) { |
| 280 |
tinymce.activeEditor.setContent(improvedReply); |
| 281 |
} else { |
| 282 |
// Plain textarea: convert <br> to newlines |
| 283 |
var improvedReplyText = improvedReply.replace(/<br\s*\/?>(\r?\n)?/gi, "\n"); |
| 284 |
jQuery(".wpsc_textarea").val(improvedReplyText); |
| 285 |
} |
| 286 |
wpsc_close_modal(); |
| 287 |
}); |
| 288 |
|
| 289 |
/** |
| 290 |
* Fetches and displays AI-generated ticket summary. |
| 291 |
* @param {HTMLElement} el - The element triggering the request. |
| 292 |
* @param {string|number} ticket_id - The ticket ID. |
| 293 |
* @param {string} nonce - The security nonce. |
| 294 |
*/ |
| 295 |
function wpsc_generate_ticket_summary(el, ticket_id, nonce) { |
| 296 |
var summary_container = jQuery(".wpsc-ticket-summary-container"); |
| 297 |
|
| 298 |
summary_container.html(supportcandy.loader_html); |
| 299 |
jQuery.ajax({ |
| 300 |
url: supportcandy.ajax_url, |
| 301 |
type: "POST", |
| 302 |
data: { |
| 303 |
action: "wpsc_get_ticket_summary", |
| 304 |
ticket_id: ticket_id, |
| 305 |
_ajax_nonce: nonce, |
| 306 |
}, |
| 307 |
success: function (response) { |
| 308 |
if (response.success) { |
| 309 |
summary_container.html(response.data.summary); |
| 310 |
} else { |
| 311 |
summary_container.text("Failed to fetch ticket summary."); |
| 312 |
} |
| 313 |
}, |
| 314 |
error: function () { |
| 315 |
summary_container.text("Error fetching ticket summary."); |
| 316 |
}, |
| 317 |
}); |
| 318 |
} |
| 319 |
|
| 320 |
/** |
| 321 |
* Calls the AI assistant and gets an improved reply. |
| 322 |
* @param {HTMLElement} el - The element triggering the request. |
| 323 |
* @param {string|number} id - The ticket ID. |
| 324 |
* @param {string} nonce - The security nonce. |
| 325 |
*/ |
| 326 |
function wpsc_handle_ai_auto_draft(el, id, nonce) { |
| 327 |
|
| 328 |
jQuery.post({ |
| 329 |
url: supportcandy.ajax_url, |
| 330 |
data: { |
| 331 |
action: "wpsc_auto_draft_ticket_reply_with_ai", |
| 332 |
id, |
| 333 |
_ajax_nonce: nonce, |
| 334 |
}, |
| 335 |
}).done(function (res) { |
| 336 |
|
| 337 |
jQuery(".wpsc-modal-header").html(res.title); |
| 338 |
jQuery(".wpsc-modal-body").html(res.body); |
| 339 |
jQuery(".wpsc-modal-footer").html(res.footer); |
| 340 |
|
| 341 |
wpsc_show_modal(); |
| 342 |
wpsc_show_modal_inner_container(); |
| 343 |
var $model_body = jQuery(".wpsc-modal-body"); |
| 344 |
var loaderDiv = jQuery("<div>").addClass("wpsc-loading-ai-response").html(supportcandy.ai_loader_html); |
| 345 |
$model_body.append(loaderDiv); |
| 346 |
jQuery(".wpsc-modal-footer").hide(); |
| 347 |
fetchAndAppendAIDraft(id, nonce); |
| 348 |
}); |
| 349 |
} |
| 350 |
|
| 351 |
/** |
| 352 |
* Fetches the AI-generated reply and appends it to the chat UI. |
| 353 |
* @param {string|number} id - The ticket ID. |
| 354 |
* @param {string} nonce - The security nonce. |
| 355 |
*/ |
| 356 |
function fetchAndAppendAIDraft(id, nonce) { |
| 357 |
jQuery |
| 358 |
.post({ |
| 359 |
url: supportcandy.ajax_url, |
| 360 |
data: { |
| 361 |
action: "wpsc_generate_ai_auto_draft", |
| 362 |
id: id, |
| 363 |
_ajax_nonce: nonce, |
| 364 |
}, |
| 365 |
}) |
| 366 |
.done(function (ai) { |
| 367 |
jQuery(".wpsc-loading-ai-response").html(""); |
| 368 |
jQuery(".wpsc-modal-footer").show(); |
| 369 |
|
| 370 |
if (!ai.success) { |
| 371 |
appendMessageToChat("assistant", "Failed to generate AI reply."); |
| 372 |
return; |
| 373 |
} |
| 374 |
|
| 375 |
var $model_body = jQuery(".wpsc-modal-body"); |
| 376 |
var lastMsg = $model_body.find(".wpsc-ai-reply-message").last().text(); |
| 377 |
if (lastMsg !== ai.data.draft_reply) { |
| 378 |
ensureAIConversation().push({ |
| 379 |
role: "assistant", |
| 380 |
content: ai.data.draft_reply, |
| 381 |
}); |
| 382 |
appendMessageToChat("assistant", ai.data.draft_reply); |
| 383 |
} |
| 384 |
}); |
| 385 |
} |