codex-lb 0.3.0__py3-none-any.whl → 0.3.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
app/static/index.js CHANGED
@@ -506,6 +506,33 @@
506
506
  };
507
507
  const routingLabel = (strategy) => ROUTING_LABELS[strategy] || "unknown";
508
508
  const errorLabel = (code) => ERROR_LABELS[code] || "--";
509
+ const calculateProgressClass = (status, remainingPercent) => {
510
+ if (status === "exceeded") return "error";
511
+ if (status === "paused" || status === "deactivated") return "";
512
+ const percent = toNumber(remainingPercent) || 0;
513
+ if (percent <= 20) return "error";
514
+ if (percent <= 50) return "limited";
515
+ return "success";
516
+ };
517
+ const calculateProgressTextClass = (status, remainingPercent) => {
518
+ const cls = calculateProgressClass(status, remainingPercent);
519
+ return cls ? `text-${cls}` : "";
520
+ };
521
+
522
+ const calculateTextUsageClass = (status, remainingPercent) => {
523
+ if (status === "exceeded") return "error";
524
+ // For text, we show usage color even if paused. Only deactivated is plain.
525
+ if (status === "deactivated") return "";
526
+ const percent = toNumber(remainingPercent) || 0;
527
+ if (percent <= 20) return "error";
528
+ if (percent <= 50) return "limited";
529
+ return "success";
530
+ };
531
+
532
+ const calculateTextUsageTextClass = (status, remainingPercent) => {
533
+ const cls = calculateTextUsageClass(status, remainingPercent);
534
+ return cls ? `text-${cls}` : "";
535
+ };
509
536
  const progressClass = (status) => PROGRESS_CLASS_BY_STATUS[status] || "";
510
537
 
511
538
  const normalizeSearchInput = (value) =>
@@ -937,9 +964,9 @@
937
964
  const entries =
938
965
  window.key === "primary"
939
966
  ? applySecondaryExhaustedToPrimary(
940
- rawEntries,
941
- secondaryExhaustedAccounts,
942
- )
967
+ rawEntries,
968
+ secondaryExhaustedAccounts,
969
+ )
943
970
  : rawEntries;
944
971
  const remaining =
945
972
  hasPrimaryAdjustments
@@ -954,21 +981,40 @@
954
981
  window.key,
955
982
  );
956
983
  const gradient = buildDonutGradient(items, capacity);
957
- const legendItems = items.map((item) => ({
958
- label: item.label,
959
- detailLabel: "Remaining",
960
- detailValue: formatPercent(item.remainingPercent),
961
- color: item.color,
962
- }));
963
- if (capacity > 0 && consumed > 0) {
984
+ const legendItems = items.map((item) => {
985
+ const percent = item.remainingPercent;
986
+ let valueClass = "success";
987
+ if (percent <= 20) {
988
+ valueClass = "error";
989
+ } else if (percent <= 50) {
990
+ valueClass = "limited";
991
+ }
992
+ return {
993
+ label: truncateText(item.label, 28),
994
+ fullLabel: item.label,
995
+ detailLabel: "Remaining",
996
+ detailValue: formatPercent(item.remainingPercent),
997
+ valueClass,
998
+ color: item.color,
999
+ };
1000
+ });
1001
+ if (capacity > 0) {
964
1002
  const consumedPercent = Math.min(
965
1003
  100,
966
1004
  Math.max(0, (consumed / capacity) * 100),
967
1005
  );
1006
+ let consumedClass = "success";
1007
+ if (consumedPercent >= 80) {
1008
+ consumedClass = "error";
1009
+ } else if (consumedPercent >= 50) {
1010
+ consumedClass = "limited";
1011
+ }
968
1012
  legendItems.push({
969
1013
  label: "Consumed",
1014
+ fullLabel: "Consumed",
970
1015
  detailLabel: "",
971
1016
  detailValue: formatPercent(consumedPercent),
1017
+ valueClass: consumedClass,
972
1018
  color: CONSUMED_COLOR,
973
1019
  });
974
1020
  }
@@ -995,7 +1041,7 @@
995
1041
  },
