@aiyiran/myclaw 1.0.209 → 1.0.210
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/assets/myclaw-artifacts.js +245 -27
- package/index.js +201 -48
- package/package.json +1 -1
- package/skills/yiran-skill-media/config.json +6 -6
- package/skills/yiran-skill-media/scripts/__pycache__/generate.cpython-311.pyc +0 -0
- package/skills/yiran-skill-media/scripts/generate.py +73 -7
- package/skills/yiran-skill-media/scripts/generation_log.json +36 -0
|
@@ -137,13 +137,25 @@
|
|
|
137
137
|
].join(';');
|
|
138
138
|
header.innerHTML = '<span>\uD83C\uDFA8 \u5B66\u751F\u4F5C\u54C1</span>';
|
|
139
139
|
|
|
140
|
+
var headerRight = document.createElement('span');
|
|
141
|
+
headerRight.style.cssText = 'display:flex;align-items:center;gap:10px;';
|
|
142
|
+
|
|
143
|
+
var publishBtn = document.createElement('span');
|
|
144
|
+
publishBtn.textContent = '\u53D1\u5E03';
|
|
145
|
+
publishBtn.style.cssText = 'cursor:pointer;padding:2px 10px;border-radius:3px;font-size:12px;background:rgba(255,255,255,0.08);transition:background 0.15s;';
|
|
146
|
+
publishBtn.onmouseenter = function () { publishBtn.style.background = 'rgba(255,255,255,0.18)'; };
|
|
147
|
+
publishBtn.onmouseleave = function () { publishBtn.style.background = 'rgba(255,255,255,0.08)'; };
|
|
148
|
+
publishBtn.onclick = function () { openPublishModal(); };
|
|
149
|
+
headerRight.appendChild(publishBtn);
|
|
150
|
+
|
|
140
151
|
var closeBtn = document.createElement('span');
|
|
141
152
|
closeBtn.textContent = '\u2715';
|
|
142
153
|
closeBtn.style.cssText = 'cursor:pointer;padding:2px 6px;border-radius:3px;font-size:14px;transition:background 0.15s;';
|
|
143
154
|
closeBtn.onmouseenter = function () { closeBtn.style.background = 'rgba(255,255,255,0.1)'; };
|
|
144
155
|
closeBtn.onmouseleave = function () { closeBtn.style.background = 'none'; };
|
|
145
156
|
closeBtn.onclick = function () { closeArtifactsPanel(); };
|
|
146
|
-
|
|
157
|
+
headerRight.appendChild(closeBtn);
|
|
158
|
+
header.appendChild(headerRight);
|
|
147
159
|
|
|
148
160
|
// 内容区
|
|
149
161
|
var content = document.createElement('div');
|
|
@@ -235,13 +247,32 @@
|
|
|
235
247
|
].join(';');
|
|
236
248
|
tableHeader.innerHTML = [
|
|
237
249
|
'<span style="width:30px;text-align:center;">#</span>',
|
|
238
|
-
'<span style="flex:2;"
|
|
239
|
-
'<span style="flex:1
|
|
250
|
+
'<span style="flex:2;">文件名</span>',
|
|
251
|
+
'<span style="flex:1;text-align:center;">类别</span>',
|
|
240
252
|
'<span style="flex:1;text-align:right;">更新时间</span>',
|
|
241
253
|
].join('');
|
|
242
254
|
container.appendChild(tableHeader);
|
|
243
255
|
|
|
244
|
-
|
|
256
|
+
// 按 updated_at 从新到旧排序
|
|
257
|
+
var sorted = data.assets.slice().sort(function (a, b) {
|
|
258
|
+
var ta = a.updated_at || '';
|
|
259
|
+
var tb = b.updated_at || '';
|
|
260
|
+
return tb.localeCompare(ta);
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
var topUpdatedAt = data.updated_at || '';
|
|
264
|
+
|
|
265
|
+
sorted.forEach(function (asset, idx) {
|
|
266
|
+
// 判断是否为最新(asset 的 updated_at 与顶层一致)
|
|
267
|
+
var isLatest = topUpdatedAt && asset.updated_at === topUpdatedAt;
|
|
268
|
+
var latestSeenKey = 'myclaw-artifacts-latest-seen-' + (asset.id || asset.path);
|
|
269
|
+
var isLatestSeen = localStorage.getItem(latestSeenKey);
|
|
270
|
+
|
|
271
|
+
// 判断是否有更新(created_at !== updated_at)
|
|
272
|
+
var isUpdated = asset.created_at && asset.updated_at && asset.created_at !== asset.updated_at;
|
|
273
|
+
var updateSeenKey = 'myclaw-artifacts-update-seen-' + (asset.id || asset.path);
|
|
274
|
+
var isUpdateSeen = localStorage.getItem(updateSeenKey);
|
|
275
|
+
|
|
245
276
|
var row = document.createElement('div');
|
|
246
277
|
row.style.cssText = [
|
|
247
278
|
'display: flex',
|
|
@@ -253,31 +284,47 @@
|
|
|
253
284
|
].join(';');
|
|
254
285
|
row.onmouseenter = function () { row.style.background = '#2d2d3f'; };
|
|
255
286
|
row.onmouseleave = function () { row.style.background = 'transparent'; };
|
|
256
|
-
row.onclick = function () {
|
|
287
|
+
row.onclick = function () {
|
|
288
|
+
if (isLatest && !isLatestSeen) localStorage.setItem(latestSeenKey, '1');
|
|
289
|
+
if (isUpdated && !isUpdateSeen) localStorage.setItem(updateSeenKey, '1');
|
|
290
|
+
openPreviewModal(data, asset);
|
|
291
|
+
};
|
|
257
292
|
|
|
258
293
|
// 编号
|
|
259
294
|
var num = document.createElement('span');
|
|
260
295
|
num.style.cssText = 'width:30px;text-align:center;color:#888;flex-shrink:0;';
|
|
261
296
|
num.textContent = String(idx + 1);
|
|
262
297
|
|
|
263
|
-
//
|
|
264
|
-
var title = document.createElement('span');
|
|
265
|
-
title.style.cssText = 'flex:2;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;';
|
|
266
|
-
title.textContent = asset.name || '未命名';
|
|
267
|
-
title.title = asset.name || '';
|
|
268
|
-
|
|
269
|
-
// 文件名
|
|
298
|
+
// 文件名(从 path 提取)+ 标记
|
|
270
299
|
var fname = document.createElement('span');
|
|
271
|
-
fname.style.cssText = 'flex:
|
|
272
|
-
// 从 path 中提取文件名
|
|
300
|
+
fname.style.cssText = 'flex:2;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;display:flex;align-items:center;gap:6px;';
|
|
273
301
|
var pathParts = (asset.path || '').split('/');
|
|
274
|
-
|
|
275
|
-
|
|
302
|
+
var nameSpan = document.createElement('span');
|
|
303
|
+
nameSpan.textContent = pathParts[pathParts.length - 1] || asset.name || '未命名';
|
|
304
|
+
nameSpan.title = asset.path || '';
|
|
305
|
+
fname.appendChild(nameSpan);
|
|
306
|
+
|
|
307
|
+
// [最新] 优先于 [有更新]
|
|
308
|
+
if (isLatest && !isLatestSeen) {
|
|
309
|
+
var badge = document.createElement('span');
|
|
310
|
+
badge.style.cssText = 'color:#ff4444;font-size:10px;font-weight:bold;flex-shrink:0;';
|
|
311
|
+
badge.textContent = '[最新]';
|
|
312
|
+
fname.appendChild(badge);
|
|
313
|
+
} else if (isUpdated && !isUpdateSeen) {
|
|
314
|
+
var badge = document.createElement('span');
|
|
315
|
+
badge.style.cssText = 'color:#10b981;font-size:10px;font-weight:bold;flex-shrink:0;';
|
|
316
|
+
badge.textContent = '[有更新]';
|
|
317
|
+
fname.appendChild(badge);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// 类别
|
|
321
|
+
var type = document.createElement('span');
|
|
322
|
+
type.style.cssText = 'flex:1;text-align:center;color:#888;';
|
|
323
|
+
type.textContent = asset.type ? asset.type.toUpperCase() : '';
|
|
276
324
|
|
|
277
325
|
// 更新时间
|
|
278
326
|
var time = document.createElement('span');
|
|
279
327
|
time.style.cssText = 'flex:1;text-align:right;color:#888;font-size:11px;flex-shrink:0;';
|
|
280
|
-
// 优先用 asset 自身的,fallback 到 data 级别的 updated_at
|
|
281
328
|
var updatedAt = asset.updated_at || data.updated_at || '';
|
|
282
329
|
if (updatedAt) {
|
|
283
330
|
try {
|
|
@@ -289,8 +336,8 @@
|
|
|
289
336
|
}
|
|
290
337
|
|
|
291
338
|
row.appendChild(num);
|
|
292
|
-
row.appendChild(title);
|
|
293
339
|
row.appendChild(fname);
|
|
340
|
+
row.appendChild(type);
|
|
294
341
|
row.appendChild(time);
|
|
295
342
|
container.appendChild(row);
|
|
296
343
|
});
|
|
@@ -318,8 +365,8 @@
|
|
|
318
365
|
|
|
319
366
|
var box = document.createElement('div');
|
|
320
367
|
box.style.cssText = [
|
|
321
|
-
'width:
|
|
322
|
-
'height:
|
|
368
|
+
'width: 98vw',
|
|
369
|
+
'height: 96vh',
|
|
323
370
|
'background: #1e1e2e',
|
|
324
371
|
'border-radius: 8px',
|
|
325
372
|
'overflow: hidden',
|
|
@@ -346,8 +393,8 @@
|
|
|
346
393
|
|
|
347
394
|
var closeBtn = document.createElement('span');
|
|
348
395
|
closeBtn.textContent = '\u2715';
|
|
349
|
-
closeBtn.style.cssText = 'cursor:pointer;padding:
|
|
350
|
-
closeBtn.onmouseenter = function () { closeBtn.style.background = 'rgba(255,255,255,0.
|
|
396
|
+
closeBtn.style.cssText = 'cursor:pointer;padding:4px 10px;border-radius:4px;font-size:18px;font-weight:bold;transition:background 0.15s;';
|
|
397
|
+
closeBtn.onmouseenter = function () { closeBtn.style.background = 'rgba(255,255,255,0.15)'; };
|
|
351
398
|
closeBtn.onmouseleave = function () { closeBtn.style.background = 'none'; };
|
|
352
399
|
closeBtn.onclick = function () { closePreviewModal(); };
|
|
353
400
|
header.appendChild(closeBtn);
|
|
@@ -366,11 +413,6 @@
|
|
|
366
413
|
box.appendChild(iframe);
|
|
367
414
|
overlay.appendChild(box);
|
|
368
415
|
|
|
369
|
-
// 点击遮罩关闭
|
|
370
|
-
overlay.onclick = function (e) {
|
|
371
|
-
if (e.target === overlay) closePreviewModal();
|
|
372
|
-
};
|
|
373
|
-
|
|
374
416
|
document.body.appendChild(overlay);
|
|
375
417
|
}
|
|
376
418
|
|
|
@@ -379,6 +421,182 @@
|
|
|
379
421
|
if (modal) modal.remove();
|
|
380
422
|
}
|
|
381
423
|
|
|
424
|
+
// ═══ 发布弹框 ═══
|
|
425
|
+
function openPublishModal() {
|
|
426
|
+
if (document.querySelector('#myclaw-artifacts-publish-modal')) return;
|
|
427
|
+
if (!cachedData) return;
|
|
428
|
+
|
|
429
|
+
var overlay = document.createElement('div');
|
|
430
|
+
overlay.id = 'myclaw-artifacts-publish-modal';
|
|
431
|
+
overlay.style.cssText = [
|
|
432
|
+
'position: fixed',
|
|
433
|
+
'top: 0',
|
|
434
|
+
'left: 0',
|
|
435
|
+
'width: 100vw',
|
|
436
|
+
'height: 100vh',
|
|
437
|
+
'background: rgba(0, 0, 0, 0.4)',
|
|
438
|
+
'z-index: 99999',
|
|
439
|
+
'display: flex',
|
|
440
|
+
'align-items: center',
|
|
441
|
+
'justify-content: center',
|
|
442
|
+
'animation: myclaw-fade-in 0.15s ease',
|
|
443
|
+
].join(';');
|
|
444
|
+
|
|
445
|
+
var box = document.createElement('div');
|
|
446
|
+
box.style.cssText = [
|
|
447
|
+
'width: 500px',
|
|
448
|
+
'background: #1e1e2e',
|
|
449
|
+
'border-radius: 8px',
|
|
450
|
+
'overflow: hidden',
|
|
451
|
+
'display: flex',
|
|
452
|
+
'flex-direction: column',
|
|
453
|
+
'box-shadow: 0 8px 32px rgba(0,0,0,0.5)',
|
|
454
|
+
].join(';');
|
|
455
|
+
|
|
456
|
+
// 标题栏
|
|
457
|
+
var header = document.createElement('div');
|
|
458
|
+
header.style.cssText = [
|
|
459
|
+
'display: flex',
|
|
460
|
+
'align-items: center',
|
|
461
|
+
'justify-content: space-between',
|
|
462
|
+
'padding: 10px 14px',
|
|
463
|
+
'background: #2d2d3f',
|
|
464
|
+
'color: #cdd6f4',
|
|
465
|
+
'font-size: 13px',
|
|
466
|
+
'font-family: monospace',
|
|
467
|
+
'user-select: none',
|
|
468
|
+
'flex-shrink: 0',
|
|
469
|
+
].join(';');
|
|
470
|
+
header.innerHTML = '<span>\uD83D\uDE80 \u53D1\u5E03\u4F5C\u54C1</span>';
|
|
471
|
+
|
|
472
|
+
var closeBtn = document.createElement('span');
|
|
473
|
+
closeBtn.textContent = '\u2715';
|
|
474
|
+
closeBtn.style.cssText = 'cursor:pointer;padding:2px 6px;border-radius:3px;font-size:14px;transition:background 0.15s;';
|
|
475
|
+
closeBtn.onmouseenter = function () { closeBtn.style.background = 'rgba(255,255,255,0.1)'; };
|
|
476
|
+
closeBtn.onmouseleave = function () { closeBtn.style.background = 'none'; };
|
|
477
|
+
closeBtn.onclick = function () { closePublishModal(); };
|
|
478
|
+
header.appendChild(closeBtn);
|
|
479
|
+
|
|
480
|
+
// 表单内容
|
|
481
|
+
var form = document.createElement('div');
|
|
482
|
+
form.style.cssText = 'padding: 20px;display:flex;flex-direction:column;gap:16px;color:#cdd6f4;font-family:monospace;font-size:13px;';
|
|
483
|
+
|
|
484
|
+
// 字段 0:作品名称
|
|
485
|
+
var titleGroup = document.createElement('div');
|
|
486
|
+
titleGroup.style.cssText = 'display:flex;flex-direction:column;gap:6px;';
|
|
487
|
+
var titleLabel = document.createElement('label');
|
|
488
|
+
titleLabel.textContent = '\u4F5C\u54C1\u540D\u79F0';
|
|
489
|
+
titleLabel.style.cssText = 'font-size:12px;color:#888;';
|
|
490
|
+
var titleInput = document.createElement('input');
|
|
491
|
+
titleInput.type = 'text';
|
|
492
|
+
titleInput.placeholder = '\u8F93\u5165\u5C55\u793A\u6807\u9898';
|
|
493
|
+
titleInput.value = cachedData.title || '';
|
|
494
|
+
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;';
|
|
495
|
+
titleInput.onfocus = function () { titleInput.style.borderColor = '#6c6caa'; };
|
|
496
|
+
titleInput.onblur = function () { titleInput.style.borderColor = '#3d3d5c'; };
|
|
497
|
+
titleGroup.appendChild(titleLabel);
|
|
498
|
+
titleGroup.appendChild(titleInput);
|
|
499
|
+
form.appendChild(titleGroup);
|
|
500
|
+
|
|
501
|
+
// 字段 1:选择封面图片
|
|
502
|
+
var coverGroup = document.createElement('div');
|
|
503
|
+
coverGroup.style.cssText = 'display:flex;flex-direction:column;gap:6px;';
|
|
504
|
+
var coverLabel = document.createElement('label');
|
|
505
|
+
coverLabel.textContent = '\u9009\u62E9\u5C01\u9762\u56FE\u7247';
|
|
506
|
+
coverLabel.style.cssText = 'font-size:12px;color:#888;';
|
|
507
|
+
var coverRow = document.createElement('div');
|
|
508
|
+
coverRow.style.cssText = 'display:flex;align-items:center;gap:10px;';
|
|
509
|
+
var coverSelect = document.createElement('select');
|
|
510
|
+
coverSelect.style.cssText = 'flex:1;padding:8px 10px;background:#252536;border:1px solid #3d3d5c;border-radius:4px;color:#cdd6f4;font-size:13px;font-family:monospace;outline:none;';
|
|
511
|
+
coverSelect.onfocus = function () { coverSelect.style.borderColor = '#6c6caa'; };
|
|
512
|
+
coverSelect.onblur = function () { coverSelect.style.borderColor = '#3d3d5c'; };
|
|
513
|
+
var coverDefaultOpt = document.createElement('option');
|
|
514
|
+
coverDefaultOpt.value = '';
|
|
515
|
+
coverDefaultOpt.textContent = '\u4E0D\u9009\u62E9';
|
|
516
|
+
coverSelect.appendChild(coverDefaultOpt);
|
|
517
|
+
var imageAssets = (cachedData.assets || []).filter(function (a) { return a.type === 'image'; });
|
|
518
|
+
imageAssets.forEach(function (asset) {
|
|
519
|
+
var opt = document.createElement('option');
|
|
520
|
+
opt.value = asset.path;
|
|
521
|
+
opt.textContent = asset.name;
|
|
522
|
+
coverSelect.appendChild(opt);
|
|
523
|
+
});
|
|
524
|
+
var coverPreview = document.createElement('img');
|
|
525
|
+
coverPreview.style.cssText = 'width:60px;height:40px;object-fit:cover;border-radius:4px;border:1px solid #3d3d5c;display:none;background:#252536;';
|
|
526
|
+
coverSelect.onchange = function () {
|
|
527
|
+
if (coverSelect.value) {
|
|
528
|
+
coverPreview.src = buildPreviewUrl(cachedData, coverSelect.value);
|
|
529
|
+
coverPreview.style.display = 'block';
|
|
530
|
+
} else {
|
|
531
|
+
coverPreview.src = '';
|
|
532
|
+
coverPreview.style.display = 'none';
|
|
533
|
+
}
|
|
534
|
+
};
|
|
535
|
+
coverRow.appendChild(coverSelect);
|
|
536
|
+
coverRow.appendChild(coverPreview);
|
|
537
|
+
coverGroup.appendChild(coverLabel);
|
|
538
|
+
coverGroup.appendChild(coverRow);
|
|
539
|
+
form.appendChild(coverGroup);
|
|
540
|
+
|
|
541
|
+
// 字段 2:入口文件
|
|
542
|
+
var entryGroup = document.createElement('div');
|
|
543
|
+
entryGroup.style.cssText = 'display:flex;flex-direction:column;gap:6px;';
|
|
544
|
+
var entryLabel = document.createElement('label');
|
|
545
|
+
entryLabel.textContent = '\u5165\u53E3\u6587\u4EF6';
|
|
546
|
+
entryLabel.style.cssText = 'font-size:12px;color:#888;';
|
|
547
|
+
var entrySelect = document.createElement('select');
|
|
548
|
+
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;';
|
|
549
|
+
entrySelect.onfocus = function () { entrySelect.style.borderColor = '#6c6caa'; };
|
|
550
|
+
entrySelect.onblur = function () { entrySelect.style.borderColor = '#3d3d5c'; };
|
|
551
|
+
var entryDefaultOpt = document.createElement('option');
|
|
552
|
+
entryDefaultOpt.value = '';
|
|
553
|
+
entryDefaultOpt.textContent = '\u4E0D\u9009\u62E9';
|
|
554
|
+
entrySelect.appendChild(entryDefaultOpt);
|
|
555
|
+
var htmlAssets = (cachedData.assets || []).filter(function (a) { return a.type === 'html'; });
|
|
556
|
+
htmlAssets.forEach(function (asset) {
|
|
557
|
+
var opt = document.createElement('option');
|
|
558
|
+
opt.value = asset.path;
|
|
559
|
+
opt.textContent = asset.name;
|
|
560
|
+
entrySelect.appendChild(opt);
|
|
561
|
+
});
|
|
562
|
+
entryGroup.appendChild(entryLabel);
|
|
563
|
+
entryGroup.appendChild(entrySelect);
|
|
564
|
+
form.appendChild(entryGroup);
|
|
565
|
+
|
|
566
|
+
// 确认发布按钮
|
|
567
|
+
var submitBtn = document.createElement('button');
|
|
568
|
+
submitBtn.textContent = '\u786E\u8BA4\u53D1\u5E03';
|
|
569
|
+
submitBtn.style.cssText = 'margin:0 20px 20px;padding:10px;background:#4a4a7a;border:none;border-radius:4px;color:#cdd6f4;font-size:13px;font-family:monospace;cursor:pointer;transition:background 0.15s;';
|
|
570
|
+
submitBtn.onmouseenter = function () { submitBtn.style.background = '#5a5a9a'; };
|
|
571
|
+
submitBtn.onmouseleave = function () { submitBtn.style.background = '#4a4a7a'; };
|
|
572
|
+
submitBtn.onclick = function () {
|
|
573
|
+
var titleVal = titleInput.value.trim();
|
|
574
|
+
if (!titleVal) {
|
|
575
|
+
titleInput.style.borderColor = '#ff4444';
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
var publishData = {
|
|
579
|
+
title: titleVal,
|
|
580
|
+
cover_path: coverSelect.value || '',
|
|
581
|
+
entry_path: entrySelect.value || '',
|
|
582
|
+
published_at: new Date().toISOString(),
|
|
583
|
+
};
|
|
584
|
+
console.log('[myclaw-artifacts-publish]', JSON.stringify(publishData));
|
|
585
|
+
closePublishModal();
|
|
586
|
+
};
|
|
587
|
+
|
|
588
|
+
box.appendChild(header);
|
|
589
|
+
box.appendChild(form);
|
|
590
|
+
box.appendChild(submitBtn);
|
|
591
|
+
overlay.appendChild(box);
|
|
592
|
+
document.body.appendChild(overlay);
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
function closePublishModal() {
|
|
596
|
+
var modal = document.querySelector('#myclaw-artifacts-publish-modal');
|
|
597
|
+
if (modal) modal.remove();
|
|
598
|
+
}
|
|
599
|
+
|
|
382
600
|
// ═══ 注入样式 ═══
|
|
383
601
|
function injectStyles() {
|
|
384
602
|
if (document.querySelector('#myclaw-artifacts-styles')) return;
|
package/index.js
CHANGED
|
@@ -396,6 +396,142 @@ function runPrepare() {
|
|
|
396
396
|
});
|
|
397
397
|
}
|
|
398
398
|
|
|
399
|
+
// ============================================================================
|
|
400
|
+
// TUI 唤起新对话上下文
|
|
401
|
+
// ============================================================================
|
|
402
|
+
|
|
403
|
+
// 预置的特殊 session ID 字典(按顺序使用)
|
|
404
|
+
const TUI_SESSION_SUFFIXES = [
|
|
405
|
+
'helper', 'AAA', 'GO', '007', 'VIP', '666',
|
|
406
|
+
'China', 'PRO', 'MAX', 'TOP', 'BEST',
|
|
407
|
+
'GO', 'OK', 'YES', 'WIN', 'FUN'
|
|
408
|
+
];
|
|
409
|
+
|
|
410
|
+
// Session 索引存储文件
|
|
411
|
+
const SESSION_INDEX_FILE = path.join(os.homedir(), '.openclaw', 'myclaw-session-index.json');
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* 获取 agent 的下一个 session 序号索引
|
|
415
|
+
*/
|
|
416
|
+
function getNextSessionIndex(agentName) {
|
|
417
|
+
const fs = require('fs');
|
|
418
|
+
let indexData = {};
|
|
419
|
+
|
|
420
|
+
// 读取现有索引
|
|
421
|
+
if (fs.existsSync(SESSION_INDEX_FILE)) {
|
|
422
|
+
try {
|
|
423
|
+
const raw = fs.readFileSync(SESSION_INDEX_FILE, 'utf8');
|
|
424
|
+
indexData = JSON.parse(raw);
|
|
425
|
+
} catch (err) {
|
|
426
|
+
console.warn('[' + colors.yellow + '警告' + colors.nc + '] 读取 session 索引失败,将重置: ' + err.message);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// 获取当前 agent 的索引,不存在则从 0 开始
|
|
431
|
+
const currentIndex = indexData[agentName] || 0;
|
|
432
|
+
|
|
433
|
+
// 计算下一个索引(循环使用)
|
|
434
|
+
const nextIndex = currentIndex % TUI_SESSION_SUFFIXES.length;
|
|
435
|
+
|
|
436
|
+
// 更新索引数据
|
|
437
|
+
indexData[agentName] = currentIndex + 1;
|
|
438
|
+
|
|
439
|
+
// 确保目录存在
|
|
440
|
+
const dir = path.dirname(SESSION_INDEX_FILE);
|
|
441
|
+
if (!fs.existsSync(dir)) {
|
|
442
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// 写回文件
|
|
446
|
+
try {
|
|
447
|
+
fs.writeFileSync(SESSION_INDEX_FILE, JSON.stringify(indexData, null, 2) + '\n', 'utf8');
|
|
448
|
+
} catch (err) {
|
|
449
|
+
console.warn('[' + colors.yellow + '警告' + colors.nc + '] 保存 session 索引失败: ' + err.message);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
return nextIndex;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
/**
|
|
456
|
+
* 获取 agent 的下一个 session 后缀
|
|
457
|
+
*/
|
|
458
|
+
function getNextSessionSuffix(agentName) {
|
|
459
|
+
const index = getNextSessionIndex(agentName);
|
|
460
|
+
return TUI_SESSION_SUFFIXES[index];
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
function runTui(interactive = false) {
|
|
464
|
+
const { spawn } = require('child_process');
|
|
465
|
+
|
|
466
|
+
// 获取 agent 名称
|
|
467
|
+
let agentName;
|
|
468
|
+
if (interactive) {
|
|
469
|
+
// 交互模式:需要询问用户
|
|
470
|
+
return askAgentNameForTui();
|
|
471
|
+
} else {
|
|
472
|
+
// 命令行模式:从参数获取,默认 'main'
|
|
473
|
+
agentName = args[1] || 'main';
|
|
474
|
+
doTuiCommand(agentName);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// 交互式询问 Agent 名称
|
|
479
|
+
function askAgentNameForTui() {
|
|
480
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
481
|
+
rl.question(colors.cyan + '请输入 Agent 名称: ' + colors.nc, function (answer) {
|
|
482
|
+
rl.close();
|
|
483
|
+
const agentName = (answer || '').trim();
|
|
484
|
+
if (!agentName) {
|
|
485
|
+
console.log(colors.red + 'Agent 名称不能为空。' + colors.nc);
|
|
486
|
+
console.log('');
|
|
487
|
+
askAgentNameForTui();
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
doTuiCommand(agentName);
|
|
491
|
+
});
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// 执行 TUI 命令
|
|
495
|
+
function doTuiCommand(agentName) {
|
|
496
|
+
const { spawn } = require('child_process');
|
|
497
|
+
|
|
498
|
+
// 获取当前 agent 的下一个 session 后缀
|
|
499
|
+
const sessionSuffix = getNextSessionSuffix(agentName);
|
|
500
|
+
const sessionKey = 'agent:' + agentName + ':' + sessionSuffix;
|
|
501
|
+
|
|
502
|
+
// 默认消息
|
|
503
|
+
const messageParam = '你好';
|
|
504
|
+
|
|
505
|
+
console.log('[' + colors.blue + 'TUI' + colors.nc + '] 唤起新对话上下文');
|
|
506
|
+
console.log(' Agent: ' + colors.green + agentName + colors.nc);
|
|
507
|
+
console.log(' Session: ' + colors.cyan + sessionKey + colors.nc);
|
|
508
|
+
console.log(' Message: ' + colors.dim + messageParam + colors.nc);
|
|
509
|
+
console.log('');
|
|
510
|
+
|
|
511
|
+
// 发送消息给 agent
|
|
512
|
+
const openclawArgs = ['tui', '--session', sessionKey, '--message', messageParam];
|
|
513
|
+
|
|
514
|
+
// 打印命令时给消息加上引号(仅用于显示)
|
|
515
|
+
const displayArgs = ['tui', '--session', sessionKey, '--message', '"' + messageParam + '"'];
|
|
516
|
+
console.log('[' + colors.dim + '执行命令: openclaw ' + displayArgs.join(' ') + colors.nc + ']');
|
|
517
|
+
console.log('');
|
|
518
|
+
|
|
519
|
+
// 启动进程,保存引用以便稍后杀掉
|
|
520
|
+
const child = spawn('openclaw', openclawArgs, {
|
|
521
|
+
stdio: 'ignore',
|
|
522
|
+
shell: true
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
console.log('[' + colors.blue + 'TUI' + colors.nc + '] 消息已发送,等待对话生成...');
|
|
526
|
+
|
|
527
|
+
// 等待 10 秒后杀掉进程
|
|
528
|
+
setTimeout(() => {
|
|
529
|
+
child.kill();
|
|
530
|
+
console.log('[' + colors.green + '完成' + colors.nc + '] 对话上下文已唤起');
|
|
531
|
+
process.exit(0);
|
|
532
|
+
}, 10000);
|
|
533
|
+
}
|
|
534
|
+
|
|
399
535
|
// ============================================================================
|
|
400
536
|
// 创建 Windows 桌面快捷启动脚本
|
|
401
537
|
// ============================================================================
|
|
@@ -411,7 +547,7 @@ function runBat() {
|
|
|
411
547
|
}
|
|
412
548
|
|
|
413
549
|
const myClawDir = path.join(process.env.LOCALAPPDATA || os.tmpdir(), 'myclaw');
|
|
414
|
-
try { fs.mkdirSync(myClawDir, { recursive: true }); } catch {}
|
|
550
|
+
try { fs.mkdirSync(myClawDir, { recursive: true }); } catch { }
|
|
415
551
|
|
|
416
552
|
const batPath = path.join(myClawDir, 'openclaw-launcher.bat');
|
|
417
553
|
const desktopPath = path.join(os.homedir(), 'Desktop');
|
|
@@ -429,9 +565,9 @@ function runBat() {
|
|
|
429
565
|
try {
|
|
430
566
|
const oldLinks = fs.readdirSync(desktopPath).filter(f => f.endsWith('.lnk') && (f.includes('OpenClaw') || f.match(/^\d+\.\d+\.\d+_OpenClaw/)));
|
|
431
567
|
for (const old of oldLinks) {
|
|
432
|
-
try { fs.unlinkSync(path.join(desktopPath, old)); } catch {}
|
|
568
|
+
try { fs.unlinkSync(path.join(desktopPath, old)); } catch { }
|
|
433
569
|
}
|
|
434
|
-
} catch {}
|
|
570
|
+
} catch { }
|
|
435
571
|
|
|
436
572
|
const lnkPath = path.join(desktopPath, ver + '_OpenClaw_' + mm + '-' + dd + '_' + hh + '-' + mi + '-' + ss + '.lnk');
|
|
437
573
|
|
|
@@ -461,7 +597,7 @@ pause >nul
|
|
|
461
597
|
// 用 PowerShell 下载图标并创建带图标的桌面快捷方式 + 刷新桌面
|
|
462
598
|
const iconPath = path.join(myClawDir, 'openclaw.ico');
|
|
463
599
|
const iconUrl = 'https://cdn.yiranlaoshi.com/software/myclaw/openclaw.ico';
|
|
464
|
-
|
|
600
|
+
|
|
465
601
|
const psFile = path.join(myClawDir, 'create-shortcuts.ps1');
|
|
466
602
|
const psContent = [
|
|
467
603
|
'$ErrorActionPreference = \'Continue\'',
|
|
@@ -580,7 +716,7 @@ function runOpen() {
|
|
|
580
716
|
try {
|
|
581
717
|
const releaseInfo = fs.readFileSync('/proc/version', 'utf8');
|
|
582
718
|
if (/microsoft|wsl/i.test(releaseInfo)) isWSL = true;
|
|
583
|
-
} catch {}
|
|
719
|
+
} catch { }
|
|
584
720
|
|
|
585
721
|
if (isWSL) {
|
|
586
722
|
// WSL2 环境下优先使用 Windows 端的 Chrome
|
|
@@ -612,7 +748,7 @@ function runOpen() {
|
|
|
612
748
|
chromePath = p;
|
|
613
749
|
break;
|
|
614
750
|
}
|
|
615
|
-
} catch {}
|
|
751
|
+
} catch { }
|
|
616
752
|
}
|
|
617
753
|
|
|
618
754
|
// 如果没有找到 Chrome
|
|
@@ -641,7 +777,7 @@ function runOpen() {
|
|
|
641
777
|
}
|
|
642
778
|
}
|
|
643
779
|
}
|
|
644
|
-
} catch {}
|
|
780
|
+
} catch { }
|
|
645
781
|
|
|
646
782
|
console.log('安装 Chrome 后重新运行: ' + colors.yellow + 'myclaw open' + colors.nc);
|
|
647
783
|
console.log('');
|
|
@@ -727,7 +863,7 @@ function runPatch() {
|
|
|
727
863
|
current[parts[parts.length - 1]] = value;
|
|
728
864
|
}
|
|
729
865
|
const { config, configPath } = patchConfig(nested);
|
|
730
|
-
|
|
866
|
+
|
|
731
867
|
// 【关键修复】深度合并不会删除错误的历史残留配置,这里主动帮学生清理掉遗留在顶层的 "exec"
|
|
732
868
|
let requireSave = false;
|
|
733
869
|
if (config && config.exec) {
|
|
@@ -735,7 +871,7 @@ function runPatch() {
|
|
|
735
871
|
requireSave = true;
|
|
736
872
|
console.log('[myclaw-config] ✅ 已清理历史遗留的错误顶层 "exec" 常驻配置');
|
|
737
873
|
}
|
|
738
|
-
|
|
874
|
+
|
|
739
875
|
if (requireSave) {
|
|
740
876
|
const { writeConfig } = require('./find-config');
|
|
741
877
|
writeConfig(config, configPath);
|
|
@@ -893,7 +1029,7 @@ function runUpdate() {
|
|
|
893
1029
|
let currentVersion = 'unknown';
|
|
894
1030
|
try {
|
|
895
1031
|
currentVersion = require(path.join(__dirname, 'package.json')).version;
|
|
896
|
-
} catch {}
|
|
1032
|
+
} catch { }
|
|
897
1033
|
|
|
898
1034
|
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
899
1035
|
try {
|
|
@@ -942,7 +1078,7 @@ function runUpdate() {
|
|
|
942
1078
|
console.log('');
|
|
943
1079
|
console.log('[' + colors.yellow + '重试' + colors.nc + '] 8 秒后重试 (' + attempt + '/' + maxRetries + ')...');
|
|
944
1080
|
const waitCmd = require('os').platform() === 'win32' ? 'timeout /t 8 >nul' : 'sleep 8';
|
|
945
|
-
try { execSync(waitCmd, { stdio: 'ignore' }); } catch {}
|
|
1081
|
+
try { execSync(waitCmd, { stdio: 'ignore' }); } catch { }
|
|
946
1082
|
} else {
|
|
947
1083
|
console.log('');
|
|
948
1084
|
console.log('[' + colors.red + '错误' + colors.nc + '] 升级失败: ' + err.message);
|
|
@@ -1037,31 +1173,38 @@ function padRight(str, len) {
|
|
|
1037
1173
|
// ============================================================================
|
|
1038
1174
|
|
|
1039
1175
|
const MENU_ITEMS = [
|
|
1040
|
-
{ key: 'start',
|
|
1041
|
-
{ key: 'restart', label: '重启',
|
|
1042
|
-
{ key: 'new',
|
|
1043
|
-
{ key: '
|
|
1044
|
-
{ key: '
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
{ key: '
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1176
|
+
{ key: 'start', label: '🦞启动🦞', cmd: 'mc start', desc: '把你的 AI 助手叫醒,让它开始工作', action: () => { const start = require('./start'); start.run(); } },
|
|
1177
|
+
{ key: 'restart', label: '重启', cmd: 'mc restart', desc: 'AI 助手卡住了?让它重新启动一下', action: runRestart },
|
|
1178
|
+
{ key: 'new', label: '😊新伙伴', cmd: 'mc new', desc: '创建一个新的 AI 助手,给它取个名字', action: runNew },
|
|
1179
|
+
{ key: 'tui', label: '新对话', cmd: 'mc tui', desc: '唤起新对话上下文', action: () => runTui(true) },
|
|
1180
|
+
{ key: 'status', label: '网址', cmd: 'mc status', desc: '获取控制台链接,复制到浏览器打开', action: runStatus },
|
|
1181
|
+
{
|
|
1182
|
+
key: 'update', label: '升级', cmd: 'mc up', desc: '让 MyClaw 工具升级到最新版本', action: () => {
|
|
1183
|
+
runUpdate();
|
|
1184
|
+
console.log('');
|
|
1185
|
+
console.log('🔧 自动执行 patch 以恢复自定义配置...');
|
|
1186
|
+
runPatch();
|
|
1187
|
+
if (detectPlatform() === 'windows') runBat();
|
|
1188
|
+
}
|
|
1189
|
+
},
|
|
1190
|
+
{ key: 'patch', label: '修复', cmd: 'mc patch', desc: '给 AI 助手装上新技能和好看的外衣', action: runPatch },
|
|
1191
|
+
{ key: 'reinstall', label: '重装', cmd: 'mc longxia', desc: '出了大问题?把 AI 助手删了重新安装', action: runReinstall },
|
|
1192
|
+
{ key: 'uninstall', label: '卸载', cmd: 'mc uninstall', desc: '卸载 MyClaw,恢复 npm 源地址', action: runUninstall },
|
|
1193
|
+
{
|
|
1194
|
+
key: 'wsl2reinstall', label: 'WSL2虚拟机重装', cmd: 'mc wsl2 --remote --force-phase1', desc: '强制从网络重新下载并重装 WSL2 虚拟机(仅限 Windows)', action: () => {
|
|
1195
|
+
process.argv.push('--remote', '--force-phase1');
|
|
1196
|
+
const wsl2 = require('./wsl2');
|
|
1197
|
+
wsl2.run();
|
|
1198
|
+
}
|
|
1199
|
+
},
|
|
1200
|
+
{
|
|
1201
|
+
key: 'safe', label: '虚拟机屏蔽', cmd: 'mc safe', desc: '禁止 WSL2 访问 Windows 盘符(仅限 Windows)', action: () => {
|
|
1202
|
+
const restrict = require('./restrict');
|
|
1203
|
+
restrict.run();
|
|
1204
|
+
}
|
|
1205
|
+
},
|
|
1063
1206
|
{ key: 'bat', label: '桌面快捷方式', cmd: 'mc bat', desc: '在桌面生成一键启动脚本(仅限 Windows)', action: runBat },
|
|
1064
|
-
{ key: 'quit',
|
|
1207
|
+
{ key: 'quit', label: '退出', cmd: 'Ctrl+C', desc: '不玩了,下次见', action: () => { console.log(' 👋 再见!\n'); process.exit(0); } },
|
|
1065
1208
|
];
|
|
1066
1209
|
|
|
1067
1210
|
function runInteractiveMenu() {
|
|
@@ -1124,6 +1267,13 @@ function runInteractiveMenu() {
|
|
|
1124
1267
|
return;
|
|
1125
1268
|
}
|
|
1126
1269
|
|
|
1270
|
+
// tui 命令需要交互式输入,不走菜单的"按回车返回"逻辑
|
|
1271
|
+
if (item.key === 'tui') {
|
|
1272
|
+
console.log('');
|
|
1273
|
+
item.action();
|
|
1274
|
+
return;
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1127
1277
|
console.log('');
|
|
1128
1278
|
item.action();
|
|
1129
1279
|
|
|
@@ -1171,25 +1321,25 @@ function runInteractiveMenu() {
|
|
|
1171
1321
|
// ============================================================================
|
|
1172
1322
|
|
|
1173
1323
|
const INJECT_MENU = [
|
|
1174
|
-
{ key: '1', cmd: 'inject-minimax',
|
|
1175
|
-
{ key: '2', cmd: 'inject-zai',
|
|
1176
|
-
{ key: '3', cmd: 'inject-image',
|
|
1177
|
-
{ key: '4', cmd: 'inject-search',
|
|
1178
|
-
{ key: '5', cmd: 'inject-token',
|
|
1324
|
+
{ key: '1', cmd: 'inject-minimax', desc: '注入 MiniMax 模型配置' },
|
|
1325
|
+
{ key: '2', cmd: 'inject-zai', desc: '注入智谱 GLM 模型配置' },
|
|
1326
|
+
{ key: '3', cmd: 'inject-image', desc: '注入图像生成模型配置 (vveai)' },
|
|
1327
|
+
{ key: '4', cmd: 'inject-search', desc: '注入 Tavily 搜索插件配置' },
|
|
1328
|
+
{ key: '5', cmd: 'inject-token', desc: '设置 Gateway Token 为 aiyiran' },
|
|
1179
1329
|
{ key: '6', cmd: 'inject-workspaceAndSoul', desc: '替换默认 workspace 的 SOUL.md' },
|
|
1180
|
-
{ key: '7', cmd: 'inject-tooldeny',
|
|
1181
|
-
{ key: 'a', cmd: 'all',
|
|
1330
|
+
{ key: '7', cmd: 'inject-tooldeny', desc: 'deny image_generate + music_generate 内置工具' },
|
|
1331
|
+
{ key: 'a', cmd: 'all', desc: '执行以上全部注入' },
|
|
1182
1332
|
];
|
|
1183
1333
|
|
|
1184
1334
|
function runInjectCommand(cmd, extraArgs) {
|
|
1185
1335
|
const modules = {
|
|
1186
|
-
'inject-minimax':
|
|
1187
|
-
'inject-zai':
|
|
1188
|
-
'inject-image':
|
|
1189
|
-
'inject-search':
|
|
1190
|
-
'inject-token':
|
|
1336
|
+
'inject-minimax': './inject-minimax',
|
|
1337
|
+
'inject-zai': './inject-zai',
|
|
1338
|
+
'inject-image': './inject-image',
|
|
1339
|
+
'inject-search': './inject-search',
|
|
1340
|
+
'inject-token': './inject-token',
|
|
1191
1341
|
'inject-workspaceAndSoul': './inject-workspaceAndSoul',
|
|
1192
|
-
'inject-tooldeny':
|
|
1342
|
+
'inject-tooldeny': './inject-tooldeny',
|
|
1193
1343
|
};
|
|
1194
1344
|
const mod = require(modules[cmd]);
|
|
1195
1345
|
mod.run(extraArgs || []);
|
|
@@ -1267,6 +1417,7 @@ function showHelp() {
|
|
|
1267
1417
|
console.log(' update 自动升级 MyClaw 到最新版本');
|
|
1268
1418
|
console.log(' up 升级 + 刷新桌面快捷方式 (= update + bat)');
|
|
1269
1419
|
console.log(' open 打开浏览器控制台(自动带 token)');
|
|
1420
|
+
console.log(' tui 唤起新对话上下文 (用法: mc tui <agentname>)');
|
|
1270
1421
|
console.log(' fix 兜底修复(自动补装 WSL + Chrome,仅限 Windows)');
|
|
1271
1422
|
console.log(' wsl2 WSL2 一键安装/修复 (仅限 Windows, 可选: --cli, --remote)');
|
|
1272
1423
|
console.log(' safe 开启虚拟机屏蔽 (禁止自动挂载 Windows 盘符)');
|
|
@@ -1343,6 +1494,8 @@ if (!command) {
|
|
|
1343
1494
|
runStart();
|
|
1344
1495
|
} else if (command === 'open') {
|
|
1345
1496
|
runOpen();
|
|
1497
|
+
} else if (command === 'tui') {
|
|
1498
|
+
runTui();
|
|
1346
1499
|
} else if (command === 'fix') {
|
|
1347
1500
|
runFix();
|
|
1348
1501
|
} else if (command === 'wsl2') {
|
package/package.json
CHANGED
|
@@ -2,16 +2,16 @@
|
|
|
2
2
|
"output_dir": "media",
|
|
3
3
|
"image": {
|
|
4
4
|
"primary": {
|
|
5
|
-
"provider": "minimax_image",
|
|
6
|
-
"model": "image-01",
|
|
7
|
-
"base_url": "https://api.minimaxi.com/v1",
|
|
8
|
-
"api_key": "sk-cp-DC5lWd2Stt9CBFzLIT2awP4K-ZEn5AkYwjl3Cdj-mIBmgjxod518F2LaVF2L9c35Wv5-Eox0F1ctJD5vXtB9p3OmxoWLd9ge9zIUIMrCVuqBYdL_s6kb8Qs"
|
|
9
|
-
},
|
|
10
|
-
"fallback": {
|
|
11
5
|
"provider": "vapi_image",
|
|
12
6
|
"model": "nano-banana-pro",
|
|
13
7
|
"base_url": "https://api.v3.cm/v1",
|
|
14
8
|
"api_key": "sk-PXPUzqllWKJy2oj011Df510242264219Ba21093e3d2b2335"
|
|
9
|
+
},
|
|
10
|
+
"fallback": {
|
|
11
|
+
"provider": "minimax_image",
|
|
12
|
+
"model": "image-01",
|
|
13
|
+
"base_url": "https://api.minimaxi.com/v1",
|
|
14
|
+
"api_key": "sk-cp-DC5lWd2Stt9CBFzLIT2awP4K-ZEn5AkYwjl3Cdj-mIBmgjxod518F2LaVF2L9c35Wv5-Eox0F1ctJD5vXtB9p3OmxoWLd9ge9zIUIMrCVuqBYdL_s6kb8Qs"
|
|
15
15
|
}
|
|
16
16
|
},
|
|
17
17
|
"music": {
|
|
@@ -44,10 +44,57 @@ def get_output_dir():
|
|
|
44
44
|
return fallback
|
|
45
45
|
|
|
46
46
|
|
|
47
|
-
def
|
|
48
|
-
"""
|
|
47
|
+
def ratio_to_size(ratio_str, max_dim=2048):
|
|
48
|
+
"""Parse '16:9' style ratio and compute (width, height) capped at max_dim."""
|
|
49
|
+
if not ratio_str or ratio_str == "1:1":
|
|
50
|
+
return 1024, 1024
|
|
51
|
+
|
|
52
|
+
parts = ratio_str.split(":")
|
|
53
|
+
if len(parts) != 2:
|
|
54
|
+
return 1024, 1024
|
|
55
|
+
|
|
56
|
+
try:
|
|
57
|
+
rw, rh = float(parts[0]), float(parts[1])
|
|
58
|
+
except ValueError:
|
|
59
|
+
return 1024, 1024
|
|
60
|
+
|
|
61
|
+
# Scale so the longer side = max_dim
|
|
62
|
+
scale = max_dim / max(rw, rh)
|
|
63
|
+
w = round(rw * scale)
|
|
64
|
+
h = round(rh * scale)
|
|
65
|
+
return w, h
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def make_output_path(out_dir, resource_type, model, params, ext):
|
|
69
|
+
"""Generate filename with model name and key parameters.
|
|
70
|
+
|
|
71
|
+
Format: {type}_{model}_{params}_{timestamp}.{ext}
|
|
72
|
+
Example: image_nano-banana-pro_2048x1152_20260414_123456.png
|
|
73
|
+
"""
|
|
49
74
|
ts = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
50
|
-
|
|
75
|
+
|
|
76
|
+
# Extract key parameters for filename
|
|
77
|
+
param_parts = []
|
|
78
|
+
if resource_type == "image":
|
|
79
|
+
aspect_ratio = params.get("aspect_ratio", "1:1")
|
|
80
|
+
# Convert ratio to actual dimensions
|
|
81
|
+
w, h = ratio_to_size(aspect_ratio)
|
|
82
|
+
param_parts.append(f"{w}x{h}")
|
|
83
|
+
elif resource_type == "music":
|
|
84
|
+
if params.get("instrumental"):
|
|
85
|
+
param_parts.append("instrumental")
|
|
86
|
+
|
|
87
|
+
# Build filename parts
|
|
88
|
+
# Clean model name: replace special chars with dash
|
|
89
|
+
clean_model = model.replace("/", "-").replace("_", "-")
|
|
90
|
+
|
|
91
|
+
parts = [resource_type, clean_model]
|
|
92
|
+
if param_parts:
|
|
93
|
+
parts.extend(param_parts)
|
|
94
|
+
parts.append(ts)
|
|
95
|
+
|
|
96
|
+
filename = "_".join(parts) + f".{ext}"
|
|
97
|
+
return os.path.join(out_dir, filename)
|
|
51
98
|
|
|
52
99
|
|
|
53
100
|
def append_log(log_path, entry):
|
|
@@ -114,27 +161,46 @@ def main():
|
|
|
114
161
|
|
|
115
162
|
out_dir = get_output_dir()
|
|
116
163
|
|
|
164
|
+
# Prepare kwargs
|
|
117
165
|
if args.type == "image":
|
|
118
|
-
output_path = args.output or make_output_path(out_dir, "image", "png")
|
|
119
166
|
kwargs = {
|
|
120
167
|
"out_dir": out_dir,
|
|
121
|
-
"output_path": output_path,
|
|
122
168
|
"aspect_ratio": args.aspect_ratio,
|
|
123
169
|
}
|
|
170
|
+
ext = "png"
|
|
124
171
|
else:
|
|
125
|
-
output_path = args.output or make_output_path(out_dir, "music", "mp3")
|
|
126
172
|
kwargs = {
|
|
127
173
|
"out_dir": out_dir,
|
|
128
|
-
"output_path": output_path,
|
|
129
174
|
"lyrics": args.lyrics,
|
|
130
175
|
"instrumental": args.instrumental,
|
|
131
176
|
}
|
|
177
|
+
ext = "mp3"
|
|
132
178
|
|
|
133
179
|
start_time = time.time()
|
|
134
180
|
start_dt = datetime.now()
|
|
135
181
|
|
|
136
182
|
try:
|
|
183
|
+
# Handle output path: user-specified or auto-named with model info
|
|
184
|
+
if args.output:
|
|
185
|
+
# User specified output path - use it directly
|
|
186
|
+
kwargs["output_path"] = args.output
|
|
187
|
+
else:
|
|
188
|
+
# Auto-naming: generate to temp path first, then rename with model info
|
|
189
|
+
import tempfile
|
|
190
|
+
with tempfile.NamedTemporaryFile(delete=False, suffix=f".{ext}", dir=out_dir) as tmp:
|
|
191
|
+
tmp_path = tmp.name
|
|
192
|
+
kwargs["output_path"] = tmp_path
|
|
193
|
+
|
|
137
194
|
files, used_provider = dispatch(args.type, args.prompt, **kwargs)
|
|
195
|
+
|
|
196
|
+
# Rename with proper filename containing model and params (only for auto-naming)
|
|
197
|
+
if not args.output:
|
|
198
|
+
model = used_provider["model"]
|
|
199
|
+
proper_path = make_output_path(out_dir, args.type, model, kwargs, ext)
|
|
200
|
+
import shutil
|
|
201
|
+
shutil.move(files[0], proper_path)
|
|
202
|
+
files = [proper_path]
|
|
203
|
+
|
|
138
204
|
duration = round(time.time() - start_time, 2)
|
|
139
205
|
end_dt = datetime.now()
|
|
140
206
|
|
|
@@ -16,5 +16,41 @@
|
|
|
16
16
|
"started_at": "2026-04-13 14:11:12",
|
|
17
17
|
"finished_at": "2026-04-13 14:11:36",
|
|
18
18
|
"duration_seconds": 23.5
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"id": "20260414_005846",
|
|
22
|
+
"type": "image",
|
|
23
|
+
"name": "test_output.png",
|
|
24
|
+
"files": [
|
|
25
|
+
"/tmp/test_output.png"
|
|
26
|
+
],
|
|
27
|
+
"prompt": "a cute cat",
|
|
28
|
+
"params": {
|
|
29
|
+
"aspect_ratio": "1:1",
|
|
30
|
+
"output_path": "/tmp/test_output.png"
|
|
31
|
+
},
|
|
32
|
+
"provider": "vapi_image",
|
|
33
|
+
"model": "nano-banana-pro",
|
|
34
|
+
"started_at": "2026-04-14 00:58:46",
|
|
35
|
+
"finished_at": "2026-04-14 00:59:08",
|
|
36
|
+
"duration_seconds": 21.9
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"id": "20260414_005910",
|
|
40
|
+
"type": "image",
|
|
41
|
+
"name": "image_nano-banana-pro_2048x1152_20260414_005930.png",
|
|
42
|
+
"files": [
|
|
43
|
+
"/Users/yiran/.openclaw/workspace/media/image_nano-banana-pro_2048x1152_20260414_005930.png"
|
|
44
|
+
],
|
|
45
|
+
"prompt": "a beautiful sunset",
|
|
46
|
+
"params": {
|
|
47
|
+
"aspect_ratio": "16:9",
|
|
48
|
+
"output_path": "/Users/yiran/.openclaw/workspace/media/tmpdy8csqoa.png"
|
|
49
|
+
},
|
|
50
|
+
"provider": "vapi_image",
|
|
51
|
+
"model": "nano-banana-pro",
|
|
52
|
+
"started_at": "2026-04-14 00:59:10",
|
|
53
|
+
"finished_at": "2026-04-14 00:59:30",
|
|
54
|
+
"duration_seconds": 20.44
|
|
19
55
|
}
|
|
20
56
|
]
|