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.
- janito/__init__.py +1 -1
- janito/agent/config.py +3 -3
- janito/agent/config_defaults.py +3 -2
- janito/agent/conversation.py +73 -27
- janito/agent/conversation_api.py +104 -4
- janito/agent/conversation_exceptions.py +6 -0
- janito/agent/conversation_tool_calls.py +17 -3
- janito/agent/event.py +24 -0
- janito/agent/event_dispatcher.py +24 -0
- janito/agent/event_handler_protocol.py +5 -0
- janito/agent/event_system.py +15 -0
- janito/agent/message_handler.py +4 -1
- janito/agent/message_handler_protocol.py +5 -0
- janito/agent/openai_client.py +5 -6
- janito/agent/openai_schema_generator.py +23 -4
- janito/agent/platform_discovery.py +90 -0
- janito/agent/profile_manager.py +34 -110
- janito/agent/queued_message_handler.py +22 -3
- janito/agent/rich_message_handler.py +3 -1
- janito/agent/templates/profiles/system_prompt_template_base.txt.j2 +14 -0
- janito/agent/templates/profiles/system_prompt_template_base_pt.txt.j2 +13 -0
- janito/agent/test_handler_protocols.py +47 -0
- janito/agent/tests/__init__.py +1 -0
- janito/agent/tool_base.py +1 -1
- janito/agent/tool_executor.py +109 -0
- janito/agent/tool_registry.py +3 -75
- janito/agent/tool_use_tracker.py +46 -0
- janito/agent/tools/__init__.py +11 -8
- janito/agent/tools/ask_user.py +26 -12
- janito/agent/tools/create_directory.py +50 -18
- janito/agent/tools/create_file.py +60 -29
- janito/agent/tools/dir_walk_utils.py +16 -0
- janito/agent/tools/fetch_url.py +10 -11
- janito/agent/tools/find_files.py +49 -40
- janito/agent/tools/get_lines.py +60 -25
- janito/agent/tools/memory.py +48 -0
- janito/agent/tools/move_file.py +72 -23
- janito/agent/tools/outline_file/__init__.py +85 -0
- janito/agent/tools/outline_file/formatting.py +20 -0
- janito/agent/tools/outline_file/markdown_outline.py +14 -0
- janito/agent/tools/outline_file/python_outline.py +71 -0
- janito/agent/tools/present_choices.py +62 -0
- janito/agent/tools/present_choices_test.py +18 -0
- janito/agent/tools/remove_directory.py +31 -26
- janito/agent/tools/remove_file.py +31 -13
- janito/agent/tools/replace_text_in_file.py +135 -36
- janito/agent/tools/run_bash_command.py +113 -97
- janito/agent/tools/run_powershell_command.py +169 -0
- janito/agent/tools/run_python_command.py +53 -29
- janito/agent/tools/search_outline.py +17 -0
- janito/agent/tools/search_text.py +208 -0
- janito/agent/tools/tools_utils.py +47 -4
- janito/agent/tools/utils.py +14 -15
- janito/agent/tools/validate_file_syntax.py +163 -0
- janito/cli/_print_config.py +1 -1
- janito/cli/arg_parser.py +36 -4
- janito/cli/config_commands.py +1 -1
- janito/cli/logging_setup.py +7 -2
- janito/cli/main.py +97 -3
- janito/cli/runner/__init__.py +0 -2
- janito/cli/runner/_termweb_log_utils.py +17 -0
- janito/cli/runner/cli_main.py +121 -89
- janito/cli/runner/config.py +6 -4
- janito/cli/termweb_starter.py +73 -0
- janito/cli_chat_shell/chat_loop.py +52 -13
- janito/cli_chat_shell/chat_state.py +1 -1
- janito/cli_chat_shell/chat_ui.py +2 -3
- janito/cli_chat_shell/commands/__init__.py +17 -6
- janito/cli_chat_shell/commands/{history_reset.py → history_start.py} +13 -5
- janito/cli_chat_shell/commands/lang.py +16 -0
- janito/cli_chat_shell/commands/prompt.py +42 -0
- janito/cli_chat_shell/commands/session_control.py +36 -1
- janito/cli_chat_shell/commands/sum.py +49 -0
- janito/cli_chat_shell/commands/termweb_log.py +86 -0
- janito/cli_chat_shell/commands/utility.py +5 -2
- janito/cli_chat_shell/commands/verbose.py +29 -0
- janito/cli_chat_shell/load_prompt.py +47 -8
- janito/cli_chat_shell/session_manager.py +9 -1
- janito/cli_chat_shell/shell_command_completer.py +20 -0
- janito/cli_chat_shell/ui.py +110 -93
- janito/i18n/__init__.py +35 -0
- janito/i18n/messages.py +23 -0
- janito/i18n/pt.py +46 -0
- janito/rich_utils.py +43 -43
- janito/termweb/app.py +95 -0
- janito/termweb/static/editor.html +238 -0
- janito/termweb/static/editor.html.bak +238 -0
- janito/termweb/static/explorer.html.bak +59 -0
- janito/termweb/static/favicon.ico +0 -0
- janito/termweb/static/favicon.ico.bak +0 -0
- janito/termweb/static/index.html +55 -0
- janito/termweb/static/index.html.bak +55 -0
- janito/termweb/static/index.html.bak.bak +175 -0
- janito/termweb/static/landing.html.bak +36 -0
- janito/termweb/static/termicon.svg +1 -0
- janito/termweb/static/termweb.css +235 -0
- janito/termweb/static/termweb.css.bak +286 -0
- janito/termweb/static/termweb.js +187 -0
- janito/termweb/static/termweb.js.bak +187 -0
- janito/termweb/static/termweb.js.bak.bak +157 -0
- janito/termweb/static/termweb_quickopen.js +135 -0
- janito/termweb/static/termweb_quickopen.js.bak +125 -0
- janito/web/app.py +10 -13
- {janito-1.6.0.dist-info → janito-1.8.0.dist-info}/METADATA +73 -32
- janito-1.8.0.dist-info/RECORD +127 -0
- {janito-1.6.0.dist-info → janito-1.8.0.dist-info}/WHEEL +1 -1
- janito/agent/tool_registry_core.py +0 -2
- janito/agent/tools/get_file_outline.py +0 -117
- janito/agent/tools/py_compile_file.py +0 -40
- janito/agent/tools/replace_file.py +0 -51
- janito/agent/tools/search_files.py +0 -71
- janito/cli/runner/scan.py +0 -44
- janito/cli_chat_shell/commands/system.py +0 -73
- janito-1.6.0.dist-info/RECORD +0 -81
- {janito-1.6.0.dist-info → janito-1.8.0.dist-info}/entry_points.txt +0 -0
- {janito-1.6.0.dist-info → janito-1.8.0.dist-info}/licenses/LICENSE +0 -0
- {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 = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' };
|
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
|
-
|
30
|
+
profile_name="base",
|
32
31
|
interaction_mode=unified_config.get("interaction_mode", "prompt"),
|
33
|
-
verbose_tools=
|
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(
|
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.
|
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 =
|
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}")
|