syafiq-unijaya/laravel-ai-chatbox
/ai-chatbox/admin/conversations/data endpoint now accepts a search query parameter; conversations are filtered server-side to those containing at least one message matching the term (SQL LIKE) so large conversation lists can be narrowed down without a page reloadsaveMessage() on ConversationRepositoryInterface — new interface method that persists a single message immediately (before the AI call starts); DatabaseConversationRepository creates the conversation record if it doesn't exist and appends the message directly; SessionConversationRepository is a no-op (session history is saved atomically by saveHistory())output_buffering ini setting — warns when enabled, as it can silently buffer SSE tokens before they reach the browserproxy_buffering off / X-Accel-Buffering: no is set (the package sets the header automatically, but NginX config may override it)mod_deflate gzip compression buffering SSE streamsX-Forwarded-For, X-Forwarded-Host, X-Forwarded-Proto) — info notice to verify that the CDN or proxy forwards SSE without bufferingtemperature range, max_tokens floor, timeout, history/context-token-limit mismatch estimate, frontend driver validity, widget appearance settings (color_scheme, position, sound_volume, toggle_icon), admin middleware security posture, and RAG pipeline configuration completenessmax_tokens changed from null to 300 — gives small local models a sensible upper bound out of the box; set to null to restore the model's own defaulttemperature changed from 0.7 to 0.5 — more consistent and factual responses by default; increase for creative use casesAdminController::index() refactored to modular style — the monolithic index() method has been split into 11 focused private check methods (checkPhpExtensions, checkActiveProvider, checkNamedProviders, checkSecurity, checkResponse, checkHistory, checkFrontendAndWidget, checkRag, checkMemoryDriver, checkAdminProtection, checkStreaming) plus dedicated data-builder helpers; index() is now ~30 lines; class constants hold shared placeholder arraysgrid-cols-1 md:grid-cols-3 layout; each column always renders and shows "No errors / No warnings / No notices" with a checkmark when empty, replacing the previous stacked conditional panels and separate all-clear banner* handling corrected — CorsMiddleware now sets Access-Control-Allow-Origin: * on both preflight and regular responses when allowed_origins contains '*'; previously it echoed the request Origin header instead, which is technically incorrect and may be rejected by strict CORS implementationsmax_tokens not forwarded to AI API during streaming — OpenAiCompatibleEngine::beginStream() was missing max_tokens from the JSON payload; the value is now read from the resolved config and included in the stream request (excluded when null via array_filter)rag_chunk_size and rag_chunk_overlap ignored in per-provider config — RagController was reading these values directly from the global config() helper instead of from the resolved provider config array; switching providers or overriding chunk settings per-provider now takes effect correctlysendMessage() and streamMessage(), the user's turn is now persisted to the repository immediately before the AI call begins; if the AI call fails the user message is already stored rather than being discardedstreamMessage() the history persistence block was nested inside the stream-reading try/catch, causing it to be skipped when any stream error occurred; restructured so history is saved after the stream finishes (successfully or not) as long as $fullReply is non-emptysendMessage() and streamMessage() now wrap all repository save calls in try/catch blocks; a persistence failure is logged but does not abort the HTTP responseadmin_middleware config key — new admin_middleware key (null by default) lets you set separate middleware for the admin dashboard (diagnostics, config viewer, conversations) independently of rag_admin_middleware which guards the Knowledge Base; when null, the dashboard inherits rag_admin_middleware so existing deployments are unaffectedConversation::latestMessage() relation — new HasOne relation on the Conversation model using latestOfMany('id'); used in the admin conversations list to eager-load the last message and avoid N+1 queriesAdminController::conversationMessages() now returns messages in pages of 50 (with current_page, last_page, total, per_page metadata) instead of fetching all messages in one queryContextManager — ContextManager::trim() accepts a new $ragBudget parameter; when RAG is enabled the controller estimates the token cost of the top-K chunks (rag_top_k × rag_chunk_size) and passes it as headroom so history is trimmed to leave room for injected RAG contextOPTIONS) support — CorsMiddleware now short-circuits OPTIONS requests with a 204 response including Access-Control-Max-Age: 86400 before the request reaches any controller, fixing browsers that require a successful preflight before sending the actual requestthread_id validation on clear-history — ChatboxController::clearHistory() now validates that thread_id is a nullable string with a maximum length of 36 charactersDatabaseConversationRepository scopes reads and writes to the authenticated user — getHistory(), saveHistory(), trimToLimit(), and clear() all use a new private findConversation() helper that adds a user_id constraint when a user is authenticated; this prevents one authenticated user from reading or writing into another user's thread while preserving guest (unauthenticated) behaviour unchangedSessionConversationRepository::key() hashes non-UUID thread IDs — previously any non-UUID thread_id fell back to the shared base session key; now every distinct non-empty thread_id gets its own slot via md5 truncated to 16 hex chars, preventing thread collisions for custom thread identifiersContextManager::estimateTokens() uses mb_strlen — token estimation now uses mb_strlen(..., 'UTF-8') instead of strlen so multi-byte characters (CJK, Arabic, emoji) are counted by character rather than by byte, producing more accurate token estimatesPruneConversations empty-conversation query scoped correctly — the fresh-empty query (doesntHave('messages')->where('updated_at', '>=', $cutoff)) now excludes conversations already covered by the stale pass, preventing double-counting and reporting the real deleted count from the Eloquent return valueAiChatboxServiceProvider::boot() uses AiManager::resolveConfig() — the debug-mode API token check now resolves via AiManager (matching all other provider resolution paths) and catches InvalidArgumentException when no providers are configured, instead of reading raw config keys directlyadminRouteConfiguration() reads admin_middleware first — the admin route group now uses admin_middleware ?? rag_admin_middleware so the new key takes effect without requiring changes to existing configsAdminController conversations list eager-loads latestMessage — replaced the per-row messages()->orderByDesc('id')->first() sub-query with a with('latestMessage') eager load, eliminating N+1 queries on the conversations list endpointclassifyConnectException() and classifyHttpStatus() changed to protected — these methods in OpenAiCompatibleEngine were public but are internal helpers; visibility narrowed to protected to clarify the intended API surfacetheme_color updated — config default changed from #4f46e5 (indigo) to #0dad35 (green)groq provider removed from default config — the sample groq provider block has been removed from src/Config/ai-chatbox.php; Groq can still be configured as a custom OpenAI-compatible provider using any provider namehealthCheck returns 400 for unknown provider names — previously an InvalidArgumentException from AiManager::resolveConfig() would bubble up as a 500; the controller now catches it and returns a JSON 400 with a clear error messagemarked.js + DOMPurify loaded from CDN; user messages continue to display as plain escaped textdocument.execCommand('copy') on HTTP (non-secure) contexts where navigator.clipboard is unavailable; icon switches to a checkmark for 2 seconds to confirm success<textarea> that auto-expands up to 3 visible rows as the user types; text wraps naturally with no horizontal overflow; the field shrinks back to one row after the message is sentcleared_after_id cursor is recorded on the conversation row; messages before the cursor remain stored and visible to admins in the conversations modal, while the AI context starts fresh from the next messageai-chatbox:prune-conversations now deletes empty conversations — the prune command runs a second pass using doesntHave('messages') to remove conversations that were created but never received any messages; both stale and empty counts are reported separately in --dry-run outputscrollbar-width: thin on Firefox, using the --chatbox-scrollbar CSS variable to match the messages-area scrollbar; pre-built chatbox.css updated accordinglyChatboxController::sendMessage() and streamMessage() were saving the context-trimmed history subset back to the database instead of the full history, causing older messages to be permanently lost on each turn; the controller now keeps $fullHistory (for persistence) separate from $contextHistory (passed to the AI prompt only)ai-chatbox:prune-conversations compatibility — replaced self::SUCCESS / self::FAILURE class constants (introduced in Laravel 9 / Symfony Console 5.1) with integer literals 0 and 1 so the command works on Laravel 8 and earlier2024_01_01_000005_add_cleared_after_id_to_ai_chatbox_conversations_table — adds a nullable cleared_after_id (unsigned big integer) column to ai_chatbox_conversations; run php artisan migrate after updatingcolor_scheme now applies to the chat widget — the color_scheme config key (AI_CHATBOX_COLOR_SCHEME) previously only controlled admin pages; it now also forces the chat widget into light or dark mode across all three frontend drivers (vue, blade, livewire); auto (default) continues to follow the OS/browser prefers-color-scheme preference with no change requiredrag_embedding_timeout promoted to universal global config — previously a per-provider key that had to be duplicated across every named provider entry; it is now a single top-level rag_embedding_timeout key (AI_CHATBOX_EMBEDDING_TIMEOUT, default 10 seconds) shared by all providers; if you published the config and have rag_embedding_timeout inside a provider block, remove it — the top-level value takes effect automaticallyEmbeddingService no longer reads config values directly — all parameters (URL, model, token, timeout) must be injected via the constructor; the service now correctly sources all embedding settings from AiManager::resolveConfig() rather than ever falling back to global config() calls; two new getters (resolvedUrl(), resolvedModel()) expose the active values for accurate log outputRagRetriever log messages now report the actual resolved embedding URL and model (via the new EmbeddingService getters) rather than the potentially-wrong global config valuescolor_scheme config comment updated — header changed from Color Scheme (Admin RAG UI) to Color Scheme; description clarifies it applies to both the widget and all admin pagesai-chatbox:prune-conversations Artisan command — bulk-delete conversations that have been inactive beyond a configurable retention period:
--days=N — override the retention threshold for this run--dry-run — preview how many conversations would be removed without deleting anything--force — proceed even when memory_driver is not databasememory_driver is database, that both ai_chatbox_conversations and ai_chatbox_messages tables exist, and that --days is ≥ 1conversation_prune_days config key (AI_CHATBOX_PRUNE_DAYS, default 30) — sets the default retention period used by ai-chatbox:prune-conversations when --days is not passedPruneConversationsTest — 19 feature tests covering pre-flight validation, happy-path deletion, boundary conditions, cascade behaviour, --dry-run, --force, config key precedence, and the saveHistory updated_at touch behaviourFlow.md — architecture flow documentation describing the request lifecycle across the three-layer architectureDatabaseConversationRepository::saveHistory() now calls $conversation->touch() after persisting messages so that updated_at always reflects the time of the last message, not just the conversation creation time; this makes the prune command's inactivity window accurateapi_url, api_token, and api_model config keys (and their AI_CHATBOX_API_* env vars) have been removed; all provider settings must now be defined under providers.{name} in the config file. This change had been the documented practice since 0.2.1; this release enforces it.AiManager::resolveConfig('default') now routes through active_provider — previously it returned the (now-removed) top-level keys; it now resolves the active named provider by reading active_provider config, falling back to the first defined provider if active_provider is 'default' or emptyAdminController uses resolved provider config for diagnostics — the admin dashboard now calls AiManager::resolveConfig() to obtain the effective api_url, api_token, and api_model values; diagnostic messages now reference provider-specific env var names (e.g. OLLAMA_URL, OPENAI_URL) rather than the removed AI_CHATBOX_API_* names; a try/catch ensures the page still renders when the active provider is misconfiguredConfig/ai-chatbox.php documentation reorganised — inline comments rewritten to reflect the named-provider model; the top-level API credential block is removedcomposer.json keywords expanded — added Packagist keywords for improved discoverability (llm, gpt, local-llm, multi-provider, admin-dashboard, knowledge-base, token-streaming, and others)AI_CHATBOX_API_URL, AI_CHATBOX_API_TOKEN, and AI_CHATBOX_API_MODEL replaced with named-provider equivalentsapi_url/api_token/api_model "inheriting from top-level defaults" — those defaults no longer exist; the error messages now correctly point to provider-specific env varsrag_embedding_timeout config key (AI_CHATBOX_EMBEDDING_TIMEOUT, default 10) — dedicated timeout in seconds for embedding API calls; previously the embedding HTTP client was hardcoded to 60 s regardless of model speedEmbeddingService now accepts an optional $timeout constructor parameter; RagController passes the active provider's resolved timeout when instantiating the serviceapi_url and rag_embedding_url changed from localhost to 127.0.0.1 to avoid DNS resolution issues on some Windows setups; default api_model updated to phi-3.5-mini-instruct; default rag_embedding_model updated to text-embedding-nomic-embed-text-v1.5EmbeddingService constructor injection — EmbeddingService now accepts optional $url, $model, $token constructor parameters so it can be instantiated with per-provider settings; falls back to config values when parameters are nullRagController active-provider awareness — embedding config (URL, model, token) is now resolved through the active provider (active_provider config key) rather than always reading the top-level config; switching providers via .env now also updates which embedding endpoint is usedAdminDiagnosticsTest, EmbeddingServiceTest, expanded AiManagerTest, RagDocumentTest, HealthCheckTest, SendMessageTest, StreamMessageTest; TestCase base class updated to expose mockGuzzle() helper for HTTP mockingsrc/Engine/): AiEngineInterface, OpenAiCompatibleEngine, PromptBuilder, HealthChecker, AiEngineException. All HTTP calls, prompt assembly, error classification, and health checks live here.src/Memory/): ConversationRepositoryInterface, SessionConversationRepository, DatabaseConversationRepository, ContextManager, Conversation model, Message model. All history persistence and context trimming live here.src/Http/Controllers/, src/resources/): ChatboxController handles HTTP request/response only and delegates entirely to Layers 1 and 2.AI facade (SyafiqUnijaya\AiChatbox\AI) — call AI::chat($prompt) or AI::provider('openai')->chat($prompt) from any controller, job, or serviceAiManager — resolves named providers from the providers config group, merging each entry with the global defaultsAiProvider — fluent immutable wrapper; each modifier (withModel, withTemperature, withSystemPrompt, withLanguage, withMaxTokens, withTimeout, withConfig) returns a new cloned instance so the original is never mutatedAiEngineInterface — public contract for the AI engine; implement it to add a custom provider (e.g. Anthropic, Gemini, Cohere) and bind it in the service providerConversationRepositoryInterface — public contract for the memory layer; implement it to add a custom storage backend (e.g. Redis, MongoDB)beginStream() on the engine — establishes the AI HTTP connection before response()->stream() starts, so network errors can still return a proper JSON error response (non-200) rather than a corrupted SSE streammemory_driver config key (AI_CHATBOX_MEMORY_DRIVER, default session). Set to database to persist conversation history in the ai_chatbox_conversations / ai_chatbox_messages tables; history survives PHP session expiry and is queryable via Eloquentai_chatbox_conversations (thread_id, user_id) and ai_chatbox_messages (conversation_id, role, content) — auto-loaded; no manual registration requiredactive_provider config key (AI_CHATBOX_ACTIVE_PROVIDER, default ollama) — point the chatbox widget at a named provider without duplicating credentialsproviders config group — define named providers (ollama, openai, groq, lmstudio); each entry only needs the keys that differ from the global defaults; all other settings are inherited automaticallyAdminController and admin views (admin.blade.php, admin-conversations.blade.php) — admin dashboard with configuration diagnostics, stat cards, named provider overview, and async-paginated conversations viewer with message modalGET /ai-chatbox/admin, GET /ai-chatbox/admin/conversations, GET /ai-chatbox/admin/conversations/data, GET /ai-chatbox/admin/conversations/{id}/messagesAiFacadeTest, AiManagerTest, AiProviderTest (unit), ContextManagerTest (unit), PromptBuilderTest (unit), DatabaseMemoryTestChatboxController reduced from ~600 lines to ~120 lines — pure HTTP I/O, no business logicE01–E19) moved from ChatboxController to OpenAiCompatibleEngine (now public methods, directly testable)ErrorClassificationTest updated to target OpenAiCompatibleEngine directly (no more reflection into the controller)composer.json keywords for better Packagist discoverabilitycomposer.json to reflect RAG and streaming capabilitiesfrontend config key (AI_CHATBOX_FRONTEND) controls which UI [@aichatbox](https://github.com/aichatbox) renders:
vue (default) — pre-compiled Vue 3 SFC bundle; zero config, no CDN callsblade — self-contained vanilla JS widget; no framework dependency; marked.js + DOMPurify loaded from CDN only when markdown=truelivewire — Alpine.js widget mounted via Livewire 3; Alpine.js is bundled automatically by Livewirenone — outputs only window.AiChatboxConfig; use this when bringing your own React/Svelte/Vue component[@aichatboxConfig](https://github.com/aichatboxConfig) Blade directive — outputs only window.AiChatboxConfig regardless of the frontend setting; useful when mounting <livewire:ai-chatbox /> independently or building a custom frontendai-chatbox) — auto-registered when livewire/livewire is installed; mount anywhere with <livewire:ai-chatbox />chatbox-config.blade.php — shared config injector view extracted from the main chatbox view; all drivers read window.AiChatboxConfigchatbox-blade.blade.php — new vanilla JS driver; identical HTML structure and CSS class names as the Vue driver; no compilation requiredchatbox.blade.php refactored into a dispatcher — reads frontend config and includes the appropriate driver partial/ai-chatbox/rag admin UI now fully supports light and dark themes with a new color_scheme config key (auto / light / dark)
auto (default) follows the user's OS/browser preference via prefers-color-scheme and updates live without a page reloadlight / dark forces a fixed mode server-side with no flash-of-wrong-themerag_context_prompt config key (AI_CHATBOX_RAG_CONTEXT_PROMPT) lets you customise the instruction prepended to retrieved chunks; use {chunks} as a placeholder for where the retrieved text is insertedrag_processing_timeout config key (AI_CHATBOX_RAG_PROCESSING_TIMEOUT, default 0 = no limit) controls how long PHP is allowed to run during document upload/reprocess, preventing Maximum execution time exceeded errors on slow local embedding models[system → history → RAG context → user]) rather than at the front of the message list; models pay most attention to content nearest the end of the context window, so this significantly improves answer accuracy0.3 to 0.2 to improve recall for local embedding models (e.g. nomic-embed-text) which typically produce lower cosine similarity scores than OpenAI models.md and .txt files (up to 10 MB) via the admin UI at /ai-chatbox/ragPending → Processing → Ready / Failed), chunk count, and error details['web', 'auth'] middleware by default (configurable)rag_enabled, rag_embedding_url, rag_embedding_model, rag_top_k, rag_chunk_size, rag_chunk_overlap, rag_similarity_threshold, rag_admin_middlewareai_chatbox_rag_documents, ai_chatbox_rag_chunks (auto-loaded migrations, no manual registration required)DocumentChunker, EmbeddingService, RagRetrieverRagDocument, RagChunkRagController with index, store, destroy, reprocess actionsai-chatbox-migrations — publish migrations before running if you want to review or customise them/v1/embeddings, Ollama /api/embed, and OpenAI /v1/embeddings without any extra configuration--theme) so it inherits the configured theme_color without a build stepChatboxController now injects RAG context into every chat and stream request when rag_enabled is truelocalStorage/sessionStorage, scoped to both the app URL and the authenticated user; multiple independent conversations never share context▋ cursor; uses POST /ai-chatbox/stream (Fetch API + ReadableStream on the client, Guzzle stream: true on the server)context_token_limit config key (AI_CHATBOX_CONTEXT_TOKENS, default 4000) trims conversation history oldest-pair-first by estimated token count (~4 chars/token) to stay within the model's context windowstream / AI_CHATBOX_STREAM (default true) to toggle between SSE streaming and full-response modePOST /ai-chatbox/clear route to clear the server-side session history for a specific threadStreamMessageTest, expanded SendMessageTest, ClearHistoryTestX-Accel-Buffering: no response header set automatically on SSE responses to disable Nginx proxy bufferingTROUBLESHOOTING.md with additional error scenariosAiChatbox.vue) using the Composition API, compiled to a self-contained IIFE bundle via Viteaxios, marked, and DOMPurify — no external CDN calls required at runtimepackage.json and vite.config.js for contributors to rebuild the frontend assets (npm run build)php artisan config:cache)SendMessageTest — message proxying, error handling, history, language enforcementClearHistoryTest — session history clearing per threadCorsMiddlewareTest — origin validation, preflight requestsHealthCheckTest — AI service ping, SSRF blockingErrorClassificationTest — all E01–E19 error codes.github/workflows/tests.yml) running tests on PHP 8.2/8.3 × Laravel 10/11/12phpunit.xml configuration with SQLite in-memory database for fast test runsE01–E19) — every failure path in the controller now returns a machine-readable error code alongside the human-readable message, making it easy to diagnose issues from storage/logs/laravel.logTROUBLESHOOTING.md — full reference guide mapping each error code to its cause and resolution stepsai-chatbox.cors) — restricts chatbox endpoints to requests originating from your app's own URL (APP_URL); additional origins can be added via allowed_origins configlocalhost, 10.x, 172.16.x, 192.168.x, 169.254.x) to prevent Server-Side Request Forgery attacks; disable with AI_CHATBOX_SSRF_PROTECTION=false for local developmentssrf_protection, allowed_originslocalStorage key is now scoped to both the application URL and the authenticated user — previously all users on the same browser shared the same chat history key, causing messages from one user to appear for anotherlocalStorage is written and read correctly; state is no longer lost on navigationlocalStorage and restored when the user returns to the page; chat no longer resets on every page loadollama.com) compatibility — auto-detects the Ollama native chat response format (different from the OpenAI-compatible format) and parses it correctly; both Ollama local (OpenAI-compatible) and Ollama cloud (native) APIs now work without any extra configuration.env examplelanguage / AI_CHATBOX_LANGUAGE config forces the AI to always reply in a specified language regardless of the language the user writes in; uses both a system prompt instruction and a per-message reminder for better compliance on small modelssystem_prompt config key for a fully customisable system message with a {language} placeholderhealth_check, offline_message config keys)bottom-right, bottom-left, top-right, top-left (position / AI_CHATBOX_POSITION)sound, sound_volume config keys)marked.js + DOMPurify, both bundled (markdown / AI_CHATBOX_MARKDOWN)history_enabled, history_limit config keys)temperature and max_tokens config keyslocalStorage and sessionStorage (storage / AI_CHATBOX_STORAGE)prefers-color-scheme: darkthrottle:20,1 middleware applied to all chatbox routes (rate_limit, rate_window config keys)route_prefix config key, default ai-chatbox)AI_CHATBOX_TITLE[@aichatbox](https://github.com/aichatbox) Blade directive — no build tools required in the host applicationlocalhost:11434 with phi3:mini.envHow can I help you explore Laravel packages today?