dsh-blackjack 0.1.3 → 0.1.4

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/README.md CHANGED
@@ -94,6 +94,21 @@ dsh --profile web
94
94
  出一个残缺的状态。这不是本插件独有的坑,其它社区 dsh 插件的安装文档也踩过同一
95
95
  处并确认了同样的结论。
96
96
 
97
+ ### 先进入一个会话,再用 `/blackjack`
98
+
99
+ 在 dsh 的**启动页**(新开 dsh 时那个 “Into the Unknown” 界面)执行 `/blackjack`,
100
+ 命令会真的执行,但**画面上什么都不会出现**——牌桌要等你进入一个会话后才显示。
101
+
102
+ 这不是本插件的问题:宿主对**所有插件注册的命令**都这样(用一个只返回一行文字、
103
+ 不碰任何服务的最小插件验证过,表现完全一致),而宿主自己的内置命令(如 `/goal`)
104
+ 则会正常渲染并切进会话视图。
105
+
106
+ 所以**第一次使用请先让对话真正开始**——随便发一句话,或者从侧边栏打开一个已有
107
+ 对话——再执行 `/blackjack agree`。注意点“新建会话”是没用的:那会把你送回启动页,
108
+ 正是坏掉的那个状态。
109
+
110
+ 只要对话已经开始,后面全程正常,牌桌、按钮、结算都没有任何问题。
111
+
97
112
  ### 版本要求:本插件锁定 dsh `0.1.0-rc.6`
98
113
 
99
114
  插件把 `@deepseek-ai/dsh-*` 的 peer 全部**精确锁**在 `0.1.0-rc.6`,不是写成
@@ -29,6 +29,15 @@ export interface BlackjackTableProps {
29
29
  /** Brief inline notice for a failed action/deal call (container-owned; cleared on the next successful one). */
30
30
  error?: string;
31
31
  }
