From 8e84e4c21f22febe7bd4f20777d06fa1d4914e8e Mon Sep 17 00:00:00 2001 From: leefer Date: Wed, 21 Oct 2026 20:13:03 +0800 Subject: [PATCH] feat(HEL-728): establish approved themes and compatible studio navigation Co-authored-by: multica-agent --- calliope-web/src/app.html | 47 +- .../src/lib/components/shell/AppShell.svelte | 1168 ++++++------ .../components/shell/CommandPalette.svelte | 412 +++-- .../lib/components/shell/ThemeChoices.svelte | 189 ++ calliope-web/src/lib/components/shell/nav.ts | 237 +-- calliope-web/src/lib/theme.ts | 68 + calliope-web/src/routes/+layout.svelte | 28 +- calliope-web/src/routes/app.css | 1478 +++++++-------- calliope-web/src/routes/create/+page.ts | 9 + .../src/routes/model-connections/+page.ts | 9 + calliope-web/src/routes/project/[id]/+page.ts | 11 +- calliope-web/src/routes/projects/+page.ts | 6 +- calliope-web/src/routes/settings/+page.svelte | 1542 +++++++++------- calliope-web/static/themes.css | 547 ++++++ design/nocturne/design-spec-v1.md | 140 ++ design/nocturne/design-spec-v2.md | 105 ++ design/nocturne/reference.html | 1640 +++++++++++++++++ docs/hel-728/backup-hashes.txt | 5 + docs/hel-728/baseline-browser.log | 31 + docs/hel-728/baseline-lint.log | 74 + docs/hel-728/baseline-pytest.log | 413 +++++ docs/hel-728/baseline.md | 33 + docs/hel-728/browser-results.json | 49 + docs/hel-728/build.log | 264 +++ docs/hel-728/changed-prettier.log | 2 + docs/hel-728/check.log | 21 + docs/hel-728/eslint.log | 15 + docs/hel-728/frontend-test.log | 51 + docs/hel-728/implementation.md | 13 + docs/hel-728/legacy-browser.log | 48 + docs/hel-728/lint.log | 69 + docs/hel-728/pytest.log | 442 +++++ docs/hel-728/server-audit.txt | 201 ++ docs/hel-728/server-backend.txt | 79 + docs/hel-728/theme-browser.log | 49 + docs/hel-728/validation.md | 47 + e2e/hel728.cjs | 397 ++++ e2e/run-hel728.py | 43 + 38 files changed, 7567 insertions(+), 2415 deletions(-) create mode 100644 calliope-web/src/lib/components/shell/ThemeChoices.svelte create mode 100644 calliope-web/src/lib/theme.ts create mode 100644 calliope-web/src/routes/create/+page.ts create mode 100644 calliope-web/src/routes/model-connections/+page.ts create mode 100644 calliope-web/static/themes.css create mode 100644 design/nocturne/design-spec-v1.md create mode 100644 design/nocturne/design-spec-v2.md create mode 100644 design/nocturne/reference.html create mode 100644 docs/hel-728/backup-hashes.txt create mode 100644 docs/hel-728/baseline-browser.log create mode 100644 docs/hel-728/baseline-lint.log create mode 100644 docs/hel-728/baseline-pytest.log create mode 100644 docs/hel-728/baseline.md create mode 100644 docs/hel-728/browser-results.json create mode 100644 docs/hel-728/build.log create mode 100644 docs/hel-728/changed-prettier.log create mode 100644 docs/hel-728/check.log create mode 100644 docs/hel-728/eslint.log create mode 100644 docs/hel-728/frontend-test.log create mode 100644 docs/hel-728/implementation.md create mode 100644 docs/hel-728/legacy-browser.log create mode 100644 docs/hel-728/lint.log create mode 100644 docs/hel-728/pytest.log create mode 100644 docs/hel-728/server-audit.txt create mode 100644 docs/hel-728/server-backend.txt create mode 100644 docs/hel-728/theme-browser.log create mode 100644 docs/hel-728/validation.md create mode 100644 e2e/hel728.cjs create mode 100644 e2e/run-hel728.py diff --git a/calliope-web/src/app.html b/calliope-web/src/app.html index 124c4539..02629c38 100644 --- a/calliope-web/src/app.html +++ b/calliope-web/src/app.html @@ -1,19 +1,30 @@ - - - - - - - 影工坊 - - - - %sveltekit.head% - - -
%sveltekit.body%
- + + + + + + + + + 影工坊 + + + + %sveltekit.head% + + +
%sveltekit.body%
+ diff --git a/calliope-web/src/lib/components/shell/AppShell.svelte b/calliope-web/src/lib/components/shell/AppShell.svelte index 024cbcdc..6ee60a51 100644 --- a/calliope-web/src/lib/components/shell/AppShell.svelte +++ b/calliope-web/src/lib/components/shell/AppShell.svelte @@ -1,586 +1,626 @@ -{title} · AIGC工作台 +{title} · AIGC工作台 -
- -
-
- -
- {#if scopeProjectId}{/if} - {#if actions}
{@render actions()}
{/if} - - - -
-
{@render children?.()}
-
+
+ +
+
+ +
+ {#if currentNav?.id === "connections"} + ComfyUI 连接 + 大语言模型 + {/if} + {#if scopeProjectId}{/if} + {#if actions}
{@render actions()}
{/if} + + + +
+
{@render children?.()}
+
+ + + + diff --git a/calliope-web/src/lib/components/shell/CommandPalette.svelte b/calliope-web/src/lib/components/shell/CommandPalette.svelte index eaaac0a8..ec4f390f 100644 --- a/calliope-web/src/lib/components/shell/CommandPalette.svelte +++ b/calliope-web/src/lib/components/shell/CommandPalette.svelte @@ -1,218 +1,232 @@ - -
- -
- {#if entries.length === 0} -

{t('shell.searchEmpty', { q: query.trim() })}

- {/if} - {#each entries as entry, i (entry.key)} - {#if i === 0 || entries[i - 1].group !== entry.group} - - {/if} -
(activeIndex = i)} - onmousedown={(e) => e.preventDefault()} - onclick={() => go(entry)} - onkeydown={handleKeydown} - > - - {entry.label} - {entry.href} -
- {/each} -
-

- ↑↓{t('shell.searchHintMove')} - Enter{t('shell.searchHintOpen')} - Esc{t('shell.searchHintClose')} -

-
+ +
+ +
+ {#if entries.length === 0} +

+ {t("shell.searchEmpty", { q: query.trim() })} +

+ {/if} + {#each entries as entry, i (entry.key)} + {#if i === 0 || entries[i - 1].group !== entry.group} + + {/if} +
(activeIndex = i)} + onmousedown={(e) => e.preventDefault()} + onclick={() => go(entry)} + onkeydown={handleKeydown} + > + + {entry.label} + {entry.href} +
+ {/each} +
+

+ ↑↓{t("shell.searchHintMove")} + Enter{t("shell.searchHintOpen")} + Esc{t("shell.searchHintClose")} +

+
diff --git a/calliope-web/src/lib/components/shell/ThemeChoices.svelte b/calliope-web/src/lib/components/shell/ThemeChoices.svelte new file mode 100644 index 00000000..fe2584ea --- /dev/null +++ b/calliope-web/src/lib/components/shell/ThemeChoices.svelte @@ -0,0 +1,189 @@ + + +
+ {#each THEMES as item} + + {/each} +
+{#if $themeStorageUnavailable}

+ 浏览器禁止本地存储:本次有效,其他操作不受影响 +

{/if} + + diff --git a/calliope-web/src/lib/components/shell/nav.ts b/calliope-web/src/lib/components/shell/nav.ts index 71927285..fffef095 100644 --- a/calliope-web/src/lib/components/shell/nav.ts +++ b/calliope-web/src/lib/components/shell/nav.ts @@ -1,123 +1,130 @@ -import type { IconName } from '$lib/components/ui/icons'; +import type { IconName } from "$lib/components/ui/icons"; +// IDs are retained for the existing page contracts while B1–B4 replace layouts. export type NavId = - | 'projects' - | 'workbench' - | 'image' - | 'video' - | 'tasks' - | 'agents' - | 'connections' - | 'models' - | 'library' - | 'settings' - | 'canvas' - | 'workflows'; - + | "projects" + | "workbench" + | "image" + | "video" + | "tasks" + | "agents" + | "connections" + | "models" + | "library" + | "settings" + | "canvas" + | "workflows"; export interface NavItem { - id: NavId; - labelKey: string; - icon: IconName; - href: string; + id: NavId; + label: string; + icon: IconName; + href: string; + scene: "project" | "image" | "agent" | "manage" | "asset"; + matches: (path: string) => boolean; } - -// Order is fixed by spec-v2 §3: 项目 / 创作工作台 / 生图 / 生视频 / 生成任务 / -// 智能体 / ComfyUI 连接 / 模型设置 / 设置. HEL-649 §二 puts the connection -// and settings entries at the foot of the 60px rail. export const NAV_MAIN: NavItem[] = [ - { id: 'projects', labelKey: 'shell.navProjects', icon: 'home', href: '/' }, - { - id: 'workbench', - labelKey: 'shell.navWorkbench', - icon: 'story', - href: '/workbench', - }, - { id: 'image', labelKey: 'shell.navImage', icon: 'image', href: '/image' }, - { id: 'video', labelKey: 'shell.navVideo', icon: 'film', href: '/video' }, - { id: 'tasks', labelKey: 'shell.navTasks', icon: 'queue', href: '/tasks' }, - { id: 'canvas', labelKey: 'nav.canvas', icon: 'story', href: '/canvas' }, - { - id: 'agents', - labelKey: 'shell.navAgents', - icon: 'sparkle', - href: '/agents', - }, + { + id: "projects", + label: "工作室", + icon: "home", + href: "/", + scene: "project", + matches: (p) => p === "/", + }, + { + id: "workbench", + label: "项目", + icon: "folder", + href: "/workbench", + scene: "project", + matches: (p) => /^\/(workbench|project)(\/|$)/.test(p), + }, + { + id: "image", + label: "创作台", + icon: "image", + href: "/create", + scene: "image", + matches: (p) => + ["/create", "/image", "/video", "/playground", "/build-scene"].includes( + p, + ), + }, + { + id: "agents", + label: "智能体", + icon: "sparkle", + href: "/agents", + scene: "agent", + matches: (p) => p === "/agents", + }, + { + id: "canvas", + label: "画布", + icon: "story", + href: "/canvas", + scene: "manage", + matches: (p) => p.startsWith("/canvas"), + }, + { + id: "library", + label: "素材库", + icon: "assets", + href: "/library", + scene: "asset", + matches: (p) => p === "/library", + }, +]; +export const NAV_MANAGE: NavItem[] = [ + { + id: "tasks", + label: "任务", + icon: "clock", + href: "/tasks", + scene: "manage", + matches: (p) => p === "/tasks", + }, + { + id: "workflows", + label: "工作流", + icon: "playground", + href: "/workflows", + scene: "manage", + matches: (p) => p === "/workflows", + }, + { + id: "connections", + label: "模型连接", + icon: "link", + href: "/model-connections", + scene: "manage", + matches: (p) => + ["/connections", "/models", "/model-connections"].includes(p), + }, ]; - export const NAV_FOOT: NavItem[] = [ - { - id: 'connections', - labelKey: 'shell.navConnections', - icon: 'link', - href: '/connections', - }, - { - id: 'models', - labelKey: 'shell.navModels', - icon: 'assets', - href: '/models', - }, - { - id: 'workflows', - labelKey: 'connections.wfTitle', - icon: 'playground', - href: '/workflows', - }, - { - id: 'library', - labelKey: 'shell.navLibrary', - icon: 'folder', - href: '/library', - }, - { - id: 'settings', - labelKey: 'shell.navSettings', - icon: 'settings', - href: '/settings', - }, + { + id: "settings", + label: "设置", + icon: "settings", + href: "/settings", + scene: "manage", + matches: (p) => p === "/settings", + }, ]; - -export const NAV: NavItem[] = [...NAV_MAIN, ...NAV_FOOT]; - -export interface TabItem { - id: 'projects' | 'workbench' | 'generate'; - labelKey: string; - icon: IconName; - href: string; - /** Pages that light this tab up. */ - covers: NavId[]; -} - -/** ≤760px bottom tab bar (§二): 项目 / 创作 / 生成 / 更多. */ -export const TABS: TabItem[] = [ - { - id: 'projects', - labelKey: 'shell.tabProjects', - icon: 'home', - href: '/', - covers: ['projects'], - }, - { - id: 'workbench', - labelKey: 'shell.tabCreate', - icon: 'story', - href: '/workbench', - covers: ['workbench'], - }, - { - id: 'generate', - labelKey: 'shell.tabGenerate', - icon: 'image', - href: '/image', - covers: ['image', 'video'], - }, -]; - -/** - * Every page a tab does not link to directly lives in the 更多 sheet. 生视频 - * is still its own route, so it is listed there too until the two - * generation pages merge. - */ -export const MORE_ITEMS: NavItem[] = NAV.filter( - (item) => !TABS.some((tab) => tab.href === item.href), -); +// Tools awaiting their replacement keep reachable entries in global search. +export const NAV = [ + ...NAV_MAIN, + ...NAV_MANAGE, + ...NAV_FOOT, + { id: "video", label: "视频创作", icon: "film", href: "/video" }, + { id: "models", label: "大语言模型", icon: "assets", href: "/models" }, + { + id: "connections", + label: "ComfyUI 连接", + icon: "link", + href: "/connections", + }, + { id: "image", label: "试验台", icon: "playground", href: "/playground" }, + { id: "video", label: "三维搭景", icon: "story", href: "/build-scene" }, +] as const; diff --git a/calliope-web/src/lib/theme.ts b/calliope-web/src/lib/theme.ts new file mode 100644 index 00000000..a6831a37 --- /dev/null +++ b/calliope-web/src/lib/theme.ts @@ -0,0 +1,68 @@ +import { writable } from "svelte/store"; + +/** Approved HEL-726 themes. Colours live only in static/themes.css. */ +export const THEMES = [ + { + id: "night", + name: "夜片场", + english: "Nocturne", + description: "墨黑与钨丝金 · 沉浸式创作", + }, + { + id: "day", + name: "日光棚", + english: "Daylight", + description: "暖白纸面 · 白天办公与长时间审稿", + }, + { + id: "grade", + name: "调色间", + english: "Grading", + description: "中性深灰 · 判断作品色彩", + }, + { + id: "bluehour", + name: "蓝调时刻", + english: "Blue Hour", + description: "靛蓝与珊瑚 · 提案与灵感发散", + }, +] as const; +export type ThemeId = (typeof THEMES)[number]["id"]; +export const THEME_KEY = "aigc-theme"; +export const theme = writable("night"); +export const themeStorageUnavailable = writable(false); + +export function isTheme(value: unknown): value is ThemeId { + return THEMES.some((item) => item.id === value); +} + +export function setTheme(id: ThemeId) { + if (typeof document === "undefined" || !isTheme(id)) return; + // Only the root attribute changes: editors, uploads and jobs stay mounted. + document.documentElement.dataset.theme = id; + theme.set(id); + try { + localStorage.setItem(THEME_KEY, id); + themeStorageUnavailable.set(false); + } catch { + themeStorageUnavailable.set(true); + } +} + +export function initTheme() { + const current = document.documentElement.dataset.theme; + theme.set(isTheme(current) ? current : "night"); + try { + localStorage.getItem(THEME_KEY); + } catch { + themeStorageUnavailable.set(true); + } + const sync = (event: StorageEvent) => { + if (event.key !== THEME_KEY && event.key !== null) return; + const id = isTheme(event.newValue) ? event.newValue : "night"; + document.documentElement.dataset.theme = id; + theme.set(id); + }; + window.addEventListener("storage", sync); + return () => window.removeEventListener("storage", sync); +} diff --git a/calliope-web/src/routes/+layout.svelte b/calliope-web/src/routes/+layout.svelte index 0a378bc7..cd91f504 100644 --- a/calliope-web/src/routes/+layout.svelte +++ b/calliope-web/src/routes/+layout.svelte @@ -1,20 +1,22 @@ - - + + diff --git a/calliope-web/src/routes/app.css b/calliope-web/src/routes/app.css index b832d5d2..874cf09e 100644 --- a/calliope-web/src/routes/app.css +++ b/calliope-web/src/routes/app.css @@ -2,201 +2,59 @@ @tailwind components; @tailwind utilities; -:root { - color-scheme: dark; - --accent-grad-hover: linear-gradient(135deg, #39dcee, #00b4c5); - --accent-ring: #00cae040; - --accent-wash: #00cae016; - --btn-glow-hover: 0 3px 0 #2235a01f; - --composer-bg: #131219d1; - --composer-focus: #131219e6; - --float-bg: #1b1c21; - --on-media: #fff; - --on-media-line: #ffffff66; - --sel-shadow: 0 0 0 1px #354fe0; - --bg: #0f0f12; - --bg-2: #141419; - --bg-3: #1b1c21; - --bg-4: #292b33; - --paper: #141419; - --lime: #203e39; - --rose: #402837; - --line: #ffffff14; - --line-2: #ffffff29; - --line-3: #ffffff40; - --text: #f5fbff; - --text-2: #b7bdc8; - --text-3: #8c99a0; - --accent: #00cae0; - --accent-hi: #79effa; - --accent-lo: #00a8b8; - --accent-ink: #071719; - --accent-soft: #00cae016; - --accent-line: #00cae059; - --accent-grad: linear-gradient(135deg, #00cae0, #00a8b8); - --accent-glow: transparent; - --btn-glow: 0 3px 0 #2235a01f; - --page-glow: linear-gradient(transparent, transparent); - --ok: #55d4ad; - --ok-soft: #10b98118; - --run: #a78bfa; - --run-soft: #7850ff18; - --run-grad: linear-gradient(90deg, #7850ff, #c4b5fd); - --err: #ff8c88; - --err-solid: #ef706b; - --err-soft: #ef706b18; - --err-line: #ef706b66; - --warn: #f5b042; - --warn-soft: #f5b04218; - --overlay: #000000bf; - --media-scrim: #101017ee; - --media-text: #fff; - --media-line: #ffffff66; - --stage-bg: #0f1117; - --font: - system-ui, -apple-system, 'PingFang SC', 'Microsoft YaHei', sans-serif; - --mono: ui-monospace, monospace; - --fs-title: 15px; - --fs-section: 14px; - --fs-body: 13px; - --fs-body-sm: 12.5px; - --fs-aux: 12px; - --fs-mini: 11px; - --lh-body: 1.55; - --sp-1: 4px; - --sp-2: 6px; - --sp-3: 8px; - --sp-4: 10px; - --sp-5: 12px; - --sp-6: 16px; - --sp-7: 20px; - --sp-8: 28px; - --r-sm: 6px; - --r-md: 10px; - --r-lg: 14px; - --r-pill: 999px; - --shadow-float: 0 20px 60px #0008, 0 0 0 1px var(--line); - --rail-w: 68px; - --topbar-h: 52px; - --tabbar-h: 0px; - --sprocket: none; - --frame-w: 0px; - --frame-arm: 12px; - --frame-inset: -4px; - --paper-w: 4px; - --ease-out: cubic-bezier(0.23, 1, 0.32, 1); - --ease-in-out: cubic-bezier(0.77, 0, 0.175, 1); - --ease-drawer: cubic-bezier(0.32, 0.72, 0, 1); - --ease-spring: cubic-bezier(0.34, 1.45, 0.44, 1); - --d-press: 160ms; - --d-state: 160ms; - --d-pop: 180ms; - --d-leave: 200ms; - --d-enter: 220ms; - --d-drawer: 250ms; - --d-flash: 900ms; - --d-progress: 400ms; - --press-scale: 0.97; - --avatar-1: #e8c6b7; - --avatar-2: #e1eb9a; - --avatar-3: #c6d3e4; - --avatar-4: #d6d6c1; - --avatar-5: #d8c7e5; - --id-1: #875440; - --id-2: #5c6b23; - --id-3: #45698d; - --id-4: #626348; - --id-5: #73578c; - --surface: var(--bg-2); - --surface-2: var(--bg-3); - --surface-3: var(--bg-4); - --border: var(--line); - --border-strong: var(--line-2); - --border-hover: var(--line-2); - --accent-dim: var(--accent-soft); - --success: var(--ok); - --success-dim: var(--ok-soft); - --info: var(--run); - --info-dim: var(--run-soft); - --error: var(--err); - --error-dim: var(--err-soft); - --warning: var(--warn); - --bg-primary: var(--bg); - --bg-surface: var(--bg-2); - --bg-elevated: var(--bg-3); - --text-primary: var(--text); - --text-secondary: var(--text-2); - --text-muted: var(--text-3); - --font-a-mono: var(--mono); - --font-display: var(--font); - --font-body: var(--font); - --font-mono: var(--mono); - --radius-sm: var(--r-sm); - --radius-md: var(--r-md); - --radius-lg: var(--r-lg); - --space-sm: var(--sp-3); - --space-md: var(--sp-6); - --pill-bg: var(--bg-3); - --pill-border: var(--line-2); - --pill-radius: var(--r-pill); - --pill-height: 36px; - --pill-padding: 0 14px; - --pill-font-size: var(--fs-body); -} - /* 减少动态效果 (§五):系统设置自动生效,顶栏开关写 data-reduce="1"。 位移/缩放/弹入/光晕全部取消,只留 ≤120ms 透明度,进度条直接跳变。 */ -:root[data-reduce='1'] { - --d-press: 0ms; - --d-state: 120ms; - --d-pop: 120ms; - --d-leave: 120ms; - --d-enter: 120ms; - --d-drawer: 120ms; - --d-flash: 0ms; - --d-progress: 0ms; - --press-scale: 1; - --ease-spring: ease; +:root[data-reduce="1"] { + --d-press: 0ms; + --d-state: 120ms; + --d-pop: 120ms; + --d-leave: 120ms; + --d-enter: 120ms; + --d-drawer: 120ms; + --d-flash: 0ms; + --d-progress: 0ms; + --press-scale: 1; + --ease-spring: ease; } @media (prefers-reduced-motion: reduce) { - :root { - --d-press: 0ms; - --d-state: 120ms; - --d-pop: 120ms; - --d-leave: 120ms; - --d-enter: 120ms; - --d-drawer: 120ms; - --d-flash: 0ms; - --d-progress: 0ms; - --press-scale: 1; - --ease-spring: ease; - } + :root { + --d-press: 0ms; + --d-state: 120ms; + --d-pop: 120ms; + --d-leave: 120ms; + --d-enter: 120ms; + --d-drawer: 120ms; + --d-flash: 0ms; + --d-progress: 0ms; + --press-scale: 1; + --ease-spring: ease; + } } body { - background: var(--page-glow), var(--bg); - color: var(--text); - font-family: var(--font); - font-size: var(--fs-body); - line-height: var(--lh-body); - -webkit-font-smoothing: antialiased; - margin: 0; - min-height: 100vh; + background: var(--page-glow), var(--bg); + color: var(--text); + font-family: var(--font); + font-size: var(--fs-body); + line-height: var(--lh-body); + -webkit-font-smoothing: antialiased; + margin: 0; + min-height: 100vh; } .a-darkroom { - background: var(--page-glow), var(--bg); - color: var(--text); - font-family: var(--font); - font-size: var(--fs-body); - line-height: var(--lh-body); + background: var(--page-glow), var(--bg); + color: var(--text); + font-family: var(--font); + font-size: var(--fs-body); + line-height: var(--lh-body); } code, .mono, .kh { - font-family: var(--mono); - font-variant-numeric: tabular-nums; + font-family: var(--mono); + font-variant-numeric: tabular-nums; } /* ------------------------------------------------------------------ @@ -206,555 +64,555 @@ code, * (width / margin), never re-declare colors. * ------------------------------------------------------------------ */ .a-btn { - display: inline-flex; - align-items: center; - justify-content: center; - gap: var(--sp-2); - min-height: 34px; - padding: 0 var(--sp-6); - border-radius: var(--r-md); - border: 1px solid transparent; - font-family: inherit; - font-size: var(--fs-body); - font-weight: 600; - white-space: nowrap; - text-decoration: none; - cursor: pointer; - box-sizing: border-box; - transition: - transform var(--d-press) var(--ease-out), - background-color var(--d-state) ease, - border-color var(--d-state) ease, - box-shadow var(--d-state) ease, - color var(--d-state) ease, - opacity var(--d-state) ease; + display: inline-flex; + align-items: center; + justify-content: center; + gap: var(--sp-2); + min-height: 34px; + padding: 0 var(--sp-6); + border-radius: var(--r-md); + border: 1px solid transparent; + font-family: inherit; + font-size: var(--fs-body); + font-weight: 600; + white-space: nowrap; + text-decoration: none; + cursor: pointer; + box-sizing: border-box; + transition: + transform var(--d-press) var(--ease-out), + background-color var(--d-state) ease, + border-color var(--d-state) ease, + box-shadow var(--d-state) ease, + color var(--d-state) ease, + opacity var(--d-state) ease; } .a-btn.lg { - min-height: 38px; - padding: 0 var(--sp-7); + min-height: 38px; + padding: 0 var(--sp-7); } .a-btn.sm { - min-height: 28px; - padding: 0 var(--sp-4); - font-size: var(--fs-aux); - border-radius: var(--r-sm); + min-height: 28px; + padding: 0 var(--sp-4); + font-size: var(--fs-aux); + border-radius: var(--r-sm); } .a-btn.xs { - min-height: 24px; - padding: 0 var(--sp-3); - font-size: var(--fs-mini); - border-radius: var(--r-sm); + min-height: 24px; + padding: 0 var(--sp-3); + font-size: var(--fs-mini); + border-radius: var(--r-sm); } .a-btn.block { - width: 100%; + width: 100%; } .a-btn.primary { - background: var(--accent-grad); - color: var(--accent-ink); - box-shadow: var(--btn-glow); + background: var(--accent-grad); + color: var(--accent-ink); + box-shadow: var(--btn-glow); } .a-btn.secondary { - background: var(--bg-3); - border-color: var(--line); - color: var(--text); + background: var(--bg-3); + border-color: var(--line); + color: var(--text); } .a-btn.ghost { - background: transparent; - color: var(--text-2); + background: transparent; + color: var(--text-2); } /* 危险两级制:列表行内只用描边红 danger;实心红 danger-solid 只在确认弹层内。 */ .a-btn.danger { - background: transparent; - border-color: var(--err-line); - color: var(--err); + background: transparent; + border-color: var(--err-line); + color: var(--err); } .a-btn.danger-solid { - background: var(--err-solid); - color: var(--on-media); + background: var(--err-solid); + color: var(--on-media); } @media (hover: hover) and (pointer: fine) { - .a-btn.primary:hover:not(:disabled) { - background: var(--accent-grad-hover); - box-shadow: var(--btn-glow-hover); - } - .a-btn.secondary:hover:not(:disabled) { - border-color: var(--line-2); - background: var(--bg-4); - } - .a-btn.ghost:hover:not(:disabled) { - background: var(--bg-4); - color: var(--text); - } - .a-btn.danger:hover:not(:disabled) { - background: var(--err-soft); - } - .a-btn.danger-solid:hover:not(:disabled) { - box-shadow: - 0 0 0 1px var(--err-line), - 0 0 18px var(--err-soft); - } + .a-btn.primary:hover:not(:disabled) { + background: var(--accent-grad-hover); + box-shadow: var(--btn-glow-hover); + } + .a-btn.secondary:hover:not(:disabled) { + border-color: var(--line-2); + background: var(--bg-4); + } + .a-btn.ghost:hover:not(:disabled) { + background: var(--bg-4); + color: var(--text); + } + .a-btn.danger:hover:not(:disabled) { + background: var(--err-soft); + } + .a-btn.danger-solid:hover:not(:disabled) { + box-shadow: + 0 0 0 1px var(--err-line), + 0 0 18px var(--err-soft); + } } .a-btn:active:not(:disabled) { - transform: scale(var(--press-scale)); + transform: scale(var(--press-scale)); } /* 禁用必须在旁边给出中文原因(§1.2),由调用方渲染。 */ .a-btn:disabled, -.a-btn[aria-disabled='true'] { - opacity: 0.45; - box-shadow: none; - cursor: not-allowed; +.a-btn[aria-disabled="true"] { + opacity: 0.45; + box-shadow: none; + cursor: not-allowed; } /* Top-bar status capsule (queue / connection / save state) — §二 顶栏胶囊. */ .status-chip { - display: inline-flex; - align-items: center; - gap: var(--sp-2); - height: 30px; - padding: 0 var(--sp-5); - border-radius: var(--r-pill); - border: 1px solid var(--line); - background: var(--bg-2); - color: var(--text-2); - font-size: var(--fs-aux); - white-space: nowrap; - box-sizing: border-box; + display: inline-flex; + align-items: center; + gap: var(--sp-2); + height: 30px; + padding: 0 var(--sp-5); + border-radius: var(--r-pill); + border: 1px solid var(--line); + background: var(--bg-2); + color: var(--text-2); + font-size: var(--fs-aux); + white-space: nowrap; + box-sizing: border-box; } .status-chip.accent { - background: var(--accent-soft); - border-color: var(--accent-line); - color: var(--accent); + background: var(--accent-soft); + border-color: var(--accent-line); + color: var(--accent); } a.status-chip, button.status-chip { - font-family: inherit; - text-decoration: none; - cursor: pointer; - transition: - border-color var(--d-state) ease, - color var(--d-state) ease, - transform var(--d-press) var(--ease-out); + font-family: inherit; + text-decoration: none; + cursor: pointer; + transition: + border-color var(--d-state) ease, + color var(--d-state) ease, + transform var(--d-press) var(--ease-out); } a.status-chip:active, button.status-chip:active { - transform: scale(var(--press-scale)); + transform: scale(var(--press-scale)); } @media (hover: hover) and (pointer: fine) { - a.status-chip:hover, - button.status-chip:hover { - border-color: var(--text-3); - color: var(--text); - } + a.status-chip:hover, + button.status-chip:hover { + border-color: var(--text-3); + color: var(--text); + } } /* Keyboard shortcut hint, e.g. the Ctrl K badge on the search capsule. */ .a-kbd { - font: var(--fs-mini) var(--mono); - color: var(--text-3); - border: 1px solid var(--line-2); - border-bottom-width: 2px; - border-radius: var(--r-sm); - padding: 0 var(--sp-2); - line-height: 16px; - white-space: nowrap; + font: var(--fs-mini) var(--mono); + color: var(--text-3); + border: 1px solid var(--line-2); + border-bottom-width: 2px; + border-radius: var(--r-sm); + padding: 0 var(--sp-2); + line-height: 16px; + white-space: nowrap; } .status-chip-dot { - width: 6px; - height: 6px; - border-radius: 50%; - flex-shrink: 0; - background: currentColor; + width: 6px; + height: 6px; + border-radius: 50%; + flex-shrink: 0; + background: currentColor; } .status-chip-dot.ok { - background: var(--ok); + background: var(--ok); } .status-chip-dot.run { - background: var(--run); + background: var(--run); } .status-chip-dot.fail { - background: var(--err); + background: var(--err); } .status-chip-dot.idle { - background: var(--text-3); + background: var(--text-3); } /* Status label (§四.2):语义色文字 + 13% 软底;默认为中性(排队 / 未开始)。 */ .a-tag { - display: inline-flex; - align-items: center; - gap: var(--sp-1); - height: 20px; - padding: 0 var(--sp-2); - border-radius: var(--r-sm); - background: var(--bg-4); - color: var(--text-2); - font-size: var(--fs-aux); - white-space: nowrap; - box-sizing: border-box; - transition: - color var(--d-state) ease, - background-color var(--d-state) ease; + display: inline-flex; + align-items: center; + gap: var(--sp-1); + height: 20px; + padding: 0 var(--sp-2); + border-radius: var(--r-sm); + background: var(--bg-4); + color: var(--text-2); + font-size: var(--fs-aux); + white-space: nowrap; + box-sizing: border-box; + transition: + color var(--d-state) ease, + background-color var(--d-state) ease; } .a-tag.accent { - color: var(--accent); - background: var(--accent-soft); + color: var(--accent); + background: var(--accent-soft); } .a-tag.ok { - color: var(--ok); - background: var(--ok-soft); + color: var(--ok); + background: var(--ok-soft); } .a-tag.run { - color: var(--run); - background: var(--run-soft); + color: var(--run); + background: var(--run-soft); } .a-tag.err { - color: var(--err); - background: var(--err-soft); + color: var(--err); + background: var(--err-soft); } .a-tag.warn { - color: var(--warn); - background: var(--warn-soft); + color: var(--warn); + background: var(--warn-soft); } /* Real-progress bar (§1.3):
. Only for true progress values — never fake progress. */ .a-progress { - position: relative; - height: 4px; - border-radius: var(--r-pill); - background: var(--bg-4); - overflow: hidden; + position: relative; + height: 4px; + border-radius: var(--r-pill); + background: var(--bg-4); + overflow: hidden; } .a-progress > i { - position: absolute; - inset: 0; - background: var(--run-grad); - box-shadow: 0 0 8px var(--run-soft); - transform-origin: left; - transform: scaleX(var(--p, 0)); - transition: transform var(--d-progress) linear; + position: absolute; + inset: 0; + background: var(--run-grad); + box-shadow: 0 0 8px var(--run-soft); + transform-origin: left; + transform: scaleX(var(--p, 0)); + transition: transform var(--d-progress) linear; } /* Film sprocket divider — only between the take feed and the composer (§1.7). */ .a-sprocket { - height: 6px; - background-image: var(--sprocket); - background-size: 18px 6px; - background-repeat: repeat-x; - background-position: center; - opacity: 0.55; + height: 6px; + background-image: var(--sprocket); + background-size: 18px 6px; + background-repeat: repeat-x; + background-position: center; + opacity: 0.55; } -:root[data-reduce='1'] .a-sprocket { - opacity: 0.3; +:root[data-reduce="1"] .a-sprocket { + opacity: 0.3; } /* Segmented control (§四 分段): the pressed option is lifted, never gold. */ .a-seg { - display: inline-flex; - padding: 2px; - border: 1px solid var(--line); - border-radius: var(--r-md); - background: var(--bg-2); + display: inline-flex; + padding: 2px; + border: 1px solid var(--line); + border-radius: var(--r-md); + background: var(--bg-2); } .a-seg > button { - height: 26px; - padding: 0 var(--sp-4); - border: 0; - border-radius: 8px; - background: transparent; - color: var(--text-3); - font-family: inherit; - font-size: var(--fs-body-sm); - white-space: nowrap; - cursor: pointer; - transition: - color var(--d-state) ease, - background-color var(--d-state) ease; + height: 26px; + padding: 0 var(--sp-4); + border: 0; + border-radius: 8px; + background: transparent; + color: var(--text-3); + font-family: inherit; + font-size: var(--fs-body-sm); + white-space: nowrap; + cursor: pointer; + transition: + color var(--d-state) ease, + background-color var(--d-state) ease; } -.a-seg > button[aria-pressed='true'] { - background: var(--bg-4); - color: var(--text); - box-shadow: inset 0 0 0 1px var(--line-2); +.a-seg > button[aria-pressed="true"] { + background: var(--bg-4); + color: var(--text); + box-shadow: inset 0 0 0 1px var(--line-2); } @media (hover: hover) and (pointer: fine) { - .a-seg > button:hover { - color: var(--text); - } + .a-seg > button:hover { + color: var(--text); + } } /* Composer chips (§3.3 输入台底行): workflow / connection / parameter chips. Gold (`changed`) only marks a parameter that differs from the workflow value. */ .a-chip { - display: inline-flex; - align-items: center; - gap: var(--sp-2); - height: 28px; - max-width: 280px; - padding: 0 var(--sp-4); - border: 0; - border-radius: var(--r-pill); - background: var(--bg-3); - color: var(--text-2); - font-family: inherit; - font-size: var(--fs-aux); - white-space: nowrap; - text-decoration: none; - cursor: pointer; - box-sizing: border-box; - transition: - background-color var(--d-state) ease, - color var(--d-state) ease, - transform var(--d-press) var(--ease-out); + display: inline-flex; + align-items: center; + gap: var(--sp-2); + height: 28px; + max-width: 280px; + padding: 0 var(--sp-4); + border: 0; + border-radius: var(--r-pill); + background: var(--bg-3); + color: var(--text-2); + font-family: inherit; + font-size: var(--fs-aux); + white-space: nowrap; + text-decoration: none; + cursor: pointer; + box-sizing: border-box; + transition: + background-color var(--d-state) ease, + color var(--d-state) ease, + transform var(--d-press) var(--ease-out); } .a-chip:active { - transform: scale(var(--press-scale)); + transform: scale(var(--press-scale)); } .a-chip > .lbl { - min-width: 0; - overflow: hidden; - text-overflow: ellipsis; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; } .a-chip.wf { - background: var(--bg-4); - color: var(--text); - font-weight: 500; - box-shadow: inset 0 0 0 1px var(--line-2); + background: var(--bg-4); + color: var(--text); + font-weight: 500; + box-shadow: inset 0 0 0 1px var(--line-2); } .a-chip.changed { - color: var(--accent); + color: var(--accent); } /* Selected filter chip (§3.5 筛选芯片组): 当前选中 = 取景框角线缩影 + 相纸白文字 + accent-soft 底 (HEL-680) */ .a-chip.sel { - position: relative; - background: var(--accent-soft); - color: var(--accent); + position: relative; + background: var(--accent-soft); + color: var(--accent); } .a-chip.sel::after { - content: ''; - position: absolute; - inset: 2px; - pointer-events: none; - background-image: - linear-gradient(var(--accent) 0 0), linear-gradient(var(--accent) 0 0), - linear-gradient(var(--accent) 0 0), linear-gradient(var(--accent) 0 0), - linear-gradient(var(--accent) 0 0), linear-gradient(var(--accent) 0 0), - linear-gradient(var(--accent) 0 0), linear-gradient(var(--accent) 0 0); - background-position: - left top, - left top, - right top, - right top, - left bottom, - left bottom, - right bottom, - right bottom; - background-size: - 6px 1.5px, - 1.5px 6px, - 6px 1.5px, - 1.5px 6px, - 6px 1.5px, - 1.5px 6px, - 6px 1.5px, - 1.5px 6px; - background-repeat: no-repeat; + content: ""; + position: absolute; + inset: 2px; + pointer-events: none; + background-image: + linear-gradient(var(--accent) 0 0), linear-gradient(var(--accent) 0 0), + linear-gradient(var(--accent) 0 0), linear-gradient(var(--accent) 0 0), + linear-gradient(var(--accent) 0 0), linear-gradient(var(--accent) 0 0), + linear-gradient(var(--accent) 0 0), linear-gradient(var(--accent) 0 0); + background-position: + left top, + left top, + right top, + right top, + left bottom, + left bottom, + right bottom, + right bottom; + background-size: + 6px 1.5px, + 1.5px 6px, + 6px 1.5px, + 1.5px 6px, + 6px 1.5px, + 1.5px 6px, + 6px 1.5px, + 1.5px 6px; + background-repeat: no-repeat; } .a-chip.warn { - color: var(--warn); + color: var(--warn); } .a-chip.err { - color: var(--err); + color: var(--err); } @media (hover: hover) and (pointer: fine) { - .a-chip:hover { - background: var(--bg-4); - color: var(--text); - } - .a-chip.changed:hover { - color: var(--accent); - } + .a-chip:hover { + background: var(--bg-4); + color: var(--text); + } + .a-chip.changed:hover { + color: var(--accent); + } } /* Composer input slots (§3.3 输入槽): 58px tiles — empty dashed / drag gold / uploading ring / filled with hover ✕. MediaSlot and RefImageGrid render these. */ .a-slot { - display: flex; - flex-direction: column; - gap: var(--sp-2); - min-width: 0; + display: flex; + flex-direction: column; + gap: var(--sp-2); + min-width: 0; } .a-slot-label { - display: flex; - align-items: center; - gap: var(--sp-2); - min-height: 20px; - font-size: var(--fs-mini); - color: var(--text-3); - white-space: nowrap; + display: flex; + align-items: center; + gap: var(--sp-2); + min-height: 20px; + font-size: var(--fs-mini); + color: var(--text-3); + white-space: nowrap; } .a-slot-label .req { - color: var(--warn); + color: var(--warn); } .a-slot-row { - display: flex; - flex-wrap: wrap; - gap: var(--sp-2); + display: flex; + flex-wrap: wrap; + gap: var(--sp-2); } .a-slot-hint { - font-size: var(--fs-mini); - color: var(--text-3); + font-size: var(--fs-mini); + color: var(--text-3); } .a-slot-hint.err { - color: var(--err); + color: var(--err); } .a-tile { - position: relative; - flex: none; - display: grid; - place-items: center; - width: 58px; - height: 58px; - padding: 0; - border: 1px solid var(--line-2); - border-radius: var(--r-md); - background: var(--bg-3); - color: var(--text-3); - font-family: inherit; - box-sizing: border-box; - transition: - border-color var(--d-state) ease, - background-color var(--d-state) ease, - color var(--d-state) ease, - transform var(--d-press) var(--ease-out); + position: relative; + flex: none; + display: grid; + place-items: center; + width: 58px; + height: 58px; + padding: 0; + border: 1px solid var(--line-2); + border-radius: var(--r-md); + background: var(--bg-3); + color: var(--text-3); + font-family: inherit; + box-sizing: border-box; + transition: + border-color var(--d-state) ease, + background-color var(--d-state) ease, + color var(--d-state) ease, + transform var(--d-press) var(--ease-out); } button.a-tile { - cursor: pointer; + cursor: pointer; } button.a-tile:disabled { - cursor: default; + cursor: default; } .a-tile.add { - border-style: dashed; + border-style: dashed; } .a-tile.add:active:not(:disabled) { - transform: scale(var(--press-scale)); + transform: scale(var(--press-scale)); } .a-tile.drag { - border-color: var(--accent); - background-color: var(--accent-soft); - color: var(--accent); + border-color: var(--accent); + background-color: var(--accent-soft); + color: var(--accent); } .a-tile.need { - border-color: var(--warn); + border-color: var(--warn); } .a-tile.bad { - border-color: var(--err); - color: var(--err); + border-color: var(--err); + color: var(--err); } .a-tile-hit { - position: absolute; - inset: 0; - display: grid; - place-items: center; - padding: 0; - border: 0; - border-radius: inherit; - overflow: hidden; - background: transparent; - color: inherit; - cursor: pointer; + position: absolute; + inset: 0; + display: grid; + place-items: center; + padding: 0; + border: 0; + border-radius: inherit; + overflow: hidden; + background: transparent; + color: inherit; + cursor: pointer; } .a-tile-hit img, .a-tile-hit video { - display: block; - width: 100%; - height: 100%; - object-fit: cover; + display: block; + width: 100%; + height: 100%; + object-fit: cover; } /* Uploads have no byte progress (fetch), so the ring spins instead of filling. */ .a-tile-ring { - position: absolute; - inset: 0; - display: grid; - place-items: center; - border-radius: inherit; - background: var(--media-scrim); - pointer-events: none; + position: absolute; + inset: 0; + display: grid; + place-items: center; + border-radius: inherit; + background: var(--media-scrim); + pointer-events: none; } .a-tile-ring > i { - width: 26px; - height: 26px; - border-radius: 50%; - background: conic-gradient(var(--accent) 0.3turn, var(--line-2) 0); - -webkit-mask: radial-gradient(circle, transparent 9.5px, #000 10.5px); - mask: radial-gradient(circle, transparent 9.5px, #000 10.5px); - animation: a-tile-spin 900ms linear infinite; + width: 26px; + height: 26px; + border-radius: 50%; + background: conic-gradient(var(--accent) 0.3turn, var(--line-2) 0); + -webkit-mask: radial-gradient(circle, transparent 9.5px, #000 10.5px); + mask: radial-gradient(circle, transparent 9.5px, #000 10.5px); + animation: a-tile-spin 900ms linear infinite; } @keyframes a-tile-spin { - to { - transform: rotate(1turn); - } + to { + transform: rotate(1turn); + } } -:root[data-reduce='1'] .a-tile-ring > i { - animation: none; +:root[data-reduce="1"] .a-tile-ring > i { + animation: none; } .a-tile-x { - position: absolute; - top: -6px; - right: -6px; - z-index: 1; - display: grid; - place-items: center; - width: 18px; - height: 18px; - padding: 0; - border: 1px solid var(--line-2); - border-radius: 50%; - background: var(--bg-4); - color: var(--text-2); - cursor: pointer; - opacity: 0; - transition: - opacity var(--d-state) ease, - color var(--d-state) ease; + position: absolute; + top: -6px; + right: -6px; + z-index: 1; + display: grid; + place-items: center; + width: 18px; + height: 18px; + padding: 0; + border: 1px solid var(--line-2); + border-radius: 50%; + background: var(--bg-4); + color: var(--text-2); + cursor: pointer; + opacity: 0; + transition: + opacity var(--d-state) ease, + color var(--d-state) ease; } .a-tile:focus-within .a-tile-x { - opacity: 1; + opacity: 1; } .a-tile-idx { - position: absolute; - left: 3px; - top: 2px; - font: 10px var(--mono); - color: var(--on-media); - text-shadow: 0 1px 2px var(--bg); - pointer-events: none; + position: absolute; + left: 3px; + top: 2px; + font: 10px var(--mono); + color: var(--on-media); + text-shadow: 0 1px 2px var(--bg); + pointer-events: none; } @media (hover: hover) and (pointer: fine) { - .a-tile:hover .a-tile-x { - opacity: 1; - } - .a-tile-x:hover { - color: var(--err); - } - button.a-tile.add:hover:not(:disabled) { - border-color: var(--text-3); - color: var(--text); - } + .a-tile:hover .a-tile-x { + opacity: 1; + } + .a-tile-x:hover { + color: var(--err); + } + button.a-tile.add:hover:not(:disabled) { + border-color: var(--text-3); + color: var(--text); + } } @media (hover: none) { - .a-tile-x { - opacity: 1; - } + .a-tile-x { + opacity: 1; + } } /* Projection stage — only behind media in the inspector (§1.7). */ .a-stage { - position: relative; - border-radius: var(--r-lg); - overflow: hidden; - background: var(--stage-bg); - box-shadow: 0 0 0 1px var(--line); + position: relative; + border-radius: var(--r-lg); + overflow: hidden; + background: var(--stage-bg); + box-shadow: 0 0 0 1px var(--line); } /* ------------------------------------------------------------------ @@ -763,154 +621,154 @@ button.a-tile:disabled { * overflow:hidden 裁切的容器改用 --frame-inset 正值画在内侧)。 * ------------------------------------------------------------------ */ .a-frame { - position: relative; + position: relative; } .a-frame::after { - content: ''; - position: absolute; - inset: var(--frame-inset, -4px); - z-index: 3; - pointer-events: none; - background-image: - linear-gradient(var(--accent) 0 0), linear-gradient(var(--accent) 0 0), - linear-gradient(var(--accent) 0 0), linear-gradient(var(--accent) 0 0), - linear-gradient(var(--accent) 0 0), linear-gradient(var(--accent) 0 0), - linear-gradient(var(--accent) 0 0), linear-gradient(var(--accent) 0 0); - background-position: - left top, - left top, - right top, - right top, - left bottom, - left bottom, - right bottom, - right bottom; - background-size: - var(--frame-arm, 12px) var(--frame-w, 2px), - var(--frame-w, 2px) var(--frame-arm, 12px), - var(--frame-arm, 12px) var(--frame-w, 2px), - var(--frame-w, 2px) var(--frame-arm, 12px), - var(--frame-arm, 12px) var(--frame-w, 2px), - var(--frame-w, 2px) var(--frame-arm, 12px), - var(--frame-arm, 12px) var(--frame-w, 2px), - var(--frame-w, 2px) var(--frame-arm, 12px); - background-repeat: no-repeat; + content: ""; + position: absolute; + inset: var(--frame-inset, -4px); + z-index: 3; + pointer-events: none; + background-image: + linear-gradient(var(--accent) 0 0), linear-gradient(var(--accent) 0 0), + linear-gradient(var(--accent) 0 0), linear-gradient(var(--accent) 0 0), + linear-gradient(var(--accent) 0 0), linear-gradient(var(--accent) 0 0), + linear-gradient(var(--accent) 0 0), linear-gradient(var(--accent) 0 0); + background-position: + left top, + left top, + right top, + right top, + left bottom, + left bottom, + right bottom, + right bottom; + background-size: + var(--frame-arm, 12px) var(--frame-w, 2px), + var(--frame-w, 2px) var(--frame-arm, 12px), + var(--frame-arm, 12px) var(--frame-w, 2px), + var(--frame-w, 2px) var(--frame-arm, 12px), + var(--frame-arm, 12px) var(--frame-w, 2px), + var(--frame-w, 2px) var(--frame-arm, 12px), + var(--frame-arm, 12px) var(--frame-w, 2px), + var(--frame-w, 2px) var(--frame-arm, 12px); + background-repeat: no-repeat; } /* 相纸白边 (HEL-680):结果媒体外的相纸白框,底边略厚如手工相纸。 挂在承载 img/video 的媒体容器上;容器尺寸由调用方决定。 */ .a-photo { - background: var(--accent); - border: var(--paper-w, 4px) solid var(--accent); - border-bottom-width: calc(var(--paper-w, 4px) + 2px); - border-radius: 2px; - box-shadow: 0 2px 12px rgba(0, 0, 0, 0.35); - box-sizing: border-box; + background: var(--accent); + border: var(--paper-w, 4px) solid var(--accent); + border-bottom-width: calc(var(--paper-w, 4px) + 2px); + border-radius: 2px; + box-shadow: 0 2px 12px rgba(0, 0, 0, 0.35); + box-sizing: border-box; } /* 「定影」完成章 (HEL-680):已定影结果右上角的完成印记,随 jobst.done 文案走(中文=定影)。absolute 定位,挂在有媒体的结果容器内。 */ .a-fix { - position: absolute; - top: var(--sp-3); - right: var(--sp-3); - z-index: 2; - padding: 1px 6px; - border: 1px solid currentColor; - border-radius: 3px; - background: var(--media-scrim); - color: var(--ok); - font-size: var(--fs-mini); - font-weight: 700; - letter-spacing: 0.1em; - text-indent: 0.1em; - transform: rotate(-4deg); - pointer-events: none; + position: absolute; + top: var(--sp-3); + right: var(--sp-3); + z-index: 2; + padding: 1px 6px; + border: 1px solid currentColor; + border-radius: 3px; + background: var(--media-scrim); + color: var(--ok); + font-size: var(--fs-mini); + font-weight: 700; + letter-spacing: 0.1em; + text-indent: 0.1em; + transform: rotate(-4deg); + pointer-events: none; } /* Floating surface for pop / drawer / confirm / toast / lightbox (§四.3). */ .a-float { - background: var(--float-bg); - border-radius: var(--r-lg); - box-shadow: var(--shadow-float); + background: var(--float-bg); + border-radius: var(--r-lg); + box-shadow: var(--shadow-float); } /* Rows inside pops, sheets and the Ctrl+K palette (§四.3 pop). Gold marks only the current page / project; keyboard and hover highlight is neutral. */ .a-menu-group { - padding: var(--sp-4) var(--sp-4) var(--sp-1); - font-size: var(--fs-mini); - color: var(--text-3); + padding: var(--sp-4) var(--sp-4) var(--sp-1); + font-size: var(--fs-mini); + color: var(--text-3); } .a-menu-item { - display: flex; - align-items: center; - gap: var(--sp-3); - width: 100%; - min-height: 40px; - padding: var(--sp-3) var(--sp-4); - border: 0; - border-radius: var(--r-md); - background: transparent; - color: var(--text-2); - font-family: inherit; - font-size: var(--fs-body); - text-align: left; - text-decoration: none; - cursor: pointer; - box-sizing: border-box; - transition: - background-color var(--d-state) ease, - color var(--d-state) ease; + display: flex; + align-items: center; + gap: var(--sp-3); + width: 100%; + min-height: 40px; + padding: var(--sp-3) var(--sp-4); + border: 0; + border-radius: var(--r-md); + background: transparent; + color: var(--text-2); + font-family: inherit; + font-size: var(--fs-body); + text-align: left; + text-decoration: none; + cursor: pointer; + box-sizing: border-box; + transition: + background-color var(--d-state) ease, + color var(--d-state) ease; } .a-menu-item.active, .a-menu-item:focus-visible { - background: var(--bg-4); - color: var(--text); + background: var(--bg-4); + color: var(--text); } @media (hover: hover) and (pointer: fine) { - .a-menu-item:hover { - background: var(--bg-4); - color: var(--text); - } + .a-menu-item:hover { + background: var(--bg-4); + color: var(--text); + } } -.a-menu-item[aria-current='page'], -.a-menu-item[aria-current='true'] { - color: var(--accent); +.a-menu-item[aria-current="page"], +.a-menu-item[aria-current="true"] { + color: var(--accent); } .a-menu-label { - flex: 1; - min-width: 0; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; + flex: 1; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; } .a-menu-meta { - flex-shrink: 0; - font-size: var(--fs-mini); - color: var(--text-3); - white-space: nowrap; + flex-shrink: 0; + font-size: var(--fs-mini); + color: var(--text-3); + white-space: nowrap; } /* Card / panel surface (§1.5:卡片不投影,用底色差分层). */ .a-panel { - background: var(--bg-2); - border: 1px solid var(--line); - border-radius: var(--r-lg); - padding: var(--sp-7); - box-sizing: border-box; + background: var(--bg-2); + border: 1px solid var(--line); + border-radius: var(--r-lg); + padding: var(--sp-7); + box-sizing: border-box; } .a-panel-title { - margin: 0; - font-size: var(--fs-section); - font-weight: 600; - color: var(--text); + margin: 0; + font-size: var(--fs-section); + font-weight: 600; + color: var(--text); } .a-panel-lead { - margin: var(--sp-1) 0 0; - font-size: var(--fs-aux); - line-height: var(--lh-body); - color: var(--text-3); + margin: var(--sp-1) 0 0; + font-size: var(--fs-aux); + line-height: var(--lh-body); + color: var(--text-3); } .a-select, @@ -919,48 +777,48 @@ button.a-tile:disabled { .field-input, .field-textarea, .field-select { - box-sizing: border-box; - background: var(--bg-3); - border: 1px solid var(--line); - border-radius: var(--r-md); - color: var(--text); - font-family: inherit; - font-size: var(--fs-body-sm); - transition: border-color var(--d-state) ease; + box-sizing: border-box; + background: var(--bg-3); + border: 1px solid var(--line); + border-radius: var(--r-md); + color: var(--text); + font-family: inherit; + font-size: var(--fs-body-sm); + transition: border-color var(--d-state) ease; } .a-select { - padding: var(--sp-2) var(--sp-4); - cursor: pointer; + padding: var(--sp-2) var(--sp-4); + cursor: pointer; } .a-input, .field-input, .field-select { - width: 100%; - padding: var(--sp-3) var(--sp-5); + width: 100%; + padding: var(--sp-3) var(--sp-5); } .a-textarea, .field-textarea { - width: 100%; - padding: var(--sp-4) var(--sp-5); - resize: vertical; - min-height: 96px; - line-height: var(--lh-body); + width: 100%; + padding: var(--sp-4) var(--sp-5); + resize: vertical; + min-height: 96px; + line-height: var(--lh-body); } .a-input::placeholder, .a-textarea::placeholder, .field-input::placeholder, .field-textarea::placeholder { - color: var(--text-3); + color: var(--text-3); } @media (hover: hover) and (pointer: fine) { - .a-select:hover, - .a-input:hover, - .a-textarea:hover, - .field-input:hover, - .field-textarea:hover, - .field-select:hover { - border-color: var(--line-2); - } + .a-select:hover, + .a-input:hover, + .a-textarea:hover, + .field-input:hover, + .field-textarea:hover, + .field-select:hover { + border-color: var(--line-2); + } } .a-select:focus, .a-input:focus, @@ -968,79 +826,79 @@ button.a-tile:disabled { .field-input:focus, .field-textarea:focus, .field-select:focus { - outline: none; - border-color: var(--accent-line); + outline: none; + border-color: var(--accent-line); } .a-input.invalid, .a-textarea.invalid, .field-input.invalid { - border-color: var(--err); + border-color: var(--err); } .a-select option.unavailable { - color: var(--text-3); + color: var(--text-3); } .field { - display: block; - margin-bottom: var(--sp-5); + display: block; + margin-bottom: var(--sp-5); } .field-label { - display: block; - font-size: var(--fs-aux); - font-weight: 500; - color: var(--text-2); - margin-bottom: var(--sp-2); + display: block; + font-size: var(--fs-aux); + font-weight: 500; + color: var(--text-2); + margin-bottom: var(--sp-2); } .field-hint { - margin: var(--sp-2) 0 0; - font-size: var(--fs-aux); - color: var(--text-3); + margin: var(--sp-2) 0 0; + font-size: var(--fs-aux); + color: var(--text-3); } .field-error { - margin: var(--sp-2) 0 0; - font-size: var(--fs-aux); - color: var(--err); + margin: var(--sp-2) 0 0; + font-size: var(--fs-aux); + color: var(--err); } /* Dark theme scrollbars (Firefox + Chromium) */ * { - scrollbar-width: thin; - scrollbar-color: var(--bg-4) transparent; + scrollbar-width: thin; + scrollbar-color: var(--bg-4) transparent; } *::-webkit-scrollbar { - width: 10px; - height: 10px; + width: 10px; + height: 10px; } *::-webkit-scrollbar-track { - background: transparent; + background: transparent; } *::-webkit-scrollbar-thumb { - background: var(--bg-4); - border-radius: var(--r-pill); - border: 2px solid transparent; - background-clip: padding-box; + background: var(--bg-4); + border-radius: var(--r-pill); + border: 2px solid transparent; + background-clip: padding-box; } *::-webkit-scrollbar-thumb:hover { - background: var(--text-3); - border: 2px solid transparent; - background-clip: padding-box; + background: var(--text-3); + border: 2px solid transparent; + background-clip: padding-box; } *::-webkit-scrollbar-corner { - background: transparent; + background: transparent; } /* Global keyboard focus (§四.8): every natively focusable element gets the gold ring. Component-scoped :focus-visible rules override this via higher specificity. */ :focus-visible { - outline: 2px solid var(--accent); - outline-offset: 2px; - border-radius: var(--r-sm); + outline: 2px solid var(--accent); + outline-offset: 2px; + border-radius: var(--r-sm); } /* Reduced-motion baseline for components that still carry their own @@ -1048,148 +906,146 @@ button.a-tile:disabled { ProgressBar indeterminate, StatusChip pulse) also handle this in their scoped styles. */ @media (prefers-reduced-motion: reduce) { - *, - *::before, - *::after { - animation-duration: 0.01ms !important; - animation-iteration-count: 1 !important; - transition-duration: 0.01ms !important; - } + *, + *::before, + *::after { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + } } -:root[data-reduce='1'] *, -:root[data-reduce='1'] *::before, -:root[data-reduce='1'] *::after { - animation-duration: 0.01ms !important; - animation-iteration-count: 1 !important; - transition-duration: 0.01ms !important; +:root[data-reduce="1"] *, +:root[data-reduce="1"] *::before, +:root[data-reduce="1"] *::after { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; } /* Refract shared behavior: desktop composition, paper surfaces, direct feedback. */ [hidden] { - display: none !important; + display: none !important; } button, input, textarea, select { - font-family: inherit; + font-family: inherit; } button { - transition: - transform var(--d-press) var(--ease-out), - background-color var(--d-state) ease, - color var(--d-state) ease; + transition: + transform var(--d-press) var(--ease-out), + background-color var(--d-state) ease, + color var(--d-state) ease; } button:active:not(:disabled) { - transform: scale(var(--press-scale)); + transform: scale(var(--press-scale)); } button:focus-visible, a:focus-visible, input:focus-visible, textarea:focus-visible, select:focus-visible { - outline: 2px solid var(--accent); - outline-offset: 3px; + outline: 2px solid var(--accent); + outline-offset: 3px; } .a-btn.primary { - border-color: transparent; - border-bottom-width: 0; - min-height: 40px; - padding: 0 18px; - border-radius: 8px; - font-weight: 550; + border-color: transparent; + border-bottom-width: 0; + min-height: 40px; + padding: 0 18px; + border-radius: 8px; + font-weight: 550; } .a-btn.secondary { - background: var(--surface); - border-radius: 8px; + background: var(--surface); + border-radius: 8px; } .a-stage { - background: var(--stage-bg); - border-radius: 14px; + background: var(--stage-bg); + border-radius: 14px; } .a-sprocket { - display: none; + display: none; } .a-photo { - box-shadow: none; - background: var(--surface); + box-shadow: none; + background: var(--surface); } .a-fix { - background: var(--surface); - color: var(--ok); + background: var(--surface); + color: var(--ok); } .a-input, .a-select, .field-input, .field-textarea { - background: var(--surface); - color: var(--text); + background: var(--surface); + color: var(--text); } @media (prefers-reduced-motion: reduce) { - *, - *::before, - *::after { - animation: none !important; - transition-duration: 0.01ms !important; - scroll-behavior: auto !important; - } + *, + *::before, + *::after { + animation: none !important; + transition-duration: 0.01ms !important; + scroll-behavior: auto !important; + } } -:root[data-reduce='1'] *, -:root[data-reduce='1'] *::before, -:root[data-reduce='1'] *::after { - animation: none !important; - transition-duration: 0.01ms !important; - scroll-behavior: auto !important; +:root[data-reduce="1"] *, +:root[data-reduce="1"] *::before, +:root[data-reduce="1"] *::after { + animation: none !important; + transition-duration: 0.01ms !important; + scroll-behavior: auto !important; } /* Shared reference interface: explicit hover states and press feedback. */ .reference-option { - position: relative; - isolation: isolate; - overflow: hidden; + position: relative; + isolation: isolate; + overflow: hidden; } .reference-option::after { - content: ''; - position: absolute; - inset: 0; - border-radius: inherit; - pointer-events: none; - opacity: 0; - background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='33' height='19'%3E%3Cpath d='M5.5 0h11l5.5 9.5-5.5 9.5h-11L0 9.5ZM22 9.5h11' fill='none' stroke='%23fff' stroke-opacity='.35'/%3E%3C/svg%3E"); - mask-image: radial-gradient( - 70px circle at var(--pointer-x, -100px) var(--pointer-y, -100px), - #000, - transparent - ); - transition: opacity 180ms ease; + content: ""; + position: absolute; + inset: 0; + border-radius: inherit; + pointer-events: none; + opacity: 0; + background-image: var(--lattice); + mask-image: radial-gradient( + 70px circle at var(--pointer-x, -100px) var(--pointer-y, -100px), + #000, + transparent + ); + transition: opacity 180ms ease; } @media (hover: hover) and (pointer: fine) { - .reference-option:hover::after { - opacity: 1; - } + .reference-option:hover::after { + opacity: 1; + } } -:root[data-reduce='1'] .reference-option::after { - display: none; +:root[data-reduce="1"] .reference-option::after { + display: none; } @media (prefers-reduced-motion: reduce) { - .reference-option::after { - display: none; - } + .reference-option::after { + display: none; + } } .a-btn.gold { - background: linear-gradient(135deg, #ffd700, #f7931a); - color: #211703; - border-color: #f5b042; - border-radius: 999px; - box-shadow: - 0 2px 12px #ffd70035, - inset 0 1px 0 #ffffff4d; + background: var(--grad-gold); + color: var(--tx-inv); + border-color: var(--gold); + border-radius: var(--r-pill); + box-shadow: var(--sh-gold); } .a-btn.gold:hover { - background: linear-gradient(135deg, #ffe250, #ffac3c); + box-shadow: var(--sh-gold-hover); } .a-btn.purple { - background: linear-gradient(135deg, #8b5cf6bf, #6366f1b3); - color: white; - border-color: #a78bfa40; + background: color-mix(in srgb, var(--sc-agent) 16%, var(--bg-3)); + color: color-mix(in srgb, var(--sc-agent) 70%, var(--hi)); + border-color: color-mix(in srgb, var(--sc-agent) 35%, transparent); } diff --git a/calliope-web/src/routes/create/+page.ts b/calliope-web/src/routes/create/+page.ts new file mode 100644 index 00000000..8dd58fbb --- /dev/null +++ b/calliope-web/src/routes/create/+page.ts @@ -0,0 +1,9 @@ +import { redirect } from "@sveltejs/kit"; +export const ssr = false; +export const prerender = false; +// B0 compatibility gateway: existing working editors remain the destination +// until the unified editor is delivered in B1. Preserve all source context. +export function load({ url }: { url: URL }) { + const kind = url.searchParams.get("kind") === "video" ? "video" : "image"; + redirect(307, `/${kind}${url.search}${new URL(url.href).hash}`); +} diff --git a/calliope-web/src/routes/model-connections/+page.ts b/calliope-web/src/routes/model-connections/+page.ts new file mode 100644 index 00000000..72c1cc44 --- /dev/null +++ b/calliope-web/src/routes/model-connections/+page.ts @@ -0,0 +1,9 @@ +import { redirect } from "@sveltejs/kit"; +export const ssr = false; +export const prerender = false; +// Both existing management pages stay usable until B2 combines their tabs. +export function load({ url }: { url: URL }) { + const destination = + url.searchParams.get("tab") === "llm" ? "/models" : "/connections"; + redirect(307, destination + url.search + new URL(url.href).hash); +} diff --git a/calliope-web/src/routes/project/[id]/+page.ts b/calliope-web/src/routes/project/[id]/+page.ts index 9cd802be..a2b8ac5d 100644 --- a/calliope-web/src/routes/project/[id]/+page.ts +++ b/calliope-web/src/routes/project/[id]/+page.ts @@ -1,9 +1,12 @@ -import { redirect } from '@sveltejs/kit'; -import type { PageLoad } from './$types'; +import { redirect } from "@sveltejs/kit"; +import type { PageLoad } from "./$types"; export const prerender = false; export const ssr = false; -export const load: PageLoad = ({ params }) => { - redirect(308, `/workbench/${params.id}`); +export const load: PageLoad = ({ params, url }) => { + redirect( + 308, + `/workbench/${params.id}${url.search}${new URL(url.href).hash}`, + ); }; diff --git a/calliope-web/src/routes/projects/+page.ts b/calliope-web/src/routes/projects/+page.ts index a8f63ebf..436bbb11 100644 --- a/calliope-web/src/routes/projects/+page.ts +++ b/calliope-web/src/routes/projects/+page.ts @@ -1,8 +1,8 @@ -import { redirect } from '@sveltejs/kit'; +import { redirect } from "@sveltejs/kit"; export const ssr = false; export const prerender = false; -export function load() { - redirect(308, '/'); +export function load({ url }: { url: URL }) { + redirect(308, "/" + url.search + new URL(url.href).hash); } diff --git a/calliope-web/src/routes/settings/+page.svelte b/calliope-web/src/routes/settings/+page.svelte index 474fa595..3e7a1763 100644 --- a/calliope-web/src/routes/settings/+page.svelte +++ b/calliope-web/src/routes/settings/+page.svelte @@ -1,741 +1,907 @@ {#snippet numberField(s: Settings, f: NumField)} - {@const limits = NUMERIC_LIMITS[f.key]} - + {@const limits = NUMERIC_LIMITS[f.key]} + {/snippet} - - {#snippet actions()} - - {#if isDirty}{/if} - {isDirty ? t('settings.unsaved') : t('settings.allSaved')} - - - - {/snippet} + + {#snippet actions()} + + {#if isDirty}{/if} + {isDirty ? t("settings.unsaved") : t("settings.allSaved")} + + + + {/snippet} -
- -
- {#if $settingsQuery.isLoading} -

{t('settings.loading')}

- {:else if $settingsQuery.isError} - - {:else if $settingsQuery.data} - {@const s = $settingsQuery.data} - {#if tab === 'queue'} -
-
-

{t('settings.queueSection')}

-

{t('settings.queueLead')}

-
-
- {#each QUEUE_FIELDS as f (f.key)} - {@render numberField(s, f)} - {/each} -
-
+
+ {#if tab === "appearance"} +
+
+

外观主题

+

同一个片场,换一种灯光。

+
+ +
+ {:else if $settingsQuery.isLoading} +

{t("settings.loading")}

+ {:else if $settingsQuery.isError} + + {:else if $settingsQuery.data} + {@const s = $settingsQuery.data} + {#if tab === "queue"} +
+
+

{t("settings.queueSection")}

+

{t("settings.queueLead")}

+
+
+ {#each QUEUE_FIELDS as f (f.key)} + {@render numberField(s, f)} + {/each} +
+
-
-
-
-

{t('settings.dryRunTitle')}

-

{t('settings.dryRunHint')}

-
- (draft.dry_run = next)} - /> -
-
- {:else if tab === 'agent'} -
-
-

{t('settings.agentRunSection')}

-

{t('settings.agentRunLead')}

-
-
- {@render numberField(s, { - key: 'agent_max_steps', - labelKey: 'settings.agentMaxStepsField', - hintKey: 'settings.agentMaxStepsHint', - step: '1', - })} -
- {@render numberField(s, { - key: 'agent_history_char_budget', - labelKey: 'settings.historyBudgetField', - step: '1000', - })} -
- {#each HISTORY_BUDGET_PRESETS as preset (preset.value)} - - {/each} -
-
-
-

{t('settings.historyBudgetHint')}

-
+
+
+
+

{t("settings.dryRunTitle")}

+

{t("settings.dryRunHint")}

+
+ (draft.dry_run = next)} + /> +
+
+ {:else if tab === "agent"} +
+
+

{t("settings.agentRunSection")}

+

{t("settings.agentRunLead")}

+
+
+ {@render numberField(s, { + key: "agent_max_steps", + labelKey: "settings.agentMaxStepsField", + hintKey: "settings.agentMaxStepsHint", + step: "1", + })} +
+ {@render numberField(s, { + key: "agent_history_char_budget", + labelKey: "settings.historyBudgetField", + step: "1000", + })} +
+ {#each HISTORY_BUDGET_PRESETS as preset (preset.value)} + + {/each} +
+
+
+

{t("settings.historyBudgetHint")}

+
-
-
-

{t('settings.hardeningSection')}

-

- {t('settings.hardeningLead')} - {t('settings.hardeningCalloutStrong')}{t('settings.hardeningCalloutBody')} -

-
- -
+
+
+

{t("settings.hardeningSection")}

+

+ {t("settings.hardeningLead")} + {t("settings.hardeningCalloutStrong")}{t( + "settings.hardeningCalloutBody", + )} +

+
+ +
-
-
-
-

{t('settings.shellSection')}

-

{t('settings.shellLead')}

-
- (draft.agent_shell_enabled = next)} - /> -
-
- -

- {t('settings.shellCalloutStrong')} - {t('settings.shellCalloutBody')} -

-
-

{t('settings.shellHint')}

-
+
+
+
+

{t("settings.shellSection")}

+

{t("settings.shellLead")}

+
+ (draft.agent_shell_enabled = next)} + /> +
+
+ +

+ {t("settings.shellCalloutStrong")} + {t("settings.shellCalloutBody")} +

+
+

{t("settings.shellHint")}

+
- - {:else if tab === 'storage'} -
-
-

{t('settings.storageSection')}

-

{t('settings.storageLead')}

-
-
- -

- {t('settings.storageCalloutStrong')} - {t('settings.storageCalloutBody')} -

-
- - - -
- {/if} - {/if} -
-
+ + {:else if tab === "storage"} +
+
+

{t("settings.storageSection")}

+

{t("settings.storageLead")}

+
+
+ +

+ {t("settings.storageCalloutStrong")} + {t("settings.storageCalloutBody")} +

+
+ + + +
+ {/if} + {/if} +
+
(pendingUrl = null)} + bind:open={leaveOpen} + title={t("settings.leaveTitle")} + message={t("settings.leaveMessage")} + confirmLabel={t("settings.leaveConfirm")} + danger + onconfirm={confirmLeave} + oncancel={() => (pendingUrl = null)} /> diff --git a/calliope-web/static/themes.css b/calliope-web/static/themes.css new file mode 100644 index 00000000..e955055b --- /dev/null +++ b/calliope-web/static/themes.css @@ -0,0 +1,547 @@ +/* HEL-726 approved themes. Loaded before the app for first-paint safety. */ +:root, +[data-theme="night"] { + color-scheme: dark; + /* surfaces: ink black with a faint blue cast, never pure #000 */ + --bg-0: #07080c; + --bg-1: #0c0e14; + --bg-2: #12151d; + --bg-3: #1a1e29; + --bg-4: #232838; + --line: rgb(var(--ink) / 0.06); + --line-2: rgb(var(--ink) / 0.11); + --line-3: rgb(var(--ink) / 0.18); + /* text: warm paper white */ + --tx-1: #f3efe6; + --tx-2: #aaa79e; + --tx-3: #6f6d68; + --tx-inv: #1a1206; + /* key light: tungsten gold */ + --gold: #e9b04b; + --gold-hi: #f8d68c; + --gold-lo: #b8771f; + --grad-gold: linear-gradient(135deg, #fbe1a1 0%, #efb54f 45%, #d98a26 100%); + /* scene lights — each workspace has its own colour temperature */ + --sc-image: #3fd8c6; + --sc-video: #ff8a3d; + --sc-agent: #a879ff; + --sc-project: #e9b04b; + --sc-manage: #7f93b5; + --scene: var(--gold); + /* status */ + --ok: #43d39a; + --warn: #f2b33d; + --err: #ff5d6c; + --info: #5aa9ff; + /* type */ + --font: + "PingFang SC", "HarmonyOS Sans SC", "Microsoft YaHei UI", "Microsoft YaHei", + system-ui, sans-serif; + --mono: "JetBrains Mono", "Cascadia Mono", Consolas, monospace; + --fs-display: 28px; + --fs-h1: 20px; + --fs-h2: 16px; + --fs-body: 13px; + --fs-small: 12px; + --fs-micro: 11px; + /* space (4 base) */ + --s1: 4px; + --s2: 8px; + --s3: 12px; + --s4: 16px; + --s5: 20px; + --s6: 24px; + --s8: 32px; + /* radius */ + --r-xs: 4px; + --r-sm: 6px; + --r-md: 10px; + --r-lg: 14px; + --r-xl: 20px; + --r-pill: 999px; + /* elevation */ + --sh-1: 0 1px 0 rgb(var(--ink) / 0.04) inset, 0 2px 8px rgba(0, 0, 0, 0.35); + --sh-2: 0 1px 0 rgb(var(--ink) / 0.05) inset, 0 12px 32px rgba(0, 0, 0, 0.5); + --sh-gold: + 0 6px 22px rgba(233, 160, 60, 0.35), + 0 0 0 1px rgba(255, 220, 150, 0.35) inset; + /* motion */ + --ease-out: cubic-bezier(0.22, 1, 0.36, 1); + --ease-io: cubic-bezier(0.65, 0, 0.35, 1); + --ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1); + --t-fast: 120ms; + --t-base: 200ms; + --t-slow: 320ms; + --t-reveal: 700ms; + /* layout */ + --rail: 68px; + --list: 272px; + --hist: 92px; + --head: 48px; + /* ---- theme-semantic tokens (v2 · 主题化) ---- */ + --ink: 255 255 255; /* overlay channel: hover tint, hairlines (white in dark, ink in light) */ + --hi: white; /* scene-coloured text is mixed toward this for contrast */ + --on-scene: #140a03; /* text on a solid scene/status colour */ + --on-ok: #06140e; + --sc-asset: #ff86b0; /* 素材资产 helper colour (agent roster) */ + --glass: color-mix(in srgb, var(--bg-1) 70%, transparent); + --scrim: rgba(4, 5, 8, 0.6); + --well: var(--bg-0); /* inputs, segmented track */ + --media-shadow: + 0 30px 80px rgba(0, 0, 0, 0.6), 0 0 0 1px rgb(255 255 255 / 0.07); + --sh-gold-hover: + 0 8px 28px rgba(233, 160, 60, 0.5), 0 0 0 1px rgba(255, 230, 170, 0.5) inset; + --brand-grad: radial-gradient( + circle at 30% 25%, + #fff3cf 0, + #efb54f 35%, + #9b5a12 100% + ); + --brand-glow: 0 0 24px rgba(233, 176, 75, 0.35); + --brand-ink: #1b1206; + --display-grad: linear-gradient( + 90deg, + var(--gold-hi), + var(--gold) 60%, + #ff9b5a + ); + --glow: 1; /* 1 = glows on (dark themes), 0 = off (light / neutral) */ + /* signature effects */ + --gen-bg: #0b0d12; + --grain: rgba(255, 255, 255, 0.08); + --beam: linear-gradient( + 90deg, + transparent, + color-mix(in srgb, var(--scene) 30%, transparent), + rgba(255, 255, 255, 0.25), + transparent + ); + --lattice: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='24' viewBox='0 0 14 24'%3E%3Cpath d='M7 0l7 4v8l-7 4-7-4V4zM7 16v8' fill='none' stroke='white' stroke-opacity='.16' stroke-width='.8'/%3E%3C/svg%3E"); + --scanline: rgb(var(--ink) / 0.012); + --stage-tint: 7%; + /* agent atmosphere */ + --aur-1: #7c4dff; + --aur-2: #ff5fa8; + --aur-3: #2fd5c5; + --aur-op: 0.33; + --agent-hero: linear-gradient( + 120deg, + rgba(32, 20, 58, 0.9), + rgba(14, 12, 26, 0.92) 55%, + rgba(10, 22, 30, 0.9) + ); + --agent-title: linear-gradient(90deg, #fff, #d9c6ff 50%, #ffb3d9); + --orb-core: radial-gradient( + circle at 35% 30%, + #fff 0, + #e3d4ff 12%, + #a879ff 40%, + #4b1fa8 75%, + transparent 76% + ); + --particle: 214, 190, 255; + --fail-a: #1a1215; + --fail-b: #140f12; +} + +[data-theme="day"] { + color-scheme: light; + --bg-0: #e9e4da; + --bg-1: #f4f1ea; + --bg-2: #fbf9f5; + --bg-3: #ffffff; + --bg-4: #efe9df; + --ink: 38 30 18; + --line: rgb(38 30 18 / 0.09); + --line-2: rgb(38 30 18 / 0.15); + --line-3: rgb(38 30 18 / 0.26); + --tx-1: #1f1b15; + --tx-2: #55504a; + --tx-3: #736d65; + --tx-inv: #ffffff; + --gold: #a65a0c; + --gold-hi: #8a4a06; + --gold-lo: #c98a3a; + --grad-gold: linear-gradient(135deg, #bd6512 0%, #a9580d 55%, #984d08 100%); + --sc-image: #0b8378; + --sc-video: #c2511a; + --sc-agent: #6d45d0; + --sc-project: #a65a0c; + --sc-manage: #4c6286; + --sc-asset: #b93a72; + --ok: #11804f; + --warn: #9a6400; + --err: #c42b41; + --info: #2263c2; + --hi: #141109; + --on-scene: #ffffff; + --on-ok: #ffffff; + --sh-1: + 0 1px 0 rgba(255, 255, 255, 0.7) inset, 0 1px 2px rgba(60, 44, 20, 0.1); + --sh-2: + 0 1px 0 rgba(255, 255, 255, 0.8) inset, 0 12px 32px rgba(60, 44, 20, 0.14), + 0 2px 6px rgba(60, 44, 20, 0.06); + --sh-gold: + 0 4px 14px rgba(166, 90, 12, 0.28), 0 0 0 1px rgba(120, 60, 0, 0.18) inset; + --sh-gold-hover: + 0 6px 20px rgba(166, 90, 12, 0.38), 0 0 0 1px rgba(120, 60, 0, 0.24) inset; + --glass: color-mix(in srgb, var(--bg-1) 88%, transparent); + --scrim: rgba(40, 32, 20, 0.34); + --well: #ffffff; + --media-shadow: + 0 14px 36px rgba(50, 38, 18, 0.2), 0 0 0 1px rgba(38, 30, 18, 0.1); + --brand-grad: linear-gradient(145deg, #2c261d, #14110c); + --brand-glow: 0 2px 8px rgba(40, 30, 15, 0.25); + --brand-ink: #f2c46d; + --display-grad: linear-gradient(90deg, #a65a0c, #c2511a); + --glow: 0; + --gen-bg: #e3ddd1; + --grain: rgba(40, 30, 18, 0.1); + --beam: linear-gradient( + 90deg, + transparent, + color-mix(in srgb, var(--scene) 24%, transparent), + rgba(255, 255, 255, 0.85), + transparent + ); + --lattice: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='24' viewBox='0 0 14 24'%3E%3Cpath d='M7 0l7 4v8l-7 4-7-4V4zM7 16v8' fill='none' stroke='%23261e12' stroke-opacity='.16' stroke-width='.8'/%3E%3C/svg%3E"); + --scanline: transparent; + --stage-tint: 6%; + --aur-1: #b9a0ff; + --aur-2: #ffb39a; + --aur-3: #8fe0d4; + --aur-op: 0.42; + --agent-hero: linear-gradient(120deg, #efe7ff, #faf7f1 55%, #e4f3ef); + --agent-title: linear-gradient(90deg, #24163f, #5b34c4 55%, #a2346f); + --orb-core: radial-gradient( + circle at 35% 30%, + #fff 0, + #efe6ff 14%, + #a98cf0 42%, + #6d45d0 75%, + transparent 76% + ); + --particle: 109, 69, 208; + --fail-a: #f6e3e3; + --fail-b: #efd6d7; +} + +[data-theme="grade"] { + color-scheme: dark; + --bg-0: #1b1b1c; + --bg-1: #222223; + --bg-2: #29292b; + --bg-3: #323234; + --bg-4: #3c3c3f; + --ink: 255 255 255; + --line: rgb(255 255 255 / 0.075); + --line-2: rgb(255 255 255 / 0.13); + --line-3: rgb(255 255 255 / 0.22); + --tx-1: #ecebe8; + --tx-2: #acaba6; + --tx-3: #85847f; + --tx-inv: #141516; + --r-xs: 2px; + --r-sm: 4px; + --r-md: 6px; + --r-lg: 8px; + --r-xl: 10px; + --gold: #d9dde2; + --gold-hi: #f2f4f6; + --gold-lo: #9aa1aa; + --grad-gold: linear-gradient(180deg, #f6f7f8 0%, #d5d9de 100%); + --sc-image: #6fc2b7; + --sc-video: #d8a06c; + --sc-agent: #a598d8; + --sc-project: #d4bc8a; + --sc-manage: #909cae; + --sc-asset: #d394b0; + --ok: #4cc38f; + --warn: #e3a93b; + --err: #ee5d70; + --info: #5d9fe3; + --hi: white; + --on-scene: #141516; + --on-ok: #06140e; + --sh-1: + 0 1px 0 rgba(255, 255, 255, 0.035) inset, 0 2px 6px rgba(0, 0, 0, 0.35); + --sh-2: + 0 1px 0 rgba(255, 255, 255, 0.04) inset, 0 12px 30px rgba(0, 0, 0, 0.45); + --sh-gold: + 0 4px 14px rgba(0, 0, 0, 0.45), 0 0 0 1px rgba(255, 255, 255, 0.55) inset; + --sh-gold-hover: + 0 6px 18px rgba(0, 0, 0, 0.5), 0 0 0 1px rgba(255, 255, 255, 0.7) inset; + --glass: color-mix(in srgb, var(--bg-1) 82%, transparent); + --scrim: rgba(0, 0, 0, 0.55); + --well: #18181a; + --media-shadow: + 0 18px 44px rgba(0, 0, 0, 0.5), 0 0 0 1px rgba(255, 255, 255, 0.07); + --brand-grad: linear-gradient(145deg, #f1f2f3, #aeb3ba); + --brand-glow: none; + --brand-ink: #17181a; + --display-grad: linear-gradient(90deg, #ffffff, #bfc5cc); + --glow: 0; + --gen-bg: #1a1b1d; + --grain: rgba(255, 255, 255, 0.06); + --beam: linear-gradient( + 90deg, + transparent, + rgba(255, 255, 255, 0.07), + rgba(255, 255, 255, 0.22), + transparent + ); + --scanline: transparent; + --stage-tint: 0%; + --aur-1: #5d5a80; + --aur-2: #7a6070; + --aur-3: #4f7470; + --aur-op: 0.16; + --agent-hero: linear-gradient(120deg, #202027, #1b1c1e 60%, #1b2020); + --agent-title: linear-gradient(90deg, #ffffff, #d4d0ea); + --orb-core: radial-gradient( + circle at 35% 30%, + #fff 0, + #e7e5f2 14%, + #a598d8 44%, + #3e3a5c 76%, + transparent 77% + ); + --particle: 205, 205, 215; + --fail-a: #211a1b; + --fail-b: #1b1617; +} + +[data-theme="bluehour"] { + color-scheme: dark; + --bg-0: #0a1029; + --bg-1: #0f1838; + --bg-2: #162148; + --bg-3: #1f2c5c; + --bg-4: #2a386e; + --r-sm: 8px; + --r-md: 12px; + --r-lg: 18px; + --r-xl: 26px; + --ink: 214 224 255; + --line: rgb(214 224 255 / 0.07); + --line-2: rgb(214 224 255 / 0.12); + --line-3: rgb(214 224 255 / 0.2); + --tx-1: #eef1fb; + --tx-2: #a9b1cc; + --tx-3: #7680a2; + --tx-inv: #2b0c05; + --gold: #ff9a6c; + --gold-hi: #ffc7a8; + --gold-lo: #d9603f; + --grad-gold: linear-gradient(135deg, #ffd3b4 0%, #ff9a6c 45%, #f0644f 100%); + --sc-image: #4fd6ea; + --sc-video: #ffbb55; + --sc-agent: #b592ff; + --sc-project: #ff9a6c; + --sc-manage: #8aa0dc; + --sc-asset: #ff86c0; + --ok: #4fd69c; + --warn: #ffc94d; + --err: #ff4d6d; + --info: #69b4ff; + --hi: white; + --on-scene: #1a0b04; + --on-ok: #05150e; + --sh-1: + 0 1px 0 rgba(214, 224, 255, 0.05) inset, 0 2px 8px rgba(0, 0, 10, 0.4); + --sh-2: + 0 1px 0 rgba(214, 224, 255, 0.06) inset, 0 12px 32px rgba(0, 0, 12, 0.55); + --sh-gold: + 0 6px 24px rgba(255, 120, 90, 0.38), + 0 0 0 1px rgba(255, 214, 186, 0.42) inset; + --sh-gold-hover: + 0 8px 30px rgba(255, 120, 90, 0.52), + 0 0 0 1px rgba(255, 226, 206, 0.55) inset; + --scrim: rgba(4, 6, 16, 0.62); + --media-shadow: + 0 30px 80px rgba(0, 0, 14, 0.62), 0 0 0 1px rgba(214, 224, 255, 0.08); + --brand-grad: radial-gradient( + circle at 30% 25%, + #ffe4d2 0, + #ff9a6c 38%, + #a3363a 100% + ); + --brand-glow: 0 0 26px rgba(255, 140, 100, 0.42); + --brand-ink: #2b0c05; + --display-grad: linear-gradient(90deg, #ffd3b4, #ff9a6c 55%, #ff7096); + --glow: 1; + --gen-bg: #0b1124; + --grain: rgba(214, 224, 255, 0.08); + --beam: linear-gradient( + 90deg, + transparent, + color-mix(in srgb, var(--scene) 34%, transparent), + rgba(255, 236, 220, 0.3), + transparent + ); + --stage-tint: 10%; + --aur-1: #3e5bff; + --aur-2: #ff7a8a; + --aur-3: #2fd5e5; + --aur-op: 0.36; + --agent-hero: linear-gradient( + 120deg, + rgba(26, 32, 84, 0.92), + rgba(12, 16, 40, 0.94) 55%, + rgba(44, 18, 52, 0.9) + ); + --agent-title: linear-gradient(90deg, #ffffff, #c9d4ff 50%, #ffb8a0); + --orb-core: radial-gradient( + circle at 35% 30%, + #fff 0, + #ffe1cf 12%, + #ff9a6c 38%, + #5a2fb0 76%, + transparent 77% + ); + --particle: 255, 206, 186; + --fail-a: #1d1220; + --fail-b: #160e1a; +} + +@media (max-width: 1366px) { + :root { + --list: 248px; + --hist: 84px; + } +} +@media (max-width: 1280px) { + :root { + --rail: 60px; + --list: 236px; + --hist: 76px; + --head: 44px; + } +} + +/* Compatibility names for existing pages; no independent colour palette. */ +:root { + --accent-grad-hover: var(--grad-gold); + --accent-ring: color-mix(in srgb, var(--gold) 25%, transparent); + --accent-wash: var(--accent-soft); + --btn-glow-hover: var(--sh-gold-hover); + --composer-bg: var(--glass); + --composer-focus: var(--bg-2); + --float-bg: var(--bg-2); + --on-media: #fff; + --on-media-line: #ffffff66; + --sel-shadow: 0 0 0 1px var(--gold); + --bg: var(--bg-0); + --paper: var(--bg-2); + --lime: color-mix(in srgb, var(--sc-image) 14%, var(--bg-2)); + --rose: color-mix(in srgb, var(--sc-asset) 14%, var(--bg-2)); + --text: var(--tx-1); + --text-2: var(--tx-2); + --text-3: var(--tx-3); + --accent: var(--gold); + --accent-hi: var(--gold-hi); + --accent-lo: var(--gold-lo); + --accent-ink: var(--tx-inv); + --accent-soft: color-mix(in srgb, var(--gold) 9%, transparent); + --accent-line: color-mix(in srgb, var(--gold) 35%, transparent); + --accent-grad: var(--grad-gold); + --accent-glow: var(--sh-gold); + --btn-glow: var(--sh-gold); + --page-glow: linear-gradient(transparent, transparent); + --ok-soft: color-mix(in srgb, var(--ok) 9%, transparent); + --run: var(--info); + --run-soft: color-mix(in srgb, var(--info) 9%, transparent); + --run-grad: linear-gradient(90deg, var(--info), var(--sc-agent)); + --err-solid: var(--err); + --err-soft: color-mix(in srgb, var(--err) 9%, transparent); + --err-line: color-mix(in srgb, var(--err) 40%, transparent); + --warn-soft: color-mix(in srgb, var(--warn) 9%, transparent); + --overlay: var(--scrim); + --media-scrim: #101017ee; + --media-text: #fff; + --media-line: #ffffff66; + --stage-bg: var(--bg-0); + --fs-title: 15px; + --fs-section: 14px; + --fs-body-sm: 12.5px; + --fs-aux: 12px; + --fs-mini: 11px; + --lh-body: 1.55; + --sp-1: 4px; + --sp-2: 6px; + --sp-3: 8px; + --sp-4: 10px; + --sp-5: 12px; + --sp-6: 16px; + --sp-7: 20px; + --sp-8: 28px; + --shadow-float: var(--sh-2); + --rail-w: var(--rail); + --topbar-h: var(--head); + --tabbar-h: 0px; + --sprocket: none; + --frame-w: 0px; + --frame-arm: 12px; + --frame-inset: -4px; + --paper-w: 4px; + --ease-in-out: cubic-bezier(0.77, 0, 0.175, 1); + --ease-drawer: cubic-bezier(0.32, 0.72, 0, 1); + --d-press: 160ms; + --d-state: 160ms; + --d-pop: 180ms; + --d-leave: 200ms; + --d-enter: 220ms; + --d-drawer: 250ms; + --d-flash: 900ms; + --d-progress: 400ms; + --press-scale: 0.97; + --avatar-1: color-mix(in srgb, var(--sc-project) 20%, var(--bg-2)); + --avatar-2: color-mix(in srgb, var(--sc-image) 20%, var(--bg-2)); + --avatar-3: color-mix(in srgb, var(--sc-video) 20%, var(--bg-2)); + --avatar-4: color-mix(in srgb, var(--sc-asset) 20%, var(--bg-2)); + --avatar-5: color-mix(in srgb, var(--sc-agent) 20%, var(--bg-2)); + --id-1: var(--sc-project); + --id-2: var(--sc-image); + --id-3: var(--sc-video); + --id-4: var(--sc-asset); + --id-5: var(--sc-agent); + --surface: var(--bg-2); + --surface-2: var(--bg-3); + --surface-3: var(--bg-4); + --border: var(--line); + --border-strong: var(--line-2); + --border-hover: var(--line-2); + --accent-dim: var(--accent-soft); + --success: var(--ok); + --success-dim: var(--ok-soft); + --info-dim: color-mix(in srgb, var(--info) 9%, transparent); + --error: var(--err); + --error-dim: var(--err-soft); + --warning: var(--warn); + --bg-primary: var(--bg); + --bg-surface: var(--bg-2); + --bg-elevated: var(--bg-3); + --text-primary: var(--text); + --text-secondary: var(--text-2); + --text-muted: var(--text-3); + --font-a-mono: var(--mono); + --font-display: var(--font); + --font-body: var(--font); + --font-mono: var(--mono); + --radius-sm: var(--r-sm); + --radius-md: var(--r-md); + --radius-lg: var(--r-lg); + --space-sm: var(--sp-3); + --space-md: var(--sp-6); + --pill-bg: var(--bg-3); + --pill-border: var(--line-2); + --pill-radius: var(--r-pill); + --pill-height: 36px; + --pill-padding: 0 14px; + --pill-font-size: var(--fs-body); +} + +html { + background: var(--bg-0); + color: var(--tx-1); +} diff --git a/design/nocturne/design-spec-v1.md b/design/nocturne/design-spec-v1.md new file mode 100644 index 00000000..1fad27d3 --- /dev/null +++ b/design/nocturne/design-spec-v1.md @@ -0,0 +1,140 @@ +# 夜片场 Nocturne Set — AIGC 工作台设计规范 v1(待白栖知确认) + +> 视觉总监出具。本文件和 `index.html`(交互样本)、`shots/*.png` 一起,是施工和验收的唯一品味依据。样本中的画面都是程序生成的示意图,不是真实生成结果,也不调用任何接口。 + +## 0. 一句话方向 + +**深夜片场:墨黑的棚里只亮一盏钨丝灯,作品是唯一被照亮的东西。** +本项目本质是“从故事到成片”的制片工具,所以主题元素取自片场,而不是暗房或印刷:取景框角标(选中)、扫描光束(生成中)、显影(结果到达)、场记编号与时间码(等宽数字)、每个工作区一种“场景色温”。参考站学的是它的骨架:紧凑、左侧双栏、底部输入座、参数胶囊、按场景变化的主题色;没有照抄它的配色。 + +## 1. 事实盘点(已核实) + +| 项 | 事实 | 来源 | +|---|---|---| +| 前端路由 | 项目首页 `/`、工作台 `/workbench/[id]`、生图 `/image`、生视频 `/video`、任务 `/tasks`、智能体 `/agents`、画布 `/canvas/[id]`、素材库 `/library`、ComfyUI 连接 `/connections`、模型设置 `/models`、设置 `/settings`、三维搭景 `/build-scene`、试验台 `/playground` | 仓库 `calliope-web/src/routes` | +| 五阶段 | 剧本 → 视觉 → 分镜 → 素材 → 镜头脚本;每阶段一个智能体,草稿需人工确认后才交给下一阶段,可恢复已确认版 | `i18n/zh.ts` workbench.* | +| 工作流文件 | 仓库 `example_ComfyUI_workflows` 共 **156** 个文件:**10** 个是 API 格式,**146** 个是 ComfyUI 界面格式(需重新导出 API 格式并做输入映射才能执行) | 逐个文件检查 `nodes`/`last_node_id` 字段 | +| 已可执行 | 测试站 `/api/workflows` 当前 **9** 条(图片 5、视频 4) | 测试站接口,只读 | +| 素材槽 | 参考图(1–N)、遮罩、首帧、尾帧(可选)、参考视频、参考音频;按工作流声明出现 | studio.* 文案 + 组件 | +| 模型设置 | 当前仅开放 LLM;图片/视频模型标“后续开放” | models.* | +| 历次投诉 | 结果被挤小、历史穿插在生成区、大片空白、窗口不全、生成按钮要滚动才看到、视频自动播放且空格乱切、找不到播放键、百余工作流难找、智能体只做成了聊天 | 主卡 10/13–10/14 评论 | + +## 2. 页面分工与核心路径 + +**用户要做的三件事**,决定了三个主场;其余页面都是它们的仓库或后台。 + +| 用户要做什么 | 从哪里进入 | 连续操作 | 看到/处理结果 | 回到项目 | +|---|---|---|---|---| +| A. 推进一个片子 | 工作室首页「继续创作」大卡 / 项目卡 | 项目页顶部五阶段 → 当前阶段的内容网格 → 右侧本阶段助手(补充要求/重新生成) | 草稿条提示“待确认”,底栏固定「确认,进入下一阶段」 | 本身就在项目里 | +| B. 生成一张图/一段视频 | 侧栏「创作台」或项目镜头卡「去生视频」 | 左列找工作流 → 底座放素材、写正反提示词 → 参数胶囊 → 生成 | 中间舞台整幅显示;右侧历史竖条单独存在,点开时顶部提示“正在查看历史”,可复用或回到最新 | 结果下方「存入项目」→ 选项目与阶段,进入“待确认” | +| C. 让助手干活 | 侧栏「智能体」或项目阶段的「对话」 | 首页大输入框写想法、绑定项目/模型/技能 → 任务页看计划、工具调用 | 右侧成果栏是可编辑草稿(分镜表/角色/提示词),单镜可重画 | 「带回项目待确认」→ 项目页出现紫色草稿条 | + +**导航(左侧 68px 总导航,图标+文字常显)**:工作室、项目、创作台、智能体、画布、素材库 | 任务、工作流、模型连接 | 底部:队列、动效开关、设置。 +- 双左栏只在创作台出现(总导航 68 + 工作流列 272),两栏分别回答“去哪个功能”和“用哪个工作流”,宽度差一倍,不会混淆。 +- 原「生图」「生视频」两个路由合并为创作台,用顶部分段切换;切换时各自记住工作流、参数和输入。 +- 原「ComfyUI 连接」与「模型设置」合为「模型连接」,四个页签:ComfyUI 连接 / 大语言模型 / 图片模型 / 视频模型。 + +**设计判断(为什么这样分)** +1. 创作台舞台是“作品优先”:不放详情、不放历史,结果按真实比例 `contain` 适配剩余空间;操作条固定在作品正下方,不覆盖画面。 +2. 历史是独立竖条(92px),只承担“回看”;回看是显式状态(蓝色提示条),绝不替换当前输入。 +3. 工作流先按“我要做什么”(任务胶囊)缩小,再按模型族分组,置顶永远在最上;长文件名只在悬停/表头次要位置出现,主名是人话名。 +4. “待配置”的工作流默认隐藏(开关「仅可运行」),避免把 146 个未映射文件冒充可用工具;关掉开关能看到并引导去映射。 +5. 智能体不是聊天:任务列表 + 计划 + 工具卡 + 成果栏三栏,成果有归属(项目·阶段)和确认流程。 +6. 管理页(任务/工作流/模型连接/素材库)共用“左筛选 220 / 中列表 / 右详情 360”,详情不跳页。 + +## 3. 设计 Token + +```css +/* 表面 */ --bg-0:#07080c; --bg-1:#0c0e14; --bg-2:#12151d; --bg-3:#1a1e29; --bg-4:#232838; +/* 描边 */ --line:rgba(255,255,255,.06); --line-2:.11; --line-3:.18; +/* 文字 */ --tx-1:#f3efe6; --tx-2:#aaa79e; --tx-3:#6f6d68; --tx-inv:#1a1206; +/* 主光 */ --gold:#e9b04b; --gold-hi:#f8d68c; --gold-lo:#b8771f; + --grad-gold:linear-gradient(135deg,#fbe1a1,#efb54f 45%,#d98a26); +/* 场景色温 */ --sc-image:#3fd8c6; --sc-video:#ff8a3d; --sc-agent:#a879ff; --sc-project:#e9b04b; --sc-manage:#7f93b5; +/* 状态 */ --ok:#43d39a; --warn:#f2b33d; --err:#ff5d6c; --info:#5aa9ff; +/* 字号 */ --fs-display:28px/700; --fs-h1:20px/600; --fs-h2:16px/600; --fs-body:13px/400 lh1.55; --fs-small:12px; --fs-micro:11px; +/* 字体 */ --font:"PingFang SC","HarmonyOS Sans SC","Microsoft YaHei UI",sans-serif; --mono:"JetBrains Mono",Consolas,monospace; +/* 间距 */ --s1:4 --s2:8 --s3:12 --s4:16 --s5:20 --s6:24 --s8:32; +/* 圆角 */ --r-xs:4 --r-sm:6 --r-md:10 --r-lg:14 --r-xl:20 --r-pill:999; +/* 阴影 */ --sh-1 内高光+2/8; --sh-2 内高光+12/32 黑50%; --sh-gold 金色外发光(仅主按钮); +/* 动效 */ --ease-out:cubic-bezier(.22,1,.36,1); --ease-io:(.65,0,.35,1); --ease-spring:(.34,1.56,.64,1); + --t-fast:120ms; --t-base:200ms; --t-slow:320ms; --t-reveal:700ms; +/* 布局 */ --rail:68; --list:272; --hist:92; --head:48; +``` + +**色彩规则** +- 一个页面只有一个金色主按钮(生成 / 确认 / 带回项目 / 开始任务)。 +- 场景色只用于“位置与焦点”:选中边框、取景框角标、进度条、焦点光环、导航指示条;禁止做大面积底色。智能体页是唯一允许场景色做氛围(极光、光球)的页面。 +- 数字信息(编号、时间码、比例、种子、延迟)一律等宽字体。 + +## 4. 组件与状态 + +| 组件 | 尺寸 | 默认 | 悬停 | 选中/按下 | 禁用/异常 | +|---|---|---|---|---|---| +| 按钮 `.btn` | 26/32/38 高 | bg-3 + line-2 | bg-4 + line-3 | 下移 1px | — | +| 主按钮 `.btn.primary` | 32/38 | 金渐变 + sh-gold | 提亮 6% + 扫光一次 | 下移 1px | 去饱和 60% 亮度,旁边红字写“还缺:…” | +| 胶囊 `.chip` | 24–26 | bg-2 | 文字变亮 | 场景色 13% 底 + 60% 描边 | — | +| 参数胶囊 `.pchip` | 28 | 透明 4% | 描边变亮 | 点开弹层 | — | +| 工作流行 `.wfi` | 50 | 透明 | 光栅悬停 + 3% 底 | 场景色渐变底 + 35% 描边 | 待配置:族色块灰化、名字降一级、黄标签 | +| 素材槽 `.slot` | 56(矮屏 50) | 虚线 line-3 | 场景色虚线 | 实线 + 缩略图 + 悬停出现 × | 必填未填:黄色虚线 | +| 作品框 `.frame` | 适配 | 取景框角标常显 | — | — | 失败:斜纹底 + 原因 + 重试 | +| 视频播放器 | 画面 + 40 控制栏 | **暂停**,中央大播放键 | 大播放键放大 1.08 | 播放后大键隐藏,控制栏常显 | — | +| 草稿条 | 44 | 紫色渐变 + 描边 | — | — | — | +| 弹层 `.pop` | ≤620×64vh | 底部固定“取消/保存”,内部滚动 | — | — | — | + +**交互铁律** +- 视频永不自动播放;切换结果保持暂停;空格只在播放器获得焦点时切换。 +- 生成按钮、确认按钮、带回项目按钮永远在首屏固定位置,不随内容滚走。 +- 参数弹层“取消不改参数”;图片与视频分别记忆参数。 +- 切换工作流保留提示词;新工作流有反向提示词时自动出现右栏(左正右反)。 +- 置顶不切换当前工作流、不清空输入;置顶分图片/视频保存。 + +## 5. 图标与插图 + +- 图标:1.7px 线性描边、圆角端点、24 网格;尺寸 14/18/22。不用填充图标,置顶激活例外(填充金色)。 +- 工作流族色块:34px 圆角方块 + 族缩写(Qwen/Kr2/FLX/Z/H3/Wan/GPT/NB/可灵/即梦),待配置时灰化。 +- 插图:阶段助手卡与工作室大卡用真实项目产物做底图(无产物时用场景色渐变 + 胶片颗粒),不用卡通吉祥物。 + +## 6. 动效规范 + +| 名称 | 触发 | 时长 · 曲线 | 可打断 | 减少动效 | +|---|---|---|---|---| +| 页面进入 | 切一级页 | 320ms · ease-out,上移 6px 淡入 | 是 | 直接出现 | +| 光栅悬停 | 指针进入列表行/卡片 | 320ms 渐显,跟随指针的光斑 + 蜂窝纹 | 是 | 只变背景亮度 | +| 取景框 | 选中工作流/作品/历史 | 200ms · ease-out,角标外扩 2px | 是 | 保留角标 | +| 扫描光束 | 生成中 | 1.6s 循环 · ease-io | 取消即停 | 只留进度条 | +| 显影 | 结果到达 | 700ms · ease-out(模糊/过曝 → 清晰) | 点击跳过 | 淡入 | +| 主按钮扫光 | 悬停主按钮 | 700ms 一次 | 是 | 关闭 | +| 弹层 | 打开参数/菜单 | 200ms · 上移 6px + 缩放 .98 | Esc / 点外部 | 直接出现 | +| 提示 Toast | 操作完成 | 320ms spring,3.2s 消失 | 是 | 淡入 | +| 智能体氛围 | 进入智能体页 | 极光 18s 漂移、粒子上升、光球呼吸 4.5s | 离开页面即停 | 全部关闭 | + +**性能降级**:粒子用单 canvas、≤46 个,离开页面停止 rAF;极光用 3 个模糊圆、transform 动画;`prefers-reduced-motion` 或侧栏「动效」开关关闭时加 `html.reduce`,去掉所有位移/循环,仅保留颜色与状态变化。 + +## 7. 桌面断点 + +| 窗口宽 | 总导航 | 工作流列 | 历史栏 | 其他 | +|---|---|---|---|---| +| ≥1600 | 68 | 272 | 92 | 工作室右栏 340,智能体成果栏 420 | +| 1367–1599 | 68 | 272 | 92 | 标准(1440×900 样稿) | +| 1281–1366 | 68 | 248 | 84 | 页头隐藏工作流文件名 | +| ≤1280 | 60 | 236 | 76 | 页头 44;历史栏可收起 | +| 高 ≤800 | 导航压缩 | 提示词 48 高、素材槽 50;长提示词点「展开」到 28vh,内部滚动 | + +最小支持 1280×720(已出样稿 `14-create-image-1280x720.png`、`15-agent-task-1280x720.png`)。不做手机版。 + +## 8. 待确认事项(请总管转白栖知) + +1. **主题方向**:「夜片场」(墨黑 + 钨丝金 + 各区场景色温)是否认可?这是本卡唯一主方向,不再并列给多套。 +2. **生图/生视频合并**为一个「创作台」,顶部切换图片/视频(各自记忆)——是否同意? +3. **试验台(Playground)并入创作台**、旧入口下线;**三维搭景**不进一级导航,入口放在项目「镜头脚本」阶段和视频参考槽——是否同意? +4. 「仅可运行」默认打开:146 个未映射文件默认不出现在创作台,只在「工作流库」里显示待配置——是否接受? +5. ComfyUI 连接与模型设置合并为「模型连接」一页四个页签——是否同意? +6. 云端图片/视频模型(GPT-Image-2、Nano Banana、可灵、即梦)在样本中按“已接入”展示;当前后端只开放 LLM,接入属于新增能力,需另行排期。 + +## 9. 施工提示(给总工/施工员) + +- Token 直接替换现有 `app.css` 变量体系;组件按第 4 节状态表逐一对照。 +- 舞台尺寸用容器实测(ResizeObserver)计算 contain 尺寸,不用固定高度;视频控件高度计入。 +- 历史栏、工作流列、成果栏、弹层内容都各自滚动;页面本身不滚(工作室、规范页除外)。 +- 验收看图看操作:1440×900、1366×768、1280×720 三档下,作品完整、主按钮可见、视频默认暂停。 diff --git a/design/nocturne/design-spec-v2.md b/design/nocturne/design-spec-v2.md new file mode 100644 index 00000000..39aa1230 --- /dev/null +++ b/design/nocturne/design-spec-v2.md @@ -0,0 +1,105 @@ +# 夜片场 · 多主题规范 v2(待白栖知看样确认) + +> 视觉总监出具,在 HEL-716《夜片场》规范 v1 的基础上**增补**主题。v1 中的布局、组件、动效和断点全部继续有效,本文件只写新增和改动的部分。样本使用的是示例画面和演示状态,不调用真实生成、不产生费用。 + +## 0. 原则 + +**同一个片场,换一种灯光。** 四套主题共用同一套布局、功能、操作顺序和动效,只改变语义变量和少量“气质”参数(圆角、发光开关、氛围层)。组件 CSS 里只能引用语义变量,不允许写死颜色;主题差异只能写在 `[data-theme="…"]` 块中。 + +| ID | 中文名 | 气质与适用场景 | 默认 | +|---|---|---|---| +| `night` | **夜片场** Nocturne | 墨黑底、钨丝金主键、各工作区有自己的场景色温。夜间、沉浸式创作。 | ✅ 默认(即已认可的原版,一个像素都没改) | +| `day` | **日光棚** Daylight | 暖白纸面、墨色文字、深琥珀主键;不使用发光,靠描边、实色和对比度区分。白天办公、长时间审稿。 | | +| `grade` | **调色间** Grading | 中性深灰、白色主键、场景色降低饱和度、舞台不加色调;圆角收紧到 2–8px,更像工具。用于判断作品色彩,适合长时间看片。 | | +| `bluehour` | **蓝调时刻** Blue Hour | 靛蓝到紫的天色渐变、珊瑚橙主键、圆角放大到 8–26px、主按钮做成胶囊形,氛围最浓。用于提案、展示和灵感发散。 | | + +## 1. 语义变量(施工直接使用) + +基础变量名沿用 v1;v2 新增的语义变量标 ★。 + +| 语义 | 变量 | 夜片场 | 日光棚 | 调色间 | 蓝调时刻 | +|---|---|---|---|---|---| +| 页面底 / 输入凹槽 | `--bg-0` | #07080c | #e9e4da | #1b1b1c | #0a1029 | +| 面板(导航、列表、历史栏) | `--bg-1` | #0c0e14 | #f4f1ea | #222223 | #0f1838 | +| 卡片 | `--bg-2` | #12151d | #fbf9f5 | #29292b | #162148 | +| 浮起层 / 按钮 | `--bg-3` | #1a1e29 | #ffffff | #323234 | #1f2c5c | +| 悬停 | `--bg-4` | #232838 | #efe9df | #3c3c3f | #2a386e | +| 主文字 | `--tx-1` | #f3efe6 | #1f1b15 | #ecebe8 | #eef1fb | +| 次要文字 | `--tx-2` | #aaa79e | #55504a | #acaba6 | #a9b1cc | +| 弱文字(≥4.3:1,浅色主题同样适用) | `--tx-3` | #6f6d68 | #736d65 | #85847f | #7680a2 | +| 叠加通道 ★(悬停、细线用 `rgb(var(--ink)/a)`) | `--ink` | 255 255 255 | 38 30 18 | 255 255 255 | 214 224 255 | +| 边框 1/2/3 | `--line*` | 白 .06/.11/.18 | 墨 .09/.15/.26 | 白 .075/.13/.22 | 冷白 .07/.12/.2 | +| 主操作(“去做”,全页唯一) | `--gold` / `--grad-gold` | #e9b04b 金 | #a65a0c 深琥珀 | #d9dde2 校色白 | #ff9a6c 珊瑚 | +| 主操作上的文字 | `--tx-inv` | #1a1206 | #ffffff | #141516 | #2b0c05 | +| 场景 · 图片 | `--sc-image` | #3fd8c6 | #0b8378 | #6fc2b7 | #4fd6ea | +| 场景 · 视频 | `--sc-video` | #ff8a3d | #c2511a | #d8a06c | #ffbb55 | +| 场景 · 智能体 | `--sc-agent` | #a879ff | #6d45d0 | #a598d8 | #b592ff | +| 场景 · 项目 | `--sc-project` | #e9b04b | #a65a0c | #d4bc8a | #ff9a6c | +| 场景 · 管理 | `--sc-manage` | #7f93b5 | #4c6286 | #909cae | #8aa0dc | +| 场景 · 素材资产 ★ | `--sc-asset` | #ff86b0 | #b93a72 | #d394b0 | #ff86c0 | +| 成功 | `--ok` | #43d39a | #11804f | #4cc38f | #4fd69c | +| 提醒 / 待配置 | `--warn` | #f2b33d | #9a6400 | #e3a93b | #ffc94d | +| 错误 / 失败 | `--err` | #ff5d6c | #c42b41 | #ee5d70 | #ff4d6d | +| 信息 / 历史回看 | `--info` | #5aa9ff | #2263c2 | #5d9fe3 | #69b4ff | +| 场景色文字混色端 ★ | `--hi` | white | #141109 | white | white | +| 场景/状态实底上的文字 ★ | `--on-scene` / `--on-ok` | 深色 | #fff | 深色 | 深色 | +| 输入框底 ★ | `--well` | bg-0 | #ffffff | #18181a | bg-0 | +| 页头毛玻璃 ★ | `--glass` | bg-1 70% | bg-1 88% | bg-1 82% | 定制 55% | +| 遮罩层 ★ | `--scrim` | 黑 .6 | 暖墨 .34 | 黑 .55 | 深蓝 .62 | +| 作品阴影 ★ | `--media-shadow` | 重 | 轻、暖 | 中 | 重、偏蓝 | +| 发光开关 ★ | `--glow` | 1 | 0 | 0 | 1 | + +另有特效变量 ★:`--gen-bg`(生成中底色)、`--beam`(扫描光)、`--grain`(颗粒)、`--lattice`(光栅蜂窝纹)、`--scanline`、`--stage-tint`(舞台场景色晕 0–10%)、`--aur-1/2/3`、`--aur-op`(极光)、`--agent-hero`、`--agent-title`、`--orb-core`、`--particle`、`--brand-grad/-glow/-ink`、`--display-grad`、`--fail-a/b`。完整取值见交互样本 ` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ + +
+
工作室
晚上好,今天从哪里继续?
+
Ctrl K
+ 设计演示 · 数据为示例
+
+
+
+
+
+ 继续创作 · 上次编辑 2 小时前 +
《雾港来信》
分镜设计 等你确认
+

分镜智能体已交回 12 个镜头的草稿;确认后进入素材资产阶段。

+
故事
风格
分镜
素材
镜头脚本
+
+
+
+
+
从创意开始

一句话想法 → 五阶段项目

+
导入剧本

TXT / DOCX / Fountain

+
独立生图

文生图 · 图生图 · 遮罩

+
独立生视频

文生 · 首尾帧 · 参考

+
+

我的项目

4
+
+
+ +
+
+ + +
+ + +
+
+
+
+ +
+ 本机 ComfyUI-5090D · 38ms + 设计演示 · 不调用真实接口、不计费 +
+
+
正在查看 历史 #— · 当前输入未改动
+
+
显影中
采样 0/8
+
+
+
+
+
创作设置
+
+
取消不会改动当前参数 · 图片 / 视频分别记忆
+
+
+
+
●正向提示词
+
0 字Enter 换行 · Ctrl+Enter 生成
+
●反向提示词
+
0 字
+
+
+
+
+ + +
+ + +
+
+
智能体
把创作任务交给助手,成果回到项目等你确认
设计演示
+
+ +
+
+
+ 主创智能体 · 统筹五个阶段助手 +

从一个想法,到一组能直接开拍的镜头

+

它会先列计划,再调用剧本、视觉、分镜、素材与镜头助手;每一步的成果都是可编辑草稿,你确认后才写入项目。

+
1说出想法2确认计划3编辑成果4带回项目
+
+ +
绑定:雾港来信DeepSeek-V4技能 3参考材料
+
+
+
+
+

阶段助手

每个助手对应项目的一个阶段,也可以单独调用
+
+

最近成果点开即可编辑,再带回项目

+

技能库12

+ 港风电影分镜Minimax-H3 提示词角色三视图规范台词口语化镜头时长分配短剧钩子Qwen 遮罩重绘+ 学习新技能
+

技能 = 可复用的规则 + 示例。可从对话中“记住这个做法”生成,也可导入 Markdown。

+
+
+
+
+ + +
+
+
智能体/雾港来信 · 分镜设计
进行中 · 4/5 步
DeepSeek-V4港风电影分镜 · 镜头时长分配设计演示
+
+ +
+
+
我
按已确认的故事和“冷雾港口”风格板,把第一场拆成镜头,控制在 60 秒左右,台词保留原文。
+
+
执行计划已确认
+
读取项目:故事 v3、风格板「冷雾港口」2s
+
按台词与动作分配 12 个镜头(平均 5.2 秒)14s
+
为每个镜头写画面提示词(H3 规范)21s
+
4生成 12 张分镜草图(Krea2 · 4 宫格)7/12
+
5整理成可编辑分镜表,交回项目待确认
+
+
调用 queue_image_jobs· 12 个任务 · Krea2 文生图已提交
{ workflow: "krea2_turbo_文生图_4_6_8宫格故事板", count: 12, size: "1344×768", project: "雾港来信", stage: "storyboard" }
+
使用技能 港风电影分镜· 规则 6 条
长焦压缩、霓虹反光、雨夜湿地面;对白镜头优先过肩 + 反打…
+
已完成 7 张草图。右侧分镜表可以直接改文字、换镜头时长;不满意的镜头点「重画」只会重做那一个。
+
正在画第 8 个镜头…
+
+
+
@ 工作流+ 参考材料
+
+ +
+
+ + +
+
工作室/雾港来信
短片 · 90s · 16:9
设计演示
+
+
+
+
分镜助手交回了 12 个镜头的草稿(2 分钟前)。确认后才会交给「素材」阶段使用。
+
+
+ +
+
草稿 · 未确认已编辑 2 处 · 自动保存于 21:04
+
+ + +
+
工作流库
仓库 156 个文件 · 已配置可运行 9 个(测试站实况)
设计演示
+
+ +
+
名称用途模型族需要的素材状态
+ +
+
+ + +
+
模型与连接
设计演示
+
+
+ + +
+
生成任务
设计演示
+
任务来源工作流连接进度时间
+
+ + +
+
素材库
项目:全部 设计演示
+
+
+ + +
+
灵感画布 · 三维搭景 · 设置
其余已有功能统一后的组织方式
设计演示 · 组织方式说明
+
+

灵感画布

无限画布 + 右侧智能体对话栏(可收起)。卡片=角色 / 场景 / 图片 / 视频产物,沿用创作台的取景框选中、显影出现动效。从画布卡片可「送入创作台作参考」或「存入项目」。

导航:一级 · 画布
+

三维搭景

属于“镜头”阶段的辅助工具:从项目镜头卡或创作台视频参考槽「从三维搭景截取」进入。版面=左场景列表 / 中 Three.js 视口 / 下时间线 / 右智能体。暂不进一级导航,入口放在项目「镜头脚本」阶段和创作台素材槽。

已确认:不进一级导航
+

试验台(Playground)

功能与创作台重叠(任意工作流 + 任意输入)。建议并入创作台:工作流列表中“待配置”项点开即进入映射测试;不再单列页面。

已确认:并入创作台
+

设置

两列分组卡片(参考站偏好设置的组织方式):语言、存储路径、队列并发与超时、Dry-run、动效强度、默认画幅。每组带「规则说明」,底部固定保存。

导航:侧栏底部
+

技能与参考材料

归在智能体页:技能库、参考材料库是智能体首页下半区的面板;编辑在抽屉中完成,不另起一级页面。

+

管理三页的共同骨架

左筛选 220 / 中列表 / 右详情 360。任务、工作流、模型连接、素材库共用一套表格、标签、状态色;详情抽屉不跳页,返回不丢筛选。

+
+
+
+
设置
设计演示
+
+
外观主题· 同一布局与功能;切换不影响正在编辑的内容,选择会被记住
+
界面语言规则说明
中文English日本語
+
任务队列
并发数
2
单任务超时
1800s
+
动效强度
完整克制关闭(减少动效)

系统开启“减少动态效果”时自动切到「关闭」。

+
Dry-run测试用
只写占位文件,不调用 ComfyUI
+
存储路径
+
+
+
+
设计规范 · 夜片场(多主题 v2)
施工唯一品味依据 · 变量名见 design-spec.md
设计规范
+
+

01 色彩 · 当前主题「夜片场」(切换后实时更新)

+
+

规则:界面只用一个主光(主操作色,夜片场为金)表示“去做”;场景色只表示“你在哪里”(图片=青、视频=琥珀、智能体=紫、项目=金、管理=钢蓝),用于选中、焦点、进度,不用于大面积底色。作品永远是画面里最亮、最饱和的东西。

+
+

02 文字层级

+
展示 28/700--fs-display
+
页面标题 20/600--fs-h1
+
区块标题 16/600--fs-h2
+
正文 / 输入 13/400 行高 1.55--fs-body
+
辅助 12/400--fs-small
+
#61 · 00:05 · 3:4 · 952567--mono 11
+

03 间距 · 圆角 · 阴影

+ +
+
+

4px 基准;控件高 26 / 32 / 38;列表行 50;面板内边距 12–16;页面外边距 16–24。

+
+

04 组件状态

+
按钮
+
筛选胶囊
默认选中参数 3:4
+
工作流行
Qwen
默认
文生图
Qwen
选中
局部重绘
Qwen
待配置
待配置
+
素材槽
上传
必填
+
状态标签
完成/可运行待配置失败草稿未确认
+
取景框选中
右:光栅悬停(随鼠标)
+
+
+

05 主题 · 语义色对照(直接读取四套主题变量)

+

场景色只表示“你在哪里”,状态色只表示“结果如何”,两组在每套主题里都单独取值、互不借用;浅色主题不用发光表达状态,靠描边、实色与对比度。作品框、视频控制条、作品上的角标在所有主题保持深色,避免主题给作品染色。

+
+

06 动效

+ + + + + + + + + +
名称触发时长 · 曲线可打断减少动效时
页面进入切换一级页320ms · ease-out是直接出现
光栅悬停指针进入列表/卡片320ms 渐显 · 跟随指针是仅背景变亮
取景框选中工作流/作品200ms · ease-out,角标外扩 2px是保留角标,无位移
扫描光束生成中1.6s 循环 · ease-in-out取消即停只保留进度条
显影结果到达700ms · ease-out(模糊→清晰)点任意处跳过淡入 1ms
主按钮扫光悬停主操作700ms 一次是关闭
弹层打开参数/菜单200ms · 上移 6px + 缩放 .98Esc/点外部直接出现
智能体氛围进入智能体页极光 18s、粒子、光球呼吸 4.5s离开页面即停全部关闭
提示操作完成320ms · spring,3.2s 后消失是淡入
切换主题「开灯」选择新主题520ms · ease-out,新主题从点击处圆形展开是(再次选择即接管)瞬间替换
+

曲线:--ease-out cubic-bezier(.22,1,.36,1) · --ease-io (.65,0,.35,1) · --ease-spring (.34,1.56,.64,1)。视频永不自动播放;空格只在播放器获得焦点时生效。

+

07 桌面断点

+ + + + +
窗口总导航工作流列历史栏其他
≥16006827292工作室右栏 340;智能体成果栏 420
1367–15996827292标准布局(1440×900 样稿)
1281–13666824884隐藏工作流文件名
≤12806023676页头 44;可收起历史栏
高度 ≤800提示词默认 48 高、素材槽 50;长提示词点「展开」到 28vh,内部滚动
+

最小支持 1280×720。主操作(生成、确认、带回项目)始终在首屏,不随内容滚走;长列表各自滚动。

+
+
+
+
+
+ +
+
存入项目
+
+ + + + + + diff --git a/docs/hel-728/backup-hashes.txt b/docs/hel-728/backup-hashes.txt new file mode 100644 index 00000000..715ca637 --- /dev/null +++ b/docs/hel-728/backup-hashes.txt @@ -0,0 +1,5 @@ +c32f1f7e6814350d2f5470e900b5a697bf8f34f02eac3837eca9a7a3fe9b25d4 /opt/aigc-home/releases/formal-v5-35fd07a.tgz +f1fb2e4167a7e552de69f7857347ca95a1ebf0f1a5ab1d075692ccf1ae5a103b /opt/aigc-home/releases/backup-HEL665-v5-35fd07a-20261015.tgz +8374951e292df80f43aac3425a7aaa312e59f3a0b953b45a4d051934612ff304 /opt/aigc-home/releases/rollback-HEL665-v5-35fd07a-20261015.sh +old-source-present +old-static-present diff --git a/docs/hel-728/baseline-browser.log b/docs/hel-728/baseline-browser.log new file mode 100644 index 00000000..c71c48b3 --- /dev/null +++ b/docs/hel-728/baseline-browser.log @@ -0,0 +1,31 @@ +{"image_wf": 1, "video_wf": 2, "endpoint": "cba0eaa4-57f2-4f57-99b1-ba7b6f0b0835"} +{'dual': 3, 'h3': 4, 'project': 1} +RUN e2e/reference/check.cjs +PASS 真实工作流显示正反向左右分栏 +PASS 工作流列表可应对超过 48 项 +PASS 列表搜索过滤真实元数据 +PASS 置顶不切换工作流、不清空草稿 +PASS 参数取消不改当前值 +PASS 参数保存应用到当前工作流 +PASS 真实生成请求提交且取得结果 +PASS 正反向与种子写入任务 +locator.click: Timeout 12000ms exceeded. +Call log: + - waiting for locator('.result-item').first() + + at /home/xiaomo/multica_workspaces/hello-af23464d222e/hel-728-a07b128af8f7/workdir/aigc-home/e2e/reference/check.cjs:146:45 { + log: [ " - waiting for locator('.result-item').first()" ], + name: 'TimeoutError' +} +RUN e2e/reference/check-copy-fallback.cjs +locator.waitFor: Timeout 15000ms exceeded. +Call log: + - waiting for getByRole('button', { name: /编辑内容并带回项目/ }) to be visible + + at /home/xiaomo/multica_workspaces/hello-af23464d222e/hel-728-a07b128af8f7/workdir/aigc-home/e2e/reference/check-copy-fallback.cjs:44:54 { + log: [ + " - waiting for getByRole('button', { name: /编辑内容并带回项目/ }) to be visible" + ], + name: 'TimeoutError' +} +FAILED SCRIPTS ['e2e/reference/check.cjs', 'e2e/reference/check-copy-fallback.cjs'] diff --git a/docs/hel-728/baseline-lint.log b/docs/hel-728/baseline-lint.log new file mode 100644 index 00000000..fe2ed9d9 --- /dev/null +++ b/docs/hel-728/baseline-lint.log @@ -0,0 +1,74 @@ + +> calliope-web@1.5.8 lint +> prettier --check . && eslint . + +Checking formatting... +[warn] package-lock.json +[warn] package.json +[warn] scripts/check-storage-ssr.mjs +[warn] src/app.html +[warn] src/hooks.client.ts +[warn] src/hooks.server.ts +[warn] src/lib/actions/spotlight.ts +[warn] src/lib/agentComposer.ts +[warn] src/lib/agentTasks.ts +[warn] src/lib/assetPicker.ts +[warn] src/lib/comfy/classifyInput.ts +[warn] src/lib/comfy/parser.ts +[warn] src/lib/comfy/promptInput.ts +[warn] src/lib/comfy/resolution.ts +[warn] src/lib/comfy/types.ts +[warn] src/lib/comfy/useUpload.svelte.ts +[warn] src/lib/components/agent/agentPalette.ts +[warn] src/lib/components/generate/context.ts +[warn] src/lib/components/generate/schema.ts +[warn] src/lib/components/generate/takeDelete.svelte.ts +[warn] src/lib/components/generate/takes.ts +[warn] src/lib/components/shell/nav.ts +[warn] src/lib/components/ui/icons.ts +[warn] src/lib/components/ui/modalLayers.ts +[warn] src/lib/components/ui/README.md +[warn] src/lib/dbtime.ts +[warn] src/lib/durationBudget.ts +[warn] src/lib/endpointKind.ts +[warn] src/lib/events.ts +[warn] src/lib/formOptions.ts +[warn] src/lib/i18n.svelte.ts +[warn] src/lib/i18n/de.ts +[warn] src/lib/i18n/en.ts +[warn] src/lib/i18n/es.ts +[warn] src/lib/i18n/fr.ts +[warn] src/lib/i18n/ja.ts +[warn] src/lib/i18n/ko.ts +[warn] src/lib/i18n/zh.ts +[warn] src/lib/jobProgress.ts +[warn] src/lib/jobStatus.ts +[warn] src/lib/missingOutputs.svelte.ts +[warn] src/lib/promptTemplates.ts +[warn] src/lib/reduceMotion.svelte.ts +[warn] src/lib/referenceAgent.ts +[warn] src/lib/shot/api.ts +[warn] src/lib/shot/calibration/compositionPresets.ts +[warn] src/lib/shot/calibration/shotSolver.ts +[warn] src/lib/shot/helpers/cameraUtils.ts +[warn] src/lib/shot/helpers/jointConfig.ts +[warn] src/lib/shot/helpers/mannequinFactory.ts +[warn] src/lib/shot/helpers/mirror.ts +[warn] src/lib/shot/helpers/posture.ts +[warn] src/lib/shot/helpers/primitiveFactory.ts +[warn] src/lib/shot/motion/sampleTrack.ts +[warn] src/lib/shot/shotStore.svelte.ts +[warn] src/lib/shot/types/mannequin-js.d.ts +[warn] src/lib/storage.ts +[warn] src/lib/toast.ts +[warn] src/lib/wideLayout.ts +[warn] src/routes/+layout.ts +[warn] src/routes/+page.ts +[warn] src/routes/app.css +[warn] src/routes/project/[id]/+page.ts +[warn] src/routes/projects/+page.ts +[warn] svelte.config.js +[warn] tailwind.config.js +[warn] tsconfig.json +[warn] vite.config.ts +[warn] Code style issues found in 68 files. Run Prettier with --write to fix. diff --git a/docs/hel-728/baseline-pytest.log b/docs/hel-728/baseline-pytest.log new file mode 100644 index 00000000..a6c0665f --- /dev/null +++ b/docs/hel-728/baseline-pytest.log @@ -0,0 +1,413 @@ +FFFFF [100%] +=================================== FAILURES =================================== +________________________ test_comfy_server_info_scoping ________________________ + + def test_comfy_server_info_scoping(): + """Native health check stays visible in both blind and linked sessions.""" + from calliope.agent.harness import tools as harness_tools + + blind = ToolContext(session_id=1, project_id=None) + linked = ToolContext(session_id=1, project_id=99) + +> blind_names = {e["function"]["name"] for e in harness_tools.openai_tools_payload(blind)} + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +tests/test_agent_comfy_mcp.py:370: +_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ +src/calliope/agent/harness/tools.py:27: in openai_tools_payload + return _registry.openai_payload(ctx) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +src/calliope/agent/harness/registry.py:219: in openai_payload + if self._visible(t, ctx) + ^^^^^^^^^^^^^^^^^^^^^ +src/calliope/agent/harness/registry.py:203: in _visible + if not user_allows_render(ctx): + ^^^^^^^^^^^^^^^^^^^^^^^ +src/calliope/agent/harness/policy.py:156: in user_allows_render + if has_structured_approval(ctx, "render"): + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +src/calliope/agent/harness/policy.py:277: in has_structured_approval + answer = latest_answer(ctx.session_id) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +src/calliope/agent/harness/policy.py:260: in latest_answer + events = read_events(session_id) + ^^^^^^^^^^^^^^^^^^^^^^^ +_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ + +session_id = 1 + + def read_events(session_id: int) -> list[SessionEvent]: + conn = _db() + try: +> rows = conn.execute( + "SELECT * FROM agent_events WHERE session_id = ? ORDER BY seq", (session_id,) + ).fetchall() +E sqlite3.OperationalError: no such table: agent_events + +src/calliope/agent/harness/log.py:146: OperationalError +----------------------------- Captured stderr call ----------------------------- +2026-10-21 20:00:31,637 | INFO | calliope.harness | Harness composed: 79 tools +------------------------------ Captured log call ------------------------------- +INFO calliope.harness:__init__.py:214 Harness composed: 79 tools +___________ test_h3_messages_bind_neighbor_shots_and_keep_file_slots ___________ + + def test_h3_messages_bind_neighbor_shots_and_keep_file_slots(): + from calliope.agent.prompts import build_minimax_h3_ref_messages + from calliope.agent.video_agent import resolve_h3_references + from calliope.comfyui.parser import parse_dynamic_inputs + + plan = { + "overview": {"style": "noir", "lighting": "sodium"}, + "requirements": {"lighting": "sodium lamps only"}, + "shots": [ + { + "clip_id": 1, + "window": "00:00.000-00:04.000", + "action": "enter", + "camera": "push in", + "lighting": "dim sodium", + "speakers": "MAYA: hello", + "spatial": "screen left", + "state_after": "door open", + }, + { + "clip_id": 2, + "window": "00:04.000-00:08.000", + "action": "turn", + "camera": "hold", + "lighting": "dim sodium", + "speakers": "", + "spatial": "screen left", + "state_after": "", + }, + { + "clip_id": 3, + "window": "00:08.000-00:12.000", + "action": "leave", + "camera": "pull out", + "lighting": "brighter", + "speakers": "", + "spatial": "", + "state_after": "hallway empty", + }, + ], + } + lock = continuity_lock_text(plan, 2) + assert "Previous shot" in lock + assert "Next shot" in lock + assert "Do not recast a subject" in lock + assert "screen direction" in lock + assert "enter" in lock and "leave" in lock + + scene = {"heading": "INT. HALL", "action": "She turns.", "duration_sec": 4, "dialog": ""} + subjects = [ + { + "index": 1, + "kind": "reference", + "name": "mercs", + "appearance": "", + "path": r"E:\assets\mercs.png", + } + ] + messages = build_minimax_h3_ref_messages(scene, subjects, continuity=lock) + text = messages[1]["content"] + assert "Previous shot" in text + assert "Next shot" in text + assert "screen direction" in text + assert "do not substitute another character or location" in text + + workflow = { + "148": { + "class_type": "LoadImage", + "inputs": {"image": ""}, + "_meta": {"title": "Ref 1 (Input:image)"}, + }, + "149": { + "class_type": "LoadImage", + "inputs": {"image": ""}, + "_meta": {"title": "Ref 2 (Input:image)"}, + }, + } + inputs = parse_dynamic_inputs(workflow) + resolved, paths, _videos = resolve_h3_references( + inputs, + {"148": r"E:\assets\hero.png", "149": r"E:\assets\mercs.png"}, + [{"name": "Maya", "appearance": "olive jacket", "sheet_path": r"E:\assets\maya.png"}], + {"name": "Metro Line", "description": "rain-slick rooftops"}, + r"E:\assets\metro.png", + ) +> assert resolved[1]["name"] == "mercs" +E AssertionError: assert 'E:\\assets\\mercs' == 'mercs' +E +E - mercs +E + E:\assets\mercs + +tests/test_continuity.py:249: AssertionError +___________ test_resolve_h3_references_prefers_form_files_over_story ___________ + + def test_resolve_h3_references_prefers_form_files_over_story(): + """Slot files the user picked replace the story cast; empty slots keep it.""" + from calliope.agent.video_agent import resolve_h3_references + from calliope.comfyui.parser import parse_dynamic_inputs + + workflow = { + "148": { + "class_type": "LoadImage", + "inputs": {"image": ""}, + "_meta": {"title": "Ref 1 (Input:image)"}, + }, + "149": { + "class_type": "LoadImage", + "inputs": {"image": ""}, + "_meta": {"title": "Ref 2 (Input:image)"}, + }, + "150": { + "class_type": "LoadVideo", + "inputs": {"video": ""}, + "_meta": {"title": "Motion (Input:video)"}, + }, + } + inputs = parse_dynamic_inputs(workflow) + characters = [ + { + "name": "Maya", + "appearance": "olive jacket", + "sheet_path": r"E:\assets\maya.png", + } + ] + location = {"name": "Metro Line", "description": "rain-slick rooftops"} + loc_image = r"E:\assets\metro.png" + + empty_subjects, empty_paths, empty_videos = resolve_h3_references( + inputs, {}, characters, location, loc_image + ) + assert [s["name"] for s in empty_subjects] == ["Maya", "Metro Line"] + assert empty_paths == [r"E:\assets\maya.png", r"E:\assets\metro.png"] + assert empty_videos == [] + + subjects, paths, videos = resolve_h3_references( + inputs, + { + "148": r"E:\assets\maya.png", + "149": r"E:\assets\mercs.png", + "150": r"E:\assets\fight.mp4", + }, + characters, + location, + loc_image, + ) + assert subjects[0]["kind"] == "character" + assert subjects[0]["name"] == "Maya" + assert subjects[1]["kind"] == "reference" +> assert subjects[1]["name"] == "mercs" +E AssertionError: assert 'E:\\assets\\mercs' == 'mercs' +E +E - mercs +E + E:\assets\mercs + +tests/test_h3_profile.py:215: AssertionError +___________ test_preview_prompt_uses_form_references_not_story_cast ____________ + +client = +monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x7f3b525d0c80> + + def test_preview_prompt_uses_form_references_not_story_cast(client, monkeypatch): + """The Video-tab image and video slots are the prompt's subjects. + + Story character + location text must not replace files the user picked, + and Regenerate (force) must not return a draft written before those files. + """ + pid = _mk_project(client, "Preview refs") + scene = _add_scene(client, pid, 1, action="She swings the extinguisher.") + clip_id = _scene_default_clip(pid, scene["id"]) + + wf = { + "10": { + "class_type": "CLIPTextEncode", + "inputs": {"text": ""}, + "_meta": {"title": "Main Prompt (Input:prompt)"}, + }, + "148": { + "class_type": "LoadImage", + "inputs": {"image": ""}, + "_meta": {"title": "Ref 1 (Input:image)"}, + }, + "149": { + "class_type": "LoadImage", + "inputs": {"image": ""}, + "_meta": {"title": "Ref 2 (Input:image)"}, + }, + "150": { + "class_type": "LoadVideo", + "inputs": {"video": ""}, + "_meta": {"title": "Motion (Input:video)"}, + }, + } + conn = get_db(settings.db_path) + try: + cur = conn.execute( + """ + INSERT INTO workflows (name, kind, workflow_json, input_schema, output_schema, + prompt_profile, is_enabled) + VALUES (?, 'video', ?, '[]', '[]', 'minimax_h3_ref', 1) + """, + ("H3 refs", json.dumps(wf)), + ) + wf_id = cur.lastrowid + char = conn.execute( + """ + INSERT INTO characters (project_id, name, appearance, sheet_path) + VALUES (?, 'Maya', 'olive jacket', ?) + """, + (pid, r"E:\story\maya-sheet.png"), + ) + conn.execute( + "INSERT INTO scene_characters (scene_id, character_id) VALUES (?, ?)", + (scene["id"], char.lastrowid), + ) + loc = conn.execute( + """ + INSERT INTO locations (project_id, name, description, reference_image_path) + VALUES (?, 'Metro Line', 'rain-slick rooftops', ?) + """, + (pid, r"E:\story\metro.png"), + ) + conn.execute( + "UPDATE scenes SET location_id = ? WHERE id = ?", + (loc.lastrowid, scene["id"]), + ) + conn.execute( + "UPDATE clips SET workflow_id = ? WHERE id = ?", (wf_id, clip_id) + ) + conn.commit() + finally: + conn.close() + + seen: dict = {} + + async def fake_rewrite(scene_, subjects, **kwargs): + seen["subjects"] = subjects + seen["videos"] = kwargs.get("videos") + seen["calls"] = seen.get("calls", 0) + 1 + return "REWRITE FROM REFS" + + monkeypatch.setattr("calliope.agent.video_agent._h3_rewrite", fake_rewrite) + + form = { + "148": r"E:\refs\hero.png", + "149": r"E:\refs\mercs.png", + "150": r"E:\refs\fight.mp4", + } + r = client.post( + f"/api/jobs/projects/{pid}/preview-prompt", + json={"clip_id": clip_id, "input_values": form}, + ) + assert r.status_code == 200 + body = r.json() + assert body["prompt"] == "REWRITE FROM REFS" + assert body["from_draft"] is False + names = [s["name"] for s in seen["subjects"]] +> assert names == ["hero", "mercs"] +E AssertionError: assert ['E:\\refs\\h...\refs\\mercs'] == ['hero', 'mercs'] +E +E At index 0 diff: 'E:\\refs\\hero' != 'hero' +E Use -v to get more diff + +tests/test_scene_settings.py:487: AssertionError +---------------------------- Captured stderr setup ----------------------------- +2026-10-21 20:00:31,702 | INFO | calliope | Calliope started — db=/tmp/multica-task-3084682389/tmpj2vinw90/calliope.db dry_run=True +2026-10-21 20:00:31,702 | INFO | calliope.worker | Queue worker started +------------------------------ Captured log setup ------------------------------ +INFO calliope:main.py:112 Calliope started — db=/tmp/multica-task-3084682389/tmpj2vinw90/calliope.db dry_run=True +INFO calliope.worker:worker.py:79 Queue worker started +----------------------------- Captured stderr call ----------------------------- +2026-10-21 20:00:31,707 | INFO | httpx2 | HTTP Request: POST http://testserver/api/projects "HTTP/1.1 200 OK" +2026-10-21 20:00:31,721 | INFO | httpx2 | HTTP Request: POST http://testserver/api/projects/1/scenes "HTTP/1.1 200 OK" +2026-10-21 20:00:31,733 | INFO | httpx2 | HTTP Request: POST http://testserver/api/jobs/projects/1/preview-prompt "HTTP/1.1 200 OK" +------------------------------ Captured log call ------------------------------- +INFO httpx2:_client.py:1085 HTTP Request: POST http://testserver/api/projects "HTTP/1.1 200 OK" +INFO httpx2:_client.py:1085 HTTP Request: POST http://testserver/api/projects/1/scenes "HTTP/1.1 200 OK" +INFO httpx2:_client.py:1085 HTTP Request: POST http://testserver/api/jobs/projects/1/preview-prompt "HTTP/1.1 200 OK" +--------------------------- Captured stderr teardown --------------------------- +2026-10-21 20:00:32,204 | INFO | calliope.worker | Queue worker stopped +2026-10-21 20:00:32,204 | INFO | calliope | Calliope shutting down +---------------------------- Captured log teardown ----------------------------- +INFO calliope.worker:worker.py:185 Queue worker stopped +INFO calliope:main.py:118 Calliope shutting down +_________________ test_script_regenerate_keeps_expanded_count __________________ + +client = +monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x7f3b525d2180> + + def test_script_regenerate_keeps_expanded_count(client, monkeypatch): + calls: list[int] = [] + + async def fake_structured(messages, temperature=0.7): + user = messages[1]["content"] + # Parse required count from prompt + n = 4 + for line in user.splitlines(): + if line.startswith("required_scene_count:"): + n = int(line.split(":", 1)[1].strip()) + break + calls.append(n) + return { + "scenes": [ + { + "order_index": i, + "heading": f"SCENE {i}", + "action": f"action {i}", + "dialog": "", + "duration_sec": 5, + "character_ids": [], + "location_id": None, + } + for i in range(1, n + 1) + ] + } + + monkeypatch.setattr("calliope.agent.script_agent.generate_structured", fake_structured) + + r = client.post("/api/projects", json={"title": "Expand", "idea": "desert", "target_duration": "30 seconds"}) + pid = r.json()["id"] + + # Seed 4 scenes via generate + r = client.post(f"/api/projects/{pid}/generate-script", json={"replace": True}) +> assert r.status_code == 200 +E assert 500 == 200 +E + where 500 = .status_code + +tests/test_script_scene_count.py:59: AssertionError +---------------------------- Captured stderr setup ----------------------------- +2026-10-21 20:00:32,234 | INFO | calliope | Calliope started — db=/tmp/multica-task-3084682389/tmp2hqlzdiy/calliope.db dry_run=True +2026-10-21 20:00:32,234 | INFO | calliope.worker | Queue worker started +------------------------------ Captured log setup ------------------------------ +INFO calliope:main.py:112 Calliope started — db=/tmp/multica-task-3084682389/tmp2hqlzdiy/calliope.db dry_run=True +INFO calliope.worker:worker.py:79 Queue worker started +----------------------------- Captured stderr call ----------------------------- +2026-10-21 20:00:32,238 | INFO | httpx2 | HTTP Request: POST http://testserver/api/projects "HTTP/1.1 200 OK" +2026-10-21 20:00:32,264 | INFO | calliope.llm | LLM stream request to http://127.0.0.1:11434/v1/chat/completions with model llama3.2 +2026-10-21 20:00:32,266 | INFO | httpx2 | HTTP Request: POST http://testserver/api/projects/1/generate-script "HTTP/1.1 500 Internal Server Error" +------------------------------ Captured log call ------------------------------- +INFO httpx2:_client.py:1085 HTTP Request: POST http://testserver/api/projects "HTTP/1.1 200 OK" +INFO calliope.llm:llm.py:360 LLM stream request to http://127.0.0.1:11434/v1/chat/completions with model llama3.2 +INFO httpx2:_client.py:1085 HTTP Request: POST http://testserver/api/projects/1/generate-script "HTTP/1.1 500 Internal Server Error" +--------------------------- Captured stderr teardown --------------------------- +2026-10-21 20:00:32,735 | INFO | calliope.worker | Queue worker stopped +2026-10-21 20:00:32,736 | INFO | calliope | Calliope shutting down +---------------------------- Captured log teardown ----------------------------- +INFO calliope.worker:worker.py:185 Queue worker stopped +INFO calliope:main.py:118 Calliope shutting down +=============================== warnings summary =============================== +../../aigc-home/calliope-backend/.venv/lib/python3.12/site-packages/starlette/testclient.py:53 + /home/xiaomo/multica_workspaces/hello-af23464d222e/hel-728-a07b128af8f7/workdir/aigc-home/calliope-backend/.venv/lib/python3.12/site-packages/starlette/testclient.py:53: DeprecationWarning: The anyio.abc.BlockingPortal alias is deprecated, use anyio.from_thread.BlockingPortal instead. + _PortalFactoryType = Callable[[], AbstractContextManager[anyio.abc.BlockingPortal]] + +-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html +=========================== short test summary info ============================ +FAILED tests/test_agent_comfy_mcp.py::test_comfy_server_info_scoping - sqlite... +FAILED tests/test_continuity.py::test_h3_messages_bind_neighbor_shots_and_keep_file_slots +FAILED tests/test_h3_profile.py::test_resolve_h3_references_prefers_form_files_over_story +FAILED tests/test_scene_settings.py::test_preview_prompt_uses_form_references_not_story_cast +FAILED tests/test_script_scene_count.py::test_script_regenerate_keeps_expanded_count +5 failed, 1 warning in 1.19s diff --git a/docs/hel-728/baseline.md b/docs/hel-728/baseline.md new file mode 100644 index 00000000..96869354 --- /dev/null +++ b/docs/hel-728/baseline.md @@ -0,0 +1,33 @@ +# HEL-728 B0 版本核实与回退记录 + +核实日期:2026-10-21。服务器仅执行只读查询;未部署、重启服务或修改配置/数据。 + +| 对象 | 精确版本 / 结论 | +| --- | --- | +| `origin/main` | `1fc8bdb8bb2a88ccace6f21a507fd77bcf22f532` | +| `origin/deploy/hel-691` | `2b3bbf9`,是第五版的祖先,不是当前部署版本 | +| `origin/feat/hel-665-refract`、PR #1 head | `35fd07af24e62add1f96f6a9c38e363f8e23d778` | +| 当前测试站 | `DEPLOYED_REVISION` 标为第五版 `35fd07a`;前端 191 文件、后端 79 个 Python 文件逐项 SHA-256 全匹配该提交 | +| 静态构建 | 当前 static 与 `stage-HEL665-v5-35fd07a-20261015/build` 全目录 `diff -qr` 无差异;两份 index.html 的 SHA-256 均为 `acd3ba6e0ab29b92f5133358398f3544baa0ce2bea03560004a42e812b6abc8a` | +| PR #1 | https://gitea.xbay.cc/leefer/aigc-home/pulls/1 :open、未合并、目标 main、head=35fd07a | +| B0 底稿 | 从完整第五版另开 `feat/hel-728-night-foundation`,没有合并旧 PR 或更改 main | + +第五版领先 main 22 个提交、领先 deploy/hel-691 10 个提交。HEL-692、HEL-695 的实装和后续修复均在该祖先链上:从 aaddd3d 到 35fd07a 的创作草稿/端点归属、手动播放、智能体编辑成果、阶段建议与确认隔离保留。第五版也完整包含 HEL-681/682 批量导入和 HEL-684 生成结果回流。 + +HEL-727 把两个 API 返回 422 作为部署版本依据的结论已由源码/发布包实证纠正。当前差异均已解释,没有以旧 deploy 分支覆盖后续工作。 + +本任务专用检出初始 `git status --short` 为空,无未提交/未推送工作被覆盖。服务器应用目录不是 Git 检出,以全部源码哈希替代 git status 核实,没有发现与第五版不同的产品源码。已有 `.before-*` 和发布备份保留。未触碰其他任务私有工作区。 + +本地保全引用:`backup/hel-728-main-1fc8bdb` 与 `backup/hel-728-v5-35fd07a`。完整证据见同目录 `server-audit.txt`、`server-backend.txt`、`backup-hashes.txt`。 + +## 已存在的服务器备份 + +目录 `/opt/aigc-home/releases`: + +- `formal-v5-35fd07a.tgz`:当前第五版发布包。 +- `backup-HEL665-v5-35fd07a-20261015.tgz`:部署第五版之前的静态页、前端源码、workbench 路由及版本标记。 +- `rollback-HEL665-v5-35fd07a-20261015.sh`:第五版回退脚本,旧源码/静态目录均仍存在。**此脚本会回到第五版之前,不能当成本批将来的回退脚本。** + +备份和脚本哈希已留档。本批尚未部署,因此当前站点无需回退。后续由总工部署 B0 时,须先重新核对当前源码/构建哈希及活动任务,并备份当前第五版,才能替换 B0 静态页、前端源码和新增的 app.html/themes.css。B0 回退应恢复这份第五版完整备份;无需数据库回退。本批无后端、数据迁移或用户配置改动。 + +代码回退:以 `35fd07af24e62add1f96f6a9c38e363f8e23d778` 为基线重新构建第五版,或恢复总工部署前生成的完整备份。不要只切回 `deploy/hel-691`。 diff --git a/docs/hel-728/browser-results.json b/docs/hel-728/browser-results.json new file mode 100644 index 00000000..a8f64a43 --- /dev/null +++ b/docs/hel-728/browser-results.json @@ -0,0 +1,49 @@ +{ + "checks": [ + "默认主题为夜片场", + "night 保留原编辑组件和正反向草稿", + "day 保留原编辑组件和正反向草稿", + "grade 保留原编辑组件和正反向草稿", + "bluehour 保留原编辑组件和正反向草稿", + "刷新恢复主题和草稿", + "方向键移动主题选项焦点", + "Esc 关闭并恢复入口焦点", + "创作台兼容入口保留全部参数及片段", + "隔离真实队列已收到任务", + "night 上传素材和运行中编辑状态保留", + "day 上传素材和运行中编辑状态保留", + "grade 上传素材和运行中编辑状态保留", + "bluehour 上传素材和运行中编辑状态保留", + "切主题后原任务完成且产物归属不变", + "切主题保留视频节点和暂停状态", + "切主题保留智能体任务输入组件和草稿", + "切主题保留未保存设置", + "1440,900 night 导航入口和四主题完整可见", + "1440,900 day 导航入口和四主题完整可见", + "1440,900 grade 导航入口和四主题完整可见", + "1440,900 bluehour 导航入口和四主题完整可见", + "1366,768 night 导航入口和四主题完整可见", + "1366,768 day 导航入口和四主题完整可见", + "1366,768 grade 导航入口和四主题完整可见", + "1366,768 bluehour 导航入口和四主题完整可见", + "1280,720 night 导航入口和四主题完整可见", + "1280,720 day 导航入口和四主题完整可见", + "1280,720 grade 导航入口和四主题完整可见", + "1280,720 bluehour 导航入口和四主题完整可见", + "模型连接入口保留页签参数", + "旧项目链接保留阶段参数", + "全局搜索保留 视频创作", + "全局搜索保留 大语言模型", + "全局搜索保留 试验台", + "全局搜索保留 三维搭景", + "night 首次绘制前恢复主题及底色", + "day 首次绘制前恢复主题及底色", + "grade 首次绘制前恢复主题及底色", + "bluehour 首次绘制前恢复主题及底色", + "存储被禁用仍可切换且显示提示", + "存储被禁用保留输入", + "无效存储回退夜片场", + "浏览器无运行错误" + ], + "errors": [] +} \ No newline at end of file diff --git a/docs/hel-728/build.log b/docs/hel-728/build.log new file mode 100644 index 00000000..1db95a5d --- /dev/null +++ b/docs/hel-728/build.log @@ -0,0 +1,264 @@ + +> calliope-web@1.5.8 build +> vite build + +vite v6.4.3 building SSR bundle for production... +transforming... +8:10:50 PM [vite-plugin-svelte] src/lib/components/workbench/ShotStagePanel.svelte:156:5 `