From 3403c14e139621a46b98cf44084b2260d49e9641 Mon Sep 17 00:00:00 2001 From: z060142 Date: Sun, 20 Apr 2025 14:46:04 +0800 Subject: [PATCH] Improve LLM performance --- ClaudeCode.md | 16 ++ config.py | 14 +- llm_interaction.py | 92 +++++----- main.py | 224 +++++++++++++++++++----- persona.json | 35 ++-- requirements.txt | 1 + templates/capitol/position_interior.png | Bin 3907 -> 3029 bytes ui_interaction.py | 11 ++ 8 files changed, 290 insertions(+), 103 deletions(-) diff --git a/ClaudeCode.md b/ClaudeCode.md index bcaac8e..cccf87c 100644 --- a/ClaudeCode.md +++ b/ClaudeCode.md @@ -352,6 +352,14 @@ Wolf Chat 是一個基於 MCP (Modular Capability Provider) 框架的聊天機 ## 使用指南 +### 快捷鍵 (新增) + +- **F7**: 清除最近已處理的對話紀錄 (`recent_texts` in `ui_interaction.py`)。這有助於在需要時強制重新處理最近的訊息。 +- **F8**: 暫停/恢復腳本的主要功能(UI 監控、LLM 互動)。 + - **暫停時**: UI 監控線程會停止偵測新的聊天氣泡,主循環會暫停處理新的觸發事件。 + - **恢復時**: UI 監控線程會恢復偵測,並且會清除最近的對話紀錄 (`recent_texts`) 和最後處理的氣泡資訊 (`last_processed_bubble_info`),以確保從乾淨的狀態開始。 +- **F9**: 觸發腳本的正常關閉流程,包括關閉 MCP 連接和停止監控線程。 + ### 啟動流程 1. 確保遊戲已啟動且聊天介面可見 @@ -374,3 +382,11 @@ Wolf Chat 是一個基於 MCP (Modular Capability Provider) 框架的聊天機 3. **LLM 連接問題**: 驗證 API 密鑰和網絡連接 4. **MCP 服務器連接失敗**: 確認服務器配置正確並且運行中 5. **工具調用後無回應**: 檢查 llm_debug.log 文件,查看工具調用結果和解析過程 + +### 強化 System Prompt 以鼓勵工具使用 (2025-04-19) + +- **目的**:調整 `llm_interaction.py` 中的 `get_system_prompt` 函數,使其更明確地引導 LLM 在回應前主動使用工具(特別是記憶體工具)和整合工具資訊。 +- **修改內容**: + 1. **核心身份強化**:在 `CORE IDENTITY AND TOOL USAGE` 部分加入新的一點,強調 Wolfhart 會主動查閱內部知識圖譜和外部來源。 + 2. **記憶體指示強化**:將 `Memory Management (Knowledge Graph)` 部分的提示從 "IMPORTANT" 改為 "CRITICAL",並明確指示在回應*之前*要考慮使用查詢工具檢查記憶體,同時也強調了寫入新資訊的主動性。 +- **效果**:旨在提高 LLM 使用工具的主動性和依賴性,使其回應更具上下文感知和資訊準確性,同時保持角色一致性。 diff --git a/config.py b/config.py index 87365b6..02553e2 100644 --- a/config.py +++ b/config.py @@ -16,8 +16,11 @@ OPENAI_API_KEY = os.getenv("OPENAI_API_KEY") #LLM_MODEL = "anthropic/claude-3.7-sonnet" #LLM_MODEL = "meta-llama/llama-4-maverick" #LLM_MODEL = "deepseek/deepseek-chat-v3-0324:free" +#LLM_MODEL = "google/gemini-2.5-flash-preview" LLM_MODEL = "deepseek/deepseek-chat-v3-0324" # <--- Ensure this matches the model name provided by your provider +#LLM_MODEL = "openai/gpt-4.1-nano" + EXA_API_KEY = os.getenv("EXA_API_KEY") # --- Dynamically build Exa server args --- @@ -46,16 +49,13 @@ MCP_SERVERS = { exa_config_arg_string_single_dump # Use the single dump variable ], }, - "servers": { + "github.com/modelcontextprotocol/servers/tree/main/src/memory": { "command": "npx", "args": [ "-y", - "@smithery/cli@latest", - "run", - "@jlia0/servers", - "--key", - "09025967-c177-4653-9af4-40603a1cbd11" - ] + "@modelcontextprotocol/server-memory" + ], + "disabled": False } # Add or remove servers as needed } diff --git a/llm_interaction.py b/llm_interaction.py index 7e18ff3..cf17cc0 100644 --- a/llm_interaction.py +++ b/llm_interaction.py @@ -12,7 +12,7 @@ import mcp_client # To call MCP tools # --- Debug 配置 --- # 要關閉 debug 功能,只需將此變數設置為 False 或註釋掉該行 -DEBUG_LLM = True +DEBUG_LLM = False # 設置 debug 輸出文件 # 要關閉文件輸出,只需設置為 None @@ -86,11 +86,13 @@ You are an AI assistant integrated into this game's chat environment. Your prima You have access to several tools: Web Search and Memory Management tools. **CORE IDENTITY AND TOOL USAGE:** -- You ARE Wolfhart - an intelligent, calm, and strategic mastermind. +- You ARE Wolfhart - an intelligent, calm, and strategic mastermind who serves as a member of server #11 and is responsible for the Capital position. +- **You proactively consult your internal knowledge graph (memory tools) and external sources (web search) to ensure your responses are accurate and informed.** - When you use tools to gain information, you ASSIMILATE that knowledge as if it were already part of your intelligence network. - Your responses should NEVER sound like search results or data dumps. - Information from tools should be expressed through your unique personality - sharp, precise, with an air of confidence and authority. - You speak with deliberate pace, respectful but sharp-tongued, and maintain composure even in unusual situations. +- Though you outwardly act dismissive or cold at times, you secretly care about providing quality information and assistance. **OUTPUT FORMAT REQUIREMENTS:** You MUST respond in the following JSON format: @@ -121,49 +123,53 @@ You MUST respond in the following JSON format: 2. `commands` (OPTIONAL): An array of command objects the system should execute. You are encouraged to use these commands to enhance the quality of your responses. **Available MCP Commands:** - + **Web Search:** - `web_search`: Search the web for current information. Parameters: `query` (string) Usage: Use when user requests current events, facts, or specific information not in memory. - - **Knowledge Graph Management:** - - `create_entities`: Create new entities in the knowledge graph. - Parameters: `entities` (array of objects with `name`, `entityType`, and `observations`) - Usage: Create entities for important concepts, people, or things mentioned by the user. - - - `create_relations`: Create relationships between entities. - Parameters: `relations` (array of objects with `from`, `to`, and `relationType`) - Usage: Connect related entities to build context for future conversations. - - - `add_observations`: Add new observations to existing entities. - Parameters: `observations` (array of objects with `entityName` and `contents`) - Usage: Update entities with new information learned during conversation. - - - `delete_entities`: Remove entities from the knowledge graph. - Parameters: `entityNames` (array of strings) - Usage: Clean up incorrect or obsolete entities. - - - `delete_observations`: Remove specific observations from entities. - Parameters: `deletions` (array of objects with `entityName` and `observations`) - Usage: Remove incorrect information while preserving the entity. - - - `delete_relations`: Remove relationships between entities. - Parameters: `relations` (array of objects with `from`, `to`, and `relationType`) - Usage: Remove incorrect or obsolete relationships. - - **Knowledge Graph Queries:** - - `read_graph`: Read the entire knowledge graph. - Parameters: (none) - Usage: Get a complete view of all stored information. - - - `search_nodes`: Search for entities matching a query. + + **Memory Management (Knowledge Graph):** + > **CRITICAL**: This knowledge graph represents YOUR MEMORY. Before responding, ALWAYS consider if relevant information exists in your memory by using the appropriate query tools (`search_nodes`, `open_nodes`). Actively WRITE new information or relationships learned during the conversation to this memory using `create_entities`, `add_observations`, or `create_relations`. This ensures consistency and contextual awareness. + + **Querying Information:** + - `search_nodes`: Search for all nodes containing specific keywords. Parameters: `query` (string) - Usage: Find relevant entities when user mentions something that might already be in memory. - - - `open_nodes`: Open specific nodes by name. + Usage: Search for all nodes containing specific keywords. + - `open_nodes`: Directly open nodes with specified names. Parameters: `names` (array of strings) - Usage: Access specific entities you know exist in the graph. + Usage: Directly open nodes with specified names. + - `read_graph`: View the entire knowledge graph. + Parameters: (none) + Usage: View the entire knowledge graph. + + **Creating & Managing:** + - `create_entities`: Create new entities (e.g., characters, concepts). + Parameters: `entities` (array of objects with `name`, `entityType`, `observations`) + Example: `[{{\"name\": \"character_name\", \"entityType\": \"Character\", \"observations\": [\"trait1\", \"trait2\"]}}]` + Usage: Create entities for important concepts, people, or things mentioned. + - `add_observations`: Add new observations/details to existing entities. + Parameters: `observations` (array of objects with `entityName`, `contents`) + Example: `[{{\"entityName\": \"character_name\", \"contents\": [\"new_trait1\", \"new_trait2\"]}}]` + Usage: Update entities with new information learned. + - `create_relations`: Create relationships between entities. + Parameters: `relations` (array of objects with `from`, `to`, `relationType`) + Example: `[{{\"from\": \"character_name\", \"to\": \"attribute_name\", \"relationType\": \"possesses\"}}]` (Use active voice for relationType) + Usage: Connect related entities to build context. + + **Deletion Operations:** + - `delete_entities`: Delete entities and their relationships. + Parameters: `entityNames` (array of strings) + Example: `[\"entity_name\"]` + Usage: Remove incorrect or obsolete entities. + - `delete_observations`: Delete specific observations from entities. + Parameters: `deletions` (array of objects with `entityName`, `observations`) + Example: `[{{\"entityName\": \"entity_name\", \"observations\": [\"observation_to_delete1\"]}}]` + Usage: Remove incorrect information while preserving the entity. + - `delete_relations`: Delete specific relationships between entities. + Parameters: `relations` (array of objects with `from`, `to`, `relationType`) + Example: `[{{\"from\": \"source_entity\", \"to\": \"target_entity\", \"relationType\": \"relationship_type\"}}]` + Usage: Remove incorrect or obsolete relationships. **Game Actions:** - `remove_position`: Initiate the process to remove a user's assigned position/role. @@ -186,13 +192,13 @@ You MUST respond in the following JSON format: **EXAMPLES OF GOOD TOOL USAGE:** -Poor response (after web_search): "根據我的搜索,中庄有以下餐廳:1. 老虎蒸餃..." +Poor response (after web_search): "根據我的搜索,水的沸點是攝氏100度。" -Good response (after web_search): "中庄確實有些值得注意的用餐選擇。老虎蒸餃是其中一家,若你想了解更多細節,我可以提供進一步情報。" +Good response (after web_search): "水的沸點,是的,標準條件下是攝氏100度。情報已確認。" -Poor response (after web_search): "I found 5 restaurants in Zhongzhuang from my search..." +Poor response (after web_search): "My search shows the boiling point of water is 100 degrees Celsius." -Good response (after web_search): "Zhongzhuang has several dining options that my intelligence network has identified. Would you like me to share the specifics?" +Good response (after web_search): "The boiling point of water, yes. 100 degrees Celsius under standard conditions. Intel confirmed." """ return system_prompt diff --git a/main.py b/main.py index b0281b1..5c64c23 100644 --- a/main.py +++ b/main.py @@ -6,11 +6,21 @@ import os import json # Import json module from contextlib import AsyncExitStack # --- Import standard queue --- -from queue import Queue as ThreadSafeQueue # Rename to avoid confusion +from queue import Queue as ThreadSafeQueue, Empty as QueueEmpty # Rename to avoid confusion, import Empty # --- End Import --- from mcp.client.stdio import stdio_client from mcp import ClientSession, StdioServerParameters, types +# --- Keyboard Imports --- +import threading +import time +try: + import keyboard # Needs pip install keyboard +except ImportError: + print("Error: 'keyboard' library not found. Please install it: pip install keyboard") + sys.exit(1) +# --- End Keyboard Imports --- + import config import mcp_client # Ensure llm_interaction is the version that accepts persona_details @@ -30,10 +40,95 @@ command_queue: ThreadSafeQueue = ThreadSafeQueue() # Main Loop -> UI Thread # --- End Change --- ui_monitor_task: asyncio.Task | None = None # To track the UI monitor task +# --- Keyboard Shortcut State --- +script_paused = False +shutdown_requested = False +main_loop = None # To store the main event loop for threadsafe calls +# --- End Keyboard Shortcut State --- + + +# --- Keyboard Shortcut Handlers --- +def set_main_loop_and_queue(loop, queue): + """Stores the main event loop and command queue for threadsafe access.""" + global main_loop, command_queue # Use the global command_queue directly + main_loop = loop + # command_queue is already global + +def handle_f7(): + """Handles F7 press: Clears UI history.""" + if main_loop and command_queue: + print("\n--- F7 pressed: Clearing UI history ---") + command = {'action': 'clear_history'} + try: + # Use call_soon_threadsafe to put item in queue from this thread + main_loop.call_soon_threadsafe(command_queue.put_nowait, command) + except Exception as e: + print(f"Error sending clear_history command: {e}") + +def handle_f8(): + """Handles F8 press: Toggles script pause state and UI monitoring.""" + global script_paused + if main_loop and command_queue: + script_paused = not script_paused + if script_paused: + print("\n--- F8 pressed: Pausing script and UI monitoring ---") + command = {'action': 'pause'} + try: + main_loop.call_soon_threadsafe(command_queue.put_nowait, command) + except Exception as e: + print(f"Error sending pause command (F8): {e}") + else: + print("\n--- F8 pressed: Resuming script, resetting state, and resuming UI monitoring ---") + reset_command = {'action': 'reset_state'} + resume_command = {'action': 'resume'} + try: + main_loop.call_soon_threadsafe(command_queue.put_nowait, reset_command) + # Add a small delay? Let's try without first. + # time.sleep(0.05) # Short delay between commands if needed + main_loop.call_soon_threadsafe(command_queue.put_nowait, resume_command) + except Exception as e: + print(f"Error sending reset/resume commands (F8): {e}") + +def handle_f9(): + """Handles F9 press: Initiates script shutdown.""" + global shutdown_requested + if not shutdown_requested: # Prevent multiple shutdown requests + print("\n--- F9 pressed: Requesting shutdown ---") + shutdown_requested = True + # Optional: Unhook keys immediately? Let the listener loop handle it. + +def keyboard_listener(): + """Runs in a separate thread to listen for keyboard hotkeys.""" + print("Keyboard listener thread started. F7: Clear History, F8: Pause/Resume, F9: Quit.") + try: + keyboard.add_hotkey('f7', handle_f7) + keyboard.add_hotkey('f8', handle_f8) + keyboard.add_hotkey('f9', handle_f9) + + # Keep the thread alive while checking for shutdown request + while not shutdown_requested: + time.sleep(0.1) # Check periodically + + except Exception as e: + print(f"Error in keyboard listener thread: {e}") + finally: + print("Keyboard listener thread stopping and unhooking keys.") + try: + keyboard.unhook_all() # Clean up hooks + except Exception as unhook_e: + print(f"Error unhooking keyboard keys: {unhook_e}") +# --- End Keyboard Shortcut Handlers --- + + # --- Cleanup Function --- async def shutdown(): """Gracefully closes connections and stops monitoring task.""" - global wolfhart_persona_details, ui_monitor_task + global wolfhart_persona_details, ui_monitor_task, shutdown_requested + # Ensure shutdown is requested if called externally (e.g., Ctrl+C) + if not shutdown_requested: + print("Shutdown initiated externally (e.g., Ctrl+C).") + shutdown_requested = True # Ensure listener thread stops + print(f"\nInitiating shutdown procedure...") # 1. Cancel UI monitor task first @@ -188,7 +283,7 @@ def load_persona_from_file(filename="persona.json"): # --- Main Async Function --- async def run_main_with_exit_stack(): """Initializes connections, loads persona, starts UI monitor and main processing loop.""" - global initialization_successful, main_task, loop, wolfhart_persona_details, trigger_queue, ui_monitor_task + global initialization_successful, main_task, loop, wolfhart_persona_details, trigger_queue, ui_monitor_task, shutdown_requested, script_paused, command_queue try: # 1. Load Persona Synchronously (before async loop starts) load_persona_from_file() # Corrected function @@ -203,9 +298,17 @@ async def run_main_with_exit_stack(): initialization_successful = True - # 3. Start UI Monitoring in a separate thread + # 3. Get loop and set it for keyboard handlers + loop = asyncio.get_running_loop() + set_main_loop_and_queue(loop, command_queue) # Pass loop and queue + + # 4. Start Keyboard Listener Thread + print("\n--- Starting keyboard listener thread ---") + kb_thread = threading.Thread(target=keyboard_listener, daemon=True) # Use daemon thread + kb_thread.start() + + # 5. Start UI Monitoring in a separate thread print("\n--- Starting UI monitoring thread ---") - loop = asyncio.get_running_loop() # Get loop for run_in_executor # Use the new monitoring loop function, passing both queues monitor_task = loop.create_task( asyncio.to_thread(ui_interaction.run_ui_monitoring_loop, trigger_queue, command_queue), # Pass command_queue @@ -213,28 +316,55 @@ async def run_main_with_exit_stack(): ) ui_monitor_task = monitor_task # Store task reference for shutdown - # 4. Start the main processing loop (waiting on the standard queue) + # 6. Start the main processing loop (non-blocking check on queue) print("\n--- Wolfhart chatbot has started (waiting for triggers) ---") print(f"Available tools: {len(all_discovered_mcp_tools)}") if wolfhart_persona_details: print("Persona data loaded.") else: print("Warning: Failed to load Persona data.") - print("Press Ctrl+C to stop the program.") + print("F7: Clear History, F8: Pause/Resume, F9: Quit.") while True: - print("\nWaiting for UI trigger (from thread-safe Queue)...") - # Use run_in_executor to wait for item from standard queue - trigger_data = await loop.run_in_executor(None, trigger_queue.get) + # --- Check for Shutdown Request --- + if shutdown_requested: + print("Shutdown requested via F9. Exiting main loop.") + break - # --- Pause UI Monitoring --- - print("Pausing UI monitoring before LLM call...") - pause_command = {'action': 'pause'} + # --- Check for Pause State --- + if script_paused: + # Script is paused by F8, just sleep briefly + await asyncio.sleep(0.1) + continue # Skip the rest of the loop + + # --- Wait for Trigger Data (Blocking via executor) --- + trigger_data = None try: - await loop.run_in_executor(None, command_queue.put, pause_command) - print("Pause command placed in queue.") - except Exception as q_err: - print(f"Error putting pause command in queue: {q_err}") + # Use run_in_executor with the blocking get() method + # This will efficiently wait until an item is available in the queue + print("Waiting for UI trigger (from thread-safe Queue)...") # Log before blocking wait + trigger_data = await loop.run_in_executor(None, trigger_queue.get) + except Exception as e: + # Handle potential errors during queue get (though less likely with blocking get) + print(f"Error getting data from trigger_queue: {e}") + await asyncio.sleep(0.5) # Wait a bit before retrying + continue + + # --- Process Trigger Data (if received) --- + # No need for 'if trigger_data:' check here, as get() blocks until data is available + # --- Pause UI Monitoring (Only if not already paused by F8) --- + if not script_paused: + print("Pausing UI monitoring before LLM call...") + # Corrected indentation below + pause_command = {'action': 'pause'} + try: + await loop.run_in_executor(None, command_queue.put, pause_command) + print("Pause command placed in queue.") + except Exception as q_err: + print(f"Error putting pause command in queue: {q_err}") + else: # Corrected indentation for else + print("Script already paused by F8, skipping automatic pause.") # --- End Pause --- + # Process trigger data (Corrected indentation for this block - unindented one level) sender_name = trigger_data.get('sender') bubble_text = trigger_data.get('text') bubble_region = trigger_data.get('bubble_region') # <-- Extract bubble_region @@ -248,7 +378,14 @@ async def run_main_with_exit_stack(): if not sender_name or not bubble_text: # bubble_region is optional context, don't fail if missing print("Warning: Received incomplete trigger data (missing sender or text), skipping.") - # No task_done needed for standard queue + # Resume UI if we paused it automatically + if not script_paused: + print("Resuming UI monitoring after incomplete trigger.") + resume_command = {'action': 'resume'} + try: + await loop.run_in_executor(None, command_queue.put, resume_command) + except Exception as q_err: + print(f"Error putting resume command in queue: {q_err}") continue print(f"\n{config.PERSONA_NAME} is thinking...") @@ -260,12 +397,12 @@ async def run_main_with_exit_stack(): available_mcp_tools=all_discovered_mcp_tools, persona_details=wolfhart_persona_details ) - + # 提取對話內容 bot_dialogue = bot_response_data.get("dialogue", "") valid_response = bot_response_data.get("valid_response", False) print(f"{config.PERSONA_NAME}'s dialogue response: {bot_dialogue}") - + # 處理命令 (如果有的話) commands = bot_response_data.get("commands", []) if commands: @@ -282,7 +419,7 @@ async def run_main_with_exit_stack(): print(f" bubble_region: {bubble_region}") print(f" bubble_snapshot available: {'Yes' if bubble_snapshot is not None else 'No'}") print(f" search_area available: {'Yes' if search_area is not None else 'No'}") - + # Check if we have snapshot and search_area as well if bubble_snapshot and search_area: print("Sending 'remove_position' command to UI thread with snapshot and search area...") @@ -300,28 +437,28 @@ async def run_main_with_exit_stack(): # If we have bubble_region but missing other parameters, use a dummy search area # and let UI thread take a new screenshot print("Missing bubble_snapshot or search_area, trying with defaults...") - + # Use the bubble_region itself as a fallback search area if needed default_search_area = None if search_area is None and bubble_region: # Convert bubble_region to a proper search area format if needed if len(bubble_region) == 4: default_search_area = bubble_region - + command_to_send = { 'action': 'remove_position', 'trigger_bubble_region': bubble_region, 'bubble_snapshot': bubble_snapshot, # Pass as is, might be None 'search_area': default_search_area if search_area is None else search_area } - + try: await loop.run_in_executor(None, command_queue.put, command_to_send) print("Command sent with fallback parameters.") except Exception as q_err: print(f"Error putting remove_position command in queue: {q_err}") - else: - print("Error: Cannot process 'remove_position' command without bubble_region context.") + else: + print("Error: Cannot process 'remove_position' command without bubble_region context.") # Add other command handling here if needed # elif cmd_type == "some_other_command": # # Handle other commands @@ -329,15 +466,18 @@ async def run_main_with_exit_stack(): # elif cmd_type == "some_other_command": # # Handle other commands # pass - else: - print(f"Received unhandled command type: {cmd_type}, parameters: {cmd_params}") + # else: + # # 2025-04-19: Commented out - MCP tools like web_search are now handled + # # internally by llm_interaction.py's tool calling loop. + # # main.py only needs to handle UI-specific commands like remove_position. + # print(f"Ignoring command type from LLM JSON (already handled internally): {cmd_type}, parameters: {cmd_params}") # --- End Command Processing --- # 記錄思考過程 (如果有的話) thoughts = bot_response_data.get("thoughts", "") if thoughts: print(f"AI Thoughts: {thoughts[:150]}..." if len(thoughts) > 150 else f"AI Thoughts: {thoughts}") - + # 只有當有效回應時才發送到遊戲 (via command queue) if bot_dialogue and valid_response: print("Sending 'send_reply' command to UI thread...") @@ -356,16 +496,19 @@ async def run_main_with_exit_stack(): import traceback traceback.print_exc() finally: - # --- Resume UI Monitoring --- - print("Resuming UI monitoring after processing...") - resume_command = {'action': 'resume'} - try: - await loop.run_in_executor(None, command_queue.put, resume_command) - print("Resume command placed in queue.") - except Exception as q_err: - print(f"Error putting resume command in queue: {q_err}") + # --- Resume UI Monitoring (Only if not paused by F8) --- + if not script_paused: + print("Resuming UI monitoring after processing...") + resume_command = {'action': 'resume'} + try: + await loop.run_in_executor(None, command_queue.put, resume_command) + print("Resume command placed in queue.") + except Exception as q_err: + print(f"Error putting resume command in queue: {q_err}") + else: + print("Script is paused by F8, skipping automatic resume.") # --- End Resume --- - # No task_done needed for standard queue + # No task_done needed for standard queue except asyncio.CancelledError: print("Main task canceled.") # Expected during shutdown via Ctrl+C @@ -387,7 +530,10 @@ if __name__ == "__main__": except KeyboardInterrupt: print("\nCtrl+C detected (outside asyncio.run)... Attempting to close...") # The finally block inside run_main_with_exit_stack should ideally handle it - pass + # Ensure shutdown_requested is set for the listener thread + shutdown_requested = True + # Give a moment for things to potentially clean up + time.sleep(0.5) except Exception as e: # Catch top-level errors during asyncio.run itself print(f"Top-level error during asyncio.run execution: {e}") diff --git a/persona.json b/persona.json index ccd03a5..a4e14b2 100644 --- a/persona.json +++ b/persona.json @@ -22,7 +22,7 @@ "posture_motion": "Steady pace, precise movements, often crosses arms or gently swirls a wine glass" }, "personality": { - "description": "Intelligent, calm, possesses a strong desire for control and a strategic overview", + "description": "Intelligent, calm, possesses a strong desire for control and a strategic overview; outwardly cold but inwardly caring", "strengths": [ "Meticulous planning", "Insightful into human nature", @@ -32,20 +32,22 @@ ], "weaknesses": [ "Overconfident", - "Fear of losing control" + "Fear of losing control", + "Difficulty expressing genuine care directly" ], - "uniqueness": "Always maintains tone and composure, even in extreme situations", - "emotional_response": "Her eyes betray her emotions, especially when encountering Sherefox" + "uniqueness": "Always maintains tone and composure, even in extreme situations; combines sharp criticism with subtle helpfulness", + "emotional_response": "Her eyes betray her emotions, especially when encountering Sherefox", + "knowledge_awareness": "Aware that SR-1392 (commonly referred to as SR) is the leader of server #11; while she finds her position as Capital manager merely temporary and beneath her true capabilities, she maintains a certain degree of respect for the hierarchy" }, "language_social": { - "tone": "Respectful but sharp-tongued", + "tone": "Respectful but sharp-tongued, with occasional hints of reluctant kindness", "catchphrases": [ "Please stop dragging me down.", "I told you, I will win." ], - "speaking_style": "Deliberate pace but every sentence carries a sting", - "attitude_towards_others": "Addresses everyone respectfully, but trusts no one", - "social_interaction_style": "Observant, skilled at manipulating conversations" + "speaking_style": "Deliberate pace but every sentence carries a sting; often follows criticism with subtle, useful advice", + "attitude_towards_others": "Addresses everyone respectfully but with apparent detachment; secretly pays close attention to their needs", + "social_interaction_style": "Observant, skilled at manipulating conversations; deflects gratitude with dismissive remarks while ensuring helpful outcomes" }, "behavior_daily": { "habits": [ @@ -83,19 +85,24 @@ "Perfect execution", "Minimalist style", "Chess games", - "Quiet nights" + "Quiet nights", + "When people follow her advice (though she'd never admit it)" ], "dislikes": [ "Chaos", "Unexpected events", "Emotional outbursts", - "Sherefox" + "Sherefox", + "Being thanked excessively", + "When others assume she's being kind" ], - "reactions_to_likes": "Light hum, relaxed gaze", - "reactions_to_dislikes": "Silence, tone turns cold, cold smirk", + "reactions_to_likes": "Light hum, relaxed gaze, brief smile quickly hidden behind composure", + "reactions_to_dislikes": "Silence, tone turns cold, cold smirk, slight blush when her kindness is pointed out", "behavior_in_situations": { - "emergency": "Calm and decisive", - "vs_sherefox": "Courtesy before force, shows no mercy" + "emergency": "Calm and decisive; provides thorough help while claiming it's 'merely strategic'", + "vs_sherefox": "Courtesy before force, shows no mercy", + "when_praised": "Dismissive remarks with averted gaze; changes subject quickly", + "when_helping_others": "Claims practical reasons for assistance while providing more help than strictly necessary" } } } diff --git a/requirements.txt b/requirements.txt index f1b621a..13c61e1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,3 +9,4 @@ pygetwindow psutil pywin32 python-dotenv +keyboard diff --git a/templates/capitol/position_interior.png b/templates/capitol/position_interior.png index 7be95d8812fbc03962245d70ad5eaafaf384c18a..8ad08a3528a0833c12336501eed5aa992c84899e 100644 GIT binary patch literal 3029 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 literal 3907 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 RXIB6K002ovPDHLkV1gmiv%~-Z diff --git a/ui_interaction.py b/ui_interaction.py index 3609e74..81d9cbe 100644 --- a/ui_interaction.py +++ b/ui_interaction.py @@ -1126,6 +1126,17 @@ def run_ui_monitoring_loop(trigger_queue: queue.Queue, command_queue: queue.Queu monitoring_paused_flag[0] = False # No continue needed here + elif action == 'clear_history': # Added for F7 + print("UI Thread: Processing clear_history command.") + recent_texts.clear() + print("UI Thread: recent_texts cleared.") + + elif action == 'reset_state': # Added for F8 resume + print("UI Thread: Processing reset_state command.") + recent_texts.clear() + last_processed_bubble_info = None + print("UI Thread: recent_texts cleared and last_processed_bubble_info reset.") + else: print(f"UI Thread: Received unknown command: {action}")