ai-panel 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +47 -0
- package/dist/core.d.ts +9 -0
- package/dist/core.js +160 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +5 -0
- package/dist/next.d.ts +3 -0
- package/dist/next.js +30 -0
- package/dist/overlay.d.ts +21 -0
- package/dist/overlay.js +203 -0
- package/dist/types.d.ts +39 -0
- package/dist/types.js +1 -0
- package/dist/vite.d.ts +2 -0
- package/dist/vite.js +36 -0
- package/package.json +48 -0
package/README.md
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# AI Dev Panel
|
|
2
|
+
|
|
3
|
+
Uma ferramenta revolucionária para edição de aplicações web em tempo real usando IA.
|
|
4
|
+
|
|
5
|
+
## 🧩 Arquitetura
|
|
6
|
+
|
|
7
|
+
O projeto é um monorepo TypeScript dividido em:
|
|
8
|
+
|
|
9
|
+
- `packages/core`: Tipagens e contratos compartilhados.
|
|
10
|
+
- `packages/overlay`: Script injetado no navegador que fornece a interface de seleção e chat.
|
|
11
|
+
- `packages/plugin`: Plugin Vite que serve o overlay e processa as alterações no código.
|
|
12
|
+
- `packages/cli-connector`: Módulo responsável pela comunicação com CLIs de IA (Claude, OpenAI, etc).
|
|
13
|
+
|
|
14
|
+
## 🚀 Como usar (Demo)
|
|
15
|
+
|
|
16
|
+
1. Instale as dependências:
|
|
17
|
+
```bash
|
|
18
|
+
npm install
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
2. Inicie o projeto de exemplo:
|
|
22
|
+
```bash
|
|
23
|
+
npm run dev
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
3. Abra o navegador em `http://localhost:3000`.
|
|
27
|
+
|
|
28
|
+
4. Use o atalho **Ctrl + Espaço** para ativar o modo IA.
|
|
29
|
+
|
|
30
|
+
5. Selecione um elemento (ex: o botão azul) e solicite uma alteração no painel lateral.
|
|
31
|
+
|
|
32
|
+
## 🔥 Funcionalidades Implementadas
|
|
33
|
+
|
|
34
|
+
- [x] Overlay flutuante com suporte a seleção de elementos.
|
|
35
|
+
- [x] Highlight visual de elementos em hover.
|
|
36
|
+
- [x] Painel de chat lateral integrado.
|
|
37
|
+
- [x] Plugin Vite para injeção automática e Hot Reload.
|
|
38
|
+
- [x] Middleware de servidor para processar comandos da IA.
|
|
39
|
+
- [x] Mock de conector de IA com suporte a diffs.
|
|
40
|
+
- [x] Aplicação automática de mudanças no sistema de arquivos.
|
|
41
|
+
|
|
42
|
+
## 🛠️ Próximos Passos
|
|
43
|
+
|
|
44
|
+
- Integração real com `claude-cli` ou `ollama`.
|
|
45
|
+
- Uso de AST (Babel/TS Morph) para injeção de metadados de origem de forma robusta.
|
|
46
|
+
- Preview de diff antes de aplicar.
|
|
47
|
+
- Undo/Redo de alterações.
|
package/dist/core.d.ts
ADDED
package/dist/core.js
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { spawn } from 'child_process';
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
import * as os from 'os';
|
|
5
|
+
export class AIPanelCore {
|
|
6
|
+
projectRoot;
|
|
7
|
+
historyFile;
|
|
8
|
+
constructor(projectRoot = process.cwd()) {
|
|
9
|
+
this.projectRoot = projectRoot;
|
|
10
|
+
this.historyFile = path.join(this.projectRoot, 'ai_history.json');
|
|
11
|
+
}
|
|
12
|
+
async handleRequest(body) {
|
|
13
|
+
const { action } = body;
|
|
14
|
+
if (action === 'getHistory') {
|
|
15
|
+
return this.getHistory();
|
|
16
|
+
}
|
|
17
|
+
if (action === 'execute') {
|
|
18
|
+
const command = body;
|
|
19
|
+
const response = await this.executeClaude(command);
|
|
20
|
+
// Save to history
|
|
21
|
+
await this.saveToHistory(command, response);
|
|
22
|
+
return response;
|
|
23
|
+
}
|
|
24
|
+
throw new Error(`Unknown action: ${action}`);
|
|
25
|
+
}
|
|
26
|
+
getHistory() {
|
|
27
|
+
if (fs.existsSync(this.historyFile)) {
|
|
28
|
+
try {
|
|
29
|
+
return JSON.parse(fs.readFileSync(this.historyFile, 'utf-8'));
|
|
30
|
+
}
|
|
31
|
+
catch (e) {
|
|
32
|
+
console.error('[AI Core] Error parsing history file:', e);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return { messages: [] };
|
|
36
|
+
}
|
|
37
|
+
async saveToHistory(command, response) {
|
|
38
|
+
try {
|
|
39
|
+
const history = this.getHistory();
|
|
40
|
+
history.messages.push({
|
|
41
|
+
role: 'user',
|
|
42
|
+
content: command.instruction,
|
|
43
|
+
element: command.element,
|
|
44
|
+
timestamp: new Date().toISOString()
|
|
45
|
+
});
|
|
46
|
+
history.messages.push({
|
|
47
|
+
role: 'ai',
|
|
48
|
+
content: response.message,
|
|
49
|
+
timestamp: new Date().toISOString()
|
|
50
|
+
});
|
|
51
|
+
fs.writeFileSync(this.historyFile, JSON.stringify(history, null, 2));
|
|
52
|
+
}
|
|
53
|
+
catch (e) {
|
|
54
|
+
console.error('[AI Core] Error saving history:', e);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
async executeClaude(command) {
|
|
58
|
+
const { instruction, element, context } = command;
|
|
59
|
+
const absolutePath = element?.source?.file ? path.resolve(this.projectRoot, element.source.file) : null;
|
|
60
|
+
const prompt = `
|
|
61
|
+
USER INSTRUCTION: ${instruction}
|
|
62
|
+
${element ? `TARGET ELEMENT: ${element.tagName}${element.className ? `.${element.className}` : ''}` : ''}
|
|
63
|
+
${element?.source ? `FILE: ${element.source.file}` : ''}
|
|
64
|
+
|
|
65
|
+
CONTEXT:
|
|
66
|
+
${context.targetFileContent ? `Current file content:\n${context.targetFileContent}` : ''}
|
|
67
|
+
${context.projectContext ? `Project context:\n${context.projectContext}` : ''}
|
|
68
|
+
|
|
69
|
+
FINAL DIRECTIVE: ${instruction}
|
|
70
|
+
`;
|
|
71
|
+
const tempPromptFile = path.join(os.tmpdir(), `claude_prompt_${Date.now()}.txt`);
|
|
72
|
+
const tempScriptFile = path.join(os.tmpdir(), `claude_run_${Date.now()}.ps1`);
|
|
73
|
+
try {
|
|
74
|
+
// Sync IDE content to disk before running Claude if possible
|
|
75
|
+
if (absolutePath && context.targetFileContent && fs.existsSync(absolutePath)) {
|
|
76
|
+
fs.writeFileSync(absolutePath, context.targetFileContent, 'utf8');
|
|
77
|
+
}
|
|
78
|
+
fs.writeFileSync(tempPromptFile, prompt, 'utf8');
|
|
79
|
+
const scriptContent = `
|
|
80
|
+
$ErrorActionPreference = 'SilentlyContinue'
|
|
81
|
+
$PromptText = Get-Content -Path "${tempPromptFile.replace(/\\/g, '\\\\')}" -Raw
|
|
82
|
+
|
|
83
|
+
Write-Output "---CLAUDE-RESPONSE-START---"
|
|
84
|
+
$input = $PromptText + [System.Environment]::NewLine + "exit"
|
|
85
|
+
$input | & claude --dangerously-skip-permissions
|
|
86
|
+
Write-Output "---CLAUDE-RESPONSE-END---"
|
|
87
|
+
`;
|
|
88
|
+
fs.writeFileSync(tempScriptFile, scriptContent, 'utf8');
|
|
89
|
+
let capturedStdout = '';
|
|
90
|
+
await new Promise((resolve) => {
|
|
91
|
+
const child = spawn('powershell.exe', ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', tempScriptFile], {
|
|
92
|
+
stdio: ['pipe', 'pipe', 'inherit'],
|
|
93
|
+
cwd: this.projectRoot,
|
|
94
|
+
shell: false
|
|
95
|
+
});
|
|
96
|
+
child.stdout?.on('data', (data) => {
|
|
97
|
+
const str = data.toString();
|
|
98
|
+
capturedStdout += str;
|
|
99
|
+
process.stdout.write(str);
|
|
100
|
+
if (capturedStdout.includes("---CLAUDE-RESPONSE-END---")) {
|
|
101
|
+
resolve(null);
|
|
102
|
+
child.kill();
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
child.on('exit', () => resolve(null));
|
|
106
|
+
setTimeout(() => { try {
|
|
107
|
+
child.kill();
|
|
108
|
+
}
|
|
109
|
+
catch (e) { } resolve(null); }, 60000);
|
|
110
|
+
});
|
|
111
|
+
const stdout = capturedStdout.replace(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, '');
|
|
112
|
+
// Parse diffs (simplified: read the file back)
|
|
113
|
+
let diffs = [];
|
|
114
|
+
let newContent = context.targetFileContent || '';
|
|
115
|
+
if (absolutePath && fs.existsSync(absolutePath)) {
|
|
116
|
+
newContent = fs.readFileSync(absolutePath, 'utf8');
|
|
117
|
+
if (newContent !== context.targetFileContent) {
|
|
118
|
+
diffs.push({
|
|
119
|
+
path: element?.source?.file || 'unknown',
|
|
120
|
+
original: context.targetFileContent || '',
|
|
121
|
+
modified: newContent,
|
|
122
|
+
patch: ''
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
// Extract message
|
|
127
|
+
const startMarker = "---CLAUDE-RESPONSE-START---";
|
|
128
|
+
const endMarker = "---CLAUDE-RESPONSE-END---";
|
|
129
|
+
const startIndex = stdout.indexOf(startMarker);
|
|
130
|
+
const endIndex = stdout.indexOf(endMarker);
|
|
131
|
+
let responseMessage = "";
|
|
132
|
+
if (startIndex !== -1) {
|
|
133
|
+
if (endIndex !== -1) {
|
|
134
|
+
responseMessage = stdout.substring(startIndex + startMarker.length, endIndex).trim();
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
responseMessage = stdout.substring(startIndex + startMarker.length).trim();
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
responseMessage = responseMessage
|
|
141
|
+
.replace(/USER INSTRUCTION:[\s\S]*?FINAL DIRECTIVE:.*\n/g, '')
|
|
142
|
+
.replace(/\(To exit, type 'exit' or press Ctrl\+C\)/g, '')
|
|
143
|
+
.trim();
|
|
144
|
+
if (!responseMessage) {
|
|
145
|
+
responseMessage = diffs.length > 0 ? `Updated ${element?.source?.file}` : "Claude finished execution.";
|
|
146
|
+
}
|
|
147
|
+
return { message: responseMessage, diffs };
|
|
148
|
+
}
|
|
149
|
+
finally {
|
|
150
|
+
try {
|
|
151
|
+
fs.unlinkSync(tempPromptFile);
|
|
152
|
+
}
|
|
153
|
+
catch (e) { }
|
|
154
|
+
try {
|
|
155
|
+
fs.unlinkSync(tempScriptFile);
|
|
156
|
+
}
|
|
157
|
+
catch (e) { }
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
package/dist/next.d.ts
ADDED
package/dist/next.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { AIPanelCore } from './core';
|
|
2
|
+
export function createNextAIPanelHandler(options = {}) {
|
|
3
|
+
const core = new AIPanelCore(options.projectRoot);
|
|
4
|
+
return async function handler(req, res) {
|
|
5
|
+
if (req.method !== 'POST') {
|
|
6
|
+
if (res.status)
|
|
7
|
+
return res.status(405).json({ error: 'Method not allowed' });
|
|
8
|
+
return new Response(JSON.stringify({ error: 'Method not allowed' }), { status: 405 });
|
|
9
|
+
}
|
|
10
|
+
try {
|
|
11
|
+
// Handle both Pages API (req.body) and App Router (req.json())
|
|
12
|
+
const body = req.body || (await req.json());
|
|
13
|
+
const result = await core.handleRequest(body);
|
|
14
|
+
if (res.status) {
|
|
15
|
+
return res.status(200).json(result);
|
|
16
|
+
}
|
|
17
|
+
return new Response(JSON.stringify(result), {
|
|
18
|
+
status: 200,
|
|
19
|
+
headers: { 'Content-Type': 'application/json' }
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
catch (e) {
|
|
23
|
+
const error = e;
|
|
24
|
+
if (res.status) {
|
|
25
|
+
return res.status(500).json({ error: error.message });
|
|
26
|
+
}
|
|
27
|
+
return new Response(JSON.stringify({ error: error.message }), { status: 500 });
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export declare class AIOverlay {
|
|
2
|
+
private container;
|
|
3
|
+
private chatContent;
|
|
4
|
+
private input;
|
|
5
|
+
private selectedElement;
|
|
6
|
+
private isProcessing;
|
|
7
|
+
private endpoint;
|
|
8
|
+
constructor(options?: {
|
|
9
|
+
endpoint?: string;
|
|
10
|
+
});
|
|
11
|
+
private init;
|
|
12
|
+
private createStyles;
|
|
13
|
+
private createUI;
|
|
14
|
+
private loadHistory;
|
|
15
|
+
private addMessageToChat;
|
|
16
|
+
private startInspecting;
|
|
17
|
+
private setupElementPicker;
|
|
18
|
+
private handleSend;
|
|
19
|
+
private getElementMetadata;
|
|
20
|
+
private getFileContent;
|
|
21
|
+
}
|
package/dist/overlay.js
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
export class AIOverlay {
|
|
2
|
+
container = null;
|
|
3
|
+
chatContent = null;
|
|
4
|
+
input = null;
|
|
5
|
+
selectedElement = null;
|
|
6
|
+
isProcessing = false;
|
|
7
|
+
endpoint;
|
|
8
|
+
constructor(options = {}) {
|
|
9
|
+
this.endpoint = options.endpoint || '/__ai_dev_api';
|
|
10
|
+
this.init();
|
|
11
|
+
}
|
|
12
|
+
init() {
|
|
13
|
+
if (typeof document === 'undefined')
|
|
14
|
+
return;
|
|
15
|
+
this.createStyles();
|
|
16
|
+
this.createUI();
|
|
17
|
+
this.setupElementPicker();
|
|
18
|
+
this.loadHistory();
|
|
19
|
+
}
|
|
20
|
+
createStyles() {
|
|
21
|
+
const style = document.createElement('style');
|
|
22
|
+
style.textContent = `
|
|
23
|
+
#ai-panel-root {
|
|
24
|
+
position: fixed; top: 0; right: 0; width: 350px; height: 100vh;
|
|
25
|
+
background: #1e1e1e; color: white; z-index: 99999;
|
|
26
|
+
display: flex; flex-direction: column; font-family: sans-serif;
|
|
27
|
+
box-shadow: -2px 0 10px rgba(0,0,0,0.5); transform: translateX(100%);
|
|
28
|
+
transition: transform 0.3s ease; border-left: 1px solid #333;
|
|
29
|
+
}
|
|
30
|
+
#ai-panel-root.open { transform: translateX(0); }
|
|
31
|
+
#ai-panel-toggle {
|
|
32
|
+
position: fixed; bottom: 20px; right: 20px; width: 50px; height: 50px;
|
|
33
|
+
background: #007acc; border-radius: 25px; cursor: pointer; z-index: 100000;
|
|
34
|
+
display: flex; align-items: center; justify-content: center; box-shadow: 0 2px 10px rgba(0,0,0,0.3);
|
|
35
|
+
}
|
|
36
|
+
#ai-panel-header { padding: 15px; border-bottom: 1px solid #333; font-weight: bold; display: flex; justify-content: space-between; }
|
|
37
|
+
#ai-panel-chat { flex: 1; overflow-y: auto; padding: 15px; display: flex; flex-direction: column; gap: 10px; }
|
|
38
|
+
.chat-msg { padding: 8px 12px; border-radius: 8px; max-width: 85%; font-size: 14px; line-height: 1.4; }
|
|
39
|
+
.chat-msg.user { background: #007acc; align-self: flex-end; }
|
|
40
|
+
.chat-msg.ai { background: #333; align-self: flex-start; }
|
|
41
|
+
#ai-panel-input-container { padding: 15px; border-top: 1px solid #333; }
|
|
42
|
+
#ai-panel-input { width: 100%; background: #2d2d2d; border: 1px solid #444; color: white; padding: 10px; border-radius: 4px; resize: none; box-sizing: border-box; }
|
|
43
|
+
#ai-panel-footer { padding: 0 15px 15px; display: flex; gap: 10px; }
|
|
44
|
+
.ai-btn { padding: 8px 12px; border-radius: 4px; border: none; cursor: pointer; font-size: 12px; }
|
|
45
|
+
.ai-btn-primary { background: #007acc; color: white; }
|
|
46
|
+
.ai-btn-secondary { background: #444; color: white; }
|
|
47
|
+
.inspecting { cursor: crosshair !important; }
|
|
48
|
+
.inspecting * { cursor: crosshair !important; }
|
|
49
|
+
.ai-highlight { outline: 2px solid #007acc !important; outline-offset: 2px; }
|
|
50
|
+
`;
|
|
51
|
+
document.head.appendChild(style);
|
|
52
|
+
}
|
|
53
|
+
createUI() {
|
|
54
|
+
this.container = document.createElement('div');
|
|
55
|
+
this.container.id = 'ai-panel-root';
|
|
56
|
+
this.container.innerHTML = `
|
|
57
|
+
<div id="ai-panel-header">
|
|
58
|
+
AI Assistant
|
|
59
|
+
<span style="cursor:pointer" onclick="document.getElementById('ai-panel-root').classList.remove('open')">×</span>
|
|
60
|
+
</div>
|
|
61
|
+
<div id="ai-panel-chat"></div>
|
|
62
|
+
<div id="ai-panel-input-container">
|
|
63
|
+
<div id="selected-element-info" style="font-size:10px; color:#aaa; margin-bottom:5px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis"></div>
|
|
64
|
+
<textarea id="ai-panel-input" placeholder="O que deseja alterar?" rows="3"></textarea>
|
|
65
|
+
</div>
|
|
66
|
+
<div id="ai-panel-footer">
|
|
67
|
+
<button id="ai-btn-inspect" class="ai-btn ai-btn-secondary">Selecionar Elemento</button>
|
|
68
|
+
<button id="ai-btn-send" class="ai-btn ai-btn-primary">Enviar</button>
|
|
69
|
+
</div>
|
|
70
|
+
`;
|
|
71
|
+
document.body.appendChild(this.container);
|
|
72
|
+
const toggle = document.createElement('div');
|
|
73
|
+
toggle.id = 'ai-panel-toggle';
|
|
74
|
+
toggle.innerHTML = 'AI';
|
|
75
|
+
toggle.onclick = () => this.container?.classList.toggle('open');
|
|
76
|
+
document.body.appendChild(toggle);
|
|
77
|
+
this.chatContent = this.container.querySelector('#ai-panel-chat');
|
|
78
|
+
this.input = this.container.querySelector('#ai-panel-input');
|
|
79
|
+
this.container.querySelector('#ai-btn-send')?.addEventListener('click', () => this.handleSend());
|
|
80
|
+
this.container.querySelector('#ai-btn-inspect')?.addEventListener('click', () => this.startInspecting());
|
|
81
|
+
this.input?.addEventListener('keydown', (e) => {
|
|
82
|
+
if (e.key === 'Enter' && !e.shiftKey) {
|
|
83
|
+
e.preventDefault();
|
|
84
|
+
this.handleSend();
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
async loadHistory() {
|
|
89
|
+
try {
|
|
90
|
+
const response = await fetch(this.endpoint, {
|
|
91
|
+
method: 'POST',
|
|
92
|
+
headers: { 'Content-Type': 'application/json' },
|
|
93
|
+
body: JSON.stringify({ action: 'getHistory' }),
|
|
94
|
+
});
|
|
95
|
+
const data = await response.json();
|
|
96
|
+
if (data.messages) {
|
|
97
|
+
data.messages.forEach((msg) => {
|
|
98
|
+
this.addMessageToChat(msg.role, msg.content);
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
catch (e) {
|
|
103
|
+
console.error('Failed to load history:', e);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
addMessageToChat(role, content) {
|
|
107
|
+
if (!this.chatContent)
|
|
108
|
+
return;
|
|
109
|
+
const msg = document.createElement('div');
|
|
110
|
+
msg.className = `chat-msg ${role}`;
|
|
111
|
+
msg.textContent = content;
|
|
112
|
+
this.chatContent.appendChild(msg);
|
|
113
|
+
this.chatContent.scrollTop = this.chatContent.scrollHeight;
|
|
114
|
+
}
|
|
115
|
+
startInspecting() {
|
|
116
|
+
document.body.classList.add('inspecting');
|
|
117
|
+
this.container?.classList.remove('open');
|
|
118
|
+
const onMouseOver = (e) => {
|
|
119
|
+
e.target.classList.add('ai-highlight');
|
|
120
|
+
};
|
|
121
|
+
const onMouseOut = (e) => {
|
|
122
|
+
e.target.classList.remove('ai-highlight');
|
|
123
|
+
};
|
|
124
|
+
const onClick = (e) => {
|
|
125
|
+
e.preventDefault();
|
|
126
|
+
e.stopPropagation();
|
|
127
|
+
this.selectedElement = e.target;
|
|
128
|
+
const info = this.container?.querySelector('#selected-element-info');
|
|
129
|
+
if (info)
|
|
130
|
+
info.textContent = `Selecionado: <${this.selectedElement.tagName.toLowerCase()}>`;
|
|
131
|
+
stop();
|
|
132
|
+
this.container?.classList.add('open');
|
|
133
|
+
};
|
|
134
|
+
const stop = () => {
|
|
135
|
+
document.body.classList.remove('inspecting');
|
|
136
|
+
document.removeEventListener('mouseover', onMouseOver);
|
|
137
|
+
document.removeEventListener('mouseout', onMouseOut);
|
|
138
|
+
document.removeEventListener('click', onClick, true);
|
|
139
|
+
};
|
|
140
|
+
document.addEventListener('mouseover', onMouseOver);
|
|
141
|
+
document.addEventListener('mouseout', onMouseOut);
|
|
142
|
+
document.addEventListener('click', onClick, true);
|
|
143
|
+
}
|
|
144
|
+
setupElementPicker() {
|
|
145
|
+
// Already handled in startInspecting
|
|
146
|
+
}
|
|
147
|
+
async handleSend() {
|
|
148
|
+
const text = this.input?.value.trim();
|
|
149
|
+
if (!text || this.isProcessing)
|
|
150
|
+
return;
|
|
151
|
+
this.isProcessing = true;
|
|
152
|
+
this.addMessageToChat('user', text);
|
|
153
|
+
if (this.input)
|
|
154
|
+
this.input.value = '';
|
|
155
|
+
const aiMsg = document.createElement('div');
|
|
156
|
+
aiMsg.className = 'chat-msg ai';
|
|
157
|
+
aiMsg.textContent = 'AI está pensando...';
|
|
158
|
+
this.chatContent?.appendChild(aiMsg);
|
|
159
|
+
try {
|
|
160
|
+
const metadata = this.selectedElement ? this.getElementMetadata(this.selectedElement) : undefined;
|
|
161
|
+
const response = await fetch(this.endpoint, {
|
|
162
|
+
method: 'POST',
|
|
163
|
+
headers: { 'Content-Type': 'application/json' },
|
|
164
|
+
body: JSON.stringify({
|
|
165
|
+
action: 'execute',
|
|
166
|
+
instruction: text,
|
|
167
|
+
element: metadata,
|
|
168
|
+
context: {
|
|
169
|
+
targetFileContent: metadata?.source?.file ? await this.getFileContent(metadata.source.file) : undefined
|
|
170
|
+
}
|
|
171
|
+
}),
|
|
172
|
+
});
|
|
173
|
+
const result = await response.json();
|
|
174
|
+
aiMsg.textContent = result.message;
|
|
175
|
+
// If code was changed, we might want to notify or reload parts of the page
|
|
176
|
+
if (result.diffs && result.diffs.length > 0) {
|
|
177
|
+
console.log('Changes applied:', result.diffs);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
catch (e) {
|
|
181
|
+
aiMsg.textContent = 'Erro ao processar solicitação.';
|
|
182
|
+
console.error(e);
|
|
183
|
+
}
|
|
184
|
+
finally {
|
|
185
|
+
this.isProcessing = false;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
getElementMetadata(el) {
|
|
189
|
+
// This is a placeholder - in a real lib, we'd use a Vite plugin to inject source info
|
|
190
|
+
return {
|
|
191
|
+
tagName: el.tagName,
|
|
192
|
+
className: el.className,
|
|
193
|
+
id: el.id,
|
|
194
|
+
outerHTML: el.outerHTML,
|
|
195
|
+
source: el.__source
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
async getFileContent(file) {
|
|
199
|
+
// In a development environment, we might have an endpoint to read files
|
|
200
|
+
// For now, return empty or handle via core
|
|
201
|
+
return "";
|
|
202
|
+
}
|
|
203
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export interface ElementMetadata {
|
|
2
|
+
tagName: string;
|
|
3
|
+
className?: string;
|
|
4
|
+
id?: string;
|
|
5
|
+
textContent?: string;
|
|
6
|
+
outerHTML: string;
|
|
7
|
+
source?: {
|
|
8
|
+
file: string;
|
|
9
|
+
line?: number;
|
|
10
|
+
column?: number;
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
export interface AIDiff {
|
|
14
|
+
path: string;
|
|
15
|
+
original: string;
|
|
16
|
+
modified: string;
|
|
17
|
+
patch: string;
|
|
18
|
+
}
|
|
19
|
+
export interface AIResponse {
|
|
20
|
+
message: string;
|
|
21
|
+
diffs: AIDiff[];
|
|
22
|
+
}
|
|
23
|
+
export interface AICommand {
|
|
24
|
+
instruction: string;
|
|
25
|
+
element?: ElementMetadata;
|
|
26
|
+
context: {
|
|
27
|
+
targetFileContent?: string;
|
|
28
|
+
projectContext?: string;
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
export interface ChatMessage {
|
|
32
|
+
role: 'user' | 'ai';
|
|
33
|
+
content: string;
|
|
34
|
+
element?: ElementMetadata;
|
|
35
|
+
timestamp: string;
|
|
36
|
+
}
|
|
37
|
+
export interface ChatHistory {
|
|
38
|
+
messages: ChatMessage[];
|
|
39
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/vite.d.ts
ADDED
package/dist/vite.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { AIPanelCore } from './core';
|
|
2
|
+
export function aiPanelVite() {
|
|
3
|
+
const core = new AIPanelCore();
|
|
4
|
+
return {
|
|
5
|
+
name: 'vite-plugin-ai-panel',
|
|
6
|
+
// Inject source information during transformation (simplified)
|
|
7
|
+
transform(code, id) {
|
|
8
|
+
if (!id.endsWith('.tsx') && !id.endsWith('.jsx') && !id.endsWith('.html'))
|
|
9
|
+
return;
|
|
10
|
+
// In a real implementation, we would use a parser to inject data-source attributes
|
|
11
|
+
return code;
|
|
12
|
+
},
|
|
13
|
+
configureServer(server) {
|
|
14
|
+
server.middlewares.use(async (req, res, next) => {
|
|
15
|
+
if (req.url === '/__ai_dev_api') {
|
|
16
|
+
let body = '';
|
|
17
|
+
req.on('data', chunk => body += chunk);
|
|
18
|
+
req.on('end', async () => {
|
|
19
|
+
try {
|
|
20
|
+
const data = JSON.parse(body);
|
|
21
|
+
const result = await core.handleRequest(data);
|
|
22
|
+
res.setHeader('Content-Type', 'application/json');
|
|
23
|
+
res.end(JSON.stringify(result));
|
|
24
|
+
}
|
|
25
|
+
catch (e) {
|
|
26
|
+
res.statusCode = 500;
|
|
27
|
+
res.end(JSON.stringify({ error: e.message }));
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
next();
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ai-panel",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"module": "dist/index.mjs",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist"
|
|
10
|
+
],
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"import": "./dist/index.mjs",
|
|
15
|
+
"require": "./dist/index.js"
|
|
16
|
+
},
|
|
17
|
+
"./client": {
|
|
18
|
+
"types": "./dist/overlay.d.ts",
|
|
19
|
+
"import": "./dist/overlay.mjs",
|
|
20
|
+
"require": "./dist/overlay.js"
|
|
21
|
+
},
|
|
22
|
+
"./server": {
|
|
23
|
+
"types": "./dist/core.d.ts",
|
|
24
|
+
"import": "./dist/core.mjs",
|
|
25
|
+
"require": "./dist/core.js"
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"scripts": {
|
|
29
|
+
"build": "tsc",
|
|
30
|
+
"dev": "vite example",
|
|
31
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
32
|
+
},
|
|
33
|
+
"keywords": [],
|
|
34
|
+
"author": "",
|
|
35
|
+
"license": "ISC",
|
|
36
|
+
"type": "commonjs",
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@types/dotenv": "^6.1.1",
|
|
39
|
+
"@types/node": "^25.2.3",
|
|
40
|
+
"ts-node": "^10.9.2",
|
|
41
|
+
"typescript": "^5.9.3",
|
|
42
|
+
"vite": "^7.3.1"
|
|
43
|
+
},
|
|
44
|
+
"dependencies": {
|
|
45
|
+
"dotenv": "^17.2.4",
|
|
46
|
+
"gsap": "^3.14.2"
|
|
47
|
+
}
|
|
48
|
+
}
|