opalacoder 0.1.17__tar.gz → 0.1.19__tar.gz
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.
- {opalacoder-0.1.17 → opalacoder-0.1.19}/PKG-INFO +3 -2
- {opalacoder-0.1.17 → opalacoder-0.1.19}/README.md +1 -1
- opalacoder-0.1.19/docs/specs/backlog_register.md +28 -0
- {opalacoder-0.1.17 → opalacoder-0.1.19}/gui_src/src/App.jsx +348 -47
- {opalacoder-0.1.17 → opalacoder-0.1.19}/gui_src/src/index.css +48 -2
- {opalacoder-0.1.17 → opalacoder-0.1.19}/opalacoder/__init__.py +1 -1
- opalacoder-0.1.17/opalacoder/gui/assets/index-JnX7gh-E.css → opalacoder-0.1.19/opalacoder/gui/assets/index-BzHZtmXE.css +1 -1
- opalacoder-0.1.19/opalacoder/gui/assets/index-Cka3h8dT.js +220 -0
- {opalacoder-0.1.17 → opalacoder-0.1.19}/opalacoder/gui/index.html +2 -2
- {opalacoder-0.1.17 → opalacoder-0.1.19}/opalacoder/ide_server.py +83 -10
- {opalacoder-0.1.17 → opalacoder-0.1.19}/pyproject.toml +2 -1
- {opalacoder-0.1.17 → opalacoder-0.1.19}/requirements.txt +1 -0
- opalacoder-0.1.19/skills/chat-orchestrator/SKILL.md +48 -0
- opalacoder-0.1.19/skills/command-line/SKILL.md +33 -0
- opalacoder-0.1.19/skills/skills_store/html-css-js/SKILL.md +40 -0
- opalacoder-0.1.19/skills/skills_store/implement-feature/SKILL.md +32 -0
- opalacoder-0.1.17/opalacoder/gui/assets/index-FS_uvdys.js +0 -210
- opalacoder-0.1.17/skills/chat-orchestrator/SKILL.md +0 -63
- opalacoder-0.1.17/skills/command-line/SKILL.md +0 -33
- opalacoder-0.1.17/skills/skills_store/html-css-js/SKILL.md +0 -48
- opalacoder-0.1.17/skills/skills_store/implement-feature/SKILL.md +0 -46
- {opalacoder-0.1.17 → opalacoder-0.1.19}/.claude/rules/RULES.md +0 -0
- {opalacoder-0.1.17 → opalacoder-0.1.19}/.claude/settings.json +0 -0
- {opalacoder-0.1.17 → opalacoder-0.1.19}/.github/workflows/publish.yml +0 -0
- {opalacoder-0.1.17 → opalacoder-0.1.19}/.gitignore +0 -0
- {opalacoder-0.1.17 → opalacoder-0.1.19}/AGENTS.md +0 -0
- {opalacoder-0.1.17 → opalacoder-0.1.19}/CLAUDE.md +0 -0
- {opalacoder-0.1.17 → opalacoder-0.1.19}/GEMINI.md +0 -0
- {opalacoder-0.1.17 → opalacoder-0.1.19}/agents.yaml +0 -0
- {opalacoder-0.1.17 → opalacoder-0.1.19}/config.yaml +0 -0
- {opalacoder-0.1.17 → opalacoder-0.1.19}/debug/scratch_tail.py +0 -0
- {opalacoder-0.1.17 → opalacoder-0.1.19}/docs/guide/analysis_results.md +0 -0
- {opalacoder-0.1.17 → opalacoder-0.1.19}/docs/guide/figs/logotipo.png +0 -0
- {opalacoder-0.1.17 → opalacoder-0.1.19}/docs/guide/paper_memplan.md +0 -0
- {opalacoder-0.1.17 → opalacoder-0.1.19}/docs/guide/skills-plugin-system.md +0 -0
- {opalacoder-0.1.17 → opalacoder-0.1.19}/docs/specs/01-arquitetura.md +0 -0
- {opalacoder-0.1.17 → opalacoder-0.1.19}/docs/specs/02-memgpt-orquestrador.md +0 -0
- {opalacoder-0.1.17 → opalacoder-0.1.19}/docs/specs/03-skill-implement-feature.md +0 -0
- {opalacoder-0.1.17 → opalacoder-0.1.19}/docs/specs/04-memoria.md +0 -0
- {opalacoder-0.1.17 → opalacoder-0.1.19}/docs/specs/05-vcs-sombra.md +0 -0
- {opalacoder-0.1.17 → opalacoder-0.1.19}/docs/specs/06-skills-e-plugins.md +0 -0
- {opalacoder-0.1.17 → opalacoder-0.1.19}/docs/specs/07-configuracao.md +0 -0
- {opalacoder-0.1.17 → opalacoder-0.1.19}/docs/specs/08-ide.md +0 -0
- {opalacoder-0.1.17 → opalacoder-0.1.19}/docs/specs/09-ide-projectcreationinterface.md +0 -0
- {opalacoder-0.1.17 → opalacoder-0.1.19}/docs/specs/README.md +0 -0
- {opalacoder-0.1.17 → opalacoder-0.1.19}/gui_src/index.html +0 -0
- {opalacoder-0.1.17 → opalacoder-0.1.19}/gui_src/package-lock.json +0 -0
- {opalacoder-0.1.17 → opalacoder-0.1.19}/gui_src/package.json +0 -0
- {opalacoder-0.1.17 → opalacoder-0.1.19}/gui_src/src/main.jsx +0 -0
- {opalacoder-0.1.17 → opalacoder-0.1.19}/gui_src/vite.config.js +0 -0
- {opalacoder-0.1.17 → opalacoder-0.1.19}/main.py +0 -0
- {opalacoder-0.1.17 → opalacoder-0.1.19}/opalacoder/agent_stdin.py +0 -0
- {opalacoder-0.1.17 → opalacoder-0.1.19}/opalacoder/agents.py +0 -0
- {opalacoder-0.1.17 → opalacoder-0.1.19}/opalacoder/api_keys.py +0 -0
- {opalacoder-0.1.17 → opalacoder-0.1.19}/opalacoder/archival.py +0 -0
- {opalacoder-0.1.17 → opalacoder-0.1.19}/opalacoder/cli.py +0 -0
- {opalacoder-0.1.17 → opalacoder-0.1.19}/opalacoder/cli_commands.py +0 -0
- {opalacoder-0.1.17 → opalacoder-0.1.19}/opalacoder/code_index.py +0 -0
- {opalacoder-0.1.17 → opalacoder-0.1.19}/opalacoder/config.py +0 -0
- {opalacoder-0.1.17 → opalacoder-0.1.19}/opalacoder/copy_gui.py +0 -0
- {opalacoder-0.1.17 → opalacoder-0.1.19}/opalacoder/embeddings.py +0 -0
- {opalacoder-0.1.17 → opalacoder-0.1.19}/opalacoder/i18n.py +0 -0
- {opalacoder-0.1.17 → opalacoder-0.1.19}/opalacoder/memgpt_runtime.py +0 -0
- {opalacoder-0.1.17 → opalacoder-0.1.19}/opalacoder/orchestrator.py +0 -0
- {opalacoder-0.1.17 → opalacoder-0.1.19}/opalacoder/planner.py +0 -0
- {opalacoder-0.1.17 → opalacoder-0.1.19}/opalacoder/plugins/__init__.py +0 -0
- {opalacoder-0.1.17 → opalacoder-0.1.19}/opalacoder/plugins/html_css_js_tools.py +0 -0
- {opalacoder-0.1.17 → opalacoder-0.1.19}/opalacoder/project.py +0 -0
- {opalacoder-0.1.17 → opalacoder-0.1.19}/opalacoder/session.py +0 -0
- {opalacoder-0.1.17 → opalacoder-0.1.19}/opalacoder/skills.py +0 -0
- {opalacoder-0.1.17 → opalacoder-0.1.19}/opalacoder/structured.py +0 -0
- {opalacoder-0.1.17 → opalacoder-0.1.19}/opalacoder/terminal.py +0 -0
- {opalacoder-0.1.17 → opalacoder-0.1.19}/opalacoder/terminal_manager.py +0 -0
- {opalacoder-0.1.17 → opalacoder-0.1.19}/opalacoder/tools.py +0 -0
- {opalacoder-0.1.17 → opalacoder-0.1.19}/opalacoder/vcs.py +0 -0
- {opalacoder-0.1.17 → opalacoder-0.1.19}/opalacoder/vector_index.py +0 -0
- {opalacoder-0.1.17 → opalacoder-0.1.19}/opalacoder/workflow_orchestrator.py +0 -0
- {opalacoder-0.1.17 → opalacoder-0.1.19}/opalacoder/workflow_tools.py +0 -0
- {opalacoder-0.1.17 → opalacoder-0.1.19}/pytest.ini +0 -0
- {opalacoder-0.1.17 → opalacoder-0.1.19}/skills/command-line/scripts/command_executor.py +0 -0
- {opalacoder-0.1.17 → opalacoder-0.1.19}/skills/skills_store/html-css-js/scripts/check_contracts.py +0 -0
- {opalacoder-0.1.17 → opalacoder-0.1.19}/skills/skills_store/implement-feature/scripts/run_workflow.py +0 -0
- {opalacoder-0.1.17 → opalacoder-0.1.19}/tests/test_agent_config.py +0 -0
- {opalacoder-0.1.17 → opalacoder-0.1.19}/tests/test_agent_stdin.py +0 -0
- {opalacoder-0.1.17 → opalacoder-0.1.19}/tests/test_code_index_integration.py +0 -0
- {opalacoder-0.1.17 → opalacoder-0.1.19}/tests/test_command_line_skill.py +0 -0
- {opalacoder-0.1.17 → opalacoder-0.1.19}/tests/test_complexity_evaluator.py +0 -0
- {opalacoder-0.1.17 → opalacoder-0.1.19}/tests/test_context_guard.py +0 -0
- {opalacoder-0.1.17 → opalacoder-0.1.19}/tests/test_file_delete.py +0 -0
- {opalacoder-0.1.17 → opalacoder-0.1.19}/tests/test_i18n_coverage.py +0 -0
- {opalacoder-0.1.17 → opalacoder-0.1.19}/tests/test_implement_feature_script.py +0 -0
- {opalacoder-0.1.17 → opalacoder-0.1.19}/tests/test_memgpt_runtime.py +0 -0
- {opalacoder-0.1.17 → opalacoder-0.1.19}/tests/test_model_commands.py +0 -0
- {opalacoder-0.1.17 → opalacoder-0.1.19}/tests/test_planner_oracle_live.py +0 -0
- {opalacoder-0.1.17 → opalacoder-0.1.19}/tests/test_planner_output.py +0 -0
- {opalacoder-0.1.17 → opalacoder-0.1.19}/tests/test_project_store.py +0 -0
- {opalacoder-0.1.17 → opalacoder-0.1.19}/tests/test_refinement_loop.py +0 -0
- {opalacoder-0.1.17 → opalacoder-0.1.19}/tests/test_search_bugs.py +0 -0
- {opalacoder-0.1.17 → opalacoder-0.1.19}/tests/test_skills_directory_loader.py +0 -0
- {opalacoder-0.1.17 → opalacoder-0.1.19}/tests/test_unhashable_dict_bug.py +0 -0
- {opalacoder-0.1.17 → opalacoder-0.1.19}/tests/test_verification_strategies.py +0 -0
- {opalacoder-0.1.17 → opalacoder-0.1.19}/tests/test_workflow_pipeline.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: opalacoder
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.19
|
|
4
4
|
Summary: Autonomous coding agent with interactive planning and modular execution
|
|
5
5
|
Project-URL: Homepage, https://github.com/gilzamir/OpalaCoder
|
|
6
6
|
Project-URL: Bug Tracker, https://github.com/gilzamir/OpalaCoder/issues
|
|
@@ -15,6 +15,7 @@ Requires-Dist: chromadb>=0.4.0
|
|
|
15
15
|
Requires-Dist: instructor>=1.15.1
|
|
16
16
|
Requires-Dist: litellm>=1.40.0
|
|
17
17
|
Requires-Dist: numpy>=1.21.0
|
|
18
|
+
Requires-Dist: pyqt6-webengine>=6.0.0
|
|
18
19
|
Requires-Dist: pyqt6>=6.0.0
|
|
19
20
|
Requires-Dist: python-dotenv>=1.0.0
|
|
20
21
|
Requires-Dist: pywebview>=4.0
|
|
@@ -59,7 +60,7 @@ OpalaCoder features an integrated desktop GUI built using React, Vite, and `pywe
|
|
|
59
60
|
- **Integrated Terminal**: Includes a real-time xterm.js terminal with shell/PTY integration for running and inspecting commands natively.
|
|
60
61
|
- **Git Source Control Sidebar**: A dedicated panel that tracks file modifications (color-coded as Modified/Untracked/Deleted) and provides a commit interface.
|
|
61
62
|
- **Global Settings Panel**: Customize the editor font size, tab size, and word wrapping, with dynamic toggle support for Light and Dark themes.
|
|
62
|
-
- **About Tab**: Version tracking (currently `0.1.
|
|
63
|
+
- **About Tab**: Version tracking (currently `0.1.19 alfa`), licensing, and developer details in the settings panel.
|
|
63
64
|
|
|
64
65
|
### 6. Persistent Projects and CLI Commands
|
|
65
66
|
|
|
@@ -30,7 +30,7 @@ OpalaCoder features an integrated desktop GUI built using React, Vite, and `pywe
|
|
|
30
30
|
- **Integrated Terminal**: Includes a real-time xterm.js terminal with shell/PTY integration for running and inspecting commands natively.
|
|
31
31
|
- **Git Source Control Sidebar**: A dedicated panel that tracks file modifications (color-coded as Modified/Untracked/Deleted) and provides a commit interface.
|
|
32
32
|
- **Global Settings Panel**: Customize the editor font size, tab size, and word wrapping, with dynamic toggle support for Light and Dark themes.
|
|
33
|
-
- **About Tab**: Version tracking (currently `0.1.
|
|
33
|
+
- **About Tab**: Version tracking (currently `0.1.19 alfa`), licensing, and developer details in the settings panel.
|
|
34
34
|
|
|
35
35
|
### 6. Persistent Projects and CLI Commands
|
|
36
36
|
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# BUGS TO FIX DETECTED ON LAST PUBLIC RELEASE (READ THE LAST SUBTOPIC)
|
|
2
|
+
|
|
3
|
+
## FROM CURRENT = 0.1.17 TO NEXT = 0.1.18
|
|
4
|
+
|
|
5
|
+
1. Terminal dont work, no obvious message in back terminal, but on IDE terminal we get the message "[OpalaCoder] Conexão com o terminal perdida. Reconectando...".
|
|
6
|
+
|
|
7
|
+
2. A janela de criação de projeto somente deveria permitir confirmar criação de projeto se o diretório especificado for válido.
|
|
8
|
+
|
|
9
|
+
3. New feature: install optional modules must be on IDE startup.
|
|
10
|
+
|
|
11
|
+
## FROM CURRENT = 0.1.18 TO NEXT = 0.1.19
|
|
12
|
+
|
|
13
|
+
1. Projeto não mostra nenhum erro quando o backend de modelos falha, por exemplo, tenta-se rodar um modelo que o ollama não tem instalado.
|
|
14
|
+
|
|
15
|
+
2. Ao criar um novo arquivo, ficou congelado em carregando.
|
|
16
|
+
|
|
17
|
+
3. A aba de Problems nunca mostra nada errado, mesmo tendo.
|
|
18
|
+
|
|
19
|
+
4. Adicionar opção de renomear arquivo/diretório selecionado
|
|
20
|
+
|
|
21
|
+
5. Adicionar opção de limpar output e problems.
|
|
22
|
+
|
|
23
|
+
6. Abrir mais de uma aba no editor de arquivo (vários arquivos abertos ao mesmo tempo)
|
|
24
|
+
|
|
25
|
+
7. Verificar se o agente de comunicação com o backend é um LLMAgentBlock e, se for, como está as configurações de limite de chamada de ferramentas e de reflexão e outras.
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
Mensagens no chat não podem ser copiadas.
|
|
@@ -9,6 +9,8 @@ import {
|
|
|
9
9
|
File,
|
|
10
10
|
Plus,
|
|
11
11
|
Trash2,
|
|
12
|
+
Edit2,
|
|
13
|
+
Trash,
|
|
12
14
|
RefreshCw,
|
|
13
15
|
X,
|
|
14
16
|
Undo,
|
|
@@ -53,6 +55,9 @@ export default function App() {
|
|
|
53
55
|
const [files, setFiles] = useState([]);
|
|
54
56
|
const [selectedFile, setSelectedFile] = useState(null);
|
|
55
57
|
const [fileContent, setFileContent] = useState('');
|
|
58
|
+
const [openFiles, setOpenFiles] = useState([]);
|
|
59
|
+
const [fileContents, setFileContents] = useState({});
|
|
60
|
+
const [rightClickedNode, setRightClickedNode] = useState(null);
|
|
56
61
|
const [isSaving, setIsSaving] = useState(false);
|
|
57
62
|
const [chatMessages, setChatMessages] = useState([]);
|
|
58
63
|
const [chatInput, setChatInput] = useState('');
|
|
@@ -81,6 +86,7 @@ export default function App() {
|
|
|
81
86
|
const [isInstallingDeps, setIsInstallingDeps] = useState(false);
|
|
82
87
|
const [installDepsStatus, setInstallDepsStatus] = useState('');
|
|
83
88
|
const [installDepsLog, setInstallDepsLog] = useState('');
|
|
89
|
+
const [showInstallPrompt, setShowInstallPrompt] = useState(false);
|
|
84
90
|
|
|
85
91
|
|
|
86
92
|
const terminalRef = useRef(null);
|
|
@@ -105,6 +111,19 @@ export default function App() {
|
|
|
105
111
|
// Shape: { name, project_name, project_path, model, alternative_model, mode, description } | null
|
|
106
112
|
const [editingProject, setEditingProject] = useState(null);
|
|
107
113
|
|
|
114
|
+
const fetchProblems = async () => {
|
|
115
|
+
if (!activeProject) return;
|
|
116
|
+
try {
|
|
117
|
+
const res = await fetch(`/api/opalacoder/problems?projectPath=${encodeURIComponent(activeProject.project_path)}`);
|
|
118
|
+
if (res.ok) {
|
|
119
|
+
const data = await res.json();
|
|
120
|
+
setProblems(data.problems || []);
|
|
121
|
+
}
|
|
122
|
+
} catch (err) {
|
|
123
|
+
console.error("Failed to fetch problems", err);
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
|
|
108
127
|
const startResizing = (mouseDownEvent, direction) => {
|
|
109
128
|
mouseDownEvent.preventDefault();
|
|
110
129
|
const startX = mouseDownEvent.clientX;
|
|
@@ -119,12 +138,12 @@ export default function App() {
|
|
|
119
138
|
const newWidth = Math.max(150, Math.min(600, startWidthLeft + deltaX));
|
|
120
139
|
setSidebarWidth(newWidth);
|
|
121
140
|
} else if (direction === 'right') {
|
|
122
|
-
const deltaX =
|
|
123
|
-
const newWidth = Math.max(200, Math.min(
|
|
141
|
+
const deltaX = mouseMoveEvent.clientX - startX;
|
|
142
|
+
const newWidth = Math.max(200, Math.min(600, startWidthRight - deltaX));
|
|
124
143
|
setChatWidth(newWidth);
|
|
125
144
|
} else if (direction === 'bottom') {
|
|
126
|
-
const deltaY =
|
|
127
|
-
const newHeight = Math.max(
|
|
145
|
+
const deltaY = mouseMoveEvent.clientY - startY;
|
|
146
|
+
const newHeight = Math.max(100, Math.min(600, startHeightBottom - deltaY));
|
|
128
147
|
setBottomPanelHeight(newHeight);
|
|
129
148
|
}
|
|
130
149
|
};
|
|
@@ -154,12 +173,27 @@ export default function App() {
|
|
|
154
173
|
// Initial load
|
|
155
174
|
useEffect(() => {
|
|
156
175
|
fetchProjects();
|
|
176
|
+
const checkOptionalDeps = async () => {
|
|
177
|
+
try {
|
|
178
|
+
const res = await fetch('/api/settings/check-dependencies');
|
|
179
|
+
if (res.ok) {
|
|
180
|
+
const data = await res.json();
|
|
181
|
+
if (!data.installed) {
|
|
182
|
+
setShowInstallPrompt(true);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
} catch (e) {
|
|
186
|
+
console.error("Failed to check optional dependencies", e);
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
checkOptionalDeps();
|
|
157
190
|
}, []);
|
|
158
191
|
|
|
159
192
|
// Sync files and greetings when project changes
|
|
160
193
|
useEffect(() => {
|
|
161
194
|
if (activeProject) {
|
|
162
195
|
fetchFiles();
|
|
196
|
+
fetchProblems();
|
|
163
197
|
setChatMessages([
|
|
164
198
|
{ role: 'assistant', content: `Olá! Estou pronto para auxiliar no projeto **${activeProject.project_name || activeProject.name}**.` }
|
|
165
199
|
]);
|
|
@@ -167,6 +201,9 @@ export default function App() {
|
|
|
167
201
|
setFiles([]);
|
|
168
202
|
setSelectedFile(null);
|
|
169
203
|
setFileContent('');
|
|
204
|
+
setOpenFiles([]);
|
|
205
|
+
setFileContents({});
|
|
206
|
+
setProblems([]);
|
|
170
207
|
}
|
|
171
208
|
}, [activeProject]);
|
|
172
209
|
|
|
@@ -481,15 +518,28 @@ export default function App() {
|
|
|
481
518
|
if (!activeProject) return;
|
|
482
519
|
e.preventDefault();
|
|
483
520
|
e.stopPropagation();
|
|
521
|
+
setRightClickedNode(null);
|
|
522
|
+
setContextMenu({
|
|
523
|
+
x: e.clientX,
|
|
524
|
+
y: e.clientY
|
|
525
|
+
});
|
|
526
|
+
};
|
|
527
|
+
|
|
528
|
+
const handleNodeContextMenu = (e, node) => {
|
|
529
|
+
if (!activeProject) return;
|
|
530
|
+
e.preventDefault();
|
|
531
|
+
e.stopPropagation();
|
|
532
|
+
setRightClickedNode(node);
|
|
484
533
|
setContextMenu({
|
|
485
534
|
x: e.clientX,
|
|
486
535
|
y: e.clientY
|
|
487
536
|
});
|
|
488
537
|
};
|
|
489
538
|
|
|
490
|
-
const handleCreateNewFile = async () => {
|
|
539
|
+
const handleCreateNewFile = async (parentPath) => {
|
|
491
540
|
if (!activeProject) return;
|
|
492
|
-
const
|
|
541
|
+
const defaultPath = parentPath ? `${parentPath}/` : '';
|
|
542
|
+
const filename = window.prompt("Nome do novo arquivo (ex: src/utils.py):", defaultPath);
|
|
493
543
|
if (!filename) return;
|
|
494
544
|
|
|
495
545
|
try {
|
|
@@ -506,8 +556,8 @@ export default function App() {
|
|
|
506
556
|
if (res.ok) {
|
|
507
557
|
addLog('info', `Arquivo criado: ${filename}`);
|
|
508
558
|
await fetchFiles();
|
|
509
|
-
|
|
510
|
-
|
|
559
|
+
// Open it in the editor
|
|
560
|
+
await handleFileSelect(filename);
|
|
511
561
|
} else {
|
|
512
562
|
const errData = await res.json();
|
|
513
563
|
addLog('error', `Falha ao criar arquivo: ${errData.error}`);
|
|
@@ -518,44 +568,201 @@ export default function App() {
|
|
|
518
568
|
}
|
|
519
569
|
};
|
|
520
570
|
|
|
521
|
-
const
|
|
522
|
-
if (!activeProject || !
|
|
523
|
-
|
|
524
|
-
|
|
571
|
+
const handleRenameNode = async (node) => {
|
|
572
|
+
if (!activeProject || !node) return;
|
|
573
|
+
const newPath = window.prompt(`Digite o novo caminho/nome para "${node.path}":`, node.path);
|
|
574
|
+
if (!newPath || newPath === node.path) return;
|
|
575
|
+
|
|
576
|
+
try {
|
|
577
|
+
const res = await fetch('/api/file/rename', {
|
|
578
|
+
method: 'POST',
|
|
579
|
+
headers: { 'Content-Type': 'application/json' },
|
|
580
|
+
body: JSON.stringify({
|
|
581
|
+
projectPath: activeProject.project_path,
|
|
582
|
+
oldPath: node.path,
|
|
583
|
+
newPath: newPath
|
|
584
|
+
})
|
|
585
|
+
});
|
|
586
|
+
|
|
587
|
+
if (res.ok) {
|
|
588
|
+
addLog('info', `${node.isDirectory ? 'Diretório' : 'Arquivo'} renomeado de ${node.path} para ${newPath}`);
|
|
589
|
+
|
|
590
|
+
if (!node.isDirectory) {
|
|
591
|
+
// If it is a file, update tabs
|
|
592
|
+
setOpenFiles(prev => prev.map(f => f === node.path ? newPath : f));
|
|
593
|
+
setFileContents(prev => {
|
|
594
|
+
const next = { ...prev };
|
|
595
|
+
const content = next[node.path];
|
|
596
|
+
delete next[node.path];
|
|
597
|
+
next[newPath] = content;
|
|
598
|
+
return next;
|
|
599
|
+
});
|
|
600
|
+
if (selectedFile === node.path) {
|
|
601
|
+
setSelectedFile(newPath);
|
|
602
|
+
}
|
|
603
|
+
} else {
|
|
604
|
+
// If it's a directory, update any open files inside it
|
|
605
|
+
const prefix = `${node.path}/`;
|
|
606
|
+
setOpenFiles(prev => prev.map(f => f.startsWith(prefix) ? f.replace(node.path, newPath) : f));
|
|
607
|
+
setFileContents(prev => {
|
|
608
|
+
const next = {};
|
|
609
|
+
for (const [k, v] of Object.entries(prev)) {
|
|
610
|
+
if (k.startsWith(prefix)) {
|
|
611
|
+
next[k.replace(node.path, newPath)] = v;
|
|
612
|
+
} else {
|
|
613
|
+
next[k] = v;
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
return next;
|
|
617
|
+
});
|
|
618
|
+
if (selectedFile && selectedFile.startsWith(prefix)) {
|
|
619
|
+
setSelectedFile(prev => prev.replace(node.path, newPath));
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
await fetchFiles();
|
|
623
|
+
} else {
|
|
624
|
+
const errData = await res.json();
|
|
625
|
+
addLog('error', `Falha ao renomear: ${errData.error}`);
|
|
626
|
+
alert(`Erro ao renomear: ${errData.error}`);
|
|
627
|
+
}
|
|
628
|
+
} catch (err) {
|
|
629
|
+
addLog('error', `Erro ao renomear: ${err.message}`);
|
|
630
|
+
}
|
|
631
|
+
};
|
|
632
|
+
|
|
633
|
+
const handleDeleteNode = async (node) => {
|
|
634
|
+
if (!activeProject || !node) return;
|
|
635
|
+
const confirmMsg = `Tem certeza que deseja deletar o ${node.isDirectory ? 'diretório' : 'arquivo'} "${node.path}"?${node.isDirectory ? ' Todos os arquivos internos serão removidos!' : ''}`;
|
|
636
|
+
if (!window.confirm(confirmMsg)) return;
|
|
637
|
+
|
|
525
638
|
try {
|
|
526
639
|
const res = await fetch('/api/file/delete', {
|
|
527
640
|
method: 'POST',
|
|
528
641
|
headers: { 'Content-Type': 'application/json' },
|
|
529
642
|
body: JSON.stringify({
|
|
530
643
|
projectPath: activeProject.project_path,
|
|
531
|
-
filePath:
|
|
644
|
+
filePath: node.path
|
|
532
645
|
})
|
|
533
646
|
});
|
|
534
|
-
|
|
647
|
+
|
|
535
648
|
if (res.ok) {
|
|
536
|
-
addLog('info',
|
|
537
|
-
|
|
538
|
-
|
|
649
|
+
addLog('info', `${node.isDirectory ? 'Diretório' : 'Arquivo'} excluído: ${node.path}`);
|
|
650
|
+
|
|
651
|
+
if (!node.isDirectory) {
|
|
652
|
+
// Remove from tabs
|
|
653
|
+
setOpenFiles(prev => prev.filter(f => f !== node.path));
|
|
654
|
+
setFileContents(prev => {
|
|
655
|
+
const next = { ...prev };
|
|
656
|
+
delete next[node.path];
|
|
657
|
+
return next;
|
|
658
|
+
});
|
|
659
|
+
if (selectedFile === node.path) {
|
|
660
|
+
setSelectedFile(prev => {
|
|
661
|
+
const remaining = openFiles.filter(f => f !== node.path);
|
|
662
|
+
if (remaining.length > 0) {
|
|
663
|
+
const nextActive = remaining[remaining.length - 1];
|
|
664
|
+
setTimeout(() => {
|
|
665
|
+
setFileContent(fileContents[nextActive] || '');
|
|
666
|
+
}, 0);
|
|
667
|
+
return nextActive;
|
|
668
|
+
}
|
|
669
|
+
setFileContent('');
|
|
670
|
+
return null;
|
|
671
|
+
});
|
|
672
|
+
}
|
|
673
|
+
} else {
|
|
674
|
+
// Remove all open files inside this directory
|
|
675
|
+
const prefix = `${node.path}/`;
|
|
676
|
+
setOpenFiles(prev => prev.filter(f => !f.startsWith(prefix)));
|
|
677
|
+
setFileContents(prev => {
|
|
678
|
+
const next = {};
|
|
679
|
+
for (const [k, v] of Object.entries(prev)) {
|
|
680
|
+
if (!k.startsWith(prefix)) {
|
|
681
|
+
next[k] = v;
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
return next;
|
|
685
|
+
});
|
|
686
|
+
if (selectedFile && selectedFile.startsWith(prefix)) {
|
|
687
|
+
setSelectedFile(prev => {
|
|
688
|
+
const remaining = openFiles.filter(f => !f.startsWith(prefix));
|
|
689
|
+
if (remaining.length > 0) {
|
|
690
|
+
const nextActive = remaining[remaining.length - 1];
|
|
691
|
+
setTimeout(() => {
|
|
692
|
+
setFileContent(fileContents[nextActive] || '');
|
|
693
|
+
}, 0);
|
|
694
|
+
return nextActive;
|
|
695
|
+
}
|
|
696
|
+
setFileContent('');
|
|
697
|
+
return null;
|
|
698
|
+
});
|
|
699
|
+
}
|
|
700
|
+
}
|
|
539
701
|
await fetchFiles();
|
|
540
702
|
} else {
|
|
541
703
|
const errData = await res.json();
|
|
542
|
-
addLog('error', `Falha ao deletar
|
|
543
|
-
alert(`Erro ao deletar
|
|
704
|
+
addLog('error', `Falha ao deletar: ${errData.error}`);
|
|
705
|
+
alert(`Erro ao deletar: ${errData.error}`);
|
|
544
706
|
}
|
|
545
707
|
} catch (err) {
|
|
546
|
-
addLog('error', `Erro
|
|
708
|
+
addLog('error', `Erro ao deletar: ${err.message}`);
|
|
547
709
|
}
|
|
548
710
|
};
|
|
549
711
|
|
|
712
|
+
const handleCloseTab = (filePath, e) => {
|
|
713
|
+
if (e) {
|
|
714
|
+
e.stopPropagation();
|
|
715
|
+
e.preventDefault();
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
if (selectedFile === filePath) {
|
|
719
|
+
setFileContents(prev => ({ ...prev, [filePath]: fileContent }));
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
setOpenFiles(prev => {
|
|
723
|
+
const remaining = prev.filter(f => f !== filePath);
|
|
724
|
+
|
|
725
|
+
if (selectedFile === filePath) {
|
|
726
|
+
if (remaining.length > 0) {
|
|
727
|
+
const nextActive = remaining[remaining.length - 1];
|
|
728
|
+
setSelectedFile(nextActive);
|
|
729
|
+
setFileContent(fileContents[nextActive] || '');
|
|
730
|
+
} else {
|
|
731
|
+
setSelectedFile(null);
|
|
732
|
+
setFileContent('');
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
return remaining;
|
|
736
|
+
});
|
|
737
|
+
};
|
|
550
738
|
|
|
551
739
|
const handleFileSelect = async (filePath) => {
|
|
552
740
|
if (!activeProject) return;
|
|
741
|
+
|
|
742
|
+
if (selectedFile) {
|
|
743
|
+
setFileContents(prev => ({ ...prev, [selectedFile]: fileContent }));
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
setOpenFiles(prev => {
|
|
747
|
+
if (!prev.includes(filePath)) {
|
|
748
|
+
return [...prev, filePath];
|
|
749
|
+
}
|
|
750
|
+
return prev;
|
|
751
|
+
});
|
|
752
|
+
|
|
553
753
|
setSelectedFile(filePath);
|
|
754
|
+
|
|
755
|
+
if (fileContents[filePath] !== undefined) {
|
|
756
|
+
setFileContent(fileContents[filePath]);
|
|
757
|
+
return;
|
|
758
|
+
}
|
|
759
|
+
|
|
554
760
|
try {
|
|
555
761
|
const res = await fetch(`/api/file/read?projectPath=${encodeURIComponent(activeProject.project_path)}&filePath=${encodeURIComponent(filePath)}`);
|
|
556
762
|
if (res.ok) {
|
|
557
763
|
const data = await res.json();
|
|
558
764
|
setFileContent(data.content);
|
|
765
|
+
setFileContents(prev => ({ ...prev, [filePath]: data.content }));
|
|
559
766
|
} else {
|
|
560
767
|
addLog('error', `Erro ao ler arquivo: ${filePath}`);
|
|
561
768
|
}
|
|
@@ -579,7 +786,9 @@ export default function App() {
|
|
|
579
786
|
});
|
|
580
787
|
if (res.ok) {
|
|
581
788
|
addLog('info', `Arquivo salvo: ${selectedFile}`);
|
|
789
|
+
setFileContents(prev => ({ ...prev, [selectedFile]: fileContent }));
|
|
582
790
|
fetchGitStatus();
|
|
791
|
+
fetchProblems();
|
|
583
792
|
} else {
|
|
584
793
|
addLog('error', `Erro ao salvar arquivo: ${selectedFile}`);
|
|
585
794
|
}
|
|
@@ -770,9 +979,11 @@ export default function App() {
|
|
|
770
979
|
|
|
771
980
|
} catch (err) {
|
|
772
981
|
addLog('error', `Falha na execução: ${err.message}`);
|
|
982
|
+
setChatMessages(prev => [...prev, { role: 'assistant', content: `🔴 Falha na execução: ${err.message}` }]);
|
|
773
983
|
} finally {
|
|
774
984
|
setIsAgentRunning(false);
|
|
775
985
|
fetchFiles();
|
|
986
|
+
fetchProblems();
|
|
776
987
|
}
|
|
777
988
|
};
|
|
778
989
|
|
|
@@ -820,6 +1031,7 @@ export default function App() {
|
|
|
820
1031
|
break;
|
|
821
1032
|
case 'error':
|
|
822
1033
|
addLog('error', data.message);
|
|
1034
|
+
setChatMessages(prev => [...prev, { role: 'assistant', content: `🔴 Erro do Agente: ${data.message}` }]);
|
|
823
1035
|
break;
|
|
824
1036
|
case 'problem':
|
|
825
1037
|
addLog('error', `[Problema em ${data.tool}]: ${data.message}`);
|
|
@@ -1010,6 +1222,7 @@ export default function App() {
|
|
|
1010
1222
|
node={node}
|
|
1011
1223
|
selectedFile={selectedFile}
|
|
1012
1224
|
handleFileSelect={handleFileSelect}
|
|
1225
|
+
handleNodeContextMenu={handleNodeContextMenu}
|
|
1013
1226
|
/>
|
|
1014
1227
|
))}
|
|
1015
1228
|
</div>
|
|
@@ -1124,16 +1337,28 @@ export default function App() {
|
|
|
1124
1337
|
<div className="vscode-editor-panel">
|
|
1125
1338
|
{/* Tab Header bar */}
|
|
1126
1339
|
<div className="vscode-tabs">
|
|
1127
|
-
<div className="flex h-full">
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1340
|
+
<div className="flex h-full overflow-x-auto" style={{ gap: '2px' }}>
|
|
1341
|
+
{openFiles.map(filePath => {
|
|
1342
|
+
const isActive = filePath === selectedFile;
|
|
1343
|
+
return (
|
|
1344
|
+
<div
|
|
1345
|
+
key={filePath}
|
|
1346
|
+
onClick={() => handleFileSelect(filePath)}
|
|
1347
|
+
className={`vscode-tab ${isActive ? 'active' : ''}`}
|
|
1348
|
+
style={{ cursor: 'pointer', userSelect: 'none' }}
|
|
1349
|
+
>
|
|
1350
|
+
<span style={{ color: isActive ? '#ffffff' : '#a0a0a0' }}>
|
|
1351
|
+
{filePath.split('/').pop()}
|
|
1352
|
+
</span>
|
|
1353
|
+
<button
|
|
1354
|
+
onClick={(e) => handleCloseTab(filePath, e)}
|
|
1355
|
+
className="vscode-tab-close-btn"
|
|
1356
|
+
>
|
|
1357
|
+
<X size={12} />
|
|
1358
|
+
</button>
|
|
1359
|
+
</div>
|
|
1360
|
+
);
|
|
1361
|
+
})}
|
|
1137
1362
|
</div>
|
|
1138
1363
|
|
|
1139
1364
|
<div>
|
|
@@ -1152,6 +1377,7 @@ export default function App() {
|
|
|
1152
1377
|
<div className="vscode-editor-container">
|
|
1153
1378
|
<Editor
|
|
1154
1379
|
height="100%"
|
|
1380
|
+
path={selectedFile}
|
|
1155
1381
|
language={getLanguage(selectedFile)}
|
|
1156
1382
|
theme={theme === 'light' ? 'light' : 'vs-dark'}
|
|
1157
1383
|
value={fileContent}
|
|
@@ -1208,12 +1434,24 @@ export default function App() {
|
|
|
1208
1434
|
TERMINAL
|
|
1209
1435
|
</span>
|
|
1210
1436
|
</div>
|
|
1211
|
-
<
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1437
|
+
<div className="flex items-center" style={{ gap: '8px' }}>
|
|
1438
|
+
{(activeBottomTab === 'output' || activeBottomTab === 'problems') && (
|
|
1439
|
+
<button
|
|
1440
|
+
onClick={activeBottomTab === 'output' ? () => setTerminalLogs([]) : () => setProblems([])}
|
|
1441
|
+
className="vscode-bottom-panel-clear-btn"
|
|
1442
|
+
title={activeBottomTab === 'output' ? 'Limpar Output' : 'Limpar Problemas'}
|
|
1443
|
+
>
|
|
1444
|
+
<Trash size={12} />
|
|
1445
|
+
<span>Clear</span>
|
|
1446
|
+
</button>
|
|
1447
|
+
)}
|
|
1448
|
+
<button
|
|
1449
|
+
onClick={() => setIsTerminalCollapsed(!isTerminalCollapsed)}
|
|
1450
|
+
style={{ background: 'transparent', border: 'none', cursor: 'pointer', color: '#a0a0a0' }}
|
|
1451
|
+
>
|
|
1452
|
+
{isTerminalCollapsed ? <Maximize2 size={12} /> : <Minimize2 size={12} />}
|
|
1453
|
+
</button>
|
|
1454
|
+
</div>
|
|
1217
1455
|
</div>
|
|
1218
1456
|
|
|
1219
1457
|
{!isTerminalCollapsed && (
|
|
@@ -1404,6 +1642,52 @@ export default function App() {
|
|
|
1404
1642
|
</div>
|
|
1405
1643
|
</footer>
|
|
1406
1644
|
|
|
1645
|
+
{/* Prompt for optional dependencies on startup */}
|
|
1646
|
+
{showInstallPrompt && (
|
|
1647
|
+
<div className="vscode-modal-overlay">
|
|
1648
|
+
<div className="vscode-modal" style={{ maxWidth: '440px', width: '90%' }}>
|
|
1649
|
+
<div className="vscode-sidebar-header" style={{ padding: '10px 16px' }}>
|
|
1650
|
+
<span className="vscode-sidebar-title" style={{ color: '#ffffff' }}>MÓDULOS OPCIONAIS REQUERIDOS</span>
|
|
1651
|
+
<button
|
|
1652
|
+
onClick={() => setShowInstallPrompt(false)}
|
|
1653
|
+
style={{ background: 'transparent', border: 'none', cursor: 'pointer', color: '#a0a0a0' }}
|
|
1654
|
+
>
|
|
1655
|
+
<X size={14} />
|
|
1656
|
+
</button>
|
|
1657
|
+
</div>
|
|
1658
|
+
<div style={{ padding: '16px', color: '#cccccc', display: 'flex', flexDirection: 'column', gap: '12px' }}>
|
|
1659
|
+
<p style={{ fontSize: '13px', lineHeight: '1.5' }}>
|
|
1660
|
+
Os módulos opcionais para embeddings offline (<code>sentence-transformers</code>) não foram encontrados no ambiente.
|
|
1661
|
+
</p>
|
|
1662
|
+
<p style={{ fontSize: '12px', color: '#888888', lineHeight: '1.4' }}>
|
|
1663
|
+
Recomendamos a instalação para habilitar o processamento local de vetores e a indexação de código sem depender de APIs externas.
|
|
1664
|
+
</p>
|
|
1665
|
+
|
|
1666
|
+
<div style={{ display: 'flex', justifyContent: 'flex-end', gap: '8px', marginTop: '8px', borderTop: '1px solid #3c3c3c', paddingTop: '12px' }}>
|
|
1667
|
+
<button
|
|
1668
|
+
onClick={() => setShowInstallPrompt(false)}
|
|
1669
|
+
className="vscode-button"
|
|
1670
|
+
style={{ backgroundColor: '#3c3c3c', color: '#ffffff' }}
|
|
1671
|
+
>
|
|
1672
|
+
Ignorar
|
|
1673
|
+
</button>
|
|
1674
|
+
<button
|
|
1675
|
+
onClick={() => {
|
|
1676
|
+
setShowInstallPrompt(false);
|
|
1677
|
+
setIsSettingsOpen(true);
|
|
1678
|
+
setSettingsTab('preferences');
|
|
1679
|
+
handleInstallOptionalDeps();
|
|
1680
|
+
}}
|
|
1681
|
+
className="vscode-button"
|
|
1682
|
+
>
|
|
1683
|
+
Instalar Agora
|
|
1684
|
+
</button>
|
|
1685
|
+
</div>
|
|
1686
|
+
</div>
|
|
1687
|
+
</div>
|
|
1688
|
+
</div>
|
|
1689
|
+
)}
|
|
1690
|
+
|
|
1407
1691
|
{/* VSCode Style New Project Modal */}
|
|
1408
1692
|
{showNewProjectModal && (
|
|
1409
1693
|
<div className="vscode-modal-overlay">
|
|
@@ -1537,19 +1821,33 @@ export default function App() {
|
|
|
1537
1821
|
>
|
|
1538
1822
|
<div
|
|
1539
1823
|
className="vscode-context-menu-item"
|
|
1540
|
-
onClick={
|
|
1824
|
+
onClick={() => {
|
|
1825
|
+
const p = rightClickedNode
|
|
1826
|
+
? (rightClickedNode.isDirectory ? rightClickedNode.path : rightClickedNode.path.split('/').slice(0, -1).join('/'))
|
|
1827
|
+
: '';
|
|
1828
|
+
handleCreateNewFile(p);
|
|
1829
|
+
}}
|
|
1541
1830
|
>
|
|
1542
1831
|
<Plus size={13} style={{ color: '#007acc' }} />
|
|
1543
1832
|
<span>New File...</span>
|
|
1544
1833
|
</div>
|
|
1545
|
-
{
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1834
|
+
{rightClickedNode && (
|
|
1835
|
+
<>
|
|
1836
|
+
<div
|
|
1837
|
+
className="vscode-context-menu-item"
|
|
1838
|
+
onClick={() => handleRenameNode(rightClickedNode)}
|
|
1839
|
+
>
|
|
1840
|
+
<Edit2 size={13} style={{ color: '#e2b52b' }} />
|
|
1841
|
+
<span>Rename...</span>
|
|
1842
|
+
</div>
|
|
1843
|
+
<div
|
|
1844
|
+
className="vscode-context-menu-item"
|
|
1845
|
+
onClick={() => handleDeleteNode(rightClickedNode)}
|
|
1846
|
+
>
|
|
1847
|
+
<Trash2 size={13} style={{ color: '#f48771' }} />
|
|
1848
|
+
<span>Delete</span>
|
|
1849
|
+
</div>
|
|
1850
|
+
</>
|
|
1553
1851
|
)}
|
|
1554
1852
|
</div>
|
|
1555
1853
|
)}
|
|
@@ -1914,7 +2212,7 @@ export default function App() {
|
|
|
1914
2212
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '14px', color: '#cccccc' }}>
|
|
1915
2213
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '4px' }}>
|
|
1916
2214
|
<span className="vscode-sidebar-section-title" style={{ padding: 0 }}>Versão</span>
|
|
1917
|
-
<span style={{ fontSize: '13px', fontWeight: 'bold', color: '#ffffff' }}>0.1.
|
|
2215
|
+
<span style={{ fontSize: '13px', fontWeight: 'bold', color: '#ffffff' }}>0.1.19 alfa</span>
|
|
1918
2216
|
</div>
|
|
1919
2217
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '4px' }}>
|
|
1920
2218
|
<span className="vscode-sidebar-section-title" style={{ padding: 0 }}>Autor</span>
|
|
@@ -1945,7 +2243,7 @@ export default function App() {
|
|
|
1945
2243
|
}
|
|
1946
2244
|
|
|
1947
2245
|
// Subcomponente de nó de arquivo para respeitar as Regras de Hooks
|
|
1948
|
-
function FileNode({ node, selectedFile, handleFileSelect }) {
|
|
2246
|
+
function FileNode({ node, selectedFile, handleFileSelect, handleNodeContextMenu }) {
|
|
1949
2247
|
const isDir = node.isDirectory;
|
|
1950
2248
|
const [isOpen, setIsOpen] = useState(false);
|
|
1951
2249
|
|
|
@@ -1955,6 +2253,7 @@ function FileNode({ node, selectedFile, handleFileSelect }) {
|
|
|
1955
2253
|
<div
|
|
1956
2254
|
onClick={() => setIsOpen(!isOpen)}
|
|
1957
2255
|
className="vscode-tree-node"
|
|
2256
|
+
onContextMenu={(e) => handleNodeContextMenu(e, node)}
|
|
1958
2257
|
>
|
|
1959
2258
|
{isOpen ? <ChevronDown size={14} /> : <ChevronRight size={14} />}
|
|
1960
2259
|
<Folder size={14} className="text-white" style={{ color: '#e8a838' }} />
|
|
@@ -1968,6 +2267,7 @@ function FileNode({ node, selectedFile, handleFileSelect }) {
|
|
|
1968
2267
|
node={child}
|
|
1969
2268
|
selectedFile={selectedFile}
|
|
1970
2269
|
handleFileSelect={handleFileSelect}
|
|
2270
|
+
handleNodeContextMenu={handleNodeContextMenu}
|
|
1971
2271
|
/>
|
|
1972
2272
|
))}
|
|
1973
2273
|
</div>
|
|
@@ -1981,6 +2281,7 @@ function FileNode({ node, selectedFile, handleFileSelect }) {
|
|
|
1981
2281
|
<div
|
|
1982
2282
|
onClick={() => handleFileSelect(node.path)}
|
|
1983
2283
|
className={`vscode-tree-node ${isSelected ? 'active' : ''}`}
|
|
2284
|
+
onContextMenu={(e) => handleNodeContextMenu(e, node)}
|
|
1984
2285
|
>
|
|
1985
2286
|
<File size={13} style={{ color: '#a0a0a0' }} />
|
|
1986
2287
|
<span className="truncate">{node.name}</span>
|