Release v2.1.8: Company Agent roster overhaul and UI polish
This commit is contained in:
+94
-22
@@ -179,7 +179,7 @@
|
||||
const recordsLatest = document.getElementById('recordsLatest');
|
||||
|
||||
function escAttr(t) { return String(t == null ? '' : t).replace(/[&<>"]/g, c => ({ '&': '&', '<': '<', '>': '>', '"': '"' }[c])); }
|
||||
function fmtMixHint(w) { return `Model ${100 - w}% · Brain ${w}%`; }
|
||||
function fmtMixHint(w) { return `모델 ${100 - w}% · 두뇌 ${w}%`; }
|
||||
/** Compact relative-time formatter for chips (e.g. "2m ago", "just now"). */
|
||||
function formatRelativeTime(iso) {
|
||||
try {
|
||||
@@ -405,7 +405,7 @@
|
||||
: Math.max(0, Math.min(100, agentMapDraft.secondBrainWeight | 0));
|
||||
slider.value = String(value);
|
||||
hint.textContent = useGlobal
|
||||
? `Use global · ${fmtMixHint(value)}`
|
||||
? `전역 설정 사용 · ${fmtMixHint(value)}`
|
||||
: fmtMixHint(value);
|
||||
}
|
||||
|
||||
@@ -815,12 +815,24 @@
|
||||
case 'deleteCompanyAgentResult': {
|
||||
const v = msg.value || {};
|
||||
if (v.ok) {
|
||||
showToast(`🗑 '${v.agentId}' 에이전트 삭제됨`, 'warn');
|
||||
const msg2 = v.kind === 'builtin-hidden'
|
||||
? `🗑 기본 에이전트 '${v.agentId}' 숨김 처리 (목록 하단에서 복원 가능)`
|
||||
: `🗑 '${v.agentId}' 에이전트 삭제됨`;
|
||||
showToast(msg2, 'warn');
|
||||
} else {
|
||||
showToast(`삭제 실패: ${v.reason || '알 수 없음'}`, 'warn');
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'restoreHiddenAgentResult': {
|
||||
const v = msg.value || {};
|
||||
if (v.ok) {
|
||||
showToast(`↩ '${v.agentId}' 에이전트 복원 완료`, 'info');
|
||||
} else {
|
||||
showToast(`복원 실패: ${v.reason || '알 수 없음'}`, 'warn');
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'companyPipelines': {
|
||||
if (typeof window.__renderCompanyPipelines === 'function') {
|
||||
window.__renderCompanyPipelines(msg.value || {});
|
||||
@@ -2279,7 +2291,24 @@
|
||||
}
|
||||
if (payload && payload.roleCategoryLabels) _roleCategoryLabels = payload.roleCategoryLabels;
|
||||
if (payload && Array.isArray(payload.roleCategoryOrder)) _roleCategoryOrder = payload.roleCategoryOrder;
|
||||
const agents = (payload && Array.isArray(payload.agents)) ? payload.agents : [];
|
||||
const rawAgents = (payload && Array.isArray(payload.agents)) ? payload.agents : [];
|
||||
// 활성/비활성 정렬: CEO는 항상 최상단(alwaysOn), 그 다음 활성(켜짐) 에이전트,
|
||||
// 마지막으로 비활성. 같은 그룹 내에서는 백엔드가 보낸 원래 순서를 유지(stable).
|
||||
const agents = rawAgents
|
||||
.map((a, i) => ({ a, i }))
|
||||
.sort((x, y) => {
|
||||
// CEO/alwaysOn 최우선
|
||||
const xLocked = x.a.alwaysOn ? 0 : 1;
|
||||
const yLocked = y.a.alwaysOn ? 0 : 1;
|
||||
if (xLocked !== yLocked) return xLocked - yLocked;
|
||||
// 그 다음 active 여부
|
||||
const xAct = x.a.active ? 0 : 1;
|
||||
const yAct = y.a.active ? 0 : 1;
|
||||
if (xAct !== yAct) return xAct - yAct;
|
||||
// tiebreak: 원래 순서
|
||||
return x.i - y.i;
|
||||
})
|
||||
.map((p) => p.a);
|
||||
for (const a of agents) {
|
||||
const li = document.createElement('li');
|
||||
li.className = 'company-agent-card';
|
||||
@@ -2358,10 +2387,10 @@
|
||||
|
||||
const editBtn = document.createElement('button');
|
||||
editBtn.className = 'company-agent-edit';
|
||||
editBtn.textContent = '✎ Edit';
|
||||
editBtn.textContent = '✎ 편집';
|
||||
if (a.personaOverridden || a.specialtyOverridden || a.taglineOverridden) {
|
||||
editBtn.classList.add('dirty');
|
||||
editBtn.title = 'prompt 편집됨 (원본과 다름)';
|
||||
editBtn.title = '프롬프트가 사용자에 의해 편집되었습니다 (원본과 다름)';
|
||||
}
|
||||
editBtn.onclick = () => {
|
||||
const expanded = li.getAttribute('data-expanded') === 'true';
|
||||
@@ -2370,15 +2399,15 @@
|
||||
|
||||
const toggle = document.createElement('button');
|
||||
toggle.className = 'company-agent-toggle';
|
||||
toggle.textContent = a.active ? 'ON' : 'OFF';
|
||||
toggle.textContent = a.active ? '켜짐' : '꺼짐';
|
||||
if (a.alwaysOn) {
|
||||
toggle.disabled = true;
|
||||
toggle.textContent = 'LOCKED';
|
||||
toggle.textContent = '고정';
|
||||
} else {
|
||||
toggle.onclick = () => {
|
||||
const wantActive = !(li.getAttribute('data-active') === 'true');
|
||||
li.setAttribute('data-active', wantActive ? 'true' : 'false');
|
||||
toggle.textContent = wantActive ? 'ON' : 'OFF';
|
||||
toggle.textContent = wantActive ? '켜짐' : '꺼짐';
|
||||
const nextIds = Array.from(_companyAgentList.querySelectorAll('.company-agent-card'))
|
||||
.filter(el => el.getAttribute('data-active') === 'true')
|
||||
.map(el => el.dataset.agentId)
|
||||
@@ -2389,17 +2418,30 @@
|
||||
controls.appendChild(roleSelEl);
|
||||
controls.appendChild(modelSelEl);
|
||||
controls.appendChild(editBtn);
|
||||
// 사용자 추가 에이전트만 삭제 가능. 기본 9명은 코드에 박혀 있어
|
||||
// 백엔드도 거부하므로 UI에서도 버튼을 노출하지 않음.
|
||||
if (a.custom) {
|
||||
// 삭제 버튼 — CEO만 빼고 빌트인/커스텀 모두 노출. 단, 어떤 워크 파이프라인의
|
||||
// stage라도 이 에이전트를 참조하고 있으면 disabled로 처리하고 tooltip에
|
||||
// 사용 중인 파이프라인을 적어, 사용자가 어디로 가서 빼야 하는지 보이게 한다.
|
||||
if (!a.alwaysOn) {
|
||||
const delBtn = document.createElement('button');
|
||||
delBtn.className = 'company-agent-edit';
|
||||
delBtn.className = 'company-agent-edit company-agent-delete';
|
||||
delBtn.textContent = '🗑';
|
||||
delBtn.title = `'${a.name}' 에이전트 삭제`;
|
||||
delBtn.onclick = () => {
|
||||
if (!confirm(`'${a.name}' 에이전트를 삭제할까요? 이 에이전트의 모든 설정(모델·프롬프트·지식 믹스)도 함께 삭제됩니다.`)) return;
|
||||
vscode.postMessage({ type: 'deleteCompanyAgent', agentId: a.id });
|
||||
};
|
||||
const usedIn = Array.isArray(a.usedInPipelines) ? a.usedInPipelines : [];
|
||||
if (usedIn.length > 0) {
|
||||
delBtn.disabled = true;
|
||||
delBtn.classList.add('disabled');
|
||||
delBtn.title = `삭제 불가 — 다음 파이프라인에서 사용 중: ${usedIn.map((n) => `'${n}'`).join(', ')}. 파이프라인에서 먼저 빼거나 다른 에이전트로 교체하세요.`;
|
||||
} else {
|
||||
delBtn.title = a.custom
|
||||
? `'${a.name}' 에이전트 삭제 (모든 설정 함께 제거)`
|
||||
: `'${a.name}' 에이전트 숨기기 (기본 에이전트라 복원 가능)`;
|
||||
delBtn.onclick = () => {
|
||||
const confirmMsg = a.custom
|
||||
? `'${a.name}' 에이전트를 삭제할까요? 이 에이전트의 모든 설정(모델·프롬프트·지식 비중)도 함께 삭제됩니다.`
|
||||
: `기본 에이전트 '${a.name}'을(를) 목록에서 숨길까요? 나중에 [목록 맨 아래 → 삭제된 기본 에이전트] 영역에서 복원할 수 있습니다.`;
|
||||
if (!confirm(confirmMsg)) return;
|
||||
vscode.postMessage({ type: 'deleteCompanyAgent', agentId: a.id });
|
||||
};
|
||||
}
|
||||
controls.appendChild(delBtn);
|
||||
}
|
||||
controls.appendChild(toggle);
|
||||
@@ -2422,6 +2464,36 @@
|
||||
li.appendChild(_buildAgentPromptEditor(a));
|
||||
_companyAgentList.appendChild(li);
|
||||
}
|
||||
// ── 숨겨진 기본 에이전트 복원 영역 ──
|
||||
// 사용자가 "삭제"한 빌트인 에이전트를 다시 꺼낼 수 있는 출구. 빈 배열이면
|
||||
// 섹션 자체를 그리지 않아 평소엔 시야에서 사라진다.
|
||||
const hidden = Array.isArray(payload && payload.hiddenBuiltins) ? payload.hiddenBuiltins : [];
|
||||
if (hidden.length > 0) {
|
||||
const restoreLi = document.createElement('li');
|
||||
restoreLi.className = 'company-agent-hidden-section';
|
||||
const head = document.createElement('div');
|
||||
head.className = 'company-agent-hidden-head';
|
||||
head.textContent = `삭제된 기본 에이전트 (${hidden.length}명)`;
|
||||
restoreLi.appendChild(head);
|
||||
const hint = document.createElement('div');
|
||||
hint.className = 'company-agent-hidden-hint';
|
||||
hint.textContent = '복원하면 목록에 다시 추가됩니다. 설정은 모두 디폴트로 시작합니다.';
|
||||
restoreLi.appendChild(hint);
|
||||
const list = document.createElement('div');
|
||||
list.className = 'company-agent-hidden-list';
|
||||
for (const h of hidden) {
|
||||
const chip = document.createElement('button');
|
||||
chip.className = 'company-agent-hidden-chip';
|
||||
chip.textContent = `${h.emoji || '🙂'} ${h.name} · ${h.role} ↩ 복원`;
|
||||
chip.title = `'${h.name}' 복원`;
|
||||
chip.onclick = () => {
|
||||
vscode.postMessage({ type: 'restoreHiddenAgent', agentId: h.id });
|
||||
};
|
||||
list.appendChild(chip);
|
||||
}
|
||||
restoreLi.appendChild(list);
|
||||
_companyAgentList.appendChild(restoreLi);
|
||||
}
|
||||
if (_companyStatusEl) _companyStatusEl.textContent = '';
|
||||
}
|
||||
|
||||
@@ -2573,15 +2645,15 @@
|
||||
|
||||
const resetBtn = document.createElement('button');
|
||||
resetBtn.className = 'danger';
|
||||
resetBtn.textContent = 'Reset';
|
||||
resetBtn.title = '이 에이전트의 모든 override 제거 → 디폴트로 복귀 (이름·역할·프롬프트 전부)';
|
||||
resetBtn.textContent = '기본값으로';
|
||||
resetBtn.title = '이 에이전트의 모든 사용자 변경 사항 제거 → 디폴트로 복귀 (이름·역할·프롬프트 전부)';
|
||||
resetBtn.onclick = () => {
|
||||
vscode.postMessage({ type: 'setCompanyAgentPrompt', agentId: a.id, override: null });
|
||||
vscode.postMessage({ type: 'setCompanyAgentDisplay', agentId: a.id, override: null });
|
||||
};
|
||||
|
||||
const cancelBtn = document.createElement('button');
|
||||
cancelBtn.textContent = 'Cancel';
|
||||
cancelBtn.textContent = '취소';
|
||||
cancelBtn.onclick = () => {
|
||||
const card = editor.closest('.company-agent-card');
|
||||
if (card) card.setAttribute('data-expanded', 'false');
|
||||
@@ -2589,7 +2661,7 @@
|
||||
|
||||
const saveBtn = document.createElement('button');
|
||||
saveBtn.className = 'primary';
|
||||
saveBtn.textContent = 'Save';
|
||||
saveBtn.textContent = '저장';
|
||||
saveBtn.onclick = () => {
|
||||
// 두 개의 message로 분리 전송 — display(name/role/emoji/color)와
|
||||
// prompt(tagline/specialty/persona)는 백엔드에서 서로 다른
|
||||
|
||||
Reference in New Issue
Block a user