dsh-skill-picker 0.4.0 → 0.5.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 +11 -9
- package/lib/client.js +167 -42
- package/lib/client.js.map +2 -2
- package/lib/index.js +147 -10
- package/lib/index.js.map +4 -4
- package/package.json +68 -68
- package/src/client/index.jsx +192 -28
- package/src/index.js +17 -0
- package/src/patch-ui-skill.js +185 -0
package/README.md
CHANGED
|
@@ -17,7 +17,7 @@ DSH Web GUI 的技能选择器:在输入框(composer)工具行右侧加一
|
|
|
17
17
|
|
|
18
18
|
English: A skill picker for the DSH Web GUI — a button in the composer's right tool row opens a searchable list of installed skills; picking one inserts the official `/skill-name` gesture into the draft, so DSH's native user-invocation path loads the skill with your message.
|
|
19
19
|
|
|
20
|
-
当前版本:**v0.
|
|
20
|
+
当前版本:**v0.5.2**(⚡ 面板**置顶分组** + `/` 补全**自动增强补丁** + 拼音搜索)
|
|
21
21
|
|
|
22
22
|
## 为什么用它(vs 官方 `/` 补全)
|
|
23
23
|
|
|
@@ -104,19 +104,21 @@ DSH 的 [dsh-tool-skill](https://github.com/deepseek-ai/deepseek-harness) 在 `a
|
|
|
104
104
|
|
|
105
105
|
**v0.2.0–0.3.4**:插件注册了一个独立的 `/` 候选源(`skill-fuzzy`),与官方 ui-skill 源**并列**——菜单里出现两个技能分组,搜索行为相互独立(冲突风险、视觉重复)。
|
|
106
106
|
|
|
107
|
-
**v0.4.0 起**:**不再注册平行源**。改为给官方 `@deepseek-ai/dsh-client-ui-skill` 包的 candidates **打补丁**——其候选逻辑从 `skill.name.startsWith(query)`(前缀匹配)换成调用插件注入的全局函数 `window.__dshSkillPickerFuzzy`(fuzzysort 模糊 + pinyin-pro 拼音 + 最近/常用排行,与 ⚡
|
|
107
|
+
**v0.4.0 起**:**不再注册平行源**。改为给官方 `@deepseek-ai/dsh-client-ui-skill` 包的 candidates **打补丁**——其候选逻辑从 `skill.name.startsWith(query)`(前缀匹配)换成调用插件注入的全局函数 `window.__dshSkillPickerFuzzy`(fuzzysort 模糊 + pinyin-pro 拼音 + 最近/常用排行,与 ⚡ 面板同一套规则)。**v0.5.1 起,补丁由 host 端每次启动自动应用**(另加 `order: 2→-1`:技能组排在命令组之上),首次修改前自动备份 `.bak`,DSH 升级覆盖官方包后自动重打——**安装插件即生效,无需手动操作**。
|
|
108
108
|
|
|
109
|
-
**效果**:官方「技能」分组**仍是唯一一个 `/` 技能列表**(官方规则全部保留:`userInvocable`
|
|
109
|
+
**效果**:官方「技能」分组**仍是唯一一个 `/` 技能列表**(官方规则全部保留:`userInvocable` 区分、菜单文案、排序基础),只是匹配行为被升级、分组排序被前移;插件不再产生第二列表。
|
|
110
110
|
|
|
111
|
-
|
|
112
|
-
1. 把官方包拷到 `profiles/web/local/dsh-client-ui-skill/`(`lib/client.js` 改 candidates 为 `window.__dshSkillPickerFuzzy` 优先,`startsWith` 兜底)
|
|
113
|
-
2. profile package.json 加依赖 `"@deepseek-ai/dsh-client-ui-skill": "link:C:/Users/<user>/.dsh/profiles/web/local/dsh-client-ui-skill"`
|
|
114
|
-
3. `pnpm install` 后重启 DSH
|
|
115
|
-
|
|
116
|
-
> 官方 DSH 升级后(如 alpha.6),若 ui-skill 包变动:重拷新包覆盖 local 副本、重打补丁即可;插件本身逻辑不变。
|
|
111
|
+
> 手动兜底(旧流程,一般不需要):把官方包拷到 `profiles/web/local/dsh-client-ui-skill/`(`lib/client.js` 改 candidates 为 `window.__dshSkillPickerFuzzy` 优先、`order` 改 `-1`),profile package.json 加依赖 `"@deepseek-ai/dsh-client-ui-skill": "link:C:/Users/<user>/.dsh/profiles/web/local/dsh-client-ui-skill"`,`pnpm install` 后重启 DSH。自动补丁对 local 副本与 npm 安装两种形态都适用,升级后自愈,无需重复手动操作。
|
|
117
112
|
|
|
118
113
|
## 更新日志
|
|
119
114
|
|
|
115
|
+
- **v0.5.4**:**斜杠选技能后 ⚡ 面板同步变「最近使用」**——修复双列表状态不同步:斜杠路径的 usage 记录此前只写 localStorage、面板组件不会刷新(React state 挂载后不再重读),现在 trackPick 写入后会广播 `usage-updated` 事件,⚡ 面板实时重读;面板每次打开时也强制刷新 usage。两条路径的选择现在**双向同步**
|
|
116
|
+
- **v0.5.3**:**官方 `/` 列表与 ⚡ 面板排序同步**——搜索时 fuzzysort 只决定「哪些技能入围」,入围后的显示顺序统一按 ⚡ 面板规则(置顶 → 最近使用 → 常用 → 其余);之前 `/` 列表按匹配分数排序、⚡ 面板按使用记录排序,同一技能在两个列表的相对位置不一致;候选上限 12 → 30
|
|
117
|
+
- **v0.5.2**:**官方 `/` 菜单选技能也记录使用记录**——自愈补丁新增第三个 patch(`pick-tracking`):官方 ui-skill 的 `onPick` 会调用插件的 `window.__dshSkillPickerTrack`,斜杠选中的技能与 ⚡ 面板点选一样进入「最近使用」排序(之前只有按钮路径记账,斜杠路径不记账)
|
|
118
|
+
- **v0.5.1**:**host 端自愈补丁**——每次 DSH 启动自动扫描所有 profile 的官方 `ui-skill` 包(local 副本或 npm 安装),自动应用两个升级补丁(`order: 2→-1` 技能组排到命令组之上;candidates 前缀匹配→模糊+拼音匹配),首次修改前自动备份 `.bak`,幂等且 DSH 升级覆盖官方包后自动重打。安装本插件即可获得官方 `/` 补全的完整增强,无需手动改文件(旧版 v0.4.0 的手动 patch 流程退役)
|
|
119
|
+
- **v0.5.0**:**⚡ 面板置顶分组 + 修复 alpha.5 草稿丢失 bug**
|
|
120
|
+
- 新增**手动置顶**:面板按「📌 置顶 → 🔥 最近使用 → 🗂️ 全部」分组展示(浏览时显示分组标题,搜索时折叠为置顶优先的单一列表);每条技能右侧 📍 按钮一键置顶/取消,置顶顺序固定、持久化到 localStorage;`/` 补全候选同步置顶优先
|
|
121
|
+
- **修复严重 bug**:DSH alpha.5 重构后 `conversation.input.right` 插槽不再提供 `input` 快照,点选技能时误走「事件回调内调用 `useInput` hook」分支(违反 React 规则,抛错被吞 → 草稿读空 → **覆盖用户已输入的内容**)。改为渲染期把最新草稿同步到 ref,点击回调只读 ref——追加永远基于真实草稿
|
|
120
122
|
- **v0.4.0**:**单列表模糊搜索**——不再注册独立 `/` 候选源,改为 patch 官方 ui-skill 的 candidates(模糊+拼音注入,官方列表是唯一来源,无并列列表、无搜索冲突);实测 `/jiyi` → 官方「技能」组 backup-memory 排第一
|
|
121
123
|
- **v0.3.4**:适配 DSH 0.1.2-alpha.5 —— 技能列表改用官方 `remote.skills` RPC(alpha.5 将 rc.x 的 `connection.api.skills` 改名),`/` 补全与 ⚡ 面板统一「官方 RPC → host 扫描兜底」;`dsh.client.inject` 声明 `dsh-client-ui-input-trigger`(alpha.5 装载器只给声明了提供者的插件暴露 `inputTriggers` 服务)。修复 alpha.5 下 `/` 模糊/拼音搜索失效(实测 `/jiyi` → backup-memory)
|
|
122
124
|
- **v0.3.3**:兜底扫描对齐官方全部技能根——补扫 user-agents 层(`~/.agents/skills`,含 `DSH_AGENTS_HOME`),扫描顺序与官方 rank 一致(项目级优先于用户级);走兜底时 ⚡ 面板显示「本地扫描」来源徽标便于排障(对应 issue #5)
|
package/lib/client.js
CHANGED
|
@@ -25766,6 +25766,23 @@ function pinyin(word, options) {
|
|
|
25766
25766
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
25767
25767
|
var inject = ["slots", "connection", "sessions", "inputTriggers"];
|
|
25768
25768
|
var USAGE_KEY = "dsh-skill-picker:usage";
|
|
25769
|
+
var PINNED_KEY = "dsh-skill-picker:pinned";
|
|
25770
|
+
function loadPinned() {
|
|
25771
|
+
try {
|
|
25772
|
+
const raw = localStorage.getItem(PINNED_KEY);
|
|
25773
|
+
if (raw === null) return [];
|
|
25774
|
+
const parsed = JSON.parse(raw);
|
|
25775
|
+
return Array.isArray(parsed) ? parsed.filter((name) => typeof name === "string") : [];
|
|
25776
|
+
} catch {
|
|
25777
|
+
return [];
|
|
25778
|
+
}
|
|
25779
|
+
}
|
|
25780
|
+
function savePinned(pinned) {
|
|
25781
|
+
try {
|
|
25782
|
+
localStorage.setItem(PINNED_KEY, JSON.stringify(pinned));
|
|
25783
|
+
} catch {
|
|
25784
|
+
}
|
|
25785
|
+
}
|
|
25769
25786
|
function loadUsage() {
|
|
25770
25787
|
try {
|
|
25771
25788
|
const raw = localStorage.getItem(USAGE_KEY);
|
|
@@ -25795,6 +25812,23 @@ function rankByUsage(skills, usage) {
|
|
|
25795
25812
|
return a.name.localeCompare(b.name);
|
|
25796
25813
|
});
|
|
25797
25814
|
}
|
|
25815
|
+
function groupByPinned(skills, usage, pinned) {
|
|
25816
|
+
const pinnedSet = new Set(pinned);
|
|
25817
|
+
const pinnedList = pinned.map((name) => skills.find((s) => s.name === name)).filter(Boolean);
|
|
25818
|
+
const ranked = rankByUsage(skills, usage);
|
|
25819
|
+
const recent = [];
|
|
25820
|
+
const rest = [];
|
|
25821
|
+
for (const skill of ranked) {
|
|
25822
|
+
if (pinnedSet.has(skill.name)) continue;
|
|
25823
|
+
if (usage[skill.name] !== void 0) recent.push(skill);
|
|
25824
|
+
else rest.push(skill);
|
|
25825
|
+
}
|
|
25826
|
+
return [
|
|
25827
|
+
{ title: "\u{1F4CC} \u7F6E\u9876", items: pinnedList },
|
|
25828
|
+
{ title: "\u{1F525} \u6700\u8FD1\u4F7F\u7528", items: recent },
|
|
25829
|
+
{ title: "\u{1F5C2}\uFE0F \u5168\u90E8", items: rest }
|
|
25830
|
+
].filter((group) => group.items.length > 0);
|
|
25831
|
+
}
|
|
25798
25832
|
var pinyinCache = /* @__PURE__ */ new Map();
|
|
25799
25833
|
function skillPinyinText(name, description = "") {
|
|
25800
25834
|
const key = `${name}\0${description}`;
|
|
@@ -25942,9 +25976,25 @@ function SkillPickerButton(props) {
|
|
|
25942
25976
|
const [source, setSource] = (0, import_react.useState)(void 0);
|
|
25943
25977
|
const [query, setQuery] = (0, import_react.useState)("");
|
|
25944
25978
|
const [usage, setUsage] = (0, import_react.useState)(() => loadUsage());
|
|
25979
|
+
const [pinned, setPinned] = (0, import_react.useState)(() => loadPinned());
|
|
25945
25980
|
const [active, setActive] = (0, import_react.useState)(0);
|
|
25946
25981
|
const boxRef = (0, import_react.useRef)(null);
|
|
25947
25982
|
const itemRefs = (0, import_react.useRef)([]);
|
|
25983
|
+
(0, import_react.useEffect)(() => {
|
|
25984
|
+
const onUsageUpdated = () => setUsage(loadUsage());
|
|
25985
|
+
window.addEventListener("dsh-skill-picker:usage-updated", onUsageUpdated);
|
|
25986
|
+
return () => window.removeEventListener("dsh-skill-picker:usage-updated", onUsageUpdated);
|
|
25987
|
+
}, []);
|
|
25988
|
+
const draftRef = (0, import_react.useRef)("");
|
|
25989
|
+
if (typeof props.useInput === "function") {
|
|
25990
|
+
try {
|
|
25991
|
+
const state = props.useInput((s) => s);
|
|
25992
|
+
if (state !== void 0 && typeof state.draft === "string") draftRef.current = state.draft;
|
|
25993
|
+
} catch {
|
|
25994
|
+
}
|
|
25995
|
+
} else if (props.input !== void 0 && typeof props.input.draft === "string") {
|
|
25996
|
+
draftRef.current = props.input.draft;
|
|
25997
|
+
}
|
|
25948
25998
|
const load = (0, import_react.useCallback)(async () => {
|
|
25949
25999
|
if (skills !== void 0 || error !== void 0) return;
|
|
25950
26000
|
try {
|
|
@@ -25969,21 +26019,14 @@ function SkillPickerButton(props) {
|
|
|
25969
26019
|
}
|
|
25970
26020
|
}, [skills, error, props.listSkills, props.session, props.cwd]);
|
|
25971
26021
|
const toggle = () => {
|
|
25972
|
-
if (!open)
|
|
26022
|
+
if (!open) {
|
|
26023
|
+
setUsage(loadUsage());
|
|
26024
|
+
void load();
|
|
26025
|
+
}
|
|
25973
26026
|
setOpen(!open);
|
|
25974
26027
|
};
|
|
25975
26028
|
const pick = (name) => {
|
|
25976
|
-
|
|
25977
|
-
try {
|
|
25978
|
-
if (props.input !== void 0 && typeof props.input.draft === "string") {
|
|
25979
|
-
draft = props.input.draft;
|
|
25980
|
-
} else if (typeof props.useInput === "function") {
|
|
25981
|
-
const state = props.useInput((s) => s);
|
|
25982
|
-
if (state !== void 0 && typeof state.draft === "string") draft = state.draft;
|
|
25983
|
-
}
|
|
25984
|
-
} catch (cause) {
|
|
25985
|
-
console.error("[dsh-skill-picker] reading draft failed:", cause);
|
|
25986
|
-
}
|
|
26029
|
+
const draft = draftRef.current;
|
|
25987
26030
|
const separator = draft === "" || draft.endsWith(" ") || draft.endsWith("\n") ? "" : " ";
|
|
25988
26031
|
const next = `${draft}${separator}/${name} `;
|
|
25989
26032
|
try {
|
|
@@ -26001,6 +26044,11 @@ function SkillPickerButton(props) {
|
|
|
26001
26044
|
setOpen(false);
|
|
26002
26045
|
setQuery("");
|
|
26003
26046
|
};
|
|
26047
|
+
const togglePin = (name) => {
|
|
26048
|
+
const next = pinned.includes(name) ? pinned.filter((n) => n !== name) : [...pinned, name];
|
|
26049
|
+
setPinned(next);
|
|
26050
|
+
savePinned(next);
|
|
26051
|
+
};
|
|
26004
26052
|
(0, import_react.useEffect)(() => {
|
|
26005
26053
|
if (!open) return;
|
|
26006
26054
|
const onDown = (event) => {
|
|
@@ -26009,12 +26057,15 @@ function SkillPickerButton(props) {
|
|
|
26009
26057
|
document.addEventListener("mousedown", onDown);
|
|
26010
26058
|
return () => document.removeEventListener("mousedown", onDown);
|
|
26011
26059
|
}, [open]);
|
|
26012
|
-
const
|
|
26013
|
-
const
|
|
26060
|
+
const groups = groupByPinned(skills ?? [], usage, pinned);
|
|
26061
|
+
const flat = groups.flatMap((group) => group.items);
|
|
26062
|
+
const filtered = flat.filter((skill) => {
|
|
26014
26063
|
const q = query.trim().toLowerCase();
|
|
26015
26064
|
if (q === "") return true;
|
|
26016
26065
|
return skill.name.toLowerCase().includes(q) || String(skill.description ?? "").toLowerCase().includes(q) || skillPinyinText(skill.name, skill.description ?? "").toLowerCase().includes(q);
|
|
26017
26066
|
}).slice(0, 60);
|
|
26067
|
+
const showTitles = query.trim() === "" && groups.length > 1;
|
|
26068
|
+
const filteredNames = new Set(filtered.map((skill) => skill.name));
|
|
26018
26069
|
(0, import_react.useEffect)(() => {
|
|
26019
26070
|
setActive(0);
|
|
26020
26071
|
}, [query]);
|
|
@@ -26068,32 +26119,94 @@ function SkillPickerButton(props) {
|
|
|
26068
26119
|
}
|
|
26069
26120
|
),
|
|
26070
26121
|
error !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: statusStyle, children: `\u52A0\u8F7D\u5931\u8D25\uFF1A${error}` }) : skills === void 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: statusStyle, children: "\u52A0\u8F7D\u4E2D\u2026" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
26071
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: listStyle, children: filtered.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: statusStyle, children: "\u6CA1\u6709\u5339\u914D\u7684\u6280\u80FD" }) :
|
|
26072
|
-
|
|
26073
|
-
|
|
26074
|
-
|
|
26075
|
-
|
|
26076
|
-
|
|
26122
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: listStyle, children: filtered.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: statusStyle, children: "\u6CA1\u6709\u5339\u914D\u7684\u6280\u80FD" }) : (() => {
|
|
26123
|
+
let itemIndex = 0;
|
|
26124
|
+
const renderItem = (skill, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
26125
|
+
"button",
|
|
26126
|
+
{
|
|
26127
|
+
type: "button",
|
|
26128
|
+
ref: (el) => {
|
|
26129
|
+
itemRefs.current[index] = el;
|
|
26130
|
+
},
|
|
26131
|
+
onClick: () => pick(skill.name),
|
|
26132
|
+
onMouseEnter: (event) => {
|
|
26133
|
+
setActive(index);
|
|
26134
|
+
event.currentTarget.style.background = "var(--dsw-alias-interactive-bg-hover, rgba(128,128,128,0.12))";
|
|
26135
|
+
},
|
|
26136
|
+
onMouseLeave: (event) => {
|
|
26137
|
+
event.currentTarget.style.background = "transparent";
|
|
26138
|
+
},
|
|
26139
|
+
style: {
|
|
26140
|
+
...itemStyle,
|
|
26141
|
+
flexDirection: "row",
|
|
26142
|
+
alignItems: "center",
|
|
26143
|
+
...index === active ? { background: "var(--dsw-alias-interactive-bg-hover, rgba(128,128,128,0.12))" } : {}
|
|
26144
|
+
},
|
|
26145
|
+
children: [
|
|
26146
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: { display: "flex", flexDirection: "column", alignItems: "flex-start", gap: "2px", flex: "1", minWidth: "0" }, children: [
|
|
26147
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: nameStyle, children: `/${skill.name}` }),
|
|
26148
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: descStyle, children: skill.description ?? "" })
|
|
26149
|
+
] }),
|
|
26150
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
26151
|
+
"span",
|
|
26152
|
+
{
|
|
26153
|
+
role: "button",
|
|
26154
|
+
tabIndex: -1,
|
|
26155
|
+
title: pinned.includes(skill.name) ? "\u53D6\u6D88\u7F6E\u9876" : "\u7F6E\u9876\u5230\u5217\u8868\u9876\u90E8",
|
|
26156
|
+
"aria-label": pinned.includes(skill.name) ? "\u53D6\u6D88\u7F6E\u9876" : "\u7F6E\u9876",
|
|
26157
|
+
onClick: (event) => {
|
|
26158
|
+
event.stopPropagation();
|
|
26159
|
+
togglePin(skill.name);
|
|
26160
|
+
},
|
|
26161
|
+
style: {
|
|
26162
|
+
flex: "none",
|
|
26163
|
+
marginLeft: "6px",
|
|
26164
|
+
padding: "2px 4px",
|
|
26165
|
+
borderRadius: "6px",
|
|
26166
|
+
fontSize: "12px",
|
|
26167
|
+
lineHeight: "16px",
|
|
26168
|
+
cursor: "pointer",
|
|
26169
|
+
color: pinned.includes(skill.name) ? "var(--dsw-alias-label-primary-bluish, #4cc9f0)" : "var(--dsw-alias-label-tertiary, #8a94a6)",
|
|
26170
|
+
opacity: pinned.includes(skill.name) ? 1 : 0.55,
|
|
26171
|
+
userSelect: "none"
|
|
26172
|
+
},
|
|
26173
|
+
children: pinned.includes(skill.name) ? "\u{1F4CC}" : "\u{1F4CD}"
|
|
26174
|
+
}
|
|
26175
|
+
)
|
|
26176
|
+
]
|
|
26077
26177
|
},
|
|
26078
|
-
|
|
26079
|
-
|
|
26080
|
-
|
|
26081
|
-
|
|
26082
|
-
|
|
26083
|
-
|
|
26084
|
-
|
|
26085
|
-
|
|
26086
|
-
|
|
26087
|
-
|
|
26088
|
-
|
|
26089
|
-
|
|
26090
|
-
|
|
26091
|
-
|
|
26092
|
-
|
|
26093
|
-
|
|
26094
|
-
|
|
26095
|
-
|
|
26096
|
-
|
|
26178
|
+
skill.name
|
|
26179
|
+
);
|
|
26180
|
+
if (!showTitles) {
|
|
26181
|
+
return filtered.map((skill) => renderItem(skill, itemIndex++));
|
|
26182
|
+
}
|
|
26183
|
+
return groups.map((group) => {
|
|
26184
|
+
const items = group.items.filter((skill) => filteredNames.has(skill.name));
|
|
26185
|
+
if (items.length === 0) return null;
|
|
26186
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react.Fragment, { children: [
|
|
26187
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
26188
|
+
"div",
|
|
26189
|
+
{
|
|
26190
|
+
style: {
|
|
26191
|
+
display: "flex",
|
|
26192
|
+
alignItems: "center",
|
|
26193
|
+
justifyContent: "space-between",
|
|
26194
|
+
padding: "6px 10px 2px",
|
|
26195
|
+
color: "var(--dsw-alias-label-tertiary, #8a94a6)",
|
|
26196
|
+
fontSize: "11px",
|
|
26197
|
+
fontWeight: 600,
|
|
26198
|
+
letterSpacing: "0.04em"
|
|
26199
|
+
},
|
|
26200
|
+
children: [
|
|
26201
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: group.title }),
|
|
26202
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { opacity: 0.7 }, children: items.length })
|
|
26203
|
+
]
|
|
26204
|
+
}
|
|
26205
|
+
),
|
|
26206
|
+
items.map((skill) => renderItem(skill, itemIndex++))
|
|
26207
|
+
] }, group.title);
|
|
26208
|
+
});
|
|
26209
|
+
})() }),
|
|
26097
26210
|
source === "host" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: sourceBadgeStyle, title: "\u5B98\u65B9\u6280\u80FD API \u4E0D\u53EF\u7528\uFF0C\u5217\u8868\u6765\u81EA\u672C\u5730\u76EE\u5F55\u626B\u63CF\uFF08\u4E0E\u5B98\u65B9 / \u8865\u5168\u540C\u6E90\uFF09", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: sourceBadgeTextStyle, children: "\u672C\u5730\u626B\u63CF" }) })
|
|
26098
26211
|
] })
|
|
26099
26212
|
] })
|
|
@@ -26142,23 +26255,35 @@ function apply(ctx) {
|
|
|
26142
26255
|
}, "dsh-skill-picker: composer input slot");
|
|
26143
26256
|
ctx.effect(() => {
|
|
26144
26257
|
const fuzzyMatch = (skills, query = "") => {
|
|
26145
|
-
const ordered =
|
|
26258
|
+
const ordered = groupByPinned(skills, loadUsage(), loadPinned()).flatMap((group) => group.items);
|
|
26146
26259
|
const q = String(query).trim().toLowerCase();
|
|
26147
26260
|
if (q === "") return ordered;
|
|
26261
|
+
const rankByName = new Map(ordered.map((skill, index) => [skill.name, index]));
|
|
26148
26262
|
const targets = ordered.map((s) => ({
|
|
26149
26263
|
s,
|
|
26150
26264
|
search: `${s.name} ${s.description ?? ""} ${skillPinyinText(s.name, s.description ?? "")}`
|
|
26151
26265
|
}));
|
|
26152
26266
|
const results = fuzzysort_default.go(q, targets, {
|
|
26153
26267
|
key: "search",
|
|
26154
|
-
limit:
|
|
26268
|
+
limit: 30,
|
|
26155
26269
|
threshold: -1e4
|
|
26156
26270
|
});
|
|
26157
|
-
return results.filter((r) => r.score > 0).map((r) => r.obj.s);
|
|
26271
|
+
return results.filter((r) => r.score > 0).map((r) => r.obj.s).sort((a, b) => (rankByName.get(a.name) ?? 0) - (rankByName.get(b.name) ?? 0));
|
|
26158
26272
|
};
|
|
26159
26273
|
window.__dshSkillPickerFuzzy = fuzzyMatch;
|
|
26274
|
+
const trackPick = (name) => {
|
|
26275
|
+
const usage = loadUsage();
|
|
26276
|
+
const nextUsage = { ...usage, [name]: { count: (usage[name]?.count ?? 0) + 1, lastUsed: Date.now() } };
|
|
26277
|
+
saveUsage(nextUsage);
|
|
26278
|
+
try {
|
|
26279
|
+
window.dispatchEvent(new CustomEvent("dsh-skill-picker:usage-updated"));
|
|
26280
|
+
} catch {
|
|
26281
|
+
}
|
|
26282
|
+
};
|
|
26283
|
+
window.__dshSkillPickerTrack = trackPick;
|
|
26160
26284
|
return () => {
|
|
26161
26285
|
if (window.__dshSkillPickerFuzzy === fuzzyMatch) delete window.__dshSkillPickerFuzzy;
|
|
26286
|
+
if (window.__dshSkillPickerTrack === trackPick) delete window.__dshSkillPickerTrack;
|
|
26162
26287
|
};
|
|
26163
26288
|
}, "dsh-skill-picker: fuzzy matcher for official / source");
|
|
26164
26289
|
}
|