agens-studio 0.1.3 → 0.1.6
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/cli.js +98 -48
- package/config.js +3 -1
- package/package.json +1 -1
- package/src/services/agensClient.js +15 -3
package/cli.js
CHANGED
|
@@ -469,9 +469,25 @@ async function mainMenu() {
|
|
|
469
469
|
}
|
|
470
470
|
}
|
|
471
471
|
|
|
472
|
+
/**
|
|
473
|
+
* 检查 API 是否已配置,未配置时自动引导 setup wizard
|
|
474
|
+
* @returns {Promise<boolean>} true = 已配置可继续,false = 用户取消
|
|
475
|
+
*/
|
|
476
|
+
async function ensureApiConfigured() {
|
|
477
|
+
if (config.agens.image.apiKey) return true;
|
|
478
|
+
console.log(`\n${C.red}${C.bold} ⚠ API Key 未配置,无法生成${C.reset}`);
|
|
479
|
+
console.log(`${C.gray} 请先配置 Agnes API Key 后重试${C.reset}\n`);
|
|
480
|
+
const go = await confirm('是否现在配置 API Key');
|
|
481
|
+
if (go) {
|
|
482
|
+
await setupWizard(false);
|
|
483
|
+
}
|
|
484
|
+
return false;
|
|
485
|
+
}
|
|
486
|
+
|
|
472
487
|
// ═══════════════════ [1] 文生图 ═══════════════════
|
|
473
488
|
|
|
474
489
|
async function modeText2Image() {
|
|
490
|
+
if (!(await ensureApiConfigured())) return;
|
|
475
491
|
console.log(`\n${C.bold}─────── 文生图 ───────${C.reset}`);
|
|
476
492
|
|
|
477
493
|
const rawPrompt = await ask('描述你想生成的画面');
|
|
@@ -511,6 +527,7 @@ async function modeText2Image() {
|
|
|
511
527
|
// ═══════════════════ [2] 图生图 ═══════════════════
|
|
512
528
|
|
|
513
529
|
async function modeImage2Image() {
|
|
530
|
+
if (!(await ensureApiConfigured())) return;
|
|
514
531
|
console.log(`\n${C.bold}─────── 图生图 ───────${C.reset}`);
|
|
515
532
|
console.log(`${C.gray}输入参考图片的本地文件路径(支持拖拽文件到窗口)${C.reset}`);
|
|
516
533
|
|
|
@@ -598,6 +615,7 @@ async function callVisionDirect(dataUri) {
|
|
|
598
615
|
// ═══════════════════ [3] 文生视频 ═══════════════════
|
|
599
616
|
|
|
600
617
|
async function modeText2Video() {
|
|
618
|
+
if (!(await ensureApiConfigured())) return;
|
|
601
619
|
console.log(`\n${C.bold}─────── 文生视频 ───────${C.reset}`);
|
|
602
620
|
|
|
603
621
|
const rawPrompt = await ask('描述你想生成的视频场景');
|
|
@@ -644,6 +662,7 @@ async function modeText2Video() {
|
|
|
644
662
|
// ═══════════════════ [4] 图生视频 ═══════════════════
|
|
645
663
|
|
|
646
664
|
async function modeImage2Video() {
|
|
665
|
+
if (!(await ensureApiConfigured())) return;
|
|
647
666
|
console.log(`\n${C.bold}─────── 图生视频 ───────${C.reset}`);
|
|
648
667
|
console.log(`${C.gray}输入参考图片路径(支持拖拽)${C.reset}`);
|
|
649
668
|
|
|
@@ -696,6 +715,7 @@ async function modeImage2Video() {
|
|
|
696
715
|
// ═══════════════════ [5] 多图生视频 ═══════════════════
|
|
697
716
|
|
|
698
717
|
async function modeMulti2Video() {
|
|
718
|
+
if (!(await ensureApiConfigured())) return;
|
|
699
719
|
console.log(`\n${C.bold}─────── 多图生视频 ───────${C.reset}`);
|
|
700
720
|
console.log(`${C.gray}输入多张图片路径,生成过渡视频(2-5 张)${C.reset}`);
|
|
701
721
|
|
|
@@ -758,6 +778,7 @@ async function modeMulti2Video() {
|
|
|
758
778
|
// ═══════════════════ [6] 关键帧动画 ═══════════════════
|
|
759
779
|
|
|
760
780
|
async function modeKeyframe() {
|
|
781
|
+
if (!(await ensureApiConfigured())) return;
|
|
761
782
|
console.log(`\n${C.bold}─────── 关键帧动画 ───────${C.reset}`);
|
|
762
783
|
console.log(`${C.gray}至少输入首帧和尾帧图片(2-5 张关键帧)${C.reset}`);
|
|
763
784
|
|
|
@@ -840,59 +861,88 @@ async function menuAssets() {
|
|
|
840
861
|
else if (c === '4') search = await ask('搜索关键词');
|
|
841
862
|
else if (c !== '1') { console.log(`${C.red}无效选择${C.reset}`); continue; }
|
|
842
863
|
|
|
843
|
-
const
|
|
864
|
+
const pageSize = 20;
|
|
865
|
+
let page = 1;
|
|
844
866
|
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
867
|
+
while (true) {
|
|
868
|
+
const { items, total } = await assetStore.listAssets({ type, search, page, pageSize });
|
|
869
|
+
const totalPages = Math.max(1, Math.ceil(total / pageSize));
|
|
870
|
+
const startIdx = (page - 1) * pageSize;
|
|
849
871
|
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
const note = a.note ? ` | ${a.note}` : '';
|
|
855
|
-
console.log(` ${C.cyan}${String(i + 1).padStart(2)}.${C.reset} ${icon} ${a.filename}`);
|
|
856
|
-
console.log(` ${C.gray}${prompt}${note}${C.reset}`);
|
|
857
|
-
console.log(` ${formatSize(a.size)} | ${fmtDate(a.createdAt)} | ${a.kept ? C.green + '保留' + C.reset : C.gray + '未保留' + C.reset}`);
|
|
858
|
-
});
|
|
872
|
+
if (items.length === 0) {
|
|
873
|
+
console.log(`\n${C.gray}暂无素材${total > 0 ? `(共 ${total} 条,当前页为空)` : ''}${C.reset}`);
|
|
874
|
+
break;
|
|
875
|
+
}
|
|
859
876
|
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
877
|
+
console.log(`\n${C.bold}共 ${total} 条素材(第 ${page}/${totalPages} 页):${C.reset}\n`);
|
|
878
|
+
items.forEach((a, i) => {
|
|
879
|
+
const globalIdx = startIdx + i + 1;
|
|
880
|
+
const icon = a.type === 'video' ? '🎬' : '🖼';
|
|
881
|
+
const prompt = (a.prompt || '').slice(0, 50);
|
|
882
|
+
const note = a.note ? ` | ${a.note}` : '';
|
|
883
|
+
console.log(` ${C.cyan}${String(globalIdx).padStart(3)}.${C.reset} ${icon} ${a.filename}`);
|
|
884
|
+
console.log(` ${C.gray}${prompt}${note}${C.reset}`);
|
|
885
|
+
console.log(` ${formatSize(a.size)} | ${fmtDate(a.createdAt)} | ${a.kept ? C.green + '保留' + C.reset : C.gray + '未保留' + C.reset}`);
|
|
886
|
+
});
|
|
866
887
|
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
await assetStore.
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
} else if (action === 3) {
|
|
892
|
-
if (await confirm(`确认删除 ${asset.filename}`)) {
|
|
893
|
-
await assetStore.deleteAsset(asset.id);
|
|
894
|
-
console.log(`${C.green}已删除${C.reset}`);
|
|
888
|
+
const navHints = [];
|
|
889
|
+
if (page < totalPages) navHints.push('n=下一页');
|
|
890
|
+
if (page > 1) navHints.push('p=上一页');
|
|
891
|
+
const navStr = navHints.length ? `(${navHints.join(',')})` : '';
|
|
892
|
+
|
|
893
|
+
console.log('');
|
|
894
|
+
const op = await ask(`输入序号查看详情${navStr},或回车返回`);
|
|
895
|
+
if (!op) break;
|
|
896
|
+
|
|
897
|
+
if (op.toLowerCase() === 'n' && page < totalPages) { page++; continue; }
|
|
898
|
+
if (op.toLowerCase() === 'p' && page > 1) { page--; continue; }
|
|
899
|
+
|
|
900
|
+
const num = parseInt(op, 10);
|
|
901
|
+
if (isNaN(num) || num < 1 || num > total) { console.log(`${C.red}无效序号(范围 1-${total})${C.reset}`); continue; }
|
|
902
|
+
|
|
903
|
+
const targetPage = Math.ceil(num / pageSize);
|
|
904
|
+
const localIdx = num - (targetPage - 1) * pageSize - 1;
|
|
905
|
+
let asset;
|
|
906
|
+
if (targetPage === page) {
|
|
907
|
+
asset = items[localIdx];
|
|
908
|
+
} else {
|
|
909
|
+
const nextPageData = await assetStore.listAssets({ type, search, page: targetPage, pageSize });
|
|
910
|
+
asset = nextPageData.items[localIdx];
|
|
911
|
+
page = targetPage;
|
|
895
912
|
}
|
|
913
|
+
if (asset) await showAssetDetail(asset);
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
async function showAssetDetail(asset) {
|
|
919
|
+
const absPath = path.join(config.dirs.root, asset.path);
|
|
920
|
+
console.log(`\n${C.bold}素材详情:${C.reset}`);
|
|
921
|
+
console.log(` ID:${asset.id}`);
|
|
922
|
+
console.log(` 文件:${absPath}`);
|
|
923
|
+
console.log(` 提示词:${asset.prompt || '(无)'}`);
|
|
924
|
+
console.log(` 模式:${asset.mode || '(无)'}`);
|
|
925
|
+
console.log(` 大小:${formatSize(asset.size)}`);
|
|
926
|
+
console.log(` 创建:${fmtDate(asset.createdAt)}`);
|
|
927
|
+
console.log(` 保留:${asset.kept ? '是' : '否'}`);
|
|
928
|
+
if (asset.note) console.log(` 备注:${asset.note}`);
|
|
929
|
+
|
|
930
|
+
const action = await choose('操作', ['打开文件', '切换保留状态', '添加备注', '删除']);
|
|
931
|
+
if (action === 0) {
|
|
932
|
+
openFile(absPath);
|
|
933
|
+
} else if (action === 1) {
|
|
934
|
+
await assetStore.updateAsset(asset.id, { kept: !asset.kept });
|
|
935
|
+
console.log(`${C.green}已${asset.kept ? '取消保留' : '标记保留'}${C.reset}`);
|
|
936
|
+
} else if (action === 2) {
|
|
937
|
+
const note = await ask('输入备注');
|
|
938
|
+
if (note) {
|
|
939
|
+
await assetStore.updateAsset(asset.id, { note });
|
|
940
|
+
console.log(`${C.green}备注已保存${C.reset}`);
|
|
941
|
+
}
|
|
942
|
+
} else if (action === 3) {
|
|
943
|
+
if (await confirm(`确认删除 ${asset.filename}`)) {
|
|
944
|
+
await assetStore.deleteAsset(asset.id);
|
|
945
|
+
console.log(`${C.green}已删除${C.reset}`);
|
|
896
946
|
}
|
|
897
947
|
}
|
|
898
948
|
}
|
package/config.js
CHANGED
|
@@ -132,7 +132,9 @@ export function saveUserConfig(cfg) {
|
|
|
132
132
|
*/
|
|
133
133
|
export function readUserConfig() {
|
|
134
134
|
try {
|
|
135
|
-
|
|
135
|
+
const data = JSON.parse(fs.readFileSync(USER_CONFIG_FILE, 'utf8'));
|
|
136
|
+
if (data && typeof data === 'object' && Object.keys(data).length > 0) return data;
|
|
137
|
+
return null;
|
|
136
138
|
} catch {
|
|
137
139
|
return null;
|
|
138
140
|
}
|
package/package.json
CHANGED
|
@@ -76,7 +76,7 @@ async function localImageToString(localPath, asDataUri) {
|
|
|
76
76
|
|
|
77
77
|
async function agnesFetch(url, opts, options) {
|
|
78
78
|
const opts2 = options || {};
|
|
79
|
-
const retries = opts2.retries != null ? opts2.retries :
|
|
79
|
+
const retries = opts2.retries != null ? opts2.retries : 5;
|
|
80
80
|
const timeoutMs = opts2.timeoutMs != null ? opts2.timeoutMs : 120000;
|
|
81
81
|
let lastErr;
|
|
82
82
|
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
@@ -87,8 +87,20 @@ async function agnesFetch(url, opts, options) {
|
|
|
87
87
|
clearTimeout(timer);
|
|
88
88
|
if ((res.status === 429 || res.status === 503) && attempt < retries) {
|
|
89
89
|
const t = await res.text().catch(function () { return ''; });
|
|
90
|
-
|
|
91
|
-
|
|
90
|
+
// 429 = 限频(1次/分钟),等 65 秒;503 = 服务忙,等 15 秒
|
|
91
|
+
const waitSec = res.status === 429 ? 65 : 15;
|
|
92
|
+
const reason = res.status === 429 ? '限频' : '服务忙';
|
|
93
|
+
console.log('[agnes] ' + res.status + ' ' + reason + ',' + waitSec + '秒后重试 (' + (attempt + 1) + '/' + retries + ')');
|
|
94
|
+
// 倒计时显示(仅 429 长等待时显示)
|
|
95
|
+
if (waitSec >= 30) {
|
|
96
|
+
for (let s = waitSec; s > 0; s--) {
|
|
97
|
+
process.stdout.write('\r 等待中... ' + s + '秒 ');
|
|
98
|
+
await new Promise(function (r) { setTimeout(r, 1000); });
|
|
99
|
+
}
|
|
100
|
+
process.stdout.write('\r' + ' '.repeat(40) + '\r');
|
|
101
|
+
} else {
|
|
102
|
+
await new Promise(function (r) { setTimeout(r, waitSec * 1000); });
|
|
103
|
+
}
|
|
92
104
|
continue;
|
|
93
105
|
}
|
|
94
106
|
return res;
|