From 1c3db89016c2f4864a87debdb2289d0b2cf5421b Mon Sep 17 00:00:00 2001 From: Jay Date: Thu, 16 Apr 2026 01:23:56 +0900 Subject: [PATCH] =?UTF-8?q?feat(agent):=20Connect=20AI=20=EC=9B=B9?= =?UTF-8?q?=EC=82=AC=EC=9D=B4=ED=8A=B8=20=ED=83=90=EC=83=89=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5(read=5Furl)=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20?= =?UTF-8?q?=EC=9B=B9=20=EC=8A=A4=ED=81=AC=EB=9E=98=ED=95=91=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ehalsh.txt | 0 package.json | 2 +- src/extension.ts | 36 +++- test.ts | 15 ++ test10.js | 9 + test11.js | 19 +++ test12.js | 11 ++ test13.js | 16 ++ test14.js | 14 ++ test15.js | 18 ++ test16.js | 8 + test17.js | 4 + test18.js | 8 + test19.js | 20 +++ test20.js | 8 + test21.js | 29 ++++ test21.ts | 42 +++++ test22.js | 19 +++ test22.ts | 31 ++++ test23.js | 17 ++ test4.js | 10 ++ test5.js | 17 ++ test6.js | 37 +++++ test7.js | 4 + test8.js | 4 + test9.js | 7 + test_ax.js | 17 ++ test_ax2.js | 15 ++ test_eval.js | 20 +++ test_final.js | 22 +++ test_final.ts | 29 ++++ test_flags.js | 8 + test_flags2.js | 13 ++ test_flags3.js | 13 ++ test_html.js | 20 +++ test_html.ts | 12 ++ test_html2.js | 369 +++++++++++++++++++++++++++++++++++++++++ test_html2.ts | 8 + test_jsdom.js | 9 + test_regex_isolated.js | 4 + test_script.js | 192 +++++++++++++++++++++ test_v2.html | 367 ++++++++++++++++++++++++++++++++++++++++ test_v2_full.js | 18 ++ test_v2_script.js | 191 +++++++++++++++++++++ test_verify.html | 367 ++++++++++++++++++++++++++++++++++++++++ test_verify_script.js | 191 +++++++++++++++++++++ 46 files changed, 2286 insertions(+), 4 deletions(-) create mode 100644 ehalsh.txt create mode 100644 test.ts create mode 100644 test10.js create mode 100644 test11.js create mode 100644 test12.js create mode 100644 test13.js create mode 100644 test14.js create mode 100644 test15.js create mode 100644 test16.js create mode 100644 test17.js create mode 100644 test18.js create mode 100644 test19.js create mode 100644 test20.js create mode 100644 test21.js create mode 100644 test21.ts create mode 100644 test22.js create mode 100644 test22.ts create mode 100644 test23.js create mode 100644 test4.js create mode 100644 test5.js create mode 100644 test6.js create mode 100644 test7.js create mode 100644 test8.js create mode 100644 test9.js create mode 100644 test_ax.js create mode 100644 test_ax2.js create mode 100644 test_eval.js create mode 100644 test_final.js create mode 100644 test_final.ts create mode 100644 test_flags.js create mode 100644 test_flags2.js create mode 100644 test_flags3.js create mode 100644 test_html.js create mode 100644 test_html.ts create mode 100644 test_html2.js create mode 100644 test_html2.ts create mode 100644 test_jsdom.js create mode 100644 test_regex_isolated.js create mode 100644 test_script.js create mode 100644 test_v2.html create mode 100644 test_v2_full.js create mode 100644 test_v2_script.js create mode 100644 test_verify.html create mode 100644 test_verify_script.js diff --git a/ehalsh.txt b/ehalsh.txt new file mode 100644 index 0000000..e69de29 diff --git a/package.json b/package.json index a5d9dbb..cff0501 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "connect-ai-lab", "displayName": "Connect AI", "description": "100% 로컬 AI 코딩 에이전트 — 파일 생성, 코드 편집, 터미널 실행을 오프라인으로. Ollama + Gemma/Llama/DeepSeek 지원.", - "version": "2.1.3", + "version": "2.1.4", "publisher": "connectailab", "license": "MIT", "icon": "assets/icon.png", diff --git a/src/extension.ts b/src/extension.ts index c6bf251..85a7fb4 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -78,6 +78,10 @@ Example — user says "서버 실행해줘": filename.md Use this to READ documents from the user's personal knowledge base. +━━━ ACTION 8: READ WEBSITES ━━━ +https://example.com +Use this to read the textual content of any website on the internet. + CRITICAL RULES: 1. ALWAYS respond in the same language the user uses. 2. When the user asks to create, edit, delete files or run commands, you MUST use the action tags above. NEVER just show code without action tags. @@ -817,7 +821,7 @@ class SidebarChatProvider implements vscode.WebviewViewProvider { this._view.webview.postMessage({ type: 'streamEnd' }); this._chatHistory.push({ role: 'assistant', content: aiMessage }); - const report = this._executeActions(aiMessage); + const report = await this._executeActions(aiMessage); if (report.length > 0) { const reportMsg = `\n\n---\n**에이전트 작업 결과**\n${report.join('\n')}`; this._view.webview.postMessage({ type: 'streamChunk', value: reportMsg }); @@ -1006,7 +1010,7 @@ class SidebarChatProvider implements vscode.WebviewViewProvider { this._chatHistory.push({ role: 'assistant', content: aiMessage }); // 5. Execute agent actions - const report = this._executeActions(aiMessage); + const report = await this._executeActions(aiMessage); // 6. Agent report 추가 (있을 때만) if (report.length > 0) { @@ -1065,7 +1069,7 @@ class SidebarChatProvider implements vscode.WebviewViewProvider { // -------------------------------------------------------- // Execute ALL agent actions from AI response // -------------------------------------------------------- - private _executeActions(aiMessage: string): string[] { + private async _executeActions(aiMessage: string): Promise { const report: string[] = []; let rootPath = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath; @@ -1247,6 +1251,32 @@ class SidebarChatProvider implements vscode.WebviewViewProvider { } } + // ACTION 8: Read Urls (Web Scraping) + const urlRegex = /<(?:read_url|url|fetch_url)>([\s\S]*?)<\/(?:read_url|url|fetch_url)>/gi; + while ((match = urlRegex.exec(aiMessage)) !== null) { + const url = match[1].trim(); + try { + // Fetch the HTML content + const { data } = await axios.get(url, { headers: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' }, timeout: 10000 }); + // Strip scripts and styles first + let cleaned = data.toString() + .replace(/)<[^<]*)*<\/script>/gi, '') + .replace(/)<[^<]*)*<\/style>/gi, '') + // Strip remaining HTML tags + .replace(/<[^>]+>/g, ' ') + // Consolidate whitespaces + .replace(/\s+/g, ' ') + .trim(); + + const preview = cleaned.slice(0, 500); + report.push(`🌐 웹사이트 읽기: ${url} (${cleaned.length}자)\n\`\`\`\n${preview}...\n\`\`\``); + this._chatHistory.push({ role: 'user', content: `[시스템: read_url 결과]\nURL: ${url}\n\`\`\`\n${cleaned.slice(0, 15000)}\n\`\`\`` }); + } catch (err: any) { + report.push(`❌ 웹사이트 접속 실패: ${url} — ${err.message}`); + this._chatHistory.push({ role: 'user', content: `[시스템: read_url 실패]\n${err.message}` }); + } + } + // FALLBACK: If AI used markdown code blocks with filenames instead of XML tags if (report.length === 0) { const fallbackRegex = /```(?:[a-zA-Z]*)?\s*\n\/\/\s*(?:file|파일):\s*([^\n]+)\n([\s\S]*?)```/gi; diff --git a/test.ts b/test.ts new file mode 100644 index 0000000..00d6d00 --- /dev/null +++ b/test.ts @@ -0,0 +1,15 @@ +const code = ` +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'); +} +`; +console.log(code); diff --git a/test10.js b/test10.js new file mode 100644 index 0000000..8723fa7 --- /dev/null +++ b/test10.js @@ -0,0 +1,9 @@ +const templateHTML = ` + +`; +console.log(templateHTML); +const {JSDOM} = require('jsdom'); +try { new JSDOM(templateHTML, {runScripts:'dangerously'}); console.log('JSDOM OK'); } catch(e) { console.error('ERR:', e.message); } diff --git a/test11.js b/test11.js new file mode 100644 index 0000000..ff3b5ca --- /dev/null +++ b/test11.js @@ -0,0 +1,19 @@ +const templateHTML = ` + +`; +const {JSDOM} = require('jsdom'); +try { new JSDOM(templateHTML, {runScripts:'dangerously'}); console.log('JSDOM OK'); } catch(e) { console.error('ERR:', e.message); } diff --git a/test12.js b/test12.js new file mode 100644 index 0000000..93a170a --- /dev/null +++ b/test12.js @@ -0,0 +1,11 @@ +const ext = require('./out/extension'); +const fs = require('fs'); +const code = fs.readFileSync('out/extension.js', 'utf8'); +const match = code.match(/_getHtml.*?_getHtml\(\)\s*\{\s*return\s+(`(?:[^`]|\\`)*`);?/m); +if (match) { + const html = eval(match[1]); + const lines = html.split('\n'); + console.log("Total lines:", lines.length); + console.log("Line 50:", lines[49]); + console.log("Line 100:", lines[99]); +} diff --git a/test13.js b/test13.js new file mode 100644 index 0000000..b2eebb3 --- /dev/null +++ b/test13.js @@ -0,0 +1,16 @@ +const Module = require('module'); +const originalRequire = Module.prototype.require; +Module.prototype.require = function(request) { + if (request === 'vscode') return {}; + return originalRequire.apply(this, arguments); +}; + +const fs = require('fs'); +const code = fs.readFileSync('out/extension.js', 'utf8'); +const match = code.match(/_getHtml.*?([\s\S]*?)<\/html>.*?;/m); +if (match) { + const html = match[1] + ""; + const lines = html.split('\n'); + console.log("Total lines:", lines.length); + // print from line 60 to end +} diff --git a/test14.js b/test14.js new file mode 100644 index 0000000..497d021 --- /dev/null +++ b/test14.js @@ -0,0 +1,14 @@ +const ext = require('./out/extension'); +const fs = require('fs'); +const code = fs.readFileSync('out/extension.js', 'utf8'); +const match = code.match(/_getHtml.*?\{\s*return\s+(`(?:[^`]|\\`)*`);/m); +if (match) { + let templateBody = match[1]; + const html = eval(templateBody); + const lines = html.split('\n'); + console.log("Line 92:", lines[91]); + console.log("Line 93:", lines[92]); + console.log("Line 94:", lines[93]); + console.log("Line 95:", lines[94]); + console.log("Line 96:", lines[95]); +} diff --git a/test15.js b/test15.js new file mode 100644 index 0000000..cf26320 --- /dev/null +++ b/test15.js @@ -0,0 +1,18 @@ +const Module = require('module'); +const originalRequire = Module.prototype.require; +Module.prototype.require = function(request) { + if (request === 'vscode') return {}; + return originalRequire.apply(this, arguments); +}; +const fs = require('fs'); +const code = fs.readFileSync('out/extension.js', 'utf8'); +const match = code.match(/_getHtml.*?_getHtml\(\)\s*\{\s*return\s+(`(?:[^`]|\\`)*`);?/m); +if (match) { + const html = eval(match[1]); + const lines = html.split('\n'); + console.log("Line 92:", lines[91]); + console.log("Line 93:", lines[92]); + console.log("Line 94:", lines[93]); + console.log("Line 95:", lines[94]); + console.log("Line 96:", lines[95]); +} diff --git a/test16.js b/test16.js new file mode 100644 index 0000000..fb642b8 --- /dev/null +++ b/test16.js @@ -0,0 +1,8 @@ +const fs = require('fs'); +let html = fs.readFileSync('test.html', 'utf8'); +const lines = html.split('\n'); +console.log("Line 92:", lines[91]); +console.log("Line 93:", lines[92]); +console.log("Line 94:", lines[93]); +console.log("Line 95:", lines[94]); +console.log("Line 96:", lines[95]); diff --git a/test17.js b/test17.js new file mode 100644 index 0000000..d9b3d32 --- /dev/null +++ b/test17.js @@ -0,0 +1,4 @@ +const s = `\\/\\/[^\\n]*`; +console.log("Characters in string:"); +for (let i = 0; i < s.length; i++) console.log(s[i], s.charCodeAt(i)); +console.log("Printed:", s); diff --git a/test18.js b/test18.js new file mode 100644 index 0000000..5e6cc95 --- /dev/null +++ b/test18.js @@ -0,0 +1,8 @@ +const templateHTML = ` + +`; +const {JSDOM} = require('jsdom'); +try { new JSDOM(templateHTML, {runScripts:'dangerously'}); console.log('JSDOM OK'); } catch(e) { console.error('ERR:', e.message); } diff --git a/test19.js b/test19.js new file mode 100644 index 0000000..27ffada --- /dev/null +++ b/test19.js @@ -0,0 +1,20 @@ +const templateHTML = ` + +`; +const {JSDOM} = require('jsdom'); +try { new JSDOM(templateHTML, {runScripts:'dangerously'}); console.log('JSDOM OK'); } catch(e) { console.error('ERR:', e.message); } diff --git a/test20.js b/test20.js new file mode 100644 index 0000000..f95a449 --- /dev/null +++ b/test20.js @@ -0,0 +1,8 @@ +const templateHTML = ` + +`; +const {JSDOM} = require('jsdom'); +try { new JSDOM(templateHTML, {runScripts:'dangerously'}); console.log('JSDOM OK'); } catch(e) { console.error('ERR:', e.message); } diff --git a/test21.js b/test21.js new file mode 100644 index 0000000..beadedc --- /dev/null +++ b/test21.js @@ -0,0 +1,29 @@ +var esc = function (s) { return s; }; +function getHtml() { + var pendingFiles = [{ name: 'foo' }]; + var text = 'bar'; + var t = ''; + if ((t.match(/\`\`\`/g) || []).length % 2 !== 0) + t += '\\\\n\`\`\`'; + var h = "var x = 1; // test"; + h = h.replace(new RegExp("(\\\\/\\\\/[^\\\\n]*)", "g"), '$1'); + h = h.replace(new RegExp("(#[^\\\\n]*)", "g"), '$1'); + h = h.replace(new RegExp("(\\\\/\\\\*[\\\\s\\\\S]*?\\\\*\\\\/)", "g"), '$1'); + h = h.replace(/("[^&]*?"|'[^&]*?')/g, '$1'); + h = h.replace(new RegExp("\\\\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(new RegExp("\\\\b(\\\\d+\\\\.?\\\\d*)\\\\b", "g"), '$1'); + h = h.replace(new RegExp("\\\\b(True|False|None|true|false|null|undefined|NaN)\\\\b", "g"), '$1'); + h = h.replace(new RegExp("\\\\b(String|Number|Boolean|Array|Object|Map|Set|Promise|void|int|float|str|list|dict|tuple)\\\\b", "g"), '$1'); + h = h.replace(/([=!+\\-*/%|&^~?:]+)/g, '$1'); + var displayText = text + (pendingFiles.length > 0 ? '\\\\n\\ud83d\\udcce ' + pendingFiles.map(function (f) { return f.name; }).join(', ') : ''); + return "\n \n "); +} +var html = getHtml(); +var JSDOM = require('jsdom').JSDOM; +try { + new JSDOM(html, { runScripts: 'dangerously' }); + console.log('JSDOM OK'); +} +catch (e) { + console.error('ERR:', e.message); +} diff --git a/test21.ts b/test21.ts new file mode 100644 index 0000000..6f37ed3 --- /dev/null +++ b/test21.ts @@ -0,0 +1,42 @@ +const esc = (s:string)=>s; +function getHtml() { + let pendingFiles = [{name: 'foo'}]; + let text = 'bar'; + let t = ''; + if((t.match(/\`\`\`/g)||[]).length % 2 !== 0) t += '\\\\n\`\`\`'; + + let h = "var x = 1; // test"; + h=h.replace(new RegExp("(\\\\/\\\\/[^\\\\n]*)", "g"),'$1'); + h=h.replace(new RegExp("(#[^\\\\n]*)", "g"),'$1'); + h=h.replace(new RegExp("(\\\\/\\\\*[\\\\s\\\\S]*?\\\\*\\\\/)", "g"),'$1'); + h=h.replace(/("[^&]*?"|'[^&]*?')/g,'$1'); + h=h.replace(new RegExp("\\\\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(new RegExp("\\\\b(\\\\d+\\\\.?\\\\d*)\\\\b", "g"),'$1'); + h=h.replace(new RegExp("\\\\b(True|False|None|true|false|null|undefined|NaN)\\\\b", "g"),'$1'); + h=h.replace(new RegExp("\\\\b(String|Number|Boolean|Array|Object|Map|Set|Promise|void|int|float|str|list|dict|tuple)\\\\b", "g"),'$1'); + h=h.replace(/([=!+\\-*/%|&^~?:]+)/g,'$1'); + + const displayText=text+(pendingFiles.length>0?'\\\\n\\ud83d\\udcce '+pendingFiles.map(f=>f.name).join(', '):''); + + return ` + + `; +} +const html = getHtml(); +const {JSDOM} = require('jsdom'); +try { new JSDOM(html, {runScripts:'dangerously'}); console.log('JSDOM OK'); } catch(e) { console.error('ERR:', e.message); } diff --git a/test22.js b/test22.js new file mode 100644 index 0000000..9e52bda --- /dev/null +++ b/test22.js @@ -0,0 +1,19 @@ +var esc = function (s) { return s; }; +function getHtml() { + var pendingFiles = [{ name: 'foo' }]; + var text = 'bar'; + var t = ''; + if ((t.match(/\`\`\`/g) || []).length % 2 !== 0) + t += '\\\\n\`\`\`'; + var displayText = text + (pendingFiles.length > 0 ? '\\\\n\\ud83d\\udcce ' + pendingFiles.map(function (f) { return f.name; }).join(', ') : ''); + return "\n \n "); +} +var html = getHtml(); +var JSDOM = require('jsdom').JSDOM; +try { + new JSDOM(html, { runScripts: 'dangerously' }); + console.log('JSDOM OK'); +} +catch (e) { + console.error('ERR:', e.message); +} diff --git a/test22.ts b/test22.ts new file mode 100644 index 0000000..e3710e2 --- /dev/null +++ b/test22.ts @@ -0,0 +1,31 @@ +const esc = (s:string)=>s; +function getHtml() { + let pendingFiles = [{name: 'foo'}]; + let text = 'bar'; + let t = ''; + if((t.match(/\`\`\`/g)||[]).length % 2 !== 0) t += '\\\\n\`\`\`'; + + const displayText=text+(pendingFiles.length>0?'\\\\n\\ud83d\\udcce '+pendingFiles.map(f=>f.name).join(', '):''); + + return ` + + `; +} +const html = getHtml(); +const {JSDOM} = require('jsdom'); +try { new JSDOM(html, {runScripts:'dangerously'}); console.log('JSDOM OK'); } catch(e) { console.error('ERR:', e.message); } diff --git a/test23.js b/test23.js new file mode 100644 index 0000000..485ac91 --- /dev/null +++ b/test23.js @@ -0,0 +1,17 @@ +const Module = require('module'); +const originalRequire = Module.prototype.require; +Module.prototype.require = function(request) { + if (request === 'vscode') return { window: {}, workspace: {}, Uri: {}, EventEmitter: class {} }; + return originalRequire.apply(this, arguments); +}; +const fs = require('fs'); +const code = fs.readFileSync('out/extension.js', 'utf8'); +const match = code.match(/_getHtml.*?([\s\S]*?)<\/html>.*?;/m); +if (!match) { + const match2 = code.match(/_getHtml.*?\{\s*return\s+(`(?:[^`]|\\`)*`);?/m); + if (match2) { + const html = eval(match2[1]); + const {JSDOM} = require('jsdom'); + try { new JSDOM(html, {runScripts:'dangerously'}); console.log('JSDOM EVAL OK'); } catch(e) { console.error('EVAL ERR:', e.message); } + } else { console.log('no match2 either'); } +} diff --git a/test4.js b/test4.js new file mode 100644 index 0000000..d1be385 --- /dev/null +++ b/test4.js @@ -0,0 +1,10 @@ +const ext = require('./out/extension'); +const html = ext.ConnectAIPanel.prototype._getHtml.call({_getHtml: ext.ConnectAIPanel.prototype._getHtml}); +require('fs').writeFileSync('test4.html', html); +const {JSDOM} = require('jsdom'); +try { + new JSDOM(html, {runScripts:'dangerously'}); + console.log("JSDOM OK"); +} catch(e) { + console.log("JSDOM FATAL ERROR:", e.message); +} diff --git a/test5.js b/test5.js new file mode 100644 index 0000000..95c7fa4 --- /dev/null +++ b/test5.js @@ -0,0 +1,17 @@ +const Module = require('module'); +const originalRequire = Module.prototype.require; +Module.prototype.require = function(request) { + if (request === 'vscode') return { window: {}, workspace: {}, Uri: {}, EventEmitter: class {} }; + return originalRequire.apply(this, arguments); +}; + +const fs = require('fs'); +let html = fs.readFileSync('test.html', 'utf8'); + +const {JSDOM} = require('jsdom'); +try { + new JSDOM(html, {runScripts:'dangerously'}); + console.log("JSDOM OK"); +} catch(e) { + console.log("JSDOM FATAL ERROR:", e.message); +} diff --git a/test6.js b/test6.js new file mode 100644 index 0000000..7b15f7d --- /dev/null +++ b/test6.js @@ -0,0 +1,37 @@ +const Module = require('module'); +const originalRequire = Module.prototype.require; +Module.prototype.require = function(request) { + if (request === 'vscode') return { window: {}, workspace: {}, Uri: {}, EventEmitter: class {} }; + return originalRequire.apply(this, arguments); +}; +const ext = require('./out/extension'); + +// Try calling _getHtml +let html; +try { + html = ext.ConnectAIPanel.prototype._getHtml.call({_getHtml: ext.ConnectAIPanel.prototype._getHtml}); +} catch(e) { + console.log("Could not call _getHtml directly:", e.message); + // Alternative: match from the original code and eval it + const fs = require('fs'); + const code = fs.readFileSync('out/extension.js', 'utf8'); + const match = code.match(/_getHtml.*?_getHtml\(\)\s*\{\s*return\s+(`(?:[^`]|\\`)*`);?/m); + if (match) { + html = eval(match[1]); + } +} + +if (!html) { + console.log("Failed to get html"); + process.exit(1); +} + +const fs = require('fs'); +fs.writeFileSync('test_eval2.html', html); +const {JSDOM} = require('jsdom'); +try { + new JSDOM(html, {runScripts:'dangerously'}); + console.log("JSDOM OK"); +} catch(e) { + console.log("JSDOM FATAL ERROR:", e.message); +} diff --git a/test7.js b/test7.js new file mode 100644 index 0000000..16f72f9 --- /dev/null +++ b/test7.js @@ -0,0 +1,4 @@ +const fs=require('fs'); +let t=fs.readFileSync('out/extension.js', 'utf8'); +let m = t.match(/h=h\.replace\([^)]*\)/g); +console.log(m); diff --git a/test8.js b/test8.js new file mode 100644 index 0000000..6b057f1 --- /dev/null +++ b/test8.js @@ -0,0 +1,4 @@ +const fs=require('fs'); +let t=fs.readFileSync('out/extension.js', 'utf8'); +let m = t.match(/h=h\.replace\([^)]*\)/g); +m.forEach(x => console.log(x)); diff --git a/test9.js b/test9.js new file mode 100644 index 0000000..bb9b8e4 --- /dev/null +++ b/test9.js @@ -0,0 +1,7 @@ +const templateHTML = ` + +`; +console.log(templateHTML); diff --git a/test_ax.js b/test_ax.js new file mode 100644 index 0000000..05cc2ac --- /dev/null +++ b/test_ax.js @@ -0,0 +1,17 @@ +const axios = require('axios'); +async function run() { + try { + const res = await axios.post('http://127.0.0.1:1234/v1/chat/completions', { + model: 'test', messages: [{role:'user', content:'hi'}], stream: true + }, {responseType: 'stream'}); + } catch (error) { + if (error.response?.data?.on) { + let buf=''; + error.response.data.on('data', c=>buf+=c.toString()); + error.response.data.on('end', () => console.log('Parsed stream error:', JSON.parse(buf).error.message)); + } else { + console.log('Plain error msg:', error.message); + } + } +} +run(); diff --git a/test_ax2.js b/test_ax2.js new file mode 100644 index 0000000..4d8f553 --- /dev/null +++ b/test_ax2.js @@ -0,0 +1,15 @@ +const axios = require('axios'); +async function run() { + try { + await axios.post('http://127.0.0.1:1234/v1/chat/completions', {model:'test',messages:[{role:'user',content:'a'}]}, {responseType:'stream'}); + } catch(error) { + const status = error.response?.status; + console.log('STATUS:', status); + if (error.response?.data?.on) { + let b=''; + error.response.data.on('data',c=>b+=c.toString()); + error.response.data.on('end',()=>console.log('BODY:', b)); + } + } +} +run(); diff --git a/test_eval.js b/test_eval.js new file mode 100644 index 0000000..802ef8e --- /dev/null +++ b/test_eval.js @@ -0,0 +1,20 @@ +const Module = require('module'); +const originalRequire = Module.prototype.require; +Module.prototype.require = function(request) { + if (request === 'vscode') { + return { + window: { createWebviewPanel: () => {} }, + workspace: {}, + Uri: {}, + EventEmitter: class {}, + ExtensionContext: class {}, + WebviewPanel: class {} + }; + } + return originalRequire.apply(this, arguments); +}; + +const ext = require('./out/extension'); +const html = ext.ConnectAIPanel.prototype._getHtml.call({_getHtml: ext.ConnectAIPanel.prototype._getHtml}); +require('fs').writeFileSync('test_eval.html', html); +console.log('Evaluated length:', html.length); diff --git a/test_final.js b/test_final.js new file mode 100644 index 0000000..765d6ff --- /dev/null +++ b/test_final.js @@ -0,0 +1,22 @@ +var esc = function (s) { return s; }; +function getHtml() { + var pendingFiles = [{ name: 'foo' }]; + var text = 'bar'; + var t = ''; + if ((t.match(/\`\`\`/g) || []).length % 2 !== 0) + t += '\\\\n\`\`\`'; + var h = "var x = 1; // test"; + h = h.replace(/(\\/, { 2: }[ ^ ], n, * ) / g, '$1'; + ; + h = h.replace(/(#[^\\\\n]*)/g, '$1'); + h = h.replace(/(\\/, { 1: }, * [s, S] * ? : , * , /{1})/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'); + var displayText = text + (pendingFiles.length > 0 ? '\\\\n\\ud83d\\udcce ' + pendingFiles.map(function (f) { return f.name; }).join(', ') : ''); + return "\n \n "); +} +console.log(getHtml()); diff --git a/test_final.ts b/test_final.ts new file mode 100644 index 0000000..14fb6b0 --- /dev/null +++ b/test_final.ts @@ -0,0 +1,29 @@ +const esc = (s:string)=>s; +function getHtml() { + let pendingFiles = [{name: 'foo'}]; + let text = 'bar'; + let t = ''; + if((t.match(/\`\`\`/g)||[]).length % 2 !== 0) t += '\\\\n\`\`\`'; + + let h = "var x = 1; // test"; + h=h.replace(/(\\/{2}[^\\\\n]*)/g,'$1'); + h=h.replace(/(#[^\\\\n]*)/g,'$1'); + h=h.replace(/(\\/{1}\\*[\\s\\S]*?\\*\\/{1})/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'); + + const displayText=text+(pendingFiles.length>0?'\\\\n\\ud83d\\udcce '+pendingFiles.map(f=>f.name).join(', '):''); + + return ` + + `; +} +console.log(getHtml()); diff --git a/test_flags.js b/test_flags.js new file mode 100644 index 0000000..e4db8ad --- /dev/null +++ b/test_flags.js @@ -0,0 +1,8 @@ +const html = ` h=h.replace(/([=!<>+\\\\-*/%|&^~?:]+)/g,'$1');`; +console.log(html); +try { + eval(html); + console.log("OK"); +} catch(e) { + console.log("ERROR:", e.message); +} diff --git a/test_flags2.js b/test_flags2.js new file mode 100644 index 0000000..f92ffd1 --- /dev/null +++ b/test_flags2.js @@ -0,0 +1,13 @@ +const lines = [ + `h=h.replace(/(\\\\/\\\\/[^\\\\n]*)/g,'');`, + `h=h.replace(/(#[^\\\\n]*)/g,'');`, + `h=h.replace(/(\\\\/\\\\*[\\\\s\\\\S]*?\\\\*\\\\/)/g,'');`, +]; +for(let line of lines) { + try { + eval(line); + console.log("OK:", line); + } catch(e) { + console.log("ERROR:", e.message, "ON", line); + } +} diff --git a/test_flags3.js b/test_flags3.js new file mode 100644 index 0000000..747bbc0 --- /dev/null +++ b/test_flags3.js @@ -0,0 +1,13 @@ +const lines = [ + `h=h.replace(/(\\/\\/[^\\n]*)/g,'');`, + `h=h.replace(/(#[^\\n]*)/g,'');`, + `h=h.replace(/(\\/\\*[\\s\\S]*?\\*\\/)/g,'');`, +]; +for(let line of lines) { + try { + eval(line); + console.log("OK:", line); + } catch(e) { + console.log("ERROR:", e.message, "ON:", line); + } +} diff --git a/test_html.js b/test_html.js new file mode 100644 index 0000000..03dcd64 --- /dev/null +++ b/test_html.js @@ -0,0 +1,20 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var fs = require("fs"); +var text = fs.readFileSync('src/extension.ts', 'utf-8'); +var match = text.match(/_getHtml.*?\{([\s\S]*?)^\s*\}/m); +if (match) { + var inner = match[1]; + // extract the returned string + var strMatch = inner.match(/return\s+`(.*)`/ms); + if (strMatch) { + fs.writeFileSync('test.html', strMatch[1]); + console.log("Wrote test.html length", strMatch[1].length); + } + else { + console.log('no string'); + } +} +else { + console.log('no func'); +} diff --git a/test_html.ts b/test_html.ts new file mode 100644 index 0000000..dd70783 --- /dev/null +++ b/test_html.ts @@ -0,0 +1,12 @@ +import * as fs from 'fs'; +const text = fs.readFileSync('src/extension.ts', 'utf-8'); +const match = text.match(/_getHtml.*?\{([\s\S]*?)^\s*\}/m); +if (match) { + let inner = match[1]; + // extract the returned string + const strMatch = inner.match(/return\s+`(.*)`/ms); + if (strMatch) { + fs.writeFileSync('test.html', strMatch[1]); + console.log("Wrote test.html length", strMatch[1].length); + } else { console.log('no string'); } +} else { console.log('no func'); } diff --git a/test_html2.js b/test_html2.js new file mode 100644 index 0000000..7d1e745 --- /dev/null +++ b/test_html2.js @@ -0,0 +1,369 @@ +const html = ` + +Connect AI + +
Connect AI
+
+
+
+
+ +
Connect AI
+
\ubcf4\uc548 \u00b7 \ube44\uc6a9\ucd5c\uc801\ud654 \u00b7 \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.
+
+
+
+ +
+
+
+`; +fs.writeFileSync('test_eval3.html', html); +console.log(html.length); \ No newline at end of file diff --git a/test_html2.ts b/test_html2.ts new file mode 100644 index 0000000..385a868 --- /dev/null +++ b/test_html2.ts @@ -0,0 +1,8 @@ +import * as fs from 'fs'; +const base = fs.readFileSync('src/extension.ts', 'utf-8'); +const match = base.match(/_getHtml.*?\{\s*return\s+(`(?:[^`]|\\`)*`);/m); +if (match) { + let templateBody = match[1]; + // make it a valid JS file + fs.writeFileSync('test_html2.js', `const html = ${templateBody};\nfs.writeFileSync('test_eval3.html', html);\nconsole.log(html.length);`); +} diff --git a/test_jsdom.js b/test_jsdom.js new file mode 100644 index 0000000..ae2c3e9 --- /dev/null +++ b/test_jsdom.js @@ -0,0 +1,9 @@ +const orig = ` + +`; +const {JSDOM} = require('jsdom'); +try { new JSDOM(orig, {runScripts:'dangerously'}); console.log('JSDOM OK'); } catch(e) { console.error('ERR:', e.message); } diff --git a/test_regex_isolated.js b/test_regex_isolated.js new file mode 100644 index 0000000..e2aeb00 --- /dev/null +++ b/test_regex_isolated.js @@ -0,0 +1,4 @@ +// Test just the create_file regex pattern in isolation +const t = 'console.log("hi")'; +const result = t.replace(/([\s\S]*?)<\/create_file>/g, 'MATCH:$1:$2'); +console.log(result); diff --git a/test_script.js b/test_script.js new file mode 100644 index 0000000..8334690 --- /dev/null +++ b/test_script.js @@ -0,0 +1,192 @@ + +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
'+esc(c)+'
')); + t=t.replace(/([\\s\\S]*?)<\\/edit_file>/g,(_,p,c)=>pushB('
\u270F\uFE0F '+esc(p)+' \u2014 \uD3B8\uC9D1\uB428
'+esc(c)+'
')); + 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='
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='
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 + '
'; +} diff --git a/test_v2.html b/test_v2.html new file mode 100644 index 0000000..9242eb5 --- /dev/null +++ b/test_v2.html @@ -0,0 +1,367 @@ + + +Connect AI + +
Connect AI
+
+
+
+
+ +
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.
+
+
+
+ +
+
+
+ \ No newline at end of file diff --git a/test_v2_full.js b/test_v2_full.js new file mode 100644 index 0000000..bc316e7 --- /dev/null +++ b/test_v2_full.js @@ -0,0 +1,18 @@ +const Module = require('module'); +const originalRequire = Module.prototype.require; +Module.prototype.require = function(request) { + if (request === 'vscode') return { window: {}, workspace: {}, Uri: {}, EventEmitter: class {} }; + return originalRequire.apply(this, arguments); +}; +const fs = require('fs'); +const connectAI = require('./out/extension'); +const htmlSource = connectAI.ConnectAIPanel.prototype._getHtml.toString(); +const htmlBodyMatch = htmlSource.match(/return\s+`([\s\S]*?)`/); +if (htmlBodyMatch) { + const evaluateTemplateString = new Function('return `' + htmlBodyMatch[1] + '`'); + const evaluatedHtml = evaluateTemplateString(); + const {JSDOM} = require('jsdom'); + try { new JSDOM(evaluatedHtml, {runScripts:'dangerously'}); console.log('JSDOM FULL HTML OK'); } catch(e) { console.error('EVAL ERR:', e.stack); } +} else { + console.log('no match'); +} diff --git a/test_v2_script.js b/test_v2_script.js new file mode 100644 index 0000000..5161c90 --- /dev/null +++ b/test_v2_script.js @@ -0,0 +1,191 @@ + +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(new RegExp("(\\\\/\\\\/[^\\\\n]*)", "g"),'$1'); + h=h.replace(new RegExp("(#[^\\\\n]*)", "g"),'$1'); + h=h.replace(new RegExp("(\\\\/\\\\*[\\\\s\\\\S]*?\\\\*\\\\/)", "g"),'$1'); + h=h.replace(/("[^&]*?"|'[^&]*?')/g,'$1'); + h=h.replace(new RegExp("\\\\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(new RegExp("\\\\b(\\\\d+\\\\.?\\\\d*)\\\\b", "g"),'$1'); + h=h.replace(new RegExp("\\\\b(True|False|None|true|false|null|undefined|NaN)\\\\b", "g"),'$1'); + h=h.replace(new RegExp("\\\\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
'+esc(c)+'
')); + t=t.replace(/([\\s\\S]*?)<\\/edit_file>/g,(_,p,c)=>pushB('
\u270F\uFE0F '+esc(p)+' \u2014 \uD3B8\uC9D1\uB428
'+esc(c)+'
')); + 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?'\\\\n\\ud83d\\udcce '+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='
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='
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 + '
'; +} diff --git a/test_verify.html b/test_verify.html new file mode 100644 index 0000000..9242eb5 --- /dev/null +++ b/test_verify.html @@ -0,0 +1,367 @@ + + +Connect AI + +
Connect AI
+
+
+
+
+ +
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.
+
+
+
+ +
+
+
+ \ No newline at end of file diff --git a/test_verify_script.js b/test_verify_script.js new file mode 100644 index 0000000..5161c90 --- /dev/null +++ b/test_verify_script.js @@ -0,0 +1,191 @@ + +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(new RegExp("(\\\\/\\\\/[^\\\\n]*)", "g"),'$1'); + h=h.replace(new RegExp("(#[^\\\\n]*)", "g"),'$1'); + h=h.replace(new RegExp("(\\\\/\\\\*[\\\\s\\\\S]*?\\\\*\\\\/)", "g"),'$1'); + h=h.replace(/("[^&]*?"|'[^&]*?')/g,'$1'); + h=h.replace(new RegExp("\\\\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(new RegExp("\\\\b(\\\\d+\\\\.?\\\\d*)\\\\b", "g"),'$1'); + h=h.replace(new RegExp("\\\\b(True|False|None|true|false|null|undefined|NaN)\\\\b", "g"),'$1'); + h=h.replace(new RegExp("\\\\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
'+esc(c)+'
')); + t=t.replace(/([\\s\\S]*?)<\\/edit_file>/g,(_,p,c)=>pushB('
\u270F\uFE0F '+esc(p)+' \u2014 \uD3B8\uC9D1\uB428
'+esc(c)+'
')); + 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?'\\\\n\\ud83d\\udcce '+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='
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='
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 + '
'; +}