@biunow/agent-biuart 0.1.2 → 0.1.5

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 (67) hide show
  1. package/bin/server.mjs +2 -83
  2. package/manifest.json +3 -3
  3. package/package.json +1 -1
  4. package/studio/app.js +46 -14
  5. package/studio/composer.css +2 -2
  6. package/studio/index.html +0 -1
  7. package/studio/styles.css +6 -0
  8. package/studio/thumbs/01_cave.png +0 -0
  9. package/studio/thumbs/02_egypt.png +0 -0
  10. package/studio/thumbs/03_greek.png +0 -0
  11. package/studio/thumbs/04_roman.png +0 -0
  12. package/studio/thumbs/05_gothic.png +0 -0
  13. package/studio/thumbs/06_renaissance.png +0 -0
  14. package/studio/thumbs/08_impressionism.png +0 -0
  15. package/studio/thumbs/09_postimp.png +0 -0
  16. package/studio/thumbs/10_nouveau.png +0 -0
  17. package/studio/thumbs/11_cubism.png +0 -0
  18. package/studio/thumbs/12_bauhaus.png +0 -0
  19. package/studio/thumbs/13_pop.png +0 -0
  20. package/studio/thumbs/14_8bit.png +0 -0
  21. package/studio/thumbs/15_raytrace.png +0 -0
  22. package/studio/thumbs/16_2026.png +0 -0
  23. package/studio/thumbs/17_ink.png +0 -0
  24. package/studio/thumbs/18_klimt.png +0 -0
  25. package/studio/thumbs/19_munch.png +0 -0
  26. package/studio/thumbs/20_dunhuang.png +0 -0
  27. package/studio/thumbs/21_kusama.png +0 -0
  28. package/studio/thumbs/22_constructivism.png +0 -0
  29. package/studio/thumbs/23_dali.png +0 -0
  30. package/studio/thumbs/24_hopper.png +0 -0
  31. package/studio/thumbs/25_ghibli.png +0 -0
  32. package/studio/thumbs/26_vaporwave.png +0 -0
  33. package/studio/thumbs/27_kirby.png +0 -0
  34. package/studio/thumbs/28_monet.png +0 -0
  35. package/studio/thumbs/29_seurat.png +0 -0
  36. package/studio/thumbs/30_matisse.png +0 -0
  37. package/studio/thumbs/31_haring.png +0 -0
  38. package/studio/thumbs/32_rembrandt.png +0 -0
  39. package/studio/thumbs/33_rubberhose.png +0 -0
  40. package/studio/thumbs/34_shadowpuppet.png +0 -0
  41. package/studio/thumbs/35_shinkai.png +0 -0
  42. package/studio/thumbs/36_picasso_blue.png +0 -0
  43. package/studio/thumbs/38_giotto.png +0 -0
  44. package/studio/thumbs/39_piero.png +0 -0
  45. package/studio/thumbs/40_raphael.png +0 -0
  46. package/studio/thumbs/41_michelangelo.png +0 -0
  47. package/studio/thumbs/42_guardi.png +0 -0
  48. package/studio/thumbs/43_lorenzetti.png +0 -0
  49. package/studio/thumbs/44_cortona.png +0 -0
  50. package/studio/thumbs/45_renoir.png +0 -0
  51. package/studio/thumbs/46_pissarro.png +0 -0
  52. package/studio/thumbs/47_sisley.png +0 -0
  53. package/studio/thumbs/48_gauguin.png +0 -0
  54. package/studio/thumbs/49_chagall.png +0 -0
  55. package/studio/thumbs/50_ma_yuan.png +0 -0
  56. package/studio/thumbs/51_persian_miniature.png +0 -0
  57. package/studio/thumbs/52_south_park.png +0 -0
  58. package/studio/thumbs/53_pixar.png +0 -0
  59. package/studio/thumbs/54_ghibli_lab.png +0 -0
  60. package/studio/thumbs/55_dunhuang.png +0 -0
  61. package/studio/thumbs/t1_3b1b.png +0 -0
  62. package/studio/thumbs/t2_keynote_ui.png +0 -0
  63. package/studio/thumbs/t3_finance_chart.png +0 -0
  64. package/studio/thumbs/y1_kurzgesagt.png +0 -0
  65. package/studio/thumbs/y2_vox.png +0 -0
  66. package/studio/thumbs/y3_whiteboard.png +0 -0
  67. package/studio/thumbs/y5_kinetic_type.png +0 -0
