python-codex 0.1.14__py3-none-any.whl → 0.2.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/agent.py +6 -11
- pycodex/cli.py +3 -0
- pycodex/context.py +12 -0
- pycodex/model.py +10 -3
- pycodex/runtime.py +10 -0
- pycodex/tools/code_mode_manager.py +1 -1
- pycodex/tools/unified_exec_manager.py +7 -92
- pycodex/utils/session_persist.py +42 -1
- pycodex/utils/truncation.py +206 -0
- {python_codex-0.1.14.dist-info → python_codex-0.2.1.dist-info}/METADATA +1 -1
- {python_codex-0.1.14.dist-info → python_codex-0.2.1.dist-info}/RECORD +17 -16
- workspace_server/__init__.py +2 -0
- workspace_server/app.py +473 -109
- workspace_server/workspace.html +360 -150
- {python_codex-0.1.14.dist-info → python_codex-0.2.1.dist-info}/WHEEL +0 -0
- {python_codex-0.1.14.dist-info → python_codex-0.2.1.dist-info}/entry_points.txt +0 -0
- {python_codex-0.1.14.dist-info → python_codex-0.2.1.dist-info}/licenses/LICENSE +0 -0
workspace_server/workspace.html
CHANGED
|
@@ -7,20 +7,38 @@
|
|
|
7
7
|
<style>
|
|
8
8
|
:root {
|
|
9
9
|
color-scheme: light;
|
|
10
|
-
--bg: #
|
|
10
|
+
--bg: #f2f6f3;
|
|
11
11
|
--panel: #ffffff;
|
|
12
|
-
--ink: #
|
|
13
|
-
--muted: #
|
|
14
|
-
--line: #
|
|
15
|
-
--accent: #
|
|
12
|
+
--ink: #1e2529;
|
|
13
|
+
--muted: #5f6f75;
|
|
14
|
+
--line: #d2ddd5;
|
|
15
|
+
--accent: #0f8f55;
|
|
16
16
|
--accent-ink: #ffffff;
|
|
17
|
-
--
|
|
17
|
+
--warn-soft: #fff5cf;
|
|
18
|
+
--info: #1f6fa4;
|
|
19
|
+
--info-soft: #edf8ff;
|
|
20
|
+
--danger: #b23a3a;
|
|
21
|
+
--danger-soft: #fff1ef;
|
|
22
|
+
--violet: #6b4fb0;
|
|
23
|
+
--violet-soft: #f5f0ff;
|
|
24
|
+
--code: #f3f7f8;
|
|
25
|
+
--entry-shadow: 0 1px 2px rgba(25, 35, 31, 0.04);
|
|
26
|
+
--space-1: 2px;
|
|
27
|
+
--space-2: 4px;
|
|
28
|
+
--space-3: 6px;
|
|
29
|
+
--space-4: 8px;
|
|
30
|
+
--space-5: 10px;
|
|
31
|
+
--space-6: 12px;
|
|
32
|
+
--space-7: 14px;
|
|
33
|
+
--radius-sm: 4px;
|
|
34
|
+
--radius-md: 6px;
|
|
18
35
|
--chat-width: min(42vw, 760px);
|
|
36
|
+
--splitter-width: 8px;
|
|
19
37
|
}
|
|
20
38
|
* { box-sizing: border-box; }
|
|
21
39
|
body {
|
|
22
40
|
margin: 0;
|
|
23
|
-
height:
|
|
41
|
+
height: 100dvh;
|
|
24
42
|
overflow: hidden;
|
|
25
43
|
background: var(--bg);
|
|
26
44
|
color: var(--ink);
|
|
@@ -28,35 +46,68 @@
|
|
|
28
46
|
}
|
|
29
47
|
.workspace {
|
|
30
48
|
display: grid;
|
|
31
|
-
grid-template-columns: minmax(
|
|
32
|
-
height:
|
|
33
|
-
min-width:
|
|
49
|
+
grid-template-columns: minmax(0, 1fr) var(--splitter-width) var(--chat-width);
|
|
50
|
+
height: 100dvh;
|
|
51
|
+
min-width: 0;
|
|
52
|
+
}
|
|
53
|
+
.workspace.collapsed .splitter::after {
|
|
54
|
+
background: #4f6f60;
|
|
55
|
+
}
|
|
56
|
+
.board-pane {
|
|
57
|
+
min-width: 0;
|
|
58
|
+
height: 100dvh;
|
|
59
|
+
display: grid;
|
|
60
|
+
grid-template-rows: auto minmax(0, 1fr);
|
|
61
|
+
background: #fff;
|
|
62
|
+
}
|
|
63
|
+
.boardbar {
|
|
64
|
+
min-width: 0;
|
|
65
|
+
border-bottom: 1px solid var(--line);
|
|
66
|
+
padding: var(--space-4) var(--space-6);
|
|
67
|
+
color: var(--muted);
|
|
68
|
+
font-size: 12px;
|
|
69
|
+
white-space: nowrap;
|
|
70
|
+
overflow: hidden;
|
|
71
|
+
text-overflow: ellipsis;
|
|
34
72
|
}
|
|
35
73
|
.board {
|
|
36
74
|
border: 0;
|
|
75
|
+
min-width: 0;
|
|
37
76
|
width: 100%;
|
|
38
77
|
height: 100%;
|
|
39
78
|
background: #fff;
|
|
40
79
|
}
|
|
41
80
|
.splitter {
|
|
81
|
+
position: relative;
|
|
42
82
|
width: 8px;
|
|
43
|
-
height:
|
|
83
|
+
height: 100dvh;
|
|
44
84
|
border: 0;
|
|
45
|
-
border-left: 1px solid
|
|
46
|
-
border-right: 1px solid
|
|
47
|
-
background: #
|
|
85
|
+
border-left: 1px solid #c1cec6;
|
|
86
|
+
border-right: 1px solid #c1cec6;
|
|
87
|
+
background: #dfe8e2;
|
|
48
88
|
cursor: col-resize;
|
|
49
89
|
padding: 0;
|
|
50
90
|
touch-action: none;
|
|
51
91
|
}
|
|
52
92
|
.splitter::after {
|
|
53
93
|
content: "";
|
|
54
|
-
|
|
55
|
-
|
|
94
|
+
position: absolute;
|
|
95
|
+
left: 50%;
|
|
96
|
+
top: 50%;
|
|
97
|
+
width: 3px;
|
|
56
98
|
height: 42px;
|
|
57
|
-
margin: calc(50vh - 21px) auto 0;
|
|
58
99
|
border-radius: 999px;
|
|
59
|
-
background: #
|
|
100
|
+
background: #7d9588;
|
|
101
|
+
transform: translate(-50%, -50%);
|
|
102
|
+
}
|
|
103
|
+
.splitter:hover,
|
|
104
|
+
.workspace.resizing .splitter {
|
|
105
|
+
background: #d3dfd7;
|
|
106
|
+
border-color: #aebeb4;
|
|
107
|
+
}
|
|
108
|
+
.splitter:hover::after,
|
|
109
|
+
.workspace.resizing .splitter::after {
|
|
110
|
+
background: #527465;
|
|
60
111
|
}
|
|
61
112
|
.workspace.resizing,
|
|
62
113
|
.workspace.resizing * {
|
|
@@ -64,45 +115,48 @@
|
|
|
64
115
|
user-select: none;
|
|
65
116
|
}
|
|
66
117
|
.chat {
|
|
118
|
+
position: relative;
|
|
67
119
|
min-width: 0;
|
|
68
|
-
height:
|
|
120
|
+
height: 100dvh;
|
|
69
121
|
overflow: hidden;
|
|
70
122
|
display: grid;
|
|
71
123
|
grid-template-rows: auto minmax(0, 1fr) auto;
|
|
72
124
|
background: var(--panel);
|
|
73
125
|
}
|
|
74
126
|
.topbar {
|
|
75
|
-
|
|
127
|
+
min-width: 0;
|
|
128
|
+
padding: var(--space-4) var(--space-7);
|
|
76
129
|
border-bottom: 1px solid var(--line);
|
|
77
130
|
display: grid;
|
|
78
|
-
gap:
|
|
131
|
+
gap: var(--space-4);
|
|
79
132
|
}
|
|
80
133
|
.tabbar {
|
|
81
134
|
display: flex;
|
|
82
135
|
align-items: center;
|
|
83
|
-
gap:
|
|
136
|
+
gap: var(--space-2);
|
|
84
137
|
min-width: 0;
|
|
85
138
|
}
|
|
86
139
|
.tabs {
|
|
87
140
|
display: flex;
|
|
88
141
|
align-items: center;
|
|
89
|
-
gap:
|
|
142
|
+
gap: var(--space-1);
|
|
90
143
|
min-width: 0;
|
|
91
144
|
overflow-x: auto;
|
|
92
145
|
scrollbar-width: thin;
|
|
93
146
|
}
|
|
94
147
|
.tab {
|
|
148
|
+
position: relative;
|
|
95
149
|
display: inline-flex;
|
|
96
150
|
align-items: center;
|
|
97
|
-
gap:
|
|
151
|
+
gap: var(--space-2);
|
|
98
152
|
min-width: 88px;
|
|
99
153
|
max-width: 180px;
|
|
100
|
-
height:
|
|
101
|
-
border: 1px solid
|
|
102
|
-
border-radius:
|
|
103
|
-
background: #
|
|
154
|
+
height: 31px;
|
|
155
|
+
border: 1px solid #d5ddd7;
|
|
156
|
+
border-radius: var(--radius-sm);
|
|
157
|
+
background: #f6f8f7;
|
|
104
158
|
color: var(--ink);
|
|
105
|
-
padding: 0
|
|
159
|
+
padding: 0 var(--space-3) 0 var(--space-4);
|
|
106
160
|
font: inherit;
|
|
107
161
|
cursor: pointer;
|
|
108
162
|
user-select: none;
|
|
@@ -113,8 +167,24 @@
|
|
|
113
167
|
outline-offset: 1px;
|
|
114
168
|
}
|
|
115
169
|
.tab.active {
|
|
116
|
-
border-color: #
|
|
117
|
-
background: #
|
|
170
|
+
border-color: #afd6bd;
|
|
171
|
+
background: #eef7f1;
|
|
172
|
+
box-shadow: inset 0 -2px 0 var(--accent);
|
|
173
|
+
}
|
|
174
|
+
.tab.running::before {
|
|
175
|
+
content: "";
|
|
176
|
+
position: absolute;
|
|
177
|
+
left: 6px;
|
|
178
|
+
top: 50%;
|
|
179
|
+
width: 5px;
|
|
180
|
+
height: 5px;
|
|
181
|
+
margin-top: -2.5px;
|
|
182
|
+
border-radius: 999px;
|
|
183
|
+
background: var(--accent);
|
|
184
|
+
animation: spinner-pulse 1.2s cubic-bezier(0.4, 0, 0.2, 1) infinite;
|
|
185
|
+
}
|
|
186
|
+
.tab.running .tab-label {
|
|
187
|
+
padding-left: var(--space-4);
|
|
118
188
|
}
|
|
119
189
|
.tab-label {
|
|
120
190
|
min-width: 0;
|
|
@@ -126,11 +196,11 @@
|
|
|
126
196
|
}
|
|
127
197
|
.tab-close,
|
|
128
198
|
.tab-new {
|
|
129
|
-
width:
|
|
130
|
-
height:
|
|
199
|
+
width: 31px;
|
|
200
|
+
height: 31px;
|
|
131
201
|
border: 1px solid var(--line);
|
|
132
|
-
border-radius:
|
|
133
|
-
background: #
|
|
202
|
+
border-radius: var(--radius-md);
|
|
203
|
+
background: #eef7f1;
|
|
134
204
|
color: var(--muted);
|
|
135
205
|
padding: 0;
|
|
136
206
|
line-height: 1;
|
|
@@ -142,59 +212,80 @@
|
|
|
142
212
|
height: 18px;
|
|
143
213
|
border: 0;
|
|
144
214
|
background: transparent;
|
|
215
|
+
opacity: 0.42;
|
|
216
|
+
font-size: 17px;
|
|
217
|
+
font-weight: 500;
|
|
218
|
+
line-height: 18px;
|
|
219
|
+
}
|
|
220
|
+
.tab-new {
|
|
221
|
+
font-size: 22px;
|
|
222
|
+
font-weight: 400;
|
|
223
|
+
line-height: 31px;
|
|
224
|
+
}
|
|
225
|
+
.tab:hover .tab-close,
|
|
226
|
+
.tab:focus-within .tab-close,
|
|
227
|
+
.tab.active .tab-close {
|
|
228
|
+
opacity: 0.85;
|
|
229
|
+
}
|
|
230
|
+
.tab-close:disabled {
|
|
231
|
+
opacity: 0.22;
|
|
232
|
+
cursor: default;
|
|
145
233
|
}
|
|
146
234
|
.tab-close:hover,
|
|
147
235
|
.tab-new:hover {
|
|
148
236
|
color: var(--ink);
|
|
149
|
-
background: #
|
|
150
|
-
}
|
|
151
|
-
.title { font-weight: 650; }
|
|
152
|
-
.subtitle {
|
|
153
|
-
color: var(--muted);
|
|
154
|
-
font-size: 12px;
|
|
155
|
-
overflow: hidden;
|
|
156
|
-
text-overflow: ellipsis;
|
|
157
|
-
white-space: nowrap;
|
|
237
|
+
background: #ffffff;
|
|
158
238
|
}
|
|
159
239
|
.log {
|
|
160
240
|
min-height: 0;
|
|
161
241
|
overflow-y: auto;
|
|
162
242
|
overflow-x: hidden;
|
|
163
|
-
padding:
|
|
243
|
+
padding: var(--space-6) var(--space-7);
|
|
164
244
|
display: flex;
|
|
165
245
|
flex-direction: column;
|
|
166
|
-
gap:
|
|
246
|
+
gap: var(--space-4);
|
|
167
247
|
background: #fbfcfa;
|
|
168
248
|
}
|
|
169
249
|
.entry {
|
|
250
|
+
min-width: 0;
|
|
170
251
|
border: 1px solid var(--line);
|
|
171
|
-
border-radius:
|
|
172
|
-
padding:
|
|
252
|
+
border-radius: var(--radius-sm);
|
|
253
|
+
padding: var(--space-4) var(--space-5);
|
|
173
254
|
background: #fff;
|
|
255
|
+
box-shadow: var(--entry-shadow);
|
|
174
256
|
}
|
|
175
257
|
.entry.user {
|
|
176
|
-
border-color: #
|
|
177
|
-
background:
|
|
258
|
+
border-color: #edd17a;
|
|
259
|
+
background: var(--warn-soft);
|
|
260
|
+
color: #5a4714;
|
|
178
261
|
}
|
|
179
262
|
.entry.response,
|
|
180
263
|
.entry.assistant {
|
|
181
|
-
border-color: #
|
|
264
|
+
border-color: #d0ddd5;
|
|
182
265
|
background: #ffffff;
|
|
183
266
|
}
|
|
184
267
|
.entry.thinking {
|
|
185
|
-
border-color: #
|
|
186
|
-
background: #
|
|
268
|
+
border-color: #b9dcc5;
|
|
269
|
+
background: #f0fbf3;
|
|
270
|
+
color: #256b43;
|
|
271
|
+
animation: thinking-breathe 4.8s ease-in-out infinite;
|
|
187
272
|
}
|
|
188
273
|
.entry.control {
|
|
189
|
-
border-color: #
|
|
190
|
-
background:
|
|
274
|
+
border-color: #b9d8ea;
|
|
275
|
+
background: var(--info-soft);
|
|
276
|
+
color: var(--info);
|
|
191
277
|
}
|
|
192
278
|
.entry.tool {
|
|
193
|
-
border-color: #
|
|
194
|
-
background:
|
|
195
|
-
color:
|
|
279
|
+
border-color: #d7c8f0;
|
|
280
|
+
background: var(--violet-soft);
|
|
281
|
+
color: var(--violet);
|
|
196
282
|
font-size: 12px;
|
|
197
283
|
}
|
|
284
|
+
.entry.error {
|
|
285
|
+
border-color: #efb4ad;
|
|
286
|
+
background: var(--danger-soft);
|
|
287
|
+
color: var(--danger);
|
|
288
|
+
}
|
|
198
289
|
.text {
|
|
199
290
|
white-space: pre-wrap;
|
|
200
291
|
overflow-wrap: anywhere;
|
|
@@ -204,7 +295,7 @@
|
|
|
204
295
|
overflow-wrap: anywhere;
|
|
205
296
|
}
|
|
206
297
|
.markdown p {
|
|
207
|
-
margin: 0 0
|
|
298
|
+
margin: 0 0 var(--space-4);
|
|
208
299
|
}
|
|
209
300
|
.markdown p:last-child,
|
|
210
301
|
.markdown ul:last-child,
|
|
@@ -215,17 +306,17 @@
|
|
|
215
306
|
}
|
|
216
307
|
.markdown ul,
|
|
217
308
|
.markdown ol {
|
|
218
|
-
margin: 0 0
|
|
309
|
+
margin: 0 0 var(--space-4) 18px;
|
|
219
310
|
padding: 0;
|
|
220
311
|
}
|
|
221
312
|
.markdown li {
|
|
222
313
|
margin: 2px 0;
|
|
223
314
|
}
|
|
224
315
|
.markdown pre {
|
|
225
|
-
margin: 0 0
|
|
226
|
-
padding:
|
|
316
|
+
margin: 0 0 var(--space-4);
|
|
317
|
+
padding: var(--space-4) var(--space-5);
|
|
227
318
|
border: 1px solid var(--line);
|
|
228
|
-
border-radius:
|
|
319
|
+
border-radius: var(--radius-sm);
|
|
229
320
|
background: var(--code);
|
|
230
321
|
overflow-x: auto;
|
|
231
322
|
white-space: pre;
|
|
@@ -238,45 +329,56 @@
|
|
|
238
329
|
}
|
|
239
330
|
.markdown table {
|
|
240
331
|
width: 100%;
|
|
241
|
-
margin: 0 0
|
|
332
|
+
margin: 0 0 var(--space-4);
|
|
242
333
|
border-collapse: collapse;
|
|
243
334
|
font-size: 13px;
|
|
244
335
|
}
|
|
245
336
|
.markdown th,
|
|
246
337
|
.markdown td {
|
|
247
338
|
border: 1px solid var(--line);
|
|
248
|
-
padding:
|
|
339
|
+
padding: var(--space-3) var(--space-4);
|
|
249
340
|
text-align: left;
|
|
250
341
|
vertical-align: top;
|
|
251
342
|
}
|
|
252
343
|
.markdown th {
|
|
253
|
-
background: #
|
|
344
|
+
background: #eef7f1;
|
|
254
345
|
font-weight: 650;
|
|
255
346
|
}
|
|
256
347
|
.markdown a {
|
|
257
|
-
color: #
|
|
348
|
+
color: #176fa8;
|
|
258
349
|
text-decoration: none;
|
|
259
350
|
}
|
|
260
351
|
.markdown a:hover {
|
|
261
352
|
text-decoration: underline;
|
|
262
353
|
}
|
|
263
354
|
.composer {
|
|
355
|
+
min-width: 0;
|
|
264
356
|
border-top: 1px solid var(--line);
|
|
265
|
-
padding:
|
|
357
|
+
padding: var(--space-4) var(--space-6) var(--space-5);
|
|
266
358
|
display: grid;
|
|
267
|
-
gap:
|
|
359
|
+
gap: var(--space-3);
|
|
360
|
+
align-content: end;
|
|
268
361
|
background: #fff;
|
|
269
362
|
}
|
|
270
363
|
textarea {
|
|
364
|
+
min-width: 0;
|
|
271
365
|
resize: vertical;
|
|
272
|
-
min-height:
|
|
366
|
+
min-height: 40px;
|
|
273
367
|
max-height: 35vh;
|
|
274
368
|
width: 100%;
|
|
275
369
|
border: 1px solid var(--line);
|
|
276
|
-
border-radius:
|
|
277
|
-
padding:
|
|
370
|
+
border-radius: var(--radius-sm);
|
|
371
|
+
padding: var(--space-4) var(--space-5);
|
|
372
|
+
background: #ffffff;
|
|
373
|
+
color: var(--ink);
|
|
278
374
|
font: inherit;
|
|
279
375
|
line-height: 1.4;
|
|
376
|
+
overflow-y: auto;
|
|
377
|
+
}
|
|
378
|
+
textarea:focus {
|
|
379
|
+
border-color: #65b782;
|
|
380
|
+
box-shadow: 0 0 0 3px rgba(15, 143, 85, 0.13);
|
|
381
|
+
outline: 0;
|
|
280
382
|
}
|
|
281
383
|
.actions {
|
|
282
384
|
display: flex;
|
|
@@ -284,51 +386,107 @@
|
|
|
284
386
|
align-items: center;
|
|
285
387
|
}
|
|
286
388
|
.spinner {
|
|
287
|
-
|
|
389
|
+
position: relative;
|
|
390
|
+
display: inline-flex;
|
|
391
|
+
align-items: center;
|
|
392
|
+
justify-self: start;
|
|
393
|
+
align-self: end;
|
|
394
|
+
max-width: 100%;
|
|
395
|
+
min-height: 20px;
|
|
288
396
|
color: var(--muted);
|
|
289
397
|
font-size: 12px;
|
|
290
398
|
white-space: nowrap;
|
|
291
399
|
overflow: hidden;
|
|
292
400
|
text-overflow: ellipsis;
|
|
401
|
+
padding: var(--space-1) var(--space-4) var(--space-1) 18px;
|
|
402
|
+
border-radius: 999px;
|
|
403
|
+
background: rgba(240, 250, 244, 0.96);
|
|
404
|
+
border: 1px solid rgba(172, 216, 188, 0.9);
|
|
405
|
+
}
|
|
406
|
+
.spinner:empty {
|
|
407
|
+
display: none;
|
|
408
|
+
}
|
|
409
|
+
.spinner:not(:empty) {
|
|
410
|
+
margin-bottom: var(--space-1);
|
|
411
|
+
}
|
|
412
|
+
.spinner:not(:empty)::before {
|
|
413
|
+
content: "";
|
|
414
|
+
position: absolute;
|
|
415
|
+
left: 7px;
|
|
416
|
+
top: 50%;
|
|
417
|
+
width: 6px;
|
|
418
|
+
height: 6px;
|
|
419
|
+
margin-top: -3px;
|
|
420
|
+
border-radius: 999px;
|
|
421
|
+
background: var(--accent);
|
|
422
|
+
animation: spinner-pulse 1.2s cubic-bezier(0.4, 0, 0.2, 1) infinite;
|
|
423
|
+
}
|
|
424
|
+
@keyframes spinner-pulse {
|
|
425
|
+
0%, 100% {
|
|
426
|
+
opacity: 0.45;
|
|
427
|
+
transform: scale(0.82);
|
|
428
|
+
}
|
|
429
|
+
50% {
|
|
430
|
+
opacity: 1;
|
|
431
|
+
transform: scale(1.08);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
@keyframes thinking-breathe {
|
|
435
|
+
0%, 100% {
|
|
436
|
+
border-color: #b9dcc5;
|
|
437
|
+
box-shadow: var(--entry-shadow);
|
|
438
|
+
}
|
|
439
|
+
50% {
|
|
440
|
+
border-color: #72d494;
|
|
441
|
+
box-shadow:
|
|
442
|
+
0 0 0 2px rgba(22, 168, 90, 0.16),
|
|
443
|
+
0 2px 7px rgba(25, 35, 31, 0.08);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
@media (prefers-reduced-motion: reduce) {
|
|
447
|
+
.entry.thinking,
|
|
448
|
+
.tab.running::before,
|
|
449
|
+
.spinner:not(:empty)::before {
|
|
450
|
+
animation: none;
|
|
451
|
+
}
|
|
452
|
+
.tab.running::before,
|
|
453
|
+
.spinner:not(:empty)::before {
|
|
454
|
+
opacity: 0.8;
|
|
455
|
+
transform: none;
|
|
456
|
+
}
|
|
293
457
|
}
|
|
294
458
|
code {
|
|
295
459
|
background: var(--code);
|
|
296
460
|
border: 1px solid var(--line);
|
|
297
|
-
border-radius:
|
|
461
|
+
border-radius: var(--radius-sm);
|
|
298
462
|
padding: 1px 4px;
|
|
299
463
|
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
300
464
|
font-size: 12px;
|
|
301
465
|
}
|
|
302
466
|
@media (max-width: 900px) {
|
|
303
467
|
body { overflow: auto; height: auto; }
|
|
304
|
-
.workspace {
|
|
305
|
-
.board { height: 56vh; border-right: 0; border-bottom: 1px solid var(--line); }
|
|
306
|
-
.splitter { display: none; }
|
|
307
|
-
.chat { min-height: 44vh; }
|
|
468
|
+
.workspace { min-width: 0; }
|
|
308
469
|
}
|
|
309
470
|
</style>
|
|
310
471
|
</head>
|
|
311
472
|
<body>
|
|
312
473
|
<div class="workspace">
|
|
313
|
-
<
|
|
474
|
+
<section class="board-pane" aria-label="workspace board">
|
|
475
|
+
<div class="boardbar">Board: <code>__BOARD_LABEL__</code></div>
|
|
476
|
+
<iframe id="boardFrame" class="board" src="board" title="board"></iframe>
|
|
477
|
+
</section>
|
|
314
478
|
<button id="splitter" class="splitter" type="button" aria-label="Resize chat panel"></button>
|
|
315
479
|
<section class="chat" aria-label="pycodex chat">
|
|
316
480
|
<div class="topbar">
|
|
317
481
|
<div class="tabbar">
|
|
318
|
-
<div id="tabs" class="tabs"></div>
|
|
482
|
+
<div id="tabs" class="tabs" role="tablist"></div>
|
|
319
483
|
<button id="newTab" class="tab-new" type="button" title="New session" aria-label="New session">+</button>
|
|
320
484
|
</div>
|
|
321
|
-
<div>
|
|
322
|
-
<div class="title">pycodex</div>
|
|
323
|
-
<div class="subtitle">Board: <code>__BOARD_LABEL__</code></div>
|
|
324
|
-
</div>
|
|
325
485
|
</div>
|
|
326
486
|
<div id="log" class="log"></div>
|
|
327
487
|
<form id="composer" class="composer">
|
|
488
|
+
<div id="spinner" class="spinner" role="status" aria-live="polite"></div>
|
|
328
489
|
<textarea id="prompt" placeholder="Ask pycodex or type /help..."></textarea>
|
|
329
|
-
<div class="actions">
|
|
330
|
-
<div id="spinner" class="spinner"></div>
|
|
331
|
-
</div>
|
|
332
490
|
</form>
|
|
333
491
|
</section>
|
|
334
492
|
</div>
|
|
@@ -346,17 +504,18 @@
|
|
|
346
504
|
const newTabButton = document.getElementById("newTab");
|
|
347
505
|
let pollTimer = null;
|
|
348
506
|
let boardPollTimer = null;
|
|
507
|
+
let pollInFlight = false;
|
|
508
|
+
let boardPollInFlight = false;
|
|
349
509
|
let lastBoardSignature = "";
|
|
350
510
|
let lastRenderedSignature = "";
|
|
351
511
|
let resizeStart = null;
|
|
352
|
-
let spinnerTimer = null;
|
|
353
|
-
let spinnerFrameIndex = 0;
|
|
354
512
|
let spinnerText = "";
|
|
355
513
|
let activeSessionId = "";
|
|
356
514
|
let sessions = [];
|
|
515
|
+
let isComposing = false;
|
|
357
516
|
const sessionState = new Map();
|
|
358
517
|
let restoreScrollTop = null;
|
|
359
|
-
|
|
518
|
+
let suppressScrollSave = false;
|
|
360
519
|
const markdownRenderer = window.markdownit
|
|
361
520
|
? window.markdownit({html: false, linkify: true, breaks: false})
|
|
362
521
|
: null;
|
|
@@ -412,17 +571,36 @@
|
|
|
412
571
|
if (stored > 0) setChatWidth(stored);
|
|
413
572
|
}
|
|
414
573
|
|
|
574
|
+
function centerSplit() {
|
|
575
|
+
setChatWidth(Math.max(0, (window.innerWidth - 8) / 2));
|
|
576
|
+
}
|
|
577
|
+
|
|
415
578
|
function setChatWidth(width) {
|
|
416
579
|
const viewportWidth = window.innerWidth || 0;
|
|
417
|
-
const
|
|
418
|
-
const
|
|
419
|
-
const
|
|
580
|
+
const splitterWidth = 8;
|
|
581
|
+
const gutterWidth = Math.min(24, Math.max(12, Math.round(viewportWidth * 0.06)));
|
|
582
|
+
const availableWidth = Math.max(0, viewportWidth - splitterWidth);
|
|
583
|
+
const maxChatWidth = Math.max(gutterWidth, availableWidth - gutterWidth);
|
|
584
|
+
const collapseWidth = Math.min(
|
|
585
|
+
280,
|
|
586
|
+
Math.max(120, Math.round(viewportWidth * 0.25)),
|
|
587
|
+
);
|
|
588
|
+
let nextWidth = Math.max(gutterWidth, Math.min(maxChatWidth, Math.round(width)));
|
|
589
|
+
let collapsed = false;
|
|
590
|
+
if (nextWidth < collapseWidth) {
|
|
591
|
+
nextWidth = gutterWidth;
|
|
592
|
+
collapsed = true;
|
|
593
|
+
} else if (availableWidth - nextWidth < collapseWidth) {
|
|
594
|
+
nextWidth = maxChatWidth;
|
|
595
|
+
collapsed = true;
|
|
596
|
+
}
|
|
420
597
|
workspace.style.setProperty("--chat-width", `${nextWidth}px`);
|
|
598
|
+
workspace.classList.toggle("collapsed", collapsed);
|
|
421
599
|
window.localStorage.setItem("pycodex.workspace.chatWidth", String(nextWidth));
|
|
422
600
|
}
|
|
423
601
|
|
|
424
602
|
function startResize(event) {
|
|
425
|
-
if (!workspace
|
|
603
|
+
if (!workspace) return;
|
|
426
604
|
const chatWidth = document.querySelector(".chat").getBoundingClientRect().width;
|
|
427
605
|
resizeStart = { pointerId: event.pointerId, startX: event.clientX, startWidth: chatWidth };
|
|
428
606
|
splitter.setPointerCapture(event.pointerId);
|
|
@@ -451,25 +629,7 @@
|
|
|
451
629
|
|
|
452
630
|
function setSpinner(text) {
|
|
453
631
|
spinnerText = String(text || "").trim();
|
|
454
|
-
|
|
455
|
-
renderSpinner();
|
|
456
|
-
if (spinnerText && !spinnerTimer) {
|
|
457
|
-
spinnerTimer = setInterval(renderSpinner, 120);
|
|
458
|
-
}
|
|
459
|
-
if (!spinnerText && spinnerTimer) {
|
|
460
|
-
clearInterval(spinnerTimer);
|
|
461
|
-
spinnerTimer = null;
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
function renderSpinner() {
|
|
466
|
-
if (!spinnerText) {
|
|
467
|
-
spinner.textContent = "";
|
|
468
|
-
return;
|
|
469
|
-
}
|
|
470
|
-
const frame = spinnerFrames[spinnerFrameIndex % spinnerFrames.length];
|
|
471
|
-
spinnerFrameIndex += 1;
|
|
472
|
-
spinner.textContent = `${frame} ${spinnerText}`;
|
|
632
|
+
spinner.textContent = spinnerText;
|
|
473
633
|
}
|
|
474
634
|
|
|
475
635
|
function isNearBottom() {
|
|
@@ -480,10 +640,18 @@
|
|
|
480
640
|
log.scrollTop = log.scrollHeight;
|
|
481
641
|
}
|
|
482
642
|
|
|
643
|
+
function resizePrompt() {
|
|
644
|
+
prompt.style.height = "auto";
|
|
645
|
+
const minHeight = 40;
|
|
646
|
+
const maxHeight = Math.max(minHeight, Math.round(window.innerHeight * 0.35));
|
|
647
|
+
const nextHeight = Math.max(minHeight, Math.min(prompt.scrollHeight, maxHeight));
|
|
648
|
+
prompt.style.height = `${nextHeight}px`;
|
|
649
|
+
}
|
|
650
|
+
|
|
483
651
|
function stateForSession(sessionId) {
|
|
484
652
|
const key = String(sessionId || "");
|
|
485
653
|
if (!sessionState.has(key)) {
|
|
486
|
-
sessionState.set(key, {lastRenderedSignature: "", scrollTop:
|
|
654
|
+
sessionState.set(key, {lastRenderedSignature: "", scrollTop: null, draft: ""});
|
|
487
655
|
}
|
|
488
656
|
return sessionState.get(key);
|
|
489
657
|
}
|
|
@@ -491,7 +659,9 @@
|
|
|
491
659
|
function saveActiveSessionUiState() {
|
|
492
660
|
if (!activeSessionId) return;
|
|
493
661
|
const state = stateForSession(activeSessionId);
|
|
494
|
-
|
|
662
|
+
if (log.scrollHeight > log.clientHeight) {
|
|
663
|
+
state.scrollTop = log.scrollTop;
|
|
664
|
+
}
|
|
495
665
|
state.draft = prompt.value;
|
|
496
666
|
state.lastRenderedSignature = lastRenderedSignature;
|
|
497
667
|
}
|
|
@@ -509,10 +679,14 @@
|
|
|
509
679
|
sessions.forEach((session, index) => {
|
|
510
680
|
const sessionId = session.id || "";
|
|
511
681
|
const tab = document.createElement("div");
|
|
512
|
-
tab.className =
|
|
682
|
+
tab.className =
|
|
683
|
+
"tab" +
|
|
684
|
+
(sessionId === activeSessionId ? " active" : "") +
|
|
685
|
+
(session.running ? " running" : "");
|
|
513
686
|
tab.title = session.title || `Session ${index + 1}`;
|
|
514
687
|
tab.setAttribute("role", "tab");
|
|
515
688
|
tab.setAttribute("aria-selected", sessionId === activeSessionId ? "true" : "false");
|
|
689
|
+
if (session.running) tab.setAttribute("aria-busy", "true");
|
|
516
690
|
tab.addEventListener("click", () => switchSession(sessionId));
|
|
517
691
|
|
|
518
692
|
const label = document.createElement("span");
|
|
@@ -538,11 +712,8 @@
|
|
|
538
712
|
|
|
539
713
|
function messageSignature(snapshot) {
|
|
540
714
|
const turns = (snapshot && snapshot.turns) || [];
|
|
541
|
-
return JSON.stringify(
|
|
542
|
-
|
|
543
|
-
spinner: snapshot.spinner || "",
|
|
544
|
-
title: snapshot.title || "",
|
|
545
|
-
turns: turns.map((item) => [
|
|
715
|
+
return JSON.stringify(
|
|
716
|
+
turns.map((item) => [
|
|
546
717
|
item.submission_id || "",
|
|
547
718
|
item.turn_id || "",
|
|
548
719
|
item.prompt || "",
|
|
@@ -552,17 +723,23 @@
|
|
|
552
723
|
item.error || "",
|
|
553
724
|
item.kind || "",
|
|
554
725
|
]),
|
|
555
|
-
|
|
726
|
+
);
|
|
556
727
|
}
|
|
557
728
|
|
|
558
729
|
async function pollSession() {
|
|
730
|
+
if (pollInFlight) return;
|
|
731
|
+
pollInFlight = true;
|
|
732
|
+
const requestedSessionId = activeSessionId;
|
|
559
733
|
try {
|
|
560
|
-
const path =
|
|
561
|
-
? `api/session?session_id=${encodeURIComponent(
|
|
734
|
+
const path = requestedSessionId
|
|
735
|
+
? `api/session?session_id=${encodeURIComponent(requestedSessionId)}`
|
|
562
736
|
: "api/session";
|
|
563
737
|
const response = await fetch(relativeUrl(path));
|
|
564
738
|
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
565
739
|
const payload = await response.json();
|
|
740
|
+
if (requestedSessionId && requestedSessionId !== activeSessionId) {
|
|
741
|
+
return;
|
|
742
|
+
}
|
|
566
743
|
if (payload.session_id && payload.session_id !== activeSessionId) {
|
|
567
744
|
activeSessionId = payload.session_id;
|
|
568
745
|
}
|
|
@@ -570,6 +747,8 @@
|
|
|
570
747
|
renderSnapshot(payload.snapshot);
|
|
571
748
|
} catch (error) {
|
|
572
749
|
setSpinner(`poll error: ${error.message}`);
|
|
750
|
+
} finally {
|
|
751
|
+
pollInFlight = false;
|
|
573
752
|
}
|
|
574
753
|
}
|
|
575
754
|
|
|
@@ -586,6 +765,8 @@
|
|
|
586
765
|
}
|
|
587
766
|
|
|
588
767
|
async function pollBoard() {
|
|
768
|
+
if (boardPollInFlight) return;
|
|
769
|
+
boardPollInFlight = true;
|
|
589
770
|
try {
|
|
590
771
|
const response = await fetch(relativeUrl("api/board"));
|
|
591
772
|
if (!response.ok) return;
|
|
@@ -602,6 +783,8 @@
|
|
|
602
783
|
}
|
|
603
784
|
} catch (_error) {
|
|
604
785
|
return;
|
|
786
|
+
} finally {
|
|
787
|
+
boardPollInFlight = false;
|
|
605
788
|
}
|
|
606
789
|
}
|
|
607
790
|
|
|
@@ -623,25 +806,30 @@
|
|
|
623
806
|
lastRenderedSignature = signature;
|
|
624
807
|
const shouldStick = isNearBottom();
|
|
625
808
|
const previousScrollTop = log.scrollTop;
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
turns.
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
809
|
+
suppressScrollSave = true;
|
|
810
|
+
try {
|
|
811
|
+
log.textContent = "";
|
|
812
|
+
const turns = Array.isArray(snapshot.turns) ? snapshot.turns : [];
|
|
813
|
+
if (turns.length) {
|
|
814
|
+
turns.forEach((turn) => renderTurn(turn, shouldStick));
|
|
815
|
+
}
|
|
816
|
+
setSpinner(snapshot.spinner || "");
|
|
817
|
+
if (restoreScrollTop !== null) {
|
|
818
|
+
log.scrollTop = Math.min(
|
|
819
|
+
restoreScrollTop,
|
|
820
|
+
Math.max(0, log.scrollHeight - log.clientHeight),
|
|
821
|
+
);
|
|
822
|
+
restoreScrollTop = null;
|
|
823
|
+
} else if (shouldStick) {
|
|
824
|
+
scrollToBottom();
|
|
825
|
+
} else {
|
|
826
|
+
log.scrollTop = Math.min(
|
|
827
|
+
previousScrollTop,
|
|
828
|
+
Math.max(0, log.scrollHeight - log.clientHeight),
|
|
829
|
+
);
|
|
830
|
+
}
|
|
831
|
+
} finally {
|
|
832
|
+
suppressScrollSave = false;
|
|
645
833
|
}
|
|
646
834
|
state.scrollTop = log.scrollTop;
|
|
647
835
|
}
|
|
@@ -665,7 +853,7 @@
|
|
|
665
853
|
);
|
|
666
854
|
}
|
|
667
855
|
if (turn.thinking) addEntry("thinking", turn.thinking || "", "thinking", shouldStick);
|
|
668
|
-
if (turn.error) addEntry("error", turn.error || "", "
|
|
856
|
+
if (turn.error) addEntry("error", turn.error || "", "error", shouldStick);
|
|
669
857
|
}
|
|
670
858
|
|
|
671
859
|
async function submitPrompt(text) {
|
|
@@ -676,7 +864,7 @@
|
|
|
676
864
|
});
|
|
677
865
|
const payload = await response.json();
|
|
678
866
|
if (!response.ok || !payload.ok) {
|
|
679
|
-
addEntry("error", payload.error || `HTTP ${response.status}`, "
|
|
867
|
+
addEntry("error", payload.error || `HTTP ${response.status}`, "error");
|
|
680
868
|
setSpinner("");
|
|
681
869
|
return;
|
|
682
870
|
}
|
|
@@ -697,9 +885,12 @@
|
|
|
697
885
|
state.lastRenderedSignature = "";
|
|
698
886
|
lastRenderedSignature = "";
|
|
699
887
|
prompt.value = state.draft || "";
|
|
888
|
+
resizePrompt();
|
|
889
|
+
suppressScrollSave = true;
|
|
700
890
|
log.textContent = "";
|
|
891
|
+
suppressScrollSave = false;
|
|
701
892
|
setSpinner("");
|
|
702
|
-
restoreScrollTop = state.scrollTop
|
|
893
|
+
restoreScrollTop = state.scrollTop;
|
|
703
894
|
await pollSession();
|
|
704
895
|
renderTabs();
|
|
705
896
|
}
|
|
@@ -709,17 +900,18 @@
|
|
|
709
900
|
const response = await fetch(relativeUrl("api/sessions"), {method: "POST"});
|
|
710
901
|
const payload = await response.json();
|
|
711
902
|
if (!response.ok || !payload.ok) {
|
|
712
|
-
addEntry("error", payload.error || `HTTP ${response.status}`, "
|
|
903
|
+
addEntry("error", payload.error || `HTTP ${response.status}`, "error");
|
|
713
904
|
return;
|
|
714
905
|
}
|
|
715
906
|
activeSessionId = payload.session_id || "";
|
|
716
907
|
const state = stateForSession(activeSessionId);
|
|
717
908
|
state.draft = "";
|
|
718
|
-
state.scrollTop =
|
|
909
|
+
state.scrollTop = null;
|
|
719
910
|
state.lastRenderedSignature = "";
|
|
720
911
|
lastRenderedSignature = "";
|
|
721
912
|
prompt.value = "";
|
|
722
|
-
|
|
913
|
+
resizePrompt();
|
|
914
|
+
restoreScrollTop = null;
|
|
723
915
|
updateSessions(payload.sessions || []);
|
|
724
916
|
renderSnapshot(payload.snapshot);
|
|
725
917
|
}
|
|
@@ -731,7 +923,7 @@
|
|
|
731
923
|
});
|
|
732
924
|
const payload = await response.json();
|
|
733
925
|
if (!response.ok || !payload.ok) {
|
|
734
|
-
addEntry("error", payload.error || `HTTP ${response.status}`, "
|
|
926
|
+
addEntry("error", payload.error || `HTTP ${response.status}`, "error");
|
|
735
927
|
return;
|
|
736
928
|
}
|
|
737
929
|
sessionState.delete(sessionId);
|
|
@@ -740,7 +932,10 @@
|
|
|
740
932
|
activeSessionId = sessions.length ? sessions[0].id || "" : "";
|
|
741
933
|
lastRenderedSignature = "";
|
|
742
934
|
prompt.value = "";
|
|
935
|
+
resizePrompt();
|
|
936
|
+
suppressScrollSave = true;
|
|
743
937
|
log.textContent = "";
|
|
938
|
+
suppressScrollSave = false;
|
|
744
939
|
await pollSession();
|
|
745
940
|
} else {
|
|
746
941
|
renderTabs();
|
|
@@ -752,20 +947,32 @@
|
|
|
752
947
|
const text = prompt.value.trim();
|
|
753
948
|
if (!text) return;
|
|
754
949
|
prompt.value = "";
|
|
950
|
+
resizePrompt();
|
|
755
951
|
stateForSession(activeSessionId).draft = "";
|
|
756
952
|
submitPrompt(text);
|
|
757
953
|
});
|
|
758
954
|
|
|
759
955
|
prompt.addEventListener("input", () => {
|
|
760
956
|
stateForSession(activeSessionId).draft = prompt.value;
|
|
957
|
+
resizePrompt();
|
|
958
|
+
});
|
|
959
|
+
|
|
960
|
+
prompt.addEventListener("compositionstart", () => {
|
|
961
|
+
isComposing = true;
|
|
962
|
+
});
|
|
963
|
+
|
|
964
|
+
prompt.addEventListener("compositionend", () => {
|
|
965
|
+
isComposing = false;
|
|
761
966
|
});
|
|
762
967
|
|
|
763
968
|
log.addEventListener("scroll", () => {
|
|
969
|
+
if (suppressScrollSave) return;
|
|
764
970
|
if (!activeSessionId) return;
|
|
765
971
|
stateForSession(activeSessionId).scrollTop = log.scrollTop;
|
|
766
972
|
});
|
|
767
973
|
|
|
768
974
|
prompt.addEventListener("keydown", (event) => {
|
|
975
|
+
if (event.isComposing || isComposing || event.keyCode === 229) return;
|
|
769
976
|
if (event.key === "Enter" && !event.shiftKey) {
|
|
770
977
|
event.preventDefault();
|
|
771
978
|
form.requestSubmit();
|
|
@@ -776,13 +983,16 @@
|
|
|
776
983
|
splitter.addEventListener("pointermove", updateResize);
|
|
777
984
|
splitter.addEventListener("pointerup", endResize);
|
|
778
985
|
splitter.addEventListener("pointercancel", endResize);
|
|
986
|
+
splitter.addEventListener("dblclick", centerSplit);
|
|
779
987
|
newTabButton.addEventListener("click", createSession);
|
|
780
988
|
window.addEventListener("resize", () => {
|
|
781
989
|
const current = Number(window.localStorage.getItem("pycodex.workspace.chatWidth") || 0);
|
|
782
990
|
if (current > 0) setChatWidth(current);
|
|
991
|
+
resizePrompt();
|
|
783
992
|
});
|
|
784
993
|
|
|
785
994
|
loadChatWidth();
|
|
995
|
+
resizePrompt();
|
|
786
996
|
startPolling();
|
|
787
997
|
startBoardPolling();
|
|
788
998
|
</script>
|