Gileskk commited on
Commit
af01b6f
·
verified ·
1 Parent(s): 6f4a284

Upload 8 files

Browse files
.env ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ # Together AI 配置
2
+ TOGETHER_API_KEY=520fa4af1471aafb9949110e98dde9232c629fc8e51e81a830a85f791c1cfe64
3
+
4
+ # NocoDB 配置
5
+ NOCODB_API_KEY=niITP3GMp_241YJeZvjKQtw4Wp8ltnwgUtUcF2Q8
6
+ NOCODB_URL=https://app.nocodb.com
7
+ NOCODB_PROJECT_ID=p1lg93fo8zt1jsg
app.py ADDED
@@ -0,0 +1,1025 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import pandas as pd
3
+ import plotly.express as px
4
+ import plotly.graph_objects as go
5
+ from datetime import datetime, timedelta
6
+ import hashlib
7
+ import uuid
8
+ import time
9
+ from config import APP_TITLE, APP_DESCRIPTION, GAME_CONFIG
10
+ from database.nocodb import nocodb_client
11
+ from llm.together import together_client
12
+
13
+ # 设置页面配置
14
+ st.set_page_config(
15
+ page_title=APP_TITLE,
16
+ page_icon="⚔️",
17
+ layout="wide",
18
+ initial_sidebar_state="expanded"
19
+ )
20
+
21
+ # 自定义CSS样式
22
+ st.markdown("""
23
+ <style>
24
+ .main {
25
+ background-color: #f0f2f6;
26
+ }
27
+ .stButton>button {
28
+ background-color: #4CAF50;
29
+ color: white;
30
+ border-radius: 5px;
31
+ padding: 10px 20px;
32
+ font-weight: bold;
33
+ transition: all 0.3s ease;
34
+ }
35
+ .stButton>button:hover {
36
+ background-color: #45a049;
37
+ transform: translateY(-2px);
38
+ box-shadow: 0 2px 5px rgba(0,0,0,0.2);
39
+ }
40
+ .character-card {
41
+ background-color: white;
42
+ padding: 20px;
43
+ border-radius: 10px;
44
+ box-shadow: 0 2px 5px rgba(0,0,0,0.1);
45
+ transition: all 0.3s ease;
46
+ }
47
+ .character-card:hover {
48
+ transform: translateY(-5px);
49
+ box-shadow: 0 5px 15px rgba(0,0,0,0.2);
50
+ }
51
+ .battle-log {
52
+ background-color: #fff3cd;
53
+ padding: 15px;
54
+ border-radius: 5px;
55
+ margin: 10px 0;
56
+ border-left: 5px solid #ffc107;
57
+ }
58
+ .success-message {
59
+ color: #28a745;
60
+ font-weight: bold;
61
+ padding: 10px;
62
+ border-radius: 5px;
63
+ background-color: #d4edda;
64
+ }
65
+ .error-message {
66
+ color: #dc3545;
67
+ font-weight: bold;
68
+ padding: 10px;
69
+ border-radius: 5px;
70
+ background-color: #f8d7da;
71
+ }
72
+ .sidebar-content {
73
+ padding: 20px;
74
+ background-color: #f8f9fa;
75
+ border-radius: 10px;
76
+ margin-bottom: 20px;
77
+ }
78
+ .tab-content {
79
+ padding: 20px;
80
+ background-color: white;
81
+ border-radius: 10px;
82
+ box-shadow: 0 2px 5px rgba(0,0,0,0.1);
83
+ }
84
+ .stat-value {
85
+ font-size: 1.2em;
86
+ font-weight: bold;
87
+ color: #2c3e50;
88
+ }
89
+ .class-badge {
90
+ padding: 5px 10px;
91
+ border-radius: 15px;
92
+ font-size: 0.9em;
93
+ font-weight: bold;
94
+ }
95
+ .warrior { background-color: #e74c3c; color: white; }
96
+ .mage { background-color: #3498db; color: white; }
97
+ .rogue { background-color: #2ecc71; color: white; }
98
+ .priest { background-color: #9b59b6; color: white; }
99
+ .archer { background-color: #f1c40f; color: white; }
100
+ </style>
101
+ """, unsafe_allow_html=True)
102
+
103
+ # 初始化会话状态
104
+ if 'user_id' not in st.session_state:
105
+ st.session_state.user_id = None
106
+ if 'character' not in st.session_state:
107
+ st.session_state.character = None
108
+ if 'characters' not in st.session_state:
109
+ st.session_state.characters = []
110
+ if 'is_logged_in' not in st.session_state:
111
+ st.session_state.is_logged_in = False
112
+ if 'current_tab' not in st.session_state:
113
+ st.session_state.current_tab = "角色信息"
114
+
115
+ def hash_password(password: str) -> str:
116
+ """对密码进行哈希处理"""
117
+ return hashlib.sha256(password.encode()).hexdigest()
118
+
119
+ def login_user(username: str, password: str) -> bool:
120
+ """用户登录"""
121
+ try:
122
+ users = nocodb_client.get_records("users", {
123
+ "where": f"(username,eq,{username})"
124
+ })
125
+ if users and users[0]["password"] == hash_password(password):
126
+ st.session_state.user_id = users[0]["Id"]
127
+ st.session_state.is_logged_in = True
128
+ # 加载用户的所有角色
129
+ characters = nocodb_client.get_records("characters", {
130
+ "where": f"(user_id,eq,{users[0]['Id']})"
131
+ })
132
+ st.session_state.characters = characters
133
+ return True
134
+ except Exception as e:
135
+ st.error(f"登录失败: {str(e)}")
136
+ return False
137
+
138
+ def register_user(username: str, password: str) -> bool:
139
+ """用户注册"""
140
+ try:
141
+ # 检查用户名是否已存在
142
+ existing_users = nocodb_client.get_records("users", {
143
+ "where": f"(username,eq,{username})"
144
+ })
145
+ if existing_users:
146
+ st.error("用户名已存在")
147
+ return False
148
+
149
+ # 创建新用户
150
+ user = {
151
+ "username": username,
152
+ "password": hash_password(password),
153
+ "created_time": datetime.now().isoformat()
154
+ }
155
+ result = nocodb_client.create_record("users", user)
156
+ return True
157
+ except Exception as e:
158
+ st.error(f"注册失败: {str(e)}")
159
+ return False
160
+
161
+ def load_character(character_id: str) -> None:
162
+ """加载指定角色"""
163
+ try:
164
+ characters = nocodb_client.get_records("characters", {
165
+ "where": f"(Id,eq,{character_id})"
166
+ })
167
+ if characters:
168
+ # 确保所有必需的属性都存在
169
+ character = characters[0]
170
+ required_fields = {
171
+ 'level': 1,
172
+ 'EXP': 0,
173
+ 'ATK': GAME_CONFIG["base_stats"]["ATK"],
174
+ 'DEF': GAME_CONFIG["base_stats"]["DEF"],
175
+ 'MAG': GAME_CONFIG["base_stats"]["MAG"],
176
+ 'DEX': GAME_CONFIG["base_stats"]["DEX"],
177
+ 'LUK': GAME_CONFIG["base_stats"]["LUK"]
178
+ }
179
+
180
+ # 如果缺少任何必需字段,使用默认值
181
+ for field, default_value in required_fields.items():
182
+ if field not in character:
183
+ character[field] = default_value
184
+ # 更新数据库中的记录
185
+ nocodb_client.update_record("characters", character_id, {field: default_value})
186
+
187
+ st.session_state.character = character
188
+ st.rerun()
189
+ except Exception as e:
190
+ st.error(f"加载角色失败: {str(e)}")
191
+
192
+ def create_character(name: str, character_class: str) -> bool:
193
+ """创建新角色"""
194
+ try:
195
+ # 获取职业基础属性
196
+ class_stats = GAME_CONFIG["class_base_stats"][character_class]
197
+
198
+ # 确保所有必需的字段都有默认值
199
+ character = {
200
+ "user_id": int(st.session_state.user_id),
201
+ "name": name,
202
+ "class": character_class,
203
+ "level": 1,
204
+ "EXP": 0,
205
+ "ATK": class_stats["ATK"],
206
+ "DEF": class_stats["DEF"],
207
+ "MAG": class_stats["MAG"],
208
+ "DEX": class_stats["DEX"],
209
+ "LUK": class_stats["LUK"],
210
+ "created_time": datetime.now().isoformat(),
211
+ "last_login": datetime.now().isoformat()
212
+ }
213
+
214
+ result = nocodb_client.create_record("characters", character)
215
+ if result:
216
+ # 确保返回的结果包含所有必需的字段
217
+ for field, value in character.items():
218
+ if field not in result:
219
+ result[field] = value
220
+ st.session_state.character = result
221
+ return True
222
+ return False
223
+ except Exception as e:
224
+ st.error(f"创建角色失败: {str(e)}")
225
+ return False
226
+
227
+ def update_character_stats(character_id: str, stats: dict) -> bool:
228
+ """更新角色属性"""
229
+ try:
230
+ nocodb_client.update_record("characters", character_id, stats)
231
+ return True
232
+ except Exception as e:
233
+ st.error(f"更新属性失败: {str(e)}")
234
+ return False
235
+
236
+ def get_class_badge(character_class: str) -> str:
237
+ """获取职业徽章样式"""
238
+ class_styles = {
239
+ "战士": "warrior",
240
+ "法师": "mage",
241
+ "刺客": "rogue",
242
+ "牧师": "priest",
243
+ "弓箭手": "archer"
244
+ }
245
+ return f'<span class="class-badge {class_styles.get(character_class, "")}">{character_class}</span>'
246
+
247
+ def create_character_stats_chart(character):
248
+ """创建角色属性雷达图"""
249
+ stats = {
250
+ '属性': ['攻击力', '防御力', '魔法力', '敏捷', '幸运'],
251
+ '数值': [
252
+ character['ATK'],
253
+ character['DEF'],
254
+ character['MAG'],
255
+ character['DEX'],
256
+ character['LUK']
257
+ ]
258
+ }
259
+
260
+ fig = go.Figure()
261
+ fig.add_trace(go.Scatterpolar(
262
+ r=stats['数值'],
263
+ theta=stats['属性'],
264
+ fill='toself',
265
+ name='当前属性'
266
+ ))
267
+
268
+ # 添加职业基准线
269
+ class_stats = GAME_CONFIG["class_base_stats"][character['class']]
270
+ base_stats = [
271
+ class_stats['ATK'],
272
+ class_stats['DEF'],
273
+ class_stats['MAG'],
274
+ class_stats['DEX'],
275
+ class_stats['LUK']
276
+ ]
277
+
278
+ fig.add_trace(go.Scatterpolar(
279
+ r=base_stats,
280
+ theta=stats['属性'],
281
+ fill='toself',
282
+ name='职业基准'
283
+ ))
284
+
285
+ fig.update_layout(
286
+ polar=dict(
287
+ radialaxis=dict(
288
+ visible=True,
289
+ range=[0, max(max(stats['数值']), max(base_stats)) * 1.2]
290
+ )),
291
+ showlegend=True,
292
+ title='角色属性雷达图'
293
+ )
294
+
295
+ return fig
296
+
297
+ def create_level_progress_chart(character):
298
+ """创建等级进度条"""
299
+ # 计算升级所需经验值(示例:每级需要1000经验)
300
+ exp_needed = character['level'] * 1000
301
+ progress = (character['EXP'] / exp_needed) * 100
302
+
303
+ fig = go.Figure(go.Indicator(
304
+ mode="gauge+number",
305
+ value=progress,
306
+ title={'text': f"升级进度 (Lv.{character['level']})"},
307
+ gauge={
308
+ 'axis': {'range': [0, 100]},
309
+ 'bar': {'color': "darkblue"},
310
+ 'steps': [
311
+ {'range': [0, 100], 'color': "lightgray"}
312
+ ],
313
+ 'threshold': {
314
+ 'line': {'color': "red", 'width': 4},
315
+ 'thickness': 0.75,
316
+ 'value': 100
317
+ }
318
+ }
319
+ ))
320
+
321
+ fig.update_layout(
322
+ height=200,
323
+ margin=dict(l=20, r=20, t=50, b=20)
324
+ )
325
+
326
+ return fig
327
+
328
+ def create_friend_ranking_table(character, available_opponents):
329
+ """创建好友排行榜表格"""
330
+ try:
331
+ # 确保当前角色的所有属性都有值
332
+ current_char = {
333
+ 'name': character['name'],
334
+ 'level': character.get('level', 1),
335
+ 'ATK': character.get('ATK', 10),
336
+ 'DEF': character.get('DEF', 10),
337
+ 'MAG': character.get('MAG', 10),
338
+ 'DEX': character.get('DEX', 10),
339
+ 'LUK': character.get('LUK', 10),
340
+ 'class': character['class'],
341
+ 'username': '你'
342
+ }
343
+ current_char['total_stats'] = current_char['ATK'] + current_char['DEF'] + current_char['MAG'] + current_char['DEX'] + current_char['LUK']
344
+
345
+ # 确保所有对手的属性都有值
346
+ for opp in available_opponents:
347
+ opp['ATK'] = opp.get('ATK', 10)
348
+ opp['DEF'] = opp.get('DEF', 10)
349
+ opp['MAG'] = opp.get('MAG', 10)
350
+ opp['DEX'] = opp.get('DEX', 10)
351
+ opp['LUK'] = opp.get('LUK', 10)
352
+ opp['level'] = opp.get('level', 1)
353
+ opp['total_stats'] = opp['ATK'] + opp['DEF'] + opp['MAG'] + opp['DEX'] + opp['LUK']
354
+
355
+ all_chars = available_opponents + [current_char]
356
+
357
+ # 按总属性值排序
358
+ sorted_chars = sorted(all_chars, key=lambda x: x['total_stats'], reverse=True)
359
+
360
+ # 创建表格数据
361
+ table_data = []
362
+ for i, char in enumerate(sorted_chars, 1):
363
+ table_data.append({
364
+ "排名": i,
365
+ "角色名": char['name'],
366
+ "职业": char['class'],
367
+ "等级": char['level'],
368
+ "攻击力": char['ATK'],
369
+ "防御力": char['DEF'],
370
+ "魔法力": char['MAG'],
371
+ "敏捷": char['DEX'],
372
+ "幸运": char['LUK'],
373
+ "总属性": char['total_stats'],
374
+ "玩家": char['username']
375
+ })
376
+
377
+ # 创建DataFrame
378
+ df = pd.DataFrame(table_data)
379
+
380
+ return df
381
+ except Exception as e:
382
+ st.error(f"创建排行榜失败: {str(e)}")
383
+ return None
384
+
385
+ # 添加请求限制装饰器
386
+ def rate_limit(max_requests=10, time_window=60):
387
+ """请求限制装饰器"""
388
+ last_reset = time.time()
389
+ request_count = 0
390
+
391
+ def decorator(func):
392
+ def wrapper(*args, **kwargs):
393
+ nonlocal last_reset, request_count
394
+ current_time = time.time()
395
+
396
+ # 重置计数器
397
+ if current_time - last_reset >= time_window:
398
+ last_reset = current_time
399
+ request_count = 0
400
+
401
+ # 检查是否超过限制
402
+ if request_count >= max_requests:
403
+ time.sleep(time_window - (current_time - last_reset))
404
+ last_reset = time.time()
405
+ request_count = 0
406
+
407
+ request_count += 1
408
+ return func(*args, **kwargs)
409
+ return wrapper
410
+ return decorator
411
+
412
+ # 添加重试机制
413
+ def retry_on_error(max_retries=3, delay=1):
414
+ """重试装饰器"""
415
+ def decorator(func):
416
+ def wrapper(*args, **kwargs):
417
+ for attempt in range(max_retries):
418
+ try:
419
+ return func(*args, **kwargs)
420
+ except Exception as e:
421
+ if "429" in str(e):
422
+ if attempt < max_retries - 1:
423
+ time.sleep(delay * (attempt + 1))
424
+ continue
425
+ raise e
426
+ return func(*args, **kwargs)
427
+ return wrapper
428
+ return decorator
429
+
430
+ # 修改获取好友列表的函数
431
+ @rate_limit(max_requests=10, time_window=60)
432
+ @retry_on_error(max_retries=3, delay=2)
433
+ def get_available_opponents(current_user_id):
434
+ """获取可用的对手列表"""
435
+ try:
436
+ # 使用缓存机制获取所有用户和角色数据
437
+ users = nocodb_client.get_records("users")
438
+ if not users:
439
+ return []
440
+
441
+ # 获取所有角色数据(一次性获取)
442
+ all_characters = nocodb_client.get_records("characters")
443
+
444
+ # 在内存中过滤和处理数据
445
+ available_opponents = []
446
+ for user in users:
447
+ if user["Id"] != current_user_id: # 排除自己
448
+ # 在内存中过滤该用户的角色
449
+ user_characters = [char for char in all_characters if char.get("user_id") == user["Id"]]
450
+ if user_characters:
451
+ for char in user_characters:
452
+ available_opponents.append({
453
+ "id": char["Id"],
454
+ "name": char["name"],
455
+ "class": char["class"],
456
+ "level": char.get("level", 1),
457
+ "username": user["username"],
458
+ "ATK": char.get("ATK", 10),
459
+ "DEF": char.get("DEF", 10),
460
+ "MAG": char.get("MAG", 10),
461
+ "DEX": char.get("DEX", 10),
462
+ "LUK": char.get("LUK", 10)
463
+ })
464
+ return available_opponents
465
+ except Exception as e:
466
+ st.error(f"获取好友列表失败: {str(e)}")
467
+ return []
468
+
469
+ # 修改对战记录获取逻辑
470
+ def get_battle_records(character_id):
471
+ """获取对战记录(带缓存)"""
472
+ try:
473
+ # 添加错误处理和重试逻辑
474
+ max_retries = 3
475
+ retry_delay = 2
476
+
477
+ for attempt in range(max_retries):
478
+ try:
479
+ battles = nocodb_client.get_records("battles", {
480
+ "where": f"(attacker,eq,{character_id})"
481
+ })
482
+ return battles[-5:] if battles else [] # 只返回最近5场对战
483
+ except Exception as e:
484
+ if "429" in str(e) and attempt < max_retries - 1:
485
+ time.sleep(retry_delay * (attempt + 1))
486
+ continue
487
+ raise e
488
+ except Exception as e:
489
+ st.error(f"获取对战记录失败: {str(e)}")
490
+ return []
491
+
492
+ # 修改Boss挑战记录获取逻辑
493
+ def get_boss_challenge_records(character_id):
494
+ """获取Boss挑战记录(带缓存)"""
495
+ try:
496
+ # 添加错误处理和重试逻辑
497
+ max_retries = 3
498
+ retry_delay = 2
499
+
500
+ for attempt in range(max_retries):
501
+ try:
502
+ challenges = nocodb_client.get_records("boss_challenges", {
503
+ "where": f"(team,like,{character_id})"
504
+ })
505
+ return challenges[-5:] if challenges else [] # 只返回最近5场挑战
506
+ except Exception as e:
507
+ if "429" in str(e) and attempt < max_retries - 1:
508
+ time.sleep(retry_delay * (attempt + 1))
509
+ continue
510
+ raise e
511
+ except Exception as e:
512
+ st.error(f"获取挑战记录失败: {str(e)}")
513
+ return []
514
+
515
+ # 侧边栏
516
+ with st.sidebar:
517
+ st.title("⚔️ RPG冒险游戏")
518
+
519
+ if not st.session_state.is_logged_in:
520
+ with st.container():
521
+ st.markdown('<div class="sidebar-content">', unsafe_allow_html=True)
522
+ tab1, tab2 = st.tabs(["登录", "注册"])
523
+
524
+ with tab1:
525
+ with st.form("login_form"):
526
+ login_username = st.text_input("用户名")
527
+ login_password = st.text_input("密码", type="password")
528
+ if st.form_submit_button("登录", use_container_width=True):
529
+ if login_user(login_username, login_password):
530
+ st.success("登录成功!")
531
+ st.rerun()
532
+
533
+ with tab2:
534
+ with st.form("register_form"):
535
+ reg_username = st.text_input("新用户名")
536
+ reg_password = st.text_input("新密码", type="password")
537
+ reg_confirm = st.text_input("确认密码", type="password")
538
+ if st.form_submit_button("注册", use_container_width=True):
539
+ if reg_password == reg_confirm:
540
+ if register_user(reg_username, reg_password):
541
+ st.success("注册成功!请登录")
542
+ else:
543
+ st.error("两次输入的密码不一致")
544
+ st.markdown('</div>', unsafe_allow_html=True)
545
+ else:
546
+ with st.container():
547
+ st.markdown('<div class="sidebar-content">', unsafe_allow_html=True)
548
+ st.success(f"欢迎回来!")
549
+
550
+ # 角色选择
551
+ if st.session_state.characters:
552
+ st.markdown("### 选择角色")
553
+ character_names = [f"{char['name']} ({char['class']})" for char in st.session_state.characters]
554
+ selected_character = st.selectbox(
555
+ "选择要使用的角色",
556
+ character_names,
557
+ index=0 if not st.session_state.character else character_names.index(f"{st.session_state.character['name']} ({st.session_state.character['class']})")
558
+ )
559
+ if selected_character:
560
+ selected_id = st.session_state.characters[character_names.index(selected_character)]["Id"]
561
+ if not st.session_state.character or st.session_state.character["Id"] != selected_id:
562
+ load_character(selected_id)
563
+
564
+ # 功能导航
565
+ st.markdown("### 功能导航")
566
+ tabs = ["角色信息", "角色培养", "PVP对战", "Boss挑战"]
567
+ selected_tab = st.radio("选择功能", tabs, index=tabs.index(st.session_state.current_tab))
568
+ st.session_state.current_tab = selected_tab
569
+
570
+ if st.button("退出登录", use_container_width=True):
571
+ st.session_state.is_logged_in = False
572
+ st.session_state.user_id = None
573
+ st.session_state.character = None
574
+ st.session_state.characters = []
575
+ st.rerun()
576
+ st.markdown('</div>', unsafe_allow_html=True)
577
+
578
+ # 模型选择
579
+ with st.container():
580
+ st.markdown('<div class="sidebar-content">', unsafe_allow_html=True)
581
+ st.markdown("### AI模型设置")
582
+ try:
583
+ models = together_client.get_available_models()
584
+ if models:
585
+ model_names = [model["name"] for model in models]
586
+ selected_model = st.selectbox(
587
+ "选择AI模型",
588
+ model_names,
589
+ index=0
590
+ )
591
+ together_client.set_model(selected_model)
592
+ except Exception as e:
593
+ st.warning(f"获取模型列表失败: {str(e)}")
594
+ st.markdown('</div>', unsafe_allow_html=True)
595
+
596
+ # 主界面
597
+ if not st.session_state.is_logged_in:
598
+ st.markdown("""
599
+ <div style='text-align: center; padding: 50px;'>
600
+ <h1>欢迎来到RPG冒险游戏</h1>
601
+ <p>请先登录或注册以开始游戏</p>
602
+ </div>
603
+ """, unsafe_allow_html=True)
604
+ else:
605
+ # 检查NocoDB连接
606
+ try:
607
+ nocodb_client.get_records("characters")
608
+ except Exception as e:
609
+ st.error(f"无法连接到NocoDB数据库: {str(e)}")
610
+ st.stop()
611
+
612
+ # 根据选择的标签页显示不同内容
613
+ if st.session_state.current_tab == "角色信息":
614
+ if not st.session_state.character:
615
+ st.markdown("""
616
+ <div style='text-align: center;'>
617
+ <h2>创建你的角色</h2>
618
+ <p>选择一个职业,开始你的冒险之旅!</p>
619
+ </div>
620
+ """, unsafe_allow_html=True)
621
+
622
+ with st.form("character_creation"):
623
+ col1, col2 = st.columns(2)
624
+
625
+ with col1:
626
+ name = st.text_input("角色名称")
627
+ character_class = st.selectbox(
628
+ "选择职业",
629
+ GAME_CONFIG["classes"]
630
+ )
631
+
632
+ with col2:
633
+ st.markdown("### 职业介绍")
634
+ class_descriptions = {
635
+ "战士": "擅长近战,拥有较高的攻击力和防御力",
636
+ "法师": "精通魔法,拥有强大的魔法攻击力",
637
+ "刺客": "敏捷灵活,擅长暴击和闪避",
638
+ "牧师": "精通治疗和辅助魔法",
639
+ "弓箭手": "远程攻击专家,拥有较高的命中率"
640
+ }
641
+ st.write(class_descriptions[character_class])
642
+
643
+ if st.form_submit_button("创建角色", use_container_width=True):
644
+ if name:
645
+ if create_character(name, character_class):
646
+ st.success("角色创建成功!")
647
+ st.rerun()
648
+ else:
649
+ st.warning("请输入角色名称")
650
+ else:
651
+ # 角色基本信息卡片
652
+ st.markdown(f"""
653
+ <div class='character-card'>
654
+ <h2>{st.session_state.character['name']} {get_class_badge(st.session_state.character['class'])}</h2>
655
+ <div style='display: flex; justify-content: space-between;'>
656
+ <div>
657
+ <p>ID:<span class='stat-value'>{st.session_state.character['Id']}</span></p>
658
+ <p>等级:<span class='stat-value'>{st.session_state.character['level']}</span></p>
659
+ <p>经验值:<span class='stat-value'>{st.session_state.character['EXP']}</span></p>
660
+ </div>
661
+ <div>
662
+ <p>攻击力:<span class='stat-value'>{st.session_state.character['ATK']}</span></p>
663
+ <p>防御力:<span class='stat-value'>{st.session_state.character['DEF']}</span></p>
664
+ <p>魔法力:<span class='stat-value'>{st.session_state.character['MAG']}</span></p>
665
+ <p>敏捷:<span class='stat-value'>{st.session_state.character['DEX']}</span></p>
666
+ <p>幸运:<span class='stat-value'>{st.session_state.character['LUK']}</span></p>
667
+ </div>
668
+ </div>
669
+ </div>
670
+ """, unsafe_allow_html=True)
671
+
672
+ # 添加属性雷达图
673
+ st.plotly_chart(create_character_stats_chart(st.session_state.character), use_container_width=True)
674
+
675
+ # 添加等级进度条
676
+ st.plotly_chart(create_level_progress_chart(st.session_state.character), use_container_width=True)
677
+
678
+ # 添加职业特点说明
679
+ st.markdown("### 职业特点")
680
+ class_descriptions = {
681
+ "战士": "擅长近战,拥有较高的攻击力和防御力,是团队中的主要输出和防御力量。",
682
+ "法师": "精通魔法,拥有强大的魔法攻击力,但防御较弱,需要队友保护。",
683
+ "刺客": "敏捷灵活,擅长暴击和闪避,是团队中的突袭手。",
684
+ "牧师": "精通治疗和辅助魔法,是团队中的重要支援角色。",
685
+ "弓箭手": "远程攻击专家,拥有较高的命中率,适合风筝战术。"
686
+ }
687
+ st.info(class_descriptions[st.session_state.character['class']])
688
+
689
+ elif st.session_state.current_tab == "角色培养":
690
+ if not st.session_state.character:
691
+ st.warning("请先创建角色")
692
+ else:
693
+ st.markdown("""
694
+ <div style='text-align: center;'>
695
+ <h2>角色培养</h2>
696
+ <p>通过每日登录和属性点分配来提升你的角色!</p>
697
+ </div>
698
+ """, unsafe_allow_html=True)
699
+
700
+ try:
701
+ # 检查每日登录奖励
702
+ last_login = st.session_state.character.get("last_login")
703
+ if last_login:
704
+ try:
705
+ # 尝试解析日期时间字符串
706
+ if isinstance(last_login, str):
707
+ last_login_dt = datetime.fromisoformat(last_login)
708
+ if datetime.now() - last_login_dt > timedelta(days=1):
709
+ # 更新等级和经验值
710
+ new_level = st.session_state.character["level"] + 1
711
+ new_exp = st.session_state.character["EXP"] + 100 # 每次升级增加100经验值
712
+
713
+ # 更新角色数据
714
+ update_data = {
715
+ "level": new_level,
716
+ "EXP": new_exp,
717
+ "last_login": datetime.now().isoformat()
718
+ }
719
+
720
+ if update_character_stats(st.session_state.character["Id"], update_data):
721
+ st.session_state.character["level"] = new_level
722
+ st.session_state.character["EXP"] = new_exp
723
+ st.session_state.character["last_login"] = datetime.now().isoformat()
724
+
725
+ st.markdown(f"""
726
+ <div class='success-message'>
727
+ 获得每日登录奖励:
728
+ <br>等级提升:{new_level-1} → {new_level}
729
+ <br>经验值增加:+100
730
+ <br>获得5点属性点!
731
+ </div>
732
+ """, unsafe_allow_html=True)
733
+ except (ValueError, TypeError):
734
+ # 如果解析失败,忽略日期检查
735
+ pass
736
+
737
+ # 属性点分配
738
+ st.markdown("### 属性点分配")
739
+ col1, col2 = st.columns(2)
740
+
741
+ with col1:
742
+ atk_points = st.number_input("攻击力", min_value=0, value=0)
743
+ def_points = st.number_input("防御力", min_value=0, value=0)
744
+ mag_points = st.number_input("魔法力", min_value=0, value=0)
745
+
746
+ with col2:
747
+ dex_points = st.number_input("敏捷", min_value=0, value=0)
748
+ luk_points = st.number_input("幸运", min_value=0, value=0)
749
+
750
+ if st.button("确认加点", use_container_width=True):
751
+ total_points = atk_points + def_points + mag_points + dex_points + luk_points
752
+ if total_points <= GAME_CONFIG["max_daily_points"]:
753
+ # 更新角色属性
754
+ st.session_state.character["ATK"] += atk_points
755
+ st.session_state.character["DEF"] += def_points
756
+ st.session_state.character["MAG"] += mag_points
757
+ st.session_state.character["DEX"] += dex_points
758
+ st.session_state.character["LUK"] += luk_points
759
+
760
+ if update_character_stats(st.session_state.character["Id"], {
761
+ "ATK": st.session_state.character["ATK"],
762
+ "DEF": st.session_state.character["DEF"],
763
+ "MAG": st.session_state.character["MAG"],
764
+ "DEX": st.session_state.character["DEX"],
765
+ "LUK": st.session_state.character["LUK"]
766
+ }):
767
+ st.success("属性点分配成功!")
768
+ else:
769
+ st.warning(f"每日最多只能分配{GAME_CONFIG['max_daily_points']}点属性点")
770
+ except Exception as e:
771
+ st.error(f"角色培养系统出错: {str(e)}")
772
+
773
+ elif st.session_state.current_tab == "PVP对战":
774
+ if not st.session_state.character:
775
+ st.warning("请先创建角色")
776
+ else:
777
+ st.markdown("""
778
+ <div style='text-align: center;'>
779
+ <h2>PVP对战</h2>
780
+ <p>与其他玩家进行对战,展示你的实力!</p>
781
+ </div>
782
+ """, unsafe_allow_html=True)
783
+
784
+ col1, col2 = st.columns(2)
785
+
786
+ with col1:
787
+ # 添加好友挑战选项
788
+ st.markdown("### 好友挑战")
789
+ try:
790
+ # 使用新的获取对手函数
791
+ available_opponents = get_available_opponents(st.session_state.user_id)
792
+
793
+ if available_opponents:
794
+ # 显示排行榜
795
+ st.markdown("### 好友排行榜")
796
+ ranking_table = create_friend_ranking_table(st.session_state.character, available_opponents)
797
+ if ranking_table is not None:
798
+ # 使用Streamlit的表格显示功能
799
+ st.dataframe(
800
+ ranking_table,
801
+ use_container_width=True,
802
+ hide_index=True,
803
+ column_config={
804
+ "排名": st.column_config.NumberColumn("排名", width="small"),
805
+ "角色名": st.column_config.TextColumn("角色名", width="medium"),
806
+ "职业": st.column_config.TextColumn("职业", width="small"),
807
+ "等级": st.column_config.NumberColumn("等级", width="small"),
808
+ "攻击力": st.column_config.NumberColumn("攻击力", width="small"),
809
+ "防御力": st.column_config.NumberColumn("防御力", width="small"),
810
+ "魔法力": st.column_config.NumberColumn("魔法力", width="small"),
811
+ "敏捷": st.column_config.NumberColumn("敏捷", width="small"),
812
+ "幸运": st.column_config.NumberColumn("幸运", width="small"),
813
+ "总属性": st.column_config.NumberColumn("总属性", width="small"),
814
+ "玩家": st.column_config.TextColumn("玩家", width="medium")
815
+ }
816
+ )
817
+
818
+ # 高亮显示当前玩家的行
819
+ current_player_row = ranking_table[ranking_table['角色名'] == st.session_state.character['name']]
820
+ if not current_player_row.empty:
821
+ st.markdown(f"""
822
+ <div style='background-color: #FFD700; padding: 10px; border-radius: 5px; margin-top: 10px;'>
823
+ <strong>你的排名:第{current_player_row['排名'].iloc[0]}名</strong>
824
+ </div>
825
+ """, unsafe_allow_html=True)
826
+
827
+ # 创建选择框
828
+ opponent_options = [f"{opp['name']} ({opp['class']}) - 等级{opp['level']} - 玩家:{opp['username']}" for opp in available_opponents]
829
+ selected_opponent = st.selectbox(
830
+ "选择要挑战的好友",
831
+ opponent_options,
832
+ index=0
833
+ )
834
+
835
+ if st.button("挑战好友", use_container_width=True):
836
+ selected_id = available_opponents[opponent_options.index(selected_opponent)]["id"]
837
+ try:
838
+ # 获取对手信息
839
+ opponents = nocodb_client.get_records("characters", {
840
+ "where": f"(Id,eq,{selected_id})"
841
+ })
842
+
843
+ if opponents:
844
+ opponent = opponents[0]
845
+ # 生成对战剧情
846
+ battle_story = together_client.generate_battle_story(
847
+ st.session_state.character,
848
+ opponent
849
+ )
850
+
851
+ # 保存对战记录
852
+ battle_record = {
853
+ "attacker": st.session_state.character["Id"],
854
+ "defender": opponent["Id"],
855
+ "battle_story": battle_story,
856
+ "created_time": datetime.now().isoformat()
857
+ }
858
+ nocodb_client.create_record("battles", battle_record)
859
+
860
+ # 显示对战剧情
861
+ st.markdown("### 对战剧情")
862
+ st.markdown(f"""
863
+ <div class='battle-log'>
864
+ {battle_story}
865
+ </div>
866
+ """, unsafe_allow_html=True)
867
+ except Exception as e:
868
+ st.error(f"对战失败: {str(e)}")
869
+ else:
870
+ st.info("当前没有可挑战的好友")
871
+ except Exception as e:
872
+ st.error(f"获取好友列表失败: {str(e)}")
873
+
874
+ st.markdown("---")
875
+ st.markdown("### 直接挑战")
876
+ opponent_id = st.text_input("输入对手的ID")
877
+ if st.button("开始对战", use_container_width=True):
878
+ if opponent_id:
879
+ try:
880
+ # 获取对手信息
881
+ opponents = nocodb_client.get_records("characters", {
882
+ "where": f"(Id,eq,{opponent_id})"
883
+ })
884
+
885
+ if opponents:
886
+ opponent = opponents[0]
887
+ # 生成对战剧情
888
+ battle_story = together_client.generate_battle_story(
889
+ st.session_state.character,
890
+ opponent
891
+ )
892
+
893
+ # 保存对战记录
894
+ battle_record = {
895
+ "attacker": st.session_state.character["Id"],
896
+ "defender": opponent["Id"],
897
+ "battle_story": battle_story,
898
+ "created_time": datetime.now().isoformat()
899
+ }
900
+ nocodb_client.create_record("battles", battle_record)
901
+
902
+ # 显示对战剧情
903
+ st.markdown("### 对战剧情")
904
+ st.markdown(f"""
905
+ <div class='battle-log'>
906
+ {battle_story}
907
+ </div>
908
+ """, unsafe_allow_html=True)
909
+ else:
910
+ st.warning("未找到对手")
911
+ except Exception as e:
912
+ st.error(f"对战失败: {str(e)}")
913
+ else:
914
+ st.warning("请输入对手的ID")
915
+
916
+ with col2:
917
+ st.markdown("### 最近对战记录")
918
+ try:
919
+ battles = get_battle_records(st.session_state.character["Id"])
920
+ if battles:
921
+ for battle in battles: # 显示最近5场对战
922
+ st.markdown(f"""
923
+ <div class='battle-log'>
924
+ <p>对战时间:{battle['created_time']}</p>
925
+ <p>对战剧情:{battle['battle_story'][:100]}...</p>
926
+ </div>
927
+ """, unsafe_allow_html=True)
928
+ else:
929
+ st.info("暂无对战记录")
930
+ except Exception as e:
931
+ st.error(f"获取对战记录失败: {str(e)}")
932
+
933
+ elif st.session_state.current_tab == "Boss挑战":
934
+ if not st.session_state.character:
935
+ st.warning("请先创建角色")
936
+ else:
937
+ st.markdown("""
938
+ <div style='text-align: center;'>
939
+ <h2>Boss挑战</h2>
940
+ <p>组队挑战强大的Boss,获取丰厚奖励!</p>
941
+ </div>
942
+ """, unsafe_allow_html=True)
943
+
944
+ col1, col2 = st.columns(2)
945
+
946
+ with col1:
947
+ # 显示当前Boss信息
948
+ st.markdown("### 当前Boss")
949
+ for boss in GAME_CONFIG["bosses"]:
950
+ st.markdown(f"""
951
+ <div class='character-card'>
952
+ <h3>{boss['name']}</h3>
953
+ <p>攻击力:<span class='stat-value'>{boss['ATK']}</span></p>
954
+ <p>防御力:<span class='stat-value'>{boss['DEF']}</span></p>
955
+ <p>魔法力:<span class='stat-value'>{boss['MAG']}</span></p>
956
+ <p>敏捷:<span class='stat-value'>{boss['DEX']}</span></p>
957
+ <p>幸运:<span class='stat-value'>{boss['LUK']}</span></p>
958
+ </div>
959
+ """, unsafe_allow_html=True)
960
+
961
+ # 组队挑战
962
+ team_member_ids = st.text_area("输入队友ID(每行一个)")
963
+ if st.button("开始挑战", use_container_width=True):
964
+ if team_member_ids:
965
+ try:
966
+ # 获取队友信息
967
+ team = [st.session_state.character]
968
+ for id in team_member_ids.strip().split("\n"):
969
+ members = nocodb_client.get_records("characters", {
970
+ "where": f"(Id,eq,{id})"
971
+ })
972
+ if members:
973
+ team.append(members[0])
974
+
975
+ if len(team) > 1:
976
+ # 生成Boss战剧情
977
+ boss_story = together_client.generate_boss_story(team, GAME_CONFIG["bosses"][0])
978
+
979
+ # 保存挑战记录
980
+ challenge_record = {
981
+ "team": ",".join([str(member["Id"]) for member in team]),
982
+ "boss_id": 1,
983
+ "story": boss_story,
984
+ "created_time": datetime.now().isoformat()
985
+ }
986
+ nocodb_client.create_record("boss_challenges", challenge_record)
987
+
988
+ # 显示挑战剧情
989
+ st.markdown("### 挑战剧情")
990
+ st.markdown(f"""
991
+ <div class='battle-log'>
992
+ {boss_story}
993
+ </div>
994
+ """, unsafe_allow_html=True)
995
+ else:
996
+ st.warning("需要至少一名队友")
997
+ except Exception as e:
998
+ st.error(f"挑战失败: {str(e)}")
999
+ else:
1000
+ st.warning("请输入队友ID")
1001
+
1002
+ with col2:
1003
+ st.markdown("### 最近挑战记录")
1004
+ try:
1005
+ challenges = get_boss_challenge_records(st.session_state.character["Id"])
1006
+ if challenges:
1007
+ for challenge in challenges: # 显示最近5场挑战
1008
+ st.markdown(f"""
1009
+ <div class='battle-log'>
1010
+ <p>挑战时间:{challenge['created_time']}</p>
1011
+ <p>挑战剧情:{challenge['story'][:100]}...</p>
1012
+ </div>
1013
+ """, unsafe_allow_html=True)
1014
+ else:
1015
+ st.info("暂无挑战记录")
1016
+ except Exception as e:
1017
+ st.error(f"获取挑战记录失败: {str(e)}")
1018
+
1019
+ # 页脚
1020
+ st.markdown("---")
1021
+ st.markdown("""
1022
+ <div style='text-align: center;'>
1023
+ <p>Powered by Together AI, NocoDB, and Hugging Face Spaces</p>
1024
+ </div>
1025
+ """, unsafe_allow_html=True)
config.py ADDED
@@ -0,0 +1,108 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from dotenv import load_dotenv
3
+
4
+ # 加载环境变量
5
+ load_dotenv()
6
+
7
+ # Together AI Configuration
8
+ TOGETHER_API_KEY = os.getenv("TOGETHER_API_KEY")
9
+ # 设置默认模型为 DeepSeek R1 Distill Llama 70B Free
10
+ TOGETHER_MODEL = "deepseek-ai/deepseek-r1-distill-llama-70b-free"
11
+
12
+ # NocoDB Configuration
13
+ NOCODB_API_KEY = os.getenv("NOCODB_API_KEY")
14
+ NOCODB_URL = os.getenv("NOCODB_URL")
15
+ NOCODB_PROJECT_ID = os.getenv("NOCODB_PROJECT_ID")
16
+
17
+ # Application Configuration
18
+ APP_TITLE = "RPG Adventure Game"
19
+ APP_DESCRIPTION = "An AI-powered RPG adventure game with character development, PVP battles, and Boss challenges"
20
+ AVAILABLE_MODELS = [
21
+ "deepseek-ai/deepseek-r1-distill-llama-70b-free",
22
+ "meta-llama/Llama-3.3-70B-Instruct-Turbo-Free",
23
+ "mistralai/Mixtral-8x7B-Instruct-v0.1",
24
+ "meta-llama/Llama-3.3-70B-Instruct"
25
+ ]
26
+
27
+ # Game Configuration
28
+ GAME_CONFIG = {
29
+ "max_daily_points": 5, # 每日最大属性点
30
+ "base_stats": { # 基础属性
31
+ "ATK": 10,
32
+ "DEF": 10,
33
+ "MAG": 10,
34
+ "DEX": 10,
35
+ "LUK": 10
36
+ },
37
+ "classes": [ # 可用职业
38
+ "Warrior",
39
+ "Mage",
40
+ "Rogue",
41
+ "Priest",
42
+ "Archer"
43
+ ],
44
+ "class_base_stats": { # 各职业初始属性
45
+ "Warrior": {
46
+ "ATK": 15, # 高攻击力
47
+ "DEF": 15, # 高防御力
48
+ "MAG": 5, # 低魔法力
49
+ "DEX": 8, # 中等敏捷
50
+ "LUK": 7 # 中等幸运
51
+ },
52
+ "Mage": {
53
+ "ATK": 5, # 低攻击力
54
+ "DEF": 7, # 低防御力
55
+ "MAG": 20, # 高魔法力
56
+ "DEX": 8, # 中等敏捷
57
+ "LUK": 10 # 较高幸运
58
+ },
59
+ "Rogue": {
60
+ "ATK": 12, # 较高攻击力
61
+ "DEF": 7, # 低防御力
62
+ "MAG": 8, # 中等魔法力
63
+ "DEX": 18, # 高敏捷
64
+ "LUK": 15 # 高幸运
65
+ },
66
+ "Priest": {
67
+ "ATK": 6, # 低攻击力
68
+ "DEF": 12, # 较高防御力
69
+ "MAG": 15, # 高魔法力
70
+ "DEX": 8, # 中等敏捷
71
+ "LUK": 9 # 中等幸运
72
+ },
73
+ "Archer": {
74
+ "ATK": 12, # 较高攻击力
75
+ "DEF": 8, # 中等防御力
76
+ "MAG": 7, # 低魔法力
77
+ "DEX": 15, # 高敏捷
78
+ "LUK": 8 # 中等幸运
79
+ }
80
+ },
81
+ "bosses": [ # 预设Boss
82
+ {
83
+ "id": 1,
84
+ "name": "Shadow Lord",
85
+ "ATK": 100,
86
+ "DEF": 80,
87
+ "MAG": 120,
88
+ "DEX": 60,
89
+ "LUK": 40
90
+ }
91
+ ]
92
+ }
93
+
94
+ def validate_config():
95
+ """Validate required environment variables"""
96
+ required_vars = {
97
+ "TOGETHER_API_KEY": TOGETHER_API_KEY,
98
+ "NOCODB_API_KEY": NOCODB_API_KEY,
99
+ "NOCODB_URL": NOCODB_URL,
100
+ "NOCODB_PROJECT_ID": NOCODB_PROJECT_ID
101
+ }
102
+
103
+ missing_vars = [var for var, value in required_vars.items() if not value]
104
+
105
+ if missing_vars:
106
+ raise ValueError(f"Missing required environment variables: {', '.join(missing_vars)}")
107
+
108
+ validate_config()
database/__pycache__/nocodb.cpython-39.pyc ADDED
Binary file (4.95 kB). View file
 
