=== MoonPress Chat ===
Contributors: norml
Tags: chatbot, ai, assistant, chat, live chat
Requires at least: 6.2
Tested up to: 7.1
Requires PHP: 7.4
Stable tag: 5.2.0
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

White-label AI chat assistant: bring your own LLM API key (Claude, OpenAI, Gemini, OpenRouter) and get a knowledge-based chat widget on your site.

== Description ==

MoonPress Chat adds an AI-powered chat assistant to your WordPress site. You connect your own LLM provider API key, teach the assistant about your business in the knowledge base, and the widget answers visitors' questions grounded in that knowledge — it does not invent facts you never gave it.

**Features**

* **Bring your own key** — works with Anthropic (Claude), OpenAI, Google Gemini, and OpenRouter. Pick the active provider and model in Settings; keys are stored encrypted with your site's security keys, write-only, and never displayed or sent back to the browser.
* **Knowledge base** — describe your business, services and FAQs in wp-admin; the assistant answers only from what you provided.
* **Conversations inbox** — read every dialog, filter leads, and take over a conversation as a human operator when needed.
* **Lead capture** — the assistant collects contact details into a lead list you can process from wp-admin.
* **Templates and reports** — reusable answer templates and usage statistics.
* **Honest fallback** — with no key configured (or the provider unreachable) the widget says so and offers your contact channels instead of hallucinating.

**Privacy**

Visitor conversations are stored in your own WordPress database. No data is sent anywhere except to the LLM provider you explicitly configured (see External services below). The plugin sends nothing to the plugin authors. The widget also records interaction events — which button a visitor pressed, and when (a topic, the call or request buttons, opening the widget, switching language) — with no message text and no page address; they are stored in your database with the same anonymous retention as the transcripts and are deleted by the same setting. Weekly totals — counts only, no transcripts and no visitor identifiers — are kept indefinitely by default so that reports stay comparable over time; a setting under Anonymous conversations can limit them to one, two or five years.

= External services =

This plugin connects to the API of the LLM provider **you select and configure** in its settings. It sends the visitor's chat messages, the conversation history of the current session, and the knowledge-base context you authored to that provider in order to generate the assistant's replies. No request carrying visitor data is made until you enter an API key and enable the widget.

Depending on which provider you choose, data is sent to exactly one of:

