dsh-skill-hub 0.3.5 → 0.3.6
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/lib/client.js +333 -138
- package/lib/client.js.map +1 -1
- package/package.json +29 -28
- package/src/client/index.tsx +9 -5
- package/src/client/panel/panel.module.css +2 -2
- package/src/client/slash-dots.tsx +3 -2
package/lib/client.js
CHANGED
|
@@ -846,6 +846,333 @@ window.__ModuleLoader__.load({
|
|
|
846
846
|
};
|
|
847
847
|
}
|
|
848
848
|
//#endregion
|
|
849
|
+
//#region src/client/grouping.ts
|
|
850
|
+
/**
|
|
851
|
+
* Derive a group switch view from its member names and the set of currently
|
|
852
|
+
* enabled skill names (catalog.skills). Members not in the enabled set count
|
|
853
|
+
* as disabled (catalog.disabled or absent read-only rows).
|
|
854
|
+
*/
|
|
855
|
+
function groupSwitchView(members, enabledNames) {
|
|
856
|
+
const enabled = [];
|
|
857
|
+
const disabled = [];
|
|
858
|
+
for (const name of members) if (enabledNames.has(name)) enabled.push(name);
|
|
859
|
+
else disabled.push(name);
|
|
860
|
+
return {
|
|
861
|
+
state: disabled.length === 0 ? "on" : enabled.length === 0 ? "off" : "mixed",
|
|
862
|
+
enabled,
|
|
863
|
+
disabled
|
|
864
|
+
};
|
|
865
|
+
}
|
|
866
|
+
/** Names of every group a skill belongs to (tags + collections). */
|
|
867
|
+
function groupNamesOf(name, tags, collections) {
|
|
868
|
+
const names = [];
|
|
869
|
+
for (const tag of tags) if (tag.skillNames.includes(name)) names.push(tag.name);
|
|
870
|
+
for (const collection of collections) if (collection.skillNames.includes(name)) names.push(collection.name);
|
|
871
|
+
return names;
|
|
872
|
+
}
|
|
873
|
+
/**
|
|
874
|
+
* Members of a group that are currently enabled AND also belong to at least
|
|
875
|
+
* one other group — the set a "close" action must ask about.
|
|
876
|
+
*/
|
|
877
|
+
function conflictsOnClose(members, enabledNames, otherGroups) {
|
|
878
|
+
return members.filter((name) => {
|
|
879
|
+
if (!enabledNames.has(name)) return false;
|
|
880
|
+
return otherGroups.some((group) => group.members.includes(name));
|
|
881
|
+
});
|
|
882
|
+
}
|
|
883
|
+
/** Origin-repo filter value: skills with no source record (private skills). */
|
|
884
|
+
const PRIVATE_SOURCE = "private";
|
|
885
|
+
/**
|
|
886
|
+
* Apply the origin filter ('all' or a specific origin repo; skills without a
|
|
887
|
+
* source record count as PRIVATE_SOURCE). The origins map is the store's
|
|
888
|
+
* skillName → repo derivation, so filtering follows the tracked source
|
|
889
|
+
* records instead of the filesystem root a skill happens to live under.
|
|
890
|
+
* 项目级技能(有 workspace 归属)永远不算「个人」。
|
|
891
|
+
*/
|
|
892
|
+
function filterBySource(skills, source, origins) {
|
|
893
|
+
if (source === "all") return [...skills];
|
|
894
|
+
return skills.filter((skill) => {
|
|
895
|
+
if (isProjectSource(skill.source)) return false;
|
|
896
|
+
return (origins[skill.name] ?? "private") === source;
|
|
897
|
+
});
|
|
898
|
+
}
|
|
899
|
+
/**
|
|
900
|
+
* Sort a skill list in place-safe copy order: name ascending, added
|
|
901
|
+
* descending (newest first, unknown addedAt last), or uses descending
|
|
902
|
+
* (most-called first). Unknown values always trail.
|
|
903
|
+
*/
|
|
904
|
+
function sortSkills(skills, key, getUses) {
|
|
905
|
+
const list = [...skills];
|
|
906
|
+
if (key === "name") list.sort((a, b) => a.name.localeCompare(b.name));
|
|
907
|
+
else if (key === "added") list.sort((a, b) => (b.addedAt ?? -Infinity) - (a.addedAt ?? -Infinity));
|
|
908
|
+
else if (key === "uses") list.sort((a, b) => (getUses?.(b.name) ?? 0) - (getUses?.(a.name) ?? 0));
|
|
909
|
+
return list;
|
|
910
|
+
}
|
|
911
|
+
/** Format an epoch-ms timestamp as a short relative time bucket. */
|
|
912
|
+
function formatRelativeTime(ms, now = Date.now()) {
|
|
913
|
+
const diff = Math.max(0, now - ms);
|
|
914
|
+
const minutes = Math.floor(diff / 6e4);
|
|
915
|
+
if (minutes < 1) return { key: "time.justNow" };
|
|
916
|
+
if (minutes < 60) return {
|
|
917
|
+
key: "time.minutesAgo",
|
|
918
|
+
value: minutes
|
|
919
|
+
};
|
|
920
|
+
const hours = Math.floor(minutes / 60);
|
|
921
|
+
if (hours < 24) return {
|
|
922
|
+
key: "time.hoursAgo",
|
|
923
|
+
value: hours
|
|
924
|
+
};
|
|
925
|
+
const days = Math.floor(hours / 24);
|
|
926
|
+
if (days < 7) return {
|
|
927
|
+
key: "time.daysAgo",
|
|
928
|
+
value: days
|
|
929
|
+
};
|
|
930
|
+
return {
|
|
931
|
+
key: "time.weeksAgo",
|
|
932
|
+
value: Math.floor(days / 7)
|
|
933
|
+
};
|
|
934
|
+
}
|
|
935
|
+
//#endregion
|
|
936
|
+
//#region src/client/helpers.ts
|
|
937
|
+
/**
|
|
938
|
+
* Shared panel helpers: the active-dictionary pick (document-language
|
|
939
|
+
* based, family precedent) plus a small error-message extractor.
|
|
940
|
+
*/
|
|
941
|
+
/** Active dictionary, picked by the document language at call time. */
|
|
942
|
+
function dictionary() {
|
|
943
|
+
return (typeof document !== "undefined" ? document.documentElement.lang : "zh").toLowerCase().startsWith("en") ? { ...en } : { ...zh };
|
|
944
|
+
}
|
|
945
|
+
/** Translate a key with optional {name} template params (current language). */
|
|
946
|
+
function tt(key, values) {
|
|
947
|
+
return t(dictionary(), key, values);
|
|
948
|
+
}
|
|
949
|
+
/** Human-readable error text from an unknown thrown value. */
|
|
950
|
+
function errorMessage(error) {
|
|
951
|
+
if (error instanceof Error) return error.message;
|
|
952
|
+
return String(error);
|
|
953
|
+
}
|
|
954
|
+
//#endregion
|
|
955
|
+
//#region src/client/panel/format.ts
|
|
956
|
+
/** Model-invocable dot color default. Single source for the TS side; the
|
|
957
|
+
* panel's CSS mirrors it via --hub-model (panel.module.css). */
|
|
958
|
+
const DEFAULT_DOT_MODEL_COLOR = "#2f81f7";
|
|
959
|
+
/** User-invocable dot color default. Single source for the TS side; the
|
|
960
|
+
* panel's CSS mirrors it via --hub-user (panel.module.css). */
|
|
961
|
+
const DEFAULT_DOT_USER_COLOR = "#3fb950";
|
|
962
|
+
/** Dot inline style from the user-chosen color (undefined keeps the CSS default). */
|
|
963
|
+
function dotStyle(color) {
|
|
964
|
+
if (color === void 0) return void 0;
|
|
965
|
+
return {
|
|
966
|
+
background: color,
|
|
967
|
+
borderColor: color
|
|
968
|
+
};
|
|
969
|
+
}
|
|
970
|
+
/** Localized relative-time text for a Unix-ms timestamp. */
|
|
971
|
+
function relativeTimeText(ms) {
|
|
972
|
+
const rt = formatRelativeTime(ms);
|
|
973
|
+
return tt(rt.key, rt.value !== void 0 ? { value: rt.value } : void 0);
|
|
974
|
+
}
|
|
975
|
+
/** Localized absolute time for the detail meta (e.g. "2026/8/16 11:00:00"). */
|
|
976
|
+
function formatDateTime(ms) {
|
|
977
|
+
return new Date(ms).toLocaleString();
|
|
978
|
+
}
|
|
979
|
+
/** Short form of a commit SHA for display. */
|
|
980
|
+
function shortSha(sha) {
|
|
981
|
+
return sha.length > 7 ? sha.slice(0, 7) : sha;
|
|
982
|
+
}
|
|
983
|
+
//#endregion
|
|
984
|
+
//#region src/client/slash-dots.tsx
|
|
985
|
+
/** The core plugin's skill source identity on the '/' trigger. */
|
|
986
|
+
const SKILL_SOURCE = {
|
|
987
|
+
trigger: "/",
|
|
988
|
+
name: "skill"
|
|
989
|
+
};
|
|
990
|
+
/** How long one catalog-derived modelInvocable map stays hot before refresh. */
|
|
991
|
+
const MODEL_TTL_MS = 6e4;
|
|
992
|
+
/**
|
|
993
|
+
* Find the core `/skill` source through the runtime registry, or undefined.
|
|
994
|
+
* The lookup never throws: a missing/reshaped registry just means no dots. Exported
|
|
995
|
+
* for unit tests; the apply path only calls it indirectly through setupSkillSlashDots.
|
|
996
|
+
* @param service - the ctx.inputTriggers service face.
|
|
997
|
+
* @returns the registered skill source, or undefined.
|
|
998
|
+
*/
|
|
999
|
+
function findSkillSource(service) {
|
|
1000
|
+
const live = service.live;
|
|
1001
|
+
if (live?.sources === void 0) return void 0;
|
|
1002
|
+
return live.sources.find((source) => source.trigger === SKILL_SOURCE.trigger && source.name === SKILL_SOURCE.name);
|
|
1003
|
+
}
|
|
1004
|
+
/** One catalog-derived name → modelInvocable snapshot with a load timestamp. */
|
|
1005
|
+
let modelCache;
|
|
1006
|
+
/**
|
|
1007
|
+
* Clear the modelInvocable cache. Called on connection/reset so a fresh
|
|
1008
|
+
* catalog wins after reconnect; exported for deterministic unit tests.
|
|
1009
|
+
*/
|
|
1010
|
+
function resetModelCache() {
|
|
1011
|
+
modelCache = void 0;
|
|
1012
|
+
}
|
|
1013
|
+
/**
|
|
1014
|
+
* Resolve name → modelInvocable from the hub's catalog, cached for
|
|
1015
|
+
* MODEL_TTL_MS. A failed load caches the failure briefly so a downed route
|
|
1016
|
+
* doesn't hammer the host on every keystroke; the returned map is empty then
|
|
1017
|
+
* (callers fall back to the model dot for unknown names).
|
|
1018
|
+
* @param api - the hub browser API.
|
|
1019
|
+
* @returns name → whether the model may call the skill.
|
|
1020
|
+
*/
|
|
1021
|
+
async function modelInvocableMap(api) {
|
|
1022
|
+
const now = Date.now();
|
|
1023
|
+
const cached = modelCache;
|
|
1024
|
+
if (cached !== void 0 && now - cached.at < MODEL_TTL_MS) return "failed" in cached ? /* @__PURE__ */ new Map() : cached.map;
|
|
1025
|
+
try {
|
|
1026
|
+
const catalog = await api.catalog();
|
|
1027
|
+
const map = new Map(catalog.skills.map((skill) => [skill.name, skill.invocation.modelInvocable]));
|
|
1028
|
+
modelCache = {
|
|
1029
|
+
at: now,
|
|
1030
|
+
map
|
|
1031
|
+
};
|
|
1032
|
+
return map;
|
|
1033
|
+
} catch (error) {
|
|
1034
|
+
console.error("[dsh-skill-hub] slash dot color lookup failed:", error);
|
|
1035
|
+
modelCache = {
|
|
1036
|
+
at: now,
|
|
1037
|
+
failed: true
|
|
1038
|
+
};
|
|
1039
|
+
return /* @__PURE__ */ new Map();
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
/**
|
|
1043
|
+
* One menu-row dot element. Inline span so it needs no CSS module; the
|
|
1044
|
+
* candidate menu centers it inside its 16×16 leading icon slot.
|
|
1045
|
+
* @param color - the dot's background color.
|
|
1046
|
+
* @returns a React node (memory-only, never crosses the Host boundary).
|
|
1047
|
+
*/
|
|
1048
|
+
function dotIcon(color) {
|
|
1049
|
+
return (0, react.createElement)("span", {
|
|
1050
|
+
"aria-hidden": true,
|
|
1051
|
+
style: {
|
|
1052
|
+
display: "inline-block",
|
|
1053
|
+
width: 6,
|
|
1054
|
+
height: 6,
|
|
1055
|
+
borderRadius: 3,
|
|
1056
|
+
background: color,
|
|
1057
|
+
flex: "none"
|
|
1058
|
+
}
|
|
1059
|
+
});
|
|
1060
|
+
}
|
|
1061
|
+
/**
|
|
1062
|
+
* Wrap the core skill source so every menu row carries the invocation dot.
|
|
1063
|
+
* Exported for unit tests; production wiring goes through setupSkillSlashDots.
|
|
1064
|
+
* @param source - the registered `/skill` source.
|
|
1065
|
+
* @param api - hub browser API for the modelInvocable lookup.
|
|
1066
|
+
* @param scope - hub settings scope for the dot colors.
|
|
1067
|
+
* @returns a disposer restoring the original candidates.
|
|
1068
|
+
*/
|
|
1069
|
+
/**
|
|
1070
|
+
* DOM 兜底:在 alpha.2 新版 MenuView(icon 仅枚举)下通过直接操作
|
|
1071
|
+
* 已渲染的 `[role="option"]` 列表注入彩色点,绕过 `icon` 限制。
|
|
1072
|
+
* 旧版仍走 `icon` 注入,此处仅为新版。
|
|
1073
|
+
*/
|
|
1074
|
+
function injectDotsViaDOM(modelByName, modelColor, userColor) {
|
|
1075
|
+
if (typeof document === "undefined" || typeof requestAnimationFrame === "undefined") return;
|
|
1076
|
+
const run = () => {
|
|
1077
|
+
const listbox = document.querySelector("[class*=\"_3e4SsG_menu\"]")?.querySelector("[role=\"listbox\"]") ?? document.querySelector("[role=\"listbox\"]");
|
|
1078
|
+
if (listbox === null) return;
|
|
1079
|
+
const options = listbox.querySelectorAll("[role=\"option\"]");
|
|
1080
|
+
for (const opt of options) {
|
|
1081
|
+
if (opt.querySelector("[data-skill-dot]") !== null) continue;
|
|
1082
|
+
const nameEl = opt.querySelector("[class*=\"itemName\"]");
|
|
1083
|
+
if (nameEl === null) continue;
|
|
1084
|
+
const name = (nameEl.textContent ?? "").trim();
|
|
1085
|
+
if (name.length === 0) continue;
|
|
1086
|
+
if (!modelByName.has(name)) continue;
|
|
1087
|
+
const color = modelByName.get(name) ?? true ? modelColor : userColor;
|
|
1088
|
+
const wrapper = document.createElement("span");
|
|
1089
|
+
wrapper.setAttribute("data-skill-dot", "");
|
|
1090
|
+
wrapper.setAttribute("aria-hidden", "true");
|
|
1091
|
+
wrapper.style.width = "16px";
|
|
1092
|
+
wrapper.style.height = "16px";
|
|
1093
|
+
wrapper.style.display = "inline-flex";
|
|
1094
|
+
wrapper.style.justifyContent = "center";
|
|
1095
|
+
wrapper.style.alignItems = "center";
|
|
1096
|
+
wrapper.style.flex = "none";
|
|
1097
|
+
const dot = document.createElement("span");
|
|
1098
|
+
dot.style.display = "inline-block";
|
|
1099
|
+
dot.style.width = "6px";
|
|
1100
|
+
dot.style.height = "6px";
|
|
1101
|
+
dot.style.borderRadius = "3px";
|
|
1102
|
+
dot.style.background = color;
|
|
1103
|
+
dot.style.flex = "none";
|
|
1104
|
+
wrapper.appendChild(dot);
|
|
1105
|
+
nameEl.parentElement?.insertBefore(wrapper, nameEl);
|
|
1106
|
+
}
|
|
1107
|
+
};
|
|
1108
|
+
requestAnimationFrame(() => setTimeout(run, 0));
|
|
1109
|
+
}
|
|
1110
|
+
function wrapSkillSource(source, api, scope) {
|
|
1111
|
+
const original = source.candidates;
|
|
1112
|
+
source.candidates = async (session, req) => {
|
|
1113
|
+
const items = await original(session, req);
|
|
1114
|
+
if (req.signal.aborted) return items;
|
|
1115
|
+
const isNewHost = req !== null && typeof req === "object" && "drilled" in req;
|
|
1116
|
+
const modelByName = await modelInvocableMap(api);
|
|
1117
|
+
if (req.signal.aborted) return items;
|
|
1118
|
+
const snapshot = scope.getSnapshot();
|
|
1119
|
+
const modelColor = snapshot.value?.dotModelColor ?? "#2f81f7";
|
|
1120
|
+
const userColor = snapshot.value?.dotUserColor ?? "#3fb950";
|
|
1121
|
+
if (isNewHost) {
|
|
1122
|
+
injectDotsViaDOM(modelByName, modelColor, userColor);
|
|
1123
|
+
return items;
|
|
1124
|
+
}
|
|
1125
|
+
return items.map((item) => ({
|
|
1126
|
+
...item,
|
|
1127
|
+
icon: dotIcon(modelByName.get(item.name) ?? true ? modelColor : userColor)
|
|
1128
|
+
}));
|
|
1129
|
+
};
|
|
1130
|
+
return () => {
|
|
1131
|
+
source.candidates = original;
|
|
1132
|
+
};
|
|
1133
|
+
}
|
|
1134
|
+
/**
|
|
1135
|
+
* Mount the slash-menu dots on the registered `/skill` source. Idempotent and
|
|
1136
|
+
* defensive: if the core source isn't registered yet (or the registry shape
|
|
1137
|
+
* changes), it retries briefly and then gives up silently — the chat keeps
|
|
1138
|
+
* working, it simply shows no dots. The returned disposer restores the
|
|
1139
|
+
* original candidates and clears the model cache.
|
|
1140
|
+
* @param ctx - the client root context (inputTriggers + events).
|
|
1141
|
+
* @param api - hub browser API.
|
|
1142
|
+
* @param scope - hub settings scope for dot colors.
|
|
1143
|
+
* @returns a cleanup function for `ctx.effect`.
|
|
1144
|
+
*/
|
|
1145
|
+
function setupSkillSlashDots(ctx, api, scope) {
|
|
1146
|
+
const inputTriggers = ctx.get("inputTriggers");
|
|
1147
|
+
if (inputTriggers === void 0) return () => {};
|
|
1148
|
+
let disposed = false;
|
|
1149
|
+
let restore;
|
|
1150
|
+
let attempts = 0;
|
|
1151
|
+
const attempt = () => {
|
|
1152
|
+
if (disposed) return;
|
|
1153
|
+
const source = findSkillSource(inputTriggers);
|
|
1154
|
+
if (source === void 0) {
|
|
1155
|
+
if (attempts < 10) {
|
|
1156
|
+
attempts += 1;
|
|
1157
|
+
setTimeout(attempt, 100);
|
|
1158
|
+
}
|
|
1159
|
+
return;
|
|
1160
|
+
}
|
|
1161
|
+
restore = wrapSkillSource(source, api, scope);
|
|
1162
|
+
};
|
|
1163
|
+
attempt();
|
|
1164
|
+
const clearCache = () => {
|
|
1165
|
+
resetModelCache();
|
|
1166
|
+
};
|
|
1167
|
+
const offReset = ctx.on("connection/reset", clearCache);
|
|
1168
|
+
return () => {
|
|
1169
|
+
disposed = true;
|
|
1170
|
+
offReset();
|
|
1171
|
+
restore?.();
|
|
1172
|
+
restore = void 0;
|
|
1173
|
+
};
|
|
1174
|
+
}
|
|
1175
|
+
//#endregion
|
|
849
1176
|
//#region src/client/icons.tsx
|
|
850
1177
|
/** ic_ds_chevron_down_outline_14 */
|
|
851
1178
|
function IconChevronDownOutline14({ size = 14, className }) {
|
|
@@ -2113,141 +2440,6 @@ window.__ModuleLoader__.load({
|
|
|
2113
2440
|
}
|
|
2114
2441
|
};
|
|
2115
2442
|
//#endregion
|
|
2116
|
-
//#region src/client/grouping.ts
|
|
2117
|
-
/**
|
|
2118
|
-
* Derive a group switch view from its member names and the set of currently
|
|
2119
|
-
* enabled skill names (catalog.skills). Members not in the enabled set count
|
|
2120
|
-
* as disabled (catalog.disabled or absent read-only rows).
|
|
2121
|
-
*/
|
|
2122
|
-
function groupSwitchView(members, enabledNames) {
|
|
2123
|
-
const enabled = [];
|
|
2124
|
-
const disabled = [];
|
|
2125
|
-
for (const name of members) if (enabledNames.has(name)) enabled.push(name);
|
|
2126
|
-
else disabled.push(name);
|
|
2127
|
-
return {
|
|
2128
|
-
state: disabled.length === 0 ? "on" : enabled.length === 0 ? "off" : "mixed",
|
|
2129
|
-
enabled,
|
|
2130
|
-
disabled
|
|
2131
|
-
};
|
|
2132
|
-
}
|
|
2133
|
-
/** Names of every group a skill belongs to (tags + collections). */
|
|
2134
|
-
function groupNamesOf(name, tags, collections) {
|
|
2135
|
-
const names = [];
|
|
2136
|
-
for (const tag of tags) if (tag.skillNames.includes(name)) names.push(tag.name);
|
|
2137
|
-
for (const collection of collections) if (collection.skillNames.includes(name)) names.push(collection.name);
|
|
2138
|
-
return names;
|
|
2139
|
-
}
|
|
2140
|
-
/**
|
|
2141
|
-
* Members of a group that are currently enabled AND also belong to at least
|
|
2142
|
-
* one other group — the set a "close" action must ask about.
|
|
2143
|
-
*/
|
|
2144
|
-
function conflictsOnClose(members, enabledNames, otherGroups) {
|
|
2145
|
-
return members.filter((name) => {
|
|
2146
|
-
if (!enabledNames.has(name)) return false;
|
|
2147
|
-
return otherGroups.some((group) => group.members.includes(name));
|
|
2148
|
-
});
|
|
2149
|
-
}
|
|
2150
|
-
/** Origin-repo filter value: skills with no source record (private skills). */
|
|
2151
|
-
const PRIVATE_SOURCE = "private";
|
|
2152
|
-
/**
|
|
2153
|
-
* Apply the origin filter ('all' or a specific origin repo; skills without a
|
|
2154
|
-
* source record count as PRIVATE_SOURCE). The origins map is the store's
|
|
2155
|
-
* skillName → repo derivation, so filtering follows the tracked source
|
|
2156
|
-
* records instead of the filesystem root a skill happens to live under.
|
|
2157
|
-
* 项目级技能(有 workspace 归属)永远不算「个人」。
|
|
2158
|
-
*/
|
|
2159
|
-
function filterBySource(skills, source, origins) {
|
|
2160
|
-
if (source === "all") return [...skills];
|
|
2161
|
-
return skills.filter((skill) => {
|
|
2162
|
-
if (isProjectSource(skill.source)) return false;
|
|
2163
|
-
return (origins[skill.name] ?? "private") === source;
|
|
2164
|
-
});
|
|
2165
|
-
}
|
|
2166
|
-
/**
|
|
2167
|
-
* Sort a skill list in place-safe copy order: name ascending, added
|
|
2168
|
-
* descending (newest first, unknown addedAt last), or uses descending
|
|
2169
|
-
* (most-called first). Unknown values always trail.
|
|
2170
|
-
*/
|
|
2171
|
-
function sortSkills(skills, key, getUses) {
|
|
2172
|
-
const list = [...skills];
|
|
2173
|
-
if (key === "name") list.sort((a, b) => a.name.localeCompare(b.name));
|
|
2174
|
-
else if (key === "added") list.sort((a, b) => (b.addedAt ?? -Infinity) - (a.addedAt ?? -Infinity));
|
|
2175
|
-
else if (key === "uses") list.sort((a, b) => (getUses?.(b.name) ?? 0) - (getUses?.(a.name) ?? 0));
|
|
2176
|
-
return list;
|
|
2177
|
-
}
|
|
2178
|
-
/** Format an epoch-ms timestamp as a short relative time bucket. */
|
|
2179
|
-
function formatRelativeTime(ms, now = Date.now()) {
|
|
2180
|
-
const diff = Math.max(0, now - ms);
|
|
2181
|
-
const minutes = Math.floor(diff / 6e4);
|
|
2182
|
-
if (minutes < 1) return { key: "time.justNow" };
|
|
2183
|
-
if (minutes < 60) return {
|
|
2184
|
-
key: "time.minutesAgo",
|
|
2185
|
-
value: minutes
|
|
2186
|
-
};
|
|
2187
|
-
const hours = Math.floor(minutes / 60);
|
|
2188
|
-
if (hours < 24) return {
|
|
2189
|
-
key: "time.hoursAgo",
|
|
2190
|
-
value: hours
|
|
2191
|
-
};
|
|
2192
|
-
const days = Math.floor(hours / 24);
|
|
2193
|
-
if (days < 7) return {
|
|
2194
|
-
key: "time.daysAgo",
|
|
2195
|
-
value: days
|
|
2196
|
-
};
|
|
2197
|
-
return {
|
|
2198
|
-
key: "time.weeksAgo",
|
|
2199
|
-
value: Math.floor(days / 7)
|
|
2200
|
-
};
|
|
2201
|
-
}
|
|
2202
|
-
//#endregion
|
|
2203
|
-
//#region src/client/helpers.ts
|
|
2204
|
-
/**
|
|
2205
|
-
* Shared panel helpers: the active-dictionary pick (document-language
|
|
2206
|
-
* based, family precedent) plus a small error-message extractor.
|
|
2207
|
-
*/
|
|
2208
|
-
/** Active dictionary, picked by the document language at call time. */
|
|
2209
|
-
function dictionary() {
|
|
2210
|
-
return (typeof document !== "undefined" ? document.documentElement.lang : "zh").toLowerCase().startsWith("en") ? { ...en } : { ...zh };
|
|
2211
|
-
}
|
|
2212
|
-
/** Translate a key with optional {name} template params (current language). */
|
|
2213
|
-
function tt(key, values) {
|
|
2214
|
-
return t(dictionary(), key, values);
|
|
2215
|
-
}
|
|
2216
|
-
/** Human-readable error text from an unknown thrown value. */
|
|
2217
|
-
function errorMessage(error) {
|
|
2218
|
-
if (error instanceof Error) return error.message;
|
|
2219
|
-
return String(error);
|
|
2220
|
-
}
|
|
2221
|
-
//#endregion
|
|
2222
|
-
//#region src/client/panel/format.ts
|
|
2223
|
-
/** Model-invocable dot color default. Single source for the TS side; the
|
|
2224
|
-
* panel's CSS mirrors it via --hub-model (panel.module.css). */
|
|
2225
|
-
const DEFAULT_DOT_MODEL_COLOR = "#2f81f7";
|
|
2226
|
-
/** User-invocable dot color default. Single source for the TS side; the
|
|
2227
|
-
* panel's CSS mirrors it via --hub-user (panel.module.css). */
|
|
2228
|
-
const DEFAULT_DOT_USER_COLOR = "#3fb950";
|
|
2229
|
-
/** Dot inline style from the user-chosen color (undefined keeps the CSS default). */
|
|
2230
|
-
function dotStyle(color) {
|
|
2231
|
-
if (color === void 0) return void 0;
|
|
2232
|
-
return {
|
|
2233
|
-
background: color,
|
|
2234
|
-
borderColor: color
|
|
2235
|
-
};
|
|
2236
|
-
}
|
|
2237
|
-
/** Localized relative-time text for a Unix-ms timestamp. */
|
|
2238
|
-
function relativeTimeText(ms) {
|
|
2239
|
-
const rt = formatRelativeTime(ms);
|
|
2240
|
-
return tt(rt.key, rt.value !== void 0 ? { value: rt.value } : void 0);
|
|
2241
|
-
}
|
|
2242
|
-
/** Localized absolute time for the detail meta (e.g. "2026/8/16 11:00:00"). */
|
|
2243
|
-
function formatDateTime(ms) {
|
|
2244
|
-
return new Date(ms).toLocaleString();
|
|
2245
|
-
}
|
|
2246
|
-
/** Short form of a commit SHA for display. */
|
|
2247
|
-
function shortSha(sha) {
|
|
2248
|
-
return sha.length > 7 ? sha.slice(0, 7) : sha;
|
|
2249
|
-
}
|
|
2250
|
-
//#endregion
|
|
2251
2443
|
//#region src/client/SkillHubSettingsCard.tsx
|
|
2252
2444
|
/**
|
|
2253
2445
|
* The dsh-skill-hub plugin settings card: bridges the hub's settings
|
|
@@ -2466,7 +2658,7 @@ window.__ModuleLoader__.load({
|
|
|
2466
2658
|
}
|
|
2467
2659
|
//#endregion
|
|
2468
2660
|
//#region \0dsh-css:/Users/huanyi/Documents/personal/tools/dsh-skill-hub/src/client/panel/panel.module.css.mjs
|
|
2469
|
-
const css = "._6VtqdG_panel{-webkit-font-smoothing:antialiased;color-scheme:light dark;--hub-model:#2f81f7;--hub-user:#3fb950;flex-direction:column;gap:14px;max-width:720px;margin:0 auto;padding:28px 20px 44px;font-family:-apple-system,BlinkMacSystemFont,SF Pro Text,Helvetica Neue,sans-serif;display:flex}._6VtqdG_header{flex-wrap:wrap;align-items:baseline;gap:10px;display:flex}._6VtqdG_title{letter-spacing:-.02em;margin:0;font-size:24px;font-weight:700}._6VtqdG_hint{opacity:.5;font-size:12px}._6VtqdG_actions{gap:8px;margin-left:auto;display:flex}._6VtqdG_segmented{background:#80808024;border-radius:9px;gap:2px;padding:2px;display:flex}._6VtqdG_segBtn{color:inherit;opacity:.62;cursor:pointer;background:0 0;border:none;border-radius:7px;padding:5px 13px;font-size:12px;transition:background .15s,opacity .15s}._6VtqdG_segBtn:hover{opacity:.9}._6VtqdG_segBtnActive{opacity:1;background:#80808047;font-weight:600}._6VtqdG_search{width:100%;color:inherit;background:#8080801f;border:none;border-radius:10px;outline:none;padding:10px 14px;font-size:13px;transition:background .15s}._6VtqdG_search::placeholder{opacity:.45}._6VtqdG_search:focus{background:#8080802e}._6VtqdG_section{background:#8080800f;border-radius:12px;flex-direction:column;margin-top:4px;display:flex;overflow:hidden;box-shadow:0 1px 2px #00000008,0 4px 12px #0000000a}._6VtqdG_sectionTitle{margin:14px 16px 2px;font-size:13px;font-weight:700}._6VtqdG_groupHead{box-sizing:border-box;align-items:center;gap:8px;min-height:44px;padding:10px 14px;display:flex}._6VtqdG_groupTitle{opacity:.92;flex:1;align-items:center;gap:6px;min-width:0;font-size:13px;font-weight:600;line-height:1;display:inline-flex}._6VtqdG_groupOps{flex-wrap:wrap;flex:none;align-items:center;gap:6px;display:inline-flex}._6VtqdG_opBtn{color:inherit;cursor:pointer;background:#80808021;border:none;border-radius:999px;padding:3px 11px;font-size:11px;transition:background .15s}._6VtqdG_opBtn:hover{background:#80808038}._6VtqdG_opBtn:disabled{opacity:.4;cursor:default}._6VtqdG_row{cursor:pointer;background:0 0;align-items:center;gap:12px;padding:10px 14px;transition:background .12s;display:flex}._6VtqdG_row:hover{background:#80808014}._6VtqdG_row:focus-visible{outline:2px solid var(--hub-model);outline-offset:-2px}._6VtqdG_rowStatic{cursor:default}._6VtqdG_rowStatic:hover{background:0 0}._6VtqdG_row+._6VtqdG_row{border-top:.5px solid #80808021}._6VtqdG_rowMain{flex:1;min-width:0}._6VtqdG_rowName{align-items:center;gap:5px;font-size:13.5px;display:flex}._6VtqdG_rowNameText{font-weight:600}._6VtqdG_rowDesc{opacity:.52;white-space:nowrap;text-overflow:ellipsis;margin-top:1px;font-size:12.5px;overflow:hidden}._6VtqdG_rowMeta{opacity:.45;white-space:nowrap;flex:none;font-size:11.5px}._6VtqdG_badges{flex:none;align-items:center;gap:6px;display:flex}._6VtqdG_badge{opacity:.8;white-space:nowrap;border:.5px solid #8080804d;border-radius:999px;padding:1px 7px;font-size:10.5px}._6VtqdG_badgeModel{color:var(--hub-model);border-color:currentColor}._6VtqdG_badgeUser{color:var(--hub-user);border-color:currentColor}._6VtqdG_badgeUses{color:#d29922;border-color:currentColor}._6VtqdG_badgeReadonly{opacity:.5}._6VtqdG_switch{cursor:pointer;background:#8080804d;border:none;border-radius:999px;flex:none;width:36px;height:21px;padding:2px;transition:background .18s}._6VtqdG_switch:disabled{opacity:.5;cursor:default}._6VtqdG_switchOn{background:#34c759}._6VtqdG_switchThumb{background:#fff;border-radius:50%;width:17px;height:17px;transition:transform .18s;display:block;transform:translate(0);box-shadow:0 1px 2px #00000040}._6VtqdG_switchOn ._6VtqdG_switchThumb{transform:translate(15px)}._6VtqdG_empty{opacity:.5;text-align:center;padding:20px 0;font-size:13px}._6VtqdG_diagRow{background:#d299220f;border-left:3px solid #d29922;padding:9px 14px;font-size:12px}._6VtqdG_diagPath{opacity:.75;word-break:break-all;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px}._6VtqdG_diagReason{opacity:.65;margin-top:1px}._6VtqdG_updateLink{color:inherit;font-weight:600;text-decoration:underline}._6VtqdG_errorBanner{color:#d1242f;white-space:pre-wrap;word-break:break-all;background:#d1242f12;border-radius:10px;align-items:flex-start;gap:10px;padding:10px 14px;font-size:12.5px;display:flex}._6VtqdG_successBanner{color:#3fb950;white-space:pre-wrap;word-break:break-all;background:#3fb95014;border-radius:10px;align-items:flex-start;gap:10px;padding:10px 14px;font-size:12.5px;display:flex}._6VtqdG_detailHead{flex-wrap:wrap;align-items:center;gap:10px;display:flex}._6VtqdG_back{color:inherit;cursor:pointer;background:#8080801f;border:none;border-radius:8px;padding:6px 12px;font-size:12.5px}._6VtqdG_back:hover{background:#80808033}._6VtqdG_detailName{letter-spacing:-.01em;font-size:19px;font-weight:700}._6VtqdG_detailMeta{opacity:.6;flex-direction:column;gap:4px;font-size:12px;display:flex}._6VtqdG_detailMetaLine{word-break:break-all}._6VtqdG_detailContent{white-space:pre-wrap;word-break:break-word;background:#8080800d;border-radius:12px;max-height:62vh;padding:14px 16px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;line-height:1.6;overflow:auto}._6VtqdG_form{background:#8080800d;border-radius:12px;flex-direction:column;gap:10px;padding:14px 16px;display:flex;box-shadow:0 1px 2px #00000008,0 4px 12px #0000000a}._6VtqdG_formRow{flex-direction:column;gap:5px;display:flex}._6VtqdG_formLabel{opacity:.6;font-size:12px}._6VtqdG_input,._6VtqdG_select{color:inherit;background:#8080801f;border:none;border-radius:9px;outline:none;padding:9px 12px;font-size:13px}._6VtqdG_input:focus,._6VtqdG_select:focus{background:#8080802e}._6VtqdG_buttons{align-items:center;gap:8px;display:flex}._6VtqdG_button{cursor:pointer;color:inherit;background:#8080801f;border:none;border-radius:8px;padding:6px 13px;font-size:12.5px;transition:background .15s}._6VtqdG_button:hover{background:#80808033}._6VtqdG_primary{background:var(--hub-model);color:#fff}._6VtqdG_primary:hover{opacity:.92;background:#2f81f7}._6VtqdG_primary:disabled{opacity:.45;cursor:default}._6VtqdG_danger{color:#fff;background:#d1242f}._6VtqdG_danger:hover{opacity:.92;background:#d1242f}._6VtqdG_danger:disabled{opacity:.45;cursor:default}._6VtqdG_formError{color:#d1242f}._6VtqdG_formSuccess{color:#3fb950}._6VtqdG_muted{opacity:.55}._6VtqdG_dot{vertical-align:middle;border-radius:50%;width:7px;height:7px;margin-left:5px;display:inline-block}._6VtqdG_dotModel{background:var(--hub-model)}._6VtqdG_dotUser{background:var(--hub-user)}._6VtqdG_legend{opacity:.55;flex-wrap:wrap;align-items:center;gap:12px;padding:2px 2px 0;font-size:11.5px;display:flex}._6VtqdG_legendItem{align-items:center;gap:4px;display:inline-flex}._6VtqdG_legendItem ._6VtqdG_dot{margin-left:0}._6VtqdG_legendHint{opacity:.8}._6VtqdG_badgeSource,._6VtqdG_badgeCount{opacity:.72;background:#8080801a;border:none}._6VtqdG_badgeDisabled{opacity:.58;background:#8080801a;border:none}._6VtqdG_disclosure{min-width:0;color:inherit;cursor:pointer;text-align:left;background:0 0;border:none;flex:1;align-items:center;gap:7px;min-height:24px;padding:2px 0;display:flex}._6VtqdG_disclosure:hover ._6VtqdG_groupTitle{opacity:1}._6VtqdG_chevron{opacity:.55;border-bottom:1.5px solid;border-right:1.5px solid;flex:none;align-self:center;width:8px;height:8px;margin-right:2px;transition:transform .15s;transform:rotate(45deg)translateY(-1px)}._6VtqdG_chevronCollapsed{transform:rotate(-45deg)translateY(-1px)}._6VtqdG_headerCount{opacity:.5;white-space:nowrap;margin-left:2px;font-size:12px}._6VtqdG_pluginVersion{opacity:.45;white-space:nowrap;font-variant-numeric:tabular-nums;margin-left:6px;font-size:12px;font-weight:500}._6VtqdG_subbar{flex-wrap:wrap;align-items:center;gap:8px;display:flex}._6VtqdG_legendToggle{width:22px;height:22px;color:inherit;cursor:pointer;opacity:.55;background:#80808024;border:none;border-radius:50%;flex:none;font-size:12px;line-height:1;transition:opacity .15s,background .15s}._6VtqdG_legendToggle:hover{opacity:1}._6VtqdG_legendToggleActive{opacity:1;background:#80808047}._6VtqdG_filterBar{flex-wrap:wrap;align-items:center;gap:8px;display:flex}._6VtqdG_filterBar ._6VtqdG_formLabel{margin:0}._6VtqdG_useCount{color:#d29922;vertical-align:1px;background:#d299221f;border-radius:999px;margin-left:6px;padding:0 6px;font-size:11px;font-weight:700;line-height:1.6;display:inline-block}._6VtqdG_useTime{opacity:.45;white-space:nowrap;margin-left:auto;font-size:11px}._6VtqdG_switchMixed{background:#2f81f773}._6VtqdG_switchMixed ._6VtqdG_switchThumb{transform:translate(7.5px);box-shadow:0 1px 2px #00000040}._6VtqdG_groupTitleInner{align-items:baseline;display:inline-flex}._6VtqdG_sourceLink{color:inherit;opacity:.9;border-bottom:1px dotted;font-weight:600;text-decoration:none}._6VtqdG_sourceLink:hover{opacity:1}._6VtqdG_statusBadges{flex:none;align-items:center;gap:4px;display:inline-flex}._6VtqdG_statusBadge{opacity:.8;white-space:nowrap;background:#8080801a;border:.5px solid #8080804d;border-radius:999px;padding:1px 7px;font-size:10.5px}._6VtqdG_statusOk{color:#3fb950;background:0 0;border-color:currentColor}._6VtqdG_statusUpdated{color:#d29922;background:#d2992214;border-color:currentColor}._6VtqdG_statusError{color:#d1242f;background:#d1242f14;border-color:currentColor}._6VtqdG_statusWrap{cursor:pointer;background:0 0;border:none;align-items:center;gap:4px;padding:0;font-family:inherit;display:inline-flex}._6VtqdG_statusWrap:hover ._6VtqdG_statusBadge{opacity:1}._6VtqdG_statusButton{cursor:pointer;font-family:inherit}._6VtqdG_statusButton:hover{opacity:1}._6VtqdG_opDanger{color:#d1242f;background:#d1242f1a}._6VtqdG_opDanger:hover{background:#d1242f2e}._6VtqdG_iconBtn{border-radius:999px;justify-content:center;align-items:center;width:24px;height:24px;padding:0;display:inline-flex}._6VtqdG_sourceCard{background:#8080800d;border-radius:12px;flex-direction:column;gap:4px;padding:12px 14px;font-size:12px;display:flex}._6VtqdG_sourceCardTitle{flex-wrap:wrap;align-items:center;gap:8px;display:flex}._6VtqdG_dialogOverlay{z-index:50;-webkit-backdrop-filter:blur(2px);background:#00000059;justify-content:center;align-items:center;padding:24px;display:flex;position:fixed;inset:0}._6VtqdG_dialog{background:var(--dsw-surface,#f5f5f7);min-width:min(320px,100%);max-width:380px;color:var(--dsw-text,#1d1d1f);border-radius:14px;padding:18px 18px 14px;box-shadow:0 12px 40px #00000047,0 2px 8px #00000029}@media (prefers-color-scheme:dark){._6VtqdG_dialog{color:#f5f5f7;background:#2c2c2e}}._6VtqdG_dialogTitle{letter-spacing:-.01em;margin:0 0 8px;font-size:15px;font-weight:700}._6VtqdG_dialogText{opacity:.65;margin:0 0 10px;font-size:12.5px;line-height:1.5}._6VtqdG_dialogList{flex-direction:column;gap:5px;max-height:200px;margin:0 0 14px;padding:0;list-style:none;display:flex;overflow:auto}._6VtqdG_dialogList li{opacity:.85;word-break:break-all;font-size:12.5px}._6VtqdG_dialogActions{justify-content:flex-end;gap:8px;display:flex}._6VtqdG_filterBar ._6VtqdG_search{flex:1;min-width:160px}._6VtqdG_filterBar ._6VtqdG_formLabel{flex:none}._6VtqdG_filterBar ._6VtqdG_select{flex:none;max-width:150px}._6VtqdG_filterBar ._6VtqdG_segmented{flex:none}._6VtqdG_titleIcon{vertical-align:-2px;opacity:.8;margin-right:2px;display:inline-block}._6VtqdG_grow{flex:1}._6VtqdG_hintLine{opacity:.55;margin:0;font-size:11.5px}._6VtqdG_hintPadded{margin:6px 12px 2px}._6VtqdG_hintInline{margin:4px 2px}._6VtqdG_rowMuted{cursor:default;opacity:.55}._6VtqdG_actionsPadded{padding:8px 12px}._6VtqdG_actionsTop{margin-top:8px}._6VtqdG_actionsBottom{margin-bottom:8px}._6VtqdG_groupTime{margin-left:6px}._6VtqdG_sectionHeadRow{align-items:center;gap:8px;display:flex}._6VtqdG_sectionTitleFill{flex:1}._6VtqdG_dialogSelect{width:100%;margin-bottom:12px}._6VtqdG_dialogRow{align-items:center;gap:8px;display:flex}._6VtqdG_workspaceBox{align-items:center;gap:6px;display:inline-flex}._6VtqdG_workspaceInput{width:200px;padding:6px 10px;font-size:12px}._6VtqdG_projectNest{border-left:1px solid #80808024;flex-direction:column;margin:2px 0 0 14px;display:flex}._6VtqdG_dragHandle{cursor:grab;opacity:.32;letter-spacing:1px;user-select:none;flex:none;justify-content:center;align-self:center;align-items:center;height:20px;padding:0 4px;font-size:11px;display:inline-flex}._6VtqdG_dragHandle:active{cursor:grabbing}._6VtqdG_dragging{opacity:.45}._6VtqdG_dragOver{outline:2px dashed var(--hub-model,#2f81f7);outline-offset:-2px;background:#2f81f712}._6VtqdG_scanList{background:#8080800f;border:.5px solid #80808021;border-radius:12px;max-height:360px;margin-top:8px;overflow:auto}._6VtqdG_scanProgressTrack{background:#80808021;border-radius:4px;flex:1;height:6px;overflow:hidden}._6VtqdG_scanProgressFill{background:var(--hub-model,#2f81f7);height:100%;transition:width .2s}";
|
|
2661
|
+
const css = "._6VtqdG_panel{-webkit-font-smoothing:antialiased;color-scheme:light dark;--hub-model:#2f81f7;--hub-user:#3fb950;flex-direction:column;gap:14px;max-width:720px;margin:0 auto;padding:28px 20px 44px;font-family:-apple-system,BlinkMacSystemFont,SF Pro Text,Helvetica Neue,sans-serif;display:flex}._6VtqdG_header{flex-wrap:wrap;align-items:baseline;gap:10px;display:flex}._6VtqdG_title{letter-spacing:-.02em;margin:0;font-size:24px;font-weight:700}._6VtqdG_hint{opacity:.5;font-size:12px}._6VtqdG_actions{gap:8px;margin-left:auto;display:flex}._6VtqdG_segmented{background:#80808024;border-radius:9px;gap:2px;padding:2px;display:flex}._6VtqdG_segBtn{color:inherit;opacity:.62;cursor:pointer;background:0 0;border:none;border-radius:7px;padding:5px 13px;font-size:12px;transition:background .15s,opacity .15s}._6VtqdG_segBtn:hover{opacity:.9}._6VtqdG_segBtnActive{opacity:1;background:#80808047;font-weight:600}._6VtqdG_search{width:100%;color:inherit;background:#8080801f;border:none;border-radius:10px;outline:none;padding:10px 14px;font-size:13px;transition:background .15s}._6VtqdG_search::placeholder{opacity:.45}._6VtqdG_search:focus{background:#8080802e}._6VtqdG_section{background:#8080800f;border-radius:12px;flex-direction:column;margin-top:4px;display:flex;overflow:hidden;box-shadow:0 1px 2px #00000008,0 4px 12px #0000000a}._6VtqdG_sectionTitle{margin:14px 16px 2px;font-size:13px;font-weight:700}._6VtqdG_groupHead{box-sizing:border-box;align-items:center;gap:8px;min-height:44px;padding:10px 14px;display:flex}._6VtqdG_groupTitle{opacity:.92;flex:1;align-items:center;gap:6px;min-width:0;font-size:13px;font-weight:600;line-height:1;display:inline-flex}._6VtqdG_groupOps{flex-wrap:wrap;flex:none;align-items:center;gap:6px;display:inline-flex}._6VtqdG_opBtn{color:inherit;cursor:pointer;background:#80808021;border:none;border-radius:999px;padding:3px 11px;font-size:11px;transition:background .15s}._6VtqdG_opBtn:hover{background:#80808038}._6VtqdG_opBtn:disabled{opacity:.4;cursor:default}._6VtqdG_row{cursor:pointer;background:0 0;align-items:center;gap:12px;padding:10px 14px;transition:background .12s;display:flex}._6VtqdG_row:hover{background:#80808014}._6VtqdG_row:focus-visible{outline:2px solid var(--hub-model);outline-offset:-2px}._6VtqdG_rowStatic{cursor:default}._6VtqdG_rowStatic:hover{background:0 0}._6VtqdG_row+._6VtqdG_row{border-top:.5px solid #80808021}._6VtqdG_rowMain{flex:1;min-width:0}._6VtqdG_rowName{align-items:center;gap:5px;font-size:13.5px;display:flex}._6VtqdG_rowNameText{font-weight:600}._6VtqdG_rowDesc{opacity:.52;white-space:nowrap;text-overflow:ellipsis;margin-top:1px;font-size:12.5px;overflow:hidden}._6VtqdG_rowMeta{opacity:.45;white-space:nowrap;flex:none;font-size:11.5px}._6VtqdG_badges{flex:none;align-items:center;gap:6px;display:flex}._6VtqdG_badge{opacity:.8;white-space:nowrap;border:.5px solid #8080804d;border-radius:999px;padding:1px 7px;font-size:10.5px}._6VtqdG_badgeModel{color:var(--hub-model);border-color:currentColor}._6VtqdG_badgeUser{color:var(--hub-user);border-color:currentColor}._6VtqdG_badgeUses{color:#d29922;border-color:currentColor}._6VtqdG_badgeReadonly{opacity:.5}._6VtqdG_switch{cursor:pointer;background:#8080804d;border:none;border-radius:999px;flex:none;width:36px;height:21px;padding:2px;transition:background .18s}._6VtqdG_switch:disabled{opacity:.5;cursor:default}._6VtqdG_switchOn{background:#34c759}._6VtqdG_switchThumb{background:#fff;border-radius:50%;width:17px;height:17px;transition:transform .18s;display:block;transform:translate(0);box-shadow:0 1px 2px #00000040}._6VtqdG_switchOn ._6VtqdG_switchThumb{transform:translate(15px)}._6VtqdG_empty{opacity:.5;text-align:center;padding:20px 0;font-size:13px}._6VtqdG_diagRow{background:#d299220f;border-left:3px solid #d29922;padding:9px 14px;font-size:12px}._6VtqdG_diagPath{opacity:.75;word-break:break-all;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px}._6VtqdG_diagReason{opacity:.65;margin-top:1px}._6VtqdG_updateLink{color:inherit;font-weight:600;text-decoration:underline}._6VtqdG_errorBanner{color:#d1242f;white-space:pre-wrap;word-break:break-all;background:#d1242f12;border-radius:10px;align-items:flex-start;gap:10px;padding:10px 14px;font-size:12.5px;display:flex}._6VtqdG_successBanner{color:#3fb950;white-space:pre-wrap;word-break:break-all;background:#3fb95014;border-radius:10px;align-items:flex-start;gap:10px;padding:10px 14px;font-size:12.5px;display:flex}._6VtqdG_detailHead{flex-wrap:wrap;align-items:center;gap:10px;display:flex}._6VtqdG_back{color:inherit;cursor:pointer;background:#8080801f;border:none;border-radius:8px;padding:6px 12px;font-size:12.5px}._6VtqdG_back:hover{background:#80808033}._6VtqdG_detailName{letter-spacing:-.01em;font-size:19px;font-weight:700}._6VtqdG_detailMeta{opacity:.6;flex-direction:column;gap:4px;font-size:12px;display:flex}._6VtqdG_detailMetaLine{word-break:break-all}._6VtqdG_detailContent{white-space:pre-wrap;word-break:break-word;background:#8080800d;border-radius:12px;max-height:62vh;padding:14px 16px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;line-height:1.6;overflow:auto}._6VtqdG_form{background:#8080800d;border-radius:12px;flex-direction:column;gap:10px;padding:14px 16px;display:flex;box-shadow:0 1px 2px #00000008,0 4px 12px #0000000a}._6VtqdG_formRow{flex-direction:column;gap:5px;display:flex}._6VtqdG_formLabel{opacity:.6;font-size:12px}._6VtqdG_input,._6VtqdG_select{color:inherit;background:#8080801f;border:none;border-radius:9px;outline:none;padding:9px 12px;font-size:13px}._6VtqdG_input:focus,._6VtqdG_select:focus{background:#8080802e}._6VtqdG_buttons{align-items:center;gap:8px;display:flex}._6VtqdG_button{cursor:pointer;color:inherit;background:#8080801f;border:none;border-radius:8px;padding:6px 13px;font-size:12.5px;transition:background .15s}._6VtqdG_button:hover{background:#80808033}._6VtqdG_primary{background:var(--hub-model);color:#fff}._6VtqdG_primary:hover{opacity:.92;background:#2f81f7}._6VtqdG_primary:disabled{opacity:.45;cursor:default}._6VtqdG_danger{color:#fff;background:#d1242f}._6VtqdG_danger:hover{opacity:.92;background:#d1242f}._6VtqdG_danger:disabled{opacity:.45;cursor:default}._6VtqdG_formError{color:#d1242f}._6VtqdG_formSuccess{color:#3fb950}._6VtqdG_muted{opacity:.55}._6VtqdG_dot{vertical-align:middle;border-radius:50%;width:7px;height:7px;margin-left:5px;display:inline-block}._6VtqdG_dotModel{background:var(--hub-model)}._6VtqdG_dotUser{background:var(--hub-user)}._6VtqdG_legend{opacity:.55;flex-wrap:wrap;align-items:center;gap:12px;padding:2px 2px 0;font-size:11.5px;display:flex}._6VtqdG_legendItem{align-items:center;gap:4px;display:inline-flex}._6VtqdG_legendItem ._6VtqdG_dot{margin-left:0}._6VtqdG_legendHint{opacity:.8}._6VtqdG_badgeSource,._6VtqdG_badgeCount{opacity:.72;background:#8080801a;border:none}._6VtqdG_badgeDisabled{opacity:.58;background:#8080801a;border:none}._6VtqdG_disclosure{min-width:0;color:inherit;cursor:pointer;text-align:left;background:0 0;border:none;flex:1;align-items:center;gap:7px;min-height:24px;padding:2px 0;display:flex}._6VtqdG_disclosure:hover ._6VtqdG_groupTitle{opacity:1}._6VtqdG_chevron{opacity:.55;border-bottom:1.5px solid;border-right:1.5px solid;flex:none;align-self:center;width:8px;height:8px;margin-right:2px;transition:transform .15s;transform:rotate(45deg)translateY(-1px)}._6VtqdG_chevronCollapsed{transform:rotate(-45deg)translateY(-1px)}._6VtqdG_headerCount{opacity:.5;white-space:nowrap;margin-left:2px;font-size:12px}._6VtqdG_pluginVersion{opacity:.45;white-space:nowrap;font-variant-numeric:tabular-nums;margin-left:6px;font-size:12px;font-weight:500}._6VtqdG_subbar{flex-wrap:wrap;align-items:center;gap:8px;display:flex}._6VtqdG_legendToggle{width:22px;height:22px;color:inherit;cursor:pointer;opacity:.55;background:#80808024;border:none;border-radius:50%;flex:none;font-size:12px;line-height:1;transition:opacity .15s,background .15s}._6VtqdG_legendToggle:hover{opacity:1}._6VtqdG_legendToggleActive{opacity:1;background:#80808047}._6VtqdG_filterBar{flex-wrap:wrap;align-items:center;gap:8px;display:flex}._6VtqdG_filterBar ._6VtqdG_formLabel{margin:0}._6VtqdG_useCount{color:#d29922;vertical-align:1px;background:#d299221f;border-radius:999px;margin-left:6px;padding:0 6px;font-size:11px;font-weight:700;line-height:1.6;display:inline-block}._6VtqdG_useTime{opacity:.45;white-space:nowrap;margin-left:auto;font-size:11px}._6VtqdG_switchMixed{background:#2f81f773}._6VtqdG_switchMixed ._6VtqdG_switchThumb{transform:translate(7.5px);box-shadow:0 1px 2px #00000040}._6VtqdG_groupTitleInner{align-items:baseline;display:inline-flex}._6VtqdG_sourceLink{color:inherit;opacity:.9;border-bottom:1px dotted;font-weight:600;text-decoration:none}._6VtqdG_sourceLink:hover{opacity:1}._6VtqdG_statusBadges{flex:none;align-items:center;gap:4px;display:inline-flex}._6VtqdG_statusBadge{opacity:.8;white-space:nowrap;background:#8080801a;border:.5px solid #8080804d;border-radius:999px;padding:1px 7px;font-size:10.5px}._6VtqdG_statusOk{color:#3fb950;background:0 0;border-color:currentColor}._6VtqdG_statusUpdated{color:#d29922;background:#d2992214;border-color:currentColor}._6VtqdG_statusError{color:#d1242f;background:#d1242f14;border-color:currentColor}._6VtqdG_statusWrap{cursor:pointer;background:0 0;border:none;align-items:center;gap:4px;padding:0;font-family:inherit;display:inline-flex}._6VtqdG_statusWrap:hover ._6VtqdG_statusBadge{opacity:1}._6VtqdG_statusButton{cursor:pointer;font-family:inherit}._6VtqdG_statusButton:hover{opacity:1}._6VtqdG_opDanger{color:#d1242f;background:#d1242f1a}._6VtqdG_opDanger:hover{background:#d1242f2e}._6VtqdG_iconBtn{border-radius:999px;justify-content:center;align-items:center;width:24px;height:24px;padding:0;display:inline-flex}._6VtqdG_sourceCard{background:#8080800d;border-radius:12px;flex-direction:column;gap:4px;padding:12px 14px;font-size:12px;display:flex}._6VtqdG_sourceCardTitle{flex-wrap:wrap;align-items:center;gap:8px;display:flex}._6VtqdG_dialogOverlay{z-index:50;-webkit-backdrop-filter:blur(2px);background:#00000059;justify-content:center;align-items:center;padding:24px;display:flex;position:fixed;inset:0}._6VtqdG_dialog{background:var(--dsw-surface,#f5f5f7);min-width:min(320px,100%);max-width:380px;color:var(--dsw-text,#1d1d1f);border-radius:14px;padding:18px 18px 14px;box-shadow:0 12px 40px #00000047,0 2px 8px #00000029}@media (prefers-color-scheme:dark){._6VtqdG_dialog{color:#f5f5f7;background:#2c2c2e}}._6VtqdG_dialogTitle{letter-spacing:-.01em;margin:0 0 8px;font-size:15px;font-weight:700}._6VtqdG_dialogText{opacity:.65;margin:0 0 10px;font-size:12.5px;line-height:1.5}._6VtqdG_dialogList{flex-direction:column;gap:5px;max-height:200px;margin:0 0 14px;padding:0;list-style:none;display:flex;overflow:auto}._6VtqdG_dialogList li{opacity:.85;word-break:break-all;font-size:12.5px}._6VtqdG_dialogActions{justify-content:flex-end;gap:8px;display:flex}._6VtqdG_filterBar ._6VtqdG_search{flex:1;min-width:160px}._6VtqdG_filterBar ._6VtqdG_formLabel{flex:none}._6VtqdG_filterBar ._6VtqdG_select{flex:none;max-width:150px}._6VtqdG_filterBar ._6VtqdG_segmented{flex:none}._6VtqdG_titleIcon{vertical-align:-2px;opacity:.8;margin-right:2px;display:inline-block}._6VtqdG_grow{flex:1}._6VtqdG_hintLine{opacity:.55;margin:0;font-size:11.5px}._6VtqdG_hintPadded{margin:6px 12px 2px}._6VtqdG_hintInline{margin:4px 2px}._6VtqdG_rowMuted{cursor:default;opacity:.55}._6VtqdG_actionsPadded{padding:8px 12px}._6VtqdG_actionsTop{margin-top:8px}._6VtqdG_actionsBottom{margin-bottom:8px}._6VtqdG_groupTime{margin-left:6px}._6VtqdG_sectionHeadRow{align-items:center;gap:8px;display:flex}._6VtqdG_sectionTitleFill{flex:1}._6VtqdG_dialogSelect{width:100%;margin-bottom:12px}._6VtqdG_dialogRow{align-items:center;gap:8px;display:flex}._6VtqdG_workspaceBox{align-items:center;gap:6px;display:inline-flex}._6VtqdG_workspaceInput{width:200px;padding:6px 10px;font-size:12px}._6VtqdG_projectNest{border-left:1px solid #80808024;flex-direction:column;margin:2px 0 0 14px;display:flex}._6VtqdG_dragHandle{cursor:grab;opacity:.28;letter-spacing:0;user-select:none;flex:none;justify-content:center;align-self:center;align-items:center;width:8px;height:16px;padding:0 1px;font-size:10px;display:inline-flex;overflow:hidden}._6VtqdG_dragHandle:active{cursor:grabbing}._6VtqdG_dragging{opacity:.45}._6VtqdG_dragOver{outline:2px dashed var(--hub-model,#2f81f7);outline-offset:-2px;background:#2f81f712}._6VtqdG_scanList{background:#8080800f;border:.5px solid #80808021;border-radius:12px;max-height:360px;margin-top:8px;overflow:auto}._6VtqdG_scanProgressTrack{background:#80808021;border-radius:4px;flex:1;height:6px;overflow:hidden}._6VtqdG_scanProgressFill{background:var(--hub-model,#2f81f7);height:100%;transition:width .2s}";
|
|
2470
2662
|
const tagId = "dsh-skill-hub/panel.module.css";
|
|
2471
2663
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
|
|
2472
2664
|
const tag = document.createElement("style");
|
|
@@ -6333,7 +6525,8 @@ window.__ModuleLoader__.load({
|
|
|
6333
6525
|
"locale",
|
|
6334
6526
|
"connection",
|
|
6335
6527
|
"remote",
|
|
6336
|
-
"settingsScope"
|
|
6528
|
+
"settingsScope",
|
|
6529
|
+
"inputTriggers"
|
|
6337
6530
|
];
|
|
6338
6531
|
/**
|
|
6339
6532
|
* Mount the settings card and the skill hub section.
|
|
@@ -6346,7 +6539,9 @@ window.__ModuleLoader__.load({
|
|
|
6346
6539
|
}), "dsh-skill-hub: dictionaries");
|
|
6347
6540
|
const t = ctx.locale.bind(NS);
|
|
6348
6541
|
const api = new SkillHubApi();
|
|
6349
|
-
const
|
|
6542
|
+
const scope = ctx.settingsScope.bind({ namespace: NS });
|
|
6543
|
+
const settingsCard = new SkillHubSettingsCardController(scope);
|
|
6544
|
+
ctx.effect(() => setupSkillSlashDots(ctx, api, scope), "dsh-skill-hub: slash dots");
|
|
6350
6545
|
ctx.effect(() => ctx.slots.inject("settings.plugin.item", () => ctx.slots.register({
|
|
6351
6546
|
name: "settings.plugin.item",
|
|
6352
6547
|
key: NS,
|