package/bin/server.mjs CHANGED
@@ -34,7 +34,7 @@ function publicStyle(item) {
34
34
  film: item.film || '',
35
35
  previewable: !!item.era,
36
36
  kind: 'style',
37
- thumb: `/api/thumb/${item.id}.png`,
37
+ thumb: `/thumbs/${item.id}.png`,
38
38
  desc: item.summary || '',
39
39
  };
40
40
  }
@@ -50,7 +50,7 @@ function publicGrammar(item) {
50
50
  film: '',
51
51
  previewable: false,
52
52
  kind: 'grammar',
53
- thumb: `/api/thumb/${item.id}.png`,
53
+ thumb: `/thumbs/${item.id}.png`,
54
54
  desc: item.summary || '',
55
55
  };
56
56
  }
@@ -98,55 +98,6 @@ async function saveUploads(workspace, jobId, files) {
98
98
  return saved;
99
99
  }
100
100
 
101
- /* ---- 画风缩略图:用引擎渲一帧静帧做封面,工作区缓存 ---- */
102
- const thumbPending = new Set();
103
-
104
- function thumbDirOf(cfg) {
105
- return ensureDir(path.join(cfg.workspace, 'thumbs'));
106
- }
107
-
108
- async function renderThumb(cfg, engineRoot, item, kind) {
109
- const dir = thumbDirOf(cfg);
110
- const dest = path.join(dir, `${item.id}.png`);
111
- if (fs.existsSync(dest)) return;
112
- const failedMark = path.join(dir, `${item.id}.failed`);
113
- if (fs.existsSync(failedMark)) return;
114
- if (thumbPending.has(item.id)) return;
115
- thumbPending.add(item.id);
116
- const tmpDir = ensureDir(path.join(dir, '.tmp', item.id));
117
- try {
118
- if (kind === 'style' && item.era) {
119
- await render({
120
- engineRoot, out: tmpDir, solo: item.era, film: item.film || '',
121
- stills: '1.2', stillWidth: 640,
122
- });
123
- } else {
124
- const example = path.join(engineRoot, 'examples', `${item.id}.json`);
125
- if (!fs.existsSync(example)) throw new Error(`缺少示例 spec:examples/${item.id}.json`);
126
- await render({ engineRoot, out: tmpDir, specPath: example, stills: '0.8', stillWidth: 640 });
127
- }
128
- const png = fs.readdirSync(tmpDir).find((name) => name.endsWith('.png'));
129
- if (!png) throw new Error('引擎没有产出静帧');
130
- fs.copyFileSync(path.join(tmpDir, png), dest);
131
- } catch (error) {
132
- // 写失败标记:避免每次启动/每次请求都重复触发一次注定失败的 Chromium 渲染
133
- fs.writeFileSync(failedMark, String(error?.message || error));
134
- console.warn(`[biuart] 缩略图失败 ${item.id}:`, error?.message || error);
135
- } finally {
136
- thumbPending.delete(item.id);
137
- fs.rmSync(tmpDir, { recursive: true, force: true });
138
- }
139
- }
140
-
141
- function ensureThumbs(cfg) {
142
- const engine = engineReady(cfg);
143
- if (!engine.present) return;
144
- void (async () => {
145
- for (const item of STYLES) await renderThumb(cfg, engine.engineRoot, item, 'style');
146
- for (const item of GRAMMARS) await renderThumb(cfg, engine.engineRoot, item, 'grammar');
147
- })();
148
- }
149
-
150
101
  function seedSkillDir(projectDir, packageRoot, skillId) {
151
102
  const src = path.join(packageRoot, 'skills', skillId);
152
103
  if (!fs.existsSync(src)) return null;
@@ -174,35 +125,6 @@ const app = createAgentServer({
174
125
  return true;
175
126
  }
176
127
 
177
- if (pathname.startsWith('/api/thumb/') && req.method === 'GET') {
178
- const id = decodeURIComponent(pathname.slice('/api/thumb/'.length)).replace(/\.png$/, '');
179
- if (!/^[\w-]+$/.test(id)) {
180
- sendJson(res, 400, { error: 'bad id' });
181
- return true;
182
- }
183
- const dir = thumbDirOf(cfg);
184
- const file = path.join(dir, `${id}.png`);
185
- if (fs.existsSync(file)) {
186
- serveLocalFile(req, res, file);
187
- } else {
188
- const engine = engineReady(cfg);
189
- const item = STYLES.find((s) => s.id === id);
190
- const grammar = GRAMMARS.find((g) => g.id === id);
191
- if (!item && !grammar) {
192
- sendJson(res, 404, { error: 'unknown id' });
193
- return true;
194
- }
195
- if (fs.existsSync(path.join(dir, `${id}.failed`)) || !engine.present) {
196
- sendJson(res, 404, { error: 'thumb unavailable' });
197
- return true;
198
- }
199
- void renderThumb(cfg, engine.engineRoot, item || grammar, item ? 'style' : 'grammar');
200
- res.writeHead(202, { 'content-type': 'application/json; charset=utf-8' });
201
- res.end(JSON.stringify({ pending: true }));
202
- }
203
- return true;
204
- }
205
-
206
128
  if (pathname === '/api/meta' && req.method === 'GET') {
207
129
  const manifest = JSON.parse(fs.readFileSync(path.join(root, 'manifest.json'), 'utf8'));
208
130
  const engine = engineReady(cfg);
@@ -570,6 +492,3 @@ const app = createAgentServer({
570
492
  },
571
493
  });
572
494
 
573
- // 启动后后台补齐画风缩略图(已存在则跳过)
574
- ensureThumbs(app.cfg);
575
-
package/manifest.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "schemaVersion": 1,
3
3
  "id": "biuart",
4
4
  "name": "BiuArt",
5
- "version": "0.1.2",
5
+ "version": "0.1.5",
6
6
  "publisher": "Biunow",
7
7
  "description": "BiuArt 艺术动画:锁定一种画风,用代码画出会动的画面,一镜一镜渲成片。",
8
8
  "entry": {
@@ -45,8 +45,8 @@
45
45
  "requiresEntitlement": "export"
46
46
  },
47
47
  "integrity": {
48
- "sha256": "f6fca5f754b9d3c4fb1df85456d1b83232b58c3c14578a547816bfde8a1f88e1",
49
- "signature": "mxu0dAVRKJFWK4dOJRH7rn5rkd3Phe/uX6xnGKl+08yQMLZY7m0U+b48JK7JISI2VhPDc31oRhUqLTdz2f50Bw==",
48
+ "sha256": "9a17bbf3bd7357cb00cc767b09778ab5ab0290b2a852a2bcd52eee2ab772f5c8",
49
+ "signature": "vjO1S9jyuueulxk024KVjLDq2zyvr8ajjoi/HvTOhmo0R90zR6YqEys8Ulh9WL2KvD4cyrYO5Su2SXL0+K/4BA==",
50
50
  "signedBy": "publisher-key"
51
51
  }
52
52
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@biunow/agent-biuart",
3
- "version": "0.1.2",
3
+ "version": "0.1.5",
4
4
  "description": "BiuArt",
5
5
  "license": "UNLICENSED",
6
6
  "publishConfig": {
package/studio/app.js CHANGED
@@ -79,14 +79,14 @@
79
79
  state.style = item?.id || state.style;
80
80
  if (!item) return;
81
81
  $('style-group').textContent = item.group || '—';
82
- const parts = [item.summary || '', item.previewable ? '可试渲染 2 秒。' : '参数化片段,由代码按 spec 渲染。'];
82
+ const parts = [item.summary || '', item.previewable ? '可试渲染 2 秒。' : '这一种直接出片,不做试渲染。'];
83
83
  $('style-summary').textContent = parts.filter(Boolean).join(' ');
84
84
  const canPreview = !!item.previewable && state.meta.enginePresent;
85
85
  $('preview').disabled = !canPreview;
86
- $('preview').title = canPreview ? '渲一段示例看画风' : (item.previewable ? '引擎未安装' : '这个语法没有内置示例场景');
86
+ $('preview').title = canPreview ? '渲一段示例看画风' : (item.previewable ? '画风样本准备中' : '这一种没有试渲染');
87
87
  $('preview-status').textContent = item.previewable
88
- ? (state.meta.enginePresent ? '示例场景可用。' : '引擎未安装。')
89
- : '解说语法走 spec,不提供内置预览。';
88
+ ? (state.meta.enginePresent ? '示例场景可用。' : '样本准备中。')
89
+ : '';
90
90
  }
91
91
 
92
92
  /* ---------- 小选择器:时长 / 画幅 ---------- */
@@ -216,6 +216,7 @@
216
216
  host.prepend(el);
217
217
  }
218
218
  const mp4 = job.outputMp4 && /\.mp4$/i.test(job.outputMp4);
219
+ el.className = `job${mp4 ? ' playable' : ''}`;
219
220
  el.innerHTML = `
220
221
  <div class="job-top">
221
222
  <div class="job-title">${escapeHtml(job.title || job.styleName || '任务')}</div>
@@ -223,13 +224,45 @@
223
224
  </div>
224
225
  <div class="job-msg">${escapeHtml(job.message || '')}</div>
225
226
  <div class="job-bar"><span style="width:${Math.max(0, Math.min(100, Number(job.progress) || 0))}%"></span></div>
226
- ${mp4 ? `<video class="job-video" controls playsinline preload="metadata" src="/api/file?path=${encodeURIComponent(job.outputMp4)}"></video>` : ''}
227
- <div class="job-actions">
228
- ${job.projectPath ? `<span class="job-msg">${escapeHtml(job.projectPath)}</span>` : ''}
229
- ${job.error ? `<span class="job-msg">${escapeHtml(job.error)}</span>` : ''}
230
- </div>
227
+ ${mp4 ? '<div class="job-play">▷ 点击播放成片</div>' : ''}
228
+ ${job.error ? `<div class="job-msg">${escapeHtml(job.error)}</div>` : ''}
231
229
  `;
230
+ el.onclick = mp4 ? () => openFilm(job) : null;
231
+ }
232
+ }
233
+
234
+ /* ---------- 成片弹窗 ---------- */
235
+ function openFilm(job) {
236
+ let back = document.querySelector('.film-modal');
237
+ if (!back) {
238
+ back = document.createElement('div');
239
+ back.className = 'film-modal';
240
+ back.innerHTML = `
241
+ <div class="film-modal-card" role="dialog" aria-modal="true">
242
+ <div class="film-modal-bar">
243
+ <strong></strong>
244
+ <button type="button" class="film-modal-x" aria-label="关闭">×</button>
245
+ </div>
246
+ <video controls playsinline></video>
247
+ </div>`;
248
+ document.body.appendChild(back);
249
+ const close = () => {
250
+ const video = back.querySelector('video');
251
+ video.pause();
252
+ video.removeAttribute('src');
253
+ video.load();
254
+ back.hidden = true;
255
+ };
256
+ back.querySelector('.film-modal-x').addEventListener('click', close);
257
+ back.addEventListener('click', (e) => { if (e.target === back) close(); });
258
+ document.addEventListener('keydown', (e) => { if (e.key === 'Escape') close(); });
232
259
  }
260
+ back.querySelector('strong').textContent = job.title || job.styleName || '成片';
261
+ const src = `/api/jobs/${encodeURIComponent(job.id)}/film`;
262
+ const video = back.querySelector('video');
263
+ video.src = src;
264
+ back.hidden = false;
265
+ video.play().catch(() => {});
233
266
  }
