Spaces:
Running
on
Zero
Running
on
Zero
import gradio as gr | |
import os | |
from dotenv import load_dotenv | |
from PIL import Image | |
import io | |
import base64 | |
import requests | |
from datetime import datetime | |
from pytz import timezone | |
import numpy as np | |
load_dotenv() | |
# 初回訪問のメッセージ | |
welcome_message = """ | |
<h2>このサービスは初めてご利用ですか?</h2> | |
<p>以下から選択してください。</p> | |
""" | |
# Googleフォームの送信URL | |
apps_script_url = os.environ["APPS_SCRIPT_URL"] | |
# ローカライズ用辞書 | |
translations = { | |
"en": { | |
"welcome_message": "<h2>Is this your first time using this service?</h2><p>Please select below.</p>", | |
"visit_choices": ["First time", "Returning"], | |
"returning_message": "<h2>Thank you for returning!</h2><p>Click the button below to proceed.</p>", | |
"proceed_button": "Proceed", | |
"survey_title": "<h2>Please answer the survey</h2><p>Fill out the form below and submit.</p>", | |
"form": { | |
"source": "1. Where did you learn about this Space?", | |
"country": "2. Which country or region do you live in?", | |
"ai_usage": "3. Have you used AI to generate illustrations?", | |
"ai_usage_choices": ["Select...", "Frequently", "Sometimes", "Never"], | |
"drawing_experience": "4. Have you practiced traditional hand-drawn illustrations?", | |
"drawing_experience_choices": ["Select...", "As a hobby", "For work", "Never"], | |
"issues": "5. (Optional) Tell us about any challenges you've faced while practicing illustrations.", | |
"interest": "6. (Optional) What interested you in this Space?", | |
"contact_info": "7. (Optional) Provide your contact information (e.g., SNS, URL, email)", | |
"contact_info_placeholder": "e.g., Twitter, portfolio URL, email", | |
"submit_button": "Submit" | |
} | |
}, | |
"ja": { | |
"welcome_message": "<h2>このサービスは初めてご利用ですか?</h2><p>以下から選択してください。</p>", | |
"visit_choices": ["初めて利用する", "2回目以降"], | |
"returning_message": "<h2>再訪ありがとうございます!</h2><p>以下のボタンをクリックして進んでください。</p>", | |
"proceed_button": "進む", | |
"survey_title": "<h2>アンケートにご回答ください</h2><p>以下のフォームに入力して送信してください。</p>", | |
"form": { | |
"source": "1. このSpaceをどこで知りましたか?", | |
"country": "2. お住まいの国や地域を教えてください。", | |
"ai_usage": "3. 生成AIでイラスト生成をしたことがありますか?", | |
"ai_usage_choices": ["選択してください...", "よくする", "ある", "ない"], | |
"drawing_experience": "4. 生成AIではない従来の手描きイラストを練習した経験はありますか?", | |
"drawing_experience_choices": ["選択してください...", "趣味で", "仕事で", "ない"], | |
"issues": "5. (任意)イラストの練習中に困った経験があれば教えてください", | |
"interest": "6. (任意)このSpaceに興味を持った理由を教えてください。", | |
"contact_info": "7. (任意)連絡先(SNS、URL、メールアドレスなど)を教えてください", | |
"contact_info_placeholder": "例: Twitterアカウント、ポートフォリオURL、メールアドレスなど", | |
"submit_button": "送信" | |
} | |
}, | |
"zh": { | |
"welcome_message": "<h2>这是您第一次使用此服务吗?</h2><p>请从下面选择。</p>", | |
"visit_choices": ["第一次", "再次访问"], | |
"returning_message": "<h2>感谢您的再次访问!</h2><p>请点击下面的按钮继续。</p>", | |
"proceed_button": "继续", | |
"survey_title": "<h2>请回答问卷</h2><p>填写以下表格并提交。</p>", | |
"form": { | |
"source": "1. 您是从哪里得知此服务的?", | |
"country": "2. 您居住的国家或地区是?", | |
"ai_usage": "3. 您是否使用过AI生成插图?", | |
"ai_usage_choices": ["请选择...", "经常", "偶尔", "从未"], | |
"drawing_experience": "4. 您是否练习过传统手绘插图?", | |
"drawing_experience_choices": ["请选择...", "作为爱好", "为了工作", "从未"], | |
"issues": "5. (可选)在练习插图过程中遇到的挑战是什么?", | |
"interest": "6. (可选)是什么让您对这个Space感兴趣?", | |
"contact_info": "7. (可选)提供您的联系方式(如:SNS、网址、电子邮件等)", | |
"contact_info_placeholder": "例如:Twitter、作品集网址、电子邮件", | |
"submit_button": "提交" | |
} | |
} | |
} | |
# 言語選択に応じてローカライズ | |
def localize(language): | |
t = translations[language] | |
return { | |
"welcome_message": t["welcome_message"], | |
"visit_choices": t["visit_choices"], | |
"returning_message": t["returning_message"], | |
"proceed_button": t["proceed_button"], | |
"form_html": f""" | |
<h2>{t['survey_title']}</h2> | |
<form id="survey-form" action="{apps_script_url}" method="POST" target="hidden_iframe"> | |
<label for="source">{t['form']['source']}</label><br> | |
<input type="text" name="source" id="source" required><br><br> | |
<label for="country">{t['form']['country']}</label><br> | |
<input type="text" name="country" id="country" required><br><br> | |
<label for="ai_usage">{t['form']['ai_usage']}</label><br> | |
<select name="ai_usage" id="ai_usage" required> | |
<option value="" selected disabled>{t['form']['ai_usage_choices'][0]}</option> | |
{"".join([f'<option value="{choice}">{choice}</option>' for choice in t['form']['ai_usage_choices'][1:]])} | |
</select><br><br> | |
<label for="drawing_experience">{t['form']['drawing_experience']}</label><br> | |
<select name="drawing_experience" id="drawing_experience" required> | |
<option value="" selected disabled>{t['form']['drawing_experience_choices'][0]}</option> | |
{"".join([f'<option value="{choice}">{choice}</option>' for choice in t['form']['drawing_experience_choices'][1:]])} | |
</select><br><br> | |
<label for="issues">{t['form']['issues']}</label><br> | |
<textarea name="issues" id="issues"></textarea><br><br> | |
<label for="interest">{t['form']['interest']}</label><br> | |
<textarea name="interest" id="interest"></textarea><br><br> | |
<label for="contact_info">{t['form']['contact_info']}</label><br> | |
<input type="text" name="contact_info" id="contact_info" placeholder="{t['form']['contact_info_placeholder']}"><br><br> | |
<button type="submit" id="submit-button">{t['form']['submit_button']}</button> | |
</form> | |
<iframe name="hidden_iframe" style="display:none;"></iframe> | |
""" | |
} | |
# 初回訪問の選択肢に応じた処理 | |
def handle_visit_choice(choice, language): | |
if choice == localize(language)["visit_choices"][0]: | |
return gr.update(visible=False), gr.update(visible=True), gr.update(visible=False) | |
else: | |
return gr.update(visible=False), gr.update(visible=False), gr.update(visible=True) | |
# フォーム送信後の処理 | |
def handle_form_submission(flag_value): | |
print("form submitted") | |
return gr.update(visible=False), gr.update(visible=True) | |
# 進むボタンを押した後の処理 | |
def handle_proceed(): | |
print("more than once") | |
return gr.update(visible=False), gr.update(visible=True) | |
script = """ | |
() => { | |
function attachFormListener() { | |
const form = document.getElementById("survey-form"); | |
if (form) { | |
function submitForm() { | |
console.log('form submitted'); | |
const flagInput = document.querySelector("#form_flag textarea"); | |
flagInput.value = 'true'; | |
flagInput.dispatchEvent(new Event('input')); | |
} | |
// イベントを削除 | |
form.removeEventListener("submit", submitForm); | |
// イベントを追加 | |
form.addEventListener("submit", submitForm); | |
} | |
} | |
// 初期ロード時にイベントリスナーを設定 | |
attachFormListener(); | |
// DOMが動的に更新された場合にもイベントリスナーを再設定 | |
const observer = new MutationObserver(() => { | |
attachFormListener(); | |
}); | |
observer.observe(document.body, { childList: true, subtree: true }); | |
} | |
""" | |
# Google Apps ScriptのURL | |
feedback_script_url = os.environ["FEEDBACK_SCRIPT_URL"] | |
# 画像生成パラメータ | |
def generate_image(mode, weight1, weight2): | |
# ダミーの画像生成処理 | |
image = Image.new("RGB", (256, 256), color=(int(255*weight1), int(255*weight2), int(255*weight1*weight2))) | |
return image | |
# Google Driveに保存する処理 | |
def send_feedback(image, filename): | |
# numpy.ndarray を PIL.Image に変換 | |
if isinstance(image, np.ndarray): | |
image = Image.fromarray(image) | |
# 画像をBase64にエンコード | |
buffered = io.BytesIO() | |
image.save(buffered, format="PNG") | |
img_str = base64.b64encode(buffered.getvalue()).decode() | |
# Google Apps Scriptに送信 | |
response = requests.post( | |
feedback_script_url, | |
json={"image": img_str, "filename": filename} | |
) | |
if response.status_code == 200: | |
return gr.update(value="Thank you for cooperation!/ご協力ありがとうございます!", interactive=False) | |
else: | |
return gr.update(value="Failed to send. Try again./送信に失敗しました。もう一度お試しください。", interactive=True) | |