database/nocodb.py ADDED
@@ -0,0 +1,133 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import requests
2
+ from typing import Dict, List, Any, Optional
3
+ from config import NOCODB_API_KEY, NOCODB_URL, NOCODB_PROJECT_ID
4
+ from datetime import datetime
5
+ import time
6
+ import json
7
+
8
+ class NocoDBClient:
9
+ def __init__(self):
10
+ self.base_url = NOCODB_URL.rstrip('/')
11
+ self.headers = {
12
+ "xc-token": NOCODB_API_KEY,
13
+ "Content-Type": "application/json"
14
+ }
15
+ self.project_id = NOCODB_PROJECT_ID
16
+ self.cache = {}
17
+ self.cache_timeout = 300 # Cache timeout in seconds
18
+ print(f"Initializing NocoDB client:")
19
+ print(f"Base URL: {self.base_url}")
20
+ print(f"Project ID: {self.project_id}")
21
+ print(f"Headers: {self.headers}")
22
+
23
+ def _get_cache_key(self, table_name: str, params: Optional[Dict[str, Any]] = None) -> str:
24
+ """Generate cache key"""
25
+ if params:
26
+ params_str = json.dumps(params, sort_keys=True)
27
+ return f"{table_name}:{params_str}"
28
+ return table_name
29
+
30
+ def _is_cache_valid(self, cache_key: str) -> bool:
31
+ """Check if cache is valid"""
32
+ if cache_key in self.cache:
33
+ cache_time, _ = self.cache[cache_key]
34
+ return time.time() - cache_time < self.cache_timeout
35
+ return False
36
+
37
+ def _get_cached_data(self, cache_key: str) -> Optional[List[Dict[str, Any]]]:
38
+ """Get cached data"""
39
+ if self._is_cache_valid(cache_key):
40
+ return self.cache[cache_key][1]
41
+ return None
42
+
43
+ def _set_cache_data(self, cache_key: str, data: List[Dict[str, Any]]) -> None:
44
+ """Set cache data"""
45
+ self.cache[cache_key] = (time.time(), data)
46
+
47
+ def _get_endpoint(self, table_name: str) -> str:
48
+ """Build API endpoint URL"""
49
+ return f"{self.base_url}/api/v1/db/data/v1/{self.project_id}/{table_name}"
50
+
51
+ def create_record(self, table_name: str, data: Dict[str, Any]) -> Dict[str, Any]:
52
+ """Create new record"""
53
+ data['created_time'] = datetime.now().isoformat()
54
+ endpoint = self._get_endpoint(table_name)
55
+ try:
56
+ response = requests.post(endpoint, json=data, headers=self.headers)
57
+ response.raise_for_status()
58
+ self.cache.clear()
59
+ return response.json()
60
+ except requests.exceptions.RequestException as e:
61
+ print(f"Failed to create record: {str(e)}")
62
+ if hasattr(e.response, 'text'):
63
+ print(f"Error response: {e.response.text}")
64
+ raise
65
+
66
+ def get_records(self, table_name: str, params: Optional[Dict[str, Any]] = None) -> List[Dict[str, Any]]:
67
+ """Get records with caching"""
68
+ cache_key = self._get_cache_key(table_name, params)
69
+ cached_data = self._get_cached_data(cache_key)
70
+ if cached_data is not None:
71
+ return cached_data
72
+
73
+ endpoint = self._get_endpoint(table_name)
74
+ default_params = {
75
+ "limit": "25",
76
+ "offset": "0"
77
+ }
78
+
79
+ if params and "where" in params:
80
+ where_condition = params["where"]
81
+ if where_condition.startswith("(") and where_condition.endswith(")"):
82
+ where_condition = where_condition[1:-1]
83
+ parts = where_condition.split(",")
84
+ if len(parts) == 3:
85
+ field, op, value = parts
86
+ if "-" in value or not value.isalnum():
87
+ value = f"'{value}'"
88
+ params["where"] = f"({field},{op},{value})"
89
+
90
+ if params:
91
+ default_params.update(params)
92
+
93
+ try:
94
+ response = requests.get(endpoint, params=default_params, headers=self.headers)
95
+ response.raise_for_status()
96
+ data = response.json().get("list", [])
97
+ self._set_cache_data(cache_key, data)
98
+ return data
99
+ except requests.exceptions.RequestException as e:
100
+ print(f"Failed to get records: {str(e)}")
101
+ if hasattr(e.response, 'text'):
102
+ print(f"Error response: {e.response.text}")
103
+ raise
104
+
105
+ def update_record(self, table_name: str, record_id: str, data: Dict[str, Any]) -> Dict[str, Any]:
106
+ """Update record"""
107
+ endpoint = f"{self._get_endpoint(table_name)}/{record_id}"
108
+ try:
109
+ response = requests.patch(endpoint, json=data, headers=self.headers)
110
+ response.raise_for_status()
111
+ self.cache.clear()
112
+ return response.json()
113
+ except requests.exceptions.RequestException as e:
114
+ print(f"Failed to update record: {str(e)}")
115
+ if hasattr(e.response, 'text'):
116
+ print(f"Error response: {e.response.text}")
117
+ raise
118
+
119
+ def delete_record(self, table_name: str, record_id: int) -> None:
120
+ """Delete record"""
121
+ endpoint = f"{self._get_endpoint(table_name)}/{record_id}"
122
+ try:
123
+ response = requests.delete(endpoint, headers=self.headers)
124
+ response.raise_for_status()
125
+ self.cache.clear()
126
+ except requests.exceptions.RequestException as e:
127
+ print(f"Failed to delete record: {str(e)}")
128
+ if hasattr(e.response, 'text'):
129
+ print(f"Error response: {e.response.text}")
130
+ raise
131
+
132
+ # 创建默认的 NocoDB 客户端实例
133
+ nocodb_client = NocoDBClient()
llm/__pycache__/together.cpython-39.pyc ADDED
Binary file (4.89 kB). View file
 
