Spaces:
Running
Running
<html lang="ja"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>Gemini Prompt Generator</title> | |
<link href="https://unpkg.com/bootstrap@5.3.1/dist/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous"> | |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/all.min.css" | |
crossorigin="anonymous"> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/json5/2.2.3/index.min.js" | |
integrity="sha512-44jdhc+R2TFfzBflS3/dGNEABiNUxBkkrqwO7GWTvGsj3HkQNr3GESvI9PUvAxmqxSnTosR0Ij9y3+o+6J1hig==" | |
crossorigin="anonymous" referrerpolicy="no-referrer"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/i18next/23.14.0/i18next.min.js" | |
integrity="sha512-8ANNUVMWPf6aWGXZqDhS4OXJWBCRxfjlW7lKfupuiG1FZah0ST6LiI2qnEb1L5mp05v/+0hn3s2FO4EwIbIgfA==" | |
crossorigin="anonymous" referrerpolicy="no-referrer"></script> | |
<script | |
src="https://cdnjs.cloudflare.com/ajax/libs/i18next-browser-languagedetector/8.0.0/i18nextBrowserLanguageDetector.min.js" | |
integrity="sha512-8/RTkAM23B3lQzi6fmPs+Yf9qhIHzrzRpeSZsBsQ8OEmo95mbVp+68dB647VDCuyQIBbF+OIbS9b30aTWUkoog==" | |
crossorigin="anonymous" referrerpolicy="no-referrer"></script> | |
<style> | |
#query { | |
min-height: 40vh; | |
} | |
#promptEn, | |
#promptMyLanguage { | |
min-height: 20vh; | |
} | |
</style> | |
</head> | |
<body data-bs-theme="dark"> | |
<div class="container"> | |
<div class="row m-0 border-start border-end border-2"> | |
<div class="row"> | |
<div id="inputQuery" class="col-md-6 mb-4"> | |
<div class="card"> | |
<div class="card-header bg-primary text-white"> | |
<h5 class="mb-0" id="inputQueryTitle">入力クエリ</h5> | |
</div> | |
<div class="card-body"> | |
<div class="form-group"> | |
<textarea class="form-control" id="query" placeholder="ここにクエリを入力してください"></textarea> | |
</div> | |
</div> | |
<div class="card-footer d-flex align-items-center"> | |
<button id="generatePromptButton" class="btn btn-primary flex-grow-1 me-2" | |
onclick="generatePrompt()"> | |
<i class="fas fa-magic me-2"></i><span id="generateButtonText">プロンプト生成</span> | |
<i class="fas fa-spinner fa-spin me-2 d-none" id="loading"></i> | |
</button> | |
<div class="form-check form-switch" id="splitStringsSwitchWrapper"> | |
<input class="form-check-input" type="checkbox" id="splitStringsSwitch"> | |
<label class="form-check-label" for="splitStringsSwitch"> | |
<i class="fas fa-shield-alt" id="splitStrings">分割送信</i> | |
</label> | |
</div> | |
</div> | |
</div> | |
</div> | |
<div id="outputPrompt" class="col-md-6 mb-4"> | |
<div class="card"> | |
<div class="card-header bg-success text-white"> | |
<h5 class="mb-0" id="outputPromptTitle">生成されたプロンプト</h5> | |
</div> | |
<div class="card-body"> | |
<div class="form-group"> | |
<textarea class="form-control" id="promptEn" | |
placeholder="英語のプロンプトがここに表示されます"></textarea> | |
</div> | |
<div class="form-group mt-3"> | |
<textarea class="form-control" id="promptMyLanguage" disabled | |
placeholder="日本訳がここに表示されます"></textarea> | |
</div> | |
</div> | |
</div> | |
</div> | |
<div class="col-12 mb-4"> | |
<div class="card"> | |
<div class="card-header bg-primary text-white"> | |
<h5 class="mb-0" id="settingsTitle">設定</h5> | |
</div> | |
<div class="card-body"> | |
<div class="form-group mb-3"> | |
<label for="danbooruTags" class="form-label" id="danbooruTagsLabel"> | |
danbooru tags | |
</label> | |
<input type="text" class="form-control" id="danbooruTags" placeholder="danbooru tags" | |
readonly> | |
</div> | |
<div class="form-group mb-3"> | |
<label for="apiKey" class="form-label" id="apiKeyLabel"> | |
<a href="https://aistudio.google.com/app/apikey?hl=ja" target="_blank">APIキー</a> | |
</label> | |
<input type="text" class="form-control" id="apiKey" placeholder="APIキーを入力してください"> | |
</div> | |
<div class="form-group"> | |
<label for="characterCount" class="form-label" id="characterCountLabel">文字数</label> | |
<input type="number" value="320" class="form-control" id="characterCount" | |
placeholder="生成するプロンプトの文字数を入力してください"> | |
</div> | |
<div class="form-group"> | |
<label for="languageSelect" class="form-label" id="languageSelectLabel">Language</label> | |
<select class="form-select" id="languageSelect"> | |
<option value="ja">日本語</option> | |
<option value="en">English</option> | |
<option value="zh">中文</option> | |
<option value="ko">한국어</option> | |
<option value="fr">Français</option> | |
<option value="es">Español</option> | |
<option value="de">Deutsch</option> | |
<option value="it">Italiano</option> | |
</select> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
<script> | |
let language; | |
const translations = { | |
ja: { | |
inputQueryTitle: "入力クエリ", | |
generateButtonText: "プロンプト生成", | |
splitStrings: "分割送信", | |
outputPromptTitle: "生成されたプロンプト", | |
settingsTitle: "設定", | |
apiKeyLabel: "APIキー", | |
characterCountLabel: "文字数", | |
languageSelectLabel: "言語", | |
promptEnPlaceholder: "英語のプロンプトがここに表示されます", | |
promptMyLanguagePlaceholder: "日本訳がここに表示されます", | |
apiKeyPlaceholder: "APIキーを入力してください", | |
characterCountPlaceholder: "生成するプロンプトの文字数を入力してください" | |
}, | |
en: { | |
inputQueryTitle: "Input Query", | |
generateButtonText: "Generate Prompt", | |
splitStrings: "Split Strings", | |
outputPromptTitle: "Generated Prompt", | |
settingsTitle: "Settings", | |
apiKeyLabel: "API Key", | |
characterCountLabel: "Character Count", | |
languageSelectLabel: "Language", | |
promptEnPlaceholder: "English prompt will be displayed here", | |
promptMyLanguagePlaceholder: "Translation will be displayed here", | |
apiKeyPlaceholder: "Enter your API key", | |
characterCountPlaceholder: "Enter the number of characters for the generated prompt" | |
}, | |
zh: { | |
inputQueryTitle: "输入查询", | |
generateButtonText: "生成提示", | |
splitStrings: "分割字符串", | |
outputPromptTitle: "生成的提示", | |
settingsTitle: "设置", | |
apiKeyLabel: "API密钥", | |
characterCountLabel: "字符数", | |
languageSelectLabel: "语言", | |
promptEnPlaceholder: "英文提示将显示在这里", | |
promptMyLanguagePlaceholder: "翻译将显示在这里", | |
apiKeyPlaceholder: "请输入您的API密钥", | |
characterCountPlaceholder: "请输入生成提示的字符数" | |
}, | |
ko: { | |
inputQueryTitle: "입력 쿼리", | |
generateButtonText: "프롬프트 생성", | |
splitStrings: "문자열 분할", | |
outputPromptTitle: "생성된 프롬프트", | |
settingsTitle: "설정", | |
apiKeyLabel: "API 키", | |
characterCountLabel: "문자 수", | |
languageSelectLabel: "언어", | |
promptEnPlaceholder: "영어 프롬프트가 여기에 표시됩니다", | |
promptMyLanguagePlaceholder: "번역이 여기에 표시됩니다", | |
apiKeyPlaceholder: "API 키를 입력하세요", | |
characterCountPlaceholder: "생성할 프롬프트의 문자 수를 입력하세요" | |
}, | |
fr: { | |
inputQueryTitle: "Requête d'entrée", | |
generateButtonText: "Générer le prompt", | |
splitStrings: "Diviser les chaînes", | |
outputPromptTitle: "Prompt généré", | |
settingsTitle: "Paramètres", | |
apiKeyLabel: "Clé API", | |
characterCountLabel: "Nombre de caractères", | |
languageSelectLabel: "Langue", | |
promptEnPlaceholder: "Le prompt en anglais s'affichera ici", | |
promptMyLanguagePlaceholder: "La traduction s'affichera ici", | |
apiKeyPlaceholder: "Entrez votre clé API", | |
characterCountPlaceholder: "Entrez le nombre de caractères pour le prompt généré" | |
}, | |
es: { | |
inputQueryTitle: "Consulta de entrada", | |
generateButtonText: "Generar prompt", | |
splitStrings: "Dividir cadenas", | |
outputPromptTitle: "Prompt generado", | |
settingsTitle: "Configuración", | |
apiKeyLabel: "Clave API", | |
characterCountLabel: "Recuento de caracteres", | |
languageSelectLabel: "Idioma", | |
promptEnPlaceholder: "El prompt en inglés se mostrará aquí", | |
promptMyLanguagePlaceholder: "La traducción se mostrará aquí", | |
apiKeyPlaceholder: "Ingrese su clave API", | |
characterCountPlaceholder: "Ingrese el número de caracteres para el prompt generado" | |
}, | |
de: { | |
inputQueryTitle: "Eingabeabfrage", | |
generateButtonText: "Prompt generieren", | |
splitStrings: "Zeichenketten aufteilen", | |
outputPromptTitle: "Generierter Prompt", | |
settingsTitle: "Einstellungen", | |
apiKeyLabel: "API-Schlüssel", | |
characterCountLabel: "Zeichenanzahl", | |
languageSelectLabel: "Sprache", | |
promptEnPlaceholder: "Der englische Prompt wird hier angezeigt", | |
promptMyLanguagePlaceholder: "Die Übersetzung wird hier angezeigt", | |
apiKeyPlaceholder: "Geben Sie Ihren API-Schlüssel ein", | |
characterCountPlaceholder: "Geben Sie die Anzahl der Zeichen für den generierten Prompt ein" | |
}, | |
it: { | |
inputQueryTitle: "Query di input", | |
generateButtonText: "Genera prompt", | |
splitStrings: "Dividi stringhe", | |
outputPromptTitle: "Prompt generato", | |
settingsTitle: "Impostazioni", | |
apiKeyLabel: "Chiave API", | |
characterCountLabel: "Conteggio caratteri", | |
languageSelectLabel: "Lingua", | |
promptEnPlaceholder: "Il prompt in inglese verrà visualizzato qui", | |
promptMyLanguagePlaceholder: "La traduzione verrà visualizzata qui", | |
apiKeyPlaceholder: "Inserisci la tua chiave API", | |
characterCountPlaceholder: "Inserisci il numero di caratteri per il prompt generato" | |
} | |
} | |
const resources = { | |
ja: { | |
translation: translations.ja | |
}, | |
en: { | |
translation: translations.en | |
}, | |
zh: { | |
translation: translations.zh | |
}, | |
ko: { | |
translation: translations.ko | |
}, | |
fr: { | |
translation: translations.fr | |
}, | |
es: { | |
translation: translations.es | |
}, | |
de: { | |
translation: translations.de | |
}, | |
it: { | |
translation: translations.it | |
} | |
} | |
// 既存のスクリプトの前に追加 | |
document.addEventListener('DOMContentLoaded', function () { | |
i18next | |
.use(i18nextBrowserLanguageDetector) | |
.init({ | |
fallbackLng: 'ja', // デフォルト言語 | |
resources: resources | |
}) | |
.then(function (t) { | |
document.getElementById('languageSelect').value = i18next.language; | |
document.getElementById('languageSelect').dispatchEvent(new Event('change')); | |
}); | |
}); | |
function updateContent() { | |
// 各要素のテキストを更新 | |
document.getElementById('inputQueryTitle').textContent = i18next.t('inputQueryTitle'); | |
document.getElementById('generateButtonText').textContent = i18next.t('generateButtonText'); | |
document.getElementById('splitStrings').textContent = i18next.t('splitStrings'); | |
document.getElementById('outputPromptTitle').textContent = i18next.t('outputPromptTitle'); | |
document.getElementById('settingsTitle').textContent = i18next.t('settingsTitle'); | |
document.querySelector('#apiKeyLabel > a').textContent = i18next.t('apiKeyLabel'); | |
document.getElementById('characterCountLabel').textContent = i18next.t('characterCountLabel'); | |
document.getElementById('languageSelectLabel').textContent = i18next.t('languageSelectLabel'); | |
// プレースホルダーを更新 | |
document.getElementById('promptEn').placeholder = i18next.t('promptEnPlaceholder'); | |
document.getElementById('promptMyLanguage').placeholder = i18next.t('promptMyLanguagePlaceholder'); | |
document.getElementById('apiKey').placeholder = i18next.t('apiKeyPlaceholder'); | |
document.getElementById('characterCount').placeholder = i18next.t('characterCountPlaceholder'); | |
} | |
// 言語切り替え関数 | |
function changeLang(language) { | |
i18next.changeLanguage(language, (err, t) => { | |
if (err) return console.error('言語切り替えエラー', err); | |
updateContent(); | |
}); | |
} | |
document.getElementById('languageSelect').addEventListener('change', function () { | |
changeLang(this.value); | |
}); | |
</script> | |
<script> | |
function generatePrompt() { | |
if (!document.getElementById('apiKey').value) { | |
alert("APIキーを入力してください"); | |
return; | |
} | |
let query = document.getElementById('query').value; | |
let textFormat = 'str'; | |
if (document.getElementById('splitStringsSwitch').checked) { | |
query = Array.from(document.getElementById('query').value).join(":::"); | |
//textFormat = 'array, # テキストは1character(not word)ずつ格納した配列にして返すこと。 Example: ["I", "t", " ", "i", "s", " ", "a", " ", "p", "e", "n", "."], ["こ", "れ", "は", " ", "ペ", "ン", "で", "す", "。"], '; | |
} | |
let anotherLanguage = ""; | |
if (!["en", "ja"].includes(i18next.language)) { | |
anotherLanguage = `, | |
{ | |
"language": "${i18next.language}", | |
"text": ${textFormat} | |
}`; | |
} | |
const text = `「 ${query} 」をテーマに画像生成AIに送るプロンプトを考えてください。 | |
背景や小物のディテイール、構図、視覚効果など視覚的な情報のみに言及すること。 | |
その上で長文を日本語と英語で返信してください。 | |
返答は以下のフォーマットのjson形式でのみ行う。json以外の内容をレスポンスに含めないこと。また、出力されるjsonには改行コード(\\n)や空白は含めないこと。 | |
\`\`\`json | |
{ | |
"results": [ | |
{ | |
"language": "en", | |
"text": ${textFormat} # ${document.getElementById('characterCount').value}文字程度, | |
}, | |
{ | |
"language": "danbooru", | |
"tags": [str], | |
}, | |
{ | |
"language": "ja", | |
"text": ${textFormat} | |
}${anotherLanguage} | |
] | |
} | |
\`\`\` | |
`; | |
const url = "https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-pro-exp-0827:generateContent?key=" + document.getElementById('apiKey').value; | |
const payload = { | |
contents: [ | |
{ | |
parts: [ | |
{ text: text } | |
] | |
} | |
], | |
generation_config: { | |
max_output_tokens: 4095, | |
temperature: 1, | |
top_p: 1, | |
top_k: 32 | |
}, | |
safetySettings: [ | |
{ | |
category: "HARM_CATEGORY_SEXUALLY_EXPLICIT", | |
threshold: "BLOCK_NONE" | |
} | |
] | |
}; | |
console.debug(text); | |
// fetchを使用してリクエストを送信 | |
// ローディングアイコンを表示 | |
document.getElementById('loading').classList.remove('d-none'); | |
// generatePromptボタンを無効化 | |
document.getElementById('generatePromptButton').disabled = true; | |
document.getElementById('generatePromptButton').classList.remove('btn-primary'); | |
document.getElementById('generatePromptButton').classList.remove('btn-danger'); | |
document.getElementById('generatePromptButton').classList.add('btn-secondary'); | |
fetch(url, { | |
method: 'POST', | |
headers: { | |
'Content-Type': 'application/json' | |
}, | |
body: JSON.stringify(payload) | |
}) | |
.then(response => response.json()) | |
.then(data => { | |
console.debug(data.candidates[0].content); | |
// レスポンスからテキストを抽出 | |
const responseText = data.candidates[0].content.parts[0].text | |
console.debug(responseText); | |
let jsonString = responseText.replace(/```json|```/g, '').trim(); | |
jsonString = jsonString.replace(/\\n/g, ''); | |
console.debug(jsonString); | |
// JSONをパース | |
const parsedData = JSON5.parse(jsonString); | |
// 結果を表示 | |
console.debug(parsedData); | |
let promptEn = parsedData.results.find(x => x.language === 'en').text; | |
let promptMyLanguage = parsedData.results.find(x => x.language === i18next.language).text; | |
[promptEn, promptMyLanguage] = [promptEn, promptMyLanguage].map(x => { | |
return Array.isArray(x) ? x.join("") : x; | |
}); | |
document.getElementById('promptEn').value = promptEn.replace(/\. ?/g, '.\n\n'); | |
document.getElementById('promptEn').value = document.getElementById('promptEn').value.replace(/^ */g, ''); | |
document.getElementById('promptMyLanguage').value = promptMyLanguage.replace(/\。 ?/g, '。\n\n'); | |
let danbooruTags = parsedData.results.find(x => x.language === 'danbooru'); | |
if(danbooruTags.tags){ | |
danbooruTags = danbooruTags.tags.map(x => x.replace("_", " ")); | |
document.getElementById('danbooruTags').value = danbooruTags.join(", "); | |
} | |
if(danbooruTags.text){ | |
danbooruTags = danbooruTags.text.map(x => x.replace("_", " ")); | |
document.getElementById('danbooruTags').value = danbooruTags.join(", "); | |
} | |
// ローディングアイコンを非表示 | |
document.getElementById('loading').classList.add('d-none'); | |
document.getElementById('generatePromptButton').disabled = false; | |
document.getElementById('generatePromptButton').classList.remove('btn-secondary'); | |
document.getElementById('generatePromptButton').classList.add('btn-primary'); | |
saveToUserStorage(true); | |
}) | |
.catch(error => { | |
console.error(error); | |
// エラー時もローディングアイコンを非表示 | |
document.getElementById('loading').classList.add('d-none'); | |
document.getElementById('generatePromptButton').disabled = false; | |
document.getElementById('generatePromptButton').classList.remove('btn-secondary'); | |
document.getElementById('generatePromptButton').classList.add('btn-danger'); | |
}); | |
}; | |
let lastSaveTimestamp = new Date(); | |
function saveToUserStorage(force = false) { | |
const currentTime = new Date(); | |
if (!force && currentTime - lastSaveTimestamp < 5000) { | |
return; | |
} | |
const data = {}; | |
document.querySelectorAll('input, textarea').forEach(input => { | |
data[input.id] = input.value; | |
}); | |
localStorage.setItem('gemini_prompt', JSON.stringify(data)); | |
lastSaveTimestamp = currentTime; | |
return true; | |
} | |
function loadFromUserStorage() { | |
const data = JSON.parse(localStorage.getItem('gemini_prompt')) || {}; | |
document.querySelectorAll('input, textarea').forEach(input => { | |
let v = data[input.id] || ""; | |
if (v) { | |
if (input.type === "number") { | |
v = parseInt(v); | |
} | |
input.value = v; | |
} | |
}); | |
} | |
loadFromUserStorage(); | |
// 60秒ごとに自動保存を実行 | |
setInterval(() => { | |
saveToUserStorage(); | |
}, 60000); | |
document.querySelectorAll('input, textarea').forEach(input => { | |
input.addEventListener('input', saveToUserStorage); | |
}); | |
// Ctrl+Enterでプロンプト生成を実行する | |
document.addEventListener('keydown', function (event) { | |
if (event.ctrlKey && event.key === 'Enter') { | |
event.preventDefault(); // デフォルトの動作を防ぐ | |
generatePrompt(); // プロンプト生成関数を呼び出す | |
} | |
}); | |
</script> | |
</body> | |
</html> |