234
267
 
235
268
  function stateLabel(status) {
@@ -341,15 +374,14 @@
341
374
  updateCharHint();
342
375
  updateStoryboardHint();
343
376
  const chips = [];
344
- chips.push(`<span class="chip ${state.meta.enginePresent ? '' : 'bad'}">${state.meta.enginePresent ? '引擎已就绪' : '引擎未安装'}</span>`);
345
- if (!state.meta.canExport) chips.push('<span class="chip warn">未激活</span>');
377
+ chips.push(`<span class="chip ${state.meta.enginePresent ? '' : 'warn'}">${state.meta.enginePresent ? '画风已就绪' : '画风准备中'}</span>`);
378
+ if (!state.meta.canExport) chips.push('<span class="chip bad">未授权</span>');
379
+ chips.push(`<span class="chip">${state.meta.styles.length} 种画风</span>`);
346
380
  $('health-chips').innerHTML = chips.join('');
347
- $('engine-line').textContent = state.meta.enginePresent
348
- ? `Engine: ${state.meta.engineRoot}`
349
- : 'ENGINE_ROOT 未设置:把面板 settings.engineRoots["biuart"] 指向 huashu-art-motion/scripts/engine。';
350
381
  if (!state.meta.enginePresent) {
351
382
  $('preview').disabled = true;
352
383
  $('submit').disabled = true;
384
+ showError('BiuArt 暂时还没准备好,请稍后再试;一直不行请联系客服。');
353
385
  }
354
386
  await refreshJobs();
355
387
  setInterval(refreshJobs, 3000);
@@ -400,7 +400,7 @@ button:disabled { cursor: not-allowed; opacity: .5; }
400
400
  display: block; width: 100%; aspect-ratio: 16 / 9; border-radius: 7px;
401
401
  overflow: hidden; background: #ecece6;
402
402
  }
