SenY commited on
Commit
0aabf7d
1 Parent(s): a99ea9f

Upload 6 files

Browse files
Files changed (6) hide show
  1. control.js +22 -0
  2. history.js +120 -0
  3. index.html +178 -409
  4. prompt.js +156 -0
  5. storage.js +30 -0
  6. translation.js +184 -0
control.js ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ updateHistoryList();
2
+ loadFromUserStorage();
3
+ setInterval(() => {
4
+ saveToUserStorage();
5
+ }, 60000);
6
+ // 入力フォームの内容を保存する
7
+ document.querySelectorAll('input, textarea').forEach(input => {
8
+ input.addEventListener('input', saveToUserStorage);
9
+ });
10
+ // Ctrl+Enterでプロンプト生成を実行する
11
+ document.addEventListener('keydown', function (event) {
12
+ if (event.ctrlKey && event.key === 'Enter') {
13
+ event.preventDefault(); // デフォルトの動作を防ぐ
14
+ generatePrompt(); // プロンプト生成関数を呼び出す
15
+ }
16
+ });
17
+
18
+ // サイドバーの切り替え機能を追加
19
+ document.getElementById('sidebarToggle').addEventListener('click', function () {
20
+ document.getElementById('sidebar').classList.toggle('active');
21
+ document.getElementById('content').classList.toggle('active');
22
+ });
history.js ADDED
@@ -0,0 +1,120 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ function saveToHistory() {
2
+ const historyItem = {
3
+ query: document.getElementById('query').value,
4
+ promptEn: document.getElementById('promptEn').value,
5
+ promptMyLanguage: document.getElementById('promptMyLanguage').value,
6
+ danbooruTags: document.getElementById('danbooruTags').value,
7
+ timestamp: new Date().toISOString()
8
+ };
9
+
10
+ let history = JSON.parse(localStorage.getItem('gemini_prompt_history') || '[]');
11
+ history.unshift(historyItem);
12
+ history = history.slice(0, 10); // 最新の10件のみを保持
13
+ localStorage.setItem('gemini_prompt_history', JSON.stringify(history));
14
+
15
+ updateHistoryList();
16
+ }
17
+ function updateHistoryList() {
18
+ const historyList = document.getElementById('historyList');
19
+ const noHistoryMessage = document.getElementById('noHistoryMessage');
20
+ historyList.innerHTML = '';
21
+
22
+ const history = JSON.parse(localStorage.getItem('gemini_prompt_history') || '[]');
23
+
24
+ if (history.length === 0) {
25
+ noHistoryMessage.classList.remove('d-none');
26
+ historyList.classList.add('d-none');
27
+ } else {
28
+ noHistoryMessage.classList.add('d-none');
29
+ historyList.classList.remove('d-none');
30
+
31
+ history.forEach((item, index) => {
32
+ const li = document.createElement('li');
33
+ li.className = 'list-group-item list-group-item-action d-flex justify-content-between align-items-start';
34
+
35
+ const contentDiv = document.createElement('div');
36
+ contentDiv.className = 'ms-2 me-auto';
37
+ contentDiv.style.width = 'calc(100% - 40px)';
38
+ contentDiv.style.cursor = 'pointer';
39
+ contentDiv.onclick = () => loadHistoryItem(index);
40
+
41
+ const queryDiv = document.createElement('div');
42
+ queryDiv.className = 'text-truncate';
43
+ queryDiv.textContent = item.query;
44
+
45
+ const dateDiv = document.createElement('div');
46
+ dateDiv.className = 'small text-muted';
47
+ dateDiv.textContent = new Date(item.timestamp).toLocaleString();
48
+
49
+ contentDiv.appendChild(queryDiv);
50
+ contentDiv.appendChild(dateDiv);
51
+
52
+ const deleteButton = document.createElement('button');
53
+ deleteButton.className = 'btn btn-danger btn-sm';
54
+ deleteButton.innerHTML = '<i class="fas fa-trash"></i>';
55
+ deleteButton.onclick = (e) => {
56
+ e.stopPropagation();
57
+ deleteHistoryItem(index);
58
+ };
59
+
60
+ li.appendChild(contentDiv);
61
+ li.appendChild(deleteButton);
62
+ historyList.appendChild(li);
63
+ });
64
+ }
65
+ }
66
+ function deleteHistoryItem(index) {
67
+ if (confirm('この履歴項目を削除してもよろしいですか?')) {
68
+ let history = JSON.parse(localStorage.getItem('gemini_prompt_history') || '[]');
69
+ history.splice(index, 1);
70
+ localStorage.setItem('gemini_prompt_history', JSON.stringify(history));
71
+ updateHistoryList();
72
+ }
73
+ }
74
+ function loadHistoryItem(index) {
75
+ const history = JSON.parse(localStorage.getItem('gemini_prompt_history') || '[]');
76
+ const item = history[index];
77
+
78
+ document.getElementById('query').value = item.query;
79
+ document.getElementById('promptEn').value = item.promptEn;
80
+ document.getElementById('promptMyLanguage').value = item.promptMyLanguage;
81
+ document.getElementById('danbooruTags').value = item.danbooruTags;
82
+
83
+ saveToUserStorage(true);
84
+ }
85
+
86
+ function clearHistory() {
87
+ if (confirm('本当に履歴をすべて削除してもよろしいですか?')) {
88
+ localStorage.removeItem('gemini_prompt_history');
89
+ updateHistoryList();
90
+ }
91
+ }
92
+
93
+ function createHistoryItem(item, index) {
94
+ const li = document.createElement('li');
95
+ li.className = 'list-group-item d-flex justify-content-between align-items-center';
96
+ li.textContent = item.query;
97
+
98
+ const buttonsContainer = document.createElement('div');
99
+
100
+ const useButton = document.createElement('button');
101
+ useButton.className = 'btn btn-sm btn-primary me-2';
102
+ useButton.innerHTML = '<i class="fas fa-redo"></i>';
103
+ useButton.onclick = () => useHistoryItem(index);
104
+
105
+ const deleteButton = document.createElement('button');
106
+ deleteButton.className = 'btn btn-sm btn-danger';
107
+ deleteButton.innerHTML = '<i class="fas fa-trash"></i>';
108
+ deleteButton.onclick = () => {
109
+ // 個別の履歴項目削除時にも確認ポップアップを表示
110
+ if (confirm('この履歴項目を削除してもよろしいですか?')) {
111
+ deleteHistoryItem(index);
112
+ }
113
+ };
114
+
115
+ buttonsContainer.appendChild(useButton);
116
+ buttonsContainer.appendChild(deleteButton);
117
+ li.appendChild(buttonsContainer);
118
+
119
+ return li;
120
+ }
index.html CHANGED
@@ -8,16 +8,6 @@
8
  <link href="https://unpkg.com/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous">
