codex-lb 0.2.0__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.
- app/core/auth/__init__.py +10 -0
- app/core/balancer/logic.py +33 -6
- app/core/config/settings.py +2 -0
- app/core/usage/__init__.py +2 -0
- app/core/usage/logs.py +12 -2
- app/core/usage/quota.py +10 -4
- app/core/usage/types.py +3 -2
- app/db/migrations/__init__.py +14 -3
- app/db/migrations/versions/add_accounts_chatgpt_account_id.py +29 -0
- app/db/migrations/versions/add_accounts_reset_at.py +29 -0
- app/db/migrations/versions/add_dashboard_settings.py +31 -0
- app/db/migrations/versions/add_request_logs_reasoning_effort.py +21 -0
- app/db/models.py +33 -0
- app/db/session.py +71 -11
- app/dependencies.py +27 -1
- app/main.py +11 -2
- app/modules/accounts/auth_manager.py +44 -3
- app/modules/accounts/repository.py +14 -6
- app/modules/accounts/service.py +4 -2
- app/modules/oauth/service.py +4 -3
- app/modules/proxy/load_balancer.py +74 -5
- app/modules/proxy/service.py +155 -31
- app/modules/proxy/sticky_repository.py +56 -0
- app/modules/request_logs/repository.py +6 -3
- app/modules/request_logs/schemas.py +2 -0
- app/modules/request_logs/service.py +8 -1
- app/modules/settings/__init__.py +1 -0
- app/modules/settings/api.py +37 -0
- app/modules/settings/repository.py +40 -0
- app/modules/settings/schemas.py +13 -0
- app/modules/settings/service.py +33 -0
- app/modules/shared/schemas.py +16 -2
- app/modules/usage/schemas.py +1 -0
- app/modules/usage/service.py +17 -1
- app/modules/usage/updater.py +36 -7
- app/static/7.css +73 -0
- app/static/index.css +33 -4
- app/static/index.html +51 -4
- app/static/index.js +231 -25
- {codex_lb-0.2.0.dist-info → codex_lb-0.3.0.dist-info}/METADATA +2 -2
- {codex_lb-0.2.0.dist-info → codex_lb-0.3.0.dist-info}/RECORD +44 -34
- {codex_lb-0.2.0.dist-info → codex_lb-0.3.0.dist-info}/WHEEL +0 -0
- {codex_lb-0.2.0.dist-info → codex_lb-0.3.0.dist-info}/entry_points.txt +0 -0
- {codex_lb-0.2.0.dist-info → codex_lb-0.3.0.dist-info}/licenses/LICENSE +0 -0
app/static/index.js
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
oauthStart: "/api/oauth/start",
|
|
17
17
|
oauthStatus: "/api/oauth/status",
|
|
18
18
|
oauthComplete: "/api/oauth/complete",
|
|
19
|
+
settings: "/api/settings",
|
|
19
20
|
};
|
|
20
21
|
|
|
21
22
|
const PAGES = [
|
|
@@ -33,6 +34,13 @@
|
|
|
33
34
|
title: "Codex Load Balancer - Accounts",
|
|
34
35
|
path: "/accounts",
|
|
35
36
|
},
|
|
37
|
+
{
|
|
38
|
+
id: "settings",
|
|
39
|
+
tabId: "tab-settings",
|
|
40
|
+
label: "Settings",
|
|
41
|
+
title: "Codex Load Balancer - Settings",
|
|
42
|
+
path: "/settings",
|
|
43
|
+
},
|
|
36
44
|
];
|
|
37
45
|
|
|
38
46
|
const BASE_PATH = "/dashboard";
|
|
@@ -187,6 +195,7 @@
|
|
|
187
195
|
metrics: {
|
|
188
196
|
requests7d: 0,
|
|
189
197
|
tokensSecondaryWindow: null,
|
|
198
|
+
cachedTokensSecondaryWindow: null,
|
|
190
199
|
cost7d: 0,
|
|
191
200
|
errorRate7d: null,
|
|
192
201
|
topError: "",
|
|
@@ -242,6 +251,40 @@
|
|
|
242
251
|
return compactFormatter.format(numeric);
|
|
243
252
|
};
|
|
244
253
|
|
|
254
|
+
const formatTokensWithCached = (totalTokens, cachedInputTokens) => {
|
|
255
|
+
const total = toNumber(totalTokens);
|
|
256
|
+
if (total === null) {
|
|
257
|
+
return "--";
|
|
258
|
+
}
|
|
259
|
+
const cached = toNumber(cachedInputTokens);
|
|
260
|
+
if (cached === null || cached <= 0) {
|
|
261
|
+
return formatCompactNumber(total);
|
|
262
|
+
}
|
|
263
|
+
return `${formatCompactNumber(total)} (${formatCompactNumber(cached)} Cached)`;
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
const formatCachedTokensMeta = (totalTokens, cachedInputTokens) => {
|
|
267
|
+
const total = toNumber(totalTokens);
|
|
268
|
+
const cached = toNumber(cachedInputTokens);
|
|
269
|
+
if (total === null || total <= 0 || cached === null || cached <= 0) {
|
|
270
|
+
return "Cached: --";
|
|
271
|
+
}
|
|
272
|
+
const percent = Math.min(100, Math.max(0, (cached / total) * 100));
|
|
273
|
+
return `Cached: ${formatCompactNumber(cached)} (${Math.round(percent)}%)`;
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
const formatModelLabel = (model, reasoningEffort) => {
|
|
277
|
+
const base = (model || "").trim();
|
|
278
|
+
if (!base) {
|
|
279
|
+
return "--";
|
|
280
|
+
}
|
|
281
|
+
const effort = (reasoningEffort || "").trim();
|
|
282
|
+
if (!effort) {
|
|
283
|
+
return base;
|
|
284
|
+
}
|
|
285
|
+
return `${base} (${effort})`;
|
|
286
|
+
};
|
|
287
|
+
|
|
245
288
|
const formatCurrency = (value) => {
|
|
246
289
|
const numeric = toNumber(value);
|
|
247
290
|
if (numeric === null) {
|
|
@@ -565,8 +608,10 @@
|
|
|
565
608
|
accountId: entry.accountId,
|
|
566
609
|
requestId: entry.requestId,
|
|
567
610
|
model: entry.model,
|
|
611
|
+
reasoningEffort: entry.reasoningEffort ?? null,
|
|
568
612
|
status: entry.status,
|
|
569
613
|
tokens: toNumber(entry.tokens),
|
|
614
|
+
cachedInputTokens: toNumber(entry.cachedInputTokens),
|
|
570
615
|
cost: toNumber(entry.costUsd),
|
|
571
616
|
errorCode: entry.errorCode ?? null,
|
|
572
617
|
errorMessage: entry.errorMessage ?? null,
|
|
@@ -595,17 +640,21 @@
|
|
|
595
640
|
const secondaryRemainingPercent = toNumber(
|
|
596
641
|
secondaryRow?.remainingPercentAvg,
|
|
597
642
|
);
|
|
643
|
+
const mergedSecondaryRemaining =
|
|
644
|
+
secondaryRemainingPercent ??
|
|
645
|
+
account.usage?.secondaryRemainingPercent ??
|
|
646
|
+
0;
|
|
647
|
+
const mergedPrimaryRemaining =
|
|
648
|
+
primaryRemainingPercent ??
|
|
649
|
+
account.usage?.primaryRemainingPercent ??
|
|
650
|
+
0;
|
|
651
|
+
const effectivePrimaryRemaining =
|
|
652
|
+
mergedSecondaryRemaining <= 0 ? 0 : mergedPrimaryRemaining;
|
|
598
653
|
return {
|
|
599
654
|
...account,
|
|
600
655
|
usage: {
|
|
601
|
-
primaryRemainingPercent:
|
|
602
|
-
|
|
603
|
-
account.usage?.primaryRemainingPercent ??
|
|
604
|
-
0,
|
|
605
|
-
secondaryRemainingPercent:
|
|
606
|
-
secondaryRemainingPercent ??
|
|
607
|
-
account.usage?.secondaryRemainingPercent ??
|
|
608
|
-
0,
|
|
656
|
+
primaryRemainingPercent: effectivePrimaryRemaining,
|
|
657
|
+
secondaryRemainingPercent: mergedSecondaryRemaining,
|
|
609
658
|
},
|
|
610
659
|
resetAtPrimary: account.resetAtPrimary ?? null,
|
|
611
660
|
resetAtSecondary: account.resetAtSecondary ?? null,
|
|
@@ -641,6 +690,7 @@
|
|
|
641
690
|
accountId: entry.accountId,
|
|
642
691
|
capacityCredits: toNumber(entry.capacityCredits) || 0,
|
|
643
692
|
remainingCredits: toNumber(entry.remainingCredits) || 0,
|
|
693
|
+
remainingPercentAvg: toNumber(entry.remainingPercentAvg),
|
|
644
694
|
})),
|
|
645
695
|
};
|
|
646
696
|
};
|
|
@@ -654,6 +704,7 @@
|
|
|
654
704
|
const metrics = summary?.metrics || {};
|
|
655
705
|
const requests7d = toNumber(metrics.requests7d) ?? 0;
|
|
656
706
|
const tokensSecondaryWindow = toNumber(metrics.tokensSecondaryWindow);
|
|
707
|
+
const cachedTokensSecondaryWindow = toNumber(metrics.cachedTokensSecondaryWindow);
|
|
657
708
|
const errorRate7d = toNumber(metrics.errorRate7d);
|
|
658
709
|
const topError = metrics.topError || "";
|
|
659
710
|
return {
|
|
@@ -665,6 +716,7 @@
|
|
|
665
716
|
metrics: {
|
|
666
717
|
requests7d,
|
|
667
718
|
tokensSecondaryWindow,
|
|
719
|
+
cachedTokensSecondaryWindow,
|
|
668
720
|
cost7d: toNumber(summary?.cost?.totalUsd7d) || 0,
|
|
669
721
|
errorRate7d,
|
|
670
722
|
topError,
|
|
@@ -699,7 +751,7 @@
|
|
|
699
751
|
return acc;
|
|
700
752
|
}, {});
|
|
701
753
|
|
|
702
|
-
const buildRemainingItems = (entries, accounts, capacity) => {
|
|
754
|
+
const buildRemainingItems = (entries, accounts, capacity, windowKey) => {
|
|
703
755
|
const accountMap = new Map(
|
|
704
756
|
(accounts || []).map((account) => [account.id, account]),
|
|
705
757
|
);
|
|
@@ -707,7 +759,23 @@
|
|
|
707
759
|
const account = accountMap.get(entry.accountId);
|
|
708
760
|
const label = account ? account.email : entry.accountId;
|
|
709
761
|
const value = toNumber(entry.remainingCredits) || 0;
|
|
710
|
-
const
|
|
762
|
+
const percentFromApi = toNumber(entry.remainingPercentAvg);
|
|
763
|
+
const percentFromAccount =
|
|
764
|
+
windowKey === "primary"
|
|
765
|
+
? toNumber(account?.usage?.primaryRemainingPercent)
|
|
766
|
+
: windowKey === "secondary"
|
|
767
|
+
? toNumber(account?.usage?.secondaryRemainingPercent)
|
|
768
|
+
: null;
|
|
769
|
+
const entryCapacity = toNumber(entry.capacityCredits) || 0;
|
|
770
|
+
const denominator = entryCapacity > 0 ? entryCapacity : capacity;
|
|
771
|
+
const rawPercent =
|
|
772
|
+
percentFromApi !== null
|
|
773
|
+
? percentFromApi
|
|
774
|
+
: percentFromAccount !== null
|
|
775
|
+
? percentFromAccount
|
|
776
|
+
: denominator > 0
|
|
777
|
+
? (value / denominator) * 100
|
|
778
|
+
: 0;
|
|
711
779
|
const remainingPercent = Math.min(100, Math.max(0, rawPercent));
|
|
712
780
|
return {
|
|
713
781
|
accountId: entry.accountId,
|
|
@@ -734,6 +802,38 @@
|
|
|
734
802
|
}));
|
|
735
803
|
};
|
|
736
804
|
|
|
805
|
+
const buildSecondaryExhaustedIndex = (accounts) => {
|
|
806
|
+
const exhausted = new Set();
|
|
807
|
+
(accounts || []).forEach((account) => {
|
|
808
|
+
const remaining = toNumber(account?.usage?.secondaryRemainingPercent);
|
|
809
|
+
if (remaining !== null && remaining <= 0 && account?.id) {
|
|
810
|
+
exhausted.add(account.id);
|
|
811
|
+
}
|
|
812
|
+
});
|
|
813
|
+
return exhausted;
|
|
814
|
+
};
|
|
815
|
+
|
|
816
|
+
const applySecondaryExhaustedToPrimary = (entries, exhaustedIds) => {
|
|
817
|
+
if (!entries?.length || !exhaustedIds?.size) {
|
|
818
|
+
return entries || [];
|
|
819
|
+
}
|
|
820
|
+
return entries.map((entry) => {
|
|
821
|
+
if (entry?.accountId && exhaustedIds.has(entry.accountId)) {
|
|
822
|
+
return {
|
|
823
|
+
...entry,
|
|
824
|
+
remainingCredits: 0,
|
|
825
|
+
};
|
|
826
|
+
}
|
|
827
|
+
return entry;
|
|
828
|
+
});
|
|
829
|
+
};
|
|
830
|
+
|
|
831
|
+
const sumRemainingCredits = (entries) =>
|
|
832
|
+
(entries || []).reduce(
|
|
833
|
+
(acc, entry) => acc + (toNumber(entry?.remainingCredits) || 0),
|
|
834
|
+
0,
|
|
835
|
+
);
|
|
836
|
+
|
|
737
837
|
const buildDonutGradient = (items, total) => {
|
|
738
838
|
if (!items.length || total <= 0) {
|
|
739
839
|
return `conic-gradient(${CONSUMED_COLOR} 0 100%)`;
|
|
@@ -776,6 +876,7 @@
|
|
|
776
876
|
const statusCounts = countByStatus(accounts);
|
|
777
877
|
const secondaryWindowMinutes =
|
|
778
878
|
state.dashboardData.usage?.secondary?.windowMinutes ?? null;
|
|
879
|
+
const secondaryExhaustedAccounts = buildSecondaryExhaustedIndex(accounts);
|
|
779
880
|
|
|
780
881
|
const badges = ["active", "paused", "limited", "exceeded", "deactivated"]
|
|
781
882
|
.map((status) => {
|
|
@@ -795,7 +896,10 @@
|
|
|
795
896
|
{
|
|
796
897
|
title: `Tokens (${formatWindowLabel("secondary", secondaryWindowMinutes)})`,
|
|
797
898
|
value: formatCompactNumber(metrics.tokensSecondaryWindow),
|
|
798
|
-
meta:
|
|
899
|
+
meta: formatCachedTokensMeta(
|
|
900
|
+
metrics.tokensSecondaryWindow,
|
|
901
|
+
metrics.cachedTokensSecondaryWindow,
|
|
902
|
+
),
|
|
799
903
|
},
|
|
800
904
|
{
|
|
801
905
|
title: "Cost (7d)",
|
|
@@ -823,18 +927,37 @@
|
|
|
823
927
|
resetAt: null,
|
|
824
928
|
byAccount: [],
|
|
825
929
|
};
|
|
826
|
-
const
|
|
930
|
+
const rawEntries = usage.byAccount || [];
|
|
931
|
+
const hasPrimaryAdjustments =
|
|
932
|
+
window.key === "primary" &&
|
|
933
|
+
rawEntries.some(
|
|
934
|
+
(entry) =>
|
|
935
|
+
entry?.accountId && secondaryExhaustedAccounts.has(entry.accountId),
|
|
936
|
+
);
|
|
937
|
+
const entries =
|
|
938
|
+
window.key === "primary"
|
|
939
|
+
? applySecondaryExhaustedToPrimary(
|
|
940
|
+
rawEntries,
|
|
941
|
+
secondaryExhaustedAccounts,
|
|
942
|
+
)
|
|
943
|
+
: rawEntries;
|
|
944
|
+
const remaining =
|
|
945
|
+
hasPrimaryAdjustments
|
|
946
|
+
? sumRemainingCredits(entries)
|
|
947
|
+
: toNumber(usage.remaining) || 0;
|
|
827
948
|
const capacity = Math.max(remaining, toNumber(usage.capacity) || 0);
|
|
828
949
|
const consumed = Math.max(0, capacity - remaining);
|
|
829
950
|
const items = buildRemainingItems(
|
|
830
|
-
|
|
951
|
+
entries,
|
|
831
952
|
accounts,
|
|
832
953
|
capacity,
|
|
954
|
+
window.key,
|
|
833
955
|
);
|
|
834
956
|
const gradient = buildDonutGradient(items, capacity);
|
|
835
957
|
const legendItems = items.map((item) => ({
|
|
836
958
|
label: item.label,
|
|
837
|
-
|
|
959
|
+
detailLabel: "Remaining",
|
|
960
|
+
detailValue: formatPercent(item.remainingPercent),
|
|
838
961
|
color: item.color,
|
|
839
962
|
}));
|
|
840
963
|
if (capacity > 0 && consumed > 0) {
|
|
@@ -844,7 +967,8 @@
|
|
|
844
967
|
);
|
|
845
968
|
legendItems.push({
|
|
846
969
|
label: "Consumed",
|
|
847
|
-
|
|
970
|
+
detailLabel: "",
|
|
971
|
+
detailValue: formatPercent(consumedPercent),
|
|
848
972
|
color: CONSUMED_COLOR,
|
|
849
973
|
});
|
|
850
974
|
}
|
|
@@ -884,17 +1008,18 @@
|
|
|
884
1008
|
const requests = state.dashboardData.recentRequests.map((request) => {
|
|
885
1009
|
const rawError = request.errorMessage || request.errorCode || "";
|
|
886
1010
|
const accountLabel = formatAccountLabel(request.accountId, accounts);
|
|
1011
|
+
const modelLabel = formatModelLabel(request.model, request.reasoningEffort);
|
|
887
1012
|
return {
|
|
888
1013
|
key: `${request.requestId}-${request.timestamp}`,
|
|
889
1014
|
requestId: request.requestId,
|
|
890
1015
|
time: formatTimeLong(request.timestamp),
|
|
891
1016
|
account: accountLabel,
|
|
892
|
-
model:
|
|
1017
|
+
model: modelLabel,
|
|
893
1018
|
status: {
|
|
894
1019
|
class: requestStatusClass(request.status),
|
|
895
1020
|
label: requestStatusLabel(request.status),
|
|
896
1021
|
},
|
|
897
|
-
tokens:
|
|
1022
|
+
tokens: formatTokensWithCached(request.tokens, request.cachedInputTokens),
|
|
898
1023
|
cost: formatCurrency(request.cost),
|
|
899
1024
|
error: rawError ? truncateText(rawError, 80) : "--",
|
|
900
1025
|
errorTitle: rawError,
|
|
@@ -1031,6 +1156,20 @@
|
|
|
1031
1156
|
return responsePayload;
|
|
1032
1157
|
};
|
|
1033
1158
|
|
|
1159
|
+
const putJson = async (url, payload, label) => {
|
|
1160
|
+
const response = await fetch(url, {
|
|
1161
|
+
method: "PUT",
|
|
1162
|
+
headers: { "Content-Type": "application/json" },
|
|
1163
|
+
body: JSON.stringify(payload || {}),
|
|
1164
|
+
});
|
|
1165
|
+
const responsePayload = await readResponsePayload(response);
|
|
1166
|
+
if (!response.ok) {
|
|
1167
|
+
const message = extractErrorMessage(responsePayload);
|
|
1168
|
+
throw new Error(message || `Failed to ${label} (${response.status})`);
|
|
1169
|
+
}
|
|
1170
|
+
return responsePayload;
|
|
1171
|
+
};
|
|
1172
|
+
|
|
1034
1173
|
const deleteJson = async (url, label) => {
|
|
1035
1174
|
const response = await fetch(url, { method: "DELETE" });
|
|
1036
1175
|
const responsePayload = await readResponsePayload(response);
|
|
@@ -1055,6 +1194,16 @@
|
|
|
1055
1194
|
const fetchRequestLogs = async (limit) =>
|
|
1056
1195
|
fetchJson(API_ENDPOINTS.requestLogs(limit), "request logs");
|
|
1057
1196
|
|
|
1197
|
+
const normalizeSettingsPayload = (payload) => ({
|
|
1198
|
+
stickyThreadsEnabled: Boolean(payload?.stickyThreadsEnabled),
|
|
1199
|
+
preferEarlierResetAccounts: Boolean(payload?.preferEarlierResetAccounts),
|
|
1200
|
+
});
|
|
1201
|
+
|
|
1202
|
+
const fetchSettings = async () => {
|
|
1203
|
+
const payload = await fetchJson(API_ENDPOINTS.settings, "settings");
|
|
1204
|
+
return normalizeSettingsPayload(payload);
|
|
1205
|
+
};
|
|
1206
|
+
|
|
1058
1207
|
const registerApp = () => {
|
|
1059
1208
|
Alpine.data("feApp", () => ({
|
|
1060
1209
|
view: "dashboard",
|
|
@@ -1063,6 +1212,11 @@
|
|
|
1063
1212
|
ui: createUiConfig(),
|
|
1064
1213
|
dashboardData: createEmptyDashboardData(),
|
|
1065
1214
|
dashboard: createEmptyDashboardView(),
|
|
1215
|
+
settings: {
|
|
1216
|
+
stickyThreadsEnabled: false,
|
|
1217
|
+
preferEarlierResetAccounts: false,
|
|
1218
|
+
isSaving: false,
|
|
1219
|
+
},
|
|
1066
1220
|
accounts: {
|
|
1067
1221
|
selectedId: "",
|
|
1068
1222
|
rows: [],
|
|
@@ -1158,12 +1312,14 @@
|
|
|
1158
1312
|
primaryResult,
|
|
1159
1313
|
secondaryResult,
|
|
1160
1314
|
requestLogsResult,
|
|
1315
|
+
settingsResult,
|
|
1161
1316
|
] = await Promise.allSettled([
|
|
1162
1317
|
fetchAccounts(),
|
|
1163
1318
|
fetchUsageSummary(),
|
|
1164
1319
|
fetchUsageWindow("primary"),
|
|
1165
1320
|
fetchUsageWindow("secondary"),
|
|
1166
1321
|
fetchRequestLogs(50),
|
|
1322
|
+
fetchSettings(),
|
|
1167
1323
|
]);
|
|
1168
1324
|
const errors = [];
|
|
1169
1325
|
if (accountsResult.status !== "fulfilled") {
|
|
@@ -1196,6 +1352,12 @@
|
|
|
1196
1352
|
errors.push(requestLogsResult.reason);
|
|
1197
1353
|
}
|
|
1198
1354
|
|
|
1355
|
+
const settings =
|
|
1356
|
+
settingsResult.status === "fulfilled" ? settingsResult.value : null;
|
|
1357
|
+
if (settingsResult.status === "rejected") {
|
|
1358
|
+
errors.push(settingsResult.reason);
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1199
1361
|
const mergedAccounts = mergeUsageIntoAccounts(
|
|
1200
1362
|
accountsResult.value,
|
|
1201
1363
|
primaryUsage,
|
|
@@ -1208,6 +1370,7 @@
|
|
|
1208
1370
|
primaryUsage,
|
|
1209
1371
|
secondaryUsage,
|
|
1210
1372
|
requestLogs,
|
|
1373
|
+
settings,
|
|
1211
1374
|
},
|
|
1212
1375
|
preferredId,
|
|
1213
1376
|
);
|
|
@@ -1252,11 +1415,54 @@
|
|
|
1252
1415
|
primaryUsage: data.primaryUsage,
|
|
1253
1416
|
secondaryUsage: data.secondaryUsage,
|
|
1254
1417
|
requestLogs: data.requestLogs,
|
|
1418
|
+
settings: data.settings,
|
|
1255
1419
|
});
|
|
1420
|
+
if (data.settings) {
|
|
1421
|
+
this.settings.stickyThreadsEnabled = Boolean(
|
|
1422
|
+
data.settings.stickyThreadsEnabled,
|
|
1423
|
+
);
|
|
1424
|
+
this.settings.preferEarlierResetAccounts = Boolean(
|
|
1425
|
+
data.settings.preferEarlierResetAccounts,
|
|
1426
|
+
);
|
|
1427
|
+
}
|
|
1256
1428
|
this.ui.usageWindows = buildUsageWindowConfig(data.summary);
|
|
1257
1429
|
this.dashboard = buildDashboardView(this);
|
|
1258
1430
|
this.syncAccountSearchSelection();
|
|
1259
1431
|
},
|
|
1432
|
+
async saveSettings() {
|
|
1433
|
+
if (this.settings.isSaving) {
|
|
1434
|
+
return;
|
|
1435
|
+
}
|
|
1436
|
+
this.settings.isSaving = true;
|
|
1437
|
+
try {
|
|
1438
|
+
const payload = {
|
|
1439
|
+
stickyThreadsEnabled: this.settings.stickyThreadsEnabled,
|
|
1440
|
+
preferEarlierResetAccounts: this.settings.preferEarlierResetAccounts,
|
|
1441
|
+
};
|
|
1442
|
+
const updated = await putJson(
|
|
1443
|
+
API_ENDPOINTS.settings,
|
|
1444
|
+
payload,
|
|
1445
|
+
"save settings",
|
|
1446
|
+
);
|
|
1447
|
+
const normalized = normalizeSettingsPayload(updated);
|
|
1448
|
+
this.settings.stickyThreadsEnabled = normalized.stickyThreadsEnabled;
|
|
1449
|
+
this.settings.preferEarlierResetAccounts =
|
|
1450
|
+
normalized.preferEarlierResetAccounts;
|
|
1451
|
+
this.openMessageBox({
|
|
1452
|
+
tone: "success",
|
|
1453
|
+
title: "Settings saved",
|
|
1454
|
+
message: "Routing settings updated.",
|
|
1455
|
+
});
|
|
1456
|
+
} catch (error) {
|
|
1457
|
+
this.openMessageBox({
|
|
1458
|
+
tone: "error",
|
|
1459
|
+
title: "Settings save failed",
|
|
1460
|
+
message: error.message || "Failed to save settings.",
|
|
1461
|
+
});
|
|
1462
|
+
} finally {
|
|
1463
|
+
this.settings.isSaving = false;
|
|
1464
|
+
}
|
|
1465
|
+
},
|
|
1260
1466
|
focusAccountSearch() {
|
|
1261
1467
|
this.$refs.accountSearch?.focus();
|
|
1262
1468
|
},
|
|
@@ -1693,15 +1899,15 @@
|
|
|
1693
1899
|
const items =
|
|
1694
1900
|
this.view === "accounts"
|
|
1695
1901
|
? [
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1902
|
+
`Selection: ${this.accounts.selectedId || "--"}`,
|
|
1903
|
+
`Rotation: ${this.dashboardData.routing?.rotationEnabled ? "enabled" : "disabled"}`,
|
|
1904
|
+
`Last sync: ${lastSync}`,
|
|
1905
|
+
]
|
|
1700
1906
|
: [
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1907
|
+
`Last sync: ${lastSync}`,
|
|
1908
|
+
`Routing: ${routingLabel(this.dashboardData.routing?.strategy)}`,
|
|
1909
|
+
`Backend: ${this.backendPath}`,
|
|
1910
|
+
];
|
|
1705
1911
|
if (this.importState.isLoading) {
|
|
1706
1912
|
items.unshift(
|
|
1707
1913
|
`Importing ${this.importState.fileName || "auth.json"}...`,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codex-lb
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
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>
|
|
@@ -39,7 +39,7 @@ Classifier: Topic :: Software Development :: Libraries
|
|
|
39
39
|
Classifier: Topic :: System :: Networking
|
|
40
40
|
Requires-Python: >=3.13
|
|
41
41
|
Requires-Dist: aiohttp-retry>=2.9.1
|
|
42
|
-
Requires-Dist: aiohttp>=3.13.
|
|
42
|
+
Requires-Dist: aiohttp>=3.13.3
|
|
43
43
|
Requires-Dist: aiosqlite>=0.22.1
|
|
44
44
|
Requires-Dist: cryptography>=46.0.3
|
|
45
45
|
Requires-Dist: fastapi[standard]>=0.128.0
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
app/__init__.py,sha256=uqZSnn_VEL8TIUxsYqdf4zA2ByJYfjA06fArVAzrHFo,89
|
|
2
2
|
app/cli.py,sha256=gkIAkYOT9SbQjUDnVmwhVKZeKjL3YJCMrOjFINwBx54,544
|
|
3
|
-
app/dependencies.py,sha256=
|
|
4
|
-
app/main.py,sha256=
|
|
3
|
+
app/dependencies.py,sha256=kfB_TxeZve_cnBxhOHZezKwkOwTNFwoHzNCJDUkGJD8,4377
|
|
4
|
+
app/main.py,sha256=wCELolhaBfJCEfFfSELNTQ5hZ1Xo-G5Wa3vMkIOY6gk,4665
|
|
5
5
|
app/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
6
|
app/core/crypto.py,sha256=zUz2GVqXigzXB5zX2Diq2cR41Kl37bBqxJiZmWGiIcY,1145
|
|
7
7
|
app/core/errors.py,sha256=go4Q5vv6_Rt8ZS230Mp436yCViEKu_xICylGF0gvGJg,1805
|
|
8
8
|
app/core/plan_types.py,sha256=6if0Zj2sTrtvpijkiJ0AtfkY0EfwARjKQetcD7Q8DM8,1548
|
|
9
9
|
app/core/types.py,sha256=cVWOt-cYNjleEBvPYQVOO-tru38mZYsrBrlYRUfXPxg,208
|
|
10
|
-
app/core/auth/__init__.py,sha256=
|
|
10
|
+
app/core/auth/__init__.py,sha256=5BmHcd8P05YmMpnKAxokWVkOkZK2Opaf04qgEk9P8k4,3257
|
|
11
11
|
app/core/auth/models.py,sha256=iyygu0uHK7fu8txyIyCkXmlgqYPbqEZu3IFysD1t-gk,1557
|
|
12
12
|
app/core/auth/refresh.py,sha256=jhYxT2mQFX4CCvuBQfYNcI53iOtWa0vSkTuZAfbpd9k,5105
|
|
13
13
|
app/core/balancer/__init__.py,sha256=bGy7gITRPObt6P9NdG3pg8t3qWoSW0XWoADqfNYrhdg,405
|
|
14
|
-
app/core/balancer/logic.py,sha256=
|
|
14
|
+
app/core/balancer/logic.py,sha256=N8skZL7ye_qLtUMBcHjgqSjsuCKWFOPdBposu_DLxuc,6730
|
|
15
15
|
app/core/balancer/types.py,sha256=gDgjlTy-NH3YhHYl2-YYpIabnckN9Q8-4cRy6S1u0K4,191
|
|
16
16
|
app/core/clients/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
17
17
|
app/core/clients/http.py,sha256=yfFwsaIZNbSmNtyV02WnMKn00JdoNNSlSIx5xB3QY4o,987
|
|
@@ -19,69 +19,79 @@ app/core/clients/oauth.py,sha256=XgAzQVAMudBUCQ9nGKUC9N7zagSiawBdhIVmxf9HHwQ,118
|
|
|
19
19
|
app/core/clients/proxy.py,sha256=ZWs0TBYcOgDz_jZ3tucDsRiqQ_9TGxU1oPGiI1mu-bE,9378
|
|
20
20
|
app/core/clients/usage.py,sha256=kG7TXqmy8IX9m4wJx5fOGDB2hqunivOU6o2xfoXCGy4,4800
|
|
21
21
|
app/core/config/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
22
|
-
app/core/config/settings.py,sha256=
|
|
22
|
+
app/core/config/settings.py,sha256=h6JoHfsaqZpUmv8RPKw38zv45eHyjv53PbHCw3pK7h8,2648
|
|
23
23
|
app/core/openai/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
24
24
|
app/core/openai/models.py,sha256=SzVPqmp9IDbJTW6gLGHeLxrpAmFehJPSC4fVsqzEmQA,3344
|
|
25
25
|
app/core/openai/parsing.py,sha256=VuE1OyPAv1umrSbkzqa6dcjPYfk00isOVB3O0xUPLBw,1537
|
|
26
26
|
app/core/openai/requests.py,sha256=Nnd4ZtxUtXP-jJ7LGKpPf_JDBIFo-X9k0qMw9SyANTc,1675
|
|
27
|
-
app/core/usage/__init__.py,sha256=
|
|
28
|
-
app/core/usage/logs.py,sha256=
|
|
27
|
+
app/core/usage/__init__.py,sha256=8SBpiClJR2wl653Cj1DTvmVUvi2jzU6kKXJtq91ofmE,5726
|
|
28
|
+
app/core/usage/logs.py,sha256=8TrE9nccEjIJslMfAk8AJLmX_Zzeksu8qMTvrGwePx0,2129
|
|
29
29
|
app/core/usage/models.py,sha256=FtBQx4Rb7jpwcqxmGXxg7RTVV17LK1QOSWaJIkaaNoQ,878
|
|
30
30
|
app/core/usage/pricing.py,sha256=6p8rJ26Gk61mz2t_h9sa0T7NiPDUTiNpzoDewMzT6E0,5464
|
|
31
|
-
app/core/usage/quota.py,sha256=
|
|
32
|
-
app/core/usage/types.py,sha256=
|
|
31
|
+
app/core/usage/quota.py,sha256=rM6p5NshxrAO5sXcSIE69cFY8ChQXfa-_9WnjAI1uMU,2235
|
|
32
|
+
app/core/usage/types.py,sha256=EbZQjJuh2y2cMztRYf-Qe15AkPQd08LLu7WSlC4PWTY,2172
|
|
33
33
|
app/core/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
34
34
|
app/core/utils/request_id.py,sha256=gxafI-Se8dRQTir-HNGelMzC9S2gwYZntTkVzEqzp_I,705
|
|
35
35
|
app/core/utils/retry.py,sha256=UmBap1Wh-CBT7r4fHzVb_PI9-LR9-HjUtDzRnhRjP2U,822
|
|
36
36
|
app/core/utils/sse.py,sha256=DJMOU4vW5Ir_4WeL5t5t7i33aRMnqVPU0eQvGn4sBv8,537
|
|
37
37
|
app/core/utils/time.py,sha256=B6FfSe43Eq_puE6eourly1X3gajyihK2VOAwJ8M3wyI,497
|
|
38
38
|
app/db/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
39
|
-
app/db/models.py,sha256=
|
|
40
|
-
app/db/session.py,sha256=
|
|
41
|
-
app/db/migrations/__init__.py,sha256=
|
|
39
|
+
app/db/models.py,sha256=Wp7OUMYtqkT-8BD-aoYUsdZgTCMMXg-e0VU1vSKMRg8,5244
|
|
40
|
+
app/db/session.py,sha256=T_UdGcqzn5YXo-fuRW8s5xJQvOuJsV4eTSZE0_V2C44,3910
|
|
41
|
+
app/db/migrations/__init__.py,sha256=hRZVLgAQfP-okuhIiKkQtwfnPtBvYXcLJTqg0Yex1Vc,2633
|
|
42
42
|
app/db/migrations/versions/__init__.py,sha256=U4S_2y3zgLZVfMenHRaJFBW8yqh2mUBuI291LGQVOJ8,35
|
|
43
|
+
app/db/migrations/versions/add_accounts_chatgpt_account_id.py,sha256=5QHfko2f7dq0G34Cqe8CkcKbQor2W5LegfnVu_HgZN8,978
|
|
44
|
+
app/db/migrations/versions/add_accounts_reset_at.py,sha256=cO4Z4LU82JZADkBkYygxzkKdZrsNhGp899zPEtUBvtk,958
|
|
45
|
+
app/db/migrations/versions/add_dashboard_settings.py,sha256=SA6GpVU2-jStSyzKK1osLCTO_WKr4WF58fjNdP900Ec,778
|
|
46
|
+
app/db/migrations/versions/add_request_logs_reasoning_effort.py,sha256=jBy4rH5R2yCAzo5N7KbiA3tbjsoxxDjAy95Poe_qen4,771
|
|
43
47
|
app/db/migrations/versions/normalize_account_plan_types.py,sha256=WpPhkJ2gtpgw5mbErj2He9ahjeqNV3Z7c9RmmMjMmtM,575
|
|
44
48
|
app/modules/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
45
49
|
app/modules/accounts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
46
50
|
app/modules/accounts/api.py,sha256=rSkYrg3_p_JE1kHZ4xXa5lBFTdT40v1oNAmlTDGOguY,2807
|
|
47
|
-
app/modules/accounts/auth_manager.py,sha256=
|
|
48
|
-
app/modules/accounts/repository.py,sha256=
|
|
51
|
+
app/modules/accounts/auth_manager.py,sha256=YUKdeHbszRLYZ4uRQzvk6ICk2cB73LjlJMEiWphBWWw,4682
|
|
52
|
+
app/modules/accounts/repository.py,sha256=xOj47_BeYzVPj_WBejrlGtlTDd0tNR4o6sZIrM8QriE,3382
|
|
49
53
|
app/modules/accounts/schemas.py,sha256=gtlbPg5uxM3t_V5JxCL6eP-UaU6TSE0UoX2yIpxM_a0,1659
|
|
50
|
-
app/modules/accounts/service.py,sha256=
|
|
54
|
+
app/modules/accounts/service.py,sha256=M3_SRD21yp0YN820IHdqNIlZsynNsSUN6TO6RcESDeI,8973
|
|
51
55
|
app/modules/health/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
52
56
|
app/modules/health/api.py,sha256=CSs09qeEy0DjM9twmmQGg-kNt2J4XNFBX72CwLuK-Gs,297
|
|
53
57
|
app/modules/health/schemas.py,sha256=nnF32SQbR-5rLSaRtwwiC3ZOhjSnRHPgr2cagwW2rqo,177
|
|
54
58
|
app/modules/oauth/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
55
59
|
app/modules/oauth/api.py,sha256=Wu_DEXgN3hP9Si6MMkl6-0v_Blwjahqwtun1iP7DjVk,1877
|
|
56
60
|
app/modules/oauth/schemas.py,sha256=sdDKP7u9bO87lcZXjK7uSokurHPS22nN2p_jkw9iEBc,777
|
|
57
|
-
app/modules/oauth/service.py,sha256=
|
|
61
|
+
app/modules/oauth/service.py,sha256=fEzUtoq1g-20NgWZlB_maA2XB-scMa0HJV24SxZPnqQ,12908
|
|
58
62
|
app/modules/oauth/templates/oauth_success.html,sha256=YNSGUIozcZEJQjpFtM2sgF4n8jqfbmx8LRwdXTraym4,3799
|
|
59
63
|
app/modules/proxy/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
60
64
|
app/modules/proxy/api.py,sha256=BR_qNlNZg2Ft5GYQ-7AzlLlJFf6RVJmlzJzFr_KHUIc,2921
|
|
61
65
|
app/modules/proxy/helpers.py,sha256=-XVTNTpkDvJ3KfsuGHztfUVBGBFm2H-AaNTNH2dNe6o,8739
|
|
62
|
-
app/modules/proxy/load_balancer.py,sha256=
|
|
66
|
+
app/modules/proxy/load_balancer.py,sha256=Forib096qbWud11aKw9TRbBN1bIQjOmE3ZYY62vhVYk,9831
|
|
63
67
|
app/modules/proxy/schemas.py,sha256=55pXtUCl2R_93kAPOJJ7Ji4Jn3qVu10vq2KSCCkNdp4,2748
|
|
64
|
-
app/modules/proxy/service.py,sha256=
|
|
68
|
+
app/modules/proxy/service.py,sha256=wXXXu4Nd88RDzY1EQvA7OZPH5EW1F9hCINB1vGjT1q8,23647
|
|
69
|
+
app/modules/proxy/sticky_repository.py,sha256=peFaAnaCVr064W-Sh0Kjvz-MuPbBfrh86RLQOGJ6qqs,2177
|
|
65
70
|
app/modules/proxy/types.py,sha256=iqEyoO8vGr8N5oEzUSvVWCai7UZbJAU62IvO7JNS9qs,927
|
|
66
71
|
app/modules/request_logs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
67
72
|
app/modules/request_logs/api.py,sha256=6nV2Uv_hnK7WI3gNpKrgTx4MyUQIXk1QxKp40nij0Xo,1037
|
|
68
|
-
app/modules/request_logs/repository.py,sha256=
|
|
69
|
-
app/modules/request_logs/schemas.py,sha256=
|
|
70
|
-
app/modules/request_logs/service.py,sha256=
|
|
73
|
+
app/modules/request_logs/repository.py,sha256=iIaXGIG1qjol4UiTsVASIJN0g5zIdMg6tRNolkXZ0pI,3460
|
|
74
|
+
app/modules/request_logs/schemas.py,sha256=Xlpk2hEVCQxA2TI7dh4EA6G0kSfzYcT_LlvyTVLIW2w,675
|
|
75
|
+
app/modules/request_logs/service.py,sha256=8IvC4GIhPh6FVQGkuww1Ej1onFanObnX7mY3tDEy99A,2643
|
|
76
|
+
app/modules/settings/__init__.py,sha256=U4S_2y3zgLZVfMenHRaJFBW8yqh2mUBuI291LGQVOJ8,35
|
|
77
|
+
app/modules/settings/api.py,sha256=dbnvKUF4lgX5ipxOik4SJBeJ6hctJKe3lcT2Uf0F5BQ,1461
|
|
78
|
+
app/modules/settings/repository.py,sha256=r-GjuURPcftCx5HnFOZKFpv34g9bvy0m53owVCc3tpI,1204
|
|
79
|
+
app/modules/settings/schemas.py,sha256=V27alhIa4LtJHNPuZIj_EGX0v0kzn0v3esn27s1z_bc,343
|
|
80
|
+
app/modules/settings/service.py,sha256=FUayQsKEsZf2roe84j4HRH7vTXXi4W071IZlOCOkjpM,1188
|
|
71
81
|
app/modules/shared/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
72
|
-
app/modules/shared/schemas.py,sha256=
|
|
82
|
+
app/modules/shared/schemas.py,sha256=2JgPY4N6vRXX7C0ETackykmjKgTjobZYBMWiwpPfLic,650
|
|
73
83
|
app/modules/usage/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
74
84
|
app/modules/usage/api.py,sha256=jpPc2VSjmiP6DjRZvotHCh_trLINOngSyTdS7MY9fgg,1108
|
|
75
85
|
app/modules/usage/repository.py,sha256=DtJI4kgajW7YUJ0JKJjdNCPBXT_fdBwqDoepi9aznyA,4791
|
|
76
|
-
app/modules/usage/schemas.py,sha256=
|
|
77
|
-
app/modules/usage/service.py,sha256=
|
|
78
|
-
app/modules/usage/updater.py,sha256=
|
|
79
|
-
app/static/7.css,sha256=
|
|
80
|
-
app/static/index.css,sha256=
|
|
81
|
-
app/static/index.html,sha256=
|
|
82
|
-
app/static/index.js,sha256=
|
|
83
|
-
codex_lb-0.
|
|
84
|
-
codex_lb-0.
|
|
85
|
-
codex_lb-0.
|
|
86
|
-
codex_lb-0.
|
|
87
|
-
codex_lb-0.
|
|
86
|
+
app/modules/usage/schemas.py,sha256=eCgunQOvQeYEqv9IecjunONPVLpg2MPn_YGzsnBTcpQ,1633
|
|
87
|
+
app/modules/usage/service.py,sha256=8-XX8m4TgQteum-a53l0DS-EL2NnC4r9artehfFltvM,10315
|
|
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,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|