Files
connectai/media/settings-panel.html
T
g1nation 4153f640c2 feat: v2.2.83 → v2.2.91 — info prompt 강화 + 사용자 노출 설정 + 답변 포맷 정리
[v2.2.83] /youtube info 프롬프트 강화
- 비유 방향 보존 룰 (Hugging Face=자료실 같은 짝 뒤집기 방지)
- 신뢰도 라벨 4종 ([근거 명시] / [화자 주장] / [가정] / [정리자 추론])
- 타임스탬프 fail 룰 (인용·구간 요약 모두 mm:ss 필수)
- "정리자 노트" 별도 섹션으로 추론 격리

[v2.2.85] polishPersona self-check 5가지
- 정리·리뷰·요약 답변 출력 직전 머릿속 체크:
  (1) 사실 오류  (2) 없는 내용 추가  (3) 뉘앙스 유지
  (4) 중요도 비례  (5) 중복 제거

[v2.2.86] chunkedSwitchTokens 절대 임계값 게이트
- 입력 < 50k 토큰이면 키워드·길이 트리거 무시하고 단일 호출
- 큰 컨텍스트 모델(131k+)에서 chunked 과잉 발동 방지

[v2.2.87] MAX_SECTIONS 5→3 cap
- 총 호출 7회 → 5회 (outline + 3 section + polish)
- 사용자 피드백 "6+회는 과하다"

[v2.2.88] 이모지 사용 금지 룰
- polishPersona / directPersona / sectionPersona 모두 적용
- 사용자 피드백 "이모지는 시각 노이즈"

[v2.2.89] 사용자 노출 설정 두 항목
- chunkedMaxSections config 신규 (default 3, 1~10 clamp)
- MAX_SECTIONS_HARD_CEILING (10) 으로 안전망 격상
- Astra Settings 패널 "고급" 섹션에 두 슬라이더 노출

[v2.2.90] 가이드 문구 단순화
- "작은 모델은 낮추라" 문구 빼고 일관되게 50000 권장으로

[v2.2.91] 답변 포맷 가독성 fix
- persona 의 "TL;DR" 표현 전부 "한 줄 요약" 으로 단일화
- stripMarkdownFormatting 에 헤더 후 빈 줄 강제 삽입
  (marked.parse 가 라벨·본문을 별도 단락으로 인식 → 시각 분리)

[테스트] 400/400 통과 (resilience_stress + chunked flow + MAX_SECTIONS cap 등)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-24 14:12:56 +09:00

399 lines
22 KiB
HTML

