experiment_A / app.py
ryuss613's picture
Update app.py
4600f4d verified
import gradio as gr
import uuid
import os
import csv
import datetime
from datetime import timezone, timedelta
import re
from openai import OpenAI
import sys
# --- JST(日本時間)の定義 ---
JST = timezone(timedelta(hours=9), 'JST')
# --- API Key and Client Initialization ---
OPENROUTER_API_KEY = os.environ.get("OPENROUTER_API_KEY")
if not OPENROUTER_API_KEY:
print("Warning: OpenRouter API Key not found.", file=sys.stderr)
try:
client = OpenAI(
base_url="https://openrouter.ai/api/v1",
api_key=OPENROUTER_API_KEY,
)
print("OpenAI client initialized.")
except Exception as e:
print(f"Failed to initialize OpenAI client: {e}", file=sys.stderr)
# --- Helper Functions ---
def generate_user_id():
return str(uuid.uuid4())
def load_prompt(level):
try:
filename = f"{level.lower()}_prompt.txt"
if os.path.exists(filename):
with open(filename, "r", encoding="utf-8") as f:
return f.read()
return "You are a helpful AI assistant."
except Exception:
return "You are a helpful AI assistant."
# --- Core Logic Functions ---
def chat_process(message, display_history, full_logs, prompt_level):
"""
チャット処理を行う関数
"""
if display_history is None: display_history = []
if full_logs is None: full_logs = []
# 1. ユーザーメッセージの作成
current_time_user = datetime.datetime.now(JST).strftime("%Y-%m-%d %H:%M:%S")
user_msg_data = {
"role": "user",
"content": message,
"timestamp": current_time_user
}
temp_display = display_history + [user_msg_data]
temp_logs = full_logs + [user_msg_data]
# APIリクエスト用のメッセージ作成
system_prompt = load_prompt(prompt_level)
messages_for_api = [{"role": "system", "content": system_prompt}]
for msg in temp_display:
messages_for_api.append({"role": msg["role"], "content": msg["content"]})
ai_response = ""
try:
model_name = "google/gemini-2.5-flash"
response = client.chat.completions.create(
model=model_name,
messages=messages_for_api,
temperature=0.7,
max_tokens=500,
)
content = response.choices[0].message.content
ai_response = content if content is not None else ""
if not ai_response:
ai_response = "(応答が空でした)"
except Exception as e:
error_msg = f"API Error: {e}"
print(error_msg, file=sys.stderr)
ai_response = f"システムエラーが発生しました: {e}"
# 2. AIメッセージの作成
current_time_ai = datetime.datetime.now(JST).strftime("%Y-%m-%d %H:%M:%S")
ai_msg_data = {
"role": "assistant",
"content": ai_response,
"timestamp": current_time_ai
}
new_display = temp_display + [ai_msg_data]
new_logs = temp_logs + [ai_msg_data]
return "", new_display, new_display, new_logs
def change_difficulty_logic(new_level, full_logs):
"""
難易度変更時の処理
"""
if full_logs is None: full_logs = []
notification_msg = (
f"**[システム通知]**\n"
f"難易度を **{new_level}** に変更しました。\n"
f"解答開始と入力すれば指導が始まります。"
)
current_time = datetime.datetime.now(JST).strftime("%Y-%m-%d %H:%M:%S")
msg_data = {
"role": "assistant",
"content": notification_msg,
"timestamp": current_time
}
new_display_history = [msg_data]
new_full_logs = full_logs + [msg_data]
return new_display_history, new_display_history, new_full_logs, new_level
def export_csv_logic(user_id, user_name, full_logs):
print(f"Exporting CSV for UserID: {user_id}, Name: '{user_name}'")
if not user_name or not user_name.strip():
gr.Warning("名前が正しく取得できませんでした")
return None
if not full_logs:
gr.Warning("保存する履歴がまだありません。")
return None
timestamp_str = datetime.datetime.now(JST).strftime("%Y%m%d_%H%M%S")
safe_name = re.sub(r'[\\/*?:"<>|]', "", user_name)
safe_name = safe_name.strip().replace(" ", "_")
filename = f"chat_history_{safe_name}_{timestamp_str}.csv"
try:
with open(filename, "w", newline="", encoding="utf-8-sig") as f:
writer = csv.writer(f)
writer.writerow(["Timestamp", "Role", "Content"])
for msg in full_logs:
ts = msg.get("timestamp", "")
role = msg.get("role", "")
content = msg.get("content", "")
writer.writerow([ts, role, content])
print(f"Successfully saved to {filename}")
return filename
except Exception as e:
print(f"CSV Export Error: {e}", file=sys.stderr)
gr.Warning(f"CSV保存エラー: {e}")
return None
def start_app_logic(user_name):
"""
名前が入力されているか確認し、画面表示を切り替える
"""
if not user_name or not user_name.strip():
raise gr.Error("お名前を入力してください。")
# Start画面を隠し(False)、Main画面を表示(True)
return gr.update(visible=False), gr.update(visible=True)
# --- UI Definition ---
with gr.Blocks() as demo:
# ステート定義
user_id_state = gr.State(generate_user_id)
display_history_state = gr.State([]) # 画面用
full_logs_state = gr.State([]) # 保存用
prompt_level_state = gr.State("Beginner")
start_msg_constant = gr.State("解答開始")
# =========================================
# 1. スタート画面
# =========================================
with gr.Column(visible=True) as start_screen:
gr.Markdown("# AI Chatbot System")
gr.Markdown("学習を始めるには、お名前を入力して「解答開始」を押してください。")
user_name_input = gr.Textbox(
label="お名前",
placeholder="山田 太郎",
scale=2
)
start_btn = gr.Button("解答開始", variant="primary", size="lg")
# =========================================
# 2. メインチャット画面
# =========================================
with gr.Column(visible=False) as main_screen:
gr.Markdown("# Multi-Level AI Chatbot")
with gr.Row():
export_btn = gr.DownloadButton("📥 会話をCSVで保存", scale=1)
with gr.Row():
gr.Markdown("## 問題の難易度を選択してください(初級から順番に取り組んでください)")
with gr.Row():
level_radio = gr.Radio(
["Beginner", "Intermediate", "Advanced"],
label="Difficulty Level",
value="Beginner",
interactive=True
)
# 【修正箇所】Chatbotの定義にlatex_delimitersを追加
chatbot = gr.Chatbot(
type="messages",
height=500,
latex_delimiters=[
{"left": "$$", "right": "$$", "display": True}, # 行全体(ディスプレイ数式)
{"left": "$", "right": "$", "display": False}, # インライン数式
{"left": "\\(", "right": "\\)", "display": False},
{"left": "\\[", "right": "\\]", "display": True},
]
)
msg_input = gr.Textbox(
placeholder="メッセージを入力してください... (Enterで送信)",
label="Chat Input",
lines=1
)
send_btn = gr.Button("送信", variant="primary")
# --- Event Handling ---
# A. スタートボタンの処理
start_btn.click(
fn=start_app_logic,
inputs=[user_name_input],
outputs=[start_screen, main_screen]
).then(
fn=chat_process,
inputs=[start_msg_constant, display_history_state, full_logs_state, prompt_level_state],
outputs=[msg_input, chatbot, display_history_state, full_logs_state]
)
# B. メッセージ送信時の処理
chat_event_args = {
"fn": chat_process,
"inputs": [msg_input, display_history_state, full_logs_state, prompt_level_state],
"outputs": [msg_input, chatbot, display_history_state, full_logs_state]
}
msg_input.submit(**chat_event_args)
send_btn.click(**chat_event_args)
# C. 難易度変更時の処理
level_radio.change(
fn=change_difficulty_logic,
inputs=[level_radio, full_logs_state],
outputs=[chatbot, display_history_state, full_logs_state, prompt_level_state]
)
# D. CSV保存処理
export_btn.click(
fn=export_csv_logic,
inputs=[user_id_state, user_name_input, full_logs_state],
outputs=[export_btn]
)
if __name__ == "__main__":
demo.launch(debug=True, share=False)