{"id":390047,"date":"2026-10-26T13:48:17","date_gmt":"2026-10-26T13:48:17","guid":{"rendered":"https:\/\/wordpress.org\/plugins\/mybot-hybrid-ai-chat\/"},"modified":"2026-10-27T14:57:46","modified_gmt":"2026-10-27T14:57:46","slug":"aihouse","status":"publish","type":"plugin","link":"https:\/\/ory.wordpress.org\/plugins\/aihouse\/","author":15410903,"comment_status":"closed","ping_status":"closed","template":"","meta":{"version":"1.22.0","stable_tag":"1.22.0","tested":"7.1.4","requires":"6.2","requires_php":"7.4","requires_plugins":null,"header_name":"AI House Knowledge Chat","header_author":"Vu Nguyen","header_description":"Hybrid AI chatbot: quick questions go straight to an LLM, deeper questions are answered from your own content with RAG. Built to survive shared hosting.","assets_banners_color":"ffffff","last_updated":"2026-10-27 14:57:46","external_support_url":"","external_repository_url":"","donate_link":"","header_plugin_uri":"","header_author_uri":"https:\/\/profiles.wordpress.org\/vunguyenbh\/","rating":0,"author_block_rating":0,"active_installs":0,"downloads":127,"num_ratings":0,"support_threads":0,"support_threads_resolved":0,"author_block_count":0,"sections":["description","installation","faq","changelog"],"tags":{"1.22.0":{"tag":"1.22.0","author":"vunguyenbh","date":"2026-10-27 14:57:46","revision":3768847},"1.22.1":{"tag":"1.22.1","author":"vunguyenbh","date":"2026-10-28 13:53:43","revision":3770804}},"upgrade_notice":{"1.22.0":"<p>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.<\/p>","1.21.0":"<p>The plugin was renamed to AI House Knowledge Chat (slug <code>aihouse<\/code>) 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.<\/p>","1.20.0":"<p>Adds <code>[mybot_help]<\/code>: a help-centre block with a search box over your published pages and a button that opens the assistant. Nothing to configure \u2014 paste the shortcode into an ordinary page. Settings and stored data are untouched.<\/p>","1.19.0":"<p>Install this if you took 1.18.4 for a WordPress.org submission: the directory assigned the slug <code>aihouse<\/code>, so the text domain had to change everywhere, and the automated scan refused the old one. Nothing else changes \u2014 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.<\/p>","1.18.4":"<p>Five screenshots taken on a real site are now listed in this readme, and the pictures themselves are committed to the directory&#039;s <code>assets<\/code> folder. No code change, no setting, no string, no database: install it only if you want the readme to match the directory listing.<\/p>","1.18.3":"<p>One line in this file changed: <code>Contributors:<\/code> now names the real WordPress.org account (<code>vunguyenbh<\/code>) instead of a placeholder. No code, no setting, no database change \u2014 install it only if you want the credits to point at the right person.<\/p>","1.18.2":"<p>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, <code>Tested up to<\/code> 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.<\/p>","1.18.1":"<p>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 \u2014 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.<\/p>","1.18.0":"<p>Install this if the bot ever answered a greeting or a thank-you with &quot;this question is outside the information this website holds&quot;. 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&#039;s docs\/17 into it. No new option, no database change: install over 1.17.0.<\/p>","1.17.0":"<p>Install this to collect your visitors&#039; 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 <strong>Customers<\/strong> screen lists everyone with a CSV download and a delete button. The details stay on your own site \u2014 never copied to Supabase \u2014 consent is stored with the exact sentence the visitor agreed to, and WordPress&#039; own Tools \u2192 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.<\/p>","1.16.0":"<p>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 &quot;Send&quot; still exists for screen readers in all twelve languages \u2014 it moved into the button&#039;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.<\/p>","1.15.0":"<p>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 \u2014 no class, attribute, label or translated string \u2014 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.<\/p>","1.14.1":"<p>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 \u2014 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&#039;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.<\/p>","1.14.0":"<p>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 \u2014 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: <code>.htaccess<\/code> is ignored there, so the folder is unguessable rather than actively denied.<\/p>","1.13.0":"<p>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&#039;s own speech recognition, so the sound never reaches your server or your AI provider \u2014 but note that browsers do this by sending the audio to their own vendor&#039;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.<\/p>","1.12.1":"<p>Install this if you installed 1.12.0. That version could show &quot;There has been a critical error on this website&quot; on every page when the plugin folder was only partly updated \u2014 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 &quot;the feature is unavailable&quot; instead of a dead site. If your site is already showing that page, delete the <code>mybot<\/code> folder over FTP and install this version fresh. No database change: install over the top.<\/p>","1.12.0":"<p>Install this for the groundwork of picture and voice input: <code>Test connection<\/code> now reports whether the selected model can read an image, and message content can carry one. Nothing changes about how the assistant answers today \u2014 a normal question produces exactly the same request as 1.11.1 \u2014 and <code>\/chat<\/code> still accepts text only, so there is no attachment button yet. If you use <code>Test connection<\/code>, you will see one extra line: <code>Vision:<\/code>. No new option, no database change: install over the top.<\/p>","1.11.1":"<p>Install this if you have 1.11.0: <code>Knowledge base<\/code> moves up to sit directly under <code>Get started<\/code>, because two of the four setup steps are about content and the section they send you to should be beside them. Nothing else changes \u2014 no option is renamed, no string changes, and no behaviour of the chat or the indexing changes. Install over the top.<\/p>","1.11.0":"<p>Install this for the redesigned Settings screen: the tabs are now a sidebar of nine sections, the screen opens on <code>Get started<\/code>, the add-ons list is a section instead of its own menu entry, and the report cards have a <code>Status<\/code> 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 \u2014 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.<\/p>","1.10.0":"<p>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 \u2014 <code>wp-content\/uploads\/mybot\/khoa-tri-thuc.md<\/code> present means on, <code>che-do: off<\/code> 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 <code>ma-quan-tri<\/code> line, which is now simply ignored. No database change: install over the top.<\/p>","1.9.10":"<p>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&#039;s behaviour changes \u2014 turning it off from the settings card still needs the admin code, and the message now also tells you the file&#039;s path and the two ways to open the site without a code (set <code>che-do: off<\/code> in that file, or delete it). No database change: install over the top.<\/p>","1.9.9":"<p>Install this if you installed 1.9.8: that version made every wp-admin screen show &quot;There has been a critical error on this website&quot;, while your visitors&#039; chat kept working. If you cannot reach the Plugins screen to update, delete the <code>mybot<\/code> folder over FTP and install this version. No database change: install over the top.<\/p>","1.9.4":"<p>Install this if you want the floating button to say something \u2014 &quot;Contact us&quot;, your brand, anything \u2014 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.<\/p>","1.9.3":"<p>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.<\/p>","1.9.2":"<p>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.<\/p>","1.9.1":"<p>Install this if you use <code>[mybot lang=&amp;quot;...&amp;quot;]<\/code> or <code>[mybot title=&amp;quot;...&amp;quot;]<\/code> \u2014 both attributes did nothing \u2014 or if any of your visitors use a screen reader, where the \u00d7 button, the question box and the floating launcher were still read in your site&#039;s language. No database change, no new option: install over the top.<\/p>","1.9.0":"<p>If you have enabled more than one chat language, install this: the widget&#039;s own buttons and messages used to stay in your site&#039;s language while the conversation moved to your visitor&#039;s, and the &quot;leave your email&quot; 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.<\/p>","1.8.0":"<p>The AI House dashboard is translated now instead of always English, and Settings \u2192 General \u2192 Interface language lets you choose the language of the plugin&#039;s own screens separately from your WordPress profile. Your visitors&#039; chat language is unaffected. One new setting, no database change: install over the top.<\/p>","1.7.2":"<p>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.<\/p>","1.7.1":"<p>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.<\/p>","1.7.0":"<p>The chatbot now answers in the language the visitor actually wrote in, rather than the language of the widget&#039;s selector \u2014 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.<\/p>","1.6.9":"<p>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.<\/p>","1.6.8":"<p>The Requests screen printed each visitor&#039;s clock time wrong by exactly the site&#039;s UTC offset \u2014 a request that arrived &quot;2 mins ago&quot; could be shown as tomorrow 04:28 \u2014 while the &quot;ago&quot; 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.<\/p>","1.6.7":"<p>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.<\/p>","1.6.6":"<p>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 \u2014 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.<\/p>","1.6.2":"<p>The Status card now reports the state of the &quot;question for a human&quot; hand-over: how many are waiting, when the last one was saved, and \u2014 if the last visitor attempt failed \u2014 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.<\/p>","1.6.1":"<p>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 \u2014 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.<\/p>","1.6.0":"<p>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 &quot;the index says 26 of 26 but answers without citations&quot; 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.<\/p>","1.5.9":"<p>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.<\/p>","1.5.8":"<p>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.<\/p>","1.5.7":"<p>Picking a chat model now sends it one short test message and tells you straight away whether it answers or is a reasoning model \u2014 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.<\/p>","1.5.6":"<p>Clearer failure messages: a red &quot;LLM&quot; line now says the chat is answering with the fallback message, the empty-reply message names the model and budget, and &quot;Settings in use&quot; no longer prints the embedding model as if it had a doubled prefix.<\/p>","1.5.5":"<p><strong>Re-index your knowledge after updating.<\/strong> 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.<\/p>","1.5.4":"<p>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.<\/p>","1.5.3":"<p>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 &quot;Embedding API key&quot; no longer reads as an error. If the bot answers without citing your content, lower Minimum score on the RAG tab.<\/p>","1.5.2":"<p>Test connection now also verifies that the vector store accepts writes, so a public &quot;anon&quot; 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.<\/p>","1.5.1":"<p>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 &quot;processing&quot; so indexing appeared to stop. Test connection now also tells you when your settings have not been saved yet.<\/p>","1.5.0":"<p>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 &quot;Items per batch&quot; setting.<\/p>","1.4.2":"<p>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.<\/p>","1.4.1":"<p>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.<\/p>","1.4.0":"<p>Paste an API key and the models load straight away \u2014 no need to save first. OpenAI and Gemini also get model lists now.<\/p>","1.3.7":"<p>Fixes &quot;Load paid models&quot; listing the same models as the free button. Load the paid list again to see the paid models.<\/p>","1.3.6":"<p>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.<\/p>","1.3.5":"<p>Fixes the &quot;Load free models&quot; button, which gave no visible result. Loaded models now appear in a dropdown beside the field.<\/p>","1.3.4":"<p>Fixes saving the LLM provider: choosing OpenRouter no longer reverts to OpenAI, and the free model list now loads.<\/p>","1.3.3":"<p>Fixes the admin menu: clicking AI House now opens the dashboard instead of the knowledge list.<\/p>","1.3.2":"<p>Fixes a fatal error on PHP 8 that prevented the plugin from being deleted after deactivation. Deletion now removes all plugin data.<\/p>","1.3.1":"<p>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.<\/p>","1.3.0":"<p>Adds knowledge import from Markdown, CSV, JSON and text files, with preview before importing.<\/p>"},"ratings":[],"assets_icons":{"icon-128x128.png":{"filename":"icon-128x128.png","revision":3766617,"resolution":"128x128","location":"assets","locale":"","width":128,"height":128},"icon-256x256.png":{"filename":"icon-256x256.png","revision":3766670,"resolution":"256x256","location":"assets","locale":"","width":256,"height":256}},"assets_banners":{"banner-1544x500.png":{"filename":"banner-1544x500.png","revision":3766617,"resolution":"1544x500","location":"assets","locale":"","width":1544,"height":500},"banner-772x250.png":{"filename":"banner-772x250.png","revision":3766617,"resolution":"772x250","location":"assets","locale":"","width":772,"height":250}},"assets_blueprints":{},"all_blocks":[],"tagged_versions":["1.22.0","1.22.1"],"block_files":[],"assets_screenshots":[],"screenshots":{"1":"Settings \u2192 <strong>Get started<\/strong>: nine sections in a sidebar, and the four things that stand between a fresh install and a working assistant \u2014 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 <strong>left<\/strong> of the message box, and Send as an arrow that takes your site's colour.","3":"<strong>Overview<\/strong>: messages, sessions, cache hits and average latency, the background job queue, and the provider circuit breaker.","4":"<strong>Knowledge<\/strong>: drop in a file, then watch every item it became being chunked and indexed.","5":"<strong>Customers<\/strong>: one card per person, the exact sentence they agreed to, <strong>Download CSV<\/strong>, and a Delete button on every row."}},"plugin_section":[],"plugin_tags":[2353,5707,2364,226124,70446],"plugin_category":[41],"plugin_contributors":[288711],"plugin_business_model":[],"class_list":["post-390047","plugin","type-plugin","status-publish","hentry","plugin_tags-ai","plugin_tags-chat","plugin_tags-chatbot","plugin_tags-llm","plugin_tags-rag","plugin_category-communication","plugin_contributors-vunguyenbh","plugin_committers-vunguyenbh"],"banners":{"banner":"https:\/\/ps.w.org\/aihouse\/assets\/banner-772x250.png?rev=3766617","banner_2x":"https:\/\/ps.w.org\/aihouse\/assets\/banner-1544x500.png?rev=3766617","banner_rtl":false,"banner_2x_rtl":false},"icons":{"svg":false,"icon":"https:\/\/ps.w.org\/aihouse\/assets\/icon-128x128.png?rev=3766617","icon_2x":"https:\/\/ps.w.org\/aihouse\/assets\/icon-256x256.png?rev=3766670","generated":false},"screenshots":[],"raw_content":"<!--section=description-->\n<p>AI House answers customer questions using your own website content, with two answer branches:<\/p>\n\n<ul>\n<li><strong>Quick chat<\/strong> \u2014 a direct LLM call for greetings and simple questions.<\/li>\n<li><strong>Deep knowledge<\/strong> \u2014 searches a vector database for relevant content, then answers with citations.<\/li>\n<\/ul>\n\n<p>On shared hosting the scarce resource is PHP workers, and a chat request that holds one\nfor several seconds is a real cost. This plugin is built around that limit: every outbound\ncall has a hard timeout, a circuit breaker, an answer cache, and a quota guard.<\/p>\n\n<p><strong>Measured cost per chat request: 3 database queries, 1-3 outbound HTTP calls, and zero writes to wp_options.<\/strong><\/p>\n\n<h4>Providers<\/h4>\n\n<ul>\n<li><strong>LLM:<\/strong> OpenAI, Google Gemini, OpenRouter (including free models)<\/li>\n<li><strong>Vector store:<\/strong> Supabase (pgvector), Pinecone, Qdrant<\/li>\n<li>API keys are stored per provider and encrypted at rest, or supplied as constants in wp-config.php<\/li>\n<\/ul>\n\n<h4>Knowledge sources<\/h4>\n\n<ul>\n<li>Any public post type you choose: posts, pages, products, custom types<\/li>\n<li><strong>File import<\/strong> \u2014 Markdown, CSV, JSON and plain text, with preview before importing<\/li>\n<li>A dedicated Knowledge post type you can edit in wp-admin<\/li>\n<li>Automatic re-index when content changes<\/li>\n<\/ul>\n\n<h4>Languages<\/h4>\n\n<ul>\n<li><strong>The chat widget ships with Vietnamese and English enabled by default.<\/strong> Twelve languages are built in: Vietnamese, English, Simplified Chinese, Japanese, Korean, Thai, Indonesian, French, German, Spanish, Portuguese and Russian.<\/li>\n<li>Visitors switch language with a button in the widget. <strong>The bot answers in the language the visitor actually wrote in<\/strong> \u2014 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\").<\/li>\n<li>Welcome message, placeholder, system prompt and language instruction are configurable per language.<\/li>\n<li><strong>The plugin's own screens are translated, and you choose which translation to use.<\/strong> Settings \u2192 General \u2192 Interface language picks the language of the AI House screens \u2014 the dashboard, the settings form, the notices \u2014 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.<\/li>\n<\/ul>\n\n<h4>Admin dashboard<\/h4>\n\n<ul>\n<li><strong>Overview<\/strong> \u2014 messages, sessions, cache hit rate, latency, queue and circuit breaker state<\/li>\n<li><strong>Conversations<\/strong> \u2014 browse sessions and read full message threads<\/li>\n<li><strong>Knowledge<\/strong> \u2014 import from file, manage items, watch indexing progress<\/li>\n<li><strong>Diagnostics<\/strong> \u2014 test connections, inspect the environment and running configuration<\/li>\n<\/ul>\n\n<h4>Also included<\/h4>\n\n<ul>\n<li>Floating chat widget and <code>[mybot]<\/code> shortcode, light and dark themes<\/li>\n<li>A AI House shortcut on the WordPress admin toolbar, with quick links to the dashboard, knowledge list and settings<\/li>\n<li>Answer cache keyed per language, with question normalisation<\/li>\n<li>Visitor feedback (helpful \/ not helpful) stored per message<\/li>\n<li>Source citations returned to the widget so visitors can verify answers<\/li>\n<li>Background queue with automatic retry and backoff<\/li>\n<li>Automatic daily purge of old conversation logs<\/li>\n<li>No jQuery dependency on the front end<\/li>\n<\/ul>\n\n<h3>External services<\/h3>\n\n<p>AI House has <strong>no server of its own<\/strong>. It sends nothing to the plugin authors, and it collects no analytics. It does call services <strong>that you configure, with your own accounts<\/strong>, and this section lists every one of them, what is sent, and where to read their terms.<\/p>\n\n<p><strong>The plugin does nothing until you enter a key.<\/strong> With no key configured there is no outbound request of any kind, other than the two self-tests you can press yourself.<\/p>\n\n<h4>AI model provider \u2014 you choose one<\/h4>\n\n<ul>\n<li><strong>OpenRouter<\/strong> \u2014 https:\/\/openrouter.ai \u00b7 Terms: https:\/\/openrouter.ai\/terms \u00b7 Privacy: https:\/\/openrouter.ai\/privacy<\/li>\n<li><strong>OpenAI<\/strong> \u2014 https:\/\/openai.com \u00b7 Terms: https:\/\/openai.com\/policies\/terms-of-use \u00b7 Privacy: https:\/\/openai.com\/policies\/privacy-policy<\/li>\n<li><strong>Google Gemini<\/strong> \u2014 https:\/\/ai.google.dev \u00b7 Terms: https:\/\/ai.google.dev\/terms \u00b7 Privacy: https:\/\/policies.google.com\/privacy<\/li>\n<\/ul>\n\n<p><strong>What is sent:<\/strong> the visitor's question, the recent turns of that conversation, your system prompt for the current language, and \u2014 when the knowledge base has a match \u2014 the retrieved text chunks. <strong>What is not sent:<\/strong> the visitor's IP address, any WordPress user data, your other plugin settings, or anything from your admin screens.<\/p>\n\n<h4>Embedding provider \u2014 only when you index your own content<\/h4>\n\n<p>The same three providers can supply embeddings (OpenAI, Google and OpenRouter). <strong>What is sent:<\/strong> the text of the content you chose to index, split into chunks. This happens only when you press <strong>Index knowledge<\/strong>, which you do yourself; nothing is indexed in the background without it.<\/p>\n\n<h4>Vector database \u2014 optional<\/h4>\n\n<ul>\n<li><strong>Supabase<\/strong> \u2014 https:\/\/supabase.com \u00b7 Terms: https:\/\/supabase.com\/terms \u00b7 Privacy: https:\/\/supabase.com\/privacy<\/li>\n<li><strong>Pinecone<\/strong> \u2014 https:\/\/www.pinecone.io \u00b7 Terms: https:\/\/www.pinecone.io\/terms\/ \u00b7 Privacy: https:\/\/www.pinecone.io\/privacy\/<\/li>\n<li><strong>Qdrant<\/strong> \u2014 https:\/\/qdrant.tech (self-hosted, or the hosted service; no account is needed for the plugin itself)<\/li>\n<\/ul>\n\n<p><strong>What is sent:<\/strong> 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.<\/p>\n\n<h4>Webhook \u2014 optional, off until you set a URL<\/h4>\n\n<p>If you paste a webhook URL (n8n, Zapier, Make, your own endpoint) <strong>and<\/strong> switch on the hand-over form, AI House POSTs the visitor's question and the contact details they typed \u2014 name, email, phone and their note \u2014 to that URL. <strong>You<\/strong> choose the destination; the plugin has no default and sends nothing when the field is empty.<\/p>\n\n<h4>Your responsibilities<\/h4>\n\n<p>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\/.<\/p>\n\n<h3>Shortcodes<\/h3>\n\n<p>Two shortcodes, for two different kinds of page.<\/p>\n\n<p><strong><code>[mybot]<\/code> \u2014 the chat widget, inline.<\/strong> Put it on a support page when you want the conversation itself to be the page.<\/p>\n\n<pre><code>`\n<\/code><\/pre>\n\n<p>[mybot title=\"Support\" height=\"480\" theme=\"light\" lang=\"en\"]\n    `<\/p>\n\n\n\n\n  Attribute\n  Default\n  What it does\n\n\n\n\n  <code>title<\/code>\n  the bot's name\n  The heading above the chat. Set by you, so the script leaves it alone.\n\n\n  <code>height<\/code>\n  <code>480<\/code>\n  Height in pixels of the inline widget.\n\n\n  <code>theme<\/code>\n  <code>light<\/code>\n  <code>light<\/code> or <code>dark<\/code>.\n\n\n  <code>mode<\/code>\n  <code>auto<\/code>\n  <code>auto<\/code> (the plugin decides), <code>quick<\/code> (always the model) or <code>deep<\/code> (always your content).\n\n\n  <code>lang<\/code>\n  the site's chat language\n  Which language the chat opens in, when it is one of the twelve.\n\n\n\n\n<p><strong><code>[mybot_help]<\/code> \u2014 a help-centre block: search over your own pages, then a way to reach a person.<\/strong> 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 <strong>published<\/strong> pages and posts only \u2014 never the knowledge base you imported, which is private on purpose.<\/p>\n\n<pre><code>`\n<\/code><\/pre>\n\n<p>[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\"]\n    `<\/p>\n\n\n\n\n  Attribute\n  Default\n  What it does\n\n\n\n\n  <code>title<\/code>\n  \"How can we help?\"\n  The heading.\n\n\n  <code>subtitle<\/code>\n  empty\n  One line under the heading.\n\n\n  <code>placeholder<\/code>\n  \"Search the help pages\"\n  Text inside the search field.\n\n\n  <code>button<\/code>\n  \"Search\"\n  The submit button's label.\n\n\n  <code>label<\/code>\n  \"Search the help pages\"\n  What a screen reader announces for the field.\n\n\n  <code>ask<\/code>\n  \"Ask the assistant instead\"\n  The button that opens the chat.\n\n\n  <code>topics<\/code>\n  empty\n  Comma-separated label and URL pairs, drawn as chips under the box. A refused URL scheme is dropped.\n\n\n\n\n<p>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.<\/p>\n\n<!--section=installation-->\n<ol>\n<li>Upload the <code>mybot<\/code> folder to <code>\/wp-content\/plugins\/<\/code>, or install the ZIP through Plugins &gt; Add New &gt; Upload Plugin.<\/li>\n<li>Activate the plugin. Five database tables are created automatically.<\/li>\n<li>Open <strong>AI House<\/strong> in the admin menu, then go to <strong>Settings &gt; Get started<\/strong> \u2014 it lists the four things still missing, and nothing there is sent anywhere until you add your own API key.<\/li>\n<\/ol>\n\n<h4>Quick start with no cost<\/h4>\n\n<p><strong>AI House &gt; Settings &gt; Get started<\/strong> walks through the four things a new install needs, and says which are still missing. Nothing on that screen runs a test by itself.<\/p>\n\n<ol>\n<li><strong>Step 1 \u2014 connect an AI provider.<\/strong> Choose OpenRouter and paste a key from https:\/\/openrouter.ai\/keys. The key is checked as soon as you paste it. Then press <strong>Test connection<\/strong>: the step's light turns green when the provider actually answers.<\/li>\n<li><strong>Step 2 \u2014 give it something to answer from.<\/strong> Leave <strong>Vector store<\/strong> set to \"None\" to start with: the assistant then answers from the model alone. To answer from your own content, follow <em>Adding your own knowledge<\/em> below and press <strong>Index knowledge<\/strong>.<\/li>\n<li><strong>Step 3 \u2014 prove the knowledge base can be searched.<\/strong> Type a question that is written on your site and press <strong>Look this up<\/strong>. A score above your Minimum score is a pass.<\/li>\n<li><strong>Step 4 \u2014 see what it answers from.<\/strong> This step reports whether the <strong>knowledge lock<\/strong> is on, and points at the card that shows the file it lives in. It has no switch on purpose \u2014 see <em>Answering only from your own content<\/em> below.<\/li>\n<li>Visit your site and open the chat widget.<\/li>\n<\/ol>\n\n<h4>Answering only from your own content (the knowledge lock)<\/h4>\n\n<p>The assistant answers only from your website's content when a file called <strong>the knowledge lock<\/strong> is on your server:<\/p>\n\n<pre><code>`\n<\/code><\/pre>\n\n<p>wp-content\/uploads\/mybot\/khoa-tri-thuc.md\n    `<\/p>\n\n<p>To switch it on, create that file with this block at the top:<\/p>\n\n<pre><code>`\n<\/code><\/pre>\n\n<p>\n    `<\/p>\n\n<pre><code>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 \u2014 there is no button, no admin code, and nothing in this plugin ever writes to that file, so a plugin update cannot change it.\n<\/code><\/pre>\n\n<p>With the lock on, everything is searched against your knowledge base, and a question with nothing above your <strong>Minimum score<\/strong> is refused <strong>without calling the model<\/strong>, 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 <code>MYBOT-LOCK<\/code> block is not a lock file; a file that declares a lock but cannot be read properly (a missing or unknown <code>che-do<\/code>) is treated as <strong>on<\/strong>, with the reason shown on the card.<\/p>\n\n<p>\u26a0\ufe0f <strong>A new install does not arrive locked<\/strong> (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.<\/p>\n\n<h4>Adding your own knowledge<\/h4>\n\n<ol>\n<li>Create a Supabase project in the region closest to your hosting<\/li>\n<li>Run the SQL file from the project <code>sql\/supabase-schema.sql<\/code> in the SQL Editor<\/li>\n<li>Go to <strong>AI House &gt; Settings &gt; Knowledge base<\/strong>, choose Supabase, and paste your project URL and service_role key<\/li>\n<li>Go to <strong>AI House &gt; Knowledge<\/strong>, drop in a file, click Preview, then Import<\/li>\n<li>Go to <strong>Settings &gt; Get started<\/strong> and press <strong>Index knowledge<\/strong>, then let WP-Cron process the queue<\/li>\n<\/ol>\n\n<h4>What works without an account of ours<\/h4>\n\n<p><strong>You do not need an account with us, and there is no paid tier of this plugin.<\/strong> 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.<\/p>\n\n<p>What it does need is <strong>your own<\/strong> account with the provider you choose \u2014 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.<\/p>\n\n<!--section=faq-->\n<dl>\n<dt id=\"does%20this%20train%20or%20fine-tune%20a%20model%3F\"><h3>Does this train or fine-tune a model?<\/h3><\/dt>\n<dd><p>No. It uses RAG. Your content is split into chunks, converted to vectors, and stored. When\na visitor asks something, the closest chunks are retrieved and placed in the prompt.\nNothing is trained, updates are instant, and you can see which source produced each answer.<\/p><\/dd>\n<dt id=\"can%20i%20use%20it%20without%20paying%20for%20ai%3F\"><h3>Can I use it without paying for AI?<\/h3><\/dt>\n<dd><p>Yes. OpenRouter offers free models and Google Gemini has a free embedding tier. Note that\nOpenRouter free models are limited to 20 requests per minute and 50 per day until you have\npurchased 10 credits. AI House checks your remaining quota before calling and returns a\nfriendly message instead of a raw error.<\/p><\/dd>\n<dt id=\"what%20happens%20if%20the%20ai%20provider%20goes%20down%3F\"><h3>What happens if the AI provider goes down?<\/h3><\/dt>\n<dd><p>The circuit breaker opens after a configurable number of consecutive failures and serves\nyour fallback message immediately, without holding a PHP worker. The real reason is\nrecorded and shown on the dashboard.<\/p><\/dd>\n<dt id=\"where%20are%20conversations%20stored%3F\"><h3>Where are conversations stored?<\/h3><\/dt>\n<dd><p>In the <code>{prefix}mybot_messages<\/code> table, written in batches by a background job rather than\nduring the visitor request. Recent messages are sent as context for follow-up questions,\nand old logs are purged automatically.<\/p><\/dd>\n<dt id=\"does%20it%20work%20on%20multisite%3F\"><h3>Does it work on multisite?<\/h3><\/dt>\n<dd><p>Yes. Each site gets its own tables, options and knowledge base.<\/p><\/dd>\n<dt id=\"will%20my%20knowledge%20pages%20appear%20in%20google%3F\"><h3>Will my knowledge pages appear in Google?<\/h3><\/dt>\n<dd><p>No. The knowledge post type is not public, so it creates no front-end pages and is\nexcluded from search. Citations therefore show the title only. If you want linked\ncitations, index regular pages or posts instead.<\/p><\/dd>\n<dt id=\"which%20file%20formats%20can%20i%20import%3F\"><h3>Which file formats can I import?<\/h3><\/dt>\n<dd><p>Markdown, CSV, JSON and plain text. PDF and DOCX are not supported because extracting\ntext from them requires a PHP library that most shared hosts cannot install. Convert those\ndocuments to Markdown first.<\/p><\/dd>\n\n<\/dl>\n\n<!--section=changelog-->\n<h4>1.22.0 (26\/10\/2026)<\/h4>\n\n<ul>\n<li><strong>Cited links for knowledge imported from a file.<\/strong> Citations have always carried a URL and the widget has always drawn it as a link \u2014 but the URL came from <code>get_permalink()<\/code>, 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 <strong>link<\/strong>, set in a <strong>Citation link<\/strong> box on that item's edit screen or by a <code>url<\/code> column in the import file (all three templates include it).<\/li>\n<li><strong>A link is accepted only inside this site:<\/strong> a path such as <code>\/dang-ky<\/code>, or a URL on the same host including <code>www.<\/code>. Another domain, <code>javascript:<\/code>, and an empty value are refused, and the check runs when the link is <strong>read<\/strong> 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.<\/li>\n<li>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.<\/li>\n<\/ul>\n\n<h4>1.21.0 (18\/10\/2026)<\/h4>\n\n<ul>\n<li><strong>This plugin's name changed.<\/strong> The first submission was rejected because \"MyBot\" was too close to an existing chatbot product called MyBot.chat. It is now <strong>AI House Knowledge Chat<\/strong>, the slug is <code>aihouse<\/code>, 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.<\/li>\n<li><strong>The description no longer compares this plugin to others<\/strong>, 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.<\/li>\n<li>No setting was renamed and nothing else changed: options, knowledge base and conversations are untouched.<\/li>\n<\/ul>\n\n<h4>1.20.0 (18\/10\/2026)<\/h4>\n\n<ul>\n<li><strong>New: a help-centre shortcode, <code>[mybot_help]<\/code>.<\/strong> 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. <code>[mybot]<\/code> still embeds the chat itself, inline, at whatever height you give it.<\/li>\n<li><strong>The search finds your published pages and posts, and nothing else.<\/strong> It never returns the knowledge base you imported \u2014 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.<\/li>\n<li><strong>Nothing on the help page can be turned into markup.<\/strong> The title, the labels and the topic links are escaped when printed, a <code>javascript:<\/code> topic link is refused, results are written with <code>textContent<\/code>, and the excerpt the server sends has its tags and shortcodes stripped \u2014 so a page title containing a script is shown as text.<\/li>\n<li><strong>A search box that cannot be used as free workload:<\/strong> at least two characters, at most 120, six results, and the same rate limiter as the chat. One character returns nothing and says why.<\/li>\n<li>No new option, no database change: install over 1.19.0, and a page that already uses <code>[mybot]<\/code> keeps working unchanged.<\/li>\n<\/ul>\n\n<h4>1.19.0 (18\/10\/2026)<\/h4>\n\n<ul>\n<li><strong>The directory gave this plugin the slug <code>aihouse<\/code>, not <code>mybot<\/code>, and WordPress.org requires the text domain to equal the slug.<\/strong> The first submission scan came back with about three hundred <code>TextDomainMismatch<\/code> errors; they were all one fact. The text domain is now <code>aihouse<\/code> in 976 places: 656 PHP strings, 160 in the admin sources, 160 in the built bundle, the plugin header, <code>load_plugin_textdomain()<\/code>, the <code>plugin_locale<\/code> filter, the translation file names, and the Jed key the dashboard reads.<\/li>\n<li><strong>Renamed by tokenizer, not by search-and-replace.<\/strong> 674 bare <code>'mybot'<\/code> literals exist in this codebase and most of them have to stay: the <code>[mybot]<\/code> shortcode, the admin page slug, the cache group, the menu parent, the Supabase\/Pinecone namespace, the folder the knowledge file lives in, <code>function_exists( 'mybot' )<\/code>. Only the ones that are the text-domain argument of a gettext call were touched \u2014 18 were deliberately left alone, and the tool lists them so a person can check.<\/li>\n<li><strong><code>Plugin URI<\/code> no longer points at <code>example.com<\/code>.<\/strong> 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 \u2014 a unique https URL is what the directory asks for.<\/li>\n<li><strong>Twenty <code>translators:<\/code> comments added and five exception messages annotated.<\/strong> 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 <code>throw<\/code> carries a one-line reason explaining that an exception message is not HTML.<\/li>\n<li><strong>New guard: <code>tools\/check-textdomain.mjs<\/code><\/strong> \u2014 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\" \u2014 a value that was consistent everywhere and wrong everywhere.<\/li>\n<li>No new option, no database change: install over 1.18.4. <strong>Your saved settings are untouched<\/strong> \u2014 option names did not change, only the text domain used for translations.<\/li>\n<\/ul>\n\n<h4>1.18.4 (18\/10\/2026)<\/h4>\n\n<ul>\n<li><strong>Screenshots exist now.<\/strong> Five of them, taken on a running site rather than mocked up: <code>Get started<\/code>, the widget on a real page, <code>Overview<\/code>, <code>Knowledge<\/code>, <code>Customers<\/code>. The <code>== Screenshots ==<\/code> section that 1.18.2 had to remove is back, and its five captions match five real files (1600 px wide PNG, held in <code>assets\/wp-org\/<\/code>).<\/li>\n<li>Where they go: WordPress.org reads <code>screenshot-1.png<\/code> \u2026 <code>screenshot-5.png<\/code> from <strong>SVN <code>\/assets\/<\/code><\/strong>, not from the plugin ZIP \u2014 so this release carries the captions, and the pictures are committed separately next to the icon and banner.<\/li>\n<li>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.<\/li>\n<\/ul>\n\n<h4>1.18.3 (18\/10\/2026)<\/h4>\n\n<ul>\n<li><strong><code>Contributors:<\/code> now carries the real WordPress.org username.<\/strong> It held the placeholder <code>aihouse<\/code>; the profile that a plugin page credits is read from this field, so a placeholder means the page credits nobody. It is <code>vunguyenbh<\/code> now. Nothing else changed: no code, no setting, no string, no database.<\/li>\n<li>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.<\/li>\n<\/ul>\n\n<h4>1.18.2 (18\/10\/2026)<\/h4>\n\n<ul>\n<li><strong>The copy submitted to the directory, checked against the directory's own rules.<\/strong> Tags are down from eight to five (the directory accepts at most five, and the brand names of model vendors were dropped \u2014 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), <code>Tested up to<\/code> 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 <code>docs\/19<\/code>.<\/li>\n<li><strong>Two places printed without escaping, found by running the coding-standard check the way it is documented<\/strong> (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 <code>phpcs:ignore<\/code> never covered it, and the requests screen formatted <code>Request #%d<\/code> with an integer the reader could not see had been cast. Both now escape a finished string.<\/li>\n<li><strong>A number I published was wrong, and this release corrects it.<\/strong> 1.18.1 recorded \"46 errors \/ 16 warnings, exactly the previous level\". Re-measured on all three release trees with the documented command: <strong>1.16.0 = 46\/16<\/strong>, <strong>1.18.0 = 100\/53<\/strong>, <strong>1.18.2 = 42\/16<\/strong>. The 46\/16 figure belonged to 1.16.0, not to 1.18.1 \u2014 54 errors came in with the new files and I had not measured them. The real table is in <code>docs\/18<\/code>, and 92 of them were fixed mechanically with <code>phpcbf<\/code> (layout only).<\/li>\n<li><strong>Evidence that the reformat changed layout and nothing else:<\/strong> every string literal in the three files <code>phpcbf<\/code> 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.<\/li>\n<li><strong>The 42 errors and 16 warnings that remain are each accounted for<\/strong> in <code>docs\/18<\/code>: 20 missing translator comments, 9 block comments, 8 <code>current_time( 'timestamp' )<\/code> calls, 5 exception messages (an exception is not output), 3 <code>meta_key<\/code> reads on an indexed key, one interpolated <code>%s,%s,%s<\/code> built from a count rather than from user data, and one <code>$submenu<\/code> \u2014 which is how WordPress lets a plugin order its submenu. None of them reaches a page.<\/li>\n<li>No new option, no database change, no new translatable string: install this over 1.18.1.<\/li>\n<\/ul>\n\n<h4>1.18.1 (16\/10\/2026)<\/h4>\n\n<ul>\n<li><strong>Security review before the WordPress.org submission<\/strong>, written up in the project as docs\/18: the measured attack surface (19 REST routes, 3 admin-post actions, <strong>no upload path at all<\/strong>, 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.<\/li>\n<li><strong>Two real fixes.<\/strong> The chat route refused an oversized picture only <em>after<\/em> decoding it, so a 50 MB payload cost the server about 37 MB of memory before the refusal \u2014 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.<\/li>\n<li><strong>New automated check (9 rules, 10 injected-fault controls)<\/strong> 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 <em>and<\/em> a capability on every admin action, an uninstall that removes all six tables, and the readme fields the directory requires.<\/li>\n<li><strong>No new coding-standard violations<\/strong> (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.<\/li>\n<\/ul>\n\n<h4>1.18.0 (16\/10\/2026)<\/h4>\n\n<ul>\n<li><strong>Fixed: the bot refused a thank-you.<\/strong> A visitor wrote \"thanks for your support\" and received \"this question is outside the information this website holds\" \u2014 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 \u2014 so the lock stays a lock.<\/li>\n<li><strong>Vietnamese with or without tones is the same person:<\/strong> \"cam on ban da ho tro\" matches \"c\u1ea3m \u01a1n b\u1ea1n \u0111\u00e3 h\u1ed7 tr\u1ee3\".<\/li>\n<li><strong>The default System prompt now carries the written guideline:<\/strong> 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 \u2014 say plainly that you do not know and offer a human instead. A prompt you have edited yourself is never overwritten.<\/li>\n<li>No new option, no database change, no new translatable string: install this over 1.17.0.<\/li>\n<\/ul>\n\n<h4>1.17.0 (16\/10\/2026)<\/h4>\n\n<ul>\n<li><strong>New: the chat can now collect your visitors' contact details, and you can read them on a screen of their own.<\/strong> A person icon in the message box opens a short form (a name, and <strong>either<\/strong> a phone number <strong>or<\/strong> an email address), and after three questions the chat offers the form <strong>once<\/strong>, as a bubble with a button \u2014 never a form that opens by itself, and never anything that blocks the conversation.<\/li>\n<li><strong>New screen: Customers.<\/strong> Name, how to reach them (and which channel they prefer), whether they agreed to be contacted, their note, language and date \u2014 with <strong>Download CSV<\/strong> and a <strong>Delete<\/strong> button on every row. The CSV opens in Excel with Vietnamese characters intact, and any cell a visitor typed that begins with <code>=<\/code>, <code>+<\/code>, <code>-<\/code> or <code>@<\/code> is neutralised: visitor text is untrusted input, and Excel would otherwise run it as a formula on the computer of whoever exported it.<\/li>\n<li><strong>One person is one card.<\/strong> 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 (<code>+84 912-345-678<\/code> and <code>0912345678<\/code> are the same person), and an empty field never overwrites something the visitor told you earlier.<\/li>\n<li><strong>Consent is recorded so that it can be proved:<\/strong> not only a tick, but the exact sentence the visitor agreed to <strong>and<\/strong> the moment they agreed \u2014 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.<\/li>\n<li><strong>WordPress' two privacy tools now cover this data.<\/strong> Tools \u2192 <strong>Export Personal Data<\/strong> and Tools \u2192 <strong>Erase Personal Data<\/strong> 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\".<\/li>\n<li><strong>The details never leave your site:<\/strong> they are not copied to Supabase, not put into the knowledge base, and the card records only the page path \u2014 never its query string. A card is deleted when you delete it, and the whole table is dropped if the plugin is uninstalled.<\/li>\n<li><strong>One database change<\/strong> (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.<\/li>\n<\/ul>\n\n<h4>1.16.0 (16\/10\/2026)<\/h4>\n\n<ul>\n<li><strong>All the tools moved to the left of the message box.<\/strong> 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.<\/li>\n<li><strong>Send is an arrow now, not the word.<\/strong> 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 \u2014 it moved to the three places that give a button its NAME (<code>aria-label<\/code>, <code>title<\/code>, 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\u1eedi\" on a Vietnamese chat window.<\/li>\n<li><strong>The box strengthens while there is something to send.<\/strong> 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 \u2014 a control that merely looks quiet is honest, while a truly disabled one would be a button that does nothing.<\/li>\n<li><strong>Fixed quietly on the way:<\/strong> the widget used to rely on your theme for the <code>.screen-reader-text<\/code> class. On a theme that does not define it, the hidden labels would have become visible text \u2014 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.<\/li>\n<li><strong>No new option, no database change, no new translatable string<\/strong> \u2014 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.<\/li>\n<\/ul>\n\n<h4>1.15.0 (15\/10\/2026)<\/h4>\n\n<ul>\n<li><strong>The message box has been redrawn<\/strong> into the shape of the chat app composer the plugin's author sent a picture of: <strong>flat icons in a row along the bottom<\/strong>, and <strong>the field as a filled pill<\/strong>.<\/li>\n<li><strong>The microphone and the picture button lost their outlines.<\/strong> 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.<\/li>\n<li><strong>The field lost its border and gained a background<\/strong> \u2014 a filled pill that turns white and takes your site's colour as a ring while it is focused. The <strong>Send<\/strong> button is rounded to match, and <strong>all three controls are now exactly the same height<\/strong>, so the row is straight instead of a pixel out here and there.<\/li>\n<li><strong>The strip that shows a picture before it is sent<\/strong> was redrawn to match: no outlines, and a round remove button with a soft red background on hover.<\/li>\n<li><strong>The dark theme was corrected in the direction that is easy to get wrong:<\/strong> on a dark panel the pill is <em>lighter<\/em> than what is behind it, because a dark field on a dark panel disappears.<\/li>\n<li><strong>Nothing was renamed<\/strong> \u2014 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.<\/li>\n<li><strong>Two new automated rules protect the new shape<\/strong>, 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.<\/li>\n<li>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.<\/li>\n<\/ul>\n\n<h4>1.14.1 (14\/10\/2026)<\/h4>\n\n<ul>\n<li><strong>Fixed: the attach-a-picture button in 1.14.0 opened the file picker, but choosing a photo then did nothing.<\/strong> 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.<\/li>\n<li><strong>Why this release exists at all:<\/strong> every automated check in this project passed while the button was dead. They are all <em>readers<\/em> \u2014 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 <code>hidden<\/code> 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.<\/li>\n<li><strong>New: a check that clicks the buttons.<\/strong> 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 \u2014 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.<\/li>\n<li><strong>Fixed in the same release:<\/strong> 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 \u2014 in both cases the button must stay hidden rather than appear and refuse.<\/li>\n<li>No new option, no database change, no new translatable string: install this over 1.14.0.<\/li>\n<\/ul>\n\n<h4>1.14.0 (14\/10\/2026)<\/h4>\n\n<ul>\n<li><strong>New: the attach-a-picture button, so a visitor can send a photo with their question<\/strong> \u2014 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 <code>Test connection<\/code> answers).<\/li>\n<li><strong>Pictures are NOT put in the Media Library.<\/strong> Anything under <code>uploads\/<\/code> 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.<\/li>\n<li><strong>Only an administrator can look at one, and only through a permission-checked link.<\/strong> A picture is addressed by <em>message number<\/em>, never by file name; the request is refused unless you are logged in with <code>manage_options<\/code>; 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.<\/li>\n<li><strong>A picture is sent together with the question, inside the normal chat request<\/strong> \u2014 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 <strong>text<\/strong>, 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.<\/li>\n<li><strong>Photos are shrunk in the browser before they are sent:<\/strong> longest side 1024 pixels, JPEG. A phone photo is 3\u20138 MB and 4000 pixels wide, and image cost is charged by area \u2014 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.<\/li>\n<li><strong>Pictures are deleted with the conversation.<\/strong> When the retention job removes old conversations it removes their files first and their rows second \u2014 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.<\/li>\n<li><strong>New on the Requests screen: \"Pictures visitors sent\".<\/strong> 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 \u2014 that is the honest state of it.)<\/li>\n<li><strong>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<\/strong>, and the upgrade step returns early while that version matches \u2014 so an existing site would have kept the old 13-column table while the logger now writes 14, which would have broken <em>every<\/em> 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.<\/li>\n<li><strong>Privacy, in plain words:<\/strong> the picture is stored on <strong>your<\/strong> site, readable by <strong>your<\/strong> administrators, and deleted with the conversation it belongs to; it is not copied to Supabase. On a host running <strong>Nginx<\/strong>, <code>.htaccess<\/code> is ignored, so the folder is not actively denied there \u2014 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.<\/li>\n<li>20 new translatable strings, all translated; no new option; one database change (<code>mybot_messages<\/code> gains a column, applied automatically on upgrade).<\/li>\n<\/ul>\n\n<h4>1.13.0 (14\/10\/2026)<\/h4>\n\n<ul>\n<li><strong>New: a microphone in the chat, so a visitor can speak their question instead of typing it.<\/strong> The words are recognised by the browser's own speech service and arrive in the text box as text \u2014 they are <strong>not<\/strong> 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.<\/li>\n<li><strong>The button is hidden in the HTML and only revealed when the browser can actually recognise speech.<\/strong> 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 \u2014 including the one CSS line (<code>display<\/code>) that would silently override <code>hidden<\/code> in every browser.<\/li>\n<li><strong>The language is asked for properly.<\/strong> The speech API wants a tag like <code>vi-VN<\/code>, while this plugin's language codes are two letters; given <code>vi<\/code> 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 <em>its own<\/em> locale, silently).<\/li>\n<li><strong>No audio is recorded, stored or uploaded \u2014 only text.<\/strong> 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 <em>your<\/em> server or <em>your<\/em> AI provider, but browsers implement this by sending it to their own vendor's service (Chrome to Google).<\/li>\n<li><strong>What is still missing:<\/strong> the attach-an-image button. That is the next step and the bigger one \u2014 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.<\/li>\n<li>No new option, no database change, and no new translatable admin string (the two labels are per-language <em>content<\/em>, like the rest of the widget's words): install over the top.<\/li>\n<\/ul>\n\n<h4>1.12.1 (14\/10\/2026)<\/h4>\n\n<ul>\n<li><strong>Fixed (important): 1.12.0 could take a whole site down if the plugin folder was only partly updated.<\/strong> 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 <em>interface<\/em>, and PHP resolves an unimplemented interface method the moment the class file is loaded \u2014 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).<\/li>\n<li><strong>Fixed by making those two methods optional<\/strong> 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.<\/li>\n<li><strong>The rule this leaves behind:<\/strong> 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 <code>mybot<\/code> folder and install this version fresh \u2014 a partly copied folder is what breaks, so removing it first is the fix.<\/li>\n<li>No new option, no database change, no string change: install over the top.<\/li>\n<\/ul>\n\n<h4>1.12.0 (13\/10\/2026)<\/h4>\n\n<ul>\n<li><strong>New: AI House can now be asked whether the chosen model can read an image, and <code>Test connection<\/code> prints the answer.<\/strong> 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, <em>after<\/em> the request is paid for \u2014 so the plugin answers the question first, on the screen you already use to check the connection.<\/li>\n<li><strong>New: message content can carry a picture.<\/strong> 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.<\/li>\n<li><strong>Nothing is ever dropped.<\/strong> A part the destination format cannot express still travels \u2014 as text carrying its own JSON \u2014 instead of vanishing. A silently ignored photo is worse than an error, because the answer looks like an answer.<\/li>\n<li><strong>The text path is unchanged, and that is measured, not promised:<\/strong> for a normal question, OpenAI and OpenRouter still receive <code>content<\/code> as a plain string and Gemini still receives exactly one text part, byte for byte as in 1.11.1.<\/li>\n<li><strong>The list of image-capable models is a hand-kept belief, not a measurement<\/strong> \u2014 no provider publishes a field this plugin can read, and the  \u2026<\/li>\n<\/ul>","raw_excerpt":"AI chatbot: quick questions go to an LLM, deeper ones are answered from your own content with RAG. Made for shared hosting.","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/ory.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin\/390047","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/ory.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin"}],"about":[{"href":"https:\/\/ory.wordpress.org\/plugins\/wp-json\/wp\/v2\/types\/plugin"}],"replies":[{"embeddable":true,"href":"https:\/\/ory.wordpress.org\/plugins\/wp-json\/wp\/v2\/comments?post=390047"}],"author":[{"embeddable":true,"href":"https:\/\/ory.wordpress.org\/plugins\/wp-json\/wporg\/v1\/users\/vunguyenbh"}],"wp:attachment":[{"href":"https:\/\/ory.wordpress.org\/plugins\/wp-json\/wp\/v2\/media?parent=390047"}],"wp:term":[{"taxonomy":"plugin_section","embeddable":true,"href":"https:\/\/ory.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_section?post=390047"},{"taxonomy":"plugin_tags","embeddable":true,"href":"https:\/\/ory.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_tags?post=390047"},{"taxonomy":"plugin_category","embeddable":true,"href":"https:\/\/ory.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_category?post=390047"},{"taxonomy":"plugin_contributors","embeddable":true,"href":"https:\/\/ory.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_contributors?post=390047"},{"taxonomy":"plugin_business_model","embeddable":true,"href":"https:\/\/ory.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_business_model?post=390047"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}