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