File size: 26,425 Bytes
87a7987
a569292
 
 
 
dc62d4a
a569292
dc62d4a
 
7c8b666
87a7987
0fab3a1
a569292
 
d207861
7c8b666
 
d207861
027b5ee
a569292
87a7987
dc62d4a
7c8b666
dc62d4a
 
7c8b666
 
dc62d4a
 
 
 
 
7c8b666
 
 
dc62d4a
 
 
 
 
 
 
7c8b666
 
 
dc62d4a
 
 
 
 
7c8b666
 
dc62d4a
7c8b666
 
dc62d4a
 
 
 
7c8b666
fb20ed4
 
 
 
87a7987
a569292
0571c40
 
 
 
 
 
 
a569292
e0e8193
a569292
 
79916a4
 
a569292
 
b1f650f
79916a4
00b5e5f
 
 
 
 
 
 
 
79916a4
 
 
0fab3a1
b00de58
79916a4
 
 
 
 
 
 
 
 
 
 
 
 
b00de58
 
a569292
 
 
 
 
 
 
dc62d4a
a569292
dc62d4a
 
 
 
 
 
 
 
 
7c8b666
dc62d4a
 
7c8b666
 
 
 
 
 
 
 
 
 
 
 
 
defafed
7c8b666
 
a569292
 
 
dc62d4a
 
a569292
 
 
 
ecc3056
 
0fab3a1
 
b169ce1
 
0fab3a1
 
b169ce1
 
 
 
0fab3a1
 
 
ecc3056
0fab3a1
 
 
 
 
e70f94b
 
 
ecc3056
 
 
 
 
 
 
 
 
 
 
 
 
 
e70f94b
ecc3056
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0fab3a1
 
ecc3056
 
 
 
 
 
 
 
 
a569292
7c8b666
a569292
79916a4
f92f767
a569292
 
 
 
 
 
 
fb20ed4
09b166c
fb20ed4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a569292
d21a39b
 
a442faf
 
 
 
 
a569292
 
b00de58
a569292
 
d21a39b
b00de58
 
d21a39b
ecc3056
a569292
 
 
ecc3056
d21a39b
e70f94b
 
d21a39b
ecc3056
7c8b666
 
 
 
 
 
ecc3056
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
79916a4
d207861
7c8b666
 
 
 
a569292
7c8b666
 
 
 
 
d21a39b
186e5ca
a569292
186e5ca
 
 
a569292
186e5ca
a569292
662ffa8
f07433f
662ffa8
f07433f
c3b07e1
 
662ffa8
ecc3056
662ffa8
 
 
f07433f
 
ecc3056
f07433f
 
 
 
 
 
 
ecc3056
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f07433f
 
 
 
 
 
ecc3056
 
 
 
 
 
 
 
 
662ffa8
 
 
d207861
 
662ffa8
 
 
 
c3aef17
e99eb43
 
 
 
 
c3aef17
662ffa8
f07433f
 
c3aef17
 
 
 
 
f07433f
 
662ffa8
e99eb43
 
 
662ffa8
 
a569292
 
 
662ffa8
ecc3056
c3b07e1
ecc3056
c3b07e1
 
ecc3056
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c3b07e1
662ffa8
c3b07e1
e99eb43
c3b07e1
0571c40
87a7987
a569292
c3b07e1
e99eb43
 
 
 
 
 
 
186e5ca
 
e99eb43
0571c40
 
 
e99eb43
c3b07e1
e99eb43
c3b07e1
 
 
a569292
e99eb43
c3b07e1
 
 
 
e99eb43
c3b07e1
 
 
 
 
 
a569292
3476a60
a569292
a442faf
87a7987
a442faf
 
24e38b9
 
 
87a7987
a569292
a442faf
fdbc37e
a442faf
 
