Release v2.2.0: Milestone - Human-Centric UI & Workflow Evolution
This commit is contained in:
+143
-77
@@ -1009,22 +1009,22 @@
|
||||
if (v.ok) {
|
||||
if (errEl) errEl.textContent = '';
|
||||
if (typeof window.__closePipelineEditor === 'function') window.__closePipelineEditor();
|
||||
showToast('✅ 파이프라인 저장 완료', 'info');
|
||||
showToast('작업 흐름 저장 완료', 'info');
|
||||
} else {
|
||||
if (errEl) errEl.textContent = v.reason || '파이프라인 저장 실패.';
|
||||
if (errEl) errEl.textContent = v.reason || '작업 흐름 저장 실패.';
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'deleteCompanyPipelineResult': {
|
||||
const v = msg.value || {};
|
||||
if (v.ok) showToast(`🗑 파이프라인 '${v.pipelineId}' 삭제됨`, 'warn');
|
||||
if (v.ok) showToast(`작업 흐름 '${v.pipelineId}' 삭제됨`, 'warn');
|
||||
else showToast(`삭제 실패: ${v.reason || '알 수 없음'}`, 'warn');
|
||||
break;
|
||||
}
|
||||
case 'setActiveCompanyPipelineResult': {
|
||||
const v = msg.value || {};
|
||||
if (v.ok) {
|
||||
showToast(v.pipelineId ? `▶ 파이프라인 '${v.pipelineId}' 활성` : '▶ 기본(CEO 자유 분배)로 복귀', 'info');
|
||||
showToast(v.pipelineId ? `작업 흐름 '${v.pipelineId}' 사용 중` : '대표가 알아서 분배하도록 변경', 'info');
|
||||
} else {
|
||||
showToast(`활성 설정 실패: ${v.reason || '알 수 없음'}`, 'warn');
|
||||
}
|
||||
@@ -2021,7 +2021,7 @@
|
||||
// populateAgentModelSelect는 첫 옵션 라벨이 "default (global)"인데
|
||||
// stage 맥락에선 "기본 (에이전트 설정 사용)"이 더 정확. 첫 옵션 텍스트만 교체.
|
||||
if (stageModelSel.options.length > 0 && stageModelSel.options[0].value === '') {
|
||||
stageModelSel.options[0].text = '기본 (에이전트 설정)';
|
||||
stageModelSel.options[0].text = '담당자 설정 사용';
|
||||
}
|
||||
stageModelSel.onchange = () => { stage.modelOverride = stageModelSel.value || ''; };
|
||||
row.appendChild(modelLbl); row.appendChild(stageModelSel);
|
||||
@@ -2040,15 +2040,15 @@
|
||||
approvalCb.onchange = () => { stage.requiresApproval = approvalCb.checked; };
|
||||
approvalWrap.appendChild(approvalCb);
|
||||
const approvalText = document.createElement('span');
|
||||
approvalText.textContent = '✋ 이 단계 후 내 승인 받기';
|
||||
approvalText.title = '체크하면 stage 완료 후 자동 진행하지 않고 승인/수정요청/중단 버튼이 채팅창에 뜸';
|
||||
approvalText.textContent = '이 단계 후 내 승인 받기';
|
||||
approvalText.title = '체크하면 단계 완료 후 자동 진행하지 않고 승인/수정요청/중단 버튼이 채팅창에 뜹니다';
|
||||
approvalWrap.appendChild(approvalText);
|
||||
body.appendChild(approvalWrap);
|
||||
|
||||
// 지시 텍스트 + 토큰 버튼
|
||||
const instrLabelDiv = document.createElement('div');
|
||||
instrLabelDiv.className = 'psc-field-label';
|
||||
instrLabelDiv.textContent = '지시 내용 (담당자에게 전달될 메시지)';
|
||||
instrLabelDiv.textContent = '담당자에게 전달할 요청';
|
||||
body.appendChild(instrLabelDiv);
|
||||
|
||||
const tokens = document.createElement('div');
|
||||
@@ -2091,20 +2091,20 @@
|
||||
const summary = document.createElement('summary');
|
||||
const hasLoop = !!(stage.loopBackPattern && stage.loopBackTo);
|
||||
summary.textContent = hasLoop
|
||||
? `🔁 재시도 활성: "${stage.loopBackPattern}" 발견 시 → ${_editStages.find((s) => s.id === stage.loopBackTo)?.label || stage.loopBackTo}`
|
||||
: '🔁 재시도 조건 설정하기 (선택)';
|
||||
? `재작업 활성: "${stage.loopBackPattern}" 발견 시 → ${_editStages.find((s) => s.id === stage.loopBackTo)?.label || stage.loopBackTo}`
|
||||
: '문제가 있으면 이전 단계로 되돌리기 (선택)';
|
||||
loop.appendChild(summary);
|
||||
const grid = document.createElement('div');
|
||||
grid.className = 'psc-loop-grid';
|
||||
// condition
|
||||
const condLbl = document.createElement('label'); condLbl.textContent = '조건 (regex):';
|
||||
const condLbl = document.createElement('label'); condLbl.textContent = '감지할 표현:';
|
||||
const condInput = document.createElement('input');
|
||||
condInput.type = 'text';
|
||||
condInput.placeholder = '예: 버그|오류|fail|재작업';
|
||||
condInput.value = stage.loopBackPattern || '';
|
||||
condInput.oninput = () => { stage.loopBackPattern = condInput.value; };
|
||||
// target
|
||||
const tgtLbl = document.createElement('label'); tgtLbl.textContent = '되돌아갈 단계:';
|
||||
const tgtLbl = document.createElement('label'); tgtLbl.textContent = '돌아갈 단계:';
|
||||
const tgtSel = document.createElement('select');
|
||||
const emptyOpt = document.createElement('option');
|
||||
emptyOpt.value = ''; emptyOpt.textContent = '(선택 안 함)';
|
||||
@@ -2296,11 +2296,11 @@
|
||||
// 템플릿 드롭다운 채우기.
|
||||
const tplSel = document.getElementById('pipelineTemplateSel');
|
||||
if (tplSel && payload && Array.isArray(payload.templates)) {
|
||||
tplSel.innerHTML = '<option value="">📋 템플릿에서…</option>';
|
||||
tplSel.innerHTML = '<option value="">템플릿으로 시작</option>';
|
||||
for (const t of payload.templates) {
|
||||
const opt = document.createElement('option');
|
||||
opt.value = t.templateId;
|
||||
opt.textContent = `${t.name} (${t.stageCount}단계)`;
|
||||
opt.textContent = `${t.name} · ${t.stageCount}단계`;
|
||||
opt.title = t.description || '';
|
||||
tplSel.appendChild(opt);
|
||||
}
|
||||
@@ -2310,11 +2310,11 @@
|
||||
_renderStages();
|
||||
}
|
||||
// active dropdown
|
||||
_activePipelineSel.innerHTML = '<option value="">기본 (CEO 자유 분배)</option>';
|
||||
_activePipelineSel.innerHTML = '<option value="">대표가 알아서 분배</option>';
|
||||
for (const p of Object.values(pipelines)) {
|
||||
const opt = document.createElement('option');
|
||||
opt.value = p.id;
|
||||
opt.textContent = `${p.name || p.id} (${(p.stages || []).length} stages)`;
|
||||
opt.textContent = `${p.name || p.id} · ${(p.stages || []).length}단계`;
|
||||
_activePipelineSel.appendChild(opt);
|
||||
}
|
||||
_activePipelineSel.value = activeId;
|
||||
@@ -2323,32 +2323,31 @@
|
||||
const entries = Object.values(pipelines);
|
||||
if (entries.length === 0) {
|
||||
const li = document.createElement('li');
|
||||
li.className = 'map-item';
|
||||
li.style.cssText = 'color: var(--text-dim); font-style: italic; padding: 6px 4px;';
|
||||
li.textContent = '아직 파이프라인이 없습니다. "+ Pipeline" 으로 새로 만드세요.';
|
||||
li.className = 'pipeline-empty-state';
|
||||
li.textContent = '아직 저장된 작업 흐름이 없습니다. 템플릿으로 시작하거나 직접 만들어보세요.';
|
||||
_pipelineList.appendChild(li);
|
||||
return;
|
||||
}
|
||||
for (const p of entries) {
|
||||
const li = document.createElement('li');
|
||||
li.className = 'map-item';
|
||||
li.style.cssText = 'padding:8px 10px; background:var(--surface); border:1px solid var(--border); border-radius:6px;';
|
||||
li.className = 'pipeline-summary-card';
|
||||
const head = document.createElement('div');
|
||||
head.style.cssText = 'display:flex; gap:8px; align-items:center; justify-content:space-between;';
|
||||
head.className = 'pipeline-summary-head';
|
||||
const title = document.createElement('div');
|
||||
title.innerHTML = `<strong>${escAttr(p.name || p.id)}</strong> <span style="font-size:10px; color:var(--text-dim)">${escAttr(p.id)} · ${(p.stages || []).length} stages${p.id === activeId ? ' · <span style="color:var(--accent)">● 활성</span>' : ''}</span>`;
|
||||
title.className = 'pipeline-summary-title';
|
||||
title.innerHTML = `<strong>${escAttr(p.name || p.id)}</strong><span>${(p.stages || []).length}단계${p.id === activeId ? ' · 현재 사용 중' : ''}</span>`;
|
||||
const actions = document.createElement('div');
|
||||
actions.style.cssText = 'display:flex; gap:4px;';
|
||||
actions.className = 'pipeline-summary-actions';
|
||||
const editBtn = document.createElement('button');
|
||||
editBtn.className = 'company-agent-edit';
|
||||
editBtn.textContent = '✎ 편집';
|
||||
editBtn.textContent = '편집';
|
||||
editBtn.onclick = () => _openPipelineEditor(p);
|
||||
const delBtn = document.createElement('button');
|
||||
delBtn.className = 'company-agent-edit';
|
||||
delBtn.textContent = '🗑';
|
||||
delBtn.title = '파이프라인 삭제';
|
||||
delBtn.textContent = '삭제';
|
||||
delBtn.title = '작업 흐름 삭제';
|
||||
delBtn.onclick = () => {
|
||||
if (!confirm(`'${p.name || p.id}' 파이프라인을 삭제할까요?`)) return;
|
||||
if (!confirm(`'${p.name || p.id}' 작업 흐름을 삭제할까요?`)) return;
|
||||
vscode.postMessage({ type: 'deleteCompanyPipeline', pipelineId: p.id });
|
||||
};
|
||||
actions.appendChild(editBtn);
|
||||
@@ -2356,6 +2355,13 @@
|
||||
head.appendChild(title);
|
||||
head.appendChild(actions);
|
||||
li.appendChild(head);
|
||||
const flow = document.createElement('div');
|
||||
flow.className = 'pipeline-summary-flow';
|
||||
const stages = Array.isArray(p.stages) ? p.stages : [];
|
||||
flow.textContent = stages.length > 0
|
||||
? stages.map((s) => s.label || s.id).join(' → ')
|
||||
: '단계가 없습니다';
|
||||
li.appendChild(flow);
|
||||
_pipelineList.appendChild(li);
|
||||
}
|
||||
};
|
||||
@@ -2400,7 +2406,7 @@
|
||||
sel.innerHTML = '';
|
||||
const useDefault = document.createElement('option');
|
||||
useDefault.value = '';
|
||||
useDefault.innerText = 'default (global)';
|
||||
useDefault.innerText = '기본 모델 사용';
|
||||
sel.appendChild(useDefault);
|
||||
const seen = new Set();
|
||||
for (const opt of modelSel.options) {
|
||||
@@ -2414,7 +2420,7 @@
|
||||
if (current && !seen.has(current)) {
|
||||
const o = document.createElement('option');
|
||||
o.value = current;
|
||||
o.innerText = `${current} (saved)`;
|
||||
o.innerText = `${current} (저장됨)`;
|
||||
sel.appendChild(o);
|
||||
}
|
||||
sel.value = current || '';
|
||||
@@ -2464,12 +2470,30 @@
|
||||
return x.i - y.i;
|
||||
})
|
||||
.map((p) => p.a);
|
||||
let sectionKey = '';
|
||||
const appendTeamSection = (key, title, hint) => {
|
||||
if (sectionKey === key) return;
|
||||
sectionKey = key;
|
||||
const section = document.createElement('li');
|
||||
section.className = 'company-agent-section-label';
|
||||
// 시각적 그룹핑용 라벨 — 스크린리더는 카드 갯수만 세도록 presentation 처리.
|
||||
section.setAttribute('role', 'presentation');
|
||||
section.setAttribute('aria-hidden', 'true');
|
||||
section.innerHTML = `<span>${escAttr(title)}</span><small>${escAttr(hint)}</small>`;
|
||||
_companyAgentList.appendChild(section);
|
||||
};
|
||||
for (const a of agents) {
|
||||
if (a.alwaysOn || a.active) {
|
||||
appendTeamSection('active', '참여 중', '이번 작업에 함께 응답합니다');
|
||||
} else {
|
||||
appendTeamSection('standby', '대기 중', '필요할 때 팀에 합류시킬 수 있습니다');
|
||||
}
|
||||
const li = document.createElement('li');
|
||||
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;
|
||||
li.style.setProperty('--agent-color', a.color || 'var(--accent)');
|
||||
|
||||
// ── Row 1: emoji + name/tagline + controls ──
|
||||
// CSS handles layout via `.company-agent-head` (flex-wrap,
|
||||
@@ -2485,13 +2509,30 @@
|
||||
body.className = 'company-agent-body';
|
||||
const name = document.createElement('div');
|
||||
name.className = 'company-agent-name';
|
||||
name.innerHTML = `${escAttr(a.name)} <span class="company-agent-role">${escAttr(a.role)}</span>`;
|
||||
name.innerHTML = `<span>${escAttr(a.name)}</span><span class="company-agent-role">${escAttr(a.role)}</span>`;
|
||||
const tag = document.createElement('div');
|
||||
tag.className = 'company-agent-tagline';
|
||||
tag.textContent = a.tagline || '';
|
||||
tag.title = a.specialty || '';
|
||||
body.appendChild(name);
|
||||
body.appendChild(tag);
|
||||
const meta = document.createElement('div');
|
||||
meta.className = 'company-agent-meta';
|
||||
const roleChip = document.createElement('span');
|
||||
roleChip.className = 'company-agent-chip';
|
||||
roleChip.textContent = _roleCategoryLabels[a.roleCategory || a.defaultRoleCategory] || a.roleCategory || a.defaultRoleCategory || '역할';
|
||||
const modelChip = document.createElement('span');
|
||||
modelChip.className = 'company-agent-chip';
|
||||
modelChip.textContent = a.modelOverride ? '전용 모델' : '기본 모델';
|
||||
meta.appendChild(roleChip);
|
||||
meta.appendChild(modelChip);
|
||||
if (a.personaOverridden || a.specialtyOverridden || a.taglineOverridden || a.nameOverridden || a.roleOverridden) {
|
||||
const tunedChip = document.createElement('span');
|
||||
tunedChip.className = 'company-agent-chip tuned';
|
||||
tunedChip.textContent = '커스텀';
|
||||
meta.appendChild(tunedChip);
|
||||
}
|
||||
body.appendChild(meta);
|
||||
|
||||
const controls = document.createElement('div');
|
||||
controls.className = 'company-agent-controls';
|
||||
@@ -2542,57 +2583,79 @@
|
||||
|
||||
const editBtn = document.createElement('button');
|
||||
editBtn.className = 'company-agent-edit';
|
||||
editBtn.textContent = '✎ 편집';
|
||||
editBtn.textContent = '프로필';
|
||||
if (a.personaOverridden || a.specialtyOverridden || a.taglineOverridden) {
|
||||
editBtn.classList.add('dirty');
|
||||
editBtn.title = '프롬프트가 사용자에 의해 편집되었습니다 (원본과 다름)';
|
||||
editBtn.title = '프로필과 응답 방식이 사용자에 의해 조정되었습니다';
|
||||
} else {
|
||||
editBtn.title = '이름, 역할, 잘하는 일, 응답 스타일 편집';
|
||||
}
|
||||
editBtn.onclick = () => {
|
||||
const expanded = li.getAttribute('data-expanded') === 'true';
|
||||
li.setAttribute('data-expanded', expanded ? 'false' : 'true');
|
||||
// 카드가 갑자기 두 배로 길어지지 않게 다른 패널은 자동으로 접는다.
|
||||
li.setAttribute('data-settings-expanded', 'false');
|
||||
};
|
||||
const settingsBtn = document.createElement('button');
|
||||
settingsBtn.className = 'company-agent-edit company-agent-settings-btn';
|
||||
settingsBtn.textContent = '설정';
|
||||
settingsBtn.title = '역할 그룹, 모델, 참고 지식 반영 조정';
|
||||
settingsBtn.onclick = () => {
|
||||
const expanded = li.getAttribute('data-settings-expanded') === 'true';
|
||||
li.setAttribute('data-settings-expanded', expanded ? 'false' : 'true');
|
||||
li.setAttribute('data-expanded', 'false');
|
||||
};
|
||||
|
||||
const toggle = document.createElement('button');
|
||||
toggle.className = 'company-agent-toggle';
|
||||
toggle.textContent = a.active ? '켜짐' : '꺼짐';
|
||||
toggle.textContent = a.active ? '참여 중' : '+ 참여';
|
||||
if (a.alwaysOn) {
|
||||
toggle.disabled = true;
|
||||
toggle.textContent = '고정';
|
||||
toggle.textContent = '항상 참여';
|
||||
} else {
|
||||
toggle.onclick = () => {
|
||||
const wantActive = !(li.getAttribute('data-active') === 'true');
|
||||
li.setAttribute('data-active', wantActive ? 'true' : 'false');
|
||||
toggle.textContent = wantActive ? '켜짐' : '꺼짐';
|
||||
const nextIds = Array.from(_companyAgentList.querySelectorAll('.company-agent-card'))
|
||||
.filter(el => el.getAttribute('data-active') === 'true')
|
||||
.map(el => el.dataset.agentId)
|
||||
// ── Optimistic update ──
|
||||
// 백엔드 응답이 오기 전에 카드를 옳은 섹션(참여 중 / 대기 중)으로
|
||||
// 즉시 이동시켜야 라벨과 카드 위치가 어긋난 상태로 머무르지 않는다.
|
||||
// 캐시된 페이로드의 active 플래그를 갱신한 뒤 같은 페이로드로 한 번 더
|
||||
// 렌더하면 sort + 섹션 라벨이 새 상태대로 재계산됨.
|
||||
if (_lastCompanyAgentsPayload && Array.isArray(_lastCompanyAgentsPayload.agents)) {
|
||||
const found = _lastCompanyAgentsPayload.agents.find((x) => x.id === a.id);
|
||||
if (found) found.active = wantActive;
|
||||
}
|
||||
const nextIds = (_lastCompanyAgentsPayload?.agents || [])
|
||||
.filter((x) => x.active || x.alwaysOn)
|
||||
.map((x) => x.id)
|
||||
.filter(Boolean);
|
||||
// 백엔드 알림은 먼저 보내고 (네트워크 latency 흡수), 그 직후 동기 재렌더로
|
||||
// UI를 정렬. 백엔드 응답이 오면 같은 결과로 한 번 더 그려지지만 idempotent.
|
||||
vscode.postMessage({ type: 'setCompanyActiveAgents', value: nextIds });
|
||||
if (_lastCompanyAgentsPayload) renderCompanyAgentCards(_lastCompanyAgentsPayload);
|
||||
};
|
||||
}
|
||||
controls.appendChild(roleSelEl);
|
||||
controls.appendChild(modelSelEl);
|
||||
controls.appendChild(editBtn);
|
||||
controls.appendChild(settingsBtn);
|
||||
// 삭제 버튼 — CEO만 빼고 빌트인/커스텀 모두 노출. 단, 어떤 워크 파이프라인의
|
||||
// stage라도 이 에이전트를 참조하고 있으면 disabled로 처리하고 tooltip에
|
||||
// 사용 중인 파이프라인을 적어, 사용자가 어디로 가서 빼야 하는지 보이게 한다.
|
||||
if (!a.alwaysOn) {
|
||||
const delBtn = document.createElement('button');
|
||||
delBtn.className = 'company-agent-edit company-agent-delete';
|
||||
delBtn.textContent = '🗑';
|
||||
delBtn.textContent = a.custom ? '삭제' : '숨김';
|
||||
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(', ')}. 파이프라인에서 먼저 빼거나 다른 에이전트로 교체하세요.`;
|
||||
delBtn.title = `숨길 수 없음 — 다음 작업 흐름에서 사용 중: ${usedIn.map((n) => `'${n}'`).join(', ')}. 작업 흐름에서 먼저 빼거나 다른 팀원으로 교체하세요.`;
|
||||
} else {
|
||||
delBtn.title = a.custom
|
||||
? `'${a.name}' 에이전트 삭제 (모든 설정 함께 제거)`
|
||||
: `'${a.name}' 에이전트 숨기기 (기본 에이전트라 복원 가능)`;
|
||||
? `'${a.name}' 팀원 삭제 (모든 설정 함께 제거)`
|
||||
: `'${a.name}' 팀원 숨기기 (기본 팀원이라 복원 가능)`;
|
||||
delBtn.onclick = () => {
|
||||
const confirmMsg = a.custom
|
||||
? `'${a.name}' 에이전트를 삭제할까요? 이 에이전트의 모든 설정(모델·프롬프트·지식 비중)도 함께 삭제됩니다.`
|
||||
: `기본 에이전트 '${a.name}'을(를) 목록에서 숨길까요? 나중에 [목록 맨 아래 → 삭제된 기본 에이전트] 영역에서 복원할 수 있습니다.`;
|
||||
? `'${a.name}' 팀원을 삭제할까요? 이 팀원의 모든 설정(모델·응답 방식·지식 반영)도 함께 삭제됩니다.`
|
||||
: `기본 팀원 '${a.name}'을(를) 목록에서 숨길까요? 나중에 [목록 맨 아래 → 숨긴 기본 팀원] 영역에서 복원할 수 있습니다.`;
|
||||
if (!confirm(confirmMsg)) return;
|
||||
vscode.postMessage({ type: 'deleteCompanyAgent', agentId: a.id });
|
||||
};
|
||||
@@ -2606,14 +2669,25 @@
|
||||
row.appendChild(controls);
|
||||
li.appendChild(row);
|
||||
|
||||
// ── Row 1.5: per-agent Knowledge Mix slider ──
|
||||
const settings = document.createElement('div');
|
||||
settings.className = 'company-agent-settings';
|
||||
const settingsGrid = document.createElement('div');
|
||||
settingsGrid.className = 'company-agent-settings-grid';
|
||||
const roleWrap = document.createElement('label');
|
||||
roleWrap.textContent = '역할 그룹';
|
||||
roleWrap.appendChild(roleSelEl);
|
||||
const modelWrap = document.createElement('label');
|
||||
modelWrap.textContent = '모델';
|
||||
modelWrap.appendChild(modelSelEl);
|
||||
settingsGrid.appendChild(roleWrap);
|
||||
settingsGrid.appendChild(modelWrap);
|
||||
settings.appendChild(settingsGrid);
|
||||
// CEO doesn't dispatch agents itself, it only synthesises,
|
||||
// so the brain mix for CEO turns is governed by the
|
||||
// *specialist* it dispatched — exposing the slider for CEO
|
||||
// would just be a confusing dead control.
|
||||
// so the brain mix for CEO turns is governed by specialists.
|
||||
if (a.id !== 'ceo') {
|
||||
li.appendChild(_buildAgentKnowledgeMixSlider(a, payload.globalKnowledgeMixWeight));
|
||||
settings.appendChild(_buildAgentKnowledgeMixSlider(a, payload.globalKnowledgeMixWeight));
|
||||
}
|
||||
li.appendChild(settings);
|
||||
|
||||
// ── Row 2 (collapsed by default): prompt editor ──
|
||||
li.appendChild(_buildAgentPromptEditor(a));
|
||||
@@ -2628,7 +2702,7 @@
|
||||
restoreLi.className = 'company-agent-hidden-section';
|
||||
const head = document.createElement('div');
|
||||
head.className = 'company-agent-hidden-head';
|
||||
head.textContent = `삭제된 기본 에이전트 (${hidden.length}명)`;
|
||||
head.textContent = `숨긴 기본 팀원 (${hidden.length}명)`;
|
||||
restoreLi.appendChild(head);
|
||||
const hint = document.createElement('div');
|
||||
hint.className = 'company-agent-hidden-hint';
|
||||
@@ -2664,18 +2738,13 @@
|
||||
const usingOverride = a.knowledgeMixOverride !== null && a.knowledgeMixOverride !== undefined;
|
||||
const effective = a.effectiveKnowledgeMixWeight;
|
||||
|
||||
// Tight label — "Mix" alone keeps the row narrow. The 🎚 emoji
|
||||
// signals what it controls without needing the full word.
|
||||
const label = document.createElement('span');
|
||||
label.className = 'company-agent-mix-label';
|
||||
label.textContent = '🎚 Mix';
|
||||
label.textContent = '참고 지식 반영';
|
||||
|
||||
// Source badge ("GLOBAL" / "OVERRIDE") visually communicates
|
||||
// *which knob* the value is coming from — clearer than packing
|
||||
// it into the hint text.
|
||||
const sourceBadge = document.createElement('span');
|
||||
sourceBadge.className = 'company-agent-mix-source' + (usingOverride ? ' override' : '');
|
||||
sourceBadge.textContent = usingOverride ? 'override' : 'global';
|
||||
sourceBadge.textContent = usingOverride ? '직접 조정' : '전체 설정';
|
||||
|
||||
const slider = document.createElement('input');
|
||||
slider.type = 'range';
|
||||
@@ -2684,26 +2753,23 @@
|
||||
slider.disabled = !usingOverride;
|
||||
slider.className = 'company-agent-mix-slider';
|
||||
|
||||
// Compact hint: "Brain 55%" only — the model% is just 100 -
|
||||
// brain%, so showing both was redundant noise. Stays narrow
|
||||
// enough not to push the checkbox off the row.
|
||||
const hint = document.createElement('span');
|
||||
hint.className = 'company-agent-mix-hint';
|
||||
const renderHint = () => {
|
||||
const w = parseInt(slider.value, 10) || 50;
|
||||
hint.textContent = `Brain ${w}%`;
|
||||
hint.textContent = `${w}%`;
|
||||
};
|
||||
|
||||
const cb = document.createElement('input');
|
||||
cb.type = 'checkbox'; cb.checked = !usingOverride;
|
||||
cb.className = 'company-agent-mix-cb';
|
||||
cb.title = '글로벌 슬라이더 값 사용';
|
||||
cb.title = '전체 참고 지식 설정 사용';
|
||||
cb.onchange = () => {
|
||||
if (cb.checked) {
|
||||
// Reset to global.
|
||||
slider.disabled = true;
|
||||
slider.value = String(globalWeight ?? 50);
|
||||
sourceBadge.textContent = 'global';
|
||||
sourceBadge.textContent = '전체 설정';
|
||||
sourceBadge.classList.remove('override');
|
||||
renderHint();
|
||||
vscode.postMessage({
|
||||
@@ -2713,7 +2779,7 @@
|
||||
} else {
|
||||
// Take ownership at the current displayed value.
|
||||
slider.disabled = false;
|
||||
sourceBadge.textContent = 'override';
|
||||
sourceBadge.textContent = '직접 조정';
|
||||
sourceBadge.classList.add('override');
|
||||
const w = parseInt(slider.value, 10) || 50;
|
||||
vscode.postMessage({
|
||||
@@ -2735,7 +2801,7 @@
|
||||
const cbWrap = document.createElement('label');
|
||||
cbWrap.className = 'company-agent-mix-cbwrap';
|
||||
cbWrap.appendChild(cb);
|
||||
cbWrap.appendChild(document.createTextNode(' global'));
|
||||
cbWrap.appendChild(document.createTextNode(' 전체 설정 사용'));
|
||||
row.appendChild(label);
|
||||
row.appendChild(sourceBadge);
|
||||
row.appendChild(slider);
|
||||
@@ -2759,8 +2825,8 @@
|
||||
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>');
|
||||
? '<span class="field-flag">조정됨</span>'
|
||||
: '<span class="field-flag" style="color:var(--text-dim)">기본값</span>');
|
||||
editor.appendChild(lbl);
|
||||
const el = isTextarea
|
||||
? document.createElement('textarea')
|
||||
@@ -2776,22 +2842,22 @@
|
||||
// 빌트인이든 커스텀이든 사용자가 자유롭게 리네이밍 가능. 변경 후
|
||||
// CEO 보고서·planner enumeration·세션 로그 등 모든 표시 지점에
|
||||
// 즉시 반영된다 — resolveAgent가 override를 머지하므로.
|
||||
const nameInput = _field('name', '이름 (Display Name)', false, a.name, a.defaultName, a.nameOverridden);
|
||||
const roleInput = _field('role', '역할 (Role Title)', false, a.role, a.defaultRole, a.roleOverridden);
|
||||
const nameInput = _field('name', '이름', false, a.name, a.defaultName, a.nameOverridden);
|
||||
const roleInput = _field('role', '역할 소개', false, a.role, a.defaultRole, a.roleOverridden);
|
||||
// 이모지·색상은 한 줄에 나란히 — CSS는 grid 없이 inline flex로 처리.
|
||||
const visualWrap = document.createElement('div');
|
||||
visualWrap.style.cssText = 'display:flex; gap:8px;';
|
||||
const emojiInput = _field('emoji', '이모지', false, a.emoji, a.defaultEmoji, a.emojiOverridden);
|
||||
const colorInput = _field('color', '색상 (#hex)', false, a.color, a.defaultColor, a.colorOverridden);
|
||||
const colorInput = _field('color', '테마 색상', false, a.color, a.defaultColor, a.colorOverridden);
|
||||
// 위에서 만든 두 필드는 editor에 이미 append됨. 한 줄로 묶고 싶으면
|
||||
// 부모에서 분리해 visualWrap에 다시 넣는다 — label은 직전 sibling.
|
||||
// 더 단순하게: emoji/color 입력 후 reflow는 그냥 두고 max-width만 줄임.
|
||||
emojiInput.style.maxWidth = '80px';
|
||||
colorInput.style.maxWidth = '120px';
|
||||
|
||||
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);
|
||||
const tagInput = _field('tagline', '한 줄 소개', false, a.tagline, a.defaultTagline, a.taglineOverridden);
|
||||
const specInput = _field('specialty', '잘하는 일', true, a.specialty, a.defaultSpecialty, a.specialtyOverridden);
|
||||
const persInput = _field('persona', '응답 스타일', true, a.persona, a.defaultPersona, a.personaOverridden);
|
||||
specInput.rows = 3;
|
||||
persInput.rows = 5;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user