janito 1.6.0__py3-none-any.whl → 1.8.0__py3-none-any.whl

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 (117) hide show
  1. janito/__init__.py +1 -1
  2. janito/agent/config.py +3 -3
  3. janito/agent/config_defaults.py +3 -2
  4. janito/agent/conversation.py +73 -27
  5. janito/agent/conversation_api.py +104 -4
  6. janito/agent/conversation_exceptions.py +6 -0
  7. janito/agent/conversation_tool_calls.py +17 -3
  8. janito/agent/event.py +24 -0
  9. janito/agent/event_dispatcher.py +24 -0
  10. janito/agent/event_handler_protocol.py +5 -0
  11. janito/agent/event_system.py +15 -0
  12. janito/agent/message_handler.py +4 -1
  13. janito/agent/message_handler_protocol.py +5 -0
  14. janito/agent/openai_client.py +5 -6
  15. janito/agent/openai_schema_generator.py +23 -4
  16. janito/agent/platform_discovery.py +90 -0
  17. janito/agent/profile_manager.py +34 -110
  18. janito/agent/queued_message_handler.py +22 -3
  19. janito/agent/rich_message_handler.py +3 -1
  20. janito/agent/templates/profiles/system_prompt_template_base.txt.j2 +14 -0
  21. janito/agent/templates/profiles/system_prompt_template_base_pt.txt.j2 +13 -0
  22. janito/agent/test_handler_protocols.py +47 -0
  23. janito/agent/tests/__init__.py +1 -0
  24. janito/agent/tool_base.py +1 -1
  25. janito/agent/tool_executor.py +109 -0
  26. janito/agent/tool_registry.py +3 -75
  27. janito/agent/tool_use_tracker.py +46 -0
  28. janito/agent/tools/__init__.py +11 -8
  29. janito/agent/tools/ask_user.py +26 -12
  30. janito/agent/tools/create_directory.py +50 -18
  31. janito/agent/tools/create_file.py +60 -29
  32. janito/agent/tools/dir_walk_utils.py +16 -0
  33. janito/agent/tools/fetch_url.py +10 -11
  34. janito/agent/tools/find_files.py +49 -40
  35. janito/agent/tools/get_lines.py +60 -25
  36. janito/agent/tools/memory.py +48 -0
  37. janito/agent/tools/move_file.py +72 -23
  38. janito/agent/tools/outline_file/__init__.py +85 -0
  39. janito/agent/tools/outline_file/formatting.py +20 -0
  40. janito/agent/tools/outline_file/markdown_outline.py +14 -0
  41. janito/agent/tools/outline_file/python_outline.py +71 -0
  42. janito/agent/tools/present_choices.py +62 -0
  43. janito/agent/tools/present_choices_test.py +18 -0
  44. janito/agent/tools/remove_directory.py +31 -26
  45. janito/agent/tools/remove_file.py +31 -13
  46. janito/agent/tools/replace_text_in_file.py +135 -36
  47. janito/agent/tools/run_bash_command.py +113 -97
  48. janito/agent/tools/run_powershell_command.py +169 -0
  49. janito/agent/tools/run_python_command.py +53 -29
  50. janito/agent/tools/search_outline.py +17 -0
  51. janito/agent/tools/search_text.py +208 -0
  52. janito/agent/tools/tools_utils.py +47 -4
  53. janito/agent/tools/utils.py +14 -15
  54. janito/agent/tools/validate_file_syntax.py +163 -0
  55. janito/cli/_print_config.py +1 -1
  56. janito/cli/arg_parser.py +36 -4
  57. janito/cli/config_commands.py +1 -1
  58. janito/cli/logging_setup.py +7 -2
  59. janito/cli/main.py +97 -3
  60. janito/cli/runner/__init__.py +0 -2
  61. janito/cli/runner/_termweb_log_utils.py +17 -0
  62. janito/cli/runner/cli_main.py +121 -89
  63. janito/cli/runner/config.py +6 -4
  64. janito/cli/termweb_starter.py +73 -0
  65. janito/cli_chat_shell/chat_loop.py +52 -13
  66. janito/cli_chat_shell/chat_state.py +1 -1
  67. janito/cli_chat_shell/chat_ui.py +2 -3
  68. janito/cli_chat_shell/commands/__init__.py +17 -6
  69. janito/cli_chat_shell/commands/{history_reset.py → history_start.py} +13 -5
  70. janito/cli_chat_shell/commands/lang.py +16 -0
  71. janito/cli_chat_shell/commands/prompt.py +42 -0
  72. janito/cli_chat_shell/commands/session_control.py +36 -1
  73. janito/cli_chat_shell/commands/sum.py +49 -0
  74. janito/cli_chat_shell/commands/termweb_log.py +86 -0
  75. janito/cli_chat_shell/commands/utility.py +5 -2
  76. janito/cli_chat_shell/commands/verbose.py +29 -0
  77. janito/cli_chat_shell/load_prompt.py +47 -8
  78. janito/cli_chat_shell/session_manager.py +9 -1
  79. janito/cli_chat_shell/shell_command_completer.py +20 -0
  80. janito/cli_chat_shell/ui.py +110 -93
  81. janito/i18n/__init__.py +35 -0
  82. janito/i18n/messages.py +23 -0
  83. janito/i18n/pt.py +46 -0
  84. janito/rich_utils.py +43 -43
  85. janito/termweb/app.py +95 -0
  86. janito/termweb/static/editor.html +238 -0
  87. janito/termweb/static/editor.html.bak +238 -0
  88. janito/termweb/static/explorer.html.bak +59 -0
  89. janito/termweb/static/favicon.ico +0 -0
  90. janito/termweb/static/favicon.ico.bak +0 -0
  91. janito/termweb/static/index.html +55 -0
  92. janito/termweb/static/index.html.bak +55 -0
  93. janito/termweb/static/index.html.bak.bak +175 -0
  94. janito/termweb/static/landing.html.bak +36 -0
  95. janito/termweb/static/termicon.svg +1 -0
  96. janito/termweb/static/termweb.css +235 -0
  97. janito/termweb/static/termweb.css.bak +286 -0
  98. janito/termweb/static/termweb.js +187 -0
  99. janito/termweb/static/termweb.js.bak +187 -0
  100. janito/termweb/static/termweb.js.bak.bak +157 -0
  101. janito/termweb/static/termweb_quickopen.js +135 -0
  102. janito/termweb/static/termweb_quickopen.js.bak +125 -0
  103. janito/web/app.py +10 -13
  104. {janito-1.6.0.dist-info → janito-1.8.0.dist-info}/METADATA +73 -32
  105. janito-1.8.0.dist-info/RECORD +127 -0
  106. {janito-1.6.0.dist-info → janito-1.8.0.dist-info}/WHEEL +1 -1
  107. janito/agent/tool_registry_core.py +0 -2
  108. janito/agent/tools/get_file_outline.py +0 -117
  109. janito/agent/tools/py_compile_file.py +0 -40
  110. janito/agent/tools/replace_file.py +0 -51
  111. janito/agent/tools/search_files.py +0 -71
  112. janito/cli/runner/scan.py +0 -44
  113. janito/cli_chat_shell/commands/system.py +0 -73
  114. janito-1.6.0.dist-info/RECORD +0 -81
  115. {janito-1.6.0.dist-info → janito-1.8.0.dist-info}/entry_points.txt +0 -0
  116. {janito-1.6.0.dist-info → janito-1.8.0.dist-info}/licenses/LICENSE +0 -0
  117. {janito-1.6.0.dist-info → janito-1.8.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,157 @@
1
+ // Directory/File browser logic for explorer with right-side preview and URL sync (using /path)
2
+ let explorerView = localStorage.getItem('explorerView') || 'list';
3
+ let currentExplorerPath = '.';
4
+
5
+ function getParentPath(path) {
6
+ // Normalize slashes
7
+ path = (path || '').replace(/\\/g, '/').replace(/^\/+|\/+$/g, '');
8
+ if (!path || path === '.' || path === '') return '.';
9
+ const parts = path.split('/');
10
+ if (parts.length <= 1) return '.';
11
+ parts.pop();
12
+ const parent = parts.join('/');
13
+ return parent === '' ? '.' : parent;
14
+ }
15
+
16
+
17
+ function setExplorerView(view) {
18
+ explorerView = view;
19
+ localStorage.setItem('explorerView', view);
20
+ document.getElementById('view-list').classList.toggle('active', view === 'list');
21
+ document.getElementById('view-icons').classList.toggle('active', view === 'icons');
22
+ }
23
+
24
+ function normalizeExplorerPath(path) {
25
+ if (!path || path === '/' || path === '' || path === '.') return '.';
26
+ return path.replace(/^\/+|\/+$/g, '');
27
+ }
28
+
29
+ function updateExplorerUrl(path, push=true) {
30
+ let url = '/';
31
+ if (path && path !== '.' && path !== '/') {
32
+ url = '/' + path.replace(/^\/+|\/+$/g, '');
33
+ }
34
+ if (push) {
35
+ console.log('[DEBUG] updateExplorerUrl: pushing url', url, 'for path', path);
36
+ window.history.pushState({ explorerPath: path }, '', url);
37
+ } else {
38
+ console.log('[DEBUG] updateExplorerUrl: not pushing, url would be', url, 'for path', path);
39
+ }
40
+ }
41
+
42
+ function renderExplorer(path, pushUrl=true) {
43
+ currentExplorerPath = normalizeExplorerPath(path);
44
+ console.log('[DEBUG] renderExplorer: path', path, 'normalized to', currentExplorerPath, 'pushUrl:', pushUrl);
45
+ fetch(`/api/explorer/${encodeURIComponent(currentExplorerPath)}`)
46
+ .then(resp => resp.json())
47
+ .then(data => {
48
+ const main = document.getElementById('explorer-main');
49
+ if (!main) return;
50
+ if (data.error) {
51
+ main.innerHTML = `<div class='error'>${data.error}</div>`;
52
+ return;
53
+ }
54
+ if (data.type === 'dir') {
55
+ let html = `<h3>Directory: ${data.path}</h3>`;
56
+ if (explorerView === 'list') {
57
+ html += `<ul class='explorer-list'>`;
58
+ if (data.path !== '.') {
59
+ const parent = getParentPath(data.path);
60
+ html += `<li><a href='#' data-path='${parent}' class='explorer-link'>(.. parent)</a></li>`;
61
+ }
62
+ for (const entry of data.entries) {
63
+ const entryPath = data.path === '.' ? entry.name : data.path + '/' + entry.name;
64
+ if (entry.is_dir) {
65
+ html += `<li><a href='#' data-path='${entryPath}' class='explorer-link'>📁 ${entry.name}</a></li>`;
66
+ } else {
67
+ html += `<li><a href='#' data-path='${entryPath}' class='explorer-link file-link'>📄 ${entry.name}</a></li>`;
68
+ }
69
+ }
70
+ html += '</ul>';
71
+ } else {
72
+ html += `<div class='explorer-icons'>`;
73
+ if (data.path !== '.') {
74
+ const parent = getParentPath(data.path);
75
+ html += `<div class='explorer-icon'><a href='#' data-path='${parent}' class='explorer-link' title='Parent'>(..)</a></div>`;
76
+ }
77
+ for (const entry of data.entries) {
78
+ const entryPath = data.path === '.' ? entry.name : data.path + '/' + entry.name;
79
+ if (entry.is_dir) {
80
+ html += `<div class='explorer-icon'><a href='#' data-path='${entryPath}' class='explorer-link' title='${entry.name}'>📁<br>${entry.name}</a></div>`;
81
+ } else {
82
+ html += `<div class='explorer-icon'><a href='#' data-path='${entryPath}' class='explorer-link file-link' title='${entry.name}'>📄<br>${entry.name}</a></div>`;
83
+ }
84
+ }
85
+ html += '</div>';
86
+ }
87
+ main.innerHTML = html;
88
+ // Clear preview panel when changing directories
89
+ const preview = document.getElementById('explorer-preview');
90
+ if (preview) preview.innerHTML = '';
91
+ if (pushUrl) updateExplorerUrl(currentExplorerPath, true);
92
+ }
93
+ // Attach click handlers
94
+ document.querySelectorAll('.explorer-link').forEach(link => {
95
+ link.onclick = function(e) {
96
+ e.preventDefault();
97
+ const p = this.getAttribute('data-path');
98
+ // If file, show preview; if dir, update explorer
99
+ if (this.classList.contains('file-link')) {
100
+ const preview = document.getElementById('explorer-preview');
101
+ if (preview) {
102
+ preview.innerHTML = `<div class='spinner' style='display:inline-block;vertical-align:middle;'></div> <span style='vertical-align:middle;'>Loading file...</span>`;
103
+ }
104
+ fetch(`/api/explorer/${encodeURIComponent(p)}`)
105
+ .then(resp => resp.json())
106
+ .then(fileData => {
107
+ if (preview && fileData.type === 'file') {
108
+ preview.innerHTML = `<h3>File: ${fileData.path}</h3><pre class='explorer-file'>${escapeHtml(fileData.content)}</pre>`;
109
+ }
110
+ });
111
+ } else {
112
+ console.log('[DEBUG] explorer-link click: navigating to dir', p);
113
+ renderExplorer(p, true);
114
+ }
115
+ };
116
+ });
117
+ });
118
+ }
119
+
120
+ function escapeHtml(text) {
121
+ const map = { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#039;' };
122
+ return text.replace(/[&<>"']'/g, m => map[m]);
123
+ }
124
+
125
+ function getExplorerPathFromLocation() {
126
+ let path = window.location.pathname;
127
+ // Remove leading slash
128
+ if (path.startsWith('/')) path = path.slice(1);
129
+ // If empty, root, or dot, treat as '.'
130
+ if (!path || path === '' || path === '.' || path === '/') return '.';
131
+ // Remove trailing slashes
132
+ path = path.replace(/\/+$/g, '');
133
+ console.log('[DEBUG] getExplorerPathFromLocation: extracted', path, 'from', window.location.pathname);
134
+ return path;
135
+ }
136
+
137
+ document.addEventListener('DOMContentLoaded', function() {
138
+ const initialPath = getExplorerPathFromLocation();
139
+ console.log('[DEBUG] DOMContentLoaded: initialPath', initialPath);
140
+ currentExplorerPath = initialPath; // Ensure currentExplorerPath matches URL
141
+ setExplorerView(localStorage.getItem('explorerView') || 'list');
142
+ renderExplorer(initialPath, false);
143
+ document.getElementById('view-list').onclick = function() {
144
+ setExplorerView('list');
145
+ renderExplorer(currentExplorerPath, false);
146
+ };
147
+ document.getElementById('view-icons').onclick = function() {
148
+ setExplorerView('icons');
149
+ renderExplorer(currentExplorerPath, false);
150
+ };
151
+ });
152
+
153
+ window.addEventListener('popstate', function(event) {
154
+ const path = (event.state && event.state.explorerPath) || getExplorerPathFromLocation();
155
+ console.log('[DEBUG] popstate: path', path, 'event.state:', event.state);
156
+ renderExplorer(path, false);
157
+ });
@@ -0,0 +1,135 @@
1
+ // Quick Open (Ctrl+P) modal logic for TermWeb
2
+ (function() {
3
+ let allFiles = [];
4
+ let modal = document.getElementById('quickopen-modal');
5
+ let input = document.getElementById('quickopen-input');
6
+ let results = document.getElementById('quickopen-results');
7
+ let selectedIdx = -1;
8
+ let loaded = false;
9
+
10
+ // Recursively fetch all file paths from /api/explorer/
11
+ async function fetchAllFiles(path = '.') {
12
+ let files = [];
13
+ try {
14
+ let resp = await fetch(`/api/explorer/${encodeURIComponent(path)}`);
15
+ let data = await resp.json();
16
+ if (data.type === 'dir' && data.entries) {
17
+ for (const entry of data.entries) {
18
+ const entryPath = path === '.' ? entry.name : path + '/' + entry.name;
19
+ if (entry.is_dir) {
20
+ files = files.concat(await fetchAllFiles(entryPath));
21
+ } else {
22
+ files.push(entryPath);
23
+ }
24
+ }
25
+ }
26
+ } catch (e) { console.error('QuickOpen fetch error', e); }
27
+ return files;
28
+ }
29
+
30
+ async function ensureLoaded() {
31
+ if (!loaded) {
32
+ allFiles = await fetchAllFiles('.');
33
+ loaded = true;
34
+ }
35
+ }
36
+
37
+ function showModal() {
38
+ modal.style.display = 'flex';
39
+ input.value = '';
40
+ results.innerHTML = '';
41
+ selectedIdx = -1;
42
+ setTimeout(() => input.focus(), 50);
43
+ }
44
+ function hideModal() {
45
+ modal.style.display = 'none';
46
+ }
47
+
48
+ function renderResults(filtered) {
49
+ results.innerHTML = '';
50
+ filtered.slice(0, 15).forEach((file, idx) => {
51
+ let li = document.createElement('li');
52
+ li.textContent = file;
53
+ li.tabIndex = -1;
54
+ li.className = 'quickopen-result' + (idx === selectedIdx ? ' selected' : '');
55
+ li.onclick = () => openFile(file);
56
+ results.appendChild(li);
57
+ });
58
+ }
59
+
60
+ // Improved openFile: poll for file link and click when ready
61
+ function openFile(path) {
62
+ hideModal();
63
+ window.renderExplorer && window.renderExplorer(getParentPath(path), false);
64
+ const maxAttempts = 20; // 20 x 100ms = 2s max
65
+ let attempts = 0;
66
+ function tryClickFile() {
67
+ let fileLinks = document.querySelectorAll(`a.file-link[data-path='${path}']`);
68
+ if (fileLinks.length) {
69
+ fileLinks[0].click();
70
+ // Optionally, add a highlight class
71
+ fileLinks[0].classList.add('quickopen-selected');
72
+ } else if (attempts < maxAttempts) {
73
+ attempts++;
74
+ setTimeout(tryClickFile, 100);
75
+ }
76
+ }
77
+ tryClickFile();
78
+ }
79
+
80
+ input.addEventListener('input', async function() {
81
+ await ensureLoaded();
82
+ let q = input.value.trim().toLowerCase();
83
+ let filtered = allFiles.filter(f => f.toLowerCase().includes(q));
84
+ selectedIdx = -1;
85
+ renderResults(filtered);
86
+ });
87
+ input.addEventListener('keydown', function(e) {
88
+ let items = results.querySelectorAll('li');
89
+ if (e.key === 'ArrowDown') {
90
+ selectedIdx = Math.min(selectedIdx + 1, items.length - 1);
91
+ renderResults(Array.from(items).map(li => li.textContent));
92
+ e.preventDefault();
93
+ } else if (e.key === 'ArrowUp') {
94
+ selectedIdx = Math.max(selectedIdx - 1, 0);
95
+ renderResults(Array.from(items).map(li => li.textContent));
96
+ e.preventDefault();
97
+ } else if (e.key === 'Enter') {
98
+ if (selectedIdx >= 0 && items[selectedIdx]) {
99
+ openFile(items[selectedIdx].textContent);
100
+ } else if (items.length === 1) {
101
+ openFile(items[0].textContent);
102
+ }
103
+ } else if (e.key === 'Escape') {
104
+ hideModal();
105
+ }
106
+ });
107
+
108
+ document.addEventListener('keydown', function(e) {
109
+ if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === 'p' && !e.shiftKey && !e.altKey) {
110
+ showModal();
111
+ e.preventDefault();
112
+ } else if (e.key === 'Escape' && modal.style.display === 'flex') {
113
+ hideModal();
114
+ }
115
+ });
116
+ modal.addEventListener('mousedown', function(e) {
117
+ if (e.target === modal) hideModal();
118
+ });
119
+
120
+ // Helper: get parent path
121
+ function getParentPath(path) {
122
+ path = (path || '').replace(/\\/g, '/').replace(/^\/+/g, '').replace(/\/+$/g, '');
123
+ if (!path || path === '.' || path === '') return '.';
124
+ const parts = path.split('/');
125
+ if (parts.length <= 1) return '.';
126
+ parts.pop();
127
+ const parent = parts.join('/');
128
+ return parent === '' ? '.' : parent;
129
+ }
130
+
131
+ // Style for selected result and quickopen-selected file link
132
+ let style = document.createElement('style');
133
+ style.textContent = `.quickopen-result.selected { background:#333; color:#8be9fd; } .quickopen-result { padding:0.3em 0.7em; cursor:pointer; border-radius:0.3em; } .quickopen-result:hover { background:#222; } a.file-link.quickopen-selected { background:#444 !important; color:#8be9fd !important; border-radius:0.3em; }`;
134
+ document.head.appendChild(style);
135
+ })();
@@ -0,0 +1,125 @@
1
+ // Quick Open (Ctrl+P) modal logic for TermWeb
2
+ (function() {
3
+ let allFiles = [];
4
+ let modal = document.getElementById('quickopen-modal');
5
+ let input = document.getElementById('quickopen-input');
6
+ let results = document.getElementById('quickopen-results');
7
+ let selectedIdx = -1;
8
+ let loaded = false;
9
+
10
+ // Recursively fetch all file paths from /api/explorer/
11
+ async function fetchAllFiles(path = '.') {
12
+ let files = [];
13
+ try {
14
+ let resp = await fetch(`/api/explorer/${encodeURIComponent(path)}`);
15
+ let data = await resp.json();
16
+ if (data.type === 'dir' && data.entries) {
17
+ for (const entry of data.entries) {
18
+ const entryPath = path === '.' ? entry.name : path + '/' + entry.name;
19
+ if (entry.is_dir) {
20
+ files = files.concat(await fetchAllFiles(entryPath));
21
+ } else {
22
+ files.push(entryPath);
23
+ }
24
+ }
25
+ }
26
+ } catch (e) { console.error('QuickOpen fetch error', e); }
27
+ return files;
28
+ }
29
+
30
+ async function ensureLoaded() {
31
+ if (!loaded) {
32
+ allFiles = await fetchAllFiles('.');
33
+ loaded = true;
34
+ }
35
+ }
36
+
37
+ function showModal() {
38
+ modal.style.display = 'flex';
39
+ input.value = '';
40
+ results.innerHTML = '';
41
+ selectedIdx = -1;
42
+ setTimeout(() => input.focus(), 50);
43
+ }
44
+ function hideModal() {
45
+ modal.style.display = 'none';
46
+ }
47
+
48
+ function renderResults(filtered) {
49
+ results.innerHTML = '';
50
+ filtered.slice(0, 15).forEach((file, idx) => {
51
+ let li = document.createElement('li');
52
+ li.textContent = file;
53
+ li.tabIndex = -1;
54
+ li.className = 'quickopen-result' + (idx === selectedIdx ? ' selected' : '');
55
+ li.onclick = () => openFile(file);
56
+ results.appendChild(li);
57
+ });
58
+ }
59
+
60
+ function openFile(path) {
61
+ hideModal();
62
+ // Simulate clicking the file in explorer
63
+ window.renderExplorer && window.renderExplorer(getParentPath(path), false);
64
+ setTimeout(() => {
65
+ let fileLinks = document.querySelectorAll(`a.file-link[data-path='${path}']`);
66
+ if (fileLinks.length) fileLinks[0].click();
67
+ }, 250);
68
+ }
69
+
70
+ input.addEventListener('input', async function() {
71
+ await ensureLoaded();
72
+ let q = input.value.trim().toLowerCase();
73
+ let filtered = allFiles.filter(f => f.toLowerCase().includes(q));
74
+ selectedIdx = -1;
75
+ renderResults(filtered);
76
+ });
77
+ input.addEventListener('keydown', function(e) {
78
+ let items = results.querySelectorAll('li');
79
+ if (e.key === 'ArrowDown') {
80
+ selectedIdx = Math.min(selectedIdx + 1, items.length - 1);
81
+ renderResults(Array.from(items).map(li => li.textContent));
82
+ e.preventDefault();
83
+ } else if (e.key === 'ArrowUp') {
84
+ selectedIdx = Math.max(selectedIdx - 1, 0);
85
+ renderResults(Array.from(items).map(li => li.textContent));
86
+ e.preventDefault();
87
+ } else if (e.key === 'Enter') {
88
+ if (selectedIdx >= 0 && items[selectedIdx]) {
89
+ openFile(items[selectedIdx].textContent);
90
+ } else if (items.length === 1) {
91
+ openFile(items[0].textContent);
92
+ }
93
+ } else if (e.key === 'Escape') {
94
+ hideModal();
95
+ }
96
+ });
97
+
98
+ document.addEventListener('keydown', function(e) {
99
+ if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === 'p' && !e.shiftKey && !e.altKey) {
100
+ showModal();
101
+ e.preventDefault();
102
+ } else if (e.key === 'Escape' && modal.style.display === 'flex') {
103
+ hideModal();
104
+ }
105
+ });
106
+ modal.addEventListener('mousedown', function(e) {
107
+ if (e.target === modal) hideModal();
108
+ });
109
+
110
+ // Helper: get parent path
111
+ function getParentPath(path) {
112
+ path = (path || '').replace(/\\/g, '/').replace(/^\/+|\/+$/g, '');
113
+ if (!path || path === '.' || path === '') return '.';
114
+ const parts = path.split('/');
115
+ if (parts.length <= 1) return '.';
116
+ parts.pop();
117
+ const parent = parts.join('/');
118
+ return parent === '' ? '.' : parent;
119
+ }
120
+
121
+ // Style for selected result
122
+ let style = document.createElement('style');
123
+ style.textContent = `.quickopen-result.selected { background:#333; color:#8be9fd; } .quickopen-result { padding:0.3em 0.7em; cursor:pointer; border-radius:0.3em; } .quickopen-result:hover { background:#222; }`;
124
+ document.head.appendChild(style);
125
+ })();
janito/web/app.py CHANGED
@@ -9,14 +9,13 @@ from flask import (
9
9
  from queue import Queue
10
10
  import json
11
11
  from janito.agent.queued_message_handler import QueuedMessageHandler
12
- from janito.agent.openai_client import Agent
13
12
  from janito.agent.profile_manager import AgentProfileManager
14
13
  import os
15
14
  import threading
16
15
  import traceback
17
16
  import sys
18
17
 
19
- from janito.agent.runtime_config import unified_config
18
+ from janito.agent.runtime_config import unified_config, runtime_config
20
19
 
21
20
  # Render system prompt from config
22
21
  role = unified_config.get("role", "software engineer")
@@ -28,14 +27,16 @@ else:
28
27
  api_key=unified_config.get("api_key"),
29
28
  model=unified_config.get("model"),
30
29
  role=role,
31
- interaction_style=unified_config.get("interaction_style", "default"),
30
+ profile_name="base",
32
31
  interaction_mode=unified_config.get("interaction_mode", "prompt"),
33
- verbose_tools=unified_config.get("verbose_tools", False),
32
+ verbose_tools=runtime_config.get("verbose_tools", False),
34
33
  base_url=unified_config.get("base_url", None),
35
- azure_openai_api_version=unified_config.get("azure_openai_api_version", None),
34
+ azure_openai_api_version=unified_config.get(
35
+ "azure_openai_api_version", "2023-05-15"
36
+ ),
36
37
  use_azure_openai=unified_config.get("use_azure_openai", False),
37
38
  )
38
- system_prompt_template = profile_manager.render_prompt()
39
+ system_prompt_template = profile_manager.system_prompt_template
39
40
 
40
41
  app = Flask(
41
42
  __name__,
@@ -60,11 +61,7 @@ stream_queue = Queue()
60
61
  message_handler = QueuedMessageHandler(stream_queue)
61
62
 
62
63
  # Instantiate the Agent with config-driven parameters (no tool_handler)
63
- agent = Agent(
64
- api_key=unified_config.get("api_key"),
65
- model=unified_config.get("model"),
66
- base_url=unified_config.get("base_url"),
67
- )
64
+ agent = profile_manager.agent
68
65
 
69
66
 
70
67
  @app.route("/get_config")
@@ -147,7 +144,7 @@ def index():
147
144
  def load_conversation():
148
145
  global conversation
149
146
  try:
150
- with open(conversation_file, "r") as f:
147
+ with open(conversation_file, "r", encoding="utf-8") as f:
151
148
  conversation = json.load(f)
152
149
  except (FileNotFoundError, json.JSONDecodeError):
153
150
  conversation = []
@@ -187,7 +184,7 @@ def execute_stream():
187
184
  )
188
185
  try:
189
186
  os.makedirs(os.path.dirname(conversation_file), exist_ok=True)
190
- with open(conversation_file, "w") as f:
187
+ with open(conversation_file, "w", encoding="utf-8") as f:
191
188
  json.dump(conversation, f, indent=2)
192
189
  except Exception as e:
193
190
  print(f"Error saving conversation: {e}")