privateboard 0.1.19 → 0.1.21

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/public/app.js CHANGED
@@ -308,6 +308,31 @@
308
308
  : (btn.getAttribute("data-more") || this._t("convene_show_more"));
309
309
  btn.textContent = label;
310
310
  });
311
+ // Live-subtitle minimize / restore · attached in CAPTURE phase
312
+ // so it wins ahead of any outside-click handlers (picker dismiss
313
+ // / overlay close) that might `stopPropagation` and swallow the
314
+ // first click. Without capture, a click on the minimize button
315
+ // takes two presses to register — the first one gets eaten by an
316
+ // unrelated bubble-phase listener.
317
+ const onSubBtn = (e) => {
318
+ if (e.target.closest("[data-rt-sub-min]")) {
319
+ e.preventDefault();
320
+ e.stopPropagation();
321
+ document.body.classList.add("is-subtitle-min");
322
+ return;
323
+ }
324
+ if (e.target.closest("[data-rt-sub-restore]")) {
325
+ e.preventDefault();
326
+ e.stopPropagation();
327
+ document.body.classList.remove("is-subtitle-min");
328
+ // Re-render so the subtitle re-populates with the latest
329
+ // streaming / replay state (it was hidden via CSS, not via
330
+ // `slot.hidden`, so its innerHTML is preserved — but a
331
+ // fresh paint guarantees the cursor / kicker is current).
332
+ this.renderRtSubtitle();
333
+ }
334
+ };
335
+ document.addEventListener("click", onSubBtn, true);
311
336
  document.addEventListener("boardroom:locale", () => {
312
337
  this.renderSidebarRooms();
313
338
  this.renderSidebarAgents();
@@ -1471,6 +1496,16 @@
1471
1496
  this.currentMessages = this.currentMessages.filter((m) => m.id !== data.messageId);
1472
1497
  const article = document.querySelector(`[data-message-id="${data.messageId}"]`);
1473
1498
  if (article) article.remove();
1499
+ // Round-table stage · the just-removed message was the in-flight
1500
+ // streaming placeholder. Without repainting, `renderRoundTable`'s
1501
+ // speaker-detection path (which scans `currentMessages` for the
1502
+ // most recent `meta.streaming === true` entry) keeps reporting
1503
+ // the removed director as still speaking — the "thinking / speaking"
1504
+ // bubble stays glued to their seat after a hard-pause "stop
1505
+ // immediately" tore the message out. Mirrors `message-final`'s
1506
+ // repaint at line ~1440.
1507
+ this.renderRoundTable();
1508
+ this.renderRtSubtitle();
1474
1509
  });
1475
1510
 
