fix(ai): switch to OpenAI, add error handling in Insights
- AiService: use OpenAI chat completions API (gpt-4o-mini) instead of Anthropic - Add services.openai.api_key config entry - Insights component: check for API key, handle empty links, catch HTTP errors - View: show error banner, hide placeholder when error shown Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>main
parent
3eada67b9a
commit
d9f665f749
|
|
@ -6,24 +6,23 @@ use Illuminate\Support\Facades\Http;
|
|||
|
||||
class AiService
|
||||
{
|
||||
public function complete(string $prompt, string $model = 'claude-sonnet-4-6'): string
|
||||
public function complete(string $prompt, string $model = 'gpt-4o-mini'): string
|
||||
{
|
||||
$apiKey = config('services.anthropic.api_key');
|
||||
$apiKey = config('services.openai.api_key');
|
||||
|
||||
if (! $apiKey) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$response = Http::withHeaders([
|
||||
'x-api-key' => $apiKey,
|
||||
'anthropic-version' => '2023-06-01',
|
||||
'content-type' => 'application/json',
|
||||
])->post('https://api.anthropic.com/v1/messages', [
|
||||
'Authorization' => 'Bearer ' . $apiKey,
|
||||
'Content-Type' => 'application/json',
|
||||
])->post('https://api.openai.com/v1/chat/completions', [
|
||||
'model' => $model,
|
||||
'max_tokens' => 256,
|
||||
'max_tokens' => 512,
|
||||
'messages' => [['role' => 'user', 'content' => $prompt]],
|
||||
]);
|
||||
|
||||
return trim($response->json('content.0.text') ?? '');
|
||||
return trim($response->json('choices.0.message.content') ?? '');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ class Insights extends Component
|
|||
{
|
||||
public string $insights = '';
|
||||
|
||||
public bool $loading = false;
|
||||
public string $error = '';
|
||||
|
||||
public int $workspaceId = 0;
|
||||
|
||||
|
|
@ -23,8 +23,16 @@ class Insights extends Component
|
|||
|
||||
public function generate(): void
|
||||
{
|
||||
$this->error = '';
|
||||
$this->insights = '';
|
||||
|
||||
if (! config('services.openai.api_key')) {
|
||||
$this->error = 'OPENAI_API_KEY is not configured. Add it to .env and restart.';
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$workspace = Workspace::findOrFail($this->workspaceId);
|
||||
$this->loading = true;
|
||||
|
||||
$topLinks = Link::where('workspace_id', $workspace->id)
|
||||
->withCount('clicks')
|
||||
|
|
@ -32,12 +40,26 @@ class Insights extends Component
|
|||
->limit(10)
|
||||
->get(['slug', 'target_url', 'clicks_count']);
|
||||
|
||||
if ($topLinks->isEmpty()) {
|
||||
$this->error = 'No links with clicks found. Create and share some links first.';
|
||||
return;
|
||||
}
|
||||
|
||||
$summary = $topLinks->map(fn ($l) => "{$l->slug}: {$l->clicks_count} clicks → {$l->target_url}")->implode("\n");
|
||||
|
||||
$prompt = "You are a marketing analyst. Analyze these link performance stats and provide 3-5 actionable insights:\n\n{$summary}\n\nBe concise and specific.";
|
||||
|
||||
$this->insights = app(AiService::class)->complete($prompt);
|
||||
$this->loading = false;
|
||||
$result = app(AiService::class)->complete($prompt);
|
||||
|
||||
if ($result === '') {
|
||||
$this->error = 'AI returned an empty response. Check your API key and try again.';
|
||||
return;
|
||||
}
|
||||
|
||||
$this->insights = $result;
|
||||
} catch (\Throwable $e) {
|
||||
$this->error = 'Failed to generate insights: ' . $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
public function render(): View
|
||||
|
|
|
|||
|
|
@ -39,4 +39,8 @@ return [
|
|||
'api_key' => env('ANTHROPIC_API_KEY'),
|
||||
],
|
||||
|
||||
'openai' => [
|
||||
'api_key' => env('OPENAI_API_KEY'),
|
||||
],
|
||||
|
||||
];
|
||||
|
|
|
|||
|
|
@ -10,6 +10,12 @@
|
|||
</x-ui.button>
|
||||
</div>
|
||||
|
||||
@if($error)
|
||||
<div class="p-4 bg-red-500/10 border border-red-500/20 rounded-xl text-sm text-red-400">
|
||||
{{ $error }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if($insights)
|
||||
<div class="p-5 bg-s1 border border-white/[.06] rounded-xl">
|
||||
<div class="flex items-center gap-2 mb-3 text-accent text-xs font-medium">
|
||||
|
|
@ -18,7 +24,7 @@
|
|||
</div>
|
||||
<div class="text-sm text-t1 whitespace-pre-wrap leading-relaxed">{{ $insights }}</div>
|
||||
</div>
|
||||
@else
|
||||
@elseif(!$error)
|
||||
<div class="p-8 bg-s1 border border-white/[.06] rounded-xl text-center">
|
||||
<x-heroicon-o-sparkles class="w-10 h-10 mx-auto text-t3 mb-3" />
|
||||
<p class="text-sm text-t2">Click "Generate Insights" to analyze your top 10 links with AI.</p>
|
||||
|
|
|
|||
Loading…
Reference in New Issue