dsh-remote-plugin 0.6.12 → 0.6.14

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/public/index.html CHANGED
@@ -100,6 +100,8 @@
100
100
 
101
101
  <div id="session-cards" class="cards"></div>
102
102
 
103
+ <div id="queue-dock" class="queue-dock hidden" aria-live="polite"></div>
104
+
103
105
  <div class="section-head small">
104
106
  <span data-i18n="session.history">对话</span>
105
107
  <span id="history-hint" class="muted"></span>
@@ -114,6 +116,7 @@
114
116
  </div>
115
117
 
116
118
  <div id="composer-wrap" class="composer-wrap">
119
+ <div id="composer-status" class="composer-status hidden" role="status" aria-live="polite"><span class="composer-status-dot" aria-hidden="true"></span><span data-i18n="composer.running">运行中…</span></div>
117
120
  <div id="composer-menu" class="composer-menu hidden">
118
121
  <div class="menu-group">
119
122
  <div class="menu-title" data-i18n="menu.commandsTitle">输入指令</div>
@@ -346,9 +349,9 @@
346
349
  <button id="btn-server-add" class="mini-btn" data-i18n="settings.add">添加</button>
347
350
  </div>
348
351
  <div class="setting-row">
349
- <div><div class="setting-name" data-i18n="settings.scanTitle">扫码连接</div><div class="setting-desc" data-i18n="settings.scanDesc">拍照/相册本地解码(无谷歌服务依赖);也可系统相机扫码或手动粘贴</div></div>
352
+ <div><div class="setting-name" data-i18n="settings.scanTitle">扫码连接</div><div class="setting-desc" data-i18n="settings.scanDesc">实时相机识别二维码,也可拍照/相册本地解码;无需谷歌服务</div></div>
350
353
  <div class="setting-actions">
351
- <button id="btn-scan-camera" class="mini-btn" data-i18n="scan.promptPhoto">拍照扫描</button>
354
+ <button id="btn-scan-camera" class="mini-btn" data-i18n="scan.promptLive">实时扫码</button>
352
355
  <button id="btn-scan-gallery" class="mini-btn" data-i18n="scan.promptGallery">从相册选择</button>
353
356
  </div>
354
357
  </div>
@@ -698,6 +701,40 @@
698
701
  </div>
699
702
  </div>
700
703
 
704
+ <!-- App / 网关版本差异提醒 -->
705
+ <div id="modal-app-version-warning" class="modal hidden" role="dialog" aria-modal="true" aria-labelledby="app-version-warning-title">
706
+ <div class="modal-card app-version-warning-card">
707
+ <div class="app-version-warning-icon" aria-hidden="true">↑</div>
708
+ <div id="app-version-warning-title" class="modal-title app-version-warning-title" data-i18n="update.gatewayAheadTitle">App 版本需要更新</div>
709
+ <div class="modal-body app-version-warning-body" data-i18n="update.gatewayAheadBody">网关已经更新,但当前 App 仍是较旧版本,部分功能可能缺失或显示异常。</div>
710
+ <div class="app-version-comparison" aria-label="App and gateway versions">
711
+ <div><span data-i18n="update.appVersion">App 版本</span><strong id="app-version-current">—</strong></div>
712
+ <i aria-hidden="true">→</i>
713
+ <div><span data-i18n="update.gatewayVersion">网关版本</span><strong id="app-version-gateway">—</strong></div>
714
+ </div>
715
+ <div class="app-version-warning-hint" data-i18n="update.gatewayAheadHint">建议同步更新 App,以保持与网关功能一致。</div>
716
+ <div class="modal-actions">
717
+ <button id="app-version-later" class="btn subtle" data-i18n="update.remindLater">稍后提醒</button>
718
+ <button id="app-version-update" class="btn primary" data-i18n="update.checkNow">立即检查更新</button>
719
+ </div>
720
+ </div>
721
+ </div>
722
+
723
+ <!-- 实时扫码模态 -->
724
+ <div id="modal-scan-live" class="modal hidden" role="dialog" aria-modal="true" aria-labelledby="scan-live-title">
725
+ <div class="modal-card scan-live-card">
726
+ <div id="scan-live-title" class="modal-title" data-i18n="scan.liveTitle">实时扫码连接</div>
727
+ <div class="scan-live-preview">
728
+ <video id="scan-live-video" autoplay playsinline muted></video>
729
+ <div class="scan-live-frame" aria-hidden="true"><span></span></div>
730
+ </div>
731
+ <div id="scan-live-status" class="scan-live-status" data-i18n="scan.liveHint">将二维码放入框内,识别后自动连接</div>
732
+ <div class="modal-actions">
733
+ <button id="scan-live-cancel" class="btn subtle" data-i18n="scan.liveCancel">取消</button>
734
+ </div>
735
+ </div>
736
+ </div>
737
+
701
738
  <!-- 更新内容模态 -->
702
739
  <div id="modal-notes" class="modal hidden" role="dialog" aria-modal="true">
703
740
  <div class="modal-card">
@@ -823,7 +860,7 @@
823
860
  'tool.default': '工具', 'tool.unknown': '未知工具', 'tool.result': '结果',
824
861
  'event.taskStart': '▶ 任务开始', 'event.taskEnd': '■ 任务结束',
825
862
  'event.systemReminder': '系统提示',
826
- 'block.image': '图片', 'block.thinking': '🧠 思考过程', 'block.toolCall': '工具调用', 'block.toolResult': '工具结果', 'block.unknown': '块 · {type}',
863
+ 'block.image': '图片', 'block.thinking': '🧠 思考过程', 'block.thinkingLive': '🧠 思考中…', 'block.toolCall': '工具调用', 'block.toolResult': '工具结果', 'block.unknown': '块 · {type}',
827
864
  'truncated': '…(截断)',
828
865
  'stats.title': '本轮统计', 'stats.roundTitle': '本轮统计', 'stats.turnsSteps': '轮次 / 步骤',
829
866
  'stats.llmTime': '模型耗时', 'stats.minutes': ' 分钟', 'stats.outputCache': '输出 / 缓存读',
