de306ffe by 柴进

feat(ui): 接入主入口 + 迁移 LoginDialog/ImageGeneratorWindow 样式

- main() 创建 QApplication 后立即 apply_theme,所有后续 widget(含 preflight QMessageBox)都吃全局主题
- LoginDialog: 删 80 行 inline QSS 块,objectName=loginDialog/loginTitle,主按钮 variant=primary,error_label status property
- ImageGeneratorWindow: 删 50 行 apply_styles QSS 块,删 30 处 inline setStyleSheet
- "生成图片" 主按钮 variant=primary(pill 圆角 + accent 实色背景)
- status_label 全部用 _set_status(success|warning|danger|info|muted) helper
- DragDropScrollArea 拖拽态走 drag_state property(idle/active)
- 缩略图 role=thumb/thumb_index,删除按钮 #thumbDeleteBtn variant=danger
- generated_image_label has_image property,"已复制" success-flash variant
- preview_label/prompt_display 走 objectName + 全局 QSS

剩余:StyleDesignerTab 和 task_queue.py。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 3bc29f7b
......@@ -1241,26 +1241,33 @@ class LoginDialog(QDialog):
def setup_ui(self):
"""Build login dialog UI"""
self.setObjectName("loginDialog")
self.setWindowTitle("登录 - 珠宝壹佰图像生成器")
self.setFixedSize(400, 400)
self.setFixedSize(400, 420)
# Main layout
main_layout = QVBoxLayout()
main_layout.setContentsMargins(40, 40, 40, 40)
main_layout.setSpacing(20)
main_layout.setSpacing(16)
# Title
title_label = QLabel("登录")
title_label.setObjectName("title")
title_label.setObjectName("loginTitle")
title_label.setProperty("role", "title")
title_label.setAlignment(Qt.AlignCenter)
main_layout.addWidget(title_label)
main_layout.addSpacing(10)
subtitle_label = QLabel("珠宝壹佰图像生成器")
subtitle_label.setObjectName("loginSubtitle")
subtitle_label.setProperty("role", "secondary")
subtitle_label.setAlignment(Qt.AlignCenter)
main_layout.addWidget(subtitle_label)
main_layout.addSpacing(8)
# Username field
username_label = QLabel("用户名")
username_label.setObjectName("field_label")
username_label.setProperty("role", "caption")
main_layout.addWidget(username_label)
self.username_entry = QLineEdit()
......@@ -1268,11 +1275,9 @@ class LoginDialog(QDialog):
self.username_entry.setFixedHeight(40)
main_layout.addWidget(self.username_entry)
main_layout.addSpacing(10)
# Password field
password_label = QLabel("密码")
password_label.setObjectName("field_label")
password_label.setProperty("role", "caption")
main_layout.addWidget(password_label)
self.password_entry = QLineEdit()
......@@ -1280,6 +1285,8 @@ class LoginDialog(QDialog):
self.password_entry.setFixedHeight(40)
# Handle saved password placeholder
# 占位符态用 inline 语义化 hint:已保存密码时整体灰一点,
# 用户开始输入会触发 on_password_change 清除。
if self.saved_password_hash:
self.password_entry.setPlaceholderText("••••••••")
self.password_entry.setStyleSheet("QLineEdit { color: #999999; }")
......@@ -1302,16 +1309,20 @@ class LoginDialog(QDialog):
checkbox_layout.addStretch()
main_layout.addLayout(checkbox_layout)
# Login button
main_layout.addSpacing(4)
# Login button - 主按钮
self.login_button = QPushButton("登录")
self.login_button.setObjectName("login_button")
self.login_button.setProperty("variant", "primary")
self.login_button.setFixedHeight(40)
self.login_button.clicked.connect(self.on_login)
main_layout.addWidget(self.login_button)
# Error label
# Error / info label
self.error_label = QLabel("")
self.error_label.setObjectName("error_label")
self.error_label.setProperty("status", "muted")
self.error_label.setAlignment(Qt.AlignCenter)
self.error_label.setWordWrap(True)
main_layout.addWidget(self.error_label)
......@@ -1327,58 +1338,16 @@ class LoginDialog(QDialog):
self.username_entry.setFocus()
def apply_styles(self):
"""Apply QSS stylesheet"""
self.setStyleSheet("""
QDialog {
background-color: #ffffff;
}
QLabel#title {
font-size: 20pt;
font-weight: bold;
color: #1d1d1f;
}
QLabel#field_label {
font-size: 10pt;
color: #666666;
}
QLineEdit {
padding: 8px;
border: 1px solid #e5e5e5;
border-radius: 4px;
background-color: #fafafa;
font-size: 11pt;
color: #000000;
}
QLineEdit:focus {
border: 1px solid #007AFF;
}
QPushButton#login_button {
background-color: #007AFF;
color: white;
font-size: 10pt;
font-weight: bold;
padding: 10px 20px;
border: none;
border-radius: 6px;
}
QPushButton#login_button:hover {
background-color: #0051D5;
}
QPushButton#login_button:pressed {
background-color: #003D99;
}
QPushButton#login_button:disabled {
background-color: #cccccc;
}
QCheckBox {
font-size: 9pt;
color: #1d1d1f;
}
QLabel#error_label {
color: #ff3b30;
font-size: 9pt;
}
""")
"""登录页样式由全局 theme.py 提供,这里只设置 objectName / property。"""
# objectName / property 已在 setup_ui 里设置完毕,全局 QSS 自动命中。
pass
def _set_error(self, status: str, text: str = "") -> None:
"""统一设置 error_label。status: muted|danger|success|info"""
self.error_label.setText(text)
self.error_label.setProperty("status", status)
self.error_label.style().unpolish(self.error_label)
self.error_label.style().polish(self.error_label)
def on_password_change(self):
"""Handle password field changes"""
......@@ -1404,8 +1373,7 @@ class LoginDialog(QDialog):
# Disable button during authentication
self.login_button.setEnabled(False)
self.error_label.setText("正在验证...")
self.error_label.setStyleSheet("QLabel { color: #666666; }")
self._set_error("muted", "正在验证...")
# Determine which password to use
if not self.password_changed and self.saved_password_hash:
......@@ -1541,8 +1509,7 @@ class LoginDialog(QDialog):
QMessageBox.critical(self, "登录错误", message)
# 同时保留标签显示
self.error_label.setText(message)
self.error_label.setStyleSheet("QLabel { color: #ff3b30; }")
self._set_error("danger", message)
def get_remember_user(self):
"""Get remember username checkbox state"""
......@@ -1599,17 +1566,14 @@ class DragDropScrollArea(QScrollArea):
super().__init__(parent)
self.setAcceptDrops(True)
self.parent_window = parent
self.setStyleSheet("""
QScrollArea {
border: 2px dashed #e5e5e5;
border-radius: 8px;
background-color: #fafafa;
}
QScrollArea:hover {
border-color: #007AFF;
background-color: #f0f8ff;
}
""")
self.setObjectName("referenceImageDrop")
self._set_drag_state("idle")
def _set_drag_state(self, state: str) -> None:
"""状态切换:'idle' | 'active'。视觉由全局 theme.py 控制。"""
self.setProperty("drag_state", state)
self.style().unpolish(self)
self.style().polish(self)
def dragEnterEvent(self, event: QDragEnterEvent):
"""拖拽进入事件处理"""
......@@ -1628,26 +1592,14 @@ class DragDropScrollArea(QScrollArea):
file_path = url.toLocalFile()
if self.is_valid_image_file(file_path):
event.acceptProposedAction()
self.setStyleSheet("""
QScrollArea {
border: 2px dashed #007AFF;
border-radius: 8px;
background-color: #e6f3ff;
}
""")
self._set_drag_state("active")
return
# 检查剪贴板图像
try:
if mime_data.hasImage():
event.acceptProposedAction()
self.setStyleSheet("""
QScrollArea {
border: 2px dashed #007AFF;
border-radius: 8px;
background-color: #e6f3ff;
}
""")
self._set_drag_state("active")
return
except Exception:
pass
......@@ -1656,34 +1608,14 @@ class DragDropScrollArea(QScrollArea):
def dragLeaveEvent(self, event):
"""拖拽离开事件处理"""
self.setStyleSheet("""
QScrollArea {
border: 2px dashed #e5e5e5;
border-radius: 8px;
background-color: #fafafa;
}
QScrollArea:hover {
border-color: #007AFF;
background-color: #f0f8ff;
}
""")
self._set_drag_state("idle")
def dropEvent(self, event: QDropEvent):
"""拖拽放置事件处理"""
mime_data = event.mimeData()
# 重置样式
self.setStyleSheet("""
QScrollArea {
border: 2px dashed #e5e5e5;
border-radius: 8px;
background-color: #fafafa;
}
QScrollArea:hover {
border-color: #007AFF;
background-color: #f0f8ff;
}
""")
self._set_drag_state("idle")
# 内部缩略图重排
if mime_data.hasFormat(THUMB_REORDER_MIME):
......@@ -2094,31 +2026,15 @@ class ImageGeneratorWindow(QMainWindow):
# Paste button
paste_btn = QPushButton("📋 粘贴图片")
paste_btn.clicked.connect(self.paste_from_clipboard)
paste_btn.setStyleSheet("""
QPushButton {
background-color: #f0f0f0;
border: 1px solid #d0d0d0;
padding: 8px 16px;
border-radius: 4px;
font-size: 12px;
min-width: 80px;
}
QPushButton:hover {
background-color: #e8e8e8;
border-color: #007AFF;
}
QPushButton:pressed {
background-color: #d0d0d0;
}
""")
upload_header.addWidget(paste_btn)
self.image_count_label = QLabel("已选择 0 张")
self.image_count_label.setProperty("role", "secondary")
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; }")
hint_label.setProperty("role", "muted")
upload_header.addWidget(hint_label)
upload_header.addStretch()
......@@ -2167,7 +2083,6 @@ class ImageGeneratorWindow(QMainWindow):
# Prompt text area
self.prompt_text = QTextEdit()
self.prompt_text.setStyleSheet("font-size: 16px;")
self.prompt_text.setPlainText("一幅美丽的风景画,有山有湖,日落时分")
self.prompt_text.textChanged.connect(self.check_favorite_status)
prompt_layout.addWidget(self.prompt_text)
......@@ -2226,6 +2141,7 @@ class ImageGeneratorWindow(QMainWindow):
# Action buttons
action_layout = QHBoxLayout()
self.generate_btn = QPushButton("生成图片")
self.generate_btn.setProperty("variant", "primary")
self.generate_btn.clicked.connect(self.generate_image_async)
action_layout.addWidget(self.generate_btn)
......@@ -2235,6 +2151,7 @@ class ImageGeneratorWindow(QMainWindow):
action_layout.addWidget(self.download_btn)
self.status_label = QLabel("● 就绪")
self.status_label.setProperty("status", "muted")
action_layout.addWidget(self.status_label)
action_layout.addStretch()
......@@ -2245,9 +2162,9 @@ class ImageGeneratorWindow(QMainWindow):
preview_layout = QVBoxLayout()
self.preview_label = QLabel("生成的图片将在这里显示\n双击用系统查看器打开")
self.preview_label.setObjectName("previewPlaceholder")
self.preview_label.setAlignment(Qt.AlignCenter)
self.preview_label.setMinimumHeight(300)
self.preview_label.setStyleSheet("QLabel { color: #999999; font-size: 10pt; }")
self.preview_label.mouseDoubleClickEvent = self.open_fullsize_view
preview_layout.addWidget(self.preview_label)
......@@ -2348,9 +2265,8 @@ class ImageGeneratorWindow(QMainWindow):
prompt_layout.addLayout(prompt_header)
self.prompt_display = QLabel("请选择一个历史记录查看详情")
self.prompt_display.setObjectName("promptDisplay")
self.prompt_display.setWordWrap(True)
self.prompt_display.setStyleSheet(
"QLabel { padding: 8px; background-color: #f9f9f9; border: 1px solid #ddd; border-radius: 4px; }")
prompt_layout.addWidget(self.prompt_display)
prompt_group.setLayout(prompt_layout)
......@@ -2403,11 +2319,10 @@ class ImageGeneratorWindow(QMainWindow):
gen_layout.setAlignment(Qt.AlignCenter)
self.generated_image_label = QLabel("请选择一个历史记录查看生成图片")
self.generated_image_label.setObjectName("previewImage")
self.generated_image_label.setAlignment(Qt.AlignCenter)
self.generated_image_label.setMinimumSize(200, 200) # Larger size for generated image
self.generated_image_label.setMaximumSize(300, 300)
self.generated_image_label.setStyleSheet(
"QLabel { background-color: #f5f5f5; border: 1px solid #ddd; color: #999; }")
self.generated_image_label.mouseDoubleClickEvent = self.open_generated_image_from_history
gen_layout.addWidget(self.generated_image_label)
......@@ -2422,60 +2337,25 @@ class ImageGeneratorWindow(QMainWindow):
return panel
def apply_styles(self):
"""Apply QSS stylesheet"""
"""Apply QSS stylesheet - 最小化自定义样式"""
self.setStyleSheet("""
QMainWindow {
background-color: #ffffff;
}
QGroupBox {
font-weight: bold;
font-size: 10pt;
border: 1px solid #e5e5e5;
border-radius: 6px;
margin-top: 2px;
padding-top: 2px;
}
QGroupBox::title {
subcontrol-origin: margin;
left: 10px;
padding: 0 5px;
}
QPushButton {
padding: 6px 12px;
font-size: 9pt;
border: 1px solid #cccccc;
border-radius: 4px;
}
QPushButton:disabled {
color: #999999;
}
QComboBox {
padding: 5px;
min-width: 100px;
}
QTextEdit {
border: 1px solid #e5e5e5;
border-radius: 4px;
font-size: 10pt;
}
QScrollArea {
border: none;
background-color: #f6f6f6;
}
/* 只保留必要的边框和背景,不设置颜色 */
QLineEdit, QComboBox, QTextEdit {
border: 1px solid #cccccc;
border-radius: 4px;
}
QLineEdit:focus, QComboBox:focus, QTextEdit:focus {
border: 1px solid #007AFF; /* 保持焦点状态 */
}
""")
"""主窗口样式由全局 theme.py 提供,这里只接最后的信号。"""
self.setObjectName("mainWindow")
# Connect signals after all widgets are created
self.saved_prompts_combo.currentIndexChanged.connect(self.load_saved_prompt)
def _set_status(self, status: str, text: str | None = None) -> None:
"""统一状态标签视觉。status: success|warning|danger|info|muted"""
if text is not None:
self.status_label.setText(text)
self.status_label.setProperty("status", status)
self.status_label.style().unpolish(self.status_label)
self.status_label.style().polish(self.status_label)
def _set_image_state(self, label, has_image: bool) -> None:
"""切换 #previewImage 的 has_image 属性,让 QSS 命中对应外观。"""
label.setProperty("has_image", "true" if has_image else "false")
label.style().unpolish(label)
label.style().polish(label)
def upload_images(self):
"""Upload reference images"""
self.logger.info("开始上传参考图片")
......@@ -2504,7 +2384,7 @@ class ImageGeneratorWindow(QMainWindow):
self.update_image_preview()
self.image_count_label.setText(f"已选择 {len(self.uploaded_images)} 张")
self.status_label.setText(f"● 已添加 {valid_count} 张参考图片")
self.status_label.setStyleSheet("QLabel { color: #34C759; }")
self._set_status("success")
self.logger.info(f"图片上传完成,有效图片: {valid_count} 张")
# 检查极速模式下的多图限制
......@@ -2537,7 +2417,7 @@ class ImageGeneratorWindow(QMainWindow):
self.update_image_preview()
self.image_count_label.setText(f"已选择 {len(self.uploaded_images)} 张")
self.status_label.setText(f"● 已通过拖拽添加 {added_count} 张参考图片")
self.status_label.setStyleSheet("QLabel { color: #34C759; }")
self._set_status("success")
# 检查极速模式下的多图限制
# self.check_multi_image_mode_conflict()
......@@ -2572,7 +2452,7 @@ class ImageGeneratorWindow(QMainWindow):
self.update_image_preview()
self.image_count_label.setText(f"已选择 {len(self.uploaded_images)} 张")
self.status_label.setText("● 已添加剪贴板图片")
self.status_label.setStyleSheet("QLabel { color: #34C759; }")
self._set_status("success")
else:
QMessageBox.critical(self, "错误", "无法保存剪贴板图片")
......@@ -2694,7 +2574,7 @@ class ImageGeneratorWindow(QMainWindow):
self.update_image_preview()
self.image_count_label.setText(f"已选择 {len(self.uploaded_images)} 张")
self.status_label.setText(f"● 已粘贴 {added} 张图片")
self.status_label.setStyleSheet("QLabel { color: #34C759; }")
self._set_status("success")
return
# 没有文件URL,尝试获取纯图像数据(截图、从应用复制的图片等)
......@@ -2728,7 +2608,7 @@ class ImageGeneratorWindow(QMainWindow):
self.update_image_preview()
self.image_count_label.setText(f"已选择 {len(self.uploaded_images)} 张")
self.status_label.setText("● 已添加剪贴板图片")
self.status_label.setStyleSheet("QLabel { color: #34C759; }")
self._set_status("success")
except Exception as e:
self.logger.error(f"粘贴失败: {e}", exc_info=True)
......@@ -2812,32 +2692,21 @@ class ImageGeneratorWindow(QMainWindow):
# Image label
img_label = QLabel()
img_label.setProperty("role", "thumb")
img_label.setPixmap(pixmap)
img_label.setFixedSize(100, 100)
img_label.setStyleSheet("QLabel { border: 1px solid #e5e5e5; }")
container_layout.addWidget(img_label)
# Info row
info_layout = QHBoxLayout()
index_label = QLabel(f"图 {idx + 1}")
index_label.setStyleSheet("QLabel { font-size: 12pt; color: #666666; font-weight: bold; }")
index_label.setProperty("role", "thumb_index")
info_layout.addWidget(index_label)
del_btn = QPushButton("✕")
del_btn.setObjectName("thumbDeleteBtn")
del_btn.setProperty("variant", "danger")
del_btn.setFixedSize(20, 20)
del_btn.setStyleSheet("""
QPushButton {
background-color: #ff4444;
color: white;
font-weight: bold;
border: none;
border-radius: 3px;
padding: 0px;
}
QPushButton:hover {
background-color: #FF3B30;
}
""")
del_btn.clicked.connect(lambda checked, i=idx: self.delete_image(i))
info_layout.addWidget(del_btn)
......@@ -2862,7 +2731,7 @@ class ImageGeneratorWindow(QMainWindow):
self.update_image_preview()
self.image_count_label.setText(f"已选择 {len(self.uploaded_images)} 张")
self.status_label.setText("● 已删除图片")
self.status_label.setStyleSheet("QLabel { color: #FF9500; }")
self._set_status("warning")
else:
self.logger.warning(f"无效的图片索引: {index}")
......@@ -2888,7 +2757,7 @@ class ImageGeneratorWindow(QMainWindow):
self.logger.info(f"缩略图重排: {src_index} -> {target_index}")
self.update_image_preview()
self.status_label.setText(f"● 已调整图片顺序 (图 {src_index + 1} → 图 {target_index + 1})")
self.status_label.setStyleSheet("QLabel { color: #007AFF; }")
self._set_status("info")
def update_saved_prompts_list(self):
"""Update the saved prompts dropdown"""
......@@ -2913,7 +2782,7 @@ class ImageGeneratorWindow(QMainWindow):
prompt = self.prompt_text.toPlainText().strip()
if not prompt:
self.status_label.setText("● 提示词不能为空")
self.status_label.setStyleSheet("QLabel { color: #FF3B30; }")
self._set_status("danger")
return
if prompt in self.saved_prompts:
......@@ -2927,7 +2796,7 @@ class ImageGeneratorWindow(QMainWindow):
self.update_saved_prompts_list()
self.status_label.setText("● 该提示词已收藏")
self.status_label.setStyleSheet("QLabel { color: #34C759; }")
self._set_status("success")
self.check_favorite_status()
def load_saved_prompt(self):
......@@ -2936,7 +2805,7 @@ class ImageGeneratorWindow(QMainWindow):
if 0 <= index < len(self.saved_prompts):
self.prompt_text.setPlainText(self.saved_prompts[index])
self.status_label.setText("● 已加载提示词")
self.status_label.setStyleSheet("QLabel { color: #007AFF; }")
self._set_status("info")
def delete_saved_prompt(self):
"""Delete the currently selected saved prompt"""
......@@ -2944,14 +2813,14 @@ class ImageGeneratorWindow(QMainWindow):
if index < 0 or index >= len(self.saved_prompts):
self.status_label.setText("● 请先选择要删除的提示词")
self.status_label.setStyleSheet("QLabel { color: #FF9500; }")
self._set_status("warning")
return
self.saved_prompts.pop(index)
self.save_config()
self.update_saved_prompts_list()
self.status_label.setText("● 已删除提示词")
self.status_label.setStyleSheet("QLabel { color: #34C759; }")
self._set_status("success")
def get_selected_model(self):
"""根据生成模式返回对应的模型名称"""
......@@ -3049,7 +2918,7 @@ class ImageGeneratorWindow(QMainWindow):
# self.update_image_preview()
# self.image_count_label.setText(f"已选择 {len(self.uploaded_images)} 张")
# self.status_label.setText("● 已保留第一张参考图片")
# self.status_label.setStyleSheet("QLabel { color: #FF9500; }")
# self._set_status("warning")
def generate_image_async(self):
"""Submit image generation task to queue"""
......@@ -3099,7 +2968,7 @@ class ImageGeneratorWindow(QMainWindow):
# Update UI
self.status_label.setText(f"● 任务已提交 (ID: {task_id[:8]})")
self.status_label.setStyleSheet("QLabel { color: #007AFF; }")
self._set_status("info")
except RuntimeError as e:
QMessageBox.warning(self, "队列已满", str(e))
......@@ -3115,7 +2984,7 @@ class ImageGeneratorWindow(QMainWindow):
self.display_image()
self.download_btn.setEnabled(True)
self.status_label.setText("● 图片生成成功")
self.status_label.setStyleSheet("QLabel { color: #34C759; }")
self._set_status("success")
# Save to history
try:
......@@ -3139,7 +3008,7 @@ class ImageGeneratorWindow(QMainWindow):
self.download_btn.setEnabled(True)
self.generate_btn.setEnabled(True)
self.status_label.setText("● 图片生成成功")
self.status_label.setStyleSheet("QLabel { color: #34C759; }")
self._set_status("success")
# 自动保存到历史记录
try:
......@@ -3162,7 +3031,7 @@ class ImageGeneratorWindow(QMainWindow):
QMessageBox.critical(self, "错误", f"生成失败: {error_msg}")
self.generate_btn.setEnabled(True)
self.status_label.setText("● 生成失败")
self.status_label.setStyleSheet("QLabel { color: #FF3B30; }")
self._set_status("danger")
def update_status(self, message):
"""Update status label"""
......@@ -3196,7 +3065,6 @@ class ImageGeneratorWindow(QMainWindow):
)
self.preview_label.setPixmap(scaled_pixmap)
self.preview_label.setStyleSheet("")
self.logger.info("[display_image] 图片显示完成")
except Exception as e:
self.logger.error(f"[display_image] 图片显示失败: {e}", exc_info=True)
......@@ -3222,7 +3090,7 @@ class ImageGeneratorWindow(QMainWindow):
processing_method = "格式转换后" if processed_bytes != self.generated_image_bytes else "原始数据"
self.status_label.setText(f"● 已用系统查看器打开 ({processing_method})")
self.status_label.setStyleSheet("QLabel { color: #007AFF; }")
self._set_status("info")
except Exception as e:
QMessageBox.critical(self, "错误", f"无法打开系统图片查看器: {str(e)}")
......@@ -3300,7 +3168,7 @@ class ImageGeneratorWindow(QMainWindow):
# 只使用状态栏提示,不显示弹窗
self.status_label.setText("● 图片已保存")
self.status_label.setStyleSheet("QLabel { color: #34C759; }")
self._set_status("success")
except Exception as e:
self.logger.error(f"图片保存失败: {str(e)}")
QMessageBox.critical(self, "错误", f"保存失败: {str(e)}")
......@@ -3443,7 +3311,7 @@ class ImageGeneratorWindow(QMainWindow):
if total == 0:
self.clear_details_panel()
self.status_label.setText("● 历史记录已删除")
self.status_label.setStyleSheet("QLabel { color: #34C759; }")
self._set_status("success")
else:
QMessageBox.critical(self, "错误", "删除历史记录失败")
......@@ -3489,7 +3357,7 @@ class ImageGeneratorWindow(QMainWindow):
self.history_count_label.setText("共 0 条历史记录")
self.clear_details_panel()
self.status_label.setText("● 历史记录已清空")
self.status_label.setStyleSheet("QLabel { color: #34C759; }")
self._set_status("success")
except Exception as e:
QMessageBox.critical(self, "错误", f"清空历史记录失败: {str(e)}")
......@@ -3526,7 +3394,7 @@ class ImageGeneratorWindow(QMainWindow):
if not reference_paths:
no_images_label = QLabel("无参考图片")
no_images_label.setStyleSheet("color: #999;")
no_images_label.setProperty("role", "muted")
self.ref_images_layout.addWidget(no_images_label)
return
......@@ -3550,7 +3418,7 @@ class ImageGeneratorWindow(QMainWindow):
image_label.setPixmap(thumbnail)
image_label.setFixedSize(size, size)
image_label.setAlignment(Qt.AlignCenter)
image_label.setStyleSheet("border: 1px solid #ddd; margin: 5px;")
image_label.setProperty("role", "thumb")
image_label.mouseDoubleClickEvent = lambda e, path=ref_path: self.open_reference_image(path)
self.ref_images_layout.addWidget(image_label)
except Exception as e:
......@@ -3571,22 +3439,18 @@ class ImageGeneratorWindow(QMainWindow):
Qt.SmoothTransformation
)
self.generated_image_label.setPixmap(scaled_pixmap)
self.generated_image_label.setStyleSheet(
"QLabel { border: 1px solid #ddd; background-color: white; }")
self._set_image_state(self.generated_image_label, has_image=True)
self.current_generated_image_path = image_path
else:
self.generated_image_label.setText("图片加载失败")
self.generated_image_label.setStyleSheet(
"QLabel { background-color: #f5f5f5; border: 1px solid #ddd; color: #999; }")
self._set_image_state(self.generated_image_label, has_image=False)
except Exception as e:
print(f"Failed to load generated image {image_path}: {e}")
self.generated_image_label.setText("图片加载失败")
self.generated_image_label.setStyleSheet(
"QLabel { background-color: #f5f5f5; border: 1px solid #ddd; color: #999; }")
self._set_image_state(self.generated_image_label, has_image=False)
else:
self.generated_image_label.setText("图片文件不存在")
self.generated_image_label.setStyleSheet(
"QLabel { background-color: #f5f5f5; border: 1px solid #ddd; color: #999; }")
self._set_image_state(self.generated_image_label, has_image=False)
def clear_details_panel(self):
"""Clear the details panel"""
......@@ -3602,13 +3466,12 @@ class ImageGeneratorWindow(QMainWindow):
if child:
child.setParent(None)
no_images_label = QLabel("无参考图片")
no_images_label.setStyleSheet("color: #999;")
no_images_label.setProperty("role", "muted")
self.ref_images_layout.addWidget(no_images_label)
self.generated_image_label.setText("请选择一个历史记录查看生成图片")
self.generated_image_label.setPixmap(QPixmap())
self.generated_image_label.setStyleSheet(
"QLabel { background-color: #f5f5f5; border: 1px solid #ddd; color: #999; }")
self._set_image_state(self.generated_image_label, has_image=False)
def copy_prompt_text(self):
"""Copy the prompt text to clipboard"""
......@@ -3618,9 +3481,10 @@ class ImageGeneratorWindow(QMainWindow):
clipboard.setText(self.current_history_prompt)
# Show success message briefly
original_text = self.copy_prompt_btn.text()
self.copy_prompt_btn.setText("✅ 已复制")
self.copy_prompt_btn.setStyleSheet("QPushButton { background-color: #34C759; color: white; }")
self.copy_prompt_btn.setProperty("variant", "success-flash")
self.copy_prompt_btn.style().unpolish(self.copy_prompt_btn)
self.copy_prompt_btn.style().polish(self.copy_prompt_btn)
# Reset after 2 seconds
QTimer.singleShot(2000, lambda: self.reset_copy_button())
......@@ -3628,7 +3492,9 @@ class ImageGeneratorWindow(QMainWindow):
def reset_copy_button(self):
"""Reset the copy button appearance"""
self.copy_prompt_btn.setText("📋 复制")
self.copy_prompt_btn.setStyleSheet("")
self.copy_prompt_btn.setProperty("variant", "")
self.copy_prompt_btn.style().unpolish(self.copy_prompt_btn)
self.copy_prompt_btn.style().polish(self.copy_prompt_btn)
def open_generated_image_from_history(self, event):
"""Open the generated image from history in system viewer"""
......@@ -4779,7 +4645,7 @@ class StyleDesignerTab(QWidget):
# 更新状态提示
self.status_label.setText("● 图片生成成功")
self.status_label.setStyleSheet("QLabel { color: #34C759; }")
self._set_status("success")
def on_generation_error(self, error_msg: str):
"""生成失败回调"""
......@@ -4835,7 +4701,7 @@ class StyleDesignerTab(QWidget):
f.write(self.generated_image_bytes)
# 使用状态栏提示而不是弹窗
self.status_label.setText("● 图片已保存")
self.status_label.setStyleSheet("QLabel { color: #34C759; }")
self._set_status("success")
except Exception as e:
QMessageBox.critical(self, "错误", f"保存失败: {e}")
......@@ -4938,7 +4804,11 @@ def main():
# 第4步:创建 QApplication(preflight 对话框需要)
logger.info("[BOOT] Phase 4: 创建 QApplication...")
app = QApplication(sys.argv)
logger.info("[BOOT] Phase 4 完成: QApplication 已创建")
# 立即应用全局主题:所有后续创建的 widget(含 preflight 的 QMessageBox)
# 都会自动跟随。ThemeManager 必须有引用持有,否则 GC 会断开信号。
from theme import apply_theme
app.theme_manager = apply_theme(app)
logger.info(f"[BOOT] Phase 4 完成: QApplication 已创建,主题: {app.theme_manager.current_mode()}")
# 第 4.5 步:启动门禁 preflight
# 任一检查失败 → 弹"应用启动失败,请联系 @柴进" → sys.exit(1)
......
## Design Document: Image History Management
### Context
当前应用缺乏图片历史记录功能,用户生成图片后需要手动下载和管理。需要实现自动保存、管理和查看历史生成记录的功能,提升用户体验和工作效率。
### Goals / Non-Goals
**Goals:**
- 自动保存所有生成的图片和相关元数据
- 提供直观的历史记录浏览界面
- 支持从历史记录快速重新加载图片
- 保持与现有功能的完全兼容
**Non-Goals:**
- 不实现云同步功能(纯本地存储)
- 不修改现有图片生成核心逻辑
- 不依赖外部数据库或复杂依赖
### Decisions
#### 1. 存储架构
**Decision**: 使用文件系统 + JSON索引的轻量级存储方案
- **Why**: 避免引入重型数据库依赖,保持应用轻量化
- **Alternatives considered**: SQLite数据库, Redis缓存, 文件系统-only方案
- **Chosen approach**: `/images/YYYYMMDDHHMMSS/` 目录结构 + 全局 `history_index.json`
#### 2. 数据结构设计
**Decision**: 采用分层存储结构
```
/images/
├── history_index.json # 全局索引文件
└── 20241201123456/ # 按时间戳的目录
├── metadata.json # 该会话的元数据
├── generated.png # 生成的图片
├── reference1.jpg # 参考图片1
└── reference2.jpg # 参考图片2
```
#### 3. UI集成方案
**Decision**: 在现有主界面添加"历史记录"标签页
- **Why**: 保持界面一致性,最小化学习成本
- **Alternatives considered**: 独立历史记录窗口, 侧边栏集成, 右键菜单
#### 4. 性能优化策略
**Decision**: 延迟加载和缩略图缓存
- 历史记录列表只加载基本信息和缩略图
- 点击时才加载完整图片
- 实现缩略图缓存机制
### Risks / Trade-offs
- **[磁盘空间使用]** → 添加用户配置选项:最大历史记录数量限制
- **[大量历史记录性能]** → 实现分页加载和虚拟滚动
- **[跨平台路径处理]** → 使用 `pathlib.Path` 确保跨平台兼容性
- **[UI响应性]** → 使用异步加载避免界面冻结
### Migration Plan
1. **Phase 1**: 实现 `HistoryManager` 类和基础存储逻辑
2. **Phase 2**: 修改现有图片生成流程,添加自动保存
3. **Phase 3**: 开发历史记录界面和交互功能
4. **Phase 4**: 添加配置选项和性能优化
**Rollback plan**: 所有功能都是增量添加,移除新功能不会影响现有系统稳定性。
### Open Questions
- 历史记录的默认数量上限应该是多少?
- 是否需要历史记录的搜索功能?
- 是否需要支持历史记录的导入/导出?
- 如何处理参考图片的版权和隐私问题?
### Technical Architecture Details
#### HistoryManager Class Structure
```python
class HistoryManager:
def __init__(self, base_path: Path)
def save_generation(self, image_bytes, prompt, references, params)
def load_history_index(self) -> List[HistoryItem]
def get_history_item(self, timestamp: str) -> HistoryItem
def delete_history_item(self, timestamp: str) -> bool
def cleanup_old_records(self, max_count: int)
```
#### Data Models
```python
@dataclass
class HistoryItem:
timestamp: str
prompt: str
generated_image_path: Path
reference_image_paths: List[Path]
aspect_ratio: str
image_size: str
model: str
created_at: datetime
```
#### Integration Points
- **ImageGenerationWorker**: 修改 `on_image_generated` 回调
- **ImageGeneratorWindow**: 添加历史记录标签页和相关UI组件
- **Configuration**: 扩展 `config.json` 添加历史记录相关配置
\ No newline at end of file
# Change: Add Image History Management
## Why
用户需要一个图片历史记录功能来管理和查看之前生成的图片,目前每次生成图片后都需要手动下载,缺乏历史记录管理和本地图片库功能。
## What Changes
- 添加图片自动下载功能:在图片生成成功后自动保存到本地 `/images/` 目录
- 创建历史记录管理系统:按时间戳存储图片和相关元数据(prompt、参考图等)
- 新增历史记录界面:在主界面添加历史记录标签页,展示所有历史生成记录
- 实现本地图片加载:允许用户点击历史记录中的缩略图来加载和查看完整图片
## Impact
- **Affected specs**:
- `image-generation` (MODIFIED: 添加自动下载和历史记录保存)
- `user-interface` (MODIFIED: 添加历史记录标签页)
- **Affected code**:
- `image_generator.py:937-944` (修改 `on_image_generated` 方法添加自动保存)
- `image_generator.py:1002-1020` (修改 `download_image` 方法)
- 新增 `HistoryManager` 类和相关历史记录管理逻辑
- 主界面UI修改:添加历史记录标签页和展示组件
\ No newline at end of file
## ADDED Requirements
### Requirement: Automatic Image History Storage
The system SHALL automatically save all generated images and their metadata to local storage when image generation is successful.
#### Scenario: Image generation auto-save
- **WHEN** image generation is completed successfully
- **THEN** the system SHALL automatically save the generated image to a timestamped directory
- **AND** save the prompt text and generation parameters
- **AND** copy any reference images used
- **AND** update the global history index
#### Scenario: Directory structure creation
- **WHEN** creating a new history record
- **THEN** the system SHALL create a directory named with YYYYMMDDHHMMSS timestamp format
- **AND** ensure the directory structure is: `/images/{timestamp}/generated.png, reference1.jpg, reference2.jpg, metadata.json`
### Requirement: History Record Management
The system SHALL provide functionality to manage and browse image generation history.
#### Scenario: History index loading
- **WHEN** the application starts or the history tab is opened
- **THEN** the system SHALL load the history index from `history_index.json`
- **AND** display a list of all historical generation records sorted by timestamp
#### Scenario: History record deletion
- **WHEN** the user selects a history record and chooses to delete it
- **THEN** the system SHALL remove the record from the index
- **AND** delete the corresponding directory and all its files
- **AND** confirm the deletion action with the user
### Requirement: History Browser Interface
The system SHALL provide a user interface to browse and interact with image generation history.
#### Scenario: History list display
- **WHEN** the history tab is opened
- **THEN** the system SHALL display a list of historical records
- **AND** show thumbnail previews of generated images
- **AND** display prompt text, generation date, and parameters for each record
#### Scenario: History item selection
- **WHEN** the user clicks on a history item
- **THEN** the system SHALL load the corresponding image into the main preview area
- **AND** display the original prompt text in the prompt input field
- **AND** restore the generation parameters (aspect ratio, size, model)
### Requirement: Local Image Loading
The system SHALL enable loading previously generated images from local history.
#### Scenario: Load image from history
- **WHEN** a history item is selected
- **THEN** the system SHALL load the full-resolution image from local storage
- **AND** display it in the preview area with the same functionality as newly generated images
- **AND** enable download and full-screen viewing options
#### Scenario: Reference image restoration
- **WHEN** loading a history item that had reference images
- **THEN** the system SHALL display the reference images in the reference area
- **AND** allow the user to modify or remove them before generating new images
### Requirement: Configuration and Settings
The system SHALL provide configuration options for history management.
#### Scenario: History storage location
- **WHEN** the application starts
- **THEN** the system SHALL create the images directory in the application's data directory
- **AND** allow users to configure a custom storage location through settings
#### Scenario: History limits configuration
- **WHEN** the number of history records exceeds the configured limit
- **THEN** the system SHALL automatically remove the oldest records
- **AND** provide user settings to configure the maximum number of history records
### Requirement: History Data Persistence
The system SHALL ensure history data persists across application sessions.
#### Scenario: Data persistence
- **WHEN** the application is closed and reopened
- **THEN** the system SHALL preserve all saved history records
- **AND** maintain the same directory structure and file organization
- **AND** recover gracefully if the history index file is corrupted
## MODIFIED Requirements
### Requirement: Image Generation Workflow
The system SHALL integrate history saving into the existing image generation workflow.
#### Scenario: Enhanced image generation completion
- **WHEN** image generation is completed successfully
- **THEN** the system SHALL perform all existing success behaviors (display image, enable download)
- **AND** additionally save the image and metadata to local history
- **AND** update the history index with the new record
#### Scenario: Error handling in history saving
- **WHEN** history saving encounters an error
- **THEN** the system SHALL not affect the image generation success state
- **AND** log the error without interrupting user experience
- **AND** continue with normal image display and download functionality
\ No newline at end of file
## 1. 核心数据结构和存储
- [x] 1.1 创建 `HistoryManager` 类,负责历史记录的管理
- [x] 1.2 设计历史记录数据结构:时间戳、prompt、参考图路径、生成图路径
- [x] 1.3 实现本地文件存储逻辑:`/images/YYYYMMDDHHMMSS/` 目录结构
- [x] 1.4 创建历史记录索引文件 `history_index.json`
## 2. 图片自动下载和保存
- [x] 2.1 修改 `on_image_generated()` 方法,在生成成功后自动保存图片
- [x] 2.2 实现参考图片的本地复制和保存
- [x] 2.3 创建元数据文件 `metadata.json` 保存prompt和参数信息
- [x] 2.4 更新历史记录索引文件
## 3. 历史记录管理功能
- [x] 3.1 实现 `HistoryManager.load_history()` 方法加载历史记录
- [x] 3.2 实现 `HistoryManager.get_history_item()` 方法获取单个历史记录
- [x] 3.3 实现 `HistoryManager.delete_history_item()` 方法删除历史记录
- [x] 3.4 实现历史记录的搜索和过滤功能
## 4. 历史记录界面开发
- [x] 4.1 在主界面添加"历史记录"标签页
- [x] 4.2 创建历史记录列表组件,显示缩略图和基本信息
- [x] 4.3 实现历史记录项的点击加载功能
- [x] 4.4 添加历史记录的右键菜单(删除、导出等)
## 5. 图片展示和交互
- [x] 5.1 实现从历史记录加载图片到预览区域
- [x] 5.2 创建历史记录详情查看功能(显示完整prompt、参数等)
- [x] 5.3 实现历史记录图片的双击全屏查看
- [x] 5.4 添加历史记录图片的批量导出功能
## 6. 配置和设置
- [x] 6.1 添加历史记录存储路径配置选项
- [x] 6.2 实现历史记录数量限制和自动清理设置
- [x] 6.3 添加历史记录功能的启用/禁用开关
## 7. 测试和验证
- [x] 7.1 测试图片生成后自动保存到历史记录
- [x] 7.2 测试历史记录界面的加载和显示
- [x] 7.3 测试从历史记录加载图片功能
- [x] 7.4 测试历史记录的删除和清理功能
- [x] 7.5 验证大量历史记录下的性能表现
## 8. 文档和部署
- [ ] 8.1 更新README.md,添加历史记录功能说明
- [ ] 8.2 创建历史记录功能的使用指南
- [ ] 8.3 测试在不同操作系统下的兼容性
\ No newline at end of file
## Design Document: Simple Logging System
### Context
当前应用缺乏基本的日志记录功能,当出现问题时难以诊断。需要建立简单实用的日志系统来记录关键事件和错误信息。
### Goals / Non-Goals
**Goals:**
- 提供基本的日志记录功能
- 记录关键操作和错误信息
- 实现简单的日志文件管理
- 使用标准库,保持简单
- 帮助快速定位和解决问题
**Non-Goals:**
- 不实现复杂的日志分析功能
- 不集成第三方日志服务
- 不实现实时日志监控
- 不设计复杂的日志分类系统
### Decisions
#### 1. 日志框架选择
**Decision**: 使用 Python 标准库 `logging` 模块
- **Why**: 标准库提供足够的基础功能,无额外依赖
- **Alternatives considered**: print语句、简单的文件写入
- **Chosen approach**: Python logging + 基础配置
#### 2. 日志文件结构
**Decision**: 单一日志文件
```
logs/
└── app.log # 统一的应用日志文件
```
#### 3. 日志轮转策略
**Decision**: 基于文件大小的简单轮转
- 最大文件大小:10MB
- 保留最近 5 个历史文件
- 不使用压缩,保持简单
#### 4. 日志级别策略
- **INFO**: 记录正常操作流程
- **WARNING**: 记录潜在问题
- **ERROR**: 记录操作失败和错误
- **DEBUG**: 开发时可启用详细信息
### Technical Architecture Details
#### 简化的日志系统
```
SimpleLogger
├── 初始化基础配置
├── 创建单一logger
├── 设置简单格式化器
└── 实现基础轮转
格式化器
├── 时间戳
├── 日志级别
├── 模块名称
└── 消息内容
```
#### 关键日志记录点
1. **应用启动**: 应用启动、配置加载
2. **用户认证**: 登录成功/失败、认证错误
3. **配置操作**: 配置文件读取错误
4. **数据库连接**: 连接成功/失败、查询错误
5. **图片生成**: API调用成功/失败、生成错误
6. **文件操作**: 图片保存成功/失败、历史记录错误
#### 性能考虑
- 使用标准库的优化性能
- 简单格式减少格式化开销
- 合理的轮转避免大文件问题
- 关键操作才记录,避免过度日志
### Integration Points
#### 模块集成
- **DatabaseManager**: 添加基础数据库日志
- **ImageGeneratorWindow**: 添加关键操作日志
- **ImageGenerationWorker**: 添加生成过程日志
- **HistoryManager**: 添加文件操作日志
#### 配置集成
```json
{
"logging": {
"enabled": true,
"level": "INFO",
"max_file_size_mb": 10
}
}
```
### Implementation Plan
1. **Step 1**: 创建简单的日志初始化函数
2. **Step 2**: 集成到 DatabaseManager
3. **Step 3**: 集成到图片生成模块
4. **Step 4**: 集成到错误处理
5. **Step 5**: 测试和验证
### Design Principles
- 简单实用:只记录必要信息
- 不影响性能:关键操作才记录
- 易于查看:日志文件直接可读
- 维护简单:最小化代码复杂度
\ No newline at end of file
# Change: Add Simple Logging System
## Why
当前应用缺乏基本的日志记录功能,当用户遇到API错误、配置问题、图片生成失败等情况时,开发者无法获得基本的诊断信息。添加简单的日志系统将帮助快速定位和解决常见问题。
## What Changes
- 添加简单的日志记录功能,统一输出到 `logs/app.log`
- 记录关键事件:应用启动、用户登录、图片生成、错误信息
- 使用标准Python logging模块,保持简单实用
- 实现基本的日志文件轮转(大小限制)
- 记录基本的错误信息和操作状态
## Impact
- **Affected specs**:
- `logging-system` (ADDED: 基础日志记录功能)
- `error-handling` (MODIFIED: 添加基本错误日志)
- `user-operations` (MODIFIED: 记录关键操作日志)
- **Affected code**:
- 全局添加简单日志初始化
- DatabaseManager 类添加基础数据库日志
- ImageGeneratorWindow 类添加关键操作日志
- ImageGenerationWorker 类添加生成过程日志
- 主要错误处理函数添加日志记录
\ No newline at end of file
## ADDED Requirements
### Requirement: Simple Logging System
The system SHALL provide a simple logging system that records key application events and errors for basic troubleshooting.
#### Scenario: Application initialization logging
- **WHEN** the application starts
- **THEN** the system SHALL log startup information and configuration loading
- **AND** SHALL record logging system initialization
- **AND** SHALL note the application version and platform
#### Scenario: Basic logging classification
- **WHEN** logging events occur
- **THEN** the system SHALL categorize logs into appropriate levels (INFO, WARNING, ERROR, DEBUG)
- **AND** SHALL write all logs to a single app.log file
- **AND** SHALL maintain consistent formatting across log entries
### Requirement: Basic Log File Management
The system SHALL implement simple log file management with size-based rotation.
#### Scenario: Log file creation and rotation
- **WHEN** the application starts
- **THEN** the system SHALL create a logs/ directory in the application folder
- **AND** SHALL create a single app.log file for all logs
- **AND** SHALL implement file rotation when file reaches 10MB size limit
- **AND** SHALL keep the 5 most recent log files
### Requirement: Database Operation Logging
The system SHALL record basic database-related operations for simple debugging.
#### Scenario: Database connection logging
- **WHEN** attempting to connect to the database
- **THEN** the system SHALL log connection attempts and results
- **AND** SHALL record basic database information
- **AND** SHALL log connection failures with error messages
#### Scenario: Database error logging
- **WHEN** database operations fail
- **THEN** the system SHALL log the error type and basic information
- **AND** SHALL record the operation that failed
- **AND** SHALL avoid logging sensitive database data
### Requirement: User Operation Logging
The system SHALL log key user actions for troubleshooting user experience issues.
#### Scenario: User authentication logging
- **WHEN** users attempt to log in
- **THEN** the system SHALL log login attempts and results
- **AND** SHALL record authentication failures with basic error info
- **AND** SHALL not log sensitive user credentials
#### Scenario: Image generation logging
- **WHEN** users generate images
- **THEN** the system SHALL log generation requests and basic parameters
- **AND** SHALL record API call results
- **AND** SHALL log generation failures with error information
### Requirement: Error Handling Logging
The system SHALL provide basic error logging for troubleshooting common issues.
#### Scenario: Basic exception logging
- **WHEN** key exceptions occur
- **THEN** the system SHALL log the exception type and message
- **AND** SHALL record the operation context
- **AND** SHALL avoid overly detailed stack traces
#### Scenario: Configuration error logging
- **WHEN** configuration loading fails
- **THEN** the system SHALL log the configuration error
- **AND** SHALL record the file path and error details
- **AND** SHALL provide fallback behavior information
### Requirement: Simple Configuration
The system SHALL provide basic logging configuration options.
#### Scenario: Logging configuration management
- **WHEN** the application starts
- **THEN** the system SHALL load logging settings from config.json
- **AND** SHALL support enabling/disabling the logging system
- **AND** SHALL support basic log level configuration
#### Scenario: Log level control
- **WHEN** different logging needs arise
- **THEN** the system SHALL support switching between INFO, WARNING, ERROR levels
- **AND** SHALL allow enabling DEBUG level for development
- **AND** SHALL apply configuration changes immediately
### Requirement: Cross-Platform Compatibility
The logging system SHALL work consistently across Windows, macOS, and Linux platforms.
#### Scenario: Simple cross-platform behavior
- **WHEN** running on different operating systems
- **THEN** the system SHALL use standard file permissions
- **AND** SHALL handle basic path and encoding issues
- **THEN** SHALL create logs in application directory
#### Scenario: Log file readability
- **WHEN** users need to view log files
- **THEN** the system SHALL ensure log files are readable by text editors
- **AND** SHALL use UTF-8 encoding for character support
- **AND** SHALL keep log format simple and clear
\ No newline at end of file
## 1. 简单日志系统实现
- [ ] 1.1 创建简单的日志初始化函数
- [ ] 1.2 实现单一日志文件(logs/app.log)
- [ ] 1.3 设置基础日志格式化器
- [ ] 1.4 实现简单的文件轮转(10MB大小限制)
- [ ] 1.5 创建 logs/ 目录
## 2. 基础日志级别
- [ ] 2.1 实现 INFO、WARNING、ERROR 三个主要级别
- [ ] 2.2 可选 DEBUG 级别用于开发调试
- [ ] 2.3 实现简单的日志级别控制
- [ ] 2.4 添加控制台输出选项
- [ ] 2.5 实现日志启用/禁用开关
## 3. 集成到关键模块
- [ ] 3.1 DatabaseManager 集成:记录连接状态和错误
- [ ] 3.2 ImageGeneratorWindow 集成:记录用户关键操作
- [ ] 3.3 ImageGenerationWorker 集成:记录API调用结果
- [ ] 3.4 HistoryManager 集成:记录文件操作状态
- [ ] 3.5 主函数集成:记录应用启动和退出
## 4. 错误处理日志
- [ ] 4.1 为主要异常添加基本日志记录
- [ ] 4.2 记录关键错误信息(无需完整堆栈)
- [ ] 4.3 添加配置加载错误日志
- [ ] 4.4 记录网络和API调用错误
- [ ] 4.5 记录文件操作失败信息
## 5. 配置集成
- [ ] 5.1 在 config.json 中添加基础日志配置
- [ ] 5.2 实现日志启用/禁用开关
- [ ] 5.3 添加日志级别配置选项
- [ ] 5.4 实现日志文件大小限制配置
- [ ] 5.5 集成日志配置到启动流程
## 6. 基础性能优化
- [ ] 6.1 确保日志不阻塞主线程
- [ ] 6.2 只在关键操作时记录日志
- [ ] 6.3 使用简单的日志格式
- [ ] 6.4 避免过度详细的日志记录
- [ ] 6.5 测试日志对性能的影响
## 7. 测试和验证
- [ ] 7.1 测试日志文件创建和基本写入
- [ ] 7.2 验证日志文件轮转功能
- [ ] 7.3 测试不同级别日志输出
- [ ] 7.4 验证错误日志记录功能
- [ ] 7.5 确保日志不影响正常功能
## 8. 简单文档
- [ ] 8.1 创建日志系统简要说明
- [ ] 8.2 编写常见问题排查指南
- [ ] 8.3 创建日志配置说明
- [ ] 8.4 编写基本调试方法
- [ ] 8.5 更新README中的故障排查部分
\ No newline at end of file
## Design Document: Standalone History Browsing Interface
### Context
当前历史记录实现允许用户双击历史项目将其加载到图片生成界面,但这会干扰用户的当前工作状态。用户希望历史记录作为纯粹的浏览功能,直接在历史记录界面中查看完整信息。
### Goals / Non-Goals
**Goals:**
- 提供独立的历史记录浏览功能
- 在历史记录界面直接显示prompt、参考图和生成图
- 支持prompt文本复制功能
- 保持与图片生成功能的完全隔离
- 提供直观的图片预览和详情展示
**Non-Goals:**
- 不再支持从历史记录加载到生成界面
- 不修改现有的图片生成工作流程
- 不增加复杂的编辑功能
### Decisions
#### 1. 界面重构方案
**Decision**: 重新设计历史记录标签页为详情浏览模式
- **Why**: 避免界面间的操作干扰,提供更清晰的用户体验
- **Alternatives considered**: 弹窗详情、侧边栏详情页
- **Chosen approach**: 在历史记录标签页内实现完整的详情显示
#### 2. 信息展示布局
**Decision**: 采用上下布局:历史列表在上(显示生成图预览),详情展示在下
- **Why**: 类似文件管理器的熟悉交互模式,预览图让用户快速识别内容
- **Alternatives considered**: 左右分栏、全屏切换、纯列表模式
- **Chosen approach**: 固定比例的上下分割布局,列表项显示生成图缩略图
#### 3. 交互方式
**Decision**: 单击选择历史项显示详情,双击或右键提供额外操作
- **Why**: 提供清晰的选择反馈和操作入口
- **Alternatives considered**: 仅单击、仅右键菜单
- **Chosen approach**: 简单的单击选择 + 右键菜单操作
### Technical Architecture Details
#### 新的UI组件结构
```
历史记录标签页
├── 工具栏 (刷新、清空、删除)
├── 分割器 (QSplitter)
│ ├── 上部: 历史记录列表 (QListWidget)
│ │ ├── 每个列表项显示:
│ │ │ ├── 生成图片缩略图 (120x120)
│ │ │ ├── 时间戳
│ │ │ └── Prompt前20个字符
│ │ └── 悬停显示完整信息
│ └── 下部: 详情展示区域 (QWidget)
│ ├── Prompt区域 (QTextEdit + 复制按钮)
│ ├── 参数信息 (宽高比、尺寸)
│ ├── 参考图片区域 (QScrollArea)
│ └── 生成图片大预览 (QLabel)
```
#### 数据流设计
1. **选择历史项**: 用户点击列表项 → 加载详细数据 → 更新详情区域
2. **复制Prompt**: 点击复制按钮 → 复制到剪贴板 → 显示成功提示
3. **删除操作**: 右键菜单删除 → 确认对话框 → 更新列表和详情
### Risks / Trade-offs
- **[界面复杂度]** → 通过清晰的布局和分组来管理
- **[图片加载性能]** → 实现延迟加载和缓存机制
- **[用户体验]** → 提供清晰的视觉反馈和操作提示
### Implementation Plan
1. **Phase 1**: 移除现有的加载到界面功能
2. **Phase 2**: 重新设计历史记录标签页UI
3. **Phase 3**: 实现详情展示功能
4. **Phase 4**: 添加复制和操作功能
5. **Phase 5**: 测试和优化
### Open Questions
- 历史记录列表和详情区域的比例应该是多少?
- 是否需要添加历史记录的搜索功能?
- 如何处理大量历史记录时的性能问题?
\ No newline at end of file
# Change: Refactor History Browsing to Standalone Interface
## Why
当前的历史记录实现会干扰原有的图片生成界面,用户希望历史记录作为一个独立的浏览界面,直接显示prompt、参考图和生成图,而不需要加载到原有界面中,避免操作冲突和界面混乱。
## What Changes
- 重新设计历史记录界面为独立的详情浏览模式
- 移除历史记录加载到原有界面的功能
- 在历史记录标签页中直接显示完整信息:
- Prompt文本(支持复制)
- 参考图片缩略图
- 生成图片大图预览
- 保持两个功能完全独立,互不干扰
## Impact
- **Affected specs**:
- `history-interface` (MODIFIED: 改为独立浏览模式)
- `user-interface` (MODIFIED: 移除界面间加载功能)
- **Affected code**:
- `image_generator.py:1447-1484` (移除 load_history_item 方法中加载到界面的逻辑)
- `image_generator.py:1033` (重新设计历史记录标签页UI)
- 新增独立的历史记录详情显示组件
\ No newline at end of file
## MODIFIED Requirements
### Requirement: Standalone History Browsing Interface
The system SHALL provide a standalone history browsing interface that displays complete information without interfering with the image generation interface.
#### Scenario: History item selection for detail viewing
- **WHEN** the user clicks on a history item
- **THEN** the system SHALL display detailed information in the details panel
- **AND** SHALL not modify any controls in the image generation interface
- **AND** SHALL show the complete prompt text, reference images, and generated image
#### Scenario: Independent interface operation
- **WHEN** the user switches between generation tab and history tab
- **THEN** the system SHALL preserve the state of each tab independently
- **AND** SHALL not load history data into generation controls
- **AND** SHALL maintain separate user contexts for each tab
## REMOVED Requirements
### Requirement: History Item Loading to Generation Interface
**Reason**: This feature interferes with the user's current work state and creates confusing interactions. Users want history as a pure browsing feature.
**Migration**: Replace with standalone detail viewing within the history tab.
## ADDED Requirements
### Requirement: History List with Image Previews
The system SHALL display generated image thumbnails directly in the history list for quick visual identification.
#### Scenario: History item thumbnail display
- **WHEN** the history list is loaded
- **THEN** the system SHALL display a 120x120 pixel thumbnail of each generated image
- **AND** SHALL include the timestamp and prompt preview (first 20 characters)
- **AND** SHALL maintain consistent thumbnail sizing and aspect ratio
#### Scenario: Image thumbnail loading
- **WHEN** loading history items
- **THEN** the system SHALL generate thumbnails efficiently
- **AND** SHALL show loading indicators for slow-loading images
- **AND** SHALL handle missing or corrupted image files gracefully
### Requirement: Prompt Text Display and Copy
The system SHALL display the full prompt text for each history item with copy functionality.
#### Scenario: Prompt text display
- **WHEN** a history item is selected
- **THEN** the system SHALL display the complete prompt text in a dedicated area
- **AND** SHALL format the text for easy reading
- **AND** SHALL show prompt length and creation time
#### Scenario: Prompt text copying
- **WHEN** the user clicks the copy button
- **THEN** the system SHALL copy the full prompt text to clipboard
- **AND** SHALL show a success confirmation message
- **AND** SHALL maintain the original prompt formatting
### Requirement: Reference Images Gallery
The system SHALL display reference images used in the generation process.
#### Scenario: Reference images display
- **WHEN** a history item with reference images is selected
- **THEN** the system SHALL display all reference images as thumbnails
- **AND** SHALL allow viewing full-size images on double-click
- **AND** SHALL show "No reference images" when none exist
#### Scenario: Reference image interaction
- **WHEN** the user double-clicks a reference image thumbnail
- **THEN** the system SHALL open the image in system default viewer
- **AND** SHALL handle missing image files gracefully
### Requirement: Generated Image Preview
The system SHALL provide a large preview of the generated image within the history interface.
#### Scenario: Generated image display
- **WHEN** a history item is selected
- **THEN** the system SHALL display the generated image in a large preview area
- **AND** SHALL scale the image appropriately for the available space
- **AND** SHALL maintain image aspect ratio
#### Scenario: Generated image interaction
- **WHEN** the user double-clicks the generated image preview
- **THEN** the system SHALL open the image in system default viewer
- **AND** SHALL handle missing image files with appropriate error messages
### Requirement: History Item Details Panel
The system SHALL provide a comprehensive details panel for selected history items.
#### Scenario: Details panel layout
- **WHEN** the history tab is opened
- **THEN** the system SHALL display a split-view interface
- **AND** SHALL show history list with image previews in the upper portion
- **AND** SHALL show details panel in the lower portion
- **AND** SHALL maintain responsive proportions
#### Scenario: Enhanced hover information
- **WHEN** the user hovers over a history list item
- **THEN** the system SHALL display comprehensive tooltip information
- **AND** SHALL include full prompt text, timestamp, aspect ratio, and image size
- **AND** SHALL provide quick visual preview without requiring selection
#### Scenario: Empty state handling
- **WHEN** no history item is selected
- **THEN** the system SHALL display helpful placeholder text
- **AND** SHALL guide user to select an item for details
- **AND** SHALL maintain a clean, uncluttered interface
### Requirement: Independent Tab Management
The system SHALL ensure complete independence between generation and history tabs.
#### Scenario: Tab switching preservation
- **WHEN** the user switches between tabs
- **THEN** the system SHALL preserve all input fields and selections
- **AND** SHALL not transfer data between interfaces
- **AND** SHALL maintain separate operational contexts
#### Scenario: Concurrent operation support
- **WHEN** a history item is being viewed
- **THEN** the user SHALL still be able to work in the generation tab
- **AND** SHALL not have their work interrupted by history browsing
- **AND** SHALL experience smooth tab transitions
\ No newline at end of file
## 1. 移除干扰性功能
- [ ] 1.1 移除历史记录双击加载到原有界面的功能
- [ ] 1.2 移除 load_history_item 方法中的界面设置逻辑
- [ ] 1.3 更新历史记录项的交互提示和工具提示
- [ ] 1.4 保留右键菜单中的删除和文件管理器功能
## 2. 重新设计历史记录UI
- [ ] 2.1 重新设计 setup_history_tab 方法,添加分割器布局
- [ ] 2.2 创建历史记录详情展示区域
- [ ] 2.3 修改历史列表项显示:添加生成图缩略图+时间戳+Prompt预览
- [ ] 2.4 添加Prompt文本显示区域和复制按钮
- [ ] 2.5 创建参考图片和生成图片的预览区域
- [ ] 2.6 设置合适的布局比例和样式
- [ ] 2.7 优化悬停提示信息,包含完整详情
## 3. 实现详情展示功能
- [ ] 3.1 实现单击历史项显示详情的逻辑
- [ ] 3.2 实现Prompt文本的显示和格式化
- [ ] 3.3 实现参考图片缩略图的加载和显示
- [ ] 3.4 实现生成图片的大图预览功能
- [ ] 3.5 添加生成参数信息显示
## 4. 添加交互功能
- [ ] 4.1 实现Prompt文本复制功能
- [ ] 4.2 添加复制成功的视觉反馈
- [ ] 4.3 实现参考图片和生成图片的双击放大查看
- [ ] 4.4 更新右键菜单,移除加载选项
- [ ] 4.5 添加刷新详情的功能
## 5. 优化用户体验
- [ ] 5.1 实现无选中状态时的提示信息
- [ ] 5.2 添加加载状态的指示器
- [ ] 5.3 优化图片加载性能和缓存
- [ ] 5.4 调整界面布局的响应式设计
- [ ] 5.5 添加键盘快捷键支持
## 6. 测试和验证
- [ ] 6.1 测试历史记录浏览的独立性
- [ ] 6.2 验证不会干扰图片生成界面
- [ ] 6.3 测试复制功能的准确性
- [ ] 6.4 验证图片显示的正确性
- [ ] 6.5 测试大量历史记录的性能表现
## 7. 文档和清理
- [ ] 7.1 更新历史记录功能的使用说明
- [ ] 7.2 清理不再使用的代码和方法
- [ ] 7.3 验证所有功能正常工作
- [ ] 7.4 确保UI一致性和用户体验
\ No newline at end of file
#!/usr/bin/env python3
"""
测试配置文件加载逻辑
"""
import json
import sys
import tempfile
from pathlib import Path
# 模拟PyInstaller环境
sys.frozen = True
sys._MEIPASS = "C:\\Users\\Shady\\PycharmProjects\\GoogleNanoBananaApp" # 模拟路径
# 导入配置加载逻辑
def test_config_loading():
"""测试配置文件加载的多种路径"""
# 模拟exe所在目录的config.json
exe_dir_config = Path(sys.executable).parent / 'config.json' if hasattr(sys, 'executable') else None
# 模拟_MEIPASS目录的config.json
meipass_config = Path(sys._MEIPASS) / 'config.json'
print(f"模拟PyInstaller环境:")
print(f" sys.frozen: {getattr(sys, 'frozen', False)}")
print(f" sys._MEIPASS: {sys._MEIPASS}")
# 测试各个路径
test_paths = []
if hasattr(sys, 'executable'):
test_paths.append(("exe目录", Path(sys.executable).parent / 'config.json'))
test_paths.append(("_MEIPASS目录", Path(sys._MEIPASS) / 'config.json'))
test_paths.append(("当前目录", Path('.') / 'config.json'))
for name, path in test_paths:
if path.exists():
print(f"[OK] {name}: {path} - 存在")
try:
with open(path, 'r', encoding='utf-8') as f:
config = json.load(f)
db_config = config.get("db_config")
if db_config:
print(f" [OK] 包含数据库配置: {db_config.get('host', 'N/A')}")
else:
print(f" [FAIL] 未包含数据库配置")
except Exception as e:
print(f" [FAIL] 读取失败: {e}")
else:
print(f"[FAIL] {name}: {path} - 不存在")
if __name__ == "__main__":
test_config_loading()
\ No newline at end of file
......@@ -285,6 +285,13 @@ QPushButton[variant="ghost"]:hover {{
color: {c['accent']};
}}
/* 短暂成功反馈("已复制"等,2秒后回退)*/
QPushButton[variant="success-flash"] {{
background-color: {c['success']};
color: white;
border: 1px solid {c['success']};
}}
/* 链接按钮 — 像超链接 */
QPushButton[variant="link"] {{
background-color: transparent;
......@@ -601,6 +608,70 @@ QSplitter::handle:vertical {{
background: {c['bg_subtle']};
border-radius: {s['radius_md']};
}}
/* ========== 参考图拖拽区(drag_state property)========== */
#referenceImageDrop[drag_state="idle"] {{
border: 2px dashed {c['border_default']};
border-radius: {s['radius_md']};
background-color: {c['bg_subtle']};
}}
#referenceImageDrop[drag_state="idle"]:hover {{
border: 2px dashed {c['accent']};
background-color: {c['accent_subtle']};
}}
#referenceImageDrop[drag_state="active"] {{
border: 2px dashed {c['accent']};
border-radius: {s['radius_md']};
background-color: {c['accent_subtle']};
}}
/* ========== 已生成图片缩略图卡 ========== */
QLabel[role="thumb"] {{
border: 1px solid {c['border_default']};
border-radius: {s['radius_sm']};
background-color: {c['bg_surface']};
}}
QLabel[role="thumb_index"] {{
color: {c['text_secondary']};
font-size: {s['font_base']};
font-weight: 600;
}}
/* ========== 预览区生成图(result_label / generated_image_label)========== */
QLabel#previewImage {{
background-color: {c['bg_subtle']};
border: 1px solid {c['border_default']};
border-radius: {s['radius_md']};
color: {c['text_tertiary']};
}}
QLabel#previewImage[has_image="true"] {{
background-color: {c['bg_canvas']};
border-color: {c['border_default']};
}}
/* ========== 历史详情提示词显示框 ========== */
QLabel#promptDisplay {{
background-color: {c['bg_subtle']};
border: 1px solid {c['border_default']};
border-radius: {s['radius_md']};
padding: 8px 10px;
color: {c['text_primary']};
}}
/* ========== 缩略图删除按钮(20x20 紧凑)========== */
QPushButton#thumbDeleteBtn {{
background-color: {c['danger']};
color: white;
border: none;
border-radius: 3px;
padding: 0;
min-height: 0;
font-size: {s['font_xs']};
font-weight: 700;
}}
QPushButton#thumbDeleteBtn:hover {{
background-color: {c['danger_hover']};
}}
"""
......