@aiyiran/myclaw 1.1.24 → 1.1.26

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 (83) hide show
  1. package/.claude/settings.local.json +25 -1
  2. package/assets/myclaw-artifacts.js +1070 -126
  3. package/assets/myclaw-inject.js +913 -121
  4. package/delete_agents.js +268 -0
  5. package/index.js +361 -20
  6. package/package.json +1 -1
  7. package/patches/patch-manifest.json +10 -0
  8. package/server/sync_workspace.py +444 -14
  9. package/skills/yiran-course-template-pipeline/README.md +127 -0
  10. package/skills/yiran-course-template-pipeline/SKILL.md +65 -0
  11. package/skills/yiran-course-template-pipeline/assets/a100-teacher.example.html +66 -0
  12. package/skills/yiran-course-template-pipeline/assets/student-template.html +64 -0
  13. package/skills/yiran-course-template-pipeline/assets/teacher-portrait-demo.html +105 -0
  14. package/skills/yiran-course-template-pipeline/assets/teacher-task-view.html +110 -0
  15. package/skills/yiran-course-template-pipeline/prompts//351/230/266/346/256/2651-demo/347/224/237/346/210/220.md +92 -0
  16. package/skills/yiran-course-template-pipeline/prompts//351/230/266/346/256/2652-student/347/224/237/346/210/220.md +115 -0
  17. package/skills/yiran-course-template-pipeline/prompts//351/230/266/346/256/2653-teacher/347/224/237/346/210/220.md +131 -0
  18. package/skills/yiran-course-template-pipeline/prompts//351/230/266/346/256/2654-/346/211/223/345/214/205/350/220/275/347/233/230.md +77 -0
  19. package/skills/yiran-course-template-pipeline/references/student-example.json +38 -0
  20. package/skills/yiran-course-template-pipeline/references/student-fields.md +195 -0
  21. package/skills/yiran-course-template-pipeline/references/student-scaffold.json +34 -0
  22. package/skills/yiran-course-template-pipeline/references/teacher-fields.md +265 -0
  23. package/skills/yiran-course-template-pipeline/references/teacher-scaffold.json +25 -0
  24. package/skills/yiran-course-template-pipeline/scripts/build_template_views.py +125 -0
  25. package/skills/yiran-course-template-pipeline/scripts/move_template_task.py +59 -0
  26. package/skills/yiran-course-template-pipeline/scripts/render_student_page.py +52 -0
  27. package/skills/yiran-course-template-pipeline/scripts/render_teacher_view.py +108 -0
  28. package/skills/yiran-playground-template-use/SKILL.md +105 -0
  29. package/skills/yiran-playground-template-use/prompts/remix-handoff.txt +11 -0
  30. package/skills/yiran-playground-template-use/scripts/build_template_index.py +103 -0
  31. package/skills/yiran-playground-template-use/scripts/deploy_template.py +34 -0
  32. package/skills/yiran-playground-template-use/scripts/deploy_to_workspace.py +211 -0
  33. package/skills/yiran-playground-template-use/scripts/prepare_playgrounds.py +39 -0
  34. package/skills/yiran-playground-template-use/scripts/query_template.py +171 -0
  35. package/skills/yiran-playground-template-use/scripts/run_playgrounds_flow.py +44 -0
  36. package/skills/yiran-playground-template-use/scripts/start_tui_handoff.py +77 -0
  37. package/skills/yiran-playground-template-use/search-agent-prompt.md +39 -0
  38. package/skills/yiran-playground-template-use/template-index.json +136 -0
  39. package/skills/yiran-playground-template-use/template-index.md +38 -0
  40. package/skills/yiran-playground-template-use/templates/a100_/347/273/231/344/276/235/347/204/266/350/200/201/345/270/210/350/256/276/350/256/241/344/270/200/344/270/252AI/347/224/273/345/203/217/__demo__.html +140 -0
  41. package/skills/yiran-playground-template-use/templates/a100_/347/273/231/344/276/235/347/204/266/350/200/201/345/270/210/350/256/276/350/256/241/344/270/200/344/270/252AI/347/224/273/345/203/217/__student-view__.html +64 -0
  42. package/skills/yiran-playground-template-use/templates/a100_/347/273/231/344/276/235/347/204/266/350/200/201/345/270/210/350/256/276/350/256/241/344/270/200/344/270/252AI/347/224/273/345/203/217/__student__.json +38 -0
  43. package/skills/yiran-playground-template-use/templates/a100_/347/273/231/344/276/235/347/204/266/350/200/201/345/270/210/350/256/276/350/256/241/344/270/200/344/270/252AI/347/224/273/345/203/217/__teacher-view__.html +52 -0
  44. package/skills/yiran-playground-template-use/templates/a100_/347/273/231/344/276/235/347/204/266/350/200/201/345/270/210/350/256/276/350/256/241/344/270/200/344/270/252AI/347/224/273/345/203/217/__teacher__.json +36 -0
  45. package/skills/yiran-playground-template-use/templates/a100_/347/273/231/344/276/235/347/204/266/350/200/201/345/270/210/350/256/276/350/256/241/344/270/200/344/270/252AI/347/224/273/345/203/217/index.html +61 -0
  46. package/skills/yiran-playground-template-use/templates/a101_/345/201/2323/345/274/240/345/220/214/344/270/273/351/242/230/345/233/276/347/211/207/__demo__.html +131 -0
  47. package/skills/yiran-playground-template-use/templates/a101_/345/201/2323/345/274/240/345/220/214/344/270/273/351/242/230/345/233/276/347/211/207/__student-view__.html +64 -0
  48. package/skills/yiran-playground-template-use/templates/a101_/345/201/2323/345/274/240/345/220/214/344/270/273/351/242/230/345/233/276/347/211/207/__student__.json +34 -0
  49. package/skills/yiran-playground-template-use/templates/a101_/345/201/2323/345/274/240/345/220/214/344/270/273/351/242/230/345/233/276/347/211/207/__teacher-view__.html +52 -0
  50. package/skills/yiran-playground-template-use/templates/a101_/345/201/2323/345/274/240/345/220/214/344/270/273/351/242/230/345/233/276/347/211/207/__teacher__.json +34 -0
  51. package/skills/yiran-playground-template-use/templates/a103_/345/201/232/344/270/200/344/270/252/344/273/213/347/273/215/351/241/265/351/235/242/__demo__.html +77 -0
  52. package/skills/yiran-playground-template-use/templates/a103_/345/201/232/344/270/200/344/270/252/344/273/213/347/273/215/351/241/265/351/235/242/__student-view__.html +64 -0
  53. package/skills/yiran-playground-template-use/templates/a103_/345/201/232/344/270/200/344/270/252/344/273/213/347/273/215/351/241/265/351/235/242/__student__.json +38 -0
  54. package/skills/yiran-playground-template-use/templates/a103_/345/201/232/344/270/200/344/270/252/344/273/213/347/273/215/351/241/265/351/235/242/__teacher-view__.html +52 -0
  55. package/skills/yiran-playground-template-use/templates/a103_/345/201/232/344/270/200/344/270/252/344/273/213/347/273/215/351/241/265/351/235/242/__teacher__.json +34 -0
  56. package/skills/yiran-playground-template-use/templates/b100_/345/201/232/344/270/200/344/270/252/346/214/211/351/222/256/351/241/265/351/235/242/__demo__.html +162 -0
  57. package/skills/yiran-playground-template-use/templates/b100_/345/201/232/344/270/200/344/270/252/346/214/211/351/222/256/351/241/265/351/235/242/__student-view__.html +64 -0
  58. package/skills/yiran-playground-template-use/templates/b100_/345/201/232/344/270/200/344/270/252/346/214/211/351/222/256/351/241/265/351/235/242/__student__.json +34 -0
  59. package/skills/yiran-playground-template-use/templates/b100_/345/201/232/344/270/200/344/270/252/346/214/211/351/222/256/351/241/265/351/235/242/__teacher-view__.html +52 -0
  60. package/skills/yiran-playground-template-use/templates/b100_/345/201/232/344/270/200/344/270/252/346/214/211/351/222/256/351/241/265/351/235/242/__teacher__.json +34 -0
  61. package/skills/yiran-playground-template-use/templates/c100_/347/273/231/345/260/217/347/273/204/344/275/234/345/223/201/345/201/232/344/270/200/346/254/241/345/260/217/344/277/256/346/224/271/__demo__.html +180 -0
  62. package/skills/yiran-playground-template-use/templates/c100_/347/273/231/345/260/217/347/273/204/344/275/234/345/223/201/345/201/232/344/270/200/346/254/241/345/260/217/344/277/256/346/224/271/__student-view__.html +64 -0
  63. package/skills/yiran-playground-template-use/templates/c100_/347/273/231/345/260/217/347/273/204/344/275/234/345/223/201/345/201/232/344/270/200/346/254/241/345/260/217/344/277/256/346/224/271/__student__.json +38 -0
  64. package/skills/yiran-playground-template-use/templates/c100_/347/273/231/345/260/217/347/273/204/344/275/234/345/223/201/345/201/232/344/270/200/346/254/241/345/260/217/344/277/256/346/224/271/__teacher-view__.html +52 -0
  65. package/skills/yiran-playground-template-use/templates/c100_/347/273/231/345/260/217/347/273/204/344/275/234/345/223/201/345/201/232/344/270/200/346/254/241/345/260/217/344/277/256/346/224/271/__teacher__.json +41 -0
  66. package/skills/yiran-playground-template-use/templates/c100_/347/273/231/345/260/217/347/273/204/344/275/234/345/223/201/345/201/232/344/270/200/346/254/241/345/260/217/344/277/256/346/224/271/demo.html +180 -0
  67. package/skills/yiran-playground-template-use/templates/c100_/347/273/231/345/260/217/347/273/204/344/275/234/345/223/201/345/201/232/344/270/200/346/254/241/345/260/217/344/277/256/346/224/271/index.html +121 -0
  68. package/skills/yiran-playground-template-use/templates/c100_/347/273/231/345/260/217/347/273/204/344/275/234/345/223/201/345/201/232/344/270/200/346/254/241/345/260/217/344/277/256/346/224/271//345/260/217/347/273/204/345/220/211/347/245/245/347/211/251_26.png +0 -0
  69. package/skills/yiran-playground-template-use/templates/c100_/347/273/231/345/260/217/347/273/204/344/275/234/345/223/201/345/201/232/344/270/200/346/254/241/345/260/217/344/277/256/346/224/271//345/260/217/347/273/204/345/233/276/345/275/242/346/240/207/345/277/227_83.png +0 -0
  70. package/skills/yiran-playground-template-use/templates/c100_/347/273/231/345/260/217/347/273/204/344/275/234/345/223/201/345/201/232/344/270/200/346/254/241/345/260/217/344/277/256/346/224/271//347/217/255/347/272/247/345/260/217/347/273/204/345/276/275/347/253/240_47.png +0 -0
  71. package/skills/yiran-skill-media/SKILL.md +6 -15
  72. package/skills/yiran-skill-media/scripts/generate.py +47 -18
  73. package/skills/yiran-skill-media/scripts/generation_log.json +1 -56
  74. package/skills/yiran-skill-media/scripts/providers/__pycache__/__init__.cpython-311.pyc +0 -0
  75. package/skills/yiran-skill-media/scripts/providers/__pycache__/__init__.cpython-37.pyc +0 -0
  76. package/skills/yiran-skill-media/scripts/providers/__pycache__/jimeng_image.cpython-37.pyc +0 -0
  77. package/skills/yiran-skill-media/scripts/providers/__pycache__/jimeng_video.cpython-311.pyc +0 -0
  78. package/skills/yiran-skill-media/scripts/providers/__pycache__/jimeng_video.cpython-37.pyc +0 -0
  79. package/skills/yiran-skill-media/scripts/providers/__pycache__/minimax_image.cpython-37.pyc +0 -0
  80. package/skills/yiran-skill-media/scripts/providers/__pycache__/minimax_music.cpython-37.pyc +0 -0
  81. package/skills/yiran-skill-media/scripts/providers/__pycache__/minimax_video.cpython-311.pyc +0 -0
  82. package/skills/yiran-skill-media/scripts/providers/__pycache__/minimax_video.cpython-37.pyc +0 -0
  83. package/skills/yiran-skill-media/scripts/providers/__pycache__/vapi_image.cpython-37.pyc +0 -0
