From 68aa62db6f3c1cdcac62d5da2ff06db7e813b56a Mon Sep 17 00:00:00 2001 From: boban Date: Sun, 19 Apr 2026 07:31:36 +0200 Subject: [PATCH] Fix: korrekte Credit-Verrechnung + Duplikat-Schutz MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Credits nur bei status='success': Konflikt/Fehler/Failed → 0 Credits - Multi-Action: Credits proportional zu erfolgreichen Aktionen - AgentActionService: Duplikat-Check vor Event/Task/Notiz-Anlage - Multi-Action-Status: 'success'/'partial'/'failed' statt immer 'success' - Gilt für Web (Livewire/Agent/Index) und API (AgentChatController) Co-Authored-By: Claude Sonnet 4.6 --- .../Controllers/Api/AgentChatController.php | 21 +++++++++++++---- src/app/Livewire/Agent/Index.php | 15 ++++++++---- src/app/Services/AgentActionService.php | 23 +++++++++++++++++-- 3 files changed, 48 insertions(+), 11 deletions(-) diff --git a/src/app/Http/Controllers/Api/AgentChatController.php b/src/app/Http/Controllers/Api/AgentChatController.php index 224d6ba..ce4b642 100644 --- a/src/app/Http/Controllers/Api/AgentChatController.php +++ b/src/app/Http/Controllers/Api/AgentChatController.php @@ -137,9 +137,13 @@ class AgentChatController extends Controller foreach ($parsed['_multi'] as $action) { $results[] = $actionService->handle($user, $action); } + $successCount = collect($results)->where('status', 'success')->count(); + $totalCount = max(1, count($results)); $actionResult = [ - 'status' => collect($results)->every(fn ($r) => $r['status'] === 'success') ? 'success' : 'partial', - 'results' => $results, + 'status' => $successCount === count($results) ? 'success' : ($successCount > 0 ? 'partial' : 'failed'), + 'results' => $results, + 'success_count' => $successCount, + 'total_count' => $totalCount, ]; } elseif (isset($parsed['type']) && $parsed['type'] !== 'chat') { $actionService = new AgentActionService(); @@ -180,9 +184,16 @@ class AgentChatController extends Controller $shouldLog = true; if ($isAction) { - $credits = (($actionResult['status'] ?? '') === 'error') - ? 0 - : $this->calculateCredits($usage, $aiConfig, $type); + $status = $actionResult['status'] ?? 'error'; + if ($status !== 'success' && $status !== 'partial') { + $credits = 0; + } elseif (isset($actionResult['success_count'], $actionResult['total_count'])) { + // Multi-Action: proportional zu erfolgreichen Aktionen + $full = $this->calculateCredits($usage, $aiConfig, $type); + $credits = (int) ceil($full * $actionResult['success_count'] / $actionResult['total_count']); + } else { + $credits = $this->calculateCredits($usage, $aiConfig, $type); + } } elseif ($historyCount === 0) { $credits = 5; } else { diff --git a/src/app/Livewire/Agent/Index.php b/src/app/Livewire/Agent/Index.php index 8104c4b..48d4be4 100644 --- a/src/app/Livewire/Agent/Index.php +++ b/src/app/Livewire/Agent/Index.php @@ -129,12 +129,17 @@ class Index extends Component } $duration = round((microtime(true) - $startTime) * 1000); - $credits = $this->calculateCredits(['type' => 'multi'], $duration, $usage); + $successCount = collect($results)->where('status', 'success')->count(); + $totalCount = max(1, count($results)); + $fullCredits = $this->calculateCredits(['type' => 'multi'], $duration, $usage); + $credits = $successCount > 0 + ? (int) ceil($fullCredits * $successCount / $totalCount) + : 0; $combinedResult = [ - 'status' => 'success', + 'status' => $successCount === count($results) ? 'success' : ($successCount > 0 ? 'partial' : 'failed'), 'message' => implode(' | ', $messages) ?: 'Erledigt!', - 'meta' => ['actions' => count($actions)], + 'meta' => ['actions' => count($actions)], ]; $this->logConversationAction($userMessage, ['type' => 'multi'], $combinedResult, $model, $duration, $credits); @@ -209,7 +214,9 @@ class Index extends Component $this->dispatch('agent:sent'); } else { - $credits = $this->calculateCredits($parsed, $duration, $usage); + $credits = ($result['status'] === 'success') + ? $this->calculateCredits($parsed, $duration, $usage) + : 0; $this->logConversationAction($userMessage, $parsed, $result, $model, $duration, $credits); if ($result['status'] === 'success' && ($parsed['type'] ?? '') === 'event') { diff --git a/src/app/Services/AgentActionService.php b/src/app/Services/AgentActionService.php index 192e007..fd48875 100644 --- a/src/app/Services/AgentActionService.php +++ b/src/app/Services/AgentActionService.php @@ -48,9 +48,14 @@ class AgentActionService } if (!empty($data['start']) && !empty($data['end'])) { + $title = $data['title'] ?? 'Termin'; + $dateStr = Carbon::parse($data['start'])->toDateString(); + if (Event::where('user_id', $user->id)->where('title', $title)->whereDate('starts_at', $dateStr)->exists()) { + return ['status' => 'failed', 'message' => "Termin \"{$title}\" existiert an diesem Tag bereits.", 'meta' => []]; + } return $planner->plan($user, array_merge($data, [ - 'title' => $data['title'] ?? 'Termin', + 'title' => $title, 'start' => $data['start'], 'end' => $data['end'], 'duration_minutes' => Carbon::parse($data['start']) @@ -67,9 +72,14 @@ class AgentActionService */ if (!empty($data['datetime'])) { + $title = $data['title'] ?? 'Termin'; + $dateStr = Carbon::parse($data['datetime'])->toDateString(); + if (Event::where('user_id', $user->id)->where('title', $title)->whereDate('starts_at', $dateStr)->exists()) { + return ['status' => 'failed', 'message' => "Termin \"{$title}\" existiert an diesem Tag bereits.", 'meta' => []]; + } return $planner->plan($user, array_merge($data, [ - 'title' => $data['title'] ?? 'Termin', + 'title' => $title, 'start' => $data['datetime'], 'duration_minutes' => $data['duration_minutes'] ?? null, 'ai_duration' => $data['ai_duration'] ?? null, @@ -92,6 +102,11 @@ class AgentActionService protected static function handleNote(User $user, array $data): array { + $content = $data['content'] ?? $data['text'] ?? ''; + if ($content && Note::where('user_id', $user->id)->where('content', $content)->whereDate('created_at', today())->exists()) { + return ['status' => 'failed', 'message' => 'Diese Notiz wurde heute bereits angelegt.', 'meta' => []]; + } + $note = Note::create([ 'user_id' => $user->id, 'title' => $data['title'] ?? null, @@ -127,6 +142,10 @@ class AgentActionService { $title = $data['title'] ?? $data['description'] ?? 'Aufgabe'; + if (Task::where('user_id', $user->id)->where('title', $title)->whereDate('created_at', today())->exists()) { + return ['status' => 'failed', 'message' => "Aufgabe \"{$title}\" wurde heute bereits angelegt.", 'meta' => []]; + } + // due_at: AI schickt due_at, due_date oder datetime $dueAt = null; if (!empty($data['due_at'])) {