Files
neonail-database/app/Jobs/ProcessNailPolishImage.php
T
HousemannandClaude Sonnet 4.6 f60221d0ef Make Ollama prompt editable in admin AI settings
- Add prompt column to ai_settings (migration)
- AiSetting: DEFAULT_PROMPT constant + getActivePrompt() (falls back to default if empty)
- ProcessNailPolishImage: reads prompt from DB instead of hardcoded
- Admin view: textarea to edit prompt, shows active prompt, reset-to-default link

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 18:11:47 +02:00

111 lines
3.6 KiB
PHP

<?php
namespace App\Jobs;
use App\Models\AiSetting;
use App\Models\Manufacturer;
use App\Models\NailPolish;
use App\Models\NailPolishImport;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
class ProcessNailPolishImage implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public int $timeout = 180;
public int $tries = 2;
public function __construct(private int $importId) {}
public function handle(): void
{
$import = NailPolishImport::findOrFail($this->importId);
$import->update(['status' => 'processing']);
try {
$ai = AiSetting::get();
$imageB64 = base64_encode(Storage::disk('public')->get($import->image_path));
$prompt = $ai->getActivePrompt();
$response = Http::timeout(160)->post("{$ai->ollama_url}/api/generate", [
'model' => $ai->ollama_model,
'prompt' => $prompt,
'images' => [$imageB64],
'stream' => false,
]);
if ($response->failed()) {
throw new \RuntimeException('Ollama API Fehler: ' . $response->status());
}
$raw = $response->json('response', '');
$data = $this->parseJson($raw);
if (!$data) {
throw new \RuntimeException('Ollama-Antwort konnte nicht als JSON geparst werden: ' . $raw);
}
// Hersteller suchen oder anlegen
$manufacturer = null;
if (!empty($data['manufacturer'])) {
$manufacturer = Manufacturer::firstOrCreate(
['name' => trim($data['manufacturer'])]
);
}
// Nagellack-Nummer eindeutig machen falls nötig
$number = $data['number'] ?? ('import-' . $this->importId);
if (NailPolish::where('number', $number)->exists()) {
$number .= '-' . $this->importId;
}
$nailPolish = NailPolish::create([
'name' => $data['name'] ?? 'Unbekannt',
'number' => $number,
'manufacturer_id' => $manufacturer?->id,
'image_path' => $import->image_path,
]);
// Zur Sammlung des Users hinzufügen
$import->user->nailPolishes()->syncWithoutDetaching([$nailPolish->id]);
$import->update([
'status' => 'done',
'result' => $data,
'nail_polish_id' => $nailPolish->id,
]);
} catch (\Throwable $e) {
Log::error('ProcessNailPolishImage fehlgeschlagen', [
'import_id' => $this->importId,
'error' => $e->getMessage(),
]);
$import->update([
'status' => 'failed',
'error_message' => $e->getMessage(),
]);
}
}
private function parseJson(string $raw): ?array
{
// JSON aus der Antwort extrahieren (Ollama kann Text drumherum haben)
if (preg_match('/\{.*\}/s', $raw, $matches)) {
$decoded = json_decode($matches[0], true);
if (json_last_error() === JSON_ERROR_NONE) {
return $decoded;
}
}
return null;
}
}