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.
Files changed (102) hide show
  1. {opalacoder-0.1.17 → opalacoder-0.1.19}/PKG-INFO +3 -2
  2. {opalacoder-0.1.17 → opalacoder-0.1.19}/README.md +1 -1
  3. opalacoder-0.1.19/docs/specs/backlog_register.md +28 -0
  4. {opalacoder-0.1.17 → opalacoder-0.1.19}/gui_src/src/App.jsx +348 -47
  5. {opalacoder-0.1.17 → opalacoder-0.1.19}/gui_src/src/index.css +48 -2
  6. {opalacoder-0.1.17 → opalacoder-0.1.19}/opalacoder/__init__.py +1 -1
  7. opalacoder-0.1.17/opalacoder/gui/assets/index-JnX7gh-E.css → opalacoder-0.1.19/opalacoder/gui/assets/index-BzHZtmXE.css +1 -1
  8. opalacoder-0.1.19/opalacoder/gui/assets/index-Cka3h8dT.js +220 -0
  9. {opalacoder-0.1.17 → opalacoder-0.1.19}/opalacoder/gui/index.html +2 -2
  10. {opalacoder-0.1.17 → opalacoder-0.1.19}/opalacoder/ide_server.py +83 -10
  11. {opalacoder-0.1.17 → opalacoder-0.1.19}/pyproject.toml +2 -1
  12. {opalacoder-0.1.17 → opalacoder-0.1.19}/requirements.txt +1 -0
  13. opalacoder-0.1.19/skills/chat-orchestrator/SKILL.md +48 -0
  14. opalacoder-0.1.19/skills/command-line/SKILL.md +33 -0
  15. opalacoder-0.1.19/skills/skills_store/html-css-js/SKILL.md +40 -0
  16. opalacoder-0.1.19/skills/skills_store/implement-feature/SKILL.md +32 -0
  17. opalacoder-0.1.17/opalacoder/gui/assets/index-FS_uvdys.js +0 -210
  18. opalacoder-0.1.17/skills/chat-orchestrator/SKILL.md +0 -63
  19. opalacoder-0.1.17/skills/command-line/SKILL.md +0 -33
  20. opalacoder-0.1.17/skills/skills_store/html-css-js/SKILL.md +0 -48
  21. opalacoder-0.1.17/skills/skills_store/implement-feature/SKILL.md +0 -46
  22. {opalacoder-0.1.17 → opalacoder-0.1.19}/.claude/rules/RULES.md +0 -0
  23. {opalacoder-0.1.17 → opalacoder-0.1.19}/.claude/settings.json +0 -0
  24. {opalacoder-0.1.17 → opalacoder-0.1.19}/.github/workflows/publish.yml +0 -0
  25. {opalacoder-0.1.17 → opalacoder-0.1.19}/.gitignore +0 -0
  26. {opalacoder-0.1.17 → opalacoder-0.1.19}/AGENTS.md +0 -0
  27. {opalacoder-0.1.17 → opalacoder-0.1.19}/CLAUDE.md +0 -0
  28. {opalacoder-0.1.17 → opalacoder-0.1.19}/GEMINI.md +0 -0
  29. {opalacoder-0.1.17 → opalacoder-0.1.19}/agents.yaml +0 -0
  30. {opalacoder-0.1.17 → opalacoder-0.1.19}/config.yaml +0 -0
  31. {opalacoder-0.1.17 → opalacoder-0.1.19}/debug/scratch_tail.py +0 -0
  32. {opalacoder-0.1.17 → opalacoder-0.1.19}/docs/guide/analysis_results.md +0 -0
  33. {opalacoder-0.1.17 → opalacoder-0.1.19}/docs/guide/figs/logotipo.png +0 -0
  34. {opalacoder-0.1.17 → opalacoder-0.1.19}/docs/guide/paper_memplan.md +0 -0
  35. {opalacoder-0.1.17 → opalacoder-0.1.19}/docs/guide/skills-plugin-system.md +0 -0
  36. {opalacoder-0.1.17 → opalacoder-0.1.19}/docs/specs/01-arquitetura.md +0 -0
  37. {opalacoder-0.1.17 → opalacoder-0.1.19}/docs/specs/02-memgpt-orquestrador.md +0 -0
  38. {opalacoder-0.1.17 → opalacoder-0.1.19}/docs/specs/03-skill-implement-feature.md +0 -0
  39. {opalacoder-0.1.17 → opalacoder-0.1.19}/docs/specs/04-memoria.md +0 -0
  40. {opalacoder-0.1.17 → opalacoder-0.1.19}/docs/specs/05-vcs-sombra.md +0 -0
  41. {opalacoder-0.1.17 → opalacoder-0.1.19}/docs/specs/06-skills-e-plugins.md +0 -0
  42. {opalacoder-0.1.17 → opalacoder-0.1.19}/docs/specs/07-configuracao.md +0 -0
  43. {opalacoder-0.1.17 → opalacoder-0.1.19}/docs/specs/08-ide.md +0 -0
  44. {opalacoder-0.1.17 → opalacoder-0.1.19}/docs/specs/09-ide-projectcreationinterface.md +0 -0
  45. {opalacoder-0.1.17 → opalacoder-0.1.19}/docs/specs/README.md +0 -0
  46. {opalacoder-0.1.17 → opalacoder-0.1.19}/gui_src/index.html +0 -0
  47. {opalacoder-0.1.17 → opalacoder-0.1.19}/gui_src/package-lock.json +0 -0
  48. {opalacoder-0.1.17 → opalacoder-0.1.19}/gui_src/package.json +0 -0
  49. {opalacoder-0.1.17 → opalacoder-0.1.19}/gui_src/src/main.jsx +0 -0
  50. {opalacoder-0.1.17 → opalacoder-0.1.19}/gui_src/vite.config.js +0 -0
  51. {opalacoder-0.1.17 → opalacoder-0.1.19}/main.py +0 -0
  52. {opalacoder-0.1.17 → opalacoder-0.1.19}/opalacoder/agent_stdin.py +0 -0
  53. {opalacoder-0.1.17 → opalacoder-0.1.19}/opalacoder/agents.py +0 -0
  54. {opalacoder-0.1.17 → opalacoder-0.1.19}/opalacoder/api_keys.py +0 -0
  55. {opalacoder-0.1.17 → opalacoder-0.1.19}/opalacoder/archival.py +0 -0
  56. {opalacoder-0.1.17 → opalacoder-0.1.19}/opalacoder/cli.py +0 -0
  57. {opalacoder-0.1.17 → opalacoder-0.1.19}/opalacoder/cli_commands.py +0 -0
  58. {opalacoder-0.1.17 → opalacoder-0.1.19}/opalacoder/code_index.py +0 -0
  59. {opalacoder-0.1.17 → opalacoder-0.1.19}/opalacoder/config.py +0 -0
  60. {opalacoder-0.1.17 → opalacoder-0.1.19}/opalacoder/copy_gui.py +0 -0
  61. {opalacoder-0.1.17 → opalacoder-0.1.19}/opalacoder/embeddings.py +0 -0
  62. {opalacoder-0.1.17 → opalacoder-0.1.19}/opalacoder/i18n.py +0 -0
  63. {opalacoder-0.1.17 → opalacoder-0.1.19}/opalacoder/memgpt_runtime.py +0 -0
  64. {opalacoder-0.1.17 → opalacoder-0.1.19}/opalacoder/orchestrator.py +0 -0
  65. {opalacoder-0.1.17 → opalacoder-0.1.19}/opalacoder/planner.py +0 -0
  66. {opalacoder-0.1.17 → opalacoder-0.1.19}/opalacoder/plugins/__init__.py +0 -0
  67. {opalacoder-0.1.17 → opalacoder-0.1.19}/opalacoder/plugins/html_css_js_tools.py +0 -0
  68. {opalacoder-0.1.17 → opalacoder-0.1.19}/opalacoder/project.py +0 -0
  69. {opalacoder-0.1.17 → opalacoder-0.1.19}/opalacoder/session.py +0 -0
  70. {opalacoder-0.1.17 → opalacoder-0.1.19}/opalacoder/skills.py +0 -0
  71. {opalacoder-0.1.17 → opalacoder-0.1.19}/opalacoder/structured.py +0 -0
  72. {opalacoder-0.1.17 → opalacoder-0.1.19}/opalacoder/terminal.py +0 -0
  73. {opalacoder-0.1.17 → opalacoder-0.1.19}/opalacoder/terminal_manager.py +0 -0
  74. {opalacoder-0.1.17 → opalacoder-0.1.19}/opalacoder/tools.py +0 -0
  75. {opalacoder-0.1.17 → opalacoder-0.1.19}/opalacoder/vcs.py +0 -0
  76. {opalacoder-0.1.17 → opalacoder-0.1.19}/opalacoder/vector_index.py +0 -0
  77. {opalacoder-0.1.17 → opalacoder-0.1.19}/opalacoder/workflow_orchestrator.py +0 -0
  78. {opalacoder-0.1.17 → opalacoder-0.1.19}/opalacoder/workflow_tools.py +0 -0
  79. {opalacoder-0.1.17 → opalacoder-0.1.19}/pytest.ini +0 -0
  80. {opalacoder-0.1.17 → opalacoder-0.1.19}/skills/command-line/scripts/command_executor.py +0 -0
  81. {opalacoder-0.1.17 → opalacoder-0.1.19}/skills/skills_store/html-css-js/scripts/check_contracts.py +0 -0
  82. {opalacoder-0.1.17 → opalacoder-0.1.19}/skills/skills_store/implement-feature/scripts/run_workflow.py +0 -0
  83. {opalacoder-0.1.17 → opalacoder-0.1.19}/tests/test_agent_config.py +0 -0
  84. {opalacoder-0.1.17 → opalacoder-0.1.19}/tests/test_agent_stdin.py +0 -0
  85. {opalacoder-0.1.17 → opalacoder-0.1.19}/tests/test_code_index_integration.py +0 -0
  86. {opalacoder-0.1.17 → opalacoder-0.1.19}/tests/test_command_line_skill.py +0 -0
  87. {opalacoder-0.1.17 → opalacoder-0.1.19}/tests/test_complexity_evaluator.py +0 -0
  88. {opalacoder-0.1.17 → opalacoder-0.1.19}/tests/test_context_guard.py +0 -0
  89. {opalacoder-0.1.17 → opalacoder-0.1.19}/tests/test_file_delete.py +0 -0
  90. {opalacoder-0.1.17 → opalacoder-0.1.19}/tests/test_i18n_coverage.py +0 -0
  91. {opalacoder-0.1.17 → opalacoder-0.1.19}/tests/test_implement_feature_script.py +0 -0
  92. {opalacoder-0.1.17 → opalacoder-0.1.19}/tests/test_memgpt_runtime.py +0 -0
  93. {opalacoder-0.1.17 → opalacoder-0.1.19}/tests/test_model_commands.py +0 -0
  94. {opalacoder-0.1.17 → opalacoder-0.1.19}/tests/test_planner_oracle_live.py +0 -0
  95. {opalacoder-0.1.17 → opalacoder-0.1.19}/tests/test_planner_output.py +0 -0
  96. {opalacoder-0.1.17 → opalacoder-0.1.19}/tests/test_project_store.py +0 -0
  97. {opalacoder-0.1.17 → opalacoder-0.1.19}/tests/test_refinement_loop.py +0 -0
  98. {opalacoder-0.1.17 → opalacoder-0.1.19}/tests/test_search_bugs.py +0 -0
  99. {opalacoder-0.1.17 → opalacoder-0.1.19}/tests/test_skills_directory_loader.py +0 -0
  100. {opalacoder-0.1.17 → opalacoder-0.1.19}/tests/test_unhashable_dict_bug.py +0 -0
  101. {opalacoder-0.1.17 → opalacoder-0.1.19}/tests/test_verification_strategies.py +0 -0
  102. {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.17
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.17 alfa`), licensing, and developer details in the settings panel.
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.17 alfa`), licensing, and developer details in the settings panel.
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 = startX - mouseMoveEvent.clientX;
123
- const newWidth = Math.max(200, Math.min(800, startWidthRight + deltaX));
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 = startY - mouseMoveEvent.clientY;
127
- const newHeight = Math.max(80, Math.min(600, startHeightBottom + deltaY));
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 filename = window.prompt("Nome do novo arquivo (ex: src/utils.py):");
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
- setSelectedFile(filename);
510
- setFileContent('');
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 handleDeleteSelectedFile = async () => {
522
- if (!activeProject || !selectedFile) return;
523
- if (!window.confirm(`Tem certeza que deseja deletar o arquivo "${selectedFile}"?`)) return;
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: selectedFile
644
+ filePath: node.path
532
645
  })