llm/together.py ADDED
@@ -0,0 +1,123 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import together
2
+ from typing import Dict, Any, List, Optional
3
+ from config import TOGETHER_API_KEY, TOGETHER_MODEL, AVAILABLE_MODELS
4
+
5
+ class TogetherAIClient:
6
+ def __init__(self):
7
+ together.api_key = TOGETHER_API_KEY
8
+ self.model = TOGETHER_MODEL
9
+
10
+ def generate_battle_story(self, attacker: Dict[str, Any], defender: Dict[str, Any]) -> str:
11
+ """Generate battle story"""
12
+ prompt = f"""You are an RPG game battle story generator. Generate a battle story and record directly, without any thought process.
13
+
14
+ Battle Information:
15
+ Attacker: {attacker['name']} ({attacker['class']})
16
+ - Attack: {attacker['ATK']}
17
+ - Defense: {attacker['DEF']}
18
+ - Magic: {attacker['MAG']}
19
+ - Dexterity: {attacker['DEX']}
20
+ - Luck: {attacker['LUK']}
21
+
22
+ Defender: {defender['name']} ({defender['class']})
23
+ - Attack: {defender['ATK']}
24
+ - Defense: {defender['DEF']}
25
+ - Magic: {defender['MAG']}
26
+ - Dexterity: {defender['DEX']}
27
+ - Luck: {defender['LUK']}
28
+
29
+ Output format:
30
+ Battle Process: [Describe 1-2 key rounds of battle]
31
+ Battle Result: [Clearly state who won]
32
+
33
+ Requirements:
34
+ 1. Only output detailed and epic battle process and result
35
+ 2. Keep total length under 200 words
36
+ 3. Use precise language
37
+ 4. No thought process or analysis"""
38
+
39
+ return self.generate_response(prompt, max_tokens=500, temperature=0.7)
40
+
41
+ def generate_boss_story(self, team: List[Dict[str, Any]], boss: Dict[str, Any]) -> str:
42
+ """Generate Boss battle story"""
43
+ team_info = "\n".join([
44
+ f"- {member['name']} ({member['class']}): ATK {member['ATK']}, DEF {member['DEF']}, MAG {member['MAG']}, DEX {member['DEX']}, LUK {member['LUK']}"
45
+ for member in team
46
+ ])
47
+
48
+ prompt = f"""You are an RPG game Boss battle record generator. Generate a battle record directly, without any thought process.
49
+
50
+ Battle Information:
51
+ Team Members:
52
+ {team_info}
53
+
54
+ Boss: {boss['name']}
55
+ - Attack: {boss['ATK']}
56
+ - Defense: {boss['DEF']}
57
+ - Magic: {boss['MAG']}
58
+ - Dexterity: {boss['DEX']}
59
+ - Luck: {boss['LUK']}
60
+
61
+ Output format:
62
+ Battle Process: [Describe 1-2 key rounds of battle]
63
+ Battle Result: [Clearly state if Boss was defeated]
64
+
65
+ Requirements:
66
+ 1. Only output battle process and result
67
+ 2. Keep total length under 150 words
68
+ 3. Use concise language
69
+ 4. No thought process or analysis"""
70
+
71
+ return self.generate_response(prompt, max_tokens=800, temperature=0.7)
72
+
73
+ def generate_response(
74
+ self,
75
+ prompt: str,
76
+ max_tokens: int = 1024,
77
+ temperature: float = 0.7,
78
+ top_p: float = 0.7,
79
+ top_k: int = 50,
80
+ repetition_penalty: float = 1.1,
81
+ stop: Optional[List[str]] = None
82
+ ) -> str:
83
+ try:
84
+ params = {
85
+ "model": self.model,
86
+ "prompt": prompt,
87
+ "max_tokens": max_tokens,
88
+ "temperature": temperature,
89
+ "top_p": top_p,
90
+ "top_k": top_k,
91
+ "repetition_penalty": repetition_penalty,
92
+ }
93
+ if stop:
94
+ params["stop"] = stop
95
+
96
+ response = together.Complete.create(**params)
97
+ if isinstance(response, dict) and 'output' in response:
98
+ return response['output']['choices'][0]['text']
99
+ elif isinstance(response, dict) and 'choices' in response:
100
+ return response['choices'][0]['text']
101
+ else:
102
+ raise Exception(f"Unexpected response format: {response}")
103
+
104
+ except Exception as e:
105
+ raise Exception(f"Together AI API call failed: {str(e)}")
106
+
107
+ def get_available_models(self) -> List[Dict[str, Any]]:
108
+ """Get available models list"""
109
+ try:
110
+ return [{"name": model} for model in AVAILABLE_MODELS]
111
+ except Exception as e:
112
+ print(f"Failed to get models list: {str(e)}")
113
+ return [{"name": TOGETHER_MODEL}]
114
+
115
+ def set_model(self, model_name: str) -> None:
116
+ """Set model to use"""
117
+ if model_name in AVAILABLE_MODELS:
118
+ self.model = model_name
119
+ else:
120
+ print(f"Warning: Model {model_name} not in available list, using default model")
121
+ self.model = TOGETHER_MODEL
122
+
123
+ together_client = TogetherAIClient()
requirements.txt ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ streamlit>=1.24.0
2
+ plotly>=5.13.0
3
+ pandas>=1.5.0
4
+ python-dotenv>=0.19.0
5
+ requests>=2.28.0
6
+ together>=0.1.5