python-codex 0.1.14__py3-none-any.whl → 0.2.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.
@@ -16,11 +16,12 @@
16
16
  --accent-ink: #ffffff;
17
17
  --code: #f6f8fa;
18
18
  --chat-width: min(42vw, 760px);
19
+ --splitter-width: 8px;
19
20
  }
20
21
  * { box-sizing: border-box; }
21
22
  body {
22
23
  margin: 0;
23
- height: 100vh;
24
+ height: 100dvh;
24
25
  overflow: hidden;
25
26
  background: var(--bg);
26
27
  color: var(--ink);
@@ -28,19 +29,40 @@
28
29
  }
29
30
  .workspace {
30
31
  display: grid;
31
- grid-template-columns: minmax(360px, 1fr) 8px minmax(340px, var(--chat-width));
32
- height: 100vh;
33
- min-width: 760px;
32
+ grid-template-columns: minmax(0, 1fr) var(--splitter-width) var(--chat-width);
33
+ height: 100dvh;
34
+ min-width: 0;
35
+ }
36
+ .workspace.collapsed .splitter::after {
37
+ background: #7e9187;
38
+ }
39
+ .board-pane {
40
+ min-width: 0;
41
+ height: 100dvh;
42
+ display: grid;
43
+ grid-template-rows: auto minmax(0, 1fr);
44
+ background: #fff;
45
+ }
46
+ .boardbar {
47
+ min-width: 0;
48
+ border-bottom: 1px solid var(--line);
49
+ padding: 8px 12px;
50
+ color: var(--muted);
51
+ font-size: 12px;
52
+ white-space: nowrap;
53
+ overflow: hidden;
54
+ text-overflow: ellipsis;
34
55
  }
35
56
  .board {
36
57
  border: 0;
58
+ min-width: 0;
37
59
  width: 100%;
38
60
  height: 100%;
39
61
  background: #fff;
40
62
  }
41
63
  .splitter {
42
64
  width: 8px;
43
- height: 100vh;
65
+ height: 100dvh;
44
66
  border: 0;
45
67
  border-left: 1px solid var(--line);
46
68
  border-right: 1px solid var(--line);
@@ -54,7 +76,7 @@
54
76
  display: block;
55
77
  width: 2px;
56
78
  height: 42px;
57
- margin: calc(50vh - 21px) auto 0;
79
+ margin: calc(50dvh - 21px) auto 0;
58
80
  border-radius: 999px;
59
81
  background: #b9c1ba;
60
82
  }
@@ -64,14 +86,16 @@
64
86
  user-select: none;
65
87
  }
66
88
  .chat {
89
+ position: relative;
67
90
  min-width: 0;
68
- height: 100vh;
91
+ height: 100dvh;
69
92
  overflow: hidden;
70
93
  display: grid;
71
94
  grid-template-rows: auto minmax(0, 1fr) auto;
72
95
  background: var(--panel);
73
96
  }
74
97
  .topbar {
98
+ min-width: 0;
75
99
  padding: 12px 14px;
76
100
  border-bottom: 1px solid var(--line);
77
101
  display: grid;
@@ -148,14 +172,6 @@
148
172
  color: var(--ink);
149
173
  background: #f3f6f4;
150
174
  }
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;
158
- }
159
175
  .log {
160
176
  min-height: 0;
161
177
  overflow-y: auto;
@@ -167,6 +183,7 @@
167
183
  background: #fbfcfa;
168
184
  }
169
185
  .entry {
186
+ min-width: 0;
170
187
  border: 1px solid var(--line);
171
188
  border-radius: 6px;
172
189
  padding: 10px 11px;
@@ -195,6 +212,11 @@
195
212
  color: #4c4261;
196
213
  font-size: 12px;
197
214
  }
215
+ .entry.error {
216
+ border-color: #e4b6b6;
217
+ background: #fff7f7;
218
+ color: #7a2c2c;
219
+ }
198
220
  .text {
199
221
  white-space: pre-wrap;
200
222
  overflow-wrap: anywhere;
@@ -261,13 +283,15 @@
261
283
  text-decoration: underline;
262
284
  }
