Codex/ecommerce history sync #29

Merged
stringadmin merged 14 commits from codex/ecommerce-history-sync into main 2026-06-18 02:20:06 +00:00
15 changed files with 1580 additions and 55 deletions
Showing only changes of commit 1adcda08b3 - Show all commits
+39
View File
@@ -0,0 +1,39 @@
# Gitea Actions CI —— 防回潮检查。
#
# 注意:本文件需 Gitea 服务端【启用 Actions】并【配置 act_runner】后才会执行。
# 未配置 runner 时本文件无副作用(不影响本地开发与 husky 钩子)。
# 启用方式:Gitea 站点管理 → 启用 Actions;在 runner 主机注册 act_runner 并打 label。
#
# 本地已有 husky 钩子兜底:pre-commit 跑 tsc+eslint(增量)pre-push 跑 css:audit。
name: CI
on:
push:
branches: [main, "main-merge-work"]
pull_request:
branches: [main]
jobs:
verify:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: "20"
cache: "npm"
- name: Install dependencies
run: npm ci
- name: Type check
run: npm run type-check
- name: Tests
run: npm run test:run
- name: Lint (error 阻断,warning 放行)
run: npm run lint
+75
View File
@@ -0,0 +1,75 @@
// ESLint flat configESLint 9)。防回潮基建:锁定去重/抽取/合规成果,约束新代码。
// 策略:warn 基线——历史问题(如 exhaustive-deps)设 warn 不阻断提交,
// 新代码的 error 类问题(unused vars 等)强制清零。CI/pre-commit 只拦 error。
import js from "@eslint/js";
import tseslint from "typescript-eslint";
import reactHooks from "eslint-plugin-react-hooks";
import reactRefresh from "eslint-plugin-react-refresh";
import unusedImports from "eslint-plugin-unused-imports";
import globals from "globals";
export default tseslint.config(
{
// 忽略构建产物、依赖、配置脚本、JS shim。
ignores: [
"dist/**",
"node_modules/**",
"coverage/**",
"**/*.config.{js,ts,mjs,cjs}",
"scripts/**",
"src/data/ossAssets.js",
],
},
js.configs.recommended,
...tseslint.configs.recommended,
{
files: ["**/*.{ts,tsx}"],
languageOptions: {
ecmaVersion: 2022,
sourceType: "module",
globals: {
...globals.browser,
...globals.es2022,
},
},
plugins: {
"react-hooks": reactHooks,
"react-refresh": reactRefresh,
"unused-imports": unusedImports,
},
rules: {
...reactHooks.configs.recommended.rules,
// 历史问题:warn 不阻断,渐进清理。
"react-hooks/exhaustive-deps": "warn",
"@typescript-eslint/no-explicit-any": "warn",
// 未使用 importerror 且可 autofix 自动删除(unused-imports 插件专长)。
"@typescript-eslint/no-unused-vars": "off",
"unused-imports/no-unused-imports": "error",
// 未使用局部变量:warn 基线(不自动删,避免误删 dead code 有副作用的赋值)。
"unused-imports/no-unused-vars": [
"warn",
{ vars: "all", varsIgnorePattern: "^_", args: "after-used", argsIgnorePattern: "^_" },
],
// 允许 warn/error(现有 console.warn/error 是有意的诊断输出)。
"no-console": ["warn", { allow: ["warn", "error"] }],
"react-refresh/only-export-components": ["warn", { allowConstantExport: true }],
},
},
{
// 测试文件:补 vitest 全局,避免 describe/it/expect 误报未定义。
files: ["**/*.{test,spec}.{ts,tsx}"],
languageOptions: {
globals: {
...globals.node,
describe: "readonly",
it: "readonly",
expect: "readonly",
vi: "readonly",
beforeEach: "readonly",
afterEach: "readonly",
beforeAll: "readonly",
afterAll: "readonly",
},
},
},
);
+5 -8
View File
@@ -1,10 +1,7 @@
// lint-staged 配置 —— 配合 husky pre-commit 使用
//
// 当前只运行 tsc 全量类型检查(tsc 不接受单文件增量检查)
// 未来可扩展 ESLint / Prettier / stylelint 等按文件的检查
//
// 函数语法返回原始命令字符串,lint-staged 不会追加文件名。
// lint-stagedpre-commit 时对暂存文件运行检查。
// - tsc --noEmit:全量类型检查(函数语法返回命令,不追加文件名)。
// - eslint --fix:仅对暂存的改动文件增量检查(新代码强制 error=0
// warning 不阻断提交)。存量历史问题不会因此被卡住
export default {
"*.{ts,tsx}": () => "tsc --noEmit",
"*.{ts,tsx}": [() => "tsc --noEmit", "eslint --fix"],
};
+1446
View File
File diff suppressed because it is too large Load Diff
+10
View File
@@ -8,6 +8,9 @@
"build": "vite build",
"preview": "vite preview --host 127.0.0.1",
"type-check": "tsc -p tsconfig.json --noEmit",
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"lint:strict": "eslint . --max-warnings=0",
"test": "vitest",
"test:run": "vitest run",
"test:coverage": "vitest run --coverage",
@@ -23,13 +26,20 @@
"zustand": "5.0.13"
},
"devDependencies": {
"@eslint/js": "^9.18.0",
"@types/react": "18.2.55",
"@types/react-dom": "18.2.18",
"@vitejs/plugin-react": "4.2.1",
"@vitest/coverage-v8": "^1.6.0",
"eslint": "^9.18.0",
"eslint-plugin-react-hooks": "^5.1.0",
"eslint-plugin-react-refresh": "^0.4.18",
"eslint-plugin-unused-imports": "^4.1.4",
"globals": "^15.14.0",
"husky": "^9.1.7",
"lint-staged": "^17.0.7",
"typescript": "5.3.3",
"typescript-eslint": "^8.20.0",
"vite": "5.1.0",
"vite-plugin-compression2": "2.5.3",
"vitest": "^1.6.0"
+1 -1
View File
@@ -1,4 +1,4 @@
import { lazy, Suspense, useCallback, useEffect, useMemo, useState } from "react";
import { lazy, Suspense, useCallback, useEffect, useState } from "react";
import {
BugOutlined,
CheckCircleFilled,
+1 -1
View File
@@ -108,7 +108,7 @@ export interface ComplianceCheck {
}
function findJsonSlice(raw: string): string {
const start = raw.search(/[\[{]/);
const start = raw.search(/[{[]/);
if (start < 0) return raw;
const stack: string[] = [];
+1 -1
View File
@@ -1,4 +1,4 @@
import { useCallback, useEffect, useRef, useState } from "react";
import { useEffect, useState } from "react";
export type ToastType = "success" | "error" | "info";
-36
View File
@@ -26,15 +26,8 @@ import {
} from "@ant-design/icons";
import {
ArrowsCounterClockwise,
Fire,
FrameCorners,
Gift,
MagicWand,
Mountains,
PaperPlaneRight,
ShoppingBag,
User,
VideoCamera,
} from "@phosphor-icons/react";
import { Fragment, useEffect, useMemo, useRef, useState, type CSSProperties, type ChangeEvent, type DragEvent, type KeyboardEvent as ReactKeyboardEvent, type MouseEvent as ReactMouseEvent, type PointerEvent as ReactPointerEvent, type ReactNode } from "react";
import { createPortal } from "react-dom";
@@ -58,43 +51,25 @@ import EcommerceCopywritingPanel from "./panels/EcommerceCopywritingPanel";
import { ecommerceOssScopes, saveUnifiedEcommerceGenerationRecord, deleteEcommerceGenerationRecord } from "./ecommerceGenerationPersistence";
import { downloadResultAsset } from "../workbench/workbenchDownload";
import {
appendEnglish,
defaultCloneOutput,
defaultEcommercePlatform,
defaultMarketLanguageOption,
defaultPlatformSpec,
defaultProductSetOutput,
domesticPlatformLabels,
domesticPlatformLanguages,
formatUploadedImageRatio,
getMarketLanguageOptions,
getPlatformDefaultLanguage,
getPlatformDefaultRatio,
getPlatformLanguageOptions,
getPlatformRatioGroup,
getPlatformRatioOptions,
getPlatformSpec,
getUniqueRatioOptions,
isDomesticPlatform,
languageAliases,
legacyPlatformAliases,
marketLanguageOptions,
marketOptions,
normalizeLanguage,
normalizeLanguageForPlatform,
normalizeMarket,
normalizePlatform,
normalizeRatioForPlatform,
platformOptions,
platformSpecOptions,
uniqueLanguages,
} from "./utils/platformRules";
import type {
CloneOutputKey,
PlatformRatioModeKey,
ProductSetOutputKey,
} from "./utils/platformRules";
import type { EcommercePlatformSpec, PlatformRatioGroup } from "./utils/platformRules";
import {
buildHistoryTurnFromRecord,
cloneLatestSettingStorageKey,
@@ -102,15 +77,9 @@ import {
defaultCloneSetCounts,
ecommerceHistoryStorageKey,
getTurnResults,
isCloneImageItem,
isCloneResult,
isCloneSavedSetting,
isEcommerceHistoryRecord,
normalizeEcommerceHistoryRecord,
normalizeEcommerceHistoryTurn,
readCloneLatestSetting,
readEcommerceHistoryRecords,
removeFilePayloadFromImages,
writeCloneLatestSetting,
writeEcommerceHistoryRecords,
} from "./utils/clonePersistence";
@@ -275,18 +244,13 @@ import {
rgbToHex,
} from "./utils/colorUtils";
import {
formatAspectRatio,
formatRatioDisplayValue,
getQuickSetRatioValue,
getRatioDisplayParts,
greatestCommonDivisor,
normalizeRatioForApi,
normalizeRatioToken,
parseRatioToAspectCss,
quickSetRatioOptions,
supportedImageApiRatios,
toSupportedImageApiRatio,
type SupportedImageApiRatio,
} from "./utils/ratioUtils";
@@ -1,4 +1,4 @@
import { Fragment, useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import "../../styles/pages/ecommerce-video.css";
import {
CloseOutlined,
@@ -33,7 +33,6 @@ import { useGenerationTasks } from "../../hooks/useGenerationTasks";
import {
saveEcommerceVideoState,
loadEcommerceVideoState,
clearEcommerceVideoState,
} from "./ecommerceVideoKeepalive";
import { saveUnifiedEcommerceGenerationRecord } from "./ecommerceGenerationPersistence";
import { useVideoSceneRunner } from "./useVideoSceneRunner";
@@ -4,7 +4,6 @@ import {
CopyOutlined,
EditOutlined,
FileTextOutlined,
FireOutlined,
GlobalOutlined,
MessageOutlined,
SmileOutlined,
@@ -13,8 +13,6 @@ import {
} from "./ecommerceVideoService";
import { waitForTask } from "../../api/taskSubscription";
import { ServerRequestError } from "../../api/serverConnection";
import { ENTERPRISE_DEFAULT_VIDEO_MODEL } from "../../utils/enterpriseVideoPolicy";
import { resolveVideoRequestModel } from "../../utils/resolveVideoModel";
import {
saveEcommerceVideoState,
} from "./ecommerceVideoKeepalive";
-1
View File
@@ -1,5 +1,4 @@
import { create } from "zustand";
import type { WebGenerationPreviewTask } from "../types";
export type QueueItemStatus = "pending" | "running" | "completed" | "failed" | "cancelled";
-1
View File
@@ -1,4 +1,3 @@
import type { ReactNode } from "react";
export type WebViewKey =
| "home"
+1 -1
View File
@@ -10,7 +10,7 @@ interface ErrorReport {
sessionId?: string;
}
let reportQueue: ErrorReport[] = [];
const reportQueue: ErrorReport[] = [];
let flushTimer: ReturnType<typeof setTimeout> | null = null;
function getSessionId(): string | undefined {