sirochild commited on
Commit
9009e44
·
verified ·
1 Parent(s): 8b8ef1f

Upload 3 files

Browse files
async_storage_manager.py CHANGED
@@ -35,6 +35,10 @@ class AsyncStorageManager:
35
  self.file_path.parent.mkdir(parents=True, exist_ok=True)
36
  self.backup_dir.mkdir(parents=True, exist_ok=True)
37
 
 
 
 
 
38
  # 初期データ構造
39
  self.default_data = {
40
  "users": {},
@@ -45,20 +49,30 @@ class AsyncStorageManager:
45
  }
46
  }
47
 
48
- async def load_data(self) -> Dict[str, Any]:
49
- """データファイルを読み込み"""
50
  async with self.lock:
 
 
 
 
51
  try:
52
  if not self.file_path.exists():
53
- logger.info("データファイルが存在しないため、初期データを作成します")
 
54
  await self._save_data_unsafe(self.default_data)
55
- return self.default_data.copy()
 
 
56
 
57
  # ファイルサイズチェック
58
  if self.file_path.stat().st_size == 0:
59
- logger.warning("データファイルが空のため、初期データを作成します")
 
60
  await self._save_data_unsafe(self.default_data)
61
- return self.default_data.copy()
 
 
62
 
63
  # JSONファイルの読み込み
64
  with open(self.file_path, 'r', encoding='utf-8') as f:
@@ -67,7 +81,14 @@ class AsyncStorageManager:
67
  # データ構造の検証と修復
68
  data = self._validate_and_repair_data(data)
69
 
70
- logger.info(f"データファイルを正常に読み込みました: {self.file_path}")
 
 
 
 
 
 
 
71
  return data
72
 
73
  except json.JSONDecodeError as e:
@@ -83,6 +104,9 @@ class AsyncStorageManager:
83
  """データファイルに保存"""
84
  async with self.lock:
85
  await self._save_data_unsafe(data)
 
 
 
86
 
87
  async def _save_data_unsafe(self, data: Dict[str, Any]) -> None:
88
  """ロックなしでデータを保存(内部使用)"""
@@ -99,7 +123,11 @@ class AsyncStorageManager:
99
  # アトミックな移動
100
  shutil.move(str(temp_path), str(self.file_path))
101
 
102
- logger.info(f"データを正常に保存しました: {self.file_path}")
 
 
 
 
103
 
104
  except Exception as e:
105
  logger.error(f"データ保存エラー: {e}")
@@ -199,13 +227,20 @@ class AsyncStorageManager:
199
  # 復旧したデータを保存
200
  await self._save_data_unsafe(data)
201
 
 
 
 
 
202
  return data
203
 
204
  except Exception as e:
205
  logger.error(f"バックアップからの復旧に失敗: {e}")
206
  logger.info("初期データを使用します")
207
  await self._save_data_unsafe(self.default_data)
208
- return self.default_data.copy()
 
 
 
209
 
210
  async def _cleanup_old_backups(self, days: int = 7) -> None:
211
  """古いバックアップファイルを削除"""
@@ -362,6 +397,16 @@ class AsyncStorageManager:
362
  except Exception as e:
363
  logger.error(f"統計情報の取得エラー: {e}")
364
  return {}
 
 
 
 
 
 
 
 
 
 
365
 
366
 
367
  # テスト用の関数
 
35
  self.file_path.parent.mkdir(parents=True, exist_ok=True)
36
  self.backup_dir.mkdir(parents=True, exist_ok=True)
37
 
38
+ # データキャッシュ(重複読み込み防止)
39
+ self._cached_data = None
40
+ self._data_loaded = False
41
+
42
  # 初期データ構造
43
  self.default_data = {
44
  "users": {},
 
49
  }
50
  }
51
 
52
+ async def load_data(self, force_reload: bool = False) -> Dict[str, Any]:
53
+ """データファイルを読み込み(キャッシュ機能付き)"""
54
  async with self.lock:
55
+ # キャッシュされたデータがあり、強制リロードでない場合はキャッシュを返す
56
+ if self._data_loaded and self._cached_data is not None and not force_reload:
57
+ return self._cached_data.copy()
58
+
59
  try:
60
  if not self.file_path.exists():
61
+ if not self._data_loaded: # 初回のみログ出力
62
+ logger.debug("データファイルが存在しないため、初期データを作成します")
63
  await self._save_data_unsafe(self.default_data)
64
+ self._cached_data = self.default_data.copy()
65
+ self._data_loaded = True
66
+ return self._cached_data.copy()
67
 
68
  # ファイルサイズチェック
69
  if self.file_path.stat().st_size == 0:
70
+ if not self._data_loaded: # 初回のみログ出力
71
+ logger.warning("データファイルが空のため、初期データを作成します")
72
  await self._save_data_unsafe(self.default_data)
73
+ self._cached_data = self.default_data.copy()
74
+ self._data_loaded = True
75
+ return self._cached_data.copy()
76
 
77
  # JSONファイルの読み込み
78
  with open(self.file_path, 'r', encoding='utf-8') as f:
 
81
  # データ構造の検証と修復
82
  data = self._validate_and_repair_data(data)
83
 
84
+ # キャッシュを更新
85
+ self._cached_data = data.copy()
86
+
87
+ # 初回のみログ出力
88
+ if not self._data_loaded:
89
+ logger.debug(f"データファイルを正常に読み込みました: {self.file_path}")
90
+ self._data_loaded = True
91
+
92
  return data
93
 
94
  except json.JSONDecodeError as e:
 
104
  """データファイルに保存"""
105
  async with self.lock:
106
  await self._save_data_unsafe(data)
107
+ # キャッシュを更新
108
+ self._cached_data = data.copy()
109
+ self._data_loaded = True
110
 
111
  async def _save_data_unsafe(self, data: Dict[str, Any]) -> None:
112
  """ロックなしでデータを保存(内部使用)"""
 
123
  # アトミックな移動
124
  shutil.move(str(temp_path), str(self.file_path))
125
 
126
+ # キャッシュを更新
127
+ self._cached_data = validated_data.copy()
128
+ self._data_loaded = True
129
+
130
+ logger.debug(f"データを正常に保存しました: {self.file_path}")
131
 
132
  except Exception as e:
133
  logger.error(f"データ保存エラー: {e}")
 
227
  # 復旧したデータを保存
228
  await self._save_data_unsafe(data)
229
 
230
+ # キャッシュを更新
231
+ self._cached_data = data.copy()
232
+ self._data_loaded = True
233
+
234
  return data
235
 
236
  except Exception as e:
237
  logger.error(f"バックアップからの復旧に失敗: {e}")
238
  logger.info("初期データを使用します")
239
  await self._save_data_unsafe(self.default_data)
240
+ # キャッシュを更新
241
+ self._cached_data = self.default_data.copy()
242
+ self._data_loaded = True
243
+ return self._cached_data.copy()
244
 
245
  async def _cleanup_old_backups(self, days: int = 7) -> None:
246
  """古いバックアップファイルを削除"""
 
397
  except Exception as e:
398
  logger.error(f"統計情報の取得エラー: {e}")
399
  return {}
400
+
401
+ def invalidate_cache(self) -> None:
402
+ """キャッシュを無効化(外部からファイルが変更された場合など)"""
403
+ self._cached_data = None
404
+ self._data_loaded = False
405
+ logger.debug("データキャッシュを無効化しました")
406
+
407
+ async def reload_data(self) -> Dict[str, Any]:
408
+ """データを強制的に再読み込み"""
409
+ return await self.load_data(force_reload=True)
410
 
411
 
412
  # テスト用の関数
components_chat_interface.py CHANGED
@@ -792,7 +792,21 @@ class ChatInterface:
792
  st.chat_input(placeholder, disabled=True)
793
  return None
794
 
795
- # 通常の入力フィールド
 
 
 
 
 
 
 
 
 
 
 
 
 
 
796
  user_input = st.chat_input(placeholder)
797
 
798
  if user_input:
 
792
  st.chat_input(placeholder, disabled=True)
793
  return None
794
 