@@ -833,19 +870,20 @@
833
870
  'goal.actionFailed': '目标操作失败', 'goal.actionSubmitted': '目标操作已提交',
834
871
  'goal.cannotEmpty': '目标不能为空', 'goal.updateFailed': '更新失败', 'goal.updated': '目标已更新',
835
872
  'todos.title': '任务清单',
836
- 'subagent.title': '子代理', 'subagent.diagnostic': '诊断项', 'subagent.running': '· 运行中', 'subagent.interrupt': '中断',
873
+ 'subagent.title': '子代理', 'subagent.count': '子代理 · {n} 个', 'subagent.expand': '展开子代理', 'subagent.collapse': '收起子代理', 'subagent.diagnostic': '诊断项', 'subagent.running': '· 运行中', 'subagent.interrupt': '中断',
837
874
  'subagent.confirmInterrupt': '中断这个子代理当前回合?', 'subagent.interruptFailed': '中断失败', 'subagent.interruptSubmitted': '中断请求已提交',
875
+ 'queue.title': '排队消息', 'queue.steer': '插话', 'queue.steerUnavailable': '仅运行中可插话', 'queue.steerSubmitted': '插话请求已提交', 'queue.steerFailed': '插话失败:{msg}', 'queue.image': '图片消息',
838
876
  'send.failed': '发送失败', 'send.commandSent': '指令已发送', 'send.sent': '已发送', 'send.imageSent': '图片已发送', 'send.commandExecuted': '命令已执行',
839
877
  'models.loading': '模型加载中…', 'models.loadFailed': '模型列表加载失败:{msg}', 'models.unavailable': '不可用', 'models.none': '没有可用模型',
840
878
  'models.switchFailed': '切换模型失败', 'models.switched': '已切换模型:{model}',
841
- 'models.effortFailed': '切换思考深度失败', 'models.effortSwitched': '思考深度:{effort}',
879
+ 'models.effortFailed': '切换思考深度失败', 'models.effortSwitched': '思考深度:{effort}', 'models.effortLow': '低', 'models.effortHigh': '高', 'models.effortMax': '极高', 'models.effortOff': '关闭', 'models.effortCustomHint': '该路由未公布档位,按 DSH 兼容值尝试;不支持时不会更改当前设置。',
842
880
  'menu.commandsTitle': '输入指令',
843
881
  'menu.compact': '/compact 压缩对话历史', 'menu.export': '/export 导出会话日志 ZIP',
844
882
  'menu.feedback': '/feedback 反馈当前会话', 'menu.goal': '/goal 设置/查看任务目标',
845
883
  'menu.permission': '/permission 切换权限预设', 'menu.plan': '/plan 进入/退出计划模式',
846
884
  'perm.readOnly': 'read-only', 'perm.workspaceWrite': 'workspace-write', 'perm.dangerFullAccess': 'danger-full-access',
847
885
  'menu.modelTitle': '模型切换', 'menu.refresh': '刷新', 'menu.modelAuto': '点开时自动加载', 'menu.effortTitle': '思考深度',
848
- 'composer.placeholder': '给 DSH 发消息…', 'composer.send': '发送', 'composer.fullscreen': '全屏编辑', 'composer.exitFullscreen': '退出全屏编辑', 'composer.addImage': '添加图片', 'composer.takePhoto': '拍照', 'composer.choosePhoto': '相册', 'composer.imageAdded': '已添加图片', 'composer.imageRemoved': '已移除图片', 'composer.removeImage': '移除图片', 'composer.imageUnsupported': '仅支持 PNG、JPEG、WebP 或 GIF 图片', 'composer.imageTooLarge': '单张图片不能超过 {size}', 'composer.imageLimit': '最多添加 {count} 张图片', 'composer.imageSlashUnsupported': '带图片时不能发送斜杠命令', 'composer.imageReadFailed': '图片读取失败:{msg}',
886
+ 'composer.placeholder': '给 DSH 发消息…', 'composer.running': '运行中…', 'composer.send': '发送', 'composer.fullscreen': '全屏编辑', 'composer.exitFullscreen': '退出全屏编辑', 'composer.addImage': '添加图片', 'composer.takePhoto': '拍照', 'composer.choosePhoto': '相册', 'composer.imageAdded': '已添加图片', 'composer.imageRemoved': '已移除图片', 'composer.removeImage': '移除图片', 'composer.imageUnsupported': '仅支持 PNG、JPEG、WebP 或 GIF 图片', 'composer.imageTooLarge': '单张图片不能超过 {size}', 'composer.imageLimit': '最多添加 {count} 张图片', 'composer.imageSlashUnsupported': '带图片时不能发送斜杠命令', 'composer.imageReadFailed': '图片读取失败:{msg}',
849
887
  'pending.title': '待处理', 'pending.count': '{n} 项', 'pending.approvalTitle': '🔧 {tool} 请求批准', 'pending.noReason': '无说明',
850
888
  'pending.allow': '允许', 'pending.reject': '拒绝', 'pending.question': '❓ {question}',
851
889
  'pending.questionCount': '共 {n} 个问题', 'pending.answer': '去回答', 'pending.empty': '暂无待处理事项',
@@ -880,11 +918,14 @@
880
918
  'update.corrupted': '下载文件损坏,请重试', 'update.serverFileMissing': '服务器上还没有对应版本的文件,请稍后再试',
881
919
  'update.installUnsupported': '当前版本不支持 App 内安装,已转浏览器下载',
882
920
  'update.expand': '展开', 'update.collapse': '收起',
921
+ 'update.gatewayAheadTitle': 'App 版本需要更新', 'update.gatewayAheadBody': '网关已经更新,但当前 App 仍是较旧版本,部分功能可能缺失或显示异常。',
922
+ 'update.appVersion': 'App 版本', 'update.gatewayVersion': '网关版本', 'update.gatewayAheadHint': '建议同步更新 App,以保持与网关功能一致。',
923
+ 'update.remindLater': '稍后提醒', 'update.checkNow': '立即检查更新',
883
924
  'notes.title': '更新内容', 'notes.close': '关闭', 'notes.page': '版本号 {current}/{total}',
