Upload 5 files
Browse files
app.py
CHANGED
|
@@ -21,9 +21,21 @@ def create_limiter_state():
|
|
| 21 |
return {"timestamps": [], "is_blocked": False}
|
| 22 |
|
| 23 |
def check_limiter(limiter_state):
|
| 24 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 25 |
now = time.time()
|
| 26 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 27 |
if len(limiter_state["timestamps"]) >= RATE_LIMIT_MAX_REQUESTS:
|
| 28 |
logger.error("レートリミット超過! API呼び出しをブロックします。")
|
| 29 |
limiter_state["is_blocked"] = True
|
|
@@ -159,25 +171,73 @@ def detect_scene_change(history, message):
|
|
| 159 |
return None # この関数はrespond内で直接ロジックを記述
|
| 160 |
|
| 161 |
def generate_dialogue(history, message, affection, stage_name, scene_params, instruction=None):
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 165 |
return call_llm(SYSTEM_PROMPT_MARI, user_prompt)
|
| 166 |
|
| 167 |
def get_relationship_stage(affection):
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 171 |
|
| 172 |
def update_affection(message, affection):
|
| 173 |
-
|
|
|
|
|
|
|
| 174 |
analyzer = get_sentiment_analyzer()
|
| 175 |
-
if not analyzer:
|
|
|
|
|
|
|
| 176 |
try:
|
|
|
|
|
|
|
|
|
|
| 177 |
result = analyzer(message)[0]
|
| 178 |
-
if result
|
| 179 |
-
|
| 180 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 181 |
return affection
|
| 182 |
|
| 183 |
# --- 6. Gradio応答関数 (v5構文に完全対応) ---
|
|
@@ -213,6 +273,10 @@ def respond(message, chat_history, affection, scene_params, limiter_state):
|
|
| 213 |
|
| 214 |
new_affection = update_affection(message, affection)
|
| 215 |
stage_name = get_relationship_stage(new_affection)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 216 |
final_scene_params = scene_params.copy()
|
| 217 |
|
| 218 |
bot_message = ""
|
|
@@ -256,13 +320,21 @@ def respond(message, chat_history, affection, scene_params, limiter_state):
|
|
| 256 |
try:
|
| 257 |
parsed = json.loads(new_scene_name_json)
|
| 258 |
if isinstance(parsed, dict):
|
| 259 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 260 |
else:
|
| 261 |
logger.warning(f"想定外のJSON形式が返されました: {parsed}")
|
| 262 |
except Exception as e:
|
| 263 |
logger.error(f"JSONパースに失敗しました: {e}\n元の出力: {new_scene_name_json}")
|
| 264 |
|
| 265 |
-
if new_scene_name and
|
|
|
|
|
|
|
|
|
|
| 266 |
if not check_limiter(limiter_state):
|
| 267 |
bot_message = "(…少し考える時間がほしい)"
|
| 268 |
else:
|
|
|
|
| 21 |
return {"timestamps": [], "is_blocked": False}
|
| 22 |
|
| 23 |
def check_limiter(limiter_state):
|
| 24 |
+
# limiter_stateが辞書であることを確認
|
| 25 |
+
if not isinstance(limiter_state, dict):
|
| 26 |
+
logger.error(f"limiter_stateが辞書ではありません: {type(limiter_state)}")
|
| 27 |
+
return False
|
| 28 |
+
|
| 29 |
+
if limiter_state.get("is_blocked", False):
|
| 30 |
+
return False
|
| 31 |
+
|
| 32 |
now = time.time()
|
| 33 |
+
timestamps = limiter_state.get("timestamps", [])
|
| 34 |
+
if not isinstance(timestamps, list):
|
| 35 |
+
timestamps = []
|
| 36 |
+
limiter_state["timestamps"] = timestamps
|
| 37 |
+
|
| 38 |
+
limiter_state["timestamps"] = [t for t in timestamps if now - t < RATE_LIMIT_IN_SECONDS]
|
| 39 |
if len(limiter_state["timestamps"]) >= RATE_LIMIT_MAX_REQUESTS:
|
| 40 |
logger.error("レートリミット超過! API呼び出しをブロックします。")
|
| 41 |
limiter_state["is_blocked"] = True
|
|
|
|
| 171 |
return None # この関数はrespond内で直接ロジックを記述
|
| 172 |
|
| 173 |
def generate_dialogue(history, message, affection, stage_name, scene_params, instruction=None):
|
| 174 |
+
if not isinstance(history, list):
|
| 175 |
+
history = []
|
| 176 |
+
if not isinstance(scene_params, dict):
|
| 177 |
+
scene_params = {"theme": "default"}
|
| 178 |
+
if not isinstance(message, str):
|
| 179 |
+
message = ""
|
| 180 |
+
|
| 181 |
+
# 履歴を安全に処理
|
| 182 |
+
safe_history = []
|
| 183 |
+
for item in history:
|
| 184 |
+
if isinstance(item, (list, tuple)) and len(item) >= 2:
|
| 185 |
+
user_msg = str(item[0]) if item[0] is not None else ""
|
| 186 |
+
bot_msg = str(item[1]) if item[1] is not None else ""
|
| 187 |
+
safe_history.append((user_msg, bot_msg))
|
| 188 |
+
|
| 189 |
+
history_text = "\n".join([f"ユーザー: {u}\n麻理: {m}" for u, m in safe_history])
|
| 190 |
+
|
| 191 |
+
current_theme = scene_params.get("theme", "default")
|
| 192 |
+
user_prompt = f'''# 現在の状況
|
| 193 |
+
- 現在地: {current_theme}
|
| 194 |
+
- 好感度: {affection} ({stage_name})
|
| 195 |
+
|
| 196 |
+
# 会話履歴
|
| 197 |
+
{history_text}
|
| 198 |
+
---
|
| 199 |
+
# 指示
|
| 200 |
+
{f"【特別指示】{instruction}" if instruction else f"ユーザーの発言「{message}」に応答してください。"}
|
| 201 |
+
|
| 202 |
+
麻理の応答:'''
|
| 203 |
+
|
| 204 |
return call_llm(SYSTEM_PROMPT_MARI, user_prompt)
|
| 205 |
|
| 206 |
def get_relationship_stage(affection):
|
| 207 |
+
if not isinstance(affection, (int, float)):
|
| 208 |
+
affection = 30 # デフォルト値
|
| 209 |
+
|
| 210 |
+
if affection < 20:
|
| 211 |
+
return "ステージ1:敵対"
|
| 212 |
+
elif affection < 40:
|
| 213 |
+
return "ステージ2:警戒"
|
| 214 |
+
elif affection < 60:
|
| 215 |
+
return "ステージ3:中立"
|
| 216 |
+
elif affection < 80:
|
| 217 |
+
return "ステージ4:好意"
|
| 218 |
+
else:
|
| 219 |
+
return "ステージ5:親密"
|
| 220 |
|
| 221 |
def update_affection(message, affection):
|
| 222 |
+
if not isinstance(affection, (int, float)):
|
| 223 |
+
affection = 30 # デフォルト値
|
| 224 |
+
|
| 225 |
analyzer = get_sentiment_analyzer()
|
| 226 |
+
if not analyzer:
|
| 227 |
+
return affection
|
| 228 |
+
|
| 229 |
try:
|
| 230 |
+
if not isinstance(message, str) or len(message.strip()) == 0:
|
| 231 |
+
return affection
|
| 232 |
+
|
| 233 |
result = analyzer(message)[0]
|
| 234 |
+
if result.get('label') == 'positive':
|
| 235 |
+
return min(100, affection + 3)
|
| 236 |
+
elif result.get('label') == 'negative':
|
| 237 |
+
return max(0, affection - 3)
|
| 238 |
+
except Exception as e:
|
| 239 |
+
logger.error(f"感情分析エラー: {e}")
|
| 240 |
+
|
| 241 |
return affection
|
| 242 |
|
| 243 |
# --- 6. Gradio応答関数 (v5構文に完全対応) ---
|
|
|
|
| 273 |
|
| 274 |
new_affection = update_affection(message, affection)
|
| 275 |
stage_name = get_relationship_stage(new_affection)
|
| 276 |
+
|
| 277 |
+
# scene_paramsが辞書であることを確認
|
| 278 |
+
if not isinstance(scene_params, dict):
|
| 279 |
+
scene_params = {"theme": "default"}
|
| 280 |
final_scene_params = scene_params.copy()
|
| 281 |
|
| 282 |
bot_message = ""
|
|
|
|
| 320 |
try:
|
| 321 |
parsed = json.loads(new_scene_name_json)
|
| 322 |
if isinstance(parsed, dict):
|
| 323 |
+
scene_value = parsed.get("scene")
|
| 324 |
+
# scene_valueが文字列であることを確認
|
| 325 |
+
if isinstance(scene_value, str):
|
| 326 |
+
new_scene_name = scene_value
|
| 327 |
+
else:
|
| 328 |
+
logger.warning(f"scene値が文字列ではありません: {scene_value} (型: {type(scene_value)})")
|
| 329 |
else:
|
| 330 |
logger.warning(f"想定外のJSON形式が返されました: {parsed}")
|
| 331 |
except Exception as e:
|
| 332 |
logger.error(f"JSONパースに失敗しました: {e}\n元の出力: {new_scene_name_json}")
|
| 333 |
|
| 334 |
+
if (new_scene_name and
|
| 335 |
+
isinstance(new_scene_name, str) and
|
| 336 |
+
new_scene_name != "none" and
|
| 337 |
+
new_scene_name != final_scene_params.get("theme")):
|
| 338 |
if not check_limiter(limiter_state):
|
| 339 |
bot_message = "(…少し考える時間がほしい)"
|
| 340 |
else:
|
style.css
CHANGED
|
@@ -10,6 +10,31 @@ body {
|
|
| 10 |
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif !important;
|
| 11 |
}
|
| 12 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
/* --- Layout --- */
|
| 14 |
.gradio-container {
|
| 15 |
max-width: 1000px !important;
|
|
|
|
| 10 |
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif !important;
|
| 11 |
}
|
| 12 |
|
| 13 |
+
/* Gradio内部のフォント参照を上書き */
|
| 14 |
+
@font-face {
|
| 15 |
+
font-family: 'ui-sans-serif';
|
| 16 |
+
src: local('system-ui'), local('-apple-system'), local('BlinkMacSystemFont');
|
| 17 |
+
font-weight: normal;
|
| 18 |
+
}
|
| 19 |
+
|
| 20 |
+
@font-face {
|
| 21 |
+
font-family: 'ui-sans-serif';
|
| 22 |
+
src: local('system-ui'), local('-apple-system'), local('BlinkMacSystemFont');
|
| 23 |
+
font-weight: bold;
|
| 24 |
+
}
|
| 25 |
+
|
| 26 |
+
@font-face {
|
| 27 |
+
font-family: 'system-ui';
|
| 28 |
+
src: local('system-ui'), local('-apple-system'), local('BlinkMacSystemFont');
|
| 29 |
+
font-weight: normal;
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
+
@font-face {
|
| 33 |
+
font-family: 'system-ui';
|
| 34 |
+
src: local('system-ui'), local('-apple-system'), local('BlinkMacSystemFont');
|
| 35 |
+
font-weight: bold;
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
/* --- Layout --- */
|
| 39 |
.gradio-container {
|
| 40 |
max-width: 1000px !important;
|