795
+ # 通常の入力フィールド(autocomplete属性付き)
796
+ # Streamlitのchat_inputにautocomplete属性を追加するためのCSS
797
+ chat_input_css = """
798
+ <style>
799
+ /* チャット入力フィールドのautocomplete属性を有効化 */
800
+ .stChatInput input[type="text"] {
801
+ autocomplete: on !important;
802
+ }
803
+ .stChatInput textarea {
804
+ autocomplete: on !important;
805
+ }
806
+ </style>
807
+ """
808
+ st.markdown(chat_input_css, unsafe_allow_html=True)
809
+
810
  user_input = st.chat_input(placeholder)
811
 
812
  if user_input:
main_app.py CHANGED
@@ -181,21 +181,11 @@ def run_async(coro):
181
  return asyncio.run(coro)
182
 
183
  def update_background(scene_manager: SceneManager, theme: str):
184
- """現在のテーマに基づいて背景画像を動的に設定するCSSを注入する(重複実行防止)"""
185
- # 重複実行防止: 同じテーマで既に更新済みの場合はスキップ
186
- current_background_theme = st.session_state.get('current_background_theme', None)
187
- if current_background_theme == theme:
188
- logger.debug(f"背景更新スキップ - 既に同じテーマ適用済み: {theme}")
189
- return
190
-
191
- logger.info(f"背景更新開始 - テーマ: {theme}")
192
-
193
- # 背景更新中フラグを設定(重複呼び出し防止)
194
- if st.session_state.get('background_updating', False):
195
- logger.debug(f"背景更新中 - スキップ: {theme}")
196
- return
197
 
198
- st.session_state.background_updating = True
 
199
 
200
  try:
201
  logger.info(f"背景更新を開始します - テーマ: {theme}")
@@ -366,8 +356,9 @@ def update_background(scene_manager: SceneManager, theme: str):
366
  forceApplyBackground();
367
  }}
368
 
369
- // Streamlitの再レンダリング対策
370
- setTimeout(forceApplyBackground, 100);
 
371
  setTimeout(forceApplyBackground, 500);
372
  setTimeout(forceApplyBackground, 1000);
373
 
@@ -384,7 +375,7 @@ def update_background(scene_manager: SceneManager, theme: str):
384
 
385
  observer.observe(document.body, {{ childList: true, subtree: true }});
386
 
387
- // 定期的な背景チェック(rerun対策)
388
  setInterval(() => {{
389
  const target = document.querySelector('[data-testid="stAppViewContainer"]');
390
  if (target) {{
@@ -393,7 +384,7 @@ def update_background(scene_manager: SceneManager, theme: str):
393
  forceApplyBackground();
394
  }}
395
  }}
396
- }}, 2000); // 2秒ごとにチェック
397
  </script>
398
  """
399
  st.markdown(background_js, unsafe_allow_html=True)
@@ -402,11 +393,11 @@ def update_background(scene_manager: SceneManager, theme: str):
402
  if st.session_state.get("debug_mode", False) and image_url:
403
  st.success(f"🖼️ 背景更新: {theme}")
404
 
405
- # 現在のテーマを記録(重複実行防止用)
406
  st.session_state.current_background_theme = theme
407
  st.session_state.last_background_theme = theme
408
 
409
- logger.info(f"背景更新完了 - テーマ: {theme}")
410
 
411
  except Exception as e:
412
  logger.error(f"背景更新エラー: {e}")
@@ -425,11 +416,9 @@ def update_background(scene_manager: SceneManager, theme: str):
425
  </style>
426
  """
427
  st.markdown(fallback_css, unsafe_allow_html=True)
 
428
  st.session_state.current_background_theme = theme
429
  st.session_state.last_background_theme = theme
430
- finally:
431
- # 背景更新中フラグをクリア
432
- st.session_state.background_updating = False
433
 
434
  # --- ▼▼▼ 1. 初期化処理の一元管理 ▼▼▼ ---
435
 
@@ -824,6 +813,28 @@ def inject_custom_css(file_path="streamlit_styles.css"):
824
  except Exception as e:
825
  logger.error(f"CSS読み込みエラー: {e}")
826
  apply_fallback_css()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
827
 
