994a9ea2 by 柴进

增加款式设计的部分标签

1 parent 32c7b837
...@@ -3,8 +3,9 @@ ...@@ -3,8 +3,9 @@
3 <component name="NewModuleRootManager"> 3 <component name="NewModuleRootManager">
4 <content url="file://$MODULE_DIR$"> 4 <content url="file://$MODULE_DIR$">
5 <excludeFolder url="file://$MODULE_DIR$/.venv" /> 5 <excludeFolder url="file://$MODULE_DIR$/.venv" />
6 <excludeFolder url="file://$MODULE_DIR$/venv" />
6 </content> 7 </content>
7 <orderEntry type="jdk" jdkName="Python 3.11 (GoogleNanoBananaApp)" jdkType="Python SDK" /> 8 <orderEntry type="inheritedJdk" />
8 <orderEntry type="sourceFolder" forTests="false" /> 9 <orderEntry type="sourceFolder" forTests="false" />
9 </component> 10 </component>
10 <component name="PyDocumentationSettings"> 11 <component name="PyDocumentationSettings">
......
...@@ -10,7 +10,7 @@ from PySide6.QtWidgets import ( ...@@ -10,7 +10,7 @@ from PySide6.QtWidgets import (
10 QLabel, QLineEdit, QPushButton, QCheckBox, QTextEdit, 10 QLabel, QLineEdit, QPushButton, QCheckBox, QTextEdit,
11 QComboBox, QScrollArea, QGroupBox, QFileDialog, QMessageBox, 11 QComboBox, QScrollArea, QGroupBox, QFileDialog, QMessageBox,
12 QListWidget, QListWidgetItem, QTabWidget, QSplitter, 12 QListWidget, QListWidgetItem, QTabWidget, QSplitter,
13 QMenu, QProgressBar 13 QMenu, QProgressBar, QInputDialog
14 ) 14 )
15 from PySide6.QtCore import Qt, QThread, Signal, QSize, QTimer, QMimeData 15 from PySide6.QtCore import Qt, QThread, Signal, QSize, QTimer, QMimeData
16 from PySide6.QtGui import QPixmap, QFont, QIcon, QDesktopServices, QAction, QImage, QDragEnterEvent, QDropEvent 16 from PySide6.QtGui import QPixmap, QFont, QIcon, QDesktopServices, QAction, QImage, QDragEnterEvent, QDropEvent
...@@ -25,6 +25,7 @@ import shutil ...@@ -25,6 +25,7 @@ import shutil
25 import tempfile 25 import tempfile
26 import platform 26 import platform
27 import logging 27 import logging
28 import random
28 from pathlib import Path 29 from pathlib import Path
29 from google import genai 30 from google import genai
30 from google.genai import types 31 from google.genai import types
...@@ -1264,6 +1265,16 @@ class ImageGeneratorWindow(QMainWindow): ...@@ -1264,6 +1265,16 @@ class ImageGeneratorWindow(QMainWindow):
1264 generation_tab = self.setup_generation_tab() 1265 generation_tab = self.setup_generation_tab()
1265 self.tab_widget.addTab(generation_tab, "图片生成") 1266 self.tab_widget.addTab(generation_tab, "图片生成")
1266 1267
1268 # Create style designer tab
1269 try:
1270 jewelry_lib_manager = JewelryLibraryManager(self.get_config_dir())
1271 style_designer_tab = StyleDesignerTab(jewelry_lib_manager, parent=self)
1272 self.tab_widget.addTab(style_designer_tab, "款式设计")
1273 self.logger.info("款式设计 Tab 创建成功")
1274 except Exception as e:
1275 self.logger.error(f"款式设计 Tab 创建失败: {e}")
1276 # 即使失败也继续运行,不阻止应用启动
1277
1267 # Create history tab 1278 # Create history tab
1268 history_tab = self.setup_history_tab() 1279 history_tab = self.setup_history_tab()
1269 self.tab_widget.addTab(history_tab, "历史记录") 1280 self.tab_widget.addTab(history_tab, "历史记录")
...@@ -1286,7 +1297,7 @@ class ImageGeneratorWindow(QMainWindow): ...@@ -1286,7 +1297,7 @@ class ImageGeneratorWindow(QMainWindow):
1286 1297
1287 # Upload button and count 1298 # Upload button and count
1288 upload_header = QHBoxLayout() 1299 upload_header = QHBoxLayout()
1289 upload_btn = QPushButton("+ 添加图片") 1300 upload_btn = QPushButton("添加图片")
1290 upload_btn.clicked.connect(self.upload_images) 1301 upload_btn.clicked.connect(self.upload_images)
1291 upload_header.addWidget(upload_btn) 1302 upload_header.addWidget(upload_btn)
1292 1303
...@@ -1357,7 +1368,7 @@ class ImageGeneratorWindow(QMainWindow): ...@@ -1357,7 +1368,7 @@ class ImageGeneratorWindow(QMainWindow):
1357 self.saved_prompts_combo = QComboBox() 1368 self.saved_prompts_combo = QComboBox()
1358 prompt_toolbar.addWidget(self.saved_prompts_combo) 1369 prompt_toolbar.addWidget(self.saved_prompts_combo)
1359 1370
1360 delete_prompt_btn = QPushButton("🗑️ 删除") 1371 delete_prompt_btn = QPushButton("删除")
1361 delete_prompt_btn.clicked.connect(self.delete_saved_prompt) 1372 delete_prompt_btn.clicked.connect(self.delete_saved_prompt)
1362 prompt_toolbar.addWidget(delete_prompt_btn) 1373 prompt_toolbar.addWidget(delete_prompt_btn)
1363 prompt_toolbar.addStretch() 1374 prompt_toolbar.addStretch()
...@@ -2090,7 +2101,7 @@ class ImageGeneratorWindow(QMainWindow): ...@@ -2090,7 +2101,7 @@ class ImageGeneratorWindow(QMainWindow):
2090 processing_method = "格式转换" if processed_bytes != self.generated_image_bytes else "原始数据" 2101 processing_method = "格式转换" if processed_bytes != self.generated_image_bytes else "原始数据"
2091 self.logger.info(f"图片下载完成 - 方法: {processing_method}, 文件: {file_path}, 大小: {file_size} 字节") 2102 self.logger.info(f"图片下载完成 - 方法: {processing_method}, 文件: {file_path}, 大小: {file_size} 字节")
2092 2103
2093 QMessageBox.information(self, "成功", f"图片已保存到:\n{file_path}\n\n文件大小: {file_size:,} 字节") 2104 # 只使用状态栏提示,不显示弹窗
2094 self.status_label.setText("● 图片已保存") 2105 self.status_label.setText("● 图片已保存")
2095 self.status_label.setStyleSheet("QLabel { color: #34C759; }") 2106 self.status_label.setStyleSheet("QLabel { color: #34C759; }")
2096 except Exception as e: 2107 except Exception as e:
...@@ -2527,6 +2538,874 @@ class ImageGenerationWorker(QThread): ...@@ -2527,6 +2538,874 @@ class ImageGenerationWorker(QThread):
2527 self.error.emit(error_msg) 2538 self.error.emit(error_msg)
2528 2539
2529 2540
2541 # ============================================================================
2542 # 珠宝设计功能模块
2543 # ============================================================================
2544
2545 # 默认珠宝词库(纯中文)
2546 DEFAULT_JEWELRY_LIBRARY = {
2547 "主石形状": [
2548 "椭圆形",
2549 "圆形",
2550 "祖母绿形",
2551 "梨形",
2552 "垫形",
2553 "公主方形",
2554 "心形"
2555 ],
2556 "主石材质": [
2557 "黑发晶",
2558 "莫桑石",
2559 "红宝石",
2560 "蓝宝石",
2561 "绿碧玺",
2562 "黄水晶带天然包裹体",
2563 "钻石",
2564 "粉红蓝宝石"
2565 ],
2566 "金属": [
2567 "14K黄金",
2568 "18K玫瑰金",
2569 "14K白金",
2570 "铂金(PT950)",
2571 "双色14K黄金+白金",
2572 "950银镀铑",
2573 "钛金属"
2574 ],
2575 "花头形式": [
2576 "花卉风格光环",
2577 "经典圆形光环",
2578 "复古风格米粒边光环",
2579 "双层光环",
2580 "几何六边形光环",
2581 "非对称光环",
2582 "简约爪镶无光环"
2583 ],
2584 "戒臂结构": [
2585 "扭转戒臂",
2586 "分裂戒臂",
2587 "刀刃戒臂",
2588 "大教堂戒臂",
2589 "交叉戒臂",
2590 "直线平滑戒臂",
2591 "三股编织戒臂"
2592 ],
2593 "戒臂处理": [
2594 "雕刻镂空花丝工艺",
2595 "密钉镶戒臂",
2596 "抛光平滑戒臂",
2597 "浮雕雕刻",
2598 "编织纹理",
2599 "极简干净戒臂",
2600 "穿孔镂空细节",
2601 "锤纹处理"
2602 ],
2603 "特殊元素": [
2604 "凯尔特结图案",
2605 "装饰艺术几何元素",
2606 "自然风格叶子图案",
2607 "复古米粒边细节",
2608 "哥特式图案元素",
2609 "天体图案(星星、月亮)",
2610 "藤蔓装饰曲线",
2611 "蝴蝶结装饰"
2612 ],
2613 "辅石镶嵌": [
2614 "密钉镶",
2615 "微密钉镶",
2616 "槽镶",
2617 "珠粒镶",
2618 "共爪镶",
2619 "包镶",
2620 "轨道镶"
2621 ]
2622 }
2623
2624
2625 class JewelryLibraryManager:
2626 """珠宝词库管理器"""
2627
2628 def __init__(self, config_dir: Path):
2629 """初始化词库管理器
2630
2631 Args:
2632 config_dir: 配置文件目录
2633 """
2634 self.logger = logging.getLogger(__name__)
2635 self.config_dir = config_dir
2636 self.config_path = config_dir / "jewelry_library.json"
2637 self.library = self.load_library()
2638
2639 def load_library(self) -> Dict[str, List[str]]:
2640 """加载词库,不存在则创建默认词库"""
2641 if self.config_path.exists():
2642 try:
2643 with open(self.config_path, 'r', encoding='utf-8') as f:
2644 library = json.load(f)
2645
2646 # 检查是否需要进行数据迁移
2647 library = self._migrate_library_if_needed(library)
2648
2649 self.logger.info(f"珠宝词库加载成功: {self.config_path}")
2650 return library
2651 except Exception as e:
2652 self.logger.error(f"珠宝词库加载失败: {e},使用默认词库")
2653 return DEFAULT_JEWELRY_LIBRARY.copy()
2654 else:
2655 # 首次使用,创建默认词库
2656 self.logger.info("未找到珠宝词库文件,创建默认词库")
2657 library = DEFAULT_JEWELRY_LIBRARY.copy()
2658 self.save_library(library)
2659 return library
2660
2661 def _migrate_library_if_needed(self, library: Dict[str, List[str]]) -> Dict[str, List[str]]:
2662 """检查并执行数据迁移"""
2663 # 如果存在旧的"主石"字段,需要拆分
2664 if "主石" in library:
2665 self.logger.info("检测到旧版主石字段,执行数据迁移...")
2666
2667 # 创建新字段
2668 shapes = []
2669 materials = []
2670
2671 # 分析现有数据
2672 for item in library["主石"]:
2673 # 提取形状信息
2674 shape = self._extract_shape(item)
2675 if shape and shape not in shapes:
2676 shapes.append(shape)
2677
2678 # 提取材质信息
2679 material = self._extract_material(item)
2680 if material and material not in materials:
2681 materials.append(material)
2682
2683 # 更新词库
2684 library["主石形状"] = shapes if shapes else DEFAULT_JEWELRY_LIBRARY["主石形状"]
2685 library["主石材质"] = materials if materials else DEFAULT_JEWELRY_LIBRARY["主石材质"]
2686
2687 # 保留旧字段作为备份
2688 # library.pop("主石", None) # 可以选择是否移除
2689
2690 # 保存迁移后的数据
2691 self.save_library(library)
2692 self.logger.info(f"数据迁移完成:形状 {len(shapes)} 个,材质 {len(materials)} 个")
2693
2694 return library
2695
2696 def _extract_shape(self, item: str) -> Optional[str]:
2697 """从主石描述中提取形状"""
2698 shapes = ["椭圆形", "圆形", "祖母绿形", "梨形", "垫形", "公主方形", "心形"]
2699 for shape in shapes:
2700 if item.startswith(shape):
2701 return shape
2702 return None
2703
2704 def _extract_material(self, item: str) -> Optional[str]:
2705 """从主石描述中提取材质"""
2706 shapes = ["椭圆形", "圆形", "祖母绿形", "梨形", "垫形", "公主方形", "心形"]
2707 for shape in shapes:
2708 if item.startswith(shape):
2709 material = item[len(shape):]
2710 # 去除可能的连接词
2711 if material.startswith("的"):
2712 material = material[1:]
2713 return material if material else None
2714 return item # 如果没有形状前缀,返回整个字符串
2715
2716 def save_library(self, library: Dict[str, List[str]] = None):
2717 """保存词库到配置文件
2718
2719 Args:
2720 library: 要保存的词库,如果为None则保存当前词库
2721 """
2722 if library is None:
2723 library = self.library
2724
2725 try:
2726 self.config_dir.mkdir(parents=True, exist_ok=True)
2727 with open(self.config_path, 'w', encoding='utf-8') as f:
2728 json.dump(library, f, ensure_ascii=False, indent=2)
2729 self.logger.info(f"珠宝词库保存成功: {self.config_path}")
2730 except Exception as e:
2731 self.logger.error(f"珠宝词库保存失败: {e}")
2732 raise
2733
2734 def add_item(self, category: str, value: str):
2735 """添加词库项
2736
2737 Args:
2738 category: 类别名称(如"主石")
2739 value: 词条内容(纯中文)
2740 """
2741 if category not in self.library:
2742 self.library[category] = []
2743
2744 if value not in self.library[category]:
2745 self.library[category].append(value)
2746 self.save_library()
2747 self.logger.info(f"添加词库项: {category} -> {value}")
2748 else:
2749 self.logger.warning(f"词库项已存在: {category} -> {value}")
2750
2751 def remove_item(self, category: str, value: str):
2752 """删除词库项
2753
2754 Args:
2755 category: 类别名称
2756 value: 词条内容
2757 """
2758 if category in self.library and value in self.library[category]:
2759 self.library[category].remove(value)
2760 self.save_library()
2761 self.logger.info(f"删除词库项: {category} -> {value}")
2762 else:
2763 self.logger.warning(f"词库项不存在: {category} -> {value}")
2764
2765 def reset_category(self, category: str):
2766 """恢复单个类别的默认词库
2767
2768 Args:
2769 category: 类别名称
2770 """
2771 if category in DEFAULT_JEWELRY_LIBRARY:
2772 self.library[category] = DEFAULT_JEWELRY_LIBRARY[category].copy()
2773 self.save_library()
2774 self.logger.info(f"恢复默认词库: {category}")
2775 else:
2776 self.logger.warning(f"未知的类别: {category}")
2777
2778 def reset_all(self):
2779 """恢复所有类别的默认词库"""
2780 self.library = DEFAULT_JEWELRY_LIBRARY.copy()
2781 self.save_library()
2782 self.logger.info("恢复所有默认词库")
2783
2784
2785 class PromptAssembler:
2786 """Prompt 组装器(纯中文)"""
2787
2788 BASE_TEMPLATE = """一款高端精品珠宝戒指设计,主石为{主石},镶嵌于{金属}中。戒头采用{花头形式}围绕主石。戒臂采用{戒臂结构}设计,表面处理为{戒臂处理}。风格元素包括{特殊元素}。辅石采用{辅石镶嵌}增加光彩。高端珠宝渲染,干净的摄影棚光线,精准的金属抛光,强调宝石清晰度,不要出现手部。"""
2789
2790 @staticmethod
2791 def assemble(form_data: Dict[str, str]) -> str:
2792 """智能组装 prompt,处理空值
2793
2794 Args:
2795 form_data: 表单数据,key为字段名,value为选中的值
2796
2797 Returns:
2798 组装后的 prompt 字符串
2799 """
2800 # 提取字段值(支持新旧两种格式)
2801 center_stone = form_data.get("主石", "").strip() # 向后兼容
2802
2803 # 新格式:拆分形状和材质
2804 center_stone_shape = form_data.get("主石形状", "").strip()
2805 center_stone_material = form_data.get("主石材质", "").strip()
2806 metal = form_data.get("金属", "").strip()
2807 halo_style = form_data.get("花头形式", "").strip()
2808 shank_structure = form_data.get("戒臂结构", "").strip()
2809 shank_treatment = form_data.get("戒臂处理", "").strip()
2810 special_motifs = form_data.get("特殊元素", "").strip()
2811 accent_setting = form_data.get("辅石镶嵌", "").strip()
2812
2813 # 构建 prompt 片段
2814 parts = []
2815
2816 # 构建主石描述(优先使用新格式)
2817 if center_stone_shape and center_stone_material:
2818 # 新格式:形状+材质
2819 main_stone_desc = f"{center_stone_shape}{center_stone_material}"
2820 elif center_stone:
2821 # 旧格式:直接使用
2822 main_stone_desc = center_stone
2823 elif center_stone_shape:
2824 # 只有形状
2825 main_stone_desc = center_stone_shape
2826 elif center_stone_material:
2827 # 只有材质
2828 main_stone_desc = center_stone_material
2829 else:
2830 main_stone_desc = ""
2831
2832 # 主体部分(主石和金属)
2833 if main_stone_desc and metal:
2834 parts.append(f"一款高端精品珠宝戒指设计,主石为{main_stone_desc},镶嵌于{metal}中。")
2835 elif metal:
2836 parts.append(f"一款高端精品珠宝戒指设计,镶嵌于{metal}中。")
2837 elif main_stone_desc:
2838 parts.append(f"一款高端精品珠宝戒指设计,主石为{main_stone_desc}。")
2839 else:
2840 parts.append("一款高端精品珠宝戒指设计。")
2841
2842 # 戒头部分
2843 if halo_style:
2844 if main_stone_desc:
2845 parts.append(f"戒头采用{halo_style}围绕主石。")
2846 else:
2847 parts.append(f"戒头采用{halo_style}设计。")
2848
2849 # 戒臂部分
2850 if shank_structure and shank_treatment:
2851 parts.append(f"戒臂采用{shank_structure}设计,表面处理为{shank_treatment}。")
2852 elif shank_structure:
2853 parts.append(f"戒臂采用{shank_structure}设计。")
2854 elif shank_treatment:
2855 parts.append(f"戒臂表面处理为{shank_treatment}。")
2856
2857 # 特殊元素
2858 if special_motifs:
2859 parts.append(f"风格元素包括{special_motifs}。")
2860
2861 # 辅石镶嵌
2862 if accent_setting:
2863 parts.append(f"辅石采用{accent_setting}增加光彩。")
2864
2865 # 固定的渲染要求
2866 parts.append("高端珠宝渲染,干净的摄影棚光线,精准的金属抛光,强调宝石清晰度,不要出现手部。")
2867
2868 return "".join(parts)
2869
2870
2871 class StyleDesignerTab(QWidget):
2872 """款式设计 Tab(纯中文)"""
2873
2874 def __init__(self, library_manager: JewelryLibraryManager, parent=None):
2875 super().__init__(parent)
2876 self.logger = logging.getLogger(__name__)
2877 self.library_manager = library_manager
2878 self.parent_window = parent
2879
2880 # 字段顺序
2881 self.categories = ["主石形状", "主石材质", "金属", "花头形式", "戒臂结构", "戒臂处理", "特殊元素", "辅石镶嵌"]
2882
2883 # 存储每个类别的 ComboBox
2884 self.combo_boxes = {}
2885
2886 # 生成相关
2887 self.generated_image_bytes = None
2888
2889 self.setup_ui()
2890
2891 def setup_ui(self):
2892 """创建 UI 布局"""
2893 main_layout = QVBoxLayout()
2894 main_layout.setContentsMargins(10, 10, 10, 10)
2895 main_layout.setSpacing(15)
2896
2897 # 表单区域 - 每行两列布局
2898 form_group = QGroupBox("珠宝元素选择")
2899 form_layout = QVBoxLayout()
2900
2901 # 使用网格布局实现每行两列
2902 grid_layout = QGridLayout()
2903 grid_layout.setHorizontalSpacing(20) # 列间距
2904 grid_layout.setVerticalSpacing(10) # 行间距
2905
2906 for i, category in enumerate(self.categories):
2907 field_widget = self.create_field_widget(category)
2908 row = i // 2
2909 col = i % 2
2910 grid_layout.addWidget(field_widget, row, col)
2911
2912 form_layout.addLayout(grid_layout)
2913
2914 # 全局按钮区域
2915 buttons_layout = QHBoxLayout()
2916
2917 # 随机生成按钮
2918 random_btn = QPushButton("🎲 随机生成参数")
2919 random_btn.clicked.connect(self.randomize_parameters)
2920 random_btn.setStyleSheet("""
2921 QPushButton {
2922 background-color: #4CAF50;
2923 color: white;
2924 padding: 8px;
2925 border-radius: 4px;
2926 font-weight: bold;
2927 }
2928 QPushButton:hover {
2929 background-color: #45a049;
2930 }
2931 """)
2932 buttons_layout.addWidget(random_btn)
2933
2934 # 全局恢复按钮
2935 reset_all_btn = QPushButton("🔄 恢复所有默认词库")
2936 reset_all_btn.clicked.connect(self.reset_all_library)
2937 reset_all_btn.setStyleSheet("""
2938 QPushButton {
2939 background-color: #ff9800;
2940 color: white;
2941 padding: 8px;
2942 border-radius: 4px;
2943 font-weight: bold;
2944 }
2945 QPushButton:hover {
2946 background-color: #f57c00;
2947 }
2948 """)
2949 buttons_layout.addWidget(reset_all_btn)
2950
2951 buttons_layout.addStretch()
2952 form_layout.addLayout(buttons_layout)
2953
2954 form_group.setLayout(form_layout)
2955 main_layout.addWidget(form_group)
2956
2957 # Content row: Prompt (left) + Settings (right) - 与图片生成页面保持一致
2958 content_row = QHBoxLayout()
2959
2960 # Prompt section
2961 prompt_group = QGroupBox("Prompt 预览")
2962 prompt_layout = QVBoxLayout()
2963 self.prompt_preview = QTextEdit()
2964 self.prompt_preview.setReadOnly(True)
2965 prompt_layout.addWidget(self.prompt_preview)
2966 prompt_group.setLayout(prompt_layout)
2967 content_row.addWidget(prompt_group, 2)
2968
2969 # Settings section
2970 settings_group = QGroupBox("生成设置")
2971 settings_layout = QVBoxLayout()
2972
2973 aspect_label = QLabel("宽高比")
2974 aspect_label.setStyleSheet("QLabel { font-size: 14px; line-height: 18px; }")
2975 settings_layout.addWidget(aspect_label)
2976 self.aspect_ratio = QComboBox()
2977 self.aspect_ratio.addItems(["1:1", "2:3", "3:2", "3:4", "4:3", "16:9"])
2978 settings_layout.addWidget(self.aspect_ratio)
2979
2980 settings_layout.addSpacing(10)
2981
2982 size_label = QLabel("图片尺寸")
2983 size_label.setStyleSheet("QLabel { font-size: 14px; line-height: 18px; }")
2984 settings_layout.addWidget(size_label)
2985 self.image_size = QComboBox()
2986 self.image_size.addItems(["1K", "2K", "4K"])
2987 self.image_size.setCurrentIndex(1)
2988 settings_layout.addWidget(self.image_size)
2989
2990 settings_layout.addSpacing(10)
2991
2992 settings_layout.addStretch()
2993 settings_group.setLayout(settings_layout)
2994 content_row.addWidget(settings_group, 1)
2995
2996 main_layout.addLayout(content_row)
2997
2998 # Action buttons - 与图片生成页面保持一致
2999 action_layout = QHBoxLayout()
3000 self.generate_btn = QPushButton("🎨 生成珠宝图片")
3001 self.generate_btn.clicked.connect(self.generate_image)
3002 self.generate_btn.setStyleSheet("""
3003 QPushButton {
3004 background-color: #007AFF;
3005 color: white;
3006 padding: 12px;
3007 border-radius: 4px;
3008 font-size: 14px;
3009 font-weight: bold;
3010 }
3011 QPushButton:hover {
3012 background-color: #0051D5;
3013 }
3014 """)
3015 action_layout.addWidget(self.generate_btn)
3016
3017 self.download_btn = QPushButton("💾 下载图片")
3018 self.download_btn.clicked.connect(self.download_image)
3019 self.download_btn.setEnabled(False)
3020 action_layout.addWidget(self.download_btn)
3021
3022 self.status_label = QLabel("● 就绪")
3023 self.status_label.setStyleSheet("QLabel { font-size: 14px; line-height: 18px; }")
3024 action_layout.addWidget(self.status_label)
3025 action_layout.addStretch()
3026
3027 main_layout.addLayout(action_layout)
3028
3029 # Preview section - 与图片生成页面保持一致
3030 preview_group = QGroupBox("预览")
3031 preview_layout = QVBoxLayout()
3032
3033 self.result_label = QLabel("生成的图片将在这里显示\n双击用系统查看器打开")
3034 self.result_label.setAlignment(Qt.AlignCenter)
3035 self.result_label.setMinimumHeight(300)
3036 self.result_label.setMinimumWidth(400)
3037 self.result_label.setStyleSheet("""
3038 QLabel {
3039 color: #999999;
3040 font-size: 14px;
3041 line-height: 18px;
3042 border: 1px solid #ddd;
3043 border-radius: 4px;
3044 background-color: #f9f9f9;
3045 }
3046 """)
3047 self.result_label.setScaledContents(False)
3048 # 保存原始的双击处理方法引用
3049 self.original_mouseDoubleClickEvent = self.result_label.mouseDoubleClickEvent
3050 # 重写双击事件
3051 self.result_label.mouseDoubleClickEvent = self.open_fullsize_view
3052 preview_layout.addWidget(self.result_label)
3053
3054 preview_group.setLayout(preview_layout)
3055 main_layout.addWidget(preview_group, 1)
3056
3057 main_layout.addStretch()
3058 self.setLayout(main_layout)
3059
3060 # 初始化 prompt 预览
3061 self.update_prompt_preview()
3062
3063 def randomize_parameters(self):
3064 """随机生成一套参数"""
3065 for category, combo in self.combo_boxes.items():
3066 if combo.count() > 0:
3067 random_index = random.randint(0, combo.count() - 1)
3068 combo.setCurrentIndex(random_index)
3069 # 触发prompt预览更新
3070 self.update_prompt_preview()
3071
3072 def create_field_widget(self, category: str) -> QWidget:
3073 """创建单个字段的 UI 组件"""
3074 widget = QWidget()
3075 layout = QHBoxLayout()
3076 layout.setContentsMargins(0, 0, 0, 0)
3077
3078 # 标签
3079 label = QLabel(f"{category}:")
3080 label.setMinimumWidth(70) # 减小标签宽度以适应两列布局
3081 label.setStyleSheet("QLabel { font-size: 14px; line-height: 18px; }")
3082 layout.addWidget(label)
3083
3084 # 下拉框
3085 combo = QComboBox()
3086 combo.addItem("") # 空选项
3087 items = self.library_manager.library.get(category, [])
3088 combo.addItems(items)
3089 combo.currentTextChanged.connect(self.update_prompt_preview)
3090 self.combo_boxes[category] = combo
3091 layout.addWidget(combo, 3)
3092
3093 # 添加按钮
3094 add_btn = QPushButton("添加")
3095 add_btn.clicked.connect(lambda: self.add_library_item(category))
3096 add_btn.setFixedWidth(60)
3097 layout.addWidget(add_btn)
3098
3099 # 删除按钮
3100 del_btn = QPushButton("删除")
3101 del_btn.clicked.connect(lambda: self.remove_library_item(category))
3102 del_btn.setFixedWidth(60)
3103 layout.addWidget(del_btn)
3104
3105 widget.setLayout(layout)
3106 return widget
3107
3108 def update_prompt_preview(self):
3109 """实时更新 prompt 预览"""
3110 form_data = {}
3111 for category, combo in self.combo_boxes.items():
3112 form_data[category] = combo.currentText()
3113
3114 prompt = PromptAssembler.assemble(form_data)
3115 self.prompt_preview.setPlainText(prompt)
3116
3117 def add_library_item(self, category: str):
3118 """添加词库项 UI"""
3119 value, ok = QInputDialog.getText(
3120 self,
3121 f"添加{category}词库项",
3122 f"请输入新的{category}词条(纯中文):",
3123 QLineEdit.Normal
3124 )
3125
3126 if ok and value.strip():
3127 value = value.strip()
3128 try:
3129 self.library_manager.add_item(category, value)
3130 # 刷新下拉框
3131 self.refresh_combo_box(category)
3132 QMessageBox.information(self, "成功", f"已添加词库项: {value}")
3133 except Exception as e:
3134 self.logger.error(f"添加词库项失败: {e}")
3135 QMessageBox.warning(self, "错误", f"添加失败: {e}")
3136 else:
3137 self.logger.info(f"用户取消了添加{category}词库项的操作")
3138
3139 def remove_library_item(self, category: str):
3140 """删除词库项 UI"""
3141 combo = self.combo_boxes.get(category)
3142 if not combo:
3143 return
3144
3145 current_value = combo.currentText()
3146 if not current_value:
3147 QMessageBox.warning(self, "提示", "请先选择要删除的词库项")
3148 return
3149
3150 reply = QMessageBox.question(
3151 self,
3152 "确认删除",
3153 f"确定要删除词库项 \"{current_value}\" 吗?",
3154 QMessageBox.Yes | QMessageBox.No
3155 )
3156
3157 if reply == QMessageBox.Yes:
3158 try:
3159 self.library_manager.remove_item(category, current_value)
3160 # 刷新下拉框
3161 self.refresh_combo_box(category)
3162 QMessageBox.information(self, "成功", f"已删除词库项: {current_value}")
3163 except Exception as e:
3164 QMessageBox.warning(self, "错误", f"删除失败: {e}")
3165
3166 def reset_category_library(self, category: str):
3167 """恢复单个类别的默认词库 UI"""
3168 reply = QMessageBox.question(
3169 self,
3170 "确认恢复",
3171 f"确定要恢复 \"{category}\" 的默认词库吗?\n这将删除所有自定义词条。",
3172 QMessageBox.Yes | QMessageBox.No
3173 )
3174
3175 if reply == QMessageBox.Yes:
3176 try:
3177 self.library_manager.reset_category(category)
3178 # 刷新下拉框
3179 self.refresh_combo_box(category)
3180 QMessageBox.information(self, "成功", f"已恢复 {category} 的默认词库")
3181 except Exception as e:
3182 QMessageBox.warning(self, "错误", f"恢复失败: {e}")
3183
3184 def reset_all_library(self):
3185 """恢复所有字段的默认词库 UI"""
3186 reply = QMessageBox.question(
3187 self,
3188 "确认恢复所有默认词库",
3189 "确定要恢复所有字段的默认词库吗?\n这将删除所有自定义词条。",
3190 QMessageBox.Yes | QMessageBox.No
3191 )
3192
3193 if reply == QMessageBox.Yes:
3194 try:
3195 self.library_manager.reset_all()
3196 # 刷新所有下拉框
3197 for category in self.categories:
3198 self.refresh_combo_box(category)
3199 QMessageBox.information(self, "成功", "已恢复所有默认词库")
3200 except Exception as e:
3201 QMessageBox.warning(self, "错误", f"恢复失败: {e}")
3202
3203 def open_fullsize_view(self, event):
3204 """双击打开完整尺寸查看器"""
3205 if not self.generated_image_bytes:
3206 QMessageBox.warning(self, "提示", "没有可预览的图片")
3207 return
3208
3209 # 保存为临时文件并使用系统查看器打开
3210 import tempfile
3211 with tempfile.NamedTemporaryFile(suffix='.png', delete=False) as tmp_file:
3212 tmp_file.write(self.generated_image_bytes)
3213 temp_path = Path(tmp_file.name)
3214
3215 # 使用系统默认程序打开
3216 QDesktopServices.openUrl(QUrl.fromLocalFile(str(temp_path)))
3217
3218 def refresh_combo_box(self, category: str):
3219 """刷新指定类别的下拉框"""
3220 combo = self.combo_boxes.get(category)
3221 if not combo:
3222 return
3223
3224 # 保存当前选中值
3225 current_value = combo.currentText()
3226
3227 # 清空并重新加载
3228 combo.clear()
3229 combo.addItem("") # 空选项
3230 items = self.library_manager.library.get(category, [])
3231 combo.addItems(items)
3232
3233 # 尝试恢复选中值
3234 index = combo.findText(current_value)
3235 if index >= 0:
3236 combo.setCurrentIndex(index)
3237 else:
3238 combo.setCurrentIndex(0)
3239
3240 def generate_image(self):
3241 """调用文生图 API"""
3242 # 获取 prompt
3243 prompt = self.prompt_preview.toPlainText()
3244 if not prompt.strip():
3245 QMessageBox.warning(self, "提示", "Prompt 不能为空")
3246 return
3247
3248 # 获取设置
3249 aspect_ratio = self.aspect_ratio.currentText()
3250 image_size = self.image_size.currentText()
3251 model = "imagen-3.0-generate-002" # 硬编码使用默认模型
3252
3253 # 获取父窗口的 API key
3254 if not hasattr(self.parent_window, 'api_key') or not self.parent_window.api_key:
3255 QMessageBox.warning(self, "错误", "未找到 API 密钥,请在主窗口配置")
3256 return
3257
3258 # 非阻塞状态提示
3259 self.generate_btn.setEnabled(False)
3260 self.status_label.setText("● 生成中...")
3261 QApplication.processEvents()
3262
3263 try:
3264 # 创建生成线程
3265 self.gen_thread = ImageGenerationWorker(
3266 api_key=self.parent_window.api_key,
3267 prompt=prompt,
3268 images=[],
3269 aspect_ratio=aspect_ratio,
3270 image_size=image_size,
3271 model=model
3272 )
3273 self.gen_thread.finished.connect(self.on_generation_success)
3274 self.gen_thread.error.connect(self.on_generation_error)
3275 self.gen_thread.start()
3276
3277 except Exception as e:
3278 self.generate_btn.setEnabled(True)
3279 self.status_label.setText("● 就绪")
3280 QMessageBox.critical(self, "错误", f"生成失败: {e}")
3281
3282 def on_generation_success(self, image_bytes: bytes):
3283 """生成成功回调"""
3284 # 恢复按钮状态
3285 self.generate_btn.setEnabled(True)
3286 self.status_label.setText("● 就绪")
3287
3288 self.generated_image_bytes = image_bytes
3289
3290 # 启用下载按钮
3291 self.download_btn.setEnabled(True)
3292
3293 # 显示图片
3294 pixmap = QPixmap()
3295 pixmap.loadFromData(image_bytes)
3296
3297 # 清除之前的文本
3298 self.result_label.setText("")
3299
3300 # 获取标签可用空间
3301 label_size = self.result_label.size()
3302 if label_size.width() < 100 or label_size.height() < 100:
3303 # 如果标签还没有正确的尺寸,使用默认尺寸
3304 label_size = QSize(400, 300)
3305
3306 # 缩放图片以适应显示区域,保持宽高比且完整显示
3307 scaled_pixmap = pixmap.scaled(
3308 label_size,
3309 Qt.KeepAspectRatio,
3310 Qt.SmoothTransformation
3311 )
3312
3313 # 设置缩放后的图片,并确保居中对齐
3314 self.result_label.setPixmap(scaled_pixmap)
3315 self.result_label.setAlignment(Qt.AlignCenter)
3316
3317 # 保存到历史记录
3318 try:
3319 # 添加到历史记录管理器
3320 if hasattr(self.parent_window, 'history_manager'):
3321 timestamp = self.parent_window.history_manager.save_generation(
3322 image_bytes=image_bytes,
3323 prompt=self.prompt_preview.toPlainText(),
3324 reference_images=[], # 款式设计无参考图
3325 aspect_ratio=self.aspect_ratio.currentText(),
3326 image_size=self.image_size.currentText(),
3327 model="imagen-3.0-generate-002"
3328 )
3329 self.logger.info(f"款式设计已添加到历史记录: {timestamp}")
3330
3331 # 刷新历史记录列表
3332 if hasattr(self.parent_window, 'refresh_history'):
3333 self.parent_window.refresh_history()
3334 else:
3335 self.logger.warning("未找到历史记录管理器")
3336
3337 except Exception as e:
3338 self.logger.error(f"保存历史记录失败: {e}")
3339
3340 # 更新状态提示
3341 self.status_label.setText("● 图片生成成功")
3342 self.status_label.setStyleSheet("QLabel { color: #34C759; }")
3343
3344 def on_generation_error(self, error_msg: str):
3345 """生成失败回调"""
3346 # 恢复按钮状态
3347 self.generate_btn.setEnabled(True)
3348 self.status_label.setText("● 就绪")
3349 QMessageBox.critical(self, "生成失败", error_msg)
3350
3351 def download_image(self):
3352 """下载图片"""
3353 if not self.generated_image_bytes:
3354 QMessageBox.warning(self, "提示", "没有可下载的图片")
3355 return
3356
3357 file_path, _ = QFileDialog.getSaveFileName(
3358 self,
3359 "保存图片",
3360 f"jewelry_design_{datetime.now().strftime('%Y%m%d%H%M%S')}.png",
3361 "PNG Files (*.png)"
3362 )
3363
3364 if file_path:
3365 try:
3366 with open(file_path, 'wb') as f:
3367 f.write(self.generated_image_bytes)
3368 # 使用状态栏提示而不是弹窗
3369 self.status_label.setText("● 图片已保存")
3370 self.status_label.setStyleSheet("QLabel { color: #34C759; }")
3371 except Exception as e:
3372 QMessageBox.critical(self, "错误", f"保存失败: {e}")
3373
3374 def preview_image(self):
3375 """预览大图"""
3376 if not self.generated_image_bytes:
3377 QMessageBox.warning(self, "提示", "没有可预览的图片")
3378 return
3379
3380 # 创建预览对话框
3381 dialog = QDialog(self)
3382 dialog.setWindowTitle("预览大图")
3383 dialog.resize(800, 600)
3384
3385 layout = QVBoxLayout()
3386
3387 # 图片标签
3388 label = QLabel()
3389 pixmap = QPixmap()
3390 pixmap.loadFromData(self.generated_image_bytes)
3391 label.setPixmap(pixmap)
3392 label.setScaledContents(True)
3393
3394 # 滚动区域
3395 scroll = QScrollArea()
3396 scroll.setWidget(label)
3397 scroll.setWidgetResizable(True)
3398 layout.addWidget(scroll)
3399
3400 # 关闭按钮
3401 close_btn = QPushButton("关闭")
3402 close_btn.clicked.connect(dialog.close)
3403 layout.addWidget(close_btn)
3404
3405 dialog.setLayout(layout)
3406 dialog.exec()
3407
3408
2530 def main(): 3409 def main():
2531 """Main application entry point""" 3410 """Main application entry point"""
2532 # 初始化日志系统 3411 # 初始化日志系统
......
1 {
2 "主石": [
3 "椭圆形黑发晶",
4 "圆形莫桑石",
5 "祖母绿形红宝石",
6 "梨形蓝宝石",
7 "垫形绿碧玺",
8 "椭圆形黄水晶带天然包裹体",
9 "公主方形钻石",
10 "心形粉红蓝宝石"
11 ],
12 "金属": [
13 "14K黄金",
14 "18K玫瑰金",
15 "14K白金",
16 "铂金(PT950)",
17 "双色14K黄金+白金",
18 "950银镀铑",
19 "钛金属"
20 ],
21 "花头形式": [
22 "花卉风格光环",
23 "经典圆形光环",
24 "复古风格米粒边光环",
25 "双层光环",
26 "几何六边形光环",
27 "非对称光环",
28 "简约爪镶无光环"
29 ],
30 "戒臂结构": [
31 "扭转戒臂",
32 "分裂戒臂",
33 "刀刃戒臂",
34 "大教堂戒臂",
35 "交叉戒臂",
36 "直线平滑戒臂",
37 "三股编织戒臂"
38 ],
39 "戒臂处理": [
40 "雕刻镂空花丝工艺",
41 "密钉镶戒臂",
42 "抛光平滑戒臂",
43 "浮雕雕刻",
44 "编织纹理",
45 "极简干净戒臂",
46 "穿孔镂空细节",
47 "锤纹处理"
48 ],
49 "特殊元素": [
50 "凯尔特结图案",
51 "装饰艺术几何元素",
52 "自然风格叶子图案",
53 "复古米粒边细节",
54 "哥特式图案元素",
55 "天体图案(星星、月亮)",
56 "藤蔓装饰曲线",
57 "蝴蝶结装饰"
58 ],
59 "辅石镶嵌": [
60 "密钉镶",
61 "微密钉镶",
62 "槽镶",
63 "珠粒镶",
64 "共爪镶",
65 "包镶",
66 "轨道镶"
67 ],
68 "主石形状": [
69 "椭圆形",
70 "圆形",
71 "祖母绿形",
72 "梨形",
73 "垫形",
74 "公主方形",
75 "心形"
76 ],
77 "主石材质": [
78 "黑发晶",
79 "莫桑石",
80 "红宝石",
81 "蓝宝石",
82 "绿碧玺",
83 "黄水晶带天然包裹体",
84 "钻石",
85 "粉红蓝宝石"
86 ]
87 }
...\ No newline at end of file ...\ No newline at end of file