9
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/all.min.css"
10
  crossorigin="anonymous">
11
- <script src="https://cdnjs.cloudflare.com/ajax/libs/json5/2.2.3/index.min.js"
12
- integrity="sha512-44jdhc+R2TFfzBflS3/dGNEABiNUxBkkrqwO7GWTvGsj3HkQNr3GESvI9PUvAxmqxSnTosR0Ij9y3+o+6J1hig=="
13
- crossorigin="anonymous" referrerpolicy="no-referrer"></script>
14
- <script src="https://cdnjs.cloudflare.com/ajax/libs/i18next/23.14.0/i18next.min.js"
15
- integrity="sha512-8ANNUVMWPf6aWGXZqDhS4OXJWBCRxfjlW7lKfupuiG1FZah0ST6LiI2qnEb1L5mp05v/+0hn3s2FO4EwIbIgfA=="
16
- crossorigin="anonymous" referrerpolicy="no-referrer"></script>
17
- <script
18
- src="https://cdnjs.cloudflare.com/ajax/libs/i18next-browser-languagedetector/8.0.0/i18nextBrowserLanguageDetector.min.js"
19
- integrity="sha512-8/RTkAM23B3lQzi6fmPs+Yf9qhIHzrzRpeSZsBsQ8OEmo95mbVp+68dB647VDCuyQIBbF+OIbS9b30aTWUkoog=="
20
- crossorigin="anonymous" referrerpolicy="no-referrer"></script>
21
  <style>
22
  #query {
23
  min-height: 40vh;
@@ -27,12 +17,155 @@
27
  #promptMyLanguage {
28
  min-height: 20vh;
29
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
  </style>
31
  </head>
32
 
33
  <body data-bs-theme="dark">
34
- <div class="container">
35
- <div class="row m-0 border-start border-end border-2">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
  <div class="row">
37
  <div id="inputQuery" class="col-md-6 mb-4">
38
  <div class="card">
@@ -73,421 +206,57 @@
73
  <textarea class="form-control" id="promptMyLanguage" disabled
74
  placeholder="日本訳がここに表示されます"></textarea>
75
  </div>
76
- </div>
77
- </div>
78
- </div>
79
- <div class="col-12 mb-4">
80
- <div class="card">
81
- <div class="card-header bg-primary text-white">
82
- <h5 class="mb-0" id="settingsTitle">設定</h5>
83
- </div>
84
- <div class="card-body">
85
- <div class="form-group mb-3">
86
  <label for="danbooruTags" class="form-label" id="danbooruTagsLabel">
87
  danbooru tags
88
  </label>
89
  <input type="text" class="form-control" id="danbooruTags" placeholder="danbooru tags"
90
  readonly>
91
  </div>
92
- <div class="form-group mb-3">
93
- <label for="apiKey" class="form-label" id="apiKeyLabel">
94
- <a href="https://aistudio.google.com/app/apikey?hl=ja" target="_blank">APIキー</a>
95
- </label>
96
- <input type="text" class="form-control" id="apiKey" placeholder="APIキーを入力してください">
97
- </div>
98
- <div class="form-group">
99
- <label for="characterCount" class="form-label" id="characterCountLabel">文字数</label>
100
- <input type="number" value="320" class="form-control" id="characterCount"
101
- placeholder="生成するプロンプトの文字数を入力してください">
102
- </div>
103
- <div class="form-group">
104
- <label for="languageSelect" class="form-label" id="languageSelectLabel">Language</label>
105
- <select class="form-select" id="languageSelect">
106
- <option value="ja">日本語</option>
107
- <option value="en">English</option>
108
- <option value="zh">中文</option>
109
- <option value="ko">한국어</option>
110
- <option value="fr">Français</option>
111
- <option value="es">Español</option>
112
- <option value="de">Deutsch</option>
113
- <option value="it">Italiano</option>
114
- </select>
115
- </div>
116
  </div>
117
  </div>
118
  </div>
119
  </div>
120
  </div>
121
  </div>
