- handle-chat.ts: excel/coder+shell/email/kakao/sqlite_query/image_edit/ image_read/run_command/news_search/pdf_*/schedule_job를 전용 앱 세션· 키워드·첨부 신호로 스코핑 (기존 weather/legal/pptx/dental/mind 패턴과 동일). 일반 대화 기준 도구 스키마 오버헤드 10,204→3,951 토큰(-61%) - desktop_*(10개, Windows 전용) 완전 제외 — 이 리눅스 서버에서 전량 실패 확인(tool_audit.log), process.platform 체크라 윈도우 이전시 자동 복구 - 채팅 기본 num_predict 4096→6000 (사고형 모델 빈 응답 여유분) - ollama-adapter.ts: 턴마다 prompt/completion 토큰을 token-usage.jsonl에 기록 (Ollama Cloud vs 종량제 API 비용 비교용) - web-ui/js/ 25개 파일을 app/·code-editor/·voice/·music/·claude-app/ 서브디렉토리로 재배치, 전체 참조 경로 갱신 - package.json build 스크립트에 dist 클린 단계 추가 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
218 lines
10 KiB
HTML
218 lines
10 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="ko">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>🤖 Claude App</title>
|
|
<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>
|
|
<style>
|
|
:root {
|
|
/* Default: Ubuntu — overridden by applyTheme() in JS */
|
|
--bg-main: #4B1D45;
|
|
--bg-panel: #3A1636;
|
|
--bg-input: #2C0F29;
|
|
--border: #6b2f63;
|
|
--accent: #E95420;
|
|
--accent-bg: rgba(233,84,32,0.16);
|
|
--accent-bg-hover: rgba(233,84,32,0.30);
|
|
--text: #EEEEEC;
|
|
--text-muted: #c9a9c4;
|
|
--dot-on: #8AE234;
|
|
--dot-off: #8a6c86;
|
|
}
|
|
* { box-sizing: border-box; }
|
|
html, body { height: 100%; margin: 0; background: var(--bg-main); font-family: 'IBM Plex Mono', 'D2Coding', 'NanumGothicCoding', monospace; }
|
|
#app { display: flex; flex-direction: column; height: 100%; }
|
|
|
|
#topbar {
|
|
display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
|
|
padding: 8px 12px; background: var(--bg-panel); border-bottom: 1px solid var(--border);
|
|
flex-shrink: 0;
|
|
}
|
|
#topbar .title { color: var(--accent); font-weight: 700; font-size: 13px; flex-shrink: 0; margin-right: 4px; }
|
|
#topbar select, #topbar input {
|
|
background: var(--bg-input); border: 1px solid var(--border); color: var(--text);
|
|
border-radius: 4px; padding: 4px 6px; font-size: 11px; font-family: inherit;
|
|
}
|
|
#topbar input[type=text] { width: 150px; }
|
|
#topbar label { color: var(--text-muted); font-size: 10px; text-transform: uppercase; letter-spacing: .04em; }
|
|
#topbar button {
|
|
background: var(--accent-bg); border: 1px solid var(--accent); color: var(--accent); border-radius: 4px;
|
|
padding: 4px 10px; font-size: 11px; cursor: pointer; font-family: inherit; white-space: nowrap;
|
|
}
|
|
#topbar button:hover { background: var(--accent-bg-hover); }
|
|
.field { display: flex; align-items: center; gap: 4px; }
|
|
.dot { width: 8px; height: 8px; border-radius: 50%; background: var(--dot-off); flex-shrink: 0; }
|
|
.dot.on { background: var(--dot-on); }
|
|
#folder-status { color: var(--text-muted); font-size: 11px; max-width: 160px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
|
|
#mode-strip {
|
|
display: flex; align-items: center; gap: 6px;
|
|
padding: 4px 12px; background: var(--bg-panel); border-bottom: 1px solid var(--border);
|
|
flex-shrink: 0;
|
|
}
|
|
.mode-label { color: var(--text-muted); font-size: 10px; text-transform: uppercase; letter-spacing: .04em; margin-right: 2px; }
|
|
.mode-btn {
|
|
background: var(--bg-input); border: 1px solid var(--border); color: var(--text-muted);
|
|
border-radius: 12px; padding: 2px 10px; font-size: 11px; cursor: pointer; font-family: inherit;
|
|
transition: all .15s;
|
|
}
|
|
.mode-btn:hover { border-color: var(--accent); color: var(--text); }
|
|
.mode-btn.active { background: var(--accent-bg); border-color: var(--accent); color: var(--accent); font-weight: 600; }
|
|
|
|
#main-row { flex: 1; min-height: 0; display: flex; }
|
|
|
|
#file-panel {
|
|
width: 260px; flex-shrink: 0; display: flex; flex-direction: column;
|
|
background: var(--bg-panel); border-right: 1px solid var(--border); overflow: hidden;
|
|
}
|
|
#file-panel-header {
|
|
display: flex; align-items: center; justify-content: space-between;
|
|
padding: 6px 10px; border-bottom: 1px solid var(--border); flex-shrink: 0;
|
|
}
|
|
#file-panel-header span { color: var(--text-muted); font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: .04em; }
|
|
#file-panel-header button {
|
|
background: none; border: none; color: var(--text-muted); cursor: pointer; font-size: 12px; padding: 0 4px;
|
|
}
|
|
#file-tree { flex: 1; min-height: 60px; overflow-y: auto; padding: 4px 0; font-size: 12px; }
|
|
.ft-item { padding: 3px 8px; color: var(--text); cursor: pointer; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
.ft-item:hover { background: var(--accent-bg); }
|
|
.ft-item.ft-active { background: var(--accent-bg-hover); color: var(--accent); }
|
|
.ft-empty { padding: 10px; color: var(--text-muted); font-size: 11px; }
|
|
|
|
#file-editor { display: none; flex-direction: column; flex-shrink: 0; height: 45%; border-top: 1px solid var(--border); }
|
|
#file-editor-header {
|
|
display: flex; align-items: center; justify-content: space-between;
|
|
padding: 5px 8px; background: var(--bg-panel); border-bottom: 1px solid var(--border); flex-shrink: 0;
|
|
}
|
|
#file-editor-name { color: var(--text-muted); font-size: 11px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
#file-editor-header button { background: none; border: none; color: var(--text-muted); cursor: pointer; font-size: 13px; padding: 0 4px; }
|
|
#file-editor-textarea {
|
|
flex: 1; min-height: 0; resize: none; border: none; outline: none;
|
|
background: var(--bg-input); color: var(--text); font-family: inherit; font-size: 12px; padding: 8px;
|
|
}
|
|
#file-editor-actions { display: flex; gap: 6px; padding: 6px 8px; border-top: 1px solid var(--border); flex-shrink: 0; }
|
|
#file-editor-actions button {
|
|
background: var(--accent-bg); border: 1px solid var(--accent); color: var(--accent); border-radius: 4px;
|
|
padding: 4px 8px; font-size: 11px; cursor: pointer; font-family: inherit;
|
|
}
|
|
#file-editor-actions button:hover { background: var(--accent-bg-hover); }
|
|
#file-editor-status { color: var(--text-muted); font-size: 10px; align-self: center; }
|
|
|
|
#term-wrap { flex: 1; min-height: 0; position: relative; padding: 6px; background: var(--bg-main); }
|
|
#term-container { position: absolute; inset: 6px; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div id="app">
|
|
<div id="topbar">
|
|
<span class="title">🤖 Claude App</span>
|
|
|
|
<div class="field">
|
|
<label>백엔드</label>
|
|
<select id="backend-select">
|
|
<option value="ollama">Ollama</option>
|
|
<option value="lm_studio">LM Studio</option>
|
|
<option value="anthropic">Anthropic</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="field">
|
|
<label>모델</label>
|
|
<select id="model-select"><option value="">(불러오는 중...)</option></select>
|
|
<button id="refresh-models-btn" title="모델 목록 새로고침">↻</button>
|
|
</div>
|
|
|
|
<div class="field">
|
|
<label>엔드포인트</label>
|
|
<input type="text" id="endpoint-input">
|
|
</div>
|
|
|
|
<div class="field" id="anthropic-key-field" title="Anthropic Console에서 발급한 API 키. 브라우저에 저장되며 api.anthropic.com에 직접 전송됩니다.">
|
|
<label>API 키</label>
|
|
<input type="password" id="anthropic-key-input" placeholder="sk-ant-...">
|
|
</div>
|
|
|
|
<div class="field" id="proxy-toggle-field" title="Ollama/LM Studio가 이 homeclaw 서버와 같은 컴퓨터에서 돌고 있을 때 사용. 브라우저에서 직접 접속하지 않고 서버를 통해 우회하므로 CORS 설정이 필요 없습니다.">
|
|
<input type="checkbox" id="proxy-toggle">
|
|
<label for="proxy-toggle">서버 프록시</label>
|
|
</div>
|
|
|
|
<div class="field">
|
|
<button id="folder-btn">📁 폴더 연결</button>
|
|
<span id="folder-status">(폴더 없음)</span>
|
|
</div>
|
|
|
|
<div class="field" title="Bash 명령이 실행될 작업 디렉토리. companion.py가 연결된 컴퓨터의 절대 경로를 입력하세요.">
|
|
<label>Bash 경로</label>
|
|
<input type="text" id="local-path-input" placeholder="예: C:\Users\name\project 또는 /home/user/project">
|
|
</div>
|
|
|
|
<div class="field" title="companion.py 연결 상태">
|
|
<span id="companion-dot" class="dot"></span>
|
|
<span style="color:var(--text-muted);font-size:10px">companion</span>
|
|
<a href="/companion.py" download title="companion.py 다운로드 (Bash 실행용 로컬 클라이언트, Windows/macOS/Linux 공용 — 업데이트 후에는 재다운로드 필요)" style="color:var(--text-muted);font-size:11px;text-decoration:none;margin-left:2px">⬇</a>
|
|
<a href="/install_autostart.py" download title="install_autostart.py 다운로드 (Windows 전용) — 이 파일 하나만 실행하면 최신 companion.py를 %LOCALAPPDATA%\PythonCompanion에 설치하고 Windows 로그인 시 자동 실행되도록 등록합니다. 다시 실행하면 최신 버전으로 업데이트됩니다" style="color:var(--text-muted);font-size:11px;text-decoration:none;margin-left:2px">⚙</a>
|
|
</div>
|
|
|
|
<div class="field">
|
|
<label>🎨 테마</label>
|
|
<select id="theme-select">
|
|
<optgroup label="다크">
|
|
<option value="ubuntu">Ubuntu</option>
|
|
<option value="dracula">Dracula</option>
|
|
<option value="nord">Nord</option>
|
|
<option value="solarized">Solarized Dark</option>
|
|
<option value="monokai">Monokai</option>
|
|
<option value="catppuccin">Catppuccin</option>
|
|
<option value="tokyo">Tokyo Night</option>
|
|
</optgroup>
|
|
<optgroup label="라이트">
|
|
<option value="solarized-light">Solarized Light</option>
|
|
<option value="catppuccin-latte">Catppuccin Latte</option>
|
|
<option value="github-light">GitHub Light</option>
|
|
<option value="paper">Paper</option>
|
|
</optgroup>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="mode-strip">
|
|
<span class="mode-label">실행 모드</span>
|
|
<button class="mode-btn" data-mode="plan">📋 Plan</button>
|
|
<button class="mode-btn" data-mode="confirm">🔒 Confirm</button>
|
|
<button class="mode-btn active" data-mode="auto">✏️ Auto</button>
|
|
<button class="mode-btn" data-mode="yolo">🤖 Yolo</button>
|
|
</div>
|
|
|
|
<div id="main-row">
|
|
<div id="file-panel">
|
|
<div id="file-panel-header">
|
|
<span>📁 파일</span>
|
|
<button id="file-refresh-btn" title="새로고침">↻</button>
|
|
</div>
|
|
<div id="file-tree"><div class="ft-empty">폴더를 연결하세요</div></div>
|
|
<div id="file-editor">
|
|
<div id="file-editor-header">
|
|
<span id="file-editor-name"></span>
|
|
<button id="file-editor-close-btn" title="닫기">✕</button>
|
|
</div>
|
|
<textarea id="file-editor-textarea" spellcheck="false"></textarea>
|
|
<div id="file-editor-actions">
|
|
<button id="file-editor-save-btn">💾 저장</button>
|
|
<button id="file-editor-mention-btn">@ 터미널에 참조 추가</button>
|
|
<span id="file-editor-status"></span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div id="term-wrap"><div id="term-container"></div></div>
|
|
</div>
|
|
</div>
|
|
|
|
<script src="../js/claude-app/claude-app.js"></script>
|
|
</body>
|
|
</html>
|