@memtensor/memos-local-openclaw-plugin 0.3.20 → 1.0.1

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.
Files changed (106) hide show
  1. package/README.md +239 -22
  2. package/dist/capture/index.d.ts +1 -1
  3. package/dist/capture/index.d.ts.map +1 -1
  4. package/dist/capture/index.js +33 -8
  5. package/dist/capture/index.js.map +1 -1
  6. package/dist/index.d.ts +1 -1
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +2 -1
  9. package/dist/index.js.map +1 -1
  10. package/dist/ingest/providers/anthropic.d.ts.map +1 -1
  11. package/dist/ingest/providers/anthropic.js +22 -8
  12. package/dist/ingest/providers/anthropic.js.map +1 -1
  13. package/dist/ingest/providers/bedrock.d.ts.map +1 -1
  14. package/dist/ingest/providers/bedrock.js +22 -8
  15. package/dist/ingest/providers/bedrock.js.map +1 -1
  16. package/dist/ingest/providers/gemini.d.ts.map +1 -1
  17. package/dist/ingest/providers/gemini.js +22 -8
  18. package/dist/ingest/providers/gemini.js.map +1 -1
  19. package/dist/ingest/providers/index.d.ts +13 -18
  20. package/dist/ingest/providers/index.d.ts.map +1 -1
  21. package/dist/ingest/providers/index.js +213 -139
  22. package/dist/ingest/providers/index.js.map +1 -1
  23. package/dist/ingest/providers/openai.d.ts +1 -1
  24. package/dist/ingest/providers/openai.d.ts.map +1 -1
  25. package/dist/ingest/providers/openai.js +37 -17
  26. package/dist/ingest/providers/openai.js.map +1 -1
  27. package/dist/ingest/task-processor.d.ts +28 -3
  28. package/dist/ingest/task-processor.d.ts.map +1 -1
  29. package/dist/ingest/task-processor.js +166 -67
  30. package/dist/ingest/task-processor.js.map +1 -1
  31. package/dist/ingest/worker.d.ts.map +1 -1
  32. package/dist/ingest/worker.js +97 -75
  33. package/dist/ingest/worker.js.map +1 -1
  34. package/dist/shared/llm-call.d.ts +26 -0
  35. package/dist/shared/llm-call.d.ts.map +1 -0
  36. package/dist/shared/llm-call.js +163 -0
  37. package/dist/shared/llm-call.js.map +1 -0
  38. package/dist/skill/evaluator.d.ts +0 -3
  39. package/dist/skill/evaluator.d.ts.map +1 -1
  40. package/dist/skill/evaluator.js +34 -59
  41. package/dist/skill/evaluator.js.map +1 -1
  42. package/dist/skill/evolver.d.ts +22 -1
  43. package/dist/skill/evolver.d.ts.map +1 -1
  44. package/dist/skill/evolver.js +191 -32
  45. package/dist/skill/evolver.js.map +1 -1
  46. package/dist/skill/generator.d.ts +0 -3
  47. package/dist/skill/generator.d.ts.map +1 -1
  48. package/dist/skill/generator.js +15 -50
  49. package/dist/skill/generator.js.map +1 -1
  50. package/dist/skill/upgrader.d.ts +0 -2
  51. package/dist/skill/upgrader.d.ts.map +1 -1
  52. package/dist/skill/upgrader.js +4 -39
  53. package/dist/skill/upgrader.js.map +1 -1
  54. package/dist/skill/validator.d.ts +0 -2
  55. package/dist/skill/validator.d.ts.map +1 -1
  56. package/dist/skill/validator.js +14 -44
  57. package/dist/skill/validator.js.map +1 -1
  58. package/dist/storage/sqlite.d.ts +13 -2
  59. package/dist/storage/sqlite.d.ts.map +1 -1
  60. package/dist/storage/sqlite.js +92 -15
  61. package/dist/storage/sqlite.js.map +1 -1
  62. package/dist/tools/memory-get.d.ts.map +1 -1
  63. package/dist/tools/memory-get.js +5 -1
  64. package/dist/tools/memory-get.js.map +1 -1
  65. package/dist/tools/memory-search.d.ts.map +1 -1
  66. package/dist/tools/memory-search.js +5 -0
  67. package/dist/tools/memory-search.js.map +1 -1
  68. package/dist/tools/memory-timeline.d.ts.map +1 -1
  69. package/dist/tools/memory-timeline.js +11 -2
  70. package/dist/tools/memory-timeline.js.map +1 -1
  71. package/dist/types.d.ts +2 -1
  72. package/dist/types.d.ts.map +1 -1
  73. package/dist/types.js +1 -1
  74. package/dist/types.js.map +1 -1
  75. package/dist/viewer/html.d.ts +1 -1
  76. package/dist/viewer/html.d.ts.map +1 -1
  77. package/dist/viewer/html.js +380 -26
  78. package/dist/viewer/html.js.map +1 -1
  79. package/dist/viewer/server.d.ts +9 -0
  80. package/dist/viewer/server.d.ts.map +1 -1
  81. package/dist/viewer/server.js +549 -184
  82. package/dist/viewer/server.js.map +1 -1
  83. package/index.ts +9 -3
  84. package/package.json +2 -1
  85. package/src/capture/index.ts +39 -10
  86. package/src/index.ts +3 -2
  87. package/src/ingest/providers/anthropic.ts +22 -8
  88. package/src/ingest/providers/bedrock.ts +22 -8
  89. package/src/ingest/providers/gemini.ts +22 -8
  90. package/src/ingest/providers/index.ts +192 -142
  91. package/src/ingest/providers/openai.ts +37 -17
  92. package/src/ingest/task-processor.ts +183 -65
  93. package/src/ingest/worker.ts +98 -77
  94. package/src/shared/llm-call.ts +144 -0
  95. package/src/skill/evaluator.ts +35 -64
  96. package/src/skill/evolver.ts +201 -33
  97. package/src/skill/generator.ts +16 -59
  98. package/src/skill/upgrader.ts +5 -43
  99. package/src/skill/validator.ts +15 -47
  100. package/src/storage/sqlite.ts +107 -15
  101. package/src/tools/memory-get.ts +6 -1
  102. package/src/tools/memory-search.ts +6 -0
  103. package/src/tools/memory-timeline.ts +13 -1
  104. package/src/types.ts +2 -1
  105. package/src/viewer/html.ts +380 -26
  106. package/src/viewer/server.ts +535 -197
@@ -417,7 +417,7 @@ input,textarea,select{font-family:inherit;font-size:inherit}
417
417
 
418
418
  /* ─── Analytics / 统计 ─── */
419
419
  .nav-tabs{display:flex;align-items:center;gap:2px;background:rgba(255,255,255,.06);border-radius:10px;padding:3px}
420
- .nav-tabs .tab{padding:6px 20px;border-radius:8px;font-size:13px;font-weight:600;color:var(--text-sec);background:transparent;border:1px solid transparent;cursor:pointer;transition:all .2s;white-space:nowrap}
420
+ .nav-tabs .tab{padding:6px 20px;border-radius:8px;font-size:13px;font-weight:600;color:var(--text-sec);background:transparent;border:1px solid rgba(0,0,0,0);cursor:pointer;transition:color .2s,background .2s,box-shadow .2s;white-space:nowrap}
421
421
  .nav-tabs .tab:hover{color:var(--text)}
422
422
  .nav-tabs .tab.active{color:var(--text);background:rgba(255,255,255,.1);border-color:var(--border);box-shadow:0 1px 4px rgba(0,0,0,.15)}
423
423
  [data-theme="light"] .nav-tabs{background:rgba(0,0,0,.05)}