122
- <script>
123
- let language;
124
- const translations = {
125
- ja: {
126
- inputQueryTitle: "入力クエリ",
127
- generateButtonText: "プロンプト生成",
128
- splitStrings: "分割送信",
129
- outputPromptTitle: "生成されたプロンプト",
130
- settingsTitle: "設定",
131
- apiKeyLabel: "APIキー",
132
- characterCountLabel: "文字数",
133
- languageSelectLabel: "言語",
134
- promptEnPlaceholder: "英語のプロンプトがここに表示されます",
135
- promptMyLanguagePlaceholder: "日本訳がここに表示されます",
136
- apiKeyPlaceholder: "APIキーを入力してください",
137
- characterCountPlaceholder: "生成するプロンプトの文字数を入力してください"
138
- },
139
- en: {
140
- inputQueryTitle: "Input Query",
141
- generateButtonText: "Generate Prompt",
142
- splitStrings: "Split Strings",
143
- outputPromptTitle: "Generated Prompt",
144
- settingsTitle: "Settings",
145
- apiKeyLabel: "API Key",
146
- characterCountLabel: "Character Count",
147
- languageSelectLabel: "Language",
148
- promptEnPlaceholder: "English prompt will be displayed here",
149
- promptMyLanguagePlaceholder: "Translation will be displayed here",
150
- apiKeyPlaceholder: "Enter your API key",
151
- characterCountPlaceholder: "Enter the number of characters for the generated prompt"
152
- },
153
- zh: {
154
- inputQueryTitle: "输入查询",
155
- generateButtonText: "生成提示",
156
- splitStrings: "分割字符串",
157
- outputPromptTitle: "生成的提示",
158
- settingsTitle: "设置",
159
- apiKeyLabel: "API密钥",
160
- characterCountLabel: "字符数",
161
- languageSelectLabel: "语言",
162
- promptEnPlaceholder: "英文提示将显示在这里",
163
- promptMyLanguagePlaceholder: "翻译将显示在这里",
164
- apiKeyPlaceholder: "请输入您的API密钥",
165
- characterCountPlaceholder: "请输入生成提示的字符数"
166
- },
167
- ko: {
168
- inputQueryTitle: "입력 쿼리",
169
- generateButtonText: "프롬프트 생성",
170
- splitStrings: "문자열 분할",
171
- outputPromptTitle: "생성된 프롬프트",
172
- settingsTitle: "설정",
173
- apiKeyLabel: "API 키",
174
- characterCountLabel: "문자 수",
175
- languageSelectLabel: "언어",
176
- promptEnPlaceholder: "영어 프롬프트가 여기에 표시됩니다",
177
- promptMyLanguagePlaceholder: "번역이 여기에 표시됩니다",
178
- apiKeyPlaceholder: "API 키를 입력하세요",
179
- characterCountPlaceholder: "생성할 프롬프트의 문자 수를 입력하세요"
180
- },
181
- fr: {
182
- inputQueryTitle: "Requête d'entrée",
183
- generateButtonText: "Générer le prompt",
184
- splitStrings: "Diviser les chaînes",
185
- outputPromptTitle: "Prompt généré",
186
- settingsTitle: "Paramètres",
187
- apiKeyLabel: "Clé API",
188
- characterCountLabel: "Nombre de caractères",
189
- languageSelectLabel: "Langue",
190
- promptEnPlaceholder: "Le prompt en anglais s'affichera ici",
191
- promptMyLanguagePlaceholder: "La traduction s'affichera ici",
192
- apiKeyPlaceholder: "Entrez votre clé API",
193
- characterCountPlaceholder: "Entrez le nombre de caractères pour le prompt généré"
194
- },
195
- es: {
196
- inputQueryTitle: "Consulta de entrada",
197
- generateButtonText: "Generar prompt",
198
- splitStrings: "Dividir cadenas",
199
- outputPromptTitle: "Prompt generado",
200
- settingsTitle: "Configuración",
201
- apiKeyLabel: "Clave API",
202
- characterCountLabel: "Recuento de caracteres",
203
- languageSelectLabel: "Idioma",
204
- promptEnPlaceholder: "El prompt en inglés se mostrará aquí",
205
- promptMyLanguagePlaceholder: "La traducción se mostrará aquí",
206
- apiKeyPlaceholder: "Ingrese su clave API",
207
- characterCountPlaceholder: "Ingrese el número de caracteres para el prompt generado"
208
- },
209
- de: {
210
- inputQueryTitle: "Eingabeabfrage",
211
- generateButtonText: "Prompt generieren",
212
- splitStrings: "Zeichenketten aufteilen",
213
- outputPromptTitle: "Generierter Prompt",
214
- settingsTitle: "Einstellungen",
215
- apiKeyLabel: "API-Schlüssel",
216
- characterCountLabel: "Zeichenanzahl",
217
- languageSelectLabel: "Sprache",
218
- promptEnPlaceholder: "Der englische Prompt wird hier angezeigt",
219
- promptMyLanguagePlaceholder: "Die Übersetzung wird hier angezeigt",
220
- apiKeyPlaceholder: "Geben Sie Ihren API-Schlüssel ein",
221
- characterCountPlaceholder: "Geben Sie die Anzahl der Zeichen für den generierten Prompt ein"
222
- },
223
- it: {
224
- inputQueryTitle: "Query di input",
225
- generateButtonText: "Genera prompt",
226
- splitStrings: "Dividi stringhe",
227
- outputPromptTitle: "Prompt generato",
228
- settingsTitle: "Impostazioni",
229
- apiKeyLabel: "Chiave API",
230
- characterCountLabel: "Conteggio caratteri",
231
- languageSelectLabel: "Lingua",
232
- promptEnPlaceholder: "Il prompt in inglese verrà visualizzato qui",
233
- promptMyLanguagePlaceholder: "La traduzione verrà visualizzata qui",
234
- apiKeyPlaceholder: "Inserisci la tua chiave API",
235
- characterCountPlaceholder: "Inserisci il numero di caratteri per il prompt generato"
236
- }
237
- }
238
- const resources = {
239
- ja: {
240
- translation: translations.ja
241
- },
242
- en: {
243
- translation: translations.en
244
- },
245
- zh: {
246
- translation: translations.zh
247
- },
248
- ko: {
249
- translation: translations.ko
250
- },
251
- fr: {
252
- translation: translations.fr
253
- },
254
- es: {
255
- translation: translations.es
256
- },
257
- de: {
258
- translation: translations.de
259
- },
260
- it: {
261
- translation: translations.it
262
- }
263
- }
264
-
265
- // 既存のスクリプトの前に追加
266
- document.addEventListener('DOMContentLoaded', function () {
267
- i18next
268
- .use(i18nextBrowserLanguageDetector)
269
- .init({
270
- fallbackLng: 'ja', // デフォルト言語
271
- resources: resources
272
- })
273
- .then(function (t) {
274
- document.getElementById('languageSelect').value = i18next.language;
275
- document.getElementById('languageSelect').dispatchEvent(new Event('change'));
276
- });
277
- });
278
-
279
- function updateContent() {
280
- // 各要素のテキストを更新
281
- document.getElementById('inputQueryTitle').textContent = i18next.t('inputQueryTitle');
282
- document.getElementById('generateButtonText').textContent = i18next.t('generateButtonText');
283
- document.getElementById('splitStrings').textContent = i18next.t('splitStrings');
284
- document.getElementById('outputPromptTitle').textContent = i18next.t('outputPromptTitle');
285
- document.getElementById('settingsTitle').textContent = i18next.t('settingsTitle');
286
- document.querySelector('#apiKeyLabel > a').textContent = i18next.t('apiKeyLabel');
287
- document.getElementById('characterCountLabel').textContent = i18next.t('characterCountLabel');
288
- document.getElementById('languageSelectLabel').textContent = i18next.t('languageSelectLabel');
289
-
290
- // プレースホルダーを更新
291
- document.getElementById('promptEn').placeholder = i18next.t('promptEnPlaceholder');
292
- document.getElementById('promptMyLanguage').placeholder = i18next.t('promptMyLanguagePlaceholder');
293
- document.getElementById('apiKey').placeholder = i18next.t('apiKeyPlaceholder');
294
- document.getElementById('characterCount').placeholder = i18next.t('characterCountPlaceholder');
295
- }
296
-
297
- // 言語切り替え関数
298
- function changeLang(language) {
299
- i18next.changeLanguage(language, (err, t) => {
300
- if (err) return console.error('言語切り替えエラー', err);
301
- updateContent();
302
- });
303
- }
304
 
305
- document.getElementById('languageSelect').addEventListener('change', function () {
306
- changeLang(this.value);
307
- });
308
- </script>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
309
 
310
  <script>
