Add Bot setup UI

This commit is contained in:
z060142 2025-04-26 14:00:26 +08:00
parent 96f53ecdfc
commit 494f6e2943
7 changed files with 1365 additions and 359 deletions

4
.gitignore vendored
View File

@ -1,6 +1,8 @@
.env
*.log
llm_debug.log
config.py
__pycache__/
debug_screenshots/
chat_logs/
chat_logs/
backup/

1163
Setup.py Normal file

File diff suppressed because it is too large Load Diff

134
config.py
View File

@ -1,134 +0,0 @@
# config.py
import os
import json # Import json for building args string
from dotenv import load_dotenv # Import load_dotenv
# --- Load environment variables from .env file ---
load_dotenv()
print("Attempted to load environment variables from .env file.")
# --- End Load ---
# OpenAI API Configuration / OpenAI-Compatible Provider Settings
# --- Modify these lines ---
# Leave OPENAI_API_BASE_URL as None or "" to use official OpenAI
OPENAI_API_BASE_URL = "https://openrouter.ai/api/v1" # <--- For example "http://localhost:1234/v1" or your provider URL
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")
MCP_REDIS_API_KEY = os.getenv("MCP_REDIS_APU_KEY")
MCP_REDIS_PATH = os.getenv("MCP_REDIS_PATH")
# --- Dynamically build Exa server args ---
exa_config_dict = {"exaApiKey": EXA_API_KEY if EXA_API_KEY else "YOUR_EXA_KEY_MISSING"}
# Need to dump dict to JSON string, then properly escape it for cmd arg
# Using json.dumps handles internal quotes correctly.
# The outer quotes for cmd might need careful handling depending on OS / shell.
# For cmd /c on Windows, embedding escaped JSON often works like this:
exa_config_arg_string = json.dumps(json.dumps(exa_config_dict)) # Double dump for cmd escaping? Or just one? Test needed.
# Let's try single dump first, often sufficient if passed correctly by subprocess
exa_config_arg_string_single_dump = json.dumps(exa_config_dict) # Use this one
# --- MCP Server Configuration ---
MCP_SERVERS = {
#"exa": { # Temporarily commented out to prevent blocking startup
## "command": "cmd",
# "args": [
# "/c",
# "npx",
# "-y",
# "@smithery/cli@latest",
# "run",
# "exa",
# "--config",
# # Pass the dynamically created config string with the environment variable key
# exa_config_arg_string_single_dump # Use the single dump variable
# ],
#},
"exa": {
"command": "npx",
"args": [
"C:/Users/Bigspring/AppData/Roaming/npm/exa-mcp-server",
"--tools=web_search,research_paper_search,twitter_search,company_research,crawling,competitor_finder"
],
"env": {
"EXA_API_KEY": EXA_API_KEY
}
},
#"github.com/modelcontextprotocol/servers/tree/main/src/memory": {
# "command": "npx",
# "args": [
# "-y",
# "@modelcontextprotocol/server-memory"
# ],
# "disabled": False
#},
#"redis": {
# "command": "uv",
# "args": [
# "--directory",
# MCP_REDIS_PATH,
# "run",
# "src/main.py"
# ],
# "env": {
# "REDIS_HOST": "127.0.0.1",
# "REDIS_PORT": "6379",
# "REDIS_SSL": "False",
# "REDIS_CLUSTER_MODE": "False"
# }
# }
#"basic-memory": {
# "command": "uvx",
# "args": [
# "basic-memory",
# "mcp"
# ],
#}
"chroma": {
"command": "uvx",
"args": [
"chroma-mcp",
"--client-type",
"persistent",
"--data-dir",
"Z:/mcp/Server/Chroma-MCP"
]
}
}
# MCP Client Configuration
MCP_CONFIRM_TOOL_EXECUTION = False # True: Confirm before execution, False: Execute automatically
# --- Chat Logging Configuration ---
ENABLE_CHAT_LOGGING = True # True: Enable logging, False: Disable logging
LOG_DIR = "chat_logs" # Directory to store chat logs
# Persona Configuration
PERSONA_NAME = "Wolfhart"
# PERSONA_RESOURCE_URI = "persona://wolfhart/details" # Now using local file instead
# Game window title (used in ui_interaction.py and game_monitor.py)
WINDOW_TITLE = "Last War-Survival Game"
# --- Game Monitor Configuration ---
ENABLE_SCHEDULED_RESTART = True # 是否啟用定時重啟遊戲功能
RESTART_INTERVAL_MINUTES = 60 # 定時重啟的間隔時間(分鐘),預設 4 小時
GAME_EXECUTABLE_PATH = r"C:\Users\Bigspring\AppData\Local\TheLastWar\Launch.exe" # Path to the game launcher
GAME_WINDOW_X = 50 # Target X position for the game window
GAME_WINDOW_Y = 30 # Target Y position for the game window
GAME_WINDOW_WIDTH = 600 # Target width for the game window
GAME_WINDOW_HEIGHT = 1070 # Target height for the game window
MONITOR_INTERVAL_SECONDS = 5 # How often to check the window (in seconds)
# --- Print loaded keys for verification (Optional - BE CAREFUL!) ---
# print(f"DEBUG: Loaded OPENAI_API_KEY: {'*' * (len(OPENAI_API_KEY) - 4) + OPENAI_API_KEY[-4:] if OPENAI_API_KEY else 'Not Found'}")
print(f"DEBUG: Loaded EXA_API_KEY: {'*' * (len(EXA_API_KEY) - 4) + EXA_API_KEY[-4:] if EXA_API_KEY else 'Not Found'}") # Uncommented Exa key check
# print(f"DEBUG: Exa args: {MCP_SERVERS['exa']['args']}")