32
+ /**
33
+ * 把纯文本里的裸链接变成可点的。命令回复是纯文本(终端 profile 也要能读),
34
+ * 但在图形界面里让人手动复制粘贴一条 60 字符的 URL 是不合理的——运营者的
35
+ * 原话是「应该直接打开一个浏览器」。插件进程里 spawn `open` 依赖运行环境
36
+ * 且很唐突,可点的链接是这个场景下最合适的实现。
37
+ */
38
+ export declare function Linkified({ text }: {
39
+ text: string;
40
+ }): JSX.Element;
32
41
  /**
33
42
  * 动作要额外掏多少 CHIP。镜像服务端 `game.ts` 的 INCREMENT:双倍/分牌各再出
34
43
  * 一份注额,保险出一半。抽成导出的纯函数,好让"余额不足时必须置灰"能被断言。
@@ -1,4 +1,4 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useState } from 'react';
3
3
  import { stakeLine, outcomeLine } from '../copy.js';
4
4
  const SUITS = { S: '♠', H: '♥', D: '♦', C: '♣' };
@@ -43,6 +43,18 @@ const CSS = `
43
43
  padding: 4px 8px; margin-bottom: 8px;
44
44
  }
45
45
  `;
46
+ /**
47
+ * 把纯文本里的裸链接变成可点的。命令回复是纯文本(终端 profile 也要能读),
48
+ * 但在图形界面里让人手动复制粘贴一条 60 字符的 URL 是不合理的——运营者的
49
+ * 原话是「应该直接打开一个浏览器」。插件进程里 spawn `open` 依赖运行环境
50
+ * 且很唐突,可点的链接是这个场景下最合适的实现。
51
+ */
52
+ export function Linkified({ text }) {
53
+ const parts = text.split(/(https?:\/\/[^\s,。))]+)/g);
54
+ return (_jsx(_Fragment, { children: parts.map((part, i) => /^https?:\/\//.test(part)
55
+ ? _jsx("a", { href: part, target: "_blank", rel: "noreferrer noopener", children: part }, i)
56
+ : part) }));
57
+ }
46
58
  function CardFace({ card }) {
47
59
  if ('hidden' in card) {
48
60
  return _jsx("div", { className: "bj-card bj-card-back", "aria-label": "\u6697\u724C" });
@@ -61,8 +73,17 @@ function DealerRow({ dealer }) {
61
73
  function ConsentPrompt() {
62
74
  return (_jsx("div", { className: "bj-table", children: _jsxs("p", { children: ["\u8BF7\u5148\u5728\u547D\u4EE4\u884C\u8F93\u5165 ", _jsx("code", { children: "/blackjack agree" }), "\uFF0C\u9605\u8BFB\u5E76\u540C\u610F\u89C4\u5219\u540E\u624D\u80FD\u5F00\u59CB\u3002"] }) }));
63
75
  }
76
+ /**
77
+ * 余额条。除了数字,还必须回答"赢来的 CHIP 有什么用"——只用图形界面的人此前
78
+ * 完全看不到兑换这回事(门槛只在文字命令的 renderMe 里出现过),于是不知道
79
+ * CHIP 能干什么,而那正是这个产品的全部意义。没到门槛时显示还差多少,本身
80
+ * 就是回访的理由。
81
+ */
64
82
  function MeHeader({ me }) {
65
- return (_jsxs("div", { className: "bj-meta", children: ["\u4F59\u989D\uFF1A", n(me.chips), " CHIP\u3000\u4ECA\u65E5\u5269\u4F59\u514D\u8D39\u624B\uFF1A", me.freeHandsRemaining, me.points > 0 ? ` 积分:${n(me.points)}` : '', me.poolEmpty ? '(本期奖池已发完,现在赢牌将发放积分)' : '', me.sponsorText ? _jsx("div", { children: me.sponsorText }) : null] }));
83
+ const short = me.exchangeThresholdChips - me.chips;
84
+ return (_jsxs("div", { className: "bj-meta", children: ["\u4F59\u989D\uFF1A", n(me.chips), " CHIP\u3000\u4ECA\u65E5\u5269\u4F59\u514D\u8D39\u624B\uFF1A", me.freeHandsRemaining, me.points > 0 ? ` 积分:${n(me.points)}` : '', me.exchangedChips > 0 ? ` 已兑换额度:${n(me.exchangedChips)} CHIP` : '', _jsx("div", { className: "bj-note", children: short <= 0
85
+ ? `已达兑换门槛(${n(me.exchangeThresholdChips)} CHIP)· 用 /blackjack exchange 把 CHIP 换成模型额度`
86
+ : `还差 ${n(short)} CHIP 到兑换门槛(${n(me.exchangeThresholdChips)})· 过线后可用 /blackjack exchange 换成模型额度` }), me.exchangedChips > 0 ? (_jsxs("div", { className: "bj-note", children: ["\u5DF2\u5151\u6362\u7684 ", n(me.exchangedChips), " CHIP \u4F1A\u5728\u4F60\u81EA\u5DF1\u7684\u6A21\u578B\u989D\u5EA6\u8017\u5C3D\u65F6\u81EA\u52A8\u63A5\u7BA1\u90A3\u4E00\u6B21\u8BF7\u6C42\uFF0C\u5E73\u65F6\u4E0D\u4F1A\u52A8\u7528\u3002"] })) : null, me.poolEmpty ? _jsx("div", { children: "\uFF08\u672C\u671F\u5956\u6C60\u5DF2\u53D1\u5B8C\uFF0C\u73B0\u5728\u8D62\u724C\u5C06\u53D1\u653E\u79EF\u5206\uFF09" }) : null, me.sponsorText ? _jsx("div", { children: me.sponsorText }) : null] }));
66
87
  }
67
88
  function RoundFooter({ round }) {
68
89
  return (_jsxs("div", { className: "bj-meta", children: [round.phase === 'settled' ? outcomeLine(round) : stakeLine(round), round.pointsGranted ? _jsxs("div", { children: ["\u672C\u671F\u5956\u6C60\u5DF2\u53D1\u5B8C\uFF0C\u6539\u4E3A\u53D1\u653E ", n(round.pointsGranted), " \u79EF\u5206\u3002"] }) : null] }));
@@ -128,7 +149,7 @@ function IdleTable({ me, onDeal }) {
128
149
  const base = me?.baseBetChips ?? 0;
129
150
  const presets = me ? [base, base * 2] : [];
130
151
  const affordable = me ? presets.some((a) => me.chips >= a) : false;
131
- return (_jsxs("div", { children: [_jsx("div", { className: "bj-meta", children: "\u5F53\u524D\u6CA1\u6709\u8FDB\u884C\u4E2D\u7684\u724C\u5C40\u3002" }), _jsxs("div", { className: "bj-group", children: [_jsxs("div", { className: "bj-group-title", children: ["\u514D\u8D39\u624B \u00B7 ", freeLeft > 0 ? `今日剩 ${freeLeft} 手` : '今日已用完,明天再来'] }), me ? (_jsxs("div", { className: "bj-note", children: ["\u4E0D\u7528\u51FA CHIP\uFF1B\u8D62\u4E86\u62FF ", n(base), " CHIP\uFF08\u5929\u751F 21 \u70B9 ", n(Math.floor(base * 1.5)), "\uFF09\uFF0C\u8F93\u4E86\u4E0D\u635F\u5931\u4EFB\u4F55\u4E1C\u897F\u3002"] })) : null, _jsx("div", { className: "bj-actions", children: _jsx("button", { type: "button", disabled: freeLeft <= 0, onClick: () => onDeal(), children: "\u5F00\u59CB\u514D\u8D39\u7684\u4E00\u5C40" }) })] }), me ? (_jsxs("div", { className: "bj-group", children: [_jsxs("div", { className: "bj-group-title", children: ["\u7528\u4F59\u989D\u52A0\u6CE8", affordable ? '' : ' · 余额不足'] }), _jsx("div", { className: "bj-note", children: "\u52A0\u6CE8\u7684 CHIP \u4ECE\u4F60\u7684\u4F59\u989D\u91CC\u51FA\uFF0C\u8D62\u4E86\u7FFB\u500D\uFF0C\u8F93\u4E86\u6536\u4E0D\u56DE\u3002" }), _jsx("div", { className: "bj-actions", children: presets.map((amount) => (_jsxs("button", { type: "button", disabled: me.chips < amount, onClick: () => onDeal(amount), children: [n(amount), " CHIP"] }, amount))) }), _jsx(CustomBet, { me: me, onDeal: onDeal })] })) : null] }));
152
+ return (_jsxs("div", { children: [_jsx("div", { className: "bj-meta", children: "\u5F53\u524D\u6CA1\u6709\u8FDB\u884C\u4E2D\u7684\u724C\u5C40\u3002" }), _jsxs("div", { className: "bj-group", children: [_jsxs("div", { className: "bj-group-title", children: ["\u514D\u8D39\u624B \u00B7 ", freeLeft > 0 ? `今日剩 ${freeLeft} 手` : '今日已用完,明天再来'] }), me ? (_jsxs("div", { className: "bj-note", children: ["\u4E0D\u7528\u51FA CHIP\u3002\u8D62\u4E86\u62FF ", n(base), " CHIP\uFF1B\u5934\u4E24\u5F20\u724C\u5C31\u51D1\u6210 21 \u70B9\uFF08A \u914D 10/J/Q/K\uFF09\u62FF", ' ', n(Math.floor(base * 1.5)), "\u3002\u8F93\u4E86\u6216\u5E73\u5C40\u90FD\u4E0D\u635F\u5931\u4EFB\u4F55\u4E1C\u897F\u3002"] })) : null, _jsx("div", { className: "bj-actions", children: _jsx("button", { type: "button", disabled: freeLeft <= 0, onClick: () => onDeal(), children: "\u5F00\u59CB\u514D\u8D39\u7684\u4E00\u5C40" }) })] }), me ? (_jsxs("div", { className: "bj-group", children: [_jsxs("div", { className: "bj-group-title", children: ["\u7528\u4F59\u989D\u52A0\u6CE8", affordable ? '' : ' · 余额不足'] }), _jsx("div", { className: "bj-note", children: "\u52A0\u6CE8\u7684 CHIP \u4ECE\u4F60\u7684\u4F59\u989D\u91CC\u51FA\uFF0C\u8D62\u4E86\u7FFB\u500D\uFF0C\u8F93\u4E86\u6536\u4E0D\u56DE\u3002" }), _jsx("div", { className: "bj-actions", children: presets.map((amount) => (_jsxs("button", { type: "button", disabled: me.chips < amount, onClick: () => onDeal(amount), children: [n(amount), " CHIP"] }, amount))) }), _jsx(CustomBet, { me: me, onDeal: onDeal })] })) : null] }));
132
153
  }
133
154
  /**
134
155
  * 结算后的出口。牌局结算不再让画面直接跳回空闲态——完整牌面与结果留在原地,
@@ -1,7 +1,7 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useCallback, useEffect, useState } from 'react';
3
- import { BlackjackTable } from './Table.js';
4
- import { reduce } from './state.js';
3
+ import { BlackjackTable, Linkified } from './Table.js';
4
+ import { reduce, rowPlan } from './state.js';
5
5
  /**
6
6
  * Client module entry: registers the graphical table into the KEYED
7
7
  * `conversation.chat.commandview` slot under `key: 'blackjack'`, so every
@@ -30,7 +30,11 @@ const ACTION_FAILED_NOTICE = '上一次操作没有成功,已为你刷新到
30
30
  * 丢掉、再拉一次 /api/state,而那个端点只报进行中的牌局——于是玩家一爆牌,
31
31
  * 画面就跳回"当前没有进行中的牌局",从头到尾看不见自己怎么输的。
32
32
  */
33
- function BlackjackTableContainer() {
33
+ function BlackjackTableContainer({ node }) {
34
+ // 这一行渲染什么由子命令决定。keyed 槽按命令名分发,注册 `blackjack` 会顶掉
35
+ // 宿主负责渲染文字的 GenericCommandCard —— 不自己把文字渲染回来,
36
+ // /blackjack exchange 的配对码和 /blackjack agree 的须知就会凭空消失。
37
+ const plan = rowPlan(node);
34
38
  const [state, setState] = useState(null);
35
39
  const [actionError, setActionError] = useState(null);
36
40
  /** 整份同步:挂载时,以及动作失败后状态存疑时。 */
@@ -46,8 +50,9 @@ function BlackjackTableContainer() {
46
50
  .catch(() => { });
47
51
  }, []);
48
52
  useEffect(() => {
49
- sync();
50
- }, [sync]);
53
+ if (plan.table)
54
+ sync();
55
+ }, [sync, plan.table]);
51
56
  const post = useCallback((path, body) => {
52
57
  getJson(path, {
53
58
  method: 'POST',
@@ -69,9 +74,14 @@ function BlackjackTableContainer() {
69
74
  const onDeal = useCallback((betChips) => post('/blackjack/api/deal', betChips === undefined ? {} : { betChips }), [post]);
70
75
  const onAction = useCallback((action) => post('/blackjack/api/action', { action }), [post]);
71
76
  const onDismiss = useCallback(() => setState((s) => (s ? reduce(s, { type: 'dismissed' }) : s)), []);
77
+ // 样式内联:文字型的行不渲染牌桌,Table 里那段 <style> 也就不会挂载,
78
+ // 靠 class 的话须知全文会被压成一行。
79
+ const notice = plan.text === undefined ? null : (_jsx("div", { style: { whiteSpace: 'pre-wrap', marginBottom: '8px', ...(plan.isError ? { color: '#c0392b' } : {}) }, children: _jsx(Linkified, { text: plan.text }) }));
80
+ if (!plan.table)
81
+ return notice;
72
82
  if (state === null)
73
- return null;
74
- return (_jsx(BlackjackTable, { state: state, onAction: onAction, onDeal: onDeal, onDismiss: onDismiss, error: actionError ?? undefined }));
83
+ return notice;
84
+ return (_jsxs("div", { children: [notice, _jsx(BlackjackTable, { state: state, onAction: onAction, onDeal: onDeal, onDismiss: onDismiss, error: actionError ?? undefined })] }));
75
85
  }
76
86
  export function apply(ctx) {
77
87
  ctx.slots.inject('conversation.chat.commandview', () => ctx.slots.register({
@@ -37,3 +37,31 @@ export type TableEvent =
37
37
  type: 'dismissed';
38
38
  };
39
39
  export declare function reduce(state: TableState, event: TableEvent): TableState;
40
+ /** 宿主传给命令行组件的那份命令生命周期节点(只用到我们关心的字段)。 */
41
+ export interface CommandNodeLike {
42
+ args: string | null;
43
+ outcome: {
44
+ kind: 'success' | 'error';
45
+ text?: string;
46
+ } | null;
47
+ }
48
+ /** 这一行该渲染什么。 */
49
+ export interface RowPlan {
50
+ /** 要显示的命令回复文字;缺省表示这一行不靠文字表达。 */
51
+ text?: string;
52
+ /** 该文字是否是失败提示。 */
53
+ isError: boolean;
54
+ /** 是否渲染图形牌桌。 */
55
+ table: boolean;
56
+ }
57
+ /**
58
+ * 决定 `/blackjack …` 的某一行怎么渲染。
59
+ *
60
+ * 存在的理由:`conversation.chat.commandview` 是**按命令名分发**的 keyed 槽,
61
+ * 注册 `blackjack` 会顶掉宿主负责渲染文字的 GenericCommandCard。0.1.3 的容器
62
+ * 完全无视这个节点、永远渲染牌桌,于是 `/blackjack exchange` 的配对码和
63
+ * `/blackjack agree` 的须知全文被凭空吞掉——运营者因此以为"命令没反应"。
64
+ *
65
+ * 不变式:**永远不吞文字**。未知子命令默认走文字,所以以后新增子命令不会再犯。
66
+ */
67
+ export declare function rowPlan(node: CommandNodeLike): RowPlan;
@@ -10,3 +10,25 @@ export function reduce(state, event) {
10
10
  return { ...state, round: undefined };
11
11
  }
12
12
  }
13
+ /** 牌桌是这些子命令的正确视图;其余一律以文字为准。 */
14
+ const TABLE_SUBCOMMANDS = new Set(['', 'deal']);
15
+ /**
16
+ * 决定 `/blackjack …` 的某一行怎么渲染。
17
+ *
18
+ * 存在的理由:`conversation.chat.commandview` 是**按命令名分发**的 keyed 槽,
19
+ * 注册 `blackjack` 会顶掉宿主负责渲染文字的 GenericCommandCard。0.1.3 的容器
20
+ * 完全无视这个节点、永远渲染牌桌,于是 `/blackjack exchange` 的配对码和
21
+ * `/blackjack agree` 的须知全文被凭空吞掉——运营者因此以为"命令没反应"。
22
+ *
23
+ * 不变式:**永远不吞文字**。未知子命令默认走文字,所以以后新增子命令不会再犯。
24
+ */
25
+ export function rowPlan(node) {
26
+ const sub = (node.args ?? '').trim().split(/\s+/)[0] ?? '';
27
+ const isTable = TABLE_SUBCOMMANDS.has(sub);
28
+ const outcome = node.outcome;
29
+ const isError = outcome?.kind === 'error';
30
+ // 牌桌型子命令平时不重复显示文字(牌桌已经把状态画出来了),但失败提示必须露出来。
31
+ if (isTable)
32
+ return { table: true, isError, ...(isError && outcome?.text ? { text: outcome.text } : {}) };
33
+ return { table: false, isError, ...(outcome?.text !== undefined ? { text: outcome.text } : {}) };
34
+ }
package/dist/client.js CHANGED
@@ -3,7 +3,7 @@ window.__ModuleLoader__.load({
3
3
  factory: (require) => {
4
4
  var module = { exports: {} };
5
5
  var exports = module.exports;
6
- "use strict";var h=Object.defineProperty;var R=Object.getOwnPropertyDescriptor;var T=Object.getOwnPropertyNames;var V=Object.prototype.hasOwnProperty;var H=(e,n)=>{for(var i in n)h(e,i,{get:n[i],enumerable:!0})},E=(e,n,i,a)=>{if(n&&typeof n=="object"||typeof n=="function")for(let o of T(n))!V.call(e,o)&&o!==i&&h(e,o,{get:()=>n[o],enumerable:!(a=R(n,o))||a.enumerable});return e};var I=e=>E(h({},"__esModule",{value:!0}),e);var Z={};H(Z,{apply:()=>Y,inject:()=>K});module.exports=I(Z);var c=require("react");var j=require("react");var b=e=>e.toLocaleString("en-US");function y(e){return e.mode==="free"?`\u8FD9\u662F\u514D\u8D39\u7684\u4E00\u5C40\uFF1A\u8D62\u4E86\u62FF ${b(e.betChips)} CHIP\uFF0C\u8F93\u4E86\u4E0D\u635F\u5931\u4EFB\u4F55\u4E1C\u897F\u3002`:`\u672C\u5C40\u5DF2\u52A0\u6CE8 ${b(e.betChips)} CHIP\uFF0C\u4ECE\u4F60\u7684\u4F59\u989D\u91CC\u51FA\u3002`}function C(e){let n=e.payoutChips??0,i=e.betChips;return e.hands.length>0&&e.hands.every(a=>a.outcome==="push")?e.mode==="free"?"\u5E73\u5C40\u3002\u8FD9\u624B\u4E0D\u635F\u5931\u4E5F\u4E0D\u83B7\u5F97\u3002":`\u5E73\u5C40\u3002\u52A0\u6CE8\u7684 ${b(i)} CHIP \u539F\u6837\u9000\u56DE\u3002`:n>0?e.mode==="free"?`\u8D62\u4E86\uFF0C\u62FF\u5230 ${b(n)} CHIP\u3002`:`\u8D62\u4E86\uFF0C\u62FF\u56DE ${b(n)} CHIP\uFF08\u52A0\u6CE8 ${b(i)}\uFF09\u3002`:e.mode==="free"?"\u8FD9\u624B\u8F93\u4E86\u3002\u514D\u8D39\u624B\u4E0D\u635F\u5931\u4EFB\u4F55\u4E1C\u897F\u3002":`\u8FD9\u624B\u8F93\u4E86\uFF0C\u52A0\u6CE8\u7684 ${b(i)} CHIP \u6536\u4E0D\u56DE\u3002`}var t=require("react/jsx-runtime"),P={S:"\u2660",H:"\u2665",D:"\u2666",C:"\u2663"},$=new Set(["H","D"]),l=e=>e.toLocaleString("en-US"),D={hit:"\u8981\u724C",stand:"\u505C\u724C",double:"\u53CC\u500D",split:"\u5206\u724C",insure:"\u4E70\u4FDD\u9669",decline:"\u4E0D\u4E70\u4FDD\u9669"},M=`
6
+ "use strict";var C=Object.defineProperty;var P=Object.getOwnPropertyDescriptor;var V=Object.getOwnPropertyNames;var $=Object.prototype.hasOwnProperty;var L=(e,n)=>{for(var i in n)C(e,i,{get:n[i],enumerable:!0})},B=(e,n,i,o)=>{if(n&&typeof n=="object"||typeof n=="function")for(let a of V(n))!$.call(e,a)&&a!==i&&C(e,a,{get:()=>n[a],enumerable:!(o=P(n,a))||o.enumerable});return e};var D=e=>B(C({},"__esModule",{value:!0}),e);var ae={};L(ae,{apply:()=>oe,inject:()=>te});module.exports=D(ae);var l=require("react");var j=require("react");var m=e=>e.toLocaleString("en-US");function y(e){return e.mode==="free"?`\u8FD9\u662F\u514D\u8D39\u7684\u4E00\u5C40\uFF1A\u8D62\u4E86\u62FF ${m(e.betChips)} CHIP\uFF0C\u8F93\u4E86\u4E0D\u635F\u5931\u4EFB\u4F55\u4E1C\u897F\u3002`:`\u672C\u5C40\u5DF2\u52A0\u6CE8 ${m(e.betChips)} CHIP\uFF0C\u4ECE\u4F60\u7684\u4F59\u989D\u91CC\u51FA\u3002`}function w(e){let n=e.payoutChips??0,i=e.betChips;return e.hands.length>0&&e.hands.every(o=>o.outcome==="push")?e.mode==="free"?"\u5E73\u5C40\u3002\u8FD9\u624B\u4E0D\u635F\u5931\u4E5F\u4E0D\u83B7\u5F97\u3002":`\u5E73\u5C40\u3002\u52A0\u6CE8\u7684 ${m(i)} CHIP \u539F\u6837\u9000\u56DE\u3002`:n>0?e.mode==="free"?`\u8D62\u4E86\uFF0C\u62FF\u5230 ${m(n)} CHIP\u3002`:`\u8D62\u4E86\uFF0C\u62FF\u56DE ${m(n)} CHIP\uFF08\u52A0\u6CE8 ${m(i)}\uFF09\u3002`:e.mode==="free"?"\u8FD9\u624B\u8F93\u4E86\u3002\u514D\u8D39\u624B\u4E0D\u635F\u5931\u4EFB\u4F55\u4E1C\u897F\u3002":`\u8FD9\u624B\u8F93\u4E86\uFF0C\u52A0\u6CE8\u7684 ${m(i)} CHIP \u6536\u4E0D\u56DE\u3002`}var t=require("react/jsx-runtime"),M={S:"\u2660",H:"\u2665",D:"\u2666",C:"\u2663"},J=new Set(["H","D"]),d=e=>e.toLocaleString("en-US"),A={hit:"\u8981\u724C",stand:"\u505C\u724C",double:"\u53CC\u500D",split:"\u5206\u724C",insure:"\u4E70\u4FDD\u9669",decline:"\u4E0D\u4E70\u4FDD\u9669"},X=`
7
7
  .bj-table { --bj-felt: #0f3d2e; --bj-line: rgba(255,255,255,.18); font-size: 14px; }
8
8
  .bj-hand { display: flex; gap: 6px; flex-wrap: wrap; align-items: center; margin: 4px 0; }
9
9
  .bj-card {
@@ -38,7 +38,7 @@ window.__ModuleLoader__.load({
38
38
  color: #c0392b; border: 1px solid rgba(192,57,43,.4); border-radius: 6px;
39
39
  padding: 4px 8px; margin-bottom: 8px;
40
40
  }
41
- `;function k({card:e}){if("hidden"in e)return(0,t.jsx)("div",{className:"bj-card bj-card-back","aria-label":"\u6697\u724C"});let n=P[e.suit]??e.suit,i=$.has(e.suit)?"bj-card-red":"bj-card-black";return(0,t.jsx)("div",{className:`bj-card ${i}`,children:`${e.rank}${n}`})}function B({hand:e,label:n}){let i=e.soft?"\u8F6F":"";return(0,t.jsxs)("div",{children:[(0,t.jsxs)("div",{className:"bj-meta",children:[n,"\uFF08",i,e.total,"\uFF09",e.doubled?" [\u53CC\u500D]":""]}),(0,t.jsx)("div",{className:"bj-hand",children:e.cards.map((a,o)=>(0,t.jsx)(k,{card:a},o))})]})}function J({dealer:e}){return(0,t.jsxs)("div",{children:[(0,t.jsxs)("div",{className:"bj-meta",children:["\u5E84\u5BB6",e.total!==void 0?`\uFF08${e.total}\uFF09`:""]}),(0,t.jsx)("div",{className:"bj-hand",children:e.cards.map((n,i)=>(0,t.jsx)(k,{card:n},i))})]})}function L(){return(0,t.jsx)("div",{className:"bj-table",children:(0,t.jsxs)("p",{children:["\u8BF7\u5148\u5728\u547D\u4EE4\u884C\u8F93\u5165 ",(0,t.jsx)("code",{children:"/blackjack agree"}),"\uFF0C\u9605\u8BFB\u5E76\u540C\u610F\u89C4\u5219\u540E\u624D\u80FD\u5F00\u59CB\u3002"]})})}function A({me:e}){return(0,t.jsxs)("div",{className:"bj-meta",children:["\u4F59\u989D\uFF1A",l(e.chips)," CHIP\u3000\u4ECA\u65E5\u5269\u4F59\u514D\u8D39\u624B\uFF1A",e.freeHandsRemaining,e.points>0?`\u3000\u79EF\u5206\uFF1A${l(e.points)}`:"",e.poolEmpty?"\uFF08\u672C\u671F\u5956\u6C60\u5DF2\u53D1\u5B8C\uFF0C\u73B0\u5728\u8D62\u724C\u5C06\u53D1\u653E\u79EF\u5206\uFF09":"",e.sponsorText?(0,t.jsx)("div",{children:e.sponsorText}):null]})}function X({round:e}){return(0,t.jsxs)("div",{className:"bj-meta",children:[e.phase==="settled"?C(e):y(e),e.pointsGranted?(0,t.jsxs)("div",{children:["\u672C\u671F\u5956\u6C60\u5DF2\u53D1\u5B8C\uFF0C\u6539\u4E3A\u53D1\u653E ",l(e.pointsGranted)," \u79EF\u5206\u3002"]}):null]})}function O(e,n){return e==="double"||e==="split"?n:e==="insure"?Math.ceil(n/2):0}function z({round:e,me:n,onAction:i}){if(e.actions.length===0)return null;let a=n?.chips??0;return(0,t.jsx)("div",{className:"bj-actions",children:e.actions.map(o=>{let r=O(o,e.betChips),s=r>a;return(0,t.jsxs)("button",{type:"button",disabled:s,title:s?`\u8FD8\u8981\u518D\u51FA ${l(r)} CHIP\uFF0C\u4F59\u989D\u4E0D\u8DB3`:void 0,onClick:()=>i(o),children:[D[o]??o,r>0?`\uFF08${l(r)}\uFF09`:""]},o)})})}function U(e,n){return!Number.isInteger(e)||e<1?"\u6CE8\u989D\u8981\u586B\u6B63\u6574\u6570\u3002":e>n.maxChips?`\u5355\u5C40\u6700\u591A ${l(n.maxChips)} CHIP\u3002`:e>n.balanceChips?"\u4F59\u989D\u4E0D\u8DB3\u4EE5\u4E0B\u8FD9\u4E2A\u6CE8\u3002":null}function _({me:e,onDeal:n,freeLabel:i}){let a=e?.freeHandsRemaining??0,o=e?[e.baseBetChips,e.baseBetChips*2]:[];return(0,t.jsxs)("div",{className:"bj-actions",children:[(0,t.jsxs)("button",{type:"button",disabled:a<=0,onClick:()=>n(),children:[i,e?`\xB7\u5269 ${a} \u624B`:""]}),o.map(r=>(0,t.jsxs)("button",{type:"button",disabled:(e?.chips??0)<r,onClick:()=>n(r),children:[l(r)," CHIP"]},r))]})}function F({me:e,onDeal:n}){let i=e.maxRaiseBetChips,[a,o]=(0,j.useState)(!1),[r,s]=(0,j.useState)("");if(i===void 0)return null;let m=Number(r),p=r.trim()===""?"\u8BF7\u8F93\u5165\u6CE8\u989D\u3002":U(m,{balanceChips:e.chips,maxChips:i});return(0,t.jsxs)("div",{children:[(0,t.jsx)("div",{className:"bj-actions",children:(0,t.jsx)("button",{type:"button",onClick:()=>o(!a),children:"\u81EA\u5B9A\u4E49\u6CE8\u989D"})}),a?(0,t.jsxs)("div",{className:"bj-custom",children:[(0,t.jsx)("input",{type:"number",min:1,max:i,value:r,onChange:v=>s(v.target.value),"aria-label":"\u81EA\u5B9A\u4E49\u6CE8\u989D"}),(0,t.jsx)("button",{type:"button",disabled:p!==null,onClick:()=>n(m),children:"\u5F00\u59CB"}),(0,t.jsx)("span",{className:"bj-hint",children:p??`1 \uFF5E ${l(i)} CHIP`})]}):null]})}function G({me:e,onDeal:n}){let i=e?.freeHandsRemaining??0,a=e?.baseBetChips??0,o=e?[a,a*2]:[],r=e?o.some(s=>e.chips>=s):!1;return(0,t.jsxs)("div",{children:[(0,t.jsx)("div",{className:"bj-meta",children:"\u5F53\u524D\u6CA1\u6709\u8FDB\u884C\u4E2D\u7684\u724C\u5C40\u3002"}),(0,t.jsxs)("div",{className:"bj-group",children:[(0,t.jsxs)("div",{className:"bj-group-title",children:["\u514D\u8D39\u624B \xB7 ",i>0?`\u4ECA\u65E5\u5269 ${i} \u624B`:"\u4ECA\u65E5\u5DF2\u7528\u5B8C\uFF0C\u660E\u5929\u518D\u6765"]}),e?(0,t.jsxs)("div",{className:"bj-note",children:["\u4E0D\u7528\u51FA CHIP\uFF1B\u8D62\u4E86\u62FF ",l(a)," CHIP\uFF08\u5929\u751F 21 \u70B9 ",l(Math.floor(a*1.5)),"\uFF09\uFF0C\u8F93\u4E86\u4E0D\u635F\u5931\u4EFB\u4F55\u4E1C\u897F\u3002"]}):null,(0,t.jsx)("div",{className:"bj-actions",children:(0,t.jsx)("button",{type:"button",disabled:i<=0,onClick:()=>n(),children:"\u5F00\u59CB\u514D\u8D39\u7684\u4E00\u5C40"})})]}),e?(0,t.jsxs)("div",{className:"bj-group",children:[(0,t.jsxs)("div",{className:"bj-group-title",children:["\u7528\u4F59\u989D\u52A0\u6CE8",r?"":" \xB7 \u4F59\u989D\u4E0D\u8DB3"]}),(0,t.jsx)("div",{className:"bj-note",children:"\u52A0\u6CE8\u7684 CHIP \u4ECE\u4F60\u7684\u4F59\u989D\u91CC\u51FA\uFF0C\u8D62\u4E86\u7FFB\u500D\uFF0C\u8F93\u4E86\u6536\u4E0D\u56DE\u3002"}),(0,t.jsx)("div",{className:"bj-actions",children:o.map(s=>(0,t.jsxs)("button",{type:"button",disabled:e.chips<s,onClick:()=>n(s),children:[l(s)," CHIP"]},s))}),(0,t.jsx)(F,{me:e,onDeal:n})]}):null]})}function q({me:e,onDeal:n,onDismiss:i}){return(0,t.jsxs)("div",{children:[(0,t.jsx)(_,{me:e,onDeal:n,freeLabel:"\u518D\u6765\u4E00\u5C40\uFF08\u514D\u8D39"}),(0,t.jsx)("div",{className:"bj-actions",children:(0,t.jsx)("button",{type:"button",onClick:i,children:"\u6536\u8D77"})})]})}function w({state:e,onAction:n,onDeal:i,onDismiss:a,error:o}){if(!e.consented)return(0,t.jsx)(L,{});let{me:r,round:s}=e;return(0,t.jsxs)("div",{className:"bj-table",children:[(0,t.jsx)("style",{children:M}),o?(0,t.jsx)("div",{className:"bj-error",children:o}):null,r?(0,t.jsx)(A,{me:r}):null,s?(0,t.jsxs)("div",{children:[s.hands.map((m,p)=>(0,t.jsx)(B,{hand:m,label:s.hands.length>1?`\u4F60\u7684\u7B2C ${p+1} \u624B`:"\u4F60\u7684\u724C"},p)),(0,t.jsx)(J,{dealer:s.dealer}),(0,t.jsx)(X,{round:s}),(0,t.jsx)(z,{round:s,me:r,onAction:n}),s.phase==="settled"?(0,t.jsx)(q,{me:r,onDeal:i,onDismiss:a}):null]}):(0,t.jsx)(G,{me:r,onDeal:i})]})}function f(e,n){switch(n.type){case"synced":return{consented:n.payload.consented,me:n.payload.me,round:n.payload.round};case"round":return{...e,round:n.round};case"meRefreshed":return{...e,me:n.me};case"dismissed":return{...e,round:void 0}}}var S=require("react/jsx-runtime"),K=["slots"];async function x(e,n){let i=await fetch(e,n),a=await i.text(),o=a.length>0?JSON.parse(a):{};if(!i.ok){let r=typeof o?.error=="string"?o.error:`${i.status}`;throw new Error(r)}return o}var Q="\u4E0A\u4E00\u6B21\u64CD\u4F5C\u6CA1\u6709\u6210\u529F\uFF0C\u5DF2\u4E3A\u4F60\u5237\u65B0\u5230\u6700\u65B0\u72B6\u6001\u3002";function W(){let[e,n]=(0,c.useState)(null),[i,a]=(0,c.useState)(null),o=(0,c.useCallback)(()=>{x("/blackjack/api/state").then(d=>n(u=>f(u??{consented:!1},{type:"synced",payload:d}))).catch(()=>n({consented:!1}))},[]),r=(0,c.useCallback)(()=>{x("/blackjack/api/state").then(d=>n(u=>u&&f(u,{type:"meRefreshed",me:d.me}))).catch(()=>{})},[]);(0,c.useEffect)(()=>{o()},[o]);let s=(0,c.useCallback)((d,u)=>{x(d,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(u)}).then(N=>{a(null),n(g=>g&&f(g,{type:"round",round:N})),r()}).catch(()=>{a(Q),o()})},[r,o]),m=(0,c.useCallback)(d=>s("/blackjack/api/deal",d===void 0?{}:{betChips:d}),[s]),p=(0,c.useCallback)(d=>s("/blackjack/api/action",{action:d}),[s]),v=(0,c.useCallback)(()=>n(d=>d&&f(d,{type:"dismissed"})),[]);return e===null?null:(0,S.jsx)(w,{state:e,onAction:p,onDeal:m,onDismiss:v,error:i??void 0})}function Y(e){e.slots.inject("conversation.chat.commandview",()=>e.slots.register({name:"conversation.chat.commandview",key:"blackjack",inject:()=>({})},W))}
41
+ `;function S({text:e}){let n=e.split(/(https?:\/\/[^\s,。))]+)/g);return(0,t.jsx)(t.Fragment,{children:n.map((i,o)=>/^https?:\/\//.test(i)?(0,t.jsx)("a",{href:i,target:"_blank",rel:"noreferrer noopener",children:i},o):i)})}function N({card:e}){if("hidden"in e)return(0,t.jsx)("div",{className:"bj-card bj-card-back","aria-label":"\u6697\u724C"});let n=M[e.suit]??e.suit,i=J.has(e.suit)?"bj-card-red":"bj-card-black";return(0,t.jsx)("div",{className:`bj-card ${i}`,children:`${e.rank}${n}`})}function O({hand:e,label:n}){let i=e.soft?"\u8F6F":"";return(0,t.jsxs)("div",{children:[(0,t.jsxs)("div",{className:"bj-meta",children:[n,"\uFF08",i,e.total,"\uFF09",e.doubled?" [\u53CC\u500D]":""]}),(0,t.jsx)("div",{className:"bj-hand",children:e.cards.map((o,a)=>(0,t.jsx)(N,{card:o},a))})]})}function _({dealer:e}){return(0,t.jsxs)("div",{children:[(0,t.jsxs)("div",{className:"bj-meta",children:["\u5E84\u5BB6",e.total!==void 0?`\uFF08${e.total}\uFF09`:""]}),(0,t.jsx)("div",{className:"bj-hand",children:e.cards.map((n,i)=>(0,t.jsx)(N,{card:n},i))})]})}function U(){return(0,t.jsx)("div",{className:"bj-table",children:(0,t.jsxs)("p",{children:["\u8BF7\u5148\u5728\u547D\u4EE4\u884C\u8F93\u5165 ",(0,t.jsx)("code",{children:"/blackjack agree"}),"\uFF0C\u9605\u8BFB\u5E76\u540C\u610F\u89C4\u5219\u540E\u624D\u80FD\u5F00\u59CB\u3002"]})})}function z({me:e}){let n=e.exchangeThresholdChips-e.chips;return(0,t.jsxs)("div",{className:"bj-meta",children:["\u4F59\u989D\uFF1A",d(e.chips)," CHIP\u3000\u4ECA\u65E5\u5269\u4F59\u514D\u8D39\u624B\uFF1A",e.freeHandsRemaining,e.points>0?`\u3000\u79EF\u5206\uFF1A${d(e.points)}`:"",e.exchangedChips>0?`\u3000\u5DF2\u5151\u6362\u989D\u5EA6\uFF1A${d(e.exchangedChips)} CHIP`:"",(0,t.jsx)("div",{className:"bj-note",children:n<=0?`\u5DF2\u8FBE\u5151\u6362\u95E8\u69DB\uFF08${d(e.exchangeThresholdChips)} CHIP\uFF09\xB7 \u7528 /blackjack exchange \u628A CHIP \u6362\u6210\u6A21\u578B\u989D\u5EA6`:`\u8FD8\u5DEE ${d(n)} CHIP \u5230\u5151\u6362\u95E8\u69DB\uFF08${d(e.exchangeThresholdChips)}\uFF09\xB7 \u8FC7\u7EBF\u540E\u53EF\u7528 /blackjack exchange \u6362\u6210\u6A21\u578B\u989D\u5EA6`}),e.exchangedChips>0?(0,t.jsxs)("div",{className:"bj-note",children:["\u5DF2\u5151\u6362\u7684 ",d(e.exchangedChips)," CHIP \u4F1A\u5728\u4F60\u81EA\u5DF1\u7684\u6A21\u578B\u989D\u5EA6\u8017\u5C3D\u65F6\u81EA\u52A8\u63A5\u7BA1\u90A3\u4E00\u6B21\u8BF7\u6C42\uFF0C\u5E73\u65F6\u4E0D\u4F1A\u52A8\u7528\u3002"]}):null,e.poolEmpty?(0,t.jsx)("div",{children:"\uFF08\u672C\u671F\u5956\u6C60\u5DF2\u53D1\u5B8C\uFF0C\u73B0\u5728\u8D62\u724C\u5C06\u53D1\u653E\u79EF\u5206\uFF09"}):null,e.sponsorText?(0,t.jsx)("div",{children:e.sponsorText}):null]})}function F({round:e}){return(0,t.jsxs)("div",{className:"bj-meta",children:[e.phase==="settled"?w(e):y(e),e.pointsGranted?(0,t.jsxs)("div",{children:["\u672C\u671F\u5956\u6C60\u5DF2\u53D1\u5B8C\uFF0C\u6539\u4E3A\u53D1\u653E ",d(e.pointsGranted)," \u79EF\u5206\u3002"]}):null]})}function G(e,n){return e==="double"||e==="split"?n:e==="insure"?Math.ceil(n/2):0}function q({round:e,me:n,onAction:i}){if(e.actions.length===0)return null;let o=n?.chips??0;return(0,t.jsx)("div",{className:"bj-actions",children:e.actions.map(a=>{let r=G(a,e.betChips),s=r>o;return(0,t.jsxs)("button",{type:"button",disabled:s,title:s?`\u8FD8\u8981\u518D\u51FA ${d(r)} CHIP\uFF0C\u4F59\u989D\u4E0D\u8DB3`:void 0,onClick:()=>i(a),children:[A[a]??a,r>0?`\uFF08${d(r)}\uFF09`:""]},a)})})}function K(e,n){return!Number.isInteger(e)||e<1?"\u6CE8\u989D\u8981\u586B\u6B63\u6574\u6570\u3002":e>n.maxChips?`\u5355\u5C40\u6700\u591A ${d(n.maxChips)} CHIP\u3002`:e>n.balanceChips?"\u4F59\u989D\u4E0D\u8DB3\u4EE5\u4E0B\u8FD9\u4E2A\u6CE8\u3002":null}function Q({me:e,onDeal:n,freeLabel:i}){let o=e?.freeHandsRemaining??0,a=e?[e.baseBetChips,e.baseBetChips*2]:[];return(0,t.jsxs)("div",{className:"bj-actions",children:[(0,t.jsxs)("button",{type:"button",disabled:o<=0,onClick:()=>n(),children:[i,e?`\xB7\u5269 ${o} \u624B`:""]}),a.map(r=>(0,t.jsxs)("button",{type:"button",disabled:(e?.chips??0)<r,onClick:()=>n(r),children:[d(r)," CHIP"]},r))]})}function W({me:e,onDeal:n}){let i=e.maxRaiseBetChips,[o,a]=(0,j.useState)(!1),[r,s]=(0,j.useState)("");if(i===void 0)return null;let u=Number(r),p=r.trim()===""?"\u8BF7\u8F93\u5165\u6CE8\u989D\u3002":K(u,{balanceChips:e.chips,maxChips:i});return(0,t.jsxs)("div",{children:[(0,t.jsx)("div",{className:"bj-actions",children:(0,t.jsx)("button",{type:"button",onClick:()=>a(!o),children:"\u81EA\u5B9A\u4E49\u6CE8\u989D"})}),o?(0,t.jsxs)("div",{className:"bj-custom",children:[(0,t.jsx)("input",{type:"number",min:1,max:i,value:r,onChange:g=>s(g.target.value),"aria-label":"\u81EA\u5B9A\u4E49\u6CE8\u989D"}),(0,t.jsx)("button",{type:"button",disabled:p!==null,onClick:()=>n(u),children:"\u5F00\u59CB"}),(0,t.jsx)("span",{className:"bj-hint",children:p??`1 \uFF5E ${d(i)} CHIP`})]}):null]})}function Y({me:e,onDeal:n}){let i=e?.freeHandsRemaining??0,o=e?.baseBetChips??0,a=e?[o,o*2]:[],r=e?a.some(s=>e.chips>=s):!1;return(0,t.jsxs)("div",{children:[(0,t.jsx)("div",{className:"bj-meta",children:"\u5F53\u524D\u6CA1\u6709\u8FDB\u884C\u4E2D\u7684\u724C\u5C40\u3002"}),(0,t.jsxs)("div",{className:"bj-group",children:[(0,t.jsxs)("div",{className:"bj-group-title",children:["\u514D\u8D39\u624B \xB7 ",i>0?`\u4ECA\u65E5\u5269 ${i} \u624B`:"\u4ECA\u65E5\u5DF2\u7528\u5B8C\uFF0C\u660E\u5929\u518D\u6765"]}),e?(0,t.jsxs)("div",{className:"bj-note",children:["\u4E0D\u7528\u51FA CHIP\u3002\u8D62\u4E86\u62FF ",d(o)," CHIP\uFF1B\u5934\u4E24\u5F20\u724C\u5C31\u51D1\u6210 21 \u70B9\uFF08A \u914D 10/J/Q/K\uFF09\u62FF"," ",d(Math.floor(o*1.5)),"\u3002\u8F93\u4E86\u6216\u5E73\u5C40\u90FD\u4E0D\u635F\u5931\u4EFB\u4F55\u4E1C\u897F\u3002"]}):null,(0,t.jsx)("div",{className:"bj-actions",children:(0,t.jsx)("button",{type:"button",disabled:i<=0,onClick:()=>n(),children:"\u5F00\u59CB\u514D\u8D39\u7684\u4E00\u5C40"})})]}),e?(0,t.jsxs)("div",{className:"bj-group",children:[(0,t.jsxs)("div",{className:"bj-group-title",children:["\u7528\u4F59\u989D\u52A0\u6CE8",r?"":" \xB7 \u4F59\u989D\u4E0D\u8DB3"]}),(0,t.jsx)("div",{className:"bj-note",children:"\u52A0\u6CE8\u7684 CHIP \u4ECE\u4F60\u7684\u4F59\u989D\u91CC\u51FA\uFF0C\u8D62\u4E86\u7FFB\u500D\uFF0C\u8F93\u4E86\u6536\u4E0D\u56DE\u3002"}),(0,t.jsx)("div",{className:"bj-actions",children:a.map(s=>(0,t.jsxs)("button",{type:"button",disabled:e.chips<s,onClick:()=>n(s),children:[d(s)," CHIP"]},s))}),(0,t.jsx)(W,{me:e,onDeal:n})]}):null]})}function Z({me:e,onDeal:n,onDismiss:i}){return(0,t.jsxs)("div",{children:[(0,t.jsx)(Q,{me:e,onDeal:n,freeLabel:"\u518D\u6765\u4E00\u5C40\uFF08\u514D\u8D39"}),(0,t.jsx)("div",{className:"bj-actions",children:(0,t.jsx)("button",{type:"button",onClick:i,children:"\u6536\u8D77"})})]})}function T({state:e,onAction:n,onDeal:i,onDismiss:o,error:a}){if(!e.consented)return(0,t.jsx)(U,{});let{me:r,round:s}=e;return(0,t.jsxs)("div",{className:"bj-table",children:[(0,t.jsx)("style",{children:X}),a?(0,t.jsx)("div",{className:"bj-error",children:a}):null,r?(0,t.jsx)(z,{me:r}):null,s?(0,t.jsxs)("div",{children:[s.hands.map((u,p)=>(0,t.jsx)(O,{hand:u,label:s.hands.length>1?`\u4F60\u7684\u7B2C ${p+1} \u624B`:"\u4F60\u7684\u724C"},p)),(0,t.jsx)(_,{dealer:s.dealer}),(0,t.jsx)(F,{round:s}),(0,t.jsx)(q,{round:s,me:r,onAction:n}),s.phase==="settled"?(0,t.jsx)(Z,{me:r,onDeal:i,onDismiss:o}):null]}):(0,t.jsx)(Y,{me:r,onDeal:i})]})}function h(e,n){switch(n.type){case"synced":return{consented:n.payload.consented,me:n.payload.me,round:n.payload.round};case"round":return{...e,round:n.round};case"meRefreshed":return{...e,me:n.me};case"dismissed":return{...e,round:void 0}}}var ee=new Set(["","deal"]);function H(e){let n=(e.args??"").trim().split(/\s+/)[0]??"",i=ee.has(n),o=e.outcome,a=o?.kind==="error";return i?{table:!0,isError:a,...a&&o?.text?{text:o.text}:{}}:{table:!1,isError:a,...o?.text!==void 0?{text:o.text}:{}}}var f=require("react/jsx-runtime"),te=["slots"];async function k(e,n){let i=await fetch(e,n),o=await i.text(),a=o.length>0?JSON.parse(o):{};if(!i.ok){let r=typeof a?.error=="string"?a.error:`${i.status}`;throw new Error(r)}return a}var ne="\u4E0A\u4E00\u6B21\u64CD\u4F5C\u6CA1\u6709\u6210\u529F\uFF0C\u5DF2\u4E3A\u4F60\u5237\u65B0\u5230\u6700\u65B0\u72B6\u6001\u3002";function ie({node:e}){let n=H(e),[i,o]=(0,l.useState)(null),[a,r]=(0,l.useState)(null),s=(0,l.useCallback)(()=>{k("/blackjack/api/state").then(c=>o(b=>h(b??{consented:!1},{type:"synced",payload:c}))).catch(()=>o({consented:!1}))},[]),u=(0,l.useCallback)(()=>{k("/blackjack/api/state").then(c=>o(b=>b&&h(b,{type:"meRefreshed",me:c.me}))).catch(()=>{})},[]);(0,l.useEffect)(()=>{n.table&&s()},[s,n.table]);let p=(0,l.useCallback)((c,b)=>{k(c,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(b)}).then(R=>{r(null),o(v=>v&&h(v,{type:"round",round:R})),u()}).catch(()=>{r(ne),s()})},[u,s]),g=(0,l.useCallback)(c=>p("/blackjack/api/deal",c===void 0?{}:{betChips:c}),[p]),E=(0,l.useCallback)(c=>p("/blackjack/api/action",{action:c}),[p]),I=(0,l.useCallback)(()=>o(c=>c&&h(c,{type:"dismissed"})),[]),x=n.text===void 0?null:(0,f.jsx)("div",{style:{whiteSpace:"pre-wrap",marginBottom:"8px",...n.isError?{color:"#c0392b"}:{}},children:(0,f.jsx)(S,{text:n.text})});return!n.table||i===null?x:(0,f.jsxs)("div",{children:[x,(0,f.jsx)(T,{state:i,onAction:E,onDeal:g,onDismiss:I,error:a??void 0})]})}function oe(e){e.slots.inject("conversation.chat.commandview",()=>e.slots.register({name:"conversation.chat.commandview",key:"blackjack",inject:()=>({})},ie))}
42
42
  return module.exports;
43
43
  }
44
44
  });
package/dist/commands.js CHANGED
@@ -163,12 +163,17 @@ export function registerCommands(ctx, deps) {
163
163
  if (me.githubLogin === null) {
164
164
  const pairing = await api.startPairing(token);
165
165
  const minutes = Math.max(1, Math.round(pairing.expiresInSeconds / 60));
166
+ // 只给链接,不单独列配对码:链接里已经含它了,而单独列出会让人以为要
167
+ // 手动填到某处(运营者第一次用就这么理解了,连开了三次都没敢往下走)。
166
168
  return ok([
167
- '首次兑换需要先在浏览器里绑定 GitHub 账号:',
168
- `配对码:${pairing.code}`,
169
- `链接:${pairing.url}`,
170
- `有效期:约 ${minutes} 分钟`,
171
- '绑定完成后,以后直接用 /blackjack exchange <数量> 就能兑换,不必再开浏览器。',
169
+ '首次兑换要先绑定 GitHub,只需做这一次。',
170
+ '',
171
+ '请在浏览器里打开这个链接:',
172
+ pairing.url,
173
+ '',
174
+ '打开后点「用 GitHub 继续」授权,然后在同一个页面填写要兑换的数量并提交。',
175
+ `链接约 ${minutes} 分钟内有效;过期了就再执行一次 /blackjack exchange 拿一个新的。`,
176
+ '绑定完成后,以后直接用 /blackjack exchange <数量> 兑换,不用再开浏览器。',
172
177
  ].join('\n'));
173
178
  }
174
179
  if (amountArg === undefined) {
@@ -182,9 +187,12 @@ export function registerCommands(ctx, deps) {
182
187
  if (!Number.isInteger(chips) || chips <= 0)
183
188
  return fail('兑换数量必须是正整数。');
184
189
  const result = await api.exchange(token, chips);
190
+ // 必须讲清楚这份额度什么时候会被用上:运营者兑换完就问"我该怎么用"——
191
+ // 因为兑换成功的提示从没说过触发条件,玩家以为兑换完就能直接花。
185
192
  return ok([
186
193
  `已兑换 ${n(result.chips)} CHIP。`,
187
194
  `累计已兑换额度:${n(result.exchangedChips)} CHIP`,
195
+ '这份额度会在你自己的模型额度耗尽时,自动接管那一次失败的请求;平时不会动用,你不需要做任何事。',
188
196
  result.note,
189
197
  ].join('\n'));
190
198
  }
package/dist/router.d.ts CHANGED
@@ -27,6 +27,23 @@ export declare function shouldFallback(input: {
27
27
  hasToken: boolean;
28
28
  provider: string;
29
29
  }): boolean;
30
+ /**
31
+ * 这次失败是否该提示玩家"你有已兑换额度,但续命现在用不上"。
32
+ *
33
+ * 续命的触发条件是**你自己的额度耗尽**(QUOTA_EXCEEDED),不是根本没配 key。
34
+ * 运营者兑换到 50,000 CHIP 后问"我该怎么用",正是因为他没配 key、报的是
35
+ * MISSING_CREDENTIAL,于是什么都没发生,也没有任何东西解释为什么。这里只负责
36
+ * 说明,不改变任何行为——原始失败照样原样透传。
37
+ */
38
+ export declare const NO_CREDENTIAL_HINT = "\u7EED\u547D\u53EA\u5728\u4F60\u81EA\u5DF1\u7684\u6A21\u578B\u989D\u5EA6\u8017\u5C3D\u65F6\u63A5\u7BA1\uFF1B\u5F53\u524D\u662F\u6CA1\u6709\u914D\u7F6E key\uFF0C\u6240\u4EE5\u7528\u4E0D\u5230\u4F60\u5DF2\u5151\u6362\u7684\u989D\u5EA6\u3002";
39
+ /** 抛出来的错误是否读作"没配 key"。 */
40
+ export declare function isMissingCredentialError(error: unknown): boolean;
41
+ export declare function shouldHintNoCredential(input: {
42
+ finish: StreamChunk & {
43
+ type: 'finish';
44
+ };
45
+ provider: string;
46
+ }): boolean;
30
47
  export interface FallbackDeps {
31
48
  hasToken: () => Promise<boolean>;
32
49
  poolStream: (options: GenerateOptions) => AsyncIterable<StreamChunk>;
package/dist/router.js CHANGED
@@ -43,6 +43,36 @@ export function shouldFallback(input) {
43
43
  return false;
44
44
  return isQuotaFailure(reason.failure);
45
45
  }
46
+ /**
47
+ * 「没配 key」的错误码。dsh-llm@0.1.0-rc.6 只导出了 INVALID_CREDENTIAL_CODE 和
48
+ * QUOTA_EXCEEDED_CODE,这个没有导出常量——字面值由该包源码里 INVALID_CREDENTIAL
49
+ * 的注释("Distinct from `MISSING_CREDENTIAL`")与宿主 UI 上的徽章共同确认。
50
+ */
51
+ const MISSING_CREDENTIAL_CODE = 'MISSING_CREDENTIAL';
52
+ /**
53
+ * 这次失败是否该提示玩家"你有已兑换额度,但续命现在用不上"。
54
+ *
55
+ * 续命的触发条件是**你自己的额度耗尽**(QUOTA_EXCEEDED),不是根本没配 key。
56
+ * 运营者兑换到 50,000 CHIP 后问"我该怎么用",正是因为他没配 key、报的是
57
+ * MISSING_CREDENTIAL,于是什么都没发生,也没有任何东西解释为什么。这里只负责
58
+ * 说明,不改变任何行为——原始失败照样原样透传。
59
+ */
60
+ export const NO_CREDENTIAL_HINT = '续命只在你自己的模型额度耗尽时接管;当前是没有配置 key,所以用不到你已兑换的额度。';
61
+ /** 抛出来的错误是否读作"没配 key"。 */
62
+ export function isMissingCredentialError(error) {
63
+ const e = error;
64
+ if (e && typeof e === 'object' && e.code === MISSING_CREDENTIAL_CODE)
65
+ return true;
66
+ return typeof e?.message === 'string' && /no API key|MISSING_CREDENTIAL/i.test(e.message);
67
+ }
68
+ export function shouldHintNoCredential(input) {
69
+ if (input.provider === POOL_PROVIDER)
70
+ return false;
71
+ const reason = input.finish.reason;
72
+ if (reason.kind !== 'error')
73
+ return false;
74
+ return reason.failure.code === MISSING_CREDENTIAL_CODE;
75
+ }
46
76
  /**
47
77
  * Wrap one model call: pass everything through, and only when the call ends
48
78
  * with a content-free quota failure retry it against the pool route. Any
@@ -69,18 +99,36 @@ export async function* runWithFallback(options, next, deps) {
69
99
  let emittedContent = false;
70
100
  let finish;
71
101
  const pendingUsage = [];
72
- for await (const c of next()) {
73
- if (c.type === 'finish') {
74
- finish = c;
75
- break;
102
+ try {
103
+ for await (const c of next()) {
104
+ if (c.type === 'finish') {
105
+ finish = c;
106
+ break;
107
+ }
108
+ if (c.type === 'usage') {
109
+ pendingUsage.push(c);
110
+ continue;
111
+ }
112
+ if (CONTENT_TYPES.has(c.type))
113
+ emittedContent = true;
114
+ yield c;
76
115
  }
77
- if (c.type === 'usage') {
78
- pendingUsage.push(c);
79
- continue;
116
+ }
117
+ catch (error) {
118
+ // 没配 key 时适配器在发请求前就抛,根本不产出 finish 分片。只说明,
119
+ // 不改变行为——原始错误原样抛出去,装了这个插件不会让任何调用变差。
120
+ if (options.provider !== POOL_PROVIDER && isMissingCredentialError(error)) {
121
+ let hinted = false;
122
+ try {
123
+ hinted = await deps.hasToken();
124
+ }
125
+ catch {
126
+ hinted = false;
127
+ }
128
+ if (hinted)
129
+ deps.notify(NO_CREDENTIAL_HINT);
80
130
  }
81
- if (CONTENT_TYPES.has(c.type))
82
- emittedContent = true;
83
- yield c;
131
+ throw error;
84
132
  }
85
133
  if (!finish)
86
134
  return;
@@ -90,6 +138,21 @@ export async function* runWithFallback(options, next, deps) {
90
138
  // a credential resolve, and a credentials-provider rejection can only ever
91
139
  // affect a call this plugin was already about to intervene on.
92
140
  if (!shouldFallback({ finish, emittedContent, hasToken: true, provider: options.provider })) {
141
+ // 唯一的例外:没配 key 时,玩家可能正攥着一份用不上的已兑换额度而不知道
142
+ // 为什么。这条只说明、不改行为。await hasToken() 只发生在这个已经失败
143
+ // 且罕见的分支上,正常调用仍然不付凭证解析的代价。
144
+ if (shouldHintNoCredential({ finish, provider: options.provider })) {
145
+ let hinted = false;
146
+ try {
147
+ hinted = await deps.hasToken();
148
+ }
149
+ catch {
150
+ hinted = false;
151
+ }
152
+ if (hinted) {
153
+ deps.notify(NO_CREDENTIAL_HINT);
154
+ }
155
+ }
93
156
  yield* pendingUsage;
94
157
  yield finish;
95
158
  return;
@@ -113,6 +176,11 @@ export async function* runWithFallback(options, next, deps) {
113
176
  try {
114
177
  for await (const c of deps.poolStream({ ...options, provider: POOL_PROVIDER })) {
115
178
  if (c.type === 'finish' && c.reason.kind === 'error') {
179
+ // 奖池这一侧的失败必须留痕。原先它被静默丢弃、连日志都没有,于是
180
+ // "续命明明接管了却还是失败"这种情况从外面完全看不出来——真实事故里
181
+ // 奖池回的是 `unknown model: deepseek-v4-flash`(模型不在价目表里),
182
+ // 而玩家只看到原始的 Insufficient Balance,跟没装插件一模一样。
183
+ deps.logError?.(new Error(`dsh-blackjack: pool retry failed after a quota exhaustion; pool said ${JSON.stringify(c.reason.failure)}`));
116
184
  yield finish;
117
185
  return;
118
186
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-blackjack",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "社区福利小游戏插件:在 dsh 里玩 21 点赢取可消费的模型额度",
5
5
  "license": "MIT",
6
6
  "keywords": [