311
- function generatePrompt() {
312
- if (!document.getElementById('apiKey').value) {
313
- alert("APIキーを入力してください");
314
- return;
315
- }
316
- let query = document.getElementById('query').value;
317
- let textFormat = 'str';
318
 
319
- if (document.getElementById('splitStringsSwitch').checked) {
320
- query = Array.from(document.getElementById('query').value).join(":::");
321
- //textFormat = 'array, # テキストは1character(not word)ずつ格納した配列にして返すこと。 Example: ["I", "t", " ", "i", "s", " ", "a", " ", "p", "e", "n", "."], ["こ", "れ", "は", " ", "ペ", "ン", "で", "す", "。"], ';
322
- }
323
-
324
- let anotherLanguage = "";
325
-
326
- if (!["en", "ja"].includes(i18next.language)) {
327
- anotherLanguage = `,
328
- {
329
- "language": "${i18next.language}",
330
- "text": ${textFormat}
331
- }`;
332
- }
333
-
334
-
335
- const text = `「 ${query} 」をテーマに画像生成AIに送るプロンプトを考えてください。
336
- 背景や小物のディテイール、構図、視覚効果など視覚的な情報のみに言及すること。
337
- その上で長文を日本語と英語で返信してください。
338
- 返答は以下のフォーマットのjson形式でのみ行う。json以外の内容をレスポンスに含めないこと。また、出力されるjsonには改行コード(\\n)や空白は含めないこと。
339
- \`\`\`json
340
- {
341
- "results": [
342
- {
343
- "language": "en",
344
- "text": ${textFormat} # ${document.getElementById('characterCount').value}文字程度,
345
- },
346
- {
347
- "language": "danbooru",
348
- "tags": [str],
349
- },
350
- {
351
- "language": "ja",
352
- "text": ${textFormat}
353
- }${anotherLanguage}
354
- ]
355
- }
356
- \`\`\`
357
- `;
358
- const url = "https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-pro-exp-0827:generateContent?key=" + document.getElementById('apiKey').value;
359
- const payload = {
360
- contents: [
361
- {
362
- parts: [
363
- { text: text }
364
- ]
365
- }
366
- ],
367
- generation_config: {
368
- max_output_tokens: 4095,
369
- temperature: 1,
370
- top_p: 1,
371
- top_k: 32
372
- },
373
- safetySettings: [
374
- {
375
- category: "HARM_CATEGORY_SEXUALLY_EXPLICIT",
376
- threshold: "BLOCK_NONE"
377
- }
378
- ]
379
- };
380
-
381
- console.debug(text);
382
-
383
- // fetchを使用してリクエストを送信
384
- // ローディングアイコンを表示
385
- document.getElementById('loading').classList.remove('d-none');
386
- // generatePromptボタンを無効化
387
- document.getElementById('generatePromptButton').disabled = true;
388
- document.getElementById('generatePromptButton').classList.remove('btn-primary');
389
- document.getElementById('generatePromptButton').classList.remove('btn-danger');
390
- document.getElementById('generatePromptButton').classList.add('btn-secondary');
391
- fetch(url, {
392
- method: 'POST',
393
- headers: {
394
- 'Content-Type': 'application/json'
395
- },
396
- body: JSON.stringify(payload)
397
- })
398
- .then(response => response.json())
399
- .then(data => {
400
- console.debug(data.candidates[0].content);
401
- // レスポンスからテキストを抽出
402
- const responseText = data.candidates[0].content.parts[0].text
403
- console.debug(responseText);
404
-
405
- let jsonString = responseText.replace(/```json|```/g, '').trim();
406
- jsonString = jsonString.replace(/\\n/g, '');
407
- console.debug(jsonString);
408
- // JSONをパース
409
- const parsedData = JSON5.parse(jsonString);
410
-
411
- // 結果を表示
412
- console.debug(parsedData);
413
- let promptEn = parsedData.results.find(x => x.language === 'en').text;
414
- let promptMyLanguage = parsedData.results.find(x => x.language === i18next.language).text;
415
- [promptEn, promptMyLanguage] = [promptEn, promptMyLanguage].map(x => {
416
- return Array.isArray(x) ? x.join("") : x;
417
- });
418
- document.getElementById('promptEn').value = promptEn.replace(/\. ?/g, '.\n\n');
419
- document.getElementById('promptEn').value = document.getElementById('promptEn').value.replace(/^ */g, '');
420
- document.getElementById('promptMyLanguage').value = promptMyLanguage.replace(/\。 ?/g, '。\n\n');
421
-
422
- let danbooruTags = parsedData.results.find(x => x.language === 'danbooru');
423
- if(danbooruTags.tags){
424
- danbooruTags = danbooruTags.tags.map(x => x.replace("_", " "));
425
- document.getElementById('danbooruTags').value = danbooruTags.join(", ");
426
- }
427
- if(danbooruTags.text){
428
- danbooruTags = danbooruTags.text.map(x => x.replace("_", " "));
429
- document.getElementById('danbooruTags').value = danbooruTags.join(", ");
430
- }
431
-
432
-
433
- // ローディングアイコンを非表示
434
- document.getElementById('loading').classList.add('d-none');
435
- document.getElementById('generatePromptButton').disabled = false;
436
- document.getElementById('generatePromptButton').classList.remove('btn-secondary');
437
- document.getElementById('generatePromptButton').classList.add('btn-primary');
438
- saveToUserStorage(true);
439
- })
440
- .catch(error => {
441
- console.error(error);
442
- // エラー時もローディングアイコンを非表示
443
- document.getElementById('loading').classList.add('d-none');
444
- document.getElementById('generatePromptButton').disabled = false;
445
- document.getElementById('generatePromptButton').classList.remove('btn-secondary');
446
- document.getElementById('generatePromptButton').classList.add('btn-danger');
447
- });
448
- };
449
- let lastSaveTimestamp = new Date();
450
- function saveToUserStorage(force = false) {
451
- const currentTime = new Date();
452
- if (!force && currentTime - lastSaveTimestamp < 5000) {
453
- return;
454
- }
455
- const data = {};
456
- document.querySelectorAll('input, textarea').forEach(input => {
457
- data[input.id] = input.value;
458
- });
459
- localStorage.setItem('gemini_prompt', JSON.stringify(data));
460
- lastSaveTimestamp = currentTime;
461
- return true;
462
- }
463
- function loadFromUserStorage() {
464
- const data = JSON.parse(localStorage.getItem('gemini_prompt')) || {};
465
- document.querySelectorAll('input, textarea').forEach(input => {
466
- let v = data[input.id] || "";
467
- if (v) {
468
- if (input.type === "number") {
469
- v = parseInt(v);
470
- }
471
- input.value = v;
472
- }
473
- });
474
- }
475
- loadFromUserStorage();
476
- // 60秒ごとに自動保存を実行
477
- setInterval(() => {
478
- saveToUserStorage();
479
- }, 60000);
480
- document.querySelectorAll('input, textarea').forEach(input => {
481
- input.addEventListener('input', saveToUserStorage);
482
- });
483
- // Ctrl+Enterでプロンプト生成を実行する
484
- document.addEventListener('keydown', function (event) {
485
- if (event.ctrlKey && event.key === 'Enter') {
486
- event.preventDefault(); // デフォルトの動作を防ぐ
487
- generatePrompt(); // プロンプト生成関数を呼び出す
488
- }
489
  });
