user()->artworks()->with(['tags', 'categories.contentType', 'artworkAiAssist'])->findOrFail($id); return response()->json([ 'data' => $this->aiAssist->payloadFor($artwork), ]); } public function analyze(Request $request, int $id): JsonResponse { $artwork = $request->user()->artworks()->with(['tags', 'categories.contentType'])->findOrFail($id); $payload = $request->validate([ 'direct' => ['sometimes', 'boolean'], 'intent' => ['sometimes', 'nullable', 'string', 'in:analyze,title,description,tags,category,similar'], 'provider' => ['sometimes', 'nullable', 'string'], ]); $direct = (bool) ($payload['direct'] ?? false); $intent = $payload['intent'] ?? null; $provider = $this->normalizeProviderOption($payload['provider'] ?? null); if ($provider === null && array_key_exists('provider', $payload) && $payload['provider'] !== null) { return response()->json([ 'success' => false, 'message' => 'Invalid provider. Supported values: lm_studio, together.', ], 422); } if ($direct) { $assist = $this->aiAssist->analyzeDirect($artwork, false, $intent, $provider); return response()->json([ 'success' => true, 'status' => $assist->status, 'direct' => true, 'data' => $this->aiAssist->payloadFor($artwork->fresh(['tags', 'categories.contentType', 'artworkAiAssist'])), ]); } $assist = $this->aiAssist->queueAnalysis($artwork, false, $intent, $provider); return response()->json([ 'success' => true, 'status' => $assist->status, 'direct' => false, ], 202); } public function regenerate(Request $request, int $id): JsonResponse { $artwork = $request->user()->artworks()->with(['tags', 'categories.contentType'])->findOrFail($id); $payload = $request->validate([ 'direct' => ['sometimes', 'boolean'], 'intent' => ['sometimes', 'nullable', 'string', 'in:analyze,title,description,tags,category,similar'], 'provider' => ['sometimes', 'nullable', 'string'], ]); $direct = (bool) ($payload['direct'] ?? false); $intent = $payload['intent'] ?? null; $provider = $this->normalizeProviderOption($payload['provider'] ?? null); if ($provider === null && array_key_exists('provider', $payload) && $payload['provider'] !== null) { return response()->json([ 'success' => false, 'message' => 'Invalid provider. Supported values: lm_studio, together.', ], 422); } if ($direct) { $assist = $this->aiAssist->analyzeDirect($artwork, true, $intent, $provider); return response()->json([ 'success' => true, 'status' => $assist->status, 'direct' => true, 'data' => $this->aiAssist->payloadFor($artwork->fresh(['tags', 'categories.contentType', 'artworkAiAssist'])), ]); } $assist = $this->aiAssist->queueAnalysis($artwork, true, $intent, $provider); return response()->json([ 'success' => true, 'status' => $assist->status, 'direct' => false, ], 202); } public function apply(ApplyArtworkAiAssistRequest $request, int $id): JsonResponse { $artwork = $request->user()->artworks()->with(['tags', 'categories.contentType', 'artworkAiAssist'])->findOrFail($id); return response()->json([ 'success' => true, 'data' => $this->aiAssist->applySuggestions($artwork, $request->validated()), ]); } public function event(Request $request, int $id): JsonResponse { $payload = $request->validate([ 'event_type' => ['required', 'string', 'max:64'], 'meta' => ['sometimes', 'array'], ]); $artwork = $request->user()->artworks()->with('artworkAiAssist')->findOrFail($id); $this->eventService->record( $artwork, (string) $payload['event_type'], (array) ($payload['meta'] ?? []), $artwork->artworkAiAssist, ); return response()->json(['success' => true], 201); } private function normalizeProviderOption(mixed $value): ?string { if ($value === null || trim((string) $value) === '') { return null; } return match (strtolower(trim((string) $value))) { 'lm_studio', 'lm-studio', 'local', 'home' => 'lm_studio', 'together', 'together_ai' => 'together', default => null, }; } }