agentgui 1.0.93 → 1.0.95
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.prd +1 -0
- package/database.js +43 -1
- package/lib/claude-runner.js +24 -22
- package/package.json +5 -3
- package/server.js +209 -146
- package/static/index.html +354 -94
- package/static/js/client.js +218 -144
- package/static/js/features.js +243 -0
- package/static/js/websocket-manager.js +1 -1
- package/static/styles.css +46 -4
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Features Module
|
|
3
|
+
* Drag-and-drop file upload, fsbrowse file browser toggle, mobile sidebar
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
(function() {
|
|
7
|
+
const BASE = window.__BASE_URL || '';
|
|
8
|
+
let currentConversation = null;
|
|
9
|
+
let currentView = 'chat';
|
|
10
|
+
let dragCounter = 0;
|
|
11
|
+
|
|
12
|
+
function init() {
|
|
13
|
+
setupHamburgerMenu();
|
|
14
|
+
setupDragAndDrop();
|
|
15
|
+
setupViewToggle();
|
|
16
|
+
setupConversationListener();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// --- Hamburger Menu & Mobile Sidebar ---
|
|
20
|
+
function setupHamburgerMenu() {
|
|
21
|
+
const hamburger = document.querySelector('[data-hamburger]');
|
|
22
|
+
const sidebar = document.querySelector('[data-sidebar]');
|
|
23
|
+
const overlay = document.querySelector('[data-sidebar-overlay]');
|
|
24
|
+
|
|
25
|
+
if (!hamburger || !sidebar) return;
|
|
26
|
+
|
|
27
|
+
hamburger.addEventListener('click', function(e) {
|
|
28
|
+
e.stopPropagation();
|
|
29
|
+
const isOpen = sidebar.classList.contains('mobile-visible');
|
|
30
|
+
if (isOpen) {
|
|
31
|
+
closeSidebar();
|
|
32
|
+
} else {
|
|
33
|
+
openSidebar();
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
if (overlay) {
|
|
38
|
+
overlay.addEventListener('click', closeSidebar);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function openSidebar() {
|
|
42
|
+
sidebar.classList.add('mobile-visible');
|
|
43
|
+
if (overlay) overlay.classList.add('visible');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function closeSidebar() {
|
|
47
|
+
sidebar.classList.remove('mobile-visible');
|
|
48
|
+
if (overlay) overlay.classList.remove('visible');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Close sidebar when conversation is selected (mobile)
|
|
52
|
+
window.addEventListener('conversation-selected', function() {
|
|
53
|
+
if (window.innerWidth <= 768) {
|
|
54
|
+
closeSidebar();
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// Close sidebar on window resize to desktop
|
|
59
|
+
window.addEventListener('resize', function() {
|
|
60
|
+
if (window.innerWidth > 768) {
|
|
61
|
+
closeSidebar();
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// --- Drag and Drop File Upload ---
|
|
67
|
+
function setupDragAndDrop() {
|
|
68
|
+
const dropZone = document.querySelector('[data-drop-zone]');
|
|
69
|
+
const overlay = document.getElementById('dropZoneOverlay');
|
|
70
|
+
|
|
71
|
+
if (!dropZone || !overlay) return;
|
|
72
|
+
|
|
73
|
+
dropZone.addEventListener('dragenter', function(e) {
|
|
74
|
+
e.preventDefault();
|
|
75
|
+
e.stopPropagation();
|
|
76
|
+
dragCounter++;
|
|
77
|
+
if (dragCounter === 1) {
|
|
78
|
+
overlay.classList.add('visible');
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
dropZone.addEventListener('dragover', function(e) {
|
|
83
|
+
e.preventDefault();
|
|
84
|
+
e.stopPropagation();
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
dropZone.addEventListener('dragleave', function(e) {
|
|
88
|
+
e.preventDefault();
|
|
89
|
+
e.stopPropagation();
|
|
90
|
+
dragCounter--;
|
|
91
|
+
if (dragCounter <= 0) {
|
|
92
|
+
dragCounter = 0;
|
|
93
|
+
overlay.classList.remove('visible');
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
dropZone.addEventListener('drop', function(e) {
|
|
98
|
+
e.preventDefault();
|
|
99
|
+
e.stopPropagation();
|
|
100
|
+
dragCounter = 0;
|
|
101
|
+
overlay.classList.remove('visible');
|
|
102
|
+
|
|
103
|
+
if (!currentConversation) {
|
|
104
|
+
showToast('Select a conversation first', 'error');
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const files = e.dataTransfer.files;
|
|
109
|
+
if (!files || files.length === 0) return;
|
|
110
|
+
|
|
111
|
+
uploadFiles(files);
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function uploadFiles(files) {
|
|
116
|
+
if (!currentConversation) {
|
|
117
|
+
showToast('No conversation selected', 'error');
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const formData = new FormData();
|
|
122
|
+
for (let i = 0; i < files.length; i++) {
|
|
123
|
+
formData.append('file', files[i]);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
showToast('Uploading ' + files.length + ' file(s)...', 'info');
|
|
127
|
+
|
|
128
|
+
fetch(BASE + '/api/upload/' + currentConversation, {
|
|
129
|
+
method: 'POST',
|
|
130
|
+
body: formData
|
|
131
|
+
})
|
|
132
|
+
.then(function(res) { return res.json(); })
|
|
133
|
+
.then(function(data) {
|
|
134
|
+
if (data.ok) {
|
|
135
|
+
showToast(data.count + ' file(s) uploaded', 'success');
|
|
136
|
+
} else {
|
|
137
|
+
showToast('Upload failed: ' + (data.error || 'Unknown error'), 'error');
|
|
138
|
+
}
|
|
139
|
+
})
|
|
140
|
+
.catch(function(err) {
|
|
141
|
+
showToast('Upload failed: ' + err.message, 'error');
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function showToast(message, type) {
|
|
146
|
+
var existing = document.querySelector('.upload-toast');
|
|
147
|
+
if (existing) existing.remove();
|
|
148
|
+
|
|
149
|
+
var toast = document.createElement('div');
|
|
150
|
+
toast.className = 'upload-toast ' + (type || 'info');
|
|
151
|
+
toast.textContent = message;
|
|
152
|
+
document.body.appendChild(toast);
|
|
153
|
+
|
|
154
|
+
setTimeout(function() {
|
|
155
|
+
toast.style.opacity = '0';
|
|
156
|
+
setTimeout(function() { toast.remove(); }, 300);
|
|
157
|
+
}, 3000);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// --- View Toggle (Chat / Files) ---
|
|
161
|
+
function setupViewToggle() {
|
|
162
|
+
var bar = document.getElementById('viewToggleBar');
|
|
163
|
+
if (!bar) return;
|
|
164
|
+
|
|
165
|
+
var buttons = bar.querySelectorAll('.view-toggle-btn');
|
|
166
|
+
buttons.forEach(function(btn) {
|
|
167
|
+
btn.addEventListener('click', function() {
|
|
168
|
+
var view = btn.dataset.view;
|
|
169
|
+
switchView(view);
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function switchView(view) {
|
|
175
|
+
currentView = view;
|
|
176
|
+
var bar = document.getElementById('viewToggleBar');
|
|
177
|
+
var chatArea = document.getElementById('output-scroll');
|
|
178
|
+
var execPanel = document.querySelector('.execution-panel');
|
|
179
|
+
var fileBrowser = document.getElementById('fileBrowserContainer');
|
|
180
|
+
var iframe = document.getElementById('fileBrowserIframe');
|
|
181
|
+
|
|
182
|
+
if (!bar) return;
|
|
183
|
+
|
|
184
|
+
// Update active button
|
|
185
|
+
bar.querySelectorAll('.view-toggle-btn').forEach(function(btn) {
|
|
186
|
+
btn.classList.toggle('active', btn.dataset.view === view);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
if (view === 'files') {
|
|
190
|
+
if (chatArea) chatArea.style.display = 'none';
|
|
191
|
+
if (execPanel) execPanel.style.display = 'none';
|
|
192
|
+
if (fileBrowser) {
|
|
193
|
+
fileBrowser.style.display = 'flex';
|
|
194
|
+
if (iframe && currentConversation) {
|
|
195
|
+
var src = BASE + '/files/' + currentConversation + '/';
|
|
196
|
+
if (iframe.src !== location.origin + src) {
|
|
197
|
+
iframe.src = src;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
} else {
|
|
202
|
+
if (chatArea) chatArea.style.display = '';
|
|
203
|
+
if (execPanel) execPanel.style.display = '';
|
|
204
|
+
if (fileBrowser) fileBrowser.style.display = 'none';
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function updateViewToggleVisibility() {
|
|
209
|
+
var bar = document.getElementById('viewToggleBar');
|
|
210
|
+
if (!bar) return;
|
|
211
|
+
|
|
212
|
+
// Show toggle bar only when a conversation is selected
|
|
213
|
+
if (currentConversation) {
|
|
214
|
+
bar.style.display = 'flex';
|
|
215
|
+
} else {
|
|
216
|
+
bar.style.display = 'none';
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// --- Conversation Listener ---
|
|
221
|
+
function setupConversationListener() {
|
|
222
|
+
window.addEventListener('conversation-selected', function(e) {
|
|
223
|
+
currentConversation = e.detail.conversationId;
|
|
224
|
+
updateViewToggleVisibility();
|
|
225
|
+
// If currently in files view, reload the iframe
|
|
226
|
+
if (currentView === 'files') {
|
|
227
|
+
switchView('files');
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
// Also listen for conversation created
|
|
232
|
+
window.addEventListener('create-new-conversation', function() {
|
|
233
|
+
// Will be updated when conversation-selected fires
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Initialize when DOM is ready
|
|
238
|
+
if (document.readyState === 'loading') {
|
|
239
|
+
document.addEventListener('DOMContentLoaded', init);
|
|
240
|
+
} else {
|
|
241
|
+
init();
|
|
242
|
+
}
|
|
243
|
+
})();
|
|
@@ -50,7 +50,7 @@ class WebSocketManager {
|
|
|
50
50
|
getWebSocketURL() {
|
|
51
51
|
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
52
52
|
const baseURL = window.__BASE_URL || '/gm';
|
|
53
|
-
return `${protocol}//${window.location.host}${baseURL}/
|
|
53
|
+
return `${protocol}//${window.location.host}${baseURL}/sync`;
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
/**
|
package/static/styles.css
CHANGED
|
@@ -1201,18 +1201,21 @@ html, body {
|
|
|
1201
1201
|
/* Responsive design */
|
|
1202
1202
|
@media (max-width: 768px) {
|
|
1203
1203
|
.sidebar {
|
|
1204
|
-
position:
|
|
1204
|
+
position: fixed;
|
|
1205
1205
|
left: 0;
|
|
1206
1206
|
top: 0;
|
|
1207
1207
|
bottom: 0;
|
|
1208
|
-
z-index:
|
|
1208
|
+
z-index: 1000;
|
|
1209
1209
|
width: 280px;
|
|
1210
1210
|
transform: translateX(-100%);
|
|
1211
|
-
box-shadow:
|
|
1211
|
+
box-shadow: none;
|
|
1212
|
+
transition: transform 0.3s ease;
|
|
1212
1213
|
}
|
|
1213
1214
|
|
|
1214
|
-
.sidebar.open
|
|
1215
|
+
.sidebar.open,
|
|
1216
|
+
.sidebar.mobile-visible {
|
|
1215
1217
|
transform: translateX(0);
|
|
1218
|
+
box-shadow: var(--shadow-lg);
|
|
1216
1219
|
}
|
|
1217
1220
|
|
|
1218
1221
|
.sidebar-toggle {
|
|
@@ -1229,6 +1232,7 @@ html, body {
|
|
|
1229
1232
|
|
|
1230
1233
|
.chat-input-section {
|
|
1231
1234
|
padding: 1rem 1.5rem;
|
|
1235
|
+
padding-bottom: calc(1rem + env(safe-area-inset-bottom));
|
|
1232
1236
|
}
|
|
1233
1237
|
|
|
1234
1238
|
.chat-footer {
|
|
@@ -1846,3 +1850,41 @@ p code {
|
|
|
1846
1850
|
word-wrap: break-word;
|
|
1847
1851
|
line-height: 1.5;
|
|
1848
1852
|
}
|
|
1853
|
+
|
|
1854
|
+
/* Touch-friendly interactive elements */
|
|
1855
|
+
@media (pointer: coarse) {
|
|
1856
|
+
.chat-item,
|
|
1857
|
+
.new-chat-btn,
|
|
1858
|
+
.settings-btn,
|
|
1859
|
+
.action-btn,
|
|
1860
|
+
.gm-btn,
|
|
1861
|
+
.chat-option-btn,
|
|
1862
|
+
.close-btn {
|
|
1863
|
+
min-height: 44px;
|
|
1864
|
+
min-width: 44px;
|
|
1865
|
+
}
|
|
1866
|
+
|
|
1867
|
+
.chat-input {
|
|
1868
|
+
font-size: 16px;
|
|
1869
|
+
min-height: 44px;
|
|
1870
|
+
}
|
|
1871
|
+
|
|
1872
|
+
.chat-messages {
|
|
1873
|
+
-webkit-overflow-scrolling: touch;
|
|
1874
|
+
}
|
|
1875
|
+
}
|
|
1876
|
+
|
|
1877
|
+
/* Safe area padding for notched phones */
|
|
1878
|
+
@supports (padding-top: env(safe-area-inset-top)) {
|
|
1879
|
+
#app {
|
|
1880
|
+
padding-top: env(safe-area-inset-top);
|
|
1881
|
+
}
|
|
1882
|
+
|
|
1883
|
+
.chat-input-section {
|
|
1884
|
+
padding-bottom: calc(1rem + env(safe-area-inset-bottom));
|
|
1885
|
+
}
|
|
1886
|
+
|
|
1887
|
+
.chat-footer {
|
|
1888
|
+
padding-bottom: calc(0.75rem + env(safe-area-inset-bottom));
|
|
1889
|
+
}
|
|
1890
|
+
}
|