@@ -31,9 +31,10 @@
31
31
  var cachedData = null;
32
32
  var pollTimer = null;
33
33
  var lastKnownClawName = null; // 本地环境下从 API 获取,remote 环境每次从 hostname 实时派生
34
- var MYCLAW_API_PORT = 8080;
34
+ var _preloadCache = {}; // url → Image 对象,防止重复预加载
35
+ var MYCLAW_API_PORT = 18800;
35
36
  // 远程环境(公网域名):nginx 已将 /sync 反代到 127.0.0.1:8080,直接同域访问
36
- // 本地环境:直接访问 127.0.0.1:8080
37
+ // 本地环境:直接访问 127.0.0.1:18800
37
38
  var MYCLAW_API_BASE = (function () {
38
39
  var h = window.location.hostname;
39
40
  var isRemote = h === 'claw.yiranlaoshi.com' || h.endsWith('.kekouen.cn');
@@ -198,47 +199,43 @@
198
199
  'user-select: none',
199
200
  'flex-shrink: 0',
200
201
  ].join(';');
201
- header.innerHTML = '<span>\uD83C\uDFA8 \u5B66\u751F\u4F5C\u54C1</span>';
202
+ var titleSpan = document.createElement('span');
203
+ titleSpan.textContent = '🎨 学生作品';
204
+ titleSpan.style.cssText = 'cursor:pointer;border-radius:3px;padding:1px 4px;transition:background 0.15s;';
205
+ titleSpan.title = '查看统计信息';
206
+ titleSpan.onmouseenter = function () { titleSpan.style.background = 'rgba(255,255,255,0.1)'; };
207
+ titleSpan.onmouseleave = function () { titleSpan.style.background = 'none'; };
208
+ titleSpan.onclick = function () { openStatsModal(); };
209
+ header.appendChild(titleSpan);
210
+
211
+ // 搜索框(或获取已存在的搜索框)
212
+ var searchBoxHeader = document.querySelector('#myclaw-search-box');
213
+ if (!searchBoxHeader) {
214
+ searchBoxHeader = document.createElement('input');
215
+ searchBoxHeader.id = 'myclaw-search-box';
216
+ searchBoxHeader.placeholder = '搜索...';
217
+ searchBoxHeader.style.cssText = 'max-width:120px;height:22px;padding:2px 8px;background:rgba(255,255,255,0.05);border:1px solid rgba(255,255,255,0.1);border-radius:4px;color:#cdd6f4;font-size:11px;font-family:monospace;box-sizing:border-box;';
218
+ searchBoxHeader.onkeyup = function() {
219
+ applySearch();
220
+ };
221
+ header.appendChild(searchBoxHeader);
222
+ }
223
+
224
+ // 搜索过滤函数
225
+ window.applySearch = function() {
226
+ var searchBox = document.querySelector('#myclaw-search-box');
227
+ if (!searchBox || !searchBox.value) return;
228
+ var keyword = searchBox.value.toLowerCase();
229
+ var items = document.querySelectorAll('[data-artifact-item]');
230
+ items.forEach(function(item) {
231
+ var fileName = item.getAttribute('data-artifact-name') || '';
232
+ item.style.display = (keyword === '' || fileName.toLowerCase().indexOf(keyword) !== -1) ? 'flex' : 'none';
233
+ });
234
+ };
202
235
 
203
236
  var headerRight = document.createElement('span');
204
237
  headerRight.style.cssText = 'display:flex;align-items:center;gap:10px;';
205
238
 
206
- // 刷新按钮(全量同步)
207
- var resyncBtn = document.createElement('span');
208
- resyncBtn.textContent = '\uD83D\uDD04';
209
- resyncBtn.title = '\u5237\u65B0\u540C\u6B65';
210
- resyncBtn.style.cssText = 'cursor:pointer;padding:2px 6px;border-radius:3px;font-size:13px;transition:all 0.15s;';
211
- resyncBtn.onmouseenter = function () { resyncBtn.style.background = 'rgba(255,255,255,0.1)'; };
212
- resyncBtn.onmouseleave = function () { if (!resyncBtn._syncing) resyncBtn.style.background = 'none'; };
213
- resyncBtn.onclick = function () {
214
- if (resyncBtn._syncing) return;
215
- resyncBtn._syncing = true;
216
- resyncBtn.style.animation = 'myclaw-spin 1s linear infinite';
217
- resyncBtn.style.background = 'rgba(255,255,255,0.1)';
218
-
219
- var wsPrefix = getWorkspaceId();
220
- fetch(MYCLAW_API_BASE + '/api/resync?workspace=' + encodeURIComponent(wsPrefix), { method: 'POST' })
221
- .then(function (res) { return res.json(); })
222
- .then(function (data) {
223
- if (data.ok) {
224
- console.log('[myclaw-artifacts] \u2705 \u540C\u6B65\u5B8C\u6210');
225
- var contentEl = document.querySelector('#myclaw-artifacts-content');
226
- if (contentEl) fetchArtifacts(contentEl);
227
- } else {
228
- console.error('[myclaw-artifacts] \u274C \u540C\u6B65\u5931\u8D25:', data.error);
229
- }
230
- })
231
- .catch(function (err) {
232
- console.error('[myclaw-artifacts] \u274C \u540C\u6B65\u8BF7\u6C42\u5931\u8D25:', err.message);
233
- })
234
- .then(function () {
235
- resyncBtn._syncing = false;
236
- resyncBtn.style.animation = '';
237
- resyncBtn.style.background = 'none';
238
- });
239
- };
240
- headerRight.appendChild(resyncBtn);
241
-
242
239
  // 展示间跳转(发布按钮左边)
243
240
  var showcaseLink = document.createElement('a');
244
241
  var _scEnv = detectEnvironment();
@@ -283,31 +280,36 @@
283
280
 
284
281
  content.innerHTML = '<div style="text-align:center;padding:32px;color:#888;">加载中...</div>';
285
282
 
286
- // Fork 按钮底部栏
283
+ // 底部栏:历史 + Fork
287
284
  var panelFooter = document.createElement('div');
288
- panelFooter.style.cssText = [
289
- 'padding: 8px 10px',
290
- 'background: #252536',
291
- 'border-top: 1px solid #3d3d5c',
292
- 'flex-shrink: 0',
293
- ].join(';');
285
+ panelFooter.style.cssText = 'padding:8px 10px;background:#252536;border-top:1px solid #3d3d5c;flex-shrink:0;display:flex;gap:8px;';
286
+
287
+ var footerBtnStyle = 'cursor:pointer;flex:1;padding:6px 10px;border-radius:4px;font-size:12px;font-family:monospace;color:#cdd6f4;background:rgba(255,255,255,0.06);text-align:center;transition:background 0.15s;';
288
+
289
+ var historyFooterBtn = document.createElement('div');
290
+ historyFooterBtn.textContent = '🕐 历史版本';
291
+ historyFooterBtn.style.cssText = footerBtnStyle;
292
+ historyFooterBtn.onmouseenter = function () { historyFooterBtn.style.background = 'rgba(255,255,255,0.14)'; };
293
+ historyFooterBtn.onmouseleave = function () { historyFooterBtn.style.background = 'rgba(255,255,255,0.06)'; };
294
+ historyFooterBtn.onclick = function () { openHistoryModal(); };
295
+
294
296
  var forkPanelBtn = document.createElement('div');
295
- forkPanelBtn.textContent = '\uD83D\uDD00 fork\u4ED6\u4EBA\u4F5C\u54C1';
296
- forkPanelBtn.style.cssText = [
297
- 'cursor: pointer',
298
- 'padding: 6px 10px',
299
- 'border-radius: 4px',
300
- 'font-size: 12px',
301
- 'font-family: monospace',
302
- 'color: #cdd6f4',
303
- 'background: rgba(255,255,255,0.06)',
304
- 'text-align: center',
305
- 'transition: background 0.15s',
306
- ].join(';');
297
+ forkPanelBtn.textContent = '🔀 获取作品';
298
+ forkPanelBtn.style.cssText = footerBtnStyle;
307
299
  forkPanelBtn.onmouseenter = function () { forkPanelBtn.style.background = 'rgba(255,255,255,0.14)'; };
308
300
  forkPanelBtn.onmouseleave = function () { forkPanelBtn.style.background = 'rgba(255,255,255,0.06)'; };
309
301
  forkPanelBtn.onclick = function () { openForkModal(); };
302
+
303
+ var templateFooterBtn = document.createElement('div');
304
+ templateFooterBtn.textContent = '📋 模板';
305
+ templateFooterBtn.style.cssText = footerBtnStyle;
306
+ templateFooterBtn.onmouseenter = function () { templateFooterBtn.style.background = 'rgba(255,255,255,0.14)'; };
307
+ templateFooterBtn.onmouseleave = function () { templateFooterBtn.style.background = 'rgba(255,255,255,0.06)'; };
308
+ templateFooterBtn.onclick = function () { openTemplateModal(); };
309
+
310
+ panelFooter.appendChild(historyFooterBtn);
310
311
  panelFooter.appendChild(forkPanelBtn);
312
+ panelFooter.appendChild(templateFooterBtn);
311
313
 
312
314
  panel.appendChild(header);
313
315
  panel.appendChild(content);
@@ -360,6 +362,25 @@
360
362
  });
361
363
  }
362
364
 
365
+ function autoPreloadImages(data) {
366
+ if (!data || !data.assets) return;
367
+ data.assets.forEach(function (asset) {
368
+ if (!isImageAsset(asset)) return;
369
+ var url = buildPreviewUrl(data, asset.path);
370
+ if (!url || _preloadCache[url]) return;
371
+ var pre = new Image();
372
+ pre.onload = function () {
373
+ console.log('[preload] ✓ ' + asset.path);
374
+ };
375
+ pre.onerror = function () {
376
+ console.warn('[preload] ✗ ' + asset.path);
377
+ delete _preloadCache[url]; // 失败的下次可以重试
378
+ };
379
+ pre.src = url;
380
+ _preloadCache[url] = pre; // 占位,防止并发重复触发
381
+ });
382
+ }
383
+
363
384
  function fetchArtifacts(contentEl) {
364
385
  var wsPrefix = getWorkspaceId();
365
386
 
@@ -371,6 +392,7 @@
371
392
  })
