AI Assistant — multi-provider chat (Claude / GPT / Gemini)

Schema:
- ai_chats: company_id, user_id, title, provider; index pe activitate
- ai_messages: role (system/user/assistant), content, meta JSON (tokens, latency, model)

Service AiAssistantService (multi-provider):
- ask($chat, $message): persistă mesajul user, build system prompt cu context
  tenant (statistici clienți/mașini/cereri/datorii), apelează API-ul providerului,
  persistă răspunsul cu meta (tokens, latency)
- callClaude: api.anthropic.com/v1/messages cu claude-sonnet-4-5
- callOpenAI: api.openai.com/v1/chat/completions cu gpt-4o-mini
- callGemini: generativelanguage.googleapis.com cu gemini-1.5-flash
- Try/catch pe toate; eroare devine mesaj asistent fără să crape

System prompt include:
- Numele și orașul companiei
- Statistici curente (clienți, mașini, cereri noi, fișe active, datorii)
- Limita stricta: NU inventează date

Custom Filament Page /app/ai-assistant (group Analiză):
- Sidebar stâng: listă conversații (last 20), buton 'Nouă' + delete cu confirm
- Main: bubble chat (user dreapta albastru, asistent stânga gri)
- Meta jos pe răspuns: provider · latency · tokens
- Empty state friendly cu instrucțiuni configurare
- Loading indicator (3 dots animate) când AI răspunde
- Auto-scroll la mesaj nou
- Enter trimite, Shift+Enter newline
- Auto-titlu chat din primul mesaj user (60 chars)

Settings page extins cu secțiune 'Asistent AI':
- Provider implicit (claude/gpt/gemini)
- 3 chei API (password fields, revealable)
- Key-urile salvate în companies.settings.ai (per tenant, izolat)
This commit is contained in:
2026-05-07 14:50:56 +00:00
parent 7ce78c350c
commit 976c0f03e3
7 changed files with 586 additions and 0 deletions
@@ -0,0 +1,40 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('ai_chats', function (Blueprint $t) {
$t->id();
$t->foreignId('company_id')->constrained()->cascadeOnDelete();
$t->foreignId('user_id')->constrained()->cascadeOnDelete();
$t->string('title')->default('Conversație nouă');
$t->string('provider')->default('claude'); // claude / gpt / gemini
$t->timestamps();
$t->index(['company_id', 'user_id', 'updated_at']);
});
Schema::create('ai_messages', function (Blueprint $t) {
$t->id();
$t->foreignId('company_id')->constrained()->cascadeOnDelete();
$t->foreignId('ai_chat_id')->constrained()->cascadeOnDelete();
$t->string('role'); // system / user / assistant
$t->longText('content');
$t->json('meta')->nullable(); // tokens, model, latency_ms
$t->timestamps();
$t->index(['company_id', 'ai_chat_id', 'created_at']);
});
}
public function down(): void
{
Schema::dropIfExists('ai_messages');
Schema::dropIfExists('ai_chats');
}
};