@aiyiran/myclaw 1.1.87 → 1.1.89

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.
@@ -632,32 +632,23 @@
632
632
 
633
633
  var fullPath = asset.path || asset.name || '未命名';
634
634
  var parts = fullPath.split('/').filter(function (p) { return p !== ''; });
635
- // 上行:倒数第二层以上的路径(若只有一层目录则无上行)
636
- // 下行:直接父文件夹/文件名
637
- var topPart = '';
638
- var bottomPart = fullPath;
639
- if (parts.length >= 3) {
640
- // 有多层:上行 = 除最后两段外的路径,下行 = 最后两段
641
- topPart = parts.slice(0, parts.length - 2).join('/') + '/';
642
- bottomPart = parts.slice(parts.length - 2).join('/');
643
- } else if (parts.length === 2) {
644
- // 只有一层目录:只显示下行(dir/file)
645
- topPart = '';
646
- bottomPart = parts.join('/');
647
- }
635
+ // 上行:相对路径(除文件名外的所有目录)
636
+ // 下行:文件名
637
+ var fileName = parts[parts.length - 1] || fullPath;
638
+ var dirPath = parts.length >= 2 ? parts.slice(0, parts.length - 1).join('/') + '/' : '';
648
639
 
649
- // 上行:更上层路径(灰色极小字)
650
- if (topPart) {
640
+ // 上行:相对路径(灰色极小字)
641
+ if (dirPath) {
651
642
  var dirSpan = document.createElement('span');
652
- dirSpan.textContent = topPart;
643
+ dirSpan.textContent = dirPath;
653
644
  dirSpan.title = fullPath;
654
- dirSpan.style.cssText = 'color:#666;font-size:9px;line-height:1.3;white-space:nowrap;';
645
+ dirSpan.style.cssText = 'color:#666;font-size:9px;line-height:1.3;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;';
655
646
  fname.appendChild(dirSpan);
656
647
  }
657
648
 
658
- // 下行:直接父文件夹/文件名(大字体)
649
+ // 下行:文件名(大字体)
659
650
  var nameSpan = document.createElement('span');
660
- nameSpan.textContent = bottomPart;
651
+ nameSpan.textContent = fileName;
661
652
  nameSpan.title = fullPath;
662
653
  nameSpan.style.cssText = 'font-size:13px;font-weight:500;line-height:1.5;white-space:nowrap;';
663
654
  fname.appendChild(nameSpan);
@@ -1230,8 +1221,6 @@
1230
1221
  row.appendChild(row3);
1231
1222
  leftPane.appendChild(row);
1232
1223
 
1233
- // 默认选中第一条并预览
1234
- if (idx === 0) setActive();
1235
1224
  });
1236
1225
  }
1237
1226
 
@@ -2366,9 +2355,8 @@
2366
2355
 
2367
2356
  var deployBtn = document.createElement('button');
2368
2357
  deployBtn.textContent = '⬇创建 -> 训练场';
2369
- deployBtn.style.cssText = _btnMuted;
2370
- deployBtn.onmouseenter = function () { deployBtn.style.background = 'rgba(167,139,250,0.22)'; };
2371
- deployBtn.onmouseleave = function () { deployBtn.style.background = 'rgba(167,139,250,0.12)'; };
2358
+ deployBtn.disabled = true;
2359
+ deployBtn.style.cssText = _btnBase + 'background:rgba(255,255,255,0.05);border:1px solid rgba(255,255,255,0.1);color:rgba(205,214,244,0.3);cursor:not-allowed;';
2372
2360
 
2373
2361
  var copyLocalBtn = document.createElement('button');
2374
2362
  copyLocalBtn.textContent = '⬇创建 -> 新伙伴';
@@ -2393,9 +2381,9 @@
2393
2381
  var footerStatus = document.createElement('span');
2394
2382
  footerStatus.style.cssText = 'font-size:11px;color:rgba(205,214,244,0.45);flex:1;';
2395
2383
 
2396
- rightFooter.appendChild(deployBtn);
2397
2384
  rightFooter.appendChild(copyLocalBtn);
2398
2385
  rightFooter.appendChild(promptBtn);
2386
+ rightFooter.appendChild(deployBtn);
2399
2387
  rightFooter.appendChild(footerStatus);
2400
2388
 
2401
2389
  // currentTpl 在 setActive 时更新,按钮 onclick 通过闭包读取
@@ -2594,7 +2582,7 @@
2594
2582
  // ── 并行:加载本地 index + 检查 CDN 更新 ──────────────────────────────
2595
2583
  var _pollTimer = null; // 提到外层,doSync 守卫用
2596
2584
 
2597
- function doSync() {
2585
+ function doSync(force) {
2598
2586
  // 轮询进行中时点击无效,避免重复起 timer
2599
2587
  if (_pollTimer) return;
2600
2588
  headSyncBtn.disabled = true;
@@ -2646,7 +2634,7 @@
2646
2634
  }, 3000);
2647
2635
  }
2648
2636
 
2649
- fetch(MYCLAW_API_BASE + '/api/sync-templates?force=1')
2637
+ fetch(MYCLAW_API_BASE + '/api/sync-templates' + (force ? '?force=1' : ''))
2650
2638
  .then(function (r) { return r.json(); })
2651
2639
  .then(function (sync) {
2652
2640
  headSyncBtn.disabled = false;
@@ -2680,7 +2668,7 @@
2680
2668
  });
2681
2669
  }
2682
2670
 
2683
- headSyncBtn.onclick = doSync;
2671
+ headSyncBtn.onclick = function () { doSync(true); };
2684
2672
  doSync();
2685
2673
 
2686
2674
  fetch(MYCLAW_API_BASE + '/api/file?path=' + encodeURIComponent(TEMPLATE_ROOT + '/template-index.json'))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiyiran/myclaw",
3
- "version": "1.1.87",
3
+ "version": "1.1.89",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -65,6 +65,9 @@ HISTORY_FILENAME = "history.json"
65
65
  _rollback_suppressed: dict = {}
66
66
  _ROLLBACK_SUPPRESS_TTL = 3.0 # 秒
67
67
 
68
+ # 时间窗口合并:同一文件在此时间窗口内的多次变更只保留最新快照(覆盖,不新增版本)
69
+ _HISTORY_MERGE_WINDOW = 20.0 # 秒
70
+
68
71
 
69
72
  def _is_rollback_suppressed(abs_path: str) -> bool:
70
73
  ts = _rollback_suppressed.get(abs_path)
@@ -94,36 +97,70 @@ def save_history_version(workspace_id: str, relative_path: str, file_path: str):
94
97
 
95
98
  history_data = _load_history_json(history_json_path, workspace_id)
96
99
 
97
- next_ver = history_data.get("next_version", 1)
98
- ver_dir_name = f"v{next_ver}"
100
+ # 检查该文件最后一次快照是否在合并窗口内
101
+ now_ts = time.time()
102
+ records = history_data.get("records", [])
103
+ last_record = next(
104
+ (r for r in reversed(records) if r.get("path") == relative_path),
105
+ None
106
+ )
107
+
108
+ if last_record:
109
+ try:
110
+ last_ts = datetime.fromisoformat(last_record["snapshot_at"]).timestamp()
111
+ except Exception:
112
+ last_ts = 0
113
+ within_window = (now_ts - last_ts) < _HISTORY_MERGE_WINDOW
114
+ else:
115
+ within_window = False
99
116
 
100
- # 复制当前文件内容到 __history__/vN/<relative_path>
101
117
  rel_parts = relative_path.replace("\\", "/").split("/")
102
- dest_path = os.path.join(history_base, ver_dir_name, *rel_parts)
103
- os.makedirs(os.path.dirname(dest_path), exist_ok=True)
104
- try:
105
- with open(file_path, "rb") as src, open(dest_path, "wb") as dst:
106
- dst.write(src.read())
107
- except Exception as e:
108
- print(f"[history] 快照写入失败: {e}")
109
- return
110
118
 
111
- # 更新 next_version、records、current_versions
112
- history_data["next_version"] = next_ver + 1
113
- history_data["records"].append({
114
- "version": next_ver,
115
- "version_dir": ver_dir_name,
116
- "path": relative_path,
117
- "snapshot_at": now_iso()
118
- })
119
- if "current_versions" not in history_data:
120
- history_data["current_versions"] = {}
121
- history_data["current_versions"][relative_path] = next_ver
119
+ if within_window:
120
+ # 覆盖最后一次快照,不新增版本
121
+ ver_dir_name = last_record["version_dir"]
122
+ dest_path = os.path.join(history_base, ver_dir_name, *rel_parts)
123
+ os.makedirs(os.path.dirname(dest_path), exist_ok=True)
124
+ try:
125
+ with open(file_path, "rb") as src, open(dest_path, "wb") as dst:
126
+ dst.write(src.read())
127
+ except Exception as e:
128
+ print(f"[history] 快照覆盖失败: {e}")
129
+ return
130
+ # 更新该 record 的时间戳
131
+ last_record["snapshot_at"] = now_iso()
132
+ with open(history_json_path, "w", encoding="utf-8") as f:
133
+ json.dump(history_data, f, ensure_ascii=False, indent=2)
134
+ print(f"[history] 合并覆盖快照 {ver_dir_name}: {relative_path}")
135
+ else:
136
+ # 超出时间窗口,新增版本
137
+ next_ver = history_data.get("next_version", 1)
138
+ ver_dir_name = f"v{next_ver}"
122
139
 
123
- with open(history_json_path, "w", encoding="utf-8") as f:
124
- json.dump(history_data, f, ensure_ascii=False, indent=2)
140
+ dest_path = os.path.join(history_base, ver_dir_name, *rel_parts)
141
+ os.makedirs(os.path.dirname(dest_path), exist_ok=True)
142
+ try:
143
+ with open(file_path, "rb") as src, open(dest_path, "wb") as dst:
144
+ dst.write(src.read())
145
+ except Exception as e:
146
+ print(f"[history] 快照写入失败: {e}")
147
+ return
148
+
149
+ history_data["next_version"] = next_ver + 1
150
+ history_data["records"].append({
151
+ "version": next_ver,
152
+ "version_dir": ver_dir_name,
153
+ "path": relative_path,
154
+ "snapshot_at": now_iso()
155
+ })
156
+ if "current_versions" not in history_data:
157
+ history_data["current_versions"] = {}
158
+ history_data["current_versions"][relative_path] = next_ver
159
+
160
+ with open(history_json_path, "w", encoding="utf-8") as f:
161
+ json.dump(history_data, f, ensure_ascii=False, indent=2)
125
162
 
126
- print(f"[history] 已保存快照 {ver_dir_name}: {relative_path}")
163
+ print(f"[history] 已保存快照 {ver_dir_name}: {relative_path}")
127
164
 
128
165
 
129
166
  def rollback_to_version(workspace_id: str, relative_path: str, version_dir: str):
@@ -1,5 +1,5 @@
1
1
  {
2
- "index_updated_at": "2026-05-07T09:28:31Z",
2
+ "index_updated_at": "2026-05-08T02:45:34Z",
3
3
  "templates": {
4
4
  "696de592": {
5
5
  "id": "696de592",
@@ -329,9 +329,9 @@
329
329
  "编号": "101",
330
330
  "名称": "万物图签:我的AI卡牌宇宙",
331
331
  "文件夹名": "c101_万物图签:我的AI卡牌宇宙",
332
- "version": "v5",
333
- "路径": "templates/c101_万物图签:我的AI卡牌宇宙/v5",
334
- "入口文件": "templates/c101_万物图签:我的AI卡牌宇宙/v5/index.html",
332
+ "version": "v6",
333
+ "路径": "templates/c101_万物图签:我的AI卡牌宇宙/v6",
334
+ "入口文件": "templates/c101_万物图签:我的AI卡牌宇宙/v6/index.html",
335
335
  "一句话说明": "分四个阶段逐步做出你的精灵卡牌:先让角色诞生,再加属性和特效,最后扩展成一个系列。",
336
336
  "主能力标签": "第四能力:迭代推进",
337
337
  "任务类型标签": "视听表达",
@@ -348,7 +348,7 @@
348
348
  "第四能力:迭代推进",
349
349
  "视听表达"
350
350
  ],
351
- "updated_at": "2026-05-07T09:02:35Z"
351
+ "updated_at": "2026-05-08T02:45:29Z"
352
352
  },
353
353
  "cf03758c": {
354
354
  "id": "cf03758c",
@@ -112,9 +112,9 @@
112
112
 
113
113
  ## C101 万物图签:我的AI卡牌宇宙
114
114
  - ID:a2f9502c
115
- - 版本:v5
116
- - 路径:templates/c101_万物图签:我的AI卡牌宇宙/v5
117
- - 入口文件:templates/c101_万物图签:我的AI卡牌宇宙/v5/index.html
115
+ - 版本:v6
116
+ - 路径:templates/c101_万物图签:我的AI卡牌宇宙/v6
117
+ - 入口文件:templates/c101_万物图签:我的AI卡牌宇宙/v6/index.html
118
118
  - 主能力标签:第四能力:迭代推进
119
119
  - 任务类型标签:视听表达
120
120
  - 关键词:c101_万物图签:我的AI卡牌宇宙, 分四个阶段逐步做出你的精灵卡牌:先让角色诞生,再加属性和特效,最后扩展成一个系列。, 已经做过基础卡牌任务的学生, 对卡牌游戏有兴趣、想做出有系统感作品的学生, 会做但容易停在第一版不改的学生, 在复杂框架上读懂结构,再做定向修改, 规划主题和阵营分组逻辑, 明确识别问题并做一次有意识的迭代升级, 万物图签:我的AI卡牌宇宙, 第四能力:迭代推进, 视听表达