372
393
  .then(function (data) {
373
394
  cachedData = data;
395
+ autoPreloadImages(data);
374
396
  if (!contentEl) return;
375
397
  if (!data || !data.assets || !data.assets.length) {
376
398
  contentEl.innerHTML = '<div style="text-align:center;padding:32px;color:#888;">暂无作品</div>';
@@ -413,12 +435,14 @@
413
435
  'flex-shrink: 0',
414
436
  ].join(';');
415
437
  tableHeader.innerHTML = [
416
- '<span style="flex-shrink:0;width:44px;"></span>',
417
- '<span style="flex-shrink:0;width:70px;">更新时间</span>',
438
+ '<span style="flex-shrink:0;width:52px;"></span>',
418
439
  '<span style="flex:1;">文件名</span>',
419
440
  ].join('');
420
441
  container.appendChild(tableHeader);
421
442
 
443
+ // 列表内容容器(可滚动)
444
+ var listContent = document.createElement('div');
445
+
422
446
  // 按 updated_at 从新到旧排序
423
447
  var sorted = data.assets.slice().sort(function (a, b) {
424
448
  var ta = a.updated_at || '';
@@ -426,6 +450,18 @@
426
450
  return tb.localeCompare(ta);
427
451
  });
428
452
 
453
+ // 黑名单:隐藏以下文件(__XXX__ 格式的内部文件),豁免 student-view.html
454
+ var ARTIFACT_BLACKLIST = [
455
+ '__demo__.html',
456
+ '__student__.json',
457
+ '__teacher__.json',
458
+ '__teacher-view__.html',
459
+ ];
460
+ sorted = sorted.filter(function (asset) {
461
+ var basename = (asset.path || asset.name || '').split('/').pop();
462
+ return ARTIFACT_BLACKLIST.indexOf(basename) === -1;
463
+ });
464
+
429
465
  sorted.forEach(function (asset, idx) {
430
466
  // 状态判断:基于 last_open 时间戳
431
467
  // - 无 last_open → [最新](从未点开过)
@@ -438,9 +474,11 @@
438
474
  var isUpdated = lastOpen && assetUpdated && new Date(assetUpdated) > new Date(lastOpen);
439
475
 
440
476
  var row = document.createElement('div');
477
+ row.setAttribute('data-artifact-item', '1');
478
+ row.setAttribute('data-artifact-name', asset.path || asset.name || '');
441
479
  row.style.cssText = [
442
480
  'display: flex',
443
- 'align-items: center',
481
+ 'align-items: flex-start',
444
482
  'padding: 8px 10px',
445
483
  'border-bottom: 1px solid rgba(255,255,255,0.05)',
446
484
  'cursor: pointer',
@@ -451,9 +489,9 @@
451
489
  row.onclick = function () {
452
490
  // 记录点开时间
453
491
  localStorage.setItem(lastOpenKey, new Date().toISOString());
454
- // 复制文件名到剪贴板
455
- var fileName = pathParts[pathParts.length - 1] || asset.name || '未命名';
456
- navigator.clipboard.writeText(fileName).then(function () {
492
+ // 复制路径到剪贴板
493
+ var displayName = asset.path || asset.name || '未命名';
494
+ navigator.clipboard.writeText(displayName).then(function () {
457
495
  var orig = nameSpan.textContent;
458
496
  nameSpan.textContent = '✓ 已复制';
459
497
  nameSpan.style.color = '#10b981';
@@ -465,26 +503,21 @@
465
503
  openPreviewModal(data, asset);
466
504
  };
467
505
 
468
- // 更新时间
469
- var time = document.createElement('span');
470
- time.style.cssText = 'flex-shrink:0;width:70px;color:#888;font-size:11px;';
471
- var updatedAt = asset.updated_at || data.updated_at || '';
472
- if (updatedAt) {
473
- try {
474
- var d = new Date(updatedAt);
475
- time.textContent = (d.getMonth() + 1) + '/' + d.getDate() + ' ' + String(d.getHours()).padStart(2, '0') + ':' + String(d.getMinutes()).padStart(2, '0');
476
- } catch (e) {
477
- time.textContent = '';
478
- }
479
- }
506
+ // 左侧列:上下结构,固定宽度
507
+ var leftCol = document.createElement('div');
508
+ leftCol.style.cssText = 'flex-shrink:0;width:80px;display:flex;flex-direction:column;gap:4px;margin-right:8px;';
509
+
510
+ // 上部:路径按钮(左) + 标签(右)
511
+ var topRow = document.createElement('div');
512
+ topRow.style.cssText = 'display:flex;align-items:center;justify-content:space-between;gap:4px;';
480
513
 
481
- // 复制相对路径按钮(最左侧)
514
+ // 路径按钮(原"复制")
482
515
  var copyPathBtn = document.createElement('button');
483
- copyPathBtn.textContent = '复制';
516
+ copyPathBtn.textContent = '路径';
484
517
  copyPathBtn.style.cssText = [
485
518
  'flex-shrink: 0',
486
519
  'width: 44px',
487
- 'height: 28px',
520
+ 'height: 24px',
488
521
  'background: #2d2d4a',
489
522
  'color: #a78bfa',
490
523
  'border: 1px solid #4a4a7a',
@@ -504,38 +537,92 @@
504
537
  copyPathBtn.style.color = '#10b981';
505
538
  copyPathBtn.style.borderColor = '#10b981';
506
539
  setTimeout(function () {
507
- copyPathBtn.textContent = '复制';
540
+ copyPathBtn.textContent = '路径';
508
541
  copyPathBtn.style.color = '#a78bfa';
509
542
  copyPathBtn.style.borderColor = '#4a4a7a';
510
543
  }, 1500);
511
544
  });
512
545
  };
513
546
 
514
- // 文件名(从 path 提取)+ 标记
515
- var fname = document.createElement('span');
516
- fname.style.cssText = 'flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;display:flex;align-items:center;gap:6px;';
517
- var pathParts = (asset.path || '').split('/');
518
- var nameSpan = document.createElement('span');
519
- nameSpan.textContent = pathParts[pathParts.length - 1] || asset.name || '未命名';
520
- nameSpan.title = asset.path || '';
547
+ // 状态 badge
548
+ var badge = document.createElement('span');
521
549
  if (isLatest) {
522
- var badge = document.createElement('span');
523
- badge.style.cssText = 'color:#ff4444;font-size:10px;font-weight:bold;flex-shrink:0;';
550
+ badge.style.cssText = 'color:#ff4444;font-size:9px;font-weight:bold;';
524
551
  badge.textContent = '[最新]';
525
- fname.appendChild(badge);
526
552
  } else if (isUpdated) {
527
- var badge = document.createElement('span');
528
- badge.style.cssText = 'color:#10b981;font-size:10px;font-weight:bold;flex-shrink:0;';
529
- badge.textContent = '[有更新]';
530
- fname.appendChild(badge);
553
+ badge.style.cssText = 'color:#10b981;font-size:9px;font-weight:bold;';
554
+ badge.textContent = '[更新]';
555
+ } else {
556
+ badge.style.display = 'none';
557
+ }
558
+
559
+ topRow.appendChild(copyPathBtn);
560
+ topRow.appendChild(badge);
561
+ leftCol.appendChild(topRow);
562
+
563
+ // 下部:时间 + 预加载按钮
564
+ var bottomRow = document.createElement('div');
565
+ bottomRow.style.cssText = 'display:flex;align-items:center;justify-content:center;gap:3px;';
566
+
567
+ // 更新时间(可点击 → 打开该文件的历史版本)
568
+ var time = document.createElement('span');
569
+ time.style.cssText = 'color:#888;font-size:10px;cursor:pointer;border-radius:3px;padding:1px 2px;transition:background 0.15s,color 0.15s;white-space:nowrap;';
570
+ time.title = '查看历史版本';
571
+ var updatedAt = asset.updated_at || data.updated_at || '';
572
+ if (updatedAt) {
573
+ try {
574
+ var d = new Date(updatedAt);
575
+ time.textContent = (d.getMonth() + 1) + '/' + d.getDate() + ' ' + String(d.getHours()).padStart(2, '0') + ':' + String(d.getMinutes()).padStart(2, '0');
576
+ } catch (e) {
577
+ time.textContent = '';
578
+ }
579
+ }
580
+ time.onmouseenter = function () { time.style.background = 'rgba(100,149,237,0.2)'; time.style.color = '#90c2ff'; };
581
+ time.onmouseleave = function () { time.style.background = ''; time.style.color = '#888'; };
582
+ time.onclick = (function (path) {
583
+ return function (e) {
584
+ e.stopPropagation();
585
+ openHistoryModal(path);
586
+ };
587
+ }(asset.path || ''));
588
+
589
+ bottomRow.appendChild(time);
590
+ leftCol.appendChild(bottomRow);
591
+
592
+ // 文件显示(上下两行:路径+文件名)
593
+ var fname = document.createElement('div');
594
+ fname.style.cssText = 'flex:1;min-width:0;display:flex;flex-direction:column;justify-content:center;gap:2px;overflow:hidden;';
595
+
596
+ var fullPath = asset.path || asset.name || '未命名';
597
+ var lastSlash = fullPath.lastIndexOf('/');
598
+ var dirPart = lastSlash > 0 ? fullPath.substring(0, lastSlash + 1) : '';
599
+ var namePart = lastSlash > 0 ? fullPath.substring(lastSlash + 1) : fullPath;
600
+
601
+ // 上行:目录路径(灰色极小字,单行,不换行)
602
+ if (dirPart) {
603
+ var dirSpan = document.createElement('span');
604
+ dirSpan.textContent = dirPart;
605
+ dirSpan.title = fullPath;
606
+ dirSpan.style.cssText = 'color:#666;font-size:9px;line-height:1.3;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;';
607
+ fname.appendChild(dirSpan);
531
608
  }
609
+
610
+ // 下行:文件名(大字体,单行,不换行)
611
+ var nameSpan = document.createElement('span');
612
+ nameSpan.textContent = namePart;
613
+ nameSpan.title = fullPath;
614
+ nameSpan.style.cssText = 'font-size:13px;font-weight:500;line-height:1.5;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;';
532
615
  fname.appendChild(nameSpan);
533
616
 
534
- row.appendChild(copyPathBtn);
535
- row.appendChild(time);
617
+ row.appendChild(leftCol);
536
618
  row.appendChild(fname);
537
- container.appendChild(row);
619
+ listContent.appendChild(row);
538
620
  });
621
+
622
+ container.appendChild(listContent);
623
+
624
+ // 重新应用搜索过滤(保留搜索框的值)
625
+ if (window.applySearch) applySearch();
539
626
  }
540
627
 
541
628
  // ═══ 预览弹框 ═══
@@ -657,13 +744,7 @@
657
744
  'background: #252536',
658
745
  ].join(';');
659
746
 
660
- // 加载中提示
661
- var hint = document.createElement('div');
662
- hint.textContent = '\u52A0\u8F7D\u4E2D...';
663
- hint.style.cssText = 'color:#666;font-size:13px;font-family:monospace;position:absolute;';
664
- imgArea.appendChild(hint);
665
-
666
- // img 在占位区内加载,opacity 0 → 1
747
+ // img 直接渲染,浏览器自然从上往下加载
667
748
  var img = document.createElement('img');
668
749
  img.src = previewUrl;
669
750
  img.alt = asset.name || asset.path;
@@ -673,17 +754,15 @@
673
754
  'width: auto',
674
755
  'height: auto',
675
756
  'object-fit: contain',
676
- 'opacity: 0',
677
- 'transition: opacity 0.25s ease',
678
757
  'position: relative',
679
758
  'z-index: 1',
680
759
  ].join(';');
681
- img.onload = function () {
682
- img.style.opacity = '1';
683
- hint.style.display = 'none';
684
- };
685
760
  img.onerror = function () {
686
- hint.textContent = '\u52A0\u8F7D\u5931\u8D25';
761
+ img.style.display = 'none';
762
+ var errMsg = document.createElement('div');
763
+ errMsg.textContent = '加载失败';
764
+ errMsg.style.cssText = 'color:#666;font-size:13px;font-family:monospace;';
765
+ imgArea.appendChild(errMsg);
687
766
  };
688
767
 
689
768
  imgArea.appendChild(img);
@@ -706,7 +785,6 @@
706
785
  box.appendChild(makeHeader('\uD83C\uDFA8 ' + (asset.name || '预览')));
707
786
 
708
787
  var iframe = document.createElement('iframe');
709
- iframe.src = previewUrl + '?t=' + Date.now();
710
788
  iframe.style.cssText = [
711
789
  'flex: 1',
712
790
  'width: 100%',
@@ -714,6 +792,15 @@
714
792
  'background: #fff',
715
793
  ].join(';');
716
794
 
795
+ // 文本类文件走本地 server(已有 charset=utf-8),避免 CDN 无 charset 导致中文乱码
796
+ var assetExt = (asset.path || '').split('.').pop().toLowerCase();
797
+ var TEXT_EXTS = ['md', 'txt', 'py', 'js', 'ts', 'jsx', 'tsx', 'css', 'json', 'yaml', 'yml', 'sh', 'xml', 'ini', 'toml', 'env', 'vue', 'csv'];
798
+ if (TEXT_EXTS.indexOf(assetExt) !== -1) {
799
+ iframe.src = MYCLAW_API_BASE + '/api/file?path=' + encodeURIComponent(getWorkspaceId() + '/' + asset.path) + '&t=' + Date.now();
800
+ } else {
801
+ iframe.src = previewUrl + '?t=' + Date.now();
802
+ }
803
+
717
804
  box.appendChild(iframe);
718
805
  overlay.appendChild(box);
719
806
  document.body.appendChild(overlay);
@@ -729,6 +816,574 @@
729
816
  if (modal) modal.remove();
730
817
  }
731
818
 
819
+ // ═══ 历史记录弹框 ═══
820
+ function openHistoryModal(defaultPath) {
821
+ if (document.querySelector('#myclaw-history-modal')) return;
822
+
823
+ var ws = getWorkspaceId();
824
+ var url = MYCLAW_API_BASE + '/api/history?workspace=' + encodeURIComponent(ws);
825
+
826
+ // 遮罩
827
+ var overlay = document.createElement('div');
828
+ overlay.id = 'myclaw-history-modal';
829
+ overlay.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,0.55);z-index:99999;display:flex;align-items:center;justify-content:center;font-family:monospace;';
830
+ overlay.onclick = function (e) { if (e.target === overlay) overlay.remove(); };
831
+
832
+ // 弹框主体:上下结构,尽量占满全屏
833
+ var box = document.createElement('div');
834
+ box.style.cssText = 'background:#1e1e2e;border:1px solid rgba(255,255,255,0.1);border-radius:6px;width:calc(100vw - 24px);height:calc(100vh - 24px);display:flex;flex-direction:column;color:#cdd6f4;overflow:hidden;';
835
+
836
+ // 弹框头部
837
+ var head = document.createElement('div');
838
+ head.style.cssText = 'display:flex;align-items:center;justify-content:space-between;padding:14px 18px;border-bottom:1px solid rgba(255,255,255,0.1);flex-shrink:0;';
839
+ var headTitle = document.createElement('span');
840
+ headTitle.textContent = '🕐 历史版本';
841
+ headTitle.style.cssText = 'font-size:14px;font-weight:bold;';
842
+ var headClose = document.createElement('span');
843
+ headClose.textContent = '✕';
844
+ headClose.style.cssText = 'cursor:pointer;font-size:14px;padding:2px 6px;border-radius:3px;';
845
+ headClose.onclick = function () { overlay.remove(); };
846
+ head.appendChild(headTitle);
847
+ head.appendChild(headClose);
848
+ box.appendChild(head);
849
+
850
+ // 文件筛选条(顶部 header 下方)
851
+ var filterBar = document.createElement('div');
852
+ filterBar.style.cssText = 'padding:6px 10px;border-bottom:1px solid rgba(255,255,255,0.07);display:flex;flex-wrap:wrap;gap:4px;flex-shrink:0;align-items:center;overflow-x:auto;';
853
+ box.appendChild(filterBar);
854
+
855
+ // 左右主体区域
856
+ var mainArea = document.createElement('div');
857
+ mainArea.style.cssText = 'flex:1;display:flex;overflow:hidden;';
858
+
859
+ // 左栏:版本列表(固定高度,固定行数)
860
+ var leftPane = document.createElement('div');
861
+ leftPane.style.cssText = 'width:260px;height:380px;flex-shrink:0;overflow-y:auto;overflow-x:auto;border-right:1px solid rgba(255,255,255,0.07);padding:8px;display:flex;flex-direction:column;gap:4px;';
862
+
863
+ // 右栏:iframe 预览
864
+ var rightPane = document.createElement('div');
865
+ rightPane.style.cssText = 'flex:1;display:flex;flex-direction:column;overflow:hidden;background:#111;';
866
+
867
+ // 右栏顶部:当前预览的版本信息
868
+ var previewHeader = document.createElement('div');
869
+ previewHeader.style.cssText = 'padding:6px 14px;font-size:11px;color:rgba(205,214,244,0.4);border-bottom:1px solid rgba(255,255,255,0.07);flex-shrink:0;height:30px;display:flex;align-items:center;';
870
+ previewHeader.textContent = '← 点击左侧版本预览';
871
+
872
+ // HTML iframe
873
+ var previewIframe = document.createElement('iframe');
874
+ previewIframe.style.cssText = 'flex:1;border:none;background:#fff;display:none;';
875
+
876
+ // 图片预览
877
+ var previewImg = document.createElement('div');
878
+ previewImg.style.cssText = 'flex:1;display:none;align-items:center;justify-content:center;overflow:auto;padding:16px;background:#111;';
879
+ var previewImgEl = document.createElement('img');
880
+ previewImgEl.style.cssText = 'max-width:100%;max-height:100%;object-fit:contain;border-radius:4px;';
881
+ previewImg.appendChild(previewImgEl);
882
+
883
+ // 代码 / 文本预览
884
+ var previewText = document.createElement('pre');
885
+ previewText.style.cssText = 'flex:1;margin:0;padding:16px;font-size:12px;line-height:1.6;white-space:pre-wrap;word-break:break-all;color:#cdd6f4;overflow:auto;display:none;';
886
+
887
+ // 不支持预览的占位
888
+ var previewUnsupported = document.createElement('div');
889
+ previewUnsupported.style.cssText = 'flex:1;display:none;align-items:center;justify-content:center;color:rgba(205,214,244,0.3);font-size:13px;';
890
+ previewUnsupported.textContent = '该文件类型暂不支持预览';
891
+
892
+ rightPane.appendChild(previewHeader);
893
+ rightPane.appendChild(previewIframe);
894
+ rightPane.appendChild(previewImg);
895
+ rightPane.appendChild(previewText);
896
+ rightPane.appendChild(previewUnsupported);
897
+
898
+ mainArea.appendChild(leftPane);
899
+ mainArea.appendChild(rightPane);
900
+ box.appendChild(mainArea);
901
+
902
+ overlay.appendChild(box);
903
+ document.body.appendChild(overlay);
904
+
905
+ var activeRow = null; // 当前高亮的左栏行
906
+
907
+ function loadPreview(rec) {
908
+ var versionDir = rec.version_dir || ('v' + rec.version);
909
+ var filePath = rec.path || '';
910
+ var ext = filePath.split('.').pop().toLowerCase();
911
+
912
+ previewHeader.textContent = versionDir + ' ' + filePath;
913
+
914
+ // 隐藏所有预览区
915
+ previewIframe.style.display = 'none';
916
+ previewImg.style.display = 'none';
917
+ previewText.style.display = 'none';
918
+ previewUnsupported.style.display = 'none';
919
+
920
+ var apiUrl = MYCLAW_API_BASE + '/api/history/file?workspace=' + encodeURIComponent(ws)
921
+ + '&version=' + encodeURIComponent(versionDir)
922
+ + '&path=' + encodeURIComponent(filePath);
923
+
924
+ if (['html', 'htm'].indexOf(ext) !== -1) {
925
+ // HTML → iframe srcdoc
926
+ previewIframe.style.display = 'block';
927
+ previewIframe.srcdoc = '<body style="font:13px monospace;padding:20px;color:#888">加载中…</body>';
928
+ fetch(apiUrl)
929
+ .then(function (r) { return r.ok ? r.text() : Promise.reject('HTTP ' + r.status); })
930
+ .then(function (text) { previewIframe.srcdoc = text; })
931
+ .catch(function (e) { previewIframe.srcdoc = '<body style="font:13px monospace;padding:20px;color:#c00">加载失败: ' + e + '</body>'; });
932
+
933
+ } else if (['png', 'jpg', 'jpeg', 'gif', 'webp', 'svg', 'bmp', 'ico'].indexOf(ext) !== -1) {
934
+ // 图片 → <img> src 直接指向 API URL(浏览器可直接加载二进制)
935
+ previewImg.style.display = 'flex';
936
+ previewImgEl.src = apiUrl;
937
+ previewImgEl.alt = filePath;
938
+
939
+ } else if (['js', 'ts', 'css', 'json', 'txt', 'md', 'py', 'sh', 'yaml', 'yml', 'xml', 'csv', 'ini', 'toml', 'env', 'jsx', 'tsx', 'vue', 'html'].indexOf(ext) !== -1) {
940
+ // 文本类 → pre 代码展示
941
+ previewText.style.display = 'block';
942
+ previewText.textContent = '加载中…';
943
+ fetch(apiUrl)
944
+ .then(function (r) { return r.ok ? r.arrayBuffer() : Promise.reject('HTTP ' + r.status); })
945
+ .then(function (arrayBuf) {
946
+ // 使用 TextDecoder 强制 UTF-8 解码,避免中文乱码
947
+ var decoder = new TextDecoder('utf-8');
948
+ var text = decoder.decode(arrayBuf);
949
+ previewText.textContent = text;
950
+ })
951
+ .catch(function (e) { previewText.textContent = '加载失败: ' + e; });
952
+
953
+ } else {
954
+ // 其余类型暂不支持
955
+ previewUnsupported.style.display = 'flex';
956
+ previewUnsupported.textContent = '.' + ext + ' 文件暂不支持预览';
957
+ }
958
+ }
959
+
960
+ // 拉取历史数据
961
+ fetch(url)
962
+ .then(function (r) { return r.json(); })
963
+ .then(function (data) {
964
+ var allRecords = data.records || [];
965
+ if (allRecords.length === 0) {
966
+ leftPane.textContent = '暂无历史版本记录';
967
+ leftPane.style.color = 'rgba(205,214,244,0.4)';
968
+ leftPane.style.fontSize = '13px';
969
+ leftPane.style.padding = '24px';
970
+ return;
971
+ }
972
+
973
+ var currentVersions = data.current_versions || {};
974
+
975
+ var maxVersionPerPath = {};
976
+ allRecords.forEach(function (r) {
977
+ var p = r.path || '';
978
+ if (!maxVersionPerPath[p] || r.version > maxVersionPerPath[p]) maxVersionPerPath[p] = r.version;
979
+ });
980
+
981
+ var uniquePaths = [];
982
+ allRecords.forEach(function (r) {
983
+ if (r.path && uniquePaths.indexOf(r.path) === -1) uniquePaths.push(r.path);
984
+ });
985
+
986
+ // 若传入了 defaultPath 且存在于历史中,则预设选中
987
+ var selectedPath = (defaultPath && uniquePaths.indexOf(defaultPath) !== -1) ? defaultPath : null;
988
+
989
+ function renderFilterBar() {
990
+ filterBar.textContent = '';
991
+ [null].concat(uniquePaths).forEach(function (p) {
992
+ var chip = document.createElement('span');
993
+ var isActive = selectedPath === p;
994
+ chip.textContent = p === null ? '全部' : p.split('/').pop();
995
+ if (p !== null) chip.title = p;
996
+ chip.style.cssText = 'cursor:pointer;font-size:10px;padding:2px 8px;border-radius:6px;white-space:nowrap;flex-shrink:0;transition:background 0.15s;'
997
+ + (isActive ? 'background:#4a4a7a;color:#cdd6f4;' : 'background:rgba(255,255,255,0.07);color:rgba(205,214,244,0.55);');
998
+ chip.onclick = function () { selectedPath = p; renderFilterBar(); renderList(); };
999
+ filterBar.appendChild(chip);
1000
+ });
1001
+ }
1002
+
1003
+ function renderList() {
1004
+ leftPane.textContent = '';
1005
+ activeRow = null;
1006
+ var recs = allRecords.slice().reverse();
1007
+ if (selectedPath !== null) recs = recs.filter(function (r) { return r.path === selectedPath; });
1008
+
1009
+ recs.forEach(function (rec, idx) {
1010
+ var recPath = rec.path || '';
1011
+ var recVer = rec.version;
1012
+ var isLatest = recVer === maxVersionPerPath[recPath];
1013
+ var effectiveCurrent = recPath in currentVersions ? currentVersions[recPath] : maxVersionPerPath[recPath];
1014
+ var isCurrent = recVer === effectiveCurrent;
1015
+
1016
+ var row = document.createElement('div');
1017
+ row.style.cssText = 'padding:7px 8px;border-radius:5px;cursor:pointer;transition:background 0.12s;';
1018
+ row.style.background = 'transparent';
1019
+
1020
+ function setActive() {
1021
+ if (activeRow) activeRow.style.background = 'transparent';
1022
+ activeRow = row;
1023
+ row.style.background = 'rgba(100,149,237,0.15)';
1024
+ loadPreview(rec);
1025
+ }
1026
+ row.onclick = setActive;
1027
+ row.onmouseenter = function () { if (row !== activeRow) row.style.background = 'rgba(255,255,255,0.06)'; };
1028
+ row.onmouseleave = function () { if (row !== activeRow) row.style.background = 'transparent'; };
1029
+
1030
+ // 第一行:时间 + 切换按钮
1031
+ var row1 = document.createElement('div');
1032
+ row1.style.cssText = 'display:flex;align-items:center;justify-content:space-between;margin-bottom:3px;';
1033
+
1034
+ var timeSpan = document.createElement('span');
1035
+ timeSpan.textContent = (rec.snapshot_at || '').replace('T', ' ').replace(/\.\d+.*$/, '').replace(/\+.*$/, '').slice(5);
1036
+ timeSpan.style.cssText = 'font-size:11px;color:#cdd6f4;font-weight:500;';
1037
+
1038
+ var rollbackBtn = document.createElement('span');
1039
+ if (isCurrent) {
1040
+ rollbackBtn.textContent = '使用中';
1041
+ rollbackBtn.style.cssText = 'font-size:10px;color:rgba(205,214,244,0.25);';
1042
+ } else {
1043
+ rollbackBtn.textContent = '切换';
1044
+ rollbackBtn.style.cssText = 'font-size:10px;cursor:pointer;padding:1px 7px;border-radius:4px;background:rgba(100,149,237,0.2);color:#90c2ff;transition:background 0.15s;';
1045
+ rollbackBtn.onmouseenter = function () { rollbackBtn.style.background = 'rgba(100,149,237,0.4)'; };
1046
+ rollbackBtn.onmouseleave = function () { rollbackBtn.style.background = 'rgba(100,149,237,0.2)'; };
1047
+ rollbackBtn.onclick = (function (r, btn) {
1048
+ return function (e) {
1049
+ e.stopPropagation();
1050
+ btn.textContent = '…';
1051
+ fetch(MYCLAW_API_BASE + '/api/history/rollback', {
1052
+ method: 'POST',
1053
+ headers: { 'Content-Type': 'application/json' },
1054
+ body: JSON.stringify({ workspace: ws, version: r.version_dir || ('v' + r.version), path: r.path })
1055
+ })
1056
+ .then(function (res) { return res.json(); })
1057
+ .then(function (result) {
1058
+ if (result.ok) { currentVersions[r.path] = r.version; renderList(); }
1059
+ else { alert('切换失败: ' + (result.error || '')); btn.textContent = '切换'; }
1060
+ })
1061
+ .catch(function (err) { alert('切换失败: ' + err.message); btn.textContent = '切换'; });
1062
+ };
1063
+ }(rec, rollbackBtn));
1064
+ }
1065
+
1066
+ row1.appendChild(timeSpan);
1067
+ row1.appendChild(rollbackBtn);
1068
+
1069
+ // 第二行:文件名
1070
+ var row2 = document.createElement('div');
1071
+ var fileName = document.createElement('span');
1072
+ fileName.textContent = recPath.split('/').pop();
1073
+ fileName.title = recPath;
1074
+ fileName.style.cssText = 'font-size:12px;color:#cdd6f4;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;display:block;';
1075
+ row2.appendChild(fileName);
1076
+
1077
+ // 第三行:版本号 + 状态标签
1078
+ var row3 = document.createElement('div');
1079
+ row3.style.cssText = 'display:flex;align-items:center;gap:4px;flex-wrap:wrap;margin-top:3px;';
1080
+
1081
+ var vTag = document.createElement('span');
1082
+ vTag.textContent = rec.version_dir || ('v' + recVer);
1083
+ vTag.style.cssText = 'font-size:10px;font-weight:bold;background:#4a4a7a;color:#cdd6f4;padding:1px 6px;border-radius:8px;';
1084
+ row3.appendChild(vTag);
1085
+
1086
+ if (isLatest) {
1087
+ var lb = document.createElement('span');
1088
+ lb.textContent = '最新';
1089
+ lb.style.cssText = 'font-size:9px;background:#2d6a4f;color:#95d5b2;padding:1px 5px;border-radius:8px;';
1090
+ row3.appendChild(lb);
1091
+ }
1092
+ if (isCurrent) {
1093
+ var cb = document.createElement('span');
1094
+ cb.textContent = '当前';
1095
+ cb.style.cssText = 'font-size:9px;background:#1e3a5f;color:#90c2ff;padding:1px 5px;border-radius:8px;';
1096
+ row3.appendChild(cb);
1097
+ }
1098
+
1099
+ row.appendChild(row1);
1100
+ row.appendChild(row2);
1101
+ row.appendChild(row3);
1102
+ leftPane.appendChild(row);
1103
+
1104
+ // 默认选中第一条并预览
1105
+ if (idx === 0) setActive();
1106
+ });
1107
+ }
1108
+
1109
+ renderFilterBar();
1110
+ renderList();
1111
+ })
1112
+ .catch(function (err) {
1113
+ leftPane.textContent = '加载失败: ' + err.message;
1114
+ });
1115
+ }
1116
+
1117
+ // ═══ 统计弹框 ═══
1118
+ function openStatsModal() {
1119
+ if (document.querySelector('#myclaw-stats-modal')) return;
1120
+
1121
+ var overlay = document.createElement('div');
1122
+ overlay.id = 'myclaw-stats-modal';
1123
+ overlay.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,0.6);z-index:99999;display:flex;align-items:center;justify-content:center;';
1124
+ overlay.onclick = function (e) { if (e.target === overlay) overlay.remove(); };
1125
+
1126
+ var box = document.createElement('div');
1127
+ box.style.cssText = 'background:#1e1e2e;border-radius:10px;width:min(92vw,480px);overflow:hidden;box-shadow:0 12px 40px rgba(0,0,0,0.7);font-family:monospace;color:#cdd6f4;';
1128
+
1129
+ // 弹框 header
1130
+ var mHeader = document.createElement('div');
1131
+ mHeader.style.cssText = 'display:flex;align-items:center;justify-content:space-between;padding:12px 16px;background:#2d2d3f;font-size:13px;';
1132
+ var mTitle = document.createElement('span');
1133
+ mTitle.style.cssText = 'display:flex;align-items:center;gap:10px;';
1134
+ var mTitleText = document.createElement('span');
1135
+ mTitleText.textContent = '📊 作品统计';
1136
+ var mTitleTime = document.createElement('span');
1137
+ mTitleTime.style.cssText = 'font-size:11px;color:#888;font-weight:normal;';
1138
+ function updateStatsTime() {
1139
+ var now = new Date();
1140
+ var pad = function(n){return n<10?'0'+n:n;};
1141
+ mTitleTime.textContent = now.getFullYear()+'-'+pad(now.getMonth()+1)+'-'+pad(now.getDate())+' '+pad(now.getHours())+':'+pad(now.getMinutes())+':'+pad(now.getSeconds());
1142
+ }
1143
+ updateStatsTime();
1144
+ var statsTimerInterval = setInterval(updateStatsTime, 1000);
1145
+ overlay.addEventListener('remove', function(){ clearInterval(statsTimerInterval); }, {once:true});
1146
+ // 监听 overlay 被移除时清除 timer
1147
+ var statsTimerObserver = new MutationObserver(function(mutations){
1148
+ mutations.forEach(function(m){
1149
+ m.removedNodes.forEach(function(n){
1150
+ if(n === overlay){ clearInterval(statsTimerInterval); statsTimerObserver.disconnect(); }
1151
+ });
1152
+ });
1153
+ });
1154
+ statsTimerObserver.observe(document.body, {childList:true});
1155
+ mTitle.appendChild(mTitleText);
1156
+ mTitle.appendChild(mTitleTime);
1157
+ var mClose = document.createElement('span');
1158
+ mClose.textContent = '✕';
1159
+ mClose.style.cssText = 'cursor:pointer;padding:2px 6px;border-radius:3px;font-size:14px;transition:background 0.15s;';
1160
+ mClose.onmouseenter = function () { mClose.style.background = 'rgba(255,255,255,0.1)'; };
1161
+ mClose.onmouseleave = function () { mClose.style.background = 'none'; };
1162
+ mClose.onclick = function () { overlay.remove(); };
1163
+ mHeader.appendChild(mTitle);
1164
+ mHeader.appendChild(mClose);
1165
+ box.appendChild(mHeader);
1166
+
1167
+ var body = document.createElement('div');
1168
+ body.style.cssText = 'padding:0;';
1169
+
1170
+ // ── 通用:行样式 ──
1171
+ function makeSection(icon, title) {
1172
+ var sec = document.createElement('div');
1173
+ sec.style.cssText = 'padding:14px 16px;border-bottom:1px solid rgba(255,255,255,0.07);';
1174
+ var secTitle = document.createElement('div');
1175
+ secTitle.style.cssText = 'font-size:12px;color:#888;margin-bottom:8px;letter-spacing:0.5px;';
1176
+ secTitle.textContent = icon + ' ' + title;
1177
+ sec.appendChild(secTitle);
1178
+ return sec;
1179
+ }
1180
+ function makeRow(label, value, valueColor) {
1181
+ var row = document.createElement('div');
1182
+ row.style.cssText = 'display:flex;justify-content:space-between;align-items:baseline;margin-bottom:4px;font-size:12px;';
1183
+ var l = document.createElement('span');
1184
+ l.style.cssText = 'color:#888;';
1185
+ l.textContent = label;
1186
+ var v = document.createElement('span');
1187
+ v.style.cssText = 'color:' + (valueColor || '#cdd6f4') + ';text-align:right;max-width:60%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;';
1188
+ v.textContent = value;
1189
+ v.title = value;
1190
+ row.appendChild(l);
1191
+ row.appendChild(v);
1192
+ return row;
1193
+ }
1194
+
1195
+ // ── JSON 预览弹框 ──
1196
+ // sortKey: 若提供,则对数组(或对象内的数组字段)按该字段降序排列
1197
+ function openJsonPreviewModal(title, jsonUrl, sortKey) {
1198
+ var jOverlay = document.createElement('div');
1199
+ jOverlay.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,0.7);z-index:100000;display:flex;align-items:center;justify-content:center;';
1200
+ jOverlay.onclick = function(e){ if(e.target===jOverlay) jOverlay.remove(); };
1201
+
1202
+ var jBox = document.createElement('div');
1203
+ jBox.style.cssText = 'background:#1e1e2e;border-radius:10px;width:min(94vw,700px);height:min(80vh,600px);overflow:hidden;box-shadow:0 16px 48px rgba(0,0,0,0.8);font-family:monospace;color:#cdd6f4;display:flex;flex-direction:column;';
1204
+
1205
+ var jHead = document.createElement('div');
1206
+ jHead.style.cssText = 'display:flex;align-items:center;justify-content:space-between;padding:10px 16px;background:#2d2d3f;font-size:12px;flex-shrink:0;';
1207
+ var jTitle = document.createElement('span');
1208
+ jTitle.textContent = title;
1209
+ var jClose = document.createElement('span');
1210
+ jClose.textContent = '✕';
1211
+ jClose.style.cssText = 'cursor:pointer;padding:2px 6px;border-radius:3px;font-size:14px;transition:background 0.15s;';
1212
+ jClose.onmouseenter = function(){ jClose.style.background='rgba(255,255,255,0.1)'; };
1213
+ jClose.onmouseleave = function(){ jClose.style.background='none'; };
1214
+ jClose.onclick = function(){ jOverlay.remove(); };
1215
+ jHead.appendChild(jTitle);
1216
+ jHead.appendChild(jClose);
1217
+
1218
+ var jBody = document.createElement('pre');
1219
+ jBody.style.cssText = 'flex:1;overflow:auto;margin:0;padding:16px;font-size:11px;line-height:1.6;color:#a6e3a1;white-space:pre-wrap;word-break:break-all;background:#181825;';
1220
+ jBody.textContent = '加载中...';
1221
+
1222
+ jBox.appendChild(jHead);
1223
+ jBox.appendChild(jBody);
1224
+ jOverlay.appendChild(jBox);
1225
+ document.body.appendChild(jOverlay);
1226
+
1227
+ fetch(jsonUrl)
1228
+ .then(function(r){ return r.ok ? r.json() : Promise.reject('HTTP ' + r.status); })
1229
+ .then(function(data){
1230
+ if (sortKey) {
1231
+ // 支持顶层数组 或 对象内某个数组字段(如 { assets: [...] })
1232
+ var arr = Array.isArray(data) ? data : (data && typeof data === 'object' ? (
1233
+ Object.values(data).find(function(v){ return Array.isArray(v); })
1234
+ ) : null);
1235
+ if (arr) {
1236
+ arr.sort(function(a, b){
1237
+ var ta = a[sortKey] ? new Date(a[sortKey]).getTime() : 0;
1238
+ var tb = b[sortKey] ? new Date(b[sortKey]).getTime() : 0;
1239
+ return tb - ta;
1240
+ });
1241
+ }
1242
+ }
1243
+ jBody.style.color = '#a6e3a1';
1244
+ jBody.textContent = JSON.stringify(data, null, 2);
1245
+ })
1246
+ .catch(function(err){
1247
+ jBody.style.color = '#f87171';
1248
+ jBody.textContent = '加载失败: ' + err;
1249
+ });
1250
+ }
1251
+
1252
+ // ── Section 1: 文件资产 ──
1253
+ var sec1 = makeSection('📁', '文件资产');
1254
+ sec1.style.cursor = 'pointer';
1255
+ sec1.title = '点击查看 JSON';
1256
+ sec1.onmouseenter = function(){ sec1.style.background='rgba(255,255,255,0.04)'; };
1257
+ sec1.onmouseleave = function(){ sec1.style.background=''; };
1258
+ sec1.onclick = function(){
1259
+ var wsPrefix = getWorkspaceId();
1260
+ openJsonPreviewModal('📁 文件资产 — __MY_ARTIFACTS__.json', MYCLAW_API_BASE + '/api/file?path=' + encodeURIComponent(wsPrefix + '/.myclaw/__MY_ARTIFACTS__.json'), 'updated_at');
1261
+ };
1262
+ if (cachedData && cachedData.assets && cachedData.assets.length) {
1263
+ var assets = cachedData.assets;
1264
+ // 最后更新时间 & 文件
1265
+ var sorted = assets.slice().sort(function (a, b) {
1266
+ return new Date(b.updated_at || 0) - new Date(a.updated_at || 0);
1267
+ });
1268
+ var latest = sorted[0];
1269
+ var lastTime = latest.updated_at ? latest.updated_at.replace('T', ' ').slice(0, 16) : '-';
1270
+ var lastName = latest.path ? latest.path.split('/').pop() : '-';
1271
+
1272
+ // 按后缀统计
1273
+ var extMap = {};
1274
+ assets.forEach(function (a) {
1275
+ var ext = (a.type || (a.path && a.path.split('.').pop()) || '其他').toLowerCase();
1276
+ extMap[ext] = (extMap[ext] || 0) + 1;
1277
+ });
1278
+ var extStr = Object.keys(extMap).sort().map(function (k) { return k + ': ' + extMap[k]; }).join(' ');
1279
+
1280
+ sec1.appendChild(makeRow('最后更新', lastTime));
1281
+ sec1.appendChild(makeRow('最后更新文件', lastName));
1282
+ sec1.appendChild(makeRow('总文件数', assets.length + ' 个'));
1283
+ var extRow = document.createElement('div');
1284
+ extRow.style.cssText = 'font-size:11px;color:#666;margin-top:6px;line-height:1.8;word-break:break-all;';
1285
+ extRow.textContent = extStr;
1286
+ sec1.appendChild(extRow);
1287
+ } else {
1288
+ sec1.appendChild(makeRow('状态', '暂无数据', '#666'));
1289
+ }
1290
+ body.appendChild(sec1);
1291
+
1292
+ // ── Section 2: 美术资源 ──
1293
+ var sec2 = makeSection('🎨', '美术资源');
1294
+ sec2.style.cursor = 'pointer';
1295
+ sec2.title = '点击查看 JSON';
1296
+ sec2.onmouseenter = function(){ sec2.style.background='rgba(255,255,255,0.04)'; };
1297
+ sec2.onmouseleave = function(){ sec2.style.background=''; };
1298
+ sec2.onclick = function(){
1299
+ openJsonPreviewModal('🎨 美术资源 — media-generation-log.json', MYCLAW_API_BASE + '/api/file?path=myclaw/log/media-generation-log.json', 'started_at');
1300
+ };
1301
+ var mediaPlaceholder = document.createElement('div');
1302
+ mediaPlaceholder.style.cssText = 'font-size:12px;color:#666;';
1303
+ mediaPlaceholder.textContent = '加载中...';
1304
+ sec2.appendChild(mediaPlaceholder);
1305
+ body.appendChild(sec2);
1306
+
1307
+ // 异步读取 media log
1308
+ fetch(MYCLAW_API_BASE + '/api/file?path=myclaw/log/media-generation-log.json')
1309
+ .then(function (r) { return r.ok ? r.json() : Promise.reject(r.status); })
1310
+ .then(function (log) {
1311
+ mediaPlaceholder.remove();
1312
+ if (!log || !log.length) {
1313
+ sec2.appendChild(makeRow('状态', '暂无记录', '#666'));
1314
+ return;
1315
+ }
1316
+ var sorted = log.slice().sort(function (a, b) {
1317
+ return new Date(b.started_at || 0) - new Date(a.started_at || 0);
1318
+ });
1319
+ var last = sorted[0];
1320
+ var statusColor = last.status === 'success' ? '#4ade80' : last.status === 'failed' ? '#f87171' : '#facc15';
1321
+ sec2.appendChild(makeRow('最后生成', last.started_at || '-'));
1322
+ sec2.appendChild(makeRow('使用模型', last.model || last.provider || '-'));
1323
+ sec2.appendChild(makeRow('生成状态', last.status || '-', statusColor));
1324
+ sec2.appendChild(makeRow('总记录数', log.length + ' 条'));
1325
+ })
1326
+ .catch(function () {
1327
+ mediaPlaceholder.textContent = '暂无记录';
1328
+ });
1329
+
1330
+ // ── Section 3: 同步 ──
1331
+ var sec3 = document.createElement('div');
1332
+ sec3.style.cssText = 'padding:14px 16px;';
1333
+ // 标题行:左边文字 + 右边同步按钮
1334
+ var sec3Head = document.createElement('div');
1335
+ sec3Head.style.cssText = 'display:flex;align-items:center;justify-content:space-between;';
1336
+ var sec3Label = document.createElement('div');
1337
+ sec3Label.style.cssText = 'font-size:12px;color:#888;letter-spacing:0.5px;';
1338
+ sec3Label.textContent = '🔄 数据同步';
1339
+ var syncBtn = document.createElement('button');
1340
+ syncBtn.textContent = '全量同步';
1341
+ syncBtn.style.cssText = 'padding:2px 10px;background:none;color:#555;border:1px solid rgba(255,255,255,0.1);border-radius:4px;font-size:11px;font-family:monospace;cursor:pointer;transition:color 0.15s,border-color 0.15s;';
1342
+ syncBtn.onmouseenter = function () { syncBtn.style.color='#aaa'; syncBtn.style.borderColor='rgba(255,255,255,0.25)'; };
1343
+ syncBtn.onmouseleave = function () { syncBtn.style.color='#555'; syncBtn.style.borderColor='rgba(255,255,255,0.1)'; };
1344
+ syncBtn.onclick = function () {
1345
+ var confirm = document.createElement('div');
1346
+ confirm.style.cssText = 'margin-top:10px;padding:10px;background:rgba(250,204,21,0.08);border:1px solid rgba(250,204,21,0.2);border-radius:6px;font-size:11px;color:#facc15;line-height:1.7;';
1347
+ confirm.innerHTML = '⚠️ 将把当前工作空间所有文件重新上传同步。<br>确认继续?';
1348
+ var btnRow = document.createElement('div');
1349
+ btnRow.style.cssText = 'display:flex;gap:8px;margin-top:8px;';
1350
+ var cancelBtn = document.createElement('button');
1351
+ cancelBtn.textContent = '取消';
1352
+ cancelBtn.style.cssText = 'flex:1;padding:5px;background:rgba(255,255,255,0.06);color:#888;border:1px solid rgba(255,255,255,0.1);border-radius:4px;font-size:11px;font-family:monospace;cursor:pointer;';
1353
+ cancelBtn.onclick = function () { confirm.remove(); };
1354
+ var okBtn = document.createElement('button');
1355
+ okBtn.textContent = '确认同步';
1356
+ okBtn.style.cssText = 'flex:1;padding:5px;background:rgba(250,204,21,0.15);color:#facc15;border:1px solid rgba(250,204,21,0.3);border-radius:4px;font-size:11px;font-family:monospace;cursor:pointer;';
1357
+ okBtn.onclick = function () {
1358
+ okBtn.textContent = '同步中...';
1359
+ okBtn.disabled = true;
1360
+ var wsPrefix = getWorkspaceId();
1361
+ fetch(MYCLAW_API_BASE + '/api/resync?workspace=' + encodeURIComponent(wsPrefix), { method: 'POST' })
1362
+ .then(function () {
1363
+ confirm.innerHTML = '<span style="color:#4ade80">✓ 同步已触发,请稍候</span>';
1364
+ })
1365
+ .catch(function () {
1366
+ confirm.innerHTML = '<span style="color:#f87171">✗ 同步失败</span>';
1367
+ });
1368
+ };
1369
+ btnRow.appendChild(cancelBtn);
1370
+ btnRow.appendChild(okBtn);
1371
+ confirm.appendChild(btnRow);
1372
+ var existing = sec3.querySelector('.sync-confirm');
1373
+ if (existing) existing.remove();
1374
+ confirm.className = 'sync-confirm';
1375
+ sec3.appendChild(confirm);
1376
+ };
1377
+ sec3Head.appendChild(sec3Label);
1378
+ sec3Head.appendChild(syncBtn);
1379
+ sec3.appendChild(sec3Head);
1380
+ body.appendChild(sec3);
1381
+
1382
+ box.appendChild(body);
1383
+ overlay.appendChild(box);
1384
+ document.body.appendChild(overlay);
1385
+ }
1386
+
732
1387
  // ═══ 发布弹框 ═══