263
285
  .composer {
286
+ min-width: 0;
264
287
  border-top: 1px solid var(--line);
265
- padding: 12px;
288
+ padding: 12px 12px 28px;
266
289
  display: grid;
267
290
  gap: 8px;
268
291
  background: #fff;
269
292
  }
270
293
  textarea {
294
+ min-width: 0;
271
295
  resize: vertical;
272
296
  min-height: 88px;
273
297
  max-height: 35vh;
@@ -284,6 +308,10 @@
284
308
  align-items: center;
285
309
  }
286
310
  .spinner {
311
+ position: absolute;
312
+ left: 12px;
313
+ right: 12px;
314
+ bottom: 6px;
287
315
  min-height: 18px;
288
316
  color: var(--muted);
289
317
  font-size: 12px;
@@ -301,16 +329,16 @@
301
329
  }
302
330
  @media (max-width: 900px) {
303
331
  body { overflow: auto; height: auto; }
304
- .workspace { display: block; min-width: 0; height: auto; }
305
- .board { height: 56vh; border-right: 0; border-bottom: 1px solid var(--line); }
306
- .splitter { display: none; }
307
- .chat { min-height: 44vh; }
332
+ .workspace { min-width: 0; }
308
333
  }
309
334
  </style>
310
335
  </head>
311
336
  <body>
312
337
  <div class="workspace">
313
- <iframe id="boardFrame" class="board" src="board" title="board"></iframe>
338
+ <section class="board-pane" aria-label="workspace board">
339
+ <div class="boardbar">Board: <code>__BOARD_LABEL__</code></div>
340
+ <iframe id="boardFrame" class="board" src="board" title="board"></iframe>
341
+ </section>
314
342
  <button id="splitter" class="splitter" type="button" aria-label="Resize chat panel"></button>
315
343
  <section class="chat" aria-label="pycodex chat">
316
344
  <div class="topbar">
@@ -318,18 +346,12 @@
318
346
  <div id="tabs" class="tabs"></div>
319
347
  <button id="newTab" class="tab-new" type="button" title="New session" aria-label="New session">+</button>
320
348
  </div>
321
- <div>
322
- <div class="title">pycodex</div>
323
- <div class="subtitle">Board: <code>__BOARD_LABEL__</code></div>
324
- </div>
325
349
  </div>
326
350
  <div id="log" class="log"></div>
327
351
  <form id="composer" class="composer">
328
352
  <textarea id="prompt" placeholder="Ask pycodex or type /help..."></textarea>
329
- <div class="actions">
330
- <div id="spinner" class="spinner"></div>
331
- </div>
332
353
  </form>
354
+ <div id="spinner" class="spinner"></div>
333
355
  </section>
334
356
  </div>
335
357
  <script src="https://cdn.jsdelivr.net/npm/markdown-it@14.1.0/dist/markdown-it.min.js"></script>
@@ -346,6 +368,8 @@
346
368
  const newTabButton = document.getElementById("newTab");
347
369
  let pollTimer = null;
348
370
  let boardPollTimer = null;
371
+ let pollInFlight = false;
372
+ let boardPollInFlight = false;
349
373
  let lastBoardSignature = "";
350
374
  let lastRenderedSignature = "";
351
375
  let resizeStart = null;
@@ -354,8 +378,10 @@
354
378
  let spinnerText = "";
355
379
  let activeSessionId = "";
356
380
  let sessions = [];
381
+ let isComposing = false;
357
382
  const sessionState = new Map();
358
383
  let restoreScrollTop = null;
384
+ let suppressScrollSave = false;
359
385
  const spinnerFrames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
360
386
  const markdownRenderer = window.markdownit
361
387
  ? window.markdownit({html: false, linkify: true, breaks: false})
@@ -412,17 +438,36 @@
412
438
  if (stored > 0) setChatWidth(stored);
413
439
  }
414
440
 
