auto-save 2026-05-19 11:29 (~6)
This commit is contained in:
@@ -523,6 +523,25 @@
|
||||
"type": "session-heartbeat",
|
||||
"message": "Codex 会话活跃 · 最近命令:codex · 分支 master · 1 项未提交变更 · 最近提交:fix: update OpenAI image response handling",
|
||||
"files_changed": 1
|
||||
},
|
||||
{
|
||||
"ts": "2026-05-19T11:13:07+08:00",
|
||||
"type": "commit",
|
||||
"message": "auto-save 2026-05-19 11:13 (~2)",
|
||||
"hash": "74148d0",
|
||||
"files_changed": 2
|
||||
},
|
||||
{
|
||||
"ts": "2026-05-19T03:20:00Z",
|
||||
"type": "session-heartbeat",
|
||||
"message": "Codex 会话活跃 · 最近命令:codex · 分支 master · 1 项未提交变更 · 最近提交:auto-save 2026-05-19 11:13 (~2)",
|
||||
"files_changed": 1
|
||||
},
|
||||
{
|
||||
"ts": "2026-05-19T03:30:00Z",
|
||||
"type": "session-heartbeat",
|
||||
"message": "Codex 会话活跃 · 最近命令:codex · 分支 master · 6 项未提交变更 · 最近提交:auto-save 2026-05-19 11:13 (~2)",
|
||||
"files_changed": 6
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -3,13 +3,22 @@ import { generateAssetPack } from '@/lib/packGenerator';
|
||||
import { detectProvider } from '@/lib/providers';
|
||||
import { loadSession, saveSession } from '@/lib/storage';
|
||||
import { PACK_ORDER } from '@/lib/templates';
|
||||
import type { GenerateAllPacksRequest, GenerateAllPacksResponse } from '@/lib/types';
|
||||
import type { AssetPack, GenerateAllPacksRequest, GenerateAllPacksResponse, GenSession } from '@/lib/types';
|
||||
|
||||
export const runtime = 'nodejs';
|
||||
export const dynamic = 'force-dynamic';
|
||||
|
||||
async function persistPackProgress(session: GenSession, imageId: string, pack: AssetPack) {
|
||||
session.characterSpec = pack.characterSpec;
|
||||
session.packs = [
|
||||
...(session.packs ?? []).filter(existing => !(existing.kind === pack.kind && existing.sourceImageId === imageId)),
|
||||
{ ...pack, assets: [...pack.assets] },
|
||||
];
|
||||
await saveSession(session);
|
||||
}
|
||||
|
||||
export async function POST(req: Request) {
|
||||
const { sessionId, imageId } = (await req.json()) as GenerateAllPacksRequest;
|
||||
const { sessionId, imageId, background = false } = (await req.json()) as GenerateAllPacksRequest;
|
||||
if (!sessionId || !imageId) {
|
||||
return NextResponse.json({ error: 'sessionId and imageId required' }, { status: 400 });
|
||||
}
|
||||
@@ -23,13 +32,20 @@ export async function POST(req: Request) {
|
||||
return NextResponse.json({ error: 'image must be selected before generating packs' }, { status: 400 });
|
||||
}
|
||||
|
||||
try {
|
||||
const baseSession = session;
|
||||
const baseSourceImage = sourceImage;
|
||||
async function run() {
|
||||
const packs = [];
|
||||
const manifests = [];
|
||||
let workingSession = session;
|
||||
let workingSession: GenSession = baseSession;
|
||||
|
||||
for (const kind of PACK_ORDER) {
|
||||
const generated = await generateAssetPack({ session: workingSession, sourceImage, kind });
|
||||
const generated = await generateAssetPack({
|
||||
session: workingSession,
|
||||
sourceImage: baseSourceImage,
|
||||
kind,
|
||||
onProgress: progressPack => persistPackProgress(workingSession, imageId, progressPack),
|
||||
});
|
||||
packs.push(generated.pack);
|
||||
manifests.push(generated.manifest);
|
||||
workingSession = {
|
||||
@@ -48,11 +64,24 @@ export async function POST(req: Request) {
|
||||
|
||||
await saveSession(workingSession);
|
||||
|
||||
return NextResponse.json({
|
||||
return {
|
||||
packs,
|
||||
manifests,
|
||||
provider: detectProvider(),
|
||||
} satisfies GenerateAllPacksResponse);
|
||||
} satisfies GenerateAllPacksResponse;
|
||||
}
|
||||
|
||||
if (background) {
|
||||
void run().catch(error => console.error('[packs:all] background generation failed', error));
|
||||
return NextResponse.json({
|
||||
ok: true,
|
||||
background: true,
|
||||
provider: detectProvider(),
|
||||
}, { status: 202 });
|
||||
}
|
||||
|
||||
try {
|
||||
return NextResponse.json(await run());
|
||||
} catch (error) {
|
||||
return NextResponse.json({ error: String(error) }, { status: 500 });
|
||||
}
|
||||
|
||||
@@ -1,15 +1,25 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { generateAssetPack } from '@/lib/packGenerator';
|
||||
import { detectProvider } from '@/lib/providers';
|
||||
import { loadSession, saveSession } from '@/lib/storage';
|
||||
import type { GeneratePackRequest, GeneratePackResponse, PackKind } from '@/lib/types';
|
||||
import type { AssetPack, GeneratePackRequest, GeneratePackResponse, GenSession, PackKind } from '@/lib/types';
|
||||
|
||||
export const runtime = 'nodejs';
|
||||
export const dynamic = 'force-dynamic';
|
||||
|
||||
const PACK_KINDS: PackKind[] = ['patent', 'accessories', 'production', 'marketing'];
|
||||
|
||||
async function persistPackProgress(session: GenSession, imageId: string, pack: AssetPack) {
|
||||
session.characterSpec = pack.characterSpec;
|
||||
session.packs = [
|
||||
...(session.packs ?? []).filter(existing => !(existing.kind === pack.kind && existing.sourceImageId === imageId)),
|
||||
{ ...pack, assets: [...pack.assets] },
|
||||
];
|
||||
await saveSession(session);
|
||||
}
|
||||
|
||||
export async function POST(req: Request) {
|
||||
const { sessionId, imageId, kind } = (await req.json()) as GeneratePackRequest;
|
||||
const { sessionId, imageId, kind, background = false } = (await req.json()) as GeneratePackRequest;
|
||||
if (!sessionId || !imageId || !PACK_KINDS.includes(kind)) {
|
||||
return NextResponse.json({ error: 'sessionId, imageId and valid kind required' }, { status: 400 });
|
||||
}
|
||||
@@ -23,8 +33,13 @@ export async function POST(req: Request) {
|
||||
return NextResponse.json({ error: 'image must be selected before generating packs' }, { status: 400 });
|
||||
}
|
||||
|
||||
try {
|
||||
const { pack, manifest, provider } = await generateAssetPack({ session, sourceImage, kind });
|
||||
async function run() {
|
||||
const { pack, manifest, provider } = await generateAssetPack({
|
||||
session,
|
||||
sourceImage,
|
||||
kind,
|
||||
onProgress: progressPack => persistPackProgress(session, imageId, progressPack),
|
||||
});
|
||||
session.characterSpec = pack.characterSpec;
|
||||
session.packs = [
|
||||
...(session.packs ?? []).filter(existing => !(existing.kind === kind && existing.sourceImageId === imageId)),
|
||||
@@ -35,8 +50,23 @@ export async function POST(req: Request) {
|
||||
manifest,
|
||||
];
|
||||
await saveSession(session);
|
||||
return { pack, manifest, provider } satisfies GeneratePackResponse;
|
||||
}
|
||||
|
||||
return NextResponse.json({ pack, manifest, provider } satisfies GeneratePackResponse);
|
||||
if (background) {
|
||||
void run().catch(error => console.error(`[pack:${kind}] background generation failed`, error));
|
||||
return NextResponse.json({
|
||||
ok: true,
|
||||
background: true,
|
||||
kind,
|
||||
provider: detectProvider(),
|
||||
}, { status: 202 });
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await run();
|
||||
|
||||
return NextResponse.json(response);
|
||||
} catch (error) {
|
||||
return NextResponse.json({ error: String(error) }, { status: 500 });
|
||||
}
|
||||
|
||||
@@ -136,7 +136,7 @@ export default function Home() {
|
||||
const r = await fetch('/api/packs/generate', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ sessionId: current.id, imageId: image.id, kind }),
|
||||
body: JSON.stringify({ sessionId: current.id, imageId: image.id, kind, background: true }),
|
||||
});
|
||||
if (!r.ok) {
|
||||
alert('素材包生成失败:' + (await r.text()));
|
||||
@@ -147,6 +147,7 @@ export default function Home() {
|
||||
const all = await refreshSessions();
|
||||
const updated = all.find(x => x.id === current.id) ?? null;
|
||||
setCurrent(updated);
|
||||
scheduleSessionRefresh(current.id);
|
||||
} finally {
|
||||
setLoadingKind(null);
|
||||
}
|
||||
@@ -159,6 +160,15 @@ export default function Home() {
|
||||
return updated;
|
||||
}
|
||||
|
||||
function scheduleSessionRefresh(sessionId: string, remaining = 90) {
|
||||
if (remaining <= 0) return;
|
||||
window.setTimeout(async () => {
|
||||
const updated = await reloadCurrent(sessionId);
|
||||
const hasDraftPack = updated?.packs?.some(pack => pack.status === 'draft') ?? false;
|
||||
if (hasDraftPack || remaining > 84) scheduleSessionRefresh(sessionId, remaining - 1);
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
async function handleLockCharacter(image: GenImage) {
|
||||
if (!current || characterLoading) return;
|
||||
setCharacterLoading(true);
|
||||
@@ -187,7 +197,7 @@ export default function Home() {
|
||||
const r = await fetch('/api/packs/generate-all', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ sessionId: current.id, imageId: image.id }),
|
||||
body: JSON.stringify({ sessionId: current.id, imageId: image.id, background: true }),
|
||||
});
|
||||
if (!r.ok) {
|
||||
alert('完整三包生成失败:' + (await r.text()));
|
||||
@@ -196,6 +206,7 @@ export default function Home() {
|
||||
const d: GenerateAllPacksResponse = await r.json();
|
||||
setProvider(d.provider);
|
||||
await reloadCurrent(current.id);
|
||||
scheduleSessionRefresh(current.id);
|
||||
} finally {
|
||||
setAllLoading(false);
|
||||
}
|
||||
|
||||
@@ -274,6 +274,7 @@ export async function generateAssetPack(opts: {
|
||||
session: GenSession;
|
||||
sourceImage: GenImage;
|
||||
kind: PackKind;
|
||||
onProgress?: (pack: AssetPack) => Promise<void>;
|
||||
}): Promise<{ pack: AssetPack; manifest: ExportManifest; provider: 'mock' | 'gpt' }> {
|
||||
const templates = sortTemplatesByAnchor(getPackTemplates(opts.kind));
|
||||
const initialCharacterSpec = opts.session.characterSpec?.sourceImageId === opts.sourceImage.id
|
||||
@@ -291,6 +292,20 @@ export async function generateAssetPack(opts: {
|
||||
const provider = detectProvider();
|
||||
|
||||
const assets: ToyAsset[] = [];
|
||||
const pack: AssetPack = {
|
||||
id: packId,
|
||||
kind: opts.kind,
|
||||
sessionId: opts.session.id,
|
||||
sourceImageId: opts.sourceImage.id,
|
||||
sourceImageUrl: opts.sourceImage.url,
|
||||
characterSpec,
|
||||
assets,
|
||||
manifestId: `manifest_${packId}`,
|
||||
createdAt,
|
||||
version,
|
||||
status: 'draft',
|
||||
};
|
||||
|
||||
for (const template of templates) {
|
||||
const assetId = `${opts.kind}_${template.filenamePart}_${randomBytes(3).toString('hex')}`;
|
||||
const anchorAsset = template.anchorTemplateId
|
||||
@@ -332,6 +347,7 @@ export async function generateAssetPack(opts: {
|
||||
anchorTemplateId: template.anchorTemplateId,
|
||||
},
|
||||
});
|
||||
await opts.onProgress?.(pack);
|
||||
continue;
|
||||
}
|
||||
const generated = await generateAssetImage({
|
||||
@@ -366,25 +382,13 @@ export async function generateAssetPack(opts: {
|
||||
raw: generated.raw,
|
||||
},
|
||||
});
|
||||
await opts.onProgress?.(pack);
|
||||
}
|
||||
|
||||
const manifestId = `manifest_${packId}`;
|
||||
const pack: AssetPack = {
|
||||
id: packId,
|
||||
kind: opts.kind,
|
||||
sessionId: opts.session.id,
|
||||
sourceImageId: opts.sourceImage.id,
|
||||
sourceImageUrl: opts.sourceImage.url,
|
||||
characterSpec,
|
||||
assets,
|
||||
manifestId,
|
||||
createdAt,
|
||||
version,
|
||||
status: 'complete',
|
||||
};
|
||||
pack.status = 'complete';
|
||||
|
||||
const manifest: ExportManifest = {
|
||||
id: manifestId,
|
||||
id: pack.manifestId,
|
||||
sessionId: opts.session.id,
|
||||
packId,
|
||||
packKind: opts.kind,
|
||||
|
||||
@@ -190,6 +190,7 @@ export type GeneratePackRequest = {
|
||||
sessionId: string;
|
||||
imageId: string;
|
||||
kind: PackKind;
|
||||
background?: boolean;
|
||||
};
|
||||
|
||||
export type GeneratePackResponse = {
|
||||
@@ -201,6 +202,7 @@ export type GeneratePackResponse = {
|
||||
export type GenerateAllPacksRequest = {
|
||||
sessionId: string;
|
||||
imageId: string;
|
||||
background?: boolean;
|
||||
};
|
||||
|
||||
export type GenerateAllPacksResponse = {
|
||||
|
||||
Reference in New Issue
Block a user