dsh-cloudq 0.1.1 → 0.1.3

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.
Files changed (3) hide show
  1. package/README.md +52 -39
  2. package/lib/client.js +396 -110
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1,61 +1,69 @@
1
1
  # dsh-cloudq
2
2
 
3
- 面向 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) 的 CloudQ 集成插件。插件为 Web Profile 提供 CloudQ 模式,内置 `cloudq` Skill,并提供 CloudQ 用量、架构视图、本地凭证配置和插件管理能力。
3
+ English | [简体中文](README-zh.md)
4
4
 
5
- ## 环境要求
5
+ CloudQ integration for [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness). It adds a CloudQ mode to the Web profile, bundles the `cloudq` skill, and provides CloudQ usage and architecture views, local credential setup, and plugin management.
6
+
7
+ ## Screenshots
8
+
9
+ ![CloudQ risk analysis conversation](assets/screenshots/chat-risk-analysis.png)
10
+
11
+ ![CloudQ settings and plugin management](assets/screenshots/settings.png)
12
+
13
+ ## Requirements
6
14
 
7
15
  - Node.js `>=22.19.0`
8
- - DeepSeek Harness `0.1.1-rc.2` 或兼容的更高 `0.1.x` 版本
9
- - `dsh plugin` 命令可调用 `pnpm`
10
- - 可通过 `python3` 命令运行 Python 3
11
- - macOS Linux
16
+ - DeepSeek Harness `0.1.1-rc.2` or a compatible newer `0.1.x` release
17
+ - `pnpm` available to the `dsh plugin` command
18
+ - Python 3 available as `python3`
19
+ - macOS or Linux
12
20
 
13
- ## 安装
21
+ ## Install
14
22
 
15
23
  ```sh
16
24
  dsh plugin --profile web add dsh-cloudq
17
25
  ```
18
26
 
19
- 安装完成后重启 Web Profile:
27
+ Restart the Web profile after installation:
20
28
 
21
29
  ```sh
22
30
  dsh --profile web
23
31
  ```
24
32
 
25
- 打开 DSH 输出的访问地址,即可在对话输入区和侧边栏看到 CloudQ 入口。也可以通过 `/cloudq` 显式调用内置 Skill。
33
+ Open the URL printed by DSH. The conversation input area and sidebar will expose the CloudQ entry. You can also invoke the bundled skill explicitly with `/cloudq`.
26
34
 
27
- ## 升级与卸载
35
+ ## Upgrade and remove
28
36
 
29
37
  ```sh
30
38
  dsh plugin --profile web update dsh-cloudq
31
39
  dsh plugin --profile web remove dsh-cloudq
32
40
  ```
33
41
 
34
- 修改已安装的插件后,需要重启 Web Profile。
42
+ Restart the Web profile after changing the installed package set.
35
43
 
36
- ## 凭证配置
44
+ ## Credentials
37
45
 
38
- 设置卡片支持配置腾讯云 `SecretId` `SecretKey` 凭证。
46
+ The settings card accepts a Tencent Cloud `SecretId`/`SecretKey` pair.
39
47
 
40
- - 凭证保存在本机 `~/.tencent-cloudq/credential.json`,文件权限仅允许当前用户访问。
41
- - 敏感凭证通过标准输入传递给 Python 辅助脚本,不会放入命令行参数。
42
- - 浏览器接口仅返回凭证状态和脱敏标识,不返回凭证内容或本地凭证路径。
43
- - 可通过退出登录操作删除本地凭证。
48
+ - Credentials are stored locally at `~/.tencent-cloudq/credential.json` with owner-only permissions.
49
+ - Secret values are sent to Python helpers through standard input, never command-line arguments.
50
+ - Browser APIs return only credential state and masked identifiers; local credential paths and secret values are not returned.
51
+ - Use the logout action to remove the stored credential.
44
52
 
45
- 请遵循最小权限原则,只授予 CloudQ 操作所需的权限。内置 Skill 可以调用云管理的读写操作,请在批准前检查每项操作的具体内容。
53
+ Follow least-privilege: grant only the permissions required for the CloudQ operations you intend to run. The bundled skill can invoke read and write cloud-management operations; review each action before approving it.
46
54
 
47
- ## 安全机制
55
+ ## Security model
48
56
 
