@xuda.io/account_module 1.2.2296 → 1.2.2297
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.
- package/download_model.js +56 -0
- package/download_model.mjs +56 -0
- package/get_active_account_profile_info.mjs +21 -0
- package/get_user_info.mjs +126 -0
- package/index copy.js +1467 -0
- package/index.js +1467 -0
- package/index.mjs +49 -15
- package/index.mjs.premerge.bak +4725 -0
- package/package.json +1 -1
- package/scripts/run_backfill.mjs.premerge.bak +51 -0
- package/scripts/run_migrate_account.mjs.premerge.bak +54 -0
- package/scripts/run_migrate_all.mjs.premerge.bak +109 -0
package/index.mjs
CHANGED
|
@@ -1999,10 +1999,10 @@ export const ops_account_snapshot = async function (req = {}) {
|
|
|
1999
1999
|
const a = ar.data;
|
|
2000
2000
|
let credits = { max: 0, used: 0, remaining: 0 };
|
|
2001
2001
|
try {
|
|
2002
|
-
const cr = await get_account_ai_usage({ uid: account_uid });
|
|
2002
|
+
const cr = await get_account_ai_usage({ uid: account_uid, ..._LIFETIME_WINDOW });
|
|
2003
2003
|
if (cr && cr.code === 55) {
|
|
2004
2004
|
const max = Math.round((cr.data?.credits?.total || 0) * 100) / 100;
|
|
2005
|
-
const used = Math.round((cr.data?.usage
|
|
2005
|
+
const used = Math.round(_usage_total(cr.data?.usage) * 100) / 100;
|
|
2006
2006
|
credits = { max, used, remaining: Math.round((max - used) * 100) / 100, by_source: cr.data?.credits || {} };
|
|
2007
2007
|
}
|
|
2008
2008
|
} catch (e) { console.error('[ops snapshot credits]', e.message); }
|
|
@@ -6471,11 +6471,13 @@ export const save_cache_hit = async function (_id) {
|
|
|
6471
6471
|
///////////////////////////////
|
|
6472
6472
|
|
|
6473
6473
|
export const get_account_ai_usage = async function (req, job_id, headers) {
|
|
6474
|
-
|
|
6475
|
-
|
|
6476
|
-
|
|
6477
|
-
|
|
6478
|
-
|
|
6474
|
+
// Default to the LIFETIME window (all-time spend) — the credit model is a
|
|
6475
|
+
// remaining-pool ledger (see _LIFETIME_WINDOW + the Credit Management page).
|
|
6476
|
+
// Callers that want a specific range (the usage chart in WorkspaceUsage.vue)
|
|
6477
|
+
// pass explicit year/month/day. The nav meter + ring call with no window, so
|
|
6478
|
+
// this makes them read lifetime "used" and match the Credit Management page
|
|
6479
|
+
// instead of a current-month slice against a lifetime pool.
|
|
6480
|
+
const { uid, year_from = 2000, month_from = 1, day_from = 1, year_to = 2999, month_to = 12, day_to = 31 } = req;
|
|
6479
6481
|
try {
|
|
6480
6482
|
const app_id = await get_account_default_project_id(uid);
|
|
6481
6483
|
|
|
@@ -6499,8 +6501,13 @@ export const get_account_ai_usage = async function (req, job_id, headers) {
|
|
|
6499
6501
|
|
|
6500
6502
|
// console.log('ai_usage', ai_usage.rows);
|
|
6501
6503
|
|
|
6502
|
-
let total_usage = response.total_usage;
|
|
6503
6504
|
let profile = response.profiles;
|
|
6505
|
+
// The ai_usage2 list function emits per-profile spend but sometimes a null
|
|
6506
|
+
// rolled-up total. Roll it up HERE at the source so every consumer gets a real
|
|
6507
|
+
// usage.total — including the dashboard nav (shared-store fetchCredits reads
|
|
6508
|
+
// data.usage.total directly). Without this, Number(null) -> 0 hid all spend in
|
|
6509
|
+
// the meter/ring and disabled the account-wide credit cap.
|
|
6510
|
+
let total_usage = _usage_total({ total: response.total_usage, profile });
|
|
6504
6511
|
|
|
6505
6512
|
const ai_credits = await db_module.get_couch_view('xuda_billing', 'ai_credits', {
|
|
6506
6513
|
startkey: [uid, ''],
|
|
@@ -6610,6 +6617,23 @@ const _fold_usage = function (profile_map) {
|
|
|
6610
6617
|
return { by_profile, by_user };
|
|
6611
6618
|
};
|
|
6612
6619
|
|
|
6620
|
+
// Single source of truth for an account's rolled-up AI-credit usage. The usage
|
|
6621
|
+
// view reliably emits per-profile spend but sometimes returns a null rolled-up
|
|
6622
|
+
// total (usage.total); Number(null)||0 then collapsed "used" to 0 EVERYWHERE —
|
|
6623
|
+
// hiding spend in the nav/meter AND making the account-wide credit cap never
|
|
6624
|
+
// fire. Prefer the view's own total when finite, otherwise sum the per-profile
|
|
6625
|
+
// fold (same source, same credit unit).
|
|
6626
|
+
const _usage_total = function (usage_data) {
|
|
6627
|
+
// NOTE: Number(null) === 0 (and IS finite), which is exactly what collapsed
|
|
6628
|
+
// "used" to 0 — so guard on the RAW type, not the coerced number. Only trust
|
|
6629
|
+
// the view's own total when it is a real finite number; otherwise roll up the
|
|
6630
|
+
// per-profile fold.
|
|
6631
|
+
const t = usage_data?.total;
|
|
6632
|
+
if (typeof t === 'number' && Number.isFinite(t)) return t;
|
|
6633
|
+
const { by_profile } = _fold_usage(usage_data?.profile);
|
|
6634
|
+
return Object.values(by_profile).reduce((a, b) => a + (Number(b) || 0), 0);
|
|
6635
|
+
};
|
|
6636
|
+
|
|
6613
6637
|
// Wide window so "used" reads as all-time spend (the lifetime / remaining-pool model).
|
|
6614
6638
|
const _LIFETIME_WINDOW = { year_from: 2000, month_from: 1, day_from: 1, year_to: 2999, month_to: 12, day_to: 31 };
|
|
6615
6639
|
|
|
@@ -6625,8 +6649,8 @@ const _get_scoped_usage = async function (owner_uid, need_raw) {
|
|
|
6625
6649
|
if (cached && now - cached.ts < _SCOPED_USAGE_TTL && (!need_raw || cached.has_raw)) return cached.data;
|
|
6626
6650
|
|
|
6627
6651
|
const usage_ret = await get_account_ai_usage({ uid: owner_uid, ..._LIFETIME_WINDOW });
|
|
6628
|
-
const total = Number(usage_ret?.data?.usage?.total) || 0;
|
|
6629
6652
|
const { by_profile, by_user } = _fold_usage(usage_ret?.data?.usage?.profile);
|
|
6653
|
+
const total = _usage_total(usage_ret?.data?.usage);
|
|
6630
6654
|
|
|
6631
6655
|
let by_model = {};
|
|
6632
6656
|
let by_source = {};
|
|
@@ -6757,8 +6781,8 @@ export const evaluate_credit_gate = async function (req) {
|
|
|
6757
6781
|
|
|
6758
6782
|
// Back-compat: no rules or disabled => legacy single ledger cap, byte-for-byte.
|
|
6759
6783
|
if (!rules || rules.enabled === false) {
|
|
6760
|
-
const usage = await get_account_ai_usage({ uid: owner });
|
|
6761
|
-
const over = (Number(usage?.data?.credits?.total) || 0) - (
|
|
6784
|
+
const usage = await get_account_ai_usage({ uid: owner, ..._LIFETIME_WINDOW });
|
|
6785
|
+
const over = (Number(usage?.data?.credits?.total) || 0) - _usage_total(usage?.data?.usage) < -0.5;
|
|
6762
6786
|
return { block: over, scope: over ? 'pool' : null, reason: over ? _SCOPE_REASON.ledger : '', hard_breached: over ? [{ scope: 'pool', key: 'ledger' }] : [], soft_breached: [], account_id: owner };
|
|
6763
6787
|
}
|
|
6764
6788
|
|
|
@@ -7105,10 +7129,13 @@ export const broadcast_credits = function (uid) {
|
|
|
7105
7129
|
_credits_broadcast_timers[uid] = setTimeout(async () => {
|
|
7106
7130
|
delete _credits_broadcast_timers[uid];
|
|
7107
7131
|
try {
|
|
7108
|
-
|
|
7132
|
+
// Lifetime window (matches the Credit Management meter) + the same null-total
|
|
7133
|
+
// rollup as _get_scoped_usage — get_account_ai_usage can return a null
|
|
7134
|
+
// rolled-up usage.total, which otherwise broadcasts used:0 to the live nav.
|
|
7135
|
+
const ret = await get_account_ai_usage({ uid, ..._LIFETIME_WINDOW });
|
|
7109
7136
|
if (!ret || ret.code !== 55) return;
|
|
7110
7137
|
const max = Math.round((ret.data?.credits?.total || 0) * 100) / 100;
|
|
7111
|
-
const used = Math.round((ret.data?.usage
|
|
7138
|
+
const used = Math.round(_usage_total(ret.data?.usage) * 100) / 100;
|
|
7112
7139
|
ws_dashboard_msa.emit_message_to_dashboard({
|
|
7113
7140
|
service: 'credits_update',
|
|
7114
7141
|
to: [uid],
|
|
@@ -7907,7 +7934,12 @@ export const newsletter_generate = async function (req = {}) {
|
|
|
7907
7934
|
if (!req.system && !_ops_is_super(req)) return { code: -403, data: 'superuser only' };
|
|
7908
7935
|
// Master-only (dev exempt for testing): the issue DB must never be created
|
|
7909
7936
|
// on a regional couch, and the weekly coupon/sample flow belongs to master.
|
|
7910
|
-
|
|
7937
|
+
// MASTER ONLY, dev included. Dev is is_debug but still sends real [SAMPLE]/
|
|
7938
|
+
// [OPS] mail and keeps its own issue counter, so a "Generate now" on dev
|
|
7939
|
+
// blasts live info@ inboxes and creates dev-only drafts that never appear in
|
|
7940
|
+
// the master Newsletter Manager (dev does not replicate to master). Preview /
|
|
7941
|
+
// Test still work on dev for reviewing render; only issuing is locked here.
|
|
7942
|
+
if (process.env.XUDA_HOSTNAME !== 'master.xuda.ai') return { code: -1, data: 'newsletter_generate runs on master only' };
|
|
7911
7943
|
await _nl_ensure_storage();
|
|
7912
7944
|
|
|
7913
7945
|
// Two DISTINCT tips: one for "Did you know?", one for "Tip of the week".
|
|
@@ -8068,7 +8100,9 @@ export const newsletter_publish = async function (req = {}) {
|
|
|
8068
8100
|
if (!_ops_is_super(req)) return { code: -403, data: 'superuser only' };
|
|
8069
8101
|
// Same master-only gate as generate: the blast must run where xuda_newsletter
|
|
8070
8102
|
// and the authoritative xuda_accounts live (dev exempt for testing).
|
|
8071
|
-
|
|
8103
|
+
// MASTER ONLY, dev included: a publish on dev would blast dev's opted-in
|
|
8104
|
+
// accounts with real mail. Locked so only the real master issues a newsletter.
|
|
8105
|
+
if (process.env.XUDA_HOSTNAME !== 'master.xuda.ai') return { code: -1, data: 'newsletter_publish runs on master only' };
|
|
8072
8106
|
const id = req.id || `nl_issue_${req.issue_number}`;
|
|
8073
8107
|
const gret = await db_module.get_couch_doc(NEWSLETTER_DB, id);
|
|
8074
8108
|
if (gret.code < 0 || !gret.data) return { code: -1, data: 'issue not found' };
|