@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.
- package/gen-ui.js +160 -108
- 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:
|
|
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
|
-
|
|
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
|
-
|
|
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="
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
<
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
<
|
|
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;
|
|
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 {
|
|
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
|
-
|
|
169
|
-
|
|
170
|
-
|
|
175
|
+
this.shadowRoot.addEventListener('click', (e) => {
|
|
176
|
+
if (!e.target.closest('#context-menu')) {
|
|
177
|
+
this.#hideContextMenu();
|
|
178
|
+
}
|
|
179
|
+
});
|
|
171
180
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
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
|
-
|
|
186
|
-
const
|
|
187
|
-
|
|
188
|
-
: 'クリップボードにコピーしますか?';
|
|
189
|
-
if (!confirm(msg)) return;
|
|
210
|
+
#showContextMenu(x, y) {
|
|
211
|
+
const menu = this.#elements.contextMenu;
|
|
212
|
+
const rect = this.getBoundingClientRect();
|
|
190
213
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
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
|
-
|
|
222
|
-
|
|
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
|
-
|
|
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
|
|
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.
|
|
419
|
-
4.
|
|
420
|
-
5.
|
|
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.
|
|
426
|
-
|
|
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
|
-
-
|
|
440
|
-
|
|
441
|
-
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
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: ${
|
|
508
|
+
- 対象HTML: ${targetHtml}
|
|
509
|
+
- 親ページのHTML: ${parentHtml}
|
|
458
510
|
|
|
459
511
|
## 出力指示子
|
|
460
512
|
- 回答は必ずJSON形式でなければなりません。
|