Changeset 3414259
- Timestamp:
- 12/08/2025 12:12:46 PM (2 months ago)
- Location:
- askeet/trunk
- Files:
-
- 4 edited
-
README.txt (modified) (3 diffs)
-
askeet.php (modified) (8 diffs)
-
assets/css/style.css (modified) (3 diffs)
-
assets/js/script.js (modified) (7 diffs)
Legend:
- Unmodified
- Added
- Removed
-
askeet/trunk/README.txt
r3395155 r3414259 5 5 Requires at least: 6.2 6 6 Tested up to: 6.8 7 Stable tag: 2. 07 Stable tag: 2.5 8 8 License: GPLv2 or later 9 9 License URI: http://www.gnu.org/licenses/gpl-2.0.html … … 234 234 * NEW: Add annual payment and date renew 235 235 236 = 2.5 = 237 * NEW: Star rating system (1-5 stars) for AI responses - Rate response quality with a simple click 238 * NEW: Feedback modal with thumbs up/down and optional comments - Share your experience directly from the plugin 239 * NEW: Smart auto-retry system - Automatically retries up to 5 times silently before showing error 240 * NEW: AI disclaimer notice displayed with error messages to remind users to verify responses 241 * NEW: "Send Feedback" button in header for quick access 242 * IMPROVED: Modern animated modals for success/error notifications (glassmorphism design) 243 * FIXED: Discord, Slack, and Contact button text visibility (white text on gradient background) 244 * FIXED: Subscription upgrade now correctly modifies existing subscription instead of creating duplicates 245 * IMPROVED: Full internationalization (i18n) support for all new UI elements 246 * IMPROVED: Better error handling with user-friendly messages 247 236 248 = Upcoming Features = 237 249 … … 258 270 * UPDATED: UI/UX design optimization with better responsiveness 259 271 272 = 2.5 = 273 Major update with user feedback features! Rate AI responses with stars, send feedback directly from the plugin, and enjoy improved reliability with smart auto-retry. Fixed button visibility issues and subscription upgrade bugs. 274 260 275 == Source Code == 261 276 The unminified source code for JavaScript and CSS is included in the /assets/js/ and /assets/css/ folders of this plugin. If you use a minified file, the original source is present alongside it. -
askeet/trunk/askeet.php
r3395869 r3414259 3 3 * Plugin Name: Askeet 4 4 * Description: Askeet is your AI assistant for WooCommerce: generate, run, and analyze SQL queries securely, no code needed, with a modern multilingual interface. 5 * Version: 2. 05 * Version: 2.5 6 6 * Author: Reach Technologies 7 7 * License: GPL-2.0+ … … 37 37 update_option('askeet_install_id', wp_generate_password(24, false)); 38 38 } 39 define('ASKEET_VERSION', '2. 0');39 define('ASKEET_VERSION', '2.5'); 40 40 41 41 $install_id = get_option('askeet_install_id'); … … 179 179 'install_id' => $install_id, 180 180 'current_plan' => $current_plan, 181 'api_url' => askeet_get_api_url(), 182 'site_url' => get_site_url(), 181 183 )); 182 184 wp_localize_script('askeet-script', 'askeet_query_assistant_i18n', array( … … 215 217 'ai_fixing_query' => __('The assistant is trying to fix the query...', 'askeet'), 216 218 'ai_failed_3_times' => __('Unable to process the request, please reformulate your question.', 'askeet'), 219 'ai_failed_5_times' => __('Unable to process the request after 5 attempts, please reformulate your question.', 'askeet'), 220 'ai_retrying' => __('Retrying...', 'askeet'), 221 'ai_warning' => __('Askeet is an AI and may make mistakes. Please verify the responses.', 'askeet'), 217 222 'no_result_found' => __('No result found.', 'askeet'), 223 // Star Rating System 224 'rate_response' => __('Rate this response:', 'askeet'), 225 'rating_sending' => __('Sending...', 'askeet'), 226 'rating_thank_you' => __('Thank you!', 'askeet'), 227 'rating_error' => __('Error', 'askeet'), 228 // Feedback Modal 229 'submit_feedback' => __('Submit Feedback', 'askeet'), 230 'feedback_title' => __('Send Feedback', 'askeet'), 231 'feedback_subtitle' => __('Help us improve Askeet', 'askeet'), 232 'satisfied_option' => __('Satisfied', 'askeet'), 233 'not_satisfied_option' => __('Not Satisfied', 'askeet'), 234 'feedback_placeholder' => __('Tell us more about your experience... (optional)', 'askeet'), 235 'feedback_success' => __('Thank you! Your feedback has been submitted.', 'askeet'), 236 'feedback_error' => __('Error submitting feedback. Please try again.', 'askeet'), 218 237 'already_unlimited' => __('You are already on the unlimited plan.', 'askeet'), 219 238 'upgrade_success' => __('Upgrade successful!', 'askeet'), … … 258 277 ?> 259 278 <div class="wrap askeet"> 260 <div class="wcqa-header-flex" >279 <div class="wcqa-header-flex" style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;"> 261 280 <img src="<?php echo esc_url(plugins_url('assets/img/askeet-logo.svg', __FILE__)); ?>" alt="<?php esc_attr_e('Ask Woo Logo', 'askeet'); ?>"> 262 </div> 281 <button type="button" id="askeet-feedback-btn" class="askeet-feedback-trigger"> 282 <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 283 <path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path> 284 </svg> 285 <?php echo esc_html__('Send Feedback', 'askeet'); ?> 286 </button> 287 </div> 288 289 <!-- Feedback Modal --> 290 <div id="askeet-feedback-overlay" class="askeet-feedback-overlay"> 291 <div class="askeet-feedback-modal"> 292 <div class="askeet-feedback-header"> 293 <h2><?php echo esc_html__('Send your Feedback', 'askeet'); ?></h2> 294 <button type="button" class="askeet-feedback-close" id="askeet-feedback-close"> 295 <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 296 <path d="M18 6l-12 12"></path> 297 <path d="M6 6l12 12"></path> 298 </svg> 299 </button> 300 </div> 301 302 <div class="askeet-feedback-body"> 303 <!-- Info box --> 304 <div class="askeet-feedback-info"> 305 <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 306 <path d="M3 12a9 9 0 1 0 18 0a9 9 0 0 0 -18 0"></path> 307 <path d="M12 9h.01"></path> 308 <path d="M11 12h1v4h1"></path> 309 </svg> 310 <p><?php echo esc_html__('Your feedback helps us improve Askeet for everyone!', 'askeet'); ?></p> 311 <a href="https://calendly.com/askeet/feedback" target="_blank" rel="noopener noreferrer" class="askeet-feedback-book"> 312 <?php echo esc_html__('Book a call', 'askeet'); ?> 313 </a> 314 </div> 315 316 <!-- Satisfaction question --> 317 <div class="askeet-feedback-satisfaction"> 318 <p class="askeet-feedback-question"><?php echo esc_html__('Are you satisfied with your experience?', 'askeet'); ?></p> 319 <div class="askeet-feedback-thumbs"> 320 <button type="button" class="askeet-thumb-btn" data-satisfaction="yes" id="thumb-yes"> 321 <svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 322 <path d="M7 11v8a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1v-7a1 1 0 0 1 1 -1h3a4 4 0 0 0 4 -4v-1a2 2 0 0 1 4 0v5h3a2 2 0 0 1 2 2l-1 5a2 3 0 0 1 -2 2h-7a3 3 0 0 1 -3 -3"></path> 323 </svg> 324 <span><?php echo esc_html__('Yes', 'askeet'); ?></span> 325 </button> 326 <button type="button" class="askeet-thumb-btn" data-satisfaction="no" id="thumb-no"> 327 <svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 328 <path d="M7 13v-8a1 1 0 0 0 -1 -1h-2a1 1 0 0 0 -1 1v7a1 1 0 0 0 1 1h3a4 4 0 0 1 4 4v1a2 2 0 0 0 4 0v-5h3a2 2 0 0 0 2 -2l-1 -5a2 3 0 0 0 -2 -2h-7a3 3 0 0 0 -3 3"></path> 329 </svg> 330 <span><?php echo esc_html__('No', 'askeet'); ?></span> 331 </button> 332 </div> 333 </div> 334 335 <!-- Feedback textarea --> 336 <div class="askeet-feedback-textarea-wrapper"> 337 <textarea id="askeet-feedback-text" class="askeet-feedback-textarea" placeholder="<?php echo esc_attr__('Type your feedback (Optional)', 'askeet'); ?>" maxlength="500"></textarea> 338 <div class="askeet-feedback-counter"> 339 <span id="askeet-char-count">0</span>/500 340 </div> 341 </div> 342 </div> 343 344 <div class="askeet-feedback-footer"> 345 <button type="button" id="askeet-submit-feedback" class="askeet-submit-btn" disabled> 346 <?php echo esc_html__('Submit Feedback', 'askeet'); ?> 347 </button> 348 <button type="button" id="askeet-cancel-feedback" class="askeet-cancel-btn"> 349 <?php echo esc_html__('Cancel', 'askeet'); ?> 350 </button> 351 </div> 352 </div> 353 </div> 354 263 355 <div id="wcqa-chat-container"> 264 356 <div id="wcqa-messages"></div> … … 323 415 $previous_ai_response = isset($data['previous_ai_response']) ? sanitize_textarea_field(wp_unslash($data['previous_ai_response'])) : ''; 324 416 $install_id = isset($data['install_id']) ? sanitize_text_field(wp_unslash($data['install_id'])) : ''; 417 $failed_sql_query = isset($data['failed_sql_query']) ? sanitize_textarea_field(wp_unslash($data['failed_sql_query'])) : ''; 418 $sql_error = isset($data['sql_error']) ? sanitize_textarea_field(wp_unslash($data['sql_error'])) : ''; 419 $retry_attempt = isset($data['retry_attempt']) ? intval($data['retry_attempt']) : 0; 325 420 } else { 326 421 $query_request = isset($_POST['query_request']) ? sanitize_textarea_field(wp_unslash($_POST['query_request'])) : ''; … … 328 423 $previous_ai_response = isset($_POST['previous_ai_response']) ? sanitize_textarea_field(wp_unslash($_POST['previous_ai_response'])) : ''; 329 424 $install_id = isset($_POST['install_id']) ? sanitize_text_field(wp_unslash($_POST['install_id'])) : ''; 425 $failed_sql_query = isset($_POST['failed_sql_query']) ? sanitize_textarea_field(wp_unslash($_POST['failed_sql_query'])) : ''; 426 $sql_error = isset($_POST['sql_error']) ? sanitize_textarea_field(wp_unslash($_POST['sql_error'])) : ''; 427 $retry_attempt = isset($_POST['retry_attempt']) ? intval($_POST['retry_attempt']) : 0; 330 428 } 331 429 $db_structure = $this->askeet_get_full_db_structure(); … … 338 436 'previous_ai_response' => $previous_ai_response, 339 437 'install_id' => $install_id, 438 'failed_sql_query' => $failed_sql_query, 439 'sql_error' => $sql_error, 440 'retry_attempt' => $retry_attempt, 340 441 ); 341 442 $response = wp_remote_post($full_url, array( -
askeet/trunk/assets/css/style.css
r3395155 r3414259 45 45 } 46 46 47 .btn { 48 color: white; 47 .btn, 48 a.btn, 49 .button-group a.btn, 50 .button-group .btn { 51 color: #ffffff !important; 49 52 border: none; 50 53 padding: 17px 34px; … … 54 57 text-align: center; 55 58 min-width: 160px; 56 transition: background0.3s ease;59 transition: all 0.3s ease; 57 60 border-radius: 10px; 58 background: linear-gradient(180deg, rgb(61 63 66 / 74%) 0%, #13191e 100%); 59 } 60 61 .btn:hover { 62 background: #004d8c; 63 color: white!important; 61 background: linear-gradient(135deg, #005a9e 0%, #0077cc 100%) !important; 62 text-decoration: none !important; 63 display: inline-flex; 64 align-items: center; 65 justify-content: center; 66 box-shadow: 0 4px 12px rgba(0, 90, 158, 0.25); 67 } 68 69 .btn:hover, 70 a.btn:hover, 71 .button-group a.btn:hover, 72 .button-group .btn:hover { 73 background: linear-gradient(135deg, #004080 0%, #005fa3 100%) !important; 74 color: #ffffff !important; 75 text-decoration: none !important; 76 transform: translateY(-2px); 77 box-shadow: 0 6px 20px rgba(0, 90, 158, 0.35); 78 } 79 80 .btn:focus, 81 a.btn:focus { 82 outline: none; 83 box-shadow: 0 0 0 3px rgba(0, 90, 158, 0.3); 84 } 85 86 #btn-discord, 87 #btn-slack, 88 #btn-support { 89 color: #ffffff !important; 90 background: linear-gradient(135deg, #005a9e 0%, #0077cc 100%) !important; 64 91 } 65 92 … … 1195 1222 } 1196 1223 } 1224 1225 /* ======================================== 1226 FEEDBACK MODAL STYLES 1227 ======================================== */ 1228 1229 /* Feedback Trigger Button */ 1230 .askeet-feedback-trigger { 1231 display: inline-flex; 1232 align-items: center; 1233 gap: 8px; 1234 padding: 10px 20px; 1235 background: linear-gradient(135deg, #005a9e 0%, #0077cc 100%); 1236 color: #ffffff !important; 1237 border: none; 1238 border-radius: 10px; 1239 font-size: 14px; 1240 font-weight: 600; 1241 cursor: pointer; 1242 transition: all 0.3s ease; 1243 box-shadow: 0 4px 12px rgba(0, 90, 158, 0.25); 1244 } 1245 1246 .askeet-feedback-trigger:hover { 1247 background: linear-gradient(135deg, #004080 0%, #005fa3 100%); 1248 transform: translateY(-2px); 1249 box-shadow: 0 6px 20px rgba(0, 90, 158, 0.35); 1250 } 1251 1252 .askeet-feedback-trigger svg { 1253 stroke: #ffffff; 1254 } 1255 1256 /* Feedback Modal Overlay */ 1257 .askeet-feedback-overlay { 1258 display: none; 1259 position: fixed; 1260 top: 0; 1261 left: 0; 1262 width: 100%; 1263 height: 100%; 1264 background: rgba(0, 0, 0, 0.6); 1265 backdrop-filter: blur(8px); 1266 z-index: 999999; 1267 opacity: 0; 1268 transition: opacity 0.3s ease; 1269 } 1270 1271 .askeet-feedback-overlay.active { 1272 display: flex; 1273 align-items: center; 1274 justify-content: center; 1275 opacity: 1; 1276 } 1277 1278 /* Feedback Modal */ 1279 .askeet-feedback-modal { 1280 background: #ffffff; 1281 border-radius: 16px; 1282 max-width: 520px; 1283 width: 90%; 1284 box-shadow: 0 25px 80px rgba(0, 0, 0, 0.25); 1285 transform: scale(0.9) translateY(20px); 1286 opacity: 0; 1287 transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1); 1288 overflow: hidden; 1289 } 1290 1291 .askeet-feedback-overlay.active .askeet-feedback-modal { 1292 transform: scale(1) translateY(0); 1293 opacity: 1; 1294 } 1295 1296 /* Modal Header */ 1297 .askeet-feedback-header { 1298 position: relative; 1299 padding: 24px 24px 16px; 1300 border-bottom: 1px solid #f0f0f0; 1301 } 1302 1303 .askeet-feedback-header h2 { 1304 font-size: 24px; 1305 font-weight: 700; 1306 color: #1a1a1a; 1307 margin: 0; 1308 text-align: center; 1309 } 1310 1311 .askeet-feedback-close { 1312 position: absolute; 1313 top: 20px; 1314 right: 20px; 1315 background: transparent; 1316 border: none; 1317 padding: 8px; 1318 cursor: pointer; 1319 border-radius: 50%; 1320 transition: background 0.2s ease; 1321 } 1322 1323 .askeet-feedback-close:hover { 1324 background: #f0f0f0; 1325 } 1326 1327 .askeet-feedback-close svg { 1328 stroke: #666; 1329 display: block; 1330 } 1331 1332 /* Modal Body */ 1333 .askeet-feedback-body { 1334 padding: 24px; 1335 } 1336 1337 /* Info Box */ 1338 .askeet-feedback-info { 1339 display: flex; 1340 align-items: center; 1341 gap: 12px; 1342 padding: 14px 16px; 1343 background: linear-gradient(135deg, #e8f4fd 0%, #f0f7ff 100%); 1344 border-radius: 10px; 1345 margin-bottom: 24px; 1346 } 1347 1348 .askeet-feedback-info svg { 1349 flex-shrink: 0; 1350 stroke: #005a9e; 1351 } 1352 1353 .askeet-feedback-info p { 1354 font-size: 14px; 1355 color: #333; 1356 margin: 0; 1357 flex-grow: 1; 1358 } 1359 1360 .askeet-feedback-book { 1361 color: #005a9e !important; 1362 font-size: 14px; 1363 font-weight: 600; 1364 text-decoration: none; 1365 white-space: nowrap; 1366 border-bottom: 1px solid transparent; 1367 transition: border-color 0.2s ease; 1368 } 1369 1370 .askeet-feedback-book:hover { 1371 border-bottom-color: #005a9e; 1372 text-decoration: none !important; 1373 } 1374 1375 /* Satisfaction Section */ 1376 .askeet-feedback-satisfaction { 1377 text-align: center; 1378 margin-bottom: 24px; 1379 } 1380 1381 .askeet-feedback-question { 1382 font-size: 14px; 1383 color: #666; 1384 margin-bottom: 16px; 1385 } 1386 1387 .askeet-feedback-thumbs { 1388 display: flex; 1389 justify-content: center; 1390 gap: 16px; 1391 } 1392 1393 .askeet-thumb-btn { 1394 display: flex; 1395 align-items: center; 1396 gap: 10px; 1397 padding: 12px 24px; 1398 background: transparent; 1399 border: 2px solid #e0e0e0; 1400 border-radius: 12px; 1401 cursor: pointer; 1402 transition: all 0.2s ease; 1403 min-width: 100px; 1404 justify-content: center; 1405 } 1406 1407 .askeet-thumb-btn:hover { 1408 border-color: #bbb; 1409 background: #fafafa; 1410 } 1411 1412 .askeet-thumb-btn.selected { 1413 border-color: #005a9e; 1414 background: linear-gradient(135deg, #e8f4fd 0%, #f0f7ff 100%); 1415 } 1416 1417 .askeet-thumb-btn.selected svg { 1418 stroke: #005a9e; 1419 } 1420 1421 .askeet-thumb-btn svg { 1422 stroke: #888; 1423 transition: stroke 0.2s ease; 1424 } 1425 1426 .askeet-thumb-btn span { 1427 font-size: 15px; 1428 font-weight: 600; 1429 color: #333; 1430 } 1431 1432 /* Textarea */ 1433 .askeet-feedback-textarea-wrapper { 1434 position: relative; 1435 } 1436 1437 .askeet-feedback-textarea { 1438 width: 100%; 1439 height: 120px; 1440 padding: 14px 16px; 1441 border: 1px solid #e0e0e0; 1442 border-radius: 10px; 1443 font-size: 14px; 1444 font-family: inherit; 1445 resize: none; 1446 outline: none; 1447 transition: border-color 0.2s ease, box-shadow 0.2s ease; 1448 box-sizing: border-box; 1449 } 1450 1451 .askeet-feedback-textarea:focus { 1452 border-color: #005a9e; 1453 box-shadow: 0 0 0 3px rgba(0, 90, 158, 0.15); 1454 } 1455 1456 .askeet-feedback-textarea::placeholder { 1457 color: #aaa; 1458 } 1459 1460 .askeet-feedback-counter { 1461 text-align: right; 1462 margin-top: 8px; 1463 font-size: 12px; 1464 color: #999; 1465 } 1466 1467 /* Modal Footer */ 1468 .askeet-feedback-footer { 1469 padding: 20px 24px 24px; 1470 border-top: 1px solid #f0f0f0; 1471 display: flex; 1472 flex-direction: column; 1473 gap: 12px; 1474 align-items: center; 1475 } 1476 1477 .askeet-submit-btn { 1478 display: inline-flex; 1479 align-items: center; 1480 justify-content: center; 1481 padding: 14px 40px; 1482 background: linear-gradient(135deg, #005a9e 0%, #0077cc 100%); 1483 color: #ffffff !important; 1484 border: none; 1485 border-radius: 10px; 1486 font-size: 16px; 1487 font-weight: 600; 1488 cursor: pointer; 1489 transition: all 0.3s ease; 1490 box-shadow: 0 8px 20px rgba(0, 90, 158, 0.3); 1491 min-width: 180px; 1492 } 1493 1494 .askeet-submit-btn:hover:not(:disabled) { 1495 background: linear-gradient(135deg, #004080 0%, #005fa3 100%); 1496 transform: translateY(-2px); 1497 box-shadow: 0 10px 25px rgba(0, 90, 158, 0.4); 1498 } 1499 1500 .askeet-submit-btn:disabled { 1501 background: #e0e0e0; 1502 color: #999 !important; 1503 cursor: not-allowed; 1504 box-shadow: none; 1505 opacity: 0.7; 1506 } 1507 1508 .askeet-cancel-btn { 1509 background: transparent; 1510 border: none; 1511 color: #666 !important; 1512 font-size: 15px; 1513 font-weight: 500; 1514 cursor: pointer; 1515 padding: 8px 16px; 1516 transition: color 0.2s ease; 1517 } 1518 1519 .askeet-cancel-btn:hover { 1520 color: #333 !important; 1521 } 1522 1523 /* Responsive */ 1524 @media (max-width: 480px) { 1525 .askeet-feedback-modal { 1526 width: 95%; 1527 max-width: none; 1528 } 1529 1530 .askeet-feedback-header { 1531 padding: 20px 16px 14px; 1532 } 1533 1534 .askeet-feedback-header h2 { 1535 font-size: 20px; 1536 } 1537 1538 .askeet-feedback-body { 1539 padding: 16px; 1540 } 1541 1542 .askeet-feedback-info { 1543 flex-direction: column; 1544 text-align: center; 1545 } 1546 1547 .askeet-feedback-thumbs { 1548 flex-direction: column; 1549 gap: 10px; 1550 } 1551 1552 .askeet-thumb-btn { 1553 width: 100%; 1554 } 1555 } 1556 1557 /* ======================================== 1558 RETRY CONTAINER & AI WARNING STYLES 1559 ======================================== */ 1560 1561 .wcqa-retry-container { 1562 display: flex; 1563 flex-wrap: wrap; 1564 align-items: center; 1565 gap: 12px; 1566 margin-top: 12px; 1567 padding: 12px 16px; 1568 background: linear-gradient(135deg, rgba(255, 193, 7, 0.1) 0%, rgba(255, 152, 0, 0.08) 100%); 1569 border-radius: 10px; 1570 border: 1px solid rgba(255, 193, 7, 0.3); 1571 } 1572 1573 .wcqa-retry-query.button { 1574 background: linear-gradient(135deg, #ff6b6b 0%, #ee5a5a 100%) !important; 1575 color: #fff !important; 1576 border: none !important; 1577 padding: 8px 18px !important; 1578 border-radius: 8px !important; 1579 font-weight: 600 !important; 1580 font-size: 13px !important; 1581 cursor: pointer !important; 1582 transition: all 0.3s ease !important; 1583 box-shadow: 0 2px 8px rgba(255, 107, 107, 0.3) !important; 1584 } 1585 1586 .wcqa-retry-query.button:hover { 1587 background: linear-gradient(135deg, #ff5252 0%, #e53935 100%) !important; 1588 transform: translateY(-1px) !important; 1589 box-shadow: 0 4px 12px rgba(255, 107, 107, 0.4) !important; 1590 } 1591 1592 .wcqa-ai-warning { 1593 display: flex; 1594 align-items: center; 1595 gap: 8px; 1596 font-size: 12.5px; 1597 color: #856404; 1598 font-style: italic; 1599 line-height: 1.4; 1600 flex: 1; 1601 min-width: 200px; 1602 } 1603 1604 .wcqa-ai-warning::before { 1605 content: "\26A0"; 1606 font-size: 16px; 1607 color: #f39c12; 1608 font-style: normal; 1609 } 1610 1611 @media (max-width: 600px) { 1612 .wcqa-retry-container { 1613 flex-direction: column; 1614 align-items: flex-start; 1615 } 1616 1617 .wcqa-ai-warning { 1618 min-width: unset; 1619 } 1620 } 1621 1622 /* ======================================== 1623 STAR RATING SYSTEM STYLES 1624 ======================================== */ 1625 1626 .wcqa-rating-container { 1627 display: flex; 1628 flex-wrap: wrap; 1629 align-items: center; 1630 gap: 10px; 1631 margin-top: 14px; 1632 padding: 10px 14px; 1633 background: linear-gradient(135deg, rgba(255, 215, 0, 0.08) 0%, rgba(255, 193, 7, 0.05) 100%); 1634 border-radius: 10px; 1635 border: 1px solid rgba(255, 193, 7, 0.2); 1636 } 1637 1638 .wcqa-rating-label { 1639 font-size: 12.5px; 1640 color: #666; 1641 font-weight: 500; 1642 } 1643 1644 .wcqa-stars-container { 1645 display: flex; 1646 gap: 4px; 1647 cursor: pointer; 1648 } 1649 1650 .wcqa-stars-container.rated { 1651 cursor: default; 1652 } 1653 1654 .wcqa-star { 1655 font-size: 22px; 1656 color: #ddd; 1657 transition: all 0.2s ease; 1658 line-height: 1; 1659 } 1660 1661 .wcqa-star:hover, 1662 .wcqa-star.hovered { 1663 color: #ffc107; 1664 transform: scale(1.15); 1665 } 1666 1667 .wcqa-star.selected { 1668 color: #ffc107; 1669 } 1670 1671 .wcqa-stars-container.rated .wcqa-star { 1672 cursor: default; 1673 } 1674 1675 .wcqa-stars-container.rated .wcqa-star:hover { 1676 transform: none; 1677 } 1678 1679 .wcqa-rating-feedback { 1680 font-size: 12px; 1681 color: #888; 1682 font-style: italic; 1683 transition: all 0.3s ease; 1684 } 1685 1686 .wcqa-rating-feedback.success { 1687 color: #4caf50; 1688 font-weight: 600; 1689 } 1690 1691 .wcqa-rating-feedback.error { 1692 color: #f44336; 1693 } 1694 1695 @media (max-width: 500px) { 1696 .wcqa-rating-container { 1697 flex-direction: column; 1698 align-items: flex-start; 1699 } 1700 } -
askeet/trunk/assets/js/script.js
r3395155 r3414259 24 24 function renderMessage(content, sender = 'ai', queryText = null, isError = false) { 25 25 const msg = $('<div class="wcqa-message"></div>').addClass(sender); 26 const messageId = 'msg-' + Date.now() + '-' + Math.random().toString(36).substr(2, 9); 27 msg.attr('data-message-id', messageId); 28 26 29 if (sender === 'user' && queryText) { 27 30 // Add Save button for user queries … … 41 44 } 42 45 if (isError) { 46 // Create a container for retry button and AI warning 47 const retryContainer = $('<div class="wcqa-retry-container"></div>'); 43 48 const retryBtn = $('<button class="wcqa-retry-query button">'+askeet_query_assistant_i18n.retry+'</button>'); 44 49 retryBtn.on('click', function(e) { … … 46 51 retryLastQuery(); 47 52 }); 48 msg.append(retryBtn); 49 } 53 retryContainer.append(retryBtn); 54 // Add AI warning text 55 const aiWarning = $('<span class="wcqa-ai-warning">'+(askeet_query_assistant_i18n.ai_warning || 'Askeet est une AI et peut faire des erreurs. Veuillez svp vérifier les réponses.')+'</span>'); 56 retryContainer.append(aiWarning); 57 msg.append(retryContainer); 58 } 59 60 // Add star rating for AI messages (not for errors) 61 if (sender === 'ai' && content && !isError) { 62 const ratingContainer = $('<div class="wcqa-rating-container"></div>'); 63 const ratingLabel = $('<span class="wcqa-rating-label">'+(askeet_query_assistant_i18n.rate_response || 'Rate this response:')+'</span>'); 64 const starsContainer = $('<div class="wcqa-stars-container" data-message-id="'+messageId+'"></div>'); 65 66 // Create 5 stars 67 for (let i = 1; i <= 5; i++) { 68 const star = $('<span class="wcqa-star" data-rating="'+i+'">☆</span>'); 69 starsContainer.append(star); 70 } 71 72 const ratingFeedback = $('<span class="wcqa-rating-feedback"></span>'); 73 ratingContainer.append(ratingLabel); 74 ratingContainer.append(starsContainer); 75 ratingContainer.append(ratingFeedback); 76 msg.append(ratingContainer); 77 } 78 50 79 $('#wcqa-messages').append(msg); 51 80 scrollChatToBottom(); … … 194 223 $('#process-query').prop('disabled', true); 195 224 let attempt = 0; 225 const maxAttempts = 5; // Retry up to 5 times before showing error 226 let lastFailedSqlQuery = ''; // Track failed SQL for retry context 227 let lastErrorMessage = ''; // Track error message for retry context 228 196 229 function tryProcessQuery() { 197 230 attempt++; 231 // Build request data with retry context if this is a retry attempt 232 let requestData = { 233 action: 'askeet_process_query_request', 234 query_request: queryRequest, 235 previous_user_query: previous_user_query, 236 previous_ai_response: previous_ai_response, 237 nonce: askeet_query_assistant.nonce, 238 install_id: askeet_query_assistant.install_id 239 }; 240 241 // If this is a retry, include the failed query and error for AI context 242 if (attempt > 1 && lastFailedSqlQuery) { 243 requestData.failed_sql_query = lastFailedSqlQuery; 244 requestData.sql_error = lastErrorMessage; 245 requestData.retry_attempt = attempt; 246 } 247 198 248 $.ajax({ 199 249 url: askeet_query_assistant.ajax_url, 200 250 type: 'POST', 201 data: { 202 action: 'askeet_process_query_request', 203 query_request: queryRequest, 204 previous_user_query: previous_user_query, 205 previous_ai_response: previous_ai_response, 206 nonce: askeet_query_assistant.nonce, 207 install_id: askeet_query_assistant.install_id 208 }, 251 data: requestData, 209 252 success: function(response) { 210 // // console.log('[WCQA] AJAX success:', response); // Debug log211 removeLoading();212 $('#process-query').prop('disabled', false);213 253 // PATCH: Detect limit errors in AJAX responses and show only one modal 214 if (handleLimitModals(response)) return; 254 if (handleLimitModals(response)) { 255 removeLoading(); 256 $('#process-query').prop('disabled', false); 257 return; 258 } 215 259 let results = (response.data && response.data.results) ? response.data.results : (response.results ? response.results : []); 216 260 let sql_query = (response.data && response.data.sql_query) ? response.data.sql_query : (response.sql_query ? response.sql_query : ''); … … 218 262 // Only execute the paginated query, do NOT render SQL or button 219 263 if (response.success && currentSqlQuery) { 220 // TODO: Hide this in production 264 removeLoading(); 265 $('#process-query').prop('disabled', false); 221 266 clearResultMessages(); 222 267 executeQueryPage(1, true); 223 268 } else if (response.success) { 269 removeLoading(); 270 $('#process-query').prop('disabled', false); 224 271 clearResultMessages(); 225 272 renderMessage('<div class="query-success">'+askeet_query_assistant_i18n.query_executed_successfully+' 0 '+askeet_query_assistant_i18n.results_found+'.</div>', 'ai'); 226 273 } else { 227 clearErrorMessages(); 228 let errorMsg = ''; 229 if (response.data && response.data.message && response.data.message.indexOf('Seules les requêtes SELECT sont autorisées') !== -1) { 230 errorMsg = response.data.message; 274 // Store failed query info for next retry 275 if (sql_query) { 276 lastFailedSqlQuery = sql_query; 277 } 278 lastErrorMessage = (response.data && response.data.message) ? response.data.message : 'Query failed'; 279 280 // Check if it's a non-retryable error (like security restriction) 281 let isNonRetryableError = response.data && response.data.message && 282 response.data.message.indexOf('Seules les requêtes SELECT sont autorisées') !== -1; 283 284 if (isNonRetryableError || attempt >= maxAttempts) { 285 // Max attempts reached or non-retryable error, show final error 286 removeLoading(); 287 $('#process-query').prop('disabled', false); 288 clearErrorMessages(); 289 let errorMsg = ''; 290 if (isNonRetryableError) { 291 errorMsg = response.data.message; 292 } else { 293 errorMsg = askeet_query_assistant_i18n.ai_failed_5_times || askeet_query_assistant_i18n.ai_failed_3_times; 294 } 295 renderMessage('<div class="query-error">'+errorMsg+'</div>', 'ai', null, true); 231 296 } else { 232 errorMsg = askeet_query_assistant_i18n.ai_failed_3_times; 297 // Auto-retry silently in background with context 298 setTimeout(tryProcessQuery, 1000); 233 299 } 234 renderMessage('<div class="query-error">'+errorMsg+'</div>', 'ai', null, true);235 300 } 236 301 }, 237 302 error: function(xhr, status, error) { 238 removeLoading(); 239 clearErrorMessages(); 303 // Store error for retry context 304 lastErrorMessage = error || 'Network error'; 305 240 306 try { 241 307 var resp = xhr.responseJSON || JSON.parse(xhr.responseText); 242 // console.log('[WCQA] AJAX error:', resp); // Debug log243 308 // PATCH: Detect limit errors in AJAX responses and show only one modal 244 if (handleLimitModals(resp)) return; 245 } catch(e){ 246 // console.log('[WCQA] AJAX error (parse fail):', xhr.responseText); 309 if (handleLimitModals(resp)) { 310 removeLoading(); 311 $('#process-query').prop('disabled', false); 312 return; 313 } 314 if (resp && resp.data && resp.data.message) { 315 lastErrorMessage = resp.data.message; 316 } 317 } catch(e){} 318 319 if (attempt >= maxAttempts) { 320 // Max attempts reached, show final error 321 removeLoading(); 322 $('#process-query').prop('disabled', false); 323 clearErrorMessages(); 324 renderMessage('<div class="query-error">'+askeet_query_assistant_i18n.error_communication+'</div>', 'ai', null, true); 325 } else { 326 // Auto-retry silently in background 327 setTimeout(tryProcessQuery, 1000); 247 328 } 248 renderMessage('<div class="query-error">'+askeet_query_assistant_i18n.error_communication+'</div>', 'ai', null, true);249 329 } 250 330 }); … … 267 347 $('#process-query').prop('disabled', true); 268 348 let attempt = 0; 349 const maxAttempts = 5; // Retry up to 5 times before showing error 350 let lastFailedSqlQuery = currentSqlQuery || ''; // Use last known SQL query 351 let lastErrorMessage = ''; // Track error message for retry context 352 269 353 function tryProcessQuery() { 270 354 attempt++; 355 // Build request data with retry context if this is a retry attempt 356 let requestData = { 357 action: 'askeet_process_query_request', 358 query_request: lastQueryRequest, 359 nonce: askeet_query_assistant.nonce, 360 install_id: askeet_query_assistant.install_id 361 }; 362 363 // If this is a retry, include the failed query and error for AI context 364 if (attempt > 1 && lastFailedSqlQuery) { 365 requestData.failed_sql_query = lastFailedSqlQuery; 366 requestData.sql_error = lastErrorMessage; 367 requestData.retry_attempt = attempt; 368 } 369 271 370 $.ajax({ 272 371 url: askeet_query_assistant.ajax_url, 273 372 type: 'POST', 274 data: { 275 action: 'askeet_process_query_request', 276 query_request: lastQueryRequest, 277 nonce: askeet_query_assistant.nonce, 278 install_id: askeet_query_assistant.install_id 279 }, 373 data: requestData, 280 374 success: function(response) { 281 // console.log('[WCQA] AJAX success:', response); // Debug log282 removeLoading();283 $('#process-query').prop('disabled', false);284 375 // PATCH: Detect limit errors in AJAX responses and show only one modal 285 if (handleLimitModals(response)) return; 376 if (handleLimitModals(response)) { 377 removeLoading(); 378 $('#process-query').prop('disabled', false); 379 return; 380 } 286 381 let results = (response.data && response.data.results) ? response.data.results : (response.results ? response.results : []); 287 382 let sql_query = (response.data && response.data.sql_query) ? response.data.sql_query : (response.sql_query ? response.sql_query : ''); 288 383 currentSqlQuery = sql_query || ''; 289 if (response.success && results && results.length > 0) { 384 if (response.success && currentSqlQuery) { 385 removeLoading(); 386 $('#process-query').prop('disabled', false); 290 387 executeQueryPage(1, true); 291 388 } else if (response.success) { 389 removeLoading(); 390 $('#process-query').prop('disabled', false); 292 391 renderMessage('<div class="query-success">'+askeet_query_assistant_i18n.query_executed_successfully+' 0 '+askeet_query_assistant_i18n.results_found+'.</div>', 'ai'); 293 392 } else { 294 clearErrorMessages(); 295 let errorMsg = ''; 296 if (response.data && response.data.message && response.data.message.indexOf('Seules les requêtes SELECT sont autorisées') !== -1) { 297 errorMsg = response.data.message; 393 // Store failed query info for next retry 394 if (sql_query) { 395 lastFailedSqlQuery = sql_query; 396 } 397 lastErrorMessage = (response.data && response.data.message) ? response.data.message : 'Query failed'; 398 399 // Check if it's a non-retryable error (like security restriction) 400 let isNonRetryableError = response.data && response.data.message && 401 response.data.message.indexOf('Seules les requêtes SELECT sont autorisées') !== -1; 402 403 if (isNonRetryableError || attempt >= maxAttempts) { 404 // Max attempts reached or non-retryable error, show final error 405 removeLoading(); 406 $('#process-query').prop('disabled', false); 407 clearErrorMessages(); 408 let errorMsg = ''; 409 if (isNonRetryableError) { 410 errorMsg = response.data.message; 411 } else { 412 errorMsg = askeet_query_assistant_i18n.ai_failed_5_times || askeet_query_assistant_i18n.ai_failed_3_times; 413 } 414 renderMessage('<div class="query-error">'+errorMsg+'</div>', 'ai', null, true); 298 415 } else { 299 errorMsg = askeet_query_assistant_i18n.ai_failed_3_times; 416 // Auto-retry silently in background with context 417 setTimeout(tryProcessQuery, 1000); 300 418 } 301 renderMessage('<div class="query-error">'+errorMsg+'</div>', 'ai', null, true);302 419 } 303 420 }, 304 421 error: function(xhr, status, error) { 305 removeLoading(); 306 clearErrorMessages(); 422 // Store error for retry context 423 lastErrorMessage = error || 'Network error'; 424 307 425 try { 308 426 var resp = xhr.responseJSON || JSON.parse(xhr.responseText); 309 // console.log('[WCQA] AJAX error:', resp); // Debug log310 427 // PATCH: Detect limit errors in AJAX responses and show only one modal 311 if (handleLimitModals(resp)) return; 312 } catch(e){ 313 // console.log('[WCQA] AJAX error (parse fail):', xhr.responseText); 428 if (handleLimitModals(resp)) { 429 removeLoading(); 430 $('#process-query').prop('disabled', false); 431 return; 432 } 433 if (resp && resp.data && resp.data.message) { 434 lastErrorMessage = resp.data.message; 435 } 436 } catch(e){} 437 438 if (attempt >= maxAttempts) { 439 // Max attempts reached, show final error 440 removeLoading(); 441 $('#process-query').prop('disabled', false); 442 clearErrorMessages(); 443 renderMessage('<div class="query-error">'+askeet_query_assistant_i18n.error_communication+'</div>', 'ai', null, true); 444 } else { 445 // Auto-retry silently in background 446 setTimeout(tryProcessQuery, 1000); 314 447 } 315 renderMessage('<div class="query-error">'+askeet_query_assistant_i18n.error_communication+'</div>', 'ai', null, true);316 448 } 317 449 }); … … 1134 1266 }); 1135 1267 }); 1268 1269 // ======================================== 1270 // FEEDBACK MODAL SYSTEM 1271 // ======================================== 1272 1273 let feedbackSatisfaction = null; 1274 1275 // Open feedback modal 1276 $('#askeet-feedback-btn').on('click', function() { 1277 $('#askeet-feedback-overlay').addClass('active'); 1278 resetFeedbackModal(); 1279 }); 1280 1281 // Close feedback modal 1282 $('#askeet-feedback-close, #askeet-cancel-feedback').on('click', function() { 1283 closeFeedbackModal(); 1284 }); 1285 1286 // Close on overlay click 1287 $('#askeet-feedback-overlay').on('click', function(e) { 1288 if ($(e.target).is('#askeet-feedback-overlay')) { 1289 closeFeedbackModal(); 1290 } 1291 }); 1292 1293 // Handle thumb buttons 1294 $('.askeet-thumb-btn').on('click', function() { 1295 $('.askeet-thumb-btn').removeClass('selected'); 1296 $(this).addClass('selected'); 1297 feedbackSatisfaction = $(this).data('satisfaction'); 1298 updateSubmitButton(); 1299 }); 1300 1301 // Character counter 1302 $('#askeet-feedback-text').on('input', function() { 1303 const count = $(this).val().length; 1304 $('#askeet-char-count').text(count); 1305 updateSubmitButton(); 1306 }); 1307 1308 // Submit feedback 1309 $('#askeet-submit-feedback').on('click', function() { 1310 const btn = $(this); 1311 const originalText = btn.text(); 1312 const feedbackText = $('#askeet-feedback-text').val().trim(); 1313 1314 if (!feedbackSatisfaction) { 1315 alert('Please select if you are satisfied or not.'); 1316 return; 1317 } 1318 1319 btn.prop('disabled', true).text('Sending...'); 1320 1321 // Get install_id from localized data 1322 const installId = window.askeet_query_assistant && window.askeet_query_assistant.install_id ? window.askeet_query_assistant.install_id : ''; 1323 const apiUrl = window.askeet_query_assistant && window.askeet_query_assistant.api_url ? window.askeet_query_assistant.api_url : 'https://api.askeet.ai'; 1324 1325 $.ajax({ 1326 url: apiUrl + '/submit-feedback', 1327 type: 'POST', 1328 contentType: 'application/json', 1329 data: JSON.stringify({ 1330 install_id: installId, 1331 satisfaction: feedbackSatisfaction, 1332 feedback_text: feedbackText, 1333 page_url: window.location.href, 1334 user_agent: navigator.userAgent 1335 }), 1336 success: function(response) { 1337 if (response.success) { 1338 // Show success message in modal 1339 showFeedbackSuccess(); 1340 } else { 1341 alert('Error: ' + (response.error || 'Failed to submit feedback')); 1342 btn.prop('disabled', false).text(originalText); 1343 } 1344 }, 1345 error: function() { 1346 alert('Network error. Please try again.'); 1347 btn.prop('disabled', false).text(originalText); 1348 } 1349 }); 1350 }); 1351 1352 function updateSubmitButton() { 1353 const hasSelection = feedbackSatisfaction !== null; 1354 $('#askeet-submit-feedback').prop('disabled', !hasSelection); 1355 } 1356 1357 function closeFeedbackModal() { 1358 $('#askeet-feedback-overlay').removeClass('active'); 1359 } 1360 1361 function resetFeedbackModal() { 1362 feedbackSatisfaction = null; 1363 $('.askeet-thumb-btn').removeClass('selected'); 1364 $('#askeet-feedback-text').val(''); 1365 $('#askeet-char-count').text('0'); 1366 $('#askeet-submit-feedback').prop('disabled', true).text(askeet_query_assistant_i18n.submit_feedback || 'Submit Feedback'); 1367 1368 // Reset to normal view (hide success) 1369 $('.askeet-feedback-body, .askeet-feedback-footer').show(); 1370 $('.askeet-feedback-success').remove(); 1371 } 1372 1373 function showFeedbackSuccess() { 1374 // Hide body and footer 1375 $('.askeet-feedback-body, .askeet-feedback-footer').hide(); 1376 1377 // Show success message 1378 const successHtml = ` 1379 <div class="askeet-feedback-success" style="padding: 60px 30px; text-align: center;"> 1380 <div style="width: 80px; height: 80px; background: linear-gradient(135deg, #4caf50 0%, #45a049 100%); border-radius: 50%; margin: 0 auto 24px; display: flex; align-items: center; justify-content: center;"> 1381 <svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"> 1382 <polyline points="20 6 9 17 4 12"></polyline> 1383 </svg> 1384 </div> 1385 <h3 style="font-size: 24px; font-weight: 700; color: #1a1a1a; margin: 0 0 12px;">Thank You!</h3> 1386 <p style="font-size: 16px; color: #666; margin: 0 0 24px;">Your feedback has been submitted successfully.</p> 1387 <button type="button" class="askeet-submit-btn" onclick="jQuery('#askeet-feedback-overlay').removeClass('active');" style="background: linear-gradient(135deg, #4caf50 0%, #45a049 100%);"> 1388 Close 1389 </button> 1390 </div> 1391 `; 1392 1393 $('.askeet-feedback-modal').append(successHtml); 1394 1395 // Auto close after 3 seconds 1396 setTimeout(function() { 1397 closeFeedbackModal(); 1398 // Reset after close animation 1399 setTimeout(resetFeedbackModal, 300); 1400 }, 3000); 1401 } 1402 1403 // Close modal on Escape key 1404 $(document).on('keydown', function(e) { 1405 if (e.key === 'Escape' && $('#askeet-feedback-overlay').hasClass('active')) { 1406 closeFeedbackModal(); 1407 } 1408 }); 1409 1410 // ======================================== 1411 // STAR RATING SYSTEM 1412 // ======================================== 1413 1414 // Get install_id and api_url from localized data 1415 const installId = window.askeet_query_assistant && window.askeet_query_assistant.install_id ? window.askeet_query_assistant.install_id : ''; 1416 const apiUrl = window.askeet_query_assistant && window.askeet_query_assistant.api_url ? window.askeet_query_assistant.api_url : 'https://api.askeet.ai'; 1417 1418 // Star hover effect 1419 $(document).on('mouseenter', '.wcqa-star', function() { 1420 const container = $(this).closest('.wcqa-stars-container'); 1421 if (container.hasClass('rated')) return; // Don't change if already rated 1422 1423 const rating = $(this).data('rating'); 1424 container.find('.wcqa-star').each(function() { 1425 const starRating = $(this).data('rating'); 1426 if (starRating <= rating) { 1427 $(this).html('★').addClass('hovered'); // Filled star 1428 } else { 1429 $(this).html('☆').removeClass('hovered'); // Empty star 1430 } 1431 }); 1432 }); 1433 1434 // Star mouse leave - reset if not rated 1435 $(document).on('mouseleave', '.wcqa-stars-container', function() { 1436 if ($(this).hasClass('rated')) return; 1437 1438 $(this).find('.wcqa-star').each(function() { 1439 $(this).html('☆').removeClass('hovered'); 1440 }); 1441 }); 1442 1443 // Star click - submit rating 1444 $(document).on('click', '.wcqa-star', function() { 1445 const container = $(this).closest('.wcqa-stars-container'); 1446 if (container.hasClass('rated')) return; // Already rated 1447 1448 const rating = $(this).data('rating'); 1449 const messageId = container.data('message-id'); 1450 const feedbackSpan = container.siblings('.wcqa-rating-feedback'); 1451 const messageContainer = container.closest('.wcqa-message'); 1452 const messageContent = messageContainer.find('.query-success, .query-error, .wcqa-table-wrapper').first().text().substring(0, 200); 1453 1454 // Show filled stars up to selected rating 1455 container.find('.wcqa-star').each(function() { 1456 const starRating = $(this).data('rating'); 1457 if (starRating <= rating) { 1458 $(this).html('★').addClass('selected'); 1459 } else { 1460 $(this).html('☆').removeClass('selected'); 1461 } 1462 }); 1463 1464 // Mark as rated 1465 container.addClass('rated'); 1466 feedbackSpan.text(askeet_query_assistant_i18n.rating_sending || 'Sending...'); 1467 1468 // Submit rating to API 1469 $.ajax({ 1470 url: apiUrl + '/submit-rating', 1471 type: 'POST', 1472 contentType: 'application/json', 1473 data: JSON.stringify({ 1474 install_id: installId, 1475 rating: rating, 1476 message_id: messageId, 1477 query: lastQueryRequest || '', 1478 response_preview: messageContent 1479 }), 1480 success: function(response) { 1481 if (response.success) { 1482 feedbackSpan.text(askeet_query_assistant_i18n.rating_thank_you || 'Thank you!').addClass('success'); 1483 } else { 1484 feedbackSpan.text(askeet_query_assistant_i18n.rating_error || 'Error').addClass('error'); 1485 // Allow retry 1486 container.removeClass('rated'); 1487 } 1488 }, 1489 error: function() { 1490 feedbackSpan.text(askeet_query_assistant_i18n.rating_error || 'Error').addClass('error'); 1491 // Allow retry 1492 container.removeClass('rated'); 1493 } 1494 }); 1495 }); 1136 1496 });
Note: See TracChangeset
for help on using the changeset viewer.