@max-null/dsh-achievements 0.1.0 → 0.1.2
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/client.js +174 -39
- package/dist/engine.d.ts +6 -0
- package/dist/engine.js +47 -4
- package/dist/rules.d.ts +7 -4
- package/dist/rules.js +54 -23
- package/package.json +1 -1
package/client.js
CHANGED
|
@@ -52,16 +52,101 @@ var STRINGS = {
|
|
|
52
52
|
desc: "SSiD \u5168\u5BB6\u6876\u8FDB\u5EA6\uFF1A\u5DE5\u5177/\u8BB0\u5FC6/\u5BA1\u8BA1/GenUI \u4F7F\u7528\u6210\u5C31\uFF0C\u89E3\u9501\u53EF\u9886 toast\u3002",
|
|
53
53
|
refresh: "\u5237\u65B0",
|
|
54
54
|
locked: "\u672A\u89E3\u9501",
|
|
55
|
-
unlocked: "\u5DF2\u89E3\u9501"
|
|
55
|
+
unlocked: "\u5DF2\u89E3\u9501",
|
|
56
|
+
pluginMissing: "\u63D2\u4EF6\u672A\u88C5",
|
|
57
|
+
growing: "\u66F4\u591A\u7EC4\u4EF6\u7684\u6210\u5C31\u6301\u7EED\u589E\u52A0\u4E2D \u2014\u2014 Guardian / Habit / \u6DA6\u8272 / \u5BA1\u67E5\u7B49\u63D2\u4EF6\u5C06\u9646\u7EED\u52A0\u5165\u3002",
|
|
58
|
+
builtin: "\u5185\u7F6E"
|
|
56
59
|
},
|
|
57
60
|
en: {
|
|
58
61
|
title: "Achievements",
|
|
59
62
|
desc: "SSiD family progress: tool/memory/audit/GenUI usage trophies with unlock toasts.",
|
|
60
63
|
refresh: "Refresh",
|
|
61
64
|
locked: "Locked",
|
|
62
|
-
unlocked: "Unlocked"
|
|
65
|
+
unlocked: "Unlocked",
|
|
66
|
+
pluginMissing: "plugin not installed",
|
|
67
|
+
growing: "More component achievements are on the way \u2014 Guardian / Habit / Polish / Review and more will join soon.",
|
|
68
|
+
builtin: "built-in"
|
|
63
69
|
}
|
|
64
70
|
};
|
|
71
|
+
var PLUGIN_SHORT = {
|
|
72
|
+
"@max-null/dsh-memory": "\u8BB0\u5FC6",
|
|
73
|
+
"dsh-context-doctor": "\u5BA1\u8BA1",
|
|
74
|
+
"@changfenhuang/dsh-genui": "GenUI",
|
|
75
|
+
"@max-null/dsh-chat-rail": "\u6536\u85CF"
|
|
76
|
+
};
|
|
77
|
+
function pluginLabel(plugin) {
|
|
78
|
+
if (plugin === null) return null;
|
|
79
|
+
return PLUGIN_SHORT[plugin] ?? plugin.split("/").pop() ?? plugin;
|
|
80
|
+
}
|
|
81
|
+
async function readChatRail() {
|
|
82
|
+
try {
|
|
83
|
+
const res = await fetch("/chat-rail/api/favorites", { headers: { Accept: "application/json" } });
|
|
84
|
+
if (!res.ok) return 0;
|
|
85
|
+
const data = await res.json();
|
|
86
|
+
const map = data.value;
|
|
87
|
+
if (map === null || typeof map !== "object" || Array.isArray(map)) return 0;
|
|
88
|
+
let total = 0;
|
|
89
|
+
for (const value of Object.values(map)) {
|
|
90
|
+
if (Array.isArray(value)) total += value.filter((id) => typeof id === "string" && id !== "").length;
|
|
91
|
+
}
|
|
92
|
+
return total;
|
|
93
|
+
} catch {
|
|
94
|
+
return 0;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
var TROPHY_PATHS = [
|
|
98
|
+
"M6 9H4.5a2.5 2.5 0 0 1 0-5H6",
|
|
99
|
+
"M18 9h1.5a2.5 2.5 0 0 0 0-5H18",
|
|
100
|
+
"M4 22h16",
|
|
101
|
+
"M10 14.66V17c0 .55-.47.98-.97 1.21C7.85 18.75 7 20.24 7 22",
|
|
102
|
+
"M14 14.66V17c0 .55.47.98.97 1.21C16.15 18.75 17 20.24 17 22",
|
|
103
|
+
"M18 2H6v7a6 6 0 0 0 12 0V2Z"
|
|
104
|
+
];
|
|
105
|
+
var SETTINGS_NAV_MARKER = "data-dsh-achievements-settings-nav";
|
|
106
|
+
var TROPHY_MASK_SVG = encodeURIComponent(
|
|
107
|
+
`<svg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'>${TROPHY_PATHS.map((d) => `<path d='${d}'/>`).join("")}</svg>`
|
|
108
|
+
).replace(/'/g, "%27").replace(/\//g, "%2F").replace(/\(/g, "%28").replace(/\)/g, "%29").replace(/#/g, "%23");
|
|
109
|
+
var NAV_ICON_CSS = `
|
|
110
|
+
[data-dsh-achievements-settings-nav] > svg:first-child { display: none; }
|
|
111
|
+
[data-dsh-achievements-settings-nav]::before {
|
|
112
|
+
content: '';
|
|
113
|
+
flex: none;
|
|
114
|
+
width: 16px;
|
|
115
|
+
height: 16px;
|
|
116
|
+
background: currentColor;
|
|
117
|
+
-webkit-mask: url("data:image/svg+xml,${TROPHY_MASK_SVG}") center / contain no-repeat;
|
|
118
|
+
mask: url("data:image/svg+xml,${TROPHY_MASK_SVG}") center / contain no-repeat;
|
|
119
|
+
}
|
|
120
|
+
`;
|
|
121
|
+
var navCssInjected = false;
|
|
122
|
+
function injectNavCss() {
|
|
123
|
+
if (navCssInjected || typeof document === "undefined") return;
|
|
124
|
+
navCssInjected = true;
|
|
125
|
+
const style = document.createElement("style");
|
|
126
|
+
style.setAttribute("data-plugin", "@max-null/dsh-achievements");
|
|
127
|
+
style.textContent = NAV_ICON_CSS;
|
|
128
|
+
document.head.append(style);
|
|
129
|
+
}
|
|
130
|
+
function registerSettingsNavIcon(label) {
|
|
131
|
+
let disposed = false;
|
|
132
|
+
const sync = () => {
|
|
133
|
+
if (disposed) return;
|
|
134
|
+
const currentLabel = label().trim();
|
|
135
|
+
const buttons = document.querySelectorAll('[role="dialog"] nav button');
|
|
136
|
+
for (const button of buttons) {
|
|
137
|
+
const matches = currentLabel.length > 0 && button.textContent?.trim() === currentLabel;
|
|
138
|
+
if (matches) button.setAttribute(SETTINGS_NAV_MARKER, "");
|
|
139
|
+
else button.removeAttribute(SETTINGS_NAV_MARKER);
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
sync();
|
|
143
|
+
const observer = new MutationObserver(sync);
|
|
144
|
+
observer.observe(document.body, { subtree: true, childList: true, characterData: true });
|
|
145
|
+
return () => {
|
|
146
|
+
disposed = true;
|
|
147
|
+
observer.disconnect();
|
|
148
|
+
};
|
|
149
|
+
}
|
|
65
150
|
var GENUI_LS_KEY = "dsh.genui.achievements";
|
|
66
151
|
function readGenUI() {
|
|
67
152
|
try {
|
|
@@ -87,6 +172,7 @@ function AchievementsView(_props) {
|
|
|
87
172
|
const reload = (0, import_react.useCallback)(async () => {
|
|
88
173
|
const genui = readGenUI();
|
|
89
174
|
await api("genui-merge", genui);
|
|
175
|
+
await api("chat-rail-merge", { total: await readChatRail() });
|
|
90
176
|
const data = await api("list");
|
|
91
177
|
if (data !== null) setSnapshot(data);
|
|
92
178
|
}, []);
|
|
@@ -103,8 +189,18 @@ function AchievementsView(_props) {
|
|
|
103
189
|
(0, import_react.createElement)(
|
|
104
190
|
"div",
|
|
105
191
|
{ className: "achHead" },
|
|
106
|
-
(0, import_react.createElement)(
|
|
107
|
-
|
|
192
|
+
(0, import_react.createElement)(
|
|
193
|
+
"div",
|
|
194
|
+
{ className: "achHeadLeft" },
|
|
195
|
+
(0, import_react.createElement)("span", { className: "achTitle" }, t.title),
|
|
196
|
+
(0, import_react.createElement)("span", { className: "achDesc" }, t.desc)
|
|
197
|
+
),
|
|
198
|
+
(0, import_react.createElement)(
|
|
199
|
+
"div",
|
|
200
|
+
{ className: "achScore", "data-locked": snapshot === null ? void 0 : String(snapshot.unlocked) },
|
|
201
|
+
(0, import_react.createElement)("span", { className: "achScoreNum" }, snapshot !== null ? `${snapshot.unlocked}` : "\u2026"),
|
|
202
|
+
(0, import_react.createElement)("span", { className: "achScoreTotal" }, snapshot !== null ? `/ ${snapshot.total}` : "")
|
|
203
|
+
),
|
|
108
204
|
(0, import_react.createElement)("button", {
|
|
109
205
|
type: "button",
|
|
110
206
|
className: "achBtn",
|
|
@@ -115,7 +211,6 @@ function AchievementsView(_props) {
|
|
|
115
211
|
}
|
|
116
212
|
}, t.refresh)
|
|
117
213
|
),
|
|
118
|
-
(0, import_react.createElement)("div", { className: "achDesc" }, t.desc),
|
|
119
214
|
(0, import_react.createElement)(
|
|
120
215
|
"div",
|
|
121
216
|
{ className: "achCats" },
|
|
@@ -126,13 +221,23 @@ function AchievementsView(_props) {
|
|
|
126
221
|
onClick: () => setCategory(c)
|
|
127
222
|
}, c))
|
|
128
223
|
),
|
|
224
|
+
(0, import_react.createElement)(
|
|
225
|
+
"div",
|
|
226
|
+
{ className: "achGrowing" },
|
|
227
|
+
(0, import_react.createElement)("span", { className: "achGrowingIcon", "aria-hidden": "" }, "\u{1F9E9}"),
|
|
228
|
+
(0, import_react.createElement)("span", null, t.growing)
|
|
229
|
+
),
|
|
129
230
|
(0, import_react.createElement)(
|
|
130
231
|
"div",
|
|
131
232
|
{ className: "achList" },
|
|
132
233
|
items.map((a) => (0, import_react.createElement)(
|
|
133
234
|
"div",
|
|
134
|
-
{ key: a.id, className: `achRow${a.
|
|
135
|
-
(0, import_react.createElement)(
|
|
235
|
+
{ key: a.id, className: `achRow${a.unlocked ? " achRowOn" : ""}${a.pluginInstalled ? "" : " achRowMuted"}` },
|
|
236
|
+
(0, import_react.createElement)(
|
|
237
|
+
"span",
|
|
238
|
+
{ className: `achIconWrap achIconWrap-${a.rarity}`, "aria-hidden": "" },
|
|
239
|
+
(0, import_react.createElement)("span", { className: "achIcon" }, a.icon)
|
|
240
|
+
),
|
|
136
241
|
(0, import_react.createElement)(
|
|
137
242
|
"div",
|
|
138
243
|
{ className: "achBody" },
|
|
@@ -144,7 +249,8 @@ function AchievementsView(_props) {
|
|
|
144
249
|
"span",
|
|
145
250
|
{ className: `achRarity achRarity-${a.rarity}` },
|
|
146
251
|
a.rarity === "legendary" ? "\u4F20\u8BF4" : a.rarity === "epic" ? "\u53F2\u8BD7" : a.rarity === "rare" ? "\u7A00\u6709" : "\u666E\u901A"
|
|
147
|
-
)
|
|
252
|
+
),
|
|
253
|
+
a.plugin !== null ? (0, import_react.createElement)("span", { className: "achPlugin" }, pluginLabel(a.plugin)) : null
|
|
148
254
|
),
|
|
149
255
|
(0, import_react.createElement)("div", { className: "achDesc2" }, a.desc),
|
|
150
256
|
(0, import_react.createElement)(
|
|
@@ -161,7 +267,9 @@ function AchievementsView(_props) {
|
|
|
161
267
|
(0, import_react.createElement)("span", { className: "achProgNum" }, `${a.progress.current} / ${a.progress.target}`)
|
|
162
268
|
)
|
|
163
269
|
),
|
|
164
|
-
(0, import_react.createElement)("span", {
|
|
270
|
+
(0, import_react.createElement)("span", {
|
|
271
|
+
className: `achState${a.pluginInstalled ? a.unlocked ? " achStateOn" : "" : " achStateOff"}`
|
|
272
|
+
}, a.pluginInstalled ? a.unlocked ? t.unlocked : t.locked : t.pluginMissing)
|
|
165
273
|
))
|
|
166
274
|
),
|
|
167
275
|
note !== "" ? (0, import_react.createElement)("div", { className: "achNote" }, note) : null
|
|
@@ -206,46 +314,73 @@ function apply(ctx) {
|
|
|
206
314
|
label: () => STRINGS[lang()].title
|
|
207
315
|
}, () => (0, import_react.createElement)(AchievementsView, { visible: true })));
|
|
208
316
|
disposers.push(mountToastLayer());
|
|
317
|
+
injectNavCss();
|
|
318
|
+
disposers.push(registerSettingsNavIcon(() => STRINGS[lang()].title));
|
|
209
319
|
return () => {
|
|
210
320
|
for (const dispose of disposers) dispose();
|
|
211
321
|
};
|
|
212
322
|
}
|
|
213
|
-
if (typeof document !== "undefined" && document.querySelector("style[data-dsh-achievements-css]") === null) {
|
|
214
|
-
const tag = document.createElement("style");
|
|
215
|
-
tag.dataset.dshAchievementsCss = "1";
|
|
216
|
-
tag.textContent = CSS;
|
|
217
|
-
document.head.appendChild(tag);
|
|
218
|
-
}
|
|
219
323
|
var CSS = [
|
|
220
|
-
|
|
221
|
-
".
|
|
222
|
-
|
|
223
|
-
".
|
|
224
|
-
".
|
|
225
|
-
".
|
|
324
|
+
/* ---- 视图容器 ---- */
|
|
325
|
+
".ach{display:flex;flex-direction:column;gap:14px;padding:8px 0 4px}",
|
|
326
|
+
/* ---- 头部:标题+进度徽章+刷新 ---- */
|
|
327
|
+
".achHead{display:flex;align-items:center;gap:14px}",
|
|
328
|
+
".achHeadLeft{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}",
|
|
329
|
+
".achTitle{font-size:16px;font-weight:600;letter-spacing:.01em;color:var(--dsw-alias-label-primary,inherit)}",
|
|
330
|
+
".achDesc{font-size:12px;line-height:1.6;color:var(--dsw-alias-label-tertiary,inherit)}",
|
|
331
|
+
".achScore{flex:none;display:flex;align-items:baseline;gap:2px;padding:8px 14px;border-radius:14px;background:color-mix(in srgb,var(--dsw-alias-state-business-primary,#4f8ef7) 10%,transparent);border:1px solid color-mix(in srgb,var(--dsw-alias-state-business-primary,#4f8ef7) 25%,transparent)}",
|
|
332
|
+
".achScoreNum{font-size:20px;font-weight:700;color:var(--dsw-alias-state-business-primary,#4f8ef7);font-variant-numeric:tabular-nums}",
|
|
333
|
+
".achScoreTotal{font-size:13px;color:var(--dsw-alias-label-secondary,inherit);font-variant-numeric:tabular-nums}",
|
|
334
|
+
".achBtn{flex:none;padding:5px 14px;border:1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.4));border-radius:999px;background:transparent;color:var(--dsw-alias-label-secondary,inherit);font-size:12px;cursor:pointer;transition:background .15s}",
|
|
335
|
+
".achBtn:hover{background:var(--dsw-alias-fill-hover,rgba(127,127,127,.12))}",
|
|
336
|
+
/* ---- 类别 tabs ---- */
|
|
226
337
|
".achCats{display:flex;flex-wrap:wrap;gap:6px}",
|
|
227
|
-
".achCat{padding:3px
|
|
228
|
-
".
|
|
229
|
-
".
|
|
230
|
-
|
|
231
|
-
".
|
|
232
|
-
".
|
|
233
|
-
".
|
|
338
|
+
".achCat{padding:3px 11px;border:1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.35));border-radius:999px;background:transparent;color:var(--dsw-alias-label-secondary,inherit);font-size:12px;line-height:1.6;cursor:pointer;transition:all .15s}",
|
|
339
|
+
".achCat:hover{color:var(--dsw-alias-label-primary,inherit)}",
|
|
340
|
+
".achCatOn{border-color:var(--dsw-alias-state-business-primary,#4f8ef7);color:var(--dsw-alias-state-business-primary,#4f8ef7);background:color-mix(in srgb,var(--dsw-alias-state-business-primary,#4f8ef7) 10%,transparent)}",
|
|
341
|
+
/* ---- 成就列表 ---- */
|
|
342
|
+
".achList{display:flex;flex-direction:column;gap:8px;max-height:480px;overflow:auto;padding-right:2px}",
|
|
343
|
+
".achRow{display:flex;align-items:center;gap:12px;padding:10px 12px;border:1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.22));border-radius:14px;background:var(--dsw-alias-bg-layer-2,transparent);transition:border-color .15s}",
|
|
344
|
+
".achRowOn{border-color:color-mix(in srgb,var(--dsw-alias-state-business-primary,#4f8ef7) 40%,transparent);background:color-mix(in srgb,var(--dsw-alias-state-business-primary,#4f8ef7) 6%,transparent)}",
|
|
345
|
+
/* icon 圆底(稀有度 tint) */
|
|
346
|
+
".achIconWrap{flex:none;width:40px;height:40px;display:flex;align-items:center;justify-content:center;border-radius:12px;font-size:20px;line-height:1;background:color-mix(in srgb,var(--dsw-alias-state-business-primary,#4f8ef7) 12%,transparent);border:1px solid color-mix(in srgb,var(--dsw-alias-state-business-primary,#4f8ef7) 18%,transparent)}",
|
|
347
|
+
".achIconWrap-rare{background:color-mix(in srgb,#f59e0b 12%,transparent);border-color:color-mix(in srgb,#f59e0b 20%,transparent)}",
|
|
348
|
+
".achIconWrap-epic{background:color-mix(in srgb,#a78bfa 12%,transparent);border-color:color-mix(in srgb,#a78bfa 20%,transparent)}",
|
|
349
|
+
".achIconWrap-legendary{background:color-mix(in srgb,#f43f5e 12%,transparent);border-color:color-mix(in srgb,#f43f5e 20%,transparent)}",
|
|
350
|
+
".achIcon{filter:saturate(1.05)}",
|
|
351
|
+
/* 行正文 */
|
|
352
|
+
".achBody{flex:1;min-width:0;display:flex;flex-direction:column;gap:3px}",
|
|
234
353
|
".achName{display:flex;align-items:center;gap:8px;font-size:13px;font-weight:600;color:var(--dsw-alias-label-primary,inherit)}",
|
|
235
|
-
".achRarity{font-size:
|
|
236
|
-
".achRarity-rare{background:color-mix(in srgb,#f59e0b
|
|
237
|
-
".achRarity-epic{background:color-mix(in srgb,#a78bfa
|
|
238
|
-
".achRarity-legendary{background:color-mix(in srgb,#f43f5e
|
|
354
|
+
".achRarity{font-size:10px;line-height:1.6;padding:0 7px;border-radius:999px;background:color-mix(in srgb,var(--dsw-alias-state-business-primary,#4f8ef7) 12%,transparent);color:var(--dsw-alias-state-business-primary,#4f8ef7);font-weight:500}",
|
|
355
|
+
".achRarity-rare{background:color-mix(in srgb,#f59e0b 12%,transparent);color:#d97706}",
|
|
356
|
+
".achRarity-epic{background:color-mix(in srgb,#a78bfa 12%,transparent);color:#7c3aed}",
|
|
357
|
+
".achRarity-legendary{background:color-mix(in srgb,#f43f5e 12%,transparent);color:#e11d48}",
|
|
358
|
+
/* 所属插件标签 */
|
|
359
|
+
".achPlugin{font-size:10px;line-height:1.6;padding:0 7px;border-radius:999px;background:var(--dsw-alias-fill-hover,rgba(127,127,127,.12));color:var(--dsw-alias-label-secondary,inherit);font-weight:500;border:1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.25))}",
|
|
360
|
+
/* 成就持续增加中提示卡 */
|
|
361
|
+
".achGrowing{display:flex;align-items:flex-start;gap:10px;padding:10px 12px;border:1px dashed var(--dsw-alias-border-l2,rgba(127,127,127,.35));border-radius:12px;background:var(--dsw-alias-bg-layer-1,transparent);font-size:12px;line-height:1.6;color:var(--dsw-alias-label-secondary,inherit)}",
|
|
362
|
+
".achGrowingIcon{flex:none;font-size:15px;line-height:1.4}",
|
|
363
|
+
/* 插件未装置灰 */
|
|
364
|
+
".achRowMuted{opacity:.55;filter:grayscale(.6)}",
|
|
365
|
+
".achStateOff{color:var(--dsw-alias-label-tertiary,inherit);background:var(--dsw-alias-fill-hover,rgba(127,127,127,.08))}",
|
|
239
366
|
".achDesc2{font-size:12px;line-height:1.5;color:var(--dsw-alias-label-secondary,inherit)}",
|
|
240
|
-
".achProgRow{display:flex;align-items:center;gap:8px;margin-top:
|
|
241
|
-
".achProg{flex:1;height:
|
|
242
|
-
".achProgFill{height:100%;background:var(--dsw-alias-state-business-primary,#4f8ef7)}",
|
|
243
|
-
".achProgNum{font-size:11px;color:var(--dsw-alias-label-tertiary,inherit)}",
|
|
244
|
-
|
|
245
|
-
".
|
|
246
|
-
".
|
|
367
|
+
".achProgRow{display:flex;align-items:center;gap:8px;margin-top:2px}",
|
|
368
|
+
".achProg{flex:1;height:5px;border-radius:999px;background:var(--dsw-alias-border-l1,rgba(127,127,127,.28));overflow:hidden}",
|
|
369
|
+
".achProgFill{height:100%;border-radius:999px;background:var(--dsw-alias-state-business-primary,#4f8ef7);transition:width .3s}",
|
|
370
|
+
".achProgNum{flex:none;font-size:11px;color:var(--dsw-alias-label-tertiary,inherit);font-variant-numeric:tabular-nums}",
|
|
371
|
+
/* 状态徽章 */
|
|
372
|
+
".achState{flex:none;font-size:11px;line-height:1.6;padding:0 8px;border-radius:999px;color:var(--dsw-alias-label-tertiary,inherit);background:var(--dsw-alias-fill-hover,rgba(127,127,127,.1))}",
|
|
373
|
+
".achStateOn{color:var(--dsw-alias-state-success-primary,#16a34a);background:color-mix(in srgb,var(--dsw-alias-state-success-primary,#16a34a) 12%,transparent)}",
|
|
374
|
+
".achNote{font-size:12px;color:var(--dsw-alias-state-success-primary,#16a34a)}",
|
|
375
|
+
/* toast */
|
|
247
376
|
".at-row{position:fixed;right:16px;bottom:16px;z-index:9999;width:300px;padding:10px 12px;border:1px solid var(--dsw-alias-state-business-primary,#4f8ef7);border-radius:12px;background:var(--dsw-alias-bg-layer-2,rgba(0,0,0,.85));color:var(--dsw-alias-state-business-primary,#4f8ef7);font-size:13px;box-shadow:0 8px 24px rgba(0,0,0,.25)}"
|
|
248
377
|
].join("");
|
|
378
|
+
if (typeof document !== "undefined" && document.querySelector("style[data-dsh-achievements-css]") === null) {
|
|
379
|
+
const tag = document.createElement("style");
|
|
380
|
+
tag.dataset.dshAchievementsCss = "1";
|
|
381
|
+
tag.textContent = CSS;
|
|
382
|
+
document.head.appendChild(tag);
|
|
383
|
+
}
|
|
249
384
|
return module.exports;
|
|
250
385
|
},
|
|
251
386
|
});
|
package/dist/engine.d.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import type { Context } from '@deepseek-ai/cordis';
|
|
2
2
|
import { type AchieveState } from './rules.ts';
|
|
3
|
+
/** 有成就关联的插件(探测存在性以置灰未装插件的成就)。 */
|
|
4
|
+
export declare const ASSOCIATED_PLUGINS: readonly string[];
|
|
5
|
+
/** 探测已安装的关联插件:SSID_PROFILE_DIR(壳注入)与常见 profile 目录。 */
|
|
6
|
+
export declare function installedPlugins(): Set<string>;
|
|
3
7
|
export interface EngineSnapshot {
|
|
4
8
|
state: AchieveState;
|
|
5
9
|
distinct: Record<string, number>;
|
|
@@ -28,6 +32,8 @@ export declare class AchievementsEngine {
|
|
|
28
32
|
recordTokens(amount: number): void;
|
|
29
33
|
/** GenUI 计数合并(client 写入;host 侧直接写计数键)。 */
|
|
30
34
|
mergeGenUI(unlockedCount: number, fences: number): void;
|
|
35
|
+
/** chat-rail 收藏绝对值(localStorage 上报;setMax 防重复虚增)。 */
|
|
36
|
+
mergeChatRail(total: number): void;
|
|
31
37
|
/** 检查解锁;返回新解锁(进队列 + 持久化;计数每次落盘)。 */
|
|
32
38
|
flush(): void;
|
|
33
39
|
snapshot(): EngineSnapshot;
|
package/dist/engine.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* 隐私纪律:只读叶级标量(工具名/成功标志/session 事件类型/token 数/
|
|
6
6
|
* 当天时刻),绝不读消息正文/文件内容/错误详情。
|
|
7
7
|
*/
|
|
8
|
-
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
8
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
9
9
|
import { homedir } from 'node:os';
|
|
10
10
|
import { join } from 'node:path';
|
|
11
11
|
import { defineTool } from '@deepseek-ai/dsh-tools';
|
|
@@ -71,6 +71,14 @@ class StateStore {
|
|
|
71
71
|
bump(key, by = 1) {
|
|
72
72
|
this.counters[key] = (this.counters[key] ?? 0) + by;
|
|
73
73
|
}
|
|
74
|
+
/** merge 上报语义:取 max(外部系统计数为绝对值,重复上报不虚增)。 */
|
|
75
|
+
setMax(key, value) {
|
|
76
|
+
if (value <= 0)
|
|
77
|
+
return;
|
|
78
|
+
if ((this.counters[key] ?? 0) >= value)
|
|
79
|
+
return;
|
|
80
|
+
this.counters[key] = value;
|
|
81
|
+
}
|
|
74
82
|
addDistinct(key, value) {
|
|
75
83
|
let set = this.distinct[key];
|
|
76
84
|
if (set === undefined) {
|
|
@@ -101,6 +109,29 @@ const MEMORY_TOOLS = new Set([
|
|
|
101
109
|
const CONFIRM_TOOLS = new Set(['memory_confirm']);
|
|
102
110
|
const TEMPLATE_TOOLS = new Set(['prompt_search', 'prompt_get', 'prompt_list', 'prompt_add']);
|
|
103
111
|
const AUDIT_TOOLS = new Set(['context_audit']);
|
|
112
|
+
/** 有成就关联的插件(探测存在性以置灰未装插件的成就)。 */
|
|
113
|
+
export const ASSOCIATED_PLUGINS = [
|
|
114
|
+
'@max-null/dsh-memory',
|
|
115
|
+
'dsh-context-doctor',
|
|
116
|
+
'@changfenhuang/dsh-genui',
|
|
117
|
+
'@max-null/dsh-chat-rail',
|
|
118
|
+
];
|
|
119
|
+
/** 探测已安装的关联插件:SSID_PROFILE_DIR(壳注入)与常见 profile 目录。 */
|
|
120
|
+
export function installedPlugins() {
|
|
121
|
+
const candidates = [
|
|
122
|
+
process.env.SSID_PROFILE_DIR,
|
|
123
|
+
process.env.DSH_HOME ? join(process.env.DSH_HOME, 'profiles', 'ssid') : undefined,
|
|
124
|
+
join(homedir(), '.dsh', 'profiles', 'ssid'),
|
|
125
|
+
].filter((dir) => typeof dir === 'string' && dir !== '');
|
|
126
|
+
const found = new Set();
|
|
127
|
+
for (const dir of candidates) {
|
|
128
|
+
for (const name of ASSOCIATED_PLUGINS) {
|
|
129
|
+
if (existsSync(join(dir, 'node_modules', ...name.split('/'))))
|
|
130
|
+
found.add(name);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return found;
|
|
134
|
+
}
|
|
104
135
|
/** 引擎(带事件源的宿主注入)。 */
|
|
105
136
|
export class AchievementsEngine {
|
|
106
137
|
store = new StateStore();
|
|
@@ -141,8 +172,12 @@ export class AchievementsEngine {
|
|
|
141
172
|
this.store.bump('tokens', amount); }
|
|
142
173
|
/** GenUI 计数合并(client 写入;host 侧直接写计数键)。 */
|
|
143
174
|
mergeGenUI(unlockedCount, fences) {
|
|
144
|
-
this.store.
|
|
145
|
-
this.store.
|
|
175
|
+
this.store.setMax('genuiUnlocked', Math.max(0, unlockedCount));
|
|
176
|
+
this.store.setMax('genuiFences', Math.max(0, fences));
|
|
177
|
+
}
|
|
178
|
+
/** chat-rail 收藏绝对值(localStorage 上报;setMax 防重复虚增)。 */
|
|
179
|
+
mergeChatRail(total) {
|
|
180
|
+
this.store.setMax('chatRailFavorites', Math.max(0, total));
|
|
146
181
|
}
|
|
147
182
|
/** 检查解锁;返回新解锁(进队列 + 持久化;计数每次落盘)。 */
|
|
148
183
|
flush() {
|
|
@@ -169,8 +204,9 @@ export class AchievementsEngine {
|
|
|
169
204
|
return queue;
|
|
170
205
|
}
|
|
171
206
|
fullSnapshot() {
|
|
207
|
+
const installed = installedPlugins();
|
|
172
208
|
return {
|
|
173
|
-
...snapshot(this.count, this.store.unlockMap),
|
|
209
|
+
...snapshot(this.count, this.store.unlockMap, installed),
|
|
174
210
|
recent: this.snapshot().recent,
|
|
175
211
|
distinct: this.store.distinctCounts,
|
|
176
212
|
};
|
|
@@ -272,6 +308,13 @@ export function registerRoutes(ctx, engine) {
|
|
|
272
308
|
writeJson(res, 200, { ok: true, value: engine.fullSnapshot() });
|
|
273
309
|
return;
|
|
274
310
|
}
|
|
311
|
+
case 'chat-rail-merge': {
|
|
312
|
+
const body = (await readJsonBody(req));
|
|
313
|
+
engine.mergeChatRail(typeof body.total === 'number' ? body.total : 0);
|
|
314
|
+
engine.flush();
|
|
315
|
+
writeJson(res, 200, { ok: true, value: engine.fullSnapshot() });
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
275
318
|
default:
|
|
276
319
|
writeJson(res, 404, { ok: false, error: { code: 'not-found', message: `unknown method "${method}"` } });
|
|
277
320
|
}
|
package/dist/rules.d.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* 文件内容或错误详情。GenUI 类别由 client 侧读取 genui 插件的计数
|
|
7
7
|
* localStorage 合并(见 client/index.tsx)。
|
|
8
8
|
*/
|
|
9
|
-
export type Category = '启程' | '记忆' | '审计' | 'GenUI' | '工具' | '行为' | '隐藏';
|
|
9
|
+
export type Category = '启程' | '记忆' | '审计' | 'GenUI' | '收藏' | '工具' | '行为' | '隐藏';
|
|
10
10
|
export type Rarity = 'common' | 'rare' | 'epic' | 'legendary';
|
|
11
11
|
export interface AchievementDef {
|
|
12
12
|
id: string;
|
|
@@ -21,6 +21,8 @@ export interface AchievementDef {
|
|
|
21
21
|
counter: string;
|
|
22
22
|
target: number;
|
|
23
23
|
};
|
|
24
|
+
/** 所属插件(未装对应插件时成就置灰);null = 引擎内置(工具/行为/隐藏)。 */
|
|
25
|
+
plugin: string | null;
|
|
24
26
|
}
|
|
25
27
|
/** 计数键(leaf scalars only)。 */
|
|
26
28
|
export declare const KEYS: {
|
|
@@ -35,6 +37,7 @@ export declare const KEYS: {
|
|
|
35
37
|
readonly audits: "audits";
|
|
36
38
|
readonly genuiUnlocked: "genuiUnlocked";
|
|
37
39
|
readonly genuiFences: "genuiFences";
|
|
40
|
+
readonly chatRailFavorites: "chatRailFavorites";
|
|
38
41
|
readonly marathonTurns: "marathonTurns";
|
|
39
42
|
readonly midnightTurns: "midnightTurns";
|
|
40
43
|
readonly selfQueries: "selfQueries";
|
|
@@ -49,7 +52,7 @@ export interface AchieveState {
|
|
|
49
52
|
export declare function emptyState(): AchieveState;
|
|
50
53
|
/** 检查新解锁(不看 hidden——hidden 仅影响展示)。 */
|
|
51
54
|
export declare function checkUnlocks(state: AchieveState, unlocked: Record<string, number>): AchievementDef[];
|
|
52
|
-
/** 视图行(client
|
|
53
|
-
export declare function viewOf(achievement: AchievementDef, state: AchieveState, unlocked: Record<string, number
|
|
55
|
+
/** 视图行(client 渲染用;pluginInstalled 由 host 探测注入)。 */
|
|
56
|
+
export declare function viewOf(achievement: AchievementDef, state: AchieveState, unlocked: Record<string, number>, pluginInstalled?: boolean): Record<string, unknown>;
|
|
54
57
|
/** 快照(HTTP / 工具共用)。 */
|
|
55
|
-
export declare function snapshot(state: AchieveState, unlocked: Record<string, number>): Record<string, unknown>;
|
|
58
|
+
export declare function snapshot(state: AchieveState, unlocked: Record<string, number>, installed?: Set<string>): Record<string, unknown>;
|
package/dist/rules.js
CHANGED
|
@@ -19,6 +19,7 @@ export const KEYS = {
|
|
|
19
19
|
audits: 'audits',
|
|
20
20
|
genuiUnlocked: 'genuiUnlocked',
|
|
21
21
|
genuiFences: 'genuiFences',
|
|
22
|
+
chatRailFavorites: 'chatRailFavorites',
|
|
22
23
|
marathonTurns: 'marathonTurns',
|
|
23
24
|
midnightTurns: 'midnightTurns',
|
|
24
25
|
selfQueries: 'selfQueries',
|
|
@@ -27,31 +28,59 @@ export const KEYS = {
|
|
|
27
28
|
/** SSiD 全家桶成就表。 */
|
|
28
29
|
export const ACHIEVEMENTS = [
|
|
29
30
|
// ── 启程 ──
|
|
30
|
-
{ id: 'first-session', name: '启程', desc: '第一个会话开跑。', icon: '🚀', category: '启程', rarity: 'common', threshold: { counter: KEYS.sessions, target: 1 }
|
|
31
|
-
|
|
32
|
-
{ id: 'first-
|
|
31
|
+
{ id: 'first-session', name: '启程', desc: '第一个会话开跑。', icon: '🚀', category: '启程', rarity: 'common', threshold: { counter: KEYS.sessions, target: 1 },
|
|
32
|
+
plugin: null },
|
|
33
|
+
{ id: 'first-turn', name: '初试身手', desc: '完成第一轮对话。', icon: '👋', category: '启程', rarity: 'common', threshold: { counter: KEYS.turns, target: 1 },
|
|
34
|
+
plugin: null },
|
|
35
|
+
{ id: 'first-tool', name: '工具初体验', desc: '第一次调用工具。', icon: '🛠️', category: '启程', rarity: 'common', threshold: { counter: KEYS.tools, target: 1 },
|
|
36
|
+
plugin: null },
|
|
33
37
|
// ── 记忆 ──
|
|
34
|
-
{ id: 'memory-first-save', name: '落笔成忆', desc: '第一次使用记忆工具(memory_*)。', icon: '📝', category: '记忆', rarity: 'common', threshold: { counter: KEYS.memoryTools, target: 1 }
|
|
35
|
-
|
|
36
|
-
{ id: 'memory-
|
|
37
|
-
|
|
38
|
+
{ id: 'memory-first-save', name: '落笔成忆', desc: '第一次使用记忆工具(memory_*)。', icon: '📝', category: '记忆', rarity: 'common', threshold: { counter: KEYS.memoryTools, target: 1 },
|
|
39
|
+
plugin: '@max-null/dsh-memory' },
|
|
40
|
+
{ id: 'memory-10', name: '记忆织工', desc: '累计 10 次记忆工具调用。', icon: '🧵', category: '记忆', rarity: 'rare', threshold: { counter: KEYS.memoryTools, target: 10 },
|
|
41
|
+
plugin: '@max-null/dsh-memory' },
|
|
42
|
+
{ id: 'memory-first-confirm', name: '审核人', desc: '第一次人工确认(memory_confirm)。', icon: '✅', category: '记忆', rarity: 'rare', threshold: { counter: KEYS.memoryConfirmed, target: 1 },
|
|
43
|
+
plugin: '@max-null/dsh-memory' },
|
|
44
|
+
{ id: 'template-user', name: '模板常客', desc: '第一次使用模板库工具(prompt_*)。', icon: '🗂️', category: '记忆', rarity: 'rare', threshold: { counter: KEYS.templateTools, target: 1 },
|
|
45
|
+
plugin: '@max-null/dsh-memory' },
|
|
38
46
|
// ── 审计 ──
|
|
39
|
-
{ id: 'audit-first', name: '上下文审计官', desc: '第一次跑 context_audit。', icon: '🔍', category: '审计', rarity: 'rare', threshold: { counter: KEYS.audits, target: 1 }
|
|
40
|
-
|
|
47
|
+
{ id: 'audit-first', name: '上下文审计官', desc: '第一次跑 context_audit。', icon: '🔍', category: '审计', rarity: 'rare', threshold: { counter: KEYS.audits, target: 1 },
|
|
48
|
+
plugin: 'dsh-context-doctor' },
|
|
49
|
+
{ id: 'audit-10', name: '注审老手', desc: '累计 10 次上下文审计。', icon: '🕵️', category: '审计', rarity: 'epic', threshold: { counter: KEYS.audits, target: 10 },
|
|
50
|
+
plugin: 'dsh-context-doctor' },
|
|
41
51
|
// ── GenUI(client 侧合并 genui 计数)──
|
|
42
|
-
{ id: 'genui-first', name: '第一块面板', desc: 'GenUI 渲染了第一块界面。', icon: '🎨', category: 'GenUI', rarity: 'common', threshold: { counter: KEYS.genuiUnlocked, target: 1 }
|
|
43
|
-
|
|
44
|
-
{ id: 'genui-
|
|
52
|
+
{ id: 'genui-first', name: '第一块面板', desc: 'GenUI 渲染了第一块界面。', icon: '🎨', category: 'GenUI', rarity: 'common', threshold: { counter: KEYS.genuiUnlocked, target: 1 },
|
|
53
|
+
plugin: '@changfenhuang/dsh-genui' },
|
|
54
|
+
{ id: 'genui-5', name: '界面收藏家', desc: 'GenUI 解锁 5 个成就。', icon: '🖼️', category: 'GenUI', rarity: 'rare', threshold: { counter: KEYS.genuiUnlocked, target: 5 },
|
|
55
|
+
plugin: '@changfenhuang/dsh-genui' },
|
|
56
|
+
{ id: 'genui-25', name: '界面编织者', desc: 'GenUI 渲染过 25 个界面。', icon: '🧶', category: 'GenUI', rarity: 'epic', threshold: { counter: KEYS.genuiFences, target: 25 },
|
|
57
|
+
plugin: '@changfenhuang/dsh-genui' },
|
|
58
|
+
// ── 收藏(dsh-chat-rail 消息收藏;client 侧合并 localStorage 计数)──
|
|
59
|
+
{ id: 'fav-first', name: '初见倾心', desc: '收藏第一条消息。', icon: '⭐', category: '收藏', rarity: 'common', threshold: { counter: KEYS.chatRailFavorites, target: 1 },
|
|
60
|
+
plugin: '@max-null/dsh-chat-rail' },
|
|
61
|
+
{ id: 'fav-10', name: '收藏发烧友', desc: '累计收藏 10 条消息。', icon: '📚', category: '收藏', rarity: 'rare', threshold: { counter: KEYS.chatRailFavorites, target: 10 },
|
|
62
|
+
plugin: '@max-null/dsh-chat-rail' },
|
|
63
|
+
{ id: 'fav-30', name: '收藏大师', desc: '累计收藏 30 条消息。', icon: '👑', category: '收藏', rarity: 'epic', threshold: { counter: KEYS.chatRailFavorites, target: 30 },
|
|
64
|
+
plugin: '@max-null/dsh-chat-rail' },
|
|
65
|
+
{ id: 'fav-100', name: '千禧藏家', desc: '累计收藏 100 条消息。', icon: '💎', category: '收藏', rarity: 'legendary', threshold: { counter: KEYS.chatRailFavorites, target: 100 },
|
|
66
|
+
plugin: '@max-null/dsh-chat-rail' },
|
|
45
67
|
// ── 工具 ──
|
|
46
|
-
{ id: 'tool-10', name: '工具新手', desc: '累计调用 10 次工具。', icon: '🔧', category: '工具', rarity: 'common', threshold: { counter: KEYS.tools, target: 10 }
|
|
47
|
-
|
|
48
|
-
{ id: '
|
|
68
|
+
{ id: 'tool-10', name: '工具新手', desc: '累计调用 10 次工具。', icon: '🔧', category: '工具', rarity: 'common', threshold: { counter: KEYS.tools, target: 10 },
|
|
69
|
+
plugin: null },
|
|
70
|
+
{ id: 'tool-50', name: '工具达人', desc: '累计调用 50 次工具。', icon: '⚙️', category: '工具', rarity: 'rare', threshold: { counter: KEYS.tools, target: 50 },
|
|
71
|
+
plugin: null },
|
|
72
|
+
{ id: 'five-tools', name: '多面手', desc: '使用过 5 种不同工具。', icon: '🧰', category: '工具', rarity: 'rare', threshold: { counter: KEYS.toolsUsed, target: 5 },
|
|
73
|
+
plugin: null },
|
|
49
74
|
// ── 行为 ──
|
|
50
|
-
{ id: 'marathon', name: '马拉松', desc: '单回合内调用 10 次工具。', icon: '🏃', category: '行为', rarity: 'rare', hidden: true, threshold: { counter: KEYS.marathonTurns, target: 1 }
|
|
51
|
-
|
|
75
|
+
{ id: 'marathon', name: '马拉松', desc: '单回合内调用 10 次工具。', icon: '🏃', category: '行为', rarity: 'rare', hidden: true, threshold: { counter: KEYS.marathonTurns, target: 1 },
|
|
76
|
+
plugin: null },
|
|
77
|
+
{ id: 'night-owl', name: '夜猫子', desc: '在凌晨 0-5 点发过消息。', icon: '🦉', category: '行为', rarity: 'rare', hidden: true, threshold: { counter: KEYS.midnightTurns, target: 1 },
|
|
78
|
+
plugin: null },
|
|
52
79
|
// ── 隐藏 ──
|
|
53
|
-
{ id: 'self-ref', name: '自我指涉', desc: '用成就工具查询自己。', icon: '🪞', category: '隐藏', rarity: 'rare', hidden: true, threshold: { counter: KEYS.selfQueries, target: 1 }
|
|
54
|
-
|
|
80
|
+
{ id: 'self-ref', name: '自我指涉', desc: '用成就工具查询自己。', icon: '🪞', category: '隐藏', rarity: 'rare', hidden: true, threshold: { counter: KEYS.selfQueries, target: 1 },
|
|
81
|
+
plugin: null },
|
|
82
|
+
{ id: 'billionaire', name: '亿万富翁', desc: '累计消耗一亿 token。', icon: '💰', category: '隐藏', rarity: 'legendary', hidden: true, threshold: { counter: KEYS.tokens, target: 100000000 },
|
|
83
|
+
plugin: null },
|
|
55
84
|
];
|
|
56
85
|
export function emptyState() {
|
|
57
86
|
const state = {};
|
|
@@ -64,8 +93,8 @@ export function checkUnlocks(state, unlocked) {
|
|
|
64
93
|
return ACHIEVEMENTS.filter(achievement => unlocked[achievement.id] === undefined
|
|
65
94
|
&& (state[achievement.threshold.counter] ?? 0) >= achievement.threshold.target);
|
|
66
95
|
}
|
|
67
|
-
/** 视图行(client
|
|
68
|
-
export function viewOf(achievement, state, unlocked) {
|
|
96
|
+
/** 视图行(client 渲染用;pluginInstalled 由 host 探测注入)。 */
|
|
97
|
+
export function viewOf(achievement, state, unlocked, pluginInstalled = true) {
|
|
69
98
|
const current = Math.min(state[achievement.threshold.counter] ?? 0, achievement.threshold.target);
|
|
70
99
|
return {
|
|
71
100
|
id: achievement.id,
|
|
@@ -77,13 +106,15 @@ export function viewOf(achievement, state, unlocked) {
|
|
|
77
106
|
hidden: achievement.hidden === true,
|
|
78
107
|
unlocked: unlocked[achievement.id] !== undefined,
|
|
79
108
|
progress: { current, target: achievement.threshold.target },
|
|
109
|
+
plugin: achievement.plugin,
|
|
110
|
+
pluginInstalled: achievement.plugin === null || pluginInstalled,
|
|
80
111
|
};
|
|
81
112
|
}
|
|
82
113
|
/** 快照(HTTP / 工具共用)。 */
|
|
83
|
-
export function snapshot(state, unlocked) {
|
|
114
|
+
export function snapshot(state, unlocked, installed = new Set()) {
|
|
84
115
|
return {
|
|
85
116
|
total: ACHIEVEMENTS.length,
|
|
86
117
|
unlocked: Object.keys(unlocked).length,
|
|
87
|
-
achievements: ACHIEVEMENTS.map(achievement => viewOf(achievement, state, unlocked)),
|
|
118
|
+
achievements: ACHIEVEMENTS.map(achievement => viewOf(achievement, state, unlocked, achievement.plugin === null || installed.has(achievement.plugin))),
|
|
88
119
|
};
|
|
89
120
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@max-null/dsh-achievements",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "SSiD family progress achievements — listens to session/tool events, folds activity into durable counters, unlocks trophies with toasts and an achievements page; the GenUI category merges the genui plugin's own counter data.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|