This commit is contained in:
2026-05-11 15:23:34 +08:00
parent ed70a140a2
commit 547cbbb4a5
25 changed files with 721 additions and 332 deletions
+93 -4
View File
@@ -2,6 +2,7 @@
namespace app\service;
use Throwable;
use support\Db;
class ArchiveRepository
@@ -24,8 +25,6 @@ class ArchiveRepository
'series' => $archive['series'] ?? null,
'tags' => json_encode($archive['tags'] ?? [], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
'metadata' => json_encode($archive['metadata'] ?? [], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
'content' => $archive['content'] ?? null,
'raw' => $archive['raw'] ?? null,
'chunks' => json_encode($chunkUids, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
]
);
@@ -194,6 +193,79 @@ class ArchiveRepository
Db::table('archives')->where('archive_uid', $archiveUid)->update($updates);
}
public function queuePendingAiMetadataArchives(int $limit): array
{
$limit = max(1, $limit);
$rows = Db::table('archives')
->where(function ($query): void {
$query
->whereNull('title')
->orWhere('title', '')
->orWhereNull('summary')
->orWhere('summary', '')
->orWhereNull('author')
->orWhere('author', '')
->orWhereNull('year')
->orWhere('year', '<=', 0)
->orWhereRaw("tags = '[]'::jsonb")
->orWhereRaw("metadata #>> '{title_source}' = 'fallback'");
})
->orderByDesc('updated_time')
->limit(max($limit * 5, 50))
->get()
->all();
$archiveUids = [];
foreach ($rows as $row) {
$archive = $this->archiveToArray($row);
if (!$this->archiveNeedsMetadata($archive) || !$this->shouldQueueAiMetadata($archive)) {
continue;
}
$archiveUids[] = (string) $archive['archive_uid'];
$this->markAiMetadataStatus((string) $archive['archive_uid'], 'queued', null);
if (count($archiveUids) >= $limit) {
break;
}
}
return $archiveUids;
}
public function markAiMetadataStatus(string $archiveUid, string $status, ?string $error = null, array $extra = []): void
{
$archive = $this->findArchive($archiveUid);
if ($archive === null) {
return;
}
$metadata = is_array($archive['metadata'] ?? null) ? $archive['metadata'] : [];
$aiMeta = is_array($metadata['ai_enrichment'] ?? null) ? $metadata['ai_enrichment'] : [];
$attempt = ((int) ($aiMeta['attempt'] ?? 0)) + ($status === 'processing' ? 1 : 0);
$metadata['ai_enrichment'] = array_merge($aiMeta, $extra, [
'status' => $status,
'attempt' => $attempt,
'updated_at' => date(DATE_ATOM),
'error' => $error,
]);
Db::table('archives')->where('archive_uid', $archiveUid)->update([
'metadata' => json_encode($metadata, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
]);
}
public function isAiMetadataRetryable(Throwable $exception): bool
{
if (!$exception instanceof \app\service\LLM\LLMRequestException) {
return true;
}
$statusCode = $exception->statusCode();
return $statusCode === null || $statusCode === 429 || $statusCode >= 500;
}
public function archiveNeedsMetadata(array $archive): bool
{
foreach (['title', 'year', 'author', 'tags', 'summary'] as $field) {
@@ -227,12 +299,29 @@ class ArchiveRepository
'series' => $archive->series,
'tags' => json_decode($archive->tags ?? '[]', true) ?: [],
'metadata' => json_decode($archive->metadata ?? '{}', true) ?: [],
'content' => $archive->content,
'raw' => $archive->raw,
'chunks' => json_decode($archive->chunks ?? '[]', true) ?: [],
];
}
private function shouldQueueAiMetadata(array $archive): bool
{
$aiMeta = is_array($archive['metadata']['ai_enrichment'] ?? null) ? $archive['metadata']['ai_enrichment'] : [];
$status = (string) ($aiMeta['status'] ?? '');
if ($status === 'failed_terminal') {
return false;
}
$updatedAt = strtotime((string) ($aiMeta['updated_at'] ?? '')) ?: 0;
$staleAfter = max(60, (int) config('queue.ai_metadata.stale_after_seconds', 900));
$isFresh = $updatedAt > 0 && (time() - $updatedAt) < $staleAfter;
if (in_array($status, ['queued', 'processing'], true) && $isFresh) {
return false;
}
return true;
}
private function chunkRowToArray(object $row): array
{
return [