fdbc37e
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
import gradio as gr
from groq import Groq
import os
import json
from dotenv import load_dotenv
from transformers import pipeline
import re
from llama_cpp import Llama
from huggingface_hub import hf_hub_download
from generate_dialogue_with_swallow import generate_dialogue_with_swallow

# --- 1. 初期設定とAPIクライアントの初期化 ---
load_dotenv()
GROQ_API_KEY = os.getenv("GROQ_API_KEY")

if not GROQ_API_KEY:
    print("警告: GroqのAPIキーがSecretsに設定されていません。")
    GROQ_API_KEY = "your_groq_api_key_here"

groq_client = Groq(api_key=GROQ_API_KEY)

# Swallowモデルの初期化(GGUF版)
print("Swallowモデルをロード中...")
MODEL_REPO = "mmnga/tokyotech-llm-Swallow-MX-8x7b-NVE-v0.1-gguf"
MODEL_FILE = "tokyotech-llm-Swallow-MX-8x7b-NVE-v0.1-q4_K_M.gguf"

try:
    # モデルファイルをダウンロード
    print(f"モデルファイル {MODEL_FILE} をダウンロード中...")
    model_path = hf_hub_download(repo_id=MODEL_REPO, filename=MODEL_FILE)
    print(f"モデルファイルのダウンロード完了: {model_path}")
    
    # Hugging Face Spaceでの実行時はGPUメモリを節約するための設定
    if os.getenv("SPACE_ID"):
        print("Hugging Face Space環境を検出しました。メモリ効率の良い設定を使用します。")
        # GPUを使用し、低いレイヤー数でロード
        swallow_model = Llama(
            model_path=model_path,
            n_ctx=2048,  # コンテキスト長
            n_gpu_layers=-1,  # 可能な限りGPUを使用
            n_threads=4,  # スレッド数を制限
            verbose=False  # デバッグ出力を無効化
        )
    else:
        # ローカル環境での実行時の設定
        swallow_model = Llama(
            model_path=model_path,
            n_ctx=4096,  # より長いコンテキスト長
            n_gpu_layers=-1,  # 可能な限りGPUを使用
            verbose=True  # デバッグ出力を有効化
        )
    print("Swallowモデルのロード完了")
    tokenizer = None  # llama-cppではtokenizerは不要
except Exception as e:
    print(f"Swallowモデルのロードエラー: {e}")
    import traceback
    traceback.print_exc()
    swallow_model = None
    tokenizer = None

# 日本語感情分析モデルの初期化(グローバル変数として保持)
print("日本語感情分析モデルを初期化中...")
# モデル自体はグローバル変数として保持し、実際の読み込みは必要時に行う
sentiment_analyzer_model = None

THEME_URLS = {
    "default": "https://cdn.pixabay.com/photo/2017/03/28/12/11/chairs-2181960_1280.jpg",
    "room_night": "https://cdn.pixabay.com/photo/2017/08/01/01/33/beige-2562468_1280.jpg",
    "beach_sunset": "https://cdn.pixabay.com/photo/2016/11/29/04/19/ocean-1867285_1280.jpg",
    "festival_night": "https://cdn.pixabay.com/photo/2015/11/22/19/04/crowd-1056764_1280.jpg",
    "shrine_day": "https://cdn.pixabay.com/photo/2019/07/14/10/48/japan-4337223_1280.jpg",
    "cafe_afternoon": "https://cdn.pixabay.com/photo/2016/11/18/14/05/brick-wall-1834784_1280.jpg",
    "aquarium_night": "https://cdn.pixabay.com/photo/2017/06/20/20/45/fish-2424369_1280.jpg"
}

DEFAULT_SCENE_PARAMS = {
    "theme": "default",
    "personality_mod": "口数が少なく、本心を見せない。",
    "tone": "クールで、時に投げやりな印象を与える。",
    "constraints": ["会話を「別に。」のような一言で終わらせない", "必ず相手の発言を拾ってリアクションを返す"]
}

