Spaces:
Running
Running
Upload 6 files
Browse files- control.js +22 -0
- history.js +120 -0
- index.html +178 -409
- prompt.js +156 -0
- storage.js +30 -0
- 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/bootstrap@5.3.1/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
|
35 |
-
<div class="
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
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 |
-
|
306 |
-
|
307 |
-
|
308 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
309 |
|
310 |
<script>
|
311 |
-
function
|
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 |
-
|
320 |
-
|
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/bootstrap@5.3.1/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/bootstrap@5.3.1/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 |
+
});
|