python-codex 0.2.1__py3-none-any.whl → 0.2.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pycodex/cli.py +21 -13
- pycodex/context.py +4 -1
- pycodex/feishu_link.py +32 -14
- pycodex/interactive_session.py +5 -1
- {python_codex-0.2.1.dist-info → python_codex-0.2.3.dist-info}/METADATA +21 -4
- {python_codex-0.2.1.dist-info → python_codex-0.2.3.dist-info}/RECORD +14 -12
- workspace_server/__init__.py +19 -3
- workspace_server/app.py +591 -369
- workspace_server/workspace.html +377 -32
- workspace_server/workspaces.html +551 -0
- workspace_server/workspaces.py +561 -0
- {python_codex-0.2.1.dist-info → python_codex-0.2.3.dist-info}/WHEEL +0 -0
- {python_codex-0.2.1.dist-info → python_codex-0.2.3.dist-info}/entry_points.txt +0 -0
- {python_codex-0.2.1.dist-info → python_codex-0.2.3.dist-info}/licenses/LICENSE +0 -0
workspace_server/workspace.html
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="utf-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
-
<title>
|
|
6
|
+
<title>__WORKSPACE_TITLE__</title>
|
|
7
7
|
<style>
|
|
8
8
|
:root {
|
|
9
9
|
color-scheme: light;
|
|
@@ -63,13 +63,57 @@
|
|
|
63
63
|
.boardbar {
|
|
64
64
|
min-width: 0;
|
|
65
65
|
border-bottom: 1px solid var(--line);
|
|
66
|
-
padding:
|
|
66
|
+
padding: 0;
|
|
67
67
|
color: var(--muted);
|
|
68
68
|
font-size: 12px;
|
|
69
|
+
display: flex;
|
|
70
|
+
align-items: center;
|
|
71
|
+
gap: var(--space-5);
|
|
69
72
|
white-space: nowrap;
|
|
70
73
|
overflow: hidden;
|
|
74
|
+
}
|
|
75
|
+
.boardbar-paths {
|
|
76
|
+
min-width: 0;
|
|
77
|
+
display: flex;
|
|
78
|
+
align-items: center;
|
|
79
|
+
gap: var(--space-4);
|
|
80
|
+
overflow: hidden;
|
|
81
|
+
line-height: 20px;
|
|
82
|
+
}
|
|
83
|
+
.boardbar-paths span {
|
|
84
|
+
min-width: 0;
|
|
85
|
+
overflow: hidden;
|
|
71
86
|
text-overflow: ellipsis;
|
|
72
87
|
}
|
|
88
|
+
.workspace-back {
|
|
89
|
+
flex: 0 0 auto;
|
|
90
|
+
display: inline-flex;
|
|
91
|
+
align-items: center;
|
|
92
|
+
justify-content: center;
|
|
93
|
+
width: 28px;
|
|
94
|
+
height: 20px;
|
|
95
|
+
border: 0;
|
|
96
|
+
border-radius: var(--radius-sm);
|
|
97
|
+
background: transparent;
|
|
98
|
+
color: var(--ink);
|
|
99
|
+
padding: 0;
|
|
100
|
+
text-decoration: none;
|
|
101
|
+
}
|
|
102
|
+
.workspace-back:hover {
|
|
103
|
+
background: #eef7f1;
|
|
104
|
+
}
|
|
105
|
+
.workspace-back:focus {
|
|
106
|
+
outline: 2px solid #9bb7a6;
|
|
107
|
+
outline-offset: 1px;
|
|
108
|
+
}
|
|
109
|
+
.workspace-back::before {
|
|
110
|
+
content: "";
|
|
111
|
+
width: 7px;
|
|
112
|
+
height: 7px;
|
|
113
|
+
border-left: 2px solid currentColor;
|
|
114
|
+
border-bottom: 2px solid currentColor;
|
|
115
|
+
transform: rotate(45deg);
|
|
116
|
+
}
|
|
73
117
|
.board {
|
|
74
118
|
border: 0;
|
|
75
119
|
min-width: 0;
|
|
@@ -136,6 +180,36 @@
|
|
|
136
180
|
gap: var(--space-2);
|
|
137
181
|
min-width: 0;
|
|
138
182
|
}
|
|
183
|
+
.context-meter {
|
|
184
|
+
--context-remaining: 0%;
|
|
185
|
+
--context-meter-size: 22px;
|
|
186
|
+
position: relative;
|
|
187
|
+
display: inline-grid;
|
|
188
|
+
place-items: center;
|
|
189
|
+
width: var(--context-meter-size);
|
|
190
|
+
height: var(--context-meter-size);
|
|
191
|
+
border-radius: 999px;
|
|
192
|
+
background: conic-gradient(#4ca56f 0 var(--context-remaining), #cfd8d2 0 100%);
|
|
193
|
+
color: #335243;
|
|
194
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
195
|
+
font-size: 9px;
|
|
196
|
+
line-height: 1;
|
|
197
|
+
flex: 0 0 auto;
|
|
198
|
+
}
|
|
199
|
+
.context-meter.hidden {
|
|
200
|
+
display: none;
|
|
201
|
+
}
|
|
202
|
+
.context-meter::before {
|
|
203
|
+
content: "";
|
|
204
|
+
position: absolute;
|
|
205
|
+
inset: 4px;
|
|
206
|
+
border-radius: 999px;
|
|
207
|
+
background: #fff;
|
|
208
|
+
}
|
|
209
|
+
.context-meter-value {
|
|
210
|
+
position: relative;
|
|
211
|
+
z-index: 1;
|
|
212
|
+
}
|
|
139
213
|
.tabs {
|
|
140
214
|
display: flex;
|
|
141
215
|
align-items: center;
|
|
@@ -171,21 +245,27 @@
|
|
|
171
245
|
background: #eef7f1;
|
|
172
246
|
box-shadow: inset 0 -2px 0 var(--accent);
|
|
173
247
|
}
|
|
174
|
-
.tab
|
|
248
|
+
.tab-status {
|
|
249
|
+
position: relative;
|
|
250
|
+
display: inline-flex;
|
|
251
|
+
align-items: center;
|
|
252
|
+
justify-content: center;
|
|
253
|
+
width: 15px;
|
|
254
|
+
height: 15px;
|
|
255
|
+
border: 1px solid transparent;
|
|
256
|
+
border-radius: 999px;
|
|
257
|
+
background: transparent;
|
|
258
|
+
padding: 0;
|
|
259
|
+
flex: 0 0 auto;
|
|
260
|
+
}
|
|
261
|
+
.tab-status::before {
|
|
175
262
|
content: "";
|
|
176
|
-
position: absolute;
|
|
177
|
-
left: 6px;
|
|
178
|
-
top: 50%;
|
|
179
263
|
width: 5px;
|
|
180
264
|
height: 5px;
|
|
181
|
-
margin-top: -2.5px;
|
|
182
265
|
border-radius: 999px;
|
|
183
266
|
background: var(--accent);
|
|
184
267
|
animation: spinner-pulse 1.2s cubic-bezier(0.4, 0, 0.2, 1) infinite;
|
|
185
268
|
}
|
|
186
|
-
.tab.running .tab-label {
|
|
187
|
-
padding-left: var(--space-4);
|
|
188
|
-
}
|
|
189
269
|
.tab-label {
|
|
190
270
|
min-width: 0;
|
|
191
271
|
overflow: hidden;
|
|
@@ -352,9 +432,9 @@
|
|
|
352
432
|
text-decoration: underline;
|
|
353
433
|
}
|
|
354
434
|
.composer {
|
|
435
|
+
position: relative;
|
|
355
436
|
min-width: 0;
|
|
356
|
-
|
|
357
|
-
padding: var(--space-4) var(--space-6) var(--space-5);
|
|
437
|
+
padding: var(--space-4);
|
|
358
438
|
display: grid;
|
|
359
439
|
gap: var(--space-3);
|
|
360
440
|
align-content: end;
|
|
@@ -363,7 +443,6 @@
|
|
|
363
443
|
textarea {
|
|
364
444
|
min-width: 0;
|
|
365
445
|
resize: vertical;
|
|
366
|
-
min-height: 40px;
|
|
367
446
|
max-height: 35vh;
|
|
368
447
|
width: 100%;
|
|
369
448
|
border: 1px solid var(--line);
|
|
@@ -385,14 +464,27 @@
|
|
|
385
464
|
justify-content: flex-end;
|
|
386
465
|
align-items: center;
|
|
387
466
|
}
|
|
388
|
-
.spinner {
|
|
467
|
+
.spinner-wrap {
|
|
389
468
|
position: relative;
|
|
390
469
|
display: inline-flex;
|
|
391
470
|
align-items: center;
|
|
392
471
|
justify-self: start;
|
|
393
472
|
align-self: end;
|
|
394
473
|
max-width: 100%;
|
|
395
|
-
|
|
474
|
+
}
|
|
475
|
+
.prompt-row {
|
|
476
|
+
display: flex;
|
|
477
|
+
align-items: center;
|
|
478
|
+
gap: var(--space-2);
|
|
479
|
+
min-width: 0;
|
|
480
|
+
}
|
|
481
|
+
.spinner {
|
|
482
|
+
--spinner-pill-height: 22px;
|
|
483
|
+
position: relative;
|
|
484
|
+
display: inline-flex;
|
|
485
|
+
align-items: center;
|
|
486
|
+
max-width: 100%;
|
|
487
|
+
height: var(--spinner-pill-height);
|
|
396
488
|
color: var(--muted);
|
|
397
489
|
font-size: 12px;
|
|
398
490
|
white-space: nowrap;
|
|
@@ -402,11 +494,22 @@
|
|
|
402
494
|
border-radius: 999px;
|
|
403
495
|
background: rgba(240, 250, 244, 0.96);
|
|
404
496
|
border: 1px solid rgba(172, 216, 188, 0.9);
|
|
497
|
+
font: inherit;
|
|
498
|
+
font-size: 12px;
|
|
499
|
+
cursor: pointer;
|
|
405
500
|
}
|
|
406
501
|
.spinner:empty {
|
|
407
502
|
display: none;
|
|
408
503
|
}
|
|
409
|
-
.spinner:not(:empty) {
|
|
504
|
+
.spinner-wrap:not(:has(.spinner:not(:empty))) {
|
|
505
|
+
display: none;
|
|
506
|
+
}
|
|
507
|
+
.spinner:focus-visible {
|
|
508
|
+
border-color: #65b782;
|
|
509
|
+
box-shadow: 0 0 0 3px rgba(15, 143, 85, 0.13);
|
|
510
|
+
outline: 0;
|
|
511
|
+
}
|
|
512
|
+
.spinner-wrap:has(.spinner:not(:empty)) {
|
|
410
513
|
margin-bottom: var(--space-1);
|
|
411
514
|
}
|
|
412
515
|
.spinner:not(:empty)::before {
|
|
@@ -421,6 +524,38 @@
|
|
|
421
524
|
background: var(--accent);
|
|
422
525
|
animation: spinner-pulse 1.2s cubic-bezier(0.4, 0, 0.2, 1) infinite;
|
|
423
526
|
}
|
|
527
|
+
.spinner.notify-enabled {
|
|
528
|
+
color: #0b6b43;
|
|
529
|
+
background: #e6f4eb;
|
|
530
|
+
border-color: #4ca56f;
|
|
531
|
+
box-shadow:
|
|
532
|
+
inset 0 1px 2px rgba(9, 93, 55, 0.16),
|
|
533
|
+
inset 0 0 0 1px rgba(15, 143, 85, 0.1);
|
|
534
|
+
}
|
|
535
|
+
.toast {
|
|
536
|
+
position: absolute;
|
|
537
|
+
left: 0;
|
|
538
|
+
bottom: 100%;
|
|
539
|
+
z-index: 5;
|
|
540
|
+
width: max-content;
|
|
541
|
+
max-width: min(320px, calc(100vw - var(--space-6) * 2));
|
|
542
|
+
border: 1px solid #bfd6c8;
|
|
543
|
+
border-radius: var(--radius-sm);
|
|
544
|
+
background: #f6fbf8;
|
|
545
|
+
color: var(--ink);
|
|
546
|
+
box-shadow: 0 8px 20px rgba(25, 35, 31, 0.12);
|
|
547
|
+
padding: var(--space-4) var(--space-6);
|
|
548
|
+
font-size: 12px;
|
|
549
|
+
opacity: 0;
|
|
550
|
+
pointer-events: none;
|
|
551
|
+
transform: translateY(4px);
|
|
552
|
+
transition: opacity 120ms ease, transform 120ms ease;
|
|
553
|
+
margin-bottom: var(--space-3);
|
|
554
|
+
}
|
|
555
|
+
.toast.visible {
|
|
556
|
+
opacity: 1;
|
|
557
|
+
transform: translateY(0);
|
|
558
|
+
}
|
|
424
559
|
@keyframes spinner-pulse {
|
|
425
560
|
0%, 100% {
|
|
426
561
|
opacity: 0.45;
|
|
@@ -445,11 +580,11 @@
|
|
|
445
580
|
}
|
|
446
581
|
@media (prefers-reduced-motion: reduce) {
|
|
447
582
|
.entry.thinking,
|
|
448
|
-
.tab
|
|
583
|
+
.tab-status::before,
|
|
449
584
|
.spinner:not(:empty)::before {
|
|
450
585
|
animation: none;
|
|
451
586
|
}
|
|
452
|
-
.tab
|
|
587
|
+
.tab-status::before,
|
|
453
588
|
.spinner:not(:empty)::before {
|
|
454
589
|
opacity: 0.8;
|
|
455
590
|
transform: none;
|
|
@@ -472,7 +607,13 @@
|
|
|
472
607
|
<body>
|
|
473
608
|
<div class="workspace">
|
|
474
609
|
<section class="board-pane" aria-label="workspace board">
|
|
475
|
-
<div class="boardbar">
|
|
610
|
+
<div class="boardbar">
|
|
611
|
+
<a class="workspace-back" href="/" title="Back to workspaces" aria-label="Back to workspaces"></a>
|
|
612
|
+
<div class="boardbar-paths">
|
|
613
|
+
<span>@<code>__CWD_LABEL__</code></span>
|
|
614
|
+
<span>Board: <code>__BOARD_LABEL__</code></span>
|
|
615
|
+
</div>
|
|
616
|
+
</div>
|
|
476
617
|
<iframe id="boardFrame" class="board" src="board" title="board"></iframe>
|
|
477
618
|
</section>
|
|
478
619
|
<button id="splitter" class="splitter" type="button" aria-label="Resize chat panel"></button>
|
|
@@ -485,8 +626,16 @@
|
|
|
485
626
|
</div>
|
|
486
627
|
<div id="log" class="log"></div>
|
|
487
628
|
<form id="composer" class="composer">
|
|
488
|
-
<div
|
|
489
|
-
|
|
629
|
+
<div class="spinner-wrap">
|
|
630
|
+
<div id="toast" class="toast" role="status" aria-live="polite"></div>
|
|
631
|
+
<button id="spinner" class="spinner" type="button" role="checkbox" aria-checked="false" aria-live="polite"></button>
|
|
632
|
+
</div>
|
|
633
|
+
<div class="prompt-row">
|
|
634
|
+
<div id="contextMeter" class="context-meter hidden" aria-hidden="true">
|
|
635
|
+
<span id="contextMeterValue" class="context-meter-value"></span>
|
|
636
|
+
</div>
|
|
637
|
+
<textarea id="prompt" rows="1" placeholder="Ask pycodex or type /help..."></textarea>
|
|
638
|
+
</div>
|
|
490
639
|
</form>
|
|
491
640
|
</section>
|
|
492
641
|
</div>
|
|
@@ -495,21 +644,25 @@
|
|
|
495
644
|
<script>
|
|
496
645
|
const log = document.getElementById("log");
|
|
497
646
|
const spinner = document.getElementById("spinner");
|
|
647
|
+
const toast = document.getElementById("toast");
|
|
498
648
|
const prompt = document.getElementById("prompt");
|
|
499
649
|
const form = document.getElementById("composer");
|
|
500
650
|
const boardFrame = document.getElementById("boardFrame");
|
|
501
651
|
const workspace = document.querySelector(".workspace");
|
|
502
652
|
const splitter = document.getElementById("splitter");
|
|
503
653
|
const tabsEl = document.getElementById("tabs");
|
|
654
|
+
const contextMeter = document.getElementById("contextMeter");
|
|
655
|
+
const contextMeterValue = document.getElementById("contextMeterValue");
|
|
504
656
|
const newTabButton = document.getElementById("newTab");
|
|
505
657
|
let pollTimer = null;
|
|
506
658
|
let boardPollTimer = null;
|
|
507
659
|
let pollInFlight = false;
|
|
508
660
|
let boardPollInFlight = false;
|
|
509
|
-
let lastBoardSignature =
|
|
661
|
+
let lastBoardSignature = null;
|
|
510
662
|
let lastRenderedSignature = "";
|
|
511
663
|
let resizeStart = null;
|
|
512
664
|
let spinnerText = "";
|
|
665
|
+
let toastTimer = null;
|
|
513
666
|
let activeSessionId = "";
|
|
514
667
|
let sessions = [];
|
|
515
668
|
let isComposing = false;
|
|
@@ -627,9 +780,128 @@
|
|
|
627
780
|
event.preventDefault();
|
|
628
781
|
}
|
|
629
782
|
|
|
630
|
-
function setSpinner(text) {
|
|
631
|
-
|
|
783
|
+
function setSpinner(text, options = {}) {
|
|
784
|
+
const previousText = spinnerText;
|
|
785
|
+
const nextText = String(text || "").trim();
|
|
786
|
+
const wasRunning = Boolean(previousText);
|
|
787
|
+
const isRunning = Boolean(nextText);
|
|
788
|
+
spinnerText = nextText;
|
|
632
789
|
spinner.textContent = spinnerText;
|
|
790
|
+
if (wasRunning && !isRunning && !options.suppressDoneNotification) {
|
|
791
|
+
const state = stateForSession(activeSessionId);
|
|
792
|
+
if (state.wasRunning) notifySessionDone(activeSessionId);
|
|
793
|
+
}
|
|
794
|
+
updateSpinnerNotifyUi();
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
function showToast(text) {
|
|
798
|
+
if (toastTimer) clearTimeout(toastTimer);
|
|
799
|
+
toast.textContent = text;
|
|
800
|
+
toast.classList.add("visible");
|
|
801
|
+
toastTimer = setTimeout(() => {
|
|
802
|
+
toastTimer = null;
|
|
803
|
+
toast.classList.remove("visible");
|
|
804
|
+
}, 1400);
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
function updateSpinnerNotifyUi() {
|
|
808
|
+
const state = stateForSession(activeSessionId);
|
|
809
|
+
const enabled = Boolean(spinnerText && state.notifyWhenDone);
|
|
810
|
+
spinner.classList.toggle("notify-enabled", enabled);
|
|
811
|
+
spinner.setAttribute("aria-checked", enabled ? "true" : "false");
|
|
812
|
+
if (!spinnerText) {
|
|
813
|
+
spinner.title = "Spinner is idle";
|
|
814
|
+
} else if (enabled) {
|
|
815
|
+
spinner.title = "Notify when done is on";
|
|
816
|
+
} else {
|
|
817
|
+
spinner.title = "Notify when done is off";
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
function notificationAvailable() {
|
|
822
|
+
return "Notification" in window;
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
function requestNotificationPermission() {
|
|
826
|
+
try {
|
|
827
|
+
const requestResult = Notification.requestPermission();
|
|
828
|
+
if (requestResult && typeof requestResult.then === "function") {
|
|
829
|
+
return requestResult;
|
|
830
|
+
}
|
|
831
|
+
return Promise.resolve(Notification.permission);
|
|
832
|
+
} catch (_error) {
|
|
833
|
+
return Promise.resolve(Notification.permission);
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
function notifySessionDone(sessionId, sessionSummary = null) {
|
|
838
|
+
if (!sessionId) return;
|
|
839
|
+
const state = stateForSession(sessionId);
|
|
840
|
+
if (!state.notifyWhenDone) return;
|
|
841
|
+
state.notifyWhenDone = false;
|
|
842
|
+
if (!notificationAvailable() || Notification.permission !== "granted") return;
|
|
843
|
+
const session = sessionSummary || sessionById(sessionId);
|
|
844
|
+
const title = (session && session.title) || "pycodex";
|
|
845
|
+
const lastAssistant = (session && session.last_assistant) || "Done.";
|
|
846
|
+
try {
|
|
847
|
+
new Notification(`session: ${title}`, {
|
|
848
|
+
body: lastAssistant,
|
|
849
|
+
tag: `pycodex-workspace-done-${sessionId}`,
|
|
850
|
+
});
|
|
851
|
+
} catch (_error) {
|
|
852
|
+
return;
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
function sessionById(sessionId) {
|
|
857
|
+
return sessions.find((session) => (session.id || "") === sessionId) || null;
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
function isSessionRunning(sessionId) {
|
|
861
|
+
if (sessionId === activeSessionId && spinnerText) return true;
|
|
862
|
+
const session = sessionById(sessionId);
|
|
863
|
+
if (session) return Boolean(session.running);
|
|
864
|
+
return false;
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
async function toggleSpinnerNotification() {
|
|
868
|
+
const sessionId = activeSessionId;
|
|
869
|
+
if (!sessionId || !isSessionRunning(sessionId)) {
|
|
870
|
+
showToast("notify-on-done unavailable");
|
|
871
|
+
return;
|
|
872
|
+
}
|
|
873
|
+
const state = stateForSession(sessionId);
|
|
874
|
+
if (state.notifyWhenDone) {
|
|
875
|
+
state.notifyWhenDone = false;
|
|
876
|
+
showToast("notify-on-done canceled");
|
|
877
|
+
updateSpinnerNotifyUi();
|
|
878
|
+
return;
|
|
879
|
+
}
|
|
880
|
+
if (!notificationAvailable()) {
|
|
881
|
+
showToast("browser notifications unavailable");
|
|
882
|
+
return;
|
|
883
|
+
}
|
|
884
|
+
if (Notification.permission === "denied") {
|
|
885
|
+
showToast("browser notifications blocked");
|
|
886
|
+
return;
|
|
887
|
+
}
|
|
888
|
+
state.notifyWhenDone = true;
|
|
889
|
+
showToast("notify-on-done enabled");
|
|
890
|
+
updateSpinnerNotifyUi();
|
|
891
|
+
if (Notification.permission !== "granted") {
|
|
892
|
+
const permission = await requestNotificationPermission();
|
|
893
|
+
if (permission !== "granted") {
|
|
894
|
+
state.notifyWhenDone = false;
|
|
895
|
+
showToast("notify-on-done canceled");
|
|
896
|
+
updateSpinnerNotifyUi();
|
|
897
|
+
return;
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
if (!isSessionRunning(sessionId)) {
|
|
901
|
+
state.notifyWhenDone = false;
|
|
902
|
+
showToast("notify-on-done canceled");
|
|
903
|
+
}
|
|
904
|
+
updateSpinnerNotifyUi();
|
|
633
905
|
}
|
|
634
906
|
|
|
635
907
|
function isNearBottom() {
|
|
@@ -642,16 +914,22 @@
|
|
|
642
914
|
|
|
643
915
|
function resizePrompt() {
|
|
644
916
|
prompt.style.height = "auto";
|
|
645
|
-
const
|
|
646
|
-
const maxHeight = Math.
|
|
647
|
-
const nextHeight = Math.
|
|
917
|
+
const borderHeight = prompt.offsetHeight - prompt.clientHeight;
|
|
918
|
+
const maxHeight = Math.round(window.innerHeight * 0.35);
|
|
919
|
+
const nextHeight = Math.min(prompt.scrollHeight + borderHeight, maxHeight);
|
|
648
920
|
prompt.style.height = `${nextHeight}px`;
|
|
649
921
|
}
|
|
650
922
|
|
|
651
923
|
function stateForSession(sessionId) {
|
|
652
924
|
const key = String(sessionId || "");
|
|
653
925
|
if (!sessionState.has(key)) {
|
|
654
|
-
sessionState.set(key, {
|
|
926
|
+
sessionState.set(key, {
|
|
927
|
+
lastRenderedSignature: "",
|
|
928
|
+
scrollTop: null,
|
|
929
|
+
draft: "",
|
|
930
|
+
notifyWhenDone: false,
|
|
931
|
+
wasRunning: false,
|
|
932
|
+
});
|
|
655
933
|
}
|
|
656
934
|
return sessionState.get(key);
|
|
657
935
|
}
|
|
@@ -667,11 +945,27 @@
|
|
|
667
945
|
}
|
|
668
946
|
|
|
669
947
|
function updateSessions(nextSessions) {
|
|
670
|
-
|
|
948
|
+
const incomingSessions = Array.isArray(nextSessions) ? nextSessions : sessions;
|
|
949
|
+
incomingSessions.forEach((session) => {
|
|
950
|
+
const sessionId = session.id || "";
|
|
951
|
+
if (!sessionId) return;
|
|
952
|
+
const state = stateForSession(sessionId);
|
|
953
|
+
const wasRunning = Boolean(state.wasRunning);
|
|
954
|
+
const isRunning = Boolean(session.running);
|
|
955
|
+
if (!wasRunning && isRunning) {
|
|
956
|
+
state.notifyWhenDone = false;
|
|
957
|
+
} else if (wasRunning && !isRunning) {
|
|
958
|
+
notifySessionDone(sessionId, session);
|
|
959
|
+
}
|
|
960
|
+
state.wasRunning = isRunning;
|
|
961
|
+
});
|
|
962
|
+
sessions = incomingSessions;
|
|
671
963
|
if (!activeSessionId && sessions.length) {
|
|
672
964
|
activeSessionId = sessions[0].id || "";
|
|
673
965
|
}
|
|
674
966
|
renderTabs();
|
|
967
|
+
updateContextMeter();
|
|
968
|
+
updateSpinnerNotifyUi();
|
|
675
969
|
}
|
|
676
970
|
|
|
677
971
|
function renderTabs() {
|
|
@@ -689,6 +983,13 @@
|
|
|
689
983
|
if (session.running) tab.setAttribute("aria-busy", "true");
|
|
690
984
|
tab.addEventListener("click", () => switchSession(sessionId));
|
|
691
985
|
|
|
986
|
+
if (session.running) {
|
|
987
|
+
const status = document.createElement("span");
|
|
988
|
+
status.className = "tab-status";
|
|
989
|
+
status.setAttribute("aria-hidden", "true");
|
|
990
|
+
tab.appendChild(status);
|
|
991
|
+
}
|
|
992
|
+
|
|
692
993
|
const label = document.createElement("span");
|
|
693
994
|
label.className = "tab-label";
|
|
694
995
|
label.textContent = session.title || `Session ${index + 1}`;
|
|
@@ -710,6 +1011,38 @@
|
|
|
710
1011
|
});
|
|
711
1012
|
}
|
|
712
1013
|
|
|
1014
|
+
function updateContextMeter(snapshot = null) {
|
|
1015
|
+
let percent = null;
|
|
1016
|
+
if (snapshot && snapshot.context_remaining_percent !== undefined) {
|
|
1017
|
+
percent = snapshot.context_remaining_percent;
|
|
1018
|
+
} else {
|
|
1019
|
+
const session = sessionById(activeSessionId);
|
|
1020
|
+
if (session && session.context_remaining_percent !== undefined) {
|
|
1021
|
+
percent = session.context_remaining_percent;
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
if (percent === null || percent === undefined || percent === "") {
|
|
1025
|
+
contextMeter.classList.add("hidden");
|
|
1026
|
+
contextMeterValue.textContent = "";
|
|
1027
|
+
return;
|
|
1028
|
+
}
|
|
1029
|
+
const value = Math.max(0, Math.min(100, Math.round(Number(percent))));
|
|
1030
|
+
if (!Number.isFinite(value)) {
|
|
1031
|
+
contextMeter.classList.add("hidden");
|
|
1032
|
+
contextMeterValue.textContent = "";
|
|
1033
|
+
return;
|
|
1034
|
+
}
|
|
1035
|
+
if (value >= 100) {
|
|
1036
|
+
contextMeter.classList.remove("hidden");
|
|
1037
|
+
contextMeter.style.setProperty("--context-remaining", `${value}%`);
|
|
1038
|
+
contextMeterValue.textContent = "";
|
|
1039
|
+
return;
|
|
1040
|
+
}
|
|
1041
|
+
contextMeter.classList.remove("hidden");
|
|
1042
|
+
contextMeter.style.setProperty("--context-remaining", `${value}%`);
|
|
1043
|
+
contextMeterValue.textContent = String(value);
|
|
1044
|
+
}
|
|
1045
|
+
|
|
713
1046
|
function messageSignature(snapshot) {
|
|
714
1047
|
const turns = (snapshot && snapshot.turns) || [];
|
|
715
1048
|
return JSON.stringify(
|
|
@@ -771,10 +1104,14 @@
|
|
|
771
1104
|
const response = await fetch(relativeUrl("api/board"));
|
|
772
1105
|
if (!response.ok) return;
|
|
773
1106
|
const payload = await response.json();
|
|
774
|
-
if (!payload.exists)
|
|
1107
|
+
if (!payload.exists) {
|
|
1108
|
+
lastBoardSignature = null;
|
|
1109
|
+
return;
|
|
1110
|
+
}
|
|
775
1111
|
const signature = `${payload.mtime_ns || ""}:${payload.size || ""}`;
|
|
776
|
-
if (
|
|
1112
|
+
if (lastBoardSignature === null) {
|
|
777
1113
|
lastBoardSignature = signature;
|
|
1114
|
+
boardFrame.src = `board?t=${Date.now()}`;
|
|
778
1115
|
return;
|
|
779
1116
|
}
|
|
780
1117
|
if (signature !== lastBoardSignature) {
|
|
@@ -800,6 +1137,7 @@
|
|
|
800
1137
|
const signature = messageSignature(snapshot);
|
|
801
1138
|
if (signature === state.lastRenderedSignature) {
|
|
802
1139
|
setSpinner(snapshot.spinner || "");
|
|
1140
|
+
updateContextMeter(snapshot);
|
|
803
1141
|
return;
|
|
804
1142
|
}
|
|
805
1143
|
state.lastRenderedSignature = signature;
|
|
@@ -814,6 +1152,7 @@
|
|
|
814
1152
|
turns.forEach((turn) => renderTurn(turn, shouldStick));
|
|
815
1153
|
}
|
|
816
1154
|
setSpinner(snapshot.spinner || "");
|
|
1155
|
+
updateContextMeter(snapshot);
|
|
817
1156
|
if (restoreScrollTop !== null) {
|
|
818
1157
|
log.scrollTop = Math.min(
|
|
819
1158
|
restoreScrollTop,
|
|
@@ -889,7 +1228,7 @@
|
|
|
889
1228
|
suppressScrollSave = true;
|
|
890
1229
|
log.textContent = "";
|
|
891
1230
|
suppressScrollSave = false;
|
|
892
|
-
setSpinner("");
|
|
1231
|
+
setSpinner("", {suppressDoneNotification: true});
|
|
893
1232
|
restoreScrollTop = state.scrollTop;
|
|
894
1233
|
await pollSession();
|
|
895
1234
|
renderTabs();
|
|
@@ -984,12 +1323,18 @@
|
|
|
984
1323
|
splitter.addEventListener("pointerup", endResize);
|
|
985
1324
|
splitter.addEventListener("pointercancel", endResize);
|
|
986
1325
|
splitter.addEventListener("dblclick", centerSplit);
|
|
1326
|
+
spinner.addEventListener("click", toggleSpinnerNotification);
|
|
987
1327
|
newTabButton.addEventListener("click", createSession);
|
|
988
1328
|
window.addEventListener("resize", () => {
|
|
989
1329
|
const current = Number(window.localStorage.getItem("pycodex.workspace.chatWidth") || 0);
|
|
990
1330
|
if (current > 0) setChatWidth(current);
|
|
991
1331
|
resizePrompt();
|
|
992
1332
|
});
|
|
1333
|
+
document.addEventListener("visibilitychange", () => {
|
|
1334
|
+
if (document.visibilityState !== "visible") return;
|
|
1335
|
+
pollSession();
|
|
1336
|
+
pollBoard();
|
|
1337
|
+
});
|
|
993
1338
|
|
|
994
1339
|
loadChatWidth();
|
|
995
1340
|
resizePrompt();
|