dsh-session-title-stamp 0.4.1 → 0.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -11,8 +11,8 @@ title. Beyond automatic naming:
11
11
 
12
12
  - **Auto refresh** — after a completed turn, with a 2-minute cooldown and a
13
13
  100-message freeze; forks and user-pinned titles are skipped.
14
- - **Manual refresh** — the conversation-header ↻ button, the session row's
15
- **Regenerate title** menu item, or `/retitle`.
14
+ - **Generate title** — the conversation-header ↻ button, the session row's
15
+ **Generate title** menu item, or `/retitle`.
16
16
  - **Hover stats** — elapsed time, estimated CNY cost, and token count on the
17
17
  session hover card.
18
18
  - **Category language** — `categoryPreset: zh | en`, or your own `categories`.
@@ -59,7 +59,7 @@ and mirrors conventions used by other coding agents.
59
59
  Add this market index source in **Plugins → Discover → Index sources**:
60
60
 
61
61
  ```text
62
- https://cdn.jsdelivr.net/gh/xuedipiaofei/dsh-session-title-stamp@v0.4.1/market/index.json
62
+ https://cdn.jsdelivr.net/gh/xuedipiaofei/dsh-session-title-stamp@v0.4.3/market/index.json
63
63
  ```
64
64
 
65
65
  Then install **Session title stamp** from the list. The plugin installs as a
@@ -127,15 +127,15 @@ Forks and subagent sessions are skipped, as are titles pinned by a manual
127
127
  rename. Setting `refreshOnTurnEnd: false` restores the legacy message-count
128
128
  mode using `retitleEvery`.
129
129
 
130
- ### Header refresh and hover stats
130
+ ### Header button and hover stats
131
131
 
132
- 0.4.0 adds a small browser half:
132
+ 0.4.3 adds a small browser half:
133
133
 
134
134
  - a **refresh icon** in the conversation header; it runs the same `/retitle`
135
135
  command as typing it, and is disabled while the session is running.
136
136
  - a **hover-card line** under the native relative time, for example
137
137
  `Duration 1h 51m · est. ≈¥4.11 · 131.7M tokens`;
138
- - a **Regenerate title** item at the top of the session row's `···` menu. It
138
+ - a **Generate title** item at the top of the session row's `···` menu. It
139
139
  runs the same `/retitle` command and adds no dialog.
140
140
 
141
141
  The hover line is derived from the `sessionStats` and `tokenUsage` projections,
package/README.zh.md CHANGED
@@ -5,7 +5,7 @@
5
5
  给 DSH 会话一个稳定的「日期 | 分类 | 名称」标题。除了自动命名,还包括:
6
6
 
7
7
  - **自动重算**:回答完成后更新,2 分钟冷却,超过 100 条消息后冻结;fork、子代理和手动固定的标题跳过。
8
- - **手动刷新**:会话头 ↻ 按钮、会话行 `···` 菜单「重新生成标题」、或 `/retitle`。
8
+ - **生成标题**:会话头 ↻ 按钮、会话行 `···` 菜单「自动生成标题」、或 `/retitle`。
9
9
  - **悬停统计**:会话行悬停显示耗时、估算 CNY 成本、token 数。
10
10
  - **分类语言**:`categoryPreset: zh | en`,也可以用 `categories` 自定义。
11
11
  - **失败保护**:重算失败保留当前标题,不被兜底标题覆盖。
@@ -43,7 +43,7 @@
43
43
  在 **插件 → 发现(Discover)→ 索引源** 添加:
44
44
 
45
45
  ```text
46
- https://cdn.jsdelivr.net/gh/xuedipiaofei/dsh-session-title-stamp@v0.4.1/market/index.json
46
+ https://cdn.jsdelivr.net/gh/xuedipiaofei/dsh-session-title-stamp@v0.4.3/market/index.json
47
47
  ```
48
48
 
49
49
  然后在列表里安装 **Session title stamp / 会话标题印章**。插件以 profile bundle 的形式安装,之后可在插件页启停。
@@ -93,12 +93,12 @@ fork 子会话、子代理会话,以及用户手动改名固定的标题都会
93
93
 
