task_queue.py
28.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
"""
任务队列系统
提供异步图像生成任务的队列管理和 UI 组件
"""
from dataclasses import dataclass, field
from datetime import datetime
from enum import Enum
from typing import Optional, List, Dict
from queue import Queue
from threading import Lock
import uuid
import logging
import io
from PySide6.QtCore import QObject, Signal, QTimer, Qt
from PySide6.QtWidgets import (
QWidget, QVBoxLayout, QHBoxLayout, QLabel,
QPushButton, QListWidget, QListWidgetItem, QDialog, QScrollArea, QFrame
)
from PySide6.QtGui import QPixmap, QMouseEvent
from PIL import Image
class TaskType(Enum):
"""任务类型"""
IMAGE_GENERATION = "image_gen"
STYLE_DESIGN = "style_design"
class TaskStatus(Enum):
"""任务状态"""
PENDING = "pending"
RUNNING = "running"
COMPLETED = "completed"
FAILED = "failed"
CANCELLED = "cancelled"
@dataclass
class Task:
"""任务数据模型"""
# 标识
id: str
type: TaskType
status: TaskStatus
# 输入参数
prompt: str
api_key: str
reference_images: List[str]
aspect_ratio: str
image_size: str
model: str
# 用户信息 (用于日志记录)
user_name: str = ""
device_name: str = ""
# 时间戳
created_at: datetime = field(default_factory=datetime.now)
started_at: Optional[datetime] = None
completed_at: Optional[datetime] = None
# 结果
result_bytes: Optional[bytes] = None
error_message: Optional[str] = None
# UI 相关
thumbnail: Optional[bytes] = None
progress: float = 0.0
class TaskQueueManager(QObject):
"""
单例任务队列管理器
管理所有图像生成任务的生命周期
"""
# Signals
task_added = Signal(Task)
task_started = Signal(str) # task_id
task_completed = Signal(str, bytes, str, list, str, str, str) # task_id, image_bytes, prompt, ref_images, aspect_ratio, image_size, model
task_failed = Signal(str, str) # task_id, error_message
task_progress = Signal(str, float, str) # task_id, progress, status_text
_instance = None
_lock = Lock()
def __new__(cls):
"""单例模式"""
if cls._instance is None:
with cls._lock:
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
def __init__(self):
if hasattr(self, '_initialized'):
return
super().__init__()
self.logger = logging.getLogger(__name__)
self._tasks: Dict[str, Task] = {}
self._queue = Queue()
self._current_worker = None
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,
prompt: str,
api_key: str,
reference_images: List[str],
aspect_ratio: str,
image_size: str,
model: str,
user_name: str = "",
device_name: str = ""
) -> str:
"""
提交新任务到队列
Args:
task_type: 任务类型
prompt: 图片描述
api_key: API 密钥
reference_images: 参考图片路径列表
aspect_ratio: 宽高比
image_size: 图片尺寸
model: 模型名称
user_name: 用户名 (用于日志记录)
device_name: 设备名称 (用于日志记录)
Returns:
task_id: 任务唯一标识
Raises:
RuntimeError: 队列已满
"""
# 检查队列容量
if self._queue.qsize() >= self._max_queue_size:
raise RuntimeError(f"任务队列已满 (最大 {self._max_queue_size} 个)")
# 创建任务
task = Task(
id=str(uuid.uuid4()),
type=task_type,
status=TaskStatus.PENDING,
prompt=prompt,
api_key=api_key,
reference_images=reference_images.copy() if reference_images else [],
aspect_ratio=aspect_ratio,
image_size=image_size,
model=model,
user_name=user_name,
device_name=device_name,
created_at=datetime.now()
)
self._tasks[task.id] = task
self._queue.put(task.id)
self.logger.info(f"任务已提交: {task.id[:8]} - {prompt[:30]}")
self.task_added.emit(task)
# 如果没有正在运行的任务,启动处理
if self._current_worker is None or not self._current_worker.isRunning():
self._process_next()
return task.id
def _process_next(self):
"""处理队列中的下一个任务"""
if self._queue.empty():
self.logger.debug("队列为空,无任务处理")
return
task_id = self._queue.get()
task = self._tasks[task_id]
task.status = TaskStatus.RUNNING
task.started_at = datetime.now()
self.logger.info(f"开始处理任务: {task_id[:8]}")
# 导入 ImageGenerationWorker
from image_generator import ImageGenerationWorker
# 创建 worker
self._current_worker = ImageGenerationWorker(
task.api_key,
task.prompt,
task.reference_images,
task.aspect_ratio,
task.image_size,
task.model
)
# 绑定信号
self._current_worker.finished.connect(
lambda img_bytes, prompt, ref_imgs, ar, size, model:
self._on_task_completed(task_id, img_bytes, prompt, ref_imgs, ar, size, model)
)
self._current_worker.error.connect(
lambda error: self._on_task_failed(task_id, error)
)
self._current_worker.progress.connect(
lambda status: self.task_progress.emit(task_id, 0.5, status)
)
self.task_started.emit(task_id)
self._current_worker.start()
def _on_task_completed(self, task_id: str, image_bytes: bytes, prompt: str,
reference_images: list, aspect_ratio: str, image_size: str, model: str):
"""任务完成回调"""
task = self._tasks.get(task_id)
if not task:
self.logger.error(f"任务 {task_id[:8]} 不存在")
return
task.status = TaskStatus.COMPLETED
task.completed_at = datetime.now()
task.result_bytes = image_bytes
# 生成缩略图
try:
task.thumbnail = self._create_thumbnail(image_bytes)
except Exception as e:
self.logger.warning(f"生成缩略图失败: {e}")
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)
# 清理旧任务历史,只保留最近的完成任务
self._cleanup_old_tasks()
# 处理下一个任务
self._process_next()
def _on_task_failed(self, task_id: str, error: str):
"""任务失败回调"""
task = self._tasks.get(task_id)
if not task:
self.logger.error(f"任务 {task_id[:8]} 不存在")
return
task.status = TaskStatus.FAILED
task.completed_at = datetime.now()
task.error_message = error
self.logger.error(f"任务失败: {task_id[:8]} - {error}")
# 记录使用日志
self._log_usage(task_id, 'failure', None, error)
self.task_failed.emit(task_id, error)
# 清理旧任务历史
self._cleanup_old_tasks()
# 处理下一个任务
self._process_next()
def _cleanup_old_tasks(self):
"""清理旧任务,只保留最近的完成/失败任务"""
# 获取所有已完成和失败的任务,按完成时间排序
finished_tasks = [
t for t in self._tasks.values()
if t.status in [TaskStatus.COMPLETED, TaskStatus.FAILED] and t.completed_at
]
finished_tasks.sort(key=lambda t: t.completed_at, reverse=True)
# 只保留最近的 N 条
if len(finished_tasks) > self._max_history_size:
tasks_to_remove = finished_tasks[self._max_history_size:]
for task in tasks_to_remove:
del self._tasks[task.id]
self.logger.debug(f"清理旧任务: {task.id[:8]}")
def _create_thumbnail(self, image_bytes: bytes) -> bytes:
"""
创建缩略图 (50x50)
Args:
image_bytes: 原始图片字节
Returns:
缩略图字节
"""
img = Image.open(io.BytesIO(image_bytes))
img.thumbnail((50, 50))
thumb_io = io.BytesIO()
img.save(thumb_io, format='PNG')
return thumb_io.getvalue()
def get_task(self, task_id: str) -> Optional[Task]:
"""获取任务详情"""
return self._tasks.get(task_id)
def get_all_tasks(self) -> List[Task]:
"""获取所有任务"""
return list(self._tasks.values())
def get_pending_count(self) -> int:
"""获取等待中任务数"""
return sum(1 for t in self._tasks.values() if t.status == TaskStatus.PENDING)
def get_running_count(self) -> int:
"""获取运行中任务数"""
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):
"""
右侧极窄任务列表
按设计文档显示任务状态文字列表
"""
def __init__(self, manager: TaskQueueManager, parent=None):
super().__init__(parent)
self.logger = logging.getLogger(__name__)
self.manager = manager
self.parent_window = parent # 用于数据回填
self._setup_ui()
self._connect_signals()
self._update_summary()
def _setup_ui(self):
"""构建右侧任务列表 UI"""
layout = QVBoxLayout()
layout.setContentsMargins(8, 8, 8, 8)
layout.setSpacing(2)
# 标题
title = QLabel("任务队列")
title.setStyleSheet("QLabel { font-weight: bold; font-size: 10px; color: #666; }")
title.setAlignment(Qt.AlignCenter)
title.setToolTip("鼠标悬停查看详情\n右键等待中的任务可取消")
layout.addWidget(title)
# 分隔线
line = QLabel()
line.setFrameStyle(QFrame.HLine | QFrame.Sunken)
layout.addWidget(line)
# 任务状态列表 - 可点击的列表项
self.task_list = QListWidget()
self.task_list.setStyleSheet("""
QListWidget {
border: none;
font-size: 11px;
padding: 2px;
}
QListWidget::item {
padding: 4px 2px;
border-bottom: 1px solid #eee;
min-height: 20px;
}
QListWidget::item:hover {
background-color: #e3f2fd;
cursor: pointer;
}
QListWidget::item:selected {
background-color: #bbdefb;
}
""")
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()
self.setLayout(layout)
# 设置极窄宽度
self.setMaximumWidth(120)
self.setMinimumWidth(80)
# 样式
self.setStyleSheet("""
TaskQueueWidget {
background-color: #f5f5f5;
border-left: 1px solid #ddd;
}
""")
def _connect_signals(self):
"""绑定信号"""
self.manager.task_added.connect(self._on_task_added)
self.manager.task_started.connect(self._on_task_started)
self.manager.task_completed.connect(self._on_task_completed)
self.manager.task_failed.connect(self._on_task_failed)
self.manager.task_progress.connect(self._on_task_progress)
def _update_summary(self):
"""更新任务列表 - 显示可点击的状态项"""
self.task_list.clear()
tasks = self.manager.get_all_tasks()
# 按状态分类并转换为文字
for task in tasks:
# 状态文字和颜色
if task.status == TaskStatus.RUNNING:
status_text = "执行中"
color = "#FF9500" # 橙色
elif task.status == TaskStatus.PENDING:
status_text = "等待中"
color = "#007AFF" # 蓝色
elif task.status == TaskStatus.COMPLETED:
status_text = "已完成"
color = "#34C759" # 绿色
elif task.status == TaskStatus.FAILED:
status_text = "失败"
color = "#FF3B30" # 红色
else:
status_text = "未知"
color = "#666666" # 灰色
# 创建列表项
item = QListWidgetItem(status_text)
item.setData(Qt.UserRole, task.id) # 存储任务ID
# 设置颜色
if hasattr(item, 'setForeground'):
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)
# 最多显示最近10个任务
if self.task_list.count() > 10:
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()
def _on_task_item_clicked(self, item: QListWidgetItem):
"""点击任务项 - 回填数据到主窗口"""
task_id = item.data(Qt.UserRole)
if not task_id:
return
task = self.manager.get_task(task_id)
if not task or not self.parent_window:
return
# 切换到对应的标签页
if task.type == TaskType.STYLE_DESIGN:
self.parent_window.tab_widget.setCurrentIndex(1) # 款式设计标签
style_tab = self.parent_window.tab_widget.currentWidget()
else:
self.parent_window.tab_widget.setCurrentIndex(0) # 图片生成标签
gen_tab = self.parent_window.tab_widget.currentWidget()
# 如果是已完成任务,直接在主窗口显示结果
if task.status == TaskStatus.COMPLETED and task.result_bytes:
if task.type == TaskType.STYLE_DESIGN and hasattr(style_tab, '_display_generated_image_from_bytes'):
# 款式设计:将图片数据存储到样式标签并显示
style_tab.generated_image_bytes = task.result_bytes
style_tab._display_generated_image_from_bytes()
elif hasattr(gen_tab, '_display_generated_image_from_bytes'):
# 图片生成:将图片数据存储到主窗口并显示
self.parent_window.generated_image_bytes = task.result_bytes
gen_tab._display_generated_image_from_bytes()
# 回填参数到主窗口
self._load_task_to_main_window(task)
def _load_task_to_main_window(self, task: Task):
"""将任务数据回填到主窗口"""
try:
if task.type == TaskType.STYLE_DESIGN:
# 款式设计标签页 - 回填prompt到预览框
self.parent_window.tab_widget.setCurrentIndex(1) # 款式设计标签
style_tab = self.parent_window.tab_widget.currentWidget()
# 回填prompt到预览框
if hasattr(style_tab, 'prompt_preview'):
style_tab.prompt_preview.setPlainText(task.prompt)
# 回填设置
if hasattr(style_tab, 'aspect_ratio') and task.aspect_ratio:
index = style_tab.aspect_ratio.findText(task.aspect_ratio)
if index >= 0:
style_tab.aspect_ratio.setCurrentIndex(index)
if hasattr(style_tab, 'image_size') and task.image_size:
index = style_tab.image_size.findText(task.image_size)
if index >= 0:
style_tab.image_size.setCurrentIndex(index)
else:
# 图片生成标签页
self.parent_window.tab_widget.setCurrentIndex(0) # 图片生成标签
gen_tab = self.parent_window.tab_widget.currentWidget()
# 回填prompt
if hasattr(gen_tab, 'prompt_text'):
gen_tab.prompt_text.setPlainText(task.prompt)
# 回填参考图片
if task.reference_images and hasattr(gen_tab, 'add_reference_image'):
for ref_path in task.reference_images:
gen_tab.add_reference_image(ref_path)
# 回填设置
if hasattr(gen_tab, 'aspect_ratio') and task.aspect_ratio:
index = gen_tab.aspect_ratio.findText(task.aspect_ratio)
if index >= 0:
gen_tab.aspect_ratio.setCurrentIndex(index)
if hasattr(gen_tab, 'image_size') and task.image_size:
index = gen_tab.image_size.findText(task.image_size)
if index >= 0:
gen_tab.image_size.setCurrentIndex(index)
except Exception as e:
self.logger.error(f"回填数据到主窗口失败: {e}")
def _on_task_started(self, task_id: str):
"""任务开始回调"""
self._update_summary()
def _on_task_completed(self, task_id: str, *args):
"""任务完成回调"""
self._update_summary()
def _on_task_failed(self, task_id: str, error: str):
"""任务失败回调"""
self._update_summary()
def _show_task_detail_dialog(self):
"""显示任务详情弹窗"""
if self.detail_dialog and self.detail_dialog.isVisible():
self.detail_dialog.raise_()
return
dialog = QDialog(self)
dialog.setWindowTitle("任务详情")
dialog.resize(400, 500)
layout = QVBoxLayout()
# 标题
title = QLabel("📋 任务队列")
title.setStyleSheet("QLabel { font-weight: bold; font-size: 14px; }")
layout.addWidget(title)
# 任务列表
task_list = QListWidget()
task_list.itemClicked.connect(lambda item: self._on_detail_task_clicked(item, dialog))
# 添加任务项
for task in self.manager.get_all_tasks():
item = QListWidgetItem()
item.setData(Qt.UserRole, task.id)
status_map = {
TaskStatus.RUNNING: ("●", "#FF9500"),
TaskStatus.PENDING: ("○", "#8E8E93"),
TaskStatus.COMPLETED: ("✓", "#34C759"),
TaskStatus.FAILED: ("✗", "#FF3B30"),
}
icon, color = status_map.get(task.status, ("?", "#000"))
prompt_preview = task.prompt[:30] + "..." if len(task.prompt) > 30 else task.prompt
display_text = f"{icon} {prompt_preview}"
item.setText(display_text)
if hasattr(item, 'setForeground'):
from PySide6.QtGui import QBrush, QColor
item.setForeground(QBrush(QColor(color)))
task_list.addItem(item)
layout.addWidget(task_list)
# 关闭按钮
close_btn = QPushButton("关闭")
close_btn.clicked.connect(dialog.close)
layout.addWidget(close_btn)
dialog.setLayout(layout)
self.detail_dialog = dialog
dialog.exec()
def _on_detail_task_clicked(self, item: QListWidgetItem, dialog: QDialog):
"""详情弹窗中点击任务项"""
dialog.close()
self._on_task_item_clicked(item)
def _on_task_progress(self, task_id: str, progress: float, status_text: str):
"""任务进度回调"""
# 侧边栏模式下进度信息通过状态标签显示
# 不需要额外更新,由_update_summary统一处理
pass
def _on_task_item_clicked(self, item: QListWidgetItem):
"""单击任务 - 回填数据到主窗口或显示结果"""
task_id = item.data(Qt.UserRole)
task = self.manager.get_task(task_id)
if not task or not self.parent_window:
return
# 切换到对应的标签页
if task.type == TaskType.STYLE_DESIGN:
self.parent_window.tab_widget.setCurrentIndex(1) # 款式设计标签
style_tab = self.parent_window.tab_widget.currentWidget()
else:
self.parent_window.tab_widget.setCurrentIndex(0) # 图片生成标签
gen_tab = self.parent_window.tab_widget.currentWidget()
# 如果是已完成任务,直接在主窗口显示结果
if task.status == TaskStatus.COMPLETED and task.result_bytes:
self.parent_window.generated_image_bytes = task.result_bytes
# 显示生成的图片
if hasattr(self.parent_window, 'display_generated_image'):
self.parent_window.display_generated_image()
elif task.type == TaskType.STYLE_DESIGN and hasattr(style_tab, '_display_generated_image_from_bytes'):
style_tab._display_generated_image_from_bytes()
elif hasattr(gen_tab, '_display_generated_image_from_bytes'):
gen_tab._display_generated_image_from_bytes()
# 回填参数
self._load_task_to_main_window(task)
def _load_task_to_main_window(self, task: Task):
"""将任务数据回填到主窗口"""
try:
if task.type == TaskType.STYLE_DESIGN:
# 切换到款式设计标签
self.parent_window.tab_widget.setCurrentIndex(1)
style_tab = self.parent_window.tab_widget.currentWidget()
if hasattr(style_tab, 'library_manager'):
# 款式设计不需要回填prompt,因为是参数组合
pass
else:
# 切换到图片生成标签
self.parent_window.tab_widget.setCurrentIndex(0)
gen_tab = self.parent_window.tab_widget.currentWidget()
if hasattr(gen_tab, 'prompt_input'):
# 回填prompt
gen_tab.prompt_input.setPlainText(task.prompt)
# 回填参考图片
if task.reference_images:
for ref_path in task.reference_images:
if hasattr(gen_tab, 'add_reference_image'):
gen_tab.add_reference_image(ref_path)
# 回填设置
if hasattr(gen_tab, 'aspect_ratio') and task.aspect_ratio:
index = gen_tab.aspect_ratio.findText(task.aspect_ratio)
if index >= 0:
gen_tab.aspect_ratio.setCurrentIndex(index)
if hasattr(gen_tab, 'image_size') and task.image_size:
index = gen_tab.image_size.findText(task.image_size)
if index >= 0:
gen_tab.image_size.setCurrentIndex(index)
except Exception as e:
self.logger.error(f"回填数据到主窗口失败: {e}")