Notes for developers extending the module or integrating with it.
Events the module listens to
| Identifier | Events | Effect |
|---|---|---|
Omeka\Api\Adapter\ItemAdapter |
api.create.post, api.update.post, api.delete.post |
Queue an item index job, or delete its chunks |
Omeka\Api\Adapter\MediaAdapter |
same | Queue a media index job, or delete its chunks |
Omeka\Api\Adapter\SitePageAdapter |
same | Queue a site page index job, or delete its chunks |
Blog |
blog.post.create, blog.post.update, blog.post.delete with a post_id parameter |
Queue a blog post index job, or delete its chunks |
Omeka\Controller\Admin\Item |
view.browse.after |
Add the re-index options to the batch actions dropdown |
* |
view.layout |
Inject the global widget on public site pages |
Any module that triggers the Blog events with a post_id parameter gets its posts indexed, provided the blog_post and blog_post_site tables exist.
Jobs
| Job | Arguments | Purpose |
|---|---|---|
Chatbot\Job\IndexItemJob |
action (index or delete), resource_type (item, media, blog_post, site_page), resource_id |
Index or remove one resource |
Chatbot\Job\BulkIndexJob |
item_ids (array, or null for all), resource_types (array, default ['item']) |
Rebuild whole content types |
Chatbot\Job\PurgeChatbotLog |
none | Delete log entries older than the retention period |
Dispatch them with Omeka\Job\Dispatcher like any other Omeka job.
Services
All are registered in the service manager under their class names.
| Service | Role |
|---|---|
Chatbot\Service\TursoService |
HTTP client for the Turso pipeline API: insert, delete, search, index rebuild, table rebuild, chunk count |
Chatbot\Service\IndexService |
Text extraction, chunking, embedding and storage: indexItem(), indexMedia(), indexBlogPost(), indexSitePage(), deleteResource() |
Chatbot\Service\ChatService |
chat() and semanticSearch() |
Chatbot\Service\ChatbotLogger |
Writes to module_chatbot_log: info(), warning(), error() with an optional context array |
Chatbot\Service\RateLimitService |
Per-IP fixed-window counter in module_chatbot_rate_limit |
Chatbot\Service\EncryptionService |
AES-256-CBC encrypt and decrypt with a key derived from chatbot.encryption_key |
Chatbot\Provider\OpenAiProvider |
Embeddings and chat completions over the Laminas HTTP client; retries once after 5 seconds on HTTP 429 |
Provider interfaces
OpenAiProvider implements both interfaces. To add another backend, implement them, register a factory in config/module.config.php under service_manager.factories, and change ChatServiceFactory and IndexServiceFactory to fetch your provider instead of OpenAiProvider.
interface EmbeddingsProviderInterface
{
/** @return float[] */
public function embed(string $text, int $dimensions = 0): array;
public function getName(): string;
}
interface LLMProviderInterface
{
public function complete(array $messages, string $model, float $temperature = 0.2, int $maxTokens = 1024): string;
public function getName(): string;
}
Turso schema
Created automatically on first connection and dropped by Purge All Index Data:
CREATE TABLE chunks (
id INTEGER PRIMARY KEY AUTOINCREMENT,
resource_type TEXT NOT NULL,
resource_id INTEGER NOT NULL,
chunk_id INTEGER NOT NULL,
chunk_text TEXT NOT NULL,
metadata TEXT, -- JSON: title, resource_type, site_ids, item_set_ids (items and media only)
url TEXT,
created_at TEXT NOT NULL,
embedding F32_BLOB(3072) -- size follows the Embedding dimensions setting
);
CREATE INDEX chunks_vec_idx ON chunks (libsql_vector_idx(embedding));
Searches use vector_top_k('chunks_vec_idx', vector32('[...]'), k) and order by vector_distance_cos. The DISKANN index does not see rows inserted after it was built, so the module drops and recreates it after each single-resource index and once at the end of a bulk job.
MySQL tables
module_chatbot_log:id,level,event,message,context(JSON),created_atmodule_chatbot_rate_limit:ip_address,window_start,request_count
Front-end scripts
Both scripts are plain JavaScript with no dependencies, so a theme can mount them in custom markup.
ChatbotWidget.init({ apiUrl, mode: 'widget' | 'inline', container, systemPrompt, heading, siteId, siteSlug })fromasset/js/chatbot.js.mode: 'widget'appends a floating button and panel to the body;mode: 'inline'renders the panel insidecontainer.SemanticSearch.init({ apiUrl, container, perPage, placeholder, siteId, siteSlug })fromasset/js/semantic-search.js.
Get the endpoint URLs from the chatbot-api-chat and chatbot-api-search routes, for example $this->url('chatbot-api-chat') in a view.
Tests
Unit tests use PHPUnit mocks and need no database or API keys. From the Omeka S root:
vendor/bin/phpunit -c modules/Chatbot/test/phpunit.xml