Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 69 additions & 1 deletion assets/js/admin.js
Original file line number Diff line number Diff line change
Expand Up @@ -454,7 +454,7 @@
'nuclia_taxonomy_label_map[' + taxonomy + '][fallback][labels][]' :
'nuclia_taxonomy_label_map[' + taxonomy + '][terms][' + termId + '][]';

const renderLabelCheckboxes = ( container, taxonomy, labels, termId = '', fallback = false ) => {
const renderLabelCheckboxes = ( container, taxonomy, labels, termId = '', fallback = false, checkedValues = null ) => {
container.replaceChildren();

if ( ! labels.length ) {
Expand All @@ -471,6 +471,9 @@
input.type = 'checkbox';
input.value = label;
input.name = labelCheckboxName( taxonomy, termId, fallback );
if ( checkedValues && checkedValues.includes( label ) ) {
input.checked = true;
}
text.textContent = label;

wrapper.append( input, text );
Expand All @@ -487,6 +490,31 @@
return Array.isArray( data.labels ) ? data.labels : [];
};

// Cold cache: server rendered from saved state only. Fetch confirmed
// labels and merge them in without discarding checked values.
const refreshSavedLabelset = async ( select ) => {
const labelset = select.dataset.selectedLabelset || '';
if ( ! labelset ) {
return;
}

const taxonomy = select.dataset.taxonomy;
const isFallback = select.matches( '[data-progress-agentic-rag-fallback-labelset-select]' );
const containers = isFallback ?
[ mappingContainer.querySelector( '[data-progress-agentic-rag-fallback-labels][data-taxonomy="' + selectorValue( taxonomy ) + '"]' ) ] :
Array.from( mappingContainer.querySelectorAll( '[data-progress-agentic-rag-label-checkboxes][data-taxonomy="' + selectorValue( taxonomy ) + '"]' ) );

try {
const labels = await loadMappingLabels( labelset );
containers.filter( Boolean ).forEach( ( container ) => {
const checked = ( container.dataset.checkedLabels || '' ).split( '|' ).filter( Boolean );
renderLabelCheckboxes( container, taxonomy, labels, container.dataset.termId || '', isFallback, checked );
} );
} catch ( error ) {
// Keep the server-rendered state on failure.
}
};

const buildMappingBlock = ( taxonomy ) => {
const taxonomyConfig = mappingConfig.taxonomies[ taxonomy ];
const block = document.createElement( 'div' );
Expand Down Expand Up @@ -607,6 +635,46 @@
};

if ( addMappingButton && taxonomySelect && mappingContainer ) {
if ( ! ( mappingConfig.labelsets || [] ).length ) {
// Cold-cache page load: refresh labelsets in the background instead of
// blocking render (see AdminPage::get_labelsets() AJAX handler).
request( 'progress_agentic_rag_get_labelsets' )
.then( ( data ) => {
mappingConfig.labelsets = Array.isArray( data.labelsets ) ? data.labelsets : [];

mappingContainer.querySelectorAll( '[data-progress-agentic-rag-labelset-select], [data-progress-agentic-rag-fallback-labelset-select]' ).forEach( ( select ) => {
// Saved value is the source of truth, not the select's current value.
const selected = select.dataset.selectedLabelset || '';
select.innerHTML = '';
select.appendChild( labelsetOptions() );
select.value = selected;

const label = select.closest( 'label' ) || select;
if ( label.nextElementSibling && label.nextElementSibling.hasAttribute( 'data-progress-agentic-rag-labelset-missing' ) ) {
label.nextElementSibling.remove();
}

// Confirmed list now available: a missing saved value is truly gone.
if ( selected && ! mappingConfig.labelsets.includes( selected ) ) {
const warning = document.createElement( 'p' );
warning.className = 'progress-agentic-rag__mapping-muted';
warning.setAttribute( 'data-progress-agentic-rag-labelset-missing', '' );
warning.textContent = ( config.strings.mappingLabelsetMissing || 'Labelset "%s" was not found upstream.' ).replace( '%s', selected );
label.insertAdjacentElement( 'afterend', warning );
}
} );
} )
.catch( () => {} );
}

// Label lists have their own staleness, separate from the labelset list;
// always top up (the AJAX handler is a no-op if already warm).
mappingContainer.querySelectorAll( '[data-progress-agentic-rag-labelset-select][data-selected-labelset], [data-progress-agentic-rag-fallback-labelset-select][data-selected-labelset]' ).forEach( ( select ) => {
if ( select.dataset.selectedLabelset ) {
refreshSavedLabelset( select );
}
} );

addMappingButton.addEventListener( 'click', () => {
const taxonomy = taxonomySelect.value;

Expand Down
4 changes: 2 additions & 2 deletions progress-agentic-rag.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
* Plugin Name: Progress Agentic RAG connector
* Plugin URI: https://github.com/nuclia/wordpress-plugin
* Description: Index WordPress content into Progress Agentic RAG and power knowledge-base search.
* Version: 1.0.1
* Version: 1.0.2
* Requires at least: 6.8
* Requires PHP: 8.1
* Author: Progress Software
Expand All @@ -30,7 +30,7 @@
}

// phpcs:disable WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedConstantFound -- Public plugin constants already use the Progress Agentic RAG prefix.
define( 'PROGRESS_AGENTIC_RAG_VERSION', '1.0.1' );
define( 'PROGRESS_AGENTIC_RAG_VERSION', '1.0.2' );
define( 'PROGRESS_AGENTIC_RAG_MIN_PHP_VERSION', '8.1' );
define( 'PROGRESS_AGENTIC_RAG_MIN_WP_VERSION', '6.8' );
define( 'PROGRESS_AGENTIC_RAG_FILE', __FILE__ );
Expand Down
9 changes: 8 additions & 1 deletion readme.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ Contributors: progresssoftware
Tags: search, ai, knowledge-base, rag, indexing
Requires at least: 6.8
Tested up to: 7.0
Stable tag: 1.0.1
Stable tag: 1.0.2
Requires PHP: 8.1
License: GPLv3 or later
License URI: https://www.gnu.org/licenses/gpl-3.0.html
Expand Down Expand Up @@ -43,6 +43,13 @@ No. Service credentials stay server-side, and public widget requests use the ser

== Changelog ==

= 1.0.2 =

* Fix: Admin pages no longer perform blocking upstream sync/labelset calls
during render, which caused 504 timeouts on slow or large sites. The
taxonomy labeling tab now loads instantly from cache and refreshes its
labelset dropdown in the background instead of fetching it synchronously.

= 1.0.1 =

* Fix: Advanced Custom Fields (ACF) data (e.g. Branch page locations, opening hours, contact
Expand Down
117 changes: 68 additions & 49 deletions src/Admin/AdminPage.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ public function register(): void {
add_action( 'wp_ajax_progress_agentic_rag_label_reprocess_cancel', [ $this, 'cancel_label_reprocess' ] );
add_action( 'wp_ajax_progress_agentic_rag_label_reprocess_status', [ $this, 'label_reprocess_status' ] );
add_action( 'wp_ajax_progress_agentic_rag_get_labelset_labels', [ $this, 'get_labelset_labels' ] );
add_action( 'wp_ajax_progress_agentic_rag_get_labelsets', [ $this, 'get_labelsets' ] );
add_action( 'wp_ajax_progress_agentic_rag_test_connection', [ $this, 'test_connection' ] );
add_action( 'wp_ajax_progress_agentic_rag_retry_failed_sync', [ $this, 'retry_failed_sync' ] );
add_action( 'wp_ajax_progress_agentic_rag_delete_synced_resource', [ $this, 'delete_synced_resource' ] );
Expand Down Expand Up @@ -97,53 +98,55 @@ public function enqueue_assets( string $hook_suffix ): void {
'nonce' => wp_create_nonce( 'progress_agentic_rag_manual_sync' ),
'initialSyncStatus' => $this->manual_sync->status(),
'initialDeleteStatus' => $this->manual_sync->delete_status(),
'initialBackgroundSyncStatus' => $this->manual_sync->ensure_automatic_sync(),
'mapping' => $this->mapping_data(),
'initialBackgroundSyncStatus' => $this->manual_sync->background_sync_status(),
'mapping' => $this->mapping_data_cached(),
'strings' => [
'closeDeleteProgress' => __( 'Close delete progress', 'progress-agentic-rag-connector' ),
'closeSyncProgress' => __( 'Close sync progress', 'progress-agentic-rag-connector' ),
'closeDeleteProgress' => __( 'Close delete progress', 'progress-agentic-rag-connector' ),
'closeSyncProgress' => __( 'Close sync progress', 'progress-agentic-rag-connector' ),
/* translators: %d: synced resource count. */
'confirmDelete' => __( 'Delete %d synced resource(s) from Progress Agentic RAG and clear their local sync mappings? This cannot be undone.', 'progress-agentic-rag-connector' ),
'confirmDelete' => __( 'Delete %d synced resource(s) from Progress Agentic RAG and clear their local sync mappings? This cannot be undone.', 'progress-agentic-rag-connector' ),
/* translators: %d: synced resource count. */
'confirmReprocess' => __( 'Update labels for %d synced resource(s) with the current taxonomy mapping?', 'progress-agentic-rag-connector' ),
'deleteFailed' => __( 'Synced resources could not be deleted.', 'progress-agentic-rag-connector' ),
'deleteModalLabel' => __( 'Synced resource delete', 'progress-agentic-rag-connector' ),
'deleteModalTitle' => __( 'Deleting synced resources', 'progress-agentic-rag-connector' ),
'deleteTotalLabel' => __( 'Total resources', 'progress-agentic-rag-connector' ),
'deleteDoneLabel' => __( 'Deleted', 'progress-agentic-rag-connector' ),
'deleteCurrentLabel' => __( 'Current resource', 'progress-agentic-rag-connector' ),
'deleteRunning' => __( 'Delete in progress', 'progress-agentic-rag-connector' ),
'deleting' => __( 'Preparing synced resource deletion.', 'progress-agentic-rag-connector' ),
'failed' => __( 'Manual sync could not start.', 'progress-agentic-rag-connector' ),
'running' => __( 'Sync in progress', 'progress-agentic-rag-connector' ),
'syncModalLabel' => __( 'Manual sync', 'progress-agentic-rag-connector' ),
'syncModalTitle' => __( 'Syncing selected content', 'progress-agentic-rag-connector' ),
'syncTotalLabel' => __( 'Total entities', 'progress-agentic-rag-connector' ),
'syncDoneLabel' => __( 'Synced', 'progress-agentic-rag-connector' ),
'syncCurrentLabel' => __( 'Current entity', 'progress-agentic-rag-connector' ),
'sync' => __( 'Sync manually', 'progress-agentic-rag-connector' ),
'reprocessFailed' => __( 'Label reprocessing could not start.', 'progress-agentic-rag-connector' ),
'starting' => __( 'Preparing manual sync.', 'progress-agentic-rag-connector' ),
'complete' => __( 'Manual sync complete.', 'progress-agentic-rag-connector' ),
'mappingSelectLabelset' => __( 'Select a labelset', 'progress-agentic-rag-connector' ),
'mappingRemove' => __( 'Remove', 'progress-agentic-rag-connector' ),
'mappingLabelset' => __( 'Labelset', 'progress-agentic-rag-connector' ),
'mappingTerm' => __( 'Term', 'progress-agentic-rag-connector' ),
'mappingLabels' => __( 'Progress Agentic RAG labels', 'progress-agentic-rag-connector' ),
'mappingFallback' => __( 'Fallback labels (when no terms assigned)', 'progress-agentic-rag-connector' ),
'mappingSelectLabels' => __( 'Select a labelset to load labels.', 'progress-agentic-rag-connector' ),
'mappingLoadingLabels' => __( 'Loading labels...', 'progress-agentic-rag-connector' ),
'mappingNoLabels' => __( 'No labels available.', 'progress-agentic-rag-connector' ),
'mappingLabelsFailed' => __( 'Labels could not be loaded.', 'progress-agentic-rag-connector' ),
'mappingNoTerms' => __( 'No terms available for this taxonomy.', 'progress-agentic-rag-connector' ),
'testConnection' => __( 'Testing connection...', 'progress-agentic-rag-connector' ),
'connectionTestFailed' => __( 'Connection test could not run.', 'progress-agentic-rag-connector' ),
'retryFailed' => __( 'Failed sync items could not be retried.', 'progress-agentic-rag-connector' ),
'diagnosticsCopied' => __( 'Diagnostics copied.', 'progress-agentic-rag-connector' ),
'diagnosticsFailed' => __( 'Diagnostics could not be exported.', 'progress-agentic-rag-connector' ),
'confirmDeleteSingle' => __( 'Delete this resource from Progress Agentic RAG and clear its local sync mapping?', 'progress-agentic-rag-connector' ),
'deleteSingleFailed' => __( 'The synced resource could not be removed.', 'progress-agentic-rag-connector' ),
'deleteSingleComplete' => __( 'Synced resource removed.', 'progress-agentic-rag-connector' ),
'confirmReprocess' => __( 'Update labels for %d synced resource(s) with the current taxonomy mapping?', 'progress-agentic-rag-connector' ),
'deleteFailed' => __( 'Synced resources could not be deleted.', 'progress-agentic-rag-connector' ),
'deleteModalLabel' => __( 'Synced resource delete', 'progress-agentic-rag-connector' ),
'deleteModalTitle' => __( 'Deleting synced resources', 'progress-agentic-rag-connector' ),
'deleteTotalLabel' => __( 'Total resources', 'progress-agentic-rag-connector' ),
'deleteDoneLabel' => __( 'Deleted', 'progress-agentic-rag-connector' ),
'deleteCurrentLabel' => __( 'Current resource', 'progress-agentic-rag-connector' ),
'deleteRunning' => __( 'Delete in progress', 'progress-agentic-rag-connector' ),
'deleting' => __( 'Preparing synced resource deletion.', 'progress-agentic-rag-connector' ),
'failed' => __( 'Manual sync could not start.', 'progress-agentic-rag-connector' ),
'running' => __( 'Sync in progress', 'progress-agentic-rag-connector' ),
'syncModalLabel' => __( 'Manual sync', 'progress-agentic-rag-connector' ),
'syncModalTitle' => __( 'Syncing selected content', 'progress-agentic-rag-connector' ),
'syncTotalLabel' => __( 'Total entities', 'progress-agentic-rag-connector' ),
'syncDoneLabel' => __( 'Synced', 'progress-agentic-rag-connector' ),
'syncCurrentLabel' => __( 'Current entity', 'progress-agentic-rag-connector' ),
'sync' => __( 'Sync manually', 'progress-agentic-rag-connector' ),
'reprocessFailed' => __( 'Label reprocessing could not start.', 'progress-agentic-rag-connector' ),
'starting' => __( 'Preparing manual sync.', 'progress-agentic-rag-connector' ),
'complete' => __( 'Manual sync complete.', 'progress-agentic-rag-connector' ),
'mappingSelectLabelset' => __( 'Select a labelset', 'progress-agentic-rag-connector' ),
'mappingRemove' => __( 'Remove', 'progress-agentic-rag-connector' ),
'mappingLabelset' => __( 'Labelset', 'progress-agentic-rag-connector' ),
'mappingTerm' => __( 'Term', 'progress-agentic-rag-connector' ),
'mappingLabels' => __( 'Progress Agentic RAG labels', 'progress-agentic-rag-connector' ),
'mappingFallback' => __( 'Fallback labels (when no terms assigned)', 'progress-agentic-rag-connector' ),
'mappingSelectLabels' => __( 'Select a labelset to load labels.', 'progress-agentic-rag-connector' ),
'mappingLoadingLabels' => __( 'Loading labels...', 'progress-agentic-rag-connector' ),
'mappingNoLabels' => __( 'No labels available.', 'progress-agentic-rag-connector' ),
'mappingLabelsFailed' => __( 'Labels could not be loaded.', 'progress-agentic-rag-connector' ),
'mappingNoTerms' => __( 'No terms available for this taxonomy.', 'progress-agentic-rag-connector' ),
/* translators: %s: labelset name. */
'mappingLabelsetMissing' => __( 'Labelset "%s" was not found upstream.', 'progress-agentic-rag-connector' ),
'testConnection' => __( 'Testing connection...', 'progress-agentic-rag-connector' ),
'connectionTestFailed' => __( 'Connection test could not run.', 'progress-agentic-rag-connector' ),
'retryFailed' => __( 'Failed sync items could not be retried.', 'progress-agentic-rag-connector' ),
'diagnosticsCopied' => __( 'Diagnostics copied.', 'progress-agentic-rag-connector' ),
'diagnosticsFailed' => __( 'Diagnostics could not be exported.', 'progress-agentic-rag-connector' ),
'confirmDeleteSingle' => __( 'Delete this resource from Progress Agentic RAG and clear its local sync mapping?', 'progress-agentic-rag-connector' ),
'deleteSingleFailed' => __( 'The synced resource could not be removed.', 'progress-agentic-rag-connector' ),
'deleteSingleComplete' => __( 'Synced resource removed.', 'progress-agentic-rag-connector' ),
],
]
);
Expand Down Expand Up @@ -373,6 +376,16 @@ public function get_labelset_labels(): void {
wp_send_json_success( [ 'labels' => $this->api_client->get_labelset_labels( $labelset ) ] );
}

public function get_labelsets(): void {
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( [ 'message' => __( 'You do not have permission to load labelsets.', 'progress-agentic-rag-connector' ) ], 403 );
}

check_ajax_referer( 'progress_agentic_rag_manual_sync' );

wp_send_json_success( [ 'labelsets' => $this->api_client->get_labelsets() ] );
}

public function test_connection(): void {
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( [ 'message' => __( 'You do not have permission to test the connection.', 'progress-agentic-rag-connector' ) ], 403 );
Expand Down Expand Up @@ -585,7 +598,7 @@ private function render_indexation(): void {
$api_connected = $this->settings->get_api_is_reachable();
$scheduler_available = $this->manual_sync->scheduler_available();
$scheduler_status = $this->manual_sync->scheduler_status();
$background_sync_status = $this->manual_sync->ensure_automatic_sync();
$background_sync_status = $this->manual_sync->background_sync_status();
$manual_sync_status = $this->manual_sync->status();
$delete_status = $this->manual_sync->delete_status();
$delete_running = 'running' === ( $delete_status['status'] ?? '' );
Expand Down Expand Up @@ -628,7 +641,7 @@ private function render_taxonomy_labeling(): void {
$taxonomies = get_taxonomies( [ 'public' => true ], 'objects' );
$taxonomy_label_map = $this->settings->get_taxonomy_label_map();
$taxonomy_label_map_option = SettingsRepository::OPTION_TAXONOMY_LABEL_MAP;
$labelsets = $this->api_client->get_labelsets();
$labelsets = $this->api_client->get_labelsets_cached();
$taxonomy_terms = $this->taxonomy_terms( array_keys( $taxonomies ) );
$labelset_labels = $this->labelset_labels_for_mapping( $taxonomy_label_map );
$taxonomy_mapping_warnings = $this->taxonomy_mapping_warnings( $taxonomy_label_map, $labelsets, $taxonomy_terms, $labelset_labels, (int) ( $sync_status['total_mapped'] ?? 0 ) );
Expand Down Expand Up @@ -888,9 +901,13 @@ private function connection_test_result( string $zone, string $kbid, string $tok
}

/**
* Never performs an upstream HTTP request; the taxonomy-labeling tab's
* "Add mapping" dropdown self-heals via the get_labelsets() AJAX handler
* when the cache is cold (see assets/js/admin.js).
*
* @return array<string, mixed>
*/
private function mapping_data(): array {
private function mapping_data_cached(): array {
$taxonomies = [];

foreach ( get_taxonomies( [ 'public' => true ], 'objects' ) as $taxonomy_name => $taxonomy ) {
Expand All @@ -910,7 +927,7 @@ private function mapping_data(): array {

return [
'taxonomies' => $taxonomies,
'labelsets' => $this->api_client->get_labelsets(),
'labelsets' => $this->api_client->get_labelsets_cached(),
];
}

Expand All @@ -937,6 +954,8 @@ private function taxonomy_terms( array $taxonomy_names ): array {
}

/**
* Cache-only; JS tops up stale labels via get_labelset_labels() after load.
*
* @param array<string, mixed> $mapping Taxonomy label mapping.
*
* @return array<string, list<string>>
Expand All @@ -963,7 +982,7 @@ private function labelset_labels_for_mapping( array $mapping ): array {

$labels = [];
foreach ( array_keys( $labelsets ) as $labelset ) {
$labels[ $labelset ] = $this->api_client->get_labelset_labels( $labelset );
$labels[ $labelset ] = $this->api_client->get_labelset_labels_cached( $labelset );
}

return $labels;
Expand Down
Loading
Loading