# RapidTextAI — Developer Reference

This document describes the plugin's architecture, REST API, data structures, cron pipeline, and extension points so you can hook into or extend RapidTextAI.

---

## 1. Overview & architecture

RapidTextAI is a WordPress plugin with three user-facing surfaces:

| Surface | UI | Entry point |
| --- | --- | --- |
| Settings / Auto Blogging / Chatbots admin pages | React SPA (built with Vite) | `rapidtext-ai-text-block.php`, `rapidtextai-rest-api.php` |
| AI Article Generator (post editor meta box) | React | `rapidtext-ai-meta-box.php`, `src/app/App.tsx` |
| Chatbots front-end widget | JS + shortcode | `ext/chatbots/chatbots.php` |

The React admin app and the meta box are compiled into a single Vite bundle (`assets/js/app.js` + `assets/css/app.css`) that is shared by both surfaces.

### Key files

| File | Purpose |
| --- | --- |
| `rapidtext-ai-text-block.php` | Main bootstrap: menus, AJAX handlers, auto-blogging pipeline + cron, settings, block registration. |
| `rapidtext-ai-meta-box.php` | Post-editor meta box, `Generate Article` modal, article generation AJAX. |
| `rapidtext-ai-check.php` | Account/usage checking and admin notices. |
| `rapidtextai-openaihandler.php` | Shared LLM helpers (excerpt, taxonomy terms, tags). |
| `rapidtextai-rest-api.php` | `rapidtextai/v1` REST API, React app shell, campaign/chatbot save helpers. |
| `ext/campaign-assistant/campaign-assistant.php` | AI Campaign Assistant: SEO readers, context builder, LLM client, system prompt, finalize, AJAX SSE endpoint. |
| `ext/chatbots/chatbots.php` | Chatbot CRUD, DB table, shortcode, front-end widget, chat AJAX. |
| `assets/js/app.js`, `assets/css/app.css` | Compiled React bundle (generated by Vite — do not edit by hand). |

---

## 2. React build system

The React source lives in a **separate repo** (`ai-text-block-react`), not inside the plugin. Vite builds directly into the plugin's `assets/` folder.

From `vite.config.ts`:

```ts
const WP_PLUGIN_DIR = 'I:/wamp64php8/www/www/wp5/wp-content/plugins/ai-text-block'
```

- `build.outDir` → `{WP_PLUGIN_DIR}/assets`
- `emptyOutDir: false` — never deletes the plugin's other assets
- `entryFileNames: 'js/app.js'`, CSS → `css/app.css`
- a `closeBundle` plugin deletes the stray `assets/index.html` Vite emits

**Build command:**

```bash
cd i:/wamp64php8/www/figma/ai-text-block-react
npm run build
```

### App mount

`src/main.tsx` waits for `DOMContentLoaded` (or a complete document) and mounts:

| Root element | Component | Where it appears |
| --- | --- | --- |
| `#rapidtextai-app` | `AdminApp` | Settings, Auto Blogging, Chatbots pages |
| `#rapidtextai-root` | `App` (wraps `RapidTextAIMetaBox`) | Post editor meta box |

Both roots exist only on the pages that enqueue the bundle, so the mounts are guarded.

### PHP → JS config

`rapidtextai_react_app_root( $config )` prints the root div and localizes:

```js
window.rapidtextai_app = {
  page:       'settings' | 'auto-blogging' | 'chatbots',
  rest_url:   '.../wp-json/rapidtextai/v1',
  rest_nonce: <wp_rest nonce>,
  ajax_url:   '.../admin-ajax.php',
  ajax_nonce: <rapidtextai_assistant_nonce nonce>,
  app_url:    'https://app.rapidtextai.com',
  data:       {}
};
```

The React `api.ts` reads `window.rapidtextai_app` and sends `X-WP-Nonce: <rest_nonce>` on every REST request.

### Enqueue & module loading

- `rapidtextai_enqueue_admin_app( $hook )` enqueues `assets/css/app.css` and `assets/js/app.js` on any admin page whose hook contains `rapidtextai`.
- `rapidtextai_app_script_tag` filters `script_loader_tag` to load `app.js` as `type="module"` (required by the Vite output).

---

## 3. REST API (`rapidtextai/v1`)

Namespace: `rapidtextai/v1`. All routes use the shared permission callback:

```php
function rapidtextai_rest_permission() {
    return current_user_can( 'manage_options' );
}
```

All authenticated requests must include the header `X-WP-Nonce: <wp_rest nonce>`.

