added scheduler
This commit is contained in:
@@ -0,0 +1,258 @@
|
||||
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 = '<span aria-hidden="true">i</span>';
|
||||
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 };
|
||||
}
|
||||
Reference in New Issue
Block a user