fix: keep recovered generation jobs polling
This commit is contained in:
@@ -110,7 +110,7 @@ Use this guide when the user reports behavior. Start from the symptom row, inspe
|
||||
|
||||
| Symptom | Check Files | What To Verify |
|
||||
| --- | --- | --- |
|
||||
| History missing after generation or login/account switch | `src/lib/creation-history-store.ts`, `src/app/api/creation-history/route.ts`, create panel component | History POST, `works` insert, URL not data URL except reverse prompt placeholder, and `miaojing_auth_updated` triggers a fresh server fetch. Create panels now also recover queued/running jobs from `/api/generation-jobs` so a refresh or re-login can reattach the live task before it finishes. |
|
||||
| History missing after generation or login/account switch | `src/lib/creation-history-store.ts`, `src/app/api/creation-history/route.ts`, create panel component | History POST, `works` insert, URL not data URL except reverse prompt placeholder, and `miaojing_auth_updated` triggers a fresh server fetch. Create panels now also recover queued/running jobs from `/api/generation-jobs` so a refresh or re-login can reattach the live task before it finishes. If the task card reappears after refresh but never turns into a result/error, inspect `src/components/create/use-generation-job-recovery.ts`; active-task state updates must not be part of the polling effect dependency list, or the recovery poller can be cancelled immediately after reattaching a job. |
|
||||
| Detail delete removes only local history, skips confirmation, or record reappears after refresh | `src/components/creation-detail-dialog.tsx`, `src/components/ui/alert-dialog.tsx`, `src/lib/creation-history-store.ts`, `src/app/api/creation-history/route.ts`, `src/components/profile/creation-history-tab.tsx` | The detail action is labeled `删除作品` and must open a confirmation dialog warning that deletion cannot be recovered. Logged-in deletion should call `DELETE /api/creation-history?id=...` first, then refresh local history from the server. Check bearer token availability and route ownership filter (`id` + `user_id`). |
|
||||
| Published work not in gallery or share to gallery is slow | `src/lib/creation-history-store.ts`, `src/lib/gallery-publish-media.ts`, `src/app/api/gallery/publish/route.ts`, `src/app/api/gallery/route.ts`, `src/app/gallery/page.tsx` | `is_public = true`, `status = completed`, stable `/api/local-storage/...` `result_url`, media copied/reused into gallery storage, and current filters. New generated `/api/local-storage/...` image/video URLs should use the publish fast path in `gallery-publish-media` and must not synchronously copy object-backed originals during share; external URLs still need copying and should fail the publish request if media preparation fails. Also check whether the browser marked the work shared before `/api/gallery/publish` returned success; local `published=true` without `publishedAt` is stale and should not block retry. For older incidents, inspect server logs/API status for publish failures that the previous frontend swallowed. |
|
||||
| Imported gallery images do not render after production data import | `src/app/api/admin/data-export/route.ts`, `src/app/api/admin/data-import/route.ts`, `src/lib/local-storage.ts`, `src/app/api/local-storage/[...path]/route.ts`, DB `works.result_url` | New exports should include `_media`; import should persist media through the active storage adapter. If using an older export without `_media`, DB rows alone cannot recreate missing `/api/local-storage/*` files. For object migration, run `pnpm run storage:sync-object -- --verify-only` before switching to `STORAGE_MODE=object`. |
|
||||
|
||||
@@ -59,4 +59,14 @@ await runTest('create panels restore active jobs from the server after reload or
|
||||
}
|
||||
});
|
||||
|
||||
await runTest('recovered job polling is not cancelled by active task state updates', () => {
|
||||
const source = read('src/components/create/use-generation-job-recovery.ts');
|
||||
assert.match(source, /knownJobIdsRef/);
|
||||
const effectMatches = [...source.matchAll(/useEffect\(\(\) => \{[\s\S]*?void recover\(\);[\s\S]*?\}, \[([^\]]*)\]\);/g)];
|
||||
assert.ok(effectMatches.length > 0, 'expected to find the recovery polling effect');
|
||||
const dependencies = effectMatches.at(-1)?.[1] || '';
|
||||
assert.doesNotMatch(dependencies, /\btypes\b/);
|
||||
assert.doesNotMatch(dependencies, /\bnormalizedKnownJobIds\b/);
|
||||
});
|
||||
|
||||
if (process.exitCode) process.exit(process.exitCode);
|
||||
|
||||
@@ -66,12 +66,17 @@ export function useGenerationJobRecovery({
|
||||
const onTaskRecoveredRef = useRef(onTaskRecovered);
|
||||
const onTaskFinishedRef = useRef(onTaskFinished);
|
||||
const onTaskFailedRef = useRef(onTaskFailed);
|
||||
const typesRef = useRef(types);
|
||||
const knownJobIdsRef = useRef(new Set<string>());
|
||||
const normalizedTypes = useMemo(() => types.slice().sort().join(','), [types]);
|
||||
const normalizedKnownJobIds = useMemo(
|
||||
() => new Set(knownJobIds.map(id => id.trim()).filter(Boolean)),
|
||||
[knownJobIds],
|
||||
);
|
||||
const normalizedKnownJobIds = useMemo(() => knownJobIds.map(id => id.trim()).filter(Boolean).sort().join(','), [knownJobIds]);
|
||||
|
||||
useEffect(() => {
|
||||
typesRef.current = types;
|
||||
}, [normalizedTypes, types]);
|
||||
useEffect(() => {
|
||||
knownJobIdsRef.current = new Set(normalizedKnownJobIds ? normalizedKnownJobIds.split(',') : []);
|
||||
}, [normalizedKnownJobIds]);
|
||||
useEffect(() => {
|
||||
onTaskRecoveredRef.current = onTaskRecovered;
|
||||
}, [onTaskRecovered]);
|
||||
@@ -92,11 +97,11 @@ export function useGenerationJobRecovery({
|
||||
try {
|
||||
await new Promise(resolve => window.setTimeout(resolve, 800));
|
||||
if (cancelled) return;
|
||||
const jobs = await fetchActiveGenerationJobs(types);
|
||||
const jobs = await fetchActiveGenerationJobs(typesRef.current);
|
||||
if (cancelled) return;
|
||||
for (const job of jobs) {
|
||||
const task = normalizeJobTask(job);
|
||||
if (!task || activeJobIdsRef.current.has(task.id) || normalizedKnownJobIds.has(task.id)) continue;
|
||||
if (!task || activeJobIdsRef.current.has(task.id) || knownJobIdsRef.current.has(task.id)) continue;
|
||||
activeJobIdsRef.current.add(task.id);
|
||||
onTaskRecoveredRef.current(task, job);
|
||||
void (async () => {
|
||||
@@ -154,5 +159,5 @@ export function useGenerationJobRecovery({
|
||||
cancelled = true;
|
||||
window.removeEventListener('miaojing_auth_updated', handleAuthUpdated);
|
||||
};
|
||||
}, [isEnabled, normalizedKnownJobIds, normalizedTypes, types]);
|
||||
}, [isEnabled, normalizedTypes]);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user