@linxin666/dsh-client-ui-community-plugins 0.2.1 → 0.2.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/lib/client.js CHANGED
@@ -5,6 +5,7 @@ window.__ModuleLoader__.load({
5
5
  var exports = module.exports;
6
6
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
7
  let react = require("react");
8
+ let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
8
9
  let react_jsx_runtime = require("react/jsx-runtime");
9
10
  let _deepseek_ai_dsh_client_runtime_client = require("@deepseek-ai/dsh-client-runtime/client");
10
11
  //#region \0dsh-css:packages/dsh-community-plugins/src/client/settings-card.module.css.mjs
@@ -726,7 +727,6 @@ window.__ModuleLoader__.load({
726
727
  "repo": "https://github.com/v833/dsh-chat-summary",
727
728
  "description": "总结当前对话并导出为 Markdown / DOCX / PDF,可选 LLM 智能总结(用户自配 API Key)。",
728
729
  "descriptionEn": "Summarize the current conversation and export it as Markdown / DOCX / PDF, with optional LLM summarization using your own API key.",
729
- "npm": "@linxin666/dsh-client-ui-chat-summary",
730
730
  "category": "knowledge"
731
731
  },
732
732
  {
@@ -782,16 +782,6 @@ window.__ModuleLoader__.load({
782
782
  "npm": "dsh-mnemon",
783
783
  "category": "knowledge"
784
784
  },
785
- {
786
- "id": "dsh-plugin-hub",
787
- "name": "插件中心(dsh-plugin-hub)",
788
- "nameEn": "Plugin Hub",
789
- "author": "Noob-stupid",
790
- "repo": "https://github.com/Noob-stupid/dsh-plugin-hub",
791
- "description": "插件管理面板:已安装插件一键启用/停用,内置 GitHub dsh-plugin 插件市场(官方/聚合识别、子包浏览、一键安装与本地 AI 兜底修复、删除卸载)。",
792
- "descriptionEn": "Plugin management panel: one-click enable/disable, a GitHub dsh-plugin marketplace with official/aggregate detection, subpackage browsing, one-click installs with local-AI fallback repair, and uninstall.",
793
- "category": "tools"
794
- },
795
785
  {
796
786
  "id": "dsh-genui",
797
787
  "name": "GenUI 生成式 UI",
@@ -878,16 +868,6 @@ window.__ModuleLoader__.load({
878
868
  "npm": "@linxin666/dsh-client-ui-skill-explorer",
879
869
  "category": "tools"
880
870
  },
881
- {
882
- "id": "dsh-dream-skin",
883
- "name": "dsh-dream-skin",
884
- "nameEn": "dsh-dream-skin",
885
- "author": "RevolutionLA",
886
- "repo": "https://github.com/RevolutionLA/dsh-dream-skin",
887
- "description": "DSH Web 一键换肤插件:8 套原创主题、背景壁纸(透明度/模糊/渐变/URL)、每用户强调色、主题包导入导出与分享链接、收藏与随机,纯原生 token 系统。",
888
- "descriptionEn": "One-command skinning for DSH Web: 8 original themes, wallpaper (opacity/blur/gradient/URL), per-user accent, shareable theme packs with favorites and surprise-me. Purely native token system.",
889
- "category": "ui"
890
- },
891
871
  {
892
872
  "id": "dsh-friendly-steps",
893
873
  "name": "过程精简 Friendly Steps",
@@ -1034,6 +1014,17 @@ window.__ModuleLoader__.load({
1034
1014
  "repo": "https://github.com/DamonKoy/dsh-plugins/tree/main/packages/dsh-usage-cost",
1035
1015
  "description": "统计会话与每日 token 用量和估算成本,超预算提醒。",
1036
1016
  "descriptionEn": "Tracks token usage and estimated cost per session/day with budget alerts."
1017
+ },
1018
+ {
1019
+ "id": "dsh-notifier",
1020
+ "name": "dsh-notifier",
1021
+ "nameEn": "dsh-notifier",
1022
+ "author": "wingsky-1",
1023
+ "repo": "https://github.com/wingsky-1/dsh-plugin-hub/tree/main/packages/dsh-notifier",
1024
+ "description": "审批/完成/错误事件通知:浏览器 Notification + 系统原生 toast(Windows PowerShell WinRT / Linux·macOS notify-send,零依赖);提示音可配、错误同类合并、免打扰时段、通知历史与配置面板;非安全上下文自动降级横幅。",
1025
+ "descriptionEn": "Approval / completion / error event notifications: browser Notification API plus native OS toasts (Windows PowerShell WinRT, Linux and macOS notify-send, zero dependencies); configurable sound, error merging, quiet hours, notification history and a settings panel; falls back to in-page banners outside secure contexts.",
1026
+ "npm": "@wingsky-1/dsh-notifier",
1027
+ "category": "utility"
1037
1028
  }
1038
1029
  ];
1039
1030
  //#endregion
@@ -1068,8 +1059,106 @@ window.__ModuleLoader__.load({
1068
1059
  return true;
1069
1060
  }
1070
1061
  //#endregion
1062
+ //#region src/client/plugin-manager-bridge.ts
1063
+ let snapshot = {
1064
+ face: null,
1065
+ version: 0
1066
+ };
1067
+ const listeners = /* @__PURE__ */ new Set();
1068
+ /** Replace the held face and notify subscribers. */
1069
+ function setFace(face) {
1070
+ snapshot = {
1071
+ face,
1072
+ version: snapshot.version + 1
1073
+ };
1074
+ for (const listener of listeners) listener();
1075
+ }
1076
+ /** Current bridge snapshot (cached reference, safe for useSyncExternalStore). */
1077
+ function getPluginManagerSnapshot() {
1078
+ return snapshot;
1079
+ }
1080
+ /** Subscribe to face changes; returns the unsubscribe function. */
1081
+ function subscribePluginManager(listener) {
1082
+ listeners.add(listener);
1083
+ return () => {
1084
+ listeners.delete(listener);
1085
+ };
1086
+ }
1087
+ /**
1088
+ * Bridge the optional 'pluginManager' service into the module store. Uses
1089
+ * ctx.inject (NOT the plugin's module-level inject array) so the service
1090
+ * stays optional: the inner callback runs when the sibling plugin provides
1091
+ * the face and is disposed when it goes away, which clears the store.
1092
+ * @param ctx - the client root context.
1093
+ */
1094
+ function bridgePluginManager(ctx) {
1095
+ ctx.inject(["pluginManager"], (inner) => {
1096
+ inner.effect(() => {
1097
+ setFace(inner.pluginManager ?? null);
1098
+ return () => {
1099
+ setFace(null);
1100
+ };
1101
+ }, "community-plugins: pluginManager bridge");
1102
+ });
1103
+ }
1104
+ //#endregion
1105
+ //#region src/client/install-source.ts
1106
+ /**
1107
+ * The spec handed to pluginManager.install(): the npm package name when the
1108
+ * entry is published there, else the repository URL (git install).
1109
+ * @param entry - the community index entry.
1110
+ * @returns the install spec.
1111
+ */
1112
+ function installSpec(entry) {
1113
+ return entry.npm ?? entry.repo;
1114
+ }
1115
+ /**
1116
+ * Normalize a git remote for comparison: lowercase, drop the transport
1117
+ * prefix ('git+' wrapper, 'https://', 'http://', 'git://', 'ssh://git@', the
1118
+ * scp-style 'git@host:' userinfo, and the pnpm host shorthands 'github:' /
1119
+ * 'gitlab:' / 'bitbucket:' that package.json records for shorthand installs),
1120
+ * then strip trailing slashes and one trailing '.git'
1121
+ * suffix (repeated, so 'x/.git/' and 'x.git' converge). Conservative on
1122
+ * purpose: no path rewriting beyond those steps, so distinct repositories
1123
+ * never collapse onto each other.
1124
+ * @param url - the repository URL as recorded by either side.
1125
+ * @returns the comparison key.
1126
+ */
1127
+ function normalizeGitUrl(url) {
1128
+ let key = url.trim().toLowerCase();
1129
+ key = key.replace(/^git[+]/, "");
1130
+ key = key.replace(/^(github|gitlab):/, "$1.com/");
1131
+ key = key.replace(/^bitbucket:/, "bitbucket.org/");
1132
+ key = key.replace(/^(?:https?|git):[/][/]/, "");
1133
+ key = key.replace(/^ssh:[/][/]git@/, "");
1134
+ key = key.replace(/^git@([^/:]+):/, "$1/");
1135
+ for (;;) {
1136
+ const next = key.replace(/[/]+$/, "").replace(/[.]git$/, "");
1137
+ if (next === key) return key;
1138
+ key = next;
1139
+ }
1140
+ }
1141
+ /**
1142
+ * Match one community entry against the installed snapshot. npm-published
1143
+ * entries match on the installed row's package identity (id/name): the npm
1144
+ * source spec records the dependency RANGE ('^0.3.2') or an install-time
1145
+ * spec, not necessarily the bare package name, so it is only a fallback.
1146
+ * Repository entries match on the normalized git URL. An entry with an npm
1147
+ * field ONLY matches npm installs — a same-repository git install is a
1148
+ * different source and stays unmatched (the card then offers install, which
1149
+ * the host reconciles).
1150
+ * @param entry - the community index entry.
1151
+ * @param installed - the installed snapshot from the pluginManager service.
1152
+ * @returns the matching installed row, or null.
1153
+ */
1154
+ function entryInstalled(entry, installed) {
1155
+ if (entry.npm !== void 0) return installed.find((item) => item.source.kind === "npm" && (item.id === entry.npm || item.name === entry.npm || item.source.spec === entry.npm)) ?? null;
1156
+ const key = normalizeGitUrl(entry.repo);
1157
+ return installed.find((item) => item.source.kind === "git" && normalizeGitUrl(item.source.spec) === key) ?? null;
1158
+ }
1159
+ //#endregion
1071
1160
  //#region \0dsh-css:packages/dsh-community-plugins/src/client/community.module.css.mjs
1072
- const css = ".z0J_8a_sectionList{margin:0;padding:0;list-style:none}.z0J_8a_market{flex-direction:column;gap:10px;margin-top:12px;display:flex}.z0J_8a_toolbar{display:flex}.z0J_8a_search{min-width:0;font:inherit;color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l2);border-radius:6px;flex:1;padding:6px 10px;font-size:13px;line-height:1.5}.z0J_8a_search::placeholder{color:var(--dsw-alias-label-tertiary)}.z0J_8a_search:focus{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}.z0J_8a_search:disabled{opacity:.5}.z0J_8a_filters{flex-wrap:wrap;gap:6px;display:flex}.z0J_8a_pill{font:inherit;color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l2);cursor:pointer;border-radius:999px;padding:2px 10px;font-size:12px;line-height:1.6}.z0J_8a_pill:hover:enabled{color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-dimmed)}.z0J_8a_pill:disabled{opacity:.5;cursor:default}.z0J_8a_pillActive{color:var(--dsw-alias-bg-layer-3);background:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-primary);font-weight:600}.z0J_8a_pillActive:hover:enabled{color:var(--dsw-alias-bg-layer-3);border-color:var(--dsw-alias-label-primary)}.z0J_8a_resultCount{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:1.4}.z0J_8a_grid{grid-template-columns:repeat(auto-fill,minmax(256px,1fr));gap:8px;margin:0;padding:0;list-style:none;display:grid}.z0J_8a_card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);border-radius:8px;flex-direction:column;gap:6px;min-width:0;padding:12px;display:flex}.z0J_8a_card:hover{border-color:var(--dsw-alias-label-dimmed)}.z0J_8a_cardHead{justify-content:space-between;align-items:baseline;gap:8px;min-width:0;display:flex}.z0J_8a_cardName{color:var(--dsw-alias-label-primary);text-overflow:ellipsis;white-space:nowrap;font-weight:600;overflow:hidden}.z0J_8a_badge{color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-module-platform);border:1px solid var(--dsw-alias-border-l2);white-space:nowrap;border-radius:999px;flex:none;padding:0 8px;font-size:11px;line-height:1.6}.z0J_8a_badgePublished{color:var(--dsw-alias-link-primary,var(--dsw-alias-button-info-fill));border-color:var(--dsw-alias-link-primary,var(--dsw-alias-button-info-fill))}.z0J_8a_cardMeta{min-width:0;color:var(--dsw-alias-label-tertiary);align-items:center;gap:6px;margin:0;font-size:12px;line-height:1.4;display:flex}.z0J_8a_cardCategory{color:var(--dsw-alias-label-secondary);white-space:nowrap;flex:none}.z0J_8a_cardDot{flex:none}.z0J_8a_cardAuthor{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.z0J_8a_cardDescription{color:var(--dsw-alias-label-secondary);-webkit-line-clamp:2;overflow-wrap:anywhere;-webkit-box-orient:vertical;margin:0;font-size:13px;line-height:1.45;display:-webkit-box;overflow:hidden}.z0J_8a_cardFooter{flex-direction:column;gap:6px;margin-top:auto;padding-top:6px;display:flex}.z0J_8a_cardTop{justify-content:space-between;align-items:center;gap:8px;min-width:0;display:flex}.z0J_8a_cardLink{color:var(--dsw-alias-link-primary,var(--dsw-alias-button-info-fill));font-size:13px;text-decoration:none}.z0J_8a_cardLink:hover{text-decoration:underline}.z0J_8a_cardCommand{box-sizing:border-box;width:100%;min-width:0;color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);white-space:nowrap;text-overflow:ellipsis;border-radius:4px;padding:2px 6px;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11px;overflow:hidden}.z0J_8a_installButton{font:inherit;color:var(--dsw-alias-bg-layer-3);background:var(--dsw-alias-button-info-fill);border:1px solid var(--dsw-alias-button-info-fill);cursor:pointer;white-space:nowrap;border-radius:6px;flex:none;padding:4px 12px;font-size:12px;font-weight:600;line-height:1.5}.z0J_8a_installButton:hover:enabled{opacity:.88}.z0J_8a_installButton:disabled{opacity:.5;cursor:default}.z0J_8a_installButtonCopied{color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-dimmed);background:0 0}.z0J_8a_installNote{color:var(--dsw-alias-label-tertiary);margin:8px 2px 0;font-size:12px;line-height:1.4}.z0J_8a_empty{color:var(--dsw-alias-label-tertiary);margin:0;padding:8px 2px;font-size:13px}.z0J_8a_off{color:var(--dsw-alias-label-tertiary);margin:8px 2px 0;font-size:13px}.z0J_8a_notice{color:var(--dsw-alias-label-tertiary);margin:10px 2px 0;font-size:12px;line-height:1.4}";
1161
+ const css = ".z0J_8a_sectionList{margin:0;padding:0;list-style:none}.z0J_8a_market{flex-direction:column;gap:10px;margin-top:12px;display:flex}.z0J_8a_toolbar{display:flex}.z0J_8a_search{min-width:0;font:inherit;color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l2);border-radius:6px;flex:1;padding:6px 10px;font-size:13px;line-height:1.5}.z0J_8a_search::placeholder{color:var(--dsw-alias-label-tertiary)}.z0J_8a_search:focus{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}.z0J_8a_search:disabled{opacity:.5}.z0J_8a_filters{flex-wrap:wrap;gap:6px;display:flex}.z0J_8a_pill{font:inherit;color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l2);cursor:pointer;border-radius:999px;padding:2px 10px;font-size:12px;line-height:1.6}.z0J_8a_pill:hover:enabled{color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-dimmed)}.z0J_8a_pill:disabled{opacity:.5;cursor:default}.z0J_8a_pillActive{color:var(--dsw-alias-bg-layer-3);background:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-primary);font-weight:600}.z0J_8a_pillActive:hover:enabled{color:var(--dsw-alias-bg-layer-3);border-color:var(--dsw-alias-label-primary)}.z0J_8a_resultCount{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:1.4}.z0J_8a_grid{grid-template-columns:repeat(auto-fill,minmax(256px,1fr));gap:8px;margin:0;padding:0;list-style:none;display:grid}.z0J_8a_card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);border-radius:8px;flex-direction:column;gap:6px;min-width:0;padding:12px;display:flex}.z0J_8a_card:hover{border-color:var(--dsw-alias-label-dimmed)}.z0J_8a_cardHead{justify-content:space-between;align-items:baseline;gap:8px;min-width:0;display:flex}.z0J_8a_cardBadges{flex:none;align-items:center;gap:4px;min-width:0;display:flex}.z0J_8a_cardName{color:var(--dsw-alias-label-primary);text-overflow:ellipsis;white-space:nowrap;font-weight:600;overflow:hidden}.z0J_8a_badge{color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-module-platform);border:1px solid var(--dsw-alias-border-l2);white-space:nowrap;border-radius:999px;flex:none;padding:0 8px;font-size:11px;line-height:1.6}.z0J_8a_badgePublished{color:var(--dsw-alias-link-primary,var(--dsw-alias-button-info-fill));border-color:var(--dsw-alias-link-primary,var(--dsw-alias-button-info-fill))}.z0J_8a_badgeInstalled{color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-dimmed);background:var(--dsw-alias-bg-layer-2)}.z0J_8a_cardMeta{min-width:0;color:var(--dsw-alias-label-tertiary);align-items:center;gap:6px;margin:0;font-size:12px;line-height:1.4;display:flex}.z0J_8a_cardCategory{color:var(--dsw-alias-label-secondary);white-space:nowrap;flex:none}.z0J_8a_cardDot{flex:none}.z0J_8a_cardAuthor{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.z0J_8a_cardDescription{color:var(--dsw-alias-label-secondary);-webkit-line-clamp:2;overflow-wrap:anywhere;-webkit-box-orient:vertical;margin:0;font-size:13px;line-height:1.45;display:-webkit-box;overflow:hidden}.z0J_8a_cardFooter{flex-direction:column;gap:6px;margin-top:auto;padding-top:6px;display:flex}.z0J_8a_cardTop{justify-content:space-between;align-items:center;gap:8px;min-width:0;display:flex}.z0J_8a_cardLink{color:var(--dsw-alias-link-primary,var(--dsw-alias-button-info-fill));font-size:13px;text-decoration:none}.z0J_8a_cardLink:hover{text-decoration:underline}.z0J_8a_cardCommand{box-sizing:border-box;width:100%;min-width:0;color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);white-space:nowrap;text-overflow:ellipsis;border-radius:4px;padding:2px 6px;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11px;overflow:hidden}.z0J_8a_installButton{font:inherit;color:var(--dsw-alias-bg-layer-3);background:var(--dsw-alias-button-info-fill);border:1px solid var(--dsw-alias-button-info-fill);cursor:pointer;white-space:nowrap;border-radius:6px;flex:none;padding:4px 12px;font-size:12px;font-weight:600;line-height:1.5}.z0J_8a_installButton:hover:enabled{opacity:.88}.z0J_8a_installButton:disabled{opacity:.5;cursor:default}.z0J_8a_installButtonCopied{color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-dimmed);background:0 0}.z0J_8a_installButtonSecondary{color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-border-l2);background:0 0}.z0J_8a_installButtonSecondary:hover:enabled{border-color:var(--dsw-alias-label-dimmed)}.z0J_8a_cardActions{flex:none;align-items:center;gap:6px;display:flex}.z0J_8a_progress{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:1.4}.z0J_8a_error{color:var(--dsw-alias-label-error,#c53030);overflow-wrap:anywhere;margin:0;font-size:12px;line-height:1.4}.z0J_8a_confirmBody{color:var(--dsw-alias-label-secondary);margin:0;font-size:13px;line-height:1.5}.z0J_8a_modalActions{justify-content:flex-end;gap:8px;margin-top:16px;display:flex}.z0J_8a_dangerButton{background:var(--dsw-alias-label-error,#c53030);border-color:var(--dsw-alias-label-error,#c53030)}.z0J_8a_installNote{color:var(--dsw-alias-label-tertiary);margin:8px 2px 0;font-size:12px;line-height:1.4}.z0J_8a_empty{color:var(--dsw-alias-label-tertiary);margin:0;padding:8px 2px;font-size:13px}.z0J_8a_off{color:var(--dsw-alias-label-tertiary);margin:8px 2px 0;font-size:13px}.z0J_8a_notice{color:var(--dsw-alias-label-tertiary);margin:10px 2px 0;font-size:12px;line-height:1.4}";
1073
1162
  const tagId = "@linxin666/dsh-client-ui-community-plugins/community.module.css";
1074
1163
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
1075
1164
  const tag = document.createElement("style");
@@ -1080,9 +1169,12 @@ window.__ModuleLoader__.load({
1080
1169
  }
1081
1170
  var community_module_css_default = {
1082
1171
  "badge": "z0J_8a_badge",
1172
+ "badgeInstalled": "z0J_8a_badgeInstalled",
1083
1173
  "badgePublished": "z0J_8a_badgePublished",
1084
1174
  "card": "z0J_8a_card",
1175
+ "cardActions": "z0J_8a_cardActions",
1085
1176
  "cardAuthor": "z0J_8a_cardAuthor",
1177
+ "cardBadges": "z0J_8a_cardBadges",
1086
1178
  "cardCategory": "z0J_8a_cardCategory",
1087
1179
  "cardCommand": "z0J_8a_cardCommand",
1088
1180
  "cardDescription": "z0J_8a_cardDescription",
@@ -1093,17 +1185,23 @@ window.__ModuleLoader__.load({
1093
1185
  "cardMeta": "z0J_8a_cardMeta",
1094
1186
  "cardName": "z0J_8a_cardName",
1095
1187
  "cardTop": "z0J_8a_cardTop",
1188
+ "confirmBody": "z0J_8a_confirmBody",
1189
+ "dangerButton": "z0J_8a_dangerButton",
1096
1190
  "empty": "z0J_8a_empty",
1191
+ "error": "z0J_8a_error",
1097
1192
  "filters": "z0J_8a_filters",
1098
1193
  "grid": "z0J_8a_grid",
1099
1194
  "installButton": "z0J_8a_installButton",
1100
1195
  "installButtonCopied": "z0J_8a_installButtonCopied",
1196
+ "installButtonSecondary": "z0J_8a_installButtonSecondary",
1101
1197
  "installNote": "z0J_8a_installNote",
1102
1198
  "market": "z0J_8a_market",
1199
+ "modalActions": "z0J_8a_modalActions",
1103
1200
  "notice": "z0J_8a_notice",
1104
1201
  "off": "z0J_8a_off",
1105
1202
  "pill": "z0J_8a_pill",
1106
1203
  "pillActive": "z0J_8a_pillActive",
1204
+ "progress": "z0J_8a_progress",
1107
1205
  "resultCount": "z0J_8a_resultCount",
1108
1206
  "search": "z0J_8a_search",
1109
1207
  "sectionList": "z0J_8a_sectionList",
@@ -1156,6 +1254,12 @@ window.__ModuleLoader__.load({
1156
1254
  function installCommand(entry) {
1157
1255
  return `dsh plugin --profile web add ${entry.npm ?? entry.repo}`;
1158
1256
  }
1257
+ /** Progress polling cadence while an install is in flight. */
1258
+ const PROGRESS_POLL_MS = 500;
1259
+ /** Extract a displayable reason from an install/uninstall rejection. */
1260
+ function messageOf(reason) {
1261
+ return reason instanceof Error ? reason.message : String(reason);
1262
+ }
1159
1263
  /**
1160
1264
  * Localized entry copy read through the card's `t`. Falls back to the raw
1161
1265
  * entry field when the key is missing — the dictionary only carries the
@@ -1188,6 +1292,102 @@ window.__ModuleLoader__.load({
1188
1292
  const [query, setQuery] = (0, react.useState)("");
1189
1293
  const [category, setCategory] = (0, react.useState)(null);
1190
1294
  const [copiedId, setCopiedId] = (0, react.useState)(null);
1295
+ const bridge = (0, react.useSyncExternalStore)(subscribePluginManager, getPluginManagerSnapshot);
1296
+ const face = props.pluginManager !== void 0 ? props.pluginManager : bridge.face;
1297
+ const faceLoopback = face !== null && face.isLoopback;
1298
+ const [installed, setInstalled] = (0, react.useState)(null);
1299
+ const [pending, setPending] = (0, react.useState)(null);
1300
+ const [progress, setProgress] = (0, react.useState)(null);
1301
+ const [errors, setErrors] = (0, react.useState)({});
1302
+ const [uninstallTarget, setUninstallTarget] = (0, react.useState)(null);
1303
+ (0, react.useEffect)(() => {
1304
+ if (face === null || !face.isLoopback) {
1305
+ setInstalled(null);
1306
+ return;
1307
+ }
1308
+ let alive = true;
1309
+ const refresh = () => {
1310
+ face.list().then((list) => {
1311
+ if (alive) setInstalled(list);
1312
+ }, () => {});
1313
+ };
1314
+ refresh();
1315
+ const unsubscribe = face.onChange(refresh);
1316
+ return () => {
1317
+ alive = false;
1318
+ unsubscribe();
1319
+ };
1320
+ }, [face, faceLoopback]);
1321
+ (0, react.useEffect)(() => {
1322
+ if (face === null || pending?.kind !== "install") {
1323
+ setProgress(null);
1324
+ return;
1325
+ }
1326
+ let alive = true;
1327
+ const poll = () => {
1328
+ face.status().then((item) => {
1329
+ if (alive) setProgress(item);
1330
+ }, () => {});
1331
+ };
1332
+ poll();
1333
+ const timer = setInterval(poll, PROGRESS_POLL_MS);
1334
+ return () => {
1335
+ alive = false;
1336
+ clearInterval(timer);
1337
+ };
1338
+ }, [face, pending?.kind === "install"]);
1339
+ const clearError = (id) => {
1340
+ setErrors((previous) => {
1341
+ if (!(id in previous)) return previous;
1342
+ const next = { ...previous };
1343
+ delete next[id];
1344
+ return next;
1345
+ });
1346
+ };
1347
+ const onInstall = (entry) => {
1348
+ if (face === null || !face.isLoopback || pending !== null) return;
1349
+ clearError(entry.id);
1350
+ setPending({
1351
+ kind: "install",
1352
+ id: entry.id
1353
+ });
1354
+ face.install(installSpec(entry)).then(() => {
1355
+ face.list().then(setInstalled, () => {});
1356
+ }, (reason) => {
1357
+ setErrors((previous) => ({
1358
+ ...previous,
1359
+ [entry.id]: t("installFailed", { reason: messageOf(reason) })
1360
+ }));
1361
+ }).finally(() => {
1362
+ setPending(null);
1363
+ });
1364
+ };
1365
+ const onUninstallConfirm = () => {
1366
+ const target = uninstallTarget;
1367
+ if (face === null || target === null || pending !== null) return;
1368
+ const item = entryInstalled(target, installed ?? []);
1369
+ clearError(target.id);
1370
+ if (item === null) {
1371
+ setUninstallTarget(null);
1372
+ return;
1373
+ }
1374
+ setPending({
1375
+ kind: "uninstall",
1376
+ id: target.id
1377
+ });
1378
+ face.uninstall(item.id).then((list) => {
1379
+ setInstalled(list);
1380
+ setUninstallTarget(null);
1381
+ }, (reason) => {
1382
+ setErrors((previous) => ({
1383
+ ...previous,
1384
+ [target.id]: t("uninstallFailed", { reason: messageOf(reason) })
1385
+ }));
1386
+ setUninstallTarget(null);
1387
+ }).finally(() => {
1388
+ setPending(null);
1389
+ });
1390
+ };
1191
1391
  const copyCommand = (id, command) => {
1192
1392
  const mark = () => {
1193
1393
  setCopiedId(id);
@@ -1211,6 +1411,7 @@ window.__ModuleLoader__.load({
1211
1411
  mark();
1212
1412
  };
1213
1413
  const disabled = !state.writable;
1414
+ const mutationsDisabled = disabled || pending !== null;
1214
1415
  const visible = state.enabled.text !== "false";
1215
1416
  const fieldProps = {
1216
1417
  overriddenLabel: t("settings.overridden"),
@@ -1343,6 +1544,10 @@ window.__ModuleLoader__.load({
1343
1544
  const copied = copiedId === plugin.id;
1344
1545
  const name = entryCopy(t, `name.${plugin.id}`, plugin.nameEn || plugin.name);
1345
1546
  const description = entryCopy(t, `desc.${plugin.id}`, plugin.descriptionEn ?? plugin.description ?? "");
1547
+ const installedItem = faceLoopback ? entryInstalled(plugin, installed ?? []) : null;
1548
+ const isInstalling = pending?.kind === "install" && pending.id === plugin.id;
1549
+ const isUninstalling = pending?.kind === "uninstall" && pending.id === plugin.id;
1550
+ const error = errors[plugin.id];
1346
1551
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
1347
1552
  className: community_module_css_default.card,
1348
1553
  children: [
@@ -1352,10 +1557,16 @@ window.__ModuleLoader__.load({
1352
1557
  className: community_module_css_default.cardName,
1353
1558
  title: name,
1354
1559
  children: name
1355
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1356
- className: plugin.npm ? `${community_module_css_default.badge} ${community_module_css_default.badgePublished}` : community_module_css_default.badge,
1357
- title: plugin.npm ?? plugin.repo,
1358
- children: plugin.npm ? t("badge.published") : t("badge.source")
1560
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1561
+ className: community_module_css_default.cardBadges,
1562
+ children: [installedItem !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1563
+ className: `${community_module_css_default.badge} ${community_module_css_default.badgeInstalled}`,
1564
+ children: t("badge.installed")
1565
+ }) : null, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1566
+ className: plugin.npm ? `${community_module_css_default.badge} ${community_module_css_default.badgePublished}` : community_module_css_default.badge,
1567
+ title: plugin.npm ?? plugin.repo,
1568
+ children: plugin.npm ? t("badge.published") : t("badge.source")
1569
+ })]
1359
1570
  })]
1360
1571
  }),
1361
1572
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
@@ -1383,28 +1594,64 @@ window.__ModuleLoader__.load({
1383
1594
  }) : null,
1384
1595
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1385
1596
  className: community_module_css_default.cardFooter,
1386
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1387
- className: community_module_css_default.cardTop,
1388
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
1389
- className: community_module_css_default.cardLink,
1390
- href: plugin.repo,
1391
- target: "_blank",
1392
- rel: "noreferrer",
1393
- children: t("repository")
1394
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1395
- type: "button",
1396
- className: copied ? `${community_module_css_default.installButton} ${community_module_css_default.installButtonCopied}` : community_module_css_default.installButton,
1597
+ children: [
1598
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1599
+ className: community_module_css_default.cardTop,
1600
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
1601
+ className: community_module_css_default.cardLink,
1602
+ href: plugin.repo,
1603
+ target: "_blank",
1604
+ rel: "noreferrer",
1605
+ children: t("repository")
1606
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1607
+ className: community_module_css_default.cardActions,
1608
+ children: [
1609
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1610
+ type: "button",
1611
+ className: copied ? `${community_module_css_default.installButton} ${community_module_css_default.installButtonCopied}` : faceLoopback ? `${community_module_css_default.installButton} ${community_module_css_default.installButtonSecondary}` : community_module_css_default.installButton,
1612
+ title: command,
1613
+ onClick: () => {
1614
+ copyCommand(plugin.id, command);
1615
+ },
1616
+ children: copied ? t("copied") : t("install")
1617
+ }),
1618
+ faceLoopback && installedItem === null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1619
+ type: "button",
1620
+ className: community_module_css_default.installButton,
1621
+ disabled: mutationsDisabled,
1622
+ onClick: () => {
1623
+ onInstall(plugin);
1624
+ },
1625
+ children: isInstalling ? t("installing") : t("installNow")
1626
+ }) : null,
1627
+ faceLoopback && installedItem !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1628
+ type: "button",
1629
+ className: `${community_module_css_default.installButton} ${community_module_css_default.installButtonSecondary}`,
1630
+ disabled: mutationsDisabled,
1631
+ onClick: () => {
1632
+ setUninstallTarget(plugin);
1633
+ },
1634
+ children: isUninstalling ? t("uninstalling") : t("uninstall")
1635
+ }) : null
1636
+ ]
1637
+ })]
1638
+ }),
1639
+ isInstalling ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1640
+ className: community_module_css_default.progress,
1641
+ role: "status",
1642
+ children: progress !== null && progress.kind !== "idle" ? t(`progress.${progress.stage}`) : t("installing")
1643
+ }) : null,
1644
+ error !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1645
+ className: community_module_css_default.error,
1646
+ role: "alert",
1647
+ children: error
1648
+ }) : null,
1649
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
1650
+ className: community_module_css_default.cardCommand,
1397
1651
  title: command,
1398
- onClick: () => {
1399
- copyCommand(plugin.id, command);
1400
- },
1401
- children: copied ? t("copied") : t("install")
1402
- })]
1403
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
1404
- className: community_module_css_default.cardCommand,
1405
- title: command,
1406
- children: command
1407
- })]
1652
+ children: command
1653
+ })
1654
+ ]
1408
1655
  })
1409
1656
  ]
1410
1657
  }, plugin.id);
@@ -1416,6 +1663,15 @@ window.__ModuleLoader__.load({
1416
1663
  role: "status",
1417
1664
  children: t("off")
1418
1665
  }),
1666
+ face === null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1667
+ className: community_module_css_default.installNote,
1668
+ role: "note",
1669
+ children: t("managerHint")
1670
+ }) : face.isLoopback ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1671
+ className: community_module_css_default.installNote,
1672
+ role: "note",
1673
+ children: t("remoteHint")
1674
+ }),
1419
1675
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1420
1676
  className: community_module_css_default.installNote,
1421
1677
  role: "note",
@@ -1425,13 +1681,41 @@ window.__ModuleLoader__.load({
1425
1681
  className: community_module_css_default.notice,
1426
1682
  role: "note",
1427
1683
  children: t("notice")
1684
+ }),
1685
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
1686
+ open: uninstallTarget !== null,
1687
+ onClose: () => {
1688
+ if (pending === null) setUninstallTarget(null);
1689
+ },
1690
+ title: t("uninstallConfirm.title"),
1691
+ closeLabel: t("cancel"),
1692
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1693
+ className: community_module_css_default.confirmBody,
1694
+ children: t("uninstallConfirm.body", { name: uninstallTarget === null ? "" : entryCopy(t, `name.${uninstallTarget.id}`, uninstallTarget.nameEn || uninstallTarget.name) })
1695
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1696
+ className: community_module_css_default.modalActions,
1697
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1698
+ variant: "outline",
1699
+ disabled: pending !== null,
1700
+ onClick: () => {
1701
+ setUninstallTarget(null);
1702
+ },
1703
+ children: t("cancel")
1704
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1705
+ variant: "primary",
1706
+ className: community_module_css_default.dangerButton,
1707
+ disabled: pending !== null,
1708
+ onClick: onUninstallConfirm,
1709
+ children: pending?.kind === "uninstall" ? t("uninstalling") : t("uninstallConfirm.confirm")
1710
+ })]
1711
+ })]
1428
1712
  })
1429
1713
  ]
1430
1714
  });
1431
1715
  }
1432
1716
  /** Render the community plugin index as a first-level settings page. */
1433
1717
  function CommunityPluginsSection(props) {
1434
- const { t, useCommunityPluginsCard, save, discard, edit, resetField, plugins } = props;
1718
+ const { t, useCommunityPluginsCard, save, discard, edit, resetField, plugins, pluginManager } = props;
1435
1719
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
1436
1720
  className: community_module_css_default.sectionList,
1437
1721
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CommunityPluginsCard, {
@@ -1441,7 +1725,8 @@ window.__ModuleLoader__.load({
1441
1725
  discard,
1442
1726
  edit,
1443
1727
  resetField,
1444
- plugins
1728
+ plugins,
1729
+ pluginManager
1445
1730
  })
1446
1731
  });
1447
1732
  }
@@ -1480,6 +1765,23 @@ window.__ModuleLoader__.load({
1480
1765
  "copy": "复制",
1481
1766
  "copied": "已复制",
1482
1767
  "install": "复制安装命令",
1768
+ "installNow": "安装",
1769
+ "installing": "安装中…",
1770
+ "uninstall": "卸载",
1771
+ "uninstalling": "卸载中…",
1772
+ "cancel": "取消",
1773
+ "badge.installed": "已安装 · 重启后生效",
1774
+ "progress.fetch": "获取插件信息",
1775
+ "progress.download": "下载中",
1776
+ "progress.extract": "解压中",
1777
+ "progress.write": "写入中",
1778
+ "uninstallConfirm.title": "卸载插件",
1779
+ "uninstallConfirm.body": "确定要卸载 {name} 吗?移除将在重启 dsh web 后完全生效。",
1780
+ "uninstallConfirm.confirm": "确认卸载",
1781
+ "installFailed": "安装失败:{reason}",
1782
+ "uninstallFailed": "卸载失败:{reason}",
1783
+ "managerHint": "未检测到「插件管理器」插件:安装 @linxin666/dsh-client-ui-plugin-manager 后,可在本机浏览器中直接从这里安装与卸载。",
1784
+ "remoteHint": "当前是远程浏览器,界面内安装不可用;请在宿主机器的终端执行安装命令。",
1483
1785
  "noMatch": "没有匹配的社区插件。",
1484
1786
  "search.placeholder": "搜索名称、作者或简介",
1485
1787
  "search.label": "搜索社区插件",
@@ -1494,7 +1796,7 @@ window.__ModuleLoader__.load({
1494
1796
  "category.integration": "集成与分享",
1495
1797
  "category.security": "安全与运维",
1496
1798
  "category.utility": "实用工具",
1497
- "installHint": "索引只登记、不安装代码。要运行某个插件,复制它的安装命令到终端执行;安装后,插件自带的开关与配置(若有)会出现在插件配置里。",
1799
+ "installHint": "在本机浏览器且已安装「插件管理器」插件时,可直接点击卡片上的「安装 / 卸载」;否则复制安装命令到终端执行。安装与卸载在重启 dsh web 后生效,插件自带的开关与配置(若有)会出现在插件配置里。",
1498
1800
  "empty": "暂无社区插件登记。",
1499
1801
  "off": "社区插件索引已关闭。",
1500
1802
  "notice": "条目由贡献者自行登记,与 dsh-web-ui 的发布内容无关;使用前请自行评估。"
@@ -1525,6 +1827,23 @@ window.__ModuleLoader__.load({
1525
1827
  "copy": "Copy",
1526
1828
  "copied": "Copied",
1527
1829
  "install": "Copy install command",
1830
+ "installNow": "Install",
1831
+ "installing": "Installing…",
1832
+ "uninstall": "Uninstall",
1833
+ "uninstalling": "Uninstalling…",
1834
+ "cancel": "Cancel",
1835
+ "badge.installed": "Installed · restart to take effect",
1836
+ "progress.fetch": "Fetching plugin info",
1837
+ "progress.download": "Downloading",
1838
+ "progress.extract": "Extracting",
1839
+ "progress.write": "Writing",
1840
+ "uninstallConfirm.title": "Uninstall plugin",
1841
+ "uninstallConfirm.body": "Uninstall {name}? The removal fully takes effect after restarting dsh web.",
1842
+ "uninstallConfirm.confirm": "Confirm uninstall",
1843
+ "installFailed": "Install failed: {reason}",
1844
+ "uninstallFailed": "Uninstall failed: {reason}",
1845
+ "managerHint": "The Plugin manager plugin is not detected: install @linxin666/dsh-client-ui-plugin-manager to install and uninstall entries directly from here in a local browser.",
1846
+ "remoteHint": "This is a remote browser, so in-GUI install is unavailable; run the install command in a terminal on the host machine.",
1528
1847
  "noMatch": "No matching community plugin found.",
1529
1848
  "search.placeholder": "Search name, author or description",
1530
1849
  "search.label": "Search community plugins",
@@ -1539,7 +1858,7 @@ window.__ModuleLoader__.load({
1539
1858
  "category.integration": "Integration & Sharing",
1540
1859
  "category.security": "Security & Ops",
1541
1860
  "category.utility": "Utilities",
1542
- "installHint": "The index only registers entries, it never installs code. To run a plugin, copy its install command into a terminal; once installed, the plugin provides its own switch and config (if any) in the plugin configuration section.",
1861
+ "installHint": "With the Plugin manager plugin present and a local browser, use the Install / Uninstall buttons on each card; otherwise copy the install command into a terminal. Installs and uninstalls take effect after restarting dsh web; once installed, the plugin provides its own switch and config (if any) in the plugin configuration section.",
1543
1862
  "empty": "No community plugins registered yet.",
1544
1863
  "off": "The community plugin index is turned off.",
1545
1864
  "notice": "Entries are contributed by their authors and are separate from dsh-web-ui releases; evaluate before use."
@@ -1579,6 +1898,7 @@ window.__ModuleLoader__.load({
1579
1898
  zh,
1580
1899
  en
1581
1900
  }), "community-plugins: dictionaries");
1901
+ bridgePluginManager(ctx);
1582
1902
  const controller = new CommunityPluginsCardController((ctx.get("webUiSettings") ?? ctx.settingsScope).bind({ namespace: COMMUNITY_PLUGINS_NS }));
1583
1903
  ctx.slots.inject("settings.section", () => {
1584
1904
  const unregister = ctx.slots.register({