62
config_template.py Normal file
View File

@ -0,0 +1,62 @@
# ====================================================================
# Wolf Chat Configuration Template
# This file is used by setup.py to generate the final config.py
# ====================================================================
import os
import json
from dotenv import load_dotenv
# --- Load environment variables from .env file ---
load_dotenv()
print("Loaded environment variables from .env file.")
# =============================================================================
# OpenAI API Configuration / OpenAI-Compatible Provider Settings
# =============================================================================
# Leave OPENAI_API_BASE_URL as None or "" to use official OpenAI
OPENAI_API_BASE_URL = "${OPENAI_API_BASE_URL}"
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
LLM_MODEL = "${LLM_MODEL}"
# =============================================================================
# External API Keys
# =============================================================================
EXA_API_KEY = os.getenv("EXA_API_KEY")
# --- Exa Configuration ---
exa_config_dict = {"exaApiKey": EXA_API_KEY if EXA_API_KEY else "YOUR_EXA_KEY_MISSING"}
exa_config_arg_string = json.dumps(exa_config_dict)
# =============================================================================
# MCP Server Configuration
# =============================================================================
MCP_SERVERS = ${MCP_SERVERS}
# =============================================================================
# MCP Client Configuration
# =============================================================================
MCP_CONFIRM_TOOL_EXECUTION = False # True: Confirm before execution, False: Execute automatically
# =============================================================================
# Chat Logging Configuration
# =============================================================================
ENABLE_CHAT_LOGGING = ${ENABLE_CHAT_LOGGING}
LOG_DIR = "${LOG_DIR}"
# =============================================================================
# Persona Configuration
# =============================================================================
PERSONA_NAME = "Wolfhart"
# =============================================================================
# Game Window Configuration
# =============================================================================
WINDOW_TITLE = "${WINDOW_TITLE}"
ENABLE_SCHEDULED_RESTART = ${ENABLE_SCHEDULED_RESTART}
RESTART_INTERVAL_MINUTES = ${RESTART_INTERVAL_MINUTES}
GAME_EXECUTABLE_PATH = r"${GAME_EXECUTABLE_PATH}"
GAME_WINDOW_X = ${GAME_WINDOW_X}
GAME_WINDOW_Y = ${GAME_WINDOW_Y}
GAME_WINDOW_WIDTH = ${GAME_WINDOW_WIDTH}
GAME_WINDOW_HEIGHT = ${GAME_WINDOW_HEIGHT}
MONITOR_INTERVAL_SECONDS = ${MONITOR_INTERVAL_SECONDS}

137
install.py Normal file
View File

