auto-save 2026-05-19 11:53 (~3)
This commit is contained in:
@@ -562,6 +562,19 @@
|
||||
"type": "session-heartbeat",
|
||||
"message": "Codex 会话活跃 · 最近命令:codex · 分支 master · 1 项未提交变更 · 最近提交:auto-save 2026-05-19 11:37 (~2)",
|
||||
"files_changed": 1
|
||||
},
|
||||
{
|
||||
"ts": "2026-05-19T11:46:08+08:00",
|
||||
"type": "commit",
|
||||
"message": "fix: handle board uploads and background pack generation",
|
||||
"hash": "8e27d3b",
|
||||
"files_changed": 1
|
||||
},
|
||||
{
|
||||
"ts": "2026-05-19T03:50:00Z",
|
||||
"type": "session-heartbeat",
|
||||
"message": "Codex 会话活跃 · 最近命令:codex · 分支 master · 3 项未提交变更 · 最近提交:fix: handle board uploads and background pack generation",
|
||||
"files_changed": 3
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { startGenerationLock } from '@/lib/generationLocks';
|
||||
import { generateAssetPack } from '@/lib/packGenerator';
|
||||
import { detectProvider } from '@/lib/providers';
|
||||
import { loadSession, saveSession } from '@/lib/storage';
|
||||
import { PACK_ORDER } from '@/lib/templates';
|
||||
import { getPackTemplates, PACK_ORDER } from '@/lib/templates';
|
||||
import type { AssetPack, GenerateAllPacksRequest, GenerateAllPacksResponse, GenSession } from '@/lib/types';
|
||||
|
||||
export const runtime = 'nodejs';
|
||||
@@ -17,6 +18,13 @@ async function persistPackProgress(session: GenSession, imageId: string, pack: A
|
||||
await saveSession(session);
|
||||
}
|
||||
|
||||
function isCompletePack(pack: AssetPack, imageId: string): boolean {
|
||||
if (pack.sourceImageId !== imageId || pack.status !== 'complete') return false;
|
||||
const expectedIds = new Set(getPackTemplates(pack.kind).map(template => template.id));
|
||||
const assetIds = new Set(pack.assets.map(asset => asset.templateId));
|
||||
return expectedIds.size > 0 && [...expectedIds].every(templateId => assetIds.has(templateId));
|
||||
}
|
||||
|
||||
export async function POST(req: Request) {
|
||||
const { sessionId, imageId, background = false } = (await req.json()) as GenerateAllPacksRequest;
|
||||
if (!sessionId || !imageId) {
|
||||
@@ -34,41 +42,75 @@ export async function POST(req: Request) {
|
||||
|
||||
const baseSession = session;
|
||||
const baseSourceImage = sourceImage;
|
||||
const releaseAllLock = startGenerationLock(`packs:all:${sessionId}:${imageId}`);
|
||||
if (!releaseAllLock) {
|
||||
return NextResponse.json({
|
||||
ok: true,
|
||||
background: true,
|
||||
running: true,
|
||||
provider: detectProvider(),
|
||||
}, { status: 202 });
|
||||
}
|
||||
const releaseAll = releaseAllLock;
|
||||
|
||||
async function run() {
|
||||
const packs = [];
|
||||
const packs: AssetPack[] = [];
|
||||
const manifests = [];
|
||||
let workingSession: GenSession = baseSession;
|
||||
|
||||
for (const kind of PACK_ORDER) {
|
||||
const generated = await generateAssetPack({
|
||||
session: workingSession,
|
||||
sourceImage: baseSourceImage,
|
||||
kind,
|
||||
onProgress: progressPack => persistPackProgress(workingSession, imageId, progressPack),
|
||||
});
|
||||
packs.push(generated.pack);
|
||||
manifests.push(generated.manifest);
|
||||
workingSession = {
|
||||
...workingSession,
|
||||
characterSpec: generated.pack.characterSpec,
|
||||
packs: [
|
||||
...(workingSession.packs ?? []).filter(existing => !(existing.kind === kind && existing.sourceImageId === imageId)),
|
||||
generated.pack,
|
||||
],
|
||||
exports: [
|
||||
...(workingSession.exports ?? []).filter(existing => !(existing.packKind === kind && existing.source.sourceImageId === imageId)),
|
||||
generated.manifest,
|
||||
],
|
||||
};
|
||||
try {
|
||||
for (const kind of PACK_ORDER) {
|
||||
const existingPack = workingSession.packs?.find(pack => pack.kind === kind && isCompletePack(pack, imageId));
|
||||
if (existingPack) {
|
||||
const existingManifest = workingSession.exports?.find(manifest => (
|
||||
manifest.packKind === kind &&
|
||||
manifest.source.sourceImageId === imageId &&
|
||||
manifest.packId === existingPack.id
|
||||
));
|
||||
packs.push(existingPack);
|
||||
if (existingManifest) manifests.push(existingManifest);
|
||||
continue;
|
||||
}
|
||||
|
||||
const releasePackLock = startGenerationLock(`pack:${sessionId}:${imageId}:${kind}`);
|
||||
if (!releasePackLock) continue;
|
||||
|
||||
try {
|
||||
const generated = await generateAssetPack({
|
||||
session: workingSession,
|
||||
sourceImage: baseSourceImage,
|
||||
kind,
|
||||
onProgress: progressPack => persistPackProgress(workingSession, imageId, progressPack),
|
||||
});
|
||||
packs.push(generated.pack);
|
||||
manifests.push(generated.manifest);
|
||||
workingSession = {
|
||||
...workingSession,
|
||||
characterSpec: generated.pack.characterSpec,
|
||||
packs: [
|
||||
...(workingSession.packs ?? []).filter(existing => !(existing.kind === kind && existing.sourceImageId === imageId)),
|
||||
generated.pack,
|
||||
],
|
||||
exports: [
|
||||
...(workingSession.exports ?? []).filter(existing => !(existing.packKind === kind && existing.source.sourceImageId === imageId)),
|
||||
generated.manifest,
|
||||
],
|
||||
};
|
||||
} finally {
|
||||
releasePackLock();
|
||||
}
|
||||
}
|
||||
|
||||
await saveSession(workingSession);
|
||||
|
||||
return {
|
||||
packs,
|
||||
manifests,
|
||||
provider: detectProvider(),
|
||||
} satisfies GenerateAllPacksResponse;
|
||||
} finally {
|
||||
releaseAll();
|
||||
}
|
||||
|
||||
await saveSession(workingSession);
|
||||
|
||||
return {
|
||||
packs,
|
||||
manifests,
|
||||
provider: detectProvider(),
|
||||
} satisfies GenerateAllPacksResponse;
|
||||
}
|
||||
|
||||
if (background) {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { startGenerationLock } from '@/lib/generationLocks';
|
||||
import { generateAssetPack } from '@/lib/packGenerator';
|
||||
import { detectProvider } from '@/lib/providers';
|
||||
import { loadSession, saveSession } from '@/lib/storage';
|
||||
@@ -35,24 +36,40 @@ export async function POST(req: Request) {
|
||||
|
||||
const baseSession = session;
|
||||
const baseSourceImage = sourceImage;
|
||||
async function run() {
|
||||
const { pack, manifest, provider } = await generateAssetPack({
|
||||
session: baseSession,
|
||||
sourceImage: baseSourceImage,
|
||||
const releaseLock = startGenerationLock(`pack:${sessionId}:${imageId}:${kind}`);
|
||||
if (!releaseLock) {
|
||||
return NextResponse.json({
|
||||
ok: true,
|
||||
background: true,
|
||||
running: true,
|
||||
kind,
|
||||
onProgress: progressPack => persistPackProgress(baseSession, imageId, progressPack),
|
||||
});
|
||||
baseSession.characterSpec = pack.characterSpec;
|
||||
baseSession.packs = [
|
||||
...(baseSession.packs ?? []).filter(existing => !(existing.kind === kind && existing.sourceImageId === imageId)),
|
||||
pack,
|
||||
];
|
||||
baseSession.exports = [
|
||||
...(baseSession.exports ?? []).filter(existing => !(existing.packKind === kind && existing.source.sourceImageId === imageId)),
|
||||
manifest,
|
||||
];
|
||||
await saveSession(baseSession);
|
||||
return { pack, manifest, provider } satisfies GeneratePackResponse;
|
||||
provider: detectProvider(),
|
||||
}, { status: 202 });
|
||||
}
|
||||
const release = releaseLock;
|
||||
|
||||
async function run() {
|
||||
try {
|
||||
const { pack, manifest, provider } = await generateAssetPack({
|
||||
session: baseSession,
|
||||
sourceImage: baseSourceImage,
|
||||
kind,
|
||||
onProgress: progressPack => persistPackProgress(baseSession, imageId, progressPack),
|
||||
});
|
||||
baseSession.characterSpec = pack.characterSpec;
|
||||
baseSession.packs = [
|
||||
...(baseSession.packs ?? []).filter(existing => !(existing.kind === kind && existing.sourceImageId === imageId)),
|
||||
pack,
|
||||
];
|
||||
baseSession.exports = [
|
||||
...(baseSession.exports ?? []).filter(existing => !(existing.packKind === kind && existing.source.sourceImageId === imageId)),
|
||||
manifest,
|
||||
];
|
||||
await saveSession(baseSession);
|
||||
return { pack, manifest, provider } satisfies GeneratePackResponse;
|
||||
} finally {
|
||||
release();
|
||||
}
|
||||
}
|
||||
|
||||
if (background) {
|
||||
|
||||
17
src/lib/generationLocks.ts
Normal file
17
src/lib/generationLocks.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
const globalForGenerationLocks = globalThis as typeof globalThis & {
|
||||
__toyGenerationLocks?: Set<string>;
|
||||
};
|
||||
|
||||
function getLocks(): Set<string> {
|
||||
globalForGenerationLocks.__toyGenerationLocks ??= new Set<string>();
|
||||
return globalForGenerationLocks.__toyGenerationLocks;
|
||||
}
|
||||
|
||||
export function startGenerationLock(key: string): (() => void) | null {
|
||||
const locks = getLocks();
|
||||
if (locks.has(key)) return null;
|
||||
locks.add(key);
|
||||
return () => {
|
||||
locks.delete(key);
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user