完成 Game Agent 画布 UI 对齐
中央素材画布接入共享 chrome 与平台主题 token 统一 Supervisor、主视窗和 Agent Dock 的横屏布局 补齐按钮状态、非裁剪和 1280×800 布局测试 同步技术方案、PRD 与项目长期记忆
This commit is contained in:
@@ -22,6 +22,10 @@ import {
|
||||
transformCanvasLayers,
|
||||
} from '@genarrative/image-canvas-core';
|
||||
import {
|
||||
CanvasChromeButton,
|
||||
CanvasToolbar,
|
||||
CanvasToolbarDivider,
|
||||
CanvasToolbarGroup,
|
||||
CanvasViewport as SharedCanvasViewport,
|
||||
CanvasWorld,
|
||||
LayerRenderer,
|
||||
@@ -29,6 +33,22 @@ import {
|
||||
useCanvasHistory,
|
||||
ZoomControls,
|
||||
} from '@genarrative/image-canvas-react';
|
||||
import {
|
||||
ArrowLeft,
|
||||
Check,
|
||||
ImagePlus,
|
||||
Maximize2,
|
||||
Minus,
|
||||
Plus,
|
||||
Redo2,
|
||||
RotateCcw,
|
||||
Save,
|
||||
Sparkles,
|
||||
Trash2,
|
||||
Undo2,
|
||||
Upload,
|
||||
X,
|
||||
} from 'lucide-react';
|
||||
import {
|
||||
type ChangeEvent,
|
||||
type PointerEvent as ReactPointerEvent,
|
||||
@@ -339,6 +359,7 @@ export function AssetCanvasSurface({
|
||||
const [documentVersion, setDocumentVersion] = useState(0);
|
||||
const [canvasSize, setCanvasSize] = useState({ width: 900, height: 640 });
|
||||
const viewportElementRef = useRef<HTMLDivElement | null>(null);
|
||||
const importInputRef = useRef<HTMLInputElement | null>(null);
|
||||
const layersRef = useRef(layers);
|
||||
const viewportRef = useRef(viewport);
|
||||
const backgroundRef = useRef(backgroundColor);
|
||||
@@ -1322,106 +1343,141 @@ export function AssetCanvasSurface({
|
||||
data-session-id={sessionId}
|
||||
aria-label="素材创作无限画布"
|
||||
>
|
||||
<header className="asset-canvas-surface__toolbar">
|
||||
<button
|
||||
type="button"
|
||||
onClick={
|
||||
lifecycle.kind === 'canvas.generating'
|
||||
? stopWaitingForGeneration
|
||||
: cancelCanvas
|
||||
}
|
||||
disabled={lifecycle.kind === 'canvas.saving'}
|
||||
<header className="asset-canvas-surface__toolbar-shell">
|
||||
<CanvasToolbar
|
||||
label="素材画布工具栏"
|
||||
surface="plain"
|
||||
className="asset-canvas-surface__toolbar"
|
||||
>
|
||||
<CanvasToolbarGroup>
|
||||
<CanvasChromeButton
|
||||
label={
|
||||
lifecycle.kind === 'canvas.generating'
|
||||
? '停止等待并返回'
|
||||
: '取消并返回'
|
||||
}
|
||||
icon={<ArrowLeft size={15} aria-hidden="true" />}
|
||||
onClick={
|
||||
lifecycle.kind === 'canvas.generating'
|
||||
? stopWaitingForGeneration
|
||||
: cancelCanvas
|
||||
}
|
||||
disabled={lifecycle.kind === 'canvas.saving'}
|
||||
>
|
||||
{lifecycle.kind === 'canvas.generating' ? '停止等待' : '返回'}
|
||||
</CanvasChromeButton>
|
||||
<CanvasChromeButton
|
||||
label="导入图片"
|
||||
title="导入本地图片"
|
||||
icon={<Upload size={15} aria-hidden="true" />}
|
||||
onClick={() => importInputRef.current?.click()}
|
||||
disabled={
|
||||
lifecycle.kind === 'canvas.saving' ||
|
||||
lifecycle.kind === 'canvas.generating'
|
||||
}
|
||||
>
|
||||
导入
|
||||
</CanvasChromeButton>
|
||||
</CanvasToolbarGroup>
|
||||
<CanvasToolbarDivider />
|
||||
<CanvasToolbarGroup>
|
||||
<CanvasChromeButton
|
||||
label="撤销"
|
||||
icon={<Undo2 size={15} aria-hidden="true" />}
|
||||
onClick={undo}
|
||||
disabled={!canUndo || lifecycle.kind === 'canvas.generating'}
|
||||
/>
|
||||
<CanvasChromeButton
|
||||
label="重做"
|
||||
icon={<Redo2 size={15} aria-hidden="true" />}
|
||||
onClick={redo}
|
||||
disabled={!canRedo || lifecycle.kind === 'canvas.generating'}
|
||||
/>
|
||||
<CanvasChromeButton
|
||||
label="删除"
|
||||
title="删除选中图层"
|
||||
icon={<Trash2 size={15} aria-hidden="true" />}
|
||||
onClick={deleteSelected}
|
||||
disabled={
|
||||
!selectedLayerIds.length ||
|
||||
lifecycle.kind === 'canvas.generating'
|
||||
}
|
||||
/>
|
||||
</CanvasToolbarGroup>
|
||||
<CanvasToolbarDivider />
|
||||
<CanvasToolbarGroup>
|
||||
<CanvasChromeButton
|
||||
label="AI 生成图片"
|
||||
icon={<Sparkles size={15} aria-hidden="true" />}
|
||||
onClick={openGenerationDialog}
|
||||
expanded={generationDialog !== null}
|
||||
disabled={
|
||||
lifecycle.kind === 'canvas.saving' ||
|
||||
lifecycle.kind === 'canvas.generating'
|
||||
}
|
||||
>
|
||||
AI 生成
|
||||
</CanvasChromeButton>
|
||||
</CanvasToolbarGroup>
|
||||
</CanvasToolbar>
|
||||
|
||||
<div
|
||||
className="asset-canvas-surface__savebar"
|
||||
aria-label="素材保存设置"
|
||||
>
|
||||
{lifecycle.kind === 'canvas.generating'
|
||||
? '停止等待并返回'
|
||||
: '取消并返回'}
|
||||
</button>
|
||||
<label className="asset-canvas-surface__import">
|
||||
导入图片
|
||||
<input
|
||||
aria-label="导入本地图片"
|
||||
type="file"
|
||||
accept="image/png,image/jpeg,image/webp"
|
||||
multiple
|
||||
aria-label="素材名称"
|
||||
value={assetName}
|
||||
maxLength={80}
|
||||
onChange={(event) => setAssetName(event.target.value)}
|
||||
disabled={lifecycle.kind === 'canvas.generating'}
|
||||
/>
|
||||
<input
|
||||
aria-label="素材类型"
|
||||
value={assetKind}
|
||||
maxLength={64}
|
||||
onChange={(event) => setAssetKind(event.target.value)}
|
||||
disabled={lifecycle.kind === 'canvas.generating'}
|
||||
/>
|
||||
<select
|
||||
aria-label="导出格式"
|
||||
value={exportMediaType}
|
||||
onChange={(event) =>
|
||||
setExportMediaType(
|
||||
event.target.value as 'image/png' | 'image/jpeg' | 'image/webp',
|
||||
)
|
||||
}
|
||||
>
|
||||
<option value="image/png">PNG</option>
|
||||
<option value="image/jpeg">JPEG</option>
|
||||
<option value="image/webp">WebP</option>
|
||||
</select>
|
||||
<CanvasChromeButton
|
||||
label="保存到项目"
|
||||
icon={<Save size={15} aria-hidden="true" />}
|
||||
className="asset-canvas-surface__save"
|
||||
onClick={() => void saveAsset()}
|
||||
disabled={
|
||||
lifecycle.kind === 'canvas.saving' ||
|
||||
lifecycle.kind === 'canvas.generating'
|
||||
}
|
||||
onChange={(event) => void handleImport(event)}
|
||||
/>
|
||||
</label>
|
||||
<button
|
||||
type="button"
|
||||
onClick={undo}
|
||||
disabled={!canUndo || lifecycle.kind === 'canvas.generating'}
|
||||
>
|
||||
撤销
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={redo}
|
||||
disabled={!canRedo || lifecycle.kind === 'canvas.generating'}
|
||||
>
|
||||
重做
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={deleteSelected}
|
||||
disabled={
|
||||
!selectedLayerIds.length || lifecycle.kind === 'canvas.generating'
|
||||
}
|
||||
>
|
||||
删除
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={openGenerationDialog}
|
||||
>
|
||||
保存到项目
|
||||
</CanvasChromeButton>
|
||||
</div>
|
||||
<input
|
||||
ref={importInputRef}
|
||||
className="asset-canvas-surface__import-input"
|
||||
aria-label="导入本地图片"
|
||||
type="file"
|
||||
accept="image/png,image/jpeg,image/webp"
|
||||
multiple
|
||||
disabled={
|
||||
lifecycle.kind === 'canvas.saving' ||
|
||||
lifecycle.kind === 'canvas.generating'
|
||||
}
|
||||
>
|
||||
AI 生成图片
|
||||
</button>
|
||||
<span className="asset-canvas-surface__spacer" />
|
||||
<input
|
||||
aria-label="素材名称"
|
||||
value={assetName}
|
||||
maxLength={80}
|
||||
onChange={(event) => setAssetName(event.target.value)}
|
||||
disabled={lifecycle.kind === 'canvas.generating'}
|
||||
onChange={(event) => void handleImport(event)}
|
||||
/>
|
||||
<input
|
||||
aria-label="素材类型"
|
||||
value={assetKind}
|
||||
maxLength={64}
|
||||
onChange={(event) => setAssetKind(event.target.value)}
|
||||
disabled={lifecycle.kind === 'canvas.generating'}
|
||||
/>
|
||||
<select
|
||||
aria-label="导出格式"
|
||||
value={exportMediaType}
|
||||
onChange={(event) =>
|
||||
setExportMediaType(
|
||||
event.target.value as 'image/png' | 'image/jpeg' | 'image/webp',
|
||||
)
|
||||
}
|
||||
>
|
||||
<option value="image/png">PNG</option>
|
||||
<option value="image/jpeg">JPEG</option>
|
||||
<option value="image/webp">WebP</option>
|
||||
</select>
|
||||
<button
|
||||
type="button"
|
||||
className="asset-canvas-surface__save"
|
||||
onClick={() => void saveAsset()}
|
||||
disabled={
|
||||
lifecycle.kind === 'canvas.saving' ||
|
||||
lifecycle.kind === 'canvas.generating'
|
||||
}
|
||||
>
|
||||
保存到项目
|
||||
</button>
|
||||
</header>
|
||||
|
||||
{generationDialog ? (
|
||||
@@ -1439,13 +1495,11 @@ export function AssetCanvasSurface({
|
||||
<strong id="asset-canvas-generation-title">
|
||||
{generationDialog === 'edit' ? 'AI 图片生成' : '确认图片生成'}
|
||||
</strong>
|
||||
<button
|
||||
type="button"
|
||||
aria-label="关闭图片生成面板"
|
||||
<CanvasChromeButton
|
||||
label="关闭图片生成面板"
|
||||
icon={<X size={16} aria-hidden="true" />}
|
||||
onClick={closeGenerationDialog}
|
||||
>
|
||||
×
|
||||
</button>
|
||||
/>
|
||||
</header>
|
||||
{generationDialog === 'edit' ? (
|
||||
<div className="asset-canvas-surface__generation-form">
|
||||
@@ -1531,33 +1585,49 @@ export function AssetCanvasSurface({
|
||||
</fieldset>
|
||||
) : null}
|
||||
<footer>
|
||||
<button type="button" onClick={closeGenerationDialog}>
|
||||
<CanvasChromeButton
|
||||
label="取消"
|
||||
title="取消图片生成"
|
||||
icon={<X size={15} aria-hidden="true" />}
|
||||
onClick={closeGenerationDialog}
|
||||
>
|
||||
取消
|
||||
</button>
|
||||
<button type="button" onClick={showGenerationConfirmation}>
|
||||
</CanvasChromeButton>
|
||||
<CanvasChromeButton
|
||||
label="继续确认"
|
||||
title="继续确认图片生成"
|
||||
icon={<Check size={15} aria-hidden="true" />}
|
||||
className="asset-canvas-surface__primary-action"
|
||||
onClick={showGenerationConfirmation}
|
||||
>
|
||||
继续确认
|
||||
</button>
|
||||
</CanvasChromeButton>
|
||||
</footer>
|
||||
</div>
|
||||
) : (
|
||||
<div className="asset-canvas-surface__generation-confirmation">
|
||||
<p>
|
||||
将按 {generationAspectRatio}、{generationImageSize} 生成“
|
||||
{assetName}”,并在远端完成、下载和正式 manifest 提交全部成功后才显示为新资源。
|
||||
</p>
|
||||
<p>
|
||||
本次会使用当前项目的图片生成扣费与幂等合同;关闭此面板不会自动确认。
|
||||
{assetName}”。图片保存到当前项目后会出现在资源列表中。
|
||||
</p>
|
||||
<p>本次生成会按当前账号扣除泥点。</p>
|
||||
<footer>
|
||||
<button
|
||||
type="button"
|
||||
<CanvasChromeButton
|
||||
label="返回修改"
|
||||
title="返回修改生成设置"
|
||||
icon={<ArrowLeft size={15} aria-hidden="true" />}
|
||||
onClick={() => setGenerationDialog('edit')}
|
||||
>
|
||||
返回修改
|
||||
</button>
|
||||
<button type="button" onClick={confirmGeneration}>
|
||||
</CanvasChromeButton>
|
||||
<CanvasChromeButton
|
||||
label="确认并生成"
|
||||
icon={<ImagePlus size={15} aria-hidden="true" />}
|
||||
className="asset-canvas-surface__primary-action"
|
||||
onClick={confirmGeneration}
|
||||
>
|
||||
确认并生成
|
||||
</button>
|
||||
</CanvasChromeButton>
|
||||
</footer>
|
||||
</div>
|
||||
)}
|
||||
@@ -1582,9 +1652,13 @@ export function AssetCanvasSurface({
|
||||
aria-label="图片生成进度"
|
||||
/>
|
||||
<p>生成会由平台按当前账号扣除泥点,完成后自动保存到本地项目。</p>
|
||||
<button type="button" onClick={stopWaitingForGeneration}>
|
||||
<CanvasChromeButton
|
||||
label="停止等待并返回"
|
||||
icon={<ArrowLeft size={15} aria-hidden="true" />}
|
||||
onClick={stopWaitingForGeneration}
|
||||
>
|
||||
停止等待并返回
|
||||
</button>
|
||||
</CanvasChromeButton>
|
||||
</div>
|
||||
</section>
|
||||
) : null}
|
||||
@@ -1602,24 +1676,32 @@ export function AssetCanvasSurface({
|
||||
<strong>{generationFailureTitle(lifecycle.code)}</strong>
|
||||
<p>{lifecycle.message}</p>
|
||||
<div className="asset-canvas-surface__operation-actions">
|
||||
<button type="button" onClick={cancelCanvas}>
|
||||
<CanvasChromeButton
|
||||
label="返回资源总览"
|
||||
icon={<ArrowLeft size={15} aria-hidden="true" />}
|
||||
onClick={cancelCanvas}
|
||||
>
|
||||
返回资源总览
|
||||
</button>
|
||||
</CanvasChromeButton>
|
||||
{!lifecycle.reconciliationRequired ? (
|
||||
<>
|
||||
<button
|
||||
type="button"
|
||||
<CanvasChromeButton
|
||||
label="返回修改"
|
||||
title="返回修改生成设置"
|
||||
icon={<Undo2 size={15} aria-hidden="true" />}
|
||||
onClick={() => reopenGenerationAfterFailure('edit')}
|
||||
>
|
||||
返回修改
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="asset-canvas-surface__save"
|
||||
</CanvasChromeButton>
|
||||
<CanvasChromeButton
|
||||
label="重新确认"
|
||||
title="重新确认图片生成"
|
||||
icon={<RotateCcw size={15} aria-hidden="true" />}
|
||||
className="asset-canvas-surface__primary-action"
|
||||
onClick={() => reopenGenerationAfterFailure('confirm')}
|
||||
>
|
||||
重新确认
|
||||
</button>
|
||||
</CanvasChromeButton>
|
||||
</>
|
||||
) : null}
|
||||
</div>
|
||||
@@ -1774,25 +1856,29 @@ export function AssetCanvasSurface({
|
||||
}}
|
||||
>
|
||||
{(actions) => (
|
||||
<div className="asset-canvas-surface__zoom">
|
||||
<button
|
||||
type="button"
|
||||
aria-label="缩小画布"
|
||||
<CanvasToolbar
|
||||
label="画布缩放"
|
||||
surface="floating"
|
||||
className="asset-canvas-surface__zoom"
|
||||
>
|
||||
<CanvasChromeButton
|
||||
label="缩小画布"
|
||||
icon={<Minus size={15} aria-hidden="true" />}
|
||||
onClick={actions.zoomOut}
|
||||
/>
|
||||
<CanvasChromeButton
|
||||
label={`复位画布,当前 ${actions.displayPercent}`}
|
||||
icon={<Maximize2 size={15} aria-hidden="true" />}
|
||||
onClick={actions.fit}
|
||||
>
|
||||
−
|
||||
</button>
|
||||
<button type="button" onClick={actions.fit}>
|
||||
复位 {actions.displayPercent}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
aria-label="放大画布"
|
||||
</CanvasChromeButton>
|
||||
<CanvasChromeButton
|
||||
label="放大画布"
|
||||
icon={<Plus size={15} aria-hidden="true" />}
|
||||
onClick={actions.zoomIn}
|
||||
>
|
||||
+
|
||||
</button>
|
||||
</div>
|
||||
/>
|
||||
</CanvasToolbar>
|
||||
)}
|
||||
</ZoomControls>
|
||||
{minimapModel ? (
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
.asset-canvas-surface {
|
||||
--image-canvas-brand-accent: var(--platform-button-primary-solid);
|
||||
--image-canvas-brand-accent-strong: var(--platform-text-strong);
|
||||
--image-canvas-brand-border-soft: var(--platform-icon-border);
|
||||
--image-canvas-brand-border-strong: var(--platform-nav-active-border);
|
||||
--image-canvas-brand-focus-ring: var(--platform-input-focus-ring);
|
||||
--image-canvas-brand-soft: var(--platform-nav-active-fill);
|
||||
position: relative;
|
||||
display: grid;
|
||||
grid-template-rows: auto minmax(0, 1fr) auto;
|
||||
@@ -6,74 +12,87 @@
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
border-radius: 16px;
|
||||
background: #e8e6e2;
|
||||
color: #2d2926;
|
||||
border-radius: inherit;
|
||||
background: var(--platform-overlay-fill);
|
||||
color: var(--platform-text-strong);
|
||||
}
|
||||
|
||||
.asset-canvas-surface--loading {
|
||||
place-items: center;
|
||||
}
|
||||
|
||||
.asset-canvas-surface__toolbar {
|
||||
.asset-canvas-surface__toolbar-shell {
|
||||
z-index: 4;
|
||||
display: flex;
|
||||
display: grid;
|
||||
min-width: 0;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 10px 12px;
|
||||
border-bottom: 1px solid rgba(63, 54, 48, 0.12);
|
||||
background: rgba(252, 251, 249, 0.96);
|
||||
padding: 8px 10px 9px;
|
||||
border-bottom: 1px solid var(--platform-line-soft);
|
||||
background: var(--platform-desktop-topbar-fill);
|
||||
}
|
||||
|
||||
.asset-canvas-surface button,
|
||||
.asset-canvas-surface input,
|
||||
.asset-canvas-surface select,
|
||||
.asset-canvas-surface__import {
|
||||
min-height: 34px;
|
||||
border: 1px solid rgba(63, 54, 48, 0.18);
|
||||
border-radius: 9px;
|
||||
background: #fff;
|
||||
color: inherit;
|
||||
font: inherit;
|
||||
.asset-canvas-surface__toolbar {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
padding-block: 1px 3px;
|
||||
}
|
||||
|
||||
.asset-canvas-surface button,
|
||||
.asset-canvas-surface__import {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0 11px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.asset-canvas-surface button:disabled {
|
||||
cursor: default;
|
||||
opacity: 0.45;
|
||||
}
|
||||
|
||||
.asset-canvas-surface input,
|
||||
.asset-canvas-surface select {
|
||||
.asset-canvas-surface__savebar {
|
||||
display: grid;
|
||||
grid-template-columns:
|
||||
minmax(108px, 1.35fr) minmax(88px, 0.8fr) minmax(70px, auto)
|
||||
auto;
|
||||
min-width: 0;
|
||||
align-items: center;
|
||||
gap: 7px;
|
||||
}
|
||||
|
||||
.asset-canvas-surface__savebar input,
|
||||
.asset-canvas-surface__savebar select,
|
||||
.asset-canvas-surface__generation-form select {
|
||||
min-width: 0;
|
||||
min-height: 34px;
|
||||
border: 1px solid var(--platform-surface-border);
|
||||
border-radius: 8px;
|
||||
background: var(--platform-input-fill);
|
||||
color: var(--platform-text-base);
|
||||
font: inherit;
|
||||
padding: 0 9px;
|
||||
}
|
||||
|
||||
.asset-canvas-surface__import input {
|
||||
.asset-canvas-surface__savebar input:focus-visible,
|
||||
.asset-canvas-surface__savebar select:focus-visible,
|
||||
.asset-canvas-surface__generation-form select:focus-visible,
|
||||
.asset-canvas-surface__generation-form textarea:focus-visible {
|
||||
border-color: var(--platform-surface-hover-border);
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 3px var(--platform-input-focus-ring);
|
||||
}
|
||||
|
||||
.asset-canvas-surface__import-input {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0 0 0 0);
|
||||
clip-path: inset(50%);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.asset-canvas-surface__spacer {
|
||||
flex: 1;
|
||||
.asset-canvas-surface .asset-canvas-surface__primary-action,
|
||||
.asset-canvas-surface .asset-canvas-surface__save {
|
||||
border-color: var(--platform-button-primary-border);
|
||||
background: var(--platform-button-primary-fill);
|
||||
color: var(--platform-button-primary-text);
|
||||
box-shadow: var(--platform-nav-active-shadow);
|
||||
}
|
||||
|
||||
.asset-canvas-surface__save {
|
||||
border-color: #c7653d !important;
|
||||
background: #c7653d !important;
|
||||
color: #fff !important;
|
||||
.asset-canvas-surface
|
||||
.asset-canvas-surface__primary-action:not(:disabled):hover,
|
||||
.asset-canvas-surface .asset-canvas-surface__save:not(:disabled):hover {
|
||||
border-color: var(--platform-button-primary-border);
|
||||
background: var(--platform-button-primary-fill);
|
||||
color: var(--platform-button-primary-text);
|
||||
}
|
||||
|
||||
.asset-canvas-surface[data-state='canvas.generating']
|
||||
@@ -90,18 +109,25 @@
|
||||
display: grid;
|
||||
place-items: center;
|
||||
padding: 16px;
|
||||
background: rgba(35, 31, 28, 0.48);
|
||||
background: color-mix(
|
||||
in srgb,
|
||||
var(--platform-text-strong) 36%,
|
||||
transparent
|
||||
);
|
||||
backdrop-filter: blur(5px);
|
||||
}
|
||||
|
||||
.asset-canvas-surface__generation-dialog {
|
||||
display: grid;
|
||||
width: min(560px, 100%);
|
||||
max-height: min(700px, calc(100dvh - 32px));
|
||||
max-height: calc(100% - 32px);
|
||||
min-height: 0;
|
||||
overflow: auto;
|
||||
border: 1px solid rgba(63, 54, 48, 0.16);
|
||||
border: 1px solid var(--platform-modal-border);
|
||||
border-radius: 16px;
|
||||
background: #fcfbf9;
|
||||
box-shadow: 0 24px 80px rgba(35, 31, 28, 0.24);
|
||||
background: var(--platform-modal-fill);
|
||||
color: var(--platform-text-strong);
|
||||
box-shadow: var(--platform-panel-shadow);
|
||||
}
|
||||
|
||||
.asset-canvas-surface__generation-dialog > header,
|
||||
@@ -114,12 +140,7 @@
|
||||
}
|
||||
|
||||
.asset-canvas-surface__generation-dialog > header {
|
||||
border-bottom: 1px solid rgba(63, 54, 48, 0.1);
|
||||
}
|
||||
|
||||
.asset-canvas-surface__generation-dialog > header button {
|
||||
min-width: 34px;
|
||||
padding: 0;
|
||||
border-bottom: 1px solid var(--platform-line-soft);
|
||||
}
|
||||
|
||||
.asset-canvas-surface__generation-form,
|
||||
@@ -132,18 +153,20 @@
|
||||
.asset-canvas-surface__generation-form label {
|
||||
display: grid;
|
||||
gap: 6px;
|
||||
color: var(--platform-text-base);
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.asset-canvas-surface__generation-form textarea {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
resize: vertical;
|
||||
border: 1px solid rgba(63, 54, 48, 0.18);
|
||||
border: 1px solid var(--platform-surface-border);
|
||||
border-radius: 10px;
|
||||
padding: 10px;
|
||||
background: #fff;
|
||||
color: inherit;
|
||||
background: var(--platform-input-fill);
|
||||
color: var(--platform-text-strong);
|
||||
font: inherit;
|
||||
}
|
||||
|
||||
@@ -158,7 +181,7 @@
|
||||
max-height: 180px;
|
||||
gap: 8px;
|
||||
overflow: auto;
|
||||
border: 1px solid rgba(63, 54, 48, 0.12);
|
||||
border: 1px solid var(--platform-subpanel-border);
|
||||
border-radius: 10px;
|
||||
padding: 10px;
|
||||
}
|
||||
@@ -166,6 +189,7 @@
|
||||
.asset-canvas-surface__generation-form fieldset label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.asset-canvas-surface__generation-form fieldset input {
|
||||
@@ -180,7 +204,11 @@
|
||||
.asset-canvas-surface__viewport {
|
||||
grid-row: 2;
|
||||
min-height: 0;
|
||||
background-image: radial-gradient(rgba(45, 41, 38, 0.12) 1px, transparent 1px);
|
||||
background-color: var(--platform-neutral-bg);
|
||||
background-image: radial-gradient(
|
||||
var(--platform-line-soft) 1px,
|
||||
transparent 1px
|
||||
);
|
||||
background-size: 18px 18px;
|
||||
}
|
||||
|
||||
@@ -194,19 +222,27 @@
|
||||
place-items: center;
|
||||
overflow: auto;
|
||||
padding: 20px;
|
||||
background: rgba(45, 41, 38, 0.34);
|
||||
background: color-mix(
|
||||
in srgb,
|
||||
var(--platform-text-strong) 30%,
|
||||
transparent
|
||||
);
|
||||
backdrop-filter: blur(4px);
|
||||
}
|
||||
|
||||
.asset-canvas-surface__operation-card {
|
||||
display: grid;
|
||||
width: min(440px, 100%);
|
||||
max-height: 100%;
|
||||
min-height: 0;
|
||||
gap: 14px;
|
||||
border: 1px solid rgba(63, 54, 48, 0.14);
|
||||
overflow: auto;
|
||||
border: 1px solid var(--platform-modal-border);
|
||||
border-radius: 18px;
|
||||
padding: 22px;
|
||||
background: #fcfbf9;
|
||||
box-shadow: 0 20px 64px rgba(35, 31, 28, 0.2);
|
||||
background: var(--platform-modal-fill);
|
||||
color: var(--platform-text-strong);
|
||||
box-shadow: var(--platform-panel-shadow);
|
||||
}
|
||||
|
||||
.asset-canvas-surface__operation-card strong {
|
||||
@@ -215,8 +251,9 @@
|
||||
|
||||
.asset-canvas-surface__operation-card p {
|
||||
margin: 0;
|
||||
color: #6f655f;
|
||||
color: var(--platform-text-base);
|
||||
line-height: 1.55;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.asset-canvas-surface__operation-card progress {
|
||||
@@ -225,18 +262,18 @@
|
||||
overflow: hidden;
|
||||
border: 0;
|
||||
border-radius: 999px;
|
||||
accent-color: #c7653d;
|
||||
accent-color: var(--platform-accent);
|
||||
}
|
||||
|
||||
.asset-canvas-surface__operation-kicker {
|
||||
color: #9c4c2d;
|
||||
color: var(--platform-warm-text);
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.08em;
|
||||
}
|
||||
|
||||
.asset-canvas-surface__operation-card--failed {
|
||||
border-color: rgba(164, 65, 45, 0.24);
|
||||
border-color: var(--platform-button-danger-border);
|
||||
}
|
||||
|
||||
.asset-canvas-surface__operation-actions {
|
||||
@@ -252,9 +289,9 @@
|
||||
bottom: -8px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border: 2px solid #fff;
|
||||
border: 2px solid var(--platform-input-fill);
|
||||
border-radius: 50%;
|
||||
background: #c7653d;
|
||||
background: var(--platform-accent);
|
||||
cursor: nwse-resize;
|
||||
}
|
||||
|
||||
@@ -268,14 +305,8 @@
|
||||
}
|
||||
|
||||
.asset-canvas-surface__zoom {
|
||||
display: flex;
|
||||
max-width: min(100%, 280px);
|
||||
justify-content: flex-end;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.asset-canvas-surface__zoom button {
|
||||
min-height: 30px;
|
||||
background: rgba(255, 255, 255, 0.94);
|
||||
}
|
||||
|
||||
.asset-canvas-surface .genarrative-image-canvas__minimap {
|
||||
@@ -283,28 +314,45 @@
|
||||
height: 84px;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
background: rgba(255, 255, 255, 0.94);
|
||||
border: 1px solid var(--platform-surface-border);
|
||||
background: var(--platform-input-fill);
|
||||
box-shadow: var(--platform-nav-active-shadow);
|
||||
}
|
||||
|
||||
.asset-canvas-surface .genarrative-image-canvas__minimap-layer {
|
||||
background: rgba(199, 101, 61, 0.32);
|
||||
background: var(--platform-warm-bg);
|
||||
}
|
||||
|
||||
.asset-canvas-surface .genarrative-image-canvas__minimap-viewport {
|
||||
border: 1px solid #c7653d;
|
||||
border: 1px solid var(--platform-accent);
|
||||
}
|
||||
|
||||
.asset-canvas-surface__status {
|
||||
z-index: 4;
|
||||
display: flex;
|
||||
min-height: 34px;
|
||||
max-height: 96px;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
overflow-y: auto;
|
||||
padding: 8px 12px;
|
||||
border-top: 1px solid rgba(63, 54, 48, 0.12);
|
||||
background: rgba(252, 251, 249, 0.96);
|
||||
border-top: 1px solid var(--platform-line-soft);
|
||||
background: var(--platform-desktop-topbar-fill);
|
||||
color: var(--platform-text-base);
|
||||
font-size: 12px;
|
||||
line-height: 1.4;
|
||||
scrollbar-gutter: stable;
|
||||
}
|
||||
|
||||
.asset-canvas-surface__status > span {
|
||||
min-width: 0;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.asset-canvas-surface__status > span:first-child {
|
||||
color: var(--platform-text-strong);
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
@media (max-width: 760px) {
|
||||
@@ -314,13 +362,22 @@
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.asset-canvas-surface__toolbar {
|
||||
overflow-x: auto;
|
||||
.asset-canvas-surface__toolbar-shell {
|
||||
padding-inline: 8px;
|
||||
}
|
||||
|
||||
.asset-canvas-surface__toolbar input {
|
||||
width: 112px;
|
||||
flex: 0 0 auto;
|
||||
.asset-canvas-surface__savebar {
|
||||
grid-template-columns: minmax(96px, 1fr) minmax(72px, 0.75fr) 70px;
|
||||
}
|
||||
|
||||
.asset-canvas-surface__save {
|
||||
grid-column: 1 / -1;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.asset-canvas-surface__viewport-tools {
|
||||
right: 8px;
|
||||
bottom: 52px;
|
||||
}
|
||||
|
||||
.asset-canvas-surface__generation-options {
|
||||
|
||||
@@ -3929,10 +3929,11 @@ iframe.preview-frame {
|
||||
.game-project-workbench {
|
||||
width: calc(100vw - 72px);
|
||||
max-width: none;
|
||||
gap: 12px;
|
||||
padding-top: 40px;
|
||||
padding-bottom: 0;
|
||||
color: #4b342c;
|
||||
min-height: 0;
|
||||
gap: 10px;
|
||||
padding: 40px 8px 8px;
|
||||
overflow: hidden;
|
||||
color: var(--platform-text-base);
|
||||
}
|
||||
|
||||
.launcher-main:has(.game-project-workbench) {
|
||||
@@ -3944,7 +3945,7 @@ iframe.preview-frame {
|
||||
z-index: 0;
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) minmax(360px, 0.36fr);
|
||||
gap: 12px;
|
||||
gap: 10px;
|
||||
height: clamp(560px, calc(100dvh - 206px), 780px);
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
@@ -3954,16 +3955,21 @@ iframe.preview-frame {
|
||||
.game-workbench-chat {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
border: 1px solid #ead7ce;
|
||||
border: 1px solid var(--platform-desktop-panel-border);
|
||||
border-radius: 16px;
|
||||
background: #fffdfa;
|
||||
box-shadow: 0 14px 36px rgb(103 65 48 / 8%);
|
||||
background: var(--platform-desktop-panel-fill);
|
||||
}
|
||||
|
||||
.game-workbench-stage {
|
||||
position: relative;
|
||||
display: grid;
|
||||
grid-template-rows: auto auto minmax(0, 1fr);
|
||||
box-shadow: var(--platform-panel-shadow);
|
||||
}
|
||||
|
||||
.game-workbench-chat {
|
||||
border-color: var(--platform-line-soft);
|
||||
box-shadow: var(--platform-nav-active-shadow);
|
||||
}
|
||||
|
||||
.game-workbench-stage[data-resource-view-state^='resources.asset-canvas'] {
|
||||
@@ -3983,8 +3989,8 @@ iframe.preview-frame {
|
||||
gap: 12px;
|
||||
min-height: 48px;
|
||||
padding: 8px 12px;
|
||||
border-bottom: 1px solid #f0dfd7;
|
||||
background: #fff8f4;
|
||||
border-bottom: 1px solid var(--platform-line-soft);
|
||||
background: var(--platform-desktop-topbar-fill);
|
||||
}
|
||||
|
||||
.game-workbench-tabs,
|
||||
@@ -3997,8 +4003,9 @@ iframe.preview-frame {
|
||||
|
||||
.game-workbench-tabs {
|
||||
padding: 3px;
|
||||
border: 1px solid var(--platform-subpanel-border);
|
||||
border-radius: 999px;
|
||||
background: #f3ded3;
|
||||
background: var(--platform-nav-fill);
|
||||
}
|
||||
|
||||
.game-workbench-tabs button,
|
||||
@@ -4012,28 +4019,39 @@ iframe.preview-frame {
|
||||
border: 1px solid transparent;
|
||||
border-radius: 999px;
|
||||
background: transparent;
|
||||
color: #735448;
|
||||
color: var(--platform-button-ghost-text);
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.game-workbench-tabs button:focus-visible,
|
||||
.game-workbench-view-actions button:focus-visible,
|
||||
.game-workbench-approval-trigger:focus-visible,
|
||||
.game-agent-dock-more:focus-visible {
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 3px var(--platform-input-focus-ring);
|
||||
}
|
||||
|
||||
.game-workbench-tabs button.is-active,
|
||||
.game-workbench-view-actions button.is-active {
|
||||
border-color: #d7845c;
|
||||
background: #d7845c;
|
||||
color: #fff;
|
||||
border-color: var(--platform-button-primary-border);
|
||||
background: var(--platform-button-primary-fill);
|
||||
color: var(--platform-button-primary-text);
|
||||
}
|
||||
|
||||
.game-workbench-tabs button:disabled,
|
||||
.game-workbench-tabs button[data-unavailable='true'] {
|
||||
color: #a99086;
|
||||
color: var(--platform-text-muted);
|
||||
cursor: not-allowed;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.game-workbench-view-actions button {
|
||||
border-color: #ead6cc;
|
||||
background: #fff;
|
||||
border-color: var(--platform-surface-border);
|
||||
background: var(--platform-button-secondary-fill);
|
||||
color: var(--platform-button-secondary-text);
|
||||
}
|
||||
|
||||
.game-resource-reorder-status {
|
||||
@@ -4850,8 +4868,8 @@ iframe.preview-frame {
|
||||
align-items: center;
|
||||
gap: 9px;
|
||||
padding: 12px 14px;
|
||||
border-bottom: 1px solid #f0dfd7;
|
||||
background: #fff8f4;
|
||||
border-bottom: 1px solid var(--platform-line-soft);
|
||||
background: var(--platform-subpanel-fill);
|
||||
}
|
||||
|
||||
.game-workbench-chat > header > div {
|
||||
@@ -4867,21 +4885,23 @@ iframe.preview-frame {
|
||||
min-height: 28px;
|
||||
margin-left: auto;
|
||||
padding: 0 9px;
|
||||
border: 1px solid #e2c7ba;
|
||||
border: 1px solid var(--platform-surface-border);
|
||||
border-radius: 999px;
|
||||
background: #fff;
|
||||
color: #9c5d42;
|
||||
background: var(--platform-button-ghost-fill);
|
||||
color: var(--platform-button-ghost-text);
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.game-workbench-chat > header strong {
|
||||
color: #a95531;
|
||||
color: var(--platform-text-strong);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.game-workbench-chat > header small {
|
||||
color: #a08a80;
|
||||
color: var(--platform-text-soft);
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
@@ -4890,8 +4910,8 @@ iframe.preview-frame {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border-radius: 50%;
|
||||
background: #d9794c;
|
||||
color: #fff;
|
||||
background: var(--platform-button-primary-fill);
|
||||
color: var(--platform-button-primary-text);
|
||||
font-size: 12px;
|
||||
font-weight: 900;
|
||||
place-items: center;
|
||||
@@ -4908,24 +4928,28 @@ iframe.preview-frame {
|
||||
}
|
||||
|
||||
.game-workbench-chat .project-supervisor-conversation {
|
||||
grid-template-rows: minmax(120px, 1fr) minmax(0, auto) auto auto auto;
|
||||
grid-template-rows: minmax(96px, 1fr) minmax(0, auto) auto auto auto;
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.game-workbench-chat .project-supervisor-message-list {
|
||||
min-height: 120px;
|
||||
min-height: 96px;
|
||||
max-height: none;
|
||||
overflow-y: auto;
|
||||
border: 1px solid #f1e4de;
|
||||
background: #fffdfb;
|
||||
border: 1px solid var(--platform-subpanel-border);
|
||||
background: var(--platform-input-fill);
|
||||
scrollbar-gutter: stable;
|
||||
}
|
||||
|
||||
.game-workbench-chat .agent-runtime-status {
|
||||
max-height: clamp(190px, 30dvh, 300px);
|
||||
max-height: clamp(120px, 24dvh, 240px);
|
||||
min-height: 0;
|
||||
overflow-y: auto;
|
||||
border-color: var(--platform-subpanel-border);
|
||||
background: var(--platform-neutral-bg);
|
||||
color: var(--platform-text-strong);
|
||||
overscroll-behavior: contain;
|
||||
scrollbar-gutter: stable;
|
||||
}
|
||||
@@ -4936,8 +4960,8 @@ iframe.preview-frame {
|
||||
z-index: 3;
|
||||
gap: 5px;
|
||||
padding-bottom: 7px;
|
||||
border-bottom: 1px solid #d8dde5;
|
||||
background: #f8fafd;
|
||||
border-bottom: 1px solid var(--platform-line-soft);
|
||||
background: var(--platform-neutral-bg);
|
||||
}
|
||||
|
||||
.project-runtime-empty-state {
|
||||
@@ -4946,12 +4970,12 @@ iframe.preview-frame {
|
||||
place-content: center;
|
||||
justify-items: center;
|
||||
gap: 10px;
|
||||
color: #d9794c;
|
||||
color: var(--platform-accent);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.project-runtime-empty-state strong {
|
||||
color: #55443c;
|
||||
color: var(--platform-text-strong);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
@@ -4973,6 +4997,73 @@ iframe.preview-frame {
|
||||
.game-workbench-chat .project-runtime-professional-list article {
|
||||
gap: 3px;
|
||||
padding: 7px 8px;
|
||||
border-color: var(--platform-subpanel-border);
|
||||
background: var(--platform-input-fill);
|
||||
}
|
||||
|
||||
.game-workbench-chat .project-runtime-overview,
|
||||
.game-workbench-chat
|
||||
.agent-runtime-status
|
||||
.project-runtime-pending-command {
|
||||
background: var(--platform-warm-bg);
|
||||
}
|
||||
|
||||
.game-workbench-chat .project-runtime-overview > span,
|
||||
.game-workbench-chat .project-runtime-plan-progress span,
|
||||
.game-workbench-chat .project-runtime-professional-list article > header span,
|
||||
.game-workbench-chat .project-runtime-professional-list article p,
|
||||
.game-workbench-chat .project-supervisor-workspace-status {
|
||||
color: var(--platform-text-base);
|
||||
}
|
||||
|
||||
.game-workbench-chat .project-runtime-plan-progress progress {
|
||||
accent-color: var(--platform-accent);
|
||||
}
|
||||
|
||||
.game-workbench-chat
|
||||
.agent-runtime-status
|
||||
.project-runtime-professional-list {
|
||||
border-top-color: var(--platform-line-soft);
|
||||
}
|
||||
|
||||
.game-workbench-chat .project-runtime-professional-actions button {
|
||||
border-color: var(--platform-surface-border);
|
||||
background: var(--platform-button-secondary-fill);
|
||||
color: var(--platform-button-secondary-text);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.game-workbench-chat .project-runtime-professional-actions button:last-child,
|
||||
.game-workbench-chat .project-runtime-recovery > button {
|
||||
border-color: var(--platform-button-primary-border);
|
||||
background: var(--platform-button-primary-fill);
|
||||
color: var(--platform-button-primary-text);
|
||||
}
|
||||
|
||||
.game-workbench-chat
|
||||
.agent-runtime-status
|
||||
.project-runtime-recovery {
|
||||
border-color: var(--platform-button-danger-border);
|
||||
background: var(--platform-button-danger-fill);
|
||||
}
|
||||
|
||||
.game-workbench-chat .project-runtime-recovery > span {
|
||||
color: var(--platform-button-danger-text);
|
||||
}
|
||||
|
||||
.game-workbench-chat .project-runtime-recovery > span small {
|
||||
color: var(--platform-text-base);
|
||||
}
|
||||
|
||||
.game-workbench-chat .message--user {
|
||||
color: var(--platform-warm-text);
|
||||
}
|
||||
|
||||
.game-workbench-chat .message-action,
|
||||
.game-workbench-chat .message-history-more {
|
||||
border-color: var(--platform-surface-border);
|
||||
background: var(--platform-button-secondary-fill);
|
||||
color: var(--platform-button-secondary-text);
|
||||
}
|
||||
|
||||
.game-workbench-chat .project-runtime-professional-list .project-runtime-error {
|
||||
@@ -4989,6 +5080,11 @@ iframe.preview-frame {
|
||||
.game-workbench-chat .project-supervisor-composer {
|
||||
grid-template-columns: minmax(0, 1fr) 82px;
|
||||
align-items: stretch;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
padding-top: 8px;
|
||||
border-top: 1px solid var(--platform-line-soft);
|
||||
background: var(--platform-desktop-panel-fill);
|
||||
}
|
||||
|
||||
.game-workbench-chat .project-supervisor-composer textarea {
|
||||
@@ -4996,12 +5092,26 @@ iframe.preview-frame {
|
||||
min-height: 72px;
|
||||
max-height: 72px;
|
||||
resize: none;
|
||||
border-color: var(--platform-surface-border);
|
||||
background: var(--platform-input-fill);
|
||||
color: var(--platform-text-strong);
|
||||
}
|
||||
|
||||
.game-workbench-chat .project-supervisor-composer textarea:focus-visible {
|
||||
border-color: var(--platform-surface-hover-border);
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 3px var(--platform-input-focus-ring);
|
||||
}
|
||||
|
||||
.game-workbench-chat .project-supervisor-composer button {
|
||||
width: auto;
|
||||
height: auto;
|
||||
min-height: 72px;
|
||||
border-color: var(--platform-button-primary-border);
|
||||
background: var(--platform-button-primary-fill);
|
||||
color: var(--platform-button-primary-text);
|
||||
font-weight: 800;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.game-agent-dock {
|
||||
@@ -5011,46 +5121,49 @@ iframe.preview-frame {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
gap: 6px;
|
||||
overflow: visible;
|
||||
min-height: 66px;
|
||||
padding: 8px 12px;
|
||||
border: 1px solid #ead7ce;
|
||||
border-radius: 16px;
|
||||
background: #f7e6dc;
|
||||
box-shadow: 0 12px 28px rgb(103 65 48 / 10%);
|
||||
min-height: 58px;
|
||||
padding: 6px 10px;
|
||||
border: 1px solid var(--platform-line-soft);
|
||||
border-radius: 14px;
|
||||
background: var(--platform-subpanel-fill);
|
||||
box-shadow: var(--platform-nav-active-shadow);
|
||||
}
|
||||
|
||||
.game-agent-dock-more {
|
||||
flex: 0 0 auto;
|
||||
min-height: 40px;
|
||||
min-height: 38px;
|
||||
padding: 0 12px;
|
||||
border: 1px solid #d9b8a8;
|
||||
border: 1px solid var(--platform-surface-border);
|
||||
border-radius: 12px;
|
||||
background: rgb(255 255 255 / 72%);
|
||||
color: #765447;
|
||||
background: var(--platform-button-ghost-fill);
|
||||
color: var(--platform-button-ghost-text);
|
||||
font-size: 11px;
|
||||
font-weight: 800;
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.game-agent-dock-more:hover {
|
||||
border-color: #c98361;
|
||||
color: #a7522f;
|
||||
border-color: var(--platform-surface-hover-border);
|
||||
color: var(--platform-text-strong);
|
||||
}
|
||||
|
||||
.game-agent-dock-item {
|
||||
position: relative;
|
||||
display: grid;
|
||||
grid-template-columns: 34px minmax(0, 1fr);
|
||||
flex: 1 1 128px;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
min-width: 150px;
|
||||
padding: 7px 10px;
|
||||
border: 1px solid transparent;
|
||||
min-width: 0;
|
||||
max-width: 170px;
|
||||
padding: 6px 8px;
|
||||
border: 1px solid var(--platform-icon-border);
|
||||
border-radius: 12px;
|
||||
background: rgb(255 255 255 / 68%);
|
||||
color: #584137;
|
||||
background: var(--platform-button-ghost-fill);
|
||||
color: var(--platform-text-base);
|
||||
text-align: left;
|
||||
cursor: default;
|
||||
}
|
||||
@@ -5060,8 +5173,8 @@ iframe.preview-frame {
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
border-radius: 12px;
|
||||
background: #d27a50;
|
||||
color: #fff;
|
||||
background: var(--platform-nav-active-fill);
|
||||
color: var(--platform-warm-text);
|
||||
font-size: 12px;
|
||||
font-weight: 900;
|
||||
place-items: center;
|
||||
@@ -5069,16 +5182,24 @@ iframe.preview-frame {
|
||||
|
||||
.game-agent-dock-item > span:nth-child(2) {
|
||||
display: grid;
|
||||
min-width: 0;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.game-agent-dock-item strong {
|
||||
overflow: hidden;
|
||||
color: var(--platform-text-strong);
|
||||
font-size: 11px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.game-agent-dock-item small {
|
||||
color: #96776a;
|
||||
overflow: hidden;
|
||||
color: var(--platform-text-soft);
|
||||
font-size: 9px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.game-agent-dock-item.is-running::after {
|
||||
@@ -5088,8 +5209,8 @@ iframe.preview-frame {
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
border-radius: 50%;
|
||||
background: #5ec46b;
|
||||
box-shadow: 0 0 0 3px rgb(94 196 107 / 16%);
|
||||
background: var(--platform-success-text);
|
||||
box-shadow: 0 0 0 3px var(--platform-success-border);
|
||||
content: '';
|
||||
}
|
||||
|
||||
@@ -5106,18 +5227,18 @@ iframe.preview-frame {
|
||||
}
|
||||
|
||||
.game-agent-dock-item.is-waiting::after {
|
||||
background: #df8a16;
|
||||
box-shadow: 0 0 0 3px rgb(223 138 22 / 16%);
|
||||
background: var(--platform-warm-text);
|
||||
box-shadow: 0 0 0 3px var(--platform-warm-border);
|
||||
}
|
||||
|
||||
.game-agent-dock-item.is-failed::after {
|
||||
background: #d64545;
|
||||
box-shadow: 0 0 0 3px rgb(214 69 69 / 16%);
|
||||
background: var(--platform-button-danger-text);
|
||||
box-shadow: 0 0 0 3px var(--platform-button-danger-border);
|
||||
}
|
||||
|
||||
.game-agent-dock-item.is-completed::after {
|
||||
background: #2878d4;
|
||||
box-shadow: 0 0 0 3px rgb(40 120 212 / 16%);
|
||||
background: var(--platform-accent);
|
||||
box-shadow: 0 0 0 3px var(--platform-nav-active-border);
|
||||
}
|
||||
|
||||
.game-agent-dock-detail {
|
||||
@@ -5128,11 +5249,11 @@ iframe.preview-frame {
|
||||
display: none;
|
||||
width: 220px;
|
||||
padding: 10px 12px;
|
||||
border: 1px solid #dcc2b5;
|
||||
border: 1px solid var(--platform-modal-border);
|
||||
border-radius: 10px;
|
||||
background: #4f433d;
|
||||
color: #fff;
|
||||
box-shadow: 0 12px 30px rgb(50 30 22 / 20%);
|
||||
background: var(--platform-modal-fill);
|
||||
color: var(--platform-text-strong);
|
||||
box-shadow: var(--platform-panel-shadow);
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
@@ -5286,7 +5407,7 @@ iframe.preview-frame {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@media (min-width: 761px) and (min-height: 680px) {
|
||||
@media (min-width: 761px) {
|
||||
.game-project-workbench {
|
||||
grid-template-rows: minmax(0, 1fr) auto;
|
||||
height: 100dvh;
|
||||
@@ -5385,6 +5506,7 @@ iframe.preview-frame {
|
||||
|
||||
.game-agent-dock-item {
|
||||
width: 100%;
|
||||
max-width: none;
|
||||
}
|
||||
|
||||
.game-agent-dock-detail {
|
||||
|
||||
@@ -2175,28 +2175,35 @@ export function registerProjectWorkbenchFoundationTests() {
|
||||
);
|
||||
|
||||
expect(styles).toMatch(
|
||||
/\.game-project-workbench\s*\{[^}]*width:\s*calc\(100vw - 72px\)[^}]*padding-top:\s*40px/s,
|
||||
/\.game-project-workbench\s*\{[^}]*width:\s*calc\(100vw - 72px\)[^}]*padding:\s*40px 8px 8px[^}]*overflow:\s*hidden/s,
|
||||
);
|
||||
expect(styles).toMatch(
|
||||
/@media \(min-width: 761px\) and \(min-height: 680px\)[\s\S]*?\.game-project-workbench\s*\{[^}]*grid-template-rows:\s*minmax\(0, 1fr\) auto[^}]*height:\s*100dvh/,
|
||||
/@media \(min-width: 761px\)[\s\S]*?\.game-project-workbench\s*\{[^}]*grid-template-rows:\s*minmax\(0, 1fr\) auto[^}]*height:\s*100dvh/,
|
||||
);
|
||||
expect(styles).toMatch(
|
||||
/\.game-workbench-chat \.project-supervisor-conversation\s*\{[^}]*grid-template-rows:\s*minmax\(120px, 1fr\) minmax\(0, auto\) auto auto auto[^}]*height:\s*100%[^}]*overflow:\s*hidden/s,
|
||||
/\.game-workbench-chat \.project-supervisor-conversation\s*\{[^}]*grid-template-rows:\s*minmax\(96px, 1fr\) minmax\(0, auto\) auto auto auto[^}]*height:\s*100%[^}]*overflow:\s*hidden/s,
|
||||
);
|
||||
expect(styles).toMatch(
|
||||
/\.game-workbench-chat \.agent-runtime-status\s*\{[^}]*max-height:\s*clamp\(190px, 30dvh, 300px\)[^}]*overflow-y:\s*auto/s,
|
||||
/\.game-workbench-chat \.agent-runtime-status\s*\{[^}]*max-height:\s*clamp\(120px, 24dvh, 240px\)[^}]*overflow-y:\s*auto/s,
|
||||
);
|
||||
expect(styles).toMatch(
|
||||
/\.game-workbench-chat \.project-runtime-summary\s*\{[^}]*position:\s*sticky[^}]*top:\s*-10px/s,
|
||||
);
|
||||
expect(styles).toMatch(
|
||||
/\.game-workbench-chat \.project-supervisor-composer\s*\{[^}]*grid-template-columns:\s*minmax\(0, 1fr\) 82px/s,
|
||||
/\.game-workbench-chat \.project-supervisor-composer\s*\{[^}]*grid-template-columns:\s*minmax\(0, 1fr\) 82px[^}]*z-index:\s*2[^}]*border-top:\s*1px solid var\(--platform-line-soft\)/s,
|
||||
);
|
||||
expect(styles).toMatch(
|
||||
/\.game-workbench-chat \.project-supervisor-composer button\s*\{[^}]*min-height:\s*72px[^}]*white-space:\s*nowrap/s,
|
||||
);
|
||||
const agentDockRule = styles.match(/\.game-agent-dock\s*\{([^}]*)\}/s);
|
||||
expect(agentDockRule?.[1]).toContain('overflow: visible;');
|
||||
expect(agentDockRule?.[1]).not.toContain('overflow-x:');
|
||||
expect(agentDockRule?.[1]).toContain('z-index: 30;');
|
||||
expect(agentDockRule?.[1]).toContain('isolation: isolate;');
|
||||
expect(agentDockRule?.[1]).toContain('min-height: 58px;');
|
||||
expect(styles).toMatch(
|
||||
/\.game-agent-dock-item\s*\{[^}]*flex:\s*1 1 128px[^}]*min-width:\s*0[^}]*max-width:\s*170px/s,
|
||||
);
|
||||
expect(styles).toMatch(
|
||||
/@media \(max-width: 760px\)[\s\S]*?\.game-workbench-layout\s*\{[^}]*height:\s*auto/,
|
||||
);
|
||||
|
||||
@@ -416,6 +416,82 @@ afterEach(() => {
|
||||
});
|
||||
|
||||
describe('Tauri 素材创作无限画布独立 Surface', () => {
|
||||
it('直接消费共享 chrome,并暴露工具栏结构与原生按钮状态', async () => {
|
||||
const memory = memoryHost();
|
||||
renderSurface(memory.host);
|
||||
await screen.findByText('画布可编辑');
|
||||
|
||||
const toolbar = screen.getByRole('toolbar', { name: '素材画布工具栏' });
|
||||
expect(
|
||||
toolbar.classList.contains('genarrative-image-canvas__toolbar'),
|
||||
).toBe(true);
|
||||
expect(
|
||||
toolbar.querySelectorAll('.genarrative-image-canvas__toolbar-group'),
|
||||
).toHaveLength(3);
|
||||
expect(
|
||||
toolbar.querySelectorAll('.genarrative-image-canvas__toolbar-divider'),
|
||||
).toHaveLength(2);
|
||||
|
||||
const generate = screen.getByRole('button', { name: 'AI 生成图片' });
|
||||
expect(
|
||||
generate.classList.contains(
|
||||
'genarrative-image-canvas__chrome-button',
|
||||
),
|
||||
).toBe(true);
|
||||
expect(generate.getAttribute('aria-expanded')).toBe('false');
|
||||
expect(
|
||||
(screen.getByRole('button', { name: '撤销' }) as HTMLButtonElement)
|
||||
.disabled,
|
||||
).toBe(true);
|
||||
expect(
|
||||
(screen.getByRole('button', { name: '重做' }) as HTMLButtonElement)
|
||||
.disabled,
|
||||
).toBe(true);
|
||||
expect(
|
||||
(screen.getByRole('button', { name: '删除' }) as HTMLButtonElement)
|
||||
.disabled,
|
||||
).toBe(true);
|
||||
|
||||
fireEvent.click(generate);
|
||||
expect(generate.getAttribute('aria-expanded')).toBe('true');
|
||||
expect(screen.getByRole('dialog', { name: 'AI 图片生成' })).toBeTruthy();
|
||||
expect(
|
||||
screen
|
||||
.getByRole('button', { name: '继续确认' })
|
||||
.classList.contains('genarrative-image-canvas__chrome-button'),
|
||||
).toBe(true);
|
||||
expect(screen.getByRole('button', { name: '取消' })).toBeTruthy();
|
||||
});
|
||||
|
||||
it('让生成、失败、保存与取消区域使用内部滚动而不被固定高度裁剪', () => {
|
||||
const css = readFileSync(
|
||||
resolve(
|
||||
process.cwd(),
|
||||
'apps/ai-game-creator-shell/src/features/asset-canvas/assetCanvasSurface.css',
|
||||
),
|
||||
'utf8',
|
||||
);
|
||||
|
||||
expect(css).toMatch(
|
||||
/\.asset-canvas-surface__generation-dialog\s*\{[^}]*max-height:\s*calc\(100% - 32px\)[^}]*overflow:\s*auto/s,
|
||||
);
|
||||
expect(css).toMatch(
|
||||
/\.asset-canvas-surface__operation-overlay\s*\{[^}]*overflow:\s*auto/s,
|
||||
);
|
||||
expect(css).toMatch(
|
||||
/\.asset-canvas-surface__operation-card\s*\{[^}]*max-height:\s*100%[^}]*overflow:\s*auto/s,
|
||||
);
|
||||
expect(css).toMatch(
|
||||
/\.asset-canvas-surface__status\s*\{[^}]*max-height:\s*96px[^}]*overflow-y:\s*auto/s,
|
||||
);
|
||||
expect(css).toMatch(
|
||||
/\.asset-canvas-surface__savebar\s*\{[^}]*grid-template-columns:/s,
|
||||
);
|
||||
expect(css).toMatch(
|
||||
/@media \(max-width: 760px\)[\s\S]*?\.asset-canvas-surface__save\s*\{[^}]*grid-column:\s*1 \/ -1[^}]*width:\s*100%/s,
|
||||
);
|
||||
});
|
||||
|
||||
it('取消时先把草稿标记为 cancelled,再返回资源总览', async () => {
|
||||
const memory = memoryHost();
|
||||
const { onCancel } = renderSurface(memory.host);
|
||||
|
||||
@@ -85,6 +85,8 @@
|
||||
|
||||
### 3.7 主站 UI 对齐与共享视觉边界
|
||||
|
||||
实现状态(2026-08-06):主站与 Tauri 已完成同源 chrome 接入。Tauri 现有中央素材画布直接消费共享动作按钮、工具栏、工具组和分隔符;工作台外围继续保留四区结构,并以平台 token 统一中央壳、Supervisor、Agent Dock、状态提示和主要操作。生成、保存、登录、计费、草稿、manifest、Runtime 和审批语义未随换肤改变。
|
||||
|
||||
- 项目工作台继续保留左侧平台导航、中央主视窗、右侧 Project Supervisor 和底部专业 Agent 状态栏四区结构;主站图片编辑器只作为视觉语言和共享画布组件的事实源,不把其素材库侧栏、账号业务或云端项目外壳整体搬入客户端。
|
||||
- 平台主题事实源固定为 `packages/shared/src/theme.css`。画布通用 chrome 固定落在 `@genarrative/image-canvas-react`,主站与 Tauri 必须直接 import 同一组件和作用域样式;客户端不得复制 `src/components/image-editor/`,也不得导入主站完整 `src/index.css`。
|
||||
- 第一批共享 chrome 固定覆盖画布动作按钮、工具栏、工具分组和分隔符。按钮的默认、悬停、键盘焦点、选中、禁用和主次色语义由共享层表达;宿主只提供图标、文案、事件与业务禁用条件。
|
||||
@@ -502,10 +504,10 @@ type ProjectAgentMudPointAttribution = {
|
||||
|
||||
1. `packages/image-canvas-react` 暴露共享画布动作按钮、工具栏、工具分组和分隔符;主站图片编辑器直接消费这些组件,旧网站组件只允许保留薄适配,不得继续维护另一份按钮可访问性或选中态实现。
|
||||
2. 共享样式全部使用 `.genarrative-image-canvas*` 作用域并消费 `--platform-*` / `--image-canvas-brand-*` token;Tauri 不导入主站完整 `src/index.css`,主站也不复制共享样式回业务 CSS。
|
||||
3. 后续客户端接入完成后,`1280×800` 下中央画布工具栏保持单行或受控横向收纳,中文动作不得逐字换行;聊天输入、主要保存动作和底部 Agent Dock 始终可见,document/body 不产生页面级溢出。
|
||||
3. 客户端接入完成后,`1280×800` 下中央画布工具栏保持单行或受控横向收纳,中文动作不得逐字换行;聊天输入、主要保存动作和底部 Agent Dock 始终可见,document/body 不产生页面级溢出。
|
||||
4. 主站现有图片编辑器的按钮名称、tool selection、hover/focus/pressed/disabled 行为与工具栏位置不因共享抽取回归;网站账号、钱包、素材库、生成和导出逻辑不下沉共享层。
|
||||
5. 客户端生成、保存、取消、恢复、登录失效与余额不足只更换视觉承载,不改变 Host Port 调用、幂等身份、草稿 CAS、manifest/revision、Runtime 审批或钱包刷新语义。
|
||||
6. 阶段一、二只冻结合同并让主站消费共享 chrome,不修改客户端中央素材画布;客户端中央区、Supervisor 和 Agent Dock 的正式换肤在后续同一 UI 实施阶段完成。
|
||||
6. 阶段一、二冻结合同并让主站消费共享 chrome;2026-08-06 的阶段三至五已经完成客户端中央区、Supervisor 和 Agent Dock 的正式换肤、定向测试与 `1280×800` 验收。独立 Vite 没有登录会话时继续显示真实登录门禁,不为视觉测试增加绕过入口。
|
||||
|
||||
## 8. 非目标
|
||||
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
## 2026-08-06 Game Agent 画布视觉以主站 token 与共享 chrome 为唯一来源
|
||||
|
||||
- 背景:网站与 Tauri 已经共同消费 `image-canvas-core/react` 的 viewport、selection、renderer 和 history,但客户端素材画布仍维护独立的文字工具栏、按钮和状态外观,`1280×800` 下会出现动作逐字换行、主次不清和工作台四区视觉漂移。直接复制主站 `src/components/image-editor` 或整包 `src/index.css` 会重新形成宿主分叉和隐式全局依赖。
|
||||
- 决策:保留 Game Agent 左导航、中央主视窗、右侧 Supervisor 和底部 Agent Dock 四区布局;平台颜色、边框、文本和 surface 继续以 `packages/shared/src/theme.css` 为事实源,画布动作按钮、工具栏、分组与分隔符进入现有 `@genarrative/image-canvas-react`。主站先改为直接消费共享 chrome,Tauri 在下一 UI 阶段接入同一组件。
|
||||
- 决策:保留 Game Agent 左导航、中央主视窗、右侧 Supervisor 和底部 Agent Dock 四区布局;平台颜色、边框、文本和 surface 继续以 `packages/shared/src/theme.css` 为事实源,画布动作按钮、工具栏、分组与分隔符进入现有 `@genarrative/image-canvas-react`。主站和 Tauri 均直接消费同一 chrome,Tauri 宿主只保留业务接线和布局适配。
|
||||
- 依赖边界:共享组件只接受 React props、图标节点、短文案和事件,不读取账号、钱包、项目、生成、Tauri 或 HTTP。主站 `EditorIconButton` 只保留 Lucide/特殊浮层薄适配,主站业务 CSS 只保留定位和专属状态;Tauri 禁止导入网站完整 CSS。内部错误码、开发者 API 配置和本机绝对路径不作为普通用户默认视觉内容。
|
||||
- 分期:阶段一、二只更新权威合同、抽取共享 chrome 并让主站消费,不修改客户端 `AssetCanvasSurface`;阶段三、四再统一中央素材画布、Supervisor 与 Agent Dock,阶段五验证与修复必须和客户端实施同一任务完成。
|
||||
- 验证:共享 React 组件与主站图片编辑器 7 个定向测试文件共 `92/92` 通过,覆盖可访问性、状态、透传、工具列表、选中态与 option hover/focus;主站与客户端 typecheck、编码检查和 `git diff --check` 通过。
|
||||
- 分期:阶段一、二更新权威合同、抽取共享 chrome 并让主站消费;阶段三、四已统一中央素材画布、Supervisor 与 Agent Dock;阶段五在同一变更中补齐测试、真实视口测量和发现回归修复。
|
||||
- 验证:共享 chrome 与 Tauri Surface 定向测试 `20/20` 通过,覆盖可访问性、pressed/expanded/disabled、工具组/分隔符、生成/失败/保存/取消和非裁剪 CSS;AppSurface 横屏合同 `1/1` 通过,锁定 Supervisor composer、Agent Dock 与 `100dvh` 两行工作台。客户端/全仓 typecheck、定向 ESLint、编码和 diff 检查通过。完整 AppSurface 本次运行是 `352/354` 通过:preview 权限用例单跑通过,属于全量异步顺序波动;仍失败的“画板 API Key”运行配置用例不涉及本次 diff,留在原业务范围处理。应用内浏览器 `1280×800` 实测 window/document/body client 与 scroll 均为 `1280×800`;真实入口没有登录会话时保持登录门禁,不增加测试绕过。
|
||||
- 关联:`docs/prd/【AI游戏创作】项目开发工作台PRD-2026-07-20.md`、`docs/technical/【技术方案】客户端素材创作无限画布阶段一合同-2026-08-05.md`、`packages/image-canvas-react/`。
|
||||
|
||||
## 2026-08-05 无限画布阶段三以 Tauri 持久草稿和项目 mutation 事务形成独立图片闭环
|
||||
|
||||
@@ -30,6 +30,14 @@
|
||||
- 验证:共享 React 测试重复 mount/unmount 后 wheel add/remove 数量相等、ResizeObserver 精确 disconnect,并挂两个含各自小地图的 viewport,确认只更新目标实例;网站与 Tauri 壳分别 typecheck/build。
|
||||
- 关联:`packages/image-canvas-react/src/useImageCanvasViewportControls.ts`、`packages/image-canvas-react/src/CanvasPortal.tsx`、根目录与 `apps/ai-game-creator-shell` 的 Vite 配置。
|
||||
|
||||
## 桌面工作台不要让 Supervisor 内容高度挤掉输入区和 Agent Dock
|
||||
|
||||
- 现象:`1280×800` 或更矮窗口中,Supervisor 的消息、Runtime 状态和错误正文共同按内容高度增长,聊天输入被推到栏外;底部 Dock 使用固定宽度卡片时还会在中等宽度造成页面级横向溢出。中央画布的生成卡和状态栏若同时固定高度并隐藏 overflow,进度、失败、重试、保存或取消动作会被裁掉。
|
||||
- 原因:四区工作台没有把“主区内部滚动”和“页面级滚动”分开;Supervisor 的长状态没有独立上限,Dock 卡片不能收缩,宿主又用全局按钮/固定行高样式覆盖共享 chrome。
|
||||
- 处理:桌面工作台使用 `100dvh` 两行网格,第一行 `minmax(0, 1fr)` 承载中央区与 Supervisor,第二行承载 Dock;消息和 Runtime 分别内部滚动,composer 保持最后一行并设置明确层级。Dock 卡片使用可收缩 flex 与文本省略。画布 dialog、进度/失败卡和状态栏设置 `min-height: 0`、受限最大高度与内部滚动;宿主不再覆盖所有按钮,只为主要动作和布局提供 token 化薄样式。
|
||||
- 验证:AppSurface CSS 合同检查 `100dvh`、Supervisor composer、Runtime 内滚动、Dock 常驻/可收缩;素材画布测试检查生成 dialog、operation card、状态栏和窄屏保存动作不会被裁剪。真实入口在 `1280×800` 测量 document/body client 与 scroll 一致;登录门禁不可为视觉测试绕过。
|
||||
- 关联:`apps/ai-game-creator-shell/src/styles.css`、`apps/ai-game-creator-shell/src/features/asset-canvas/assetCanvasSurface.css`、`apps/ai-game-creator-shell/tests/appSurface/project-development.suite.ts`、`apps/ai-game-creator-shell/tests/assetCanvasSurface.test.tsx`。
|
||||
|
||||
## 正式素材提交不能把多文件写入或 Tauri 事件误当成一次原子动作
|
||||
|
||||
- 现象:图片已经落到 `assets/` 但 manifest 没有资产,或 manifest 已追加而 project revision/草稿仍是旧值;进程在 emit 前后退出后,用户重试又得到第二份图片、第二个 asset 或重复选中。
|
||||
|
||||
@@ -960,3 +960,11 @@ game-project/
|
||||
- 应用重启时 `accepted/running` 只恢复 GET,`prepared` 只以私有账本原请求字节和原键恢复同一 POST。用户停止等待只失效当前 focus generation;不可取消的远端操作继续 reconcile,迟到 completed 可以更新原项目但不能抢焦点。配置缺失、结果引用不稳定、下载/提交失败和未知结果都不得显示资源创建成功。
|
||||
- 最终审计后 Tauri 撤销/重做直接使用与网站相同的共享 `useCanvasHistory`,共享 history 同时恢复 viewport、选择、图层位置和缩放边界;Tauri 自身只保留 Pointer 事件接线,平移、缩放、选择、图层变换和 renderer 算法继续来自共享 core/react。
|
||||
- 正式事务把首个快照、全部快照和 journal 写入后但 ledger 写入前纳入故障矩阵。恢复发现无 ledger 的 transaction 时,只有目录内容全部属于受控快照/临时文件、正式目标文件不存在、manifest 与 project revision 仍精确等于 before,才清理目录并返回 rolled-back;任何未知文件或权威状态变化都失败关闭。同一 commit/idempotency 身份在清理后可重放,最终仍只登记一份资产。
|
||||
|
||||
## 2026-08-06 Game Agent 画布 UI 对齐阶段三至五
|
||||
|
||||
- 中央 `AssetCanvasSurface` 沿用现有 create/refine、生成、保存、取消和恢复接线,直接消费 `@genarrative/image-canvas-react` 的 `CanvasChromeButton / CanvasToolbar / CanvasToolbarGroup / CanvasToolbarDivider`。宿主不再维护全局按钮皮肤,只保留字段、面板定位、主要动作和平台 token 映射;主站完整 `src/index.css` 没有进入 Tauri。
|
||||
- 顶部动作与保存设置分层,工具组保持中文单行并允许工具栏受控横向收纳;生成设置仍是独立 dialog。进度/失败卡、dialog 和底部状态栏使用内部滚动,不再由固定高度或父级 overflow 裁剪,保存/确认使用平台 primary 层级。
|
||||
- 工作台仍是左导航、中央主视窗、右 Project Supervisor、底部 Agent Dock 四区。中央区使用主面板层级;Supervisor 的消息/Runtime 独立滚动且 composer 常驻;Dock 改为较轻的持续状态 surface 与可收缩卡片。桌面网格按 `100dvh` 将 Dock 固定在第二行,`≤760px` 只保留开发兼容堆叠。
|
||||
- AppSurface/CSS 合同锁定 `1280×800` 下的工作台无页面级溢出、Supervisor composer、主要保存动作和 Dock 可见;共享/Tauri 定向用例锁定按钮名称、工具组/分隔符、pressed/expanded/disabled 和生成/失败/保存/取消非裁剪。真实 Vite 页面测得 window/document/body client 与 scroll 均为 `1280×800`;无登录会话时按既有安全合同停在登录门禁,不增加或使用测试绕过。
|
||||
- 本次只调整 UI 和测试/文档,未改变图片生成请求、登录态、泥点计费、钱包刷新、Host Port、Tauri command/event、草稿 CAS、manifest/revision、幂等恢复、Runtime、审批、资源投影或保存事务。
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
更新时间:`2026-08-06`
|
||||
|
||||
状态:阶段一产品与技术合同已冻结;截至 2026-08-06,客户登录态图片生成、泥点计费链路、中央进度/失败态和 Tauri 正式资产提交已落地,后续增量仍受本文合同约束。
|
||||
状态:阶段一产品与技术合同已冻结;截至 2026-08-06,客户登录态图片生成、泥点计费链路、中央进度/失败态、Tauri 正式资产提交及画布 UI 对齐阶段三至五已落地,后续增量仍受本文合同约束。
|
||||
|
||||
本文是网站与 AI 游戏创作 Tauri 客户端共享图片画布能力的下一阶段编码依据。若本文与资源管理阶段七的“美术编辑暂缓”口径冲突,以本文对后续素材创作切片的更新决定为准;资源总览既有布局、依赖图和只读聚焦合同继续有效。
|
||||
|
||||
@@ -204,7 +204,7 @@ interface ImageCanvasHostPort {
|
||||
|
||||
### 3.4 主站 UI 对齐与共享画布 chrome
|
||||
|
||||
实现状态(2026-08-06,阶段一、二):以下四个共享组件及作用域样式已经进入 `@genarrative/image-canvas-react`;主站默认 `EditorIconButton`、底部画布工具栏及画布内直接使用的常规图标/短标签动作已经消费共享按钮,网站专属 `surfaceFloating` / `darkMini` 仍保留薄适配。客户端 `AssetCanvasSurface` 尚未接入,按本节边界留给后续 UI 实施阶段。
|
||||
实现状态(2026-08-06,阶段一至五):以下四个共享组件及作用域样式已经进入 `@genarrative/image-canvas-react`;主站默认 `EditorIconButton`、底部画布工具栏及画布内直接使用的常规图标/短标签动作已经消费共享按钮,网站专属 `surfaceFloating` / `darkMini` 仍保留薄适配。客户端 `AssetCanvasSurface` 的常规动作、三组工具、两个分隔符、保存/生成/失败动作和浮动缩放栏也已直接消费同一 chrome;宿主只保留业务回调、字段、面板布局和平台 token 映射。
|
||||
|
||||
第一批通用视觉组件固定由 `@genarrative/image-canvas-react` 暴露:
|
||||
|
||||
@@ -220,7 +220,7 @@ CanvasToolbarDivider
|
||||
- 共享 CSS 继续位于 `packages/image-canvas-react/src/styles.css`,选择器必须以 `.genarrative-image-canvas` 前缀开头。颜色优先消费宿主继承的 `--image-canvas-brand-*`,再回退 `--platform-*`,最后才使用包内安全默认值。
|
||||
- 主站现役 `EditorIconButton` 可以保留为传入 Lucide icon 的薄 adapter,但默认画布按钮必须委托 `CanvasChromeButton`;特殊网站浮层或暗色媒体按钮仍可留在宿主,不能为了共享视觉把网站 portal、账号或媒体业务下沉。
|
||||
- 主站底部画布工具栏必须委托 `CanvasToolbar`、`CanvasToolbarGroup` 和 `CanvasToolbarDivider`,位置、引导态和工具 option hover 仍由网站宿主控制。共享抽取完成后,主站业务 CSS 只保留定位和主站专属状态,不重复维护通用按钮、浮动 surface、分组和分隔符样式。
|
||||
- Tauri 在后续 UI 实施阶段消费同一组件;本阶段不修改 `AssetCanvasSurface` 的 JSX/CSS,不把生成、保存、草稿、manifest 或 Runtime 逻辑带入共享包。
|
||||
- Tauri 直接消费同一组件,且没有把生成、保存、草稿、manifest、账号、钱包、HTTP 或 Runtime 逻辑带入共享包;这些事实继续由现有 Host Adapter 和宿主回调负责。
|
||||
- 主题事实源仍是 `packages/shared/src/theme.css`。Tauri 只能导入共享主题、`@genarrative/image-canvas-react/styles.css` 和自身宿主样式,禁止导入网站整包 `src/index.css`。
|
||||
- 共享组件测试必须覆盖名称、默认 `type=button`、pressed/expanded、禁用、带短标签动作、floating toolbar surface 和 ref/class/event 透传;主站测试继续锁定工具列表、选中态和 option tool 的 hover/focus 行为。
|
||||
|
||||
@@ -957,3 +957,12 @@ confirmation-required
|
||||
- `accepted/running` 重启只查询原 operation;`prepared` 只以账本中的原 endpoint、原始字节和原键恢复同一 POST。轮询超时、临时鉴权/网关错误或结果未知保持可恢复,不自动转 failed 或换键扣费。
|
||||
- 用户“停止等待”只撤销当前 session 的聚焦意图,不伪装远端取消。后台 command 可以继续安全 reconcile;应用退出后由项目恢复扫描继续原 operation。迟到完成可以更新原项目权威缓存和 manifest,但不能切回旧画布、清搜索或抢焦点。
|
||||
- 登录失效、余额不足、平台生成服务配置错误、远端 terminal failed、稳定引用缺失、下载失败、staging/正式提交失败都不得返回资源创建成功。客户模式不得把缺少开发者 API Key 显示为失败原因。只有 terminal failed 才允许用户通过新的独立确认建立新 intent;其它未知或对账状态必须保留原身份。
|
||||
|
||||
## 15. 2026-08-06 画布 UI 对齐阶段三至五收口
|
||||
|
||||
- 阶段三没有新建平行画布。现有 `AssetCanvasSurface` 直接使用 `CanvasChromeButton`、`CanvasToolbar`、`CanvasToolbarGroup` 和 `CanvasToolbarDivider`,覆盖返回、导入、撤销、重做、删除、AI 生成、保存、生成确认、失败重试、停止等待和缩放。共享组件只接收图标节点、名称、事件和原生状态,不读取 Tauri、账号、钱包或 HTTP。
|
||||
- Tauri 样式只导入 `packages/shared/src/theme.css`、`@genarrative/image-canvas-react/styles.css` 与客户端宿主样式,没有导入主站 `src/index.css`。宿主删除了覆盖全部按钮的重复视觉规则,只保留保存/确认主操作、字段、弹窗、进度/失败卡、状态栏和 viewport 定位;颜色、边框、surface、文字、focus 和主次动作均使用平台 token。
|
||||
- 生成设置继续通过独立 dialog 展示。生成/失败卡和底部状态使用受限高度加内部滚动,保存设置在窄宽下换为两行并保持主按钮整行可见;工具组不拆字,工具栏需要时横向收纳。普通确认文案不显示 manifest、幂等实现、开发者 API 配置或本机绝对路径。
|
||||
- 阶段四保留左导航、中央主视窗、右侧 Project Supervisor 和底部 Agent Dock 四区 DOM。中央壳使用主面板层级;Supervisor 降为协作面板,其消息和 Runtime 状态内部滚动、composer 固定在栏底;Dock 使用较轻 surface 和可收缩状态卡,不与中央主要操作争抢层级。
|
||||
- 桌面 `>760px` 统一使用 `100dvh` 的两行工作台网格,第一行是内部滚动的主视窗/Supervisor,第二行是常驻 Dock。正式 `1280×800` 基线下页面级尺寸合同为 window/document/body `1280×800`;无登录会话的真实 Vite 入口按安全合同停在登录页,不绕过门禁,工作台内部由 AppSurface、共享 chrome 和 CSS 合同测试提供可重复证据。
|
||||
- 阶段五定向测试覆盖共享 chrome 的 pressed、Tauri 实际消费的工具组/分隔符、按钮名称、expanded/disabled、生成确认、失败重试、保存/取消,以及生成 dialog、进度/失败卡、状态栏、Supervisor composer 和 Agent Dock 的非裁剪/可见性合同。生成请求、登录态、泥点计费、Host Port、草稿 CAS、manifest/revision、Runtime、审批和保存事务均未改变。
|
||||
|
||||
Reference in New Issue
Block a user