@ -0,0 +1,137 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Wolf Chat Installation Script
Installs required dependencies for Wolf Chat
"""
import os
import sys
import subprocess
import tkinter as tk
from tkinter import ttk, messagebox
REQUIREMENTS = [
"openai",
"mcp",
"pyautogui",
"opencv-python",
"numpy",
"pyperclip",
"pygetwindow",
"psutil",
"pywin32",
"python-dotenv",
"keyboard"
]
def install_requirements(progress_var=None, status_label=None, root=None):
"""Install all required packages using pip"""
total = len(REQUIREMENTS)
success_count = 0
failed_packages = []
for i, package in enumerate(REQUIREMENTS):
if status_label:
status_label.config(text=f"Installing {package}...")
if progress_var:
progress_var.set((i / total) * 100)
if root:
root.update()
try:
print(f"Installing {package}...")
# Use subprocess to run pip install
process = subprocess.run(
[sys.executable, "-m", "pip", "install", package],
check=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True
)
print(f"Successfully installed {package}")
success_count += 1
except subprocess.CalledProcessError as e:
print(f"Failed to install {package}: {e}")
print(f"Error output: {e.stderr}")
failed_packages.append(package)
except Exception as e:
print(f"Unexpected error installing {package}: {str(e)}")
failed_packages.append(package)
# Final progress update
if progress_var:
progress_var.set(100)
# Report results
if not failed_packages:
result_message = f"All {success_count} packages installed successfully!"
print(result_message)
if status_label:
status_label.config(text=result_message)
return True, result_message
else:
result_message = f"Installed {success_count}/{total} packages. Failed: {', '.join(failed_packages)}"
print(result_message)
if status_label:
status_label.config(text=result_message)
return False, result_message
def run_installer_gui():
"""Run a simple GUI for the installer"""
root = tk.Tk()
root.title("Wolf Chat Installer")
root.geometry("400x200")
root.resizable(False, False)
# Main frame
main_frame = ttk.Frame(root, padding=20)
main_frame.pack(fill=tk.BOTH, expand=True)
# Title
title_label = ttk.Label(main_frame, text="Wolf Chat Dependency Installer", font=("", 12, "bold"))
title_label.pack(pady=(0, 10))
# Info text
info_text = f"This will install {len(REQUIREMENTS)} required packages for Wolf Chat."
info_label = ttk.Label(main_frame, text=info_text)
info_label.pack(pady=(0, 15))
# Progress bar
progress_var = tk.DoubleVar()
progress_bar = ttk.Progressbar(main_frame, variable=progress_var, maximum=100)
progress_bar.pack(fill=tk.X, pady=(0, 10))
# Status label
status_label = ttk.Label(main_frame, text="Ready to install...")
status_label.pack(pady=(0, 15))
# Install button
def start_installation():
# Disable button during installation
install_button.config(state=tk.DISABLED)
# Run installation in a separate thread to keep UI responsive
success, message = install_requirements(progress_var, status_label, root)
# Show completion message
if success:
messagebox.showinfo("Installation Complete", message)
else:
messagebox.showwarning("Installation Issues", message)
# Close the window
root.destroy()
install_button = ttk.Button(main_frame, text="Install Dependencies", command=start_installation)
install_button.pack()
# Start the GUI loop
root.mainloop()
if __name__ == "__main__":
# If run directly, show GUI
run_installer_gui()

View File

@ -1,121 +0,0 @@
#!/usr/bin/env python
"""
Game Window Monitor Script - Keep game window on top and in position
This script monitors a specified game window, ensuring it stays
always on top and at the desired screen coordinates.
"""
import time
import argparse
import pygetwindow as gw
import win32gui
import win32con
def find_window_by_title(window_title):
"""Find the first window matching the title."""
try:
windows = gw.getWindowsWithTitle(window_title)
if windows:
return windows[0]
except Exception as e:
# pygetwindow can sometimes raise exceptions if a window disappears
# during enumeration. Ignore these for monitoring purposes.
# print(f"Error finding window: {e}")
pass
return None
def set_window_always_on_top(hwnd):
"""Set the window to be always on top."""
try:
win32gui.SetWindowPos(hwnd, win32con.HWND_TOPMOST, 0, 0, 0, 0,
win32con.SWP_NOMOVE | win32con.SWP_NOSIZE | win32con.SWP_SHOWWINDOW)
# print(f"Window {hwnd} set to always on top.")
except Exception as e:
print(f"Error setting window always on top: {e}")
def move_window_if_needed(window, target_x, target_y):
"""Move the window to the target coordinates if it's not already there."""
try:
current_x, current_y = window.topleft
if current_x != target_x or current_y != target_y:
print(f"Window moved from ({current_x}, {current_y}). Moving back to ({target_x}, {target_y}).")
window.moveTo(target_x, target_y)
# print(f"Window moved to ({target_x}, {target_y}).")
except gw.PyGetWindowException as e:
# Handle cases where the window might close unexpectedly
print(f"Error accessing window properties (might be closed): {e}")
except Exception as e:
print(f"Error moving window: {e}")
def main():
parser = argparse.ArgumentParser(description='Game Window Monitor Tool')
parser.add_argument('--window_title', default="Last War-Survival Game", help='Game window title to monitor')
parser.add_argument('--x', type=int, default=50, help='Target window X coordinate')
parser.add_argument('--y', type=int, default=30, help='Target window Y coordinate')
parser.add_argument('--interval', type=float, default=1.0, help='Check interval in seconds')
args = parser.parse_args()
print(f"Monitoring window: '{args.window_title}'")
print(f"Target position: ({args.x}, {args.y})")
print(f"Check interval: {args.interval} seconds")
print("Press Ctrl+C to stop.")
hwnd = None
last_hwnd_check_time = 0
try:
while True:
current_time = time.time()
window = None
# Find window handle (HWND) - less frequent check if already found
# pygetwindow can be slow, so avoid calling it too often if we have a valid handle
if not hwnd or current_time - last_hwnd_check_time > 5: # Re-check HWND every 5 seconds
window_obj = find_window_by_title(args.window_title)
if window_obj:
# Get the HWND (window handle) needed for win32gui
# Accessing _hWnd is using an internal attribute, but it's common practice with pygetwindow
try:
hwnd = window_obj._hWnd
window = window_obj # Keep the pygetwindow object for position checks
last_hwnd_check_time = current_time
# print(f"Found window HWND: {hwnd}")
except AttributeError:
print("Could not get HWND from window object. Retrying...")
hwnd = None
else:
if hwnd:
print(f"Window '{args.window_title}' lost.")
hwnd = None # Reset hwnd if window not found
if hwnd:
# Ensure it's always on top
set_window_always_on_top(hwnd)
# Check and correct position using the pygetwindow object if available
# Re-find the pygetwindow object if needed for position check
if not window:
window = find_window_by_title(args.window_title)
if window:
move_window_if_needed(window, args.x, args.y)
else:
# If we have hwnd but can't get pygetwindow object, maybe it's closing
print(f"Have HWND {hwnd} but cannot get window object for position check.")
hwnd = None # Force re-find next cycle
else:
# print(f"Window '{args.window_title}' not found. Waiting...")
pass # Wait for the window to appear
time.sleep(args.interval)
except KeyboardInterrupt:
print("\nMonitoring stopped by user.")
except Exception as e:
print(f"\nAn unexpected error occurred: {e}")
if __name__ == "__main__":
main()

