pinokiod 6.0.19 → 6.0.21
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/kernel/environment.js +55 -1
- package/kernel/shell.js +3 -1
- package/package.json +1 -1
- package/server/index.js +871 -101
- package/server/public/common.js +7 -0
- package/server/public/install.js +1 -1
- package/server/public/style.css +4 -5
- package/server/public/terminal-settings.js +1 -1
- package/server/scripts/fork_gemini_session.js +67 -0
- package/server/scripts/gemini_fork_and_resume.js +50 -0
- package/server/views/app.ejs +80 -5
- package/server/views/bootstrap.ejs +1 -1
- package/server/views/editor.ejs +1 -1
- package/server/views/explore.ejs +76 -1
- package/server/views/index.ejs +3 -3
- package/server/views/init/index.ejs +1 -1
- package/server/views/install.ejs +1 -1
- package/server/views/net.ejs +1 -1
- package/server/views/pro.ejs +1 -1
- package/server/views/prototype/index.ejs +1 -1
- package/server/views/shell.ejs +2 -2
- package/server/views/terminal.ejs +1 -1
- package/server/views/terminals.ejs +1359 -141
|
@@ -33,13 +33,17 @@ body {
|
|
|
33
33
|
|
|
34
34
|
body {
|
|
35
35
|
overflow: hidden;
|
|
36
|
+
display: flex !important;
|
|
37
|
+
flex-direction: column !important;
|
|
38
|
+
align-items: stretch;
|
|
36
39
|
}
|
|
37
40
|
|
|
38
41
|
main {
|
|
39
42
|
display: flex;
|
|
43
|
+
flex: 1 1 auto;
|
|
40
44
|
min-height: 0;
|
|
41
|
-
height:
|
|
42
|
-
max-height:
|
|
45
|
+
height: auto;
|
|
46
|
+
max-height: none;
|
|
43
47
|
overflow: hidden;
|
|
44
48
|
}
|
|
45
49
|
|
|
@@ -72,6 +76,29 @@ main > .container.terminals-page {
|
|
|
72
76
|
box-sizing: border-box;
|
|
73
77
|
}
|
|
74
78
|
|
|
79
|
+
@media only screen and (max-width: 768px) {
|
|
80
|
+
body {
|
|
81
|
+
flex-direction: column !important;
|
|
82
|
+
}
|
|
83
|
+
header.navheader {
|
|
84
|
+
display: block;
|
|
85
|
+
align-self: auto;
|
|
86
|
+
overflow: visible;
|
|
87
|
+
width: 100%;
|
|
88
|
+
min-width: 0;
|
|
89
|
+
max-width: none;
|
|
90
|
+
height: auto;
|
|
91
|
+
}
|
|
92
|
+
header.navheader h1 {
|
|
93
|
+
display: flex;
|
|
94
|
+
flex-direction: row;
|
|
95
|
+
align-items: center;
|
|
96
|
+
overflow-x: auto;
|
|
97
|
+
overflow-y: hidden;
|
|
98
|
+
flex-wrap: nowrap;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
75
102
|
.container.terminals-page > form.search {
|
|
76
103
|
flex: 0 0 auto;
|
|
77
104
|
padding: 10px 10px 0;
|
|
@@ -184,14 +211,14 @@ body.dark .terminals-search-form .home-mode-switch .mode-link:hover {
|
|
|
184
211
|
|
|
185
212
|
.terminals-search-form .home-mode-switch .mode-link.selected,
|
|
186
213
|
.terminals-search-form .home-mode-switch .mode-link[aria-current="page"] {
|
|
187
|
-
background:
|
|
214
|
+
background: royalblue !important;
|
|
188
215
|
color: #fff !important;
|
|
189
216
|
}
|
|
190
217
|
|
|
191
218
|
body.dark .terminals-search-form .home-mode-switch .mode-link.selected,
|
|
192
219
|
body.dark .terminals-search-form .home-mode-switch .mode-link[aria-current="page"] {
|
|
193
|
-
background:
|
|
194
|
-
color:
|
|
220
|
+
background: royalblue !important;
|
|
221
|
+
color: #fff !important;
|
|
195
222
|
}
|
|
196
223
|
|
|
197
224
|
#terminals-empty-main {
|
|
@@ -206,12 +233,23 @@ body.dark .terminals-search-form .home-mode-switch .mode-link[aria-current="page
|
|
|
206
233
|
box-sizing: border-box;
|
|
207
234
|
}
|
|
208
235
|
|
|
209
|
-
#terminals-empty-main.hidden
|
|
210
|
-
#terminals-session-bank.hidden {
|
|
236
|
+
#terminals-empty-main.hidden {
|
|
211
237
|
display: none;
|
|
212
238
|
}
|
|
213
239
|
|
|
214
240
|
.terminals-columns {
|
|
241
|
+
flex: 1;
|
|
242
|
+
min-height: 0;
|
|
243
|
+
display: flex;
|
|
244
|
+
flex-direction: column;
|
|
245
|
+
width: 100%;
|
|
246
|
+
max-width: 100%;
|
|
247
|
+
overflow: hidden;
|
|
248
|
+
gap: 8px;
|
|
249
|
+
box-sizing: border-box;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
.terminals-columns-rail {
|
|
215
253
|
flex: 1;
|
|
216
254
|
min-height: 0;
|
|
217
255
|
display: flex;
|
|
@@ -230,6 +268,103 @@ body.dark .terminals-search-form .home-mode-switch .mode-link[aria-current="page
|
|
|
230
268
|
display: none;
|
|
231
269
|
}
|
|
232
270
|
|
|
271
|
+
.terminals-list-intro {
|
|
272
|
+
margin: 10px 12px 8px;
|
|
273
|
+
padding: 10px 12px;
|
|
274
|
+
border: 1px solid rgba(127, 127, 127, 0.22);
|
|
275
|
+
border-radius: 10px;
|
|
276
|
+
background: rgba(255, 255, 255, 0.02);
|
|
277
|
+
position: relative;
|
|
278
|
+
overflow: hidden;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
body.dark .terminals-list-intro {
|
|
282
|
+
border-color: rgba(255, 255, 255, 0.18);
|
|
283
|
+
background: rgba(255, 255, 255, 0.03);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
.terminals-list-intro-head {
|
|
287
|
+
display: flex;
|
|
288
|
+
align-items: center;
|
|
289
|
+
gap: 8px;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
.terminals-list-intro-title {
|
|
293
|
+
display: inline-flex;
|
|
294
|
+
align-items: center;
|
|
295
|
+
gap: 8px;
|
|
296
|
+
min-width: 0;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
.terminals-list-intro h2 {
|
|
300
|
+
margin: 0;
|
|
301
|
+
font-size: 14px;
|
|
302
|
+
line-height: 1.2;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
.terminals-list-intro p {
|
|
306
|
+
margin: 4px 0 0;
|
|
307
|
+
font-size: 12px;
|
|
308
|
+
line-height: 1.35;
|
|
309
|
+
opacity: 0.86;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
.terminals-list-intro-refresh {
|
|
313
|
+
display: none;
|
|
314
|
+
align-items: center;
|
|
315
|
+
gap: 6px;
|
|
316
|
+
font-size: 11px;
|
|
317
|
+
line-height: 1;
|
|
318
|
+
opacity: 0.9;
|
|
319
|
+
white-space: nowrap;
|
|
320
|
+
color: #1d4ed8;
|
|
321
|
+
background: rgba(29, 78, 216, 0.12);
|
|
322
|
+
border: 1px solid rgba(29, 78, 216, 0.28);
|
|
323
|
+
border-radius: 999px;
|
|
324
|
+
padding: 3px 8px;
|
|
325
|
+
flex: 0 0 auto;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
.terminals-list-intro.is-loading .terminals-list-intro-refresh {
|
|
329
|
+
display: inline-flex;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
.terminals-list-intro-progress {
|
|
333
|
+
position: absolute;
|
|
334
|
+
left: 0;
|
|
335
|
+
top: 0;
|
|
336
|
+
width: 38%;
|
|
337
|
+
height: 2px;
|
|
338
|
+
opacity: 0;
|
|
339
|
+
pointer-events: none;
|
|
340
|
+
background: linear-gradient(90deg, rgba(29, 78, 216, 0), rgba(29, 78, 216, 0.95), rgba(29, 78, 216, 0));
|
|
341
|
+
transform: translateX(-140%);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
.terminals-list-intro.is-loading .terminals-list-intro-progress {
|
|
345
|
+
opacity: 1;
|
|
346
|
+
animation: terminalsIntroRefreshBar 1.1s linear infinite;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
.terminals-list-intro-refresh i {
|
|
350
|
+
font-size: 11px;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
body.dark .terminals-list-intro-refresh {
|
|
354
|
+
color: #93c5fd;
|
|
355
|
+
background: rgba(37, 99, 235, 0.22);
|
|
356
|
+
border-color: rgba(96, 165, 250, 0.4);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
@keyframes terminalsIntroRefreshBar {
|
|
360
|
+
0% {
|
|
361
|
+
transform: translateX(-140%);
|
|
362
|
+
}
|
|
363
|
+
100% {
|
|
364
|
+
transform: translateX(280%);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
233
368
|
.terminals-column {
|
|
234
369
|
flex: 0 0 100%;
|
|
235
370
|
border: 1px solid rgba(127, 127, 127, 0.2);
|
|
@@ -994,7 +1129,86 @@ body.dark .terminals-column .terminals-search input[type="search"] {
|
|
|
994
1129
|
}
|
|
995
1130
|
|
|
996
1131
|
.terminals-column .terminals-empty {
|
|
997
|
-
margin
|
|
1132
|
+
margin: 10px 12px 0;
|
|
1133
|
+
padding: 20px 16px;
|
|
1134
|
+
border: 1px dashed rgba(127, 127, 127, 0.34);
|
|
1135
|
+
border-radius: 10px;
|
|
1136
|
+
background: linear-gradient(180deg, rgba(255, 255, 255, 0.32), rgba(255, 255, 255, 0.08));
|
|
1137
|
+
display: flex;
|
|
1138
|
+
align-items: center;
|
|
1139
|
+
justify-content: center;
|
|
1140
|
+
text-align: center;
|
|
1141
|
+
min-height: 176px;
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
body.dark .terminals-column .terminals-empty {
|
|
1145
|
+
border-color: rgba(255, 255, 255, 0.2);
|
|
1146
|
+
background: linear-gradient(180deg, rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0.01));
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
.terminals-empty-card {
|
|
1150
|
+
width: 100%;
|
|
1151
|
+
max-width: 460px;
|
|
1152
|
+
display: flex;
|
|
1153
|
+
flex-direction: column;
|
|
1154
|
+
align-items: center;
|
|
1155
|
+
gap: 8px;
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
.terminals-empty-icon {
|
|
1159
|
+
width: 36px;
|
|
1160
|
+
height: 36px;
|
|
1161
|
+
border-radius: 999px;
|
|
1162
|
+
display: inline-flex;
|
|
1163
|
+
align-items: center;
|
|
1164
|
+
justify-content: center;
|
|
1165
|
+
background: rgba(31, 41, 55, 0.08);
|
|
1166
|
+
color: rgba(17, 24, 39, 0.88);
|
|
1167
|
+
font-size: 15px;
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
body.dark .terminals-empty-icon {
|
|
1171
|
+
background: rgba(255, 255, 255, 0.12);
|
|
1172
|
+
color: rgba(255, 255, 255, 0.92);
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
.terminals-empty-title {
|
|
1176
|
+
margin: 2px 0 0;
|
|
1177
|
+
font-size: 15px;
|
|
1178
|
+
font-weight: 700;
|
|
1179
|
+
letter-spacing: 0.01em;
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
.terminals-empty-message {
|
|
1183
|
+
font-size: 13px;
|
|
1184
|
+
line-height: 1.35;
|
|
1185
|
+
opacity: 0.9;
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
.terminals-empty-hint {
|
|
1189
|
+
font-size: 11px;
|
|
1190
|
+
line-height: 1.35;
|
|
1191
|
+
opacity: 0.66;
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
.terminals-empty[data-state="loading"] .terminals-empty-icon {
|
|
1195
|
+
background: rgba(37, 99, 235, 0.12);
|
|
1196
|
+
color: #1d4ed8;
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
body.dark .terminals-empty[data-state="loading"] .terminals-empty-icon {
|
|
1200
|
+
background: rgba(59, 130, 246, 0.22);
|
|
1201
|
+
color: #93c5fd;
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
.terminals-empty[data-state="error"] .terminals-empty-icon {
|
|
1205
|
+
background: rgba(220, 38, 38, 0.12);
|
|
1206
|
+
color: #b91c1c;
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
body.dark .terminals-empty[data-state="error"] .terminals-empty-icon {
|
|
1210
|
+
background: rgba(248, 113, 113, 0.2);
|
|
1211
|
+
color: #fecaca;
|
|
998
1212
|
}
|
|
999
1213
|
|
|
1000
1214
|
.terminals-list {
|
|
@@ -1002,6 +1216,17 @@ body.dark .terminals-column .terminals-search input[type="search"] {
|
|
|
1002
1216
|
padding: 0;
|
|
1003
1217
|
}
|
|
1004
1218
|
|
|
1219
|
+
.terminals-load-more-indicator {
|
|
1220
|
+
padding: 10px 12px 14px;
|
|
1221
|
+
font-size: 12px;
|
|
1222
|
+
text-align: center;
|
|
1223
|
+
opacity: 0.68;
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
body.dark .terminals-load-more-indicator {
|
|
1227
|
+
opacity: 0.76;
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1005
1230
|
.terminals-list .line {
|
|
1006
1231
|
margin: 0;
|
|
1007
1232
|
display: block;
|
|
@@ -1077,8 +1302,8 @@ body.dark .terminals-list .line.active {
|
|
|
1077
1302
|
}
|
|
1078
1303
|
|
|
1079
1304
|
.terminals-list .line .terminals-session-dot.is-online {
|
|
1080
|
-
background:
|
|
1081
|
-
box-shadow: 0 0 0 1px rgba(
|
|
1305
|
+
background: yellowgreen;
|
|
1306
|
+
box-shadow: 0 0 0 1px rgba(154, 205, 50, 0.42);
|
|
1082
1307
|
}
|
|
1083
1308
|
|
|
1084
1309
|
.terminals-list .line .terminals-session-dot.is-offline {
|
|
@@ -1092,8 +1317,8 @@ body.dark .terminals-list .line .terminals-session-dot {
|
|
|
1092
1317
|
}
|
|
1093
1318
|
|
|
1094
1319
|
body.dark .terminals-list .line .terminals-session-dot.is-online {
|
|
1095
|
-
background:
|
|
1096
|
-
box-shadow: 0 0 0 1px rgba(
|
|
1320
|
+
background: yellowgreen;
|
|
1321
|
+
box-shadow: 0 0 0 1px rgba(154, 205, 50, 0.42);
|
|
1097
1322
|
}
|
|
1098
1323
|
|
|
1099
1324
|
body.dark .terminals-list .line .terminals-session-dot.is-offline {
|
|
@@ -1200,8 +1425,10 @@ body.dark .terminals-list .line .terminals-session-action:hover {
|
|
|
1200
1425
|
|
|
1201
1426
|
.terminals-column .terminals-open-folder,
|
|
1202
1427
|
.terminals-column .terminals-fork-session,
|
|
1428
|
+
.terminals-column .terminals-deploy-local,
|
|
1203
1429
|
.terminals-top-session .terminals-open-folder,
|
|
1204
|
-
.terminals-top-session .terminals-fork-session
|
|
1430
|
+
.terminals-top-session .terminals-fork-session,
|
|
1431
|
+
.terminals-top-session .terminals-deploy-local {
|
|
1205
1432
|
display: inline-flex;
|
|
1206
1433
|
align-items: center;
|
|
1207
1434
|
gap: 6px;
|
|
@@ -1214,11 +1441,22 @@ body.dark .terminals-list .line .terminals-session-action:hover {
|
|
|
1214
1441
|
font-size: 12px;
|
|
1215
1442
|
white-space: nowrap;
|
|
1216
1443
|
}
|
|
1444
|
+
.terminals-copy-icon {
|
|
1445
|
+
width: 1.25em;
|
|
1446
|
+
height: 1em;
|
|
1447
|
+
line-height: 1em;
|
|
1448
|
+
}
|
|
1449
|
+
.terminals-copy-icon .fa-copy {
|
|
1450
|
+
font-size: 0.55em;
|
|
1451
|
+
transform: translate(0.5em, 0.48em);
|
|
1452
|
+
}
|
|
1217
1453
|
|
|
1218
1454
|
.terminals-column .terminals-open-folder:disabled,
|
|
1219
1455
|
.terminals-column .terminals-fork-session:disabled,
|
|
1456
|
+
.terminals-column .terminals-deploy-local:disabled,
|
|
1220
1457
|
.terminals-top-session .terminals-open-folder:disabled,
|
|
1221
|
-
.terminals-top-session .terminals-fork-session:disabled
|
|
1458
|
+
.terminals-top-session .terminals-fork-session:disabled,
|
|
1459
|
+
.terminals-top-session .terminals-deploy-local:disabled {
|
|
1222
1460
|
opacity: 0.45;
|
|
1223
1461
|
cursor: not-allowed;
|
|
1224
1462
|
}
|
|
@@ -1229,6 +1467,93 @@ body.dark .terminals-list .line .terminals-session-action:hover {
|
|
|
1229
1467
|
gap: 6px;
|
|
1230
1468
|
}
|
|
1231
1469
|
|
|
1470
|
+
.terminals-top-session .terminals-chooser-actions {
|
|
1471
|
+
position: relative;
|
|
1472
|
+
}
|
|
1473
|
+
|
|
1474
|
+
.terminals-overflow-toggle {
|
|
1475
|
+
display: none;
|
|
1476
|
+
align-items: center;
|
|
1477
|
+
gap: 6px;
|
|
1478
|
+
height: 25px;
|
|
1479
|
+
padding: 0 10px;
|
|
1480
|
+
border-radius: 5px;
|
|
1481
|
+
border: 1px solid rgba(127, 127, 127, 0.25);
|
|
1482
|
+
background: rgba(0, 0, 0, 0.02);
|
|
1483
|
+
color: inherit;
|
|
1484
|
+
cursor: pointer;
|
|
1485
|
+
font-size: 12px;
|
|
1486
|
+
line-height: 1;
|
|
1487
|
+
white-space: nowrap;
|
|
1488
|
+
box-sizing: border-box;
|
|
1489
|
+
}
|
|
1490
|
+
|
|
1491
|
+
.terminals-overflow-toggle:disabled {
|
|
1492
|
+
opacity: 0.45;
|
|
1493
|
+
cursor: not-allowed;
|
|
1494
|
+
}
|
|
1495
|
+
|
|
1496
|
+
.terminals-overflow-menu {
|
|
1497
|
+
position: absolute;
|
|
1498
|
+
right: 0;
|
|
1499
|
+
top: calc(100% + 6px);
|
|
1500
|
+
min-width: 190px;
|
|
1501
|
+
display: flex;
|
|
1502
|
+
flex-direction: column;
|
|
1503
|
+
gap: 4px;
|
|
1504
|
+
padding: 6px;
|
|
1505
|
+
border: 1px solid rgba(127, 127, 127, 0.25);
|
|
1506
|
+
border-radius: 8px;
|
|
1507
|
+
background: #fff;
|
|
1508
|
+
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2);
|
|
1509
|
+
z-index: 30;
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1512
|
+
body.dark .terminals-overflow-menu {
|
|
1513
|
+
background: #111827;
|
|
1514
|
+
border-color: rgba(255, 255, 255, 0.12);
|
|
1515
|
+
}
|
|
1516
|
+
|
|
1517
|
+
.terminals-overflow-menu button {
|
|
1518
|
+
display: inline-flex;
|
|
1519
|
+
align-items: center;
|
|
1520
|
+
gap: 8px;
|
|
1521
|
+
width: 100%;
|
|
1522
|
+
height: 30px;
|
|
1523
|
+
padding: 0 10px;
|
|
1524
|
+
border: 1px solid transparent;
|
|
1525
|
+
border-radius: 6px;
|
|
1526
|
+
background: transparent;
|
|
1527
|
+
color: inherit;
|
|
1528
|
+
font-size: 12px;
|
|
1529
|
+
text-align: left;
|
|
1530
|
+
cursor: pointer;
|
|
1531
|
+
}
|
|
1532
|
+
|
|
1533
|
+
.terminals-overflow-menu button:hover {
|
|
1534
|
+
background: rgba(0, 0, 0, 0.06);
|
|
1535
|
+
}
|
|
1536
|
+
|
|
1537
|
+
body.dark .terminals-overflow-menu button:hover {
|
|
1538
|
+
background: rgba(255, 255, 255, 0.08);
|
|
1539
|
+
}
|
|
1540
|
+
|
|
1541
|
+
.terminals-overflow-menu button:disabled {
|
|
1542
|
+
opacity: 0.45;
|
|
1543
|
+
cursor: not-allowed;
|
|
1544
|
+
}
|
|
1545
|
+
|
|
1546
|
+
@media only screen and (max-width: 980px) {
|
|
1547
|
+
.terminals-top-session .terminals-open-folder,
|
|
1548
|
+
.terminals-top-session .terminals-deploy-local,
|
|
1549
|
+
.terminals-top-session .close-column {
|
|
1550
|
+
display: none;
|
|
1551
|
+
}
|
|
1552
|
+
.terminals-top-session .terminals-overflow-toggle {
|
|
1553
|
+
display: inline-flex;
|
|
1554
|
+
}
|
|
1555
|
+
}
|
|
1556
|
+
|
|
1232
1557
|
.terminals-column iframe {
|
|
1233
1558
|
width: 100%;
|
|
1234
1559
|
flex: 1;
|
|
@@ -1315,6 +1640,194 @@ body.dark .swal2-popup.terminals-confirm-modal .swal2-styled.swal2-cancel {
|
|
|
1315
1640
|
color: rgba(255, 255, 255, 0.8);
|
|
1316
1641
|
border-color: rgba(255, 255, 255, 0.2);
|
|
1317
1642
|
}
|
|
1643
|
+
|
|
1644
|
+
.swal2-popup.pinokio-modern-modal {
|
|
1645
|
+
border-radius: 20px !important;
|
|
1646
|
+
padding: 0 !important;
|
|
1647
|
+
background: #ffffff !important;
|
|
1648
|
+
color: #0f172a !important;
|
|
1649
|
+
box-shadow: 0 32px 80px rgba(15, 23, 42, 0.25) !important;
|
|
1650
|
+
overflow: hidden !important;
|
|
1651
|
+
}
|
|
1652
|
+
|
|
1653
|
+
body.dark .swal2-popup.pinokio-modern-modal {
|
|
1654
|
+
background: #0f172a !important;
|
|
1655
|
+
color: #e2e8f0 !important;
|
|
1656
|
+
box-shadow: 0 40px 120px rgba(2, 8, 23, 0.7) !important;
|
|
1657
|
+
}
|
|
1658
|
+
|
|
1659
|
+
.pinokio-modern-html {
|
|
1660
|
+
margin: 0 !important;
|
|
1661
|
+
padding: 0 !important;
|
|
1662
|
+
}
|
|
1663
|
+
|
|
1664
|
+
.pinokio-modern-close.swal2-close {
|
|
1665
|
+
color: rgba(71, 85, 105, 0.65) !important;
|
|
1666
|
+
font-size: 18px !important;
|
|
1667
|
+
margin: 12px 12px 0 0 !important;
|
|
1668
|
+
}
|
|
1669
|
+
|
|
1670
|
+
body.dark .pinokio-modern-close.swal2-close {
|
|
1671
|
+
color: rgba(226, 232, 240, 0.6) !important;
|
|
1672
|
+
}
|
|
1673
|
+
|
|
1674
|
+
.pinokio-modern-confirm.swal2-confirm {
|
|
1675
|
+
background: #2563eb !important;
|
|
1676
|
+
color: #ffffff !important;
|
|
1677
|
+
border: none !important;
|
|
1678
|
+
border-radius: 10px !important;
|
|
1679
|
+
padding: 10px 18px !important;
|
|
1680
|
+
font-size: 13px !important;
|
|
1681
|
+
font-weight: 700 !important;
|
|
1682
|
+
}
|
|
1683
|
+
|
|
1684
|
+
.pinokio-modern-confirm.swal2-confirm:hover {
|
|
1685
|
+
background: #1d4ed8 !important;
|
|
1686
|
+
}
|
|
1687
|
+
|
|
1688
|
+
.pinokio-modern-cancel.swal2-cancel {
|
|
1689
|
+
background: rgba(148, 163, 184, 0.16) !important;
|
|
1690
|
+
color: #0f172a !important;
|
|
1691
|
+
border: none !important;
|
|
1692
|
+
border-radius: 10px !important;
|
|
1693
|
+
padding: 10px 18px !important;
|
|
1694
|
+
font-size: 13px !important;
|
|
1695
|
+
font-weight: 600 !important;
|
|
1696
|
+
}
|
|
1697
|
+
|
|
1698
|
+
body.dark .pinokio-modern-cancel.swal2-cancel {
|
|
1699
|
+
background: rgba(148, 163, 184, 0.14) !important;
|
|
1700
|
+
color: #e2e8f0 !important;
|
|
1701
|
+
}
|
|
1702
|
+
|
|
1703
|
+
.pinokio-modern-cancel.swal2-cancel:hover {
|
|
1704
|
+
background: rgba(148, 163, 184, 0.24) !important;
|
|
1705
|
+
}
|
|
1706
|
+
|
|
1707
|
+
body.dark .pinokio-modern-cancel.swal2-cancel:hover {
|
|
1708
|
+
background: rgba(148, 163, 184, 0.22) !important;
|
|
1709
|
+
}
|
|
1710
|
+
|
|
1711
|
+
.pinokio-modal-surface {
|
|
1712
|
+
display: flex;
|
|
1713
|
+
flex-direction: column;
|
|
1714
|
+
}
|
|
1715
|
+
|
|
1716
|
+
.pinokio-modal-header {
|
|
1717
|
+
display: flex;
|
|
1718
|
+
gap: 12px;
|
|
1719
|
+
align-items: center;
|
|
1720
|
+
padding: 18px 20px 8px 20px;
|
|
1721
|
+
}
|
|
1722
|
+
|
|
1723
|
+
.pinokio-modal-icon {
|
|
1724
|
+
width: 40px;
|
|
1725
|
+
height: 40px;
|
|
1726
|
+
border-radius: 12px;
|
|
1727
|
+
display: inline-flex;
|
|
1728
|
+
align-items: center;
|
|
1729
|
+
justify-content: center;
|
|
1730
|
+
background: linear-gradient(135deg, rgba(59, 130, 246, 0.18), rgba(59, 130, 246, 0.03));
|
|
1731
|
+
color: #2563eb;
|
|
1732
|
+
}
|
|
1733
|
+
|
|
1734
|
+
body.dark .pinokio-modal-icon {
|
|
1735
|
+
background: linear-gradient(135deg, rgba(59, 130, 246, 0.25), rgba(59, 130, 246, 0.05));
|
|
1736
|
+
color: #60a5fa;
|
|
1737
|
+
}
|
|
1738
|
+
|
|
1739
|
+
.pinokio-modal-heading {
|
|
1740
|
+
min-width: 0;
|
|
1741
|
+
}
|
|
1742
|
+
|
|
1743
|
+
.pinokio-modal-title {
|
|
1744
|
+
font-size: 18px;
|
|
1745
|
+
font-weight: 700;
|
|
1746
|
+
line-height: 1.2;
|
|
1747
|
+
color: #0f172a;
|
|
1748
|
+
}
|
|
1749
|
+
|
|
1750
|
+
body.dark .pinokio-modal-title {
|
|
1751
|
+
color: #f8fafc;
|
|
1752
|
+
}
|
|
1753
|
+
|
|
1754
|
+
.pinokio-modal-subtitle {
|
|
1755
|
+
margin-top: 4px;
|
|
1756
|
+
font-size: 13px;
|
|
1757
|
+
line-height: 1.4;
|
|
1758
|
+
color: rgba(71, 85, 105, 0.82);
|
|
1759
|
+
}
|
|
1760
|
+
|
|
1761
|
+
body.dark .pinokio-modal-subtitle {
|
|
1762
|
+
color: rgba(148, 163, 184, 0.85);
|
|
1763
|
+
}
|
|
1764
|
+
|
|
1765
|
+
.pinokio-modal-body {
|
|
1766
|
+
padding: 10px 20px 20px 20px;
|
|
1767
|
+
display: flex;
|
|
1768
|
+
flex-direction: column;
|
|
1769
|
+
gap: 10px;
|
|
1770
|
+
}
|
|
1771
|
+
|
|
1772
|
+
.pinokio-modal-note {
|
|
1773
|
+
margin: 0;
|
|
1774
|
+
font-size: 13px;
|
|
1775
|
+
line-height: 1.5;
|
|
1776
|
+
color: rgba(30, 41, 59, 0.9);
|
|
1777
|
+
}
|
|
1778
|
+
|
|
1779
|
+
body.dark .pinokio-modal-note {
|
|
1780
|
+
color: rgba(226, 232, 240, 0.92);
|
|
1781
|
+
}
|
|
1782
|
+
|
|
1783
|
+
.pinokio-modal-note code {
|
|
1784
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
|
1785
|
+
background: rgba(148, 163, 184, 0.2);
|
|
1786
|
+
padding: 1px 5px;
|
|
1787
|
+
border-radius: 6px;
|
|
1788
|
+
}
|
|
1789
|
+
|
|
1790
|
+
body.dark .pinokio-modal-note code {
|
|
1791
|
+
background: rgba(148, 163, 184, 0.24);
|
|
1792
|
+
}
|
|
1793
|
+
|
|
1794
|
+
.pinokio-modal-label {
|
|
1795
|
+
font-size: 12px;
|
|
1796
|
+
font-weight: 600;
|
|
1797
|
+
color: rgba(71, 85, 105, 0.95);
|
|
1798
|
+
}
|
|
1799
|
+
|
|
1800
|
+
body.dark .pinokio-modal-label {
|
|
1801
|
+
color: rgba(203, 213, 225, 0.9);
|
|
1802
|
+
}
|
|
1803
|
+
|
|
1804
|
+
.pinokio-modal-input {
|
|
1805
|
+
width: 100%;
|
|
1806
|
+
box-sizing: border-box;
|
|
1807
|
+
border-radius: 10px;
|
|
1808
|
+
border: 1px solid rgba(148, 163, 184, 0.5);
|
|
1809
|
+
background: #ffffff;
|
|
1810
|
+
color: #0f172a;
|
|
1811
|
+
font-size: 14px;
|
|
1812
|
+
padding: 10px 12px;
|
|
1813
|
+
outline: none;
|
|
1814
|
+
}
|
|
1815
|
+
|
|
1816
|
+
.pinokio-modal-input:focus {
|
|
1817
|
+
border-color: rgba(59, 130, 246, 0.6);
|
|
1818
|
+
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.2);
|
|
1819
|
+
}
|
|
1820
|
+
|
|
1821
|
+
body.dark .pinokio-modal-input {
|
|
1822
|
+
background: rgba(15, 23, 42, 0.85);
|
|
1823
|
+
border-color: rgba(148, 163, 184, 0.25);
|
|
1824
|
+
color: #f8fafc;
|
|
1825
|
+
}
|
|
1826
|
+
|
|
1827
|
+
body.dark .pinokio-modal-input:focus {
|
|
1828
|
+
border-color: rgba(96, 165, 250, 0.7);
|
|
1829
|
+
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.25);
|
|
1830
|
+
}
|
|
1318
1831
|
</style>
|
|
1319
1832
|
</head>
|
|
1320
1833
|
<body class="<%= theme %>" data-agent="<%= agent %>">
|
|
@@ -1349,7 +1862,7 @@ body.dark .swal2-popup.terminals-confirm-modal .swal2-styled.swal2-cancel {
|
|
|
1349
1862
|
<form class='search terminals-search-form'>
|
|
1350
1863
|
<div class="home-mode-switch" role="tablist" aria-label="Home modes">
|
|
1351
1864
|
<a class="mode-link" href="/home">
|
|
1352
|
-
<i class="fa-solid fa-
|
|
1865
|
+
<i class="fa-solid fa-computer"></i>
|
|
1353
1866
|
<span>Apps</span>
|
|
1354
1867
|
</a>
|
|
1355
1868
|
<a class="mode-link selected" href="/home?mode=terminals" aria-current="page">
|
|
@@ -1359,8 +1872,8 @@ body.dark .swal2-popup.terminals-confirm-modal .swal2-styled.swal2-cancel {
|
|
|
1359
1872
|
</div>
|
|
1360
1873
|
<div class="terminals-top-search-controls" id="terminals-top-search-controls">
|
|
1361
1874
|
<input id="terminals-session-search" type='search' class="flexible" placeholder='Search sessions'>
|
|
1362
|
-
<button class="terminals-create-toggle" id="terminals-create-session-button" type="button" aria-label="
|
|
1363
|
-
<i class="fa-solid fa-plus"></i><span>
|
|
1875
|
+
<button class="terminals-create-toggle" id="terminals-create-session-button" type="button" aria-label="Create workspace" aria-expanded="false">
|
|
1876
|
+
<i class="fa-solid fa-plus"></i><span>Create workspace</span>
|
|
1364
1877
|
</button>
|
|
1365
1878
|
</div>
|
|
1366
1879
|
<div class="terminals-top-session hidden" id="terminals-top-session" aria-live="polite">
|
|
@@ -1368,7 +1881,14 @@ body.dark .swal2-popup.terminals-confirm-modal .swal2-styled.swal2-cancel {
|
|
|
1368
1881
|
<div class="terminals-chooser-actions">
|
|
1369
1882
|
<button class="terminals-fork-session" id="terminals-top-fork-session" type="button" aria-label="Fork current session" disabled><i class="fa-solid fa-code-branch"></i><span>Fork</span></button>
|
|
1370
1883
|
<button class="terminals-open-folder" id="terminals-top-open-folder" type="button" aria-label="Open in File Explorer" disabled><i class="fa-solid fa-folder-open"></i><span>Open in File Explorer</span></button>
|
|
1884
|
+
<button class="terminals-deploy-local" id="terminals-top-deploy-local" type="button" aria-label="Copy to apps" disabled><span class="fa-stack terminals-copy-icon" aria-hidden="true"><i class="fa-solid fa-computer fa-stack-1x"></i><i class="fa-solid fa-copy fa-stack-1x"></i></span><span>Copy to apps</span></button>
|
|
1371
1885
|
<button class="close-column" id="terminals-top-close-session" type="button" aria-label="Close selected session">×</button>
|
|
1886
|
+
<button class="terminals-overflow-toggle" id="terminals-top-more-actions" type="button" aria-haspopup="menu" aria-expanded="false" aria-label="More actions"><i class="fa-solid fa-ellipsis"></i><span>More</span></button>
|
|
1887
|
+
<div class="terminals-overflow-menu hidden" id="terminals-top-overflow-menu" role="menu" aria-label="More session actions">
|
|
1888
|
+
<button type="button" data-action="open-folder" role="menuitem"><i class="fa-solid fa-folder-open"></i><span>Open in File Explorer</span></button>
|
|
1889
|
+
<button type="button" data-action="deploy-local" role="menuitem"><span class="fa-stack terminals-copy-icon" aria-hidden="true"><i class="fa-solid fa-computer fa-stack-1x"></i><i class="fa-solid fa-copy fa-stack-1x"></i></span><span>Copy to apps</span></button>
|
|
1890
|
+
<button type="button" data-action="close-session" role="menuitem"><i class="fa-solid fa-xmark"></i><span>Close session</span></button>
|
|
1891
|
+
</div>
|
|
1372
1892
|
</div>
|
|
1373
1893
|
</div>
|
|
1374
1894
|
</form>
|
|
@@ -1381,21 +1901,12 @@ body.dark .swal2-popup.terminals-confirm-modal .swal2-styled.swal2-cancel {
|
|
|
1381
1901
|
<span>Run one from your terminal and come back here to continue.</span>
|
|
1382
1902
|
</div>
|
|
1383
1903
|
</div>
|
|
1384
|
-
<div id='terminals-columns' class='terminals-columns hidden'
|
|
1904
|
+
<div id='terminals-columns' class='terminals-columns hidden'>
|
|
1905
|
+
<div id='terminals-columns-rail' class='terminals-columns-rail'></div>
|
|
1906
|
+
</div>
|
|
1385
1907
|
</section>
|
|
1386
1908
|
</div>
|
|
1387
1909
|
|
|
1388
|
-
<div id='terminals-session-bank' class='hidden'>
|
|
1389
|
-
<div class='terminals-list'>
|
|
1390
|
-
<% (items || []).forEach((item, index) => { %>
|
|
1391
|
-
<a href="#" role="button" data-description="<%=item.description%>" data-provider-label="<%=item.provider_label || ''%>" data-cwd="<%=item.cwd || ''%>" data-timestamp="<%=item.timestamp || ''%>" data-index="<%=index%>" data-name="<%=item.name%>" data-uri="<%=item.uri%>" data-url="<%=item.browser_url%>" data-fork-url="<%=item.fork_url || ''%>" data-fork-capable="<%=item.fork_capable === false ? '0' : '1'%>" data-fork-disabled-reason="<%=item.fork_disabled_reason || ''%>" data-online="<%=item.online ? '1' : '0'%>" class='line'>
|
|
1392
|
-
<h3><%=item.name%></h3>
|
|
1393
|
-
<div class='description'><%=item.description%></div>
|
|
1394
|
-
</a>
|
|
1395
|
-
<% }) %>
|
|
1396
|
-
</div>
|
|
1397
|
-
</div>
|
|
1398
|
-
|
|
1399
1910
|
<div id="terminals-launcher-modal" class="terminals-launcher-modal hidden" aria-hidden="true">
|
|
1400
1911
|
<div class="terminals-launcher-backdrop" data-launcher-close="1"></div>
|
|
1401
1912
|
<section class="terminals-launcher-panel" role="dialog" aria-modal="true" aria-labelledby="terminals-launcher-title">
|
|
@@ -1467,7 +1978,8 @@ body.dark .swal2-popup.terminals-confirm-modal .swal2-styled.swal2-cancel {
|
|
|
1467
1978
|
(() => {
|
|
1468
1979
|
const navHeader = document.querySelector("header.navheader")
|
|
1469
1980
|
const root = document.documentElement
|
|
1470
|
-
const
|
|
1981
|
+
const columnsWrap = document.querySelector("#terminals-columns")
|
|
1982
|
+
const columns = document.querySelector("#terminals-columns-rail")
|
|
1471
1983
|
const emptyMain = document.querySelector("#terminals-empty-main")
|
|
1472
1984
|
const searchForm = document.querySelector(".terminals-search-form")
|
|
1473
1985
|
const sessionSearchInput = document.querySelector("#terminals-session-search")
|
|
@@ -1476,7 +1988,10 @@ body.dark .swal2-popup.terminals-confirm-modal .swal2-styled.swal2-cancel {
|
|
|
1476
1988
|
const topSessionTitle = document.querySelector("#terminals-top-session-title")
|
|
1477
1989
|
const topSessionForkButton = document.querySelector("#terminals-top-fork-session")
|
|
1478
1990
|
const topSessionOpenFolderButton = document.querySelector("#terminals-top-open-folder")
|
|
1991
|
+
const topSessionDeployLocalButton = document.querySelector("#terminals-top-deploy-local")
|
|
1479
1992
|
const topSessionCloseButton = document.querySelector("#terminals-top-close-session")
|
|
1993
|
+
const topSessionMoreButton = document.querySelector("#terminals-top-more-actions")
|
|
1994
|
+
const topSessionOverflowMenu = document.querySelector("#terminals-top-overflow-menu")
|
|
1480
1995
|
const startProviders = <%-JSON.stringify(providers || [])%>
|
|
1481
1996
|
const initialSkills = <%-JSON.stringify(skills || [])%>
|
|
1482
1997
|
let availableSkills = Array.isArray(initialSkills) ? initialSkills : []
|
|
@@ -1529,35 +2044,37 @@ body.dark .swal2-popup.terminals-confirm-modal .swal2-styled.swal2-cancel {
|
|
|
1529
2044
|
return parseOnlineFlag(value)
|
|
1530
2045
|
}
|
|
1531
2046
|
|
|
1532
|
-
const
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
cwd,
|
|
1552
|
-
timestamp,
|
|
1553
|
-
uri,
|
|
1554
|
-
url,
|
|
1555
|
-
forkUrl,
|
|
1556
|
-
forkCapable,
|
|
1557
|
-
forkDisabledReason,
|
|
1558
|
-
online,
|
|
2047
|
+
const SESSION_PAGE_SIZE = 120
|
|
2048
|
+
const SESSION_LOAD_MORE_THRESHOLD_PX = 160
|
|
2049
|
+
const SESSION_SEARCH_DEBOUNCE_MS = 250
|
|
2050
|
+
let sessionItems = []
|
|
2051
|
+
let hasAttemptedSessionLoad = false
|
|
2052
|
+
let lastSessionLoadFailed = false
|
|
2053
|
+
let sessionHasMore = true
|
|
2054
|
+
let sessionNextCursor = 0
|
|
2055
|
+
let sessionAppendInProgress = false
|
|
2056
|
+
let currentSessionQuery = ""
|
|
2057
|
+
let sessionSearchDebounceTimer = null
|
|
2058
|
+
let sessionFetchAbortController = null
|
|
2059
|
+
let sessionFetchRequestId = 0
|
|
2060
|
+
let hasTriggeredBackgroundRegistrySync = false
|
|
2061
|
+
let sessionRefreshActivityCount = 0
|
|
2062
|
+
|
|
2063
|
+
const syncIntroLoadingIndicators = () => {
|
|
2064
|
+
if (!columns) {
|
|
2065
|
+
return
|
|
1559
2066
|
}
|
|
1560
|
-
|
|
2067
|
+
const isRefreshing = sessionRefreshActivityCount > 0
|
|
2068
|
+
const introNodes = columns.querySelectorAll(".terminals-list-intro")
|
|
2069
|
+
introNodes.forEach((introNode) => {
|
|
2070
|
+
introNode.classList.toggle("is-loading", isRefreshing)
|
|
2071
|
+
})
|
|
2072
|
+
}
|
|
2073
|
+
|
|
2074
|
+
const adjustSessionRefreshActivity = (delta) => {
|
|
2075
|
+
sessionRefreshActivityCount = Math.max(0, sessionRefreshActivityCount + delta)
|
|
2076
|
+
syncIntroLoadingIndicators()
|
|
2077
|
+
}
|
|
1561
2078
|
|
|
1562
2079
|
const normalizeSessionItem = (item, indexFallback = 0) => {
|
|
1563
2080
|
const index = normalizeIndex(item && typeof item.index !== "undefined" ? item.index : indexFallback)
|
|
@@ -1568,6 +2085,15 @@ body.dark .swal2-popup.terminals-confirm-modal .swal2-styled.swal2-cancel {
|
|
|
1568
2085
|
const timestamp = parseTimestamp(item && item.timestamp ? item.timestamp : null)
|
|
1569
2086
|
const uri = item && item.uri ? item.uri : ""
|
|
1570
2087
|
const url = item && (item.browser_url || item.url) ? (item.browser_url || item.url) : "#"
|
|
2088
|
+
const hasResumeCapable = Boolean(item)
|
|
2089
|
+
&& (Object.prototype.hasOwnProperty.call(item, "resume_capable") || Object.prototype.hasOwnProperty.call(item, "resumeCapable"))
|
|
2090
|
+
const resumeCapableValue = hasResumeCapable
|
|
2091
|
+
? (Object.prototype.hasOwnProperty.call(item, "resume_capable") ? item.resume_capable : item.resumeCapable)
|
|
2092
|
+
: undefined
|
|
2093
|
+
const resumeCapable = parseBooleanFlag(resumeCapableValue, true)
|
|
2094
|
+
const resumeDisabledReason = item && (item.resume_disabled_reason || item.resumeDisabledReason)
|
|
2095
|
+
? String(item.resume_disabled_reason || item.resumeDisabledReason)
|
|
2096
|
+
: ""
|
|
1571
2097
|
const forkUrl = item && (item.fork_url || item.forkUrl) ? (item.fork_url || item.forkUrl) : ""
|
|
1572
2098
|
const hasForkCapable = Boolean(item)
|
|
1573
2099
|
&& (Object.prototype.hasOwnProperty.call(item, "fork_capable") || Object.prototype.hasOwnProperty.call(item, "forkCapable"))
|
|
@@ -1588,6 +2114,8 @@ body.dark .swal2-popup.terminals-confirm-modal .swal2-styled.swal2-cancel {
|
|
|
1588
2114
|
timestamp,
|
|
1589
2115
|
uri,
|
|
1590
2116
|
url,
|
|
2117
|
+
resumeCapable,
|
|
2118
|
+
resumeDisabledReason,
|
|
1591
2119
|
forkUrl,
|
|
1592
2120
|
forkCapable,
|
|
1593
2121
|
forkDisabledReason,
|
|
@@ -1605,6 +2133,9 @@ body.dark .swal2-popup.terminals-confirm-modal .swal2-styled.swal2-cancel {
|
|
|
1605
2133
|
const normalizeIndex = (value) => {
|
|
1606
2134
|
return String(value || "").trim()
|
|
1607
2135
|
}
|
|
2136
|
+
const normalizeCwdKey = (value) => {
|
|
2137
|
+
return normalizeIndex(value).replace(/\/+$/, "")
|
|
2138
|
+
}
|
|
1608
2139
|
|
|
1609
2140
|
const isTerminalColumn = (column) => {
|
|
1610
2141
|
return Boolean(column && column.isConnected && column.dataset && column.dataset.state === "terminal")
|
|
@@ -1641,6 +2172,11 @@ body.dark .swal2-popup.terminals-confirm-modal .swal2-styled.swal2-cancel {
|
|
|
1641
2172
|
if (topSessionOpenFolderButton) {
|
|
1642
2173
|
topSessionOpenFolderButton.disabled = true
|
|
1643
2174
|
}
|
|
2175
|
+
if (topSessionDeployLocalButton) {
|
|
2176
|
+
topSessionDeployLocalButton.disabled = true
|
|
2177
|
+
}
|
|
2178
|
+
closeTopSessionOverflowMenu()
|
|
2179
|
+
syncTopSessionOverflowMenu()
|
|
1644
2180
|
return
|
|
1645
2181
|
}
|
|
1646
2182
|
|
|
@@ -1671,6 +2207,43 @@ body.dark .swal2-popup.terminals-confirm-modal .swal2-styled.swal2-cancel {
|
|
|
1671
2207
|
if (topSessionOpenFolderButton) {
|
|
1672
2208
|
topSessionOpenFolderButton.disabled = !sessionCwd
|
|
1673
2209
|
}
|
|
2210
|
+
if (topSessionDeployLocalButton) {
|
|
2211
|
+
topSessionDeployLocalButton.disabled = !sessionCwd
|
|
2212
|
+
}
|
|
2213
|
+
syncTopSessionOverflowMenu()
|
|
2214
|
+
}
|
|
2215
|
+
|
|
2216
|
+
const closeTopSessionOverflowMenu = () => {
|
|
2217
|
+
if (topSessionOverflowMenu) {
|
|
2218
|
+
topSessionOverflowMenu.classList.add("hidden")
|
|
2219
|
+
}
|
|
2220
|
+
if (topSessionMoreButton) {
|
|
2221
|
+
topSessionMoreButton.setAttribute("aria-expanded", "false")
|
|
2222
|
+
}
|
|
2223
|
+
}
|
|
2224
|
+
|
|
2225
|
+
const syncTopSessionOverflowMenu = () => {
|
|
2226
|
+
if (!topSessionOverflowMenu) {
|
|
2227
|
+
return
|
|
2228
|
+
}
|
|
2229
|
+
const openFolderOverflowButton = topSessionOverflowMenu.querySelector("[data-action='open-folder']")
|
|
2230
|
+
if (openFolderOverflowButton && topSessionOpenFolderButton) {
|
|
2231
|
+
openFolderOverflowButton.disabled = Boolean(topSessionOpenFolderButton.disabled)
|
|
2232
|
+
}
|
|
2233
|
+
const deployOverflowButton = topSessionOverflowMenu.querySelector("[data-action='deploy-local']")
|
|
2234
|
+
if (deployOverflowButton && topSessionDeployLocalButton) {
|
|
2235
|
+
deployOverflowButton.disabled = Boolean(topSessionDeployLocalButton.disabled)
|
|
2236
|
+
}
|
|
2237
|
+
const closeOverflowButton = topSessionOverflowMenu.querySelector("[data-action='close-session']")
|
|
2238
|
+
if (closeOverflowButton && topSessionCloseButton) {
|
|
2239
|
+
closeOverflowButton.disabled = Boolean(topSessionCloseButton.disabled)
|
|
2240
|
+
}
|
|
2241
|
+
if (topSessionMoreButton) {
|
|
2242
|
+
const allDisabled = [openFolderOverflowButton, deployOverflowButton, closeOverflowButton]
|
|
2243
|
+
.filter(Boolean)
|
|
2244
|
+
.every((button) => button.disabled)
|
|
2245
|
+
topSessionMoreButton.disabled = allDisabled
|
|
2246
|
+
}
|
|
1674
2247
|
}
|
|
1675
2248
|
|
|
1676
2249
|
const setActiveTerminalColumn = (column) => {
|
|
@@ -1806,10 +2379,29 @@ body.dark .swal2-popup.terminals-confirm-modal .swal2-styled.swal2-cancel {
|
|
|
1806
2379
|
}
|
|
1807
2380
|
return "Fork unavailable for this session."
|
|
1808
2381
|
}
|
|
2382
|
+
const getResumeUnavailableReason = (session) => {
|
|
2383
|
+
if (session && typeof session.resumeDisabledReason === "string" && session.resumeDisabledReason.trim().length > 0) {
|
|
2384
|
+
return session.resumeDisabledReason.trim()
|
|
2385
|
+
}
|
|
2386
|
+
return "Resume unavailable for this session."
|
|
2387
|
+
}
|
|
2388
|
+
const canSessionResume = (session) => {
|
|
2389
|
+
if (!session || typeof session !== "object") {
|
|
2390
|
+
return false
|
|
2391
|
+
}
|
|
2392
|
+
if (!parseBooleanFlag(session.resumeCapable, true)) {
|
|
2393
|
+
return false
|
|
2394
|
+
}
|
|
2395
|
+
const resumeUrl = typeof session.url === "string" ? session.url.trim() : ""
|
|
2396
|
+
return resumeUrl.length > 0 && resumeUrl !== "#"
|
|
2397
|
+
}
|
|
1809
2398
|
const canSessionFork = (session) => {
|
|
1810
2399
|
if (!session || typeof session !== "object") {
|
|
1811
2400
|
return false
|
|
1812
2401
|
}
|
|
2402
|
+
if (!canSessionResume(session)) {
|
|
2403
|
+
return false
|
|
2404
|
+
}
|
|
1813
2405
|
if (!parseBooleanFlag(session.forkCapable, true)) {
|
|
1814
2406
|
return false
|
|
1815
2407
|
}
|
|
@@ -1843,10 +2435,12 @@ body.dark .swal2-popup.terminals-confirm-modal .swal2-styled.swal2-cancel {
|
|
|
1843
2435
|
const source = session && typeof session === "object" ? session : {}
|
|
1844
2436
|
const forkSuffix = `${Date.now()}-${Math.random().toString(16).slice(2)}`
|
|
1845
2437
|
const baseName = source && source.name ? String(source.name) : "Session"
|
|
2438
|
+
const providerKey = normalizeProviderKey(getProviderKey(source) || "session")
|
|
1846
2439
|
const forkUrl = buildForkLaunchUrl(source)
|
|
1847
2440
|
return {
|
|
1848
2441
|
...source,
|
|
1849
2442
|
index: `fork-${normalizeIndex(source.index)}-${forkSuffix}`,
|
|
2443
|
+
uri: `${providerKey}:pending-fork-${forkSuffix}`,
|
|
1850
2444
|
name: `${baseName} (fork)`,
|
|
1851
2445
|
url: forkUrl,
|
|
1852
2446
|
forkCapable: forkUrl !== "#",
|
|
@@ -1911,32 +2505,15 @@ body.dark .swal2-popup.terminals-confirm-modal .swal2-styled.swal2-cancel {
|
|
|
1911
2505
|
if (aOnline !== bOnline) {
|
|
1912
2506
|
return bOnline - aOnline
|
|
1913
2507
|
}
|
|
1914
|
-
const ta = parseTimestamp(a && a.timestamp ? a.timestamp : null) || 0
|
|
1915
|
-
const tb = parseTimestamp(b && b.timestamp ? b.timestamp : null) || 0
|
|
1916
|
-
if (ta !== tb) {
|
|
1917
|
-
return tb - ta
|
|
1918
|
-
}
|
|
1919
|
-
const an = (a && a.name ? String(a.name) : "").toLowerCase()
|
|
1920
|
-
const bn = (b && b.name ? String(b.name) : "").toLowerCase()
|
|
1921
|
-
return an.localeCompare(bn)
|
|
1922
|
-
}
|
|
1923
|
-
const getExistingOnlineSessionByProvider = (providerKey) => {
|
|
1924
|
-
const normalizedProvider = normalizeProviderKey(providerKey)
|
|
1925
|
-
if (!normalizedProvider) {
|
|
1926
|
-
return null
|
|
1927
|
-
}
|
|
1928
|
-
for (let i = 0; i < sessionItems.length; i++) {
|
|
1929
|
-
const session = sessionItems[i]
|
|
1930
|
-
if (!session || !isSessionOnlineNow(session)) {
|
|
1931
|
-
continue
|
|
1932
|
-
}
|
|
1933
|
-
if (getProviderKey(session) === normalizedProvider) {
|
|
1934
|
-
return session
|
|
1935
|
-
}
|
|
1936
|
-
}
|
|
1937
|
-
return null
|
|
2508
|
+
const ta = parseTimestamp(a && a.timestamp ? a.timestamp : null) || 0
|
|
2509
|
+
const tb = parseTimestamp(b && b.timestamp ? b.timestamp : null) || 0
|
|
2510
|
+
if (ta !== tb) {
|
|
2511
|
+
return tb - ta
|
|
2512
|
+
}
|
|
2513
|
+
const an = (a && a.name ? String(a.name) : "").toLowerCase()
|
|
2514
|
+
const bn = (b && b.name ? String(b.name) : "").toLowerCase()
|
|
2515
|
+
return an.localeCompare(bn)
|
|
1938
2516
|
}
|
|
1939
|
-
|
|
1940
2517
|
if (!launcherState.provider && startProviders && startProviders[0] && startProviders[0].key) {
|
|
1941
2518
|
launcherState.provider = normalizeProviderKey(startProviders[0].key)
|
|
1942
2519
|
}
|
|
@@ -2360,14 +2937,6 @@ body.dark .swal2-popup.terminals-confirm-modal .swal2-styled.swal2-cancel {
|
|
|
2360
2937
|
setLauncherBusy(true)
|
|
2361
2938
|
let shouldClose = false
|
|
2362
2939
|
try {
|
|
2363
|
-
await refreshSessionItems({ force: true })
|
|
2364
|
-
refreshChooserRows()
|
|
2365
|
-
const existingSession = getExistingOnlineSessionByProvider(provider)
|
|
2366
|
-
if (existingSession) {
|
|
2367
|
-
openItemInColumn(column, existingSession)
|
|
2368
|
-
shouldClose = true
|
|
2369
|
-
return
|
|
2370
|
-
}
|
|
2371
2940
|
let uploadToken = ""
|
|
2372
2941
|
if (launcherState.pendingFiles.length > 0) {
|
|
2373
2942
|
const uploaded = await uploadLauncherFiles()
|
|
@@ -2536,6 +3105,156 @@ body.dark .swal2-popup.terminals-confirm-modal .swal2-styled.swal2-cancel {
|
|
|
2536
3105
|
}
|
|
2537
3106
|
}
|
|
2538
3107
|
|
|
3108
|
+
const deployLocalCopy = async ({ folderName, sessionUri, sessionCwd }) => {
|
|
3109
|
+
try {
|
|
3110
|
+
const response = await fetch("/terminals/deploy/local", {
|
|
3111
|
+
method: "POST",
|
|
3112
|
+
headers: {
|
|
3113
|
+
"Content-Type": "application/json"
|
|
3114
|
+
},
|
|
3115
|
+
body: JSON.stringify({
|
|
3116
|
+
folderName,
|
|
3117
|
+
sessionUri,
|
|
3118
|
+
sessionCwd
|
|
3119
|
+
})
|
|
3120
|
+
})
|
|
3121
|
+
let payload = null
|
|
3122
|
+
try {
|
|
3123
|
+
payload = await response.json()
|
|
3124
|
+
} catch (error) {
|
|
3125
|
+
}
|
|
3126
|
+
if (!response.ok) {
|
|
3127
|
+
const message = payload && payload.error ? payload.error : "Failed to deploy locally."
|
|
3128
|
+
return {
|
|
3129
|
+
ok: false,
|
|
3130
|
+
error: message
|
|
3131
|
+
}
|
|
3132
|
+
}
|
|
3133
|
+
return payload
|
|
3134
|
+
} catch (error) {
|
|
3135
|
+
return {
|
|
3136
|
+
ok: false,
|
|
3137
|
+
error: error && error.message ? error.message : "Failed to deploy locally."
|
|
3138
|
+
}
|
|
3139
|
+
}
|
|
3140
|
+
}
|
|
3141
|
+
|
|
3142
|
+
const openDeployLocalModal = async (column) => {
|
|
3143
|
+
if (!column || !column.dataset) {
|
|
3144
|
+
return false
|
|
3145
|
+
}
|
|
3146
|
+
const sessionCwd = column.dataset.sessionCwd || ""
|
|
3147
|
+
const sessionUri = column.dataset.sessionUri || ""
|
|
3148
|
+
if (!sessionCwd) {
|
|
3149
|
+
return false
|
|
3150
|
+
}
|
|
3151
|
+
if (typeof Swal === "undefined" || !Swal || typeof Swal.fire !== "function") {
|
|
3152
|
+
const folderName = window.prompt("Deploy to ~/pinokio/api/<folder-name>. Enter folder name:")
|
|
3153
|
+
if (!folderName) {
|
|
3154
|
+
return false
|
|
3155
|
+
}
|
|
3156
|
+
const result = await deployLocalCopy({
|
|
3157
|
+
folderName,
|
|
3158
|
+
sessionUri,
|
|
3159
|
+
sessionCwd
|
|
3160
|
+
})
|
|
3161
|
+
if (result && result.code === "exists") {
|
|
3162
|
+
window.alert("Folder already exists.")
|
|
3163
|
+
return false
|
|
3164
|
+
}
|
|
3165
|
+
if (!result || result.ok !== true) {
|
|
3166
|
+
window.alert(result && result.error ? result.error : "Failed to deploy locally.")
|
|
3167
|
+
return false
|
|
3168
|
+
}
|
|
3169
|
+
return true
|
|
3170
|
+
}
|
|
3171
|
+
|
|
3172
|
+
const result = await Swal.fire({
|
|
3173
|
+
html: `
|
|
3174
|
+
<div class="pinokio-modal-surface">
|
|
3175
|
+
<div class="pinokio-modal-header">
|
|
3176
|
+
<div class="pinokio-modal-icon"><i class="fa-solid fa-rocket"></i></div>
|
|
3177
|
+
<div class="pinokio-modal-heading">
|
|
3178
|
+
<div class="pinokio-modal-title">Deploy locally</div>
|
|
3179
|
+
<div class="pinokio-modal-subtitle">Copy this active session workspace to a new local project folder.</div>
|
|
3180
|
+
</div>
|
|
3181
|
+
</div>
|
|
3182
|
+
<div class="pinokio-modal-body">
|
|
3183
|
+
<p class="pinokio-modal-note">This will copy the current workspace to <code>~/pinokio/api/<folder-name></code>.</p>
|
|
3184
|
+
<label class="pinokio-modal-label" for="terminals-deploy-folder-input">New folder name</label>
|
|
3185
|
+
<input id="terminals-deploy-folder-input" class="pinokio-modal-input" placeholder="my-local-workspace" autocomplete="off">
|
|
3186
|
+
</div>
|
|
3187
|
+
</div>
|
|
3188
|
+
`,
|
|
3189
|
+
showCancelButton: true,
|
|
3190
|
+
confirmButtonText: "Create",
|
|
3191
|
+
cancelButtonText: "Cancel",
|
|
3192
|
+
showCloseButton: true,
|
|
3193
|
+
buttonsStyling: false,
|
|
3194
|
+
backdrop: "rgba(9,11,15,0.65)",
|
|
3195
|
+
width: "min(520px, 90vw)",
|
|
3196
|
+
focusConfirm: false,
|
|
3197
|
+
showLoaderOnConfirm: true,
|
|
3198
|
+
allowOutsideClick: () => !Swal.isLoading(),
|
|
3199
|
+
customClass: {
|
|
3200
|
+
popup: "pinokio-modern-modal",
|
|
3201
|
+
htmlContainer: "pinokio-modern-html",
|
|
3202
|
+
closeButton: "pinokio-modern-close",
|
|
3203
|
+
confirmButton: "pinokio-modern-confirm",
|
|
3204
|
+
cancelButton: "pinokio-modern-cancel"
|
|
3205
|
+
},
|
|
3206
|
+
didOpen: () => {
|
|
3207
|
+
const input = Swal.getPopup().querySelector("#terminals-deploy-folder-input")
|
|
3208
|
+
if (input) {
|
|
3209
|
+
input.focus()
|
|
3210
|
+
}
|
|
3211
|
+
},
|
|
3212
|
+
preConfirm: async () => {
|
|
3213
|
+
const input = Swal.getPopup().querySelector("#terminals-deploy-folder-input")
|
|
3214
|
+
const folderName = input ? String(input.value || "").trim() : ""
|
|
3215
|
+
if (!folderName) {
|
|
3216
|
+
Swal.showValidationMessage("Enter a folder name.")
|
|
3217
|
+
return false
|
|
3218
|
+
}
|
|
3219
|
+
if (folderName === "." || folderName === ".." || /[\\/]/.test(folderName) || folderName.includes("\0")) {
|
|
3220
|
+
Swal.showValidationMessage("Folder name cannot include path separators.")
|
|
3221
|
+
return false
|
|
3222
|
+
}
|
|
3223
|
+
const payload = await deployLocalCopy({
|
|
3224
|
+
folderName,
|
|
3225
|
+
sessionUri,
|
|
3226
|
+
sessionCwd
|
|
3227
|
+
})
|
|
3228
|
+
if (payload && payload.code === "exists") {
|
|
3229
|
+
window.alert("Folder already exists.")
|
|
3230
|
+
return false
|
|
3231
|
+
}
|
|
3232
|
+
if (!payload || payload.ok !== true) {
|
|
3233
|
+
Swal.showValidationMessage(payload && payload.error ? payload.error : "Failed to deploy locally.")
|
|
3234
|
+
return false
|
|
3235
|
+
}
|
|
3236
|
+
return payload
|
|
3237
|
+
}
|
|
3238
|
+
})
|
|
3239
|
+
|
|
3240
|
+
if (!result || !result.isConfirmed || !result.value || result.value.ok !== true) {
|
|
3241
|
+
return false
|
|
3242
|
+
}
|
|
3243
|
+
await Swal.fire({
|
|
3244
|
+
title: "Created",
|
|
3245
|
+
text: `Created /api/${result.value.folder || ""}`,
|
|
3246
|
+
icon: "success",
|
|
3247
|
+
buttonsStyling: false,
|
|
3248
|
+
customClass: {
|
|
3249
|
+
popup: "pinokio-modern-modal",
|
|
3250
|
+
htmlContainer: "pinokio-modern-html",
|
|
3251
|
+
closeButton: "pinokio-modern-close",
|
|
3252
|
+
confirmButton: "pinokio-modern-confirm"
|
|
3253
|
+
}
|
|
3254
|
+
})
|
|
3255
|
+
return true
|
|
3256
|
+
}
|
|
3257
|
+
|
|
2539
3258
|
const getSessionFromColumn = (column) => {
|
|
2540
3259
|
if (!column || !column.dataset) {
|
|
2541
3260
|
return null
|
|
@@ -2602,9 +3321,30 @@ body.dark .swal2-popup.terminals-confirm-modal .swal2-styled.swal2-cancel {
|
|
|
2602
3321
|
return
|
|
2603
3322
|
}
|
|
2604
3323
|
topSessionOpenFolderButton.disabled = true
|
|
3324
|
+
syncTopSessionOverflowMenu()
|
|
2605
3325
|
await openInFileExplorer(cwd)
|
|
2606
3326
|
if (topSessionOpenFolderButton.isConnected) {
|
|
2607
3327
|
topSessionOpenFolderButton.disabled = false
|
|
3328
|
+
syncTopSessionOverflowMenu()
|
|
3329
|
+
}
|
|
3330
|
+
})
|
|
3331
|
+
}
|
|
3332
|
+
|
|
3333
|
+
if (topSessionDeployLocalButton) {
|
|
3334
|
+
topSessionDeployLocalButton.addEventListener("click", async () => {
|
|
3335
|
+
const activeColumn = getActiveTerminalColumn()
|
|
3336
|
+
if (!activeColumn) {
|
|
3337
|
+
return
|
|
3338
|
+
}
|
|
3339
|
+
const cwd = activeColumn.dataset.sessionCwd || ""
|
|
3340
|
+
if (!cwd) {
|
|
3341
|
+
return
|
|
3342
|
+
}
|
|
3343
|
+
topSessionDeployLocalButton.disabled = true
|
|
3344
|
+
syncTopSessionOverflowMenu()
|
|
3345
|
+
await openDeployLocalModal(activeColumn)
|
|
3346
|
+
if (topSessionDeployLocalButton.isConnected) {
|
|
3347
|
+
renderTopSessionBar()
|
|
2608
3348
|
}
|
|
2609
3349
|
})
|
|
2610
3350
|
}
|
|
@@ -2617,9 +3357,11 @@ body.dark .swal2-popup.terminals-confirm-modal .swal2-styled.swal2-cancel {
|
|
|
2617
3357
|
return
|
|
2618
3358
|
}
|
|
2619
3359
|
topSessionForkButton.disabled = true
|
|
3360
|
+
syncTopSessionOverflowMenu()
|
|
2620
3361
|
const started = forkSessionFromColumn(activeColumn)
|
|
2621
3362
|
if (!started && topSessionForkButton.isConnected) {
|
|
2622
3363
|
topSessionForkButton.disabled = false
|
|
3364
|
+
syncTopSessionOverflowMenu()
|
|
2623
3365
|
}
|
|
2624
3366
|
})
|
|
2625
3367
|
}
|
|
@@ -2634,6 +3376,57 @@ body.dark .swal2-popup.terminals-confirm-modal .swal2-styled.swal2-cancel {
|
|
|
2634
3376
|
})
|
|
2635
3377
|
}
|
|
2636
3378
|
|
|
3379
|
+
if (topSessionMoreButton && topSessionOverflowMenu) {
|
|
3380
|
+
topSessionMoreButton.addEventListener("click", (event) => {
|
|
3381
|
+
event.preventDefault()
|
|
3382
|
+
event.stopPropagation()
|
|
3383
|
+
if (topSessionMoreButton.disabled) {
|
|
3384
|
+
return
|
|
3385
|
+
}
|
|
3386
|
+
const willOpen = topSessionOverflowMenu.classList.contains("hidden")
|
|
3387
|
+
topSessionOverflowMenu.classList.toggle("hidden", !willOpen)
|
|
3388
|
+
topSessionMoreButton.setAttribute("aria-expanded", willOpen ? "true" : "false")
|
|
3389
|
+
})
|
|
3390
|
+
|
|
3391
|
+
topSessionOverflowMenu.addEventListener("click", (event) => {
|
|
3392
|
+
const button = event.target && event.target.closest ? event.target.closest("button[data-action]") : null
|
|
3393
|
+
if (!button || button.disabled) {
|
|
3394
|
+
return
|
|
3395
|
+
}
|
|
3396
|
+
const action = String(button.dataset.action || "")
|
|
3397
|
+
closeTopSessionOverflowMenu()
|
|
3398
|
+
if (action === "open-folder" && topSessionOpenFolderButton && !topSessionOpenFolderButton.disabled) {
|
|
3399
|
+
topSessionOpenFolderButton.click()
|
|
3400
|
+
return
|
|
3401
|
+
}
|
|
3402
|
+
if (action === "deploy-local" && topSessionDeployLocalButton && !topSessionDeployLocalButton.disabled) {
|
|
3403
|
+
topSessionDeployLocalButton.click()
|
|
3404
|
+
return
|
|
3405
|
+
}
|
|
3406
|
+
if (action === "close-session" && topSessionCloseButton && !topSessionCloseButton.disabled) {
|
|
3407
|
+
topSessionCloseButton.click()
|
|
3408
|
+
}
|
|
3409
|
+
})
|
|
3410
|
+
|
|
3411
|
+
document.addEventListener("click", (event) => {
|
|
3412
|
+
if (topSessionOverflowMenu.classList.contains("hidden")) {
|
|
3413
|
+
return
|
|
3414
|
+
}
|
|
3415
|
+
const withinMenu = event.target && event.target.closest && event.target.closest("#terminals-top-overflow-menu")
|
|
3416
|
+
const withinToggle = event.target && event.target.closest && event.target.closest("#terminals-top-more-actions")
|
|
3417
|
+
if (withinMenu || withinToggle) {
|
|
3418
|
+
return
|
|
3419
|
+
}
|
|
3420
|
+
closeTopSessionOverflowMenu()
|
|
3421
|
+
})
|
|
3422
|
+
|
|
3423
|
+
document.addEventListener("keydown", (event) => {
|
|
3424
|
+
if (event.key === "Escape") {
|
|
3425
|
+
closeTopSessionOverflowMenu()
|
|
3426
|
+
}
|
|
3427
|
+
})
|
|
3428
|
+
}
|
|
3429
|
+
|
|
2637
3430
|
const isFrameSessionAlreadyStopped = (frame) => {
|
|
2638
3431
|
if (!frame) {
|
|
2639
3432
|
return false
|
|
@@ -2659,69 +3452,280 @@ body.dark .swal2-popup.terminals-confirm-modal .swal2-styled.swal2-cancel {
|
|
|
2659
3452
|
}
|
|
2660
3453
|
return false
|
|
2661
3454
|
}
|
|
3455
|
+
const getCurrentSessionRefreshLimit = () => {
|
|
3456
|
+
return Math.min(Math.max(sessionItems.length || 0, SESSION_PAGE_SIZE), 500)
|
|
3457
|
+
}
|
|
3458
|
+
const collectOpenColumnSessions = () => {
|
|
3459
|
+
if (!columns) {
|
|
3460
|
+
return []
|
|
3461
|
+
}
|
|
3462
|
+
const terminalColumns = columns.querySelectorAll(".terminals-column[data-state='terminal']")
|
|
3463
|
+
const collected = []
|
|
3464
|
+
for (let i = 0; i < terminalColumns.length; i++) {
|
|
3465
|
+
const column = terminalColumns[i]
|
|
3466
|
+
const session = getSessionFromColumn(column)
|
|
3467
|
+
if (!canSessionResume(session)) {
|
|
3468
|
+
continue
|
|
3469
|
+
}
|
|
3470
|
+
const normalized = normalizeSessionItem({
|
|
3471
|
+
...session,
|
|
3472
|
+
online: true,
|
|
3473
|
+
timestamp: session && session.timestamp ? session.timestamp : Date.now()
|
|
3474
|
+
}, session && session.index ? session.index : i)
|
|
3475
|
+
collected.push(normalized)
|
|
3476
|
+
}
|
|
3477
|
+
return collected
|
|
3478
|
+
}
|
|
3479
|
+
const mergeOpenColumnSessions = (incomingItems) => {
|
|
3480
|
+
const merged = Array.isArray(incomingItems) ? incomingItems.slice() : []
|
|
3481
|
+
const existingKeys = new Set()
|
|
3482
|
+
const existingContextKeys = new Set()
|
|
3483
|
+
const registerSession = (session) => {
|
|
3484
|
+
const key = session && session.uri ? `uri:${session.uri}` : `idx:${normalizeIndex(session && session.index)}`
|
|
3485
|
+
existingKeys.add(key)
|
|
3486
|
+
const providerKey = normalizeProviderKey(getProviderKey(session))
|
|
3487
|
+
const cwdKey = normalizeCwdKey(session && session.cwd ? session.cwd : "")
|
|
3488
|
+
if (providerKey && cwdKey) {
|
|
3489
|
+
existingContextKeys.add(`${providerKey}|${cwdKey}`)
|
|
3490
|
+
}
|
|
3491
|
+
}
|
|
3492
|
+
for (let i = 0; i < merged.length; i++) {
|
|
3493
|
+
registerSession(merged[i])
|
|
3494
|
+
}
|
|
3495
|
+
const openColumnSessions = collectOpenColumnSessions()
|
|
3496
|
+
for (let i = 0; i < openColumnSessions.length; i++) {
|
|
3497
|
+
const session = openColumnSessions[i]
|
|
3498
|
+
const key = session && session.uri ? `uri:${session.uri}` : `idx:${normalizeIndex(session && session.index)}`
|
|
3499
|
+
const providerKey = normalizeProviderKey(getProviderKey(session))
|
|
3500
|
+
const cwdKey = normalizeCwdKey(session && session.cwd ? session.cwd : "")
|
|
3501
|
+
const contextKey = providerKey && cwdKey ? `${providerKey}|${cwdKey}` : ""
|
|
3502
|
+
if (existingKeys.has(key)) {
|
|
3503
|
+
continue
|
|
3504
|
+
}
|
|
3505
|
+
if (contextKey && existingContextKeys.has(contextKey)) {
|
|
3506
|
+
for (let j = 0; j < merged.length; j++) {
|
|
3507
|
+
const candidate = merged[j]
|
|
3508
|
+
if (!candidate) {
|
|
3509
|
+
continue
|
|
3510
|
+
}
|
|
3511
|
+
const candidateProvider = normalizeProviderKey(getProviderKey(candidate))
|
|
3512
|
+
const candidateCwd = normalizeCwdKey(candidate.cwd)
|
|
3513
|
+
if (candidateProvider !== providerKey || candidateCwd !== cwdKey) {
|
|
3514
|
+
continue
|
|
3515
|
+
}
|
|
3516
|
+
merged[j] = {
|
|
3517
|
+
...candidate,
|
|
3518
|
+
online: true
|
|
3519
|
+
}
|
|
3520
|
+
break
|
|
3521
|
+
}
|
|
3522
|
+
continue
|
|
3523
|
+
}
|
|
3524
|
+
merged.unshift(session)
|
|
3525
|
+
registerSession(session)
|
|
3526
|
+
}
|
|
3527
|
+
return merged
|
|
3528
|
+
}
|
|
2662
3529
|
|
|
2663
3530
|
let refreshSessionPromise = null
|
|
3531
|
+
const appendSessionPage = (incomingItems) => {
|
|
3532
|
+
if (!Array.isArray(incomingItems) || incomingItems.length === 0) {
|
|
3533
|
+
return false
|
|
3534
|
+
}
|
|
3535
|
+
const existingKeys = new Set()
|
|
3536
|
+
for (let i = 0; i < sessionItems.length; i++) {
|
|
3537
|
+
const item = sessionItems[i]
|
|
3538
|
+
if (!item) {
|
|
3539
|
+
continue
|
|
3540
|
+
}
|
|
3541
|
+
const key = item.uri ? `uri:${item.uri}` : `idx:${normalizeIndex(item.index)}`
|
|
3542
|
+
existingKeys.add(key)
|
|
3543
|
+
}
|
|
3544
|
+
let changed = false
|
|
3545
|
+
for (let i = 0; i < incomingItems.length; i++) {
|
|
3546
|
+
const candidate = normalizeSessionItem(incomingItems[i], sessionItems.length + i)
|
|
3547
|
+
const key = candidate.uri ? `uri:${candidate.uri}` : `idx:${normalizeIndex(candidate.index)}`
|
|
3548
|
+
if (existingKeys.has(key)) {
|
|
3549
|
+
continue
|
|
3550
|
+
}
|
|
3551
|
+
existingKeys.add(key)
|
|
3552
|
+
sessionItems.push(candidate)
|
|
3553
|
+
changed = true
|
|
3554
|
+
}
|
|
3555
|
+
return changed
|
|
3556
|
+
}
|
|
2664
3557
|
const refreshSessionItems = async (options = {}) => {
|
|
2665
|
-
const
|
|
3558
|
+
const syncRefresh = Boolean(options && (options.sync || options.force))
|
|
2666
3559
|
const includeSkills = Boolean(options && options.skills)
|
|
3560
|
+
const append = Boolean(options && options.append)
|
|
3561
|
+
const hasQueryOverride = Boolean(options && Object.prototype.hasOwnProperty.call(options, "query"))
|
|
3562
|
+
const queryText = hasQueryOverride ? normalizeIndex(options.query) : currentSessionQuery
|
|
3563
|
+
const normalizedQuery = queryText ? String(queryText).trim() : ""
|
|
3564
|
+
const pageSize = Number.isFinite(options && options.limit) && options.limit > 0
|
|
3565
|
+
? Math.min(Math.max(Math.floor(options.limit), 1), 500)
|
|
3566
|
+
: SESSION_PAGE_SIZE
|
|
3567
|
+
if (refreshSessionPromise && append) {
|
|
3568
|
+
return refreshSessionPromise
|
|
3569
|
+
}
|
|
3570
|
+
if (append && (!sessionHasMore || sessionAppendInProgress)) {
|
|
3571
|
+
return false
|
|
3572
|
+
}
|
|
3573
|
+
if (!append && sessionFetchAbortController) {
|
|
3574
|
+
try {
|
|
3575
|
+
sessionFetchAbortController.abort()
|
|
3576
|
+
} catch (error) {}
|
|
3577
|
+
sessionFetchAbortController = null
|
|
3578
|
+
}
|
|
3579
|
+
if (append) {
|
|
3580
|
+
sessionAppendInProgress = true
|
|
3581
|
+
} else {
|
|
3582
|
+
currentSessionQuery = normalizedQuery
|
|
3583
|
+
sessionNextCursor = 0
|
|
3584
|
+
sessionHasMore = true
|
|
3585
|
+
}
|
|
3586
|
+
const requestId = ++sessionFetchRequestId
|
|
3587
|
+
const controller = new AbortController()
|
|
3588
|
+
sessionFetchAbortController = controller
|
|
2667
3589
|
const params = new URLSearchParams()
|
|
2668
3590
|
params.set("mode", "terminals")
|
|
2669
3591
|
params.set("fetch", "1")
|
|
2670
|
-
|
|
2671
|
-
|
|
3592
|
+
params.set("limit", String(pageSize))
|
|
3593
|
+
if (append && sessionNextCursor > 0) {
|
|
3594
|
+
params.set("cursor", String(sessionNextCursor))
|
|
3595
|
+
}
|
|
3596
|
+
if (currentSessionQuery) {
|
|
3597
|
+
params.set("q", currentSessionQuery)
|
|
3598
|
+
}
|
|
3599
|
+
if (syncRefresh) {
|
|
3600
|
+
params.set("sync", "1")
|
|
2672
3601
|
}
|
|
2673
3602
|
if (includeSkills) {
|
|
2674
3603
|
params.set("skills", "1")
|
|
2675
3604
|
}
|
|
2676
3605
|
const endpoint = `/home?${params.toString()}`
|
|
2677
|
-
|
|
2678
|
-
return refreshSessionPromise
|
|
2679
|
-
}
|
|
3606
|
+
adjustSessionRefreshActivity(1)
|
|
2680
3607
|
refreshSessionPromise = (async () => {
|
|
3608
|
+
let requestSucceeded = false
|
|
3609
|
+
let shouldMarkError = false
|
|
3610
|
+
let aborted = false
|
|
2681
3611
|
try {
|
|
2682
3612
|
const response = await fetch(endpoint, {
|
|
2683
3613
|
method: "GET",
|
|
3614
|
+
signal: controller.signal,
|
|
2684
3615
|
headers: {
|
|
2685
3616
|
"Accept": "application/json"
|
|
2686
3617
|
}
|
|
2687
3618
|
})
|
|
2688
3619
|
if (!response.ok) {
|
|
3620
|
+
shouldMarkError = !append
|
|
2689
3621
|
return false
|
|
2690
3622
|
}
|
|
2691
3623
|
const payload = await response.json()
|
|
3624
|
+
if (requestId !== sessionFetchRequestId || controller.signal.aborted) {
|
|
3625
|
+
aborted = true
|
|
3626
|
+
return false
|
|
3627
|
+
}
|
|
2692
3628
|
const items = Array.isArray(payload && payload.items) ? payload.items : []
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
3629
|
+
if (append) {
|
|
3630
|
+
appendSessionPage(items)
|
|
3631
|
+
} else {
|
|
3632
|
+
const normalizedItems = items.map((item, index) => normalizeSessionItem(item, index))
|
|
3633
|
+
sessionItems = mergeOpenColumnSessions(normalizedItems)
|
|
3634
|
+
}
|
|
3635
|
+
const pagination = payload && payload.pagination && typeof payload.pagination === "object" ? payload.pagination : null
|
|
3636
|
+
if (pagination) {
|
|
3637
|
+
const nextCursorRaw = Number.parseInt(pagination.nextCursor, 10)
|
|
3638
|
+
const hasMore = Boolean(pagination.hasMore)
|
|
3639
|
+
sessionHasMore = hasMore
|
|
3640
|
+
sessionNextCursor = hasMore && Number.isFinite(nextCursorRaw) && nextCursorRaw >= 0
|
|
3641
|
+
? nextCursorRaw
|
|
3642
|
+
: sessionItems.length
|
|
3643
|
+
} else {
|
|
3644
|
+
sessionHasMore = false
|
|
3645
|
+
sessionNextCursor = sessionItems.length
|
|
2696
3646
|
}
|
|
2697
3647
|
if (payload && Array.isArray(payload.skills)) {
|
|
2698
3648
|
availableSkills = payload.skills.slice()
|
|
2699
3649
|
}
|
|
3650
|
+
requestSucceeded = true
|
|
2700
3651
|
return true
|
|
2701
3652
|
} catch (error) {
|
|
3653
|
+
if (error && error.name === "AbortError") {
|
|
3654
|
+
aborted = true
|
|
3655
|
+
return false
|
|
3656
|
+
}
|
|
3657
|
+
shouldMarkError = !append
|
|
2702
3658
|
return false
|
|
2703
3659
|
} finally {
|
|
2704
|
-
|
|
3660
|
+
if (requestId === sessionFetchRequestId) {
|
|
3661
|
+
if (!aborted) {
|
|
3662
|
+
hasAttemptedSessionLoad = true
|
|
3663
|
+
if (shouldMarkError) {
|
|
3664
|
+
lastSessionLoadFailed = !requestSucceeded
|
|
3665
|
+
} else if (requestSucceeded) {
|
|
3666
|
+
lastSessionLoadFailed = false
|
|
3667
|
+
}
|
|
3668
|
+
}
|
|
3669
|
+
if (sessionFetchAbortController === controller) {
|
|
3670
|
+
sessionFetchAbortController = null
|
|
3671
|
+
}
|
|
3672
|
+
refreshSessionPromise = null
|
|
3673
|
+
}
|
|
3674
|
+
if (append) {
|
|
3675
|
+
sessionAppendInProgress = false
|
|
3676
|
+
}
|
|
3677
|
+
adjustSessionRefreshActivity(-1)
|
|
2705
3678
|
}
|
|
2706
3679
|
})()
|
|
2707
3680
|
return refreshSessionPromise
|
|
2708
3681
|
}
|
|
2709
|
-
const
|
|
3682
|
+
const loadMoreSessionItems = async () => {
|
|
3683
|
+
if (!sessionHasMore || sessionAppendInProgress) {
|
|
3684
|
+
return false
|
|
3685
|
+
}
|
|
3686
|
+
const loaded = await refreshSessionItems({ append: true })
|
|
3687
|
+
if (loaded) {
|
|
3688
|
+
refreshChooserRows()
|
|
3689
|
+
}
|
|
3690
|
+
return loaded
|
|
3691
|
+
}
|
|
3692
|
+
const maybeLoadMoreSessionsForColumn = (column) => {
|
|
3693
|
+
if (!column || !column.isConnected || column.dataset.state !== "chooser") {
|
|
3694
|
+
return
|
|
3695
|
+
}
|
|
3696
|
+
const liveQuery = normalizeIndex(getSessionSearchQuery()).trim()
|
|
3697
|
+
if (liveQuery !== currentSessionQuery) {
|
|
3698
|
+
return
|
|
3699
|
+
}
|
|
3700
|
+
if (!sessionHasMore || sessionAppendInProgress) {
|
|
3701
|
+
return
|
|
3702
|
+
}
|
|
3703
|
+
const listWrap = column.querySelector(".terminals-list-wrap")
|
|
3704
|
+
if (!listWrap) {
|
|
3705
|
+
return
|
|
3706
|
+
}
|
|
3707
|
+
const remaining = listWrap.scrollHeight - (listWrap.scrollTop + listWrap.clientHeight)
|
|
3708
|
+
if (remaining > SESSION_LOAD_MORE_THRESHOLD_PX) {
|
|
3709
|
+
return
|
|
3710
|
+
}
|
|
3711
|
+
void loadMoreSessionItems()
|
|
3712
|
+
}
|
|
3713
|
+
const findLatestSessionByProviderAndCwdIn = (sessions, providerKey, cwd, excludeUri = "") => {
|
|
2710
3714
|
const normalizedProvider = normalizeProviderKey(providerKey)
|
|
2711
|
-
const normalizedCwd =
|
|
3715
|
+
const normalizedCwd = normalizeCwdKey(cwd)
|
|
2712
3716
|
if (!normalizedProvider || !normalizedCwd) {
|
|
2713
3717
|
return null
|
|
2714
3718
|
}
|
|
2715
3719
|
let best = null
|
|
2716
|
-
for (let i = 0; i <
|
|
2717
|
-
const session =
|
|
3720
|
+
for (let i = 0; i < sessions.length; i++) {
|
|
3721
|
+
const session = sessions[i]
|
|
2718
3722
|
if (!session) {
|
|
2719
3723
|
continue
|
|
2720
3724
|
}
|
|
2721
3725
|
if (normalizeProviderKey(getProviderKey(session)) !== normalizedProvider) {
|
|
2722
3726
|
continue
|
|
2723
3727
|
}
|
|
2724
|
-
if (
|
|
3728
|
+
if (normalizeCwdKey(session.cwd) !== normalizedCwd) {
|
|
2725
3729
|
continue
|
|
2726
3730
|
}
|
|
2727
3731
|
if (excludeUri && String(session.uri || "") === excludeUri) {
|
|
@@ -2733,10 +3737,16 @@ body.dark .swal2-popup.terminals-confirm-modal .swal2-styled.swal2-cancel {
|
|
|
2733
3737
|
}
|
|
2734
3738
|
return best
|
|
2735
3739
|
}
|
|
3740
|
+
const findLatestSessionByProviderAndCwd = (providerKey, cwd, excludeUri = "") => {
|
|
3741
|
+
return findLatestSessionByProviderAndCwdIn(sessionItems, providerKey, cwd, excludeUri)
|
|
3742
|
+
}
|
|
2736
3743
|
const waitForDiscoveredSession = async (providerKey, cwd, excludeUri = "") => {
|
|
2737
3744
|
const maxAttempts = 10
|
|
2738
3745
|
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
2739
|
-
await refreshSessionItems(
|
|
3746
|
+
await refreshSessionItems({
|
|
3747
|
+
force: attempt === 0,
|
|
3748
|
+
limit: getCurrentSessionRefreshLimit()
|
|
3749
|
+
})
|
|
2740
3750
|
const found = findLatestSessionByProviderAndCwd(providerKey, cwd, excludeUri)
|
|
2741
3751
|
if (found) {
|
|
2742
3752
|
return found
|
|
@@ -2745,6 +3755,70 @@ body.dark .swal2-popup.terminals-confirm-modal .swal2-styled.swal2-cancel {
|
|
|
2745
3755
|
}
|
|
2746
3756
|
return null
|
|
2747
3757
|
}
|
|
3758
|
+
const syncDiscoveredSessionForOpenColumn = (column, discoveredSession) => {
|
|
3759
|
+
if (!column || !column.isConnected || !discoveredSession) {
|
|
3760
|
+
return
|
|
3761
|
+
}
|
|
3762
|
+
const discovered = normalizeSessionItem(discoveredSession, 0)
|
|
3763
|
+
const previousIndex = normalizeIndex(column.dataset.sessionIndex)
|
|
3764
|
+
const previousUri = normalizeIndex(column.dataset.sessionUri)
|
|
3765
|
+
const discoveredIndex = normalizeIndex(discovered.index)
|
|
3766
|
+
const discoveredUri = normalizeIndex(discovered.uri)
|
|
3767
|
+
const runningUrl = normalizeIndex(column.dataset.sessionUrl)
|
|
3768
|
+
|
|
3769
|
+
let mergedInList = false
|
|
3770
|
+
for (let i = 0; i < sessionItems.length; i++) {
|
|
3771
|
+
const item = sessionItems[i]
|
|
3772
|
+
if (!item) {
|
|
3773
|
+
continue
|
|
3774
|
+
}
|
|
3775
|
+
const itemIndex = normalizeIndex(item.index)
|
|
3776
|
+
const itemUri = normalizeIndex(item.uri)
|
|
3777
|
+
const matchesByIndex = Boolean(
|
|
3778
|
+
(previousIndex && itemIndex && itemIndex === previousIndex) ||
|
|
3779
|
+
(discoveredIndex && itemIndex && itemIndex === discoveredIndex)
|
|
3780
|
+
)
|
|
3781
|
+
const matchesByUri = Boolean(
|
|
3782
|
+
(previousUri && itemUri && itemUri === previousUri) ||
|
|
3783
|
+
(discoveredUri && itemUri && itemUri === discoveredUri)
|
|
3784
|
+
)
|
|
3785
|
+
if (!matchesByIndex && !matchesByUri) {
|
|
3786
|
+
continue
|
|
3787
|
+
}
|
|
3788
|
+
sessionItems[i] = {
|
|
3789
|
+
...item,
|
|
3790
|
+
...discovered,
|
|
3791
|
+
url: discovered.url || item.url
|
|
3792
|
+
}
|
|
3793
|
+
mergedInList = true
|
|
3794
|
+
break
|
|
3795
|
+
}
|
|
3796
|
+
if (!mergedInList) {
|
|
3797
|
+
sessionItems.unshift(discovered)
|
|
3798
|
+
}
|
|
3799
|
+
|
|
3800
|
+
if (previousIndex && discoveredIndex && previousIndex !== discoveredIndex) {
|
|
3801
|
+
setOpenState(previousIndex, "remove", column)
|
|
3802
|
+
}
|
|
3803
|
+
column.dataset.sessionIndex = discoveredIndex || previousIndex
|
|
3804
|
+
column.dataset.sessionName = discovered.name || column.dataset.sessionName || "Session"
|
|
3805
|
+
column.dataset.sessionCwd = discovered.cwd || column.dataset.sessionCwd || ""
|
|
3806
|
+
column.dataset.sessionUri = discoveredUri || previousUri
|
|
3807
|
+
column.dataset.sessionUrl = runningUrl || discovered.url || ""
|
|
3808
|
+
column.dataset.sessionForkUrl = discovered.forkUrl || ""
|
|
3809
|
+
column.dataset.sessionForkCapable = parseBooleanFlag(discovered.forkCapable, true) ? "1" : "0"
|
|
3810
|
+
column.dataset.sessionForkDisabledReason = discovered.forkDisabledReason || ""
|
|
3811
|
+
|
|
3812
|
+
const providerKey = getProviderKey(discovered) || normalizeIndex(column.dataset.sessionProviderKey).toLowerCase()
|
|
3813
|
+
column.dataset.sessionProviderKey = providerKey
|
|
3814
|
+
column.dataset.sessionProviderLabel = discovered.providerLabel || column.dataset.sessionProviderLabel || providerKey || "Session"
|
|
3815
|
+
column.dataset.sessionProviderIcon = getProviderIconSrc(discovered) || getProviderIconByKey(providerKey) || column.dataset.sessionProviderIcon || ""
|
|
3816
|
+
|
|
3817
|
+
if (column.dataset.sessionIndex) {
|
|
3818
|
+
setOpenState(column.dataset.sessionIndex, "add", column)
|
|
3819
|
+
}
|
|
3820
|
+
syncLocalOnlineState(discovered, true)
|
|
3821
|
+
}
|
|
2748
3822
|
|
|
2749
3823
|
const startProviderSession = async (column, providerKey, startButton, providerSelect, selectedSkillIds = [], uploadToken = "") => {
|
|
2750
3824
|
const normalizedProvider = normalizeIndex(providerKey).toLowerCase()
|
|
@@ -2794,9 +3868,6 @@ body.dark .swal2-popup.terminals-confirm-modal .swal2-styled.swal2-cancel {
|
|
|
2794
3868
|
online: true
|
|
2795
3869
|
}
|
|
2796
3870
|
sessionItems.unshift(normalizeSessionItem(session, 0))
|
|
2797
|
-
for (let i = 0; i < sessionItems.length; i++) {
|
|
2798
|
-
sessionItems[i].index = i
|
|
2799
|
-
}
|
|
2800
3871
|
openItemInColumn(column, session)
|
|
2801
3872
|
const optimisticUri = session.uri
|
|
2802
3873
|
const sessionCwd = payload.cwd || ""
|
|
@@ -2818,10 +3889,10 @@ body.dark .swal2-popup.terminals-confirm-modal .swal2-styled.swal2-cancel {
|
|
|
2818
3889
|
}
|
|
2819
3890
|
return
|
|
2820
3891
|
}
|
|
2821
|
-
|
|
3892
|
+
syncDiscoveredSessionForOpenColumn(column, discovered)
|
|
2822
3893
|
refreshChooserRows()
|
|
2823
3894
|
})()
|
|
2824
|
-
void refreshSessionItems({
|
|
3895
|
+
void refreshSessionItems({ sync: true, limit: getCurrentSessionRefreshLimit() }).then((refreshed) => {
|
|
2825
3896
|
if (refreshed) {
|
|
2826
3897
|
refreshChooserRows()
|
|
2827
3898
|
}
|
|
@@ -2885,7 +3956,7 @@ body.dark .swal2-popup.terminals-confirm-modal .swal2-styled.swal2-cancel {
|
|
|
2885
3956
|
if (isFrameSessionAlreadyStopped(frame)) {
|
|
2886
3957
|
closeColumn(column)
|
|
2887
3958
|
syncLocalOnlineState(columnSession, false)
|
|
2888
|
-
void refreshSessionItems({
|
|
3959
|
+
void refreshSessionItems({ sync: true, limit: getCurrentSessionRefreshLimit() }).then((refreshed) => {
|
|
2889
3960
|
if (refreshed) {
|
|
2890
3961
|
refreshChooserRows()
|
|
2891
3962
|
}
|
|
@@ -2908,7 +3979,7 @@ body.dark .swal2-popup.terminals-confirm-modal .swal2-styled.swal2-cancel {
|
|
|
2908
3979
|
}
|
|
2909
3980
|
closeColumn(column)
|
|
2910
3981
|
syncLocalOnlineState(columnSession, false)
|
|
2911
|
-
void refreshSessionItems({
|
|
3982
|
+
void refreshSessionItems({ sync: true, limit: getCurrentSessionRefreshLimit() }).then((refreshed) => {
|
|
2912
3983
|
if (refreshed) {
|
|
2913
3984
|
refreshChooserRows()
|
|
2914
3985
|
}
|
|
@@ -2977,6 +4048,55 @@ body.dark .swal2-popup.terminals-confirm-modal .swal2-styled.swal2-cancel {
|
|
|
2977
4048
|
return row
|
|
2978
4049
|
}
|
|
2979
4050
|
|
|
4051
|
+
const setPlaceholderState = (placeholder, state, details = {}) => {
|
|
4052
|
+
if (!placeholder) {
|
|
4053
|
+
return
|
|
4054
|
+
}
|
|
4055
|
+
const normalizedState = normalizeIndex(state) || "empty"
|
|
4056
|
+
const title = details && typeof details.title === "string" ? details.title : "Session history"
|
|
4057
|
+
const message = details && typeof details.message === "string" ? details.message : ""
|
|
4058
|
+
const hint = details && typeof details.hint === "string" ? details.hint : ""
|
|
4059
|
+
let iconClass = "fa-solid fa-clock-rotate-left"
|
|
4060
|
+
if (normalizedState === "loading") {
|
|
4061
|
+
iconClass = "fa-solid fa-circle-notch fa-spin"
|
|
4062
|
+
} else if (normalizedState === "error") {
|
|
4063
|
+
iconClass = "fa-solid fa-triangle-exclamation"
|
|
4064
|
+
} else if (normalizedState === "search-empty") {
|
|
4065
|
+
iconClass = "fa-solid fa-magnifying-glass"
|
|
4066
|
+
}
|
|
4067
|
+
const hintHtml = hint ? `<div class="terminals-empty-hint">${escapeHtml(hint)}</div>` : ""
|
|
4068
|
+
placeholder.dataset.state = normalizedState
|
|
4069
|
+
placeholder.innerHTML = `
|
|
4070
|
+
<div class="terminals-empty-card">
|
|
4071
|
+
<div class="terminals-empty-icon" aria-hidden="true"><i class="${iconClass}"></i></div>
|
|
4072
|
+
<h1 class="terminals-empty-title">${escapeHtml(title)}</h1>
|
|
4073
|
+
<div class="terminals-empty-message">${escapeHtml(message)}</div>
|
|
4074
|
+
${hintHtml}
|
|
4075
|
+
</div>
|
|
4076
|
+
`
|
|
4077
|
+
}
|
|
4078
|
+
const makeListIntroNode = () => {
|
|
4079
|
+
const intro = document.createElement("div")
|
|
4080
|
+
intro.className = "terminals-list-intro"
|
|
4081
|
+
if (sessionRefreshActivityCount > 0) {
|
|
4082
|
+
intro.classList.add("is-loading")
|
|
4083
|
+
}
|
|
4084
|
+
intro.innerHTML = `
|
|
4085
|
+
<div class="terminals-list-intro-progress" aria-hidden="true"></div>
|
|
4086
|
+
<div class="terminals-list-intro-head">
|
|
4087
|
+
<div class="terminals-list-intro-title">
|
|
4088
|
+
<h2>Agent Workspaces</h2>
|
|
4089
|
+
<span class="terminals-list-intro-refresh" role="status" aria-live="polite">
|
|
4090
|
+
<i class="fa-solid fa-circle-notch fa-spin" aria-hidden="true"></i>
|
|
4091
|
+
<span>Refreshing</span>
|
|
4092
|
+
</span>
|
|
4093
|
+
</div>
|
|
4094
|
+
</div>
|
|
4095
|
+
<p>Resume or fork existing sessions, or start a new workspace powered by Codex, Claude, or Gemini.</p>
|
|
4096
|
+
`
|
|
4097
|
+
return intro
|
|
4098
|
+
}
|
|
4099
|
+
|
|
2980
4100
|
const buildRows = (column, query = "") => {
|
|
2981
4101
|
const list = column.querySelector(".terminals-list")
|
|
2982
4102
|
const placeholder = column.querySelector(".terminals-empty")
|
|
@@ -2997,6 +4117,10 @@ body.dark .swal2-popup.terminals-confirm-modal .swal2-styled.swal2-cancel {
|
|
|
2997
4117
|
}
|
|
2998
4118
|
visibleSessions.sort(compareSessionsForChooser)
|
|
2999
4119
|
let visible = 0
|
|
4120
|
+
if (visibleSessions.length > 0) {
|
|
4121
|
+
const introNode = makeListIntroNode()
|
|
4122
|
+
list.appendChild(introNode)
|
|
4123
|
+
}
|
|
3000
4124
|
for (let i = 0; i < visibleSessions.length; i++) {
|
|
3001
4125
|
const session = visibleSessions[i]
|
|
3002
4126
|
const row = makeRowNode(session, normalized)
|
|
@@ -3006,10 +4130,15 @@ body.dark .swal2-popup.terminals-confirm-modal .swal2-styled.swal2-cancel {
|
|
|
3006
4130
|
})
|
|
3007
4131
|
const resumeButton = row.querySelector("[data-session-action='resume']")
|
|
3008
4132
|
if (resumeButton) {
|
|
3009
|
-
|
|
4133
|
+
const resumeAvailable = canSessionResume(session)
|
|
4134
|
+
resumeButton.disabled = !resumeAvailable
|
|
4135
|
+
resumeButton.title = resumeAvailable ? "Resume this session" : getResumeUnavailableReason(session)
|
|
3010
4136
|
resumeButton.addEventListener("click", (event) => {
|
|
3011
4137
|
event.preventDefault()
|
|
3012
4138
|
event.stopPropagation()
|
|
4139
|
+
if (!resumeAvailable) {
|
|
4140
|
+
return
|
|
4141
|
+
}
|
|
3013
4142
|
openItemInColumn(column, session)
|
|
3014
4143
|
})
|
|
3015
4144
|
}
|
|
@@ -3031,27 +4160,76 @@ body.dark .swal2-popup.terminals-confirm-modal .swal2-styled.swal2-cancel {
|
|
|
3031
4160
|
openItemInColumn(column, forkedSession)
|
|
3032
4161
|
})
|
|
3033
4162
|
}
|
|
3034
|
-
|
|
4163
|
+
const resumeAvailable = canSessionResume(session)
|
|
4164
|
+
const forkAvailable = canSessionFork(session)
|
|
4165
|
+
if (resumeAvailable && forkAvailable) {
|
|
4166
|
+
row.setAttribute("aria-label", `${session.name}. Use Resume or Fork.`)
|
|
4167
|
+
} else if (resumeAvailable) {
|
|
4168
|
+
row.setAttribute("aria-label", `${session.name}. Use Resume.`)
|
|
4169
|
+
} else {
|
|
4170
|
+
row.setAttribute("aria-label", `${session.name}. Resume unavailable.`)
|
|
4171
|
+
}
|
|
3035
4172
|
list.appendChild(row)
|
|
3036
4173
|
visible += 1
|
|
3037
4174
|
}
|
|
4175
|
+
if (visible > 0 && (sessionAppendInProgress || sessionHasMore)) {
|
|
4176
|
+
const loadMoreRow = document.createElement("div")
|
|
4177
|
+
loadMoreRow.className = "terminals-load-more-indicator"
|
|
4178
|
+
loadMoreRow.textContent = sessionAppendInProgress ? "Loading more sessions..." : "Scroll to load more sessions."
|
|
4179
|
+
list.appendChild(loadMoreRow)
|
|
4180
|
+
}
|
|
3038
4181
|
|
|
3039
4182
|
if (!placeholder) {
|
|
3040
4183
|
return
|
|
3041
4184
|
}
|
|
3042
4185
|
|
|
4186
|
+
if (!hasAttemptedSessionLoad) {
|
|
4187
|
+
setPlaceholderState(placeholder, "loading", {
|
|
4188
|
+
title: "Loading session history",
|
|
4189
|
+
message: "Finding recent Codex, Claude, and Gemini sessions.",
|
|
4190
|
+
hint: "You can continue using Pinokio while this finishes."
|
|
4191
|
+
})
|
|
4192
|
+
placeholder.classList.remove("hidden")
|
|
4193
|
+
return
|
|
4194
|
+
}
|
|
4195
|
+
|
|
3043
4196
|
if (sessionItems.length === 0 || visible === 0) {
|
|
3044
|
-
|
|
3045
|
-
|
|
4197
|
+
if (lastSessionLoadFailed && sessionItems.length === 0 && !normalized) {
|
|
4198
|
+
setPlaceholderState(placeholder, "error", {
|
|
4199
|
+
title: "Could not load sessions",
|
|
4200
|
+
message: "Session history is temporarily unavailable.",
|
|
4201
|
+
hint: "Click Refresh to retry."
|
|
4202
|
+
})
|
|
4203
|
+
placeholder.classList.remove("hidden")
|
|
4204
|
+
return
|
|
4205
|
+
}
|
|
3046
4206
|
if (normalized && sessionItems.length > 0) {
|
|
3047
|
-
if (
|
|
3048
|
-
|
|
4207
|
+
if (sessionHasMore || sessionAppendInProgress) {
|
|
4208
|
+
setPlaceholderState(placeholder, "loading", {
|
|
4209
|
+
title: "Searching more sessions",
|
|
4210
|
+
message: sessionAppendInProgress
|
|
4211
|
+
? "Loading additional sessions for your search."
|
|
4212
|
+
: "Scroll to load more results."
|
|
4213
|
+
})
|
|
4214
|
+
} else {
|
|
4215
|
+
setPlaceholderState(placeholder, "search-empty", {
|
|
4216
|
+
title: "No matching sessions",
|
|
4217
|
+
message: "No sessions matched your current search.",
|
|
4218
|
+
hint: "Try a provider name, session topic, or path keyword."
|
|
4219
|
+
})
|
|
4220
|
+
}
|
|
3049
4221
|
} else if (sessionItems.length === 0) {
|
|
3050
|
-
|
|
3051
|
-
|
|
4222
|
+
setPlaceholderState(placeholder, "empty", {
|
|
4223
|
+
title: "No sessions yet",
|
|
4224
|
+
message: "No resumable Codex, Claude, or Gemini sessions were found.",
|
|
4225
|
+
hint: "Start an agent once, then return here to resume it."
|
|
4226
|
+
})
|
|
3052
4227
|
} else {
|
|
3053
|
-
|
|
3054
|
-
|
|
4228
|
+
setPlaceholderState(placeholder, "empty", {
|
|
4229
|
+
title: "No sessions available",
|
|
4230
|
+
message: "No resumable sessions are currently available.",
|
|
4231
|
+
hint: ""
|
|
4232
|
+
})
|
|
3055
4233
|
}
|
|
3056
4234
|
placeholder.classList.remove("hidden")
|
|
3057
4235
|
} else {
|
|
@@ -3064,6 +4242,7 @@ body.dark .swal2-popup.terminals-confirm-modal .swal2-styled.swal2-cancel {
|
|
|
3064
4242
|
const chooserColumns = columns.querySelectorAll(".terminals-column[data-state='chooser']")
|
|
3065
4243
|
chooserColumns.forEach((chooserColumn) => {
|
|
3066
4244
|
buildRows(chooserColumn, query)
|
|
4245
|
+
maybeLoadMoreSessionsForColumn(chooserColumn)
|
|
3067
4246
|
})
|
|
3068
4247
|
}
|
|
3069
4248
|
|
|
@@ -3080,36 +4259,55 @@ body.dark .swal2-popup.terminals-confirm-modal .swal2-styled.swal2-cancel {
|
|
|
3080
4259
|
</div>
|
|
3081
4260
|
</header>` : ""}
|
|
3082
4261
|
<div class="terminals-chooser-content">
|
|
3083
|
-
<div class="terminals-empty">
|
|
3084
|
-
<h1>Terminal sessions.</h1>
|
|
3085
|
-
<br>
|
|
3086
|
-
<div>No resumable Codex, Claude, or Gemini sessions were found.</div>
|
|
3087
|
-
<span>Run one from your terminal and come back here to continue.</span>
|
|
3088
|
-
</div>
|
|
4262
|
+
<div class="terminals-empty" data-state="loading" aria-live="polite"></div>
|
|
3089
4263
|
<div class="terminals-list-wrap">
|
|
3090
4264
|
<div class="terminals-list"></div>
|
|
3091
4265
|
</div>
|
|
3092
4266
|
</div>
|
|
3093
4267
|
`
|
|
3094
4268
|
const closeButton = column.querySelector(".close-column")
|
|
4269
|
+
const listWrap = column.querySelector(".terminals-list-wrap")
|
|
3095
4270
|
|
|
3096
4271
|
if (closeButton) {
|
|
3097
4272
|
closeButton.addEventListener("click", () => {
|
|
3098
4273
|
removeColumn(column)
|
|
3099
4274
|
})
|
|
3100
4275
|
}
|
|
4276
|
+
if (listWrap) {
|
|
4277
|
+
listWrap.addEventListener("scroll", () => {
|
|
4278
|
+
maybeLoadMoreSessionsForColumn(column)
|
|
4279
|
+
})
|
|
4280
|
+
}
|
|
3101
4281
|
|
|
3102
4282
|
const resolvedRefreshOptions = refreshOptions && typeof refreshOptions === "object" ? refreshOptions : {}
|
|
4283
|
+
const refreshQuery = getSessionSearchQuery()
|
|
4284
|
+
const resolvedRequestOptions = {
|
|
4285
|
+
...resolvedRefreshOptions,
|
|
4286
|
+
query: refreshQuery
|
|
4287
|
+
}
|
|
3103
4288
|
buildRows(column, getSessionSearchQuery())
|
|
3104
|
-
refreshSessionItems(
|
|
4289
|
+
refreshSessionItems(resolvedRequestOptions).then(() => {
|
|
3105
4290
|
if (!column.isConnected || column.dataset.state !== "chooser") {
|
|
3106
4291
|
return
|
|
3107
4292
|
}
|
|
3108
4293
|
buildRows(column, getSessionSearchQuery())
|
|
4294
|
+
maybeLoadMoreSessionsForColumn(column)
|
|
3109
4295
|
if (launcherState.open) {
|
|
3110
4296
|
renderLauncherSelectedChips()
|
|
3111
4297
|
renderLauncherSkillList()
|
|
3112
4298
|
}
|
|
4299
|
+
if (!hasTriggeredBackgroundRegistrySync) {
|
|
4300
|
+
hasTriggeredBackgroundRegistrySync = true
|
|
4301
|
+
void refreshSessionItems({
|
|
4302
|
+
sync: true,
|
|
4303
|
+
limit: getCurrentSessionRefreshLimit(),
|
|
4304
|
+
query: getSessionSearchQuery()
|
|
4305
|
+
}).then((refreshed) => {
|
|
4306
|
+
if (refreshed || hasAttemptedSessionLoad) {
|
|
4307
|
+
refreshChooserRows()
|
|
4308
|
+
}
|
|
4309
|
+
})
|
|
4310
|
+
}
|
|
3113
4311
|
})
|
|
3114
4312
|
syncActiveStates()
|
|
3115
4313
|
ensureLayout()
|
|
@@ -3154,11 +4352,11 @@ body.dark .swal2-popup.terminals-confirm-modal .swal2-styled.swal2-cancel {
|
|
|
3154
4352
|
const ensureLayout = () => {
|
|
3155
4353
|
if (columns.children.length === 0) {
|
|
3156
4354
|
emptyMain.classList.remove("hidden")
|
|
3157
|
-
|
|
4355
|
+
columnsWrap.classList.add("hidden")
|
|
3158
4356
|
renderTopSessionBar()
|
|
3159
4357
|
return
|
|
3160
4358
|
}
|
|
3161
|
-
|
|
4359
|
+
columnsWrap.classList.remove("hidden")
|
|
3162
4360
|
emptyMain.classList.add("hidden")
|
|
3163
4361
|
renderTopSessionBar()
|
|
3164
4362
|
}
|
|
@@ -3207,8 +4405,16 @@ body.dark .swal2-popup.terminals-confirm-modal .swal2-styled.swal2-cancel {
|
|
|
3207
4405
|
const rawGap = columnsStyle.getPropertyValue("gap") || columnsStyle.getPropertyValue("column-gap") || "10px"
|
|
3208
4406
|
const parsedGap = parseFloat(rawGap)
|
|
3209
4407
|
const gap = Number.isNaN(parsedGap) ? 10 : parsedGap
|
|
3210
|
-
const
|
|
3211
|
-
const
|
|
4408
|
+
const measuredWidth = columns.getBoundingClientRect().width
|
|
4409
|
+
const parentWidth = columns.parentElement ? columns.parentElement.getBoundingClientRect().width : 0
|
|
4410
|
+
const wrapWidth = columnsWrap ? columnsWrap.getBoundingClientRect().width : 0
|
|
4411
|
+
const fallbackViewportWidth = window.innerWidth || 0
|
|
4412
|
+
const containerWidth = Math.max(columns.clientWidth || 0, measuredWidth || 0, parentWidth || 0, wrapWidth || 0, fallbackViewportWidth || 0)
|
|
4413
|
+
if (!(containerWidth > 0)) {
|
|
4414
|
+
return
|
|
4415
|
+
}
|
|
4416
|
+
const available = Math.max(0, containerWidth - (gap * Math.max(columnCount - 1, 0)))
|
|
4417
|
+
const width = `${Math.max(1, available / columnCount)}px`
|
|
3212
4418
|
Array.from(columns.children).forEach((col) => {
|
|
3213
4419
|
col.style.flex = `0 0 ${width}`
|
|
3214
4420
|
})
|
|
@@ -3233,6 +4439,9 @@ body.dark .swal2-popup.terminals-confirm-modal .swal2-styled.swal2-cancel {
|
|
|
3233
4439
|
}
|
|
3234
4440
|
|
|
3235
4441
|
const openItemInColumn = (column, session) => {
|
|
4442
|
+
if (!canSessionResume(session)) {
|
|
4443
|
+
return
|
|
4444
|
+
}
|
|
3236
4445
|
const existingColumn = getOpenColumnByIndex(session.index)
|
|
3237
4446
|
if (existingColumn && existingColumn !== column) {
|
|
3238
4447
|
existingColumn.scrollIntoView({ behavior: "smooth", inline: "end" })
|
|
@@ -3258,7 +4467,7 @@ body.dark .swal2-popup.terminals-confirm-modal .swal2-styled.swal2-cancel {
|
|
|
3258
4467
|
requestTerminalFocus(frame)
|
|
3259
4468
|
}, 40)
|
|
3260
4469
|
setTimeout(() => {
|
|
3261
|
-
void refreshSessionItems({
|
|
4470
|
+
void refreshSessionItems({ sync: true, limit: getCurrentSessionRefreshLimit() }).then((refreshed) => {
|
|
3262
4471
|
if (refreshed) {
|
|
3263
4472
|
refreshChooserRows()
|
|
3264
4473
|
}
|
|
@@ -3353,7 +4562,19 @@ body.dark .swal2-popup.terminals-confirm-modal .swal2-styled.swal2-cancel {
|
|
|
3353
4562
|
|
|
3354
4563
|
if (sessionSearchInput) {
|
|
3355
4564
|
sessionSearchInput.addEventListener("input", () => {
|
|
4565
|
+
if (sessionSearchDebounceTimer) {
|
|
4566
|
+
clearTimeout(sessionSearchDebounceTimer)
|
|
4567
|
+
}
|
|
4568
|
+
const nextQuery = sessionSearchInput.value || ""
|
|
3356
4569
|
refreshChooserRows()
|
|
4570
|
+
sessionSearchDebounceTimer = setTimeout(() => {
|
|
4571
|
+
sessionSearchDebounceTimer = null
|
|
4572
|
+
void refreshSessionItems({ query: nextQuery }).then((loaded) => {
|
|
4573
|
+
if (loaded || hasAttemptedSessionLoad) {
|
|
4574
|
+
refreshChooserRows()
|
|
4575
|
+
}
|
|
4576
|
+
})
|
|
4577
|
+
}, SESSION_SEARCH_DEBOUNCE_MS)
|
|
3357
4578
|
})
|
|
3358
4579
|
}
|
|
3359
4580
|
|
|
@@ -3390,10 +4611,7 @@ body.dark .swal2-popup.terminals-confirm-modal .swal2-styled.swal2-cancel {
|
|
|
3390
4611
|
})
|
|
3391
4612
|
|
|
3392
4613
|
const setViewport = () => {
|
|
3393
|
-
|
|
3394
|
-
const headerHeight = navHeader ? navHeader.getBoundingClientRect().height : 0
|
|
3395
|
-
const frameHeight = Math.max(0, viewportHeight - headerHeight)
|
|
3396
|
-
root.style.setProperty("--terminals-viewport-height", `${frameHeight}px`)
|
|
4614
|
+
closeTopSessionOverflowMenu()
|
|
3397
4615
|
setColumnLayout()
|
|
3398
4616
|
}
|
|
3399
4617
|
|
|
@@ -3407,7 +4625,7 @@ body.dark .swal2-popup.terminals-confirm-modal .swal2-styled.swal2-cancel {
|
|
|
3407
4625
|
window.visualViewport.addEventListener("scroll", setViewport)
|
|
3408
4626
|
}
|
|
3409
4627
|
|
|
3410
|
-
addChooserColumn(
|
|
4628
|
+
addChooserColumn()
|
|
3411
4629
|
refreshChooserRows()
|
|
3412
4630
|
if (sessionSearchInput) {
|
|
3413
4631
|
requestAnimationFrame(() => {
|