PageSourceSearch

https://enhancesportsphysio.com.au/wp-content/plugins/trx_addons/a…/shortcodes/chat/chat.js?reload=1731355848

js enhancesportsphysio.com.au collected 2026-10-08 23:52:23 UTC 25,341 bytes, 680 lines download raw bytes

1/**
2 * Shortcode AI Chat
3 *
4 * @package ThemeREX Addons
5 * @since v2.22.0
6 */
7
8/* global jQuery, TRX_ADDONS_STORAGE */
9
10
11jQuery( document ).ready( function() {
12
13	"use strict";
14
15	var $window             = jQuery( window ),
16		$document           = jQuery( document ),
17		$body               = jQuery( 'body' );
18
19	$document.on( 'action.init_hidden_elements', function(e, container) {
20
21		if ( container === undefined ) {
22			container = $body;
23		}
24
25		// Init AI Chat
26		container.find( '.sc_chat:not(.sc_chat_inited)' ).each( function() {
27
28			var $sc = jQuery( this ).addClass( 'sc_chat_inited' ),
29				sc_id = $sc.attr('id') || '',
30				$form = $sc.find( '.sc_chat_form' ),
31				$prompt = $sc.find( '.sc_chat_form_field_prompt_text' ),
32				$button = $sc.find( '.sc_chat_form_field_prompt_button' ),
33				$attachments_field = $sc.find( '.sc_chat_form_field_upload_attachments_field' ),
34				$attachments_list = $sc.find( '.sc_chat_form_field_upload_attachments_list' ),
35				$result = $sc.find( '.sc_chat_list' ),
36				$start_new = $sc.find( '.sc_chat_form_start_new' ),
37				chat = [],
38				chat_position = 0;
39
40			// Return an attachment type by file name
41			var attachment_type = function( file ) {
42				var ext = trx_addons_get_file_ext( file ).toLowerCase();
43				return ['ai', 'avi', 'css', 'csv', 'dbf', 'doc', 'docx', 'dwg', 'gif', 'htm', 'html',
44					    'jpg', 'jpeg', 'js', 'json', 'mp3', 'mp4', 'pdf', 'png', 'ppt', 'pptx',
45						'psd', 'rtf', 'svg', 'txt', 'xls', 'xlsx', 'xml', 'zip' ].indexOf( ext ) >= 0 ? ext : 'unknown';
46			};
47
48			// Update the list of attachments on change the file field
49			if ( $attachments_field.length ) {
50				$attachments_field.on( 'change', function(e) {
51					var files = e.target.files;
52					if ( files && files.length ) {
53						var clear_icon = $attachments_list.data( 'clear-icon' ) || 'trx_addons_icon-cancel-2';
54						$attachments_list.empty();
55						for ( var i = 0; i < files.length; i++ ) {
56							$attachments_list.append(
57								'<span class="sc_chat_form_field_upload_attachments_list_item" title="' + files[i].name + '">'
58									+ '<span class="sc_chat_form_field_upload_attachments_list_item_icon" style="background-image: url(' + TRX_ADDONS_STORAGE['plugin_url'] + '/css/file-types/' + attachment_type( files[i].name ) + '.png)"></span>'
59									+ '<span class="sc_chat_form_field_upload_attachments_list_item_name">' + files[i].name + '</span>'
60								+ '</span>'
61							);
62						}
63						$attachments_list.append(
64							'<a href="#" class="sc_chat_form_field_upload_attachments_list_clear">'
65								+ '<span class="sc_chat_form_field_upload_attachments_list_clear_icon ' + clear_icon + '"></span>'
66								+ '<span class="sc_chat_form_field_upload_attachments_list_clear_name">' + TRX_ADDONS_STORAGE['msg_ai_helper_sc_chat_clear'] + '</span>'
67							+ '</a>'
68						);
69						$sc.addClass( 'sc_chat_with_attachments_selected' );
70					} else {
71						$attachments_list.empty();
72						$sc.removeClass( 'sc_chat_with_attachments_selected' );
73					}
74				} );
75			}
76
77			// Remove attachments on click on the clear link
78			if ( $attachments_list.length ) {
79				$attachments_list.on( 'click', '.sc_chat_form_field_upload_attachments_list_clear', function(e) {
80					e.preventDefault();
81					$attachments_field.val( '' ).trigger( 'change' );
82					return false;
83				} );
84			}
85
86			// Open/Close chat in the popup
87			if ( $sc.hasClass( 'sc_chat_popup' ) ) {
88				var $popup_content = $sc.find( '.sc_chat_content' ),
89					$popup_button = $sc.find( '.sc_chat_popup_button' );
90				$popup_button.on( 'click', function(e) {
91					e.preventDefault();
92					$sc.addClass( 'sc_chat_opening' );
93					$popup_content.slideToggle( function() {
94						$sc.removeClass( 'sc_chat_opening' );
95						$sc.toggleClass( 'sc_chat_opened' );
96						if ( $sc.hasClass( 'sc_chat_opened' ) ) {
97							if ( $popup_button.data( 'chat-opened-svg' ) ) {
98								$popup_button.find( '.sc_chat_popup_button_svg' ).html( $popup_button.data( 'chat-opened-svg' ) );
99							} else if ( $popup_button.data( 'chat-opened-image' ) ) {
100								$popup_button.find( '.sc_chat_popup_button_image' ).attr( 'src', $popup_button.data( 'chat-opened-image' ) );
101							} else if ( $popup_button.data( 'chat-opened-icon' ) ) {
102								$popup_button.find( '.sc_chat_popup_button_icon' ).removeClass( $popup_button.data( 'chat-icon' ) ).addClass( $popup_button.data( 'chat-opened-icon' ) );
103							}
104							$document.trigger( 'action.sc_chat_popup_opened', [ $popup_content ] );
105						} else {
106							if ( $popup_button.data( 'chat-svg' ) ) {
107								$popup_button.find( '.sc_chat_popup_button_svg' ).html( $popup_button.data( 'chat-svg' ) );
108							} else if ( $popup_button.data( 'chat-image' ) ) {
109								$popup_button.find( '.sc_chat_popup_button_image' ).attr( 'src', $popup_button.data( 'chat-image' ) );
110							} else if ( $popup_button.data( 'chat-icon' ) ) {
111								$popup_button.find( '.sc_chat_popup_button_icon' ).removeClass( $popup_button.data( 'chat-opened-icon' ) ).addClass( $popup_button.data( 'chat-icon' ) );
112							}
113							$document.trigger( 'action.sc_chat_popup_closed', [ $popup_content ] );
114						}
115					} );
116					return false;
117				} );
118				// Open on load
119				if ( $sc.hasClass( 'sc_chat_open_on_load' ) ) {
120					$popup_button.trigger( 'click' );
121				}
122			}
123
124			// Enable/disable button "Generate"
125			$prompt.on( 'change keyup', function(e) {
126				$button.toggleClass( 'sc_chat_form_field_prompt_button_disabled', $prompt.val() == '' );
127			} )
128			.trigger( 'change' );
129
130			// Set padding for the prompt field to avoid overlapping the button
131			if ( $button.css( 'position' ) == 'absolute' ) {
132				var set_prompt_padding = function() {
133					var button_size = Math.ceil( $button.outerWidth() ) + 10;
134					if ( button_size > 0 ) {
135						$prompt.css( 'padding-right', button_size + 'px' );
136					}
137				};
138				set_prompt_padding();
139				$window.on( 'resize', set_prompt_padding );
140				$document.on( 'action.sc_chat_popup_opened', set_prompt_padding );
141			}
142				
143			// Close a message popup on click on the close button
144			$sc.on( 'click', '.sc_chat_message_close', function(e) {
145				e.preventDefault();
146				$form.find( '.sc_chat_message' ).slideUp();
147				return false;
148			} );
149
150			// Set a new list of messages
151			$sc.on( 'trx_addons_action_sc_chat_update', function(e, messages) {
152				if ( ! messages || ! messages.length ) {
153					return;
154				}
155				// Clear previous chat
156				chat = [];
157				chat_position = 0;
158				$result.empty();
159				$form.data( 'chat-thread-id', '' );
160				$form.data( 'chat-response-id', '' );
161				// Hide the link "Start new chat"
162				$start_new.removeClass( 'trx_addons_hidden' );
163				// Hide the message popup
164				$form.find( '.sc_chat_message' ).slideUp();
165				// Add new messages to the chat
166				for ( var i = 0; i < messages.length; i++ ) {
167					add_to_chat( messages[ i ].role, messages[ i ].content, messages[ i ].attachments );
168				}
169				// Clear the prompt field and set focus to it
170				$prompt.val( '' ).trigger( 'change' );
171				$prompt.get(0).focus();
172			} );
173
174			// Start a new chat
175			$start_new.on( 'click', function(e) {
176				e.preventDefault();
177				// Clear previous chat
178				chat = [];
179				chat_position = 0;
180				$result.empty();
181				$form.data( 'chat-thread-id', '' );
182				$form.data( 'chat-response-id', '' );
183				trx_addons_do_action( 'trx_addons_action_sc_chat_updated', chat, $sc );
184				// Hide the link "Start new chat"
185				$start_new.addClass( 'trx_addons_hidden' );
186				// Hide the message popup
187				$form.find( '.sc_chat_message' ).slideUp();
188				// Clear the attachments field and list
189				$attachments_field.val( '' ).trigger( 'change' );
190				// Clear the prompt field and set focus to it
191				$prompt.val( '' ).trigger( 'change' );
192				$prompt.get(0).focus();
193				return false;
194			} );
195
196			// Change the prompt text on click on the tag
197			$sc.on( 'click', '.sc_chat_form_field_tags_item', function(e) {
198				e.preventDefault();
199				var $self = jQuery( this );
200				if ( ! $prompt.attr( 'disabled' ) ) {
201					$prompt.val( $self.data( 'tag-prompt' ) ).trigger( 'change' ).get(0).focus();
202				}
203				return false;
204			} );
205
206			// Show previous/next message
207			$prompt.on( 'keydown', function(e) {
208				var i;
209				if ( e.keyCode == 38 ) {
210					e.preventDefault();
211					if ( chat_position > 0 ) {
212						for ( i = chat_position - 1; i >= 0; i-- ) {
213							if ( chat[i].role == 'user' ) {
214								$prompt.val( chat[i].content ).trigger( 'change' );
215								chat_position = i;
216								break;
217							}
218						}
219					}
220				} else if ( e.keyCode == 40 ) {
221					e.preventDefault();
222					if ( chat_position < chat.length - 1 ) {
223						for ( i = chat_position + 1; i <= chat.length; i++ ) {
224							if ( i == chat.length ) {
225								$prompt.val( '' ).trigger( 'change' );
226								chat_position = i;
227								break;
228							} else if ( chat[i].role == 'user' ) {
229								$prompt.val( chat[i].content ).trigger( 'change' );
230								chat_position = i;
231								break;
232							}
233						}
234					}
235				}
236			} );
237
238			// Generate answer
239			$prompt.on( 'keypress', function(e) {
240				if ( e.keyCode == 13 ) {
241					e.preventDefault();
242					$button.trigger( 'click' );
243				}
244			} );
245
246			$button.on( 'click', function(e) {
247				e.preventDefault();
248				var prompt = $prompt.val(),
249					settings = $form.data( 'chat-settings' );
250
251				if ( ! prompt || ! checkLimits() ) {
252					return;
253				}
254
255				// Add prompt to the chat
256				add_to_chat( 'user', prompt, $attachments_field.length ? $attachments_field.get(0).files : [] );
257
258				// Display loading animation
259				show_loading();
260
261				// Display the link "Start new chat"
262				$start_new.removeClass( 'trx_addons_hidden' );
263
264				// Send request via AJAX
265				var attachments_present = $attachments_field.length && $attachments_field.get(0).files.length > 0,
266					args = {
267						nonce: TRX_ADDONS_STORAGE['ajax_nonce'],
268						action: 'trx_addons_ai_helper_chat',
269						count: ( trx_addons_get_cookie( 'trx_addons_ai_helper_chat_count' ) || 0 ) * 1 + 1,
270						chat: attachments_present ? chat : JSON.stringify( chat ),
271						settings: settings,
272						thread_id: $form.data( 'chat-thread-id' ) || '',
273						response_id: $form.data( 'chat-response-id' ) || ''
274					};
275				if ( attachments_present ) {
276					args = prepare_chat_args_for_ajax( args );
277					jQuery.ajax( {
278						url: TRX_ADDONS_STORAGE['ajax_url'],
279						type: "POST",
280						data: args,
281						processData: false,		// Don't process fields to the string
282						contentType: false,		// Prevent content type header
283						success: show_answer
284					} );
285				} else {
286					jQuery.post( TRX_ADDONS_STORAGE['ajax_url'], args, show_answer );
287				}
288			} );
289
290			// Fetch answer
291			function fetch_answer( data ) {
292				jQuery.post( TRX_ADDONS_STORAGE['ajax_url'], {
293					nonce: TRX_ADDONS_STORAGE['ajax_nonce'],
294					action: 'trx_addons_ai_helper_chat_fetch',
295					thread_id: data.thread_id || '',
296					run_id: data.run_id || '',
297					response_id: data.response_id || '',
298					settings: $form.data( 'chat-settings' )
299				}, show_answer );
300			}
301
302			// Show answer
303			function show_answer( response ) {
304				// Prepare response
305				var rez = trx_addons_parse_ajax_response( response, TRX_ADDONS_STORAGE['msg_ai_helper_error'] );
306
307				// Save thread ID
308				if ( rez.thread_id ) {
309					$form.data( 'chat-thread-id', rez.thread_id );
310				}
311				// Save response ID
312				if ( rez.response_id ) {
313					$form.data( 'chat-response-id', rez.response_id );
314				}
315				// If queued - fetch answer again
316				if ( rez.finish_reason == 'queued' ) {
317					var time = rez.fetch_time ? rez.fetch_time : 4000;
318					setTimeout( function() {
319						fetch_answer( rez );
320					}, time );
321				} else {
322					// Hide loading animation
323					hide_loading();
324					// Set focus to the prompt field
325					$prompt.get(0).focus();
326					// Show result
327					if ( ! rez.error && rez.data.text ) {
328						add_to_chat( 'assistant', rez.data.text, rez.data.attachments );
329						$prompt.val( '' ).trigger( 'change' );
330						if ( $attachments_field.length ) {
331							$attachments_field.val( '' ).trigger( 'change' );
332						}
333						updateLimitsCounter();
334						updateRequestsCounter();
335						trx_addons_do_action( 'trx_addons_action_sc_chat_updated', chat, $sc );
336					}
337					if ( rez.error ) {
338						showMessage( rez.error, 'error' );
339					} else if ( rez.data.message ) {
340						showMessage( rez.data.message, 'info' );
341					}
342				}
343			}
344
345			// Prepare arguments for the AJAX request. If last message in the param 'chat' contain attachments - send all arguments as FormData
346			function prepare_chat_args_for_ajax( args, parent_key, data ) {
347				if ( parent_key === undefined ) {
348					parent_key = '';
349					if ( typeof args.chat == 'object' && args.chat.length > 1 ) {
350						// Remove all attachments from old messages (leave attachments in the last user message only)
351						for ( var i = args.chat.length - 2; i >= 0; i-- ) {
352							if ( args.chat[i].attachments ) {
353								delete args.chat[i].attachments;
354							}
355						}
356					}
357				}
358				if ( data === undefined ) {
359					data = new FormData();
360				}
361				var new_key = '';
362				for ( var key in args ) {
363					new_key = parent_key ? parent_key + '[' + key + ']' : key;
364					if ( key == 'attachments' && typeof args[ key ] == 'object' && args[ key ].length > 0 ) {
365						for ( var i = 0; i < args[ key ].length; i++ ) {
366							if ( typeof args[ key ][ i ] == 'object' ) {
367								data.append( new_key + '[]', args[ key ][ i ].name );
368								data.append( new_key + '[files][]', args[ key ][ i ], args[ key ][ i ].name );
369							} else {
370								data.append( new_key + '[]', args[ key ][ i ] );
371							}
372						}
373					} else if ( typeof args[ key ] == 'object' ) {
374						data = prepare_chat_args_for_ajax( args[ key ], new_key, data );
375					} else {
376						data.append( new_key, args[ key ] );
377					}
378				}
379				return data;
380			}
381
382			// Return a layout of the chat list item
383			function get_chat_list_item( role, message, attachments ) {
384				var dt = new Date(),
385					hours = dt.getHours(),
386					minutes = dt.getMinutes() < 10 ? '0' + dt.getMinutes() : dt.getMinutes(),
387					am = hours < 12 ? 'AM' : 'PM',
388					use_am = trx_addons_apply_filters( 'trx_addons_filter_sc_chat_time_use_am', true ),
389					hours = use_am && hours > 12 ? hours - 12 : hours;
390				var style = $form.data( 'chat-style' );
391				var id = 'sc_chat_list_item_' + trx_addons_get_unique_id(),
392					name = [ 'assistant', 'loading' ].indexOf( role ) >= 0 && style.assistant_name
393								? style.assistant_name
394								: ( role == 'user' && style.user_name
395									? style.user_name
396									: ''
397									),
398					icon = [ 'assistant', 'loading' ].indexOf( role ) >= 0 && style.assistant_icon
399								? style.assistant_icon
400								: ( role == 'user' && style.user_icon
401									? style.user_icon
402									: ''
403									),
404					image = [ 'assistant', 'loading' ].indexOf( role ) >= 0 && style.assistant_image
405								? style.assistant_image
406								: ( role == 'user' && style.user_image
407									? style.user_image
408									: ''
409									),
410					image_type = image ? trx_addons_get_file_ext( image ) : '',
411					image_svg  = image_type == 'svg'
412									? ( [ 'assistant', 'loading' ].indexOf( role ) >= 0 && style.assistant_svg
413										? style.assistant_svg
414										: ( role == 'user' && style.user_svg
415											? style.user_svg
416											: trx_addons_get_inline_svg( image, {
417													render: function( html ) {
418														if ( html ) {
419															jQuery( '#' + id + ' .sc_chat_list_item_svg' ).html( html );
420														}
421													}
422												} )
423											)
424										)
425									: '';
426				var attachments_list = '';
427				if ( attachments && attachments.length > 0 ) {
428					attachments_list = '<span class="sc_chat_list_item_attachments_list">';
429					for ( var i = 0; i < attachments.length; i++ ) {
430						var attachments_name = typeof attachments[i] == 'object' ? attachments[i].name : attachments[i];
431						attachments_list += '<span class="sc_chat_list_item_attachments_list_item" title="' + attachments_name + '">'
432							+ '<span class="sc_chat_list_item_attachments_list_item_icon" style="background-image: url(' + TRX_ADDONS_STORAGE['plugin_url'] + '/css/file-types/' + attachment_type( attachments_name ) + '.png)"></span>'
433							+ '<span class="sc_chat_list_item_attachments_list_item_name">' + attachments_name + '</span>'
434						+ '</span>';
435					}
436					attachments_list += '</span>';
437				}
438				return trx_addons_apply_filters(
439					'trx_addons_filter_sc_chat_list_item',
440					'<li id="' + id + '" class="sc_chat_list_item sc_chat_list_item_' + role
441						+ ( role == 'loading' ? ' sc_chat_list_item_assistant' : '' )
442						+ ( image || icon ? ' sc_chat_list_item_with_avatar' : ' sc_chat_list_item_without_avatar' )
443						+ ( attachments_list ? ' sc_chat_list_item_with_attachments' : '' )
444					+ '">'
445						+ ( image || icon
446							? '<span class="sc_chat_list_item_avatar">'
447								+ ( image
448									? ( image_type == 'svg'
449										? '<span class="sc_chat_list_item_svg">' + image_svg + '</span>'
450										: '<img src="' + image + '" alt="' + name + '" class="sc_chat_list_item_image">'
451										)
452									: '<span class="sc_chat_list_item_icon ' + icon + '"></span>'
453									)
454								+ '</span>'
455							: ''
456							)
457						+ '<span class="sc_chat_list_item_wrap">'
458							+ '<span class="sc_chat_list_item_content">' + message + '</span>'
459							+ ( role != 'loading' ? '<span class="sc_chat_list_item_time">' + hours + ':' + minutes + ( use_am ? ' ' + am : '' ) + '</span>' : '' )
460							+ attachments_list
461						+ '</span>'
462					+ '</li>',
463					role, message, attachments
464				);
465			}
466
467			// Add a new message to the chat and display it
468			function add_to_chat( role, message, attachments ) {
469				// Add to the list of messages
470				if ( chat.length === 0 || chat[chat.length-1].role != role || chat[chat.length-1].content != message ) {
471					var msg_obj = {
472						'role': role,
473						'content': message
474					};
475					if ( attachments && attachments.length > 0 ) {
476						msg_obj.attachments = attachments;
477					}
478					chat.push( msg_obj );
479					chat_position = chat.length;
480					// Display message
481					$result.append( get_chat_list_item( role, message, attachments ) );
482					// Display chat if it's hidden
483					if ( chat.length == 1 ) {
484						$result.parent().slideDown( function() {
485							scroll_to_bottom();
486						});
487					} else {
488						scroll_to_bottom();
489					}
490				}
491			}
492
493			// Show loading animation
494			function show_loading() {
495				$form.addClass( 'sc_chat_form_loading' );
496				// Add loading animation to the chat
497				$result.append( get_chat_list_item( 'loading', '<span class="sc_chat_list_item_loading_dot"></span><span class="sc_chat_list_item_loading_dot"></span><span class="sc_chat_list_item_loading_dot"></span>' ) );
498				// Scroll chat to the bottom
499				scroll_to_bottom();
500			}
501
502			// Hide loading animation
503			function hide_loading() {
504				$form.removeClass( 'sc_chat_form_loading' );
505				$result.find( '.sc_chat_list_item_loading' ).remove();
506			}
507
508			// Scroll the chat to the bottom
509			function scroll_to_bottom() {
510				$result.parent().animate( { scrollTop: $result.parent().prop( 'scrollHeight' ) }, 500 );
511			}
512
513			// Show message
514			function showMessage( msg, type ) {
515				if ( msg.indexOf( '<p>' ) == -1 ) {
516					msg = '<p>' + msg + '</p>';
517				}
518				$form
519					.find( '.sc_chat_message_inner' )
520						.html( msg )
521						.parent()
522							.toggleClass( 'sc_chat_message_type_error', type == 'error' )
523							.toggleClass( 'sc_chat_message_type_info', type == 'info' )
524							.toggleClass( 'sc_chat_message_type_success', type == 'success' )
525							.addClass( 'sc_chat_message_show' )
526							.slideDown();
527			}
528
529			// Check limits for generation images
530			function checkLimits() {
531				// Block the button if the limits are exceeded
532				var total, used;
533				// Check limits for the generation requests from all users
534				var $limit_total = $form.find( '.sc_chat_limits_total_value' ),
535					$limit_used  = $form.find( '.sc_chat_limits_used_value' );
536				if ( $limit_total.length && $limit_used.length ) {
537					total = parseInt( $limit_total.text(), 10 );
538					used  = parseInt( $limit_used.text(), 10 );
539					if ( ! isNaN( total ) && ! isNaN( used ) ) {
540						if ( used >= total ) {
541							$button.toggleClass( 'sc_chat_form_field_prompt_button_disabled', true );
542							$prompt.attr( 'disabled', 'disabled' );
543							showMessage( $form.data( 'chat-limit-exceed' ), 'error' );
544							return false;
545						}
546					}
547				}
548				// Check limits for the generation requests from the current user
549				var $requests_total = $form.find( '.sc_chat_limits_total_requests' ),
550					$requests_used  = $form.find( '.sc_chat_limits_used_requests' );
551				if ( $requests_total.length && $requests_used.length ) {
552					total = parseInt( $requests_total.text(), 10 );
553					//used  = parseInt( $requests_used.text(), 10 );
554					used = ( trx_addons_get_cookie( 'trx_addons_ai_helper_chat_count' ) || 0 ) * 1;
555					if ( ! isNaN( total ) && ! isNaN( used ) ) {
556						if ( used >= total ) {
557							$button.toggleClass( 'sc_chat_form_field_prompt_button_disabled', true );
558							$prompt.attr( 'disabled', 'disabled' );
559							showMessage( $form.data( 'chat-limit-exceed' ), 'error' );
560							return false;
561						}
562					}
563				}
564				return true;
565			}
566			
567			// Update a counter of requests inside a limits text
568			function updateLimitsCounter() {
569				var total, used;
570				// Update a counter of the total requests
571				var $limit_total = $form.find( '.sc_chat_limits_total_value' ),
572					$limit_used  = $form.find( '.sc_chat_limits_used_value' );
573				if ( $limit_total.length && $limit_used.length ) {
574					total = parseInt( $limit_total.text(), 10 );
575					used  = parseInt( $limit_used.text(), 10 );
576					if ( ! isNaN( total ) && ! isNaN( used ) ) {
577						if ( used < total ) {
578							used = Math.min( used + 1, total );
579							$limit_used.text( used );
580						}
581					}
582				}
583				// Update a counter of the user requests
584				var $requests_total = $form.find( '.sc_chat_limits_total_requests' ),
585					$requests_used  = $form.find( '.sc_chat_limits_used_requests' );
586				if ( $requests_total.length && $requests_used.length ) {
587					total = parseInt( $requests_total.text(), 10 );
588					// used  = parseInt( $requests_used.text(), 10 );
589					used = ( trx_addons_get_cookie( 'trx_addons_ai_helper_chat_count' ) || 0 ) * 1;
590					if ( ! isNaN( total ) && ! isNaN( used ) ) {
591						if ( used < total ) {
592							used = Math.min( used + 1, total );
593							$requests_used.text( used );
594						}
595					}
596				}
597			}
598
599			// Update a counter of the generation requests
600			function updateRequestsCounter() {
601				// Save a number of requests to the client storage
602				var count = trx_addons_get_cookie( 'trx_addons_ai_helper_chat_count' ) || 0,
603					limit = 60 * 60 * 1000 * 1,	// 1 hour
604					expired = limit - ( new Date().getTime() % limit );
605
606				trx_addons_set_cookie( 'trx_addons_ai_helper_chat_count', ++count, expired );
607			}
608			
609			// Load/Save a chat messages to the client storage
610			if ( $form.data( 'chat-save-history' ) ) {
611
612				// Load a chat messages from the client storage
613				var loadChatStorage = function() {
614					// Load saved chats from the client storage
615					var storage = trx_addons_get_storage( 'trx_addons_sc_chats' );
616					if ( storage && storage.charAt(0) == '{' ) {
617						storage = JSON.parse( storage );
618					} else {
619						storage = {};
620					}
621					// Remove old chats (more than 1 day)
622					for ( var id in storage ) {
623						if ( storage[id].date < ( new Date().getTime() - 24 * 60 * 60 * 1000 ) ) {
624							delete storage[id];
625						}
626					}
627					return storage;
628				};
629
630				// Save a chat messages to the client storage
631				var saveChatStorage = function( storage ) {
632					trx_addons_set_storage( 'trx_addons_sc_chats', JSON.stringify( storage ) );
633				};
634
635				// Update a chat messages in the client storage
636				var updateChatStorage = function( chat, $sc ) {
637					if ( ! trx_addons_is_local_storage_exists() ) {
638						return;
639					}
640					// Get saved chats from the client storage
641					var storage = loadChatStorage();
642					// Add new chat to the storage
643					if ( sc_id ) {
644						if ( ! chat || chat.length == 0 ) {
645							delete storage[sc_id];
646						} else {
647							storage[sc_id] = {
648								date: new Date().getTime(),
649								chat: chat,
650								thread_id: $form.data( 'chat-thread-id' ) || '',
651								response_id: $form.data( 'chat-response-id' ) || ''
652							};
653						}
654					}
655					// Save storage to the client storage
656					saveChatStorage( storage );
657				};
658
659				// Restore a chat messages from the client storage on page load
660				var storage = loadChatStorage();
661				if ( sc_id && storage[ sc_id ] ) {
662					$sc.trigger( 'trx_addons_action_sc_chat_update', [ storage[ sc_id ].chat ] );
663					// Thread ID should be restored after the chat messages are updated because the thread ID is set to empty value while this action
664					$form.data( 'chat-thread-id', storage[sc_id].thread_id );
665					// Response ID should be restored after the chat messages are updated because the response ID is set to empty value while this action
666					$form.data( 'chat-response-id', storage[sc_id].response_id );
667				}
668
669				// Save a chat messages to the client storage after the chat messages are updated
670				$sc.on( 'trx_addons_action_sc_chat_update', function( e, messages ) {
671					updateChatStorage( messages );
672				 } );
673				trx_addons_add_action( 'trx_addons_action_sc_chat_updated', updateChatStorage );
674			}
675
676		} );
677
678	} );
679
680} );

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.