996
1042
  remaining: remainingRounded,
997
1043
  remainingText: formatPercent(secondaryRemaining),
998
- progressClass: progressClass(account.status),
1044
+ progressClass: calculateProgressClass(account.status, secondaryRemaining),
999
1045
  marquee: account.status === "deactivated",
1000
1046
  meta: formatQuotaResetMeta(
1001
1047
  account.resetAtSecondary,
@@ -1700,12 +1746,33 @@
1700
1746
  window.open(this.authDialog.verificationUrl, "_blank", "noopener");
1701
1747
  }
1702
1748
  },
1703
- async copyToClipboard(value, label) {
1704
- if (!value) {
1705
- return;
1749
+ calculateProgressClass(status, remainingPercent) {
1750
+ return calculateProgressClass(status, remainingPercent);
1751
+ },
1752
+ calculateProgressTextClass(status, remainingPercent) {
1753
+ return calculateProgressTextClass(status, remainingPercent);
1754
+ },
1755
+ async copyToClipboard(value, label, e) {
1756
+ if (!value) return;
1757
+
1758
+ // Localized feedback in the button
1759
+ let btn = null;
1760
+ let originalText = "";
1761
+ if (e && e.target) {
1762
+ btn = e.target.tagName === "BUTTON" ? e.target : e.target.closest("button");
1763
+ if (btn) {
1764
+ originalText = btn.textContent;
1765
+ btn.textContent = "Copied!";
1766
+ btn.classList.add("copy-success");
1767
+ window.setTimeout(() => {
1768
+ btn.textContent = originalText;
1769
+ btn.classList.remove("copy-success");
1770
+ }, 4000);
1771
+ }
1706
1772
  }
1773
+
1707
1774
  try {
1708
- if (navigator.clipboard?.writeText) {
1775
+ if (navigator.clipboard && navigator.clipboard.writeText) {
1709
1776
  await navigator.clipboard.writeText(value);
1710
1777
  } else {
1711
1778
  const textarea = document.createElement("textarea");
@@ -1718,29 +1785,26 @@
1718
1785
  document.execCommand("copy");
1719
1786
  document.body.removeChild(textarea);
1720
1787
  }
1721
- if (this.authDialog.open) {
1722
- const previousLabel = this.authDialog.statusLabel;
1723
- this.authDialog.statusLabel = `${label} copied.`;
1724
- window.setTimeout(() => {
1725
- if (this.authDialog.statusLabel === `${label} copied.`) {
1726
- this.authDialog.statusLabel = previousLabel;
1727
- }
1728
- }, 2000);
1729
- } else {
1788
+
1789
+ // Only show message box if auth dialog is not open and button feedback wasn't possible
1790
+ if (!this.authDialog.open && !btn) {
1730
1791
  this.openMessageBox({
1731
1792
  tone: "success",
1732
1793
  title: "Copied",
1733
1794
  message: `${label} copied to clipboard.`,
1734
1795
  });
1735
1796
  }
1736
- } catch (error) {
1737
- if (this.authDialog.open) {
1738
- this.authDialog.statusLabel = "Copy failed.";
1739
- } else {
1797
+ } catch (err) {
1798
+ console.error("Clipboard error:", err);
1799
+ if (btn) {
1800
+ btn.textContent = "Failed";
1801
+ btn.classList.remove("copy-success");
1802
+ window.setTimeout(() => { btn.textContent = originalText; }, 2000);
1803
+ } else if (!this.authDialog.open) {
1740
1804
  this.openMessageBox({
1741
1805
  tone: "error",
1742
1806
  title: "Copy failed",
1743
- message: "Unable to copy to clipboard.",
1807
+ message: `Could not copy ${label}.`,
1744
1808
  });
1745
1809
  }
1746
1810
  }
@@ -2084,6 +2148,7 @@
2084
2148
  statusLabel,
2085
2149
  requestStatusLabel,
2086
2150
  requestStatusClass,
2151
+ calculateTextUsageTextClass,
2087
2152
  progressClass,
2088
2153
  planLabel,
2089
2154
  routingLabel,
@@ -2100,7 +2165,14 @@
2100
2165
  formatQuotaResetLabel,
2101
2166
  formatAccessTokenLabel,
2102
2167
  formatRefreshTokenLabel,
2168
+ formatAccessTokenLabel,
2169
+ formatRefreshTokenLabel,
2103
2170
  formatIdTokenLabel,
2171
+ theme: localStorage.getItem('theme') || 'dark',
2172
+ toggleTheme() {
2173
+ this.theme = this.theme === 'dark' ? 'light' : 'dark';
2174
+ localStorage.setItem('theme', this.theme);
2175
+ },
2104
2176
  }));
2105
2177
  };
2106
2178
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codex-lb
3
- Version: 0.3.0
3
+ Version: 0.3.1
4
4
  Summary: Codex load balancer and proxy for ChatGPT accounts with usage dashboard
5
5
  Author-email: Soju06 <qlskssk@gmail.com>
6
6
  Maintainer-email: Soju06 <qlskssk@gmail.com>
@@ -55,9 +55,13 @@ Description-Content-Type: text/markdown
55
55
 
56
56
  Load balancer for ChatGPT accounts. Pool multiple accounts, track usage, view everything in a dashboard.
57
57
 
58
- <p align="center">
59
- <img src="https://raw.githubusercontent.com/Soju06/codex-lb/main/docs/screenshots/dashboard.jpeg" alt="Codex Load Balancer dashboard" width="100%">
60
- </p>
58
+ ### Main Dashboard View
59
+
60
+ ![main dashboard view](docs/screenshots/dashboard.jpg)
61
+
62
+ ### Accounts View
63
+
64
+ ![Accounts list and details](docs/screenshots/accounts.jpg)
61
65
 
62
66
  ## Quick Start
63
67
 
@@ -78,9 +82,7 @@ uvx codex-lb
78
82
 
79
83
  Open [localhost:2455](http://localhost:2455) → Add account → Done.
80
84
 
81
- ## Accounts view
82
85
 
83
- ![Accounts list and details](https://raw.githubusercontent.com/Soju06/codex-lb/main/docs/screenshots/accounts.jpeg)
84
86
 
85
87
  ## Codex CLI & Extension Setup
86
88
 
@@ -102,7 +104,31 @@ requires_openai_auth = true # Required: enables model selection in Codex IDE ex
102
104
  ## Data
103
105
 
104
106
  All data stored in `~/.codex-lb/`:
107
+
105
108
  - `store.db` – accounts, usage logs
106
109
  - `encryption.key` – encrypts tokens (auto-generated)
107
110
 
108
111
  Backup this directory to preserve your accounts.
112
+
113
+ ## Contributors ✨
114
+
115
+ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
116
+ <!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
117
+ <!-- prettier-ignore-start -->
118
+ <!-- markdownlint-disable -->
119
+ <table>
120
+ <tbody>
121
+ <tr>
122
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/Soju06"><img src="https://avatars.githubusercontent.com/u/34199905?v=4?s=100" width="100px;" alt="Soju06"/><br /><sub><b>Soju06</b></sub></a><br /><a href="https://github.com/Soju06/codex-lb/commits?author=Soju06" title="Code">💻</a> <a href="https://github.com/Soju06/codex-lb/commits?author=Soju06" title="Tests">⚠️</a> <a href="#maintenance-Soju06" title="Maintenance">🚧</a> <a href="#infra-Soju06" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a></td>
123
+ <td align="center" valign="top" width="14.28%"><a href="http://jonas.kamsker.at/"><img src="https://avatars.githubusercontent.com/u/11245306?v=4?s=100" width="100px;" alt="Jonas Kamsker"/><br /><sub><b>Jonas Kamsker</b></sub></a><br /><a href="https://github.com/Soju06/codex-lb/commits?author=JKamsker" title="Code">💻</a> <a href="https://github.com/Soju06/codex-lb/issues?q=author%3AJKamsker" title="Bug reports">🐛</a> <a href="#maintenance-JKamsker" title="Maintenance">🚧</a></td>
124
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/Quack6765"><img src="https://avatars.githubusercontent.com/u/5446230?v=4?s=100" width="100px;" alt="Quack"/><br /><sub><b>Quack</b></sub></a><br /><a href="https://github.com/Soju06/codex-lb/commits?author=Quack6765" title="Code">💻</a> <a href="https://github.com/Soju06/codex-lb/issues?q=author%3AQuack6765" title="Bug reports">🐛</a> <a href="#maintenance-Quack6765" title="Maintenance">🚧</a> <a href="#design-Quack6765" title="Design">🎨</a></td>
125
+ </tr>
126
+ </tbody>
127
+ </table>
128
+
129
+ <!-- markdownlint-restore -->
130
+ <!-- prettier-ignore-end -->
131
+
132
+ <!-- ALL-CONTRIBUTORS-LIST:END -->
133
+
134
+ This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
@@ -86,12 +86,11 @@ app/modules/usage/repository.py,sha256=DtJI4kgajW7YUJ0JKJjdNCPBXT_fdBwqDoepi9azn
86
86
  app/modules/usage/schemas.py,sha256=eCgunQOvQeYEqv9IecjunONPVLpg2MPn_YGzsnBTcpQ,1633
87
87
  app/modules/usage/service.py,sha256=8-XX8m4TgQteum-a53l0DS-EL2NnC4r9artehfFltvM,10315
88
88
  app/modules/usage/updater.py,sha256=VAeRFvgfpZLCTGxfikefw35ecs8Ja7EoWldIldXDjmE,6783
89
- app/static/7.css,sha256=NjFS937GS7Wg-LTSon5eoiZaHo9fAIbHZ3ikkEDg4Qw,82378
90
- app/static/index.css,sha256=amajcY4psagBwwg5FqgINHNebEHdL1IPRmBGG4fcm24,9271
91
- app/static/index.html,sha256=JPEj3IsVKsK2ub6-_hxKj_Yr9OnRuobHsX8plH9SzDE,26860
92
- app/static/index.js,sha256=p_BZxgROGRLXxy9JGAB03SUARDwHesbmUy0LAVq-Z7w,59013
93
- codex_lb-0.3.0.dist-info/METADATA,sha256=RuWBdjeptPK-OigdjI-mbDVVcj2QvTRUU7s1RbwajKU,3828
94
- codex_lb-0.3.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
95
- codex_lb-0.3.0.dist-info/entry_points.txt,sha256=SEa5T6Uz2Fhy574No6Y0XyGmYi3PXLrhu2xStJTqyI8,42
96
- codex_lb-0.3.0.dist-info/licenses/LICENSE,sha256=cHPibxiL0TXwrUX_kNY6ym544EX1UCzKhxdaca5cFuk,1062
97
- codex_lb-0.3.0.dist-info/RECORD,,
89
+ app/static/index.css,sha256=k75jd3sqISoTjl-cqyrMo72EUlwr65FQEX9SEFSAaRA,23223
90
+ app/static/index.html,sha256=K79S0wMIla6i3oXFwVpSiG2jNlE8x5XxKzsxu177yyo,29131
91
+ app/static/index.js,sha256=odwrhEiSlwq3WW7WcnO4-AGVh_N-TnD5AMuzagOtg4M,61544
92
+ codex_lb-0.3.1.dist-info/METADATA,sha256=KyjxuKYrjCvIXQYcDQ1M5kASLBR6Vu6u3RplrzT6Znk,5840
93
+ codex_lb-0.3.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
94
+ codex_lb-0.3.1.dist-info/entry_points.txt,sha256=SEa5T6Uz2Fhy574No6Y0XyGmYi3PXLrhu2xStJTqyI8,42
95
+ codex_lb-0.3.1.dist-info/licenses/LICENSE,sha256=cHPibxiL0TXwrUX_kNY6ym544EX1UCzKhxdaca5cFuk,1062
96
+ codex_lb-0.3.1.dist-info/RECORD,,