Files
connectai/media/settings-panel.js
T

271 lines
11 KiB
JavaScript

(function () {
const vscode = acquireVsCodeApi();
const $ = (id) => document.getElementById(id);
// ---- Telegram ----
const tokenInput = $('tgToken');
const saveBtn = $('tgSaveToken');
const clearBtn = $('tgClearToken');
const testBtn = $('tgTest');
const enabledChk = $('tgEnabled');
const enrollBtn = $('tgEnroll');
const enrollCancelBtn = $('tgEnrollCancel');
const enrollStatus = $('tgEnrollStatus');
const tokenStatus = $('tgTokenStatus');
const botName = $('tgBotName');
const tgFeedback = $('tgFeedback');
const tgError = $('tgError');
const chatList = $('tgChatIds');
// ---- Connection ----
const cnUrl = $('cnUrl');
const cnModel = $('cnModel');
const cnTimeout = $('cnTimeout');
const cnRefreshModels = $('cnRefreshModels');
const cnModelHint = $('cnModelHint');
// ---- Memory ----
const memEnabled = $('memEnabled');
const memShort = $('memShort');
const memMid = $('memMid');
const memLong = $('memLong');
// ---- Brain ----
const brainName = $('brainName');
const brainPath = $('brainPath');
const brainAutoPush = $('brainAutoPush');
// ---- Advanced ----
const advDryRun = $('advDryRun');
const advMulti = $('advMulti');
const advAutoSteps = $('advAutoSteps');
const advCtxSize = $('advCtxSize');
// ---- Banner ----
const bannerError = $('bannerError');
// ---- Telegram listeners ----
saveBtn.addEventListener('click', () => {
const t = (tokenInput.value || '').trim();
if (!t) return;
vscode.postMessage({ type: 'telegram.saveToken', token: t });
tokenInput.value = '';
});
clearBtn.addEventListener('click', () => vscode.postMessage({ type: 'telegram.clearToken' }));
testBtn.addEventListener('click', () => vscode.postMessage({ type: 'telegram.testConnection' }));
enabledChk.addEventListener('change', (e) =>
vscode.postMessage({ type: 'telegram.toggleEnabled', enabled: e.target.checked })
);
enrollBtn.addEventListener('click', () => vscode.postMessage({ type: 'telegram.enroll' }));
enrollCancelBtn.addEventListener('click', () => vscode.postMessage({ type: 'telegram.cancelEnroll' }));
chatList.addEventListener('click', (e) => {
if (!(e.target instanceof HTMLElement)) return;
if (e.target.classList.contains('remove')) {
const id = Number(e.target.dataset.id);
if (Number.isFinite(id)) vscode.postMessage({ type: 'telegram.removeChatId', chatId: id });
}
});
// ---- Connection listeners ----
document.querySelector('[data-save="connection.url"]').addEventListener('click', () =>
vscode.postMessage({ type: 'connection.update', ollamaUrl: cnUrl.value })
);
document.querySelector('[data-save="connection.model"]').addEventListener('click', () =>
vscode.postMessage({ type: 'connection.update', defaultModel: cnModel.value })
);
cnRefreshModels.addEventListener('click', () =>
vscode.postMessage({ type: 'connection.update', refreshModels: true })
);
cnModel.addEventListener('change', () =>
vscode.postMessage({ type: 'connection.update', defaultModel: cnModel.value })
);
document.querySelector('[data-save="connection.timeout"]').addEventListener('click', () =>
vscode.postMessage({ type: 'connection.update', requestTimeout: Number(cnTimeout.value) })
);
// ---- Memory listeners ----
memEnabled.addEventListener('change', (e) =>
vscode.postMessage({ type: 'memory.update', memoryEnabled: e.target.checked })
);
document.querySelector('[data-save="memory.short"]').addEventListener('click', () =>
vscode.postMessage({ type: 'memory.update', memoryShortTermMessages: Number(memShort.value) })
);
document.querySelector('[data-save="memory.mid"]').addEventListener('click', () =>
vscode.postMessage({ type: 'memory.update', memoryMediumTermSessions: Number(memMid.value) })
);
document.querySelector('[data-save="memory.long"]').addEventListener('click', () =>
vscode.postMessage({ type: 'memory.update', memoryLongTermFiles: Number(memLong.value) })
);
// ---- Brain listeners ----
brainAutoPush.addEventListener('change', (e) =>
vscode.postMessage({ type: 'brain.update', autoPushBrain: e.target.checked })
);
$('brainOpenSettings').addEventListener('click', () =>
vscode.postMessage({ type: 'openVscodeSettings' })
);
// ---- Advanced listeners ----
advDryRun.addEventListener('change', (e) =>
vscode.postMessage({ type: 'advanced.update', dryRun: e.target.checked })
);
advMulti.addEventListener('change', (e) =>
vscode.postMessage({ type: 'advanced.update', multiAgentEnabled: e.target.checked })
);
document.querySelector('[data-save="advanced.autoSteps"]').addEventListener('click', () =>
vscode.postMessage({ type: 'advanced.update', maxAutoSteps: Number(advAutoSteps.value) })
);
document.querySelector('[data-save="advanced.ctxSize"]').addEventListener('click', () =>
vscode.postMessage({ type: 'advanced.update', maxContextSize: Number(advCtxSize.value) })
);
// ---- Header ----
$('openVscodeSettings').addEventListener('click', () =>
vscode.postMessage({ type: 'openVscodeSettings' })
);
// ---- State sync ----
window.addEventListener('message', (e) => {
const msg = e.data;
if (!msg || msg.type !== 'state') return;
renderState(msg.value);
});
/** Set input.value only when the field is not currently focused, so user edits aren't clobbered mid-typing. */
function setIfNotFocused(input, value) {
if (document.activeElement === input) return;
const next = value === undefined || value === null ? '' : String(value);
if (input.value !== next) input.value = next;
}
function renderState(state) {
// ---- Banner ----
if (state.bannerError) {
bannerError.hidden = false;
bannerError.textContent = state.bannerError;
} else {
bannerError.hidden = true;
bannerError.textContent = '';
}
// ---- Telegram ----
const tg = state.telegram;
tokenStatus.textContent = tg.hasToken ? '저장된 토큰이 있습니다.' : '아직 토큰이 등록되지 않았습니다.';
clearBtn.disabled = !tg.hasToken;
if (tg.botName) {
botName.textContent = `연결됨: ${tg.botName}` + (tg.connected ? ' · 폴링 중' : ' · 비활성화 상태');
botName.classList.toggle('ok', tg.connected);
} else {
botName.textContent = '';
botName.classList.remove('ok');
}
enabledChk.checked = !!tg.enabled;
enabledChk.disabled = !tg.hasToken;
if (tg.enrolling) {
enrollBtn.hidden = true;
enrollCancelBtn.hidden = false;
enrollStatus.textContent = '봇에게 메시지를 한 번 보내주세요. 다음 메시지의 채널이 자동 등록됩니다.';
} else {
enrollBtn.hidden = false;
enrollCancelBtn.hidden = true;
enrollStatus.textContent = '';
}
enrollBtn.disabled = !tg.hasToken;
chatList.innerHTML = '';
if (!tg.allowedChatIds || tg.allowedChatIds.length === 0) {
const li = document.createElement('li');
li.className = 'empty';
li.textContent = '등록된 채널 없음';
chatList.appendChild(li);
} else {
for (const id of tg.allowedChatIds) {
const li = document.createElement('li');
li.textContent = String(id);
const x = document.createElement('span');
x.className = 'remove';
x.dataset.id = String(id);
x.textContent = '✕';
x.title = '허용 목록에서 제거';
li.appendChild(x);
chatList.appendChild(li);
}
}
if (tg.lastSuccess) {
tgFeedback.hidden = false;
tgFeedback.textContent = tg.lastSuccess;
} else {
tgFeedback.hidden = true;
tgFeedback.textContent = '';
}
if (tg.lastError) {
tgError.hidden = false;
tgError.textContent = tg.lastError;
} else {
tgError.hidden = true;
tgError.textContent = '';
}
// ---- Connection ----
const cn = state.connection;
setIfNotFocused(cnUrl, cn.ollamaUrl);
setIfNotFocused(cnTimeout, cn.requestTimeout);
// Model dropdown — preserve selection, allow current value even if not in list
const wantedModel = cn.defaultModel || '';
const list = Array.isArray(cn.availableModels) ? cn.availableModels.slice() : [];
if (wantedModel && !list.includes(wantedModel)) list.unshift(wantedModel);
// Only repaint when list actually changed (otherwise we lose the user's
// open dropdown state).
const currentOptions = Array.from(cnModel.options).map((o) => o.value);
const listChanged = currentOptions.length !== list.length
|| currentOptions.some((v, i) => v !== list[i]);
if (listChanged) {
cnModel.innerHTML = '';
for (const m of list) {
const opt = document.createElement('option');
opt.value = m;
opt.textContent = m;
cnModel.appendChild(opt);
}
if (list.length === 0) {
const opt = document.createElement('option');
opt.value = '';
opt.textContent = '(모델 없음 — 엔진 연결 확인)';
opt.disabled = true;
cnModel.appendChild(opt);
}
}
cnModel.value = wantedModel;
cnModelHint.textContent = cn.modelsLoading
? '모델 목록 가져오는 중…'
: `사이드바에서 선택한 모델이 여기에도 동기화됩니다. (${list.length}개 발견)`;
// ---- Memory ----
const mem = state.memory;
memEnabled.checked = !!mem.memoryEnabled;
setIfNotFocused(memShort, mem.memoryShortTermMessages);
setIfNotFocused(memMid, mem.memoryMediumTermSessions);
setIfNotFocused(memLong, mem.memoryLongTermFiles);
// ---- Brain ----
const br = state.brain;
brainName.textContent = br.activeBrainName + (br.profileCount > 0 ? ` (전체 ${br.profileCount}개)` : '');
brainPath.textContent = br.activeBrainPath || '경로 없음';
brainAutoPush.checked = !!br.autoPushBrain;
// ---- Advanced ----
const adv = state.advanced;
advDryRun.checked = !!adv.dryRun;
advMulti.checked = !!adv.multiAgentEnabled;
setIfNotFocused(advAutoSteps, adv.maxAutoSteps);
setIfNotFocused(advCtxSize, adv.maxContextSize);
}
vscode.postMessage({ type: 'ready' });
})();