feat(workbench): 三工序图片区视觉对齐 + 任务中心聚合 + 工具台头部筛选
- model-photo / platform-cover · 头部 toolbar 落地: 时间 / 模特(平台) chip 下拉 + 折叠搜索 - model-photo / platform-cover · 图片卡片样式同步图片创作 (.io-cell): bg / hover / .gen 脉冲 / .err 红框 - model-photo / platform-cover · 单图 hover overlay: 再次生成 + 下载 + 更多(加入资产库/删除) - model-photo / platform-cover · 批次底栏: 再次生成图标统一 + 更多 menu(全部加入资产库/删除该批) - model-photo · 修 TDZ bug: renderModelMini 调用挪到 MODELS 声明后, 解决整页崩溃 - model-photo · 去掉冗余 pv-summary, 商品自动选最近编辑, task 写入 name 字段 - image-optimize · 单图右上加再次生成图标, 加入 fs-image-tasks-image 与任务中心打通 - image-optimize · 输入区拆 3 行: + 在顶 / textarea 满宽 / 发送在底栏右; 参考图缩略与加号同 64×64 - asset-factory · 任务中心加时间 chip + image 类型 + 跳转表; 删冗余类型列 - pipeline · stage2 商品卡换商品库风格 + AI 生成三视图主 CTA + .tri-missing-badge[hidden] CSS 修复
This commit is contained in:
+666
-103
@@ -62,7 +62,49 @@
|
||||
.ai-avatar { width: 26px; height: 26px; background: var(--heat); color: var(--accent-white); display: grid; place-items: center; font-size: 11px; font-weight: 700; border: 1px solid var(--heat); border-radius: 50%; }
|
||||
.del { text-decoration: line-through; color: var(--black-alpha-48); }
|
||||
.ins { background: var(--forest-bg); color: var(--accent-forest); padding: 0 3px; }
|
||||
.chat-input { padding: 14px 18px; border-top: 1px solid var(--border-faint); }
|
||||
.chat-input { padding: 14px 18px 18px; border-top: 1px solid var(--border-faint); }
|
||||
.chat-input-card {
|
||||
background: var(--background-base);
|
||||
border: 1px solid var(--border-faint);
|
||||
border-radius: var(--r-md);
|
||||
padding: 12px 14px 10px;
|
||||
transition: border-color var(--t-base), box-shadow var(--t-base);
|
||||
}
|
||||
.chat-input-card:focus-within { border-color: var(--accent-black); box-shadow: 0 0 0 3px rgba(0,0,0,.04); }
|
||||
.chat-input-area {
|
||||
width: 100%; border: none; outline: none; background: transparent;
|
||||
font-family: var(--font-sans); font-size: 13px; color: var(--accent-black);
|
||||
line-height: 1.55; resize: none; padding: 0; min-height: 42px;
|
||||
}
|
||||
.chat-input-area::placeholder { color: var(--black-alpha-40); }
|
||||
.chat-input-foot { display: flex; align-items: center; gap: 8px; margin-top: 10px; }
|
||||
.chat-input-foot .hint { font-family: var(--font-mono); font-size: 11px; color: var(--black-alpha-40); letter-spacing: .02em; }
|
||||
.chat-input-foot .spacer { flex: 1; }
|
||||
.chat-icon-btn {
|
||||
width: 28px; height: 28px; display: grid; place-items: center;
|
||||
background: transparent; border: 1px solid var(--border-faint);
|
||||
border-radius: 50%; color: var(--black-alpha-56); cursor: pointer;
|
||||
transition: border-color var(--t-base), color var(--t-base), background var(--t-base);
|
||||
}
|
||||
.chat-icon-btn:hover { border-color: var(--accent-black); color: var(--accent-black); }
|
||||
.chat-send-btn {
|
||||
width: 32px; height: 32px; display: grid; place-items: center;
|
||||
background: var(--accent-black); border: 1px solid var(--accent-black);
|
||||
border-radius: 50%; color: var(--accent-white); cursor: pointer;
|
||||
transition: background var(--t-base), border-color var(--t-base), transform var(--t-base);
|
||||
}
|
||||
.chat-send-btn:hover { background: var(--heat); border-color: var(--heat); }
|
||||
.chat-send-btn:active { transform: scale(.95); }
|
||||
.chat-send-btn:disabled { background: var(--black-alpha-12); border-color: var(--black-alpha-12); color: var(--black-alpha-40); cursor: not-allowed; transform: none; }
|
||||
.chat-attach-row { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 8px; }
|
||||
.chat-attach-chip {
|
||||
display: inline-flex; align-items: center; gap: 6px;
|
||||
padding: 3px 6px 3px 8px; background: var(--surface);
|
||||
border: 1px solid var(--border-faint); border-radius: var(--r-sm);
|
||||
font-family: var(--font-mono); font-size: 11px; color: var(--accent-black);
|
||||
}
|
||||
.chat-attach-chip .x { width: 14px; height: 14px; display: grid; place-items: center; background: transparent; border: none; color: var(--black-alpha-48); cursor: pointer; border-radius: 50%; }
|
||||
.chat-attach-chip .x:hover { background: var(--black-alpha-08); color: var(--accent-black); }
|
||||
|
||||
.shot-list { display: flex; flex-direction: column; }
|
||||
.shots-body { padding: 12px 16px; flex: 1; overflow-y: auto; max-height: 540px; display: flex; flex-direction: column; gap: 0; }
|
||||
@@ -193,20 +235,195 @@
|
||||
display: grid; place-items: center; overflow: hidden;
|
||||
}
|
||||
.prod-preview-history .h-thumb:hover { border-color: var(--heat-40); }
|
||||
.prod-preview-history .h-thumb.active { border-color: var(--heat); border-width: 2px; box-shadow: 0 0 0 2px var(--heat-12); }
|
||||
/* 已采用版本:主橙描边 + 「已采用」徽标 */
|
||||
.prod-preview-history .h-thumb.adopted { border-color: var(--heat); border-width: 2px; box-shadow: 0 0 0 2px var(--heat-12); }
|
||||
/* 仅预览(未采用):黑色描边,无徽标 */
|
||||
.prod-preview-history .h-thumb.previewing { border-color: var(--accent-black); border-width: 2px; }
|
||||
.prod-preview-history .h-thumb .v { font-family: var(--font-mono); font-size: 10px; color: var(--black-alpha-56); letter-spacing: .02em; }
|
||||
.prod-preview-history .h-thumb.active .v { color: var(--heat); font-weight: 600; }
|
||||
.prod-preview-history .h-thumb.adopted .v { color: var(--heat); font-weight: 600; }
|
||||
.prod-preview-history .h-thumb.previewing .v { color: var(--accent-black); font-weight: 600; }
|
||||
.prod-preview-history .h-thumb .badge { position: absolute; top: 2px; left: 2px; font-family: var(--font-mono); font-size: 8.5px; padding: 0 4px; line-height: 12px; background: var(--heat); color: var(--accent-white); border-radius: 2px; letter-spacing: .02em; display: none; }
|
||||
.prod-preview-history .h-thumb.active .badge { display: block; }
|
||||
.asset-card-2 { background: var(--surface); border: 1px solid var(--border-faint); border-radius: var(--r-md); cursor: pointer; transition: border-color var(--t-base), box-shadow var(--t-base); }
|
||||
.prod-preview-history .h-thumb.adopted .badge { display: block; }
|
||||
|
||||
/* 「已采用」状态 · 浅橙 + 主橙文字,与已采用徽标视觉呼应 */
|
||||
#prod-preview-adopt:disabled,
|
||||
#prod-preview-adopt:disabled:hover {
|
||||
color: var(--heat);
|
||||
border-color: var(--heat-40);
|
||||
background: var(--heat-12);
|
||||
cursor: not-allowed;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* 主图可点击放大 */
|
||||
.prod-preview-img.is-zoomable { cursor: zoom-in; transition: border-color var(--t-base); position: relative; }
|
||||
.prod-preview-img.is-zoomable:hover { border-color: var(--heat-40); }
|
||||
.prod-preview-img.is-zoomable::after {
|
||||
content: '';
|
||||
position: absolute; top: 8px; right: 8px;
|
||||
width: 22px; height: 22px;
|
||||
background: rgba(21,20,15,.72) url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><circle cx='11' cy='11' r='7'/><path d='M21 21l-4.3-4.3M8 11h6M11 8v6'/></svg>") center/14px no-repeat;
|
||||
border-radius: var(--r-sm);
|
||||
opacity: 0; transition: opacity var(--t-base);
|
||||
pointer-events: none;
|
||||
}
|
||||
.prod-preview-img.is-zoomable:hover::after { opacity: 1; }
|
||||
|
||||
/* 三视图放大查看 lightbox */
|
||||
#tri-lightbox-bg { z-index: 80; }
|
||||
#tri-lightbox-bg .tri-lightbox {
|
||||
position: relative;
|
||||
width: min(1100px, 92vw);
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border-faint);
|
||||
border-radius: var(--r-md);
|
||||
padding: 18px 20px 20px;
|
||||
display: flex; flex-direction: column; gap: 12px;
|
||||
box-shadow: 0 24px 64px rgba(0,0,0,.24);
|
||||
}
|
||||
.tri-lightbox-head {
|
||||
display: flex; align-items: center; gap: 8px;
|
||||
font-family: var(--font-mono);
|
||||
font-size: 11px; letter-spacing: .04em; text-transform: uppercase;
|
||||
color: var(--black-alpha-56);
|
||||
padding-right: 32px;
|
||||
}
|
||||
.tri-lightbox-head .lb-ver { color: var(--heat); font-weight: 600; }
|
||||
.tri-lightbox-head .lb-tag {
|
||||
margin-left: 6px;
|
||||
padding: 2px 6px;
|
||||
background: var(--heat-12); color: var(--heat);
|
||||
border-radius: 3px;
|
||||
font-size: 10px;
|
||||
}
|
||||
.tri-lightbox-close {
|
||||
position: absolute;
|
||||
top: 12px; right: 12px;
|
||||
width: 28px; height: 28px;
|
||||
display: grid; place-items: center;
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border-faint);
|
||||
border-radius: var(--r-sm);
|
||||
color: var(--black-alpha-56);
|
||||
cursor: pointer;
|
||||
transition: background var(--t-base), color var(--t-base), border-color var(--t-base);
|
||||
z-index: 2;
|
||||
}
|
||||
.tri-lightbox-close:hover { background: var(--black-alpha-08); color: var(--accent-black); border-color: var(--black-alpha-12); }
|
||||
.tri-lightbox-close svg { width: 14px; height: 14px; }
|
||||
.tri-lightbox-img { aspect-ratio: 16/9; width: 100%; }
|
||||
.tri-lightbox-foot { display: flex; align-items: center; gap: 8px; font-family: var(--font-mono); font-size: 11px; color: var(--black-alpha-48); }
|
||||
.tri-lightbox-foot .spc { flex: 1; }
|
||||
.tri-lightbox-foot kbd {
|
||||
display: inline-block;
|
||||
padding: 1px 5px;
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border-faint);
|
||||
border-bottom-width: 2px;
|
||||
border-radius: 3px;
|
||||
font-family: var(--font-mono);
|
||||
font-size: 10px;
|
||||
color: var(--black-alpha-72);
|
||||
}
|
||||
.asset-card-2 { background: var(--surface); border: 1px solid var(--border-faint); border-radius: var(--r-md); cursor: pointer; transition: border-color var(--t-base), box-shadow var(--t-base); overflow: hidden; display: flex; flex-direction: column; }
|
||||
.asset-card-2:hover { border-color: var(--heat-40); box-shadow: 0 1px 3px rgba(0,0,0,.04); }
|
||||
.asset-card-2 .thumb-2 { aspect-ratio: 1; }
|
||||
.asset-card-2 .body-2 { padding: 12px 14px; }
|
||||
.asset-card-2 .body-2 .btn-apply { background: var(--heat); color: var(--accent-white); border: 1px solid var(--heat); }
|
||||
.asset-card-2 .body-2 .btn-apply:hover { box-shadow: var(--shadow-cta-hover); }
|
||||
|
||||
/* stage2 商品卡 · 与商品库 .product-card 视觉一致 */
|
||||
.asset-card-2.prod-lib-card:hover { background: var(--background-lighter); border-color: var(--black-alpha-48); }
|
||||
.asset-card-2.prod-lib-card .prod-thumb { aspect-ratio: 1.4 / 1; position: relative; }
|
||||
.asset-card-2.prod-lib-card .prod-body { padding: 14px 14px 12px; flex: 1; }
|
||||
.asset-card-2.prod-lib-card .prod-name {
|
||||
font-size: 14px; font-weight: 600;
|
||||
color: var(--accent-black);
|
||||
line-height: 1.3;
|
||||
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
||||
}
|
||||
.asset-card-2.prod-lib-card .prod-cat {
|
||||
display: inline-flex; align-items: center;
|
||||
margin-top: 8px;
|
||||
padding: 2px 8px;
|
||||
background: var(--background-lighter);
|
||||
color: var(--black-alpha-72);
|
||||
border-radius: var(--r-sm);
|
||||
font-size: 11.5px;
|
||||
}
|
||||
.asset-card-2.prod-lib-card .prod-date {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 11px;
|
||||
color: var(--black-alpha-48);
|
||||
margin-top: 10px;
|
||||
letter-spacing: .02em;
|
||||
}
|
||||
.asset-card-2.prod-lib-card .prod-footer {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto 1fr;
|
||||
align-items: center;
|
||||
column-gap: 8px;
|
||||
padding: 10px 12px;
|
||||
border-top: 1px solid var(--border-faint);
|
||||
font-size: 11.5px;
|
||||
color: var(--black-alpha-56);
|
||||
background: var(--background-base);
|
||||
}
|
||||
.asset-card-2.prod-lib-card .prod-footer .stat {
|
||||
display: inline-flex; align-items: center; justify-content: center; gap: 5px;
|
||||
padding: 3px 8px;
|
||||
border-radius: var(--r-sm);
|
||||
font-family: var(--font-mono);
|
||||
letter-spacing: .02em;
|
||||
white-space: nowrap;
|
||||
justify-self: center;
|
||||
}
|
||||
.asset-card-2.prod-lib-card .prod-footer .stat svg { width: 13px; height: 13px; color: var(--black-alpha-48); flex-shrink: 0; }
|
||||
.asset-card-2.prod-lib-card .prod-footer .stat b { color: var(--accent-black); font-weight: 600; }
|
||||
.asset-card-2.prod-lib-card .prod-footer .sep { color: var(--black-alpha-24); font-family: var(--font-mono); flex-shrink: 0; }
|
||||
.asset-card-2.prod-lib-card .prod-action {
|
||||
padding: 10px 12px;
|
||||
border-top: 1px solid var(--border-faint);
|
||||
background: var(--surface);
|
||||
}
|
||||
.asset-card-2.prod-lib-card .prod-action[hidden] { display: none; }
|
||||
.asset-card-2.prod-lib-card .prod-action .btn-aigen {
|
||||
width: 100%;
|
||||
display: inline-flex; align-items: center; justify-content: center; gap: 6px;
|
||||
height: 34px; padding: 0 14px;
|
||||
background: var(--heat);
|
||||
color: var(--accent-white);
|
||||
border: 1px solid var(--heat);
|
||||
border-radius: var(--r-sm);
|
||||
font-size: 13px; font-weight: 500;
|
||||
cursor: pointer;
|
||||
font-family: inherit;
|
||||
box-shadow:
|
||||
inset 0 -2px 4px rgba(250, 93, 25, 0.20),
|
||||
0 1px 1px rgba(250, 93, 25, 0.12),
|
||||
0 2px 4px rgba(250, 93, 25, 0.10);
|
||||
transition: background var(--t-base), box-shadow var(--t-base), transform var(--t-base);
|
||||
}
|
||||
.asset-card-2.prod-lib-card .prod-action .btn-aigen:hover {
|
||||
background: #FB6E2E;
|
||||
box-shadow:
|
||||
inset 0 -2px 4px rgba(250, 93, 25, 0.24),
|
||||
0 2px 4px rgba(250, 93, 25, 0.20),
|
||||
0 4px 12px rgba(250, 93, 25, 0.18);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
.asset-card-2.prod-lib-card .prod-action .btn-aigen:active { transform: translateY(0); }
|
||||
.asset-card-2.prod-lib-card .prod-action .btn-aigen:disabled {
|
||||
opacity: .65; cursor: not-allowed; transform: none;
|
||||
box-shadow: inset 0 -2px 4px rgba(250, 93, 25, 0.20);
|
||||
}
|
||||
.asset-card-2.prod-lib-card .prod-action .btn-aigen .ai-spark {
|
||||
width: 14px; height: 14px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* 通用资产详情 modal · 参考布局 v2 */
|
||||
.asset-modal-bg { position: fixed; inset: 0; background: rgba(0,0,0,.4); z-index: 1000; display: none; align-items: center; justify-content: center; padding: 40px; }
|
||||
.asset-modal-bg { position: fixed; inset: 0; background: rgba(0,0,0,.4); z-index: 1010; display: none; align-items: center; justify-content: center; padding: 40px; }
|
||||
.asset-modal-bg.show { display: flex; }
|
||||
.asset-modal { background: var(--surface); border: 1px solid var(--border-faint); border-radius: var(--r-md); width: min(1040px, 100%); max-height: calc(100vh - 80px); overflow: hidden; display: flex; flex-direction: column; box-shadow: 0 16px 48px rgba(0,0,0,.18); }
|
||||
.asset-modal-h { display: flex; align-items: center; gap: 10px; padding: 14px 20px; border-bottom: 1px solid var(--border-faint); }
|
||||
@@ -393,16 +610,57 @@
|
||||
.ml-toolbar .chip:hover { color: var(--accent-black); }
|
||||
.ml-toolbar .chip.active { background: var(--heat-12); color: var(--heat); border-color: var(--heat-40); font-weight: 600; }
|
||||
.ml-scroll { flex: 1; min-height: 0; overflow-y: auto; padding: 20px 28px 28px; }
|
||||
.ml-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: 14px; }
|
||||
.ml-card { background: var(--surface); border: 1px solid var(--border-faint); border-radius: var(--r-md); padding: 10px; cursor: pointer; transition: border-color var(--t-base), box-shadow var(--t-base); display: flex; flex-direction: column; gap: 8px; }
|
||||
.ml-card:hover { border-color: var(--heat-40); box-shadow: 0 1px 3px rgba(0,0,0,.04); }
|
||||
.ml-card .placeholder { aspect-ratio: 3/4; }
|
||||
.ml-card .ml-card-nm { font-size: 13px; font-weight: 500; color: var(--accent-black); }
|
||||
.ml-card .ml-card-sub { font-family: var(--font-mono); font-size: 10.5px; color: var(--black-alpha-48); }
|
||||
.ml-card .ml-card-foot { display: flex; align-items: center; gap: 6px; margin-top: auto; }
|
||||
.ml-card .ml-card-foot .pill { font-size: 10.5px; padding: 1px 7px; }
|
||||
.ml-card .ml-card-foot .btn-apply { margin-left: auto; height: 26px; padding: 0 12px; background: var(--heat); color: var(--accent-white); border: 1px solid var(--heat); border-radius: var(--r-sm); font-size: 11.5px; cursor: pointer; font-family: inherit; }
|
||||
.ml-card .ml-card-foot .btn-apply:hover { box-shadow: var(--shadow-cta-hover); }
|
||||
/* 卡片 · 视觉对齐 model-photo .model-card (padding 8 / gap 6 / 无 foot 行) */
|
||||
.ml-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(170px, 1fr)); gap: 12px; }
|
||||
.ml-card {
|
||||
position: relative;
|
||||
background: var(--background-lighter);
|
||||
border: 1px solid var(--border-faint);
|
||||
border-radius: var(--r-md);
|
||||
padding: 8px;
|
||||
cursor: pointer;
|
||||
display: flex; flex-direction: column; gap: 6px;
|
||||
transition: background var(--t-base), border-color var(--t-base);
|
||||
}
|
||||
.ml-card:hover { background: var(--surface); }
|
||||
.ml-card .placeholder { aspect-ratio: 3/4; border-radius: var(--r-sm); }
|
||||
.ml-card .ml-card-nm { font-size: 12.5px; font-weight: 500; color: var(--accent-black); }
|
||||
.ml-card .ml-card-sub { font-family: var(--font-mono); font-size: 10.5px; color: var(--black-alpha-48); letter-spacing: .02em; }
|
||||
|
||||
/* 「添加演员/场景」入口卡 · 与 model-photo 模特库视觉一致 */
|
||||
.ml-card.ml-upload-card { border: 1.5px dashed var(--black-alpha-24); background: var(--surface); display: flex; flex-direction: column; gap: 8px; transition: border-color var(--t-base), background var(--t-base); }
|
||||
.ml-card.ml-upload-card:hover { border-color: var(--heat); background: var(--heat-12); box-shadow: none; }
|
||||
.ml-card.ml-upload-card:focus-visible { outline: 2px solid var(--heat); outline-offset: 2px; }
|
||||
.ml-card.ml-upload-card .up-thumb { aspect-ratio: 3/4; border-radius: var(--r-sm); background: transparent; display: grid; place-items: center; }
|
||||
.ml-card.ml-upload-card .up-plus { width: 44px; height: 44px; border-radius: 50%; background: var(--surface); border: 1px solid var(--black-alpha-12); color: var(--black-alpha-56); display: grid; place-items: center; transition: background var(--t-base), color var(--t-base), border-color var(--t-base), transform var(--t-base); }
|
||||
.ml-card.ml-upload-card:hover .up-plus { background: var(--heat); border-color: var(--heat); color: var(--accent-white); transform: scale(1.06); }
|
||||
.ml-card.ml-upload-card .up-plus svg { width: 22px; height: 22px; }
|
||||
.ml-card.ml-upload-card .ml-card-nm { color: var(--accent-black); }
|
||||
.ml-card.ml-upload-card:hover .ml-card-nm { color: var(--heat); }
|
||||
|
||||
/* ─── 添加来源 · 选择 modal (AI 生成 / 本地上传) ─── */
|
||||
.ml-up-choice-bg { position: fixed; inset: 0; z-index: 1200; background: rgba(21, 20, 15, .42); display: none; place-items: center; padding: 16px; }
|
||||
.ml-up-choice-bg.show { display: grid; }
|
||||
.ml-up-choice { width: min(560px, 92vw); background: var(--surface); border: 1px solid var(--border-faint); border-radius: var(--r-md); box-shadow: 0 16px 48px rgba(21, 20, 15, .18); overflow: hidden; position: relative; }
|
||||
.ml-up-choice .uc-h { display: flex; align-items: center; gap: 12px; padding: 18px 22px 14px; border-bottom: 1px solid var(--border-faint); }
|
||||
.ml-up-choice .uc-h .ic-m { width: 36px; height: 36px; border-radius: var(--r-md); background: var(--heat-12); color: var(--heat); display: grid; place-items: center; flex-shrink: 0; }
|
||||
.ml-up-choice .uc-h .ic-m svg { width: 18px; height: 18px; }
|
||||
.ml-up-choice .uc-h .ti { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
|
||||
.ml-up-choice .uc-h .ti strong { font-size: 15px; color: var(--accent-black); font-weight: 600; }
|
||||
.ml-up-choice .uc-h .ti .mono { font-family: var(--font-mono); font-size: 11.5px; color: var(--black-alpha-48); letter-spacing: .02em; }
|
||||
.ml-up-choice .uc-h .uc-x { margin-left: auto; width: 28px; height: 28px; background: transparent; border: 0; border-radius: var(--r-sm); color: var(--black-alpha-56); cursor: pointer; display: grid; place-items: center; }
|
||||
.ml-up-choice .uc-h .uc-x:hover { background: var(--background-lighter); color: var(--accent-black); }
|
||||
.ml-up-choice .uc-h .uc-x svg { width: 14px; height: 14px; }
|
||||
.ml-up-choice .uc-body { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; padding: 20px 22px 22px; }
|
||||
.ml-up-choice .uc-option { background: var(--surface); border: 1px solid var(--border-faint); border-radius: var(--r-md); padding: 18px 16px; text-align: left; cursor: pointer; font-family: inherit; display: flex; flex-direction: column; gap: 10px; transition: border-color var(--t-base), background var(--t-base); }
|
||||
.ml-up-choice .uc-option:hover { border-color: var(--heat); background: var(--heat-12); }
|
||||
.ml-up-choice .uc-option .opt-ic { width: 40px; height: 40px; border-radius: var(--r-md); background: var(--background-lighter); color: var(--heat); border: 1px solid var(--heat-20); display: grid; place-items: center; transition: background var(--t-base), color var(--t-base); }
|
||||
.ml-up-choice .uc-option:hover .opt-ic { background: var(--heat); color: var(--accent-white); border-color: var(--heat); }
|
||||
.ml-up-choice .uc-option .opt-ic svg { width: 18px; height: 18px; }
|
||||
.ml-up-choice .uc-option .opt-t { font-size: 14px; font-weight: 600; color: var(--accent-black); }
|
||||
.ml-up-choice .uc-option .opt-d { font-family: var(--font-mono); font-size: 11.5px; color: var(--black-alpha-56); letter-spacing: .02em; line-height: 1.55; }
|
||||
.ml-up-choice .uc-option .opt-tag { margin-top: auto; align-self: flex-start; font-family: var(--font-mono); font-size: 10.5px; padding: 2px 8px; border-radius: var(--r-sm); background: var(--background-lighter); color: var(--black-alpha-72); letter-spacing: .04em; }
|
||||
.ml-up-choice .uc-option:hover .opt-tag { background: var(--surface); color: var(--heat); }
|
||||
|
||||
/* 新增人物 modal · 立绘 + 三视图 上传区 */
|
||||
.upload-zone { aspect-ratio: 3/4; background: var(--background-lighter); border: 1px dashed var(--border-faint); border-radius: var(--r-md); display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 8px; cursor: pointer; transition: border-color var(--t-base), background var(--t-base); padding: 16px; text-align: center; color: var(--black-alpha-56); font-size: 12px; }
|
||||
@@ -586,6 +844,8 @@
|
||||
<div class="pane-h">
|
||||
<strong>镜头脚本</strong>
|
||||
<span class="muted-2 mono" id="shots-meta" style="font-size:11px;">· 空 · 待生成</span>
|
||||
<span class="spacer"></span>
|
||||
<button class="btn btn-ghost btn-sm" id="chat-regen-btn">↻ 整体重写</button>
|
||||
</div>
|
||||
<div class="shots-body" id="shots-body">
|
||||
<!-- JS 注入空态/镜头卡片 -->
|
||||
@@ -604,12 +864,20 @@
|
||||
<!-- JS 注入空态/对话内容 -->
|
||||
</div>
|
||||
<div class="chat-input">
|
||||
<textarea class="textarea" id="chat-textarea" placeholder="对脚本的修改诉求 · 比如:让第 3 场更夸张一点、整体加一场结尾……" rows="2"></textarea>
|
||||
<div class="hstack" style="margin-top:8px;">
|
||||
<button class="btn btn-ghost btn-sm" id="chat-regen-btn">↻ 整体重写</button>
|
||||
<span class="spacer"></span>
|
||||
<button class="btn btn-primary" id="chat-send-btn">发送 ⌘↵</button>
|
||||
<div class="chat-input-card">
|
||||
<div class="chat-attach-row" id="chat-attach-row" hidden></div>
|
||||
<textarea class="chat-input-area" id="chat-textarea" placeholder='聊聊你的脚本想法,或输入 "@" 引用镜头……' rows="2"></textarea>
|
||||
<div class="chat-input-foot">
|
||||
<button class="chat-icon-btn" id="chat-upload-btn" title="上传脚本附件" aria-label="上传脚本附件">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 5v14M5 12h14"/></svg>
|
||||
</button>
|
||||
<span class="spacer"></span>
|
||||
<button class="chat-send-btn" id="chat-send-btn" title="发送" aria-label="发送">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14M13 6l6 6-6 6"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<input type="file" id="chat-upload-input" hidden accept=".txt,.md,.docx,.doc,.pdf,.srt,.json">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -647,14 +915,35 @@
|
||||
<span class="spacer"></span>
|
||||
</div>
|
||||
<div class="prod-row">
|
||||
<div class="asset-card-2" data-asset-kind="product" data-asset-id="prod-main" id="asset-prod-card">
|
||||
<div class="placeholder thumb-2"><span class="ph-frame" id="asset-prod-thumb-label">透真补水面膜 · 主图</span></div>
|
||||
<div class="body-2">
|
||||
<div class="hstack"><strong style="font-size:13.5px;" id="asset-prod-card-name">透真补水面膜</strong><span class="spacer"></span><span class="pill info" id="asset-prod-pill"><span class="dot"></span>缺三视图</span></div>
|
||||
<div class="hstack" style="margin-top:10px;">
|
||||
<span class="spacer"></span>
|
||||
<button class="btn btn-sm btn-apply" data-stop id="asset-prod-aigen-btn" style="background: var(--heat); color: var(--accent-white); border-color: var(--heat);">AI 生成三视图</button>
|
||||
</div>
|
||||
<div class="asset-card-2 prod-lib-card" data-asset-kind="product" data-asset-id="prod-main" id="asset-prod-card">
|
||||
<div class="placeholder prod-thumb">
|
||||
<span class="tri-missing-badge" id="asset-prod-tri-badge" tabindex="0" role="button" aria-label="缺三视图,查看说明">
|
||||
<span class="ico" aria-hidden="true"></span>
|
||||
<span class="lbl-mono">缺三视图</span>
|
||||
<span class="tri-missing-pop" role="tooltip">
|
||||
<span class="pop-h">
|
||||
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="M12 9v4M12 17h.01"/><path d="M10.3 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/></svg>
|
||||
MISSING TRI-VIEW
|
||||
</span>
|
||||
<span class="pop-body">该商品还未生成 <b>正 / 侧 / 背</b> 三视图。直接生成图片或视频,模型缺少多角度参考,角色一致性、姿态稳定性可能下降。</span>
|
||||
<span class="pop-tip">建议:点右下 <b>AI 生成三视图</b> 先补齐三视图,再发起后续生成。</span>
|
||||
</span>
|
||||
</span>
|
||||
<span class="ph-frame" id="asset-prod-thumb-label">透真补水面膜 · 主图</span>
|
||||
</div>
|
||||
<div class="prod-body">
|
||||
<div class="prod-name" id="asset-prod-card-name">透真补水面膜</div>
|
||||
<div class="prod-cat">美妆个护</div>
|
||||
<div class="prod-date">2026-05-15 创建</div>
|
||||
</div>
|
||||
<div class="prod-action" id="asset-prod-action">
|
||||
<button class="btn-aigen" type="button" data-stop id="asset-prod-aigen-btn">
|
||||
<svg class="ai-spark" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||
<path d="M12 3l1.6 4.4L18 9l-4.4 1.6L12 15l-1.6-4.4L6 9l4.4-1.6L12 3z"/>
|
||||
<path d="M19 14l.7 1.8L21.5 16.5l-1.8.7L19 19l-.7-1.8L16.5 16.5l1.8-.7L19 14z"/>
|
||||
</svg>
|
||||
AI 生成三视图
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="prod-preview" id="asset-prod-preview">
|
||||
@@ -1191,6 +1480,43 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ===== 添加演员 / 场景 · 选择来源 modal ===== -->
|
||||
<div class="ml-up-choice-bg" id="ml-up-choice-bg">
|
||||
<div class="ml-up-choice" role="dialog" aria-label="添加来源">
|
||||
<div class="uc-h">
|
||||
<div class="ic-m">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><circle cx="9" cy="8" r="4"/><path d="M3 21c0-3.5 3-6 6-6s6 2.5 6 6"/><path d="M19 8v6M22 11h-6"/></svg>
|
||||
</div>
|
||||
<div class="ti">
|
||||
<strong id="ml-up-title">添加</strong>
|
||||
<span class="mono">// 选择来源 · AI 生成或本地上传</span>
|
||||
</div>
|
||||
<button class="uc-x" type="button" id="ml-up-x" aria-label="关闭">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"><path d="M18 6L6 18M6 6l12 12"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="uc-body">
|
||||
<button type="button" class="uc-option" id="ml-up-ai">
|
||||
<span class="opt-ic">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2l1.7 4.6L18 9l-4.3 1.4L12 15l-1.7-4.6L6 9l4.3-1.4L12 2z"/><path d="M19 16l.85 2.3L22 19l-2.15.7L19 22l-.85-2.3L16 19l2.15-.7L19 16z"/></svg>
|
||||
</span>
|
||||
<div class="opt-t">AI 生成</div>
|
||||
<div class="opt-d" id="ml-up-ai-desc">描述外形 + 风格,AI 自动生成新形象与三视图</div>
|
||||
<span class="opt-tag">[ AI · STUDIO ]</span>
|
||||
</button>
|
||||
<button type="button" class="uc-option" id="ml-up-local">
|
||||
<span class="opt-ic">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg>
|
||||
</span>
|
||||
<div class="opt-t">本地上传</div>
|
||||
<div class="opt-d" id="ml-up-local-desc">上传真人 / 既有素材,后续可生成三视图统一镜头</div>
|
||||
<span class="opt-tag">[ UPLOAD ]</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<input type="file" id="ml-up-file" accept="image/*" multiple hidden>
|
||||
|
||||
<!-- ===== 额度预检 modal · PRD §10.3 四层预检 ===== -->
|
||||
<div class="modal-bg" id="quota-bg" onclick="if(event.target===this)Shell.closeModal('quota-bg')">
|
||||
<div class="modal" id="quota-modal" style="width: min(440px, 92vw);">
|
||||
@@ -1466,12 +1792,50 @@ const Stage1 = (function () {
|
||||
});
|
||||
const sendBtn = document.getElementById('chat-send-btn');
|
||||
const ta = document.getElementById('chat-textarea');
|
||||
const attachRow = document.getElementById('chat-attach-row');
|
||||
let attachments = [];
|
||||
const renderAttach = () => {
|
||||
if (!attachRow) return;
|
||||
if (!attachments.length) { attachRow.hidden = true; attachRow.innerHTML = ''; return; }
|
||||
attachRow.hidden = false;
|
||||
attachRow.innerHTML = attachments.map((f, i) => `
|
||||
<span class="chat-attach-chip">
|
||||
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 2v6h6"/></svg>
|
||||
${f.name.replace(/</g, '<')}
|
||||
<button class="x" data-rm="${i}" aria-label="移除">
|
||||
<svg width="9" height="9" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6L6 18M6 6l12 12"/></svg>
|
||||
</button>
|
||||
</span>`).join('');
|
||||
attachRow.querySelectorAll('button[data-rm]').forEach(b => {
|
||||
b.addEventListener('click', () => {
|
||||
attachments.splice(+b.dataset.rm, 1);
|
||||
renderAttach();
|
||||
});
|
||||
});
|
||||
};
|
||||
const upBtn = document.getElementById('chat-upload-btn');
|
||||
const upInput = document.getElementById('chat-upload-input');
|
||||
if (upBtn && upInput) {
|
||||
upBtn.addEventListener('click', () => upInput.click());
|
||||
upInput.addEventListener('change', (e) => {
|
||||
const files = Array.from(e.target.files || []);
|
||||
if (!files.length) return;
|
||||
attachments.push(...files);
|
||||
renderAttach();
|
||||
Shell.toast('已附加脚本文件', files.map(f => f.name).join('、'));
|
||||
upInput.value = '';
|
||||
});
|
||||
}
|
||||
if (sendBtn && ta) {
|
||||
const send = () => {
|
||||
const v = ta.value.trim();
|
||||
if (!v) return;
|
||||
pushMsg('user', v.replace(/</g, '<'));
|
||||
if (!v && !attachments.length) return;
|
||||
const fileTags = attachments.length
|
||||
? `<div class="hstack" style="gap:6px; flex-wrap:wrap; margin-bottom:6px;">${attachments.map(f => `<span class="pill" style="font-family:var(--font-mono); font-size:10.5px;">📎 ${f.name.replace(/</g, '<')}</span>`).join('')}</div>`
|
||||
: '';
|
||||
pushMsg('user', fileTags + (v ? v.replace(/</g, '<') : '<span class="muted-2">(已附加文件)</span>'));
|
||||
ta.value = '';
|
||||
attachments = []; renderAttach();
|
||||
renderChat();
|
||||
setTimeout(() => {
|
||||
pushMsg('ai', '收到。我会按这个方向调整脚本(静态演示;实际接 LLM API)。');
|
||||
@@ -1688,30 +2052,164 @@ const Stage2 = (function () {
|
||||
applyLabel: '应用到当前项目',
|
||||
});
|
||||
}
|
||||
// 用户上传的演员 / 场景(分别累积,source='own')
|
||||
const MODEL_OWN = [];
|
||||
const SCENE_OWN = [];
|
||||
let _curLibKind = 'model';
|
||||
let _curLibSource = 'all';
|
||||
function _libItemsForSource(kind, src) {
|
||||
const isModel = kind === 'model';
|
||||
const presets = isModel ? MODEL_LIB : SCENE_LIB;
|
||||
const owns = isModel ? MODEL_OWN : SCENE_OWN;
|
||||
if (src === 'preset') return presets;
|
||||
if (src === 'own') return owns;
|
||||
return [...owns, ...presets];
|
||||
}
|
||||
function _renderLibGrid() {
|
||||
const isModel = _curLibKind === 'model';
|
||||
const items = _libItemsForSource(_curLibKind, _curLibSource);
|
||||
const grid = document.getElementById('ml-grid');
|
||||
|
||||
// 「添加演员 / 添加场景」入口卡 · 平台预设是只读素材,不展示入口
|
||||
const uploadCardHTML = (_curLibSource === 'preset') ? '' : `
|
||||
<div class="ml-card ml-upload-card" id="ml-upload-card" role="button" tabindex="0" aria-label="${isModel ? '添加演员' : '添加场景'}">
|
||||
<div class="up-thumb">
|
||||
<div class="up-plus">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M12 5v14M5 12h14"/></svg>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ml-card-nm">${isModel ? '添加演员' : '添加场景'}</div>
|
||||
<div class="ml-card-sub">// AI 生成 / 本地上传</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
grid.innerHTML = uploadCardHTML + items.map(it => `
|
||||
<div class="ml-card" data-name="${it.name}" data-sub="${it.sub}">
|
||||
<div class="placeholder"><span class="ph-frame">${it.name}</span></div>
|
||||
<div class="ml-card-nm">${it.name}</div>
|
||||
<div class="ml-card-sub">${it.sub}</div>
|
||||
</div>
|
||||
`).join('');
|
||||
|
||||
const upCard = grid.querySelector('#ml-upload-card');
|
||||
if (upCard) {
|
||||
upCard.addEventListener('click', () => _openLibUploadChoice());
|
||||
upCard.addEventListener('keydown', e => {
|
||||
if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); _openLibUploadChoice(); }
|
||||
});
|
||||
}
|
||||
|
||||
// 普通卡片 click → 应用 / 详情
|
||||
grid.querySelectorAll('.ml-card:not(.ml-upload-card)').forEach(card => {
|
||||
card.addEventListener('click', (e) => {
|
||||
const name = card.dataset.name;
|
||||
const sub = card.dataset.sub;
|
||||
if (e.target.closest('[data-apply]')) {
|
||||
e.stopPropagation();
|
||||
Shell.toast('已应用「' + name + '」', isModel ? '演员库 · 来自' + (_curLibSource === 'own' ? '我的上传' : '预设') : '场景库 · 来自' + (_curLibSource === 'own' ? '我的上传' : '预设'));
|
||||
document.getElementById('ml-modal-bg').classList.remove('show');
|
||||
return;
|
||||
}
|
||||
openStripDetail(name, sub, _curLibKind);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/* ─── 添加演员/场景 · 选择来源 modal ─── */
|
||||
function _openLibUploadChoice() {
|
||||
const isModel = _curLibKind === 'model';
|
||||
document.getElementById('ml-up-title').textContent = isModel ? '添加演员' : '添加场景';
|
||||
document.getElementById('ml-up-ai-desc').textContent = isModel
|
||||
? '描述外形 + 风格,AI 自动生成新演员形象与三视图'
|
||||
: '描述类型 + 氛围,AI 自动生成新场景图与三视图';
|
||||
document.getElementById('ml-up-local-desc').textContent = isModel
|
||||
? '上传真人 / 既有演员素材,后续可生成三视图统一镜头'
|
||||
: '上传商家自有场景图,后续可生成三视图统一镜头';
|
||||
document.getElementById('ml-up-choice-bg').classList.add('show');
|
||||
}
|
||||
function _closeLibUploadChoice() {
|
||||
document.getElementById('ml-up-choice-bg').classList.remove('show');
|
||||
}
|
||||
(function _bindLibUploadChoice() {
|
||||
const bg = document.getElementById('ml-up-choice-bg');
|
||||
if (!bg) return;
|
||||
document.getElementById('ml-up-x').addEventListener('click', _closeLibUploadChoice);
|
||||
bg.addEventListener('click', e => { if (e.target === bg) _closeLibUploadChoice(); });
|
||||
document.addEventListener('keydown', e => {
|
||||
if (e.key === 'Escape' && bg.classList.contains('show')) _closeLibUploadChoice();
|
||||
});
|
||||
|
||||
// AI 生成 → 待新增独立工作台,先 toast 不跳转
|
||||
document.getElementById('ml-up-ai').addEventListener('click', () => {
|
||||
const isModel = _curLibKind === 'model';
|
||||
_closeLibUploadChoice();
|
||||
Shell.toast(isModel ? 'AI 演员工作台' : 'AI 场景工作台', '页面待新增 · 暂未跳转');
|
||||
});
|
||||
|
||||
// 本地上传 → 触发文件选择
|
||||
const fileInput = document.getElementById('ml-up-file');
|
||||
document.getElementById('ml-up-local').addEventListener('click', () => {
|
||||
_closeLibUploadChoice();
|
||||
fileInput.click();
|
||||
});
|
||||
fileInput.addEventListener('change', e => {
|
||||
const files = [...(e.target.files || [])].filter(f => /^image\//.test(f.type));
|
||||
if (!files.length) return;
|
||||
const isModel = _curLibKind === 'model';
|
||||
const ownsArr = isModel ? MODEL_OWN : SCENE_OWN;
|
||||
files.forEach((f, i) => {
|
||||
const baseName = (f.name || (isModel ? '我的演员' : '我的场景')).replace(/\.[^.]+$/, '').slice(0, 12);
|
||||
ownsArr.unshift({
|
||||
id: 'up-' + Date.now().toString(36) + i,
|
||||
name: baseName,
|
||||
sub: isModel ? '我的上传 · 待生成三视图' : '我的上传 · 待生成三视图',
|
||||
source: 'own',
|
||||
});
|
||||
});
|
||||
e.target.value = '';
|
||||
// 切到「我的上传」让用户立即看到
|
||||
_curLibSource = 'own';
|
||||
const side = document.getElementById('ml-side');
|
||||
side.querySelectorAll('.ml-side-item').forEach(x => x.classList.toggle('active', x.dataset.source === 'own'));
|
||||
// 更新左侧计数
|
||||
const ownCt = side.querySelector('.ml-side-item[data-source="own"] .ct');
|
||||
if (ownCt) ownCt.textContent = ownsArr.length;
|
||||
const allCt = side.querySelector('.ml-side-item[data-source="all"] .ct');
|
||||
if (allCt) allCt.textContent = ownsArr.length + (isModel ? MODEL_LIB.length : SCENE_LIB.length);
|
||||
_renderLibGrid();
|
||||
Shell.toast('已上传', `+ ${files.length} 张 · 来源 我的上传`);
|
||||
});
|
||||
})();
|
||||
|
||||
function openLib(kind) {
|
||||
_curLibKind = kind;
|
||||
_curLibSource = 'all';
|
||||
const isModel = kind === 'model';
|
||||
const title = isModel ? '演员库' : '场景库';
|
||||
const items = isModel ? MODEL_LIB : SCENE_LIB;
|
||||
const presets = isModel ? MODEL_LIB : SCENE_LIB;
|
||||
const owns = isModel ? MODEL_OWN : SCENE_OWN;
|
||||
|
||||
document.getElementById('ml-modal-title').textContent = title;
|
||||
document.getElementById('ml-modal-ct').textContent = '// 共 ' + items.length + ' 个预设';
|
||||
document.getElementById('ml-modal-ct').textContent = '// 共 ' + (presets.length + owns.length) + ' 个';
|
||||
|
||||
// 侧栏 · 来源
|
||||
const side = document.getElementById('ml-side');
|
||||
side.innerHTML = `
|
||||
<div class="ml-side-h">来源</div>
|
||||
<div class="ml-side-item active" data-source="all">全部 <span class="ct">${items.length}</span></div>
|
||||
<div class="ml-side-item" data-source="preset">平台预设 <span class="ct">${items.length}</span></div>
|
||||
<div class="ml-side-item" data-source="own">我的上传 <span class="ct">0</span></div>
|
||||
<div class="ml-side-item active" data-source="all">全部 <span class="ct">${presets.length + owns.length}</span></div>
|
||||
<div class="ml-side-item" data-source="preset">平台预设 <span class="ct">${presets.length}</span></div>
|
||||
<div class="ml-side-item" data-source="own">我的上传 <span class="ct">${owns.length}</span></div>
|
||||
`;
|
||||
side.querySelectorAll('.ml-side-item').forEach(it => {
|
||||
it.addEventListener('click', () => {
|
||||
side.querySelectorAll('.ml-side-item').forEach(x => x.classList.remove('active'));
|
||||
it.classList.add('active');
|
||||
_curLibSource = it.dataset.source;
|
||||
_renderLibGrid();
|
||||
});
|
||||
});
|
||||
|
||||
// toolbar · chip groups + 上传按钮
|
||||
// toolbar · chip groups (去掉了 btn-up 上传按钮,改用网格内入口卡)
|
||||
const toolbar = document.getElementById('ml-toolbar');
|
||||
if (isModel) {
|
||||
toolbar.innerHTML = `
|
||||
@@ -1727,10 +2225,6 @@ const Stage2 = (function () {
|
||||
<button class="chip" type="button">青年</button>
|
||||
<button class="chip" type="button">中年</button>
|
||||
</div>
|
||||
<button class="btn-up" type="button" style="margin-left:auto">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4M17 8l-5-5-5 5M12 3v12"/></svg>
|
||||
上传我的演员
|
||||
</button>
|
||||
`;
|
||||
} else {
|
||||
toolbar.innerHTML = `
|
||||
@@ -1746,10 +2240,6 @@ const Stage2 = (function () {
|
||||
<button class="chip" type="button">日</button>
|
||||
<button class="chip" type="button">夜</button>
|
||||
</div>
|
||||
<button class="btn-up" type="button" style="margin-left:auto">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4M17 8l-5-5-5 5M12 3v12"/></svg>
|
||||
上传我的场景
|
||||
</button>
|
||||
`;
|
||||
}
|
||||
toolbar.querySelectorAll('.chip-group').forEach(group => {
|
||||
@@ -1760,36 +2250,9 @@ const Stage2 = (function () {
|
||||
});
|
||||
});
|
||||
});
|
||||
toolbar.querySelector('.btn-up')?.addEventListener('click', () => {
|
||||
Shell.toast('上传我的' + (isModel ? '演员' : '场景'), '占位 · 选择本地文件');
|
||||
});
|
||||
|
||||
// 卡片网格
|
||||
const grid = document.getElementById('ml-grid');
|
||||
grid.innerHTML = items.map(it => `
|
||||
<div class="ml-card" data-name="${it.name}" data-sub="${it.sub}">
|
||||
<div class="placeholder"><span class="ph-frame">${it.name}</span></div>
|
||||
<div class="ml-card-nm">${it.name}</div>
|
||||
<div class="ml-card-sub">${it.sub}</div>
|
||||
<div class="ml-card-foot">
|
||||
<span class="pill info"><span class="dot"></span>预设</span>
|
||||
<button class="btn-apply" type="button" data-apply>应用</button>
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
grid.querySelectorAll('.ml-card').forEach(card => {
|
||||
card.addEventListener('click', (e) => {
|
||||
const name = card.dataset.name;
|
||||
const sub = card.dataset.sub;
|
||||
if (e.target.closest('[data-apply]')) {
|
||||
e.stopPropagation();
|
||||
Shell.toast('已应用「' + name + '」', isModel ? '演员库 · 来自预设' : '场景库 · 来自预设');
|
||||
document.getElementById('ml-modal-bg').classList.remove('show');
|
||||
return;
|
||||
}
|
||||
openStripDetail(name, sub, kind);
|
||||
});
|
||||
});
|
||||
// 卡片网格(含 + 入口 + apply 绑定都在 _renderLibGrid 内完成)
|
||||
_renderLibGrid();
|
||||
|
||||
document.getElementById('ml-modal-bg').classList.add('show');
|
||||
}
|
||||
@@ -1860,11 +2323,12 @@ const Stage2 = (function () {
|
||||
el.addEventListener('click', () => bg.classList.remove('show'));
|
||||
});
|
||||
});
|
||||
// 详情 modal · 应用
|
||||
// 详情 modal · 应用 → 关详情 + 关演员/场景库 → 回到项目页
|
||||
document.getElementById('asset-detail-apply-btn')?.addEventListener('click', () => {
|
||||
const name = document.getElementById('asset-detail-title').textContent;
|
||||
Shell.toast('已应用「' + name + '」', '已加入当前项目');
|
||||
document.getElementById('asset-detail-modal').classList.remove('show');
|
||||
document.getElementById('ml-modal-bg')?.classList.remove('show');
|
||||
Shell.toast('已应用「' + name + '」', '已加入当前项目');
|
||||
});
|
||||
// 详情 modal · AI 生成三视图
|
||||
document.querySelectorAll('.ai-gen-btn').forEach(btn => {
|
||||
@@ -1892,21 +2356,24 @@ const Stage2 = (function () {
|
||||
});
|
||||
const thumbLbl = document.getElementById('asset-prod-thumb-label');
|
||||
if (thumbLbl) thumbLbl.textContent = CURRENT_PRODUCT_NAME + ' · 主图';
|
||||
// 商品卡 · AI 生成三视图 → 右侧 prod-preview 显示单张 16:9 三视图 + 历史版本
|
||||
// 商品卡 · AI 生成三视图 → 右侧 prod-preview · 预览/采用 双状态 + 点击主图放大
|
||||
(function setupProdPreview() {
|
||||
const aigenBtn = document.getElementById('asset-prod-aigen-btn');
|
||||
const pane = document.getElementById('asset-prod-preview');
|
||||
const img = document.getElementById('prod-preview-img');
|
||||
const statusEl = document.getElementById('prod-preview-status');
|
||||
const foot = document.getElementById('prod-preview-foot');
|
||||
const pill = document.getElementById('asset-prod-pill');
|
||||
const triBadge = document.getElementById('asset-prod-tri-badge');
|
||||
const prodAction = document.getElementById('asset-prod-action');
|
||||
const history = document.getElementById('prod-preview-history');
|
||||
const historyRow = document.getElementById('prod-preview-history-row');
|
||||
const historyCount = document.getElementById('prod-preview-history-count');
|
||||
if (!aigenBtn || !pane || !img || !statusEl || !foot || !history || !historyRow) return;
|
||||
|
||||
const versions = []; // [{ ts, label }]
|
||||
let currentIdx = -1; // 当前选中的版本:主图显示 + 缩略图高亮 + 商品资产即应用此版
|
||||
let previewIdx = -1; // 主图正在「预览」哪一版(浏览态,不动采用状态)
|
||||
let adoptedIdx = -1; // 真正被「采用」的那一版,决定商品资产生效版本
|
||||
let generating = false;
|
||||
|
||||
function prodName() {
|
||||
return CURRENT_PRODUCT_NAME || (document.getElementById('asset-prod-card-name')?.textContent ?? '商品');
|
||||
@@ -1919,43 +2386,73 @@ const Stage2 = (function () {
|
||||
}
|
||||
history.classList.add('show');
|
||||
historyCount.textContent = versions.length;
|
||||
historyRow.innerHTML = versions.map((ver, i) => `
|
||||
<div class="h-thumb${i === currentIdx ? ' active' : ''}" data-idx="${i}" title="${ver.label} · ${ver.ts}${i === currentIdx ? ' · 当前应用' : ''}">
|
||||
<span class="badge">当前</span>
|
||||
<span class="v">${ver.label}</span>
|
||||
</div>
|
||||
`).join('');
|
||||
historyRow.innerHTML = versions.map((ver, i) => {
|
||||
const isAdopted = i === adoptedIdx;
|
||||
const isPreview = i === previewIdx;
|
||||
const cls = [
|
||||
isAdopted ? 'adopted' : '',
|
||||
isPreview && !isAdopted ? 'previewing' : '',
|
||||
].filter(Boolean).join(' ');
|
||||
const titleParts = [ver.label, ver.ts];
|
||||
if (isAdopted) titleParts.push('已采用');
|
||||
else if (isPreview) titleParts.push('预览中');
|
||||
return `
|
||||
<div class="h-thumb ${cls}" data-idx="${i}" title="${titleParts.join(' · ')}">
|
||||
<span class="badge">已采用</span>
|
||||
<span class="v">${ver.label}</span>
|
||||
</div>
|
||||
`;
|
||||
}).join('');
|
||||
historyRow.querySelectorAll('.h-thumb').forEach(el => {
|
||||
el.addEventListener('click', () => {
|
||||
const idx = Number(el.dataset.idx);
|
||||
if (idx === currentIdx) return;
|
||||
setCurrent(idx, /* fromClick */ true);
|
||||
if (idx === previewIdx) return;
|
||||
setPreview(idx);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function renderMain() {
|
||||
if (currentIdx < 0) return;
|
||||
const ver = versions[currentIdx];
|
||||
if (previewIdx < 0) return;
|
||||
const ver = versions[previewIdx];
|
||||
const isAdopted = previewIdx === adoptedIdx;
|
||||
img.innerHTML = `<span class="ph-frame">${prodName()} · 三视图(正/侧/背) · ${ver.label}</span>`;
|
||||
statusEl.textContent = `${ver.label} · 已应用,不满意可重跑`;
|
||||
img.classList.add('is-zoomable');
|
||||
img.title = '点击放大查看';
|
||||
statusEl.textContent = isAdopted
|
||||
? `${ver.label} · 已采用,不满意可重跑`
|
||||
: `${ver.label} · 预览中(未采用)`;
|
||||
foot.innerHTML = `
|
||||
<button class="btn btn-ghost btn-sm" id="prod-preview-rerun">↻ 重跑</button>
|
||||
<button class="btn btn-sm ${isAdopted ? '' : 'btn-primary'}" id="prod-preview-adopt" ${isAdopted ? 'disabled title="此版本已采用"' : 'title="将此版本设为商品采用版本,其余转为不通过"'} style="display:inline-flex; align-items:center; gap:4px;">
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 12l5 5L20 6"/></svg>
|
||||
${isAdopted ? '已采用' : '采用此版本'}
|
||||
</button>
|
||||
<span class="spacer"></span>
|
||||
<span class="muted-2 mono" style="font-size:11px;">~¥0.30 / 次</span>
|
||||
`;
|
||||
document.getElementById('prod-preview-rerun')?.addEventListener('click', start);
|
||||
document.getElementById('prod-preview-adopt')?.addEventListener('click', adoptPreview);
|
||||
}
|
||||
|
||||
function setCurrent(idx, fromClick) {
|
||||
currentIdx = idx;
|
||||
const ver = versions[idx];
|
||||
// 商品状态徽标:选中任意版本即视为已三视图
|
||||
if (pill) {
|
||||
pill.className = 'pill ok';
|
||||
pill.innerHTML = '<span class="dot"></span>已三视图';
|
||||
}
|
||||
// 同步到商品详情 modal 的数据源 ASSET_DETAILS['prod-main']
|
||||
// 仅切预览主图,不动采用/不动商品资产
|
||||
function setPreview(idx) {
|
||||
previewIdx = idx;
|
||||
renderHistory();
|
||||
renderMain();
|
||||
}
|
||||
|
||||
// 显式「采用」当前预览版本 · 同步商品资产 + 隐藏缺三视图徽标
|
||||
function adoptPreview() {
|
||||
if (previewIdx < 0) return;
|
||||
if (previewIdx === adoptedIdx) return;
|
||||
adoptedIdx = previewIdx;
|
||||
applyAdoption(/* fromClick */ true);
|
||||
}
|
||||
|
||||
function applyAdoption(fromClick) {
|
||||
const ver = versions[adoptedIdx];
|
||||
if (triBadge) triBadge.hidden = true;
|
||||
const detail = ASSET_DETAILS['prod-main'];
|
||||
if (detail) {
|
||||
detail.hasTri = true;
|
||||
@@ -1963,34 +2460,57 @@ const Stage2 = (function () {
|
||||
detail.info = [
|
||||
['类别', '商品 · 当前项目'],
|
||||
['名称', prodName()],
|
||||
['三视图', '已生成 · ' + ver.label],
|
||||
['三视图', '已采用 · ' + ver.label],
|
||||
['状态', '已三视图'],
|
||||
];
|
||||
}
|
||||
renderHistory();
|
||||
renderMain();
|
||||
if (fromClick) Shell.toast('已切换 ' + ver.label, prodName() + ' · 商品资产已更新');
|
||||
if (fromClick) Shell.toast('已采用 ' + ver.label, prodName() + ' · 商品资产已更新为该版本');
|
||||
}
|
||||
|
||||
function renderLoading() {
|
||||
img.innerHTML = `<div style="display:flex;flex-direction:column;gap:6px;align-items:center;"><div class="spinner"></div><span class="ph-frame" style="font-size:10.5px;">生成中</span></div>`;
|
||||
img.classList.remove('is-zoomable');
|
||||
img.removeAttribute('title');
|
||||
statusEl.textContent = '生成中 · 约 12s';
|
||||
foot.innerHTML = '<span class="muted-2 mono" style="font-size:11px;">// POST /assets/tri-view</span>';
|
||||
aigenBtn.disabled = true;
|
||||
}
|
||||
|
||||
function start() {
|
||||
if (generating) return;
|
||||
generating = true;
|
||||
pane.classList.add('show');
|
||||
renderLoading();
|
||||
setTimeout(() => {
|
||||
generating = false;
|
||||
aigenBtn.disabled = false;
|
||||
const now = new Date();
|
||||
const ts = String(now.getHours()).padStart(2, '0') + ':' + String(now.getMinutes()).padStart(2, '0');
|
||||
versions.push({ ts, label: 'v' + (versions.length + 1) });
|
||||
setCurrent(versions.length - 1, /* fromClick */ false);
|
||||
const newVer = { ts, label: 'v' + (versions.length + 1) };
|
||||
versions.push(newVer);
|
||||
const newIdx = versions.length - 1;
|
||||
previewIdx = newIdx;
|
||||
if (adoptedIdx === -1) {
|
||||
adoptedIdx = newIdx;
|
||||
applyAdoption(/* fromClick */ false);
|
||||
} else {
|
||||
renderHistory();
|
||||
renderMain();
|
||||
Shell.toast('三视图已生成 ' + newVer.label, prodName() + ' · 预览中,满意请点「采用此版本」');
|
||||
}
|
||||
}, 1800);
|
||||
}
|
||||
|
||||
// 主图点击 → 放大查看
|
||||
img.addEventListener('click', (e) => {
|
||||
if (!img.classList.contains('is-zoomable')) return;
|
||||
if (previewIdx < 0) return;
|
||||
e.stopPropagation();
|
||||
openTriLightbox(versions[previewIdx], previewIdx === adoptedIdx, prodName());
|
||||
});
|
||||
|
||||
aigenBtn.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
start();
|
||||
@@ -2224,6 +2744,49 @@ window.Quota = (function () {
|
||||
|
||||
return { preflight };
|
||||
})();
|
||||
|
||||
/* ============================================================
|
||||
三视图 · 放大查看 lightbox · setupProdPreview 共用
|
||||
============================================================ */
|
||||
function openTriLightbox(ver, isAdopted, prodName) {
|
||||
let bg = document.getElementById('tri-lightbox-bg');
|
||||
if (!bg) {
|
||||
bg = document.createElement('div');
|
||||
bg.id = 'tri-lightbox-bg';
|
||||
bg.className = 'modal-bg';
|
||||
bg.innerHTML = `
|
||||
<div class="tri-lightbox" role="dialog" aria-label="三视图放大查看">
|
||||
<button class="tri-lightbox-close" type="button" aria-label="关闭">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6L6 18M6 6l12 12"/></svg>
|
||||
</button>
|
||||
<div class="tri-lightbox-head">
|
||||
// 三视图(正/侧/背) · <span class="lb-ver" id="tri-lightbox-label">v1</span>
|
||||
<span class="lb-tag" id="tri-lightbox-tag" hidden>已采用</span>
|
||||
</div>
|
||||
<div class="placeholder tri-lightbox-img" id="tri-lightbox-img"></div>
|
||||
<div class="tri-lightbox-foot">
|
||||
<span id="tri-lightbox-meta">// 生成于 --:--</span>
|
||||
<span class="spc"></span>
|
||||
<span><kbd>Esc</kbd> 关闭</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
document.body.appendChild(bg);
|
||||
bg.addEventListener('click', (e) => {
|
||||
if (e.target === bg) Shell.closeModal('tri-lightbox-bg');
|
||||
});
|
||||
bg.querySelector('.tri-lightbox-close')?.addEventListener('click', () => {
|
||||
Shell.closeModal('tri-lightbox-bg');
|
||||
});
|
||||
}
|
||||
bg.querySelector('#tri-lightbox-img').innerHTML =
|
||||
`<span class="ph-frame">${prodName} · 三视图(正/侧/背) · ${ver.label}</span>`;
|
||||
bg.querySelector('#tri-lightbox-label').textContent = ver.label;
|
||||
const tag = bg.querySelector('#tri-lightbox-tag');
|
||||
tag.hidden = !isAdopted;
|
||||
bg.querySelector('#tri-lightbox-meta').textContent = `// 生成于 ${ver.ts}`;
|
||||
Shell.openModal('tri-lightbox-bg');
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user