Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6cc81e3804 | |||
| c1c4086383 | |||
| 3493f169c0 | |||
| b81128d7ca |
@@ -0,0 +1,72 @@
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import process from "node:process";
|
||||
|
||||
const repoRoot = process.cwd();
|
||||
const failures = [];
|
||||
|
||||
function read(relativePath) {
|
||||
return fs.readFileSync(path.join(repoRoot, relativePath), "utf8");
|
||||
}
|
||||
|
||||
function assertMatch(label, content, pattern) {
|
||||
if (!pattern.test(content)) {
|
||||
failures.push(label);
|
||||
}
|
||||
}
|
||||
|
||||
function assertNoMatch(label, content, pattern) {
|
||||
if (pattern.test(content)) {
|
||||
failures.push(label);
|
||||
}
|
||||
}
|
||||
|
||||
const serverConnection = read("src/api/serverConnection.ts");
|
||||
const generationClient = read("src/api/aiGenerationClient.ts");
|
||||
const ecommerceVideoService = read("src/features/ecommerce/ecommerceVideoService.ts");
|
||||
const workbenchPersistence = read("src/features/workbench/workbenchResultPersistence.ts");
|
||||
|
||||
assertMatch(
|
||||
"serverConnection must build same-origin /api URLs",
|
||||
serverConnection,
|
||||
/return\s+`\/api\/\$\{cleanPath\}`;/,
|
||||
);
|
||||
assertNoMatch(
|
||||
"frontend generation flow must not use fixed VITE environment config",
|
||||
`${serverConnection}\n${generationClient}`,
|
||||
/\b(?:import\.meta\.env|VITE_[A-Z0-9_]+)\b/,
|
||||
);
|
||||
assertNoMatch(
|
||||
"frontend generation flow must not call provider hosts directly",
|
||||
generationClient,
|
||||
/dashscope\.aliyuncs\.com|\/dashscope-api\b|Bearer\s+sk-/i,
|
||||
);
|
||||
assertMatch("image generation must go through the app API", generationClient, /buildApiUrl\("ai\/image"\)/);
|
||||
assertMatch("video generation must go through the app API", generationClient, /buildApiUrl\("ai\/video"\)/);
|
||||
assertMatch("binary uploads must go through the app OSS API", generationClient, /buildApiUrl\("oss\/upload-binary"\)/);
|
||||
assertMatch("URL uploads must go through the app OSS API", generationClient, /buildApiUrl\("oss\/upload-by-url"\)/);
|
||||
assertMatch(
|
||||
"ecommerce video history must durable-copy media before saving",
|
||||
ecommerceVideoService,
|
||||
/buildDurableVideoHistoryPayload\(payload\)/,
|
||||
);
|
||||
assertMatch(
|
||||
"ecommerce video history must filter temporary provider URLs on read",
|
||||
ecommerceVideoService,
|
||||
/items:\s*history\.items\.map\(removeTemporaryHistoryUrls\)/,
|
||||
);
|
||||
assertMatch(
|
||||
"workbench results must persist generated media through OSS",
|
||||
workbenchPersistence,
|
||||
/uploadAssetByUrl\(/,
|
||||
);
|
||||
|
||||
if (failures.length) {
|
||||
console.error("Mocked generation smoke check failed:");
|
||||
for (const failure of failures) {
|
||||
console.error(`- ${failure}`);
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log("Mocked generation smoke check passed.");
|
||||
@@ -159,7 +159,7 @@ function normalizeTaskStatus(status: AiTaskStatus["status"]): WebGenerationPrevi
|
||||
function taskTitle(task: AiTaskStatus): string {
|
||||
const prompt = typeof task.params?.prompt === "string" ? task.params.prompt.trim() : "";
|
||||
if (prompt) return prompt.length > 20 ? `${prompt.slice(0, 20)}...` : prompt;
|
||||
return task.type === "video" ? "视频生成任务" : "图像生成任务";
|
||||
return task.type === "video" ? "\u89c6\u9891\u751f\u6210\u4efb\u52a1" : "\u56fe\u50cf\u751f\u6210\u4efb\u52a1";
|
||||
}
|
||||
|
||||
function toPreviewTask(task: AiTaskStatus): WebGenerationPreviewTask {
|
||||
@@ -512,7 +512,7 @@ export const aiGenerationClient = {
|
||||
}
|
||||
|
||||
const reader = res.body?.getReader();
|
||||
if (!reader) throw new Error("无法读取响应流");
|
||||
if (!reader) throw new Error("\u65e0\u6cd5\u8bfb\u53d6\u54cd\u5e94\u6d41");
|
||||
|
||||
const decoder = new TextDecoder();
|
||||
let buffer = "";
|
||||
|
||||
@@ -913,7 +913,7 @@ export const keyServerClient = {
|
||||
async getProjectContent(projectId: string): Promise<WebCanvasWorkflow> {
|
||||
const stored = readStoredSession();
|
||||
if (!stored) {
|
||||
throw new Error("闇€瑕佸厛鐧诲綍");
|
||||
throw new Error("需要先登录");
|
||||
}
|
||||
|
||||
const safeProjectId = encodeURIComponent(projectId.trim());
|
||||
@@ -1000,7 +1000,7 @@ export const keyServerClient = {
|
||||
async deleteProject(projectId: string, options?: DeleteProjectOptions): Promise<void> {
|
||||
const stored = readStoredSession();
|
||||
if (!stored) {
|
||||
throw new Error("闇€瑕佸厛鐧诲綍");
|
||||
throw new Error("需要先登录");
|
||||
}
|
||||
|
||||
const path = options?.cleanupUserData ? `projects/${encodeURIComponent(projectId)}?cleanupUserData=1` : `projects/${encodeURIComponent(projectId)}`;
|
||||
|
||||
@@ -71,7 +71,7 @@ export const modelCapabilitiesClient = {
|
||||
|
||||
let payload: unknown;
|
||||
try {
|
||||
payload = await serverRequest<unknown>(`config/profile?name=${encodeURIComponent(name)}`);
|
||||
payload = await serverRequest<unknown>(`public/config/profile?name=${encodeURIComponent(name)}`);
|
||||
} catch (error) {
|
||||
if (isOptionalApiRouteMissing(error)) {
|
||||
modelCapabilitiesRouteMissing = true;
|
||||
|
||||
@@ -54,15 +54,13 @@ export function waitForTask(
|
||||
if (event.status === "completed") {
|
||||
settle(() => resolve(event.resultUrl || null));
|
||||
} else if (event.status === "failed" || event.status === "cancelled") {
|
||||
settle(() => reject(new Error(event.error || "任务失败")));
|
||||
settle(() => reject(new Error(event.error || "任务失败,请稍后重试")));
|
||||
}
|
||||
};
|
||||
|
||||
// Try SSE first
|
||||
cleanup = aiGenerationClient.subscribeTaskStatus(taskId, handleUpdate);
|
||||
sseConnected = true;
|
||||
|
||||
// Fallback: if SSE doesn't deliver any event within 5s, switch to polling
|
||||
fallbackTimerId = setTimeout(() => {
|
||||
if (settled || !sseConnected) return;
|
||||
if (cleanup) cleanup();
|
||||
@@ -72,7 +70,10 @@ export function waitForTask(
|
||||
function startPolling() {
|
||||
const poll = async () => {
|
||||
while (!settled) {
|
||||
if (abortRef?.current) { settle(() => resolve(null)); return; }
|
||||
if (abortRef?.current) {
|
||||
settle(() => resolve(null));
|
||||
return;
|
||||
}
|
||||
await new Promise((r) => setTimeout(r, POLL_INTERVAL));
|
||||
if (settled || abortRef?.current) return;
|
||||
try {
|
||||
|
||||
@@ -103,7 +103,7 @@ export const webGenerationGateway = {
|
||||
prompt,
|
||||
createdAt,
|
||||
source: "server",
|
||||
errorMessage: err instanceof Error ? err.message : "请求失败",
|
||||
errorMessage: err instanceof Error ? err.message : "请求失败,请稍后重试",
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -19,6 +19,102 @@ import type {
|
||||
PlanStep,
|
||||
} from "./ecommerceVideoTypes";
|
||||
|
||||
type UploadAssetByUrl = typeof aiGenerationClient.uploadAssetByUrl;
|
||||
|
||||
interface DurableMediaUrl {
|
||||
url: string | null;
|
||||
originalUrl?: string | null;
|
||||
ossKey?: string | null;
|
||||
}
|
||||
|
||||
const TEMP_MEDIA_HOST_RE = /^file\d*\.aitohumanize\.com$/i;
|
||||
const OSS_MEDIA_HOST_RE = /\.oss-[^.]+\.aliyuncs\.com$/i;
|
||||
|
||||
function isTemporaryProviderUrl(url: string): boolean {
|
||||
try {
|
||||
return TEMP_MEDIA_HOST_RE.test(new URL(url).hostname);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function isDurableOssUrl(url: string): boolean {
|
||||
try {
|
||||
const parsed = new URL(url);
|
||||
return parsed.protocol === "https:" && OSS_MEDIA_HOST_RE.test(parsed.hostname);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function getMediaExtension(url: string, mimeType: string): string {
|
||||
const normalizedMime = mimeType.split(";")[0]?.trim().toLowerCase();
|
||||
if (normalizedMime === "image/jpeg") return "jpg";
|
||||
if (normalizedMime === "image/png") return "png";
|
||||
if (normalizedMime === "image/webp") return "webp";
|
||||
if (normalizedMime === "image/gif") return "gif";
|
||||
if (normalizedMime === "video/mp4") return "mp4";
|
||||
if (normalizedMime === "video/webm") return "webm";
|
||||
if (normalizedMime === "video/quicktime") return "mov";
|
||||
|
||||
try {
|
||||
const matched = new URL(url).pathname.match(/\.([a-z0-9]{2,5})$/i);
|
||||
if (matched?.[1]) return matched[1].toLowerCase();
|
||||
} catch {
|
||||
// Keep mime fallback below.
|
||||
}
|
||||
|
||||
return mimeType.startsWith("video/") ? "mp4" : "png";
|
||||
}
|
||||
|
||||
function buildDurableMediaName(prefix: string, url: string, mimeType: string): string {
|
||||
const normalized = prefix
|
||||
.trim()
|
||||
.replace(/[\\/:*?"<>|]+/g, "-")
|
||||
.replace(/\s+/g, " ")
|
||||
.slice(0, 80)
|
||||
.trim();
|
||||
return `${normalized || "ecommerce-video-media"}.${getMediaExtension(url, mimeType)}`;
|
||||
}
|
||||
|
||||
export async function resolveDurableMediaUrl(
|
||||
url: string | null | undefined,
|
||||
options: {
|
||||
mediaType: "image" | "video";
|
||||
namePrefix: string;
|
||||
scope?: string;
|
||||
uploadAssetByUrl?: UploadAssetByUrl;
|
||||
},
|
||||
): Promise<DurableMediaUrl> {
|
||||
const sourceUrl = String(url || "").trim();
|
||||
if (!sourceUrl) return { url: null };
|
||||
if (isDurableOssUrl(sourceUrl)) return { url: sourceUrl };
|
||||
|
||||
const mimeType = options.mediaType === "video" ? "video/mp4" : "image/png";
|
||||
const uploadAssetByUrl = options.uploadAssetByUrl || aiGenerationClient.uploadAssetByUrl.bind(aiGenerationClient);
|
||||
|
||||
try {
|
||||
const uploaded = await uploadAssetByUrl({
|
||||
sourceUrl,
|
||||
name: buildDurableMediaName(options.namePrefix, sourceUrl, mimeType),
|
||||
mimeType,
|
||||
scope: options.scope || "ecommerce-video-history",
|
||||
});
|
||||
return {
|
||||
url: uploaded.url || null,
|
||||
originalUrl: sourceUrl,
|
||||
ossKey: uploaded.ossKey || null,
|
||||
};
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error || "");
|
||||
console.warn("[ecommerce-video] history media persistence failed:", message);
|
||||
if (isTemporaryProviderUrl(sourceUrl)) {
|
||||
return { url: null, originalUrl: sourceUrl };
|
||||
}
|
||||
return { url: sourceUrl };
|
||||
}
|
||||
}
|
||||
|
||||
export interface PlanCallbacks {
|
||||
onStepStart: (step: PlanStep) => void;
|
||||
onStepDone: (step: PlanStep) => void;
|
||||
@@ -268,6 +364,15 @@ export interface VideoHistoryScene {
|
||||
videoUrl?: string | null;
|
||||
}
|
||||
|
||||
interface SaveVideoHistoryPayload {
|
||||
title: string;
|
||||
config: Record<string, unknown>;
|
||||
plan: Record<string, unknown>;
|
||||
scenes: VideoHistoryScene[];
|
||||
sourceImageUrls: string[];
|
||||
uploadAssetByUrl?: UploadAssetByUrl;
|
||||
}
|
||||
|
||||
export interface VideoHistoryItem {
|
||||
id: number;
|
||||
title: string;
|
||||
@@ -293,22 +398,74 @@ function getAuthHeaders(): Record<string, string> {
|
||||
return token ? { Authorization: `Bearer ${token}` } : {};
|
||||
}
|
||||
|
||||
export async function saveVideoHistory(payload: {
|
||||
title: string;
|
||||
config: Record<string, unknown>;
|
||||
plan: Record<string, unknown>;
|
||||
scenes: VideoHistoryScene[];
|
||||
sourceImageUrls: string[];
|
||||
}): Promise<{ id: number; createdAt: string }> {
|
||||
export async function buildDurableVideoHistoryPayload(payload: SaveVideoHistoryPayload): Promise<SaveVideoHistoryPayload> {
|
||||
const uploadAssetByUrl = payload.uploadAssetByUrl;
|
||||
const scenes = await Promise.all(
|
||||
payload.scenes.map(async (scene) => {
|
||||
const [image, video] = await Promise.all([
|
||||
resolveDurableMediaUrl(scene.imageUrl, {
|
||||
mediaType: "image",
|
||||
namePrefix: `ecommerce-scene-${scene.sceneId}-image`,
|
||||
uploadAssetByUrl,
|
||||
}),
|
||||
resolveDurableMediaUrl(scene.videoUrl, {
|
||||
mediaType: "video",
|
||||
namePrefix: `ecommerce-scene-${scene.sceneId}-video`,
|
||||
uploadAssetByUrl,
|
||||
}),
|
||||
]);
|
||||
return {
|
||||
...scene,
|
||||
imageUrl: image.url,
|
||||
videoUrl: video.url,
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
const sourceImageUrls = (
|
||||
await Promise.all(
|
||||
payload.sourceImageUrls.map((url, index) =>
|
||||
resolveDurableMediaUrl(url, {
|
||||
mediaType: "image",
|
||||
namePrefix: `ecommerce-source-${index + 1}`,
|
||||
uploadAssetByUrl,
|
||||
}),
|
||||
),
|
||||
)
|
||||
)
|
||||
.map((item) => item.url)
|
||||
.filter((url): url is string => Boolean(url));
|
||||
|
||||
return {
|
||||
...payload,
|
||||
scenes,
|
||||
sourceImageUrls,
|
||||
};
|
||||
}
|
||||
|
||||
export async function saveVideoHistory(payload: SaveVideoHistoryPayload): Promise<{ id: number; createdAt: string }> {
|
||||
const { uploadAssetByUrl: _uploadAssetByUrl, ...historyPayload } = await buildDurableVideoHistoryPayload(payload);
|
||||
const res = await fetch(API_BASE, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json", ...getAuthHeaders() },
|
||||
body: JSON.stringify(payload),
|
||||
body: JSON.stringify(historyPayload),
|
||||
});
|
||||
if (!res.ok) throw new Error("保存历史记录失败");
|
||||
return res.json();
|
||||
}
|
||||
|
||||
function removeTemporaryHistoryUrls(item: VideoHistoryItem): VideoHistoryItem {
|
||||
return {
|
||||
...item,
|
||||
scenes: item.scenes.map((scene) => ({
|
||||
...scene,
|
||||
imageUrl: scene.imageUrl && !isTemporaryProviderUrl(scene.imageUrl) ? scene.imageUrl : null,
|
||||
videoUrl: scene.videoUrl && !isTemporaryProviderUrl(scene.videoUrl) ? scene.videoUrl : null,
|
||||
})),
|
||||
sourceImageUrls: item.sourceImageUrls.filter((url) => !isTemporaryProviderUrl(url)),
|
||||
};
|
||||
}
|
||||
|
||||
export async function fetchVideoHistory(
|
||||
limit = 20,
|
||||
offset = 0,
|
||||
@@ -318,7 +475,11 @@ export async function fetchVideoHistory(
|
||||
{ headers: getAuthHeaders() },
|
||||
);
|
||||
if (!res.ok) throw new Error("获取历史记录失败");
|
||||
return res.json();
|
||||
const history = (await res.json()) as VideoHistoryListResponse;
|
||||
return {
|
||||
...history,
|
||||
items: history.items.map(removeTemporaryHistoryUrls),
|
||||
};
|
||||
}
|
||||
|
||||
export async function deleteVideoHistory(id: number): Promise<void> {
|
||||
|
||||
@@ -857,25 +857,25 @@ function ProfilePage({
|
||||
任务 {tasks.length}
|
||||
</button>
|
||||
</div>
|
||||
<div className="profile-page__account-summary">
|
||||
<div className="profile-page__upload-card profile-page__upload-card--meta">
|
||||
{accountPanel === "credits" ? (
|
||||
<>
|
||||
<span className="profile-page__account-summary-main">
|
||||
<span className="profile-page__meta-item">
|
||||
<small>当前账号</small>
|
||||
<strong>{displayName}</strong>
|
||||
</span>
|
||||
<span className="profile-page__account-summary-metric">
|
||||
<span className="profile-page__meta-item">
|
||||
<small>积分剩余</small>
|
||||
<strong>{(usage.balanceCents / 100).toFixed(2)}</strong>
|
||||
</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<span className="profile-page__account-summary-main">
|
||||
<small>任务概览</small>
|
||||
<strong>{tasks.length} 个任务</strong>
|
||||
<span className="profile-page__meta-item">
|
||||
<small>任务总数</small>
|
||||
<strong>{tasks.length}</strong>
|
||||
</span>
|
||||
<span className="profile-page__account-summary-metric">
|
||||
<span className="profile-page__meta-item">
|
||||
<small>已完成</small>
|
||||
<strong>{completedTasks.length}</strong>
|
||||
</span>
|
||||
|
||||
@@ -3,24 +3,6 @@ import type { ReactNode } from "react";
|
||||
import type { WorkbenchOption, WorkbenchFieldGroup } from "./workbenchConstants";
|
||||
import { getRatioOptionClassName, getSettingsGridColumnsClassName } from "./workbenchReferenceUtils";
|
||||
|
||||
const VIDEO_MODEL_ICON_URLS = {
|
||||
happyHorse: "https://stringtest.oss-cn-hangzhou.aliyuncs.com/static/model-icons/HappyHorse.svg",
|
||||
pixverse: "https://stringtest.oss-cn-hangzhou.aliyuncs.com/static/model-icons/Pixverse.svg",
|
||||
vidu: "https://stringtest.oss-cn-hangzhou.aliyuncs.com/static/model-icons/viduQ3.svg",
|
||||
wanxiang: "https://stringtest.oss-cn-hangzhou.aliyuncs.com/static/model-icons/wan.svg",
|
||||
kling: "https://stringtest.oss-cn-hangzhou.aliyuncs.com/static/model-icons/kling.svg",
|
||||
} as const;
|
||||
|
||||
function getVideoModelIconUrl(option: WorkbenchOption): string | null {
|
||||
const text = `${option.value} ${option.label}`.toLowerCase();
|
||||
if (text.includes("happyhorse")) return VIDEO_MODEL_ICON_URLS.happyHorse;
|
||||
if (text.includes("pixverse")) return VIDEO_MODEL_ICON_URLS.pixverse;
|
||||
if (text.includes("vidu")) return VIDEO_MODEL_ICON_URLS.vidu;
|
||||
if (text.includes("wan") || text.includes("万相")) return VIDEO_MODEL_ICON_URLS.wanxiang;
|
||||
if (text.includes("kling") || text.includes("可灵")) return VIDEO_MODEL_ICON_URLS.kling;
|
||||
return null;
|
||||
}
|
||||
|
||||
export function SelectChip({
|
||||
chipId,
|
||||
value,
|
||||
@@ -74,7 +56,6 @@ export function SelectChip({
|
||||
>
|
||||
{options.map((option, index) => {
|
||||
const active = option.value === value;
|
||||
const iconUrl = chipId === "video-model" ? getVideoModelIconUrl(option) : null;
|
||||
return (
|
||||
<button
|
||||
key={option.value}
|
||||
@@ -90,11 +71,6 @@ export function SelectChip({
|
||||
>
|
||||
<span className="ai-workbench-select-chip__option-label">
|
||||
<span className="ai-workbench-select-chip__option-dot" aria-hidden="true" />
|
||||
{iconUrl ? (
|
||||
<span className="ai-workbench-select-chip__option-icon" aria-hidden="true">
|
||||
<img src={iconUrl} alt="" loading="lazy" />
|
||||
</span>
|
||||
) : null}
|
||||
<span className="ai-workbench-select-chip__option-copy">
|
||||
<span className="ai-workbench-select-chip__option-title">
|
||||
<span>{option.label}</span>
|
||||
@@ -285,4 +261,4 @@ export function InlineOptionChip({
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1157,25 +1157,6 @@
|
||||
background: #202c28;
|
||||
}
|
||||
|
||||
.product-clone-page[data-tool="clone"] .clone-ai-upload-zone.is-full {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.product-clone-page[data-tool="clone"] .clone-ai-upload-zone.is-full strong {
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
color: #aeb8c4;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.product-clone-page[data-tool="clone"] .clone-ai-upload-zone.is-full:hover {
|
||||
border-color: rgba(255, 255, 255, 0.16);
|
||||
background:
|
||||
radial-gradient(circle at 50% 0%, rgba(var(--ecm-accent-rgb), 0.09), transparent 58%),
|
||||
var(--ecm-inset);
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.product-clone-page[data-tool="clone"] .clone-ai-upload-zone:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
@@ -1293,27 +1274,6 @@
|
||||
transform: scale(0.92);
|
||||
}
|
||||
|
||||
.product-clone-page[data-tool="clone"] .clone-ai-card,
|
||||
.product-clone-page[data-tool="clone"] .clone-ai-upload-zone,
|
||||
.product-clone-page[data-tool="clone"] .clone-ai-uploaded-files {
|
||||
position: relative;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.product-clone-page[data-tool="clone"] .clone-ai-upload-zone {
|
||||
z-index: 8;
|
||||
}
|
||||
|
||||
.product-clone-page[data-tool="clone"] .clone-ai-upload-zone:has(.clone-ai-uploaded-file:hover),
|
||||
.product-clone-page[data-tool="clone"] .clone-ai-upload-zone:has(.clone-ai-uploaded-file:focus-within) {
|
||||
z-index: 90;
|
||||
}
|
||||
|
||||
.product-clone-page[data-tool="clone"] .clone-ai-uploaded-file:hover,
|
||||
.product-clone-page[data-tool="clone"] .clone-ai-uploaded-file:focus-within {
|
||||
z-index: 95;
|
||||
}
|
||||
|
||||
.product-clone-page[data-tool="clone"] .clone-ai-settings-section {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
@@ -3505,7 +3465,7 @@
|
||||
|
||||
.uploaded-image-zoom {
|
||||
position: absolute;
|
||||
z-index: 220;
|
||||
z-index: 70;
|
||||
left: 50%;
|
||||
bottom: calc(100% + 10px);
|
||||
display: block;
|
||||
@@ -3526,18 +3486,6 @@
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.product-clone-page[data-tool="clone"] .clone-ai-uploaded-file .uploaded-image-zoom {
|
||||
left: 0;
|
||||
bottom: calc(100% + 12px);
|
||||
width: min(240px, 58vw);
|
||||
transform: translate(0, 8px) scale(0.96);
|
||||
}
|
||||
|
||||
.product-clone-page[data-tool="clone"] .clone-ai-uploaded-file:hover .uploaded-image-zoom,
|
||||
.product-clone-page[data-tool="clone"] .clone-ai-uploaded-file:focus-within .uploaded-image-zoom {
|
||||
transform: translate(0, 0) scale(1);
|
||||
}
|
||||
|
||||
.uploaded-image-zoom img {
|
||||
display: block;
|
||||
width: 100%;
|
||||
@@ -3560,233 +3508,6 @@
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.product-clone-page[data-tool="clone"] .clone-ai-upload-zone:has(.clone-ai-upload-preview-wrap) {
|
||||
align-content: start;
|
||||
justify-items: stretch;
|
||||
gap: 10px;
|
||||
min-height: 0;
|
||||
padding: 12px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.product-clone-page[data-tool="clone"] .clone-ai-upload-zone:has(.clone-ai-upload-preview-wrap) .clone-ai-upload-main {
|
||||
grid-template-columns: 34px minmax(0, 1fr) auto;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
justify-items: start;
|
||||
gap: 4px 9px;
|
||||
}
|
||||
|
||||
.product-clone-page[data-tool="clone"] .clone-ai-upload-zone:has(.clone-ai-upload-preview-wrap) .clone-ai-upload-icon {
|
||||
grid-row: span 2;
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
border-radius: 10px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.product-clone-page[data-tool="clone"] .clone-ai-upload-zone:has(.clone-ai-upload-preview-wrap) .clone-ai-upload-title {
|
||||
min-width: 0;
|
||||
color: #c9d2dd;
|
||||
font-size: 12px;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.product-clone-page[data-tool="clone"] .clone-ai-upload-zone:has(.clone-ai-upload-preview-wrap) strong {
|
||||
grid-row: span 2;
|
||||
min-width: 96px;
|
||||
height: 34px;
|
||||
padding: 0 12px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.product-clone-page[data-tool="clone"] .clone-ai-upload-zone:has(.clone-ai-upload-preview-wrap) .clone-ai-upload-hint {
|
||||
grid-column: 2;
|
||||
min-width: 0;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.product-clone-page[data-tool="clone"] .clone-ai-upload-preview-wrap {
|
||||
display: grid;
|
||||
gap: 7px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.product-clone-page[data-tool="clone"] .clone-ai-upload-preview {
|
||||
position: relative;
|
||||
display: grid;
|
||||
width: 100%;
|
||||
min-height: 126px;
|
||||
overflow: hidden;
|
||||
border: 1px solid rgba(var(--ecm-accent-rgb), 0.2);
|
||||
border-radius: 10px;
|
||||
background:
|
||||
linear-gradient(180deg, rgba(255, 255, 255, 0.04), transparent 48%),
|
||||
rgba(8, 10, 12, 0.56);
|
||||
}
|
||||
|
||||
.product-clone-page[data-tool="clone"] .clone-ai-upload-preview img {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 126px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.product-clone-page[data-tool="clone"] .clone-ai-upload-preview__meta {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
padding: 0 2px;
|
||||
color: #eef2f6;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.product-clone-page[data-tool="clone"] .clone-ai-upload-preview__meta span {
|
||||
display: grid;
|
||||
min-width: 0;
|
||||
gap: 1px;
|
||||
}
|
||||
|
||||
.product-clone-page[data-tool="clone"] .clone-ai-upload-preview__meta b,
|
||||
.product-clone-page[data-tool="clone"] .clone-ai-upload-preview__meta em {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
max-width: 260px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.product-clone-page[data-tool="clone"] .clone-ai-upload-preview__meta b {
|
||||
color: var(--ecm-accent);
|
||||
font-size: 10px;
|
||||
font-style: normal;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.product-clone-page[data-tool="clone"] .clone-ai-upload-preview__meta em {
|
||||
color: #aeb8c4;
|
||||
font-size: 10px;
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.product-clone-page[data-tool="clone"] .clone-ai-uploaded-files {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 7px;
|
||||
margin-top: 0;
|
||||
padding-top: 2px;
|
||||
}
|
||||
|
||||
.product-clone-page[data-tool="clone"] .clone-ai-uploaded-stack {
|
||||
display: grid;
|
||||
gap: 7px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.product-clone-page[data-tool="clone"] .clone-ai-uploaded-head {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
color: #768292;
|
||||
font-size: 10px;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.product-clone-page[data-tool="clone"] .clone-ai-uploaded-head span {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.product-clone-page[data-tool="clone"] .clone-ai-uploaded-head b {
|
||||
flex: 0 0 auto;
|
||||
padding: 2px 7px;
|
||||
border: 1px solid rgba(var(--ecm-accent-rgb), 0.18);
|
||||
border-radius: 999px;
|
||||
background: rgba(var(--ecm-accent-rgb), 0.07);
|
||||
color: var(--ecm-accent);
|
||||
font-size: 10px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.product-clone-page[data-tool="clone"] .clone-ai-uploaded-file {
|
||||
width: 46px;
|
||||
height: 46px;
|
||||
overflow: hidden;
|
||||
border-color: rgba(255, 255, 255, 0.12);
|
||||
border-radius: 9px;
|
||||
background: rgba(255, 255, 255, 0.035);
|
||||
transition:
|
||||
border-color 160ms ease,
|
||||
box-shadow 160ms ease,
|
||||
transform 160ms ease;
|
||||
}
|
||||
|
||||
.product-clone-page[data-tool="clone"] .clone-ai-uploaded-file > button:not(.clone-ai-uploaded-file__thumb) {
|
||||
top: 2px;
|
||||
right: 2px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background: rgba(8, 10, 12, 0.82);
|
||||
color: #fff;
|
||||
font-size: 9px;
|
||||
}
|
||||
|
||||
.product-clone-page[data-tool="clone"] .clone-ai-uploaded-file.is-active {
|
||||
border-color: rgba(var(--ecm-accent-rgb), 0.86);
|
||||
box-shadow: 0 0 0 2px rgba(var(--ecm-accent-rgb), 0.12);
|
||||
}
|
||||
|
||||
.product-clone-page[data-tool="clone"] .clone-ai-uploaded-file .clone-ai-uploaded-file__thumb {
|
||||
position: static;
|
||||
inset: auto;
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
border-radius: inherit;
|
||||
background: transparent;
|
||||
color: inherit;
|
||||
cursor: pointer;
|
||||
font-size: inherit;
|
||||
opacity: 1;
|
||||
transform: none;
|
||||
transition: none;
|
||||
}
|
||||
|
||||
.product-clone-page[data-tool="clone"] .clone-ai-uploaded-file .clone-ai-uploaded-file__thumb img {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.product-clone-page[data-tool="clone"] .clone-ai-uploaded-file__thumb span {
|
||||
position: absolute;
|
||||
left: 3px;
|
||||
bottom: 3px;
|
||||
min-width: 14px;
|
||||
height: 14px;
|
||||
border-radius: 999px;
|
||||
background: rgba(8, 10, 12, 0.76);
|
||||
color: #eef2f6;
|
||||
font-size: 9px;
|
||||
font-weight: 900;
|
||||
line-height: 14px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.product-clone-page[data-tool="clone"] .clone-ai-uploaded-file:hover {
|
||||
border-color: rgba(var(--ecm-accent-rgb), 0.62);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
@keyframes image-mention-menu-rise {
|
||||
from {
|
||||
opacity: 0;
|
||||
@@ -9026,13 +8747,6 @@
|
||||
box-shadow: 0 10px 28px rgba(var(--ecm-accent-rgb), 0.18);
|
||||
}
|
||||
|
||||
.product-clone-page[data-tool="clone"] .clone-ai-upload-zone.is-full strong {
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
color: #aeb8c4;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.product-clone-page[data-tool="clone"] :is(.clone-ai-generate:hover:not(:disabled), .clone-ai-send-button:hover:not(:disabled)) {
|
||||
filter: brightness(1.03);
|
||||
transform: translateY(-1px);
|
||||
|
||||
@@ -1485,40 +1485,6 @@
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"] .ai-workbench-select-chip__option-icon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex: 0 0 24px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
margin-top: 0;
|
||||
overflow: hidden;
|
||||
border: 1px solid rgba(255, 255, 255, 0.075);
|
||||
border-radius: 8px;
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"] .ai-workbench-select-chip__option-icon img {
|
||||
display: block;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
object-fit: contain;
|
||||
filter: brightness(0) invert(1);
|
||||
opacity: 0.86;
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"] .ai-workbench-select-chip__option:hover .ai-workbench-select-chip__option-icon,
|
||||
.web-shell[data-ui-theme="dark-green"] .ai-workbench-select-chip__option.is-active .ai-workbench-select-chip__option-icon {
|
||||
border-color: rgba(var(--accent-rgb), 0.2);
|
||||
background: rgba(var(--accent-rgb), 0.08);
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"] .ai-workbench-select-chip__option:hover .ai-workbench-select-chip__option-icon img,
|
||||
.web-shell[data-ui-theme="dark-green"] .ai-workbench-select-chip__option.is-active .ai-workbench-select-chip__option-icon img {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"] .ai-workbench-select-chip__option-copy {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
@@ -6438,75 +6404,6 @@
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"] .profile-page--dashboard .profile-page__account-summary {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
gap: 10px;
|
||||
align-items: stretch;
|
||||
min-width: 0;
|
||||
padding: 10px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.055);
|
||||
border-radius: 11px;
|
||||
background:
|
||||
linear-gradient(135deg, rgba(var(--accent-rgb), 0.055), transparent 62%),
|
||||
rgba(255, 255, 255, 0.022);
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"] .profile-page--dashboard .profile-page__account-summary-main,
|
||||
.web-shell[data-ui-theme="dark-green"] .profile-page--dashboard .profile-page__account-summary-metric {
|
||||
display: grid;
|
||||
min-width: 0;
|
||||
align-content: center;
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"] .profile-page--dashboard .profile-page__account-summary-main {
|
||||
gap: 3px;
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"] .profile-page--dashboard .profile-page__account-summary-metric {
|
||||
min-width: 86px;
|
||||
justify-items: end;
|
||||
padding-left: 10px;
|
||||
border-left: 1px solid rgba(255, 255, 255, 0.06);
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"] .profile-page--dashboard .profile-page__account-summary small {
|
||||
overflow: hidden;
|
||||
color: rgba(225, 235, 231, 0.52);
|
||||
font-size: 10px;
|
||||
font-weight: 800;
|
||||
line-height: 1.2;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"] .profile-page--dashboard .profile-page__account-summary strong {
|
||||
overflow: hidden;
|
||||
color: var(--fg);
|
||||
font-size: 16px;
|
||||
font-weight: 850;
|
||||
line-height: 1.25;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"] .profile-page--dashboard .profile-page__account-summary-metric strong {
|
||||
color: var(--accent);
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"] .profile-page--dashboard .profile-page__account-summary em {
|
||||
overflow: hidden;
|
||||
color: rgba(225, 235, 231, 0.42);
|
||||
font-size: 10px;
|
||||
font-style: normal;
|
||||
font-weight: 650;
|
||||
line-height: 1.35;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"] .profile-page--dashboard .profile-page__account-card .profile-page__upload-card--meta {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 8px;
|
||||
@@ -7016,93 +6913,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* Profile center: align with the ecommerce generator's flatter dark-green tone. */
|
||||
.web-shell[data-ui-theme="dark-green"] .profile-page--dashboard {
|
||||
background: var(--bg-base);
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"] .profile-page--dashboard .profile-page__banner {
|
||||
background: var(--bg-panel);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"] .profile-page--dashboard .profile-page__banner.has-image {
|
||||
background-color: var(--bg-panel);
|
||||
background-blend-mode: luminosity;
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"] .profile-page--dashboard .profile-page__banner-overlay {
|
||||
background:
|
||||
linear-gradient(180deg, rgba(13, 15, 15, 0.7), rgba(13, 15, 15, 0.88)),
|
||||
linear-gradient(90deg, rgba(13, 15, 15, 0.62), rgba(13, 15, 15, 0.28) 44%, rgba(13, 15, 15, 0.68));
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"] .profile-page--dashboard .profile-page__sidebar,
|
||||
.web-shell[data-ui-theme="dark-green"] .profile-page--dashboard .profile-page__account-card,
|
||||
.web-shell[data-ui-theme="dark-green"] .profile-page--dashboard .profile-page__main-tabs,
|
||||
.web-shell[data-ui-theme="dark-green"] .profile-page--dashboard .profile-page__section,
|
||||
.web-shell[data-ui-theme="dark-green"] .profile-page--dashboard .profile-page__list-card,
|
||||
.web-shell[data-ui-theme="dark-green"] .profile-page--dashboard .profile-page__review-item,
|
||||
.web-shell[data-ui-theme="dark-green"] .profile-page--dashboard .profile-page__empty-state,
|
||||
.web-shell[data-ui-theme="dark-green"] .profile-page--dashboard .profile-page__upload-card {
|
||||
border-color: var(--border-weak);
|
||||
background: var(--bg-panel);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"] .profile-page--dashboard .profile-page__main-tabs,
|
||||
.web-shell[data-ui-theme="dark-green"] .profile-page--dashboard .profile-page__account-card .profile-page__list-tabs {
|
||||
background: var(--bg-inset);
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"] .profile-page--dashboard .profile-page__main-tabs button,
|
||||
.web-shell[data-ui-theme="dark-green"] .profile-page--dashboard .profile-page__account-card .profile-page__list-tabs button {
|
||||
border: 1px solid transparent;
|
||||
background: transparent;
|
||||
color: var(--fg-soft);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"] .profile-page--dashboard .profile-page__main-tabs button.is-active,
|
||||
.web-shell[data-ui-theme="dark-green"] .profile-page--dashboard .profile-page__account-card .profile-page__list-tabs button.is-active {
|
||||
border-color: rgba(var(--accent-rgb), 0.42);
|
||||
background: rgba(var(--accent-rgb), 0.12);
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"] .profile-page--dashboard .profile-page__bio-display,
|
||||
.web-shell[data-ui-theme="dark-green"] .profile-page--dashboard .profile-page__bio,
|
||||
.web-shell[data-ui-theme="dark-green"] .profile-page--dashboard .profile-page__count,
|
||||
.web-shell[data-ui-theme="dark-green"] .profile-page--dashboard .profile-page__account-summary,
|
||||
.web-shell[data-ui-theme="dark-green"] .profile-page--dashboard .profile-page__account-card .profile-page__meta-item {
|
||||
border-color: rgba(255, 255, 255, 0.065);
|
||||
background: rgba(255, 255, 255, 0.024);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"] .profile-page--dashboard .profile-page__list-card-preview,
|
||||
.web-shell[data-ui-theme="dark-green"] .profile-page--dashboard .profile-page__list-card-preview:not(.has-media) {
|
||||
border-color: rgba(255, 255, 255, 0.065);
|
||||
background: rgba(255, 255, 255, 0.024);
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"] .profile-page--dashboard .profile-page__list-card-placeholder {
|
||||
border-color: rgba(var(--accent-rgb), 0.2);
|
||||
background: rgba(var(--accent-rgb), 0.08);
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"] .profile-page--dashboard .profile-page__media-badge {
|
||||
background: rgba(8, 14, 12, 0.78);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"] .profile-page--dashboard .profile-page__share-btn,
|
||||
.web-shell[data-ui-theme="dark-green"] .profile-page--dashboard .profile-page__avatar,
|
||||
.web-shell[data-ui-theme="dark-green"] .profile-page--dashboard .profile-page__avatar-ring .profile-page__avatar {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
/* Ecommerce generation page: keep its carousel and composer independent from
|
||||
the community carousel rules that share class names. */
|
||||
.web-shell[data-ui-theme="dark-green"] .ecommerce-landing-page {
|
||||
|
||||
+1
-1
@@ -25,7 +25,7 @@ export default defineConfig(() => ({
|
||||
drop: ["console", "debugger"],
|
||||
},
|
||||
build: {
|
||||
sourcemap: "hidden",
|
||||
sourcemap: false,
|
||||
rollupOptions: {
|
||||
output: {
|
||||
manualChunks(id: string) {
|
||||
|
||||
Reference in New Issue
Block a user