Media:
- media rows now take the private flag from the caller instead of
guessing it from the model type
- agent avatars upload as public so help center readers can load them,
contact and visitor avatars stay private
- backfill existing agent avatars to public
Public pages:
- dedupe article feedback by reader IP for 24 hours
- keep the article locale when submitting feedback
- skip view count bumps from crawlers on the JSON API
- only log a search when the search itself worked
Admin and data:
- lock the help center row while creating an article, since slugs are
unique per help center but the DB index is per collection
- block changing a collection language when it still has articles or
sub collections
- validate article locale on every update, not only when the collection
changes
- reject slugs with characters that break /hc/ URLs
- exclude the whole subtree from the parent picker so a collection
cannot be moved into itself
- drop articles of a paused help center from the AI index
- take the embed generation at enqueue time so jobs commit in edit order
Editor and UI:
- show an error when a YouTube URL is not valid
- do not break the icon picker when the sprite fails to load
- ignore a slow article fetch after another article is opened
i18n: move generic strings like Published, Archived, Slug, Icon, Author,
Language, Text color and Platform to globals, and drop hint text that
just repeated the field label.
Resolved the AI embedding conflicts by keeping this branch's generic embedSource path and layering main's tag indexing on top. Search now covers snippets and help articles, tags stay excluded. Dropped SetContentID since main's link-message-media query stamps content_id itself.
Public pages were missing most of what a search engine needs. They now serve
a sitemap index at /sitemap.xml plus a robots.txt, and every page carries a
canonical URL, hreflang alternates, an x-default, and JSON-LD. Article pages
also send published and modified times. Search pages and the markdown view
are marked noindex. Requests from bots no longer bump the view counters, so
the insights numbers reflect real readers. Offscreen images in articles are
lazy loaded.
Public page text is now translated per locale instead of always using the
desk language, and pages with more than one translation show a language
switcher.
In the admin, collections and articles can be dragged to reorder, and an
article can be dragged into a different collection. Both save through new
sort order endpoints. Collections can have a lucide icon, picked from a new
icon picker backed by a sprite sheet. The landing page has layout options
for card grid or one per row, cards per row, and which details to show.
The help center list is a data table now, so HelpCenterCard is gone. The
tree page moved into AdminSplitLayout, its actions sit on their own row
below the breadcrumb, and it gained visit site and expand/collapse all.
Tag suggestion used to send the first 300 tags to the model, so anything past
that was invisible. Tags are now embedded into the same index as the knowledge
base, and only the tags most similar to the conversation get sent. The reconcile
loop embeds new and renamed tags and drops vectors for deleted ones, and a
provider change purges them so they are rebuilt with the new model.
Review pass over the help center commit. Bug fixes:
- Image-only or video-only articles were rejected on save because the
editor blanked the content when it had no text.
- An article could be moved into a collection in another help center.
Slugs are only unique per help center, so two articles could end up
on the same public URL.
- The article editor fired the "agent is typing" indicator into
whatever conversation was last open, which the contact could see.
- Unsupported locales like /hc/x/zz-ZZ returned an empty 200 page with
a canonical URL, so crawlers saw unlimited URLs. They 404 now.
- Article feedback was accepted for paused help centers.
- French pages announced lang="en".
- Editing a help center popped validation errors mid-keystroke.
Shared code that was duplicated:
- Help articles had their own copy of the snippet embedding lifecycle.
The copy was missing the wait group, the concurrency cap and the
generation gating, so article embeds were killed at shutdown, all hit
the provider at once, and a slow embed could overwrite a newer edit or
bring back a deleted article. Both content types now go through one
embedSource lifecycle. Fingerprints are unchanged, so nothing
re-embeds on deploy.
- Callout and collapsible CSS was written twice, once for the editor and
once for the public page. They now share article-content.css, so the
editor stays WYSIWYG.
- The excerpt was computed three ways. The page uses the stored one.
Less work at runtime:
- The admin tree no longer sends every article's HTML. The edit sheet
loads the one article it needs. Publishing an article used to
re-download the whole help center.
- Embedding reconcile no longer reads every article body every minute.
- The tree view fetched the help center twice on load.
Cleanups: one update-article route instead of two, media privacy comes
from the model type instead of a bool threaded through the handler, a
LinkListField component replaces three copies of the same repeater,
TreeNode renders the article row once, and 12 duplicate or dead i18n
keys are gone.
Agents can now publish a public knowledge base per help center.
Backend:
- new internal/helpcenter package for help centers, collections and articles
- admin JSON API behind a new help_center:manage permission
- public /hc/{slug} pages, JSON API, search and sitemap, all rate limited
- migration v2.7.0 for the new tables
AI:
- published articles with the AI flag on get embedded, so the agent answers
from them. Unpublishing or clearing the flag removes the embeddings.
Media:
- media rows now carry a private flag. Help article images are public so the
public pages can serve them without auth, and only agents with
help_center:manage can upload them.
Frontend:
- admin help center list, tree view and article editor
- the old TextEditor is split into ArticleEditor and ConversationEditor over a
shared useTextEditor composable, so article-only tools like callouts,
collapsibles and YouTube embeds stay out of the reply box
Knowledge base: fold the embedding provider base URL into the snippet
fingerprint so re-pointing the provider triggers a reindex even when the
model name is unchanged. Reject empty knowledge base content. Cap
concurrent background snippet embeds at 4 and tie embedding work to the
app lifecycle context. Reindex when the embedding base URL changes, not
just the model or dimensions.
Prompt injection: neutralize << >> block delimiters in snippets,
transcripts, subjects, and contact fields so untrusted content can't
forge a boundary the model relies on.
OTP: set the verified flag inside the Lua match script so verification
is atomic. Only count codes that were actually emailed toward the resend
cap, and check the cap before sending instead of incrementing up front.
Tools: fetch only the enabled tools among the allowed IDs, and route all
registrations through one helper so custom tools can't shadow built-ins.
Agent queue: hand a dropped response job off to a human instead of
leaving the conversation assigned to the assistant with no reply.
Also let GetAllConversationMessages return every message when limit is
non-positive, populate admin forms without triggering validation, and
default the copilot name to Juno.
Custom tools can now require a verified contact before they run. This gives tools a trustworthy signal about who the customer is without relying on DMARC or the JWT login.
ai_tools gets a requires_verification column, defaulting to true (fail-closed). A flagged tool is blocked in httpTool.Execute until the conversation is verified, and every tool call now carries an X-Libredesk-Contact-Verified header so tool authors can tell an OTP-verified contact from a self-claimed one.
The AI agent gets three native tools: send_email_verification emails a 6-digit code out of band, check_email_verification confirms it, and set_contact_email lets an anonymous visitor add an email to send the code to. Codes and the verified window live in Redis, scoped per conversation, with attempt and resend caps. JWT livechat contacts stay trusted without OTP; email contacts and visitors verify by code.
The tool admin form gets a "require verified contact" toggle (default on) with a confirm dialog when turning it off.
Copilot and Generate Reply get four read-only, access-filtered tools to look up
a customer's history: list the contact's other conversations, search
conversations by exact email, fetch one conversation by reference number, and
search contacts. Tool output is marked untrusted and capped so a big response
can't blow the context window.
Copilot panel changes:
- per-agent persona picker that borrows an enabled assistant's voice, language
and instructions without changing the tool set (stored in localStorage)
- answers render as HTML; copy, insert-into-reply, and add-as-private-note
actions per answer
- chat history moves to its own copilot store; server history is persisted only
after a successful reply and read back with a limit
Adds AI tag suggestions: a new endpoint suggests up to 3 existing tags for a
conversation, applied from the sidebar, never auto-applied.
Hardening and fixes:
- OIDC callback rejects non-agent users
- custom tool URLs and params schema validated on save; tool HTTP client no
longer follows redirects; query keys pinned in the tool URL win
- GetAllConversationMessages takes an explicit limit (cap 1000)
- FAQ mining skips a candidate already pending review
- ai agent handoff records its event only after the move actually lands
- share chat message role constants; rename v2.7.0 migration to v2.6.0 and add
the fix_grammar_spelling prompt
Correctness fixes from the internal/ai code review:
- gate reindex commits on the snippet row still existing, so a delete
racing reconcile can't re-insert its embeddings forever
- cap one logical provider request at 90s across all retries, so a
hanging provider can't stall the reply box for minutes
- return proper error envelopes from the copilot message store instead
of raw sqlx errors that surfaced as "Error interface conversion failed"
- reject blank AI replies in the generate-reply and copilot handlers
- build GET tool URLs with url.Parse so a # in the URL doesn't swallow
the query params
- let a blank api_key clear the stored key (masked keeps it), restoring
a way to disable AI
Cleanups and hardening:
- share one SSRF transport between tool and provider clients instead of
building a new transport per AI call
- copy the provider config struct instead of listing every field
- backfill temperature 0.7 for configured providers upgrading from
releases that hardcoded it
- only pass user/assistant roles from copilot history to the provider
- reject enc:-prefixed secrets that would be stored raw and fail decrypt
- rune-safe truncation of test errors, one-line doc comments
The reply-box AI prompts used a legacy inline dialog to set the OpenAI key, hitting a separate PUT /api/v1/ai/provider endpoint. That is now fully covered by the admin provider settings page, so drop the dialog, the endpoint, the UpdateProvider method, the set-completion-key query, and the orphaned i18n keys. With no provider configured an agent now gets the 'ask your administrator' toast instead.
Also make the inline AI prompts (Make Friendly, etc.) toggle the existing isGenerating state so the editor shows the same generating animation as Generate Reply, with a guard against overlapping runs.
Snippet import: new "Import from URL" flow fetches a page and stores its
readable content as a snippet. Extraction uses the mackee/go-readability
library (Mozilla Readability port) and outputs Markdown, so nav/footer
boilerplate is dropped. The snippet list shows the source, and the edit
dialog is now wider with a taller content box.
Summarize: new "Summarize with AI" action on a conversation calls the AI and
adds the result as a private note. It shows an info toast right away so the
user knows it started, since the call can take a few seconds. This adds an
"info" toast variant that any feature can use.
Assistant languages: assistants can be given a list of allowed reply
languages. The assistant replies in the customer's language when it is one of
them, otherwise it falls back to the first. The preview also lists the
knowledge sources it used.
Cleanup: replace hardcoded gray/zinc/white colors with theme tokens
(text-muted-foreground, text-foreground, bg-accent) across several components.
Medium (title + short body):
add WIP autonomous AI agent
New internal/aiagent package runs AI assistants that reply to customers
on conversations assigned to them, grounded on a knowledge base. Also mines
resolved conversations for FAQ suggestions. Adds admin UI and the v2.7.0
schema. Still work in progress.
Adds two agent-facing AI features: a copilot chat panel in the conversation
sidebar and a generate-reply button in the reply box. Both run an agentic
tool-calling loop whose first tool searches the knowledge base.
Snippets are chunked and embedded on save, then searched in memory with
brute-force cosine similarity (no pgvector). Providers are split into completion
and embedding types. Both are OpenAI-compatible and the API key is encrypted at
rest. Admins can also register custom HTTP tools the model can call.
A new admin AI settings page covers provider config, snippets, and tools. The
v2.6.0 migration and schema add the ai_knowledge_base, embeddings, and ai_tools
tables plus the ai_providers type column.
- Translate web template pass i18n dependency
- Fix colors in menu card
- Show update description if avaialble in AppUpdate component
- Remvoe i18n from settings as i18n and settings depend on each other to load initial lang.
- Clear inbox password as the update SQL query now returns the config.
- Fetch agents and inboxes from the store instead of directly fetching using axios instance.
- feat: full screen tiptap editor, esentially two editors that attempt to keep their state in sync.
- moves tiptap editor menu bar to to show up as a bubble once text is selected.
- Layout fixes and improvements.
- Improves /reports/overview charts and cards.