site-builder: dynamic CTAs, section anchors, edit-with-Sitesmith
Three related features: 1. Dynamic CTA buttons on HeroSimple, CTASection, CallToAction. New shared ctas[] array (text + href + variant + target) replaces the primary/secondary pair. Settings panel gets add/remove/reorder controls. Legacy fields stay readable for backwards compat — first user edit migrates the section onto the new array. 2. Anchor IDs on all layout/section components (Container, Section, BackgroundSection, ColumnLayout, plus 6 section blocks done by parallel subagent, plus Hero/CTA/CallToAction). Anchor input lives in the settings panel with an "auto from heading" button that walks the subtree for the first Heading.text. Renders as id="..." on the outermost element so #anchor URLs resolve. 3. Edit-with-Sitesmith targeted invocation. Right-click → "Ask Sitesmith" and a button at the top of the right-side settings panel both open the modal pre-targeted at the selected node. The node's serialized subtree is sent to the server; system prompt is augmented to require a patch with replace_node. Editor lifts modal state into a new SitesmithContext. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -4,6 +4,7 @@ import { useEditorConfig } from '../../state/EditorConfigContext';
|
||||
import { useSitesmith } from '../../hooks/useSitesmith';
|
||||
import { useApplyAiResponse } from '../../utils/apply-ai-response';
|
||||
import { summarizeCanvas } from '../../utils/canvas-summary';
|
||||
import { SitesmithTarget } from '../../state/SitesmithContext';
|
||||
import { UpgradeBanner } from './UpgradeBanner';
|
||||
import { ScopeConfirmDialog } from './ScopeConfirmDialog';
|
||||
import { MessageList } from './MessageList';
|
||||
@@ -11,9 +12,14 @@ import { ChatInput } from './ChatInput';
|
||||
import { WorkingIndicator } from './WorkingIndicator';
|
||||
import { SitesmithResponse } from '../../types/sitesmith';
|
||||
|
||||
interface Props { onClose: () => void; }
|
||||
interface Props {
|
||||
onClose: () => void;
|
||||
/** When set, the chat is biased toward editing this specific node and the AI is
|
||||
* instructed to return a `patch` op. The node's serialized tree is sent along. */
|
||||
target?: SitesmithTarget | null;
|
||||
}
|
||||
|
||||
export const SitesmithModal: React.FC<Props> = ({ onClose }) => {
|
||||
export const SitesmithModal: React.FC<Props> = ({ onClose, target }) => {
|
||||
const cfg = useEditorConfig();
|
||||
const siteId = cfg.whpConfig?.siteId ?? 0;
|
||||
const { query } = useEditor();
|
||||
@@ -38,13 +44,17 @@ export const SitesmithModal: React.FC<Props> = ({ onClose }) => {
|
||||
setBusy(true); setError(null);
|
||||
try {
|
||||
const canvas = summarizeCanvas(query.getSerializedNodes());
|
||||
const result = await send(text, canvas);
|
||||
const result = await send(text, canvas, target ? {
|
||||
node_id: target.nodeId,
|
||||
display_name: target.displayName,
|
||||
tree_json: target.treeJson,
|
||||
} : undefined);
|
||||
if (!result.ok) { setError(result.message || 'Failed'); return; }
|
||||
if (result.response.type === 'replace' && result.response.scope === 'site') {
|
||||
setPendingReplace(result.response);
|
||||
return;
|
||||
}
|
||||
const applied = await apply(result.response);
|
||||
const applied = await apply(result.response, target?.nodeId);
|
||||
if (!applied.ok) setError(applied.message || 'Apply failed');
|
||||
} catch (e: any) { setError(String(e?.message ?? e)); }
|
||||
finally { setBusy(false); }
|
||||
@@ -86,6 +96,16 @@ export const SitesmithModal: React.FC<Props> = ({ onClose }) => {
|
||||
</div>
|
||||
<div style={body}>
|
||||
<UpgradeBanner summary={summary} />
|
||||
{target && (
|
||||
<div style={{
|
||||
background: 'rgba(59,130,246,0.12)', border: '1px solid rgba(59,130,246,0.4)',
|
||||
borderRadius: 6, padding: '8px 12px', marginBottom: 10, fontSize: 13, color: '#bfdbfe',
|
||||
display: 'flex', alignItems: 'center', gap: 8,
|
||||
}}>
|
||||
<i className="fa fa-magic" style={{ color: '#60a5fa' }} />
|
||||
<span>Editing <strong style={{ color: '#fff' }}>{target.displayName}</strong> — describe the change you want and Sitesmith will modify just this block.</span>
|
||||
</div>
|
||||
)}
|
||||
{error && <div role="alert" style={errBox}>{error}</div>}
|
||||
{loading
|
||||
? <div style={{ color: '#71717a', textAlign: 'center', padding: 30 }}>Loading…</div>
|
||||
|
||||
Reference in New Issue
Block a user