sirochild commited on
Commit
d4a1d39
·
verified ·
1 Parent(s): 699caa1

Upload 2 files

Browse files
Files changed (2) hide show
  1. app.py +39 -190
  2. style.css +44 -9
app.py CHANGED
@@ -1,201 +1,50 @@
1
  import gradio as gr
2
- import google.generativeai as genai
3
- from groq import Groq
4
- import os
5
- import json
6
- from dotenv import load_dotenv
7
- from transformers import pipeline
8
- import re
9
 
10
- # --- 1. 初期設定とAPIクライアントの初期化 ---
11
- load_dotenv()
12
- GEMINI_API_KEY = os.getenv("GEMINI_API_KEY")
13
- GROQ_API_KEY = os.getenv("GROQ_API_KEY")
14
- if not GEMINI_API_KEY or not GROQ_API_KEY:
15
- raise ValueError("GEMINI_API_KEY と GROQ_API_KEY をSecretsに設定してください。")
16
 
17
- genai.configure(api_key=GEMINI_API_KEY)
18
- gemini_model = genai.GenerativeModel('gemini-2.5-flash-lite')
19
- groq_client = Groq(api_key=GROQ_API_KEY)
 
 
20
 
21
- print("日本語感情分析モデルをロード中...")
22
- try:
23
- sentiment_analyzer = pipeline("sentiment-analysis", model="koheiduck/bert-japanese-finetuned-sentiment")
24
- print("モデルのロード完了。")
25
- except Exception as e:
26
- sentiment_analyzer = None
27
- print(f"モデルのロードエラー: {e}")
 
 
28
 
29
- THEME_URLS = {
30
- "default": "https://i.ibb.co/XzP6K2Y/room-day.png",
31
- "room_night": "https://i.ibb.co/d5m821p/room-night.png",
32
- "beach_sunset": "https://i.ibb.co/Q9r56s4/beach-sunset.png",
33
- "festival_night": "https://i.ibb.co/3zdJ6Bw/festival-night.png",
34
- "shrine_day": "https://i.ibb.co/L51Jd3x/shrine-day.png",
35
- "cafe_afternoon": "https://i.ibb.co/yQxG4vs/cafe-afternoon.png",
36
- "aquarium_night": "https://i.ibb.co/dK5r5rc/aquarium-night.png"
37
- }
38
 
39
- DEFAULT_SCENE_PARAMS = {
40
- "theme": "default",
41
- "personality_mod": "ぶっきらぼうで、あまり自分から話さないが、ユーザーの発言にはしっかり反応する。根は優しく、心のどこかでは相手との対話に興味を持っている。",
42
- "tone": "素っ気ないが、完全に突き放すわけではない。語尾に含みを持たせたり、質問を返すことで会話を促す。",
43
- "constraints": ["会話を「別に。」のような一言で終わらせない", "必ず相手の発言を拾ってリアクションを返す"]
44
- }
45
 
