| 1 |
import React, { useState } from 'react' |
| 2 |
import { __, sprintf } from '@wordpress/i18n' |
| 3 |
import classnames from 'classnames' |
| 4 |
import { DEMO_EXAMPLES } from './demoScript' |
| 5 |
import { hasReached } from './types' |
| 6 |
import type { DemoStage } from './types' |
| 7 |
|
| 8 |
type DemoSidebarTab = 'actions' | 'history' |
| 9 |
|
| 10 |
interface DemoQuotaWindow { |
| 11 |
label: string |
| 12 |
used: number |
| 13 |
limit: number |
| 14 |
} |
| 15 |
|
| 16 |
/** Sample usage, so the meters the real agent shows have something to draw. */ |
| 17 |
const DEMO_QUOTA: readonly DemoQuotaWindow[] = [ |
| 18 |
{ label: __('Today', 'code-snippets'), used: 2, limit: 25 }, |
| 19 |
{ label: __('This month', 'code-snippets'), used: 34, limit: 300 } |
| 20 |
] |
| 21 |
|
| 22 |
const FULL_PERCENT = 100 |
| 23 |
|
| 24 |
/** Conversations the scripted library already holds when the demo opens. */ |
| 25 |
const PAST_CONVERSATIONS: readonly string[] = [ |
| 26 |
__('Hide the admin bar for subscribers', 'code-snippets'), |
| 27 |
__('Custom login screen logo', 'code-snippets') |
| 28 |
] |
| 29 |
|
| 30 |
interface DemoStatus { |
| 31 |
label: string |
| 32 |
modifier?: string |
| 33 |
} |
| 34 |
|
| 35 |
/** |
| 36 |
* How the conversation the walkthrough is having would be labelled in the |
| 37 |
* history rail at each point of the script. |
| 38 |
*/ |
| 39 |
const currentStatus = (stage: DemoStage): DemoStatus => { |
| 40 |
if ('applying' === stage) { |
| 41 |
return { label: __('Updating', 'code-snippets') } |
| 42 |
} |
| 43 |
|
| 44 |
if (hasReached(stage, 'result-ready')) { |
| 45 |
return { label: __('Completed', 'code-snippets'), modifier: 'is-done' } |
| 46 |
} |
| 47 |
|
| 48 |
if (hasReached(stage, 'building')) { |
| 49 |
return { label: __('Building', 'code-snippets') } |
| 50 |
} |
| 51 |
|
| 52 |
if (hasReached(stage, 'plan-ready')) { |
| 53 |
return { label: __('Plan ready', 'code-snippets'), modifier: 'is-plan' } |
| 54 |
} |
| 55 |
|
| 56 |
return { label: __('Planning', 'code-snippets') } |
| 57 |
} |
| 58 |
|
| 59 |
const DemoQuota: React.FC = () => |
| 60 |
<div className="ai-agent-quota"> |
| 61 |
<h2 className="ai-agent-sidebar__heading">{__('AI usage', 'code-snippets')}</h2> |
| 62 |
|
| 63 |
{DEMO_QUOTA.map(({ label, used, limit }) => |
| 64 |
<div key={label} className="ai-agent-quota__row"> |
| 65 |
<div className="ai-agent-quota__label"> |
| 66 |
<span>{label}</span> |
| 67 |
<span>{sprintf( |
| 68 |
/* translators: 1: used count, 2: limit count. */ |
| 69 |
__('%1$d / %2$d', 'code-snippets'), |
| 70 |
used, |
| 71 |
limit |
| 72 |
)}</span> |
| 73 |
</div> |
| 74 |
|
| 75 |
<div className="ai-agent-quota__track is-ok"> |
| 76 |
<div |
| 77 |
className="ai-agent-quota__fill" |
| 78 |
style={{ inlineSize: `${FULL_PERCENT * used / limit}%` }} |
| 79 |
/> |
| 80 |
</div> |
| 81 |
</div>)} |
| 82 |
</div> |
| 83 |
|
| 84 |
const DemoHistory: React.FC<{ stage: DemoStage }> = ({ stage }) => { |
| 85 |
const status = currentStatus(stage) |
| 86 |
|
| 87 |
return ( |
| 88 |
<div className="ai-agent-history"> |
| 89 |
<ul className="ai-agent-history__list"> |
| 90 |
{hasReached(stage, 'prompt-sent') && |
| 91 |
<li className="ai-agent-history__item is-active"> |
| 92 |
<span className="ai-agent-history__open"> |
| 93 |
<span className="ai-agent-history__title"> |
| 94 |
{__('Dismissible welcome banner', 'code-snippets')} |
| 95 |
</span> |
| 96 |
<span className={classnames('ai-agent-history__status', status.modifier)}> |
| 97 |
{status.label} |
| 98 |
</span> |
| 99 |
</span> |
| 100 |
</li>} |
| 101 |
|
| 102 |
{PAST_CONVERSATIONS.map(title => |
| 103 |
<li key={title} className="ai-agent-history__item"> |
| 104 |
<span className="ai-agent-history__open"> |
| 105 |
<span className="ai-agent-history__title">{title}</span> |
| 106 |
<span className="ai-agent-history__status is-done"> |
| 107 |
{__('Completed', 'code-snippets')} |
| 108 |
</span> |
| 109 |
</span> |
| 110 |
</li>)} |
| 111 |
</ul> |
| 112 |
</div> |
| 113 |
) |
| 114 |
} |
| 115 |
|
| 116 |
interface DemoTabProps { |
| 117 |
tab: DemoSidebarTab |
| 118 |
current: DemoSidebarTab |
| 119 |
label: string |
| 120 |
onSelect: (tab: DemoSidebarTab) => void |
| 121 |
} |
| 122 |
|
| 123 |
const DemoTab: React.FC<DemoTabProps> = ({ tab, current, label, onSelect }) => |
| 124 |
<button |
| 125 |
type="button" |
| 126 |
role="tab" |
| 127 |
id={`ai-agent-demo-tab-${tab}`} |
| 128 |
aria-selected={tab === current} |
| 129 |
aria-controls={`ai-agent-demo-panel-${tab}`} |
| 130 |
className={classnames('ai-agent-sidebar__tab', { 'is-active': tab === current })} |
| 131 |
onClick={() => onSelect(tab)} |
| 132 |
> |
| 133 |
{label} |
| 134 |
</button> |
| 135 |
|
| 136 |
/** |
| 137 |
* The sidebar the real agent uses, rendered inert. Only the tabs respond: the |
| 138 |
* prompt chips, usage meters and conversation list are here for the shape of |
| 139 |
* the page, not to be operated. |
| 140 |
*/ |
| 141 |
export const DemoSidebar: React.FC<{ stage: DemoStage }> = ({ stage }) => { |
| 142 |
const [tab, setTab] = useState<DemoSidebarTab>('actions') |
| 143 |
|
| 144 |
return ( |
| 145 |
<aside className="ai-agent-sidebar" aria-label={__('AI Agent tools', 'code-snippets')}> |
| 146 |
<div className="ai-agent-sidebar__tabs" role="tablist"> |
| 147 |
<DemoTab |
| 148 |
tab="actions" |
| 149 |
current={tab} |
| 150 |
label={__('Prompt Actions', 'code-snippets')} |
| 151 |
onSelect={setTab} |
| 152 |
/> |
| 153 |
|
| 154 |
<DemoTab |
| 155 |
tab="history" |
| 156 |
current={tab} |
| 157 |
label={__('Past Conversations', 'code-snippets')} |
| 158 |
onSelect={setTab} |
| 159 |
/> |
| 160 |
</div> |
| 161 |
|
| 162 |
<div |
| 163 |
className="ai-agent-sidebar__panel" |
| 164 |
role="tabpanel" |
| 165 |
id="ai-agent-demo-panel-actions" |
| 166 |
aria-labelledby="ai-agent-demo-tab-actions" |
| 167 |
hidden={'actions' !== tab} |
| 168 |
> |
| 169 |
<div className="ai-agent-examples"> |
| 170 |
<h2 className="ai-agent-sidebar__heading">{__('Prompt examples', 'code-snippets')}</h2> |
| 171 |
|
| 172 |
{DEMO_EXAMPLES.map(example => |
| 173 |
<button key={example} type="button" className="ai-agent-examples__chip" disabled> |
| 174 |
{example} |
| 175 |
</button>)} |
| 176 |
</div> |
| 177 |
|
| 178 |
<DemoQuota /> |
| 179 |
</div> |
| 180 |
|
| 181 |
<div |
| 182 |
className="ai-agent-sidebar__panel" |
| 183 |
role="tabpanel" |
| 184 |
id="ai-agent-demo-panel-history" |
| 185 |
aria-labelledby="ai-agent-demo-tab-history" |
| 186 |
hidden={'history' !== tab} |
| 187 |
> |
| 188 |
<DemoHistory stage={stage} /> |
| 189 |
</div> |
| 190 |
</aside> |
| 191 |
) |
| 192 |
} |
| 193 |
|