AI House Knowledge Chat

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

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.

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.

FAQ

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.

Reviews

There are no reviews for this plugin.

Contributors & Developers

“AI House Knowledge Chat” is open source software. The following people have contributed to this plugin.

Contributors

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 …