python-codex 0.2.6__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 +18 -14
- pycodex/agent.py +468 -462
- pycodex/bootstrap.py +417 -0
- pycodex/cli.py +236 -436
- 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 +329 -252
- pycodex/model_metadata.py +19 -7
- pycodex/portable.py +90 -52
- pycodex/portable_server.py +32 -24
- pycodex/prompts/models.json +235 -803
- pycodex/protocol.py +177 -137
- pycodex/runtime.py +579 -174
- pycodex/runtime_services.py +204 -157
- pycodex/tools/__init__.py +4 -1
- pycodex/tools/apply_patch_tool.py +69 -48
- pycodex/tools/base_tool.py +89 -42
- pycodex/tools/clock_tool.py +201 -0
- 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 +13 -13
- 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 +50 -66
- 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/utils/image_utils.py +76 -0
- pycodex/utils/random_ids.py +1 -2
- pycodex/utils/session_persist.py +263 -161
- 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 +25 -22
- responses_server/messages_api.py +96 -49
- responses_server/payload_processors.py +25 -19
- responses_server/server.py +11 -11
- responses_server/session_store.py +14 -11
- responses_server/stream_router.py +196 -107
- responses_server/tools/custom_adapter.py +17 -16
- responses_server/tools/web_search.py +39 -36
- responses_server/trajectory_dump.py +51 -13
- workspace_server/__main__.py +0 -1
- workspace_server/app.py +470 -384
- workspace_server/workspace.html +859 -232
- workspace_server/workspaces.html +94 -95
- workspace_server/workspaces.py +168 -100
- 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 -553
- python_codex-0.2.6.dist-info/METADATA +0 -441
- python_codex-0.2.6.dist-info/RECORD +0 -91
- {python_codex-0.2.6.dist-info → python_codex-0.3.0.dist-info}/WHEEL +0 -0
- {python_codex-0.2.6.dist-info → python_codex-0.3.0.dist-info}/entry_points.txt +0 -0
- {python_codex-0.2.6.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;
|
|
94
97
|
}
|
|
98
|
+
.boardbar-title {
|
|
99
|
+
color: var(--ink);
|
|
100
|
+
font-size: 14px;
|
|
101
|
+
font-weight: 600;
|
|
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,26 +388,39 @@
|
|
|
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;
|
|
306
396
|
background: transparent;
|
|
307
|
-
opacity: 0
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
397
|
+
opacity: 0;
|
|
398
|
+
visibility: hidden;
|
|
399
|
+
pointer-events: none;
|
|
400
|
+
}
|
|
401
|
+
.tab-close svg {
|
|
402
|
+
display: block;
|
|
403
|
+
width: 12px;
|
|
404
|
+
height: 12px;
|
|
311
405
|
}
|
|
312
406
|
.tab-new {
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
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;
|
|
316
417
|
}
|
|
317
|
-
.tab:hover .tab-close,
|
|
318
|
-
.tab:focus-within .tab-close,
|
|
319
418
|
.tab.active .tab-close {
|
|
320
419
|
opacity: 0.85;
|
|
420
|
+
visibility: visible;
|
|
421
|
+
pointer-events: auto;
|
|
321
422
|
}
|
|
322
|
-
.tab-close:disabled {
|
|
423
|
+
.tab.active .tab-close:disabled {
|
|
323
424
|
opacity: 0.22;
|
|
324
425
|
cursor: default;
|
|
325
426
|
}
|
|
@@ -332,52 +433,70 @@
|
|
|
332
433
|
min-height: 0;
|
|
333
434
|
overflow-y: auto;
|
|
334
435
|
overflow-x: hidden;
|
|
335
|
-
padding:
|
|
436
|
+
padding: 16px 20px 20px;
|
|
336
437
|
display: flex;
|
|
337
438
|
flex-direction: column;
|
|
338
|
-
gap:
|
|
339
|
-
background: var(--
|
|
439
|
+
gap: 14px;
|
|
440
|
+
background: var(--panel-raised);
|
|
441
|
+
scrollbar-width: thin;
|
|
442
|
+
scrollbar-color: var(--line) transparent;
|
|
340
443
|
}
|
|
341
444
|
.entry {
|
|
342
445
|
min-width: 0;
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
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);
|
|
348
454
|
}
|
|
349
455
|
.entry.user {
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
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);
|
|
353
461
|
}
|
|
354
462
|
.entry.response,
|
|
355
463
|
.entry.assistant {
|
|
356
|
-
|
|
357
|
-
|
|
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;
|
|
358
473
|
}
|
|
359
474
|
.entry.thinking {
|
|
360
|
-
border-color: var(--
|
|
361
|
-
|
|
362
|
-
color: var(--success);
|
|
363
|
-
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;
|
|
364
477
|
}
|
|
365
478
|
.entry.control {
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
color: var(--info);
|
|
479
|
+
white-space: pre-wrap;
|
|
480
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
369
481
|
}
|
|
370
482
|
.entry.tool {
|
|
371
|
-
border-color: #d8cdf1;
|
|
372
|
-
background: var(--violet-soft);
|
|
373
|
-
color: var(--violet);
|
|
374
483
|
font-size: 12px;
|
|
375
484
|
}
|
|
376
485
|
.entry.error {
|
|
377
|
-
border-
|
|
486
|
+
border-left: 2px solid var(--danger);
|
|
487
|
+
border-radius: var(--radius-sm);
|
|
488
|
+
padding: 12px 16px;
|
|
378
489
|
background: var(--danger-soft);
|
|
379
490
|
color: var(--danger);
|
|
380
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; }
|
|
381
500
|
.text {
|
|
382
501
|
white-space: pre-wrap;
|
|
383
502
|
overflow-wrap: anywhere;
|
|
@@ -387,7 +506,7 @@
|
|
|
387
506
|
overflow-wrap: anywhere;
|
|
388
507
|
}
|
|
389
508
|
.markdown p {
|
|
390
|
-
margin: 0 0
|
|
509
|
+
margin: 0 0 8px;
|
|
391
510
|
}
|
|
392
511
|
.markdown p:last-child,
|
|
393
512
|
.markdown ul:last-child,
|
|
@@ -398,17 +517,17 @@
|
|
|
398
517
|
}
|
|
399
518
|
.markdown ul,
|
|
400
519
|
.markdown ol {
|
|
401
|
-
margin: 0 0
|
|
520
|
+
margin: 0 0 8px 18px;
|
|
402
521
|
padding: 0;
|
|
403
522
|
}
|
|
404
523
|
.markdown li {
|
|
405
524
|
margin: 2px 0;
|
|
406
525
|
}
|
|
407
526
|
.markdown pre {
|
|
408
|
-
margin: 0 0
|
|
409
|
-
padding:
|
|
410
|
-
border:
|
|
411
|
-
border-radius: var(--radius-
|
|
527
|
+
margin: 0 0 10px;
|
|
528
|
+
padding: 10px 12px;
|
|
529
|
+
border: 0;
|
|
530
|
+
border-radius: var(--radius-md);
|
|
412
531
|
background: var(--code);
|
|
413
532
|
overflow-x: auto;
|
|
414
533
|
white-space: pre;
|
|
@@ -443,6 +562,13 @@
|
|
|
443
562
|
.markdown a:hover {
|
|
444
563
|
text-decoration: underline;
|
|
445
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); }
|
|
446
572
|
.markdown .katex-display {
|
|
447
573
|
margin: 0 0 var(--space-4);
|
|
448
574
|
overflow-x: auto;
|
|
@@ -462,32 +588,175 @@
|
|
|
462
588
|
.markdown .katex-display:last-child {
|
|
463
589
|
margin-bottom: 0;
|
|
464
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
|
+
}
|
|
465
672
|
.composer {
|
|
466
673
|
position: relative;
|
|
467
674
|
min-width: 0;
|
|
468
|
-
padding:
|
|
675
|
+
padding: 8px 20px 10px;
|
|
469
676
|
display: grid;
|
|
470
|
-
gap:
|
|
677
|
+
gap: 6px;
|
|
471
678
|
align-content: end;
|
|
472
|
-
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;
|
|
473
739
|
}
|
|
474
740
|
textarea {
|
|
475
741
|
min-width: 0;
|
|
476
|
-
resize:
|
|
742
|
+
resize: none;
|
|
477
743
|
max-height: 35vh;
|
|
478
744
|
width: 100%;
|
|
479
|
-
|
|
480
|
-
border
|
|
481
|
-
padding:
|
|
482
|
-
|
|
745
|
+
min-height: 28px;
|
|
746
|
+
border: 0;
|
|
747
|
+
padding: 3px 0;
|
|
748
|
+
display: block;
|
|
749
|
+
background: transparent;
|
|
483
750
|
color: var(--ink);
|
|
484
751
|
font: inherit;
|
|
485
|
-
line-height:
|
|
752
|
+
line-height: 22px;
|
|
486
753
|
overflow-y: auto;
|
|
754
|
+
scrollbar-width: none;
|
|
755
|
+
}
|
|
756
|
+
textarea::-webkit-scrollbar {
|
|
757
|
+
display: none;
|
|
487
758
|
}
|
|
488
759
|
textarea:focus {
|
|
489
|
-
border-color: var(--accent);
|
|
490
|
-
box-shadow: 0 0 0 3px rgba(8, 127, 154, 0.18);
|
|
491
760
|
outline: 0;
|
|
492
761
|
}
|
|
493
762
|
.actions {
|
|
@@ -497,11 +766,15 @@
|
|
|
497
766
|
}
|
|
498
767
|
.spinner-wrap {
|
|
499
768
|
position: relative;
|
|
500
|
-
display:
|
|
769
|
+
display: flex;
|
|
501
770
|
align-items: center;
|
|
502
|
-
|
|
503
|
-
align-self: end;
|
|
771
|
+
gap: var(--space-3);
|
|
504
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);
|
|
505
778
|
}
|
|
506
779
|
.prompt-row {
|
|
507
780
|
display: flex;
|
|
@@ -510,21 +783,21 @@
|
|
|
510
783
|
min-width: 0;
|
|
511
784
|
}
|
|
512
785
|
.spinner {
|
|
513
|
-
--spinner-pill-height: 22px;
|
|
514
786
|
position: relative;
|
|
515
787
|
display: inline-flex;
|
|
516
788
|
align-items: center;
|
|
517
789
|
max-width: 100%;
|
|
518
|
-
|
|
790
|
+
min-width: 0;
|
|
791
|
+
height: 22px;
|
|
519
792
|
color: var(--muted);
|
|
520
793
|
font-size: 12px;
|
|
521
794
|
white-space: nowrap;
|
|
522
795
|
overflow: hidden;
|
|
523
796
|
text-overflow: ellipsis;
|
|
524
797
|
padding: var(--space-1) var(--space-4) var(--space-1) 18px;
|
|
525
|
-
border-radius:
|
|
526
|
-
background:
|
|
527
|
-
border: 1px solid
|
|
798
|
+
border-radius: var(--radius-sm);
|
|
799
|
+
background: transparent;
|
|
800
|
+
border: 1px solid transparent;
|
|
528
801
|
font: inherit;
|
|
529
802
|
font-size: 12px;
|
|
530
803
|
cursor: pointer;
|
|
@@ -532,16 +805,15 @@
|
|
|
532
805
|
.spinner:empty {
|
|
533
806
|
display: none;
|
|
534
807
|
}
|
|
535
|
-
.spinner
|
|
808
|
+
.spinner:hover {
|
|
809
|
+
background: var(--panel-deep);
|
|
810
|
+
}
|
|
811
|
+
.spinner-wrap:not(:has(.spinner:not(:empty), .queue-hint:not([hidden]))) {
|
|
536
812
|
display: none;
|
|
537
813
|
}
|
|
538
814
|
.spinner:focus-visible {
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
outline: 0;
|
|
542
|
-
}
|
|
543
|
-
.spinner-wrap:has(.spinner:not(:empty)) {
|
|
544
|
-
margin-bottom: var(--space-1);
|
|
815
|
+
outline: 2px solid var(--accent);
|
|
816
|
+
outline-offset: 1px;
|
|
545
817
|
}
|
|
546
818
|
.spinner:not(:empty)::before {
|
|
547
819
|
content: "";
|
|
@@ -556,12 +828,8 @@
|
|
|
556
828
|
animation: spinner-pulse 1.2s cubic-bezier(0.4, 0, 0.2, 1) infinite;
|
|
557
829
|
}
|
|
558
830
|
.spinner.notify-enabled {
|
|
559
|
-
color: var(--
|
|
560
|
-
background: var(--
|
|
561
|
-
border-color: var(--success-line);
|
|
562
|
-
box-shadow:
|
|
563
|
-
inset 0 1px 2px rgba(27, 57, 72, 0.08),
|
|
564
|
-
inset 0 0 0 1px rgba(36, 122, 82, 0.1);
|
|
831
|
+
color: var(--accent);
|
|
832
|
+
background: var(--accent-soft);
|
|
565
833
|
}
|
|
566
834
|
.toast {
|
|
567
835
|
position: absolute;
|
|
@@ -597,17 +865,9 @@
|
|
|
597
865
|
transform: scale(1.08);
|
|
598
866
|
}
|
|
599
867
|
}
|
|
600
|
-
@keyframes
|
|
601
|
-
0%, 100% {
|
|
602
|
-
|
|
603
|
-
box-shadow: var(--entry-shadow);
|
|
604
|
-
}
|
|
605
|
-
50% {
|
|
606
|
-
border-color: var(--success);
|
|
607
|
-
box-shadow:
|
|
608
|
-
0 0 0 2px rgba(36, 122, 82, 0.14),
|
|
609
|
-
0 2px 7px rgba(27, 57, 72, 0.08);
|
|
610
|
-
}
|
|
868
|
+
@keyframes activity-breathe {
|
|
869
|
+
0%, 100% { border-left-color: var(--line); }
|
|
870
|
+
50% { border-left-color: var(--accent); }
|
|
611
871
|
}
|
|
612
872
|
@media (prefers-reduced-motion: reduce) {
|
|
613
873
|
.entry.thinking,
|
|
@@ -629,21 +889,61 @@
|
|
|
629
889
|
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
630
890
|
font-size: 12px;
|
|
631
891
|
}
|
|
632
|
-
@media (max-width:
|
|
633
|
-
|
|
634
|
-
|
|
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; }
|
|
635
923
|
}
|
|
636
924
|
</style>
|
|
637
925
|
</head>
|
|
638
926
|
<body>
|
|
639
|
-
<div class="workspace">
|
|
927
|
+
<div class="workspace" data-mobile-pane="chat">
|
|
640
928
|
<section class="board-pane" aria-label="workspace board">
|
|
641
929
|
<div class="boardbar">
|
|
642
|
-
<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>
|
|
643
936
|
<div class="boardbar-paths">
|
|
644
|
-
<span
|
|
645
|
-
<span
|
|
937
|
+
<span class="boardbar-title">__WORKSPACE_TITLE__</span>
|
|
938
|
+
<span title="__CWD_LABEL__"><code>__CWD_LABEL__</code></span>
|
|
646
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>
|
|
647
947
|
</div>
|
|
648
948
|
<iframe id="boardFrame" class="board" src="board" title="board"></iframe>
|
|
649
949
|
</section>
|
|
@@ -652,24 +952,75 @@
|
|
|
652
952
|
<div class="topbar">
|
|
653
953
|
<div class="tabbar">
|
|
654
954
|
<div id="tabs" class="tabs" role="tablist"></div>
|
|
655
|
-
<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>
|
|
656
968
|
</div>
|
|
657
969
|
</div>
|
|
658
|
-
<div id="log" class="log"></div>
|
|
970
|
+
<div id="log" class="log" aria-label="Conversation"></div>
|
|
659
971
|
<form id="composer" class="composer">
|
|
660
|
-
<div class="
|
|
661
|
-
<div
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
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>
|
|
667
1012
|
</div>
|
|
668
|
-
<textarea id="prompt" rows="1" placeholder="Ask pycodex or type /help..."></textarea>
|
|
669
1013
|
</div>
|
|
670
1014
|
</form>
|
|
671
1015
|
</section>
|
|
672
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>
|
|
673
1024
|
<script src="https://cdn.jsdelivr.net/npm/markdown-it@14.1.0/dist/markdown-it.min.js"></script>
|
|
674
1025
|
<script src="https://cdn.jsdelivr.net/npm/dompurify@3.2.6/dist/purify.min.js"></script>
|
|
675
1026
|
<script src="https://cdn.jsdelivr.net/npm/katex@0.16.22/dist/katex.min.js"></script>
|
|
@@ -677,6 +1028,9 @@
|
|
|
677
1028
|
<script>
|
|
678
1029
|
const log = document.getElementById("log");
|
|
679
1030
|
const spinner = document.getElementById("spinner");
|
|
1031
|
+
const queueHint = document.getElementById("queueHint");
|
|
1032
|
+
const steerHint = document.getElementById("steerHint");
|
|
1033
|
+
const enqueueHint = document.getElementById("enqueueHint");
|
|
680
1034
|
const toast = document.getElementById("toast");
|
|
681
1035
|
const prompt = document.getElementById("prompt");
|
|
682
1036
|
const form = document.getElementById("composer");
|
|
@@ -689,7 +1043,7 @@
|
|
|
689
1043
|
const newTabButton = document.getElementById("newTab");
|
|
690
1044
|
let pollTimer = null;
|
|
691
1045
|
let boardPollTimer = null;
|
|
692
|
-
let
|
|
1046
|
+
let sessionPoll = null;
|
|
693
1047
|
let boardPollInFlight = false;
|
|
694
1048
|
let lastBoardSignature = null;
|
|
695
1049
|
let lastRenderedSignature = "";
|
|
@@ -713,6 +1067,40 @@
|
|
|
713
1067
|
];
|
|
714
1068
|
const mathTokenPrefix = "\uE000math";
|
|
715
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
|
+
})();
|
|
716
1104
|
|
|
717
1105
|
function relativeUrl(path) {
|
|
718
1106
|
return new URL(path, window.location.href).toString();
|
|
@@ -737,9 +1125,9 @@
|
|
|
737
1125
|
entry.className = "entry " + (cls || role);
|
|
738
1126
|
const body = document.createElement("div");
|
|
739
1127
|
body.className = "text markdown";
|
|
740
|
-
renderMarkdownInto(body, text || "");
|
|
741
1128
|
entry.appendChild(body);
|
|
742
1129
|
log.appendChild(entry);
|
|
1130
|
+
renderMarkdownInto(body, text || "");
|
|
743
1131
|
if (stickToBottom) scrollToBottom();
|
|
744
1132
|
return body;
|
|
745
1133
|
}
|
|
@@ -760,6 +1148,102 @@
|
|
|
760
1148
|
anchor.target = "_blank";
|
|
761
1149
|
anchor.rel = "noopener noreferrer";
|
|
762
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();
|
|
763
1247
|
}
|
|
764
1248
|
|
|
765
1249
|
function isEscapedAt(source, index) {
|
|
@@ -1166,6 +1650,7 @@
|
|
|
1166
1650
|
draft: "",
|
|
1167
1651
|
notifyWhenDone: false,
|
|
1168
1652
|
wasRunning: false,
|
|
1653
|
+
inputRequest: null,
|
|
1169
1654
|
});
|
|
1170
1655
|
}
|
|
1171
1656
|
return sessionState.get(key);
|
|
@@ -1201,88 +1686,201 @@
|
|
|
1201
1686
|
activeSessionId = sessions[0].id || "";
|
|
1202
1687
|
}
|
|
1203
1688
|
renderTabs();
|
|
1689
|
+
updatePromptPlaceholder();
|
|
1204
1690
|
updateContextMeter();
|
|
1205
1691
|
updateSpinnerNotifyUi();
|
|
1206
1692
|
}
|
|
1207
1693
|
|
|
1208
1694
|
function renderTabs() {
|
|
1209
|
-
|
|
1695
|
+
const existingTabs = new Map(
|
|
1696
|
+
Array.from(tabsEl.children, tab => [tab.dataset.sessionId, tab])
|
|
1697
|
+
);
|
|
1210
1698
|
sessions.forEach((session, index) => {
|
|
1211
1699
|
const sessionId = session.id || "";
|
|
1212
|
-
|
|
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);
|
|
1213
1737
|
tab.className =
|
|
1214
1738
|
"tab" +
|
|
1215
1739
|
(sessionId === activeSessionId ? " active" : "") +
|
|
1216
1740
|
(session.running ? " running" : "");
|
|
1217
1741
|
tab.title = session.title || `Session ${index + 1}`;
|
|
1218
|
-
tab.setAttribute("role", "tab");
|
|
1219
1742
|
tab.setAttribute("aria-selected", sessionId === activeSessionId ? "true" : "false");
|
|
1220
|
-
|
|
1221
|
-
tab.
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
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);
|
|
1228
1751
|
}
|
|
1752
|
+
});
|
|
1753
|
+
existingTabs.forEach(tab => tab.remove());
|
|
1754
|
+
}
|
|
1229
1755
|
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
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
|
+
}
|
|
1248
1788
|
});
|
|
1789
|
+
input.addEventListener("blur", () => finish(true));
|
|
1790
|
+
input.focus();
|
|
1791
|
+
input.select();
|
|
1249
1792
|
}
|
|
1250
1793
|
|
|
1251
|
-
function
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
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}`);
|
|
1259
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");
|
|
1260
1812
|
}
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
}
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
if (
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
}
|
|
1278
|
-
|
|
1279
|
-
contextMeter.
|
|
1280
|
-
|
|
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);
|
|
1281
1879
|
}
|
|
1282
1880
|
|
|
1283
1881
|
function messageSignature(snapshot) {
|
|
1284
1882
|
const turns = (snapshot && snapshot.turns) || [];
|
|
1285
|
-
return JSON.stringify(
|
|
1883
|
+
return JSON.stringify([
|
|
1286
1884
|
turns.map((item) => [
|
|
1287
1885
|
item.submission_id || "",
|
|
1288
1886
|
item.turn_id || "",
|
|
@@ -1293,21 +1891,26 @@
|
|
|
1293
1891
|
item.error || "",
|
|
1294
1892
|
item.kind || "",
|
|
1295
1893
|
]),
|
|
1296
|
-
|
|
1894
|
+
snapshot.input_request || null,
|
|
1895
|
+
]);
|
|
1297
1896
|
}
|
|
1298
1897
|
|
|
1299
1898
|
async function pollSession() {
|
|
1300
|
-
if (pollInFlight) return;
|
|
1301
|
-
pollInFlight = true;
|
|
1302
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;
|
|
1303
1906
|
try {
|
|
1304
1907
|
const path = requestedSessionId
|
|
1305
1908
|
? `api/session?session_id=${encodeURIComponent(requestedSessionId)}`
|
|
1306
1909
|
: "api/session";
|
|
1307
|
-
const response = await fetch(relativeUrl(path));
|
|
1910
|
+
const response = await fetch(relativeUrl(path), {signal: request.controller.signal});
|
|
1308
1911
|
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
1309
1912
|
const payload = await response.json();
|
|
1310
|
-
if (
|
|
1913
|
+
if (sessionPoll !== request || requestedSessionId !== activeSessionId) {
|
|
1311
1914
|
return;
|
|
1312
1915
|
}
|
|
1313
1916
|
if (payload.session_id && payload.session_id !== activeSessionId) {
|
|
@@ -1316,9 +1919,11 @@
|
|
|
1316
1919
|
updateSessions(payload.sessions || []);
|
|
1317
1920
|
renderSnapshot(payload.snapshot);
|
|
1318
1921
|
} catch (error) {
|
|
1319
|
-
|
|
1922
|
+
if (sessionPoll === request && requestedSessionId === activeSessionId) {
|
|
1923
|
+
setSpinner(`poll error: ${error.message}`);
|
|
1924
|
+
}
|
|
1320
1925
|
} finally {
|
|
1321
|
-
|
|
1926
|
+
if (sessionPoll === request) sessionPoll = null;
|
|
1322
1927
|
}
|
|
1323
1928
|
}
|
|
1324
1929
|
|
|
@@ -1368,9 +1973,25 @@
|
|
|
1368
1973
|
boardPollTimer = setInterval(pollBoard, 2000);
|
|
1369
1974
|
}
|
|
1370
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
|
+
|
|
1371
1989
|
function renderSnapshot(snapshot) {
|
|
1372
1990
|
if (!snapshot) return;
|
|
1991
|
+
updatePromptPlaceholder(snapshot);
|
|
1373
1992
|
const state = stateForSession(activeSessionId);
|
|
1993
|
+
state.inputRequest = snapshot.input_request || null;
|
|
1994
|
+
renderQueueHint(snapshot.queued_inputs);
|
|
1374
1995
|
const signature = messageSignature(snapshot);
|
|
1375
1996
|
if (signature === state.lastRenderedSignature) {
|
|
1376
1997
|
setSpinner(snapshot.spinner || "");
|
|
@@ -1387,6 +2008,11 @@
|
|
|
1387
2008
|
const turns = Array.isArray(snapshot.turns) ? snapshot.turns : [];
|
|
1388
2009
|
if (turns.length) {
|
|
1389
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);
|
|
1390
2016
|
}
|
|
1391
2017
|
setSpinner(snapshot.spinner || "");
|
|
1392
2018
|
updateContextMeter(snapshot);
|
|
@@ -1421,12 +2047,11 @@
|
|
|
1421
2047
|
);
|
|
1422
2048
|
}
|
|
1423
2049
|
if (turn.response) {
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
);
|
|
2050
|
+
if (isControl) {
|
|
2051
|
+
addEntry("result", turn.response, "control", shouldStick);
|
|
2052
|
+
} else {
|
|
2053
|
+
addMarkdownEntry("response", turn.response, "response", shouldStick);
|
|
2054
|
+
}
|
|
1430
2055
|
}
|
|
1431
2056
|
if (turn.thinking) addEntry("thinking", turn.thinking || "", "thinking", shouldStick);
|
|
1432
2057
|
if (turn.error) addEntry("error", turn.error || "", "error", shouldStick);
|
|
@@ -1457,6 +2082,9 @@
|
|
|
1457
2082
|
if (!sessionId || sessionId === activeSessionId) return;
|
|
1458
2083
|
saveActiveSessionUiState();
|
|
1459
2084
|
activeSessionId = sessionId;
|
|
2085
|
+
renderTabs();
|
|
2086
|
+
updatePromptPlaceholder();
|
|
2087
|
+
updateContextMeter();
|
|
1460
2088
|
const state = stateForSession(sessionId);
|
|
1461
2089
|
state.lastRenderedSignature = "";
|
|
1462
2090
|
lastRenderedSignature = "";
|
|
@@ -1464,6 +2092,7 @@
|
|
|
1464
2092
|
resizePrompt();
|
|
1465
2093
|
suppressScrollSave = true;
|
|
1466
2094
|
log.textContent = "";
|
|
2095
|
+
renderQueueHint([]);
|
|
1467
2096
|
suppressScrollSave = false;
|
|
1468
2097
|
setSpinner("", {suppressDoneNotification: true});
|
|
1469
2098
|
restoreScrollTop = state.scrollTop;
|
|
@@ -1502,26 +2131,17 @@
|
|
|
1502
2131
|
addEntry("error", payload.error || `HTTP ${response.status}`, "error");
|
|
1503
2132
|
return;
|
|
1504
2133
|
}
|
|
1505
|
-
sessionState.delete(sessionId);
|
|
1506
2134
|
updateSessions(payload.sessions || []);
|
|
1507
2135
|
if (activeSessionId === sessionId) {
|
|
1508
|
-
|
|
1509
|
-
lastRenderedSignature = "";
|
|
1510
|
-
prompt.value = "";
|
|
1511
|
-
resizePrompt();
|
|
1512
|
-
suppressScrollSave = true;
|
|
1513
|
-
log.textContent = "";
|
|
1514
|
-
suppressScrollSave = false;
|
|
1515
|
-
await pollSession();
|
|
1516
|
-
} else {
|
|
1517
|
-
renderTabs();
|
|
2136
|
+
await switchSession(sessions[0].id);
|
|
1518
2137
|
}
|
|
2138
|
+
sessionState.delete(sessionId);
|
|
1519
2139
|
}
|
|
1520
2140
|
|
|
1521
2141
|
form.addEventListener("submit", (event) => {
|
|
1522
2142
|
event.preventDefault();
|
|
1523
2143
|
const text = prompt.value.trim();
|
|
1524
|
-
if (!text) return;
|
|
2144
|
+
if (!text && !stateForSession(activeSessionId).inputRequest) return;
|
|
1525
2145
|
prompt.value = "";
|
|
1526
2146
|
resizePrompt();
|
|
1527
2147
|
stateForSession(activeSessionId).draft = "";
|
|
@@ -1562,6 +2182,12 @@
|
|
|
1562
2182
|
splitter.addEventListener("dblclick", centerSplit);
|
|
1563
2183
|
spinner.addEventListener("click", toggleSpinnerNotification);
|
|
1564
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
|
+
});
|
|
1565
2191
|
window.addEventListener("resize", () => {
|
|
1566
2192
|
const current = Number(window.localStorage.getItem("pycodex.workspace.chatWidth") || 0);
|
|
1567
2193
|
if (current > 0) setChatWidth(current);
|
|
@@ -1574,6 +2200,7 @@
|
|
|
1574
2200
|
});
|
|
1575
2201
|
|
|
1576
2202
|
loadChatWidth();
|
|
2203
|
+
setupContextDetails();
|
|
1577
2204
|
resizePrompt();
|
|
1578
2205
|
startPolling();
|
|
1579
2206
|
startBoardPolling();
|