aicodingfun commited on
Commit
7eacbe0
·
verified ·
1 Parent(s): a9304c7

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +646 -0
app.py ADDED
@@ -0,0 +1,646 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import json
3
+ import random
4
+ import tempfile
5
+ from datetime import datetime
6
+ from typing import Dict, Tuple, Optional
7
+ from PIL import Image
8
+ import io
9
+
10
+ from google import genai
11
+ from google.genai import types
12
+ import os
13
+ import gtts
14
+
15
+ API_KEY = os.environ.get("GOOGLE_API_KEY")
16
+ client = genai.Client(api_key=API_KEY)
17
+
18
+ # Pet state storage
19
+ pet_state = {
20
+ "name": "",
21
+ "species": "",
22
+ "personality": "",
23
+ "appearance": "",
24
+ "mood": "快樂", # Mood is stored in Chinese, mapped to English for image generation
25
+ "hunger": 80,
26
+ "energy": 90,
27
+ "experience": 0,
28
+ "level": 1,
29
+ "interaction_history": [],
30
+ "battle_stats": {"wins": 0, "losses": 0}
31
+ }
32
+
33
+ def generate_pet_description(custom_input: str = "") -> Dict:
34
+ """Generate pet basic settings using Gemini 2.0 Flash"""
35
+ try:
36
+ if custom_input.strip():
37
+ prompt = f"""
38
+ 請根據使用者描述創建一隻寶可夢風格的寵物:{custom_input}
39
+
40
+ 請以 JSON 格式返回:
41
+ {{
42
+ "name": "寵物名稱",
43
+ "species": "物種類型",
44
+ "personality": "性格描述",
45
+ "appearance": "外觀特徵描述"
46
+ }}
47
+ """
48
+ else:
49
+ prompt = """
50
+ 請隨機創建一隻可愛的寶可夢風格寵物。
51
+
52
+ 請以 JSON 格式返回:
53
+ {
54
+ "name": "寵物名稱",
55
+ "species": "物種類型",
56
+ "personality": "性格描述",
57
+ "appearance": "外觀特徵描述"
58
+ }
59
+
60
+ 要求:名稱要可愛,性格要有趣,外觀要生動形象。
61
+ """
62
+
63
+ response = client.models.generate_content(
64
+ model="gemini-2.5-flash-preview-04-17",
65
+ contents=prompt
66
+ )
67
+
68
+ response_text = response.text.strip()
69
+ if response_text.startswith('```json'):
70
+ response_text = response_text[7:-3]
71
+ elif response_text.startswith('```'):
72
+ response_text = response_text[3:-3]
73
+
74
+ pet_data = json.loads(response_text)
75
+ return pet_data
76
+
77
+ except Exception as e:
78
+ print(f"Error generating pet description: {e}")
79
+ return {
80
+ "name": "皮卡丘",
81
+ "species": "電氣鼠寶可夢",
82
+ "personality": "活潑好動,喜歡交朋友",
83
+ "appearance": "黃色毛髮,紅色臉頰,長長尾巴"
84
+ }
85
+
86
+ def generate_pet_image(description: str, mood: str = "快樂", context_desc: str = "") -> Optional[Image.Image]:
87
+ """Generate pet image using Gemini 2.0 Flash, including context"""
88
+ try:
89
+ mood_map = {
90
+ "快樂": "happy and cheerful",
91
+ "飢餓": "hungry and tired",
92
+ "興奮": "excited and energetic",
93
+ "疲憊": "sleepy and tired",
94
+ "生氣": "angry and upset",
95
+ "滿足": "satisfied and content"
96
+ }
97
+
98
+ mood_desc = mood_map.get(mood, "happy")
99
+
100
+ full_description = f"{description}"
101
+ if context_desc:
102
+ full_description += f", {context_desc}"
103
+
104
+ image_prompt = f"""
105
+ 請繪製一張可愛的寶可夢風格寵物插圖,細節如下:
106
+ - 描述: {full_description}
107
+ - 情緒: {mood_desc}
108
+ - 風格: 色彩鮮豔、可愛友善的動漫風格,呈現寵物全身
109
+ - 背景: 簡單、柔和的色彩。優先考慮透明背景,若不可行則使用簡單背景
110
+ - 品質: 高解析度、細節豐富
111
+ - 長寬比:正方形 (1:1)
112
+ - **請勿**在圖片中加入任何文字。
113
+ """
114
+
115
+ print(f"Generating image with prompt: {image_prompt}")
116
+
117
+ image_response = client.models.generate_content(
118
+ model="gemini-2.0-flash-preview-image-generation",
119
+ # model="imagen-3.0-generate-002",
120
+ contents=image_prompt,
121
+ config=types.GenerateContentConfig(
122
+ response_modalities=['TEXT', 'IMAGE']
123
+ )
124
+ )
125
+
126
+ if image_response and image_response.candidates and image_response.candidates[0].content.parts:
127
+ for part in image_response.candidates[0].content.parts:
128
+ if part.inline_data and part.inline_data.mime_type.startswith('image/'):
129
+ image_data_base64 = part.inline_data.data
130
+ img = Image.open(io.BytesIO(image_data_base64))
131
+
132
+ img = img.resize((512, 512), Image.Resampling.LANCZOS)
133
+ print("Image generated successfully.")
134
+ return img
135
+
136
+ print("Image data not found in any response parts or not an image.")
137
+ return None
138
+ else:
139
+ print("Image generation response is empty or malformed.")
140
+ return None
141
+
142
+ except Exception as e:
143
+ print(f"Error generating pet image: {e}")
144
+ return None
145
+
146
+ def generate_tts_audio(text: str, lang: str = "zh-TW") -> Optional[str]:
147
+ """Generate text-to-speech audio file"""
148
+ try:
149
+ tts = gtts.gTTS(text=text, lang=lang, slow=False)
150
+ temp_file = tempfile.NamedTemporaryFile(delete=False, suffix=".mp3")
151
+ tts.save(temp_file.name)
152
+ return temp_file.name
153
+ except Exception as e:
154
+ print(f"TTS generation failed: {e}")
155
+ return None
156
+
157
+ def chat_with_pet(user_message: str) -> Tuple[str, Optional[str], None, Optional[Image.Image]]:
158
+ """Chat with the pet"""
159
+ global pet_state
160
+
161
+ try:
162
+ if not pet_state["name"]:
163
+ return "請先創建你的寵物!", None, None, None
164
+
165
+ context = f"""
166
+ 你是一隻名叫 {pet_state['name']} 的 {pet_state['species']},擁有 {pet_state['personality']} 的獨特個性。
167
+ 你現在的心情是 {pet_state['mood']},感覺 {pet_state['hunger']}/100 飢餓,精力則有 {pet_state['energy']}/100。
168
+
169
+ 請你完全融入這個角色,以最符合你當前狀態(心情、飢餓、精力)和個性的可愛、友善口吻,回應主人對你說的話:"{user_message}"。
170
+
171
+ 回應要求:
172
+ 1. 保持角色設定並融入當前狀態 (心情、飢餓、精力)。
173
+ 2. 以可愛、友善的寵物口吻回應。
174
+ 3. 回應長度約在 20 至 50 字之間。
175
+ 4. 可以表達你的情感和需求。
176
+ 5. 使用繁體中文。
177
+ """
178
+
179
+ response = client.models.generate_content(
180
+ model="gemini-2.5-flash-preview-04-17",
181
+ contents=context
182
+ )
183
+
184
+ pet_response = response.text.strip()
185
+
186
+ pet_state["interaction_history"].append({
187
+ "timestamp": datetime.now().strftime("%H:%M:%S"),
188
+ "user": user_message,
189
+ "pet": pet_response,
190
+ "type": "chat"
191
+ })
192
+
193
+ audio_file = generate_tts_audio(pet_response)
194
+
195
+ image_description = f"{pet_state['species']} - {pet_state.get('appearance', '')}" if pet_state['appearance'] else pet_state['species']
196
+ new_image = generate_pet_image(
197
+ image_description,
198
+ pet_state["mood"],
199
+ context_desc=f"chatting with the user, response: {pet_response}"
200
+ )
201
+
202
+ return pet_response, audio_file, None, new_image
203
+
204
+ except Exception as e:
205
+ error_msg = f"Chat function error: {e}"
206
+ print(error_msg)
207
+ return "嗚... 我現在說不出話來...", None, None, None
208
+
209
+ def feed_pet(food_type: str) -> Tuple[str, str, Optional[str], Optional[Image.Image]]:
210
+ """Feed the pet"""
211
+ global pet_state
212
+
213
+ try:
214
+ if not pet_state["name"]:
215
+ return "請先創建你的寵物!", "請先創建你的寵物!", None, None
216
+
217
+ if not food_type:
218
+ foods = ["樹果", "寶可夢食物", "蘋果", "餅乾", "糖果"]
219
+ food_type = random.choice(foods)
220
+
221
+ hunger_increase = random.randint(15, 25)
222
+ pet_state["hunger"] = min(100, pet_state["hunger"] + hunger_increase)
223
+
224
+ if pet_state["hunger"] > 80:
225
+ pet_state["mood"] = "快樂"
226
+ elif pet_state["hunger"] > 50:
227
+ pet_state["mood"] = "滿足"
228
+
229
+ context = f"""
230
+ {pet_state['name']} 剛剛吃了 {food_type},飢餓程度從之前提升到 {pet_state['hunger']}/100。
231
+ 請以寵物的角度描述享用食物的感受和反應。
232
+
233
+ 要求:
234
+ 1. 50-80字的可愛回應
235
+ 2. 表達對食物的喜愛
236
+ 3. 用繁體中文
237
+ 4. 符合 {pet_state['personality']} 的性格
238
+ """
239
+
240
+ response = client.models.generate_content(
241
+ model="gemini-2.5-flash-preview-04-17",
242
+ contents=context
243
+ )
244
+
245
+ pet_response = response.text.strip()
246
+
247
+ pet_state["interaction_history"].append({
248
+ "timestamp": datetime.now().strftime("%H:%M:%S"),
249
+ "action": f"餵食 {food_type}",
250
+ "result": pet_response,
251
+ "type": "feed"
252
+ })
253
+
254
+ image_description = f"{pet_state['species']} - {pet_state.get('appearance', '')}" if pet_state['appearance'] else pet_state['species']
255
+ new_image = generate_pet_image(
256
+ image_description,
257
+ pet_state["mood"],
258
+ context_desc=f"eating {food_type}, reaction: {pet_response}"
259
+ )
260
+
261
+ status_update = f"等級: {pet_state['level']} | 飢餓: {pet_state['hunger']}/100 | 精力: {pet_state['energy']}/100 | 心情: {pet_state['mood']}"
262
+
263
+ audio_file = generate_tts_audio(pet_response)
264
+
265
+ return pet_response, status_update, audio_file, new_image
266
+
267
+ except Exception as e:
268
+ error_msg = f"Feed function error: {e}"
269
+ print(error_msg)
270
+ status_update = f"等級: {pet_state['level']} | 飢餓: {pet_state['hunger']}/100 | 精力: {pet_state['energy']}/100 | 心情: {pet_state['mood']}"
271
+ return f"嗚... 餵食時出了點問題。({error_msg})", status_update, None, None
272
+
273
+ def adventure_with_pet() -> Tuple[str, str, Optional[str], Optional[Image.Image]]:
274
+ """Go on an adventure with the pet"""
275
+ global pet_state
276
+
277
+ try:
278
+ if not pet_state["name"]:
279
+ return "請先創建你的寵物!", "請先創建你的寵物!", None, None
280
+
281
+ energy_cost = random.randint(10, 20)
282
+ pet_state["energy"] = max(0, pet_state["energy"] - energy_cost)
283
+
284
+ exp_gain = random.randint(5, 15)
285
+ pet_state["experience"] += exp_gain
286
+
287
+ level_up_msg = ""
288
+ while pet_state["experience"] >= pet_state["level"] * 100:
289
+ pet_state["experience"] -= pet_state["level"] * 100
290
+ pet_state["level"] += 1
291
+ level_up_msg += f"恭喜!{pet_state['name']} 升到了 {pet_state['level']} 級!\n"
292
+ level_up_msg = level_up_msg.strip()
293
+
294
+ context = f"""
295
+ {pet_state['name']} 正在進行一次探險冒險。請創造一個有趣的探險場景:
296
+
297
+ 寵物資訊:
298
+ - 種族:{pet_state['species']}
299
+ - 性格:{pet_state['personality']}
300
+ - 等級:{pet_state['level']}
301
+ - 目前精力:{pet_state['energy']}/100
302
+
303
+ 請描述一個短小精彩的探險故事(80-100字),包含:
304
+ 1. 探險場景
305
+ 2. 遇到的事件或發現
306
+ 3. 寵物的反應和收獲
307
+ 4. 用繁體中文撰寫
308
+ """
309
+
310
+ response = client.models.generate_content(
311
+ model="gemini-2.5-flash-preview-04-17",
312
+ contents=context
313
+ )
314
+
315
+ adventure_story = response.text.strip()
316
+
317
+ image_description = f"{pet_state['species']} - {pet_state.get('appearance', '')}" if pet_state['appearance'] else pet_state['species']
318
+ new_image = generate_pet_image(
319
+ image_description,
320
+ pet_state["mood"],
321
+ context_desc=f"on an adventure: {adventure_story}"
322
+ )
323
+
324
+ if level_up_msg:
325
+ adventure_story += f"\n\n🎉 {level_up_msg}"
326
+ adventure_story += f"\n\n📊 獲得經驗值: +{exp_gain}"
327
+
328
+ record_result = adventure_story
329
+ pet_state["interaction_history"].append({
330
+ "timestamp": datetime.now().strftime("%H:%M:%S"),
331
+ "action": "探險",
332
+ "result": record_result,
333
+ "type": "adventure",
334
+ "exp_gained": exp_gain
335
+ })
336
+
337
+ if pet_state["energy"] > 60:
338
+ pet_state["mood"] = "興奮"
339
+ elif pet_state["energy"] > 30:
340
+ pet_state["mood"] = "滿足"
341
+ else:
342
+ pet_state["mood"] = "疲憊"
343
+
344
+ audio_file = generate_tts_audio(adventure_story)
345
+
346
+ status_update = f"等級: {pet_state['level']} | 經驗: {pet_state['experience']}/{pet_state['level']*100} | 精力: {pet_state['energy']}/100 | 心情: {pet_state['mood']}"
347
+
348
+ return adventure_story, status_update, audio_file, new_image
349
+
350
+ except Exception as e:
351
+ error_msg = f"Adventure function error: {e}"
352
+ print(error_msg)
353
+ status_update = f"等級: {pet_state['level']} | 經驗: {pet_state['experience']}/{pet_state['level']*100} | 精力: {pet_state['energy']}/100 | 心情: {pet_state['mood']}"
354
+ return "嗚... 探險時出了點問題。", status_update, None, None
355
+
356
+ def battle_with_pet() -> Tuple[str, str, Optional[str], Optional[Image.Image]]:
357
+ """Battle with the pet"""
358
+ global pet_state
359
+
360
+ try:
361
+ if not pet_state["name"]:
362
+ return "請先創建你的寵物!", "請先創建你的寵物!", None, None
363
+
364
+ opponents = ["野生皮卡丘", "小火龍", "傑尼龜", "妙蛙種子", "小拉達", "波波"]
365
+ opponent = random.choice(opponents)
366
+
367
+ pet_power = pet_state["level"] * 10 + random.randint(1, 20)
368
+ opponent_power = random.randint(10, 50)
369
+
370
+ win = pet_power > opponent_power
371
+
372
+ if win:
373
+ pet_state["battle_stats"]["wins"] += 1
374
+ result = "勝利"
375
+ exp_gain = random.randint(10, 20)
376
+ pet_state["experience"] += exp_gain
377
+ pet_state["mood"] = "興奮"
378
+ else:
379
+ pet_state["battle_stats"]["losses"] += 1
380
+ result = "失敗"
381
+ exp_gain = random.randint(3, 8)
382
+ pet_state["experience"] += exp_gain
383
+ pet_state["mood"] = "疲憊"
384
+
385
+ level_up_msg = ""
386
+ while pet_state["experience"] >= pet_state["level"] * 100:
387
+ pet_state["experience"] -= pet_state["level"] * 100
388
+ pet_state["level"] += 1
389
+ level_up_msg += f"恭喜!{pet_state['name']} 升到了 {pet_state['level']} 級!\n"
390
+ level_up_msg = level_up_msg.strip()
391
+
392
+ energy_cost = random.randint(15, 25)
393
+ pet_state["energy"] = max(0, pet_state["energy"] - energy_cost)
394
+
395
+ context = f"""
396
+ {pet_state['name']} 剛剛和 {opponent} 進行了一場對戰,結果是{result}。
397
+
398
+ 請描述這場戰鬥的過程和結果(80-120字):
399
+ 1. 戰鬥場面描述
400
+ 2. 使用的招式 (寶可夢風格)
401
+ 3. 戰鬥結果
402
+ 4. {pet_state['name']} 的反應
403
+ 5. 用繁體中文撰寫
404
+ 6. 符合 {pet_state['personality']} 的性格
405
+ """
406
+
407
+ response = client.models.generate_content(
408
+ model="gemini-2.5-flash-preview-04-17",
409
+ contents=context
410
+ )
411
+
412
+ battle_story = response.text.strip()
413
+
414
+ image_description = f"{pet_state['species']} - {pet_state.get('appearance', '')}" if pet_state['appearance'] else pet_state['species']
415
+ new_image = generate_pet_image(
416
+ image_description,
417
+ pet_state["mood"],
418
+ context_desc=f"in a battle against {opponent}, based on the story: {battle_story}"
419
+ )
420
+
421
+ battle_story += f"\n\n📊 獲得經驗值: +{exp_gain}"
422
+ if level_up_msg:
423
+ battle_story += f"\n\n🎉 {level_up_msg}"
424
+
425
+ record_result = f"{result} - {battle_story}"
426
+ pet_state["interaction_history"].append({
427
+ "timestamp": datetime.now().strftime("%H:%M:%S"),
428
+ "action": f"對戰 vs {opponent}",
429
+ "result": record_result,
430
+ "type": "battle"
431
+ })
432
+
433
+ audio_file = generate_tts_audio(battle_story)
434
+
435
+ status_update = f"等級: {pet_state['level']} | 經驗: {pet_state['experience']}/{pet_state['level']*100} | 勝場: {pet_state['battle_stats']['wins']} | 敗場: {pet_state['battle_stats']['losses']} | 精力: {pet_state['energy']}/100 | 心情: {pet_state['mood']}"
436
+
437
+ return battle_story, status_update, audio_file, new_image
438
+
439
+ except Exception as e:
440
+ error_msg = f"Battle function error: {e}"
441
+ print(error_msg)
442
+ status_update = f"等級: {pet_state['level']} | 經驗: {pet_state['experience']}/{pet_state['level']*100} | 勝場: {pet_state['battle_stats']['wins']} | 敗場: {pet_state['battle_stats']['losses']} | 精力: {pet_state['energy']}/100 | 心情: {pet_state['mood']}"
443
+ return f"嗚... 對戰時出了點問題。", status_update, None, None
444
+
445
+ def initialize_pet(custom_input: str) -> Tuple[Optional[Image.Image], str, str, str, Optional[str]]:
446
+ """Initialize the pet"""
447
+ global pet_state
448
+
449
+ pet_data = generate_pet_description(custom_input)
450
+
451
+ pet_state.update({
452
+ "name": pet_data.get("name", "未知寵物"),
453
+ "species": pet_data.get("species", "未知物種"),
454
+ "personality": pet_data.get("personality", "溫和"),
455
+ "appearance": pet_data.get("appearance", ""),
456
+ "mood": "快樂",
457
+ "hunger": 80,
458
+ "energy": 90,
459
+ "experience": 0,
460
+ "level": 1,
461
+ "interaction_history": [],
462
+ "battle_stats": {"wins": 0, "losses": 0}
463
+ })
464
+
465
+ image_description = f"{pet_state['species']} - {pet_state['appearance']}" if pet_state['appearance'] else pet_state['species']
466
+ pet_image = generate_pet_image(image_description, pet_state["mood"])
467
+
468
+ welcome_msg = f"🎉 歡迎來到寶可夢世界!\n\n" \
469
+ f"✨ 名稱:{pet_state['name']}\n" \
470
+ f"🐾 種族:{pet_state['species']}\n" \
471
+ f"💭 性格:{pet_state['personality']}\n" \
472
+ f"😊 心情:{pet_state['mood']}\n\n" \
473
+ f"現在你可以和 {pet_state['name']} 互動了!"
474
+
475
+ status_info = f"等級: {pet_state['level']} | 飢餓: {pet_state['hunger']}/100 | 精力: {pet_state['energy']}/100 | 心情: {pet_state['mood']}"
476
+
477
+ audio_file = generate_tts_audio(f"你好!我是{pet_state['name']},很高興認識你!")
478
+
479
+ return pet_image, welcome_msg, status_info, "", audio_file
480
+
481
+ def get_interaction_history() -> str:
482
+ """Get interaction history"""
483
+ if not pet_state["interaction_history"]:
484
+ return "還沒有互動記錄呢!快和你的寵物互動吧!"
485
+
486
+ history_text = f"📋 {pet_state['name']} 的互動日誌\n" + "="*50 + "\n\n"
487
+
488
+ for record in pet_state["interaction_history"][-20:]:
489
+ time_stamp = record["timestamp"]
490
+ if record["type"] == "chat":
491
+ history_text += f"🕐 {time_stamp} [聊天]\n"
492
+ history_text += f"👤 你: {record['user']}\n"
493
+ history_text += f"🐾 {pet_state['name']}: {record['pet']}\n\n"
494
+ else:
495
+ action_desc = record.get('action', record['type'])
496
+ result_desc = record.get('result', '無結果描述')
497
+ history_text += f"🕐 {time_stamp} [{action_desc}]\n"
498
+ history_text += f"📝 {result_desc}\n\n"
499
+
500
+ return history_text
501
+
502
+ def create_app():
503
+ """Create the Gradio application"""
504
+
505
+ with gr.Blocks(
506
+ title="Virtual Pokemon Companion",
507
+ theme=gr.themes.Soft(),
508
+ css="""
509
+ .main-container { max-width: 1200px; margin: 0 auto; }
510
+ .pet-image { border-radius: 15px; box-shadow: 0 4px 8px rgba(0,0,0,0.1); object-fit: contain; }
511
+ .status-box { background: linear-gradient(45deg, #ff9a9e, #fecfef); padding: 15px; border-radius: 10px; }
512
+ .interaction-box { background: linear-gradient(45deg, #a8edea, #fed6e3); padding: 15px; border-radius: 10px; }
513
+ """) as app:
514
+
515
+ gr.Markdown("""
516
+ # 🌟 Virtual Pokemon Companion
517
+ ### An AI-powered pet system using Google Gemini
518
+ """)
519
+
520
+ with gr.Row():
521
+ with gr.Column(scale=1):
522
+ gr.Markdown("## 🐾 Your Pet")
523
+
524
+ pet_image = gr.Image(
525
+ label="Pet Image",
526
+ height=300,
527
+ show_label=False,
528
+ elem_classes=["pet-image"],
529
+ interactive=False
530
+ )
531
+
532
+ pet_status = gr.Textbox(
533
+ label="Pet Status",
534
+ value="Please create your pet first!",
535
+ interactive=False,
536
+ lines=3,
537
+ elem_classes=["status-box"]
538
+ )
539
+
540
+ gr.Markdown("### 🎮 Create Pet")
541
+ custom_pet_input = gr.Textbox(
542
+ label="Custom Pet Traits (Optional)",
543
+ placeholder="e.g., A flying blue dragon with a gentle personality..."
544
+ )
545
+
546
+ with gr.Row():
547
+ create_random_btn = gr.Button("🎲 Randomize", variant="primary")
548
+ create_custom_btn = gr.Button("✨ Create Custom", variant="secondary")
549
+
550
+ with gr.Column(scale=1):
551
+ gr.Markdown("## 💬 Interaction Area")
552
+
553
+ chat_output = gr.Textbox(
554
+ label="Pet Response",
555
+ value="Your pet is waiting to be created...",
556
+ interactive=False,
557
+ lines=4,
558
+ elem_classes=["interaction-box"]
559
+ )
560
+
561
+ audio_output = gr.Audio(
562
+ label="Pet Voice",
563
+ visible=True,
564
+ autoplay=True
565
+ )
566
+
567
+ gr.Markdown("### 💭 Chat")
568
+ chat_input = gr.Textbox(
569
+ label="Talk to your pet",
570
+ placeholder="Enter your message here..."
571
+ )
572
+ chat_btn = gr.Button("💬 Chat", variant="primary")
573
+
574
+ gr.Markdown("### 🎯 Activities")
575
+ with gr.Row():
576
+ feed_btn = gr.Button("🍎 Feed", variant="secondary")
577
+ adventure_btn = gr.Button("🗺️ Adventure", variant="secondary")
578
+
579
+ with gr.Row():
580
+ battle_btn = gr.Button("⚔️ Battle", variant="secondary")
581
+ history_btn = gr.Button("📋 View Log", variant="secondary")
582
+
583
+ with gr.Row():
584
+ history_output = gr.Textbox(
585
+ label="📜 Interaction Log",
586
+ lines=8,
587
+ interactive=False,
588
+ visible=False,
589
+ max_lines=20
590
+ )
591
+
592
+ # Event bindings
593
+ create_random_btn.click(
594
+ fn=lambda: initialize_pet(""),
595
+ outputs=[pet_image, chat_output, pet_status, custom_pet_input, audio_output]
596
+ )
597
+
598
+ create_custom_btn.click(
599
+ fn=initialize_pet,
600
+ inputs=[custom_pet_input],
601
+ outputs=[pet_image, chat_output, pet_status, custom_pet_input, audio_output]
602
+ )
603
+
604
+ chat_btn.click(
605
+ fn=chat_with_pet,
606
+ inputs=[chat_input],
607
+ outputs=[chat_output, audio_output, chat_input, pet_image]
608
+ )
609
+
610
+ chat_input.submit(
611
+ fn=chat_with_pet,
612
+ inputs=[chat_input],
613
+ outputs=[chat_output, audio_output, chat_input, pet_image]
614
+ )
615
+
616
+ feed_btn.click(
617
+ fn=lambda: feed_pet(""),
618
+ outputs=[chat_output, pet_status, audio_output, pet_image]
619
+ )
620
+
621
+ adventure_btn.click(
622
+ fn=adventure_with_pet,
623
+ outputs=[chat_output, pet_status, audio_output, pet_image]
624
+ )
625
+
626
+ battle_btn.click(
627
+ fn=battle_with_pet,
628
+ outputs=[chat_output, pet_status, audio_output, pet_image]
629
+ )
630
+
631
+ def toggle_history():
632
+ current_visibility = history_output.visible
633
+ new_visibility = not current_visibility
634
+ history_content = get_interaction_history() if new_visibility else ""
635
+ return gr.update(visible=new_visibility), history_content
636
+
637
+ history_btn.click(
638
+ fn=toggle_history,
639
+ outputs=[history_output, history_output]
640
+ )
641
+
642
+ return app
643
+
644
+ app = create_app()
645
+
646
+ app.launch()