@@ -515,6 +515,12 @@ input,textarea,select{font-family:inherit;font-size:inherit}
515
515
  .toggle-slider::before{content:'';position:absolute;height:14px;width:14px;left:3px;bottom:3px;background:#fff;border-radius:50%;transition:.2s}
516
516
  .toggle-switch input:checked+.toggle-slider{background:var(--pri)}
517
517
  .toggle-switch input:checked+.toggle-slider::before{transform:translateX(16px)}
518
+ .test-conn-row{display:flex;align-items:center;gap:10px;margin-top:12px;padding-top:10px;border-top:1px dashed var(--border)}
519
+ .test-conn-row .btn{font-size:11px;padding:5px 14px;border:1px solid var(--border);border-radius:6px}
520
+ .test-result{font-size:12px;line-height:1.5;word-break:break-word}
521
+ .test-result.ok{color:#22c55e}
522
+ .test-result.fail{color:var(--rose)}
523
+ .test-result.loading{color:var(--text-muted)}
518
524
  .settings-actions{display:flex;gap:12px;justify-content:flex-end;align-items:center;margin-top:16px;padding-top:16px;border-top:1px solid var(--border)}
519
525
  .settings-actions .btn{min-width:110px;padding:10px 20px;font-size:13px}
520
526
  .settings-actions .btn-primary{background:rgba(99,102,241,.08);color:var(--pri);border:1px solid rgba(99,102,241,.25);font-weight:600}
@@ -819,6 +825,7 @@ input,textarea,select{font-family:inherit;font-size:inherit}
819
825
  <div class="task-detail-summary" id="taskDetailSummary"></div>
820
826
  <div class="task-detail-chunks-title" data-i18n="tasks.chunks">Related Memories</div>
821
827
  <div class="task-detail-chunks" id="taskDetailChunks"></div>
828
+ <div id="taskDetailActions" style="display:flex;gap:8px;margin-top:16px;padding-top:12px;border-top:1px solid var(--border)"></div>
822
829
  </div>
823
830
  </div>
824
831
  </div>
@@ -867,6 +874,7 @@ input,textarea,select{font-family:inherit;font-size:inherit}
867
874
  <div class="task-detail-chunks" id="skillVersionsList" style="gap:10px"></div>
868
875
  <div class="task-detail-chunks-title" style="margin-top:16px" data-i18n="skills.related">Related Tasks</div>
869
876
  <div class="task-detail-chunks" id="skillRelatedTasks" style="gap:8px"></div>
877
+ <div id="skillDetailActions" style="display:flex;gap:8px;margin-top:16px;padding-top:12px;border-top:1px solid var(--border)"></div>
870
878
  </div>
871
879
  </div>
872
880
  <div class="analytics-view" id="analyticsView">
@@ -940,9 +948,12 @@ input,textarea,select{font-family:inherit;font-size:inherit}
940
948
  <div class="settings-grid">
941
949
  <div class="settings-field">
942
950
  <label data-i18n="settings.provider">Provider</label>
943
- <select id="cfgEmbProvider">
951
+ <select id="cfgEmbProvider" onchange="onProviderChange('embedding')">
944
952
  <option value="openai_compatible">OpenAI Compatible</option>
945
953
  <option value="openai">OpenAI</option>
954
+ <option value="siliconflow">SiliconFlow (\u7845\u57FA\u6D41\u52A8)</option>
955
+ <option value="zhipu">Zhipu AI (\u667A\u8C31)</option>
956
+ <option value="bailian">Alibaba Bailian (\u767E\u70BC)</option>
946
957
  <option value="gemini">Gemini</option>
947
958
  <option value="azure_openai">Azure OpenAI</option>
948
959
  <option value="cohere">Cohere</option>
@@ -964,6 +975,10 @@ input,textarea,select{font-family:inherit;font-size:inherit}
964
975
  <input type="password" id="cfgEmbApiKey" placeholder="\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022">
965
976
  </div>
966
977
  </div>
978
+ <div class="test-conn-row">
979
+ <button class="btn btn-sm btn-ghost" onclick="testModel('embedding')" id="testEmbBtn" data-i18n="settings.test">Test Connection</button>
980
+ <span class="test-result" id="testEmbResult"></span>
981
+ </div>
967
982
  </div>
968
983
 
969
984
  <div class="settings-section">
@@ -971,9 +986,14 @@ input,textarea,select{font-family:inherit;font-size:inherit}
971
986
  <div class="settings-grid">
972
987
  <div class="settings-field">
973
988
  <label data-i18n="settings.provider">Provider</label>
974
- <select id="cfgSumProvider">
989
+ <select id="cfgSumProvider" onchange="onProviderChange('summarizer')">
975
990
  <option value="openai_compatible">OpenAI Compatible</option>
976
991
  <option value="openai">OpenAI</option>
992
+ <option value="siliconflow">SiliconFlow (\u7845\u57FA\u6D41\u52A8)</option>
993
+ <option value="zhipu">Zhipu AI (\u667A\u8C31)</option>
994
+ <option value="deepseek">DeepSeek</option>
995
+ <option value="bailian">Alibaba Bailian (\u767E\u70BC)</option>
996
+ <option value="moonshot">Moonshot (Kimi)</option>
977
997
  <option value="anthropic">Anthropic</option>
978
998
  <option value="gemini">Gemini</option>
979
999
  <option value="azure_openai">Azure OpenAI</option>
@@ -997,6 +1017,10 @@ input,textarea,select{font-family:inherit;font-size:inherit}
997
1017
  <input type="number" id="cfgSumTemp" step="0.1" min="0" max="2" placeholder="0">
998
1018
  </div>
999
1019
  </div>
1020
+ <div class="test-conn-row">
1021
+ <button class="btn btn-sm btn-ghost" onclick="testModel('summarizer')" id="testSumBtn" data-i18n="settings.test">Test Connection</button>
1022
+ <span class="test-result" id="testSumResult"></span>
1023
+ </div>
1000
1024
  </div>
1001
1025
  </div>
1002
1026
 
@@ -1026,10 +1050,15 @@ input,textarea,select{font-family:inherit;font-size:inherit}
1026
1050
  <div class="settings-grid">
1027
1051
  <div class="settings-field">
1028
1052
  <label data-i18n="settings.provider">Provider</label>
1029
- <select id="cfgSkillProvider">
1053
+ <select id="cfgSkillProvider" onchange="onProviderChange('skill')">
1030
1054
  <option value="">— <span data-i18n="settings.skill.usemain">Use main summarizer</span> —</option>
1031
1055
  <option value="openai_compatible">OpenAI Compatible</option>
1032
1056
  <option value="openai">OpenAI</option>
1057
+ <option value="siliconflow">SiliconFlow (\u7845\u57FA\u6D41\u52A8)</option>
1058
+ <option value="zhipu">Zhipu AI (\u667A\u8C31)</option>
1059
+ <option value="deepseek">DeepSeek</option>
1060
+ <option value="bailian">Alibaba Bailian (\u767E\u70BC)</option>
1061
+ <option value="moonshot">Moonshot (Kimi)</option>
1033
1062
  <option value="anthropic">Anthropic</option>
1034
1063
  <option value="gemini">Gemini</option>
1035
1064
  <option value="azure_openai">Azure OpenAI</option>
@@ -1049,6 +1078,10 @@ input,textarea,select{font-family:inherit;font-size:inherit}
1049
1078
  <input type="password" id="cfgSkillApiKey" placeholder="\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022">
1050
1079
  </div>
1051
1080
  </div>
1081
+ <div class="test-conn-row">
1082
+ <button class="btn btn-sm btn-ghost" onclick="testModel('skill')" id="testSkillBtn" data-i18n="settings.test">Test Connection</button>
1083
+ <span class="test-result" id="testSkillResult"></span>
1084
+ </div>
1052
1085
  </div>
1053
1086
  </div>
1054
1087
 
@@ -1122,8 +1155,20 @@ input,textarea,select{font-family:inherit;font-size:inherit}
1122
1155
  <div id="migrateActions" style="display:flex;gap:12px;align-items:center;flex-wrap:wrap">
1123
1156
  <button class="btn btn-ghost" onclick="migrateScan()" id="migrateScanBtn" data-i18n="migrate.scan">Scan Data Sources</button>
1124
1157
  <button class="btn btn-primary" onclick="migrateStart()" id="migrateStartBtn" style="display:none" data-i18n="migrate.start">Start Import</button>
1158
+ <span id="migrateConcurrencyRow" style="display:none;align-items:center;gap:6px">
1159
+ <span style="font-size:11px;color:var(--text-muted)" data-i18n="migrate.concurrency.label">Concurrent agents</span>
1160
+ <select id="migrateConcurrency" class="filter-select" style="min-width:auto;padding:3px 10px;font-size:11px">
1161
+ <option value="1" selected>1</option>
1162
+ <option value="2">2</option>
1163
+ <option value="4">4</option>
1164
+ <option value="8">8</option>
1165
+ </select>
1166
+ </span>
1125
1167
  <span id="migrateStatus" style="font-size:11px;color:var(--text-muted)"></span>
1126
1168
  </div>
1169
+ <div id="migrateConcurrencyWarn" style="display:none;margin-top:8px;padding:8px 12px;background:rgba(245,158,11,.06);border:1px solid rgba(245,158,11,.2);border-radius:8px;font-size:11px;color:#f59e0b;line-height:1.5">
1170
+ <span data-i18n="migrate.concurrency.warn">\u26A0 Increasing concurrency raises LLM API call frequency, which may trigger rate limits and cause failures.</span>
1171
+ </div>
1127
1172
 
1128
1173
  <!-- Post-process section: shown after import completes -->
1129
1174
  <div id="postprocessSection" style="display:none;margin-top:16px">
@@ -1146,11 +1191,23 @@ input,textarea,select{font-family:inherit;font-size:inherit}
1146
1191
  </div>
1147
1192
  </label>
1148
1193
  </div>
1149
- <div style="display:flex;gap:10px;align-items:center">
1194
+ <div style="display:flex;gap:10px;align-items:center;flex-wrap:wrap">
1150
1195
  <button class="btn btn-primary" id="ppStartBtn" onclick="ppStart()" data-i18n="pp.start">Start Processing</button>
1151
1196
  <button class="btn btn-sm" id="ppStopBtn" onclick="ppStop()" style="display:none;background:rgba(239,68,68,.12);color:#ef4444;border:1px solid rgba(239,68,68,.3);font-size:12px;padding:5px 16px;font-weight:600" data-i18n="migrate.stop">\u25A0 Stop</button>
1197
+ <span style="display:inline-flex;align-items:center;gap:6px">
1198
+ <span style="font-size:11px;color:var(--text-muted)" data-i18n="pp.concurrency.label">Concurrent agents</span>
1199
+ <select id="ppConcurrency" class="filter-select" style="min-width:auto;padding:3px 10px;font-size:11px">
1200
+ <option value="1" selected>1</option>
1201
+ <option value="2">2</option>
1202
+ <option value="4">4</option>
1203
+ <option value="8">8</option>
1204
+ </select>
1205
+ </span>
1152
1206
  <span id="ppStatus" style="font-size:11px;color:var(--text-muted)"></span>
1153
1207
  </div>
1208
+ <div id="ppConcurrencyWarn" style="display:none;margin-top:8px;padding:8px 12px;background:rgba(245,158,11,.06);border:1px solid rgba(245,158,11,.2);border-radius:8px;font-size:11px;color:#f59e0b;line-height:1.5">
1209
+ <span data-i18n="pp.concurrency.warn">\u26A0 Increasing concurrency raises LLM API call frequency, which may trigger rate limits and cause failures.</span>
1210
+ </div>
1154
1211
  <div id="ppProgress" style="display:none;margin-top:12px">
1155
1212
  <div style="display:flex;align-items:center;gap:12px;margin-bottom:8px">
1156
1213
  <div style="font-size:12px;font-weight:600;color:var(--text)" id="ppPhaseLabel"></div>
@@ -1437,11 +1494,24 @@ const I18N={
1437
1494
  'settings.telemetry.hint':'Anonymous usage analytics to help improve the plugin. Only sends tool names, latencies, and version info. No memory content, queries, or personal data is ever sent.',
1438
1495
  'settings.viewerport':'Viewer Port',
1439
1496
  'settings.viewerport.hint':'Requires restart to take effect',
1497
+ 'settings.test':'Test Connection',
1498
+ 'settings.test.loading':'Testing...',
1499
+ 'settings.test.ok':'Connected',
1500
+ 'settings.test.fail':'Failed',
1440
1501
  'settings.save':'Save Settings',
1441
1502
  'settings.reset':'Reset',
1442
1503
  'settings.saved':'Saved',
1443
1504
  'settings.restart.hint':'Some changes require restarting the OpenClaw gateway to take effect.',
1444
1505
  'settings.save.fail':'Failed to save settings',
1506
+ 'settings.save.emb.required':'Embedding model is required. Please configure an embedding model before saving.',
1507
+ 'settings.save.emb.fail':'Embedding model test failed, cannot save',
1508
+ 'settings.save.sum.fail':'Summarizer model test failed, cannot save',
1509
+ 'settings.save.skill.fail':'Skill model test failed, cannot save',
1510
+ 'settings.save.sum.fallback':'Summarizer model is not configured — will use OpenClaw native model as fallback.',
1511
+ 'settings.save.skill.fallback':'Skill dedicated model is not configured — will use OpenClaw native model as fallback.',
1512
+ 'settings.save.fallback.model':'Fallback model: ',
1513
+ 'settings.save.fallback.none':'Not available (no OpenClaw native model found)',
1514
+ 'settings.save.fallback.confirm':'Continue to save?',
1445
1515
  'migrate.title':'Import OpenClaw Memory',
1446
1516
  'migrate.desc':'Migrate your existing OpenClaw built-in memories and conversation history into this plugin. The import process uses smart deduplication to avoid duplicates.',
1447
1517
  'migrate.modes.title':'Three ways to use:',
@@ -1455,6 +1525,8 @@ const I18N={
1455
1525
  'migrate.config.warn.desc':'Please configure both Embedding Model and Summarizer Model above before importing. These are required for processing memories.',
1456
1526
  'migrate.sqlite.label':'Memory Index (SQLite)',
1457
1527
  'migrate.sessions.label':'Conversation History',
1528
+ 'migrate.concurrency.label':'Concurrent agents',
1529
+ 'migrate.concurrency.warn':'\u26A0 Increasing concurrency raises LLM API call frequency, which may trigger rate limits and cause failures.',
1458
1530
  'migrate.scan':'Scan Data Sources',
1459
1531
  'migrate.start':'Start Import',
1460
1532
  'migrate.scanning':'Scanning...',
@@ -1478,6 +1550,8 @@ const I18N={
1478
1550
  'pp.tasks.hint':'Group imported messages into tasks and generate a structured summary (title, goal, steps, result) for each one. Makes it easier to search and recall past work.',
1479
1551
  'pp.skills.label':'Trigger skill evolution',
1480
1552
  'pp.skills.hint':'Analyze completed tasks and automatically create or upgrade reusable skills (SKILL.md). Requires task summaries to be enabled. May take longer due to LLM evaluation.',
1553
+ 'pp.concurrency.label':'Concurrent agents',
1554
+ 'pp.concurrency.warn':'\u26A0 Increasing concurrency raises LLM API call frequency, which may trigger rate limits and cause failures.',
1481
1555
  'pp.start':'Start Processing',
1482
1556
  'pp.resume':'Resume Processing',
1483
1557
  'pp.running':'Processing',
@@ -1526,7 +1600,25 @@ const I18N={
1526
1600
  'tasks.role.assistant':'Assistant',
1527
1601
  'tasks.error':'Error',
1528
1602
  'tasks.error.detail':'Failed to load task details',
1529
- 'tasks.untitled.related':'Untitled'
1603
+ 'tasks.untitled.related':'Untitled',
1604
+ 'task.edit':'Edit',
1605
+ 'task.delete':'Delete',
1606
+ 'task.save':'Save',
1607
+ 'task.cancel':'Cancel',
1608
+ 'task.delete.confirm':'Are you sure you want to delete this task? This cannot be undone.',
1609
+ 'task.delete.error':'Failed to delete task: ',
1610
+ 'task.save.error':'Failed to save task: ',
1611
+ 'task.retrySkill':'Retry Skill Generation',
1612
+ 'task.retrySkill.short':'Retry Skill',
1613
+ 'task.retrySkill.confirm':'Re-trigger skill generation for this task?',
1614
+ 'task.retrySkill.error':'Failed to retry skill generation: ',
1615
+ 'skill.edit':'Edit',
1616
+ 'skill.delete':'Delete',
1617
+ 'skill.save':'Save',
1618
+ 'skill.cancel':'Cancel',
1619
+ 'skill.delete.confirm':'Are you sure you want to delete this skill? This will also remove all associated files and cannot be undone.',
1620
+ 'skill.delete.error':'Failed to delete skill: ',
1621
+ 'skill.save.error':'Failed to save skill: '
1530
1622
  },
1531
1623
  zh:{
1532
1624
  'title':'OpenClaw 记忆',
@@ -1708,11 +1800,24 @@ const I18N={
1708
1800
  'settings.telemetry.hint':'匿名使用统计,帮助改进插件。仅发送工具名称、响应时间和版本信息,不会发送任何记忆内容、搜索查询或个人数据。',
1709
1801
  'settings.viewerport':'Viewer 端口',
1710
1802
  'settings.viewerport.hint':'修改后需重启网关生效',
1803
+ 'settings.test':'测试连接',
1804
+ 'settings.test.loading':'测试中...',
1805
+ 'settings.test.ok':'连接成功',
1806
+ 'settings.test.fail':'连接失败',
1711
1807
  'settings.save':'保存设置',
1712
1808
  'settings.reset':'重置',
1713
1809
  'settings.saved':'已保存',
1714
1810
  'settings.restart.hint':'部分设置修改后需要重启 OpenClaw 网关才能生效。',
1715
1811
  'settings.save.fail':'保存设置失败',
1812
+ 'settings.save.emb.required':'嵌入模型为必填项,请先配置嵌入模型再保存。',
1813
+ 'settings.save.emb.fail':'嵌入模型测试失败,无法保存',
1814
+ 'settings.save.sum.fail':'摘要模型测试失败,无法保存',
1815
+ 'settings.save.skill.fail':'技能模型测试失败,无法保存',
1816
+ 'settings.save.sum.fallback':'摘要模型未配置 — 将使用 OpenClaw 原生模型作为降级方案。',
1817
+ 'settings.save.skill.fallback':'技能专用模型未配置 — 将使用 OpenClaw 原生模型作为降级方案。',
1818
+ 'settings.save.fallback.model':'降级模型:',
1819
+ 'settings.save.fallback.none':'不可用(未检测到 OpenClaw 原生模型)',
1820
+ 'settings.save.fallback.confirm':'是否继续保存?',
1716
1821
  'migrate.title':'导入 OpenClaw 记忆',
1717
1822
  'migrate.desc':'将 OpenClaw 内置的记忆数据和对话历史迁移到本插件中。导入过程使用智能去重,避免重复导入。',
1718
1823
  'migrate.modes.title':'三种使用方式:',
@@ -1726,6 +1831,8 @@ const I18N={
1726
1831
  'migrate.config.warn.desc':'请先在上方配置好 Embedding 模型和 Summarizer 模型,这两项是处理记忆所必需的。',
1727
1832
  'migrate.sqlite.label':'记忆索引 (SQLite)',
1728
1833
  'migrate.sessions.label':'对话历史',
1834
+ 'migrate.concurrency.label':'并行 Agent 数',
1835
+ 'migrate.concurrency.warn':'\u26A0 提高并行数会增加 LLM API 调用频率,可能触发限流而导致失败。',
1729
1836
  'migrate.scan':'扫描数据源',
1730
1837
  'migrate.start':'开始导入',
1731
1838
  'migrate.scanning':'扫描中...',
@@ -1749,6 +1856,8 @@ const I18N={
1749
1856
  'pp.tasks.hint':'将导入的消息按任务分组,为每个任务生成结构化摘要(标题、目标、步骤、结果),方便日后搜索和回忆。',
1750
1857
  'pp.skills.label':'触发技能进化',
1751
1858
  'pp.skills.hint':'分析已完成的任务,自动创建或升级可复用的技能(SKILL.md)。需要先启用任务摘要。由于需要 LLM 评估,耗时较长。',
1859
+ 'pp.concurrency.label':'并行 Agent 数',
1860
+ 'pp.concurrency.warn':'\u26A0 提高并行数会增加 LLM API 调用频率,可能触发限流而导致失败。',
1752
1861
  'pp.start':'开始处理',
1753
1862
  'pp.resume':'继续处理',
1754
1863
  'pp.running':'正在处理',
@@ -1797,7 +1906,25 @@ const I18N={
1797
1906
  'tasks.role.assistant':'助手',
1798
1907
  'tasks.error':'出错了',
1799
1908
  'tasks.error.detail':'加载任务详情失败',
1800
- 'tasks.untitled.related':'未命名'
1909
+ 'tasks.untitled.related':'未命名',
1910
+ 'task.edit':'编辑',
1911
+ 'task.delete':'删除',
1912
+ 'task.save':'保存',
1913
+ 'task.cancel':'取消',
1914
+ 'task.delete.confirm':'确定要删除此任务吗?此操作不可撤销。',
1915
+ 'task.delete.error':'删除任务失败:',
1916
+ 'task.save.error':'保存任务失败:',
1917
+ 'task.retrySkill':'重新生成技能',
1918
+ 'task.retrySkill.short':'重试技能',
1919
+ 'task.retrySkill.confirm':'确定要为此任务重新触发技能生成吗?',
1920
+ 'task.retrySkill.error':'重新生成技能失败:',
1921
+ 'skill.edit':'编辑',
1922
+ 'skill.delete':'删除',
1923
+ 'skill.save':'保存',
1924
+ 'skill.cancel':'取消',
1925
+ 'skill.delete.confirm':'确定要删除此技能吗?关联的文件也会被删除,此操作不可撤销。',
1926
+ 'skill.delete.error':'删除技能失败:',
1927
+ 'skill.save.error':'保存技能失败:'
1801
1928
  }
1802
1929
  };
1803
1930
  const LANG_KEY='memos-viewer-lang';
@@ -2216,6 +2343,11 @@ async function loadTasks(){
2216
2343
  '<span class="tag"><span class="icon">\\u{1F4DD}</span> '+task.chunkCount+' '+t('tasks.chunks.label')+'</span>'+
2217
2344
  '<span class="tag"><span class="icon">\\u{1F4C2}</span> '+(task.sessionKey||'').slice(0,12)+'</span>'+
2218
2345
  '</div>'+
2346
+ '<div class="card-actions" onclick="event.stopPropagation()">'+
2347
+ '<button class="btn btn-sm btn-ghost" onclick="openTaskDetail(\\''+task.id+'\\')">'+t('card.expand')+'</button>'+
2348
+ (task.status==='completed'&&(!task.skillStatus||task.skillStatus==='not_generated'||task.skillStatus==='skipped')?'<button class="btn btn-sm btn-ghost" onclick="retrySkillGen(\\''+task.id+'\\')">'+t('task.retrySkill.short')+'</button>':'')+
2349
+ '<button class="btn btn-sm btn-ghost" style="color:var(--accent)" onclick="deleteTask(\\''+task.id+'\\')">'+t('task.delete')+'</button>'+
2350
+ '</div>'+
2219
2351
  '</div>';
2220
2352
  }).join('');
2221
2353
 
@@ -2240,7 +2372,10 @@ function renderTasksPagination(total){
2240
2372
  el.innerHTML=html;
2241
2373
  }
2242
2374
 
2375
+ var _currentTaskId=null;
2376
+ var _currentTaskData=null;
2243
2377
  async function openTaskDetail(taskId){
2378
+ _currentTaskId=taskId;
2244
2379
  const overlay=document.getElementById('taskDetailOverlay');
2245
2380
  overlay.classList.add('show');
2246
2381
  document.getElementById('taskDetailTitle').textContent=t('tasks.loading');
@@ -2249,6 +2384,7 @@ async function openTaskDetail(taskId){
2249
2384
  document.getElementById('taskSkillSection').className='task-skill-section';
2250
2385
  document.getElementById('taskDetailSummary').textContent='';
2251
2386
  document.getElementById('taskDetailChunks').innerHTML='<div class="spinner"></div>';
2387
+ document.getElementById('taskDetailActions').innerHTML='';
2252
2388
 
2253
2389
  try{
2254
2390
  const r=await fetch('/api/task/'+taskId);
@@ -2266,9 +2402,13 @@ async function openTaskDetail(taskId){
2266
2402
  meta.push('<div style="width:100%;margin-top:4px"><span class="meta-item" style="width:100%">'+t('tasks.taskid')+'<span class="task-id-full">'+esc(task.id)+'</span></span></div>');
2267
2403
  document.getElementById('taskDetailMeta').innerHTML=meta.join('');
2268
2404
 
2405
+ _currentTaskData=task;
2406
+
2269
2407
  // ── Skill status section ──
2270
2408
  renderTaskSkillSection(task);
2271
2409
 
2410
+ document.getElementById('taskDetailActions').innerHTML='';
2411
+
2272
2412
  var summaryEl=document.getElementById('taskDetailSummary');
2273
2413
  if(task.status==='skipped'){
2274
2414
  summaryEl.innerHTML='<div style="color:var(--text-muted);font-style:italic;display:flex;align-items:flex-start;gap:8px"><span style="font-size:18px">\\u26A0\\uFE0F</span><span>'+esc(task.summary||t('tasks.skipped.default'))+'</span></div>';
@@ -2322,19 +2462,31 @@ function renderTaskSkillSection(task){
2322
2462
  }else if(ss==='not_generated'){
2323
2463
  section.className='task-skill-section status-not_generated';
2324
2464
  section.innerHTML='<div class="skill-status-header">\\u274C \u672A\u751F\u6210\u6280\u80FD</div>'+
2325
- '<div class="skill-status-reason">\u539F\u56E0\uFF1A'+esc(task.skillReason||'\u7ECF LLM \u8BC4\u4F30\uFF0C\u8BE5\u4EFB\u52A1\u4E0D\u9002\u5408\u63D0\u70BC\u4E3A\u53EF\u590D\u7528\u6280\u80FD\u3002')+'</div>';
2465
+ '<div class="skill-status-reason">\u539F\u56E0\uFF1A'+esc(task.skillReason||'\u7ECF LLM \u8BC4\u4F30\uFF0C\u8BE5\u4EFB\u52A1\u4E0D\u9002\u5408\u63D0\u70BC\u4E3A\u53EF\u590D\u7528\u6280\u80FD\u3002')+'</div>'+
2466
+ (task.status==='completed'?'<button class="btn btn-primary" onclick="retrySkillGen(\\''+esc(task.id)+'\\')" style="margin-top:8px;font-size:12px">'+t('task.retrySkill')+'</button>':'');
2326
2467
  }else if(ss==='skipped'){
2327
2468
  section.className='task-skill-section status-skipped';
2328
2469
  section.innerHTML='<div class="skill-status-header">\\u23ED \u8DF3\u8FC7\u6280\u80FD\u8BC4\u4F30</div>'+
2329
- '<div class="skill-status-reason">\u539F\u56E0\uFF1A'+esc(task.skillReason||'')+'</div>';
2470
+ '<div class="skill-status-reason">\u539F\u56E0\uFF1A'+esc(task.skillReason||'')+'</div>'+
2471
+ (task.status==='completed'?'<button class="btn btn-primary" onclick="retrySkillGen(\\''+esc(task.id)+'\\')" style="margin-top:8px;font-size:12px">'+t('task.retrySkill')+'</button>':'');
2472
+ }else if(ss==='queued'){
2473
+ section.className='task-skill-section status-generating';
2474
+ section.innerHTML='<div class="skill-status-header">\\u{1F4CB} \u6392\u961F\u4E2D</div>'+
2475
+ '<div class="skill-status-reason">'+esc(task.skillReason||'\u7B49\u5F85\u6280\u80FD\u8BC4\u4F30\uFF0C\u524D\u65B9\u4EFB\u52A1\u5904\u7406\u5B8C\u6210\u540E\u81EA\u52A8\u5F00\u59CB\u3002')+'</div>';
2330
2476
  }else if(task.status==='active'){
2331
2477
  section.className='task-skill-section status-skipped';
2332
2478
  section.innerHTML='<div class="skill-status-header">\\u23F8 \u4EFB\u52A1\u8FDB\u884C\u4E2D</div>'+
2333
2479
  '<div class="skill-status-reason">\u6280\u80FD\u8BC4\u4F30\u5728\u4EFB\u52A1\u5B8C\u6210\u540E\u81EA\u52A8\u8FD0\u884C\u3002</div>';
2480
+ }else if(task.status==='completed'){
2481
+ section.className='task-skill-section status-generating';
2482
+ section.innerHTML='<div class="skill-status-header">\\u23F3 \u7B49\u5F85\u8BC4\u4F30</div>'+
2483
+ '<div class="skill-status-reason">\u4EFB\u52A1\u5DF2\u5B8C\u6210\uFF0C\u6280\u80FD\u8BC4\u4F30\u5373\u5C06\u5F00\u59CB\u3002</div>'+
2484
+ '<button class="btn btn-primary" onclick="retrySkillGen(\\''+esc(task.id)+'\\')" style="margin-top:8px;font-size:12px">'+t('task.retrySkill')+'</button>';
2334
2485
  }else{
2335
2486
  section.className='task-skill-section status-skipped';
2336
2487
  section.innerHTML='<div class="skill-status-header">\\u2014 \u65E0\u6280\u80FD\u4FE1\u606F</div>'+
2337
- '<div class="skill-status-reason">\u8BE5\u4EFB\u52A1\u5728\u6280\u80FD\u8FDB\u5316\u7CFB\u7EDF\u542F\u7528\u4E4B\u524D\u5B8C\u6210\uFF0C\u65E0\u6280\u80FD\u8BC4\u4F30\u8BB0\u5F55\u3002</div>';
2488
+ '<div class="skill-status-reason">\u8BE5\u4EFB\u52A1\u672A\u8FDB\u884C\u6280\u80FD\u8BC4\u4F30\u3002</div>'+
2489
+ (task.status==='completed'?'<button class="btn btn-primary" onclick="retrySkillGen(\\''+esc(task.id)+'\\')" style="margin-top:8px;font-size:12px">'+t('task.retrySkill')+'</button>':'');
2338
2490
  }
2339
2491
  }
2340
2492
 
@@ -2343,6 +2495,29 @@ function closeTaskDetail(event){
2343
2495
  document.getElementById('taskDetailOverlay').classList.remove('show');
2344
2496
  }
2345
2497
 
2498
+ async function retrySkillGen(taskId){
2499
+ if(!confirm(t('task.retrySkill.confirm'))) return;
2500
+ try{
2501
+ const r=await fetch('/api/task/'+taskId+'/retry-skill',{method:'POST'});
2502
+ const d=await r.json();
2503
+ if(!r.ok) throw new Error(d.error||'unknown');
2504
+ openTaskDetail(taskId);
2505
+ }catch(e){ alert(t('task.retrySkill.error')+e.message); }
2506
+ }
2507
+
2508
+ async function deleteTask(taskId){
2509
+ if(!confirm(t('task.delete.confirm'))) return;
2510
+ try{
2511
+ const r=await fetch('/api/task/'+taskId,{method:'DELETE'});
2512
+ const d=await r.json();
2513
+ if(!r.ok) throw new Error(d.error||'unknown');
2514
+ closeTaskDetail();
2515
+ document.getElementById('taskDetailOverlay').classList.remove('show');
2516
+ loadTasks();
2517
+ }catch(e){ alert(t('task.delete.error')+e.message); }
2518
+ }
2519
+
2520
+
2346
2521
  /* ─── Skills View Logic ─── */
2347
2522
  let skillsStatusFilter='';
2348
2523
 
@@ -2400,6 +2575,11 @@ async function loadSkills(){
2400
2575
  '<span class="tag"><span class="icon">\\u{1F4E6}</span> '+skill.sourceType+'</span>'+
2401
2576
  (tags.length>0?'<div class="skill-card-tags">'+tags.map(t=>'<span class="skill-tag">'+esc(t)+'</span>').join('')+'</div>':'')+
2402
2577
  '</div>'+
2578
+ '<div class="card-actions" onclick="event.stopPropagation()">'+
2579
+ '<button class="btn btn-sm btn-ghost" onclick="openSkillDetail(\\''+skill.id+'\\')">'+t('card.expand')+'</button>'+
2580
+ (skill.visibility==='public'?'<button class="btn btn-sm btn-ghost" onclick="toggleSkillPublic(\\''+skill.id+'\\',false)">\\u{1F512} '+t('skills.setPrivate')+'</button>':'<button class="btn btn-sm btn-ghost" onclick="toggleSkillPublic(\\''+skill.id+'\\',true)">\\u{1F310} '+t('skills.setPublic')+'</button>')+
2581
+ '<button class="btn btn-sm btn-ghost" style="color:var(--accent)" onclick="deleteSkill(\\''+skill.id+'\\')">'+t('skill.delete')+'</button>'+
2582
+ '</div>'+
2403
2583
  '</div>';
2404
2584
  }).join('');
2405
2585
  }catch(e){
@@ -2424,6 +2604,7 @@ async function openSkillDetail(skillId){
2424
2604
  document.getElementById('skillDetailContent').innerHTML='<div class="spinner"></div>';
2425
2605
  document.getElementById('skillVersionsList').innerHTML='<div class="spinner"></div>';
2426
2606
  document.getElementById('skillRelatedTasks').innerHTML='';
2607
+ document.getElementById('skillDetailActions').innerHTML='';
2427
2608
 
2428
2609
  try{
2429
2610
  const r=await fetch('/api/skill/'+skillId);
@@ -2519,6 +2700,9 @@ async function openSkillDetail(skillId){
2519
2700
  ).join('');
2520
2701
  }
2521
2702
 
2703
+ window._currentSkillData=skill;
2704
+ document.getElementById('skillDetailActions').innerHTML='';
2705
+
2522
2706
  }catch(e){
2523
2707
  document.getElementById('skillDetailTitle').textContent=t('skills.error');
2524
2708
  document.getElementById('skillDetailContent').innerHTML='<div style="color:var(--rose);padding:16px">'+t('skills.error.detail')+esc(String(e))+'</div>';
@@ -2539,11 +2723,23 @@ async function toggleSkillVisibility(){
2539
2723
  const newVis=btn.dataset.vis==='public'?'private':'public';
2540
2724
  try{
2541
2725
  const r=await fetch('/api/skill/'+currentSkillId+'/visibility',{method:'PUT',headers:{'Content-Type':'application/json'},body:JSON.stringify({visibility:newVis})});
2542
- if(!r.ok) throw new Error('Failed: '+r.status);
2726
+ if(!r.ok){var errBody='';try{var ej=await r.json();errBody=ej.error||JSON.stringify(ej);}catch(x){errBody=await r.text();}throw new Error(r.status+': '+errBody);}
2543
2727
  openSkillDetail(currentSkillId);
2544
2728
  loadSkills();
2545
2729
  }catch(e){
2546
- alert('Error: '+e.message);
2730
+ toast('Error: '+e.message,'error');
2731
+ }
2732
+ }
2733
+
2734
+ async function toggleSkillPublic(id,setPublic){
2735
+ const newVis=setPublic?'public':'private';
2736
+ try{
2737
+ const r=await fetch('/api/skill/'+id+'/visibility',{method:'PUT',headers:{'Content-Type':'application/json'},body:JSON.stringify({visibility:newVis})});
2738
+ if(!r.ok){var errBody='';try{var ej=await r.json();errBody=ej.error||JSON.stringify(ej);}catch(x){errBody=await r.text();}throw new Error(r.status+': '+errBody);}
2739
+ toast(setPublic?t('toast.setPublic'):t('toast.setPrivate'),'success');
2740
+ loadSkills();
2741
+ }catch(e){
2742
+ toast('Error: '+e.message,'error');
2547
2743
  }
2548
2744
  }
2549
2745
 
@@ -2587,7 +2783,37 @@ async function loadConfig(){
2587
2783
  }
2588
2784
  }
2589
2785
 
2786
+ var _providerDefaults={
2787
+ siliconflow:{endpoint:'https://api.siliconflow.cn/v1',embModel:'BAAI/bge-m3',chatModel:'Qwen/Qwen2.5-7B-Instruct'},
2788
+ openai:{endpoint:'https://api.openai.com/v1',embModel:'text-embedding-3-small',chatModel:'gpt-4o-mini'},
2789
+ anthropic:{endpoint:'https://api.anthropic.com/v1/messages',chatModel:'claude-3-haiku-20240307'},
2790
+ cohere:{endpoint:'https://api.cohere.com/v2',embModel:'embed-english-v3.0'},
2791
+ mistral:{endpoint:'https://api.mistral.ai/v1',embModel:'mistral-embed'},
2792
+ voyage:{endpoint:'https://api.voyageai.com/v1',embModel:'voyage-3'},
2793
+ gemini:{endpoint:'',embModel:'text-embedding-004',chatModel:'gemini-2.0-flash'},
2794
+ zhipu:{endpoint:'https://open.bigmodel.cn/api/paas/v4',embModel:'embedding-3',chatModel:'glm-4-flash'},
2795
+ deepseek:{endpoint:'https://api.deepseek.com/v1',chatModel:'deepseek-chat'},
2796
+ bailian:{endpoint:'https://dashscope.aliyuncs.com/compatible-mode/v1',embModel:'text-embedding-v3',chatModel:'qwen-max'},
2797
+ moonshot:{endpoint:'https://api.moonshot.cn/v1',chatModel:'moonshot-v1-8k'}
2798
+ };
2799
+ function onProviderChange(section){
2800
+ var map={embedding:['cfgEmbEndpoint','cfgEmbModel','emb'],summarizer:['cfgSumEndpoint','cfgSumModel','chat'],skill:['cfgSkillEndpoint','cfgSkillModel','chat']};
2801
+ var m=map[section];if(!m)return;
2802
+ var sel=document.getElementById(section==='embedding'?'cfgEmbProvider':section==='summarizer'?'cfgSumProvider':'cfgSkillProvider');
2803
+ var pv=sel.value;
2804
+ var def=_providerDefaults[pv];
2805
+ if(!def)return;
2806
+ var epEl=document.getElementById(m[0]);
2807
+ var mdEl=document.getElementById(m[1]);
2808
+ if(def.endpoint&&!epEl.value.trim()) epEl.value=def.endpoint;
2809
+ if(m[2]==='emb'&&def.embModel&&!mdEl.value.trim()) mdEl.value=def.embModel;
2810
+ if(m[2]==='chat'&&def.chatModel&&!mdEl.value.trim()) mdEl.value=def.chatModel;
2811
+ }
2812
+
2590
2813
  async function saveConfig(){
2814
+ var saveBtn=document.querySelector('.settings-actions .btn-primary');
2815
+ saveBtn.disabled=true;saveBtn.textContent=t('settings.test.loading');
2816
+
2591
2817
  const cfg={};
2592
2818
  const embP=document.getElementById('cfgEmbProvider').value;
2593
2819
  if(embP){
@@ -2597,11 +2823,15 @@ async function saveConfig(){
2597
2823
  const k=document.getElementById('cfgEmbApiKey').value.trim();if(k) cfg.embedding.apiKey=k;
2598
2824
  }
2599
2825
  const sumP=document.getElementById('cfgSumProvider').value;
2600
- if(sumP){
2826
+ const sumModel=document.getElementById('cfgSumModel').value.trim();
2827
+ const sumEndpoint=document.getElementById('cfgSumEndpoint').value.trim();
2828
+ const sumApiKey=document.getElementById('cfgSumApiKey').value.trim();
2829
+ var hasSumConfig=!!(sumModel||sumEndpoint||sumApiKey);
2830
+ if(hasSumConfig&&sumP){
2601
2831
  cfg.summarizer={provider:sumP};
2602
- const v=document.getElementById('cfgSumModel').value.trim();if(v) cfg.summarizer.model=v;
2603
- const e=document.getElementById('cfgSumEndpoint').value.trim();if(e) cfg.summarizer.endpoint=e;
2604
- const k=document.getElementById('cfgSumApiKey').value.trim();if(k) cfg.summarizer.apiKey=k;
2832
+ if(sumModel) cfg.summarizer.model=sumModel;
2833
+ if(sumEndpoint) cfg.summarizer.endpoint=sumEndpoint;
2834
+ if(sumApiKey) cfg.summarizer.apiKey=sumApiKey;
2605
2835
  const tp=document.getElementById('cfgSumTemp').value.trim();if(tp!=='') cfg.summarizer.temperature=Number(tp);
2606
2836
  }
2607
2837
  cfg.skillEvolution={
@@ -2612,29 +2842,118 @@ async function saveConfig(){
2612
2842
  const mk=document.getElementById('cfgSkillMinChunks').value.trim();if(mk) cfg.skillEvolution.minChunksForEval=Number(mk);
2613
2843
 
2614
2844
  const skP=document.getElementById('cfgSkillProvider').value;
2615
- if(skP){
2845
+ const skModel=document.getElementById('cfgSkillModel').value.trim();
2846
+ const skEndpoint=document.getElementById('cfgSkillEndpoint').value.trim();
2847
+ const skApiKey=document.getElementById('cfgSkillApiKey').value.trim();
2848
+ var hasSkillConfig=!!(skP&&(skModel||skEndpoint||skApiKey));
2849
+ if(hasSkillConfig){
2616
2850
  cfg.skillEvolution.summarizer={provider:skP};
2617
- const sv=document.getElementById('cfgSkillModel').value.trim();if(sv) cfg.skillEvolution.summarizer.model=sv;
2618
- const se=document.getElementById('cfgSkillEndpoint').value.trim();if(se) cfg.skillEvolution.summarizer.endpoint=se;
2619
- const sk=document.getElementById('cfgSkillApiKey').value.trim();if(sk) cfg.skillEvolution.summarizer.apiKey=sk;
2851
+ if(skModel) cfg.skillEvolution.summarizer.model=skModel;
2852
+ if(skEndpoint) cfg.skillEvolution.summarizer.endpoint=skEndpoint;
2853
+ if(skApiKey) cfg.skillEvolution.summarizer.apiKey=skApiKey;
2620
2854
  }
2621
2855
 
2622
2856
  const vp=document.getElementById('cfgViewerPort').value.trim();
2623
2857
  if(vp) cfg.viewerPort=Number(vp);
2858
+ cfg.telemetry={enabled:document.getElementById('cfgTelemetryEnabled').checked};
2624
2859
 
2625
- cfg.telemetry={
2626
- enabled:document.getElementById('cfgTelemetryEnabled').checked
2627
- };
2860
+ function done(){saveBtn.disabled=false;saveBtn.textContent=t('settings.save');}
2861
+
2862
+ // 1) Embedding model is required
2863
+ if(!embP||embP===''){done();toast(t('settings.save.emb.required'),'error');return;}
2864
+
2865
+ // 2) Test embedding
2866
+ try{
2867
+ var er=await fetch('/api/test-model',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({type:'embedding',provider:cfg.embedding.provider,model:cfg.embedding.model||'',endpoint:cfg.embedding.endpoint||'',apiKey:cfg.embedding.apiKey||''})});
2868
+ var ed=await er.json();
2869
+ if(!ed.ok){done();toast(t('settings.save.emb.fail')+': '+ed.error,'error');document.getElementById('testEmbResult').className='test-result fail';document.getElementById('testEmbResult').innerHTML='\\u274C '+ed.error;return;}
2870
+ document.getElementById('testEmbResult').className='test-result ok';document.getElementById('testEmbResult').innerHTML='\\u2705 '+t('settings.test.ok');
2871
+ }catch(e){done();toast(t('settings.save.emb.fail')+': '+e.message,'error');return;}
2872
+
2873
+ // 3) Test summarizer if user filled it
2874
+ if(hasSumConfig&&cfg.summarizer){
2875
+ try{
2876
+ var sr=await fetch('/api/test-model',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({type:'summarizer',provider:cfg.summarizer.provider,model:cfg.summarizer.model||'',endpoint:cfg.summarizer.endpoint||'',apiKey:cfg.summarizer.apiKey||''})});
2877
+ var sd=await sr.json();
2878
+ if(!sd.ok){done();toast(t('settings.save.sum.fail')+': '+sd.error,'error');document.getElementById('testSumResult').className='test-result fail';document.getElementById('testSumResult').innerHTML='\\u274C '+sd.error;return;}
2879
+ document.getElementById('testSumResult').className='test-result ok';document.getElementById('testSumResult').innerHTML='\\u2705 '+t('settings.test.ok');
2880
+ }catch(e){done();toast(t('settings.save.sum.fail')+': '+e.message,'error');return;}
2881
+ }
2882
+
2883
+ // 4) Test skill model if user filled it
2884
+ if(hasSkillConfig&&cfg.skillEvolution.summarizer){
2885
+ try{
2886
+ var kr=await fetch('/api/test-model',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({type:'summarizer',provider:cfg.skillEvolution.summarizer.provider,model:cfg.skillEvolution.summarizer.model||'',endpoint:cfg.skillEvolution.summarizer.endpoint||'',apiKey:cfg.skillEvolution.summarizer.apiKey||''})});
2887
+ var kd=await kr.json();
2888
+ if(!kd.ok){done();toast(t('settings.save.skill.fail')+': '+kd.error,'error');document.getElementById('testSkillResult').className='test-result fail';document.getElementById('testSkillResult').innerHTML='\\u274C '+kd.error;return;}
2889
+ document.getElementById('testSkillResult').className='test-result ok';document.getElementById('testSkillResult').innerHTML='\\u2705 '+t('settings.test.ok');
2890
+ }catch(e){done();toast(t('settings.save.skill.fail')+': '+e.message,'error');return;}
2891
+ }
2628
2892
 
2893
+ // 5) If summarizer or skill model not configured, check OpenClaw fallback and confirm
2894
+ if(!hasSumConfig||!hasSkillConfig){
2895
+ try{
2896
+ var fr=await fetch('/api/fallback-model');
2897
+ var fb=await fr.json();
2898
+ var msgs=[];
2899
+ if(!hasSumConfig){msgs.push(t('settings.save.sum.fallback'));}
2900
+ if(!hasSkillConfig){msgs.push(t('settings.save.skill.fallback'));}
2901
+ var fbInfo=fb.available?(fb.model+' ('+fb.baseUrl+')'):t('settings.save.fallback.none');
2902
+ var confirmMsg=msgs.join('\\n')+'\\n\\n'+t('settings.save.fallback.model')+fbInfo+'\\n\\n'+t('settings.save.fallback.confirm');
2903
+ if(!confirm(confirmMsg)){done();return;}
2904
+ }catch(e){}
2905
+ }
2906
+
2907
+ // 6) All tests passed, save
2629
2908
  try{
2630
2909
  const r=await fetch('/api/config',{method:'PUT',headers:{'Content-Type':'application/json'},body:JSON.stringify(cfg)});
2631
2910
  if(!r.ok) throw new Error(await r.text());
2632
2911
  const el=document.getElementById('settingsSaved');
2633
2912
  el.classList.add('show');
2634
2913
  setTimeout(()=>el.classList.remove('show'),2500);
2914
+ toast(t('settings.saved'),'success');
2635
2915
  }catch(e){
2636
- showToast(t('settings.save.fail')+': '+e.message,'error');
2916
+ toast(t('settings.save.fail')+': '+e.message,'error');
2917
+ }finally{done();}
2918
+ }
2919
+
2920
+ async function testModel(type){
2921
+ var ids={embedding:['Emb','cfgEmbProvider','cfgEmbModel','cfgEmbEndpoint','cfgEmbApiKey'],summarizer:['Sum','cfgSumProvider','cfgSumModel','cfgSumEndpoint','cfgSumApiKey'],skill:['Skill','cfgSkillProvider','cfgSkillModel','cfgSkillEndpoint','cfgSkillApiKey']};
2922
+ var c=ids[type];if(!c)return;
2923
+ var resultEl=document.getElementById('test'+c[0]+'Result');
2924
+ var btn=document.getElementById('test'+c[0]+'Btn');
2925
+ var provider=document.getElementById(c[1]).value;
2926
+ var model=document.getElementById(c[2]).value.trim();
2927
+ var endpoint=document.getElementById(c[3]).value.trim();
2928
+ var apiKey=document.getElementById(c[4]).value.trim();
2929
+ if(!provider||(provider!=='local'&&!model)){
2930
+ resultEl.className='test-result fail';
2931
+ resultEl.innerHTML='\\u274C '+t('settings.test.fail')+'<div style="margin-top:4px;font-size:11px;color:var(--text-muted)">Provider and Model are required</div>';
2932
+ return;
2933
+ }
2934
+ if(provider!=='local'&&!apiKey){
2935
+ resultEl.className='test-result fail';
2936
+ resultEl.innerHTML='\\u274C '+t('settings.test.fail')+'<div style="margin-top:4px;font-size:11px;color:var(--text-muted)">API Key is required</div>';
2937
+ return;
2637
2938
  }
2939
+ resultEl.className='test-result loading';resultEl.textContent=t('settings.test.loading');
2940
+ btn.disabled=true;
2941
+ try{
2942
+ var body={type:type,provider:provider,model:model,endpoint:endpoint,apiKey:apiKey};
2943
+ var r=await fetch('/api/test-model',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(body)});
2944
+ var d=await r.json();
2945
+ if(d.ok){
2946
+ resultEl.className='test-result ok';
2947
+ resultEl.innerHTML='\\u2705 '+t('settings.test.ok')+'<div style="margin-top:4px;font-size:11px;color:var(--text-muted)">'+esc(d.detail||'')+'</div>';
2948
+ }else{
2949
+ var errMsg=d.error||'Unknown error';
2950
+ resultEl.className='test-result fail';
2951
+ resultEl.innerHTML='\\u274C '+t('settings.test.fail')+'<div style="margin-top:6px;font-size:11px;padding:8px 10px;background:rgba(239,68,68,.06);border:1px solid rgba(239,68,68,.15);border-radius:6px;white-space:pre-wrap;word-break:break-all;max-height:120px;overflow-y:auto;font-family:SF Mono,Monaco,Consolas,monospace">'+esc(errMsg)+'</div>';
2952
+ }
2953
+ }catch(e){
2954
+ resultEl.className='test-result fail';
2955
+ resultEl.innerHTML='\\u274C '+t('settings.test.fail')+'<div style="margin-top:6px;font-size:11px;padding:8px 10px;background:rgba(239,68,68,.06);border:1px solid rgba(239,68,68,.15);border-radius:6px;white-space:pre-wrap;word-break:break-all">'+esc(e.message)+'</div>';
2956
+ }finally{btn.disabled=false;}
2638
2957
  }
2639
2958
 
2640
2959
  function renderSkillMarkdown(md){
@@ -2668,6 +2987,19 @@ function closeSkillDetail(event){
2668
2987
  document.getElementById('skillDetailOverlay').classList.remove('show');
2669
2988
  }
2670
2989
 
2990
+ async function deleteSkill(skillId){
2991
+ if(!confirm(t('skill.delete.confirm'))) return;
2992
+ try{
2993
+ const r=await fetch('/api/skill/'+skillId,{method:'DELETE'});
2994
+ const d=await r.json();
2995
+ if(!r.ok) throw new Error(d.error||'unknown');
2996
+ closeSkillDetail();
2997
+ document.getElementById('skillDetailOverlay').classList.remove('show');
2998
+ loadSkills();
2999
+ }catch(e){ alert(t('skill.delete.error')+e.message); }
3000
+ }
3001
+
3002
+
2671
3003
  function formatDuration(ms){
2672
3004
  const s=Math.floor(ms/1000);
2673
3005
  if(s<60) return s+'s';
@@ -3371,6 +3703,19 @@ async function clearAll(){
3371
3703
  let migrateScanData=null;
3372
3704
  let migrateStats={stored:0,skipped:0,merged:0,errors:0};
3373
3705
 
3706
+ (function(){
3707
+ const sel=document.getElementById('migrateConcurrency');
3708
+ if(sel) sel.addEventListener('change',function(){
3709
+ const w=document.getElementById('migrateConcurrencyWarn');
3710
+ if(w) w.style.display=parseInt(this.value,10)>1?'block':'none';
3711
+ });
3712
+ const ppSel=document.getElementById('ppConcurrency');
3713
+ if(ppSel) ppSel.addEventListener('change',function(){
3714
+ const w=document.getElementById('ppConcurrencyWarn');
3715
+ if(w) w.style.display=parseInt(this.value,10)>1?'block':'none';
3716
+ });
3717
+ })();
3718
+
3374
3719
  async function migrateScan(){
3375
3720
  const btn=document.getElementById('migrateScanBtn');
3376
3721
  btn.disabled=true;
@@ -3406,6 +3751,7 @@ async function migrateScan(){
3406
3751
 
3407
3752
  if(d.totalItems>0 && d.configReady){
3408
3753
  document.getElementById('migrateStartBtn').style.display='inline-flex';
3754
+ document.getElementById('migrateConcurrencyRow').style.display='inline-flex';
3409
3755
  }
3410
3756
 
3411
3757
  if(d.totalItems===0){
@@ -3427,10 +3773,15 @@ function migrateStart(){
3427
3773
  if(!migrateScanData||!migrateScanData.configReady)return;
3428
3774
  if(!confirm(t('migrate.start')+'?'))return;
3429
3775
 
3776
+ const concSel=document.getElementById('migrateConcurrency');
3777
+ const concurrency=concSel?parseInt(concSel.value,10)||1:1;
3778
+
3430
3779
  window._migrateRunning=true;
3431
3780
  _migrateStatusChecked=false;
3432
3781
  document.getElementById('migrateStartBtn').style.display='none';
3433
3782
  document.getElementById('migrateScanBtn').disabled=true;
3783
+ document.getElementById('migrateConcurrencyRow').style.display='none';
3784
+ document.getElementById('migrateConcurrencyWarn').style.display='none';
3434
3785
  document.getElementById('migrateProgress').style.display='block';
3435
3786
  document.getElementById('migrateLiveLog').innerHTML='';
3436
3787
  migrateStats={stored:0,skipped:0,merged:0,errors:0};
@@ -3440,7 +3791,7 @@ function migrateStart(){
3440
3791
  document.getElementById('migrateBar').style.width='0%';
3441
3792
  document.getElementById('migrateBar').style.background='linear-gradient(90deg,#6366f1,#8b5cf6)';
3442
3793
  document.getElementById('migrateCounter').textContent='';
3443
- const body=JSON.stringify({sources:['sqlite','sessions']});
3794
+ const body=JSON.stringify({sources:['sqlite','sessions'],concurrency});
3444
3795
  connectMigrateSSE('/api/migrate/start','POST',body);
3445
3796
  }
3446
3797
 
@@ -3645,6 +3996,9 @@ function ppStart(){
3645
3996
  var enableSkills=document.getElementById('ppEnableSkills').checked;
3646
3997
  if(!enableTasks&&!enableSkills){toast(t('pp.select.warn'),'error');return;}
3647
3998
 
3999
+ var ppConcSel=document.getElementById('ppConcurrency');
4000
+ var ppConcurrency=ppConcSel?parseInt(ppConcSel.value,10)||1:1;
4001
+
3648
4002
  window._ppRunning=true;
3649
4003
  _ppSSEConnected=false;
3650
4004
  ppStats={tasks:0,skills:0,errors:0,skipped:0};
@@ -3661,7 +4015,7 @@ function ppStart(){
3661
4015
  document.getElementById('ppLiveLog').innerHTML='';
3662
4016
  updatePPStats();
3663
4017
 
3664
- var body=JSON.stringify({enableTasks:enableTasks,enableSkills:enableSkills});
4018
+ var body=JSON.stringify({enableTasks:enableTasks,enableSkills:enableSkills,concurrency:ppConcurrency});
3665
4019
  fetch('/api/migrate/postprocess',{method:'POST',headers:{'Content-Type':'application/json'},body:body})
3666
4020
  .then(function(r){
3667
4021
  if(!r.ok){