import '../../features/shared/job_files_repository.dart'; import '../../features/shared/tenant_team_repository.dart'; import '../../models/job_file.dart'; import '../../models/tenant.dart'; import '../api/pocketbase_client.dart'; // ── Message segments ────────────────────────────────────────────────────────── sealed class MessageSegment {} class TextSegment extends MessageSegment { TextSegment(this.text); final String text; } class ActionSegment extends MessageSegment { ActionSegment(this.action); final AiAction action; } // ── Action model ────────────────────────────────────────────────────────────── class AiAction { const AiAction({ required this.type, required this.params, required this.label, }); final String type; final Map params; final String label; bool get isDangerous => type == 'cancel_job'; bool get isFileAction => type == 'job_files'; } // ── Action outcome ──────────────────────────────────────────────────────────── sealed class ActionOutcome {} class ActionSuccess extends ActionOutcome { ActionSuccess(this.message); final String message; } class ActionError extends ActionOutcome { ActionError(this.error); final String error; } class ActionFiles extends ActionOutcome { ActionFiles(this.files); final List files; } // ── Parser ──────────────────────────────────────────────────────────────────── List parseSegments(String text) { // Strip code fences wrapping tags that the AI sometimes emits. // Handles: ```xml\n\n``` and ```\n\n``` text = text.replaceAllMapped( RegExp(r'```(?:xml)?\s*\n(\s*]*/>)\s*\n\s*```'), (m) => m.group(1)!, ); // Also handle inline variant: ```xml ``` text = text.replaceAllMapped( RegExp(r'```(?:xml)?\s*(]*/>)\s*```'), (m) => m.group(1)!, ); final pattern = RegExp(r'', dotAll: true); final segments = []; int last = 0; for (final m in pattern.allMatches(text)) { final before = text.substring(last, m.start).trim(); if (before.isNotEmpty) segments.add(TextSegment(before)); final attrs = _parseAttrs(m.group(1) ?? ''); segments.add(ActionSegment(AiAction( type: attrs['type'] ?? '', params: attrs, label: attrs['label'] ?? attrs['type'] ?? 'İşlem', ))); last = m.end; } final rest = text.substring(last).trim(); if (rest.isNotEmpty) segments.add(TextSegment(rest)); return segments; } Map _parseAttrs(String s) { final result = {}; for (final m in RegExp(r'(\w+)="([^"]*)"').allMatches(s)) { result[m.group(1)!] = m.group(2)!; } return result; } // ── Executor ────────────────────────────────────────────────────────────────── class AiActionExecutor { static final _pb = PocketBaseClient.instance.pb; static Future execute( AiAction action, TenantMembership membership, ) async { try { return switch (action.type) { 'cancel_job' => await _cancelJob(action.params), 'mark_delivered' => await _markDelivered(action.params), 'job_files' => await _jobFiles(action.params), 'add_member' => await _addMember(action.params, membership), _ => ActionError('Bilinmeyen işlem türü: ${action.type}'), }; } catch (e) { final msg = e.toString(); if (msg.length > 120) return ActionError('Sunucu hatası'); return ActionError(msg); } } static Future _cancelJob(Map p) async { final id = p['job_id']; if (id == null || id.isEmpty) return ActionError('İş ID bulunamadı.'); await _pb.collection('jobs').update(id, body: {'status': 'cancelled'}); return ActionSuccess('İş başarıyla iptal edildi.'); } static Future _markDelivered(Map p) async { final id = p['job_id']; if (id == null || id.isEmpty) return ActionError('İş ID bulunamadı.'); await _pb.collection('jobs').update(id, body: {'status': 'delivered'}); return ActionSuccess('İş teslim edildi olarak işaretlendi.'); } static Future _jobFiles(Map p) async { final id = p['job_id']; if (id == null || id.isEmpty) return ActionError('İş ID bulunamadı.'); final files = await JobFilesRepository.instance.listForJob(id); if (files.isEmpty) return ActionSuccess('Bu iş için henüz dosya yüklenmemiş.'); return ActionFiles(files); } static Future _addMember( Map p, TenantMembership membership, ) async { final email = p['email']; final firstName = p['first_name']; final lastName = p['last_name'] ?? ''; final role = p['role']; final password = p['password']; if (email == null || firstName == null || role == null || password == null) { return ActionError('Eksik bilgi: e-posta, ad, rol ve şifre gerekli.'); } await TenantTeamRepository.instance.addMember( tenantId: membership.tenant.id, email: email, password: password, firstName: firstName, lastName: lastName, role: TenantMembership.parseRole(role), ); return ActionSuccess('$firstName $lastName ekibe eklendi.'); } }