733
1388
  function openPublishModal() {
734
1389
  if (document.querySelector('#myclaw-artifacts-publish-modal')) return;
@@ -800,11 +1455,11 @@
800
1455
  var titleGroup = document.createElement('div');
801
1456
  titleGroup.style.cssText = 'display:flex;flex-direction:column;gap:6px;';
802
1457
  var titleLabel = document.createElement('label');
803
- titleLabel.textContent = '\u4F5C\u54C1\u540D\u79F0';
1458
+ titleLabel.textContent = '作品名称';
804
1459
  titleLabel.style.cssText = 'font-size:12px;color:#888;';
805
1460
  var titleInput = document.createElement('input');
806
1461
  titleInput.type = 'text';
807
- titleInput.placeholder = '\u8F93\u5165\u5C55\u793A\u6807\u9898';
1462
+ titleInput.placeholder = '给你的作品起一个闪亮的名字吧';
808
1463
  titleInput.value = savedForm.title || cachedData.title || '';
809
1464
  titleInput.style.cssText = 'padding:8px 10px;background:#252536;border:1px solid #3d3d5c;border-radius:4px;color:#cdd6f4;font-size:13px;font-family:monospace;outline:none;';
810
1465
  titleInput.onfocus = function () { titleInput.style.borderColor = '#6c6caa'; };
@@ -813,11 +1468,11 @@
813
1468
  titleGroup.appendChild(titleInput);
814
1469
  form.appendChild(titleGroup);
815
1470
 
816
- // 字段 1:选择封面图片
1471
+ // 字段 1:选择宣传图
817
1472
  var coverGroup = document.createElement('div');
818
1473
  coverGroup.style.cssText = 'display:flex;flex-direction:column;gap:6px;';
819
1474
  var coverLabel = document.createElement('label');
820
- coverLabel.textContent = '\u9009\u62E9\u5C01\u9762\u56FE\u7247';
1475
+ coverLabel.textContent = '封面图';
821
1476
  coverLabel.style.cssText = 'font-size:12px;color:#888;';
822
1477
  var coverRow = document.createElement('div');
823
1478
  coverRow.style.cssText = 'display:flex;align-items:center;gap:10px;';
@@ -827,7 +1482,7 @@
827
1482
  coverSelect.onblur = function () { coverSelect.style.borderColor = '#3d3d5c'; };
828
1483
  var coverDefaultOpt = document.createElement('option');
829
1484
  coverDefaultOpt.value = '';
830
- coverDefaultOpt.textContent = '\u4E0D\u9009\u62E9';
1485
+ coverDefaultOpt.textContent = '选择一个你满意的图片作为项目宣传图';
831
1486
  coverSelect.appendChild(coverDefaultOpt);
832
1487
  var imageTypes = ['png', 'jpg', 'jpeg', 'gif', 'svg', 'webp', 'bmp', 'image'];
833
1488
  var imageAssets = (cachedData.assets || []).filter(function (a) { return a.type && imageTypes.indexOf(a.type.toLowerCase()) !== -1; });
@@ -862,11 +1517,11 @@
862
1517
  coverGroup.appendChild(coverRow);
863
1518
  form.appendChild(coverGroup);
864
1519
 
865
- // 字段 2:入口文件
1520
+ // 字段 2:网页入口
866
1521
  var entryGroup = document.createElement('div');
867
1522
  entryGroup.style.cssText = 'display:flex;flex-direction:column;gap:6px;';
868
1523
  var entryLabel = document.createElement('label');
869
- entryLabel.textContent = '\u5165\u53E3\u6587\u4EF6';
1524
+ entryLabel.textContent = '网页入口';
870
1525
  entryLabel.style.cssText = 'font-size:12px;color:#888;';
871
1526
  var entrySelect = document.createElement('select');
872
1527
  entrySelect.style.cssText = 'padding:8px 10px;background:#252536;border:1px solid #3d3d5c;border-radius:4px;color:#cdd6f4;font-size:13px;font-family:monospace;outline:none;';
@@ -874,7 +1529,7 @@
874
1529
  entrySelect.onblur = function () { entrySelect.style.borderColor = '#3d3d5c'; };
875
1530
  var entryDefaultOpt = document.createElement('option');
876
1531
  entryDefaultOpt.value = '';
877
- entryDefaultOpt.textContent = '\u4E0D\u9009\u62E9';
1532
+ entryDefaultOpt.textContent = '选择作品的网页入口文件';
878
1533
  entrySelect.appendChild(entryDefaultOpt);
879
1534
  var htmlAssets = (cachedData.assets || []).filter(function (a) { return a.type && a.type.toLowerCase() === 'html'; });
880
1535
  htmlAssets.forEach(function (asset) {
@@ -899,10 +1554,20 @@
899
1554
  submitBtn.onmouseleave = function () { submitBtn.style.background = '#4a4a7a'; };
900
1555
  submitBtn.onclick = function () {
901
1556
  var titleVal = titleInput.value.trim();
1557
+ var coverVal = coverSelect.value;
1558
+ var entryVal = entrySelect.value;
1559
+
902
1560
  if (!titleVal) {
903
1561
  titleInput.style.borderColor = '#ff4444';
904
1562
  return;
905
1563
  }
1564
+
1565
+ // 校验:宣传图和网页入口至少要选一个
1566
+ if (!coverVal && !entryVal) {
1567
+ coverSelect.style.borderColor = '#ff4444';
1568
+ entrySelect.style.borderColor = '#ff4444';
1569
+ return;
1570
+ }
906
1571
  var payload = {
907
1572
  title: titleVal,
908
1573
  workspace: getWorkspaceId(),
@@ -1241,7 +1906,7 @@
1241
1906
  btn.style.background = '#7a2a2a';
1242
1907
  btn.style.cursor = 'pointer';
1243
1908
  setTimeout(function () {
1244
- btn.textContent = '\u786E\u8BA4 Fork';
1909
+ btn.textContent = '\u786E\u8BA4\u83B7\u53D6';
1245
1910
  btn.style.background = '#4a4a7a';
1246
1911
  }, 2500);
1247
1912
  }
@@ -1255,7 +1920,7 @@
1255
1920
  'box-shadow:0 4px 16px rgba(0,0,0,0.3)',
1256
1921
  'animation:myclaw-slide-in-right 0.25s ease',
1257
1922
  ].join(';');
1258
- toast.innerHTML = '\u2705 Fork 完成!<br>'
1923
+ toast.innerHTML = '\u2705 \u83B7\u53D6\u5B8C\u6210\uFF01<br>'
1259
1924
  + '<span style="opacity:0.85;font-size:11px;">' + workspace + '(' + files + ' 个文件)</span>';
1260
1925
  document.body.appendChild(toast);
1261
1926
  setTimeout(function () { toast.remove(); }, 4000);
@@ -1307,7 +1972,7 @@
1307
1972
  'user-select: none',
1308
1973
  'flex-shrink: 0',
1309
1974
  ].join(';');
1310
- forkHeader.innerHTML = '<span>\uD83D\uDD00 Fork \u4ED6\u4EBA\u4F5C\u54C1</span>';
1975
+ forkHeader.innerHTML = '<span>\uD83D\uDD00 \u83B7\u53D6\u4F5C\u54C1</span>';
1311
1976
  var forkCloseBtn = document.createElement('span');
1312
1977
  forkCloseBtn.textContent = '\u2715';
1313
1978
  forkCloseBtn.style.cssText = 'cursor:pointer;padding:2px 6px;border-radius:3px;font-size:14px;transition:background 0.15s;';
@@ -1351,7 +2016,7 @@
1351
2016
 
1352
2017
  // 确认按钮
1353
2018
  var forkSubmitBtn = document.createElement('button');
1354
- forkSubmitBtn.textContent = '\u786E\u8BA4 Fork';
2019
+ forkSubmitBtn.textContent = '\u786E\u8BA4\u83B7\u53D6';
1355
2020
  forkSubmitBtn.style.cssText = [
1356
2021
  'margin: 0 20px 20px',
1357
2022
  'padding: 10px',
@@ -1388,7 +2053,7 @@
1388
2053
  errVisible = false;
1389
2054
 
1390
2055
  forkSubmitBtn.disabled = true;
1391
- forkSubmitBtn.textContent = '\u23F3 Fork 中...';
2056
+ forkSubmitBtn.textContent = '\u23F3 \u83B7\u53D6\u4E2D...';
1392
2057
  forkSubmitBtn.style.background = '#3a3a5a';
1393
2058
  forkSubmitBtn.style.cursor = 'default';
1394
2059
  urlInput.disabled = true;
@@ -1413,7 +2078,7 @@
1413
2078
  if (job.status === 'running') return;
1414
2079
  clearInterval(timer);
1415
2080
  if (job.status === 'done') {
1416
- forkSubmitBtn.textContent = '\u2705 Fork 成功!';
2081
+ forkSubmitBtn.textContent = '\u2705 \u83B7\u53D6\u6210\u529F\uFF01';
1417
2082
  forkSubmitBtn.style.background = '#10b981';
1418
2083
  setTimeout(function () {
1419
2084
  closeForkModal();
@@ -1447,6 +2112,285 @@
1447
2112
  if (modal) modal.remove();
1448
2113
  }
1449
2114
 
2115
+ // ═══ 作品模板列表弹框 ═══
2116
+ // 使用通用文件接口 GET /api/file?path=(路径相对 .openclaw 根)
2117
+ var TEMPLATE_ROOT = 'workspace-ai-demo/skills/yiran-playground-template-use';
2118
+
2119
+ function openTemplateModal() {
2120
+ if (document.querySelector('#myclaw-template-modal')) return;
2121
+
2122
+ var overlay = document.createElement('div');
2123
+ overlay.id = 'myclaw-template-modal';
2124
+ overlay.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,0.55);z-index:99999;display:flex;align-items:center;justify-content:center;font-family:monospace;animation:myclaw-fade-in 0.15s ease;';
2125
+ overlay.onclick = function (e) { if (e.target === overlay) overlay.remove(); };
2126
+
2127
+ var box = document.createElement('div');
2128
+ box.style.cssText = 'background:#1e1e2e;border:1px solid rgba(255,255,255,0.1);border-radius:6px;width:calc(100vw - 24px);height:calc(100vh - 24px);display:flex;flex-direction:column;color:#cdd6f4;overflow:hidden;';
2129
+
2130
+ // ── 头部 ──
2131
+ var head = document.createElement('div');
2132
+ head.style.cssText = 'display:flex;align-items:center;justify-content:space-between;padding:12px 18px;border-bottom:1px solid rgba(255,255,255,0.1);flex-shrink:0;';
2133
+ var headTitle = document.createElement('span');
2134
+ headTitle.textContent = '📋 作品模板库';
2135
+ headTitle.style.cssText = 'font-size:14px;font-weight:bold;';
2136
+ var headClose = document.createElement('span');
2137
+ headClose.textContent = '✕';
2138
+ headClose.style.cssText = 'cursor:pointer;font-size:14px;padding:2px 8px;border-radius:3px;transition:background 0.12s;';
2139
+ headClose.onmouseenter = function () { headClose.style.background = 'rgba(255,255,255,0.1)'; };
2140
+ headClose.onmouseleave = function () { headClose.style.background = 'none'; };
2141
+ headClose.onclick = function () { overlay.remove(); };
2142
+ head.appendChild(headTitle);
2143
+ head.appendChild(headClose);
2144
+ box.appendChild(head);
2145
+
2146
+ // ── 主体:左列表 + 右预览 ──
2147
+ var mainArea = document.createElement('div');
2148
+ mainArea.style.cssText = 'flex:1;display:flex;overflow:hidden;';
2149
+
2150
+ // 左栏:模板列表
2151
+ var leftPane = document.createElement('div');
2152
+ leftPane.style.cssText = 'width:240px;flex-shrink:0;overflow-y:auto;border-right:1px solid rgba(255,255,255,0.07);padding:8px;display:flex;flex-direction:column;gap:2px;';
2153
+
2154
+ // 右栏:iframe 预览
2155
+ var rightPane = document.createElement('div');
2156
+ rightPane.style.cssText = 'flex:1;display:flex;flex-direction:column;overflow:hidden;';
2157
+
2158
+ // 右栏顶部:当前模板信息 + 使用模板(复制提示词)
2159
+ var rightHeader = document.createElement('div');
2160
+ rightHeader.style.cssText = 'padding:8px 14px;border-bottom:1px solid rgba(255,255,255,0.07);flex-shrink:0;min-height:44px;display:flex;align-items:center;justify-content:space-between;gap:12px;background:#1a1a2a;';
2161
+ rightHeader.innerHTML = '<span style="font-size:12px;color:rgba(205,214,244,0.4);">← 点击左侧模板查看 demo</span>';
2162
+
2163
+ var previewIframe = document.createElement('iframe');
2164
+ previewIframe.style.cssText = 'flex:1;border:none;background:#fff;';
2165
+
2166
+ // 右栏底部:操作按钮
2167
+ var rightFooter = document.createElement('div');
2168
+ rightFooter.style.cssText = 'padding:10px 14px;background:#1a1a2a;border-top:1px solid rgba(255,255,255,0.07);flex-shrink:0;display:flex;align-items:center;gap:10px;';
2169
+
2170
+ var copyLocalBtn = document.createElement('button');
2171
+ copyLocalBtn.textContent = '📂 复制到本地';
2172
+ copyLocalBtn.style.cssText = 'padding:6px 18px;background:rgba(255,255,255,0.07);border:1px solid rgba(255,255,255,0.15);border-radius:4px;color:#cdd6f4;font-size:12px;font-family:monospace;cursor:pointer;transition:all 0.15s;';
2173
+ copyLocalBtn.onmouseenter = function () { copyLocalBtn.style.background = 'rgba(255,255,255,0.14)'; };
2174
+ copyLocalBtn.onmouseleave = function () { copyLocalBtn.style.background = 'rgba(255,255,255,0.07)'; };
2175
+
2176
+ var deployBtn = document.createElement('button');
2177
+ deployBtn.textContent = '🚀 创建';
2178
+ deployBtn.style.cssText = 'padding:6px 18px;background:#a78bfa;border:none;border-radius:4px;color:#fff;font-size:12px;font-family:monospace;cursor:pointer;transition:background 0.15s;';
2179
+ deployBtn.onmouseenter = function () { deployBtn.style.background = '#8b5cf6'; };
2180
+ deployBtn.onmouseleave = function () { deployBtn.style.background = '#a78bfa'; };
2181
+
2182
+ var footerStatus = document.createElement('span');
2183
+ footerStatus.style.cssText = 'font-size:11px;color:rgba(205,214,244,0.45);flex:1;';
2184
+
2185
+ rightFooter.appendChild(copyLocalBtn);
2186
+ rightFooter.appendChild(deployBtn);
2187
+ rightFooter.appendChild(footerStatus);
2188
+
2189
+ // currentTpl 在 setActive 时更新,按钮 onclick 通过闭包读取
2190
+ var currentTpl = null;
2191
+
2192
+ function setFooterStatus(msg, color) {
2193
+ footerStatus.textContent = msg;
2194
+ footerStatus.style.color = color || 'rgba(205,214,244,0.45)';
2195
+ }
2196
+
2197
+ function setBtnLoading(btn, text) {
2198
+ btn.disabled = true;
2199
+ btn.style.opacity = '0.6';
2200
+ btn.style.cursor = 'default';
2201
+ btn._origText = btn.textContent;
2202
+ btn.textContent = text;
2203
+ }
2204
+
2205
+ function resetBtn(btn) {
2206
+ btn.disabled = false;
2207
+ btn.style.opacity = '1';
2208
+ btn.style.cursor = 'pointer';
2209
+ btn.textContent = btn._origText || btn.textContent;
2210
+ }
2211
+
2212
+ copyLocalBtn.onclick = function () {
2213
+ if (!currentTpl) return;
2214
+ setBtnLoading(copyLocalBtn, '复制中...');
2215
+ setFooterStatus('');
2216
+ fetch(MYCLAW_API_BASE + '/api/template/copy', {
2217
+ method: 'POST',
2218
+ headers: { 'Content-Type': 'application/json' },
2219
+ body: JSON.stringify({ folder: currentTpl['文件夹名'], workspace: getWorkspaceId() }),
2220
+ })
2221
+ .then(function (r) { return r.json(); })
2222
+ .then(function (res) {
2223
+ resetBtn(copyLocalBtn);
2224
+ if (res.ok) {
2225
+ setFooterStatus('✓ 已复制到 ' + res.folder_rel_path, '#10b981');
2226
+ } else {
2227
+ setFooterStatus('✗ ' + (res.error || '复制失败'), '#ff4444');
2228
+ }
2229
+ })
2230
+ .catch(function (err) {
2231
+ resetBtn(copyLocalBtn);
2232
+ setFooterStatus('✗ ' + err.message, '#ff4444');
2233
+ });
2234
+ };
2235
+
2236
+ deployBtn.onclick = function () {
2237
+ if (!currentTpl) return;
2238
+ setBtnLoading(deployBtn, '创建中...');
2239
+ setFooterStatus('');
2240
+ fetch(MYCLAW_API_BASE + '/api/template/deploy', {
2241
+ method: 'POST',
2242
+ headers: { 'Content-Type': 'application/json' },
2243
+ body: JSON.stringify({ folder: currentTpl['文件夹名'] }),
2244
+ })
2245
+ .then(function (r) { return r.json(); })
2246
+ .then(function (res) {
2247
+ resetBtn(deployBtn);
2248
+ if (res.ok) {
2249
+ var deploy = res.deploy || {};
2250
+ setFooterStatus('✓ 马上创建完毕...', '#10b981');
2251
+ if (deploy.agent && deploy.session) {
2252
+ var sessionParam = 'agent:' + deploy.agent + ':' + deploy.session;
2253
+ setTimeout(function () {
2254
+ window.location.href = 'http://127.0.0.1:18789/chat?session=' + encodeURIComponent(sessionParam);
2255
+ }, 4000);
2256
+ }
2257
+ } else {
2258
+ setFooterStatus('✗ ' + (res.error || '创建失败'), '#ff4444');
2259
+ }
2260
+ })
2261
+ .catch(function (err) {
2262
+ resetBtn(deployBtn);
2263
+ setFooterStatus('✗ ' + err.message, '#ff4444');
2264
+ });
2265
+ };
2266
+
2267
+ rightPane.appendChild(rightHeader);
2268
+ rightPane.appendChild(previewIframe);
2269
+ rightPane.appendChild(rightFooter);
2270
+ mainArea.appendChild(leftPane);
2271
+ mainArea.appendChild(rightPane);
2272
+ box.appendChild(mainArea);
2273
+ overlay.appendChild(box);
2274
+ document.body.appendChild(overlay);
2275
+
2276
+ // ── 加载模板索引 ──
2277
+ var loadingEl = document.createElement('div');
2278
+ loadingEl.style.cssText = 'padding:24px;color:rgba(205,214,244,0.4);font-size:13px;';
2279
+ loadingEl.textContent = '加载中...';
2280
+ leftPane.appendChild(loadingEl);
2281
+
2282
+ fetch(MYCLAW_API_BASE + '/api/file?path=' + encodeURIComponent(TEMPLATE_ROOT + '/template-index.json'))
2283
+ .then(function (r) {
2284
+ if (!r.ok) throw new Error('HTTP ' + r.status);
2285
+ return r.json();
2286
+ })
2287
+ .then(function (indexData) {
2288
+ // 按系列、编号升序展平
2289
+ var templates = [];
2290
+ Object.keys(indexData).sort().forEach(function (series) {
2291
+ Object.keys(indexData[series]).sort(function (a, b) { return parseInt(a) - parseInt(b); }).forEach(function (num) {
2292
+ templates.push(indexData[series][num]);
2293
+ });
2294
+ });
2295
+
2296
+ leftPane.textContent = '';
2297
+ var activeRow = null;
2298
+
2299
+ templates.forEach(function (tpl, idx) {
2300
+ var row = document.createElement('div');
2301
+ row.style.cssText = 'padding:10px;border-radius:5px;cursor:pointer;transition:background 0.12s;';
2302
+
2303
+ function setActive() {
2304
+ if (activeRow) activeRow.style.background = 'transparent';
2305
+ activeRow = row;
2306
+ row.style.background = 'rgba(100,149,237,0.15)';
2307
+ currentTpl = tpl;
2308
+ setFooterStatus('');
2309
+
2310
+ // 更新右侧 header
2311
+ rightHeader.innerHTML = '';
2312
+
2313
+ var infoSpan = document.createElement('span');
2314
+ infoSpan.style.cssText = 'font-size:12px;color:#cdd6f4;font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;';
2315
+ infoSpan.textContent = tpl['系列'] + tpl['编号'] + ' ' + tpl['名称'];
2316
+
2317
+ var useBtn = document.createElement('button');
2318
+ useBtn.textContent = '✨ 使用模板';
2319
+ useBtn.style.cssText = 'flex-shrink:0;padding:5px 14px;background:#a78bfa;border:none;border-radius:4px;color:#fff;font-size:12px;font-family:monospace;cursor:pointer;transition:background 0.15s;white-space:nowrap;';
2320
+ useBtn.onmouseenter = function () { useBtn.style.background = '#8b5cf6'; };
2321
+ useBtn.onmouseleave = function () { useBtn.style.background = '#a78bfa'; };
2322
+ useBtn.onclick = function () {
2323
+ var promptText = '我要使用' + tpl['系列'] + tpl['编号'] + '模板:' + tpl['名称'] + '。' + tpl['一句话说明'];
2324
+ navigator.clipboard.writeText(promptText).then(function () {
2325
+ useBtn.textContent = '✓ 提示词已复制';
2326
+ useBtn.style.background = '#10b981';
2327
+ setTimeout(function () {
2328
+ useBtn.textContent = '✨ 使用模板';
2329
+ useBtn.style.background = '#a78bfa';
2330
+ }, 2000);
2331
+ });
2332
+ };
2333
+
2334
+ rightHeader.appendChild(infoSpan);
2335
+ rightHeader.appendChild(useBtn);
2336
+
2337
+ // 加载 demo.html(text/html,server 直接返回,iframe 可正常渲染)
2338
+ previewIframe.src = MYCLAW_API_BASE + '/api/file?path='
2339
+ + encodeURIComponent(TEMPLATE_ROOT + '/templates/' + tpl['文件夹名'] + '/demo.html');
2340
+ }
2341
+
2342
+ row.onclick = setActive;
2343
+ row.onmouseenter = function () { if (row !== activeRow) row.style.background = 'rgba(255,255,255,0.06)'; };
2344
+ row.onmouseleave = function () { if (row !== activeRow) row.style.background = 'transparent'; };
2345
+
2346
+ // 系列+编号徽章 + 名称
2347
+ var topRow = document.createElement('div');
2348
+ topRow.style.cssText = 'display:flex;align-items:center;gap:6px;margin-bottom:4px;';
2349
+
2350
+ var badge = document.createElement('span');
2351
+ badge.textContent = tpl['系列'] + tpl['编号'];
2352
+ badge.style.cssText = 'font-size:10px;font-weight:bold;background:#4a4a7a;color:#cdd6f4;padding:1px 7px;border-radius:8px;flex-shrink:0;';
2353
+
2354
+ var nameEl = document.createElement('span');
2355
+ nameEl.textContent = tpl['名称'];
2356
+ nameEl.style.cssText = 'font-size:12px;color:#cdd6f4;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;';
2357
+
2358
+ topRow.appendChild(badge);
2359
+ topRow.appendChild(nameEl);
2360
+
2361
+ // 一句话说明
2362
+ var descEl = document.createElement('div');
2363
+ descEl.textContent = tpl['一句话说明'];
2364
+ descEl.style.cssText = 'font-size:11px;color:rgba(205,214,244,0.5);line-height:1.4;margin-top:2px;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;';
2365
+
2366
+ // 标签
2367
+ var tagsEl = document.createElement('div');
2368
+ tagsEl.style.cssText = 'display:flex;gap:4px;margin-top:5px;flex-wrap:wrap;';
2369
+ [tpl['主能力标签'], tpl['任务类型标签']].filter(Boolean).forEach(function (tag) {
2370
+ var tagEl = document.createElement('span');
2371
+ tagEl.textContent = tag;
2372
+ tagEl.style.cssText = 'font-size:9px;padding:1px 7px;border-radius:8px;background:rgba(167,139,250,0.15);color:#a78bfa;';
2373
+ tagsEl.appendChild(tagEl);
2374
+ });
2375
+
2376
+ row.appendChild(topRow);
2377
+ row.appendChild(descEl);
2378
+ row.appendChild(tagsEl);
2379
+ leftPane.appendChild(row);
2380
+
2381
+ // 默认选中第一个
2382
+ if (idx === 0) setActive();
2383
+ });
2384
+ })
2385
+ .catch(function (err) {
2386
+ leftPane.textContent = '';
2387
+ var errEl = document.createElement('div');
2388
+ errEl.style.cssText = 'padding:24px;color:rgba(205,214,244,0.4);font-size:13px;';
2389
+ errEl.textContent = '加载失败: ' + err.message;
2390
+ leftPane.appendChild(errEl);
2391
+ });
2392
+ }
2393
+
1450
2394
  // ═══ 注入样式 ═══
1451
2395
  function injectStyles() {
1452
2396
  if (document.querySelector('#myclaw-artifacts-styles')) return;