Warning: session_start(): Session cannot be started after headers have already been sent in /home/tvrreohg/public_html/manga.php on line 13
load('documents'); $session->load(['messages' => fn ($query) => $query->latest()->take(12)]); $history = $session->messages ->sortBy('created_at') ->map(fn ($chat) => strtoupper($chat->role).": {$chat->content}") ->implode("\n\n"); $prompt = <<phase} Case: Title: {$case->title} Category: {$case->category} Summary: {$case->summary} Timeline: {$case->timeline} People involved: {$case->people_involved} Questions: {$case->questions} Concerns: {$case->concerns} Uploaded documents with extracted text: {$this->documentContext($case)} Recent conversation: {$history} Client message: {$message} PROMPT; return $this->generate($prompt, $case); } public function packet(CaseRecord $case): array { $disclaimer = self::DISCLAIMER; $case->load('documents'); $prompt = <<title} Category: {$case->category} Summary: {$case->summary} Timeline: {$case->timeline} People involved: {$case->people_involved} Questions: {$case->questions} Concerns: {$case->concerns} Uploaded documents: {$this->documentContext($case)} PROMPT; return $this->generate($prompt, $case); } public function explainDocument(CaseDocument $document): array { $disclaimer = self::DISCLAIMER; $text = $document->extracted_text ?: 'No readable text was extracted from this document. If it is an image or scan, describe only what can be safely inferred from the file name and category.'; $prompt = <<original_name} Document category: {$document->category} Extracted text: {$text} PROMPT; return $this->generate($prompt, $document->caseRecord); } private function documentContext(CaseRecord $case): string { return $case->documents ->map(function ($document) { $text = filled($document->extracted_text) ? mb_substr($document->extracted_text, 0, 12000) : 'No extracted text available.'; return "DOCUMENT: {$document->original_name}\nCategory: {$document->category}\nPlain summary: {$document->plain_language_summary}\nText: {$text}"; }) ->implode("\n\n"); } private function generate(string $prompt, ?CaseRecord $case = null): array { $key = config('services.gemini.key'); $model = config('services.gemini.model'); if (! $key) { throw new RuntimeException('Gemini is not configured. Add GEMINI_API_KEY to the environment.'); } $parts = [['text' => mb_substr($prompt, 0, 180000)]]; $imageBytes = 0; if ($case) { foreach ($case->documents->filter(fn ($document) => str_starts_with((string) $document->mime_type, 'image/'))->take(3) as $image) { if (! Storage::disk($image->disk)->exists($image->path) || $image->size > 5 * 1024 * 1024) { continue; } $imageBytes += $image->size; if ($imageBytes > 8 * 1024 * 1024) { break; } $parts[] = [ 'inline_data' => [ 'mime_type' => $image->mime_type, 'data' => base64_encode(Storage::disk($image->disk)->get($image->path)), ], ]; } } try { $response = Http::withHeaders(['x-goog-api-key' => $key]) ->acceptJson() ->timeout(120) ->post("https://generativelanguage.googleapis.com/v1beta/models/{$model}:generateContent", [ 'contents' => [[ 'role' => 'user', 'parts' => $parts, ]], 'generationConfig' => [ 'temperature' => 0.2, 'maxOutputTokens' => 2400, ], ]) ->throw() ->json(); } catch (RequestException $exception) { $status = $exception->response->status(); $providerStatus = $exception->response->json('error.status'); $message = match (true) { in_array($status, [401, 403], true) || $providerStatus === 'PERMISSION_DENIED' => 'The Gemini API key is invalid or does not have access to this model.', $status === 429 || $providerStatus === 'RESOURCE_EXHAUSTED' => 'The Gemini API rate or free-tier limit has been reached. Please try again later.', default => 'The AI service could not complete this request. Please try again later.', }; throw new RuntimeException($message, previous: $exception); } $text = collect(data_get($response, 'candidates.0.content.parts', [])) ->pluck('text') ->filter() ->implode("\n"); if (! $text) { $blockReason = data_get($response, 'promptFeedback.blockReason'); throw new RuntimeException($blockReason ? 'Gemini could not process this request because of its content safety settings.' : 'The AI service returned an empty response.'); } return [ 'content' => $text, 'model' => data_get($response, 'modelVersion', $model), 'input_tokens' => data_get($response, 'usageMetadata.promptTokenCount'), 'output_tokens' => data_get($response, 'usageMetadata.candidatesTokenCount'), ]; } }