| Method | Route | Purpose | Returns |
| --- | --- | --- | --- |
| GET | `/settings` | Read API key status | `{ api_key, api_key_set }` |
| POST | `/settings/api-key` | Save API key | `{ message }` |
| GET | `/account` | Proxy account/usage info from app.rapidtextai.com | account object |
| GET | `/options` | Authors + categories for dropdowns | `{ authors, categories }` |
| GET | `/models` | List available models (proxied) | model array |
| GET | `/campaigns` | List all campaigns (adds `next_run`) | campaign array |
| GET | `/campaigns/{id}` | Single campaign | campaign object |
| POST | `/campaigns` | Create or update a campaign (`id` present = update) | `{ id, message }` |
| POST | `/campaigns/{id}/toggle` | Enable/disable + reschedule cron | `{ enabled, message }` |
| DELETE | `/campaigns/{id}` | Delete campaign + clear cron | `{ message }` |
| POST | `/campaigns/{id}/generate` | Generate one post now | `{ message }` |
| POST | `/improve-topics` | Rewrite topics into advanced format via AI | `{ message, improved_topics }` |
| GET | `/logs` | Read auto-blogging logs | `{ logs }` |
| DELETE | `/logs` | Clear logs | `{ message }` |
| GET | `/chatbots` | List chatbots (normalized) | chatbot array |
| GET | `/chatbots/{id}` | Single chatbot | chatbot object |
| POST | `/chatbots` | Create/update chatbot | `{ id, message }` |
| DELETE | `/chatbots/{id}` | Delete chatbot | `{ message }` |
| GET | `/assistant/context` | Campaign Assistant site context (cached) | context object |
| POST | `/assistant/finalize` | Finalize an assistant-created campaign | `{ id, message }` |

Campaign IDs are strings of the form `campaign_<timestamp>[_<rand>]`; chatbot IDs are integers.

---

## 4. Data structures

### 4.1 Auto-blogging campaigns

Stored in the option **`rapidtextai_auto_blogging_campaigns`** as an associative array keyed by campaign id.

```php
array(
  'id'                     => 'campaign_1712345678',
  'name'                   => 'Tech Blog Campaign',
  'enabled'                => 1,
  'schedule'               => 'daily',        // hourly | twicedaily | daily | weekly
  'post_status'            => 'draft',        // publish | draft | pending
  'post_author'            => 1,              // user id
  'topics'                 => "Topic: ...; Keywords: ...;\nTopic: ...",
  'model'                  => 'gpt-3.5-turbo',
  'tone'                   => 'informative',  // informative | conversational | formal | friendly | persuasive
  'post_category'          => array( 3, 5 ),  // category term ids
  'generate_tags'          => 1,
  'tags_count'             => 5,
  'excerpt_length'         => 55,
  'taxonomy_limit'         => 5,
  'include_images'         => 1,
  'include_featured_image' => 1,
  'max_images'             => 5,
  'enable_logging'         => 1,
  'created'                => '2024-01-01 10:00:00',
  'updated'                => '2024-01-02 10:00:00',
)
```

The save routine is `rapidtextai_rest_save_campaign( $params )` in `rapidtextai-rest-api.php`. It sanitizes every field, generates ids for new campaigns, updates the option, and (re)schedules cron. A single campaign is also mirrored into the legacy option `rapidtextai_auto_blogging` for backward compatibility.

### 4.2 Chatbots

Stored in the DB table **`{prefix}rapidtextai_chatbots`** (created with `dbDelta` on activation and lazily on first use):

```
id              mediumint(9)   PRIMARY KEY AUTO_INCREMENT
name            varchar(255)
description     text
model           varchar(100)
theme           varchar(50)    modern | classic | minimal | dark
status          varchar(20)    active | inactive
system_message  text
welcome_message text
settings        longtext       JSON-encoded appearance/behavior settings
knowledge_base  longtext       serialized array of {title, content} docs
tools           longtext       serialized array of tool definitions
created_at      datetime
updated_at      datetime
```

The `settings` column holds:

```php
array(
  'primary_color'    => '#007cba',
  'secondary_color'  => '#005177',
  'text_color'       => '#ffffff',
  'background_color' => '#f9f9f9',
  'position'         => 'bottom-right', // bottom-left | top-right | top-left
  'size'             => 'medium',       // small | medium | large
  'auto_open'        => 0,
  'auto_open_delay'  => 3000,
  'show_avatar'      => 1,
  'avatar_url'       => '',
  'max_tokens'       => 2000,
  'temperature'      => 0.7,
)
```

Each `tool` entry: `{ name, method, description, url, headers, response_field, parameters }` (`parameters` is JSON Schema as a JSON string).

The chatbot shortcode is `[rapidtextai_chatbot id="123"]`.

