# AI Chat (e-Filiz) Bileşeni — AI Ajan Dokümanı

> Bu doküman, `e-Filiz` AI sohbet balonunun mimarisini, veri modelini ve
> entegrasyon kurallarını açıklar. AI ajanları bu bileşen üzerinde çalışırken
> buradaki sözleşmelere uymak zorundadır.

## 1. Genel Bakış

`e-Filiz`, sayfanın herhangi bir yerine yerleştirilebilen kompakt, tek balonluk
bir AI sohbet bileşenidir. İki temel görevi vardır:

1. **Görsel/animasyon katmanı** — mesajları daktilo efektiyle yazar, avatar
   konuşurken hareket eder, içgörü detaylarını ve aksiyon bağlantısını
   yazma bittikten sonra açar.
2. **Kalıcılık katmanı** — konuşmayı ve mesajları `ai_conversations` /
   `ai_conversation_messages` tablolarına kaydeder (multi-tenant uyumlu).

Bileşen **konuşma akışını yönetmez**. Akış (fatura yükleme, AI çıkarma,
onay vb.) sayfaya aittir; sayfa, bileşenin `window` köprülerini çağırarak
e-Filiz'e "ne söyleyeceğini" bildirir.

## 2. Dosya Haritası

| Dosya | Görev |
|---|---|
| `app/Livewire/AiChat.php` | Livewire v3 bileşeni; kalıcılık metodları |
| `resources/views/livewire/ai-chat.blade.php` | Balon UI, Alpine.js animasyon, window köprüleri |
| `app/Models/AiConversation.php` | Konuşma modeli (`currentTeamScope` global scope) |
| `app/Models/AiConversationMessage.php` | Mesaj modeli |
| `app/Http/Controllers/HomeController.php` | Dashboard'da mod/karşılama/içgörü verilerini hazırlar |
| `app/Services/DashboardGreetingService.php` | Saate/duruma göre karşılama metni üretir |
| `app/Services/FinancialInsightService.php` | Finansal içgörü metni + detay + aksiyon üretir |
| `tests/Feature/AiChatHistoryTest.php` | Lifecycle ve render testleri |

## 3. Bileşen Parametreleri (mount)

Blade'de `@livewire('ai-chat', [...])` ile veya `<livewire:ai-chat ... />`
etiketiyle yerleştirilir:

```blade
<livewire:ai-chat :greeting="$greeting_message" :mode="$chat_mode"
    :insight="$insight_message" :action="$insight_action" :actions="$insight_actions"
    :details="$insight_details" conversation-type="invoice_creation" />
```

| Parametre | Tip | Varsayılan | Açıklama |
|---|---|---|---|
| `greeting` | `string` | `''` | `greet` modunda yazılacak karşılama metni |
| `assistantName` | `string` | `'e-Filiz'` | Balon başlığında görünen isim |
| `avatarSrc` | `string` | CDN avatarı | Avatar görseli (boş geçilirse varsayılan korunur) |
| `conversationType` | `string` | `'invoice_creation'` | `ai_conversations.type` değerine yazılır |
| `mode` | `string` | `'greet'` | `'greet'` veya `'insight'` (diğer her şey greet sayılır) |
| `insight` | `string` | `''` | `insight` modunda yazılacak metin |
| `action` | `?array` | `null` | `['label' => string, 'url' => string]` — içgörü butonu |
| `actions` | `array` | `[]` | `[['label','url'], ...]` — içgörü/rehberlik sonrası ek butonlar (çoklu yönlendirme) |
| `details` | `array` | `[]` | `[['label','value','note'], ...]` — içgörü kalemleri |

### Mod Davranışı

- **`greet`**: Karşılama metni hemen daktilo ile yazılır. `insight` doluysa
  60–120 sn rastgele gecikme sonrası içgörü ayrıca sunulur.
- **`insight`**: Karşılama atlanır, doğrudan içgörü metni yazılır; yazma
  bitince `details` listesi ve `action`/`actions` butonları balon içinde açılır.

Dashboard'da mod kararı `HomeController` içinde verilir:

- `users.last_seen_at` boş, bugüne ait değil veya 12 saatten eski ise → `greet`
- Aksi halde → `insight` (içgörü yoksa `FinancialInsightService::fallback()`)
- Her dashboard ziyaretinde `last_seen_at = now()` güncellenir.
- 30 dakikadır dokunulmamış `active` konuşmalar `cancelled` yapılır.

## 4. Window Köprüleri (Entegrasyon Sözleşmesi)

