2fef6a45 by 柴进

增加使用人的信息记录与日志留存

1 parent 34feac02
......@@ -1091,6 +1091,7 @@ class ImageGeneratorWindow(QMainWindow):
self.db_config = None
self.last_user = ""
self.saved_password_hash = ""
self.authenticated_user = "" # 当前登录用户名
self.load_config()
self.set_window_icon()
......@@ -1401,21 +1402,21 @@ class ImageGeneratorWindow(QMainWindow):
self.image_count_label = QLabel("已选择 0 张")
upload_header.addWidget(self.image_count_label)
# Drag and drop hint - 紧凑显示在右侧
hint_label = QLabel("💡 拖拽或粘贴图片到下方区域")
hint_label.setStyleSheet("QLabel { color: #666666; font-size: 11px; margin-left: 10px; }")
upload_header.addWidget(hint_label)
upload_header.addStretch()
ref_layout.addLayout(upload_header)
# Drag and drop hint
hint_label = QLabel("💡 提示:可以直接拖拽图片到下方区域,或使用 Ctrl+V 粘贴截图")
hint_label.setStyleSheet("QLabel { color: #666666; font-size: 11px; margin: 2px 0; }")
hint_label.setWordWrap(True)
ref_layout.addWidget(hint_label)
# Image preview scroll area with drag-and-drop support
self.img_scroll = DragDropScrollArea(self)
self.img_scroll.setWidgetResizable(True)
self.img_scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
self.img_scroll.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.img_scroll.setFixedHeight(180)
self.img_scroll.setFixedHeight(220)
self.img_container = QWidget()
self.img_layout = QHBoxLayout()
......@@ -2098,6 +2099,7 @@ class ImageGeneratorWindow(QMainWindow):
try:
# Submit task to queue
import socket
task_id = self.task_manager.submit_task(
task_type=TaskType.IMAGE_GENERATION,
prompt=prompt,
......@@ -2105,7 +2107,9 @@ class ImageGeneratorWindow(QMainWindow):
reference_images=self.uploaded_images.copy(),
aspect_ratio=self.aspect_ratio.currentText(),
image_size=self.image_size.currentText(),
model="gemini-3-pro-image-preview"
model="gemini-3-pro-image-preview",
user_name=self.authenticated_user,
device_name=socket.gethostname()
)
# Connect to task completion signal
......@@ -3520,6 +3524,7 @@ class StyleDesignerTab(QWidget):
try:
# Submit task to queue
import socket
task_id = self.parent_window.task_manager.submit_task(
task_type=TaskType.STYLE_DESIGN,
prompt=prompt,
......@@ -3527,7 +3532,9 @@ class StyleDesignerTab(QWidget):
reference_images=[],
aspect_ratio=aspect_ratio,
image_size=image_size,
model=model
model=model,
user_name=self.parent_window.authenticated_user,
device_name=socket.gethostname()
)
# Connect to task completion signal
......@@ -3861,6 +3868,7 @@ def main():
# Show main window
main_window = ImageGeneratorWindow()
main_window.authenticated_user = authenticated_user # 设置登录用户名
main_window.show()
sys.exit(app.exec())
......
......@@ -53,8 +53,12 @@ class Task:
image_size: str
model: str
# 用户信息 (用于日志记录)
user_name: str = ""
device_name: str = ""
# 时间戳
created_at: datetime
created_at: datetime = field(default_factory=datetime.now)
started_at: Optional[datetime] = None
completed_at: Optional[datetime] = None
......@@ -102,9 +106,28 @@ class TaskQueueManager(QObject):
self._max_queue_size = 10
self._max_history_size = 10 # 只保留最近10条完成任务
# 加载数据库配置用于日志记录
self._db_config = None
self._load_db_config()
self._initialized = True
self.logger.info("TaskQueueManager 初始化完成")
def _load_db_config(self):
"""加载数据库配置"""
try:
import json
from pathlib import Path
config_file = Path("config.json")
if config_file.exists():
with open(config_file, 'r', encoding='utf-8') as f:
config = json.load(f)
self._db_config = config.get('db_config')
if self._db_config:
self.logger.info("数据库配置已加载")
except Exception as e:
self.logger.warning(f"加载数据库配置失败: {e}")
def submit_task(
self,
task_type: TaskType,
......@@ -113,7 +136,9 @@ class TaskQueueManager(QObject):
reference_images: List[str],
aspect_ratio: str,
image_size: str,
model: str
model: str,
user_name: str = "",
device_name: str = ""
) -> str:
"""
提交新任务到队列
......@@ -126,6 +151,8 @@ class TaskQueueManager(QObject):
aspect_ratio: 宽高比
image_size: 图片尺寸
model: 模型名称
user_name: 用户名 (用于日志记录)
device_name: 设备名称 (用于日志记录)
Returns:
task_id: 任务唯一标识
......@@ -148,6 +175,8 @@ class TaskQueueManager(QObject):
aspect_ratio=aspect_ratio,
image_size=image_size,
model=model,
user_name=user_name,
device_name=device_name,
created_at=datetime.now()
)
......@@ -225,6 +254,9 @@ class TaskQueueManager(QObject):
elapsed = (task.completed_at - task.started_at).total_seconds()
self.logger.info(f"任务完成: {task_id[:8]} - 耗时 {elapsed:.1f}s")
# 记录使用日志
self._log_usage(task_id, 'success', 'memory', None)
self.task_completed.emit(task_id, image_bytes, prompt, reference_images,
aspect_ratio, image_size, model)
......@@ -247,6 +279,9 @@ class TaskQueueManager(QObject):
self.logger.error(f"任务失败: {task_id[:8]} - {error}")
# 记录使用日志
self._log_usage(task_id, 'failure', None, error)
self.task_failed.emit(task_id, error)
# 清理旧任务历史
......@@ -305,11 +340,86 @@ class TaskQueueManager(QObject):
return sum(1 for t in self._tasks.values() if t.status == TaskStatus.RUNNING)
def cancel_task(self, task_id: str):
"""取消任务 (仅等待中任务)"""
"""
取消任务 (仅等待中任务)
将任务状态设为 CANCELLED 并从队列中移除
"""
task = self._tasks.get(task_id)
if task and task.status == TaskStatus.PENDING:
task.status = TaskStatus.CANCELLED
task.completed_at = datetime.now()
# 从队列中移除任务 (需要重建队列以移除特定ID)
temp_queue = Queue()
while not self._queue.empty():
tid = self._queue.get()
if tid != task_id:
temp_queue.put(tid)
# 替换原队列
self._queue = temp_queue
self.logger.info(f"任务已取消: {task_id[:8]}")
self.task_failed.emit(task_id, "用户取消") # 发送取消信号以更新 UI
def _log_usage(self, task_id: str, status: str, result_path: Optional[str], error_message: Optional[str]):
"""
记录用户使用日志到数据库
Args:
task_id: 任务ID
status: 'success' 或 'failure'
result_path: 成功时的图片路径,失败时为 None
error_message: 失败时的错误信息,成功时为 None
"""
if not self._db_config:
self.logger.debug("数据库配置未加载,跳过日志记录")
return
task = self._tasks.get(task_id)
if not task:
self.logger.warning(f"任务 {task_id[:8]} 不存在,无法记录日志")
return
try:
import pymysql
# 处理未登录用户
user_name = task.user_name if task.user_name else "未知用户"
device_name = task.device_name if task.device_name else "未知设备"
# 连接数据库
connection = pymysql.connect(
host=self._db_config['host'],
port=self._db_config['port'],
user=self._db_config['user'],
password=self._db_config['password'],
database=self._db_config['database']
)
with connection.cursor() as cursor:
sql = """
INSERT INTO `nano_banana_user_use_log`
(`user_name`, `device_name`, `prompt`, `result_path`, `status`, `error_message`)
VALUES (%s, %s, %s, %s, %s, %s)
"""
cursor.execute(sql, (
user_name,
device_name,
task.prompt,
result_path,
status,
error_message
))
connection.commit()
connection.close()
self.logger.info(f"使用日志已记录: {task_id[:8]} - {status}")
except Exception as e:
# 日志记录失败不应影响主流程
self.logger.error(f"记录使用日志失败: {e}", exc_info=False)
class TaskQueueWidget(QWidget):
......@@ -338,6 +448,7 @@ class TaskQueueWidget(QWidget):
title = QLabel("任务队列")
title.setStyleSheet("QLabel { font-weight: bold; font-size: 10px; color: #666; }")
title.setAlignment(Qt.AlignCenter)
title.setToolTip("鼠标悬停查看详情\n右键等待中的任务可取消")
layout.addWidget(title)
# 分隔线
......@@ -367,6 +478,11 @@ class TaskQueueWidget(QWidget):
}
""")
self.task_list.itemClicked.connect(self._on_task_item_clicked)
# 启用右键菜单
self.task_list.setContextMenuPolicy(Qt.CustomContextMenu)
self.task_list.customContextMenuRequested.connect(self._show_context_menu)
layout.addWidget(self.task_list)
layout.addStretch()
......@@ -426,6 +542,15 @@ class TaskQueueWidget(QWidget):
from PySide6.QtGui import QBrush, QColor
item.setForeground(QBrush(QColor(color)))
# 设置 Tooltip - 显示完整的 Prompt 和任务信息
tooltip_text = f"Prompt: {task.prompt}\n"
tooltip_text += f"创建时间: {task.created_at.strftime('%Y-%m-%d %H:%M:%S')}\n"
tooltip_text += f"宽高比: {task.aspect_ratio}\n"
tooltip_text += f"尺寸: {task.image_size}"
if task.error_message:
tooltip_text += f"\n错误: {task.error_message}"
item.setToolTip(tooltip_text)
# 添加到列表
self.task_list.addItem(item)
......@@ -434,7 +559,34 @@ class TaskQueueWidget(QWidget):
for i in range(self.task_list.count() - 10):
self.task_list.takeItem(0)
def _show_context_menu(self, position):
"""显示右键菜单 - 仅为等待中的任务提供取消选项"""
from PySide6.QtWidgets import QMenu
# 获取点击的任务项
item = self.task_list.itemAt(position)
if not item:
return
task_id = item.data(Qt.UserRole)
if not task_id:
return
task = self.manager.get_task(task_id)
if not task:
return
# 仅为 PENDING 状态的任务显示取消选项
if task.status == TaskStatus.PENDING:
menu = QMenu()
cancel_action = menu.addAction("取消任务")
action = menu.exec_(self.task_list.mapToGlobal(position))
if action == cancel_action:
self.manager.cancel_task(task_id)
self._update_summary()
def _on_task_added(self, task: Task):
"""任务添加回调"""
self._update_summary()
......