python-codex 0.2.7__py3-none-any.whl → 0.3.0__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/__init__.py +14 -14
- pycodex/agent.py +465 -499
- pycodex/bootstrap.py +417 -0
- pycodex/cli.py +236 -510
- pycodex/compat.py +19 -5
- pycodex/context.py +222 -212
- pycodex/doctor.py +52 -48
- pycodex/events.py +857 -0
- pycodex/feishu_card.py +217 -163
- pycodex/feishu_link.py +43 -83
- pycodex/model.py +324 -253
- pycodex/model_metadata.py +19 -7
- pycodex/portable.py +76 -45
- pycodex/portable_server.py +32 -24
- pycodex/prompts/models.json +245 -983
- pycodex/protocol.py +177 -137
- pycodex/runtime.py +579 -176
- pycodex/runtime_services.py +204 -157
- pycodex/tools/__init__.py +1 -1
- pycodex/tools/apply_patch_tool.py +69 -48
- pycodex/tools/base_tool.py +89 -42
- pycodex/tools/clock_tool.py +58 -25
- pycodex/tools/close_agent_tool.py +2 -2
- pycodex/tools/code_mode_manager.py +77 -64
- pycodex/tools/exec_command_tool.py +26 -11
- pycodex/tools/exec_tool.py +4 -4
- pycodex/tools/grep_files_tool.py +12 -10
- pycodex/tools/ipython_tool.py +10 -13
- pycodex/tools/list_dir_tool.py +13 -9
- pycodex/tools/read_file_tool.py +29 -17
- pycodex/tools/request_permissions_tool.py +15 -5
- pycodex/tools/request_user_input_tool.py +13 -104
- pycodex/tools/resume_agent_tool.py +2 -2
- pycodex/tools/send_input_tool.py +11 -8
- pycodex/tools/shell_command_tool.py +7 -5
- pycodex/tools/shell_tool.py +7 -5
- pycodex/tools/spawn_agent_tool.py +7 -4
- pycodex/tools/unified_exec_manager.py +102 -69
- pycodex/tools/update_plan_tool.py +8 -5
- pycodex/tools/view_image_tool.py +7 -5
- pycodex/tools/wait_agent_tool.py +27 -4
- pycodex/tools/wait_tool.py +5 -4
- pycodex/tools/web_search_tool.py +4 -2
- pycodex/tools/write_stdin_tool.py +12 -11
- pycodex/utils/__init__.py +2 -17
- pycodex/utils/compactor.py +41 -72
- pycodex/utils/debug.py +2 -2
- pycodex/utils/dotenv.py +6 -7
- pycodex/utils/event_helpers.py +190 -0
- pycodex/utils/get_env.py +27 -70
- pycodex/{image_utils.py → utils/image_utils.py} +8 -11
- pycodex/utils/random_ids.py +1 -2
- pycodex/utils/session_persist.py +217 -163
- pycodex/utils/truncation.py +21 -45
- python_codex-0.3.0.dist-info/METADATA +704 -0
- python_codex-0.3.0.dist-info/RECORD +90 -0
- responses_server/__init__.py +1 -5
- responses_server/__main__.py +0 -1
- responses_server/app.py +36 -31
- responses_server/config.py +23 -23
- responses_server/messages_api.py +51 -53
- responses_server/payload_processors.py +25 -20
- responses_server/server.py +11 -11
- responses_server/session_store.py +14 -11
- responses_server/stream_router.py +101 -98
- responses_server/tools/custom_adapter.py +17 -16
- responses_server/tools/web_search.py +39 -36
- responses_server/trajectory_dump.py +36 -14
- workspace_server/__main__.py +0 -1
- workspace_server/app.py +461 -375
- workspace_server/workspace.html +852 -228
- workspace_server/workspaces.html +94 -95
- workspace_server/workspaces.py +137 -79
- pycodex/collaboration.py +0 -20
- pycodex/interactive_session.py +0 -415
- pycodex/prompts/collaboration_default.md +0 -11
- pycodex/prompts/collaboration_plan.md +0 -128
- pycodex/utils/toolcall_visualize.py +0 -713
- pycodex/utils/visualize.py +0 -560
- python_codex-0.2.7.dist-info/METADATA +0 -455
- python_codex-0.2.7.dist-info/RECORD +0 -93
- {python_codex-0.2.7.dist-info → python_codex-0.3.0.dist-info}/WHEEL +0 -0
- {python_codex-0.2.7.dist-info → python_codex-0.3.0.dist-info}/entry_points.txt +0 -0
- {python_codex-0.2.7.dist-info → python_codex-0.3.0.dist-info}/licenses/LICENSE +0 -0
workspace_server/workspace.html
CHANGED
|
@@ -42,8 +42,8 @@
|
|
|
42
42
|
--space-5: 10px;
|
|
43
43
|
--space-6: 12px;
|
|
44
44
|
--space-7: 14px;
|
|
45
|
-
--radius-sm:
|
|
46
|
-
--radius-md:
|
|
45
|
+
--radius-sm: 6px;
|
|
46
|
+
--radius-md: 10px;
|
|
47
47
|
--chat-width: min(42vw, 760px);
|
|
48
48
|
--splitter-width: 8px;
|
|
49
49
|
}
|
|
@@ -54,8 +54,10 @@
|
|
|
54
54
|
overflow: hidden;
|
|
55
55
|
background: var(--bg);
|
|
56
56
|
color: var(--ink);
|
|
57
|
-
font: 14px/1.
|
|
57
|
+
font: 14px/1.55 -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
58
58
|
}
|
|
59
|
+
button, textarea { font: inherit; }
|
|
60
|
+
.pane-toggle { display: none; }
|
|
59
61
|
.workspace {
|
|
60
62
|
display: grid;
|
|
61
63
|
grid-template-columns: minmax(0, 1fr) var(--splitter-width) var(--chat-width);
|
|
@@ -75,23 +77,31 @@
|
|
|
75
77
|
.boardbar {
|
|
76
78
|
min-width: 0;
|
|
77
79
|
border-bottom: 1px solid var(--line);
|
|
78
|
-
|
|
80
|
+
height: 44px;
|
|
81
|
+
padding: 0 12px;
|
|
79
82
|
color: var(--muted);
|
|
80
83
|
font-size: 12px;
|
|
81
84
|
display: flex;
|
|
82
85
|
align-items: center;
|
|
83
|
-
gap:
|
|
86
|
+
gap: 10px;
|
|
84
87
|
white-space: nowrap;
|
|
85
88
|
overflow: hidden;
|
|
86
89
|
}
|
|
87
90
|
.boardbar-paths {
|
|
88
91
|
min-width: 0;
|
|
89
92
|
display: flex;
|
|
90
|
-
align-items:
|
|
91
|
-
gap:
|
|
93
|
+
align-items: baseline;
|
|
94
|
+
gap: 12px;
|
|
92
95
|
overflow: hidden;
|
|
93
|
-
line-height:
|
|
96
|
+
line-height: 1.4;
|
|
97
|
+
}
|
|
98
|
+
.boardbar-title {
|
|
99
|
+
color: var(--ink);
|
|
100
|
+
font-size: 14px;
|
|
101
|
+
font-weight: 600;
|
|
94
102
|
}
|
|
103
|
+
.boardbar-paths code { border: 0; padding: 0; background: none; font-size: 11px; }
|
|
104
|
+
.board-location { margin-left: auto; font-size: 11px; overflow: hidden; text-overflow: ellipsis; }
|
|
95
105
|
.boardbar-paths span {
|
|
96
106
|
min-width: 0;
|
|
97
107
|
overflow: hidden;
|
|
@@ -102,8 +112,8 @@
|
|
|
102
112
|
display: inline-flex;
|
|
103
113
|
align-items: center;
|
|
104
114
|
justify-content: center;
|
|
105
|
-
width:
|
|
106
|
-
height:
|
|
115
|
+
width: 32px;
|
|
116
|
+
height: 32px;
|
|
107
117
|
border: 0;
|
|
108
118
|
border-radius: var(--radius-sm);
|
|
109
119
|
background: transparent;
|
|
@@ -118,13 +128,10 @@
|
|
|
118
128
|
outline: 2px solid var(--accent);
|
|
119
129
|
outline-offset: 1px;
|
|
120
130
|
}
|
|
121
|
-
.workspace-back
|
|
122
|
-
|
|
123
|
-
width:
|
|
124
|
-
height:
|
|
125
|
-
border-left: 2px solid currentColor;
|
|
126
|
-
border-bottom: 2px solid currentColor;
|
|
127
|
-
transform: rotate(45deg);
|
|
131
|
+
.workspace-back svg {
|
|
132
|
+
display: block;
|
|
133
|
+
width: 18px;
|
|
134
|
+
height: 18px;
|
|
128
135
|
}
|
|
129
136
|
.board {
|
|
130
137
|
border: 0;
|
|
@@ -138,9 +145,8 @@
|
|
|
138
145
|
width: 8px;
|
|
139
146
|
height: 100dvh;
|
|
140
147
|
border: 0;
|
|
141
|
-
border-left: 1px solid var(--line);
|
|
142
148
|
border-right: 1px solid var(--line);
|
|
143
|
-
background: var(--
|
|
149
|
+
background: var(--board-chrome);
|
|
144
150
|
cursor: col-resize;
|
|
145
151
|
padding: 0;
|
|
146
152
|
touch-action: none;
|
|
@@ -150,10 +156,10 @@
|
|
|
150
156
|
position: absolute;
|
|
151
157
|
left: 50%;
|
|
152
158
|
top: 50%;
|
|
153
|
-
width:
|
|
154
|
-
height:
|
|
159
|
+
width: 2px;
|
|
160
|
+
height: 28px;
|
|
155
161
|
border-radius: 999px;
|
|
156
|
-
background: var(--
|
|
162
|
+
background: var(--line-strong);
|
|
157
163
|
transform: translate(-50%, -50%);
|
|
158
164
|
}
|
|
159
165
|
.splitter:hover,
|
|
@@ -177,55 +183,107 @@
|
|
|
177
183
|
overflow: hidden;
|
|
178
184
|
display: grid;
|
|
179
185
|
grid-template-rows: auto minmax(0, 1fr) auto;
|
|
180
|
-
background: var(--panel);
|
|
186
|
+
background: var(--panel-raised);
|
|
181
187
|
}
|
|
182
188
|
.topbar {
|
|
183
189
|
min-width: 0;
|
|
184
|
-
|
|
190
|
+
height: 44px;
|
|
191
|
+
padding: 6px 20px;
|
|
185
192
|
border-bottom: 1px solid var(--line);
|
|
186
193
|
display: grid;
|
|
187
|
-
|
|
194
|
+
align-content: center;
|
|
188
195
|
}
|
|
189
196
|
.tabbar {
|
|
190
197
|
display: flex;
|
|
191
198
|
align-items: center;
|
|
192
|
-
gap:
|
|
199
|
+
gap: 8px;
|
|
193
200
|
min-width: 0;
|
|
194
201
|
}
|
|
202
|
+
.context-control {
|
|
203
|
+
position: relative;
|
|
204
|
+
display: flex;
|
|
205
|
+
flex: 0 0 auto;
|
|
206
|
+
}
|
|
195
207
|
.context-meter {
|
|
196
208
|
--context-remaining: 0%;
|
|
197
|
-
--context-meter-size:
|
|
209
|
+
--context-meter-size: 24px;
|
|
198
210
|
position: relative;
|
|
199
211
|
display: inline-grid;
|
|
200
212
|
place-items: center;
|
|
201
213
|
width: var(--context-meter-size);
|
|
202
214
|
height: var(--context-meter-size);
|
|
215
|
+
border: 0;
|
|
216
|
+
padding: 0;
|
|
203
217
|
border-radius: 999px;
|
|
204
|
-
background: conic-gradient(var(--accent) 0 var(--context-remaining), var(--line
|
|
205
|
-
color: var(--
|
|
218
|
+
background: conic-gradient(var(--accent) 0 var(--context-remaining), var(--line) 0 100%);
|
|
219
|
+
color: var(--ink);
|
|
206
220
|
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
207
|
-
font-size:
|
|
221
|
+
font-size: 10px;
|
|
222
|
+
font-weight: 600;
|
|
208
223
|
line-height: 1;
|
|
209
224
|
flex: 0 0 auto;
|
|
225
|
+
cursor: pointer;
|
|
210
226
|
}
|
|
211
|
-
.context-meter
|
|
212
|
-
|
|
227
|
+
.context-meter:focus-visible {
|
|
228
|
+
outline: 2px solid var(--accent);
|
|
229
|
+
outline-offset: 3px;
|
|
213
230
|
}
|
|
214
231
|
.context-meter::before {
|
|
215
232
|
content: "";
|
|
216
233
|
position: absolute;
|
|
217
|
-
inset:
|
|
234
|
+
inset: 3px;
|
|
218
235
|
border-radius: 999px;
|
|
219
|
-
background: var(--panel);
|
|
236
|
+
background: var(--panel-raised);
|
|
220
237
|
}
|
|
221
238
|
.context-meter-value {
|
|
222
239
|
position: relative;
|
|
223
240
|
z-index: 1;
|
|
224
241
|
}
|
|
242
|
+
.context-details {
|
|
243
|
+
position: absolute;
|
|
244
|
+
bottom: 100%;
|
|
245
|
+
left: -6px;
|
|
246
|
+
z-index: 5;
|
|
247
|
+
width: 264px;
|
|
248
|
+
max-width: calc(100vw - 48px);
|
|
249
|
+
padding-bottom: 10px;
|
|
250
|
+
color: var(--ink);
|
|
251
|
+
font-size: 12px;
|
|
252
|
+
line-height: 1.5;
|
|
253
|
+
}
|
|
254
|
+
.context-details-card {
|
|
255
|
+
padding: 12px 14px;
|
|
256
|
+
border: 1px solid var(--line);
|
|
257
|
+
border-radius: 10px;
|
|
258
|
+
background: var(--panel-raised);
|
|
259
|
+
box-shadow: 0 6px 24px rgba(27, 57, 72, 0.12);
|
|
260
|
+
}
|
|
261
|
+
.context-details-heading {
|
|
262
|
+
display: flex;
|
|
263
|
+
justify-content: space-between;
|
|
264
|
+
font-weight: 600;
|
|
265
|
+
}
|
|
266
|
+
.context-details-heading span {
|
|
267
|
+
color: var(--muted);
|
|
268
|
+
font-weight: 400;
|
|
269
|
+
}
|
|
270
|
+
.context-details dl {
|
|
271
|
+
display: grid;
|
|
272
|
+
grid-template-columns: 1fr auto;
|
|
273
|
+
gap: 4px 12px;
|
|
274
|
+
margin: 8px 0;
|
|
275
|
+
}
|
|
276
|
+
.context-details dt { color: var(--muted); }
|
|
277
|
+
.context-details dd {
|
|
278
|
+
margin: 0;
|
|
279
|
+
font-variant-numeric: tabular-nums;
|
|
280
|
+
text-align: right;
|
|
281
|
+
}
|
|
282
|
+
.context-details p { margin: 0; color: var(--muted); }
|
|
225
283
|
.tabs {
|
|
226
284
|
display: flex;
|
|
227
285
|
align-items: center;
|
|
228
|
-
gap:
|
|
286
|
+
gap: 4px;
|
|
229
287
|
min-width: 0;
|
|
230
288
|
overflow-x: auto;
|
|
231
289
|
scrollbar-width: thin;
|
|
@@ -237,25 +295,28 @@
|
|
|
237
295
|
gap: var(--space-2);
|
|
238
296
|
min-width: 88px;
|
|
239
297
|
max-width: 180px;
|
|
240
|
-
height:
|
|
241
|
-
border:
|
|
242
|
-
border-radius: var(--radius-
|
|
243
|
-
background: var(--panel-
|
|
244
|
-
color: var(--
|
|
245
|
-
padding: 0
|
|
246
|
-
font:
|
|
298
|
+
height: 30px;
|
|
299
|
+
border: 0;
|
|
300
|
+
border-radius: var(--radius-md);
|
|
301
|
+
background: var(--panel-deep);
|
|
302
|
+
color: var(--muted);
|
|
303
|
+
padding: 0 8px 0 12px;
|
|
304
|
+
font-size: 13px;
|
|
247
305
|
cursor: pointer;
|
|
248
306
|
user-select: none;
|
|
249
307
|
}
|
|
250
|
-
.tab:
|
|
251
|
-
|
|
308
|
+
.tab:hover {
|
|
309
|
+
background: var(--panel-hover);
|
|
310
|
+
color: var(--ink);
|
|
311
|
+
}
|
|
312
|
+
.tab-new:focus-visible {
|
|
252
313
|
outline: 2px solid var(--accent);
|
|
253
314
|
outline-offset: 1px;
|
|
254
315
|
}
|
|
255
316
|
.tab.active {
|
|
256
|
-
border-color: var(--accent);
|
|
257
317
|
background: var(--accent-soft);
|
|
258
|
-
|
|
318
|
+
color: var(--accent);
|
|
319
|
+
font-weight: 600;
|
|
259
320
|
}
|
|
260
321
|
.tab-status {
|
|
261
322
|
position: relative;
|
|
@@ -278,6 +339,10 @@
|
|
|
278
339
|
background: var(--accent);
|
|
279
340
|
animation: spinner-pulse 1.2s cubic-bezier(0.4, 0, 0.2, 1) infinite;
|
|
280
341
|
}
|
|
342
|
+
.tab-status[hidden],
|
|
343
|
+
.tab-label[hidden] {
|
|
344
|
+
display: none;
|
|
345
|
+
}
|
|
281
346
|
.tab-label {
|
|
282
347
|
min-width: 0;
|
|
283
348
|
overflow: hidden;
|
|
@@ -285,12 +350,35 @@
|
|
|
285
350
|
white-space: nowrap;
|
|
286
351
|
flex: 1;
|
|
287
352
|
text-align: left;
|
|
353
|
+
border: 0;
|
|
354
|
+
padding: 0;
|
|
355
|
+
background: transparent;
|
|
356
|
+
color: inherit;
|
|
357
|
+
font: inherit;
|
|
358
|
+
cursor: inherit;
|
|
359
|
+
}
|
|
360
|
+
.tab-label:focus-visible {
|
|
361
|
+
outline: 0;
|
|
362
|
+
text-decoration: underline;
|
|
363
|
+
text-underline-offset: 3px;
|
|
364
|
+
}
|
|
365
|
+
.tab-title-input {
|
|
366
|
+
min-width: 0;
|
|
367
|
+
width: 0;
|
|
368
|
+
flex: 1;
|
|
369
|
+
border: 0;
|
|
370
|
+
padding: 0;
|
|
371
|
+
outline: 0;
|
|
372
|
+
background: transparent;
|
|
373
|
+
color: inherit;
|
|
374
|
+
font: inherit;
|
|
375
|
+
user-select: text;
|
|
288
376
|
}
|
|
289
377
|
.tab-close,
|
|
290
378
|
.tab-new {
|
|
291
379
|
width: 31px;
|
|
292
380
|
height: 31px;
|
|
293
|
-
border:
|
|
381
|
+
border: 0;
|
|
294
382
|
border-radius: var(--radius-md);
|
|
295
383
|
background: var(--panel-raised);
|
|
296
384
|
color: var(--muted);
|
|
@@ -300,6 +388,8 @@
|
|
|
300
388
|
flex: 0 0 auto;
|
|
301
389
|
}
|
|
302
390
|
.tab-close {
|
|
391
|
+
display: grid;
|
|
392
|
+
place-items: center;
|
|
303
393
|
width: 18px;
|
|
304
394
|
height: 18px;
|
|
305
395
|
border: 0;
|
|
@@ -307,14 +397,23 @@
|
|
|
307
397
|
opacity: 0;
|
|
308
398
|
visibility: hidden;
|
|
309
399
|
pointer-events: none;
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
400
|
+
}
|
|
401
|
+
.tab-close svg {
|
|
402
|
+
display: block;
|
|
403
|
+
width: 12px;
|
|
404
|
+
height: 12px;
|
|
313
405
|
}
|
|
314
406
|
.tab-new {
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
407
|
+
display: grid;
|
|
408
|
+
place-items: center;
|
|
409
|
+
width: 30px;
|
|
410
|
+
height: 30px;
|
|
411
|
+
background: var(--panel-deep);
|
|
412
|
+
}
|
|
413
|
+
.tab-new svg {
|
|
414
|
+
display: block;
|
|
415
|
+
width: 18px;
|
|
416
|
+
height: 18px;
|
|
318
417
|
}
|
|
319
418
|
.tab.active .tab-close {
|
|
320
419
|
opacity: 0.85;
|
|
@@ -334,52 +433,70 @@
|
|
|
334
433
|
min-height: 0;
|
|
335
434
|
overflow-y: auto;
|
|
336
435
|
overflow-x: hidden;
|
|
337
|
-
padding:
|
|
436
|
+
padding: 16px 20px 20px;
|
|
338
437
|
display: flex;
|
|
339
438
|
flex-direction: column;
|
|
340
|
-
gap:
|
|
341
|
-
background: var(--
|
|
439
|
+
gap: 14px;
|
|
440
|
+
background: var(--panel-raised);
|
|
441
|
+
scrollbar-width: thin;
|
|
442
|
+
scrollbar-color: var(--line) transparent;
|
|
342
443
|
}
|
|
343
444
|
.entry {
|
|
344
445
|
min-width: 0;
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
446
|
+
flex: 0 0 auto;
|
|
447
|
+
line-height: 1.6;
|
|
448
|
+
}
|
|
449
|
+
.entry-label {
|
|
450
|
+
margin-bottom: 8px;
|
|
451
|
+
font-size: 12px;
|
|
452
|
+
font-weight: 600;
|
|
453
|
+
color: var(--muted);
|
|
350
454
|
}
|
|
351
455
|
.entry.user {
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
456
|
+
align-self: flex-end;
|
|
457
|
+
max-width: 92%;
|
|
458
|
+
padding: 7px 12px;
|
|
459
|
+
border-radius: 10px 10px 3px 10px;
|
|
460
|
+
background: var(--success-soft);
|
|
355
461
|
}
|
|
356
462
|
.entry.response,
|
|
357
463
|
.entry.assistant {
|
|
358
|
-
|
|
359
|
-
|
|
464
|
+
padding: 0;
|
|
465
|
+
}
|
|
466
|
+
.entry.thinking,
|
|
467
|
+
.entry.control,
|
|
468
|
+
.entry.tool {
|
|
469
|
+
border-left: 2px solid var(--line);
|
|
470
|
+
padding: 2px 0 2px 10px;
|
|
471
|
+
color: var(--muted);
|
|
472
|
+
font-size: 13px;
|
|
360
473
|
}
|
|
361
474
|
.entry.thinking {
|
|
362
|
-
border-color: var(--
|
|
363
|
-
|
|
364
|
-
color: var(--success);
|
|
365
|
-
animation: thinking-breathe 4.8s ease-in-out infinite;
|
|
475
|
+
border-left-color: var(--accent);
|
|
476
|
+
animation: activity-breathe 1.6s ease-in-out infinite;
|
|
366
477
|
}
|
|
367
478
|
.entry.control {
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
color: var(--info);
|
|
479
|
+
white-space: pre-wrap;
|
|
480
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
371
481
|
}
|
|
372
482
|
.entry.tool {
|
|
373
|
-
border-color: #d8cdf1;
|
|
374
|
-
background: var(--violet-soft);
|
|
375
|
-
color: var(--violet);
|
|
376
483
|
font-size: 12px;
|
|
377
484
|
}
|
|
378
485
|
.entry.error {
|
|
379
|
-
border-
|
|
486
|
+
border-left: 2px solid var(--danger);
|
|
487
|
+
border-radius: var(--radius-sm);
|
|
488
|
+
padding: 12px 16px;
|
|
380
489
|
background: var(--danger-soft);
|
|
381
490
|
color: var(--danger);
|
|
382
491
|
}
|
|
492
|
+
.empty-chat {
|
|
493
|
+
margin: auto 0;
|
|
494
|
+
padding: 36px 0 56px;
|
|
495
|
+
max-width: 360px;
|
|
496
|
+
}
|
|
497
|
+
.empty-chat .entry-label { color: var(--accent); }
|
|
498
|
+
.empty-chat h1 { margin: 12px 0; font-size: 28px; line-height: 1.25; font-weight: 600; letter-spacing: -0.6px; }
|
|
499
|
+
.empty-chat p { margin: 0; color: var(--muted); line-height: 1.75; }
|
|
383
500
|
.text {
|
|
384
501
|
white-space: pre-wrap;
|
|
385
502
|
overflow-wrap: anywhere;
|
|
@@ -389,7 +506,7 @@
|
|
|
389
506
|
overflow-wrap: anywhere;
|
|
390
507
|
}
|
|
391
508
|
.markdown p {
|
|
392
|
-
margin: 0 0
|
|
509
|
+
margin: 0 0 8px;
|
|
393
510
|
}
|
|
394
511
|
.markdown p:last-child,
|
|
395
512
|
.markdown ul:last-child,
|
|
@@ -400,17 +517,17 @@
|
|
|
400
517
|
}
|
|
401
518
|
.markdown ul,
|
|
402
519
|
.markdown ol {
|
|
403
|
-
margin: 0 0
|
|
520
|
+
margin: 0 0 8px 18px;
|
|
404
521
|
padding: 0;
|
|
405
522
|
}
|
|
406
523
|
.markdown li {
|
|
407
524
|
margin: 2px 0;
|
|
408
525
|
}
|
|
409
526
|
.markdown pre {
|
|
410
|
-
margin: 0 0
|
|
411
|
-
padding:
|
|
412
|
-
border:
|
|
413
|
-
border-radius: var(--radius-
|
|
527
|
+
margin: 0 0 10px;
|
|
528
|
+
padding: 10px 12px;
|
|
529
|
+
border: 0;
|
|
530
|
+
border-radius: var(--radius-md);
|
|
414
531
|
background: var(--code);
|
|
415
532
|
overflow-x: auto;
|
|
416
533
|
white-space: pre;
|
|
@@ -445,6 +562,13 @@
|
|
|
445
562
|
.markdown a:hover {
|
|
446
563
|
text-decoration: underline;
|
|
447
564
|
}
|
|
565
|
+
.markdown h1, .markdown h2, .markdown h3 { line-height: 1.4; margin: 16px 0 8px; font-weight: 600; }
|
|
566
|
+
.markdown h1 { font-size: 20px; }
|
|
567
|
+
.markdown h2 { font-size: 18px; }
|
|
568
|
+
.markdown h3 { font-size: 16px; }
|
|
569
|
+
.markdown > :first-child { margin-top: 0; }
|
|
570
|
+
.markdown blockquote { margin: 16px 0; padding-left: 16px; border-left: 2px solid var(--line); color: var(--muted); }
|
|
571
|
+
.markdown img { max-width: 100%; height: auto; border-radius: var(--radius-sm); }
|
|
448
572
|
.markdown .katex-display {
|
|
449
573
|
margin: 0 0 var(--space-4);
|
|
450
574
|
overflow-x: auto;
|
|
@@ -464,32 +588,175 @@
|
|
|
464
588
|
.markdown .katex-display:last-child {
|
|
465
589
|
margin-bottom: 0;
|
|
466
590
|
}
|
|
591
|
+
.markdown .mermaid-figure {
|
|
592
|
+
margin: 0 0 var(--space-4);
|
|
593
|
+
}
|
|
594
|
+
.markdown .mermaid-diagram {
|
|
595
|
+
overflow-x: auto;
|
|
596
|
+
padding: 8px 0;
|
|
597
|
+
color: var(--muted);
|
|
598
|
+
font-size: 13px;
|
|
599
|
+
}
|
|
600
|
+
.markdown .mermaid-diagram svg {
|
|
601
|
+
display: block;
|
|
602
|
+
height: auto;
|
|
603
|
+
}
|
|
604
|
+
.markdown .mermaid-source {
|
|
605
|
+
flex: 1;
|
|
606
|
+
min-width: 0;
|
|
607
|
+
color: var(--muted);
|
|
608
|
+
font-size: 12px;
|
|
609
|
+
}
|
|
610
|
+
.markdown .mermaid-source summary {
|
|
611
|
+
width: fit-content;
|
|
612
|
+
cursor: pointer;
|
|
613
|
+
}
|
|
614
|
+
.markdown .mermaid-source pre {
|
|
615
|
+
margin-top: 8px;
|
|
616
|
+
}
|
|
617
|
+
.mermaid-controls,
|
|
618
|
+
.mermaid-viewer-header {
|
|
619
|
+
display: flex;
|
|
620
|
+
align-items: flex-start;
|
|
621
|
+
gap: 12px;
|
|
622
|
+
}
|
|
623
|
+
.mermaid-controls button,
|
|
624
|
+
.mermaid-viewer button {
|
|
625
|
+
flex: 0 0 auto;
|
|
626
|
+
padding: 1px 5px;
|
|
627
|
+
border: 0;
|
|
628
|
+
border-radius: 4px;
|
|
629
|
+
background: transparent;
|
|
630
|
+
color: var(--muted);
|
|
631
|
+
font: inherit;
|
|
632
|
+
font-size: 12px;
|
|
633
|
+
cursor: pointer;
|
|
634
|
+
}
|
|
635
|
+
.mermaid-controls button:hover,
|
|
636
|
+
.mermaid-viewer button:hover {
|
|
637
|
+
background: var(--accent-soft);
|
|
638
|
+
color: var(--ink);
|
|
639
|
+
}
|
|
640
|
+
.mermaid-viewer {
|
|
641
|
+
width: min(1400px, calc(100vw - 32px));
|
|
642
|
+
max-height: calc(100dvh - 32px);
|
|
643
|
+
padding: 16px;
|
|
644
|
+
border: 1px solid var(--line);
|
|
645
|
+
border-radius: var(--radius-md);
|
|
646
|
+
background: var(--panel);
|
|
647
|
+
color: var(--ink);
|
|
648
|
+
}
|
|
649
|
+
.mermaid-viewer::backdrop {
|
|
650
|
+
background: rgba(23, 38, 48, 0.35);
|
|
651
|
+
}
|
|
652
|
+
.mermaid-viewer-header {
|
|
653
|
+
margin-bottom: 12px;
|
|
654
|
+
}
|
|
655
|
+
.mermaid-viewer-header strong {
|
|
656
|
+
flex: 1;
|
|
657
|
+
font-size: 14px;
|
|
658
|
+
}
|
|
659
|
+
.mermaid-viewer-content {
|
|
660
|
+
max-height: calc(100dvh - 110px);
|
|
661
|
+
overflow: auto;
|
|
662
|
+
}
|
|
663
|
+
.mermaid-viewer-content img {
|
|
664
|
+
display: block;
|
|
665
|
+
max-width: 100%;
|
|
666
|
+
height: auto;
|
|
667
|
+
margin: auto;
|
|
668
|
+
}
|
|
669
|
+
.mermaid-viewer-content.actual-size img {
|
|
670
|
+
max-width: none;
|
|
671
|
+
}
|
|
467
672
|
.composer {
|
|
468
673
|
position: relative;
|
|
469
674
|
min-width: 0;
|
|
470
|
-
padding:
|
|
675
|
+
padding: 8px 20px 10px;
|
|
471
676
|
display: grid;
|
|
472
|
-
gap:
|
|
677
|
+
gap: 6px;
|
|
473
678
|
align-content: end;
|
|
474
|
-
background: var(--panel);
|
|
679
|
+
background: var(--panel-raised);
|
|
680
|
+
}
|
|
681
|
+
.composer-surface {
|
|
682
|
+
border: 1px solid var(--line-strong);
|
|
683
|
+
border-radius: 10px;
|
|
684
|
+
background: var(--panel-raised);
|
|
685
|
+
box-shadow: 0 4px 16px rgba(27, 57, 72, 0.04);
|
|
686
|
+
transition: border-color 120ms, box-shadow 120ms;
|
|
687
|
+
}
|
|
688
|
+
.composer-surface:focus-within {
|
|
689
|
+
border-color: var(--accent);
|
|
690
|
+
box-shadow: 0 0 0 3px rgba(8, 127, 154, 0.08);
|
|
691
|
+
}
|
|
692
|
+
.composer-box {
|
|
693
|
+
min-width: 0;
|
|
694
|
+
padding: 4px 10px 4px 6px;
|
|
695
|
+
display: flex;
|
|
696
|
+
align-items: center;
|
|
697
|
+
gap: 10px;
|
|
698
|
+
}
|
|
699
|
+
.queue-hint {
|
|
700
|
+
display: flex;
|
|
701
|
+
align-items: center;
|
|
702
|
+
gap: var(--space-2);
|
|
703
|
+
min-width: 0;
|
|
704
|
+
max-width: 65%;
|
|
705
|
+
}
|
|
706
|
+
.queue-hint[hidden],
|
|
707
|
+
.queue-indicator[hidden] {
|
|
708
|
+
display: none;
|
|
709
|
+
}
|
|
710
|
+
.queue-indicator {
|
|
711
|
+
display: inline-flex;
|
|
712
|
+
align-items: center;
|
|
713
|
+
gap: var(--space-2);
|
|
714
|
+
min-width: 0;
|
|
715
|
+
max-width: 160px;
|
|
716
|
+
height: 22px;
|
|
717
|
+
padding: 0 7px;
|
|
718
|
+
border: 0;
|
|
719
|
+
border-radius: var(--radius-sm);
|
|
720
|
+
font-size: 12px;
|
|
721
|
+
color: var(--muted);
|
|
722
|
+
background: var(--panel-deep);
|
|
723
|
+
}
|
|
724
|
+
.queue-symbol {
|
|
725
|
+
flex-shrink: 0;
|
|
726
|
+
font-size: 12px;
|
|
727
|
+
line-height: 1;
|
|
728
|
+
}
|
|
729
|
+
.queue-symbol svg {
|
|
730
|
+
display: block;
|
|
731
|
+
width: 14px;
|
|
732
|
+
height: 14px;
|
|
733
|
+
}
|
|
734
|
+
.queue-preview {
|
|
735
|
+
min-width: 0;
|
|
736
|
+
overflow: hidden;
|
|
737
|
+
text-overflow: ellipsis;
|
|
738
|
+
white-space: nowrap;
|
|
475
739
|
}
|
|
476
740
|
textarea {
|
|
477
741
|
min-width: 0;
|
|
478
|
-
resize:
|
|
742
|
+
resize: none;
|
|
479
743
|
max-height: 35vh;
|
|
480
744
|
width: 100%;
|
|
481
|
-
|
|
482
|
-
border
|
|
483
|
-
padding:
|
|
484
|
-
|
|
745
|
+
min-height: 28px;
|
|
746
|
+
border: 0;
|
|
747
|
+
padding: 3px 0;
|
|
748
|
+
display: block;
|
|
749
|
+
background: transparent;
|
|
485
750
|
color: var(--ink);
|
|
486
751
|
font: inherit;
|
|
487
|
-
line-height:
|
|
752
|
+
line-height: 22px;
|
|
488
753
|
overflow-y: auto;
|
|
754
|
+
scrollbar-width: none;
|
|
755
|
+
}
|
|
756
|
+
textarea::-webkit-scrollbar {
|
|
757
|
+
display: none;
|
|
489
758
|
}
|
|
490
759
|
textarea:focus {
|
|
491
|
-
border-color: var(--accent);
|
|
492
|
-
box-shadow: 0 0 0 3px rgba(8, 127, 154, 0.18);
|
|
493
760
|
outline: 0;
|
|
494
761
|
}
|
|
495
762
|
.actions {
|
|
@@ -499,11 +766,15 @@
|
|
|
499
766
|
}
|
|
500
767
|
.spinner-wrap {
|
|
501
768
|
position: relative;
|
|
502
|
-
display:
|
|
769
|
+
display: flex;
|
|
503
770
|
align-items: center;
|
|
504
|
-
|
|
505
|
-
align-self: end;
|
|
771
|
+
gap: var(--space-3);
|
|
506
772
|
max-width: 100%;
|
|
773
|
+
min-width: 0;
|
|
774
|
+
padding: 4px 6px;
|
|
775
|
+
border-bottom: 1px solid var(--line);
|
|
776
|
+
border-radius: 9px 9px 0 0;
|
|
777
|
+
background: var(--panel);
|
|
507
778
|
}
|
|
508
779
|
.prompt-row {
|
|
509
780
|
display: flex;
|
|
@@ -512,21 +783,21 @@
|
|
|
512
783
|
min-width: 0;
|
|
513
784
|
}
|
|
514
785
|
.spinner {
|
|
515
|
-
--spinner-pill-height: 22px;
|
|
516
786
|
position: relative;
|
|
517
787
|
display: inline-flex;
|
|
518
788
|
align-items: center;
|
|
519
789
|
max-width: 100%;
|
|
520
|
-
|
|
790
|
+
min-width: 0;
|
|
791
|
+
height: 22px;
|
|
521
792
|
color: var(--muted);
|
|
522
793
|
font-size: 12px;
|
|
523
794
|
white-space: nowrap;
|
|
524
795
|
overflow: hidden;
|
|
525
796
|
text-overflow: ellipsis;
|
|
526
797
|
padding: var(--space-1) var(--space-4) var(--space-1) 18px;
|
|
527
|
-
border-radius:
|
|
528
|
-
background:
|
|
529
|
-
border: 1px solid
|
|
798
|
+
border-radius: var(--radius-sm);
|
|
799
|
+
background: transparent;
|
|
800
|
+
border: 1px solid transparent;
|
|
530
801
|
font: inherit;
|
|
531
802
|
font-size: 12px;
|
|
532
803
|
cursor: pointer;
|
|
@@ -534,16 +805,15 @@
|
|
|
534
805
|
.spinner:empty {
|
|
535
806
|
display: none;
|
|
536
807
|
}
|
|
537
|
-
.spinner
|
|
808
|
+
.spinner:hover {
|
|
809
|
+
background: var(--panel-deep);
|
|
810
|
+
}
|
|
811
|
+
.spinner-wrap:not(:has(.spinner:not(:empty), .queue-hint:not([hidden]))) {
|
|
538
812
|
display: none;
|
|
539
813
|
}
|
|
540
814
|
.spinner:focus-visible {
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
outline: 0;
|
|
544
|
-
}
|
|
545
|
-
.spinner-wrap:has(.spinner:not(:empty)) {
|
|
546
|
-
margin-bottom: var(--space-1);
|
|
815
|
+
outline: 2px solid var(--accent);
|
|
816
|
+
outline-offset: 1px;
|
|
547
817
|
}
|
|
548
818
|
.spinner:not(:empty)::before {
|
|
549
819
|
content: "";
|
|
@@ -558,12 +828,8 @@
|
|
|
558
828
|
animation: spinner-pulse 1.2s cubic-bezier(0.4, 0, 0.2, 1) infinite;
|
|
559
829
|
}
|
|
560
830
|
.spinner.notify-enabled {
|
|
561
|
-
color: var(--
|
|
562
|
-
background: var(--
|
|
563
|
-
border-color: var(--success-line);
|
|
564
|
-
box-shadow:
|
|
565
|
-
inset 0 1px 2px rgba(27, 57, 72, 0.08),
|
|
566
|
-
inset 0 0 0 1px rgba(36, 122, 82, 0.1);
|
|
831
|
+
color: var(--accent);
|
|
832
|
+
background: var(--accent-soft);
|
|
567
833
|
}
|
|
568
834
|
.toast {
|
|
569
835
|
position: absolute;
|
|
@@ -599,17 +865,9 @@
|
|
|
599
865
|
transform: scale(1.08);
|
|
600
866
|
}
|
|
601
867
|
}
|
|
602
|
-
@keyframes
|
|
603
|
-
0%, 100% {
|
|
604
|
-
|
|
605
|
-
box-shadow: var(--entry-shadow);
|
|
606
|
-
}
|
|
607
|
-
50% {
|
|
608
|
-
border-color: var(--success);
|
|
609
|
-
box-shadow:
|
|
610
|
-
0 0 0 2px rgba(36, 122, 82, 0.14),
|
|
611
|
-
0 2px 7px rgba(27, 57, 72, 0.08);
|
|
612
|
-
}
|
|
868
|
+
@keyframes activity-breathe {
|
|
869
|
+
0%, 100% { border-left-color: var(--line); }
|
|
870
|
+
50% { border-left-color: var(--accent); }
|
|
613
871
|
}
|
|
614
872
|
@media (prefers-reduced-motion: reduce) {
|
|
615
873
|
.entry.thinking,
|
|
@@ -631,21 +889,61 @@
|
|
|
631
889
|
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
632
890
|
font-size: 12px;
|
|
633
891
|
}
|
|
634
|
-
@media (max-width:
|
|
635
|
-
|
|
636
|
-
|
|
892
|
+
@media (max-width: 760px) {
|
|
893
|
+
.pane-toggle {
|
|
894
|
+
display: inline-grid;
|
|
895
|
+
place-items: center;
|
|
896
|
+
width: 30px;
|
|
897
|
+
height: 30px;
|
|
898
|
+
margin-left: auto;
|
|
899
|
+
padding: 0;
|
|
900
|
+
flex: 0 0 auto;
|
|
901
|
+
border: 0;
|
|
902
|
+
border-radius: var(--radius-sm);
|
|
903
|
+
background: transparent;
|
|
904
|
+
color: var(--muted);
|
|
905
|
+
cursor: pointer;
|
|
906
|
+
}
|
|
907
|
+
.pane-toggle:hover { background: var(--panel-deep); color: var(--ink); }
|
|
908
|
+
.pane-toggle:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
|
|
909
|
+
.pane-toggle svg { width: 18px; height: 18px; }
|
|
910
|
+
.workspace { height: 100dvh; display: block; }
|
|
911
|
+
.board-pane, .chat { height: 100%; }
|
|
912
|
+
.splitter { display: none; }
|
|
913
|
+
.workspace[data-mobile-pane="chat"] .board-pane,
|
|
914
|
+
.workspace[data-mobile-pane="board"] .chat { display: none; }
|
|
915
|
+
.topbar { height: 44px; padding: 6px 16px; }
|
|
916
|
+
.boardbar { padding: 0 16px; }
|
|
917
|
+
.board-location { display: none; }
|
|
918
|
+
.log { padding: 14px 16px; gap: 14px; }
|
|
919
|
+
.composer { padding: 8px 16px max(10px, env(safe-area-inset-bottom)); }
|
|
920
|
+
textarea { font-size: 16px; line-height: 24px; padding: 2px 0; }
|
|
921
|
+
.tab-title-input { font-size: 16px; }
|
|
922
|
+
.empty-chat h1 { font-size: 26px; }
|
|
637
923
|
}
|
|
638
924
|
</style>
|
|
639
925
|
</head>
|
|
640
926
|
<body>
|
|
641
|
-
<div class="workspace">
|
|
927
|
+
<div class="workspace" data-mobile-pane="chat">
|
|
642
928
|
<section class="board-pane" aria-label="workspace board">
|
|
643
929
|
<div class="boardbar">
|
|
644
|
-
<a class="workspace-back" href="/" title="Back to workspaces" aria-label="Back to workspaces"
|
|
930
|
+
<a class="workspace-back" href="/" title="Back to workspaces" aria-label="Back to workspaces">
|
|
931
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"
|
|
932
|
+
stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
933
|
+
<path d="M15 6l-6 6 6 6"/>
|
|
934
|
+
</svg>
|
|
935
|
+
</a>
|
|
645
936
|
<div class="boardbar-paths">
|
|
646
|
-
<span
|
|
647
|
-
<span
|
|
937
|
+
<span class="boardbar-title">__WORKSPACE_TITLE__</span>
|
|
938
|
+
<span title="__CWD_LABEL__"><code>__CWD_LABEL__</code></span>
|
|
648
939
|
</div>
|
|
940
|
+
<span class="board-location" title="__BOARD_LABEL__">Board</span>
|
|
941
|
+
<button class="pane-toggle" type="button" data-pane="chat" title="Show chat" aria-label="Show chat">
|
|
942
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"
|
|
943
|
+
stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
944
|
+
<path d="M5 4h14a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H9l-5 3v-3a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2Z"/>
|
|
945
|
+
</svg>
|
|
946
|
+
</button>
|
|
649
947
|
</div>
|
|
650
948
|
<iframe id="boardFrame" class="board" src="board" title="board"></iframe>
|
|
651
949
|
</section>
|
|
@@ -654,24 +952,75 @@
|
|
|
654
952
|
<div class="topbar">
|
|
655
953
|
<div class="tabbar">
|
|
656
954
|
<div id="tabs" class="tabs" role="tablist"></div>
|
|
657
|
-
<button id="newTab" class="tab-new" type="button" title="New session" aria-label="New session"
|
|
955
|
+
<button id="newTab" class="tab-new" type="button" title="New session" aria-label="New session">
|
|
956
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"
|
|
957
|
+
stroke-linecap="round" aria-hidden="true">
|
|
958
|
+
<path d="M12 5v14M5 12h14"/>
|
|
959
|
+
</svg>
|
|
960
|
+
</button>
|
|
961
|
+
<button class="pane-toggle" type="button" data-pane="board" title="Show board" aria-label="Show board">
|
|
962
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"
|
|
963
|
+
stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
964
|
+
<rect x="3" y="3" width="18" height="18" rx="3"/>
|
|
965
|
+
<path d="M3 9h18M9 9v12"/>
|
|
966
|
+
</svg>
|
|
967
|
+
</button>
|
|
658
968
|
</div>
|
|
659
969
|
</div>
|
|
660
|
-
<div id="log" class="log"></div>
|
|
970
|
+
<div id="log" class="log" aria-label="Conversation"></div>
|
|
661
971
|
<form id="composer" class="composer">
|
|
662
|
-
<div class="
|
|
663
|
-
<div
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
972
|
+
<div class="composer-surface">
|
|
973
|
+
<div class="spinner-wrap">
|
|
974
|
+
<div id="toast" class="toast" role="status" aria-live="polite"></div>
|
|
975
|
+
<div id="queueHint" class="queue-hint" role="status" aria-live="polite" hidden>
|
|
976
|
+
<span id="steerHint" class="queue-indicator" hidden>
|
|
977
|
+
<span class="queue-symbol" role="img" aria-label="Steer">↑</span>
|
|
978
|
+
<span class="queue-preview"></span>
|
|
979
|
+
</span>
|
|
980
|
+
<span id="enqueueHint" class="queue-indicator enqueue" hidden>
|
|
981
|
+
<span class="queue-symbol" role="img" aria-label="Queue">
|
|
982
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75"
|
|
983
|
+
stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
984
|
+
<circle cx="12" cy="12" r="9"/>
|
|
985
|
+
<path d="M12 7v5l3 2"/>
|
|
986
|
+
</svg>
|
|
987
|
+
</span>
|
|
988
|
+
<span class="queue-preview"></span>
|
|
989
|
+
</span>
|
|
990
|
+
</div>
|
|
991
|
+
<button id="spinner" class="spinner" type="button" role="checkbox" aria-checked="false" aria-live="polite"></button>
|
|
992
|
+
</div>
|
|
993
|
+
<div class="composer-box">
|
|
994
|
+
<div id="contextControl" class="context-control">
|
|
995
|
+
<button id="contextMeter" class="context-meter" type="button" aria-label="Context usage"
|
|
996
|
+
aria-expanded="false" aria-controls="contextDetails">
|
|
997
|
+
<span id="contextMeterValue" class="context-meter-value">—</span>
|
|
998
|
+
</button>
|
|
999
|
+
<div id="contextDetails" class="context-details" role="region" aria-label="Context usage" hidden>
|
|
1000
|
+
<div class="context-details-card">
|
|
1001
|
+
<div class="context-details-heading">Context usage <span>tokens</span></div>
|
|
1002
|
+
<dl>
|
|
1003
|
+
<dt>Current</dt><dd id="contextCurrent">—</dd>
|
|
1004
|
+
<dt>Compact limit</dt><dd id="contextCompact">—</dd>
|
|
1005
|
+
<dt>Max length</dt><dd id="contextMax">—</dd>
|
|
1006
|
+
</dl>
|
|
1007
|
+
<p id="contextNote"></p>
|
|
1008
|
+
</div>
|
|
1009
|
+
</div>
|
|
1010
|
+
</div>
|
|
1011
|
+
<textarea id="prompt" rows="1" aria-label="Message" title="Enter to send · Shift + Enter for a new line" placeholder="Message…"></textarea>
|
|
669
1012
|
</div>
|
|
670
|
-
<textarea id="prompt" rows="1" placeholder="Ask pycodex or type /help..."></textarea>
|
|
671
1013
|
</div>
|
|
672
1014
|
</form>
|
|
673
1015
|
</section>
|
|
674
1016
|
</div>
|
|
1017
|
+
<template id="emptyChat">
|
|
1018
|
+
<div class="empty-chat">
|
|
1019
|
+
<div class="entry-label">pycodex</div>
|
|
1020
|
+
<h1>What are we working on?</h1>
|
|
1021
|
+
<p>Explore an idea, work through a problem, or build something together. Your conversation starts here.</p>
|
|
1022
|
+
</div>
|
|
1023
|
+
</template>
|
|
675
1024
|
<script src="https://cdn.jsdelivr.net/npm/markdown-it@14.1.0/dist/markdown-it.min.js"></script>
|
|
676
1025
|
<script src="https://cdn.jsdelivr.net/npm/dompurify@3.2.6/dist/purify.min.js"></script>
|
|
677
1026
|
<script src="https://cdn.jsdelivr.net/npm/katex@0.16.22/dist/katex.min.js"></script>
|
|
@@ -679,6 +1028,9 @@
|
|
|
679
1028
|
<script>
|
|
680
1029
|
const log = document.getElementById("log");
|
|
681
1030
|
const spinner = document.getElementById("spinner");
|
|
1031
|
+
const queueHint = document.getElementById("queueHint");
|
|
1032
|
+
const steerHint = document.getElementById("steerHint");
|
|
1033
|
+
const enqueueHint = document.getElementById("enqueueHint");
|
|
682
1034
|
const toast = document.getElementById("toast");
|
|
683
1035
|
const prompt = document.getElementById("prompt");
|
|
684
1036
|
const form = document.getElementById("composer");
|
|
@@ -691,7 +1043,7 @@
|
|
|
691
1043
|
const newTabButton = document.getElementById("newTab");
|
|
692
1044
|
let pollTimer = null;
|
|
693
1045
|
let boardPollTimer = null;
|
|
694
|
-
let
|
|
1046
|
+
let sessionPoll = null;
|
|
695
1047
|
let boardPollInFlight = false;
|
|
696
1048
|
let lastBoardSignature = null;
|
|
697
1049
|
let lastRenderedSignature = "";
|
|
@@ -715,6 +1067,40 @@
|
|
|
715
1067
|
];
|
|
716
1068
|
const mathTokenPrefix = "\uE000math";
|
|
717
1069
|
const mathTokenSuffix = "\uE001";
|
|
1070
|
+
const renderMermaidSvg = (() => {
|
|
1071
|
+
let rendererPromise = null;
|
|
1072
|
+
let diagramId = 0;
|
|
1073
|
+
return async (source, figure) => {
|
|
1074
|
+
if (!rendererPromise) {
|
|
1075
|
+
rendererPromise = import(
|
|
1076
|
+
"https://cdn.jsdelivr.net/npm/mermaid@11.12.0/dist/mermaid.esm.min.mjs"
|
|
1077
|
+
).then(({default: renderer}) => {
|
|
1078
|
+
renderer.initialize({
|
|
1079
|
+
startOnLoad: false,
|
|
1080
|
+
securityLevel: "strict",
|
|
1081
|
+
suppressErrorRendering: true,
|
|
1082
|
+
htmlLabels: false,
|
|
1083
|
+
flowchart: {htmlLabels: false},
|
|
1084
|
+
theme: "neutral",
|
|
1085
|
+
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
|
|
1086
|
+
});
|
|
1087
|
+
return renderer;
|
|
1088
|
+
});
|
|
1089
|
+
}
|
|
1090
|
+
const renderer = await rendererPromise;
|
|
1091
|
+
if (!figure.isConnected) return null;
|
|
1092
|
+
const measure = document.createElement("div");
|
|
1093
|
+
measure.style.cssText =
|
|
1094
|
+
"position:fixed;left:0;top:0;visibility:hidden;pointer-events:none";
|
|
1095
|
+
document.body.appendChild(measure);
|
|
1096
|
+
try {
|
|
1097
|
+
diagramId += 1;
|
|
1098
|
+
return await renderer.render("workspace-diagram-" + diagramId, source, measure);
|
|
1099
|
+
} finally {
|
|
1100
|
+
measure.remove();
|
|
1101
|
+
}
|
|
1102
|
+
};
|
|
1103
|
+
})();
|
|
718
1104
|
|
|
719
1105
|
function relativeUrl(path) {
|
|
720
1106
|
return new URL(path, window.location.href).toString();
|
|
@@ -739,9 +1125,9 @@
|
|
|
739
1125
|
entry.className = "entry " + (cls || role);
|
|
740
1126
|
const body = document.createElement("div");
|
|
741
1127
|
body.className = "text markdown";
|
|
742
|
-
renderMarkdownInto(body, text || "");
|
|
743
1128
|
entry.appendChild(body);
|
|
744
1129
|
log.appendChild(entry);
|
|
1130
|
+
renderMarkdownInto(body, text || "");
|
|
745
1131
|
if (stickToBottom) scrollToBottom();
|
|
746
1132
|
return body;
|
|
747
1133
|
}
|
|
@@ -762,6 +1148,102 @@
|
|
|
762
1148
|
anchor.target = "_blank";
|
|
763
1149
|
anchor.rel = "noopener noreferrer";
|
|
764
1150
|
});
|
|
1151
|
+
renderMermaidInto(root);
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
async function renderMermaidInto(root) {
|
|
1155
|
+
for (const code of root.querySelectorAll("pre > code.language-mermaid")) {
|
|
1156
|
+
if (!code.isConnected) return;
|
|
1157
|
+
const source = code.textContent;
|
|
1158
|
+
const pre = code.parentElement;
|
|
1159
|
+
const figure = document.createElement("figure");
|
|
1160
|
+
figure.className = "mermaid-figure";
|
|
1161
|
+
const diagram = document.createElement("div");
|
|
1162
|
+
diagram.className = "mermaid-diagram";
|
|
1163
|
+
diagram.textContent = "Rendering diagram…";
|
|
1164
|
+
const details = document.createElement("details");
|
|
1165
|
+
details.className = "mermaid-source";
|
|
1166
|
+
details.open = true;
|
|
1167
|
+
const summary = document.createElement("summary");
|
|
1168
|
+
summary.textContent = "Diagram source";
|
|
1169
|
+
details.appendChild(summary);
|
|
1170
|
+
pre.replaceWith(figure);
|
|
1171
|
+
details.appendChild(pre);
|
|
1172
|
+
const controls = document.createElement("div");
|
|
1173
|
+
controls.className = "mermaid-controls";
|
|
1174
|
+
controls.appendChild(details);
|
|
1175
|
+
figure.append(diagram, controls);
|
|
1176
|
+
try {
|
|
1177
|
+
const result = await renderMermaidSvg(source, figure);
|
|
1178
|
+
if (!result || !figure.isConnected) return;
|
|
1179
|
+
const stickToBottom = isNearBottom();
|
|
1180
|
+
diagram.innerHTML = window.DOMPurify.sanitize(result.svg, {
|
|
1181
|
+
USE_PROFILES: {svg: true, svgFilters: true},
|
|
1182
|
+
});
|
|
1183
|
+
const svg = diagram.querySelector("svg");
|
|
1184
|
+
svg.style.width = svg.viewBox.baseVal.width + "px";
|
|
1185
|
+
svg.style.maxWidth = "100%";
|
|
1186
|
+
const expand = document.createElement("button");
|
|
1187
|
+
expand.type = "button";
|
|
1188
|
+
expand.textContent = "Expand";
|
|
1189
|
+
expand.addEventListener("click", () => showMermaidDiagram(svg));
|
|
1190
|
+
controls.appendChild(expand);
|
|
1191
|
+
details.open = false;
|
|
1192
|
+
if (stickToBottom) scrollToBottom();
|
|
1193
|
+
} catch (error) {
|
|
1194
|
+
if (!figure.isConnected) return;
|
|
1195
|
+
diagram.textContent = "Diagram could not be rendered. Source is shown below.";
|
|
1196
|
+
details.open = true;
|
|
1197
|
+
console.warn("Mermaid diagram rendering failed", error);
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
function showMermaidDiagram(svg) {
|
|
1203
|
+
const copy = svg.cloneNode(true);
|
|
1204
|
+
copy.style.removeProperty("width");
|
|
1205
|
+
copy.style.removeProperty("max-width");
|
|
1206
|
+
copy.setAttribute("width", svg.viewBox.baseVal.width);
|
|
1207
|
+
copy.setAttribute("height", svg.viewBox.baseVal.height);
|
|
1208
|
+
const imageUrl = URL.createObjectURL(new Blob(
|
|
1209
|
+
[new XMLSerializer().serializeToString(copy)],
|
|
1210
|
+
{type: "image/svg+xml"},
|
|
1211
|
+
));
|
|
1212
|
+
const dialog = document.createElement("dialog");
|
|
1213
|
+
dialog.className = "mermaid-viewer";
|
|
1214
|
+
dialog.setAttribute("aria-label", "Expanded diagram");
|
|
1215
|
+
const header = document.createElement("div");
|
|
1216
|
+
header.className = "mermaid-viewer-header";
|
|
1217
|
+
const title = document.createElement("strong");
|
|
1218
|
+
title.textContent = "Diagram";
|
|
1219
|
+
const zoom = document.createElement("button");
|
|
1220
|
+
zoom.type = "button";
|
|
1221
|
+
zoom.textContent = "Actual size";
|
|
1222
|
+
const close = document.createElement("button");
|
|
1223
|
+
close.type = "button";
|
|
1224
|
+
close.textContent = "Close";
|
|
1225
|
+
const content = document.createElement("div");
|
|
1226
|
+
content.className = "mermaid-viewer-content";
|
|
1227
|
+
const image = document.createElement("img");
|
|
1228
|
+
image.src = imageUrl;
|
|
1229
|
+
image.alt = "Diagram";
|
|
1230
|
+
content.appendChild(image);
|
|
1231
|
+
header.append(title, zoom, close);
|
|
1232
|
+
dialog.append(header, content);
|
|
1233
|
+
zoom.addEventListener("click", () => {
|
|
1234
|
+
const actualSize = content.classList.toggle("actual-size");
|
|
1235
|
+
zoom.textContent = actualSize ? "Fit width" : "Actual size";
|
|
1236
|
+
});
|
|
1237
|
+
close.addEventListener("click", () => dialog.close());
|
|
1238
|
+
dialog.addEventListener("click", event => {
|
|
1239
|
+
if (event.target === dialog) dialog.close();
|
|
1240
|
+
});
|
|
1241
|
+
dialog.addEventListener("close", () => {
|
|
1242
|
+
URL.revokeObjectURL(imageUrl);
|
|
1243
|
+
dialog.remove();
|
|
1244
|
+
}, {once: true});
|
|
1245
|
+
document.body.appendChild(dialog);
|
|
1246
|
+
dialog.showModal();
|
|
765
1247
|
}
|
|
766
1248
|
|
|
767
1249
|
function isEscapedAt(source, index) {
|
|
@@ -1168,6 +1650,7 @@
|
|
|
1168
1650
|
draft: "",
|
|
1169
1651
|
notifyWhenDone: false,
|
|
1170
1652
|
wasRunning: false,
|
|
1653
|
+
inputRequest: null,
|
|
1171
1654
|
});
|
|
1172
1655
|
}
|
|
1173
1656
|
return sessionState.get(key);
|
|
@@ -1203,88 +1686,201 @@
|
|
|
1203
1686
|
activeSessionId = sessions[0].id || "";
|
|
1204
1687
|
}
|
|
1205
1688
|
renderTabs();
|
|
1689
|
+
updatePromptPlaceholder();
|
|
1206
1690
|
updateContextMeter();
|
|
1207
1691
|
updateSpinnerNotifyUi();
|
|
1208
1692
|
}
|
|
1209
1693
|
|
|
1210
1694
|
function renderTabs() {
|
|
1211
|
-
|
|
1695
|
+
const existingTabs = new Map(
|
|
1696
|
+
Array.from(tabsEl.children, tab => [tab.dataset.sessionId, tab])
|
|
1697
|
+
);
|
|
1212
1698
|
sessions.forEach((session, index) => {
|
|
1213
1699
|
const sessionId = session.id || "";
|
|
1214
|
-
|
|
1700
|
+
let tab = existingTabs.get(sessionId);
|
|
1701
|
+
if (!tab) {
|
|
1702
|
+
tab = document.createElement("div");
|
|
1703
|
+
tab.dataset.sessionId = sessionId;
|
|
1704
|
+
tab.setAttribute("role", "tab");
|
|
1705
|
+
tab.addEventListener("click", () => switchSession(sessionId));
|
|
1706
|
+
|
|
1707
|
+
const status = document.createElement("span");
|
|
1708
|
+
status.className = "tab-status";
|
|
1709
|
+
status.setAttribute("aria-hidden", "true");
|
|
1710
|
+
tab.appendChild(status);
|
|
1711
|
+
|
|
1712
|
+
const label = document.createElement("button");
|
|
1713
|
+
label.type = "button";
|
|
1714
|
+
label.className = "tab-label";
|
|
1715
|
+
label.addEventListener("click", event => {
|
|
1716
|
+
if (sessionId !== activeSessionId) return;
|
|
1717
|
+
event.stopPropagation();
|
|
1718
|
+
editSessionTitle(tab, sessionId);
|
|
1719
|
+
});
|
|
1720
|
+
tab.appendChild(label);
|
|
1721
|
+
|
|
1722
|
+
const close = document.createElement("button");
|
|
1723
|
+
close.type = "button";
|
|
1724
|
+
close.className = "tab-close";
|
|
1725
|
+
close.title = "Close session";
|
|
1726
|
+
close.setAttribute("aria-label", "Close session");
|
|
1727
|
+
close.innerHTML = '<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" ' +
|
|
1728
|
+
'stroke-width="1.5" stroke-linecap="round" aria-hidden="true">' +
|
|
1729
|
+
'<path d="M4 4l8 8M12 4l-8 8"/></svg>';
|
|
1730
|
+
close.addEventListener("click", event => {
|
|
1731
|
+
event.stopPropagation();
|
|
1732
|
+
closeSession(sessionId);
|
|
1733
|
+
});
|
|
1734
|
+
tab.appendChild(close);
|
|
1735
|
+
}
|
|
1736
|
+
existingTabs.delete(sessionId);
|
|
1215
1737
|
tab.className =
|
|
1216
1738
|
"tab" +
|
|
1217
1739
|
(sessionId === activeSessionId ? " active" : "") +
|
|
1218
1740
|
(session.running ? " running" : "");
|
|
1219
1741
|
tab.title = session.title || `Session ${index + 1}`;
|
|
1220
|
-
tab.setAttribute("role", "tab");
|
|
1221
1742
|
tab.setAttribute("aria-selected", sessionId === activeSessionId ? "true" : "false");
|
|
1222
|
-
|
|
1223
|
-
tab.
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
tab.
|
|
1743
|
+
tab.setAttribute("aria-busy", String(Boolean(session.running)));
|
|
1744
|
+
tab.querySelector(".tab-status").hidden = !session.running;
|
|
1745
|
+
const label = tab.querySelector(".tab-label");
|
|
1746
|
+
label.textContent = session.title || `Session ${index + 1}`;
|
|
1747
|
+
label.title = sessionId === activeSessionId ? "Click to rename session" : tab.title;
|
|
1748
|
+
tab.querySelector(".tab-close").disabled = sessions.length <= 1;
|
|
1749
|
+
if (tabsEl.children[index] !== tab) {
|
|
1750
|
+
tabsEl.insertBefore(tab, tabsEl.children[index] || null);
|
|
1230
1751
|
}
|
|
1752
|
+
});
|
|
1753
|
+
existingTabs.forEach(tab => tab.remove());
|
|
1754
|
+
}
|
|
1231
1755
|
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1756
|
+
function editSessionTitle(tab, sessionId) {
|
|
1757
|
+
const session = sessionById(sessionId);
|
|
1758
|
+
const label = tab.querySelector(".tab-label");
|
|
1759
|
+
const input = document.createElement("input");
|
|
1760
|
+
input.type = "text";
|
|
1761
|
+
input.className = "tab-title-input";
|
|
1762
|
+
input.setAttribute("aria-label", "Session title");
|
|
1763
|
+
input.value = session.title || "";
|
|
1764
|
+
input.placeholder = label.textContent;
|
|
1765
|
+
tab.style.width = `${tab.getBoundingClientRect().width}px`;
|
|
1766
|
+
label.hidden = true;
|
|
1767
|
+
tab.insertBefore(input, label);
|
|
1768
|
+
let finished = false;
|
|
1769
|
+
const finish = save => {
|
|
1770
|
+
if (finished) return;
|
|
1771
|
+
finished = true;
|
|
1772
|
+
const title = input.value.trim();
|
|
1773
|
+
input.remove();
|
|
1774
|
+
label.hidden = false;
|
|
1775
|
+
tab.style.width = "";
|
|
1776
|
+
if (save && title && title !== session.title) {
|
|
1777
|
+
renameSession(sessionId, title);
|
|
1778
|
+
}
|
|
1779
|
+
};
|
|
1780
|
+
input.addEventListener("click", event => event.stopPropagation());
|
|
1781
|
+
input.addEventListener("keydown", event => {
|
|
1782
|
+
if (event.isComposing) return;
|
|
1783
|
+
if (event.key === "Enter" || event.key === "Escape") {
|
|
1784
|
+
event.preventDefault();
|
|
1785
|
+
finish(event.key === "Enter");
|
|
1786
|
+
label.focus();
|
|
1787
|
+
}
|
|
1250
1788
|
});
|
|
1789
|
+
input.addEventListener("blur", () => finish(true));
|
|
1790
|
+
input.focus();
|
|
1791
|
+
input.select();
|
|
1251
1792
|
}
|
|
1252
1793
|
|
|
1253
|
-
function
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1794
|
+
async function renameSession(sessionId, title) {
|
|
1795
|
+
try {
|
|
1796
|
+
const response = await fetch(relativeUrl("api/session/message"), {
|
|
1797
|
+
method: "POST",
|
|
1798
|
+
headers: {"Content-Type": "application/json"},
|
|
1799
|
+
body: JSON.stringify({prompt: `/title ${title}`, sender: "web", session_id: sessionId}),
|
|
1800
|
+
});
|
|
1801
|
+
const payload = await response.json();
|
|
1802
|
+
if (!response.ok || !payload.ok) {
|
|
1803
|
+
throw new Error(payload.error || `HTTP ${response.status}`);
|
|
1261
1804
|
}
|
|
1805
|
+
const session = sessionById(sessionId);
|
|
1806
|
+
if (session) {
|
|
1807
|
+
session.title = payload.snapshot.title;
|
|
1808
|
+
renderTabs();
|
|
1809
|
+
}
|
|
1810
|
+
} catch (error) {
|
|
1811
|
+
addEntry("error", `Could not rename session: ${error.message}`, "error");
|
|
1262
1812
|
}
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
}
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
if (
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
}
|
|
1280
|
-
|
|
1281
|
-
contextMeter.
|
|
1282
|
-
|
|
1813
|
+
}
|
|
1814
|
+
|
|
1815
|
+
function updatePromptPlaceholder(snapshot = null) {
|
|
1816
|
+
const session = snapshot || sessionById(activeSessionId);
|
|
1817
|
+
const label = session && session.model ? `Message to ${session.model}` : "Message";
|
|
1818
|
+
prompt.placeholder = `${label}…`;
|
|
1819
|
+
prompt.setAttribute("aria-label", label);
|
|
1820
|
+
}
|
|
1821
|
+
|
|
1822
|
+
function updateContextMeter(snapshot = null) {
|
|
1823
|
+
const session = snapshot || sessionById(activeSessionId);
|
|
1824
|
+
if (!session) return;
|
|
1825
|
+
const percent = session.context_remaining_percent;
|
|
1826
|
+
const compact = session.auto_compact_token_limit;
|
|
1827
|
+
const formatTokens = value => value == null ? "—" : value.toLocaleString("en-US");
|
|
1828
|
+
const remainingLabel = compact == null ? "context remaining" : "remaining until compact";
|
|
1829
|
+
contextMeter.style.setProperty("--context-remaining", `${percent == null ? 0 : percent}%`);
|
|
1830
|
+
contextMeterValue.textContent = percent == null ? "—" : percent >= 100 ? "" : String(percent);
|
|
1831
|
+
contextMeter.setAttribute("aria-label", percent == null
|
|
1832
|
+
? "Context usage details"
|
|
1833
|
+
: `${percent}% ${remainingLabel}. Context usage details`);
|
|
1834
|
+
document.getElementById("contextCurrent").textContent = formatTokens(session.usage_tokens);
|
|
1835
|
+
document.getElementById("contextCompact").textContent = compact == null ? "Off" : formatTokens(compact);
|
|
1836
|
+
document.getElementById("contextMax").textContent = formatTokens(session.max_context_window);
|
|
1837
|
+
const note = compact == null
|
|
1838
|
+
? "Auto-compact is off. The ring shows remaining context."
|
|
1839
|
+
: "The ring reaches 0% at the compact limit. Compaction runs before the next model request.";
|
|
1840
|
+
document.getElementById("contextNote").textContent = session.usage_tokens == null
|
|
1841
|
+
? `Waiting for current usage. ${note}`
|
|
1842
|
+
: note;
|
|
1843
|
+
}
|
|
1844
|
+
|
|
1845
|
+
function setupContextDetails() {
|
|
1846
|
+
const control = document.getElementById("contextControl");
|
|
1847
|
+
const details = document.getElementById("contextDetails");
|
|
1848
|
+
let pinned = false;
|
|
1849
|
+
const show = visible => {
|
|
1850
|
+
details.hidden = !visible;
|
|
1851
|
+
contextMeter.setAttribute("aria-expanded", String(visible));
|
|
1852
|
+
};
|
|
1853
|
+
const close = () => {
|
|
1854
|
+
pinned = false;
|
|
1855
|
+
show(false);
|
|
1856
|
+
};
|
|
1857
|
+
control.addEventListener("pointerenter", event => {
|
|
1858
|
+
if (event.pointerType !== "touch") show(true);
|
|
1859
|
+
});
|
|
1860
|
+
control.addEventListener("pointerleave", () => {
|
|
1861
|
+
if (!pinned) show(false);
|
|
1862
|
+
});
|
|
1863
|
+
contextMeter.addEventListener("focus", () => show(true));
|
|
1864
|
+
control.addEventListener("focusout", event => {
|
|
1865
|
+
if (!pinned && !control.contains(event.relatedTarget)) show(false);
|
|
1866
|
+
});
|
|
1867
|
+
contextMeter.addEventListener("click", () => {
|
|
1868
|
+
pinned = !pinned;
|
|
1869
|
+
show(pinned);
|
|
1870
|
+
});
|
|
1871
|
+
document.addEventListener("pointerdown", event => {
|
|
1872
|
+
if (!control.contains(event.target)) close();
|
|
1873
|
+
});
|
|
1874
|
+
document.addEventListener("keydown", event => {
|
|
1875
|
+
if (event.key === "Escape") close();
|
|
1876
|
+
});
|
|
1877
|
+
tabsEl.addEventListener("click", close);
|
|
1878
|
+
newTabButton.addEventListener("click", close);
|
|
1283
1879
|
}
|
|
1284
1880
|
|
|
1285
1881
|
function messageSignature(snapshot) {
|
|
1286
1882
|
const turns = (snapshot && snapshot.turns) || [];
|
|
1287
|
-
return JSON.stringify(
|
|
1883
|
+
return JSON.stringify([
|
|
1288
1884
|
turns.map((item) => [
|
|
1289
1885
|
item.submission_id || "",
|
|
1290
1886
|
item.turn_id || "",
|
|
@@ -1295,21 +1891,26 @@
|
|
|
1295
1891
|
item.error || "",
|
|
1296
1892
|
item.kind || "",
|
|
1297
1893
|
]),
|
|
1298
|
-
|
|
1894
|
+
snapshot.input_request || null,
|
|
1895
|
+
]);
|
|
1299
1896
|
}
|
|
1300
1897
|
|
|
1301
1898
|
async function pollSession() {
|
|
1302
|
-
if (pollInFlight) return;
|
|
1303
|
-
pollInFlight = true;
|
|
1304
1899
|
const requestedSessionId = activeSessionId;
|
|
1900
|
+
if (sessionPoll) {
|
|
1901
|
+
if (sessionPoll.sessionId === requestedSessionId) return;
|
|
1902
|
+
sessionPoll.controller.abort();
|
|
1903
|
+
}
|
|
1904
|
+
const request = {sessionId: requestedSessionId, controller: new AbortController()};
|
|
1905
|
+
sessionPoll = request;
|
|
1305
1906
|
try {
|
|
1306
1907
|
const path = requestedSessionId
|
|
1307
1908
|
? `api/session?session_id=${encodeURIComponent(requestedSessionId)}`
|
|
1308
1909
|
: "api/session";
|
|
1309
|
-
const response = await fetch(relativeUrl(path));
|
|
1910
|
+
const response = await fetch(relativeUrl(path), {signal: request.controller.signal});
|
|
1310
1911
|
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
1311
1912
|
const payload = await response.json();
|
|
1312
|
-
if (
|
|
1913
|
+
if (sessionPoll !== request || requestedSessionId !== activeSessionId) {
|
|
1313
1914
|
return;
|
|
1314
1915
|
}
|
|
1315
1916
|
if (payload.session_id && payload.session_id !== activeSessionId) {
|
|
@@ -1318,9 +1919,11 @@
|
|
|
1318
1919
|
updateSessions(payload.sessions || []);
|
|
1319
1920
|
renderSnapshot(payload.snapshot);
|
|
1320
1921
|
} catch (error) {
|
|
1321
|
-
|
|
1922
|
+
if (sessionPoll === request && requestedSessionId === activeSessionId) {
|
|
1923
|
+
setSpinner(`poll error: ${error.message}`);
|
|
1924
|
+
}
|
|
1322
1925
|
} finally {
|
|
1323
|
-
|
|
1926
|
+
if (sessionPoll === request) sessionPoll = null;
|
|
1324
1927
|
}
|
|
1325
1928
|
}
|
|
1326
1929
|
|
|
@@ -1370,9 +1973,25 @@
|
|
|
1370
1973
|
boardPollTimer = setInterval(pollBoard, 2000);
|
|
1371
1974
|
}
|
|
1372
1975
|
|
|
1976
|
+
function renderQueueHint(inputs) {
|
|
1977
|
+
queueHint.hidden = inputs.length === 0;
|
|
1978
|
+
for (const [kind, label, badge] of [
|
|
1979
|
+
["steer", "[soon]", steerHint],
|
|
1980
|
+
["enqueue", "[on done]", enqueueHint],
|
|
1981
|
+
]) {
|
|
1982
|
+
const text = inputs.filter(item => item.queue === kind).map(item => item.prompt).join("\n");
|
|
1983
|
+
badge.hidden = !text;
|
|
1984
|
+
badge.querySelector(".queue-preview").textContent = text;
|
|
1985
|
+
badge.title = label + "\n" + text;
|
|
1986
|
+
}
|
|
1987
|
+
}
|
|
1988
|
+
|
|
1373
1989
|
function renderSnapshot(snapshot) {
|
|
1374
1990
|
if (!snapshot) return;
|
|
1991
|
+
updatePromptPlaceholder(snapshot);
|
|
1375
1992
|
const state = stateForSession(activeSessionId);
|
|
1993
|
+
state.inputRequest = snapshot.input_request || null;
|
|
1994
|
+
renderQueueHint(snapshot.queued_inputs);
|
|
1376
1995
|
const signature = messageSignature(snapshot);
|
|
1377
1996
|
if (signature === state.lastRenderedSignature) {
|
|
1378
1997
|
setSpinner(snapshot.spinner || "");
|
|
@@ -1389,6 +2008,11 @@
|
|
|
1389
2008
|
const turns = Array.isArray(snapshot.turns) ? snapshot.turns : [];
|
|
1390
2009
|
if (turns.length) {
|
|
1391
2010
|
turns.forEach((turn) => renderTurn(turn, shouldStick));
|
|
2011
|
+
} else if (!state.inputRequest) {
|
|
2012
|
+
log.appendChild(document.getElementById("emptyChat").content.cloneNode(true));
|
|
2013
|
+
}
|
|
2014
|
+
if (state.inputRequest) {
|
|
2015
|
+
addEntry("input requested", state.inputRequest.text, "control", shouldStick);
|
|
1392
2016
|
}
|
|
1393
2017
|
setSpinner(snapshot.spinner || "");
|
|
1394
2018
|
updateContextMeter(snapshot);
|
|
@@ -1423,12 +2047,11 @@
|
|
|
1423
2047
|
);
|
|
1424
2048
|
}
|
|
1425
2049
|
if (turn.response) {
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
);
|
|
2050
|
+
if (isControl) {
|
|
2051
|
+
addEntry("result", turn.response, "control", shouldStick);
|
|
2052
|
+
} else {
|
|
2053
|
+
addMarkdownEntry("response", turn.response, "response", shouldStick);
|
|
2054
|
+
}
|
|
1432
2055
|
}
|
|
1433
2056
|
if (turn.thinking) addEntry("thinking", turn.thinking || "", "thinking", shouldStick);
|
|
1434
2057
|
if (turn.error) addEntry("error", turn.error || "", "error", shouldStick);
|
|
@@ -1460,6 +2083,8 @@
|
|
|
1460
2083
|
saveActiveSessionUiState();
|
|
1461
2084
|
activeSessionId = sessionId;
|
|
1462
2085
|
renderTabs();
|
|
2086
|
+
updatePromptPlaceholder();
|
|
2087
|
+
updateContextMeter();
|
|
1463
2088
|
const state = stateForSession(sessionId);
|
|
1464
2089
|
state.lastRenderedSignature = "";
|
|
1465
2090
|
lastRenderedSignature = "";
|
|
@@ -1467,6 +2092,7 @@
|
|
|
1467
2092
|
resizePrompt();
|
|
1468
2093
|
suppressScrollSave = true;
|
|
1469
2094
|
log.textContent = "";
|
|
2095
|
+
renderQueueHint([]);
|
|
1470
2096
|
suppressScrollSave = false;
|
|
1471
2097
|
setSpinner("", {suppressDoneNotification: true});
|
|
1472
2098
|
restoreScrollTop = state.scrollTop;
|
|
@@ -1505,26 +2131,17 @@
|
|
|
1505
2131
|
addEntry("error", payload.error || `HTTP ${response.status}`, "error");
|
|
1506
2132
|
return;
|
|
1507
2133
|
}
|
|
1508
|
-
sessionState.delete(sessionId);
|
|
1509
2134
|
updateSessions(payload.sessions || []);
|
|
1510
2135
|
if (activeSessionId === sessionId) {
|
|
1511
|
-
|
|
1512
|
-
lastRenderedSignature = "";
|
|
1513
|
-
prompt.value = "";
|
|
1514
|
-
resizePrompt();
|
|
1515
|
-
suppressScrollSave = true;
|
|
1516
|
-
log.textContent = "";
|
|
1517
|
-
suppressScrollSave = false;
|
|
1518
|
-
await pollSession();
|
|
1519
|
-
} else {
|
|
1520
|
-
renderTabs();
|
|
2136
|
+
await switchSession(sessions[0].id);
|
|
1521
2137
|
}
|
|
2138
|
+
sessionState.delete(sessionId);
|
|
1522
2139
|
}
|
|
1523
2140
|
|
|
1524
2141
|
form.addEventListener("submit", (event) => {
|
|
1525
2142
|
event.preventDefault();
|
|
1526
2143
|
const text = prompt.value.trim();
|
|
1527
|
-
if (!text) return;
|
|
2144
|
+
if (!text && !stateForSession(activeSessionId).inputRequest) return;
|
|
1528
2145
|
prompt.value = "";
|
|
1529
2146
|
resizePrompt();
|
|
1530
2147
|
stateForSession(activeSessionId).draft = "";
|
|
@@ -1565,6 +2182,12 @@
|
|
|
1565
2182
|
splitter.addEventListener("dblclick", centerSplit);
|
|
1566
2183
|
spinner.addEventListener("click", toggleSpinnerNotification);
|
|
1567
2184
|
newTabButton.addEventListener("click", createSession);
|
|
2185
|
+
document.querySelectorAll("[data-pane]").forEach((button) => {
|
|
2186
|
+
button.addEventListener("click", () => {
|
|
2187
|
+
workspace.dataset.mobilePane = button.dataset.pane;
|
|
2188
|
+
if (button.dataset.pane === "chat") resizePrompt();
|
|
2189
|
+
});
|
|
2190
|
+
});
|
|
1568
2191
|
window.addEventListener("resize", () => {
|
|
1569
2192
|
const current = Number(window.localStorage.getItem("pycodex.workspace.chatWidth") || 0);
|
|
1570
2193
|
if (current > 0) setChatWidth(current);
|
|
@@ -1577,6 +2200,7 @@
|
|
|
1577
2200
|
});
|
|
1578
2201
|
|
|
1579
2202
|
loadChatWidth();
|
|
2203
|
+
setupContextDetails();
|
|
1580
2204
|
resizePrompt();
|
|
1581
2205
|
startPolling();
|
|
1582
2206
|
startBoardPolling();
|