Bileşen `init()` sırasında `window` üzerine 4 fonksiyon bağlar. Sayfa JS'i
yalnızca bu fonksiyonları kullanır; bileşenin iç durumuna dokunmaz.

> ⚠️ Çağrılardan önce `if (window.eFilizSay)` gibi null kontrolü yapın;
> bileşen henüz render olmamış olabilir.

### `window.eFilizStart(metadata?: object)`

Yeni bir konuşma kaydı açar. Zaten açık konuşma varsa no-op.

- `metadata` → `ai_conversations.metadata` (JSON) olarak saklanır.
- Örnek: `window.eFilizStart({ file_name: 'fatura.pdf' })`

### `window.eFilizSay(message, options?)`

Asistan mesajını balonda daktilo ile gösterir **ve** veritabanına kaydeder.

- `message: string` — boşsa sadece bekleyen içgörü ekleri açılır.
- `options`:
  - `conversation_status: string` — konuşma durumu güncellenir
    (`active`, `completed`, `error`, `cancelled`).
  - `metadata: object` — mesaja özel meta (`invoice_number`, `edit_url` vb.).
- Yeni mesaj yazılırsa bekleyen içgörü ekleri (detay listesi/buton) iptal edilir.
- Konuşma henüz yoksa `persistAssistant` tembel olarak `startConversation()`
  çağırır — yani ilk `eFilizSay` çağrısı konuşmayı kendiliğinden açar.

### `window.eFilizUser(message, metadata?, conversationStatus?)`

Kullanıcı aksiyonunu (dosya yükleme, işletme onayı, vazgeçme vb.) kaydeder.
Balonda **görsel gösterim yapılmaz**, yalnızca kalıcılık vardır.

- `conversationStatus` verilirse konuşma durumu güncellenir.
- Örnek: `window.eFilizUser('İşletme onaylandı: X Ltd.', { company_id: 5 })`

### `window.eFilizFinish(status)`

Konuşmayı verilen durumla kapatır (`completed`, `cancelled`, `error` ...).

### Tipik Akış (Fatura Yükleme Örneği)

```
eFilizStart({ file_name })
eFilizUser('Dosya yüklendi: fatura.pdf', { file_name })
eFilizSay('Dosyanı aldım, incelemeye başlıyorum…')
eFilizSay('Faturanın içeriğini okuyorum…')
   └─ (AI extract isteği)
eFilizUser('İşletme onaylandı: X Ltd.', { company_id: 7 })
eFilizSay('Taslak faturanı hazırladım!', { conversation_status: 'completed',
                                            metadata: { invoice_number, edit_url } })
```

Dashboard, tekrar eden çağrıları kısaltmak için bir `say(message, options)`
yardımcısı tanımlar; bu helper yalnızca `window.eFilizSay`'i sarmalar.

## 5. Livewire Public Metodları

Bileşenin kalıcılık metodları doğrudan da test edilebilir
(`Livewire::test(AiChat::class)`):

| Metod | Davranış |
|---|---|
| `startConversation(array $metadata = [])` | `conversationId` yoksa yeni `AiConversation` oluşturur |
| `persistAssistant(string $content, array $options = [])` | `role=assistant` mesaj kaydı + opsiyonel durum güncelleme |
| `persistUser(string $content, ?array $metadata, ?string $status)` | `role=user` mesaj kaydı + opsiyonel durum güncelleme |
| `finishConversation(string $status)` | Konuşma durumunu günceller |

Kayıt kuralları:

- `content` en fazla **2000 karakter** (`mb_substr` ile kırpılır).
- `sort_order` mevcut maksimum değerin 1 fazlası olarak atanır.
- Konuşma yoksa `persistAssistant`/`persistUser` sessizce tembel kurulum yapar;
  bileşen hata fırlatmaz.

## 6. Veri Modeli

### `ai_conversations`

| Kolon | Tip | Açıklama |
|---|---|---|
| `user_id` | FK | Konuşmayı başlatan kullanıcı |
| `current_team_id` | FK | Multi-tenant takım kimliği |
| `type` | string | Konuşma türü (`invoice_creation` vb.) |
| `status` | string | `active` / `completed` / `cancelled` / `error` |
| `metadata` | JSON (cast) | Başlangıç bağlamı (ör. `file_name`) |

### `ai_conversation_messages`

