window.onerror = function(msg, url, line, col, error) {
document.body.innerHTML += '
ERROR: ' + msg + ' at line ' + line + '
';
};
window.addEventListener('unhandledrejection', function(event) {
document.body.innerHTML += 'PROMISE REJECTION: ' + event.reason + '
';
});
try {
const vscode=acquireVsCodeApi(),chat=document.getElementById('chat'),input=document.getElementById('input'),
sendBtn=document.getElementById('sendBtn'),stopBtn=document.getElementById('stopBtn'),
modelSel=document.getElementById('modelSel'),newChatBtn=document.getElementById('newChatBtn'),settingsBtn=document.getElementById('settingsBtn'),brainBtn=document.getElementById('brainBtn'),
attachBtn=document.getElementById('attachBtn'),fileInput=document.getElementById('fileInput'),attachPreview=document.getElementById('attachPreview'),
thinkingBar=document.getElementById('thinkingBar');
let loader=null,sending=false,pendingFiles=[];
/* Syntax Highlighting (lightweight) */
function highlight(code,lang){
let h=esc(code);
h=h.replace(/(\\/\\/[^\\n]*)/g,'$1');
h=h.replace(/(#[^\\n]*)/g,'$1');
h=h.replace(/(\\/\\*[\\s\\S]*?\\*\\/)/g,'$1');
h=h.replace(/("[^&]*?"|'[^&]*?')/g,'$1');
h=h.replace(/\\b(function|const|let|var|return|if|else|for|while|class|import|export|from|default|async|await|try|catch|throw|new|this|def|self|print|lambda|yield|with|as|raise|except|finally)\\b/g,'$1');
h=h.replace(/\\b(\\d+\\.?\\d*)\\b/g,'$1');
h=h.replace(/\\b(True|False|None|true|false|null|undefined|NaN)\\b/g,'$1');
h=h.replace(/\\b(String|Number|Boolean|Array|Object|Map|Set|Promise|void|int|float|str|list|dict|tuple)\\b/g,'$1');
h=h.replace(/([=!<>+\\-*/%|&^~?:]+)/g,'$1');
return h;
}
/* Clipboard Paste (Ctrl+V images) */
input.addEventListener('paste',(e)=>{
const items=e.clipboardData&&e.clipboardData.items;
if(!items)return;
for(const item of items){
if(item.type.startsWith('image/')){
e.preventDefault();
const file=item.getAsFile();
if(!file)return;
const reader=new FileReader();
reader.onload=()=>{
const base64=reader.result.split(',')[1];
pendingFiles.push({name:'clipboard-image.png',type:file.type,data:base64});
renderPreview();
};
reader.readAsDataURL(file);
return;
}
}
});
vscode.postMessage({type:'getModels'});
setTimeout(()=>vscode.postMessage({type:'ready'}),300);
input.addEventListener('input',()=>{input.style.height='auto';input.style.height=Math.min(input.scrollHeight,150)+'px'});
function getTime(){return new Date().toLocaleTimeString('ko-KR',{hour:'2-digit',minute:'2-digit'})}
function esc(s){const d=document.createElement('div');d.innerText=s;return d.innerHTML}
function fmt(t){
if(t.lastIndexOf(' t.lastIndexOf('')) t += '';
if(t.lastIndexOf(' t.lastIndexOf('')) t += '';
if(t.lastIndexOf(' t.lastIndexOf('')) t += '';
if((t.match(/\`\`\`/g)||[]).length % 2 !== 0) t += '\\n\`\`\`';
const blocks = [];
function pushB(h){ blocks.push(h); return '__B' + (blocks.length-1) + '__'; }
t=t.replace(/([\\s\\S]*?)<\\/create_file>/g,(_,p,c)=>pushB('\u{1F4C1} '+esc(p)+' \u2014 \uC790\uB3D9 \uC0DD\uC131\uB428
'));
t=t.replace(/([\\s\\S]*?)<\\/edit_file>/g,(_,p,c)=>pushB('\u270F\uFE0F '+esc(p)+' \u2014 \uD3B8\uC9D1\uB428
'));
t=t.replace(/([\\s\\S]*?)<\\/run_command>/g,(_,c)=>pushB('\u25B6 '+esc(c)+'
'));
t=t.replace(/\`\`\`(\\w*)\\n([\\s\\S]*?)\`\`\`/g,(_,lang,c)=>{const l=lang||'code';return pushB(''+esc(l)+''+highlight(c,l)+'
');});
t=t.replace(/\`([^\`]+)\`/g,(_,c)=>pushB(''+esc(c)+''));
t=esc(t);
t=t.replace(/\\*\\*([^*]+)\\*\\*/g,'$1');
t=t.replace(/\\[([^\\]]+)\\]\\(([^)]+)\\)/g, '$1');
t=t.replace(/__B(\\d+)__/g, (_,i)=>blocks[i]);
return t;
}
function copyCode(btn){const code=btn.parentElement.querySelector('code');if(!code)return;navigator.clipboard.writeText(code.innerText).then(()=>{btn.textContent='\u2713 Copied';btn.classList.add('copied');setTimeout(()=>{btn.textContent='Copy';btn.classList.remove('copied')},1500)})}
function addMsg(text,role){
const isUser=role==='user',isErr=role==='error';
const el=document.createElement('div');el.className='msg'+(isUser?' msg-user':'')+(isErr?' msg-error':'');
const head=document.createElement('div');head.className='msg-head';
head.innerHTML=(isUser?'\u{1F464}
You':'\u2726
Connect AI')+''+getTime()+'';
const body=document.createElement('div');body.className='msg-body';
if(isUser){body.innerText=text}else{body.innerHTML=fmt(text)}
el.appendChild(head);el.appendChild(body);chat.appendChild(el);chat.scrollTop=chat.scrollHeight;
}
function showLoader(){loader=document.createElement('div');loader.className='msg';loader.innerHTML='\u2726
Connect AI'+getTime()+'
\uC0DD\uAC01\uD558\uB294 \uC911... ';chat.appendChild(loader);chat.scrollTop=chat.scrollHeight;thinkingBar.classList.add('active')}
function hideLoader(){if(loader&&loader.parentNode)loader.parentNode.removeChild(loader);loader=null;thinkingBar.classList.remove('active')}
function setSending(v){sending=v;sendBtn.disabled=v;stopBtn.classList.toggle('visible',v);input.disabled=v;if(!v){input.focus();thinkingBar.classList.remove('active')}}
function send(){
const text=input.value.trim();
if((!text&&pendingFiles.length===0)||sending)return;
document.body.classList.remove('init');
const w=document.querySelector('.welcome');if(w)w.remove();
document.querySelectorAll('.quick-actions').forEach(e=>e.remove());
const displayText=text+(pendingFiles.length>0?'
\u{1F4CE} '+pendingFiles.map(f=>f.name).join(', '):'');
addMsg(displayText,'user');
input.value='';input.style.height='auto';setSending(true);showLoader();
if(pendingFiles.length>0){
vscode.postMessage({type:'promptWithFile',value:text||'\uC774 \uD30C\uC77C\uC744 \uBD84\uC11D\uD574\uC8FC\uC138\uC694.',model:modelSel.value,files:pendingFiles});
pendingFiles=[];attachPreview.innerHTML='';attachPreview.classList.remove('visible');
} else {
vscode.postMessage({type:'prompt',value:text,model:modelSel.value});
}
}
/* Attachment Logic */
attachBtn.addEventListener('click',()=>fileInput.click());
fileInput.addEventListener('change',()=>{
const files=Array.from(fileInput.files);
files.forEach(file=>{
const reader=new FileReader();
reader.onload=()=>{
const base64=reader.result.split(',')[1];
pendingFiles.push({name:file.name,type:file.type,data:base64});
renderPreview();
};
reader.readAsDataURL(file);
});
fileInput.value='';
});
function renderPreview(){
attachPreview.innerHTML='';
if(pendingFiles.length===0){attachPreview.classList.remove('visible');return;}
attachPreview.classList.add('visible');
pendingFiles.forEach((f,i)=>{
const chip=document.createElement('div');chip.className='attach-chip';
const isImg=f.type.startsWith('image/');
if(isImg){
const thumb=document.createElement('img');thumb.className='attach-thumb';thumb.src='data:'+f.type+';base64,'+f.data;chip.appendChild(thumb);
} else {
const icon=document.createElement('span');icon.className='chip-icon';icon.textContent=f.type.startsWith('audio/')?'\u{1F3A7}':'\u{1F4C4}';chip.appendChild(icon);
}
const nm=document.createElement('span');nm.className='chip-name';nm.textContent=f.name;chip.appendChild(nm);
const rm=document.createElement('span');rm.className='chip-remove';rm.textContent='\u2715';
rm.addEventListener('click',()=>{pendingFiles.splice(i,1);renderPreview();});
chip.appendChild(rm);
attachPreview.appendChild(chip);
});
}
document.addEventListener('click',e=>{if(e.target.classList.contains('qa-btn')){const p=e.target.getAttribute('data-prompt');if(p){input.value=p;send()}}});
sendBtn.addEventListener('click',send);
input.addEventListener('keydown',e=>{if(e.key==='Enter'&&!e.shiftKey){e.preventDefault();send()}});
newChatBtn.addEventListener('click',()=>vscode.postMessage({type:'newChat'}));
settingsBtn.addEventListener('click',()=>vscode.postMessage({type:'openSettings'}));
brainBtn.addEventListener('click',()=>vscode.postMessage({type:'syncBrain'}));
stopBtn.addEventListener('click',()=>{vscode.postMessage({type:'stopGeneration'});hideLoader();setSending(false);if(streamBody){streamBody.classList.remove('stream-active')}streamEl=null;streamBody=null;});
let streamEl=null,streamBody=null;
window.addEventListener('message',e=>{const msg=e.data;switch(msg.type){
case 'response':hideLoader();setSending(false);addMsg(msg.value,'ai');break;
case 'error':hideLoader();setSending(false);addMsg(msg.value,'error');break;
case 'streamStart':{
hideLoader();
streamEl=document.createElement('div');streamEl.className='msg';
const h=document.createElement('div');h.className='msg-head';
h.innerHTML='\u2726
Connect AI'+getTime()+'';
streamBody=document.createElement('div');streamBody.className='msg-body stream-active';
streamEl.appendChild(h);streamEl.appendChild(streamBody);chat.appendChild(streamEl);chat.scrollTop=chat.scrollHeight;
break;}
case 'streamChunk':{
if(streamBody){streamBody.innerHTML=fmt(streamBody._raw=(streamBody._raw||'')+msg.value);chat.scrollTop=chat.scrollHeight;}
break;}
case 'streamEnd':{
if(streamBody)streamBody.classList.remove('stream-active');
/* Add regenerate button */
if(streamEl){
const rb=document.createElement('button');rb.className='regen-btn';rb.innerHTML='\u{1F504} Regenerate';
rb.addEventListener('click',()=>{rb.remove();vscode.postMessage({type:'regenerate'});showLoader();setSending(true);});
streamEl.appendChild(rb);
}
setSending(false);streamEl=null;streamBody=null;
break;}
case 'modelsList':modelSel.innerHTML='';msg.value.forEach(m=>{const o=document.createElement('option');o.value=m;o.textContent=m;modelSel.appendChild(o)});break;
case 'clearChat':
document.body.classList.add('init');
chat.innerHTML='\u2726
Connect AI
\uBCF4\uC548 \xB7 \uBE44\uC6A9\uCD5C\uC801\uD654 \xB7 \uC9C0\uC2DD\uC5F0\uACB0
\uD504\uB85C\uC81D\uD2B8\uB97C \uC774\uD574\uD558\uACE0, \uCF54\uB4DC\uB97C \uC791\uC131\uD558\uACE0, \uC2E4\uD589\uD569\uB2C8\uB2E4.
';
break;
case 'restoreMessages':
chat.innerHTML='';
if(msg.value&&msg.value.length>0){
document.body.classList.remove('init');
msg.value.forEach(m=>addMsg(m.text,m.role));
} else {
document.body.classList.add('init');
chat.innerHTML='\u2726
Connect AI
\uBCF4\uC548 \xB7 \uBE44\uC6A9\uCD5C\uC801\uD654 \xB7 \uC9C0\uC2DD\uC5F0\uACB0
\uD504\uB85C\uC81D\uD2B8\uB97C \uC774\uD574\uD558\uACE0, \uCF54\uB4DC\uB97C \uC791\uC131\uD558\uACE0, \uC2E4\uD589\uD569\uB2C8\uB2E4.
';
}
break;
case 'focusInput':input.focus();break;
case 'injectPrompt':input.value=msg.value;input.style.height='auto';input.style.height=Math.min(input.scrollHeight,150)+'px';send();break;
} });
} catch(err) {
document.body.innerHTML = '\u26A0\uFE0F WEBVIEW JS CRASH
' + err.name + ': ' + err.message + '\\n' + err.stack + '
';
}