441
+ function centerSplit() {
442
+ setChatWidth(Math.max(0, (window.innerWidth - 8) / 2));
443
+ }
444
+
415
445
  function setChatWidth(width) {
416
446
  const viewportWidth = window.innerWidth || 0;
417
- const minWidth = Math.min(340, Math.max(260, viewportWidth - 420));
418
- const maxWidth = Math.max(minWidth, viewportWidth - 420);
419
- const nextWidth = Math.max(minWidth, Math.min(maxWidth, Math.round(width)));
447
+ const splitterWidth = 8;
448
+ const gutterWidth = Math.min(24, Math.max(12, Math.round(viewportWidth * 0.06)));
449
+ const availableWidth = Math.max(0, viewportWidth - splitterWidth);
450
+ const maxChatWidth = Math.max(gutterWidth, availableWidth - gutterWidth);
451
+ const collapseWidth = Math.min(
452
+ 280,
453
+ Math.max(120, Math.round(viewportWidth * 0.25)),
454
+ );
455
+ let nextWidth = Math.max(gutterWidth, Math.min(maxChatWidth, Math.round(width)));
456
+ let collapsed = false;
457
+ if (nextWidth < collapseWidth) {
458
+ nextWidth = gutterWidth;
459
+ collapsed = true;
460
+ } else if (availableWidth - nextWidth < collapseWidth) {
461
+ nextWidth = maxChatWidth;
462
+ collapsed = true;
463
+ }
420
464
  workspace.style.setProperty("--chat-width", `${nextWidth}px`);
465
+ workspace.classList.toggle("collapsed", collapsed);
421
466
  window.localStorage.setItem("pycodex.workspace.chatWidth", String(nextWidth));
422
467
  }
423
468
 
