| 1 | --- |
| 2 | import { Code } from '@astrojs/starlight/components'; |
| 3 | import { AGENTS, AGENTS_WITH_TOKEN } from '../data/agents'; |
| 4 | interface Props { variant?: 'browser' | 'token'; } |
| 5 | const { variant = 'browser' } = Astro.props; |
| 6 | const agents = variant === 'token' ? AGENTS_WITH_TOKEN : AGENTS; |
| 7 | const base = `agent-${crypto.randomUUID()}`; |
| 8 | const parts = (text: string) => text.split('`').map((part, index) => ({ part, code: index % 2 === 1 })); |
| 9 | --- |
| 10 | <g1t-agent-setup> |
| 11 | <div role="tablist" aria-label="AI client"> |
| 12 | {agents.map((agent, index) => <button type="button" role="tab" id={`${base}-${index}`} aria-controls={`${base}-panel-${index}`} aria-selected={index === 0 ? 'true' : 'false'} tabindex={index === 0 ? 0 : -1}>{agent.label}</button>)} |
| 13 | </div> |
| 14 | {agents.map((agent, index) => <section role="tabpanel" id={`${base}-panel-${index}`} aria-labelledby={`${base}-${index}`} hidden={index !== 0}> |
| 15 | <Code code={agent.code} lang={agent.lang} title={agent.file ?? undefined} frame={agent.file ? 'code' : 'none'} /> |
| 16 | {agent.then && <p>{parts(agent.then).map(({ part, code }) => code ? <code>{part}</code> : part)}</p>} |
| 17 | </section>)} |
| 18 | </g1t-agent-setup> |
| 19 | <style> |
| 20 | g1t-agent-setup { display: block; } |
| 21 | [role=tablist] { display: flex; flex-wrap: wrap; border-bottom: 2px solid var(--sl-color-gray-5); } |
| 22 | [role=tab] { background: transparent; border: 0; color: var(--sl-color-gray-2); padding: .6rem .8rem; font: inherit; font-size: .875rem; cursor: pointer; } |
| 23 | [role=tab][aria-selected=true] { color: var(--sl-color-white); box-shadow: 0 2px var(--sl-color-text-accent); font-weight: 600; } |
| 24 | [role=tabpanel] { margin-top: 1rem; } |
| 25 | [hidden] { display: none; } |
| 26 | </style> |
| 27 | <script> |
| 28 | import { AGENTS, AGENT_IDS, DEFAULT_AGENT, cookieAgent, getAgentChoice, setAgentChoice, subscribeAgentChoice } from '@g1t/contracts/agent-setup'; |
| 29 | class AgentSetup extends HTMLElement { |
| 30 | stop?: () => void; |
| 31 | connectedCallback() { |
| 32 | try { |
| 33 | // Migrate the docs' previous Starlight choice once, without overriding app choices. |
| 34 | if (!cookieAgent('g1t_agent') && !localStorage.getItem('g1t:agent')) { |
| 35 | const label = localStorage.getItem('starlight-synced-tabs__agent'); |
| 36 | const old = AGENT_IDS.find(id => AGENTS[id].label === label); |
| 37 | if (old) setAgentChoice(old); |
| 38 | } |
| 39 | } catch { /* Tabs still work with storage blocked. */ } |
| 40 | const tabs = [...this.querySelectorAll<HTMLButtonElement>('[role=tab]')]; |
| 41 | const panels = [...this.querySelectorAll<HTMLElement>('[role=tabpanel]')]; |
| 42 | const ids = tabs.map(tab => AGENT_IDS.find(id => AGENTS[id].label === tab.textContent?.trim())!); |
| 43 | const update = () => { |
| 44 | const selected = getAgentChoice(cookieAgent('g1t_agent_default') ?? DEFAULT_AGENT); |
| 45 | const index = Math.max(0, ids.indexOf(selected)); |
| 46 | tabs.forEach((tab, i) => { tab.setAttribute('aria-selected', String(i === index)); tab.tabIndex = i === index ? 0 : -1; panels[i].hidden = i !== index; }); |
| 47 | }; |
| 48 | tabs.forEach((tab, index) => { |
| 49 | tab.onclick = () => setAgentChoice(ids[index]); |
| 50 | tab.onkeydown = event => { |
| 51 | const next = event.key === 'ArrowRight' ? (index + 1) % tabs.length : event.key === 'ArrowLeft' ? (index + tabs.length - 1) % tabs.length : event.key === 'Home' ? 0 : event.key === 'End' ? tabs.length - 1 : null; |
| 52 | if (next === null) return; |
| 53 | event.preventDefault(); setAgentChoice(ids[next]); tabs[next].focus(); |
| 54 | }; |
| 55 | }); |
| 56 | this.stop = subscribeAgentChoice(update); |
| 57 | update(); |
| 58 | } |
| 59 | disconnectedCallback() { this.stop?.(); } |
| 60 | } |
| 61 | if (!customElements.get('g1t-agent-setup')) customElements.define('g1t-agent-setup', AgentSetup); |
| 62 | </script> |