agentgui 1.0.458 → 1.0.460

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentgui",
3
- "version": "1.0.458",
3
+ "version": "1.0.460",
4
4
  "description": "Multi-agent ACP client with real-time communication",
5
5
  "type": "module",
6
6
  "main": "server.js",
@@ -0,0 +1,316 @@
1
+ .tools-manager-btn {
2
+ background: none;
3
+ border: none;
4
+ color: var(--color-text-primary);
5
+ cursor: pointer;
6
+ padding: 0.5rem;
7
+ display: flex;
8
+ align-items: center;
9
+ justify-content: center;
10
+ border-radius: 0.375rem;
11
+ transition: background-color 0.2s, color 0.2s;
12
+ position: relative;
13
+ }
14
+
15
+ .tools-manager-btn:hover {
16
+ background-color: var(--color-bg-secondary);
17
+ }
18
+
19
+ .tools-manager-btn svg {
20
+ width: 1.25rem;
21
+ height: 1.25rem;
22
+ stroke-width: 2;
23
+ }
24
+
25
+ .tools-popup {
26
+ position: fixed;
27
+ inset: 0;
28
+ background: rgba(0, 0, 0, 0.5);
29
+ display: none;
30
+ align-items: center;
31
+ justify-content: center;
32
+ z-index: 9999;
33
+ }
34
+
35
+ .tools-popup.open {
36
+ display: flex;
37
+ }
38
+
39
+ .tools-popup-content {
40
+ background: var(--color-bg-secondary);
41
+ border-radius: 1rem;
42
+ width: 90%;
43
+ max-width: 32rem;
44
+ max-height: 80vh;
45
+ display: flex;
46
+ flex-direction: column;
47
+ box-shadow: 0 25px 50px rgba(0, 0, 0, 0.5);
48
+ color: var(--color-text-primary);
49
+ font-family: system-ui, -apple-system, sans-serif;
50
+ }
51
+
52
+ .tools-popup-header {
53
+ display: flex;
54
+ justify-content: space-between;
55
+ align-items: center;
56
+ padding: 1.25rem 1.5rem;
57
+ flex-shrink: 0;
58
+ border-bottom: 1px solid var(--color-border);
59
+ }
60
+
61
+ .tools-popup-header h2 {
62
+ margin: 0;
63
+ font-size: 1.25rem;
64
+ font-weight: 700;
65
+ }
66
+
67
+ .tools-popup-close {
68
+ background: none;
69
+ border: none;
70
+ color: var(--color-text-secondary);
71
+ font-size: 1.5rem;
72
+ cursor: pointer;
73
+ padding: 0;
74
+ line-height: 1;
75
+ transition: color 0.2s;
76
+ }
77
+
78
+ .tools-popup-close:hover {
79
+ color: var(--color-text-primary);
80
+ }
81
+
82
+ .tools-popup-scroll {
83
+ flex: 1;
84
+ overflow-y: auto;
85
+ padding: 1rem;
86
+ display: flex;
87
+ flex-direction: column;
88
+ gap: 0.75rem;
89
+ }
90
+
91
+ .tools-popup-scroll::-webkit-scrollbar {
92
+ width: 0.5rem;
93
+ }
94
+
95
+ .tools-popup-scroll::-webkit-scrollbar-track {
96
+ background: transparent;
97
+ }
98
+
99
+ .tools-popup-scroll::-webkit-scrollbar-thumb {
100
+ background: var(--color-border);
101
+ border-radius: 0.25rem;
102
+ }
103
+
104
+ .tools-popup-scroll::-webkit-scrollbar-thumb:hover {
105
+ background: var(--color-text-secondary);
106
+ }
107
+
108
+ .tool-item {
109
+ padding: 1rem;
110
+ border: 1px solid var(--color-border);
111
+ border-radius: 0.5rem;
112
+ background: var(--color-bg-primary);
113
+ display: flex;
114
+ flex-direction: column;
115
+ gap: 0.5rem;
116
+ transition: border-color 0.2s, background-color 0.2s;
117
+ }
118
+
119
+ .tool-item:hover {
120
+ border-color: var(--color-primary);
121
+ background: var(--color-bg-secondary);
122
+ }
123
+
124
+ .tool-header {
125
+ display: flex;
126
+ align-items: center;
127
+ gap: 0.75rem;
128
+ justify-content: space-between;
129
+ }
130
+
131
+ .tool-name {
132
+ font-weight: 600;
133
+ font-size: 0.95rem;
134
+ flex: 1;
135
+ }
136
+
137
+ .tool-status-indicator {
138
+ display: inline-flex;
139
+ align-items: center;
140
+ gap: 0.375rem;
141
+ font-size: 0.8rem;
142
+ padding: 0.25rem 0.5rem;
143
+ border-radius: 0.25rem;
144
+ background: var(--color-bg-tertiary);
145
+ color: var(--color-text-secondary);
146
+ }
147
+
148
+ .tool-status-indicator.installed {
149
+ background: rgba(16, 185, 129, 0.15);
150
+ color: #10b981;
151
+ }
152
+
153
+ .tool-status-indicator.not-installed {
154
+ background: rgba(107, 114, 128, 0.15);
155
+ color: #9ca3af;
156
+ }
157
+
158
+ .tool-status-indicator.installing {
159
+ background: rgba(59, 130, 246, 0.15);
160
+ color: #3b82f6;
161
+ }
162
+
163
+ .tool-status-indicator.updating {
164
+ background: rgba(245, 158, 11, 0.15);
165
+ color: #f59e0b;
166
+ }
167
+
168
+ .tool-status-indicator.failed {
169
+ background: rgba(239, 68, 68, 0.15);
170
+ color: #ef4444;
171
+ }
172
+
173
+ .tool-status-dot {
174
+ width: 0.375rem;
175
+ height: 0.375rem;
176
+ border-radius: 50%;
177
+ background: currentColor;
178
+ display: inline-block;
179
+ }
180
+
181
+ .tool-details {
182
+ font-size: 0.8rem;
183
+ color: var(--color-text-secondary);
184
+ display: flex;
185
+ align-items: center;
186
+ gap: 0.5rem;
187
+ }
188
+
189
+ .tool-progress-container {
190
+ display: flex;
191
+ flex-direction: column;
192
+ gap: 0.25rem;
193
+ }
194
+
195
+ .tool-progress-bar {
196
+ width: 100%;
197
+ height: 0.375rem;
198
+ background: var(--color-bg-tertiary);
199
+ border-radius: 0.1875rem;
200
+ overflow: hidden;
201
+ }
202
+
203
+ .tool-progress-fill {
204
+ height: 100%;
205
+ background: linear-gradient(90deg, #3b82f6, #2563eb);
206
+ width: 0%;
207
+ transition: width 0.3s ease;
208
+ border-radius: 0.1875rem;
209
+ }
210
+
211
+ .tool-progress-text {
212
+ font-size: 0.75rem;
213
+ color: var(--color-text-secondary);
214
+ }
215
+
216
+ .tool-actions {
217
+ display: flex;
218
+ gap: 0.5rem;
219
+ flex-wrap: wrap;
220
+ }
221
+
222
+ .tool-btn {
223
+ padding: 0.5rem 0.875rem;
224
+ border: none;
225
+ border-radius: 0.375rem;
226
+ cursor: pointer;
227
+ font-size: 0.8rem;
228
+ font-weight: 600;
229
+ transition: all 0.2s;
230
+ flex: 1;
231
+ min-width: fit-content;
232
+ white-space: nowrap;
233
+ }
234
+
235
+ .tool-btn:disabled {
236
+ opacity: 0.6;
237
+ cursor: not-allowed;
238
+ }
239
+
240
+ .tool-btn-primary {
241
+ background: var(--color-primary);
242
+ color: white;
243
+ }
244
+
245
+ .tool-btn-primary:hover:not(:disabled) {
246
+ background: var(--color-primary-dark);
247
+ transform: translateY(-0.0625rem);
248
+ }
249
+
250
+ .tool-btn-secondary {
251
+ background: transparent;
252
+ color: var(--color-text-primary);
253
+ border: 1px solid var(--color-border);
254
+ }
255
+
256
+ .tool-btn-secondary:hover:not(:disabled) {
257
+ background: var(--color-bg-tertiary);
258
+ border-color: var(--color-primary);
259
+ }
260
+
261
+ .tool-error-message {
262
+ font-size: 0.75rem;
263
+ color: #ef4444;
264
+ background: rgba(239, 68, 68, 0.1);
265
+ padding: 0.5rem;
266
+ border-radius: 0.25rem;
267
+ border-left: 2px solid #ef4444;
268
+ }
269
+
270
+ .tools-popup-footer {
271
+ padding: 1rem 1.5rem;
272
+ border-top: 1px solid var(--color-border);
273
+ flex-shrink: 0;
274
+ display: flex;
275
+ justify-content: flex-end;
276
+ gap: 0.75rem;
277
+ }
278
+
279
+ .tools-popup-refresh-btn {
280
+ padding: 0.625rem 1rem;
281
+ background: var(--color-primary);
282
+ color: white;
283
+ border: none;
284
+ border-radius: 0.375rem;
285
+ cursor: pointer;
286
+ font-size: 0.8rem;
287
+ font-weight: 600;
288
+ transition: all 0.2s;
289
+ }
290
+
291
+ .tools-popup-refresh-btn:hover:not(:disabled) {
292
+ background: var(--color-primary-dark);
293
+ transform: translateY(-0.0625rem);
294
+ }
295
+
296
+ .tools-popup-refresh-btn:disabled {
297
+ opacity: 0.6;
298
+ cursor: not-allowed;
299
+ }
300
+
301
+ .tool-empty-state {
302
+ padding: 2rem 1rem;
303
+ text-align: center;
304
+ color: var(--color-text-secondary);
305
+ }
306
+
307
+ .tool-empty-state-icon {
308
+ font-size: 2.5rem;
309
+ margin-bottom: 0.75rem;
310
+ opacity: 0.5;
311
+ }
312
+
313
+ .tool-empty-state-text {
314
+ font-size: 0.875rem;
315
+ margin-bottom: 0.5rem;
316
+ }
package/static/index.html CHANGED
@@ -3035,7 +3035,7 @@
3035
3035
  .toast-error { background: var(--color-error); color: white; }
3036
3036
  .toast-warning { background: var(--color-warning); color: white; }
3037
3037
  </style>
3038
- <link rel="stylesheet" href="/gm/css/tool-status.css">
3038
+ <link rel="stylesheet" href="/gm/css/tools-popup.css">
3039
3039
  </head>
3040
3040
  <body>
3041
3041
  <!-- Sidebar overlay (mobile) -->
@@ -3096,6 +3096,21 @@
3096
3096
  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"></path></svg>
3097
3097
  <div class="agent-auth-dropdown" id="agentAuthDropdown"></div>
3098
3098
  </button>
3099
+ <button class="header-icon-btn tools-manager-btn" id="toolsManagerBtn" title="Tools & extensions" aria-label="Tools & extensions" style="display:none;">
3100
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 6V2M12 22v-4M6 12H2M22 12h-4M7.07 7.07L4.22 4.22M19.78 19.78L16.93 16.93M7.07 16.93L4.22 19.78M19.78 4.22L16.93 7.07"/></svg>
3101
+ <div class="tools-popup" id="toolsPopup">
3102
+ <div class="tools-popup-content" onclick="event.stopPropagation()">
3103
+ <div class="tools-popup-header">
3104
+ <h2>Tools & Extensions</h2>
3105
+ <button class="tools-popup-close" onclick="document.getElementById('toolsPopup').classList.remove('open')">×</button>
3106
+ </div>
3107
+ <div class="tools-popup-scroll"></div>
3108
+ <div class="tools-popup-footer">
3109
+ <button class="tools-popup-refresh-btn" onclick="window.toolsManager.refresh()">Refresh All</button>
3110
+ </div>
3111
+ </div>
3112
+ </div>
3113
+ </button>
3099
3114
  <div class="status-badge">
3100
3115
  <div class="status-indicator" data-status="disconnected"></div>
3101
3116
  <span id="connectionStatus" data-status-indicator>Disconnected</span>
@@ -3122,7 +3137,6 @@
3122
3137
  <button class="view-toggle-btn" data-view="files">Files</button>
3123
3138
  <button class="view-toggle-btn" data-view="voice" id="voiceTabBtn" style="display:none;">Voice</button>
3124
3139
  <button class="view-toggle-btn" data-view="terminal" id="terminalTabBtn">Terminal</button>
3125
- <button class="view-toggle-btn" data-view="tools">Tools</button>
3126
3140
  </div>
3127
3141
 
3128
3142
  <!-- Messages scroll area -->
@@ -3186,11 +3200,6 @@
3186
3200
  </div>
3187
3201
  </div>
3188
3202
 
3189
- <!-- Tools management view -->
3190
- <div id="toolsContainer" class="tools-container" style="display:none;">
3191
- <div id="tool-status-container"></div>
3192
- </div>
3193
-
3194
3203
  <!-- Input area: fixed at bottom -->
3195
3204
  <div class="input-section">
3196
3205
  <div class="input-wrapper">
@@ -3260,7 +3269,7 @@
3260
3269
  <script defer src="/gm/js/conversations.js"></script>
3261
3270
  <script defer src="/gm/js/terminal.js"></script>
3262
3271
  <script defer src="/gm/js/script-runner.js"></script>
3263
- <script defer src="/gm/js/tool-status.js"></script>
3272
+ <script defer src="/gm/js/tools-manager.js"></script>
3264
3273
  <script defer src="/gm/js/client.js"></script>
3265
3274
  <script type="module" src="/gm/js/voice.js"></script>
3266
3275
  <script defer src="/gm/js/features.js"></script>
@@ -2327,6 +2327,11 @@ class AgentGUIClient {
2327
2327
  this.state.currentSession = null;
2328
2328
  }
2329
2329
 
2330
+ const cachedConv = this.state.conversations.find(c => c.id === conversationId);
2331
+ if (cachedConv && this.state.currentConversation?.id !== conversationId) {
2332
+ this.state.currentConversation = cachedConv;
2333
+ }
2334
+
2330
2335
  this.updateUrlForConversation(conversationId);
2331
2336
  if (this.wsManager.isConnected) {
2332
2337
  this.wsManager.sendMessage({ type: 'subscribe', conversationId });
@@ -149,7 +149,6 @@
149
149
  var fileIframe = document.getElementById('fileBrowserIframe');
150
150
  var voiceContainer = document.getElementById('voiceContainer');
151
151
  var terminalContainer = document.getElementById('terminalContainer');
152
- var toolsContainer = document.getElementById('toolsContainer');
153
152
  if (!bar) return;
154
153
  bar.querySelectorAll('.view-toggle-btn').forEach(function(btn) {
155
154
  btn.classList.toggle('active', btn.dataset.view === view);
@@ -159,7 +158,6 @@
159
158
  if (fileBrowser) fileBrowser.style.display = view === 'files' ? 'flex' : 'none';
160
159
  if (voiceContainer) voiceContainer.style.display = view === 'voice' ? 'flex' : 'none';
161
160
  if (terminalContainer) terminalContainer.style.display = view === 'terminal' ? 'flex' : 'none';
162
- if (toolsContainer) toolsContainer.style.display = view === 'tools' ? 'flex' : 'none';
163
161
  if (view === 'files' && fileIframe && currentConversation) {
164
162
  var src = BASE + '/files/' + currentConversation + '/';
165
163
  if (fileIframe.src !== location.origin + src) fileIframe.src = src;
@@ -0,0 +1,213 @@
1
+ (function() {
2
+ var btn = document.getElementById('toolsManagerBtn');
3
+ var popup = document.getElementById('toolsPopup');
4
+ var tools = [];
5
+ var isRefreshing = false;
6
+ var operationInProgress = new Set();
7
+
8
+ function init() {
9
+ if (!btn || !popup) return;
10
+ btn.style.display = 'flex';
11
+ btn.addEventListener('click', togglePopup);
12
+ document.addEventListener('click', function(e) {
13
+ if (!btn.contains(e.target) && !popup.contains(e.target)) closePopup();
14
+ });
15
+ window.addEventListener('ws-message', onWsMessage);
16
+ refresh();
17
+ }
18
+
19
+ function refresh() {
20
+ fetchTools();
21
+ }
22
+
23
+ function fetchTools() {
24
+ window.wsClient.rpc('tools.list')
25
+ .then(function(data) {
26
+ tools = data.tools || [];
27
+ render();
28
+ })
29
+ .catch(function() {
30
+ fetch('/gm/api/tools')
31
+ .then(r => r.json())
32
+ .then(d => {
33
+ tools = d.tools || [];
34
+ render();
35
+ })
36
+ .catch(function(e) {
37
+ console.error('[TOOLS-MGR]', e.message);
38
+ });
39
+ });
40
+ }
41
+
42
+ function getStatusColor(tool) {
43
+ if (tool.status === 'installing' || tool.status === 'updating') return '#3b82f6';
44
+ if (tool.status === 'installed' && tool.hasUpdate) return '#f59e0b';
45
+ if (tool.status === 'installed') return '#10b981';
46
+ if (tool.status === 'failed') return '#ef4444';
47
+ return '#6b7280';
48
+ }
49
+
50
+ function getStatusText(tool) {
51
+ if (tool.status === 'installing') return 'Installing...';
52
+ if (tool.status === 'updating') return 'Updating...';
53
+ if (tool.status === 'installed') {
54
+ return tool.hasUpdate ? `v${tool.version || '?'} (update available)` : `v${tool.version || '?'}`;
55
+ }
56
+ if (tool.status === 'failed') return 'Installation failed';
57
+ return 'Not installed';
58
+ }
59
+
60
+ function getStatusClass(tool) {
61
+ if (tool.status === 'installing' || tool.status === 'updating') return 'installing';
62
+ if (tool.status === 'installed' && tool.hasUpdate) return 'updating';
63
+ if (tool.status === 'installed') return 'installed';
64
+ if (tool.status === 'failed') return 'failed';
65
+ return 'not-installed';
66
+ }
67
+
68
+ function install(toolId) {
69
+ if (operationInProgress.has(toolId)) return;
70
+ operationInProgress.add(toolId);
71
+ fetch(`/gm/api/tools/${toolId}/install`, { method: 'POST' })
72
+ .then(r => r.json())
73
+ .then(d => {
74
+ if (!d.success) {
75
+ alert(`Install failed: ${d.error || 'Unknown error'}`);
76
+ operationInProgress.delete(toolId);
77
+ }
78
+ })
79
+ .catch(e => {
80
+ alert(`Install failed: ${e.message}`);
81
+ operationInProgress.delete(toolId);
82
+ });
83
+ }
84
+
85
+ function update(toolId) {
86
+ if (operationInProgress.has(toolId)) return;
87
+ operationInProgress.add(toolId);
88
+ fetch(`/gm/api/tools/${toolId}/update`, { method: 'POST' })
89
+ .then(r => r.json())
90
+ .then(d => {
91
+ if (!d.success) {
92
+ alert(`Update failed: ${d.error || 'Unknown error'}`);
93
+ operationInProgress.delete(toolId);
94
+ }
95
+ })
96
+ .catch(e => {
97
+ alert(`Update failed: ${e.message}`);
98
+ operationInProgress.delete(toolId);
99
+ });
100
+ }
101
+
102
+ function togglePopup(e) {
103
+ e.stopPropagation();
104
+ if (!popup.classList.contains('open')) {
105
+ isRefreshing = false;
106
+ refresh();
107
+ }
108
+ popup.classList.toggle('open');
109
+ }
110
+
111
+ function closePopup() {
112
+ popup.classList.remove('open');
113
+ }
114
+
115
+ function onWsMessage(e) {
116
+ var data = e.detail;
117
+ if (!data) return;
118
+
119
+ if (data.type === 'tool_install_started' || data.type === 'tool_update_progress') {
120
+ var tool = tools.find(t => t.id === data.toolId);
121
+ if (tool) {
122
+ tool.status = data.type === 'tool_install_started' ? 'installing' : 'updating';
123
+ tool.progress = (tool.progress || 0) + 5;
124
+ if (tool.progress > 90) tool.progress = 90;
125
+ render();
126
+ }
127
+ } else if (data.type === 'tool_install_complete' || data.type === 'tool_update_complete') {
128
+ var tool = tools.find(t => t.id === data.toolId);
129
+ if (tool) {
130
+ tool.status = 'installed';
131
+ tool.version = data.data?.version || tool.version;
132
+ tool.hasUpdate = false;
133
+ tool.progress = 100;
134
+ setTimeout(fetchTools, 1000);
135
+ }
136
+ } else if (data.type === 'tool_install_failed' || data.type === 'tool_update_failed') {
137
+ var tool = tools.find(t => t.id === data.toolId);
138
+ if (tool) {
139
+ tool.status = 'failed';
140
+ tool.error_message = data.data?.error;
141
+ tool.progress = 0;
142
+ operationInProgress.delete(data.toolId);
143
+ render();
144
+ }
145
+ } else if (data.type === 'tools_refresh_complete') {
146
+ isRefreshing = false;
147
+ fetchTools();
148
+ }
149
+ }
150
+
151
+ function render() {
152
+ var scroll = popup.querySelector('.tools-popup-scroll');
153
+ if (!scroll) return;
154
+
155
+ if (tools.length === 0) {
156
+ scroll.innerHTML = '<div class="tool-empty-state"><div class="tool-empty-state-icon">⚙️</div><div class="tool-empty-state-text">No tools available</div></div>';
157
+ return;
158
+ }
159
+
160
+ scroll.innerHTML = tools.map(function(tool) {
161
+ var statusClass = getStatusClass(tool);
162
+ var isInstalling = tool.status === 'installing' || tool.status === 'updating';
163
+ var hasAction = !tool.installed || tool.hasUpdate || tool.status === 'failed';
164
+
165
+ return '<div class="tool-item">' +
166
+ '<div class="tool-header">' +
167
+ '<span class="tool-name">' + esc(tool.name || tool.id) + '</span>' +
168
+ '<span class="tool-status-indicator ' + statusClass + '">' +
169
+ '<span class="tool-status-dot"></span>' +
170
+ '<span>' + getStatusText(tool) + '</span>' +
171
+ '</span>' +
172
+ '</div>' +
173
+ (tool.description ? '<div class="tool-details">' + esc(tool.description) + '</div>' : '') +
174
+ (isInstalling && tool.progress !== undefined ?
175
+ '<div class="tool-progress-container">' +
176
+ '<div class="tool-progress-bar"><div class="tool-progress-fill" style="width:' + Math.min(tool.progress, 100) + '%"></div></div>' +
177
+ '<div class="tool-progress-text">' + Math.floor(tool.progress) + '%</div>' +
178
+ '</div>' : '') +
179
+ (tool.error_message ? '<div class="tool-error-message">Error: ' + esc(tool.error_message.substring(0, 60)) + '</div>' : '') +
180
+ '<div class="tool-actions">' +
181
+ (tool.status === 'not_installed' ?
182
+ '<button class="tool-btn tool-btn-primary" onclick="window.toolsManager.install(\'' + tool.id + '\')" ' + (operationInProgress.has(tool.id) ? 'disabled' : '') + '>Install</button>' :
183
+ tool.hasUpdate ?
184
+ '<button class="tool-btn tool-btn-primary" onclick="window.toolsManager.update(\'' + tool.id + '\')" ' + (operationInProgress.has(tool.id) ? 'disabled' : '') + '>Update to v' + esc(tool.latestVersion || '?') + '</button>' :
185
+ tool.status === 'failed' ?
186
+ '<button class="tool-btn tool-btn-primary" onclick="window.toolsManager.install(\'' + tool.id + '\')" ' + (operationInProgress.has(tool.id) ? 'disabled' : '') + '>Retry</button>' :
187
+ '<button class="tool-btn tool-btn-secondary" onclick="window.toolsManager.refresh()" ' + (isRefreshing ? 'disabled' : '') + '>Check for updates</button>'
188
+ ) +
189
+ '</div>' +
190
+ '</div>';
191
+ }).join('');
192
+ }
193
+
194
+ function esc(s) {
195
+ var d = document.createElement('div');
196
+ d.textContent = s;
197
+ return d.innerHTML;
198
+ }
199
+
200
+ if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
201
+ else init();
202
+
203
+ window.toolsManager = {
204
+ refresh: function() {
205
+ isRefreshing = true;
206
+ render();
207
+ fetch('/gm/api/tools/refresh-all', { method: 'POST' })
208
+ .catch(function(e) { console.error('[TOOLS-MGR]', e.message); });
209
+ },
210
+ install: function(toolId) { install(toolId); },
211
+ update: function(toolId) { update(toolId); }
212
+ };
213
+ })();
@@ -1,21 +0,0 @@
1
- #tool-status-container { padding: 16px; border: 1px solid #e0e0e0; border-radius: 4px; margin-bottom: 16px; }
2
- .tool-status-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px; }
3
- .tool-status-header h3 { margin: 0; font-size: 18px; }
4
- .tool-refresh-btn { padding: 6px 12px; background: #2196F3; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 12px; }
5
- .tool-refresh-btn:disabled { opacity: 0.6; cursor: not-allowed; }
6
- .tool-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 12px; }
7
- .tool-card { padding: 12px; border: 1px solid #ddd; border-radius: 4px; background: #f9f9f9; }
8
- .tool-name { font-weight: bold; margin-bottom: 4px; font-size: 14px; }
9
- .tool-status { font-size: 12px; color: #666; margin-bottom: 8px; }
10
- .tool-progress { width: 100%; height: 6px; background: #e0e0e0; border-radius: 3px; overflow: hidden; margin-bottom: 8px; }
11
- .progress-bar { height: 100%; background: #4CAF50; transition: width 0.3s; }
12
- .tool-actions { display: flex; flex-direction: column; gap: 6px; }
13
- .tool-btn { padding: 6px; border: none; border-radius: 3px; cursor: pointer; font-size: 12px; white-space: nowrap; }
14
- .tool-btn-primary { background: #2196F3; color: white; }
15
- .tool-btn-primary:hover { background: #1976D2; }
16
- .tool-btn-secondary { background: #f0f0f0; color: #333; border: 1px solid #ddd; }
17
- .tool-btn-secondary:hover { background: #e0e0e0; }
18
- .tool-btn-warning { background: #FF9800; color: white; }
19
- .tool-btn-warning:hover { background: #F57C00; }
20
- .tool-error { font-size: 11px; color: #F44336; padding: 6px; background: #ffebee; border-radius: 2px; margin-top: 4px; }
21
- .tool-error a { color: #C62828; text-decoration: underline; cursor: pointer; }
@@ -1,48 +0,0 @@
1
- const ToolStatusComponent = {
2
- state: { tools: [], refreshing: false },
3
-
4
- init() {
5
- this.loadTools();
6
- if (window.wsManager) {
7
- const events = ['tool_install_started', 'tool_install_progress', 'tool_install_complete', 'tool_install_failed', 'tool_update_complete', 'tool_update_failed', 'tools_refresh_complete'];
8
- events.forEach(e => window.wsManager.on(e, (d) => this.handleEvent(e, d)));
9
- }
10
- },
11
-
12
- async loadTools() {
13
- try { const res = await fetch('/gm/api/tools'); this.state.tools = (await res.json()).tools || []; this.render(); } catch (e) { console.error('[TOOL-STATUS]', e.message); }
14
- },
15
-
16
- async act(action, toolId) {
17
- try { const res = await fetch(`/gm/api/tools/${toolId}/${action}`, { method: 'POST' }); const data = await res.json(); if (!data.success) alert(`${action} failed: ${data.error || 'Unknown error'}`); } catch (e) { alert(`${action} failed: ${e.message}`); }
18
- },
19
-
20
- async refreshTools() { this.state.refreshing = true; this.render(); try { await fetch('/gm/api/tools/refresh-all', { method: 'POST' }); } catch (e) { console.error('[TOOL-STATUS]', e.message); } },
21
-
22
- handleEvent(event, data) {
23
- const tool = this.state.tools.find(t => t.id === data.toolId);
24
- if (!tool) return;
25
- if (event === 'tool_install_started') { tool.status = 'installing'; tool.progress = 0; }
26
- else if (event === 'tool_install_progress') { tool.progress = Math.min((tool.progress || 0) + 5, 90); }
27
- else if (event.includes('_complete')) { tool.status = 'installed'; tool.version = data.data.version; tool.hasUpdate = false; tool.progress = 100; setTimeout(() => this.loadTools(), 1000); }
28
- else if (event.includes('_failed')) { tool.status = 'failed'; tool.error_message = data.data.error; tool.progress = 0; }
29
- else if (event === 'tools_refresh_complete') { this.state.refreshing = false; this.loadTools(); return; }
30
- this.render();
31
- },
32
-
33
- getStatusColor(tool) { return tool.status === 'installed' && !tool.hasUpdate ? '#4CAF50' : tool.status === 'installed' && tool.hasUpdate ? '#FFC107' : ['installing', 'updating'].includes(tool.status) ? '#2196F3' : tool.status === 'failed' ? '#F44336' : '#9E9E9E'; },
34
-
35
- getStatusText(tool) { return tool.status === 'installed' ? (tool.hasUpdate ? `Update available (v${tool.latestVersion})` : `Installed v${tool.version || '?'}`) : tool.status === 'installing' ? 'Installing...' : tool.status === 'updating' ? 'Updating...' : tool.status === 'failed' ? 'Failed' : 'Not installed'; },
36
-
37
- render() {
38
- const c = document.getElementById('tool-status-container');
39
- if (!c) return;
40
- c.innerHTML = `
41
- <div class="tool-status-header"><h3>Tools</h3><button onclick="window.toolStatus.refreshTools()" ${this.state.refreshing ? 'disabled' : ''} class="tool-refresh-btn">${this.state.refreshing ? 'Refreshing...' : 'Refresh'}</button></div>
42
- <div class="tool-grid">${this.state.tools.map(t => `<div class="tool-card" style="border-left: 4px solid ${this.getStatusColor(t)}"><div class="tool-name">${t.name || t.id}</div><div class="tool-status">${this.getStatusText(t)}</div>${t.progress !== undefined && ['installing', 'updating'].includes(t.status) ? `<div class="tool-progress"><div class="progress-bar" style="width: ${t.progress}%"></div></div>` : ''}<div class="tool-actions">${!t.installed ? `<button onclick="window.toolStatus.act('install','${t.id}')" class="tool-btn tool-btn-primary">Install</button>` : t.hasUpdate ? `<button onclick="window.toolStatus.act('update','${t.id}')" class="tool-btn tool-btn-primary">Update</button>` : `<button onclick="window.toolStatus.refreshTools()" class="tool-btn tool-btn-secondary">Check Updates</button>`}${t.status === 'failed' ? `<button onclick="window.toolStatus.act('install','${t.id}')" class="tool-btn tool-btn-warning">Retry</button>` : ''}${t.error_message ? `<div class="tool-error" title="${t.error_message}">Error: ${t.error_message.substring(0, 30)}...<a href="#" onclick="alert('${t.error_message.replace(/"/g, '\\"')}'); return false;">Details</a></div>` : ''}</div></div>`).join('')}</div>
43
- `;
44
- window.toolStatus = this;
45
- }
46
- };
47
-
48
- if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => ToolStatusComponent.init()); } else { ToolStatusComponent.init(); }