884
- 'scan.imageLoadFailed': '图片加载失败', 'scan.decodeUnsupported': '当前设备不支持图片解码',
925
+ 'scan.imageLoadFailed': '图片加载失败', 'scan.decodeUnsupported': '当前设备不支持图片解码', 'scan.liveTitle': '实时扫码连接', 'scan.liveHint': '将二维码放入框内,识别后自动连接', 'scan.liveStarting': '正在打开摄像头…', 'scan.liveCancel': '取消',
885
926
  'scan.browserHint': '浏览器请打开主机管理页,用手机相机扫码', 'scan.unsupported': '当前 App 版本不支持拍照扫码,请先更新 App',
886
927
  'scan.permissionDenied': '未授予相机权限',
887
- 'scan.promptHeader': '扫描 DSH Remote 配对二维码', 'scan.promptPhoto': '拍照扫描', 'scan.promptGallery': '从相册选择',
928
+ 'scan.promptHeader': '扫描 DSH Remote 配对二维码', 'scan.promptLive': '实时扫码', 'scan.promptPhoto': '拍照扫描', 'scan.promptGallery': '从相册选择',
888
929
  'scan.noPhoto': '没有拿到图片,请重试', 'scan.noQr': '未识别到二维码,请对准后重试',
889
930
  'scan.paired': '配对成功,正在连接', 'scan.pairedLink': '已通过二维码配对', 'scan.notPair': '这不是 DSH Remote 的配对二维码',
890
931
  'scan.cancelled': '已取消扫码', 'scan.failed': '扫码失败:{msg}',
@@ -896,7 +937,7 @@
896
937
  'settings.donate': '赞赏支持', 'settings.donateDesc': '如果 dsh-remote 帮到了你,欢迎赞赏支持开发 ☕', 'settings.donateThanks': '感谢你的支持 ☕',
897
938
  'settings.back': '‹ 返回',
898
939
  'settings.servers': '服务器地址(可多个)', 'settings.speedTest': '测速', 'settings.add': '添加',
899
- 'settings.scanTitle': '扫码连接', 'settings.scanDesc': '拍照/相册本地解码(无谷歌服务依赖);也可系统相机扫码或手动粘贴', 'settings.scan': '扫码',
940
+ 'settings.scanTitle': '扫码连接', 'settings.scanDesc': '实时相机识别二维码,也可拍照/相册本地解码;无需谷歌服务', 'settings.scan': '扫码',
900
941
  'settings.notifyTitle': '通知', 'settings.notifyDesc': '收到审批/提问时推送', 'settings.announcementHistoryTitle': '历史公告', 'settings.announcementHistoryDesc': '查看以前收到的公告', 'settings.announcementHistory': '查看记录',
901
942
  'settings.testNotifyTitle': '测试通知', 'settings.testNotifyDesc': '立即排一条 3 秒后的本地通知,用于排查系统通知限制', 'settings.testNotify': '发送测试通知',
902
943
  'settings.testNotifySent': '测试通知已排定,3 秒后到达', 'settings.testNotifyFailed': '发送测试通知失败:{msg}', 'settings.testNotifyUnavailable': '测试通知仅在 App 内可用',
@@ -1031,7 +1072,7 @@
1031
1072
  'tool.default': 'tool', 'tool.unknown': 'Unknown tool', 'tool.result': 'Result',
1032
1073
  'event.taskStart': '▶ Task started', 'event.taskEnd': '■ Task finished',
1033
1074
  'event.systemReminder': 'System reminder',
1034
- 'block.image': 'Image', 'block.thinking': '🧠 Thinking', 'block.toolCall': 'Tool call', 'block.toolResult': 'Tool result', 'block.unknown': 'Block · {type}',
1075
+ 'block.image': 'Image', 'block.thinking': '🧠 Thinking', 'block.thinkingLive': '🧠 Thinking…', 'block.toolCall': 'Tool call', 'block.toolResult': 'Tool result', 'block.unknown': 'Block · {type}',
1035
1076
  'truncated': '…(truncated)',
1036
1077
  'stats.title': 'This round', 'stats.roundTitle': 'This round', 'stats.turnsSteps': 'Turns / steps',
1037
1078
  'stats.llmTime': 'LLM time', 'stats.minutes': ' min', 'stats.outputCache': 'Output / cache read',
@@ -1041,19 +1082,20 @@
1041
1082
  'goal.actionFailed': 'Goal action failed', 'goal.actionSubmitted': 'Goal action submitted',
1042
1083
  'goal.cannotEmpty': 'Goal cannot be empty', 'goal.updateFailed': 'Update failed', 'goal.updated': 'Goal updated',
1043
1084
  'todos.title': 'Todos',
1044
- 'subagent.title': 'Subagents', 'subagent.diagnostic': 'Diagnostic', 'subagent.running': '· running', 'subagent.interrupt': 'Interrupt',
1085
+ 'subagent.title': 'Subagents', 'subagent.count': 'Subagents · {n}', 'subagent.expand': 'Expand subagents', 'subagent.collapse': 'Collapse subagents', 'subagent.diagnostic': 'Diagnostic', 'subagent.running': '· running', 'subagent.interrupt': 'Interrupt',
1045
1086
  'subagent.confirmInterrupt': 'Interrupt this subagent\'s current turn?', 'subagent.interruptFailed': 'Interrupt failed', 'subagent.interruptSubmitted': 'Interrupt requested',
1087
+ 'queue.title': 'Queued messages', 'queue.steer': 'Interject', 'queue.steerUnavailable': 'Only available while running', 'queue.steerSubmitted': 'Interjection requested', 'queue.steerFailed': 'Interjection failed: {msg}', 'queue.image': 'Image message',
1046
1088
  'send.failed': 'Send failed', 'send.commandSent': 'Command sent', 'send.sent': 'Sent', 'send.imageSent': 'Image sent', 'send.commandExecuted': 'Command executed',
