' . esc_html__( 'The sync report has expired. Run Sync content with AI again to see results.', 'ai-search' ) . '
';
} else {
$ok_count = count( $report['ok'] );
$failed_count = count( $report['failed'] );
if ( (int) $report['requested'] === 0 ) {
echo ''
. esc_html__( 'Nothing to do: every published item of the selected type is already synced. To force a re-sync, delete existing data in the Cache & Clean tab first.', 'ai-search' )
. '
';
} else {
$class = $failed_count === 0 ? 'notice-success' : ( $ok_count === 0 ? 'notice-error' : 'notice-warning' );
echo '';
printf(
/* translators: 1: number synced, 2: number failed, 3: number requested */
esc_html__( 'Sync finished: %1$d synced, %2$d failed (of %3$d selected).', 'ai-search' ),
(int) $ok_count,
(int) $failed_count,
(int) $report['requested']
);
echo '';
if ( ! empty( $report['stopped'] ) ) {
echo ' ' . esc_html__( 'Sync was stopped early because the provider returned an error that would repeat for every item. Fix the cause below and run again — items already synced are kept.', 'ai-search' );
}
echo '
';
}
if ( $failed_count > 0 ) {
// Group failures by error code so one root cause reads as one line.
$groups = [];
foreach ( $report['failed'] as $f ) {
$code = isset( $f['code'] ) ? (string) $f['code'] : 'unknown';
if ( ! isset( $groups[ $code ] ) ) {
$groups[ $code ] = [ 'message' => (string) $f['message'], 'titles' => [] ];
}
$groups[ $code ]['titles'][] = (string) $f['title'];
}
echo '' . esc_html__( 'Why items failed:', 'ai-search' ) . '
';
foreach ( $groups as $code => $g ) {
$titles = array_slice( $g['titles'], 0, 5 );
$more = count( $g['titles'] ) - count( $titles );
$summary = implode( ', ', $titles );
if ( $more > 0 ) {
/* translators: %d: number of additional items */
$summary .= ' ' . sprintf( esc_html__( 'and %d more', 'ai-search' ), (int) $more );
}
echo '' . esc_html( $code ) . ' — ' . esc_html( $g['message'] );
echo '
' . esc_html( $summary ) . ' ';
}
echo '
';
echo '
' . esc_html__( 'The same details are written to the PHP error log (wp-content/debug.log when WP_DEBUG_LOG is on).', 'ai-search' ) . '
';
}
if ( $ok_count > 0 ) {
echo '';
/* translators: %d: number of items */
printf( esc_html__( 'Show %d generated items', 'ai-search' ), (int) $ok_count );
echo '
';
foreach ( $report['ok'] as $title ) {
echo '- ' . esc_html( $title ) . '
';
}
echo '
';
}
}
}
?>
true, 'exclude_from_search' => false ], 'objects' );
unset( $ai_status_types['attachment'] );
$ai_status_rows = $wpdb->get_results(
"SELECT p.post_type,
COUNT(*) AS total,
SUM( CASE WHEN pm.post_id IS NULL THEN 0 ELSE 1 END ) AS indexed
FROM {$wpdb->posts} p
LEFT JOIN {$wpdb->postmeta} pm ON pm.post_id = p.ID AND pm.meta_key = '_ai_search_embedding'
WHERE p.post_status = 'publish'
GROUP BY p.post_type",
OBJECT_K
);
$ai_status_pending = 0;
// v1.35.0: per-model breakdown per post type. Posts embedded before 1.35
// do not have the _ai_search_embedding_model sidecar - they show up in
// the "unknown" bucket and get resolved the moment the user regenerates.
$ai_model_rows = $wpdb->get_results(
"SELECT p.post_type,
COALESCE( mm.meta_value, '' ) AS model,
COUNT(*) AS c
FROM {$wpdb->posts} p
INNER JOIN {$wpdb->postmeta} pm ON pm.post_id = p.ID AND pm.meta_key = '_ai_search_embedding'
LEFT JOIN {$wpdb->postmeta} mm ON mm.post_id = p.ID AND mm.meta_key = '_ai_search_embedding_model'
WHERE p.post_status = 'publish'
GROUP BY p.post_type, model"
);
$ai_status_by_model = [];
foreach ( (array) $ai_model_rows as $r ) {
$pt = (string) $r->post_type;
$model = (string) $r->model;
$bucket = 'unknown';
if ( 'text-embedding-3-large' === $model ) {
$bucket = 'large';
} elseif ( 'text-embedding-3-small' === $model ) {
$bucket = 'small';
}
if ( ! isset( $ai_status_by_model[ $pt ] ) ) {
$ai_status_by_model[ $pt ] = [ 'large' => 0, 'small' => 0, 'unknown' => 0 ];
}
$ai_status_by_model[ $pt ][ $bucket ] += (int) $r->c;
}
?>
|
|
|
|
name ] ) ? $ai_status_rows[ $ai_status_type->name ] : null;
$total = $row ? (int) $row->total : 0;
$indexed = $row ? (int) $row->indexed : 0;
$missing = max( 0, $total - $indexed );
$ai_status_pending += $missing;
if ( 0 === $total ) {
continue;
}
$models = isset( $ai_status_by_model[ $ai_status_type->name ] )
? $ai_status_by_model[ $ai_status_type->name ]
: [ 'large' => 0, 'small' => 0, 'unknown' => 0 ];
$ai_status_unknown_total += $models['unknown'];
?>
| labels->name ); ?> |
|
—';
} else {
$parts = [];
if ( $models['large'] > 0 ) {
$parts[] = '' . esc_html( number_format_i18n( $models['large'] ) ) . ' ' . esc_html__( 'on large', 'ai-search' );
}
if ( $models['small'] > 0 ) {
$parts[] = '' . esc_html( number_format_i18n( $models['small'] ) ) . ' ' . esc_html__( 'on small', 'ai-search' );
}
if ( $models['unknown'] > 0 ) {
$parts[] = '' . esc_html( number_format_i18n( $models['unknown'] ) ) . ' ' . esc_html__( 'unknown', 'ai-search' ) . '';
}
echo wp_kses(
implode( ' · ', $parts ),
[ 'strong' => [], 'span' => [ 'class' => true ] ]
);
}
?> |
0 ) {
echo '' . esc_html( number_format_i18n( $missing ) ) . '';
} else {
echo '' . esc_html__( 'Up to date', 'ai-search' ) . '';
}
?> |
0 ) : ?>
' . esc_html( number_format_i18n( $ai_status_unknown_total ) ) . ''
);
?>
0 ) : ?>
' . esc_html( number_format_i18n( $ai_status_pending ) ) . '' );
?>