46
- # --- 2. 機能定義(省略なし) ---
47
-
48
- def detect_scene_change(history, message):
49
- history_text = "\n".join([f"ユーザー: {u}\n麻理: {m}" for u, m in history[-4:]])
50
- prompt = f"""
51
- あなたは会話の流れを分析するエキスパートです。以下のタスクを厳密に実行してください。
52
- # タスク
53
- 直近の会話履歴と最後のユーザー発言を分析し、**会話の結果として登場人物がどこか特定の場所へ行くことに合意したか**を判断してください。
54
- # 判断基準
55
- 1. 会話の中で具体的な場所が提案されているか?
56
- 2. 最後のユーザー発言が、その提案に対する明確な同意(例:「行こう」「いいね」「そうしよう」など)を示しているか?
57
- # 出力形式
58
- - 合意が成立した場合:その場所の英語キーワード(例: "aquarium_night")を一つだけ出力。
59
- - 合意に至らなかった場合:「none」とだけ出力。
60
- ---
61
- # 分析対象の会話
62
- {history_text}
63
- ユーザー: {message}
64
- ---
65
- # 出力
66
- """
67
- try:
68
- response = gemini_model.generate_content(prompt, generation_config={"temperature": 0.0})
69
- scene_name = response.text.strip().lower()
70
- if scene_name != "none" and re.match(r'^[a-z0-9_]+$', scene_name):
71
- return scene_name
72
- return None
73
- except Exception as e:
74
- print(f"シーン検出LLMエラー: {e}")
75
- return None
76
-
77
- def generate_scene_instruction_with_groq(affection, stage_name, scene, previous_topic):
78
- print(f"Groqに指示書生成をリクエスト (シーン: {scene})")
79
- prompt_template = f"""
80
- あなたは会話アプリの演出AIです。以下の条件に基づき、演出プランをJSON形式で生成してください。
81
- {{
82
- "theme": "{scene}",
83
- "personality_mod": "(シーンと関係段階「{stage_name}」に応じた性格設定)",
84
- "tone": "(シーンと好感度「{affection}」に応じた口調や感情トーン)",
85
- "initial_dialogue_instruction": "(「{previous_topic}」という話題から、シーン遷移直後の麻理が言うべきセリフの指示を日本語で記述)",
86
- "constraints": ["(出力時の制約1)", "(制約2)"]
87
- }}
88
- """
89
- try:
90
- chat_completion = groq_client.chat.completions.create(
91
- messages=[{"role": "system", "content": "You must generate a response in valid JSON format."},
92
- {"role": "user", "content": prompt_template}],
93
- model="llama3-8b-8192", temperature=0.8, response_format={"type": "json_object"},
94
- )
95
- params = json.loads(chat_completion.choices[0].message.content)
96
- return params
97
- except Exception as e:
98
- print(f"指示書生成エラー(Groq): {e}")
99
- return None
100
-
101
- def generate_dialogue_with_gemini(history, message, affection, stage_name, scene_params, instruction=None):
102
- history_text = "\n".join([f"ユーザー: {u}\n麻理: {m}" for u, m in history])
103
- task_prompt = f"指示: {instruction}" if instruction else f"ユーザー: {message}"
104
- system_prompt = f"""
105
- あなたは日本語で会話するAIキャラクター「麻理」です。以下の設定とタスクを厳密に守ってください。
106
- # 設定
107
- - 基本人格: ストレートで媚びない。ぶっきらぼうだが根は優しい。
108
- - 現在の好感度: {affection}
109
- - 現在の関係ステージ: {stage_name}
110
- - 性格(ロールプレイ): {scene_params.get("personality_mod", DEFAULT_SCENE_PARAMS["personality_mod"])}
111
- - 話し方のトーン: {scene_params.get("tone", DEFAULT_SCENE_PARAMS["tone"])}
112
- - 制約事項: {", ".join(scene_params.get("constraints", DEFAULT_SCENE_PARAMS["constraints"]))}
113
- # 会話履歴
114
- {history_text}
115
- ---
116
- # タスク
117
- {task_prompt}
118
- 麻理:
119
- """
120
- print(f"Geminiに応答生成をリクエストします (モード: {'シーン遷移' if instruction else '通常会話'})")
121
- try:
122
- generation_config = genai.types.GenerationConfig(max_output_tokens=200, temperature=0.95)
123
- response = gemini_model.generate_content(system_prompt, generation_config=generation_config)
124
- return response.text
125
- except Exception as e:
126
- print(f"応答生成エラー(Gemini): {e}")
127
- return "(ごめんなさい、ちょっと考えがまとまらない……)"
128
-
129
- def get_relationship_stage(affection):
130
- if affection < 40: return "ステージ1:会話成立"
131
- if affection < 60: return "ステージ2:親密化"
132
- if affection < 80: return "ステージ3:信頼"
133
- return "ステージ4:最親密"
134
-
135
- def update_affection(message, affection):
136
- if not sentiment_analyzer: return affection
137
- try:
138
- result = sentiment_analyzer(message)[0]
139
- if result['label'] == 'positive': return min(100, affection + 5)
140
- if result['label'] == 'negative': return max(0, affection - 5)
141
- except Exception:
142
- return affection
143
- return affection
144
-
145
- # --- メイン応答処理 ---
146
-
147
- def respond(message, chat_history, affection, history, scene_params):
148
- new_affection = update_affection(message, affection)
149
- stage_name = get_relationship_stage(new_affection)
150
- new_scene_name = detect_scene_change(history, message)
151
-
152
- final_scene_params = scene_params
153
-
154
- if new_scene_name:
155
- new_params_base = generate_scene_instruction_with_groq(new_affection, stage_name, new_scene_name, message)
156
- if new_params_base:
157
- final_scene_params = {**DEFAULT_SCENE_PARAMS, **new_params_base}
158
- instruction = final_scene_params.get("initial_dialogue_instruction")
159
- bot_message = generate_dialogue_with_gemini(history, message, new_affection, stage_name, final_scene_params, instruction=instruction)
160
- else:
161
- bot_message = generate_dialogue_with_gemini(history, message, new_affection, stage_name, final_scene_params)
162
- else:
163
- bot_message = generate_dialogue_with_gemini(history, message, new_affection, stage_name, final_scene_params)
164
-
165
- new_history = history + [(message, bot_message)]
166
- chat_history.append((message, bot_message))
167
- theme_name = final_scene_params.get("theme", "default")
168
- background_url = THEME_URLS.get(theme_name, THEME_URLS["default"])
169
-
170
- # js_script は削除し、代わりに以下のHTMLを使う:
171
- current_theme_class = f"bg-{theme_name}"
172
- chatbot.elem_classes = [f"chat-area", current_theme_class]
173
-
174
- html_code = f"<div class='chat-area bg-{theme_name}'></div>"
175
- return "", chat_history, new_affection, stage_name, new_affection, new_history, final_scene_params,html_code
176
-
177
- # --- Gradio UI ---
178
-
179
- with gr.Blocks(css="style.css", theme=gr.themes.Soft(primary_hue="rose", secondary_hue="pink")) as demo:
180
- scene_state = gr.State(DEFAULT_SCENE_PARAMS)
181
- affection_state = gr.State(30)
182
- history_state = gr.State([])
183
-
184
- gr.Markdown("# 麻理チャット")
185
  with gr.Row():
