@telosmaylx/dsh-session-notify 0.1.8 → 0.1.10
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.en.md +590 -0
- package/README.ja.md +590 -0
- package/README.ko.md +590 -0
- package/README.md +590 -577
- package/README.zh-TW.md +590 -0
- package/lib/client.js +168 -68
- package/lib/core.js +72 -31
- package/lib/index.js +46 -10
- package/package.json +74 -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,10 @@ 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]
|
|
978
|
+
var titleInputRef = useRef(null) // 全局推送标题 input(「+ 会话标题」在光标处插入)
|
|
939
979
|
|
|
940
980
|
/** 用户手势内请求通知权限(Chromium 忽略非手势的自动请求,必须由点击触发);授权结果经重渲染刷新状态区。 */
|
|
941
981
|
function requestPermissionNow() {
|
|
@@ -1025,6 +1065,10 @@ window.__ModuleLoader__.load({
|
|
|
1025
1065
|
setCustomPresets(next)
|
|
1026
1066
|
saveCustomPresets(next)
|
|
1027
1067
|
setActivePreset(null)
|
|
1068
|
+
// 删除的是当前使用的预设 → 表单恢复默认预设内容(presetIdOf 自动匹配「默认」)
|
|
1069
|
+
if (shownEntry.id === (activePreset && activePreset.id)) {
|
|
1070
|
+
setDraft(Object.assign({}, DEFAULT_FLAT))
|
|
1071
|
+
}
|
|
1028
1072
|
setNote(t.customDeleted)
|
|
1029
1073
|
log('custom preset deleted')
|
|
1030
1074
|
}
|
|
@@ -1032,15 +1076,18 @@ window.__ModuleLoader__.load({
|
|
|
1032
1076
|
function save() {
|
|
1033
1077
|
var flat = flatOf(cur)
|
|
1034
1078
|
var templates = {}
|
|
1079
|
+
var titleTemplates = {}
|
|
1035
1080
|
for (var i = 0; i < REASON_FIELDS.length; i++) {
|
|
1036
1081
|
var k = REASON_FIELDS[i]
|
|
1037
1082
|
templates[k] = flat['tpl-' + k]
|
|
1083
|
+
titleTemplates[k] = flat['tt-' + k]
|
|
1038
1084
|
}
|
|
1039
1085
|
setNote(t.saving)
|
|
1040
1086
|
var tasks = [
|
|
1041
1087
|
scope.set('language', flat.language),
|
|
1042
1088
|
scope.set('templates', templates),
|
|
1043
1089
|
scope.set('titleTemplate', flat.titleTemplate),
|
|
1090
|
+
scope.set('titleTemplates', titleTemplates),
|
|
1044
1091
|
scope.set('pushMode', flat.pushMode),
|
|
1045
1092
|
]
|
|
1046
1093
|
Promise.all(tasks).then(function () {
|
|
@@ -1054,7 +1101,7 @@ window.__ModuleLoader__.load({
|
|
|
1054
1101
|
var found = null
|
|
1055
1102
|
if (shownCustom && shownEntry) {
|
|
1056
1103
|
targetId = shownEntry.id
|
|
1057
|
-
entry = Object.assign({}, shownEntry, { language: lang, titleTemplate: flat.titleTemplate, templates: templates })
|
|
1104
|
+
entry = Object.assign({}, shownEntry, { language: lang, titleTemplate: flat.titleTemplate, titleTemplates: titleTemplates, templates: templates })
|
|
1058
1105
|
list = customPresets.map(function (cp) {
|
|
1059
1106
|
if (cp.id === targetId) { found = Object.assign({}, cp, entry); return found }
|
|
1060
1107
|
return cp
|
|
@@ -1063,7 +1110,7 @@ window.__ModuleLoader__.load({
|
|
|
1063
1110
|
} else {
|
|
1064
1111
|
var n = nextUnnamedNumber(customPresets)
|
|
1065
1112
|
targetId = n === 1 ? 'unnamed' : 'unnamed-' + n
|
|
1066
|
-
entry = { id: targetId, name: null, auto: true, language: lang, titleTemplate: flat.titleTemplate, templates: templates }
|
|
1113
|
+
entry = { id: targetId, name: null, auto: true, language: lang, titleTemplate: flat.titleTemplate, titleTemplates: titleTemplates, templates: templates }
|
|
1067
1114
|
list = customPresets.concat([entry])
|
|
1068
1115
|
found = entry
|
|
1069
1116
|
}
|
|
@@ -1080,21 +1127,46 @@ window.__ModuleLoader__.load({
|
|
|
1080
1127
|
})
|
|
1081
1128
|
}
|
|
1082
1129
|
|
|
1083
|
-
/**
|
|
1130
|
+
/** 发送测试通知:用当前模板(空模板用默认文案)渲染该原因的通知;标题按原因优先级。 */
|
|
1131
|
+
function sendTestNotice(field) {
|
|
1132
|
+
try {
|
|
1133
|
+
var samples = t.samples
|
|
1134
|
+
var titleTpl = titleTplOf(cur, field) || String(cur.titleTemplate || '').trim() || t.ttPlaceholders[field]
|
|
1135
|
+
var title = String(titleTpl).replaceAll('{title}', samples.title).trim() || samples.title
|
|
1136
|
+
var tpl = templateOf(cur, field)
|
|
1137
|
+
var bodyTpl = tpl !== '' ? tpl : defaultHintOf(field, cur.language)
|
|
1138
|
+
var body = String(bodyTpl)
|
|
1139
|
+
.replace(/\{label\}/g, '')
|
|
1140
|
+
.replaceAll('{title}', samples.title)
|
|
1141
|
+
.replaceAll('{duration}', samples.duration)
|
|
1142
|
+
.replaceAll('{usage}', samples.usage)
|
|
1143
|
+
.replaceAll('{error}', samples.error)
|
|
1144
|
+
.replaceAll('{cache}', samples.cache)
|
|
1145
|
+
.replaceAll('{tps}', samples.tps)
|
|
1146
|
+
notifyUser(title, body, ['dual', 'system', 'toast'].indexOf(cur.pushMode) >= 0 ? cur.pushMode : 'dual')
|
|
1147
|
+
log('test notice sent: ' + field)
|
|
1148
|
+
} catch (e) {
|
|
1149
|
+
log('test notice failed: ' + (e && e.message ? e.message : e))
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
/** 一键重置:除语言外全部还原为默认值(语言保留当前选择)并立即保存。 */
|
|
1084
1154
|
function resetAll() {
|
|
1085
1155
|
var def = DEFAULT_FLAT
|
|
1156
|
+
var lang = LANG_IDS.indexOf(cur.language) >= 0 ? cur.language : 'zh' // 重置不改变语言
|
|
1086
1157
|
setNote(t.saving)
|
|
1087
1158
|
var tasks = [
|
|
1088
|
-
scope.set('language',
|
|
1159
|
+
scope.set('language', lang),
|
|
1089
1160
|
scope.set('templates', def.templates),
|
|
1090
1161
|
scope.set('titleTemplate', def.titleTemplate),
|
|
1162
|
+
scope.set('titleTemplates', def.titleTemplates),
|
|
1091
1163
|
scope.set('pushMode', def.pushMode),
|
|
1092
1164
|
]
|
|
1093
1165
|
Promise.all(tasks).then(function () {
|
|
1094
1166
|
setDraft(null)
|
|
1095
1167
|
setNote(t.resetDone)
|
|
1096
1168
|
setJustSaved(true)
|
|
1097
|
-
log('settings reset to defaults')
|
|
1169
|
+
log('settings reset to defaults (language kept: ' + lang + ')')
|
|
1098
1170
|
}).catch(function (e) {
|
|
1099
1171
|
setNote(t.saveFailed + (e && e.message ? e.message : e))
|
|
1100
1172
|
log('settings reset failed: ' + (e && e.message ? e.message : e))
|
|
@@ -1183,33 +1255,49 @@ window.__ModuleLoader__.load({
|
|
|
1183
1255
|
h('div', { style: { fontSize: '13px', color: 'var(--dsw-alias-label-secondary, inherit)' } }, t.titleLabel),
|
|
1184
1256
|
h('button', {
|
|
1185
1257
|
type: 'button', title: t.insertTitle,
|
|
1186
|
-
onClick: function () {
|
|
1258
|
+
onClick: function () {
|
|
1259
|
+
var el = titleInputRef.current
|
|
1260
|
+
var val = String(cur.titleTemplate ?? '')
|
|
1261
|
+
var pos = el ? (el.selectionStart != null ? el.selectionStart : val.length) : val.length
|
|
1262
|
+
var next = val.slice(0, pos) + '{title}' + val.slice(pos)
|
|
1263
|
+
setVal('titleTemplate', next)
|
|
1264
|
+
setTimeout(function () {
|
|
1265
|
+
if (el) { el.focus(); var np = pos + 7; el.setSelectionRange(np, np) }
|
|
1266
|
+
}, 0)
|
|
1267
|
+
},
|
|
1187
1268
|
style: { appearance: 'none', font: 'inherit', fontSize: '12px', cursor: 'pointer', padding: '3px 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)' },
|
|
1188
|
-
}, t.
|
|
1269
|
+
}, '+ ' + t.tokens.title),
|
|
1189
1270
|
),
|
|
1190
|
-
h(
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
hint: '',
|
|
1271
|
+
h('input', {
|
|
1272
|
+
ref: titleInputRef,
|
|
1273
|
+
value: String(cur.titleTemplate ?? ''),
|
|
1194
1274
|
placeholder: t.titleHint,
|
|
1195
|
-
onChange: function (
|
|
1196
|
-
|
|
1197
|
-
if (handle) editorRefs.current.title = handle
|
|
1198
|
-
else delete editorRefs.current.title
|
|
1199
|
-
},
|
|
1275
|
+
onChange: function (e) { setVal('titleTemplate', e.target.value) },
|
|
1276
|
+
style: Object.assign({}, SELECT_STYLE, { flex: 1, fontSize: '13px' }),
|
|
1200
1277
|
}),
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1278
|
+
),
|
|
1279
|
+
// 按原因定制标题(折叠区,避免界面臃肿):留空 = 用上方全局模板或语言默认标题
|
|
1280
|
+
h('div', { key: 'title-tpl', style: { display: 'flex', flexDirection: 'column', gap: '6px', padding: '12px 0', borderTop: DIVIDER } },
|
|
1281
|
+
h('button', {
|
|
1282
|
+
type: 'button',
|
|
1283
|
+
onClick: function () { setTitleTplOpen(!titleTplOpen) },
|
|
1284
|
+
style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', appearance: 'none', background: 'none', border: 'none', cursor: 'pointer', padding: '0', font: 'inherit', textAlign: 'left' },
|
|
1285
|
+
},
|
|
1286
|
+
h('span', { style: { fontSize: '13px', color: 'var(--dsw-alias-label-secondary, inherit)' } }, t.titleTplLabel),
|
|
1287
|
+
h('span', { style: { fontSize: '12px', color: SUBTLE } }, titleTplOpen ? t.titleTplClose : t.titleTplOpen),
|
|
1288
|
+
),
|
|
1289
|
+
titleTplOpen
|
|
1290
|
+
? REASON_FIELDS.map(function (field) {
|
|
1291
|
+
return h('div', { key: 'tt-' + field, style: { display: 'flex', alignItems: 'center', gap: '8px' } },
|
|
1292
|
+
h('span', { style: { width: '76px', flex: 'none', fontSize: '12px', color: 'var(--dsw-alias-label-tertiary, rgba(127,127,127,0.8))' } }, t.fields[field]),
|
|
1293
|
+
h('input', {
|
|
1294
|
+
value: titleTplOf(cur, field),
|
|
1295
|
+
placeholder: t.ttPlaceholders[field],
|
|
1296
|
+
onChange: function (e) { setVal('tt-' + field, e.target.value) },
|
|
1297
|
+
style: Object.assign({}, SELECT_STYLE, { flex: 1, fontSize: '13px' }),
|
|
1298
|
+
}),
|
|
1299
|
+
)
|
|
1300
|
+
})
|
|
1213
1301
|
: null,
|
|
1214
1302
|
),
|
|
1215
1303
|
]
|
|
@@ -1225,18 +1313,30 @@ window.__ModuleLoader__.load({
|
|
|
1225
1313
|
key: 'tpl-' + field,
|
|
1226
1314
|
style: { display: 'flex', flexDirection: 'column', gap: '6px', padding: '12px 0', borderTop: DIVIDER },
|
|
1227
1315
|
},
|
|
1228
|
-
h('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center' } },
|
|
1316
|
+
h('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: '8px' } },
|
|
1229
1317
|
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
|
-
|
|
1318
|
+
h('div', { style: { display: 'flex', gap: '6px' } },
|
|
1319
|
+
h('button', {
|
|
1320
|
+
type: 'button',
|
|
1321
|
+
title: t.sendTestTitle,
|
|
1322
|
+
onClick: function () { sendTestNotice(field) },
|
|
1323
|
+
style: {
|
|
1324
|
+
appearance: 'none', font: 'inherit', fontSize: '12px', cursor: 'pointer', padding: '3px 10px',
|
|
1325
|
+
borderRadius: '999px', border: '1px solid var(--dsw-alias-border-l2, rgba(127,127,127,0.35))',
|
|
1326
|
+
background: 'none', color: 'var(--dsw-alias-label-secondary, inherit)',
|
|
1327
|
+
},
|
|
1328
|
+
}, t.sendTest),
|
|
1329
|
+
h('button', {
|
|
1330
|
+
type: 'button',
|
|
1331
|
+
title: t.insertTitle,
|
|
1332
|
+
onClick: function () { setMenuFor(menuFor === field ? null : field) },
|
|
1333
|
+
style: {
|
|
1334
|
+
appearance: 'none', font: 'inherit', fontSize: '12px', cursor: 'pointer', padding: '3px 10px',
|
|
1335
|
+
borderRadius: '999px', border: '1px solid var(--dsw-alias-border-l2, rgba(127,127,127,0.35))',
|
|
1336
|
+
background: 'none', color: 'var(--dsw-alias-label-secondary, inherit)',
|
|
1337
|
+
},
|
|
1338
|
+
}, t.insertInfo),
|
|
1339
|
+
),
|
|
1240
1340
|
),
|
|
1241
1341
|
h(ChipEditor, {
|
|
1242
1342
|
template: tplValue,
|
|
@@ -1263,7 +1363,7 @@ window.__ModuleLoader__.load({
|
|
|
1263
1363
|
var handle = editorRefs.current[field]
|
|
1264
1364
|
if (handle) handle.insertAtCaret(k)
|
|
1265
1365
|
else setVal('tpl-' + field, templateOf(cur, field) + '{' + k + '}')
|
|
1266
|
-
|
|
1366
|
+
// 插入后保持菜单展开:方便连续插入多个标签,由用户自行折叠
|
|
1267
1367
|
},
|
|
1268
1368
|
style: {
|
|
1269
1369
|
appearance: 'none', font: 'inherit', fontSize: '12px', cursor: 'pointer', padding: '4px 10px',
|