@tosk/gen-ui 1.0.3 → 1.0.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 (2) hide show
  1. package/gen-ui.js +160 -108
  2. package/package.json +1 -1
package/gen-ui.js CHANGED
@@ -17,72 +17,84 @@ class GenUi extends HTMLElement {
17
17
  static SELECTORS = {
18
18
  loadingOverlay: '#loading-overlay',
19
19
  previewOutput: '#preview-output',
20
- editBtn: '#edit-btn',
21
- completeBtn: '#complete-btn',
22
20
  chatWindow: '#chat-window',
23
21
  chatInput: '#chat-input',
24
22
  chatSubmit: '#chat-submit',
25
23
  chatCancel: '#chat-cancel',
26
- connectBtn: '#connect-btn',
27
24
  uiTitle: '#ui-title',
25
+ contextMenu: '#context-menu',
26
+ ctxInsert: '#ctx-insert',
27
+ ctxCopy: '#ctx-copy',
28
+ ctxEdit: '#ctx-edit',
28
29
  };
29
30
 
30
31
  static TEMPLATE = (() => {
31
32
  const template = document.createElement('template');
32
33
  template.innerHTML = `
33
34
  <style>
34
- :host { display: block; width: 100%; height: 100vh; background: #f5f5f5; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; }
35
- #container { display: flex; flex-direction: column; height: 100%; }
36
- header { height: 64px; background: #ffffff; border-bottom: 1px solid #e0e0e0; display: flex; align-items: center; justify-content: space-between; padding: 0 20px; box-shadow: 0 1px 3px rgba(0,0,0,0.05); z-index: 50; box-sizing: border-box; }
37
- .header-left { flex: 1; }
38
- .header-center { flex: 2; display: flex; align-items: center; justify-content: center; }
39
- #ui-title { background-color: #f0f0f0; padding: 8px 24px; border-radius: 8px; font-weight: 600; color: #333; font-size: 1rem; min-width: 200px; text-align: center; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; transition: all 0.3s ease; }
40
- #ui-title.loading { color: #888; background-color: #f5f5f5; animation: pulse 1.5s infinite ease-in-out; }
41
- @keyframes pulse { 0% { opacity: 0.6; } 50% { opacity: 1; } 100% { opacity: 0.6; } }
42
- .header-right { flex: 1; display: flex; justify-content: flex-end; gap: 8px; align-items: center; }
43
- .icon-btn { background: transparent; border: 1px solid transparent; border-radius: 8px; cursor: pointer; padding: 10px 14px; font-size: 1.2rem; color: #555; transition: all 0.2s; display: flex; align-items: center; justify-content: center; }
44
- .icon-btn:hover { background-color: #f3f4f6; color: #111; }
45
- .icon-btn:active { background-color: #e5e7eb; }
46
- .btn-connect.active { color: #3b82f6; background: #eff6ff; border-color: #bfdbfe; }
47
- .btn-complete { color: #2ecc71; font-weight: bold; }
48
- .btn-complete:hover { background: #f0fdf4; color: #22c55e; }
49
- .content-area { flex: 1; position: relative; overflow: hidden; width: 100%; }
35
+ :host { display: block; width: 100%; height: 100vh; background: transparent; font-family: sans-serif; }
50
36
  iframe { width: 100%; height: 100%; border: none; display: block; }
51
- .loading-overlay { position: absolute; inset: 0; background: #f5f5f5; display: flex; align-items: center; justify-content: center; z-index: 20; }
37
+
38
+ .loading-overlay { position: absolute; inset: 0; background: rgba(255,255,255,0.8); display: flex; align-items: center; justify-content: center; z-index: 20; }
52
39
  .spinner { width: 24px; height: 24px; border: 3px solid rgba(0, 0, 0, 0.1); border-left-color: #3b82f6; border-radius: 50%; animation: spin 1s linear infinite; }
53
40
  @keyframes spin { to { transform: rotate(360deg); } }
54
- .chat-overlay { position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; background: rgba(255, 255, 255, 0.85); backdrop-filter: blur(4px); z-index: 9999; display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 20px; box-sizing: border-box; }
41
+
42
+ .chat-overlay { position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; background: rgba(0, 0, 0, 0.4); backdrop-filter: blur(2px); z-index: 9999; display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 20px; box-sizing: border-box; }
55
43
  .chat-box { width: 100%; max-width: 500px; display: flex; flex-direction: column; gap: 10px; background: white; padding: 20px; border-radius: 8px; box-shadow: 0 4px 15px rgba(0,0,0,0.15); }
56
44
  .chat-input { width: 100%; padding: 10px; border: 1px solid #ccc; border-radius: 4px; font-family: inherit; resize: vertical; min-height: 100px; box-sizing: border-box; }
57
45
  .chat-actions { display: flex; justify-content: flex-end; gap: 10px; }
46
+
58
47
  button.btn { padding: 8px 16px; border: none; border-radius: 4px; cursor: pointer; font-weight: bold; }
59
48
  .btn-primary { background: #3b82f6; color: white; }
60
49
  .btn-primary:hover { opacity: 0.9; }
61
50
  .btn-cancel { background: #eee; color: #333; }
51
+
62
52
  .hidden { display: none !important; }
53
+
54
+ .context-menu {
55
+ position: absolute;
56
+ background: white;
57
+ border: 1px solid #e0e0e0;
58
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
59
+ border-radius: 8px;
60
+ padding: 4px 0;
61
+ z-index: 1000;
62
+ min-width: 160px;
63
+ display: none;
64
+ flex-direction: column;
65
+ }
66
+ .context-menu.visible { display: flex; }
67
+ .context-menu-item {
68
+ padding: 8px 16px;
69
+ cursor: pointer;
70
+ font-size: 0.9rem;
71
+ color: #333;
72
+ display: flex;
73
+ align-items: center;
74
+ gap: 8px;
75
+ transition: background 0.2s;
76
+ }
77
+ .context-menu-item:hover { background: #f5f5f5; }
78
+
79
+ #ui-title { display: none; }
63
80
  </style>
64
- <div id="container">
65
- <header>
66
- <div class="header-left"></div>
67
- <div class="header-center"><div id="ui-title"></div></div>
68
- <div class="header-right">
69
- <button id="connect-btn" class="icon-btn btn-connect" title="ファイル連携">🔗</button>
70
- <button id="edit-btn" class="icon-btn" title="修正指示">✏️</button>
71
- <button id="complete-btn" class="icon-btn btn-complete" title="確定">✅</button>
72
- </div>
73
- </header>
74
- <div class="content-area">
75
- <div id="loading-overlay" class="loading-overlay hidden"><div class="spinner"></div></div>
76
- <iframe id="preview-output" title="Generated UI"></iframe>
77
- </div>
78
- <div id="chat-window" class="chat-overlay hidden">
79
- <div class="chat-box">
80
- <p style="margin:0; font-weight:bold; color:#555;">修正指示を入力</p>
81
- <textarea id="chat-input" class="chat-input" placeholder="例: 背景を暗くして、文字を大きくして"></textarea>
82
- <div class="chat-actions">
83
- <button id="chat-cancel" class="btn btn-cancel">閉じる</button>
84
- <button id="chat-submit" class="btn btn-primary">修正する</button>
85
- </div>
81
+ <div id="loading-overlay" class="loading-overlay hidden"><div class="spinner"></div></div>
82
+ <iframe id="preview-output" title="Generated UI"></iframe>
83
+ <div id="ui-title"></div>
84
+
85
+ <div id="context-menu" class="context-menu">
86
+ <div id="ctx-insert" class="context-menu-item">挿入</div>
87
+ <div id="ctx-copy" class="context-menu-item">コピー</div>
88
+ <div id="ctx-edit" class="context-menu-item">修正</div>
89
+ </div>
90
+
91
+ <div id="chat-window" class="chat-overlay hidden">
92
+ <div class="chat-box">
93
+ <p style="margin:0; font-weight:bold; color:#555;">修正指示を入力</p>
94
+ <textarea id="chat-input" class="chat-input" placeholder="例: 背景を暗くして、文字を大きくして"></textarea>
95
+ <div class="chat-actions">
96
+ <button id="chat-cancel" class="btn btn-cancel">閉じる</button>
97
+ <button id="chat-submit" class="btn btn-primary">修正する</button>
86
98
  </div>
87
99
  </div>
88
100
  </div>
@@ -102,7 +114,7 @@ class GenUi extends HTMLElement {
102
114
  #abortController = null;
103
115
  #fileHandle = null;
104
116
  #currentCode = { html: '', css: '', javascript: '' };
105
- static #prettierModules = null; // Cache for Prettier
117
+ static #prettierModules = null;
106
118
 
107
119
  constructor() {
108
120
  super();
@@ -139,8 +151,8 @@ class GenUi extends HTMLElement {
139
151
  this.#saveKey = this.getAttribute('save-key');
140
152
  this.#originalHtml = this.innerHTML.trim();
141
153
 
142
- if (!this.#apiKey) return console.error('GenUi: "api-key" attribute is required.');
143
154
  if (this.#loadKey) return this.#loadFromFirestore();
155
+ if (!this.#apiKey) return console.error('GenUi: "api-key" attribute is required for generation.');
144
156
  if (this.#requestPrompt) this.#processRequest();
145
157
  }
146
158
 
@@ -148,15 +160,10 @@ class GenUi extends HTMLElement {
148
160
  // Event Handlers
149
161
  // ==================================================================================
150
162
  #setupInteractions() {
151
- const { editBtn, completeBtn, chatWindow, chatCancel, chatSubmit, chatInput, connectBtn } = this.#elements;
152
-
153
- editBtn.addEventListener('click', () => {
154
- chatWindow.classList.remove('hidden');
155
- chatInput.focus();
156
- });
163
+ const { chatWindow, chatCancel, chatSubmit, chatInput } = this.#elements;
164
+ const { ctxInsert, ctxCopy, ctxEdit } = this.#elements;
157
165
 
158
166
  chatCancel.addEventListener('click', () => chatWindow.classList.add('hidden'));
159
-
160
167
  chatSubmit.addEventListener('click', () => {
161
168
  const instruction = chatInput.value.trim();
162
169
  if (!instruction) return;
@@ -165,35 +172,57 @@ class GenUi extends HTMLElement {
165
172
  this.#processRefinement(instruction);
166
173
  });
167
174
 
168
- connectBtn.addEventListener('click', () => this.#handleFileConnect());
169
- completeBtn.addEventListener('click', () => this.#handleCompletion());
170
- }
175
+ this.shadowRoot.addEventListener('click', (e) => {
176
+ if (!e.target.closest('#context-menu')) {
177
+ this.#hideContextMenu();
178
+ }
179
+ });
171
180
 
172
- async #handleFileConnect() {
173
- try {
174
- const [handle] = await window.showOpenFilePicker({
175
- types: [{ description: 'HTML Files', accept: { 'text/html': ['.html'] } }],
176
- multiple: false,
177
- });
178
- this.#fileHandle = handle;
179
- this.#elements.connectBtn.classList.add('active');
180
- this.#elements.connectBtn.title = `連携中: ${handle.name}`;
181
- alert(`「${handle.name}」と連携しました。\n確定ボタンを押すと、このファイルが自動的に書き換えられます。`);
182
- } catch (err) { /* Cancelled */ }
181
+ ctxInsert.addEventListener('click', async () => {
182
+ this.#hideContextMenu();
183
+ try {
184
+ const [handle] = await window.showOpenFilePicker({
185
+ types: [{ description: 'HTML Files', accept: { 'text/html': ['.html'] } }],
186
+ multiple: false,
187
+ });
188
+
189
+ this.#fileHandle = handle;
190
+ await this.#directWriteToFile();
191
+
192
+ } catch (err) {
193
+ // ファイル選択キャンセル時などは何もしない
194
+ }
195
+ });
196
+
197
+ ctxCopy.addEventListener('click', () => {
198
+ this.#hideContextMenu();
199
+ this.#copyToClipboard();
200
+ alert('コピーしました。');
201
+ });
202
+
203
+ ctxEdit.addEventListener('click', () => {
204
+ this.#hideContextMenu();
205
+ chatWindow.classList.remove('hidden');
206
+ chatInput.focus();
207
+ });
183
208
  }
184
209
 
185
- async #handleCompletion() {
186
- const msg = this.#fileHandle
187
- ? '連携中のファイルを書き換えますか?\n(Git等でバックアップを推奨)'
188
- : 'クリップボードにコピーしますか?';
189
- if (!confirm(msg)) return;
210
+ #showContextMenu(x, y) {
211
+ const menu = this.#elements.contextMenu;
212
+ const rect = this.getBoundingClientRect();
190
213
 
191
- if (this.#fileHandle) {
192
- await this.#directWriteToFile();
193
- } else {
194
- await this.#copyToClipboard();
195
- alert('コピーしました。');
196
- }
214
+ let left = x;
215
+ let top = y;
216
+ if (left + 160 > rect.width) left = rect.width - 160;
217
+ if (top + 120 > rect.height) top = rect.height - 120;
218
+
219
+ menu.style.left = `${left}px`;
220
+ menu.style.top = `${top}px`;
221
+ menu.classList.add('visible');
222
+ }
223
+
224
+ #hideContextMenu() {
225
+ this.#elements.contextMenu.classList.remove('visible');
197
226
  }
198
227
 
199
228
  // ==================================================================================
@@ -208,8 +237,6 @@ class GenUi extends HTMLElement {
208
237
  const prettierData = await this.#loadPrettier();
209
238
  const rawCode = this.#assembleFinalCode('web-component', myId);
210
239
 
211
- // Prettier formatting ensures the inserted block is clean and consistently indented,
212
- // reducing 'messy' diffs in the editor's Undo/Redo stack.
213
240
  const formattedCode = await this.#formatCodeWithPrettier(rawCode, prettierData);
214
241
 
215
242
  const newContent = originalContent.replace(targetRegex, formattedCode.trim());
@@ -218,8 +245,8 @@ class GenUi extends HTMLElement {
218
245
  await writable.write(newContent);
219
246
  await writable.close();
220
247
 
221
- alert('書き換え完了!Prettierで整形しました✨');
222
- if (confirm('反映のためにリロードしますか?')) location.reload();
248
+ // 修正: 書き込み完了後のアラートとリロード確認を削除
249
+
223
250
  } catch (err) {
224
251
  console.error(err);
225
252
  alert(`エラー: ${err.message}`);
@@ -236,8 +263,6 @@ class GenUi extends HTMLElement {
236
263
  let targetRegex;
237
264
 
238
265
  if (myId) {
239
- // Improved Regex: Ensures we capture the full opening tag even with multiline attributes
240
- // and lazily matches content up to the closing tag if it exists.
241
266
  targetRegex = new RegExp(`<gen-ui[^>]*id=["']${myId}["'][^>]*>([\\s\\S]*?<\\/gen-ui>)?`, 'i');
242
267
  if (!targetRegex.test(content)) throw new Error(`ID="${myId}" が見つかりません。`);
243
268
  } else {
@@ -256,7 +281,6 @@ class GenUi extends HTMLElement {
256
281
 
257
282
  if (mode === 'web-component') {
258
283
  const processedJs = javascript.replace(/document\.(querySelector|querySelectorAll|getElementById)/g, 'root.$1');
259
- // Intentionally unindented to let Prettier handle the final layout
260
284
  return `
261
285
  <gen-ui id="${componentId}">
262
286
  <template>
@@ -314,7 +338,6 @@ ${html}
314
338
  if (!template) return;
315
339
  this.shadowRoot.innerHTML = '';
316
340
  this.shadowRoot.appendChild(template.content.cloneNode(true));
317
- // Re-activate scripts by cloning them
318
341
  this.shadowRoot.querySelectorAll('script').forEach(old => {
319
342
  const fresh = document.createElement('script');
320
343
  fresh.textContent = old.textContent;
@@ -336,8 +359,27 @@ ${html}
336
359
 
337
360
  #renderPreview(html, css, javascript, title) {
338
361
  this.#currentCode = { html, css, javascript };
339
- this.#elements.previewOutput.srcdoc = `<!DOCTYPE html><html lang="ja"><head><meta charset="UTF-8"><style>body{margin:0;padding:20px;min-height:100vh;display:flex;align-items:center;justify-content:center;background:#f5f5f5;}*,*::before,*::after{box-sizing:border-box;}${css}</style></head><body>${html}<script>try{${javascript||''}}catch(e){console.error(e)}<\/script></body></html>`;
362
+
363
+ const iframeCss = `body{margin:0;padding:0;min-height:100vh;background:#fff;}*,*::before,*::after{box-sizing:border-box;}${css}`;
364
+
365
+ this.#elements.previewOutput.srcdoc = `<!DOCTYPE html><html lang="ja"><head><meta charset="UTF-8"><style>${iframeCss}</style></head><body>${html}<script>try{${javascript||''}}catch(e){console.error(e)}<\/script></body></html>`;
340
366
  this.#elements.uiTitle.textContent = title || this.#requestPrompt || 'No Title';
367
+
368
+ this.#elements.previewOutput.onload = () => {
369
+ try {
370
+ const doc = this.#elements.previewOutput.contentDocument;
371
+ doc.addEventListener('contextmenu', (e) => {
372
+ e.preventDefault();
373
+ const iframeRect = this.#elements.previewOutput.getBoundingClientRect();
374
+ const x = e.clientX + iframeRect.left;
375
+ const y = e.clientY + iframeRect.top;
376
+ this.#showContextMenu(x, y);
377
+ });
378
+ doc.addEventListener('click', () => this.#hideContextMenu());
379
+ } catch (e) {
380
+ console.warn("Context menu access denied", e);
381
+ }
382
+ };
341
383
  }
342
384
 
343
385
  // ==================================================================================
@@ -370,15 +412,16 @@ ${html}
370
412
  }
371
413
 
372
414
  async #processRequest() {
373
- await this.#executeGemini((html) => this.#buildPrompt(`<style>${Array.from(document.querySelectorAll('style')).map(s=>s.textContent).join('\n')}</style>${html}`, this.#requestPrompt));
415
+ await this.#executeGemini((html) => this.#buildPrompt(`<style>${Array.from(document.querySelectorAll('style')).map(s=>s.textContent).join('\n')}</style>${html}`, this.#requestPrompt, document.body.innerHTML));
374
416
  }
375
417
 
376
418
  async #processRefinement(instruction) {
377
- await this.#executeGemini(() => this.#buildPrompt(this.#currentCode.html, instruction));
419
+ await this.#executeGemini(() => this.#buildPrompt(this.#currentCode.html, instruction, document.body.innerHTML));
378
420
  }
379
421
 
380
422
  async #executeGemini(promptBuilder) {
381
423
  this.#updateUIState('LOADING');
424
+
382
425
  this.#abortController = new AbortController();
383
426
  try {
384
427
  const prompt = promptBuilder(this.#originalHtml);
@@ -404,8 +447,10 @@ ${html}
404
447
  }
405
448
  }
406
449
 
407
- #buildPrompt(html, request) {
408
- const htmlContent = html ? `${html}` : `なし。指示に基づき新規生成`;
450
+ #buildPrompt(html, request, parent) {
451
+ const targetHtml = html ? `${html}` : `なし。指示に基づき新規生成`;
452
+ const parentHtml = parent ? `${parent}` : `なし`;
453
+
409
454
  return `
410
455
  ## 命令
411
456
  あなたは世界トップクラスのUIエンジニアです。
@@ -415,15 +460,18 @@ ${html}
415
460
  ### HTML
416
461
  1. 「対象HTML」があれば、セマンティックHTML(\`main\`や\`header\`等)を使用して意味的に正しくリファクタリングしてください。
417
462
  2. 「対象HTML」がなければ、指示に基づき最適なHTMLを新規生成してください。
418
- 3. 正しいARIAロールと属性を必ず使用してください。
419
- 4. 純粋に装飾目的の画像、またはスクリーンリーダーにとって繰り返しになる場合を除き、すべての画像に代替テキストを追加してください。
420
- 5. 配置用の親要素(divやwrapper等)で囲まず、コンポーネント本体をルート要素として出力してください。
463
+ 3. 「親ページのHTML」があれば、デザインとレイアウトの整合性を取るために参照してください。ただし生成するコードに含めないでください。
464
+ 4. 正しいARIAロールと属性を必ず使用してください。
465
+ 5. 純粋に装飾目的の画像、またはスクリーンリーダーにとって繰り返しになる場合を除き、すべての画像に代替テキストを追加してください。
466
+ 6. 配置用の親要素(divやwrapper等)で囲まず、コンポーネント本体をルート要素として出力してください。
421
467
 
422
468
  ### CSS
423
469
  1. CSSはマテリアルデザインの原則に従ってください。
424
470
  2. CSSはレスポンシブデザインを実装してください。
425
- 3. 画像は、アスペクト比を維持し、画像全体が表示されるようにしてください。意図しないトリミングが発生する \`object-fit: cover;\` は避け、必要であれば \`object-fit: contain;\` や \`height: auto;\` を使用して、画像が途切れないようにしてください。
426
- 4. \`object-fit: contain;\` や \`height: auto;\` を使用して画像が途切れないようにする場合、画像コンテナの背景色は、コンポーネント全体の背景色(通常は \`#ffffff\`)と一致させるか、透明 (\`transparent\`) に設定し、余白部分の色が浮かないようにしてください。
471
+ 3. 画像の表示(\`object-fit\`)は、画像の役割に応じて以下のように使い分けてください
472
+ - 背景・装飾・風景・アバター: コンテナ全体を埋めるために \`object-fit: cover;\` を使用し、余白が出ないようにしてください。
473
+ - 商品・図解・グラフ: 全体が見えることが重要な場合は \`object-fit: contain;\` を使用してください。ただし、その場合は余白が目立たないよう、画像コンテナの背景色を調整(透明または画像と馴染む色)してください。
474
+ 4. 画像エリアは、レイアウト崩れを防ぐために適切な高さ指定(\`height\`)またはアスペクト比(\`aspect-ratio\`)を設定してください。
427
475
  5. CSSセレクタは、可能な限り特定のクラス名を使用し、bodyやhtmlタグへの直接的なスタイル適用は避けてください。
428
476
  6. 画面中央揃えやbodyへのレイアウト指定は禁止です。コンポーネント内部のスタイルのみ記述してください。
429
477
 
@@ -433,18 +481,21 @@ ${html}
433
481
  3. コードは \`document.addEventListener('DOMContentLoaded', () => { ... })\` 内に記述し、DOM読み込み後に実行されるようにしてください。
434
482
  4. エラーハンドリング(try-catch等)を適切に行い、コンソールエラーが出ないように配慮してください。
435
483
 
436
- ### 画像リソースのルール(重要)
437
- 画像(imgタグやbackground-image等)が必要な場合は、架空のパスではなく、以下のルールに従ってください
438
- 1. 一般的な画像(背景、商品、記事サムネイルなど)
439
- - 書式: "https://picsum.photos/{width}/{height}?random={unique_id}"
440
- - {width}, {height} は必要なサイズ(例: 800/600)に置き換える。
441
- - {unique_id} には要素ごとに異なるランダムな数字(1, 2, 3...)を入れる。
442
- - 例: <img src="https://picsum.photos/400/300?random=1" alt="記事画像">
443
-
444
- 2. ユーザープロフィール画像(アバター・アイコン):
445
- - 書式: "https://i.pravatar.cc/{size}?img={1-70}"
446
- - {size} はサイズ(例: 150)。imgパラメータには1〜70のランダムな数字を入れる。
447
- - 例: <img src="https://i.pravatar.cc/150?img=12" alt="ユーザーアイコン" style="border-radius: 50%;">
484
+ ### 画像リソースのルール
485
+ 画像(imgタグやbackground-image等)の扱いは、以下の優先順位とルールを厳守してください。
486
+ 1. 既存パスの維持(最優先):
487
+ - 「対象HTML」に既に記述されている画像パスは、そのまま出力してください。
488
+ 2. 新規ダミー画像の生成:
489
+ - 指示により新しく画像要素を追加する場合や、元画像のパスが空の場合に限り、以下のURL形式を使用してください。
490
+ A. 一般的な画像(背景、商品、記事等):
491
+ - 書式: "https://picsum.photos/seed/{seed_id}/{width}/{height}"
492
+ - {width}, {height} は必要なサイズ(例: 800/600)に置き換える。
493
+ - {seed_id} には画像の文脈を表す固定の英単語(例: "nature", "city", "food")を入れてください。
494
+ - 例: <img src="https://picsum.photos/seed/nature/400/300" alt="風景">
495
+ B. ユーザープロフィール画像(アバター・アイコン):
496
+ - 書式: "https://i.pravatar.cc/{size}?u={unique_id}"
497
+ - {size} はサイズ(例: 150)。uパラメータには固定の文字列を入れる。
498
+ - 例: <img src="https://i.pravatar.cc/150?u=user1" alt="ユーザーアイコン" style="border-radius: 50%;">
448
499
 
449
500
  ### 制約条件
450
501
  1. CSSは、外部のライブラリやフレームワーク(Tailwind CSS や Bootstrap等)に依存してはいけません。
@@ -454,7 +505,8 @@ ${html}
454
505
 
455
506
  ## 入力データ
456
507
  - ユーザーからの指示: ${request}
457
- - 対象HTML: ${htmlContent}
508
+ - 対象HTML: ${targetHtml}
509
+ - 親ページのHTML: ${parentHtml}
458
510
 
459
511
  ## 出力指示子
460
512
  - 回答は必ずJSON形式でなければなりません。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tosk/gen-ui",
3
- "version": "1.0.3",
3
+ "version": "1.0.5",
4
4
  "main": "gen-ui.js",
5
5
  "files": [
6
6
  "gen-ui.js"