828
  def apply_fallback_css():
829
  """フォールバック用の基本CSSを適用"""
@@ -1201,7 +1212,7 @@ def render_chat_tab(managers):
1201
  # 背景を更新
1202
  try:
1203
  current_theme = st.session_state.chat['scene_params'].get("theme", "default")
1204
- logger.debug(f"チャットタブ背景更新: テーマ '{current_theme}'")
1205
  update_background(managers['scene_manager'], current_theme)
1206
  except Exception as e:
1207
  logger.error(f"チャットタブ背景更新でエラーが発生: {e}")
@@ -2261,7 +2272,7 @@ Streamlit情報:
2261
 
2262
  # シーン変更時に背景を更新
2263
  try:
2264
- logger.debug(f"シーン変更時の背景更新: テーマ '{new_theme}'")
2265
  update_background(managers['scene_manager'], new_theme)
2266
  except Exception as e:
2267
  logger.error(f"シーン変更時の背景更新でエラーが発生: {e}")
@@ -2716,6 +2727,16 @@ def render_letter_tab(managers):
2716
  with st.form("instant_letter_form"):
2717
  st.write("💌 **初回手紙生成**")
2718
 
 
 
 
 
 
 
 
 
 
 
2719
  theme = st.text_input(
2720
  "手紙に書いてほしいテーマ",
2721
  placeholder="例: 今日見た美しい夕日について",
@@ -2741,6 +2762,16 @@ def render_letter_tab(managers):
2741
  with st.form("scheduled_letter_form"):
2742
  st.write("📮 **手紙のリクエスト**")
2743
 
 
 
 
 
 
 
 
 
 
 
2744
  theme = st.text_input(
2745
  "手紙に書いてほしいテーマ",
2746
  placeholder="例: 今日見た美しい夕日について",
@@ -3015,9 +3046,9 @@ def main():
3015
  # CSS読み込み完了後に背景を更新(初期設定)
3016
  try:
3017
  initial_theme = st.session_state.chat['scene_params'].get('theme', 'default')
3018
- logger.debug(f"初期背景設定: テーマ '{initial_theme}'")
3019
 
3020
- # 常に背景を更新(CSS読み込み待ちなし)
3021
  update_background(managers['scene_manager'], initial_theme)
3022
 
3023
  except Exception as e:
 
181
  return asyncio.run(coro)
182
 
183
  def update_background(scene_manager: SceneManager, theme: str):
184
+ """現在のテーマに基づいて背景画像を動的に設定するCSSを注入する(重複更新許容版)"""
185
+ logger.info(f"背景更新開始 - テーマ: {theme} (重複更新許容)")
 
 
 
 
 
 
 
 
 
 
 
186
 
187
+ # 重複更新を許容: 毎回テーマ更新を実行
188
+ # rerun時でも確実に背景が適用されるようにする
189
 
190
  try:
191
  logger.info(f"背景更新を開始します - テーマ: {theme}")
 
356
  forceApplyBackground();
357
  }}
358
 
359
+ // Streamlitの再レンダリング対策(重複更新許容)
360
+ setTimeout(forceApplyBackground, 50);
361
+ setTimeout(forceApplyBackground, 200);
362
  setTimeout(forceApplyBackground, 500);
363
  setTimeout(forceApplyBackground, 1000);
364
 
 
375
 
376
  observer.observe(document.body, {{ childList: true, subtree: true }});
377
 
378
+ // 定期的な背景チェック(重複更新許容・高頻度)
379
  setInterval(() => {{
380
  const target = document.querySelector('[data-testid="stAppViewContainer"]');
381
  if (target) {{
 
384
  forceApplyBackground();
385
  }}
386
  }}
387
+ }}, 1000); // 1秒ごとにチェック(重複更新許容)
388
  </script>
389
  """
390
  st.markdown(background_js, unsafe_allow_html=True)
 
393
  if st.session_state.get("debug_mode", False) and image_url:
394
  st.success(f"🖼️ 背景更新: {theme}")
395
 
396
+ # 現在のテーマを記録(参考用)
397
  st.session_state.current_background_theme = theme
398
  st.session_state.last_background_theme = theme
399
 
400
+ logger.info(f"背景更新完了 - テーマ: {theme} (重複更新許容)")
401
 
402
  except Exception as e:
403
  logger.error(f"背景更新エラー: {e}")
 
416
  </style>
417
  """
