Tại sao plugin này là reference chính tắc
Hầu hết plugin trong production đều nhỏ. Một sending driver là một class cộng một blade connection. Một payment gateway là một service cộng một redirect controller. Một add-on sidebar đơn giản là một call Hook::add. Không cái nào trong số đó exercise toàn bộ plugin SDK — và plugin nhỏ không phải là bài tập đọc đúng cho author đang cố hiểu plugin có thể grow lớn cỡ nào một cách trách nhiệm.
acelle/ai tồn tại ở đầu kia của phổ. Nó là một subsystem AI self-contained: chatbox agent, universal text-rewrite component, KB-grounded coach persona, và admin observability dashboard. Activate plugin trong install AcelleMail add toàn bộ surface AI mà không động vào core code; deactivate remove nó sạch sẽ. Plugin sử dụng mọi khái niệm phần còn lại của doc này cover, trong production. Đọc nó là cách nhanh nhất để thấy pattern combine ra sao khi feature là non-trivial.
File tree at a glance
Từ README riêng của plugin:
| Folder | Owns cái gì |
src/AIHandler/ | AI runtime engine — engine, agent loop, tool, settings resolver, observability writer/reader, KB lookup, URL sanitiser. |
src/Models/ | Tám Eloquent model cho substrate audit (AIConversation, AIMessage, AIRequest, AIToolCall, AIFeedback, AIRawBlob, AIDailyRollup, AIToolUndoRecord). |
src/Controllers/ | Admin controller (/rui/admin/ai-*) + public-API controller (/api/v1/ai/*) + PluginDashboardController plugin-landing. |
src/Services/ | PluginStatusReport, AISettingsService, AutomationService, AIObservabilityPolicy, v.v. |
src/ServiceProvider.php | Entry point duy nhất — register PSR-4, route, view, lang file, hook, middleware alias, lifecycle listener. |
database/migrations/ | Mười bốn migration substrate-audit. Run lúc activate, rolled back lúc delete. |
resources/views/ | Hơn sáu mươi Blade template admin + ba universal anonymous component (<x-mc-ai-chatbox>, <x-mc-ai-rewrite>, <x-mc-ai-subject-ab-generator>) + JS partial chatbox / sparkle. |
resources/assets/ | CSS (~14 file) + JS (~21 file) publish tới public/plugins/acelle/ai/ qua vendor:publish --tag=plugin --force lúc plugin install. |
resources/lang/ | Mười tám locale × chín lang file = full surface của module AI được dịch. |
tests/ | Hơn một trăm Pest test (Feature + Unit) + base class Acelle\Ai\Tests\PluginTestCase. |
routes.php | Route plugin (admin + public API + dashboard plugin-landing tại /plugins/acelle/ai/dashboard). |
composer.json | Metadata plugin; extra.setting-route point tới PluginDashboardController@index để nút "Settings" trên trang admin Plugins deep-link vào dashboard riêng của plugin. |
Không folder nào trong đó là bespoke. Mọi folder map trực tiếp tới một section trong phần còn lại của các doc này. Đọc plugin là quá trình nhận ra cùng pattern shipped ở scale lớn.
Tám Eloquent model — substrate audit
Data layer của module AI được shape quanh auditability: mọi conversation, mọi invocation model, mọi tool call, mọi user feedback, và mọi blob raw provider output được capture cho replay và observability. Tám model cover substrate đó:
| Model | Table | Đại diện cái gì |
AIConversation | ai_conversations | Một row mỗi session agent / support multi-turn. Carry FK customer + user, task key, route screen, và token / cost total rolled-up. |
AIMessage | ai_messages | Một row mỗi turn user / agent. Role, content JSON, FK tới tool-call, latency, model used. |
AIRequest | ai_requests | Một row mỗi API call upstream. Engine, prompt hash, latency, cost, error status. Bridge AIMessage tới HTTP traffic thực tế. |
AIToolCall | ai_tool_calls | Invocation function-call spawn bởi turn agent. Tool name, input/output JSON, source flag. |
AIFeedback | ai_feedback | Thumbs-up/down + free-text feedback mỗi message + mỗi conversation. |
AIRawBlob | ai_raw_blobs | Raw provider response gốc, kept cho replay / audit. Table riêng vì table rollup cần stay small. |
AIDailyRollup | ai_daily_rollup | Aggregate per-day cho dashboard observability admin — token total, cost, error rate. Pre-aggregated để dashboard read cheaply. |
AIToolUndoRecord | ai_tool_undo_records | Track reversible tool action cho feature "undo last". |
Ba pattern từ list này translate trực tiếp sang plugin khác. Split "raw provider response" thành table riêng khỏi "rolled-up summary" giúp table rollup stay nhỏ đủ để scan. FK nullable tới customers và users giúp cùng row work cho cả traffic authenticated lẫn anonymous. Rollup one-row-per-day cho admin dashboard read cheap mà không cần JOIN nặng vào table activity.
Mười bốn migration one-line mỗi cái
Filename migration trong storage/app/plugins/acelle/ai/database/migrations/ kể câu chuyện riêng của chúng — additive theo thời gian, immediately reversible, không bao giờ là một schema move destructive:
| Filename | Làm gì |
2026_04_28_000001_create_ai_conversations_table.php | Session chat multi-turn — uid, FK customer_id, status enum, rollup token / cost |
2026_04_28_000002_create_ai_messages_table.php | Một turn user / agent — role, content JSON, FK tool-call, latency, model used |
2026_04_28_000003_create_ai_requests_table.php | Một row mỗi API call upstream — engine, prompt hash, latency, cost, error |
2026_04_28_000004_create_ai_tool_calls_table.php | Invocation function-call spawn bởi turn agent — input / output JSON |
2026_04_28_000005_create_ai_feedback_table.php | Thumbs-up/down + free-text feedback mỗi message + mỗi conversation |
2026_04_28_000006_create_ai_raw_blobs_table.php | Raw provider response gốc, kept cho replay / audit |
2026_04_28_000007_create_ai_daily_rollup_table.php | Aggregate per-day cho admin dashboard — token total, cost, error rate |
2026_04_29_000001_add_client_message_id_to_ai_messages.php | Column dedup cross-tab — additive, không default |
2026_04_30_000002_add_source_to_ai_tool_calls.php | Track xem một tool call đến từ route agent hay support |
2026_05_02_180000_widen_ai_conversations_client_session_uid.php | Fix width ULID / UUID — migration column-altering, fully reversible |
2026_05_02_200000_create_ai_tool_undo_records_table.php | Track reversible tool action cho feature "undo last" |
2026_05_03_000001_add_url_sanitization_to_ai_requests.php | Add column JSON cho telemetry URL sanitised |
2026_05_04_000001_create_ai_settings_table.php | Settings admin plugin-level — kept riêng khỏi plugins.data để mỗi row có thể indexed |
Đọc migration top-to-bottom và bạn có evolution schema của toàn bộ module AI. Mọi migration additive là một feature ship — shape additive là cái làm rollback delete_plugin_* của plugin luôn work, kể cả khi admin uninstall sau một năm feature accretion.
Mọi hook plugin sử dụng
ServiceProvider của plugin exercise mọi hook pattern trừ FILTER. Grep Hook::* trên storage/app/plugins/acelle/ai/src/ServiceProvider.php:
REGISTRY (Hook::add) — sáu contribution
- Chín entry
add_translation_file tại register() line 175-197 — một per translation surface (rewrite, chatbox, prompts, wait, subject AB, settings, admin usage, audit, permissions). Mỗi surface là một file editable riêng trong UI admin Languages.
layout.head.assets tại boot() line 688 — contribute CSS + JS chatbox cho mọi page extend master layout app / admin.
layout.body.before_close tại boot() line 699 — contribute HTML bubble chatbox và popover sparkle trước </body> của mọi page.
admin.sidebar.groups tại boot() line 718 — add group AI với ba hay bốn child link vào sidebar admin.
Cả sáu gate chính nó với aiPluginAvailable() — một helper cuối cùng resolve về Plugin::getByName('acelle/ai')->isActive(). Return null khi plugin bị gate off là cách conventional để biến mất sạch sẽ mà không cần unload service provider.
EVENT (Hook::on) — hai lifecycle listener
activate_plugin_acelle/ai tại line 472 — run artisan migrate đối với folder migration của plugin.
delete_plugin_acelle/ai tại line 546 — accept flag $keepData, rollback migration khi không set, preserve table audit khi set.
BEHAVIOR (Hook::set) — một override icon URL
icon_url_acelle/ai tại line 522 — override hook BEHAVIOR per-plugin để trang admin Plugins render icon riêng của module AI thay vì plugin.svg default của host.
Cùng nhau những cái này là hầu hết hook surface của plugin. Đọc ServiceProvider.php top-to-bottom là cách nhanh nhất để thấy pattern combine ra sao trong production.
UI surface chatbox
Plugin contribute ba universal Blade component trong resources/views/components/ — không cái nào cần host application biết chúng tồn tại:
<x-mc-ai-chatbox> — bubble chatbox floating mở vào conversation agent multi-turn. Mount qua hook REGISTRY layout.body.before_close để nó xuất hiện trên mọi page app + admin.
<x-mc-ai-rewrite> — affordance "rewrite this text" universal có thể drop bên cạnh bất kỳ textarea nào trong host. Plugin-namespaced, không cần registration central.
<x-mc-ai-subject-ab-generator> — generate variant subject-line A/B từ prompt. Used trong campaign editor.
Ba component này show pattern cho "plugin contribute UI cho nhiều host page mà không fork mỗi page": ship component như anonymous Blade component dưới namespace view của plugin của bạn, register nó qua hook layout REGISTRY cho global mounting, hoặc để host page opt-in bằng cách include trực tiếp. Cả hai pattern đều work; plugin AI dùng cả hai.
Chín file × mười tám locale
register() của plugin register chín file translation riêng biệt. Machinery Language::dump() sau đó materialise mỗi cái thành mười bảy folder runtime non-English locale dưới storage/app/data/plugins/acelle/ai/lang/. Result trên disk: 153 runtime translation file (9 file × 17 non-English locale + 9 English original = 162 minus 9 English master = 153 dump-clone), mỗi cái editable riêng qua UI admin Languages của host.
Chín file surface (registered qua loop $aiLangFiles trong ServiceProvider::register()):
ai_rewrite — universal text rewrite component
ai_chatbox — UI chatbox
ai_chatbox_prompts — prompt pre-canned show trong chatbox
ai_chatbox_wait — UI smart-wait ("looking up… running tool… composing reply")
ai_subject_ab — subject A/B generator
ai_settings — label trang settings admin
admin_ai_usage — dashboard usage / cost admin
admin_ai_audit — UI audit / replay admin
admin_ai_permissions — toggle permission per-feature admin
Split này là câu trả lời thực tế cho "làm sao tôi keep file translation đủ nhỏ để translator có thể edit một cái trong một lần ngồi": một master mỗi surface logic, registered riêng, materialised per locale, edit độc lập qua UI admin.
Config file plugin-owned
Plugin own hai file config dưới config/ — registered trong ServiceProvider::boot() qua $this->mergeConfigFrom() và reachable qua helper standard config('ai.*') và config('ai-navigation-hints.*'). Config plugin-owned là nơi đúng cho metadata static không change per-install (engine catalog, prompt template, navigation default); setting admin-editable sống trong table ai_settings seeded bởi migration.
Split — config cho default immutable plugin-shipped, row DB cho setting mutable admin-editable — là một pattern port sạch sẽ sang plugin khác. Đặt cả hai trong JSON column plugins.data là tempting nhưng punish UI admin performance; một table indexed dedicated là quyết định đúng.
Test infrastructure
Test directory của plugin follow shape tests/ của host — folder Unit + Feature cộng một base class PluginTestCase tại root:
storage/app/plugins/acelle/ai/tests/
├── PluginTestCase.php ← seeds the plugin row as active before every test
├── Feature/
│ ├── AIHandler/ ← engines, agent loop, tools, observability writer
│ └── PluginLifecycle/ ← lifecycle integration tests
├── Unit/ ← isolated unit tests (no Laravel boot for some)
├── Fixtures/ ← test fixtures + factories
├── Snapshots/ ← Pest snapshot artefacts
└── Support/ ← test-only helpers
phpunit.xml của host register testsuite của plugin là <testsuite name="Plugin: acelle/ai">. ./vendor/bin/pest --testsuite="Plugin: acelle/ai" run full suite song song với suite Unit + Feature riêng của host.
PluginTestCase tại root demonstrate trap per-request gate-cache reset mà mọi plugin shipping middleware nên adopt — xem Testing § gate-cache trap cho full pattern. Không có nó, test thứ hai trong suite trở đi observe stale cache state từ boot của test thứ nhất.
Cách học từ nó
Đọc mọi line của một plugin trăm-nghìn-token không phải bài tập đúng. Một công thức bốn-bước hữu ích hơn:
-
Clone hoặc symlink plugin vào host install. Activate nó. Mở sidebar admin — group AI nên xuất hiện. Click "Settings" trên entry trang admin Plugins — dashboard plugin-landing nên load. Cái này prove UI surface của plugin được wired vào host local của bạn.
-
Đọc
src/ServiceProvider.php top-to-bottom. Bốn mươi phút. Mọi khái niệm covered trong Plugin architecture + Hook system + UI injection + Translations xuất hiện trong một file này ở production scale. Cross-reference với các trang deep-dive khi bạn đi.
-
Trace một feature end-to-end. Pick bubble chatbox. Tìm entry point (hook
layout.body.before_close trong ServiceProvider), follow partial nó return (ai::partials.body_assets), tìm anonymous component render bubble, tìm JS mount nó, tìm route API JS call, tìm controller, follow nó xuống runtime engine AIHandler, watch row AIConversation + AIMessage + AIRequest được insert. Hai-tới-ba giờ, end-to-end. Sau cái này bạn sẽ biết plugin AI dùng pattern nào và không dùng pattern nào.
-
Adapt một pattern sang plugin của riêng bạn. Pick cái nhỏ nhất map được — thường là loop registration per-translation-surface, hoặc group sidebar admin, hoặc approach table rollup per-day. Strip out code AI-specific; keep pattern structural. Đó là gain productivity day-one của plugin author.
Đi tiếp đâu
Đây là cái cuối trong mười một deep-dive developer. Từ đây, hub index là recap tự nhiên: Documentation index show mọi trang trong cluster organised vào Foundation / Building / Quality / Reference. Trang landing developer là entry point marketing-shaped cho visitor mới đến từ search.
Hai bước kế tiếp thực tế khi bạn ready ship một plugin thực: cycle activate → test → delete từ deep-dive Testing prove hook listener delete_plugin_* của plugin cleanup đúng. Plugin architecture § Recovery from broken state là trang nên bookmark cho production runtime issue — ba failure mode cộng fix path chính xác cho mỗi cái.
Beyond pattern AcelleMail-specific, ecosystem Laravel rộng hơn áp dụng. Code plugin là vanilla Laravel; runtime loader của host là phần non-standard duy nhất. Bất cứ thứ gì bạn biết về Eloquent, Blade, Pest, queue, scheduling, hoặc middleware work bên trong folder plugin unchanged.