49
- - Host API 只接受来自回环地址的同源请求。
50
- - JSON 请求体大小限制为 64 KiB
51
- - Python 辅助脚本的输出大小受到限制,非预期的内部错误不会返回到浏览器。
52
- - 远程数据通过 DOM 文本节点渲染,不使用 HTML 注入。
53
- - 下载链接必须使用 HTTPS
54
- - npm 包不包含凭证、Token 或本地环境文件。
57
+ - Host APIs accept only loopback, same-origin requests.
58
+ - JSON request bodies are limited to 64 KiB.
59
+ - Python helper output is bounded, and unexpected internal errors are not returned to the browser.
60
+ - Remote values are inserted with DOM text nodes rather than HTML injection.
61
+ - Download links require HTTPS.
62
+ - No credentials, tokens, or local environment files are included in the npm package.
55
63
 
56
- 如需报告安全问题,请通过 [GitHub Issues](https://github.com/TencentCloud/cloudq-for-dsh/issues) 提交,且不要附带真实凭证。
64
+ Report security issues through [GitHub Issues](https://github.com/TencentCloud/cloudq-for-dsh/issues) without including live credentials.
57
65
 
58
- ## 本地开发
66
+ ## Development
59
67
 
60
68
  ```sh
61
69
  pnpm install
@@ -67,23 +75,28 @@ pnpm run check:client
67
75
  npm pack --dry-run --registry=https://registry.npmjs.org/
68
76
  ```
69
77
 
70
- npm 包内包含预构建的 Host Web Client 产物、Bundle Patch、CloudQ 运行时图标以及内置 Skill。用户从 Registry 安装时不需要执行构建脚本。
78
+ The npm package ships prebuilt Host and Web client artifacts, the bundle patch, the runtime logo, and the bundled skill. Registry installation runs no build scripts.
71
79
 
72
- ## 目录结构
80
+ ## Repository layout
73
81
 
74
82
  ```text
75
- src/ Host Web Client 源码
76
- skills/cloudq/ 内置 CloudQ Skill Python 辅助脚本
77
- assets/cloudq.png Host 提供的运行时图标
78
- scripts/ 构建和发布检查脚本
79
- tests/ 单元测试、集成测试与包契约测试
80
- cordis.patch.yml DSH Bundle 配置层
83
+ src/ Host and Web client source
84
+ skills/cloudq/ Bundled CloudQ skill and Python helpers
85
+ assets/cloudq.png Runtime logo served by the Host
86
+ scripts/ Build and release checks
87
+ tests/ Unit, integration, and package-contract tests
88
+ cordis.patch.yml DSH bundle layer
81
89
  ```
82
90
 
83
- ## 发布
91
+ ## Release
92
+
93
+ Source is reviewed and versioned in [TencentCloud/cloudq-for-dsh](https://github.com/TencentCloud/cloudq-for-dsh). An npm version is published to the official registry only after the repository checks and the package-install smoke test pass.
94
+
95
+ ## More documentation
84
96
 
85
- 源码在 [TencentCloud/cloudq-for-dsh](https://github.com/TencentCloud/cloudq-for-dsh) 完成评审和版本管理。只有仓库检查与安装包冒烟测试全部通过后,才可将 npm 版本发布到官方 Registry。
97
+ - Changelog: [`CHANGELOG.md`](CHANGELOG.md)
98
+ - Development & maintenance guide: [`DEVELOPMENT.md`](DEVELOPMENT.md)
86
99
 
87
- ## 许可证
100
+ ## License
88
101
 
89
- 本项目采用 MIT 许可证,详情请参阅 [`LICENSE`](LICENSE)
102
+ MIT. See [`LICENSE`](LICENSE).
package/lib/client.js CHANGED
@@ -432,10 +432,6 @@ window.__ModuleLoader__.load({
432
432
  font-size: 11px;
433
433
  font-weight: 500;
434
434
  }
435
- .dsh-cloudq-pm__row-tag--self {
436
- background: var(--dsw-alias-state-business-primary, #315efb);
437
- color: #fff;
438
- }
439
435
  .dsh-cloudq-pm__row-tag--off {
440
436
  background: var(--dsw-alias-state-danger, #d54941);
441
437
  color: #fff;
@@ -1143,12 +1139,145 @@ body.dsh-cloudq-mode-active [class*=composerHero] [class*=root][class*=hero] {
1143
1139
  margin-bottom: 16px;
1144
1140
  }
1145
1141
  .dsh-cloudq-usage__card {
1146
- flex: 1;
1142
+ flex: 0 1 auto;
1143
+ min-width: 240px;
1147
1144
  padding: 14px 16px;
1148
1145
  border: 1px solid var(--dsw-alias-border-l2, #e1e4e8);
1149
1146
  border-radius: 10px;
1150
1147
  background: var(--dsw-alias-bg-sub, #f7f8fa);
1151
1148
  }
1149
+ .dsh-cloudq-usage__card-value-row {
1150
+ display: flex;
1151
+ align-items: baseline;
1152
+ gap: 6px;
1153
+ flex-wrap: wrap;
1154
+ }
1155
+ .dsh-cloudq-usage__card-unit {
1156
+ font-size: 13px;
1157
+ color: var(--dsw-alias-label-secondary, #626a76);
1158
+ }
1159
+ .dsh-cloudq-usage__free-badge {
1160
+ position: relative;
1161
+ display: inline-flex;
1162
+ align-items: center;
1163
+ gap: 4px;
1164
+ padding: 1px 8px;
1165
+ border-radius: 999px;
1166
+ background: var(--dsw-alias-state-business-primary-bg, #e8efff);
1167
+ color: var(--dsw-alias-state-business-primary, #315efb);
1168
+ font-size: 12px;
1169
+ cursor: help;
1170
+ }
1171
+ .dsh-cloudq-usage__free-badge .dsh-cloudq-tooltip {
1172
+ opacity: 0;
1173
+ visibility: hidden;
1174
+ transition: opacity .12s ease;
1175
+ }
1176
+ .dsh-cloudq-usage__free-badge:hover .dsh-cloudq-tooltip,
1177
+ .dsh-cloudq-usage__free-badge:focus-visible .dsh-cloudq-tooltip {
1178
+ opacity: 1;
1179
+ visibility: visible;
1180
+ }
1181
+ .dsh-cloudq-usage__range {
1182
+ display: flex;
1183
+ align-items: center;
1184
+ gap: 8px;
1185
+ flex-wrap: wrap;
1186
+ margin-bottom: 12px;
1187
+ }
1188
+ .dsh-cloudq-usage__range-picker {
1189
+ position: relative;
1190
+ }
1191
+ .dsh-cloudq-usage__range-btn {
1192
+ display: inline-flex;
1193
+ align-items: center;
1194
+ gap: 8px;
1195
+ height: 28px;
1196
+ padding: 0 10px;
1197
+ border: 1px solid var(--dsw-alias-interactive-border, #cdd3da);
1198
+ border-radius: 6px;
1199
+ background: var(--dsw-alias-bg-card, #fff);
1200
+ color: var(--dsw-alias-label-primary, #1d2129);
1201
+ font-size: 12px;
1202
+ cursor: pointer;
1203
+ transition: border-color .15s ease, box-shadow .15s ease;
1204
+ }
1205
+ .dsh-cloudq-usage__range-btn:hover {
1206
+ border-color: var(--dsw-alias-state-business-primary, #315efb);
1207
+ }
1208
+ .dsh-cloudq-usage__range-picker.is-open .dsh-cloudq-usage__range-btn {
1209
+ border-color: var(--dsw-alias-state-business-primary, #315efb);
1210
+ box-shadow: 0 0 0 2px rgba(49, 94, 251, .12);
1211
+ }
1212
+ .dsh-cloudq-usage__range-btn-chevron {
1213
+ font-size: 10px;
1214
+ color: var(--dsw-alias-label-tertiary, #9aa1ab);
1215
+ transition: transform .15s ease;
1216
+ }
1217
+ .dsh-cloudq-usage__range-picker.is-open .dsh-cloudq-usage__range-btn-chevron {
1218
+ transform: rotate(180deg);
1219
+ }
1220
+ .dsh-cloudq-usage__range-menu {
1221
+ position: absolute;
1222
+ z-index: 1400;
1223
+ top: calc(100% + 6px);
1224
+ left: 0;
1225
+ min-width: 136px;
1226
+ padding: 4px;
1227
+ border: 1px solid var(--dsw-alias-border-l2, #e1e4e8);
1228
+ border-radius: 8px;
1229
+ background: var(--dsw-alias-bg-card, #fff);
1230
+ box-shadow: 0 8px 24px rgba(29, 33, 41, .12);
1231
+ }
1232
+ .dsh-cloudq-usage__range-option {
1233
+ display: flex;
1234
+ align-items: center;
1235
+ gap: 4px;
1236
+ width: 100%;
1237
+ padding: 6px 8px;
1238
+ border: 0;
1239
+ border-radius: 5px;
1240
+ background: transparent;
1241
+ color: var(--dsw-alias-label-primary, #1d2129);
1242
+ font-size: 12px;
1243
+ text-align: left;
1244
+ cursor: pointer;
1245
+ }
1246
+ .dsh-cloudq-usage__range-option:hover {
1247
+ background: var(--dsw-alias-bg-sub, #f2f4f8);
1248
+ }
1249
+ .dsh-cloudq-usage__range-option.is-active {
1250
+ color: var(--dsw-alias-state-business-primary, #315efb);
1251
+ font-weight: 500;
1252
+ }
1253
+ .dsh-cloudq-usage__range-option-check {
1254
+ width: 14px;
1255
+ flex: none;
1256
+ color: var(--dsw-alias-state-business-primary, #315efb);
1257
+ visibility: hidden;
1258
+ }
1259
+ .dsh-cloudq-usage__range-option.is-active .dsh-cloudq-usage__range-option-check {
1260
+ visibility: visible;
1261
+ }
1262
+ .dsh-cloudq-usage__date {
1263
+ height: 28px;
1264
+ padding: 0 8px;
1265
+ border: 1px solid var(--dsw-alias-interactive-border, #cdd3da);
1266
+ border-radius: 6px;
1267
+ background: var(--dsw-alias-bg-card, #fff);
1268
+ color: var(--dsw-alias-label-primary, #1d2129);
1269
+ font-size: 12px;
1270
+ outline: none;
1271
+ transition: border-color .15s ease, box-shadow .15s ease;
1272
+ }
1273
+ .dsh-cloudq-usage__date:focus {
1274
+ border-color: var(--dsw-alias-state-business-primary, #315efb);
1275
+ box-shadow: 0 0 0 2px rgba(49, 94, 251, .12);
1276
+ }
1277
+ .dsh-cloudq-usage__range-error {
1278
+ font-size: 12px;
1279
+ color: var(--dsw-alias-state-danger, #d54941);
1280
+ }
1152
1281
  .dsh-cloudq-usage__card-label {
1153
1282
  font-size: 12px;
1154
1283
  color: var(--dsw-alias-label-tertiary, #9aa1ab);
@@ -1751,22 +1880,58 @@ display: none;
1751
1880
  const API_ARCH_DIRECTORIES = "/api/dsh-cloudq/architecture/directories";
1752
1881
  const API_ARCH_LIST = "/api/dsh-cloudq/architecture/list";
1753
1882
  const CLOUDQ_ARCH_FOLDER_KEY = "dsh-cloudq.architecture-folder-id";
1754
- function defaultUsageRange() {
1883
+ const USAGE_RANGE_PRESETS = [
1884
+ {
1885
+ id: "3d",
1886
+ label: "最近 3 天",
1887
+ days: 3
1888
+ },
1889
+ {
1890
+ id: "7d",
1891
+ label: "最近 1 周",
1892
+ days: 7
1893
+ },
1894
+ {
1895
+ id: "30d",
1896
+ label: "最近 1 月",
1897
+ days: 30
1898
+ },
1899
+ {
1900
+ id: "custom",
1901
+ label: "自定义",
1902
+ days: 0
1903
+ }
1904
+ ];
1905
+ let usagePreset = "7d";
1906
+ let usageCustomStart = "";
1907
+ let usageCustomEnd = "";
1908
+ function formatUsageTimestamp(d) {
1909
+ const pad = (n) => String(n).padStart(2, "0");
1910
+ return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
1911
+ }
1912
+ /** Resolve the current range to API timestamps; null when custom dates are incomplete. */
1913
+ function usageRangeTimestamps() {
1914
+ if (usagePreset === "custom") {
1915
+ if (!usageCustomStart || !usageCustomEnd) return null;
1916
+ return {
1917
+ start: `${usageCustomStart} 00:00:00`,
1918
+ end: `${usageCustomEnd} 23:59:59`
1919
+ };
1920
+ }
1921
+ const preset = USAGE_RANGE_PRESETS.find((item) => item.id === usagePreset) ?? USAGE_RANGE_PRESETS[1];
1755
1922
  const end = /* @__PURE__ */ new Date();
1756
- const start = /* @__PURE__ */ new Date(end.getTime() - 6048e5);
1757
- const fmt = (d) => {
1758
- const pad = (n) => String(n).padStart(2, "0");
1759
- return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
1760
- };
1761
1923
  return {
1762
- start: fmt(start),
1763
- end: fmt(end)
1924
+ start: formatUsageTimestamp(/* @__PURE__ */ new Date(end.getTime() - preset.days * 24 * 60 * 60 * 1e3)),
1925
+ end: formatUsageTimestamp(end)
1764
1926
  };
1765
1927
  }
1766
1928
  function formatCredits(value) {
1767
1929
  const num = typeof value === "number" ? value : Number(value);
1768
1930
  if (!Number.isFinite(num)) return "—";
1769
- return num.toFixed(2);
1931
+ return num.toLocaleString("en-US", {
1932
+ minimumFractionDigits: 2,
1933
+ maximumFractionDigits: 2
1934
+ });
1770
1935
  }
1771
1936
  function formatDateTime(raw) {
1772
1937
  if (!raw) return "—";
@@ -1828,93 +1993,221 @@ display: none;
1828
1993
  let architecturePickerCleanup = null;
1829
1994
  function renderUsageView() {
1830
1995
  panelBody.textContent = "";
1831
- const range = defaultUsageRange();
1832
- const hint = document.createElement("div");
1833
- hint.className = "dsh-cloudq-panel__hint";
1834
- hint.textContent = "正在加载用量…";
1835
- panelBody.appendChild(hint);
1836
- cloudqRequest(`${API_USAGE}?start=${encodeURIComponent(range.start)}&end=${encodeURIComponent(range.end)}`).then((data) => {
1837
- if (panelView !== "usage" || !panelExpanded) return;
1838
- panelBody.textContent = "";
1839
- const overview = data.overview ?? {};
1840
- const rows = Array.isArray(data.detail) ? data.detail : [];
1841
- const overviewWrap = document.createElement("div");
1842
- overviewWrap.className = "dsh-cloudq-usage__overview";
1843
- const createUsageCard = (labelText, valueText, detailText) => {
1844
- const card = document.createElement("div");
1845
- card.className = "dsh-cloudq-usage__card";
1846
- const label = document.createElement("div");
1847
- label.className = "dsh-cloudq-usage__card-label";
1848
- label.textContent = labelText;
1849
- const value = document.createElement("div");
1850
- value.className = "dsh-cloudq-usage__card-value";
1851
- value.textContent = valueText;
1852
- const detail = document.createElement("div");
1853
- detail.className = "dsh-cloudq-usage__card-sub";
1854
- detail.textContent = detailText;
1855
- card.append(label, value, detail);
1856
- return card;
1857
- };
1858
- const cardTotal = createUsageCard("剩余积分", formatCredits(overview.TotalCredits), `自 ${overview.TotalStartDate ?? "—"} 起累计`);
1859
- const monthly = overview.MonthlyCredits;
1860
- const cardMonthly = createUsageCard("本月积分", monthly === null || monthly === void 0 ? "—" : formatCredits(monthly), overview.MonthlyStartDate ? `起于 ${overview.MonthlyStartDate}` : "暂无月度数据");
1861
- overviewWrap.append(cardTotal, cardMonthly);
1862
- const table = document.createElement("table");
1863
- table.className = "dsh-cloudq-usage__table";
1864
- const thead = document.createElement("thead");
1865
- const headerRow = document.createElement("tr");
1866
- for (const heading of [
1867
- "时间",
1868
- "渠道",
1869
- "内容",
1870
- "积分"
1871
- ]) {
1872
- const cell = document.createElement("th");
1873
- cell.textContent = heading;
1874
- headerRow.appendChild(cell);
1875
- }
1876
- thead.appendChild(headerRow);
1877
- table.appendChild(thead);
1878
- const tbody = document.createElement("tbody");
1879
- if (rows.length === 0) {
1880
- const tr = document.createElement("tr");
1881
- const empty = document.createElement("td");
1882
- empty.colSpan = 4;
1883
- empty.className = "dsh-cloudq-usage__table__empty";
1884
- empty.textContent = " 7 天暂无用量记录";
1885
- tr.appendChild(empty);
1886
- tbody.appendChild(tr);
1887
- } else for (const row of rows) {
1888
- const tr = document.createElement("tr");
1889
- const time = document.createElement("td");
1890
- time.textContent = formatDateTime(row.StartTime);
1891
- const channel = document.createElement("td");
1892
- channel.textContent = row.Channel ?? "—";
1893
- const message = document.createElement("td");
1894
- const msgSpan = document.createElement("span");
1895
- msgSpan.className = "dsh-cloudq-usage__table__message";
1896
- msgSpan.textContent = row.Message ?? "";
1897
- message.appendChild(msgSpan);
1898
- const credits = document.createElement("td");
1899
- credits.className = "dsh-cloudq-usage__table__credits";
1900
- credits.textContent = formatCredits(row.Credits);
1901
- tr.appendChild(time);
1902
- tr.appendChild(channel);
1903
- tr.appendChild(message);
1904
- tr.appendChild(credits);
1905
- tbody.appendChild(tr);
1996
+ const overviewWrap = document.createElement("div");
1997
+ overviewWrap.className = "dsh-cloudq-usage__overview";
1998
+ const card = document.createElement("div");
1999
+ card.className = "dsh-cloudq-usage__card";
2000
+ const label = document.createElement("div");
2001
+ label.className = "dsh-cloudq-usage__card-label";
2002
+ label.textContent = "累计消耗 Credit";
2003
+ const valueRow = document.createElement("div");
2004
+ valueRow.className = "dsh-cloudq-usage__card-value-row";
2005
+ const value = document.createElement("span");
2006
+ value.className = "dsh-cloudq-usage__card-value";
2007
+ value.textContent = "";
2008
+ const unit = document.createElement("span");
2009
+ unit.className = "dsh-cloudq-usage__card-unit";
2010
+ unit.textContent = "";
2011
+ const freeBadge = document.createElement("span");
2012
+ freeBadge.className = "dsh-cloudq-usage__free-badge";
2013
+ freeBadge.textContent = "公测期免费 ⓘ";
2014
+ freeBadge.tabIndex = 0;
2015
+ const freeBadgeTip = document.createElement("span");
2016
+ freeBadgeTip.className = "dsh-cloudq-tooltip";
2017
+ freeBadgeTip.setAttribute("role", "tooltip");
2018
+ freeBadgeTip.textContent = "正式计费将提前 1 个月通知";
2019
+ freeBadge.appendChild(freeBadgeTip);
2020
+ valueRow.append(value, unit, freeBadge);
2021
+ card.append(label, valueRow);
2022
+ overviewWrap.appendChild(card);
2023
+ panelBody.appendChild(overviewWrap);
2024
+ const rangeBar = document.createElement("div");
2025
+ rangeBar.className = "dsh-cloudq-usage__range";
2026
+ const picker = document.createElement("div");
2027
+ picker.className = "dsh-cloudq-usage__range-picker";
2028
+ const pickerBtn = document.createElement("button");
2029
+ pickerBtn.type = "button";
2030
+ pickerBtn.className = "dsh-cloudq-usage__range-btn";
2031
+ pickerBtn.setAttribute("aria-haspopup", "listbox");
2032
+ pickerBtn.setAttribute("aria-expanded", "false");
2033
+ pickerBtn.setAttribute("aria-label", "用量时间范围");
2034
+ const pickerLabel = document.createElement("span");
2035
+ pickerLabel.className = "dsh-cloudq-usage__range-btn-label";
2036
+ const pickerChevron = document.createElement("span");
2037
+ pickerChevron.className = "dsh-cloudq-usage__range-btn-chevron";
2038
+ pickerChevron.textContent = "▾";
2039
+ pickerBtn.append(pickerLabel, pickerChevron);
2040
+ const menu = document.createElement("div");
2041
+ menu.className = "dsh-cloudq-usage__range-menu";
2042
+ menu.setAttribute("role", "listbox");
2043
+ menu.hidden = true;
2044
+ const optionButtons = /* @__PURE__ */ new Map();
2045
+ for (const preset of USAGE_RANGE_PRESETS) {
2046
+ const option = document.createElement("button");
2047
+ option.type = "button";
2048
+ option.className = "dsh-cloudq-usage__range-option";
2049
+ option.setAttribute("role", "option");
2050
+ const check = document.createElement("span");
2051
+ check.className = "dsh-cloudq-usage__range-option-check";
2052
+ check.textContent = "✓";
2053
+ const text = document.createElement("span");
2054
+ text.textContent = preset.label;
2055
+ option.append(check, text);
2056
+ option.addEventListener("click", () => {
2057
+ usagePreset = preset.id;
2058
+ syncPicker();
2059
+ closeMenu();
2060
+ syncCustomVisibility();
2061
+ loadUsage();
2062
+ });
2063
+ optionButtons.set(preset.id, option);
2064
+ menu.appendChild(option);
2065
+ }
2066
+ const syncPicker = () => {
2067
+ const active = USAGE_RANGE_PRESETS.find((item) => item.id === usagePreset) ?? USAGE_RANGE_PRESETS[1];
2068
+ pickerLabel.textContent = active.label;
2069
+ for (const [id, option] of optionButtons) {
2070
+ const isActive = id === active.id;
2071
+ option.classList.toggle("is-active", isActive);
2072
+ option.setAttribute("aria-selected", String(isActive));
1906
2073
  }
1907
- table.appendChild(tbody);
1908
- panelBody.appendChild(overviewWrap);
1909
- panelBody.appendChild(table);
1910
- }).catch((error) => {
1911
- if (!panelExpanded) return;
1912
- panelBody.textContent = "";
1913
- const err = document.createElement("div");
1914
- err.className = "dsh-cloudq-panel__error";
1915
- err.textContent = cloudqPanelErrorText(error, "用量加载失败");
1916
- panelBody.appendChild(err);
2074
+ };
2075
+ const pickerAbort = new AbortController();
2076
+ const closeMenu = () => {
2077
+ menu.hidden = true;
2078
+ picker.classList.remove("is-open");
2079
+ pickerBtn.setAttribute("aria-expanded", "false");
2080
+ };
2081
+ pickerBtn.addEventListener("click", () => {
2082
+ const willOpen = menu.hidden;
2083
+ menu.hidden = !willOpen;
2084
+ picker.classList.toggle("is-open", willOpen);
2085
+ pickerBtn.setAttribute("aria-expanded", String(willOpen));
1917
2086
  });
2087
+ document.addEventListener("mousedown", (event) => {
2088
+ if (!menu.isConnected) {
2089
+ pickerAbort.abort();
2090
+ return;
2091
+ }
2092
+ if (!picker.contains(event.target)) closeMenu();
2093
+ }, { signal: pickerAbort.signal });
2094
+ document.addEventListener("keydown", (event) => {
2095
+ if (!menu.isConnected) return;
2096
+ if (event.key === "Escape") closeMenu();
2097
+ }, { signal: pickerAbort.signal });
2098
+ syncPicker();
2099
+ const startDate = document.createElement("input");
2100
+ startDate.type = "date";
2101
+ startDate.className = "dsh-cloudq-usage__date";
2102
+ startDate.setAttribute("aria-label", "开始日期");
2103
+ startDate.value = usageCustomStart;
2104
+ const endDate = document.createElement("input");
2105
+ endDate.type = "date";
2106
+ endDate.className = "dsh-cloudq-usage__date";
2107
+ endDate.setAttribute("aria-label", "结束日期");
2108
+ endDate.value = usageCustomEnd;
2109
+ const rangeError = document.createElement("span");
2110
+ rangeError.className = "dsh-cloudq-usage__range-error";
2111
+ const syncCustomVisibility = () => {
2112
+ const custom = usagePreset === "custom";
2113
+ startDate.hidden = !custom;
2114
+ endDate.hidden = !custom;
2115
+ };
2116
+ const tableWrap = document.createElement("div");
2117
+ const loadUsage = () => {
2118
+ const range = usageRangeTimestamps();
2119
+ rangeError.textContent = "";
2120
+ if (!range) {
2121
+ tableWrap.textContent = "";
2122
+ const hint = document.createElement("div");
2123
+ hint.className = "dsh-cloudq-panel__hint";
2124
+ hint.textContent = "请选择开始日期与结束日期。";
2125
+ tableWrap.appendChild(hint);
2126
+ return;
2127
+ }
2128
+ if (usagePreset === "custom" && usageCustomStart > usageCustomEnd) {
2129
+ rangeError.textContent = "开始日期不能晚于结束日期。";
2130
+ return;
2131
+ }
2132
+ tableWrap.textContent = "";
2133
+ const hint = document.createElement("div");
2134
+ hint.className = "dsh-cloudq-panel__hint";
2135
+ hint.textContent = "正在加载用量…";
2136
+ tableWrap.appendChild(hint);
2137
+ cloudqRequest(`${API_USAGE}?start=${encodeURIComponent(range.start)}&end=${encodeURIComponent(range.end)}`).then((data) => {
2138
+ if (panelView !== "usage" || !panelExpanded) return;
2139
+ value.textContent = formatCredits(data.overview?.TotalCredits);
2140
+ const rows = Array.isArray(data.detail) ? data.detail : [];
2141
+ tableWrap.textContent = "";
2142
+ const table = document.createElement("table");
2143
+ table.className = "dsh-cloudq-usage__table";
2144
+ const thead = document.createElement("thead");
2145
+ const headerRow = document.createElement("tr");
2146
+ for (const heading of [
2147
+ "时间",
2148
+ "渠道",
2149
+ "内容",
2150
+ "积分"
2151
+ ]) {
2152
+ const cell = document.createElement("th");
2153
+ cell.textContent = heading;
2154
+ headerRow.appendChild(cell);
2155
+ }
2156
+ thead.appendChild(headerRow);
2157
+ table.appendChild(thead);
2158
+ const tbody = document.createElement("tbody");
2159
+ if (rows.length === 0) {
2160
+ const tr = document.createElement("tr");
2161
+ const empty = document.createElement("td");
2162
+ empty.colSpan = 4;
2163
+ empty.className = "dsh-cloudq-usage__table__empty";
2164
+ empty.textContent = "当前时间范围内暂无用量记录";
2165
+ tr.appendChild(empty);
2166
+ tbody.appendChild(tr);
2167
+ } else for (const row of rows) {
2168
+ const tr = document.createElement("tr");
2169
+ const time = document.createElement("td");
2170
+ time.textContent = formatDateTime(row.StartTime);
2171
+ const channel = document.createElement("td");
2172
+ channel.textContent = row.Channel ?? "—";
2173
+ const message = document.createElement("td");
2174
+ const msgSpan = document.createElement("span");
2175
+ msgSpan.className = "dsh-cloudq-usage__table__message";
2176
+ msgSpan.textContent = row.Message ?? "";
2177
+ message.appendChild(msgSpan);
2178
+ const credits = document.createElement("td");
2179
+ credits.className = "dsh-cloudq-usage__table__credits";
2180
+ credits.textContent = formatCredits(row.Credits);
2181
+ tr.appendChild(time);
2182
+ tr.appendChild(channel);
2183
+ tr.appendChild(message);
2184
+ tr.appendChild(credits);
2185
+ tbody.appendChild(tr);
2186
+ }
2187
+ table.appendChild(tbody);
2188
+ tableWrap.appendChild(table);
2189
+ }).catch((error) => {
2190
+ if (panelView !== "usage" || !panelExpanded) return;
2191
+ tableWrap.textContent = "";
2192
+ const err = document.createElement("div");
2193
+ err.className = "dsh-cloudq-panel__error";
2194
+ err.textContent = cloudqPanelErrorText(error, "用量加载失败");
2195
+ tableWrap.appendChild(err);
2196
+ });
2197
+ };
2198
+ const onCustomDateChange = () => {
2199
+ usageCustomStart = startDate.value;
2200
+ usageCustomEnd = endDate.value;
2201
+ loadUsage();
2202
+ };
2203
+ startDate.addEventListener("change", onCustomDateChange);
2204
+ endDate.addEventListener("change", onCustomDateChange);
2205
+ picker.append(pickerBtn, menu);
2206
+ rangeBar.append(picker, startDate, endDate, rangeError);
2207
+ syncCustomVisibility();
2208
+ panelBody.appendChild(rangeBar);
2209
+ panelBody.appendChild(tableWrap);
2210
+ loadUsage();
1918
2211
  }
1919
2212
  const INSPIRATION_TABS = [
1920
2213
  {
@@ -3206,17 +3499,10 @@ display: none;
3206
3499
  className: "dsh-cloudq-pm__row-info",
3207
3500
  children: [react_jsx_runtime.jsx("div", {
3208
3501
  className: "dsh-cloudq-pm__row-name",
3209
- children: [
3210
- plugin.name,
3211
- plugin.self ? react_jsx_runtime.jsx("span", {
3212
- className: "dsh-cloudq-pm__row-tag dsh-cloudq-pm__row-tag--self",
3213
- children: "本插件"
3214
- }) : null,
3215
- !on ? react_jsx_runtime.jsx("span", {
3216
- className: "dsh-cloudq-pm__row-tag dsh-cloudq-pm__row-tag--off",
3217
- children: "已停用"
3218
- }) : null
3219
- ]
3502
+ children: [plugin.name, !on ? react_jsx_runtime.jsx("span", {
3503
+ className: "dsh-cloudq-pm__row-tag dsh-cloudq-pm__row-tag--off",
3504
+ children: "已停用"
3505
+ }) : null]
3220
3506
  }), react_jsx_runtime.jsx("div", {
3221
3507
  className: "dsh-cloudq-pm__row-bundle",
3222
3508
  children: `${plugin.id} · ${plugin.bundle}`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-cloudq",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "CloudQ integration for DeepSeek Harness with secure credential, workspace, and plugin-management surfaces",
5
5
  "license": "MIT",
6
6
  "type": "module",