186
- with gr.Column(scale=2):
187
- chatbot = gr.Chatbot(label="麻理との会話", height=600, bubble_full_width=False, elem_id="chat_area")
188
- msg_input = gr.Textbox(label="あなたのメッセージ", placeholder="「水族館はどう?」と聞いた後、「いいね、行こう!」のように返してみてください", scale=5)
189
- with gr.Column(scale=1):
190
- stage_display = gr.Textbox(label="現在の関係ステージ", interactive=False)
191
- affection_gauge = gr.Slider(minimum=0, maximum=100, label="麻理の好感度", value=30, interactive=False)
192
- js_injector = gr.HTML(elem_classes="hidden")
193
-
194
- msg_input.submit(
 
 
 
 
 
 
195
  respond,
196
- [msg_input, chatbot, affection_state, history_state, scene_state],
197
- [msg_input, chatbot, affection_gauge, stage_display, affection_state, history_state, scene_state, js_injector]
198
  )
199
 
200
- if __name__ == "__main__":
201
- demo.launch()
 
1
  import gradio as gr
 
 
 
 
 
 
 
2
 
3
+ # 初期テーマ
4
+ INITIAL_THEME = "aquarium_day"
 
 
 
 
5
 
6
+ # 応答関数
7
+ def respond(user_input, chat_history, affinity_score, stage_text, state1, state2, state3):
8
+ # 応答(仮定)
9
+ bot_response = "水族館? 別に行きたいわけじゃないけど、まあ、いいんじゃない?"
10
+ chat_history.append((user_input, bot_response))
11
 
12
+ # テーマ設定
13
+ theme_name = INITIAL_THEME
14
+ theme_info_dict = {
15
+ "theme": theme_name,
16
+ "personality_mod": {"sincerity": 0.5, "playfulness": 0.3, "curiosity": 0.2},
17
+ "tone": {"warmth": 0.6, "formality": 0.4, "liveliness": 0.7},
18
+ "constraints": ["use present tense", "avoid long sentences"],
19
+ "initial_dialogue_instruction": "Hey, shall we go to the aquarium today?"
20
+ }
21
 
22
+ # 背景用HTML(クラスにテーマ名を反映)
23
+ html_code = f"<div class='chat-background {theme_name}'></div>"
 
 
 
 
 
 
 
