2fef6a45 by 柴进

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

1 parent 34feac02
...@@ -1091,6 +1091,7 @@ class ImageGeneratorWindow(QMainWindow): ...@@ -1091,6 +1091,7 @@ class ImageGeneratorWindow(QMainWindow):
1091 self.db_config = None 1091 self.db_config = None
1092 self.last_user = "" 1092 self.last_user = ""
1093 self.saved_password_hash = "" 1093 self.saved_password_hash = ""
1094 self.authenticated_user = "" # 当前登录用户名
1094 1095
1095 self.load_config() 1096 self.load_config()
1096 self.set_window_icon() 1097 self.set_window_icon()
...@@ -1401,21 +1402,21 @@ class ImageGeneratorWindow(QMainWindow): ...@@ -1401,21 +1402,21 @@ class ImageGeneratorWindow(QMainWindow):
1401 1402
1402 self.image_count_label = QLabel("已选择 0 张") 1403 self.image_count_label = QLabel("已选择 0 张")
1403 upload_header.addWidget(self.image_count_label) 1404 upload_header.addWidget(self.image_count_label)
1405
1406 # Drag and drop hint - 紧凑显示在右侧
1407 hint_label = QLabel("💡 拖拽或粘贴图片到下方区域")
1408 hint_label.setStyleSheet("QLabel { color: #666666; font-size: 11px; margin-left: 10px; }")
1409 upload_header.addWidget(hint_label)
1410
1404 upload_header.addStretch() 1411 upload_header.addStretch()
1405 ref_layout.addLayout(upload_header) 1412 ref_layout.addLayout(upload_header)
1406 1413
1407 # Drag and drop hint
1408 hint_label = QLabel("💡 提示:可以直接拖拽图片到下方区域,或使用 Ctrl+V 粘贴截图")
1409 hint_label.setStyleSheet("QLabel { color: #666666; font-size: 11px; margin: 2px 0; }")
1410 hint_label.setWordWrap(True)
1411 ref_layout.addWidget(hint_label)
1412
1413 # Image preview scroll area with drag-and-drop support 1414 # Image preview scroll area with drag-and-drop support
1414 self.img_scroll = DragDropScrollArea(self) 1415 self.img_scroll = DragDropScrollArea(self)
1415 self.img_scroll.setWidgetResizable(True) 1416 self.img_scroll.setWidgetResizable(True)
1416 self.img_scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOn) 1417 self.img_scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
1417 self.img_scroll.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) 1418 self.img_scroll.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
1418 self.img_scroll.setFixedHeight(180) 1419 self.img_scroll.setFixedHeight(220)
1419 1420
1420 self.img_container = QWidget() 1421 self.img_container = QWidget()
1421 self.img_layout = QHBoxLayout() 1422 self.img_layout = QHBoxLayout()
...@@ -2098,6 +2099,7 @@ class ImageGeneratorWindow(QMainWindow): ...@@ -2098,6 +2099,7 @@ class ImageGeneratorWindow(QMainWindow):
2098 2099
2099 try: 2100 try:
2100 # Submit task to queue 2101 # Submit task to queue
2102 import socket
2101 task_id = self.task_manager.submit_task( 2103 task_id = self.task_manager.submit_task(
2102 task_type=TaskType.IMAGE_GENERATION, 2104 task_type=TaskType.IMAGE_GENERATION,
2103 prompt=prompt, 2105 prompt=prompt,
...@@ -2105,7 +2107,9 @@ class ImageGeneratorWindow(QMainWindow): ...@@ -2105,7 +2107,9 @@ class ImageGeneratorWindow(QMainWindow):
2105 reference_images=self.uploaded_images.copy(), 2107 reference_images=self.uploaded_images.copy(),
2106 aspect_ratio=self.aspect_ratio.currentText(), 2108 aspect_ratio=self.aspect_ratio.currentText(),
2107 image_size=self.image_size.currentText(), 2109 image_size=self.image_size.currentText(),
2108 model="gemini-3-pro-image-preview" 2110 model="gemini-3-pro-image-preview",
2111 user_name=self.authenticated_user,
2112 device_name=socket.gethostname()
2109 ) 2113 )
2110 2114
2111 # Connect to task completion signal 2115 # Connect to task completion signal
...@@ -3520,6 +3524,7 @@ class StyleDesignerTab(QWidget): ...@@ -3520,6 +3524,7 @@ class StyleDesignerTab(QWidget):
3520 3524
3521 try: 3525 try:
3522 # Submit task to queue 3526 # Submit task to queue
3527 import socket
3523 task_id = self.parent_window.task_manager.submit_task( 3528 task_id = self.parent_window.task_manager.submit_task(
3524 task_type=TaskType.STYLE_DESIGN, 3529 task_type=TaskType.STYLE_DESIGN,
3525 prompt=prompt, 3530 prompt=prompt,
...@@ -3527,7 +3532,9 @@ class StyleDesignerTab(QWidget): ...@@ -3527,7 +3532,9 @@ class StyleDesignerTab(QWidget):
3527 reference_images=[], 3532 reference_images=[],
3528 aspect_ratio=aspect_ratio, 3533 aspect_ratio=aspect_ratio,
3529 image_size=image_size, 3534 image_size=image_size,
3530 model=model 3535 model=model,
3536 user_name=self.parent_window.authenticated_user,
3537 device_name=socket.gethostname()
3531 ) 3538 )
3532 3539
3533 # Connect to task completion signal 3540 # Connect to task completion signal
...@@ -3861,6 +3868,7 @@ def main(): ...@@ -3861,6 +3868,7 @@ def main():
3861 3868
3862 # Show main window 3869 # Show main window
3863 main_window = ImageGeneratorWindow() 3870 main_window = ImageGeneratorWindow()
3871 main_window.authenticated_user = authenticated_user # 设置登录用户名
3864 main_window.show() 3872 main_window.show()
3865 3873
3866 sys.exit(app.exec()) 3874 sys.exit(app.exec())
......
...@@ -53,8 +53,12 @@ class Task: ...@@ -53,8 +53,12 @@ class Task:
53 image_size: str 53 image_size: str
54 model: str 54 model: str
55 55
56 # 用户信息 (用于日志记录)
57 user_name: str = ""
58 device_name: str = ""
59
56 # 时间戳 60 # 时间戳
57 created_at: datetime 61 created_at: datetime = field(default_factory=datetime.now)
58 started_at: Optional[datetime] = None 62 started_at: Optional[datetime] = None
59 completed_at: Optional[datetime] = None 63 completed_at: Optional[datetime] = None
60 64
...@@ -102,9 +106,28 @@ class TaskQueueManager(QObject): ...@@ -102,9 +106,28 @@ class TaskQueueManager(QObject):
102 self._max_queue_size = 10 106 self._max_queue_size = 10
103 self._max_history_size = 10 # 只保留最近10条完成任务 107 self._max_history_size = 10 # 只保留最近10条完成任务
104 108
109 # 加载数据库配置用于日志记录
110 self._db_config = None
111 self._load_db_config()
112
105 self._initialized = True 113 self._initialized = True
106 self.logger.info("TaskQueueManager 初始化完成") 114 self.logger.info("TaskQueueManager 初始化完成")
107 115
116 def _load_db_config(self):
117 """加载数据库配置"""
118 try:
119 import json
120 from pathlib import Path
121 config_file = Path("config.json")
122 if config_file.exists():
123 with open(config_file, 'r', encoding='utf-8') as f:
124 config = json.load(f)
125 self._db_config = config.get('db_config')
126 if self._db_config:
127 self.logger.info("数据库配置已加载")
128 except Exception as e:
129 self.logger.warning(f"加载数据库配置失败: {e}")
130
108 def submit_task( 131 def submit_task(
109 self, 132 self,
110 task_type: TaskType, 133 task_type: TaskType,
...@@ -113,7 +136,9 @@ class TaskQueueManager(QObject): ...@@ -113,7 +136,9 @@ class TaskQueueManager(QObject):
113 reference_images: List[str], 136 reference_images: List[str],
114 aspect_ratio: str, 137 aspect_ratio: str,
115 image_size: str, 138 image_size: str,
116 model: str 139 model: str,
140 user_name: str = "",
141 device_name: str = ""
117 ) -> str: 142 ) -> str:
118 """ 143 """
119 提交新任务到队列 144 提交新任务到队列
...@@ -126,6 +151,8 @@ class TaskQueueManager(QObject): ...@@ -126,6 +151,8 @@ class TaskQueueManager(QObject):
126 aspect_ratio: 宽高比 151 aspect_ratio: 宽高比
127 image_size: 图片尺寸 152 image_size: 图片尺寸
128 model: 模型名称 153 model: 模型名称
154 user_name: 用户名 (用于日志记录)
155 device_name: 设备名称 (用于日志记录)
129 156
130 Returns: 157 Returns:
131 task_id: 任务唯一标识 158 task_id: 任务唯一标识
...@@ -148,6 +175,8 @@ class TaskQueueManager(QObject): ...@@ -148,6 +175,8 @@ class TaskQueueManager(QObject):
148 aspect_ratio=aspect_ratio, 175 aspect_ratio=aspect_ratio,
149 image_size=image_size, 176 image_size=image_size,
150 model=model, 177 model=model,
178 user_name=user_name,
179 device_name=device_name,
151 created_at=datetime.now() 180 created_at=datetime.now()
152 ) 181 )
153 182
...@@ -225,6 +254,9 @@ class TaskQueueManager(QObject): ...@@ -225,6 +254,9 @@ class TaskQueueManager(QObject):
225 elapsed = (task.completed_at - task.started_at).total_seconds() 254 elapsed = (task.completed_at - task.started_at).total_seconds()
226 self.logger.info(f"任务完成: {task_id[:8]} - 耗时 {elapsed:.1f}s") 255 self.logger.info(f"任务完成: {task_id[:8]} - 耗时 {elapsed:.1f}s")
227 256
257 # 记录使用日志
258 self._log_usage(task_id, 'success', 'memory', None)
259
228 self.task_completed.emit(task_id, image_bytes, prompt, reference_images, 260 self.task_completed.emit(task_id, image_bytes, prompt, reference_images,
229 aspect_ratio, image_size, model) 261 aspect_ratio, image_size, model)
230 262
...@@ -247,6 +279,9 @@ class TaskQueueManager(QObject): ...@@ -247,6 +279,9 @@ class TaskQueueManager(QObject):
247 279
248 self.logger.error(f"任务失败: {task_id[:8]} - {error}") 280 self.logger.error(f"任务失败: {task_id[:8]} - {error}")
249 281
282 # 记录使用日志
283 self._log_usage(task_id, 'failure', None, error)
284
250 self.task_failed.emit(task_id, error) 285 self.task_failed.emit(task_id, error)
251 286
252 # 清理旧任务历史 287 # 清理旧任务历史
...@@ -305,11 +340,86 @@ class TaskQueueManager(QObject): ...@@ -305,11 +340,86 @@ class TaskQueueManager(QObject):
305 return sum(1 for t in self._tasks.values() if t.status == TaskStatus.RUNNING) 340 return sum(1 for t in self._tasks.values() if t.status == TaskStatus.RUNNING)
306 341
307 def cancel_task(self, task_id: str): 342 def cancel_task(self, task_id: str):
308 """取消任务 (仅等待中任务)""" 343 """
344 取消任务 (仅等待中任务)
345 将任务状态设为 CANCELLED 并从队列中移除
346 """
309 task = self._tasks.get(task_id) 347 task = self._tasks.get(task_id)
310 if task and task.status == TaskStatus.PENDING: 348 if task and task.status == TaskStatus.PENDING:
311 task.status = TaskStatus.CANCELLED 349 task.status = TaskStatus.CANCELLED
350 task.completed_at = datetime.now()
351
352 # 从队列中移除任务 (需要重建队列以移除特定ID)
353 temp_queue = Queue()
354 while not self._queue.empty():
355 tid = self._queue.get()
356 if tid != task_id:
357 temp_queue.put(tid)
358
359 # 替换原队列
360 self._queue = temp_queue
361
312 self.logger.info(f"任务已取消: {task_id[:8]}") 362 self.logger.info(f"任务已取消: {task_id[:8]}")
363 self.task_failed.emit(task_id, "用户取消") # 发送取消信号以更新 UI
364
365 def _log_usage(self, task_id: str, status: str, result_path: Optional[str], error_message: Optional[str]):
366 """
367 记录用户使用日志到数据库
368
369 Args:
370 task_id: 任务ID
371 status: 'success' 或 'failure'
372 result_path: 成功时的图片路径,失败时为 None
373 error_message: 失败时的错误信息,成功时为 None
374 """
375 if not self._db_config:
376 self.logger.debug("数据库配置未加载,跳过日志记录")
377 return
378
379 task = self._tasks.get(task_id)
380 if not task:
381 self.logger.warning(f"任务 {task_id[:8]} 不存在,无法记录日志")
382 return
383
384 try:
385 import pymysql
386
387 # 处理未登录用户
388 user_name = task.user_name if task.user_name else "未知用户"
389 device_name = task.device_name if task.device_name else "未知设备"
390
391 # 连接数据库
392 connection = pymysql.connect(
393 host=self._db_config['host'],
394 port=self._db_config['port'],
395 user=self._db_config['user'],
396 password=self._db_config['password'],
397 database=self._db_config['database']
398 )
399
400 with connection.cursor() as cursor:
401 sql = """
402 INSERT INTO `nano_banana_user_use_log`
403 (`user_name`, `device_name`, `prompt`, `result_path`, `status`, `error_message`)
404 VALUES (%s, %s, %s, %s, %s, %s)
405 """
406 cursor.execute(sql, (
407 user_name,
408 device_name,
409 task.prompt,
410 result_path,
411 status,
412 error_message
413 ))
414
415 connection.commit()
416 connection.close()
417
418 self.logger.info(f"使用日志已记录: {task_id[:8]} - {status}")
419
420 except Exception as e:
421 # 日志记录失败不应影响主流程
422 self.logger.error(f"记录使用日志失败: {e}", exc_info=False)
313 423
314 424
315 class TaskQueueWidget(QWidget): 425 class TaskQueueWidget(QWidget):
...@@ -338,6 +448,7 @@ class TaskQueueWidget(QWidget): ...@@ -338,6 +448,7 @@ class TaskQueueWidget(QWidget):
338 title = QLabel("任务队列") 448 title = QLabel("任务队列")
339 title.setStyleSheet("QLabel { font-weight: bold; font-size: 10px; color: #666; }") 449 title.setStyleSheet("QLabel { font-weight: bold; font-size: 10px; color: #666; }")
340 title.setAlignment(Qt.AlignCenter) 450 title.setAlignment(Qt.AlignCenter)
451 title.setToolTip("鼠标悬停查看详情\n右键等待中的任务可取消")
341 layout.addWidget(title) 452 layout.addWidget(title)
342 453
343 # 分隔线 454 # 分隔线
...@@ -367,6 +478,11 @@ class TaskQueueWidget(QWidget): ...@@ -367,6 +478,11 @@ class TaskQueueWidget(QWidget):
367 } 478 }
368 """) 479 """)
369 self.task_list.itemClicked.connect(self._on_task_item_clicked) 480 self.task_list.itemClicked.connect(self._on_task_item_clicked)
481
482 # 启用右键菜单
483 self.task_list.setContextMenuPolicy(Qt.CustomContextMenu)
484 self.task_list.customContextMenuRequested.connect(self._show_context_menu)
485
370 layout.addWidget(self.task_list) 486 layout.addWidget(self.task_list)
371 487
372 layout.addStretch() 488 layout.addStretch()
...@@ -426,6 +542,15 @@ class TaskQueueWidget(QWidget): ...@@ -426,6 +542,15 @@ class TaskQueueWidget(QWidget):
426 from PySide6.QtGui import QBrush, QColor 542 from PySide6.QtGui import QBrush, QColor
427 item.setForeground(QBrush(QColor(color))) 543 item.setForeground(QBrush(QColor(color)))
428 544
545 # 设置 Tooltip - 显示完整的 Prompt 和任务信息
546 tooltip_text = f"Prompt: {task.prompt}\n"
547 tooltip_text += f"创建时间: {task.created_at.strftime('%Y-%m-%d %H:%M:%S')}\n"
548 tooltip_text += f"宽高比: {task.aspect_ratio}\n"
549 tooltip_text += f"尺寸: {task.image_size}"
550 if task.error_message:
551 tooltip_text += f"\n错误: {task.error_message}"
552 item.setToolTip(tooltip_text)
553
429 # 添加到列表 554 # 添加到列表
430 self.task_list.addItem(item) 555 self.task_list.addItem(item)
431 556
...@@ -434,6 +559,33 @@ class TaskQueueWidget(QWidget): ...@@ -434,6 +559,33 @@ class TaskQueueWidget(QWidget):
434 for i in range(self.task_list.count() - 10): 559 for i in range(self.task_list.count() - 10):
435 self.task_list.takeItem(0) 560 self.task_list.takeItem(0)
436 561
562 def _show_context_menu(self, position):
563 """显示右键菜单 - 仅为等待中的任务提供取消选项"""
564 from PySide6.QtWidgets import QMenu
565
566 # 获取点击的任务项
567 item = self.task_list.itemAt(position)
568 if not item:
569 return
570
571 task_id = item.data(Qt.UserRole)
572 if not task_id:
573 return
574
575 task = self.manager.get_task(task_id)
576 if not task:
577 return
578
579 # 仅为 PENDING 状态的任务显示取消选项
580 if task.status == TaskStatus.PENDING:
581 menu = QMenu()
582 cancel_action = menu.addAction("取消任务")
583
584 action = menu.exec_(self.task_list.mapToGlobal(position))
585
586 if action == cancel_action:
587 self.manager.cancel_task(task_id)
588 self._update_summary()
437 589
438 def _on_task_added(self, task: Task): 590 def _on_task_added(self, task: Task):
439 """任务添加回调""" 591 """任务添加回调"""
......