| Kolon | Tip | Açıklama |
|---|---|---|
| `ai_conversation_id` | FK | Üst konuşma |
| `role` | string | `assistant` / `user` |
| `content` | text | Mesaj metni (max 2000 karakter) |
| `metadata` | JSON (cast) | Mesaja özel meta |
| `sort_order` | int | Mesaj sırası (1, 2, 3, ...) |

### Multi-Tenant Kuralları

- `AiConversation` modelinde `currentTeamScope` global scope aktiftir.
- Bileşen kendi kaydına erişirken `withoutGlobalScopes()` kullanır çünkü
  `conversationId` doğrudan kendi oluşturduğu kaydın id'sidir; ancak
  **yeni sorgularda scope'u bypass etmeyin**.
- `AiConversationMessage` üzerinde takım scope'u yoktur; erişim her zaman üst
  konuşma üzerinden yapılmalıdır.

## 7. Frontend Detayları (Blade/Alpine)

- Balon, `x-data` ile yönetilen şu durumlara sahiptir: `typedText`, `typing`,
  `talking`, `details`, `insightActions`, `showDetails`, `showAction`, `pendingReveal`.
- `wire:ignore` span: daktilo animasyonu Livewire morph'undan etkilenmesin
  diye `x-text` ile yazılır.
- `data-*` attribute'ları (`data-greeting`, `data-mode`, `data-insight`,
  `data-insight-action`, `data-insight-actions`, `data-insight-details`)
  `@json(...)` + `JSON_HEX_*` bayraklarıyla XSS'e karşı güvenli şekilde serileştirilir.
- Avatar hareketi (`#efiliz-avatar`) JS ile rastgele üretilir; `transition`
  ile yumuşatılır. Bileşen dışında bu id'yi kullanan başka öğe koymayın.
- İçgörü ekleri (`details` listesi + `action`/`actions` butonları) yalnızca
  metin yazımı bittikten sonra açılır (`revealInsightExtras`).

### Rehberlik Butonları (`actions`)

Kullanıcının hiç kullanmadığı fonksiyonlara yönlendiren metinler sonundaki
butonlar `actions` parametresiyle taşınır:

- Biçim: `[['label' => 'Buton metni', 'url' => route('...')], ...]`
- Her açılışta en fazla **bir** rehber sunulur (`FinancialInsightService::unusedFeatures()`).
- Tek butonluk `action` ile birlikte de kullanılabilir; butonlar tek satırda
  alt alta değil, sarmalı (flex-wrap) dizilir.

## 8. Testler

`tests/Feature/AiChatHistoryTest.php` şu davranışları kapsar:

1. Dashboard bileşeni render eder (`efiliz-avatar`, `window.eFilizSay` görünür).
2. Lifecycle kaydı: `startConversation` → mesajlar → durum/metin/sıra doğrulaması.
3. `last_seen_at` mantığı: ilk giriş `greet`, aynı gün tekrar `insight`.
4. İçgörü detay satırları ve aksiyon linki render edilir.

`tests/Feature/FinancialInsightTest.php` içgörü üretimini kapsar:

1. Alacak içgörüleri: detay kalemleri, gecikme notları, `Tümünü gör` aksiyonu.
2. Rehberlik: ekipte hiç kayıt olmayan özellik için rehberlik üretilir
   (`VARLIKLAR › Nakit Kasası` vb.), özellik zaten kullanılıyorsa üretilmez.

> Not: Testler paylaşımlı geliştirme veritabanına karşı çalışır;
> `RefreshDatabase` kullanılmaz, her test kendi kaydını temizler.

## 9. AI Ajanları İçin Kurallar (Özet)

1. Yeni bir sayfada e-Filiz kullanacaksanız **yalnızca window köprülerini**
   çağırın; bileşen props'larını runtime'da değiştirmeye çalışmayın.
2. Her `eFilizSay` çağrısı kalıcı kayıt oluşturur — döngü içinde spam
   çağrı yapmayın.
3. Konuşma durumunu akışın sonunda mutlaka kapatın
   (`conversation_status: 'completed' | 'error' | 'cancelled'`).
4. Kullanıcı aksiyonlarını `eFilizUser` ile kaydedin; bunlar balonda
   görünmez, analiz için saklanır.
5. Global scope'u bypass ederek konuşma listelemeyin; gerekiyorsa Policy
   ile yetkilendirin.
6. `metadata` JSON kolonlarına şema gereği kritik veri koymayın; kimlik
   ilişkileri için gerçek kolonlar kullanın.
7. Bileşen paragrafı değiştirirken `wire:ignore` span'ı ve `data-*`
   serileştirme bayraklarını koruyun.