418
  st.markdown(fallback_css, unsafe_allow_html=True)
419
+ # フォールバック時もテーマを記録
420
  st.session_state.current_background_theme = theme
421
  st.session_state.last_background_theme = theme
 
 
 
422
 
423
  # --- ▼▼▼ 1. 初期化処理の一元管理 ▼▼▼ ---
424
 
 
813
  except Exception as e:
814
  logger.error(f"CSS読み込みエラー: {e}")
815
  apply_fallback_css()
816
+
817
+ # 全体的なautocomplete設定を追加
818
+ autocomplete_css = """
819
+ <style>
820
+ /* 全ての入力フィールドでautocompleteを有効化 */
821
+ input[type="text"],
822
+ input[type="email"],
823
+ input[type="password"],
824
+ textarea {
825
+ autocomplete: on !important;
826
+ }
827
+
828
+ /* Streamlit固有の入力フィールド */
829
+ .stTextInput input,
830
+ .stTextArea textarea,
831
+ .stChatInput input,
832
+ .stChatInput textarea {
833
+ autocomplete: on !important;
834
+ }
835
+ </style>
836
+ """
837
+ st.markdown(autocomplete_css, unsafe_allow_html=True)
838
 
839
  def apply_fallback_css():
840
  """フォールバック用の基本CSSを適用"""
 
1212
  # 背景を更新
1213
  try:
1214
  current_theme = st.session_state.chat['scene_params'].get("theme", "default")
1215
+ logger.info(f"チャットタブ背景更新: テーマ '{current_theme}' (重複更新許容)")
1216
  update_background(managers['scene_manager'], current_theme)
1217
  except Exception as e:
1218
  logger.error(f"チャットタブ背景更新でエラーが発生: {e}")
 
2272
 
2273
  # シーン変更時に背景を更新
2274
  try:
2275
+ logger.info(f"シーン変更時の背景更新: テーマ '{new_theme}' (重複更新許容)")
2276
  update_background(managers['scene_manager'], new_theme)
2277
  except Exception as e:
2278
  logger.error(f"シーン変更時の背景更新でエラーが発生: {e}")
 
2727
  with st.form("instant_letter_form"):
2728
  st.write("💌 **初回手紙生成**")
2729
 
2730
+ # テーマ入力フィールドのautocomplete属性を有効化
2731
+ theme_input_css = """
2732
+ <style>
2733
+ .stTextInput input[type="text"] {
2734
+ autocomplete: on !important;
2735
+ }
2736
+ </style>
2737
+ """
2738
+ st.markdown(theme_input_css, unsafe_allow_html=True)
2739
+
2740
  theme = st.text_input(
2741
  "手紙に書いてほしいテーマ",
2742
  placeholder="例: 今日見た美しい夕日について",
 
2762
  with st.form("scheduled_letter_form"):
2763
  st.write("📮 **手紙のリクエスト**")
2764
 
2765
+ # テーマ入力フィールドのautocomplete属性を有効化
2766
+ theme_input_css = """
2767
+ <style>
2768
+ .stTextInput input[type="text"] {
2769
+ autocomplete: on !important;
2770
+ }
2771
+ </style>
2772
+ """
2773
+ st.markdown(theme_input_css, unsafe_allow_html=True)
2774
+
2775
  theme = st.text_input(
2776
  "手紙に書いてほしいテーマ",
2777
  placeholder="例: 今日見た美しい夕日について",
 
3046
  # CSS読み込み完了後に背景を更新(初期設定)
3047
  try:
3048
  initial_theme = st.session_state.chat['scene_params'].get('theme', 'default')
3049
+ logger.info(f"初期背景設定: テーマ '{initial_theme}' (重複更新許容)")
3050
 
3051
+ # 常に背景を更新(重複更新許容)
3052
  update_background(managers['scene_manager'], initial_theme)
3053
 
3054
  except Exception as e: