Files
homeclaw/web-ui/html/index.html
T
kimandClaude Sonnet 5 6a61c3f4af 스튜디오 앱에 프롬프트 작성 도우미 채팅 + 생성 진행률 바 추가
이미지/동영상/사진편집/이모티콘세트 탭과 ComfyUI 탭 양쪽에 AI 프롬프트
도우미를 붙여, 한국어로 설명하면 SDXL/FLUX/LTX용 영어 프롬프트를 만들어줌.
생성 중에는 예상 소요시간 기준 진행률 바를 표시.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-06 12:27:39 +09:00

2015 lines
154 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!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&#10;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">&times;</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">&#x2715;</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 &lt;token&gt;</code><br>
or <code style="background:#f4f4f4;padding:2px 5px;border-radius:4px">X-Gateway-Token: &lt;token&gt;</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&#10;@modelcontextprotocol/server-filesystem&#10;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">&#8211;</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>