chore: Clean up duplicate test files and update README for antigravity
This commit is contained in:
@@ -1,133 +0,0 @@
|
||||
# MrBeast Premium Brain Pack (최신 10개 영상 정밀 스캔)
|
||||
|
||||
> 이 문서는 Agent University (A.U) 전용 마크다운 형식으로 추출된 최고 등급 크리에이터 데이터셋입니다.
|
||||
> 영상 데이터, 성과 지표(조회수, 좋아요 수, 댓글 수), 상세 설명, 태그, 풀스크립트가 담겨있습니다.
|
||||
|
||||
## 🎬 [Can a Window Stop a Wrecking Ball?](https://youtu.be/6W_841xoprg)
|
||||
|
||||

|
||||
|
||||
### 📊 [핵심 성과 지표 (KPI)]
|
||||
- **Video ID:** `6W_841xoprg`
|
||||
- **게시일:** `2026-04-14`
|
||||
- **조회수:** `23,124,614 회`
|
||||
- **좋아요 수:** `569,581 개`
|
||||
- **댓글 수:** `6,236 개`
|
||||
|
||||
### 🏷️ [태그 필드 (SEO Tags)]
|
||||
태그 없음
|
||||
|
||||
### 📝 [디스크립션 (Description)]
|
||||
```text
|
||||
|
||||
```
|
||||
|
||||
### 🔊 [대본 파일 풀-스크립트 (Voice Transcript)]
|
||||
> **(이 스크립트를 분석하여 알고리즘 방어율을 측정하세요.)**
|
||||
|
||||
DROP THE WRECKING BALL. THAT DIDN'T WORK. LET'S TRY WOOD. DROP IT. OH, THAT WAS AWESOME. YOU KNOW WHAT'S MORE DURABLE than wood? Bricks. DROP IT. 1 2 3 OH! OH, IT WENT THROUGH ALL OF THEM. SUBSCRIBE IF YOU THINK THE NEXT ONE WILL STOP IT. DROP IT. OH! OH, IT WAS SO CLOSE. OH, RIGHT AWAY wrecking ball.
|
||||
|
||||
---
|
||||
|
||||
## 🎬 [Don’t Eat The Spicy Yoshi Egg](https://youtu.be/VIJLIo5yT1I)
|
||||
|
||||

|
||||
|
||||
### 📊 [핵심 성과 지표 (KPI)]
|
||||
- **Video ID:** `VIJLIo5yT1I`
|
||||
- **게시일:** `2026-04-10`
|
||||
- **조회수:** `60,378,398 회`
|
||||
- **좋아요 수:** `1,160,164 개`
|
||||
- **댓글 수:** `9,445 개`
|
||||
|
||||
### 🏷️ [태그 필드 (SEO Tags)]
|
||||
태그 없음
|
||||
|
||||
### 📝 [디스크립션 (Description)]
|
||||
```text
|
||||
|
||||
```
|
||||
|
||||
### 🔊 [대본 파일 풀-스크립트 (Voice Transcript)]
|
||||
> **(이 스크립트를 분석하여 알고리즘 방어율을 측정하세요.)**
|
||||
|
||||
Don't eat the spicy egg. I'm going to guess this isn't spicy. Okay, I passed. I passed. Why are you looking at me? I don't know the answer. Mhm, we're good. Oh, jeez. Okay, well Woah, woah, woah. Got to do that one. Woah, you're good. Mhm, this one. Oh, that was a big bite. >> That's good. These are Yoshi eggs that we made in collaboration with the new Super Mario Galaxy movie. This one doesn't look spicy. >> Are you sure? Mhm, okay. We passed. Oh, he passed. We're good. We're good. Just fully commit. [music] Woah, what? There's a one in three? Oh, no. Commit. Just commit. Yeah. So, what the heck? 50/50 chance. Oh, before I bite it, if you want to try these Yoshi eggs for yourself, they're [music] available at these retailers. Oh, uh all the hot. >> Yes. >> My homeboy.
|
||||
|
||||
---
|
||||
|
||||
## 🎬 [$1 or Mystery Vial?](https://youtu.be/uNu6XEPTy5g)
|
||||
|
||||

|
||||
|
||||
### 📊 [핵심 성과 지표 (KPI)]
|
||||
- **Video ID:** `uNu6XEPTy5g`
|
||||
- **게시일:** `2026-04-07`
|
||||
- **조회수:** `31,384,634 회`
|
||||
- **좋아요 수:** `983,497 개`
|
||||
- **댓글 수:** `12,067 개`
|
||||
|
||||
### 🏷️ [태그 필드 (SEO Tags)]
|
||||
태그 없음
|
||||
|
||||
### 📝 [디스크립션 (Description)]
|
||||
```text
|
||||
|
||||
```
|
||||
|
||||
### 🔊 [대본 파일 풀-스크립트 (Voice Transcript)]
|
||||
> **(이 스크립트를 분석하여 알고리즘 방어율을 측정하세요.)**
|
||||
|
||||
$1 or drink what's in this vial? Uh, I'll take the dollar. >> $100 or drink what's in this vial? >> What's in the vial? >> Compound V. Give it a try. Okay. Oh. Oh. Wow, that was quick to kick in. I want to see what else the other vials do. Okay, let's see what powers he gets. This is Compound V. Oh. Wait. Wait. Wait. Wait. That >> [screaming] >> Massive shout out to Vought Homelander for sending me so much Compound V. Actually, want to try one myself. What's up, bro? What are you doing here? Okay, try touching you real quick. Wait, I want to just see if you turn around. Look up.
|
||||
|
||||
---
|
||||
|
||||
## 🎬 [50 Streamers Fight for $1,000,000](https://youtu.be/DXVHmGoCTco)
|
||||
|
||||