424
469
  function startResize(event) {
425
- if (!workspace || window.innerWidth <= 900) return;
470
+ if (!workspace) return;
426
471
  const chatWidth = document.querySelector(".chat").getBoundingClientRect().width;
427
472
  resizeStart = { pointerId: event.pointerId, startX: event.clientX, startWidth: chatWidth };
428
473
  splitter.setPointerCapture(event.pointerId);
@@ -483,7 +528,7 @@
483
528
  function stateForSession(sessionId) {
484
529
  const key = String(sessionId || "");
485
530
  if (!sessionState.has(key)) {
486
- sessionState.set(key, {lastRenderedSignature: "", scrollTop: 0, draft: ""});
531
+ sessionState.set(key, {lastRenderedSignature: "", scrollTop: null, draft: ""});
487
532
  }
488
533
  return sessionState.get(key);
489
534
  }
@@ -491,7 +536,9 @@
491
536
  function saveActiveSessionUiState() {
492
537
  if (!activeSessionId) return;
493
538
  const state = stateForSession(activeSessionId);
494
- state.scrollTop = log.scrollTop;
539
+ if (log.scrollHeight > log.clientHeight) {
540
+ state.scrollTop = log.scrollTop;
541
+ }
495
542
  state.draft = prompt.value;
496
543
  state.lastRenderedSignature = lastRenderedSignature;
497
544
  }
@@ -538,11 +585,8 @@
538
585
 
539
586
  function messageSignature(snapshot) {
540
587
  const turns = (snapshot && snapshot.turns) || [];
541
- return JSON.stringify({
542
- running: !!snapshot.running,
543
- spinner: snapshot.spinner || "",
544
- title: snapshot.title || "",
545
- turns: turns.map((item) => [
588
+ return JSON.stringify(
589
+ turns.map((item) => [
546
590
  item.submission_id || "",
547
591
  item.turn_id || "",
548
592
  item.prompt || "",
@@ -552,17 +596,23 @@
552
596
  item.error || "",
553
597
  item.kind || "",
554
598
  ]),
555
- });
599
+ );
556
600
  }
557
601
 
558
602
  async function pollSession() {
603
+ if (pollInFlight) return;
604
+ pollInFlight = true;
605
+ const requestedSessionId = activeSessionId;
559
606
  try {
560
- const path = activeSessionId
561
- ? `api/session?session_id=${encodeURIComponent(activeSessionId)}`
607
+ const path = requestedSessionId
608
+ ? `api/session?session_id=${encodeURIComponent(requestedSessionId)}`
562
609
  : "api/session";
563
610
  const response = await fetch(relativeUrl(path));
564
611
  if (!response.ok) throw new Error(`HTTP ${response.status}`);
565
612
  const payload = await response.json();
613
+ if (requestedSessionId && requestedSessionId !== activeSessionId) {
614
+ return;
615
+ }
566
616
  if (payload.session_id && payload.session_id !== activeSessionId) {
567
617
  activeSessionId = payload.session_id;
568
618
  }
@@ -570,6 +620,8 @@
570
620
  renderSnapshot(payload.snapshot);
571
621
  } catch (error) {
572
622
  setSpinner(`poll error: ${error.message}`);
623
+ } finally {
624
+ pollInFlight = false;
573
625
  }
574
626
  }
575
627
 
@@ -586,6 +638,8 @@
586
638
  }
587
639
 
588
640
  async function pollBoard() {
641
+ if (boardPollInFlight) return;
642
+ boardPollInFlight = true;
589
643
  try {
590
644
  const response = await fetch(relativeUrl("api/board"));
591
645
  if (!response.ok) return;
@@ -602,6 +656,8 @@
602
656
  }
603
657
  } catch (_error) {
604
658
  return;
659
+ } finally {
660
+ boardPollInFlight = false;
605
661
  }
606
662
  }
607
663
 
@@ -623,25 +679,30 @@
623
679
  lastRenderedSignature = signature;
624
680
  const shouldStick = isNearBottom();
625
681
  const previousScrollTop = log.scrollTop;
626
- log.textContent = "";
627
- const turns = Array.isArray(snapshot.turns) ? snapshot.turns : [];
628
- if (turns.length) {
629
- turns.forEach((turn) => renderTurn(turn, shouldStick));
630
- }
631
- setSpinner(snapshot.spinner || "");
632
- if (restoreScrollTop !== null) {
633
- log.scrollTop = Math.min(
634
- restoreScrollTop,
635
- Math.max(0, log.scrollHeight - log.clientHeight),
636
- );
637
- restoreScrollTop = null;
638
- } else if (shouldStick) {
639
- scrollToBottom();
640
- } else {
641
- log.scrollTop = Math.min(
642
- previousScrollTop,
643
- Math.max(0, log.scrollHeight - log.clientHeight),
644
- );
682
+ suppressScrollSave = true;
683
+ try {
684
+ log.textContent = "";
685
+ const turns = Array.isArray(snapshot.turns) ? snapshot.turns : [];
686
+ if (turns.length) {
687
+ turns.forEach((turn) => renderTurn(turn, shouldStick));
688
+ }
689
+ setSpinner(snapshot.spinner || "");
690
+ if (restoreScrollTop !== null) {
691
+ log.scrollTop = Math.min(
692
+ restoreScrollTop,
693
+ Math.max(0, log.scrollHeight - log.clientHeight),
694
+ );
695
+ restoreScrollTop = null;
696
+ } else if (shouldStick) {
697
+ scrollToBottom();
698
+ } else {
699
+ log.scrollTop = Math.min(
700
+ previousScrollTop,
701
+ Math.max(0, log.scrollHeight - log.clientHeight),
702
+ );
703
+ }
704
+ } finally {
705
+ suppressScrollSave = false;
645
706
  }
646
707
  state.scrollTop = log.scrollTop;
647
708
  }
@@ -665,7 +726,7 @@
665
726
  );
666
727
  }
667
728
  if (turn.thinking) addEntry("thinking", turn.thinking || "", "thinking", shouldStick);
668
- if (turn.error) addEntry("error", turn.error || "", "tool", shouldStick);
729
+ if (turn.error) addEntry("error", turn.error || "", "error", shouldStick);
669
730
  }
670
731
 
671
732
  async function submitPrompt(text) {
@@ -676,7 +737,7 @@
676
737
  });
677
738
  const payload = await response.json();