---

## 5. Cron & the auto-blogging pipeline

**Per-campaign schedule hook:** `rapidtextai_auto_blogging_cron_{campaign_id}`

- Scheduled by `rapidtextai_schedule_campaign_cron( $campaign_id, $campaign )` using `wp_schedule_event()` with the campaign's `schedule` (hourly/twicedaily/daily/weekly).
- Re-registered on every page load by `rapidtextai_register_campaign_cron_hooks()` (hooked to `plugins_loaded`), because dynamic `add_action()` calls would otherwise be lost between WP-Cron invocations.
- Cleared on delete/toggle/save via `wp_clear_scheduled_hook()`.
- Legacy hook `rapidtextai_auto_blogging_cron` still maps to `rapidtextai_generate_auto_blog_post()` for migrated installs.

**Main pipeline** (draft → publish):

| Hook | Handler | Role |
| --- | --- | --- |
| `rapidtextai_auto_blogging_cron_{id}` | `rapidtextai_generate_auto_blog_post()` | Streams the draft from the campaign topic/model; schedules Cron 2. |
| `rapidtextai_refine_and_publish` | `rapidtextai_refine_and_publish_handler()` | Polish → heading map → title → create post (draft); schedules Cron 3. |
| `rapidtextai_finalize_post` | `rapidtextai_finalize_post_handler()` | Tags, images, featured image, final status (publish/draft/pending). |

**Agent Mode (meta box) uses single-event cron** with a transient (key passed as the event argument) to carry state between runs:

| Hook | Handler |
| --- | --- |
| `rapidtextai_polish_post` | `rapidtextai_polish_post_handler()` |
| `rapidtextai_optimize_headings` | `rapidtextai_optimize_headings_handler()` |
| `rapidtextai_generate_title` | `rapidtextai_generate_title_handler()` |
| `rapidtextai_create_post` | `rapidtextai_create_post_handler()` |
| `rapidtextai_finalize_post` | `rapidtextai_finalize_post_handler()` |

Each handler schedules the next with `wp_schedule_single_event( time() + 2, ... )`.

Deactivation hook `rapidtextai_deactivate_auto_blogging()` clears all scheduled cron hooks.

---

## 6. Extension points

### 6.1 Add support for another SEO plugin

Implement the `RapidTextAI_SEO_Reader` interface (`ext/campaign-assistant/campaign-assistant.php`):

```php
interface RapidTextAI_SEO_Reader {
    public function id();                 // string slug, e.g. 'myseo'
    public function label();              // human label, e.g. 'My SEO'
    public function is_active();          // bool — is the plugin loaded?
    public function site_keywords();      // array of site-wide keywords
    public function meta_templates();     // array( 'title' => ..., 'description' => ... )
    public function post_keyphrase( $post_id );  // string focus keyphrase
    public function post_seo_score( $post_id );  // int|null
}
```

Then register it in `RapidTextAI_SEO_Reader_Factory::detect()` by adding an instance to the `$readers` array. The factory returns the first active reader (priority order: Yoast → Rank Math → AIOSEO → SEOPress).

The detected reader feeds `Campaign_Context_Builder`, which supplies the Campaign Assistant system prompt.

### 6.2 Add another LLM provider

Implement `RapidTextAI_LLM_Client`:

```php
interface RapidTextAI_LLM_Client {
    /**
     * @param array    $messages  OpenAI-format messages.
     * @param callable $on_chunk  Optional callback( $delta, $accumulated ).
     * @param array    $options   Optional overrides (model, temperature, max_tokens).
     * @return string  Accumulated completion text.
     */
    public function stream_chat( array $messages, $on_chunk = null, array $options = array() );
}
```

The bundled implementation `RapidTextAI_LLM_Client_HTTP` streams SSE from:

```
https://app.rapidtextai.com/openai/v1/chat/completions-stream?gigsixkey={key}
```

Swap the client used in `rapidtextai_assistant_chat_callback()` to plug in a different provider.

### 6.3 Campaign Context Builder

```php
class Campaign_Context_Builder {
    const CACHE_KEY = 'rapidtextai_assistant_context';
    const CACHE_TTL = HOUR_IN_SECONDS; // 1 hour

    public function get_context();   // returns cached context, building if needed
    public function invalidate();    // delete_transient( CACHE_KEY )
    public function build();         // array( site, seo, existing_topics, taxonomy, past_campaigns )
}
```

Context shape:

```
site:            title, description, language, seo_plugin, industry
seo:             site_keywords, meta_template
existing_topics: recent 50 posts (title, keyphrase, seo_score, categories, tags, date)
taxonomy:        categories/tags (name + count, up to 100 each)
past_campaigns:  id, name, topics, schedule, model, tone
```