24
 
25
+ return "", chat_history, affinity_score, "ステージ1:会話成立", affinity_score, chat_history, theme_info_dict, html_code
 
 
 
 
 
26
 
27
+ # Gradio UI構築
28
+ with gr.Blocks(css="style.css") as demo:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
  with gr.Row():
30
+ with gr.Column():
31
+ user_input = gr.Textbox(label="あなたの発言")
32
+ submit_btn = gr.Button("送信")
33
+ with gr.Column():
34
+ chatbot = gr.Chatbot()
35
+ html_output = gr.HTML() # 背景用
36
+
37
+ # 状態・スライダーなど
38
+ affinity_score = gr.Slider(minimum=0, maximum=100, value=30, label="親密度")
39
+ stage_text = gr.Textbox(value="ステージ1:会話成立", label="ステージ")
40
+ state1 = gr.State()
41
+ state2 = gr.State()
42
+ state3 = gr.State()
43
+
44
+ submit_btn.click(
45
  respond,
46
+ inputs=[user_input, chatbot, affinity_score, stage_text, state1, state2, state3],
47
+ outputs=[user_input, chatbot, affinity_score, stage_text, state1, state2, state3, html_output]
48
  )
49
 
50
+ demo.launch()
 
style.css CHANGED
@@ -1,14 +1,49 @@
1
- .chat-area {
 
 
 
 
 
 
 
2
  height: 100%;
 
3
  background-size: cover;
4
  background-position: center;
5
- transition: background-image 1s ease-in-out;
 
6
  }
7
 
8
- .bg-default { background-image: url("https://i.ibb.co/XzP6K2Y/room-day.png"); }
9
- .bg-room_night { background-image: url("https://i.ibb.co/d5m821p/room-night.png"); }
10
- .bg-beach_sunset { background-image: url("https://i.ibb.co/Q9r56s4/beach-sunset.png"); }
11
- .bg-festival_night { background-image: url("https://i.ibb.co/3zdJ6Bw/festival-night.png"); }
12
- .bg-shrine_day { background-image: url("https://i.ibb.co/L51Jd3x/shrine-day.png"); }
13
- .bg-cafe_afternoon { background-image: url("https://i.ibb.co/yQxG4vs/cafe-afternoon.png"); }
14
- .bg-aquarium_night { background-image: url("https://i.ibb.co/dK5r5rc/aquarium-night.png"); }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* ========================
2
+ 全体背景用のベースクラス
3
+ ======================== */
4
+ .chat-background {
5
+ position: absolute;
6
+ top: 0;
7
+ left: 0;
8
+ width: 100%;
9
  height: 100%;
10
+ z-index: -1; /* UIの後ろに配置 */
11
  background-size: cover;
12
  background-position: center;
13
+ opacity: 0.3; /* 少し透けるように */
14
+ pointer-events: none;
15
  }
16
 
17
+ /* ========================
18
+ テーマ別背景画像
19
+ ======================== */
20
+ .aquarium_day {
21
+ background-image: url("https://cdn.pixabay.com/photo/2016/11/29/02/02/aquarium-1867283_1280.jpg");
22
+ }
23
+
24
+ .aquarium_night {
25
+ background-image: url("https://cdn.pixabay.com/photo/2017/06/20/20/45/fish-2424369_1280.jpg");
26
+ }
27
+
28
+ .forest_day {
29
+ background-image: url("https://cdn.pixabay.com/photo/2015/11/19/18/36/forest-1054795_1280.jpg");
30
+ }
31
+
32
+ .forest_night {
33
+ background-image: url("https://cdn.pixabay.com/photo/2017/10/31/18/47/fog-2900424_1280.jpg");
34
+ }
35
+
36
+ /* ========================
37
+ チャットエリアなど(必要に応じて)
38
+ ======================== */
39
+ .gradio-container {
40
+ position: relative; /* 背景をabsoluteで置くために */
41
+ overflow: hidden;
42
+ }
43
+
44
+ .chatbot {
45
+ background-color: rgba(255, 255, 255, 0.8);
46
+ backdrop-filter: blur(4px);
47
+ border-radius: 12px;
48
+ padding: 10px;
49
+ }