Compare commits
17 Commits
66b761314b
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 3b72455062 | |||
| 526ad490f7 | |||
| 4993f6eeec | |||
| 79f220dbbf | |||
| c1c7cb3cc7 | |||
| b67f2e7601 | |||
| f056547160 | |||
| de3eb1d06a | |||
| f929be30ed | |||
| a2875738ce | |||
| 85adcdceef | |||
| ab99e3bf2f | |||
| e3b48e2614 | |||
| 5b316a2399 | |||
| 3f1954b38d | |||
| 96d335db8a | |||
| 307537a7ce |
@@ -0,0 +1 @@
|
|||||||
|
export * from "./aiGenerationClient.ts";
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from "./apiErrorUtils.ts";
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from "./generationRecordClient.ts";
|
||||||
@@ -43,15 +43,40 @@ export interface SaveGenerationRecordResult {
|
|||||||
// 单个 poller 内也会重复触发。这里做客户端幂等:in-flight 合流 + 成功后短期拦截,
|
// 单个 poller 内也会重复触发。这里做客户端幂等:in-flight 合流 + 成功后短期拦截,
|
||||||
// 避免后端在缺少去重时插入重复记录。
|
// 避免后端在缺少去重时插入重复记录。
|
||||||
const inFlightSaves = new Map<string, Promise<SaveGenerationRecordResult>>();
|
const inFlightSaves = new Map<string, Promise<SaveGenerationRecordResult>>();
|
||||||
const recentlySavedAt = new Map<string, number>();
|
const recentlySavedRecords = new Map<string, { savedAt: number; signature: string }>();
|
||||||
const SAVE_DEDUPE_WINDOW_MS = 60_000;
|
const SAVE_DEDUPE_WINDOW_MS = 60_000;
|
||||||
|
|
||||||
function pruneRecentlySaved(now: number): void {
|
function pruneRecentlySaved(now: number): void {
|
||||||
for (const [id, savedAt] of recentlySavedAt) {
|
for (const [id, record] of recentlySavedRecords) {
|
||||||
if (now - savedAt > SAVE_DEDUPE_WINDOW_MS) recentlySavedAt.delete(id);
|
if (now - record.savedAt > SAVE_DEDUPE_WINDOW_MS) recentlySavedRecords.delete(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function stableJsonStringify(value: unknown): string {
|
||||||
|
if (value === null || typeof value !== "object") return JSON.stringify(value);
|
||||||
|
if (Array.isArray(value)) return `[${value.map(stableJsonStringify).join(",")}]`;
|
||||||
|
const entries = Object.entries(value as Record<string, unknown>)
|
||||||
|
.filter(([, entryValue]) => entryValue !== undefined)
|
||||||
|
.sort(([a], [b]) => a.localeCompare(b));
|
||||||
|
return `{${entries.map(([key, entryValue]) => `${JSON.stringify(key)}:${stableJsonStringify(entryValue)}`).join(",")}}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildSaveSignature(input: SaveGenerationRecordInput): string {
|
||||||
|
return stableJsonStringify({
|
||||||
|
tool: input.tool,
|
||||||
|
mode: input.mode,
|
||||||
|
title: input.title,
|
||||||
|
status: input.status,
|
||||||
|
prompt: input.prompt,
|
||||||
|
taskIds: input.taskIds,
|
||||||
|
assets: input.assets,
|
||||||
|
config: input.config,
|
||||||
|
result: input.result,
|
||||||
|
metadata: input.metadata,
|
||||||
|
createdAt: input.createdAt,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function readPendingRecords(): SaveGenerationRecordInput[] {
|
function readPendingRecords(): SaveGenerationRecordInput[] {
|
||||||
try {
|
try {
|
||||||
const raw = window.localStorage.getItem(PENDING_RECORDS_KEY);
|
const raw = window.localStorage.getItem(PENDING_RECORDS_KEY);
|
||||||
@@ -78,26 +103,29 @@ export async function saveGenerationRecord(input: SaveGenerationRecordInput): Pr
|
|||||||
pruneRecentlySaved(now);
|
pruneRecentlySaved(now);
|
||||||
|
|
||||||
const recordId = input.clientRecordId;
|
const recordId = input.clientRecordId;
|
||||||
|
const signature = buildSaveSignature(input);
|
||||||
if (recordId) {
|
if (recordId) {
|
||||||
const inFlight = inFlightSaves.get(recordId);
|
const saveKey = `${recordId}:${signature}`;
|
||||||
|
const inFlight = inFlightSaves.get(saveKey);
|
||||||
if (inFlight) return inFlight;
|
if (inFlight) return inFlight;
|
||||||
const savedAt = recentlySavedAt.get(recordId);
|
const savedRecord = recentlySavedRecords.get(recordId);
|
||||||
if (savedAt !== undefined && now - savedAt <= SAVE_DEDUPE_WINDOW_MS) {
|
if (savedRecord && savedRecord.signature === signature && now - savedRecord.savedAt <= SAVE_DEDUPE_WINDOW_MS) {
|
||||||
// 终态记录只需落库一次;窗口内的重复调用直接视为已保存。
|
// 相同 clientRecordId 且 payload 完全一致时才拦截;同一记录的多轮更新需要继续保存。
|
||||||
return { source: "server", id: recordId };
|
return { source: "server", id: recordId };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const promise = saveGenerationRecordInternal(input);
|
const promise = saveGenerationRecordInternal(input);
|
||||||
if (recordId) {
|
if (recordId) {
|
||||||
inFlightSaves.set(recordId, promise);
|
const saveKey = `${recordId}:${signature}`;
|
||||||
|
inFlightSaves.set(saveKey, promise);
|
||||||
void promise
|
void promise
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
if (result.source === "server") recentlySavedAt.set(recordId, Date.now());
|
if (result.source === "server") recentlySavedRecords.set(recordId, { savedAt: Date.now(), signature });
|
||||||
})
|
})
|
||||||
.catch(() => undefined)
|
.catch(() => undefined)
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
inFlightSaves.delete(recordId);
|
inFlightSaves.delete(saveKey);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return promise;
|
return promise;
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from "./serverConnection.ts";
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from "./taskSubscription.ts";
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from "./webGenerationGateway.ts";
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from "./toastStore.ts";
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from "./ossAssets.ts";
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from "./workflows.ts";
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
export { default } from "./EcommercePage.tsx";
|
||||||
|
export * from "./EcommercePage.tsx";
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1 @@
|
|||||||
|
export * from "./ecommerceGenerationPersistence.ts";
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from "./ecommerceImageValidation.ts";
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from "./ecommerceTemplates.ts";
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from "./workbenchDownload.ts";
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from "./useGenerationTasks.ts";
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from "./useTypewriter.ts";
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from "./backgroundTaskRunner.ts";
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from "./index.ts";
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from "./useAppStore.ts";
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from "./useGenerationStore.ts";
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from "./useProjectStore.ts";
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from "./useSessionStore.ts";
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from "./useTaskStore.ts";
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -2950,6 +2950,15 @@
|
|||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.product-clone-page[data-tool="clone"] .clone-ai-result-stack > .clone-ai-node-label {
|
||||||
|
position: absolute;
|
||||||
|
top: -6px;
|
||||||
|
left: 50%;
|
||||||
|
z-index: 5;
|
||||||
|
transform: translate(-50%, -100%);
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
.product-clone-page[data-tool="clone"] .clone-ai-source-corner-action {
|
.product-clone-page[data-tool="clone"] .clone-ai-source-corner-action {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: -6px;
|
top: -6px;
|
||||||
@@ -7852,7 +7861,7 @@
|
|||||||
.product-set-preview-backdrop {
|
.product-set-preview-backdrop {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
inset: 0;
|
inset: 0;
|
||||||
z-index: 100;
|
z-index: 4000;
|
||||||
display: grid;
|
display: grid;
|
||||||
place-items: center;
|
place-items: center;
|
||||||
background: rgb(17 24 39 / 58%);
|
background: rgb(17 24 39 / 58%);
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from "./types.ts";
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from "./enterpriseVideoPolicy.ts";
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from "./happyHorseRouting.ts";
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from "./pixverseRouting.ts";
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from "./resolveVideoModel.ts";
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from "./taskLifecycle.ts";
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from "./translateTaskError.ts";
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from "./viduRouting.ts";
|
||||||
Reference in New Issue
Block a user