533
646
  });
534
-
647
+
535
648
  if (res.ok) {
536
- addLog('info', `Arquivo excluído: ${selectedFile}`);
537
- setSelectedFile(null);
538
- setFileContent('');
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 arquivo: ${errData.error}`);
543
- alert(`Erro ao deletar arquivo: ${errData.error}`);
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 na chamada de exclusão de arquivo: ${err.message}`);
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
- <div className="vscode-tab">
1129
- <span style={{ color: '#ffffff' }}>{selectedFile.split('/').pop()}</span>
1130
- <button
1131
- onClick={() => setSelectedFile(null)}
1132
- className="vscode-tab-close-btn"
1133
- >
1134
- <X size={12} />
1135
- </button>
1136
- </div>
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
- <button
1212
- onClick={() => setIsTerminalCollapsed(!isTerminalCollapsed)}
1213
- style={{ background: 'transparent', border: 'none', cursor: 'pointer', color: '#a0a0a0' }}
1214
- >
1215
- {isTerminalCollapsed ? <Maximize2 size={12} /> : <Minimize2 size={12} />}
1216
- </button>
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={handleCreateNewFile}
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
- {selectedFile && (
1546
- <div
1547
- className="vscode-context-menu-item"
1548
- onClick={handleDeleteSelectedFile}
1549
- >
1550
- <Trash2 size={13} style={{ color: '#f48771' }} />
1551
- <span>Delete File</span>
1552
- </div>
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.17 alfa</span>
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>