Save workspace changes
This commit is contained in:
@@ -12,6 +12,7 @@ use App\Models\ContentType;
|
||||
use App\Services\TagNormalizer;
|
||||
use App\Services\TagService;
|
||||
use App\Services\Vision\AiArtworkVectorSearchService;
|
||||
use App\Services\Vision\ArtworkLlmTagSuggestionService;
|
||||
use App\Services\Vision\VisionService;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
@@ -26,11 +27,12 @@ final class StudioAiAssistService
|
||||
private readonly AiArtworkVectorSearchService $similarity,
|
||||
private readonly TagService $tagService,
|
||||
private readonly TagNormalizer $tagNormalizer,
|
||||
private readonly ArtworkLlmTagSuggestionService $llmTagSuggestions,
|
||||
private readonly StudioAiAssistEventService $eventService,
|
||||
) {
|
||||
}
|
||||
|
||||
public function queueAnalysis(Artwork $artwork, bool $force = false, ?string $intent = null): ArtworkAiAssist
|
||||
public function queueAnalysis(Artwork $artwork, bool $force = false, ?string $intent = null, ?string $provider = null): ArtworkAiAssist
|
||||
{
|
||||
$assist = $this->assistRecord($artwork);
|
||||
$mode = $assist->mode ?: $this->builder->detectMode($artwork->loadMissing(['tags', 'categories.contentType']), []);
|
||||
@@ -42,26 +44,26 @@ final class StudioAiAssistService
|
||||
])->save();
|
||||
|
||||
$artwork->forceFill(['ai_status' => ArtworkAiAssist::STATUS_QUEUED])->saveQuietly();
|
||||
$meta = ['force' => $force, 'direct' => false, 'intent' => $intent];
|
||||
$meta = ['force' => $force, 'direct' => false, 'intent' => $intent, 'provider' => $provider];
|
||||
$this->appendAction($assist, 'analysis_requested', $meta);
|
||||
$this->eventService->record($artwork, 'analysis_requested', $meta, $assist);
|
||||
|
||||
AnalyzeArtworkAiAssistJob::dispatch($artwork->id, $force)->afterCommit();
|
||||
AnalyzeArtworkAiAssistJob::dispatch($artwork->id, $force, $intent, $provider)->afterCommit();
|
||||
|
||||
return $assist->fresh();
|
||||
}
|
||||
|
||||
public function analyzeDirect(Artwork $artwork, bool $force = false, ?string $intent = null): ArtworkAiAssist
|
||||
public function analyzeDirect(Artwork $artwork, bool $force = false, ?string $intent = null, ?string $provider = null): ArtworkAiAssist
|
||||
{
|
||||
$assist = $this->assistRecord($artwork);
|
||||
$meta = ['force' => $force, 'direct' => true, 'intent' => $intent];
|
||||
$meta = ['force' => $force, 'direct' => true, 'intent' => $intent, 'provider' => $provider];
|
||||
$this->appendAction($assist, 'analysis_requested', $meta);
|
||||
$this->eventService->record($artwork, 'analysis_requested', $meta, $assist);
|
||||
|
||||
return $this->analyze($artwork, $force, $intent);
|
||||
return $this->analyze($artwork, $force, $intent, $provider);
|
||||
}
|
||||
|
||||
public function analyze(Artwork $artwork, bool $force = false, ?string $intent = null): ArtworkAiAssist
|
||||
public function analyze(Artwork $artwork, bool $force = false, ?string $intent = null, ?string $provider = null): ArtworkAiAssist
|
||||
{
|
||||
$artwork->loadMissing(['tags', 'categories.contentType', 'user']);
|
||||
|
||||
@@ -99,7 +101,9 @@ final class StudioAiAssistService
|
||||
|
||||
$titleSuggestions = $this->builder->buildTitleSuggestions($artwork, $analysis, $mode);
|
||||
$descriptionSuggestions = $this->builder->buildDescriptionSuggestions($artwork, $analysis, $mode);
|
||||
$tagSuggestions = $this->builder->buildTagSuggestions($artwork, $analysis, $mode);
|
||||
$fallbackTagSuggestions = $this->builder->buildTagSuggestions($artwork, $analysis, $mode);
|
||||
$llmTagGeneration = $this->llmTagSuggestions->suggestForArtwork($artwork, 10, 15, $provider);
|
||||
$tagSuggestions = $this->mergeTagSuggestions($llmTagGeneration, $fallbackTagSuggestions);
|
||||
$similarCandidates = $this->buildSimilarCandidates($artwork);
|
||||
|
||||
$assist->forceFill([
|
||||
@@ -115,6 +119,7 @@ final class StudioAiAssistService
|
||||
'artwork_id' => (int) $artwork->id,
|
||||
'hash' => $hash,
|
||||
'intent' => $intent,
|
||||
'provider' => $provider,
|
||||
'force' => $force,
|
||||
'current_title' => (string) ($artwork->title ?? ''),
|
||||
'current_description' => (string) ($artwork->description ?? ''),
|
||||
@@ -122,6 +127,7 @@ final class StudioAiAssistService
|
||||
],
|
||||
'vision_debug' => $visionDebug,
|
||||
'analysis' => $analysis,
|
||||
'tag_generation' => $llmTagGeneration,
|
||||
'generated_at' => \now()->toIso8601String(),
|
||||
'force' => $force,
|
||||
],
|
||||
@@ -134,6 +140,7 @@ final class StudioAiAssistService
|
||||
'force' => $force,
|
||||
'mode' => $mode,
|
||||
'intent' => $intent,
|
||||
'provider' => $llmTagGeneration['provider'] ?? $provider,
|
||||
'title_suggestion_count' => count($titleSuggestions),
|
||||
'description_suggestion_count' => count($descriptionSuggestions),
|
||||
'tag_suggestion_count' => count($tagSuggestions),
|
||||
@@ -326,11 +333,54 @@ final class StudioAiAssistService
|
||||
'request' => $assist->raw_response_json['request'] ?? null,
|
||||
'vision_debug' => $assist->raw_response_json['vision_debug'] ?? null,
|
||||
'analysis' => $assist->raw_response_json['analysis'] ?? null,
|
||||
'tag_generation' => $assist->raw_response_json['tag_generation'] ?? null,
|
||||
'generated_at' => $assist->raw_response_json['generated_at'] ?? null,
|
||||
] : null,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array{tags: list<string>, model: ?string, endpoint: ?string, image_url: ?string, variant: string, raw_content?: string, reason?: string, error?: string} $llmResult
|
||||
* @param array<int, array{tag: string, confidence: float|null}> $fallback
|
||||
* @return array<int, array{tag: string, confidence: float|null, source: string}>
|
||||
*/
|
||||
private function mergeTagSuggestions(array $llmResult, array $fallback, int $min = 10, int $max = 15): array
|
||||
{
|
||||
$rows = collect();
|
||||
|
||||
foreach (array_values($llmResult['tags'] ?? []) as $index => $tag) {
|
||||
$rows->push([
|
||||
'tag' => $tag,
|
||||
'confidence' => round(max(0.55, 0.94 - ($index * 0.03)), 2),
|
||||
'source' => 'llm',
|
||||
]);
|
||||
}
|
||||
|
||||
foreach ($fallback as $row) {
|
||||
$rows->push([
|
||||
'tag' => (string) ($row['tag'] ?? ''),
|
||||
'confidence' => isset($row['confidence']) && is_numeric($row['confidence']) ? (float) $row['confidence'] : null,
|
||||
'source' => 'vision',
|
||||
]);
|
||||
}
|
||||
|
||||
$merged = $rows
|
||||
->filter(fn (array $row): bool => trim((string) ($row['tag'] ?? '')) !== '')
|
||||
->unique('tag')
|
||||
->take($max)
|
||||
->values();
|
||||
|
||||
if ($merged->count() < $min && count($fallback) > $merged->count()) {
|
||||
$merged = $rows
|
||||
->filter(fn (array $row): bool => trim((string) ($row['tag'] ?? '')) !== '')
|
||||
->unique('tag')
|
||||
->take(max($min, min($max, $rows->count())))
|
||||
->values();
|
||||
}
|
||||
|
||||
return $merged->all();
|
||||
}
|
||||
|
||||
private function assistRecord(Artwork $artwork): ArtworkAiAssist
|
||||
{
|
||||
return ArtworkAiAssist::query()->firstOrCreate(
|
||||
|
||||
Reference in New Issue
Block a user