codex-lb 0.3.1__py3-none-any.whl → 0.4.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.
app/static/index.css CHANGED
@@ -90,6 +90,14 @@ body {
90
90
  -webkit-font-smoothing: antialiased;
91
91
  }
92
92
 
93
+ body[data-theme="dark"] {
94
+ color-scheme: dark;
95
+ }
96
+
97
+ body[data-theme="light"] {
98
+ color-scheme: light;
99
+ }
100
+
93
101
  .app-shell {
94
102
  display: flex;
95
103
  flex-direction: column;
@@ -424,6 +432,11 @@ body {
424
432
  color: var(--status-error-text);
425
433
  }
426
434
 
435
+ .status-pill.error {
436
+ background: var(--status-error-bg);
437
+ color: var(--status-error-text);
438
+ }
439
+
427
440
  .status-pill.deactivated,
428
441
  .status-pill.paused {
429
442
  background: var(--status-paused-bg);
@@ -585,6 +598,306 @@ body {
585
598
  background: var(--accent-primary-hover);
586
599
  }
587
600
 
601
+ .controls-toolbar {
602
+ display: flex;
603
+ flex-wrap: wrap;
604
+ gap: 12px;
605
+ margin-bottom: 16px;
606
+ align-items: center;
607
+ justify-content: space-between;
608
+ }
609
+
610
+ .controls-group {
611
+ display: flex;
612
+ flex-wrap: wrap;
613
+ gap: 8px;
614
+ align-items: center;
615
+ }
616
+
617
+ .filter-input {
618
+ background: var(--bg-surface);
619
+ border: 1px solid var(--border-subtle);
620
+ color: var(--text-main);
621
+ padding: 8px 12px;
622
+ border-radius: 6px;
623
+ font-size: 13px;
624
+ outline: none;
625
+ min-width: 120px;
626
+ }
627
+
628
+ .filter-input:focus {
629
+ border-color: var(--accent-primary);
630
+ }
631
+
632
+ .filter-select {
633
+ background: var(--bg-surface);
634
+ border: 1px solid var(--border-subtle);
635
+ color: var(--text-main);
636
+ padding: 8px 32px 8px 12px;
637
+ border-radius: 6px;
638
+ font-size: 13px;
639
+ outline: none;
640
+ -webkit-appearance: none;
641
+ appearance: none;
642
+ background-image: url("data:image/svg+xml;charset=US-ASCII,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22292.4%22%20height%3D%22292.4%22%3E%3Cpath%20fill%3D%22%2358A6FF%22%20d%3D%22M287%2069.4a17.6%2017.6%200%200%200-13-5.4H18.4c-5%200-9.3%201.8-12.9%205.4A17.6%2017.6%200%200%200%200%2082.2c0%205%201.8%209.3%205.4%2012.9l128%20127.9c3.6%203.6%207.8%205.4%2012.8%205.4s9.2-1.8%2012.8-5.4L287%2095c3.5-3.5%205.4-7.8%205.4-12.8%200-5-1.9-9.2-5.5-12.8z%22%2F%3E%3C%2Fsvg%3E");
643
+ background-repeat: no-repeat;
644
+ background-position: right 10px top 50%;
645
+ background-size: 10px auto;
646
+ }
647
+
648
+ .filter-select[multiple] {
649
+ padding-right: 12px;
650
+ background-image: none;
651
+ }
652
+
653
+ .filter-select option {
654
+ background: var(--bg-surface);
655
+ color: var(--text-main);
656
+ }
657
+
658
+ .single-select {
659
+ position: relative;
660
+ }
661
+
662
+ .single-select-trigger {
663
+ display: inline-flex;
664
+ align-items: center;
665
+ justify-content: space-between;
666
+ gap: 12px;
667
+ min-width: 120px;
668
+ cursor: pointer;
669
+ position: relative;
670
+ background-image: none;
671
+ }
672
+
673
+ .single-select-trigger::after {
674
+ content: "";
675
+ position: absolute;
676
+ right: 10px;
677
+ top: 50%;
678
+ width: 10px;
679
+ height: 10px;
680
+ background-image: url("data:image/svg+xml;charset=US-ASCII,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22292.4%22%20height%3D%22292.4%22%3E%3Cpath%20fill%3D%22%2358A6FF%22%20d%3D%22M287%2069.4a17.6%2017.6%200%200%200-13-5.4H18.4c-5%200-9.3%201.8-12.9%205.4A17.6%2017.6%200%200%200%200%2082.2c0%205%201.8%209.3%205.4%2012.9l128%20127.9c3.6%203.6%207.8%205.4%2012.8%205.4s9.2-1.8%2012.8-5.4L287%2095c3.5-3.5%205.4-7.8%205.4-12.8%200-5-1.9-9.2-5.5-12.8z%22%2F%3E%3C%2Fsvg%3E");
681
+ background-repeat: no-repeat;
682
+ background-size: contain;
683
+ transform: translateY(-50%) rotate(0deg);
684
+ transition: transform 0.25s ease;
685
+ }
686
+
687
+ .single-select-trigger[aria-expanded="true"] {
688
+ border-color: var(--accent-primary);
689
+ box-shadow: 0 0 0 1px var(--accent-primary);
690
+ }
691
+
692
+ .single-select-trigger[aria-expanded="true"]::after {
693
+ transform: translateY(-50%) rotate(180deg);
694
+ }
695
+
696
+ .single-select-menu {
697
+ position: absolute;
698
+ top: calc(100% + 8px);
699
+ left: 0;
700
+ z-index: 50;
701
+ width: max-content;
702
+ min-width: 140px;
703
+ padding: 8px;
704
+ padding-top: 12px;
705
+ border-radius: 10px;
706
+ border: 1px solid var(--border-subtle);
707
+ background: var(--bg-panel);
708
+ box-shadow: var(--shadow-md);
709
+ }
710
+
711
+ .single-select-menu::before {
712
+ content: "";
713
+ position: absolute;
714
+ top: -6px;
715
+ left: 16px;
716
+ width: 10px;
717
+ height: 10px;
718
+ background: var(--bg-panel);
719
+ border-left: 1px solid var(--border-subtle);
720
+ border-top: 1px solid var(--border-subtle);
721
+ transform: rotate(45deg);
722
+ }
723
+
724
+ .single-select-item {
725
+ display: flex;
726
+ align-items: center;
727
+ gap: 10px;
728
+ padding: 8px 10px;
729
+ border-radius: 8px;
730
+ cursor: pointer;
731
+ user-select: none;
732
+ color: var(--text-main);
733
+ font-size: 13px;
734
+ }
735
+
736
+ .single-select-item:hover {
737
+ background: var(--bg-list-hover);
738
+ }
739
+
740
+ .single-select-item.is-selected {
741
+ background: var(--bg-list-selected);
742
+ }
743
+
744
+ .single-select-item input[type="radio"] {
745
+ display: none;
746
+ }
747
+
748
+ .multi-select {
749
+ position: relative;
750
+ }
751
+
752
+ .multi-select-trigger {
753
+ display: inline-flex;
754
+ align-items: center;
755
+ justify-content: space-between;
756
+ gap: 12px;
757
+ min-width: 180px;
758
+ cursor: pointer;
759
+ position: relative;
760
+ background-image: none;
761
+ }
762
+
763
+ .multi-select-trigger::after {
764
+ content: "";
765
+ position: absolute;
766
+ right: 10px;
767
+ top: 50%;
768
+ width: 10px;
769
+ height: 10px;
770
+ background-image: url("data:image/svg+xml;charset=US-ASCII,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22292.4%22%20height%3D%22292.4%22%3E%3Cpath%20fill%3D%22%2358A6FF%22%20d%3D%22M287%2069.4a17.6%2017.6%200%200%200-13-5.4H18.4c-5%200-9.3%201.8-12.9%205.4A17.6%2017.6%200%200%200%200%2082.2c0%205%201.8%209.3%205.4%2012.9l128%20127.9c3.6%203.6%207.8%205.4%2012.8%205.4s9.2-1.8%2012.8-5.4L287%2095c3.5-3.5%205.4-7.8%205.4-12.8%200-5-1.9-9.2-5.5-12.8z%22%2F%3E%3C%2Fsvg%3E");
771
+ background-repeat: no-repeat;
772
+ background-size: contain;
773
+ transform: translateY(-50%) rotate(0deg);
774
+ transition: transform 0.25s ease;
775
+ }
776
+
777
+ .multi-select-trigger:disabled {
778
+ opacity: 0.7;
779
+ cursor: not-allowed;
780
+ }
781
+
782
+ .multi-select-trigger[aria-expanded="true"] {
783
+ border-color: var(--accent-primary);
784
+ box-shadow: 0 0 0 1px var(--accent-primary);
785
+ }
786
+
787
+ .multi-select-trigger[aria-expanded="true"]::after {
788
+ transform: translateY(-50%) rotate(180deg);
789
+ }
790
+
791
+
792
+ .multi-select-menu {
793
+ position: absolute;
794
+ top: calc(100% + 8px);
795
+ left: 0;
796
+ z-index: 50;
797
+ width: max-content;
798
+ min-width: 220px;
799
+ max-width: 360px;
800
+ padding: 8px;
801
+ padding-top: 12px;
802
+ border-radius: 10px;
803
+ border: 1px solid var(--border-subtle);
804
+ background: var(--bg-panel);
805
+ box-shadow: var(--shadow-md);
806
+ overflow: visible;
807
+ }
808
+
809
+ .multi-select-scroller {
810
+ max-height: 280px;
811
+ overflow-y: auto;
812
+ overflow-x: hidden;
813
+ /* Add padding to ensure scrollbar doesn't overlap content tightly if needed,
814
+ but we removed padding from parent. Wait, parent has padding 8px.
815
+ The scroller is inside. */
816
+ }
817
+
818
+ .multi-select-menu::before {
819
+ content: "";
820
+ position: absolute;
821
+ top: -6px;
822
+ left: 16px;
823
+ width: 10px;
824
+ height: 10px;
825
+ background: var(--bg-panel);
826
+ border-left: 1px solid var(--border-subtle);
827
+ border-top: 1px solid var(--border-subtle);
828
+ transform: rotate(45deg);
829
+ }
830
+
831
+ .multi-select-actions {
832
+ display: flex;
833
+ justify-content: flex-end;
834
+ margin-bottom: 6px;
835
+ }
836
+
837
+ .multi-select-action {
838
+ background: transparent;
839
+ border: 1px solid var(--border-subtle);
840
+ color: var(--text-muted);
841
+ padding: 6px 10px;
842
+ border-radius: 8px;
843
+ font-size: 12px;
844
+ cursor: pointer;
845
+ }
846
+
847
+ .multi-select-action:hover {
848
+ border-color: var(--border-strong);
849
+ color: var(--text-main);
850
+ }
851
+
852
+ .multi-select-item {
853
+ display: flex;
854
+ align-items: center;
855
+ gap: 10px;
856
+ padding: 8px 10px;
857
+ border-radius: 8px;
858
+ cursor: pointer;
859
+ user-select: none;
860
+ }
861
+
862
+ .multi-select-item:hover {
863
+ background: var(--bg-list-hover);
864
+ }
865
+
866
+ .multi-select-item input[type="checkbox"] {
867
+ accent-color: var(--accent-primary);
868
+ }
869
+
870
+ .multi-select-label {
871
+ color: var(--text-main);
872
+ font-size: 13px;
873
+ white-space: nowrap;
874
+ overflow: hidden;
875
+ text-overflow: ellipsis;
876
+ max-width: 280px;
877
+ }
878
+
879
+ .filter-apply {
880
+ font-size: 13px;
881
+ padding: 8px 12px;
882
+ border-radius: 6px;
883
+ border: 1px solid var(--accent-primary);
884
+ background: var(--accent-primary);
885
+ color: var(--accent-primary-text);
886
+ cursor: pointer;
887
+ font-weight: 500;
888
+ transition: all 0.2s;
889
+ }
890
+
891
+ .filter-apply:hover {
892
+ border-color: var(--accent-primary-hover);
893
+ background: var(--accent-primary-hover);
894
+ }
895
+
896
+ .filter-apply:disabled {
897
+ opacity: 0.6;
898
+ cursor: not-allowed;
899
+ }
900
+
588
901
  .table-wrap {
589
902
  width: 100%;
590
903
  overflow-x: auto;
@@ -592,6 +905,18 @@ body {
592
905
  border-radius: 8px;
593
906
  }
594
907
 
908
+ .table-wrap--requests th,
909
+ .table-wrap--requests td {
910
+ text-align: center;
911
+ }
912
+
913
+ .table-wrap--requests td:nth-child(2),
914
+ .table-wrap--requests td:nth-child(5) {
915
+ text-align: left;
916
+ }
917
+
918
+
919
+
595
920
  table {
596
921
  width: 100%;
597
922
  border-collapse: collapse;
@@ -606,6 +931,56 @@ table {
606
931
  text-overflow: ellipsis;
607
932
  }
608
933
 
934
+ .error-cell {
935
+ display: flex;
936
+ align-items: flex-start;
937
+ gap: 6px;
938
+ min-width: 0;
939
+ }
940
+
941
+ .error-cell.placeholder {
942
+ justify-content: center;
943
+ }
944
+
945
+ .error-cell.placeholder .error-text {
946
+ text-align: center;
947
+ }
948
+
949
+ .error-text {
950
+ flex: 1;
951
+ min-width: 0;
952
+ }
953
+
954
+ .error-text.truncated {
955
+ white-space: nowrap;
956
+ overflow: hidden;
957
+ text-overflow: ellipsis;
958
+ }
959
+
960
+ .cell-error {
961
+ vertical-align: middle;
962
+ }
963
+
964
+ .error-toggle {
965
+ background: transparent;
966
+ border: none;
967
+ color: var(--accent-primary);
968
+ font-size: 11px;
969
+ padding: 0;
970
+ cursor: pointer;
971
+ white-space: nowrap;
972
+ margin-top: 3px;
973
+ font-weight: 500;
974
+ line-height: 1.2;
975
+ min-width: 32px;
976
+ text-align: left;
977
+ flex-shrink: 0;
978
+ }
979
+
980
+ .error-toggle:hover {
981
+ text-decoration: underline;
982
+ }
983
+
609
984
  /* ... existing styles ... */
610
985
 
611
986
  .list-actions .searchbox button {
@@ -641,12 +1016,14 @@ th {
641
1016
  font-size: 12px;
642
1017
  text-transform: uppercase;
643
1018
  border-bottom: 1px solid var(--border-subtle);
1019
+ vertical-align: middle;
644
1020
  }
645
1021
 
646
1022
  td {
647
- padding: 14px 20px;
1023
+ padding: 12px 20px;
648
1024
  border-bottom: 1px solid var(--border-subtle);
649
1025
  color: var(--text-main);
1026
+ vertical-align: middle;
650
1027
  }
651
1028
 
652
1029
  tr:last-child td {
app/static/index.html CHANGED
@@ -4,7 +4,7 @@
4
4
  <head>
5
5
  <meta charset="utf-8">
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1">
7
- <meta name="color-scheme" content="light">
7
+ <meta name="color-scheme" content="light dark">
8
8
  <title>Codex LB</title>
9
9
  <link rel="icon"
10
10
  href="data:image/svg+xml,%3Csvg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;32&quot; height=&quot;32&quot; fill=&quot;none&quot; viewBox=&quot;0 0 32 32&quot;%3E%3Cpath stroke=&quot;%23000&quot; stroke-linecap=&quot;round&quot; stroke-width=&quot;2.484&quot; d=&quot;M22.356 19.797H17.17M9.662 12.29l1.979 3.576a.511.511 0 0 1-.005.504l-1.974 3.409M30.758 16c0 8.15-6.607 14.758-14.758 14.758-8.15 0-14.758-6.607-14.758-14.758C1.242 7.85 7.85 1.242 16 1.242c8.15 0 14.758 6.608 14.758 14.758Z&quot;/%3E%3C/svg%3E"
@@ -44,7 +44,7 @@
44
44
  <div class="loading-overlay" x-show="isLoading" x-transition.opacity>
45
45
  <div class="loading-panel" role="status" aria-live="polite">
46
46
  <span class="spinner animate" aria-hidden="true"></span>
47
- <div>Loading dashboard...</div>
47
+ <div>Loading Dashboard...</div>
48
48
  </div>
49
49
  </div>
50
50
  <section class="tabs" aria-label="Codex FE tabs">
@@ -149,6 +149,161 @@
149
149
 
150
150
  <div class="panel" style="margin-top: 12px">
151
151
  <h3>Recent requests</h3>
152
+
153
+ <div class="controls-toolbar">
154
+ <div class="controls-group">
155
+ <input type="text" class="filter-input" placeholder="Search..." x-model="filtersDraft.search"
156
+ @keyup.enter="applyFilters()" style="width: 200px;">
157
+
158
+ <div class="single-select" x-data="{ open: false }" @click.outside="open = false">
159
+ <button type="button" class="filter-select single-select-trigger" @click="open = !open"
160
+ :aria-expanded="open">
161
+ <span x-text="timeframeLabel(filtersDraft.timeframe)"></span>
162
+ </button>
163
+ <div class="single-select-menu" x-show="open" x-transition.opacity>
164
+ <label class="single-select-item" :class="{ 'is-selected': filtersDraft.timeframe === 'all' }">
165
+ <input type="radio" name="timeframe" value="all" x-model="filtersDraft.timeframe"
166
+ @change="open = false">
167
+ <span>All time</span>
168
+ </label>
169
+ <label class="single-select-item" :class="{ 'is-selected': filtersDraft.timeframe === '1h' }">
170
+ <input type="radio" name="timeframe" value="1h" x-model="filtersDraft.timeframe"
171
+ @change="open = false">
172
+ <span>Last 1h</span>
173
+ </label>
174
+ <label class="single-select-item" :class="{ 'is-selected': filtersDraft.timeframe === '24h' }">
175
+ <input type="radio" name="timeframe" value="24h" x-model="filtersDraft.timeframe"
176
+ @change="open = false">
177
+ <span>Last 24h</span>
178
+ </label>
179
+ <label class="single-select-item" :class="{ 'is-selected': filtersDraft.timeframe === '7d' }">
180
+ <input type="radio" name="timeframe" value="7d" x-model="filtersDraft.timeframe"
181
+ @change="open = false">
182
+ <span>Last 7d</span>
183
+ </label>
184
+ </div>
185
+ </div>
186
+
187
+
188
+ <div class="multi-select" x-data="{ open: false }" @click.outside="open = false">
189
+ <button type="button" class="filter-select multi-select-trigger" @click="open = !open"
190
+ :aria-expanded="open" :disabled="requestLogOptions.isLoading">
191
+ <span
192
+ x-text="multiSelectSummary(filtersDraft.accountIds, 'All accounts', 'account', 'accounts')"></span>
193
+ </button>
194
+ <div class="multi-select-menu" x-show="open" x-transition.opacity>
195
+ <div class="multi-select-scroller">
196
+ <div class="multi-select-actions">
197
+ <button type="button" class="multi-select-action"
198
+ @click="filtersDraft.accountIds = []; open = false">Clear</button>
199
+ </div>
200
+ <template x-for="accountId in requestLogOptions.accountIds" :key="accountId">
201
+ <label class="multi-select-item">
202
+ <input type="checkbox" :checked="filtersDraft.accountIds.includes(accountId)"
203
+ @change="toggleMultiSelectValue('accountIds', accountId)">
204
+ <span class="multi-select-label" x-text="accountFilterLabel(accountId)"></span>
205
+ </label>
206
+ </template>
207
+ </div>
208
+ </div>
209
+ </div>
210
+
211
+ <div class="multi-select" x-data="{ open: false }" @click.outside="open = false">
212
+ <button type="button" class="filter-select multi-select-trigger" @click="open = !open"
213
+ :aria-expanded="open" :disabled="requestLogOptions.isLoading">
214
+ <span
215
+ x-text="multiSelectSummary(filtersDraft.modelOptions, 'All models', 'model', 'models')"></span>
216
+ </button>
217
+ <div class="multi-select-menu" x-show="open" x-transition.opacity>
218
+ <div class="multi-select-scroller">
219
+ <div class="multi-select-actions">
220
+ <button type="button" class="multi-select-action"
221
+ @click="filtersDraft.modelOptions = []; open = false">Clear</button>
222
+ </div>
223
+ <template x-for="modelOption in requestLogOptions.modelOptions"
224
+ :key="modelOptionValue(modelOption)">
225
+ <label class="multi-select-item">
226
+ <input type="checkbox"
227
+ :checked="filtersDraft.modelOptions.includes(modelOptionValue(modelOption))"
228
+ @change="toggleMultiSelectValue('modelOptions', modelOptionValue(modelOption))">
229
+ <span class="multi-select-label" x-text="modelOptionLabel(modelOption)"></span>
230
+ </label>
231
+ </template>
232
+ </div>
233
+ </div>
234
+ </div>
235
+
236
+ <div class="multi-select" x-data="{ open: false }" @click.outside="open = false">
237
+ <button type="button" class="filter-select multi-select-trigger" @click="open = !open"
238
+ :aria-expanded="open">
239
+ <span x-text="multiSelectSummary(filtersDraft.statuses, 'All status', 'status', 'statuses')"></span>
240
+ </button>
241
+ <div class="multi-select-menu" x-show="open" x-transition.opacity>
242
+ <div class="multi-select-scroller">
243
+ <div class="multi-select-actions">
244
+ <button type="button" class="multi-select-action"
245
+ @click="filtersDraft.statuses = []; open = false">Clear</button>
246
+ </div>
247
+ <label class="multi-select-item">
248
+ <input type="checkbox" :checked="filtersDraft.statuses.includes('ok')"
249
+ @change="toggleMultiSelectValue('statuses', 'ok')">
250
+ <span class="multi-select-label">OK</span>
251
+ </label>
252
+ <label class="multi-select-item">
253
+ <input type="checkbox" :checked="filtersDraft.statuses.includes('rate_limit')"
254
+ @change="toggleMultiSelectValue('statuses', 'rate_limit')">
255
+ <span class="multi-select-label">Rate Limit</span>
256
+ </label>
257
+ <label class="multi-select-item">
258
+ <input type="checkbox" :checked="filtersDraft.statuses.includes('quota')"
259
+ @change="toggleMultiSelectValue('statuses', 'quota')">
260
+ <span class="multi-select-label">Quota</span>
261
+ </label>
262
+ <label class="multi-select-item">
263
+ <input type="checkbox" :checked="filtersDraft.statuses.includes('error')"
264
+ @change="toggleMultiSelectValue('statuses', 'error')">
265
+ <span class="multi-select-label">Error</span>
266
+ </label>
267
+ </div>
268
+ </div>
269
+ </div>
270
+
271
+ <input type="number" class="filter-input" placeholder="Min Cost ($)" x-model="filtersDraft.minCost"
272
+ @keyup.enter="applyFilters()" style="width: 110px;" step="0.01">
273
+
274
+ <button type="button" class="filter-apply" @click="applyFilters()"
275
+ :disabled="recentRequestsState.isLoading">Apply</button>
276
+ </div>
277
+
278
+ <div class="controls-group">
279
+ <span style="font-size: 13px; color: var(--text-muted);">Show:</span>
280
+ <div class="single-select" x-data="{ open: false }" @click.outside="open = false">
281
+ <button type="button" class="filter-select single-select-trigger" @click="open = !open"
282
+ :aria-expanded="open" style="min-width: 60px">
283
+ <span x-text="pagination.limit"></span>
284
+ </button>
285
+ <div class="single-select-menu" x-show="open" x-transition.opacity style="min-width: 80px">
286
+ <label class="single-select-item" :class="{ 'is-selected': pagination.limit == 25 }">
287
+ <input type="radio" name="limit" value="25" x-model="pagination.limit" @change="open = false">
288
+ <span>25</span>
289
+ </label>
290
+ <label class="single-select-item" :class="{ 'is-selected': pagination.limit == 50 }">
291
+ <input type="radio" name="limit" value="50" x-model="pagination.limit" @change="open = false">
292
+ <span>50</span>
293
+ </label>
294
+ <label class="single-select-item" :class="{ 'is-selected': pagination.limit == 100 }">
295
+ <input type="radio" name="limit" value="100" x-model="pagination.limit" @change="open = false">
296
+ <span>100</span>
297
+ </label>
298
+ <label class="single-select-item" :class="{ 'is-selected': pagination.limit == 250 }">
299
+ <input type="radio" name="limit" value="250" x-model="pagination.limit" @change="open = false">
300
+ <span>250</span>
301
+ </label>
302
+ </div>
303
+ </div>
304
+ </div>
305
+ </div>
306
+
152
307
  <div class="table-wrap table-wrap--requests table-wrap--column-lines has-scrollbar">
153
308
  <table>
154
309
  <thead>
@@ -158,21 +313,41 @@
158
313
  <th style="width: 15%">Model</th>
159
314
  <th style="width: 10%">Status</th>
160
315
  <th style="width: 15%">Error</th>
161
- <th style="text-align: right; width: 10%">Tokens</th>
162
- <th style="text-align: right; width: 10%">Cost</th>
316
+ <th style="width: 10%">Tokens</th>
317
+ <th style="width: 10%">Cost</th>
163
318
  </tr>
164
319
  </thead>
165
320
  <tbody>
166
321
  <template x-for="request in dashboard.requests" :key="request.key">
167
322
  <tr>
168
- <td x-text="request.time"></td>
323
+ <td>
324
+ <div x-text="request.time.time"></div>
325
+ <div x-text="request.time.date" class="text-muted" style="font-size: 11px;"></div>
326
+ </td>
169
327
  <td class="cell-truncate" x-text="request.account" :title="request.account"></td>
170
328
  <td class="cell-truncate" x-text="request.model" :title="request.model"></td>
171
329
  <td><span class="status-pill" :class="request.status.class" x-text="request.status.label"></span>
172
330
  </td>
173
- <td class="cell-truncate" x-text="request.error" :title="request.errorTitle || ''"></td>
174
- <td style="text-align: right" x-text="request.tokens"></td>
175
- <td style="text-align: right" x-text="request.cost"></td>
331
+ <td class="cell-error">
332
+ <div class="error-cell" :class="request.isErrorPlaceholder ? 'placeholder' : ''"
333
+ x-data="{ expanded: false }">
334
+ <div class="error-text" :class="!expanded ? 'truncated' : ''"
335
+ x-text="expanded ? request.errorTitle : request.error"
336
+ :title="!expanded ? request.errorTitle : ''"></div>
337
+ <template x-if="request.isTruncated">
338
+ <button class="error-toggle" @click="expanded = !expanded"
339
+ x-text="expanded ? 'Less' : 'More'"></button>
340
+ </template>
341
+ </div>
342
+ </td>
343
+ <td>
344
+ <div x-text="request.tokens.total"></div>
345
+ <template x-if="request.tokens.cached">
346
+ <div x-text="`${request.tokens.cached} Cached`" class="text-muted" style="font-size: 11px;">
347
+ </div>
348
+ </template>
349
+ </td>
350
+ <td x-text="request.cost"></td>
176
351
  </tr>
177
352
  </template>
178
353
  </tbody>