=== AI House Knowledge Chat === Contributors: vunguyenbh Tags: chatbot, ai, llm, rag, chat Requires at least: 6.2 Tested up to: 7.1 Requires PHP: 7.4 Stable tag: 1.22.0 License: GPLv2 or later License URI: https://www.gnu.org/licenses/gpl-2.0.html AI chatbot: quick questions go to an LLM, deeper ones are answered from your own content with RAG. Made for shared hosting. == Description == AI House answers customer questions using your own website content, with two answer branches: * **Quick chat** — a direct LLM call for greetings and simple questions. * **Deep knowledge** — searches a vector database for relevant content, then answers with citations. On shared hosting the scarce resource is PHP workers, and a chat request that holds one for several seconds is a real cost. This plugin is built around that limit: every outbound call has a hard timeout, a circuit breaker, an answer cache, and a quota guard. **Measured cost per chat request: 3 database queries, 1-3 outbound HTTP calls, and zero writes to wp_options.** = Providers = * **LLM:** OpenAI, Google Gemini, OpenRouter (including free models) * **Vector store:** Supabase (pgvector), Pinecone, Qdrant * API keys are stored per provider and encrypted at rest, or supplied as constants in wp-config.php = Knowledge sources = * Any public post type you choose: posts, pages, products, custom types * **File import** — Markdown, CSV, JSON and plain text, with preview before importing * A dedicated Knowledge post type you can edit in wp-admin * Automatic re-index when content changes = Languages = * **The chat widget ships with Vietnamese and English enabled by default.** Twelve languages are built in: Vietnamese, English, Simplified Chinese, Japanese, Korean, Thai, Indonesian, French, German, Spanish, Portuguese and Russian. * Visitors switch language with a button in the widget. **The bot answers in the language the visitor actually wrote in** — a Vietnamese question typed into a widget sitting on EN comes back in Vietnamese, and the widget follows the visitor's language. The chosen language is what the answer falls back to when a message carries no signal ("ok", "iphone"). * Welcome message, placeholder, system prompt and language instruction are configurable per language. * **The plugin's own screens are translated, and you choose which translation to use.** Settings → General → Interface language picks the language of the AI House screens — the dashboard, the settings form, the notices — independently of the language of your WordPress profile. The choices are read from the translation files that ship with the plugin (English and Vietnamese today), so a locale added to the plugin shows up there without a code change, and "Follow WordPress" is the default. = Admin dashboard = * **Overview** — messages, sessions, cache hit rate, latency, queue and circuit breaker state * **Conversations** — browse sessions and read full message threads * **Knowledge** — import from file, manage items, watch indexing progress * **Diagnostics** — test connections, inspect the environment and running configuration = Also included = * Floating chat widget and `[mybot]` shortcode, light and dark themes * A AI House shortcut on the WordPress admin toolbar, with quick links to the dashboard, knowledge list and settings * Answer cache keyed per language, with question normalisation * Visitor feedback (helpful / not helpful) stored per message * Source citations returned to the widget so visitors can verify answers * Background queue with automatic retry and backoff * Automatic daily purge of old conversation logs * No jQuery dependency on the front end == Installation == 1. Upload the `mybot` folder to `/wp-content/plugins/`, or install the ZIP through Plugins > Add New > Upload Plugin. 2. Activate the plugin. Five database tables are created automatically. 3. Open **AI House** in the admin menu, then go to **Settings > Get started** — it lists the four things still missing, and nothing there is sent anywhere until you add your own API key. = Quick start with no cost = **AI House > Settings > Get started** walks through the four things a new install needs, and says which are still missing. Nothing on that screen runs a test by itself. 1. **Step 1 — connect an AI provider.** Choose OpenRouter and paste a key from https://openrouter.ai/keys. The key is checked as soon as you paste it. Then press **Test connection**: the step's light turns green when the provider actually answers. 2. **Step 2 — give it something to answer from.** Leave **Vector store** set to "None" to start with: the assistant then answers from the model alone. To answer from your own content, follow *Adding your own knowledge* below and press **Index knowledge**. 3. **Step 3 — prove the knowledge base can be searched.** Type a question that is written on your site and press **Look this up**. A score above your Minimum score is a pass. 4. **Step 4 — see what it answers from.** This step reports whether the **knowledge lock** is on, and points at the card that shows the file it lives in. It has no switch on purpose — see *Answering only from your own content* below. 5. Visit your site and open the chat widget. = Answering only from your own content (the knowledge lock) = The assistant answers only from your website's content when a file called **the knowledge lock** is on your server: ``` wp-content/uploads/mybot/khoa-tri-thuc.md ``` To switch it on, create that file with this block at the top: ``` ``` `che-do: off` on that line pauses the lock while keeping the file; **deleting the file** removes the lock completely. That is the whole interface — there is no button, no admin code, and nothing in this plugin ever writes to that file, so a plugin update cannot change it. With the lock on, everything is searched against your knowledge base, and a question with nothing above your **Minimum score** is refused **without calling the model**, so it costs no tokens and the visitor is offered the "leave your email" form instead. Short greetings are still answered. A file with no `MYBOT-LOCK` block is not a lock file; a file that declares a lock but cannot be read properly (a missing or unknown `che-do`) is treated as **on**, with the reason shown on the card. ⚠️ **A new install does not arrive locked** (changed in 1.10.0: earlier versions created this file for you and handed over an admin code; that code was removed at the request of the plugin's author after it locked people out of their own setting). Until you create the file, the assistant still answers questions your content cannot cover, from the model's own general knowledge, on your API key. = Adding your own knowledge = 1. Create a Supabase project in the region closest to your hosting 2. Run the SQL file from the project `sql/supabase-schema.sql` in the SQL Editor 3. Go to **AI House > Settings > Knowledge base**, choose Supabase, and paste your project URL and service_role key 4. Go to **AI House > Knowledge**, drop in a file, click Preview, then Import 5. Go to **Settings > Get started** and press **Index knowledge**, then let WP-Cron process the queue = What works without an account of ours = **You do not need an account with us, and there is no paid tier of this plugin.** AI House has no server of its own: it is free, complete, and the code you install is the whole product. No feature is locked, no trial expires, and installing it sends nothing to the authors. What it does need is **your own** account with the provider you choose — an API key for the AI model, and optionally a vector database. That is your account, your key, and your bill, and the plugin talks to those services directly from your site. == Frequently Asked Questions == = Does this train or fine-tune a model? = No. It uses RAG. Your content is split into chunks, converted to vectors, and stored. When a visitor asks something, the closest chunks are retrieved and placed in the prompt. Nothing is trained, updates are instant, and you can see which source produced each answer. = Can I use it without paying for AI? = Yes. OpenRouter offers free models and Google Gemini has a free embedding tier. Note that OpenRouter free models are limited to 20 requests per minute and 50 per day until you have purchased 10 credits. AI House checks your remaining quota before calling and returns a friendly message instead of a raw error. = What happens if the AI provider goes down? = The circuit breaker opens after a configurable number of consecutive failures and serves your fallback message immediately, without holding a PHP worker. The real reason is recorded and shown on the dashboard. = Where are conversations stored? = In the `{prefix}mybot_messages` table, written in batches by a background job rather than during the visitor request. Recent messages are sent as context for follow-up questions, and old logs are purged automatically. = Does it work on multisite? = Yes. Each site gets its own tables, options and knowledge base. = Will my knowledge pages appear in Google? = No. The knowledge post type is not public, so it creates no front-end pages and is excluded from search. Citations therefore show the title only. If you want linked citations, index regular pages or posts instead. = Which file formats can I import? = Markdown, CSV, JSON and plain text. PDF and DOCX are not supported because extracting text from them requires a PHP library that most shared hosts cannot install. Convert those documents to Markdown first. == External services == AI House has **no server of its own**. It sends nothing to the plugin authors, and it collects no analytics. It does call services **that you configure, with your own accounts**, and this section lists every one of them, what is sent, and where to read their terms. **The plugin does nothing until you enter a key.** With no key configured there is no outbound request of any kind, other than the two self-tests you can press yourself. = AI model provider — you choose one = * **OpenRouter** — https://openrouter.ai · Terms: https://openrouter.ai/terms · Privacy: https://openrouter.ai/privacy * **OpenAI** — https://openai.com · Terms: https://openai.com/policies/terms-of-use · Privacy: https://openai.com/policies/privacy-policy * **Google Gemini** — https://ai.google.dev · Terms: https://ai.google.dev/terms · Privacy: https://policies.google.com/privacy **What is sent:** the visitor's question, the recent turns of that conversation, your system prompt for the current language, and — when the knowledge base has a match — the retrieved text chunks. **What is not sent:** the visitor's IP address, any WordPress user data, your other plugin settings, or anything from your admin screens. = Embedding provider — only when you index your own content = The same three providers can supply embeddings (OpenAI, Google and OpenRouter). **What is sent:** the text of the content you chose to index, split into chunks. This happens only when you press **Index knowledge**, which you do yourself; nothing is indexed in the background without it. = Vector database — optional = * **Supabase** — https://supabase.com · Terms: https://supabase.com/terms · Privacy: https://supabase.com/privacy * **Pinecone** — https://www.pinecone.io · Terms: https://www.pinecone.io/terms/ · Privacy: https://www.pinecone.io/privacy/ * **Qdrant** — https://qdrant.tech (self-hosted, or the hosted service; no account is needed for the plugin itself) **What is sent:** the text chunks and their vectors, plus the question's vector when a search runs, so the nearest chunks can be returned. If you turn on the optional conversation mirror, the conversation text is copied there too. = Webhook — optional, off until you set a URL = If you paste a webhook URL (n8n, Zapier, Make, your own endpoint) **and** switch on the hand-over form, AI House POSTs the visitor's question and the contact details they typed — name, email, phone and their note — to that URL. **You** choose the destination; the plugin has no default and sends nothing when the field is empty. = Your responsibilities = You are the data controller for your visitors' conversations. Before enabling a provider, read its terms, and tell your visitors what you do with their messages in your own privacy policy. WordPress's own guidance on this is at https://wordpress.org/documentation/article/privacy/. == Shortcodes == Two shortcodes, for two different kinds of page. **`[mybot]` — the chat widget, inline.** Put it on a support page when you want the conversation itself to be the page. ``` [mybot title="Support" height="480" theme="light" lang="en"] ``` | Attribute | Default | What it does | |---|---|---| | `title` | the bot's name | The heading above the chat. Set by you, so the script leaves it alone. | | `height` | `480` | Height in pixels of the inline widget. | | `theme` | `light` | `light` or `dark`. | | `mode` | `auto` | `auto` (the plugin decides), `quick` (always the model) or `deep` (always your content). | | `lang` | the site's chat language | Which language the chat opens in, when it is one of the twelve. | **`[mybot_help]` — a help-centre block: search over your own pages, then a way to reach a person.** This is the shape of a support front page: a heading, a large search box, results as the visitor types, topic links you choose, and a button that opens the assistant. It searches your **published** pages and posts only — never the knowledge base you imported, which is private on purpose. ``` [mybot_help title="How can we help?" subtitle="Search the guides, or ask the assistant." placeholder="Search the help pages" button="Search" ask="Ask the assistant instead" topics="Getting started|/getting-started, Billing|/billing"] ``` | Attribute | Default | What it does | |---|---|---| | `title` | "How can we help?" | The heading. | | `subtitle` | empty | One line under the heading. | | `placeholder` | "Search the help pages" | Text inside the search field. | | `button` | "Search" | The submit button's label. | | `label` | "Search the help pages" | What a screen reader announces for the field. | | `ask` | "Ask the assistant instead" | The button that opens the chat. | | `topics` | empty | Comma-separated label and URL pairs, drawn as chips under the box. A refused URL scheme is dropped. | Every visible word is an attribute, so the page can be written in your visitors' language without touching the plugin. No template is needed: paste the shortcode into an ordinary Page and it renders. == Screenshots == 1. Settings → **Get started**: nine sections in a sidebar, and the four things that stand between a fresh install and a working assistant — each one saying whether it is done, and what to do when it is not. 2. The widget on a real page: the language switcher, both tools on the **left** of the message box, and Send as an arrow that takes your site's colour. 3. **Overview**: messages, sessions, cache hits and average latency, the background job queue, and the provider circuit breaker. 4. **Knowledge**: drop in a file, then watch every item it became being chunked and indexed. 5. **Customers**: one card per person, the exact sentence they agreed to, **Download CSV**, and a Delete button on every row. == Changelog == = 1.22.0 (26/10/2026) = * **Cited links for knowledge imported from a file.** Citations have always carried a URL and the widget has always drawn it as a link — but the URL came from `get_permalink()`, which is empty for the private knowledge type and for every item imported from a file, so a citation showed a title with nothing to click. Each knowledge item now has its own **link**, set in a **Citation link** box on that item's edit screen or by a `url` column in the import file (all three templates include it). * **A link is accepted only inside this site:** a path such as `/dang-ky`, or a URL on the same host including `www.`. Another domain, `javascript:`, and an empty value are refused, and the check runs when the link is **read** as well as when it is saved, so a bad URL already in the database cannot reach the widget. On a customer's site, "the same host" is that customer's own site. * No widget change, no database change: the stored link travels through chunk metadata into the sources list the widget already renders. The knowledge list also shows a Link column, so an item without one is visible without opening it. = 1.21.0 (18/10/2026) = * **This plugin's name changed.** The first submission was rejected because "MyBot" was too close to an existing chatbot product called MyBot.chat. It is now **AI House Knowledge Chat**, the slug is `aihouse`, and the text domain, plugin header and translation file names all follow it. If an older version is installed, delete that folder after activating this one. * **The description no longer compares this plugin to others**, and a dead link to a Qdrant terms page was removed. Twenty translator comments were added and five exception messages annotated, both at the request of the review team. * No setting was renamed and nothing else changed: options, knowledge base and conversations are untouched. = 1.20.0 (18/10/2026) = * **New: a help-centre shortcode, `[mybot_help]`.** A support front page needs three things in one block: a heading, a search box over your own pages, and a way to reach a person. That is now one shortcode with seven attributes, and every visible word of it is yours to write in your visitors' language. `[mybot]` still embeds the chat itself, inline, at whatever height you give it. * **The search finds your published pages and posts, and nothing else.** It never returns the knowledge base you imported — that table is private by design, and a public search box is exactly where an internal price list or a policy draft would leak. Draft, private and password-protected pages are excluded by the query itself, not by a list that could drift. * **Nothing on the help page can be turned into markup.** The title, the labels and the topic links are escaped when printed, a `javascript:` topic link is refused, results are written with `textContent`, and the excerpt the server sends has its tags and shortcodes stripped — so a page title containing a script is shown as text. * **A search box that cannot be used as free workload:** at least two characters, at most 120, six results, and the same rate limiter as the chat. One character returns nothing and says why. * No new option, no database change: install over 1.19.0, and a page that already uses `[mybot]` keeps working unchanged. = 1.19.0 (18/10/2026) = * **The directory gave this plugin the slug `aihouse`, not `mybot`, and WordPress.org requires the text domain to equal the slug.** The first submission scan came back with about three hundred `TextDomainMismatch` errors; they were all one fact. The text domain is now `aihouse` in 976 places: 656 PHP strings, 160 in the admin sources, 160 in the built bundle, the plugin header, `load_plugin_textdomain()`, the `plugin_locale` filter, the translation file names, and the Jed key the dashboard reads. * **Renamed by tokenizer, not by search-and-replace.** 674 bare `'mybot'` literals exist in this codebase and most of them have to stay: the `[mybot]` shortcode, the admin page slug, the cache group, the menu parent, the Supabase/Pinecone namespace, the folder the knowledge file lives in, `function_exists( 'mybot' )`. Only the ones that are the text-domain argument of a gettext call were touched — 18 were deliberately left alone, and the tool lists them so a person can check. * **`Plugin URI` no longer points at `example.com`.** The automated scan rejects that domain outright; it now points at this plugin's own page on WordPress.org. Replace it with your own homepage when you have one — a unique https URL is what the directory asks for. * **Twenty `translators:` comments added and five exception messages annotated.** The scanner treats a placeholder string without a translator comment as an error, and an exception built from a variable as unescaped output. Both were fixed at the source: the comments now say what each placeholder means, and each `throw` carries a one-line reason explaining that an exception message is not HTML. * **New guard: `tools/check-textdomain.mjs`** — nine rules and four injected-fault controls. It exists because nineteen guards all asked "do these parts agree with each other", which they did, and none asked "do they agree with the slug" — a value that was consistent everywhere and wrong everywhere. * No new option, no database change: install over 1.18.4. **Your saved settings are untouched** — option names did not change, only the text domain used for translations. = 1.18.4 (18/10/2026) = * **Screenshots exist now.** Five of them, taken on a running site rather than mocked up: `Get started`, the widget on a real page, `Overview`, `Knowledge`, `Customers`. The `== Screenshots ==` section that 1.18.2 had to remove is back, and its five captions match five real files (1600 px wide PNG, held in `assets/wp-org/`). * Where they go: WordPress.org reads `screenshot-1.png` … `screenshot-5.png` from **SVN `/assets/`**, not from the plugin ZIP — so this release carries the captions, and the pictures are committed separately next to the icon and banner. * Nothing else changed: no code, no setting, no string, no database. Install over 1.18.3, or ignore this one if you install from the directory. = 1.18.3 (18/10/2026) = * **`Contributors:` now carries the real WordPress.org username.** It held the placeholder `aihouse`; the profile that a plugin page credits is read from this field, so a placeholder means the page credits nobody. It is `vunguyenbh` now. Nothing else changed: no code, no setting, no string, no database. * Why a new version number for one line: a ZIP that has been handed over is never edited in place. One line is exactly what a version number is for. = 1.18.2 (18/10/2026) = * **The copy submitted to the directory, checked against the directory's own rules.** Tags are down from eight to five (the directory accepts at most five, and the brand names of model vendors were dropped — this plugin is not affiliated with any of them), the short description is 123 characters instead of 165 (the submission form's field holds 150), `Tested up to` says 7.1 rather than 6.6 (that is the WordPress this build was run on), and the Screenshots section is gone until the pictures exist: a tab of five broken images is worse than no tab. The five shots still to take are listed in `docs/19`. * **Two places printed without escaping, found by running the coding-standard check the way it is documented** (1.18.1 ran it narrowed to security sniffs, which is why they were missed): the chat panel markup was echoed through a call spanning several lines, so a single-line `phpcs:ignore` never covered it, and the requests screen formatted `Request #%d` with an integer the reader could not see had been cast. Both now escape a finished string. * **A number I published was wrong, and this release corrects it.** 1.18.1 recorded "46 errors / 16 warnings, exactly the previous level". Re-measured on all three release trees with the documented command: **1.16.0 = 46/16**, **1.18.0 = 100/53**, **1.18.2 = 42/16**. The 46/16 figure belonged to 1.16.0, not to 1.18.1 — 54 errors came in with the new files and I had not measured them. The real table is in `docs/18`, and 92 of them were fixed mechanically with `phpcbf` (layout only). * **Evidence that the reformat changed layout and nothing else:** every string literal in the three files `phpcbf` rewrote is still present the same number of times (124/124, 40/40, 916/916), all 69 tone-map pairs are unchanged, the 189 runtime assertions still pass, and all 19 guards pass. * **The 42 errors and 16 warnings that remain are each accounted for** in `docs/18`: 20 missing translator comments, 9 block comments, 8 `current_time( 'timestamp' )` calls, 5 exception messages (an exception is not output), 3 `meta_key` reads on an indexed key, one interpolated `%s,%s,%s` built from a count rather than from user data, and one `$submenu` — which is how WordPress lets a plugin order its submenu. None of them reaches a page. * No new option, no database change, no new translatable string: install this over 1.18.1. = 1.18.1 (16/10/2026) = * **Security review before the WordPress.org submission**, written up in the project as docs/18: the measured attack surface (19 REST routes, 3 admin-post actions, **no upload path at all**, no wp_ajax_, 45 prepared statements, no eval/extract/curl_), three layers of checking, and every finding classified as fixed, verified, or accepted with a reason. * **Two real fixes.** The chat route refused an oversized picture only *after* decoding it, so a 50 MB payload cost the server about 37 MB of memory before the refusal — the size is now checked on the string first, and on the bytes afterwards. And the hand-over route stored a question of any length; it is now capped at 5,000 characters (20,000 for the answer), which is far above anything the widget can send. * **New automated check (9 rules, 10 injected-fault controls)** that encodes what a WordPress.org reviewer looks for by hand: a REST route without a permission_callback is a PUBLIC route, no unprepared database read, no request value printed unescaped, an ABSPATH guard in every file, a nonce *and* a capability on every admin action, an uninstall that removes all six tables, and the readme fields the directory requires. * **No new coding-standard violations** (46 errors / 16 warnings, exactly the previous level) and 189 runtime assertions still pass. No new option, no database change: install over 1.18.0. = 1.18.0 (16/10/2026) = * **Fixed: the bot refused a thank-you.** A visitor wrote "thanks for your support" and received "this question is outside the information this website holds" — a refusal to somebody who had just been polite. The lock recognises politeness only by a 15-character length rule, so it caught "Hi" and missed every polite sentence longer than that: the more polite the visitor, the worse the reply. Politeness now has its own list in all twelve languages, with guards that keep it from swallowing a real question (no digits, no question mark, no question word, and the rest of the message may only be filler), and a greeting is answered warmly with no facts in it — so the lock stays a lock. * **Vietnamese with or without tones is the same person:** "cam on ban da ho tro" matches "cảm ơn bạn đã hỗ trợ". * **The default System prompt now carries the written guideline:** two or three sentences and never more than five, greetings and thanks answered as greetings and thanks, and never an invented price, a date, a policy or a phone number — say plainly that you do not know and offer a human instead. A prompt you have edited yourself is never overwritten. * No new option, no database change, no new translatable string: install this over 1.17.0. = 1.17.0 (16/10/2026) = * **New: the chat can now collect your visitors' contact details, and you can read them on a screen of their own.** A person icon in the message box opens a short form (a name, and **either** a phone number **or** an email address), and after three questions the chat offers the form **once**, as a bubble with a button — never a form that opens by itself, and never anything that blocks the conversation. * **New screen: Customers.** Name, how to reach them (and which channel they prefer), whether they agreed to be contacted, their note, language and date — with **Download CSV** and a **Delete** button on every row. The CSV opens in Excel with Vietnamese characters intact, and any cell a visitor typed that begins with `=`, `+`, `-` or `@` is neutralised: visitor text is untrusted input, and Excel would otherwise run it as a formula on the computer of whoever exported it. * **One person is one card.** A returning visitor arrives on a new session, so a card is matched by session, then by email, then by phone, and updated in place. Phone numbers are normalised (`+84 912-345-678` and `0912345678` are the same person), and an empty field never overwrites something the visitor told you earlier. * **Consent is recorded so that it can be proved:** not only a tick, but the exact sentence the visitor agreed to **and** the moment they agreed — taken from the plugin's own language table and never from the browser, because a client that can send its own sentence can record agreement to anything. * **WordPress' two privacy tools now cover this data.** Tools → **Export Personal Data** and Tools → **Erase Personal Data** find and remove a customer by their email address or phone number, including the questions they left for your team. Until this release, the honest answer to "what do you hold about me" was "open phpMyAdmin and look". * **The details never leave your site:** they are not copied to Supabase, not put into the knowledge base, and the card records only the page path — never its query string. A card is deleted when you delete it, and the whole table is dropped if the plugin is uninstalled. * **One database change** (a new table, applied automatically on upgrade) and 36 new translatable strings, all translated. Install this over 1.16.0, deleting the plugin folder first as always. = 1.16.0 (16/10/2026) = * **All the tools moved to the left of the message box.** The microphone used to sit between the field and Send, which put one tool on each side of the box; the picture button and the microphone now stand together on the left, the field takes the rest of the row, and Send alone ends it. * **Send is an arrow now, not the word.** The button is a round 40-pixel control with a paper-plane drawn as a vector path: it takes your site's colour, stays sharp at any size, and adds no page weight. The word "Send" is not gone — it moved to the three places that give a button its NAME (`aria-label`, `title`, and a visually hidden line inside it); all three are printed in the visitor's language and all three follow the language switcher, so a screen reader still says "Gửi" on a Vietnamese chat window. * **The box strengthens while there is something to send.** Empty, the pill is quiet and the arrow is muted; as soon as a question is typed, the pill turns white and the arrow comes up to full colour with a soft shadow; clearing the field puts both back. The arrow stays clickable while it is muted — a control that merely looks quiet is honest, while a truly disabled one would be a button that does nothing. * **Fixed quietly on the way:** the widget used to rely on your theme for the `.screen-reader-text` class. On a theme that does not define it, the hidden labels would have become visible text — which, now that Send is an icon, would have printed "Send" right next to the arrow. The widget defines that rule itself, so no theme can undo it. * **No new option, no database change, no new translatable string** — the word "Send" is reused, only the place it is printed changed. Three new automated rules now protect exactly the three things requested (tools on the left, an icon-only Send with a real accessible name, and the strengthened state), and each one has a deliberately broken version the test must catch. Install this over 1.15.0, deleting the plugin folder first as always. = 1.15.0 (15/10/2026) = * **The message box has been redrawn** into the shape of the chat app composer the plugin's author sent a picture of: **flat icons in a row along the bottom**, and **the field as a filled pill**. * **The microphone and the picture button lost their outlines.** They were 36-pixel circles with a grey border, which reads as a form control; they are now plain icons in your site's colour, and a soft round background appears only when a finger or a mouse is on them, or when they are reached with the keyboard. * **The field lost its border and gained a background** — a filled pill that turns white and takes your site's colour as a ring while it is focused. The **Send** button is rounded to match, and **all three controls are now exactly the same height**, so the row is straight instead of a pixel out here and there. * **The strip that shows a picture before it is sent** was redrawn to match: no outlines, and a round remove button with a soft red background on hover. * **The dark theme was corrected in the direction that is easy to get wrong:** on a dark panel the pill is *lighter* than what is behind it, because a dark field on a dark panel disappears. * **Nothing was renamed** — not one class, attribute, label or translated string. That is deliberate, and it is what makes this release safe: the widget's script, its twelve languages and its accessibility labels all keep working on exactly the same names. * **Two new automated rules protect the new shape**, and they are checked in a real DOM rather than by reading the stylesheet: the row must stay aligned along the bottom (a six-line question must not leave the icons floating in the middle of a tall box), and the microphone, the picture button and Send must stay the same height. Both rules fail against 1.14.1 and pass here, which is how the change is proved. * No new option, no database change, no new translatable string: install this over 1.14.1. As always, delete the plugin folder before uploading, so no old stylesheet is left behind. = 1.14.1 (14/10/2026) = * **Fixed: the attach-a-picture button in 1.14.0 opened the file picker, but choosing a photo then did nothing.** The button was there, the picker opened, and after choosing a picture no preview appeared and nothing could be sent. The cause was one line of JavaScript in which a variable was declared inside one function and used in the one around it, so the script stopped with an error on the first photo a visitor chose. * **Why this release exists at all:** every automated check in this project passed while the button was dead. They are all *readers* — they confirmed the button ships hidden, that the script reveals it, that the reveal is gated on whether your model can read images, that the stylesheet keeps `hidden` working, and that a picture travels as a data URI inside the chat request. All of that was true. None of it ran the code. * **New: a check that clicks the buttons.** The plugin now has an automated test that loads the shipped script into the shipped markup in a real DOM and actually presses the microphone and the picture button, chooses a file, removes it, tries to send a picture with no question, and sends one with a question — 28 assertions, plus four deliberately broken versions of the widget that the test must catch (one of them is exactly the 1.14.0 defect). It fails against 1.14.0 and passes against this release, which is how the fix is proved. * **Fixed in the same release:** the picture button is now also tested against a browser that cannot shrink a picture in a canvas and against a model that cannot read images — in both cases the button must stay hidden rather than appear and refuse. * No new option, no database change, no new translatable string: install this over 1.14.0. = 1.14.0 (14/10/2026) = * **New: the attach-a-picture button, so a visitor can send a photo with their question** — a photo of a damaged part, a screenshot of an error, a picture of the form they are filling in. It sits at the left of the message box, and it appears only when the model configured on your site can actually read an image (the plugin asks first, the same question `Test connection` answers). * **Pictures are NOT put in the Media Library.** Anything under `uploads/` is published at a web address that anyone who has it can open, with no login, and a visitor's photo is not that kind of file. Pictures are stored in a separate folder that the web server is told to deny, under a name made of 16 random bytes; nothing links to them, nothing lists them, and the file name is never printed on any screen. * **Only an administrator can look at one, and only through a permission-checked link.** A picture is addressed by *message number*, never by file name; the request is refused unless you are logged in with `manage_options`; and the reply is marked "never cache", so a photo the site promises to delete does not live on in a browser cache or a shared proxy. * **A picture is sent together with the question, inside the normal chat request** — there is no separate upload endpoint, so there are no orphan files and no second permission to get wrong. The question is still required: the knowledge search works on **text**, so a photo on its own has nothing to look up, and the widget says so in the visitor's own language instead of doing nothing. * **Photos are shrunk in the browser before they are sent:** longest side 1024 pixels, JPEG. A phone photo is 3–8 MB and 4000 pixels wide, and image cost is charged by area — roughly a thousand tokens at 1024 pixels against about sixteen times that unshrunk, for detail nobody asked about. It is also what keeps a visitor on mobile data from waiting a minute for an upload. Small PNG and WebP pictures are sent untouched, because re-encoding those as JPEG would throw away their transparency. * **Pictures are deleted with the conversation.** When the retention job removes old conversations it removes their files first and their rows second — a deleted row with a file left behind is a photo on your disk that nothing remembers, that no screen can show, and that no cleanup would ever find again. A guard rule checks that order. * **New on the Requests screen: "Pictures visitors sent".** A closed list showing the recent photos with their size and the conversation they belong to, so a picture is something you can actually look at. (The Conversations screen is a React application whose build is not part of this repository, so it does not draw photos yet — that is the honest state of it.) * **Fixed (my mistake, and the kind that only appears on a site already running): adding the new database column did not bump the database version**, and the upgrade step returns early while that version matches — so an existing site would have kept the old 13-column table while the logger now writes 14, which would have broken *every* log write, not only the picture ones. The version is bumped, and the plugin's own harness found this by running against a real site's real database. * **Privacy, in plain words:** the picture is stored on **your** site, readable by **your** administrators, and deleted with the conversation it belongs to; it is not copied to Supabase. On a host running **Nginx**, `.htaccess` is ignored, so the folder is not actively denied there — the file is still unguessable and still served only through the admin-only link, but that is a weaker promise, and it is better said than discovered. * 20 new translatable strings, all translated; no new option; one database change (`mybot_messages` gains a column, applied automatically on upgrade). = 1.13.0 (14/10/2026) = * **New: a microphone in the chat, so a visitor can speak their question instead of typing it.** The words are recognised by the browser's own speech service and arrive in the text box as text — they are **not** sent straight away, because recognition mishears and a confident answer to a misheard question is worse than one the visitor had to read twice. They read it, correct it if needed, then press Send. * **The button is hidden in the HTML and only revealed when the browser can actually recognise speech.** Firefox cannot, so on Firefox the button never appears at all. A visible button that does nothing is a defect this plugin has shipped before, and the guard tool now fails the build if the button stops being hidden — including the one CSS line (`display`) that would silently override `hidden` in every browser. * **The language is asked for properly.** The speech API wants a tag like `vi-VN`, while this plugin's language codes are two letters; given `vi` it listens and returns nothing, with no error at all. All twelve languages now carry a proper tag, a site can correct one with a filter, and an unknown language falls back to a real tag rather than an empty one (an empty tag makes the browser listen in *its own* locale, silently). * **No audio is recorded, stored or uploaded — only text.** The recognition happens inside the browser's own speech service; the plugin never receives the sound. A guard rule fails the build if a recorder or an upload ever appears in the widget's script. Be aware of what that sentence does and does not mean: the sound never reaches *your* server or *your* AI provider, but browsers implement this by sending it to their own vendor's service (Chrome to Google). * **What is still missing:** the attach-an-image button. That is the next step and the bigger one — the chosen design stores visitor photos privately and serves them through a permission-checked route, which needs a new database column, a private folder, and a screen to show the photo on. * No new option, no database change, and no new translatable admin string (the two labels are per-language *content*, like the rest of the widget's words): install over the top. = 1.12.1 (14/10/2026) = * **Fixed (important): 1.12.0 could take a whole site down if the plugin folder was only partly updated.** WordPress showed "There has been a critical error on this website" on every page. The cause was mine: 1.12.0 declared two capability methods on a PHP *interface*, and PHP resolves an unimplemented interface method the moment the class file is loaded — so an OLD provider file sitting next to the NEW interface killed the site. That is exactly what a File Manager or FTP upload leaves behind when one file fails to copy (and what an opcache can serve even when the file on disk is new). * **Fixed by making those two methods optional** and routing every question through one defensive call that answers "no" when the method is missing. Nothing else changed: the three providers still declare them, and a normal question still produces exactly the same request as before. * **The rule this leaves behind:** a provider missing a capability method must mean "the feature is unavailable", never "the site is down". If you are on 1.12.0 and your site is showing the critical-error page, delete the `mybot` folder and install this version fresh — a partly copied folder is what breaks, so removing it first is the fix. * No new option, no database change, no string change: install over the top. = 1.12.0 (13/10/2026) = * **New: AI House can now be asked whether the chosen model can read an image, and `Test connection` prints the answer.** This is the first step towards picture and voice input, and it is deliberately a question rather than a feature. Sending a photo to a model that only reads text fails at the provider, in English, *after* the request is paid for — so the plugin answers the question first, on the screen you already use to check the connection. * **New: message content can carry a picture.** Until this release a message was plain text, and each of the three providers turned that text into its own format on its own. Picture input would have meant changing all three, and one mistake there is invisible: a provider that does not understand a part drops it and answers as if you had sent words only. Content now has one canonical shape (text, image) with one converter per wire format. * **Nothing is ever dropped.** A part the destination format cannot express still travels — as text carrying its own JSON — instead of vanishing. A silently ignored photo is worse than an error, because the answer looks like an answer. * **The text path is unchanged, and that is measured, not promised:** for a normal question, OpenAI and OpenRouter still receive `content` as a plain string and Gemini still receives exactly one text part, byte for byte as in 1.11.1. * **The list of image-capable models is a hand-kept belief, not a measurement** — no provider publishes a field this plugin can read, and the model box is free text. So the wording on screen says "on this plugin's list", and a new filter (`mybot_model_capability`) lets a site correct the list without editing the plugin. A model that is not stated answers "no", deliberately. * **Voice: the question is declared, and every provider currently answers "no".** Voice is planned as speech-to-text **in the browser**, so the recording never reaches a provider — nothing to store, nothing to delete. `supports_audio()` exists now so the widget can ask before the feature does. * **What this release does NOT do:** `/chat` still accepts text only and the widget has no attachment button yet. No new option, no database change (633 translatable strings, all translated, two of them new): install over the top. = 1.11.1 (13/10/2026) = * **`Knowledge base` moved up to sit directly under `Get started`**, at the request of the plugin's author after seeing 1.11.0 on a real site. Two of the four setup steps are about content — give the assistant something to answer from, then prove the knowledge base can be searched — so the section those steps send you to now sits beside them instead of four items down. `General` (button colour, widget position) moves to third: a site with nothing to answer from cannot chat about anything, and a colour can wait. * The order is now: `Get started` · `Knowledge base` · `General` · `Language` · `AI tuning` · `Connections` · `Limits & cost` · `Add-ons` · `Status`, and `Get started` is still the section that opens first. * This release changes the ORDER OF LINKS ONLY. No option was renamed, no translatable string changed (631, all translated), and nothing about how the assistant answers, indexes or sends changed: install over the top. = 1.11.0 (13/10/2026) = * **Settings is now a sidebar of sections instead of a row of tabs.** Nine sections in the order a new site meets them — Get started, General, Language, AI tuning, Knowledge base, Connections, Limits & cost, Add-ons, Status — and the screen opens on **Get started**, which is the one a fresh install needs. Nothing moved between sections and no setting was renamed, so everything you already configured is exactly where its section says it is. The tab strip was replaced rather than restyled because it ran out of width once add-ons could contribute a section, and because the dashboard next to it has used a left sidebar since 1.9.7. * **`AI House → Add-ons` is now a section of Settings.** It answered one question — what is installed, and which features are switched on — and that is a setting, two menu clicks away from every other setting. The list, the refused registrations with their reasons, and the add-on API version are unchanged; the parent menu is down to two entries (Dashboard and Requests). * **New: an add-on key field, and it says plainly what it does not do.** Paste the key you were sent, press **Check this key**, and AI House trims it, checks its shape, stores it encrypted (the same way provider API keys are stored, shown back only as its last four characters), and records the verdict with the time it was taken. **It does not verify the key.** There is no licence server yet — that is a later milestone in the add-on plan — so the card states in words that this build can store a key and judge its shape, and cannot verify one, refuse one, or switch a feature off. Nothing on your site waits for a key, and nothing stops working without one. We would rather ship an honest stub than a button that reports "activated" against nothing. * **New: a `Status` section**, collecting the six report cards (statistics, failed background jobs, the Supabase mirror, the vector check, the last lookup, the knowledge lock) that used to sit in a right-hand column. The Save button is hidden there, because that section has nothing to save and every control on it acts immediately. * Nineteen new translatable strings, all translated; one string left with the removed screen. New options have defaults, there is no database change and no migration: install over the top. = 1.10.0 (12/10/2026) = * **The knowledge lock no longer has an on/off switch, and no longer has an admin code.** The file is the switch: `wp-content/uploads/mybot/khoa-tri-thuc.md` present means the lock is on, `che-do: off` pauses it, deleting the file removes it. Everything on the admin side that could change it is gone — the "turn the lock on" and "turn the lock off" buttons, the admin code field, the code stored in the file, the handling that wrote to the file, and the wp-admin notice that handed a generated code over. The card in **Settings** now only reports: on or off, the file's path, and a warning when the file is not readable as a lock. * **Why the code was removed rather than fixed.** It existed to stop somebody with the WordPress admin screen (but not the server's files) from switching the lock off from a button. That is a narrow threat, and it cost a wide failure: an owner who lost the code could not switch their own lock off, and that failure path raised a PHP warning (fixed in 1.9.10). Anyone who can edit or delete the file on the server always had the real power, and the plugin has stopped competing with them. Nothing in this plugin writes that file any more, so a plugin update cannot alter what you put in it. * **A new install no longer arrives locked.** Earlier versions created the lock file on activation and showed you a generated code; both are gone, so a fresh install starts unlocked and stays unlocked until you create the file. Step 4 of **Get started** now reports the state instead of asking you to decide, and its warning tells you to index your content rather than to switch a lock off. * **Strings removed with the controls:** 34 translatable strings for the code field, both buttons, the wrong-code paragraphs, the first-run notice and the button labels left the translation files; 8 new ones describe the file-only switch (613 in total, all translated). * No database change, no new option, and no migration: install over the top. = 1.9.10 (12/10/2026) = * **Fixed: switching the knowledge lock off with a code that does not match raised a PHP warning instead of an explanation.** The check that reports a failed unlock read a variable that one of its three branches never set, so the failure path — the one a locked-out owner is most likely to take — printed `Undefined variable $result` on every wrong code. It shipped with the lock itself in 1.9.2 and survived eight releases, because the success path never reaches that line and because the screen still redirected correctly: only a site with `WP_DEBUG` shows it. The variable is now initialised before the branches, and a guard rule (R11 of the plugin's own `check-knowledge-lock` tool) refuses code shaped like it — proved against the released 1.9.2, 1.9.3 and 1.9.9 packages. * **The "wrong code" message now says the way out**, not only what went wrong: it prints the path of the lock file and both ways to open the site without a code — change the `che-do` line to `che-do: off`, or delete the file, because the file IS the switch. It also says plainly that a code generated and then dismissed before it was saved cannot be recovered. Turning the lock off from the settings card still needs the code: that guards the screen, not the owner, who can always edit the file on the server. * Two translatable strings added or reworded (639 in total, all translated). No database change, no new option: install over the top. = 1.9.9 (12/10/2026) = * **Fixed (critical): 1.9.8 broke every wp-admin screen.** The admin menu read a class constant that was never declared — only a global one with a similar name existed — and PHP resolves class constants at run time, so a syntax check, the WordPress coding standards and every other guard passed it. On a real site every screen under `/wp-admin/` showed "There has been a critical error on this website". The front end and the chat route were unaffected, because the fault was inside the admin menu hook. If you installed 1.9.8, update — or delete the plugin folder and install again. * **Found by running the plugin, not by reading it:** this release was verified by installing it into a real WordPress and asking for every admin screen over HTTP, which named the file and the line in one run. A new rule (R7 of the plugin's `check-constants` tool) now rejects any `self::NAME` that has no matching `const NAME` in the same file, and it fails on the released 1.9.8 package. * No database change, no new option: install over the top. = 1.9.8 (11/10/2026) = * **New: the add-on framework.** AI House can now be extended by separate plugins that register themselves on an action and declare named features (`woo.stock`, `woo.warranty`). The core never needs to know which add-on is installed — it only asks whether a named feature is switched on — and an add-on cannot switch on a feature another add-on already claimed. AI House on its own is unchanged and complete: nothing is locked, nothing expires, and no feature of the free plugin waits for an add-on. * **New: an `Add-ons` screen**, which also prints the registrations AI House refused and the reason for each one. That second half is why the screen exists: a refused add-on stays active in WordPress, its menu does not appear, and nothing used to say why. * **Eight extension points**, each documented where it is defined: registering add-ons, asking whether a feature is on, AI tools (function calling), delivery channels, knowledge sources, settings tabs, a knowledge-ingested action, and an add-on-registered action. * **456 WordPress coding-standard violations fixed automatically** across 24 files (521 down to 65). The remaining ones are security, internationalisation and date-handling sniffs that need a person to read them, not a tool to rewrite them. * No database change, no new option: with no add-on installed every hook returns the value it always did, so installing over the top changes nothing about how the assistant answers. = 1.9.7 (11/10/2026) = * **New: a `Get started` tab with four steps, each with a status light.** Counting the inputs showed the problem was never the number of tabs: required fields and tuning knobs sat mixed together, and the tab holding the knowledge settings had sixteen inputs of which six are tuning. The four things that actually stand between a fresh install and a working assistant now live on one screen — connect an AI provider, give it content to answer from, prove the knowledge base can be searched, and decide what it may answer from — and each one says whether it is done. * **The three connection fields moved here** (provider, API key, model): they belong with "get this working", and they are not repeated on the AI tuning tab, because two inputs with one name are two sources of truth and which one wins depends on the order they appear in the document. * **Fixed: a trap that was worse than the tab layout, and that lived on no tab at all.** A fresh install arrives with the knowledge lock ON, so with no API key yet the bot refuses EVERY question — including "hello" — and a new user reasonably concludes the plugin is broken. Step 4 now says so in red, with the admin code field right there to switch the lock off. The protection itself is unchanged; only the directions are new. * **The six tuning options are now collapsed** behind a "Show the tuning options" block: chunk size, overlap, chunks to retrieve, minimum score, maximum context and items per batch. It is a checkbox and a label rather than a `
` element, because the whole settings screen is one `
` — an element between the form and its inputs would silently stop those inputs from being saved, and only for the people who never opened the block. * **One setting that was invisible is now reachable:** `rag_max_context` has been read by the code all along but had no field anywhere, so it could not be changed from the interface. * **Messages from the new buttons appear on the new tab**, all log boxes receive them, and a button press returns you to the tab you were on instead of dropping you on General. * **Two duplicate element ids in the knowledge lock card are fixed** (the on-branch and the off-branch both used the same id). * **Three tabs were renamed** — `LLM (2a)` → **AI tuning**, `RAG (2b)` → **Knowledge base**, `Resource protection` → **Limits & cost** — because "2a" tells a newcomer nothing. No fields moved except the three connection ones. * 44 new translatable strings (611 in total, all translated) and a new guard tool, `tools/check-get-started.mjs` — 6 rules, including that every step has a light the script can paint, that every button on the screen is handled by the script, that every log box the script writes to exists, and that the three connection fields have exactly one home. Its control case produces 10 findings. No database change, no new option. = 1.9.6 (11/10/2026) = * **New: a `Connections` tab in Settings.** Everything this plugin sends OUT of your site now sits on one screen: the notification email (address and switch), the **Webhook URL** for n8n, Zapier or Make, and a note for developers about the `mybot_support_request_created` action. The tab opens by saying that none of it is required: leave all of it empty and the assistant still works, storing conversations in WordPress and sending nothing anywhere. * **Fixed: the webhook field was buried inside the RAG (2b) table**, between "Minimum score" and "Content types to index". Nothing was broken by that — which was the problem. "Where does a question the bot could not answer go?" is a question about outbound integrations, and nobody thinks to ask a tab named RAG about that. It also carried a cost that only shows up later: the plugin grows by adding integrations, and every one of them would have had to find a home inside a table about vector search. * **The notification email moved with it**, because it is the same idea: how a question reaches a human. * **What stayed behind, and why:** the switch "Ask the visitor for an email address and save the question for a human" is still on RAG (2b). It is a knowledge policy — it pairs with the knowledge lock — not a delivery channel, and moving it would split one decision across two screens. Its description now points at the Connections tab instead. * **Nothing was renamed:** the three options keep their names, so there is no migration, no lost value, and installing over the top is all that is needed. * Five new translatable strings (571 in total, all translated) and a new guard tool, `tools/check-settings-tabs.mjs` — 6 rules that check BOTH directions of a move: every tab has its panel and every panel its tab (no orphans either way), exactly one panel is visible on load, the webhook must be on Connections and must NOT be on RAG, the email switch must share the webhook's tab, the support policy must stay on RAG, and no duplicate tab names. Its control case produces 12 findings for the old layout. = 1.9.5 = * **Fixed: the indexing progress figure measured two different things, so it never reached its own maximum.** The card paired *items processed* (only the ones that were queued) with *all published items*. On any site where most content has not changed since the last run — the normal case — the fraction stopped short and read like an abandoned job, while the line right below it said "Waiting in queue: 0". The denominator is now the number of items that were **queued**, and the items that were deliberately left alone are **named**: "Processed 18 of 18 queued items. 24 unchanged, so they were not indexed again." The skipped count was already computed and already returned by the REST route; no screen had ever drawn it. * **The dashboard says the same thing now.** The Overview and Knowledge screens divided by every published item as well, so the dashboard and the settings card disagreed about the same run. Both now use the queued count. * **Fixed: the log box could print a stored vector check with no date on it.** The "Vector check:" line is read from an option, so it outlives the run that produced it. The Status card printed its age; the log box printed the same sentence with no date at all, in green — a green line that proved nothing about the click that produced it. It now reads "Vector check: … (from 12 mins ago)", and the age is computed on the server, because a browser cannot date a value whose timestamp it never receives. * **The auto-refresh watches the queue, not a ratio.** It used to poll while "processed < total"; on a run that skipped unchanged content that condition was true forever until the page was reloaded. It now polls while jobs are still **pending**. * No behaviour change in indexing itself: nothing here adds a network call, changes chunking, or changes a model. Only what is **printed** changed — which is why it is safe to install over the top, between two measurements of the knowledge base. * Six new translatable strings (566 in total, all translated) and a new guard tool, `tools/check-progress.mjs` — 7 rules, two of which check the mistake itself: no file may pair "processed" with "total" again, and every stored measurement must be printed with its age. Its control case produces 19 findings for the shape released as 1.9.4. No database change, no new option. = 1.9.4 = * **The floating button can carry words, and an invitation bubble can draw the click.** Two new per-language texts, next to the bot name on the **Language** tab: **Text on the chat button** (for example "Contact us") and **Invitation bubble** (for example "Need help? Ask us anything."). Leave either empty and nothing is drawn — a site that never fills them in keeps exactly the button it has today. * **Two layouts, chosen in General:** text **under the icon** (narrow button, the style in the screenshot you sent) or **beside the icon** (easier to read). * **The invitation is off until you ask for it**, and it is shown **once per visitor**: set **Invitation bubble after (seconds)** (0–120, default 0) to turn it on. Closing the bubble, opening the chat, or pressing Escape retires it for good, and it never appears while the chat is already open. The close control is a real button with a label in the visitor's language, so a keyboard can dismiss it. * **Fixed: renaming the assistant worked, but nothing told you where.** The name — which is the title at the top of the chat window — lives on the Language tab, while the General tab only said "Bot name, welcome message, placeholder" without saying that this is the name above the chat. It now says so, so changing "AI Assistant" to your brand is one field rather than a search. * **Both new texts are listed in two places on purpose** (`CONTENT_FIELDS` and `sanitize_lang_strings()`): that sanitizer assigns the whole array, so a key missing from it is not left alone, it is **deleted at the next Save** — the exact defect of 1.7.0's language work. The new guard rule R6 checks both lists, and so does the comment that has been in the code since then. * 10 new translatable strings (562 in total, all translated) and a new guard tool, `tools/check-launcher.mjs` — 10 rules, including that the bubble starts hidden, that its dismissal is remembered, and that the close control is reachable by keyboard. Two new options, no database change: install over the top. = 1.9.3 = * **A fresh install now arrives with the knowledge lock ON.** Installing AI House on a new site creates the lock file with an admin code generated for that site, and a notice in wp-admin gives you the code and the two ways forward: add your content, or turn the lock off. So a copy downloaded from here is no longer an open general-purpose AI attached to the site owner's API key — a question outside the knowledge base is refused without calling the model at all, which costs nothing. * **This is a default, not a feature gate: adding knowledge stays free and unrestricted.** Importing files, the Knowledge post type and indexing are all untouched, the plugin's code is complete, and nothing calls home — you open the lock with your own code. That is why it is allowed: the WordPress.org guidelines forbid functionality restricted to be made available by payment or upgrade, and a default you can change is not that. * **Updating does NOT lock a site that is already running.** The default applies to new installs only — an update that suddenly started refusing a working site's questions would be a nasty surprise, not a feature. To lock the site you already have, press **Turn the lock on** on the Knowledge lock card. * **Fixed: the "Turn the lock on" button used to overwrite the whole lock file, and that file is yours.** The admin code you set and the answering-policy text you wrote in it were destroyed silently — on the one button that exists to make things safer. Now an existing file is only switched (`che-do`), never re-created; a new file is created only when there is none. * **The admin code field now states the minimum:** "at least 8 characters, or leave empty and one is generated for you". The error you may have seen — "The admin code must be at least 8 characters long." — is the correct answer to a shorter code; leaving the field empty generates a 22-character one. * Five new translatable strings (552 in total, all translated), and two new guard rules: **R9** keeps the create/switch order in the settings handler, **R10** keeps it at activation and requires the first-run notice to be wired. No database change, no new option beyond the notice (deleted on dismiss and on uninstall): install over the top. = 1.9.2 = * **New: the knowledge lock — the assistant answers only from this website, and a question it cannot answer costs zero tokens.** The switch is a FILE you keep: `wp-content/uploads/mybot/khoa-tri-thuc.md` (template in the plugin package as `khoa-tri-thuc.md`). File present, the lock is ON; delete the file and it is OFF; change the code inside it and you change who can turn it off. It lives in `uploads/` on purpose — a file inside the plugin folder is deleted by the next plugin update, and the lock would disappear with it, silently. * **Why it needed code and not a setting.** Two things used to make the model answer from its own general knowledge: (1) a question shorter than 15 characters or without a keyword skipped the knowledge base entirely when "When to use RAG" was left on Auto — a setting closes that one; and (2) when the search ran but nothing cleared the score floor, the model was **still called** with no data and no "answer only from this" instruction, so the reply came from outside knowledge and your key paid for it. **No setting closes the second one.** With the lock on, the plugin forces the search and **refuses before calling the model** — a question outside your content costs nothing at all, and the visitor gets the "leave your email" form instead. * **Short greetings are still answered**, as you chose: a message under 15 characters — the same boundary the plugin already used for small talk — goes to the model under a separate instruction that permits a greeting and forbids stating one fact. It does not also show the contact form, because a greeting answered with "leave your email" is the wrong experience. * **New card: Knowledge lock**, in AI House → Settings. It shows ON or OFF, the path of the file, and a warning when the file cannot be read as a lock. **Turning the lock on needs no code; turning it off needs the admin code from the file** — so somebody with the WordPress admin screen but no access to the server's files cannot quietly reopen your site to general-knowledge answers. A generated code is shown once, through a short-lived transient rather than the URL. * **An unreadable declaration fails safe:** a file with no `MYBOT-LOCK` line is not a lock file and the lock stays off (so a knowledge file dropped in the wrong folder cannot silently start refusing questions), but a file that declares a lock and cannot be read properly — missing `che-do`, unknown `phien-ban` — is treated as **ON**, with the reason shown on the card. * **28 new translatable strings** (547 in total, all translated), and a new guard tool `tools/check-knowledge-lock.mjs` that checks the wiring and the order: the refusal must come before the model call, the lock must be asked only after the store is known to be reachable, and the code must be checked before the lock is switched off — with a control case that produces 17 findings for a lock implemented the plausible wrong way. * No database change, no new option (the switch is a file, not a setting), no migration: install over the top. = 1.9.1 = * **1.9.0 was right and narrower than its own name: three more sentences of the widget still followed your SITE's language, and all three are sentences nobody sees.** The × button's label, the label only a screen reader reads for the question box, and the floating launcher's label — which only repainted itself after the first click. A visitor who chose Japanese still heard the site's language read aloud, and the launcher still introduced itself in the administrator's language. All three now come from the same per-language table as everything else, including a new twelfth key for the question box. * **`[mybot lang="ja"]` did nothing, and it has been documented since the shortcode existed.** PHP painted the widget in the default language while the root element declared Japanese, and the script then repainted the whole widget from the visitor's stored choice or the site default. The script now reads the language the widget was painted in, and the markup is painted in the language it declares. The visitor's own choice still wins. * **`[mybot title="..."]` did nothing either.** The language repaint replaced the title with the bot's name a moment after the page loaded, because the bot's name is never empty. A title you type is now kept, and the language only paints the title when you did not give one. * **The × button inside a shortcode widget was a button that did nothing** — the click handler only exists inside the floating panel — so it is now printed only for the floating widget, where something listens for it. * **Two strings leave the translation catalogue (521 → 519).** `Minimise chat` and `Enter your question` are now content in the widget's own string table, in all twelve languages, for the same reason the other eleven sentences are: visitors read them in the visitor's language, while the catalogue is only loaded for the admin language. No database change, no new option: install over the top. = 1.9.0 = * **The chat widget spoke two languages at once: your visitor's language for the conversation, and your site's language for everything around it.** Eleven sentences of the widget itself — Send, Open chat, Close chat, "Finding an answer…", "Searching the knowledge base…", the error line, the Sources heading, the two rating buttons, the thank-you line and the language label — were printed by PHP through the site's translation, while the greeting, the placeholder and the answer followed the language the visitor picked. Choose Japanese on an English site and the answer arrived in Japanese beside a button labelled "Send". * **The hand-over form existed in only two languages, and a language with no entry produced nothing at all.** So ten of the twelve languages opened a completely English form — on the last screen before a visitor who has run out of answers leaves. * **Both now follow the visitor, from one array.** The widget's own strings ship in all twelve languages (11 sentences) and so does the hand-over form (12 sentences), and the Send button and the language-group label are repainted whenever the language changes instead of being printed once for the default language. * **The English fallback is per line, not per table**, so one missing sentence no longer drags a whole screen into English — and your site's own wording remains the last fallback, so nothing ever renders as "undefined". * **The ten new hand-over translations are first-pass**: written to be correct and polite, not yet reviewed by a native speaker. Correcting them needs no code change — edit the table in `includes/class-mybot-languages.php` or use the `mybot_support_strings` filter. * No new translatable strings (these are content rather than `__()` strings, because visitors read them in the visitor's language while the catalogue is loaded for the admin language), no database change, no new option: install over the top. = 1.8.0 = * **The dashboard spoke a different language from the rest of the plugin. Now it does not, and you can choose which language it speaks.** Two things were true at the same time: every PHP screen followed the language of your WordPress profile, and the React dashboard — Overview, Conversations, Knowledge, Diagnostics — was English on every site, because its translations were never handed to the browser. On a Vietnamese site that meant half of AI House in Vietnamese and half in English, with nothing on the screen reporting a problem. The dashboard now receives the same translations the rest of the plugin uses, and all 134 strings it asks for already had Vietnamese wording waiting in the catalogue. * **New setting: Settings → General → Interface language.** Choose the language of AI House's own screens, independently of your WordPress profile language. The list is read from the translation files that actually ship with the plugin, so it can never offer a language that does nothing when picked, and "Follow WordPress" remains the default — a site that was happy changes nothing. * **The front end is deliberately untouched.** The chat widget's buttons still follow the site language and visitors still choose their own chat language; this setting only affects the screens the site owner reads. The plugin's own REST responses follow it, because the dashboard reads most of its wording from them and two languages on one screen is worse than one wrong one. *(1.9.0 then made the widget's own strings follow the visitor as well — see above.)* * One new setting, no database change, no migration: install over the top. = 1.7.2 = * **The language detector read Japanese messages as Chinese, and the cause was the order the scripts were asked in, not a missing script.** The detector asked for Han first, and Japanese is written with Han ideographs alongside Kana — so an ordinary Japanese sentence such as "保証期間はどのくらいですか?" matched both patterns, and the first match decided. On a site with Chinese enabled, a Japanese visitor's widget switched to Chinese (and that choice was stored, so it stayed). On a site without Chinese, the detector returned nothing at all and the widget did not move, even though the message was never in doubt. Nothing on the screen reported either outcome. * **The fix is the order itself.** The two scripts only one language uses — Kana for Japanese, Hangul for Korean — are now asked before the one they share. Han on its own is still read as Chinese, and that is now stated plainly in the code: Han-only text is the single case no character test can settle between Chinese and Japanese. * **Only the detector changed.** Its script table and its comment, nothing else: no new strings, no new option, no database change. Install over the top. = 1.7.1 = * **Two sentences on this page were wrong about the plugin, and both were numbers in prose that nothing checked.** The installation steps said "four database tables" while the code creates five (the requests table arrived in 1.5.9), so a reader following that would look for four and never find the fifth. And the language section still said the bot answers in the chosen language, which stopped being true in 1.7.0 when answers began following the language the visitor writes. Both are corrected here. No code changed; if you are already on 1.7.0, this release is documentation only. = 1.7.0 = * **The chatbot now answers in the language of the visitor's own message, instead of the language of the switcher.** The instruction sent to the model used to say the opposite in so many words — "Always answer in English, even if the customer writes in another language" — so the widget's selector decided the whole conversation and the visitor's own words had no say. On a Vietnamese site whose widget sat on EN, every Vietnamese question came back correct, cited, and in the wrong language, with nothing on the screen reporting a problem. All twelve instructions now ask for the language the customer used in their latest message, falling back to that line's own language only when a message carries no signal at all. * **The model decides the answer's language; a deliberately conservative detector decides the interface's.** The model identifies language far better than a pattern can — including a message written without diacritics, which no accent test can read — but it does not write the widget's buttons, its placeholder or the fallback sentence, and those come from the language code. The detector therefore speaks only when the evidence is beyond doubt: Han, Kana, Hangul, Thai and Cyrillic scripts, and for Vietnamese the characters only Vietnamese uses (not `ă â ê ô đ`, shared with Romanian, French and Croatian, and not `ã õ`, which are Portuguese). A wrong switch repaints everything the visitor reads next, so an unaccented Vietnamese message keeps the interface as it was while still receiving a Vietnamese answer. * **The fallback sentence used when the AI provider is unreachable is now per language too.** It was read from the top-level field, which put an English sentence inside a Vietnamese conversation — and on a site whose provider is down, that sentence is not rare: it is every single reply. * **The detected language is decided before the cache key is built.** The reply's language is part of what gets cached, so deciding afterwards files a Vietnamese answer under the switcher's language and serves it to the next visitor who asks the same thing in English. The log and the conversation record now carry the language actually used. * **Fixed: a per-language value was deleted on every Save.** The sanitizer assigns the whole array of per-language content, so a key it does not list is not left alone, it is removed — a value the site owner had typed would disappear at the next Save and only come back through the read-time defaults merge. `fallback_message` is now in that list. * **No new translatable strings, no new option, no database change: install over the top.** = 1.6.9 = * **The question box in the chat window now grows with the question.** It was a single-line field, so a long question scrolled out of sight while it was being written — the one moment the visitor needs to read back what they are about to send, and there was nothing to tell them the beginning of their sentence had gone. It is a textarea now, it grows with its content up to a ceiling, and only past that ceiling does it scroll. That ceiling is one number, declared in the stylesheet, and the script reads it from there instead of keeping a second copy that could drift. * **Enter still sends, and that had to be written down because a textarea does not do it by itself.** Shift+Enter starts a new line, and `isComposing` is respected: for anyone typing with an input method, Enter is also the key that accepts a candidate, and sending on that keypress would cut a half-finished word loose. Sending still goes through exactly one path — the form's submit handler. * **A taller question box means a taller chat window, which had to be handled in the same release.** The floating panel is anchored a fixed distance from the bottom of the screen, so on a phone a five-line question would push the panel's own header and close button off the top. The widget now has a viewport-aware maximum height and the message list is the part that gives way: it is a scroll area, so it may shrink below its content. On a short screen this also fixes the widget being clipped by its own overflow. * **Only presentation changed.** No new strings, no new option, no database change: install over the top. = 1.6.8 = * **The Requests screen printed every visitor's clock time wrong by exactly the site's UTC offset, while the "ago" label right beside it was right.** Both halves of that line read the same value, in two different ways. `created_at` is written with `current_time( 'mysql' )`, the site's local wall clock, so `strtotime()` returns "the local time read as if it were UTC" — the shape WordPress' own `date_i18n()` calls a "sum of Unix timestamp and timezone offset" and carries a reverse operation for. The `ago` half pairs that with `current_time( 'timestamp' )`, which carries the same offset, so the subtraction is correct. The clock half handed the same value to `wp_date()`, which adds the site offset itself — so it was added twice, and on a UTC+7 site a request that arrived "2 mins ago" was displayed as 04:28 tomorrow. It now goes through `mysql2date()`, the function whose documented contract is exactly this input. The bug was in the screen added in 1.6.6, so it had never run on a real site, and it is invisible on a site set to UTC. = 1.6.7 = * **Test connection now tests the embedding key as well, and that check was missing for a reason worth reading.** The report called `chat()` and the vector store and never once called `embed()`. The embedding key is a *different* key: the plugin prefers a separate "Embedding API key" field and only falls back to the provider key, so a stale value there produces a completely green connection test next to indexing runs that die with "API key expired." That is exactly what happened on a real site on 06/10/2026 — green at 20:33, dead at 14:27 — and nothing on the screen connected the two. There is a third line now, `Embedding:`, reporting the model and the number of dimensions it returned. * **Background jobs that gave up are now listed on the Status card, with the reason.** The Overview screen counted them ("Error 2") and no screen anywhere said which job failed or why: the reason was returned by the indexing status route and rendered by nobody. On a real site the only way to read it was a line typed into the browser console. The card now lists the five most recent jobs that used up their retries, with the attempt count and the error text. * **The indexing progress bar no longer adds up across runs.** Progress was accumulated while the total only ever described the run that had just started, so a second pass over the same content reported "84 of 42" — a ratio that cannot exist — and the bar jumped to 100% before any work had been done. The counter is reset when a run begins. = 1.6.6 = * **Everything a visitor types into the hand-over form now has a screen of its own.** It used to be a card at the bottom of the settings page, under four tabs of configuration — a strange place to go looking for a customer's phone number. The new **Requests** screen, in the AI House menu, lists every request with the question, the answer the bot gave before handing over, the name, email, phone, the visitor's own note, the language and the conversation it came from, with filters by status, paging, and buttons to reply by email, mark answered, close or reopen. Nothing about the database changed; this is the same data, somewhere you can actually manage it. * **The plugin has a logo, and it is built from the brand file rather than redrawn.** It appears in the WordPress admin menu, at the top of every AI House screen, in the dashboard sidebar and as the badge on the chat widget and its launcher. The menu icon is a white cut-out on transparency: WordPress paints that icon over its dark sidebar at 60% opacity, so the full-colour version would have arrived there as an opaque white square. * **One sentence from 1.6.5 became untrue when the list moved, and is corrected in the same release that moved it.** The Status card used to end with "the request is the line below". It now names the screen and links to it — a sentence can be right when written and quietly stop being right, and that is worth treating as a bug. * **The launcher kept its logo only until the first click.** The widget rewrote that icon by assigning text, which deletes an image; it now toggles a class instead. = 1.6.5 = * **The Status card now says where the visitor's details actually are, next to the line that says they were saved.** This closes a question that has been asked twice: "the customer filled the form and I cannot find the information anywhere". Both places that get searched — the Supabase mirror and the React dashboard — are correctly places the data is not in: the mirror holds conversations and never contact details, and the dashboard does not list requests at all. The data was in WordPress the whole time, on that very screen. When a request has been saved, the card now prints one more sentence saying exactly that, so nobody has to go looking again. The hand-over notes in docs/08 explained this on 04/10 and it did not help the person searching, because documentation is not where somebody looks when they are hunting for a row. = 1.6.4 = * **The refusal that happened in front of every other refusal now leaves a trace too.** 1.6.2 recorded a trace for each way the hand-over route could refuse a submission inside the handler: the option switched off (403), a repeat within five minutes (429), a missing table, an invalid email address. The fifth way sits in front of all of them — the REST nonce check in the permission callback. When that fails the handler is never called, so no trace was written, and the Status card kept saying "Nothing has been sent yet" while the visitor was reading an error. That is exactly the report "I filled the form in and nothing was saved anywhere", and it was the one cause the card built to explain that report could not see. The plugin now listens for the REST authentication error on the hand-over route and records it, and the handler repeats the permission check so the reason is written with a `permission:` prefix. * **The widget recovers from a stale nonce by itself.** `config.nonce` is written into the HTML when the page is generated, so on a site that caches pages a visitor can be handed a nonce from hours earlier — WordPress refuses it and the visitor reads a generic error. A 403 on the hand-over form now makes the widget request a fresh nonce from `GET /mybot/v1/config` (readable with the public nonce) and send once more. Only a 403 is retried: a 429 must not be, because sending again is what it forbids. Errors also carry their HTTP status now, so the refusals can be told apart. * **A "Test the hand-over path" button, so the path can be checked on demand.** Until now the only way to find out whether the hand-over worked was to have somebody fill in the widget and then read a card — a diagnostic that cannot be repeated when you need it. The button on the Questions-for-a-human card runs the whole path and prints one line per step: the feature is on, the table exists, a real row was written, it could be read back, and it was deleted again. The first red line is the answer. The probe row is stored as `closed` so a failed delete cannot leave a request looking unanswered, it uses a session id no visitor can have so the five-minute duplicate window cannot fail it, it sends no notification, and it does not overwrite the trace of what a real visitor's attempt did. = 1.6.3 = * **The "Load paid models" list no longer offers seven entries that are not models.** OpenRouter marks its routers and meta-models (`openrouter/auto`, `/auto-beta`, `/fusion`, `/pareto-code`, `/bodybuilder`, `nvidia/switchyard`, `typesafe/jev-router`) with `pricing.prompt = "-1"`, because they bill whatever model they forward to instead of a rate of their own. The filter only skipped a price of exactly 0, so a negative price counted as "paid" and all seven stayed in the list you are told to pick a model from — measured on 05/10/2026 at positions 46, 47, 49, 203, 311, 314 and 412 of 444. Three of them declare `reasoning`, so choosing one brought back the state where every question is answered with the fallback message and no citation can appear. A router is also the one choice that cannot keep the promise this list makes, since the model that finally answers is picked per request, and the real-message test that runs when a model is picked therefore proves nothing about the next question. Entries whose price is missing or negative are now dropped from the list. * **The catalogue report tool no longer points at the trap it exists to avoid.** It prints the cheapest models verified as non-reasoning, but its top four rows were those same routers — a negative number sorts below every real price — and it printed their price as `$-1000000.000`, because the per-million conversion was applied twice. The tool now lists the excluded routers separately and prints prices correctly. = 1.6.2 = * **The Status card now says where the "question for a human" hand-over stands.** One line counts the questions waiting, and one line reports the last one that was saved (with its request number and how long ago) — or, if the last visitor attempt failed, the reason it failed. * **A failed submission now leaves a trace in WordPress.** The visitor always saw a small red line in the chat, but the administrator saw nothing at all: no row, no log entry, nothing on any screen — the silent-failure shape this project has already met four times. Every attempt is now recorded, so "I filled the form and nothing appeared" has an answer on the admin screen: the request was saved at 20:41 as #7, or it was refused because the feature was off, or because a question from that conversation had just been sent, or because the requests table does not exist yet. * The reason is recorded for all three failure paths: the option switched off (403), a repeat within five minutes (429), and a storage error such as a missing table or an invalid email address. = 1.6.1 = * **The "table does not match the schema" message now names the missing column.** A real site answered `Could not find the 'lang' column of 'mybot_messages' in the schema cache` while the plugin said the primary key was probably still the old `id` one — right family of causes, wrong case, and it pointed the reader at the wrong fix. The message now quotes the column PostgREST names and gives both routes: check `select count(*) from public.mybot_messages;`, then drop and recreate the table when it is 0 (which is what to expect, because nothing has ever been written), or use the new repair block when it is not. * **`sql/supabase-schema.sql` gained a repair block** that adds the columns introduced after the first version of the schema (`wp_id`, `lang`, `tokens_in`, `tokens_out`) plus the unique index, so a table that already holds rows can be repaired instead of deleted. Wait a few seconds afterwards for PostgREST to reload its schema cache. = 1.6.0 = * **Indexing now reads its own vectors back.** Every counter until now measured the write ("26 of 26, no errors") and none of them could see that the vector stored under a chunk belonged to a different chunk or came from a different embedding model — the two silent faults of 1.5.5 and 1.5.7. After writing a post, the indexer asks the store for a vector it has just sent and expects the store to find that chunk at about 1.00. It runs once per indexing run, never fails the job, and prints the result in the Status card, in the log box right after "Index knowledge", and in the indexing status API. A score below 0.99 says plainly that the stored vectors were made by another embedding model, so lowering Minimum score cannot help and everything must be re-indexed. A store that returns no rows at all says that indexing can write but cannot read, which points at the key rather than at the content. * **Test connection now checks the Supabase conversation log too.** When the mirror is switched on, the button writes one probe row (`wp_id = -1`, which no real message can have) into `public.mybot_messages`, removes it again, and reports what Supabase answered with a hint per status code — 401 needs the service_role key, 404 needs the table, 400/409 needs the old table dropped and recreated. Before this, the feature had no way to be tested at all: the only evidence it was broken was an empty table days later. * **The mirror can now say what it is doing.** A new status line reports whether it is on, switched off after repeated failures, or on but never yet successful — plus when it last copied anything. A successful test also clears the failure counter, so fixing the problem and pressing Test connection is enough. * **Fixed: the mirrored rows carried the wrong WordPress IDs.** MySQL reports the *first* generated ID of a multi-row INSERT, and the code assumed the last one, so every key was shifted down by one per extra row in the block. The keys stayed unique, which is why nothing looked broken, but they were no longer the IDs of the rows they described and the Supabase copy could not be joined back to WordPress. If the ID cannot be trusted, nothing is written rather than every row sharing one key. = 1.5.9 = * **A question the bot cannot answer is now handed to a human instead of ending the conversation.** When the knowledge base has nothing to answer with, or the model says it does not have the information, the chat asks for a name, an email address and an optional phone number, and stores the question so somebody can reply. Off by default, on the RAG tab. * **Two independent signals decide "the bot could not answer"**: the model appends a hidden token, and the knowledge base reports whether it matched anything. One alone is not enough — a model can answer from general knowledge without ever admitting it does not know, and a search can return a vaguely similar chunk that answers nothing. * **Tell somebody in whatever way suits the team.** A WordPress action fires for developers, a webhook URL connects an n8n / Zapier / Make workflow (which can deliver to Slack, Telegram, Zalo, a sheet or a CRM with no code), and an optional wp_mail notification is there for sites that want nothing else. A failing webhook is recorded on the request and never blocks the visitor. * **A new screen lists the questions** with counts per status, the question, the phone number and anything the visitor added, plus buttons to reply by email, mark answered, or close. * **Fixed: the Supabase conversation log never worked in 1.5.8.** The key was read with `get()`, which returns the *encrypted* value from the database, so every message went out with a ciphertext bearer token: a 401 on every request and not one row in `mybot_messages`, while the option was ticked and nothing complained. It now uses `secret()`, the accessor the vector store has always used. Failures explain themselves now too — a 401 says the key must be `service_role`, a 404 says the table has not been created, a 400/409 says the table still has the old `id` column instead of `wp_id` — and the reason is repeated in the Status card. = 1.5.8 = * **Conversations can now be copied to Supabase as well as WordPress.** WordPress remains the source of truth and always keeps the complete log; this adds a second copy in the same Supabase project that holds the vectors, so the history can be read and queried from outside WordPress and the write load leaves MySQL. Off by default — it sends visitor messages to a service outside WordPress, so it is a deliberate choice per site, on the RAG tab. * **The mirror cannot damage anything else.** A failed Supabase write never fails the message: the row is already stored in WordPress, the mirror is idempotent (it keys on the WordPress message ID and asks Supabase to ignore a duplicate, so a retried job cannot double the log), and after three failures in a row it switches itself off with the reason shown in the settings, instead of filling the queue with failing log jobs. = 1.5.7 = * **A chat model is now tried with a real message the moment it is picked from the list.** A reasoning model spends its token budget thinking and returns empty content; while it is saved the chat answers every question with the fallback message and no citation can ever appear. It has now cost two sessions, and the model ID never gives it away — one real case (`dots-studio/dots-3-note-preview:free`) matches no obvious pattern. Picking a model sends one short message and reports straight away, next to the Model field, whether the model answers or is a reasoning model, before anything is saved. The choice is still yours: Save remains the only thing that changes a setting. * **Fixed a leftover way for vectors to be paired with the wrong chunk.** Both embedding providers dropped blank entries and re-indexed the batch before sending it, so a blank chunk shifted every later chunk by one — the same silent mispairing fixed in 1.5.5, reached by a different path. Positions are preserved now. * **Indexing no longer accepts a short answer from the embedding provider.** When fewer vectors came back than chunks were sent, the chunks without a vector were quietly skipped and the post was still recorded as indexed, so the missing chunks were never retried. That post now fails loudly and is sent again on the next run instead of leaving an invisible hole in the knowledge base. = 1.5.6 = * **A red "LLM" line now says what it means for the visitor**: the chat answers every question with the fallback message, so no citation can appear until it is fixed. The reason is easy to misread as a RAG fault, and one real report spent an evening on the knowledge base while the chat itself was down. * **The empty-reply message names the model and the token budget.** "OpenRouter returned reasoning text but no answer" left the reader comparing two log lines to find out which model was at fault; it now reads `… spent the whole 64-token budget on thinking. Model in use: ` and mentions that a reasoning model is slower, so the request timeout matters as much as Max tokens. * **The "Settings in use" line no longer looks like a doubled prefix.** It joined provider and model with a slash, so `openai/text-embedding-3-small` through OpenRouter was printed as `openrouter/openai/text-embedding-3-small` and read as a misconfigured model. It now reads `embeddings=openai/text-embedding-3-small via openrouter`. = 1.5.5 = * **Fixed embeddings being paired with the wrong chunk.** Both providers matched each vector to a chunk by the order the API returned them and ignored the `index` field it sends for exactly that purpose. A response in another order therefore attached every chunk to a different chunk's vector — with no error anywhere: indexing still reported 26/26, and every similarity score afterwards was meaningless. Found after a real site reported "the closest chunk scored 0.06" with a healthy-looking index. **Re-index after updating.** * **A lookup that finds nothing now tells apart the causes, and they need opposite fixes.** The closest chunk scoring 0.06 can mean "your content does not answer this question" or "the vectors do not belong to the embedding model selected now". The second cannot be fixed by any threshold, because cosine similarity cannot compare two different vector spaces. * **The lookup test now proves which one it is** by embedding a chunk that is already stored and searching for it: the same model must find it at ~1.0. When it cannot, the report names the Embedding model and a re-index instead of Minimum score. * The lookup report prints the settings that produced it (`question`, `embedding` model, vector dimensions, `min_score`, `top_k`) and, when nothing cleared the threshold, the titles of the three closest chunks with their scores. = 1.5.4 = * **A knowledge base lookup can now be tested on its own**, and what it did on the last question is kept. Being told "the answer has no sources" was impossible to act on: the question may never have reached the search, the search may have found nothing, or the call may have failed, and all three look the same from outside. The RAG tab now takes a question and reports the numbers — how many chunks cleared the threshold, the score of the closest chunk, and the embedding model used — and the Status card repeats what the last visitor question actually did. * **When a lookup comes back empty, the threshold is measured instead of guessed.** The search filters below the threshold inside SQL, so an empty result cannot tell "the table holds no vectors" from "every vector scored under the bar" — and those two need opposite fixes. The test asks a second time with the threshold removed and reports the real best score, so the message can say "the closest chunk scored 0.41, lower Minimum score" or "the store returned no rows at all, index the knowledge". * Test connection now prints the Minimum score it used, alongside the provider, models and vector store. * The lookup report is coloured by its result — green when the knowledge base returned chunks, grey when nothing cleared the threshold, red when the call failed — so a healthy lookup no longer looks like an error. = 1.5.3 = * **Answers from the knowledge base now keep their sources when they come from the cache.** A cached reply was returned with an empty source list, so asking the same question twice showed citations the first time and none the second. * **The similarity threshold is now a setting, and its default is 0.35 instead of 0.72.** At 0.72 no chunk on a real site cleared the bar: the search ran, matched nothing, and the model answered from its own knowledge with no source shown — which looks exactly like a broken knowledge base. A stored 0.72 that was never changed is lowered once. * The "Embedding API key" field no longer says "Not configured" when it is empty and the embedding provider already has a key. Empty is the documented way to reuse that key, so it now says which key it is using. = 1.5.2 = * **Test connection now writes, not just reads.** With Supabase, row level security is on and no policy exists, so the public "anon" key receives an empty list and HTTP 200 from the search function while every real write is refused with "new row violates row-level security policy". The test wrote nothing, so it passed on a key that could never index anything. It now writes one probe row and removes it again, and the failure message names the likely cause. * **Test connection prints the settings it used** — provider, chat model, embedding provider and model, and vector store. The test reads the saved settings rather than the form on screen, so naming them turns a verdict like "The vector store is not enabled." into an explanation. * **Failed background jobs report their error in the Status log.** The reason was already stored in the database; it is now printed next to the buttons that produced it instead of requiring phpMyAdmin. = 1.5.1 = * **Fixed Test connection reporting a working key as broken when the chat model reasons before it answers.** The test asks for one short word within 16 tokens; a reasoning model spends that budget on its thinking and returns empty content, so a valid key came back as "returned an empty reply" — and because the chat path returns the same error, the widget answered with the fallback message. The test now allows 64 tokens, and an empty reply says why: reasoning-only output, a token limit, or a model ID that does not exist. * **Fixed background indexing jobs staying in "processing" forever.** A cron run claims its whole batch up front, and when it hit its 20-second budget it returned only the job in hand; the rest stayed "processing" with nothing left to reset them, so indexing stopped part-way and the progress bar froze. Every job still waiting in the batch now goes back to the queue. * The settings screen warns you when you press **Test connection** with unsaved changes — the test reads the settings stored in the database, so testing before saving reports the previous values. = 1.5.0 = * **OpenRouter can now generate embeddings**, so a site can run the whole chatbot on one provider and one key: `openai/text-embedding-3-small` through OpenRouter has the same 1536 dimensions the bundled Supabase schema already uses, so no SQL change is needed. OpenRouter serves 33 embedding models, and a few of them are free. * OpenRouter namespaces its embedding catalogue (`openai/text-embedding-3-small`) while OpenAI and Gemini use flat names, so a saved flat name is qualified before it is sent rather than being sent as-is and rejected. * **Fixed the "Embedding API key" field description**, which said to leave it empty to reuse the *chat* key. It actually falls back to the key of the **embedding provider** above — with OpenRouter as the chat provider that meant no key at all, and RAG silently never ran. * **Fixed "Items per batch" doing nothing.** The value was saved, shown in the form, and never read: the indexer used a hard-coded 20. It now controls the batch size, which is what the field always claimed. = 1.4.2 = * **The widget now opens in English by default.** English was the safer guess for a site whose audience is unknown; the default was Vietnamese. An install that never picked a language is switched once, while a language you chose yourself is left alone. * **Fixed Vietnamese text in the widget being rendered with the wrong font.** Typography is now pinned inside the widget, for every descendant and for the form controls. A theme whose font is a webfont loaded with only the "latin" subset has no Vietnamese glyphs, so the browser filled them from a different font and one sentence came out in two typefaces. * A visitor who already chose a language keeps it: the choice is stored in the browser and wins over the default. = 1.4.1 = * **Fixed the API key check accepting keys that do not work.** The key was verified by loading the model list, and OpenRouter serves that list publicly: it answers HTTP 200 with no key at all and with a wrong key. So any key — an empty field, a placeholder, or a key belonging to another provider — was reported as accepted and stored, while every real request failed with 401 and the chat widget answered with the fallback message. * Each provider is now verified against an endpoint that genuinely requires authentication: `/key` for OpenRouter, `/models` for OpenAI, `/models?key=` for Gemini. A key that has not been proven is never written to the database. * An authentication failure from OpenRouter now explains itself. "Missing Authentication header" is what OpenRouter returns for a key that is not in its own format, so the message now says that OpenRouter keys start with `sk-or-v1-` and that the key may belong to another provider. * Keys are cleaned of invisible characters before they are stored. A non-breaking space or a zero-width space survives `trim()` and ends up inside the request header, which makes a valid key fail with the same confusing error. = 1.4.0 = * **The API key is now checked as soon as you paste it, and the model list appears without pressing Save first.** The model endpoint used to read the provider from the saved settings, so a key that had only been typed in was invisible to it. Pasting a key now verifies it against the provider and stores it when the provider accepts it. * **Model lists for OpenAI and Gemini too**, not just OpenRouter. The two providers answer with their own catalogues, so every provider can now be chosen from a dropdown instead of by typing a model ID. * Each provider caches its model list under its own key, so one provider can no longer be served another provider's list. * With OpenAI or Gemini selected, the two OpenRouter buttons become a single **Load models** button, because those providers publish one list rather than a free and a paid one. = 1.3.7 = * Fixed **Load paid models** showing the same models as **Load free models**. The route declared `free` with a default of `true`, so a request that omitted the parameter was answered with the free list. The two lists are now disjoint and the parameter is required, so a caller can no longer silently receive the wrong one. * The model list is no longer truncated at 300 entries, which was hiding paid models from the dropdown. * Only the dropdown for the button that was just used stays open, and a failed request now reports its error next to the Model field instead of only in the log box at the bottom of the page. = 1.3.6 = * Reorganised the LLM (2a) tab: Provider, then a single API key field that follows the selected provider, then Model. * Added a **Load paid models** button beside **Load free models**, with its own dropdown. The REST route already accepted a `free` parameter and cached both lists separately, so no server change was needed. * The three provider key inputs all remain in the form and are only hidden, never removed. A hidden input still submits and an empty value means "keep the stored key", so switching providers back and forth cannot lose a saved key. = 1.3.5 = * Fixed the "Load free models" button appearing to do nothing. The models were loaded into a ``, which stays invisible until the Model field is focused and typed into, and Safari does not support it at all. The result was also written to the log box inside the Knowledge (RAG) card at the bottom of the page, far from the button on the LLM tab. * Loaded models now fill a real dropdown that appears next to the button once the list arrives, and the outcome is reported on the spot, next to the field. * The Model field is still free text, so a model ID can always be pasted by hand. = 1.3.4 = * Fixed the LLM provider setting: selecting OpenRouter and saving silently reverted to OpenAI. The allow-list used when saving listed only openai and gemini, so every save replaced the choice with the default value. * Fixed the free model list never loading. The /models endpoint refuses any provider other than OpenRouter, so because the provider could never be saved as openrouter, the button always failed. * Both provider allow-lists are now built from the same factory lists that render the two dropdowns, so a provider can no longer be offered in the form and then rejected on save. * The embedding provider dropdown no longer offers OpenRouter. It was offered but rejected on save, which looked like a silent revert. OpenRouter embeddings need namespaced model IDs, so support is deliberately not enabled yet. = 1.3.3 = * Fixed the admin menu: clicking "AI House" now opens the dashboard instead of the knowledge list. WordPress builds the link of a top-level menu item from its first submenu entry, and the knowledge post type submenu was being registered before the dashboard, so the parent link pointed at the knowledge list. The dashboard is now moved to the front once every entry exists. * The "Dashboard" and "AI House Knowledge" submenu items now appear in that order. = 1.3.2 = * Fixed a fatal error that stopped the plugin from being deleted: uninstall.php used a constant that only existed in the plugin bootstrap, which WordPress does not load while deleting. * All option, transient and post meta names now come from one shared file, mybot-constants.php, loaded by both the plugin and uninstall.php, so the two can no longer drift apart. * Uninstall now also removes the options and transients added since 1.3.0, and can no longer be blocked by a database error. = 1.3.1 = * Content defaults (bot name, welcome message, input placeholder, system prompt, fallback message) are no longer passed through the translation layer, so the settings fields always show the English default instead of a translated sentence. * A saved value that still holds one of the old Vietnamese defaults is upgraded to the English default once. Values you edited yourself are left untouched. * The per-language content for Vietnamese visitors is unchanged. = 1.3.0 = * Added knowledge import from file: Markdown, CSV, JSON and plain text. * Added preview before import, merge or replace modes, and fingerprint-based duplicate detection. * Added a dedicated knowledge post type, editable in wp-admin. * Added downloadable templates for each file format. = 1.2.0 = * Added the React admin dashboard: overview, conversations, knowledge and diagnostics. * Added the OpenRouter provider with free model support. * API keys are now stored per provider. * Added a live model list loader. * Added a free quota check before calling the LLM. * The last failure reason is now shown on the dashboard. = 1.1.0 = * Added multilingual chat: visitors switch language in the widget. * Added 12 built-in languages with per-language content. * Added a lang column to the log table with an automatic schema upgrade. * Cache keys are now separated per language. = 1.0.0 = * First release. == Upgrade Notice == = 1.22.0 = Knowledge imported from a file can now carry a link inside your own site, and it appears as a clickable source under the answer. Set it in the Citation link box on the item, or add a url column when importing. No setting changes and no database change. = 1.21.0 = The plugin was renamed to AI House Knowledge Chat (slug `aihouse`) at the request of the WordPress.org review team, because the previous name was too close to another chatbot product. Delete the old plugin folder after installing this version. Settings, knowledge base and conversations are not affected. = 1.20.0 = Adds `[mybot_help]`: a help-centre block with a search box over your published pages and a button that opens the assistant. Nothing to configure — paste the shortcode into an ordinary page. Settings and stored data are untouched. = 1.19.0 = Install this if you took 1.18.4 for a WordPress.org submission: the directory assigned the slug `aihouse`, so the text domain had to change everywhere, and the automated scan refused the old one. Nothing else changes — no settings, no data, no behaviour. The translation files were renamed, so pick your language again in Settings if the interface comes up in English. = 1.18.4 = Five screenshots taken on a real site are now listed in this readme, and the pictures themselves are committed to the directory's `assets` folder. No code change, no setting, no string, no database: install it only if you want the readme to match the directory listing. = 1.18.3 = One line in this file changed: `Contributors:` now names the real WordPress.org account (`vunguyenbh`) instead of a placeholder. No code, no setting, no database change — install it only if you want the credits to point at the right person. = 1.18.2 = This is the copy handed to WordPress.org for review. It corrects the readme fields the directory checks (five tags, a short description under 150 characters, `Tested up to` 7.1) and closes the last two places that printed without escaping. It also corrects a number: the coding-standard level published in 1.18.1 was measured on 1.16.0, and the real figure for this tree is now 42 errors and 16 warnings, every one of them explained. No setting, no database change, nothing to reconfigure: install over 1.18.1. = 1.18.1 = Install this before submitting the plugin to WordPress.org: it is the security review. Two real hardening fixes (an oversized picture is now refused before it is decoded, and the hand-over route caps how much text it will store), plus a new automated check that encodes what a reviewer looks for — every REST route gated, no unprepared query, every file refusing direct access, every admin action with a nonce and a capability, and an uninstall that removes what the plugin created. No new option, no database change: install over 1.18.0. = 1.18.0 = Install this if the bot ever answered a greeting or a thank-you with "this question is outside the information this website holds". That refusal is fixed in all twelve languages, a question that merely starts politely is still refused correctly, and the default System prompt now says how long an answer may be and what to do when the assistant does not know. If you edited your System prompt yourself, paste the guideline from the project's docs/17 into it. No new option, no database change: install over 1.17.0. = 1.17.0 = Install this to collect your visitors' contact details and read them: a person icon in the chat opens a short form (a name, and a phone number or an email address), the chat offers it once after three questions, and a new **Customers** screen lists everyone with a CSV download and a delete button. The details stay on your own site — never copied to Supabase — consent is stored with the exact sentence the visitor agreed to, and WordPress' own Tools → Export/Erase Personal Data now find and remove a customer by their email or phone number. This release adds one database table, applied automatically on upgrade. Install it over 1.16.0, deleting the plugin folder first as always. = 1.16.0 = Install this for three changes to the message box: all the tools (picture, microphone) now sit together on the LEFT of the field, Send is a round arrow instead of the word, and the box strengthens while a question is being typed. The word "Send" still exists for screen readers in all twelve languages — it moved into the button's accessible name, so nothing is lost. No new option, no database change, no new string. If you are on 1.14.0, install 1.14.1 or later anyway: in 1.14.0 choosing a photo did nothing. = 1.15.0 = Install this for the redrawn message box: flat icons in a row along the bottom, the field as a filled pill, and the microphone, the picture button and Send all the same height. Nothing was renamed — no class, attribute, label or translated string — so the script, the twelve languages and the accessibility labels are untouched, and there is no new option and no database change. The dark theme was corrected with it. If you are on 1.14.0, install 1.14.1 or later anyway: in 1.14.0 choosing a photo did nothing. = 1.14.1 = Install this if you installed 1.14.0: in that release the attach-a-picture button opened the file picker, but after choosing a photo nothing happened — no preview, and nothing could be sent. The cause was one line of JavaScript, and this release fixes it. It also adds an automated test that presses the widget's buttons in a real DOM, because the existing checks only read the code and all of them passed while the button was dead. No new option, no database change, no new string: install it over 1.14.0. = 1.14.0 = Install this for the attach-a-picture button: a visitor can send a photo with their question, and the assistant can read it. Photos are stored privately on your own site (not in the Media Library), are shrunk in the browser before they are sent, are visible only to administrators through a permission-checked link, and are deleted together with the conversation they belong to. The button appears only when the model you selected can actually read an image, and a question is still required — the knowledge search works on text, so a picture on its own has nothing to look up. This release adds one database column, applied automatically; if your site runs on Nginx, note the limitation stated in the description: `.htaccess` is ignored there, so the folder is unguessable rather than actively denied. = 1.13.0 = Install this for the microphone in the chat: a visitor can speak their question and the words arrive in the text box for them to check before sending. It uses the browser's own speech recognition, so the sound never reaches your server or your AI provider — but note that browsers do this by sending the audio to their own vendor's service (Chrome to Google). The button appears only in browsers that can do it: on Firefox it stays hidden, and nothing is recorded or stored. No new option, no database change: install over the top. The attach-an-image button is the next release, not this one. = 1.12.1 = Install this if you installed 1.12.0. That version could show "There has been a critical error on this website" on every page when the plugin folder was only partly updated — an old file left next to a new one by a File Manager or FTP upload, which is a normal way for an update to go wrong. This version makes the two methods involved optional and routes every question through one defensive call, so a missing method means "the feature is unavailable" instead of a dead site. If your site is already showing that page, delete the `mybot` folder over FTP and install this version fresh. No database change: install over the top. = 1.12.0 = Install this for the groundwork of picture and voice input: `Test connection` now reports whether the selected model can read an image, and message content can carry one. Nothing changes about how the assistant answers today — a normal question produces exactly the same request as 1.11.1 — and `/chat` still accepts text only, so there is no attachment button yet. If you use `Test connection`, you will see one extra line: `Vision:`. No new option, no database change: install over the top. = 1.11.1 = Install this if you have 1.11.0: `Knowledge base` moves up to sit directly under `Get started`, because two of the four setup steps are about content and the section they send you to should be beside them. Nothing else changes — no option is renamed, no string changes, and no behaviour of the chat or the indexing changes. Install over the top. = 1.11.0 = Install this for the redesigned Settings screen: the tabs are now a sidebar of nine sections, the screen opens on `Get started`, the add-ons list is a section instead of its own menu entry, and the report cards have a `Status` section of their own. Every setting stays on the same section and keeps its name, so nothing you configured moves. This release also adds an add-on key field — and it says in plain words that it stores and shape-checks a key but does NOT verify one, because the licence server it would be verified against does not exist yet. If you do not sell add-ons, ignore that field; nothing on your site waits for a key. New options come with defaults and there is no database change: install over the top. = 1.10.0 = Install this if you use the knowledge lock: the buttons and the admin code are gone, and the lock is now switched by the file alone — `wp-content/uploads/mybot/khoa-tri-thuc.md` present means on, `che-do: off` pauses it, deleting it removes it. Nothing in the plugin writes that file any more, so your own text inside it is safe from updates. A new install no longer arrives locked. If you are upgrading, your existing lock file keeps working exactly as it did, including the `ma-quan-tri` line, which is now simply ignored. No database change: install over the top. = 1.9.10 = Install this if you use the knowledge lock, and especially if you have ever typed a wrong unlock code: that path printed a PHP warning on every attempt. Nothing about the lock's behaviour changes — turning it off from the settings card still needs the admin code, and the message now also tells you the file's path and the two ways to open the site without a code (set `che-do: off` in that file, or delete it). No database change: install over the top. = 1.9.9 = Install this if you installed 1.9.8: that version made every wp-admin screen show "There has been a critical error on this website", while your visitors' chat kept working. If you cannot reach the Plugins screen to update, delete the `mybot` folder over FTP and install this version. No database change: install over the top. = 1.9.4 = Install this if you want the floating button to say something — "Contact us", your brand, anything — or if you want a bubble that invites the click. Both are opt-in: give the button a text on the Language tab, set a delay (in seconds) in General for the bubble, and nothing changes until you do. The bubble is shown once per visitor and can be dismissed with a keyboard. Also fixes the missing signpost for renaming the assistant, which is the bot name on the Language tab. Two new options, no database change: install over the top. = 1.9.3 = Install this if you have not installed 1.9.2 yet, or if you set up the lock with the settings card: that button used to overwrite the whole lock file, taking the admin code and any policy text you had written into it. New installs now arrive locked, with the code shown once in wp-admin; updating an existing site does not lock it. No database change: install over the top. = 1.9.2 = Install this if you want the chatbot to answer only from your own content. A public widget whose model answers from general knowledge is a free general-purpose AI on your API key, and one of the two ways that happened had no setting to switch it off. Create the lock (a file in your uploads folder, or the new Knowledge lock card) and a question outside your content is refused without spending a token. Nothing changes until you create the file: no database change, no new option, install over the top. = 1.9.1 = Install this if you use `[mybot lang="..."]` or `[mybot title="..."]` — both attributes did nothing — or if any of your visitors use a screen reader, where the × button, the question box and the floating launcher were still read in your site's language. No database change, no new option: install over the top. = 1.9.0 = If you have enabled more than one chat language, install this: the widget's own buttons and messages used to stay in your site's language while the conversation moved to your visitor's, and the "leave your email" form was English for ten of the twelve languages. Both now follow the visitor, in all twelve. No database change, no new option: install over the top. = 1.8.0 = The AI House dashboard is translated now instead of always English, and Settings → General → Interface language lets you choose the language of the plugin's own screens separately from your WordPress profile. Your visitors' chat language is unaffected. One new setting, no database change: install over the top. = 1.7.2 = If you have enabled Japanese (or Korean) in the language switcher, install this: the detector used to read a Japanese message as Chinese, which moved the interface to Chinese on sites that also enabled it, and left the interface alone on sites that did not. It affects the language the widget speaks, not the answer the model writes. No database change, no new option: install over the top. = 1.7.1 = Documentation only, and the only reason it is a release at all is that readme.txt ships inside the plugin: the installation steps said four database tables where there are five, and the language section still described the pre-1.7.0 behaviour. If you are on 1.7.0 there is nothing to gain but nothing to lose either. = 1.7.0 = The chatbot now answers in the language the visitor actually wrote in, rather than the language of the widget's selector — so a Vietnamese question in an EN widget comes back in Vietnamese, and the interface follows the visitor too. The sentence shown when the AI provider is unreachable now follows as well. No database change, no new option: install over the top. = 1.6.9 = The question box grows with what you type instead of scrolling your own sentence out of sight, Enter still sends, and Shift+Enter starts a new line. The chat window keeps itself inside the screen while the box grows. Presentation only: no database change, no new option, install over the top. = 1.6.8 = The Requests screen printed each visitor's clock time wrong by exactly the site's UTC offset — a request that arrived "2 mins ago" could be shown as tomorrow 04:28 — while the "ago" label right beside it was correct, because the two halves read the same stored value in two different ways. The clock time now goes through the WordPress function whose documented contract matches that value. Only a site that does not run on UTC could ever see this, and the screen was added in 1.6.6. Presentation only: no database change, no new option, install over the top. = 1.6.7 = Three things that were invisible are now on screen: Test connection also checks the embedding key (it never did, which is how a green report can sit next to indexing failures), the Status card lists background jobs that gave up along with the error that stopped them, and the indexing progress bar no longer reports impossible numbers. No database change, no new option: install over the top. = 1.6.6 = The questions visitors leave for a human now have their own screen in the AI House menu, with every field they filled in, filters, paging and the reply/close buttons — instead of a card at the bottom of the settings page. The plugin also has a logo now, in the admin menu, on every AI House screen and on the chat widget. No database change, no new option: install over the top. = 1.6.2 = The Status card now reports the state of the "question for a human" hand-over: how many are waiting, when the last one was saved, and — if the last visitor attempt failed — why. Until now a failed submission left no trace anywhere in WordPress, so a form that never reached the database looked exactly like a form nobody had used. = 1.6.1 = Diagnostics only. When Supabase refuses the conversation-log write because the table is missing a column, the message now names that column and gives both fixes instead of guessing at the primary key — and sql/supabase-schema.sql gained a repair block for a table that already holds rows. Nothing changes for a site whose log mirror already works. = 1.6.0 = Indexing now proves it can read back what it wrote: after each indexing run the plugin asks the vector store for a vector it has just stored and reports whether the knowledge base still recognises its own text. That turns "the index says 26 of 26 but answers without citations" into a number and a next step. Test connection also checks the optional Supabase conversation log by writing and removing a probe row, and the log mirror reports whether it is on, off, or has never managed to copy anything. = 1.5.9 = Optional: when the chatbot cannot answer a question, it can now ask the visitor for an email address and save the question for a human, with an optional email or webhook notification. Off by default. Deactivate and activate the plugin once so the new requests table is created. = 1.5.8 = Optional: copy the conversation log into Supabase alongside the vectors, for reading the history from outside WordPress and for taking the write load off MySQL. Off by default, and a failure there can never lose a message or break the chat. Needs the table and primary key from sql/supabase-schema.sql. = 1.5.7 = Picking a chat model now sends it one short test message and tells you straight away whether it answers or is a reasoning model — a reasoning model makes every visitor question return the fallback message and blocks citations entirely. Also closes a leftover path that could pair vectors with the wrong chunk, and makes indexing refuse a short answer from the embedding provider instead of leaving invisible gaps. = 1.5.6 = Clearer failure messages: a red "LLM" line now says the chat is answering with the fallback message, the empty-reply message names the model and budget, and "Settings in use" no longer prints the embedding model as if it had a doubled prefix. = 1.5.5 = **Re-index your knowledge after updating.** Embeddings could be paired with the wrong chunk when the provider returned them out of order, which silently made every similarity score meaningless. The lookup test now checks that the knowledge base can still recognise its own text, and says whether an empty result is missing content or vectors that no longer match the embedding model. = 1.5.4 = Adds a lookup test on the RAG tab, plus a record of what the knowledge base did on the last question, so an answer without citations says which of the three causes it was. If a lookup finds nothing, the test measures the real best score with the threshold removed and tells you whether to lower Minimum score or re-index. = 1.5.3 = Answers taken from the cache now keep their sources, the similarity threshold has a setting and a saner default (0.35 instead of 0.72), and an empty "Embedding API key" no longer reads as an error. If the bot answers without citing your content, lower Minimum score on the RAG tab. = 1.5.2 = Test connection now also verifies that the vector store accepts writes, so a public "anon" Supabase key is caught at the test button instead of after a failed indexing run. The test prints the settings it used, and failed indexing jobs now show their error in the Status log. = 1.5.1 = Fixes Test connection reporting a false failure when the chat model reasons before it answers, and fixes background indexing jobs that could stay stuck in "processing" so indexing appeared to stop. Test connection now also tells you when your settings have not been saved yet. = 1.5.0 = OpenRouter can now be used for embeddings too, so one provider and one key can run the whole chatbot. Also fixes the embedding key field description and the "Items per batch" setting. = 1.4.2 = The chat widget now opens in English instead of Vietnamese, and Vietnamese text renders in a font that has all its accents. Visitors who already picked a language keep their choice. = 1.4.1 = Fixes 1.4.0 accepting an API key without really testing it, which left the chat answering with the fallback message. Paste the key again after updating. = 1.4.0 = Paste an API key and the models load straight away — no need to save first. OpenAI and Gemini also get model lists now. = 1.3.7 = Fixes "Load paid models" listing the same models as the free button. Load the paid list again to see the paid models. = 1.3.6 = Reorganises the LLM tab: one API key field that follows the provider, and a paid model list beside the free one. No stored key is affected. = 1.3.5 = Fixes the "Load free models" button, which gave no visible result. Loaded models now appear in a dropdown beside the field. = 1.3.4 = Fixes saving the LLM provider: choosing OpenRouter no longer reverts to OpenAI, and the free model list now loads. = 1.3.3 = Fixes the admin menu: clicking AI House now opens the dashboard instead of the knowledge list. = 1.3.2 = Fixes a fatal error on PHP 8 that prevented the plugin from being deleted after deactivation. Deletion now removes all plugin data. = 1.3.1 = Content defaults are now plain English in the settings fields instead of the translated default. Existing Vietnamese defaults that were never edited are upgraded automatically. = 1.3.0 = Adds knowledge import from Markdown, CSV, JSON and text files, with preview before importing.