1047
1089
  'models.loading': 'Loading models…', 'models.loadFailed': 'Failed to load models: {msg}', 'models.unavailable': 'unavailable', 'models.none': 'No models available',
1048
1090
  'models.switchFailed': 'Model switch failed', 'models.switched': 'Switched model: {model}',
1049
- 'models.effortFailed': 'Failed to switch reasoning effort', 'models.effortSwitched': 'Reasoning effort: {effort}',
1091
+ 'models.effortFailed': 'Failed to switch reasoning effort', 'models.effortSwitched': 'Reasoning effort: {effort}', 'models.effortLow': 'Low', 'models.effortHigh': 'High', 'models.effortMax': 'Max', 'models.effortOff': 'Off', 'models.effortCustomHint': 'This route does not publish effort metadata. DSH compatibility values are tried; unsupported values leave the current setting unchanged.',
1050
1092
  'menu.commandsTitle': 'Commands',
1051
1093
  'menu.compact': '/compact Compress conversation history', 'menu.export': '/export Export session log ZIP',
1052
1094
  'menu.feedback': '/feedback Feedback current session', 'menu.goal': '/goal Set/view task goal',
1053
1095
  'menu.permission': '/permission Switch permission preset', 'menu.plan': '/plan Enter/exit plan mode',
1054
1096
  'perm.readOnly': 'read-only', 'perm.workspaceWrite': 'workspace-write', 'perm.dangerFullAccess': 'danger-full-access',
1055
1097
  'menu.modelTitle': 'Model', 'menu.refresh': 'Refresh', 'menu.modelAuto': 'Auto-loads when opened', 'menu.effortTitle': 'Reasoning effort',
1056
- 'composer.placeholder': 'Message DSH…', 'composer.send': 'Send', 'composer.fullscreen': 'Fullscreen editor', 'composer.exitFullscreen': 'Exit fullscreen editor', 'composer.addImage': 'Add image', 'composer.takePhoto': 'Camera', 'composer.choosePhoto': 'Gallery', 'composer.imageAdded': 'Image added', 'composer.imageRemoved': 'Image removed', 'composer.removeImage': 'Remove image', 'composer.imageUnsupported': 'Only PNG, JPEG, WebP or GIF images are supported', 'composer.imageTooLarge': 'Each image must be under {size}', 'composer.imageLimit': 'You can add up to {count} images', 'composer.imageSlashUnsupported': 'Slash commands cannot include images', 'composer.imageReadFailed': 'Could not read image: {msg}',
1098
+ 'composer.placeholder': 'Message DSH…', 'composer.running': 'Running…', 'composer.send': 'Send', 'composer.fullscreen': 'Fullscreen editor', 'composer.exitFullscreen': 'Exit fullscreen editor', 'composer.addImage': 'Add image', 'composer.takePhoto': 'Camera', 'composer.choosePhoto': 'Gallery', 'composer.imageAdded': 'Image added', 'composer.imageRemoved': 'Image removed', 'composer.removeImage': 'Remove image', 'composer.imageUnsupported': 'Only PNG, JPEG, WebP or GIF images are supported', 'composer.imageTooLarge': 'Each image must be under {size}', 'composer.imageLimit': 'You can add up to {count} images', 'composer.imageSlashUnsupported': 'Slash commands cannot include images', 'composer.imageReadFailed': 'Could not read image: {msg}',
1057
1099
  'pending.title': 'Inbox', 'pending.count': '{n} items', 'pending.approvalTitle': '🔧 {tool} requests approval', 'pending.noReason': 'No reason given',
1058
1100
  'pending.allow': 'Allow', 'pending.reject': 'Reject', 'pending.question': '❓ {question}',
1059
1101
  'pending.questionCount': '{n} questions', 'pending.answer': 'Answer', 'pending.empty': 'Nothing pending',
@@ -1088,11 +1130,14 @@
1088
1130
  'update.corrupted': 'Downloaded file is corrupted, please retry', 'update.serverFileMissing': 'The file for this version is not on the server yet, please try again later',
1089
1131
  'update.installUnsupported': 'This version cannot install in-app, opening browser download',
1090
1132
  'update.expand': 'Expand', 'update.collapse': 'Collapse',
1133
+ 'update.gatewayAheadTitle': 'App update recommended', 'update.gatewayAheadBody': 'The gateway is newer than this App. Some features may be missing or display incorrectly.',
1134
+ 'update.appVersion': 'App version', 'update.gatewayVersion': 'Gateway version', 'update.gatewayAheadHint': 'Update the App to keep its features aligned with the gateway.',
1135
+ 'update.remindLater': 'Remind me later', 'update.checkNow': 'Check for updates',
1091
1136
  'notes.title': 'What\'s New', 'notes.close': 'Close', 'notes.page': 'Version {current}/{total}',
1092
- 'scan.imageLoadFailed': 'Image failed to load', 'scan.decodeUnsupported': 'This device cannot decode images',
1137
+ 'scan.imageLoadFailed': 'Image failed to load', 'scan.decodeUnsupported': 'This device cannot decode images', 'scan.liveTitle': 'Live QR scanner', 'scan.liveHint': 'Place the QR code inside the frame to connect automatically', 'scan.liveStarting': 'Opening camera…', 'scan.liveCancel': 'Cancel',
1093
1138
  'scan.browserHint': 'In a browser, open the host admin page and scan with your phone camera', 'scan.unsupported': 'This app version cannot scan, please update first',
1094
1139
  'scan.permissionDenied': 'Camera permission not granted',
1095
- 'scan.promptHeader': 'Scan DSH Remote pairing QR', 'scan.promptPhoto': 'Take photo', 'scan.promptGallery': 'Choose from gallery',
1140
+ 'scan.promptHeader': 'Scan DSH Remote pairing QR', 'scan.promptLive': 'Live scan', 'scan.promptPhoto': 'Take photo', 'scan.promptGallery': 'Choose from gallery',
1096
1141
  'scan.noPhoto': 'No image received, try again', 'scan.noQr': 'No QR code detected, aim and try again',