<!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>
<!-- Datacollect -->
<section class="section" data-section="datacollect">
<h2>Datacollect (slash 명령)</h2>
<p class="hint">채팅에서 <code>/research</code> · <code>/benchmark</code> · <code>/youtube</code> 를 입력하면 Datacollect Bridge로 위임됩니다. Bridge는 Datacollect 프로젝트에서 <code>npm run bridge</code> 로 실행해야 합니다.</p>
<div class="row">
<label for="dcBridgeUrl">Bridge URL</label>
<div class="input-group">
<input id="dcBridgeUrl" type="text" placeholder="http://127.0.0.1:3002" spellcheck="false" />
<button data-save="datacollect.bridgeUrl">저장</button>
</div>
</div>
<div class="row">
<label for="dcSavePath">결과물 저장 폴더</label>
<div class="input-group">
<input id="dcSavePath" type="text" placeholder="(비우면 Bridge 기본 위치)" spellcheck="false" />
<button data-save="datacollect.savePath">저장</button>
</div>
<small class="hint">/benchmark 등의 결과 markdown 저장 위치. 비워두면 Bridge의 <code>WIKI_RAW_PATH</code> 환경변수가 결정합니다 (코드에 절대경로 하드코딩 없음). 특정 폴더로 저장하려면 절대경로를 입력하세요.</small>
</div>
<div class="row">
<label for="dcCrawlDepth">크롤 깊이 기본값</label>
<div class="input-group narrow">
<input id="dcCrawlDepth" type="number" min="0" max="3" step="1" />
<button data-save="datacollect.crawlDepth">저장</button>
</div>
<small class="hint">/benchmark 사이트맵 크롤 깊이. 0=루트만, 1=직속 자식, 2=손자, 3=깊은 크롤. 명령어에서 <code>depth=N</code> 으로 그때그때 덮어쓸 수 있습니다.</small>
</div>
<div class="row">
<label for="dcMaxPages">최대 페이지 기본값</label>
<div class="input-group narrow">
<input id="dcMaxPages" type="number" min="1" max="20" step="1" />
<button data-save="datacollect.maxPages">저장</button>
</div>
<small class="hint">/benchmark 스캔 최대 페이지 수. 명령어에서 <code>pages=N</code> 으로 덮어쓸 수 있습니다 (Bridge 상한 20).</small>
</div>
<div class="row">
<label for="dcSynthTemp">합성 Temperature</label>
<div class="input-group narrow">
<input id="dcSynthTemp" type="number" min="0" max="2" step="0.05" />
<button data-save="datacollect.synthesisTemperature">저장</button>
</div>
<small class="hint">/benchmark LLM 4-렌즈 합성의 temperature. 낮을수록(0.1) 환각·깨진 문자가 줄고 결정적입니다. 기본 0.1 권장.</small>
</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>두뇌 (지식 폴더)</h2>
<p class="hint">현재 활성 두뇌 프로필 정보입니다. 추가·수정은 사이드바의 [변경 ▾ → 두뇌] 또는 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>
<!-- Google (Calendar + Sheets) -->
<section class="section" data-section="google">
<h2>Google (Calendar · Sheets)</h2>
<p class="hint">회의록·할일을 Google Calendar 에 자동 등록하고 Sheets 를 읽고 쓰려면 OAuth 가 필요합니다. <a href="https://console.cloud.google.com/apis/credentials" target="_blank">Google Cloud Console</a> 에서 Desktop OAuth Client 만들고 Client ID/Secret 을 아래에 붙여넣으세요.</p>
<div class="row">
<label>현재 연결 상태</label>
<div class="readout" id="googleConnStatus"></div>
</div>
<div class="row">
<label for="gClientId">OAuth Client ID</label>
<div class="input-group">
<input id="gClientId" type="text" placeholder="xxxxxxxx.apps.googleusercontent.com" autocomplete="off" spellcheck="false" />
<button data-save="google.clientId">저장</button>
</div>
</div>
<div class="row">
<label for="gClientSecret">OAuth Client Secret</label>
<div class="input-group">
<input id="gClientSecret" type="password" placeholder="GOCSPX-..." autocomplete="off" spellcheck="false" />
<button data-save="google.clientSecret">저장</button>
</div>
<small class="hint">Desktop OAuth client 의 secret 은 Google 가이드상 비공개 아님. 그래도 Settings Sync 에는 포함되지 않습니다 (machine scope).</small>
</div>
<div class="row">
<button id="googleConnect">OAuth 연결 / 재연결</button>
<button id="googleDisconnect" class="ghost">연결 해제</button>
<span id="googleConnStatusInline" class="status-inline"></span>
</div>
<div class="row">
<label for="gCalendarId">Calendar ID</label>
<div class="input-group">
<input id="gCalendarId" type="text" placeholder="primary" autocomplete="off" spellcheck="false" />
<button data-save="google.calendarId">저장</button>
</div>
<small class="hint">기본 'primary' (본인 메인 캘린더). 다른 캘린더 ID 입력 가능.</small>
</div>
<div class="row">
<label for="gDefaultDur">기본 일정 길이 (분)</label>
<div class="input-group narrow">
<input id="gDefaultDur" type="number" min="5" max="720" step="5" />
<button data-save="google.defaultEventDurationMinutes">저장</button>
</div>
<small class="hint">duration / end 안 지정된 일정 생성 시 사용. Default 60.</small>
</div>
<h3 style="margin-top:18px;font-size:13px;color:var(--muted)">iCal 읽기 (선택 사항)</h3>
<p class="hint">OAuth 없이 비공개 iCal URL 로 일정만 읽고 싶을 때. OAuth 연결돼 있으면 비워둬도 됩니다.</p>
<div class="row">
<label for="gIcalUrl">iCal URL</label>
<div class="input-group">
<input id="gIcalUrl" type="password" placeholder="https://calendar.google.com/calendar/ical/.../basic.ics" autocomplete="off" spellcheck="false" />
<button data-save="google.icalUrl">저장</button>
</div>
<small class="hint">URL 자체가 capability 토큰이라 password 처리. Settings Sync 에 안 포함됨.</small>
</div>
<div class="row">
<label for="gIcalDays">iCal 미리 가져올 일수</label>
<div class="input-group narrow">
<input id="gIcalDays" type="number" min="1" max="90" step="1" />
<button data-save="google.icalDaysAhead">저장</button>
</div>
</div>
<div class="row">
<button id="googleIcalRefresh">지금 iCal 새로고침</button>
<span id="googleIcalStatus" class="status-inline"></span>
</div>
<div id="googleFeedback" class="feedback" hidden></div>
<div id="googleError" class="error" hidden></div>
</section>
<!-- Cloud LLM Providers -->
<section class="section" data-section="providers">
<h2>Cloud LLM Providers</h2>
<p class="hint">Ollama / LM Studio 로컬 외에 cloud API 를 붙여서 모델 선택지를 확장. API key 는 모두 Secret Storage 에 저장 (settings.json 침범 X). 사이드바 모델 dropdown 에서 활성 provider 의 모델이 함께 표시됩니다.</p>
<!-- OpenRouter -->
<h3 style="margin-top:6px;font-size:13px;color:var(--text)">OpenRouter</h3>
<p class="hint">100+ 모델 (Claude / Gemini / GPT / Llama 전부) 을 단일 API 로. <a href="https://openrouter.ai/keys" target="_blank">openrouter.ai/keys</a> 에서 API key 발급.</p>
<div class="row toggle">
<label><input id="prOpenrouterEnabled" type="checkbox"> OpenRouter 활성화</label>
</div>
<div class="row">
<label for="prOpenrouterKey">API Key</label>
<div class="input-group">
<input id="prOpenrouterKey" type="password" placeholder="sk-or-..." autocomplete="off" spellcheck="false" />
<button data-save="providers.openrouter.apiKey">저장</button>
</div>
</div>
<div class="row">
<label for="prOpenrouterDefault">기본 모델 (선택)</label>
<div class="input-group">
<input id="prOpenrouterDefault" type="text" placeholder="anthropic/claude-3.5-sonnet" autocomplete="off" />
<button data-save="providers.openrouter.defaultModel">저장</button>
</div>
</div>
<!-- Anthropic -->
<h3 style="margin-top:18px;font-size:13px;color:var(--text)">Anthropic Claude (직통)</h3>
<p class="hint">Anthropic 직접 API — prompt caching 등 native 기능 활용 가능. <a href="https://console.anthropic.com/settings/keys" target="_blank">console.anthropic.com/settings/keys</a> 에서 API key 발급.</p>
<div class="row toggle">
<label><input id="prAnthropicEnabled" type="checkbox"> Anthropic 활성화</label>
</div>
<div class="row">
<label for="prAnthropicKey">API Key</label>
<div class="input-group">
<input id="prAnthropicKey" type="password" placeholder="sk-ant-..." autocomplete="off" spellcheck="false" />
<button data-save="providers.anthropic.apiKey">저장</button>
</div>
</div>
<div class="row">
<label for="prAnthropicDefault">기본 모델</label>
<div class="input-group">
<input id="prAnthropicDefault" type="text" placeholder="claude-3-5-sonnet-20241022" autocomplete="off" />
<button data-save="providers.anthropic.defaultModel">저장</button>
</div>
</div>
<!-- Gemini -->
<h3 style="margin-top:18px;font-size:13px;color:var(--text)">Google Gemini (직통)</h3>
<p class="hint">1M context (gemini-1.5-pro), 무료 tier 사용 가능. <a href="https://aistudio.google.com/app/apikey" target="_blank">aistudio.google.com/app/apikey</a> 에서 발급.</p>
<div class="row toggle">
<label><input id="prGeminiEnabled" type="checkbox"> Gemini 활성화</label>
</div>
<div class="row">
<label for="prGeminiKey">API Key</label>
<div class="input-group">
<input id="prGeminiKey" type="password" placeholder="AIzaSy..." autocomplete="off" spellcheck="false" />
<button data-save="providers.gemini.apiKey">저장</button>
</div>
</div>
<div class="row">
<label for="prGeminiDefault">기본 모델</label>
<div class="input-group">
<input id="prGeminiDefault" type="text" placeholder="gemini-2.0-flash-exp" autocomplete="off" />
<button data-save="providers.gemini.defaultModel">저장</button>
</div>
</div>
<div id="providersFeedback" class="feedback" hidden></div>
<div id="providersError" class="error" hidden></div>
</section>
<!-- Devil Agent (도현) -->
<section class="section" data-section="devilAgent">
<h2>🎭 Devil's Advocate (도현)</h2>
<p class="hint">매 답변 직후 별도 LLM 호출로 *비판적 sparring partner* 가 한 문단 반박. 사용자의 사고를 능동적 방어로 전환. 같은 모델 재사용 (~10-15% 추가 비용).</p>
<div class="row toggle">
<label><input id="devilEnabled" type="checkbox"> 도현 활성화 — 답변마다 반박 카드</label>
</div>
<div class="row">
<small class="hint">규칙: 한 답변당 약점 1개만, 통계·수치 인용 금지 (환각 차단), 끝에 우려+검증 방법 명시. 명령 팔레트 <code>Astra: Toggle Devil Agent 🎭</code> 로도 토글.</small>
</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>
<div class="row">
<label for="advChatTemp">채팅 Temperature</label>
<div class="input-group narrow">
<input id="advChatTemp" type="number" min="0" max="2" step="0.05" />
<button data-save="advanced.chatTemperature">저장</button>
</div>
<small class="hint">채팅 응답 생성의 temperature. 낮을수록(0.2~0.3) 한국어 오타·깨진 토큰이 줄고 안정적입니다. 기본 0.3 권장.</small>
</div>
<div class="row">
<label for="advChunkedSwitch">Chunked 진입 토큰 임계값 (chunkedSwitchTokens)</label>
<div class="input-group narrow">
<input id="advChunkedSwitch" type="number" min="1000" step="1000" />
<button data-save="advanced.chunkedSwitchTokens">저장</button>
</div>
<small class="hint">입력 prompt 가 이 토큰 수 *미만* 이면 chunked 파이프라인 발동 안 함 — 단일 호출로 답변. 기본 50000 권장 (대부분의 모델에 적합). 매우 작은 모델로 큰 입력 처리 시 OOM 가능성 있으면 이 값을 낮추면 됨.</small>
</div>
<div class="row">
<label for="advChunkedMax">Chunked 최대 섹션 수 (chunkedMaxSections)</label>
<div class="input-group narrow">
<input id="advChunkedMax" type="number" min="1" max="10" step="1" />
<button data-save="advanced.chunkedMaxSections">저장</button>
</div>
<small class="hint">Chunked 가 답변을 쪼갤 수 있는 최대 섹션 수. 실제 LLM 호출 = `2 + N` 회 (outline 1 + section N + polish 1). 기본 3 (총 5회). 빨리 받고 싶으면 2 (총 4회), 답변을 더 세분화하려면 5 (총 7회).</small>
</div>
</section>
</main>
<script src="__SCRIPT_URI__"></script>
</body>
</html>