`rapidtextai_assistant_finalize_campaign()` calls `invalidate()` after a successful save so the next chat sees fresh data.

### 6.4 Assistant prompt & finalize

- `rapidtextai_assistant_system_prompt( array $context )` builds the system prompt (truncates existing topics > 30 and tags > 200 to bound size).
- `rapidtextai_assistant_finalize_campaign( $params )` maps assistant JSON output to the campaign schema:
  - With `campaign_id` of an existing campaign → **merge** (preserve all settings, override only `name`, `topics`, and any provided `schedule`/`tone`/`model`/`post_status`).
  - Without → **create new**, saved disabled (`enabled = 0`) for review.
  - Delegates persistence to `rapidtextai_rest_save_campaign()`.

---

## 7. AJAX endpoints (`admin-ajax.php`)

| Action | Auth | Notes |
| --- | --- | --- |
| `rapidtextai_generate_content` | public (+nopriv) | Classic content block generation |
| `rapidtextai_generate_content_block` | public (+nopriv) | Gutenberg block generation |
| `rapidtextai_save_api_key` | admin | Legacy key save |
| `rapidtextai_generate_article` | admin | Article generator (meta box) |
| `rapidtextai_improve_topics` | public (+nopriv) | Improve topics |
| `rapidtextai_get_excerpt` | public (+nopriv) | Generate excerpt |
| `rapidtextai_get_taxonomy_terms` | public (+nopriv) | Fetch taxonomy terms |
| `rapidtextai_get_tags` | public (+nopriv) | Fetch tags |
| `rapidtextai_get_featured_image` | public (+nopriv) | Find featured image |
| `rapidtextai_upload_image_from_url` | public (+nopriv) | Sideload an image |
| `rapidtextai_get_models` | admin | Chatbot model list (chatbots.php) |
| `rapidtextai_chatbot_message` | public (+nopriv) | Front-end chat message |
| `rapidtextai_assistant_chat` | admin | **SSE stream** for the Campaign Assistant |
| `rapidtextai_dismiss_usage_notice` | admin | Dismiss usage-limit notice |

### 7.1 Campaign Assistant SSE (`rapidtextai_assistant_chat`)

- Requires `manage_options`.
- Validates nonce `rapidtextai_assistant_nonce` from POST field `nonce`.
- Accepts `messages` (JSON array, OpenAI format).
- Prepends the system prompt (built from `Campaign_Context_Builder`).
- Disables output buffering/compression, sends `Content-Type: text/event-stream`, then streams chunks as:

```
data: {"content":"..."}

data: [DONE]
```

---

## 8. Security conventions

- **REST:** every route uses `rapidtextai_rest_permission()` (`manage_options`) + WordPress nonce via `X-WP-Nonce`.
- **AJAX:** capability checks (`current_user_can('manage_options')` for admin actions) and `wp_verify_nonce()`.
- **Input:** all persisted values run through `sanitize_text_field` / `sanitize_textarea_field` / `sanitize_hex_color` / `esc_url_raw` / `intval` / `floatval`. Topic text and prompts are passed to the LLM as-is by design.
- **Output:** `wp_json_encode()` with `JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT` for the localized config; `esc_url_raw()` for URLs.
- **LLM transport:** cURL with a 300 s timeout; the stream endpoint is called server-side so the API key is never exposed to the browser.

---

## 9. Useful hooks to build on

| Hook | Type | Notes |
| --- | --- | --- |
| `rapidtextai_auto_blogging_cron_{id}` | action | Per-campaign schedule (hourly/twicedaily/daily/weekly) |
| `rapidtextai_refine_and_publish` | action | Arg: transient key |
| `rapidtextai_finalize_post` | action | Arg: post id |
| `rapidtextai_polish_post` | action | Agent Mode stage (arg: transient key) |
| `rapidtextai_optimize_headings` | action | Agent Mode stage (arg: transient key) |
| `rapidtextai_generate_title` | action | Agent Mode stage (arg: transient key) |
| `rapidtextai_create_post` | action | Agent Mode stage (arg: transient key) |
| `script_loader_tag` | filter | Loads `app.js` as an ES module |
| `admin_enqueue_scripts` | action | Enqueues the React bundle on `rapidtextai` pages |
| `rest_api_init` | action | Registers `rapidtextai/v1` routes |

`rapidtextai_react_app_root( $config )` accepts a `$config` array merged over the defaults, so you can localize extra page data into `window.rapidtextai_app` when adding new React pages.
