@bongos/core 1.24.19 → 1.24.20
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/.bongos-core.json +21 -21
- package/docs/copy-inventory.md +27 -21
- package/docs/copy-registry.json +86 -22
- package/docs/module-api-changelog.md +2 -0
- package/docs/page-readings.json +81 -79
- package/modules/ai-studio/studio.js +1 -1
- package/modules/hall-ui/public/ai-studio-lib.js +74 -9
- package/modules/hall-ui/public/ai-studio.css +12 -0
- package/modules/hall-ui/public/ai-studio.js +37 -1
- package/modules/hall-ui/public/roster.css +43 -0
- package/modules/hall-ui/records/builders-roster.md +9 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +10 -0
- package/src/module-api.js +1 -1
- package/tests/hall_ai_studio.mjs +44 -1
- package/tests/hall_roster_world.mjs +10 -0
package/release-notes.json
CHANGED
|
@@ -10999,5 +10999,15 @@
|
|
|
10999
10999
|
"id": "1005030",
|
|
11000
11000
|
"text": "A mockup of the Builders leaderboard now shows the circuit-trace divider from the owner's Studio idea between the column headings and the table. The live page is unchanged."
|
|
11001
11001
|
}
|
|
11002
|
+
],
|
|
11003
|
+
"1.24.20": [
|
|
11004
|
+
{
|
|
11005
|
+
"id": "1005031",
|
|
11006
|
+
"text": "The Studio now shows what a picture will cost and asks before it starts, shows the final cost, and writes its answers in readable headings and lists."
|
|
11007
|
+
},
|
|
11008
|
+
{
|
|
11009
|
+
"id": "1005032",
|
|
11010
|
+
"text": "The Builders page now has the circuit-trace divider from the owner's Studio idea between the column headings and the first row, in light and dark and on phones."
|
|
11011
|
+
}
|
|
11002
11012
|
]
|
|
11003
11013
|
}
|
package/src/module-api.js
CHANGED
|
@@ -81,7 +81,7 @@ const { sleep } = require('./sleep');
|
|
|
81
81
|
// MAJOR (see allowBoxScope below): passes the request through untouched.
|
|
82
82
|
function deprecatedNoopMiddleware(_req, _res, next) { next(); }
|
|
83
83
|
|
|
84
|
-
const CORE_VERSION = '1.24.
|
|
84
|
+
const CORE_VERSION = '1.24.20'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
|
|
85
85
|
|
|
86
86
|
// A namespaced logger so a module's log lines are attributable + consistent.
|
|
87
87
|
// Usage: const log = api.logger('discord'); log.info('mounted');
|
package/tests/hall_ai_studio.mjs
CHANGED
|
@@ -53,13 +53,37 @@ test('the receipt: our key splits the provider and our markup; an own key charge
|
|
|
53
53
|
const ours = lib.receipt(done, 1.08);
|
|
54
54
|
assert.deepEqual(ours.rows, [['Paid with', 'your balance'], ['OpenRouter', '$0.0390'], ['Our 8%', '$0.0031'], ['You paid', '$0.0421']]);
|
|
55
55
|
const gemini = lib.receipt(dated(fixture('ai-studio__requests__own')).request, 1.08);
|
|
56
|
-
assert.deepEqual(gemini.rows, [['Paid with', 'your own Gemini key'], ['Cost', '
|
|
56
|
+
assert.deepEqual(gemini.rows, [['Paid with', 'your own Gemini key'], ['Cost', 'about $0.0640']]);
|
|
57
|
+
assert.match(gemini.note, /estimate/);
|
|
57
58
|
assert.match(gemini.note, /We charged nothing/);
|
|
58
59
|
const openrouter = lib.receipt({ status: 'done', key_source: 'own', provider_cost_micro: 39000 }, 1.08);
|
|
59
60
|
assert.deepEqual(openrouter.rows, [['Paid with', 'your own OpenRouter key'], ['Cost', '$0.0390']]);
|
|
60
61
|
assert.deepEqual(lib.receipt(dated(fixture('ai-studio__requests__failed')).request, 1.08).rows, [], 'a failed run has no receipt');
|
|
61
62
|
});
|
|
62
63
|
|
|
64
|
+
test('the estimate is the median of finished runs, capped at the ceiling; the cost line reads plainly', () => {
|
|
65
|
+
assert.deepEqual(lib.estimate([], 500000), { typical: null, max: 500000, basis: 0 });
|
|
66
|
+
const runs = [{ status: 'done', key_source: 'ours', price_micro: 40000 }, { status: 'done', key_source: 'ours', price_micro: 20000 }, { status: 'done', key_source: 'ours', price_micro: 90000 }, { status: 'failed', key_source: 'ours', price_micro: 999999 }, { status: 'done', key_source: 'own', provider_cost_micro: 30000, price_micro: 0 }];
|
|
67
|
+
assert.deepEqual(lib.estimate(runs, 500000, true), { typical: 40000, max: 500000, basis: 3 });
|
|
68
|
+
assert.deepEqual(lib.estimate(runs, 500000, false), { typical: 30000, max: 500000, basis: 1 }, 'an own-key guess uses the provider cost, no markup');
|
|
69
|
+
assert.equal(lib.estimate([{ status: 'done', key_source: 'ours', price_micro: 900000 }], 500000, true).typical, 500000);
|
|
70
|
+
assert.equal(lib.costHeadline({ status: 'done', key_source: 'ours', price_micro: 41000 }), 'This cost $0.0410, taken from your balance.');
|
|
71
|
+
assert.match(lib.costHeadline({ status: 'done', key_source: 'own', provider_cost_micro: null }), /about \$0\.0640 on your Gemini key/);
|
|
72
|
+
assert.equal(lib.geminiEstimate(3), 142000, 'a reply plus each picture');
|
|
73
|
+
assert.equal(lib.costHeadline({ status: 'failed', key_source: 'ours', price_micro: 1 }), '');
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test('the reply reads as headings, a list and folded code, never raw markup', () => {
|
|
77
|
+
const fence = '`'.repeat(3);
|
|
78
|
+
const md = ['### What works', '* **Feel:** the `rule` line', '* Second', '--', `${fence}xml`, '<svg a="1"/>', fence, 'Done <b>x</b>'].join('\n');
|
|
79
|
+
const html = lib.replyHtml(md, escapeHtml);
|
|
80
|
+
assert.ok(html.includes('<h3 class="ais-rh">What works</h3>'), html);
|
|
81
|
+
assert.ok(html.includes('<ul><li><b>Feel:</b> the <code>rule</code> line</li><li>Second</li></ul>'), html);
|
|
82
|
+
assert.ok(html.includes('<summary>Code from the answer</summary><pre><svg a="1"/></pre></details>'), html);
|
|
83
|
+
assert.ok(html.includes('<p>Done <b>x</b></p>'), html);
|
|
84
|
+
assert.ok(!html.includes('###') && !html.includes(fence) && !html.includes('<p>--'), html);
|
|
85
|
+
});
|
|
86
|
+
|
|
63
87
|
test('the month: added, spent, money back and own-key requests, from the ledger', () => {
|
|
64
88
|
const bal = dated(fixture('ai-studio__balance'));
|
|
65
89
|
const sum = lib.monthSummary(bal.ledger, new Date());
|
|
@@ -368,6 +392,9 @@ test('page: step 3 sends the request on the card pressed, with copies of the car
|
|
|
368
392
|
assert.match(p.page.innerHTML, /\$12\.40 left/);
|
|
369
393
|
const ours = p.page.querySelectorAll('[data-pay]').find((b) => b.getAttribute('data-pay') === 'ours');
|
|
370
394
|
await ours.fire('click');
|
|
395
|
+
assert.match(p.page.querySelector('#ais-confirm').innerHTML, /to make this/, 'the cost is shown before anything is sent');
|
|
396
|
+
assert.equal(p.calls.filter((c) => c.method === 'POST').length, 0, 'nothing is sent until the artist says yes');
|
|
397
|
+
await p.page.querySelector('#ais-confirm').querySelector('#ais-confirm-go').fire('click');
|
|
371
398
|
await settle();
|
|
372
399
|
const uploads = p.calls.filter((c) => c.method === 'POST' && c.url.endsWith('/ai-studio/uploads'));
|
|
373
400
|
assert.equal(uploads.length, 3, 'a file goes with one request only, so each carried file is uploaded again');
|
|
@@ -470,6 +497,19 @@ test('walls: the studio home shows the AI door only where the module is on', ()
|
|
|
470
497
|
assert.match(js, /loadAi\(\)\]/);
|
|
471
498
|
});
|
|
472
499
|
|
|
500
|
+
test('page: Not yet closes the confirm box and sends nothing', async () => {
|
|
501
|
+
const p = bootPage('?step=make&from=1001');
|
|
502
|
+
await settle();
|
|
503
|
+
const ours = p.page.querySelectorAll('[data-pay]').find((b) => b.getAttribute('data-pay') === 'ours');
|
|
504
|
+
await ours.fire('click');
|
|
505
|
+
const box = p.page.querySelector('#ais-confirm');
|
|
506
|
+
assert.ok(box.innerHTML.includes('It will never cost more than $0.50.'), box.innerHTML);
|
|
507
|
+
await box.querySelector('#ais-confirm-cancel').fire('click');
|
|
508
|
+
assert.equal(box.innerHTML, '');
|
|
509
|
+
assert.equal(box.hidden, true);
|
|
510
|
+
assert.equal(p.calls.filter((c) => c.method === 'POST').length, 0);
|
|
511
|
+
});
|
|
512
|
+
|
|
473
513
|
test('page: with both own keys saved, step 3 offers one card each and sends the one pressed', async () => {
|
|
474
514
|
const both = { keys: [{ provider: 'openrouter', set: true, last4: '4f2a' }, { provider: 'gemini', set: true, last4: '9c1d' }] };
|
|
475
515
|
assert.deepEqual(lib.ownKeys(both).map((k) => k.provider), ['openrouter', 'gemini']);
|
|
@@ -480,6 +520,9 @@ test('page: with both own keys saved, step 3 offers one card each and sends the
|
|
|
480
520
|
assert.match(p.page.innerHTML, /My Gemini key/);
|
|
481
521
|
const gem = p.page.querySelectorAll('[data-pay]').find((b) => b.getAttribute('data-pay') === 'gemini');
|
|
482
522
|
await gem.fire('click');
|
|
523
|
+
assert.match(p.page.querySelector('#ais-confirm').innerHTML, /to make this/, 'the cost is shown before anything is sent');
|
|
524
|
+
assert.equal(p.calls.filter((c) => c.method === 'POST').length, 0, 'nothing is sent until the artist says yes');
|
|
525
|
+
await p.page.querySelector('#ais-confirm').querySelector('#ais-confirm-go').fire('click');
|
|
483
526
|
await settle();
|
|
484
527
|
const sent = p.calls.find((c) => c.method === 'POST' && c.url.endsWith('/ai-studio/requests'));
|
|
485
528
|
assert.equal(sent.body.pay_with, 'gemini');
|
|
@@ -302,3 +302,13 @@ test('the states files exercise the redesign\'s new states', () => {
|
|
|
302
302
|
const sessions = JSON.parse(read('sessions.states.json'));
|
|
303
303
|
assert.deepEqual(sessions.states.ledger.expect.visible, ['#stats-scroll', '#sessions-scroll']);
|
|
304
304
|
});
|
|
305
|
+
|
|
306
|
+
test('the roster draws the circuit divider in CSS, on the first row\'s cell and not the row', () => {
|
|
307
|
+
const css = read('roster.css');
|
|
308
|
+
const rule = css.slice(css.indexOf('the circuit divider'));
|
|
309
|
+
assert.match(rule, /tr:first-child:not\(:has\(\.ledger__empty\)\) td:first-child::before/, 'the traces hang on the first cell');
|
|
310
|
+
assert.match(rule, /td:first-child::after/, 'and so do the nodes');
|
|
311
|
+
assert.doesNotMatch(rule, /:has\(\.ledger__empty\)\)::(before|after)/, 'never on the tr itself: a tr pseudo becomes a table cell and shifts the row');
|
|
312
|
+
assert.match(rule, /background: var\(--ink-faint\)/, 'traces spend the faint ink token');
|
|
313
|
+
assert.match(rule, /background: var\(--accent\)/, 'nodes spend the accent token');
|
|
314
|
+
});
|