403
- .sm-thumb img { width: 100%; height: 100%; object-fit: contain; display: block; }
403
+ .sm-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
404
404
  .sm-thumb-ph {
405
405
  display: flex; align-items: center; justify-content: center; width: 100%; height: 100%;
406
406
  font-size: 17px; font-weight: 600; color: var(--muted);
@@ -413,7 +413,7 @@ button:disabled { cursor: not-allowed; opacity: .5; }
413
413
  overflow: hidden; animation: sm-in .16s ease-out;
414
414
  }
415
415
  .sm-preview-img { width: 100%; aspect-ratio: 16 / 9; background: #ecece6; }
416
- .sm-preview-img img { width: 100%; height: 100%; object-fit: contain; display: block; }
416
+ .sm-preview-img img { width: 100%; height: 100%; object-fit: cover; display: block; }
417
417
  .sm-preview-body { padding: 11px 14px 13px; }
418
418
  .sm-preview-title { display: flex; align-items: baseline; gap: 8px; }
419
419
  .sm-preview-title strong {
package/studio/index.html CHANGED
@@ -14,7 +14,6 @@
14
14
  <div class="eyebrow">BIUART · ART MOTION</div>
15
15
  <h1>把画风写成会动的代码</h1>
16
16
  <p class="hero-desc">选一种画风,写下要讲的事。BiuArt 先出镜头表,再逐帧渲成片;画面由代码绘制,不靠拼图转场。</p>
17
- <div class="sub" id="engine-line">正在检查引擎…</div>
18
17
  </div>
19
18
  <div class="hero-aside">
20
19
  <div class="hero-badge">
package/studio/styles.css CHANGED
@@ -59,6 +59,12 @@
59
59
  .job-bar { height: 4px; border-radius: 99px; background: #ededE7; margin-top: 8px; overflow: hidden; }
60
60
  .job-bar > span { display: block; height: 100%; background: var(--accent-deep); transition: width .3s ease; }
61
61
  .job-video { width: 100%; border-radius: 8px; margin-top: 8px; background: #111; }
62
+ .job.playable { cursor: pointer; }
63
+ .job.playable:hover { border-color: var(--accent-deep); background: #fbfdf2; }
64
+ .job-play {
65
+ margin-top: 8px; font-size: 11px; font-weight: 650; color: #4d5c16;
66
+ background: var(--accent-soft); border-radius: 99px; padding: 4px 10px; display: inline-block;
67
+ }
62
68
  .job-actions { display: flex; gap: 8px; margin-top: 7px; flex-wrap: wrap; }
63
69
  .job-actions a { font-size: 11px; color: var(--ink); text-decoration: underline; text-underline-offset: 3px; }
64
70
  .empty { color: var(--muted-2); font-size: 12px; padding: 8px 0; }
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file