1476
1511
  this.sse.addEventListener("voice-chunk", (e) => {
@@ -1527,7 +1562,17 @@
1527
1562
 
1528
1563
  this.sse.addEventListener("voice-final", (e) => {
1529
1564
  const data = JSON.parse(e.data);
1530
- const q = this.voiceQueues[data.messageId] || (this.voiceQueues[data.messageId] = { chunks: [], final: false, scheduled: false, roomId, messageId: data.messageId });
1565
+ // Do NOT auto-create a queue entry on voice-final · this event
1566
+ // can arrive AFTER a hard-pause `room-paused` already cleared
1567
+ // `voiceQueues` (the speakerTurn finalizer runs on its own
1568
+ // async tick, separate from the route handler that emitted
1569
+ // room-paused — so the SSE ordering is voice-final-after-pause
1570
+ // even though emission was earlier in source code). Re-creating
1571
+ // the queue here re-arms renderRoundTable's voiceQueues path
1572
+ // and the speaking bubble glues itself to the seat forever.
1573
+ // If the queue is already gone, there's nothing to finalize.
1574
+ const q = this.voiceQueues[data.messageId];
1575
+ if (!q) return;
1531
1576
  q.final = true;
1532
1577
  q.messageId = data.messageId;
1533
1578
  q.roomId = roomId;
@@ -2634,8 +2679,8 @@
2634
2679
  },
2635
2680
 
2636
2681
  /** Pre-flight gate · returns true when at least one MODEL provider
2637
- * (anthropic / openai / google / xai / deepseek / openrouter) is
2638
- * configured. When none are, opens a modal asking the user to
2682
+ * (anthropic / openai / google / xai / deepseek / openrouter / bai)
2683
+ * is configured. When none are, opens a modal asking the user to
2639
2684
  * configure a key + returns false. Brave is search, not a model
2640
2685
  * provider, so it's intentionally excluded.
2641
2686
  *
@@ -2853,9 +2898,15 @@
2853
2898
  /** Pure cache read · returns true when the local app.keys map
2854
2899
  * reports any model provider as configured. Used by the gate
2855
2900
  * and (via wrappers) anywhere downstream UI needs the answer
2856
- * cheaply. */
2901
+ * cheaply.
2902
+ *
2903
+ * Mirror the server-side LLM_PROVIDERS set (`routes/keys.ts:47`).
2904
+ * Without B.AI in this list, a user who configured ONLY a B.AI
2905
+ * key gets bounced to "configure an API key first" on every
2906
+ * pre-flight gate (topic-recs trigger, convene a room, etc.)
2907
+ * even though every model with a baiId is fully reachable. */
2857
2908
  hasAnyModelKey() {
2858
- const MODEL_PROVIDERS = ["anthropic", "openai", "google", "xai", "deepseek", "openrouter"];
2909
+ const MODEL_PROVIDERS = ["anthropic", "openai", "google", "xai", "deepseek", "openrouter", "bai"];
2859
2910
  const keys = this.keys || {};
2860
2911
  return MODEL_PROVIDERS.some((p) => keys[p] && keys[p].configured);
2861
2912
  },
@@ -9261,10 +9312,14 @@
9261
9312
  // quote dots never collide with the title text · the deco
9262
9313
  // stays visible only at the periphery + along the top band.
9263
9314
  // Same hygiene pass as the agent composer's motif.
9315
+ // All motif fills below pull from theme tokens (`--accent-line`
9316
+ // is the brand-tinted hairline = very subtle in both themes)
9317
+ // so the backdrop adopts the page's palette instead of staying
9318
+ // a fixed wood-brown patch that looks "dirty" on a light bg.
9264
9319
  const chairs = [
9265
- { x: 108, y: 138, fill: "#7A5230" },
9266
- { x: 372, y: 46, fill: "#7A5230" },
9267
- { x: 678, y: 148, fill: "#7A5230" },
9320
+ { x: 108, y: 138 },
9321
+ { x: 372, y: 46 },
9322
+ { x: 678, y: 148 },
9268
9323
  ];
9269
9324
  const sparks = [
9270
9325
  [98, 78], [640, 62], [588, 156],
@@ -9274,20 +9329,16 @@
9274
9329
  ];
9275
9330
  motif = `
9276
9331
  <g shape-rendering="crispEdges">
9277
- <!-- Mini chair silhouettes (3 wood + 1 cyan moderator) -->
9332
+ <!-- Mini chair silhouettes fill pulled from theme token -->
9278
9333
  ${chairs.map((c, i) => `
9279
- <g class="deco-bob" ${_delay(i + 3, 3.2)} fill="${c.fill}">
9334
+ <g class="deco-bob" ${_delay(i + 3, 3.2)} fill="var(--accent-line)">
9280
9335
  <rect x="${c.x}" y="${c.y + 10}" width="6" height="2"/>
9281
9336
  <rect x="${c.x}" y="${c.y}" width="2" height="10"/>
9282
9337
  <rect x="${c.x + 6}" y="${c.y}" width="2" height="10"/>
9283
9338
  </g>
9284
9339
  `).join("")}
9285
- <!-- Tiny microphones · static (small, would feel busy).
9286
- Removed the (296, 206) centre-lower mic and the
9287
- (244, 216) cyan moderator chair — both sat in the
9288
- title's footprint and read as typos behind the
9289
- prompt. -->
9290
- <g fill="#8E8B83">
9340
+ <!-- Tiny microphones · static (small, would feel busy). -->
9341
+ <g fill="var(--accent-line)">
9291
9342
  <rect x="226" y="46" width="3" height="2"/>
9292
9343
  <rect x="227" y="42" width="1" height="4"/>
9293
9344
  <rect x="514" y="100" width="3" height="2"/>
@@ -9328,11 +9379,13 @@
9328
9379
  ];
9329
9380
  motif = `
9330
9381
  <g shape-rendering="crispEdges">
9331
- <!-- Mini character heads · 4×4 face + 1px hat band -->
9382
+ <!-- Mini character heads · fills pulled from theme tokens
9383
+ so the heads stop reading as out-of-place skin-tan +
9384
+ dark-brown patches against a white bg. -->
9332
9385
  ${heads.map(([x, y], i) => `
9333
9386
  <g class="deco-bob" ${_delay(i + 21, 3.0)}>
9334
- <rect x="${x}" y="${y}" width="4" height="4" fill="#D8A878"/>
9335
- <rect x="${x}" y="${y}" width="4" height="1" fill="#5C3A1F"/>
9387
+ <rect x="${x}" y="${y}" width="4" height="4" fill="var(--accent-line)"/>
9388
+ <rect x="${x}" y="${y}" width="4" height="1" fill="var(--lime-dim)"/>
9336
9389
  </g>
9337
9390
  `).join("")}
9338
9391
  <!-- Tiny speech bubbles · 10×6 pill with 1-pixel tail · blink -->
@@ -16184,6 +16237,11 @@
16184
16237
  renderRtSubtitle() {
16185
16238
  const slot = document.querySelector("[data-rt-subtitle]");
16186
16239
  if (!slot) return;
16240
+ // Minimize-affordance template · prepended to every populated
16241
+ // innerHTML branch below so the corner button is present on the
16242
+ // thinking / streaming / replay variants without being a sibling
16243
+ // of the slot (which would survive the slot.hidden = true reset).
16244
+ const minBtn = `<button type="button" class="rt-sub-min" data-rt-sub-min title="Minimize" aria-label="Minimize subtitle"></button>`;
16187
16245
  const msgs = this.currentMessages || [];
16188
16246
  let speakerId = null;
16189
16247
  let body = "";
@@ -16264,7 +16322,7 @@
16264
16322
  })[stage];
16265
16323
  if (titleKey && deckKey) {
16266
16324
  slot.hidden = false;
16267
- slot.innerHTML =
16325
+ slot.innerHTML = minBtn +
16268
16326
  `<span class="rt-sub-kicker">${this.escape(this.currentChair.name || "")} · ${this.escape(this._t(titleKey))}</span>` +
16269
16327
  `<p class="rt-sub-text rt-sub-thinking">` +
16270
16328
  `<span class="rt-sub-dots" aria-hidden="true"><i></i><i></i><i></i></span>` +
@@ -16304,7 +16362,7 @@
16304
16362
  // to the normal caption render below.
16305
16363
  if (isStreaming) {
16306
16364
  slot.hidden = false;
16307
- slot.innerHTML =
16365
+ slot.innerHTML = minBtn +
16308
16366
  `<span class="rt-sub-kicker">${this.escape(speaker.name || "")}</span>` +
16309
16367
  `<p class="rt-sub-text rt-sub-thinking">` +
16310
16368
  `<span class="rt-sub-dots" aria-hidden="true"><i></i><i></i><i></i></span>` +
@@ -16395,7 +16453,7 @@
16395
16453
  visible = parts.length ? parts[parts.length - 1] : text;
16396
16454
  }
16397
16455
  slot.hidden = false;
16398
- slot.innerHTML =
16456
+ slot.innerHTML = minBtn +
16399
16457
  `<span class="rt-sub-kicker">${this.escape(speaker.name || "")}</span>` +
16400
16458
  `<p class="rt-sub-text">${this.escape(visible.trim())}</p>`;
16401
16459
  },
package/public/home.html CHANGED
@@ -370,114 +370,6 @@
370
370
  }
371
371
  @media (max-width: 700px) { .section { padding: 24px 18px; } }
372
372
 
373
- /* ─── Mac download card · sits at the top of `#install` as the
374
- primary install path for macOS users. Spans both grid columns
375
- so the lime download button reads as the section's hero CTA.
376
- Brutalist register · hairline border + bracketed corner marks
377
- (lime ones, same vocabulary as `.install` / `.hero-right` /
378
- `.final-cta`), mono kicker + meta, generous click target. */
379
- .install-mac {
380
- grid-column: 1 / -1;
381
- display: flex;
382
- align-items: center;
383
- gap: 24px;
384
- padding: 22px 26px;
385
- background: var(--bg);
386
- border: 1px solid var(--line-bright);
387
- position: relative;
388
- flex-wrap: wrap;
389
- }
390
- .install-mac::before,
391
- .install-mac::after {
392
- content: "";
393
- position: absolute;
394
- width: 10px; height: 10px;
395
- border: 1px solid var(--lime);
396
- }
397
- .install-mac::before {
398
- top: -1px; left: -1px;
399
- border-right: none; border-bottom: none;
400
- }
401
- .install-mac::after {
402
- bottom: -1px; right: -1px;
403
- border-left: none; border-top: none;
404
- }
405
- .install-mac-meta {
406
- flex: 1 1 320px;
407
- min-width: 0;
408
- }
409
- .install-mac-kicker {
410
- font-family: var(--mono);
411
- font-size: 10px;
412
- letter-spacing: 0.22em;
413
- color: var(--lime);
414
- text-transform: uppercase;
415
- font-weight: 700;
416
- margin-bottom: 8px;
417
- }
418
- .install-mac-kicker::before { content: "▸ "; }
419
- .install-mac-title {
420
- font-size: 22px;
421
- font-weight: 700;
422
- color: var(--text);
423
- letter-spacing: -0.005em;
424
- margin-bottom: 6px;
425
- line-height: 1.2;
426
- }
427
- .install-mac-sub {
428
- font-family: var(--mono);
429
- font-size: 11px;
430
- color: var(--text-soft);
431
- letter-spacing: 0.06em;
432
- display: flex;
433
- flex-wrap: wrap;
434
- gap: 6px 8px;
435
- align-items: center;
436
- }
437
- .install-mac-sub .im-sep { color: var(--text-faint); }
438
- .install-mac-sub .im-ok {
439
- color: var(--lime);
440
- /* Bracketed `[ ✓ ]` mark reused from `.install-bullet` vocabulary
441
- — same visual cue for "verified / passes". */
442
- }
443
- .install-mac-btn {
444
- display: inline-flex;
445
- align-items: center;
446
- gap: 10px;
447
- padding: 14px 22px;
448
- background: var(--lime);
449
- color: var(--bg);
450
- border: 1px solid var(--lime);
451
- font-family: var(--mono);
452
- font-size: 13px;
453
- font-weight: 700;
454
- letter-spacing: 0.14em;
455
- text-transform: uppercase;
456
- text-decoration: none;
457
- transition: background 0.12s, color 0.12s, border-color 0.12s, transform 0.12s;
458
- white-space: nowrap;
459
- flex-shrink: 0;
460
- }
461
- .install-mac-btn:hover {
462
- background: var(--bg);
463
- color: var(--lime);
464
- }
465
- .install-mac-btn:active { transform: translateY(1px); }
466
- .install-mac-btn .im-btn-mark {
467
- font-size: 16px;
468
- line-height: 1;
469
- }
470
- /* Mobile: stack meta above button, full-width button so it remains
471
- a clear primary CTA on narrow screens. */
472
- @media (max-width: 700px) {
473
- .install-mac { padding: 18px 18px; gap: 16px; }
474
- .install-mac-btn {
475
- width: 100%;
476
- justify-content: center;
477
- padding: 14px 18px;
478
- }
479
- }
480
-
481
373
  /* ─── Install module · terminal-flavored ─── */
482
374
  .install {
483
375
  background: var(--panel);
@@ -1910,7 +1802,10 @@
1910
1802
 
1911
1803
  <div class="hero-actions">
1912
1804
  <a href="#install" class="big-btn primary">[ ◆ Convene a Room ]</a>
1913
- <a href="#how" class="big-btn secondary">→ See How It Works</a>
1805
+ <a href="https://github.com/kaysaith1900/privateboard/releases/download/v0.1.18/PrivateBoard-0.1.18-arm64.dmg"
1806
+ class="big-btn secondary"
1807
+ download
1808
+ aria-label="Download PrivateBoard for macOS (Apple Silicon)"><svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="M12 3v12"/><polyline points="6 11 12 17 18 11"/><path d="M5 21h14"/></svg> Download for Mac</a>
1914
1809
  </div>
1915
1810
  </div>
1916
1811
 
@@ -1976,33 +1871,6 @@
1976
1871
 
1977
1872
  <!-- ─────────── INSTALL · LOCAL-FIRST ─────────── -->
1978
1873
  <section class="install bracketed" id="install">
1979
- <!-- Mac download card · primary install path for macOS users.
1980
- Sits above the terminal install card so it's the first thing
1981
- the eye lands on inside the install section. CLI install
1982
- (npx / npm) lives in the existing 2-col grid below for users
1983
- who'd rather run from the terminal. -->
1984
- <div class="install-mac">
1985
- <div class="install-mac-meta">
1986
- <div class="install-mac-kicker">macOS · desktop app</div>
1987
- <div class="install-mac-title">PrivateBoard.app</div>
1988
- <div class="install-mac-sub">
1989
- <span>v0.1.18</span>
1990
- <span class="im-sep">·</span>
1991
- <span>Apple Silicon</span>
1992
- <span class="im-sep">·</span>
1993
- <span>123 MB</span>
1994
- <span class="im-sep">·</span>
1995
- <span class="im-ok">[ ✓ ] signed &amp; notarized</span>
1996
- </div>
1997
- </div>
1998
- <a href="https://github.com/kaysaith1900/privateboard/releases/download/v0.1.18/PrivateBoard-0.1.18-arm64.dmg"
1999
- class="install-mac-btn"
2000
- download>
2001
- <span class="im-btn-mark">↓</span>
2002
- <span class="im-btn-text">Download .dmg</span>
2003
- </a>
2004
- </div>
2005
-
2006
1874
  <div class="install-head">
2007
1875
  <div class="install-tag">▸ install · runs locally</div>
2008
1876
  <h2 class="install-title">Bring PrivateBoard to your terminal.</h2>
package/public/i18n.js CHANGED
@@ -383,7 +383,6 @@
383
383
  ao_tenure_meta: "tenure ·",
384
384
  ao_first_room_meta: "first room ·",
385
385
  ao_free: "free",
386
- ao_convene_cta: "[ ◆ Convene with them ]",
387
386
  ao_signin_cta: "[ → Sign in to convene ]",
388
387
  ao_room_notes_empty: "no live room. open a room to see this director's in-room notes.",
389
388
  ao_room_notes_waiting: "no turns yet — once they speak, claims and stance shifts land here.",
@@ -551,8 +550,10 @@
551
550
  onb_v2_key_title: "Pick one brain — or pick many.",
552
551
  onb_v2_key_body: "Your key stays on this machine. We never upload it.",
553
552
  onb_v2_key_recommend_badge: "// recommended",
554
- onb_v2_key_recommend_name: "OpenRouter · one key, every model",
555
- onb_v2_key_recommend_body: "Each director can run on a different model — Claude as the skeptic, GPT as the pattern hunter, Gemini as the long-horizon strategist. The chair routes each turn to the right brain.",
553
+ onb_v2_key_recommend_openrouter_name: "OpenRouter · one key, every model",
554
+ onb_v2_key_recommend_openrouter_body: "Each director can run on a different model — Claude as the skeptic, GPT as the pattern hunter, Gemini as the long-horizon strategist. The chair routes each turn to the right brain.",
555
+ onb_v2_key_recommend_bai_name: "B.AI · one key, every model",
556
+ onb_v2_key_recommend_bai_body: "A second universal aggregator with its own pricing and access channels. Pick whichever has the credits or regional pricing that suits you, or use it as a fallback when an OpenRouter route is throttled.",
556
557
  onb_v2_key_or: "or — a direct provider",
557
558
  onb_v2_key_or_body: "Same model for every director. Personas stay distinct, but they all share one brain underneath.",
558
559
  onb_v2_voice_kicker: "03 — Give them a voice · optional",
@@ -960,7 +961,7 @@ When the room ___, raise an objection.`,
960
961
  us_modal_kicker_right: "// local",
961
962
  us_close: "Close",
962
963
  us_nav_api_key: "API Key",
963
- us_nav_other_settings: "Other settings",
964
+ us_nav_other_settings: "General",
964
965
  us_locale_label: "Interface language",
965
966
  us_locale_deck:
966
967
  "Sidebar labels, dialogs, and app chrome follow this locale. Room messages and briefs still mirror the language you write in.",
@@ -1371,7 +1372,6 @@ When the room ___, raise an objection.`,
1371
1372
  ao_tenure_meta: "任期 ·",
1372
1373
  ao_first_room_meta: "首场 ·",
1373
1374
  ao_free: "免费",
1374
- ao_convene_cta: "[ ◆ 与其开会 ]",
1375
1375
  ao_signin_cta: "[ → 登录后召开 ]",
1376
1376
  ao_room_notes_empty: "当前无进行中的会议 · 打开会议室后可查看该董事的场内笔记。",
1377
1377
  ao_room_notes_waiting: "尚未发言 · 发言后关键论断与立场变化会出现在此。",
@@ -1526,8 +1526,10 @@ When the room ___, raise an objection.`,
1526
1526
  onb_v2_key_title: "选一个脑袋,或者一群脑袋。",
1527
1527
  onb_v2_key_body: "Key 只留在这台机器上,我们不会上传。",
1528
1528
  onb_v2_key_recommend_badge: "// 推荐",
1529
- onb_v2_key_recommend_name: "OpenRouter · 一把钥匙,所有模型",
1530
- onb_v2_key_recommend_body: "每位 director 可以跑在不同的模型上——Claude 当怀疑论者,GPT 当模式猎手,Gemini 当长期主义者。主席每一轮都会把发言派给最合适的脑袋。",
1529
+ onb_v2_key_recommend_openrouter_name: "OpenRouter · 一把钥匙,所有模型",
1530
+ onb_v2_key_recommend_openrouter_body: "每位 director 可以跑在不同的模型上——Claude 当怀疑论者,GPT 当模式猎手,Gemini 当长期主义者。主席每一轮都会把发言派给最合适的脑袋。",
1531
+ onb_v2_key_recommend_bai_name: "B.AI · 一把钥匙,所有模型",
1532
+ onb_v2_key_recommend_bai_body: "另一家通用聚合器——和 OpenRouter 一样一把钥匙跑全部模型,自带定价与渠道。挑一个对你区域更顺手的,或在 OpenRouter 限流时作为备用线路。",
1531
1533
  onb_v2_key_or: "或者,用一家直连厂商",
1532
1534
  onb_v2_key_or_body: "所有 director 共用一个模型。人格仍然各异,但底下是同一颗脑袋。",
1533
1535
  onb_v2_voice_kicker: "03 — 让他们开口说话 · 可跳过",
@@ -1929,7 +1931,7 @@ When the room ___, raise an objection.`,
1929
1931
  us_modal_kicker_right: "// 本地",
1930
1932
  us_close: "关闭",
1931
1933
  us_nav_api_key: "API 密钥",
1932
- us_nav_other_settings: "其他设置",
1934
+ us_nav_other_settings: "通用",
1933
1935
  us_locale_label: "界面语言",
1934
1936
  us_locale_deck: "侧边栏、对话框和应用外壳使用该语言;房间消息与简报仍会以你书写的内容为准。",
1935
1937
  us_replay_onb_label: "回放 Onboarding",
@@ -2326,7 +2328,6 @@ When the room ___, raise an objection.`,
2326
2328
  ao_tenure_meta: "在任 ·",
2327
2329
  ao_first_room_meta: "最初の会議室 ·",
2328
2330
  ao_free: "空き",
2329
- ao_convene_cta: "[ ◆ 一緒に開催 ]",
2330
2331
  ao_signin_cta: "[ → サインインして開催 ]",
2331
2332
  ao_room_notes_empty: "進行中の会議室なし。会議室を開いてこのディレクターのノートを表示。",
2332
2333
  ao_room_notes_waiting: "まだターンなし — 発言があれば主張と立場の変化がここに表示されます。",
@@ -2477,8 +2478,10 @@ When the room ___, raise an objection.`,
2477
2478
  onb_v2_key_title: "一つの脳を選ぶ——あるいは、たくさん選ぶ。",
2478
2479
  onb_v2_key_body: "key はこの端末にのみ保存され、サーバへ送られることはありません。",
2479
2480
  onb_v2_key_recommend_badge: "// おすすめ",
2480
- onb_v2_key_recommend_name: "OpenRouter · 一つの鍵で、すべてのモデル",
2481
- onb_v2_key_recommend_body: "各 director がそれぞれ違うモデルで動けます——Claude は懐疑家、GPT はパターン捜索、Gemini は長期戦略家。議長が毎ターン、最適な脳に発言を振り分けます。",
2481
+ onb_v2_key_recommend_openrouter_name: "OpenRouter · 一つの鍵で、すべてのモデル",
2482
+ onb_v2_key_recommend_openrouter_body: "各 director がそれぞれ違うモデルで動けます——Claude は懐疑家、GPT はパターン捜索、Gemini は長期戦略家。議長が毎ターン、最適な脳に発言を振り分けます。",
2483
+ onb_v2_key_recommend_bai_name: "B.AI · 一つの鍵で、すべてのモデル",
2484
+ onb_v2_key_recommend_bai_body: "もう一つの統合アグリゲーター。OpenRouter と同じ「一鍵で全モデル」、独自の価格・経路。地域や残高に合うほうを選ぶか、OpenRouter のスロットリング時のバックアップとして併用できます。",
2482
2485
  onb_v2_key_or: "あるいは、直接プロバイダ",
2483
2486
  onb_v2_key_or_body: "全 director が同じモデルを共有します。ペルソナは別々ですが、土台は一つの脳です。",
2484
2487
  onb_v2_voice_kicker: "03 — 声を与える · オプション",
@@ -2710,7 +2713,7 @@ When the room ___, raise an objection.`,
2710
2713
  us_modal_kicker_right: "// ローカル",
2711
2714
  us_close: "閉じる",
2712
2715
  us_nav_api_key: "API キー",
2713
- us_nav_other_settings: "その他の設定",
2716
+ us_nav_other_settings: "一般",
2714
2717
  us_locale_label: "インターフェース言語",
2715
2718
  us_locale_deck: "サイドバー、ダイアログ、アプリ全体のクロームがこの言語に従います。会議室のメッセージとブリーフはあなたが書いた言語のままです。",
2716
2719
  us_replay_onb_label: "オンボーディングを再生",
@@ -3097,7 +3100,6 @@ When the room ___, raise an objection.`,
3097
3100
  ao_tenure_meta: "antigüedad ·",
3098
3101
  ao_first_room_meta: "primera sala ·",
3099
3102
  ao_free: "libre",
3100
- ao_convene_cta: "[ ◆ Convocar con ellos ]",
3101
3103
  ao_signin_cta: "[ → Inicia sesión para convocar ]",
3102
3104
  ao_room_notes_empty: "ninguna sala en vivo. abre una sala para ver las notas de este director.",
3103
3105
  ao_room_notes_waiting: "aún no hay turnos — cuando hablen, las afirmaciones y los cambios de postura aparecerán aquí.",
@@ -3248,8 +3250,10 @@ When the room ___, raise an objection.`,
3248
3250
  onb_v2_key_title: "Elige un cerebro — o elige varios.",
3249
3251
  onb_v2_key_body: "Tu key se queda en esta máquina. Nunca la subimos.",
3250
3252
  onb_v2_key_recommend_badge: "// recomendado",
3251
- onb_v2_key_recommend_name: "OpenRouter · una key, todos los modelos",
3252
- onb_v2_key_recommend_body: "Cada director puede correr en un modelo distinto — Claude como escéptico, GPT como cazador de patrones, Gemini como estratega de largo plazo. El chair enruta cada turno al cerebro adecuado.",
3253
+ onb_v2_key_recommend_openrouter_name: "OpenRouter · una key, todos los modelos",
3254
+ onb_v2_key_recommend_openrouter_body: "Cada director puede correr en un modelo distinto — Claude como escéptico, GPT como cazador de patrones, Gemini como estratega de largo plazo. El chair enruta cada turno al cerebro adecuado.",
3255
+ onb_v2_key_recommend_bai_name: "B.AI · una key, todos los modelos",
3256
+ onb_v2_key_recommend_bai_body: "Un segundo agregador universal con sus propios precios y rutas de acceso. Elige el que tenga mejores créditos o precios regionales para ti, o úsalo como respaldo cuando una ruta de OpenRouter esté limitada.",
3253
3257
  onb_v2_key_or: "o — un proveedor directo",
3254
3258
  onb_v2_key_or_body: "Mismo modelo para todos los directores. Las personas siguen siendo distintas, pero comparten un solo cerebro.",
3255
3259
  onb_v2_voice_kicker: "03 — Dales una voz · opcional",
@@ -3482,7 +3486,7 @@ When the room ___, raise an objection.`,
3482
3486
  us_modal_kicker_right: "// local",
3483
3487
  us_close: "Cerrar",
3484
3488
  us_nav_api_key: "Clave API",
3485
- us_nav_other_settings: "Otros ajustes",
3489
+ us_nav_other_settings: "General",
3486
3490
  us_locale_label: "Idioma de interfaz",
3487
3491
  us_locale_deck: "Las etiquetas de la barra lateral, los diálogos y la interfaz siguen este idioma. Los mensajes de sala y los informes mantienen el idioma en que escribes.",
3488
3492
  us_replay_onb_label: "Repetir onboarding",
Binary file