이미지/동영상/사진편집/이모티콘세트 탭과 ComfyUI 탭 양쪽에 AI 프롬프트 도우미를 붙여, 한국어로 설명하면 SDXL/FLUX/LTX용 영어 프롬프트를 만들어줌. 생성 중에는 예상 소요시간 기준 진행률 바를 표시. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2015 lines
154 KiB
HTML
2015 lines
154 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>HomeClaw</title>
|
||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||
<link href="https://fonts.googleapis.com/css2?family=Nanum+Gothic:wght@400;700;800&display=swap" rel="stylesheet">
|
||
<link rel="icon" type="image/png" sizes="64x64" href="/homeclaw_logo.png">
|
||
<link rel="apple-touch-icon" sizes="128x128" href="/homeclaw_logo.png">
|
||
<link rel="stylesheet" href="/vendor/codemirror/lib/codemirror.css">
|
||
<script src="/vendor/codemirror/lib/codemirror.js"></script>
|
||
<script src="/vendor/codemirror/mode/xml/xml.js"></script>
|
||
<script src="/vendor/codemirror/mode/css/css.js"></script>
|
||
<script src="/vendor/codemirror/mode/javascript/javascript.js"></script>
|
||
<script src="/vendor/codemirror/mode/htmlmixed/htmlmixed.js"></script>
|
||
<script src="/vendor/codemirror/mode/python/python.js"></script>
|
||
<script src="/vendor/codemirror/mode/markdown/markdown.js"></script>
|
||
<link rel="stylesheet" href="/vendor/xterm.css">
|
||
<script src="/vendor/xterm.js"></script>
|
||
<script src="/vendor/addon-fit.js"></script>
|
||
<script src="/vendor/addon-web-links.js"></script>
|
||
<link rel="stylesheet" href="/vendor/katex/katex.min.css">
|
||
<script src="/vendor/katex/katex.min.js"></script>
|
||
<script src="/vendor/katex/auto-render.min.js"></script>
|
||
<script src="/vendor/katex/mhchem.min.js"></script>
|
||
<script src="/vendor/smiles-drawer/smiles-drawer.min.js"></script>
|
||
<link rel="stylesheet" href="../css/styles.css">
|
||
|
||
<script src="/vendor/tone-midi.js"></script>
|
||
<script src="https://cdn.jsdelivr.net/combine/npm/tone@14,npm/@magenta/music@1.23.1/es6/core.js,npm/html-midi-player@1.6.0" defer></script>
|
||
<script src="/vendor/vs/loader.js"></script>
|
||
<script>
|
||
// Hide AMD from deferred UMD libs (tone, @magenta/music, html-midi-player) so they
|
||
// don't call Monaco's anonymous define() — that would cause "Can only have one
|
||
// anonymous define call per script". Restore define.amd on DOMContentLoaded (after
|
||
// deferred scripts have run as globals) so Monaco's lazy modules like
|
||
// vs/base/common/marked/marked can register themselves via AMD as expected.
|
||
var _savedDefineAmd = window.define ? window.define.amd : undefined;
|
||
if (window.define) window.define.amd = false;
|
||
|
||
document.addEventListener('DOMContentLoaded', function() {
|
||
if (window.define && _savedDefineAmd !== undefined) window.define.amd = _savedDefineAmd;
|
||
}, { once: true });
|
||
|
||
// Resume Tone.js AudioContext on first user gesture (Chrome autoplay policy)
|
||
document.addEventListener('click', function() {
|
||
if (window.Tone && Tone.context && Tone.context.state !== 'running') Tone.context.resume();
|
||
}, { once: true });
|
||
</script>
|
||
</head>
|
||
<body>
|
||
|
||
<!-- HEADER -->
|
||
<header>
|
||
<div class="logo">
|
||
<img src="/homeclaw_logo.png" alt="🏠"> HomeClaw
|
||
</div>
|
||
|
||
<div class="mode-toggle" style="margin-left:8px">
|
||
<button class="mode-btn" onclick="window.open('/html/investor-app.html','_blank')" title="투자 어시스턴트">📈 투자</button>
|
||
<button class="mode-btn" onclick="window.open('/html/weather-app.html','_blank')" title="날씨 어시스턴트">🌤️ 날씨</button>
|
||
<button class="mode-btn" onclick="window.open('/html/code.html','_blank')" title="코드 에디터">💻 코드</button>
|
||
<button class="mode-btn" onclick="window.open('/html/pptx-wizard.html','_blank')" title="슬라이드 제작">📊 슬라이드</button>
|
||
<button class="mode-btn" onclick="window.open('/html/language-app.html','_blank')" title="우즈베크어 학습">🇺🇿 언어</button>
|
||
<button class="mode-btn" onclick="window.open('/html/music-app.html','_blank')" title="음악 스튜디오">🎵 음악</button>
|
||
<button class="mode-btn" onclick="window.open('/html/studio-app.html','_blank')" title="이미지·동영상 생성">🎨 스튜디오</button>
|
||
<button class="mode-btn" onclick="window.open('/html/accountant-app.html','_blank')" title="회계 어시스턴트">📊 회계</button>
|
||
<button class="mode-btn" onclick="window.open('/html/mind-app.html','_blank')" title="마음 상담실">🧠 상담</button>
|
||
<button class="mode-btn" onclick="window.open('/html/lawyer-app.html','_blank')" title="법률 어시스턴트">⚖️ 법률</button>
|
||
<button class="mode-btn" onclick="window.open('/html/detective-app.html','_blank')" title="탐정 어시스턴트">🔍 탐정</button>
|
||
<button class="mode-btn" onclick="window.open('/html/traffic-app.html','_blank')" title="교통 CCTV">🚦 교통</button>
|
||
<button class="mode-btn" onclick="window.open('/html/nvr-app.html','_blank')" title="홈 CCTV">📹 홈CCTV</button>
|
||
<button class="mode-btn" onclick="window.open('/dental-agent.html?t='+Date.now(),'_blank')" title="치과 사전 이미지 에이전트">🦷 치과</button>
|
||
<button class="mode-btn" onclick="window.open('/html/writer-app.html','_blank')" title="작가 어시스턴트">✍️ 작가</button>
|
||
</div>
|
||
|
||
<div class="status-bar">
|
||
<button
|
||
id="theme-toggle"
|
||
class="icon-btn"
|
||
data-theme-state="light"
|
||
onclick="toggleTheme()"
|
||
title="다크 모드로 전환"
|
||
aria-label="다크 모드 전환"
|
||
>
|
||
<svg class="theme-icon sun" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
|
||
<circle cx="12" cy="12" r="4"></circle>
|
||
<path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M4.93 19.07l1.41-1.41M17.66 6.34l1.41-1.41"></path>
|
||
</svg>
|
||
<svg class="theme-icon moon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
|
||
<path d="M21 12.79A9 9 0 1 1 11.21 3c-.04.25-.06.5-.06.76a8 8 0 0 0 9.85 7.84z"></path>
|
||
</svg>
|
||
</button>
|
||
<!-- User info + logout (hidden until auth check confirms logged-in user) -->
|
||
<div id="user-info-pill" style="display:none;align-items:center;gap:6px;background:var(--panel);border:1px solid var(--line);border-radius:var(--radius-sm);padding:4px 10px 4px 8px;font-size:12px;color:var(--muted)">
|
||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="8" r="4"/><path d="M4 20c0-4 3.6-7 8-7s8 3 8 7"/></svg>
|
||
<span id="user-info-name" style="font-weight:600;color:var(--text);max-width:90px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap"></span>
|
||
<button onclick="logout()" title="로그아웃" style="display:flex;align-items:center;gap:2px;background:none;border:none;cursor:pointer;padding:1px 2px;border-radius:4px;font-size:9px;color:var(--muted)" onmouseover="this.style.background='var(--line)';this.style.color='var(--text)'" onmouseout="this.style.background='none';this.style.color='var(--muted)'">
|
||
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/><polyline points="16 17 21 12 16 7"/><line x1="21" y1="12" x2="9" y2="12"/></svg>
|
||
</button>
|
||
</div>
|
||
<button onclick="openSettings()" title="설정" class="icon-btn">
|
||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg>
|
||
</button>
|
||
</div>
|
||
</header>
|
||
|
||
<!-- SIDEBAR -->
|
||
<aside>
|
||
<button id="mob-menu-close" onclick="mobTab('chat')" style="display:none;position:absolute;top:12px;right:12px;z-index:10;border:1px solid var(--line);background:var(--panel-2);border-radius:999px;padding:6px 14px;font-size:13px;font-weight:700;color:var(--muted);cursor:pointer;font-family:'Manrope',sans-serif">✕ 닫기</button>
|
||
<div id="sidebar-tabs">
|
||
<button class="sidebar-tab active" id="tab-jobs" onclick="setSidebarTab('jobs')">세션</button>
|
||
<button class="sidebar-tab" id="tab-schedule" onclick="setSidebarTab('schedule')">일정</button>
|
||
<button class="sidebar-tab" id="tab-skills" onclick="setSidebarTab('skills')">스킬</button>
|
||
<button class="sidebar-tab" id="tab-bgtasks" onclick="setSidebarTab('bgtasks')">작업</button>
|
||
</div>
|
||
|
||
<!-- SESSIONS TAB -->
|
||
<div id="sidebar-jobs">
|
||
<div id="jobs-list" style="flex:1;min-height:0;overflow-y:auto">
|
||
<div class="empty-state">아직 대화가 없습니다.<br>새 대화를 시작해 보세요.</div>
|
||
</div>
|
||
<!-- 새 채팅 + 편집 버튼 (하단) -->
|
||
<div class="sidebar-section" style="flex-shrink:0;border-top:1px solid var(--line);border-bottom:none">
|
||
<span></span>
|
||
<div style="display:flex;align-items:center;gap:8px">
|
||
<button class="sidebar-action" onclick="newChatSession()">+ 새 채팅</button>
|
||
<button class="sidebar-action" id="sessions-edit-btn" onclick="toggleSessionsEditMode()">편집</button>
|
||
</div>
|
||
</div>
|
||
<!-- 모델명 (한 줄) -->
|
||
<div id="model-pill" onclick="openSettings('models')" title="모델 변경하려면 클릭"
|
||
style="cursor:pointer;flex-shrink:0;display:flex;align-items:center;gap:8px;padding:9px 14px;border-top:1px solid var(--line);background:var(--panel-2);font-size:13px;font-weight:800;color:var(--text)">
|
||
<span style="font-size:11px;color:var(--muted);font-weight:700">모델</span>
|
||
<span id="current-provider-badge" style="font-size:11px;font-weight:800;color:var(--muted)"></span>
|
||
<span id="current-model" style="overflow:hidden;text-overflow:ellipsis;white-space:nowrap">-</span>
|
||
<span id="google-usage-badge" title="Gemini 무료 티어 일일 요청 한도(대략치, 예고없이 바뀔 수 있음)" style="display:none;font-size:10px;font-weight:700;font-family:'IBM Plex Mono',monospace;color:var(--muted);padding:2px 6px;border-radius:6px;background:var(--panel);flex-shrink:0"></span>
|
||
<span class="status-pill" style="margin-left:auto;flex-shrink:0;padding:4px 9px;font-size:11px">
|
||
<span class="dot" id="ollama-dot"></span>
|
||
<span id="ollama-status" style="font-weight:700">확인 중...</span>
|
||
</span>
|
||
</div>
|
||
<!-- 리소스 바 -->
|
||
<div id="system-pill" style="flex-shrink:0;display:flex;flex-direction:column;gap:5px;padding:9px 14px;border-top:1px solid var(--line);background:var(--panel-2)">
|
||
<div style="display:flex;align-items:center;gap:8px">
|
||
<span style="font-size:11px;font-weight:700;color:var(--muted);width:32px;flex-shrink:0">CPU</span>
|
||
<div class="sys-meter-track"><i id="sb-sys-cpu-meter"></i></div>
|
||
<span id="sb-sys-cpu" style="font-size:11px;font-weight:700;font-family:'IBM Plex Mono',monospace;color:var(--text);width:36px;text-align:right;flex-shrink:0">--%</span>
|
||
</div>
|
||
<div style="display:flex;align-items:center;gap:8px">
|
||
<span style="font-size:11px;font-weight:700;color:var(--muted);width:32px;flex-shrink:0">RAM</span>
|
||
<div class="sys-meter-track"><i id="sb-sys-ram-meter"></i></div>
|
||
<span id="sb-sys-ram" style="font-size:11px;font-weight:700;font-family:'IBM Plex Mono',monospace;color:var(--text);width:36px;text-align:right;flex-shrink:0">--%</span>
|
||
</div>
|
||
<div id="sb-gpu0-row" style="display:flex;align-items:center;gap:8px">
|
||
<span style="font-size:11px;font-weight:700;color:var(--muted);width:32px;flex-shrink:0">GPU0</span>
|
||
<div class="sys-meter-track"><i id="sb-sys-vram0-meter"></i></div>
|
||
<span id="sb-sys-vram0" style="font-size:11px;font-weight:700;font-family:'IBM Plex Mono',monospace;color:var(--text);width:36px;text-align:right;flex-shrink:0">--%</span>
|
||
</div>
|
||
<div id="sb-gpu1-row" style="display:none;align-items:center;gap:8px">
|
||
<span style="font-size:11px;font-weight:700;color:var(--muted);width:32px;flex-shrink:0">GPU1</span>
|
||
<div class="sys-meter-track"><i id="sb-sys-vram1-meter"></i></div>
|
||
<span id="sb-sys-vram1" style="font-size:11px;font-weight:700;font-family:'IBM Plex Mono',monospace;color:var(--text);width:36px;text-align:right;flex-shrink:0">--%</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- SKILLS TAB -->
|
||
<div id="sidebar-skills">
|
||
<div class="sidebar-section" style="border-top:none">
|
||
<span>Skills</span>
|
||
<div style="display:flex;gap:6px">
|
||
<button class="sidebar-action" onclick="openCreateSkillModal()">+ Create</button>
|
||
<button class="sidebar-action" onclick="setSidebarTab('jobs')" title="세션 패널로 돌아가기">✕</button>
|
||
</div>
|
||
</div>
|
||
<div id="skills-user-selector" style="display:none;padding:5px 10px 6px;border-bottom:1px solid var(--line);background:var(--panel-2)">
|
||
<label style="font-size:10px;color:var(--muted);display:block;margin-bottom:3px">대상 유저</label>
|
||
<select id="skills-target-user" onchange="onSkillsUserChange()" style="width:100%;font-size:12px;padding:3px 6px;background:var(--bg2);color:var(--text);border:1px solid var(--line);border-radius:6px">
|
||
<option value="">글로벌 기본값</option>
|
||
</select>
|
||
</div>
|
||
<div id="skills-list" style="flex:1;min-height:0;overflow-y:auto;padding:10px">
|
||
<div class="empty-state" style="padding:16px">스킬 로딩 중...</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- SCHEDULE TAB -->
|
||
<div id="sidebar-schedule" style="display:none;flex-direction:column;flex:1;min-height:0;overflow:hidden">
|
||
<div class="sidebar-section" style="border-top:none;flex-shrink:0">
|
||
<div style="display:flex;align-items:center;gap:8px">
|
||
<span style="font-weight:700">일정</span>
|
||
<span id="schedule-count" style="font-size:11px;background:#eaf2ff;color:#0d4faf;border-radius:999px;padding:2px 8px;font-weight:700">0</span>
|
||
</div>
|
||
<div style="display:flex;gap:6px">
|
||
<button class="sidebar-action" onclick="openScheduleCreateModal()">+ 새 일정</button>
|
||
<button class="sidebar-action" onclick="refreshSchedules()" title="새로고침">↺</button>
|
||
</div>
|
||
</div>
|
||
<div id="schedule-list" style="flex:1;min-height:0;overflow-y:auto;padding:10px;display:flex;flex-direction:column;gap:8px">
|
||
<div class="empty-state" style="text-align:center;color:var(--muted);padding:40px 20px">No schedules yet.<br><strong>+ 새 일정</strong>으로 시작하세요.</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- CODE SIDEBAR -->
|
||
|
||
<!-- PPTX SIDEBAR -->
|
||
<div id="sidebar-pptx" style="display:none;flex-direction:column;flex:1;min-height:0;overflow:hidden">
|
||
<!-- inner tabs -->
|
||
<div style="display:flex;border-bottom:1px solid var(--line);flex-shrink:0;background:var(--panel-2)">
|
||
<button class="code-sb-tab active" id="psb-tab-setup" onclick="setPptxSbTab('setup')">설정</button>
|
||
<button class="code-sb-tab" id="psb-tab-images" onclick="setPptxSbTab('images')">이미지 <span id="psb-img-count"></span></button>
|
||
<button class="code-sb-tab" id="psb-tab-outline" onclick="setPptxSbTab('outline')">아웃라인</button>
|
||
</div>
|
||
|
||
<!-- 설정 탭 -->
|
||
<div id="psb-setup" style="flex:1;overflow-y:auto;padding:14px 12px;display:flex;flex-direction:column;gap:10px">
|
||
<div>
|
||
<div style="font-size:10px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:5px">주제</div>
|
||
<input type="text" id="psb-topic" placeholder="예: 상악동 거상술 최신 동향"
|
||
style="width:100%;box-sizing:border-box;font-size:12px;padding:6px 8px;background:var(--panel-2);color:var(--text);border:1px solid var(--line);border-radius:8px">
|
||
</div>
|
||
<div>
|
||
<div style="font-size:10px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:5px">PMCIDs <span style="font-weight:400;text-transform:none">(줄바꿈 또는 쉼표)</span></div>
|
||
<textarea id="psb-pmcids" rows="4" placeholder="PMC8273047 PMC10340512"
|
||
style="width:100%;box-sizing:border-box;font-size:12px;padding:6px 8px;background:var(--panel-2);color:var(--text);border:1px solid var(--line);border-radius:8px;font-family:'IBM Plex Mono',monospace;resize:vertical"></textarea>
|
||
</div>
|
||
<div>
|
||
<div style="font-size:10px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:5px">템플릿</div>
|
||
<select id="psb-template"
|
||
style="width:100%;font-size:12px;padding:6px 8px;background:var(--panel-2);color:var(--text);border:1px solid var(--line);border-radius:8px;cursor:pointer">
|
||
<option value="dark">dark</option>
|
||
<option value="business">business</option>
|
||
<option value="minimal">minimal</option>
|
||
<option value="creative">creative</option>
|
||
<option value="pastel">pastel</option>
|
||
<option value="warm">warm</option>
|
||
</select>
|
||
</div>
|
||
<div>
|
||
<div style="font-size:10px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:5px">슬라이드 수 (선택)</div>
|
||
<input type="number" id="psb-slide-count" min="5" max="30" placeholder="자동"
|
||
style="width:100%;box-sizing:border-box;font-size:12px;padding:6px 8px;background:var(--panel-2);color:var(--text);border:1px solid var(--line);border-radius:8px">
|
||
</div>
|
||
<button onclick="pptxStartWorkflow()" id="psb-start-btn"
|
||
style="padding:9px;background:var(--brand);color:#fff;border:none;border-radius:8px;cursor:pointer;font-size:13px;font-weight:700">
|
||
▶ 시작
|
||
</button>
|
||
<div id="psb-status" style="font-size:11px;color:var(--muted);min-height:16px"></div>
|
||
</div>
|
||
|
||
<!-- 이미지 탭 -->
|
||
<div id="psb-images" style="display:none;flex:1;overflow-y:auto;padding:10px">
|
||
<div id="psb-image-grid" style="display:grid;grid-template-columns:repeat(2,1fr);gap:5px">
|
||
<div style="grid-column:1/-1;color:var(--muted);font-size:11px;padding:8px;text-align:center">이미지 추출 후 표시됩니다</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 아웃라인 탭 -->
|
||
<div id="psb-outline" style="display:none;flex-direction:column;flex:1;min-height:0">
|
||
<div id="psb-outline-cards" style="flex:1;overflow-y:auto;padding:10px;display:flex;flex-direction:column;gap:5px">
|
||
<div style="color:var(--muted);font-size:11px;padding:8px;text-align:center">아웃라인 생성 후 표시됩니다</div>
|
||
</div>
|
||
<div style="padding:8px 10px;border-top:1px solid var(--line);flex-shrink:0;display:flex;gap:6px">
|
||
<button onclick="pptxGenerate()" id="psb-gen-btn" disabled
|
||
style="flex:1;padding:8px;background:var(--brand);color:#fff;border:none;border-radius:8px;cursor:pointer;font-size:12px;font-weight:700;opacity:.5">
|
||
✅ 슬라이드 생성
|
||
</button>
|
||
<button onclick="pptxResetWorkflow()" title="초기화"
|
||
style="padding:8px 10px;background:var(--panel-2);color:var(--muted);border:1px solid var(--line);border-radius:8px;cursor:pointer;font-size:12px">
|
||
↺
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- SERVER LOG PANEL -->
|
||
<div id="sidebar-log-panel">
|
||
<div id="sidebar-log-header" onclick="toggleLogPanel()" style="cursor:pointer">
|
||
<span>서버 로그</span>
|
||
<div style="display:flex;gap:4px;align-items:center" onclick="event.stopPropagation()">
|
||
<button id="log-scroll-lock-btn" onclick="toggleLogScrollLock()" title="자동 스크롤 고정">↓</button>
|
||
<button onclick="clearLogPanel()" title="지우기">✕</button>
|
||
<button id="log-collapse-btn" onclick="toggleLogPanel()" title="접기/펼치기" style="font-size:13px">▾</button>
|
||
</div>
|
||
</div>
|
||
<div id="sidebar-log-filters" onclick="event.stopPropagation()" style="display:flex;gap:3px;padding:4px 8px;border-bottom:1px solid var(--line);flex-shrink:0;flex-wrap:wrap">
|
||
<button class="log-filter-btn active" data-level="log" onclick="toggleLogFilter('log')">INFO</button>
|
||
<button class="log-filter-btn active" data-level="warn" onclick="toggleLogFilter('warn')">WARN</button>
|
||
<button class="log-filter-btn active" data-level="error" onclick="toggleLogFilter('error')">ERROR</button>
|
||
<button class="log-filter-btn log-filter-all active" onclick="setAllLogFilters(true)" style="margin-left:auto">전체</button>
|
||
<button class="log-filter-btn log-filter-none" onclick="setAllLogFilters(false)">없음</button>
|
||
</div>
|
||
<div id="sidebar-log-search" onclick="event.stopPropagation()" style="display:flex;align-items:center;gap:4px;padding:3px 8px;border-bottom:1px solid var(--line);flex-shrink:0">
|
||
<span style="font-size:10px;color:var(--muted);flex-shrink:0">🔍</span>
|
||
<input id="log-search-input" type="text" placeholder="검색..." oninput="onLogSearch(this.value)" onkeydown="if(event.key==='Escape'){this.value='';onLogSearch('');}"
|
||
style="flex:1;background:none;border:none;outline:none;font-size:11px;font-family:'IBM Plex Mono',monospace;color:var(--text);min-width:0">
|
||
<span id="log-search-count" style="font-size:10px;color:var(--muted);flex-shrink:0;display:none"></span>
|
||
<button id="log-search-clear" onclick="document.getElementById('log-search-input').value='';onLogSearch('');" title="지우기"
|
||
style="display:none;background:none;border:none;cursor:pointer;font-size:11px;color:var(--muted);padding:0 2px">✕</button>
|
||
</div>
|
||
<div id="sidebar-log-body"></div>
|
||
</div>
|
||
</aside>
|
||
|
||
<!-- MAIN -->
|
||
<main>
|
||
<!-- CHAT VIEW (default) -->
|
||
<div id="chat-view">
|
||
<div id="chat-messages">
|
||
<div class="chat-welcome" id="chat-welcome">
|
||
<div class="chat-welcome-icon"><img src="/homeclaw_logo.png" alt="🏠"></div>
|
||
<h2>HomeClaw</h2>
|
||
<p>로컬 모델과 직접 대화하세요. API 키도, 클라우드도 없이 — 오직 나와 모델만.</p>
|
||
<div class="hint">멀티스텝 에이전트 작업은 에이전트 모드로 전환하세요.</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Chat inline terminal -->
|
||
<div id="chat-terminal-panel">
|
||
<div id="chat-term-resize-handle"></div>
|
||
<div class="ct-bar">
|
||
<div id="chat-shell-tabs"></div>
|
||
<div style="display:flex;gap:2px;flex-shrink:0">
|
||
<button onclick="addChatTermShell()" title="새 터미널" style="background:none;border:none;cursor:pointer;color:#767676;font-size:18px;padding:0 6px;line-height:1;font-family:monospace">+</button>
|
||
<button onclick="toggleChatTerminal()" title="닫기" style="background:none;border:none;cursor:pointer;color:#767676;font-size:14px;padding:0 6px;line-height:1">×</button>
|
||
</div>
|
||
</div>
|
||
<div id="chat-term-container"></div>
|
||
</div>
|
||
|
||
<div class="chat-input-area">
|
||
<div class="agent-toggle">
|
||
<span class="agent-toggle-label">모드:</span>
|
||
<label class="toggle-switch" title="에이전트 모드 전환 (AI에 도구 접근 허용)">
|
||
<input type="checkbox" id="agent-mode-toggle" onchange="updateAgentMode()">
|
||
<span class="toggle-track"></span>
|
||
</label>
|
||
<span class="agent-toggle-label" id="agent-mode-label">채팅</span>
|
||
<span class="agent-mode-badge" id="agent-mode-badge">AGENT</span>
|
||
<span style="color:var(--line);margin:0 6px;user-select:none">|</span>
|
||
<span class="agent-toggle-label">TTS</span>
|
||
<label class="toggle-switch" title="AI 응답 음성 읽기 켜기/끄기">
|
||
<input type="checkbox" id="tts-mode-toggle" onchange="updateTTSMode()">
|
||
<span class="toggle-track"></span>
|
||
</label>
|
||
<span style="color:var(--line);margin:0 6px;user-select:none">|</span>
|
||
<span class="agent-toggle-label">바로보내기</span>
|
||
<label class="toggle-switch" title="음성 인식 후 자동 전송">
|
||
<input type="checkbox" id="mic-autosend-toggle" onchange="updateMicAutosend()">
|
||
<span class="toggle-track"></span>
|
||
</label>
|
||
<div class="agent-toggle-right" style="display:flex;align-items:center;gap:6px">
|
||
<button class="quick-mode-btn" id="context-pin-btn" onclick="toggleContextPinMode()" title="메시지를 컨텍스트에 고정">
|
||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 17v5"/><path d="M9 10.76a2 2 0 0 1-1.11 1.79l-1.78.9A2 2 0 0 0 5 15.24V16a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-.76a2 2 0 0 0-1.11-1.79l-1.78-.9A2 2 0 0 1 15 10.76V7a1 1 0 0 1 1-1 2 2 0 0 0 0-4H8a2 2 0 0 0 0 4 1 1 0 0 1 1 1z"/></svg>
|
||
<span id="context-pin-label">컨텍스트</span>
|
||
</button>
|
||
<div class="quick-mode-popover" id="context-pin-popover" style="width:280px">
|
||
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:8px">
|
||
<div class="quick-mode-title" style="margin:0">컨텍스트에 메시지 고정</div>
|
||
<span id="context-pin-count" style="font-size:11px;color:var(--muted);font-weight:700">0/3 selected</span>
|
||
</div>
|
||
<div style="font-size:11px;color:var(--muted);margin-bottom:8px;line-height:1.5">채팅에서 메시지를 클릭하여 고정하세요. 고정된 메시지는 매 요청마다 컨텍스트에 재주입됩니다.</div>
|
||
<div id="context-pin-list" style="max-height:200px;overflow-y:auto;margin-bottom:8px"></div>
|
||
<div style="display:flex;justify-content:flex-end;gap:6px">
|
||
<button class="skill-mini-btn" onclick="cancelContextPin()" style="display:flex;align-items:center;gap:4px">
|
||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
|
||
취소
|
||
</button>
|
||
<button class="skill-mini-btn enabled" onclick="confirmContextPin()" style="display:flex;align-items:center;gap:4px">
|
||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><polyline points="20 6 9 17 4 12"/></svg>
|
||
Done
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<button class="quick-mode-btn" id="quick-mode-btn" onclick="toggleQuickModePopover()" title="웹 검색 / 사고 설정">
|
||
<span id="quick-mode-label">Verified</span>
|
||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="6 9 12 15 18 9"/></svg>
|
||
</button>
|
||
<div class="quick-mode-popover" id="quick-mode-popover">
|
||
<div class="quick-mode-title">웹 검색 엄격도</div>
|
||
<button class="quick-mode-opt" id="rigor-fast" onclick="setQuickSearchRigor('fast')">빠름</button>
|
||
<button class="quick-mode-opt" id="rigor-verified" onclick="setQuickSearchRigor('verified')">검증됨</button>
|
||
<button class="quick-mode-opt" id="rigor-strict" onclick="setQuickSearchRigor('strict')">엄격</button>
|
||
<div class="quick-mode-title" style="margin-top:8px">사고 노력</div>
|
||
<button class="quick-mode-opt" id="think-standard" onclick="setQuickThinkingEffort('standard')">기본</button>
|
||
<button class="quick-mode-opt" id="think-extended" onclick="setQuickThinkingEffort('extended')">확장</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="queued-prompts-panel" id="queued-prompts-panel">
|
||
<div class="queued-prompts-head">
|
||
<span id="queued-prompts-title">대기 중인 프롬프트</span>
|
||
<button class="queued-clear-btn" onclick="clearQueuedPrompts()">지우기</button>
|
||
</div>
|
||
<div class="queued-prompts-list" id="queued-prompts-list"></div>
|
||
</div>
|
||
<!-- 첨부 파일 카드 영역 (전송 전 표시) -->
|
||
<div id="staged-attachments" style="display:none;flex-wrap:wrap;gap:8px;padding:8px 12px 0;border-top:1px solid var(--line)"></div>
|
||
<div id="voice-call-bar" style="display:none;align-items:center;gap:8px;padding:8px 12px;border-top:1px solid var(--line);font-size:13px;color:var(--text-dim,#888)">
|
||
<span id="voice-call-status">통화 연결 중…</span>
|
||
<span id="voice-call-caption" style="flex:1;opacity:0.85;font-style:italic"></span>
|
||
<span aria-hidden="true">🔈</span>
|
||
<input type="range" id="voice-call-volume" min="0" max="1.5" step="0.05" value="1" oninput="voiceCallSetVolume(this.value)" title="통화 음량" style="width:70px" />
|
||
<button class="send-btn" onclick="voiceCallHangup()" title="통화 종료" style="font-size:14px;width:32px;height:32px;background:#c0392b" aria-label="통화 종료">✕</button>
|
||
</div>
|
||
<div class="chat-input-row">
|
||
<input type="file" id="file-upload" accept="image/*,.pdf,.txt,.md,.csv,.xlsx,.xls,.docx,.pptx,.mid,.midi" style="display:none" onchange="handleFileUpload(this)" />
|
||
<button class="send-btn" onclick="document.getElementById('file-upload').click()" title="파일 첨부 (이미지·PDF·Excel·txt 등)" style="font-size:18px;width:44px;height:44px" aria-label="파일 첨부">📎</button>
|
||
<button class="send-btn" id="mic-btn" onclick="toggleMicRecording()" title="음성 입력 (클릭: 시작/중지)" style="font-size:18px;width:44px;height:44px" aria-label="음성 입력">🎤</button>
|
||
<button class="send-btn" id="voice-call-btn" onclick="toggleVoiceCall()" title="실시간 음성 대화 (연속 대화 모드)" style="font-size:18px;width:44px;height:44px" aria-label="실시간 음성 대화">📞</button>
|
||
<textarea
|
||
id="chat-input"
|
||
placeholder="메시지를 입력하세요... (Enter로 전송, Shift+Enter로 줄바꿈)"
|
||
rows="1"
|
||
autocomplete="off"
|
||
></textarea>
|
||
<button class="send-btn" id="send-btn" onclick="handleSendStop()" title="Send">
|
||
<svg id="send-icon" width="15" height="15" viewBox="0 0 24 24" fill="currentColor" stroke="none"><polygon points="22 2 15 22 11 13 2 9"/></svg>
|
||
<svg id="stop-icon" width="13" height="13" viewBox="0 0 14 14" fill="currentColor" style="display:none"><rect x="2" y="2" width="10" height="10" rx="1.5"/></svg>
|
||
</button>
|
||
</div>
|
||
<div class="chat-hint">
|
||
<span id="chat-model-name">모델</span>과 대화 중 — <span id="chat-provider-name">HomeClaw</span> 경유.
|
||
<span id="queued-prompts-indicator" style="display:none;margin-left:8px;color:var(--brand);font-weight:700"></span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
</main>
|
||
|
||
<!-- Task Modal -->
|
||
<div id="task-modal" style="display:none"></div>
|
||
|
||
<!-- BACKGROUND TASKS KANBAN VIEW -->
|
||
<div id="bgtasks-view" style="display:none;flex-direction:column;gap:0;position:absolute;top:90px;bottom:12px;left:12px;right:12px;background:var(--bg);padding:16px;overflow:hidden;border:1px solid var(--line);border-radius:var(--radius-lg);box-shadow:var(--shadow-sm);z-index:5;">
|
||
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:16px;flex-shrink:0">
|
||
<div style="display:flex;align-items:center;gap:12px">
|
||
<span style="font-size:16px;font-weight:800;letter-spacing:-0.01em">백그라운드 작업</span>
|
||
<span id="bgt-count" style="font-size:11px;background:#eaf2ff;color:#0d4faf;border-radius:999px;padding:2px 10px;font-weight:700">0 tasks</span>
|
||
</div>
|
||
<div style="display:flex;gap:8px;align-items:center">
|
||
<button
|
||
id="bgt-heartbeat-label"
|
||
onclick="openBgtHeartbeatModal()"
|
||
style="border:1px solid var(--line);background:var(--panel);color:var(--muted);border-radius:8px;padding:5px 10px;font-size:11px;font-weight:600;cursor:pointer"
|
||
title="작업 하트비트 설정"
|
||
>? heartbeat: 10min</button>
|
||
<button onclick="openNewBgTaskModal()" style="border:1px solid var(--brand);background:var(--brand);color:#fff;border-radius:8px;padding:5px 14px;font-size:12px;font-weight:700;cursor:pointer">+ 태스크</button>
|
||
<button onclick="refreshBgTasks()" style="border:1px solid var(--line);background:var(--panel);color:var(--muted);border-radius:8px;padding:5px 12px;font-size:12px;font-weight:600;cursor:pointer">새로고침</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Kanban columns -->
|
||
<div id="bgt-board" style="display:flex;gap:12px;flex:1;min-height:0;overflow-x:auto;padding-bottom:8px">
|
||
<!-- Columns injected by renderBgTasks() -->
|
||
</div>
|
||
|
||
<!-- Task detail side panel -->
|
||
<div id="bgt-panel" style="display:none;position:absolute;top:0;right:0;width:480px;bottom:0;background:var(--panel);border-left:1px solid var(--line);flex-direction:column;overflow:hidden;z-index:10">
|
||
<div style="display:flex;align-items:center;justify-content:space-between;padding:14px 16px;border-bottom:1px solid var(--line);flex-shrink:0">
|
||
<span id="bgt-panel-title" style="font-size:14px;font-weight:800">작업 상세</span>
|
||
<div style="display:flex;gap:8px">
|
||
<button id="bgt-panel-pause" onclick="bgtPauseResume()" style="border:1px solid var(--line);background:var(--panel-2);border-radius:8px;padding:4px 12px;font-size:12px;font-weight:600;cursor:pointer;color:var(--muted)">일시정지</button>
|
||
<button onclick="closeBgtPanel()" style="border:0;background:none;font-size:18px;cursor:pointer;color:var(--muted);line-height:1">✕</button>
|
||
</div>
|
||
</div>
|
||
<div id="bgt-panel-body" style="flex:1;overflow-y:auto;padding:16px;display:flex;flex-direction:column;gap:14px">
|
||
<!-- Filled by openBgtPanel() -->
|
||
</div>
|
||
<!-- Persistent task chat input — always visible regardless of task status -->
|
||
<div id="bgt-chat-bar" style="flex-shrink:0;border-top:1px solid var(--line);padding:10px 12px;background:var(--panel);display:flex;flex-direction:column;gap:6px">
|
||
<div style="font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:0.05em;color:var(--muted)">에이전트와 채팅</div>
|
||
<div style="display:flex;gap:8px;align-items:flex-end">
|
||
<textarea id="bgt-chat-input" rows="2" placeholder="에이전트에게 메시지 전송... (Shift+Enter로 줄바꿈)" style="flex:1;resize:none;border:1px solid var(--line);border-radius:8px;padding:8px 10px;font-size:12px;font-family:inherit;background:var(--panel-2);color:var(--text);outline:none" onkeydown="if(event.key==='Enter'&&!event.shiftKey){event.preventDefault();bgtChatSend();}"></textarea>
|
||
<button onclick="bgtChatSend()" style="background:var(--brand);color:#fff;border:none;border-radius:8px;padding:8px 14px;font-size:12px;font-weight:700;cursor:pointer;flex-shrink:0;height:36px">전송</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Schedule Create/Edit Modal -->
|
||
<div id="schedule-modal" style="display:none;position:fixed;inset:0;background:rgba(10,20,40,0.35);z-index:9999;align-items:center;justify-content:center;padding:18px">
|
||
<div style="width:min(480px,96vw);max-height:90vh;overflow:auto;background:var(--panel);border:1px solid var(--line);border-radius:14px;box-shadow:var(--shadow-md)">
|
||
<div style="display:flex;align-items:center;justify-content:space-between;padding:14px 16px;border-bottom:1px solid var(--line);flex-shrink:0">
|
||
<div id="schedule-modal-title" style="font-weight:800">일정 생성</div>
|
||
<button onclick="closeScheduleModal()" style="border:0;background:none;font-size:18px;cursor:pointer;color:var(--muted);line-height:1">✕</button>
|
||
</div>
|
||
<div style="padding:16px;display:flex;flex-direction:column;gap:14px">
|
||
<div>
|
||
<label style="display:block;font-size:12px;font-weight:700;color:var(--text);margin-bottom:6px">Name</label>
|
||
<input id="schedule-name" type="text" placeholder="예: 매일 스탠드업" style="width:100%;border:1px solid var(--line);border-radius:8px;padding:8px 10px;font-size:13px;background:var(--panel-2)" />
|
||
</div>
|
||
<div>
|
||
<label style="display:block;font-size:12px;font-weight:700;color:var(--text);margin-bottom:6px">일정 패턴</label>
|
||
<div style="display:flex;gap:8px;margin-bottom:8px">
|
||
<input id="schedule-pattern" type="text" placeholder="예: daily at 09:00 또는 '0 9 * * *' cron" style="flex:1;border:1px solid var(--line);border-radius:8px;padding:8px 10px;font-size:13px;background:var(--panel-2)" />
|
||
<button onclick="parseSchedulePattern()" style="border:1px solid var(--brand);background:var(--brand);color:white;border-radius:8px;padding:6px 12px;font-size:12px;font-weight:600;cursor:pointer;white-space:nowrap">파싱</button>
|
||
</div>
|
||
<div id="schedule-pattern-preview" style="font-size:11px;color:var(--muted);padding:6px 8px;background:var(--panel-2);border-radius:6px;display:none"></div>
|
||
</div>
|
||
<div>
|
||
<label style="display:block;font-size:12px;font-weight:700;color:var(--text);margin-bottom:6px">시간대</label>
|
||
<select id="schedule-timezone" style="width:100%;border:1px solid var(--line);border-radius:8px;padding:8px 10px;font-size:13px;background:var(--panel-2)">
|
||
<option value="UTC">UTC</option>
|
||
<option value="America/New_York">America/New_York</option>
|
||
<option value="America/Chicago">America/Chicago</option>
|
||
<option value="America/Denver">America/Denver</option>
|
||
<option value="America/Los_Angeles">America/Los_Angeles</option>
|
||
<option value="Europe/London">Europe/London</option>
|
||
<option value="Europe/Paris">Europe/Paris</option>
|
||
<option value="Asia/Tokyo">Asia/Tokyo</option>
|
||
</select>
|
||
</div>
|
||
<div>
|
||
<label style="display:block;font-size:12px;font-weight:700;color:var(--text);margin-bottom:6px">프롬프트 / 작업</label>
|
||
<textarea id="schedule-prompt" placeholder="이 일정이 실행될 때 수행할 작업은 무엇인가요?" style="width:100%;border:1px solid var(--line);border-radius:8px;padding:8px 10px;font-size:13px;background:var(--panel-2);resize:vertical;min-height:80px;font-family:'IBM Plex Mono',monospace"></textarea>
|
||
</div>
|
||
<div>
|
||
<label style="display:block;font-size:12px;font-weight:700;color:var(--text);margin-bottom:6px">전송 채널</label>
|
||
<select id="schedule-channel" style="width:100%;border:1px solid var(--line);border-radius:8px;padding:8px 10px;font-size:13px;background:var(--panel-2)">
|
||
<option value="web">웹 (알림)</option>
|
||
<option value="telegram">텔레그램 (준비 중)</option>
|
||
<option value="discord">디스코드 (준비 중)</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
<div style="display:flex;justify-content:flex-end;gap:8px;padding:12px 16px;border-top:1px solid var(--line);flex-shrink:0">
|
||
<button class="btn btn-sm" style="background:var(--panel-2);color:var(--muted);border:1px solid var(--line)" onclick="closeScheduleModal()">취소</button>
|
||
<button class="btn btn-sm" id="schedule-save-btn" onclick="saveSchedule()">일정 생성</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- New Background Task Modal -->
|
||
<div id="bgt-new-task-modal" style="display:none;position:fixed;inset:0;background:rgba(10,20,40,0.35);z-index:9999;align-items:center;justify-content:center;padding:18px">
|
||
<div style="width:min(460px,96vw);background:var(--panel);border:1px solid var(--line);border-radius:14px;box-shadow:var(--shadow-md);display:flex;flex-direction:column">
|
||
<div style="display:flex;align-items:center;justify-content:space-between;padding:14px 16px;border-bottom:1px solid var(--line)">
|
||
<span style="font-weight:800">새 백그라운드 태스크</span>
|
||
<button onclick="closeNewBgTaskModal()" style="border:0;background:none;font-size:18px;cursor:pointer;color:var(--muted);line-height:1">✕</button>
|
||
</div>
|
||
<div style="padding:16px;display:flex;flex-direction:column;gap:12px">
|
||
<div>
|
||
<label style="display:block;font-size:12px;font-weight:700;color:var(--muted);margin-bottom:5px">태스크 제목</label>
|
||
<input id="bgt-new-title" type="text" placeholder="예: 오늘의 뉴스 요약" style="width:100%;box-sizing:border-box;border:1px solid var(--line);border-radius:8px;padding:8px 10px;font-size:13px;background:var(--panel-2);color:var(--text)" />
|
||
</div>
|
||
<div>
|
||
<label style="display:block;font-size:12px;font-weight:700;color:var(--muted);margin-bottom:5px">작업 내용</label>
|
||
<textarea id="bgt-new-prompt" rows="4" placeholder="에이전트가 수행할 작업을 입력하세요..." style="width:100%;box-sizing:border-box;border:1px solid var(--line);border-radius:8px;padding:8px 10px;font-size:13px;background:var(--panel-2);color:var(--text);resize:vertical;font-family:inherit" onkeydown="if(event.key==='Enter'&&event.ctrlKey)submitNewBgTask()"></textarea>
|
||
</div>
|
||
</div>
|
||
<div style="display:flex;justify-content:flex-end;gap:8px;padding:12px 16px;border-top:1px solid var(--line)">
|
||
<button onclick="closeNewBgTaskModal()" style="border:1px solid var(--line);background:var(--panel-2);color:var(--muted);border-radius:8px;padding:7px 16px;font-size:12px;font-weight:600;cursor:pointer">취소</button>
|
||
<button id="bgt-new-submit-btn" onclick="submitNewBgTask()" style="border:0;background:var(--brand);color:#fff;border-radius:8px;padding:7px 18px;font-size:12px;font-weight:700;cursor:pointer">▶ 시작</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Background Task Heartbeat Modal -->
|
||
<div id="bgt-heartbeat-modal" style="display:none;position:fixed;inset:0;background:rgba(10,20,40,0.35);z-index:9999;align-items:center;justify-content:center;padding:18px">
|
||
<div style="width:min(420px,96vw);max-height:90vh;overflow:auto;background:var(--panel);border:1px solid var(--line);border-radius:14px;box-shadow:var(--shadow-md)">
|
||
<div style="display:flex;align-items:center;justify-content:space-between;padding:14px 16px;border-bottom:1px solid var(--line)">
|
||
<div style="font-weight:800">작업 하트비트</div>
|
||
<button class="btn btn-sm" style="background:var(--panel-2);color:var(--muted);border:1px solid var(--line)" onclick="closeBgtHeartbeatModal()">닫기</button>
|
||
</div>
|
||
<div style="padding:14px 16px;display:flex;flex-direction:column;gap:12px">
|
||
<label style="display:flex;align-items:center;gap:8px;font-size:13px;cursor:pointer">
|
||
<input type="checkbox" id="bgt-hb-enabled" />
|
||
Enable task heartbeat
|
||
</label>
|
||
<div>
|
||
<label style="display:block;font-size:12px;color:var(--muted);margin-bottom:6px">Interval (minutes)</label>
|
||
<input id="bgt-hb-interval" type="number" min="1" max="1440" step="1" style="width:100%;border:1px solid var(--line);border-radius:10px;padding:8px;font-size:13px;font-family:'IBM Plex Mono',monospace" />
|
||
<div style="font-size:11px;color:var(--muted);margin-top:6px">Range: 1 to 1440 minutes</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div style="display:flex;justify-content:flex-end;gap:8px;padding:12px 16px;border-top:1px solid var(--line)">
|
||
<button class="btn btn-sm" style="background:var(--panel-2);color:var(--muted);border:1px solid var(--line)" onclick="closeBgtHeartbeatModal()">취소</button>
|
||
<button class="btn btn-sm" onclick="saveBgtHeartbeatConfig()">저장</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- RIGHT PANEL -->
|
||
<div class="right-panel" id="right-panel" style="position:relative">
|
||
<!-- Resize handle on left edge -->
|
||
<div id="right-panel-resize-handle" style="position:absolute;left:0;top:0;bottom:0;width:5px;cursor:col-resize;z-index:200;background:transparent;transition:background 0.15s ease" title="드래그하여 크기 조절"></div>
|
||
<!-- Collapsed rail (visible when right panel is minimized) -->
|
||
<div id="right-panel-rail" onclick="toggleRightPanel()" title="컨텍스트 패널 펼치기" style="flex-direction:column;align-items:center;justify-content:flex-start;gap:14px;padding:10px 0;cursor:pointer;background:var(--panel);height:100%">
|
||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" style="color:var(--muted)"><polyline points="15 18 9 12 15 6"/></svg>
|
||
<div style="writing-mode:vertical-rl;font-size:10px;font-weight:800;color:var(--muted);text-transform:uppercase;letter-spacing:0.12em">컨텍스트</div>
|
||
</div>
|
||
<!-- Right panel topbar with canvas toggle -->
|
||
<div style="display:flex;align-items:center;justify-content:space-between;padding:4px 10px;border-bottom:1px solid var(--line);flex-shrink:0;background:var(--panel);min-height:28px">
|
||
<span style="font-size:10px;font-weight:800;color:var(--muted);text-transform:uppercase;letter-spacing:0.06em">컨텍스트</span>
|
||
<button onclick="toggleRightPanel()" title="컨텍스트 패널 최소화" style="display:flex;align-items:center;justify-content:center;border:1px solid var(--line);background:var(--panel-2);color:var(--muted);border-radius:8px;width:28px;height:28px;cursor:pointer">
|
||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 18 15 12 9 6"/></svg>
|
||
</button>
|
||
</div>
|
||
|
||
<div class="right-section" style="padding:6px 10px">
|
||
<div class="sys-card full" onclick="openSettings('context-viewer')" title="컨텍스트 뷰어 열기" style="cursor:pointer">
|
||
<div class="sys-k">접속자</div>
|
||
<div class="sys-v" id="sys-online-users" style="font-size:12px;font-weight:600;letter-spacing:0">-</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="right-section" id="agent-context-section">
|
||
<div class="right-section-title">에이전트 컨텍스트</div>
|
||
<div class="agent-card">
|
||
<div class="agent-metric-grid" style="grid-template-columns:repeat(4,1fr)">
|
||
<div class="agent-metric">
|
||
<div class="k">모드</div>
|
||
<div class="v" id="r-session-mode">-</div>
|
||
</div>
|
||
<div class="agent-metric">
|
||
<div class="k">상태</div>
|
||
<div class="v" id="r-agent-state">-</div>
|
||
</div>
|
||
<div class="agent-metric">
|
||
<div class="k">작업</div>
|
||
<div class="v" id="r-task-counts">0</div>
|
||
</div>
|
||
<div class="agent-metric">
|
||
<div class="k">턴</div>
|
||
<div class="v" id="r-turn-counts">0</div>
|
||
</div>
|
||
</div>
|
||
<!-- hidden placeholders so JS still writes to them without errors -->
|
||
<span id="r-overview-objective" style="display:none"></span>
|
||
<span id="r-active-objective" style="display:none"></span>
|
||
<span id="r-heartbeat-state" style="display:none"></span>
|
||
<span id="r-heartbeat-meta" style="display:none"></span>
|
||
<div class="task-progress-wrap"><div class="task-progress-fill" id="r-task-progress-bar"></div></div>
|
||
<!-- hidden placeholders kept so JS doesn't error -->
|
||
<span id="r-ctx-fill" style="display:none"></span>
|
||
<span id="r-ctx-label" style="display:none"></span>
|
||
<div class="agent-context-scroll" id="r-agent-context-scroll">
|
||
<div class="task-list" id="r-turn-execution">
|
||
<div style="font-size:11px;color:var(--muted)">활성 턴 실행 없음.</div>
|
||
</div>
|
||
<div class="task-list" id="r-task-list">
|
||
<div style="font-size:11px;color:var(--muted)">작업 없음.</div>
|
||
</div>
|
||
<div class="task-list" id="r-live-steps">
|
||
<div style="font-size:11px;color:var(--muted)">활성 도구 단계 없음.</div>
|
||
</div>
|
||
<div id="r-recent-turns" style="padding:8px;border:1px solid var(--line);border-radius:10px;background:var(--panel-2);font-size:11px;line-height:1.6;color:var(--muted)">
|
||
Waiting for turn history...
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="right-section">
|
||
<div class="right-section-title">⚠️ Pending Approvals</div>
|
||
<div id="approvals-list">
|
||
<div style="color:var(--muted);font-size:11px">대기 중인 승인 없음</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div style="padding:5px 10px 4px; border-bottom:1px solid var(--border); display:flex; align-items:center; justify-content:space-between">
|
||
<div class="right-section-title" style="margin:0">빠른 작업</div>
|
||
<div style="display:flex;gap:6px">
|
||
<button class="btn btn-sm" style="background:var(--bg3);color:var(--muted);border:1px solid var(--border);padding:3px 8px" onclick="clearChat()">✕ Chat</button>
|
||
<button class="btn btn-sm" style="background:var(--bg3);color:var(--muted);border:1px solid var(--border);padding:3px 8px" onclick="newChatSession()">+</button>
|
||
<div class="export-dropdown">
|
||
<button class="btn btn-sm" style="background:var(--bg3);color:var(--muted);border:1px solid var(--border);padding:3px 8px" onclick="toggleExportMenu(event)" title="채팅 내보내기">↓ 내보내기</button>
|
||
<div class="export-menu" id="export-menu">
|
||
<button onclick="exportChat('md')">📄 Markdown (.md)</button>
|
||
<button onclick="exportChat('txt')">📝 텍스트 (.txt)</button>
|
||
<button onclick="exportChat('json')">{ } JSON (.json)</button>
|
||
</div>
|
||
</div>
|
||
<button class="btn btn-sm" style="background:var(--bg3);color:var(--muted);border:1px solid var(--border);padding:3px 8px" onclick="openSettings()">설정</button>
|
||
<button class="btn btn-sm" style="background:var(--bg3);color:var(--muted);border:1px solid var(--border);padding:3px 8px" onclick="setSidebarTab('skills')">🧩</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- AGENT EXECUTION PANEL -->
|
||
<div id="agent-panel" style="display:flex;flex-direction:column;max-height:100px;overflow:hidden;border:1px solid var(--border);border-radius:8px;background:var(--panel-2);margin-bottom:6px">
|
||
<div style="padding:4px 8px;border-bottom:1px solid var(--border);flex-shrink:0">
|
||
<div style="font-size:9px;text-transform:uppercase;letter-spacing:1.5px;color:var(--muted)">활성 에이전트</div>
|
||
</div>
|
||
<div id="agent-execution-panel" style="flex:1;overflow-y:auto;padding:0">
|
||
<div style="padding:8px 12px;font-size:11px;color:var(--muted)">작업 실행 대기 중...</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- WOL-GATE WAKE LOG -->
|
||
<div id="wolgate-section" style="display:flex;flex-direction:column;flex-shrink:0;max-height:120px;min-height:80px;overflow:hidden;border:1px solid var(--border);border-radius:8px;background:var(--panel-2);margin-bottom:6px">
|
||
<div style="padding:4px 8px;border-bottom:1px solid var(--border);flex-shrink:0;display:flex;align-items:center;justify-content:space-between">
|
||
<div style="font-size:9px;text-transform:uppercase;letter-spacing:1.5px;color:var(--muted)">wol-gate 깨우기 로그</div>
|
||
<button onclick="refreshWolGateLog()" style="background:none;border:none;color:var(--muted);cursor:pointer;font-size:10px;font-family:inherit">↻</button>
|
||
</div>
|
||
<div id="wolgate-log" style="flex:1;overflow-y:auto;padding:6px 10px;font-size:10px;line-height:1.6;font-family:'JetBrains Mono',monospace">
|
||
<div style="color:var(--muted);text-align:center;padding:10px 0;opacity:0.5">불러오는 중...</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- PROCESS LOG -->
|
||
<div id="process-section" style="display:flex;flex-direction:column;flex:1 1 100px;overflow:hidden;min-height:100px">
|
||
<div style="padding:4px 10px;display:flex;align-items:center;justify-content:space-between;border-bottom:1px solid var(--border);flex-shrink:0">
|
||
<a href="javascript:void(0)" onclick="openProcessLogFile()" style="font-size:10px;text-transform:uppercase;letter-spacing:1.5px;color:var(--muted);text-decoration:none;cursor:pointer;hover:color:var(--brand)">프로세스 로그</a>
|
||
<button onclick="clearProcessLog()" style="background:none;border:none;color:var(--muted);cursor:pointer;font-size:10px;font-family:inherit">✕ clear</button>
|
||
</div>
|
||
<div id="process-log" style="flex:1;overflow-y:auto;padding:8px 12px;font-size:11px;line-height:1.7;font-family:'JetBrains Mono',monospace">
|
||
<div style="color:var(--muted);text-align:center;padding:20px 0;opacity:0.5">활동 대기 중...</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- MOBILE BOTTOM NAV -->
|
||
<nav id="mobile-nav">
|
||
<button id="mob-tab-chat" class="mob-tab active" onclick="mobTab('chat')">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>
|
||
Chat
|
||
</button>
|
||
<button id="mob-tab-ctx" class="mob-tab" onclick="mobTab('ctx')">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M3 9h18M9 21V9"/></svg>
|
||
Context
|
||
</button>
|
||
<button id="mob-tab-menu" class="mob-tab" onclick="mobTab('menu')">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"><line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="18" x2="21" y2="18"/></svg>
|
||
Menu
|
||
</button>
|
||
<button id="mob-tab-logout" class="mob-tab" onclick="logout()" style="display:none;color:var(--muted)">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/><polyline points="16 17 21 12 16 7"/><line x1="21" y1="12" x2="9" y2="12"/></svg>
|
||
로그아웃
|
||
</button>
|
||
</nav>
|
||
|
||
<!-- Mobile backdrop (closes sidebar drawer) -->
|
||
<div id="mob-backdrop" onclick="mobCloseMenu()" style="display:none;position:fixed;inset:0;background:rgba(0,0,0,0.38);z-index:499;-webkit-tap-highlight-color:transparent"></div>
|
||
|
||
<!-- ERROR RESPONSE MODAL -->
|
||
<div id="error-response-panel">
|
||
<div class="error-response-header">
|
||
<div class="error-response-title" id="error-response-title">⚠️ Authentication Required</div>
|
||
<div class="error-response-desc" id="error-response-desc">작업을 계속하려면 도움이 필요합니다. 필요한 정보를 입력해 주세요.</div>
|
||
</div>
|
||
|
||
<div class="error-response-content">
|
||
<div class="error-response-options" id="error-response-options">
|
||
<!-- Options will be populated by JavaScript -->
|
||
</div>
|
||
<div class="error-response-inputs" id="error-response-inputs">
|
||
<!-- Input fields will be populated by JavaScript -->
|
||
</div>
|
||
</div>
|
||
|
||
<div class="error-response-footer">
|
||
<button class="btn-error-cancel" onclick="closeErrorResponse()">작업 취소</button>
|
||
<button class="btn-error-submit" id="error-response-submit-btn" onclick="submitErrorResponse()">계속</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- CREATE SKILL MODAL -->
|
||
<div id="skill-modal" style="display:none;position:fixed;inset:0;background:rgba(10,20,40,0.35);z-index:9999;align-items:center;justify-content:center;padding:18px">
|
||
<div style="width:min(520px,96vw);max-height:90vh;overflow:auto;background:#fff;border:1px solid var(--line);border-radius:14px;box-shadow:var(--shadow-md)">
|
||
<div style="display:flex;align-items:center;justify-content:space-between;padding:14px 16px;border-bottom:1px solid var(--line)">
|
||
<div style="font-weight:800" id="skill-modal-title">스킬 생성</div>
|
||
<button class="btn btn-sm" style="background:var(--panel-2);color:var(--muted);border:1px solid var(--line)" onclick="closeSkillModal()">닫기</button>
|
||
</div>
|
||
<div style="padding:14px 16px;display:flex;flex-direction:column;gap:12px">
|
||
<div>
|
||
<label style="display:block;font-size:12px;color:var(--muted);margin-bottom:4px;font-weight:700">Name</label>
|
||
<input id="skill-modal-name" style="width:100%;border:1px solid var(--line);border-radius:10px;padding:8px 10px;font-size:13px;font-family:'Manrope',sans-serif" placeholder="예: 모던 스타일링" />
|
||
</div>
|
||
<div style="display:flex;gap:10px">
|
||
<div style="flex:1">
|
||
<label style="display:block;font-size:12px;color:var(--muted);margin-bottom:4px;font-weight:700">Emoji</label>
|
||
<input id="skill-modal-emoji" style="width:100%;border:1px solid var(--line);border-radius:10px;padding:8px 10px;font-size:13px;font-family:'Manrope',sans-serif" placeholder="??" maxlength="4" />
|
||
</div>
|
||
<div style="flex:3">
|
||
<label style="display:block;font-size:12px;color:var(--muted);margin-bottom:4px;font-weight:700">짧은 설명</label>
|
||
<input id="skill-modal-desc" style="width:100%;border:1px solid var(--line);border-radius:10px;padding:8px 10px;font-size:13px;font-family:'Manrope',sans-serif" placeholder="이 스킬은 무엇을 하나요?" />
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<label style="display:block;font-size:12px;color:var(--muted);margin-bottom:4px;font-weight:700">Instructions <span style="font-weight:400">(what the model should do when this skill is active)</span></label>
|
||
<textarea id="skill-modal-instructions" style="width:100%;min-height:180px;border:1px solid var(--line);border-radius:10px;padding:10px;font-size:12px;font-family:'IBM Plex Mono',monospace;line-height:1.6;resize:vertical" placeholder="HTML 파일 생성 시, 항상 모던 CSS 패턴을 사용하세요..."></textarea>
|
||
</div>
|
||
</div>
|
||
<div style="display:flex;justify-content:flex-end;gap:8px;padding:12px 16px;border-top:1px solid var(--line)">
|
||
<button class="btn btn-sm" style="background:var(--panel-2);color:var(--muted);border:1px solid var(--line)" onclick="closeSkillModal()">취소</button>
|
||
<button class="btn btn-sm" id="skill-modal-save-btn" onclick="saveSkillFromModal()">스킬 생성</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- SETTINGS MODAL -->
|
||
<div id="settings-modal" style="display:none;position:fixed;inset:0;background:rgba(10,20,40,0.35);z-index:9999;align-items:center;justify-content:center;padding:18px">
|
||
<div style="width:min(980px,96vw);max-height:90vh;overflow:auto;background:#fff;border:1px solid var(--line);border-radius:14px;box-shadow:var(--shadow-md)">
|
||
<div style="display:flex;align-items:center;justify-content:space-between;padding:14px 16px;border-bottom:1px solid var(--line)">
|
||
<div style="font-weight:800">설정</div>
|
||
<button class="btn btn-sm" style="background:var(--panel-2);color:var(--muted);border:1px solid var(--line)" onclick="closeSettings()">닫기</button>
|
||
</div>
|
||
|
||
<div style="padding:10px 16px;border-bottom:1px solid var(--line);display:flex;gap:8px">
|
||
<button id="settings-tab-system" class="sidebar-action" onclick="setSettingsTab('system')">시스템</button>
|
||
<button id="settings-tab-search" class="sidebar-action" onclick="setSettingsTab('search')">검색</button>
|
||
<button id="settings-tab-credentials" class="sidebar-action" onclick="setSettingsTab('credentials')">자격증명</button>
|
||
<button id="settings-tab-policy" class="sidebar-action" onclick="setSettingsTab('policy')">에이전트 정책</button>
|
||
<button id="settings-tab-security" class="sidebar-action" onclick="setSettingsTab('security')">보안</button>
|
||
<button id="settings-tab-users" class="sidebar-action" onclick="setSettingsTab('users')">사용자</button>
|
||
<button id="settings-tab-models" class="sidebar-action" onclick="setSettingsTab('models')">모델</button>
|
||
<button id="settings-tab-agents" class="sidebar-action" onclick="setSettingsTab('agents')">에이전트</button>
|
||
<button id="settings-tab-channels" class="sidebar-action" onclick="setSettingsTab('channels')">채널</button>
|
||
<button id="settings-tab-voice" class="sidebar-action" onclick="setSettingsTab('voice')">음성</button>
|
||
<button id="settings-tab-session" class="sidebar-action" onclick="setSettingsTab('session')">세션</button>
|
||
<button id="settings-tab-integrations" class="sidebar-action" onclick="setSettingsTab('integrations')">연동</button>
|
||
<button id="settings-tab-context-viewer" class="sidebar-action" onclick="setSettingsTab('context-viewer')">컨텍스트 뷰어</button>
|
||
</div>
|
||
|
||
<div id="settings-panel-system" style="padding:14px 16px;display:grid;grid-template-columns:1fr 1fr;gap:14px">
|
||
<div style="border:1px solid var(--line);border-radius:12px;padding:12px;background:#fff">
|
||
<div class="right-section-title" style="margin-bottom:8px">런타임</div>
|
||
<div class="info-row"><span class="info-key">모델</span><span class="info-val" id="settings-runtime-model">-</span></div>
|
||
<div class="info-row"><span class="info-key">게이트웨이</span><span class="info-val" id="settings-runtime-gateway">-</span></div>
|
||
<div class="info-row"><span class="info-key">Ollama</span><span class="info-val" id="settings-runtime-ollama">-</span></div>
|
||
</div>
|
||
<div style="border:1px solid var(--line);border-radius:12px;padding:12px;background:#fff">
|
||
<div class="right-section-title" style="margin-bottom:8px">참고</div>
|
||
<div style="font-size:12px;color:var(--muted);line-height:1.7">
|
||
설정은 이 게이트웨이 프로세스에 즉시 적용됩니다.
|
||
소형 모델 사용 시 도구 라우팅을 엄격하게, 메모리 범위를 제한하세요.
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="settings-panel-search" style="display:none;padding:14px 16px;grid-template-columns:1fr 1fr;gap:14px">
|
||
<div style="border:1px solid var(--line);border-radius:12px;padding:12px">
|
||
<div class="right-section-title" style="margin-bottom:8px">웹 검색</div>
|
||
<label style="display:block;font-size:12px;color:var(--muted);margin-bottom:6px">검색 제공자</label>
|
||
<select id="settings-provider" onchange="toggleOllamaModelRow(this.value);toggleSearxngUrlRow(this.value)" style="width:100%;border:1px solid var(--line);border-radius:10px;padding:8px;font-size:13px">
|
||
<option value="searxng">SearXNG (셀프호스트)</option>
|
||
<option value="ddg">DuckDuckGo</option>
|
||
<option value="ollama">Ollama (모델 보조 검색)</option>
|
||
<option value="tavily">Tavily</option>
|
||
<option value="google">Google CSE</option>
|
||
<option value="brave">Brave</option>
|
||
</select>
|
||
<div id="search-ollama-model-row" style="display:none;margin-top:8px">
|
||
<label style="display:block;font-size:12px;color:var(--muted);margin-bottom:4px">Ollama 검색 모델</label>
|
||
<input id="settings-search-ollama-model" type="text" placeholder="예: kimi-k2.6:cloud" style="width:100%;border:1px solid var(--line);border-radius:8px;padding:7px 9px;font-size:12px;box-sizing:border-box">
|
||
</div>
|
||
<div id="search-searxng-url-row" style="display:none;margin-top:8px">
|
||
<label style="display:block;font-size:12px;color:var(--muted);margin-bottom:4px">SearXNG 인스턴스 URL</label>
|
||
<input id="settings-search-searxng-url" type="text" placeholder="예: http://127.0.0.1:8888" style="width:100%;border:1px solid var(--line);border-radius:8px;padding:7px 9px;font-size:12px;box-sizing:border-box">
|
||
</div>
|
||
<label style="display:block;font-size:12px;color:var(--muted);margin:10px 0 6px">검색 엄격도</label>
|
||
<select id="settings-search-rigor" style="width:100%;border:1px solid var(--line);border-radius:10px;padding:8px;font-size:13px">
|
||
<option value="fast">빠름</option>
|
||
<option value="verified">검증됨</option>
|
||
<option value="strict">엄격</option>
|
||
</select>
|
||
<div style="margin-top:12px;padding:8px 10px;background:#f0f6ff;border:1px solid #bdd3f6;border-radius:8px;font-size:11px;color:#1e40af">
|
||
🔑 API 키는 <strong>자격증명</strong> 탭에서 관리되며 암호화되어 저장됩니다.
|
||
</div>
|
||
</div>
|
||
<div style="border:1px solid var(--line);border-radius:12px;padding:12px;background:#fff">
|
||
<div class="right-section-title" style="margin-bottom:8px">동작</div>
|
||
<div style="font-size:12px;color:var(--muted);line-height:1.7">
|
||
빠름: 가장 빠른 조회, 적은 검증.
|
||
검증됨: 1회 재시도 + 신뢰도 우선.
|
||
엄격: 검증 필수 도메인에 가장 강력한 소스 요구.
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="settings-panel-credentials" style="display:none;padding:14px 16px">
|
||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:14px">
|
||
|
||
<!-- Search Provider Keys -->
|
||
<div style="border:1px solid var(--line);border-radius:12px;padding:14px">
|
||
<div class="right-section-title" style="margin-bottom:4px">검색 제공자 키</div>
|
||
<div style="font-size:11px;color:var(--muted);margin-bottom:12px">Vault에 암호화 저장 — config.json에 평문으로 저장되지 않습니다.</div>
|
||
|
||
<label style="display:block;font-size:12px;color:var(--muted);margin-bottom:5px">Tavily API 키</label>
|
||
<div style="display:flex;gap:6px;margin-bottom:10px">
|
||
<input id="cred-tavily-key" type="password" placeholder="tvLy-…" autocomplete="new-password"
|
||
style="flex:1;border:1px solid var(--line);border-radius:8px;padding:7px 10px;font-size:12px;font-family:var(--font-mono, monospace)" />
|
||
<button onclick="toggleCredVis('cred-tavily-key',this)" title="Show/hide"
|
||
style="padding:0 10px;border:1px solid var(--line);border-radius:8px;background:var(--panel-2);font-size:13px;cursor:pointer;flex-shrink:0">👁</button>
|
||
</div>
|
||
|
||
<label style="display:block;font-size:12px;color:var(--muted);margin-bottom:5px">Google API 키</label>
|
||
<div style="display:flex;gap:6px;margin-bottom:10px">
|
||
<input id="cred-google-key" type="password" placeholder="AIzaSy…" autocomplete="new-password"
|
||
style="flex:1;border:1px solid var(--line);border-radius:8px;padding:7px 10px;font-size:12px;font-family:var(--font-mono, monospace)" />
|
||
<button onclick="toggleCredVis('cred-google-key',this)" title="Show/hide"
|
||
style="padding:0 10px;border:1px solid var(--line);border-radius:8px;background:var(--panel-2);font-size:13px;cursor:pointer;flex-shrink:0">👁</button>
|
||
</div>
|
||
|
||
<label style="display:block;font-size:12px;color:var(--muted);margin-bottom:5px">Google CSE ID <span style="font-weight:400;color:var(--muted)">(비밀 아님 — config에 저장)</span></label>
|
||
<input id="cred-google-cx" type="text" placeholder="c3222d…"
|
||
style="width:100%;border:1px solid var(--line);border-radius:8px;padding:7px 10px;font-size:12px;margin-bottom:10px;box-sizing:border-box" />
|
||
|
||
<label style="display:block;font-size:12px;color:var(--muted);margin-bottom:5px">Brave API 키</label>
|
||
<div style="display:flex;gap:6px;margin-bottom:10px">
|
||
<input id="cred-brave-key" type="password" placeholder="BSA…" autocomplete="new-password"
|
||
style="flex:1;border:1px solid var(--line);border-radius:8px;padding:7px 10px;font-size:12px;font-family:var(--font-mono, monospace)" />
|
||
<button onclick="toggleCredVis('cred-brave-key',this)" title="Show/hide"
|
||
style="padding:0 10px;border:1px solid var(--line);border-radius:8px;background:var(--panel-2);font-size:13px;cursor:pointer;flex-shrink:0">👁</button>
|
||
</div>
|
||
|
||
<label style="display:block;font-size:12px;color:var(--muted);margin-bottom:5px">OpenWeather API 키 <span style="font-weight:400">(날씨 도구)</span></label>
|
||
<div style="display:flex;gap:6px">
|
||
<input id="cred-ow-key" type="password" placeholder="…" autocomplete="new-password"
|
||
style="flex:1;border:1px solid var(--line);border-radius:8px;padding:7px 10px;font-size:12px;font-family:var(--font-mono, monospace)" />
|
||
<button onclick="toggleCredVis('cred-ow-key',this)" title="Show/hide"
|
||
style="padding:0 10px;border:1px solid var(--line);border-radius:8px;background:var(--panel-2);font-size:13px;cursor:pointer;flex-shrink:0">👁</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Right column: status + info -->
|
||
<div style="display:flex;flex-direction:column;gap:12px">
|
||
|
||
<!-- Vault status card -->
|
||
<div style="border:1px solid var(--line);border-radius:12px;padding:14px">
|
||
<div class="right-section-title" style="margin-bottom:8px">Vault 상태</div>
|
||
<div id="cred-vault-status" style="font-size:12px;color:var(--muted)">Loading…</div>
|
||
</div>
|
||
|
||
<!-- How it works -->
|
||
<div style="border:1px solid #bdd3f6;border-radius:12px;padding:14px;background:#f0f6ff">
|
||
<div style="font-size:12px;font-weight:700;color:#1e40af;margin-bottom:6px">🔒 자격증명 작동 방식</div>
|
||
<div style="font-size:11.5px;color:#1e40af;line-height:1.65">
|
||
<strong>설정 저장</strong> 시 입력한 키는 AES-256-GCM으로 암호화되어 <code style="background:#dbeafe;padding:1px 4px;border-radius:4px">vault.enc</code>에 저장됩니다.<br><br>
|
||
Config 파일에는 <code style="background:#dbeafe;padding:1px 4px;border-radius:4px">vault:…</code> 참조만 포함되며, 실제 키는 저장되지 않습니다.<br><br>
|
||
필드에 <strong>••••••••</strong>이 표시되면 키가 이미 저장된 것입니다. 그대로 두면 기존 키가 유지되고, 새로 입력하면 교체됩니다.
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Audit log preview -->
|
||
<div style="border:1px solid var(--line);border-radius:12px;padding:14px">
|
||
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:8px">
|
||
<div class="right-section-title">최근 Vault 접근</div>
|
||
<button onclick="loadCredVaultLog()" style="font-size:10px;padding:3px 8px;border:1px solid var(--line);border-radius:6px;background:var(--panel-2);cursor:pointer;color:var(--muted)">새로고침</button>
|
||
</div>
|
||
<div id="cred-vault-log" style="font-size:10.5px;color:var(--muted);font-family:monospace;max-height:110px;overflow-y:auto;line-height:1.6">Loading…</div>
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="settings-panel-policy" style="display:none;padding:14px 16px">
|
||
<div style="border:1px solid var(--line);border-radius:12px;padding:12px;background:#fff">
|
||
<div class="right-section-title" style="margin-bottom:8px">에이전트 신선도 + 메모리 정책</div>
|
||
<label style="display:flex;align-items:center;gap:8px;font-size:13px;margin-bottom:10px">
|
||
<input type="checkbox" id="settings-force-web-fresh" />
|
||
최신/현재 쿼리에 대해 최소 1회 웹 검색 강제
|
||
</label>
|
||
<label style="display:flex;align-items:center;gap:8px;font-size:13px;margin-bottom:10px">
|
||
<input type="checkbox" id="settings-memory-fallback" />
|
||
모든 제공자 실패 시 메모리 대체 허용
|
||
</label>
|
||
<label style="display:flex;align-items:center;gap:8px;font-size:13px">
|
||
<input type="checkbox" id="settings-auto-store-web-facts" />
|
||
검증된 웹 팩트를 메모리에 자동 저장
|
||
</label>
|
||
<label style="display:flex;align-items:center;gap:8px;font-size:13px;margin-top:10px">
|
||
<input type="checkbox" id="settings-nl-tool-router" />
|
||
자연어 도구 라우팅 활성화 ("찾아줘", "웹 사용", "검증해줘")
|
||
</label>
|
||
<label style="display:block;font-size:12px;color:var(--muted);margin:10px 0 6px">코드 검색 모드</label>
|
||
<select id="settings-retrieval-mode" style="width:100%;border:1px solid var(--line);border-radius:10px;padding:8px;font-size:13px">
|
||
<option value="fast">빠름 (작은 스니펫, 적은 읽기)</option>
|
||
<option value="standard">표준</option>
|
||
<option value="deep">심층 (큰 스니펫, 더 많은 컨텍스트)</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="settings-panel-security" style="display:none;padding:14px 16px">
|
||
<div style="border:1px solid var(--line);border-radius:12px;padding:12px">
|
||
<div class="right-section-title" style="margin-bottom:8px">파일 접근</div>
|
||
<label style="display:block;font-size:12px;color:var(--muted);margin-bottom:6px">작업공간 경로</label>
|
||
<input id="settings-workspace-path" type="text" style="width:100%;border:1px solid var(--line);border-radius:10px;padding:8px;font-family:'IBM Plex Mono',monospace;font-size:12px;margin-bottom:10px" placeholder="예: D:\HomeClaw\workspace" />
|
||
<label style="display:block;font-size:12px;color:var(--muted);margin-bottom:6px">허용 경로 (한 줄에 하나)</label>
|
||
<textarea id="settings-allowed-paths" style="width:100%;min-height:120px;border:1px solid var(--line);border-radius:10px;padding:8px;font-family:'IBM Plex Mono',monospace;font-size:12px"></textarea>
|
||
<label style="display:block;font-size:12px;color:var(--muted);margin:10px 0 6px">차단 경로 (한 줄에 하나)</label>
|
||
<textarea id="settings-blocked-paths" style="width:100%;min-height:100px;border:1px solid var(--line);border-radius:10px;padding:8px;font-family:'IBM Plex Mono',monospace;font-size:12px"></textarea>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="settings-panel-users" style="display:none;padding:14px 16px">
|
||
<div style="border:1px solid var(--line);border-radius:12px;padding:12px">
|
||
<div class="right-section-title" style="margin-bottom:8px">계정 관리</div>
|
||
<div id="users-list" style="margin-bottom:12px"></div>
|
||
<div style="display:grid;grid-template-columns:1fr 1fr auto;gap:8px;align-items:end">
|
||
<div>
|
||
<label style="display:block;font-size:12px;color:var(--muted);margin-bottom:4px">사용자 이름</label>
|
||
<input id="new-user-username" type="text" placeholder="username" style="width:100%;border:1px solid var(--line);border-radius:10px;padding:8px;font-size:12px;font-family:'IBM Plex Mono',monospace" />
|
||
</div>
|
||
<div>
|
||
<label style="display:block;font-size:12px;color:var(--muted);margin-bottom:4px">비밀번호</label>
|
||
<input id="new-user-password" type="password" placeholder="password" style="width:100%;border:1px solid var(--line);border-radius:10px;padding:8px;font-size:12px;font-family:'IBM Plex Mono',monospace" />
|
||
</div>
|
||
<div>
|
||
<label style="display:block;font-size:12px;color:var(--muted);margin-bottom:4px">역할</label>
|
||
<select id="new-user-role" style="width:80px;border:1px solid var(--line);border-radius:10px;padding:8px;font-size:12px">
|
||
<option value="user">User</option>
|
||
<option value="admin">Admin</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
<button class="btn btn-primary" style="width:100%;margin-top:8px" onclick="addUser()">사용자 추가</button>
|
||
<div id="add-user-error" class="error-msg" style="margin-top:8px"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="settings-panel-models" style="display:none;padding:14px 16px">
|
||
|
||
<!-- Provider selector -->
|
||
<div style="border:1px solid var(--line);border-radius:12px;padding:12px;margin-bottom:14px">
|
||
<div class="right-section-title" style="margin-bottom:8px">LLM 제공자</div>
|
||
<label style="display:block;font-size:12px;color:var(--muted);margin-bottom:6px">백엔드</label>
|
||
<select id="settings-llm-provider" class="settings-input" onchange="onProviderChange()" style="font-size:13px">
|
||
<option value="ollama">Ollama (로컬)</option>
|
||
<option value="llama_cpp">llama.cpp (로컬)</option>
|
||
<option value="lm_studio">LM Studio (로컬)</option>
|
||
<option value="openai">OpenAI — API 키</option>
|
||
<option value="openai_codex">OpenAI — GPT Plus / Codex (OAuth)</option>
|
||
<option value="anthropic">Anthropic — Claude API 키</option>
|
||
<option value="google">Google — Gemini API 키</option>
|
||
</select>
|
||
</div>
|
||
|
||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:14px">
|
||
|
||
<!-- Left col: provider-specific connection fields -->
|
||
<div style="border:1px solid var(--line);border-radius:12px;padding:12px">
|
||
|
||
<!-- Ollama -->
|
||
<div id="prov-fields-ollama">
|
||
<div class="right-section-title" style="margin-bottom:8px">Ollama 연결</div>
|
||
<label style="display:block;font-size:12px;color:var(--muted);margin-bottom:6px">엔드포인트</label>
|
||
<input id="settings-ollama-endpoint" type="text" placeholder="http://localhost:11434" style="width:100%;border:1px solid var(--line);border-radius:10px;padding:8px;font-size:12px;font-family:'IBM Plex Mono',monospace" />
|
||
<div style="display:flex;gap:8px;margin-top:8px">
|
||
<button class="btn btn-sm" onclick="refreshProviderModels()" style="background:#fff;border:1px solid var(--line);color:var(--text)">모델 새로고침</button>
|
||
<button class="btn btn-sm" onclick="testProviderConnection()" style="background:#fff;border:1px solid var(--line);color:var(--text)">테스트</button>
|
||
</div>
|
||
<div id="provider-status-msg" style="font-size:11px;color:var(--muted);margin-top:6px"></div>
|
||
<div class="right-section-title" style="margin:14px 0 8px">기본 모델</div>
|
||
<label style="display:block;font-size:12px;color:var(--muted);margin-bottom:6px">활성 모델</label>
|
||
<select id="settings-primary-model" class="settings-input" style="font-size:13px">
|
||
<option value="">— 새로고침하여 로드 —</option>
|
||
</select>
|
||
</div>
|
||
|
||
<!-- llama.cpp -->
|
||
<div id="prov-fields-llama_cpp" style="display:none">
|
||
<div class="right-section-title" style="margin-bottom:8px">llama.cpp 서버</div>
|
||
<label style="display:block;font-size:12px;color:var(--muted);margin-bottom:6px">엔드포인트</label>
|
||
<input id="settings-llamacpp-endpoint" type="text" placeholder="http://localhost:8080" style="width:100%;border:1px solid var(--line);border-radius:10px;padding:8px;font-size:12px;font-family:'IBM Plex Mono',monospace" />
|
||
<label style="display:block;font-size:12px;color:var(--muted);margin:10px 0 6px">모델 이름 <span style="color:var(--muted);font-weight:400">(직접 입력)</span></label>
|
||
<input id="settings-llamacpp-model" type="text" placeholder="e.g. qwen2.5-7b" style="width:100%;border:1px solid var(--line);border-radius:10px;padding:8px;font-size:12px;font-family:'IBM Plex Mono',monospace" />
|
||
<div style="display:flex;gap:8px;margin-top:8px">
|
||
<button class="btn btn-sm" onclick="testProviderConnection()" style="background:#fff;border:1px solid var(--line);color:var(--text)">연결 테스트</button>
|
||
</div>
|
||
<div id="provider-status-msg-llamacpp" style="font-size:11px;color:var(--muted);margin-top:6px"></div>
|
||
<div style="font-size:11px;color:var(--muted);margin-top:12px;line-height:1.6">Start with: <code style="background:var(--bg-soft);padding:1px 5px;border-radius:4px">llama-server -m model.gguf --port 8080</code></div>
|
||
</div>
|
||
|
||
<!-- LM Studio -->
|
||
<div id="prov-fields-lm_studio" style="display:none">
|
||
<div class="right-section-title" style="margin-bottom:8px">LM Studio 연결</div>
|
||
<label style="display:block;font-size:12px;color:var(--muted);margin-bottom:6px">엔드포인트</label>
|
||
<input id="settings-lmstudio-endpoint" type="text" placeholder="http://localhost:1234" style="width:100%;border:1px solid var(--line);border-radius:10px;padding:8px;font-size:12px;font-family:'IBM Plex Mono',monospace" />
|
||
<label style="display:block;font-size:12px;color:var(--muted);margin:10px 0 6px">모델 이름</label>
|
||
<input id="settings-lmstudio-model" type="text" placeholder="e.g. qwen2.5-7b-instruct" style="width:100%;border:1px solid var(--line);border-radius:10px;padding:8px;font-size:12px;font-family:'IBM Plex Mono',monospace" />
|
||
<div style="display:flex;gap:8px;margin-top:8px">
|
||
<button class="btn btn-sm" onclick="refreshProviderModels()" style="background:#fff;border:1px solid var(--line);color:var(--text)">모델 감지</button>
|
||
<button class="btn btn-sm" onclick="testProviderConnection()" style="background:#fff;border:1px solid var(--line);color:var(--text)">테스트</button>
|
||
</div>
|
||
<div id="provider-status-msg-lmstudio" style="font-size:11px;color:var(--muted);margin-top:6px"></div>
|
||
<div style="font-size:11px;color:var(--muted);margin-top:12px;line-height:1.6">연결 전 LM Studio에서 로컬 서버를 활성화하세요.</div>
|
||
</div>
|
||
|
||
<!-- OpenAI API Key -->
|
||
<div id="prov-fields-openai" style="display:none">
|
||
<div class="right-section-title" style="margin-bottom:8px">OpenAI API 키</div>
|
||
<label style="display:block;font-size:12px;color:var(--muted);margin-bottom:6px">API 키</label>
|
||
<input id="settings-openai-key" type="password" placeholder="sk-..." style="width:100%;border:1px solid var(--line);border-radius:10px;padding:8px;font-size:12px;font-family:'IBM Plex Mono',monospace" />
|
||
<label style="display:block;font-size:12px;color:var(--muted);margin:10px 0 6px">모델</label>
|
||
<select id="settings-openai-model" class="settings-input" style="font-size:13px">
|
||
<option value="gpt-4o">gpt-4o</option>
|
||
<option value="gpt-4o-mini">gpt-4o-mini</option>
|
||
<option value="o4-mini">o4-mini</option>
|
||
<option value="o3">o3</option>
|
||
</select>
|
||
<div style="display:flex;gap:8px;margin-top:8px">
|
||
<button class="btn btn-sm" onclick="refreshOpenAIModels()" style="background:#fff;border:1px solid var(--line);color:var(--text)">모델 새로고침</button>
|
||
<button class="btn btn-sm" onclick="testProviderConnection()" style="background:#fff;border:1px solid var(--line);color:var(--text)">연결 테스트</button>
|
||
</div>
|
||
<div id="provider-status-msg-openai" style="font-size:11px;color:var(--muted);margin-top:6px"></div>
|
||
</div>
|
||
|
||
<!-- Anthropic -->
|
||
<div id="prov-fields-anthropic" style="display:none">
|
||
<div class="right-section-title" style="margin-bottom:8px">Anthropic API 키</div>
|
||
<label style="display:block;font-size:12px;color:var(--muted);margin-bottom:6px">API 키</label>
|
||
<input id="settings-anthropic-key" type="password" placeholder="sk-ant-api03-..." style="width:100%;border:1px solid var(--line);border-radius:10px;padding:8px;font-size:12px;font-family:'IBM Plex Mono',monospace" />
|
||
<label style="display:block;font-size:12px;color:var(--muted);margin:10px 0 6px">모델</label>
|
||
<select id="settings-anthropic-model" class="settings-input" style="font-size:13px">
|
||
<option value="claude-sonnet-4-6">claude-sonnet-4-6 (권장)</option>
|
||
<option value="claude-opus-4-7">claude-opus-4-7</option>
|
||
<option value="claude-haiku-4-5-20251001">claude-haiku-4-5-20251001 (빠름)</option>
|
||
</select>
|
||
<div style="display:flex;gap:8px;margin-top:8px">
|
||
<button class="btn btn-sm" onclick="testProviderConnection()" style="background:#fff;border:1px solid var(--line);color:var(--text)">연결 테스트</button>
|
||
</div>
|
||
<div id="provider-status-msg-anthropic" style="font-size:11px;color:var(--muted);margin-top:6px"></div>
|
||
</div>
|
||
|
||
<!-- Google (Gemini) -->
|
||
<div id="prov-fields-google" style="display:none">
|
||
<div class="right-section-title" style="margin-bottom:8px">Google Gemini API 키</div>
|
||
<label style="display:block;font-size:12px;color:var(--muted);margin-bottom:6px">API 키</label>
|
||
<input id="settings-google-key" type="password" placeholder="AIzaSy... 또는 AQ...." style="width:100%;border:1px solid var(--line);border-radius:10px;padding:8px;font-size:12px;font-family:'IBM Plex Mono',monospace" />
|
||
<label style="display:block;font-size:12px;color:var(--muted);margin:10px 0 6px">모델</label>
|
||
<select id="settings-google-model" class="settings-input" style="font-size:13px">
|
||
<option value="gemini-3.6-flash">gemini-3.6-flash (권장)</option>
|
||
<option value="gemini-3.5-flash-lite">gemini-3.5-flash-lite (경제적, $0.30/$2.50 per 1M)</option>
|
||
<option value="gemini-3.1-flash-lite">gemini-3.1-flash-lite (최저가, $0.25/$1.50 per 1M)</option>
|
||
</select>
|
||
<div style="display:flex;gap:8px;margin-top:8px">
|
||
<button class="btn btn-sm" onclick="testProviderConnection()" style="background:#fff;border:1px solid var(--line);color:var(--text)">연결 테스트</button>
|
||
</div>
|
||
<div id="provider-status-msg-google" style="font-size:11px;color:var(--muted);margin-top:6px"></div>
|
||
<div style="font-size:11px;color:var(--muted);margin-top:12px;line-height:1.6">키는 <a href="https://aistudio.google.com/" target="_blank" rel="noopener">Google AI Studio</a>에서 발급받으세요.</div>
|
||
</div>
|
||
|
||
<!-- OpenAI Codex OAuth -->
|
||
<div id="prov-fields-openai_codex" style="display:none">
|
||
<div class="right-section-title" style="margin-bottom:8px">OpenAI — GPT Plus / Codex</div>
|
||
<div id="codex-disconnected-state">
|
||
<div style="font-size:12px;color:var(--muted);margin-bottom:12px;line-height:1.6">OAuth로 ChatGPT Plus 또는 Pro 계정을 연결하세요. 브라우저에서 HomeClaw 인증이 열립니다.</div>
|
||
<button class="btn" onclick="startCodexOAuth()" style="width:100%;background:var(--brand);color:#fff;border:none;padding:10px;border-radius:10px;font-size:13px;cursor:pointer">ChatGPT 계정 연결</button>
|
||
<div id="codex-oauth-status" style="font-size:11px;color:var(--muted);margin-top:8px;text-align:center"></div>
|
||
</div>
|
||
<div id="codex-connected-state" style="display:none">
|
||
<div style="background:#f0faf4;border:1px solid #86d4a8;border-radius:10px;padding:10px;margin-bottom:12px">
|
||
<div style="font-size:12px;font-weight:600;color:#0a6b3d">연결됨</div>
|
||
<div id="codex-account-id" style="font-size:11px;color:var(--muted);margin-top:2px"></div>
|
||
</div>
|
||
<label style="display:block;font-size:12px;color:var(--muted);margin-bottom:6px">Model</label>
|
||
<select id="settings-codex-model" class="settings-input" style="font-size:13px">
|
||
<option value="gpt-5.3-codex">gpt-5.3-codex (recommended)</option>
|
||
<option value="gpt-5.3-codex-spark">gpt-5.3-codex-spark</option>
|
||
<option value="gpt-5.2-codex">gpt-5.2-codex</option>
|
||
<option value="gpt-5.1-codex-max">gpt-5.1-codex-max</option>
|
||
<option value="gpt-5.1-codex-mini">gpt-5.1-codex-mini (faster)</option>
|
||
<option value="gpt-5.2">gpt-5.2</option>
|
||
<option value="gpt-5.1">gpt-5.1</option>
|
||
</select>
|
||
<button class="btn btn-sm" onclick="disconnectCodex()" style="margin-top:10px;background:#fff;border:1px solid var(--err);color:var(--err)">계정 연결 해제</button>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<!-- Right col: orchestration -->
|
||
<div id="orch-settings-col" style="border:1px solid var(--line);border-radius:12px;padding:12px;background:linear-gradient(180deg,#fff 0%,#f9fbff 100%)">
|
||
<!-- -- Multi-Agent Orchestration -- -->
|
||
<div class="right-section-title" style="margin:16px 0 8px">멀티 에이전트 오케스트레이션 <span style="font-weight:400;color:var(--muted);font-size:11px">선택</span></div>
|
||
<div style="font-size:12px;color:var(--muted);margin-bottom:10px">
|
||
보조 AI 어드바이저가 기본 모델이 막히거나 계획이 필요할 때 도와줍니다.
|
||
설정 후 <strong>멀티 에이전트 오케스트레이터</strong> 스킬을 활성화하세요.
|
||
</div>
|
||
<!-- Preset buttons -->
|
||
<div style="margin-bottom:12px">
|
||
<div style="font-size:11px;color:var(--muted);margin-bottom:6px;font-weight:600">프리셋</div>
|
||
<div style="display:flex;gap:6px">
|
||
<button onclick="applyOrchPreset('conservative')" id="orch-preset-conservative" style="flex:1;padding:7px 4px;border-radius:8px;border:1px solid var(--line);background:#fff;font-size:12px;font-weight:600;color:#5a6e8a;cursor:pointer" onmouseover="this.style.borderColor='#5a6e8a'" onmouseout="this.style.borderColor=orchActivePreset==='conservative'?'#0d4faf':'var(--line)'">보수적</button>
|
||
<button onclick="applyOrchPreset('balanced')" id="orch-preset-balanced" style="flex:1;padding:7px 4px;border-radius:8px;border:1px solid var(--line);background:#fff;font-size:12px;font-weight:600;color:#5a6e8a;cursor:pointer" onmouseover="this.style.borderColor='#5a6e8a'" onmouseout="this.style.borderColor=orchActivePreset==='balanced'?'#0d4faf':'var(--line)'">균형</button>
|
||
<button onclick="applyOrchPreset('aggressive')" id="orch-preset-aggressive" style="flex:1;padding:7px 4px;border-radius:8px;border:1px solid var(--line);background:#fff;font-size:12px;font-weight:600;color:#5a6e8a;cursor:pointer" onmouseover="this.style.borderColor='#5a6e8a'" onmouseout="this.style.borderColor=orchActivePreset==='aggressive'?'#0d4faf':'var(--line)'">공격적</button>
|
||
</div>
|
||
<div id="orch-preset-desc" style="font-size:11px;color:var(--muted);margin-top:5px;min-height:16px"></div>
|
||
</div>
|
||
|
||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-bottom:10px">
|
||
<div>
|
||
<div style="font-size:11px;color:var(--muted);margin-bottom:4px">보조 제공자</div>
|
||
<select id="orch-secondary-provider" class="settings-input" onchange="updateOrchSecondaryModels()">
|
||
<option value="">— 없음 —</option>
|
||
<option value="openai_codex">ChatGPT (OAuth)</option>
|
||
<option value="openai">OpenAI API</option>
|
||
<option value="ollama">Ollama (local)</option>
|
||
<option value="llama_cpp">llama.cpp</option>
|
||
<option value="lm_studio">LM Studio</option>
|
||
</select>
|
||
</div>
|
||
<div>
|
||
<div style="font-size:11px;color:var(--muted);margin-bottom:4px">보조 모델</div>
|
||
<select id="orch-secondary-model" class="settings-input">
|
||
<option value="">— 제공자를 먼저 선택 —</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Advanced toggle -->
|
||
<div style="margin-bottom:8px">
|
||
<button onclick="toggleOrchAdvanced()" id="orch-advanced-toggle" style="display:flex;align-items:center;gap:5px;background:none;border:none;padding:0;font-size:11px;color:var(--muted);cursor:pointer;font-weight:600">
|
||
<span id="orch-advanced-arrow" style="font-size:10px;transition:transform .2s">▶</span> 고급 설정
|
||
</button>
|
||
</div>
|
||
<div id="orch-advanced-panel" style="display:none">
|
||
<div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:8px;margin-bottom:12px">
|
||
<div>
|
||
<div style="font-size:11px;color:var(--muted);margin-bottom:4px">실패 임계값</div>
|
||
<input id="orch-fail-threshold" class="settings-input" type="number" min="1" max="5" value="2" />
|
||
</div>
|
||
<div>
|
||
<div style="font-size:11px;color:var(--muted);margin-bottom:4px">정체 라운드</div>
|
||
<input id="orch-stagnation" class="settings-input" type="number" min="1" max="8" value="3" />
|
||
</div>
|
||
<div>
|
||
<div style="font-size:11px;color:var(--muted);margin-bottom:4px">루프 감지</div>
|
||
<select id="orch-loop-detect" class="settings-input">
|
||
<option value="true">예</option>
|
||
<option value="false">아니요</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
<div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:8px;margin-bottom:12px">
|
||
<div>
|
||
<div style="font-size:11px;color:var(--muted);margin-bottom:4px">위험 파일 수</div>
|
||
<input id="orch-risk-files" class="settings-input" type="number" min="1" max="30" value="6" />
|
||
</div>
|
||
<div>
|
||
<div style="font-size:11px;color:var(--muted);margin-bottom:4px">위험 줄 수정수</div>
|
||
<input id="orch-risk-ops" class="settings-input" type="number" min="10" max="2000" value="220" />
|
||
</div>
|
||
<div>
|
||
<div style="font-size:11px;color:var(--muted);margin-bottom:4px">진행 없음 (초)</div>
|
||
<input id="orch-no-progress-sec" class="settings-input" type="number" min="15" max="600" value="90" />
|
||
</div>
|
||
</div>
|
||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-bottom:12px">
|
||
<div>
|
||
<div style="font-size:11px;color:var(--muted);margin-bottom:4px">사전 어드바이저</div>
|
||
<select id="orch-preflight-mode" class="settings-input">
|
||
<option value="off">꺼짐</option>
|
||
<option value="complex_only">복잡한 작업만</option>
|
||
<option value="always">항상</option>
|
||
</select>
|
||
</div>
|
||
<div>
|
||
<div style="font-size:11px;color:var(--muted);margin-bottom:4px">보조 직접 채팅</div>
|
||
<select id="orch-allow-secondary-chat" class="settings-input">
|
||
<option value="false">아니요 (권장)</option>
|
||
<option value="true">예</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
<div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:8px;margin-bottom:12px">
|
||
<div>
|
||
<div style="font-size:11px;color:var(--muted);margin-bottom:4px">쿨다운 라운드</div>
|
||
<input id="orch-cooldown-rounds" class="settings-input" type="number" min="1" max="12" value="3" />
|
||
</div>
|
||
<div>
|
||
<div style="font-size:11px;color:var(--muted);margin-bottom:4px">턴당 최대 어시스트</div>
|
||
<input id="orch-max-assists-turn" class="settings-input" type="number" min="1" max="12" value="3" />
|
||
</div>
|
||
<div>
|
||
<div style="font-size:11px;color:var(--muted);margin-bottom:4px">세션당 최대 어시스트</div>
|
||
<input id="orch-max-assists-session" class="settings-input" type="number" min="1" max="100" value="18" />
|
||
</div>
|
||
</div>
|
||
<div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:8px;margin-bottom:12px">
|
||
<div>
|
||
<div style="font-size:11px;color:var(--muted);margin-bottom:4px">턴당 브라우저 어드바이저 호출</div>
|
||
<input id="orch-browser-calls-turn" class="settings-input" type="number" min="1" max="12" value="5" />
|
||
</div>
|
||
<div>
|
||
<div style="font-size:11px;color:var(--muted);margin-bottom:4px">브라우저 최대 수집 항목</div>
|
||
<input id="orch-browser-max-items" class="settings-input" type="number" min="12" max="240" value="80" />
|
||
</div>
|
||
<div>
|
||
<div style="font-size:11px;color:var(--muted);margin-bottom:4px">브라우저 강제 재시도</div>
|
||
<input id="orch-browser-forced-retries" class="settings-input" type="number" min="0" max="6" value="2" />
|
||
</div>
|
||
</div>
|
||
<div id="orch-preempt-panel" style="margin-bottom:12px;padding:10px;border:1px solid var(--line);border-radius:10px;background:var(--panel-2)">
|
||
<div style="font-size:11px;color:var(--muted);margin-bottom:8px">
|
||
Ollama 멈춤 선제조치: 생성이 너무 오래 걸리면 Ollama를 강제 재시작합니다 (스킬 게이트 필요).
|
||
</div>
|
||
<div style="display:grid;grid-template-columns:1fr 1fr 1fr 1fr;gap:8px">
|
||
<div>
|
||
<div style="font-size:11px;color:var(--muted);margin-bottom:4px">선제조치 활성화</div>
|
||
<select id="orch-preempt-enabled" class="settings-input">
|
||
<option value="false">아니요</option>
|
||
<option value="true">예</option>
|
||
</select>
|
||
</div>
|
||
<div>
|
||
<div style="font-size:11px;color:var(--muted);margin-bottom:4px">멈춤 임계값 (초)</div>
|
||
<input id="orch-preempt-threshold" class="settings-input" type="number" min="10" max="300" value="45" />
|
||
</div>
|
||
<div>
|
||
<div style="font-size:11px;color:var(--muted);margin-bottom:4px">턴당 최대 선제조치</div>
|
||
<input id="orch-preempt-max-turn" class="settings-input" type="number" min="1" max="3" value="1" />
|
||
</div>
|
||
<div>
|
||
<div style="font-size:11px;color:var(--muted);margin-bottom:4px">세션당 최대 선제조치</div>
|
||
<input id="orch-preempt-max-session" class="settings-input" type="number" min="1" max="10" value="3" />
|
||
</div>
|
||
</div>
|
||
<div id="orch-preempt-note" style="font-size:10px;color:var(--muted);margin-top:8px;display:none">
|
||
선제조치는 활성 기본 제공자가 Ollama일 때만 실행됩니다.
|
||
</div>
|
||
</div>
|
||
<div id="orch-fileops-panel" style="margin-bottom:12px;padding:10px;border:1px solid var(--line);border-radius:10px;background:var(--panel-2)">
|
||
<div style="font-size:11px;color:var(--muted);margin-bottom:8px">
|
||
FILE_OP v2: 크기 제한, 검증 트리거, 워치독, 체크포인트.
|
||
</div>
|
||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-bottom:10px">
|
||
<div>
|
||
<div style="font-size:11px;color:var(--muted);margin-bottom:4px">FILE_OP v2 활성화</div>
|
||
<select id="orch-fileops-enabled" class="settings-input">
|
||
<option value="true">예</option>
|
||
<option value="false">아니요</option>
|
||
</select>
|
||
</div>
|
||
<div>
|
||
<div style="font-size:11px;color:var(--muted);margin-bottom:4px">생성 시 항상 검증</div>
|
||
<select id="orch-fileops-verify-create" class="settings-input">
|
||
<option value="true">예</option>
|
||
<option value="false">아니요</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-bottom:10px">
|
||
<div>
|
||
<div style="font-size:11px;color:var(--muted);margin-bottom:4px">기본 생성 최대 줄 수</div>
|
||
<input id="orch-fileops-create-lines" class="settings-input" type="number" min="20" max="400" value="80" />
|
||
</div>
|
||
<div>
|
||
<div style="font-size:11px;color:var(--muted);margin-bottom:4px">기본 생성 최대 문자 수</div>
|
||
<input id="orch-fileops-create-chars" class="settings-input" type="number" min="800" max="40000" value="3500" />
|
||
</div>
|
||
</div>
|
||
<div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:8px;margin-bottom:10px">
|
||
<div>
|
||
<div style="font-size:11px;color:var(--muted);margin-bottom:4px">기본 수정 최대 줄 수</div>
|
||
<input id="orch-fileops-edit-lines" class="settings-input" type="number" min="1" max="80" value="12" />
|
||
</div>
|
||
<div>
|
||
<div style="font-size:11px;color:var(--muted);margin-bottom:4px">기본 수정 최대 문자 수</div>
|
||
<input id="orch-fileops-edit-chars" class="settings-input" type="number" min="100" max="8000" value="800" />
|
||
</div>
|
||
<div>
|
||
<div style="font-size:11px;color:var(--muted);margin-bottom:4px">기본 수정 최대 파일 수</div>
|
||
<input id="orch-fileops-edit-files" class="settings-input" type="number" min="1" max="8" value="1" />
|
||
</div>
|
||
</div>
|
||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-bottom:10px">
|
||
<div>
|
||
<div style="font-size:11px;color:var(--muted);margin-bottom:4px">대형 페이로드 검증 줄 수</div>
|
||
<input id="orch-fileops-verify-lines" class="settings-input" type="number" min="5" max="400" value="25" />
|
||
</div>
|
||
<div>
|
||
<div style="font-size:11px;color:var(--muted);margin-bottom:4px">대형 페이로드 검증 문자 수</div>
|
||
<input id="orch-fileops-verify-chars" class="settings-input" type="number" min="200" max="50000" value="1200" />
|
||
</div>
|
||
</div>
|
||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:8px">
|
||
<div>
|
||
<div style="font-size:11px;color:var(--muted);margin-bottom:4px">워치독 진행 없음 사이클</div>
|
||
<input id="orch-fileops-watchdog-cycles" class="settings-input" type="number" min="2" max="8" value="3" />
|
||
</div>
|
||
<div>
|
||
<div style="font-size:11px;color:var(--muted);margin-bottom:4px">체크포인트</div>
|
||
<select id="orch-fileops-checkpointing" class="settings-input">
|
||
<option value="true">활성화</option>
|
||
<option value="false">비활성화</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div id="orch-eligibility-msg" style="font-size:11px;color:var(--muted);margin-bottom:8px;padding:8px 10px;background:var(--panel-2);border:1px solid var(--line);border-radius:10px;display:none"></div>
|
||
</div><!-- /orch-advanced-panel -->
|
||
<button onclick="saveOrchestrationConfig()" class="btn" style="width:100%;margin-bottom:8px">오케스트레이션 설정 저장</button>
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
|
||
<div id="settings-panel-channels" style="display:none;padding:14px 16px">
|
||
<div style="display:grid;grid-template-columns:1.2fr 1fr;gap:14px">
|
||
<div style="border:1px solid var(--line);border-radius:12px;padding:12px;background:#fff">
|
||
<div class="right-section-title" style="margin-bottom:8px">채널 연결</div>
|
||
<label style="display:block;font-size:12px;color:var(--muted);margin-bottom:6px">채널</label>
|
||
<select id="settings-channel-select" class="settings-input" onchange="onChannelTypeChange()" style="margin-bottom:10px">
|
||
<option value="kakao">카카오톡</option>
|
||
<option value="telegram">Telegram</option>
|
||
<option value="discord">Discord</option>
|
||
<option value="whatsapp">WhatsApp</option>
|
||
<option value="email">Email</option>
|
||
</select>
|
||
|
||
<div id="tg-status-bar" style="display:flex;align-items:center;gap:8px;margin-bottom:10px;padding:8px 10px;border-radius:8px;background:#f8f9fa;font-size:12px">
|
||
<div id="tg-status-dot" style="width:8px;height:8px;border-radius:50%;background:#ccc;flex-shrink:0"></div>
|
||
<span id="tg-status-text" style="color:var(--muted)">미설정</span>
|
||
</div>
|
||
<div id="kk-status-bar" style="display:none;align-items:center;gap:8px;margin-bottom:10px;padding:8px 10px;border-radius:8px;background:#f8f9fa;font-size:12px">
|
||
<div id="kk-status-dot" style="width:8px;height:8px;border-radius:50%;background:#ccc;flex-shrink:0"></div>
|
||
<span id="kk-status-text" style="color:var(--muted)">미설정</span>
|
||
</div>
|
||
<div id="dc-status-bar" style="display:none;align-items:center;gap:8px;margin-bottom:10px;padding:8px 10px;border-radius:8px;background:#f8f9fa;font-size:12px">
|
||
<div id="dc-status-dot" style="width:8px;height:8px;border-radius:50%;background:#ccc;flex-shrink:0"></div>
|
||
<span id="dc-status-text" style="color:var(--muted)">미설정</span>
|
||
</div>
|
||
<div id="wa-status-bar" style="display:none;align-items:center;gap:8px;margin-bottom:10px;padding:8px 10px;border-radius:8px;background:#f8f9fa;font-size:12px">
|
||
<div id="wa-status-dot" style="width:8px;height:8px;border-radius:50%;background:#ccc;flex-shrink:0"></div>
|
||
<span id="wa-status-text" style="color:var(--muted)">미설정</span>
|
||
</div>
|
||
<div id="em-status-bar" style="display:none;align-items:center;gap:8px;margin-bottom:10px;padding:8px 10px;border-radius:8px;background:#f8f9fa;font-size:12px">
|
||
<div id="em-status-dot" style="width:8px;height:8px;border-radius:50%;background:#ccc;flex-shrink:0"></div>
|
||
<span id="em-status-text" style="color:var(--muted)">미설정</span>
|
||
</div>
|
||
|
||
<div id="channel-form-telegram">
|
||
<label style="display:block;font-size:12px;color:var(--muted);margin-bottom:6px">Bot Token</label>
|
||
<input id="settings-tg-token" type="password" placeholder="123456:ABC-DEF1234..." style="width:100%;border:1px solid var(--line);border-radius:10px;padding:8px;font-size:12px;font-family:'IBM Plex Mono',monospace;box-sizing:border-box" />
|
||
<label style="display:block;font-size:12px;color:var(--muted);margin:10px 0 6px">내 Telegram 사용자 ID</label>
|
||
<input id="settings-tg-userid" type="text" placeholder="e.g. 123456789, 987654321" style="width:100%;border:1px solid var(--line);border-radius:10px;padding:8px;font-size:12px;font-family:'IBM Plex Mono',monospace;box-sizing:border-box" />
|
||
<label style="display:flex;align-items:center;gap:8px;font-size:13px;margin:10px 0 0;cursor:pointer">
|
||
<input type="checkbox" id="settings-tg-enabled" />
|
||
Telegram 채널 활성화
|
||
</label>
|
||
</div>
|
||
|
||
<div id="channel-form-kakao" style="display:none">
|
||
<label style="display:block;font-size:12px;color:var(--muted);margin-bottom:6px">REST API 키</label>
|
||
<input id="settings-kk-appkey" type="password" placeholder="카카오 REST API 키..." style="width:100%;border:1px solid var(--line);border-radius:10px;padding:8px;font-size:12px;font-family:'IBM Plex Mono',monospace;box-sizing:border-box" />
|
||
<div style="margin-top:10px;padding:10px;border:1px dashed var(--line);border-radius:8px;background:rgba(255,193,7,.05)">
|
||
<div style="font-size:11px;font-weight:700;color:var(--muted);margin-bottom:6px">OAuth 로그인 (나에게 보내기 알림용)</div>
|
||
<div style="font-size:11px;color:var(--muted);margin-bottom:8px">API 키 저장 후 아래 버튼으로 카카오 로그인하면 토큰이 자동 저장됩니다.</div>
|
||
<button onclick="kakaoOAuthStart()" style="background:#FEE500;border:none;border-radius:7px;padding:6px 14px;font-size:12px;font-weight:700;color:#3C1E1E;cursor:pointer">🟡 카카오 로그인</button>
|
||
<button id="kk-send-link-btn" onclick="kakaoSendChannelLink()" style="display:none;background:none;border:1px solid rgba(56,189,248,.4);border-radius:7px;padding:6px 12px;font-size:12px;font-weight:700;color:#38bdf8;cursor:pointer;margin-left:6px">📲 채널 링크 보내기</button>
|
||
<span id="kk-oauth-status" style="font-size:11px;color:var(--muted);margin-left:8px"></span>
|
||
</div>
|
||
<label style="display:block;font-size:12px;color:var(--muted);margin:10px 0 6px">사용자 매핑 (선택, JSON)</label>
|
||
<input id="settings-kk-usermap" type="text" placeholder='{"카카오userId":"homeclaw_username"}' style="width:100%;border:1px solid var(--line);border-radius:10px;padding:8px;font-size:11px;font-family:'IBM Plex Mono',monospace;box-sizing:border-box" />
|
||
<label style="display:flex;align-items:center;gap:8px;font-size:13px;margin:10px 0 0;cursor:pointer">
|
||
<input type="checkbox" id="settings-kk-channel-enabled" />
|
||
챗봇 채널 활성화 (오픈빌더 연동)
|
||
</label>
|
||
<div style="margin-top:14px;padding:10px 12px;border:1px solid rgba(254,229,0,.25);border-radius:8px;background:rgba(254,229,0,.04);font-size:11px;line-height:2;">
|
||
<div style="font-weight:700;color:#b8960a;margin-bottom:4px">📲 채널 접속 링크</div>
|
||
<a href="http://pf.kakao.com/_KxjxexfX/chat" target="_blank" style="color:#38bdf8;word-break:break-all;">pf.kakao.com/_KxjxexfX/chat</a>
|
||
<div style="margin-top:8px;font-weight:700;color:var(--muted)">등록 방법</div>
|
||
<div style="color:var(--muted)">
|
||
1. 카카오 로그인 → 본인 계정 자동 매핑됨<br>
|
||
2. 다른 유저: 위 링크로 채널 추가 후 메시지 전송<br>
|
||
3. 서버 로그에서 userId 확인 → 사용자 매핑에 추가
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="channel-form-discord" style="display:none">
|
||
<label style="display:block;font-size:12px;color:var(--muted);margin-bottom:6px">Bot Token</label>
|
||
<input id="settings-dc-token" type="password" placeholder="Discord bot token" style="width:100%;border:1px solid var(--line);border-radius:10px;padding:8px;font-size:12px;font-family:'IBM Plex Mono',monospace;box-sizing:border-box" />
|
||
<label style="display:block;font-size:12px;color:var(--muted);margin:8px 0 6px">Application ID (optional)</label>
|
||
<input id="settings-dc-appid" type="text" placeholder="Discord app ID" style="width:100%;border:1px solid var(--line);border-radius:10px;padding:8px;font-size:12px;font-family:'IBM Plex Mono',monospace;box-sizing:border-box" />
|
||
<label style="display:block;font-size:12px;color:var(--muted);margin:8px 0 6px">Guild ID (optional)</label>
|
||
<input id="settings-dc-guildid" type="text" placeholder="Server ID" style="width:100%;border:1px solid var(--line);border-radius:10px;padding:8px;font-size:12px;font-family:'IBM Plex Mono',monospace;box-sizing:border-box" />
|
||
<label style="display:block;font-size:12px;color:var(--muted);margin:8px 0 6px">Channel ID (for send-test)</label>
|
||
<input id="settings-dc-channelid" type="text" placeholder="Channel ID" style="width:100%;border:1px solid var(--line);border-radius:10px;padding:8px;font-size:12px;font-family:'IBM Plex Mono',monospace;box-sizing:border-box" />
|
||
<label style="display:block;font-size:12px;color:var(--muted);margin:8px 0 6px">Webhook URL (optional)</label>
|
||
<input id="settings-dc-webhook" type="password" placeholder="https://discord.com/api/webhooks/..." style="width:100%;border:1px solid var(--line);border-radius:10px;padding:8px;font-size:12px;font-family:'IBM Plex Mono',monospace;box-sizing:border-box" />
|
||
<label style="display:flex;align-items:center;gap:8px;font-size:13px;margin:10px 0 0;cursor:pointer">
|
||
<input type="checkbox" id="settings-dc-enabled" />
|
||
Discord 채널 활성화
|
||
</label>
|
||
</div>
|
||
|
||
<div id="channel-form-whatsapp" style="display:none">
|
||
<label style="display:block;font-size:12px;color:var(--muted);margin-bottom:6px">Access Token</label>
|
||
<input id="settings-wa-token" type="password" placeholder="Meta access token" style="width:100%;border:1px solid var(--line);border-radius:10px;padding:8px;font-size:12px;font-family:'IBM Plex Mono',monospace;box-sizing:border-box" />
|
||
<label style="display:block;font-size:12px;color:var(--muted);margin:8px 0 6px">Phone Number ID</label>
|
||
<input id="settings-wa-phoneid" type="text" placeholder="WhatsApp phone number ID" style="width:100%;border:1px solid var(--line);border-radius:10px;padding:8px;font-size:12px;font-family:'IBM Plex Mono',monospace;box-sizing:border-box" />
|
||
<label style="display:block;font-size:12px;color:var(--muted);margin:8px 0 6px">Business Account ID (optional)</label>
|
||
<input id="settings-wa-baid" type="text" placeholder="WABA ID" style="width:100%;border:1px solid var(--line);border-radius:10px;padding:8px;font-size:12px;font-family:'IBM Plex Mono',monospace;box-sizing:border-box" />
|
||
<label style="display:block;font-size:12px;color:var(--muted);margin:8px 0 6px">Webhook Verify Token (optional)</label>
|
||
<input id="settings-wa-verify" type="password" placeholder="Verify token" style="width:100%;border:1px solid var(--line);border-radius:10px;padding:8px;font-size:12px;font-family:'IBM Plex Mono',monospace;box-sizing:border-box" />
|
||
<label style="display:block;font-size:12px;color:var(--muted);margin:8px 0 6px">Webhook Secret (optional)</label>
|
||
<input id="settings-wa-secret" type="password" placeholder="Webhook secret" style="width:100%;border:1px solid var(--line);border-radius:10px;padding:8px;font-size:12px;font-family:'IBM Plex Mono',monospace;box-sizing:border-box" />
|
||
<label style="display:block;font-size:12px;color:var(--muted);margin:8px 0 6px">Test Recipient (E.164)</label>
|
||
<input id="settings-wa-recipient" type="text" placeholder="15551234567" style="width:100%;border:1px solid var(--line);border-radius:10px;padding:8px;font-size:12px;font-family:'IBM Plex Mono',monospace;box-sizing:border-box" />
|
||
<label style="display:flex;align-items:center;gap:8px;font-size:13px;margin:10px 0 0;cursor:pointer">
|
||
<input type="checkbox" id="settings-wa-enabled" />
|
||
WhatsApp 채널 활성화
|
||
</label>
|
||
</div>
|
||
|
||
<div id="channel-form-email" style="display:none">
|
||
<div id="email-accounts-list" style="margin-bottom:10px"></div>
|
||
<div style="border:1px dashed var(--line);border-radius:8px;padding:10px;margin-top:6px">
|
||
<div style="font-size:12px;font-weight:600;margin-bottom:6px">계정 추가 / 수정</div>
|
||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:6px">
|
||
<div>
|
||
<label style="display:block;font-size:11px;color:var(--muted)">계정 ID</label>
|
||
<input id="em-id" type="text" placeholder="applecherry" style="width:100%;border:1px solid var(--line);border-radius:8px;padding:6px;font-size:12px;font-family:'IBM Plex Mono',monospace;box-sizing:border-box" />
|
||
</div>
|
||
<div>
|
||
<label style="display:block;font-size:11px;color:var(--muted)">이메일 주소</label>
|
||
<input id="em-username" type="text" placeholder="user@example.com" style="width:100%;border:1px solid var(--line);border-radius:8px;padding:6px;font-size:12px;font-family:'IBM Plex Mono',monospace;box-sizing:border-box" />
|
||
</div>
|
||
<div>
|
||
<label style="display:block;font-size:11px;color:var(--muted)">비밀번호</label>
|
||
<input id="em-password" type="password" placeholder="••••••••" style="width:100%;border:1px solid var(--line);border-radius:8px;padding:6px;font-size:12px;font-family:'IBM Plex Mono',monospace;box-sizing:border-box" />
|
||
</div>
|
||
<div>
|
||
<label style="display:block;font-size:11px;color:var(--muted)">IMAP 서버</label>
|
||
<input id="em-imap-host" type="text" placeholder="imap.example.com" style="width:100%;border:1px solid var(--line);border-radius:8px;padding:6px;font-size:12px;font-family:'IBM Plex Mono',monospace;box-sizing:border-box" />
|
||
</div>
|
||
<div>
|
||
<label style="display:block;font-size:11px;color:var(--muted)">IMAP 포트</label>
|
||
<input id="em-imap-port" type="number" value="993" style="width:100%;border:1px solid var(--line);border-radius:8px;padding:6px;font-size:12px;font-family:'IBM Plex Mono',monospace;box-sizing:border-box" />
|
||
</div>
|
||
<div>
|
||
<label style="display:block;font-size:11px;color:var(--muted)">SMTP 서버</label>
|
||
<input id="em-smtp-host" type="text" placeholder="smtp.example.com" style="width:100%;border:1px solid var(--line);border-radius:8px;padding:6px;font-size:12px;font-family:'IBM Plex Mono',monospace;box-sizing:border-box" />
|
||
</div>
|
||
<div>
|
||
<label style="display:block;font-size:11px;color:var(--muted)">SMTP 포트</label>
|
||
<input id="em-smtp-port" type="number" value="587" style="width:100%;border:1px solid var(--line);border-radius:8px;padding:6px;font-size:12px;font-family:'IBM Plex Mono',monospace;box-sizing:border-box" />
|
||
</div>
|
||
<div style="display:flex;flex-direction:column;justify-content:end;gap:4px">
|
||
<label style="display:flex;align-items:center;gap:6px;font-size:11px;cursor:pointer"><input type="checkbox" id="em-imap-tls" checked /> IMAP TLS</label>
|
||
<label style="display:flex;align-items:center;gap:6px;font-size:11px;cursor:pointer"><input type="checkbox" id="em-smtp-secure" /> SMTP SSL</label>
|
||
</div>
|
||
</div>
|
||
<div style="display:flex;gap:6px;margin-top:8px">
|
||
<button class="btn btn-sm" onclick="saveEmailAccount()" style="background:var(--brand);color:#fff;border:1px solid var(--brand);padding:5px 12px;font-size:12px">저장</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div style="margin-top:14px;padding-top:10px;border-top:1px solid var(--line)">
|
||
<div style="display:flex;align-items:center;justify-content:space-between">
|
||
<div style="font-size:13px;font-weight:600">허트비트 알림</div>
|
||
<label style="display:flex;align-items:center;gap:8px;font-size:12px;cursor:pointer">
|
||
<input type="checkbox" id="settings-hb-notify" checked onchange="saveHbNotifyConfig()" />
|
||
<span id="settings-hb-notify-label">알림 받기</span>
|
||
</label>
|
||
</div>
|
||
</div>
|
||
|
||
<div style="display:flex;gap:8px;margin-top:12px">
|
||
<button class="btn btn-sm" id="channel-test-btn" onclick="testSelectedChannel()" style="background:#fff;border:1px solid var(--line);color:var(--text)">테스트</button>
|
||
<button class="btn btn-sm" onclick="saveSelectedChannelSettings()" style="background:var(--brand);color:#fff;border:1px solid var(--brand)">저장</button>
|
||
<button class="btn btn-sm" id="channel-send-test-btn" onclick="sendSelectedChannelTest()" style="background:#fff;border:1px solid var(--line);color:var(--text)">테스트 전송</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div style="border:1px solid var(--line);border-radius:12px;padding:12px;background:#fff">
|
||
<div class="right-section-title" style="margin-bottom:8px">안내</div>
|
||
<div id="channel-guide-telegram" style="font-size:12px;color:var(--muted);line-height:1.8">
|
||
<b>Telegram Setup</b><br>
|
||
1. Create a bot via <a href="https://t.me/BotFather" target="_blank" style="color:var(--brand)">@BotFather</a>.<br>
|
||
2. Copy the bot token into this panel.<br>
|
||
3. Get your user ID(s) from <a href="https://t.me/userinfobot" target="_blank" style="color:var(--brand)">@userinfobot</a>.<br>
|
||
4. Add allowed IDs, then click <b>Test</b> and <b>Save</b>.<br>
|
||
5. Use <b>Send Test</b> to verify delivery.
|
||
</div>
|
||
<div id="channel-guide-kakao" style="display:none;font-size:12px;color:var(--muted);line-height:1.8">
|
||
<b>카카오톡 Setup</b><br>
|
||
<b>1.</b> <a href="https://developers.kakao.com" target="_blank" style="color:var(--brand)">developers.kakao.com</a> 에서 앱 생성<br>
|
||
<b>2.</b> 앱 설정 → 플랫폼 → Web 추가 (서버 도메인)<br>
|
||
<b>3.</b> 카카오 로그인 활성화 → 동의항목: <code style="background:#f4f4f4;padding:1px 4px;border-radius:3px">talk_message</code><br>
|
||
<b>4.</b> 허용된 리다이렉트 URI에 <code style="background:#f4f4f4;padding:1px 4px;border-radius:3px">{서버주소}/api/kakao/auth/callback</code> 추가<br>
|
||
<b>5.</b> REST API 키 입력 → 저장 → 🟡 카카오 로그인<br><br>
|
||
<b>챗봇 (오픈빌더)</b><br>
|
||
<b>6.</b> <a href="https://i.kakao.com" target="_blank" style="color:var(--brand)">i.kakao.com</a> 에서 챗봇 생성<br>
|
||
<b>7.</b> 스킬 서버 URL: <code style="background:#f4f4f4;padding:1px 4px;border-radius:3px">{서버주소}/api/kakao/webhook</code><br>
|
||
<b>8.</b> 스킬 편집 → 콜백 사용 체크 → 시나리오 폴백 블록에 스킬 연결<br>
|
||
<b>9.</b> 챗봇 채널 활성화 체크 → 저장
|
||
</div>
|
||
<div id="channel-guide-discord" style="display:none;font-size:12px;color:var(--muted);line-height:1.8">
|
||
<b>Discord Setup</b><br>
|
||
1. Create an app + bot in Discord Developer Portal.<br>
|
||
2. Enable required intents (including Message Content if needed).<br>
|
||
3. Invite bot to your server with message permissions.<br>
|
||
4. Paste bot token; optionally add app/guild/channel IDs.<br>
|
||
5. Click <b>Test</b>, then <b>Save</b>, then <b>Send Test</b>.
|
||
</div>
|
||
<div id="channel-guide-whatsapp" style="display:none;font-size:12px;color:var(--muted);line-height:1.8">
|
||
<b>WhatsApp Cloud API Setup</b><br>
|
||
1. In Meta Developer dashboard, open WhatsApp Cloud API.<br>
|
||
2. Copy access token and phone number ID into this panel.<br>
|
||
3. Configure webhook verify token/secret (if using inbound webhooks).<br>
|
||
4. Add a test recipient number in E.164 format.<br>
|
||
5. Click <b>Test</b>, <b>Save</b>, then <b>Send Test</b>.
|
||
</div>
|
||
<div id="channel-guide-email" style="display:none;font-size:12px;color:var(--muted);line-height:1.8">
|
||
<b>Email 계정 설정</b><br>
|
||
1. IMAP/SMTP 서버 주소와 포트를 입력하세요.<br>
|
||
2. 일반적인 포트: IMAP=993(TLS), SMTP=587(STARTTLS) 또는 465(SSL).<br>
|
||
3. Gmail은 앱 비밀번호 사용 (imap.gmail.com / smtp.gmail.com).<br>
|
||
4. 저장 후 <b>테스트</b> 버튼으로 IMAP 연결을 확인하세요.<br>
|
||
5. 여러 계정을 추가하면 email_list에서 모든 계정을 순회합니다.
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="settings-panel-agents" style="display:none;padding:14px 16px">
|
||
<div style="display:grid;grid-template-columns:0.9fr 1.1fr;gap:14px">
|
||
<div style="border:1px solid var(--line);border-radius:12px;padding:12px;background:#fff">
|
||
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:8px">
|
||
<div class="right-section-title">구성된 에이전트</div>
|
||
<button class="btn btn-sm" style="padding:4px 8px" onclick="agentFormNew()">+ New</button>
|
||
</div>
|
||
<div id="settings-agents-list" style="display:flex;flex-direction:column;gap:8px;max-height:520px;overflow:auto;font-size:12px;color:var(--muted)">에이전트 로딩 중...</div>
|
||
</div>
|
||
<div style="display:flex;flex-direction:column;gap:12px">
|
||
<div style="border:1px solid var(--line);border-radius:12px;padding:12px;background:#fff">
|
||
<div class="right-section-title" style="margin-bottom:8px">에이전트 상세</div>
|
||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:8px">
|
||
<div>
|
||
<label style="display:block;font-size:11px;color:var(--muted);margin-bottom:4px">ID</label>
|
||
<input id="agent-edit-id" type="text" placeholder="researcher" style="width:100%;border:1px solid var(--line);border-radius:8px;padding:6px 8px;font-size:12px;font-family:'IBM Plex Mono',monospace;box-sizing:border-box" />
|
||
</div>
|
||
<div>
|
||
<label style="display:block;font-size:11px;color:var(--muted);margin-bottom:4px">Name</label>
|
||
<input id="agent-edit-name" type="text" placeholder="Scout" style="width:100%;border:1px solid var(--line);border-radius:8px;padding:6px 8px;font-size:12px;box-sizing:border-box" />
|
||
</div>
|
||
<div>
|
||
<label style="display:block;font-size:11px;color:var(--muted);margin-bottom:4px">Emoji</label>
|
||
<input id="agent-edit-emoji" type="text" placeholder="🔍" style="width:100%;border:1px solid var(--line);border-radius:8px;padding:6px 8px;font-size:12px;box-sizing:border-box" />
|
||
</div>
|
||
<div>
|
||
<label style="display:block;font-size:11px;color:var(--muted);margin-bottom:4px">도구 프로필</label>
|
||
<select id="agent-edit-profile" style="width:100%;border:1px solid var(--line);border-radius:8px;padding:6px 8px;font-size:12px;box-sizing:border-box">
|
||
<option value="">(inherit)</option>
|
||
<option value="minimal">minimal</option>
|
||
<option value="coding">coding</option>
|
||
<option value="web">web</option>
|
||
<option value="full">full</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
<label style="display:block;font-size:11px;color:var(--muted);margin:8px 0 4px">설명</label>
|
||
<textarea id="agent-edit-description" rows="3" style="width:100%;border:1px solid var(--line);border-radius:8px;padding:6px 8px;font-size:12px;box-sizing:border-box;resize:vertical"></textarea>
|
||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-top:8px">
|
||
<div>
|
||
<label style="display:block;font-size:11px;color:var(--muted);margin-bottom:4px">워크스페이스</label>
|
||
<input id="agent-edit-workspace" type="text" placeholder="D:/HomeClaw/agents/researcher/workspace" style="width:100%;border:1px solid var(--line);border-radius:8px;padding:6px 8px;font-size:12px;font-family:'IBM Plex Mono',monospace;box-sizing:border-box" />
|
||
</div>
|
||
<div>
|
||
<label style="display:block;font-size:11px;color:var(--muted);margin-bottom:4px">Model</label>
|
||
<input id="agent-edit-model" type="text" placeholder="ollama/qwen3:4b" style="width:100%;border:1px solid var(--line);border-radius:8px;padding:6px 8px;font-size:12px;font-family:'IBM Plex Mono',monospace;box-sizing:border-box" />
|
||
</div>
|
||
<div>
|
||
<label style="display:block;font-size:11px;color:var(--muted);margin-bottom:4px">크론 일정</label>
|
||
<input id="agent-edit-cron" type="text" placeholder="0 8 * * *" style="width:100%;border:1px solid var(--line);border-radius:8px;padding:6px 8px;font-size:12px;font-family:'IBM Plex Mono',monospace;box-sizing:border-box" />
|
||
</div>
|
||
<div>
|
||
<label style="display:block;font-size:11px;color:var(--muted);margin-bottom:4px">최대 단계</label>
|
||
<input id="agent-edit-max-steps" type="number" min="1" step="1" placeholder="8" style="width:100%;border:1px solid var(--line);border-radius:8px;padding:6px 8px;font-size:12px;box-sizing:border-box" />
|
||
</div>
|
||
</div>
|
||
<div style="display:flex;gap:12px;flex-wrap:wrap;margin-top:10px;font-size:12px">
|
||
<label style="display:flex;align-items:center;gap:6px"><input type="checkbox" id="agent-edit-default" /> default</label>
|
||
<label style="display:flex;align-items:center;gap:6px"><input type="checkbox" id="agent-edit-minimal" /> minimalPrompt</label>
|
||
<label style="display:flex;align-items:center;gap:6px"><input type="checkbox" id="agent-edit-can-spawn" /> canSpawn</label>
|
||
</div>
|
||
<label style="display:block;font-size:11px;color:var(--muted);margin:8px 0 4px">Spawn Allowlist (comma-separated IDs)</label>
|
||
<input id="agent-edit-spawn-allowlist" type="text" placeholder="researcher,writer" style="width:100%;border:1px solid var(--line);border-radius:8px;padding:6px 8px;font-size:12px;font-family:'IBM Plex Mono',monospace;box-sizing:border-box" />
|
||
<div style="display:flex;gap:8px;margin-top:10px">
|
||
<button class="btn btn-sm" onclick="saveAgentFromForm()">에이전트 저장</button>
|
||
<button class="btn btn-sm" style="background:#fff;border:1px solid var(--line);color:var(--text)" onclick="deleteSelectedAgent()">삭제</button>
|
||
</div>
|
||
</div>
|
||
<div style="border:1px solid var(--line);border-radius:12px;padding:12px;background:#fff">
|
||
<div class="right-section-title" style="margin-bottom:8px">AGENTS.md (CodeMirror)</div>
|
||
<div id="agent-md-editor-wrap" style="border:1px solid var(--line);border-radius:8px;overflow:hidden"></div>
|
||
<div style="display:flex;gap:8px;margin-top:8px">
|
||
<button class="btn btn-sm" style="background:#fff;border:1px solid var(--line);color:var(--text)" onclick="loadSelectedAgentMd()">Reload File</button>
|
||
<button class="btn btn-sm" onclick="saveSelectedAgentMd()">Save AGENTS.md</button>
|
||
</div>
|
||
<div id="agent-md-path" style="margin-top:6px;font-size:11px;color:var(--muted);font-family:'IBM Plex Mono',monospace"></div>
|
||
</div>
|
||
<div style="border:1px solid var(--line);border-radius:12px;padding:12px;background:#fff">
|
||
<div class="right-section-title" style="margin-bottom:8px">Manual Spawn + Run History</div>
|
||
<label style="display:block;font-size:11px;color:var(--muted);margin-bottom:4px">Task</label>
|
||
<textarea id="agent-spawn-task" rows="3" style="width:100%;border:1px solid var(--line);border-radius:8px;padding:6px 8px;font-size:12px;box-sizing:border-box;resize:vertical" placeholder="Run a one-off task for this agent..."></textarea>
|
||
<div style="display:flex;gap:8px;margin-top:8px">
|
||
<button class="btn btn-sm" onclick="runSelectedAgentOnce()">Run Once</button>
|
||
<button class="btn btn-sm" style="background:#fff;border:1px solid var(--line);color:var(--text)" onclick="loadAgentRunHistory()">Refresh History</button>
|
||
</div>
|
||
<div id="agent-spawn-result" style="margin-top:8px;font-size:12px;color:var(--muted);white-space:pre-wrap;max-height:120px;overflow:auto"></div>
|
||
<div id="agent-run-history" style="margin-top:8px;display:flex;flex-direction:column;gap:6px;max-height:170px;overflow:auto;font-size:12px"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ── Voice Settings ────────────────────────────────────────── -->
|
||
<div id="settings-panel-voice" style="display:none;padding:14px 16px">
|
||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:14px">
|
||
<!-- STT -->
|
||
<div style="border:1px solid var(--line);border-radius:12px;padding:14px">
|
||
<div class="right-section-title" style="margin-bottom:10px">음성 인식 (STT)</div>
|
||
<label style="display:block;font-size:12px;color:var(--muted);margin-bottom:4px">제공자</label>
|
||
<select id="voice-stt-provider" class="settings-input" style="margin-bottom:10px;font-size:13px">
|
||
<option value="whisper_cpp">Whisper.cpp (로컬)</option>
|
||
<option value="openai">OpenAI Whisper (API)</option>
|
||
</select>
|
||
<label style="display:block;font-size:12px;color:var(--muted);margin-bottom:4px">모델 파일</label>
|
||
<input id="voice-stt-model" type="text" class="settings-input" placeholder="ggml-base.bin" style="margin-bottom:10px;font-size:12px" />
|
||
<label style="display:block;font-size:12px;color:var(--muted);margin-bottom:4px">언어</label>
|
||
<input id="voice-stt-language" type="text" class="settings-input" placeholder="ko" style="font-size:12px" />
|
||
</div>
|
||
<!-- TTS -->
|
||
<div style="border:1px solid var(--line);border-radius:12px;padding:14px">
|
||
<div class="right-section-title" style="margin-bottom:10px">음성 합성 (TTS)</div>
|
||
<label style="display:block;font-size:12px;color:var(--muted);margin-bottom:4px">제공자</label>
|
||
<select id="voice-tts-provider" class="settings-input" style="margin-bottom:10px;font-size:13px">
|
||
<option value="piper">Piper (로컬)</option>
|
||
<option value="edge_tts">Edge TTS (Microsoft)</option>
|
||
<option value="openai">OpenAI TTS (API)</option>
|
||
</select>
|
||
<label style="display:block;font-size:12px;color:var(--muted);margin-bottom:4px">모델 / 목소리</label>
|
||
<input id="voice-tts-model" type="text" class="settings-input" placeholder="ko_KR-kss-medium" style="font-size:12px" />
|
||
</div>
|
||
</div>
|
||
<div style="margin-top:14px;display:flex;justify-content:flex-end">
|
||
<button class="btn btn-sm" onclick="saveVoiceSettings()">음성 설정 저장</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ── Session Settings ──────────────────────────────────────── -->
|
||
<div id="settings-panel-session" style="display:none;padding:14px 16px">
|
||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:14px">
|
||
<div style="border:1px solid var(--line);border-radius:12px;padding:14px">
|
||
<div class="right-section-title" style="margin-bottom:10px">메시지 관리</div>
|
||
<label style="display:block;font-size:12px;color:var(--muted);margin-bottom:4px">최대 메시지 수</label>
|
||
<input id="session-max-messages" type="number" class="settings-input" min="20" max="500" step="10" style="margin-bottom:10px;font-size:12px" />
|
||
<label style="display:block;font-size:12px;color:var(--muted);margin-bottom:4px">히스토리 턴 수 <span style="font-weight:400">(모델에 전달)</span></label>
|
||
<input id="session-history-turns" type="number" class="settings-input" min="2" max="40" step="1" style="font-size:12px" />
|
||
</div>
|
||
<div style="border:1px solid var(--line);border-radius:12px;padding:14px">
|
||
<div class="right-section-title" style="margin-bottom:10px">컴팩션 임계값</div>
|
||
<label style="display:block;font-size:12px;color:var(--muted);margin-bottom:4px">컨텍스트 압축 시작 비율 <span style="font-weight:400">(0.0 – 1.0)</span></label>
|
||
<input id="session-compaction" type="number" class="settings-input" min="0.3" max="0.95" step="0.05" style="margin-bottom:10px;font-size:12px" />
|
||
<label style="display:block;font-size:12px;color:var(--muted);margin-bottom:4px">메모리 플러시 임계값 <span style="font-weight:400">(0.0 – 1.0)</span></label>
|
||
<input id="session-memory-flush" type="number" class="settings-input" min="0.3" max="0.99" step="0.05" style="font-size:12px" />
|
||
</div>
|
||
</div>
|
||
<div style="margin-top:14px;display:flex;justify-content:flex-end">
|
||
<button class="btn btn-sm" onclick="saveSessionSettings()">세션 설정 저장</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="settings-panel-integrations" style="display:none;padding:14px 16px">
|
||
<!-- Sub-tab row -->
|
||
<div style="display:flex;gap:8px;margin-bottom:14px">
|
||
<button id="itab-webhooks" onclick="setIntegTab('webhooks')" style="padding:5px 14px;border-radius:8px;font-size:12px;font-weight:600;cursor:pointer;border:1px solid var(--line);background:#eaf2ff;color:#0d4faf">Webhooks</button>
|
||
<button id="itab-mcp" onclick="setIntegTab('mcp')" style="padding:5px 14px;border-radius:8px;font-size:12px;font-weight:600;cursor:pointer;border:1px solid var(--line);background:#fff;color:var(--muted)">MCP Servers</button>
|
||
</div>
|
||
|
||
<!-- WEBHOOKS PANEL ----------------------------------------------- -->
|
||
<div id="itab-panel-webhooks">
|
||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:14px">
|
||
<!-- Left: config -->
|
||
<div style="border:1px solid var(--line);border-radius:12px;padding:14px;background:#fff">
|
||
<div class="right-section-title" style="margin-bottom:10px">Webhook Endpoint</div>
|
||
|
||
<div id="wh-status-bar" style="display:flex;align-items:center;gap:8px;margin-bottom:12px;padding:8px 10px;border-radius:8px;background:#f8f9fa;font-size:12px">
|
||
<div id="wh-status-dot" style="width:8px;height:8px;border-radius:50%;background:#ccc;flex-shrink:0"></div>
|
||
<span id="wh-status-text" style="color:var(--muted)">미설정</span>
|
||
</div>
|
||
|
||
<label style="display:flex;align-items:center;gap:8px;font-size:13px;margin-bottom:10px;cursor:pointer">
|
||
<input type="checkbox" id="wh-enabled" />
|
||
Enable webhook endpoint
|
||
</label>
|
||
|
||
<label style="display:block;font-size:12px;color:var(--muted);margin-bottom:4px">Secret Token</label>
|
||
<div style="display:flex;gap:6px;margin-bottom:10px">
|
||
<input id="wh-token" type="password" placeholder="Paste or generate a secret token" style="flex:1;border:1px solid var(--line);border-radius:8px;padding:7px 10px;font-size:12px;font-family:'IBM Plex Mono',monospace" />
|
||
<button onclick="generateWebhookToken()" style="padding:6px 10px;border:1px solid var(--line);border-radius:8px;background:#f8f9fa;font-size:11px;cursor:pointer;white-space:nowrap">Generate</button>
|
||
</div>
|
||
|
||
<label style="display:block;font-size:12px;color:var(--muted);margin-bottom:4px">URL Path Prefix</label>
|
||
<input id="wh-path" type="text" value="/hooks" style="width:100%;border:1px solid var(--line);border-radius:8px;padding:7px 10px;font-size:12px;font-family:'IBM Plex Mono',monospace;box-sizing:border-box;margin-bottom:12px" />
|
||
|
||
<div id="wh-url-display" style="display:none;background:#f0f7ff;border:1px solid #c7dff7;border-radius:8px;padding:8px 10px;font-size:11px;font-family:'IBM Plex Mono',monospace;color:#0d4faf;margin-bottom:12px;word-break:break-all"></div>
|
||
|
||
<div style="display:flex;gap:8px">
|
||
<button onclick="saveWebhookSettings()" style="padding:6px 14px;background:var(--brand);color:#fff;border:none;border-radius:8px;font-size:12px;font-weight:600;cursor:pointer">Save</button>
|
||
<button onclick="testWebhookEndpoint()" style="padding:6px 14px;background:#fff;color:var(--text);border:1px solid var(--line);border-radius:8px;font-size:12px;cursor:pointer">Test</button>
|
||
<button onclick="copyWebhookCurl()" style="padding:6px 14px;background:#fff;color:var(--text);border:1px solid var(--line);border-radius:8px;font-size:12px;cursor:pointer">Copy cURL</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Right: guide -->
|
||
<div style="border:1px solid var(--line);border-radius:12px;padding:14px;background:#fff">
|
||
<div class="right-section-title" style="margin-bottom:8px">Setup Guide</div>
|
||
<div style="font-size:12px;color:var(--muted);line-height:1.85">
|
||
<b>1. Enable + set a token</b> — any secret string you choose.<br>
|
||
<b>2. Save</b> and restart the gateway to activate.<br>
|
||
<b>3. Access from the internet?</b> Use
|
||
<a href="https://tailscale.com" target="_blank" style="color:var(--brand)">Tailscale</a> (recommended),
|
||
<a href="https://ngrok.com" target="_blank" style="color:var(--brand)">ngrok</a>, or
|
||
<a href="https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/" target="_blank" style="color:var(--brand)">Cloudflare Tunnel</a>.
|
||
<br><br>
|
||
<b>Endpoints:</b><br>
|
||
<code style="background:#f4f4f4;padding:2px 5px;border-radius:4px">POST /hooks/agent</code> — full AI run<br>
|
||
<code style="background:#f4f4f4;padding:2px 5px;border-radius:4px">POST /hooks/wake</code> — lightweight nudge<br>
|
||
<code style="background:#f4f4f4;padding:2px 5px;border-radius:4px">GET /hooks/status</code> — health check<br><br>
|
||
<b>Auth:</b> Send token as<br>
|
||
<code style="background:#f4f4f4;padding:2px 5px;border-radius:4px">Authorization: Bearer <token></code><br>
|
||
or <code style="background:#f4f4f4;padding:2px 5px;border-radius:4px">X-Gateway-Token: <token></code><br><br>
|
||
Works with: GitHub Actions, Stripe, Vercel, n8n, IFTTT, Home Assistant, or any script.
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- MCP PANEL ---------------------------------------------------- -->
|
||
<div id="itab-panel-mcp" style="display:none">
|
||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:14px">
|
||
<!-- Left: server list + add form -->
|
||
<div>
|
||
<div style="border:1px solid var(--line);border-radius:12px;padding:14px;background:#fff;margin-bottom:12px">
|
||
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:10px">
|
||
<div class="right-section-title">MCP Servers</div>
|
||
<button onclick="showMCPAddForm()" style="padding:4px 10px;border:1px solid var(--brand);border-radius:6px;background:#fff;color:var(--brand);font-size:11px;font-weight:600;cursor:pointer">+ Add Server</button>
|
||
</div>
|
||
<div id="mcp-server-list" style="font-size:12px;color:var(--muted)">Loading...</div>
|
||
</div>
|
||
|
||
<!-- Add/Edit form (hidden by default) -->
|
||
<div id="mcp-add-form" style="display:none;border:1px solid var(--brand);border-radius:12px;padding:14px;background:#f8faff">
|
||
<div class="right-section-title" style="margin-bottom:10px" id="mcp-form-title">Add MCP Server</div>
|
||
|
||
<label style="display:block;font-size:11px;color:var(--muted);margin-bottom:3px">ID <span style="color:#888">(letters/numbers/dash, no spaces)</span></label>
|
||
<input id="mcp-f-id" type="text" placeholder="e.g. filesystem" style="width:100%;border:1px solid var(--line);border-radius:7px;padding:6px 9px;font-size:12px;box-sizing:border-box;margin-bottom:8px" />
|
||
|
||
<label style="display:block;font-size:11px;color:var(--muted);margin-bottom:3px">Display Name</label>
|
||
<input id="mcp-f-name" type="text" placeholder="e.g. Filesystem" style="width:100%;border:1px solid var(--line);border-radius:7px;padding:6px 9px;font-size:12px;box-sizing:border-box;margin-bottom:8px" />
|
||
|
||
<label style="display:block;font-size:11px;color:var(--muted);margin-bottom:3px">Transport</label>
|
||
<select id="mcp-f-transport" onchange="onMCPTransportChange()" style="width:100%;border:1px solid var(--line);border-radius:7px;padding:6px 9px;font-size:12px;box-sizing:border-box;margin-bottom:8px">
|
||
<option value="stdio">stdio (local process)</option>
|
||
<option value="sse">HTTP/SSE (remote)</option>
|
||
</select>
|
||
|
||
<!-- stdio fields -->
|
||
<div id="mcp-stdio-fields">
|
||
<label style="display:block;font-size:11px;color:var(--muted);margin-bottom:3px">Command</label>
|
||
<input id="mcp-f-command" type="text" placeholder="e.g. npx" style="width:100%;border:1px solid var(--line);border-radius:7px;padding:6px 9px;font-size:12px;box-sizing:border-box;margin-bottom:8px" />
|
||
|
||
<label style="display:block;font-size:11px;color:var(--muted);margin-bottom:3px">Arguments <span style="color:#888">(one per line)</span></label>
|
||
<textarea id="mcp-f-args" placeholder="-y @modelcontextprotocol/server-filesystem C:\Users\you\Documents" style="width:100%;border:1px solid var(--line);border-radius:7px;padding:6px 9px;font-size:12px;box-sizing:border-box;height:64px;resize:vertical;font-family:'IBM Plex Mono',monospace;margin-bottom:8px"></textarea>
|
||
|
||
<label style="display:block;font-size:11px;color:var(--muted);margin-bottom:3px">Environment Variables <span style="color:#888">(KEY=VALUE, one per line)</span></label>
|
||
<textarea id="mcp-f-env" placeholder="GITHUB_PERSONAL_ACCESS_TOKEN=ghp_xxx" style="width:100%;border:1px solid var(--line);border-radius:7px;padding:6px 9px;font-size:12px;box-sizing:border-box;height:52px;resize:vertical;font-family:'IBM Plex Mono',monospace;margin-bottom:8px"></textarea>
|
||
</div>
|
||
|
||
<!-- SSE fields -->
|
||
<div id="mcp-sse-fields" style="display:none">
|
||
<label style="display:block;font-size:11px;color:var(--muted);margin-bottom:3px">Server URL</label>
|
||
<input id="mcp-f-url" type="text" placeholder="https://mcp.example.com/sse" style="width:100%;border:1px solid var(--line);border-radius:7px;padding:6px 9px;font-size:12px;box-sizing:border-box;margin-bottom:8px" />
|
||
|
||
<label style="display:block;font-size:11px;color:var(--muted);margin-bottom:3px">Headers <span style="color:#888">(KEY: value, one per line)</span></label>
|
||
<textarea id="mcp-f-headers" placeholder="Authorization: Bearer token123" style="width:100%;border:1px solid var(--line);border-radius:7px;padding:6px 9px;font-size:12px;box-sizing:border-box;height:52px;resize:vertical;font-family:'IBM Plex Mono',monospace;margin-bottom:8px"></textarea>
|
||
</div>
|
||
|
||
<label style="display:flex;align-items:center;gap:7px;font-size:12px;margin-bottom:10px;cursor:pointer">
|
||
<input type="checkbox" id="mcp-f-enabled" checked />
|
||
Enable on startup
|
||
</label>
|
||
|
||
<div style="display:flex;gap:8px">
|
||
<button onclick="saveMCPServer()" style="padding:6px 14px;background:var(--brand);color:#fff;border:none;border-radius:7px;font-size:12px;font-weight:600;cursor:pointer">Save</button>
|
||
<button onclick="hideMCPAddForm()" style="padding:6px 14px;background:#fff;color:var(--muted);border:1px solid var(--line);border-radius:7px;font-size:12px;cursor:pointer">Cancel</button>
|
||
</div>
|
||
<div id="mcp-form-msg" style="margin-top:8px;font-size:11px;color:var(--muted)"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Right: info + popular servers -->
|
||
<div>
|
||
<div style="border:1px solid var(--line);border-radius:12px;padding:14px;background:#fff;margin-bottom:12px">
|
||
<div class="right-section-title" style="margin-bottom:8px">What is MCP?</div>
|
||
<div style="font-size:12px;color:var(--muted);line-height:1.85">
|
||
<b>Model Context Protocol</b> (by Anthropic) is a standard that lets AI connect to external tools and data sources.<br><br>
|
||
Each MCP server exposes a set of <b>tools</b> — HomeClaw discovers them automatically when you connect.
|
||
The AI can then call them during tasks, just like its built-in tools.<br><br>
|
||
Servers run <b>locally as child processes</b> (stdio) or connect to <b>remote HTTP endpoints</b> (SSE).
|
||
No data leaves your machine unless the tool itself calls an external API.
|
||
</div>
|
||
</div>
|
||
|
||
<div style="border:1px solid var(--line);border-radius:12px;padding:14px;background:#fff">
|
||
<div class="right-section-title" style="margin-bottom:8px">Popular Servers</div>
|
||
<div style="font-size:12px;color:var(--muted);line-height:1">
|
||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:6px">
|
||
<button onclick="prefillMCPServer('filesystem')" style="text-align:left;padding:7px 9px;border:1px solid var(--line);border-radius:7px;background:#fafafa;cursor:pointer;font-size:11px">
|
||
<div style="font-weight:600;color:var(--text)">Filesystem</div>
|
||
<div style="color:var(--muted)">Read/write local files</div>
|
||
</button>
|
||
<button onclick="prefillMCPServer('github')" style="text-align:left;padding:7px 9px;border:1px solid var(--line);border-radius:7px;background:#fafafa;cursor:pointer;font-size:11px">
|
||
<div style="font-weight:600;color:var(--text)">GitHub</div>
|
||
<div style="color:var(--muted)">Repos, PRs, issues</div>
|
||
</button>
|
||
<button onclick="prefillMCPServer('postgres')" style="text-align:left;padding:7px 9px;border:1px solid var(--line);border-radius:7px;background:#fafafa;cursor:pointer;font-size:11px">
|
||
<div style="font-weight:600;color:var(--text)">PostgreSQL</div>
|
||
<div style="color:var(--muted)">Query your DB</div>
|
||
</button>
|
||
<button onclick="prefillMCPServer('sqlite')" style="text-align:left;padding:7px 9px;border:1px solid var(--line);border-radius:7px;background:#fafafa;cursor:pointer;font-size:11px">
|
||
<div style="font-weight:600;color:var(--text)">SQLite</div>
|
||
<div style="color:var(--muted)">Local database queries</div>
|
||
</button>
|
||
<button onclick="prefillMCPServer('brave')" style="text-align:left;padding:7px 9px;border:1px solid var(--line);border-radius:7px;background:#fafafa;cursor:pointer;font-size:11px">
|
||
<div style="font-weight:600;color:var(--text)">Brave Search</div>
|
||
<div style="color:var(--muted)">Web search via MCP</div>
|
||
</button>
|
||
<button onclick="prefillMCPServer('memory')" style="text-align:left;padding:7px 9px;border:1px solid var(--line);border-radius:7px;background:#fafafa;cursor:pointer;font-size:11px">
|
||
<div style="font-weight:600;color:var(--text)">Memory</div>
|
||
<div style="color:var(--muted)">Persistent knowledge store</div>
|
||
</button>
|
||
</div>
|
||
<div style="margin-top:8px;font-size:11px">
|
||
<a href="https://github.com/modelcontextprotocol/servers" target="_blank" style="color:var(--brand)">Browse all official servers →</a>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
|
||
|
||
<div id="settings-panel-context-viewer" style="display:none;padding:14px 16px">
|
||
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:12px">
|
||
<div class="right-section-title">사용자 컨텍스트 뷰어</div>
|
||
<button class="btn btn-sm" style="background:var(--panel-2);border:1px solid var(--line);color:var(--muted)" onclick="loadContextViewerTab()">새로고침</button>
|
||
</div>
|
||
<div id="cv-warn-banner" style="display:none;padding:8px 12px;background:#fff7ed;border:1px solid #fbbf24;border-radius:8px;font-size:12px;color:#92400e;margin-bottom:12px"></div>
|
||
<div id="cv-sessions-list" style="display:flex;flex-direction:column;gap:10px">
|
||
<div style="color:var(--muted);font-size:13px;text-align:center;padding:20px">로딩 중...</div>
|
||
</div>
|
||
|
||
<div style="display:flex;align-items:center;justify-content:space-between;margin:20px 0 6px">
|
||
<div class="right-section-title">fail2ban 차단 IP</div>
|
||
<button class="btn btn-sm" style="background:var(--panel-2);border:1px solid var(--line);color:var(--muted)" onclick="loadBannedIpsTab()">새로고침</button>
|
||
</div>
|
||
<div id="cv-banned-ips-stat" style="font-size:11px;color:var(--muted);margin-bottom:8px"></div>
|
||
<div id="cv-banned-ips-list" style="display:flex;flex-direction:column;gap:6px">
|
||
<div style="color:var(--muted);font-size:13px;text-align:center;padding:20px">로딩 중...</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div style="display:flex;justify-content:flex-end;gap:8px;padding:12px 16px;border-top:1px solid var(--line)">
|
||
<button class="btn btn-sm" style="background:var(--panel-2);color:var(--muted);border:1px solid var(--line)" onclick="closeSettings()">취소</button>
|
||
<button class="btn btn-sm" onclick="saveSettings()">저장</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script src="../js/voice/voice-call.js"></script>
|
||
<script src="../js/app/helpers-core.js"></script>
|
||
<script src="../js/app/markdown.js"></script>
|
||
<script src="../js/app/voice.js"></script>
|
||
<script src="../js/app/media.js"></script>
|
||
<script src="../js/app/files.js"></script>
|
||
<script src="../js/app/chat-sse.js"></script>
|
||
<script src="../js/app/chat-sessions.js"></script>
|
||
<script src="../js/app/chat-render.js"></script>
|
||
<script src="../js/app/settings-provider.js"></script>
|
||
<script src="../js/app/settings-agents.js"></script>
|
||
<script src="../js/app/settings-channels.js"></script>
|
||
<script src="../js/app/bg-tasks.js"></script>
|
||
<script src="../js/app/schedules.js"></script>
|
||
<script src="../js/app/integrations.js"></script>
|
||
<script src="../js/app/error-panel.js"></script>
|
||
<script src="../js/app/terminal.js"></script>
|
||
<script src="../js/app/pptx-workflow.js"></script>
|
||
<script src="../js/app/app.js"></script>
|
||
|
||
<!-- Code folder browser dropdown (fixed, rendered outside any overflow:hidden container) -->
|
||
|
||
<div id="image-zoom-modal" onclick="closeImageZoom()">
|
||
<img id="image-zoom-img" src="" alt="Zoomed image" />
|
||
</div>
|
||
<!-- ── Luckysheet Modal ─────────────────────────────────────────── -->
|
||
<div id="lucky-modal" style="display:none;position:fixed;inset:0;z-index:9999;background:rgba(0,0,0,0.55);align-items:center;justify-content:center">
|
||
<div id="lucky-modal-box" style="background:#fff;width:96vw;height:92vh;border-radius:10px;display:flex;flex-direction:column;overflow:hidden;box-shadow:0 8px 40px rgba(0,0,0,0.35);position:relative">
|
||
<div style="display:flex;align-items:center;gap:10px;padding:10px 14px;background:#f5f5f5;border-bottom:1px solid #ddd;flex-shrink:0;cursor:default">
|
||
<span id="lucky-modal-title" style="font-weight:700;font-size:14px;color:#222;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap"></span>
|
||
<button id="lucky-modal-save" onclick="saveLuckyModal()" style="background:linear-gradient(135deg,#3b82f6,#2563eb);color:#fff;border:none;border-radius:6px;padding:5px 14px;font-size:13px;font-weight:600;cursor:pointer">💾 저장</button>
|
||
<button onclick="minimizeLuckyModal()" title="최소화" style="background:#e5e7eb;color:#333;border:none;border-radius:6px;padding:5px 10px;font-size:13px;cursor:pointer">–</button>
|
||
<button onclick="closeLuckyModal()" title="닫기" style="background:#e5e7eb;color:#333;border:none;border-radius:6px;padding:5px 10px;font-size:13px;cursor:pointer">✕</button>
|
||
</div>
|
||
<div id="lucky-container" style="flex:1;position:relative;color-scheme:light"></div>
|
||
</div>
|
||
</div>
|
||
</body>
|
||
</html>
|