XiaoBai1221 commited on
Commit
65e575b
·
1 Parent(s): 921a78a
Files changed (2) hide show
  1. core/config.py +35 -9
  2. static/frontend/index.html +20 -12
core/config.py CHANGED
@@ -5,6 +5,7 @@ Bloom Ware 統一配置管理中心
5
 
6
  import os
7
  import json
 
8
  from typing import Optional, Dict, Any
9
  from dotenv import load_dotenv
10
 
@@ -22,8 +23,9 @@ class Settings:
22
  # ===== Firebase 配置 =====
23
  FIREBASE_PROJECT_ID: str = os.getenv("FIREBASE_PROJECT_ID", "")
24
 
25
- # Firebase 憑證:優先使用環境變數 JSON(生產環境),否則使用檔案路徑(開發環境)
26
  _firebase_creds_json: Optional[str] = os.getenv("FIREBASE_CREDENTIALS_JSON")
 
27
  _firebase_service_account_path: Optional[str] = os.getenv("FIREBASE_SERVICE_ACCOUNT_PATH")
28
 
29
  @classmethod
@@ -32,23 +34,34 @@ class Settings:
32
  取得 Firebase 憑證
33
 
34
  優先順序:
35
- 1. 環境變數 FIREBASE_CREDENTIALS_JSON(生產環境,Render 部署
36
- 2. 檔案路徑 FIREBASE_SERVICE_ACCOUNT_PATH(開發環境)
 
37
 
38
  Returns:
39
  dict: Firebase Service Account 憑證字典
40
 
41
  Raises:
42
- ValueError: 當兩種方式都未設定時
43
  """
 
44
  if cls._firebase_creds_json:
45
- # 生產環境:從環境變數讀取完整 JSON 字串
46
  try:
47
  return json.loads(cls._firebase_creds_json)
48
  except json.JSONDecodeError as e:
49
  raise ValueError(f"FIREBASE_CREDENTIALS_JSON 格式錯誤: {e}")
 
 
 
 
 
 
 
 
 
 
 
50
  elif cls._firebase_service_account_path:
51
- # 開發環境:從檔案讀取
52
  try:
53
  with open(cls._firebase_service_account_path, 'r', encoding='utf-8') as f:
54
  return json.load(f)
@@ -56,12 +69,15 @@ class Settings:
56
  raise ValueError(f"Firebase 憑證檔案不存在: {cls._firebase_service_account_path}")
57
  except json.JSONDecodeError as e:
58
  raise ValueError(f"Firebase 憑證檔案格式錯誤: {e}")
 
 
59
  else:
60
  raise ValueError(
61
  "Firebase 憑證未設定!\n"
62
  "請設定以下其中一項:\n"
63
- "1. FIREBASE_CREDENTIALS_JSON(生產環境)\n"
64
- "2. FIREBASE_SERVICE_ACCOUNT_PATH開發環境)"
 
65
  )
66
 
67
  # ===== OpenAI 配置 =====
@@ -155,7 +171,17 @@ class Settings:
155
  print(f"環境模式: {cls.ENVIRONMENT}")
156
  print(f"是否為生產環境: {cls.IS_PRODUCTION}")
157
  print(f"Firebase 專案 ID: {cls.FIREBASE_PROJECT_ID}")
158
- print(f"Firebase 憑證來源: {'環境變數' if cls._firebase_creds_json else '檔案'}")
 
 
 
 
 
 
 
 
 
 
159
  print(f"OpenAI 模型: {cls.OPENAI_MODEL}")
160
  print(f"OpenAI Timeout: {cls.OPENAI_TIMEOUT}s")
161
  print(f"Google OAuth 回調 URI: {cls.GOOGLE_REDIRECT_URI}")
 
5
 
6
  import os
7
  import json
8
+ import base64
9
  from typing import Optional, Dict, Any
10
  from dotenv import load_dotenv
11
 
 
23
  # ===== Firebase 配置 =====
24
  FIREBASE_PROJECT_ID: str = os.getenv("FIREBASE_PROJECT_ID", "")
25
 
26
+ # Firebase 憑證:支援三種方式
27
  _firebase_creds_json: Optional[str] = os.getenv("FIREBASE_CREDENTIALS_JSON")
28
+ _firebase_creds_base64: Optional[str] = os.getenv("FIREBASE_SERVICE_ACCOUNT_JSON_BASE64")
29
  _firebase_service_account_path: Optional[str] = os.getenv("FIREBASE_SERVICE_ACCOUNT_PATH")
30
 
31
  @classmethod
 
34
  取得 Firebase 憑證
35
 
36
  優先順序:
37
+ 1. 環境變數 FIREBASE_CREDENTIALS_JSON(生產環境,JSON 字串
38
+ 2. 環境變數 FIREBASE_SERVICE_ACCOUNT_JSON_BASE64(base64 編碼的 JSON
39
+ 3. 檔案路徑 FIREBASE_SERVICE_ACCOUNT_PATH(開發環境)
40
 
41
  Returns:
42
  dict: Firebase Service Account 憑證字典
43
 
44
  Raises:
45
+ ValueError: 當所有方式都未設定時
46
  """
47
+ # 方式 1: 直接 JSON 字串
48
  if cls._firebase_creds_json:
 
49
  try:
50
  return json.loads(cls._firebase_creds_json)
51
  except json.JSONDecodeError as e:
52
  raise ValueError(f"FIREBASE_CREDENTIALS_JSON 格式錯誤: {e}")
53
+
54
+ # 方式 2: Base64 編碼的 JSON
55
+ elif cls._firebase_creds_base64:
56
+ try:
57
+ decoded_bytes = base64.b64decode(cls._firebase_creds_base64)
58
+ decoded_str = decoded_bytes.decode('utf-8')
59
+ return json.loads(decoded_str)
60
+ except Exception as e:
61
+ raise ValueError(f"FIREBASE_SERVICE_ACCOUNT_JSON_BASE64 解碼失敗: {e}")
62
+
63
+ # 方式 3: 從檔案讀取
64
  elif cls._firebase_service_account_path:
 
65
  try:
66
  with open(cls._firebase_service_account_path, 'r', encoding='utf-8') as f:
67
  return json.load(f)
 
69
  raise ValueError(f"Firebase 憑證檔案不存在: {cls._firebase_service_account_path}")
70
  except json.JSONDecodeError as e:
71
  raise ValueError(f"Firebase 憑證檔案格式錯誤: {e}")
72
+
73
+ # 三種方式都沒設定
74
  else:
75
  raise ValueError(
76
  "Firebase 憑證未設定!\n"
77
  "請設定以下其中一項:\n"
78
+ "1. FIREBASE_CREDENTIALS_JSON(JSON 字串)\n"
79
+ "2. FIREBASE_SERVICE_ACCOUNT_JSON_BASE64base64 編碼\n"
80
+ "3. FIREBASE_SERVICE_ACCOUNT_PATH(檔案路徑)"
81
  )
82
 
83
  # ===== OpenAI 配置 =====
 
171
  print(f"環境模式: {cls.ENVIRONMENT}")
172
  print(f"是否為生產環境: {cls.IS_PRODUCTION}")
173
  print(f"Firebase 專案 ID: {cls.FIREBASE_PROJECT_ID}")
174
+
175
+ # 判斷 Firebase 憑證來源
176
+ if cls._firebase_creds_json:
177
+ firebase_source = "環境變數 (JSON)"
178
+ elif cls._firebase_creds_base64:
179
+ firebase_source = "環境變數 (Base64)"
180
+ elif cls._firebase_service_account_path:
181
+ firebase_source = "檔案"
182
+ else:
183
+ firebase_source = "未設定 ❌"
184
+ print(f"Firebase 憑證來源: {firebase_source}")
185
  print(f"OpenAI 模型: {cls.OPENAI_MODEL}")
186
  print(f"OpenAI Timeout: {cls.OPENAI_TIMEOUT}s")
187
  print(f"Google OAuth 回調 URI: {cls.GOOGLE_REDIRECT_URI}")
static/frontend/index.html CHANGED
@@ -20,9 +20,12 @@
20
  color: #1A1A1A;
21
  overflow: hidden;
22
  -webkit-font-smoothing: antialiased;
23
- /* 修復 2025 新版瀏覽器 viewport 高度問題 */
24
- height: 100vh; /* fallback for older browsers */
25
- height: 100svh; /* 使用 Small Viewport Height 確保固定高度 */
 
 
 
26
  }
