hasPlatformRelay()); } public function summarize(Transcript $transcript): AiSummary { $summary = AiSummary::create([ 'owner_ref' => $transcript->owner_ref, 'session_id' => $transcript->session_id, 'transcript_id' => $transcript->id, 'status' => 'processing', ]); try { $result = $this->generateSummary((string) $transcript->content); $summary->update([ 'status' => 'ready', 'summary' => $result['summary'] ?? '', 'decisions' => $result['decisions'] ?? [], ]); foreach ($result['action_items'] ?? [] as $item) { ActionItem::create([ 'owner_ref' => $transcript->owner_ref, 'ai_summary_id' => $summary->id, 'assignee_name' => $item['assignee'] ?? null, 'description' => $item['description'] ?? '', 'due_date' => $item['due_date'] ?? null, 'status' => 'open', ]); } } catch (\Throwable $e) { report($e); $summary->update(['status' => 'failed', 'summary' => 'AI summary could not be generated.']); } app(MeetNotificationService::class)->aiSummaryReady($summary->fresh()); return $summary->fresh(); } /** * @return array{summary: string, decisions: array, action_items: array} */ protected function generateSummary(string $transcript): array { if (! $this->enabled()) { return $this->stubSummary($transcript); } $prompt = <<hasLocalKey() ? $this->viaOpenAi($prompt) : $this->viaPlatform($prompt); $parsed = json_decode($reply, true); if (! is_array($parsed)) { return ['summary' => $reply, 'decisions' => [], 'action_items' => []]; } return $parsed; } protected function stubSummary(string $transcript): array { $preview = Str::limit($transcript, 500); return [ 'summary' => "Meeting summary (AI not configured).\n\nTranscript preview:\n".$preview, 'decisions' => [], 'action_items' => [], ]; } protected function viaOpenAi(string $prompt): string { $res = Http::withToken((string) config('meet.ai.api_key'))->acceptJson()->timeout(60) ->post('https://api.openai.com/v1/chat/completions', [ 'model' => config('meet.ai.model', 'gpt-4o-mini'), 'temperature' => 0.2, 'response_format' => ['type' => 'json_object'], 'messages' => [ ['role' => 'system', 'content' => 'You summarize Ladill Meet transcripts. Return valid JSON only.'], ['role' => 'user', 'content' => $prompt], ], ]); if ($res->failed()) { throw new RuntimeException('OpenAI request failed: '.$res->status()); } return trim((string) $res->json('choices.0.message.content', '')); } protected function viaPlatform(string $prompt): string { $base = rtrim((string) config('meet.ai.platform_api_url'), '/'); $res = Http::withToken((string) config('meet.ai.platform_api_key'))->acceptJson()->timeout(60) ->post($base.'/afia/chat', [ 'product' => 'meet', 'message' => $prompt, 'history' => [], 'system_prompt' => 'You summarize Ladill Meet transcripts. Return valid JSON only.', ]); if ($res->failed()) { throw new RuntimeException('Platform AI relay failed.'); } return trim((string) $res->json('reply', '')); } protected function hasLocalKey(): bool { return (string) config('meet.ai.api_key') !== ''; } protected function hasPlatformRelay(): bool { return rtrim((string) config('meet.ai.platform_api_url'), '/') !== '' && (string) config('meet.ai.platform_api_key') !== ''; } }