490
  </script>
 
491
  </body>
492
 
493
  </html>
 
8
  <link href="https://unpkg.com/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous">
9
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/all.min.css"
10
  crossorigin="anonymous">
 
 
 
 
 
 
 
 
 
 
11
  <style>
12
  #query {
13
  min-height: 40vh;
 
17
  #promptMyLanguage {
18
  min-height: 20vh;
19
  }
20
+
21
+ #sidebar {
22
+ position: fixed;
23
+ top: 0;
24
+ left: -250px;
25
+ width: 250px;
26
+ height: 100%;
27
+ background-color: #343a40;
28
+ transition: 0.3s;
29
+ z-index: 1000;
30
+ }
31
+
32
+ #sidebar.active {
33
+ left: 0;
34
+ }
35
+
36
+ #content {
37
+ transition: margin-left 0.3s;
38
+ }
39
+
40
+ #content.active {
41
+ margin-left: 250px;
42
+ }
43
+
44
+ #historyContainer::-webkit-scrollbar {
45
+ width: 5px;
46
+ }
47
+
48
+ #historyContainer::-webkit-scrollbar-track {
49
+ background: #f1f1f1;
50
+ }
51
+
52
+ #historyContainer::-webkit-scrollbar-thumb {
53
+ background: #888;
54
+ }
55
+
56
+ #historyContainer::-webkit-scrollbar-thumb:hover {
57
+ background: #555;
58
+ }
59
+
60
+ .comic-animation {
61
+ width: 100%;
62
+ height: auto;
63
+ max-width: 680px;
64
+ max-height: 680px;
65
+ margin: 0 auto;
66
+ background-size: contain;
67
+ background-repeat: no-repeat;
68
+ background-position: center;
69
+ aspect-ratio: 1 / 1;
70
+ animation: comic-frame-switch 6s step-end infinite;
71
+ }
72
+
73
+ @media (min-width: 768px) {
74
+ .modal-dialog.modal-large {
75
+ max-width: 700px;
76
+ }
77
+ }
78
+
79
+ @media (max-width: 767px) {
80
+ .comic-animation {
81
+ width: 100%;
82
+ height: auto;
83
+ }
84
+ }
85
+
86
+ @keyframes comic-frame-switch {
87
+
88
+ 0%,
89
+ 50% {
90
+ background-image: url("https://i.imgur.com/4A7K3TC.png");
91
+ }
92
+
93
+ 50.01%,
94
+ 100% {
95
+ background-image: url("https://i.imgur.com/LqLqwJi.png");
96
+ }
97
+ }
98
  </style>
99
  </head>
100
 
101
  <body data-bs-theme="dark">
102
+ <div id="sidebar">
103
+ <div class="p-3">
104
+ <h3 id="settingsTitle" class="text-white">設定</h3>
105
+ <div class="form-group mb-3">
106
+ <label for="apiKey" class="form-label" id="apiKeyLabel">
107
+
108
+ <a href="https://aistudio.google.com/app/apikey?hl=ja" target="_blank">APIキー</a>
109
+ </label>
110
+ <input type="text" class="form-control" id="apiKey" placeholder="APIキーを入力してください">
111
+ </div>
112
+ <div class="form-group">
113
+ <label for="characterCount" class="form-label" id="characterCountLabel">文字数</label>
114
+ <input type="number" value="320" class="form-control" id="characterCount"
115
+ placeholder="生成するプロンプトの文字数を入力してください">
116
+ </div>
117
+ <div class="form-group">
118
+ <label for="languageSelect" class="form-label" id="languageSelectLabel">Language</label>
119
+ <select class="form-select" id="languageSelect">
120
+ <option value="ja">日本語</option>
121
+ <option value="en">English</option>
122
+ <option value="zh">中文</option>
123
+ <option value="ko">한국어</option>
124
+ <option value="fr">Français</option>
125
+ <option value="es">Español</option>
126
+ <option value="de">Deutsch</option>
127
+ <option value="it">Italiano</option>
128
+ </select>
129
+ </div>
130
+ <div class="form-group mb-3">
131
+ <label for="endpointSelect" class="form-label" id="endpointSelectLabel">エンドポイント</label>
132
+ <select class="form-select" id="endpointSelect">
133
+ <option value="gemini-1.5-pro-exp-0827">gemini-1.5-pro-exp-0827</option>
134
+ <option value="gemini-1.5-flash-exp-0827">gemini-1.5-flash-exp-0827</option>
135
+ <option value="gemini-1.5-pro-latest">gemini-1.5-pro-latest</option>
136
+ <option value="gemini-1.5-flash-latest">gemini-1.5-flash-latest</option>
137
+
138
+ </select>
139
+ </div>
140
+
141
+ <h3 class="text-white mt-4">履歴</h3>
142
+ <div id="historyContainer" style="max-height: 300px; overflow-y: auto;">
143
+ <ul id="historyList" class="list-group">
144
+ <!-- 履歴項目がここに動的に追加されます -->
145
+ </ul>
146
+ <p id="noHistoryMessage" class="text-white mt-2 d-none">履歴がありません。</p>
147
+ </div>
148
+
149
+ <!-- デバッグ用のモーダル表示ボタンを追加 -->
150
+ <div class="mt-4">
151
+ <button id="showLoadingModalButton" class="btn btn-secondary w-100">
152
+ ローディングモーダルを表示
153
+ </button>
154
+ </div>
155
+ </div>
156
+ </div>
157
+
158
+ <div id="content">
159
+ <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
160
+ <div class="container-fluid">
161
+ <button id="sidebarToggle" class="btn btn-outline-light me-2">
162
+ <i class="fas fa-bars"></i>
163
+ </button>
164
+ <a class="navbar-brand" href="#">Gemini Prompt Generator</a>
165
+ </div>
166
+ </nav>
167
+
168
+ <div class="container mt-3">
169
  <div class="row">
170
  <div id="inputQuery" class="col-md-6 mb-4">
171
  <div class="card">
 
206
  <textarea class="form-control" id="promptMyLanguage" disabled
207
  placeholder="日本訳がここに表示されます"></textarea>
208
  </div>
209
+ <div class="form-group mt-3">
 
 
 
 
 
 
 
 
 
210
  <label for="danbooruTags" class="form-label" id="danbooruTagsLabel">
211
  danbooru tags
212
  </label>
213
  <input type="text" class="form-control" id="danbooruTags" placeholder="danbooru tags"
214
  readonly>
215
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
216
  </div>
217
  </div>
218
  </div>
219
  </div>