27
 
28
  /* === 控制面板 === */
@@ -119,10 +122,13 @@
119
  left: 0;
120
  right: 0;
121
  bottom: 0;
 
122
  width: 100vw;
123
- width: 100svw; /* Small Viewport Width */
124
- height: 100vh; /* fallback */
125
- height: 100svh; /* Small Viewport Height - 確保在行動裝置上固定可見 */
 
 
126
  z-index: 100;
127
  display: flex;
128
  flex-direction: column;
@@ -1098,9 +1104,9 @@
1098
  /* ChatWindow 折疊圖標 */
1099
  .chat-icon {
1100
  position: fixed;
1101
- /* 使用 calc 搭 svh 確保在所有裝置上都固定在底部 */
1102
- bottom: max(40px, env(safe-area-inset-bottom, 0px));
1103
- left: max(40px, env(safe-area-inset-left, 0px));
1104
  z-index: 101;
1105
  width: 56px;
1106
  height: 56px;
@@ -1126,8 +1132,9 @@
1126
  /* 退出按鈕 */
1127
  .exit-button {
1128
  position: absolute;
1129
- top: max(40px, env(safe-area-inset-top, 0px));
1130
- right: max(40px, env(safe-area-inset-right, 0px));
 
1131
  z-index: 50;
1132
  padding: 12px 20px;
1133
  background: rgba(255, 255, 255, 0.95);
@@ -1153,7 +1160,8 @@
1153
  /* 情緒標籤 */
1154
  .emotion-indicator {
1155
  position: absolute;
1156
- top: max(40px, env(safe-area-inset-top, 0px));
 
1157
  left: 50%;
1158
  transform: translateX(-50%);
1159
  z-index: 50;
 
20
  color: #1A1A1A;
21
  overflow: hidden;
22
  -webkit-font-smoothing: antialiased;
23
+ /* 2025 最佳實踐:多層 fallback 確保相容性 */
24
+ height: 100vh; /* fallback 1: 傳統瀏覽器 */
25
+ height: 100svh; /* fallback 2: 小視窗高度 */
26
+ height: 100dvh; /* 主要方案: 動態視窗高度(iframe 最佳) */
27
+ /* 針對 WebKit 瀏覽器的額外修正 */
28
+ height: -webkit-fill-available;
29
  }
30
 
31
  /* === 控制面板 === */
 
122
  left: 0;
123
  right: 0;
124
  bottom: 0;
125
+ /* 2025 最佳實踐:漸進式增強 */
126
  width: 100vw;
127
+ width: 100dvw; /* 動態寬度(iframe 最佳) */
128
+ height: 100vh; /* fallback 1 */
129
+ height: 100svh; /* fallback 2 */
130
+ height: 100dvh; /* 主要方案 */
131
+ height: -webkit-fill-available; /* WebKit 修正 */
132
  z-index: 100;
133
  display: flex;
134
  flex-direction: column;
 
1104
  /* ChatWindow 折疊圖標 */
1105
  .chat-icon {
1106
  position: fixed;
1107
+ /* iframe 環境適:使用固定值避免 viewport 計算問題 */
1108
+ bottom: 40px;
1109
+ left: 40px;
1110
  z-index: 101;
1111
  width: 56px;
1112
  height: 56px;
 
1132
  /* 退出按鈕 */
1133
  .exit-button {
1134
  position: absolute;
1135
+ /* iframe 環境適配:使用固定值 */
1136
+ top: 40px;
1137
+ right: 40px;
1138
  z-index: 50;
1139
  padding: 12px 20px;
1140
  background: rgba(255, 255, 255, 0.95);
 
1160
  /* 情緒標籤 */
1161
  .emotion-indicator {
1162
  position: absolute;
1163
+ /* iframe 環境適配:使用固定值 */
1164
+ top: 40px;
1165
  left: 50%;
1166
  transform: translateX(-50%);
1167
  z-index: 50;