| 1 |
import React, { useEffect, useRef } from 'react' |
| 2 |
import { __ } from '@wordpress/i18n' |
| 3 |
import { WithRestAPIContext } from '../../../hooks/useRestAPI' |
| 4 |
import { ScreenMetaSlot } from '../../common/ScreenMetaSlot' |
| 5 |
import { DemoCallout } from '../../common/demo/DemoCallout' |
| 6 |
import { DemoPageHeader } from '../../common/demo/DemoPageHeader' |
| 7 |
import { DemoSpotlight } from '../../common/demo/DemoSpotlight' |
| 8 |
import { useMarkDemoSeen } from '../../common/demo/useDemoSeen' |
| 9 |
import { getCallout } from './callouts' |
| 10 |
import { DemoPlanCard } from './DemoPlanCard' |
| 11 |
import { DemoPromptBox } from './DemoPromptBox' |
| 12 |
import { DemoResultCard } from './DemoResultCard' |
| 13 |
import { DemoSidebar } from './DemoSidebar' |
| 14 |
import { DemoMessage, DemoTransit, DemoTyping } from './DemoThread' |
| 15 |
import { AiAgentDemoUpsell } from './DemoUpsell' |
| 16 |
import { DEMO_EXAMPLES, DEMO_PROMPT, DEMO_REFINEMENT_REPLY } from './demoScript' |
| 17 |
import { useAiAgentDemo } from './useAiAgentDemo' |
| 18 |
import { hasReached } from './types' |
| 19 |
import type { DemoStage } from './types' |
| 20 |
|
| 21 |
/** |
| 22 |
* Only the steps that point at one particular thing are spotlit. Typing, |
| 23 |
* planning and building animate in place and need no dimming to be followed. |
| 24 |
*/ |
| 25 |
const STAGE_SPOTLIGHTS: Partial<Record<DemoStage, { target: string, padding?: number }>> = { |
| 26 |
'plan-ready': { target: '.ai-agent-plan', padding: 6 }, |
| 27 |
'plan-accepted': { target: '.ai-agent-plan', padding: 6 }, |
| 28 |
'result-ready': { target: '.ai-agent-result__rows', padding: 8 }, |
| 29 |
'applying': { target: '.ai-agent-result__edit', padding: 6 } |
| 30 |
} |
| 31 |
|
| 32 |
/** |
| 33 |
* Text announced to assistive technology as each stage begins, so the |
| 34 |
* walkthrough is followable without watching the animation. |
| 35 |
*/ |
| 36 |
const STAGE_ANNOUNCEMENTS: Partial<Record<DemoStage, string>> = { |
| 37 |
'typing-prompt': __('Writing a prompt.', 'code-snippets'), |
| 38 |
'prompt-sent': __('Prompt sent.', 'code-snippets'), |
| 39 |
'planning': __('Planning your snippets.', 'code-snippets'), |
| 40 |
'plan-ready': __('The plan is ready.', 'code-snippets'), |
| 41 |
'plan-accepted': __('Plan accepted.', 'code-snippets'), |
| 42 |
'building': __('Building the code.', 'code-snippets'), |
| 43 |
'result-ready': __('Snippets created.', 'code-snippets'), |
| 44 |
'refine-open': __('Choosing snippets to refine.', 'code-snippets'), |
| 45 |
'typing-refinement': __('Writing a refinement.', 'code-snippets'), |
| 46 |
'applying': __('Applying your changes.', 'code-snippets'), |
| 47 |
'saved': __('Snippets updated and saved to your site.', 'code-snippets'), |
| 48 |
'finished': __('Demo complete.', 'code-snippets') |
| 49 |
} |
| 50 |
|
| 51 |
// eslint-disable-next-line max-lines-per-function -- the timeline reads as one sequence. |
| 52 |
const AiAgentDemoPage: React.FC = () => { |
| 53 |
const { |
| 54 |
stage, |
| 55 |
typedPrompt, |
| 56 |
typedRefinement, |
| 57 |
snippets, |
| 58 |
hasStarted, |
| 59 |
isFinished, |
| 60 |
reducedMotion, |
| 61 |
play, |
| 62 |
skip, |
| 63 |
replay |
| 64 |
} = useAiAgentDemo() |
| 65 |
|
| 66 |
const markSeen = useMarkDemoSeen('ai-agent') |
| 67 |
const upsellRef = useRef<HTMLDivElement>(null) |
| 68 |
|
| 69 |
useEffect(() => { |
| 70 |
if (!isFinished) { |
| 71 |
return |
| 72 |
} |
| 73 |
|
| 74 |
markSeen() |
| 75 |
|
| 76 |
// The closing panel sits below the agent, so bring it into view rather |
| 77 |
// than leaving the walkthrough to end off screen. |
| 78 |
upsellRef.current?.scrollIntoView({ |
| 79 |
behavior: reducedMotion ? 'auto' : 'smooth', |
| 80 |
block: 'end' |
| 81 |
}) |
| 82 |
}, [isFinished, markSeen, reducedMotion]) |
| 83 |
|
| 84 |
const promptSent = hasReached(stage, 'prompt-sent') |
| 85 |
|
| 86 |
return ( |
| 87 |
<> |
| 88 |
<ScreenMetaSlot /> |
| 89 |
|
| 90 |
<DemoPageHeader |
| 91 |
title={__('AI Agent', 'code-snippets')} |
| 92 |
description={__('A guided walkthrough of the Pro AI Agent. Press play and watch it plan, build, and refine a snippet on your site.', 'code-snippets')} |
| 93 |
hasStarted={hasStarted} |
| 94 |
isFinished={isFinished} |
| 95 |
onPlay={play} |
| 96 |
onSkip={skip} |
| 97 |
onReplay={replay} |
| 98 |
/> |
| 99 |
|
| 100 |
<section className="ai-agent ai-agent-demo" aria-label={__('AI Agent demo', 'code-snippets')}> |
| 101 |
<div className="screen-reader-text" aria-live="polite">{STAGE_ANNOUNCEMENTS[stage]}</div> |
| 102 |
|
| 103 |
<DemoCallout callout={getCallout(stage)} /> |
| 104 |
|
| 105 |
<DemoSpotlight {...STAGE_SPOTLIGHTS[stage]} /> |
| 106 |
|
| 107 |
<div className="ai-agent-layout"> |
| 108 |
<div className="ai-agent-layout__main"> |
| 109 |
{!promptSent && <div className="ai-agent-empty"> |
| 110 |
<p className="ai-agent-empty__eyebrow">{__('Start with these prompts', 'code-snippets')}</p> |
| 111 |
|
| 112 |
<div className="ai-agent-empty__chips"> |
| 113 |
{DEMO_EXAMPLES.map(example => |
| 114 |
<button key={example} type="button" className="ai-agent-empty__chip" disabled> |
| 115 |
{example} |
| 116 |
</button>)} |
| 117 |
</div> |
| 118 |
</div>} |
| 119 |
|
| 120 |
<div className="ai-agent-thread"> |
| 121 |
{hasReached(stage, 'prompt-sent') && <DemoMessage speaker="user">{DEMO_PROMPT}</DemoMessage>} |
| 122 |
|
| 123 |
{'planning' === stage && <> |
| 124 |
<DemoTransit label={__('Sending your prompt to Code Snippets Cloud.', 'code-snippets')} /> |
| 125 |
<DemoTyping label={__('Planning your snippets…', 'code-snippets')} /> |
| 126 |
</>} |
| 127 |
|
| 128 |
{hasReached(stage, 'plan-ready') && !hasReached(stage, 'building') && |
| 129 |
<DemoPlanCard accepted={'plan-accepted' === stage} />} |
| 130 |
|
| 131 |
{'building' === stage && <> |
| 132 |
<DemoTransit label={__('Building the snippets in Code Snippets Cloud.', 'code-snippets')} /> |
| 133 |
<DemoTyping label={__('Building the code…', 'code-snippets')} /> |
| 134 |
</>} |
| 135 |
|
| 136 |
{hasReached(stage, 'result-ready') && <DemoResultCard |
| 137 |
stage={stage} |
| 138 |
snippets={snippets} |
| 139 |
typedRefinement={typedRefinement} |
| 140 |
/>} |
| 141 |
|
| 142 |
{'applying' === stage && <DemoTyping label={__('Applying your changes…', 'code-snippets')} />} |
| 143 |
|
| 144 |
{hasReached(stage, 'saved') && <DemoMessage speaker="assistant">{DEMO_REFINEMENT_REPLY}</DemoMessage>} |
| 145 |
</div> |
| 146 |
|
| 147 |
<DemoPromptBox |
| 148 |
value={hasReached(stage, 'planning') ? '' : typedPrompt} |
| 149 |
typing={'typing-prompt' === stage} |
| 150 |
pressed={'prompt-sent' === stage} |
| 151 |
disabled={promptSent} |
| 152 |
submitLabel={__('Send', 'code-snippets')} |
| 153 |
placeholder={__('Describe what you want to build…', 'code-snippets')} |
| 154 |
/> |
| 155 |
</div> |
| 156 |
|
| 157 |
<div className="ai-agent-layout__sidebar"> |
| 158 |
<DemoSidebar stage={stage} /> |
| 159 |
</div> |
| 160 |
</div> |
| 161 |
|
| 162 |
{isFinished && <div |
| 163 |
ref={upsellRef} |
| 164 |
className="ai-agent-demo__closing" |
| 165 |
role="region" |
| 166 |
aria-label={__('AI Agent demo complete', 'code-snippets')} |
| 167 |
> |
| 168 |
<AiAgentDemoUpsell onReplay={replay} /> |
| 169 |
</div>} |
| 170 |
</section> |
| 171 |
</> |
| 172 |
) |
| 173 |
} |
| 174 |
|
| 175 |
export const AiAgentDemo: React.FC = () => |
| 176 |
<WithRestAPIContext> |
| 177 |
<AiAgentDemoPage /> |
| 178 |
</WithRestAPIContext> |
| 179 |
|