codexmate 0.0.55 → 0.0.56
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 +2 -0
- package/README.vi.md +2 -0
- package/README.zh.md +2 -0
- package/cli/local-bridge.js +221 -22
- package/cli.js +117 -5
- package/package.json +1 -1
- package/web-ui/app.js +2 -0
- package/web-ui/modules/app.computed.session.mjs +210 -0
- package/web-ui/modules/app.methods.claude-config.mjs +128 -12
- package/web-ui/modules/app.methods.codex-config.mjs +294 -65
- package/web-ui/modules/app.methods.navigation.mjs +4 -1
- package/web-ui/modules/app.methods.providers.mjs +15 -1
- package/web-ui/modules/app.methods.runtime.mjs +7 -2
- package/web-ui/modules/app.methods.session-actions.mjs +24 -0
- package/web-ui/modules/app.methods.startup-claude.mjs +34 -1
- package/web-ui/modules/i18n/locales/en.mjs +41 -2
- package/web-ui/modules/i18n/locales/ja.mjs +41 -2
- package/web-ui/modules/i18n/locales/vi.mjs +41 -8
- package/web-ui/modules/i18n/locales/zh-tw.mjs +41 -2
- package/web-ui/modules/i18n/locales/zh.mjs +41 -2
- package/web-ui/modules/provider-default-names.mjs +25 -0
- package/web-ui/partials/index/modal-health-check.html +69 -5
- package/web-ui/partials/index/panel-config-codex.html +2 -2
- package/web-ui/partials/index/panel-sessions.html +97 -17
- package/web-ui/res/web-ui-render.precompiled.js +345 -93
- package/web-ui/session-helpers.mjs +4 -1
- package/web-ui/styles/responsive.css +98 -0
- package/web-ui/styles/sessions-preview.css +212 -2
- package/web-ui/styles/sessions-toolbar-trash.css +61 -18
- package/web-ui/styles/skills-list.css +122 -0
- package/web-ui/styles/titles-cards.css +52 -0
|
@@ -438,6 +438,8 @@ const ja = Object.freeze({
|
|
|
438
438
|
'toast.apply.fail': '設定の適用に失敗しました',
|
|
439
439
|
'toast.check.success': 'チェック成功',
|
|
440
440
|
'toast.check.fail': 'チェック失敗',
|
|
441
|
+
'toast.health.noFailedProviderSelection': '先に失敗プロバイダーを選択してください',
|
|
442
|
+
'toast.health.deleteFailedProvidersDone': '失敗プロバイダー {count} 件を削除しました',
|
|
441
443
|
'toast.noChanges': '変更が検出されませんでした',
|
|
442
444
|
'toast.template.loadFail': 'テンプレートの読み込みに失敗しました',
|
|
443
445
|
'toast.template.empty': 'テンプレートは空にできません',
|
|
@@ -649,6 +651,32 @@ const ja = Object.freeze({
|
|
|
649
651
|
'sessions.preview.rerender': '再レンダリング',
|
|
650
652
|
'sessions.preview.preparing': '準備中...',
|
|
651
653
|
'sessions.preview.clipped': 'プレビューは一部のみ表示されています',
|
|
654
|
+
'sessions.workspace.kicker': '作業メモリ',
|
|
655
|
+
'sessions.workspace.title': 'セッション再利用サマリー',
|
|
656
|
+
'sessions.workspace.subtitle': '読み込まれた {count} 件のメッセージから手掛かり、コマンド、ファイル、リンク、リスクを抽出しました。',
|
|
657
|
+
'sessions.workspace.copy': 'ブリーフをコピー',
|
|
658
|
+
'sessions.workspace.copy.title': 'セッション作業ブリーフ',
|
|
659
|
+
'sessions.workspace.copy.source': 'ソース',
|
|
660
|
+
'sessions.workspace.copy.path': 'パス',
|
|
661
|
+
'sessions.workspace.copy.empty': 'コピーできるセッション要約はまだありません',
|
|
662
|
+
'sessions.workspace.copy.success': '作業ブリーフをコピーしました',
|
|
663
|
+
'sessions.workspace.metric.messages': 'メッセージ',
|
|
664
|
+
'sessions.workspace.metric.user': 'ユーザー',
|
|
665
|
+
'sessions.workspace.metric.assistant': 'アシスタント',
|
|
666
|
+
'sessions.workspace.metric.commands': 'コマンド',
|
|
667
|
+
'sessions.workspace.metric.artifacts': '成果物',
|
|
668
|
+
'sessions.workspace.metric.risks': 'リスク / TODO',
|
|
669
|
+
'sessions.workspace.signals': 'タスクの手掛かり',
|
|
670
|
+
'sessions.workspace.commands': '再利用可能なコマンド',
|
|
671
|
+
'sessions.workspace.files': '関連ファイル',
|
|
672
|
+
'sessions.workspace.links': '関連リンク',
|
|
673
|
+
'sessions.workspace.risks': 'リスクとTODO',
|
|
674
|
+
'sessions.workspace.nextSteps': '次のステップ',
|
|
675
|
+
'sessions.workspace.empty': '明確な手掛かりはまだありません。下の元メッセージを確認してください。',
|
|
676
|
+
'sessions.workspace.noneCommands': 'コマンドの手掛かりはありません',
|
|
677
|
+
'sessions.workspace.noneFiles': 'ファイルの手掛かりはありません',
|
|
678
|
+
'sessions.workspace.noneLinks': 'リンクの手掛かりはありません',
|
|
679
|
+
'sessions.workspace.noneRisks': 'リスクやTODOは検出されていません',
|
|
652
680
|
'sessions.preview.shownCount': '{shown} / {total} 件表示',
|
|
653
681
|
'sessions.preview.loadMore': 'さらに読み込む',
|
|
654
682
|
'sessions.preview.loadingMore': 'さらに読み込み中...',
|
|
@@ -884,7 +912,18 @@ const ja = Object.freeze({
|
|
|
884
912
|
'config.health.title': '設定ヘルスチェック',
|
|
885
913
|
'config.health.run': 'チェック実行',
|
|
886
914
|
'config.health.running': 'チェック中...',
|
|
887
|
-
'config.health.hint': '
|
|
915
|
+
'config.health.hint': '現在選択中のプロバイダーのみをチェックします。未選択のプロバイダーのリモート異常は今回のエラーに含めません。',
|
|
916
|
+
'config.health.codexHint': '現在の Codex ルートプロバイダー、endpoint、認証、モデルをローカルブリッジ経由でチェックします。',
|
|
917
|
+
'config.health.providersSummary': 'プロバイダー: 合計 {total} · 正常 {green} · 設定問題 {yellow} · 失敗 {red}',
|
|
918
|
+
'config.health.failedProviders.title': '削除対象の失敗プロバイダー',
|
|
919
|
+
'config.health.failedProviders.selectAria': '失敗プロバイダー {name} を削除対象に選択',
|
|
920
|
+
'config.health.failedProviders.clearSelection': '選択解除',
|
|
921
|
+
'config.health.failedProviders.selectAll': 'すべて選択',
|
|
922
|
+
'config.health.failedProviders.selectAllAria': '削除可能な失敗プロバイダーをすべて選択',
|
|
923
|
+
'config.health.failedProviders.notDeletable': '予約済み/システムプロバイダーのため、ここでは削除できません。',
|
|
924
|
+
'config.health.failedProviders.writeRequired': '削除するには、このタブのプロバイダー書き込みを有効化してください。',
|
|
925
|
+
'config.health.failedProviders.deleteSelected': '選択した設定を削除',
|
|
926
|
+
'config.health.failedProviders.deleting': '削除中...',
|
|
888
927
|
'config.health.progress': '完了 {done}/{total} · 失敗 {failed}',
|
|
889
928
|
'config.health.ok': 'チェック通過',
|
|
890
929
|
'config.health.fail': 'チェック失敗',
|
|
@@ -1291,7 +1330,7 @@ const ja = Object.freeze({
|
|
|
1291
1330
|
'claude.health.title': '設定ヘルスチェック',
|
|
1292
1331
|
'claude.health.run': 'チェック実行',
|
|
1293
1332
|
'claude.health.running': 'チェック中...',
|
|
1294
|
-
'claude.health.hint': '
|
|
1333
|
+
'claude.health.hint': '現在選択中の Claude 設定のみをチェックします。未選択の設定のリモート異常は今回のエラーに含めません。',
|
|
1295
1334
|
'claude.health.progress': '完了 {done}/{total} · 失敗 {failed}',
|
|
1296
1335
|
'claude.md.title': 'CLAUDE.md',
|
|
1297
1336
|
'claude.md.open': 'CLAUDE.md を開く',
|
|
@@ -402,6 +402,8 @@ const vi = Object.freeze({
|
|
|
402
402
|
'toast.apply.fail': 'Áp dụng cấu hình thất bại',
|
|
403
403
|
'toast.check.success': 'Kiểm tra thành công',
|
|
404
404
|
'toast.check.fail': 'Kiểm tra thất bại',
|
|
405
|
+
'toast.health.noFailedProviderSelection': 'Hãy chọn ít nhất một provider lỗi trước',
|
|
406
|
+
'toast.health.deleteFailedProvidersDone': 'Đã xóa {count} provider lỗi',
|
|
405
407
|
'toast.noChanges': 'Không phát hiện thay đổi',
|
|
406
408
|
'toast.template.loadFail': 'Tải mẫu thất bại',
|
|
407
409
|
'toast.template.empty': 'Mẫu không được để trống',
|
|
@@ -857,6 +859,32 @@ const vi = Object.freeze({
|
|
|
857
859
|
'sessions.preview.rerender': 'Render lại',
|
|
858
860
|
'sessions.preview.preparing': 'Đang chuẩn bị nội dung phiên...',
|
|
859
861
|
'sessions.preview.clipped': 'Chỉ hiển thị {count} tin nhắn mới nhất.',
|
|
862
|
+
'sessions.workspace.kicker': 'Bộ nhớ công việc',
|
|
863
|
+
'sessions.workspace.title': 'Tóm tắt tái sử dụng phiên',
|
|
864
|
+
'sessions.workspace.subtitle': 'Trích xuất tín hiệu, lệnh, tệp, liên kết và rủi ro từ {count} tin nhắn đã tải.',
|
|
865
|
+
'sessions.workspace.copy': 'Sao chép tóm tắt',
|
|
866
|
+
'sessions.workspace.copy.title': 'Tóm tắt không gian làm việc phiên',
|
|
867
|
+
'sessions.workspace.copy.source': 'Nguồn',
|
|
868
|
+
'sessions.workspace.copy.path': 'Đường dẫn',
|
|
869
|
+
'sessions.workspace.copy.empty': 'Chưa có tóm tắt phiên để sao chép',
|
|
870
|
+
'sessions.workspace.copy.success': 'Đã sao chép tóm tắt',
|
|
871
|
+
'sessions.workspace.metric.messages': 'Tin nhắn',
|
|
872
|
+
'sessions.workspace.metric.user': 'Người dùng',
|
|
873
|
+
'sessions.workspace.metric.assistant': 'Trợ lý',
|
|
874
|
+
'sessions.workspace.metric.commands': 'Lệnh',
|
|
875
|
+
'sessions.workspace.metric.artifacts': 'Tài liệu',
|
|
876
|
+
'sessions.workspace.metric.risks': 'Rủi ro / todo',
|
|
877
|
+
'sessions.workspace.signals': 'Tín hiệu tác vụ',
|
|
878
|
+
'sessions.workspace.commands': 'Lệnh có thể tái sử dụng',
|
|
879
|
+
'sessions.workspace.files': 'Tệp liên quan',
|
|
880
|
+
'sessions.workspace.links': 'Liên kết liên quan',
|
|
881
|
+
'sessions.workspace.risks': 'Rủi ro và todo',
|
|
882
|
+
'sessions.workspace.nextSteps': 'Bước tiếp theo',
|
|
883
|
+
'sessions.workspace.empty': 'Chưa có tín hiệu rõ ràng. Hãy xem tin nhắn gốc bên dưới.',
|
|
884
|
+
'sessions.workspace.noneCommands': 'Chưa có tín hiệu lệnh',
|
|
885
|
+
'sessions.workspace.noneFiles': 'Chưa có tín hiệu tệp',
|
|
886
|
+
'sessions.workspace.noneLinks': 'Chưa có tín hiệu liên kết',
|
|
887
|
+
'sessions.workspace.noneRisks': 'Chưa phát hiện rủi ro hoặc todo',
|
|
860
888
|
'sessions.preview.shownCount': 'Hiển thị {shown} / {total}',
|
|
861
889
|
'sessions.preview.loadMore': 'Tải thêm (còn lại {remain})',
|
|
862
890
|
'sessions.preview.loadingMore': 'Đang tải tin nhắn cũ hơn...',
|
|
@@ -1029,7 +1057,18 @@ const vi = Object.freeze({
|
|
|
1029
1057
|
'config.health.title': 'Kiểm tra sức khỏe cấu hình',
|
|
1030
1058
|
'config.health.run': 'Chạy kiểm tra',
|
|
1031
1059
|
'config.health.running': 'Đang kiểm tra...',
|
|
1032
|
-
'config.health.hint': '
|
|
1060
|
+
'config.health.hint': 'Chỉ kiểm tra nhà cung cấp đang chọn; lỗi từ nhà cung cấp chưa chọn không tính vào lần kiểm tra này.',
|
|
1061
|
+
'config.health.codexHint': 'Kiểm tra provider route Codex hiện tại, endpoint, xác thực và model qua local bridge.',
|
|
1062
|
+
'config.health.providersSummary': 'Provider: tổng {total} · OK {green} · lỗi cấu hình {yellow} · thất bại {red}',
|
|
1063
|
+
'config.health.failedProviders.title': 'Provider lỗi có thể xóa',
|
|
1064
|
+
'config.health.failedProviders.selectAria': 'Chọn provider lỗi {name} để xóa',
|
|
1065
|
+
'config.health.failedProviders.clearSelection': 'Bỏ chọn',
|
|
1066
|
+
'config.health.failedProviders.selectAll': 'Chọn tất cả',
|
|
1067
|
+
'config.health.failedProviders.selectAllAria': 'Chọn tất cả provider lỗi có thể xóa',
|
|
1068
|
+
'config.health.failedProviders.notDeletable': 'Provider hệ thống/được giữ lại; không thể xóa tại đây.',
|
|
1069
|
+
'config.health.failedProviders.writeRequired': 'Bật quyền ghi provider trong tab này trước khi xóa.',
|
|
1070
|
+
'config.health.failedProviders.deleteSelected': 'Xóa cấu hình đã chọn',
|
|
1071
|
+
'config.health.failedProviders.deleting': 'Đang xóa...',
|
|
1033
1072
|
'config.health.progress': '{done}/{total} xong · {failed} thất bại',
|
|
1034
1073
|
'config.health.ok': 'Đạt',
|
|
1035
1074
|
'config.health.fail': 'Thất bại',
|
|
@@ -1180,10 +1219,6 @@ const vi = Object.freeze({
|
|
|
1180
1219
|
'settings.tab.data': 'Dữ liệu',
|
|
1181
1220
|
'settings.tabs.aria': 'Danh mục cài đặt',
|
|
1182
1221
|
'settings.quickSettings.title': 'Cài đặt nhanh',
|
|
1183
|
-
'settings.sharePrefix.title': 'Tiền tố lệnh chia sẻ',
|
|
1184
|
-
'settings.sharePrefix.meta': 'Dùng làm tiền tố cho "Sao chép lệnh chia sẻ" trong Web UI',
|
|
1185
|
-
'settings.sharePrefix.label': 'Tiền tố',
|
|
1186
|
-
'settings.sharePrefix.hint': 'Mặc định là npm start (local). Bạn có thể chuyển sang codexmate toàn cục. Cài đặt này được lưu trong trình duyệt.',
|
|
1187
1222
|
'settings.claude.title': 'Cấu hình Claude',
|
|
1188
1223
|
'settings.claude.meta': 'Sao lưu / nhập ~/.claude',
|
|
1189
1224
|
'settings.codex.title': 'Cấu hình Codex',
|
|
@@ -1219,8 +1254,6 @@ const vi = Object.freeze({
|
|
|
1219
1254
|
'settings.trash.retentionMeta': 'Mục trong thùng rác cũ hơn số ngày lưu giữ sẽ tự động bị xóa',
|
|
1220
1255
|
'settings.trash.retentionLabel': 'Số ngày lưu giữ',
|
|
1221
1256
|
'settings.trash.retentionHint': 'Phạm vi 1-365 ngày, mặc định 30. Mục hết hạn bị xóa khi tải thùng rác.',
|
|
1222
|
-
'settings.trashConfig.title': 'Cấu hình thùng rác',
|
|
1223
|
-
'settings.trashConfig.meta': 'Bật/tắt thùng rác và số ngày tự động dọn dẹp',
|
|
1224
1257
|
'settings.templateConfirm.title': 'Xác nhận áp dụng template',
|
|
1225
1258
|
'settings.templateConfirm.meta': 'Giảm ghi nhầm',
|
|
1226
1259
|
'settings.templateConfirm.toggle': 'Xem trước diff trước khi áp dụng (Xác nhận → Áp dụng)',
|
|
@@ -1284,7 +1317,7 @@ const vi = Object.freeze({
|
|
|
1284
1317
|
'claude.health.title': 'Kiểm tra sức khỏe cấu hình',
|
|
1285
1318
|
'claude.health.run': 'Chạy kiểm tra',
|
|
1286
1319
|
'claude.health.running': 'Đang kiểm tra...',
|
|
1287
|
-
'claude.health.hint': '
|
|
1320
|
+
'claude.health.hint': 'Chỉ kiểm tra cấu hình Claude đang chọn; lỗi từ cấu hình chưa chọn không tính vào lần kiểm tra này.',
|
|
1288
1321
|
'claude.health.progress': '{done}/{total} xong · {failed} thất bại',
|
|
1289
1322
|
'claude.md.title': 'CLAUDE.md',
|
|
1290
1323
|
'claude.md.open': 'Mở CLAUDE.md',
|
|
@@ -436,6 +436,8 @@ const zhTw = Object.freeze({
|
|
|
436
436
|
'toast.apply.fail': '應用設定失敗',
|
|
437
437
|
'toast.check.success': '檢查通過',
|
|
438
438
|
'toast.check.fail': '檢查失敗',
|
|
439
|
+
'toast.health.noFailedProviderSelection': '請先選擇至少一個失敗提供商',
|
|
440
|
+
'toast.health.deleteFailedProvidersDone': '已刪除 {count} 個失敗提供商',
|
|
439
441
|
'toast.noChanges': '未檢測到改動',
|
|
440
442
|
'toast.template.loadFail': '載入模板失敗',
|
|
441
443
|
'toast.template.empty': '模板不能為空',
|
|
@@ -659,6 +661,32 @@ const zhTw = Object.freeze({
|
|
|
659
661
|
'sessions.preview.rerender': '重新渲染',
|
|
660
662
|
'sessions.preview.preparing': '正在準備會話內容...',
|
|
661
663
|
'sessions.preview.clipped': '僅展示最近 {count} 條消息。',
|
|
664
|
+
'sessions.workspace.kicker': '工作記憶',
|
|
665
|
+
'sessions.workspace.title': '會話利用摘要',
|
|
666
|
+
'sessions.workspace.subtitle': '從已載入的 {count} 條消息提取線索、命令、文件、連結與風險。',
|
|
667
|
+
'sessions.workspace.copy': '複製工作簡報',
|
|
668
|
+
'sessions.workspace.copy.title': '會話工作簡報',
|
|
669
|
+
'sessions.workspace.copy.source': '來源',
|
|
670
|
+
'sessions.workspace.copy.path': '路徑',
|
|
671
|
+
'sessions.workspace.copy.empty': '暫無可複製的會話摘要',
|
|
672
|
+
'sessions.workspace.copy.success': '已複製工作簡報',
|
|
673
|
+
'sessions.workspace.metric.messages': '消息',
|
|
674
|
+
'sessions.workspace.metric.user': '使用者',
|
|
675
|
+
'sessions.workspace.metric.assistant': '助手',
|
|
676
|
+
'sessions.workspace.metric.commands': '命令',
|
|
677
|
+
'sessions.workspace.metric.artifacts': '材料',
|
|
678
|
+
'sessions.workspace.metric.risks': '風險/待辦',
|
|
679
|
+
'sessions.workspace.signals': '任務線索',
|
|
680
|
+
'sessions.workspace.commands': '可複用命令',
|
|
681
|
+
'sessions.workspace.files': '相關文件',
|
|
682
|
+
'sessions.workspace.links': '相關連結',
|
|
683
|
+
'sessions.workspace.risks': '風險與待辦',
|
|
684
|
+
'sessions.workspace.nextSteps': '下一步',
|
|
685
|
+
'sessions.workspace.empty': '暫無明顯線索,可從下方原始記錄繼續查看。',
|
|
686
|
+
'sessions.workspace.noneCommands': '暫無命令線索',
|
|
687
|
+
'sessions.workspace.noneFiles': '暫無文件線索',
|
|
688
|
+
'sessions.workspace.noneLinks': '暫無連結線索',
|
|
689
|
+
'sessions.workspace.noneRisks': '暫未識別到風險或待辦',
|
|
662
690
|
'sessions.preview.shownCount': '已顯示 {shown} / {total} 條',
|
|
663
691
|
'sessions.preview.loadMore': '載入更多(剩餘 {remain})',
|
|
664
692
|
'sessions.preview.loadingMore': '正在載入更早消息...',
|
|
@@ -894,7 +922,18 @@ const zhTw = Object.freeze({
|
|
|
894
922
|
'config.health.title': '設定健康檢查',
|
|
895
923
|
'config.health.run': '運行檢查',
|
|
896
924
|
'config.health.running': '檢查中...',
|
|
897
|
-
'config.health.hint': '
|
|
925
|
+
'config.health.hint': '僅探測目前選中的提供商;未選提供商的遠端異常不計入本次錯誤。',
|
|
926
|
+
'config.health.codexHint': '透過本機橋接探測目前 Codex 路由 provider、endpoint、鑑權與模型。',
|
|
927
|
+
'config.health.providersSummary': '提供商:共 {total} · 正常 {green} · 設定問題 {yellow} · 失敗 {red}',
|
|
928
|
+
'config.health.failedProviders.title': '待刪除的失敗提供商',
|
|
929
|
+
'config.health.failedProviders.selectAria': '選擇失敗提供商 {name} 進行刪除',
|
|
930
|
+
'config.health.failedProviders.clearSelection': '清空選擇',
|
|
931
|
+
'config.health.failedProviders.selectAll': '全選',
|
|
932
|
+
'config.health.failedProviders.selectAllAria': '全選可刪除的失敗提供商',
|
|
933
|
+
'config.health.failedProviders.notDeletable': '保留/系統提供商,不能在這裡刪除。',
|
|
934
|
+
'config.health.failedProviders.writeRequired': '刪除前請先打開本 tab 的提供商寫入開關。',
|
|
935
|
+
'config.health.failedProviders.deleteSelected': '移除所選設定',
|
|
936
|
+
'config.health.failedProviders.deleting': '刪除中...',
|
|
898
937
|
'config.health.progress': '已完成 {done}/{total} · 失敗 {failed}',
|
|
899
938
|
'config.health.ok': '檢查通過',
|
|
900
939
|
'config.health.fail': '檢查未通過',
|
|
@@ -1301,7 +1340,7 @@ const zhTw = Object.freeze({
|
|
|
1301
1340
|
'claude.health.title': '設定健康檢查',
|
|
1302
1341
|
'claude.health.run': '運行檢查',
|
|
1303
1342
|
'claude.health.running': '檢查中...',
|
|
1304
|
-
'claude.health.hint': '
|
|
1343
|
+
'claude.health.hint': '僅探測目前選中的 Claude 設定;未選設定的遠端異常不計入本次錯誤。',
|
|
1305
1344
|
'claude.health.progress': '已完成 {done}/{total} · 失敗 {failed}',
|
|
1306
1345
|
'claude.md.title': 'CLAUDE.md',
|
|
1307
1346
|
'claude.md.open': '打開 CLAUDE.md',
|
|
@@ -436,6 +436,8 @@ const zh = Object.freeze({
|
|
|
436
436
|
'toast.apply.fail': '应用配置失败',
|
|
437
437
|
'toast.check.success': '检查通过',
|
|
438
438
|
'toast.check.fail': '检查失败',
|
|
439
|
+
'toast.health.noFailedProviderSelection': '请先选择至少一个失败提供商',
|
|
440
|
+
'toast.health.deleteFailedProvidersDone': '已删除 {count} 个失败提供商',
|
|
439
441
|
'toast.noChanges': '未检测到改动',
|
|
440
442
|
'toast.template.loadFail': '加载模板失败',
|
|
441
443
|
'toast.template.empty': '模板不能为空',
|
|
@@ -659,6 +661,32 @@ const zh = Object.freeze({
|
|
|
659
661
|
'sessions.preview.rerender': '重新渲染',
|
|
660
662
|
'sessions.preview.preparing': '正在准备会话内容...',
|
|
661
663
|
'sessions.preview.clipped': '仅展示最近 {count} 条消息。',
|
|
664
|
+
'sessions.workspace.kicker': '工作记忆',
|
|
665
|
+
'sessions.workspace.title': '会话利用摘要',
|
|
666
|
+
'sessions.workspace.subtitle': '从已加载的 {count} 条消息提取线索、命令、文件、链接与风险。',
|
|
667
|
+
'sessions.workspace.copy': '复制工作简报',
|
|
668
|
+
'sessions.workspace.copy.title': '会话工作简报',
|
|
669
|
+
'sessions.workspace.copy.source': '来源',
|
|
670
|
+
'sessions.workspace.copy.path': '路径',
|
|
671
|
+
'sessions.workspace.copy.empty': '暂无可复制的会话摘要',
|
|
672
|
+
'sessions.workspace.copy.success': '已复制工作简报',
|
|
673
|
+
'sessions.workspace.metric.messages': '消息',
|
|
674
|
+
'sessions.workspace.metric.user': '用户',
|
|
675
|
+
'sessions.workspace.metric.assistant': '助手',
|
|
676
|
+
'sessions.workspace.metric.commands': '命令',
|
|
677
|
+
'sessions.workspace.metric.artifacts': '材料',
|
|
678
|
+
'sessions.workspace.metric.risks': '风险/待办',
|
|
679
|
+
'sessions.workspace.signals': '任务线索',
|
|
680
|
+
'sessions.workspace.commands': '可复用命令',
|
|
681
|
+
'sessions.workspace.files': '相关文件',
|
|
682
|
+
'sessions.workspace.links': '相关链接',
|
|
683
|
+
'sessions.workspace.risks': '风险与待办',
|
|
684
|
+
'sessions.workspace.nextSteps': '下一步',
|
|
685
|
+
'sessions.workspace.empty': '暂无明显线索,可从下方原始记录继续查看。',
|
|
686
|
+
'sessions.workspace.noneCommands': '暂无命令线索',
|
|
687
|
+
'sessions.workspace.noneFiles': '暂无文件线索',
|
|
688
|
+
'sessions.workspace.noneLinks': '暂无链接线索',
|
|
689
|
+
'sessions.workspace.noneRisks': '暂未识别到风险或待办',
|
|
662
690
|
'sessions.preview.shownCount': '已显示 {shown} / {total} 条',
|
|
663
691
|
'sessions.preview.loadMore': '加载更多(剩余 {remain})',
|
|
664
692
|
'sessions.preview.loadingMore': '正在加载更早消息...',
|
|
@@ -894,7 +922,18 @@ const zh = Object.freeze({
|
|
|
894
922
|
'config.health.title': '配置健康检查',
|
|
895
923
|
'config.health.run': '运行检查',
|
|
896
924
|
'config.health.running': '检查中...',
|
|
897
|
-
'config.health.hint': '
|
|
925
|
+
'config.health.hint': '仅探测当前选中的提供商;未选提供商的远程异常不计入本次错误。',
|
|
926
|
+
'config.health.codexHint': '通过本地桥接探测当前 Codex 路由 provider、endpoint、鉴权和模型。',
|
|
927
|
+
'config.health.providersSummary': '提供商:共 {total} · 正常 {green} · 配置问题 {yellow} · 失败 {red}',
|
|
928
|
+
'config.health.failedProviders.title': '待删除的失败提供商',
|
|
929
|
+
'config.health.failedProviders.selectAria': '选择失败提供商 {name} 进行删除',
|
|
930
|
+
'config.health.failedProviders.clearSelection': '清空选择',
|
|
931
|
+
'config.health.failedProviders.selectAll': '全选',
|
|
932
|
+
'config.health.failedProviders.selectAllAria': '全选可删除的失败提供商',
|
|
933
|
+
'config.health.failedProviders.notDeletable': '保留/系统提供商,不能在这里删除。',
|
|
934
|
+
'config.health.failedProviders.writeRequired': '删除前请先打开本 tab 的提供商写入开关。',
|
|
935
|
+
'config.health.failedProviders.deleteSelected': '移除所选配置',
|
|
936
|
+
'config.health.failedProviders.deleting': '删除中...',
|
|
898
937
|
'config.health.progress': '已完成 {done}/{total} · 失败 {failed}',
|
|
899
938
|
'config.health.ok': '检查通过',
|
|
900
939
|
'config.health.fail': '检查未通过',
|
|
@@ -1301,7 +1340,7 @@ const zh = Object.freeze({
|
|
|
1301
1340
|
'claude.health.title': '配置健康检查',
|
|
1302
1341
|
'claude.health.run': '运行检查',
|
|
1303
1342
|
'claude.health.running': '检查中...',
|
|
1304
|
-
'claude.health.hint': '
|
|
1343
|
+
'claude.health.hint': '仅探测当前选中的 Claude 配置;未选配置的远程异常不计入本次错误。',
|
|
1305
1344
|
'claude.health.progress': '已完成 {done}/{total} · 失败 {failed}',
|
|
1306
1345
|
'claude.md.title': 'CLAUDE.md',
|
|
1307
1346
|
'claude.md.open': '打开 CLAUDE.md',
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export function nextNumericProviderName(existingNames) {
|
|
2
|
+
const used = new Set();
|
|
3
|
+
const list = Array.isArray(existingNames) ? existingNames : [];
|
|
4
|
+
for (const item of list) {
|
|
5
|
+
const name = typeof item === 'string'
|
|
6
|
+
? item.trim()
|
|
7
|
+
: (item && typeof item.name === 'string' ? item.name.trim() : '');
|
|
8
|
+
if (!/^\d+$/.test(name)) continue;
|
|
9
|
+
const value = Number(name);
|
|
10
|
+
if (Number.isSafeInteger(value) && value > 0) {
|
|
11
|
+
used.add(value);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
let next = 1;
|
|
15
|
+
while (used.has(next)) next += 1;
|
|
16
|
+
return String(next);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function nextCodexProviderName(providers) {
|
|
20
|
+
return nextNumericProviderName(Array.isArray(providers) ? providers : []);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function nextClaudeConfigName(configs) {
|
|
24
|
+
return nextNumericProviderName(Object.keys(configs && typeof configs === 'object' ? configs : {}));
|
|
25
|
+
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
<div v-if="showHealthCheckModal" class="modal-overlay" @click.self="showHealthCheckModal = false">
|
|
2
|
-
<div class="modal" role="dialog" aria-modal="true" aria-labelledby="health-check-modal-title">
|
|
2
|
+
<div class="modal modal-health-check" role="dialog" aria-modal="true" aria-labelledby="health-check-modal-title">
|
|
3
3
|
<div class="modal-title" id="health-check-modal-title">{{ t('config.health.title') }}</div>
|
|
4
4
|
|
|
5
|
-
<div
|
|
6
|
-
|
|
5
|
+
<div class="health-check-modal-body">
|
|
6
|
+
<div v-if="!healthCheckResult" class="state-message">{{ t('common.notLoaded') }}</div>
|
|
7
|
+
<template v-else>
|
|
7
8
|
<div class="form-hint">
|
|
8
9
|
{{ healthCheckResult.ok ? t('config.health.ok') : t('config.health.fail') }}
|
|
9
10
|
<span v-if="healthCheckResult.issues">({{ t('config.health.issues', { count: healthCheckResult.issues.length }) }})</span>
|
|
@@ -15,6 +16,62 @@
|
|
|
15
16
|
<span v-if="healthCheckResult.remote.message"> · {{ healthCheckResult.remote.message }}</span>
|
|
16
17
|
</div>
|
|
17
18
|
|
|
19
|
+
<div v-if="healthCheckResult.remote && healthCheckResult.remote.type === 'providers-health'" class="form-hint">
|
|
20
|
+
{{ t('config.health.providersSummary', healthCheckResult.remote.summary || { total: 0, green: 0, yellow: 0, red: 0 }) }}
|
|
21
|
+
</div>
|
|
22
|
+
|
|
23
|
+
<div v-if="getHealthCheckFailedProviderItems().length" class="model-list health-failed-provider-list">
|
|
24
|
+
<div class="model-item health-failed-provider-header">
|
|
25
|
+
<span class="health-failed-provider-main">
|
|
26
|
+
<input
|
|
27
|
+
type="checkbox"
|
|
28
|
+
:checked="areAllHealthCheckFailedProvidersSelected()"
|
|
29
|
+
:disabled="!getSelectableHealthCheckFailedProviderItems().length || healthCheckFailedProviderDeleting"
|
|
30
|
+
@change="setAllHealthCheckFailedProviderSelections($event.target.checked)"
|
|
31
|
+
:aria-label="t('config.health.failedProviders.selectAllAria')"
|
|
32
|
+
>
|
|
33
|
+
<span>{{ t('config.health.failedProviders.title') }}</span>
|
|
34
|
+
</span>
|
|
35
|
+
<span class="health-failed-provider-actions">
|
|
36
|
+
<button
|
|
37
|
+
type="button"
|
|
38
|
+
class="btn-link"
|
|
39
|
+
:disabled="!getSelectableHealthCheckFailedProviderItems().length || healthCheckFailedProviderDeleting"
|
|
40
|
+
@click="setAllHealthCheckFailedProviderSelections(true)"
|
|
41
|
+
>{{ t('config.health.failedProviders.selectAll') }}</button>
|
|
42
|
+
<button
|
|
43
|
+
type="button"
|
|
44
|
+
class="btn-link"
|
|
45
|
+
:disabled="!hasHealthCheckFailedProviderSelection() || healthCheckFailedProviderDeleting"
|
|
46
|
+
@click="setAllHealthCheckFailedProviderSelections(false)"
|
|
47
|
+
>{{ t('config.health.failedProviders.clearSelection') }}</button>
|
|
48
|
+
<span class="latency error">{{ getHealthCheckFailedProviderItems().length }}</span>
|
|
49
|
+
</span>
|
|
50
|
+
</div>
|
|
51
|
+
<label
|
|
52
|
+
v-for="item in getHealthCheckFailedProviderItems()"
|
|
53
|
+
:key="item.key"
|
|
54
|
+
class="model-item health-failed-provider-item"
|
|
55
|
+
:class="{ disabled: !item.deletable }"
|
|
56
|
+
>
|
|
57
|
+
<span class="health-failed-provider-main">
|
|
58
|
+
<input
|
|
59
|
+
type="checkbox"
|
|
60
|
+
:checked="item.selected"
|
|
61
|
+
:disabled="!item.deletable || healthCheckFailedProviderDeleting"
|
|
62
|
+
@change="toggleHealthCheckFailedProviderSelection(item, $event.target.checked)"
|
|
63
|
+
:aria-label="t('config.health.failedProviders.selectAria', { name: item.name })"
|
|
64
|
+
>
|
|
65
|
+
<span>
|
|
66
|
+
<strong>{{ item.name }}</strong>
|
|
67
|
+
<span class="form-hint" v-if="item.detail">{{ item.detail }}</span>
|
|
68
|
+
<span class="form-hint" v-if="!item.deletable">{{ item.blockedReason === 'readonly' ? t('config.health.failedProviders.writeRequired') : t('config.health.failedProviders.notDeletable') }}</span>
|
|
69
|
+
</span>
|
|
70
|
+
</span>
|
|
71
|
+
<span class="latency error">{{ item.status }}</span>
|
|
72
|
+
</label>
|
|
73
|
+
</div>
|
|
74
|
+
|
|
18
75
|
<div v-if="healthCheckResult.remote && healthCheckResult.remote.speedTests" class="model-list">
|
|
19
76
|
<div
|
|
20
77
|
v-for="(result, name) in healthCheckResult.remote.speedTests"
|
|
@@ -36,9 +93,16 @@
|
|
|
36
93
|
<span>{{ issue.message || issue.code || '' }}</span>
|
|
37
94
|
</div>
|
|
38
95
|
</div>
|
|
39
|
-
|
|
96
|
+
</template>
|
|
97
|
+
</div>
|
|
40
98
|
|
|
41
|
-
<div class="btn-group">
|
|
99
|
+
<div class="btn-group health-check-modal-actions">
|
|
100
|
+
<button
|
|
101
|
+
v-if="getHealthCheckFailedProviderItems().length"
|
|
102
|
+
class="btn btn-confirm btn-danger"
|
|
103
|
+
:disabled="healthCheckFailedProviderDeleting || !hasHealthCheckFailedProviderSelection()"
|
|
104
|
+
@click="deleteSelectedHealthCheckFailedProviders()"
|
|
105
|
+
>{{ healthCheckFailedProviderDeleting ? t('config.health.failedProviders.deleting') : t('config.health.failedProviders.deleteSelected') }}</button>
|
|
42
106
|
<button class="btn btn-confirm" @click="showHealthCheckModal = false">{{ t('common.close') }}</button>
|
|
43
107
|
</div>
|
|
44
108
|
</div>
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
|
|
48
48
|
<div class="tool-config-write-scope" :class="{ locked: !isToolConfigWriteAllowed('codex') }">
|
|
49
49
|
<div class="tool-config-write-body">
|
|
50
|
-
<button class="btn-add" @click="
|
|
50
|
+
<button class="btn-add" @click="openAddProviderModal" v-if="!loading && !initError" :disabled="!isToolConfigWriteAllowed('codex')">
|
|
51
51
|
<svg class="icon" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="2"><path d="M10 4v12M4 10h12"/></svg>
|
|
52
52
|
{{ t('config.addProvider') }}
|
|
53
53
|
</button>
|
|
@@ -131,7 +131,7 @@
|
|
|
131
131
|
<div class="selector-section">
|
|
132
132
|
<div class="selector-header"><span class="selector-title">{{ t('config.health.title') }}</span></div>
|
|
133
133
|
<button class="btn-tool" @click="runHealthCheck" :disabled="healthCheckLoading || loading || !!initError">{{ healthCheckLoading ? t('config.health.running') : t('config.health.run') }}</button>
|
|
134
|
-
<div class="config-template-hint">{{ t('config.health.
|
|
134
|
+
<div class="config-template-hint">{{ t('config.health.codexHint') }}</div>
|
|
135
135
|
</div>
|
|
136
136
|
</template>
|
|
137
137
|
|
|
@@ -21,28 +21,29 @@
|
|
|
21
21
|
</div>
|
|
22
22
|
</div>
|
|
23
23
|
<div v-else>
|
|
24
|
-
<div class="selector-section">
|
|
24
|
+
<div class="selector-section session-selector-section">
|
|
25
25
|
<div class="selector-header" style="display:none">
|
|
26
26
|
<span class="selector-title">{{ t('sessions.sourceTitle') }}</span>
|
|
27
27
|
<div class="selector-actions sessions-header-actions"></div>
|
|
28
28
|
</div>
|
|
29
|
+
<div class="session-source-tabs-row">
|
|
30
|
+
<div class="session-source-pills" role="group" :aria-label="t('sessions.sourceTitle')">
|
|
31
|
+
<button
|
|
32
|
+
v-for="src in sessionSourceOptions"
|
|
33
|
+
:key="src.value"
|
|
34
|
+
:class="['session-source-pill', { active: sessionFilterSource === src.value }]"
|
|
35
|
+
:data-source="src.value"
|
|
36
|
+
@click="setSessionSource(src.value)"
|
|
37
|
+
:disabled="sessionsLoading"
|
|
38
|
+
:aria-pressed="sessionFilterSource === src.value"
|
|
39
|
+
type="button">
|
|
40
|
+
<span class="session-source-pill-dot"></span>
|
|
41
|
+
<span class="session-source-pill-label">{{ src.label }}</span>
|
|
42
|
+
</button>
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
29
45
|
<div class="session-toolbar">
|
|
30
46
|
<div class="session-toolbar-group session-toolbar-primary">
|
|
31
|
-
<div class="session-source-pills" role="radiogroup" aria-label="Session source">
|
|
32
|
-
<button
|
|
33
|
-
v-for="src in sessionSourceOptions"
|
|
34
|
-
:key="src.value"
|
|
35
|
-
:class="['session-source-pill', { active: sessionFilterSource === src.value }]"
|
|
36
|
-
:data-source="src.value"
|
|
37
|
-
@click="setSessionSource(src.value)"
|
|
38
|
-
:disabled="sessionsLoading"
|
|
39
|
-
:aria-pressed="sessionFilterSource === src.value"
|
|
40
|
-
role="radio"
|
|
41
|
-
type="button">
|
|
42
|
-
<span class="session-source-pill-dot"></span>
|
|
43
|
-
<span class="session-source-pill-label">{{ src.label }}</span>
|
|
44
|
-
</button>
|
|
45
|
-
</div>
|
|
46
47
|
<select
|
|
47
48
|
class="session-path-select"
|
|
48
49
|
v-model="sessionPathFilter"
|
|
@@ -241,6 +242,14 @@
|
|
|
241
242
|
:aria-label="(activeSession && sessionExporting[getSessionExportKey(activeSession)]) ? t('sessions.preview.exporting') : t('sessions.preview.export')">
|
|
242
243
|
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M8 2v8"/><polyline points="4 7 8 10.5 12 7"/><path d="M2.5 12v1.5a1 1 0 0 0 1 1h9a1 1 0 0 0 1-1V12"/></svg>
|
|
243
244
|
</button>
|
|
245
|
+
<button
|
|
246
|
+
class="btn-session-open session-brief-copy"
|
|
247
|
+
@click="copySessionWorkspaceBrief"
|
|
248
|
+
:disabled="!activeSessionWorkspaceSummary.available"
|
|
249
|
+
:title="t('sessions.workspace.copy')"
|
|
250
|
+
:aria-label="t('sessions.workspace.copy')">
|
|
251
|
+
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M4 2.5h6.5L13 5v8.5H4z"/><path d="M10.5 2.5V5H13"/><path d="M6 8h5"/><path d="M6 10.5h5"/></svg>
|
|
252
|
+
</button>
|
|
244
253
|
<div class="session-link-group">
|
|
245
254
|
<button
|
|
246
255
|
class="btn-session-open"
|
|
@@ -294,12 +303,77 @@
|
|
|
294
303
|
</div>
|
|
295
304
|
|
|
296
305
|
<div v-else class="session-preview-body">
|
|
306
|
+
<section v-if="activeSessionWorkspaceSummary.available" class="session-workspace" :aria-label="t('sessions.workspace.title')">
|
|
307
|
+
<div class="session-workspace-header">
|
|
308
|
+
<div>
|
|
309
|
+
<div class="session-workspace-kicker">{{ t('sessions.workspace.kicker') }}</div>
|
|
310
|
+
<h3 class="session-workspace-title">{{ t('sessions.workspace.title') }}</h3>
|
|
311
|
+
<p class="session-workspace-subtitle">
|
|
312
|
+
{{ t('sessions.workspace.subtitle', { count: activeSessionWorkspaceSummary.messageCount }) }}
|
|
313
|
+
</p>
|
|
314
|
+
</div>
|
|
315
|
+
<button class="btn-mini" type="button" @click="copySessionWorkspaceBrief">
|
|
316
|
+
{{ t('sessions.workspace.copy') }}
|
|
317
|
+
</button>
|
|
318
|
+
</div>
|
|
319
|
+
|
|
320
|
+
<div class="session-workspace-metrics">
|
|
321
|
+
<div v-for="metric in activeSessionWorkspaceSummary.metrics" :key="metric.key" class="session-workspace-metric">
|
|
322
|
+
<span class="session-workspace-metric-value">{{ metric.value }}</span>
|
|
323
|
+
<span class="session-workspace-metric-label">{{ metric.label }}</span>
|
|
324
|
+
</div>
|
|
325
|
+
</div>
|
|
326
|
+
|
|
327
|
+
<div class="session-workspace-grid">
|
|
328
|
+
<div class="session-workspace-card session-workspace-card-wide">
|
|
329
|
+
<div class="session-workspace-card-title">{{ t('sessions.workspace.signals') }}</div>
|
|
330
|
+
<ul v-if="activeSessionWorkspaceSummary.signals.length" class="session-workspace-list">
|
|
331
|
+
<li v-for="(item, index) in activeSessionWorkspaceSummary.signals" :key="'signal-' + index">{{ item }}</li>
|
|
332
|
+
</ul>
|
|
333
|
+
<div v-else class="session-workspace-empty">{{ t('sessions.workspace.empty') }}</div>
|
|
334
|
+
</div>
|
|
335
|
+
|
|
336
|
+
<div class="session-workspace-card">
|
|
337
|
+
<div class="session-workspace-card-title">{{ t('sessions.workspace.commands') }}</div>
|
|
338
|
+
<ul v-if="activeSessionWorkspaceSummary.commands.length" class="session-workspace-list session-workspace-code-list">
|
|
339
|
+
<li v-for="(item, index) in activeSessionWorkspaceSummary.commands" :key="'command-' + index"><code>{{ item }}</code></li>
|
|
340
|
+
</ul>
|
|
341
|
+
<div v-else class="session-workspace-empty">{{ t('sessions.workspace.noneCommands') }}</div>
|
|
342
|
+
</div>
|
|
343
|
+
|
|
344
|
+
<div class="session-workspace-card">
|
|
345
|
+
<div class="session-workspace-card-title">{{ t('sessions.workspace.files') }}</div>
|
|
346
|
+
<ul v-if="activeSessionWorkspaceSummary.files.length" class="session-workspace-list session-workspace-code-list">
|
|
347
|
+
<li v-for="(item, index) in activeSessionWorkspaceSummary.files" :key="'file-' + index"><code>{{ item }}</code></li>
|
|
348
|
+
</ul>
|
|
349
|
+
<div v-else class="session-workspace-empty">{{ t('sessions.workspace.noneFiles') }}</div>
|
|
350
|
+
</div>
|
|
351
|
+
|
|
352
|
+
<div class="session-workspace-card">
|
|
353
|
+
<div class="session-workspace-card-title">{{ t('sessions.workspace.links') }}</div>
|
|
354
|
+
<ul v-if="activeSessionWorkspaceSummary.links.length" class="session-workspace-list">
|
|
355
|
+
<li v-for="(item, index) in activeSessionWorkspaceSummary.links" :key="'link-' + index">{{ item }}</li>
|
|
356
|
+
</ul>
|
|
357
|
+
<div v-else class="session-workspace-empty">{{ t('sessions.workspace.noneLinks') }}</div>
|
|
358
|
+
</div>
|
|
359
|
+
|
|
360
|
+
<div class="session-workspace-card" :class="{ 'session-workspace-card-alert': activeSessionWorkspaceSummary.risks.length || activeSessionWorkspaceSummary.nextSteps.length }">
|
|
361
|
+
<div class="session-workspace-card-title">{{ t('sessions.workspace.risks') }}</div>
|
|
362
|
+
<ul v-if="activeSessionWorkspaceSummary.risks.length || activeSessionWorkspaceSummary.nextSteps.length" class="session-workspace-list">
|
|
363
|
+
<li v-for="(item, index) in activeSessionWorkspaceSummary.risks" :key="'risk-' + index">{{ item }}</li>
|
|
364
|
+
<li v-for="(item, index) in activeSessionWorkspaceSummary.nextSteps" :key="'next-' + index">{{ item }}</li>
|
|
365
|
+
</ul>
|
|
366
|
+
<div v-else class="session-workspace-empty">{{ t('sessions.workspace.noneRisks') }}</div>
|
|
367
|
+
</div>
|
|
368
|
+
</div>
|
|
369
|
+
</section>
|
|
370
|
+
|
|
297
371
|
<div class="session-preview-messages">
|
|
298
372
|
<div v-if="activeSessionDetailClipped" class="session-item-sub session-item-wrap">
|
|
299
373
|
{{ t('sessions.preview.clipped', { count: activeSessionMessages.length }) }}
|
|
300
374
|
</div>
|
|
301
375
|
<div
|
|
302
|
-
v-for="(msg, idx) in
|
|
376
|
+
v-for="(msg, idx) in activeSessionVisibleMessages"
|
|
303
377
|
:key="getRecordRenderKey(msg, idx)"
|
|
304
378
|
v-memo="[msg.text, msg.timestamp, msg.roleLabel, msg.normalizedRole]"
|
|
305
379
|
:data-message-key="getRecordRenderKey(msg, idx)"
|
|
@@ -313,6 +387,12 @@
|
|
|
313
387
|
</div>
|
|
314
388
|
<div class="session-msg-content" v-html="highlightQueryText(msg.text) || ''"></div>
|
|
315
389
|
</div>
|
|
390
|
+
<div v-if="canLoadMoreSessionMessages" class="session-preview-load-more">
|
|
391
|
+
<span>{{ t('sessions.preview.shownCount', { shown: activeSessionVisibleMessages.length, total: activeSessionMessages.length }) }}</span>
|
|
392
|
+
<button class="btn-mini" type="button" @click="loadMoreSessionMessages" :disabled="sessionPreviewLoadingMore">
|
|
393
|
+
{{ sessionPreviewLoadingMore ? t('sessions.preview.loadingMore') : t('sessions.preview.loadMore', { remain: sessionPreviewRemainingCount }) }}
|
|
394
|
+
</button>
|
|
395
|
+
</div>
|
|
316
396
|
</div>
|
|
317
397
|
</div>
|
|
318
398
|
</div>
|