Skip to content

Implement AI Assistant with Ollama LLM integration and UI enhancements#15

Open
PalmarHealer wants to merge 11 commits into
masterfrom
feature/ai-chat
Open

Implement AI Assistant with Ollama LLM integration and UI enhancements#15
PalmarHealer wants to merge 11 commits into
masterfrom
feature/ai-chat

Conversation

@PalmarHealer

Copy link
Copy Markdown
Owner

No description provided.

PalmarHealer and others added 3 commits March 22, 2026 22:41
Implements an AI assistant that can query and modify wochenplan data
through a chat interface. Runs on a self-hosted Qwen 3 8B model via
Ollama on the Leipzig server (RTX 3050 GPU).

Features:
- Filament-native UI: text input widget + conversations table on list
  page, chat view with streaming responses on conversation page
- 15 composite tools covering all resources (lessons, templates,
  absences, rooms, times, colors, layouts, deviations, users, roles,
  activity logs, PDF export, FAQ, lunch reload)
- All tools permission-gated via Spatie/Shield policies
- Streaming responses via SSE (Server-Sent Events)
- Tool calls handled non-streaming for reliability, final response
  streamed token-by-token from Ollama
- Chat conversations persisted in DB with title auto-generation
- Conversations renameable and deleteable via Filament table actions
- URL-encoded conversation IDs (?chat=ID)
- Auto-focus input on keypress
- PDF export with download button in chat
- docker-compose.local.yml for local Docker testing

New files:
- Database: chat_conversations, chat_messages tables
- Services: OllamaClient, ChatService, ToolRegistry, 15 composite tools
- UI: AiChat Filament page with Livewire + Alpine.js streaming
- Controller: AiChatStreamController for SSE endpoint
- Config: ai-chat.php for Ollama connection settings
- Route: /assistant/stream (SSE), /assistant/pdf (PDF download)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Switch to Qwen 3 8B with /no_think for fast responses
- Keep model loaded permanently (keep_alive: -1)
- Stream all responses including post-tool-call replies
- Filter <think> tags during streaming (no text jumping)
- Require user approval for all mutating tool calls with preview
- Complete server-side even if user navigates away (ignore_user_abort)
- Consolidated 45 tools into 15 composite tools (prevents GPU OOM)
- Added tools: FAQ, lunch reload, layout deviations, users, roles
- Shortened all tool descriptions for faster prompt processing
- Page subheading disclaimer instead of inline text
- Autoscroll via Livewire morph hook + DOM ID
- Matching bubble layout during streaming and after (flex gap-0.5)
- Strip leading newlines from streamed content
- Tool call counter shown live during streaming
- No flash between streamed and final message
- PDF download button without duplicate text link
- Scrollbar padding (pr-3)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an Ollama-backed AI assistant to the Wochenplan app, including a Filament UI for chat, persistent conversations/messages, an SSE streaming endpoint, and a set of “tools” the model can call to read/update application data.

Changes:

  • Introduces chat persistence (conversations + messages) and an Ollama client/service layer with tool-call support.
  • Adds a Filament “Assistant” page + Blade/Alpine UI to stream responses and approve/reject pending actions.
  • Adds new web routes and Docker/env configuration for connecting to an Ollama server.

Reviewed changes

Copilot reviewed 72 out of 73 changed files in this pull request and generated 17 comments.

