Features

Markdown

AI chat

AI chat is the product surface for AppAssistant: a dedicated /ai page, a global FAB drawer, streaming replies, conversation pin/truncate/bulk delete, and short-lived file attachments. The whole /ai/* authenticated group requires can-use-ai.

AI assistant
In-app AI assistant

Related

Agent & tool model: AI assistant model. Imports, sync, webhook: AI imports & sync. Env/provider keys: AI configuration. HTTP: AI API & webhooks.

Team chat is separate

Human-to-human threads (item + DM hub at /chat) are documented in Chat (hub & items). They use Echo, not SSE.

Access

ItemDetail
Permissioncan-use-ai (PermissionEnum::CanUseAi)
Routesroutes/ai.php — middleware auth, verified, permission:can-use-ai
Page controllerApp\Http\Controllers\Ai\AiPageController
Frontend pageresources/js/pages/ai/index.tsx
FABresources/js/components/ai/ai-fab.tsx in app-sidebar-layout

AI page

RouteNameBehavior
GET /aiai.indexConversation list (25, pinned first) + optional selection
GET /ai/{conversation}ai.showSame page with selected conversation (UUID)
GET /ai/statusai.statusAiStatusController — cached local provider /models probe

Props include normalized messages (content, tools, attachments). Empty assistant tool turns get AiToolTurnSummary.

FAB

  • Exported as AiFab, mounted in the app sidebar layout.
  • Shown only when the user has can-use-ai.
  • Hidden on /ai routes.
  • Drawer: status, presets, attachments, SSE chat via streamAiChat → POST /ai/chat.

Ask AI

List pages (users, groups, files, collections, items, activity logs) expose Ask AI via AskAiButton (resources/js/components/ai/ask-ai-button.tsx):

BehaviorDetail
PermissionShown only when can-use-ai
Modesicon (row) or labeled (bulk toolbar)
ActionOpens the FAB composer with a seeded prompt (openAiWithPrompt in resources/js/lib/ai-open.ts)
SendNever auto-sends — the user reviews/edits and submits

Bulk selection can seed multi-entity prompts (see tests/Browser/AiContextBulkBrowserTest.php).

Conversations

Controller: App\Http\Controllers\Ai\AiConversationController.

Models use laravel/ai conversation tables (agent_conversations / agent_conversation_messages via config/ai.php).

ActionRouteBehavior
IndexGET /ai/conversationsList owned conversations
StorePOST /ai/conversationsCreate
ShowGET /ai/conversations/{conversation}Detail
DestroyDELETE /ai/conversations/{conversation}Delete one
PinPOST /ai/conversations/{conversation}/pinToggle pinned_at null ↔ now()
TruncatePOST /ai/conversations/{conversation}/truncateBody from_message_id (UUID); deletes that message and all later
Bulk destroyPOST /ai/conversations/bulk-destroyBody ids (1–100 UUIDs); must be owned; deletes messages + conversations

Chat streaming

ItemDetail
ControllerApp\Http\Controllers\Ai\AiChatController (invokable)
RoutePOST /ai/chat (ai.chat)
Throttlethrottle:30,1 (30 requests / minute)
AgentApp\Ai\Agents\AppAssistant
ResponseSSE text/event-stream, ends with [DONE]

Validated body:

FieldRules
messageRequired, max 20000
conversation_idOptional UUID
attachment_idsOptional, max 5 UUIDs
context_item_idsOptional, max 10 item ids (1.4.0)
context_file_idsOptional, max 10 file ids (1.4.0)
context_prompt_idsOptional, max 5 saved prompt ids (1.4.0)

Optional daily cap: config('ai.daily_prompt_limit') ← AI_DAILY_PROMPT_LIMIT (0 = off), counted from Spatie activity log_name=ai, event=ai_prompt.

Events may include { type: 'conversation', conversation_id } and { type: 'error', … }. Logging goes through App\Ai\Support\AiActivityLogger.

Since 1.4.0 the stream also carries the model's work, not only its text: reasoning_start / reasoning_delta / reasoning_end, tool_call and tool_result, citation (and provider tools such as web_search_call), plus the token usage at stream_end.

What an answer shows

From 1.4.0 (core #565, #566, #570, #572, #615, #619, #621) every Assistant answer shows how it was produced, in the order the model produced it:

PartWhat you see
ReasoningThinking… while the model reasons, open so you can follow it; when it finishes it closes to Thought for N seconds ("Thought for a few seconds" after a reload). Open or close it yourself and it stays that way.
Tool callsEach call as one line with a sentence ("List collections", "Searched the web") and the record it works on, never its JSON. The icon shows the state: running, done, error or denied; a failed call opens to its error. Several calls in a row are grouped under Done in N steps.
TextThe answer itself, in Markdown, with tables and highlighted code blocks.
SourcesUsed N sources: the pages the model cited (for example from a provider web search), as links.
Token usageThe tokens used by the turn against the provider's context window (*_CONTEXT_WINDOW), with input, output, reasoning and cache tokens on hover.

Reasoning appears only when the provider returns it: set the reasoning effort per provider — AI configuration — Assistant provider, reasoning and context window. A write tool that needs approval pauses the answer on an approval card instead of a line: the call's sentence, Reject and Approve (red only when the call deletes data). The answer offers no follow-up suggestions until you decide; once you do, the call becomes a line and the model continues — AI tool approvals & audit.

All of it is persisted: reload the page or open the conversation later and the reasoning, every tool call with its state or error, the sources and the usage are shown again, the same way they streamed. See AI assistant model — Conversations for the stored fields.

Attachments

ItemDetail
ControllerApp\Http\Controllers\Ai\AiChatAttachmentController
RoutePOST /ai/attachments (ai.attachments.store)
Throttlethrottle:30,1
ModelApp\Models\AiChatAttachment

Allowed extensions: csv, txt, xlsx, pdf (MIME allowlist matches those families).

LimitValue
Max sizeMAX_BYTES = 5 * 1024 * 1024 (5 MiB)
TTLTTL_HOURS = 24
Disklocal

Cleanup: scheduled ai:cleanup-attachments daily (routes/console.php).

Context attachments

From externa-core 1.4.0 (#195), the composer's Attach button opens an Attach context picker for collection items, files, and saved prompts. The selection is sent with the next message as context_item_ids, context_file_ids, and context_prompt_ids.

PieceDetail
Picker searchGET /ai/context/collections, /ai/context/items, /ai/context/files (throttle:60,1)
Limits10 items, 10 files, 5 saved prompts per message (more → 422)
ItemsNeed can-show-collections, collection read and the role's item filter; field values are stripped by field rules
FilesNeed can-show-files; only metadata (id, name, MIME, size, type) is sent, never file contents
Saved promptsTheir body is added as a saved-prompt block

Access is re-checked when the message is sent, not only when the picker lists results, so access revoked in between cannot leak into the model prompt. Any id that is missing or not readable fails the whole send with 422 (One or more context items are not accessible.).

The resolved context is appended to the prompt as [AI_CONTEXT_ITEMS] / [AI_CONTEXT_FILES] / [AI_SAVED_PROMPT] blocks, and the model is told to treat them as operator-selected context.

Deleting a conversation from the /ai row menu asks for confirmation (1.4.0, core #522).

Saved prompts

Project-level reusable prompts (table ai_prompts), shared by everyone with can-use-ai.

MethodPathPermissionNotes
GET/ai/promptscan-use-aiOptional search (name / description), max 100; returns can_manage
POST/ai/promptsai.prompts.managename (≤120), description (≤255), body (≤20000)
PUT/ai/prompts/{id}ai.prompts.manageSame fields
DELETE/ai/prompts/{id}ai.prompts.manage

ai.prompts.manage is new in 1.4.0. externa:upgrade (via permissions:sync) grants it to the built-in admin role — Upgrade.

Write actions and approvals

From 1.4.0, write and destructive tool actions are denied by default in chat, while read actions keep working. When an operator enables a tool for writes, each write or destructive action asks for your approval in the conversation before it runs. Details: AI tool approvals & audit.

Presets

Presets are static frontend prompt templates, not backend entities.

ItemDetail
Definitionresources/js/lib/ai-action-presets.ts
TypesAiActionPreset, categories comuni | dati | file | admin | avanzate
ListAI_ACTION_PRESETS (~25 templates: CSV/Excel/URL import, dry-run, upsert, export, PDF→schema, files, roles/users/groups, audit, async import, scheduled sync, rollback, …)
FilterfilterAiActionPresets() — show when anyOf is empty or the user has any listed permission
UIresources/js/components/ai/ai-action-presets-drawer.tsx

Related suggested-action chips (tool results): resources/js/lib/ai-suggested-actions.ts.

Client helpers

HelperPath
Stream / chat APIresources/js/lib/ai-chat.ts
Componentsresources/js/components/ai/*

Source map

ConcernLocation
Pageapp/Http/Controllers/Ai/AiPageController.php
Chatapp/Http/Controllers/Ai/AiChatController.php
Conversationsapp/Http/Controllers/Ai/AiConversationController.php
Attachmentsapp/Http/Controllers/Ai/AiChatAttachmentController.php
Contextapp/Services/Ai/AiConversationContextService.php, AiContextSearchController.php
Saved promptsapp/Http/Controllers/Ai/AiPromptController.php, app/Models/AiPrompt.php
Routesroutes/ai.php
UIresources/js/pages/ai/index.tsx, components/ai/ai-fab.tsx, ask-ai-button.tsx, lib/ai-open.ts