* **Anthropic (Claude)** — [Terms of Service](https://www.anthropic.com/legal/commercial-terms), [Privacy Policy](https://www.anthropic.com/legal/privacy)
* **OpenAI** — [Terms of Use](https://openai.com/policies/terms-of-use), [Privacy Policy](https://openai.com/policies/privacy-policy)
* **Google Gemini API** — [Terms of Service](https://ai.google.dev/gemini-api/terms), [Privacy Policy](https://policies.google.com/privacy)
* **OpenRouter** — [Terms of Service](https://openrouter.ai/terms), [Privacy Policy](https://openrouter.ai/privacy)

Separately, and unrelated to the widget: whenever **OpenRouter** is your active provider, this plugin also fetches OpenRouter's public model list — `https://openrouter.ai/api/v1/models` — from your WordPress admin area, so Settings can offer you a current, real list of models to choose from instead of a hardcoded few. This request carries no site data and no API key (it is a plain, unauthenticated GET); it fires when you open Settings, is cached for 12 hours so it does not repeat on every load, and backs off for 5 minutes if OpenRouter is unreachable. It never runs on the front end and never involves a visitor.

You are responsible for ensuring that sending visitor messages to your chosen provider complies with the privacy regulations that apply to your site, and for disclosing it in your site's privacy policy.

= Source code and build =

The plugin ships two separate codebases, built two different ways:

* **The visitor-facing widget** (`assets/widget.js`, `assets/widget.css`) is hand-written, plain JavaScript and CSS. There is no build step: the file in the zip is exactly the file your visitors' browsers download.
* **The admin interface** (Settings, Conversations, Knowledge base, Templates, Reports) is a React app built with Vite. Its full source ships in the zip under `admin-ui/src`; `admin-ui/dist`, also in the zip, is the compiled output WordPress actually enqueues — the release workflow builds it on Linux (Node 20) from that same source. To rebuild it yourself: `cd admin-ui && npm ci && npm run build`.

== Installation ==

1. Upload the plugin to `/wp-content/plugins/moonpresschat/`, or install it through the WordPress plugins screen.
2. Activate the plugin through the **Plugins** screen.
3. Open **MoonPress Chat → Settings**, pick your LLM provider, and paste your API key (the Test button verifies it with a real minimal request).
4. Fill in the knowledge base so the assistant has something to answer from.
5. Toggle **Show on site** — or preview first on any page with `?chatbot=1`.

== Frequently Asked Questions ==

= Do I need my own API key? =

Yes. The plugin is bring-your-own-key: you create a key with Anthropic, OpenAI, Google, or OpenRouter and paste it into the settings. You pay the provider directly for usage; the plugin adds no markup and has no subscription.

= Where are my keys stored? =

In your WordPress database (`wp_options`), encrypted with your site's security keys (the salts in `wp-config.php`) and write-only: the admin UI never displays a stored key and the REST API never returns one. Keys are decrypted only server-side, at the moment the plugin calls the provider you configured. A database backup or export alone does not reveal them. If you regenerate the security keys, or restore the database under a different `wp-config.php`, the stored keys can no longer be read: Settings → Connections tells you which provider is affected and you paste the key again. Nothing is deleted automatically.

= What happens if the key is missing or the provider is down? =

The widget stays functional and honest: it tells the visitor a human will follow up and offers your configured contact channels. It never fabricates answers. When the provider refuses your key, reports that the account is out of credit, or no longer offers the chosen model, the assistant switches itself off after the first such failure: visitors see the offline text, you receive one e-mail, and Settings shows a banner naming the cause. It never switches to another provider or spends from another account on its own. Fix the key and press "Test this model", or pick another provider or model, and it comes back.

= Does the plugin send any data to the plugin authors? =

No. The only external traffic is to the LLM provider you configured.

== Screenshots ==

1. The chat widget on your site: greeting, topic chips, an answer grounded in your knowledge base, a rating and a "Leave a request" button.
2. Settings → AI providers: connect a key, then pick the model from a live, searchable list — newest first, recommended models pinned.
3. A conversation in the inbox: the captured lead, the assistant's replies and a staff reply after taking the conversation over.

== Changelog ==

= 5.2.0 =
* When the active provider refuses your key, reports that the account is out of credit, or no longer offers the chosen model, the assistant now switches itself off instead of failing on every message: visitors see the offline text with your contact channels, you receive one e-mail, and Settings shows a banner naming the cause. It comes back the moment "Test this model" succeeds again or you save another provider, model, or key. It never switches to another provider on its own.
* Provider API keys are now stored encrypted with your site's security keys (the salts in wp-config.php), so a database backup or export alone no longer reveals them. Existing keys are converted on the first use after the update; nothing to do.
* If your security keys are ever regenerated, the stored keys can no longer be decrypted: Settings → Connections says so for the affected provider and you paste the key again. Nothing is deleted automatically.

= 5.1.2 =
* Cheaper answers through OpenRouter with Claude models: the plugin now asks OpenRouter for Anthropic's prompt-cache discount on the knowledge block, the same discount the direct Anthropic connection has always used. In our measurement the same conversation cost about half of what it did before.
* Models that only work through a provider's batch API (OpenRouter's ":batch" entries) no longer appear in the model list — choosing one returned an error on every message.
* Reasoning models (GPT-5 family, Gemini Pro) no longer fall silent: the answer budget was too small for them to finish thinking, so visitors saw the fallback text while the tokens were still billed. The budget is now large enough for them to answer; short-answer models are unaffected.

= 5.1.1 =
* The model picker is now a searchable dropdown: one field that shows your current model, opens a list when you click or type, and filters by model name or id as you go — recommended models first, then the newest. Your selection only changes when you pick a row, so typing to look around never touches the saved model or the Test button.
* Long lists show the first fifty rows with a count and a hint to keep typing; each row shows the model's name with its id underneath.
* The note under Refresh says which list you are looking at — fetched with your key, the public OpenRouter list, or the last one fetched.

= 5.1.0 =
* The model list under Settings → Providers now comes from your provider's own API once a key is saved — Claude, OpenAI, Gemini and OpenRouter alike — so new models appear without a plugin update and models your key cannot use are not offered. Newest models are listed first; with more than ten, a search box appears and matches ids as well as display names. OpenRouter's recommended picks stay pinned at the top.
* If a provider cannot be reached or refuses the key, the built-in list is shown with a one-line reason and Refresh retries; a refused key is named as such, including the way Gemini reports it.
* The built-in Gemini list was out of date for new API keys — Google closed the 2.5 models to new users and two of the listed ids never existed. It now offers gemini-3.8-flash, gemini-3.6-flash and the "latest" aliases.
* This is the first version published on wordpress.org; 5.0.0 was the reviewed submission.

= 5.0.0 =
* The plugin is now called MoonPress Chat. Its folder, main file and text domain changed to `moonpresschat` (`moonpresschat/moonpresschat.php`), so WordPress sees this version as a new plugin: install it next to the old one, deactivate the old copy, activate MoonPress Chat, then delete the old folder. Settings, knowledge base, conversations, requests, ratings and reports are all kept: the internal data names (options, tables, scheduled events) now carry the `moonpresschat_` prefix and are migrated automatically the first time the new plugin runs — nothing to do. Visitors' browser-side conversation state starts fresh after the update, so the consent notice shows once more.
* The admin menu is labelled MoonPress Chat (it was the generic "Chatbot"). Everything else that used to carry the old name follows: the REST namespace is `moonpresschat/v1`, admin menu addresses are `admin.php?page=moonpresschat…` (old bookmarks need updating), widget and admin CSS classes are `moonpresschat-*`.
* If you use the paid add-on, update it to 0.3.0 or newer before switching — earlier versions look for the old folder.
* The setup API additionally reports a stable `plugin_slug` next to the display name, and the temporary setup credential is now labelled "MoonPress Chat setup".

= 4.8.0 =
* A weekly summary e-mail. Every Monday at 08:00 in your site's time zone you get last week's numbers — conversations and how many of them asked a question, requests left, helpful and wrong answers, failed replies, who took over, the busiest day, and what visitors pressed most — with the change against the week before. Numbers only, never transcripts; the daily transcript e-mail is unchanged.
* It is on by default and goes to the same address as the daily e-mail. Turn it off under Chatbot → Notifications, where the new switch sits next to the daily one; Reports says who gets it and when the next one goes out.
* A quiet week is still reported, with zeros, so a missing e-mail always means something is wrong rather than "nothing happened". Weeks counted after some transcripts were already deleted say so in the e-mail.
* Note: the send time is fixed when the schedule is created, so after a daylight-saving change it arrives an hour earlier or later until the plugin is deactivated and activated again.

= 4.7.0 =
* Reports gain a "By week" table: the last twelve weeks and the week in progress — conversations, how many of them asked a question, requests left, the share of helpful answers and failed replies. A week is counted the moment it closes and then never changes, so the numbers you sent a client last quarter still say the same thing after old conversations have been deleted under the retention setting. Weeks counted after some of their transcripts were already gone are marked as incomplete.
* History is filled in from the conversations, requests and ratings already in your database within minutes of updating; a short notice shows on Reports while that runs.
* New setting under Anonymous conversations: how long to keep the weekly totals — forever by default; they hold counts only, no transcripts.
* Updating creates one new table (the weekly totals); as before, if the database cannot create it the plugin keeps retrying on later visits instead of silently giving up.

= 4.6.0 =
* Reports now show what visitors actually press. Every topic button, the "Leave a request" and call buttons, the home-screen cards, help topics, opening the widget, starting a new chat, switching language and dictating a message are counted by the button — not by the text on it — so renaming a topic no longer splits its numbers, and buttons that were never counted before now are. "What visitors used" lists them with the name your visitors saw.
* These interaction events hold no message text and no page address, are stored in your own database, and are removed by the same anonymous-conversation retention setting as the transcripts (the retention note now reads "… messages, … events"). Nothing is recorded before a visitor accepts the AI notice, when one is shown.
* Updating creates one new table; if the database cannot create it, the plugin keeps retrying on later visits instead of silently giving up.

= 4.5.1 =
* You can rate answers again — the thumbs had stopped appearing after the multi-provider update; a "not helpful" with a comment is now counted once.

= 4.5.0 =
* Notifications and contact settings now have their own page: the daily transcript e-mail, the lead notification e-mail, the contact phones and what the assistant asks visitors for live under Chatbot → Notifications, with their own Save button. Settings keeps everything about how the assistant behaves and looks.
* The WordPress menu and the plugin's own sidebar list the pages in the same order: Start here, Conversations & leads, Setup, Knowledge base, Templates, Reports, Settings, Notifications. Start here still appears only until the install is configured.

= 4.4.1 =
* Reasoning models (for example Kimi or DeepSeek through OpenRouter) no longer fail the connection test with "Empty answer from the provider". The test gave them too little room to think before answering; it now leaves room, and a reply that is cut short is reported as cut short rather than as a broken key.
* Each provider row now has its own Save key button, right where you typed the key. Saving one row no longer clears a key you were still typing in another, and the hints in a row point at that row's own button.
* Test connection on a provider whose key comes from wp-config.php now says so: the constant in wp-config.php still wins over anything saved on this page.
* The plugin no longer queries its own tables before checking they exist, so a site that loads the plugin without activating it (staging copies, the plugin directory's own checks) gets no database errors in its logs.
* Internal: the conversations query is written so that the plugin directory's automated review can verify it is prepared. No change in behaviour.

= 4.4.0 =
* A provider can only be made active once you have connected a key to it, so you no longer pick one and find out afterwards that it cannot answer.
* The model list is real. Anthropic, OpenAI and Google show their current generation with the older ones one click away; OpenRouter's list is fetched from its own catalogue, searchable, with a short recommended set on top. If that catalogue cannot be reached the plugin offers its built-in list and says so rather than showing you nothing.
* Test now checks the model you actually chose, and sits next to it. The result is remembered, so changing the model honestly stops the plugin calling that combination verified.
* Each provider row says which provider it is and whether its key is saved, and the removal hint appears only where there is a saved key to remove.
* The settings page tells you when you have unsaved changes, and a key you have typed but not yet saved says so instead of looking connected.
* Removing the key from the provider currently in use is reported to you instead of the site being quietly switched to another provider. This also applies to the setup assistant, where clearing a key used to hand the site to whichever provider is configured by default.

= 4.3.0 =
* New: a setup assistant can now configure the plugin for you without ever being told your WordPress password. You approve a named, temporary connection in WordPress itself, and it can reach nothing on your site except the plugin's own setup steps.
* That connection really is temporary. It stops working after two hours, or after thirty minutes of silence, or the moment setup finishes — whichever comes first — and the plugin cleans it up for you rather than leaving it in your account. You can also revoke it yourself at any time from Users → Profile.
* Nothing goes live without you. Changes are checked and shown to you before anything is written, the last set of changes can be undone in one step, and the widget only starts appearing to visitors after you explicitly say go.
* Your provider API key stays where you put it: the setup steps can set it, but never read it back, never copy it anywhere else, and never include it in the undo history.
* On a WordPress multisite network the assisted setup is switched off and says so, because WordPress shares application passwords across the whole network and the temporary connection could not be kept to one site. Configure the plugin from the admin screens there instead.
* Deactivating the plugin now clears the last of its scheduled background jobs instead of leaving one behind.

= 4.2.3 =
* Every database query now binds its table name through WordPress's own identifier placeholder instead of building the name into the query text.
* The visitor widget script ships formatted for reading, and the readme now says where each part of the source lives and how the admin interface is built.

= 4.2.2 =
* The admin menu no longer offers editors pages they cannot open.
* A request left as a longer sentence now has the phone number picked out of it instead of being dropped, and a mistyped e-mail address is never quietly corrected into a different working one.
* Applying a preset or an AI draft clears leftover knowledge fields from earlier versions of the plugin.

= 4.2.1 =
* Fixed: a site that deliberately turns the AI disclosure notice off is no longer treated as unfinished — it stays configured, keeps its normal menu, and is not sent back to the "Start here" setup page.

= 4.2.0 =
* New: a "Start here" page greets a fresh install with three ways to set the assistant up — the setup skill, your own provider key, or letting the AI fill the knowledge base — and a checklist of what is still missing before you go live.
* The old onboarding notices are gone: the page reads your actual settings instead, so it never nags a site that is already configured.
* Providers now remember a successful connection test, and stop claiming success once the key, provider or model changes.

= 4.1.1 =
* The consent notice greets first-time visitors in their own language whenever the site has it configured — a warning nobody can read is not a disclosure. Detected from the browser; only languages you actually serve are used, and a visitor's saved choice always wins.

= 4.1.0 =
* The assistant now asks for whichever contact detail your business actually uses. A site with no phone number on file asks for an e-mail address instead of a phone, and the widget notices an e-mail the same way it used to notice a phone number.
* Visitor e-mails are stored with requests and shown in the admin panel alongside phone numbers.
* Fixed: requests left in the chat were silently dropped. A request now needs just one valid contact detail, and the name is optional.
* The public request endpoint is rate-limited per IP.
* The daily digest and the new-request notification are sent in your site's language. Both were previously sent in Russian regardless of the site.
* The daily digest now counts its 24 hours in your site's time zone, so conversations are no longer reported twice or missed near midnight.
* Accepting the AI notice is remembered: it no longer reappears in a new browser tab or when you start a new chat. Editing the notice text asks visitors again, on purpose.

= 4.0.1 =
* Compatibility: tested up to WordPress 7.1.

= 4.0.0 =
* The widget's styles and scripts now load through WordPress's standard asset system (enqueued files instead of inline code), so caching and optimization plugins can handle them like any other asset.
* All internal names now carry the plugin's own prefix; your existing settings, knowledge base and conversations are migrated automatically on update — nothing to do.
* Hardened the public chat endpoints: stricter validation of what the widget may write, rate limits per visitor and per address, and chat session ids are now 128-bit cryptographically random.

= 3.11.0 =
* The widget can start a new conversation: the launcher now lands on your conversation list whenever you have one, anonymous visitors get their own conversation as a single-item list, and starting a new chat keeps your earlier consent — you're never asked to accept the notice twice.

= 3.10.0 =
* The whole admin panel now runs on the new React interface: Knowledge base, Templates and Reports join Conversations and Settings in the same fast app with a shared sidebar. The old page renderers are removed; apart from the refreshed look of those three pages, nothing changes in how the plugin works.

= 3.9.1 =
* The chat avatar now uses your own logo — your theme logo or site icon — instead of a bundled image, and falls back to a neutral icon that follows your header colours.
* Header text stays readable: pick any header colour and the title, status line and controls switch between light and dark automatically.
* "Analyze my site" no longer times out on larger sites: it reads your pages in short batches and picks up where it left off, and skips login, cart and legal pages instead of loading them.
* The consent notice for regulated industries no longer repeats the same sentence twice.
* Security hardening: the plugin no longer runs any system command to look up an API key. Keys come from Settings, a wp-config constant or an environment variable, as before.


= 3.9.0 =
* The assistant's safety rules are now visible and editable in the Knowledge base as a pre-filled "Hard rules" field — with the built-in floor still guaranteed even if the field is cleared.

= 3.8.2 =
* The assistant's boundaries now follow your configured topics exactly: adjacent-industry questions your knowledge doesn't cover are deflected to your team instead of improvised.

= 3.8.1 =
* The assistant now firmly stays on topic: unrelated requests (code, homework, general knowledge) are politely declined instead of answered.

= 3.8.0 =
* Developer hooks: filters and actions so add-ons can extend the Q&A bank, language resolution and get notified of new messages and leads.

= 3.7.0 =
* Multilingual widget: add languages in Settings, the widget follows the page language, and visitors can switch languages on the fly.

= 3.6.1 =
* Directory-review readiness: hardened output escaping in the admin screens and safer database queries throughout.

= 3.6.0 =
* Widget appearance settings: pick your accent color, upload a logo and header image, choose the widget's corner and launcher size — all from Settings.

= 3.5.2 =
* Widget header spacing matches the design: clear gap between the greeting and the first card.

= 3.5.1 =
* Widget layout polish: correct card insets, greeting position and message alignment.

= 3.5.0 =
* A completely redesigned chat widget: cleaner cards, a customizable header, and a smoother chat view. Colors, logo and position are configurable (settings UI arrives next).

= 3.4.2 =
* Analyze my site now shows the step checklist with a live spinner the moment you press Generate.

= 3.4.1 =
* Applying a preset or a site analysis now refreshes the assistant's data-privacy instructions, keeping them accurate and in the right language.

= 3.4.0 =
* The assistant, widget and all built-in texts now ship in English and follow your site's language setting.

= 3.3.1 =
* Analyze my site now reads your pages without the chat widget's own text, and generation reliably follows your site's language.

= 3.3.0 =
* Analyze my site: generate a personalized starter knowledge base from your own pages, with admin review before anything is applied.

= 3.2.0 =
* AI disclosure built in: consent gate and disclaimer ship enabled by default, with industry-appropriate wording per preset.

= 3.1.0 =
* Industry presets: 13 one-click starting-knowledge templates + the Setup screen.

= 3.0.0 =
* The plugin was renamed and moved to a new folder and main file; reactivate it after updating.

= 2.1.0 =
* Removed all built-in demo knowledge content; fresh installs start with an empty knowledge base until you fill it in.

= 2.0.0 =
* White-label release: bring-your-own-key onboarding, neutral factory state.
* Multi-provider LLM support: Anthropic, OpenAI, Google Gemini, OpenRouter — active provider + model selection, write-only keys, real-request key test.
* New React (shadcn/ui) admin: Settings and Conversations, with operator takeover.
* Honest no-key/offline fallback in the widget.

== Upgrade Notice ==

= 5.2.0 =
The assistant switches itself off when your key is refused or out of credit (one e-mail, a banner naming the cause) and never moves to another provider on its own. Keys are now stored encrypted with your site's security keys; existing keys convert automatically.

= 5.1.2 =
Cheaper Claude answers through OpenRouter (prompt caching), batch-only models hidden from the list, reasoning models no longer cut off mid-thought. No settings change needed.

= 5.1.1 =
The model picker became a searchable dropdown; searching no longer looks like it changes your selection. No settings change needed.

= 5.1.0 =
Model lists are now fetched live from your provider with your saved key — newest first, searchable once there are more than ten. No settings change is needed and your chosen model stays.

= 5.0.0 =
Renamed to MoonPress Chat: the folder and main file moved to moonpresschat/moonpresschat.php, so this version installs alongside the old one. Deactivate the old plugin, activate MoonPress Chat, then delete the old folder. All data is kept and migrated to the new internal names automatically.

= 4.1.0 =
Lead capture no longer silently drops conversations that only leave an e-mail or only leave a name: a lead now just needs one valid contact detail, e-mails are stored and shown alongside phone numbers, and the assistant asks for whichever contact detail you've actually configured. No action needed.

= 4.0.0 =
Internal data (options and tables) moves to the plugin's own prefix and is migrated automatically on update — no action needed. Existing chat sessions in visitors' browsers keep working.

= 3.2.0 =
Fresh installs and installs that never served a visitor now show an AI-disclosure consent notice by default before the first chat message. Sites already serving visitors keep their current behavior automatically — nothing to do.

= 3.0.0 =
The plugin was renamed: after updating, reactivate it (the basename change deactivates it on file replacement).

= 2.0.0 =
First public release.