678
739
  if (!response.ok || !payload.ok) {
679
- addEntry("error", payload.error || `HTTP ${response.status}`, "tool");
740
+ addEntry("error", payload.error || `HTTP ${response.status}`, "error");
680
741
  setSpinner("");
681
742
  return;
682
743
  }
@@ -697,9 +758,11 @@
697
758
  state.lastRenderedSignature = "";
698
759
  lastRenderedSignature = "";
699
760
  prompt.value = state.draft || "";
761
+ suppressScrollSave = true;
700
762
  log.textContent = "";
763
+ suppressScrollSave = false;
701
764
  setSpinner("");
702
- restoreScrollTop = state.scrollTop || 0;
765
+ restoreScrollTop = state.scrollTop;
703
766
  await pollSession();
704
767
  renderTabs();
705
768
  }
@@ -709,17 +772,17 @@
709
772
  const response = await fetch(relativeUrl("api/sessions"), {method: "POST"});
710
773
  const payload = await response.json();
711
774
  if (!response.ok || !payload.ok) {
712
- addEntry("error", payload.error || `HTTP ${response.status}`, "tool");
775
+ addEntry("error", payload.error || `HTTP ${response.status}`, "error");
713
776
  return;
714
777
  }
715
778
  activeSessionId = payload.session_id || "";
716
779
  const state = stateForSession(activeSessionId);
717
780
  state.draft = "";
718
- state.scrollTop = 0;
781
+ state.scrollTop = null;
719
782
  state.lastRenderedSignature = "";
720
783
  lastRenderedSignature = "";
721
784
  prompt.value = "";
722
- restoreScrollTop = 0;
785
+ restoreScrollTop = null;
723
786
  updateSessions(payload.sessions || []);
724
787
  renderSnapshot(payload.snapshot);
725
788
  }
@@ -731,7 +794,7 @@
731
794
  });
732
795
  const payload = await response.json();
733
796
  if (!response.ok || !payload.ok) {
734
- addEntry("error", payload.error || `HTTP ${response.status}`, "tool");
797
+ addEntry("error", payload.error || `HTTP ${response.status}`, "error");
735
798
  return;
736
799
  }
737
800
  sessionState.delete(sessionId);
@@ -740,7 +803,9 @@
740
803
  activeSessionId = sessions.length ? sessions[0].id || "" : "";
741
804
  lastRenderedSignature = "";
742
805
  prompt.value = "";
806
+ suppressScrollSave = true;
743
807
  log.textContent = "";
808
+ suppressScrollSave = false;
744
809
  await pollSession();
745
810
  } else {
746
811
  renderTabs();
@@ -760,12 +825,22 @@
760
825
  stateForSession(activeSessionId).draft = prompt.value;
761
826
  });
762
827
 
828
+ prompt.addEventListener("compositionstart", () => {
829
+ isComposing = true;
830
+ });
831
+
832
+ prompt.addEventListener("compositionend", () => {
833
+ isComposing = false;
834
+ });
835
+
763
836
  log.addEventListener("scroll", () => {
837
+ if (suppressScrollSave) return;
764
838
  if (!activeSessionId) return;
765
839
  stateForSession(activeSessionId).scrollTop = log.scrollTop;
766
840
  });
767
841
 
768
842
  prompt.addEventListener("keydown", (event) => {
843
+ if (event.isComposing || isComposing || event.keyCode === 229) return;
769
844
  if (event.key === "Enter" && !event.shiftKey) {
770
845
  event.preventDefault();
771
846
  form.requestSubmit();
@@ -776,6 +851,7 @@
776
851
  splitter.addEventListener("pointermove", updateResize);
777
852
  splitter.addEventListener("pointerup", endResize);
778
853
  splitter.addEventListener("pointercancel", endResize);
854
+ splitter.addEventListener("dblclick", centerSplit);
779
855
  newTabButton.addEventListener("click", createSession);
780
856
  window.addEventListener("resize", () => {
781
857
  const current = Number(window.localStorage.getItem("pycodex.workspace.chatWidth") || 0);