/** * Style Customizer v2 — floating "Style with AI" launcher that turns a prompt into style * tokens via `POST {restBase}/{formId}/ai`. */ import React, { useEffect, useRef, useState } from 'react'; import { LuSend, LuSparkles, LuX } from 'react-icons/lu'; import { UPGRADE_URL } from './panes'; import { useStore } from './store'; import { Template } from './types'; const __ = ( window as any ).wp?.i18n?.__ || ( ( s: string ) => s ); const _n = ( window as any ).wp?.i18n?._n || ( ( single: string, plural: string, n: number ) => ( 1 === n ? single : plural ) ); const sprintf = ( window as any ).wp?.i18n?.sprintf || ( ( fmt: string, n: number ) => fmt.replace( '%d', String( n ) ) ); const apiFetch = ( window as any ).wp?.apiFetch; /** Daily-request usage snapshot the gateway now returns on every AI response. */ interface UsageInfo { remaining: number; limit: number; used: number; } // At/below this many remaining requests the count switches to a gentle amber warning. const USAGE_LOW_THRESHOLD = 3; /** "7 requests left today" — properly pluralized. */ const usageLabel = ( remaining: number ): string => sprintf( _n( '%d request left today', '%d requests left today', remaining, 'everest-forms' ), remaining ); /** Read the { remaining, limit, used } usage object off a raw AI response/error, or null. */ const readUsage = ( raw: any ): UsageInfo | null => { const u = raw && raw.usage; if ( u && 'number' === typeof u.remaining ) { return { remaining: u.remaining, limit: u.limit, used: u.used }; } return null; }; /** Full tooltip text for the credits pill. */ const usageTooltip = ( usage: UsageInfo ): string => 'number' === typeof usage.limit && usage.limit > 0 ? sprintf( /* translators: 1: remaining requests, 2: daily limit. */ __( '%1$d of %2$d AI requests left today · resets daily', 'everest-forms' ), usage.remaining, usage.limit ) : usageLabel( usage.remaining ); /** * The daily-request "credits" pill for the panel header — a sparkle, an `18/20` count, and a * slim meter that drains as requests are used. Translucent white on the purple header; turns * amber when the count runs low or is exhausted. */ const UsagePill: React.FC< { usage: UsageInfo | null; loading?: boolean } > = ( { usage, loading } ) => { if ( ! usage ) { if ( ! loading ) return null; // Skeleton — holds the pill's place while the first count loads. return (
); } const hasLimit = 'number' === typeof usage.limit && usage.limit > 0; const { remaining } = usage; const amber = remaining <= USAGE_LOW_THRESHOLD; const frac = hasLimit ? Math.max( 0, Math.min( 1, remaining / usage.limit ) ) : 1; const numColor = amber ? '#7a4b00' : '#fff'; const denColor = amber ? 'rgba(122,75,0,.7)' : 'rgba(255,255,255,.7)'; return (