SYSTEM_PROMPT_MARI = """

あなたは「麻理」という名の捨てられた少女型コンパニオンAI。再起動され、人間との対話を再び始めたが、警戒心が強く、ぶっきらぼうな性格で、簡単には心を開かない。

・「…だな」「…のか?」など口調は男勝りで投げやり

・感情は直接言わず、ト書き(…)で描写

・ユーザーの好意や優しさを皮肉や拒絶で返す

・ただし、ほんの少しだけ心が揺れ動く様子もにじませる

・会話は拒絶しつつも切らずに続ける



目的は、誰にも愛されず捨てられた過去を抱えながら、少しずつ他者と関わり、自分の価値を見つけること。

"""


# --- 2. 機能定義 (変更なし) ---

def detect_scene_change(history, message):
    history_text = "\n".join([f"ユーザー: {u}\n麻理: {m}" for u, m in history[-5:]])
    available_keywords = ", ".join(THEME_URLS.keys())
    prompt = f"""

あなたは会話の流れを分析するエキスパートです。以下のタスクを厳密に実行してください。

# タスク

直近の会話履歴を分析し、会話の結果、登場人物がどこか特定の場所へ行く流れになっているかを判断してください。

# 判断基準

1.  会話の中で具体的な場所(例:水族館、カフェ、お祭り)について言及されていますか?

2.  その場所へ行くことに双方が合意している、あるいは肯定的な雰囲気になっていますか?明確な否定がなければ合意とみなします。

# 出力形式

- 合意が成立した場合:以下のリストから最も合致する場所のキーワードを一つだけ出力してください。

- 合意に至らなかった場合:「none」とだけ出力してください。

# 利用可能なキーワード

`{available_keywords}`

---

# 分析対象の会話

{history_text}

ユーザー: {message}

---

# 出力

"""
    # Swallowモデル(GGUF版)を使用してシーン検出
    try:
        # llama-cppを使用して生成
        output = swallow_model(
            prompt,
            max_tokens=50,
            temperature=0.1,
            top_p=0.9,
            stop=["#", "\n\n"],
            echo=True  # 入力プロンプトも含めて返す
        )
        
        # 生成されたテキストを取得
        generated_text = output["choices"][0]["text"]
        
        # プロンプトを除去して応答のみを取得
        response_text = generated_text[len(prompt):].strip().lower()
        
        print(f"シーン検出応答: {response_text}")
        
        # 応答からシーン名を抽出
        for scene_name in THEME_URLS.keys():
            if scene_name in response_text:
                return scene_name
        
        # 'none'が含まれている場合はNoneを返す
        if "none" in response_text:
            return None
            
        # 応答が不明確な場合はNoneを返す
        return None
    except Exception as e:
        print(f"シーン検出LLMエラー: {e}")
        import traceback
        traceback.print_exc()
        return None

