feat(wizard): per-phase preview — graph in Analyze, form in Generate (#8)
This commit was merged in pull request #8.
This commit is contained in:
@@ -2260,3 +2260,71 @@ select.studio-input { background: var(--bp-paper); }
|
|||||||
}
|
}
|
||||||
.global-banner-warn { background: #fff6e0; color: #5a3a00; border-color: #b08a4a; }
|
.global-banner-warn { background: #fff6e0; color: #5a3a00; border-color: #b08a4a; }
|
||||||
[data-theme="dark"] .global-banner-warn { background: #3a2a0a; color: #ffd690; border-color: #6b4a14; }
|
[data-theme="dark"] .global-banner-warn { background: #3a2a0a; color: #ffd690; border-color: #6b4a14; }
|
||||||
|
|
||||||
|
/* Wizard per-phase preview: graph + form */
|
||||||
|
.wizard-preview-graph {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0;
|
||||||
|
}
|
||||||
|
.wizard-graph-node {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
position: relative;
|
||||||
|
padding-bottom: 4px;
|
||||||
|
}
|
||||||
|
.wizard-graph-bubble {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 22px 1fr;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 8px 10px;
|
||||||
|
border: 1px solid color-mix(in srgb, var(--bp-navy) 25%, transparent);
|
||||||
|
background: var(--bp-paper);
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
.wizard-graph-bubble.kind-agent { background: color-mix(in srgb, var(--bp-amber) 18%, var(--bp-paper)); border-color: color-mix(in srgb, var(--bp-amber) 60%, var(--bp-navy)); }
|
||||||
|
.wizard-graph-bubble.kind-system { background: color-mix(in srgb, var(--bp-navy) 10%, var(--bp-paper)); }
|
||||||
|
.wizard-graph-idx { font-family: var(--bp-mono); font-size: 10px; color: var(--bp-muted); text-align: right; }
|
||||||
|
.wizard-graph-name { color: var(--bp-navy); font-weight: 600; line-height: 1.3; }
|
||||||
|
.wizard-graph-meta {
|
||||||
|
font-family: var(--bp-mono);
|
||||||
|
font-size: 9.5px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
color: var(--bp-muted);
|
||||||
|
padding: 2px 0 0 30px;
|
||||||
|
}
|
||||||
|
.wizard-graph-edge {
|
||||||
|
width: 1px;
|
||||||
|
height: 12px;
|
||||||
|
background: color-mix(in srgb, var(--bp-navy) 35%, transparent);
|
||||||
|
margin: 4px 0 4px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wizard-form-preview {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 10px;
|
||||||
|
border: 1px dashed color-mix(in srgb, var(--bp-navy) 25%, transparent);
|
||||||
|
background: color-mix(in srgb, var(--bp-paper) 70%, var(--bp-canvas));
|
||||||
|
}
|
||||||
|
.wizard-form-field { display: flex; flex-direction: column; gap: 3px; }
|
||||||
|
.wizard-form-label {
|
||||||
|
font-family: var(--bp-mono);
|
||||||
|
font-size: 10px;
|
||||||
|
letter-spacing: 0.06em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--bp-muted);
|
||||||
|
}
|
||||||
|
.wizard-form-input {
|
||||||
|
padding: 6px 8px;
|
||||||
|
font-size: 12px;
|
||||||
|
border: 1px solid color-mix(in srgb, var(--bp-navy) 25%, transparent);
|
||||||
|
background: var(--bp-paper);
|
||||||
|
color: var(--bp-navy);
|
||||||
|
font-family: inherit;
|
||||||
|
}
|
||||||
|
.wizard-form-input:disabled { opacity: 0.7; }
|
||||||
|
|||||||
+60
-44
@@ -263,59 +263,75 @@ export default function Wizard() {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const renderGraphPreview = () => (
|
||||||
|
<div className="wizard-preview-section">
|
||||||
|
<div className="wizard-preview-label">Flow · {draft.nodes.length} step{draft.nodes.length === 1 ? "" : "s"}</div>
|
||||||
|
<div className="wizard-preview-graph">
|
||||||
|
{draft.nodes.map((n, i) => (
|
||||||
|
<div key={n._key || i} className="wizard-graph-node">
|
||||||
|
<div className={`wizard-graph-bubble kind-${n.dispatch_kind || "human"}`}>
|
||||||
|
<span className="wizard-graph-idx">{i + 1}</span>
|
||||||
|
<span className="wizard-graph-name">{n.display_name || "Untitled"}</span>
|
||||||
|
</div>
|
||||||
|
<div className="wizard-graph-meta">
|
||||||
|
{({ human: "Manual", agent: "Assistant", system: "System" } as Record<string, string>)[n.dispatch_kind || "human"]}
|
||||||
|
</div>
|
||||||
|
{i < draft.nodes.length - 1 && <div className="wizard-graph-edge" aria-hidden />}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
const renderFormPreview = () => (
|
||||||
|
<div className="wizard-preview-section">
|
||||||
|
<div className="wizard-preview-label">Form preview · what users will fill in</div>
|
||||||
|
<div className="wizard-form-preview">
|
||||||
|
{draft.fields.filter((f) => f.name).length === 0 ? (
|
||||||
|
<div className="wizard-preview-empty">Add fields on the left to see the form here.</div>
|
||||||
|
) : (
|
||||||
|
draft.fields.filter((f) => f.name).map((f, i) => (
|
||||||
|
<div key={i} className="wizard-form-field">
|
||||||
|
<label className="wizard-form-label">{f.name}</label>
|
||||||
|
{f.type === "boolean" ? (
|
||||||
|
<select className="wizard-form-input" disabled defaultValue=""><option value="">Yes / No</option></select>
|
||||||
|
) : f.type === "number" ? (
|
||||||
|
<input className="wizard-form-input" type="number" placeholder="0" disabled />
|
||||||
|
) : (
|
||||||
|
<input className="wizard-form-input" type="text" placeholder={`Enter ${f.name.toLowerCase()}`} disabled />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
const renderRulesPreview = () => (
|
||||||
|
<div className="wizard-preview-section">
|
||||||
|
<div className="wizard-preview-label">Rules · {draft.rules.filter((r) => r.trim()).length}</div>
|
||||||
|
<ul className="wizard-preview-rules">
|
||||||
|
{draft.rules.filter((r) => r.trim()).map((r, i) => <li key={i}>{r}</li>)}
|
||||||
|
{draft.rules.filter((r) => r.trim()).length === 0 && (
|
||||||
|
<li className="wizard-preview-empty">No rules typed yet</li>
|
||||||
|
)}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
const renderPreviewPane = () => {
|
const renderPreviewPane = () => {
|
||||||
if (draft.nodes.length === 0 && draft.fields.length === 0 && draft.rules.length === 0) return null;
|
if (draft.nodes.length === 0 && draft.fields.length === 0 && draft.rules.length === 0) return null;
|
||||||
return (
|
return (
|
||||||
<aside className="wizard-preview">
|
<aside className="wizard-preview">
|
||||||
<div className="wizard-preview-head">
|
<div className="wizard-preview-head">
|
||||||
<span className="mc-hero-eyebrow">Preview</span>
|
<span className="mc-hero-eyebrow">Live preview</span>
|
||||||
<div className="wizard-preview-title">{draft.name || "Untitled process"}</div>
|
<div className="wizard-preview-title">{draft.name || "Untitled process"}</div>
|
||||||
{draft.description && <div className="wizard-preview-desc">{draft.description.slice(0, 140)}</div>}
|
{draft.description && <div className="wizard-preview-desc">{draft.description.slice(0, 140)}</div>}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{draft.nodes.length > 0 && (
|
{(draft.step === "Analyze" || draft.step === "Generate" || draft.step === "Validate" || draft.step === "Draft saved") && draft.nodes.length > 0 && renderGraphPreview()}
|
||||||
<div className="wizard-preview-section">
|
{(draft.step === "Generate" || draft.step === "Validate" || draft.step === "Draft saved") && renderFormPreview()}
|
||||||
<div className="wizard-preview-label">Steps · {draft.nodes.length}</div>
|
{(draft.step === "Validate" || draft.step === "Draft saved") && renderRulesPreview()}
|
||||||
<ol className="wizard-preview-chain">
|
|
||||||
{draft.nodes.map((n, i) => (
|
|
||||||
<li key={n._key || i} className="wizard-preview-chain-item">
|
|
||||||
<span className="wizard-preview-chain-idx">{i + 1}</span>
|
|
||||||
<span className="wizard-preview-chain-name">{n.display_name || "Untitled step"}</span>
|
|
||||||
<span className={`wizard-preview-chain-kind kind-${n.dispatch_kind || "human"}`}>{({ human: "Manual review", agent: "Assistant", system: "System" } as Record<string, string>)[n.dispatch_kind || "human"] || "Manual review"}</span>
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ol>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{draft.fields.length > 0 && (
|
|
||||||
<div className="wizard-preview-section">
|
|
||||||
<div className="wizard-preview-label">Form · {draft.fields.length} field{draft.fields.length === 1 ? "" : "s"}</div>
|
|
||||||
<ul className="wizard-preview-fields">
|
|
||||||
{draft.fields.filter((f) => f.name).map((f, i) => (
|
|
||||||
<li key={i}>
|
|
||||||
<span className="wizard-preview-field-name">{f.name}</span>
|
|
||||||
<span className="wizard-preview-field-type">· {f.type}</span>
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
{draft.fields.filter((f) => f.name).length === 0 && (
|
|
||||||
<li className="wizard-preview-empty">No fields named yet</li>
|
|
||||||
)}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{draft.rules.length > 0 && (
|
|
||||||
<div className="wizard-preview-section">
|
|
||||||
<div className="wizard-preview-label">Rules · {draft.rules.length}</div>
|
|
||||||
<ul className="wizard-preview-rules">
|
|
||||||
{draft.rules.filter((r) => r.trim()).map((r, i) => <li key={i}>{r}</li>)}
|
|
||||||
{draft.rules.filter((r) => r.trim()).length === 0 && (
|
|
||||||
<li className="wizard-preview-empty">No rules typed yet</li>
|
|
||||||
)}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</aside>
|
</aside>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user