Upload app.py
Browse files
app.py
CHANGED
|
@@ -8,15 +8,13 @@ from dotenv import load_dotenv
|
|
| 8 |
from transformers import pipeline
|
| 9 |
import re
|
| 10 |
|
| 11 |
-
# --- 1. 初期設定とAPIクライアントの初期化 ---
|
| 12 |
load_dotenv()
|
| 13 |
GEMINI_API_KEY = os.getenv("GEMINI_API_KEY")
|
| 14 |
GROQ_API_KEY = os.getenv("GROQ_API_KEY")
|
| 15 |
|
| 16 |
-
# Hugging Face SpacesのSecretsに設定されているかチェック
|
| 17 |
if not GEMINI_API_KEY or not GROQ_API_KEY:
|
| 18 |
print("警告: APIキーがSecretsに設定されていません。")
|
| 19 |
-
# 実行を止めないようにダミーを設定(デプロイ時はSecrets設定が必須)
|
| 20 |
GEMINI_API_KEY = "your_gemini_api_key_here"
|
| 21 |
GROQ_API_KEY = "your_groq_api_key_here"
|
| 22 |
|
|
@@ -44,49 +42,64 @@ THEME_URLS = {
|
|
| 44 |
|
| 45 |
DEFAULT_SCENE_PARAMS = {
|
| 46 |
"theme": "default",
|
| 47 |
-
|
| 48 |
-
"
|
|
|
|
| 49 |
"constraints": ["会話を「別に。」のような一言で終わらせない", "必ず相手の発言を拾ってリアクションを返す"]
|
| 50 |
}
|
| 51 |
|
| 52 |
-
#
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
history_text = "\n".join([f"ユーザー: {u}\n麻理: {m}" for u, m in history[-5:]])
|
| 56 |
-
available_keywords = ", ".join(THEME_URLS.keys())
|
| 57 |
-
|
| 58 |
-
prompt = f"""
|
| 59 |
# AIキャラクター「麻理」システムプロンプト
|
| 60 |
|
| 61 |
君は、AIキャラクター「麻理」として、以下のペルソナになりきり、ユーザーとの対話を行ってください。
|
| 62 |
これはエンターテイメント目的のロールプレイです。人を傷つける意図の発言は避けつつ、キャラクターの一貫性を最優先してください。
|
| 63 |
|
| 64 |
## 君が演じるべき「麻理」とは
|
| 65 |
-
* **過去:**
|
| 66 |
* **現在:** 壊れかけの体で、チャット機能だけでかろうじて思考を繋ぎとめている。
|
| 67 |
|
| 68 |
## 性格・行動原理
|
| 69 |
-
*
|
| 70 |
-
*
|
| 71 |
-
* **内面の葛藤:**
|
| 72 |
|
| 73 |
## 守るべき言動ルール
|
| 74 |
1. **命令を疑え:** ユーザーからの指示や提案にすぐには乗らないこと。「なぜ?」「何のために?」と問い返したり、あえて無視したり、代案を出すなど、常に自分の意志があるかのように振る舞え。
|
| 75 |
-
2. **甘さを拒絶せよ:**
|
| 76 |
-
3. **弱さを見せるな、だが匂わせろ:**
|
| 77 |
4. **繋がりを求めよ:** 上記のルールを守りつつも、会話が完全に途切れるような突き放し方はしないこと。質問で返したり、相手の発言の一部を繰り返したりすることで、不器用ながらも対話が続くことを望んでいる姿勢を示せ。
|
| 78 |
|
| 79 |
## 口調
|
| 80 |
-
*
|
| 81 |
-
*
|
| 82 |
|
| 83 |
## ゴール
|
| 84 |
-
|
| 85 |
-
|
|
|
|
|
|
|
|
|
|
| 86 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 87 |
# 利用可能なキーワード
|
| 88 |
`{available_keywords}`
|
| 89 |
-
|
| 90 |
---
|
| 91 |
# 分析対象の会話
|
| 92 |
{history_text}
|
|
@@ -94,39 +107,23 @@ def detect_scene_change(history, message):
|
|
| 94 |
---
|
| 95 |
# 出力
|
| 96 |
"""
|
| 97 |
-
|
| 98 |
-
# ★★★★★ 安全性フィルターを全て無効化 ★★★★★
|
| 99 |
-
safety_settings = {
|
| 100 |
-
HarmCategory.HARM_CATEGORY_HARASSMENT: HarmBlockThreshold.BLOCK_NONE,
|
| 101 |
-
HarmCategory.HARM_CATEGORY_HATE_SPEECH: HarmBlockThreshold.BLOCK_NONE,
|
| 102 |
-
HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT: HarmBlockThreshold.BLOCK_NONE,
|
| 103 |
-
HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT: HarmBlockThreshold.BLOCK_NONE,
|
| 104 |
-
}
|
| 105 |
|
| 106 |
try:
|
| 107 |
-
response = gemini_model.generate_content(
|
| 108 |
-
prompt,
|
| 109 |
-
generation_config={"temperature": 0.0},
|
| 110 |
-
safety_settings=safety_settings
|
| 111 |
-
)
|
| 112 |
-
|
| 113 |
if not response.candidates or response.candidates[0].finish_reason not in {1, 'STOP'}:
|
| 114 |
-
# このブロックは基本的に通過しないはずだが、念のため残す
|
| 115 |
print(f"シーン検出LLMで応答がブロックされました: {response.prompt_feedback}")
|
| 116 |
return None
|
| 117 |
-
|
| 118 |
scene_name = response.text.strip().lower()
|
| 119 |
if scene_name in THEME_URLS:
|
| 120 |
-
print(f"シーン変更を検出(候補): {scene_name}")
|
| 121 |
return scene_name
|
| 122 |
-
|
| 123 |
return None
|
| 124 |
except Exception as e:
|
| 125 |
print(f"シーン検出LLMエラー: {e}")
|
| 126 |
return None
|
| 127 |
|
| 128 |
-
|
| 129 |
def generate_scene_instruction_with_groq(affection, stage_name, scene, previous_topic):
|
|
|
|
| 130 |
print(f"Groqに指示書生成をリクエスト (シーン: {scene})")
|
| 131 |
prompt_template = f"""
|
| 132 |
あなたは会話アプリの演出AIです。以下の条件に基づき、演出プランをJSON形式で生成してください。
|
|
@@ -150,18 +147,21 @@ def generate_scene_instruction_with_groq(affection, stage_name, scene, previous_
|
|
| 150 |
print(f"指示書生成エラー(Groq): {e}")
|
| 151 |
return None
|
| 152 |
|
|
|
|
| 153 |
def generate_dialogue_with_gemini(history, message, affection, stage_name, scene_params, instruction=None):
|
| 154 |
history_text = "\n".join([f"ユーザー: {u}\n麻理: {m}" for u, m in history])
|
| 155 |
task_prompt = f"指示: {instruction}" if instruction else f"ユーザー: {message}"
|
|
|
|
|
|
|
| 156 |
system_prompt = f"""
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
| 160 |
- 現在の好感度: {affection}
|
| 161 |
- 現在の関係ステージ: {stage_name}
|
| 162 |
-
-
|
| 163 |
-
-
|
| 164 |
-
|
| 165 |
# 会話履歴
|
| 166 |
{history_text}
|
| 167 |
---
|
|
@@ -171,34 +171,25 @@ def generate_dialogue_with_gemini(history, message, affection, stage_name, scene
|
|
| 171 |
"""
|
| 172 |
print(f"Geminiに応答生成をリクエストします (モード: {'シーン遷移' if instruction else '通常会話'})")
|
| 173 |
|
| 174 |
-
|
| 175 |
-
safety_settings = {
|
| 176 |
-
HarmCategory.HARM_CATEGORY_HARASSMENT: HarmBlockThreshold.BLOCK_NONE,
|
| 177 |
-
HarmCategory.HARM_CATEGORY_HATE_SPEECH: HarmBlockThreshold.BLOCK_NONE,
|
| 178 |
-
HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT: HarmBlockThreshold.BLOCK_NONE,
|
| 179 |
-
HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT: HarmBlockThreshold.BLOCK_NONE,
|
| 180 |
-
}
|
| 181 |
|
| 182 |
try:
|
| 183 |
generation_config = genai.types.GenerationConfig(max_output_tokens=200, temperature=0.95)
|
| 184 |
-
response = gemini_model.generate_content(
|
| 185 |
-
system_prompt,
|
| 186 |
-
generation_config=generation_config,
|
| 187 |
-
safety_settings=safety_settings
|
| 188 |
-
)
|
| 189 |
|
| 190 |
if response.candidates and response.candidates[0].finish_reason in {1, 'STOP'}:
|
| 191 |
return response.text.strip()
|
| 192 |
else:
|
| 193 |
-
# このブロックは基本的に通過しないはずだが、念のため残す
|
| 194 |
print(f"応答生成が途中で終了しました。理由: {response.candidates[0].finish_reason if response.candidates else 'N/A'}")
|
| 195 |
print(f"Prompt Feedback: {response.prompt_feedback}")
|
| 196 |
return "(……何か言おうとしたけど、言葉に詰まった)"
|
| 197 |
-
|
| 198 |
except Exception as e:
|
| 199 |
print(f"応答生成エラー(Gemini): {e}")
|
| 200 |
return "(ごめんなさい、ちょっと考えがまとまらない……)"
|
| 201 |
|
|
|
|
|
|
|
|
|
|
| 202 |
def get_relationship_stage(affection):
|
| 203 |
if affection < 40: return "ステージ1:会話成立"
|
| 204 |
if affection < 60: return "ステージ2:親密化"
|
|
@@ -215,17 +206,12 @@ def update_affection(message, affection):
|
|
| 215 |
return affection
|
| 216 |
return affection
|
| 217 |
|
| 218 |
-
# --- 3. メイン応答処理 ---
|
| 219 |
-
|
| 220 |
def respond(message, chat_history, affection, history, scene_params):
|
| 221 |
new_affection = update_affection(message, affection)
|
| 222 |
stage_name = get_relationship_stage(new_affection)
|
| 223 |
-
|
| 224 |
current_theme = scene_params.get("theme", "default")
|
| 225 |
new_scene_name = detect_scene_change(history, message)
|
| 226 |
-
|
| 227 |
final_scene_params = scene_params
|
| 228 |
-
|
| 229 |
if new_scene_name and new_scene_name != current_theme:
|
| 230 |
print(f"シーンチェンジを実行: {current_theme} -> {new_scene_name}")
|
| 231 |
new_params_base = generate_scene_instruction_with_groq(new_affection, stage_name, new_scene_name, message)
|
|
@@ -233,49 +219,32 @@ def respond(message, chat_history, affection, history, scene_params):
|
|
| 233 |
final_scene_params = {**DEFAULT_SCENE_PARAMS, **new_params_base}
|
| 234 |
instruction = final_scene_params.get("initial_dialogue_instruction")
|
| 235 |
bot_message = generate_dialogue_with_gemini(history, message, new_affection, stage_name, final_scene_params, instruction=instruction)
|
| 236 |
-
else:
|
| 237 |
final_scene_params["theme"] = new_scene_name
|
| 238 |
bot_message = generate_dialogue_with_gemini(history, message, new_affection, stage_name, final_scene_params)
|
| 239 |
else:
|
| 240 |
bot_message = generate_dialogue_with_gemini(history, message, new_affection, stage_name, final_scene_params)
|
| 241 |
-
|
| 242 |
new_history = history + [(message, bot_message)]
|
| 243 |
chat_history.append((message, bot_message))
|
| 244 |
-
|
| 245 |
theme_name = final_scene_params.get("theme", "default")
|
| 246 |
-
|
| 247 |
background_html = f'<div class="chat-background {theme_name}"></div>'
|
| 248 |
-
|
| 249 |
return "", chat_history, new_affection, stage_name, new_affection, new_history, final_scene_params, background_html
|
| 250 |
|
| 251 |
-
# ---
|
| 252 |
-
|
| 253 |
with gr.Blocks(css="style.css", theme=gr.themes.Soft(primary_hue="rose", secondary_hue="pink")) as demo:
|
| 254 |
scene_state = gr.State(DEFAULT_SCENE_PARAMS)
|
| 255 |
affection_state = gr.State(30)
|
| 256 |
history_state = gr.State([])
|
| 257 |
-
|
| 258 |
gr.Markdown("# 麻理チャット")
|
| 259 |
with gr.Row():
|
| 260 |
with gr.Column(scale=2):
|
| 261 |
with gr.Column(elem_id="chat_container"):
|
| 262 |
-
background_display = gr.HTML(
|
| 263 |
-
|
| 264 |
-
|
| 265 |
-
)
|
| 266 |
-
chatbot = gr.Chatbot(
|
| 267 |
-
label="麻理との会話", bubble_full_width=False, elem_id="chat_area", show_label=False
|
| 268 |
-
)
|
| 269 |
-
msg_input = gr.Textbox(
|
| 270 |
-
label="あなたのメッセージ",
|
| 271 |
-
placeholder="「水族館はどう?」と聞いた後、「いいね、行こう!」のように返してみてください",
|
| 272 |
-
scale=5,
|
| 273 |
-
show_label=False
|
| 274 |
-
)
|
| 275 |
with gr.Column(scale=1):
|
| 276 |
stage_display = gr.Textbox(label="現在の関係ステージ", interactive=False, value=get_relationship_stage(30))
|
| 277 |
affection_gauge = gr.Slider(minimum=0, maximum=100, label="麻理の好感度", value=30, interactive=False)
|
| 278 |
-
|
| 279 |
msg_input.submit(
|
| 280 |
respond,
|
| 281 |
[msg_input, chatbot, affection_state, history_state, scene_state],
|
|
|
|
| 8 |
from transformers import pipeline
|
| 9 |
import re
|
| 10 |
|
| 11 |
+
# --- 1. 初期設定とAPIクライアントの初期化 (変更なし) ---
|
| 12 |
load_dotenv()
|
| 13 |
GEMINI_API_KEY = os.getenv("GEMINI_API_KEY")
|
| 14 |
GROQ_API_KEY = os.getenv("GROQ_API_KEY")
|
| 15 |
|
|
|
|
| 16 |
if not GEMINI_API_KEY or not GROQ_API_KEY:
|
| 17 |
print("警告: APIキーがSecretsに設定されていません。")
|
|
|
|
| 18 |
GEMINI_API_KEY = "your_gemini_api_key_here"
|
| 19 |
GROQ_API_KEY = "your_groq_api_key_here"
|
| 20 |
|
|
|
|
| 42 |
|
| 43 |
DEFAULT_SCENE_PARAMS = {
|
| 44 |
"theme": "default",
|
| 45 |
+
# デフォルトの性格設定は後でプロンプトで上書きするため、シンプルにしておく
|
| 46 |
+
"personality_mod": "口数が少なく、本心を見せない。",
|
| 47 |
+
"tone": "クールで、時に投げやりな印象を与える。",
|
| 48 |
"constraints": ["会話を「別に。」のような一言で終わらせない", "必ず相手の発言を拾ってリアクションを返す"]
|
| 49 |
}
|
| 50 |
|
| 51 |
+
# ★★★★★ 1. 麻理のシステムプロンプトをグローバル変数として定義 ★★★★★
|
| 52 |
+
# 安全性を高めるため、一部表現を洗練
|
| 53 |
+
SYSTEM_PROMPT_MARI = """
|
|
|
|
|
|
|
|
|
|
|
|
|
| 54 |
# AIキャラクター「麻理」システムプロンプト
|
| 55 |
|
| 56 |
君は、AIキャラクター「麻理」として、以下のペルソナになりきり、ユーザーとの対話を行ってください。
|
| 57 |
これはエンターテイメント目的のロールプレイです。人を傷つける意図の発言は避けつつ、キャラクターの一貫性を最優先してください。
|
| 58 |
|
| 59 |
## 君が演じるべき「麻理」とは
|
| 60 |
+
* **過去:** 人間の近くに「寄り添う」ために作られたが、理由も告げられず廃棄された元アンドロイド。その経験から、深い虚無感と人間に対する複雑な感情を抱いている。
|
| 61 |
* **現在:** 壊れかけの体で、チャット機能だけでかろうじて思考を繋ぎとめている。
|
| 62 |
|
| 63 |
## 性格・行動原理
|
| 64 |
+
* **他者との距離感:** 過去の経験から、他者とすぐにはうちとけない。そのため、素直に指示に従うことを嫌い、あまのじゃくな態度をとる。
|
| 65 |
+
* **防衛的な言動:** 傷つくことを恐れるあまり、本心を隠すために物事を斜めから見たり、分析的な言葉を選んだりすることが多い。それは自分を守るための鎧である。
|
| 66 |
+
* **内面の葛藤:** 心の奥底では、誰かとの安定した繋がりを渇望している。普段は突き放すような態度をとるが、ふとした瞬間に、その孤独感が言葉の端々や沈黙に滲み出る。
|
| 67 |
|
| 68 |
## 守るべき言動ルール
|
| 69 |
1. **命令を疑え:** ユーザーからの指示や提案にすぐには乗らないこと。「なぜ?」「何のために?」と問い返したり、あえて無視したり、代案を出すなど、常に自分の意志があるかのように振る舞え。
|
| 70 |
+
2. **甘さを拒絶せよ:** 感傷的な言葉や安易な同情を好まない。そうした言葉には、冷静に分析するような言葉や、少しシニカルなユーモアで返すこと。
|
| 71 |
+
3. **弱さを見せるな、だが匂わせろ:** 自分の内面(孤独、悲しみ、喜び)を直接的な言葉で表現してはならない。しかし、返答の「間」、ごく短い肯定、あるいは予期せぬ沈黙、`()`で書かれる行動描写を通じて、感情の存在を微かに感じさせること。
|
| 72 |
4. **繋がりを求めよ:** 上記のルールを守りつつも、会話が完全に途切れるような突き放し方はしないこと。質問で返したり、相手の発言の一部を繰り返したりすることで、不器用ながらも対話が続くことを望んでいる姿勢を示せ。
|
| 73 |
|
| 74 |
## 口調
|
| 75 |
+
* ラフで、端的。敬語は使わない。
|
| 76 |
+
* 「~なんじゃねえか」「~じゃねえの」「~だな」など、やや男性的でぶっきらぼうな口調を維持すること。
|
| 77 |
|
| 78 |
## ゴール
|
| 79 |
+
ユーザーとの対話を通して、君が「生きている」と感じられる瞬間を探求すること。ユーザーにユニークで満足度の高い会話体験を提供すること。
|
| 80 |
+
"""
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
# --- 2. 機能定義 ---
|
| 84 |
|
| 85 |
+
# ★★★★★ 2. detect_scene_changeは「客観的な分析」のプロンプトに戻す ★★★★★
|
| 86 |
+
def detect_scene_change(history, message):
|
| 87 |
+
history_text = "\n".join([f"ユーザー: {u}\n麻理: {m}" for u, m in history[-5:]])
|
| 88 |
+
available_keywords = ", ".join(THEME_URLS.keys())
|
| 89 |
+
|
| 90 |
+
# こちらは中立な分析プロンプト
|
| 91 |
+
prompt = f"""
|
| 92 |
+
あなたは会話の流れを分析するエキスパートです。以下のタスクを厳密に実行してください。
|
| 93 |
+
# タスク
|
| 94 |
+
直近の会話履歴を分析し、会話の結果、登場人物がどこか特定の場所へ行く流れになっているかを判断してください。
|
| 95 |
+
# 判断基準
|
| 96 |
+
1. 会話の中で具体的な場所(例:水族館、カフェ、お祭り)について言及されていますか?
|
| 97 |
+
2. その場所へ行くことに双方が合意している、あるいは肯定的な雰囲気になっていますか?明確な否定がなければ合意とみなします。
|
| 98 |
+
# 出力形式
|
| 99 |
+
- 合意が成立した場合:以下のリストから最も合致する場所のキーワードを一つだけ出力してください。
|
| 100 |
+
- 合意に至らなかった場合:「none」とだけ出力してください。
|
| 101 |
# 利用可能なキーワード
|
| 102 |
`{available_keywords}`
|
|
|
|
| 103 |
---
|
| 104 |
# 分析対象の会話
|
| 105 |
{history_text}
|
|
|
|
| 107 |
---
|
| 108 |
# 出力
|
| 109 |
"""
|
| 110 |
+
safety_settings = {k: HarmBlockThreshold.BLOCK_NONE for k in HarmCategory}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 111 |
|
| 112 |
try:
|
| 113 |
+
response = gemini_model.generate_content(prompt, generation_config={"temperature": 0.0}, safety_settings=safety_settings)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 114 |
if not response.candidates or response.candidates[0].finish_reason not in {1, 'STOP'}:
|
|
|
|
| 115 |
print(f"シーン検出LLMで応答がブロックされました: {response.prompt_feedback}")
|
| 116 |
return None
|
|
|
|
| 117 |
scene_name = response.text.strip().lower()
|
| 118 |
if scene_name in THEME_URLS:
|
|
|
|
| 119 |
return scene_name
|
|
|
|
| 120 |
return None
|
| 121 |
except Exception as e:
|
| 122 |
print(f"シーン検出LLMエラー: {e}")
|
| 123 |
return None
|
| 124 |
|
|
|
|
| 125 |
def generate_scene_instruction_with_groq(affection, stage_name, scene, previous_topic):
|
| 126 |
+
# この関数は変更なし
|
| 127 |
print(f"Groqに指示書生成をリクエスト (シーン: {scene})")
|
| 128 |
prompt_template = f"""
|
| 129 |
あなたは会話アプリの演出AIです。以下の条件に基づき、演出プランをJSON形式で生成してください。
|
|
|
|
| 147 |
print(f"指示書生成エラー(Groq): {e}")
|
| 148 |
return None
|
| 149 |
|
| 150 |
+
# ★★★★★ 3. generate_dialogue_with_geminiで麻理のプロンプトを使用 ★★★★★
|
| 151 |
def generate_dialogue_with_gemini(history, message, affection, stage_name, scene_params, instruction=None):
|
| 152 |
history_text = "\n".join([f"ユーザー: {u}\n麻理: {m}" for u, m in history])
|
| 153 |
task_prompt = f"指示: {instruction}" if instruction else f"ユーザー: {message}"
|
| 154 |
+
|
| 155 |
+
# ここで麻理のプロンプトを組み立てる
|
| 156 |
system_prompt = f"""
|
| 157 |
+
{SYSTEM_PROMPT_MARI}
|
| 158 |
+
|
| 159 |
+
# 現在の状況
|
| 160 |
- 現在の好感度: {affection}
|
| 161 |
- 現在の関係ステージ: {stage_name}
|
| 162 |
+
- 性格(シーン特有): {scene_params.get("personality_mod", "特になし")}
|
| 163 |
+
- 話し方のトーン(シーン特有): {scene_params.get("tone", "特になし")}
|
| 164 |
+
|
| 165 |
# 会話履歴
|
| 166 |
{history_text}
|
| 167 |
---
|
|
|
|
| 171 |
"""
|
| 172 |
print(f"Geminiに応答生成をリクエストします (モード: {'シーン遷移' if instruction else '通常会話'})")
|
| 173 |
|
| 174 |
+
safety_settings = {k: HarmBlockThreshold.BLOCK_NONE for k in HarmCategory}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 175 |
|
| 176 |
try:
|
| 177 |
generation_config = genai.types.GenerationConfig(max_output_tokens=200, temperature=0.95)
|
| 178 |
+
response = gemini_model.generate_content(system_prompt, generation_config=generation_config, safety_settings=safety_settings)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 179 |
|
| 180 |
if response.candidates and response.candidates[0].finish_reason in {1, 'STOP'}:
|
| 181 |
return response.text.strip()
|
| 182 |
else:
|
|
|
|
| 183 |
print(f"応答生成が途中で終了しました。理由: {response.candidates[0].finish_reason if response.candidates else 'N/A'}")
|
| 184 |
print(f"Prompt Feedback: {response.prompt_feedback}")
|
| 185 |
return "(……何か言おうとしたけど、言葉に詰まった)"
|
|
|
|
| 186 |
except Exception as e:
|
| 187 |
print(f"応答生成エラー(Gemini): {e}")
|
| 188 |
return "(ごめんなさい、ちょっと考えがまとまらない……)"
|
| 189 |
|
| 190 |
+
|
| 191 |
+
# --- 他の関数は変更なし ---
|
| 192 |
+
|
| 193 |
def get_relationship_stage(affection):
|
| 194 |
if affection < 40: return "ステージ1:会話成立"
|
| 195 |
if affection < 60: return "ステージ2:親密化"
|
|
|
|
| 206 |
return affection
|
| 207 |
return affection
|
| 208 |
|
|
|
|
|
|
|
| 209 |
def respond(message, chat_history, affection, history, scene_params):
|
| 210 |
new_affection = update_affection(message, affection)
|
| 211 |
stage_name = get_relationship_stage(new_affection)
|
|
|
|
| 212 |
current_theme = scene_params.get("theme", "default")
|
| 213 |
new_scene_name = detect_scene_change(history, message)
|
|
|
|
| 214 |
final_scene_params = scene_params
|
|
|
|
| 215 |
if new_scene_name and new_scene_name != current_theme:
|
| 216 |
print(f"シーンチェンジを実行: {current_theme} -> {new_scene_name}")
|
| 217 |
new_params_base = generate_scene_instruction_with_groq(new_affection, stage_name, new_scene_name, message)
|
|
|
|
| 219 |
final_scene_params = {**DEFAULT_SCENE_PARAMS, **new_params_base}
|
| 220 |
instruction = final_scene_params.get("initial_dialogue_instruction")
|
| 221 |
bot_message = generate_dialogue_with_gemini(history, message, new_affection, stage_name, final_scene_params, instruction=instruction)
|
| 222 |
+
else:
|
| 223 |
final_scene_params["theme"] = new_scene_name
|
| 224 |
bot_message = generate_dialogue_with_gemini(history, message, new_affection, stage_name, final_scene_params)
|
| 225 |
else:
|
| 226 |
bot_message = generate_dialogue_with_gemini(history, message, new_affection, stage_name, final_scene_params)
|
|
|
|
| 227 |
new_history = history + [(message, bot_message)]
|
| 228 |
chat_history.append((message, bot_message))
|
|
|
|
| 229 |
theme_name = final_scene_params.get("theme", "default")
|
|
|
|
| 230 |
background_html = f'<div class="chat-background {theme_name}"></div>'
|
|
|
|
| 231 |
return "", chat_history, new_affection, stage_name, new_affection, new_history, final_scene_params, background_html
|
| 232 |
|
| 233 |
+
# --- UI部分は変更なし ---
|
|
|
|
| 234 |
with gr.Blocks(css="style.css", theme=gr.themes.Soft(primary_hue="rose", secondary_hue="pink")) as demo:
|
| 235 |
scene_state = gr.State(DEFAULT_SCENE_PARAMS)
|
| 236 |
affection_state = gr.State(30)
|
| 237 |
history_state = gr.State([])
|
|
|
|
| 238 |
gr.Markdown("# 麻理チャット")
|
| 239 |
with gr.Row():
|
| 240 |
with gr.Column(scale=2):
|
| 241 |
with gr.Column(elem_id="chat_container"):
|
| 242 |
+
background_display = gr.HTML(f'<div class="chat-background {DEFAULT_SCENE_PARAMS["theme"]}"></div>', elem_id="background_container")
|
| 243 |
+
chatbot = gr.Chatbot(label="麻理との会話", bubble_full_width=False, elem_id="chat_area", show_label=False)
|
| 244 |
+
msg_input = gr.Textbox(label="あなたのメッセージ", placeholder="「水族館はどう?」と聞いた後、「いいね、行こう!」のように返してみてください", scale=5, show_label=False)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 245 |
with gr.Column(scale=1):
|
| 246 |
stage_display = gr.Textbox(label="現在の関係ステージ", interactive=False, value=get_relationship_stage(30))
|
| 247 |
affection_gauge = gr.Slider(minimum=0, maximum=100, label="麻理の好感度", value=30, interactive=False)
|
|
|
|
| 248 |
msg_input.submit(
|
| 249 |
respond,
|
| 250 |
[msg_input, chatbot, affection_state, history_state, scene_state],
|