Add files via upload

This commit is contained in:
公明
2026-07-16 13:42:50 +08:00
committed by GitHub
parent 3169e67ca0
commit c721346ee6
4 changed files with 296 additions and 25 deletions
@@ -85,12 +85,17 @@ async function fetchRoles(baseUrl, token, signal) {
signal,
});
const list = (data && data.roles) || [];
const out = [{ id: '', label: 'Default' }];
const out = [];
for (const r of list) {
if (r.enabled === false) continue;
if (!r.name) continue;
// Server default role is named "默认"; map to empty id + English label (UI is EN).
// Skip it here and prepend a single Default entry below — avoids Default + 默认.
if (r.name === '默认') continue;
out.push({ id: r.name, label: r.name });
}
out.sort((a, b) => a.label.localeCompare(b.label, undefined, { sensitivity: 'base' }));
out.unshift({ id: '', label: 'Default' });
return out;
}
@@ -816,6 +816,7 @@ body {
background: var(--csai-surface);
color: var(--csai-text);
box-shadow: var(--csai-shadow-md);
overflow: visible;
}
#send-dialog::backdrop {
@@ -823,7 +824,11 @@ body {
backdrop-filter: blur(2px);
}
#send-dialog form { padding: 20px; margin: 0; }
#send-dialog form {
padding: 20px;
margin: 0;
overflow: visible;
}
.dialog-header {
display: flex;
@@ -849,6 +854,143 @@ body {
grid-template-columns: 1fr 1fr 1fr;
gap: 12px;
margin-bottom: 14px;
position: relative;
z-index: 2;
}
#send-dialog .field-label {
font-size: 11px;
font-weight: 600;
text-transform: none;
letter-spacing: 0;
color: var(--csai-text-muted);
margin-bottom: 4px;
}
/* ── Custom select (Send dialog) ── */
.cs-select {
position: relative;
width: 100%;
min-width: 0;
}
.cs-select-trigger {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
width: 100%;
height: 36px;
padding: 0 10px 0 12px;
border: 1px solid var(--csai-border);
border-radius: var(--csai-radius-sm);
background: var(--csai-surface-2);
color: var(--csai-text);
font: inherit;
font-size: 12px;
font-weight: 500;
line-height: 1;
cursor: pointer;
transition: border-color 0.15s, box-shadow 0.15s, background 0.15s;
}
.cs-select-trigger:hover {
border-color: var(--csai-border-strong);
background: var(--csai-surface);
}
.cs-select.open .cs-select-trigger {
border-color: var(--csai-accent);
box-shadow: 0 0 0 3px var(--csai-accent-ring);
background: var(--csai-surface);
}
.cs-select-value {
flex: 1;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
text-align: left;
}
.cs-select-caret {
flex-shrink: 0;
color: var(--csai-text-faint);
transition: transform 0.15s ease;
}
.cs-select.open .cs-select-caret {
transform: rotate(180deg);
color: var(--csai-accent);
}
.cs-select-menu {
position: absolute;
z-index: 40;
top: calc(100% + 6px);
left: 0;
right: 0;
margin: 0;
padding: 4px;
list-style: none;
max-height: 240px;
overflow-x: hidden;
overflow-y: auto;
background: var(--csai-surface);
border: 1px solid var(--csai-border);
border-radius: var(--csai-radius);
box-shadow: var(--csai-shadow-md);
}
.cs-select-menu[hidden] {
display: none !important;
}
.cs-select-option {
display: flex;
align-items: center;
gap: 8px;
width: 100%;
padding: 8px 10px;
border-radius: var(--csai-radius-sm);
font-size: 12px;
font-weight: 500;
color: var(--csai-text);
cursor: pointer;
line-height: 1.35;
transition: background 0.1s, color 0.1s;
}
.cs-select-option:hover {
background: var(--csai-surface-2);
}
.cs-select-option.is-selected {
background: var(--csai-accent-soft);
color: var(--csai-accent);
font-weight: 600;
}
.cs-select-option-check {
flex: 0 0 12px;
width: 12px;
font-size: 10px;
line-height: 1;
opacity: 0;
color: var(--csai-accent);
}
.cs-select-option.is-selected .cs-select-option-check {
opacity: 1;
}
.cs-select-option-label {
flex: 1;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
#dlg-instruction {
@@ -117,9 +117,45 @@
<h3>Send to CyberStrikeAI</h3>
</div>
<div class="send-row">
<label class="field block">Project <select id="dlg-project"></select></label>
<label class="field block">Role <select id="dlg-role"></select></label>
<label class="field block">Agent <select id="dlg-agent"></select></label>
<div class="field block">
<span class="field-label">Project</span>
<div class="cs-select" data-cs-select="dlg-project">
<input type="hidden" id="dlg-project" value="">
<button type="button" class="cs-select-trigger" aria-haspopup="listbox" aria-expanded="false">
<span class="cs-select-value">Loading…</span>
<svg class="cs-select-caret" width="12" height="12" viewBox="0 0 24 24" fill="none" aria-hidden="true">
<path d="M6 9l6 6 6-6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</button>
<ul class="cs-select-menu" role="listbox" hidden></ul>
</div>
</div>
<div class="field block">
<span class="field-label">Role</span>
<div class="cs-select" data-cs-select="dlg-role">
<input type="hidden" id="dlg-role" value="">
<button type="button" class="cs-select-trigger" aria-haspopup="listbox" aria-expanded="false">
<span class="cs-select-value">Loading…</span>
<svg class="cs-select-caret" width="12" height="12" viewBox="0 0 24 24" fill="none" aria-hidden="true">
<path d="M6 9l6 6 6-6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</button>
<ul class="cs-select-menu" role="listbox" hidden></ul>
</div>
</div>
<div class="field block">
<span class="field-label">Agent</span>
<div class="cs-select" data-cs-select="dlg-agent">
<input type="hidden" id="dlg-agent" value="">
<button type="button" class="cs-select-trigger" aria-haspopup="listbox" aria-expanded="false">
<span class="cs-select-value">Eino Single (ADK)</span>
<svg class="cs-select-caret" width="12" height="12" viewBox="0 0 24 24" fill="none" aria-hidden="true">
<path d="M6 9l6 6 6-6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</button>
<ul class="cs-select-menu" role="listbox" hidden></ul>
</div>
</div>
</div>
<label class="field block">Test instruction (editable for this request)
<textarea id="dlg-instruction" rows="6"></textarea>
@@ -757,26 +757,111 @@ async function onValidate() {
}
function fillAgentSelect(sel, selected) {
sel.innerHTML = '';
for (const m of AGENT_MODES) {
const opt = document.createElement('option');
opt.value = m.id;
opt.textContent = m.label;
sel.appendChild(opt);
}
sel.value = selected || 'eino_single';
fillSelect(sel, AGENT_MODES.map((m) => ({ id: m.id, label: m.label })), selected || 'eino_single');
}
function csSelectWrap(input) {
return input && input.closest ? input.closest('.cs-select') : null;
}
function closeAllCsSelects(except) {
document.querySelectorAll('.cs-select.open').forEach((wrap) => {
if (except && wrap === except) return;
wrap.classList.remove('open');
const trigger = wrap.querySelector('.cs-select-trigger');
const menu = wrap.querySelector('.cs-select-menu');
if (trigger) trigger.setAttribute('aria-expanded', 'false');
if (menu) menu.hidden = true;
});
}
function fillSelect(sel, items, value) {
sel.innerHTML = '';
for (const item of items) {
const opt = document.createElement('option');
opt.value = item.id;
opt.textContent = item.label;
sel.appendChild(opt);
}
const wrap = csSelectWrap(sel);
const has = items.some((i) => i.id === value);
sel.value = has ? value : (items[0] && items[0].id) || '';
const resolved = has ? value : (items[0] && items[0].id) || '';
const selectedItem = items.find((i) => i.id === resolved) || items[0];
sel.value = resolved;
if (!wrap) {
// Fallback for plain <select> if any remain
sel.innerHTML = '';
for (const item of items) {
const opt = document.createElement('option');
opt.value = item.id;
opt.textContent = item.label;
sel.appendChild(opt);
}
sel.value = resolved;
return;
}
const valueEl = wrap.querySelector('.cs-select-value');
const menu = wrap.querySelector('.cs-select-menu');
if (valueEl) valueEl.textContent = (selectedItem && selectedItem.label) || '—';
if (!menu) return;
menu.innerHTML = '';
for (const item of items) {
const li = document.createElement('li');
li.setAttribute('role', 'option');
li.className = 'cs-select-option' + (item.id === resolved ? ' is-selected' : '');
li.setAttribute('data-value', item.id);
li.setAttribute('aria-selected', item.id === resolved ? 'true' : 'false');
li.innerHTML =
'<span class="cs-select-option-check" aria-hidden="true">✓</span>' +
'<span class="cs-select-option-label"></span>';
li.querySelector('.cs-select-option-label').textContent = item.label;
li.addEventListener('click', (ev) => {
ev.preventDefault();
ev.stopPropagation();
sel.value = item.id;
if (valueEl) valueEl.textContent = item.label;
menu.querySelectorAll('.cs-select-option').forEach((opt) => {
const on = (opt.getAttribute('data-value') || '') === item.id;
opt.classList.toggle('is-selected', on);
opt.setAttribute('aria-selected', on ? 'true' : 'false');
});
closeAllCsSelects();
});
menu.appendChild(li);
}
}
function setupCsSelects() {
document.querySelectorAll('.cs-select').forEach((wrap) => {
const trigger = wrap.querySelector('.cs-select-trigger');
const menu = wrap.querySelector('.cs-select-menu');
if (!trigger || !menu || trigger.dataset.csBound) return;
trigger.dataset.csBound = '1';
trigger.addEventListener('click', (ev) => {
ev.preventDefault();
ev.stopPropagation();
const willOpen = !wrap.classList.contains('open');
closeAllCsSelects();
if (willOpen) {
wrap.classList.add('open');
trigger.setAttribute('aria-expanded', 'true');
menu.hidden = false;
const selected = menu.querySelector('.cs-select-option.is-selected');
if (selected) selected.scrollIntoView({ block: 'nearest' });
}
});
});
document.addEventListener('click', (ev) => {
if (ev.target.closest && ev.target.closest('.cs-select')) return;
closeAllCsSelects();
});
document.addEventListener('keydown', (ev) => {
if (ev.key === 'Escape') closeAllCsSelects();
});
const dlg = $('send-dialog');
if (dlg) {
dlg.addEventListener('close', () => closeAllCsSelects());
}
}
async function openSendDialog(entryOverride) {
@@ -789,14 +874,15 @@ async function openSendDialog(entryOverride) {
$('dlg-instruction').value = config.lastInstruction || defaultInstruction();
fillAgentSelect($('dlg-agent'), config.lastAgentMode);
$('dlg-project').innerHTML = '<option>Loading…</option>';
$('dlg-role').innerHTML = '<option>Loading…</option>';
fillSelect($('dlg-project'), [{ id: '', label: 'Loading…' }], '');
fillSelect($('dlg-role'), [{ id: '', label: 'Loading…' }], '');
dlg.showModal();
try {
const { projects, roles } = await fetchCatalogCached(baseUrl, token);
fillSelect($('dlg-project'), projects, config.lastProjectId);
fillSelect($('dlg-role'), roles, config.lastRole);
const lastRole = config.lastRole === '默认' ? '' : config.lastRole;
fillSelect($('dlg-role'), roles, lastRole);
} catch (err) {
if (await handleAuthFailure(err, 'Token invalid or expired — please Validate again')) {
dlg.close();
@@ -832,7 +918,8 @@ async function onSendConfirmed() {
}
const modeLabel = (AGENT_MODES.find((m) => m.id === agentMode) || {}).label || agentMode;
const roleLabel = role || 'Default';
const roleValueEl = document.querySelector('[data-cs-select="dlg-role"] .cs-select-value');
const roleLabel = (roleValueEl && roleValueEl.textContent) || role || 'Default';
const run = createRun(e, modeLabel + ' · ' + roleLabel);
activeRunId = run.id;
prependRunItem(run);
@@ -1149,6 +1236,7 @@ $('send-form').addEventListener('submit', (ev) => {
$('dlg-cancel').addEventListener('click', () => $('send-dialog').close());
setupTabs();
setupCsSelects();
setupAuthProbeHooks();
initConfig().then(() => {
if (extensionAlive()) connectBackground();