experiment_B / app.py
ryuss613's picture
Update app.py
b00b12d verified
Raw History Blame Contribute Delete
9.09 kB
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)