1097
1142
  'scan.paired': 'Paired, connecting', 'scan.pairedLink': 'Paired via QR code', 'scan.notPair': 'Not a DSH Remote pairing QR code',
1098
1143
  'scan.cancelled': 'Scan cancelled', 'scan.failed': 'Scan failed: {msg}',
@@ -1104,7 +1149,7 @@
1104
1149
  'settings.donate': 'Support', 'settings.donateDesc': 'If dsh-remote has been helpful to you, feel free to support development with a coffee ☕', 'settings.donateThanks': 'Thanks for your support ☕',
1105
1150
  'settings.back': '‹ Back',
1106
1151
  'settings.servers': 'Servers (one or more)', 'settings.speedTest': 'Test', 'settings.add': 'Add',
1107
- 'settings.scanTitle': 'Scan to pair', 'settings.scanDesc': 'Photo/gallery QR decoding on device (no Google services); system camera or manual paste also work', 'settings.scan': 'Scan',
1152
+ 'settings.scanTitle': 'Scan to pair', 'settings.scanDesc': 'Use the live camera or photo/gallery QR decoding; no Google services required', 'settings.scan': 'Scan',
1108
1153
  'settings.notifyTitle': 'Notifications', 'settings.notifyDesc': 'Push on approvals / questions', 'settings.announcementHistoryTitle': 'Announcement history', 'settings.announcementHistoryDesc': 'Review announcements you received before', 'settings.announcementHistory': 'View history',
1109
1154
  'settings.testNotifyTitle': 'Test notification', 'settings.testNotifyDesc': 'Schedule a local notification in 3 seconds to verify the notification pipeline', 'settings.testNotify': 'Send test notification',
1110
1155
  'settings.testNotifySent': 'Test notification scheduled, arriving in 3s', 'settings.testNotifyFailed': 'Failed to send test notification: {msg}', 'settings.testNotifyUnavailable': 'Test notification is only available in the app',
package/public/md.js CHANGED
@@ -2,7 +2,7 @@
2
2
  * 用法: mdToHtml(text) -> HTML 字符串
3
3
  * 安全: 先 HTML 转义再转标记; 链接仅允许 http/https, 其余保留为纯文本。
4
4
  * 支持: 代码块 / 行内代码 / #~### 标题 / **粗体** / *斜体* / - 无序列表 /
5
- * 1. 有序列表 / > 引用 / [text](url) 链接 / 换行。
5
+ * 1. 有序列表 / > 引用 / GFM 表格 / [text](url) 链接 / 换行。
6
6
  * 同时支持浏览器全局 window.mdToHtml 与 Node CommonJS module.exports。
7
7
  */
