release: v2.0.4 - Advanced Business Orchestration & UI Polishing
This commit is contained in:
+60
-1
@@ -390,8 +390,67 @@
|
||||
.company-agent-model {
|
||||
background: var(--input-bg); border: 1px solid var(--border);
|
||||
color: var(--text-primary); font-size: 10px;
|
||||
padding: 3px 6px; border-radius: 6px; max-width: 130px;
|
||||
padding: 3px 6px; border-radius: 6px;
|
||||
max-width: 150px; min-width: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
.company-agent-model option { color: var(--text-primary); background: var(--bg); }
|
||||
.company-agent-edit {
|
||||
background: transparent; border: 1px solid var(--border);
|
||||
color: var(--text-dim); font-size: 10px;
|
||||
padding: 3px 6px; border-radius: 6px; cursor: pointer;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.company-agent-edit:hover { color: var(--accent); border-color: var(--accent); }
|
||||
.company-agent-edit.dirty {
|
||||
color: var(--accent); border-color: var(--accent);
|
||||
background: var(--accent-glow);
|
||||
}
|
||||
|
||||
/* Expandable prompt editor under each agent card. Toggled via the
|
||||
Edit button. Three textareas (tagline / specialty / persona) +
|
||||
Reset / Save / Cancel — empty save clears that field's override. */
|
||||
.company-agent-editor {
|
||||
display: none;
|
||||
margin: 6px 0 0 38px; /* indent under the emoji */
|
||||
padding: 8px;
|
||||
background: var(--bg-secondary);
|
||||
border: 1px dashed var(--border);
|
||||
border-radius: 6px;
|
||||
}
|
||||
.company-agent-card[data-expanded="true"] .company-agent-editor { display: block; }
|
||||
.company-agent-editor .field-label {
|
||||
display: flex; justify-content: space-between; align-items: center;
|
||||
margin-top: 6px; font-size: 10px; color: var(--text-dim);
|
||||
text-transform: uppercase; letter-spacing: 0.04em;
|
||||
}
|
||||
.company-agent-editor .field-label:first-child { margin-top: 0; }
|
||||
.company-agent-editor .field-label .field-flag {
|
||||
text-transform: none; letter-spacing: 0;
|
||||
color: var(--accent); font-size: 9.5px;
|
||||
}
|
||||
.company-agent-editor input[type="text"],
|
||||
.company-agent-editor textarea {
|
||||
width: 100%; box-sizing: border-box;
|
||||
background: var(--input-bg); color: var(--text-primary);
|
||||
border: 1px solid var(--border); border-radius: 4px;
|
||||
padding: 6px 8px; font-size: 11px; font-family: inherit;
|
||||
margin-top: 3px;
|
||||
}
|
||||
.company-agent-editor textarea { resize: vertical; min-height: 60px; }
|
||||
.company-agent-editor .editor-actions {
|
||||
display: flex; justify-content: flex-end; gap: 6px; margin-top: 8px;
|
||||
}
|
||||
.company-agent-editor .editor-actions button {
|
||||
font-size: 10px; padding: 4px 10px; border-radius: 5px; cursor: pointer;
|
||||
background: var(--surface); color: var(--text-primary);
|
||||
border: 1px solid var(--border);
|
||||
}
|
||||
.company-agent-editor .editor-actions button.primary {
|
||||
background: var(--accent); border-color: var(--accent); color: #fff;
|
||||
}
|
||||
.company-agent-editor .editor-actions button.danger { color: var(--error); }
|
||||
.company-agent-editor .editor-actions button:hover { border-color: var(--border-bright); }
|
||||
|
||||
/* Per-phase company turn header in chat. */
|
||||
.company-phase-card {
|
||||
|
||||
+168
-18
@@ -696,6 +696,13 @@
|
||||
statusLabel.innerText = '';
|
||||
// Refresh per-agent model dropdown options (if currently visible) so it stays in sync.
|
||||
if (typeof refreshAgentMapModelOptions === 'function') refreshAgentMapModelOptions();
|
||||
// If the company manage overlay is open with cached agent data,
|
||||
// re-render its cards so each per-agent model <select> picks up
|
||||
// any newly-discovered models from the master list.
|
||||
if (typeof _lastCompanyAgentsPayload !== 'undefined' && _lastCompanyAgentsPayload
|
||||
&& document.getElementById('companyOverlay')?.classList.contains('visible')) {
|
||||
renderCompanyAgentCards(_lastCompanyAgentsPayload);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'brainProfiles':
|
||||
@@ -1430,12 +1437,53 @@
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the agent cards in the manage overlay. Each card has a
|
||||
* toggle (active on/off) and a model input (per-agent override).
|
||||
* CEO is rendered but locked-on; clicking its toggle is a no-op.
|
||||
* Keep the last payload around so we can re-render whenever the
|
||||
* model list refreshes (the top `#modelSel` is the source of truth
|
||||
* for available models — see `populateAgentModelSelect`).
|
||||
*/
|
||||
let _lastCompanyAgentsPayload = null;
|
||||
|
||||
/**
|
||||
* Populate one agent's model `<select>` from the master `#modelSel`
|
||||
* options. Mirrors the pattern used by `refreshAgentMapModelOptions`
|
||||
* so the user picks from the same canonical list everywhere.
|
||||
* Empty value = "default (global)". Saved overrides not in the list
|
||||
* are preserved as a "(saved)" option so the value never gets lost.
|
||||
*/
|
||||
function populateAgentModelSelect(sel, current) {
|
||||
sel.innerHTML = '';
|
||||
const useDefault = document.createElement('option');
|
||||
useDefault.value = '';
|
||||
useDefault.innerText = 'default (global)';
|
||||
sel.appendChild(useDefault);
|
||||
const seen = new Set();
|
||||
for (const opt of modelSel.options) {
|
||||
if (!opt.value || seen.has(opt.value)) continue;
|
||||
seen.add(opt.value);
|
||||
const o = document.createElement('option');
|
||||
o.value = opt.value;
|
||||
o.innerText = opt.innerText;
|
||||
sel.appendChild(o);
|
||||
}
|
||||
if (current && !seen.has(current)) {
|
||||
const o = document.createElement('option');
|
||||
o.value = current;
|
||||
o.innerText = `${current} (saved)`;
|
||||
sel.appendChild(o);
|
||||
}
|
||||
sel.value = current || '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the agent cards in the manage overlay. Each card has:
|
||||
* - a model dropdown (default + every loaded model)
|
||||
* - an ON/OFF toggle (CEO always-on)
|
||||
* - an Edit button that toggles an inline prompt editor with
|
||||
* tagline / specialty / persona textareas + Reset/Save/Cancel.
|
||||
*/
|
||||
function renderCompanyAgentCards(payload) {
|
||||
if (!_companyAgentList) return;
|
||||
_lastCompanyAgentsPayload = payload;
|
||||
_companyAgentList.innerHTML = '';
|
||||
if (_companyNameInput && payload && typeof payload.companyName === 'string') {
|
||||
_companyNameInput.value = payload.companyName;
|
||||
@@ -1446,6 +1494,14 @@
|
||||
li.className = 'company-agent-card';
|
||||
li.setAttribute('data-active', a.active ? 'true' : 'false');
|
||||
if (a.alwaysOn) li.setAttribute('data-locked', 'true');
|
||||
li.dataset.agentId = a.id;
|
||||
|
||||
// ── Row 1: emoji + name/tagline + controls ──
|
||||
const row = document.createElement('div');
|
||||
row.style.display = 'flex';
|
||||
row.style.alignItems = 'center';
|
||||
row.style.gap = '10px';
|
||||
row.style.width = '100%';
|
||||
|
||||
const emoji = document.createElement('span');
|
||||
emoji.className = 'company-agent-emoji';
|
||||
@@ -1466,20 +1522,30 @@
|
||||
const controls = document.createElement('div');
|
||||
controls.className = 'company-agent-controls';
|
||||
|
||||
const modelInput = document.createElement('input');
|
||||
modelInput.type = 'text';
|
||||
modelInput.className = 'company-agent-model';
|
||||
modelInput.placeholder = 'default';
|
||||
modelInput.value = a.modelOverride || '';
|
||||
modelInput.title = '비워두면 글로벌 기본 모델 사용';
|
||||
modelInput.onchange = () => {
|
||||
const modelSelEl = document.createElement('select');
|
||||
modelSelEl.className = 'company-agent-model';
|
||||
modelSelEl.title = '비워두면 글로벌 기본 모델 사용';
|
||||
populateAgentModelSelect(modelSelEl, a.modelOverride || '');
|
||||
modelSelEl.onchange = () => {
|
||||
vscode.postMessage({
|
||||
type: 'setCompanyAgentModel',
|
||||
agentId: a.id,
|
||||
model: modelInput.value.trim(),
|
||||
model: modelSelEl.value || '',
|
||||
});
|
||||
};
|
||||
|
||||
const editBtn = document.createElement('button');
|
||||
editBtn.className = 'company-agent-edit';
|
||||
editBtn.textContent = '✎ Edit';
|
||||
if (a.personaOverridden || a.specialtyOverridden || a.taglineOverridden) {
|
||||
editBtn.classList.add('dirty');
|
||||
editBtn.title = 'prompt 편집됨 (원본과 다름)';
|
||||
}
|
||||
editBtn.onclick = () => {
|
||||
const expanded = li.getAttribute('data-expanded') === 'true';
|
||||
li.setAttribute('data-expanded', expanded ? 'false' : 'true');
|
||||
};
|
||||
|
||||
const toggle = document.createElement('button');
|
||||
toggle.className = 'company-agent-toggle';
|
||||
toggle.textContent = a.active ? 'ON' : 'OFF';
|
||||
@@ -1488,8 +1554,6 @@
|
||||
toggle.textContent = 'LOCKED';
|
||||
} else {
|
||||
toggle.onclick = () => {
|
||||
// Optimistic update + send the full new list so the
|
||||
// backend has a single canonical replace operation.
|
||||
const wantActive = !(li.getAttribute('data-active') === 'true');
|
||||
li.setAttribute('data-active', wantActive ? 'true' : 'false');
|
||||
toggle.textContent = wantActive ? 'ON' : 'OFF';
|
||||
@@ -1500,18 +1564,104 @@
|
||||
vscode.postMessage({ type: 'setCompanyActiveAgents', value: nextIds });
|
||||
};
|
||||
}
|
||||
li.dataset.agentId = a.id;
|
||||
controls.appendChild(modelInput);
|
||||
controls.appendChild(modelSelEl);
|
||||
controls.appendChild(editBtn);
|
||||
controls.appendChild(toggle);
|
||||
|
||||
li.appendChild(emoji);
|
||||
li.appendChild(body);
|
||||
li.appendChild(controls);
|
||||
row.appendChild(emoji);
|
||||
row.appendChild(body);
|
||||
row.appendChild(controls);
|
||||
li.appendChild(row);
|
||||
|
||||
// ── Row 2 (collapsed by default): prompt editor ──
|
||||
li.appendChild(_buildAgentPromptEditor(a));
|
||||
_companyAgentList.appendChild(li);
|
||||
}
|
||||
if (_companyStatusEl) _companyStatusEl.textContent = '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the per-agent prompt editor. Hidden until the user clicks
|
||||
* the Edit button. Three fields (tagline / specialty / persona);
|
||||
* Save sends whichever fields actually changed; Reset sends `null`
|
||||
* to wipe all overrides at once.
|
||||
*/
|
||||
function _buildAgentPromptEditor(a) {
|
||||
const editor = document.createElement('div');
|
||||
editor.className = 'company-agent-editor';
|
||||
|
||||
const _field = (key, labelText, isTextarea, current, defaultVal, overridden) => {
|
||||
const lbl = document.createElement('label');
|
||||
lbl.className = 'field-label';
|
||||
lbl.innerHTML = `<span>${labelText}</span>` +
|
||||
(overridden
|
||||
? '<span class="field-flag">overridden</span>'
|
||||
: '<span class="field-flag" style="color:var(--text-dim)">default</span>');
|
||||
editor.appendChild(lbl);
|
||||
const el = isTextarea
|
||||
? document.createElement('textarea')
|
||||
: document.createElement('input');
|
||||
if (!isTextarea) el.type = 'text';
|
||||
el.value = current || '';
|
||||
el.placeholder = defaultVal || '';
|
||||
editor.appendChild(el);
|
||||
return el;
|
||||
};
|
||||
|
||||
const tagInput = _field('tagline', 'Tagline (한 줄)', false, a.tagline, a.defaultTagline, a.taglineOverridden);
|
||||
const specInput = _field('specialty', 'Specialty (CEO가 dispatch 판단에 사용)', true, a.specialty, a.defaultSpecialty, a.specialtyOverridden);
|
||||
const persInput = _field('persona', 'Persona (말투·관점·강조)', true, a.persona, a.defaultPersona, a.personaOverridden);
|
||||
specInput.rows = 3;
|
||||
persInput.rows = 5;
|
||||
|
||||
const actions = document.createElement('div');
|
||||
actions.className = 'editor-actions';
|
||||
|
||||
const resetBtn = document.createElement('button');
|
||||
resetBtn.className = 'danger';
|
||||
resetBtn.textContent = 'Reset';
|
||||
resetBtn.title = '이 에이전트의 모든 override 제거 → 디폴트로 복귀';
|
||||
resetBtn.onclick = () => {
|
||||
vscode.postMessage({
|
||||
type: 'setCompanyAgentPrompt',
|
||||
agentId: a.id,
|
||||
override: null,
|
||||
});
|
||||
};
|
||||
|
||||
const cancelBtn = document.createElement('button');
|
||||
cancelBtn.textContent = 'Cancel';
|
||||
cancelBtn.onclick = () => {
|
||||
const card = editor.closest('.company-agent-card');
|
||||
if (card) card.setAttribute('data-expanded', 'false');
|
||||
};
|
||||
|
||||
const saveBtn = document.createElement('button');
|
||||
saveBtn.className = 'primary';
|
||||
saveBtn.textContent = 'Save';
|
||||
saveBtn.onclick = () => {
|
||||
// Send what's currently in each field. The backend treats an
|
||||
// empty string as "clear this field" (back to default), so
|
||||
// typing nothing into Tagline + saving = Tagline default,
|
||||
// Specialty + Persona untouched if not modified.
|
||||
vscode.postMessage({
|
||||
type: 'setCompanyAgentPrompt',
|
||||
agentId: a.id,
|
||||
override: {
|
||||
tagline: tagInput.value === a.defaultTagline ? '' : tagInput.value,
|
||||
specialty: specInput.value === a.defaultSpecialty ? '' : specInput.value,
|
||||
persona: persInput.value === a.defaultPersona ? '' : persInput.value,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
actions.appendChild(resetBtn);
|
||||
actions.appendChild(cancelBtn);
|
||||
actions.appendChild(saveBtn);
|
||||
editor.appendChild(actions);
|
||||
return editor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render one phase event from the dispatcher. The chat gets a
|
||||
* card per phase so the user can follow progress in real time —
|
||||
|
||||
Reference in New Issue
Block a user