feat: claude-app 로컬 LLM 코딩 에이전트 추가 + 코드 에디터 임베드
- Ollama/LM Studio 기반 터미널형 코딩 에이전트(claude-app.html/js): 테마 11종, Plan/Confirm/Auto/Yolo 실행 모드, companion.py 연동 - companion.py에 browse_folder/fs_list/fs_read/fs_write 추가 — 네이티브 폴더 선택이 곧 Bash 작업 경로가 되도록 통합해 Write/Bash가 서로 다른 폴더를 보던 문제 해결 - code.html에 클로드 앱 하단 패널 추가, 우측 AI 채팅 패널은 그대로 유지되도록 그리드 레이아웃 조정, 패널 높이를 전체 높이의 50~70%로 제한 - iframe 관련 버그 수정: src="" 판정 오류로 패널이 로드되지 않던 문제, 드래그 리사이즈 중 마우스가 iframe 위를 지나면 릴리즈가 안 되던 문제 (미리보기/에뮬레이터 패널도 동일 수정) Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
+68
-5
@@ -904,7 +904,7 @@ function toggleCodePreview() {
|
||||
const saved = localStorage.getItem('code_preview_url');
|
||||
if (saved && urlInput) urlInput.value = saved;
|
||||
const iframe = document.getElementById('code-preview-iframe');
|
||||
if (iframe && !iframe.src) iframe.src = (urlInput ? urlInput.value : 'http://127.0.0.1:8000');
|
||||
if (iframe && !iframe.getAttribute('src')) iframe.src = (urlInput ? urlInput.value : 'http://127.0.0.1:8000');
|
||||
} else {
|
||||
panel.style.display = 'none';
|
||||
if (btn) btn.classList.remove('active');
|
||||
@@ -950,6 +950,57 @@ function emuPanelNewTab() {
|
||||
if (_emuPanelUrl) window.open(_emuPanelUrl, '_blank');
|
||||
}
|
||||
|
||||
// 클로드 앱 패널 높이는 항상 전체 높이의 1/2~7/10 사이로 제한한다.
|
||||
function _claudePanelBounds() {
|
||||
const total = document.getElementById('code-view')?.clientHeight || window.innerHeight;
|
||||
return { min: total * 0.5, max: total * 0.7 };
|
||||
}
|
||||
|
||||
let _claudePanelOpen = false;
|
||||
function toggleClaudeAppPanel() {
|
||||
const panel = document.getElementById('code-claude-panel');
|
||||
const btn = document.getElementById('code-claude-btn');
|
||||
if (!panel) return;
|
||||
_claudePanelOpen = !_claudePanelOpen;
|
||||
panel.style.display = _claudePanelOpen ? 'flex' : 'none';
|
||||
if (btn) btn.classList.toggle('active', _claudePanelOpen);
|
||||
if (_claudePanelOpen) {
|
||||
const iframe = document.getElementById('code-claude-iframe');
|
||||
if (iframe && !iframe.getAttribute('src')) iframe.src = '/claude-app.html';
|
||||
const { min, max } = _claudePanelBounds();
|
||||
const cur = panel.offsetHeight;
|
||||
if (cur < min || cur > max) panel.style.height = Math.round((min + max) / 2) + 'px';
|
||||
}
|
||||
if (monacoEditor) setTimeout(() => monacoEditor.layout(), 60);
|
||||
}
|
||||
// Claude App panel resize drag
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const drag = document.getElementById('code-claude-drag');
|
||||
const panel = document.getElementById('code-claude-panel');
|
||||
if (!drag || !panel) return;
|
||||
let startY = 0, startH = 0;
|
||||
drag.addEventListener('mouseover', () => drag.style.background = 'rgba(189,147,249,.4)');
|
||||
drag.addEventListener('mousedown', e => {
|
||||
startY = e.clientY; startH = panel.offsetHeight; e.preventDefault();
|
||||
const { min, max } = _claudePanelBounds();
|
||||
// 드래그 중 마우스가 iframe 위로 지나가면 부모 document가 mousemove/mouseup을 못 받아
|
||||
// 드래그가 안 풀리므로, 드래그하는 동안은 iframe이 마우스 이벤트를 가로채지 않게 한다.
|
||||
const iframe = document.getElementById('code-claude-iframe');
|
||||
if (iframe) iframe.style.pointerEvents = 'none';
|
||||
const onMove = e2 => {
|
||||
const dy = startY - e2.clientY;
|
||||
panel.style.height = Math.max(min, Math.min(max, startH + dy)) + 'px';
|
||||
};
|
||||
const onUp = () => {
|
||||
document.removeEventListener('mousemove', onMove);
|
||||
document.removeEventListener('mouseup', onUp);
|
||||
drag.style.background = '';
|
||||
if (iframe) iframe.style.pointerEvents = '';
|
||||
};
|
||||
document.addEventListener('mousemove', onMove); document.addEventListener('mouseup', onUp);
|
||||
});
|
||||
});
|
||||
|
||||
function openArduinoEmulator() {
|
||||
const file = codeFiles.find(f => f.id === activeCodeFileId);
|
||||
const btn = document.getElementById('code-emu-btn');
|
||||
@@ -1090,14 +1141,20 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
const panel = document.getElementById('code-preview-panel');
|
||||
if (!drag || !panel) return;
|
||||
let startY=0, startH=0;
|
||||
drag.addEventListener('mouseover', ()=>drag.style.background='rgba(96,165,250,.4)');
|
||||
drag.addEventListener('mousedown', e => {
|
||||
startY=e.clientY; startH=panel.offsetHeight; e.preventDefault();
|
||||
const iframe = document.getElementById('code-preview-iframe');
|
||||
if (iframe) iframe.style.pointerEvents = 'none';
|
||||
const onMove = e2 => {
|
||||
const dy = startY - e2.clientY;
|
||||
panel.style.height = Math.max(100, Math.min(window.innerHeight*.7, startH+dy)) + 'px';
|
||||
};
|
||||
const onUp = () => { document.removeEventListener('mousemove',onMove); document.removeEventListener('mouseup',onUp); drag.style.background=''; };
|
||||
drag.addEventListener('mouseover', ()=>drag.style.background='rgba(96,165,250,.4)');
|
||||
const onUp = () => {
|
||||
document.removeEventListener('mousemove',onMove); document.removeEventListener('mouseup',onUp);
|
||||
drag.style.background='';
|
||||
if (iframe) iframe.style.pointerEvents = '';
|
||||
};
|
||||
document.addEventListener('mousemove', onMove); document.addEventListener('mouseup', onUp);
|
||||
});
|
||||
});
|
||||
@@ -6466,11 +6523,17 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
const panel = document.getElementById('code-emu-panel');
|
||||
if (!drag || !panel) return;
|
||||
let startY=0, startH=0;
|
||||
drag.addEventListener('mouseover', ()=>drag.style.background='rgba(34,197,94,.4)');
|
||||
drag.addEventListener('mousedown', e => {
|
||||
startY=e.clientY; startH=panel.offsetHeight; e.preventDefault();
|
||||
const iframe = document.getElementById('code-emu-iframe');
|
||||
if (iframe) iframe.style.pointerEvents = 'none';
|
||||
const onMove = e2 => { const dy=startY-e2.clientY; panel.style.height=Math.max(120,Math.min(window.innerHeight*.75,startH+dy))+'px'; };
|
||||
const onUp = () => { document.removeEventListener('mousemove',onMove); document.removeEventListener('mouseup',onUp); drag.style.background=''; };
|
||||
drag.addEventListener('mouseover', ()=>drag.style.background='rgba(34,197,94,.4)');
|
||||
const onUp = () => {
|
||||
document.removeEventListener('mousemove',onMove); document.removeEventListener('mouseup',onUp);
|
||||
drag.style.background='';
|
||||
if (iframe) iframe.style.pointerEvents = '';
|
||||
};
|
||||
document.addEventListener('mousemove', onMove); document.addEventListener('mouseup', onUp);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user