236 lines
67 KiB
HTML
236 lines
67 KiB
HTML
<!doctype html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||
<title>影策 · 第一期创作 Agent 交互设计</title>
|
||
<style>
|
||
:root { color-scheme:light; --yc-bg:#f1f1f1; --yc-surface:#fff; --yc-panel:#f7f7f7; --yc-hover:#efefef; --yc-text:#202020; --yc-muted:#727272; --yc-border:#e5e5e5; --yc-strong:#181818; --yc-on-strong:#fff; --yc-success:#26754a; --yc-success-bg:#eef8f1; --yc-warn:#976017; --yc-warn-bg:#fff7e9; --yc-shadow:0 16px 48px #0000000a; --yc-node-shadow:0 5px 18px #00000009; --yc-line:#b6b6b6; --yc-radius:14px; }
|
||
[data-theme="dark"] { color-scheme:dark; --yc-bg:#111112; --yc-surface:#1c1c1e; --yc-panel:#242426; --yc-hover:#303032; --yc-text:#eeeeef; --yc-muted:#a0a0a4; --yc-border:#363639; --yc-strong:#f5f5f5; --yc-on-strong:#191919; --yc-success:#82c9a0; --yc-success-bg:#203129; --yc-warn:#e1b974; --yc-warn-bg:#362e20; --yc-shadow:0 16px 48px #0004; --yc-node-shadow:0 5px 18px #0003; --yc-line:#626268; }
|
||
* { box-sizing:border-box; } body { margin:0; background:var(--yc-bg); color:var(--yc-text); font:14px/1.6 -apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC","Microsoft YaHei",sans-serif; }
|
||
button,input,textarea,select { font:inherit; } button { cursor:pointer; } button,input,textarea,select { color:inherit; } button:disabled { opacity:.38; cursor:not-allowed; } button:focus-visible,input:focus-visible,textarea:focus-visible,select:focus-visible { outline:2px solid var(--yc-strong); outline-offset:3px; } button { border:0; background:transparent; }
|
||
.review-bar { min-height:54px; padding:9px 22px; display:flex; align-items:center; justify-content:space-between; gap:15px; background:var(--yc-strong); color:var(--yc-on-strong); font-size:12px; }
|
||
.review-label { display:flex; align-items:center; gap:10px; flex-wrap:wrap; } .review-label b { font-size:12px; letter-spacing:.05em; } .review-label span { opacity:.76; }
|
||
.review-nav { display:flex; align-items:center; gap:5px; flex-wrap:wrap; } .review-nav select { max-width:155px; border:1px solid #8887; border-radius:7px; padding:4px 8px; background:transparent; color:inherit; } .review-nav option { color:var(--yc-text); background:var(--yc-surface); } .review-nav button { color:inherit; padding:4px 8px; border-radius:6px; }
|
||
.review-nav button:hover { background:#8884; } .app-shell { display:flex; padding:7px; gap:7px; height:calc(100dvh - 54px); min-height:640px; }
|
||
.app-sidebar { width:210px; flex-shrink:0; background:var(--yc-panel); border-radius:var(--yc-radius); padding:21px 13px 14px; display:flex; flex-direction:column; } .brand { display:flex; align-items:center; gap:10px; padding:0 10px 26px; font-size:19px; font-weight:650; letter-spacing:1px; } .brand-mark { font-size:33px; line-height:30px; letter-spacing:-3px; font-weight:500; } .brand small { font-size:10px; display:block; font-weight:400; color:var(--yc-muted); letter-spacing:.06em; margin-top:-3px; }
|
||
.side-action { width:100%; display:flex; align-items:center; gap:10px; padding:10px 12px; text-align:left; border-radius:9px; color:var(--yc-muted); margin-bottom:4px; } .side-action.active { color:var(--yc-text); background:var(--yc-surface); box-shadow:0 2px 6px #00000004; } .side-action:hover { background:var(--yc-hover); color:var(--yc-text); } .side-glyph { width:18px; text-align:center; font-size:17px; } .side-group { font-size:11px; color:var(--yc-muted); padding:25px 12px 8px; } .side-foot { margin-top:auto; padding:15px 10px 0; display:flex; align-items:center; gap:9px; } .avatar { display:grid; place-items:center; width:29px; height:29px; border-radius:50%; background:var(--yc-hover); font-size:11px; font-weight:600; } .side-foot small { display:block; color:var(--yc-muted); font-size:10px; }
|
||
.app-stage { position:relative; flex:1; min-width:0; display:flex; flex-direction:column; background:var(--yc-surface); border-radius:var(--yc-radius); overflow:hidden; box-shadow:var(--yc-shadow); } .app-topbar { height:57px; flex-shrink:0; display:flex; justify-content:space-between; align-items:center; padding:0 25px; gap:10px; } .breadcrumb { display:flex; align-items:center; gap:13px; font-size:12px; color:var(--yc-muted); } .breadcrumb strong { color:var(--yc-text); font-weight:500; } .top-actions { display:flex; align-items:center; gap:8px; } .mini-btn { display:inline-flex; justify-content:center; align-items:center; gap:6px; padding:6px 10px; border-radius:7px; font-size:12px; } .mini-btn:hover { background:var(--yc-hover); } .top-status { font-size:11px; color:var(--yc-muted); }
|
||
.content { flex:1; min-height:0; overflow:hidden; position:relative; } .home-page { overflow:auto; height:100%; padding:58px 30px 30px; } .home-inner { width:min(780px,100%); margin:0 auto; } .eyebrow { color:var(--yc-muted); font-size:11px; letter-spacing:.14em; } h1 { font-size:32px; font-weight:550; letter-spacing:-1px; margin:12px 0 8px; } .home-subtitle { font-size:14px; color:var(--yc-muted); margin:0 0 27px; } .scene-switch { display:flex; gap:7px; margin:26px 0 18px; flex-wrap:wrap; } .scene-switch button { padding:7px 16px; border-radius:22px; font-size:13px; color:var(--yc-muted); } .scene-switch button[aria-pressed="true"] { background:var(--yc-hover); color:var(--yc-text); font-weight:550; } .scene-switch button:hover { background:var(--yc-panel); }
|
||
.home-composer { border:1px solid var(--yc-border); border-radius:19px; background:var(--yc-surface); padding:18px; box-shadow:0 6px 24px #00000006; } .home-composer textarea { width:100%; height:86px; resize:none; border:0; outline:none; background:transparent; font-size:15px; line-height:1.8; } .composer-tools { display:flex; justify-content:space-between; align-items:center; gap:12px; margin-top:9px; } .composer-tool-left { display:flex; align-items:center; gap:6px; flex-wrap:wrap; } .soft-pill { padding:4px 9px; border-radius:6px; background:var(--yc-panel); color:var(--yc-muted); font-size:11px; } .primary { border-radius:9px; background:var(--yc-strong); color:var(--yc-on-strong); padding:9px 15px; font-size:13px; font-weight:500; } .primary:hover { opacity:.88; } .secondary { padding:8px 13px; border:1px solid var(--yc-border); background:var(--yc-surface); border-radius:8px; font-size:12px; } .secondary:hover { background:var(--yc-hover); } .text-button { padding:4px 2px; font-size:12px; color:var(--yc-muted); text-decoration:underline; text-underline-offset:4px; }
|
||
.home-examples { display:grid; grid-template-columns:repeat(3,1fr); gap:12px; margin:36px 0; } .example { text-align:left; padding:15px 17px; min-height:118px; border:1px solid var(--yc-border); border-radius:12px; background:var(--yc-panel); } .example:hover { border-color:var(--yc-muted); } .example span { font-size:11px; color:var(--yc-muted); } .example strong { display:block; font-size:13px; font-weight:500; margin:9px 0 5px; } .example p { margin:0; color:var(--yc-muted); font-size:11px; } .home-note { color:var(--yc-muted); font-size:11px; text-align:center; }
|
||
.conversation-only { height:100%; display:flex; flex-direction:column; max-width:780px; margin:0 auto; padding:0 20px; } .chat-area { display:flex; flex-direction:column; flex:1; min-height:0; position:relative; } .chat-scroll { overflow-y:auto; flex:1; min-height:0; padding:25px 3px 18px; scrollbar-width:thin; } .message { margin-bottom:24px; } .message.user { display:flex; justify-content:flex-end; } .user-bubble { max-width:87%; background:var(--yc-panel); padding:12px 16px; border-radius:13px 13px 3px 13px; font-size:13px; white-space:pre-wrap; } .assistant-label { font-weight:550; font-size:12px; display:flex; gap:7px; align-items:center; margin-bottom:9px; } .assistant-dot { width:20px; height:20px; display:grid; place-items:center; border-radius:6px; background:var(--yc-strong); color:var(--yc-on-strong); font-size:15px; line-height:1; } .assistant-text { font-size:13px; margin:0 0 13px; line-height:1.85; } .facts { display:flex; gap:6px; flex-wrap:wrap; margin:12px 0 16px; } .fact { font-size:10px; border-radius:5px; padding:3px 7px; background:var(--yc-panel); color:var(--yc-muted); } .fact::before { content:"✓ "; color:var(--yc-success); }
|
||
.question-card,.plan-card,.quote-card,.receipt-card { border:1px solid var(--yc-border); border-radius:12px; padding:18px; margin-top:11px; background:var(--yc-surface); } .card-overline { display:flex; align-items:center; justify-content:space-between; font-size:10px; color:var(--yc-muted); margin-bottom:12px; gap:10px; } .step-dots { display:flex; gap:4px; } .step-dots i { width:17px; height:3px; background:var(--yc-border); border-radius:2px; } .step-dots i.current { background:var(--yc-strong); } .question-title { font-size:16px; line-height:1.5; margin:5px 0 5px; font-weight:550; } .question-help { color:var(--yc-muted); font-size:11px; margin:0 0 14px; } .options { display:grid; gap:7px; } .option { display:flex; text-align:left; gap:10px; align-items:flex-start; width:100%; border:1px solid var(--yc-border); border-radius:8px; padding:10px 12px; } .option[aria-pressed="true"] { background:var(--yc-panel); border-color:var(--yc-strong); } .option-radio { flex-shrink:0; margin-top:4px; height:12px; width:12px; border-radius:50%; border:1px solid var(--yc-muted); } .option[aria-pressed="true"] .option-radio { border:4px solid var(--yc-strong); } .option strong { display:block; font-size:12px; font-weight:500; } .option small { display:block; color:var(--yc-muted); font-size:10px; margin-top:2px; } .free-label { display:block; font-size:11px; color:var(--yc-muted); margin:14px 0 6px; } .field { width:100%; border:1px solid var(--yc-border); border-radius:8px; padding:9px 11px; background:var(--yc-surface); font-size:12px; resize:vertical; min-height:40px; } .card-footer { display:flex; align-items:center; justify-content:space-between; gap:8px; margin-top:15px; } .card-footer .left,.card-footer .right { display:flex; gap:7px; align-items:center; } .quiet { color:var(--yc-muted); font-size:10px; } .submitted { display:flex; justify-content:space-between; align-items:center; gap:8px; background:var(--yc-panel); border-radius:8px; padding:11px 13px; font-size:12px; } .submitted span { color:var(--yc-success); } .plan-card h3 { font-size:20px; font-weight:550; margin:5px 0 10px; } .plan-card .logline { font-size:13px; line-height:1.85; margin:0 0 15px; } .plan-summary { display:grid; grid-template-columns:1fr 1fr; gap:10px 18px; margin:14px 0 17px; font-size:11px; } .plan-summary strong { display:block; font-weight:500; color:var(--yc-text); font-size:12px; } .plan-summary span { color:var(--yc-muted); } .scope-note { padding:10px 12px; background:var(--yc-panel); border-radius:7px; font-size:11px; line-height:1.75; }
|
||
.composer-wrap { position:relative; flex-shrink:0; padding:6px 0 15px; background:var(--yc-surface); } .chat-composer { border:1px solid var(--yc-border); border-radius:12px; padding:10px 12px; background:var(--yc-surface); } .chat-composer textarea { width:100%; border:0; outline:none; background:transparent; height:47px; resize:none; font-size:12px; padding:0; } .chat-composer .composer-tools { margin:1px 0 0; } .chat-composer .primary { padding:5px 11px; font-size:12px; } .progress-trigger { width:100%; display:flex; justify-content:space-between; align-items:center; gap:10px; padding:11px 4px; text-align:left; font-size:11px; } .progress-trigger .progress-info { display:flex; align-items:center; gap:7px; } .progress-track { height:3px; width:64px; background:var(--yc-border); border-radius:4px; overflow:hidden; } .progress-track span { display:block; height:100%; background:var(--yc-strong); border-radius:4px; transition:width .25s; } .progress-count { font-variant-numeric:tabular-nums; color:var(--yc-muted); font-size:10px; } .plan-popover { position:absolute; z-index:20; left:0; right:0; bottom:calc(100% - 4px); border:1px solid var(--yc-border); background:var(--yc-surface); border-radius:12px; padding:17px; box-shadow:var(--yc-shadow); } .plan-popover h3 { font-size:13px; margin:0 0 14px; display:flex; justify-content:space-between; align-items:center; } .plan-popover ol { padding:0; margin:0; list-style:none; } .plan-popover li { display:flex; gap:10px; align-items:flex-start; padding:8px 0; font-size:11px; } .plan-popover li strong { display:block; font-size:12px; font-weight:500; } .plan-popover li small { color:var(--yc-muted); font-size:10px; } .plan-indicator { width:20px; height:20px; border-radius:50%; background:var(--yc-panel); color:var(--yc-muted); display:grid; place-items:center; font-size:10px; margin-top:1px; } .plan-indicator.done { color:var(--yc-success); background:var(--yc-success-bg); } .plan-indicator.active { background:var(--yc-strong); color:var(--yc-on-strong); }
|
||
.canvas-layout { display:flex; height:100%; } .canvas-pane { flex:1; min-width:0; position:relative; overflow:hidden; background-color:var(--yc-panel); background-image:radial-gradient(var(--yc-line) .65px,transparent .65px); background-size:19px 19px; } .canvas-heading { position:absolute; left:22px; top:17px; z-index:3; max-width:calc(100% - 44px); } .canvas-heading strong { font-size:13px; font-weight:550; } .canvas-heading span { display:block; color:var(--yc-muted); font-size:10px; margin-top:2px; } .canvas-scroll { overflow:auto; height:100%; padding-top:65px; padding-bottom:70px; scrollbar-width:thin; } .canvas-scaled { width:840px; height:675px; position:relative; transform-origin:top left; } .canvas-size { position:relative; margin:0 22px; } .edges { position:absolute; inset:0; width:840px; height:675px; pointer-events:none; overflow:visible; } .edges path { stroke:var(--yc-line); stroke-width:1.4; fill:none; } .canvas-node { position:absolute; width:210px; background:var(--yc-surface); border:1px solid var(--yc-border); border-radius:10px; box-shadow:var(--yc-node-shadow); } .canvas-node.selected { outline:1.5px solid var(--yc-strong); outline-offset:3px; } .node-header { display:flex; align-items:center; gap:6px; padding:9px 10px; font-size:10px; min-height:35px; } .node-type { color:var(--yc-muted); font-size:11px; } .node-title { flex:1; font-weight:500; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; } .node-edit { padding:0 2px; font-size:12px; color:var(--yc-muted); } .node-edit:hover { color:var(--yc-text); } .node-port { position:absolute; border:2px solid var(--yc-surface); background:var(--yc-line); width:8px; height:8px; border-radius:50%; top:50%; left:-5px; } .node-port.out { right:-5px; left:auto; } .node-text { padding:0 12px 13px; font-size:11px; line-height:1.85; } .node-text h4 { font-size:14px; margin:1px 0 9px; font-weight:550; } .node-foot { padding:7px 10px; display:flex; justify-content:space-between; align-items:center; font-size:9px; color:var(--yc-muted); } .node-foot button { font-size:9px; color:var(--yc-text); } .node-empty { height:127px; margin:0 8px; border-radius:5px; background:var(--yc-panel); color:var(--yc-muted); display:flex; flex-direction:column; align-items:center; justify-content:center; font-size:10px; gap:5px; } .node-empty strong { font-size:17px; font-weight:400; color:var(--yc-line); } .node-empty.running { background:linear-gradient(110deg,var(--yc-panel),var(--yc-hover)); } .chat-pane { width:440px; flex-shrink:0; background:var(--yc-surface); padding:0 19px; border-left:1px solid var(--yc-border); display:flex; flex-direction:column; } .chat-pane-header { display:flex; justify-content:space-between; align-items:center; padding:16px 0 8px; font-size:12px; flex-shrink:0; } .chat-pane-header strong { font-weight:550; } .chat-pane .chat-scroll { padding-top:15px; } .chat-pane .question-card,.chat-pane .plan-card,.chat-pane .quote-card { padding:14px; } .chat-pane .message { margin-bottom:21px; } .canvas-tools { position:absolute; bottom:19px; left:20px; display:flex; gap:4px; padding:5px; border:1px solid var(--yc-border); border-radius:9px; background:var(--yc-surface); z-index:3; box-shadow:var(--yc-node-shadow); } .canvas-tools button { padding:5px 8px; font-size:11px; border-radius:5px; } .canvas-tools button:hover { background:var(--yc-hover); } .canvas-tools .divider { background:var(--yc-border); width:1px; margin:3px 5px; } .canvas-legend { position:absolute; bottom:21px; right:19px; font-size:10px; color:var(--yc-muted); padding:7px 9px; background:var(--yc-surface); border-radius:6px; } .mobile-toggle { display:none; }
|
||
.scene-art { height:127px; margin:0 8px; border-radius:5px; overflow:hidden; background:#24292b; position:relative; color:#d8dbd4; } .scene-art::before { content:""; position:absolute; left:58%; top:8%; width:26%; height:85%; background:linear-gradient(100deg,#526364,#b7bc9d); transform:skewY(-6deg); box-shadow:-4px 0 20px #a4baa32b; } .scene-art::after { content:""; position:absolute; width:43px; height:67px; background:#141b1f; left:35%; bottom:-5px; border-radius:42% 42% 0 0; box-shadow:2px 0 12px #0b1418; } .scene-art .head { position:absolute; z-index:1; height:20px; width:18px; border-radius:50%; left:calc(35% + 12px); bottom:57px; background:#131a1d; } .scene-art .clock-face { position:absolute; top:18%; left:14%; width:21px; height:21px; border:1px solid #a7b1a5; border-radius:50%; opacity:.7; } .clock-face::before { content:""; position:absolute; width:1px; height:7px; background:#a7b1a5; left:9px; top:3px; transform:rotate(35deg); transform-origin:bottom; } .scene-art .art-grain { position:absolute; inset:0; background:repeating-linear-gradient(0deg,#ffffff04 0,#ffffff04 1px,transparent 1px,transparent 3px); z-index:2; pointer-events:none; } .scene-art .art-label { position:absolute; z-index:3; left:7px; bottom:5px; font-size:8px; color:#e6e6df; background:#1118; padding:1px 5px; border-radius:3px; } .scene-art .play { position:absolute; z-index:4; left:calc(50% - 14px); top:calc(50% - 14px); width:28px; height:28px; border-radius:50%; background:#0007; color:#fff; display:grid; place-items:center; font-size:10px; } .scene-art.scene-2 { background:#353029; } .scene-art.scene-2::before { left:19%; top:48%; height:25%; width:60%; background:linear-gradient(10deg,#5e5943,#b5ae8d); transform:rotate(-8deg); } .scene-art.scene-2::after { left:59%; width:55px; height:85px; bottom:-10px; transform:rotate(-25deg); } .scene-art.scene-2 .head { display:none; } .scene-art.scene-2 .clock-face { width:32px; height:22px; left:38%; top:35%; border-radius:1px; transform:rotate(-9deg); border-color:#ddd7b8; } .scene-art.scene-3 { background:#232b2d; } .scene-art.scene-3::before { left:37%; width:31%; background:#b4bbb0; } .scene-art.scene-3::after { left:40%; bottom:-3px; height:68px; } .scene-art.scene-3 .head { left:calc(40% + 12px); bottom:61px; } .scene-art.scene-3 .clock-face { display:none; }
|
||
.quote-title { display:flex; justify-content:space-between; align-items:flex-start; margin-bottom:14px; gap:10px; } .quote-title strong { font-size:15px; font-weight:550; } .quote-title b { font-size:20px; letter-spacing:-.5px; } .quote-title small { font-size:10px; font-weight:400; color:var(--yc-muted); } .quote-row { display:flex; justify-content:space-between; gap:10px; font-size:11px; margin:8px 0; } .quote-row span { color:var(--yc-muted); } .quote-total { padding-top:12px; margin-top:12px; border-top:1px solid var(--yc-border); } .quote-note { font-size:10px; color:var(--yc-muted); margin:11px 0; line-height:1.8; } .quote-check { display:flex; align-items:flex-start; gap:7px; font-size:11px; margin:14px 0; } .quote-check input { accent-color:var(--yc-strong); margin-top:4px; } .success-note { color:var(--yc-success); font-size:11px; } .warning { color:var(--yc-warn); background:var(--yc-warn-bg); padding:11px 12px; border-radius:8px; font-size:11px; margin:10px 0; } .result-grid { display:grid; grid-template-columns:repeat(3,1fr); gap:7px; margin:12px 0; } .result-grid button { min-width:0; padding:0; text-align:left; } .result-grid .scene-art { margin:0; height:114px; } .result-grid small { display:block; font-size:9px; margin-top:5px; color:var(--yc-muted); } .receipt-card { font-size:11px; } .receipt-line { display:flex; justify-content:space-between; align-items:center; gap:10px; margin:8px 0; } .receipt-line span { color:var(--yc-muted); } .receipt-card .primary { font-size:11px; } .chat-more { display:flex; flex-wrap:wrap; gap:6px; margin-top:10px; }
|
||
.dialog { width:min(820px,calc(100% - 32px)); max-height:calc(100dvh - 44px); border:1px solid var(--yc-border); border-radius:16px; padding:0; background:var(--yc-surface); color:var(--yc-text); box-shadow:0 30px 120px #0003; } .dialog::backdrop { background:#1117; backdrop-filter:blur(3px); } .dialog-head { padding:19px 24px; display:flex; align-items:center; justify-content:space-between; gap:10px; border-bottom:1px solid var(--yc-border); } .dialog-head h2 { margin:0; font-size:15px; font-weight:550; } .dialog-body { overflow:auto; padding:23px 28px; max-height:calc(100dvh - 190px); } .dialog-body h3 { font-size:14px; font-weight:550; margin:23px 0 9px; } .dialog-body h3:first-child { margin-top:0; } .dialog-body p { font-size:12px; line-height:1.9; color:var(--yc-muted); } .dialog-foot { padding:16px 24px; display:flex; justify-content:space-between; gap:12px; align-items:center; border-top:1px solid var(--yc-border); } .dialog-foot div { display:flex; gap:8px; } .detail-meta { display:flex; gap:8px; flex-wrap:wrap; margin-bottom:16px; } .detail-meta span { font-size:10px; background:var(--yc-panel); padding:4px 8px; border-radius:5px; } .detail-assets { display:grid; grid-template-columns:repeat(3,1fr); gap:9px; } .detail-asset { background:var(--yc-panel); border-radius:8px; padding:12px; font-size:11px; } .detail-asset strong { display:block; font-size:12px; font-weight:500; margin-bottom:5px; } .detail-asset span { color:var(--yc-muted); } .shot-table { width:100%; border-collapse:collapse; font-size:11px; line-height:1.8; } .shot-table th { text-align:left; font-weight:500; color:var(--yc-muted); background:var(--yc-panel); padding:8px; } .shot-table td { padding:10px 8px; border-bottom:1px solid var(--yc-border); vertical-align:top; } .shot-table th:first-child { width:65px; } .shot-table td b { display:block; font-weight:500; } .error-text { color:var(--yc-warn); font-size:11px; min-height:17px; } .large-art { max-width:270px; margin:auto; } .large-art .scene-art { height:420px; margin:0; } .large-art .scene-art::after { width:85px; height:170px; } .large-art .scene-art .head { width:36px; height:44px; bottom:160px; left:calc(35% + 24px); } .large-art .scene-art .art-label { font-size:11px; padding:4px 8px; } .toast { position:fixed; z-index:100; bottom:25px; left:50%; transform:translateX(-50%); background:var(--yc-strong); color:var(--yc-on-strong); padding:11px 17px; border-radius:9px; font-size:12px; max-width:90%; box-shadow:var(--yc-shadow); }
|
||
[hidden] { display:none!important; } .sr-only { position:absolute; width:1px; height:1px; padding:0; margin:-1px; overflow:hidden; clip:rect(0,0,0,0); white-space:nowrap; border:0; }
|
||
@media(min-width:1700px) { .chat-pane { width:470px; padding:0 25px; } .home-page { padding-top:85px; } }
|
||
@media(max-width:1250px) { .app-sidebar { width:172px; } .chat-pane { width:420px; padding:0 16px; } .canvas-legend { display:none; } .home-page { padding-top:37px; } }
|
||
@media(max-width:1040px) { .app-sidebar { width:66px; padding:19px 7px 12px; } .brand { padding:0 7px 24px; } .brand-word,.side-action .nav-text,.side-group,.side-foot .account-text { display:none; } .side-action { justify-content:center; padding:10px; } .side-foot { padding-left:10px; } .chat-pane { width:405px; } .app-topbar { padding:0 18px; } }
|
||
@media(max-width:820px) { .review-bar { align-items:flex-start; padding:9px 13px; gap:7px; } .review-label { gap:4px; flex-direction:column; align-items:flex-start; } .review-label span { font-size:10px; } .review-nav { justify-content:flex-end; } .review-nav label { display:none; } .review-nav select { width:123px; font-size:11px; } .app-shell { height:calc(100dvh - 62px); min-height:590px; } .app-sidebar { display:none; } .app-topbar { height:50px; padding:0 13px; } .top-status { display:none; } .home-page { padding:35px 19px 24px; } h1 { font-size:27px; } .home-examples { gap:8px; } .example { padding:12px; min-height:133px; } .canvas-layout .chat-pane { width:100%; border:0; padding:0 20px; } .canvas-layout .canvas-pane { display:none; } .canvas-layout.show-canvas .canvas-pane { display:block; } .canvas-layout.show-canvas .chat-pane { display:none; } .mobile-toggle { display:inline-flex; } .conversation-only { padding:0 18px; } .canvas-heading { top:15px; } .dialog-body { padding:19px; } .detail-assets { grid-template-columns:1fr; } }
|
||
@media(max-width:480px) { .review-nav button { padding:4px 5px; font-size:11px; } .review-label b { font-size:10px; } .review-nav { gap:1px; } .home-page { padding-top:28px; } .home-examples { grid-template-columns:1fr; margin-top:22px; } .example { min-height:auto; padding:12px 14px; } .example strong { margin:3px 0; } .home-subtitle { font-size:12px; } .scene-switch { gap:3px; margin-top:19px; } .scene-switch button { padding:6px 12px; font-size:12px; } .composer-tool-left { gap:3px; } .home-composer { padding:14px; } .home-composer textarea { height:100px; font-size:13px; } .home-note { display:none; } .question-card,.plan-card { padding:14px; } .plan-summary { gap:8px; } .breadcrumb { gap:8px; } .breadcrumb .optional { display:none; } .card-footer { align-items:flex-start; } .dialog-foot { align-items:flex-start; padding:14px 18px; } .dialog-foot .quiet { max-width:42%; } .dialog-foot div { flex-wrap:wrap; justify-content:flex-end; } .shot-table { font-size:10px; } }
|
||
@media(prefers-reduced-motion:reduce) { *,*::before,*::after { transition:none!important; scroll-behavior:auto!important; } }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="review-bar" aria-label="设计评审控制,与产品界面分开">
|
||
<div class="review-label"><b>交互原型 · 示例数据</b><span>不会调用模型或扣费</span></div>
|
||
<div class="review-nav"><label for="review-state">评审状态</label><select id="review-state" aria-label="跳转评审状态"><option value="home">从首页体验</option><option value="questions">需求问答</option><option value="proposal">方案待确认</option><option value="quote">画布与费用确认</option><option value="running">生成中</option><option value="complete">结果与局部调整</option><option value="failure">结果回写失败</option><option value="casual">普通聊天</option></select><button data-action="theme" aria-label="切换明暗主题">浅 / 深</button><button data-action="reset">重置</button></div>
|
||
</div>
|
||
<div id="app" class="app-shell"></div>
|
||
<dialog id="detail-dialog" class="dialog" aria-labelledby="dialog-title"></dialog>
|
||
<div id="toast" class="toast" role="status" hidden></div>
|
||
<script>
|
||
(() => {
|
||
'use strict';
|
||
const root = document.getElementById('app');
|
||
const dialog = document.getElementById('detail-dialog');
|
||
const defaultPrompt = '帮我做一集 15 秒、9:16 的悬疑推理短片。雨夜,一位年轻侦探发现一封写着自己名字的信。';
|
||
const questions = [
|
||
{id:'hook',title:'故事的悬念,你更喜欢哪种方向?',hint:'已知是悬疑推理短片,只补充影响故事走向的选择。',options:[['时间循环','那封信来自未来的自己,结尾留下循环线索。'],['身份谜局','寄信人与侦探身份交错,制造一次认知反转。'],['无声追踪','门外脚步声逐渐逼近,用环境制造压迫感。']]},
|
||
{id:'style',title:'画面希望呈现什么质感?',hint:'这会影响角色设定、镜头提示词和后续画面生成。',options:[['写实电影感','冷色雨夜、克制灯光、真实人物,突出悬疑氛围。'],['黑白侦探片','强烈明暗对比与胶片颗粒,经典黑色电影风格。'],['悬疑动画','风格化人物与场景,更适合系列化内容。']]},
|
||
{id:'ending',title:'这 15 秒,你希望如何收尾?',hint:'可以选一个方向,也可以在下面直接写你的想法。',options:[['留一个钩子','门外出现与侦探完全相同的身影,故事戛然而止。'],['揭示一次反转','侦探打开信,发现每句话都在描述眼前的动作。'],['交给你决定','根据已有设定,选择最适合短片节奏的结尾。']]}
|
||
];
|
||
const initial = () => ({stage:'home',scene:'通用',prompt:defaultPrompt,answers:['','',''],extras:['','',''],question:0,version:1,planOpen:false,quoteAgreed:false,textApproved:false,canvasCreated:false,mobileCanvas:false,phase:0,selectedNode:'',nodeNames:{},messages:[],editNote:'',retry:false,restored:false,zoom:0.8});
|
||
let s = initial(); let timer = null; let toastTimer = null; let previousFocus = null;
|
||
const esc = v => String(v ?? '').replace(/[&<>"']/g,c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));
|
||
const btn = (text,action,cls='secondary',extra='') => `<button class="${cls}" data-action="${action}" ${extra}>${text}</button>`;
|
||
const referenceNames = ['角色参考 · 年轻侦探','场景参考 · 雨夜办公室','道具参考 · 神秘信件'];
|
||
const nodeName = id => s.nodeNames[id] || (id==='script'?'故事与视频方案':id.startsWith('video')?'雨夜来信 · 15 秒短片':referenceNames[Number(id.split('-')[1])-1]);
|
||
const art = (index=1,video=false) => `<div class="scene-art scene-${index}" role="img" aria-label="${video?'15 秒短片':referenceNames[index-1]}的抽象示意,并非真实生成素材"><i class="head"></i><i class="clock-face"></i><i class="art-grain"></i><span class="art-label">${video?'短片封面':'参考图片'}示意</span>${video?'<span class="play" aria-hidden="true">▶</span>':''}</div>`;
|
||
const toast = message => { clearTimeout(toastTimer); const el=document.getElementById('toast'); el.textContent=message; el.hidden=false; toastTimer=setTimeout(()=>el.hidden=true,2800); };
|
||
const stopTimer = () => {if(timer)clearInterval(timer);timer=null;};
|
||
function progress() {
|
||
if(s.stage==='questions')return {done:0,active:0,label:'完善创作需求'};
|
||
if(s.stage==='proposal')return {done:1,active:1,label:'等待确认创意方案'};
|
||
if(s.stage==='quote')return {done:s.retry?4:3,active:s.retry?4:3,label:s.retry?'等待视频重做授权':'等待确认生成费用'};
|
||
if(s.stage==='running')return {done:s.phase<2?3:s.phase<4?4:5,active:s.phase<2?3:s.phase<4?4:5,label:s.retry?'正在重做 15 秒短片':s.phase<2?'正在生成 3 张参考图片':s.phase<4?'正在生成 1 段 15 秒短片':'正在回写结果'};
|
||
if(s.stage==='failure')return {done:5,active:5,label:'产物已就绪 · 等待恢复回写'};
|
||
if(s.stage==='complete')return {done:6,active:-1,label:'创作已完成'};
|
||
return {done:0,active:-1,label:''};
|
||
}
|
||
function shell() {
|
||
const onCanvas=s.canvasCreated;
|
||
return `<aside class="app-sidebar"><div class="brand"><span class="brand-mark">∞</span><span class="brand-word">影策<small>创作工作台</small></span></div>
|
||
<button class="side-action ${!onCanvas?'active':''}" data-action="home"><span class="side-glyph">⌂</span><span class="nav-text">创作首页</span></button>
|
||
<button class="side-action" data-action="short-drama"><span class="side-glyph">▤</span><span class="nav-text">短剧工作台</span></button>
|
||
<button class="side-action ${onCanvas?'active':''}" data-action="canvas"><span class="side-glyph">▦</span><span class="nav-text">画布</span></button>
|
||
<button class="side-action" data-action="assets"><span class="side-glyph">▧</span><span class="nav-text">素材库</span></button>
|
||
<div class="side-group">最近创作</div><button class="side-action" data-action="resume"><span class="side-glyph">◷</span><span class="nav-text">雨夜来信</span></button>
|
||
<div class="side-foot"><span class="avatar">YC</span><span class="account-text">我的工作空间<small>个人创作</small></span></div></aside>
|
||
<main class="app-stage"><header class="app-topbar"><div class="breadcrumb"><span class="optional">影策</span><span class="optional">/</span><strong>${onCanvas?'雨夜来信':s.stage==='home'?'创作首页':'新创作'}</strong>${s.stage!=='home'?'<span class="soft-pill">悬疑短片</span>':''}</div><div class="top-actions">${onCanvas?'<span class="top-status">同一会话 · 5 个节点</span>':s.stage!=='home'?'<span class="top-status">画布节点 0</span>':''}${onCanvas?btn(s.mobileCanvas?'返回会话':'查看画布','mobile-canvas','mini-btn mobile-toggle'):''}${btn('新创作','new','mini-btn')}</div></header><div class="content">${s.stage==='home'?home():onCanvas?canvas():`<section class="conversation-only">${chat()}</section>`}</div></main>`;
|
||
}
|
||
function home() {
|
||
return `<section class="home-page"><div class="home-inner"><div class="eyebrow">从一个想法,到一份作品</div><h1>今天,想创作点什么?</h1><p class="home-subtitle">告诉我你的想法,我们一起把它变成画面。</p>
|
||
<div class="scene-switch" aria-label="创作场景">${['通用','短剧','营销','电商'].map(x=>`<button data-action="scene" data-scene="${x}" aria-pressed="${s.scene===x}">${x}</button>`).join('')}</div>
|
||
<form id="home-form" class="home-composer"><label for="home-prompt" class="sr-only">描述你的创作需求</label><textarea id="home-prompt" placeholder="描述你想创作的内容,也可以添加参考素材…">${esc(s.prompt)}</textarea><div class="composer-tools"><div class="composer-tool-left">${btn('+ 素材','attach','mini-btn')}<span class="soft-pill">Agent 自动</span><span class="soft-pill">技能 · 短片创作</span></div><button type="submit" class="primary">开始创作 ↑</button></div></form>
|
||
<div class="home-examples"><button class="example" data-action="example" data-example="short"><span>短剧 / 影视</span><strong>15 秒,讲一个悬疑故事</strong><p>从创意、参考素材到完整短片</p></button><button class="example" data-action="example" data-example="marketing"><span>营销创作</span><strong>把一个卖点讲得更动人</strong><p>从受众、文案到视觉方案</p></button><button class="example" data-action="example" data-example="commerce"><span>电商设计</span><strong>让商品成为画面的主角</strong><p>从商品素材到场景图组</p></button></div><p class="home-note">本次完整演示:悬疑短片。营销、电商入口展示后续复用位置。</p></div></section>`;
|
||
}
|
||
function userMessage(text) {return `<div class="message user"><div class="user-bubble">${esc(text)}</div></div>`;}
|
||
function assistant(body) {return `<div class="message assistant"><div class="assistant-label"><span class="assistant-dot">∞</span>影策</div>${body}</div>`;}
|
||
function questionCard() {
|
||
const q=questions[s.question];
|
||
return `<div class="question-card"><div class="card-overline"><span>补充创作需求 · ${s.question+1} / ${questions.length}</span><span class="step-dots">${questions.map((_,i)=>`<i class="${i<=s.question?'current':''}"></i>`).join('')}</span></div><h3 class="question-title">${q.title}</h3><p class="question-help">${q.hint}</p><div class="options">${q.options.map(([title,desc])=>`<button class="option" data-action="answer" data-value="${esc(title)}" aria-pressed="${s.answers[s.question]===title}"><i class="option-radio"></i><span><strong>${title}</strong><small>${desc}</small></span></button>`).join('')}</div><label class="free-label" for="answer-extra">自定义或补充要求</label><textarea class="field" id="answer-extra" rows="2" placeholder="也可以直接写你的想法…">${esc(s.extras[s.question])}</textarea><div id="question-error" class="error-text" aria-live="polite"></div><div class="card-footer"><div class="left">${btn('上一题','prev-question','mini-btn',s.question===0?'disabled':'')}</div><div class="right">${btn(s.question===2?'提交需求':'下一题 →',s.question===2?'submit-answers':'next-question','primary')}</div></div></div>`;
|
||
}
|
||
function submitted() {return `<div class="submitted"><div><span>✓ 已提交</span> · ${esc(s.answers[0]||'自定义悬念')} / ${esc(s.answers[1]||'自定义风格')}</div>${btn('查看 / 修改','edit-answers','text-button')}</div>`;}
|
||
function titleText(){ return s.answers[0]==='身份谜局'?'另一位侦探':s.answers[0]==='无声追踪'?'门外的脚步':'雨夜来信'; }
|
||
function story(){ return s.answers[0]==='身份谜局'?'雨夜,侦探收到一封署着自己名字的信。信中照片上的人正坐在同一张书桌前。门开了,对方称这里是自己的办公室。':s.answers[0]==='无声追踪'?'雨夜,侦探收到一封写着自己名字的信。拆信时,门外的脚步与他的心跳同步。信纸上只有一句:不要让门外的人知道你在这里。':'雨夜,年轻侦探收到一封写着自己名字的信。信里警告:不要开门。敲门声响起,他透过门缝看见的,却是另一个自己。'; }
|
||
function proposalCard(compact=false) {
|
||
return `<div class="plan-card"><div class="card-overline"><span>创意方案 · v${s.version}</span><span>${s.canvasCreated?'已确认版本可回看':'等待你的确认'}</span></div><h3>《${titleText()}》</h3><p class="logline">${story()}</p>${compact?'':`<div class="plan-summary"><div><strong>1 集 · 15 秒 · 9:16</strong><span>一段视频,分为 3 个叙事节奏</span></div><div><strong>${esc(s.answers[1]||'写实电影感')}</strong><span>冷色雨夜 / 克制表演</span></div><div><strong>角色、场景与道具</strong><span>年轻侦探 / 旧办公室 / 神秘信件</span></div><div><strong>预期产物</strong><span>3 张参考图 + 1 段 15 秒短片</span></div></div>`}${s.editNote?`<p class="quiet">本版调整:${esc(s.editNote)}</p>`:''}<div class="scope-note">${s.canvasCreated?'这是已确认的创意方向。修改后会生成新版本,再确认受影响的节点。':'确认后创建 1 个方案文本节点、3 个参考图片节点、1 个视频节点及连线。图片和视频生成费用另行确认。'}</div><div class="card-footer"><div class="left">${btn('查看完整方案','view-plan','text-button')}</div><div class="right">${!s.canvasCreated?btn('修改','edit-plan','secondary')+btn('确认方案','confirm-plan','primary'):''}</div></div></div>`;
|
||
}
|
||
function quote() {
|
||
const retry=s.retry;
|
||
return `<div class="quote-card"><div class="card-overline"><span>${retry?'视频重做授权':'生成授权'}</span><span>示例报价 · 非真实计价</span></div><div class="quote-title"><strong>${retry?'仅重做 15 秒短片':'生成参考图与 15 秒短片'}</strong><div><b>${retry?'72':'84'}</b><small> 示例积分</small></div></div>${retry?'<div class="quote-row"><span>复用 3 张已完成参考图</span><b>0 新增积分</b></div>':'<div class="quote-row"><span>角色 / 场景 / 道具参考图 · 示例图像模型</span><b>3 × 4 = 12</b></div>'}<div class="quote-row"><span>15 秒短片 · 示例视频模型</span><b>1 × 72 = 72</b></div><div class="quote-row quote-total"><span>本次授权上限</span><b>${retry?'72':'84'} 示例积分</b></div><p class="quote-note">${retry?'仅重新生成视频;原视频保留为上一版,3 张参考图保持原样。':'先生成角色、场景、道具参考图,再按视频模型已验证的参考输入能力,生成一段 15 秒视频。'} 不包含自动重试、额外变体或后续批次。模型、数量或费用变化后需要重新确认。</p><label class="quote-check"><input type="checkbox" id="quote-agree" ${s.quoteAgreed?'checked':''}>同意上述生成范围和积分上限</label><div class="card-footer"><span class="quiet">未授权前不提交生成任务</span>${btn('确认并生成','approve','primary',s.quoteAgreed?'':'disabled')}</div></div>`;
|
||
}
|
||
function result() {
|
||
return `<p class="assistant-text">${s.restored?'15 秒短片已恢复到原视频节点。没有重新请求模型,也没有新增生成费用。':s.retry?'15 秒短片的新版本已完成,原视频和 3 张参考图保留。':'3 张参考图和 1 段 15 秒短片已完成,结果已回写到对应节点。'}</p><button data-action="preview" data-kind="video" data-shot="1" style="display:block;width:100%;padding:0">${art(1,true)}</button><p class="quiet">《${titleText()}》· 15 秒 · 9:16${s.retry?' · v2':''} · 视频封面示意</p><div class="result-grid">${[1,2,3].map(i=>`<button data-action="preview" data-kind="image" data-shot="${i}">${art(i,false)}<small>${referenceNames[i-1]}</small></button>`).join('')}</div><div class="receipt-card"><div class="receipt-line"><b>本轮结果</b><span>示例数据</span></div><div class="receipt-line"><span>画布结果</span><strong>已回写 4 项产物</strong></div><div class="receipt-line"><span>本次示例积分</span><strong>${s.retry?'72':'84'}</strong></div><div class="receipt-line"><span>视频交付</span><strong>1 段可预览的 15 秒短片</strong></div></div><div class="chat-more">${btn('只重做视频','redo','secondary')}${btn('预览短片','preview','secondary','data-kind="video" data-shot="1"')}${btn('继续调整节点','edit-node-prompt','secondary')}</div>`;
|
||
}
|
||
function chat() {
|
||
let body='';
|
||
if(s.stage==='casual') {
|
||
body=userMessage('悬疑短片和恐怖短片有什么区别?')+assistant('<p class="assistant-text">悬疑短片主要让人想知道“真相是什么”,靠线索、信息差和反转推动;恐怖短片更强调威胁感与恐惧体验。</p><p class="assistant-text">你这类 15 秒短片可以先给一个异常线索,再用最后一个镜头改变观众的理解。比如,一封来自未来的信。</p>');
|
||
} else {
|
||
body=userMessage(s.prompt)+assistant(`<p class="assistant-text">我已经记下这几个条件,接下来只补充会影响创作方向的信息。</p><div class="facts"><span class="fact">悬疑推理</span><span class="fact">1 集 · 15 秒</span><span class="fact">9:16 竖屏</span><span class="fact">雨夜 · 年轻侦探</span></div>${s.stage==='questions'?questionCard():submitted()}`);
|
||
if(s.stage!=='questions') body+=assistant(`<p class="assistant-text">根据你的选择,我整理了一个适合 15 秒表达的故事。先看看方向是否符合预期。</p>${proposalCard(s.canvasCreated)}`);
|
||
if(s.canvasCreated)body+=assistant('<p class="assistant-text">方案已确认,画布已创建 5 个节点及 6 条连线:方案文本、角色参考、场景参考、道具参考,以及 15 秒短片。节点中的提示词可以继续调整。</p>');
|
||
if(s.stage==='quote')body+=assistant(quote());
|
||
if(s.stage==='running')body+=assistant(`<p class="assistant-text">已获得${s.retry?'仅重做视频':'本批次'}授权。${s.phase<2?'正在生成角色、场景、道具参考图。':s.phase<4?'参考图已就绪,正在生成一段 15 秒短片。':'媒体已生成,正在回写画布结果。'}</p><div class="receipt-card"><div class="receipt-line"><span>当前步骤</span><strong>${s.phase<2?'生成参考图片':s.phase<4?'生成 15 秒短片':'回写结果'}</strong></div><div class="receipt-line"><span>后续操作</span><strong>${s.phase<2?'参考图片成功后才开始视频':s.phase<4?'视频完成后检查并回写':'绑定已有产物到原节点'}</strong></div><p class="quiet">评审原型正在按预设状态演示;没有真实任务。</p>${btn('暂停演示','pause','secondary')}</div>`);
|
||
if(s.stage==='failure')body+=assistant(`<p class="assistant-text">3 张参考图和 1 段 15 秒短片已经生成,但视频产物未能回写到画布。</p><div class="warning">回写中断 · 已有产物和任务记录保留。恢复时只绑定已有产物,不重新生成。</div><div class="receipt-card"><div class="receipt-line"><span>生成任务</span><strong>4 / 4 已完成</strong></div><div class="receipt-line"><span>画布回写</span><strong>3 / 4 已完成</strong></div><div class="receipt-line"><span>本次恢复新增生成费</span><strong>0 示例积分</strong></div>${btn('恢复结果回写','restore','primary')}</div>`);
|
||
if(s.stage==='complete')body+=assistant(result());
|
||
}
|
||
body+=s.messages.map(m=>m.role==='user'?userMessage(m.text):assistant(`<p class="assistant-text">${esc(m.text)}</p>`)).join('');
|
||
return `<div class="chat-area"><div class="chat-scroll" id="chat-scroll">${body}</div>${composer()}</div>`;
|
||
}
|
||
function composer() {
|
||
const p=progress(),hasTask=s.stage!=='casual'&&s.stage!=='home';
|
||
return `<div class="composer-wrap">${hasTask?`${s.planOpen?planPopover():''}<button class="progress-trigger" data-action="toggle-plan" aria-expanded="${s.planOpen}" aria-controls="execution-plan"><span class="progress-info"><span>${p.done===6?'✓':'◷'}</span>${p.label}</span><span class="progress-info"><span class="progress-track"><span style="width:${p.done/6*100}%"></span></span><span class="progress-count">${p.done}/6</span><span>${s.planOpen?'⌄':'⌃'}</span></span></button>`:''}<form class="chat-composer" id="chat-form"><label class="sr-only" for="chat-input">继续补充或调整创作需求</label><textarea id="chat-input" placeholder="${s.stage==='questions'?'也可以直接补充你的想法…':s.stage==='complete'?'继续调整,例如:把场景节点改名为「雨夜旧办公室」':'继续补充想法,或调整已有方案…'}"></textarea><div class="composer-tools"><div class="composer-tool-left">${btn('+','attach','mini-btn','aria-label="添加素材"')}<span class="soft-pill">Agent · 自动</span></div><button class="primary" type="submit" aria-label="发送消息">↑</button></div></form></div>`;
|
||
}
|
||
function planPopover() {
|
||
const p=progress(); const labels=[['完善创作需求','从上下文提取已有条件,只补问缺失项'],['确认创意方案','故事、设定、叙事节奏与创建范围'],['创建画布节点','5 个节点及 6 条连线'],['生成参考图片','角色、场景、道具各 1 张'],['生成 15 秒短片','按视频能力引用参考素材,直接生成一段视频'],['检查并回写结果','按任务回执和节点结果确认完成']];
|
||
return `<section id="execution-plan" class="plan-popover" aria-label="创作执行计划"><h3>创作计划 <span class="quiet">${p.done}/6</span>${btn('收起','toggle-plan','mini-btn')}</h3><ol>${labels.map(([name,desc],i)=>`<li><span class="plan-indicator ${i<p.done?'done':i===p.active?'active':''}">${i<p.done?'✓':i+1}</span><span><strong>${name}${i===p.active&&s.stage==='failure'?' · 等待恢复':''}</strong><small>${desc}</small></span></li>`).join('')}</ol></section>`;
|
||
}
|
||
function nodeStatus(index,video) {
|
||
if(s.stage==='complete')return 'done';
|
||
if(s.stage==='failure')return video?'failure':'done';
|
||
if(s.stage==='running') {
|
||
if(s.retry&&!video)return 'done';
|
||
if(!video)return s.phase>=2?'done':'running';
|
||
return s.phase>=4?'done':s.phase>=2?'running':'empty';
|
||
}
|
||
if(s.retry)return 'done';
|
||
return 'empty';
|
||
}
|
||
function mediaNode(i,video) {
|
||
const id=`${video?'video':'image'}-${i}`,status=nodeStatus(i,video); const name=nodeName(id); const y=video?234:23+(i-1)*211;
|
||
return `<article class="canvas-node ${s.selectedNode===id?'selected':''}" data-node="${id}" style="left:${video?567:278}px;top:${y}px;width:210px"><span class="node-port"></span>${video?'':'<span class="node-port out"></span>'}<header class="node-header"><span class="node-type">${video?'▷':'▧'}</span><span class="node-title">${esc(name)}</span><button class="node-edit" data-action="rename" data-id="${id}" aria-label="修改${esc(name)}名称">✎</button></header>${status==='done'?`<button data-action="preview" data-kind="${video?'video':'image'}" data-shot="${i}" style="display:block;padding:0;width:100%">${art(i,video)}</button>`:`<div class="node-empty ${status==='running'?'running':''}"><strong>${status==='failure'?'!':status==='running'?'◷':video?'▷':'▧'}</strong><span>${status==='failure'?'已有结果,等待恢复回写':status==='running'?'生成中 · 演示状态':'等待授权生成'}</span><span class="quiet">${video?'15 秒 · 9:16 · 完整短片':'9:16 · 参考图片'}</span></div>`}<footer class="node-foot"><span>${status==='done'?'✓ 示例产物已回写':status==='failure'?'回写待恢复':status==='running'?'任务已接受':'提示词已填充'}</span><button data-action="node-detail" data-id="${id}">查看参数</button></footer></article>`;
|
||
}
|
||
function canvas() {
|
||
return `<section class="canvas-layout ${s.mobileCanvas?'show-canvas':''}"><div class="canvas-pane"><div class="canvas-heading"><strong>《${titleText()}》</strong><span>方案 v${s.version} · 方案文本 → 角色 / 场景 / 道具参考 → 15 秒短片</span></div><div class="canvas-scroll" id="canvas-scroll"><div class="canvas-size" id="canvas-size"><div class="canvas-scaled" id="canvas-scaled"><svg class="edges" viewBox="0 0 840 675" role="img" aria-label="方案文本连接角色、场景、道具参考图片;三个已支持的参考输入汇入同一个 15 秒视频节点"><path d="M220 332 C247 332 248 118 278 118"/><path d="M220 332 C247 332 248 329 278 329"/><path d="M220 332 C247 332 248 540 278 540"/>${[118,329,540].map(y=>`<path d="M488 ${y} C525 ${y} 530 329 567 329"/>`).join('')}</svg><article class="canvas-node" style="left:20px;top:223px;width:200px"><span class="node-port out"></span><header class="node-header"><span class="node-type">T</span><span class="node-title">${esc(nodeName('script'))}</span><button class="node-edit" data-action="rename" data-id="script" aria-label="修改方案节点名称">✎</button></header><div class="node-text"><h4>${titleText()}</h4>${story()}<div class="facts"><span class="fact">15 秒</span><span class="fact">9:16</span></div><span class="quiet">片内节奏:信件出现 → 信中警告 → 门外的自己</span></div><footer class="node-foot"><span>方案 v${s.version}</span><button data-action="view-plan">查看方案</button></footer></article>${[1,2,3].map(i=>mediaNode(i,false)).join('')}${mediaNode(1,true)}</div></div></div><div class="canvas-tools">${btn('-','zoom-out','')}${btn(`${Math.round(s.zoom*100)}%`,'fit','', 'id="zoom-label"')}${btn('+','zoom-in','')}<span class="divider"></span>${btn('适应画布','fit','')}${btn('整理节点','arrange','')}</div><span class="canvas-legend">图片、视频均为示意</span></div><aside class="chat-pane"><div class="chat-pane-header"><strong>创作助手</strong><span class="quiet">接续首页会话</span></div>${chat()}</aside></section>`;
|
||
}
|
||
function resizeCanvas(fit=false) {
|
||
const pane=root.querySelector('.canvas-pane'); const scaled=document.getElementById('canvas-scaled'); const size=document.getElementById('canvas-size');
|
||
if(!pane||!scaled||!size)return;
|
||
if(fit&&pane.clientWidth>0)s.zoom=Math.min(1,Math.max(.35,(pane.clientWidth-46)/840));
|
||
scaled.style.transform=`scale(${s.zoom})`;size.style.width=`${840*s.zoom}px`;size.style.height=`${675*s.zoom}px`;
|
||
const zoomLabel=document.getElementById('zoom-label');if(zoomLabel)zoomLabel.textContent=`${Math.round(s.zoom*100)}%`;
|
||
}
|
||
function render({bottom=false,fit=false}={}) {
|
||
const oldScroll=document.getElementById('chat-scroll'); const y=oldScroll?.scrollTop||0;root.innerHTML=shell();document.getElementById('review-state').value=s.stage;resizeCanvas(fit);
|
||
const scroll=document.getElementById('chat-scroll');if(scroll)scroll.scrollTop=bottom?scroll.scrollHeight:y;
|
||
}
|
||
function showDialog(title,body,foot='') {
|
||
previousFocus=document.activeElement;dialog.innerHTML=`<div class="dialog-head"><h2 id="dialog-title">${title}</h2>${btn('关闭 ×','close-dialog','mini-btn','aria-label="关闭弹窗"')}</div><div class="dialog-body">${body}</div>${foot?`<div class="dialog-foot">${foot}</div>`:''}`;if(!dialog.open)dialog.showModal();
|
||
}
|
||
function closeDialog(){dialog.close();previousFocus?.focus?.();}
|
||
function planDetail() {
|
||
showDialog(`《${titleText()}》· 创意方案 v${s.version}`,`<div class="detail-meta"><span>1 段视频 · 15 秒</span><span>9:16 竖屏</span><span>${esc(s.answers[1]||'写实电影感')}</span><span>3 个片内叙事节奏</span></div><h3>故事大纲</h3><p>${story()} 通过一封信和一次开门动作,在 15 秒内完成“发现异常—危险逼近—身份反转”。</p><h3>角色、场景与道具参考</h3><div class="detail-assets"><div class="detail-asset"><strong>角色 · 年轻侦探</strong><span>约 28 岁,短发、深色风衣,神情克制。全片沿用同一人物参考。</span></div><div class="detail-asset"><strong>场景 · 旧办公室</strong><span>雨夜、窄窗、木桌、走廊门,冷色环境中保留一盏暖灯。</span></div><div class="detail-asset"><strong>道具 · 神秘信件</strong><span>米白信封、黑色手写字迹。文字与声音的精确表现需以模型实际能力为准。</span></div></div><h3>一段 15 秒视频内部的节奏</h3><p>以下时间段共同写入一次视频生成的创作描述,属于同一段视频内部的叙事安排。</p><table class="shot-table"><thead><tr><th>时间</th><th>画面与动作</th><th>镜头语言 / 声音意图</th></tr></thead><tbody><tr><td>00–05s</td><td><b>01 · 信件出现</b>侦探在雨夜办公室发现写着自己名字的信封。</td><td>中景缓慢推进。雨声与室内钟声。</td></tr><tr><td>05–10s</td><td><b>02 · 信中警告</b>手部拆信,读到警告,敲门声突然响起。</td><td>信纸特写,焦点切到门口。</td></tr><tr><td>10–15s</td><td><b>03 · 门外的自己</b>门缓缓打开,同样衣着的身影站在门外,停在揭示瞬间。</td><td>越肩视角,保持两人外观一致。</td></tr></tbody></table><h3>本轮产物与确认范围</h3><p>创建 1 个方案文本节点、3 个参考图片节点、1 个视频节点及 6 条连线。先生成角色、场景、道具参考图,再引用已确认方案与模型支持的参考输入,直接生成 1 段可预览的 15 秒短片。</p><div class="scope-note">本例以已验证支持 15 秒和所需参考输入的视频模型为前提;具体参考数量、声音及文字效果以所选模型能力为准。未满足时先调整方案和报价,不提交不支持的任务。查看方案不会创建或更改画布节点。</div>${s.extras.some(Boolean)?`<h3>你的补充要求</h3>${s.extras.filter(Boolean).map(x=>`<p>${esc(x)}</p>`).join('')}`:''}${s.editNote?`<h3>本版调整</h3><p>${esc(s.editNote)}</p>`:''}`,`<span class="quiet">${s.canvasCreated?'节点已按已确认版本创建':'画布节点:0 · 等待确认'}</span><div>${btn('修改方案','edit-plan','secondary')}${!s.canvasCreated?btn('确认方案,进入画布','confirm-plan','primary'):btn('返回画布','close-dialog','primary')}</div>`);
|
||
}
|
||
function editPlan() { showDialog('调整创意方案',`<p>说明你想修改的部分。更新后会展示新版本,重新确认受影响的范围。</p><label class="free-label" for="plan-edit">修改要求</label><textarea id="plan-edit" class="field" rows="5" placeholder="例如:第二镜头突出信纸细节,结尾只露出门外人的轮廓。">${esc(s.editNote)}</textarea><p class="quiet">原型会记录调整说明并生成新版本;不会调用模型改写文本。</p>`,`<span class="quiet">暂不更改画布</span><div>${btn('取消','close-dialog','secondary')}${btn('更新方案','save-plan','primary')}</div>`); }
|
||
function seed(stage) {
|
||
stopTimer();s=initial();s.stage=stage;
|
||
if(['proposal','quote','running','complete','failure'].includes(stage)){s.answers=['时间循环','写实电影感','留一个钩子'];s.canvasCreated=['quote','running','complete','failure'].includes(stage);}
|
||
if(stage==='running')s.phase=2;
|
||
render({bottom:stage!=='home'&&stage!=='questions',fit:true});
|
||
}
|
||
function startRun() {stopTimer();s.stage='running';s.phase=s.retry?2:0;s.quoteAgreed=false;render({bottom:true});timer=setInterval(()=>{if(s.stage!=='running'){stopTimer();return;}s.phase+=1;if(s.phase>=5){stopTimer();s.stage='complete';s.planOpen=false;}render({bottom:true});},1900);}
|
||
function answerSave() {const el=document.getElementById('answer-extra');if(el)s.extras[s.question]=el.value.trim();}
|
||
function answerValid() {answerSave();if(s.answers[s.question]||s.extras[s.question])return true;document.getElementById('question-error').textContent='选择一个方向,或输入你的想法后继续。';return false;}
|
||
function sendChat(text) {
|
||
if(!text.trim())return;s.messages.push({role:'user',text:text.trim()});
|
||
if(s.stage==='questions') {s.extras[s.question]=text.trim();s.messages.push({role:'assistant',text:'已记录到当前问题的补充要求。你可以直接继续下一题。'});}
|
||
else if(s.canvasCreated&&/改名|命名/.test(text)){s.nodeNames['image-2']='场景参考 · 雨夜旧办公室';s.selectedNode='image-2';s.messages.push({role:'assistant',text:'已将场景参考节点改名为“场景参考 · 雨夜旧办公室”。这是节点文字调整,没有触发重新生成。'});}
|
||
else if(s.stage==='proposal') {s.editNote=text.trim();s.version+=1;s.messages.push({role:'assistant',text:`已记录方案调整,更新为 v${s.version},请查看并确认。原型仅演示版本与确认状态。`});}
|
||
else {s.messages.push({role:'assistant',text:s.canvasCreated?'已收到。这个原型可以演示节点改名、查看参数、局部重做和回写恢复;自由对话理解将在接入现有模型工具循环后实现。':'悬疑通常用线索和反转建立吸引力。普通聊天不创建任务,所以不会出现执行计划。'});}
|
||
render({bottom:true});
|
||
}
|
||
document.addEventListener('click',event=>{
|
||
const button=event.target.closest('[data-action]');if(!button)return;const a=button.dataset.action;
|
||
if(a==='theme'){const dark=document.documentElement.dataset.theme==='dark';document.documentElement.dataset.theme=dark?'light':'dark';return;}
|
||
if(a==='reset'||a==='new'){if(dialog.open)closeDialog();seed('home');return;}
|
||
if(a==='home'){stopTimer();s.stage='home';s.canvasCreated=false;s.messages=[];render();return;}
|
||
if(a==='scene'){s.prompt=document.getElementById('home-prompt')?.value||s.prompt;s.scene=button.dataset.scene;render();return;}
|
||
if(a==='example'){if(button.dataset.example==='short'){s.prompt=defaultPrompt;s.scene='短剧';render();document.getElementById('home-prompt').focus();}else toast('本次聚焦短片完整链路;该场景沿用同一输入和问答框架。');return;}
|
||
if(a==='short-drama'){toast('产品中沿用已有短剧工作台;本原型聚焦首页创作链路。');return;}
|
||
if(a==='assets'||a==='attach'){showDialog('添加参考素材',`<p>产品中复用现有素材库与上传选择器。此评审原型不上传文件。</p><div class="scope-note">素材会进入同一会话上下文。模型已经知道的角色、场景、比例和类型,应优先从素材与用户描述中提取。</div>`,`<span class="quiet">本例暂无附件</span><div>${btn('返回创作','close-dialog','primary')}</div>`);return;}
|
||
if(a==='canvas'){if(!s.canvasCreated)toast('方案尚未确认,画布保持 0 个节点。');else{s.mobileCanvas=true;render({fit:true});}return;}
|
||
if(a==='resume'){if(s.stage==='home')seed('questions');else toast('当前就是这段创作会话。');return;}
|
||
if(a==='answer'){answerSave();s.answers[s.question]=button.dataset.value;render();return;}
|
||
if(a==='prev-question'){answerSave();s.question=Math.max(0,s.question-1);render();return;}
|
||
if(a==='next-question'){if(answerValid()){s.question=Math.min(2,s.question+1);render({bottom:true});}return;}
|
||
if(a==='submit-answers'){if(answerValid()){s.stage='proposal';s.question=0;render({bottom:true});}return;}
|
||
if(a==='edit-answers'){if(s.canvasCreated){showDialog('已提交的创作需求',questions.map((q,i)=>`<h3>${q.title}</h3><p>${esc(s.answers[i]||'自定义')} ${esc(s.extras[i])}</p>`),`<span class="quiet">调整后生成新方案版本</span><div>${btn('修改方案','edit-plan','primary')}</div>`);}else{s.stage='questions';s.question=0;s.version+=1;render({bottom:true});}return;}
|
||
if(a==='view-plan'){planDetail();return;}
|
||
if(a==='edit-plan'){editPlan();return;}
|
||
if(a==='save-plan'){const note=document.getElementById('plan-edit').value.trim();if(!note){toast('请填写调整要求。');return;}s.editNote=note;s.version+=1;closeDialog();if(s.canvasCreated){s.messages.push({role:'assistant',text:`已记录方案 v${s.version} 的调整。现有节点暂不改变;原型本期以首次方案确认及节点局部编辑为主要评审路径。`});}else s.stage='proposal';render({bottom:true});return;}
|
||
if(a==='confirm-plan'){if(dialog.open)closeDialog();s.canvasCreated=true;s.stage='quote';s.planOpen=false;s.quoteAgreed=false;render({bottom:true,fit:true});return;}
|
||
if(a==='approve-text'){s.textApproved=true;s.stage='questions';closeDialog();render({bottom:true});return;}
|
||
if(a==='approve'){if(s.quoteAgreed)startRun();return;}
|
||
if(a==='toggle-plan'){s.planOpen=!s.planOpen;render();return;}
|
||
if(a==='pause'){stopTimer();toast('演示已暂停,可在顶部选择状态继续评审。');return;}
|
||
if(a==='restore'){s.restored=true;s.stage='complete';s.planOpen=false;render({bottom:true});toast('已仅恢复结果回写,新增生成费用为 0。');return;}
|
||
if(a==='redo'){s.retry=true;s.restored=false;s.stage='quote';s.quoteAgreed=false;s.selectedNode='video-1';render({bottom:true});return;}
|
||
if(a==='edit-node-prompt'){document.getElementById('chat-input').value='把场景参考节点改名为“场景参考 · 雨夜旧办公室”';document.getElementById('chat-input').focus();return;}
|
||
if(a==='rename'){const id=button.dataset.id;showDialog('修改节点名称',`<label class="free-label" for="node-name">节点名称</label><input id="node-name" class="field" value="${esc(nodeName(id))}"><p class="quiet">仅修改画布节点名称,不触发生成。</p>`,`<span></span><div>${btn('保存名称','save-name','primary',`data-id="${id}"`)}</div>`);return;}
|
||
if(a==='save-name'){const name=document.getElementById('node-name').value.trim();if(!name){toast('节点名称不能为空。');return;}s.nodeNames[button.dataset.id]=name;s.selectedNode=button.dataset.id;closeDialog();render();toast('节点名称已更新。');return;}
|
||
if(a==='node-detail'){const id=button.dataset.id;const video=id.startsWith('video');showDialog('节点参数',`<div class="detail-meta"><span>${video?'视频节点':'图片节点'}</span><span>9:16</span><span>${video?'15 秒 · 1 段完整短片':'单张参考图'}</span></div><h3>提示词</h3><p>${video?`${story()} 一段 15 秒视频依次表现信件出现、信中警告、门外身影三个叙事节奏,使用已确认的角色、场景、道具参考。`:id.endsWith('1')?'年轻侦探,约 28 岁,短发、深色风衣、克制神情。写实电影质感,清晰体现人物外观,用作全片人物参考。':id.endsWith('2')?'雨夜旧办公室,窄窗、木桌、走廊门。冷色窗光与桌面暖灯对比,用作全片场景参考。':'米白信封、黑色手写字迹及展开的信纸。道具细节清晰,用作视频中神秘信件的外观参考。'}</p><h3>参考关系</h3><p>${video?'本例选择已验证支持 15 秒及三类参考输入的视频模型。三个参考节点成功后才提交一次视频任务。实际接入时按模型能力校验数量与输入角色。':'引用已确认的创意方案中对应设定,生成后供同一个视频节点使用。'}</p><div class="scope-note">参数调整与重新生成分开。产生新生成费用时,需要新的批次授权。</div>`,`<span class="quiet">当前展示既有语义节点</span><div>${btn('关闭','close-dialog','primary')}</div>`);return;}
|
||
if(a==='preview'){const shot=Number(button.dataset.shot||1),video=button.dataset.kind==='video';showDialog(video?`《${titleText()}》· 15 秒短片示意`:`${referenceNames[shot-1]} · 图片示意`,`<div class="large-art">${art(shot,video)}</div><p style="text-align:center">${video?'示例封面 · 没有真实视频文件,播放按钮表示产品中的完整短片预览入口。':'抽象参考图片示意,不是模型真实生成素材。'}</p>`,`<span class="quiet">${video?'15 秒 · 1 段视频':'1 张参考图'} · 9:16</span><div>${btn('返回','close-dialog','primary')}</div>`);return;}
|
||
if(a==='zoom-in'||a==='zoom-out'){s.zoom=Math.min(1.5,Math.max(.35,s.zoom+(a==='zoom-in'?.1:-.1)));resizeCanvas();return;}
|
||
if(a==='fit'){resizeCanvas(true);return;}
|
||
if(a==='arrange'){s.selectedNode='';resizeCanvas(true);toast('已按“方案 → 角色 / 场景 / 道具参考 → 15 秒短片”整理并适应画布。');return;}
|
||
if(a==='mobile-canvas'){s.mobileCanvas=!s.mobileCanvas;render({fit:true});return;}
|
||
if(a==='close-dialog'){closeDialog();return;}
|
||
});
|
||
document.addEventListener('submit',event=>{if(event.target.id==='home-form'){event.preventDefault();const input=document.getElementById('home-prompt').value.trim();if(!input){toast('先写下你的创作想法。');return;}s.prompt=input;if(!/15|悬疑|侦探|雨夜/.test(input)){toast('本原型完整演示悬疑短片,请使用下方短片示例。');return;}s.canvasCreated=false;if(!s.textApproved){showDialog('确认本次创作规划预算',`<p>理解需求、补充问答和生成创意方案也可能产生文本模型费用。</p><div class="receipt-card"><div class="receipt-line"><span>授权范围</span><strong>本次短片需求梳理与方案生成</strong></div><div class="receipt-line"><span>文本请求上限</span><strong>6 次示例请求</strong></div><div class="receipt-line"><span>总费用上限</span><strong>2 示例积分</strong></div></div><p>范围内继续补问与规划,达到请求次数或费用上限后再次确认。图片和视频任务稍后单独报价。</p><div class="scope-note">这里的模型次数和积分仅用于设计评审,不是真实价格;点击确认不会请求模型或扣费。</div>`,`<span class="quiet">示例预算 · 本次会话有效</span><div>${btn('取消','close-dialog','secondary')}${btn('确认,开始梳理需求','approve-text','primary')}</div>`);}else{s.stage='questions';render({bottom:true});}}if(event.target.id==='chat-form'){event.preventDefault();sendChat(document.getElementById('chat-input').value);}});
|
||
document.addEventListener('change',event=>{if(event.target.id==='review-state')seed(event.target.value);if(event.target.id==='quote-agree'){s.quoteAgreed=event.target.checked;const approve=root.querySelector('[data-action="approve"]');if(approve)approve.disabled=!s.quoteAgreed;}});
|
||
dialog.addEventListener('click',event=>{if(event.target===dialog){const r=dialog.getBoundingClientRect();if(event.clientX<r.left||event.clientX>r.right||event.clientY<r.top||event.clientY>r.bottom)closeDialog();}});
|
||
window.addEventListener('resize',()=>resizeCanvas(true));
|
||
document.documentElement.dataset.theme=window.matchMedia('(prefers-color-scheme:dark)').matches?'dark':'light';
|
||
render();
|
||
})();
|
||
</script>
|
||
</body>
|
||
</html>
|