209 lines
7.7 KiB
Python
209 lines
7.7 KiB
Python
import os
|
||
import time
|
||
import threading
|
||
from flask import Flask, render_template, send_from_directory, request, jsonify
|
||
from flask_cors import CORS
|
||
|
||
|
||
import chat_splitter
|
||
import analyzer
|
||
|
||
# --- 1. 配置区域 ---
|
||
|
||
# 定义项目根目录 (F:\工作\项目\G36\官网)
|
||
ROOT_PROJECT_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
|
||
|
||
app = Flask(__name__, static_folder=ROOT_PROJECT_DIR, static_url_path='/')
|
||
CORS(app) # 启用CORS,允许来自所有源的请求
|
||
|
||
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
||
UPLOADS_FOLDER = os.path.join(BASE_DIR, 'uploads')
|
||
SPLIT_LOGS_FOLDER = os.path.join(BASE_DIR, '聊天记录_按天拆分')
|
||
REPORTS_FOLDER = os.path.join(BASE_DIR, '分析报告')
|
||
LOG_FILE = os.path.join(BASE_DIR, 'app_activity.log')
|
||
# -- 新增 --: 用于持久化存储上一次分析时间戳的状态文件
|
||
STATE_FILE = os.path.join(BASE_DIR, '.app_state')
|
||
|
||
CHECK_INTERVAL_SECONDS = 300
|
||
|
||
APP_CONFIG = {
|
||
"OPENROUTER_API_KEY": "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",
|
||
"YOUR_SITE_URL": "https://your-site.com",
|
||
"YOUR_SITE_NAME": "My Game Analysis",
|
||
"MODEL_NAME": "google/gemini-2.5-pro"
|
||
}
|
||
|
||
# 全局变量,将在启动时从状态文件加载
|
||
last_known_mtime = 0
|
||
|
||
# --- 2. 日志与状态管理 (核心修改区域) ---
|
||
|
||
def log_activity(message: str):
|
||
"""记录活动到日志文件并打印到控制台。"""
|
||
timestamp = time.strftime("%Y-%m-%d %H:%M:%S")
|
||
log_message = f"[{timestamp}] {message}"
|
||
print(log_message)
|
||
try:
|
||
with open(LOG_FILE, 'a', encoding='utf-8') as f:
|
||
f.write(log_message + '\\n')
|
||
except IOError as e:
|
||
print(f"[{timestamp}] CRITICAL: Unable to write to log file '{LOG_FILE}'. Error: {e}")
|
||
|
||
# -- 新增 --: 从状态文件加载上一次的修改时间
|
||
def load_last_mtime() -> float:
|
||
"""从.app_state文件加载上次记录的mtime,如果文件不存在或无效则返回0。"""
|
||
try:
|
||
if os.path.exists(STATE_FILE):
|
||
with open(STATE_FILE, 'r') as f:
|
||
return float(f.read().strip())
|
||
except (IOError, ValueError) as e:
|
||
log_activity(f"Warning: Could not read state from '{STATE_FILE}', defaulting to 0. Error: {e}")
|
||
return 0.0
|
||
|
||
# -- 新增 --: 将最新的修改时间保存到状态文件
|
||
def save_last_mtime(mtime: float):
|
||
"""将新的mtime保存到.app_state文件。"""
|
||
try:
|
||
with open(STATE_FILE, 'w') as f:
|
||
f.write(str(mtime))
|
||
except IOError as e:
|
||
log_activity(f"Warning: Could not save state to '{STATE_FILE}'. Error: {e}")
|
||
|
||
# --- 3. 后台自动分析任务 ---
|
||
|
||
def get_latest_mtime_in_folder(folder_path: str) -> float:
|
||
"""获取指定文件夹内所有.txt文件的最新修改时间。"""
|
||
latest_mtime = 0
|
||
try:
|
||
txt_files = [f for f in os.listdir(folder_path) if f.endswith('.txt')]
|
||
if not txt_files: return 0
|
||
for filename in txt_files:
|
||
file_path = os.path.join(folder_path, filename)
|
||
try:
|
||
mtime = os.path.getmtime(file_path)
|
||
if mtime > latest_mtime:
|
||
latest_mtime = mtime
|
||
except FileNotFoundError:
|
||
continue
|
||
except FileNotFoundError:
|
||
return 0
|
||
return latest_mtime
|
||
|
||
def run_the_analysis():
|
||
"""封装核心分析逻辑,以便可以被手动调用。"""
|
||
global last_known_mtime
|
||
log_activity("--- Triggering analysis manually. ---")
|
||
|
||
current_latest_mtime = get_latest_mtime_in_folder(UPLOADS_FOLDER)
|
||
|
||
log_activity("Step 1/2: Merging and splitting all chat logs by day...")
|
||
chat_splitter.split_log_file(UPLOADS_FOLDER, SPLIT_LOGS_FOLDER)
|
||
|
||
log_activity("Step 2/2: Analyzing new daily log files...")
|
||
new_reports_count = analyzer.run_analysis_on_new_files(
|
||
input_folder=SPLIT_LOGS_FOLDER,
|
||
output_folder=REPORTS_FOLDER,
|
||
config=APP_CONFIG
|
||
)
|
||
|
||
if new_reports_count > 0:
|
||
log_activity(f"--- SUCCESS: Analysis complete. Generated/Updated {new_reports_count} report(s). ---")
|
||
else:
|
||
log_activity("--- Analysis complete. No new reports were generated. ---")
|
||
|
||
last_known_mtime = current_latest_mtime
|
||
save_last_mtime(last_known_mtime)
|
||
log_activity(f"State updated. New last_known_mtime is {last_known_mtime}.")
|
||
return new_reports_count
|
||
|
||
def background_analysis_task():
|
||
"""在后台运行的守护线程,定时检查uploads文件夹中是否有文件更新并触发分析。"""
|
||
global last_known_mtime
|
||
log_activity(f"Background monitoring started. Checking '{UPLOADS_FOLDER}' for updates...")
|
||
|
||
while True:
|
||
try:
|
||
current_latest_mtime = get_latest_mtime_in_folder(UPLOADS_FOLDER)
|
||
|
||
if current_latest_mtime > last_known_mtime:
|
||
run_the_analysis()
|
||
|
||
except Exception as e:
|
||
log_activity(f"CRITICAL ERROR in background task: {e}")
|
||
|
||
time.sleep(CHECK_INTERVAL_SECONDS)
|
||
|
||
# --- 4. API 路由 ---
|
||
|
||
@app.route('/')
|
||
def index():
|
||
return app.send_static_file('index.html')
|
||
|
||
# 为报告文件夹提供一个专门的路由,以便前端可以访问
|
||
@app.route('/网页服务/分析报告/<path:filename>')
|
||
def serve_report_file(filename):
|
||
return send_from_directory(REPORTS_FOLDER, filename)
|
||
|
||
@app.route('/reports', methods=['GET'])
|
||
def list_reports():
|
||
"""提供一个API端点,用于列出所有报告(包含修改时间)。"""
|
||
reports = []
|
||
if not os.path.exists(REPORTS_FOLDER):
|
||
return jsonify([])
|
||
for filename in sorted(os.listdir(REPORTS_FOLDER), reverse=True):
|
||
if filename.endswith(".html"):
|
||
filepath = os.path.join(REPORTS_FOLDER, filename)
|
||
reports.append({
|
||
"name": filename,
|
||
"mtime": os.path.getmtime(filepath)
|
||
})
|
||
return jsonify(reports)
|
||
|
||
|
||
|
||
@app.route('/analyze', methods=['POST'])
|
||
def handle_analysis_request():
|
||
"""处理来自前端的文件上传和分析请求。"""
|
||
if 'file' not in request.files:
|
||
return jsonify({"error": "No file part"}), 400
|
||
|
||
file = request.files['file']
|
||
if file.filename == '':
|
||
return jsonify({"error": "No selected file"}), 400
|
||
|
||
if file and file.filename.endswith('.txt'):
|
||
try:
|
||
filepath = os.path.join(UPLOADS_FOLDER, file.filename)
|
||
file.save(filepath)
|
||
log_activity(f"Received new file from web: '{file.filename}'. Saved to uploads folder.")
|
||
|
||
# 在一个新线程中运行分析,避免阻塞网页请求
|
||
analysis_thread = threading.Thread(target=run_the_analysis)
|
||
analysis_thread.start()
|
||
|
||
return jsonify({"message": f"File '{file.filename}' uploaded successfully. Analysis started in background."}), 202
|
||
except Exception as e:
|
||
log_activity(f"Error processing uploaded file: {e}")
|
||
return jsonify({"error": "Server error during file processing"}), 500
|
||
|
||
return jsonify({"error": "Invalid file type, only .txt is accepted"}), 400
|
||
|
||
|
||
# --- 5. 启动应用 (核心修改区域) ---
|
||
if __name__ == '__main__':
|
||
for folder in [UPLOADS_FOLDER, SPLIT_LOGS_FOLDER, REPORTS_FOLDER]:
|
||
if not os.path.exists(folder):
|
||
os.makedirs(folder)
|
||
|
||
# -- 修改 --: 在启动时从文件加载“记忆”,而不是实时获取
|
||
last_known_mtime = load_last_mtime()
|
||
log_activity(f"Application starting... Loaded last known mtime from state file: {last_known_mtime}.")
|
||
|
||
daemon = threading.Thread(target=background_analysis_task, daemon=True, name='Monitor')
|
||
daemon.start()
|
||
|
||
log_activity("Web service started. Background task is now monitoring for file changes.")
|
||
log_activity(f"To begin, please place your chat log files (.txt) into the '{UPLOADS_FOLDER}' folder.")
|
||
|
||
app.run(host='0.0.0.0', port=5000, use_reloader=False)
|