Merge pull request #6 from z060142/Refactoring
Improve the setup process
This commit is contained in:
commit
30e418eba4
4
.gitignore
vendored
4
.gitignore
vendored
@ -2,7 +2,9 @@
|
||||
*.log
|
||||
llm_debug.log
|
||||
config.py
|
||||
config.py.bak
|
||||
__pycache__/
|
||||
debug_screenshots/
|
||||
chat_logs/
|
||||
backup/
|
||||
backup/
|
||||
chroma_data/
|
||||
@ -176,13 +176,20 @@ Wolf Chat 是一個基於 MCP (Modular Capability Provider) 框架的聊天機
|
||||
|
||||
### 環境設定
|
||||
|
||||
1. **API 設定**:通過 .env 文件或環境變數設置 API 密鑰
|
||||
2. **MCP 服務器配置**:在 config.py 中配置要連接的 MCP 服務器
|
||||
3. **UI 樣本**:需要提供特定遊戲界面元素的截圖模板
|
||||
4. **遊戲視窗設定**:
|
||||
- 遊戲執行檔路徑 (`GAME_EXECUTABLE_PATH`):用於未來可能的自動啟動功能。
|
||||
- 目標視窗位置與大小 (`GAME_WINDOW_X`, `GAME_WINDOW_Y`, `GAME_WINDOW_WIDTH`, `GAME_WINDOW_HEIGHT`):由 `game_monitor.py` 使用。
|
||||
- 監控間隔 (`MONITOR_INTERVAL_SECONDS`):`game_monitor.py` 檢查視窗狀態的頻率。
|
||||
1. **首次設定 (Setup.py)**:
|
||||
* 執行 `python Setup.py`。
|
||||
* 此腳本會檢查 `config.py` 和 `.env` 文件是否存在。
|
||||
* 如果 `config.py` 不存在,它會使用 `config_template.py` 作為模板來創建一個新的 `config.py`。
|
||||
* 如果 `.env` 不存在,它會提示使用者輸入必要的 API 金鑰(例如 OpenAI API Key)和其他敏感配置,然後創建 `.env` 文件。
|
||||
* **重要**:`.env` 文件應加入 `.gitignore` 以避免提交敏感資訊。`config.py` 通常也應加入 `.gitignore`,因為它可能包含本地路徑或由 `Setup.py` 生成。
|
||||
2. **API 設定**:API 金鑰和其他敏感資訊儲存在 `.env` 文件中,由 `config.py` 讀取。
|
||||
3. **核心配置 (config.py)**:包含非敏感的系統參數、MCP 伺服器列表、UI 模板路徑、遊戲視窗設定等。此文件現在由 `Setup.py` 根據 `config_template.py` 生成(如果不存在)。
|
||||
4. **MCP 服務器配置**:在 `config.py` 中配置要連接的 MCP 服務器。
|
||||
5. **UI 樣本**:需要提供特定遊戲界面元素的截圖模板,路徑在 `config.py` 中定義。
|
||||
6. **遊戲視窗設定**:在 `config.py` 中配置:
|
||||
* 遊戲執行檔路徑 (`GAME_EXECUTABLE_PATH`)。
|
||||
* 目標視窗位置與大小 (`GAME_WINDOW_X`, `GAME_WINDOW_Y`, `GAME_WINDOW_WIDTH`, `GAME_WINDOW_HEIGHT`)。
|
||||
* 監控間隔 (`MONITOR_INTERVAL_SECONDS`)。
|
||||
|
||||
## 最近改進(2025-04-17)
|
||||
|
||||
@ -417,6 +424,18 @@ Wolf Chat 是一個基於 MCP (Modular Capability Provider) 框架的聊天機
|
||||
3. 恢復 UI 監控並重置狀態(清除 `recent_texts` 和 `last_processed_bubble_info`)。
|
||||
- **效果**:將暫停/恢復 UI 監控的時序控制權移至 `ui_interaction.py` 內部,減少了模塊間的直接依賴和潛在干擾,依賴持續監控來確保最終視窗狀態。
|
||||
|
||||
## 最近改進(2025-04-27)
|
||||
|
||||
### Setup.py 功能增強
|
||||
|
||||
- **目的**:增強 `Setup.py` 設定工具的功能,使其在保存設定後保持開啟,並提供直接啟動和終止 Chat Bot 及 Test 腳本的按鈕。
|
||||
- **修改內容**:
|
||||
- 修改 `save_settings` 方法,移除關閉視窗的邏輯,僅顯示保存成功的提示訊息。
|
||||
- 在 GUI 底部新增 "Run Chat Bot" 和 "Run Test" 按鈕,分別用於啟動 `main.py` 和 `test/llm_debug_script.py`。
|
||||
- 新增 "Stop Process" 按鈕,用於終止由上述兩個按鈕啟動的腳本。
|
||||
- 實現進程追蹤和按鈕狀態管理,確保在有腳本運行時禁用運行按鈕,啟用停止按鈕。
|
||||
- **效果**:提高了 `Setup.py` 的易用性,方便使用者在調整設定後直接啟動腳本進行測試,並提供了便捷的終止方式。
|
||||
|
||||
## 開發建議
|
||||
|
||||
### 優化方向
|
||||
|
||||
179
Setup.py
179
Setup.py
@ -26,13 +26,17 @@ import shutil
|
||||
VERSION = "1.0.0"
|
||||
CONFIG_TEMPLATE_PATH = "config_template.py"
|
||||
ENV_FILE_PATH = ".env"
|
||||
DEFAULT_CHROMA_DATA_PATH = "chroma_data"
|
||||
# Use absolute path for chroma_data
|
||||
DEFAULT_CHROMA_DATA_PATH = os.path.abspath("chroma_data")
|
||||
DEFAULT_CONFIG_SECTION = """# ====================================================================
|
||||
# Wolf Chat Configuration
|
||||
# Generated by setup.py - Edit with care
|
||||
# ====================================================================
|
||||
"""
|
||||
|
||||
# Get current Windows username for default paths
|
||||
CURRENT_USERNAME = os.getenv("USERNAME", "user")
|
||||
|
||||
# ===============================================================
|
||||
# Helper Functions
|
||||
# ===============================================================
|
||||
@ -61,17 +65,30 @@ def save_env_file(env_data):
|
||||
|
||||
def load_current_config():
|
||||
"""Extract settings from existing config.py if it exists"""
|
||||
# 新增一個幫助函數來標準化路徑
|
||||
def normalize_path(path):
|
||||
"""Convert backslashes to forward slashes in paths"""
|
||||
if path:
|
||||
return path.replace("\\", "/")
|
||||
return path
|
||||
|
||||
config_data = {
|
||||
"OPENAI_API_BASE_URL": "",
|
||||
"LLM_MODEL": "deepseek/deepseek-chat-v3-0324",
|
||||
"MCP_SERVERS": {},
|
||||
"MCP_SERVERS": {
|
||||
"exa": {
|
||||
"enabled": True,
|
||||
"use_smithery": False,
|
||||
"server_path": normalize_path(f"C:/Users/{CURRENT_USERNAME}/AppData/Roaming/npm/exa-mcp-server")
|
||||
}
|
||||
},
|
||||
"ENABLE_CHAT_LOGGING": True,
|
||||
"LOG_DIR": "chat_logs",
|
||||
"GAME_WINDOW_CONFIG": {
|
||||
"WINDOW_TITLE": "Last War-Survival Game",
|
||||
"ENABLE_SCHEDULED_RESTART": True,
|
||||
"RESTART_INTERVAL_MINUTES": 60,
|
||||
"GAME_EXECUTABLE_PATH": r"C:\Users\user\AppData\Local\TheLastWar\Launch.exe",
|
||||
"GAME_EXECUTABLE_PATH": normalize_path(fr"C:\Users\{CURRENT_USERNAME}\AppData\Local\TheLastWar\Launch.exe"),
|
||||
"GAME_WINDOW_X": 50,
|
||||
"GAME_WINDOW_Y": 30,
|
||||
"GAME_WINDOW_WIDTH": 600,
|
||||
@ -207,6 +224,18 @@ def generate_config_file(config_data, env_data):
|
||||
shutil.copy2("config.py", backup_path)
|
||||
print(f"Created backup of existing config at {backup_path}")
|
||||
|
||||
def normalize_path(path):
|
||||
"""Convert backslashes to forward slashes in paths"""
|
||||
if path:
|
||||
return path.replace("\\", "/")
|
||||
return path
|
||||
|
||||
# Helper function to ensure absolute path
|
||||
def ensure_absolute_path(path):
|
||||
if not os.path.isabs(path):
|
||||
return os.path.abspath(path)
|
||||
return path
|
||||
|
||||
with open("config.py", 'w', encoding='utf-8') as f:
|
||||
f.write(DEFAULT_CONFIG_SECTION)
|
||||
f.write("import os\n")
|
||||
@ -279,14 +308,17 @@ def generate_config_file(config_data, env_data):
|
||||
|
||||
# Handle Chroma server
|
||||
elif server_name == "chroma":
|
||||
# Ensure absolute path for chroma data directory
|
||||
data_dir = server_config.get("data_dir", DEFAULT_CHROMA_DATA_PATH)
|
||||
absolute_data_dir = ensure_absolute_path(data_dir)
|
||||
|
||||
f.write(" \"command\": \"uvx\",\n")
|
||||
f.write(" \"args\": [\n")
|
||||
f.write(" \"chroma-mcp\",\n")
|
||||
f.write(" \"--client-type\",\n")
|
||||
f.write(" \"persistent\",\n")
|
||||
f.write(" \"--data-dir\",\n")
|
||||
f.write(f" \"{data_dir}\"\n")
|
||||
f.write(f" \"{absolute_data_dir}\"\n")
|
||||
f.write(" ]\n")
|
||||
|
||||
# Handle custom server - just write as raw JSON
|
||||
@ -357,6 +389,9 @@ class WolfChatSetup(tk.Tk):
|
||||
# Create bottom buttons
|
||||
self.create_bottom_buttons()
|
||||
|
||||
# Initialize running process tracker
|
||||
self.running_process = None
|
||||
|
||||
# Set initial states based on loaded data
|
||||
self.update_ui_from_data()
|
||||
|
||||
@ -537,25 +572,25 @@ class WolfChatSetup(tk.Tk):
|
||||
type_label = ttk.Label(type_frame, text="Server Type:", width=15)
|
||||
type_label.pack(side=tk.LEFT)
|
||||
|
||||
self.exa_type_var = tk.StringVar(value="smithery")
|
||||
self.exa_type_var = tk.StringVar(value="local") # Changed default to local
|
||||
|
||||
smithery_radio = ttk.Radiobutton(type_frame, text="Smithery (recommended)",
|
||||
variable=self.exa_type_var, value="smithery",
|
||||
command=self.update_exa_settings_visibility)
|
||||
smithery_radio.pack(anchor=tk.W)
|
||||
|
||||
local_radio = ttk.Radiobutton(type_frame, text="Local Server",
|
||||
local_radio = ttk.Radiobutton(type_frame, text="Local Server (recommended)",
|
||||
variable=self.exa_type_var, value="local",
|
||||
command=self.update_exa_settings_visibility)
|
||||
local_radio.pack(anchor=tk.W)
|
||||
|
||||
smithery_radio = ttk.Radiobutton(type_frame, text="Smithery",
|
||||
variable=self.exa_type_var, value="smithery",
|
||||
command=self.update_exa_settings_visibility)
|
||||
smithery_radio.pack(anchor=tk.W)
|
||||
|
||||
# Local Server Path
|
||||
self.exa_local_frame = ttk.Frame(self.exa_frame)
|
||||
|
||||
local_path_label = ttk.Label(self.exa_local_frame, text="Server Path:", width=15)
|
||||
local_path_label.pack(side=tk.LEFT)
|
||||
|
||||
self.exa_path_var = tk.StringVar()
|
||||
self.exa_path_var = tk.StringVar(value=f"C:/Users/{CURRENT_USERNAME}/AppData/Roaming/npm/exa-mcp-server")
|
||||
self.exa_path_entry = ttk.Entry(self.exa_local_frame, textvariable=self.exa_path_var)
|
||||
self.exa_path_entry.pack(side=tk.LEFT, fill=tk.X, expand=True)
|
||||
|
||||
@ -577,8 +612,8 @@ class WolfChatSetup(tk.Tk):
|
||||
info_text = (
|
||||
"• Exa MCP provides web search and research capabilities\n"
|
||||
"• An Exa API key is required (obtain from https://exa.ai)\n"
|
||||
"• Smithery is easiest to use but requires an internet connection\n"
|
||||
"• Local server requires manual installation of exa-mcp-server"
|
||||
"• Local server is the default and preferred option\n"
|
||||
"• Smithery requires an internet connection each time"
|
||||
)
|
||||
|
||||
info_label = ttk.Label(info_frame, text=info_text, justify=tk.LEFT, wraplength=400)
|
||||
@ -710,7 +745,7 @@ class WolfChatSetup(tk.Tk):
|
||||
path_label = ttk.Label(path_frame, text="Game Executable Path:", width=20)
|
||||
path_label.pack(side=tk.LEFT, padx=(0, 5))
|
||||
|
||||
self.game_path_var = tk.StringVar()
|
||||
self.game_path_var = tk.StringVar(value=fr"C:\Users\{CURRENT_USERNAME}\AppData\Local\TheLastWar\Launch.exe")
|
||||
path_entry = ttk.Entry(path_frame, textvariable=self.game_path_var)
|
||||
path_entry.pack(side=tk.LEFT, fill=tk.X, expand=True)
|
||||
|
||||
@ -812,17 +847,27 @@ class WolfChatSetup(tk.Tk):
|
||||
version_label = ttk.Label(btn_frame, text=f"v{VERSION}")
|
||||
version_label.pack(side=tk.LEFT, padx=5)
|
||||
|
||||
# Install dependencies button
|
||||
install_deps_btn = ttk.Button(btn_frame, text="Install Dependencies", command=self.install_dependencies)
|
||||
install_deps_btn.pack(side=tk.RIGHT, padx=5)
|
||||
# Action buttons on right (order matters for packing)
|
||||
cancel_btn = ttk.Button(btn_frame, text="Cancel", command=self.quit)
|
||||
cancel_btn.pack(side=tk.RIGHT, padx=5)
|
||||
|
||||
# Action buttons on right
|
||||
save_btn = ttk.Button(btn_frame, text="Save Settings", command=self.save_settings)
|
||||
save_btn.pack(side=tk.RIGHT, padx=5)
|
||||
|
||||
cancel_btn = ttk.Button(btn_frame, text="Cancel", command=self.quit)
|
||||
cancel_btn.pack(side=tk.RIGHT, padx=5)
|
||||
|
||||
install_deps_btn = ttk.Button(btn_frame, text="Install Dependencies", command=self.install_dependencies)
|
||||
install_deps_btn.pack(side=tk.RIGHT, padx=5)
|
||||
|
||||
# Run buttons
|
||||
self.run_test_btn = ttk.Button(btn_frame, text="Run Test", command=self.run_test_script)
|
||||
self.run_test_btn.pack(side=tk.RIGHT, padx=5)
|
||||
|
||||
self.run_bot_btn = ttk.Button(btn_frame, text="Run Chat Bot", command=self.run_chat_bot)
|
||||
self.run_bot_btn.pack(side=tk.RIGHT, padx=5)
|
||||
|
||||
# Stop button
|
||||
self.stop_btn = ttk.Button(btn_frame, text="Stop Process", command=self.stop_process, state=tk.DISABLED)
|
||||
self.stop_btn.pack(side=tk.RIGHT, padx=5)
|
||||
|
||||
def install_dependencies(self):
|
||||
"""Run the installation script for dependencies"""
|
||||
try:
|
||||
@ -840,6 +885,72 @@ class WolfChatSetup(tk.Tk):
|
||||
except Exception as e:
|
||||
messagebox.showerror("Error", f"Failed to launch installer: {str(e)}")
|
||||
|
||||
def run_chat_bot(self):
|
||||
"""Run the main chat bot script"""
|
||||
try:
|
||||
import subprocess
|
||||
import sys
|
||||
if not os.path.exists("main.py"):
|
||||
messagebox.showerror("Error", "Could not find main.py script")
|
||||
return
|
||||
|
||||
if self.running_process is not None:
|
||||
messagebox.showwarning("Already Running", "Another process is already running. Please stop it first.")
|
||||
return
|
||||
|
||||
self.running_process = subprocess.Popen([sys.executable, "main.py"])
|
||||
print("Attempting to start main.py...")
|
||||
self.update_run_button_states(False) # Disable run buttons, enable stop
|
||||
except Exception as e:
|
||||
messagebox.showerror("Error", f"Failed to launch main.py: {str(e)}")
|
||||
self.update_run_button_states(True) # Re-enable buttons on failure
|
||||
|
||||
def run_test_script(self):
|
||||
"""Run the LLM debug script"""
|
||||
try:
|
||||
import subprocess
|
||||
import sys
|
||||
test_script_path = os.path.join("test", "llm_debug_script.py")
|
||||
if not os.path.exists(test_script_path):
|
||||
messagebox.showerror("Error", f"Could not find {test_script_path}")
|
||||
return
|
||||
|
||||
if self.running_process is not None:
|
||||
messagebox.showwarning("Already Running", "Another process is already running. Please stop it first.")
|
||||
return
|
||||
|
||||
self.running_process = subprocess.Popen([sys.executable, test_script_path])
|
||||
print(f"Attempting to start {test_script_path}...")
|
||||
self.update_run_button_states(False) # Disable run buttons, enable stop
|
||||
except Exception as e:
|
||||
messagebox.showerror("Error", f"Failed to launch {test_script_path}: {str(e)}")
|
||||
self.update_run_button_states(True) # Re-enable buttons on failure
|
||||
|
||||
def stop_process(self):
|
||||
"""Stop the currently running process"""
|
||||
if hasattr(self, 'running_process') and self.running_process is not None:
|
||||
try:
|
||||
print("Attempting to terminate running process...")
|
||||
self.running_process.terminate() # Or .kill() for a more forceful stop
|
||||
self.running_process = None
|
||||
messagebox.showinfo("Process Stopped", "The running process has been terminated.")
|
||||
except Exception as e:
|
||||
messagebox.showerror("Error", f"Failed to terminate process: {str(e)}")
|
||||
finally:
|
||||
# Re-enable run buttons and disable stop button
|
||||
self.update_run_button_states(True)
|
||||
else:
|
||||
messagebox.showinfo("No Process", "No process is currently running.")
|
||||
|
||||
def update_run_button_states(self, enable):
|
||||
"""Enable or disable the run buttons and update stop button state"""
|
||||
# Assuming run_bot_btn and run_test_btn exist and are class attributes
|
||||
if hasattr(self, 'run_bot_btn'):
|
||||
self.run_bot_btn.config(state=tk.NORMAL if enable else tk.DISABLED)
|
||||
if hasattr(self, 'run_test_btn'):
|
||||
self.run_test_btn.config(state=tk.NORMAL if enable else tk.DISABLED)
|
||||
if hasattr(self, 'stop_btn'):
|
||||
self.stop_btn.config(state=tk.DISABLED if enable else tk.NORMAL)
|
||||
|
||||
def update_ui_from_data(self):
|
||||
"""Update UI controls from loaded data"""
|
||||
@ -869,7 +980,11 @@ class WolfChatSetup(tk.Tk):
|
||||
self.chroma_enable_var.set(chroma_config.get("enabled", True))
|
||||
data_dir = chroma_config.get("data_dir", "")
|
||||
if data_dir:
|
||||
self.chroma_dir_var.set(data_dir)
|
||||
# Ensure data directory is absolute path
|
||||
self.chroma_dir_var.set(os.path.abspath(data_dir))
|
||||
else:
|
||||
# Set default as absolute path
|
||||
self.chroma_dir_var.set(DEFAULT_CHROMA_DATA_PATH)
|
||||
|
||||
# Update servers list to include custom servers
|
||||
self.update_servers_list()
|
||||
@ -932,16 +1047,20 @@ class WolfChatSetup(tk.Tk):
|
||||
initialdir=os.path.expanduser("~")
|
||||
)
|
||||
if file_path:
|
||||
self.exa_path_var.set(file_path)
|
||||
# 標準化路徑格式(將反斜線改為正斜線)
|
||||
normalized_path = file_path.replace("\\", "/")
|
||||
self.exa_path_var.set(normalized_path)
|
||||
|
||||
def browse_chroma_dir(self):
|
||||
"""Browse for Chroma data directory"""
|
||||
dir_path = filedialog.askdirectory(
|
||||
title="Select Chroma Data Directory",
|
||||
initialdir=os.path.abspath(DEFAULT_CHROMA_DATA_PATH)
|
||||
initialdir=os.path.dirname(DEFAULT_CHROMA_DATA_PATH)
|
||||
)
|
||||
if dir_path:
|
||||
self.chroma_dir_var.set(dir_path)
|
||||
# 標準化路徑格式(將反斜線改為正斜線)
|
||||
normalized_path = os.path.abspath(dir_path).replace("\\", "/")
|
||||
self.chroma_dir_var.set(normalized_path)
|
||||
|
||||
def browse_game_path(self):
|
||||
"""Browse for game executable"""
|
||||
@ -951,7 +1070,9 @@ class WolfChatSetup(tk.Tk):
|
||||
initialdir=os.path.expanduser("~")
|
||||
)
|
||||
if file_path:
|
||||
self.game_path_var.set(file_path)
|
||||
# 標準化路徑格式(將反斜線改為正斜線)
|
||||
normalized_path = file_path.replace("\\", "/")
|
||||
self.game_path_var.set(normalized_path)
|
||||
|
||||
def update_servers_list(self):
|
||||
"""Update the servers listbox with current servers"""
|
||||
@ -1148,7 +1269,7 @@ class WolfChatSetup(tk.Tk):
|
||||
generate_config_file(self.config_data, self.env_data)
|
||||
|
||||
messagebox.showinfo("Success", "Settings saved successfully.\nRestart Wolf Chat for changes to take effect.")
|
||||
self.destroy()
|
||||
# self.destroy() # Removed to keep the window open after saving
|
||||
|
||||
except Exception as e:
|
||||
messagebox.showerror("Error", f"An error occurred while saving settings:\n{str(e)}")
|
||||
@ -1160,4 +1281,4 @@ class WolfChatSetup(tk.Tk):
|
||||
# ===============================================================
|
||||
if __name__ == "__main__":
|
||||
app = WolfChatSetup()
|
||||
app.mainloop()
|
||||
app.mainloop()
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user