220
  </div>
221
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
222
 
223
+ <div class="modal fade" id="loadingModal" tabindex="-1" aria-labelledby="loadingModalLabel" aria-hidden="true">
224
+ <div class="modal-dialog modal-dialog-centered modal-large">
225
+ <div class="modal-content bg-dark text-white">
226
+ <div class="modal-body text-center">
227
+ <h5 id="loadingModalLabel">プロンプト生成中...</h5>
228
+ <div class="comic-animation"></div>
229
+ </div>
230
+ </div>
231
+ </div>
232
+ </div>
233
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/json5/2.2.3/index.min.js"
234
+ integrity="sha512-44jdhc+R2TFfzBflS3/dGNEABiNUxBkkrqwO7GWTvGsj3HkQNr3GESvI9PUvAxmqxSnTosR0Ij9y3+o+6J1hig=="
235
+ crossorigin="anonymous" referrerpolicy="no-referrer"></script>
236
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/i18next/23.14.0/i18next.min.js"
237
+ integrity="sha512-8ANNUVMWPf6aWGXZqDhS4OXJWBCRxfjlW7lKfupuiG1FZah0ST6LiI2qnEb1L5mp05v/+0hn3s2FO4EwIbIgfA=="
238
+ crossorigin="anonymous" referrerpolicy="no-referrer"></script>
239
+ <script
240
+ src="https://cdnjs.cloudflare.com/ajax/libs/i18next-browser-languagedetector/8.0.0/i18nextBrowserLanguageDetector.min.js"
241
+ integrity="sha512-8/RTkAM23B3lQzi6fmPs+Yf9qhIHzrzRpeSZsBsQ8OEmo95mbVp+68dB647VDCuyQIBbF+OIbS9b30aTWUkoog=="
242
+ crossorigin="anonymous" referrerpolicy="no-referrer"></script>
243
+ <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"
244
+ integrity="sha384-HwwvtgBNo3bZJJLYd8oVXjrBZt8cqVSpeBNS5n7C8IVInixGAoxmnlMuBnhbgrkm"
245
+ crossorigin="anonymous"></script>
246
+ <script src="translation.js"></script>
247
+ <script src="prompt.js"></script>
248
+ <script src="storage.js"></script>
249
+ <script src="history.js"></script>
250
+ <script src="control.js"></script>
251
 
252
  <script>
253
+ document.getElementById('showLoadingModalButton').addEventListener('click', function () {
 
 
 
 
 
 
254
 
255
+ var loadingModal = new bootstrap.Modal(document.getElementById('loadingModal'));
256
+ loadingModal.show();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
257
  });
258
  </script>
259
+
260
  </body>
261
 
262
  </html>
