chore: bump version to 2.80.27 and update core features
This commit is contained in:
@@ -0,0 +1,210 @@
|
||||
:root {
|
||||
--gap: 12px;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: var(--vscode-font-family);
|
||||
font-size: 13px;
|
||||
color: var(--vscode-foreground);
|
||||
background: var(--vscode-sideBar-background);
|
||||
margin: 0;
|
||||
padding: 12px 14px 24px;
|
||||
}
|
||||
|
||||
.hd {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.hd h1 {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.section {
|
||||
border: 1px solid var(--vscode-panel-border);
|
||||
border-radius: 8px;
|
||||
padding: 14px;
|
||||
margin-bottom: 14px;
|
||||
background: var(--vscode-editor-background);
|
||||
}
|
||||
|
||||
.section h2 {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
margin: 0 0 6px 0;
|
||||
}
|
||||
|
||||
.section.stub {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.hint {
|
||||
color: var(--vscode-descriptionForeground);
|
||||
font-size: 11px;
|
||||
line-height: 1.5;
|
||||
margin: 0 0 12px 0;
|
||||
}
|
||||
|
||||
.row {
|
||||
margin-bottom: var(--gap);
|
||||
}
|
||||
|
||||
.row label {
|
||||
display: block;
|
||||
font-size: 11px;
|
||||
color: var(--vscode-descriptionForeground);
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.row.toggle label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
color: var(--vscode-foreground);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.input-group {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
input[type="password"], input[type="text"] {
|
||||
flex: 1;
|
||||
padding: 6px 8px;
|
||||
background: var(--vscode-input-background);
|
||||
color: var(--vscode-input-foreground);
|
||||
border: 1px solid var(--vscode-input-border, transparent);
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
font-family: var(--vscode-editor-font-family);
|
||||
}
|
||||
|
||||
input[type="checkbox"] {
|
||||
accent-color: var(--vscode-button-background);
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 6px 10px;
|
||||
border: 1px solid var(--vscode-button-border, transparent);
|
||||
border-radius: 4px;
|
||||
background: var(--vscode-button-background);
|
||||
color: var(--vscode-button-foreground);
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
button:hover { background: var(--vscode-button-hoverBackground); }
|
||||
|
||||
button.ghost {
|
||||
background: var(--vscode-button-secondaryBackground);
|
||||
color: var(--vscode-button-secondaryForeground);
|
||||
}
|
||||
button.ghost:hover { background: var(--vscode-button-secondaryHoverBackground); }
|
||||
|
||||
button.link {
|
||||
background: transparent;
|
||||
color: var(--vscode-textLink-foreground);
|
||||
border: none;
|
||||
padding: 4px 0;
|
||||
font-size: 11px;
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
}
|
||||
button.link:hover { color: var(--vscode-textLink-activeForeground); }
|
||||
|
||||
.status {
|
||||
display: block;
|
||||
margin-top: 6px;
|
||||
font-size: 11px;
|
||||
color: var(--vscode-descriptionForeground);
|
||||
}
|
||||
|
||||
.status-inline {
|
||||
margin-left: 8px;
|
||||
font-size: 11px;
|
||||
color: var(--vscode-descriptionForeground);
|
||||
}
|
||||
|
||||
.status-inline.ok { color: var(--vscode-charts-green, #4ec9b0); }
|
||||
|
||||
.error {
|
||||
margin-top: 10px;
|
||||
padding: 8px 10px;
|
||||
border-radius: 4px;
|
||||
background: var(--vscode-inputValidation-errorBackground);
|
||||
color: var(--vscode-inputValidation-errorForeground);
|
||||
border: 1px solid var(--vscode-inputValidation-errorBorder);
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.chips {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
}
|
||||
.chips li {
|
||||
background: var(--vscode-badge-background);
|
||||
color: var(--vscode-badge-foreground);
|
||||
padding: 2px 8px;
|
||||
border-radius: 12px;
|
||||
font-size: 11px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
.chips .remove {
|
||||
cursor: pointer;
|
||||
opacity: 0.6;
|
||||
}
|
||||
.chips .remove:hover { opacity: 1; }
|
||||
|
||||
.empty {
|
||||
color: var(--vscode-descriptionForeground);
|
||||
font-size: 11px;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.banner {
|
||||
margin: 0 0 12px 0;
|
||||
padding: 10px 12px;
|
||||
border-radius: 6px;
|
||||
background: var(--vscode-inputValidation-warningBackground, #5a4a14);
|
||||
color: var(--vscode-inputValidation-warningForeground, #fff);
|
||||
border: 1px solid var(--vscode-inputValidation-warningBorder, transparent);
|
||||
font-size: 11px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.feedback {
|
||||
margin-top: 10px;
|
||||
padding: 6px 10px;
|
||||
border-radius: 4px;
|
||||
background: rgba(78, 201, 176, 0.15);
|
||||
color: var(--vscode-charts-green, #4ec9b0);
|
||||
border: 1px solid rgba(78, 201, 176, 0.4);
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.input-group.narrow input { max-width: 120px; }
|
||||
|
||||
.readout {
|
||||
padding: 6px 8px;
|
||||
background: var(--vscode-textCodeBlock-background);
|
||||
border-radius: 4px;
|
||||
font-family: var(--vscode-editor-font-family);
|
||||
font-size: 12px;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.section.stub {
|
||||
/* 5-A had this stub class; 5-B fills the sections so we no longer dim them. */
|
||||
opacity: 1;
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<title>Astra Settings</title>
|
||||
<link rel="stylesheet" href="__STYLES_URI__">
|
||||
</head>
|
||||
<body>
|
||||
<header class="hd">
|
||||
<h1>Astra Settings</h1>
|
||||
<button id="openVscodeSettings" class="link">VS Code Settings 열기</button>
|
||||
</header>
|
||||
|
||||
<div id="bannerError" class="banner" hidden></div>
|
||||
|
||||
<main id="root">
|
||||
<!-- Connection -->
|
||||
<section class="section" data-section="connection">
|
||||
<h2>연결</h2>
|
||||
<p class="hint">로컬 AI 엔진(Ollama 또는 LM Studio) 위치와 기본 모델을 설정합니다.</p>
|
||||
<div class="row">
|
||||
<label for="cnUrl">Engine URL</label>
|
||||
<div class="input-group">
|
||||
<input id="cnUrl" type="text" placeholder="http://127.0.0.1:11434" spellcheck="false" />
|
||||
<button data-save="connection.url">저장</button>
|
||||
</div>
|
||||
<small class="hint">Ollama 기본 11434 / LM Studio 기본 1234.</small>
|
||||
</div>
|
||||
<div class="row">
|
||||
<label for="cnModel">기본 모델</label>
|
||||
<div class="input-group">
|
||||
<select id="cnModel"></select>
|
||||
<button data-save="connection.model">저장</button>
|
||||
<button id="cnRefreshModels" class="ghost" title="모델 목록 새로고침">↻</button>
|
||||
</div>
|
||||
<small class="hint" id="cnModelHint">사이드바에서 선택한 모델이 여기에도 동기화됩니다.</small>
|
||||
</div>
|
||||
<div class="row">
|
||||
<label for="cnTimeout">요청 타임아웃 (초)</label>
|
||||
<div class="input-group narrow">
|
||||
<input id="cnTimeout" type="number" min="1" step="1" />
|
||||
<button data-save="connection.timeout">저장</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Memory -->
|
||||
<section class="section" data-section="memory">
|
||||
<h2>메모리</h2>
|
||||
<p class="hint">대화 응답 전에 주입되는 단기/중기/장기 메모리의 양을 조정합니다.</p>
|
||||
<div class="row toggle">
|
||||
<label><input id="memEnabled" type="checkbox"> 메모리 시스템 활성화</label>
|
||||
</div>
|
||||
<div class="row">
|
||||
<label for="memShort">최근 대화 메시지 (단기)</label>
|
||||
<div class="input-group narrow">
|
||||
<input id="memShort" type="number" min="0" step="1" />
|
||||
<button data-save="memory.short">저장</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<label for="memMid">최근 세션 (중기)</label>
|
||||
<div class="input-group narrow">
|
||||
<input id="memMid" type="number" min="0" step="1" />
|
||||
<button data-save="memory.mid">저장</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<label for="memLong">관련 brain 문서 (장기)</label>
|
||||
<div class="input-group narrow">
|
||||
<input id="memLong" type="number" min="0" step="1" />
|
||||
<button data-save="memory.long">저장</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Brain -->
|
||||
<section class="section" data-section="brain">
|
||||
<h2>Brain</h2>
|
||||
<p class="hint">현재 활성 brain 프로필 정보입니다. 프로필 추가·수정은 사이드바의 Brain 메뉴 또는 VS Code Settings에서 처리합니다.</p>
|
||||
<div class="row">
|
||||
<label>활성 프로필</label>
|
||||
<div class="readout" id="brainName">—</div>
|
||||
<small class="hint" id="brainPath">—</small>
|
||||
</div>
|
||||
<div class="row toggle">
|
||||
<label><input id="brainAutoPush" type="checkbox"> 변경 시 GitHub 자동 push (autoPushBrain)</label>
|
||||
</div>
|
||||
<div class="row">
|
||||
<button id="brainOpenSettings" class="link">brainProfiles 편집 (VS Code Settings)</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Telegram -->
|
||||
<section class="section" data-section="telegram">
|
||||
<h2>Telegram 봇</h2>
|
||||
<p class="hint">텔레그램으로 Astra와 대화하고 싶다면 BotFather에서 봇을 만들고 토큰을 여기에 저장하세요. Astra의 다른 기능에는 영향이 없습니다.</p>
|
||||
|
||||
<div class="row">
|
||||
<label for="tgToken">Bot Token</label>
|
||||
<div class="input-group">
|
||||
<input id="tgToken" type="password" placeholder="123456789:AA..." autocomplete="off" spellcheck="false" />
|
||||
<button id="tgSaveToken">저장</button>
|
||||
<button id="tgClearToken" class="ghost">삭제</button>
|
||||
</div>
|
||||
<small id="tgTokenStatus" class="status"></small>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<button id="tgTest">연결 테스트</button>
|
||||
<span id="tgBotName" class="status-inline"></span>
|
||||
</div>
|
||||
|
||||
<div class="row toggle">
|
||||
<label><input id="tgEnabled" type="checkbox"> 봇 활성화 (체크하면 폴링 시작)</label>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<button id="tgEnroll">내 채널 자동 등록</button>
|
||||
<button id="tgEnrollCancel" class="ghost" hidden>등록 취소</button>
|
||||
<small id="tgEnrollStatus" class="status"></small>
|
||||
</div>
|
||||
|
||||
<div class="row" id="tgChatList">
|
||||
<label>허용된 채널 IDs</label>
|
||||
<ul id="tgChatIds" class="chips"></ul>
|
||||
<small class="hint">목록이 비어 있으면 누구나 봇에 메시지를 보낼 수 있습니다 (자동 등록을 한 번 하시는 것을 권장).</small>
|
||||
</div>
|
||||
|
||||
<div id="tgFeedback" class="feedback" hidden></div>
|
||||
<div id="tgError" class="error" hidden></div>
|
||||
</section>
|
||||
|
||||
<!-- Advanced -->
|
||||
<section class="section" data-section="advanced">
|
||||
<h2>고급</h2>
|
||||
<p class="hint">대부분의 사용자는 건드릴 필요 없습니다.</p>
|
||||
<div class="row toggle">
|
||||
<label><input id="advDryRun" type="checkbox"> Dry Run (파일 변경 전 승인 요청)</label>
|
||||
</div>
|
||||
<div class="row toggle">
|
||||
<label><input id="advMulti" type="checkbox"> 멀티 에이전트 워크플로우 (Planner → Researcher → Writer)</label>
|
||||
</div>
|
||||
<div class="row">
|
||||
<label for="advAutoSteps">최대 자동 단계 (maxAutoSteps)</label>
|
||||
<div class="input-group narrow">
|
||||
<input id="advAutoSteps" type="number" min="1" step="1" />
|
||||
<button data-save="advanced.autoSteps">저장</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<label for="advCtxSize">최대 컨텍스트 (maxContextSize)</label>
|
||||
<div class="input-group narrow">
|
||||
<input id="advCtxSize" type="number" min="1000" step="1000" />
|
||||
<button data-save="advanced.ctxSize">저장</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<script src="__SCRIPT_URI__"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,270 @@
|
||||
(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' });
|
||||
})();
|
||||
+5
-1
@@ -311,8 +311,12 @@
|
||||
const _preferredModel = (_savedModel && msg.value.models.includes(_savedModel))
|
||||
? _savedModel
|
||||
: msg.value.selected;
|
||||
const _loadedSet = new Set(Array.isArray(msg.value.loadedModels) ? msg.value.loadedModels : []);
|
||||
msg.value.models.forEach(m => {
|
||||
const o = document.createElement('option'); o.value = m; o.innerText = m;
|
||||
const o = document.createElement('option');
|
||||
o.value = m;
|
||||
// ● = 현재 LM Studio 메모리에 로드된 모델 / ○ = 다운로드만 됨
|
||||
o.innerText = _loadedSet.has(m) ? `● ${m}` : m;
|
||||
if (m === _preferredModel) o.selected = true;
|
||||
modelSel.appendChild(o);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user