View File

@ -1,103 +0,0 @@
#!/usr/bin/env python
"""
Game Window Setup Script - Adjust game window position and size
This script will launch the game and adjust its window to a specified position and size (100,100 1280x768),
making it easier to take screenshots of UI elements for later use.
"""
import os
import time
import subprocess
import pygetwindow as gw
import psutil
import argparse
def is_process_running(process_name):
"""Check if a specified process is currently running"""
for proc in psutil.process_iter(['name']):
if proc.info['name'].lower() == process_name.lower():
return True
return False
def launch_game(game_path):
"""Launch the game"""
if not os.path.exists(game_path):
print(f"Error: Game executable not found at {game_path}")
return False
print(f"Launching game: {game_path}")
subprocess.Popen(game_path)
return True
def find_game_window(window_title, max_wait=30):
"""Find the game window"""
print(f"Searching for game window: {window_title}")
start_time = time.time()
while time.time() - start_time < max_wait:
try:
windows = gw.getWindowsWithTitle(window_title)
if windows:
return windows[0]
except Exception as e:
print(f"Error finding window: {e}")
print("Window not found, waiting 1 second before retrying...")
time.sleep(1)
print(f"Error: Game window not found within {max_wait} seconds")
return None
def set_window_position_size(window, x, y, width, height):
"""Set window position and size"""
try:
print(f"Adjusting window position to ({x}, {y}) and size to {width}x{height}")
window.moveTo(x, y)
window.resizeTo(width, height)
print("Window adjustment completed")
return True
except Exception as e:
print(f"Error adjusting window: {e}")
return False
def main():
parser = argparse.ArgumentParser(description='Game Window Setup Tool')
parser.add_argument('--launch', action='store_true', help='Whether to launch the game')
parser.add_argument('--game_path', default=r"C:\Users\Bigspring\AppData\Local\TheLastWar\Launch.exe", help='Game launcher path')
parser.add_argument('--window_title', default="Last War-Survival Game", help='Game window title')
parser.add_argument('--process_name', default="LastWar.exe", help='Game process name')
parser.add_argument('--x', type=int, default=50, help='Window X coordinate')
parser.add_argument('--y', type=int, default=30, help='Window Y coordinate')
parser.add_argument('--width', type=int, default=600, help='Window width')
parser.add_argument('--height', type=int, default=1070, help='Window height')
args = parser.parse_args()
# Check if game is already running
if not is_process_running(args.process_name):
if args.launch:
# Launch the game
if not launch_game(args.game_path):
return
else:
print(f"Game process {args.process_name} is not running, please launch the game first or use the --launch parameter")
return
else:
print(f"Game process {args.process_name} is already running")
# Find game window
window = find_game_window(args.window_title)
if not window:
return
# Set window position and size
set_window_position_size(window, args.x, args.y, args.width, args.height)
# Display final window state
print("\nFinal window state:")
print(f"Position: ({window.left}, {window.top})")
print(f"Size: {window.width}x{window.height}")
if __name__ == "__main__":
main()