994a9ea2 by 柴进

增加款式设计的部分标签

1 parent 32c7b837
......@@ -3,8 +3,9 @@
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.venv" />
<excludeFolder url="file://$MODULE_DIR$/venv" />
</content>
<orderEntry type="jdk" jdkName="Python 3.11 (GoogleNanoBananaApp)" jdkType="Python SDK" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
<component name="PyDocumentationSettings">
......
......@@ -10,7 +10,7 @@ from PySide6.QtWidgets import (
QLabel, QLineEdit, QPushButton, QCheckBox, QTextEdit,
QComboBox, QScrollArea, QGroupBox, QFileDialog, QMessageBox,
QListWidget, QListWidgetItem, QTabWidget, QSplitter,
QMenu, QProgressBar
QMenu, QProgressBar, QInputDialog
)
from PySide6.QtCore import Qt, QThread, Signal, QSize, QTimer, QMimeData
from PySide6.QtGui import QPixmap, QFont, QIcon, QDesktopServices, QAction, QImage, QDragEnterEvent, QDropEvent
......@@ -25,6 +25,7 @@ import shutil
import tempfile
import platform
import logging
import random
from pathlib import Path
from google import genai
from google.genai import types
......@@ -1264,6 +1265,16 @@ class ImageGeneratorWindow(QMainWindow):
generation_tab = self.setup_generation_tab()
self.tab_widget.addTab(generation_tab, "图片生成")
# Create style designer tab
try:
jewelry_lib_manager = JewelryLibraryManager(self.get_config_dir())
style_designer_tab = StyleDesignerTab(jewelry_lib_manager, parent=self)
self.tab_widget.addTab(style_designer_tab, "款式设计")
self.logger.info("款式设计 Tab 创建成功")
except Exception as e:
self.logger.error(f"款式设计 Tab 创建失败: {e}")
# 即使失败也继续运行,不阻止应用启动
# Create history tab
history_tab = self.setup_history_tab()
self.tab_widget.addTab(history_tab, "历史记录")
......@@ -1286,7 +1297,7 @@ class ImageGeneratorWindow(QMainWindow):
# Upload button and count
upload_header = QHBoxLayout()
upload_btn = QPushButton("+ 添加图片")
upload_btn = QPushButton("添加图片")
upload_btn.clicked.connect(self.upload_images)
upload_header.addWidget(upload_btn)
......@@ -1357,7 +1368,7 @@ class ImageGeneratorWindow(QMainWindow):
self.saved_prompts_combo = QComboBox()
prompt_toolbar.addWidget(self.saved_prompts_combo)
delete_prompt_btn = QPushButton("🗑️ 删除")
delete_prompt_btn = QPushButton("删除")
delete_prompt_btn.clicked.connect(self.delete_saved_prompt)
prompt_toolbar.addWidget(delete_prompt_btn)
prompt_toolbar.addStretch()
......@@ -2090,7 +2101,7 @@ class ImageGeneratorWindow(QMainWindow):
processing_method = "格式转换" if processed_bytes != self.generated_image_bytes else "原始数据"
self.logger.info(f"图片下载完成 - 方法: {processing_method}, 文件: {file_path}, 大小: {file_size} 字节")
QMessageBox.information(self, "成功", f"图片已保存到:\n{file_path}\n\n文件大小: {file_size:,} 字节")
# 只使用状态栏提示,不显示弹窗
self.status_label.setText("● 图片已保存")
self.status_label.setStyleSheet("QLabel { color: #34C759; }")
except Exception as e:
......@@ -2527,6 +2538,874 @@ class ImageGenerationWorker(QThread):
self.error.emit(error_msg)
# ============================================================================
# 珠宝设计功能模块
# ============================================================================
# 默认珠宝词库(纯中文)
DEFAULT_JEWELRY_LIBRARY = {
"主石形状": [
"椭圆形",
"圆形",
"祖母绿形",
"梨形",
"垫形",
"公主方形",
"心形"
],
"主石材质": [
"黑发晶",
"莫桑石",
"红宝石",
"蓝宝石",
"绿碧玺",
"黄水晶带天然包裹体",
"钻石",
"粉红蓝宝石"
],
"金属": [
"14K黄金",
"18K玫瑰金",
"14K白金",
"铂金(PT950)",
"双色14K黄金+白金",
"950银镀铑",
"钛金属"
],
"花头形式": [
"花卉风格光环",
"经典圆形光环",
"复古风格米粒边光环",
"双层光环",
"几何六边形光环",
"非对称光环",
"简约爪镶无光环"
],
"戒臂结构": [
"扭转戒臂",
"分裂戒臂",
"刀刃戒臂",
"大教堂戒臂",
"交叉戒臂",
"直线平滑戒臂",
"三股编织戒臂"
],
"戒臂处理": [
"雕刻镂空花丝工艺",
"密钉镶戒臂",
"抛光平滑戒臂",
"浮雕雕刻",
"编织纹理",
"极简干净戒臂",
"穿孔镂空细节",
"锤纹处理"
],
"特殊元素": [
"凯尔特结图案",
"装饰艺术几何元素",
"自然风格叶子图案",
"复古米粒边细节",
"哥特式图案元素",
"天体图案(星星、月亮)",
"藤蔓装饰曲线",
"蝴蝶结装饰"
],
"辅石镶嵌": [
"密钉镶",
"微密钉镶",
"槽镶",
"珠粒镶",
"共爪镶",
"包镶",
"轨道镶"
]
}
class JewelryLibraryManager:
"""珠宝词库管理器"""
def __init__(self, config_dir: Path):
"""初始化词库管理器
Args:
config_dir: 配置文件目录
"""
self.logger = logging.getLogger(__name__)
self.config_dir = config_dir
self.config_path = config_dir / "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)
self.logger.info(f"珠宝词库加载成功: {self.config_path}")
return library
except Exception as e:
self.logger.error(f"珠宝词库加载失败: {e},使用默认词库")
return DEFAULT_JEWELRY_LIBRARY.copy()
else:
# 首次使用,创建默认词库
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]:
"""从主石描述中提取形状"""
shapes = ["椭圆形", "圆形", "祖母绿形", "梨形", "垫形", "公主方形", "心形"]
for shape in shapes:
if item.startswith(shape):
return shape
return None
def _extract_material(self, item: str) -> Optional[str]:
"""从主石描述中提取材质"""
shapes = ["椭圆形", "圆形", "祖母绿形", "梨形", "垫形", "公主方形", "心形"]
for shape in shapes:
if item.startswith(shape):
material = item[len(shape):]
# 去除可能的连接词
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则保存当前词库
"""
if library is None:
library = self.library
try:
self.config_dir.mkdir(parents=True, exist_ok=True)
with open(self.config_path, 'w', encoding='utf-8') as f:
json.dump(library, f, ensure_ascii=False, indent=2)
self.logger.info(f"珠宝词库保存成功: {self.config_path}")
except Exception as e:
self.logger.error(f"珠宝词库保存失败: {e}")
raise
def add_item(self, category: str, value: str):
"""添加词库项
Args:
category: 类别名称(如"主石")
value: 词条内容(纯中文)
"""
if category not in self.library:
self.library[category] = []
if value not in self.library[category]:
self.library[category].append(value)
self.save_library()
self.logger.info(f"添加词库项: {category} -> {value}")
else:
self.logger.warning(f"词库项已存在: {category} -> {value}")
def remove_item(self, category: str, value: str):
"""删除词库项
Args:
category: 类别名称
value: 词条内容
"""
if category in self.library and value in self.library[category]:
self.library[category].remove(value)
self.save_library()
self.logger.info(f"删除词库项: {category} -> {value}")
else:
self.logger.warning(f"词库项不存在: {category} -> {value}")
def reset_category(self, category: str):
"""恢复单个类别的默认词库
Args:
category: 类别名称
"""
if category in DEFAULT_JEWELRY_LIBRARY:
self.library[category] = DEFAULT_JEWELRY_LIBRARY[category].copy()
self.save_library()
self.logger.info(f"恢复默认词库: {category}")
else:
self.logger.warning(f"未知的类别: {category}")
def reset_all(self):
"""恢复所有类别的默认词库"""
self.library = DEFAULT_JEWELRY_LIBRARY.copy()
self.save_library()
self.logger.info("恢复所有默认词库")
class PromptAssembler:
"""Prompt 组装器(纯中文)"""
BASE_TEMPLATE = """一款高端精品珠宝戒指设计,主石为{主石},镶嵌于{金属}中。戒头采用{花头形式}围绕主石。戒臂采用{戒臂结构}设计,表面处理为{戒臂处理}。风格元素包括{特殊元素}。辅石采用{辅石镶嵌}增加光彩。高端珠宝渲染,干净的摄影棚光线,精准的金属抛光,强调宝石清晰度,不要出现手部。"""
@staticmethod
def assemble(form_data: Dict[str, str]) -> str:
"""智能组装 prompt,处理空值
Args:
form_data: 表单数据,key为字段名,value为选中的值
Returns:
组装后的 prompt 字符串
"""
# 提取字段值(支持新旧两种格式)
center_stone = form_data.get("主石", "").strip() # 向后兼容
# 新格式:拆分形状和材质
center_stone_shape = form_data.get("主石形状", "").strip()
center_stone_material = form_data.get("主石材质", "").strip()
metal = form_data.get("金属", "").strip()
halo_style = form_data.get("花头形式", "").strip()
shank_structure = form_data.get("戒臂结构", "").strip()
shank_treatment = form_data.get("戒臂处理", "").strip()
special_motifs = form_data.get("特殊元素", "").strip()
accent_setting = form_data.get("辅石镶嵌", "").strip()
# 构建 prompt 片段
parts = []
# 构建主石描述(优先使用新格式)
if center_stone_shape and center_stone_material:
# 新格式:形状+材质
main_stone_desc = f"{center_stone_shape}{center_stone_material}"
elif center_stone:
# 旧格式:直接使用
main_stone_desc = center_stone
elif center_stone_shape:
# 只有形状
main_stone_desc = center_stone_shape
elif center_stone_material:
# 只有材质
main_stone_desc = center_stone_material
else:
main_stone_desc = ""
# 主体部分(主石和金属)
if main_stone_desc and metal:
parts.append(f"一款高端精品珠宝戒指设计,主石为{main_stone_desc},镶嵌于{metal}中。")
elif metal:
parts.append(f"一款高端精品珠宝戒指设计,镶嵌于{metal}中。")
elif main_stone_desc:
parts.append(f"一款高端精品珠宝戒指设计,主石为{main_stone_desc}。")
else:
parts.append("一款高端精品珠宝戒指设计。")
# 戒头部分
if halo_style:
if main_stone_desc:
parts.append(f"戒头采用{halo_style}围绕主石。")
else:
parts.append(f"戒头采用{halo_style}设计。")
# 戒臂部分
if shank_structure and shank_treatment:
parts.append(f"戒臂采用{shank_structure}设计,表面处理为{shank_treatment}。")
elif shank_structure:
parts.append(f"戒臂采用{shank_structure}设计。")
elif shank_treatment:
parts.append(f"戒臂表面处理为{shank_treatment}。")
# 特殊元素
if special_motifs:
parts.append(f"风格元素包括{special_motifs}。")
# 辅石镶嵌
if accent_setting:
parts.append(f"辅石采用{accent_setting}增加光彩。")
# 固定的渲染要求
parts.append("高端珠宝渲染,干净的摄影棚光线,精准的金属抛光,强调宝石清晰度,不要出现手部。")
return "".join(parts)
class StyleDesignerTab(QWidget):
"""款式设计 Tab(纯中文)"""
def __init__(self, library_manager: JewelryLibraryManager, parent=None):
super().__init__(parent)
self.logger = logging.getLogger(__name__)
self.library_manager = library_manager
self.parent_window = parent
# 字段顺序
self.categories = ["主石形状", "主石材质", "金属", "花头形式", "戒臂结构", "戒臂处理", "特殊元素", "辅石镶嵌"]
# 存储每个类别的 ComboBox
self.combo_boxes = {}
# 生成相关
self.generated_image_bytes = None
self.setup_ui()
def setup_ui(self):
"""创建 UI 布局"""
main_layout = QVBoxLayout()
main_layout.setContentsMargins(10, 10, 10, 10)
main_layout.setSpacing(15)
# 表单区域 - 每行两列布局
form_group = QGroupBox("珠宝元素选择")
form_layout = QVBoxLayout()
# 使用网格布局实现每行两列
grid_layout = QGridLayout()
grid_layout.setHorizontalSpacing(20) # 列间距
grid_layout.setVerticalSpacing(10) # 行间距
for i, category in enumerate(self.categories):
field_widget = self.create_field_widget(category)
row = i // 2
col = i % 2
grid_layout.addWidget(field_widget, row, col)
form_layout.addLayout(grid_layout)
# 全局按钮区域
buttons_layout = QHBoxLayout()
# 随机生成按钮
random_btn = QPushButton("🎲 随机生成参数")
random_btn.clicked.connect(self.randomize_parameters)
random_btn.setStyleSheet("""
QPushButton {
background-color: #4CAF50;
color: white;
padding: 8px;
border-radius: 4px;
font-weight: bold;
}
QPushButton:hover {
background-color: #45a049;
}
""")
buttons_layout.addWidget(random_btn)
# 全局恢复按钮
reset_all_btn = QPushButton("🔄 恢复所有默认词库")
reset_all_btn.clicked.connect(self.reset_all_library)
reset_all_btn.setStyleSheet("""
QPushButton {
background-color: #ff9800;
color: white;
padding: 8px;
border-radius: 4px;
font-weight: bold;
}
QPushButton:hover {
background-color: #f57c00;
}
""")
buttons_layout.addWidget(reset_all_btn)
buttons_layout.addStretch()
form_layout.addLayout(buttons_layout)
form_group.setLayout(form_layout)
main_layout.addWidget(form_group)
# Content row: Prompt (left) + Settings (right) - 与图片生成页面保持一致
content_row = QHBoxLayout()
# Prompt section
prompt_group = QGroupBox("Prompt 预览")
prompt_layout = QVBoxLayout()
self.prompt_preview = QTextEdit()
self.prompt_preview.setReadOnly(True)
prompt_layout.addWidget(self.prompt_preview)
prompt_group.setLayout(prompt_layout)
content_row.addWidget(prompt_group, 2)
# Settings section
settings_group = QGroupBox("生成设置")
settings_layout = QVBoxLayout()
aspect_label = QLabel("宽高比")
aspect_label.setStyleSheet("QLabel { font-size: 14px; line-height: 18px; }")
settings_layout.addWidget(aspect_label)
self.aspect_ratio = QComboBox()
self.aspect_ratio.addItems(["1:1", "2:3", "3:2", "3:4", "4:3", "16:9"])
settings_layout.addWidget(self.aspect_ratio)
settings_layout.addSpacing(10)
size_label = QLabel("图片尺寸")
size_label.setStyleSheet("QLabel { font-size: 14px; line-height: 18px; }")
settings_layout.addWidget(size_label)
self.image_size = QComboBox()
self.image_size.addItems(["1K", "2K", "4K"])
self.image_size.setCurrentIndex(1)
settings_layout.addWidget(self.image_size)
settings_layout.addSpacing(10)
settings_layout.addStretch()
settings_group.setLayout(settings_layout)
content_row.addWidget(settings_group, 1)
main_layout.addLayout(content_row)
# Action buttons - 与图片生成页面保持一致
action_layout = QHBoxLayout()
self.generate_btn = QPushButton("🎨 生成珠宝图片")
self.generate_btn.clicked.connect(self.generate_image)
self.generate_btn.setStyleSheet("""
QPushButton {
background-color: #007AFF;
color: white;
padding: 12px;
border-radius: 4px;
font-size: 14px;
font-weight: bold;
}
QPushButton:hover {
background-color: #0051D5;
}
""")
action_layout.addWidget(self.generate_btn)
self.download_btn = QPushButton("💾 下载图片")
self.download_btn.clicked.connect(self.download_image)
self.download_btn.setEnabled(False)
action_layout.addWidget(self.download_btn)
self.status_label = QLabel("● 就绪")
self.status_label.setStyleSheet("QLabel { font-size: 14px; line-height: 18px; }")
action_layout.addWidget(self.status_label)
action_layout.addStretch()
main_layout.addLayout(action_layout)
# Preview section - 与图片生成页面保持一致
preview_group = QGroupBox("预览")
preview_layout = QVBoxLayout()
self.result_label = QLabel("生成的图片将在这里显示\n双击用系统查看器打开")
self.result_label.setAlignment(Qt.AlignCenter)
self.result_label.setMinimumHeight(300)
self.result_label.setMinimumWidth(400)
self.result_label.setStyleSheet("""
QLabel {
color: #999999;
font-size: 14px;
line-height: 18px;
border: 1px solid #ddd;
border-radius: 4px;
background-color: #f9f9f9;
}
""")
self.result_label.setScaledContents(False)
# 保存原始的双击处理方法引用
self.original_mouseDoubleClickEvent = self.result_label.mouseDoubleClickEvent
# 重写双击事件
self.result_label.mouseDoubleClickEvent = self.open_fullsize_view
preview_layout.addWidget(self.result_label)
preview_group.setLayout(preview_layout)
main_layout.addWidget(preview_group, 1)
main_layout.addStretch()
self.setLayout(main_layout)
# 初始化 prompt 预览
self.update_prompt_preview()
def randomize_parameters(self):
"""随机生成一套参数"""
for category, combo in self.combo_boxes.items():
if combo.count() > 0:
random_index = random.randint(0, combo.count() - 1)
combo.setCurrentIndex(random_index)
# 触发prompt预览更新
self.update_prompt_preview()
def create_field_widget(self, category: str) -> QWidget:
"""创建单个字段的 UI 组件"""
widget = QWidget()
layout = QHBoxLayout()
layout.setContentsMargins(0, 0, 0, 0)
# 标签
label = QLabel(f"{category}:")
label.setMinimumWidth(70) # 减小标签宽度以适应两列布局
label.setStyleSheet("QLabel { font-size: 14px; line-height: 18px; }")
layout.addWidget(label)
# 下拉框
combo = QComboBox()
combo.addItem("") # 空选项
items = self.library_manager.library.get(category, [])
combo.addItems(items)
combo.currentTextChanged.connect(self.update_prompt_preview)
self.combo_boxes[category] = combo
layout.addWidget(combo, 3)
# 添加按钮
add_btn = QPushButton("添加")
add_btn.clicked.connect(lambda: self.add_library_item(category))
add_btn.setFixedWidth(60)
layout.addWidget(add_btn)
# 删除按钮
del_btn = QPushButton("删除")
del_btn.clicked.connect(lambda: self.remove_library_item(category))
del_btn.setFixedWidth(60)
layout.addWidget(del_btn)
widget.setLayout(layout)
return widget
def update_prompt_preview(self):
"""实时更新 prompt 预览"""
form_data = {}
for category, combo in self.combo_boxes.items():
form_data[category] = combo.currentText()
prompt = PromptAssembler.assemble(form_data)
self.prompt_preview.setPlainText(prompt)
def add_library_item(self, category: str):
"""添加词库项 UI"""
value, ok = QInputDialog.getText(
self,
f"添加{category}词库项",
f"请输入新的{category}词条(纯中文):",
QLineEdit.Normal
)
if ok and value.strip():
value = value.strip()
try:
self.library_manager.add_item(category, value)
# 刷新下拉框
self.refresh_combo_box(category)
QMessageBox.information(self, "成功", f"已添加词库项: {value}")
except Exception as e:
self.logger.error(f"添加词库项失败: {e}")
QMessageBox.warning(self, "错误", f"添加失败: {e}")
else:
self.logger.info(f"用户取消了添加{category}词库项的操作")
def remove_library_item(self, category: str):
"""删除词库项 UI"""
combo = self.combo_boxes.get(category)
if not combo:
return
current_value = combo.currentText()
if not current_value:
QMessageBox.warning(self, "提示", "请先选择要删除的词库项")
return
reply = QMessageBox.question(
self,
"确认删除",
f"确定要删除词库项 \"{current_value}\" 吗?",
QMessageBox.Yes | QMessageBox.No
)
if reply == QMessageBox.Yes:
try:
self.library_manager.remove_item(category, current_value)
# 刷新下拉框
self.refresh_combo_box(category)
QMessageBox.information(self, "成功", f"已删除词库项: {current_value}")
except Exception as e:
QMessageBox.warning(self, "错误", f"删除失败: {e}")
def reset_category_library(self, category: str):
"""恢复单个类别的默认词库 UI"""
reply = QMessageBox.question(
self,
"确认恢复",
f"确定要恢复 \"{category}\" 的默认词库吗?\n这将删除所有自定义词条。",
QMessageBox.Yes | QMessageBox.No
)
if reply == QMessageBox.Yes:
try:
self.library_manager.reset_category(category)
# 刷新下拉框
self.refresh_combo_box(category)
QMessageBox.information(self, "成功", f"已恢复 {category} 的默认词库")
except Exception as e:
QMessageBox.warning(self, "错误", f"恢复失败: {e}")
def reset_all_library(self):
"""恢复所有字段的默认词库 UI"""
reply = QMessageBox.question(
self,
"确认恢复所有默认词库",
"确定要恢复所有字段的默认词库吗?\n这将删除所有自定义词条。",
QMessageBox.Yes | QMessageBox.No
)
if reply == QMessageBox.Yes:
try:
self.library_manager.reset_all()
# 刷新所有下拉框
for category in self.categories:
self.refresh_combo_box(category)
QMessageBox.information(self, "成功", "已恢复所有默认词库")
except Exception as e:
QMessageBox.warning(self, "错误", f"恢复失败: {e}")
def open_fullsize_view(self, event):
"""双击打开完整尺寸查看器"""
if not self.generated_image_bytes:
QMessageBox.warning(self, "提示", "没有可预览的图片")
return
# 保存为临时文件并使用系统查看器打开
import tempfile
with tempfile.NamedTemporaryFile(suffix='.png', delete=False) as tmp_file:
tmp_file.write(self.generated_image_bytes)
temp_path = Path(tmp_file.name)
# 使用系统默认程序打开
QDesktopServices.openUrl(QUrl.fromLocalFile(str(temp_path)))
def refresh_combo_box(self, category: str):
"""刷新指定类别的下拉框"""
combo = self.combo_boxes.get(category)
if not combo:
return
# 保存当前选中值
current_value = combo.currentText()
# 清空并重新加载
combo.clear()
combo.addItem("") # 空选项
items = self.library_manager.library.get(category, [])
combo.addItems(items)
# 尝试恢复选中值
index = combo.findText(current_value)
if index >= 0:
combo.setCurrentIndex(index)
else:
combo.setCurrentIndex(0)
def generate_image(self):
"""调用文生图 API"""
# 获取 prompt
prompt = self.prompt_preview.toPlainText()
if not prompt.strip():
QMessageBox.warning(self, "提示", "Prompt 不能为空")
return
# 获取设置
aspect_ratio = self.aspect_ratio.currentText()
image_size = self.image_size.currentText()
model = "imagen-3.0-generate-002" # 硬编码使用默认模型
# 获取父窗口的 API key
if not hasattr(self.parent_window, 'api_key') or not self.parent_window.api_key:
QMessageBox.warning(self, "错误", "未找到 API 密钥,请在主窗口配置")
return
# 非阻塞状态提示
self.generate_btn.setEnabled(False)
self.status_label.setText("● 生成中...")
QApplication.processEvents()
try:
# 创建生成线程
self.gen_thread = ImageGenerationWorker(
api_key=self.parent_window.api_key,
prompt=prompt,
images=[],
aspect_ratio=aspect_ratio,
image_size=image_size,
model=model
)
self.gen_thread.finished.connect(self.on_generation_success)
self.gen_thread.error.connect(self.on_generation_error)
self.gen_thread.start()
except Exception as e:
self.generate_btn.setEnabled(True)
self.status_label.setText("● 就绪")
QMessageBox.critical(self, "错误", f"生成失败: {e}")
def on_generation_success(self, image_bytes: bytes):
"""生成成功回调"""
# 恢复按钮状态
self.generate_btn.setEnabled(True)
self.status_label.setText("● 就绪")
self.generated_image_bytes = image_bytes
# 启用下载按钮
self.download_btn.setEnabled(True)
# 显示图片
pixmap = QPixmap()
pixmap.loadFromData(image_bytes)
# 清除之前的文本
self.result_label.setText("")
# 获取标签可用空间
label_size = self.result_label.size()
if label_size.width() < 100 or label_size.height() < 100:
# 如果标签还没有正确的尺寸,使用默认尺寸
label_size = QSize(400, 300)
# 缩放图片以适应显示区域,保持宽高比且完整显示
scaled_pixmap = pixmap.scaled(
label_size,
Qt.KeepAspectRatio,
Qt.SmoothTransformation
)
# 设置缩放后的图片,并确保居中对齐
self.result_label.setPixmap(scaled_pixmap)
self.result_label.setAlignment(Qt.AlignCenter)
# 保存到历史记录
try:
# 添加到历史记录管理器
if hasattr(self.parent_window, 'history_manager'):
timestamp = self.parent_window.history_manager.save_generation(
image_bytes=image_bytes,
prompt=self.prompt_preview.toPlainText(),
reference_images=[], # 款式设计无参考图
aspect_ratio=self.aspect_ratio.currentText(),
image_size=self.image_size.currentText(),
model="imagen-3.0-generate-002"
)
self.logger.info(f"款式设计已添加到历史记录: {timestamp}")
# 刷新历史记录列表
if hasattr(self.parent_window, 'refresh_history'):
self.parent_window.refresh_history()
else:
self.logger.warning("未找到历史记录管理器")
except Exception as e:
self.logger.error(f"保存历史记录失败: {e}")
# 更新状态提示
self.status_label.setText("● 图片生成成功")
self.status_label.setStyleSheet("QLabel { color: #34C759; }")
def on_generation_error(self, error_msg: str):
"""生成失败回调"""
# 恢复按钮状态
self.generate_btn.setEnabled(True)
self.status_label.setText("● 就绪")
QMessageBox.critical(self, "生成失败", error_msg)
def download_image(self):
"""下载图片"""
if not self.generated_image_bytes:
QMessageBox.warning(self, "提示", "没有可下载的图片")
return
file_path, _ = QFileDialog.getSaveFileName(
self,
"保存图片",
f"jewelry_design_{datetime.now().strftime('%Y%m%d%H%M%S')}.png",
"PNG Files (*.png)"
)
if file_path:
try:
with open(file_path, 'wb') as f:
f.write(self.generated_image_bytes)
# 使用状态栏提示而不是弹窗
self.status_label.setText("● 图片已保存")
self.status_label.setStyleSheet("QLabel { color: #34C759; }")
except Exception as e:
QMessageBox.critical(self, "错误", f"保存失败: {e}")
def preview_image(self):
"""预览大图"""
if not self.generated_image_bytes:
QMessageBox.warning(self, "提示", "没有可预览的图片")
return
# 创建预览对话框
dialog = QDialog(self)
dialog.setWindowTitle("预览大图")
dialog.resize(800, 600)
layout = QVBoxLayout()
# 图片标签
label = QLabel()
pixmap = QPixmap()
pixmap.loadFromData(self.generated_image_bytes)
label.setPixmap(pixmap)
label.setScaledContents(True)
# 滚动区域
scroll = QScrollArea()
scroll.setWidget(label)
scroll.setWidgetResizable(True)
layout.addWidget(scroll)
# 关闭按钮
close_btn = QPushButton("关闭")
close_btn.clicked.connect(dialog.close)
layout.addWidget(close_btn)
dialog.setLayout(layout)
dialog.exec()
def main():
"""Main application entry point"""
# 初始化日志系统
......
{
"主石": [
"椭圆形黑发晶",
"圆形莫桑石",
"祖母绿形红宝石",
"梨形蓝宝石",
"垫形绿碧玺",
"椭圆形黄水晶带天然包裹体",
"公主方形钻石",
"心形粉红蓝宝石"
],
"金属": [
"14K黄金",
"18K玫瑰金",
"14K白金",
"铂金(PT950)",
"双色14K黄金+白金",
"950银镀铑",
"钛金属"
],
"花头形式": [
"花卉风格光环",
"经典圆形光环",
"复古风格米粒边光环",
"双层光环",
"几何六边形光环",
"非对称光环",
"简约爪镶无光环"
],
"戒臂结构": [
"扭转戒臂",
"分裂戒臂",
"刀刃戒臂",
"大教堂戒臂",
"交叉戒臂",
"直线平滑戒臂",
"三股编织戒臂"
],
"戒臂处理": [
"雕刻镂空花丝工艺",
"密钉镶戒臂",
"抛光平滑戒臂",
"浮雕雕刻",
"编织纹理",
"极简干净戒臂",
"穿孔镂空细节",
"锤纹处理"
],
"特殊元素": [
"凯尔特结图案",
"装饰艺术几何元素",
"自然风格叶子图案",
"复古米粒边细节",
"哥特式图案元素",
"天体图案(星星、月亮)",
"藤蔓装饰曲线",
"蝴蝶结装饰"
],
"辅石镶嵌": [
"密钉镶",
"微密钉镶",
"槽镶",
"珠粒镶",
"共爪镶",
"包镶",
"轨道镶"
],
"主石形状": [
"椭圆形",
"圆形",
"祖母绿形",
"梨形",
"垫形",
"公主方形",
"心形"
],
"主石材质": [
"黑发晶",
"莫桑石",
"红宝石",
"蓝宝石",
"绿碧玺",
"黄水晶带天然包裹体",
"钻石",
"粉红蓝宝石"
]
}
\ No newline at end of file