const FEATURE_DEFAULTS = { image_generation: false, code_interpreter: false, web_search: false, memory: true, }; const FEATURE_METADATA = { image_generation: { label: 'Image Generation', description: 'Request image outputs from supported models. When enabled the assistant may produce images alongside text responses.', }, code_interpreter: { label: 'Code Interpreter', description: 'Grant access to the sandboxed runtime for running Python snippets and data transformations during the task.', }, web_search: { label: 'Web Search', description: 'Allow the assistant to perform outbound web searches to enrich the response with current information.', }, memory: { label: 'Memory', description: 'Persist relevant conversation context for future automations so runs can recall prior outcomes.', }, }; const FEATURE_SECTION_TAG = 'Feature'; function renderFeatureList(featureState) { const container = document.querySelector('#features-select'); const hiddenInput = document.querySelector('#features-select-input'); if (!container || !hiddenInput) return; const baseState = normalizeFeatureState(featureState), featureKeys = Object.keys(baseState); container.innerHTML = ''; container.setAttribute('data-has-features', String(featureKeys.length > 0)); const selection = { ...baseState }, syncHidden = () => { hiddenInput.value = JSON.stringify(selection); hiddenInput.dispatchEvent(new Event('input', { bubbles: true })); hiddenInput.dispatchEvent(new Event('change', { bubbles: true })); }; featureKeys.forEach((id) => { const meta = getFeatureMeta(id); const pill = document.createElement('div'); pill.className = 'feature-pill'; pill.dataset.featureId = id; pill.setAttribute('role', 'option'); pill.setAttribute('tabindex', '-1'); const toggleBtn = document.createElement('button'); toggleBtn.type = 'button'; toggleBtn.className = 'feature-pill-toggle'; toggleBtn.textContent = meta.label; toggleBtn.setAttribute('aria-pressed', String(Boolean(selection[id]))); toggleBtn.addEventListener('click', () => { selection[id] = !selection[id]; const isSelected = Boolean(selection[id]); pill.classList.toggle('feature-pill--selected', isSelected); pill.setAttribute('aria-selected', String(isSelected)); toggleBtn.setAttribute('aria-pressed', String(isSelected)); syncHidden(); }); const infoBtn = document.createElement('button'); infoBtn.type = 'button'; infoBtn.className = 'feature-pill-info'; infoBtn.setAttribute('aria-label', `Show info for ${meta.label}`); infoBtn.innerHTML = ''; infoBtn.addEventListener('click', (event) => { event.stopPropagation(); generateFeatureCard({ id, ...meta, selected: Boolean(selection[id]) }, { trigger: infoBtn }); }); const isSelected = Boolean(selection[id]); pill.classList.toggle('feature-pill--selected', isSelected); pill.setAttribute('aria-selected', String(isSelected)); pill.appendChild(toggleBtn); pill.appendChild(infoBtn); container.appendChild(pill); }); syncHidden(); } function normalizeFeatureState(featureState) { const normalized = { ...FEATURE_DEFAULTS }; if (featureState && typeof featureState === 'object' && !Array.isArray(featureState)) { for (const [key, value] of Object.entries(featureState)) { if (Object.prototype.hasOwnProperty.call(FEATURE_DEFAULTS, key) || Object.prototype.hasOwnProperty.call(FEATURE_METADATA, key)) { normalized[key] = Boolean(value); } } } return normalized; } function getFeatureMeta(id) { if (Object.prototype.hasOwnProperty.call(FEATURE_METADATA, id)) { return FEATURE_METADATA[id]; } const label = id.replace(/_/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase()); return { label, description: 'Toggle this capability for the scheduled run.', }; } function generateFeatureCard(feature, options = {}) { const mount = options.mountSelector ? document.querySelector(options.mountSelector) : document.body; if (!mount) throw new Error('mount element not found'); const overlay = document.createElement('div'); overlay.className = 'fc-overlay'; overlay.setAttribute('data-testid', 'feature-card-overlay'); const dialog = document.createElement('div'); dialog.className = 'fc-dialog'; dialog.setAttribute('role', 'dialog'); dialog.setAttribute('aria-modal', 'true'); const titleId = `fc-title-${Math.random().toString(36).slice(2)}`; const descId = `fc-desc-${Math.random().toString(36).slice(2)}`; dialog.setAttribute('aria-labelledby', titleId); dialog.setAttribute('aria-describedby', descId); const header = document.createElement('header'); header.className = 'fc-header'; const titleWrap = document.createElement('div'); titleWrap.className = 'fc-titlewrap'; const h1 = document.createElement('h1'); h1.id = titleId; h1.className = 'fc-title'; h1.textContent = feature.label; const tag = document.createElement('span'); tag.className = 'fc-tag'; tag.textContent = FEATURE_SECTION_TAG; tag.setAttribute('data-testid', 'feature-tag'); titleWrap.appendChild(h1); titleWrap.appendChild(tag); const closeBtn = document.createElement('button'); closeBtn.className = 'fc-close'; closeBtn.type = 'button'; closeBtn.setAttribute('aria-label', 'close'); closeBtn.textContent = '×'; header.appendChild(titleWrap); header.appendChild(closeBtn); const body = document.createElement('div'); body.className = 'fc-body'; const desc = document.createElement('p'); desc.id = descId; desc.className = 'fc-desc'; desc.textContent = feature.description; const details = document.createElement('dl'); details.className = 'fc-details'; const pairs = [ ['Identifier', feature.id], ['Current state', feature.selected ? 'Enabled' : 'Disabled'], ]; pairs.forEach(([label, value]) => { const dt = document.createElement('dt'); dt.textContent = label; const dd = document.createElement('dd'); dd.textContent = value; details.appendChild(dt); details.appendChild(dd); }); body.appendChild(desc); body.appendChild(details); dialog.appendChild(header); dialog.appendChild(body); overlay.appendChild(dialog); mount.appendChild(overlay); const opener = options.trigger instanceof HTMLElement ? options.trigger : document.activeElement; const focusableSelectors = [ 'a[href]', 'button:not([disabled])', 'input:not([disabled])', 'select:not([disabled])', 'textarea:not([disabled])', '[tabindex]:not([tabindex="-1"])' ].join(','); const getFocusable = () => /** @type {HTMLElement[]} */(Array.from(dialog.querySelectorAll(focusableSelectors))); const focusFirst = () => { const items = getFocusable(); (items[0] || closeBtn).focus(); }; const onKeydown = (event) => { if (event.key === 'Tab') { const items = getFocusable(); if (items.length === 0) { event.preventDefault(); closeBtn.focus(); return; } const first = items[0]; const last = items[items.length - 1]; const active = /** @type {HTMLElement} */(document.activeElement); if (!event.shiftKey && active === last) { event.preventDefault(); first.focus(); } else if (event.shiftKey && active === first) { event.preventDefault(); last.focus(); } } if (event.key === 'Escape') { event.preventDefault(); close(); } }; const onOverlayClick = (event) => { if (event.target === overlay) close(); }; overlay.addEventListener('click', onOverlayClick); document.addEventListener('keydown', onKeydown); closeBtn.addEventListener('click', () => close()); const prevOverflow = document.documentElement.style.overflow; document.documentElement.style.overflow = 'hidden'; setTimeout(focusFirst, 0); function close() { overlay.removeEventListener('click', onOverlayClick); document.removeEventListener('keydown', onKeydown); document.documentElement.style.overflow = prevOverflow; overlay.remove(); if (opener && typeof opener.focus === 'function') opener.focus(); } return { close, root: overlay }; }