test(); if (is_wp_error($test)) { $ok = false; /* translators: %s: error message */ $lines[] = sprintf(__('Vector store: %s', 'aipcf-ai-provider-for-cloudflare'), $test->get_error_message()); } else { $lines[] = __('Vector store: connected.', 'aipcf-ai-provider-for-cloudflare'); } $vectors = EmbeddingClient::embed(['Connection test from AIPCF.']); if (is_wp_error($vectors)) { $ok = false; /* translators: %s: error message */ $lines[] = sprintf(__('Embedding model: %s', 'aipcf-ai-provider-for-cloudflare'), $vectors->get_error_message()); } else { /* translators: 1: model name, 2: numbers in the vector it returned (e.g. 1,024) */ $lines[] = sprintf(__('Embedding model: %1$s answered with a vector of %2$s numbers.', 'aipcf-ai-provider-for-cloudflare'), Models::current(), number_format(count($vectors[0]))); } $warnings = Settings::transportWarnings(); if (!is_wp_error($store)) { EmbeddingsStatus::storeStatus($store); // show the result on the panel, even while off } self::notice($ok ? ($warnings === [] ? 'success' : 'warning') : 'error', implode(' ', array_merge($lines, $warnings)), EmbeddingsSettings::STORE_ANCHOR); } public static function handlePrepare(): void { self::guard('aipcf_embed_prepare'); delete_transient(EmbeddingsStatus::STATUS_TRANSIENT); $store = StoreFactory::current(); $dims = (int) (Models::get(Models::current())['dims'] ?? 0); $r = is_wp_error($store) ? $store : $store->ensureIndex($dims); if (is_wp_error($r)) { self::notice('error', $r->get_error_message(), EmbeddingsSettings::STORE_ANCHOR); } /** @var \WordPress\CloudflareWorkersAiProvider\Embeddings\Stores\VectorStore $store */ delete_transient(EmbeddingsStatus::STATUS_TRANSIENT); $status = EmbeddingsStatus::storeStatus($store); // cached, so the panel shows it even while off if (empty($status['error']) && !empty($status['ready'])) { self::notice('success', sprintf( /* translators: %s: index name */ __('Index %s is ready.', 'aipcf-ai-provider-for-cloudflare'), $store->indexName() ), EmbeddingsSettings::STORE_ANCHOR); } self::notice('info', sprintf( /* translators: %s: index name */ __('Cloudflare is preparing index %s. This can take a few minutes; press "Create or check index" again to finish setting it up.', 'aipcf-ai-provider-for-cloudflare'), $store->indexName() ), EmbeddingsSettings::STORE_ANCHOR); } public static function handleRun(): void { self::guard('aipcf_embed_run'); $r = Ingest::tick(true, 20); if (!$r['ran']) { self::notice('warning', $r['reason']); } $message = sprintf( /* translators: 1: embedded, 2: unchanged, 3: removed, 4: failed */ __('Embedded %1$d, unchanged %2$d, removed %3$d, failed %4$d.', 'aipcf-ai-provider-for-cloudflare'), $r['embedded'], $r['unchanged'], $r['deleted'], $r['failed'] ); self::notice($r['failed'] > 0 || $r['stopped'] !== '' || State::get()['paused'] !== '' ? 'warning' : 'success', $message . ($r['message'] !== '' ? ' ' . $r['message'] : '')); } public static function handleReconcile(): void { self::guard('aipcf_embed_reconcile'); if (!Settings::enabled()) { self::notice('warning', __('Switch Embeddings on first.', 'aipcf-ai-provider-for-cloudflare')); } IndexTable::install(); Ingest::startReconcile(); Ingest::reconcileStep(microtime(true) + 15); self::notice('success', __('The selection is being re-checked; changes are applied on the next runs.', 'aipcf-ai-provider-for-cloudflare')); } public static function handleRetry(): void { self::guard('aipcf_embed_retry'); $n = IndexTable::exists() ? IndexTable::retryFailed() : 0; /* translators: %d: items */ self::notice('success', sprintf(_n('%d item queued again.', '%d items queued again.', $n, 'aipcf-ai-provider-for-cloudflare'), $n)); } public static function handleRebuild(): void { self::guard('aipcf_embed_rebuild'); if (!Settings::enabled()) { self::notice('warning', __('Switch Embeddings on first.', 'aipcf-ai-provider-for-cloudflare')); } Ingest::rebuild(); $store = StoreFactory::current(); if (is_wp_error($store)) { $message = __('Rebuilding. Everything is embedded again over the next runs.', 'aipcf-ai-provider-for-cloudflare'); } elseif (!$store->isSiteOwn()) { // A custom index name: Ingest::rebuild() re-embeds in place and never drops it. $message = sprintf( /* translators: %s: index name */ __('Rebuilding in %s. Everything is embedded again over the next runs, in the same index.', 'aipcf-ai-provider-for-cloudflare'), $store->indexName() ); } else { $message = sprintf( /* translators: %s: index name */ __('Rebuilding into %s. Everything is embedded again over the next runs; the old index is deleted when that is done.', 'aipcf-ai-provider-for-cloudflare'), $store->indexName() ); } self::notice('success', $message); } public static function handlePurge(): void { self::guard('aipcf_embed_purge'); $r = Ingest::purge(); if (is_wp_error($r)) { self::notice('error', $r->get_error_message()); } self::notice('success', __('Embeddings are off and this site\'s vectors were deleted from the vector store. Your content is not touched.', 'aipcf-ai-provider-for-cloudflare')); } public static function handleSearch(): void { if (!current_user_can('manage_options')) { wp_die(esc_html__('You do not have permission to do this.', 'aipcf-ai-provider-for-cloudflare'), 403); } check_admin_referer('aipcf_embed_search', 'aipcf_embed_search_nonce'); $query = isset($_POST['aipcf_embed_query']) ? sanitize_text_field(wp_unslash((string) $_POST['aipcf_embed_query'])) : ''; $payload = EmbeddingsStatus::searchPayload($query); set_transient( 'aipcf_embed_search_' . get_current_user_id(), is_wp_error($payload) ? ['query' => $query, 'error' => $payload->get_error_message()] : $payload, 5 * MINUTE_IN_SECONDS ); wp_safe_redirect(SettingsPage::tabUrl('embeddings') . '#aipcf-embed-results'); exit; } /** * "Keep going": one batch per request, driven by the open tab. */ public static function ajaxStep(): void { if (!current_user_can('manage_options') || !check_ajax_referer('aipcf_embed_step', 'nonce', false)) { wp_send_json_error(['message' => __('You do not have permission to do this.', 'aipcf-ai-provider-for-cloudflare')], 403); } $r = Ingest::tick(true, 15); $c = IndexTable::counts(Settings::signature()); $state = State::get(); wp_send_json_success([ 'ran' => $r['ran'], 'progress' => $r['embedded'] + $r['unchanged'] + $r['deleted'] + $r['failed'] + $r['copied'] + $r['requeued'], 'left' => $c['pending'] + $c['stale'] + $c['deleting'] + ($state['reconcile'] !== null ? 1 : 0), 'indexed' => $c['indexed'], 'failed' => $c['failed'], 'paused' => $state['paused'], // The daily limit or daily budget is used up: final for today, not a pause. 'stopped' => $r['stopped'] === 'budget' ? $r['message'] : '', 'message' => $r['message'] !== '' ? $r['message'] : ($r['ran'] ? '' : $r['reason']), ]); } public static function handleMoveCancel(): void { self::guard('aipcf_embed_move_cancel'); $ok = Mover::cancel(); Queue::nudge(); self::notice( $ok ? 'success' : 'warning', $ok ? __('Move cancelled. What was copied goes back in the background, without neurons; related posts and search keep using the store they used before.', 'aipcf-ai-provider-for-cloudflare') : __('No move is under way.', 'aipcf-ai-provider-for-cloudflare') ); } public static function handleCleanupOld(): void { self::guard('aipcf_embed_cleanup_old'); $r = Mover::cleanup(); if (is_wp_error($r)) { /* translators: %s: error message */ self::notice('error', sprintf(__('Nothing was deleted: %s', 'aipcf-ai-provider-for-cloudflare'), $r->get_error_message())); } self::notice('success', __('The old vectors were deleted.', 'aipcf-ai-provider-for-cloudflare')); } public static function handleCleanupKeep(): void { self::guard('aipcf_embed_cleanup_keep'); Mover::keepOld(); self::notice('success', __('The old vectors are kept. Delete them in that store\'s own dashboard when you no longer need them.', 'aipcf-ai-provider-for-cloudflare')); } public static function handleEstimate(): void { self::guard('aipcf_embed_estimate'); Estimate::get(true); self::notice('success', __('Estimate updated.', 'aipcf-ai-provider-for-cloudflare'), EmbeddingsSettings::ESTIMATE_ANCHOR); } // ================================================================ helpers private static function guard(string $action): void { if (!current_user_can('manage_options')) { wp_die(esc_html__('You do not have permission to do this.', 'aipcf-ai-provider-for-cloudflare'), 403); } check_admin_referer($action); } /** * Queues an admin notice and returns to the tab, at the panel the action belongs to. */ private static function notice(string $type, string $message, string $anchor = EmbeddingsStatus::ANCHOR): void { set_transient('aipcf_notice_' . get_current_user_id(), ['type' => $type, 'message' => $message], 60); wp_safe_redirect(SettingsPage::tabUrl('embeddings') . '#' . $anchor); exit; } }