94
94
  ### 会话头按钮与悬停统计
95
95
 
96
- 0.4.0 增加了一个很小的浏览器半边:
96
+ 0.4.3 增加了一个很小的浏览器半边:
97
97
 
98
98
  - 会话头有一个**刷新图标**,效果等同输入 `/retitle`,会话运行中禁用;
99
99
  - 鼠标悬停会话行时,在原生相对时间下面多一行:
100
100
  `耗时 1h 51m · 估算 ≈¥4.11 · 131.7M tokens`;
101
- - 会话行 `···` 菜单最上面多一项「重新生成标题」;同样调用 `/retitle`,
101
+ - 会话行 `···` 菜单最上面多一项「自动生成标题」;同样调用 `/retitle`,
102
102
  不新开弹窗。
103
103
 
104
104
  数字来自 `sessionStats` / `tokenUsage` 投影,按 DeepSeek 当前 CNY 价格估算,并标注「估算」。只在悬停时向宿主请求,不会为了显示数字去恢复未加载的会话。DSH 原生已经有运行中/待交互/完成未读的状态点与 loader,所以本插件**不再加一层状态颜色**。
package/client.js CHANGED
@@ -17,26 +17,25 @@ window.__ModuleLoader__.load({
17
17
  const { useEffect, useState } = React
18
18
  const h = React.createElement
19
19
 
20
+ const NS = 'dsh-session-title-stamp'
20
21
  const STATS_PATH = '/api/session-title-stamp/stats'
21
- const inject = ['slots', 'remote', 'remote.commands']
22
- const isZh = (document.documentElement.lang || navigator.language || '')
23
- .toLowerCase()
24
- .startsWith('zh')
25
- const LABELS = isZh
26
- ? {
27
- menu: '重新生成标题',
28
- header: '重新生成标题',
29
- running: '会话运行中,稍后再试',
30
- duration: '耗时',
31
- estimate: '估算',
32
- }
33
- : {
34
- menu: 'Regenerate title',
35
- header: 'Regenerate title',
36
- running: 'Session is running; try again later',
37
- duration: 'Duration',
38
- estimate: 'est.',
39
- }
22
+ const inject = ['slots', 'remote', 'remote.commands', 'locale']
23
+ const DICT = {
24
+ zh: {
25
+ menu: '自动生成标题',
26
+ tip: '自动识别分类,生成「日期 | 分类 | 名称」标题',
27
+ running: '会话运行中,稍后再试',
28
+ duration: '耗时',
29
+ estimate: '估算',
30
+ },
31
+ en: {
32
+ menu: 'Generate title',
33
+ tip: 'Detect the category and generate a DATE | CATEGORY | NAME title',
34
+ running: 'Session is running; try again later',
35
+ duration: 'Duration',
36
+ estimate: 'est.',
37
+ },
38
+ }
40
39
 
41
40
  function formatDuration(ms) {
42
41
  if (typeof ms !== 'number' || !Number.isFinite(ms) || ms < 0) return undefined
@@ -81,7 +80,7 @@ window.__ModuleLoader__.load({
81
80
  )
82
81
  }
83
82
 
84
- function RefreshAction({ sessionId, useSession, run }) {
83
+ function RefreshAction({ sessionId, useSession, run, t }) {
85
84
  const [busy, setBusy] = useState(false)
86
85
  const [failed, setFailed] = useState(false)
87
86
  const running =
@@ -109,8 +108,8 @@ window.__ModuleLoader__.load({
109
108
  type: 'button',
110
109
  className: 'dsh-title-refresh',
111
110
  disabled,
112
- title: running ? LABELS.running : LABELS.header,
113
- 'aria-label': LABELS.header,
111
+ title: running ? t('running') : t('tip'),
112
+ 'aria-label': t('menu'),
114
113
  onClick: click,
115
114
  style: {
116
115
  display: 'inline-flex',
@@ -133,10 +132,11 @@ window.__ModuleLoader__.load({
133
132
  )
134
133
  }
135
134
 
136
- function HoverStats({ sessionId }) {
135
+ function HoverStats({ sessionId, t }) {
137
136
  const [data, setData] = useState(null)
138
137
  useEffect(() => {
139
138
  let cancelled = false
139
+ setData(null)
140
140
  if (typeof sessionId !== 'string' || sessionId === '') return undefined
141
141
  const url = `${STATS_PATH}?sessionId=${encodeURIComponent(sessionId)}`
142
142
  fetch(url, { headers: { accept: 'application/json' } })
@@ -155,8 +155,8 @@ window.__ModuleLoader__.load({
155
155
  const cost = formatCost(data.costCny)
156
156
  const tokens = formatTokens(data.tokens)
157
157
  const parts = [
158
- duration === undefined ? undefined : `${LABELS.duration} ${duration}`,
159
- cost === undefined ? undefined : `${LABELS.estimate} ${cost}`,
158
+ duration === undefined ? undefined : `${t('duration')} ${duration}`,
159
+ cost === undefined ? undefined : `${t('estimate')} ${cost}`,
160
160
  tokens === undefined ? undefined : `${tokens} tokens`,
161
161
  ].filter(Boolean)
162
162
  if (parts.length === 0) return null
@@ -175,7 +175,7 @@ window.__ModuleLoader__.load({
175
175
  )
176
176
  }
177
177
 
178
- function RegenerateMenuItem({ sessionId, useMenuOpenState, run }) {
178
+ function RegenerateMenuItem({ sessionId, useMenuOpenState, run, t }) {
179
179
  const menuState =
180
180
  typeof useMenuOpenState === 'function' ? useMenuOpenState() : null
181
181
  return h(
@@ -184,6 +184,7 @@ window.__ModuleLoader__.load({
184
184
  type: 'button',
185
185
  role: 'menuitem',
186
186
  className: 'dsh-title-menu-item',
187
+ title: t('tip'),
187
188
  onClick: async () => {
188
189
  if (Array.isArray(menuState) && typeof menuState[1] === 'function') {
189
190
  menuState[1](false)
@@ -200,7 +201,7 @@ window.__ModuleLoader__.load({
200
201
  { className: 'dsh-title-menu-icon', 'aria-hidden': 'true' },
201
202
  h(RefreshIcon),
202
203
  ),
203
- h('span', null, LABELS.menu),
204
+ h('span', null, t('menu')),
204
205
  )
205
206
  }
206
207
 
@@ -222,6 +223,8 @@ window.__ModuleLoader__.load({
222
223
 
223
224
  function apply(ctx) {
224
225
  installStyle(ctx)
226
+ ctx.effect?.(() => ctx.locale.register(NS, DICT))
227
+ const t = ctx.locale.bind(NS)
225
228
  const run = async (sessionId) => {
226
229
  const response = await ctx.remote.commands.execute(sessionId, '/retitle', [])
227
230
  if (!response?.ok) {
@@ -240,7 +243,7 @@ window.__ModuleLoader__.load({
240
243
  name: 'conversation.session.header.actions',
241
244
  id: 'session-title-stamp-refresh',
242
245
  order: 40,
243
- inject: () => ({ run }),
246
+ inject: () => ({ run, t }),
244
247
  },
245
248
  RefreshAction,
246
249
  ),
@@ -252,7 +255,7 @@ window.__ModuleLoader__.load({
252
255
  name: 'sidebar.workspaces.session.menu.item',
253
256
  id: 'session-title-stamp-retitle',
254
257
  order: 0,
255
- inject: () => ({ run }),
258
+ inject: () => ({ run, t }),
256
259
  },
257
260
  RegenerateMenuItem,
258
261
  ),
@@ -264,7 +267,7 @@ window.__ModuleLoader__.load({
264
267
  name: 'sidebar.session.row.hover',
265
268
  id: 'session-title-stamp-stats',
266
269
  order: 0,
267
- inject: () => ({}),
270
+ inject: () => ({ t }),
268
271
  },
269
272
  HoverStats,
270
273
  ),
package/lib/index.js CHANGED
@@ -212,7 +212,14 @@ async function readCachedProjection(sessionId) {
212
212
  }
213
213
  }
214
214
 
215
+ function normalizeSessionId(sessionId) {
216
+ return typeof sessionId === 'string' && sessionId.startsWith('session-')
217
+ ? sessionId.slice('session-'.length)
218
+ : sessionId
219
+ }
220
+
215
221
  async function statsFor(scope, sessionId) {
222
+ sessionId = normalizeSessionId(sessionId)
216
223
  let tokenUsage
217
224
  let sessionStats
218
225
  let modelSelection
@@ -255,10 +262,11 @@ function registerStatsRoute(ctx, logger) {
255
262
  requestBody: 'buffered',
256
263
  fetch: async (request) => {
257
264
  const url = new URL(request.url)
258
- const sessionId = url.searchParams.get('sessionId') ?? ''
259
- if (!/^[A-Za-z0-9._-]{1,128}$/u.test(sessionId)) {
265
+ const rawSessionId = url.searchParams.get('sessionId') ?? ''
266
+ if (!/^[A-Za-z0-9._-]{1,128}$/u.test(rawSessionId)) {
260
267
  return jsonResponse({ ok: false, error: 'invalid sessionId' }, 400)
261
268
  }
269
+ const sessionId = normalizeSessionId(rawSessionId)
262
270
  const now = Date.now()
263
271
  const hit = cache.get(sessionId)
264
272
  if (hit !== undefined && now - hit.at < STATS_TTL_MS) return jsonResponse(hit.data)
package/market/index.json CHANGED
@@ -34,8 +34,8 @@
34
34
  "zh": "会话标题印章"
35
35
  },
36
36
  "description": {
37
- "en": "Keeps DSH session titles in DATE | CATEGORY | NAME with turn-aware auto-retitle, manual refresh, and hover cost/duration.",
38
- "zh": "把 DSH 会话标题固定为「日期 | 分类 | 名称」,支持回答后自动重算、手动刷新和悬停成本/耗时。"
37
+ "en": "Keeps DSH session titles in DATE | CATEGORY | NAME with turn-aware auto-retitle, a Generate title action, and hover cost/duration.",
38
+ "zh": "把 DSH 会话标题固定为「日期 | 分类 | 名称」,支持回答后自动重算、自动生成标题和悬停成本/耗时。"
39
39
  },
40
40
  "author": "xuedipiaofei",
41
41
  "icon": "../icon.svg",
@@ -57,12 +57,12 @@
57
57
  },
58
58
  "versions": [
59
59
  {
60
- "version": "0.4.1",
61
- "publishTimestamp": 1793368308,
60
+ "version": "0.4.3",
61
+ "publishTimestamp": 1793416672,
62
62
  "source": {
63
63
  "type": "git",
64
64
  "url": "https://github.com/xuedipiaofei/dsh-session-title-stamp",
65
- "commit": "2caf439c873cd4dfe863b0b27bba00a5fc832d86"
65
+ "commit": "86cbe7eb5532b241313cd462236d19abdbd7cec1"
66
66
  },
67
67
  "engines": {
68
68
  "dsh": ">=0.2.1-rc.3 <0.3.0"
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-session-title-stamp",
3
- "version": "0.4.1",
4
- "description": "DSH session titles in a DATE | CATEGORY | NAME template, with turn-aware retitling, a refresh button, and hover cost/duration.",
3
+ "version": "0.4.3",
4
+ "description": "DSH session titles in a DATE | CATEGORY | NAME template, with turn-aware auto-generation, a Generate title button, and hover cost/duration.",
5
5
  "keywords": [
6
6
  "dsh",
7
7
  "dsh-plugin",
@@ -52,7 +52,8 @@
52
52
  "inject": [
53
53
  "@deepseek-ai/dsh-client-ui-conversation",
54
54
  "@deepseek-ai/dsh-client-ui-workspace",
55
- "@deepseek-ai/dsh-client-ui-renderer"
55
+ "@deepseek-ai/dsh-client-ui-renderer",
56
+ "@deepseek-ai/dsh-client-locale"
56
57
  ]
57
58
  }
58
59
  },