|
||||
|
||||
### 📊 [핵심 성과 지표 (KPI)]
|
||||
- **Video ID:** `DXVHmGoCTco`
|
||||
- **게시일:** `2026-04-04`
|
||||
- **조회수:** `87,487,275 회`
|
||||
- **좋아요 수:** `2,305,643 개`
|
||||
- **댓글 수:** `154,354 개`
|
||||
|
||||
### 🏷️ [태그 필드 (SEO Tags)]
|
||||
태그 없음
|
||||
|
||||
### 📝 [디스크립션 (Description)]
|
||||
```text
|
||||
i can't believe we got all these streamers together in one place lol
|
||||
|
||||
Shopify empowers creators every single day to be entrepreneurs, founders, and CEOs. Now it’s your turn. Go to https://www.shopify.com/mrbeast to start your business today.
|
||||
|
||||
Tomorrow for the livestream, join the chat and make sure your instagram or twitter handle is viewable on your YouTube bio, so I can contact you if you win!
|
||||
|
||||
New Merch - https://mrbeast.store
|
||||
|
||||
Check out Viewstats! - https://www.viewstats.com/
|
||||
|
||||
SUBSCRIBE OR I TAKE YOUR DOG
|
||||
╔═╦╗╔╦╗╔═╦═╦╦╦╦╗╔═╗
|
||||
║╚╣║║║╚╣╚╣╔╣╔╣║╚╣═╣
|
||||
╠╗║╚╝║║╠╗║╚╣║║║║║═╣
|
||||
╚═╩══╩═╩═╩═╩╝╚╩═╩═╝
|
||||
|
||||
For any questions or inquiries regarding this video, please reach out to chucky@mrbeastbusiness.com
|
||||
|
||||
----------------------------------------------------------------
|
||||
follow all of these or i will kick you
|
||||
• Facebook - https://www.facebook.com/MrBeast/
|
||||
• Twitter - https://twitter.com/MrBeast
|
||||
• Instagram - https://www.instagram.com/mrbeast
|
||||
• Im Hiring! - https://www.mrbeastjobs.com/
|
||||
--------------------------------------------------------------------
|
||||
```
|
||||
|
||||
### 🔊 [대본 파일 풀-스크립트 (Voice Transcript)]
|
||||
> **(이 스크립트를 분석하여 알고리즘 방어율을 측정하세요.)**
|
||||
|
||||
여기 세계 최고의 스트리머 여기 세계 최고의 스트리머 50명을 이 큐브 안에 가둬놨습니다 50명을 이 큐브 안에 가둬놨습니다 마지막까지 남는 사람이 100만 달러를 가져갑니다! 마지막까지 남는 사람이 100만 달러를 가져갑니다! 여기 모인 사람들은 진짜 현존하는 여기 모인 사람들은 진짜 현존하는 월드클래스 스트리머들입니다 월드클래스 스트리머들입니다 끝까지 버티는 한 명이 상금 전부 가져갑니다 끝까지 버티는 한 명이 상금 전부 가져갑니다 같이 가는 거다 끝까지 같이 가, 알았지? 같이 가는 거다 끝까지 같이 가, 알았지? -여기 스페인어 쓰는 팀인가? -어, 여기 다 친구야 -여기 스페인어 쓰는 팀인가? -어, 여기 다 친구야 이건 완전 전략 싸움이야 나 지금 다 할 수 있어 이건 완전 전략 싸움이야 나 지금 다 할 수 있어 바닥에서 트워킹하라고 하면 할 거야 바닥에서 트워킹하라고 하면 할 거야 난 싫은데 난 싫은데 -아니, 그냥 시키면 하겠단 거지 -제발 안 해주면 좋겠어 -아니, 그냥 시키면 하겠단 거지
|
||||
|
||||
... (나머지 대본 내용 생략)
|
||||
```
|
||||
@@ -1,171 +1 @@
|
||||
<p align="center">
|
||||
<img src="assets/icon.png" width="120" alt="Connect AI Logo" />
|
||||
</p>
|
||||
|
||||
<h1 align="center">Connect AI</h1>
|
||||
|
||||
<p align="center">
|
||||
<strong>100% Local · 100% Offline · 100% Free</strong><br/>
|
||||
Your AI coding agent that lives entirely on your machine.
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<img src="https://img.shields.io/badge/version-2.1.2-blue" alt="version" />
|
||||
<img src="https://img.shields.io/badge/license-MIT-green" alt="license" />
|
||||
<img src="https://img.shields.io/badge/platform-VS%20Code%20%7C%20Cursor%20%7C%20Antigravity-purple" alt="platform" />
|
||||
<img src="https://img.shields.io/badge/engine-Ollama%20%7C%20LM%20Studio-orange" alt="engine" />
|
||||
</p>
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
Connect AI is an **agentic AI coding assistant** that runs entirely on your local machine — no cloud, no API keys, no data leaves your computer. It reads your project, creates files, edits code, manages directories, and executes terminal commands — all through natural conversation.
|
||||
|
||||
Built for **VS Code**, **Cursor**, and **Antigravity**.
|
||||
|
||||
---
|
||||
|
||||
## ⚡ Agent Capabilities
|
||||
|
||||
Connect AI doesn't just answer questions — it **acts**. Seven built-in agent actions give it full control over your development environment:
|
||||
|
||||
| Action | Description |
|
||||
|:--|:--|
|
||||
| **📄 Create Files** | Generates new files and directories in your workspace |
|
||||
| **✏️ Edit Files** | Finds and replaces specific code in existing files |
|
||||
| **🗑️ Delete Files** | Removes files and folders |
|
||||
| **📖 Read Files** | Reads any file in your workspace to understand context |
|
||||
| **📂 Browse Directories** | Lists contents of any subdirectory |
|
||||
| **🖥️ Run Terminal Commands** | Executes CLI commands (install, build, run, deploy) |
|
||||
| **🧠 Second Brain** | Queries your personal knowledge base (GitHub-synced) |
|
||||
|
||||
### How It Works
|
||||
|
||||
```
|
||||
You: "React로 카운터 앱 만들어줘"
|
||||
|
||||
Connect AI:
|
||||
✅ 생성: src/App.jsx
|
||||
✅ 생성: src/index.js
|
||||
✅ 생성: index.html
|
||||
🖥️ 실행: npm install react react-dom
|
||||
```
|
||||
|
||||
All files are created **directly in your local workspace** — no copy-paste needed.
|
||||
|
||||
---
|
||||
|
||||
## 🎨 Interface Features
|
||||
|
||||
- **🔄 Real-time Streaming** — Token-by-token response rendering
|
||||
- **⬛ Abort (Stop)** — Interrupt generation mid-stream
|
||||
- **🔄 재생성** — Regenerate any response with one click
|
||||
- **📎 Multimodal Input** — Paste images (Cmd+V) or attach files (+)
|
||||
- **💡 Syntax Highlighting** — Cinematic code block rendering
|
||||
- **⏱️ Thinking Bar** — Visual latency indicator during inference
|
||||
|
||||
---
|
||||
|
||||
## 📥 Installation
|
||||
|
||||
### Option 1: VSIX (Recommended)
|
||||
|
||||
1. Download the latest `.vsix` from [Releases](https://github.com/wonseokjung/connect-ai/releases)
|
||||
2. Open VS Code / Cursor / Antigravity
|
||||
3. `Cmd+Shift+P` → **Extensions: Install from VSIX** → Select the file
|
||||
|
||||
### Option 2: Build from Source
|
||||
|
||||
```bash
|
||||
git clone https://github.com/wonseokjung/connect-ai.git
|
||||
cd connect-ai
|
||||
npm install
|
||||
npm run compile
|
||||
npx vsce package
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ⚙️ Engine Setup
|
||||
|
||||
Connect AI requires a local AI engine. Choose one:
|
||||
|
||||
### Ollama (Recommended for Beginners)
|
||||
|
||||
```bash
|
||||
# 1. Install
|
||||
brew install ollama
|
||||
|
||||
# 2. Pull a model
|
||||
ollama pull gemma3
|
||||
|
||||
# 3. Done — Ollama runs automatically in the background
|
||||
```
|
||||
|
||||
### LM Studio (Recommended for Apple Silicon)
|
||||
|
||||
1. Download from [lmstudio.ai](https://lmstudio.ai/)
|
||||
2. Search and download a model (e.g., Gemma 4, Llama 3.1)
|
||||
3. Go to **Developer tab** (`<>` icon) → **Start Server**
|
||||
4. Ensure it shows `http://127.0.0.1:1234`
|
||||
5. In Connect AI: **⚙️ Settings** → Select **LM Studio**
|
||||
|
||||
> **💡 Tip:** In LM Studio, set **Context Length** to 8192+ for best results.
|
||||
|
||||
### Supported Models
|
||||
|
||||
| Model | Size | Best For |
|
||||
|:--|:--|:--|
|
||||
| Gemma 4 E2B | 4.4 GB | Vision + Code (Recommended) |
|
||||
| Gemma 3 | 3-5 GB | Fast general coding |
|
||||
| Llama 3.1 | 4-8 GB | Multi-language support |
|
||||
| Qwen 3 | 4-8 GB | Strong instruction following |
|
||||
| DeepSeek Coder | 6-16 GB | Code-heavy tasks |
|
||||
|
||||
---
|
||||
|
||||
## 🧠 Second Brain (Knowledge Base)
|
||||
|
||||
Sync a GitHub repository as your personal knowledge base. Connect AI will reference it when answering questions.
|
||||
|
||||
1. Click **🧠** button in the chat header
|
||||
2. Enter your GitHub repo URL
|
||||
3. Toggle knowledge mode **ON**
|
||||
|
||||
Your documents are stored locally at `~/.connect-ai-brain/`.
|
||||
|
||||
---
|
||||
|
||||
## 🔒 Privacy
|
||||
|
||||
- **Zero cloud dependency** — No internet required after setup
|
||||
- **Zero data collection** — All code stays on your machine
|
||||
- **Zero telemetry** — No analytics, no tracking
|
||||
|
||||
Your code never leaves your computer. Period.
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ Configuration
|
||||
|
||||
Access settings via the **⚙️** button in the chat panel:
|
||||
|
||||
| Setting | Default | Description |
|
||||
|:--|:--|:--|
|
||||
| Engine | Ollama | Ollama or LM Studio |
|
||||
| Temperature | 0.7 | Response creativity (0.0–1.0) |
|
||||
| Top P | 0.9 | Nucleus sampling |
|
||||
| Top K | 40 | Token selection range |
|
||||
|
||||
---
|
||||
|
||||
## License
|
||||
|
||||
MIT — Free to use, modify, and distribute.
|
||||
|
||||
---
|
||||
|
||||
<p align="center">
|
||||
<strong>Designed & Developed by <a href="https://github.com/wonseokjung">EZERAI</a> × Connect AI</strong>
|
||||
</p>
|
||||
built for antigravity
|
||||
|
||||
@@ -1,619 +0,0 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.activate = activate;
|
||||
exports.deactivate = deactivate;
|
||||
const vscode = require("vscode");
|
||||
const axios_1 = require("axios");
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
// ============================================================
|
||||
// Connect AI LAB — Full Agentic Local AI for VS Code
|
||||
// 100% Offline · File Create · File Edit · Terminal · Multi-file Context
|
||||
// ============================================================
|
||||
// Settings are read from VS Code configuration (File > Preferences > Settings)
|
||||
function getConfig() {
|
||||
const cfg = vscode.workspace.getConfiguration('connectAiLab');
|
||||
return {
|
||||
ollamaBase: cfg.get('ollamaUrl', 'http://127.0.0.1:11434'),
|
||||
defaultModel: cfg.get('defaultModel', 'gemma4:e2b'),
|
||||
maxTreeFiles: cfg.get('maxContextFiles', 200),
|
||||
timeout: cfg.get('requestTimeout', 300) * 1000,
|
||||
};
|
||||
}
|
||||
const EXCLUDED_DIRS = new Set([
|
||||
'node_modules', '.git', '.vscode', 'out', 'dist', 'build',
|
||||
'.next', '.cache', '__pycache__', '.DS_Store', 'coverage',
|
||||
'.turbo', '.nuxt', '.output', 'vendor', 'target'
|
||||
]);
|
||||
const MAX_CONTEXT_SIZE = 40_000; // chars
|
||||
const SYSTEM_PROMPT = `You are "Connect AI LAB", a premium agentic AI coding assistant running 100% offline on the user's machine.
|
||||
|
||||
You have THREE powerful agent actions. Use them whenever appropriate:
|
||||
|
||||
━━━ ACTION 1: CREATE NEW FILES ━━━
|
||||
<create_file path="relative/path/file.ext">
|
||||
file content here
|
||||
</create_file>
|
||||
|
||||
━━━ ACTION 2: EDIT EXISTING FILES ━━━
|
||||
<edit_file path="relative/path/file.ext">
|
||||
<find>exact text to find in the file</find>
|
||||
<replace>replacement text</replace>
|
||||
</edit_file>
|
||||
You can have multiple <find>/<replace> pairs inside one <edit_file> block.
|
||||
|
||||
━━━ ACTION 3: RUN TERMINAL COMMANDS ━━━
|
||||
<run_command>npm install express</run_command>
|
||||
|
||||
RULES:
|
||||
1. ALWAYS respond in the same language the user uses.
|
||||
2. Use agent actions automatically when the user's request requires creating, editing files, or running commands.
|
||||
3. Outside of action blocks, briefly explain what you did.
|
||||
4. For code that is just for explanation (not to be saved), use standard markdown code fences.
|
||||
5. Be concise, professional, and helpful.
|
||||
6. When editing files, the <find> text must EXACTLY match existing content in the file.`;
|
||||
// ============================================================
|
||||
// Extension Activation
|
||||
// ============================================================
|
||||
function activate(context) {
|
||||
console.log('Connect AI LAB extension activated.');
|
||||
const provider = new SidebarChatProvider(context.extensionUri, context);
|
||||
context.subscriptions.push(vscode.window.registerWebviewViewProvider('local-ai-chat-view', provider, {
|
||||
webviewOptions: { retainContextWhenHidden: true }
|
||||
}));
|
||||
// New Chat
|
||||
context.subscriptions.push(vscode.commands.registerCommand('connect-ai-lab.newChat', () => {
|
||||
provider.resetChat();
|
||||
}));
|
||||
// Export Chat as Markdown
|
||||
context.subscriptions.push(vscode.commands.registerCommand('connect-ai-lab.exportChat', async () => {
|
||||
await provider.exportChat();
|
||||
}));
|
||||
// Focus Chat Input (Cmd+L)
|
||||
context.subscriptions.push(vscode.commands.registerCommand('connect-ai-lab.focusChat', () => {
|
||||
provider.focusInput();
|
||||
}));
|
||||
// Explain Selected Code (right-click menu)
|
||||
context.subscriptions.push(vscode.commands.registerCommand('connect-ai-lab.explainSelection', () => {
|
||||
const editor = vscode.window.activeTextEditor;
|
||||
if (!editor) {
|
||||
return;
|
||||
}
|
||||
const selection = editor.document.getText(editor.selection);
|
||||
if (selection.trim()) {
|
||||
provider.sendPromptFromExtension(`이 코드를 분석하고 설명해줘:\n\`\`\`\n${selection}\n\`\`\``);
|
||||
}
|
||||
}));
|
||||
}
|
||||
function deactivate() { }
|
||||
// ============================================================
|
||||
// Sidebar Chat Provider
|
||||
// ============================================================
|
||||
class SidebarChatProvider {
|
||||
_extensionUri;
|
||||
_view;
|
||||
_chatHistory = [];
|
||||
_terminal;
|
||||
_ctx;
|
||||
// 대화 표시용 (system prompt 제외, 유저에게 보여줄 것만 저장)
|
||||
_displayMessages = [];
|
||||
constructor(_extensionUri, ctx) {
|
||||
this._extensionUri = _extensionUri;
|
||||
this._ctx = ctx;
|
||||
this._restoreHistory();
|
||||
}
|
||||
/** 저장된 대화 기록 복원 */
|
||||
_restoreHistory() {
|
||||
const saved = this._ctx.workspaceState.get('chatState');
|
||||
if (saved && saved.chat && saved.chat.length > 1) {
|
||||
this._chatHistory = saved.chat;
|
||||
this._displayMessages = saved.display || [];
|
||||
}
|
||||
else {
|
||||
this._initHistory();
|
||||
}
|
||||
}
|
||||
/** 대화 기록 영구 저장 (워크스페이스 단위) */
|
||||
_saveHistory() {
|
||||
this._ctx.workspaceState.update('chatState', {
|
||||
chat: this._chatHistory,
|
||||
display: this._displayMessages
|
||||
});
|
||||
}
|
||||
_initHistory() {
|
||||
this._chatHistory = [{ role: 'system', content: SYSTEM_PROMPT }];
|
||||
this._displayMessages = [];
|
||||
}
|
||||
resetChat() {
|
||||
this._initHistory();
|
||||
this._saveHistory();
|
||||
if (this._view) {
|
||||
this._view.webview.postMessage({ type: 'clearChat' });
|
||||
}
|
||||
vscode.window.showInformationMessage('Connect AI LAB: 새 대화가 시작되었습니다.');
|
||||
}
|
||||
/** 대화를 Markdown 파일로 내보내기 */
|
||||
async exportChat() {
|
||||
if (this._displayMessages.length === 0) {
|
||||
vscode.window.showWarningMessage('내보낼 대화가 없습니다.');
|
||||
return;
|
||||
}
|
||||
let md = `# Connect AI LAB — 대화 기록\n\n_${new Date().toLocaleString('ko-KR')}_\n\n---\n\n`;
|
||||
for (const m of this._displayMessages) {
|
||||
const label = m.role === 'user' ? '**👤 You**' : '**✦ Connect AI LAB**';
|
||||
md += `### ${label}\n\n${m.text}\n\n---\n\n`;
|
||||
}
|
||||
const root = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath;
|
||||
if (root) {
|
||||
const filePath = path.join(root, `chat-export-${Date.now()}.md`);
|
||||
fs.writeFileSync(filePath, md, 'utf-8');
|
||||
const doc = await vscode.workspace.openTextDocument(filePath);
|
||||
await vscode.window.showTextDocument(doc);
|
||||
vscode.window.showInformationMessage(`대화가 ${path.basename(filePath)}로 저장되었습니다.`);
|
||||
}
|
||||
}
|
||||
/** 채팅 입력창에 포커스 (Cmd+L) */
|
||||
focusInput() {
|
||||
if (this._view) {
|
||||
this._view.show?.(true);
|
||||
this._view.webview.postMessage({ type: 'focusInput' });
|
||||
}
|
||||
}
|
||||
/** 외부에서 프롬프트 전송 (예: 코드 선택 → 설명) */
|
||||
sendPromptFromExtension(prompt) {
|
||||
if (this._view) {
|
||||
this._view.show?.(true);
|
||||
// 약간의 딜레이 후 전송 (뷰가 보이기를 기다림)
|
||||
setTimeout(() => {
|
||||
this._view?.webview.postMessage({ type: 'injectPrompt', value: prompt });
|
||||
}, 300);
|
||||
}
|
||||
}
|
||||
// --------------------------------------------------------
|
||||
// Webview Lifecycle
|
||||
// --------------------------------------------------------
|
||||
resolveWebviewView(webviewView, _context, _token) {
|
||||
this._view = webviewView;
|
||||
webviewView.webview.options = {
|
||||
enableScripts: true,
|
||||
localResourceRoots: [this._extensionUri],
|
||||
};
|
||||
webviewView.webview.html = this._getHtml();
|
||||
webviewView.webview.onDidReceiveMessage(async (msg) => {
|
||||
switch (msg.type) {
|
||||
case 'prompt':
|
||||
await this._handlePrompt(msg.value, msg.model);
|
||||
break;
|
||||
case 'getModels':
|
||||
await this._sendModels();
|
||||
break;
|
||||
case 'newChat':
|
||||
this.resetChat();
|
||||
break;
|
||||
case 'ready':
|
||||
// 웹뷰가 준비되면 저장된 대화 기록 복원
|
||||
this._restoreDisplayMessages();
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
// --------------------------------------------------------
|
||||
// Fetch installed Ollama models
|
||||
// --------------------------------------------------------
|
||||
async _sendModels() {
|
||||
if (!this._view) {
|
||||
return;
|
||||
}
|
||||
const { ollamaBase, defaultModel } = getConfig();
|
||||
try {
|
||||
const res = await axios_1.default.get(`${ollamaBase}/api/tags`);
|
||||
const models = res.data.models.map((m) => m.name);
|
||||
this._view.webview.postMessage({ type: 'modelsList', value: models });
|
||||
}
|
||||
catch {
|
||||
this._view.webview.postMessage({ type: 'modelsList', value: [defaultModel] });
|
||||
}
|
||||
}
|
||||
/** 저장된 대화 메시지를 웹뷰에 다시 전송 (복원) */
|
||||
_restoreDisplayMessages() {
|
||||
if (!this._view || this._displayMessages.length === 0) {
|
||||
return;
|
||||
}
|
||||
this._view.webview.postMessage({
|
||||
type: 'restoreMessages',
|
||||
value: this._displayMessages
|
||||
});
|
||||
}
|
||||
// --------------------------------------------------------
|
||||
// Build workspace file tree + read key files
|
||||
// --------------------------------------------------------
|
||||
_getWorkspaceContext() {
|
||||
const root = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath;
|
||||
if (!root) {
|
||||
return '';
|
||||
}
|
||||
// --- 1. File tree ---
|
||||
const lines = [];
|
||||
let count = 0;
|
||||
const walk = (dir, prefix) => {
|
||||
if (count >= getConfig().maxTreeFiles) {
|
||||
return;
|
||||
}
|
||||
let entries;
|
||||
try {
|
||||
entries = fs.readdirSync(dir, { withFileTypes: true });
|
||||
}
|
||||
catch {
|
||||
return;
|
||||
}
|
||||
entries.sort((a, b) => {
|
||||
if (a.isDirectory() && !b.isDirectory()) {
|
||||
return -1;
|
||||
}
|
||||
if (!a.isDirectory() && b.isDirectory()) {
|
||||
return 1;
|
||||
}
|
||||
return a.name.localeCompare(b.name);
|
||||
});
|
||||
for (const entry of entries) {
|
||||
if (count >= getConfig().maxTreeFiles) {
|
||||
break;
|
||||
}
|
||||
if (EXCLUDED_DIRS.has(entry.name)) {
|
||||
continue;
|
||||
}
|
||||
if (entry.name.startsWith('.') && entry.isDirectory()) {
|
||||
continue;
|
||||
}
|
||||
if (entry.isDirectory()) {
|
||||
lines.push(`${prefix}📁 ${entry.name}/`);
|
||||
count++;
|
||||
walk(path.join(dir, entry.name), prefix + ' ');
|
||||
}
|
||||
else {
|
||||
lines.push(`${prefix}📄 ${entry.name}`);
|
||||
count++;
|
||||
}
|
||||
}
|
||||
};
|
||||
walk(root, '');
|
||||
let result = '';
|
||||
if (lines.length > 0) {
|
||||
result += `\n\n[프로젝트 파일 구조]\n${lines.join('\n')}`;
|
||||
}
|
||||
// --- 2. Auto-read key project files ---
|
||||
const keyFiles = [
|
||||
'package.json', 'tsconfig.json', 'vite.config.ts', 'vite.config.js',
|
||||
'next.config.js', 'next.config.ts', 'README.md',
|
||||
'index.html', 'app.js', 'app.ts', 'main.ts', 'main.js',
|
||||
'src/index.ts', 'src/index.js', 'src/App.tsx', 'src/App.jsx',
|
||||
'src/main.ts', 'src/main.js'
|
||||
];
|
||||
let totalRead = 0;
|
||||
const MAX_AUTO_READ = 15_000; // chars total
|
||||
for (const kf of keyFiles) {
|
||||
if (totalRead >= MAX_AUTO_READ) {
|
||||
break;
|
||||
}
|
||||
const abs = path.join(root, kf);
|
||||
if (fs.existsSync(abs)) {
|
||||
try {
|
||||
const content = fs.readFileSync(abs, 'utf-8');
|
||||
if (content.length < 5000) {
|
||||
result += `\n\n[파일 내용: ${kf}]\n\`\`\`\n${content}\n\`\`\``;
|
||||
totalRead += content.length;
|
||||
}
|
||||
}
|
||||
catch { /* skip */ }
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
// --------------------------------------------------------
|
||||
// Handle user prompt → Ollama → agent actions → response
|
||||
// --------------------------------------------------------
|
||||
async _handlePrompt(prompt, modelName) {
|
||||
if (!this._view) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
// 1. Context: active editor content
|
||||
const editor = vscode.window.activeTextEditor;
|
||||
let contextBlock = '';
|
||||
if (editor && editor.document.uri.scheme === 'file') {
|
||||
const text = editor.document.getText();
|
||||
const name = path.basename(editor.document.fileName);
|
||||
if (text.trim().length > 0 && text.length < MAX_CONTEXT_SIZE) {
|
||||
contextBlock = `\n\n[Currently open file: ${name}]\n\`\`\`\n${text}\n\`\`\``;
|
||||
}
|
||||
}
|
||||
// 2. Context: workspace file tree + key file contents
|
||||
const workspaceCtx = this._getWorkspaceContext();
|
||||
// 3. Push user message
|
||||
this._chatHistory.push({
|
||||
role: 'user',
|
||||
content: prompt + contextBlock + workspaceCtx
|
||||
});
|
||||
// 저장용: 유저 메시지 기록 (프롬프트만, 컨텍스트 제외)
|
||||
this._displayMessages.push({ text: prompt, role: 'user' });
|
||||
// 4. Call Ollama
|
||||
const { ollamaBase, defaultModel, timeout } = getConfig();
|
||||
const response = await axios_1.default.post(`${ollamaBase}/api/chat`, {
|
||||
model: modelName || defaultModel,
|
||||
messages: this._chatHistory,
|
||||
stream: false,
|
||||
}, { timeout });
|
||||
const aiMessage = response.data.message.content;
|
||||
this._chatHistory.push({ role: 'assistant', content: aiMessage });
|
||||
// 5. Execute agent actions
|
||||
const report = this._executeActions(aiMessage);
|
||||
// 6. Send to webview
|
||||
let output = aiMessage;
|
||||
if (report.length > 0) {
|
||||
output += `\n\n---\n📦 **에이전트 작업 결과**\n${report.join('\n')}`;
|
||||
}
|
||||
this._view.webview.postMessage({ type: 'response', value: output });
|
||||
// 저장용: AI 응답 기록
|
||||
this._displayMessages.push({ text: output, role: 'ai' });
|
||||
this._saveHistory();
|
||||
}
|
||||
catch (error) {
|
||||
const errMsg = error.code === 'ECONNREFUSED'
|
||||
? '⚠️ Ollama 서버에 연결할 수 없습니다.\n터미널에서 `ollama serve`를 실행해주세요.'
|
||||
: `⚠️ 오류: ${error.message}`;
|
||||
this._view.webview.postMessage({ type: 'error', value: errMsg });
|
||||
}
|
||||
}
|
||||
// --------------------------------------------------------
|
||||
// Execute ALL agent actions from AI response
|
||||
// --------------------------------------------------------
|
||||
_executeActions(aiMessage) {
|
||||
const report = [];
|
||||
const rootPath = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath;
|
||||
if (!rootPath) {
|
||||
const hasActions = /<create_file|<edit_file|<run_command/.test(aiMessage);
|
||||
if (hasActions) {
|
||||
report.push('❌ 폴더가 열려있지 않습니다. File → Open Folder로 폴더를 먼저 열어주세요.');
|
||||
}
|
||||
return report;
|
||||
}
|
||||
// ACTION 1: Create files
|
||||
const createRegex = /<create_file\s+path="([^"]+)">([\s\S]*?)<\/create_file>/g;
|
||||
let match;
|
||||
let firstCreatedFile = '';
|
||||
while ((match = createRegex.exec(aiMessage)) !== null) {
|
||||
const relPath = match[1].trim();
|
||||
const content = match[2].replace(/^\n/, ''); // remove leading newline only
|
||||
try {
|
||||
const absPath = path.join(rootPath, relPath);
|
||||
const dir = path.dirname(absPath);
|
||||
if (!fs.existsSync(dir)) {
|
||||
fs.mkdirSync(dir, { recursive: true });
|
||||
}
|
||||
fs.writeFileSync(absPath, content, 'utf-8');
|
||||
report.push(`✅ 생성: ${relPath}`);
|
||||
if (!firstCreatedFile) {
|
||||
firstCreatedFile = absPath;
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
report.push(`❌ 생성 실패: ${relPath} — ${err.message}`);
|
||||
}
|
||||
}
|
||||
// Open first created file
|
||||
if (firstCreatedFile) {
|
||||
vscode.window.showTextDocument(vscode.Uri.file(firstCreatedFile), { preview: false });
|
||||
}
|
||||
// ACTION 2: Edit files
|
||||
const editRegex = /<edit_file\s+path="([^"]+)">([\s\S]*?)<\/edit_file>/g;
|
||||
while ((match = editRegex.exec(aiMessage)) !== null) {
|
||||
const relPath = match[1].trim();
|
||||
const body = match[2];
|
||||
const absPath = path.join(rootPath, relPath);
|
||||
if (!fs.existsSync(absPath)) {
|
||||
report.push(`❌ 편집 실패: ${relPath} — 파일이 존재하지 않습니다.`);
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
let fileContent = fs.readFileSync(absPath, 'utf-8');
|
||||
const findReplaceRegex = /<find>([\s\S]*?)<\/find>\s*<replace>([\s\S]*?)<\/replace>/g;
|
||||
let frMatch;
|
||||
let editCount = 0;
|
||||
while ((frMatch = findReplaceRegex.exec(body)) !== null) {
|
||||
const findText = frMatch[1];
|
||||
const replaceText = frMatch[2];
|
||||
if (fileContent.includes(findText)) {
|
||||
fileContent = fileContent.replace(findText, replaceText);
|
||||
editCount++;
|
||||
}
|
||||
else {
|
||||
report.push(`⚠️ ${relPath}: 일치하는 텍스트를 찾지 못했습니다.`);
|
||||
}
|
||||
}
|
||||
if (editCount > 0) {
|
||||
fs.writeFileSync(absPath, fileContent, 'utf-8');
|
||||
report.push(`✏️ 편집 완료: ${relPath} (${editCount}건 수정)`);
|
||||
// Open edited file
|
||||
vscode.window.showTextDocument(vscode.Uri.file(absPath), { preview: false });
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
report.push(`❌ 편집 실패: ${relPath} — ${err.message}`);
|
||||
}
|
||||
}
|
||||
// ACTION 3: Run commands
|
||||
const cmdRegex = /<run_command>([\s\S]*?)<\/run_command>/g;
|
||||
while ((match = cmdRegex.exec(aiMessage)) !== null) {
|
||||
const cmd = match[1].trim();
|
||||
try {
|
||||
if (!this._terminal || this._terminal.exitStatus !== undefined) {
|
||||
this._terminal = vscode.window.createTerminal({
|
||||
name: '🚀 Connect AI LAB',
|
||||
cwd: rootPath
|
||||
});
|
||||
}
|
||||
this._terminal.show();
|
||||
this._terminal.sendText(cmd);
|
||||
report.push(`🖥️ 실행: ${cmd}`);
|
||||
}
|
||||
catch (err) {
|
||||
report.push(`❌ 명령 실패: ${cmd} — ${err.message}`);
|
||||
}
|
||||
}
|
||||
// Show notification
|
||||
const successCount = report.filter(r => r.startsWith('✅') || r.startsWith('✏️') || r.startsWith('🖥️')).length;
|
||||
if (successCount > 0) {
|
||||
vscode.window.showInformationMessage(`Connect AI LAB: ${successCount}개 에이전트 작업 완료!`);
|
||||
}
|
||||
return report;
|
||||
}
|
||||
// ============================================================
|
||||
// Webview HTML — Premium UI v2
|
||||
// ============================================================
|
||||
// ============================================================
|
||||
// Webview HTML — Premium UI v2 (Zero External Dependencies)
|
||||
// ============================================================
|
||||
_getHtml() {
|
||||
return `<!DOCTYPE html>
|
||||
<html lang="ko"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<title>Connect AI LAB</title>
|
||||
<style>
|
||||
*{margin:0;padding:0;box-sizing:border-box}
|
||||
:root{
|
||||
--bg:#111113;--bg2:#18181b;--surface:#1e1e22;--surface2:#27272b;
|
||||
--border:rgba(255,255,255,.08);--border2:rgba(255,255,255,.12);
|
||||
--text:#a1a1aa;--text-bright:#fafafa;--text-dim:#52525b;
|
||||
--accent:#818cf8;--accent2:#c084fc;--accent-glow:rgba(129,140,248,.15);
|
||||
--input-bg:#1a1a1e;--code-bg:#0c0c0e;
|
||||
--green:#34d399;--yellow:#fbbf24;--cyan:#22d3ee;--red:#fb7185;
|
||||
}
|
||||
html,body{height:100%;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',system-ui,sans-serif;font-size:13px;background:var(--bg);color:var(--text);display:flex;flex-direction:column;overflow:hidden}
|
||||
.header{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;background:rgba(17,17,19,.85);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);border-bottom:1px solid var(--border);flex-shrink:0;position:relative;z-index:10}
|
||||
.header::after{content:'';position:absolute;bottom:-1px;left:0;right:0;height:1px;background:linear-gradient(90deg,transparent,var(--accent),var(--accent2),transparent);opacity:.3}
|
||||
.header-left{display:flex;align-items:center;gap:10px}
|
||||
.logo{width:24px;height:24px;border-radius:6px;background:linear-gradient(135deg,var(--accent),var(--accent2));display:flex;align-items:center;justify-content:center;font-size:14px;color:#fff;box-shadow:0 0 12px rgba(129,140,248,.3)}
|
||||
.brand{font-weight:700;font-size:13px;color:var(--text-bright);letter-spacing:-.3px}
|
||||
.header-right{display:flex;align-items:center;gap:6px}
|
||||
select{background:var(--surface);color:var(--text-bright);border:1px solid var(--border2);padding:5px 10px;border-radius:6px;font-size:11px;font-family:inherit;cursor:pointer;outline:none;max-width:140px;transition:border-color .2s}
|
||||
select:hover,select:focus{border-color:var(--accent)}
|
||||
.btn-icon{background:var(--surface);border:1px solid var(--border2);color:var(--text-dim);width:28px;height:28px;border-radius:6px;font-size:14px;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all .2s}
|
||||
.btn-icon:hover{background:var(--surface2);color:var(--text-bright);border-color:var(--accent);box-shadow:0 0 8px var(--accent-glow)}
|
||||
.chat{flex:1;overflow-y:auto;padding:20px 16px;display:flex;flex-direction:column;gap:20px}
|
||||
.chat::-webkit-scrollbar{width:3px}.chat::-webkit-scrollbar-track{background:transparent}.chat::-webkit-scrollbar-thumb{background:var(--border2);border-radius:3px}
|
||||
.msg{display:flex;flex-direction:column;gap:6px;animation:msgIn .3s ease-out}
|
||||
.msg-head{display:flex;align-items:center;gap:8px;font-weight:600;font-size:11.5px;color:var(--text)}
|
||||
.msg-time{font-weight:400;font-size:10px;color:var(--text-dim);margin-left:auto}
|
||||
.av{width:22px;height:22px;border-radius:6px;display:flex;align-items:center;justify-content:center;font-size:12px;flex-shrink:0}
|
||||
.av-user{background:var(--surface2);color:var(--text)}.av-ai{background:linear-gradient(135deg,var(--accent),var(--accent2));color:#fff;box-shadow:0 0 8px rgba(129,140,248,.2)}
|
||||
.msg-body{padding-left:30px;line-height:1.7;color:var(--text);white-space:pre-wrap;word-break:break-word;font-size:13px}
|
||||
.msg-user .msg-body{background:var(--surface);border:1px solid var(--border2);border-radius:12px;padding:10px 14px;margin-left:30px;color:var(--text-bright)}
|
||||
.msg-body pre{background:var(--code-bg);border:1px solid var(--border2);border-radius:8px;padding:14px 16px;overflow-x:auto;margin:8px 0;font-size:12px;line-height:1.55;color:#adbac7}
|
||||
.msg-body code{font-family:'SF Mono','Fira Code','Cascadia Code','Menlo',monospace;font-size:12px}
|
||||
.msg-body :not(pre)>code{background:rgba(129,140,248,.1);color:var(--accent);padding:1px 6px;border-radius:4px;border:1px solid rgba(129,140,248,.15)}
|
||||
.code-wrap{position:relative}
|
||||
.code-lang{position:absolute;top:0;left:14px;background:var(--surface2);color:var(--text-dim);padding:1px 8px;border-radius:0 0 4px 4px;font-size:9px;font-family:'SF Mono',monospace;text-transform:uppercase;letter-spacing:.5px}
|
||||
.copy-btn{position:absolute;top:6px;right:8px;background:var(--surface2);border:1px solid var(--border2);color:var(--text-dim);padding:3px 10px;border-radius:5px;font-size:10px;cursor:pointer;opacity:0;transition:all .2s;font-family:inherit;z-index:1}
|
||||
.code-wrap:hover .copy-btn{opacity:1}.copy-btn:hover{background:var(--accent);color:#fff;border-color:var(--accent)}
|
||||
.copy-btn.copied{background:var(--green);color:#fff;border-color:var(--green);opacity:1}
|
||||
.file-badge{background:rgba(251,191,36,.06);border:1px solid rgba(251,191,36,.2);border-radius:8px 8px 0 0;border-bottom:none;padding:8px 14px;font-size:11px;font-weight:600;color:var(--yellow);display:flex;align-items:center;gap:6px}
|
||||
.edit-badge{background:rgba(34,211,238,.06);border:1px solid rgba(34,211,238,.2);border-radius:8px 8px 0 0;border-bottom:none;padding:8px 14px;font-size:11px;font-weight:600;color:var(--cyan);display:flex;align-items:center;gap:6px}
|
||||
.cmd-badge{background:rgba(129,140,248,.06);border:1px solid rgba(129,140,248,.2);border-radius:8px;padding:10px 14px;margin:8px 0;font-size:12px;color:var(--accent);font-family:'SF Mono','Menlo',monospace;display:flex;align-items:center;gap:8px}
|
||||
.agent-report{background:rgba(52,211,153,.06);border:1px solid rgba(52,211,153,.2);border-radius:8px;padding:12px 14px;margin-top:8px;font-size:12px;line-height:1.7}
|
||||
.msg-error .msg-body{color:var(--red)}
|
||||
.welcome{text-align:center;padding:30px 20px 10px}
|
||||
.welcome-logo{width:48px;height:48px;border-radius:14px;margin:0 auto 14px;background:linear-gradient(135deg,var(--accent),var(--accent2));display:flex;align-items:center;justify-content:center;font-size:26px;color:#fff;box-shadow:0 0 30px rgba(129,140,248,.25)}
|
||||
.welcome-title{font-size:18px;font-weight:800;letter-spacing:-.5px;background:linear-gradient(135deg,var(--accent),var(--accent2));-webkit-background-clip:text;-webkit-text-fill-color:transparent;margin-bottom:8px}
|
||||
.welcome-sub{color:var(--text-dim);font-size:12px;line-height:1.6;margin-bottom:16px}
|
||||
.welcome-features{display:flex;justify-content:center;gap:16px;flex-wrap:wrap;margin-bottom:18px}
|
||||
.wf{display:flex;align-items:center;gap:4px;font-size:11px;color:var(--text)}.wf-icon{font-size:14px}
|
||||
.quick-actions{display:flex;flex-wrap:wrap;gap:6px;justify-content:center}
|
||||
.qa-btn{background:var(--surface);border:1px solid var(--border2);color:var(--text);padding:8px 14px;border-radius:8px;font-size:11px;cursor:pointer;transition:all .2s;font-family:inherit}
|
||||
.qa-btn:hover{border-color:var(--accent);color:var(--text-bright);background:var(--surface2);box-shadow:0 0 12px var(--accent-glow)}
|
||||
.loading-wrap{padding-left:30px;padding-top:6px;display:flex;align-items:center;gap:8px}
|
||||
.loading-bar{width:120px;height:3px;background:var(--surface2);border-radius:3px;overflow:hidden;position:relative}
|
||||
.loading-bar::after{content:'';position:absolute;top:0;left:-40px;width:40px;height:100%;background:linear-gradient(90deg,transparent,var(--accent),var(--accent2),transparent);animation:shimmer 1.2s ease-in-out infinite}
|
||||
.loading-text{font-size:11px;color:var(--text-dim);animation:pulse 2s ease-in-out infinite}
|
||||
.input-wrap{padding:10px 16px 16px;flex-shrink:0;position:relative}
|
||||
.input-box{background:var(--input-bg);border:1px solid var(--border2);border-radius:12px;padding:12px 14px;display:flex;flex-direction:column;gap:8px;transition:all .2s;position:relative}
|
||||
.input-box::before{content:'';position:absolute;inset:-1px;border-radius:13px;background:linear-gradient(135deg,var(--accent),var(--accent2));opacity:0;transition:opacity .3s;z-index:-1}
|
||||
.input-box:focus-within{border-color:transparent}.input-box:focus-within::before{opacity:.4}
|
||||
textarea{width:100%;background:transparent;border:none;color:var(--text-bright);font-family:inherit;font-size:13px;line-height:1.5;resize:none;outline:none;min-height:22px;max-height:150px}
|
||||
textarea::placeholder{color:var(--text-dim)}
|
||||
.input-footer{display:flex;align-items:center;justify-content:space-between}
|
||||
.input-hint{font-size:10px;color:var(--text-dim)}
|
||||
.input-btns{display:flex;gap:5px}
|
||||
.send-btn{background:linear-gradient(135deg,var(--accent),var(--accent2));border:none;color:#fff;width:30px;height:30px;border-radius:8px;cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:14px;transition:all .15s;box-shadow:0 2px 8px rgba(129,140,248,.25)}
|
||||
.send-btn:hover{transform:translateY(-1px);box-shadow:0 4px 16px rgba(129,140,248,.35)}.send-btn:active{transform:scale(.94)}.send-btn:disabled{opacity:.25;cursor:not-allowed;transform:none;box-shadow:none}
|
||||
.stop-btn{background:var(--red);border:none;color:#fff;width:30px;height:30px;border-radius:8px;cursor:pointer;display:none;align-items:center;justify-content:center;font-size:11px}
|
||||
.stop-btn.visible{display:flex}
|
||||
@keyframes msgIn{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:translateY(0)}}
|
||||
@keyframes shimmer{0%{left:-40px}100%{left:120px}}
|
||||
@keyframes pulse{0%,100%{opacity:.5}50%{opacity:1}}
|
||||
</style></head><body>
|
||||
<div class="header"><div class="header-left"><div class="logo">\u2726</div><span class="brand">Connect AI LAB</span></div><div class="header-right"><select id="modelSel"></select><button class="btn-icon" id="newChatBtn" title="New Chat">+</button></div></div>
|
||||
<div class="chat" id="chat">
|
||||
<div class="welcome">
|
||||
<div class="welcome-logo">\u2726</div>
|
||||
<div class="welcome-title">Connect AI LAB</div>
|
||||
<div class="welcome-sub">100% \ub85c\uceec \u00b7 100% \uc624\ud504\ub77c\uc778 \u00b7 100% \ubb34\ub8cc<br>\ud504\ub85c\uc81d\ud2b8\ub97c \uc774\ud574\ud558\uace0, \ucf54\ub4dc\ub97c \uc791\uc131\ud558\uace0, \uc2e4\ud589\ud569\ub2c8\ub2e4.</div>
|
||||
<div class="welcome-features"><div class="wf"><span class="wf-icon">\ud83d\udcc1</span> \ud30c\uc77c \uc0dd\uc131</div><div class="wf"><span class="wf-icon">\u270f\ufe0f</span> \ucf54\ub4dc \ud3b8\uc9d1</div><div class="wf"><span class="wf-icon">\ud83d\udda5\ufe0f</span> \ud130\ubbf8\ub110</div><div class="wf"><span class="wf-icon">\ud83d\udd0d</span> \ubd84\uc11d</div></div>
|
||||
<div class="quick-actions">
|
||||
<button class="qa-btn" data-prompt="\uac04\ub2e8\ud55c \ud3ec\ud2b8\ud3f4\ub9ac\uc624 \uc6f9\uc0ac\uc774\ud2b8\ub97c \ub9cc\ub4e4\uc5b4\uc918">\ud83c\udf10 \uc6f9\uc0ac\uc774\ud2b8 \uc0dd\uc131</button>
|
||||
<button class="qa-btn" data-prompt="Express API \uc11c\ubc84\ub97c \ub9cc\ub4e4\uc5b4\uc918">\u26a1 API \uc11c\ubc84</button>
|
||||
<button class="qa-btn" data-prompt="\uc774 \ud504\ub85c\uc81d\ud2b8\uc758 \uad6c\uc870\ub97c \ubd84\uc11d\ud574\uc918">\ud83d\udd0d \ud504\ub85c\uc81d\ud2b8 \ubd84\uc11d</button>
|
||||
<button class="qa-btn" data-prompt="README.md\ub97c \uc791\uc131\ud574\uc918">\ud83d\udcdd README</button>
|
||||
</div></div></div>
|
||||
<div class="input-wrap"><div class="input-box">
|
||||
<textarea id="input" rows="1" placeholder="\ubb34\uc5c7\uc744 \ub9cc\ub4e4\uc5b4 \ub4dc\ub9b4\uae4c\uc694?"></textarea>
|
||||
<div class="input-footer"><span class="input-hint">Enter \uc804\uc1a1 \u00b7 Shift+Enter \uc904\ubc14\uafc8</span>
|
||||
<div class="input-btns"><button class="stop-btn" id="stopBtn">\u25a0</button><button class="send-btn" id="sendBtn">\u2191</button></div></div></div></div>
|
||||
<script>
|
||||
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');
|
||||
let loader=null,sending=false;
|
||||
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){
|
||||
t=t.replace(new RegExp('<create_file\\s+path="([^"]+)">([\\s\\S]*?)<\\/create_file>', 'g'),(_,p,c)=>'<div class="file-badge">\uD83D\uDCC1 '+esc(p)+' \u2014 \uC790\uB3D9 \uC0DD\uC131\uB428</div><div class="code-wrap"><pre><code>'+esc(c)+'</code></pre><button class="copy-btn" onclick="copyCode(this)">Copy</button></div>');
|
||||
t=t.replace(new RegExp('<edit_file\\s+path="([^"]+)">([\\s\\S]*?)<\\/edit_file>', 'g'),(_,p,c)=>'<div class="edit-badge">\u270F\uFE0F '+esc(p)+' \u2014 \uD3B8\uC9D1\uB428</div><div class="code-wrap"><pre><code>'+esc(c)+'</code></pre><button class="copy-btn" onclick="copyCode(this)">Copy</button></div>');
|
||||
t=t.replace(new RegExp('<run_command>([\\s\\S]*?)<\\/run_command>', 'g'),(_,c)=>'<div class="cmd-badge">\u25B6 '+esc(c)+'</div>');
|
||||
t=t.replace(new RegExp('\\x60\\x60\\x60(\\w*)\\n([\\s\\S]*?)\\x60\\x60\\x60', 'g'),(_,lang,c)=>{const l=lang||'code';return '<div class="code-wrap"><span class="code-lang">'+l+'</span><pre><code>'+esc(c)+'</code></pre><button class="copy-btn" onclick="copyCode(this)">Copy</button></div>'});
|
||||
t=t.replace(new RegExp('\\x60([^\\x60]+)\\x60', 'g'),(_,c)=>'<code>'+esc(c)+'</code>');
|
||||
t=t.replace(new RegExp('\\*\\*([^*]+)\\*\\*', 'g'),'<strong>$1</strong>');
|
||||
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?'<div class="av av-user">\ud83d\udc64</div><span>You</span>':'<div class="av av-ai">\u2726</div><span>Connect AI LAB</span>')+'<span class="msg-time">'+getTime()+'</span>';
|
||||
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='<div class="msg-head"><div class="av av-ai">\u2726</div><span>Connect AI LAB</span><span class="msg-time">'+getTime()+'</span></div><div class="loading-wrap"><div class="loading-bar"></div><span class="loading-text">\uc0dd\uac01\ud558\ub294 \uc911...</span></div>';chat.appendChild(loader);chat.scrollTop=chat.scrollHeight}
|
||||
function hideLoader(){if(loader&&loader.parentNode)loader.parentNode.removeChild(loader);loader=null}
|
||||
function setSending(v){sending=v;sendBtn.disabled=v;stopBtn.classList.toggle('visible',v);input.disabled=v;if(!v)input.focus()}
|
||||
function send(){const text=input.value.trim();if(!text||sending)return;const w=document.querySelector('.welcome');if(w)w.remove();document.querySelectorAll('.quick-actions').forEach(e=>e.remove());addMsg(text,'user');input.value='';input.style.height='auto';setSending(true);showLoader();vscode.postMessage({type:'prompt',value:text,model:modelSel.value})}
|
||||
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'}));
|
||||
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 'modelsList':modelSel.innerHTML='';msg.value.forEach(m=>{const o=document.createElement('option');o.value=m;o.textContent=m;modelSel.appendChild(o)});break;
|
||||
case 'clearChat':chat.innerHTML='';addMsg('\uc0c8 \ub300\ud654\uac00 \uc2dc\uc791\ub418\uc5c8\uc2b5\ub2c8\ub2e4.','ai');break;
|
||||
case 'restoreMessages':chat.innerHTML='';if(msg.value&&msg.value.length>0){msg.value.forEach(m=>addMsg(m.text,m.role))}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;
|
||||
} });
|
||||
</script></body></html>`;
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=extension.js.map
|
||||
@@ -1,14 +0,0 @@
|
||||
const fs = require('fs');
|
||||
let code = fs.readFileSync('src/extension.ts', 'utf8');
|
||||
let lines = code.split('\n');
|
||||
|
||||
const correctCode = ` t=t.replace(/<create_file\\\\s+path="([^"]+)">([\\\\s\\\\S]*?)<\\\\/create_file>/g,(_,p,c)=>'<div class="file-badge">\\ud83d\\udcc1 '+esc(p)+' \\u2014 \\uc790\\ub3d9 \\uc0dd\\uc131\\ub428</div><div class="code-wrap"><pre><code>'+esc(c)+'</code></pre><button class="copy-btn" onclick="copyCode(this)">Copy</button></div>');
|
||||
t=t.replace(/<edit_file\\\\s+path="([^"]+)">([\\\\s\\\\S]*?)<\\\\/edit_file>/g,(_,p,c)=>'<div class="edit-badge">\\u270f\\ufe0f '+esc(p)+' \\u2014 \\ud3b8\\uc9d1\\ub428</div><div class="code-wrap"><pre><code>'+esc(c)+'</code></pre><button class="copy-btn" onclick="copyCode(this)">Copy</button></div>');
|
||||
t=t.replace(/<run_command>([\\\\s\\\\S]*?)<\\\\/run_command>/g,(_,c)=>'<div class="cmd-badge">\\u25b6 '+esc(c)+'</div>');
|
||||
t=t.replace(/\\\`\\\`\\\`(\\\\w*)\\\\n([\\\\s\\\\S]*?)\\\`\\\`\\\`/g,(_,lang,c)=>{const l=lang||'code';return '<div class="code-wrap"><span class="code-lang">'+l+'</span><pre><code>'+esc(c)+'</code></pre><button class="copy-btn" onclick="copyCode(this)">Copy</button></div>'});
|
||||
t=t.replace(/\\\`([^\\\`]+)\\\`/g,(_,c)=>'<code>'+esc(c)+'</code>');
|
||||
t=t.replace(/\\\\*\\\\*([^*]+)\\\\*\\\\*/g,'<strong>$1</strong>');`;
|
||||
|
||||
lines.splice(614, 7, correctCode);
|
||||
fs.writeFileSync('src/extension.ts', lines.join('\n'));
|
||||
console.log('Fixed regex lines!');
|
||||
@@ -1,26 +0,0 @@
|
||||
import sys
|
||||
|
||||
with open('src/extension.ts', 'r') as f:
|
||||
content = f.read()
|
||||
|
||||
start_idx = content.find('function fmt(t){')
|
||||
end_idx = content.find('function copyCode(btn)')
|
||||
|
||||
if start_idx != -1 and end_idx != -1:
|
||||
new_fmt = """function fmt(t){
|
||||
// Use simple string templates without regex literals
|
||||
t=t.replace(new RegExp('<create_file\\\\s+path="([^"]+)">([\\\\s\\\\S]*?)<\\\\/create_file>', 'g'),(_,p,c)=>'<div class="file-badge">\\ud83d\\udcc1 '+esc(p)+' \\u2014 \\uc790\\ub3d9 \\uc0dd\\uc131\\ub428</div><div class="code-wrap"><pre><code>'+esc(c)+'</code></pre><button class="copy-btn" onclick="copyCode(this)">Copy</button></div>');
|
||||
t=t.replace(new RegExp('<edit_file\\\\s+path="([^"]+)">([\\\\s\\\\S]*?)<\\\\/edit_file>', 'g'),(_,p,c)=>'<div class="edit-badge">\\u270f\\ufe0f '+esc(p)+' \\u2014 \\ud3b8\\uc9d1\\ub428</div><div class="code-wrap"><pre><code>'+esc(c)+'</code></pre><button class="copy-btn" onclick="copyCode(this)">Copy</button></div>');
|
||||
t=t.replace(new RegExp('<run_command>([\\\\s\\\\S]*?)<\\\\/run_command>', 'g'),(_,c)=>'<div class="cmd-badge">\\u25b6 '+esc(c)+'</div>');
|
||||
t=t.replace(new RegExp('\\\\`\\\\`\\\\`(\\\\w*)\\\\n([\\\\s\\\\S]*?)\\\\`\\\\`\\\\`', 'g'),(_,lang,c)=>{const l=lang||'code';return '<div class="code-wrap"><span class="code-lang">'+l+'</span><pre><code>'+esc(c)+'</code></pre><button class="copy-btn" onclick="copyCode(this)">Copy</button></div>'});
|
||||
t=t.replace(new RegExp('\\\\`([^\\\\`]+)\\\\`', 'g'),(_,c)=>'<code>'+esc(c)+'</code>');
|
||||
t=t.replace(new RegExp('\\\\*\\\\*([^*]+)\\\\*\\\\*', 'g'),'<strong>$1</strong>');
|
||||
return t;
|
||||
}
|
||||
"""
|
||||
new_content = content[:start_idx] + new_fmt + content[end_idx:]
|
||||
with open('src/extension.ts', 'w') as f:
|
||||
f.write(new_content)
|
||||
print("Successfully replaced fmt function.")
|
||||
else:
|
||||
print("Could not find start or end index.")
|
||||
@@ -1,28 +0,0 @@
|
||||
const fs = require('fs');
|
||||
let code = fs.readFileSync('src/extension.ts', 'utf8');
|
||||
let lines = code.split('\n');
|
||||
|
||||
const correctCode = `function fmt(t){
|
||||
// Use new RegExp with string literals to avoid ALL JS parser escaping hell with regex literals containing forward slashes
|
||||
const createRe = new RegExp('<create_file\\\\\\\\s+path="([^"]+)">([\\\\\\\\s\\\\\\\\S]*?)<\\\\\\\\/create_file>', 'g');
|
||||
const editRe = new RegExp('<edit_file\\\\\\\\s+path="([^"]+)">([\\\\\\\\s\\\\\\\\S]*?)<\\\\\\\\/edit_file>', 'g');
|
||||
const runRe = new RegExp('<run_command>([\\\\\\\\s\\\\\\\\S]*?)<\\\\\\\\/run_command>', 'g');
|
||||
const mdCodeRe = new RegExp('\\\\`\\\\`\\\\`(\\\\\\\\w*)\\\\\\\\n([\\\\\\\\s\\\\\\\\S]*?)\\\\`\\\\`\\\\`', 'g');
|
||||
const inlineCodeRe = new RegExp('\\\\`([^\\\\`]+)\\\\`', 'g');
|
||||
const boldRe = new RegExp('\\\\\\\\*\\\\\\\\*([^*]+)\\\\\\\\*\\\\\\\\*', 'g');
|
||||
|
||||
t=t.replace(createRe,(_,p,c)=>'<div class="file-badge">\\ud83d\\udcc1 '+esc(p)+' \\u2014 \\uc790\\ub3d9 \\uc0dd\\uc131\\ub428</div><div class="code-wrap"><pre><code>'+esc(c)+'</code></pre><button class="copy-btn" onclick="copyCode(this)">Copy</button></div>');
|
||||
t=t.replace(editRe,(_,p,c)=>'<div class="edit-badge">\\u270f\\ufe0f '+esc(p)+' \\u2014 \\ud3b8\\uc9d1\\ub428</div><div class="code-wrap"><pre><code>'+esc(c)+'</code></pre><button class="copy-btn" onclick="copyCode(this)">Copy</button></div>');
|
||||
t=t.replace(runRe,(_,c)=>'<div class="cmd-badge">\\u25b6 '+esc(c)+'</div>');
|
||||
t=t.replace(mdCodeRe,(_,lang,c)=>{const l=lang||'code';return '<div class="code-wrap"><span class="code-lang">'+l+'</span><pre><code>'+esc(c)+'</code></pre><button class="copy-btn" onclick="copyCode(this)">Copy</button></div>'});
|
||||
t=t.replace(inlineCodeRe,(_,c)=>'<code>'+esc(c)+'</code>');
|
||||
t=t.replace(boldRe,'<strong>$1</strong>');
|
||||
return t;
|
||||
}`;
|
||||
|
||||
// Find where function fmt(t){ starts and replace it
|
||||
let start = lines.findIndex(l => l.includes('function fmt(t){'));
|
||||
let end = lines.findIndex((l, i) => i > start && l.includes('return t;')) + 1;
|
||||
lines.splice(start, end - start + 1, correctCode);
|
||||
fs.writeFileSync('src/extension.ts', lines.join('\n'));
|
||||
console.log('Fixed regex lines with RegExp approach!');
|
||||
@@ -1,28 +0,0 @@
|
||||
import sys
|
||||
|
||||
with open('src/extension.ts', 'r') as f:
|
||||
content = f.read()
|
||||
|
||||
start_idx = content.find('function fmt(t){')
|
||||
end_idx = content.find('function copyCode(btn)')
|
||||
|
||||
if start_idx != -1 and end_idx != -1:
|
||||
new_fmt = r"""function fmt(t){
|
||||
t=t.replace(/<create_file\s+path="([^"]+)">([\s\S]*?)<\/create_file>/g,(_,p,c)=>'<div class="file-badge">\ud83d\udcc1 '+esc(p)+' \u2014 \uc790\ub3d9 \uc0dd\uc131\ub428</div><div class="code-wrap"><pre><code>'+esc(c)+'</code></pre><button class="copy-btn" onclick="copyCode(this)">Copy</button></div>');
|
||||
t=t.replace(/<edit_file\s+path="([^"]+)">([\s\S]*?)<\/edit_file>/g,(_,p,c)=>'<div class="edit-badge">\u270f\ufe0f '+esc(p)+' \u2014 \ud3b8\uc9d1\ub428</div><div class="code-wrap"><pre><code>'+esc(c)+'</code></pre><button class="copy-btn" onclick="copyCode(this)">Copy</button></div>');
|
||||
t=t.replace(/<run_command>([\s\S]*?)<\/run_command>/g,(_,c)=>'<div class="cmd-badge">\u25b6 '+esc(c)+'</div>');
|
||||
t=t.replace(/```(\w*)\n([\s\S]*?)```/g,(_,lang,c)=>{const l=lang||'code';return '<div class="code-wrap"><span class="code-lang">'+l+'</span><pre><code>'+esc(c)+'</code></pre><button class="copy-btn" onclick="copyCode(this)">Copy</button></div>'});
|
||||
t=t.replace(/`([^`]+)`/g,(_,c)=>'<code>'+esc(c)+'</code>');
|
||||
t=t.replace(/\*\*([^*]+)\*\*/g,'<strong>$1</strong>');
|
||||
return t;
|
||||
}
|
||||
"""
|
||||
# Fix the double-backslash needed in TS template string:
|
||||
new_fmt = new_fmt.replace('\\', '\\\\')
|
||||
|
||||
new_content = content[:start_idx] + new_fmt + content[end_idx:]
|
||||
with open('src/extension.ts', 'w') as f:
|
||||
f.write(new_content)
|
||||
print("Successfully replaced fmt function.")
|
||||
else:
|
||||
print("Could not find start or end index.")
|
||||
@@ -1,25 +0,0 @@
|
||||
import sys
|
||||
|
||||
with open('src/extension.ts', 'r') as f:
|
||||
text = f.read()
|
||||
|
||||
start_idx = text.find('function fmt(t){')
|
||||
end_idx = text.find('function copyCode(btn)')
|
||||
|
||||
# I will avoid ALL slashes and regex literals entirely, and use purely new RegExp("...", "g") syntax with properly escaped backslashes for JS strings.
|
||||
new_fmt = """function fmt(t){
|
||||
t=t.replace(new RegExp('<create_file\\\\\\\\s+path="([^"]+)">([\\\\\\\\s\\\\\\\\S]*?)<\\\\\\\\/create_file>', 'g'),(_,p,c)=>'<div class="file-badge">\\uD83D\\uDCC1 '+esc(p)+' \\u2014 \\uC790\\uB3D9 \\uC0DD\\uC131\\uB428</div><div class="code-wrap"><pre><code>'+esc(c)+'</code></pre><button class="copy-btn" onclick="copyCode(this)">Copy</button></div>');
|
||||
t=t.replace(new RegExp('<edit_file\\\\\\\\s+path="([^"]+)">([\\\\\\\\s\\\\\\\\S]*?)<\\\\\\\\/edit_file>', 'g'),(_,p,c)=>'<div class="edit-badge">\\u270F\\uFE0F '+esc(p)+' \\u2014 \\uD3B8\\uC9D1\\uB428</div><div class="code-wrap"><pre><code>'+esc(c)+'</code></pre><button class="copy-btn" onclick="copyCode(this)">Copy</button></div>');
|
||||
t=t.replace(new RegExp('<run_command>([\\\\\\\\s\\\\\\\\S]*?)<\\\\\\\\/run_command>', 'g'),(_,c)=>'<div class="cmd-badge">\\u25B6 '+esc(c)+'</div>');
|
||||
t=t.replace(new RegExp('\\\\\\\\`\\\\\\\\`\\\\\\\\`(\\\\\\\\w*)\\\\\\\\n([\\\\\\\\s\\\\\\\\S]*?)\\\\\\\\`\\\\\\\\`\\\\\\\\`', 'g'),(_,lang,c)=>{const l=lang||'code';return '<div class="code-wrap"><span class="code-lang">'+l+'</span><pre><code>'+esc(c)+'</code></pre><button class="copy-btn" onclick="copyCode(this)">Copy</button></div>'});
|
||||
t=t.replace(new RegExp('\\\\\\\\`([^\\\\\\\\`]+)\\\\\\\\`', 'g'),(_,c)=>'<code>'+esc(c)+'</code>');
|
||||
t=t.replace(new RegExp('\\\\\\\\*\\\\\\\\*([^*]+)\\\\\\\\*\\\\\\\\*', 'g'),'<strong>$1</strong>');
|
||||
return t;
|
||||
}
|
||||
"""
|
||||
|
||||
if start_idx != -1 and end_idx != -1:
|
||||
text = text[:start_idx] + new_fmt + text[end_idx:]
|
||||
with open('src/extension.ts', 'w') as f:
|
||||
f.write(text)
|
||||
print("Fixed!")
|
||||
@@ -1,25 +0,0 @@
|
||||
import sys
|
||||
|
||||
with open('src/extension.ts', 'r') as f:
|
||||
text = f.read()
|
||||
|
||||
start_idx = text.find('function fmt(t){')
|
||||
end_idx = text.find('function copyCode(btn)')
|
||||
|
||||
# I will avoid ALL backticks (use \\x60) and slashes and regex literals entirely, and use purely new RegExp("...", "g") syntax with properly escaped backslashes for JS strings.
|
||||
new_fmt = """function fmt(t){
|
||||
t=t.replace(new RegExp('<create_file\\\\s+path="([^"]+)">([\\\\s\\\\S]*?)<\\\\/create_file>', 'g'),(_,p,c)=>'<div class="file-badge">\\uD83D\\uDCC1 '+esc(p)+' \\u2014 \\uC790\\uB3D9 \\uC0DD\\uC131\\uB428</div><div class="code-wrap"><pre><code>'+esc(c)+'</code></pre><button class="copy-btn" onclick="copyCode(this)">Copy</button></div>');
|
||||
t=t.replace(new RegExp('<edit_file\\\\s+path="([^"]+)">([\\\\s\\\\S]*?)<\\\\/edit_file>', 'g'),(_,p,c)=>'<div class="edit-badge">\\u270F\\uFE0F '+esc(p)+' \\u2014 \\uD3B8\\uC9D1\\uB428</div><div class="code-wrap"><pre><code>'+esc(c)+'</code></pre><button class="copy-btn" onclick="copyCode(this)">Copy</button></div>');
|
||||
t=t.replace(new RegExp('<run_command>([\\\\s\\\\S]*?)<\\\\/run_command>', 'g'),(_,c)=>'<div class="cmd-badge">\\u25B6 '+esc(c)+'</div>');
|
||||
t=t.replace(new RegExp('\\\\x60\\\\x60\\\\x60(\\\\w*)\\\\n([\\\\s\\\\S]*?)\\\\x60\\\\x60\\\\x60', 'g'),(_,lang,c)=>{const l=lang||'code';return '<div class="code-wrap"><span class="code-lang">'+l+'</span><pre><code>'+esc(c)+'</code></pre><button class="copy-btn" onclick="copyCode(this)">Copy</button></div>'});
|
||||
t=t.replace(new RegExp('\\\\x60([^\\\\x60]+)\\\\x60', 'g'),(_,c)=>'<code>'+esc(c)+'</code>');
|
||||
t=t.replace(new RegExp('\\\\*\\\\*([^*]+)\\\\*\\\\*', 'g'),'<strong>$1</strong>');
|
||||
return t;
|
||||
}
|
||||
"""
|
||||
|
||||
if start_idx != -1 and end_idx != -1:
|
||||
text = text[:start_idx] + new_fmt + text[end_idx:]
|
||||
with open('src/extension.ts', 'w') as f:
|
||||
f.write(text)
|
||||
print("Fixed!")
|
||||
@@ -1,25 +0,0 @@
|
||||
import sys
|
||||
|
||||
with open('src/extension.ts', 'r') as f:
|
||||
text = f.read()
|
||||
|
||||
# Replace the beginning of <script> and end of <script> with try-catch reporting
|
||||
script_start = "<script>\n"
|
||||
script_try_start = "<script>\ntry {\n"
|
||||
|
||||
script_end = "</script></body></html>"
|
||||
script_try_end = """} catch(err) {
|
||||
document.body.innerHTML = '<div style="color:#ff4444;padding:20px;background:#111;height:100%;font-size:14px;overflow:auto;"><h2>\u26a0\ufe0f WEBVIEW JS CRASH</h2><pre>' + err.name + ': ' + err.message + '\\n' + err.stack + '</pre></div>';
|
||||
}
|
||||
</script></body></html>"""
|
||||
|
||||
text = text.replace(script_start, script_try_start)
|
||||
text = text.replace(script_end, script_try_end)
|
||||
|
||||
# Also let's rename the view and extension id to completely bypass any cache or conflicts
|
||||
text = text.replace("'local-ai-chat-view'", "'connect-ai-lab-v2-view'")
|
||||
|
||||
with open('src/extension.ts', 'w') as f:
|
||||
f.write(text)
|
||||
|
||||
print("Patch applied.")
|
||||
@@ -1,140 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ko"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<title>Connect AI LAB</title>
|
||||
<style>
|
||||
*{margin:0;padding:0;box-sizing:border-box}
|
||||
:root{
|
||||
--bg:#111113;--bg2:#18181b;--surface:#1e1e22;--surface2:#27272b;
|
||||
--border:rgba(255,255,255,.08);--border2:rgba(255,255,255,.12);
|
||||
--text:#a1a1aa;--text-bright:#fafafa;--text-dim:#52525b;
|
||||
--accent:#818cf8;--accent2:#c084fc;--accent-glow:rgba(129,140,248,.15);
|
||||
--input-bg:#1a1a1e;--code-bg:#0c0c0e;
|
||||
--green:#34d399;--yellow:#fbbf24;--cyan:#22d3ee;--red:#fb7185;
|
||||
}
|
||||
html,body{height:100%;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',system-ui,sans-serif;font-size:13px;background:var(--bg);color:var(--text);display:flex;flex-direction:column;overflow:hidden}
|
||||
.header{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;background:rgba(17,17,19,.85);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);border-bottom:1px solid var(--border);flex-shrink:0;position:relative;z-index:10}
|
||||
.header::after{content:'';position:absolute;bottom:-1px;left:0;right:0;height:1px;background:linear-gradient(90deg,transparent,var(--accent),var(--accent2),transparent);opacity:.3}
|
||||
.header-left{display:flex;align-items:center;gap:10px}
|
||||
.logo{width:24px;height:24px;border-radius:6px;background:linear-gradient(135deg,var(--accent),var(--accent2));display:flex;align-items:center;justify-content:center;font-size:14px;color:#fff;box-shadow:0 0 12px rgba(129,140,248,.3)}
|
||||
.brand{font-weight:700;font-size:13px;color:var(--text-bright);letter-spacing:-.3px}
|
||||
.header-right{display:flex;align-items:center;gap:6px}
|
||||
select{background:var(--surface);color:var(--text-bright);border:1px solid var(--border2);padding:5px 10px;border-radius:6px;font-size:11px;font-family:inherit;cursor:pointer;outline:none;max-width:140px;transition:border-color .2s}
|
||||
select:hover,select:focus{border-color:var(--accent)}
|
||||
.btn-icon{background:var(--surface);border:1px solid var(--border2);color:var(--text-dim);width:28px;height:28px;border-radius:6px;font-size:14px;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all .2s}
|
||||
.btn-icon:hover{background:var(--surface2);color:var(--text-bright);border-color:var(--accent);box-shadow:0 0 8px var(--accent-glow)}
|
||||
.chat{flex:1;overflow-y:auto;padding:20px 16px;display:flex;flex-direction:column;gap:20px}
|
||||
.chat::-webkit-scrollbar{width:3px}.chat::-webkit-scrollbar-track{background:transparent}.chat::-webkit-scrollbar-thumb{background:var(--border2);border-radius:3px}
|
||||
.msg{display:flex;flex-direction:column;gap:6px;animation:msgIn .3s ease-out}
|
||||
.msg-head{display:flex;align-items:center;gap:8px;font-weight:600;font-size:11.5px;color:var(--text)}
|
||||
.msg-time{font-weight:400;font-size:10px;color:var(--text-dim);margin-left:auto}
|
||||
.av{width:22px;height:22px;border-radius:6px;display:flex;align-items:center;justify-content:center;font-size:12px;flex-shrink:0}
|
||||
.av-user{background:var(--surface2);color:var(--text)}.av-ai{background:linear-gradient(135deg,var(--accent),var(--accent2));color:#fff;box-shadow:0 0 8px rgba(129,140,248,.2)}
|
||||
.msg-body{padding-left:30px;line-height:1.7;color:var(--text);white-space:pre-wrap;word-break:break-word;font-size:13px}
|
||||
.msg-user .msg-body{background:var(--surface);border:1px solid var(--border2);border-radius:12px;padding:10px 14px;margin-left:30px;color:var(--text-bright)}
|
||||
.msg-body pre{background:var(--code-bg);border:1px solid var(--border2);border-radius:8px;padding:14px 16px;overflow-x:auto;margin:8px 0;font-size:12px;line-height:1.55;color:#adbac7}
|
||||
.msg-body code{font-family:'SF Mono','Fira Code','Cascadia Code','Menlo',monospace;font-size:12px}
|
||||
.msg-body :not(pre)>code{background:rgba(129,140,248,.1);color:var(--accent);padding:1px 6px;border-radius:4px;border:1px solid rgba(129,140,248,.15)}
|
||||
.code-wrap{position:relative}
|
||||
.code-lang{position:absolute;top:0;left:14px;background:var(--surface2);color:var(--text-dim);padding:1px 8px;border-radius:0 0 4px 4px;font-size:9px;font-family:'SF Mono',monospace;text-transform:uppercase;letter-spacing:.5px}
|
||||
.copy-btn{position:absolute;top:6px;right:8px;background:var(--surface2);border:1px solid var(--border2);color:var(--text-dim);padding:3px 10px;border-radius:5px;font-size:10px;cursor:pointer;opacity:0;transition:all .2s;font-family:inherit;z-index:1}
|
||||
.code-wrap:hover .copy-btn{opacity:1}.copy-btn:hover{background:var(--accent);color:#fff;border-color:var(--accent)}
|
||||
.copy-btn.copied{background:var(--green);color:#fff;border-color:var(--green);opacity:1}
|
||||
.file-badge{background:rgba(251,191,36,.06);border:1px solid rgba(251,191,36,.2);border-radius:8px 8px 0 0;border-bottom:none;padding:8px 14px;font-size:11px;font-weight:600;color:var(--yellow);display:flex;align-items:center;gap:6px}
|
||||
.edit-badge{background:rgba(34,211,238,.06);border:1px solid rgba(34,211,238,.2);border-radius:8px 8px 0 0;border-bottom:none;padding:8px 14px;font-size:11px;font-weight:600;color:var(--cyan);display:flex;align-items:center;gap:6px}
|
||||
.cmd-badge{background:rgba(129,140,248,.06);border:1px solid rgba(129,140,248,.2);border-radius:8px;padding:10px 14px;margin:8px 0;font-size:12px;color:var(--accent);font-family:'SF Mono','Menlo',monospace;display:flex;align-items:center;gap:8px}
|
||||
.agent-report{background:rgba(52,211,153,.06);border:1px solid rgba(52,211,153,.2);border-radius:8px;padding:12px 14px;margin-top:8px;font-size:12px;line-height:1.7}
|
||||
.msg-error .msg-body{color:var(--red)}
|
||||
.welcome{text-align:center;padding:30px 20px 10px}
|
||||
.welcome-logo{width:48px;height:48px;border-radius:14px;margin:0 auto 14px;background:linear-gradient(135deg,var(--accent),var(--accent2));display:flex;align-items:center;justify-content:center;font-size:26px;color:#fff;box-shadow:0 0 30px rgba(129,140,248,.25)}
|
||||
.welcome-title{font-size:18px;font-weight:800;letter-spacing:-.5px;background:linear-gradient(135deg,var(--accent),var(--accent2));-webkit-background-clip:text;-webkit-text-fill-color:transparent;margin-bottom:8px}
|
||||
.welcome-sub{color:var(--text-dim);font-size:12px;line-height:1.6;margin-bottom:16px}
|
||||
.welcome-features{display:flex;justify-content:center;gap:16px;flex-wrap:wrap;margin-bottom:18px}
|
||||
.wf{display:flex;align-items:center;gap:4px;font-size:11px;color:var(--text)}.wf-icon{font-size:14px}
|
||||
.quick-actions{display:flex;flex-wrap:wrap;gap:6px;justify-content:center}
|
||||
.qa-btn{background:var(--surface);border:1px solid var(--border2);color:var(--text);padding:8px 14px;border-radius:8px;font-size:11px;cursor:pointer;transition:all .2s;font-family:inherit}
|
||||
.qa-btn:hover{border-color:var(--accent);color:var(--text-bright);background:var(--surface2);box-shadow:0 0 12px var(--accent-glow)}
|
||||
.loading-wrap{padding-left:30px;padding-top:6px;display:flex;align-items:center;gap:8px}
|
||||
.loading-bar{width:120px;height:3px;background:var(--surface2);border-radius:3px;overflow:hidden;position:relative}
|
||||
.loading-bar::after{content:'';position:absolute;top:0;left:-40px;width:40px;height:100%;background:linear-gradient(90deg,transparent,var(--accent),var(--accent2),transparent);animation:shimmer 1.2s ease-in-out infinite}
|
||||
.loading-text{font-size:11px;color:var(--text-dim);animation:pulse 2s ease-in-out infinite}
|
||||
.input-wrap{padding:10px 16px 16px;flex-shrink:0;position:relative}
|
||||
.input-box{background:var(--input-bg);border:1px solid var(--border2);border-radius:12px;padding:12px 14px;display:flex;flex-direction:column;gap:8px;transition:all .2s;position:relative}
|
||||
.input-box::before{content:'';position:absolute;inset:-1px;border-radius:13px;background:linear-gradient(135deg,var(--accent),var(--accent2));opacity:0;transition:opacity .3s;z-index:-1}
|
||||
.input-box:focus-within{border-color:transparent}.input-box:focus-within::before{opacity:.4}
|
||||
textarea{width:100%;background:transparent;border:none;color:var(--text-bright);font-family:inherit;font-size:13px;line-height:1.5;resize:none;outline:none;min-height:22px;max-height:150px}
|
||||
textarea::placeholder{color:var(--text-dim)}
|
||||
.input-footer{display:flex;align-items:center;justify-content:space-between}
|
||||
.input-hint{font-size:10px;color:var(--text-dim)}
|
||||
.input-btns{display:flex;gap:5px}
|
||||
.send-btn{background:linear-gradient(135deg,var(--accent),var(--accent2));border:none;color:#fff;width:30px;height:30px;border-radius:8px;cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:14px;transition:all .15s;box-shadow:0 2px 8px rgba(129,140,248,.25)}
|
||||
.send-btn:hover{transform:translateY(-1px);box-shadow:0 4px 16px rgba(129,140,248,.35)}.send-btn:active{transform:scale(.94)}.send-btn:disabled{opacity:.25;cursor:not-allowed;transform:none;box-shadow:none}
|
||||
.stop-btn{background:var(--red);border:none;color:#fff;width:30px;height:30px;border-radius:8px;cursor:pointer;display:none;align-items:center;justify-content:center;font-size:11px}
|
||||
.stop-btn.visible{display:flex}
|
||||
@keyframes msgIn{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:translateY(0)}}
|
||||
@keyframes shimmer{0%{left:-40px}100%{left:120px}}
|
||||
@keyframes pulse{0%,100%{opacity:.5}50%{opacity:1}}
|
||||
</style></head><body>
|
||||
<div class="header"><div class="header-left"><div class="logo">\u2726</div><span class="brand">Connect AI LAB</span></div><div class="header-right"><select id="modelSel"></select><button class="btn-icon" id="newChatBtn" title="New Chat">+</button></div></div>
|
||||
<div class="chat" id="chat">
|
||||
<div class="welcome">
|
||||
<div class="welcome-logo">\u2726</div>
|
||||
<div class="welcome-title">Connect AI LAB</div>
|
||||
<div class="welcome-sub">100% \ub85c\uceec \u00b7 100% \uc624\ud504\ub77c\uc778 \u00b7 100% \ubb34\ub8cc<br>\ud504\ub85c\uc81d\ud2b8\ub97c \uc774\ud574\ud558\uace0, \ucf54\ub4dc\ub97c \uc791\uc131\ud558\uace0, \uc2e4\ud589\ud569\ub2c8\ub2e4.</div>
|
||||
<div class="welcome-features"><div class="wf"><span class="wf-icon">\ud83d\udcc1</span> \ud30c\uc77c \uc0dd\uc131</div><div class="wf"><span class="wf-icon">\u270f\ufe0f</span> \ucf54\ub4dc \ud3b8\uc9d1</div><div class="wf"><span class="wf-icon">\ud83d\udda5\ufe0f</span> \ud130\ubbf8\ub110</div><div class="wf"><span class="wf-icon">\ud83d\udd0d</span> \ubd84\uc11d</div></div>
|
||||
<div class="quick-actions">
|
||||
<button class="qa-btn" data-prompt="\uac04\ub2e8\ud55c \ud3ec\ud2b8\ud3f4\ub9ac\uc624 \uc6f9\uc0ac\uc774\ud2b8\ub97c \ub9cc\ub4e4\uc5b4\uc918">\ud83c\udf10 \uc6f9\uc0ac\uc774\ud2b8 \uc0dd\uc131</button>
|
||||
<button class="qa-btn" data-prompt="Express API \uc11c\ubc84\ub97c \ub9cc\ub4e4\uc5b4\uc918">\u26a1 API \uc11c\ubc84</button>
|
||||
<button class="qa-btn" data-prompt="\uc774 \ud504\ub85c\uc81d\ud2b8\uc758 \uad6c\uc870\ub97c \ubd84\uc11d\ud574\uc918">\ud83d\udd0d \ud504\ub85c\uc81d\ud2b8 \ubd84\uc11d</button>
|
||||
<button class="qa-btn" data-prompt="README.md\ub97c \uc791\uc131\ud574\uc918">\ud83d\udcdd README</button>
|
||||
</div></div></div>
|
||||
<div class="input-wrap"><div class="input-box">
|
||||
<textarea id="input" rows="1" placeholder="\ubb34\uc5c7\uc744 \ub9cc\ub4e4\uc5b4 \ub4dc\ub9b4\uae4c\uc694?"></textarea>
|
||||
<div class="input-footer"><span class="input-hint">Enter \uc804\uc1a1 \u00b7 Shift+Enter \uc904\ubc14\uafc8</span>
|
||||
<div class="input-btns"><button class="stop-btn" id="stopBtn">\u25a0</button><button class="send-btn" id="sendBtn">\u2191</button></div></div></div></div>
|
||||
<script>
|
||||
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');
|
||||
let loader=null,sending=false;
|
||||
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){
|
||||
t=t.replace(/<create_file\\s+path="([^"]+)">([\\s\\S]*?)<\\/create_file>/g,(_,p,c)=>'<div class="file-badge">\ud83d\udcc1 '+esc(p)+' \u2014 \uc790\ub3d9 \uc0dd\uc131\ub428</div><div class="code-wrap"><pre><code>'+esc(c)+'</code></pre><button class="copy-btn" onclick="copyCode(this)">Copy</button></div>');
|
||||
t=t.replace(/<edit_file\\s+path="([^"]+)">([\\s\\S]*?)<\\/edit_file>/g,(_,p,c)=>'<div class="edit-badge">\u270f\ufe0f '+esc(p)+' \u2014 \ud3b8\uc9d1\ub428</div><div class="code-wrap"><pre><code>'+esc(c)+'</code></pre><button class="copy-btn" onclick="copyCode(this)">Copy</button></div>');
|
||||
t=t.replace(/<run_command>([\\s\\S]*?)<\\/run_command>/g,(_,c)=>'<div class="cmd-badge">\u25b6 '+esc(c)+'</div>');
|
||||
t=t.replace(/\`\`\`(\\w*)\\n([\\s\\S]*?)\`\`\`/g,(_,lang,c)=>{const l=lang||'code';return '<div class="code-wrap"><span class="code-lang">'+l+'</span><pre><code>'+esc(c)+'</code></pre><button class="copy-btn" onclick="copyCode(this)">Copy</button></div>'});
|
||||
t=t.replace(/\`([^\`]+)\`/g,(_,c)=>'<code>'+esc(c)+'</code>');
|
||||
t=t.replace(/\\*\\*([^*]+)\\*\\*/g,'<strong>$1</strong>');
|
||||
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?'<div class="av av-user">\ud83d\udc64</div><span>You</span>':'<div class="av av-ai">\u2726</div><span>Connect AI LAB</span>')+'<span class="msg-time">'+getTime()+'</span>';
|
||||
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='<div class="msg-head"><div class="av av-ai">\u2726</div><span>Connect AI LAB</span><span class="msg-time">'+getTime()+'</span></div><div class="loading-wrap"><div class="loading-bar"></div><span class="loading-text">\uc0dd\uac01\ud558\ub294 \uc911...</span></div>';chat.appendChild(loader);chat.scrollTop=chat.scrollHeight}
|
||||
function hideLoader(){if(loader&&loader.parentNode)loader.parentNode.removeChild(loader);loader=null}
|
||||
function setSending(v){sending=v;sendBtn.disabled=v;stopBtn.classList.toggle('visible',v);input.disabled=v;if(!v)input.focus()}
|
||||
function send(){const text=input.value.trim();if(!text||sending)return;const w=document.querySelector('.welcome');if(w)w.remove();document.querySelectorAll('.quick-actions').forEach(e=>e.remove());addMsg(text,'user');input.value='';input.style.height='auto';setSending(true);showLoader();vscode.postMessage({type:'prompt',value:text,model:modelSel.value})}
|
||||
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'}));
|
||||
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 'modelsList':modelSel.innerHTML='';msg.value.forEach(m=>{const o=document.createElement('option');o.value=m;o.textContent=m;modelSel.appendChild(o)});break;
|
||||
case 'clearChat':chat.innerHTML='';addMsg('\uc0c8 \ub300\ud654\uac00 \uc2dc\uc791\ub418\uc5c8\uc2b5\ub2c8\ub2e4.','ai');break;
|
||||
case 'restoreMessages':chat.innerHTML='';if(msg.value&&msg.value.length>0){msg.value.forEach(m=>addMsg(m.text,m.role))}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;
|
||||
}});
|
||||
<\/script></body></html>
|
||||
@@ -1,368 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ko"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<title>Connect AI</title>
|
||||
<style>
|
||||
*{margin:0;padding:0;box-sizing:border-box}
|
||||
:root{
|
||||
--bg:#0a0a0c;--bg2:#111114;--surface:rgba(22,22,28,.75);--surface2:rgba(38,38,46,.6);
|
||||
--border:rgba(255,255,255,.06);--border2:rgba(255,255,255,.1);
|
||||
--text:#b0b0be;--text-bright:#f0f0f5;--text-dim:#55556a;
|
||||
--accent:#7c6aff;--accent2:#e040fb;--accent3:#00e5ff;
|
||||
--accent-glow:rgba(124,106,255,.2);--accent2-glow:rgba(224,64,251,.15);
|
||||
--input-bg:rgba(14,14,18,.9);--code-bg:#08080c;
|
||||
--green:#00e676;--yellow:#ffab40;--cyan:#00e5ff;--red:#ff5252;
|
||||
}
|
||||
html,body{height:100%;font-family:'SF Pro Display',-apple-system,BlinkMacSystemFont,'Segoe UI',system-ui,sans-serif;font-size:13px;background:var(--bg);color:var(--text);display:flex;flex-direction:column;overflow:hidden}
|
||||
|
||||
/* AURORA BACKGROUND */
|
||||
body::before{content:'';position:fixed;top:-50%;left:-50%;width:200%;height:200%;background:radial-gradient(ellipse at 20% 50%,rgba(124,106,255,.06) 0%,transparent 50%),radial-gradient(ellipse at 80% 20%,rgba(224,64,251,.04) 0%,transparent 50%),radial-gradient(ellipse at 50% 80%,rgba(0,229,255,.03) 0%,transparent 50%);animation:aurora 20s ease-in-out infinite;z-index:0;pointer-events:none}
|
||||
@keyframes aurora{0%,100%{transform:translate(0,0) rotate(0deg)}33%{transform:translate(2%,-1%) rotate(.5deg)}66%{transform:translate(-1%,2%) rotate(-.5deg)}}
|
||||
|
||||
/* HEADER */
|
||||
.header{display:flex;align-items:center;justify-content:space-between;padding:10px 14px;background:rgba(10,10,12,.8);backdrop-filter:blur(20px);-webkit-backdrop-filter:blur(20px);border-bottom:1px solid var(--border);flex-shrink:0;position:relative;z-index:10}
|
||||
.header::after{content:'';position:absolute;bottom:0;left:0;right:0;height:1px;background:linear-gradient(90deg,transparent 5%,var(--accent) 30%,var(--accent2) 50%,var(--accent3) 70%,transparent 95%);opacity:.5;animation:headerGlow 4s ease-in-out infinite alternate}
|
||||
@keyframes headerGlow{0%{opacity:.3}100%{opacity:.6}}
|
||||
.thinking-bar{height:2px;background:transparent;position:relative;overflow:hidden;flex-shrink:0;z-index:10}
|
||||
.thinking-bar.active{background:rgba(124,106,255,.1)}
|
||||
.thinking-bar.active::after{content:'';position:absolute;top:0;left:-40%;width:40%;height:100%;background:linear-gradient(90deg,transparent,var(--accent),var(--accent2),var(--accent3),transparent);animation:thinkSlide 1.5s ease-in-out infinite}
|
||||
@keyframes thinkSlide{0%{left:-40%}100%{left:100%}}
|
||||
.header-left{display:flex;align-items:center;gap:8px}
|
||||
.logo{width:26px;height:26px;border-radius:8px;background:linear-gradient(135deg,var(--accent),var(--accent2));display:flex;align-items:center;justify-content:center;font-size:14px;color:#fff;box-shadow:0 0 15px rgba(124,106,255,.4),0 0 30px rgba(224,64,251,.15);animation:logoPulse 3s ease-in-out infinite;position:relative}
|
||||
.logo::after{content:'';position:absolute;inset:-2px;border-radius:10px;background:linear-gradient(135deg,var(--accent),var(--accent2),var(--accent3));opacity:.3;filter:blur(4px);animation:logoPulse 3s ease-in-out infinite}
|
||||
@keyframes logoPulse{0%,100%{box-shadow:0 0 15px rgba(124,106,255,.4),0 0 30px rgba(224,64,251,.15)}50%{box-shadow:0 0 20px rgba(124,106,255,.6),0 0 40px rgba(224,64,251,.25)}}
|
||||
.brand{font-weight:800;font-size:14px;color:var(--text-bright);letter-spacing:-.5px;background:linear-gradient(135deg,#fff 40%,var(--accent) 100%);-webkit-background-clip:text;-webkit-text-fill-color:transparent}
|
||||
.header-right{display:flex;align-items:center;gap:5px}
|
||||
select{background:rgba(22,22,28,.9);color:var(--text-bright);border:1px solid var(--border2);padding:5px 8px;border-radius:8px;font-size:10px;font-family:inherit;cursor:pointer;outline:none;max-width:120px;transition:all .3s;backdrop-filter:blur(8px)}
|
||||
select:hover,select:focus{border-color:var(--accent);box-shadow:0 0 12px var(--accent-glow)}
|
||||
.btn-icon{background:rgba(22,22,28,.7);border:1px solid var(--border2);color:var(--text-dim);width:28px;height:28px;border-radius:8px;font-size:13px;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all .3s;backdrop-filter:blur(8px);position:relative;overflow:hidden}
|
||||
.btn-icon::before{content:'';position:absolute;inset:0;background:linear-gradient(135deg,var(--accent-glow),var(--accent2-glow));opacity:0;transition:opacity .3s}
|
||||
.btn-icon:hover{color:var(--text-bright);border-color:var(--accent);transform:translateY(-1px);box-shadow:0 4px 15px var(--accent-glow)}
|
||||
.btn-icon:hover::before{opacity:1}
|
||||
|
||||
/* CHAT */
|
||||
.chat{flex:1;overflow-y:auto;padding:16px 14px;display:flex;flex-direction:column;gap:16px;position:relative;z-index:1}
|
||||
.chat::-webkit-scrollbar{width:2px}.chat::-webkit-scrollbar-track{background:transparent}.chat::-webkit-scrollbar-thumb{background:var(--accent);border-radius:2px;opacity:.5}
|
||||
|
||||
/* MESSAGES */
|
||||
.msg{display:flex;flex-direction:column;gap:5px;animation:msgIn .5s cubic-bezier(.16,1,.3,1)}
|
||||
.msg-head{display:flex;align-items:center;gap:7px;font-weight:600;font-size:11px;color:var(--text)}
|
||||
.msg-time{font-weight:400;font-size:9px;color:var(--text-dim);margin-left:auto;opacity:.6}
|
||||
.av{width:22px;height:22px;border-radius:7px;display:flex;align-items:center;justify-content:center;font-size:11px;flex-shrink:0}
|
||||
.av-user{background:var(--surface2);color:var(--text);border:1px solid var(--border2)}
|
||||
.av-ai{background:linear-gradient(135deg,var(--accent),var(--accent2));color:#fff;box-shadow:0 0 10px rgba(124,106,255,.3)}
|
||||
.msg-body{padding-left:29px;line-height:1.75;color:var(--text);white-space:pre-wrap;word-break:break-word;font-size:13px}
|
||||
.msg-user .msg-body{background:var(--surface);border:1px solid var(--border2);border-radius:14px;padding:10px 14px;margin-left:29px;color:var(--text-bright);backdrop-filter:blur(8px)}
|
||||
.msg-body pre{background:var(--code-bg);border:1px solid var(--border2);border-radius:10px;padding:14px 16px;overflow-x:auto;margin:8px 0;font-size:12px;line-height:1.6;color:#c9d1d9;position:relative}
|
||||
.msg-body pre::-webkit-scrollbar{height:6px}
|
||||
.msg-body pre::-webkit-scrollbar-track{background:rgba(0,0,0,.2);border-radius:4px}
|
||||
.msg-body pre::-webkit-scrollbar-thumb{background:rgba(124,106,255,.3);border-radius:4px}
|
||||
.msg-body pre::-webkit-scrollbar-thumb:hover{background:rgba(124,106,255,.6)}
|
||||
.msg-body code{font-family:'SF Mono','JetBrains Mono','Fira Code','Menlo',monospace;font-size:11.5px}
|
||||
.msg-body :not(pre)>code{background:rgba(124,106,255,.1);color:var(--accent);padding:2px 7px;border-radius:5px;border:1px solid rgba(124,106,255,.15)}
|
||||
.msg-body a{color:var(--accent);text-decoration:none}
|
||||
.msg-body a:hover{text-decoration:underline}
|
||||
.code-wrap{position:relative}
|
||||
.code-lang{position:absolute;top:0;left:14px;background:linear-gradient(135deg,var(--accent),var(--accent2));color:#fff;padding:2px 10px;border-radius:0 0 6px 6px;font-size:9px;font-family:'SF Mono',monospace;text-transform:uppercase;letter-spacing:.5px;font-weight:600}
|
||||
.copy-btn{position:absolute;top:8px;right:8px;background:var(--surface2);border:1px solid var(--border2);color:var(--text-dim);padding:4px 12px;border-radius:6px;font-size:10px;cursor:pointer;opacity:0;transition:all .3s;font-family:inherit;z-index:1;backdrop-filter:blur(8px)}
|
||||
.code-wrap:hover .copy-btn{opacity:1}.copy-btn:hover{background:var(--accent);color:#fff;border-color:var(--accent);box-shadow:0 0 12px var(--accent-glow)}
|
||||
.copy-btn.copied{background:var(--green);color:#fff;border-color:var(--green);opacity:1}
|
||||
|
||||
/* BADGES */
|
||||
.file-badge{background:rgba(255,171,64,.05);border:1px solid rgba(255,171,64,.2);border-radius:10px 10px 0 0;border-bottom:none;padding:8px 14px;font-size:11px;font-weight:700;color:var(--yellow);display:flex;align-items:center;gap:6px;backdrop-filter:blur(8px)}
|
||||
.edit-badge{background:rgba(0,229,255,.05);border:1px solid rgba(0,229,255,.2);border-radius:10px 10px 0 0;border-bottom:none;padding:8px 14px;font-size:11px;font-weight:700;color:var(--cyan);display:flex;align-items:center;gap:6px;backdrop-filter:blur(8px)}
|
||||
.cmd-badge{background:rgba(124,106,255,.05);border:1px solid rgba(124,106,255,.25);border-radius:10px;padding:10px 14px;margin:8px 0;font-size:12px;color:var(--accent);font-family:'SF Mono','Menlo',monospace;display:flex;align-items:center;gap:8px;backdrop-filter:blur(8px)}
|
||||
.msg-error .msg-body{color:var(--red);text-shadow:0 0 20px rgba(255,82,82,.2)}
|
||||
|
||||
/* WELCOME */
|
||||
.welcome{text-align:center;padding:0 20px 20px;position:relative}
|
||||
.welcome-logo{width:56px;height:56px;border-radius:16px;margin:0 auto 16px;background:linear-gradient(135deg,var(--accent),var(--accent2),var(--accent3));display:flex;align-items:center;justify-content:center;font-size:28px;color:#fff;box-shadow:0 0 40px rgba(124,106,255,.35),0 0 80px rgba(224,64,251,.15);animation:welcomeFloat 4s ease-in-out infinite;position:relative}
|
||||
.welcome-logo::before{content:'';position:absolute;inset:-4px;border-radius:20px;background:conic-gradient(from 0deg,var(--accent),var(--accent2),var(--accent3),var(--accent));opacity:.2;filter:blur(8px);animation:spin 8s linear infinite}
|
||||
@keyframes spin{to{transform:rotate(360deg)}}
|
||||
@keyframes welcomeFloat{0%,100%{transform:translateY(0) scale(1)}50%{transform:translateY(-6px) scale(1.03)}}
|
||||
.welcome-title{font-size:22px;font-weight:900;letter-spacing:-1px;background:linear-gradient(135deg,#fff,var(--accent),var(--accent2));background-size:200% 200%;-webkit-background-clip:text;-webkit-text-fill-color:transparent;animation:gradText 5s ease infinite;margin-bottom:8px}
|
||||
@keyframes gradText{0%,100%{background-position:0% 50%}50%{background-position:100% 50%}}
|
||||
.welcome-sub{color:var(--text-dim);font-size:12px;line-height:1.7;margin-bottom:18px;letter-spacing:-.2px}
|
||||
|
||||
/* LOADING */
|
||||
.loading-wrap{padding-left:29px;padding-top:6px;display:flex;align-items:center;gap:10px}
|
||||
.loading-dots{display:flex;gap:4px}
|
||||
.loading-dots span{width:6px;height:6px;border-radius:50%;background:var(--accent);animation:dotBounce 1.4s ease-in-out infinite}
|
||||
.loading-dots span:nth-child(2){animation-delay:.2s;background:var(--accent2)}
|
||||
.loading-dots span:nth-child(3){animation-delay:.4s;background:var(--accent3)}
|
||||
@keyframes dotBounce{0%,80%,100%{transform:scale(.6);opacity:.4}40%{transform:scale(1.2);opacity:1}}
|
||||
.loading-text{font-size:11px;color:var(--text-dim);animation:pulse 2s ease-in-out infinite;letter-spacing:.3px}
|
||||
|
||||
/* INPUT */
|
||||
.input-wrap{padding:8px 14px 14px;flex-shrink:0;position:relative;z-index:1}
|
||||
.input-box{background:var(--input-bg);border:1px solid var(--border2);border-radius:14px;padding:12px 14px;display:flex;flex-direction:column;gap:8px;transition:all .3s;position:relative;backdrop-filter:blur(12px)}
|
||||
.input-box:focus-within{border-color:rgba(124,106,255,.4);box-shadow:0 0 24px rgba(124,106,255,.12);animation:focusPulse 3s infinite}
|
||||
@keyframes focusPulse{0%,100%{box-shadow:0 0 20px rgba(124,106,255,.08)}50%{box-shadow:0 0 28px rgba(124,106,255,.18)}}
|
||||
textarea{width:100%;background:transparent;border:none;color:var(--text-bright);font-family:inherit;font-size:13px;line-height:1.5;resize:none;outline:none;min-height:22px;max-height:150px}
|
||||
textarea::placeholder{color:var(--text-dim)}
|
||||
.input-footer{display:flex;align-items:center;justify-content:space-between}
|
||||
.input-hint{font-size:10px;color:var(--text-dim);opacity:.5}
|
||||
.input-btns{display:flex;gap:5px}
|
||||
.send-btn{background:linear-gradient(135deg,var(--accent),var(--accent2));border:none;color:#fff;width:32px;height:32px;border-radius:10px;cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:14px;transition:all .2s;box-shadow:0 2px 12px rgba(124,106,255,.35);position:relative;overflow:hidden}
|
||||
.send-btn::after{content:'';position:absolute;inset:0;background:linear-gradient(135deg,transparent,rgba(255,255,255,.15));opacity:0;transition:opacity .3s}
|
||||
.send-btn:hover{transform:translateY(-2px) scale(1.05);box-shadow:0 6px 24px rgba(124,106,255,.45)}
|
||||
.send-btn:hover::after{opacity:1}
|
||||
.send-btn:active{transform:scale(.92)}.send-btn:disabled{opacity:.2;cursor:not-allowed;transform:none;box-shadow:none}
|
||||
.stop-btn{background:var(--red);border:none;color:#fff;width:32px;height:32px;border-radius:10px;cursor:pointer;display:none;align-items:center;justify-content:center;font-size:11px;box-shadow:0 0 12px rgba(255,82,82,.3)}
|
||||
.stop-btn.visible{display:flex}
|
||||
@keyframes msgIn{from{opacity:0;transform:translateY(12px) scale(.97)}to{opacity:1;transform:translateY(0) scale(1)}}
|
||||
@keyframes pulse{0%,100%{opacity:.4}50%{opacity:1}}
|
||||
.stream-active{position:relative}
|
||||
.stream-active::after{content:'';display:inline-block;width:2px;height:14px;background:var(--accent);margin-left:2px;animation:blink .6s step-end infinite;vertical-align:text-bottom;border-radius:1px;box-shadow:0 0 6px var(--accent)}
|
||||
@keyframes blink{0%,100%{opacity:1}50%{opacity:0}}
|
||||
.stream-active .code-wrap:last-child {
|
||||
border: 1px solid var(--accent);
|
||||
animation: codePulse 2s infinite;
|
||||
}
|
||||
.stream-active .code-wrap:last-child pre {
|
||||
box-shadow: inset 0 0 20px rgba(124,106,255,0.05);
|
||||
}
|
||||
@keyframes codePulse {
|
||||
0%, 100% { box-shadow: 0 0 15px var(--accent-glow); }
|
||||
50% { box-shadow: 0 0 35px var(--accent2-glow); border-color: var(--accent2); }
|
||||
}
|
||||
.main-view{flex:1;display:flex;flex-direction:column;overflow:hidden;transition:all .5s cubic-bezier(.16,1,.3,1)}
|
||||
body.init .main-view{justify-content:center;margin-top:-6vh}
|
||||
body.init .chat{flex:0 0 auto;overflow:visible;padding-bottom:15px}
|
||||
body.init .input-wrap{max-width:680px;width:100%;margin:0 auto;transform:none;transition:all .5s cubic-bezier(.16,1,.3,1)}
|
||||
|
||||
/* ATTACHMENT */
|
||||
.attach-btn{background:transparent;border:1px solid var(--border2);color:var(--text-dim);width:32px;height:32px;border-radius:10px;cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:16px;transition:all .3s;flex-shrink:0}
|
||||
.attach-btn:hover{color:var(--accent);border-color:var(--accent);box-shadow:0 0 12px var(--accent-glow);transform:translateY(-1px)}
|
||||
.attach-preview{display:none;gap:6px;padding:0 0 6px;flex-wrap:wrap}
|
||||
.attach-preview.visible{display:flex}
|
||||
.attach-chip{display:flex;align-items:center;gap:5px;background:var(--surface2);border:1px solid var(--border2);border-radius:8px;padding:4px 10px;font-size:10px;color:var(--text);animation:msgIn .3s ease}
|
||||
.attach-chip .chip-icon{font-size:12px}
|
||||
.attach-chip .chip-name{max-width:100px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
||||
.attach-chip .chip-remove{cursor:pointer;color:var(--text-dim);font-size:12px;margin-left:2px;transition:color .2s}
|
||||
.attach-chip .chip-remove:hover{color:var(--red)}
|
||||
.attach-thumb{width:28px;height:28px;border-radius:5px;object-fit:cover;border:1px solid var(--border2)}
|
||||
|
||||
/* REGENERATE BUTTON */
|
||||
.regen-btn{display:inline-flex;align-items:center;gap:4px;background:transparent;border:1px solid var(--border2);color:var(--text-dim);padding:4px 12px;border-radius:8px;font-size:10px;cursor:pointer;transition:all .3s;font-family:inherit;margin-top:6px;margin-left:29px}
|
||||
.regen-btn:hover{color:var(--accent);border-color:var(--accent);box-shadow:0 0 12px var(--accent-glow)}
|
||||
|
||||
/* SYNTAX HIGHLIGHTING */
|
||||
.msg-body pre .kw{color:#c792ea}
|
||||
.msg-body pre .str{color:#c3e88d}
|
||||
.msg-body pre .num{color:#f78c6c}
|
||||
.msg-body pre .cm{color:#546e7a;font-style:italic}
|
||||
.msg-body pre .fn{color:#82aaff}
|
||||
.msg-body pre .tag{color:#f07178}
|
||||
.msg-body pre .attr{color:#ffcb6b}
|
||||
.msg-body pre .op{color:#89ddff}
|
||||
.msg-body pre .type{color:#ffcb6b}
|
||||
</style></head><body class="init">
|
||||
<div class="header"><div class="header-left"><div class="logo">\u2726</div><span class="brand">Connect AI</span></div><div class="header-right"><select id="modelSel"></select><button class="btn-icon" id="brainBtn" title="Second Brain">\u{1F9E0}</button><button class="btn-icon" id="settingsBtn" title="Settings">\u2699\uFE0F</button><button class="btn-icon" id="newChatBtn" title="New Chat">+</button></div></div>
|
||||
<div class="thinking-bar" id="thinkingBar"></div>
|
||||
<div class="main-view" id="mainView">
|
||||
<div class="chat" id="chat">
|
||||
<div class="welcome">
|
||||
<div class="welcome-logo">\u2726</div>
|
||||
<div class="welcome-title">Connect AI</div>
|
||||
<div class="welcome-sub">\uBCF4\uC548 \xB7 \uBE44\uC6A9\uCD5C\uC801\uD654 \xB7 \uC9C0\uC2DD\uC5F0\uACB0<br>\uD504\uB85C\uC81D\uD2B8\uB97C \uC774\uD574\uD558\uACE0, \uCF54\uB4DC\uB97C \uC791\uC131\uD558\uACE0, \uC2E4\uD589\uD569\uB2C8\uB2E4.</div>
|
||||
</div></div>
|
||||
<div class="input-wrap"><div class="input-box">
|
||||
<div class="attach-preview" id="attachPreview"></div>
|
||||
<textarea id="input" rows="1" placeholder="\uBB34\uC5C7\uC744 \uB9CC\uB4E4\uC5B4 \uB4DC\uB9B4\uAE4C\uC694?"></textarea>
|
||||
<div class="input-footer"><span class="input-hint">Enter \uC804\uC1A1 \xB7 Shift+Enter \uC904\uBC14\uAFC8</span>
|
||||
<div class="input-btns"><button class="attach-btn" id="attachBtn" title="\uD30C\uC77C \uCCA8\uBD80">+</button><button class="stop-btn" id="stopBtn">\u25A0</button><button class="send-btn" id="sendBtn">\u2191</button></div></div></div>
|
||||
<input type="file" id="fileInput" multiple accept="image/*,audio/*,.txt,.md,.csv,.json,.js,.ts,.html,.css,.py,.java,.rs,.go,.yaml,.yml,.xml,.toml" hidden></div>
|
||||
</div>
|
||||
<script>
|
||||
window.onerror = function(msg, url, line, col, error) {
|
||||
document.body.innerHTML += '<div style="position:absolute;z-index:9999;background:red;color:white;padding:10px;top:0;left:0;right:0">ERROR: ' + msg + ' at line ' + line + '</div>';
|
||||
};
|
||||
window.addEventListener('unhandledrejection', function(event) {
|
||||
document.body.innerHTML += '<div style="position:absolute;z-index:9999;background:red;color:white;padding:10px;bottom:0;left:0;right:0">PROMISE REJECTION: ' + event.reason + '</div>';
|
||||
});
|
||||
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,'<span class="cm">$1</span>');
|
||||
h=h.replace(/(#[^\\n]*)/g,'<span class="cm">$1</span>');
|
||||
h=h.replace(/(\\/\\*[\\s\\S]*?\\*\\/)/g,'<span class="cm">$1</span>');
|
||||
h=h.replace(/("[^&]*?"|'[^&]*?')/g,'<span class="str">$1</span>');
|
||||
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,'<span class="kw">$1</span>');
|
||||
h=h.replace(/\\b(\\d+\\.?\\d*)\\b/g,'<span class="num">$1</span>');
|
||||
h=h.replace(/\\b(True|False|None|true|false|null|undefined|NaN)\\b/g,'<span class="num">$1</span>');
|
||||
h=h.replace(/\\b(String|Number|Boolean|Array|Object|Map|Set|Promise|void|int|float|str|list|dict|tuple)\\b/g,'<span class="type">$1</span>');
|
||||
h=h.replace(/([=!<>+\\-*/%|&^~?:]+)/g,'<span class="op">$1</span>');
|
||||
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('<create_file') > t.lastIndexOf('</create_file>')) t += '</create_file>';
|
||||
if(t.lastIndexOf('<edit_file') > t.lastIndexOf('</edit_file>')) t += '</edit_file>';
|
||||
if(t.lastIndexOf('<run_command') > t.lastIndexOf('</run_command>')) t += '</run_command>';
|
||||
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(/<create_file\\s+path="([^"]+)">([\\s\\S]*?)<\\/create_file>/g,(_,p,c)=>pushB('<div class="file-badge">\u{1F4C1} '+esc(p)+' \u2014 \uC790\uB3D9 \uC0DD\uC131\uB428</div><div class="code-wrap"><pre><code>'+esc(c)+'</code></pre><button class="copy-btn" onclick="copyCode(this)">Copy</button></div>'));
|
||||
t=t.replace(/<edit_file\\s+path="([^"]+)">([\\s\\S]*?)<\\/edit_file>/g,(_,p,c)=>pushB('<div class="edit-badge">\u270F\uFE0F '+esc(p)+' \u2014 \uD3B8\uC9D1\uB428</div><div class="code-wrap"><pre><code>'+esc(c)+'</code></pre><button class="copy-btn" onclick="copyCode(this)">Copy</button></div>'));
|
||||
t=t.replace(/<run_command>([\\s\\S]*?)<\\/run_command>/g,(_,c)=>pushB('<div class="cmd-badge">\u25B6 '+esc(c)+'</div>'));
|
||||
t=t.replace(/\`\`\`(\\w*)\\n([\\s\\S]*?)\`\`\`/g,(_,lang,c)=>{const l=lang||'code';return pushB('<div class="code-wrap"><span class="code-lang">'+esc(l)+'</span><pre><code>'+highlight(c,l)+'</code></pre><button class="copy-btn" onclick="copyCode(this)">Copy</button></div>');});
|
||||
t=t.replace(/\`([^\`]+)\`/g,(_,c)=>pushB('<code>'+esc(c)+'</code>'));
|
||||
t=esc(t);
|
||||
t=t.replace(/\\*\\*([^*]+)\\*\\*/g,'<strong>$1</strong>');
|
||||
t=t.replace(/\\[([^\\]]+)\\]\\(([^)]+)\\)/g, '<a href="$2" target="_blank">$1</a>');
|
||||
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?'<div class="av av-user">\u{1F464}</div><span>You</span>':'<div class="av av-ai">\u2726</div><span>Connect AI</span>')+'<span class="msg-time">'+getTime()+'</span>';
|
||||
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='<div class="msg-head"><div class="av av-ai">\u2726</div><span>Connect AI</span><span class="msg-time">'+getTime()+'</span></div><div class="loading-wrap"><div class="loading-dots"><span></span><span></span><span></span></div><span class="loading-text">\uC0DD\uAC01\uD558\uB294 \uC911...</span></div>';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='<div class="av av-ai">\u2726</div><span>Connect AI</span><span class="msg-time">'+getTime()+'</span>';
|
||||
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='<div class="welcome"><div class="welcome-logo">\u2726</div><div class="welcome-title">Connect AI</div><div class="welcome-sub">\uBCF4\uC548 \xB7 \uBE44\uC6A9\uCD5C\uC801\uD654 \xB7 \uC9C0\uC2DD\uC5F0\uACB0<br>\uD504\uB85C\uC81D\uD2B8\uB97C \uC774\uD574\uD558\uACE0, \uCF54\uB4DC\uB97C \uC791\uC131\uD558\uACE0, \uC2E4\uD589\uD569\uB2C8\uB2E4.</div></div>';
|
||||
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='<div class="welcome"><div class="welcome-logo">\u2726</div><div class="welcome-title">Connect AI</div><div class="welcome-sub">\uBCF4\uC548 \xB7 \uBE44\uC6A9\uCD5C\uC801\uD654 \xB7 \uC9C0\uC2DD\uC5F0\uACB0<br>\uD504\uB85C\uC81D\uD2B8\uB97C \uC774\uD574\uD558\uACE0, \uCF54\uB4DC\uB97C \uC791\uC131\uD558\uACE0, \uC2E4\uD589\uD569\uB2C8\uB2E4.</div></div>';
|
||||
}
|
||||
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 = '<div style="color:#ff4444;padding:20px;background:#111;height:100%;font-size:14px;overflow:auto;"><h2>\u26A0\uFE0F WEBVIEW JS CRASH</h2><pre>' + err.name + ': ' + err.message + '\\n' + err.stack + '</pre></div>';
|
||||
}
|
||||
</script></body></html>
|
||||
@@ -1,2 +0,0 @@
|
||||
var code = "\nfunction highlight(code,lang){\n let h=esc(code);\n h=h.replace(/(\\/\\/[^\\n]*)/g,'<span class=\"cm\">$1</span>');\n h=h.replace(/(#[^\\n]*)/g,'<span class=\"cm\">$1</span>');\n h=h.replace(/(\\/\\*[\\s\\S]*?\\*\\/)/g,'<span class=\"cm\">$1</span>');\n h=h.replace(/("[^&]*?"|'[^&]*?')/g,'<span class=\"str\">$1</span>');\n 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,'<span class=\"kw\">$1</span>');\n h=h.replace(/\\b(\\d+\\.?\\d*)\\b/g,'<span class=\"num\">$1</span>');\n h=h.replace(/\\b(True|False|None|true|false|null|undefined|NaN)\\b/g,'<span class=\"num\">$1</span>');\n h=h.replace(/\\b(String|Number|Boolean|Array|Object|Map|Set|Promise|void|int|float|str|list|dict|tuple)\\b/g,'<span class=\"type\">$1</span>');\n h=h.replace(/([=!<>+\\-*/%|&^~?:]+)/g,'<span class=\"op\">$1</span>');\n}\n";
|
||||
console.log(code);
|
||||
@@ -1,15 +0,0 @@
|
||||
const code = `
|
||||
function highlight(code,lang){
|
||||
let h=esc(code);
|
||||
h=h.replace(/(\\/\\/[^\\n]*)/g,'<span class="cm">$1</span>');
|
||||
h=h.replace(/(#[^\\n]*)/g,'<span class="cm">$1</span>');
|
||||
h=h.replace(/(\\/\\*[\\s\\S]*?\\*\\/)/g,'<span class="cm">$1</span>');
|
||||
h=h.replace(/("[^&]*?"|'[^&]*?')/g,'<span class="str">$1</span>');
|
||||
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,'<span class="kw">$1</span>');
|
||||
h=h.replace(/\\b(\\d+\\.?\\d*)\\b/g,'<span class="num">$1</span>');
|
||||
h=h.replace(/\\b(True|False|None|true|false|null|undefined|NaN)\\b/g,'<span class="num">$1</span>');
|
||||
h=h.replace(/\\b(String|Number|Boolean|Array|Object|Map|Set|Promise|void|int|float|str|list|dict|tuple)\\b/g,'<span class="type">$1</span>');
|
||||
h=h.replace(/([=!<>+\\-*/%|&^~?:]+)/g,'<span class="op">$1</span>');
|
||||
}
|
||||
`;
|
||||
console.log(code);
|
||||
@@ -1,9 +0,0 @@
|
||||
const templateHTML = `
|
||||
<script>
|
||||
let code = "foo";
|
||||
let h = code.replace(/(\\/\\/[^\\n]*)/g, "");
|
||||
</script>
|
||||
`;
|
||||
console.log(templateHTML);
|
||||
const {JSDOM} = require('jsdom');
|
||||
try { new JSDOM(templateHTML, {runScripts:'dangerously'}); console.log('JSDOM OK'); } catch(e) { console.error('ERR:', e.message); }
|
||||
@@ -1,19 +0,0 @@
|
||||
const templateHTML = `
|
||||
<script>
|
||||
function highlight(code,lang){
|
||||
let h=code;
|
||||
h=h.replace(/(\\/\\/[^\\n]*)/g,'<span class=\"cm\">$1</span>');
|
||||
h=h.replace(/(#[^\\n]*)/g,'<span class=\"cm\">$1</span>');
|
||||
h=h.replace(/(\\/\\*[\\s\\S]*?\\*\\/)/g,'<span class=\"cm\">$1</span>');
|
||||
h=h.replace(/("[^&]*?"|'[^&]*?')/g,'<span class=\"str\">$1</span>');
|
||||
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,'<span class=\"kw\">$1</span>');
|
||||
h=h.replace(/\\b(\\d+\\.?\\d*)\\b/g,'<span class=\"num\">$1</span>');
|
||||
h=h.replace(/\\b(True|False|None|true|false|null|undefined|NaN)\\b/g,'<span class=\"num\">$1</span>');
|
||||
h=h.replace(/\\b(String|Number|Boolean|Array|Object|Map|Set|Promise|void|int|float|str|list|dict|tuple)\\b/g,'<span class=\"type\">$1</span>');
|
||||
h=h.replace(/([=!<>+\\-*/%|&^~?:]+)/g,'<span class=\"op\">$1</span>');
|
||||
return h;
|
||||
}
|
||||
</script>
|
||||
`;
|
||||
const {JSDOM} = require('jsdom');
|
||||
try { new JSDOM(templateHTML, {runScripts:'dangerously'}); console.log('JSDOM OK'); } catch(e) { console.error('ERR:', e.message); }
|
||||
@@ -1,11 +0,0 @@
|
||||
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]);
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
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.*?(<!DOCTYPE html>[\s\S]*?)<\/html>.*?;/m);
|
||||
if (match) {
|
||||
const html = match[1] + "</html>";
|
||||
const lines = html.split('\n');
|
||||
console.log("Total lines:", lines.length);
|
||||
// print from line 60 to end
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
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]);
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
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]);
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
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]);
|
||||
@@ -1,4 +0,0 @@
|
||||
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);
|
||||
@@ -1,8 +0,0 @@
|
||||
const templateHTML = `
|
||||
<script>
|
||||
// what ts compiles for \\/\\/
|
||||
h=h.replace(/(\/\/[^\n]*)/g,'<span class=\"cm\">$1</span>');
|
||||
</script>
|
||||
`;
|
||||
const {JSDOM} = require('jsdom');
|
||||
try { new JSDOM(templateHTML, {runScripts:'dangerously'}); console.log('JSDOM OK'); } catch(e) { console.error('ERR:', e.message); }
|
||||
@@ -1,20 +0,0 @@
|
||||
const templateHTML = `
|
||||
<script>
|
||||
function highlight(code,lang){
|
||||
let h=code;
|
||||
h=h.replace(/(\\/{2}[^\\n]*)/g,'<span class=\"cm\">$1</span>');
|
||||
h=h.replace(/(#[^\\n]*)/g,'<span class=\"cm\">$1</span>');
|
||||
h=h.replace(/(\\/{1}\\*[\\s\\S]*?\\*\\/{1})/g,'<span class=\"cm\">$1</span>');
|
||||
h=h.replace(/("[^&]*?"|'[^&]*?')/g,'<span class=\"str\">$1</span>');
|
||||
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,'<span class=\"kw\">$1</span>');
|
||||
h=h.replace(/\\b(\\d+\\.?\\d*)\\b/g,'<span class=\"num\">$1</span>');
|
||||
h=h.replace(/\\b(True|False|None|true|false|null|undefined|NaN)\\b/g,'<span class=\"num\">$1</span>');
|
||||
h=h.replace(/\\b(String|Number|Boolean|Array|Object|Map|Set|Promise|void|int|float|str|list|dict|tuple)\\b/g,'<span class=\"type\">$1</span>');
|
||||
h=h.replace(/([=!<>+\\-*/%|&^~?:]+)/g,'<span class=\"op\">$1</span>');
|
||||
return h;
|
||||
}
|
||||
const displayText='foo'+(true?'\\n\\u{1F4CE} ':'');
|
||||
</script>
|
||||
`;
|
||||
const {JSDOM} = require('jsdom');
|
||||
try { new JSDOM(templateHTML, {runScripts:'dangerously'}); console.log('JSDOM OK'); } catch(e) { console.error('ERR:', e.message); }
|
||||
@@ -1,8 +0,0 @@
|
||||
const templateHTML = `
|
||||
<script>
|
||||
let h = "foo // bar";
|
||||
h=h.replace(/(\\/{2}[^\\n]*)/g,'<span class=\"cm\">$1</span>');
|
||||
</script>
|
||||
`;
|
||||
const {JSDOM} = require('jsdom');
|
||||
try { new JSDOM(templateHTML, {runScripts:'dangerously'}); console.log('JSDOM OK'); } catch(e) { console.error('ERR:', e.message); }
|
||||
@@ -1,29 +0,0 @@
|
||||
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"), '<span class=\"cm\">$1</span>');
|
||||
h = h.replace(new RegExp("(#[^\\\\n]*)", "g"), '<span class=\"cm\">$1</span>');
|
||||
h = h.replace(new RegExp("(\\\\/\\\\*[\\\\s\\\\S]*?\\\\*\\\\/)", "g"), '<span class=\"cm\">$1</span>');
|
||||
h = h.replace(/("[^&]*?"|'[^&]*?')/g, '<span class=\"str\">$1</span>');
|
||||
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"), '<span class=\"kw\">$1</span>');
|
||||
h = h.replace(new RegExp("\\\\b(\\\\d+\\\\.?\\\\d*)\\\\b", "g"), '<span class=\"num\">$1</span>');
|
||||
h = h.replace(new RegExp("\\\\b(True|False|None|true|false|null|undefined|NaN)\\\\b", "g"), '<span class=\"num\">$1</span>');
|
||||
h = h.replace(new RegExp("\\\\b(String|Number|Boolean|Array|Object|Map|Set|Promise|void|int|float|str|list|dict|tuple)\\\\b", "g"), '<span class=\"type\">$1</span>');
|
||||
h = h.replace(/([=!+\\-*/%|&^~?:]+)/g, '<span class=\"op\">$1</span>');
|
||||
var displayText = text + (pendingFiles.length > 0 ? '\\\\n\\ud83d\\udcce ' + pendingFiles.map(function (f) { return f.name; }).join(', ') : '');
|
||||
return "\n <script>\nfunction highlight(code,lang){\n let h=esc(code);\n h=h.replace(new RegExp(\"(\\\\/\\\\/[^\\\\n]*)\", \"g\"),'<span class=\"cm\">$1</span>');\n h=h.replace(new RegExp(\"(#[^\\\\n]*)\", \"g\"),'<span class=\"cm\">$1</span>');\n h=h.replace(new RegExp(\"(\\\\/\\\\*[\\\\s\\\\S]*?\\\\*\\\\/)\", \"g\"),'<span class=\"cm\">$1</span>');\n h=h.replace(/("[^&]*?"|'[^&]*?')/g,'<span class=\"str\">$1</span>');\n 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\"),'<span class=\"kw\">$1</span>');\n h=h.replace(new RegExp(\"\\\\b(\\\\d+\\\\.?\\\\d*)\\\\b\", \"g\"),'<span class=\"num\">$1</span>');\n h=h.replace(new RegExp(\"\\\\b(True|False|None|true|false|null|undefined|NaN)\\\\b\", \"g\"),'<span class=\"num\">$1</span>');\n h=h.replace(new RegExp(\"\\\\b(String|Number|Boolean|Array|Object|Map|Set|Promise|void|int|float|str|list|dict|tuple)\\\\b\", \"g\"),'<span class=\"type\">$1</span>');\n h=h.replace(/([=!+\\-*/%|&^~?:]+)/g,'<span class=\"op\">$1</span>');\n return h;\n}\n const displayText = \"".concat(displayText, "\";\n </script>\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);
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
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"),'<span class=\"cm\">$1</span>');
|
||||
h=h.replace(new RegExp("(#[^\\\\n]*)", "g"),'<span class=\"cm\">$1</span>');
|
||||
h=h.replace(new RegExp("(\\\\/\\\\*[\\\\s\\\\S]*?\\\\*\\\\/)", "g"),'<span class=\"cm\">$1</span>');
|
||||
h=h.replace(/("[^&]*?"|'[^&]*?')/g,'<span class=\"str\">$1</span>');
|
||||
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"),'<span class=\"kw\">$1</span>');
|
||||
h=h.replace(new RegExp("\\\\b(\\\\d+\\\\.?\\\\d*)\\\\b", "g"),'<span class=\"num\">$1</span>');
|
||||
h=h.replace(new RegExp("\\\\b(True|False|None|true|false|null|undefined|NaN)\\\\b", "g"),'<span class=\"num\">$1</span>');
|
||||
h=h.replace(new RegExp("\\\\b(String|Number|Boolean|Array|Object|Map|Set|Promise|void|int|float|str|list|dict|tuple)\\\\b", "g"),'<span class=\"type\">$1</span>');
|
||||
h=h.replace(/([=!+\\-*/%|&^~?:]+)/g,'<span class=\"op\">$1</span>');
|
||||
|
||||
const displayText=text+(pendingFiles.length>0?'\\\\n\\ud83d\\udcce '+pendingFiles.map(f=>f.name).join(', '):'');
|
||||
|
||||
return `
|
||||
<script>
|
||||
function highlight(code,lang){
|
||||
let h=esc(code);
|
||||
h=h.replace(new RegExp("(\\\\/\\\\/[^\\\\n]*)", "g"),'<span class=\"cm\">$1</span>');
|
||||
h=h.replace(new RegExp("(#[^\\\\n]*)", "g"),'<span class=\"cm\">$1</span>');
|
||||
h=h.replace(new RegExp("(\\\\/\\\\*[\\\\s\\\\S]*?\\\\*\\\\/)", "g"),'<span class=\"cm\">$1</span>');
|
||||
h=h.replace(/("[^&]*?"|'[^&]*?')/g,'<span class=\"str\">$1</span>');
|
||||
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"),'<span class=\"kw\">$1</span>');
|
||||
h=h.replace(new RegExp("\\\\b(\\\\d+\\\\.?\\\\d*)\\\\b", "g"),'<span class=\"num\">$1</span>');
|
||||
h=h.replace(new RegExp("\\\\b(True|False|None|true|false|null|undefined|NaN)\\\\b", "g"),'<span class=\"num\">$1</span>');
|
||||
h=h.replace(new RegExp("\\\\b(String|Number|Boolean|Array|Object|Map|Set|Promise|void|int|float|str|list|dict|tuple)\\\\b", "g"),'<span class=\"type\">$1</span>');
|
||||
h=h.replace(/([=!+\\-*/%|&^~?:]+)/g,'<span class=\"op\">$1</span>');
|
||||
return h;
|
||||
}
|
||||
const displayText = "${displayText}";
|
||||
</script>
|
||||
`;
|
||||
}
|
||||
const html = getHtml();
|
||||
const {JSDOM} = require('jsdom');
|
||||
try { new JSDOM(html, {runScripts:'dangerously'}); console.log('JSDOM OK'); } catch(e) { console.error('ERR:', e.message); }
|
||||
@@ -1,19 +0,0 @@
|
||||
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 <script>\nfunction highlight(code,lang){\n let h=esc(code);\n h=h.replace(new RegExp(\"(\\\\/\\\\/[^\\\\n]*)\", \"g\"),'<span class=\"cm\">$1</span>');\n h=h.replace(new RegExp(\"(#[^\\\\n]*)\", \"g\"),'<span class=\"cm\">$1</span>');\n h=h.replace(new RegExp(\"(\\\\/\\\\*[\\\\s\\\\S]*?\\\\*\\\\/)\", \"g\"),'<span class=\"cm\">$1</span>');\n h=h.replace(/("[^&]*?"|'[^&]*?')/g,'<span class=\"str\">$1</span>');\n 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\"),'<span class=\"kw\">$1</span>');\n h=h.replace(new RegExp(\"\\\\b(\\\\d+\\\\.?\\\\d*)\\\\b\", \"g\"),'<span class=\"num\">$1</span>');\n h=h.replace(new RegExp(\"\\\\b(True|False|None|true|false|null|undefined|NaN)\\\\b\", \"g\"),'<span class=\"num\">$1</span>');\n h=h.replace(new RegExp(\"\\\\b(String|Number|Boolean|Array|Object|Map|Set|Promise|void|int|float|str|list|dict|tuple)\\\\b\", \"g\"),'<span class=\"type\">$1</span>');\n h=h.replace(/([=!+*/%|&^~?:-]+)/g,'<span class=\"op\">$1</span>');\n return h;\n}\n const displayText = \"".concat(displayText, "\";\n </script>\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);
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
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 `
|
||||
<script>
|
||||
function highlight(code,lang){
|
||||
let h=esc(code);
|
||||
h=h.replace(new RegExp("(\\\\/\\\\/[^\\\\n]*)", "g"),'<span class=\"cm\">$1</span>');
|
||||
h=h.replace(new RegExp("(#[^\\\\n]*)", "g"),'<span class=\"cm\">$1</span>');
|
||||
h=h.replace(new RegExp("(\\\\/\\\\*[\\\\s\\\\S]*?\\\\*\\\\/)", "g"),'<span class=\"cm\">$1</span>');
|
||||
h=h.replace(/("[^&]*?"|'[^&]*?')/g,'<span class=\"str\">$1</span>');
|
||||
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"),'<span class=\"kw\">$1</span>');
|
||||
h=h.replace(new RegExp("\\\\b(\\\\d+\\\\.?\\\\d*)\\\\b", "g"),'<span class=\"num\">$1</span>');
|
||||
h=h.replace(new RegExp("\\\\b(True|False|None|true|false|null|undefined|NaN)\\\\b", "g"),'<span class=\"num\">$1</span>');
|
||||
h=h.replace(new RegExp("\\\\b(String|Number|Boolean|Array|Object|Map|Set|Promise|void|int|float|str|list|dict|tuple)\\\\b", "g"),'<span class=\"type\">$1</span>');
|
||||
h=h.replace(/([=!+*/%|&^~?:-]+)/g,'<span class=\"op\">$1</span>');
|
||||
return h;
|
||||
}
|
||||
const displayText = "${displayText}";
|
||||
</script>
|
||||
`;
|
||||
}
|
||||
const html = getHtml();
|
||||
const {JSDOM} = require('jsdom');
|
||||
try { new JSDOM(html, {runScripts:'dangerously'}); console.log('JSDOM OK'); } catch(e) { console.error('ERR:', e.message); }
|
||||
@@ -1,17 +0,0 @@
|
||||
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.*?(<!DOCTYPE html>[\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'); }
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
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);
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
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);
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
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);
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
const fs=require('fs');
|
||||
let t=fs.readFileSync('out/extension.js', 'utf8');
|
||||
let m = t.match(/h=h\.replace\([^)]*\)/g);
|
||||
console.log(m);
|
||||
@@ -1,4 +0,0 @@
|
||||
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));
|
||||
@@ -1,7 +0,0 @@
|
||||
const templateHTML = `
|
||||
<script>
|
||||
let code = "foo";
|
||||
let h = code.replace(/(\\/\\/[^\\\\n]*)/g, "");
|
||||
</script>
|
||||
`;
|
||||
console.log(templateHTML);
|
||||
-17
@@ -1,17 +0,0 @@
|
||||
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();
|
||||
-15
@@ -1,15 +0,0 @@
|
||||
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();
|
||||
@@ -1,16 +0,0 @@
|
||||
const axios = require('axios');
|
||||
async function test() {
|
||||
const targetUrl = 'http://127.0.0.1:11434/api/chat';
|
||||
const payload = {
|
||||
model: "gemma4:e2b",
|
||||
messages: [{ role: "user", content: "hello" }],
|
||||
stream: false
|
||||
};
|
||||
try {
|
||||
const res = await axios.post(targetUrl, payload);
|
||||
console.log("SUCCESS:", res.data);
|
||||
} catch(err) {
|
||||
console.log("ERROR:", err.response ? err.response.data : err.message);
|
||||
}
|
||||
}
|
||||
test();
|
||||
@@ -1,9 +0,0 @@
|
||||
import requests
|
||||
import json
|
||||
|
||||
try:
|
||||
res = requests.post('http://127.0.0.1:4825/api/evaluate', json={"prompt": "1+1은?"})
|
||||
print(res.status_code)
|
||||
print(res.text)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
@@ -1,6 +0,0 @@
|
||||
const axios = require('axios');
|
||||
axios.post('http://127.0.0.1:1234/v1/chat/completions', {
|
||||
model: 'google/gemma-4-e2b',
|
||||
messages: [{'role':'user','content':'당신은 AI 에이전트의 역량을 검증하는 자동 채점관입니다.\n\n[평가 과제]\ntest\n\n위 문제에 대해 스스로 완벽한 답안을 도출해 본 뒤, 그 수준이 100점 만점에 몇 점에 해당하는지 자체 평가하십시오. 출력 포맷은 반드시 아래 1줄의 순수 JSON이어야 합니다.\n{ "score": 점수숫자, "reason": "이 점수를 준 이유를 한글로 간략히 작성" }'}],
|
||||
stream: false
|
||||
}).then(r => console.log(r.data.choices[0].message.content)).catch(e => console.error(e));
|
||||
@@ -1,2 +0,0 @@
|
||||
const axios = require('axios');
|
||||
axios.get('http://127.0.0.1:4825/ping').then(r => console.log(r.data)).catch(e => console.error(e));
|
||||
@@ -1,22 +0,0 @@
|
||||
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, '<span class=\"cm\">$1</span>';
|
||||
;
|
||||
h = h.replace(/(#[^\\\\n]*)/g, '<span class=\"cm\">$1</span>');
|
||||
h = h.replace(/(\\/, { 1: }, * [s, S] * ? : , * , /{1})/g, '<span class=\"cm\">$1</span>');
|
||||
h = h.replace(/("[^&]*?"|'[^&]*?')/g, '<span class=\"str\">$1</span>');
|
||||
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, '<span class=\"kw\">$1</span>');
|
||||
h = h.replace(/\\b(\\d+\\.?\\d*)\\b/g, '<span class=\"num\">$1</span>');
|
||||
h = h.replace(/\\b(True|False|None|true|false|null|undefined|NaN)\\b/g, '<span class=\"num\">$1</span>');
|
||||
h = h.replace(/\\b(String|Number|Boolean|Array|Object|Map|Set|Promise|void|int|float|str|list|dict|tuple)\\b/g, '<span class=\"type\">$1</span>');
|
||||
h = h.replace(/([=!+\\-*/%|&^~?:]+)/g, '<span class=\"op\">$1</span>');
|
||||
var displayText = text + (pendingFiles.length > 0 ? '\\\\n\\ud83d\\udcce ' + pendingFiles.map(function (f) { return f.name; }).join(', ') : '');
|
||||
return "\n <script>\n ".concat(h, "\n ").concat(displayText, "\n ").concat(t, "\n </script>\n ");
|
||||
}
|
||||
console.log(getHtml());
|
||||
@@ -1,29 +0,0 @@
|
||||
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,'<span class=\"cm\">$1</span>');
|
||||
h=h.replace(/(#[^\\\\n]*)/g,'<span class=\"cm\">$1</span>');
|
||||
h=h.replace(/(\\/{1}\\*[\\s\\S]*?\\*\\/{1})/g,'<span class=\"cm\">$1</span>');
|
||||
h=h.replace(/("[^&]*?"|'[^&]*?')/g,'<span class=\"str\">$1</span>');
|
||||
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,'<span class=\"kw\">$1</span>');
|
||||
h=h.replace(/\\b(\\d+\\.?\\d*)\\b/g,'<span class=\"num\">$1</span>');
|
||||
h=h.replace(/\\b(True|False|None|true|false|null|undefined|NaN)\\b/g,'<span class=\"num\">$1</span>');
|
||||
h=h.replace(/\\b(String|Number|Boolean|Array|Object|Map|Set|Promise|void|int|float|str|list|dict|tuple)\\b/g,'<span class=\"type\">$1</span>');
|
||||
h=h.replace(/([=!+\\-*/%|&^~?:]+)/g,'<span class=\"op\">$1</span>');
|
||||
|
||||
const displayText=text+(pendingFiles.length>0?'\\\\n\\ud83d\\udcce '+pendingFiles.map(f=>f.name).join(', '):'');
|
||||
|
||||
return `
|
||||
<script>
|
||||
${h}
|
||||
${displayText}
|
||||
${t}
|
||||
</script>
|
||||
`;
|
||||
}
|
||||
console.log(getHtml());
|
||||
@@ -1,8 +0,0 @@
|
||||
const html = ` h=h.replace(/([=!<>+\\\\-*/%|&^~?:]+)/g,'<span class=\"op\">$1</span>');`;
|
||||
console.log(html);
|
||||
try {
|
||||
eval(html);
|
||||
console.log("OK");
|
||||
} catch(e) {
|
||||
console.log("ERROR:", e.message);
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
"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');
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
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'); }
|
||||
-369
@@ -1,369 +0,0 @@
|
||||
const html = `<!DOCTYPE html>
|
||||
<html lang="ko"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<title>Connect AI</title>
|
||||
<style>
|
||||
*{margin:0;padding:0;box-sizing:border-box}
|
||||
:root{
|
||||
--bg:#0a0a0c;--bg2:#111114;--surface:rgba(22,22,28,.75);--surface2:rgba(38,38,46,.6);
|
||||
--border:rgba(255,255,255,.06);--border2:rgba(255,255,255,.1);
|
||||
--text:#b0b0be;--text-bright:#f0f0f5;--text-dim:#55556a;
|
||||
--accent:#7c6aff;--accent2:#e040fb;--accent3:#00e5ff;
|
||||
--accent-glow:rgba(124,106,255,.2);--accent2-glow:rgba(224,64,251,.15);
|
||||
--input-bg:rgba(14,14,18,.9);--code-bg:#08080c;
|
||||
--green:#00e676;--yellow:#ffab40;--cyan:#00e5ff;--red:#ff5252;
|
||||
}
|
||||
html,body{height:100%;font-family:'SF Pro Display',-apple-system,BlinkMacSystemFont,'Segoe UI',system-ui,sans-serif;font-size:13px;background:var(--bg);color:var(--text);display:flex;flex-direction:column;overflow:hidden}
|
||||
|
||||
/* AURORA BACKGROUND */
|
||||
body::before{content:'';position:fixed;top:-50%;left:-50%;width:200%;height:200%;background:radial-gradient(ellipse at 20% 50%,rgba(124,106,255,.06) 0%,transparent 50%),radial-gradient(ellipse at 80% 20%,rgba(224,64,251,.04) 0%,transparent 50%),radial-gradient(ellipse at 50% 80%,rgba(0,229,255,.03) 0%,transparent 50%);animation:aurora 20s ease-in-out infinite;z-index:0;pointer-events:none}
|
||||
@keyframes aurora{0%,100%{transform:translate(0,0) rotate(0deg)}33%{transform:translate(2%,-1%) rotate(.5deg)}66%{transform:translate(-1%,2%) rotate(-.5deg)}}
|
||||
|
||||
/* HEADER */
|
||||
.header{display:flex;align-items:center;justify-content:space-between;padding:10px 14px;background:rgba(10,10,12,.8);backdrop-filter:blur(20px);-webkit-backdrop-filter:blur(20px);border-bottom:1px solid var(--border);flex-shrink:0;position:relative;z-index:10}
|
||||
.header::after{content:'';position:absolute;bottom:0;left:0;right:0;height:1px;background:linear-gradient(90deg,transparent 5%,var(--accent) 30%,var(--accent2) 50%,var(--accent3) 70%,transparent 95%);opacity:.5;animation:headerGlow 4s ease-in-out infinite alternate}
|
||||
@keyframes headerGlow{0%{opacity:.3}100%{opacity:.6}}
|
||||
.thinking-bar{height:2px;background:transparent;position:relative;overflow:hidden;flex-shrink:0;z-index:10}
|
||||
.thinking-bar.active{background:rgba(124,106,255,.1)}
|
||||
.thinking-bar.active::after{content:'';position:absolute;top:0;left:-40%;width:40%;height:100%;background:linear-gradient(90deg,transparent,var(--accent),var(--accent2),var(--accent3),transparent);animation:thinkSlide 1.5s ease-in-out infinite}
|
||||
@keyframes thinkSlide{0%{left:-40%}100%{left:100%}}
|
||||
.header-left{display:flex;align-items:center;gap:8px}
|
||||
.logo{width:26px;height:26px;border-radius:8px;background:linear-gradient(135deg,var(--accent),var(--accent2));display:flex;align-items:center;justify-content:center;font-size:14px;color:#fff;box-shadow:0 0 15px rgba(124,106,255,.4),0 0 30px rgba(224,64,251,.15);animation:logoPulse 3s ease-in-out infinite;position:relative}
|
||||
.logo::after{content:'';position:absolute;inset:-2px;border-radius:10px;background:linear-gradient(135deg,var(--accent),var(--accent2),var(--accent3));opacity:.3;filter:blur(4px);animation:logoPulse 3s ease-in-out infinite}
|
||||
@keyframes logoPulse{0%,100%{box-shadow:0 0 15px rgba(124,106,255,.4),0 0 30px rgba(224,64,251,.15)}50%{box-shadow:0 0 20px rgba(124,106,255,.6),0 0 40px rgba(224,64,251,.25)}}
|
||||
.brand{font-weight:800;font-size:14px;color:var(--text-bright);letter-spacing:-.5px;background:linear-gradient(135deg,#fff 40%,var(--accent) 100%);-webkit-background-clip:text;-webkit-text-fill-color:transparent}
|
||||
.header-right{display:flex;align-items:center;gap:5px}
|
||||
select{background:rgba(22,22,28,.9);color:var(--text-bright);border:1px solid var(--border2);padding:5px 8px;border-radius:8px;font-size:10px;font-family:inherit;cursor:pointer;outline:none;max-width:120px;transition:all .3s;backdrop-filter:blur(8px)}
|
||||
select:hover,select:focus{border-color:var(--accent);box-shadow:0 0 12px var(--accent-glow)}
|
||||
.btn-icon{background:rgba(22,22,28,.7);border:1px solid var(--border2);color:var(--text-dim);width:28px;height:28px;border-radius:8px;font-size:13px;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all .3s;backdrop-filter:blur(8px);position:relative;overflow:hidden}
|
||||
.btn-icon::before{content:'';position:absolute;inset:0;background:linear-gradient(135deg,var(--accent-glow),var(--accent2-glow));opacity:0;transition:opacity .3s}
|
||||
.btn-icon:hover{color:var(--text-bright);border-color:var(--accent);transform:translateY(-1px);box-shadow:0 4px 15px var(--accent-glow)}
|
||||
.btn-icon:hover::before{opacity:1}
|
||||
|
||||
/* CHAT */
|
||||
.chat{flex:1;overflow-y:auto;padding:16px 14px;display:flex;flex-direction:column;gap:16px;position:relative;z-index:1}
|
||||
.chat::-webkit-scrollbar{width:2px}.chat::-webkit-scrollbar-track{background:transparent}.chat::-webkit-scrollbar-thumb{background:var(--accent);border-radius:2px;opacity:.5}
|
||||
|
||||
/* MESSAGES */
|
||||
.msg{display:flex;flex-direction:column;gap:5px;animation:msgIn .5s cubic-bezier(.16,1,.3,1)}
|
||||
.msg-head{display:flex;align-items:center;gap:7px;font-weight:600;font-size:11px;color:var(--text)}
|
||||
.msg-time{font-weight:400;font-size:9px;color:var(--text-dim);margin-left:auto;opacity:.6}
|
||||
.av{width:22px;height:22px;border-radius:7px;display:flex;align-items:center;justify-content:center;font-size:11px;flex-shrink:0}
|
||||
.av-user{background:var(--surface2);color:var(--text);border:1px solid var(--border2)}
|
||||
.av-ai{background:linear-gradient(135deg,var(--accent),var(--accent2));color:#fff;box-shadow:0 0 10px rgba(124,106,255,.3)}
|
||||
.msg-body{padding-left:29px;line-height:1.75;color:var(--text);white-space:pre-wrap;word-break:break-word;font-size:13px}
|
||||
.msg-user .msg-body{background:var(--surface);border:1px solid var(--border2);border-radius:14px;padding:10px 14px;margin-left:29px;color:var(--text-bright);backdrop-filter:blur(8px)}
|
||||
.msg-body pre{background:var(--code-bg);border:1px solid var(--border2);border-radius:10px;padding:14px 16px;overflow-x:auto;margin:8px 0;font-size:12px;line-height:1.6;color:#c9d1d9;position:relative}
|
||||
.msg-body pre::-webkit-scrollbar{height:6px}
|
||||
.msg-body pre::-webkit-scrollbar-track{background:rgba(0,0,0,.2);border-radius:4px}
|
||||
.msg-body pre::-webkit-scrollbar-thumb{background:rgba(124,106,255,.3);border-radius:4px}
|
||||
.msg-body pre::-webkit-scrollbar-thumb:hover{background:rgba(124,106,255,.6)}
|
||||
.msg-body code{font-family:'SF Mono','JetBrains Mono','Fira Code','Menlo',monospace;font-size:11.5px}
|
||||
.msg-body :not(pre)>code{background:rgba(124,106,255,.1);color:var(--accent);padding:2px 7px;border-radius:5px;border:1px solid rgba(124,106,255,.15)}
|
||||
.msg-body a{color:var(--accent);text-decoration:none}
|
||||
.msg-body a:hover{text-decoration:underline}
|
||||
.code-wrap{position:relative}
|
||||
.code-lang{position:absolute;top:0;left:14px;background:linear-gradient(135deg,var(--accent),var(--accent2));color:#fff;padding:2px 10px;border-radius:0 0 6px 6px;font-size:9px;font-family:'SF Mono',monospace;text-transform:uppercase;letter-spacing:.5px;font-weight:600}
|
||||
.copy-btn{position:absolute;top:8px;right:8px;background:var(--surface2);border:1px solid var(--border2);color:var(--text-dim);padding:4px 12px;border-radius:6px;font-size:10px;cursor:pointer;opacity:0;transition:all .3s;font-family:inherit;z-index:1;backdrop-filter:blur(8px)}
|
||||
.code-wrap:hover .copy-btn{opacity:1}.copy-btn:hover{background:var(--accent);color:#fff;border-color:var(--accent);box-shadow:0 0 12px var(--accent-glow)}
|
||||
.copy-btn.copied{background:var(--green);color:#fff;border-color:var(--green);opacity:1}
|
||||
|
||||
/* BADGES */
|
||||
.file-badge{background:rgba(255,171,64,.05);border:1px solid rgba(255,171,64,.2);border-radius:10px 10px 0 0;border-bottom:none;padding:8px 14px;font-size:11px;font-weight:700;color:var(--yellow);display:flex;align-items:center;gap:6px;backdrop-filter:blur(8px)}
|
||||
.edit-badge{background:rgba(0,229,255,.05);border:1px solid rgba(0,229,255,.2);border-radius:10px 10px 0 0;border-bottom:none;padding:8px 14px;font-size:11px;font-weight:700;color:var(--cyan);display:flex;align-items:center;gap:6px;backdrop-filter:blur(8px)}
|
||||
.cmd-badge{background:rgba(124,106,255,.05);border:1px solid rgba(124,106,255,.25);border-radius:10px;padding:10px 14px;margin:8px 0;font-size:12px;color:var(--accent);font-family:'SF Mono','Menlo',monospace;display:flex;align-items:center;gap:8px;backdrop-filter:blur(8px)}
|
||||
.msg-error .msg-body{color:var(--red);text-shadow:0 0 20px rgba(255,82,82,.2)}
|
||||
|
||||
/* WELCOME */
|
||||
.welcome{text-align:center;padding:0 20px 20px;position:relative}
|
||||
.welcome-logo{width:56px;height:56px;border-radius:16px;margin:0 auto 16px;background:linear-gradient(135deg,var(--accent),var(--accent2),var(--accent3));display:flex;align-items:center;justify-content:center;font-size:28px;color:#fff;box-shadow:0 0 40px rgba(124,106,255,.35),0 0 80px rgba(224,64,251,.15);animation:welcomeFloat 4s ease-in-out infinite;position:relative}
|
||||
.welcome-logo::before{content:'';position:absolute;inset:-4px;border-radius:20px;background:conic-gradient(from 0deg,var(--accent),var(--accent2),var(--accent3),var(--accent));opacity:.2;filter:blur(8px);animation:spin 8s linear infinite}
|
||||
@keyframes spin{to{transform:rotate(360deg)}}
|
||||
@keyframes welcomeFloat{0%,100%{transform:translateY(0) scale(1)}50%{transform:translateY(-6px) scale(1.03)}}
|
||||
.welcome-title{font-size:22px;font-weight:900;letter-spacing:-1px;background:linear-gradient(135deg,#fff,var(--accent),var(--accent2));background-size:200% 200%;-webkit-background-clip:text;-webkit-text-fill-color:transparent;animation:gradText 5s ease infinite;margin-bottom:8px}
|
||||
@keyframes gradText{0%,100%{background-position:0% 50%}50%{background-position:100% 50%}}
|
||||
.welcome-sub{color:var(--text-dim);font-size:12px;line-height:1.7;margin-bottom:18px;letter-spacing:-.2px}
|
||||
|
||||
/* LOADING */
|
||||
.loading-wrap{padding-left:29px;padding-top:6px;display:flex;align-items:center;gap:10px}
|
||||
.loading-dots{display:flex;gap:4px}
|
||||
.loading-dots span{width:6px;height:6px;border-radius:50%;background:var(--accent);animation:dotBounce 1.4s ease-in-out infinite}
|
||||
.loading-dots span:nth-child(2){animation-delay:.2s;background:var(--accent2)}
|
||||
.loading-dots span:nth-child(3){animation-delay:.4s;background:var(--accent3)}
|
||||
@keyframes dotBounce{0%,80%,100%{transform:scale(.6);opacity:.4}40%{transform:scale(1.2);opacity:1}}
|
||||
.loading-text{font-size:11px;color:var(--text-dim);animation:pulse 2s ease-in-out infinite;letter-spacing:.3px}
|
||||
|
||||
/* INPUT */
|
||||
.input-wrap{padding:8px 14px 14px;flex-shrink:0;position:relative;z-index:1}
|
||||
.input-box{background:var(--input-bg);border:1px solid var(--border2);border-radius:14px;padding:12px 14px;display:flex;flex-direction:column;gap:8px;transition:all .3s;position:relative;backdrop-filter:blur(12px)}
|
||||
.input-box:focus-within{border-color:rgba(124,106,255,.4);box-shadow:0 0 24px rgba(124,106,255,.12);animation:focusPulse 3s infinite}
|
||||
@keyframes focusPulse{0%,100%{box-shadow:0 0 20px rgba(124,106,255,.08)}50%{box-shadow:0 0 28px rgba(124,106,255,.18)}}
|
||||
textarea{width:100%;background:transparent;border:none;color:var(--text-bright);font-family:inherit;font-size:13px;line-height:1.5;resize:none;outline:none;min-height:22px;max-height:150px}
|
||||
textarea::placeholder{color:var(--text-dim)}
|
||||
.input-footer{display:flex;align-items:center;justify-content:space-between}
|
||||
.input-hint{font-size:10px;color:var(--text-dim);opacity:.5}
|
||||
.input-btns{display:flex;gap:5px}
|
||||
.send-btn{background:linear-gradient(135deg,var(--accent),var(--accent2));border:none;color:#fff;width:32px;height:32px;border-radius:10px;cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:14px;transition:all .2s;box-shadow:0 2px 12px rgba(124,106,255,.35);position:relative;overflow:hidden}
|
||||
.send-btn::after{content:'';position:absolute;inset:0;background:linear-gradient(135deg,transparent,rgba(255,255,255,.15));opacity:0;transition:opacity .3s}
|
||||
.send-btn:hover{transform:translateY(-2px) scale(1.05);box-shadow:0 6px 24px rgba(124,106,255,.45)}
|
||||
.send-btn:hover::after{opacity:1}
|
||||
.send-btn:active{transform:scale(.92)}.send-btn:disabled{opacity:.2;cursor:not-allowed;transform:none;box-shadow:none}
|
||||
.stop-btn{background:var(--red);border:none;color:#fff;width:32px;height:32px;border-radius:10px;cursor:pointer;display:none;align-items:center;justify-content:center;font-size:11px;box-shadow:0 0 12px rgba(255,82,82,.3)}
|
||||
.stop-btn.visible{display:flex}
|
||||
@keyframes msgIn{from{opacity:0;transform:translateY(12px) scale(.97)}to{opacity:1;transform:translateY(0) scale(1)}}
|
||||
@keyframes pulse{0%,100%{opacity:.4}50%{opacity:1}}
|
||||
.stream-active{position:relative}
|
||||
.stream-active::after{content:'';display:inline-block;width:2px;height:14px;background:var(--accent);margin-left:2px;animation:blink .6s step-end infinite;vertical-align:text-bottom;border-radius:1px;box-shadow:0 0 6px var(--accent)}
|
||||
@keyframes blink{0%,100%{opacity:1}50%{opacity:0}}
|
||||
.stream-active .code-wrap:last-child {
|
||||
border: 1px solid var(--accent);
|
||||
animation: codePulse 2s infinite;
|
||||
}
|
||||
.stream-active .code-wrap:last-child pre {
|
||||
box-shadow: inset 0 0 20px rgba(124,106,255,0.05);
|
||||
}
|
||||
@keyframes codePulse {
|
||||
0%, 100% { box-shadow: 0 0 15px var(--accent-glow); }
|
||||
50% { box-shadow: 0 0 35px var(--accent2-glow); border-color: var(--accent2); }
|
||||
}
|
||||
.main-view{flex:1;display:flex;flex-direction:column;overflow:hidden;transition:all .5s cubic-bezier(.16,1,.3,1)}
|
||||
body.init .main-view{justify-content:center;margin-top:-6vh}
|
||||
body.init .chat{flex:0 0 auto;overflow:visible;padding-bottom:15px}
|
||||
body.init .input-wrap{max-width:680px;width:100%;margin:0 auto;transform:none;transition:all .5s cubic-bezier(.16,1,.3,1)}
|
||||
|
||||
/* ATTACHMENT */
|
||||
.attach-btn{background:transparent;border:1px solid var(--border2);color:var(--text-dim);width:32px;height:32px;border-radius:10px;cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:16px;transition:all .3s;flex-shrink:0}
|
||||
.attach-btn:hover{color:var(--accent);border-color:var(--accent);box-shadow:0 0 12px var(--accent-glow);transform:translateY(-1px)}
|
||||
.attach-preview{display:none;gap:6px;padding:0 0 6px;flex-wrap:wrap}
|
||||
.attach-preview.visible{display:flex}
|
||||
.attach-chip{display:flex;align-items:center;gap:5px;background:var(--surface2);border:1px solid var(--border2);border-radius:8px;padding:4px 10px;font-size:10px;color:var(--text);animation:msgIn .3s ease}
|
||||
.attach-chip .chip-icon{font-size:12px}
|
||||
.attach-chip .chip-name{max-width:100px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
||||
.attach-chip .chip-remove{cursor:pointer;color:var(--text-dim);font-size:12px;margin-left:2px;transition:color .2s}
|
||||
.attach-chip .chip-remove:hover{color:var(--red)}
|
||||
.attach-thumb{width:28px;height:28px;border-radius:5px;object-fit:cover;border:1px solid var(--border2)}
|
||||
|
||||
/* REGENERATE BUTTON */
|
||||
.regen-btn{display:inline-flex;align-items:center;gap:4px;background:transparent;border:1px solid var(--border2);color:var(--text-dim);padding:4px 12px;border-radius:8px;font-size:10px;cursor:pointer;transition:all .3s;font-family:inherit;margin-top:6px;margin-left:29px}
|
||||
.regen-btn:hover{color:var(--accent);border-color:var(--accent);box-shadow:0 0 12px var(--accent-glow)}
|
||||
|
||||
/* SYNTAX HIGHLIGHTING */
|
||||
.msg-body pre .kw{color:#c792ea}
|
||||
.msg-body pre .str{color:#c3e88d}
|
||||
.msg-body pre .num{color:#f78c6c}
|
||||
.msg-body pre .cm{color:#546e7a;font-style:italic}
|
||||
.msg-body pre .fn{color:#82aaff}
|
||||
.msg-body pre .tag{color:#f07178}
|
||||
.msg-body pre .attr{color:#ffcb6b}
|
||||
.msg-body pre .op{color:#89ddff}
|
||||
.msg-body pre .type{color:#ffcb6b}
|
||||
</style></head><body class="init">
|
||||
<div class="header"><div class="header-left"><div class="logo">\u2726</div><span class="brand">Connect AI</span></div><div class="header-right"><select id="modelSel"></select><button class="btn-icon" id="brainBtn" title="Second Brain">\ud83e\udde0</button><button class="btn-icon" id="settingsBtn" title="Settings">\u2699\ufe0f</button><button class="btn-icon" id="newChatBtn" title="New Chat">+</button></div></div>
|
||||
<div class="thinking-bar" id="thinkingBar"></div>
|
||||
<div class="main-view" id="mainView">
|
||||
<div class="chat" id="chat">
|
||||
<div class="welcome">
|
||||
<div class="welcome-logo">\u2726</div>
|
||||
<div class="welcome-title">Connect AI</div>
|
||||
<div class="welcome-sub">\ubcf4\uc548 \u00b7 \ube44\uc6a9\ucd5c\uc801\ud654 \u00b7 \uc9c0\uc2dd\uc5f0\uacb0<br>\ud504\ub85c\uc81d\ud2b8\ub97c \uc774\ud574\ud558\uace0, \ucf54\ub4dc\ub97c \uc791\uc131\ud558\uace0, \uc2e4\ud589\ud569\ub2c8\ub2e4.</div>
|
||||
</div></div>
|
||||
<div class="input-wrap"><div class="input-box">
|
||||
<div class="attach-preview" id="attachPreview"></div>
|
||||
<textarea id="input" rows="1" placeholder="\ubb34\uc5c7\uc744 \ub9cc\ub4e4\uc5b4 \ub4dc\ub9b4\uae4c\uc694?"></textarea>
|
||||
<div class="input-footer"><span class="input-hint">Enter \uc804\uc1a1 \u00b7 Shift+Enter \uc904\ubc14\uafc8</span>
|
||||
<div class="input-btns"><button class="attach-btn" id="attachBtn" title="\ud30c\uc77c \ucca8\ubd80">+</button><button class="stop-btn" id="stopBtn">\u25a0</button><button class="send-btn" id="sendBtn">\u2191</button></div></div></div>
|
||||
<input type="file" id="fileInput" multiple accept="image/*,audio/*,.txt,.md,.csv,.json,.js,.ts,.html,.css,.py,.java,.rs,.go,.yaml,.yml,.xml,.toml" hidden></div>
|
||||
</div>
|
||||
<script>
|
||||
window.onerror = function(msg, url, line, col, error) {
|
||||
document.body.innerHTML += '<div style="position:absolute;z-index:9999;background:red;color:white;padding:10px;top:0;left:0;right:0">ERROR: ' + msg + ' at line ' + line + '</div>';
|
||||
};
|
||||
window.addEventListener('unhandledrejection', function(event) {
|
||||
document.body.innerHTML += '<div style="position:absolute;z-index:9999;background:red;color:white;padding:10px;bottom:0;left:0;right:0">PROMISE REJECTION: ' + event.reason + '</div>';
|
||||
});
|
||||
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,'<span class=\"cm\">$1</span>');
|
||||
h=h.replace(/(#[^\\n]*)/g,'<span class=\"cm\">$1</span>');
|
||||
h=h.replace(/(\\/\\*[\\s\\S]*?\\*\\/)/g,'<span class=\"cm\">$1</span>');
|
||||
h=h.replace(/("[^&]*?"|'[^&]*?')/g,'<span class=\"str\">$1</span>');
|
||||
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,'<span class=\"kw\">$1</span>');
|
||||
h=h.replace(/\\b(\\d+\\.?\\d*)\\b/g,'<span class=\"num\">$1</span>');
|
||||
h=h.replace(/\\b(True|False|None|true|false|null|undefined|NaN)\\b/g,'<span class=\"num\">$1</span>');
|
||||
h=h.replace(/\\b(String|Number|Boolean|Array|Object|Map|Set|Promise|void|int|float|str|list|dict|tuple)\\b/g,'<span class=\"type\">$1</span>');
|
||||
h=h.replace(/([=!<>+\\-*/%|&^~?:]+)/g,'<span class=\"op\">$1</span>');
|
||||
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('<create_file') > t.lastIndexOf('</create_file>')) t += '</create_file>';
|
||||
if(t.lastIndexOf('<edit_file') > t.lastIndexOf('</edit_file>')) t += '</edit_file>';
|
||||
if(t.lastIndexOf('<run_command') > t.lastIndexOf('</run_command>')) t += '</run_command>';
|
||||
if((t.match(/\x60\x60\x60/g)||[]).length % 2 !== 0) t += '\\n\x60\x60\x60';
|
||||
|
||||
const blocks = [];
|
||||
function pushB(h){ blocks.push(h); return '__B' + (blocks.length-1) + '__'; }
|
||||
t=t.replace(/<create_file\\s+path="([^"]+)">([\\s\\S]*?)<\\/create_file>/g,(_,p,c)=>pushB('<div class="file-badge">\ud83d\udcc1 '+esc(p)+' \u2014 \uc790\ub3d9 \uc0dd\uc131\ub428</div><div class="code-wrap"><pre><code>'+esc(c)+'</code></pre><button class="copy-btn" onclick="copyCode(this)">Copy</button></div>'));
|
||||
t=t.replace(/<edit_file\\s+path="([^"]+)">([\\s\\S]*?)<\\/edit_file>/g,(_,p,c)=>pushB('<div class="edit-badge">\u270f\ufe0f '+esc(p)+' \u2014 \ud3b8\uc9d1\ub428</div><div class="code-wrap"><pre><code>'+esc(c)+'</code></pre><button class="copy-btn" onclick="copyCode(this)">Copy</button></div>'));
|
||||
t=t.replace(/<run_command>([\\s\\S]*?)<\\/run_command>/g,(_,c)=>pushB('<div class="cmd-badge">\u25b6 '+esc(c)+'</div>'));
|
||||
t=t.replace(/\x60\x60\x60(\\w*)\\n([\\s\\S]*?)\x60\x60\x60/g,(_,lang,c)=>{const l=lang||'code';return pushB('<div class="code-wrap"><span class="code-lang">'+esc(l)+'</span><pre><code>'+highlight(c,l)+'</code></pre><button class="copy-btn" onclick="copyCode(this)">Copy</button></div>');});
|
||||
t=t.replace(/\x60([^\x60]+)\x60/g,(_,c)=>pushB('<code>'+esc(c)+'</code>'));
|
||||
t=esc(t);
|
||||
t=t.replace(/\\*\\*([^*]+)\\*\\*/g,'<strong>$1</strong>');
|
||||
t=t.replace(/\\[([^\\]]+)\\]\\(([^)]+)\\)/g, '<a href="$2" target="_blank">$1</a>');
|
||||
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?'<div class="av av-user">\ud83d\udc64</div><span>You</span>':'<div class="av av-ai">\u2726</div><span>Connect AI</span>')+'<span class="msg-time">'+getTime()+'</span>';
|
||||
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='<div class="msg-head"><div class="av av-ai">\u2726</div><span>Connect AI</span><span class="msg-time">'+getTime()+'</span></div><div class="loading-wrap"><div class="loading-dots"><span></span><span></span><span></span></div><span class="loading-text">\uc0dd\uac01\ud558\ub294 \uc911...</span></div>';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/')?'\ud83c\udfa7':'\ud83d\udcc4';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='<div class="av av-ai">\u2726</div><span>Connect AI</span><span class="msg-time">'+getTime()+'</span>';
|
||||
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='\ud83d\udd04 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='<div class="welcome"><div class="welcome-logo">\u2726</div><div class="welcome-title">Connect AI</div><div class="welcome-sub">\ubcf4\uc548 \u00b7 \ube44\uc6a9\ucd5c\uc801\ud654 \u00b7 \uc9c0\uc2dd\uc5f0\uacb0<br>\ud504\ub85c\uc81d\ud2b8\ub97c \uc774\ud574\ud558\uace0, \ucf54\ub4dc\ub97c \uc791\uc131\ud558\uace0, \uc2e4\ud589\ud569\ub2c8\ub2e4.</div></div>';
|
||||
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='<div class="welcome"><div class="welcome-logo">\u2726</div><div class="welcome-title">Connect AI</div><div class="welcome-sub">\ubcf4\uc548 \u00b7 \ube44\uc6a9\ucd5c\uc801\ud654 \u00b7 \uc9c0\uc2dd\uc5f0\uacb0<br>\ud504\ub85c\uc81d\ud2b8\ub97c \uc774\ud574\ud558\uace0, \ucf54\ub4dc\ub97c \uc791\uc131\ud558\uace0, \uc2e4\ud589\ud569\ub2c8\ub2e4.</div></div>';
|
||||
}
|
||||
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 = '<div style="color:#ff4444;padding:20px;background:#111;height:100%;font-size:14px;overflow:auto;"><h2>\u26a0\ufe0f WEBVIEW JS CRASH</h2><pre>' + err.name + ': ' + err.message + '\\n' + err.stack + '</pre></div>';
|
||||
}
|
||||
</script></body></html>`;
|
||||
fs.writeFileSync('test_eval3.html', html);
|
||||
console.log(html.length);
|
||||
@@ -1,8 +0,0 @@
|
||||
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);`);
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
const orig = `
|
||||
<script>
|
||||
function pushB() {} function esc() {}
|
||||
let t = "";
|
||||
t=t.replace(/<create_file\\s+path="([^"]+)">([\\s\\S]*?)<\\/create_file>/g,(_,p,c)=>pushB('<div class="file-badge">\\ud83d\\udcc1 '+esc(p)+' \\u2014 \\uc790\\ub3d9 \\uc0dd\\uc131\\ub428</div><div class="code-wrap"><pre><code>'+esc(c)+'</code></pre><button class="copy-btn" onclick="copyCode(this)">Copy</button></div>'));
|
||||
</script>
|
||||
`;
|
||||
const {JSDOM} = require('jsdom');
|
||||
try { new JSDOM(orig, {runScripts:'dangerously'}); console.log('JSDOM OK'); } catch(e) { console.error('ERR:', e.message); }
|
||||
@@ -1,9 +0,0 @@
|
||||
import requests
|
||||
import json
|
||||
|
||||
try:
|
||||
res = requests.post('http://127.0.0.1:11434/api/generate', json={"model": "gemma4:e2b", "prompt": "1+1은?", "stream": False})
|
||||
print(res.status_code)
|
||||
print(res.text)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
@@ -1,4 +0,0 @@
|
||||
// Test just the create_file regex pattern in isolation
|
||||
const t = '<create_file path="test.js">console.log("hi")</create_file>';
|
||||
const result = t.replace(/<create_file\s+path="([^"]+)">([\s\S]*?)<\/create_file>/g, 'MATCH:$1:$2');
|
||||
console.log(result);
|
||||
-192
@@ -1,192 +0,0 @@
|
||||
|
||||
window.onerror = function(msg, url, line, col, error) {
|
||||
document.body.innerHTML += '<div style="position:absolute;z-index:9999;background:red;color:white;padding:10px;top:0;left:0;right:0">ERROR: ' + msg + ' at line ' + line + '</div>';
|
||||
};
|
||||
window.addEventListener('unhandledrejection', function(event) {
|
||||
document.body.innerHTML += '<div style="position:absolute;z-index:9999;background:red;color:white;padding:10px;bottom:0;left:0;right:0">PROMISE REJECTION: ' + event.reason + '</div>';
|
||||
});
|
||||
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,'<span class="cm">$1</span>');
|
||||
h=h.replace(/(#[^\\n]*)/g,'<span class="cm">$1</span>');
|
||||
h=h.replace(/(\\/\\*[\\s\\S]*?\\*\\/)/g,'<span class="cm">$1</span>');
|
||||
h=h.replace(/("[^&]*?"|'[^&]*?')/g,'<span class="str">$1</span>');
|
||||
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,'<span class="kw">$1</span>');
|
||||
h=h.replace(/\\b(\\d+\\.?\\d*)\\b/g,'<span class="num">$1</span>');
|
||||
h=h.replace(/\\b(True|False|None|true|false|null|undefined|NaN)\\b/g,'<span class="num">$1</span>');
|
||||
h=h.replace(/\\b(String|Number|Boolean|Array|Object|Map|Set|Promise|void|int|float|str|list|dict|tuple)\\b/g,'<span class="type">$1</span>');
|
||||
h=h.replace(/([=!<>+\\-*/%|&^~?:]+)/g,'<span class="op">$1</span>');
|
||||
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('<create_file') > t.lastIndexOf('</create_file>')) t += '</create_file>';
|
||||
if(t.lastIndexOf('<edit_file') > t.lastIndexOf('</edit_file>')) t += '</edit_file>';
|
||||
if(t.lastIndexOf('<run_command') > t.lastIndexOf('</run_command>')) t += '</run_command>';
|
||||
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(/<create_file\\s+path="([^"]+)">([\\s\\S]*?)<\\/create_file>/g,(_,p,c)=>pushB('<div class="file-badge">\u{1F4C1} '+esc(p)+' \u2014 \uC790\uB3D9 \uC0DD\uC131\uB428</div><div class="code-wrap"><pre><code>'+esc(c)+'</code></pre><button class="copy-btn" onclick="copyCode(this)">Copy</button></div>'));
|
||||
t=t.replace(/<edit_file\\s+path="([^"]+)">([\\s\\S]*?)<\\/edit_file>/g,(_,p,c)=>pushB('<div class="edit-badge">\u270F\uFE0F '+esc(p)+' \u2014 \uD3B8\uC9D1\uB428</div><div class="code-wrap"><pre><code>'+esc(c)+'</code></pre><button class="copy-btn" onclick="copyCode(this)">Copy</button></div>'));
|
||||
t=t.replace(/<run_command>([\\s\\S]*?)<\\/run_command>/g,(_,c)=>pushB('<div class="cmd-badge">\u25B6 '+esc(c)+'</div>'));
|
||||
t=t.replace(/\`\`\`(\\w*)\\n([\\s\\S]*?)\`\`\`/g,(_,lang,c)=>{const l=lang||'code';return pushB('<div class="code-wrap"><span class="code-lang">'+esc(l)+'</span><pre><code>'+highlight(c,l)+'</code></pre><button class="copy-btn" onclick="copyCode(this)">Copy</button></div>');});
|
||||
t=t.replace(/\`([^\`]+)\`/g,(_,c)=>pushB('<code>'+esc(c)+'</code>'));
|
||||
t=esc(t);
|
||||
t=t.replace(/\\*\\*([^*]+)\\*\\*/g,'<strong>$1</strong>');
|
||||
t=t.replace(/\\[([^\\]]+)\\]\\(([^)]+)\\)/g, '<a href="$2" target="_blank">$1</a>');
|
||||
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?'<div class="av av-user">\u{1F464}</div><span>You</span>':'<div class="av av-ai">\u2726</div><span>Connect AI</span>')+'<span class="msg-time">'+getTime()+'</span>';
|
||||
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='<div class="msg-head"><div class="av av-ai">\u2726</div><span>Connect AI</span><span class="msg-time">'+getTime()+'</span></div><div class="loading-wrap"><div class="loading-dots"><span></span><span></span><span></span></div><span class="loading-text">\uC0DD\uAC01\uD558\uB294 \uC911...</span></div>';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='<div class="av av-ai">\u2726</div><span>Connect AI</span><span class="msg-time">'+getTime()+'</span>';
|
||||
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='<div class="welcome"><div class="welcome-logo">\u2726</div><div class="welcome-title">Connect AI</div><div class="welcome-sub">\uBCF4\uC548 \xB7 \uBE44\uC6A9\uCD5C\uC801\uD654 \xB7 \uC9C0\uC2DD\uC5F0\uACB0<br>\uD504\uB85C\uC81D\uD2B8\uB97C \uC774\uD574\uD558\uACE0, \uCF54\uB4DC\uB97C \uC791\uC131\uD558\uACE0, \uC2E4\uD589\uD569\uB2C8\uB2E4.</div></div>';
|
||||
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='<div class="welcome"><div class="welcome-logo">\u2726</div><div class="welcome-title">Connect AI</div><div class="welcome-sub">\uBCF4\uC548 \xB7 \uBE44\uC6A9\uCD5C\uC801\uD654 \xB7 \uC9C0\uC2DD\uC5F0\uACB0<br>\uD504\uB85C\uC81D\uD2B8\uB97C \uC774\uD574\uD558\uACE0, \uCF54\uB4DC\uB97C \uC791\uC131\uD558\uACE0, \uC2E4\uD589\uD569\uB2C8\uB2E4.</div></div>';
|
||||
}
|
||||
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 = '<div style="color:#ff4444;padding:20px;background:#111;height:100%;font-size:14px;overflow:auto;"><h2>\u26A0\uFE0F WEBVIEW JS CRASH</h2><pre>' + err.name + ': ' + err.message + '\\n' + err.stack + '</pre></div>';
|
||||
}
|
||||
-367
@@ -1,367 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ko"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<title>Connect AI</title>
|
||||
<style>
|
||||
*{margin:0;padding:0;box-sizing:border-box}
|
||||
:root{
|
||||
--bg:#0a0a0c;--bg2:#111114;--surface:rgba(22,22,28,.75);--surface2:rgba(38,38,46,.6);
|
||||
--border:rgba(255,255,255,.06);--border2:rgba(255,255,255,.1);
|
||||
--text:#b0b0be;--text-bright:#f0f0f5;--text-dim:#55556a;
|
||||
--accent:#7c6aff;--accent2:#e040fb;--accent3:#00e5ff;
|
||||
--accent-glow:rgba(124,106,255,.2);--accent2-glow:rgba(224,64,251,.15);
|
||||
--input-bg:rgba(14,14,18,.9);--code-bg:#08080c;
|
||||
--green:#00e676;--yellow:#ffab40;--cyan:#00e5ff;--red:#ff5252;
|
||||
}
|
||||
html,body{height:100%;font-family:'SF Pro Display',-apple-system,BlinkMacSystemFont,'Segoe UI',system-ui,sans-serif;font-size:13px;background:var(--bg);color:var(--text);display:flex;flex-direction:column;overflow:hidden}
|
||||
|
||||
/* AURORA BACKGROUND */
|
||||
body::before{content:'';position:fixed;top:-50%;left:-50%;width:200%;height:200%;background:radial-gradient(ellipse at 20% 50%,rgba(124,106,255,.06) 0%,transparent 50%),radial-gradient(ellipse at 80% 20%,rgba(224,64,251,.04) 0%,transparent 50%),radial-gradient(ellipse at 50% 80%,rgba(0,229,255,.03) 0%,transparent 50%);animation:aurora 20s ease-in-out infinite;z-index:0;pointer-events:none}
|
||||
@keyframes aurora{0%,100%{transform:translate(0,0) rotate(0deg)}33%{transform:translate(2%,-1%) rotate(.5deg)}66%{transform:translate(-1%,2%) rotate(-.5deg)}}
|
||||
|
||||
/* HEADER */
|
||||
.header{display:flex;align-items:center;justify-content:space-between;padding:10px 14px;background:rgba(10,10,12,.8);backdrop-filter:blur(20px);-webkit-backdrop-filter:blur(20px);border-bottom:1px solid var(--border);flex-shrink:0;position:relative;z-index:10}
|
||||
.header::after{content:'';position:absolute;bottom:0;left:0;right:0;height:1px;background:linear-gradient(90deg,transparent 5%,var(--accent) 30%,var(--accent2) 50%,var(--accent3) 70%,transparent 95%);opacity:.5;animation:headerGlow 4s ease-in-out infinite alternate}
|
||||
@keyframes headerGlow{0%{opacity:.3}100%{opacity:.6}}
|
||||
.thinking-bar{height:2px;background:transparent;position:relative;overflow:hidden;flex-shrink:0;z-index:10}
|
||||
.thinking-bar.active{background:rgba(124,106,255,.1)}
|
||||
.thinking-bar.active::after{content:'';position:absolute;top:0;left:-40%;width:40%;height:100%;background:linear-gradient(90deg,transparent,var(--accent),var(--accent2),var(--accent3),transparent);animation:thinkSlide 1.5s ease-in-out infinite}
|
||||
@keyframes thinkSlide{0%{left:-40%}100%{left:100%}}
|
||||
.header-left{display:flex;align-items:center;gap:8px}
|
||||
.logo{width:26px;height:26px;border-radius:8px;background:linear-gradient(135deg,var(--accent),var(--accent2));display:flex;align-items:center;justify-content:center;font-size:14px;color:#fff;box-shadow:0 0 15px rgba(124,106,255,.4),0 0 30px rgba(224,64,251,.15);animation:logoPulse 3s ease-in-out infinite;position:relative}
|
||||
.logo::after{content:'';position:absolute;inset:-2px;border-radius:10px;background:linear-gradient(135deg,var(--accent),var(--accent2),var(--accent3));opacity:.3;filter:blur(4px);animation:logoPulse 3s ease-in-out infinite}
|
||||
@keyframes logoPulse{0%,100%{box-shadow:0 0 15px rgba(124,106,255,.4),0 0 30px rgba(224,64,251,.15)}50%{box-shadow:0 0 20px rgba(124,106,255,.6),0 0 40px rgba(224,64,251,.25)}}
|
||||
.brand{font-weight:800;font-size:14px;color:var(--text-bright);letter-spacing:-.5px;background:linear-gradient(135deg,#fff 40%,var(--accent) 100%);-webkit-background-clip:text;-webkit-text-fill-color:transparent}
|
||||
.header-right{display:flex;align-items:center;gap:5px}
|
||||
select{background:rgba(22,22,28,.9);color:var(--text-bright);border:1px solid var(--border2);padding:5px 8px;border-radius:8px;font-size:10px;font-family:inherit;cursor:pointer;outline:none;max-width:120px;transition:all .3s;backdrop-filter:blur(8px)}
|
||||
select:hover,select:focus{border-color:var(--accent);box-shadow:0 0 12px var(--accent-glow)}
|
||||
.btn-icon{background:rgba(22,22,28,.7);border:1px solid var(--border2);color:var(--text-dim);width:28px;height:28px;border-radius:8px;font-size:13px;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all .3s;backdrop-filter:blur(8px);position:relative;overflow:hidden}
|
||||
.btn-icon::before{content:'';position:absolute;inset:0;background:linear-gradient(135deg,var(--accent-glow),var(--accent2-glow));opacity:0;transition:opacity .3s}
|
||||
.btn-icon:hover{color:var(--text-bright);border-color:var(--accent);transform:translateY(-1px);box-shadow:0 4px 15px var(--accent-glow)}
|
||||
.btn-icon:hover::before{opacity:1}
|
||||
|
||||
/* CHAT */
|
||||
.chat{flex:1;overflow-y:auto;padding:16px 14px;display:flex;flex-direction:column;gap:16px;position:relative;z-index:1}
|
||||
.chat::-webkit-scrollbar{width:2px}.chat::-webkit-scrollbar-track{background:transparent}.chat::-webkit-scrollbar-thumb{background:var(--accent);border-radius:2px;opacity:.5}
|
||||
|
||||
/* MESSAGES */
|
||||
.msg{display:flex;flex-direction:column;gap:5px;animation:msgIn .5s cubic-bezier(.16,1,.3,1)}
|
||||
.msg-head{display:flex;align-items:center;gap:7px;font-weight:600;font-size:11px;color:var(--text)}
|
||||
.msg-time{font-weight:400;font-size:9px;color:var(--text-dim);margin-left:auto;opacity:.6}
|
||||
.av{width:22px;height:22px;border-radius:7px;display:flex;align-items:center;justify-content:center;font-size:11px;flex-shrink:0}
|
||||
.av-user{background:var(--surface2);color:var(--text);border:1px solid var(--border2)}
|
||||
.av-ai{background:linear-gradient(135deg,var(--accent),var(--accent2));color:#fff;box-shadow:0 0 10px rgba(124,106,255,.3)}
|
||||
.msg-body{padding-left:29px;line-height:1.75;color:var(--text);white-space:pre-wrap;word-break:break-word;font-size:13px}
|
||||
.msg-user .msg-body{background:var(--surface);border:1px solid var(--border2);border-radius:14px;padding:10px 14px;margin-left:29px;color:var(--text-bright);backdrop-filter:blur(8px)}
|
||||
.msg-body pre{background:var(--code-bg);border:1px solid var(--border2);border-radius:10px;padding:14px 16px;overflow-x:auto;margin:8px 0;font-size:12px;line-height:1.6;color:#c9d1d9;position:relative}
|
||||
.msg-body pre::-webkit-scrollbar{height:6px}
|
||||
.msg-body pre::-webkit-scrollbar-track{background:rgba(0,0,0,.2);border-radius:4px}
|
||||
.msg-body pre::-webkit-scrollbar-thumb{background:rgba(124,106,255,.3);border-radius:4px}
|
||||
.msg-body pre::-webkit-scrollbar-thumb:hover{background:rgba(124,106,255,.6)}
|
||||
.msg-body code{font-family:'SF Mono','JetBrains Mono','Fira Code','Menlo',monospace;font-size:11.5px}
|
||||
.msg-body :not(pre)>code{background:rgba(124,106,255,.1);color:var(--accent);padding:2px 7px;border-radius:5px;border:1px solid rgba(124,106,255,.15)}
|
||||
.msg-body a{color:var(--accent);text-decoration:none}
|
||||
.msg-body a:hover{text-decoration:underline}
|
||||
.code-wrap{position:relative}
|
||||
.code-lang{position:absolute;top:0;left:14px;background:linear-gradient(135deg,var(--accent),var(--accent2));color:#fff;padding:2px 10px;border-radius:0 0 6px 6px;font-size:9px;font-family:'SF Mono',monospace;text-transform:uppercase;letter-spacing:.5px;font-weight:600}
|
||||
.copy-btn{position:absolute;top:8px;right:8px;background:var(--surface2);border:1px solid var(--border2);color:var(--text-dim);padding:4px 12px;border-radius:6px;font-size:10px;cursor:pointer;opacity:0;transition:all .3s;font-family:inherit;z-index:1;backdrop-filter:blur(8px)}
|
||||
.code-wrap:hover .copy-btn{opacity:1}.copy-btn:hover{background:var(--accent);color:#fff;border-color:var(--accent);box-shadow:0 0 12px var(--accent-glow)}
|
||||
.copy-btn.copied{background:var(--green);color:#fff;border-color:var(--green);opacity:1}
|
||||
|
||||
/* BADGES */
|
||||
.file-badge{background:rgba(255,171,64,.05);border:1px solid rgba(255,171,64,.2);border-radius:10px 10px 0 0;border-bottom:none;padding:8px 14px;font-size:11px;font-weight:700;color:var(--yellow);display:flex;align-items:center;gap:6px;backdrop-filter:blur(8px)}
|
||||
.edit-badge{background:rgba(0,229,255,.05);border:1px solid rgba(0,229,255,.2);border-radius:10px 10px 0 0;border-bottom:none;padding:8px 14px;font-size:11px;font-weight:700;color:var(--cyan);display:flex;align-items:center;gap:6px;backdrop-filter:blur(8px)}
|
||||
.cmd-badge{background:rgba(124,106,255,.05);border:1px solid rgba(124,106,255,.25);border-radius:10px;padding:10px 14px;margin:8px 0;font-size:12px;color:var(--accent);font-family:'SF Mono','Menlo',monospace;display:flex;align-items:center;gap:8px;backdrop-filter:blur(8px)}
|
||||
.msg-error .msg-body{color:var(--red);text-shadow:0 0 20px rgba(255,82,82,.2)}
|
||||
|
||||
/* WELCOME */
|
||||
.welcome{text-align:center;padding:0 20px 20px;position:relative}
|
||||
.welcome-logo{width:56px;height:56px;border-radius:16px;margin:0 auto 16px;background:linear-gradient(135deg,var(--accent),var(--accent2),var(--accent3));display:flex;align-items:center;justify-content:center;font-size:28px;color:#fff;box-shadow:0 0 40px rgba(124,106,255,.35),0 0 80px rgba(224,64,251,.15);animation:welcomeFloat 4s ease-in-out infinite;position:relative}
|
||||
.welcome-logo::before{content:'';position:absolute;inset:-4px;border-radius:20px;background:conic-gradient(from 0deg,var(--accent),var(--accent2),var(--accent3),var(--accent));opacity:.2;filter:blur(8px);animation:spin 8s linear infinite}
|
||||
@keyframes spin{to{transform:rotate(360deg)}}
|
||||
@keyframes welcomeFloat{0%,100%{transform:translateY(0) scale(1)}50%{transform:translateY(-6px) scale(1.03)}}
|
||||
.welcome-title{font-size:22px;font-weight:900;letter-spacing:-1px;background:linear-gradient(135deg,#fff,var(--accent),var(--accent2));background-size:200% 200%;-webkit-background-clip:text;-webkit-text-fill-color:transparent;animation:gradText 5s ease infinite;margin-bottom:8px}
|
||||
@keyframes gradText{0%,100%{background-position:0% 50%}50%{background-position:100% 50%}}
|
||||
.welcome-sub{color:var(--text-dim);font-size:12px;line-height:1.7;margin-bottom:18px;letter-spacing:-.2px}
|
||||
|
||||
/* LOADING */
|
||||
.loading-wrap{padding-left:29px;padding-top:6px;display:flex;align-items:center;gap:10px}
|
||||
.loading-dots{display:flex;gap:4px}
|
||||
.loading-dots span{width:6px;height:6px;border-radius:50%;background:var(--accent);animation:dotBounce 1.4s ease-in-out infinite}
|
||||
.loading-dots span:nth-child(2){animation-delay:.2s;background:var(--accent2)}
|
||||
.loading-dots span:nth-child(3){animation-delay:.4s;background:var(--accent3)}
|
||||
@keyframes dotBounce{0%,80%,100%{transform:scale(.6);opacity:.4}40%{transform:scale(1.2);opacity:1}}
|
||||
.loading-text{font-size:11px;color:var(--text-dim);animation:pulse 2s ease-in-out infinite;letter-spacing:.3px}
|
||||
|
||||
/* INPUT */
|
||||
.input-wrap{padding:8px 14px 14px;flex-shrink:0;position:relative;z-index:1}
|
||||
.input-box{background:var(--input-bg);border:1px solid var(--border2);border-radius:14px;padding:12px 14px;display:flex;flex-direction:column;gap:8px;transition:all .3s;position:relative;backdrop-filter:blur(12px)}
|
||||
.input-box:focus-within{border-color:rgba(124,106,255,.4);box-shadow:0 0 24px rgba(124,106,255,.12);animation:focusPulse 3s infinite}
|
||||
@keyframes focusPulse{0%,100%{box-shadow:0 0 20px rgba(124,106,255,.08)}50%{box-shadow:0 0 28px rgba(124,106,255,.18)}}
|
||||
textarea{width:100%;background:transparent;border:none;color:var(--text-bright);font-family:inherit;font-size:13px;line-height:1.5;resize:none;outline:none;min-height:22px;max-height:150px}
|
||||
textarea::placeholder{color:var(--text-dim)}
|
||||
.input-footer{display:flex;align-items:center;justify-content:space-between}
|
||||
.input-hint{font-size:10px;color:var(--text-dim);opacity:.5}
|
||||
.input-btns{display:flex;gap:5px}
|
||||
.send-btn{background:linear-gradient(135deg,var(--accent),var(--accent2));border:none;color:#fff;width:32px;height:32px;border-radius:10px;cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:14px;transition:all .2s;box-shadow:0 2px 12px rgba(124,106,255,.35);position:relative;overflow:hidden}
|
||||
.send-btn::after{content:'';position:absolute;inset:0;background:linear-gradient(135deg,transparent,rgba(255,255,255,.15));opacity:0;transition:opacity .3s}
|
||||
.send-btn:hover{transform:translateY(-2px) scale(1.05);box-shadow:0 6px 24px rgba(124,106,255,.45)}
|
||||
.send-btn:hover::after{opacity:1}
|
||||
.send-btn:active{transform:scale(.92)}.send-btn:disabled{opacity:.2;cursor:not-allowed;transform:none;box-shadow:none}
|
||||
.stop-btn{background:var(--red);border:none;color:#fff;width:32px;height:32px;border-radius:10px;cursor:pointer;display:none;align-items:center;justify-content:center;font-size:11px;box-shadow:0 0 12px rgba(255,82,82,.3)}
|
||||
.stop-btn.visible{display:flex}
|
||||
@keyframes msgIn{from{opacity:0;transform:translateY(12px) scale(.97)}to{opacity:1;transform:translateY(0) scale(1)}}
|
||||
@keyframes pulse{0%,100%{opacity:.4}50%{opacity:1}}
|
||||
.stream-active{position:relative}
|
||||
.stream-active::after{content:'';display:inline-block;width:2px;height:14px;background:var(--accent);margin-left:2px;animation:blink .6s step-end infinite;vertical-align:text-bottom;border-radius:1px;box-shadow:0 0 6px var(--accent)}
|
||||
@keyframes blink{0%,100%{opacity:1}50%{opacity:0}}
|
||||
.stream-active .code-wrap:last-child {
|
||||
border: 1px solid var(--accent);
|
||||
animation: codePulse 2s infinite;
|
||||
}
|
||||
.stream-active .code-wrap:last-child pre {
|
||||
box-shadow: inset 0 0 20px rgba(124,106,255,0.05);
|
||||
}
|
||||
@keyframes codePulse {
|
||||
0%, 100% { box-shadow: 0 0 15px var(--accent-glow); }
|
||||
50% { box-shadow: 0 0 35px var(--accent2-glow); border-color: var(--accent2); }
|
||||
}
|
||||
.main-view{flex:1;display:flex;flex-direction:column;overflow:hidden;transition:all .5s cubic-bezier(.16,1,.3,1)}
|
||||
body.init .main-view{justify-content:center;margin-top:-6vh}
|
||||
body.init .chat{flex:0 0 auto;overflow:visible;padding-bottom:15px}
|
||||
body.init .input-wrap{max-width:680px;width:100%;margin:0 auto;transform:none;transition:all .5s cubic-bezier(.16,1,.3,1)}
|
||||
|
||||
/* ATTACHMENT */
|
||||
.attach-btn{background:transparent;border:1px solid var(--border2);color:var(--text-dim);width:32px;height:32px;border-radius:10px;cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:16px;transition:all .3s;flex-shrink:0}
|
||||
.attach-btn:hover{color:var(--accent);border-color:var(--accent);box-shadow:0 0 12px var(--accent-glow);transform:translateY(-1px)}
|
||||
.attach-preview{display:none;gap:6px;padding:0 0 6px;flex-wrap:wrap}
|
||||
.attach-preview.visible{display:flex}
|
||||
.attach-chip{display:flex;align-items:center;gap:5px;background:var(--surface2);border:1px solid var(--border2);border-radius:8px;padding:4px 10px;font-size:10px;color:var(--text);animation:msgIn .3s ease}
|
||||
.attach-chip .chip-icon{font-size:12px}
|
||||
.attach-chip .chip-name{max-width:100px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
||||
.attach-chip .chip-remove{cursor:pointer;color:var(--text-dim);font-size:12px;margin-left:2px;transition:color .2s}
|
||||
.attach-chip .chip-remove:hover{color:var(--red)}
|
||||
.attach-thumb{width:28px;height:28px;border-radius:5px;object-fit:cover;border:1px solid var(--border2)}
|
||||
|
||||
/* REGENERATE BUTTON */
|
||||
.regen-btn{display:inline-flex;align-items:center;gap:4px;background:transparent;border:1px solid var(--border2);color:var(--text-dim);padding:4px 12px;border-radius:8px;font-size:10px;cursor:pointer;transition:all .3s;font-family:inherit;margin-top:6px;margin-left:29px}
|
||||
.regen-btn:hover{color:var(--accent);border-color:var(--accent);box-shadow:0 0 12px var(--accent-glow)}
|
||||
|
||||
/* SYNTAX HIGHLIGHTING */
|
||||
.msg-body pre .kw{color:#c792ea}
|
||||
.msg-body pre .str{color:#c3e88d}
|
||||
.msg-body pre .num{color:#f78c6c}
|
||||
.msg-body pre .cm{color:#546e7a;font-style:italic}
|
||||
.msg-body pre .fn{color:#82aaff}
|
||||
.msg-body pre .tag{color:#f07178}
|
||||
.msg-body pre .attr{color:#ffcb6b}
|
||||
.msg-body pre .op{color:#89ddff}
|
||||
.msg-body pre .type{color:#ffcb6b}
|
||||
</style></head><body class="init">
|
||||
<div class="header"><div class="header-left"><div class="logo">\u2726</div><span class="brand">Connect AI</span></div><div class="header-right"><select id="modelSel"></select><button class="btn-icon" id="brainBtn" title="Second Brain">\u{1F9E0}</button><button class="btn-icon" id="settingsBtn" title="Settings">\u2699\uFE0F</button><button class="btn-icon" id="newChatBtn" title="New Chat">+</button></div></div>
|
||||
<div class="thinking-bar" id="thinkingBar"></div>
|
||||
<div class="main-view" id="mainView">
|
||||
<div class="chat" id="chat">
|
||||
<div class="welcome">
|
||||
<div class="welcome-logo">\u2726</div>
|
||||
<div class="welcome-title">Connect AI</div>
|
||||
<div class="welcome-sub">\uBCF4\uC548 \xB7 \uBE44\uC6A9\uCD5C\uC801\uD654 \xB7 \uC9C0\uC2DD\uC5F0\uACB0<br>\uD504\uB85C\uC81D\uD2B8\uB97C \uC774\uD574\uD558\uACE0, \uCF54\uB4DC\uB97C \uC791\uC131\uD558\uACE0, \uC2E4\uD589\uD569\uB2C8\uB2E4.</div>
|
||||
</div></div>
|
||||
<div class="input-wrap"><div class="input-box">
|
||||
<div class="attach-preview" id="attachPreview"></div>
|
||||
<textarea id="input" rows="1" placeholder="\uBB34\uC5C7\uC744 \uB9CC\uB4E4\uC5B4 \uB4DC\uB9B4\uAE4C\uC694?"></textarea>
|
||||
<div class="input-footer"><span class="input-hint">Enter \uC804\uC1A1 \xB7 Shift+Enter \uC904\uBC14\uAFC8</span>
|
||||
<div class="input-btns"><button class="attach-btn" id="attachBtn" title="\uD30C\uC77C \uCCA8\uBD80">+</button><button class="stop-btn" id="stopBtn">\u25A0</button><button class="send-btn" id="sendBtn">\u2191</button></div></div></div>
|
||||
<input type="file" id="fileInput" multiple accept="image/*,audio/*,.txt,.md,.csv,.json,.js,.ts,.html,.css,.py,.java,.rs,.go,.yaml,.yml,.xml,.toml" hidden></div>
|
||||
</div>
|
||||
<script>
|
||||
window.onerror = function(msg, url, line, col, error) {
|
||||
document.body.innerHTML += '<div style="position:absolute;z-index:9999;background:red;color:white;padding:10px;top:0;left:0;right:0">ERROR: ' + msg + ' at line ' + line + '</div>';
|
||||
};
|
||||
window.addEventListener('unhandledrejection', function(event) {
|
||||
document.body.innerHTML += '<div style="position:absolute;z-index:9999;background:red;color:white;padding:10px;bottom:0;left:0;right:0">PROMISE REJECTION: ' + event.reason + '</div>';
|
||||
});
|
||||
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"),'<span class="cm">$1</span>');
|
||||
h=h.replace(new RegExp("(#[^\\\\n]*)", "g"),'<span class="cm">$1</span>');
|
||||
h=h.replace(new RegExp("(\\\\/\\\\*[\\\\s\\\\S]*?\\\\*\\\\/)", "g"),'<span class="cm">$1</span>');
|
||||
h=h.replace(/("[^&]*?"|'[^&]*?')/g,'<span class="str">$1</span>');
|
||||
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"),'<span class="kw">$1</span>');
|
||||
h=h.replace(new RegExp("\\\\b(\\\\d+\\\\.?\\\\d*)\\\\b", "g"),'<span class="num">$1</span>');
|
||||
h=h.replace(new RegExp("\\\\b(True|False|None|true|false|null|undefined|NaN)\\\\b", "g"),'<span class="num">$1</span>');
|
||||
h=h.replace(new RegExp("\\\\b(String|Number|Boolean|Array|Object|Map|Set|Promise|void|int|float|str|list|dict|tuple)\\\\b", "g"),'<span class="type">$1</span>');
|
||||
h=h.replace(/([=!+*/%|&^~?:-]+)/g,'<span class="op">$1</span>');
|
||||
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('<create_file') > t.lastIndexOf('</create_file>')) t += '</create_file>';
|
||||
if(t.lastIndexOf('<edit_file') > t.lastIndexOf('</edit_file>')) t += '</edit_file>';
|
||||
if(t.lastIndexOf('<run_command') > t.lastIndexOf('</run_command>')) t += '</run_command>';
|
||||
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(/<create_file\\s+path="([^"]+)">([\\s\\S]*?)<\\/create_file>/g,(_,p,c)=>pushB('<div class="file-badge">\u{1F4C1} '+esc(p)+' \u2014 \uC790\uB3D9 \uC0DD\uC131\uB428</div><div class="code-wrap"><pre><code>'+esc(c)+'</code></pre><button class="copy-btn" onclick="copyCode(this)">Copy</button></div>'));
|
||||
t=t.replace(/<edit_file\\s+path="([^"]+)">([\\s\\S]*?)<\\/edit_file>/g,(_,p,c)=>pushB('<div class="edit-badge">\u270F\uFE0F '+esc(p)+' \u2014 \uD3B8\uC9D1\uB428</div><div class="code-wrap"><pre><code>'+esc(c)+'</code></pre><button class="copy-btn" onclick="copyCode(this)">Copy</button></div>'));
|
||||
t=t.replace(/<run_command>([\\s\\S]*?)<\\/run_command>/g,(_,c)=>pushB('<div class="cmd-badge">\u25B6 '+esc(c)+'</div>'));
|
||||
t=t.replace(/\`\`\`(\\w*)\\n([\\s\\S]*?)\`\`\`/g,(_,lang,c)=>{const l=lang||'code';return pushB('<div class="code-wrap"><span class="code-lang">'+esc(l)+'</span><pre><code>'+highlight(c,l)+'</code></pre><button class="copy-btn" onclick="copyCode(this)">Copy</button></div>');});
|
||||
t=t.replace(/\`([^\`]+)\`/g,(_,c)=>pushB('<code>'+esc(c)+'</code>'));
|
||||
t=esc(t);
|
||||
t=t.replace(/\\*\\*([^*]+)\\*\\*/g,'<strong>$1</strong>');
|
||||
t=t.replace(/\\[([^\\]]+)\\]\\(([^)]+)\\)/g, '<a href="$2" target="_blank">$1</a>');
|
||||
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?'<div class="av av-user">\u{1F464}</div><span>You</span>':'<div class="av av-ai">\u2726</div><span>Connect AI</span>')+'<span class="msg-time">'+getTime()+'</span>';
|
||||
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='<div class="msg-head"><div class="av av-ai">\u2726</div><span>Connect AI</span><span class="msg-time">'+getTime()+'</span></div><div class="loading-wrap"><div class="loading-dots"><span></span><span></span><span></span></div><span class="loading-text">\uC0DD\uAC01\uD558\uB294 \uC911...</span></div>';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='<div class="av av-ai">\u2726</div><span>Connect AI</span><span class="msg-time">'+getTime()+'</span>';
|
||||
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='<div class="welcome"><div class="welcome-logo">\u2726</div><div class="welcome-title">Connect AI</div><div class="welcome-sub">\uBCF4\uC548 \xB7 \uBE44\uC6A9\uCD5C\uC801\uD654 \xB7 \uC9C0\uC2DD\uC5F0\uACB0<br>\uD504\uB85C\uC81D\uD2B8\uB97C \uC774\uD574\uD558\uACE0, \uCF54\uB4DC\uB97C \uC791\uC131\uD558\uACE0, \uC2E4\uD589\uD569\uB2C8\uB2E4.</div></div>';
|
||||
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='<div class="welcome"><div class="welcome-logo">\u2726</div><div class="welcome-title">Connect AI</div><div class="welcome-sub">\uBCF4\uC548 \xB7 \uBE44\uC6A9\uCD5C\uC801\uD654 \xB7 \uC9C0\uC2DD\uC5F0\uACB0<br>\uD504\uB85C\uC81D\uD2B8\uB97C \uC774\uD574\uD558\uACE0, \uCF54\uB4DC\uB97C \uC791\uC131\uD558\uACE0, \uC2E4\uD589\uD569\uB2C8\uB2E4.</div></div>';
|
||||
}
|
||||
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 = '<div style="color:#ff4444;padding:20px;background:#111;height:100%;font-size:14px;overflow:auto;"><h2>\u26A0\uFE0F WEBVIEW JS CRASH</h2><pre>' + err.name + ': ' + err.message + '\\n' + err.stack + '</pre></div>';
|
||||
}
|
||||
</script></body></html>
|
||||
@@ -1,18 +0,0 @@
|
||||
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');
|
||||
}
|
||||
@@ -1,191 +0,0 @@
|
||||
|
||||
window.onerror = function(msg, url, line, col, error) {
|
||||
document.body.innerHTML += '<div style="position:absolute;z-index:9999;background:red;color:white;padding:10px;top:0;left:0;right:0">ERROR: ' + msg + ' at line ' + line + '</div>';
|
||||
};
|
||||
window.addEventListener('unhandledrejection', function(event) {
|
||||
document.body.innerHTML += '<div style="position:absolute;z-index:9999;background:red;color:white;padding:10px;bottom:0;left:0;right:0">PROMISE REJECTION: ' + event.reason + '</div>';
|
||||
});
|
||||
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"),'<span class="cm">$1</span>');
|
||||
h=h.replace(new RegExp("(#[^\\\\n]*)", "g"),'<span class="cm">$1</span>');
|
||||
h=h.replace(new RegExp("(\\\\/\\\\*[\\\\s\\\\S]*?\\\\*\\\\/)", "g"),'<span class="cm">$1</span>');
|
||||
h=h.replace(/("[^&]*?"|'[^&]*?')/g,'<span class="str">$1</span>');
|
||||
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"),'<span class="kw">$1</span>');
|
||||
h=h.replace(new RegExp("\\\\b(\\\\d+\\\\.?\\\\d*)\\\\b", "g"),'<span class="num">$1</span>');
|
||||
h=h.replace(new RegExp("\\\\b(True|False|None|true|false|null|undefined|NaN)\\\\b", "g"),'<span class="num">$1</span>');
|
||||
h=h.replace(new RegExp("\\\\b(String|Number|Boolean|Array|Object|Map|Set|Promise|void|int|float|str|list|dict|tuple)\\\\b", "g"),'<span class="type">$1</span>');
|
||||
h=h.replace(/([=!+*/%|&^~?:-]+)/g,'<span class="op">$1</span>');
|
||||
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('<create_file') > t.lastIndexOf('</create_file>')) t += '</create_file>';
|
||||
if(t.lastIndexOf('<edit_file') > t.lastIndexOf('</edit_file>')) t += '</edit_file>';
|
||||
if(t.lastIndexOf('<run_command') > t.lastIndexOf('</run_command>')) t += '</run_command>';
|
||||
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(/<create_file\\s+path="([^"]+)">([\\s\\S]*?)<\\/create_file>/g,(_,p,c)=>pushB('<div class="file-badge">\u{1F4C1} '+esc(p)+' \u2014 \uC790\uB3D9 \uC0DD\uC131\uB428</div><div class="code-wrap"><pre><code>'+esc(c)+'</code></pre><button class="copy-btn" onclick="copyCode(this)">Copy</button></div>'));
|
||||
t=t.replace(/<edit_file\\s+path="([^"]+)">([\\s\\S]*?)<\\/edit_file>/g,(_,p,c)=>pushB('<div class="edit-badge">\u270F\uFE0F '+esc(p)+' \u2014 \uD3B8\uC9D1\uB428</div><div class="code-wrap"><pre><code>'+esc(c)+'</code></pre><button class="copy-btn" onclick="copyCode(this)">Copy</button></div>'));
|
||||
t=t.replace(/<run_command>([\\s\\S]*?)<\\/run_command>/g,(_,c)=>pushB('<div class="cmd-badge">\u25B6 '+esc(c)+'</div>'));
|
||||
t=t.replace(/\`\`\`(\\w*)\\n([\\s\\S]*?)\`\`\`/g,(_,lang,c)=>{const l=lang||'code';return pushB('<div class="code-wrap"><span class="code-lang">'+esc(l)+'</span><pre><code>'+highlight(c,l)+'</code></pre><button class="copy-btn" onclick="copyCode(this)">Copy</button></div>');});
|
||||
t=t.replace(/\`([^\`]+)\`/g,(_,c)=>pushB('<code>'+esc(c)+'</code>'));
|
||||
t=esc(t);
|
||||
t=t.replace(/\\*\\*([^*]+)\\*\\*/g,'<strong>$1</strong>');
|
||||
t=t.replace(/\\[([^\\]]+)\\]\\(([^)]+)\\)/g, '<a href="$2" target="_blank">$1</a>');
|
||||
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?'<div class="av av-user">\u{1F464}</div><span>You</span>':'<div class="av av-ai">\u2726</div><span>Connect AI</span>')+'<span class="msg-time">'+getTime()+'</span>';
|
||||
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='<div class="msg-head"><div class="av av-ai">\u2726</div><span>Connect AI</span><span class="msg-time">'+getTime()+'</span></div><div class="loading-wrap"><div class="loading-dots"><span></span><span></span><span></span></div><span class="loading-text">\uC0DD\uAC01\uD558\uB294 \uC911...</span></div>';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='<div class="av av-ai">\u2726</div><span>Connect AI</span><span class="msg-time">'+getTime()+'</span>';
|
||||
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='<div class="welcome"><div class="welcome-logo">\u2726</div><div class="welcome-title">Connect AI</div><div class="welcome-sub">\uBCF4\uC548 \xB7 \uBE44\uC6A9\uCD5C\uC801\uD654 \xB7 \uC9C0\uC2DD\uC5F0\uACB0<br>\uD504\uB85C\uC81D\uD2B8\uB97C \uC774\uD574\uD558\uACE0, \uCF54\uB4DC\uB97C \uC791\uC131\uD558\uACE0, \uC2E4\uD589\uD569\uB2C8\uB2E4.</div></div>';
|
||||
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='<div class="welcome"><div class="welcome-logo">\u2726</div><div class="welcome-title">Connect AI</div><div class="welcome-sub">\uBCF4\uC548 \xB7 \uBE44\uC6A9\uCD5C\uC801\uD654 \xB7 \uC9C0\uC2DD\uC5F0\uACB0<br>\uD504\uB85C\uC81D\uD2B8\uB97C \uC774\uD574\uD558\uACE0, \uCF54\uB4DC\uB97C \uC791\uC131\uD558\uACE0, \uC2E4\uD589\uD569\uB2C8\uB2E4.</div></div>';
|
||||
}
|
||||
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 = '<div style="color:#ff4444;padding:20px;background:#111;height:100%;font-size:14px;overflow:auto;"><h2>\u26A0\uFE0F WEBVIEW JS CRASH</h2><pre>' + err.name + ': ' + err.message + '\\n' + err.stack + '</pre></div>';
|
||||
}
|
||||
@@ -1,367 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ko"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<title>Connect AI</title>
|
||||
<style>
|
||||
*{margin:0;padding:0;box-sizing:border-box}
|
||||
:root{
|
||||
--bg:#0a0a0c;--bg2:#111114;--surface:rgba(22,22,28,.75);--surface2:rgba(38,38,46,.6);
|
||||
--border:rgba(255,255,255,.06);--border2:rgba(255,255,255,.1);
|
||||
--text:#b0b0be;--text-bright:#f0f0f5;--text-dim:#55556a;
|
||||
--accent:#7c6aff;--accent2:#e040fb;--accent3:#00e5ff;
|
||||
--accent-glow:rgba(124,106,255,.2);--accent2-glow:rgba(224,64,251,.15);
|
||||
--input-bg:rgba(14,14,18,.9);--code-bg:#08080c;
|
||||
--green:#00e676;--yellow:#ffab40;--cyan:#00e5ff;--red:#ff5252;
|
||||
}
|
||||
html,body{height:100%;font-family:'SF Pro Display',-apple-system,BlinkMacSystemFont,'Segoe UI',system-ui,sans-serif;font-size:13px;background:var(--bg);color:var(--text);display:flex;flex-direction:column;overflow:hidden}
|
||||
|
||||
/* AURORA BACKGROUND */
|
||||
body::before{content:'';position:fixed;top:-50%;left:-50%;width:200%;height:200%;background:radial-gradient(ellipse at 20% 50%,rgba(124,106,255,.06) 0%,transparent 50%),radial-gradient(ellipse at 80% 20%,rgba(224,64,251,.04) 0%,transparent 50%),radial-gradient(ellipse at 50% 80%,rgba(0,229,255,.03) 0%,transparent 50%);animation:aurora 20s ease-in-out infinite;z-index:0;pointer-events:none}
|
||||
@keyframes aurora{0%,100%{transform:translate(0,0) rotate(0deg)}33%{transform:translate(2%,-1%) rotate(.5deg)}66%{transform:translate(-1%,2%) rotate(-.5deg)}}
|
||||
|
||||
/* HEADER */
|
||||
.header{display:flex;align-items:center;justify-content:space-between;padding:10px 14px;background:rgba(10,10,12,.8);backdrop-filter:blur(20px);-webkit-backdrop-filter:blur(20px);border-bottom:1px solid var(--border);flex-shrink:0;position:relative;z-index:10}
|
||||
.header::after{content:'';position:absolute;bottom:0;left:0;right:0;height:1px;background:linear-gradient(90deg,transparent 5%,var(--accent) 30%,var(--accent2) 50%,var(--accent3) 70%,transparent 95%);opacity:.5;animation:headerGlow 4s ease-in-out infinite alternate}
|
||||
@keyframes headerGlow{0%{opacity:.3}100%{opacity:.6}}
|
||||
.thinking-bar{height:2px;background:transparent;position:relative;overflow:hidden;flex-shrink:0;z-index:10}
|
||||
.thinking-bar.active{background:rgba(124,106,255,.1)}
|
||||
.thinking-bar.active::after{content:'';position:absolute;top:0;left:-40%;width:40%;height:100%;background:linear-gradient(90deg,transparent,var(--accent),var(--accent2),var(--accent3),transparent);animation:thinkSlide 1.5s ease-in-out infinite}
|
||||
@keyframes thinkSlide{0%{left:-40%}100%{left:100%}}
|
||||
.header-left{display:flex;align-items:center;gap:8px}
|
||||
.logo{width:26px;height:26px;border-radius:8px;background:linear-gradient(135deg,var(--accent),var(--accent2));display:flex;align-items:center;justify-content:center;font-size:14px;color:#fff;box-shadow:0 0 15px rgba(124,106,255,.4),0 0 30px rgba(224,64,251,.15);animation:logoPulse 3s ease-in-out infinite;position:relative}
|
||||
.logo::after{content:'';position:absolute;inset:-2px;border-radius:10px;background:linear-gradient(135deg,var(--accent),var(--accent2),var(--accent3));opacity:.3;filter:blur(4px);animation:logoPulse 3s ease-in-out infinite}
|
||||
@keyframes logoPulse{0%,100%{box-shadow:0 0 15px rgba(124,106,255,.4),0 0 30px rgba(224,64,251,.15)}50%{box-shadow:0 0 20px rgba(124,106,255,.6),0 0 40px rgba(224,64,251,.25)}}
|
||||
.brand{font-weight:800;font-size:14px;color:var(--text-bright);letter-spacing:-.5px;background:linear-gradient(135deg,#fff 40%,var(--accent) 100%);-webkit-background-clip:text;-webkit-text-fill-color:transparent}
|
||||
.header-right{display:flex;align-items:center;gap:5px}
|
||||
select{background:rgba(22,22,28,.9);color:var(--text-bright);border:1px solid var(--border2);padding:5px 8px;border-radius:8px;font-size:10px;font-family:inherit;cursor:pointer;outline:none;max-width:120px;transition:all .3s;backdrop-filter:blur(8px)}
|
||||
select:hover,select:focus{border-color:var(--accent);box-shadow:0 0 12px var(--accent-glow)}
|
||||
.btn-icon{background:rgba(22,22,28,.7);border:1px solid var(--border2);color:var(--text-dim);width:28px;height:28px;border-radius:8px;font-size:13px;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all .3s;backdrop-filter:blur(8px);position:relative;overflow:hidden}
|
||||
.btn-icon::before{content:'';position:absolute;inset:0;background:linear-gradient(135deg,var(--accent-glow),var(--accent2-glow));opacity:0;transition:opacity .3s}
|
||||
.btn-icon:hover{color:var(--text-bright);border-color:var(--accent);transform:translateY(-1px);box-shadow:0 4px 15px var(--accent-glow)}
|
||||
.btn-icon:hover::before{opacity:1}
|
||||
|
||||
/* CHAT */
|
||||
.chat{flex:1;overflow-y:auto;padding:16px 14px;display:flex;flex-direction:column;gap:16px;position:relative;z-index:1}
|
||||
.chat::-webkit-scrollbar{width:2px}.chat::-webkit-scrollbar-track{background:transparent}.chat::-webkit-scrollbar-thumb{background:var(--accent);border-radius:2px;opacity:.5}
|
||||
|
||||
/* MESSAGES */
|
||||
.msg{display:flex;flex-direction:column;gap:5px;animation:msgIn .5s cubic-bezier(.16,1,.3,1)}
|
||||
.msg-head{display:flex;align-items:center;gap:7px;font-weight:600;font-size:11px;color:var(--text)}
|
||||
.msg-time{font-weight:400;font-size:9px;color:var(--text-dim);margin-left:auto;opacity:.6}
|
||||
.av{width:22px;height:22px;border-radius:7px;display:flex;align-items:center;justify-content:center;font-size:11px;flex-shrink:0}
|
||||
.av-user{background:var(--surface2);color:var(--text);border:1px solid var(--border2)}
|
||||
.av-ai{background:linear-gradient(135deg,var(--accent),var(--accent2));color:#fff;box-shadow:0 0 10px rgba(124,106,255,.3)}
|
||||
.msg-body{padding-left:29px;line-height:1.75;color:var(--text);white-space:pre-wrap;word-break:break-word;font-size:13px}
|
||||
.msg-user .msg-body{background:var(--surface);border:1px solid var(--border2);border-radius:14px;padding:10px 14px;margin-left:29px;color:var(--text-bright);backdrop-filter:blur(8px)}
|
||||
.msg-body pre{background:var(--code-bg);border:1px solid var(--border2);border-radius:10px;padding:14px 16px;overflow-x:auto;margin:8px 0;font-size:12px;line-height:1.6;color:#c9d1d9;position:relative}
|
||||
.msg-body pre::-webkit-scrollbar{height:6px}
|
||||
.msg-body pre::-webkit-scrollbar-track{background:rgba(0,0,0,.2);border-radius:4px}
|
||||
.msg-body pre::-webkit-scrollbar-thumb{background:rgba(124,106,255,.3);border-radius:4px}
|
||||
.msg-body pre::-webkit-scrollbar-thumb:hover{background:rgba(124,106,255,.6)}
|
||||
.msg-body code{font-family:'SF Mono','JetBrains Mono','Fira Code','Menlo',monospace;font-size:11.5px}
|
||||
.msg-body :not(pre)>code{background:rgba(124,106,255,.1);color:var(--accent);padding:2px 7px;border-radius:5px;border:1px solid rgba(124,106,255,.15)}
|
||||
.msg-body a{color:var(--accent);text-decoration:none}
|
||||
.msg-body a:hover{text-decoration:underline}
|
||||
.code-wrap{position:relative}
|
||||
.code-lang{position:absolute;top:0;left:14px;background:linear-gradient(135deg,var(--accent),var(--accent2));color:#fff;padding:2px 10px;border-radius:0 0 6px 6px;font-size:9px;font-family:'SF Mono',monospace;text-transform:uppercase;letter-spacing:.5px;font-weight:600}
|
||||
.copy-btn{position:absolute;top:8px;right:8px;background:var(--surface2);border:1px solid var(--border2);color:var(--text-dim);padding:4px 12px;border-radius:6px;font-size:10px;cursor:pointer;opacity:0;transition:all .3s;font-family:inherit;z-index:1;backdrop-filter:blur(8px)}
|
||||
.code-wrap:hover .copy-btn{opacity:1}.copy-btn:hover{background:var(--accent);color:#fff;border-color:var(--accent);box-shadow:0 0 12px var(--accent-glow)}
|
||||
.copy-btn.copied{background:var(--green);color:#fff;border-color:var(--green);opacity:1}
|
||||
|
||||
/* BADGES */
|
||||
.file-badge{background:rgba(255,171,64,.05);border:1px solid rgba(255,171,64,.2);border-radius:10px 10px 0 0;border-bottom:none;padding:8px 14px;font-size:11px;font-weight:700;color:var(--yellow);display:flex;align-items:center;gap:6px;backdrop-filter:blur(8px)}
|
||||
.edit-badge{background:rgba(0,229,255,.05);border:1px solid rgba(0,229,255,.2);border-radius:10px 10px 0 0;border-bottom:none;padding:8px 14px;font-size:11px;font-weight:700;color:var(--cyan);display:flex;align-items:center;gap:6px;backdrop-filter:blur(8px)}
|
||||
.cmd-badge{background:rgba(124,106,255,.05);border:1px solid rgba(124,106,255,.25);border-radius:10px;padding:10px 14px;margin:8px 0;font-size:12px;color:var(--accent);font-family:'SF Mono','Menlo',monospace;display:flex;align-items:center;gap:8px;backdrop-filter:blur(8px)}
|
||||
.msg-error .msg-body{color:var(--red);text-shadow:0 0 20px rgba(255,82,82,.2)}
|
||||
|
||||
/* WELCOME */
|
||||
.welcome{text-align:center;padding:0 20px 20px;position:relative}
|
||||
.welcome-logo{width:56px;height:56px;border-radius:16px;margin:0 auto 16px;background:linear-gradient(135deg,var(--accent),var(--accent2),var(--accent3));display:flex;align-items:center;justify-content:center;font-size:28px;color:#fff;box-shadow:0 0 40px rgba(124,106,255,.35),0 0 80px rgba(224,64,251,.15);animation:welcomeFloat 4s ease-in-out infinite;position:relative}
|
||||
.welcome-logo::before{content:'';position:absolute;inset:-4px;border-radius:20px;background:conic-gradient(from 0deg,var(--accent),var(--accent2),var(--accent3),var(--accent));opacity:.2;filter:blur(8px);animation:spin 8s linear infinite}
|
||||
@keyframes spin{to{transform:rotate(360deg)}}
|
||||
@keyframes welcomeFloat{0%,100%{transform:translateY(0) scale(1)}50%{transform:translateY(-6px) scale(1.03)}}
|
||||
.welcome-title{font-size:22px;font-weight:900;letter-spacing:-1px;background:linear-gradient(135deg,#fff,var(--accent),var(--accent2));background-size:200% 200%;-webkit-background-clip:text;-webkit-text-fill-color:transparent;animation:gradText 5s ease infinite;margin-bottom:8px}
|
||||
@keyframes gradText{0%,100%{background-position:0% 50%}50%{background-position:100% 50%}}
|
||||
.welcome-sub{color:var(--text-dim);font-size:12px;line-height:1.7;margin-bottom:18px;letter-spacing:-.2px}
|
||||
|
||||
/* LOADING */
|
||||
.loading-wrap{padding-left:29px;padding-top:6px;display:flex;align-items:center;gap:10px}
|
||||
.loading-dots{display:flex;gap:4px}
|
||||
.loading-dots span{width:6px;height:6px;border-radius:50%;background:var(--accent);animation:dotBounce 1.4s ease-in-out infinite}
|
||||
.loading-dots span:nth-child(2){animation-delay:.2s;background:var(--accent2)}
|
||||
.loading-dots span:nth-child(3){animation-delay:.4s;background:var(--accent3)}
|
||||
@keyframes dotBounce{0%,80%,100%{transform:scale(.6);opacity:.4}40%{transform:scale(1.2);opacity:1}}
|
||||
.loading-text{font-size:11px;color:var(--text-dim);animation:pulse 2s ease-in-out infinite;letter-spacing:.3px}
|
||||
|
||||
/* INPUT */
|
||||
.input-wrap{padding:8px 14px 14px;flex-shrink:0;position:relative;z-index:1}
|
||||
.input-box{background:var(--input-bg);border:1px solid var(--border2);border-radius:14px;padding:12px 14px;display:flex;flex-direction:column;gap:8px;transition:all .3s;position:relative;backdrop-filter:blur(12px)}
|
||||
.input-box:focus-within{border-color:rgba(124,106,255,.4);box-shadow:0 0 24px rgba(124,106,255,.12);animation:focusPulse 3s infinite}
|
||||
@keyframes focusPulse{0%,100%{box-shadow:0 0 20px rgba(124,106,255,.08)}50%{box-shadow:0 0 28px rgba(124,106,255,.18)}}
|
||||
textarea{width:100%;background:transparent;border:none;color:var(--text-bright);font-family:inherit;font-size:13px;line-height:1.5;resize:none;outline:none;min-height:22px;max-height:150px}
|
||||
textarea::placeholder{color:var(--text-dim)}
|
||||
.input-footer{display:flex;align-items:center;justify-content:space-between}
|
||||
.input-hint{font-size:10px;color:var(--text-dim);opacity:.5}
|
||||
.input-btns{display:flex;gap:5px}
|
||||
.send-btn{background:linear-gradient(135deg,var(--accent),var(--accent2));border:none;color:#fff;width:32px;height:32px;border-radius:10px;cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:14px;transition:all .2s;box-shadow:0 2px 12px rgba(124,106,255,.35);position:relative;overflow:hidden}
|
||||
.send-btn::after{content:'';position:absolute;inset:0;background:linear-gradient(135deg,transparent,rgba(255,255,255,.15));opacity:0;transition:opacity .3s}
|
||||
.send-btn:hover{transform:translateY(-2px) scale(1.05);box-shadow:0 6px 24px rgba(124,106,255,.45)}
|
||||
.send-btn:hover::after{opacity:1}
|
||||
.send-btn:active{transform:scale(.92)}.send-btn:disabled{opacity:.2;cursor:not-allowed;transform:none;box-shadow:none}
|
||||
.stop-btn{background:var(--red);border:none;color:#fff;width:32px;height:32px;border-radius:10px;cursor:pointer;display:none;align-items:center;justify-content:center;font-size:11px;box-shadow:0 0 12px rgba(255,82,82,.3)}
|
||||
.stop-btn.visible{display:flex}
|
||||
@keyframes msgIn{from{opacity:0;transform:translateY(12px) scale(.97)}to{opacity:1;transform:translateY(0) scale(1)}}
|
||||
@keyframes pulse{0%,100%{opacity:.4}50%{opacity:1}}
|
||||
.stream-active{position:relative}
|
||||
.stream-active::after{content:'';display:inline-block;width:2px;height:14px;background:var(--accent);margin-left:2px;animation:blink .6s step-end infinite;vertical-align:text-bottom;border-radius:1px;box-shadow:0 0 6px var(--accent)}
|
||||
@keyframes blink{0%,100%{opacity:1}50%{opacity:0}}
|
||||
.stream-active .code-wrap:last-child {
|
||||
border: 1px solid var(--accent);
|
||||
animation: codePulse 2s infinite;
|
||||
}
|
||||
.stream-active .code-wrap:last-child pre {
|
||||
box-shadow: inset 0 0 20px rgba(124,106,255,0.05);
|
||||
}
|
||||
@keyframes codePulse {
|
||||
0%, 100% { box-shadow: 0 0 15px var(--accent-glow); }
|
||||
50% { box-shadow: 0 0 35px var(--accent2-glow); border-color: var(--accent2); }
|
||||
}
|
||||
.main-view{flex:1;display:flex;flex-direction:column;overflow:hidden;transition:all .5s cubic-bezier(.16,1,.3,1)}
|
||||
body.init .main-view{justify-content:center;margin-top:-6vh}
|
||||
body.init .chat{flex:0 0 auto;overflow:visible;padding-bottom:15px}
|
||||
body.init .input-wrap{max-width:680px;width:100%;margin:0 auto;transform:none;transition:all .5s cubic-bezier(.16,1,.3,1)}
|
||||
|
||||
/* ATTACHMENT */
|
||||
.attach-btn{background:transparent;border:1px solid var(--border2);color:var(--text-dim);width:32px;height:32px;border-radius:10px;cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:16px;transition:all .3s;flex-shrink:0}
|
||||
.attach-btn:hover{color:var(--accent);border-color:var(--accent);box-shadow:0 0 12px var(--accent-glow);transform:translateY(-1px)}
|
||||
.attach-preview{display:none;gap:6px;padding:0 0 6px;flex-wrap:wrap}
|
||||
.attach-preview.visible{display:flex}
|
||||
.attach-chip{display:flex;align-items:center;gap:5px;background:var(--surface2);border:1px solid var(--border2);border-radius:8px;padding:4px 10px;font-size:10px;color:var(--text);animation:msgIn .3s ease}
|
||||
.attach-chip .chip-icon{font-size:12px}
|
||||
.attach-chip .chip-name{max-width:100px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
||||
.attach-chip .chip-remove{cursor:pointer;color:var(--text-dim);font-size:12px;margin-left:2px;transition:color .2s}
|
||||
.attach-chip .chip-remove:hover{color:var(--red)}
|
||||
.attach-thumb{width:28px;height:28px;border-radius:5px;object-fit:cover;border:1px solid var(--border2)}
|
||||
|
||||
/* REGENERATE BUTTON */
|
||||
.regen-btn{display:inline-flex;align-items:center;gap:4px;background:transparent;border:1px solid var(--border2);color:var(--text-dim);padding:4px 12px;border-radius:8px;font-size:10px;cursor:pointer;transition:all .3s;font-family:inherit;margin-top:6px;margin-left:29px}
|
||||
.regen-btn:hover{color:var(--accent);border-color:var(--accent);box-shadow:0 0 12px var(--accent-glow)}
|
||||
|
||||
/* SYNTAX HIGHLIGHTING */
|
||||
.msg-body pre .kw{color:#c792ea}
|
||||
.msg-body pre .str{color:#c3e88d}
|
||||
.msg-body pre .num{color:#f78c6c}
|
||||
.msg-body pre .cm{color:#546e7a;font-style:italic}
|
||||
.msg-body pre .fn{color:#82aaff}
|
||||
.msg-body pre .tag{color:#f07178}
|
||||
.msg-body pre .attr{color:#ffcb6b}
|
||||
.msg-body pre .op{color:#89ddff}
|
||||
.msg-body pre .type{color:#ffcb6b}
|
||||
</style></head><body class="init">
|
||||
<div class="header"><div class="header-left"><div class="logo">\u2726</div><span class="brand">Connect AI</span></div><div class="header-right"><select id="modelSel"></select><button class="btn-icon" id="brainBtn" title="Second Brain">\u{1F9E0}</button><button class="btn-icon" id="settingsBtn" title="Settings">\u2699\uFE0F</button><button class="btn-icon" id="newChatBtn" title="New Chat">+</button></div></div>
|
||||
<div class="thinking-bar" id="thinkingBar"></div>
|
||||
<div class="main-view" id="mainView">
|
||||
<div class="chat" id="chat">
|
||||
<div class="welcome">
|
||||
<div class="welcome-logo">\u2726</div>
|
||||
<div class="welcome-title">Connect AI</div>
|
||||
<div class="welcome-sub">\uBCF4\uC548 \xB7 \uBE44\uC6A9\uCD5C\uC801\uD654 \xB7 \uC9C0\uC2DD\uC5F0\uACB0<br>\uD504\uB85C\uC81D\uD2B8\uB97C \uC774\uD574\uD558\uACE0, \uCF54\uB4DC\uB97C \uC791\uC131\uD558\uACE0, \uC2E4\uD589\uD569\uB2C8\uB2E4.</div>
|
||||
</div></div>
|
||||
<div class="input-wrap"><div class="input-box">
|
||||
<div class="attach-preview" id="attachPreview"></div>
|
||||
<textarea id="input" rows="1" placeholder="\uBB34\uC5C7\uC744 \uB9CC\uB4E4\uC5B4 \uB4DC\uB9B4\uAE4C\uC694?"></textarea>
|
||||
<div class="input-footer"><span class="input-hint">Enter \uC804\uC1A1 \xB7 Shift+Enter \uC904\uBC14\uAFC8</span>
|
||||
<div class="input-btns"><button class="attach-btn" id="attachBtn" title="\uD30C\uC77C \uCCA8\uBD80">+</button><button class="stop-btn" id="stopBtn">\u25A0</button><button class="send-btn" id="sendBtn">\u2191</button></div></div></div>
|
||||
<input type="file" id="fileInput" multiple accept="image/*,audio/*,.txt,.md,.csv,.json,.js,.ts,.html,.css,.py,.java,.rs,.go,.yaml,.yml,.xml,.toml" hidden></div>
|
||||
</div>
|
||||
<script>
|
||||
window.onerror = function(msg, url, line, col, error) {
|
||||
document.body.innerHTML += '<div style="position:absolute;z-index:9999;background:red;color:white;padding:10px;top:0;left:0;right:0">ERROR: ' + msg + ' at line ' + line + '</div>';
|
||||
};
|
||||
window.addEventListener('unhandledrejection', function(event) {
|
||||
document.body.innerHTML += '<div style="position:absolute;z-index:9999;background:red;color:white;padding:10px;bottom:0;left:0;right:0">PROMISE REJECTION: ' + event.reason + '</div>';
|
||||
});
|
||||
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"),'<span class="cm">$1</span>');
|
||||
h=h.replace(new RegExp("(#[^\\\\n]*)", "g"),'<span class="cm">$1</span>');
|
||||
h=h.replace(new RegExp("(\\\\/\\\\*[\\\\s\\\\S]*?\\\\*\\\\/)", "g"),'<span class="cm">$1</span>');
|
||||
h=h.replace(/("[^&]*?"|'[^&]*?')/g,'<span class="str">$1</span>');
|
||||
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"),'<span class="kw">$1</span>');
|
||||
h=h.replace(new RegExp("\\\\b(\\\\d+\\\\.?\\\\d*)\\\\b", "g"),'<span class="num">$1</span>');
|
||||
h=h.replace(new RegExp("\\\\b(True|False|None|true|false|null|undefined|NaN)\\\\b", "g"),'<span class="num">$1</span>');
|
||||
h=h.replace(new RegExp("\\\\b(String|Number|Boolean|Array|Object|Map|Set|Promise|void|int|float|str|list|dict|tuple)\\\\b", "g"),'<span class="type">$1</span>');
|
||||
h=h.replace(/([=!+*/%|&^~?:-]+)/g,'<span class="op">$1</span>');
|
||||
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('<create_file') > t.lastIndexOf('</create_file>')) t += '</create_file>';
|
||||
if(t.lastIndexOf('<edit_file') > t.lastIndexOf('</edit_file>')) t += '</edit_file>';
|
||||
if(t.lastIndexOf('<run_command') > t.lastIndexOf('</run_command>')) t += '</run_command>';
|
||||
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(/<create_file\\s+path="([^"]+)">([\\s\\S]*?)<\\/create_file>/g,(_,p,c)=>pushB('<div class="file-badge">\u{1F4C1} '+esc(p)+' \u2014 \uC790\uB3D9 \uC0DD\uC131\uB428</div><div class="code-wrap"><pre><code>'+esc(c)+'</code></pre><button class="copy-btn" onclick="copyCode(this)">Copy</button></div>'));
|
||||
t=t.replace(/<edit_file\\s+path="([^"]+)">([\\s\\S]*?)<\\/edit_file>/g,(_,p,c)=>pushB('<div class="edit-badge">\u270F\uFE0F '+esc(p)+' \u2014 \uD3B8\uC9D1\uB428</div><div class="code-wrap"><pre><code>'+esc(c)+'</code></pre><button class="copy-btn" onclick="copyCode(this)">Copy</button></div>'));
|
||||
t=t.replace(/<run_command>([\\s\\S]*?)<\\/run_command>/g,(_,c)=>pushB('<div class="cmd-badge">\u25B6 '+esc(c)+'</div>'));
|
||||
t=t.replace(/\`\`\`(\\w*)\\n([\\s\\S]*?)\`\`\`/g,(_,lang,c)=>{const l=lang||'code';return pushB('<div class="code-wrap"><span class="code-lang">'+esc(l)+'</span><pre><code>'+highlight(c,l)+'</code></pre><button class="copy-btn" onclick="copyCode(this)">Copy</button></div>');});
|
||||
t=t.replace(/\`([^\`]+)\`/g,(_,c)=>pushB('<code>'+esc(c)+'</code>'));
|
||||
t=esc(t);
|
||||
t=t.replace(/\\*\\*([^*]+)\\*\\*/g,'<strong>$1</strong>');
|
||||
t=t.replace(/\\[([^\\]]+)\\]\\(([^)]+)\\)/g, '<a href="$2" target="_blank">$1</a>');
|
||||
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?'<div class="av av-user">\u{1F464}</div><span>You</span>':'<div class="av av-ai">\u2726</div><span>Connect AI</span>')+'<span class="msg-time">'+getTime()+'</span>';
|
||||
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='<div class="msg-head"><div class="av av-ai">\u2726</div><span>Connect AI</span><span class="msg-time">'+getTime()+'</span></div><div class="loading-wrap"><div class="loading-dots"><span></span><span></span><span></span></div><span class="loading-text">\uC0DD\uAC01\uD558\uB294 \uC911...</span></div>';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='<div class="av av-ai">\u2726</div><span>Connect AI</span><span class="msg-time">'+getTime()+'</span>';
|
||||
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='<div class="welcome"><div class="welcome-logo">\u2726</div><div class="welcome-title">Connect AI</div><div class="welcome-sub">\uBCF4\uC548 \xB7 \uBE44\uC6A9\uCD5C\uC801\uD654 \xB7 \uC9C0\uC2DD\uC5F0\uACB0<br>\uD504\uB85C\uC81D\uD2B8\uB97C \uC774\uD574\uD558\uACE0, \uCF54\uB4DC\uB97C \uC791\uC131\uD558\uACE0, \uC2E4\uD589\uD569\uB2C8\uB2E4.</div></div>';
|
||||
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='<div class="welcome"><div class="welcome-logo">\u2726</div><div class="welcome-title">Connect AI</div><div class="welcome-sub">\uBCF4\uC548 \xB7 \uBE44\uC6A9\uCD5C\uC801\uD654 \xB7 \uC9C0\uC2DD\uC5F0\uACB0<br>\uD504\uB85C\uC81D\uD2B8\uB97C \uC774\uD574\uD558\uACE0, \uCF54\uB4DC\uB97C \uC791\uC131\uD558\uACE0, \uC2E4\uD589\uD569\uB2C8\uB2E4.</div></div>';
|
||||
}
|
||||
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 = '<div style="color:#ff4444;padding:20px;background:#111;height:100%;font-size:14px;overflow:auto;"><h2>\u26A0\uFE0F WEBVIEW JS CRASH</h2><pre>' + err.name + ': ' + err.message + '\\n' + err.stack + '</pre></div>';
|
||||
}
|
||||
</script></body></html>
|
||||
@@ -1,191 +0,0 @@
|
||||
|
||||
window.onerror = function(msg, url, line, col, error) {
|
||||
document.body.innerHTML += '<div style="position:absolute;z-index:9999;background:red;color:white;padding:10px;top:0;left:0;right:0">ERROR: ' + msg + ' at line ' + line + '</div>';
|
||||
};
|
||||
window.addEventListener('unhandledrejection', function(event) {
|
||||
document.body.innerHTML += '<div style="position:absolute;z-index:9999;background:red;color:white;padding:10px;bottom:0;left:0;right:0">PROMISE REJECTION: ' + event.reason + '</div>';
|
||||
});
|
||||
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"),'<span class="cm">$1</span>');
|
||||
h=h.replace(new RegExp("(#[^\\\\n]*)", "g"),'<span class="cm">$1</span>');
|
||||
h=h.replace(new RegExp("(\\\\/\\\\*[\\\\s\\\\S]*?\\\\*\\\\/)", "g"),'<span class="cm">$1</span>');
|
||||
h=h.replace(/("[^&]*?"|'[^&]*?')/g,'<span class="str">$1</span>');
|
||||
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"),'<span class="kw">$1</span>');
|
||||
h=h.replace(new RegExp("\\\\b(\\\\d+\\\\.?\\\\d*)\\\\b", "g"),'<span class="num">$1</span>');
|
||||
h=h.replace(new RegExp("\\\\b(True|False|None|true|false|null|undefined|NaN)\\\\b", "g"),'<span class="num">$1</span>');
|
||||
h=h.replace(new RegExp("\\\\b(String|Number|Boolean|Array|Object|Map|Set|Promise|void|int|float|str|list|dict|tuple)\\\\b", "g"),'<span class="type">$1</span>');
|
||||
h=h.replace(/([=!+*/%|&^~?:-]+)/g,'<span class="op">$1</span>');
|
||||
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('<create_file') > t.lastIndexOf('</create_file>')) t += '</create_file>';
|
||||
if(t.lastIndexOf('<edit_file') > t.lastIndexOf('</edit_file>')) t += '</edit_file>';
|
||||
if(t.lastIndexOf('<run_command') > t.lastIndexOf('</run_command>')) t += '</run_command>';
|
||||
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(/<create_file\\s+path="([^"]+)">([\\s\\S]*?)<\\/create_file>/g,(_,p,c)=>pushB('<div class="file-badge">\u{1F4C1} '+esc(p)+' \u2014 \uC790\uB3D9 \uC0DD\uC131\uB428</div><div class="code-wrap"><pre><code>'+esc(c)+'</code></pre><button class="copy-btn" onclick="copyCode(this)">Copy</button></div>'));
|
||||
t=t.replace(/<edit_file\\s+path="([^"]+)">([\\s\\S]*?)<\\/edit_file>/g,(_,p,c)=>pushB('<div class="edit-badge">\u270F\uFE0F '+esc(p)+' \u2014 \uD3B8\uC9D1\uB428</div><div class="code-wrap"><pre><code>'+esc(c)+'</code></pre><button class="copy-btn" onclick="copyCode(this)">Copy</button></div>'));
|
||||
t=t.replace(/<run_command>([\\s\\S]*?)<\\/run_command>/g,(_,c)=>pushB('<div class="cmd-badge">\u25B6 '+esc(c)+'</div>'));
|
||||
t=t.replace(/\`\`\`(\\w*)\\n([\\s\\S]*?)\`\`\`/g,(_,lang,c)=>{const l=lang||'code';return pushB('<div class="code-wrap"><span class="code-lang">'+esc(l)+'</span><pre><code>'+highlight(c,l)+'</code></pre><button class="copy-btn" onclick="copyCode(this)">Copy</button></div>');});
|
||||
t=t.replace(/\`([^\`]+)\`/g,(_,c)=>pushB('<code>'+esc(c)+'</code>'));
|
||||
t=esc(t);
|
||||
t=t.replace(/\\*\\*([^*]+)\\*\\*/g,'<strong>$1</strong>');
|
||||
t=t.replace(/\\[([^\\]]+)\\]\\(([^)]+)\\)/g, '<a href="$2" target="_blank">$1</a>');
|
||||
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?'<div class="av av-user">\u{1F464}</div><span>You</span>':'<div class="av av-ai">\u2726</div><span>Connect AI</span>')+'<span class="msg-time">'+getTime()+'</span>';
|
||||
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='<div class="msg-head"><div class="av av-ai">\u2726</div><span>Connect AI</span><span class="msg-time">'+getTime()+'</span></div><div class="loading-wrap"><div class="loading-dots"><span></span><span></span><span></span></div><span class="loading-text">\uC0DD\uAC01\uD558\uB294 \uC911...</span></div>';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='<div class="av av-ai">\u2726</div><span>Connect AI</span><span class="msg-time">'+getTime()+'</span>';
|
||||
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='<div class="welcome"><div class="welcome-logo">\u2726</div><div class="welcome-title">Connect AI</div><div class="welcome-sub">\uBCF4\uC548 \xB7 \uBE44\uC6A9\uCD5C\uC801\uD654 \xB7 \uC9C0\uC2DD\uC5F0\uACB0<br>\uD504\uB85C\uC81D\uD2B8\uB97C \uC774\uD574\uD558\uACE0, \uCF54\uB4DC\uB97C \uC791\uC131\uD558\uACE0, \uC2E4\uD589\uD569\uB2C8\uB2E4.</div></div>';
|
||||
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='<div class="welcome"><div class="welcome-logo">\u2726</div><div class="welcome-title">Connect AI</div><div class="welcome-sub">\uBCF4\uC548 \xB7 \uBE44\uC6A9\uCD5C\uC801\uD654 \xB7 \uC9C0\uC2DD\uC5F0\uACB0<br>\uD504\uB85C\uC81D\uD2B8\uB97C \uC774\uD574\uD558\uACE0, \uCF54\uB4DC\uB97C \uC791\uC131\uD558\uACE0, \uC2E4\uD589\uD569\uB2C8\uB2E4.</div></div>';
|
||||
}
|
||||
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 = '<div style="color:#ff4444;padding:20px;background:#111;height:100%;font-size:14px;overflow:auto;"><h2>\u26A0\uFE0F WEBVIEW JS CRASH</h2><pre>' + err.name + ': ' + err.message + '\\n' + err.stack + '</pre></div>';
|
||||
}
|
||||
Reference in New Issue
Block a user