Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion apps/apsal-studio/electron/main.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,10 @@ async function callProtocol(message = {}) {
if (!RENDERER_METHODS.has(method)) throw new Error(`Studio renderer is not allowed to call APSAL method: ${method}`)
if (!currentProjectRoot && !PROJECT_OPTIONAL_METHODS.has(method)) throw new Error('请先新建或打开 APSAL 项目目录')
const params = { ...(message.params || {}), project_root: currentProjectRoot || app.getPath('userData') }
const result = await getSidecar().call(method, params)
const sidecar = getSidecar()
const wasRunning = sidecar.status().running
const result = await sidecar.call(method, params)
if (!wasRunning) publish('apsal-protocol:status', await protocolStatus())
if (PROTOCOL_MUTATIONS.has(method) || method === 'studio.view.save') {
publish('apsal-protocol:change', { method, result })
if (result?.snapshot) publish('apsal-protocol:snapshot', result.snapshot)
Expand Down
2 changes: 1 addition & 1 deletion apps/apsal-studio/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self' http://127.0.0.1:*" />
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: apsal-media:; connect-src 'self' http://127.0.0.1:*" />
<title>APSAL Studio</title>
</head>
<body>
Expand Down
8 changes: 4 additions & 4 deletions apps/apsal-studio/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -397,15 +397,15 @@ function CodexLinkPanel() {
</span>
<div>
<span className="eyebrow">CODEX 联动</span>
<h2>{linkStatus?.connected ? 'Codex 已连接' : '等待 Codex 启动'}</h2>
<h2>{linkStatus?.connected ? 'Codex 已连接' : '等待 Codex 选择项目'}</h2>
</div>
<span className="link-origin">由插件启动</span>
</div>
<div className={`connection-state ${linkStatus?.connected ? 'connected' : ''}`}>
<span className="pulse" />
{linkStatus?.connected ? '正在联动当前 APSAL 项目' : '单独打开 Studio 不会连接 Codex'}
{linkStatus?.connected ? '正在联动当前 APSAL 项目' : '请从 Codex 发起当前项目联动'}
</div>
<p>{linkStatus?.connected ? 'Codex 插件正通过本机认证桥访问当前项目。它不能代理任意路径,也不能绕过协议 revision。' : '请在 Codex 中打开 APSAL 插件并开始创作;选择“打开并联动 APSAL Studio”后,插件会自动打开并绑定此界面。'}</p>
<p>{linkStatus?.connected ? 'Codex 插件正通过本机认证桥访问当前项目。它不能代理任意路径,也不能绕过协议 revision。' : '请在 Codex 中发送“连接我刚上传的 APSAL 项目并继续分析”。插件会选择已有项目、启动本机认证桥并绑定此界面,不会额外创建主题。'}</p>
<div className="link-project">
<div><span>绑定项目</span><strong>{snapshot?.project.project_id ?? '未选择'}</strong></div>
<em>{studioUpdates.length ? `${studioUpdates.length} 条已发往 Codex` : '双方已同步'}</em>
Expand Down Expand Up @@ -949,7 +949,7 @@ export function App() {
<button type="button" className={screen === 'studio' ? 'active' : ''} onClick={() => setScreen('studio')}><GitBranch />工作流</button>
</nav>
</header>
{screen === 'library' ? <CreativeLibrary onOpenProject={() => setScreen('studio')} /> : <>
{screen === 'library' ? <CreativeLibrary onOpenProject={() => setScreen('studio')} linkStatus={linkStatus} /> : <>
{snapshot?.read_only && <div className="compatibility-banner"><span>只读模式:{snapshot.compatibility_error || '协议版本不兼容,请预览并复制迁移到 APSAL 0.16。'}</span><button type="button" disabled={migrationBusy} onClick={() => void migrateProject()}>{migrationBusy ? <RefreshCw className="spin" /> : <GitBranch />}预览并复制迁移</button>{migrationError && <em role="alert">{migrationError}</em>}</div>}
<nav className="project-pipeline-strip" aria-label="当前项目流水线">
{['参考图', '分析', '设计', '生成', 'QA', 'Skill', '分享'].map((label, index) => <span key={label} className={snapshot?.session ? (index < 3 ? 'complete' : index === 3 ? 'current' : '') : index === 0 ? 'current' : ''}><i>{index + 1}</i>{label}</span>)}
Expand Down
245 changes: 236 additions & 9 deletions apps/apsal-studio/src/CreativeLibrary.tsx

Large diffs are not rendered by default.

62 changes: 61 additions & 1 deletion apps/apsal-studio/src/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -321,8 +321,68 @@ button:disabled {
.lineage-comparison > div span { padding: 7px; border-radius: 6px; background: var(--surface-soft); color: var(--muted); font-size: 8px; }
.lineage-comparison strong { display: block; margin-bottom: 2px; color: var(--accent-soft); font-size: 13px; }
.lineage-comparison p { margin: 7px 0 0; color: var(--muted); font-size: 8px; line-height: 1.5; }
.detail-gallery { display: grid; grid-template-columns: repeat(3, 1fr); gap: 5px; margin: 0 20px 14px; }
.detail-media { margin: 0 20px 14px; }
.detail-media h3 { margin: 0 0 8px; color: var(--text-soft); font-size: 10px; }
.detail-gallery { display: grid; grid-template-columns: repeat(3, 1fr); gap: 5px; }
.detail-gallery img { width: 100%; aspect-ratio: 1; border-radius: 7px; object-fit: cover; background: #22180f; }
.analysis-execution { margin: 0 20px 14px; overflow: hidden; border: 1px solid rgba(245, 165, 36, 0.18); border-radius: 10px; background: rgba(245, 165, 36, 0.04); }
.analysis-execution > header { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 11px 12px; border-bottom: 1px solid var(--border); }
.analysis-execution > header h3 { margin: 2px 0 0; color: var(--text-soft); font-size: 11px; }
.analysis-link { padding: 4px 7px; border: 1px solid var(--border); border-radius: 99px; color: var(--muted); font-size: 8px; }
.analysis-link.connected { border-color: rgba(62, 207, 122, .35); color: var(--ok); }
.analysis-blocker { margin: 10px 10px 0; padding: 9px; border: 1px solid rgba(244, 107, 107, .24); border-radius: 7px; background: rgba(244, 107, 107, .06); }
.analysis-blocker.headless { border-color: rgba(245, 165, 36, .24); background: rgba(245, 165, 36, .06); }
.analysis-blocker strong, .analysis-blocker span { display: block; }
.analysis-blocker strong { color: var(--text-soft); font-size: 9px; }
.analysis-blocker span { margin-top: 4px; color: var(--muted); font-size: 8px; line-height: 1.45; }
.analysis-jobs { display: grid; gap: 6px; margin: 0; padding: 10px; list-style: none; }
.analysis-jobs > li { overflow: hidden; border: 1px solid var(--border); border-radius: 7px; background: rgba(0, 0, 0, .16); }
.analysis-job-heading { display: flex; align-items: center; gap: 9px; padding: 8px; }
.analysis-job-heading > i { display: grid; width: 20px; height: 20px; flex: 0 0 20px; place-items: center; border: 1px solid var(--border); border-radius: 50%; color: var(--muted); font: 8px ui-monospace, SFMono-Regular, Menlo, monospace; }
.analysis-job-heading > i svg { width: 12px; height: 12px; }
.analysis-jobs > li.in_progress .analysis-job-heading > i { border-color: var(--accent); color: var(--accent-soft); }
.analysis-jobs > li.succeeded .analysis-job-heading > i { border-color: rgba(62, 207, 122, .4); color: var(--ok); }
.analysis-job-heading strong, .analysis-job-heading span, .analysis-job-heading em { display: block; }
.analysis-job-heading strong { color: var(--text-soft); font-size: 9px; }
.analysis-job-heading span { margin-top: 3px; color: var(--muted); font-size: 8px; }
.analysis-job-heading em { margin-top: 3px; color: var(--danger); font-size: 8px; font-style: normal; }
.analysis-job-record { border-top: 1px solid var(--border); }
.analysis-job-record > summary, .analysis-json-record > summary, .analysis-elements summary { cursor: pointer; list-style-position: inside; color: var(--accent-soft); font-size: 8px; }
.analysis-job-record > summary { padding: 8px 10px; background: rgba(245, 165, 36, .035); }
.analysis-job-body { display: grid; gap: 8px; padding: 0 9px 9px; }
.analysis-contract, .analysis-attempts, .analysis-result { padding: 9px; border: 1px solid var(--border); border-radius: 7px; background: rgba(255, 255, 255, .018); }
.analysis-contract h5, .analysis-attempts h5, .analysis-elements h5 { display: flex; align-items: center; justify-content: space-between; margin: 0 0 7px; color: var(--text-soft); font-size: 9px; }
.analysis-contract > p, .analysis-attempts > p, .analysis-empty-result { margin: 0; color: var(--muted); font-size: 8px; line-height: 1.55; }
.analysis-contract dl { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1px; margin: 8px 0; overflow: hidden; border: 1px solid var(--border); border-radius: 6px; background: var(--border); }
.analysis-contract dl div { min-width: 0; padding: 6px; background: #17120e; }
.analysis-contract dt { color: var(--muted-dark); font-size: 7px; }
.analysis-contract dd { overflow-wrap: anywhere; margin: 3px 0 0; color: var(--text-soft); font: 7px/1.45 ui-monospace, SFMono-Regular, Menlo, monospace; }
.analysis-json-record { margin-top: 8px; }
.analysis-json-record > summary { padding: 6px 0; }
.analysis-json-record pre, .analysis-elements pre, .analysis-attempts pre, .analysis-timeline pre { overflow: auto; max-height: 280px; margin: 5px 0 0; padding: 8px; border: 1px solid var(--border); border-radius: 6px; background: #0d0a08; color: #d6c9bc; font: 7px/1.55 ui-monospace, SFMono-Regular, Menlo, monospace; white-space: pre-wrap; overflow-wrap: anywhere; }
.analysis-attempts h5 span, .analysis-elements h5 span, .analysis-timeline h4 span, .analysis-value-list h6 span { color: var(--muted-dark); font: 7px ui-monospace, SFMono-Regular, Menlo, monospace; }
.analysis-attempts ol { display: grid; gap: 5px; margin: 0; padding: 0; list-style: none; }
.analysis-attempts li { display: grid; gap: 3px; padding: 7px; border: 1px solid var(--border); border-radius: 6px; }
.analysis-attempts strong { color: var(--text-soft); font-size: 8px; }
.analysis-attempts time, .analysis-attempts code { color: var(--muted); font: 7px/1.45 ui-monospace, SFMono-Regular, Menlo, monospace; overflow-wrap: anywhere; }
.analysis-attempts em { color: var(--danger); font-size: 8px; font-style: normal; }
.analysis-value-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 5px; }
.analysis-value-list { padding: 7px; border: 1px solid var(--border); border-radius: 6px; }
.analysis-value-list h6 { display: flex; justify-content: space-between; margin: 0 0 5px; color: var(--text-soft); font-size: 8px; }
.analysis-value-list ul { display: grid; gap: 4px; margin: 0; padding-left: 14px; color: var(--muted); font-size: 8px; line-height: 1.45; }
.analysis-value-list p { margin: 0; color: var(--muted-dark); font-size: 8px; }
.analysis-elements { margin-top: 8px; }
.analysis-elements > div { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 5px; }
.analysis-elements details { min-width: 0; padding: 6px; border: 1px solid var(--border); border-radius: 6px; }
.analysis-timeline { margin: 0 10px 10px; padding: 9px; border-top: 1px solid var(--border); }
.analysis-timeline h4 { display: flex; justify-content: space-between; margin: 0 0 7px; color: var(--text-soft); font-size: 9px; }
.analysis-timeline ol { display: grid; gap: 6px; margin: 0; padding: 0; list-style: none; }
.analysis-timeline li { display: grid; grid-template-columns: 112px 1fr; gap: 7px; color: var(--muted); font-size: 8px; line-height: 1.4; }
.analysis-timeline time { color: var(--accent-soft); font: 7px/1.4 ui-monospace, SFMono-Regular, Menlo, monospace; }
.analysis-timeline li div > span, .analysis-timeline li code { display: block; }
.analysis-timeline li code { margin-top: 2px; color: var(--muted-dark); font: 7px ui-monospace, SFMono-Regular, Menlo, monospace; overflow-wrap: anywhere; }
.analysis-timeline p { margin: 0; color: var(--muted); font-size: 8px; }
.analysis-execution > footer { padding: 0 10px 10px; color: var(--muted-dark); font-size: 7px; line-height: 1.45; }
.detail-actions { display: grid; gap: 7px; padding: 4px 20px 16px; }
.detail-actions button { width: 100%; min-height: 40px; justify-content: center; }
.library-tags { margin: 0 20px 16px; padding: 12px; border: 1px solid var(--border); border-radius: 10px; background: rgba(255, 255, 255, .015); }
Expand Down
2 changes: 2 additions & 0 deletions docs/USAGE_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ Import 1–24 images as one root project. For each image, record source/attribut

Codex receives one strict analysis Job at a time. It separates observable facts from creative inference, records uncertainty and risk, covers the five layers and thirteen protocol roles, then synthesizes common visual DNA, conflicts, complements and recommended directions for the whole set. Interrupted Jobs resume; invalid JSON is rejected; repeating a recorded operation does not duplicate project content.

The project detail's **CODEX execution record** distinguishes waiting, actively executing, retryable failure and completion instead of calling an unclaimed Job active. Expand a Job to inspect the exact instruction, reference IDs, identity boundary, strict JSON Schema and SHA-256, every validation/write attempt, observable facts, creative inferences, risks, uncertainties, all thirteen APSAL elements and the complete JSON value stored by the Engine. The timeline shows every retained event rather than only the latest entries. Private model chain-of-thought is neither recorded nor displayed.

After analysis, **Build from analysis** creates the theme, positive and negative Prompts, QA contract and shareable Skill. A scene, camera, light, styling, series or complete nine-shot expansion always creates a child project. The child records source assets, change intent and parent snapshot digest; the parent stays byte-for-byte unchanged.

The library can search, tag, favorite and archive projects and show lineage. These are projection features: `.apsal/project.json`, analysis, themes, runs and share records inside each project remain authoritative. `~/.apsal/library/` can be rebuilt.
Expand Down
2 changes: 2 additions & 0 deletions docs/USAGE_GUIDE.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ codex plugin add apsal-studio@apsal-open

Codex 每次领取一个严格 Schema 的分析任务,将可观测事实与创作推断分开,记录不确定项和风险,覆盖五层十三元素,再综合整组图片的共同视觉 DNA、冲突、互补和推荐方向。任务可以中断恢复;不合格 JSON 会被拒绝;重复写回不会重复创建项目内容。

项目详情中的“CODEX 执行记录”会区分“等待 Codex 领取”“Codex 执行中”“分析失败,可重试”和“分析已完成”,不会再把已建立但无人领取的任务写成正在分析。展开每个任务可以查看 Codex 实际收到的指令、参考图 ID、身份权限、严格 JSON Schema 及其 SHA-256、每次校验与回写、可观测事实、创作推断、风险、不确定项、五层十三要素和 Engine 原样保存的完整 JSON。时间线显示全部事件,不只显示最近几条。模型内部隐藏思维链不记录也不展示。

分析完成后,“从分析构建设计”会生成主题、正负提示词、QA 与可分享 Skill。选择同系列延伸、场景、镜头、灯光、造型或完整九图方向时,系统先建立子项目,记录来源图片、变化目标和父快照摘要;父项目保持不变。

项目库可以搜索、加标签、收藏、归档并查看谱系,但这些只是投影功能。每个项目中的 `.apsal/project.json`、分析、主题、运行和分享记录才是真源;`~/.apsal/library/` 可以随时重建。
Expand Down
9 changes: 8 additions & 1 deletion docs/releases/0.16.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ GitHub distribution, not a different project schema.
schema. The Engine never calls a vision or image provider directly.
3. `design.build_from_analysis` compiles the complete five-layer,
thirteen-element theme, Prompts, negative constraints, QA, and Skill.
Studio's execution console preserves the exact structured task contract,
strict Schema digest, validation attempts, observable facts, creative
inference, risks, uncertainties, all thirteen element decisions, complete
stored JSON, and the full retained event timeline. Unclaimed work is labeled
as waiting; private model chain-of-thought is not stored.
4. Every creative expansion forks a child project with its parent snapshot
digest and source assets. The parent revision and bytes stay unchanged.
5. Successful generated outputs enter the content-addressed library; failed
Expand All @@ -48,7 +53,9 @@ GitHub distribution, not a different project schema.

The Codex MCP exposes creator-facing equivalents for all of these capabilities
and still includes the complete headless theme, generation, Registry and Studio
link surface. The 0.16 server exposes 50 tools.
link surface. The 0.16 server exposes 51 tools, including a non-semantic
`apsal_frontend_connect` action for selecting a Studio-created project without
creating a design session.

## Project layout

Expand Down
Loading
Loading