41026f54 by shady

增加部分log日志文件处理

1 parent ab1a3122
......@@ -379,7 +379,8 @@ class HistoryManager:
# 保存生成的图片 - 使用PNG格式验证
generated_image_path = record_dir / "generated.png"
if not save_png_with_validation(str(generated_image_path), image_bytes):
# Pillow处理失败,回退到原始方法
#
# ¬,回退到原始方法
with open(generated_image_path, 'wb') as f:
f.write(image_bytes)
self.logger.warning(f"使用原始保存方法: {generated_image_path}")
......@@ -1679,6 +1680,7 @@ class ImageGeneratorWindow(QMainWindow):
def upload_images(self):
"""Upload reference images"""
self.logger.info("开始上传参考图片")
files, _ = QFileDialog.getOpenFileNames(
self,
"选择参考图片",
......@@ -1687,198 +1689,285 @@ class ImageGeneratorWindow(QMainWindow):
)
if files:
self.logger.info(f"选择了 {len(files)} 个文件: {files}")
valid_count = 0
for file_path in files:
try:
self.uploaded_images.append(file_path)
if self.validate_image_file(file_path):
self.uploaded_images.append(file_path)
valid_count += 1
self.logger.info(f"成功添加图片: {file_path}")
else:
self.logger.warning(f"图片验证失败: {file_path}")
except Exception as e:
self.logger.error(f"加载图片失败: {file_path}, 错误: {str(e)}", exc_info=True)
QMessageBox.critical(self, "错误", f"无法加载图片: {file_path}\n{str(e)}")
self.update_image_preview()
self.image_count_label.setText(f"已选择 {len(self.uploaded_images)} 张")
self.status_label.setText(f"● 已添加 {len(files)} 张参考图片")
self.status_label.setText(f"● 已添加 {valid_count} 张参考图片")
self.status_label.setStyleSheet("QLabel { color: #34C759; }")
self.logger.info(f"图片上传完成,有效图片: {valid_count} 张")
else:
self.logger.info("用户取消了图片选择")
def add_image_files(self, file_paths):
"""添加图像文件到上传列表(用于拖拽功能)"""
self.logger.info(f"开始处理拖拽文件,共 {len(file_paths) if file_paths else 0} 个")
if not file_paths:
self.logger.warning("拖拽文件列表为空")
return
added_count = 0
for file_path in file_paths:
self.logger.info(f"处理拖拽文件: {file_path}")
try:
if self.validate_image_file(file_path):
self.uploaded_images.append(file_path)
added_count += 1
self.logger.info(f"成功添加拖拽图片: {file_path}")
else:
self.logger.warning(f"无效的图像文件: {file_path}")
except Exception as e:
self.logger.error(f"添加图片失败: {file_path}, 错误: {str(e)}")
self.logger.error(f"添加图片失败: {file_path}, 错误: {str(e)}", exc_info=True)
if added_count > 0:
self.logger.info(f"拖拽添加成功,共 {added_count} 张图片")
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; }")
else:
self.logger.warning("没有找到有效的拖拽图片文件")
QMessageBox.warning(self, "警告", "没有找到有效的图片文件")
def add_clipboard_image(self, image):
"""添加剪贴板图像(用于拖拽和粘贴功能)"""
try:
self.logger.info("开始处理剪贴板图像")
# 生成临时文件名
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
temp_dir = Path(tempfile.gettempdir()) / "nano_banana_app"
temp_dir.mkdir(exist_ok=True)
self.logger.info(f"临时目录: {temp_dir}")
# 根据图像格式选择文件扩展名
file_extension = ".png" # 默认使用PNG格式
if image.format() == QImage.Format_RGB32:
file_extension = ".bmp"
elif image.format() == QImage.Format_RGB888:
file_extension = ".jpg"
# 统一使用PNG格式保存,确保跨平台兼容
file_extension = ".png"
temp_file_path = temp_dir / f"clipboard_{timestamp}{file_extension}"
# 保存图像到临时文件
if image.save(str(temp_file_path)):
# 尝试多种保存方式,确保兼容性
success = False
try:
# 方法1: 指定PNG格式
success = image.save(str(temp_file_path), "PNG")
self.logger.info(f"方法1保存结果: {success}")
except:
try:
# 方法2: 自动格式
success = image.save(str(temp_file_path))
self.logger.info(f"方法2保存结果: {success}")
except:
# 方法3: 转换格式再保存
converted_image = QImage(image)
success = converted_image.save(str(temp_file_path), "PNG")
self.logger.info(f"方法3保存结果: {success}")
if success and temp_file_path.exists():
self.uploaded_images.append(str(temp_file_path))
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.logger.info(f"剪贴板图片已保存到: {temp_file_path}")
self.logger.info(f"剪贴板图片已成功保存到: {temp_file_path}")
else:
self.logger.error("图片保存失败")
QMessageBox.critical(self, "错误", "无法保存剪贴板图片")
except Exception as e:
self.logger.error(f"添加剪贴板图片失败: {str(e)}")
self.logger.error(f"添加剪贴板图片失败: {str(e)}", exc_info=True)
QMessageBox.critical(self, "错误", f"添加剪贴板图片失败: {str(e)}")
def paste_from_clipboard(self):
"""从剪贴板粘贴图像"""
clipboard = QApplication.clipboard()
try:
self.logger.info("开始粘贴剪贴板图片")
clipboard = QApplication.clipboard()
# 获取剪贴板MIME数据
mime_data = clipboard.mimeData()
self.logger.info(f"剪贴板MIME类型: {[mime for mime in mime_data.formats()]}")
# 检查剪贴板中是否有图像
if mime_data.hasImage():
self.logger.info("检测到剪贴板中有图像数据")
image = clipboard.image()
# 检查剪贴板中是否有图像
if clipboard.mimeData().hasImage():
image = clipboard.image()
if not image.isNull():
self.add_clipboard_image(image)
if not image.isNull():
self.logger.info(f"图像尺寸: {image.width()}x{image.height()}, 格式: {image.format()}")
self.add_clipboard_image(image)
else:
self.logger.warning("剪贴板图像为空")
QMessageBox.information(self, "信息", "剪贴板中没有有效的图片")
else:
QMessageBox.information(self, "信息", "剪贴板中没有有效的图片")
else:
QMessageBox.information(self, "信息", "剪贴板中没有图片,请先复制一张图片")
self.logger.warning(f"剪贴板中没有图像,可用格式: {mime_data.formats()}")
QMessageBox.information(self, "信息", "剪贴板中没有图片,请先复制一张图片")
except Exception as e:
self.logger.error(f"粘贴剪贴板图片时发生错误: {str(e)}", exc_info=True)
QMessageBox.critical(self, "错误", f"粘贴失败: {str(e)}")
def validate_image_file(self, file_path: str) -> bool:
"""验证图像文件"""
self.logger.info(f"开始验证图像文件: {file_path}")
try:
# 检查文件是否存在
if not Path(file_path).exists():
self.logger.warning(f"文件不存在: {file_path}")
return False
# 检查文件扩展名
valid_extensions = {'.png', '.jpg', '.jpeg', '.gif', '.bmp', '.webp'}
if Path(file_path).suffix.lower() not in valid_extensions:
file_extension = Path(file_path).suffix.lower()
if file_extension not in valid_extensions:
self.logger.warning(f"不支持的文件格式: {file_extension}, 文件: {file_path}")
return False
# 尝试加载图像以验证文件完整性
pixmap = QPixmap(file_path)
if pixmap.isNull():
return False
# 检查文件大小(限制为10MB)
# 检查文件大小
file_size = Path(file_path).stat().st_size
self.logger.info(f"文件大小: {file_size} bytes, 格式: {file_extension}")
if file_size > 10 * 1024 * 1024: # 10MB
self.logger.warning(f"文件过大: {file_size} bytes, 文件: {file_path}")
QMessageBox.warning(self, "警告", f"图片文件过大: {file_path}\n请选择小于10MB的图片")
return False
if file_size == 0:
self.logger.warning(f"文件为空: {file_path}")
return False
# 尝试加载图像以验证文件完整性
self.logger.info(f"尝试加载图片验证完整性: {file_path}")
pixmap = QPixmap(file_path)
if pixmap.isNull():
self.logger.error(f"图片加载失败或损坏: {file_path}")
return False
self.logger.info(f"图片验证成功,尺寸: {pixmap.width()}x{pixmap.height()}, 文件: {file_path}")
return True
except Exception as e:
self.logger.error(f"图像文件验证失败: {file_path}, 错误: {str(e)}")
self.logger.error(f"图像文件验证异常: {file_path}, 错误: {str(e)}", exc_info=True)
return False
def keyPressEvent(self, event):
"""处理键盘事件"""
# 检测键盘组合键
key_text = event.text()
key_int = event.key()
modifiers = event.modifiers()
self.logger.info(f"键盘事件: key={key_int}, text='{key_text}', modifiers={modifiers}")
# Ctrl+V 粘贴
if event.key() == Qt.Key_V and event.modifiers() == Qt.ControlModifier:
self.logger.info("检测到 Ctrl+V 粘贴组合键")
self.paste_from_clipboard()
event.accept()
return
# Cmd+V 粘贴 (macOS)
elif event.key() == Qt.Key_V and event.modifiers() == Qt.MetaModifier:
self.logger.info("检测到 Cmd+V 粘贴组合键 (macOS)")
self.paste_from_clipboard()
event.accept()
return
self.logger.debug(f"未处理的键盘事件: {key_text}")
super().keyPressEvent(event)
def update_image_preview(self):
"""Update image preview thumbnails"""
# Clear existing previews
while self.img_layout.count() > 1: # Keep the stretch
item = self.img_layout.takeAt(0)
if item.widget():
item.widget().deleteLater()
# Add thumbnails
for idx, file_path in enumerate(self.uploaded_images):
try:
# Load and create thumbnail
pixmap = QPixmap(file_path)
pixmap = pixmap.scaled(100, 100, Qt.KeepAspectRatio, Qt.SmoothTransformation)
# Container
container = QWidget()
container_layout = QVBoxLayout()
container_layout.setContentsMargins(5, 5, 5, 5)
# Image label
img_label = QLabel()
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: 8pt; color: #666666; }")
info_layout.addWidget(index_label)
del_btn = QPushButton("✕")
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)
self.logger.info(f"更新图片预览,共 {len(self.uploaded_images)} 张图片")
try:
# Clear existing previews
while self.img_layout.count() > 1: # Keep the stretch
item = self.img_layout.takeAt(0)
if item.widget():
item.widget().deleteLater()
# Add thumbnails
for idx, file_path in enumerate(self.uploaded_images):
self.logger.info(f"创建第 {idx + 1} 张图片缩略图: {file_path}")
try:
# Load and create thumbnail
pixmap = QPixmap(file_path)
if pixmap.isNull():
self.logger.warning(f"无法加载图片进行预览: {file_path}")
continue
container_layout.addLayout(info_layout)
container.setLayout(container_layout)
pixmap = pixmap.scaled(100, 100, Qt.KeepAspectRatio, Qt.SmoothTransformation)
self.logger.info(f"缩略图创建成功: {pixmap.width()}x{pixmap.height()}")
# Container
container = QWidget()
container_layout = QVBoxLayout()
container_layout.setContentsMargins(5, 5, 5, 5)
# Image label
img_label = QLabel()
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: 8pt; color: #666666; }")
info_layout.addWidget(index_label)
del_btn = QPushButton("✕")
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)
container_layout.addLayout(info_layout)
container.setLayout(container_layout)
self.img_layout.insertWidget(self.img_layout.count() - 1, container)
self.logger.info(f"缩略图UI组件创建完成: {file_path}")
self.img_layout.insertWidget(self.img_layout.count() - 1, container)
except Exception as e:
print(f"Failed to create thumbnail for {file_path}: {e}")
except Exception as e:
self.logger.error(f"创建缩略图失败: {file_path}, 错误: {str(e)}", exc_info=True)
except Exception as e:
self.logger.error(f"更新图片预览失败: {str(e)}", exc_info=True)
def delete_image(self, index):
"""Delete an image by index"""
self.logger.info(f"尝试删除第 {index + 1} 张图片")
if 0 <= index < len(self.uploaded_images):
deleted_file = self.uploaded_images[index]
self.uploaded_images.pop(index)
self.logger.info(f"已删除图片: {deleted_file}")
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; }")
else:
self.logger.warning(f"无效的图片索引: {index}")
def update_saved_prompts_list(self):
"""Update the saved prompts dropdown"""
......@@ -2749,98 +2838,57 @@ class JewelryLibraryManager:
self.logger = logging.getLogger(__name__)
self.config_dir = config_dir
self.config_path = config_dir / "jewelry_library.json"
# 获取默认词库文件的路径
self.default_library_path = Path(__file__).parent / "data" / "default_jewelry_library.json"
self.library = self.load_library()
def load_library(self) -> Dict[str, List[str]]:
"""加载词库,不存在则创建默认词库"""
"""加载词库,优先使用用户配置,不存在则使用默认词库"""
if self.config_path.exists():
try:
with open(self.config_path, 'r', encoding='utf-8') as f:
library = json.load(f)
# 检查是否需要进行数据迁移
library = self._migrate_library_if_needed(library)
# 验证数据完整性,补全缺失的类别
needs_update = False
for category, default_items in DEFAULT_JEWELRY_LIBRARY.items():
if category not in library:
self.logger.warning(f"检测到缺失类别: {category},从默认配置补全")
library[category] = default_items.copy()
needs_update = True
elif not library[category] or len(library[category]) == 0:
self.logger.warning(f"检测到空类别: {category},从默认配置补全")
library[category] = default_items.copy()
needs_update = True
elif len(library[category]) < len(default_items) * 0.5:
# 如果类别数据量少于默认值的50%,认为数据不完整,使用默认数据
self.logger.warning(f"检测到类别 {category} 数据不完整(仅{len(library[category])}项,默认{len(default_items)}项),从默认配置补全")
library[category] = default_items.copy()
needs_update = True
# 如果数据被补全,保存更新后的配置
if needs_update:
self.logger.info("词库数据已补全,保存更新")
self.save_library(library)
self.logger.info(f"珠宝词库加载成功: {self.config_path}")
return library
except Exception as e:
self.logger.error(f"珠宝词库加载失败: {e},使用默认词库")
return DEFAULT_JEWELRY_LIBRARY.copy()
library = DEFAULT_JEWELRY_LIBRARY.copy()
# 尝试保存默认配置,覆盖损坏的文件
try:
self.save_library(library)
except:
pass
return library
else:
# 首次使用,创建默认词库
self.logger.info("未找到珠宝词库文件,创建默认词库")
# 首次使用,从代码中的默认配置创建用户配置文件
self.logger.info("首次使用,创建用户配置文件")
library = DEFAULT_JEWELRY_LIBRARY.copy()
self.save_library(library)
return library
def _migrate_library_if_needed(self, library: Dict[str, List[str]]) -> Dict[str, List[str]]:
"""检查并执行数据迁移"""
# 如果存在旧的"主石"字段,需要拆分
if "主石" in library:
self.logger.info("检测到旧版主石字段,执行数据迁移...")
# 创建新字段
shapes = []
materials = []
# 分析现有数据
for item in library["主石"]:
# 提取形状信息
shape = self._extract_shape(item)
if shape and shape not in shapes:
shapes.append(shape)
# 提取材质信息
material = self._extract_material(item)
if material and material not in materials:
materials.append(material)
# 更新词库
library["主石形状"] = shapes if shapes else DEFAULT_JEWELRY_LIBRARY["主石形状"]
library["主石材质"] = materials if materials else DEFAULT_JEWELRY_LIBRARY["主石材质"]
# 保留旧字段作为备份
# library.pop("主石", None) # 可以选择是否移除
# 保存迁移后的数据
self.save_library(library)
self.logger.info(f"数据迁移完成:形状 {len(shapes)} 个,材质 {len(materials)} 个")
return library
def _extract_shape(self, item: str) -> Optional[str]:
"""从主石描述中提取形状"""
# 直接从 DEFAULT_JEWELRY_LIBRARY 获取形状列表,确保同步
shapes = DEFAULT_JEWELRY_LIBRARY.get("主石形状", [])
for shape in shapes:
# 处理带括号的形状(如"子弹形(Baguette 子弹刻面)")
if "(" in shape:
shape_base = shape.split("(")[0]
if item.startswith(shape_base):
return shape_base
elif item.startswith(shape):
return shape
return None
def _extract_material(self, item: str) -> Optional[str]:
"""从主石描述中提取材质"""
# 直接从 DEFAULT_JEWELRY_LIBRARY 获取形状列表,确保同步
shapes = DEFAULT_JEWELRY_LIBRARY.get("主石形状", [])
for shape in shapes:
# 处理带括号的形状(如"子弹形(Baguette 子弹刻面)")
shape_base = shape.split("(")[0] if "(" in shape else shape
if item.startswith(shape_base):
material = item[len(shape_base):]
# 去除可能的连接词
if material.startswith("的"):
material = material[1:]
return material if material else None
return item # 如果没有形状前缀,返回整个字符串
def save_library(self, library: Dict[str, List[str]] = None):
"""保存词库到配置文件
"""保存词库到用户配置目录
Args:
library: 要保存的词库,如果为None则保存当前词库
......@@ -3096,6 +3144,8 @@ class StyleDesignerTab(QWidget):
self.prompt_preview.setReadOnly(True)
prompt_layout.addWidget(self.prompt_preview)
prompt_group.setLayout(prompt_layout)
prompt_group.setStyleSheet("font-size: 16px;")
content_row.addWidget(prompt_group, 2)
# Settings section
......