Show a summary per file
File Description
routes/web.php Adds assistant streaming route and a PDF download route.
resources/views/filament/pages/ai-chat.blade.php New chat UI (list view + streaming chat view with pending-action cards).
docker-compose.yml Adds env vars for Ollama base URL/model.
docker-compose.local.yml Adds a local compose setup with Ollama env defaults.
database/seeders/ShieldSeeder.php Adds Filament page permission for AiChat and auto_approve_ai_actions.
database/migrations/2026_03_22_000001_create_chat_conversations_table.php Creates chat_conversations table.
database/migrations/2026_03_22_000002_create_chat_messages_table.php Creates chat_messages table with tool-call/pending-action fields.
config/ai-chat.php New config for Ollama base URL/model/token/temperature.
app/Services/AiChat/AiChatTool.php Defines the tool interface for LLM tool calls.
app/Services/AiChat/ToolRegistry.php Registers tools and exposes tool schemas to Ollama.
app/Services/AiChat/OllamaClient.php Implements non-streaming + streaming Ollama chat calls and title generation.
app/Services/AiChat/ChatService.php Implements message processing, tool execution, pending approvals, and message formatting.
app/Services/AiChat/Tools/GetScheduleForDate.php Tool to assemble a “day schedule” payload (lessons/templates/absences/lunch).
app/Services/AiChat/Tools/GetFaq.php Tool providing static FAQ/help content.
app/Services/AiChat/Tools/ExportDayPdf.php Tool to generate a day PDF and return a download URL.
app/Services/AiChat/Tools/ReloadLunch.php Tool to clear/reload lunch cache for a date.
app/Services/AiChat/Tools/ListActivityLogs.php Tool to query activity logs with filters.
app/Services/AiChat/Tools/ListUsers.php Standalone list-users tool (not currently registered).
app/Services/AiChat/Tools/ListTimes.php Standalone list-times tool (not currently registered).
app/Services/AiChat/Tools/ListRooms.php Standalone list-rooms tool (not currently registered).
app/Services/AiChat/Tools/ListRoles.php Standalone list-roles tool (not currently registered).
app/Services/AiChat/Tools/ListLessons.php Standalone list-lessons tool (not currently registered).
app/Services/AiChat/Tools/ListLessonTemplates.php Standalone list-templates tool (not currently registered).
app/Services/AiChat/Tools/ListLayouts.php Standalone list-layouts tool (not currently registered).
app/Services/AiChat/Tools/ListLayoutDeviations.php Standalone list-layout-deviations tool (not currently registered).
app/Services/AiChat/Tools/ListColors.php Standalone list-colors tool (not currently registered).
app/Services/AiChat/Tools/ListAbsences.php Standalone list-absences tool (not currently registered).
app/Services/AiChat/Tools/CreateUser.php Standalone create-user tool (not currently registered).
app/Services/AiChat/Tools/UpdateUser.php Standalone update-user tool (not currently registered).
app/Services/AiChat/Tools/DeleteUser.php Standalone delete-user tool (not currently registered).
app/Services/AiChat/Tools/CreateTime.php Standalone create-time tool (not currently registered).
app/Services/AiChat/Tools/UpdateTime.php Standalone update-time tool (not currently registered).
app/Services/AiChat/Tools/DeleteTime.php Standalone delete-time tool (not currently registered).
app/Services/AiChat/Tools/CreateRoom.php Standalone create-room tool (not currently registered).
app/Services/AiChat/Tools/UpdateRoom.php Standalone update-room tool (not currently registered).
app/Services/AiChat/Tools/DeleteRoom.php Standalone delete-room tool (not currently registered).
app/Services/AiChat/Tools/CreateRole.php Standalone create-role tool (not currently registered).
app/Services/AiChat/Tools/UpdateRole.php Standalone update-role tool (not currently registered).
app/Services/AiChat/Tools/DeleteRole.php Standalone delete-role tool (not currently registered).
app/Services/AiChat/Tools/CreateLesson.php Standalone create-lesson tool (not currently registered).
app/Services/AiChat/Tools/UpdateLesson.php Standalone update-lesson tool (not currently registered).
app/Services/AiChat/Tools/DeleteLesson.php Standalone delete-lesson tool (not currently registered).
app/Services/AiChat/Tools/CreateLessonTemplate.php Standalone create-template tool (not currently registered).
app/Services/AiChat/Tools/UpdateLessonTemplate.php Standalone update-template tool (not currently registered).
app/Services/AiChat/Tools/DeleteLessonTemplate.php Standalone delete-template tool (not currently registered).
app/Services/AiChat/Tools/CreateLayout.php Standalone create-layout tool (not currently registered).
app/Services/AiChat/Tools/UpdateLayout.php Standalone update-layout tool (not currently registered).
app/Services/AiChat/Tools/DeleteLayout.php Standalone delete-layout tool (not currently registered).
app/Services/AiChat/Tools/CreateLayoutDeviation.php Standalone create-layout-deviation tool (not currently registered).
app/Services/AiChat/Tools/UpdateLayoutDeviation.php Standalone update-layout-deviation tool (not currently registered).
app/Services/AiChat/Tools/DeleteLayoutDeviation.php Standalone delete-layout-deviation tool (not currently registered).
app/Services/AiChat/Tools/CreateColor.php Standalone create-color tool (not currently registered).
app/Services/AiChat/Tools/UpdateColor.php Standalone update-color tool (not currently registered).
app/Services/AiChat/Tools/DeleteColor.php Standalone delete-color tool (not currently registered).
app/Services/AiChat/Tools/CreateAbsence.php Standalone create-absence tool (not currently registered).
app/Services/AiChat/Tools/UpdateAbsence.php Standalone update-absence tool (not currently registered).
app/Services/AiChat/Tools/DeleteAbsence.php Standalone delete-absence tool (not currently registered).
app/Services/AiChat/Tools/Composite/ManageUsers.php Composite “manage users” tool (list/create/update/delete via one entrypoint).
app/Services/AiChat/Tools/Composite/ManageTimes.php Composite “manage times” tool.
app/Services/AiChat/Tools/Composite/ManageRooms.php Composite “manage rooms” tool.
app/Services/AiChat/Tools/Composite/ManageRoles.php Composite “manage roles” tool.
app/Services/AiChat/Tools/Composite/ManageLessons.php Composite “manage lessons” tool.
app/Services/AiChat/Tools/Composite/ManageLessonTemplates.php Composite “manage lesson templates” tool.
app/Services/AiChat/Tools/Composite/ManageLayouts.php Composite “manage layouts” tool.
app/Services/AiChat/Tools/Composite/ManageLayoutDeviations.php Composite “manage layout deviations” tool.
app/Services/AiChat/Tools/Composite/ManageColors.php Composite “manage colors” tool.
app/Services/AiChat/Tools/Composite/ManageAbsences.php Composite “manage absences” tool.
app/Models/ChatConversation.php New Eloquent model for chat conversations.
app/Models/ChatMessage.php New Eloquent model for chat messages (tool calls + pending actions).
app/Http/Controllers/AiChatStreamController.php New SSE controller to drive tool-call rounds + final streaming response.
app/Filament/Pages/AiChat.php New Filament page implementing chat list + chat view behavior.
.env.example Adds Ollama env variable placeholders.
docker-compose.yml Adds Ollama env variables to app service.
.gitignore Ignores .claude/settings.local.json.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread routes/web.php Outdated
Comment thread routes/web.php Outdated
Comment thread app/Services/AiChat/ChatService.php Outdated
Comment thread app/Services/AiChat/Tools/CreateLesson.php Outdated
Comment thread app/Services/AiChat/OllamaClient.php Outdated
Comment thread app/Services/AiChat/ChatService.php Outdated
Comment on lines +32 to +50
private function registerAll(): void
{
$this->tools = [
new GetScheduleForDate,
new ManageLessons,
new ManageLessonTemplates,
new ManageAbsences,
new ManageRooms,
new ManageTimes,
new ManageColors,
new ManageLayouts,
new ManageLayoutDeviations,
new ManageUsers,
new ManageRoles,
new ListActivityLogs,
new ExportDayPdf,
new GetFaq,
new ReloadLunch,
];

Copilot AI Mar 22, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ToolRegistry registers only the composite manage_* tools, but the system prompt + several tool descriptions instruct the model to call list_rooms, list_times, list_colors, create_lesson, etc. Those tool names are not registered here, so the model will produce "Unknown tool" calls. Either register the list_* / create_* / update_* / delete_* tools, or update the prompt + descriptions to use only the registered manage_* tools.

Copilot uses AI. Check for mistakes.
Comment on lines +59 to +61
->query(
ChatConversation::where('user_id', auth()->id())->latest('updated_at')
)

Copilot AI Mar 22, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The conversation list is sorted by updated_at, but sending messages doesn’t update the conversation timestamp (no touch() and ChatMessage doesn’t declare $touches = ['conversation']). After the title is set, active chats may stop bubbling to the top. Consider touching the conversation when creating messages or adding $touches on ChatMessage so updated_at reflects activity.

Copilot uses AI. Check for mistakes.
Comment thread routes/web.php
Comment on lines +17 to +35
// AI Chat streaming endpoint
Route::get('/assistant/stream', [AiChatStreamController::class, 'stream'])
->name('assistant.stream')
->middleware(['auth']);

// AI Chat PDF download
Route::get('/assistant/pdf', function (\Illuminate\Http\Request $request) {
$request->validate(['date' => 'required|date']);
$date = \Carbon\Carbon::parse($request->input('date'));
$pdfService = app(\App\Services\PdfExportService::class);
$base64 = $pdfService->getOrGeneratePdf($date->toDateString());
if (! $base64) {
abort(404, 'PDF nicht verfügbar.');
}
$binary = base64_decode($base64);
$filename = $date->locale('de')->translatedFormat('l, d.m.Y').'.pdf';

return response()->streamDownload(fn () => print ($binary), $filename, ['Content-Type' => 'application/pdf']);
})->name('assistant.pdf')->middleware(['auth']);

Copilot AI Mar 22, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New routes add important auth/authorization behavior (/assistant/stream, /assistant/pdf) but there are no accompanying feature tests. Given existing route/controller feature tests, add coverage for: unauthenticated requests are rejected, /assistant/pdf enforces view_day::pdf, and /assistant/stream rejects invalid/missing conversation_id and enforces conversation ownership.

Copilot uses AI. Check for mistakes.
Comment thread app/Http/Controllers/AiChatStreamController.php Outdated
- Change /assistant/stream from GET to POST with CSRF protection
- Add view_day::pdf authorization check on /assistant/pdf route
- Add strict mode and error handling to base64_decode for PDF downloads
- Re-check permissions in approveAction() before executing pending tools
- Scope absences by view_any_absence permission in GetScheduleForDate
- Treat composite tool "list" actions as read-only (no approval needed)
- Fix tool_call_id uniqueness across rounds in both controller and service
- Update system prompt and tool descriptions to reference manage_* tools
- Add $touches to ChatMessage so conversations sort by latest activity
- Remove dead $tmpFile/$tmpPath and unused $hadToolCalls variables
- Default ollama_base_url to localhost instead of private IP

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 72 out of 73 changed files in this pull request and generated 7 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread app/Services/AiChat/ChatService.php Outdated
}
$messages[] = [
'role' => 'tool',
'content' => $msg->content ?? '{}',

Copilot AI Mar 23, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In buildMessages(), tool messages are sent to the LLM without their tool_call_id. For OpenAI-style tool calling (which Ollama mirrors), the model uses tool_call_id to associate tool results with the corresponding tool_calls; omitting it can break multi-tool-call rounds or cause the model to ignore tool results. Include tool_call_id (and typically tool name) in the message payload for role=tool when building $messages.

Suggested change
'content' => $msg->content ?? '{}',
'content' => $msg->content ?? '{}',
'tool_call_id' => $msg->tool_call_id ?? null,
'name' => $msg->tool_name ?? null,

Copilot uses AI. Check for mistakes.
Comment on lines +74 to +76
<div class="prose dark:prose-invert prose-sm max-w-none text-sm [&>*]:my-0 [&>*+*]:mt-1.5">
{!! \Illuminate\Support\Str::markdown($msg['content']) !!}
</div>

Copilot AI Mar 23, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assistant messages are rendered with {!! Str::markdown(...) !!} which outputs unescaped HTML. Since the content comes from an LLM (untrusted), this can enable stored XSS if the markdown converter allows raw HTML (Laravel's default CommonMark config does unless explicitly disabled). Render markdown in a safe mode (strip/escape HTML + disallow unsafe links) or sanitize the generated HTML before output.

Copilot uses AI. Check for mistakes.
Comment on lines +238 to +250
try {
const resp = await fetch('/assistant/stream', {
method: 'POST',
headers: {
'Accept': 'text/event-stream',
'Content-Type': 'application/json',
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]')?.content,
},
body: JSON.stringify({ conversation_id: convId }),
});
const reader = resp.body.getReader();
const dec = new TextDecoder();
let buf = '', evt = 'content';

Copilot AI Mar 23, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The streaming fetch() handler assumes a successful response and immediately calls resp.body.getReader(). If the request fails (non-2xx) or the browser provides no body (e.g., network error), this will throw and leave the UI stuck in streaming=true. Add an resp.ok/resp.body guard and emit an error state (and set streaming=false) before returning.

Copilot uses AI. Check for mistakes.
Comment thread docker-compose.local.yml Outdated
Comment on lines +35 to +37
AZURE_PROXY:
OLLAMA_BASE_URL: http://100.90.166.15:11434
AI_CHAT_MODEL: qwen3:8b

Copilot AI Mar 23, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

docker-compose.local.yml hardcodes OLLAMA_BASE_URL to a specific private IP. Committing environment-specific infrastructure details makes local setup brittle and can leak internal network topology. Prefer leaving it blank, using a default like http://host.docker.internal:11434, or referencing a .env/override file for per-developer values.

Copilot uses AI. Check for mistakes.
Comment thread app/Http/Controllers/AiChatStreamController.php Outdated
Comment thread routes/web.php
Comment on lines +17 to +43
// AI Chat streaming endpoint
Route::post('/assistant/stream', [AiChatStreamController::class, 'stream'])
->name('assistant.stream')
->middleware(['auth']);

// AI Chat PDF download
Route::get('/assistant/pdf', function (\Illuminate\Http\Request $request) {
$request->validate(['date' => 'required|date']);

if (! $request->user()->can('view_day::pdf')) {
abort(403, 'Keine Berechtigung.');
}

$date = \Carbon\Carbon::parse($request->input('date'));
$pdfService = app(\App\Services\PdfExportService::class);
$base64 = $pdfService->getOrGeneratePdf($date->toDateString());
if (! $base64) {
abort(404, 'PDF nicht verfügbar.');
}
$binary = base64_decode($base64, true);
if ($binary === false) {
abort(500, 'PDF-Daten fehlerhaft.');
}
$filename = $date->locale('de')->translatedFormat('l, d.m.Y').'.pdf';

return response()->streamDownload(fn () => print ($binary), $filename, ['Content-Type' => 'application/pdf']);
})->name('assistant.pdf')->middleware(['auth']);

Copilot AI Mar 23, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New assistant endpoints (/assistant/stream and /assistant/pdf) introduce significant behavior (SSE streaming, tool approval workflow, permission-gated PDF download) but there are no feature tests added alongside. The repo already has feature tests for similar HTTP endpoints (e.g., LunchController); adding tests here would help prevent regressions around auth/authorization and error handling.

Copilot uses AI. Check for mistakes.
- Disable tools in approve/reject LLM continuation to force natural language
- Include tool_call_id and name in buildMessages() for proper tool correlation
- Sanitize markdown output with html_input=strip and allow_unsafe_links=false
- Add resp.ok/resp.body guard in streaming fetch to prevent stuck UI
- Add curl error handling in streamFromOllama with SSE error event
- Change docker-compose.local.yml OLLAMA_BASE_URL to host.docker.internal

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 72 out of 73 changed files in this pull request and generated 7 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +238 to +246
try {
const resp = await fetch('/assistant/stream', {
method: 'POST',
headers: {
'Accept': 'text/event-stream',
'Content-Type': 'application/json',
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]')?.content,
},
body: JSON.stringify({ conversation_id: convId }),

Copilot AI Mar 23, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In startStream() wird der Endpoint hart auf fetch('/assistant/stream', ...) verdrahtet. Das bricht in Setups mit Prefix/Subdirectory (oder wenn sich die Route ändert). Bitte die URL serverseitig via route('assistant.stream') in die View injizieren (z.B. als data- Attribut) und im JS verwenden.

Copilot uses AI. Check for mistakes.
Comment thread .env.example Outdated
Comment thread docker-compose.yml
Comment on lines +56 to +58
# AI Chat (Ollama on Leipzig server)
OLLAMA_BASE_URL:
AI_CHAT_MODEL: qwen3:8b

Copilot AI Mar 23, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OLLAMA_BASE_URL: ist in docker-compose als leerer Wert definiert. Das setzt die ENV-Variable im Container typischerweise auf leeren String und überschreibt damit den Default aus config/ai-chat.php (führt dazu, dass Ollama-Requests gegen '/api/chat' statt gegen http://... gehen). Bitte den Eintrag entfernen oder einen gültigen Default/Platzhalter setzen (oder per ${OLLAMA_BASE_URL:-http://...} arbeiten).

Copilot uses AI. Check for mistakes.
Comment on lines +205 to +211
if (trim($after) !== '') {
$ctrl->sse('content', ['text' => $after]);
}
}
// Skip think content — don't send to client
} else {
$ctrl->sse('content', ['text' => $chunk]);

Copilot AI Mar 23, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Beim Streaming werden aktuell nur <think>...</think> Blöcke herausgefiltert. OllamaClient::stripThinkingTags() entfernt aber zusätzlich <tool_call>...</tool_call>. Wenn das Modell (trotz Prompt) <tool_call> ausgibt, würde das ungefiltert zum Client gestreamt und im UI sichtbar. Bitte beim Streaming ebenfalls <tool_call>-Tags filtern/strippen (oder zentral die bestehende Strip-Funktion nutzen).

Suggested change
if (trim($after) !== '') {
$ctrl->sse('content', ['text' => $after]);
}
}
// Skip think content — don't send to client
} else {
$ctrl->sse('content', ['text' => $chunk]);
// Additionally strip any <tool_call>...</tool_call> blocks before sending
$afterStripped = preg_replace('/<tool_call>.*?<\/tool_call>/s', '', $after);
if (trim($afterStripped) !== '') {
$ctrl->sse('content', ['text' => $afterStripped]);
}
}
// Skip think content — don't send to client
} else {
// Strip any <tool_call>...</tool_call> blocks before sending regular chunks
$chunkStripped = preg_replace('/<tool_call>.*?<\/tool_call>/s', '', $chunk);
if (trim($chunkStripped) !== '') {
$ctrl->sse('content', ['text' => $chunkStripped]);
}

Copilot uses AI. Check for mistakes.
Comment thread app/Services/AiChat/ChatService.php Outdated
Comment on lines +269 to +274
Log::error('AI Chat tool execution error', [
'tool' => $tool->name(),
'error' => $e->getMessage(),
]);

return ['error' => 'Fehler bei der Ausführung: '.$e->getMessage()];

Copilot AI Mar 23, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

executeTool() gibt bei Exceptions die rohe Exception-Message an den Chat zurück (Fehler bei der Ausführung: ...). Das kann internere Details (SQL, Pfade, etc.) an UI/LLM leaken. Bitte für Benutzer/LLM eine generische Fehlermeldung zurückgeben und Details nur serverseitig loggen (ggf. mit Korrelations-ID).

Suggested change
Log::error('AI Chat tool execution error', [
'tool' => $tool->name(),
'error' => $e->getMessage(),
]);
return ['error' => 'Fehler bei der Ausführung: '.$e->getMessage()];
$correlationId = uniqid('tool_', true);
Log::error('AI Chat tool execution error', [
'tool' => $tool->name(),
'error' => $e->getMessage(),
'correlation_id' => $correlationId,
]);
return [
'error' => 'Es ist ein Fehler bei der Ausführung des Werkzeugs aufgetreten. Bitte versuchen Sie es später erneut. (Fehler-ID: ' . $correlationId . ')',
];

Copilot uses AI. Check for mistakes.
Comment on lines +57 to +60
return [
'success' => true,
'download_url' => "/assistant/pdf?date={$date}",
'message' => "PDF für {$date} wurde erstellt.",

Copilot AI Mar 23, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Der Tool-Return enthält einen hartcodierten Pfad "/assistant/pdf?date=...". Das ist fragil bei URL-Prefixes oder Route-Änderungen und erfordert manuelles URL-Encoding. Bitte die URL über route('assistant.pdf', ['date' => $date]) erzeugen (und idealerweise url() für absolute Links), damit Download-Links zuverlässig bleiben.

Copilot uses AI. Check for mistakes.
Comment thread routes/web.php Outdated
Comment on lines +30 to +32
$date = \Carbon\Carbon::parse($request->input('date'));
$pdfService = app(\App\Services\PdfExportService::class);
$base64 = $pdfService->getOrGeneratePdf($date->toDateString());

Copilot AI Mar 23, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In diesem Download-Endpoint wird immer getOrGeneratePdf() genutzt, wodurch auch für Vergangenheitsdaten PDFs (re)generiert und DB-Einträge verändert werden können. In app/Filament/Pages/Day::downloadPdf() wird für vergangene Daten bewusst getExistingPdf() verwendet. Bitte hier konsistent sein (für vergangene Daten nur ausliefern, für heute/zukünftig ggf. generieren) und zusätzlich die Locale/Formatierung wie im Day-Download über config('app.locale') statt fest de verwenden.

Copilot uses AI. Check for mistakes.
PalmarHealer and others added 3 commits March 23, 2026 14:05
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
- Use route() helper for stream URL in Blade and PDF download URL in tool
- Set sensible OLLAMA_BASE_URL defaults in .env.example and docker-compose
- Filter <tool_call> tags from streamed chunks (not just <think>)
- Use generic error messages in executeTool() with correlation IDs
- Past dates: serve existing PDFs only, consistent with Day page behavior
- Use config('app.locale') for PDF filename locale

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Local compose overrides should not be tracked in version control.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
PalmarHealer and others added 3 commits March 23, 2026 20:08
- Add requiredPermissionForAction() to AiChatTool interface
- Composite tools return action-specific permissions (e.g. create_lesson,
  update_lesson, delete_lesson) instead of just the base view permission
- Controller and service now check granular permissions before executing
  or showing pending action prompts
- Enable custom_permissions in Shield config so auto_approve_ai_actions
  is visible and manageable in the role editor UI

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Use Blade csrf_token() instead of meta tag query (Filament layout
  may not have the csrf-token meta tag, causing 419 errors)
- Add migration to create auto_approve_ai_actions permission in DB
  so it appears in Shield's custom permissions section
- Show error message to user when stream request fails

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ActivityLogResource only uses view prefix, not view_any.
Also cleaned up page_Dashboard and view_any_activity::log
permissions from DB (Dashboard is excluded from Shield).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants