@aiyiran/myclaw 1.1.88 → 1.1.90

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/111.html ADDED
@@ -0,0 +1,437 @@
1
+ <!doctype html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>AI学习小工具创造营|4节课课程大纲</title>
7
+ <style>
8
+ :root {
9
+ --bg: #f6f7fb;
10
+ --card: #ffffff;
11
+ --text: #1f2937;
12
+ --muted: #6b7280;
13
+ --line: #e5e7eb;
14
+ --primary: #2563eb;
15
+ --primary-soft: #eff6ff;
16
+ --accent: #f59e0b;
17
+ --green: #16a34a;
18
+ }
19
+
20
+ * {
21
+ box-sizing: border-box;
22
+ }
23
+
24
+ body {
25
+ margin: 0;
26
+ padding: 28px;
27
+ font-family:
28
+ -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC",
29
+ "Hiragino Sans GB", "Microsoft YaHei", sans-serif;
30
+ background: var(--bg);
31
+ color: var(--text);
32
+ line-height: 1.55;
33
+ }
34
+
35
+ .page {
36
+ max-width: 1180px;
37
+ margin: 0 auto;
38
+ background: var(--card);
39
+ border-radius: 24px;
40
+ padding: 34px 38px 36px;
41
+ box-shadow: 0 18px 50px rgba(15, 23, 42, 0.08);
42
+ }
43
+
44
+ .header {
45
+ display: grid;
46
+ grid-template-columns: 1.5fr 1fr;
47
+ gap: 24px;
48
+ align-items: end;
49
+ margin-bottom: 24px;
50
+ }
51
+
52
+ .eyebrow {
53
+ display: inline-flex;
54
+ align-items: center;
55
+ gap: 8px;
56
+ padding: 6px 12px;
57
+ border-radius: 999px;
58
+ background: var(--primary-soft);
59
+ color: var(--primary);
60
+ font-size: 14px;
61
+ font-weight: 700;
62
+ margin-bottom: 12px;
63
+ }
64
+
65
+ h1 {
66
+ margin: 0;
67
+ font-size: 36px;
68
+ line-height: 1.18;
69
+ letter-spacing: -0.5px;
70
+ }
71
+
72
+ .subtitle {
73
+ margin: 12px 0 0;
74
+ color: var(--muted);
75
+ font-size: 17px;
76
+ }
77
+
78
+ .price-card {
79
+ justify-self: end;
80
+ width: 100%;
81
+ max-width: 330px;
82
+ border: 1px solid var(--line);
83
+ border-radius: 20px;
84
+ padding: 18px 20px;
85
+ background: linear-gradient(180deg, #ffffff 0%, #f8fafc 100%);
86
+ }
87
+
88
+ .price-title {
89
+ color: var(--muted);
90
+ font-size: 14px;
91
+ font-weight: 700;
92
+ margin-bottom: 6px;
93
+ }
94
+
95
+ .price {
96
+ font-size: 30px;
97
+ font-weight: 850;
98
+ color: var(--primary);
99
+ margin: 0;
100
+ }
101
+
102
+ .price-note {
103
+ margin: 6px 0 0;
104
+ color: var(--muted);
105
+ font-size: 14px;
106
+ }
107
+
108
+ .summary {
109
+ display: grid;
110
+ grid-template-columns: repeat(3, 1fr);
111
+ gap: 14px;
112
+ margin: 24px 0 24px;
113
+ }
114
+
115
+ .summary-item {
116
+ border: 1px solid var(--line);
117
+ border-radius: 18px;
118
+ padding: 16px 18px;
119
+ background: #fff;
120
+ }
121
+
122
+ .summary-label {
123
+ color: var(--muted);
124
+ font-size: 13px;
125
+ font-weight: 700;
126
+ margin-bottom: 5px;
127
+ }
128
+
129
+ .summary-value {
130
+ font-size: 17px;
131
+ font-weight: 800;
132
+ }
133
+
134
+ table {
135
+ width: 100%;
136
+ border-collapse: separate;
137
+ border-spacing: 0;
138
+ overflow: hidden;
139
+ border: 1px solid var(--line);
140
+ border-radius: 18px;
141
+ font-size: 15px;
142
+ }
143
+
144
+ thead th {
145
+ background: #111827;
146
+ color: #ffffff;
147
+ text-align: left;
148
+ font-size: 14px;
149
+ padding: 14px 14px;
150
+ white-space: nowrap;
151
+ }
152
+
153
+ tbody td {
154
+ padding: 16px 14px;
155
+ border-top: 1px solid var(--line);
156
+ vertical-align: top;
157
+ background: #ffffff;
158
+ }
159
+
160
+ tbody tr:nth-child(even) td {
161
+ background: #fafafa;
162
+ }
163
+
164
+ .lesson {
165
+ width: 72px;
166
+ font-weight: 850;
167
+ color: var(--primary);
168
+ white-space: nowrap;
169
+ }
170
+
171
+ .project {
172
+ min-width: 150px;
173
+ font-weight: 850;
174
+ font-size: 16px;
175
+ }
176
+
177
+ .tag {
178
+ display: inline-block;
179
+ margin-top: 8px;
180
+ padding: 3px 8px;
181
+ border-radius: 999px;
182
+ background: var(--primary-soft);
183
+ color: var(--primary);
184
+ font-size: 12px;
185
+ font-weight: 750;
186
+ }
187
+
188
+ .outcome {
189
+ font-weight: 760;
190
+ color: #111827;
191
+ }
192
+
193
+ .iteration {
194
+ color: #374151;
195
+ }
196
+
197
+ .iteration strong {
198
+ color: var(--green);
199
+ }
200
+
201
+ .footer {
202
+ display: grid;
203
+ grid-template-columns: 1.2fr 1fr;
204
+ gap: 18px;
205
+ margin-top: 22px;
206
+ }
207
+
208
+ .note-box {
209
+ border-radius: 18px;
210
+ padding: 18px 20px;
211
+ background: #f8fafc;
212
+ border: 1px solid var(--line);
213
+ }
214
+
215
+ .note-box h3 {
216
+ margin: 0 0 8px;
217
+ font-size: 17px;
218
+ }
219
+
220
+ .note-box p {
221
+ margin: 0;
222
+ color: var(--muted);
223
+ font-size: 14.5px;
224
+ }
225
+
226
+ .highlight {
227
+ color: var(--accent);
228
+ font-weight: 850;
229
+ }
230
+
231
+ @media print {
232
+ body {
233
+ background: #ffffff;
234
+ padding: 0;
235
+ }
236
+
237
+ .page {
238
+ max-width: none;
239
+ min-height: 100vh;
240
+ box-shadow: none;
241
+ border-radius: 0;
242
+ padding: 20px 22px;
243
+ }
244
+
245
+ h1 {
246
+ font-size: 30px;
247
+ }
248
+
249
+ .subtitle {
250
+ font-size: 15px;
251
+ }
252
+
253
+ table {
254
+ font-size: 13px;
255
+ }
256
+
257
+ thead th,
258
+ tbody td {
259
+ padding: 10px 10px;
260
+ }
261
+
262
+ .summary {
263
+ margin: 16px 0;
264
+ }
265
+
266
+ .footer {
267
+ margin-top: 14px;
268
+ }
269
+ }
270
+
271
+ @media (max-width: 900px) {
272
+ body {
273
+ padding: 14px;
274
+ }
275
+
276
+ .page {
277
+ padding: 24px 18px;
278
+ }
279
+
280
+ .header,
281
+ .summary,
282
+ .footer {
283
+ grid-template-columns: 1fr;
284
+ }
285
+
286
+ .price-card {
287
+ justify-self: stretch;
288
+ max-width: none;
289
+ }
290
+
291
+ table {
292
+ display: block;
293
+ overflow-x: auto;
294
+ white-space: normal;
295
+ }
296
+ }
297
+ </style>
298
+ </head>
299
+ <body>
300
+ <main class="page">
301
+ <section class="header">
302
+ <div>
303
+ <div class="eyebrow">4节课 · 每节一个作品 · 课后继续迭代</div>
304
+ <h1>AI学习小工具创造营</h1>
305
+ <p class="subtitle">
306
+ 孩子借助AI完成4个可展示、可使用的小工具,把AI从“会聊天”变成“能帮助学习和表达”的实际能力。
307
+ </p>
308
+ </div>
309
+
310
+ <aside class="price-card">
311
+ <div class="price-title">课程价格</div>
312
+ <p class="price">600元 / 节</p>
313
+ <p class="price-note">
314
+ 共4节课,总价2400元。每节课完成一个作品,课后至少迭代一个版本。
315
+ </p>
316
+ </aside>
317
+ </section>
318
+
319
+ <section class="summary">
320
+ <div class="summary-item">
321
+ <div class="summary-label">课程目标</div>
322
+ <div class="summary-value">做出自己的AI学习成长系统</div>
323
+ </div>
324
+ <div class="summary-item">
325
+ <div class="summary-label">学习方式</div>
326
+ <div class="summary-value">项目制创作 + AI协作 + 作品迭代</div>
327
+ </div>
328
+ <div class="summary-item">
329
+ <div class="summary-label">最终成果</div>
330
+ <div class="summary-value">4个作品链接 + 4次课后升级记录</div>
331
+ </div>
332
+ </section>
333
+
334
+ <table>
335
+ <thead>
336
+ <tr>
337
+ <th>课次</th>
338
+ <th>项目作品</th>
339
+ <th>课堂完成内容</th>
340
+ <th>重点能力</th>
341
+ <th>课后迭代</th>
342
+ <th>家长能看到什么</th>
343
+ </tr>
344
+ </thead>
345
+ <tbody>
346
+ <tr>
347
+ <td class="lesson">第1节</td>
348
+ <td>
349
+ <div class="project">AI单词卡片PK系统</div>
350
+ <span class="tag">英语学习工具</span>
351
+ </td>
352
+ <td>
353
+ 制作一个单词卡片工具:正面是单词,背面是AI配图、解释和记忆提示;支持录入新单词;后半节加入PK、奖励和积分规则。
354
+ </td>
355
+ <td>把学习内容变成可交互卡片;用AI生成解释、图片和记忆线索。</td>
356
+ <td class="iteration">
357
+ <strong>新增10个单词</strong>,并调整一种奖励规则。
358
+ </td>
359
+ <td class="outcome">孩子做出一个可以真实背单词的小工具。</td>
360
+ </tr>
361
+
362
+ <tr>
363
+ <td class="lesson">第2节</td>
364
+ <td>
365
+ <div class="project">学习激励系统设计</div>
366
+ <span class="tag">学习习惯工具</span>
367
+ </td>
368
+ <td>
369
+ 设计一套帮助自己坚持学习的反馈系统:每日目标、完成记录、进步提示、成就徽章、学习等级、阶段奖励等,并接入前一节作品。
370
+ </td>
371
+ <td>理解学习习惯背后的结构:目标、记录、反馈、鼓励和长期坚持。</td>
372
+ <td class="iteration">
373
+ 给单词卡片增加一种<strong>学习激励机制</strong>,例如连续学习记录、进步提醒或阶段成就。
374
+ </td>
375
+ <td class="outcome">
376
+ 孩子开始理解:好工具不只是能用,还要让人更愿意坚持使用。
377
+ </td>
378
+ </tr>
379
+
380
+ <tr>
381
+ <td class="lesson">第3节</td>
382
+ <td>
383
+ <div class="project">每日任务记事本</div>
384
+ <span class="tag">作业整理工具</span>
385
+ </td>
386
+ <td>
387
+ 孩子口头描述今天作业,AI整理成清晰步骤;任务自动记录到数据库;前端显示成今日任务界面,完成后获得反馈。
388
+ </td>
389
+ <td>把模糊任务说清楚;让AI拆解流程;把真实作业变成可执行步骤。</td>
390
+ <td class="iteration">
391
+ 用它记录一天真实作业,并完成一次<strong>任务打卡</strong>。
392
+ </td>
393
+ <td class="outcome">
394
+ 孩子能把每天作业整理成清晰步骤,降低拖延和启动难度。
395
+ </td>
396
+ </tr>
397
+
398
+ <tr>
399
+ <td class="lesson">第4节</td>
400
+ <td>
401
+ <div class="project">知识答题大闯关</div>
402
+ <span class="tag">学科理解工具</span>
403
+ </td>
404
+ <td>
405
+ 输入一个学不明白的知识点或题目,AI进行引导讲解;记录每日收获;一键生成相关选择题,用答题形式检查理解。
406
+ </td>
407
+ <td>
408
+ 学会向AI提问、追问和复盘;把不会的内容转成讲解、记录和自测。
409
+ </td>
410
+ <td class="iteration">
411
+ 选择一个真实学科问题,生成一组<strong>5题小测验</strong>。
412
+ </td>
413
+ <td class="outcome">
414
+ 孩子能用AI辅助理解知识,并把理解结果变成可检查的练习。
415
+ </td>
416
+ </tr>
417
+ </tbody>
418
+ </table>
419
+
420
+ <section class="footer">
421
+ <div class="note-box">
422
+ <h3>对家长的核心承诺</h3>
423
+ <p>
424
+ 每节课不是听概念,而是完成一个真实作品;每个作品都要求课后继续升级,让孩子经历“做出来
425
+ → 用起来 → 改一版”的完整过程。
426
+ </p>
427
+ </div>
428
+ <div class="note-box">
429
+ <h3>课程边界</h3>
430
+ <p>
431
+ AI负责辅助表达、拆解、讲解和生成练习;老师会引导孩子判断结果是否合理,避免盲目依赖AI。
432
+ </p>
433
+ </div>
434
+ </section>
435
+ </main>
436
+ </body>
437
+ </html>
@@ -460,10 +460,10 @@
460
460
 
461
461
  // 黑名单:隐藏以下文件(__XXX__ 格式的内部文件以及系统文件),豁免 student-view.html
462
462
  var ARTIFACT_BLACKLIST = [
463
- '__demo__.html',
464
463
  '__student__.json',
465
464
  '__teacher__.json',
466
465
  '__teacher-view__.html',
466
+ '__任务__.html',
467
467
  'USER.md',
468
468
  'TOOLS.md',
469
469
  'SOUL.md',
@@ -499,6 +499,14 @@
499
499
  });
500
500
  }
501
501
 
502
+ // 统计重名文件名
503
+ var fileNameCount = {};
504
+ sorted.forEach(function (asset) {
505
+ var p = asset.path || asset.name || '';
506
+ var base = p.split('/').filter(Boolean).pop() || p;
507
+ fileNameCount[base] = (fileNameCount[base] || 0) + 1;
508
+ });
509
+
502
510
  sorted.forEach(function (asset, idx) {
503
511
  // 状态判断:基于 last_open 时间戳
504
512
  // - 无 last_open → [最新](从未点开过)
@@ -632,10 +640,18 @@
632
640
 
633
641
  var fullPath = asset.path || asset.name || '未命名';
634
642
  var parts = fullPath.split('/').filter(function (p) { return p !== ''; });
635
- // 上行:相对路径(除文件名外的所有目录)
636
- // 下行:文件名
637
- var fileName = parts[parts.length - 1] || fullPath;
638
- var dirPath = parts.length >= 2 ? parts.slice(0, parts.length - 1).join('/') + '/' : '';
643
+ var baseName = parts[parts.length - 1] || fullPath;
644
+ var isDuplicated = (fileNameCount[baseName] || 0) > 1;
645
+ // 重名时:下行 = 父目录/文件名,上行 = 再上层路径
646
+ // 无重名:下行 = 文件名,上行 = 完整目录路径
647
+ var fileName, dirPath;
648
+ if (isDuplicated && parts.length >= 2) {
649
+ fileName = parts.slice(parts.length - 2).join('/');
650
+ dirPath = parts.length >= 3 ? parts.slice(0, parts.length - 2).join('/') + '/' : '';
651
+ } else {
652
+ fileName = baseName;
653
+ dirPath = parts.length >= 2 ? parts.slice(0, parts.length - 1).join('/') + '/' : '';
654
+ }
639
655
 
640
656
  // 上行:相对路径(灰色极小字)
641
657
  if (dirPath) {
@@ -2355,9 +2371,8 @@
2355
2371
 
2356
2372
  var deployBtn = document.createElement('button');
2357
2373
  deployBtn.textContent = '⬇创建 -> 训练场';
2358
- deployBtn.style.cssText = _btnMuted;
2359
- deployBtn.onmouseenter = function () { deployBtn.style.background = 'rgba(167,139,250,0.22)'; };
2360
- deployBtn.onmouseleave = function () { deployBtn.style.background = 'rgba(167,139,250,0.12)'; };
2374
+ deployBtn.disabled = true;
2375
+ 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;';
2361
2376
 
2362
2377
  var copyLocalBtn = document.createElement('button');
2363
2378
  copyLocalBtn.textContent = '⬇创建 -> 新伙伴';
@@ -2382,9 +2397,9 @@
2382
2397
  var footerStatus = document.createElement('span');
2383
2398
  footerStatus.style.cssText = 'font-size:11px;color:rgba(205,214,244,0.45);flex:1;';
2384
2399
 
2385
- rightFooter.appendChild(deployBtn);
2386
2400
  rightFooter.appendChild(copyLocalBtn);
2387
2401
  rightFooter.appendChild(promptBtn);
2402
+ rightFooter.appendChild(deployBtn);
2388
2403
  rightFooter.appendChild(footerStatus);
2389
2404
 
2390
2405
  // currentTpl 在 setActive 时更新,按钮 onclick 通过闭包读取
@@ -27,10 +27,13 @@ const DEFAULT_MINIMAX_KEY = "sk-cp-DC5lWd2Stt9CBFzLIT2awP4K-ZEn5AkYwjl3Cdj-mIBmg
27
27
  function run(cliArgs) {
28
28
  // 解析参数
29
29
  let apiKey = null;
30
+ let forceDefault = false;
30
31
  for (let i = 0; i < cliArgs.length; i++) {
31
32
  if (cliArgs[i] === '--key' && cliArgs[i + 1]) {
32
33
  apiKey = cliArgs[i + 1];
33
34
  i++;
35
+ } else if (cliArgs[i] === '--force') {
36
+ forceDefault = true;
34
37
  }
35
38
  }
36
39
 
@@ -88,9 +91,14 @@ function run(cliArgs) {
88
91
  // ─── 3. 默认聊天模型 ───
89
92
  if (!config.agents) config.agents = {};
90
93
  if (!config.agents.defaults) config.agents.defaults = {};
91
- config.agents.defaults.model = {
92
- primary: "minimax/MiniMax-M2.7-highspeed"
93
- };
94
+ if (forceDefault) {
95
+ config.agents.defaults.model = {
96
+ primary: "minimax/MiniMax-M2.7-highspeed"
97
+ };
98
+ console.log('⚡ --force: 已覆盖默认模型为 minimax/MiniMax-M2.7-highspeed');
99
+ } else {
100
+ console.log('⊘ 跳过默认模型设置 (传入 --force 可覆盖)');
101
+ }
94
102
 
95
103
  // ─── 4. 白名单,清理残留 ───
96
104
  if (!config.agents.defaults.models) config.agents.defaults.models = {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiyiran/myclaw",
3
- "version": "1.1.88",
3
+ "version": "1.1.90",
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):
@@ -42,13 +42,19 @@ def main():
42
42
  data = json.loads(input_path.read_text(encoding='utf-8'))
43
43
  template = template_path.read_text(encoding='utf-8')
44
44
 
45
+ folder = input_path.parent
46
+ if (folder / '__任务__.html').exists():
47
+ preview_file = '__任务__.html'
48
+ else:
49
+ preview_file = data.get('示例页面文件', '__demo__.html')
50
+
45
51
  rendered = (template
46
52
  .replace('{{任务标题}}', esc(data.get('任务标题', '未命名任务')))
47
53
  .replace('{{一句话说明}}', esc(data.get('一句话说明', '')))
48
54
  .replace('{{步骤区域}}', render_steps(data.get('步骤', [])))
49
55
  .replace('{{评价目标区域}}', render_goals(data.get('评价目标', [])))
50
56
  .replace('{{示例区标题}}', esc(data.get('示例区标题', '示范样例')))
51
- .replace('{{示例页面文件}}', esc(data.get('示例页面文件', 'example.html')))
57
+ .replace('{{示例页面文件}}', esc(preview_file))
52
58
  .replace('{{小提示区域}}', render_tips(data.get('小提示', [])))
53
59
  )
54
60