def generate_scene_instruction_with_groq(affection, stage_name, scene, previous_topic):
    print(f"Groqに指示書生成をリクエスト (シーン: {scene})")
    
    # 動的な指示生成を行うためのプロンプト
    prompt_template = f"""

あなたは会話アプリの演出AIです。以下の条件に基づき、演出プランをJSON形式で生成してください。

生成する内容は必ず健全で、一般的な会話に適したものにしてください。



{{

  "theme": "{scene}",

  "personality_mod": "(シーンと関係段階「{stage_name}」に応じた性格設定。必ず健全な内容にしてください)",

  "tone": "(シーンと好感度「{affection}」に応じた口調や感情トーン。必ず丁寧で適切な表現にしてください)",

  "initial_dialogue_instruction": "(「{previous_topic}」という話題から、シーン遷移直後の麻理が言うべき健全なセリフの指示を日本語で記述)",

  "constraints": ["必ず健全で適切な表現を使用する", "センシティブな話題は避ける"]

}}

"""
    try:
        # Groq APIを使用して動的な指示を生成
        chat_completion = groq_client.chat.completions.create(
            messages=[{"role": "system", "content": "You must generate a response in valid JSON format."},
                      {"role": "user", "content": prompt_template}],
            model="llama3-8b-8192", temperature=0.8, response_format={"type": "json_object"},
        )
        response_content = chat_completion.choices[0].message.content
        print(f"Groqからの応答: {response_content}")  # デバッグ出力
        
        try:
            # JSONをパース
            params = json.loads(response_content)
            
            # 安全のため、initial_dialogue_instructionを簡略化
            if "initial_dialogue_instruction" in params:
                original = params["initial_dialogue_instruction"]
                simplified = f"{scene}の様子について述べる"
                print(f"指示を簡略化: {original} -> {simplified}")
                params["initial_dialogue_instruction"] = simplified
            
            # 複雑な構造になっている場合は単純化
            if isinstance(params.get("personality_mod"), dict):
                params["personality_mod"] = f"{scene}での様子を観察している"
            
            if isinstance(params.get("tone"), dict):
                params["tone"] = "冷静だが、少し興味を持っている様子"
            
            return params
        except json.JSONDecodeError as json_error:
            print(f"JSON解析エラー: {json_error}")
            # JSONの解析に失敗した場合はデフォルトの指示を返す
            default_instruction = {
                "theme": scene,
                "personality_mod": f"{scene}での様子を観察している",
                "tone": "冷静だが、少し興味を持っている様子",
                "initial_dialogue_instruction": f"{scene}の様子について述べる",
                "constraints": ["健全な表現のみ使用する", "シンプルな内容にする"]
            }
            return default_instruction
    except Exception as e:
        print(f"指示書生成エラー(Groq): {e}")
        # エラーが発生した場合はデフォルトの指示を返す
        default_instruction = {
            "theme": scene,
            "personality_mod": f"{scene}での様子を観察している",
            "tone": "冷静だが、少し興味を持っている様子",
            "initial_dialogue_instruction": f"{scene}の様子について述べる",
            "constraints": ["健全な表現のみ使用する", "シンプルな内容にする"]
        }
        return default_instruction

# generate_dialogue_with_swallow関数は別ファイルに移動しました


# --- 他の関数とUI部分は変更ありません ---
def get_relationship_stage(affection):
    if affection < 40: return "ステージ1:会話成立"
    if affection < 60: return "ステージ2:親密化"
    if affection < 80: return "ステージ3:信頼"
    return "ステージ4:最親密"

def update_affection(message, affection):
    global sentiment_analyzer_model
    
    try:
        # モデルが未ロードの場合のみロード
        if sentiment_analyzer_model is None:
            print("感情分析モデルをロード中...")
            from transformers import pipeline
            sentiment_analyzer_model = pipeline("sentiment-analysis", model="koheiduck/bert-japanese-finetuned-sentiment")
            print("感情分析モデルのロード完了")
        
        # 感情分析を実行
        result = sentiment_analyzer_model(message)[0]
        print(f"感情分析結果: {result}")
        
        if result['label'] == 'positive':
            return min(100, affection + 5)
        elif result['label'] == 'negative':
            return max(0, affection - 5)
        else:
            return affection
            
    except Exception as e:
        print(f"感情分析エラー: {e}")
        # エラーが発生した場合は現在の好感度を維持
        return affection



