agentgui 1.0.93 → 1.0.94
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/package.json +5 -3
- package/server.js +76 -0
- package/static/index.html +354 -94
- package/static/js/features.js +243 -0
- package/static/styles.css +46 -4
package/.prd
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentgui",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.94",
|
|
4
4
|
"description": "Multi-agent ACP client with real-time communication",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "server.js",
|
|
@@ -23,7 +23,9 @@
|
|
|
23
23
|
"dependencies": {
|
|
24
24
|
"@anthropic-ai/claude-code": "^1.0.128",
|
|
25
25
|
"better-sqlite3": "^12.6.2",
|
|
26
|
+
"busboy": "^1.6.0",
|
|
27
|
+
"express": "^5.2.1",
|
|
28
|
+
"fsbrowse": "file:../fsbrowse",
|
|
26
29
|
"ws": "^8.14.2"
|
|
27
|
-
}
|
|
28
|
-
"devDependencies": {}
|
|
30
|
+
}
|
|
29
31
|
}
|
package/server.js
CHANGED
|
@@ -4,9 +4,15 @@ import path from 'path';
|
|
|
4
4
|
import { fileURLToPath } from 'url';
|
|
5
5
|
import { WebSocketServer } from 'ws';
|
|
6
6
|
import { execSync } from 'child_process';
|
|
7
|
+
import { createRequire } from 'module';
|
|
7
8
|
import { queries } from './database.js';
|
|
8
9
|
import { runClaudeWithStreaming } from './lib/claude-runner.js';
|
|
9
10
|
|
|
11
|
+
const require = createRequire(import.meta.url);
|
|
12
|
+
const express = require('express');
|
|
13
|
+
const Busboy = require('busboy');
|
|
14
|
+
const fsbrowse = require('fsbrowse');
|
|
15
|
+
|
|
10
16
|
// System prompt for Claude to format responses as HTML
|
|
11
17
|
const SYSTEM_PROMPT = `Always write your responses in ripple-ui enhanced HTML. Avoid overriding light/dark mode CSS variables. Use all the benefits of HTML to express technical details with proper semantic markup, tables, code blocks, headings, and lists. Write clean, well-structured HTML that respects the existing design system.`;
|
|
12
18
|
|
|
@@ -24,6 +30,70 @@ const watch = process.argv.includes('--no-watch') ? false : (process.argv.includ
|
|
|
24
30
|
const staticDir = path.join(__dirname, 'static');
|
|
25
31
|
if (!fs.existsSync(staticDir)) fs.mkdirSync(staticDir, { recursive: true });
|
|
26
32
|
|
|
33
|
+
// Express sub-app for fsbrowse file browser and file upload
|
|
34
|
+
const expressApp = express();
|
|
35
|
+
|
|
36
|
+
// File upload endpoint - copies dropped files to conversation workingDirectory
|
|
37
|
+
expressApp.post(BASE_URL + '/api/upload/:conversationId', (req, res) => {
|
|
38
|
+
try {
|
|
39
|
+
const conv = queries.getConversation(req.params.conversationId);
|
|
40
|
+
if (!conv) return res.status(404).json({ error: 'Conversation not found' });
|
|
41
|
+
if (!conv.workingDirectory) return res.status(400).json({ error: 'No working directory set for this conversation' });
|
|
42
|
+
|
|
43
|
+
const uploadDir = conv.workingDirectory;
|
|
44
|
+
if (!fs.existsSync(uploadDir)) {
|
|
45
|
+
fs.mkdirSync(uploadDir, { recursive: true });
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const bb = Busboy({ headers: req.headers });
|
|
49
|
+
const fileNames = [];
|
|
50
|
+
const writePromises = [];
|
|
51
|
+
|
|
52
|
+
bb.on('file', (fieldname, file, info) => {
|
|
53
|
+
const safeName = path.basename(info.filename);
|
|
54
|
+
const filePath = path.join(uploadDir, safeName);
|
|
55
|
+
fileNames.push(safeName);
|
|
56
|
+
const p = new Promise((resolve) => {
|
|
57
|
+
const writeStream = fs.createWriteStream(filePath);
|
|
58
|
+
file.pipe(writeStream);
|
|
59
|
+
writeStream.on('finish', resolve);
|
|
60
|
+
writeStream.on('error', () => { file.resume(); resolve(); });
|
|
61
|
+
});
|
|
62
|
+
writePromises.push(p);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
bb.on('finish', () => {
|
|
66
|
+
Promise.all(writePromises).then(() => {
|
|
67
|
+
res.json({ ok: true, files: fileNames, count: fileNames.length });
|
|
68
|
+
}).catch(() => {
|
|
69
|
+
res.json({ ok: true, files: fileNames, count: fileNames.length });
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
bb.on('error', (err) => {
|
|
74
|
+
res.status(500).json({ error: 'Upload failed: ' + err.message });
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
req.pipe(bb);
|
|
78
|
+
} catch (err) {
|
|
79
|
+
res.status(500).json({ error: err.message });
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// fsbrowse file browser - mounted per conversation workingDirectory
|
|
84
|
+
// Route: /gm/files/:conversationId/*
|
|
85
|
+
expressApp.use(BASE_URL + '/files/:conversationId', (req, res, next) => {
|
|
86
|
+
const conv = queries.getConversation(req.params.conversationId);
|
|
87
|
+
if (!conv || !conv.workingDirectory) {
|
|
88
|
+
return res.status(404).json({ error: 'Conversation not found or no working directory' });
|
|
89
|
+
}
|
|
90
|
+
// Create a fresh fsbrowse router for this conversation's directory
|
|
91
|
+
const router = fsbrowse({ baseDir: conv.workingDirectory });
|
|
92
|
+
// Strip the conversationId param from the path before passing to fsbrowse
|
|
93
|
+
req.baseUrl = BASE_URL + '/files/' + req.params.conversationId;
|
|
94
|
+
router(req, res, next);
|
|
95
|
+
});
|
|
96
|
+
|
|
27
97
|
function discoverAgents() {
|
|
28
98
|
const agents = [];
|
|
29
99
|
const binaries = [
|
|
@@ -59,6 +129,12 @@ const server = http.createServer(async (req, res) => {
|
|
|
59
129
|
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
|
|
60
130
|
if (req.method === 'OPTIONS') { res.writeHead(200); res.end(); return; }
|
|
61
131
|
|
|
132
|
+
// Route file upload and fsbrowse requests through Express sub-app
|
|
133
|
+
const pathOnly = req.url.split('?')[0];
|
|
134
|
+
if (pathOnly.startsWith(BASE_URL + '/api/upload/') || pathOnly.startsWith(BASE_URL + '/files/')) {
|
|
135
|
+
return expressApp(req, res);
|
|
136
|
+
}
|
|
137
|
+
|
|
62
138
|
if (req.url === '/') { res.writeHead(302, { Location: BASE_URL + '/' }); res.end(); return; }
|
|
63
139
|
|
|
64
140
|
if (!req.url.startsWith(BASE_URL + '/') && req.url !== BASE_URL) {
|
package/static/index.html
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
<html lang="en" class="light">
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
|
|
6
6
|
<meta name="description" content="AgentGUI - Real-time Claude Code Execution Visualization">
|
|
7
7
|
<title>AgentGUI - Agent Execution Visualizer</title>
|
|
8
8
|
|
|
@@ -460,97 +460,7 @@
|
|
|
460
460
|
color: var(--color-text-secondary);
|
|
461
461
|
}
|
|
462
462
|
|
|
463
|
-
/*
|
|
464
|
-
@media (max-width: 768px) {
|
|
465
|
-
.layout-with-sidebar {
|
|
466
|
-
grid-template-columns: 1fr;
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
.sidebar {
|
|
470
|
-
display: none;
|
|
471
|
-
position: fixed;
|
|
472
|
-
top: 60px;
|
|
473
|
-
left: 0;
|
|
474
|
-
width: 250px;
|
|
475
|
-
height: calc(100vh - 60px);
|
|
476
|
-
z-index: 1000;
|
|
477
|
-
overflow-y: auto;
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
.sidebar.mobile-visible {
|
|
481
|
-
display: flex;
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
.execution-panel {
|
|
485
|
-
grid-template-columns: 1fr;
|
|
486
|
-
padding: 1rem;
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
.header-controls {
|
|
490
|
-
flex-wrap: wrap;
|
|
491
|
-
gap: 0.5rem;
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
.execution-input-group {
|
|
495
|
-
grid-template-columns: 1fr;
|
|
496
|
-
gap: 0.5rem;
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
#output-scroll {
|
|
500
|
-
padding: 0.75rem;
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
header {
|
|
504
|
-
padding: 0.75rem;
|
|
505
|
-
height: 50px;
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
.header-title {
|
|
509
|
-
font-size: 1.25rem;
|
|
510
|
-
}
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
@media (max-width: 480px) {
|
|
514
|
-
.layout-with-sidebar {
|
|
515
|
-
grid-template-columns: 1fr;
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
.sidebar {
|
|
519
|
-
width: 100%;
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
.execution-panel {
|
|
523
|
-
padding: 0.75rem;
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
.execution-textarea {
|
|
527
|
-
min-height: 2.5rem;
|
|
528
|
-
max-height: 6rem;
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
.btn {
|
|
532
|
-
padding: 0.5rem 1rem;
|
|
533
|
-
font-size: 0.8rem;
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
#output-scroll {
|
|
537
|
-
padding: 0.5rem;
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
header {
|
|
541
|
-
padding: 0.5rem;
|
|
542
|
-
height: 45px;
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
.header-title {
|
|
546
|
-
font-size: 1rem;
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
.status-badge {
|
|
550
|
-
padding: 0.25rem 0.5rem;
|
|
551
|
-
font-size: 0.75rem;
|
|
552
|
-
}
|
|
553
|
-
}
|
|
463
|
+
/* Old responsive rules removed - see bottom of style block for consolidated responsive CSS */
|
|
554
464
|
|
|
555
465
|
/* Folder Browser Modal */
|
|
556
466
|
.folder-modal-overlay {
|
|
@@ -959,12 +869,338 @@
|
|
|
959
869
|
.flex-shrink-0 {
|
|
960
870
|
flex-shrink: 0;
|
|
961
871
|
}
|
|
872
|
+
|
|
873
|
+
/* Hamburger menu button */
|
|
874
|
+
.hamburger-btn {
|
|
875
|
+
display: none;
|
|
876
|
+
flex-direction: column;
|
|
877
|
+
justify-content: center;
|
|
878
|
+
align-items: center;
|
|
879
|
+
gap: 4px;
|
|
880
|
+
width: 44px;
|
|
881
|
+
height: 44px;
|
|
882
|
+
background: none;
|
|
883
|
+
border: none;
|
|
884
|
+
cursor: pointer;
|
|
885
|
+
padding: 8px;
|
|
886
|
+
border-radius: 0.375rem;
|
|
887
|
+
flex-shrink: 0;
|
|
888
|
+
-webkit-tap-highlight-color: transparent;
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
.hamburger-btn:hover {
|
|
892
|
+
background-color: var(--color-border);
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
.hamburger-btn span {
|
|
896
|
+
display: block;
|
|
897
|
+
width: 20px;
|
|
898
|
+
height: 2px;
|
|
899
|
+
background-color: var(--color-text-primary);
|
|
900
|
+
border-radius: 1px;
|
|
901
|
+
transition: transform 0.2s, opacity 0.2s;
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
/* Sidebar overlay for mobile */
|
|
905
|
+
.sidebar-overlay {
|
|
906
|
+
display: none;
|
|
907
|
+
position: fixed;
|
|
908
|
+
top: 0;
|
|
909
|
+
left: 0;
|
|
910
|
+
width: 100%;
|
|
911
|
+
height: 100%;
|
|
912
|
+
background: rgba(0, 0, 0, 0.5);
|
|
913
|
+
z-index: 999;
|
|
914
|
+
-webkit-tap-highlight-color: transparent;
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
.sidebar-overlay.visible {
|
|
918
|
+
display: block;
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
/* View toggle bar */
|
|
922
|
+
.view-toggle-bar {
|
|
923
|
+
display: flex;
|
|
924
|
+
gap: 0;
|
|
925
|
+
border-bottom: 1px solid var(--color-border);
|
|
926
|
+
background: var(--color-bg-secondary);
|
|
927
|
+
flex-shrink: 0;
|
|
928
|
+
padding: 0 1rem;
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
.view-toggle-btn {
|
|
932
|
+
padding: 0.625rem 1rem;
|
|
933
|
+
border: none;
|
|
934
|
+
background: none;
|
|
935
|
+
cursor: pointer;
|
|
936
|
+
font-size: 0.875rem;
|
|
937
|
+
font-weight: 500;
|
|
938
|
+
color: var(--color-text-secondary);
|
|
939
|
+
border-bottom: 2px solid transparent;
|
|
940
|
+
transition: all 0.2s;
|
|
941
|
+
min-height: 44px;
|
|
942
|
+
-webkit-tap-highlight-color: transparent;
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
.view-toggle-btn:hover {
|
|
946
|
+
color: var(--color-text-primary);
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
.view-toggle-btn.active {
|
|
950
|
+
color: var(--color-primary);
|
|
951
|
+
border-bottom-color: var(--color-primary);
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
/* Drop zone overlay */
|
|
955
|
+
.drop-zone-overlay {
|
|
956
|
+
display: none;
|
|
957
|
+
position: absolute;
|
|
958
|
+
top: 0;
|
|
959
|
+
left: 0;
|
|
960
|
+
right: 0;
|
|
961
|
+
bottom: 0;
|
|
962
|
+
background: rgba(59, 130, 246, 0.1);
|
|
963
|
+
border: 3px dashed var(--color-primary);
|
|
964
|
+
border-radius: 0.5rem;
|
|
965
|
+
z-index: 100;
|
|
966
|
+
align-items: center;
|
|
967
|
+
justify-content: center;
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
.drop-zone-overlay.visible {
|
|
971
|
+
display: flex;
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
#output-scroll {
|
|
975
|
+
position: relative;
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
.drop-zone-content {
|
|
979
|
+
text-align: center;
|
|
980
|
+
color: var(--color-primary);
|
|
981
|
+
pointer-events: none;
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
.drop-zone-icon {
|
|
985
|
+
font-size: 3rem;
|
|
986
|
+
font-weight: 300;
|
|
987
|
+
line-height: 1;
|
|
988
|
+
margin-bottom: 0.5rem;
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
.drop-zone-text {
|
|
992
|
+
font-size: 1rem;
|
|
993
|
+
font-weight: 500;
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
/* Upload toast notification */
|
|
997
|
+
.upload-toast {
|
|
998
|
+
position: fixed;
|
|
999
|
+
bottom: 100px;
|
|
1000
|
+
left: 50%;
|
|
1001
|
+
transform: translateX(-50%);
|
|
1002
|
+
padding: 0.75rem 1.5rem;
|
|
1003
|
+
border-radius: 0.5rem;
|
|
1004
|
+
font-size: 0.875rem;
|
|
1005
|
+
font-weight: 500;
|
|
1006
|
+
z-index: 2000;
|
|
1007
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
1008
|
+
transition: opacity 0.3s;
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
.upload-toast.success {
|
|
1012
|
+
background: var(--color-success);
|
|
1013
|
+
color: white;
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
.upload-toast.error {
|
|
1017
|
+
background: var(--color-error);
|
|
1018
|
+
color: white;
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
.upload-toast.info {
|
|
1022
|
+
background: var(--color-primary);
|
|
1023
|
+
color: white;
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
/* File browser container */
|
|
1027
|
+
.file-browser-container {
|
|
1028
|
+
flex: 1;
|
|
1029
|
+
min-height: 0;
|
|
1030
|
+
overflow: hidden;
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
.file-browser-iframe {
|
|
1034
|
+
width: 100%;
|
|
1035
|
+
height: 100%;
|
|
1036
|
+
border: none;
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
/* Safe area insets for notched phones */
|
|
1040
|
+
.app-container {
|
|
1041
|
+
padding-top: env(safe-area-inset-top);
|
|
1042
|
+
padding-bottom: env(safe-area-inset-bottom);
|
|
1043
|
+
padding-left: env(safe-area-inset-left);
|
|
1044
|
+
padding-right: env(safe-area-inset-right);
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
/* Responsive improvements */
|
|
1048
|
+
@media (max-width: 768px) {
|
|
1049
|
+
.hamburger-btn {
|
|
1050
|
+
display: flex;
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
.layout-with-sidebar {
|
|
1054
|
+
grid-template-columns: 1fr;
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
.sidebar {
|
|
1058
|
+
position: fixed;
|
|
1059
|
+
top: 0;
|
|
1060
|
+
left: 0;
|
|
1061
|
+
width: 280px;
|
|
1062
|
+
height: 100%;
|
|
1063
|
+
z-index: 1000;
|
|
1064
|
+
transform: translateX(-100%);
|
|
1065
|
+
transition: transform 0.3s ease;
|
|
1066
|
+
box-shadow: none;
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
.sidebar.mobile-visible {
|
|
1070
|
+
transform: translateX(0);
|
|
1071
|
+
box-shadow: 4px 0 20px rgba(0, 0, 0, 0.2);
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
.execution-panel {
|
|
1075
|
+
grid-template-columns: 1fr;
|
|
1076
|
+
padding: 0.75rem;
|
|
1077
|
+
padding-bottom: calc(0.75rem + env(safe-area-inset-bottom));
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
.execution-input-group {
|
|
1081
|
+
grid-template-columns: 1fr;
|
|
1082
|
+
gap: 0.5rem;
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
.btn {
|
|
1086
|
+
min-height: 44px;
|
|
1087
|
+
min-width: 44px;
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
.conversation-item {
|
|
1091
|
+
padding: 0.875rem 0.5rem;
|
|
1092
|
+
min-height: 44px;
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
.sidebar-new-btn {
|
|
1096
|
+
min-height: 44px;
|
|
1097
|
+
min-width: 44px;
|
|
1098
|
+
padding: 0.5rem 0.75rem;
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
header {
|
|
1102
|
+
padding: 0.5rem 0.75rem;
|
|
1103
|
+
height: auto;
|
|
1104
|
+
min-height: 50px;
|
|
1105
|
+
padding-top: calc(0.5rem + env(safe-area-inset-top));
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
.header-title {
|
|
1109
|
+
font-size: 1.125rem;
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
.status-badge {
|
|
1113
|
+
padding: 0.375rem 0.625rem;
|
|
1114
|
+
font-size: 0.75rem;
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
#output-scroll {
|
|
1118
|
+
padding: 0.75rem;
|
|
1119
|
+
-webkit-overflow-scrolling: touch;
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
.execution-textarea {
|
|
1123
|
+
min-height: 44px;
|
|
1124
|
+
font-size: 16px;
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
.view-toggle-btn {
|
|
1128
|
+
flex: 1;
|
|
1129
|
+
text-align: center;
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
@media (max-width: 480px) {
|
|
1134
|
+
.sidebar {
|
|
1135
|
+
width: calc(100% - 3rem);
|
|
1136
|
+
max-width: 320px;
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
header {
|
|
1140
|
+
padding: 0.5rem;
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
.header-title {
|
|
1144
|
+
font-size: 1rem;
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
.header-controls {
|
|
1148
|
+
gap: 0.375rem;
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
.execution-panel {
|
|
1152
|
+
padding: 0.5rem;
|
|
1153
|
+
padding-bottom: calc(0.5rem + env(safe-area-inset-bottom));
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
.execution-textarea {
|
|
1157
|
+
min-height: 44px;
|
|
1158
|
+
max-height: 6rem;
|
|
1159
|
+
font-size: 16px;
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
.btn {
|
|
1163
|
+
padding: 0.625rem 1rem;
|
|
1164
|
+
font-size: 0.8rem;
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
#output-scroll {
|
|
1168
|
+
padding: 0.5rem;
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
.status-badge {
|
|
1172
|
+
padding: 0.25rem 0.5rem;
|
|
1173
|
+
font-size: 0.7rem;
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
.drop-zone-icon {
|
|
1177
|
+
font-size: 2rem;
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
.drop-zone-text {
|
|
1181
|
+
font-size: 0.875rem;
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
/* Tablet landscape */
|
|
1186
|
+
@media (min-width: 769px) and (max-width: 1024px) {
|
|
1187
|
+
.layout-with-sidebar {
|
|
1188
|
+
grid-template-columns: 250px 1fr;
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
.sidebar {
|
|
1192
|
+
width: auto;
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
962
1195
|
</style>
|
|
963
1196
|
</head>
|
|
964
1197
|
<body>
|
|
965
1198
|
<div class="app-container">
|
|
966
1199
|
<!-- Header -->
|
|
967
1200
|
<header>
|
|
1201
|
+
<button class="hamburger-btn" data-hamburger title="Toggle sidebar" aria-label="Toggle sidebar">
|
|
1202
|
+
<span></span><span></span><span></span>
|
|
1203
|
+
</button>
|
|
968
1204
|
<h1 class="header-title">AgentGUI</h1>
|
|
969
1205
|
|
|
970
1206
|
<div class="header-controls">
|
|
@@ -974,11 +1210,14 @@
|
|
|
974
1210
|
</div>
|
|
975
1211
|
|
|
976
1212
|
<button class="btn btn-primary" data-theme-toggle title="Toggle dark mode">
|
|
977
|
-
|
|
1213
|
+
Theme
|
|
978
1214
|
</button>
|
|
979
1215
|
</div>
|
|
980
1216
|
</header>
|
|
981
1217
|
|
|
1218
|
+
<!-- Sidebar Overlay (mobile) -->
|
|
1219
|
+
<div class="sidebar-overlay" data-sidebar-overlay></div>
|
|
1220
|
+
|
|
982
1221
|
<!-- Main Content with Sidebar -->
|
|
983
1222
|
<div class="layout-with-sidebar">
|
|
984
1223
|
<!-- Sidebar -->
|
|
@@ -994,9 +1233,27 @@
|
|
|
994
1233
|
|
|
995
1234
|
<!-- Main Content Area -->
|
|
996
1235
|
<main id="app" class="main-content">
|
|
1236
|
+
<!-- View Toggle Bar -->
|
|
1237
|
+
<div class="view-toggle-bar" id="viewToggleBar" style="display:none;">
|
|
1238
|
+
<button class="view-toggle-btn active" data-view="chat">Chat</button>
|
|
1239
|
+
<button class="view-toggle-btn" data-view="files">Files</button>
|
|
1240
|
+
</div>
|
|
1241
|
+
|
|
997
1242
|
<!-- Output Container - Scrollable -->
|
|
998
|
-
<div id="output-scroll" role="region" aria-label="Execution output" aria-live="polite" aria-atomic="false">
|
|
1243
|
+
<div id="output-scroll" role="region" aria-label="Execution output" aria-live="polite" aria-atomic="false" data-drop-zone>
|
|
999
1244
|
<div id="output" class="output"></div>
|
|
1245
|
+
<!-- Drop zone overlay -->
|
|
1246
|
+
<div class="drop-zone-overlay" id="dropZoneOverlay">
|
|
1247
|
+
<div class="drop-zone-content">
|
|
1248
|
+
<div class="drop-zone-icon">+</div>
|
|
1249
|
+
<div class="drop-zone-text">Drop files here to copy to working directory</div>
|
|
1250
|
+
</div>
|
|
1251
|
+
</div>
|
|
1252
|
+
</div>
|
|
1253
|
+
|
|
1254
|
+
<!-- File Browser iframe (hidden by default) -->
|
|
1255
|
+
<div id="fileBrowserContainer" class="file-browser-container" style="display:none;">
|
|
1256
|
+
<iframe id="fileBrowserIframe" class="file-browser-iframe" sandbox="allow-same-origin allow-scripts allow-forms allow-popups"></iframe>
|
|
1000
1257
|
</div>
|
|
1001
1258
|
|
|
1002
1259
|
<!-- Execution Panel - Fixed at bottom -->
|
|
@@ -1066,6 +1323,9 @@
|
|
|
1066
1323
|
<!-- 8. Main Client (depends on all above) -->
|
|
1067
1324
|
<script src="/gm/js/client.js"></script>
|
|
1068
1325
|
|
|
1326
|
+
<!-- 9. Features: drag-drop, file browser, mobile sidebar -->
|
|
1327
|
+
<script src="/gm/js/features.js"></script>
|
|
1328
|
+
|
|
1069
1329
|
<!-- Inline initialization -->
|
|
1070
1330
|
<script>
|
|
1071
1331
|
// Set initial theme based on preference
|
|
@@ -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
|
+
})();
|
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
|
+
}
|