auto-save 2026-05-19 11:53 (~3)

This commit is contained in:
2026-05-19 11:53:28 +08:00
parent 8e27d3b722
commit 2c2d11bcf8
4 changed files with 137 additions and 48 deletions

View File

@@ -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
}
]
}

View File

@@ -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) {

View File

@@ -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) {

View 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);
};
}