python-codex 0.2.7__py3-none-any.whl → 0.3.1__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 +321 -883
- 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.1.dist-info/METADATA +743 -0
- python_codex-0.3.1.dist-info/RECORD +91 -0
- responses_server/__init__.py +1 -5
- responses_server/__main__.py +0 -1
- responses_server/app.py +34 -33
- 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 +209 -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 +465 -377
- workspace_server/owned_process.py +27 -0
- workspace_server/workspace.html +854 -228
- workspace_server/workspaces.html +94 -95
- workspace_server/workspaces.py +141 -84
- 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.1.dist-info}/WHEEL +0 -0
- {python_codex-0.2.7.dist-info → python_codex-0.3.1.dist-info}/entry_points.txt +0 -0
- {python_codex-0.2.7.dist-info → python_codex-0.3.1.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");
|
|
@@ -689,9 +1041,10 @@
|
|
|
689
1041
|
const contextMeter = document.getElementById("contextMeter");
|
|
690
1042
|
const contextMeterValue = document.getElementById("contextMeterValue");
|
|
691
1043
|
const newTabButton = document.getElementById("newTab");
|
|
1044
|
+
const workspaceTitle = document.title;
|
|
692
1045
|
let pollTimer = null;
|
|
693
1046
|
let boardPollTimer = null;
|
|
694
|
-
let
|
|
1047
|
+
let sessionPoll = null;
|
|
695
1048
|
let boardPollInFlight = false;
|
|
696
1049
|
let lastBoardSignature = null;
|
|
697
1050
|
let lastRenderedSignature = "";
|
|
@@ -715,6 +1068,40 @@
|
|
|
715
1068
|
];
|
|
716
1069
|
const mathTokenPrefix = "\uE000math";
|
|
717
1070
|
const mathTokenSuffix = "\uE001";
|
|
1071
|
+
const renderMermaidSvg = (() => {
|
|
1072
|
+
let rendererPromise = null;
|
|
1073
|
+
let diagramId = 0;
|
|
1074
|
+
return async (source, figure) => {
|
|
1075
|
+
if (!rendererPromise) {
|
|
1076
|
+
rendererPromise = import(
|
|
1077
|
+
"https://cdn.jsdelivr.net/npm/mermaid@11.12.0/dist/mermaid.esm.min.mjs"
|
|
1078
|
+
).then(({default: renderer}) => {
|
|
1079
|
+
renderer.initialize({
|
|
1080
|
+
startOnLoad: false,
|
|
1081
|
+
securityLevel: "strict",
|
|
1082
|
+
suppressErrorRendering: true,
|
|
1083
|
+
htmlLabels: false,
|
|
1084
|
+
flowchart: {htmlLabels: false},
|
|
1085
|
+
theme: "neutral",
|
|
1086
|
+
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
|
|
1087
|
+
});
|
|
1088
|
+
return renderer;
|
|
1089
|
+
});
|
|
1090
|
+
}
|
|
1091
|
+
const renderer = await rendererPromise;
|
|
1092
|
+
if (!figure.isConnected) return null;
|
|
1093
|
+
const measure = document.createElement("div");
|
|
1094
|
+
measure.style.cssText =
|
|
1095
|
+
"position:fixed;left:0;top:0;visibility:hidden;pointer-events:none";
|
|
1096
|
+
document.body.appendChild(measure);
|
|
1097
|
+
try {
|
|
1098
|
+
diagramId += 1;
|
|
1099
|
+
return await renderer.render("workspace-diagram-" + diagramId, source, measure);
|
|
1100
|
+
} finally {
|
|
1101
|
+
measure.remove();
|
|
1102
|
+
}
|
|
1103
|
+
};
|
|
1104
|
+
})();
|
|
718
1105
|
|
|
719
1106
|
function relativeUrl(path) {
|
|
720
1107
|
return new URL(path, window.location.href).toString();
|
|
@@ -739,9 +1126,9 @@
|
|
|
739
1126
|
entry.className = "entry " + (cls || role);
|
|
740
1127
|
const body = document.createElement("div");
|
|
741
1128
|
body.className = "text markdown";
|
|
742
|
-
renderMarkdownInto(body, text || "");
|
|
743
1129
|
entry.appendChild(body);
|
|
744
1130
|
log.appendChild(entry);
|
|
1131
|
+
renderMarkdownInto(body, text || "");
|
|
745
1132
|
if (stickToBottom) scrollToBottom();
|
|
746
1133
|
return body;
|
|
747
1134
|
}
|
|
@@ -762,6 +1149,102 @@
|
|
|
762
1149
|
anchor.target = "_blank";
|
|
763
1150
|
anchor.rel = "noopener noreferrer";
|
|
764
1151
|
});
|
|
1152
|
+
renderMermaidInto(root);
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
async function renderMermaidInto(root) {
|
|
1156
|
+
for (const code of root.querySelectorAll("pre > code.language-mermaid")) {
|
|
1157
|
+
if (!code.isConnected) return;
|
|
1158
|
+
const source = code.textContent;
|
|
1159
|
+
const pre = code.parentElement;
|
|
1160
|
+
const figure = document.createElement("figure");
|
|
1161
|
+
figure.className = "mermaid-figure";
|
|
1162
|
+
const diagram = document.createElement("div");
|
|
1163
|
+
diagram.className = "mermaid-diagram";
|
|
1164
|
+
diagram.textContent = "Rendering diagram…";
|
|
1165
|
+
const details = document.createElement("details");
|
|
1166
|
+
details.className = "mermaid-source";
|
|
1167
|
+
details.open = true;
|
|
1168
|
+
const summary = document.createElement("summary");
|
|
1169
|
+
summary.textContent = "Diagram source";
|
|
1170
|
+
details.appendChild(summary);
|
|
1171
|
+
pre.replaceWith(figure);
|
|
1172
|
+
details.appendChild(pre);
|
|
1173
|
+
const controls = document.createElement("div");
|
|
1174
|
+
controls.className = "mermaid-controls";
|
|
1175
|
+
controls.appendChild(details);
|
|
1176
|
+
figure.append(diagram, controls);
|
|
1177
|
+
try {
|
|
1178
|
+
const result = await renderMermaidSvg(source, figure);
|
|
1179
|
+
if (!result || !figure.isConnected) return;
|
|
1180
|
+
const stickToBottom = isNearBottom();
|
|
1181
|
+
diagram.innerHTML = window.DOMPurify.sanitize(result.svg, {
|
|
1182
|
+
USE_PROFILES: {svg: true, svgFilters: true},
|
|
1183
|
+
});
|
|
1184
|
+
const svg = diagram.querySelector("svg");
|
|
1185
|
+
svg.style.width = svg.viewBox.baseVal.width + "px";
|
|
1186
|
+
svg.style.maxWidth = "100%";
|
|
1187
|
+
const expand = document.createElement("button");
|
|
1188
|
+
expand.type = "button";
|
|
1189
|
+
expand.textContent = "Expand";
|
|
1190
|
+
expand.addEventListener("click", () => showMermaidDiagram(svg));
|
|
1191
|
+
controls.appendChild(expand);
|
|
1192
|
+
details.open = false;
|
|
1193
|
+
if (stickToBottom) scrollToBottom();
|
|
1194
|
+
} catch (error) {
|
|
1195
|
+
if (!figure.isConnected) return;
|
|
1196
|
+
diagram.textContent = "Diagram could not be rendered. Source is shown below.";
|
|
1197
|
+
details.open = true;
|
|
1198
|
+
console.warn("Mermaid diagram rendering failed", error);
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
function showMermaidDiagram(svg) {
|
|
1204
|
+
const copy = svg.cloneNode(true);
|
|
1205
|
+
copy.style.removeProperty("width");
|
|
1206
|
+
copy.style.removeProperty("max-width");
|
|
1207
|
+
copy.setAttribute("width", svg.viewBox.baseVal.width);
|
|
1208
|
+
copy.setAttribute("height", svg.viewBox.baseVal.height);
|
|
1209
|
+
const imageUrl = URL.createObjectURL(new Blob(
|
|
1210
|
+
[new XMLSerializer().serializeToString(copy)],
|
|
1211
|
+
{type: "image/svg+xml"},
|
|
1212
|
+
));
|
|
1213
|
+
const dialog = document.createElement("dialog");
|
|
1214
|
+
dialog.className = "mermaid-viewer";
|
|
1215
|
+
dialog.setAttribute("aria-label", "Expanded diagram");
|
|
1216
|
+
const header = document.createElement("div");
|
|
1217
|
+
header.className = "mermaid-viewer-header";
|
|
1218
|
+
const title = document.createElement("strong");
|
|
1219
|
+
title.textContent = "Diagram";
|
|
1220
|
+
const zoom = document.createElement("button");
|
|
1221
|
+
zoom.type = "button";
|
|
1222
|
+
zoom.textContent = "Actual size";
|
|
1223
|
+
const close = document.createElement("button");
|
|
1224
|
+
close.type = "button";
|
|
1225
|
+
close.textContent = "Close";
|
|
1226
|
+
const content = document.createElement("div");
|
|
1227
|
+
content.className = "mermaid-viewer-content";
|
|
1228
|
+
const image = document.createElement("img");
|
|
1229
|
+
image.src = imageUrl;
|
|
1230
|
+
image.alt = "Diagram";
|
|
1231
|
+
content.appendChild(image);
|
|
1232
|
+
header.append(title, zoom, close);
|
|
1233
|
+
dialog.append(header, content);
|
|
1234
|
+
zoom.addEventListener("click", () => {
|
|
1235
|
+
const actualSize = content.classList.toggle("actual-size");
|
|
1236
|
+
zoom.textContent = actualSize ? "Fit width" : "Actual size";
|
|
1237
|
+
});
|
|
1238
|
+
close.addEventListener("click", () => dialog.close());
|
|
1239
|
+
dialog.addEventListener("click", event => {
|
|
1240
|
+
if (event.target === dialog) dialog.close();
|
|
1241
|
+
});
|
|
1242
|
+
dialog.addEventListener("close", () => {
|
|
1243
|
+
URL.revokeObjectURL(imageUrl);
|
|
1244
|
+
dialog.remove();
|
|
1245
|
+
}, {once: true});
|
|
1246
|
+
document.body.appendChild(dialog);
|
|
1247
|
+
dialog.showModal();
|
|
765
1248
|
}
|
|
766
1249
|
|
|
767
1250
|
function isEscapedAt(source, index) {
|
|
@@ -1168,6 +1651,7 @@
|
|
|
1168
1651
|
draft: "",
|
|
1169
1652
|
notifyWhenDone: false,
|
|
1170
1653
|
wasRunning: false,
|
|
1654
|
+
inputRequest: null,
|
|
1171
1655
|
});
|
|
1172
1656
|
}
|
|
1173
1657
|
return sessionState.get(key);
|
|
@@ -1199,92 +1683,206 @@
|
|
|
1199
1683
|
state.wasRunning = isRunning;
|
|
1200
1684
|
});
|
|
1201
1685
|
sessions = incomingSessions;
|
|
1686
|
+
document.title = (sessions.some(session => session.running) ? "● " : "") + workspaceTitle;
|
|
1202
1687
|
if (!activeSessionId && sessions.length) {
|
|
1203
1688
|
activeSessionId = sessions[0].id || "";
|
|
1204
1689
|
}
|
|
1205
1690
|
renderTabs();
|
|
1691
|
+
updatePromptPlaceholder();
|
|
1206
1692
|
updateContextMeter();
|
|
1207
1693
|
updateSpinnerNotifyUi();
|
|
1208
1694
|
}
|
|
1209
1695
|
|
|
1210
1696
|
function renderTabs() {
|
|
1211
|
-
|
|
1697
|
+
const existingTabs = new Map(
|
|
1698
|
+
Array.from(tabsEl.children, tab => [tab.dataset.sessionId, tab])
|
|
1699
|
+
);
|
|
1212
1700
|
sessions.forEach((session, index) => {
|
|
1213
1701
|
const sessionId = session.id || "";
|
|
1214
|
-
|
|
1702
|
+
let tab = existingTabs.get(sessionId);
|
|
1703
|
+
if (!tab) {
|
|
1704
|
+
tab = document.createElement("div");
|
|
1705
|
+
tab.dataset.sessionId = sessionId;
|
|
1706
|
+
tab.setAttribute("role", "tab");
|
|
1707
|
+
tab.addEventListener("click", () => switchSession(sessionId));
|
|
1708
|
+
|
|
1709
|
+
const status = document.createElement("span");
|
|
1710
|
+
status.className = "tab-status";
|
|
1711
|
+
status.setAttribute("aria-hidden", "true");
|
|
1712
|
+
tab.appendChild(status);
|
|
1713
|
+
|
|
1714
|
+
const label = document.createElement("button");
|
|
1715
|
+
label.type = "button";
|
|
1716
|
+
label.className = "tab-label";
|
|
1717
|
+
label.addEventListener("click", event => {
|
|
1718
|
+
if (sessionId !== activeSessionId) return;
|
|
1719
|
+
event.stopPropagation();
|
|
1720
|
+
editSessionTitle(tab, sessionId);
|
|
1721
|
+
});
|
|
1722
|
+
tab.appendChild(label);
|
|
1723
|
+
|
|
1724
|
+
const close = document.createElement("button");
|
|
1725
|
+
close.type = "button";
|
|
1726
|
+
close.className = "tab-close";
|
|
1727
|
+
close.title = "Close session";
|
|
1728
|
+
close.setAttribute("aria-label", "Close session");
|
|
1729
|
+
close.innerHTML = '<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" ' +
|
|
1730
|
+
'stroke-width="1.5" stroke-linecap="round" aria-hidden="true">' +
|
|
1731
|
+
'<path d="M4 4l8 8M12 4l-8 8"/></svg>';
|
|
1732
|
+
close.addEventListener("click", event => {
|
|
1733
|
+
event.stopPropagation();
|
|
1734
|
+
closeSession(sessionId);
|
|
1735
|
+
});
|
|
1736
|
+
tab.appendChild(close);
|
|
1737
|
+
}
|
|
1738
|
+
existingTabs.delete(sessionId);
|
|
1215
1739
|
tab.className =
|
|
1216
1740
|
"tab" +
|
|
1217
1741
|
(sessionId === activeSessionId ? " active" : "") +
|
|
1218
1742
|
(session.running ? " running" : "");
|
|
1219
1743
|
tab.title = session.title || `Session ${index + 1}`;
|
|
1220
|
-
tab.setAttribute("role", "tab");
|
|
1221
1744
|
tab.setAttribute("aria-selected", sessionId === activeSessionId ? "true" : "false");
|
|
1222
|
-
|
|
1223
|
-
tab.
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
tab.
|
|
1745
|
+
tab.setAttribute("aria-busy", String(Boolean(session.running)));
|
|
1746
|
+
tab.querySelector(".tab-status").hidden = !session.running;
|
|
1747
|
+
const label = tab.querySelector(".tab-label");
|
|
1748
|
+
label.textContent = session.title || `Session ${index + 1}`;
|
|
1749
|
+
label.title = sessionId === activeSessionId ? "Click to rename session" : tab.title;
|
|
1750
|
+
tab.querySelector(".tab-close").disabled = sessions.length <= 1;
|
|
1751
|
+
if (tabsEl.children[index] !== tab) {
|
|
1752
|
+
tabsEl.insertBefore(tab, tabsEl.children[index] || null);
|
|
1230
1753
|
}
|
|
1754
|
+
});
|
|
1755
|
+
existingTabs.forEach(tab => tab.remove());
|
|
1756
|
+
}
|
|
1231
1757
|
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1758
|
+
function editSessionTitle(tab, sessionId) {
|
|
1759
|
+
const session = sessionById(sessionId);
|
|
1760
|
+
const label = tab.querySelector(".tab-label");
|
|
1761
|
+
const input = document.createElement("input");
|
|
1762
|
+
input.type = "text";
|
|
1763
|
+
input.className = "tab-title-input";
|
|
1764
|
+
input.setAttribute("aria-label", "Session title");
|
|
1765
|
+
input.value = session.title || "";
|
|
1766
|
+
input.placeholder = label.textContent;
|
|
1767
|
+
tab.style.width = `${tab.getBoundingClientRect().width}px`;
|
|
1768
|
+
label.hidden = true;
|
|
1769
|
+
tab.insertBefore(input, label);
|
|
1770
|
+
let finished = false;
|
|
1771
|
+
const finish = save => {
|
|
1772
|
+
if (finished) return;
|
|
1773
|
+
finished = true;
|
|
1774
|
+
const title = input.value.trim();
|
|
1775
|
+
input.remove();
|
|
1776
|
+
label.hidden = false;
|
|
1777
|
+
tab.style.width = "";
|
|
1778
|
+
if (save && title && title !== session.title) {
|
|
1779
|
+
renameSession(sessionId, title);
|
|
1780
|
+
}
|
|
1781
|
+
};
|
|
1782
|
+
input.addEventListener("click", event => event.stopPropagation());
|
|
1783
|
+
input.addEventListener("keydown", event => {
|
|
1784
|
+
if (event.isComposing) return;
|
|
1785
|
+
if (event.key === "Enter" || event.key === "Escape") {
|
|
1786
|
+
event.preventDefault();
|
|
1787
|
+
finish(event.key === "Enter");
|
|
1788
|
+
label.focus();
|
|
1789
|
+
}
|
|
1250
1790
|
});
|
|
1791
|
+
input.addEventListener("blur", () => finish(true));
|
|
1792
|
+
input.focus();
|
|
1793
|
+
input.select();
|
|
1251
1794
|
}
|
|
1252
1795
|
|
|
1253
|
-
function
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1796
|
+
async function renameSession(sessionId, title) {
|
|
1797
|
+
try {
|
|
1798
|
+
const response = await fetch(relativeUrl("api/session/message"), {
|
|
1799
|
+
method: "POST",
|
|
1800
|
+
headers: {"Content-Type": "application/json"},
|
|
1801
|
+
body: JSON.stringify({prompt: `/title ${title}`, sender: "web", session_id: sessionId}),
|
|
1802
|
+
});
|
|
1803
|
+
const payload = await response.json();
|
|
1804
|
+
if (!response.ok || !payload.ok) {
|
|
1805
|
+
throw new Error(payload.error || `HTTP ${response.status}`);
|
|
1261
1806
|
}
|
|
1807
|
+
const session = sessionById(sessionId);
|
|
1808
|
+
if (session) {
|
|
1809
|
+
session.title = payload.snapshot.title;
|
|
1810
|
+
renderTabs();
|
|
1811
|
+
}
|
|
1812
|
+
} catch (error) {
|
|
1813
|
+
addEntry("error", `Could not rename session: ${error.message}`, "error");
|
|
1262
1814
|
}
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
}
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
if (
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
}
|
|
1280
|
-
|
|
1281
|
-
contextMeter.
|
|
1282
|
-
|
|
1815
|
+
}
|
|
1816
|
+
|
|
1817
|
+
function updatePromptPlaceholder(snapshot = null) {
|
|
1818
|
+
const session = snapshot || sessionById(activeSessionId);
|
|
1819
|
+
const label = session && session.model ? `Message to ${session.model}` : "Message";
|
|
1820
|
+
prompt.placeholder = `${label}…`;
|
|
1821
|
+
prompt.setAttribute("aria-label", label);
|
|
1822
|
+
}
|
|
1823
|
+
|
|
1824
|
+
function updateContextMeter(snapshot = null) {
|
|
1825
|
+
const session = snapshot || sessionById(activeSessionId);
|
|
1826
|
+
if (!session) return;
|
|
1827
|
+
const percent = session.context_remaining_percent;
|
|
1828
|
+
const compact = session.auto_compact_token_limit;
|
|
1829
|
+
const formatTokens = value => value == null ? "—" : value.toLocaleString("en-US");
|
|
1830
|
+
const remainingLabel = compact == null ? "context remaining" : "remaining until compact";
|
|
1831
|
+
contextMeter.style.setProperty("--context-remaining", `${percent == null ? 0 : percent}%`);
|
|
1832
|
+
contextMeterValue.textContent = percent == null ? "—" : percent >= 100 ? "" : String(percent);
|
|
1833
|
+
contextMeter.setAttribute("aria-label", percent == null
|
|
1834
|
+
? "Context usage details"
|
|
1835
|
+
: `${percent}% ${remainingLabel}. Context usage details`);
|
|
1836
|
+
document.getElementById("contextCurrent").textContent = formatTokens(session.usage_tokens);
|
|
1837
|
+
document.getElementById("contextCompact").textContent = compact == null ? "Off" : formatTokens(compact);
|
|
1838
|
+
document.getElementById("contextMax").textContent = formatTokens(session.max_context_window);
|
|
1839
|
+
const note = compact == null
|
|
1840
|
+
? "Auto-compact is off. The ring shows remaining context."
|
|
1841
|
+
: "The ring reaches 0% at the compact limit. Compaction runs before the next model request.";
|
|
1842
|
+
document.getElementById("contextNote").textContent = session.usage_tokens == null
|
|
1843
|
+
? `Waiting for current usage. ${note}`
|
|
1844
|
+
: note;
|
|
1845
|
+
}
|
|
1846
|
+
|
|
1847
|
+
function setupContextDetails() {
|
|
1848
|
+
const control = document.getElementById("contextControl");
|
|
1849
|
+
const details = document.getElementById("contextDetails");
|
|
1850
|
+
let pinned = false;
|
|
1851
|
+
const show = visible => {
|
|
1852
|
+
details.hidden = !visible;
|
|
1853
|
+
contextMeter.setAttribute("aria-expanded", String(visible));
|
|
1854
|
+
};
|
|
1855
|
+
const close = () => {
|
|
1856
|
+
pinned = false;
|
|
1857
|
+
show(false);
|
|
1858
|
+
};
|
|
1859
|
+
control.addEventListener("pointerenter", event => {
|
|
1860
|
+
if (event.pointerType !== "touch") show(true);
|
|
1861
|
+
});
|
|
1862
|
+
control.addEventListener("pointerleave", () => {
|
|
1863
|
+
if (!pinned) show(false);
|
|
1864
|
+
});
|
|
1865
|
+
contextMeter.addEventListener("focus", () => show(true));
|
|
1866
|
+
control.addEventListener("focusout", event => {
|
|
1867
|
+
if (!pinned && !control.contains(event.relatedTarget)) show(false);
|
|
1868
|
+
});
|
|
1869
|
+
contextMeter.addEventListener("click", () => {
|
|
1870
|
+
pinned = !pinned;
|
|
1871
|
+
show(pinned);
|
|
1872
|
+
});
|
|
1873
|
+
document.addEventListener("pointerdown", event => {
|
|
1874
|
+
if (!control.contains(event.target)) close();
|
|
1875
|
+
});
|
|
1876
|
+
document.addEventListener("keydown", event => {
|
|
1877
|
+
if (event.key === "Escape") close();
|
|
1878
|
+
});
|
|
1879
|
+
tabsEl.addEventListener("click", close);
|
|
1880
|
+
newTabButton.addEventListener("click", close);
|
|
1283
1881
|
}
|
|
1284
1882
|
|
|
1285
1883
|
function messageSignature(snapshot) {
|
|
1286
1884
|
const turns = (snapshot && snapshot.turns) || [];
|
|
1287
|
-
return JSON.stringify(
|
|
1885
|
+
return JSON.stringify([
|
|
1288
1886
|
turns.map((item) => [
|
|
1289
1887
|
item.submission_id || "",
|
|
1290
1888
|
item.turn_id || "",
|
|
@@ -1295,21 +1893,26 @@
|
|
|
1295
1893
|
item.error || "",
|
|
1296
1894
|
item.kind || "",
|
|
1297
1895
|
]),
|
|
1298
|
-
|
|
1896
|
+
snapshot.input_request || null,
|
|
1897
|
+
]);
|
|
1299
1898
|
}
|
|
1300
1899
|
|
|
1301
1900
|
async function pollSession() {
|
|
1302
|
-
if (pollInFlight) return;
|
|
1303
|
-
pollInFlight = true;
|
|
1304
1901
|
const requestedSessionId = activeSessionId;
|
|
1902
|
+
if (sessionPoll) {
|
|
1903
|
+
if (sessionPoll.sessionId === requestedSessionId) return;
|
|
1904
|
+
sessionPoll.controller.abort();
|
|
1905
|
+
}
|
|
1906
|
+
const request = {sessionId: requestedSessionId, controller: new AbortController()};
|
|
1907
|
+
sessionPoll = request;
|
|
1305
1908
|
try {
|
|
1306
1909
|
const path = requestedSessionId
|
|
1307
1910
|
? `api/session?session_id=${encodeURIComponent(requestedSessionId)}`
|
|
1308
1911
|
: "api/session";
|
|
1309
|
-
const response = await fetch(relativeUrl(path));
|
|
1912
|
+
const response = await fetch(relativeUrl(path), {signal: request.controller.signal});
|
|
1310
1913
|
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
1311
1914
|
const payload = await response.json();
|
|
1312
|
-
if (
|
|
1915
|
+
if (sessionPoll !== request || requestedSessionId !== activeSessionId) {
|
|
1313
1916
|
return;
|
|
1314
1917
|
}
|
|
1315
1918
|
if (payload.session_id && payload.session_id !== activeSessionId) {
|
|
@@ -1318,9 +1921,11 @@
|
|
|
1318
1921
|
updateSessions(payload.sessions || []);
|
|
1319
1922
|
renderSnapshot(payload.snapshot);
|
|
1320
1923
|
} catch (error) {
|
|
1321
|
-
|
|
1924
|
+
if (sessionPoll === request && requestedSessionId === activeSessionId) {
|
|
1925
|
+
setSpinner(`poll error: ${error.message}`);
|
|
1926
|
+
}
|
|
1322
1927
|
} finally {
|
|
1323
|
-
|
|
1928
|
+
if (sessionPoll === request) sessionPoll = null;
|
|
1324
1929
|
}
|
|
1325
1930
|
}
|
|
1326
1931
|
|
|
@@ -1370,9 +1975,25 @@
|
|
|
1370
1975
|
boardPollTimer = setInterval(pollBoard, 2000);
|
|
1371
1976
|
}
|
|
1372
1977
|
|
|
1978
|
+
function renderQueueHint(inputs) {
|
|
1979
|
+
queueHint.hidden = inputs.length === 0;
|
|
1980
|
+
for (const [kind, label, badge] of [
|
|
1981
|
+
["steer", "[soon]", steerHint],
|
|
1982
|
+
["enqueue", "[on done]", enqueueHint],
|
|
1983
|
+
]) {
|
|
1984
|
+
const text = inputs.filter(item => item.queue === kind).map(item => item.prompt).join("\n");
|
|
1985
|
+
badge.hidden = !text;
|
|
1986
|
+
badge.querySelector(".queue-preview").textContent = text;
|
|
1987
|
+
badge.title = label + "\n" + text;
|
|
1988
|
+
}
|
|
1989
|
+
}
|
|
1990
|
+
|
|
1373
1991
|
function renderSnapshot(snapshot) {
|
|
1374
1992
|
if (!snapshot) return;
|
|
1993
|
+
updatePromptPlaceholder(snapshot);
|
|
1375
1994
|
const state = stateForSession(activeSessionId);
|
|
1995
|
+
state.inputRequest = snapshot.input_request || null;
|
|
1996
|
+
renderQueueHint(snapshot.queued_inputs);
|
|
1376
1997
|
const signature = messageSignature(snapshot);
|
|
1377
1998
|
if (signature === state.lastRenderedSignature) {
|
|
1378
1999
|
setSpinner(snapshot.spinner || "");
|
|
@@ -1389,6 +2010,11 @@
|
|
|
1389
2010
|
const turns = Array.isArray(snapshot.turns) ? snapshot.turns : [];
|
|
1390
2011
|
if (turns.length) {
|
|
1391
2012
|
turns.forEach((turn) => renderTurn(turn, shouldStick));
|
|
2013
|
+
} else if (!state.inputRequest) {
|
|
2014
|
+
log.appendChild(document.getElementById("emptyChat").content.cloneNode(true));
|
|
2015
|
+
}
|
|
2016
|
+
if (state.inputRequest) {
|
|
2017
|
+
addEntry("input requested", state.inputRequest.text, "control", shouldStick);
|
|
1392
2018
|
}
|
|
1393
2019
|
setSpinner(snapshot.spinner || "");
|
|
1394
2020
|
updateContextMeter(snapshot);
|
|
@@ -1423,12 +2049,11 @@
|
|
|
1423
2049
|
);
|
|
1424
2050
|
}
|
|
1425
2051
|
if (turn.response) {
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
);
|
|
2052
|
+
if (isControl) {
|
|
2053
|
+
addEntry("result", turn.response, "control", shouldStick);
|
|
2054
|
+
} else {
|
|
2055
|
+
addMarkdownEntry("response", turn.response, "response", shouldStick);
|
|
2056
|
+
}
|
|
1432
2057
|
}
|
|
1433
2058
|
if (turn.thinking) addEntry("thinking", turn.thinking || "", "thinking", shouldStick);
|
|
1434
2059
|
if (turn.error) addEntry("error", turn.error || "", "error", shouldStick);
|
|
@@ -1460,6 +2085,8 @@
|
|
|
1460
2085
|
saveActiveSessionUiState();
|
|
1461
2086
|
activeSessionId = sessionId;
|
|
1462
2087
|
renderTabs();
|
|
2088
|
+
updatePromptPlaceholder();
|
|
2089
|
+
updateContextMeter();
|
|
1463
2090
|
const state = stateForSession(sessionId);
|
|
1464
2091
|
state.lastRenderedSignature = "";
|
|
1465
2092
|
lastRenderedSignature = "";
|
|
@@ -1467,6 +2094,7 @@
|
|
|
1467
2094
|
resizePrompt();
|
|
1468
2095
|
suppressScrollSave = true;
|
|
1469
2096
|
log.textContent = "";
|
|
2097
|
+
renderQueueHint([]);
|
|
1470
2098
|
suppressScrollSave = false;
|
|
1471
2099
|
setSpinner("", {suppressDoneNotification: true});
|
|
1472
2100
|
restoreScrollTop = state.scrollTop;
|
|
@@ -1505,26 +2133,17 @@
|
|
|
1505
2133
|
addEntry("error", payload.error || `HTTP ${response.status}`, "error");
|
|
1506
2134
|
return;
|
|
1507
2135
|
}
|
|
1508
|
-
sessionState.delete(sessionId);
|
|
1509
2136
|
updateSessions(payload.sessions || []);
|
|
1510
2137
|
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();
|
|
2138
|
+
await switchSession(sessions[0].id);
|
|
1521
2139
|
}
|
|
2140
|
+
sessionState.delete(sessionId);
|
|
1522
2141
|
}
|
|
1523
2142
|
|
|
1524
2143
|
form.addEventListener("submit", (event) => {
|
|
1525
2144
|
event.preventDefault();
|
|
1526
2145
|
const text = prompt.value.trim();
|
|
1527
|
-
if (!text) return;
|
|
2146
|
+
if (!text && !stateForSession(activeSessionId).inputRequest) return;
|
|
1528
2147
|
prompt.value = "";
|
|
1529
2148
|
resizePrompt();
|
|
1530
2149
|
stateForSession(activeSessionId).draft = "";
|
|
@@ -1565,6 +2184,12 @@
|
|
|
1565
2184
|
splitter.addEventListener("dblclick", centerSplit);
|
|
1566
2185
|
spinner.addEventListener("click", toggleSpinnerNotification);
|
|
1567
2186
|
newTabButton.addEventListener("click", createSession);
|
|
2187
|
+
document.querySelectorAll("[data-pane]").forEach((button) => {
|
|
2188
|
+
button.addEventListener("click", () => {
|
|
2189
|
+
workspace.dataset.mobilePane = button.dataset.pane;
|
|
2190
|
+
if (button.dataset.pane === "chat") resizePrompt();
|
|
2191
|
+
});
|
|
2192
|
+
});
|
|
1568
2193
|
window.addEventListener("resize", () => {
|
|
1569
2194
|
const current = Number(window.localStorage.getItem("pycodex.workspace.chatWidth") || 0);
|
|
1570
2195
|
if (current > 0) setChatWidth(current);
|
|
@@ -1577,6 +2202,7 @@
|
|
|
1577
2202
|
});
|
|
1578
2203
|
|
|
1579
2204
|
loadChatWidth();
|
|
2205
|
+
setupContextDetails();
|
|
1580
2206
|
resizePrompt();
|
|
1581
2207
|
startPolling();
|
|
1582
2208
|
startBoardPolling();
|