@telosmaylx/dsh-session-notify 0.1.8 → 0.1.9
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 +583 -577
- package/lib/client.js +153 -46
- package/lib/core.js +72 -31
- package/lib/index.js +46 -10
- package/package.json +70 -70
package/lib/client.js
CHANGED
|
@@ -45,13 +45,13 @@ window.__ModuleLoader__.load({
|
|
|
45
45
|
title: '会话完成提醒', subtitle: '完成推送的语言与文案模板(保存后刷新生效)',
|
|
46
46
|
preset: '预设', choosePreset: '选择预设…', language: '语言',
|
|
47
47
|
fields: { completed: '完成', error: '出错', aborted: '中止', blocked: '阻塞', 'max-tokens': '输出上限' },
|
|
48
|
-
titleLabel: '推送标题', titleHint: '
|
|
48
|
+
titleLabel: '推送标题', titleHint: '所有原因共用;留空时按原因用默认标题', titleTplLabel: '按原因定制标题(可选)', titleTplOpen: '展开', titleTplClose: '收起', ttPlaceholders: { completed: '任务已完成', error: '任务出错', aborted: '任务已中止', blocked: '任务被阻塞', 'max-tokens': '任务达到输出上限' },
|
|
49
49
|
kindLabels: { completed: '会话已完成', error: '会话出错', aborted: '会话已中止', blocked: '会话被阻塞', 'max-tokens': '会话达到输出上限' },
|
|
50
50
|
tokens: { title: '会话标题', label: '完成标签', duration: '用时', usage: '消耗', error: '错误', cache: '缓存命中', tps: '速度 TPS' },
|
|
51
51
|
samples: { title: '示例会话', label: '会话已完成', duration: '12 秒', usage: '1,240 输入 / 3,560 输出', error: '连接超时', cache: '96.5%', tps: '92 tok/s' },
|
|
52
52
|
presets: { custom: '自定义预设', unnamed: '未命名预设', default: '默认', playful: '轻松风格 🎉', formal: '正式简洁', detail: '详细报告' },
|
|
53
53
|
placeholder: '自定义文案:点「+ 插入信息」,或直接输入文字',
|
|
54
|
-
insertInfo: '+ 插入信息', insertTitle: '在光标处插入信息', preview: '预览:',
|
|
54
|
+
insertInfo: '+ 插入信息', insertTitle: '在光标处插入信息', sendTest: '发送', sendTestTitle: '发送测试通知(当前模板渲染)', preview: '预览:',
|
|
55
55
|
skipSubagents: '跳过子代理会话',
|
|
56
56
|
pushMode: '推送方式', pushDual: '系统通知 + 页内提示', pushSystem: '仅系统通知', pushToast: '仅页内提示',
|
|
57
57
|
discard: '放弃', save: '保存', saving: '保存中…', saved: '已保存 ✓ · 刷新页面后生效', saveFailed: '保存失败:', refresh: '点击刷新', reset: '重置', resetDone: '已重置为默认值 · 刷新页面后生效',
|
|
@@ -65,13 +65,13 @@ window.__ModuleLoader__.load({
|
|
|
65
65
|
title: '會話完成提醒', subtitle: '完成推送的語言與文案模板(儲存後重新整理生效)',
|
|
66
66
|
preset: '預設', choosePreset: '選擇預設…', language: '語言',
|
|
67
67
|
fields: { completed: '完成', error: '出錯', aborted: '中止', blocked: '阻塞', 'max-tokens': '輸出上限' },
|
|
68
|
-
titleLabel: '推送標題', titleHint: '
|
|
68
|
+
titleLabel: '推送標題', titleHint: '所有原因共用;留空時按原因用預設標題', titleTplLabel: '按原因自訂標題(選填)', titleTplOpen: '展開', titleTplClose: '收起', ttPlaceholders: { completed: '任務已完成', error: '任務出錯', aborted: '任務已中止', blocked: '任務被阻塞', 'max-tokens': '任務達到輸出上限' },
|
|
69
69
|
kindLabels: { completed: '會話已完成', error: '會話出錯', aborted: '會話已中止', blocked: '會話被阻塞', 'max-tokens': '會話達到輸出上限' },
|
|
70
70
|
tokens: { title: '會話標題', label: '完成標籤', duration: '用時', usage: '消耗', error: '錯誤', cache: '快取命中', tps: '速度 TPS' },
|
|
71
71
|
samples: { title: '範例會話', label: '會話已完成', duration: '12 秒', usage: '1,240 輸入 / 3,560 輸出', error: '連線逾時', cache: '96.5%', tps: '92 tok/s' },
|
|
72
72
|
presets: { custom: '自訂預設', unnamed: '未命名預設', default: '預設', playful: '輕鬆風格 🎉', formal: '正式簡潔', detail: '詳細報告' },
|
|
73
73
|
placeholder: '自訂文案:點「+ 插入資訊」,或直接輸入文字',
|
|
74
|
-
insertInfo: '+ 插入資訊', insertTitle: '在游標處插入資訊', preview: '預覽:',
|
|
74
|
+
insertInfo: '+ 插入資訊', insertTitle: '在游標處插入資訊', sendTest: '傳送', sendTestTitle: '傳送測試通知(目前範本渲染)', preview: '預覽:',
|
|
75
75
|
skipSubagents: '跳過子代理會話',
|
|
76
76
|
pushMode: '推送方式', pushDual: '系統通知 + 頁內提示', pushSystem: '僅系統通知', pushToast: '僅頁內提示',
|
|
77
77
|
discard: '放棄', save: '儲存', saving: '儲存中…', saved: '已儲存 ✓ · 重新整理頁面後生效', saveFailed: '儲存失敗:', refresh: '點擊重新整理', reset: '重置', resetDone: '已重置為預設值 · 重新整理頁面後生效',
|
|
@@ -85,13 +85,13 @@ window.__ModuleLoader__.load({
|
|
|
85
85
|
title: 'Session completion alert', subtitle: 'Language & message templates for completion pushes (refresh after save)',
|
|
86
86
|
preset: 'Preset', choosePreset: 'Choose preset…', language: 'Language',
|
|
87
87
|
fields: { completed: 'Completed', error: 'Error', aborted: 'Aborted', blocked: 'Blocked', 'max-tokens': 'Token cap' },
|
|
88
|
-
titleLabel: 'Push title', titleHint: '
|
|
88
|
+
titleLabel: 'Push title', titleHint: 'Shared by all reasons; leave empty for per-reason defaults', titleTplLabel: 'Per-reason titles (optional)', titleTplOpen: 'Expand', titleTplClose: 'Collapse', ttPlaceholders: { completed: 'Task completed', error: 'Task failed', aborted: 'Task aborted', blocked: 'Task blocked', 'max-tokens': 'Task hit the output cap' },
|
|
89
89
|
kindLabels: { completed: 'Session completed', error: 'Session failed', aborted: 'Session aborted', blocked: 'Session blocked', 'max-tokens': 'Session hit the output-token cap' },
|
|
90
90
|
tokens: { title: 'Session title', label: 'Label', duration: 'Duration', usage: 'Usage', error: 'Error', cache: 'Cache hit', tps: 'Speed TPS' },
|
|
91
91
|
samples: { title: 'Example session', label: 'Session completed', duration: '12s', usage: '1,240 in / 3,560 out', error: 'connection timeout', cache: '96.5%', tps: '92 tok/s' },
|
|
92
92
|
presets: { custom: 'Custom preset', unnamed: 'Untitled preset', default: 'Default', playful: 'Playful 🎉', formal: 'Formal', detail: 'Detailed report' },
|
|
93
93
|
placeholder: 'Custom text: use “+ Insert info”, or type directly',
|
|
94
|
-
insertInfo: '+ Insert info', insertTitle: 'Insert info at cursor', preview: 'Preview: ',
|
|
94
|
+
insertInfo: '+ Insert info', insertTitle: 'Insert info at cursor', sendTest: 'Send', sendTestTitle: 'Send test notification (rendered from current template)', preview: 'Preview: ',
|
|
95
95
|
skipSubagents: 'Skip subagent sessions',
|
|
96
96
|
pushMode: 'Push channel', pushDual: 'System + in-page', pushSystem: 'System only', pushToast: 'In-page only',
|
|
97
97
|
discard: 'Discard', save: 'Save', saving: 'Saving…', saved: 'Saved ✓ · refresh page to apply', saveFailed: 'Save failed: ', refresh: 'Refresh', reset: 'Reset', resetDone: 'Reset to defaults ✓ · refresh page to apply',
|
|
@@ -105,13 +105,13 @@ window.__ModuleLoader__.load({
|
|
|
105
105
|
title: 'セッション完了通知', subtitle: '完了プッシュの言語とメッセージテンプレート(保存後に再読み込みで反映)',
|
|
106
106
|
preset: 'プリセット', choosePreset: 'プリセットを選択…', language: '言語',
|
|
107
107
|
fields: { completed: '完了', error: 'エラー', aborted: '中止', blocked: 'ブロック', 'max-tokens': 'トークン上限' },
|
|
108
|
-
titleLabel: 'プッシュタイトル', titleHint: '
|
|
108
|
+
titleLabel: 'プッシュタイトル', titleHint: '全理由共通;空欄なら理由ごとのデフォルトを使用', titleTplLabel: '理由ごとのタイトル(任意)', titleTplOpen: '展開', titleTplClose: '折りたたむ', ttPlaceholders: { completed: 'タスク完了', error: 'タスクエラー', aborted: 'タスク中止', blocked: 'タスクがブロックされました', 'max-tokens': '出力上限に到達' },
|
|
109
109
|
kindLabels: { completed: 'セッション完了', error: 'セッションエラー', aborted: 'セッション中止', blocked: 'セッションがブロックされました', 'max-tokens': '出力トークン上限に到達' },
|
|
110
110
|
tokens: { title: 'セッションタイトル', label: '完了ラベル', duration: '所要時間', usage: '消費', error: 'エラー', cache: 'キャッシュヒット', tps: '速度 TPS' },
|
|
111
111
|
samples: { title: 'サンプルセッション', label: 'セッション完了', duration: '12 秒', usage: '1,240 入力 / 3,560 出力', error: '接続タイムアウト', cache: '96.5%', tps: '92 tok/s' },
|
|
112
112
|
presets: { custom: 'カスタムプリセット', unnamed: '未命名プリセット', default: 'デフォルト', playful: 'カジュアル 🎉', formal: 'フォーマル', detail: '詳細レポート' },
|
|
113
113
|
placeholder: 'カスタム文面:「+ 情報を挿入」または直接入力',
|
|
114
|
-
insertInfo: '+ 情報を挿入', insertTitle: 'カーソル位置に情報を挿入', preview: 'プレビュー:',
|
|
114
|
+
insertInfo: '+ 情報を挿入', insertTitle: 'カーソル位置に情報を挿入', sendTest: '送信', sendTestTitle: 'テスト通知を送信(現在のテンプレートで描画)', preview: 'プレビュー:',
|
|
115
115
|
skipSubagents: 'サブエージェントセッションをスキップ',
|
|
116
116
|
pushMode: '通知チャネル', pushDual: 'システム+ページ内', pushSystem: 'システムのみ', pushToast: 'ページ内のみ',
|
|
117
117
|
discard: '破棄', save: '保存', saving: '保存中…', saved: '保存しました ✓ · ページを再読み込み', saveFailed: '保存失敗:', refresh: '再読み込み', reset: 'リセット', resetDone: 'デフォルトに戻しました ✓ · ページを再読み込み',
|
|
@@ -125,13 +125,13 @@ window.__ModuleLoader__.load({
|
|
|
125
125
|
title: '세션 완료 알림', subtitle: '완료 푸시의 언어 및 메시지 템플릿(저장 후 새로고침 시 적용)',
|
|
126
126
|
preset: '프리셋', choosePreset: '프리셋 선택…', language: '언어',
|
|
127
127
|
fields: { completed: '완료', error: '오류', aborted: '중단', blocked: '차단', 'max-tokens': '토큰 한도' },
|
|
128
|
-
titleLabel: '푸시 제목', titleHint: '
|
|
128
|
+
titleLabel: '푸시 제목', titleHint: '모든 이유 공통;비우면 이유별 기본값 사용', titleTplLabel: '이유별 제목 (선택)', titleTplOpen: '펼치기', titleTplClose: '접기', ttPlaceholders: { completed: '작업 완료', error: '작업 오류', aborted: '작업 중단됨', blocked: '작업 차단됨', 'max-tokens': '출력 한도 도달' },
|
|
129
129
|
kindLabels: { completed: '세션 완료', error: '세션 오류', aborted: '세션 중단됨', blocked: '세션 차단됨', 'max-tokens': '출력 토큰 한도 도달' },
|
|
130
130
|
tokens: { title: '세션 제목', label: '완료 라벨', duration: '소요 시간', usage: '소모', error: '오류', cache: '캐시 히트', tps: '속도 TPS' },
|
|
131
131
|
samples: { title: '예시 세션', label: '세션 완료', duration: '12초', usage: '1,240 입력 / 3,560 출력', error: '연결 시간 초과', cache: '96.5%', tps: '92 tok/s' },
|
|
132
132
|
presets: { custom: '사용자 지정 프리셋', unnamed: '이름 없는 프리셋', default: '기본', playful: '캐주얼 🎉', formal: '정식', detail: '상세 보고' },
|
|
133
133
|
placeholder: '사용자 지정 문구: "+ 정보 삽입" 또는 직접 입력',
|
|
134
|
-
insertInfo: '+ 정보 삽입', insertTitle: '커서 위치에 정보 삽입', preview: '미리보기: ',
|
|
134
|
+
insertInfo: '+ 정보 삽입', insertTitle: '커서 위치에 정보 삽입', sendTest: '보내기', sendTestTitle: '테스트 알림 보내기 (현재 템플릿으로 렌더링)', preview: '미리보기: ',
|
|
135
135
|
skipSubagents: '하위 에이전트 세션 건너뛰기',
|
|
136
136
|
pushMode: '알림 채널', pushDual: '시스템+페이지 내', pushSystem: '시스템만', pushToast: '페이지 내만',
|
|
137
137
|
discard: '취소', save: '저장', saving: '저장 중…', saved: '저장됨 ✓ · 새로고침 후 적용', saveFailed: '저장 실패: ', refresh: '새로고침', reset: '초기화', resetDone: '기본값으로 초기화됨 ✓ · 새로고침 후 적용',
|
|
@@ -195,10 +195,10 @@ window.__ModuleLoader__.load({
|
|
|
195
195
|
var cwdLine = workspaceLine(s)
|
|
196
196
|
var start = Date.now()
|
|
197
197
|
function attempt() {
|
|
198
|
-
var
|
|
199
|
-
if (
|
|
198
|
+
var proj = readNoticeAny(id)
|
|
199
|
+
if (proj || Date.now() - start > 6000) {
|
|
200
200
|
retryTimer = null
|
|
201
|
-
notifyUser(pushTitle(name,
|
|
201
|
+
notifyUser(pushTitle(name, proj ? proj.title : '', proj ? '' : ' 已完成'), (proj && proj.text) || (cwdLine ? cwdLine + ' · ' : '') + '详情见会话内系统消息', pushModeOf())
|
|
202
202
|
return
|
|
203
203
|
}
|
|
204
204
|
retryTimer = setTimeout(attempt, 400)
|
|
@@ -217,8 +217,15 @@ window.__ModuleLoader__.load({
|
|
|
217
217
|
return 'dual'
|
|
218
218
|
}
|
|
219
219
|
|
|
220
|
-
/**
|
|
221
|
-
|
|
220
|
+
/**
|
|
221
|
+
* 推送标题:优先级——
|
|
222
|
+
* 1. 宿主投影渲染好的该原因标题(projTitle,含 {title} 替换);
|
|
223
|
+
* 2. 用户全局 titleTemplate({title} = 会话标题);
|
|
224
|
+
* 3. 默认「任务已完成」。
|
|
225
|
+
* 无通知正文时(降级)附带完成标记 suffix。
|
|
226
|
+
*/
|
|
227
|
+
function pushTitle(name, projTitle, suffix) {
|
|
228
|
+
if (typeof projTitle === 'string' && projTitle.trim() !== '') return projTitle.trim() + (suffix || '')
|
|
222
229
|
var tpl = '任务已完成'
|
|
223
230
|
try {
|
|
224
231
|
if (notifyScope) {
|
|
@@ -237,14 +244,26 @@ window.__ModuleLoader__.load({
|
|
|
237
244
|
* 2. 会话事件窗口里的 notice 节点(正在看的会话,落盘后立刻可用);
|
|
238
245
|
* 3. 无 → 降级(工作区信息)。
|
|
239
246
|
*/
|
|
247
|
+
/**
|
|
248
|
+
* 读取完成通知:返回 { text, title } | null。
|
|
249
|
+
* 优先宿主投影(新格式对象 {kind,text,title};兼容旧版字符串值);
|
|
250
|
+
* 投影缺失时从会话事件窗口读 notice 节点(此时无该原因标题,title 为空串 → 走模板/默认)。
|
|
251
|
+
*/
|
|
240
252
|
function readNoticeAny(id) {
|
|
241
253
|
try {
|
|
242
254
|
var snap = list.getSnapshot()
|
|
243
255
|
var cur = snap && snap.byId && snap.byId[id]
|
|
244
256
|
var pv = cur && cur.projectionValues && cur.projectionValues[NOTICE_PROJECTION_KEY]
|
|
245
|
-
if (
|
|
257
|
+
if (pv != null) {
|
|
258
|
+
if (typeof pv === 'string' && pv.trim() !== '') return { text: pv, title: '' }
|
|
259
|
+
if (typeof pv === 'object' && pv !== null) {
|
|
260
|
+
var txt = typeof pv.text === 'string' ? pv.text : ''
|
|
261
|
+
if (txt.trim() !== '') return { text: txt, title: typeof pv.title === 'string' ? pv.title : '' }
|
|
262
|
+
}
|
|
263
|
+
}
|
|
246
264
|
} catch (e) { /* 投影缺失 → 走窗口路径 */ }
|
|
247
|
-
|
|
265
|
+
var fallback = readNotice(id)
|
|
266
|
+
return fallback ? { text: fallback, title: '' } : null
|
|
248
267
|
}
|
|
249
268
|
|
|
250
269
|
/** 从会话事件窗口找宿主写入的最新 notice 全文(kind=context + form=notice)。 */
|
|
@@ -505,13 +524,14 @@ window.__ModuleLoader__.load({
|
|
|
505
524
|
|
|
506
525
|
/** 系统预设:仅保留「默认」(作为基线,其余以自定义预设承载)。 */
|
|
507
526
|
var PRESETS = [
|
|
508
|
-
{ id: 'default', labelKey: 'default', language: 'zh', titleTemplate: '
|
|
527
|
+
{ id: 'default', labelKey: 'default', language: 'zh', titleTemplate: '', titleTemplates: { completed: '', error: '', aborted: '', blocked: '', 'max-tokens': '' }, templates: { completed: '', error: '', aborted: '', blocked: '', 'max-tokens': '' } },
|
|
509
528
|
]
|
|
510
529
|
|
|
511
|
-
/** 重置目标:与宿主 DEFAULT_SETTINGS
|
|
530
|
+
/** 重置目标:与宿主 DEFAULT_SETTINGS 一致(设置面板可编辑字段子集;语言保留当前值)。 */
|
|
512
531
|
var DEFAULT_FLAT = {
|
|
513
532
|
language: 'zh',
|
|
514
|
-
titleTemplate: '
|
|
533
|
+
titleTemplate: '',
|
|
534
|
+
titleTemplates: { completed: '', error: '', aborted: '', blocked: '', 'max-tokens': '' },
|
|
515
535
|
pushMode: 'dual',
|
|
516
536
|
templates: { completed: '', error: '', aborted: '', blocked: '', 'max-tokens': '' },
|
|
517
537
|
}
|
|
@@ -569,6 +589,7 @@ window.__ModuleLoader__.load({
|
|
|
569
589
|
if (String(cur.titleTemplate ?? '') !== String(p.titleTemplate ?? '')) return false
|
|
570
590
|
for (var j = 0; j < REASON_FIELDS.length; j++) {
|
|
571
591
|
if (templateOf(cur, REASON_FIELDS[j]) !== String(p.templates[REASON_FIELDS[j]] ?? '').trim()) return false
|
|
592
|
+
if (titleTplOf(cur, REASON_FIELDS[j]) !== String((p.titleTemplates && p.titleTemplates[REASON_FIELDS[j]]) ?? '').trim()) return false
|
|
572
593
|
}
|
|
573
594
|
return true
|
|
574
595
|
}
|
|
@@ -599,6 +620,11 @@ window.__ModuleLoader__.load({
|
|
|
599
620
|
return String(cur && cur['tpl-' + field] !== undefined ? cur['tpl-' + field] : (tpl[field] ?? ''))
|
|
600
621
|
.replace(/\{label\}/g, '').trim()
|
|
601
622
|
}
|
|
623
|
+
/** 按原因定制标题:草稿键 tt-<field> 优先,其次已保存的 titleTemplates[<field>]。 */
|
|
624
|
+
function titleTplOf(cur, field) {
|
|
625
|
+
var tt = (cur && cur.titleTemplates) || {}
|
|
626
|
+
return String(cur && cur['tt-' + field] !== undefined ? cur['tt-' + field] : (tt[field] ?? '')).trim()
|
|
627
|
+
}
|
|
602
628
|
/** 扁平快照(用于 dirty 对比与模板打包)。 */
|
|
603
629
|
function flatOf(cur) {
|
|
604
630
|
var out = {
|
|
@@ -608,6 +634,7 @@ window.__ModuleLoader__.load({
|
|
|
608
634
|
pushMode: ['dual', 'system', 'toast'].indexOf(cur.pushMode) >= 0 ? cur.pushMode : 'dual',
|
|
609
635
|
}
|
|
610
636
|
for (var i = 0; i < REASON_FIELDS.length; i++) out['tpl-' + REASON_FIELDS[i]] = templateOf(cur, REASON_FIELDS[i])
|
|
637
|
+
for (var j = 0; j < REASON_FIELDS.length; j++) out['tt-' + REASON_FIELDS[j]] = titleTplOf(cur, REASON_FIELDS[j])
|
|
611
638
|
return out
|
|
612
639
|
}
|
|
613
640
|
function sameFlat(a, b) {
|
|
@@ -758,6 +785,9 @@ window.__ModuleLoader__.load({
|
|
|
758
785
|
function syncFromDom() {
|
|
759
786
|
var tpl = domToTemplate()
|
|
760
787
|
lastTpl.current = tpl
|
|
788
|
+
// 占位提示只随内容变化:有输入 → 隐藏;清空 → 恢复(hint 可编辑内容或 placeholder)
|
|
789
|
+
if (tpl !== '') clearPlaceholder()
|
|
790
|
+
else rebuild('')
|
|
761
791
|
propsRef.current.onChange(tpl)
|
|
762
792
|
}
|
|
763
793
|
|
|
@@ -841,9 +871,8 @@ window.__ModuleLoader__.load({
|
|
|
841
871
|
contentEditable: true,
|
|
842
872
|
suppressContentEditableWarning: true,
|
|
843
873
|
onInput: syncFromDom,
|
|
844
|
-
onMouseDown: function () {
|
|
874
|
+
onMouseDown: function () { /* 不清理占位:点击聚焦不消失,输入内容才隐藏 */ },
|
|
845
875
|
onKeyDown: function (e) {
|
|
846
|
-
clearPlaceholder()
|
|
847
876
|
if (e.key === 'Enter') { e.preventDefault(); return }
|
|
848
877
|
// Ctrl/Cmd+A:手动扩选“全部子节点(含胶囊)”——浏览器全选天然跳过
|
|
849
878
|
// contenteditable=false 的胶囊,导致全选删除后胶囊残留
|
|
@@ -881,18 +910,25 @@ window.__ModuleLoader__.load({
|
|
|
881
910
|
})
|
|
882
911
|
}
|
|
883
912
|
|
|
884
|
-
/**
|
|
913
|
+
/** 各语言「会话」前缀(用于默认提示中在标签内插入会话标题标签)。 */
|
|
914
|
+
var SESSION_PREFIX = { zh: '会话', 'zh-tw': '會話', en: 'Session ', ja: 'セッション', ko: '세션 ' }
|
|
915
|
+
|
|
916
|
+
/** 内置默认内容的提示串(空模板时直接展示真实默认效果:会话「会话标题」+ 信息标签)。 */
|
|
885
917
|
function defaultHintOf(field, langCode) {
|
|
886
918
|
var lang = LANG_IDS.indexOf(langCode) >= 0 ? langCode : 'zh'
|
|
887
919
|
var tt = tOf(lang)
|
|
888
920
|
var label = (tt.kindLabels && tt.kindLabels[field]) || tt.samples.label
|
|
889
921
|
var d = '{duration}'
|
|
890
922
|
var u = '{usage}'
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
return
|
|
923
|
+
var prefix = SESSION_PREFIX[lang] || '会话'
|
|
924
|
+
var rest = String(label).slice(prefix.length) // 剥掉「会话」前缀,保留原因后缀(已完成/中止/…)
|
|
925
|
+
var titleTag = lang === 'en' ? '"{title}"' : '「{title}」'
|
|
926
|
+
var head = prefix + titleTag + ((lang === 'en' || lang === 'ko') ? ' ' : '') + rest
|
|
927
|
+
if (lang === 'en') return head + ' (took ' + d + ', used ' + u + ').'
|
|
928
|
+
if (lang === 'ja') return head + '(所要 ' + d + '、消費 ' + u + ')。'
|
|
929
|
+
if (lang === 'ko') return head + '(소요 ' + d + ', 소모 ' + u + ')。'
|
|
930
|
+
if (lang === 'zh-tw') return head + '(用時 ' + d + ',消耗 ' + u + ')。'
|
|
931
|
+
return head + '(用时 ' + d + ',消耗 ' + u + ')。'
|
|
896
932
|
}
|
|
897
933
|
|
|
898
934
|
return function SettingsCard() {
|
|
@@ -936,6 +972,9 @@ window.__ModuleLoader__.load({
|
|
|
936
972
|
var permTickState = useState(0) // 通知权限状态刷新计数(请求授权后 setPermTick(+1) 重渲染)
|
|
937
973
|
var permTick = permTickState[0]
|
|
938
974
|
var setPermTick = permTickState[1]
|
|
975
|
+
var titleTplOpenState = useState(false) // 「按原因定制标题」折叠区展开状态
|
|
976
|
+
var titleTplOpen = titleTplOpenState[0]
|
|
977
|
+
var setTitleTplOpen = titleTplOpenState[1]
|
|
939
978
|
|
|
940
979
|
/** 用户手势内请求通知权限(Chromium 忽略非手势的自动请求,必须由点击触发);授权结果经重渲染刷新状态区。 */
|
|
941
980
|
function requestPermissionNow() {
|
|
@@ -1025,6 +1064,10 @@ window.__ModuleLoader__.load({
|
|
|
1025
1064
|
setCustomPresets(next)
|
|
1026
1065
|
saveCustomPresets(next)
|
|
1027
1066
|
setActivePreset(null)
|
|
1067
|
+
// 删除的是当前使用的预设 → 表单恢复默认预设内容(presetIdOf 自动匹配「默认」)
|
|
1068
|
+
if (shownEntry.id === (activePreset && activePreset.id)) {
|
|
1069
|
+
setDraft(Object.assign({}, DEFAULT_FLAT))
|
|
1070
|
+
}
|
|
1028
1071
|
setNote(t.customDeleted)
|
|
1029
1072
|
log('custom preset deleted')
|
|
1030
1073
|
}
|
|
@@ -1032,15 +1075,18 @@ window.__ModuleLoader__.load({
|
|
|
1032
1075
|
function save() {
|
|
1033
1076
|
var flat = flatOf(cur)
|
|
1034
1077
|
var templates = {}
|
|
1078
|
+
var titleTemplates = {}
|
|
1035
1079
|
for (var i = 0; i < REASON_FIELDS.length; i++) {
|
|
1036
1080
|
var k = REASON_FIELDS[i]
|
|
1037
1081
|
templates[k] = flat['tpl-' + k]
|
|
1082
|
+
titleTemplates[k] = flat['tt-' + k]
|
|
1038
1083
|
}
|
|
1039
1084
|
setNote(t.saving)
|
|
1040
1085
|
var tasks = [
|
|
1041
1086
|
scope.set('language', flat.language),
|
|
1042
1087
|
scope.set('templates', templates),
|
|
1043
1088
|
scope.set('titleTemplate', flat.titleTemplate),
|
|
1089
|
+
scope.set('titleTemplates', titleTemplates),
|
|
1044
1090
|
scope.set('pushMode', flat.pushMode),
|
|
1045
1091
|
]
|
|
1046
1092
|
Promise.all(tasks).then(function () {
|
|
@@ -1054,7 +1100,7 @@ window.__ModuleLoader__.load({
|
|
|
1054
1100
|
var found = null
|
|
1055
1101
|
if (shownCustom && shownEntry) {
|
|
1056
1102
|
targetId = shownEntry.id
|
|
1057
|
-
entry = Object.assign({}, shownEntry, { language: lang, titleTemplate: flat.titleTemplate, templates: templates })
|
|
1103
|
+
entry = Object.assign({}, shownEntry, { language: lang, titleTemplate: flat.titleTemplate, titleTemplates: titleTemplates, templates: templates })
|
|
1058
1104
|
list = customPresets.map(function (cp) {
|
|
1059
1105
|
if (cp.id === targetId) { found = Object.assign({}, cp, entry); return found }
|
|
1060
1106
|
return cp
|
|
@@ -1063,7 +1109,7 @@ window.__ModuleLoader__.load({
|
|
|
1063
1109
|
} else {
|
|
1064
1110
|
var n = nextUnnamedNumber(customPresets)
|
|
1065
1111
|
targetId = n === 1 ? 'unnamed' : 'unnamed-' + n
|
|
1066
|
-
entry = { id: targetId, name: null, auto: true, language: lang, titleTemplate: flat.titleTemplate, templates: templates }
|
|
1112
|
+
entry = { id: targetId, name: null, auto: true, language: lang, titleTemplate: flat.titleTemplate, titleTemplates: titleTemplates, templates: templates }
|
|
1067
1113
|
list = customPresets.concat([entry])
|
|
1068
1114
|
found = entry
|
|
1069
1115
|
}
|
|
@@ -1080,21 +1126,46 @@ window.__ModuleLoader__.load({
|
|
|
1080
1126
|
})
|
|
1081
1127
|
}
|
|
1082
1128
|
|
|
1083
|
-
/**
|
|
1129
|
+
/** 发送测试通知:用当前模板(空模板用默认文案)渲染该原因的通知;标题按原因优先级。 */
|
|
1130
|
+
function sendTestNotice(field) {
|
|
1131
|
+
try {
|
|
1132
|
+
var samples = t.samples
|
|
1133
|
+
var titleTpl = titleTplOf(cur, field) || String(cur.titleTemplate || '').trim() || t.ttPlaceholders[field]
|
|
1134
|
+
var title = String(titleTpl).replaceAll('{title}', samples.title).trim() || samples.title
|
|
1135
|
+
var tpl = templateOf(cur, field)
|
|
1136
|
+
var bodyTpl = tpl !== '' ? tpl : defaultHintOf(field, cur.language)
|
|
1137
|
+
var body = String(bodyTpl)
|
|
1138
|
+
.replace(/\{label\}/g, '')
|
|
1139
|
+
.replaceAll('{title}', samples.title)
|
|
1140
|
+
.replaceAll('{duration}', samples.duration)
|
|
1141
|
+
.replaceAll('{usage}', samples.usage)
|
|
1142
|
+
.replaceAll('{error}', samples.error)
|
|
1143
|
+
.replaceAll('{cache}', samples.cache)
|
|
1144
|
+
.replaceAll('{tps}', samples.tps)
|
|
1145
|
+
notifyUser(title, body, ['dual', 'system', 'toast'].indexOf(cur.pushMode) >= 0 ? cur.pushMode : 'dual')
|
|
1146
|
+
log('test notice sent: ' + field)
|
|
1147
|
+
} catch (e) {
|
|
1148
|
+
log('test notice failed: ' + (e && e.message ? e.message : e))
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
/** 一键重置:除语言外全部还原为默认值(语言保留当前选择)并立即保存。 */
|
|
1084
1153
|
function resetAll() {
|
|
1085
1154
|
var def = DEFAULT_FLAT
|
|
1155
|
+
var lang = LANG_IDS.indexOf(cur.language) >= 0 ? cur.language : 'zh' // 重置不改变语言
|
|
1086
1156
|
setNote(t.saving)
|
|
1087
1157
|
var tasks = [
|
|
1088
|
-
scope.set('language',
|
|
1158
|
+
scope.set('language', lang),
|
|
1089
1159
|
scope.set('templates', def.templates),
|
|
1090
1160
|
scope.set('titleTemplate', def.titleTemplate),
|
|
1161
|
+
scope.set('titleTemplates', def.titleTemplates),
|
|
1091
1162
|
scope.set('pushMode', def.pushMode),
|
|
1092
1163
|
]
|
|
1093
1164
|
Promise.all(tasks).then(function () {
|
|
1094
1165
|
setDraft(null)
|
|
1095
1166
|
setNote(t.resetDone)
|
|
1096
1167
|
setJustSaved(true)
|
|
1097
|
-
log('settings reset to defaults')
|
|
1168
|
+
log('settings reset to defaults (language kept: ' + lang + ')')
|
|
1098
1169
|
}).catch(function (e) {
|
|
1099
1170
|
setNote(t.saveFailed + (e && e.message ? e.message : e))
|
|
1100
1171
|
log('settings reset failed: ' + (e && e.message ? e.message : e))
|
|
@@ -1205,13 +1276,37 @@ window.__ModuleLoader__.load({
|
|
|
1205
1276
|
var handle = editorRefs.current.title
|
|
1206
1277
|
if (handle) handle.insertAtCaret('title')
|
|
1207
1278
|
else setVal('titleTemplate', String(cur.titleTemplate ?? '') + '{title}')
|
|
1208
|
-
|
|
1279
|
+
// 插入后保持菜单展开:方便连续插入多个标签,由用户自行折叠
|
|
1209
1280
|
},
|
|
1210
1281
|
style: { appearance: 'none', font: 'inherit', fontSize: '12px', cursor: 'pointer', padding: '4px 10px', borderRadius: '999px', border: '1px solid var(--dsw-alias-border-l2, rgba(127,127,127,0.35))', background: 'none', color: 'var(--dsw-alias-label-secondary, inherit)' },
|
|
1211
1282
|
}, '+ ' + t.tokens.title)
|
|
1212
1283
|
)
|
|
1213
1284
|
: null,
|
|
1214
1285
|
),
|
|
1286
|
+
// 按原因定制标题(折叠区,避免界面臃肿):留空 = 用上方全局模板或语言默认标题
|
|
1287
|
+
h('div', { key: 'title-tpl', style: { display: 'flex', flexDirection: 'column', gap: '6px', padding: '12px 0', borderTop: DIVIDER } },
|
|
1288
|
+
h('button', {
|
|
1289
|
+
type: 'button',
|
|
1290
|
+
onClick: function () { setTitleTplOpen(!titleTplOpen) },
|
|
1291
|
+
style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', appearance: 'none', background: 'none', border: 'none', cursor: 'pointer', padding: '0', font: 'inherit', textAlign: 'left' },
|
|
1292
|
+
},
|
|
1293
|
+
h('span', { style: { fontSize: '13px', color: 'var(--dsw-alias-label-secondary, inherit)' } }, t.titleTplLabel),
|
|
1294
|
+
h('span', { style: { fontSize: '12px', color: SUBTLE } }, titleTplOpen ? t.titleTplClose : t.titleTplOpen),
|
|
1295
|
+
),
|
|
1296
|
+
titleTplOpen
|
|
1297
|
+
? REASON_FIELDS.map(function (field) {
|
|
1298
|
+
return h('div', { key: 'tt-' + field, style: { display: 'flex', alignItems: 'center', gap: '8px' } },
|
|
1299
|
+
h('span', { style: { width: '76px', flex: 'none', fontSize: '12px', color: 'var(--dsw-alias-label-tertiary, rgba(127,127,127,0.8))' } }, t.fields[field]),
|
|
1300
|
+
h('input', {
|
|
1301
|
+
value: titleTplOf(cur, field),
|
|
1302
|
+
placeholder: t.ttPlaceholders[field],
|
|
1303
|
+
onChange: function (e) { setVal('tt-' + field, e.target.value) },
|
|
1304
|
+
style: Object.assign({}, SELECT_STYLE, { flex: 1, fontSize: '13px' }),
|
|
1305
|
+
}),
|
|
1306
|
+
)
|
|
1307
|
+
})
|
|
1308
|
+
: null,
|
|
1309
|
+
),
|
|
1215
1310
|
]
|
|
1216
1311
|
// 模板(forEach:每条回调独立绑定字段,避免 var 闭包串写)
|
|
1217
1312
|
// 每栏 = 标签行(左文案 + 右「+插入信息」)+ ChipEditor(文字+内联胶囊,光标处插入)
|
|
@@ -1225,18 +1320,30 @@ window.__ModuleLoader__.load({
|
|
|
1225
1320
|
key: 'tpl-' + field,
|
|
1226
1321
|
style: { display: 'flex', flexDirection: 'column', gap: '6px', padding: '12px 0', borderTop: DIVIDER },
|
|
1227
1322
|
},
|
|
1228
|
-
h('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center' } },
|
|
1323
|
+
h('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: '8px' } },
|
|
1229
1324
|
h('div', { style: { fontSize: '13px', color: 'var(--dsw-alias-label-secondary, inherit)' } }, t.fields[field]),
|
|
1230
|
-
h('
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1325
|
+
h('div', { style: { display: 'flex', gap: '6px' } },
|
|
1326
|
+
h('button', {
|
|
1327
|
+
type: 'button',
|
|
1328
|
+
title: t.sendTestTitle,
|
|
1329
|
+
onClick: function () { sendTestNotice(field) },
|
|
1330
|
+
style: {
|
|
1331
|
+
appearance: 'none', font: 'inherit', fontSize: '12px', cursor: 'pointer', padding: '3px 10px',
|
|
1332
|
+
borderRadius: '999px', border: '1px solid var(--dsw-alias-border-l2, rgba(127,127,127,0.35))',
|
|
1333
|
+
background: 'none', color: 'var(--dsw-alias-label-secondary, inherit)',
|
|
1334
|
+
},
|
|
1335
|
+
}, t.sendTest),
|
|
1336
|
+
h('button', {
|
|
1337
|
+
type: 'button',
|
|
1338
|
+
title: t.insertTitle,
|
|
1339
|
+
onClick: function () { setMenuFor(menuFor === field ? null : field) },
|
|
1340
|
+
style: {
|
|
1341
|
+
appearance: 'none', font: 'inherit', fontSize: '12px', cursor: 'pointer', padding: '3px 10px',
|
|
1342
|
+
borderRadius: '999px', border: '1px solid var(--dsw-alias-border-l2, rgba(127,127,127,0.35))',
|
|
1343
|
+
background: 'none', color: 'var(--dsw-alias-label-secondary, inherit)',
|
|
1344
|
+
},
|
|
1345
|
+
}, t.insertInfo),
|
|
1346
|
+
),
|
|
1240
1347
|
),
|
|
1241
1348
|
h(ChipEditor, {
|
|
1242
1349
|
template: tplValue,
|
|
@@ -1263,7 +1370,7 @@ window.__ModuleLoader__.load({
|
|
|
1263
1370
|
var handle = editorRefs.current[field]
|
|
1264
1371
|
if (handle) handle.insertAtCaret(k)
|
|
1265
1372
|
else setVal('tpl-' + field, templateOf(cur, field) + '{' + k + '}')
|
|
1266
|
-
|
|
1373
|
+
// 插入后保持菜单展开:方便连续插入多个标签,由用户自行折叠
|
|
1267
1374
|
},
|
|
1268
1375
|
style: {
|
|
1269
1376
|
appearance: 'none', font: 'inherit', fontSize: '12px', cursor: 'pointer', padding: '4px 10px',
|
package/lib/core.js
CHANGED
|
@@ -25,57 +25,96 @@ function tableFor(language) {
|
|
|
25
25
|
return LABELS[language] || LABELS.zh
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
/**
|
|
29
|
+
* 各语言默认推送标题(按结束原因差异化)。
|
|
30
|
+
* 优先级:titleTemplates[kind] > titleTemplate(全局模板)> 本表。
|
|
31
|
+
*/
|
|
32
|
+
const DEFAULT_TITLES = {
|
|
33
|
+
zh: { completed: '任务已完成', aborted: '任务已中止', blocked: '任务被阻塞', error: '任务出错', 'max-tokens': '任务达到输出上限' },
|
|
34
|
+
'zh-tw': { completed: '任務已完成', aborted: '任務已中止', blocked: '任務被阻塞', error: '任務出錯', 'max-tokens': '任務達到輸出上限' },
|
|
35
|
+
en: { completed: 'Task completed', aborted: 'Task aborted', blocked: 'Task blocked', error: 'Task failed', 'max-tokens': 'Task hit the output cap' },
|
|
36
|
+
ja: { completed: 'タスク完了', aborted: 'タスク中止', blocked: 'タスクがブロックされました', error: 'タスクエラー', 'max-tokens': '出力上限に到達' },
|
|
37
|
+
ko: { completed: '작업 완료', aborted: '작업 중단됨', blocked: '작업 차단됨', error: '작업 오류', 'max-tokens': '출력 한도 도달' },
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* 渲染某原因的实际推送标题:
|
|
42
|
+
* titleTemplates[kind](按原因定制)> titleTemplate(全局模板)> DEFAULT_TITLES[lang][kind]。
|
|
43
|
+
* 模板支持 {title} 占位符(会话标题,缺失为空);渲染后为空时回退默认标题。
|
|
44
|
+
* @param {string} kind
|
|
45
|
+
* @param {{ language?: string; titleTemplate?: string; titleTemplates?: Record<string, string> }} opts
|
|
46
|
+
* @param {string} [titleValue]
|
|
47
|
+
* @returns {string}
|
|
48
|
+
*/
|
|
49
|
+
export function renderTitle(kind, opts = {}, titleValue = '') {
|
|
50
|
+
const language = LABELS[opts.language] ? opts.language : 'zh'
|
|
51
|
+
const per = (opts.titleTemplates && typeof opts.titleTemplates[kind] === 'string' && opts.titleTemplates[kind].trim() !== '') ? opts.titleTemplates[kind] : ''
|
|
52
|
+
const global = typeof opts.titleTemplate === 'string' && opts.titleTemplate.trim() !== '' ? opts.titleTemplate : ''
|
|
53
|
+
const fallback = (DEFAULT_TITLES[language] && DEFAULT_TITLES[language][kind]) || DEFAULT_TITLES.zh[kind] || '任务已完成'
|
|
54
|
+
const tpl = per || global || fallback
|
|
55
|
+
const out = String(tpl).replaceAll('{title}', titleValue).trim()
|
|
56
|
+
return out || fallback
|
|
57
|
+
}
|
|
58
|
+
|
|
28
59
|
/**
|
|
29
60
|
* 各语言默认正文句式(templates 留空时的回退文案)。
|
|
30
61
|
* 按 reason 差异化表达,避免清一色「(用时 X,消耗 Y)」:
|
|
31
62
|
* - completed:括号紧凑式;aborted/blocked:句号拆句;error:错误详情前置;
|
|
32
63
|
* - max-tokens:句号拆句 + 建议;generic:兜底。
|
|
33
|
-
*
|
|
64
|
+
* 标签内嵌会话标题:{titleBlock}(带语言引号;无标题时为空块,自动退回「会话已完成」)。
|
|
65
|
+
* 其他占位符:{errBlock} 错误详情块(含分隔符,无错误为空)、{dur} 用时、{use} 消耗。
|
|
34
66
|
* 仅当用时与消耗都有数据时启用;数据不全回退到 buildNotice 的 parts 拼接逻辑(自动省略缺失项)。
|
|
35
67
|
*/
|
|
36
68
|
const DEFAULT_PHRASES = {
|
|
37
69
|
zh: {
|
|
38
|
-
completed: '{
|
|
39
|
-
aborted: '{
|
|
40
|
-
blocked: '{
|
|
41
|
-
error: '{
|
|
42
|
-
'max-tokens': '{
|
|
43
|
-
generic: '{
|
|
70
|
+
completed: '会话{titleBlock}已完成(用时 {dur},消耗 {use})。',
|
|
71
|
+
aborted: '会话{titleBlock}已中止。用时 {dur},消耗 {use}。',
|
|
72
|
+
blocked: '会话{titleBlock}被阻塞。用时 {dur},消耗 {use}。',
|
|
73
|
+
error: '会话{titleBlock}出错{errBlock}(用时 {dur},消耗 {use})。',
|
|
74
|
+
'max-tokens': '会话{titleBlock}达到输出上限。用时 {dur},消耗 {use},建议拆分任务后重试。',
|
|
75
|
+
generic: '会话{titleBlock}已结束。用时 {dur},消耗 {use}。',
|
|
44
76
|
},
|
|
45
77
|
'zh-tw': {
|
|
46
|
-
completed: '{
|
|
47
|
-
aborted: '{
|
|
48
|
-
blocked: '{
|
|
49
|
-
error: '{
|
|
50
|
-
'max-tokens': '{
|
|
51
|
-
generic: '{
|
|
78
|
+
completed: '會話{titleBlock}已完成(用時 {dur},消耗 {use})。',
|
|
79
|
+
aborted: '會話{titleBlock}已中止。用時 {dur},消耗 {use}。',
|
|
80
|
+
blocked: '會話{titleBlock}被阻塞。用時 {dur},消耗 {use}。',
|
|
81
|
+
error: '會話{titleBlock}出錯{errBlock}(用時 {dur},消耗 {use})。',
|
|
82
|
+
'max-tokens': '會話{titleBlock}達到輸出上限。用時 {dur},消耗 {use},建議拆分任務後重試。',
|
|
83
|
+
generic: '會話{titleBlock}已結束。用時 {dur},消耗 {use}。',
|
|
52
84
|
},
|
|
53
85
|
en: {
|
|
54
|
-
completed: '{
|
|
55
|
-
aborted: '{
|
|
56
|
-
blocked: '{
|
|
57
|
-
error: '{
|
|
58
|
-
'max-tokens': '{
|
|
59
|
-
generic: '{
|
|
86
|
+
completed: 'Session {titleBlock} completed (took {dur}, used {use}).',
|
|
87
|
+
aborted: 'Session {titleBlock} aborted. Took {dur}, used {use}.',
|
|
88
|
+
blocked: 'Session {titleBlock} blocked. Took {dur}, used {use}.',
|
|
89
|
+
error: 'Session {titleBlock} failed{errBlock} (took {dur}, used {use}).',
|
|
90
|
+
'max-tokens': 'Session {titleBlock} hit the output-token cap. Took {dur}, used {use} — consider splitting the task.',
|
|
91
|
+
generic: 'Session {titleBlock} ended. Took {dur}, used {use}.',
|
|
60
92
|
},
|
|
61
93
|
ja: {
|
|
62
|
-
completed: '{
|
|
63
|
-
aborted: '{
|
|
64
|
-
blocked: '{
|
|
65
|
-
error: '{
|
|
66
|
-
'max-tokens': '{
|
|
67
|
-
generic: '{
|
|
94
|
+
completed: 'セッション{titleBlock}完了(所要 {dur}、消費 {use})。',
|
|
95
|
+
aborted: 'セッション{titleBlock}中止。所要 {dur}、消費 {use}。',
|
|
96
|
+
blocked: 'セッション{titleBlock}がブロックされました。所要 {dur}、消費 {use}。',
|
|
97
|
+
error: 'セッション{titleBlock}エラー{errBlock}(所要 {dur}、消費 {use})。',
|
|
98
|
+
'max-tokens': 'セッション{titleBlock}が出力トークン上限に到達。所要 {dur}、消費 {use}。タスクの分割をおすすめします。',
|
|
99
|
+
generic: 'セッション{titleBlock}終了。所要 {dur}、消費 {use}。',
|
|
68
100
|
},
|
|
69
101
|
ko: {
|
|
70
|
-
completed: '{
|
|
71
|
-
aborted: '{
|
|
72
|
-
blocked: '{
|
|
73
|
-
error: '{
|
|
74
|
-
'max-tokens': '{
|
|
75
|
-
generic: '{
|
|
102
|
+
completed: '세션{titleBlock} 완료(소요 {dur}, 소모 {use})。',
|
|
103
|
+
aborted: '세션{titleBlock} 중단됨. 소요 {dur}, 소모 {use}.',
|
|
104
|
+
blocked: '세션{titleBlock} 차단됨. 소요 {dur}, 소모 {use}.',
|
|
105
|
+
error: '세션{titleBlock} 오류{errBlock}(소요 {dur}, 소모 {use}).',
|
|
106
|
+
'max-tokens': '세션{titleBlock} 출력 한도 도달. 소요 {dur}, 소모 {use} — 작업 분할을 권장합니다.',
|
|
107
|
+
generic: '세션{titleBlock} 종료. 소요 {dur}, 소모 {use}.',
|
|
76
108
|
},
|
|
77
109
|
}
|
|
78
110
|
|
|
111
|
+
/** 会话标题块:带语言引号(zh/zh-tw/ja/ko「」,en ");无标题返回空块。 */
|
|
112
|
+
function titleValueBlock(language, titleValue) {
|
|
113
|
+
const v = typeof titleValue === 'string' ? titleValue.trim() : ''
|
|
114
|
+
if (!v) return ''
|
|
115
|
+
return language === 'en' ? `"${v}"` : `「${v}」`
|
|
116
|
+
}
|
|
117
|
+
|
|
79
118
|
/**
|
|
80
119
|
* 按语言取 reason 标签。
|
|
81
120
|
* @param {string} kind
|
|
@@ -297,8 +336,10 @@ export function buildNotice(kind, reasonData, { startedAt, endedAt, usage = null
|
|
|
297
336
|
} else if (durationLine && usageLine && phraseTpl) {
|
|
298
337
|
// 默认句式(用时+消耗都有时启用,按 reason 差异化表达)
|
|
299
338
|
const errBlock = errText ? L.sep + errText : ''
|
|
339
|
+
const titleBlock = titleValueBlock(language, titleValue)
|
|
300
340
|
text = phraseTpl
|
|
301
341
|
.replaceAll('{label}', label)
|
|
342
|
+
.replaceAll('{titleBlock}', titleBlock)
|
|
302
343
|
.replaceAll('{errBlock}', errBlock)
|
|
303
344
|
.replaceAll('{dur}', durationLine)
|
|
304
345
|
.replaceAll('{use}', usageLine)
|