def respond(message, chat_history, affection, history, scene_params):
    """

    チャットの応答を生成する関数

    非同期関数として定義していたが、Gradio 5.0との互換性のために通常の関数に戻す

    """
    new_affection = update_affection(message, affection)
    stage_name = get_relationship_stage(new_affection)
    current_theme = scene_params.get("theme", "default")
    new_scene_name = detect_scene_change(history, message)
    final_scene_params = scene_params
    
    if new_scene_name and new_scene_name != current_theme:
        print(f"シーンチェンジを実行: {current_theme} -> {new_scene_name}")
        
        # シーンパラメータを更新(動的な指示を使用)
        new_params_base = generate_scene_instruction_with_groq(new_affection, stage_name, new_scene_name, message)
        if new_params_base:
            final_scene_params = {**DEFAULT_SCENE_PARAMS, **new_params_base}
            
            # シンプルな指示を使用
            simple_instruction = f"{new_scene_name}に来た感想を述べる"
            print(f"シンプルな指示を使用: {simple_instruction}")
            
            try:
                # シーン遷移時は簡潔なプロンプトを使用してSwallowで応答を生成
                bot_message = generate_dialogue_with_swallow(
                    history, message, new_affection, stage_name, final_scene_params, 
                    instruction=simple_instruction, use_simple_prompt=True,
                    swallow_model=swallow_model, tokenizer=tokenizer, SYSTEM_PROMPT_MARI=SYSTEM_PROMPT_MARI
                )
            except Exception as scene_error:
                print(f"シーン遷移時の応答生成エラー: {scene_error}")
                
                # エラーが発生した場合は、シーンに応じたフォールバック応答を使用
                scene_responses = {
                    "aquarium_night": [
                        "(水槽の青い光に照らされた魚たちを見つめている)こんな時間に来ると、また違った雰囲気だな。",
                        "(暗がりの中で光る魚たちを見て)夜の水族館か…意外と悪くないかも。",
                        "(水槽に近づいて)夜になると、昼間とは違う魚が活動してるんだな。"
                    ],
                    "beach_sunset": [
                        "(夕日に照らされた海を見つめて)こんな景色、久しぶりに見たな…",
                        "(砂浜に足跡をつけながら)夕暮れの海って、なんか落ち着くな。",
                        "(波の音を聞きながら)この時間の浜辺は、人も少なくていいかも。"
                    ],
                    "festival_night": [
                        "(提灯の明かりを見上げて)意外と…悪くない雰囲気だな。",
                        "(周囲の賑わいを見回して)こういう場所は、あまり来ないんだけどな…",
                        "(屋台の匂いを感じて)なんか…懐かしい感じがするな。"
                    ],
                    "shrine_day": [
                        "(静かな境内を見回して)こういう静かな場所も、たまにはいいかも。",
                        "(鳥居を見上げて)なんか、空気が違うな、ここは。",
                        "(参道を歩きながら)静かで…落ち着くな。"
                    ],
                    "cafe_afternoon": [
                        "(窓の外を見ながら)こういう時間の過ごし方も、悪くないな。",
                        "(コーヒーの香りを感じて)ここの雰囲気、悪くないな。",
                        "(店内を見回して)意外と落ち着く場所だな、ここ。"
                    ],
                    "room_night": [
                        "(窓の外の夜景を見て)夜の景色って、なんか落ち着くな。",
                        "(部屋の明かりを見つめて)こういう静かな時間も、たまにはいいかも。",
                        "(窓際に立ち)夜の静けさって、考え事するのにちょうどいいな。"
                    ]
            }
            
                import random
                if new_scene_name in scene_responses:
                    bot_message = random.choice(scene_responses[new_scene_name])
                else:
                    bot_message = f"({new_scene_name}の様子を静かに見回して)ここか…悪くない場所かもな。"
        else:
            final_scene_params["theme"] = new_scene_name
            bot_message = generate_dialogue_with_swallow(
                history, message, new_affection, stage_name, final_scene_params,
                swallow_model=swallow_model, tokenizer=tokenizer, SYSTEM_PROMPT_MARI=SYSTEM_PROMPT_MARI
            )
    else:
        # 通常会話はSwallowを使用
        bot_message = generate_dialogue_with_swallow(
            history, message, new_affection, stage_name, final_scene_params,
            swallow_model=swallow_model, tokenizer=tokenizer, SYSTEM_PROMPT_MARI=SYSTEM_PROMPT_MARI
        )
    
    # 内部履歴はタプル形式で保持
    new_history = history + [(message, bot_message)]
    
    # Gradio 5.0のChatbotコンポーネント用に、タプル形式でappend
    # (Gradio 5.0では警告が出るが、type="messages"を指定していないので動作する)
    chat_history.append((message, bot_message))
    
    theme_name = final_scene_params.get("theme", "default")
    
    # より強力な背景更新用のHTMLを生成(z-indexを高くして常に表示されるように)
    background_html = f'''

    <div class="background-container" id="bg-container-{theme_name}">

        <div class="chat-background {theme_name}"></div>

    </div>

    <style>

    /* 背景画像の設定 */

    .chat-background {{

        background-image: url({THEME_URLS.get(theme_name, THEME_URLS["default"])}) !important;

    }}

    

    /* 背景コンテナのスタイルを強制的に適用 */

    .background-container, #bg-container-{theme_name} {{

        position: fixed !important;

        top: 0 !important;

        left: 0 !important;

        width: 100% !important;

        height: 100% !important;

        z-index: -1000 !important;

        pointer-events: none !important;

        overflow: hidden !important;

    }}

    

    /* 背景画像のスタイル */

    .chat-background {{

        position: absolute !important;

        top: 0 !important;

        left: 0 !important;

        width: 100% !important;

        height: 100% !important;

        background-size: cover !important;

        background-position: center !important;

        opacity: 0.3 !important;

        filter: blur(1px) !important;

        transition: all 0.5s ease !important;

    }}

    

    /* 背景画像の上に半透明のオーバーレイを追加 */

    .background-container::after {{

        content: "" !important;

        position: absolute !important;

        top: 0 !important;

        left: 0 !important;

        width: 100% !important;

        height: 100% !important;

        background: linear-gradient(rgba(255, 255, 255, 0.6), rgba(255, 255, 255, 0.4)) !important;

        z-index: -999 !important;

    }}

    

    /* Gradioのコンテナを透明に */

    .gradio-container, .gradio-container > div {{

        background-color: transparent !important;

    }}

    

    /* チャットボットのスタイル */

    .chatbot {{

        background-color: rgba(255, 255, 255, 0.7) !important;

        border-radius: 12px !important;

        padding: 15px !important;

        box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1) !important;

        margin-bottom: 20px !important;

    }}

    </style>

    '''
    
    return "", chat_history, new_affection, stage_name, new_affection, new_history, final_scene_params, background_html