8
8
  (function (root, factory) {
@@ -35,6 +35,70 @@
35
35
  return s
36
36
  }
37
37
 
38
+ function tableCells(line) {
39
+ var s = String(line || '').trim()
40
+ if (s.charAt(0) === '|') s = s.slice(1)
41
+ if (s.charAt(s.length - 1) === '|') s = s.slice(0, -1)
42
+ var cells = []
43
+ var cell = ''
44
+ var escaped = false
45
+ for (var i = 0; i < s.length; i++) {
46
+ var c = s.charAt(i)
47
+ if (escaped) { cell += c; escaped = false; continue }
48
+ if (c === '\\') { escaped = true; continue }
49
+ if (c === '|') { cells.push(cell.trim()); cell = ''; continue }
50
+ cell += c
51
+ }
52
+ if (escaped) cell += '\\'
53
+ cells.push(cell.trim())
54
+ return cells
55
+ }
56
+
57
+ function tableAlignment(cell) {
58
+ var s = String(cell || '').trim()
59
+ if (!/^:?-{3,}:?$/.test(s)) return null
60
+ if (s.charAt(0) === ':' && s.charAt(s.length - 1) === ':') return 'center'
61
+ if (s.charAt(0) === ':') return 'left'
62
+ if (s.charAt(s.length - 1) === ':') return 'right'
63
+ return null
64
+ }
65
+
66
+ function tableAt(lines, start) {
67
+ if (start + 1 >= lines.length || !/\|/.test(lines[start]) || !/\|/.test(lines[start + 1])) return null
68
+ var headers = tableCells(lines[start])
69
+ var separators = tableCells(lines[start + 1])
70
+ if (headers.length < 2 || separators.length !== headers.length) return null
71
+ if (!separators.every(function (cell) { return /^:?-{3,}:?$/.test(String(cell || '').trim()) })) return null
72
+ var alignments = separators.map(tableAlignment)
73
+ var rows = []
74
+ var i = start + 2
75
+ while (i < lines.length && lines[i].trim() && /\|/.test(lines[i])) {
76
+ var cells = tableCells(lines[i])
77
+ if (cells.length !== headers.length) break
78
+ rows.push(cells)
79
+ i++
80
+ }
81
+ return { headers: headers, alignments: alignments, rows: rows, next: i }
82
+ }
83
+
84
+ function tableHtml(table) {
85
+ function cellTag(tag, value, align) {
86
+ var style = align ? ' style="text-align:' + align + '"' : ''
87
+ return '<' + tag + style + '>' + inline(value) + '</' + tag + '>'
88
+ }
89
+ var head = '<thead><tr>' + table.headers.map(function (value, i) {
90
+ return cellTag('th', value, table.alignments[i])
91
+ }).join('') + '</tr></thead>'
92
+ var body = table.rows.length
93
+ ? '<tbody>' + table.rows.map(function (row) {
94
+ return '<tr>' + row.map(function (value, i) {
95
+ return cellTag('td', value, table.alignments[i])
96
+ }).join('') + '</tr>'
97
+ }).join('') + '</tbody>'
98
+ : ''
99
+ return '<div class="md-table-wrap"><table>' + head + body + '</table></div>'
100
+ }
101
+
38
102
  function renderLines(lines) {
39
103
  var html = ''
40
104
  var i = 0
@@ -49,6 +113,12 @@
49
113
  i++
50
114
  continue
51
115
  }
116
+ var table = tableAt(lines, i)
117
+ if (table) {
118
+ html += tableHtml(table)
119
+ i = table.next
120
+ continue
121
+ }
52
122
  var q = line.match(/^&gt;\s?(.*)$/)
53
123
  if (q) {
54
124
  html += '<blockquote>' + inline(q[1]) + '</blockquote>'
package/public/styles.css CHANGED
@@ -3,6 +3,12 @@
3
3
 
4
4
  @import 'theme-vars.css';
5
5
 
6
+ /* 思考流与兼容档位提示 */
7
+ .effort-hint { flex-basis: 100%; color: var(--dsr-muted); font-size: 11px; line-height: 1.45; }
8
+ .msg.reasoning-live { border-color: var(--dsr-accent-line); box-shadow: 0 0 0 1px var(--dsr-accent-soft) inset; }
9
+ .msg.reasoning-live summary::after { content: ''; display: inline-block; width: 6px; height: 6px; margin-left: 7px; border-radius: 50%; background: var(--dsr-accent); animation: reasoning-pulse 1.2s ease-in-out infinite; }
10
+ @keyframes reasoning-pulse { 0%, 100% { opacity: .35; transform: scale(.8); } 50% { opacity: 1; transform: scale(1); } }
11
+
6
12
 
7
13
  /* ============ 基础 ============ */
8
14
  * { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
@@ -323,6 +329,23 @@ body.in-session .view {
323
329
  .card-row { display: flex; justify-content: space-between; gap: 10px; padding: 3px 0; font-size: 13px; }
324
330
  .card-row .k { color: var(--dsr-muted); }
325
331
  .card-row .v { text-align: right; word-break: break-all; }
332
+ .subagent-card { padding: 0; overflow: hidden; }
333
+ .subagent-toggle { width: 100%; display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 10px 12px; border: 0; background: transparent; color: inherit; text-align: left; cursor: pointer; }
334
+ .subagent-toggle .card-title { margin: 0; }
335
+ .subagent-toggle-icon { color: var(--dsr-muted); font-size: 16px; line-height: 1; }
336
+ .subagent-list { padding: 0 12px 10px; border-top: 1px solid var(--dsr-line); }
337
+ .queue-dock { margin: 0 0 10px; border: 1px solid var(--dsr-line); border-radius: 12px; background: var(--dsr-surface); overflow: hidden; }
338
+ .queue-dock-head { display: flex; align-items: center; gap: 8px; padding: 9px 12px; color: var(--dsr-muted); font-size: 12px; font-weight: 700; border-bottom: 1px solid var(--dsr-line); }
339
+ .queue-dock-list { max-height: 220px; overflow-y: auto; }
340
+ .queue-dock-item { display: flex; align-items: center; gap: 8px; padding: 8px 10px 8px 12px; border-bottom: 1px solid var(--dsr-line); }
341
+ .queue-dock-item:last-child { border-bottom: 0; }
342
+ .queue-dock-preview { flex: 1; min-width: 0; color: var(--dsr-text); font-size: 13px; line-height: 1.4; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
343
+ .queue-dock-action { flex: 0 0 auto; }
344
+ .md-table-wrap { max-width: 100%; overflow-x: auto; margin: 8px 0; }
345
+ .md-table-wrap table { width: max-content; min-width: 100%; border-collapse: collapse; font-size: .94em; }
346
+ .md-table-wrap th, .md-table-wrap td { padding: 6px 9px; border: 1px solid var(--dsr-line); text-align: left; white-space: nowrap; }
347
+ .md-table-wrap th { background: var(--dsr-bg-2); font-weight: 700; }
348
+ .md-table-wrap tbody tr:nth-child(even) { background: color-mix(in srgb, var(--dsr-bg-2) 45%, transparent); }
326
349
  .goal-obj { font-size: 13px; line-height: 1.55; color: var(--dsr-text); }
327
350
  .goal-phase { font-size: 12px; color: var(--dsr-info); margin-top: 3px; }
328
351
  .goal-actions { display: flex; gap: 8px; margin-top: 9px; flex-wrap: wrap; }
@@ -492,6 +515,16 @@ body.composer-fullscreen { overflow: hidden; }
492
515
  background: var(--dsr-accent); color: var(--dsr-on-accent); font-weight: 700; font-size: 14px;
493
516
  }
494
517
  .send-btn:disabled { opacity: .45; }
518
+ .composer-status { display: flex; align-items: center; gap: 7px; padding: 0 3px; color: var(--dsr-accent-strong); font-size: 12px; font-weight: 700; }
519
+ .composer-status-dot { width: 7px; height: 7px; border-radius: 50%; background: currentColor; box-shadow: 0 0 0 0 currentColor; animation: dsr-running-pulse 1.3s ease-out infinite; }
520
+ @keyframes dsr-running-pulse { 0% { box-shadow: 0 0 0 0 currentColor; opacity: 1; } 70% { box-shadow: 0 0 0 6px transparent; opacity: .65; } 100% { box-shadow: 0 0 0 0 transparent; opacity: 1; } }
521
+ .scan-live-card { width: min(92vw, 420px); }
522
+ .scan-live-preview { position: relative; width: 100%; aspect-ratio: 1 / 1; overflow: hidden; border-radius: 14px; background: #05070d; }
523
+ .scan-live-preview video { display: block; width: 100%; height: 100%; object-fit: cover; }
524
+ .scan-live-frame { position: absolute; inset: 18%; border: 2px solid rgba(255,255,255,.9); border-radius: 18px; box-shadow: 0 0 0 999px rgba(0,0,0,.28); pointer-events: none; }
525
+ .scan-live-frame::after { content: ''; position: absolute; left: 8%; right: 8%; top: 8%; height: 2px; background: var(--dsr-accent-strong); box-shadow: 0 0 12px var(--dsr-accent-strong); animation: scan-live-line 1.8s ease-in-out infinite; }
526
+ @keyframes scan-live-line { 0%, 100% { transform: translateY(0); opacity: .55; } 50% { transform: translateY(220px); opacity: 1; } }
527
+ .scan-live-status { padding: 10px 2px 0; color: var(--dsr-muted); font-size: 13px; text-align: center; }
495
528
  .composer-menu {
496
529
  max-height: min(46vh, 340px); overflow-y: auto;
497
530
  padding: 10px 12px; display: flex; flex-direction: column; gap: 12px;
@@ -900,6 +933,23 @@ button.overview-attention-item, button.overview-session-item { cursor:pointer; }
900
933
  .feedback-success-card .modal-actions { justify-content: stretch; }
901
934
  .feedback-success-card .modal-actions .btn { flex: 1; min-height: 42px; }
902
935
 
936
+ .app-version-warning-card { max-width: 390px; text-align: center; padding: 22px 20px 18px; border-top: 3px solid var(--dsr-accent); }
937
+ .app-version-warning-icon {
938
+ width: 50px; height: 50px; display: grid; place-items: center; margin: 0 auto 12px; border-radius: 15px;
939
+ color: var(--dsr-accent-strong); background: var(--dsr-accent-soft); border: 1px solid var(--dsr-accent-line);
940
+ font-size: 28px; font-weight: 800; line-height: 1;
941
+ }
942
+ .app-version-warning-title { margin-bottom: 7px; font-size: 18px; }
943
+ .app-version-warning-body, .app-version-warning-hint { color: var(--dsr-muted); line-height: 1.6; }
944
+ .app-version-comparison { display: grid; grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr); align-items: center; gap: 10px; margin: 16px 0 12px; }
945
+ .app-version-comparison > div { display: grid; gap: 5px; min-width: 0; padding: 11px 8px; border: 1px solid var(--dsr-line); border-radius: 12px; background: var(--dsr-panel); }
946
+ .app-version-comparison span { color: var(--dsr-muted); font-size: 11px; }
947
+ .app-version-comparison strong { overflow-wrap: anywhere; color: var(--dsr-text); font-size: 14px; }
948
+ .app-version-comparison > i { color: var(--dsr-accent-strong); font-style: normal; font-weight: 800; }
949
+ .app-version-warning-hint { font-size: 12px; }
950
+ .app-version-warning-card .modal-actions { justify-content: stretch; }
951
+ .app-version-warning-card .modal-actions .btn { flex: 1; min-height: 42px; }
952
+
903
953
  /* ---------- 模态 ---------- */
904
954
  .modal {
905
955
  position: fixed; inset: 0; z-index: 40; display: grid; place-items: center;
@@ -0,0 +1,74 @@
1
+ /* DSH Remote Prompt 转写核心模块(dev 分支实验功能)
2
+ * 纯逻辑、零依赖:密钥掩码、固定整理提示词、状态码文案和 SSE 增量解析。
3
+ * 浏览器通过 window.TranscribeCore 使用,Node 测试通过 CommonJS 使用。
4
+ * 当前仅在 dev 分支保留,不接入稳定版主界面。
5
+ */
6
+ (function (root, factory) {
7
+ if (typeof module === 'object' && module.exports) module.exports = factory()
8
+ else root.TranscribeCore = factory()
9
+ })(typeof self !== 'undefined' ? self : this, function () {
10
+ 'use strict'
11
+
12
+ const TRANSCRIBE_SYSTEM_PROMPT = [
13
+ '你是文本整理助手。请把用户发来的原始文字改写成一条可直接使用的提示词,要求:',
14
+ '1. 分条分点:把内容按要点拆成编号列表,层次清晰;',
15
+ '2. 逻辑清晰:按「目标—背景—要求—输出」的顺序整理,删除冗余重复;',
16
+ '3. 修正语句与错别字:修正病句、错别字、标点与大小写问题;',
17
+ '4. 删除无意义口语语气词:去掉「那个、就是说、嗯、啊、然后」等口头禅;',
18
+ '5. 保留原意:不增删核心信息,不擅自补充额外要求;',
19
+ '6. 直接输出改写结果,不解释、不客套。'
20
+ ].join('\n')
21
+
22
+ function maskApiKey(key) {
23
+ const value = String(key || '')
24
+ if (!value) return ''
25
+ if (value.length <= 8) return '****' + value.slice(-4)
26
+ return value.slice(0, 4) + '****' + value.slice(-4)
27
+ }
28
+
29
+ function statusMessage(status) {
30
+ if (status === 400) return '请求参数错误:请检查 API 地址、模型名或输入内容(400)'
31
+ if (status === 401 || status === 403) return '认证失败:API 密钥无效或无权限(' + status + ')'
32
+ if (status === 404) return '接口不存在:请检查 API 地址是否以 /v1 结尾(404)'
33
+ if (status === 429) return '请求过于频繁或额度不足(429)'
34
+ if (status >= 500) return '服务端错误(' + status + ')'
35
+ return '请求失败(HTTP ' + status + ')'
36
+ }
37
+
38
+ function parseSseData(line) {
39
+ const data = String(line || '').trim().replace(/^data:\s*/, '').trim()
40
+ if (data === '[DONE]') return { type: 'done' }
41
+ let value
42
+ try { value = JSON.parse(data) } catch { return { type: 'skip' } }
43
+ if (value.error) return { type: 'error', error: String(value.error.message || statusMessage(Number(value.error.code) || 0)) }
44
+ const piece = value.choices?.[0]?.delta?.content
45
+ return typeof piece === 'string' ? { type: 'delta', text: piece } : { type: 'skip' }
46
+ }
47
+
48
+ async function consumeSse(reader, decoder, onDelta, options = {}) {
49
+ let buffer = ''
50
+ let full = ''
51
+ while (true) {
52
+ const { done, value } = await reader.read()
53
+ if (done) break
54
+ buffer += decoder.decode(value, { stream: true })
55
+ options.onChunk?.()
56
+ let nl
57
+ while ((nl = buffer.indexOf('\n')) !== -1) {
58
+ const line = buffer.slice(0, nl).trim()
59
+ buffer = buffer.slice(nl + 1)
60
+ if (!line.startsWith('data:')) continue
61
+ const parsed = parseSseData(line)
62
+ if (parsed.type === 'done') { await reader.cancel?.(); return full }
63
+ if (parsed.type === 'error') throw new Error(parsed.error)
64
+ if (parsed.type === 'delta') {
65
+ full += parsed.text
66
+ onDelta?.(parsed.text)
67
+ }
68
+ }
69
+ }
70
+ return full
71
+ }
72
+
73
+ return { TRANSCRIBE_SYSTEM_PROMPT, maskApiKey, statusMessage, parseSseData, consumeSse }
74
+ })
@@ -1,10 +1,18 @@
1
1
  {
2
- "version": "0.6.12",
2
+ "version": "0.6.14",
3
3
  "apkUrl": "dsh-remote.apk",
4
- "sha256": "99211eee40cd121fce38fb3a9a3b85a5fcfde28546bccdac246574b38d6adb3f",
5
- "releasedAt": "2026-08-23T14:05:30.412Z",
6
- "notes": "0.6.12:修复远程启动和重启 DSH 时统一报 HTTP 502 且无法确认结果的问题;改为异步追踪 systemd 服务检查、命令提交、进程启动、DSH HTTP 恢复和 mux/host 实时通道重连阶段,成功时显示 PID、HTTP 状态和用时,失败时区分服务不存在、systemd 不可用、权限不足、命令失败、服务失败、启动超时、HTTP 恢复超时和实时通道恢复超时;优化工作区会话筛选、文件预览和应用内选择器;新增中央投票公告与反馈成功确认;主页公告栏常驻并在无未读公告时显示空状态,同时修正设置页异常右箭头、主页刷新图标居中和周末谷时提醒。",
4
+ "sha256": "4adbea85b1cf3f1768609a14c70adf3876033d892f7fab3dfa0d7f40959e643c",
5
+ "releasedAt": "2026-08-25T06:48:09.119Z",
6
+ "notes": "0.6.14-rc.7:将 Android 后台轮询服务接入与前台 App 相同的 client ID,修复 Dalvik 后台请求被显示为第二台未知设备;保留旧记录的顺序无关迁移;补齐文件上传请求的持久设备 ID;实时扫码继续使用 Worker 解码并降低取样负载;修复非用户来源的 DSH user/message 被误显示为用户输入;新增子代理折叠、排队消息插话、运行中提示和按轮次稳定排序;Markdown 支持 GFM 表格。",
7
7
  "history": [
8
+ {
9
+ "version": "0.6.14",
10
+ "notes": "0.6.14-rc.7:将 Android 后台轮询服务接入与前台 App 相同的 client ID,修复 Dalvik 后台请求被显示为第二台未知设备;保留旧记录的顺序无关迁移;补齐文件上传请求的持久设备 ID;实时扫码继续使用 Worker 解码并降低取样负载;修复非用户来源的 DSH user/message 被误显示为用户输入;新增子代理折叠、排队消息插话、运行中提示和按轮次稳定排序;Markdown 支持 GFM 表格。"
11
+ },
12
+ {
13
+ "version": "0.6.13",
14
+ "notes": "0.6.13 正式版:新增首次连接 Doctor,集中检查 DSH 服务、远程网关、局域网地址、防火墙、终端配对和实时消息通道;网关控制台新增可选的独立设备密钥,支持设备备注、最近 IP、二维码、令牌轮换、复制和退出,共享令牌在启用后仅保留管理权限;手机端和桌面端支持实时思考内容,并为未声明推理档位的模型提供 low、high、max 三档选择;普通会话列表、工作区树和主页统计不再混入子代理内部会话;Android App 落后于网关版本时显示明确更新提醒;新增网关协议与能力协商并兼容旧网关,同时补充设备隔离、重启持久化、推理显示、会话过滤和版本差异回归测试。"
15
+ },
8
16
  {
9
17
  "version": "0.6.12",
10
18
  "notes": "0.6.12:修复远程启动和重启 DSH 时统一报 HTTP 502 且无法确认结果的问题;改为异步追踪 systemd 服务检查、命令提交、进程启动、DSH HTTP 恢复和 mux/host 实时通道重连阶段,成功时显示 PID、HTTP 状态和用时,失败时区分服务不存在、systemd 不可用、权限不足、命令失败、服务失败、启动超时、HTTP 恢复超时和实时通道恢复超时;优化工作区会话筛选、文件预览和应用内选择器;新增中央投票公告与反馈成功确认;主页公告栏常驻并在无未读公告时显示空状态,同时修正设置页异常右箭头、主页刷新图标居中和周末谷时提醒。"
@@ -36,14 +44,6 @@
36
44
  {
37
45
  "version": "0.6.5",
38
46
  "notes": "连接稳定性全面优化:25 秒心跳保活(WiFi 切换 / NAT 超时不再假活);60 秒无消息自动重连,指数退避重连(1s → 30s 上限 + 抖动);网关死连接自动清理(60 秒无数据双向销毁);断网自动检测与恢复(离线 / 重连中倒计时 / 连接失败状态可见);降级轮询恢复优化。"
39
- },
40
- {
41
- "version": "0.6.4",
42
- "notes": "扫码配对改为显式双按钮(拍照/相册),修复小米等 ROM 选择器错乱;会话历史加载超时放宽至 45 秒,失败可重试;网关端口自定义(插件管理页可改,环境变量仍优先)+ 启动前端口占用检测;峰谷计费提醒改前台服务驱动,后台按时必达;消息 Markdown 渲染(标题/代码块/列表/链接);修复 DSH 重启后网关上游端口不刷新(Issue #1);新增测试通知按钮。"
43
- },
44
- {
45
- "version": "0.6.3",
46
- "notes": "修复 DSH 重启后网关 upstream 端口不刷新(Issue #1):/health 增加 upstream/upstreamOk/pid 探测;插件 ensureGateway 检测上游变化或不可达时自动杀旧网关并按新 DSH_UPSTREAM 重启;启动/刷新写插件日志与 PID 文件。"
47
47
  }
48
48
  ]
49
49
  }
@@ -1,3 +1,3 @@
1
1
  {
2
- "version": "0.6.12"
2
+ "version": "0.6.14"
3
3
  }