@morehao/dsh-github-trending 0.0.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/client.js ADDED
@@ -0,0 +1,1408 @@
1
+ /* AUTO-GENERATED from client/ by scripts/build-client.mjs — do not edit. */
2
+ /* eslint-disable */
3
+ window.__ModuleLoader__.load({
4
+ id: "@morehao/dsh-github-trending",
5
+ factory(require) {
6
+ var module = { exports: {} };
7
+ var exports = module.exports;
8
+ "use strict";
9
+ var __create = Object.create;
10
+ var __defProp = Object.defineProperty;
11
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
12
+ var __getOwnPropNames = Object.getOwnPropertyNames;
13
+ var __getProtoOf = Object.getPrototypeOf;
14
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
15
+ var __export = (target, all) => {
16
+ for (var name in all)
17
+ __defProp(target, name, { get: all[name], enumerable: true });
18
+ };
19
+ var __copyProps = (to, from, except, desc) => {
20
+ if (from && typeof from === "object" || typeof from === "function") {
21
+ for (let key of __getOwnPropNames(from))
22
+ if (!__hasOwnProp.call(to, key) && key !== except)
23
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
24
+ }
25
+ return to;
26
+ };
27
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
28
+ // If the importer is in node compatibility mode or this is not an ESM
29
+ // file that has been converted to a CommonJS file using a Babel-
30
+ // compatible transform (i.e. "__esModule" has not been set), then set
31
+ // "default" to the CommonJS "module.exports" for node compatibility.
32
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
33
+ mod
34
+ ));
35
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
36
+
37
+ // client/index.ts
38
+ var index_exports = {};
39
+ __export(index_exports, {
40
+ PANEL_ID: () => PANEL_ID,
41
+ apply: () => apply,
42
+ inject: () => inject
43
+ });
44
+ module.exports = __toCommonJS(index_exports);
45
+
46
+ // client/i18n.ts
47
+ var DICT = {
48
+ zh: {
49
+ "section.title": "GitHub 趋势",
50
+ "section.subtitle": "每天定时把 GitHub Trending 抓取、总结并提交到你的仓库;内容全部由本地核心产出。",
51
+ "tab.settings": "设置",
52
+ "tab.runs": "运行",
53
+ "card.source.title": "内容来源",
54
+ "card.source.hint": "唯一来源是本地二进制",
55
+ "card.source.desc": "插件 spawn 核心的一次性运行:核心完成抓取、总结、渲染与提交。不连接服务端,也不提供容器来源。",
56
+ "source.local": "本地二进制",
57
+ "source.local.hint": "与插件同机安装,跑完即退",
58
+ "core.label": "核心",
59
+ "core.recheck": "重新检测",
60
+ "core.checking": "检测中…",
61
+ "core.ready": "可用",
62
+ "core.unavailable": "不可用",
63
+ "core.incompatible": "版本不兼容",
64
+ "core.cached": "已缓存",
65
+ "core.notDownloaded": "未下载",
66
+ "core.download": "下载核心",
67
+ "core.downloading": "下载中…",
68
+ "core.redownload": "重新下载",
69
+ "core.downloaded": "核心已下载并校验通过",
70
+ "core.installed": "已安装",
71
+ "core.missing": "未安装",
72
+ "core.order": "解析顺序:TRENDING_DIGEST_CORE(仅开发/排障)→ Release 资产(缓存命中直接用,否则下载并校验);都不可用时报 CORE_UNAVAILABLE。",
73
+ "coexist.title": "检测到本机另有 CLI",
74
+ "coexist.hint": "插件仍使用自带核心,不切换、不接管它的定时。",
75
+ "card.model.title": "模型",
76
+ "card.model.hint": "字段与核心的 model.v1 一一对应",
77
+ "model.quickfill": "沿用 DSH 配置",
78
+ "model.quickfill.desc": "读取宿主的 provider 目录与凭据状态,填充端点、模型与密钥来源。密钥只在本机内存中传递,不落盘。",
79
+ "model.provider": "Provider",
80
+ "model.baseURL": "Base URL",
81
+ "model.baseURL.placeholder": "留空使用 provider 默认端点",
82
+ "model.model": "模型",
83
+ "model.model.placeholder": "模型 ID",
84
+ "model.mode": "密钥来源",
85
+ "model.mode.direct": "DSH 凭据 / 手动密钥",
86
+ "model.mode.proxy": "回环代理(凭据不可导出)",
87
+ "model.keyRef": "凭据引用",
88
+ "model.params": "参数",
89
+ "model.preflight": "连通性预检",
90
+ "model.preflighting": "预检中…",
91
+ "model.allowDegraded": "模型失败时允许降级为原文",
92
+ "card.repo.title": "归档仓库",
93
+ "card.repo.hint": "内容写到哪里、怎么提交",
94
+ "repo.path": "仓库路径",
95
+ "repo.path.placeholder": "绝对路径,例如 /Users/you/notes",
96
+ "repo.pick": "选择目录",
97
+ "repo.baseBranch": "目标分支",
98
+ "repo.remote": "远端",
99
+ "repo.outputDir": "输出目录",
100
+ "repo.commitMode": "提交方式",
101
+ "repo.commitMode.branch": "分支 + PR",
102
+ "repo.commitMode.direct": "直接提交",
103
+ "repo.autoMerge": "自动合并 PR",
104
+ "repo.titleTemplate": "PR 标题模板",
105
+ "card.auth.title": "GitHub 认证",
106
+ "card.auth.hint": "只申请 public_repo",
107
+ "auth.mode": "方式",
108
+ "auth.device": "Device Flow",
109
+ "auth.pat": "PAT",
110
+ "auth.gh": "本机 gh",
111
+ "auth.tokenRef": "凭据引用",
112
+ "auth.status": "状态",
113
+ "auth.configured": "已配置",
114
+ "auth.missing": "未配置",
115
+ "card.scope.title": "榜单范围",
116
+ "scope.period": "周期",
117
+ "scope.daily": "每日",
118
+ "scope.weekly": "每周",
119
+ "scope.languages": "语言",
120
+ "scope.topN": "每语言条数",
121
+ "card.schedule.title": "调度",
122
+ "schedule.enabled": "定时运行",
123
+ "schedule.time": "时间",
124
+ "schedule.timezone": "时区",
125
+ "schedule.next": "下次运行",
126
+ "schedule.source": "由插件自己的 60 s tick 驱动(宿主 schedule 是会话本地提醒,覆盖不到无人值守)",
127
+ "settings.save": "保存设置",
128
+ "settings.saving": "保存中…",
129
+ "settings.saved": "已保存",
130
+ "settings.import": "从本机 CLI 导入",
131
+ "settings.import.done": "已导入:{fields}",
132
+ "settings.import.empty": "没有找到可导入的 CLI 配置",
133
+ "settings.troubleshoot": "排障",
134
+ "settings.copyConfig": "复制脱敏配置",
135
+ "settings.copied": "已复制",
136
+ "run.now": "本次",
137
+ "run.history": "历史",
138
+ "run.button": "立即运行",
139
+ "run.dryRun": "试跑(不提交)",
140
+ "run.cancel": "取消运行",
141
+ "run.running": "运行中",
142
+ "run.idle": "空闲",
143
+ "run.steps": "步骤",
144
+ "step.resolve": "定位核心",
145
+ "step.handshake": "握手 version --json",
146
+ "step.spawn": "spawn 核心,配置经通道传入",
147
+ "step.fetch": "核心:抓取 Trending",
148
+ "step.summarize": "核心:模型生成中文简介",
149
+ "step.render": "核心:渲染文件",
150
+ "step.commit": "核心:提交 / 推送",
151
+ "step.pr": "核心:创建并合并 PR",
152
+ "step.state": "插件:写记录、刷新界面",
153
+ "run.last.title": "最近结果",
154
+ "run.last.none": "还没有运行记录",
155
+ "run.usage.files": "文件",
156
+ "run.usage.duration": "耗时",
157
+ "run.usage.core": "核心",
158
+ "run.usage.hash": "内容哈希",
159
+ "run.usage.branch": "分支",
160
+ "run.usage.pr": "PR",
161
+ "run.error.title": "最近错误",
162
+ "run.history.title": "历史记录",
163
+ "run.history.hint": "最近 30 条,超出即裁剪",
164
+ "run.history.empty": "还没有历史记录",
165
+ "col.date": "时间",
166
+ "col.result": "结果",
167
+ "col.files": "文件",
168
+ "col.duration": "耗时",
169
+ "col.commit": "提交 / PR",
170
+ "col.error": "错误",
171
+ "state.ok": "成功",
172
+ "state.skipped": "无变更",
173
+ "state.failed": "失败",
174
+ "state.dry": "试跑",
175
+ "widget.title": "GitHub 趋势",
176
+ "widget.next": "下次",
177
+ "widget.last": "上次",
178
+ "widget.never": "从未运行",
179
+ "widget.run": "立即运行",
180
+ "widget.open": "打开面板",
181
+ "quickfill.title": "沿用 DSH 已配置的模型",
182
+ "quickfill.empty": "宿主没有可用的 provider 目录",
183
+ "quickfill.hint": "选择 provider 与模型后写入下面的字段;不会自动改你已有的配置。",
184
+ "quickfill.mode.direct": "密钥可导出,直传核心",
185
+ "quickfill.mode.proxy": "凭据不可导出,走回环代理",
186
+ "quickfill.mode.missing": "缺少可用凭据,需要手动填写",
187
+ "quickfill.apply": "使用这个模型",
188
+ "quickfill.close": "关闭",
189
+ "quickfill.loading": "读取中…",
190
+ "error.prefix": "错误",
191
+ "label.optional": "可选"
192
+ },
193
+ en: {
194
+ "section.title": "GitHub Trending",
195
+ "section.subtitle": "Fetch, summarize and commit GitHub Trending into your own repo on a daily schedule. All content comes from the local core binary.",
196
+ "tab.settings": "Settings",
197
+ "tab.runs": "Runs",
198
+ "card.source.title": "Content source",
199
+ "card.source.hint": "one source: the local binary",
200
+ "card.source.desc": "The plugin spawns one core run: fetch, summarize, render and commit happen inside the core. No server, no container.",
201
+ "source.local": "Local binary",
202
+ "source.local.hint": "installed next to the plugin, exits when done",
203
+ "core.label": "Core",
204
+ "core.recheck": "Re-check",
205
+ "core.checking": "Checking…",
206
+ "core.ready": "ready",
207
+ "core.unavailable": "unavailable",
208
+ "core.incompatible": "incompatible",
209
+ "core.installed": "installed",
210
+ "core.missing": "missing",
211
+ "core.cached": "cached",
212
+ "core.notDownloaded": "not downloaded",
213
+ "core.download": "Download core",
214
+ "core.downloading": "Downloading…",
215
+ "core.redownload": "Re-download",
216
+ "core.downloaded": "Core downloaded and verified",
217
+ "core.order": "Resolution order: TRENDING_DIGEST_CORE (dev/troubleshooting only) → Release asset (cache first, otherwise download and verify); otherwise CORE_UNAVAILABLE.",
218
+ "coexist.title": "Another CLI found on this machine",
219
+ "coexist.hint": "The plugin still uses its bundled core; it never switches or takes over that CLI schedule.",
220
+ "card.model.title": "Model",
221
+ "card.model.hint": "field-for-field with the core model.v1",
222
+ "model.quickfill": "Reuse DSH configuration",
223
+ "model.quickfill.desc": "Reads the host provider catalog and credential state to fill endpoint, model and key source. Keys stay in memory.",
224
+ "model.provider": "Provider",
225
+ "model.baseURL": "Base URL",
226
+ "model.baseURL.placeholder": "empty = provider default endpoint",
227
+ "model.model": "Model",
228
+ "model.model.placeholder": "model id",
229
+ "model.mode": "Key source",
230
+ "model.mode.direct": "DSH credential / manual key",
231
+ "model.mode.proxy": "Loopback proxy (credential not exportable)",
232
+ "model.keyRef": "Credential ref",
233
+ "model.params": "Parameters",
234
+ "model.preflight": "Test connection",
235
+ "model.preflighting": "Testing…",
236
+ "model.allowDegraded": "Allow falling back to original text when the model fails",
237
+ "card.repo.title": "Archive repository",
238
+ "card.repo.hint": "where content lands and how it is committed",
239
+ "repo.path": "Repository path",
240
+ "repo.path.placeholder": "absolute path, e.g. /Users/you/notes",
241
+ "repo.pick": "Choose folder",
242
+ "repo.baseBranch": "Base branch",
243
+ "repo.remote": "Remote",
244
+ "repo.outputDir": "Output dir",
245
+ "repo.commitMode": "Commit mode",
246
+ "repo.commitMode.branch": "Branch + PR",
247
+ "repo.commitMode.direct": "Direct commit",
248
+ "repo.autoMerge": "Auto-merge PR",
249
+ "repo.titleTemplate": "PR title template",
250
+ "card.auth.title": "GitHub authentication",
251
+ "card.auth.hint": "public_repo only",
252
+ "auth.mode": "Mode",
253
+ "auth.device": "Device flow",
254
+ "auth.pat": "PAT",
255
+ "auth.gh": "Local gh",
256
+ "auth.tokenRef": "Credential ref",
257
+ "auth.status": "Status",
258
+ "auth.configured": "configured",
259
+ "auth.missing": "missing",
260
+ "card.scope.title": "Trending scope",
261
+ "scope.period": "Period",
262
+ "scope.daily": "Daily",
263
+ "scope.weekly": "Weekly",
264
+ "scope.languages": "Languages",
265
+ "scope.topN": "Per language",
266
+ "card.schedule.title": "Schedule",
267
+ "schedule.enabled": "Scheduled runs",
268
+ "schedule.time": "Time",
269
+ "schedule.timezone": "Time zone",
270
+ "schedule.next": "Next run",
271
+ "schedule.source": "Driven by the plugin’s own 60 s tick (host schedule is session-local and cannot cover unattended runs)",
272
+ "settings.save": "Save",
273
+ "settings.saving": "Saving…",
274
+ "settings.saved": "Saved",
275
+ "settings.import": "Import from local CLI",
276
+ "settings.import.done": "Imported: {fields}",
277
+ "settings.import.empty": "No importable CLI config found",
278
+ "settings.troubleshoot": "Troubleshooting",
279
+ "settings.copyConfig": "Copy redacted config",
280
+ "settings.copied": "Copied",
281
+ "run.now": "This run",
282
+ "run.history": "History",
283
+ "run.button": "Run now",
284
+ "run.dryRun": "Dry run (no commit)",
285
+ "run.cancel": "Cancel run",
286
+ "run.running": "Running",
287
+ "run.idle": "Idle",
288
+ "run.steps": "Steps",
289
+ "step.resolve": "Locate core",
290
+ "step.handshake": "Handshake version --json",
291
+ "step.spawn": "Spawn core, config through channel",
292
+ "step.fetch": "Core: fetch trending",
293
+ "step.summarize": "Core: model summaries",
294
+ "step.render": "Core: render files",
295
+ "step.commit": "Core: commit / push",
296
+ "step.pr": "Core: open & merge PR",
297
+ "step.state": "Plugin: record and refresh",
298
+ "run.last.title": "Last result",
299
+ "run.last.none": "No runs yet",
300
+ "run.usage.files": "Files",
301
+ "run.usage.duration": "Duration",
302
+ "run.usage.core": "Core",
303
+ "run.usage.hash": "Content hash",
304
+ "run.usage.branch": "Branch",
305
+ "run.usage.pr": "PR",
306
+ "run.error.title": "Last error",
307
+ "run.history.title": "History",
308
+ "run.history.hint": "last 30 records",
309
+ "run.history.empty": "No history yet",
310
+ "col.date": "Time",
311
+ "col.result": "Result",
312
+ "col.files": "Files",
313
+ "col.duration": "Duration",
314
+ "col.commit": "Commit / PR",
315
+ "col.error": "Error",
316
+ "state.ok": "ok",
317
+ "state.skipped": "no change",
318
+ "state.failed": "failed",
319
+ "state.dry": "dry run",
320
+ "widget.title": "GitHub Trending",
321
+ "widget.next": "Next",
322
+ "widget.last": "Last",
323
+ "widget.never": "never",
324
+ "widget.run": "Run",
325
+ "widget.open": "Open panel",
326
+ "quickfill.title": "Reuse a configured DSH model",
327
+ "quickfill.empty": "No provider catalog available",
328
+ "quickfill.hint": "Pick a provider and model; existing fields are only overwritten after you confirm.",
329
+ "quickfill.mode.direct": "exportable key, passed to the core",
330
+ "quickfill.mode.proxy": "credential not exportable, uses the loopback proxy",
331
+ "quickfill.mode.missing": "no usable credential, fill it in manually",
332
+ "quickfill.apply": "Use this model",
333
+ "quickfill.close": "Close",
334
+ "quickfill.loading": "Loading…",
335
+ "error.prefix": "Error",
336
+ "label.optional": "optional"
337
+ }
338
+ };
339
+ function activeLang(locale) {
340
+ try {
341
+ const code = locale?.getLocale?.().active ?? locale?.lang;
342
+ return code === "en" ? "en" : "zh";
343
+ } catch {
344
+ return "zh";
345
+ }
346
+ }
347
+ function buildTranslate(locale) {
348
+ const lang = activeLang(locale);
349
+ return (key, params) => {
350
+ const template = DICT[lang][key] ?? DICT.zh[key] ?? key;
351
+ if (params === void 0) return template;
352
+ return template.replace(/\{(\w+)\}/g, (match, name) => name in params ? String(params[name]) : match);
353
+ };
354
+ }
355
+
356
+ // client/styles.ts
357
+ var STYLE_ID = "dsh-github-trending-style";
358
+ var CSS = `
359
+ .dshgt-root{--gt-fg:var(--dsh-fg,#1f2328);--gt-muted:var(--dsh-muted,#6b7280);--gt-bg:var(--dsh-bg,#fff);--gt-card:var(--dsh-bg-subtle,#f8fafc);--gt-border:var(--dsh-border,#e5e7eb);--gt-accent:var(--dsh-accent,#2563eb);--gt-ok:#16a34a;--gt-warn:#d97706;--gt-err:#dc2626;color:var(--gt-fg);font-size:13px;line-height:1.5;display:flex;flex-direction:column;gap:12px;}
360
+ .dshgt-root *{box-sizing:border-box;}
361
+ .dshgt-head{display:flex;align-items:baseline;gap:10px;flex-wrap:wrap;}
362
+ .dshgt-title{font-size:15px;font-weight:600;}
363
+ .dshgt-sub{color:var(--gt-muted);font-size:12px;}
364
+ .dshgt-tabs{display:flex;gap:6px;border-bottom:1px solid var(--gt-border);}
365
+ .dshgt-tab{appearance:none;border:none;background:none;padding:6px 10px;font:inherit;color:var(--gt-muted);cursor:pointer;border-bottom:2px solid transparent;}
366
+ .dshgt-tab[aria-selected="true"]{color:var(--gt-fg);border-bottom-color:var(--gt-accent);font-weight:600;}
367
+ .dshgt-card{border:1px solid var(--gt-border);border-radius:10px;background:var(--gt-card);padding:12px 14px;display:flex;flex-direction:column;gap:8px;}
368
+ .dshgt-card-h{display:flex;align-items:center;gap:8px;flex-wrap:wrap;}
369
+ .dshgt-card-h h3{margin:0;font-size:13px;font-weight:600;}
370
+ .dshgt-hint{color:var(--gt-muted);font-size:12px;}
371
+ .dshgt-desc{margin:0;color:var(--gt-muted);font-size:12px;}
372
+ .dshgt-row{display:flex;gap:10px;align-items:center;min-height:28px;flex-wrap:wrap;}
373
+ .dshgt-row>label.dshgt-label{width:96px;color:var(--gt-muted);flex:none;font-size:12px;}
374
+ .dshgt-inline{display:flex;gap:8px;align-items:center;flex-wrap:wrap;}
375
+ .dshgt-input,.dshgt-select,.dshgt-textarea{font:inherit;color:inherit;background:var(--gt-bg);border:1px solid var(--gt-border);border-radius:7px;padding:5px 8px;min-width:0;}
376
+ .dshgt-input:focus,.dshgt-select:focus,.dshgt-textarea:focus{outline:2px solid color-mix(in srgb,var(--gt-accent) 35%,transparent);outline-offset:1px;}
377
+ .dshgt-mono{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;}
378
+ .dshgt-btn{appearance:none;font:inherit;cursor:pointer;border-radius:7px;border:1px solid var(--gt-border);background:var(--gt-bg);padding:5px 10px;color:inherit;}
379
+ .dshgt-btn:hover:not(:disabled){border-color:color-mix(in srgb,var(--gt-accent) 45%,var(--gt-border));}
380
+ .dshgt-btn:disabled{opacity:.55;cursor:not-allowed;}
381
+ .dshgt-btn-primary{background:var(--gt-accent);border-color:var(--gt-accent);color:#fff;}
382
+ .dshgt-btn-danger{background:transparent;border-color:var(--gt-err);color:var(--gt-err);}
383
+ .dshgt-btn-sm{padding:3px 8px;font-size:12px;}
384
+ .dshgt-pill{display:inline-flex;align-items:center;gap:6px;border:1px solid var(--gt-border);border-radius:999px;padding:2px 9px;font-size:12px;background:var(--gt-bg);}
385
+ .dshgt-dot{width:7px;height:7px;border-radius:50%;background:var(--gt-muted);flex:none;}
386
+ .dshgt-dot.ok{background:var(--gt-ok);} .dshgt-dot.warn{background:var(--gt-warn);} .dshgt-dot.err{background:var(--gt-err);}
387
+ .dshgt-alert{border:1px solid var(--gt-border);border-left:3px solid var(--gt-warn);border-radius:8px;padding:8px 10px;background:color-mix(in srgb,var(--gt-warn) 8%,var(--gt-bg));font-size:12px;}
388
+ .dshgt-alert.err{border-left-color:var(--gt-err);background:color-mix(in srgb,var(--gt-err) 8%,var(--gt-bg));}
389
+ .dshgt-alert.ok{border-left-color:var(--gt-ok);background:color-mix(in srgb,var(--gt-ok) 8%,var(--gt-bg));}
390
+ .dshgt-alert b{display:block;margin-bottom:2px;font-size:12px;}
391
+ .dshgt-steps{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:4px;}
392
+ .dshgt-step{display:flex;align-items:center;gap:8px;font-size:12px;color:var(--gt-muted);}
393
+ .dshgt-step .st{margin-left:auto;font-size:11px;}
394
+ .dshgt-step[data-state="done"]{color:var(--gt-fg);} .dshgt-step[data-state="done"] .st{color:var(--gt-ok);}
395
+ .dshgt-step[data-state="active"]{color:var(--gt-fg);} .dshgt-step[data-state="active"] .st{color:var(--gt-accent);}
396
+ .dshgt-step[data-state="failed"] .st{color:var(--gt-err);}
397
+ .dshgt-kv{display:grid;grid-template-columns:auto 1fr;gap:3px 12px;margin:0;font-size:12px;}
398
+ .dshgt-kv dt{color:var(--gt-muted);} .dshgt-kv dd{margin:0;}
399
+ .dshgt-table{width:100%;border-collapse:collapse;font-size:12px;}
400
+ .dshgt-table th,.dshgt-table td{text-align:left;padding:5px 8px;border-bottom:1px solid var(--gt-border);vertical-align:top;}
401
+ .dshgt-table th{color:var(--gt-muted);font-weight:500;}
402
+ .dshgt-chips{display:flex;gap:6px;flex-wrap:wrap;}
403
+ .dshgt-chip{appearance:none;font:inherit;cursor:pointer;border:1px solid var(--gt-border);background:var(--gt-bg);border-radius:999px;padding:2px 10px;font-size:12px;color:var(--gt-muted);}
404
+ .dshgt-chip[aria-pressed="true"]{background:color-mix(in srgb,var(--gt-accent) 14%,var(--gt-bg));border-color:var(--gt-accent);color:var(--gt-fg);}
405
+ .dshgt-switch{appearance:none;cursor:pointer;width:36px;height:20px;border-radius:999px;border:1px solid var(--gt-border);background:var(--gt-muted);position:relative;transition:background .15s;}
406
+ .dshgt-switch::after{content:"";position:absolute;top:2px;left:2px;width:14px;height:14px;border-radius:50%;background:#fff;transition:transform .15s;}
407
+ .dshgt-switch[aria-checked="true"]{background:var(--gt-accent);}
408
+ .dshgt-switch[aria-checked="true"]::after{transform:translateX(16px);}
409
+ .dshgt-widget{border:1px solid var(--gt-border);border-radius:10px;padding:10px 12px;background:var(--gt-card);display:flex;flex-direction:column;gap:6px;font-size:12px;}
410
+ .dshgt-widget h4{margin:0;font-size:12px;font-weight:600;}
411
+ .dshgt-footer{display:flex;gap:8px;align-items:center;flex-wrap:wrap;}
412
+ .dshgt-monobox{margin:0;padding:8px;border-radius:7px;background:color-mix(in srgb,var(--gt-fg) 6%,var(--gt-bg));max-height:220px;overflow:auto;white-space:pre-wrap;word-break:break-word;}
413
+ .dshgt-modal-mask{position:fixed;inset:0;background:rgba(15,23,42,.38);display:flex;align-items:center;justify-content:center;z-index:2147483000;}
414
+ .dshgt-modal{background:var(--gt-bg);border-radius:12px;border:1px solid var(--gt-border);max-width:640px;width:calc(100% - 32px);max-height:80vh;overflow:auto;padding:16px;display:flex;flex-direction:column;gap:10px;box-shadow:0 20px 50px rgba(15,23,42,.25);}
415
+ `;
416
+ function injectStyles() {
417
+ if (typeof document === "undefined") return () => void 0;
418
+ const existing = document.getElementById(STYLE_ID);
419
+ if (existing !== null) return () => void 0;
420
+ const style = document.createElement("style");
421
+ style.id = STYLE_ID;
422
+ style.textContent = CSS;
423
+ document.head.appendChild(style);
424
+ return () => {
425
+ style.remove();
426
+ };
427
+ }
428
+
429
+ // client/ui.ts
430
+ var import_react = __toESM(require("react"), 1);
431
+ var h = import_react.default.createElement;
432
+ function Card(props) {
433
+ const { title, hint, extra, children } = props;
434
+ return h(
435
+ "section",
436
+ { className: "dshgt-card" },
437
+ h(
438
+ "div",
439
+ { className: "dshgt-card-h" },
440
+ h("h3", null, title),
441
+ hint === void 0 ? null : h("span", { className: "dshgt-hint" }, hint),
442
+ extra === void 0 ? null : h("span", { style: { marginLeft: "auto" } }, extra)
443
+ ),
444
+ children
445
+ );
446
+ }
447
+ function Row(props) {
448
+ return h(
449
+ "div",
450
+ { className: "dshgt-row" },
451
+ props.label === void 0 ? null : h("label", { className: "dshgt-label" }, props.label),
452
+ props.children
453
+ );
454
+ }
455
+ function Inline(props) {
456
+ return h("div", { className: "dshgt-inline" }, props.children);
457
+ }
458
+ function Input(props) {
459
+ const { value, onInput, placeholder, mono, width, password, ...rest } = props;
460
+ return h("input", {
461
+ ...rest,
462
+ className: `dshgt-input${mono === true ? " dshgt-mono" : ""}`,
463
+ ...width === void 0 ? {} : { style: { width } },
464
+ ...value === void 0 ? {} : { value },
465
+ ...placeholder === void 0 ? {} : { placeholder },
466
+ ...password === true ? { type: "password" } : {},
467
+ onInput: (event) => onInput?.(event.target.value)
468
+ });
469
+ }
470
+ function Select(props) {
471
+ return h(
472
+ "select",
473
+ {
474
+ className: "dshgt-select",
475
+ value: props.value,
476
+ onChange: (event) => props.onChange?.(event.target.value)
477
+ },
478
+ props.options.map((option) => h("option", { key: option.value, value: option.value }, option.label))
479
+ );
480
+ }
481
+ function Btn(props) {
482
+ const classes = ["dshgt-btn"];
483
+ if (props.variant === "primary") classes.push("dshgt-btn-primary");
484
+ if (props.variant === "danger") classes.push("dshgt-btn-danger");
485
+ if (props.small === true) classes.push("dshgt-btn-sm");
486
+ return h(
487
+ "button",
488
+ {
489
+ type: "button",
490
+ className: classes.join(" "),
491
+ onClick: props.onClick,
492
+ disabled: props.disabled === true,
493
+ ...props.title === void 0 ? {} : { title: props.title }
494
+ },
495
+ props.children
496
+ );
497
+ }
498
+ function Pill(props) {
499
+ return h(
500
+ "span",
501
+ { className: `dshgt-pill${props.mono === true ? " dshgt-mono" : ""}` },
502
+ h("span", { className: `dshgt-dot ${props.kind ?? ""}`.trim() }),
503
+ props.children
504
+ );
505
+ }
506
+ function Alert(props) {
507
+ return h(
508
+ "div",
509
+ { className: `dshgt-alert${props.kind === "err" ? " err" : props.kind === "ok" ? " ok" : ""}` },
510
+ props.title === void 0 ? null : h("b", null, props.title),
511
+ h("div", null, props.children)
512
+ );
513
+ }
514
+ function Toggle(props) {
515
+ return h(
516
+ "label",
517
+ { className: "dshgt-inline", style: { gap: 6 } },
518
+ h("button", {
519
+ type: "button",
520
+ role: "switch",
521
+ className: "dshgt-switch",
522
+ "aria-checked": props.checked ? "true" : "false",
523
+ onClick: () => props.onChange(!props.checked)
524
+ }),
525
+ props.label === void 0 ? null : h("span", { className: "dshgt-hint" }, props.label)
526
+ );
527
+ }
528
+ function Chips(props) {
529
+ return h(
530
+ "div",
531
+ { className: "dshgt-chips" },
532
+ props.values.map(
533
+ (value) => h(
534
+ "button",
535
+ {
536
+ key: value,
537
+ type: "button",
538
+ className: "dshgt-chip",
539
+ "aria-pressed": props.selected.includes(value) ? "true" : "false",
540
+ onClick: () => props.onChange(
541
+ props.selected.includes(value) ? props.selected.filter((item) => item !== value) : [...props.selected, value]
542
+ )
543
+ },
544
+ value
545
+ )
546
+ )
547
+ );
548
+ }
549
+ function KV(props) {
550
+ return h(
551
+ "dl",
552
+ { className: "dshgt-kv" },
553
+ props.items.flatMap(([key, value], index) => [h("dt", { key: `k${index}` }, key), h("dd", { key: `v${index}` }, value)])
554
+ );
555
+ }
556
+ function duration(ms) {
557
+ if (ms <= 0) return "—";
558
+ if (ms < 1e3) return `${ms}ms`;
559
+ return `${(ms / 1e3).toFixed(1)}s`;
560
+ }
561
+ function shortHash(hash) {
562
+ return typeof hash === "string" && hash !== "" ? hash.slice(0, 10) : "—";
563
+ }
564
+ function localTime(iso) {
565
+ if (iso === null || iso === void 0) return "—";
566
+ const date = new Date(iso);
567
+ if (Number.isNaN(date.getTime())) return "—";
568
+ return date.toLocaleString();
569
+ }
570
+
571
+ // client/api.ts
572
+ var API = "/api/morehao/github-trending";
573
+ var HEADER = "x-dsh-github-trending";
574
+ async function apiCall(method, path, body, options = {}) {
575
+ const response = await fetch(`${API}${path}`, {
576
+ method,
577
+ headers: { "content-type": "application/json", [HEADER]: "1" },
578
+ body: body === void 0 ? void 0 : JSON.stringify(body),
579
+ cache: "no-store",
580
+ ...options.signal === void 0 ? {} : { signal: options.signal }
581
+ });
582
+ let json = {};
583
+ try {
584
+ json = await response.json();
585
+ } catch {
586
+ json = {};
587
+ }
588
+ if (!response.ok || json.ok === false) {
589
+ const error = new Error(typeof json.message === "string" ? json.message : `HTTP ${response.status}`);
590
+ error.code = typeof json.code === "string" ? json.code : void 0;
591
+ throw error;
592
+ }
593
+ return json;
594
+ }
595
+
596
+ // client/store.ts
597
+ var POLL_MS = 5e3;
598
+ function useStore() {
599
+ const [state, setState] = import_react.default.useState(null);
600
+ const [error, setError] = import_react.default.useState(null);
601
+ const [loading, setLoading] = import_react.default.useState(true);
602
+ const mounted = import_react.default.useRef(true);
603
+ const refresh = import_react.default.useCallback(async (options = {}) => {
604
+ if (options.quiet !== true) setLoading(true);
605
+ try {
606
+ const payload = await apiCall("GET", "/state");
607
+ if (!mounted.current) return;
608
+ setState(payload);
609
+ setError(null);
610
+ } catch (failure) {
611
+ if (!mounted.current) return;
612
+ setError(failure instanceof Error ? failure.message : String(failure));
613
+ } finally {
614
+ if (mounted.current) setLoading(false);
615
+ }
616
+ }, []);
617
+ import_react.default.useEffect(() => {
618
+ mounted.current = true;
619
+ void refresh();
620
+ const timer = setInterval(() => void refresh({ quiet: true }), POLL_MS);
621
+ return () => {
622
+ mounted.current = false;
623
+ clearInterval(timer);
624
+ };
625
+ }, [refresh]);
626
+ return { state, error, loading, refresh };
627
+ }
628
+
629
+ // client/settings.ts
630
+ var LANGUAGES = ["all", "typescript", "python", "go", "rust", "java", "c", "cpp", "javascript", "csharp"];
631
+ function SettingsView(props) {
632
+ const { t, store } = props;
633
+ const state = store.state;
634
+ const [draft, setDraft] = import_react.default.useState(null);
635
+ const [dirty, setDirty] = import_react.default.useState(false);
636
+ const [saving, setSaving] = import_react.default.useState(false);
637
+ const [saved, setSaved] = import_react.default.useState(false);
638
+ const [problems, setProblems] = import_react.default.useState([]);
639
+ const [message, setMessage] = import_react.default.useState(null);
640
+ const [preflight, setPreflight] = import_react.default.useState(null);
641
+ const [preflighting, setPreflighting] = import_react.default.useState(false);
642
+ const [rechecking, setRechecking] = import_react.default.useState(false);
643
+ const [downloading, setDownloading] = import_react.default.useState(false);
644
+ const [quickOpen, setQuickOpen] = import_react.default.useState(false);
645
+ import_react.default.useEffect(() => {
646
+ if (state !== null && !dirty) setDraft(state.settings);
647
+ }, [state, dirty]);
648
+ const update = (mutate) => {
649
+ if (draft === null) return;
650
+ const next = JSON.parse(JSON.stringify(draft));
651
+ mutate(next);
652
+ setDraft(next);
653
+ setDirty(true);
654
+ setSaved(false);
655
+ };
656
+ const save = async () => {
657
+ if (draft === null) return;
658
+ setSaving(true);
659
+ setProblems([]);
660
+ try {
661
+ const result = await apiCall(
662
+ "POST",
663
+ "/config",
664
+ { settings: draft }
665
+ );
666
+ if (result.saved) {
667
+ setDraft(result.settings);
668
+ setDirty(false);
669
+ setSaved(true);
670
+ setMessage(null);
671
+ await store.refresh({ quiet: true });
672
+ } else {
673
+ setProblems(result.problems ?? []);
674
+ }
675
+ } catch (error) {
676
+ setMessage(error instanceof Error ? error.message : String(error));
677
+ } finally {
678
+ setSaving(false);
679
+ }
680
+ };
681
+ const runPreflight = async () => {
682
+ if (draft === null) return;
683
+ setPreflighting(true);
684
+ setPreflight(null);
685
+ try {
686
+ const result = await apiCall("POST", "/preflight", { settings: draft });
687
+ setPreflight(result.preflight);
688
+ } catch (error) {
689
+ setPreflight({ ok: false, message: error instanceof Error ? error.message : String(error), durationMs: 0, mode: draft.model.mode });
690
+ } finally {
691
+ setPreflighting(false);
692
+ }
693
+ };
694
+ const recheck = async () => {
695
+ setRechecking(true);
696
+ try {
697
+ await apiCall("GET", "/core?fresh=1");
698
+ await store.refresh({ quiet: true });
699
+ } catch {
700
+ } finally {
701
+ setRechecking(false);
702
+ }
703
+ };
704
+ const downloadCore = async () => {
705
+ setDownloading(true);
706
+ setProblems([]);
707
+ setMessage(null);
708
+ try {
709
+ await apiCall("POST", "/core/download", {});
710
+ setMessage(t("core.downloaded"));
711
+ await store.refresh({ quiet: true });
712
+ } catch (error) {
713
+ setMessage(error instanceof Error ? error.message : String(error));
714
+ } finally {
715
+ setDownloading(false);
716
+ }
717
+ };
718
+ const importCli = async () => {
719
+ try {
720
+ const result = await apiCall(
721
+ "POST",
722
+ "/import",
723
+ {}
724
+ );
725
+ const payload = result.import;
726
+ if (payload.ok && payload.settings !== void 0) {
727
+ setDraft(payload.settings);
728
+ setDirty(false);
729
+ setSaved(true);
730
+ setMessage(t("settings.import.done", { fields: payload.imported.join("、") }));
731
+ await store.refresh({ quiet: true });
732
+ } else {
733
+ setMessage(payload.problems[0] ?? t("settings.import.empty"));
734
+ }
735
+ } catch (error) {
736
+ setMessage(error instanceof Error ? error.message : String(error));
737
+ }
738
+ };
739
+ const copyConfig = () => {
740
+ if (draft === null) return;
741
+ const redacted = JSON.stringify(
742
+ { ...draft, model: { ...draft.model, apiKeyRef: draft.model.apiKeyRef }, github: { ...draft.github } },
743
+ null,
744
+ 2
745
+ );
746
+ void navigator.clipboard?.writeText(redacted).then(() => setMessage(t("settings.copied")));
747
+ };
748
+ if (state === null || draft === null) {
749
+ return h("div", { className: "dshgt-root" }, h("p", { className: "dshgt-hint" }, store.error ?? t("quickfill.loading")));
750
+ }
751
+ const providers = state.core.supportedProviders.length > 0 ? state.core.supportedProviders : ["deepseek", "openai-compatible"];
752
+ const coreKind = state.core.state === "ready" ? "ok" : state.core.state === "incompatible" ? "warn" : "err";
753
+ return h(
754
+ "div",
755
+ { className: "dshgt-root" },
756
+ h(
757
+ "div",
758
+ { className: "dshgt-head" },
759
+ h("span", { className: "dshgt-title" }, t("section.title")),
760
+ h("span", { className: "dshgt-sub" }, t("section.subtitle")),
761
+ h("span", { className: "dshgt-hint", style: { marginLeft: "auto" } }, `v${state.plugin.version}`)
762
+ ),
763
+ // ---- 来源与核心 ----
764
+ h(
765
+ Card,
766
+ {
767
+ title: t("card.source.title"),
768
+ hint: t("card.source.hint"),
769
+ extra: h(Btn, { small: true, onClick: () => void recheck(), disabled: rechecking }, rechecking ? t("core.checking") : t("core.recheck"))
770
+ },
771
+ h("p", { className: "dshgt-desc" }, t("card.source.desc")),
772
+ h(
773
+ Row,
774
+ { label: t("card.source.title") },
775
+ h("span", { className: "dshgt-pill" }, h("span", { className: "dshgt-dot ok" }), t("source.local")),
776
+ h("span", { className: "dshgt-hint" }, t("source.local.hint"))
777
+ ),
778
+ h(
779
+ Row,
780
+ { label: t("core.label") },
781
+ h("span", { className: "dshgt-pill" }, h("span", { className: `dshgt-dot ${coreKind}` }), state.core.version ?? t(`core.${state.core.state}`)),
782
+ state.core.ipcSchema === void 0 ? null : h("span", { className: "dshgt-hint dshgt-mono" }, `ipc ${state.core.ipcSchema} · digest ${state.core.digestSchema ?? "?"}`),
783
+ state.core.release === void 0 ? null : h("span", { className: "dshgt-hint dshgt-mono" }, `${state.core.release.tag} · ${state.core.release.asset}`),
784
+ h(
785
+ "span",
786
+ { className: "dshgt-hint" },
787
+ state.core.download.state === "override" ? `覆盖路径:${state.core.override?.path ?? ""}` : state.core.release?.cached === true ? t("core.cached") : t("core.notDownloaded")
788
+ ),
789
+ state.core.download.state === "override" ? null : h(
790
+ Btn,
791
+ { small: true, onClick: () => void downloadCore(), disabled: downloading },
792
+ downloading ? t("core.downloading") : state.core.release?.cached === true ? t("core.redownload") : t("core.download")
793
+ )
794
+ ),
795
+ state.core.release === void 0 ? null : h("p", { className: "dshgt-hint dshgt-mono", style: { fontSize: 11 } }, `${state.core.release.url} → ${state.core.release.binaryPath}`),
796
+ state.core.state === "ready" ? null : h(Alert, { kind: "err", title: t("core.label") }, h("div", null, state.core.message ?? ""), h("div", { className: "dshgt-hint" }, t("core.order"))),
797
+ h("p", { className: "dshgt-hint" }, t("core.order")),
798
+ state.coexistence.found ? h(Alert, { kind: "warn", title: t("coexist.title") }, h("div", { className: "dshgt-mono", style: { fontSize: 12 } }, `${state.coexistence.path ?? ""} ${state.coexistence.version ?? ""}`), h("div", null, state.coexistence.message ?? ""), h("div", { className: "dshgt-hint" }, t("coexist.hint"))) : null,
799
+ h(Row, { label: "TRENDING_DIGEST_CORE" }, h(Input, { mono: true, width: 420, value: draft.corePath, placeholder: "/path/to/trendradar", onInput: (value) => update((next) => {
800
+ next.corePath = value;
801
+ }) }))
802
+ ),
803
+ // ---- 模型 ----
804
+ h(
805
+ Card,
806
+ {
807
+ title: t("card.model.title"),
808
+ hint: t("card.model.hint"),
809
+ extra: h(Btn, { small: true, variant: "primary", onClick: () => setQuickOpen(true) }, t("model.quickfill"))
810
+ },
811
+ h("p", { className: "dshgt-desc" }, t("model.quickfill.desc")),
812
+ h(
813
+ Row,
814
+ { label: t("model.provider") },
815
+ h(Select, {
816
+ value: draft.model.provider,
817
+ options: providers.map((provider) => ({ value: provider, label: provider })),
818
+ onChange: (value) => update((next) => {
819
+ next.model.provider = value;
820
+ })
821
+ }),
822
+ h("span", { className: "dshgt-hint" }, state.model.mode === "proxy" ? t("model.mode.proxy") : t("model.mode.direct"))
823
+ ),
824
+ h(Row, { label: t("model.baseURL") }, h(Input, {
825
+ mono: true,
826
+ width: 420,
827
+ value: draft.model.baseURL,
828
+ placeholder: t("model.baseURL.placeholder"),
829
+ onInput: (value) => update((next) => {
830
+ next.model.baseURL = value;
831
+ })
832
+ })),
833
+ h(
834
+ Row,
835
+ { label: t("model.model") },
836
+ h(Input, {
837
+ mono: true,
838
+ width: 260,
839
+ value: draft.model.model,
840
+ placeholder: t("model.model.placeholder"),
841
+ onInput: (value) => update((next) => {
842
+ next.model.model = value;
843
+ })
844
+ }),
845
+ h(Btn, { small: true, onClick: () => void runPreflight(), disabled: preflighting }, preflighting ? t("model.preflighting") : t("model.preflight"))
846
+ ),
847
+ h(
848
+ Row,
849
+ { label: t("model.keyRef") },
850
+ h(Input, { mono: true, width: 220, value: draft.model.apiKeyRef, onInput: (value) => update((next) => {
851
+ next.model.apiKeyRef = value;
852
+ }) }),
853
+ h(
854
+ Pill,
855
+ { kind: state.model.credential.configured ? "ok" : "err" },
856
+ state.model.credential.configured ? `${t("auth.configured")}${state.model.credential.source === null ? "" : ` · ${state.model.credential.source}`}` : t("auth.missing")
857
+ )
858
+ ),
859
+ h(
860
+ Row,
861
+ { label: t("model.params") },
862
+ h(Input, { mono: true, width: 70, value: String(draft.model.temperature), onInput: (value) => update((next) => {
863
+ next.model.temperature = Number(value) || 0;
864
+ }) }),
865
+ h(Input, { mono: true, width: 90, value: String(draft.model.maxTokens), onInput: (value) => update((next) => {
866
+ next.model.maxTokens = Math.round(Number(value) || 0);
867
+ }) }),
868
+ h(Toggle, { checked: draft.model.allowDegraded, label: t("model.allowDegraded"), onChange: (value) => update((next) => {
869
+ next.model.allowDegraded = value;
870
+ }) })
871
+ ),
872
+ preflight === null ? null : h(
873
+ Alert,
874
+ { kind: preflight.ok ? "ok" : "err", title: preflight.ok ? t("state.ok") : t("error.prefix") },
875
+ h("div", null, `${preflight.mode} · ${duration(preflight.durationMs)}${preflight.endpoint === void 0 ? "" : ` · ${preflight.endpoint}`}`),
876
+ preflight.message === void 0 ? null : h("div", null, preflight.message),
877
+ preflight.hint === void 0 ? null : h("div", { className: "dshgt-hint" }, preflight.hint)
878
+ )
879
+ ),
880
+ // ---- 仓库 ----
881
+ h(
882
+ Card,
883
+ { title: t("card.repo.title"), hint: t("card.repo.hint") },
884
+ h(
885
+ Row,
886
+ { label: t("repo.path") },
887
+ h(Input, {
888
+ mono: true,
889
+ width: 380,
890
+ value: draft.repo.path,
891
+ placeholder: t("repo.path.placeholder"),
892
+ onInput: (value) => update((next) => {
893
+ next.repo.path = value;
894
+ })
895
+ }),
896
+ props.pickDirectory === void 0 ? null : h(Btn, {
897
+ small: true,
898
+ onClick: () => {
899
+ void props.pickDirectory?.().then((picked) => {
900
+ if (typeof picked === "string" && picked !== "") update((next) => {
901
+ next.repo.path = picked;
902
+ });
903
+ });
904
+ }
905
+ }, t("repo.pick"))
906
+ ),
907
+ h(
908
+ Row,
909
+ { label: t("repo.baseBranch") },
910
+ h(Input, { mono: true, width: 120, value: draft.repo.baseBranch, onInput: (value) => update((next) => {
911
+ next.repo.baseBranch = value;
912
+ }) }),
913
+ h("span", { className: "dshgt-hint" }, t("repo.remote")),
914
+ h(Input, { mono: true, width: 100, value: draft.repo.remote, onInput: (value) => update((next) => {
915
+ next.repo.remote = value;
916
+ }) }),
917
+ h("span", { className: "dshgt-hint" }, t("repo.outputDir")),
918
+ h(Input, { mono: true, width: 140, value: draft.output.dir, onInput: (value) => update((next) => {
919
+ next.output.dir = value;
920
+ }) })
921
+ ),
922
+ h(
923
+ Row,
924
+ { label: t("repo.commitMode") },
925
+ h(Select, {
926
+ value: draft.repo.commitMode,
927
+ options: [
928
+ { value: "branch", label: t("repo.commitMode.branch") },
929
+ { value: "direct", label: t("repo.commitMode.direct") }
930
+ ],
931
+ onChange: (value) => update((next) => {
932
+ next.repo.commitMode = value === "direct" ? "direct" : "branch";
933
+ })
934
+ }),
935
+ h(Toggle, { checked: draft.pr.autoMerge, label: t("repo.autoMerge"), onChange: (value) => update((next) => {
936
+ next.pr.autoMerge = value;
937
+ }) })
938
+ ),
939
+ h(Row, { label: t("repo.titleTemplate") }, h(Input, {
940
+ mono: true,
941
+ width: 360,
942
+ value: draft.pr.titleTemplate,
943
+ onInput: (value) => update((next) => {
944
+ next.pr.titleTemplate = value;
945
+ })
946
+ }))
947
+ ),
948
+ // ---- 认证 ----
949
+ h(
950
+ Card,
951
+ { title: t("card.auth.title"), hint: t("card.auth.hint") },
952
+ h(
953
+ Row,
954
+ { label: t("auth.mode") },
955
+ h(Select, {
956
+ value: draft.github.authMode,
957
+ options: [
958
+ { value: "device", label: t("auth.device") },
959
+ { value: "pat", label: t("auth.pat") },
960
+ { value: "gh", label: t("auth.gh") }
961
+ ],
962
+ onChange: (value) => update((next) => {
963
+ next.github.authMode = value === "pat" ? "pat" : value === "gh" ? "gh" : "device";
964
+ })
965
+ }),
966
+ h("span", { className: "dshgt-hint" }, t("auth.tokenRef")),
967
+ h(Input, { mono: true, width: 180, value: draft.github.tokenRef, onInput: (value) => update((next) => {
968
+ next.github.tokenRef = value;
969
+ }) }),
970
+ h(Pill, { kind: state.github.configured ? "ok" : "warn" }, state.github.configured ? `${t("auth.configured")} · ${state.github.source}` : t("auth.missing"))
971
+ ),
972
+ state.github.configured ? null : h("p", { className: "dshgt-hint" }, "未配置 token 时 push / 开 PR 会以 GITHUB_AUTH 失败;试跑(dryRun)不受影响。")
973
+ ),
974
+ // ---- 榜单 ----
975
+ h(
976
+ Card,
977
+ { title: t("card.scope.title") },
978
+ h(
979
+ Row,
980
+ { label: t("scope.period") },
981
+ h(Select, {
982
+ value: draft.trending.since,
983
+ options: [
984
+ { value: "daily", label: t("scope.daily") },
985
+ { value: "weekly", label: t("scope.weekly") }
986
+ ],
987
+ onChange: (value) => update((next) => {
988
+ next.trending.since = value === "weekly" ? "weekly" : "daily";
989
+ })
990
+ }),
991
+ h("span", { className: "dshgt-hint" }, t("scope.topN")),
992
+ h(Input, { mono: true, width: 70, value: String(draft.trending.topN), onInput: (value) => update((next) => {
993
+ next.trending.topN = Math.round(Number(value) || 0);
994
+ }) })
995
+ ),
996
+ h(
997
+ Row,
998
+ { label: t("scope.languages") },
999
+ h(Chips, {
1000
+ values: LANGUAGES,
1001
+ selected: draft.trending.languages,
1002
+ onChange: (next) => update((draftNext) => {
1003
+ draftNext.trending.languages = next;
1004
+ })
1005
+ })
1006
+ )
1007
+ ),
1008
+ // ---- 调度 ----
1009
+ h(
1010
+ Card,
1011
+ { title: t("card.schedule.title"), hint: state.schedule.nextRunAt === void 0 ? void 0 : `${t("schedule.next")} ${localTime(state.schedule.nextRunAt)}` },
1012
+ h(
1013
+ Row,
1014
+ { label: t("schedule.enabled") },
1015
+ h(Toggle, { checked: draft.schedule.enabled, onChange: (value) => update((next) => {
1016
+ next.schedule.enabled = value;
1017
+ }) }),
1018
+ h("span", { className: "dshgt-hint" }, t("schedule.time")),
1019
+ h(Input, { mono: true, width: 110, value: draft.schedule.time, onInput: (value) => update((next) => {
1020
+ next.schedule.time = value;
1021
+ }) }),
1022
+ h("span", { className: "dshgt-hint" }, t("schedule.timezone")),
1023
+ h(Input, { mono: true, width: 170, value: draft.schedule.timezone, onInput: (value) => update((next) => {
1024
+ next.schedule.timezone = value;
1025
+ }) })
1026
+ ),
1027
+ h("p", { className: "dshgt-hint" }, t("schedule.source"))
1028
+ ),
1029
+ // ---- 保存 / 排障 ----
1030
+ problems.length > 0 ? h(Alert, { kind: "err", title: t("error.prefix") }, h("ul", { style: { margin: 0, paddingLeft: 16 } }, problems.map((problem, index) => h("li", { key: index }, `${problem.field}:${problem.message}`)))) : null,
1031
+ message === null ? null : h(Alert, { kind: "ok" }, message),
1032
+ h(
1033
+ "div",
1034
+ { className: "dshgt-footer" },
1035
+ h(Btn, { variant: "primary", onClick: () => void save(), disabled: saving || !dirty }, saving ? t("settings.saving") : dirty ? t("settings.save") : saved ? t("settings.saved") : t("settings.save")),
1036
+ h(Btn, { onClick: () => void importCli() }, t("settings.import")),
1037
+ h(Btn, { onClick: copyConfig }, t("settings.copyConfig")),
1038
+ state.status.lastRunAt === null ? null : h("span", { className: "dshgt-hint" }, `上次运行 ${localTime(state.status.lastRunAt)}`)
1039
+ ),
1040
+ quickOpen ? h(QuickFillModal, { t, onClose: () => setQuickOpen(false), onApplied: (settings) => {
1041
+ setDraft(settings);
1042
+ setDirty(false);
1043
+ setSaved(true);
1044
+ void store.refresh({ quiet: true });
1045
+ } }) : null
1046
+ );
1047
+ }
1048
+ function QuickFillModal(props) {
1049
+ const { t } = props;
1050
+ const [providers, setProviders] = import_react.default.useState(null);
1051
+ const [error, setError] = import_react.default.useState(null);
1052
+ const [selectedProvider, setSelectedProvider] = import_react.default.useState("");
1053
+ const [selectedModel, setSelectedModel] = import_react.default.useState("");
1054
+ const [applying, setApplying] = import_react.default.useState(false);
1055
+ import_react.default.useEffect(() => {
1056
+ void (async () => {
1057
+ try {
1058
+ const result = await apiCall("GET", "/quickfill");
1059
+ setProviders(result.providers);
1060
+ const first = result.providers[0];
1061
+ if (first !== void 0) {
1062
+ setSelectedProvider(first.id);
1063
+ setSelectedModel(first.models[0]?.id ?? "");
1064
+ }
1065
+ } catch (failure) {
1066
+ setError(failure instanceof Error ? failure.message : String(failure));
1067
+ setProviders([]);
1068
+ }
1069
+ })();
1070
+ }, []);
1071
+ const current = providers?.find((provider) => provider.id === selectedProvider);
1072
+ const apply2 = async () => {
1073
+ if (current === void 0) return;
1074
+ setApplying(true);
1075
+ try {
1076
+ const result = await apiCall("POST", "/quickfill", { providerId: current.id, modelId: selectedModel });
1077
+ props.onApplied(result.settings);
1078
+ props.onClose();
1079
+ } catch (failure) {
1080
+ setError(failure instanceof Error ? failure.message : String(failure));
1081
+ } finally {
1082
+ setApplying(false);
1083
+ }
1084
+ };
1085
+ return h(
1086
+ "div",
1087
+ { className: "dshgt-modal-mask", onClick: (event) => {
1088
+ if (event.target === event.currentTarget) props.onClose();
1089
+ } },
1090
+ h(
1091
+ "div",
1092
+ { className: "dshgt-modal" },
1093
+ h("h3", { style: { margin: 0, fontSize: 14 } }, t("quickfill.title")),
1094
+ h("p", { className: "dshgt-hint", style: { margin: 0 } }, t("quickfill.hint")),
1095
+ error === null ? null : h(Alert, { kind: "err" }, error),
1096
+ providers === null ? h("p", { className: "dshgt-hint" }, t("quickfill.loading")) : providers.length === 0 ? h("p", { className: "dshgt-hint" }, t("quickfill.empty")) : h(
1097
+ "div",
1098
+ { style: { display: "flex", flexDirection: "column", gap: 8 } },
1099
+ h(Select, {
1100
+ value: selectedProvider,
1101
+ options: providers.map((provider) => ({ value: provider.id, label: `${provider.name} (${provider.id})` })),
1102
+ onChange: (value) => {
1103
+ setSelectedProvider(value);
1104
+ const provider = providers.find((item) => item.id === value);
1105
+ setSelectedModel(provider?.models[0]?.id ?? "");
1106
+ }
1107
+ }),
1108
+ current === void 0 ? null : h(Select, {
1109
+ value: selectedModel,
1110
+ options: current.models.map((model) => ({ value: model.id, label: model.name })),
1111
+ onChange: setSelectedModel
1112
+ }),
1113
+ current === void 0 ? null : h(KV, {
1114
+ items: [
1115
+ ["provider", current.coreProvider],
1116
+ ["baseURL", current.baseURL === "" ? t("label.optional") : current.baseURL],
1117
+ ["credential", current.credential.ref ?? "—"],
1118
+ ["mode", t(`quickfill.mode.${current.credential.mode}`)]
1119
+ ]
1120
+ })
1121
+ ),
1122
+ h(
1123
+ "div",
1124
+ { className: "dshgt-footer", style: { justifyContent: "flex-end" } },
1125
+ h(Btn, { onClick: props.onClose }, t("quickfill.close")),
1126
+ h(Btn, { variant: "primary", disabled: applying || current === void 0 || selectedModel === "", onClick: () => void apply2() }, t("quickfill.apply"))
1127
+ )
1128
+ )
1129
+ );
1130
+ }
1131
+
1132
+ // client/runs.ts
1133
+ var STEP_KEYS = ["resolve", "handshake", "spawn", "fetch", "summarize", "render", "commit", "pr", "state"];
1134
+ var CORE_STEPS = /* @__PURE__ */ new Set(["fetch", "summarize", "render", "commit", "pr"]);
1135
+ function RunsView(props) {
1136
+ const { t, store } = props;
1137
+ const [tab, setTab] = import_react.default.useState("now");
1138
+ const [dryRun, setDryRun] = import_react.default.useState(false);
1139
+ const [busy, setBusy] = import_react.default.useState(false);
1140
+ const [summary, setSummary] = import_react.default.useState(null);
1141
+ const [error, setError] = import_react.default.useState(null);
1142
+ const state = store.state;
1143
+ const run = async () => {
1144
+ setBusy(true);
1145
+ setError(null);
1146
+ setSummary(null);
1147
+ try {
1148
+ const result = await apiCall("POST", "/run", { dryRun });
1149
+ setSummary(result.summary);
1150
+ await store.refresh({ quiet: true });
1151
+ } catch (failure) {
1152
+ setError(failure instanceof Error ? failure.message : String(failure));
1153
+ } finally {
1154
+ setBusy(false);
1155
+ }
1156
+ };
1157
+ const cancel = async () => {
1158
+ try {
1159
+ await apiCall("POST", "/run/cancel", {});
1160
+ } catch (failure) {
1161
+ setError(failure instanceof Error ? failure.message : String(failure));
1162
+ }
1163
+ };
1164
+ if (state === null) {
1165
+ return h("div", { className: "dshgt-root" }, h("p", { className: "dshgt-hint" }, store.error ?? t("quickfill.loading")));
1166
+ }
1167
+ const running = state.status.running !== null;
1168
+ const shown = summary;
1169
+ const lastRecord = state.status.history[0] === void 0 ? null : state.status.history[0];
1170
+ const last = shown === null ? lastRecord : { ...lastRecord, ...shown, at: (/* @__PURE__ */ new Date()).toISOString(), model: { provider: state.settings.model.provider, model: state.settings.model.model } };
1171
+ const failedStep = shown !== null && !shown.ok ? stepFromError(shown.error?.area) : null;
1172
+ return h(
1173
+ "div",
1174
+ { className: "dshgt-root" },
1175
+ h(
1176
+ "div",
1177
+ { className: "dshgt-head" },
1178
+ h("span", { className: "dshgt-title" }, t("section.title")),
1179
+ h("span", { className: "dshgt-sub" }, t("card.source.hint")),
1180
+ h("span", { style: { marginLeft: "auto" } }, h(Pill, { kind: running || busy ? "warn" : "ok" }, running || busy ? t("run.running") : t("run.idle")))
1181
+ ),
1182
+ h(
1183
+ "div",
1184
+ { className: "dshgt-tabs", role: "tablist" },
1185
+ h("button", { type: "button", className: "dshgt-tab", role: "tab", "aria-selected": tab === "now" ? "true" : "false", onClick: () => setTab("now") }, t("run.now")),
1186
+ h("button", { type: "button", className: "dshgt-tab", role: "tab", "aria-selected": tab === "history" ? "true" : "false", onClick: () => setTab("history") }, t("run.history"))
1187
+ ),
1188
+ tab === "now" ? h(
1189
+ "div",
1190
+ { style: { display: "flex", flexDirection: "column", gap: 12 } },
1191
+ h(
1192
+ Card,
1193
+ { title: t("run.now"), hint: "spawn → 单次运行 → 退出" },
1194
+ h(
1195
+ Inline,
1196
+ null,
1197
+ h(Btn, { variant: "primary", onClick: () => void run(), disabled: busy || running }, t("run.button")),
1198
+ h(Toggle, { checked: dryRun, label: t("run.dryRun"), onChange: setDryRun }),
1199
+ running || busy ? h(Btn, { variant: "danger", onClick: () => void cancel() }, t("run.cancel")) : null
1200
+ ),
1201
+ error === null ? null : h(Alert, { kind: "err" }, error),
1202
+ h(
1203
+ "ul",
1204
+ { className: "dshgt-steps" },
1205
+ STEP_KEYS.map((key) => {
1206
+ const stepState = running || busy ? CORE_STEPS.has(key) ? "pending" : key === "resolve" || key === "handshake" || key === "spawn" ? "active" : "pending" : shown === null ? "pending" : failedStep === key ? "failed" : "done";
1207
+ const label = { done: "✓", active: "…", failed: "✗", pending: "—" }[stepState];
1208
+ return h("li", { key, className: "dshgt-step", "data-state": stepState }, h("span", null, t(`step.${key}`)), h("span", { className: "st" }, label));
1209
+ })
1210
+ )
1211
+ ),
1212
+ last === null ? h(Card, { title: t("run.last.title") }, h("p", { className: "dshgt-hint" }, t("run.last.none"))) : h(
1213
+ Card,
1214
+ { title: t("run.last.title"), extra: h(Pill, { kind: last.ok ? last.skipped ? "warn" : "ok" : "err" }, last.skipped ? t("state.skipped") : last.ok ? t("state.ok") : t("state.failed")) },
1215
+ h(KV, {
1216
+ items: [
1217
+ [t("col.date"), localTime(last.at)],
1218
+ [t("run.usage.core"), last.coreVersion || "—"],
1219
+ [t("run.usage.files"), String(last.files)],
1220
+ [t("run.usage.duration"), duration(last.durationMs)],
1221
+ [t("run.usage.hash"), h("span", { className: "dshgt-mono" }, shortHash(last.contentHash))],
1222
+ [t("run.usage.branch"), h("span", { className: "dshgt-mono" }, last.branch ?? "—")],
1223
+ [t("run.usage.pr"), last.prUrl === void 0 ? "—" : h("a", { href: last.prUrl, target: "_blank", rel: "noreferrer" }, last.prUrl)],
1224
+ ["model", `${state.settings.model.provider} / ${state.settings.model.model || "—"}`]
1225
+ ]
1226
+ }),
1227
+ last.warnings.length === 0 ? null : h("p", { className: "dshgt-hint" }, last.warnings.join(";"))
1228
+ ),
1229
+ shown !== null && shown.error !== void 0 ? h(
1230
+ Card,
1231
+ { title: t("run.error.title") },
1232
+ h(
1233
+ Alert,
1234
+ { kind: "err", title: `${t("error.prefix")} ${shown.error.code}` },
1235
+ h("div", null, shown.error.message),
1236
+ h("div", { className: "dshgt-hint" }, shown.error.hint),
1237
+ shown.error.inferred ? h("div", { className: "dshgt-hint" }, "(错误码由插件按核心 stderr 推断)") : null
1238
+ ),
1239
+ shown.logFile === void 0 ? null : h("p", { className: "dshgt-hint" }, `日志:${shown.logFile}`)
1240
+ ) : null
1241
+ ) : h(HistoryTable, { t, records: state.status.history })
1242
+ );
1243
+ }
1244
+ function stepFromError(area) {
1245
+ switch (area) {
1246
+ case "core":
1247
+ return "resolve";
1248
+ case "model":
1249
+ return "summarize";
1250
+ case "repo":
1251
+ case "auth":
1252
+ return "commit";
1253
+ default:
1254
+ return "state";
1255
+ }
1256
+ }
1257
+ function HistoryTable(props) {
1258
+ const { t, records } = props;
1259
+ return h(
1260
+ Card,
1261
+ { title: t("run.history.title"), hint: t("run.history.hint") },
1262
+ records.length === 0 ? h("p", { className: "dshgt-hint" }, t("run.history.empty")) : h(
1263
+ "table",
1264
+ { className: "dshgt-table" },
1265
+ h("thead", null, h("tr", null, [t("col.date"), t("col.result"), t("col.files"), t("col.duration"), t("col.commit"), t("col.error")].map((label) => h("th", { key: label }, label)))),
1266
+ h(
1267
+ "tbody",
1268
+ null,
1269
+ records.map(
1270
+ (record) => h(
1271
+ "tr",
1272
+ { key: String(record.id) },
1273
+ h("td", { className: "dshgt-mono" }, localTime(record.at)),
1274
+ h("td", null, h(Pill, { kind: record.ok ? record.skipped ? "warn" : "ok" : "err" }, record.kind === "dry-run" ? t("state.dry") : record.skipped ? t("state.skipped") : record.ok ? t("state.ok") : t("state.failed"))),
1275
+ h("td", { className: "dshgt-mono" }, String(record.files ?? 0)),
1276
+ h("td", { className: "dshgt-mono" }, duration(Number(record.durationMs ?? 0))),
1277
+ h("td", { className: "dshgt-mono" }, [record.branch, record.prUrl].filter(Boolean).join(" · ") || "—"),
1278
+ h("td", { className: "dshgt-mono" }, record.errorCode ?? "—")
1279
+ )
1280
+ )
1281
+ )
1282
+ )
1283
+ );
1284
+ }
1285
+
1286
+ // client/widget.ts
1287
+ function Widget(props) {
1288
+ const { t, store } = props;
1289
+ const state = store.state;
1290
+ const [busy, setBusy] = import_react.default.useState(false);
1291
+ if (state === null) {
1292
+ return h("div", { className: "dshgt-widget dshgt-root" }, h("h4", null, t("widget.title")), h("span", { className: "dshgt-hint" }, store.error ?? t("quickfill.loading")));
1293
+ }
1294
+ const running = state.status.running !== null;
1295
+ const last = state.status.lastResult;
1296
+ const coreKind = state.core.state === "ready" ? "ok" : state.core.state === "incompatible" ? "warn" : "err";
1297
+ const run = async (dryRun) => {
1298
+ setBusy(true);
1299
+ try {
1300
+ await apiCall("POST", "/run", { dryRun });
1301
+ await store.refresh({ quiet: true });
1302
+ } catch {
1303
+ } finally {
1304
+ setBusy(false);
1305
+ }
1306
+ };
1307
+ return h(
1308
+ "div",
1309
+ { className: "dshgt-widget dshgt-root" },
1310
+ h(
1311
+ "div",
1312
+ { className: "dshgt-card-h" },
1313
+ h("h4", null, t("widget.title")),
1314
+ h("span", { style: { marginLeft: "auto" } }, h(Pill, { kind: running || busy ? "warn" : last === null ? "idle" : last.ok ? "ok" : "err" }, running || busy ? t("run.running") : last === null ? t("widget.never") : last.ok ? last.skipped ? t("state.skipped") : t("state.ok") : t("state.failed")))
1315
+ ),
1316
+ h("div", { className: "dshgt-hint" }, `${t("widget.next")}:${state.schedule.nextRunAt === void 0 ? "—" : localTime(state.schedule.nextRunAt)}`),
1317
+ h("div", { className: "dshgt-hint" }, `${t("widget.last")}:${last === null ? t("widget.never") : `${localTime(last.at)} · ${last.files} ${t("col.files")} · ${duration(last.durationMs)}`}`),
1318
+ h("div", { className: "dshgt-hint dshgt-mono" }, `core ${state.core.version ?? "—"} · ipc ${state.core.ipcSchema ?? "—"}`, h("span", { className: `dshgt-dot ${coreKind}`, style: { marginLeft: 6, display: "inline-block" } })),
1319
+ h(
1320
+ "div",
1321
+ { className: "dshgt-inline" },
1322
+ h(Btn, { small: true, variant: "primary", disabled: busy || running, onClick: () => void run(false) }, t("widget.run")),
1323
+ h(Btn, { small: true, disabled: busy || running, onClick: () => void run(true) }, t("run.dryRun")),
1324
+ props.onOpen === void 0 ? null : h(Btn, { small: true, onClick: props.onOpen }, t("widget.open"))
1325
+ )
1326
+ );
1327
+ }
1328
+
1329
+ // client/index.ts
1330
+ var PANEL_ID = "dsh-github-trending";
1331
+ var inject = ["slots", "locale"];
1332
+ function apply(ctx) {
1333
+ const slots = ctx.get?.("slots");
1334
+ if (slots === void 0 || typeof slots.register !== "function") return;
1335
+ const locale = ctx.get?.("locale");
1336
+ const t = buildTranslate(locale);
1337
+ const disposeStyles = injectStyles();
1338
+ const disposers = [];
1339
+ const layout = ctx.get?.("layout");
1340
+ const pickDirectory = async () => {
1341
+ const workspace = ctx.get?.("uiWorkspace");
1342
+ return workspace?.pickDirectory?.();
1343
+ };
1344
+ function store() {
1345
+ return useStore();
1346
+ }
1347
+ function SettingsSurface(props) {
1348
+ const value = store();
1349
+ return h(SettingsView, { t, store: value, pickDirectory, ...props?.close === void 0 ? {} : { onClose: props.close } });
1350
+ }
1351
+ function RunsSurface() {
1352
+ const value = store();
1353
+ return h(RunsView, { t, store: value, page: true });
1354
+ }
1355
+ function SidebarWidget() {
1356
+ const value = store();
1357
+ return h(Widget, {
1358
+ t,
1359
+ store: value,
1360
+ onOpen: () => {
1361
+ try {
1362
+ layout?.selectPanel?.(PANEL_ID);
1363
+ } catch {
1364
+ }
1365
+ }
1366
+ });
1367
+ }
1368
+ disposers.push(
1369
+ slots.inject(
1370
+ "settings.section",
1371
+ () => slots.register(
1372
+ { name: "settings.section", id: PANEL_ID, order: 40, label: () => t("section.title") },
1373
+ SettingsSurface
1374
+ )
1375
+ )
1376
+ );
1377
+ disposers.push(
1378
+ slots.inject("main", () => {
1379
+ const disposeMain = slots.register({ name: "main", key: PANEL_ID }, RunsSurface);
1380
+ const disposeEntry = slots.inject(
1381
+ "sidebar.panellist",
1382
+ () => slots.register(
1383
+ { name: "sidebar.panellist", id: PANEL_ID, order: 40, label: () => t("section.title") },
1384
+ SidebarWidget
1385
+ )
1386
+ );
1387
+ return [disposeMain, disposeEntry];
1388
+ })
1389
+ );
1390
+ if (typeof ctx.effect === "function") {
1391
+ ctx.effect(
1392
+ () => () => {
1393
+ for (const dispose of disposers.splice(0)) {
1394
+ try {
1395
+ dispose();
1396
+ } catch {
1397
+ }
1398
+ }
1399
+ disposeStyles();
1400
+ },
1401
+ "dsh-github-trending:client"
1402
+ );
1403
+ }
1404
+ }
1405
+ return module.exports;
1406
+ },
1407
+ });
1408
+