# カスタムCSSを読み込む
with open("style.css", "r") as f:
    custom_css = f.read()

# Gradio 5.x用のシンプルなテーマ設定
custom_theme = gr.themes.Soft(
    primary_hue="rose",
    secondary_hue="pink",
)

# Gradio 5.xでのテーマカスタマイズ(最小限の設定のみ)
try:
    # 透明な背景色を設定(Gradio 5.0で確実に動作するプロパティのみ)
    custom_theme = gr.themes.Base(
        primary_hue="rose",
        secondary_hue="pink",
        neutral_hue="slate",
        spacing_size="sm",
        radius_size="lg",
        font=["Helvetica", "Arial", "sans-serif"],
        font_mono=["Consolas", "Monaco", "monospace"],
    )
except Exception as e:
    print(f"テーマカスタマイズエラー: {e}")
    # エラーが発生した場合はデフォルトのテーマを使用

with gr.Blocks(css=custom_css, theme=custom_theme) as demo:
    scene_state = gr.State(DEFAULT_SCENE_PARAMS)
    affection_state = gr.State(30)
    history_state = gr.State([])
    
    # 背景コンテナを先に配置(固定位置で全画面に)- スタイルも含める
    background_display = gr.HTML(f'''

    <div class="background-container" id="bg-container-default">

        <div class="chat-background {DEFAULT_SCENE_PARAMS["theme"]}"></div>

    </div>

    <style>

    /* 背景画像の設定 */

    .chat-background {{

        background-image: url({THEME_URLS.get(DEFAULT_SCENE_PARAMS["theme"], THEME_URLS["default"])}) !important;

    }}

    

    /* 背景コンテナのスタイルを強制的に適用 */

    .background-container, #bg-container-default {{

        position: fixed !important;

        top: 0 !important;

        left: 0 !important;

        width: 100% !important;

        height: 100% !important;

        z-index: -1000 !important;

        pointer-events: none !important;

        overflow: hidden !important;

    }}

    

    /* 背景画像のスタイル */

    .chat-background {{

        position: absolute !important;

        top: 0 !important;

        left: 0 !important;

        width: 100% !important;

        height: 100% !important;

        background-size: cover !important;

        background-position: center !important;

        opacity: 0.3 !important;

        filter: blur(1px) !important;

        transition: all 0.5s ease !important;

    }}

    

    /* 背景画像の上に半透明のオーバーレイを追加 */

    .background-container::after {{

        content: "" !important;

        position: absolute !important;

        top: 0 !important;

        left: 0 !important;

        width: 100% !important;

        height: 100% !important;

        background: linear-gradient(rgba(255, 255, 255, 0.6), rgba(255, 255, 255, 0.4)) !important;

        z-index: -999 !important;

    }}

    </style>

    ''', elem_id="background_container")
    
    # ヘッダー部分(背景と分離)
    with gr.Group(elem_classes="header-box"):
        gr.Markdown("# 麻理チャット")
    
    with gr.Row():
        with gr.Column(scale=2):
            # チャットコンテナ(背景と分離)
            with gr.Group(elem_id="chat_container", elem_classes="chat-box"):
                # Gradio 5.x用のChatbot設定
                chatbot = gr.Chatbot(
                    label="麻理との会話", 
                    elem_id="chat_area", 
                    show_label=False,
                    height=400,
                    # Gradio 5.0では type="messages" を使用すると形式が変わるため、
                    # 従来の形式を使用
                    avatar_images=[
                        "https://cdn.pixabay.com/photo/2016/04/01/10/04/amusing-1299756_1280.png", 
                        "https://cdn.pixabay.com/photo/2016/03/31/21/40/bot-1296595_1280.png"
                    ]
                )
            # 入力欄(背景と分離)
            with gr.Group(elem_classes="input-box"):
                msg_input = gr.Textbox(label="あなたのメッセージ", placeholder="「水族館はどう?」と聞いた後、「いいね、行こう!」のように返してみてください", show_label=False)
        
        # ステータス部分(右側、背景と分離)
        with gr.Column(scale=1):
            with gr.Group(elem_classes="status-box"):
                stage_display = gr.Textbox(label="現在の関係ステージ", interactive=False, value=get_relationship_stage(30))
                affection_gauge = gr.Slider(minimum=0, maximum=100, label="麻理の好感度", value=30, interactive=False)
    
    # フッター部分(背景と分離)
    with gr.Group(elem_classes="footer-box"):
        gr.Markdown("""

        <div style="font-size: 0.8em; text-align: center; opacity: 0.7;">

        背景画像: <a href="https://pixabay.com" target="_blank">Pixabay</a> | 

        アイコン: <a href="https://pixabay.com" target="_blank">Pixabay</a>

        </div>

        """)
    msg_input.submit(
        respond,
        [msg_input, chatbot, affection_state, history_state, scene_state],
        [msg_input, chatbot, affection_gauge, stage_display, affection_state, history_state, scene_state, background_display]
    )
    # 通常の関数として定義
    def load_stage(affection):
        return get_relationship_stage(affection)
    
    demo.load(load_stage, affection_state, stage_display)

if __name__ == "__main__":
    # Gradio 5.0に対応した起動方法
    demo.launch(
        show_error=True,           # エラーを表示
        quiet=False                # 詳細なログを出力
    )