@wangjunjian/dsh-github-trending 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -0
- package/README.zh.md +21 -0
- package/lib/cache.js +30 -1
- package/lib/client.js +230 -21
- package/lib/index.js +140 -5
- package/lib/intros.js +131 -0
- package/lib/summaries.js +352 -0
- package/lib/types/cache.d.ts +16 -0
- package/lib/types/client/locales.d.ts +1 -1
- package/lib/types/index.d.ts +22 -0
- package/lib/types/intros.d.ts +65 -0
- package/lib/types/parser.d.ts +2 -0
- package/lib/types/summaries.d.ts +174 -0
- package/package.json +6 -1
package/README.md
CHANGED
|
@@ -27,6 +27,20 @@ In the `web` profile, the plugin additionally renders a dockable right-hand
|
|
|
27
27
|
panel (daily/weekly/monthly tabs, resizable, collapsible to a rail) backed by a
|
|
28
28
|
host-side in-memory cache served over the `/github-trending` HTTP route.
|
|
29
29
|
|
|
30
|
+
### Repository overviews (opt-in, LLM-backed)
|
|
31
|
+
|
|
32
|
+
When `overviewsEnabled` is on, every successful cache refresh asynchronously
|
|
33
|
+
fetches the README of each top repository and asks the harness LLM to write a
|
|
34
|
+
short, localized overview. Generation is sequential (one LLM call at a time),
|
|
35
|
+
starting from the first repository of the window the panel is currently
|
|
36
|
+
viewing; cards show a "generating" indicator while waiting, and the overview
|
|
37
|
+
replaces the repository tagline once ready. Clicking a card's overview area
|
|
38
|
+
opens a wide dialog with a full, uniformly structured Markdown project
|
|
39
|
+
introduction (core positioning / use cases / license), generated on demand and
|
|
40
|
+
cached in memory. The `github_trending` tool output is unchanged.
|
|
41
|
+
This consumes LLM tokens — hence disabled by default. Generation failures are
|
|
42
|
+
logged on the host and retried on the next refresh cycle.
|
|
43
|
+
|
|
30
44
|
## Installation
|
|
31
45
|
|
|
32
46
|
Install the plugin into a DeepSeek Harness profile. The plugin does not modify
|
|
@@ -71,6 +85,7 @@ profile's `cordis.patch.yml`:
|
|
|
71
85
|
timeoutMs: 30000
|
|
72
86
|
maxResults: 10
|
|
73
87
|
refreshIntervalMs: 14400000
|
|
88
|
+
overviewsEnabled: false
|
|
74
89
|
```
|
|
75
90
|
|
|
76
91
|
| Field | Default | Description |
|
|
@@ -79,6 +94,14 @@ profile's `cordis.patch.yml`:
|
|
|
79
94
|
| `timeoutMs` | `30000` | Cooperative timeout budget (ms) for each HTTP request. |
|
|
80
95
|
| `maxResults` | `10` | Maximum repositories the tool may return (clamped to 25). |
|
|
81
96
|
| `refreshIntervalMs` | `14400000` | Background refresh interval (ms) for the UI cache (4 hours). |
|
|
97
|
+
| `overviewsEnabled` | `false` | Generate LLM README overviews after each refresh. |
|
|
98
|
+
| `overviewsLanguage` | `zh` | Overview language: `zh` or `en`. |
|
|
99
|
+
| `overviewsMaxRepos` | `10` | Only the top N repositories per window get an overview. |
|
|
100
|
+
| `overviewsProvider` / `overviewsModel` | — | Optional LLM route override (must be set together); defaults to the harness default model selection. |
|
|
101
|
+
| `overviewsMaxTokens` | `2048` | Output token cap per overview call (reasoning models need the headroom). |
|
|
102
|
+
| `overviewsTimeoutMs` | `60000` | Per-call timeout (ms) for one README fetch or LLM call. |
|
|
103
|
+
| `introsMaxTokens` | `4096` | Output token cap per on-demand project introduction call. |
|
|
104
|
+
| `introsTimeoutMs` | `120000` | Timeout (ms) for one introduction generation. |
|
|
82
105
|
|
|
83
106
|
## Development
|
|
84
107
|
|
package/README.zh.md
CHANGED
|
@@ -25,6 +25,18 @@ Trending 页面(按语言和时间窗口),返回结构化的热门仓库
|
|
|
25
25
|
(daily/weekly/monthly 标签页、可拖拽调宽、可折叠为窄条),数据来自宿主侧
|
|
26
26
|
内存缓存,通过 `/github-trending` HTTP 路由提供。
|
|
27
27
|
|
|
28
|
+
### 仓库概览(可选,依赖 LLM)
|
|
29
|
+
|
|
30
|
+
开启 `overviewsEnabled` 后,每次缓存刷新成功都会异步抓取排名靠前仓库的
|
|
31
|
+
README,并调用 harness 的 LLM 生成一段简短的本地化概览。生成是串行的
|
|
32
|
+
(同时只进行一次 LLM 调用),从面板当前查看窗口的第一个仓库开始依次进行;
|
|
33
|
+
等待中的卡片会显示"生成中"标志,概览生成后替换仓库的原始简介。点击卡片的
|
|
34
|
+
概览区域会弹出宽幅对话框,展示统一结构化(一句话定位 / 它是什么 /
|
|
35
|
+
解决什么 / 怎么用 / 该不该关注)的完整 Markdown 项目介绍——按需生成并
|
|
36
|
+
缓存在内存中。
|
|
37
|
+
`github_trending` 工具的输出不受影响。概览与介绍都会消耗 LLM token——因此
|
|
38
|
+
默认关闭。生成失败会在 host 端记录日志,并在下一轮刷新时重试。
|
|
39
|
+
|
|
28
40
|
## 安装
|
|
29
41
|
|
|
30
42
|
将插件安装到某个 DeepSeek Harness profile,不会修改 `deepseek-harness`
|
|
@@ -68,6 +80,7 @@ bundle patch 默认插入一行配置。可在 profile 的 `cordis.patch.yml`
|
|
|
68
80
|
timeoutMs: 30000
|
|
69
81
|
maxResults: 10
|
|
70
82
|
refreshIntervalMs: 14400000
|
|
83
|
+
overviewsEnabled: false
|
|
71
84
|
```
|
|
72
85
|
|
|
73
86
|
| 字段 | 默认值 | 说明 |
|
|
@@ -76,6 +89,14 @@ bundle patch 默认插入一行配置。可在 profile 的 `cordis.patch.yml`
|
|
|
76
89
|
| `timeoutMs` | `30000` | 每次 HTTP 请求的协作式超时预算(毫秒)。 |
|
|
77
90
|
| `maxResults` | `10` | 工具返回仓库数上限(最大会被钳制到 25)。 |
|
|
78
91
|
| `refreshIntervalMs` | `14400000` | UI 缓存的后台刷新间隔(毫秒,4 小时)。 |
|
|
92
|
+
| `overviewsEnabled` | `false` | 每次刷新后生成基于 README 的 LLM 概览。 |
|
|
93
|
+
| `overviewsLanguage` | `zh` | 概览语言:`zh` 或 `en`。 |
|
|
94
|
+
| `overviewsMaxRepos` | `10` | 每个窗口只为前 N 个仓库生成概览。 |
|
|
95
|
+
| `overviewsProvider` / `overviewsModel` | — | 可选的 LLM 路由覆盖(必须成对配置);默认跟随 harness 默认模型选择。 |
|
|
96
|
+
| `overviewsMaxTokens` | `2048` | 单次概览调用的输出 token 上限(推理模型需要预留思考 token)。 |
|
|
97
|
+
| `overviewsTimeoutMs` | `60000` | 单次 README 抓取或 LLM 调用的超时(毫秒)。 |
|
|
98
|
+
| `introsMaxTokens` | `4096` | 单次按需生成项目介绍的输出 token 上限。 |
|
|
99
|
+
| `introsTimeoutMs` | `120000` | 单次项目介绍生成的超时(毫秒)。 |
|
|
79
100
|
|
|
80
101
|
## 开发
|
|
81
102
|
|
package/lib/cache.js
CHANGED
|
@@ -32,6 +32,21 @@ export class TrendingCache {
|
|
|
32
32
|
get(language, since) {
|
|
33
33
|
return this.entries.get(cacheKey(language, since));
|
|
34
34
|
}
|
|
35
|
+
/**
|
|
36
|
+
* Find a repository by full name across all cached entries (any language,
|
|
37
|
+
* any window). Used to attach trending metadata to on-demand generations.
|
|
38
|
+
*
|
|
39
|
+
* @param fullName - `owner/name` repository path.
|
|
40
|
+
* @returns the repository, or undefined when not currently cached.
|
|
41
|
+
*/
|
|
42
|
+
findRepository(fullName) {
|
|
43
|
+
for (const entry of this.entries.values()) {
|
|
44
|
+
const repo = entry.repositories.find((candidate) => candidate.fullName === fullName);
|
|
45
|
+
if (repo !== undefined)
|
|
46
|
+
return repo;
|
|
47
|
+
}
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
35
50
|
/**
|
|
36
51
|
* Fetch fresh data and store it. Reuses the configured timeout.
|
|
37
52
|
*
|
|
@@ -42,7 +57,20 @@ export class TrendingCache {
|
|
|
42
57
|
*/
|
|
43
58
|
async refresh(fetcher, language, since) {
|
|
44
59
|
const repositories = await this.fetchWithRetry(fetcher);
|
|
45
|
-
|
|
60
|
+
const entry = this.set(language, since, repositories);
|
|
61
|
+
this.notifyRefreshed(entry, language, since);
|
|
62
|
+
return entry;
|
|
63
|
+
}
|
|
64
|
+
/** Invoke the optional onRefreshed hook, swallowing synchronous failures. */
|
|
65
|
+
notifyRefreshed(entry, language, since) {
|
|
66
|
+
if (this.options.onRefreshed === undefined)
|
|
67
|
+
return;
|
|
68
|
+
try {
|
|
69
|
+
this.options.onRefreshed(entry, language, since);
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
// A hook failure (e.g. overview generation) must never break caching.
|
|
73
|
+
}
|
|
46
74
|
}
|
|
47
75
|
/**
|
|
48
76
|
* Fetch with a per-attempt timeout, retrying transient failures.
|
|
@@ -99,6 +127,7 @@ export class TrendingCache {
|
|
|
99
127
|
for (const [since, fetcher] of Object.entries(fetchers)) {
|
|
100
128
|
const repositories = await this.fetchWithRetry(fetcher);
|
|
101
129
|
results[since] = this.set(language, since, repositories, cachedAt);
|
|
130
|
+
this.notifyRefreshed(results[since], language, since);
|
|
102
131
|
}
|
|
103
132
|
return results;
|
|
104
133
|
}
|
package/lib/client.js
CHANGED
|
@@ -20,7 +20,7 @@ window.__ModuleLoader__.load({
|
|
|
20
20
|
const DEFAULT_REFRESH_INTERVAL_MS = 144e5;
|
|
21
21
|
//#endregion
|
|
22
22
|
//#region \0dsh-css:/Users/junjian/GitHub/wang-junjian/dsh-github-trending/src/client/GithubTrendingPanel.module.css.mjs
|
|
23
|
-
const css = ".xbtn-q_overlayRoot{background:var(--dsw-bg-float);border-left:1px solid var(--dsw-alias-border-l2);z-index:100;pointer-events:auto;flex-direction:column;display:flex;position:fixed;top:0;bottom:0;right:0}.xbtn-q_railRoot{background:var(--dsw-bg-float);border:1px solid var(--dsw-alias-border-l2);z-index:100;pointer-events:auto;border-right:none;border-radius:10px 0 0 10px;flex-direction:column;justify-content:center;align-items:center;gap:6px;width:44px;padding:10px 4px;display:flex;position:fixed;top:50%;right:0;transform:translateY(-50%)}.xbtn-q_railButton{width:36px;color:var(--dsw-text-secondary);cursor:pointer;background:0 0;border:none;border-radius:8px;flex-direction:column;justify-content:center;align-items:center;padding:8px 0;transition:background-color .15s,color .15s;display:flex}.xbtn-q_railButton:hover{background:var(--dsw-fill-hover);color:var(--dsw-text-link)}.xbtn-q_railLabel{font:var(--dsw-font-caption);writing-mode:vertical-rl;letter-spacing:.06em}.xbtn-q_resizeHandle{cursor:col-resize;z-index:1;width:8px;position:absolute;top:0;bottom:0;left:-4px}.xbtn-q_header{border-bottom:1px solid var(--dsw-border-subtle);flex:none;justify-content:space-between;align-items:center;gap:12px;padding:12px 14px;display:flex}.xbtn-q_titleWrap{align-items:center;gap:8px;min-width:0;display:flex}.xbtn-q_titleIcon{color:var(--dsw-text-brand);flex:none}.xbtn-q_title{font:var(--dsw-font-title-2);color:var(--dsw-text-primary);margin:0;font-weight:700}.xbtn-q_cachedAt{font:var(--dsw-font-caption);color:var(--dsw-text-tertiary);white-space:nowrap;font-size:10px}.xbtn-q_footer{border-top:1px solid var(--dsw-border-subtle);flex:none;justify-content:flex-end;align-items:center;gap:12px;padding:6px 14px;display:flex}.xbtn-q_headerActions{align-items:center;gap:6px;display:flex}.xbtn-q_iconButton{width:28px;height:28px;color:var(--dsw-text-secondary);cursor:pointer;background:0 0;border:none;border-radius:6px;justify-content:center;align-items:center;padding:0;transition:background-color .15s,color .15s;display:flex}.xbtn-q_iconButton:hover{background:var(--dsw-fill-hover);color:var(--dsw-text-primary)}.xbtn-q_iconButton:disabled{opacity:.5;cursor:not-allowed}.xbtn-q_spinning svg{animation:1s linear infinite xbtn-q_dshGithubTrendingSpin}@keyframes xbtn-q_dshGithubTrendingSpin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.xbtn-q_sinceTabs{border-bottom:1px solid var(--dsw-border-subtle);flex:none;align-items:center;gap:6px;padding:10px 14px;display:flex}.xbtn-q_sinceTabGroup{flex:auto;align-items:center;gap:6px;display:flex}.xbtn-q_sinceTab,.xbtn-q_sinceTabActive{border:1px solid var(--dsw-border-default);background:var(--dsw-fill-input);height:30px;color:var(--dsw-text-secondary);font:var(--dsw-font-body-2);cursor:pointer;border-radius:8px;flex:1 1 0;padding:0 8px;transition:background-color .15s,color .15s,border-color .15s}.xbtn-q_sinceTab:hover{background:var(--dsw-fill-hover);color:var(--dsw-text-primary)}.xbtn-q_sinceTabActive{background:var(--dsw-fill-brand);border-color:var(--dsw-border-brand);color:var(--dsw-text-brand);font-weight:500}.xbtn-q_list{flex-direction:column;flex:auto;gap:8px;padding:8px 10px;display:flex;overflow:auto}.xbtn-q_card{border:1px solid var(--dsw-border-subtle);background:var(--dsw-bg-base);color:inherit;border-radius:10px;flex-direction:column;gap:6px;padding:10px;text-decoration:none;transition:border-color .15s;display:flex}.xbtn-q_card:hover{border-color:var(--dsw-border-brand)}.xbtn-q_cardHeader{align-items:center;gap:8px;min-width:0;display:flex}.xbtn-q_rank,.xbtn-q_rankGold,.xbtn-q_rankSilver,.xbtn-q_rankBronze{width:22px;height:22px;font:var(--dsw-font-caption);color:var(--dsw-text-tertiary);background:var(--dsw-fill-hover);border-radius:50%;flex:none;justify-content:center;align-items:center;font-weight:600;display:flex}.xbtn-q_rankGold{color:#7c4a00;background:linear-gradient(135deg,gold,#ffb800)}.xbtn-q_rankSilver{color:#4a4a4a;background:linear-gradient(135deg,#e8e8e8,silver)}.xbtn-q_rankBronze{color:#5e3a18;background:linear-gradient(135deg,#f0a068,#cd7f32)}.xbtn-q_repoName{text-overflow:ellipsis;white-space:nowrap;min-width:0;font:var(--dsw-font-body-1);color:var(--dsw-text-primary);flex:auto;font-weight:500;overflow:hidden}.xbtn-q_card:hover .xbtn-q_repoName{color:var(--dsw-text-link)}.xbtn-q_description{-webkit-line-clamp:2;color:var(--dsw-text-tertiary);font:var(--dsw-font-caption);-webkit-box-orient:vertical;font-size:14px;line-height:1.35;display:-webkit-box;overflow:hidden}.xbtn-q_meta{color:var(--dsw-text-tertiary);font:var(--dsw-font-caption);flex-wrap:wrap;align-items:center;gap:8px;font-size:11px;display:flex}.xbtn-q_metaPill{background:var(--dsw-fill-hover);color:var(--dsw-text-tertiary);border-radius:10px;align-items:center;gap:4px;padding:1px 6px;display:inline-flex}.xbtn-q_metaDot{border-radius:50%;width:7px;height:7px}.xbtn-q_metaItem{align-items:center;gap:3px;display:inline-flex}.xbtn-q_metaToday{color:var(--dsw-text-success);font-weight:500}.xbtn-q_momentum{flex-direction:column;gap:4px;display:flex}.xbtn-q_momentumTrack{background:var(--dsw-fill-input);border:1px solid var(--dsw-border-subtle);border-radius:2.5px;height:5px;overflow:hidden}.xbtn-q_momentumFill{height:100%;transition:width .4s var(--ds-ease-in-out,ease);border-radius:2.5px}.xbtn-q_status{text-align:center;color:var(--dsw-text-secondary);font:var(--dsw-font-body-2);padding:24px 14px}";
|
|
23
|
+
const css = ".xbtn-q_overlayRoot{background:var(--dsw-bg-float);border-left:1px solid var(--dsw-alias-border-l2);z-index:100;pointer-events:auto;flex-direction:column;display:flex;position:fixed;top:0;bottom:0;right:0}.xbtn-q_railRoot{background:var(--dsw-bg-float);border:1px solid var(--dsw-alias-border-l2);z-index:100;pointer-events:auto;border-right:none;border-radius:10px 0 0 10px;flex-direction:column;justify-content:center;align-items:center;gap:6px;width:44px;padding:10px 4px;display:flex;position:fixed;top:50%;right:0;transform:translateY(-50%)}.xbtn-q_railButton{width:36px;color:var(--dsw-text-secondary);cursor:pointer;background:0 0;border:none;border-radius:8px;flex-direction:column;justify-content:center;align-items:center;padding:8px 0;transition:background-color .15s,color .15s;display:flex}.xbtn-q_railButton:hover{background:var(--dsw-fill-hover);color:var(--dsw-text-link)}.xbtn-q_railLabel{font:var(--dsw-font-caption);writing-mode:vertical-rl;letter-spacing:.06em}.xbtn-q_resizeHandle{cursor:col-resize;z-index:1;width:8px;position:absolute;top:0;bottom:0;left:-4px}.xbtn-q_header{border-bottom:1px solid var(--dsw-border-subtle);flex:none;justify-content:space-between;align-items:center;gap:12px;padding:12px 14px;display:flex}.xbtn-q_titleWrap{align-items:center;gap:8px;min-width:0;display:flex}.xbtn-q_titleIcon{color:var(--dsw-text-brand);flex:none}.xbtn-q_title{font:var(--dsw-font-title-2);color:var(--dsw-text-primary);margin:0;font-weight:700}.xbtn-q_cachedAt{font:var(--dsw-font-caption);color:var(--dsw-text-tertiary);white-space:nowrap;font-size:10px}.xbtn-q_footer{border-top:1px solid var(--dsw-border-subtle);flex:none;justify-content:flex-end;align-items:center;gap:12px;padding:6px 14px;display:flex}.xbtn-q_headerActions{align-items:center;gap:6px;display:flex}.xbtn-q_iconButton{width:28px;height:28px;color:var(--dsw-text-secondary);cursor:pointer;background:0 0;border:none;border-radius:6px;justify-content:center;align-items:center;padding:0;transition:background-color .15s,color .15s;display:flex}.xbtn-q_iconButton:hover{background:var(--dsw-fill-hover);color:var(--dsw-text-primary)}.xbtn-q_iconButton:disabled{opacity:.5;cursor:not-allowed}.xbtn-q_spinning svg{animation:1s linear infinite xbtn-q_dshGithubTrendingSpin}@keyframes xbtn-q_dshGithubTrendingSpin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.xbtn-q_sinceTabs{border-bottom:1px solid var(--dsw-border-subtle);flex:none;align-items:center;gap:6px;padding:10px 14px;display:flex}.xbtn-q_sinceTabGroup{flex:auto;align-items:center;gap:6px;display:flex}.xbtn-q_sinceTab,.xbtn-q_sinceTabActive{border:1px solid var(--dsw-border-default);background:var(--dsw-fill-input);height:30px;color:var(--dsw-text-secondary);font:var(--dsw-font-body-2);cursor:pointer;border-radius:8px;flex:1 1 0;padding:0 8px;transition:background-color .15s,color .15s,border-color .15s}.xbtn-q_sinceTab:hover{background:var(--dsw-fill-hover);color:var(--dsw-text-primary)}.xbtn-q_sinceTabActive{background:var(--dsw-fill-brand);border-color:var(--dsw-border-brand);color:var(--dsw-text-brand);font-weight:500}.xbtn-q_list{flex-direction:column;flex:auto;gap:8px;padding:8px 10px;display:flex;overflow:auto}.xbtn-q_card{border:1px solid var(--dsw-border-subtle);background:var(--dsw-bg-base);color:inherit;border-radius:10px;flex-direction:column;gap:6px;padding:10px;text-decoration:none;transition:border-color .15s;display:flex}.xbtn-q_card:hover{border-color:var(--dsw-border-brand)}.xbtn-q_cardLink{min-width:0;color:inherit;text-decoration:none;display:block}.xbtn-q_cardHeader{align-items:center;gap:8px;min-width:0;display:flex}.xbtn-q_rank,.xbtn-q_rankGold,.xbtn-q_rankSilver,.xbtn-q_rankBronze{width:22px;height:22px;font:var(--dsw-font-caption);color:var(--dsw-text-tertiary);background:var(--dsw-fill-hover);border-radius:50%;flex:none;justify-content:center;align-items:center;font-weight:600;display:flex}.xbtn-q_rankGold{color:#7c4a00;background:linear-gradient(135deg,gold,#ffb800)}.xbtn-q_rankSilver{color:#4a4a4a;background:linear-gradient(135deg,#e8e8e8,silver)}.xbtn-q_rankBronze{color:#5e3a18;background:linear-gradient(135deg,#f0a068,#cd7f32)}.xbtn-q_repoName{text-overflow:ellipsis;white-space:nowrap;min-width:0;font:var(--dsw-font-body-1);color:var(--dsw-text-primary);flex:auto;font-weight:500;overflow:hidden}.xbtn-q_card:hover .xbtn-q_repoName{color:var(--dsw-text-link)}.xbtn-q_repoNameGenerating{animation:1.4s ease-in-out infinite xbtn-q_dshGithubTrendingTitleBreath}@keyframes xbtn-q_dshGithubTrendingTitleBreath{0%,to{color:var(--dsw-alias-label-brand,var(--dsw-text-link,#7a9eff));opacity:1}50%{color:var(--dsw-alias-label-primary,var(--dsw-text-primary,currentColor));opacity:.55}}.xbtn-q_description{-webkit-line-clamp:2;color:var(--dsw-text-tertiary);font:var(--dsw-font-caption);-webkit-box-orient:vertical;font-size:14px;line-height:1.35;display:-webkit-box;overflow:hidden}.xbtn-q_overview{-webkit-line-clamp:4;color:var(--dsw-text-secondary);font:var(--dsw-font-caption);-webkit-box-orient:vertical;font-size:14px;line-height:1.4;display:-webkit-box;overflow:hidden}.xbtn-q_overviewPending{color:var(--dsw-text-tertiary);font:var(--dsw-font-caption);font-size:12px;line-height:1.4;animation:1.6s ease-in-out infinite xbtn-q_dshGithubTrendingPendingPulse}@keyframes xbtn-q_dshGithubTrendingPendingPulse{0%,to{opacity:.45}50%{opacity:1}}.xbtn-q_overviewArea{cursor:pointer;appearance:none;text-align:left;background:0 0;border:none;border-radius:6px;width:calc(100% + 8px);margin:0 -4px;padding:0 4px}.xbtn-q_overviewArea:hover{background:var(--dsw-alias-interactive-bg-hover,#7f7f7f1f)}.xbtn-q_introDialog{gap:0;width:min(760px,92vw);max-height:min(84vh,860px);padding-bottom:0}.xbtn-q_introAccent{flex:none;height:3px}.xbtn-q_introHeader{flex:none;align-items:center;gap:12px;padding:16px 20px 10px;display:flex}.xbtn-q_introAvatar{background:var(--dsw-alias-bg-layer-1,#7f7f7f1f);border-radius:8px;flex:none;width:36px;height:36px}.xbtn-q_introTitleWrap{flex-direction:column;flex:auto;gap:4px;min-width:0;display:flex}.xbtn-q_introTitle{text-overflow:ellipsis;white-space:nowrap;color:var(--dsw-alias-label-primary,currentColor);font-size:15px;font-weight:600;text-decoration:none;overflow:hidden}.xbtn-q_introTitle:hover{color:var(--dsw-alias-label-brand,#7a9eff)}.xbtn-q_introMeta{flex-wrap:wrap;align-items:center;gap:8px;display:flex}.xbtn-q_introClose{cursor:pointer;width:28px;height:28px;color:var(--dsw-alias-label-secondary,currentColor);background:0 0;border:none;border-radius:8px;flex:none;justify-content:center;align-items:center;display:inline-flex}.xbtn-q_introClose:hover{background:var(--dsw-alias-interactive-bg-hover,#7f7f7f1f)}.xbtn-q_introScroll{flex:auto;min-height:0;padding:6px 20px 22px;font-size:14px;line-height:1.7;overflow-y:auto}.xbtn-q_introStatus{color:var(--dsw-text-tertiary);justify-content:center;align-items:center;gap:8px;padding:32px 0;font-size:13px;display:flex}.xbtn-q_introRetry{border:1px solid var(--dsw-alias-border-l2,#7f7f7f4d);color:inherit;cursor:pointer;background:0 0;border-radius:6px;padding:2px 10px;font-size:12px}.xbtn-q_introRetry:hover{background:var(--dsw-alias-interactive-bg-hover,#7f7f7f1f)}.xbtn-q_meta{color:var(--dsw-text-tertiary);font:var(--dsw-font-caption);flex-wrap:wrap;align-items:center;gap:8px;font-size:11px;display:flex}.xbtn-q_metaPill{background:var(--dsw-fill-hover);color:var(--dsw-text-tertiary);border-radius:10px;align-items:center;gap:4px;padding:1px 6px;display:inline-flex}.xbtn-q_metaDot{border-radius:50%;width:7px;height:7px}.xbtn-q_metaItem{align-items:center;gap:3px;display:inline-flex}.xbtn-q_metaToday{color:var(--dsw-text-success);font-weight:500}.xbtn-q_momentum{flex-direction:column;gap:4px;display:flex}.xbtn-q_momentumTrack{background:var(--dsw-fill-input);border:1px solid var(--dsw-border-subtle);border-radius:2.5px;height:5px;overflow:hidden}.xbtn-q_momentumFill{height:100%;transition:width .4s var(--ds-ease-in-out,ease);border-radius:2.5px}.xbtn-q_status{text-align:center;color:var(--dsw-text-secondary);font:var(--dsw-font-body-2);padding:24px 14px}";
|
|
24
24
|
const tagId = "@wangjunjian/dsh-github-trending/GithubTrendingPanel.module.css";
|
|
25
25
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
|
|
26
26
|
const tag = document.createElement("style");
|
|
@@ -33,12 +33,26 @@ window.__ModuleLoader__.load({
|
|
|
33
33
|
"cachedAt": "xbtn-q_cachedAt",
|
|
34
34
|
"card": "xbtn-q_card",
|
|
35
35
|
"cardHeader": "xbtn-q_cardHeader",
|
|
36
|
+
"cardLink": "xbtn-q_cardLink",
|
|
36
37
|
"description": "xbtn-q_description",
|
|
38
|
+
"dshGithubTrendingPendingPulse": "xbtn-q_dshGithubTrendingPendingPulse",
|
|
37
39
|
"dshGithubTrendingSpin": "xbtn-q_dshGithubTrendingSpin",
|
|
40
|
+
"dshGithubTrendingTitleBreath": "xbtn-q_dshGithubTrendingTitleBreath",
|
|
38
41
|
"footer": "xbtn-q_footer",
|
|
39
42
|
"header": "xbtn-q_header",
|
|
40
43
|
"headerActions": "xbtn-q_headerActions",
|
|
41
44
|
"iconButton": "xbtn-q_iconButton",
|
|
45
|
+
"introAccent": "xbtn-q_introAccent",
|
|
46
|
+
"introAvatar": "xbtn-q_introAvatar",
|
|
47
|
+
"introClose": "xbtn-q_introClose",
|
|
48
|
+
"introDialog": "xbtn-q_introDialog",
|
|
49
|
+
"introHeader": "xbtn-q_introHeader",
|
|
50
|
+
"introMeta": "xbtn-q_introMeta",
|
|
51
|
+
"introRetry": "xbtn-q_introRetry",
|
|
52
|
+
"introScroll": "xbtn-q_introScroll",
|
|
53
|
+
"introStatus": "xbtn-q_introStatus",
|
|
54
|
+
"introTitle": "xbtn-q_introTitle",
|
|
55
|
+
"introTitleWrap": "xbtn-q_introTitleWrap",
|
|
42
56
|
"list": "xbtn-q_list",
|
|
43
57
|
"meta": "xbtn-q_meta",
|
|
44
58
|
"metaDot": "xbtn-q_metaDot",
|
|
@@ -49,6 +63,9 @@ window.__ModuleLoader__.load({
|
|
|
49
63
|
"momentumFill": "xbtn-q_momentumFill",
|
|
50
64
|
"momentumTrack": "xbtn-q_momentumTrack",
|
|
51
65
|
"overlayRoot": "xbtn-q_overlayRoot",
|
|
66
|
+
"overview": "xbtn-q_overview",
|
|
67
|
+
"overviewArea": "xbtn-q_overviewArea",
|
|
68
|
+
"overviewPending": "xbtn-q_overviewPending",
|
|
52
69
|
"railButton": "xbtn-q_railButton",
|
|
53
70
|
"railLabel": "xbtn-q_railLabel",
|
|
54
71
|
"railRoot": "xbtn-q_railRoot",
|
|
@@ -57,6 +74,7 @@ window.__ModuleLoader__.load({
|
|
|
57
74
|
"rankGold": "xbtn-q_rankGold",
|
|
58
75
|
"rankSilver": "xbtn-q_rankSilver",
|
|
59
76
|
"repoName": "xbtn-q_repoName",
|
|
77
|
+
"repoNameGenerating": "xbtn-q_repoNameGenerating",
|
|
60
78
|
"resizeHandle": "xbtn-q_resizeHandle",
|
|
61
79
|
"sinceTab": "xbtn-q_sinceTab",
|
|
62
80
|
"sinceTabActive": "xbtn-q_sinceTabActive",
|
|
@@ -156,6 +174,8 @@ window.__ModuleLoader__.load({
|
|
|
156
174
|
];
|
|
157
175
|
/** Polling interval while the panel is expanded: matches the default host cache refresh interval. */
|
|
158
176
|
const POLL_INTERVAL_MS = DEFAULT_REFRESH_INTERVAL_MS;
|
|
177
|
+
/** Backoff before retrying a failed overview long-poll request. */
|
|
178
|
+
const PENDING_RETRY_INTERVAL_MS = 5e3;
|
|
159
179
|
function formatCachedAt(iso) {
|
|
160
180
|
if (iso === void 0) return "";
|
|
161
181
|
const date = new Date(iso);
|
|
@@ -240,16 +260,60 @@ window.__ModuleLoader__.load({
|
|
|
240
260
|
});
|
|
241
261
|
const [loading, setLoading] = (0, react.useState)(false);
|
|
242
262
|
const [error, setError] = (0, react.useState)(false);
|
|
263
|
+
const [intro, setIntro] = (0, react.useState)(void 0);
|
|
264
|
+
const introCache = (0, react.useRef)(/* @__PURE__ */ new Map());
|
|
243
265
|
const resizeStart = (0, react.useRef)(void 0);
|
|
244
266
|
const rootRef = useLayoutPush(!state.collapsed, state.width);
|
|
267
|
+
/**
|
|
268
|
+
* Open the project-introduction dialog for a repository. Served from the
|
|
269
|
+
* client-side cache when available; otherwise requests an on-demand
|
|
270
|
+
* generation from the host (failures are not cached, so retry works).
|
|
271
|
+
*/
|
|
272
|
+
const openIntro = (0, react.useCallback)((repo) => {
|
|
273
|
+
const cached = introCache.current.get(repo.fullName);
|
|
274
|
+
if (cached !== void 0) {
|
|
275
|
+
setIntro({
|
|
276
|
+
repo,
|
|
277
|
+
status: "ready",
|
|
278
|
+
text: cached
|
|
279
|
+
});
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
setIntro({
|
|
283
|
+
repo,
|
|
284
|
+
status: "loading"
|
|
285
|
+
});
|
|
286
|
+
(async () => {
|
|
287
|
+
try {
|
|
288
|
+
const response = await fetch(`/github-trending?intro=${encodeURIComponent(repo.fullName)}`, { headers: { Accept: "application/json" } });
|
|
289
|
+
const data = await response.json();
|
|
290
|
+
if (!response.ok || data.introduction === void 0) throw new Error(data.error ?? `HTTP ${response.status}`);
|
|
291
|
+
introCache.current.set(repo.fullName, data.introduction);
|
|
292
|
+
setIntro((prev) => prev?.repo.fullName === repo.fullName ? {
|
|
293
|
+
repo,
|
|
294
|
+
status: "ready",
|
|
295
|
+
text: data.introduction
|
|
296
|
+
} : prev);
|
|
297
|
+
} catch {
|
|
298
|
+
setIntro((prev) => prev?.repo.fullName === repo.fullName ? {
|
|
299
|
+
repo,
|
|
300
|
+
status: "error"
|
|
301
|
+
} : prev);
|
|
302
|
+
}
|
|
303
|
+
})();
|
|
304
|
+
}, []);
|
|
245
305
|
(0, react.useEffect)(() => panelStore.subscribe(() => {
|
|
246
306
|
setState(panelStore.getSnapshot());
|
|
247
307
|
}), []);
|
|
248
308
|
const currentEntry = entries[state.since];
|
|
249
|
-
const fetchEntry = (0, react.useCallback)(async (since, refresh = false) => {
|
|
309
|
+
const fetchEntry = (0, react.useCallback)(async (since, refresh = false, signal, wait = false) => {
|
|
250
310
|
const params = new URLSearchParams({ since });
|
|
251
311
|
if (refresh) params.set("refresh", "1");
|
|
252
|
-
|
|
312
|
+
if (wait) params.set("wait", "1");
|
|
313
|
+
const response = await fetch(`/github-trending?${params.toString()}`, {
|
|
314
|
+
headers: { Accept: "application/json" },
|
|
315
|
+
signal
|
|
316
|
+
});
|
|
253
317
|
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
254
318
|
return await response.json();
|
|
255
319
|
}, []);
|
|
@@ -316,6 +380,35 @@ window.__ModuleLoader__.load({
|
|
|
316
380
|
state.collapsed,
|
|
317
381
|
load
|
|
318
382
|
]);
|
|
383
|
+
const hasPendingOverviews = currentEntry?.repositories.some((repo) => repo.overviewPending) ?? false;
|
|
384
|
+
(0, react.useEffect)(() => {
|
|
385
|
+
if (!state.open || state.collapsed || !hasPendingOverviews) return;
|
|
386
|
+
const controller = new AbortController();
|
|
387
|
+
const since = state.since;
|
|
388
|
+
(async () => {
|
|
389
|
+
for (;;) try {
|
|
390
|
+
const data = await fetchEntry(since, false, controller.signal, true);
|
|
391
|
+
if (controller.signal.aborted) return;
|
|
392
|
+
setEntries((prev) => ({
|
|
393
|
+
...prev,
|
|
394
|
+
[since]: data
|
|
395
|
+
}));
|
|
396
|
+
if (!data.repositories.some((repo) => repo.overviewPending)) return;
|
|
397
|
+
} catch {
|
|
398
|
+
if (controller.signal.aborted) return;
|
|
399
|
+
await new Promise((resolve) => setTimeout(resolve, PENDING_RETRY_INTERVAL_MS));
|
|
400
|
+
}
|
|
401
|
+
})();
|
|
402
|
+
return () => {
|
|
403
|
+
controller.abort();
|
|
404
|
+
};
|
|
405
|
+
}, [
|
|
406
|
+
state.open,
|
|
407
|
+
state.collapsed,
|
|
408
|
+
hasPendingOverviews,
|
|
409
|
+
state.since,
|
|
410
|
+
fetchEntry
|
|
411
|
+
]);
|
|
319
412
|
const onResizeStart = (0, react.useCallback)((event) => {
|
|
320
413
|
resizeStart.current = {
|
|
321
414
|
x: event.clientX,
|
|
@@ -438,28 +531,40 @@ window.__ModuleLoader__.load({
|
|
|
438
531
|
currentEntry?.repositories.map((repo) => {
|
|
439
532
|
const momentum = maxMomentum > 0 ? repo.starsToday / maxMomentum * 100 : 0;
|
|
440
533
|
const langColor = languageColor(repo.language);
|
|
534
|
+
const summary = repo.overview !== void 0 && repo.overview !== "" ? repo.overview : repo.description;
|
|
441
535
|
const rankClass = repo.rank === 1 ? GithubTrendingPanel_module_css_default.rankGold : repo.rank === 2 ? GithubTrendingPanel_module_css_default.rankSilver : repo.rank === 3 ? GithubTrendingPanel_module_css_default.rankBronze : GithubTrendingPanel_module_css_default.rank;
|
|
442
|
-
return (0, react_jsx_runtime.jsxs)("
|
|
443
|
-
href: repo.url,
|
|
444
|
-
target: "_blank",
|
|
445
|
-
rel: "noreferrer",
|
|
536
|
+
return (0, react_jsx_runtime.jsxs)("div", {
|
|
446
537
|
className: GithubTrendingPanel_module_css_default.card,
|
|
447
|
-
title: repo.fullName,
|
|
448
538
|
children: [
|
|
449
|
-
(0, react_jsx_runtime.
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
539
|
+
(0, react_jsx_runtime.jsx)("a", {
|
|
540
|
+
href: repo.url,
|
|
541
|
+
target: "_blank",
|
|
542
|
+
rel: "noreferrer",
|
|
543
|
+
className: GithubTrendingPanel_module_css_default.cardLink,
|
|
544
|
+
title: repo.fullName,
|
|
545
|
+
children: (0, react_jsx_runtime.jsxs)("div", {
|
|
546
|
+
className: GithubTrendingPanel_module_css_default.cardHeader,
|
|
547
|
+
children: [(0, react_jsx_runtime.jsx)("span", {
|
|
548
|
+
className: rankClass,
|
|
549
|
+
children: repo.rank
|
|
550
|
+
}), (0, react_jsx_runtime.jsx)("span", {
|
|
551
|
+
className: repo.overviewGenerating === true ? `${GithubTrendingPanel_module_css_default.repoName} ${GithubTrendingPanel_module_css_default.repoNameGenerating}` : GithubTrendingPanel_module_css_default.repoName,
|
|
552
|
+
children: repo.fullName
|
|
553
|
+
})]
|
|
554
|
+
})
|
|
462
555
|
}),
|
|
556
|
+
summary !== void 0 && summary !== "" ? (0, react_jsx_runtime.jsx)("button", {
|
|
557
|
+
type: "button",
|
|
558
|
+
className: `${GithubTrendingPanel_module_css_default.overviewArea} ${repo.overview !== void 0 && repo.overview !== "" ? GithubTrendingPanel_module_css_default.overview : GithubTrendingPanel_module_css_default.description}`,
|
|
559
|
+
title: t("intro.hint"),
|
|
560
|
+
onClick: () => {
|
|
561
|
+
openIntro(repo);
|
|
562
|
+
},
|
|
563
|
+
children: summary
|
|
564
|
+
}) : repo.overviewPending === true ? (0, react_jsx_runtime.jsx)("div", {
|
|
565
|
+
className: GithubTrendingPanel_module_css_default.overviewPending,
|
|
566
|
+
children: t("panel.overviewPending")
|
|
567
|
+
}) : null,
|
|
463
568
|
(0, react_jsx_runtime.jsxs)("div", {
|
|
464
569
|
className: GithubTrendingPanel_module_css_default.meta,
|
|
465
570
|
children: [
|
|
@@ -508,6 +613,98 @@ window.__ModuleLoader__.load({
|
|
|
508
613
|
className: GithubTrendingPanel_module_css_default.cachedAt,
|
|
509
614
|
children: currentEntry?.cachedAt !== void 0 ? `${t("panel.cachedAt")} ${formatCachedAt(currentEntry.cachedAt)}` : ""
|
|
510
615
|
})
|
|
616
|
+
}),
|
|
617
|
+
(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
|
|
618
|
+
open: intro !== void 0,
|
|
619
|
+
onClose: () => {
|
|
620
|
+
setIntro(void 0);
|
|
621
|
+
},
|
|
622
|
+
title: intro?.repo.fullName ?? "",
|
|
623
|
+
closeLabel: t("intro.close"),
|
|
624
|
+
headless: true,
|
|
625
|
+
className: GithubTrendingPanel_module_css_default.introDialog,
|
|
626
|
+
children: intro !== void 0 && (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
627
|
+
(0, react_jsx_runtime.jsx)("div", {
|
|
628
|
+
className: GithubTrendingPanel_module_css_default.introAccent,
|
|
629
|
+
style: { background: languageColor(intro.repo.language) }
|
|
630
|
+
}),
|
|
631
|
+
(0, react_jsx_runtime.jsxs)("div", {
|
|
632
|
+
className: GithubTrendingPanel_module_css_default.introHeader,
|
|
633
|
+
children: [
|
|
634
|
+
(0, react_jsx_runtime.jsx)("img", {
|
|
635
|
+
className: GithubTrendingPanel_module_css_default.introAvatar,
|
|
636
|
+
src: `https://avatars.githubusercontent.com/${intro.repo.owner}?s=72`,
|
|
637
|
+
alt: ""
|
|
638
|
+
}),
|
|
639
|
+
(0, react_jsx_runtime.jsxs)("div", {
|
|
640
|
+
className: GithubTrendingPanel_module_css_default.introTitleWrap,
|
|
641
|
+
children: [(0, react_jsx_runtime.jsx)("a", {
|
|
642
|
+
className: GithubTrendingPanel_module_css_default.introTitle,
|
|
643
|
+
href: intro.repo.url,
|
|
644
|
+
target: "_blank",
|
|
645
|
+
rel: "noreferrer",
|
|
646
|
+
children: intro.repo.fullName
|
|
647
|
+
}), (0, react_jsx_runtime.jsxs)("div", {
|
|
648
|
+
className: GithubTrendingPanel_module_css_default.introMeta,
|
|
649
|
+
children: [
|
|
650
|
+
intro.repo.language !== void 0 && intro.repo.language !== "" && (0, react_jsx_runtime.jsxs)("span", {
|
|
651
|
+
className: GithubTrendingPanel_module_css_default.metaPill,
|
|
652
|
+
children: [(0, react_jsx_runtime.jsx)("span", {
|
|
653
|
+
className: GithubTrendingPanel_module_css_default.metaDot,
|
|
654
|
+
style: { background: languageColor(intro.repo.language) }
|
|
655
|
+
}), intro.repo.language]
|
|
656
|
+
}),
|
|
657
|
+
intro.repo.stars > 0 && (0, react_jsx_runtime.jsxs)("span", {
|
|
658
|
+
className: GithubTrendingPanel_module_css_default.metaItem,
|
|
659
|
+
children: ["★ ", formatNumber(intro.repo.stars)]
|
|
660
|
+
}),
|
|
661
|
+
intro.repo.forks > 0 && (0, react_jsx_runtime.jsxs)("span", {
|
|
662
|
+
className: GithubTrendingPanel_module_css_default.metaItem,
|
|
663
|
+
children: ["⑂ ", formatNumber(intro.repo.forks)]
|
|
664
|
+
}),
|
|
665
|
+
intro.repo.starsToday > 0 && (0, react_jsx_runtime.jsxs)("span", {
|
|
666
|
+
className: `${GithubTrendingPanel_module_css_default.metaItem} ${GithubTrendingPanel_module_css_default.metaToday}`,
|
|
667
|
+
children: ["+", formatNumber(intro.repo.starsToday)]
|
|
668
|
+
})
|
|
669
|
+
]
|
|
670
|
+
})]
|
|
671
|
+
}),
|
|
672
|
+
(0, react_jsx_runtime.jsx)("button", {
|
|
673
|
+
type: "button",
|
|
674
|
+
className: GithubTrendingPanel_module_css_default.introClose,
|
|
675
|
+
"aria-label": t("intro.close"),
|
|
676
|
+
onClick: () => {
|
|
677
|
+
setIntro(void 0);
|
|
678
|
+
},
|
|
679
|
+
children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCloseOutline16, { size: 14 })
|
|
680
|
+
})
|
|
681
|
+
]
|
|
682
|
+
}),
|
|
683
|
+
(0, react_jsx_runtime.jsxs)("div", {
|
|
684
|
+
className: GithubTrendingPanel_module_css_default.introScroll,
|
|
685
|
+
children: [
|
|
686
|
+
intro.status === "loading" && (0, react_jsx_runtime.jsxs)("div", {
|
|
687
|
+
className: GithubTrendingPanel_module_css_default.introStatus,
|
|
688
|
+
children: [(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconLoadingOutline16, {
|
|
689
|
+
size: 16,
|
|
690
|
+
className: GithubTrendingPanel_module_css_default.spinning
|
|
691
|
+
}), t("intro.loading")]
|
|
692
|
+
}),
|
|
693
|
+
intro.status === "error" && (0, react_jsx_runtime.jsxs)("div", {
|
|
694
|
+
className: GithubTrendingPanel_module_css_default.introStatus,
|
|
695
|
+
children: [t("intro.error"), (0, react_jsx_runtime.jsx)("button", {
|
|
696
|
+
type: "button",
|
|
697
|
+
className: GithubTrendingPanel_module_css_default.introRetry,
|
|
698
|
+
onClick: () => {
|
|
699
|
+
openIntro(intro.repo);
|
|
700
|
+
},
|
|
701
|
+
children: t("intro.retry")
|
|
702
|
+
})]
|
|
703
|
+
}),
|
|
704
|
+
intro.status === "ready" && intro.text !== void 0 && (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.MarkdownText, { text: intro.text })
|
|
705
|
+
]
|
|
706
|
+
})
|
|
707
|
+
] })
|
|
511
708
|
})
|
|
512
709
|
]
|
|
513
710
|
});
|
|
@@ -533,6 +730,12 @@ window.__ModuleLoader__.load({
|
|
|
533
730
|
"panel.cachedAt": "Updated",
|
|
534
731
|
"panel.autoRefresh": "Auto",
|
|
535
732
|
"panel.collapse": "Collapse",
|
|
733
|
+
"panel.overviewPending": "Generating AI overview…",
|
|
734
|
+
"intro.hint": "Click for a full project introduction",
|
|
735
|
+
"intro.loading": "Generating project introduction…",
|
|
736
|
+
"intro.error": "Failed to generate the introduction.",
|
|
737
|
+
"intro.retry": "Retry",
|
|
738
|
+
"intro.close": "Close",
|
|
536
739
|
"repo.stars": "stars",
|
|
537
740
|
"repo.forks": "forks",
|
|
538
741
|
"repo.today": "today"
|
|
@@ -551,6 +754,12 @@ window.__ModuleLoader__.load({
|
|
|
551
754
|
"panel.cachedAt": "更新于",
|
|
552
755
|
"panel.autoRefresh": "自动",
|
|
553
756
|
"panel.collapse": "收起",
|
|
757
|
+
"panel.overviewPending": "AI 概览生成中…",
|
|
758
|
+
"intro.hint": "点击查看完整项目介绍",
|
|
759
|
+
"intro.loading": "正在生成项目介绍…",
|
|
760
|
+
"intro.error": "项目介绍生成失败。",
|
|
761
|
+
"intro.retry": "重试",
|
|
762
|
+
"intro.close": "关闭",
|
|
554
763
|
"repo.stars": "星标",
|
|
555
764
|
"repo.forks": "分支",
|
|
556
765
|
"repo.today": "今日"
|