prompt.js ADDED
@@ -0,0 +1,156 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ function generatePrompt() {
2
+ if (!document.getElementById('apiKey').value) {
3
+ alert("APIキーを入力してください");
4
+ return;
5
+ }
6
+ let query = document.getElementById('query').value;
7
+ let textFormat = 'str';
8
+
9
+ if (document.getElementById('splitStringsSwitch').checked) {
10
+ query = Array.from(document.getElementById('query').value).join(":::");
11
+ //textFormat = 'array, # テキストは1character(not word)ずつ格納した配列にして返すこと。 Example: ["I", "t", " ", "i", "s", " ", "a", " ", "p", "e", "n", "."], ["こ", "れ", "は", " ", "ペ", "ン", "で", "す", "。"], ';
12
+ }
13
+
14
+ let anotherLanguage = "";
15
+
16
+ if (!["en", "ja"].includes(i18next.language)) {
17
+ anotherLanguage = `,
18
+ {
19
+ "language": "${i18next.language}",
20
+ "text": ${textFormat}
21
+ }`;
22
+ }
23
+
24
+ const selectedEndpoint = document.getElementById('endpointSelect').value;
25
+ const url = `https://generativelanguage.googleapis.com/v1beta/models/${selectedEndpoint}:generateContent?key=` + document.getElementById('apiKey').value;
26
+
27
+ const text = `「 ${query} 」をテーマに画像生成AIに送るプロンプトを考えてください。
28
+ 背景や小物のディテイール、構図、視覚効果など視覚的な情報のみに言及すること。
29
+ その上で長文を日本語と英語で返信してください。
30
+ 返答は以下のフォーマットのjson形式でのみ行う。json以外の内容をレスポンスに含めないこと。
31
+ \`\`\`json
32
+ {
33
+ "results": [
34
+ {
35
+ "language": "en",
36
+ "text": ${textFormat} # ${document.getElementById('characterCount').value}文字程度,
37
+ },
38
+ {
39
+ "language": "danbooru",
40
+ "tags": [str],
41
+ },
42
+ {
43
+ "language": "ja",
44
+ "text": ${textFormat}
45
+ }${anotherLanguage}
46
+ ]
47
+ }
48
+ \`\`\`
49
+ `;
50
+ const payload = {
51
+ contents: [
52
+ {
53
+ parts: [
54
+ { text: text }
55
+ ]
56
+ }
57
+ ],
58
+ generation_config: {
59
+ max_output_tokens: 4095,
60
+ temperature: 1,
61
+ top_p: 1,
62
+ top_k: 32
63
+ },
64
+ safetySettings: [
65
+ {
66
+ category: "HARM_CATEGORY_SEXUALLY_EXPLICIT",
67
+ threshold: "BLOCK_NONE"
68
+ }
69
+ ]
70
+ };
71
+
72
+ console.debug(text);
73
+
74
+ // モーダルを表示
75
+ const loadingModal = new bootstrap.Modal(document.getElementById('loadingModal'));
76
+ loadingModal.show();
77
+
78
+ // ローディングアイコンを表示
79
+ document.getElementById('loading').classList.remove('d-none');
80
+ // generatePromptボタンを無効化
81
+ document.getElementById('generatePromptButton').disabled = true;
82
+ document.getElementById('generatePromptButton').classList.remove('btn-primary');
83
+ document.getElementById('generatePromptButton').classList.remove('btn-danger');
84
+ document.getElementById('generatePromptButton').classList.add('btn-secondary');
85
+ fetch(url, {
86
+ method: 'POST',
87
+ headers: {
88
+ 'Content-Type': 'application/json'
89
+ },
90
+ body: JSON.stringify(payload)
91
+ })
92
+ .then(response => response.json())
93
+ .then(data => {
94
+ console.debug(data.candidates[0].content);
95
+ // レスポンスからテキストを抽出
96
+ const responseText = data.candidates[0].content.parts[0].text
97
+ console.debug(responseText);
98
+
99
+ let jsonString = responseText.replace(/```json|```/g, '').trim();
100
+ jsonString = jsonString.replace(/\n/g, '');
101
+ console.debug(jsonString);
102
+ // JSONをパース
103
+ const parsedData = JSON5.parse(jsonString);
104
+
105
+ // 結果を表示
106
+ console.debug(parsedData);
107
+ let promptEn = parsedData.results.find(x => x.language === 'en').text;
108
+ let promptMyLanguage = parsedData.results.find(x => x.language === i18next.language).text;
109
+ [promptEn, promptMyLanguage] = [promptEn, promptMyLanguage].map(x => {
110
+ return Array.isArray(x) ? x.join("") : x;
111
+ });
112
+ document.getElementById('promptEn').value = promptEn.replace(/\. ?/g, '.\n\n');
113
+ document.getElementById('promptEn').value = document.getElementById('promptEn').value.replace(/^ */g, '');
114
+ document.getElementById('promptMyLanguage').value = promptMyLanguage.replace(/\。 ?/g, '。\n\n');
115
+
116
+ let danbooruTags = parsedData.results.find(x => x.language === 'danbooru');
117
+ if (danbooruTags.tags) {
118
+ danbooruTags = danbooruTags.tags.map(x => x.replace("_", " "));
119
+ document.getElementById('danbooruTags').value = danbooruTags.join(", ");
120
+ }
121
+ if (danbooruTags.text) {
122
+ danbooruTags = danbooruTags.text.map(x => x.replace("_", " "));
123
+ document.getElementById('danbooruTags').value = danbooruTags.join(", ");
124
+ }
125
+
126
+
127
+ // ローディングアイコンを非表示
128
+ document.getElementById('loading').classList.add('d-none');
129
+ document.getElementById('generatePromptButton').disabled = false;
130
+ document.getElementById('generatePromptButton').classList.remove('btn-secondary');
131
+ document.getElementById('generatePromptButton').classList.add('btn-primary');
132
+ saveToUserStorage(true);
133
+ saveToHistory(); // 履歴に保存
134
+ })
135
+ .catch(error => {
136
+ console.error(error);
137
+ // エラー時の処理
138
+ document.getElementById('generatePromptButton').classList.remove('btn-secondary');
139
+ document.getElementById('generatePromptButton').classList.add('btn-danger');
140
+ })
141
+ .finally(() => {
142
+ // ローディングアイコンを非表示
143
+ document.getElementById('loading').classList.add('d-none');
144
+ document.getElementById('generatePromptButton').disabled = false;
145
+ document.getElementById('generatePromptButton').classList.remove('btn-secondary');
146
+ if (!document.getElementById('generatePromptButton').classList.contains('btn-danger')) {
147
+ document.getElementById('generatePromptButton').classList.add('btn-primary');
148
+ }
149
+
150
+ // モーダルを非表示
151
+ setTimeout(() => {
152
+ loadingModal.hide();
153
+ }, 500);
154
+ });
155
+ };
156
+
storage.js ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ function saveToUserStorage(force = false) {
2
+ const currentTime = new Date().getTime();
3
+ const lastSaveTimestamp = parseInt(localStorage.getItem('lastSaveTimestamp') || '0');
4
+ if (!force && currentTime - lastSaveTimestamp < 5000) {
5
+ return;
6
+ }
7
+ const data = {};
8
+ document.querySelectorAll('input, textarea, select').forEach(input => {
9
+ data[input.id] = input.value;
10
+ });
11
+ localStorage.setItem('gemini_prompt', JSON.stringify(data));
12
+ localStorage.setItem('lastSaveTimestamp', currentTime.toString());
13
+ return true;
14
+ }
15
+ function loadFromUserStorage() {
16
+ const data = JSON.parse(localStorage.getItem('gemini_prompt')) || {};
17
+ document.querySelectorAll('input, textarea, select').forEach(input => {
18
+ let v = data[input.id] || "";
19
+ if (v) {
20
+ if (input.type === "number") {
21
+ v = parseInt(v);
22
+ }
23
+ input.value = v;
24
+ }
25
+ });
26
+ // エンドポイントが保存されていない場合、デフォルト値を設定
27
+ if (!data.endpointSelect) {
28
+ document.getElementById('endpointSelect').value = "gemini-1.5-pro-exp-0827";
29
+ }
30
+ }
translation.js ADDED
@@ -0,0 +1,184 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const translations = {
2
+ ja: {
3
+ inputQueryTitle: "入力クエリ",
4
+ generateButtonText: "プロンプト生成",
5
+ splitStrings: "分割送信",
6
+ outputPromptTitle: "生成されたプロンプト",
7
+ settingsTitle: "設定",
8
+ apiKeyLabel: "APIキー",
9
+ characterCountLabel: "文字数",
10
+ languageSelectLabel: "言語",
11
+ promptEnPlaceholder: "英語のプロンプトがここに表示されます",
12
+ promptMyLanguagePlaceholder: "日本訳がここに表示されます",
13
+ apiKeyPlaceholder: "APIキーを入力してください",
14
+ characterCountPlaceholder: "生成するプロンプトの文字数を入力してください"
15
+ },
16
+ en: {
17
+ inputQueryTitle: "Input Query",
18
+ generateButtonText: "Generate Prompt",
19
+ splitStrings: "Split Strings",
20
+ outputPromptTitle: "Generated Prompt",
21
+ settingsTitle: "Settings",
22
+ apiKeyLabel: "API Key",
23
+ characterCountLabel: "Character Count",
24
+ languageSelectLabel: "Language",
25
+ promptEnPlaceholder: "English prompt will be displayed here",
26
+ promptMyLanguagePlaceholder: "Translation will be displayed here",
27
+ apiKeyPlaceholder: "Enter your API key",
28
+ characterCountPlaceholder: "Enter the number of characters for the generated prompt"
29
+ },
30
+ zh: {
31
+ inputQueryTitle: "输入查询",
32
+ generateButtonText: "生成提示",
33
+ splitStrings: "分割字符串",
34
+ outputPromptTitle: "生成的提示",
35
+ settingsTitle: "设置",
36
+ apiKeyLabel: "API密钥",
37
+ characterCountLabel: "字符数",
38
+ languageSelectLabel: "语言",
39
+ promptEnPlaceholder: "英文提示将显示在这里",
40
+ promptMyLanguagePlaceholder: "翻译将显示在这里",
41
+ apiKeyPlaceholder: "请输入您的API密钥",
42
+ characterCountPlaceholder: "请输入生成提示的字符数"
43
+ },
44
+ ko: {
45
+ inputQueryTitle: "입력 쿼리",
46
+ generateButtonText: "프롬프트 생성",
47
+ splitStrings: "문자열 분할",
48
+ outputPromptTitle: "생성된 프롬프트",
49
+ settingsTitle: "설정",
50
+ apiKeyLabel: "API 키",
51
+ characterCountLabel: "문자 수",
52
+ languageSelectLabel: "언어",
53
+ promptEnPlaceholder: "영어 프롬프트가 여기에 표시됩니다",
54
+ promptMyLanguagePlaceholder: "번역이 여기에 표시됩니다",
55
+ apiKeyPlaceholder: "API 키를 입력하세요",
56
+ characterCountPlaceholder: "생성할 프롬프트의 문자 수를 입력하세요"
57
+ },
58
+ fr: {
59
+ inputQueryTitle: "Requête d'entrée",
60
+ generateButtonText: "Générer le prompt",
61
+ splitStrings: "Diviser les chaînes",
62
+ outputPromptTitle: "Prompt généré",
63
+ settingsTitle: "Paramètres",
64
+ apiKeyLabel: "Clé API",
65
+ characterCountLabel: "Nombre de caractères",
66
+ languageSelectLabel: "Langue",
67
+ promptEnPlaceholder: "Le prompt en anglais s'affichera ici",
68
+ promptMyLanguagePlaceholder: "La traduction s'affichera ici",
69
+ apiKeyPlaceholder: "Entrez votre clé API",
70
+ characterCountPlaceholder: "Entrez le nombre de caractères pour le prompt généré"
71
+ },
72
+ es: {
73
+ inputQueryTitle: "Consulta de entrada",
74
+ generateButtonText: "Generar prompt",
75
+ splitStrings: "Dividir cadenas",
76
+ outputPromptTitle: "Prompt generado",
77
+ settingsTitle: "Configuración",
78
+ apiKeyLabel: "Clave API",
79
+ characterCountLabel: "Recuento de caracteres",
80
+ languageSelectLabel: "Idioma",
81
+ promptEnPlaceholder: "El prompt en inglés se mostrará aquí",
82
+ promptMyLanguagePlaceholder: "La traducción se mostrará aquí",
83
+ apiKeyPlaceholder: "Ingrese su clave API",
84
+ characterCountPlaceholder: "Ingrese el número de caracteres para el prompt generado"
85
+ },
86
+ de: {
87
+ inputQueryTitle: "Eingabeabfrage",
88
+ generateButtonText: "Prompt generieren",
89
+ splitStrings: "Zeichenketten aufteilen",
90
+ outputPromptTitle: "Generierter Prompt",
91
+ settingsTitle: "Einstellungen",
92
+ apiKeyLabel: "API-Schlüssel",
93
+ characterCountLabel: "Zeichenanzahl",
94
+ languageSelectLabel: "Sprache",
95
+ promptEnPlaceholder: "Der englische Prompt wird hier angezeigt",
96
+ promptMyLanguagePlaceholder: "Die Übersetzung wird hier angezeigt",
97
+ apiKeyPlaceholder: "Geben Sie Ihren API-Schlüssel ein",
98
+ characterCountPlaceholder: "Geben Sie die Anzahl der Zeichen für den generierten Prompt ein"
99
+ },
100
+ it: {
101
+ inputQueryTitle: "Query di input",
102
+ generateButtonText: "Genera prompt",
103
+ splitStrings: "Dividi stringhe",
104
+ outputPromptTitle: "Prompt generato",
105
+ settingsTitle: "Impostazioni",
106
+ apiKeyLabel: "Chiave API",
107
+ characterCountLabel: "Conteggio caratteri",
108
+ languageSelectLabel: "Lingua",
109
+ promptEnPlaceholder: "Il prompt in inglese verrà visualizzato qui",
110
+ promptMyLanguagePlaceholder: "La traduzione verrà visualizzata qui",
111
+ apiKeyPlaceholder: "Inserisci la tua chiave API",
112
+ characterCountPlaceholder: "Inserisci il numero di caratteri per il prompt generato"
113
+ }
114
+ }
115
+ const resources = {
116
+ ja: {
117
+ translation: translations.ja
118
+ },
119
+ en: {
120
+ translation: translations.en
121
+ },
122
+ zh: {
123
+ translation: translations.zh
124
+ },
125
+ ko: {
126
+ translation: translations.ko
127
+ },
128
+ fr: {
129
+ translation: translations.fr
130
+ },
131
+ es: {
132
+ translation: translations.es
133
+ },
134
+ de: {
135
+ translation: translations.de
136
+ },
137
+ it: {
138
+ translation: translations.it
139
+ }
140
+ }
141
+
142
+ // 既存のスクリプトの前に追加
143
+ document.addEventListener('DOMContentLoaded', function () {
144
+ i18next
145
+ .use(i18nextBrowserLanguageDetector)
146
+ .init({
147
+ fallbackLng: 'ja', // デフォルト言語
148
+ resources: resources
149
+ })
150
+ .then(function (t) {
151
+ document.getElementById('languageSelect').value = i18next.language;
152
+ document.getElementById('languageSelect').dispatchEvent(new Event('change'));
153
+ });
154
+ });
155
+
156
+ function updateContent() {
157
+ // 各要素のテキストを更新
158
+ document.getElementById('inputQueryTitle').textContent = i18next.t('inputQueryTitle');
159
+ document.getElementById('generateButtonText').textContent = i18next.t('generateButtonText');
160
+ document.getElementById('splitStrings').textContent = i18next.t('splitStrings');
161
+ document.getElementById('outputPromptTitle').textContent = i18next.t('outputPromptTitle');
162
+ document.getElementById('settingsTitle').textContent = i18next.t('settingsTitle');
163
+ document.querySelector('#apiKeyLabel > a').textContent = i18next.t('apiKeyLabel');
164
+ document.getElementById('characterCountLabel').textContent = i18next.t('characterCountLabel');
165
+ document.getElementById('languageSelectLabel').textContent = i18next.t('languageSelectLabel');
166
+
167
+ // プレースホルダーを更新
168
+ document.getElementById('promptEn').placeholder = i18next.t('promptEnPlaceholder');
169
+ document.getElementById('promptMyLanguage').placeholder = i18next.t('promptMyLanguagePlaceholder');
170
+ document.getElementById('apiKey').placeholder = i18next.t('apiKeyPlaceholder');
171
+ document.getElementById('characterCount').placeholder = i18next.t('characterCountPlaceholder');
172
+ }
173
+
174
+ // 言語切り替え関数
175
+ function changeLang(language) {
176
+ i18next.changeLanguage(language, (err, t) => {
177
+ if (err) return console.error('言語切り替えエラー', err);
178
+ updateContent();
179
+ });
180
+ }
181
+
182
+ document.getElementById('languageSelect').addEventListener('change', function () {
183
+ changeLang(this.value);
184
+ });