nanobanana初版
Showing
2 changed files
with
74 additions
and
28 deletions
config.json
0 → 100644
| 1 | { | ||
| 2 | "api_key": "AIzaSyBRIH8iItqTZXg7d4zAWVNhyit2CZbXWIU", | ||
| 3 | "saved_prompts": [ | ||
| 4 | "生成一张白人夫妻正常的买家秀展现我的戒指", | ||
| 5 | "主石换成闪耀的鸽血红红宝石,火彩非常好", | ||
| 6 | "生成一张白人夫妻在自家后院里的椅子坐着,亲密姿态,展现笑容,妻子手背捂嘴笑来展现我的戒指,两个人年龄大概40岁上下,男性微胖,女性短发,男士和女士脸上有一些痕迹,手要略显粗糙,普通人长相", | ||
| 7 | "一副美丽的嵖岈山风景,冬日清晨,初雪" | ||
| 8 | ], | ||
| 9 | "db_config": { | ||
| 10 | "host": "rm-uf6hx9ka75jdm2474xo.mysql.rds.aliyuncs.com", | ||
| 11 | "port": 3306, | ||
| 12 | "user": "root", | ||
| 13 | "password": "Np2KfoleLon3nqDpDbdjPa3Rqe", | ||
| 14 | "database": "saas_user", | ||
| 15 | "table": "nano_banana_users" | ||
| 16 | }, | ||
| 17 | "last_user": "testuser", | ||
| 18 | "saved_password_hash": "" | ||
| 19 | } | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file |
| ... | @@ -21,6 +21,7 @@ from google.genai import types | ... | @@ -21,6 +21,7 @@ from google.genai import types |
| 21 | import threading | 21 | import threading |
| 22 | import hashlib | 22 | import hashlib |
| 23 | import pymysql | 23 | import pymysql |
| 24 | from datetime import datetime | ||
| 24 | 25 | ||
| 25 | 26 | ||
| 26 | def hash_password(password: str) -> str: | 27 | def hash_password(password: str) -> str: |
| ... | @@ -384,13 +385,14 @@ class ImageGeneratorApp: | ... | @@ -384,13 +385,14 @@ class ImageGeneratorApp: |
| 384 | style.configure('Accent.TButton', | 385 | style.configure('Accent.TButton', |
| 385 | background=accent_color, | 386 | background=accent_color, |
| 386 | foreground='white', | 387 | foreground='white', |
| 387 | borderwidth=0, | 388 | borderwidth=1, |
| 389 | relief='solid', | ||
| 388 | focuscolor='none', | 390 | focuscolor='none', |
| 389 | font=('Segoe UI', 10), | 391 | font=('Segoe UI', 10), |
| 390 | padding=(18, 8)) | 392 | padding=(18, 8)) |
| 391 | 393 | ||
| 392 | style.map('Accent.TButton', | 394 | style.map('Accent.TButton', |
| 393 | background=[('active', hover_color), ('pressed', hover_color)], | 395 | background=[('active', hover_color), ('pressed', '#003D99')], |
| 394 | foreground=[('disabled', '#999999')]) | 396 | foreground=[('disabled', '#999999')]) |
| 395 | 397 | ||
| 396 | # Secondary button style | 398 | # Secondary button style |
| ... | @@ -398,23 +400,24 @@ class ImageGeneratorApp: | ... | @@ -398,23 +400,24 @@ class ImageGeneratorApp: |
| 398 | background=secondary_bg, | 400 | background=secondary_bg, |
| 399 | foreground=text_color, | 401 | foreground=text_color, |
| 400 | borderwidth=1, | 402 | borderwidth=1, |
| 401 | relief='flat', | 403 | relief='solid', |
| 402 | font=('Segoe UI', 9), | 404 | font=('Segoe UI', 9), |
| 403 | padding=(12, 6)) | 405 | padding=(12, 6)) |
| 404 | 406 | ||
| 405 | style.map('Secondary.TButton', | 407 | style.map('Secondary.TButton', |
| 406 | background=[('active', '#e8e8e8'), ('pressed', '#d8d8d8')]) | 408 | background=[('active', '#e8e8e8'), ('pressed', '#c8c8c8')]) |
| 407 | 409 | ||
| 408 | # Icon button style (small, subtle) | 410 | # Icon button style (small, subtle) |
| 409 | style.configure('Icon.TButton', | 411 | style.configure('Icon.TButton', |
| 410 | background=bg_color, | 412 | background=bg_color, |
| 411 | foreground='#666666', | 413 | foreground='#666666', |
| 412 | borderwidth=0, | 414 | borderwidth=1, |
| 415 | relief='solid', | ||
| 413 | font=('Segoe UI', 9), | 416 | font=('Segoe UI', 9), |
| 414 | padding=(4, 4)) | 417 | padding=(4, 4)) |
| 415 | 418 | ||
| 416 | style.map('Icon.TButton', | 419 | style.map('Icon.TButton', |
| 417 | background=[('active', secondary_bg)], | 420 | background=[('active', secondary_bg), ('pressed', '#d8d8d8')], |
| 418 | foreground=[('active', accent_color)]) | 421 | foreground=[('active', accent_color)]) |
| 419 | 422 | ||
| 420 | # Delete button style (visible with red hover) | 423 | # Delete button style (visible with red hover) |
| ... | @@ -470,6 +473,7 @@ class ImageGeneratorApp: | ... | @@ -470,6 +473,7 @@ class ImageGeneratorApp: |
| 470 | # 初始化默认值 | 473 | # 初始化默认值 |
| 471 | self.db_config = None | 474 | self.db_config = None |
| 472 | self.last_user = "" | 475 | self.last_user = "" |
| 476 | self.saved_password_hash = "" | ||
| 473 | 477 | ||
| 474 | # Try to load from user config first | 478 | # Try to load from user config first |
| 475 | if config_path.exists(): | 479 | if config_path.exists(): |
| ... | @@ -480,6 +484,7 @@ class ImageGeneratorApp: | ... | @@ -480,6 +484,7 @@ class ImageGeneratorApp: |
| 480 | self.saved_prompts = config.get("saved_prompts", []) | 484 | self.saved_prompts = config.get("saved_prompts", []) |
| 481 | self.db_config = config.get("db_config") | 485 | self.db_config = config.get("db_config") |
| 482 | self.last_user = config.get("last_user", "") | 486 | self.last_user = config.get("last_user", "") |
| 487 | self.saved_password_hash = config.get("saved_password_hash", "") | ||
| 483 | except Exception as e: | 488 | except Exception as e: |
| 484 | print(f"Failed to load config from {config_path}: {e}") | 489 | print(f"Failed to load config from {config_path}: {e}") |
| 485 | 490 | ||
| ... | @@ -525,6 +530,12 @@ class ImageGeneratorApp: | ... | @@ -525,6 +530,12 @@ class ImageGeneratorApp: |
| 525 | else: | 530 | else: |
| 526 | config["last_user"] = "" | 531 | config["last_user"] = "" |
| 527 | 532 | ||
| 533 | # 添加保存的密码哈希 | ||
| 534 | if hasattr(self, 'saved_password_hash'): | ||
| 535 | config["saved_password_hash"] = self.saved_password_hash | ||
| 536 | else: | ||
| 537 | config["saved_password_hash"] = "" | ||
| 538 | |||
| 528 | # Ensure directory exists | 539 | # Ensure directory exists |
| 529 | config_path.parent.mkdir(parents=True, exist_ok=True) | 540 | config_path.parent.mkdir(parents=True, exist_ok=True) |
| 530 | 541 | ||
| ... | @@ -590,7 +601,7 @@ class ImageGeneratorApp: | ... | @@ -590,7 +601,7 @@ class ImageGeneratorApp: |
| 590 | prompt_toolbar.pack(fill="x", pady=(0, 8)) | 601 | prompt_toolbar.pack(fill="x", pady=(0, 8)) |
| 591 | 602 | ||
| 592 | self.save_prompt_btn = ttk.Button(prompt_toolbar, text="⭐ 收藏", | 603 | self.save_prompt_btn = ttk.Button(prompt_toolbar, text="⭐ 收藏", |
| 593 | command=self.save_prompt, | 604 | command=self.toggle_favorite, |
| 594 | style='Icon.TButton') | 605 | style='Icon.TButton') |
| 595 | self.save_prompt_btn.pack(side="left", padx=(0, 5)) | 606 | self.save_prompt_btn.pack(side="left", padx=(0, 5)) |
| 596 | self.bind_hover_effect(self.save_prompt_btn) | 607 | self.bind_hover_effect(self.save_prompt_btn) |
| ... | @@ -617,6 +628,9 @@ class ImageGeneratorApp: | ... | @@ -617,6 +628,9 @@ class ImageGeneratorApp: |
| 617 | self.prompt_text.pack(fill="both", expand=True) | 628 | self.prompt_text.pack(fill="both", expand=True) |
| 618 | self.prompt_text.insert("1.0", "一幅美丽的风景画,有山有湖,日落时分") | 629 | self.prompt_text.insert("1.0", "一幅美丽的风景画,有山有湖,日落时分") |
| 619 | 630 | ||
| 631 | # Bind text change event to check favorite status | ||
| 632 | self.prompt_text.bind('<<Modified>>', self.on_prompt_change) | ||
| 633 | |||
| 620 | # Right: Settings Section | 634 | # Right: Settings Section |
| 621 | settings_container = ttk.LabelFrame(content_row, text="生成设置", padding=12) | 635 | settings_container = ttk.LabelFrame(content_row, text="生成设置", padding=12) |
| 622 | settings_container.pack(side="right", fill="y") | 636 | settings_container.pack(side="right", fill="y") |
| ... | @@ -633,14 +647,7 @@ class ImageGeneratorApp: | ... | @@ -633,14 +647,7 @@ class ImageGeneratorApp: |
| 633 | self.image_size = ttk.Combobox(settings_container, width=18, state="readonly") | 647 | self.image_size = ttk.Combobox(settings_container, width=18, state="readonly") |
| 634 | self.image_size['values'] = ("1K", "2K", "4K") | 648 | self.image_size['values'] = ("1K", "2K", "4K") |
| 635 | self.image_size.current(1) | 649 | self.image_size.current(1) |
| 636 | self.image_size.pack(fill="x", pady=(0, 12)) | 650 | self.image_size.pack(fill="x", pady=(0, 0)) |
| 637 | |||
| 638 | # Model | ||
| 639 | ttk.Label(settings_container, text="AI 模型", foreground='#666666').pack(anchor="w", pady=(0, 4)) | ||
| 640 | self.model = ttk.Combobox(settings_container, width=18, state="readonly") | ||
| 641 | self.model['values'] = ("gemini-3-pro-image-preview") | ||
| 642 | self.model.current(0) | ||
| 643 | self.model.pack(fill="x", pady=(0, 0)) | ||
| 644 | 651 | ||
| 645 | # Action buttons | 652 | # Action buttons |
| 646 | action_frame = ttk.Frame(main_container) | 653 | action_frame = ttk.Frame(main_container) |
| ... | @@ -719,27 +726,42 @@ class ImageGeneratorApp: | ... | @@ -719,27 +726,42 @@ class ImageGeneratorApp: |
| 719 | else: | 726 | else: |
| 720 | self.saved_prompts_combo['values'] = [] | 727 | self.saved_prompts_combo['values'] = [] |
| 721 | 728 | ||
| 722 | def save_prompt(self): | 729 | def check_favorite_status(self): |
| 723 | """Save current prompt to favorites""" | 730 | """Check if current prompt is favorited and update button state""" |
| 731 | prompt = self.prompt_text.get("1.0", tk.END).strip() | ||
| 732 | if prompt in self.saved_prompts: | ||
| 733 | self.save_prompt_btn.config(text="✓ 已收藏") | ||
| 734 | else: | ||
| 735 | self.save_prompt_btn.config(text="⭐ 收藏") | ||
| 736 | |||
| 737 | def on_prompt_change(self, event=None): | ||
| 738 | """Callback when prompt text changes""" | ||
| 739 | # Clear the modified flag to avoid repeated triggers | ||
| 740 | self.prompt_text.edit_modified(False) | ||
| 741 | self.check_favorite_status() | ||
| 742 | |||
| 743 | def toggle_favorite(self): | ||
| 744 | """Toggle favorite status of current prompt""" | ||
| 724 | prompt = self.prompt_text.get("1.0", tk.END).strip() | 745 | prompt = self.prompt_text.get("1.0", tk.END).strip() |
| 725 | if not prompt: | 746 | if not prompt: |
| 726 | self.status_label.config(text="● 提示词不能为空", foreground='#FF3B30') | 747 | self.status_label.config(text="● 提示词不能为空", foreground='#FF3B30') |
| 727 | return | 748 | return |
| 728 | 749 | ||
| 729 | if prompt in self.saved_prompts: | 750 | if prompt in self.saved_prompts: |
| 730 | self.status_label.config(text="● 该提示词已收藏", foreground='#FF9500') | 751 | # Remove from favorites |
| 731 | self.save_prompt_btn.config(text="✓ 已收藏") | 752 | self.saved_prompts.remove(prompt) |
| 732 | # 2秒后恢复按钮文本 | 753 | self.save_config() |
| 733 | self.root.after(2000, lambda: self.save_prompt_btn.config(text="⭐ 收藏")) | 754 | self.update_saved_prompts_list() |
| 734 | return | 755 | self.status_label.config(text="● 该提示词已取消收藏", foreground='#34C759') |
| 735 | 756 | else: | |
| 757 | # Add to favorites | ||
| 736 | self.saved_prompts.append(prompt) | 758 | self.saved_prompts.append(prompt) |
| 737 | self.save_config() | 759 | self.save_config() |
| 738 | self.update_saved_prompts_list() | 760 | self.update_saved_prompts_list() |
| 739 | self.status_label.config(text="● 已收藏提示词", foreground='#34C759') | 761 | self.status_label.config(text="● 该提示词已收藏", foreground='#34C759') |
| 740 | self.save_prompt_btn.config(text="✓ 已收藏") | 762 | |
| 741 | # 2秒后恢复按钮文本 | 763 | # Update button state |
| 742 | self.root.after(2000, lambda: self.save_prompt_btn.config(text="⭐ 收藏")) | 764 | self.check_favorite_status() |
| 743 | 765 | ||
| 744 | def load_saved_prompt(self, event): | 766 | def load_saved_prompt(self, event): |
| 745 | """Load a saved prompt""" | 767 | """Load a saved prompt""" |
| ... | @@ -748,6 +770,7 @@ class ImageGeneratorApp: | ... | @@ -748,6 +770,7 @@ class ImageGeneratorApp: |
| 748 | self.prompt_text.delete("1.0", tk.END) | 770 | self.prompt_text.delete("1.0", tk.END) |
| 749 | self.prompt_text.insert("1.0", self.saved_prompts[index]) | 771 | self.prompt_text.insert("1.0", self.saved_prompts[index]) |
| 750 | self.status_label.config(text="● 已加载提示词", foreground='#007AFF') | 772 | self.status_label.config(text="● 已加载提示词", foreground='#007AFF') |
| 773 | self.check_favorite_status() | ||
| 751 | 774 | ||
| 752 | def delete_saved_prompt(self): | 775 | def delete_saved_prompt(self): |
| 753 | """Delete the currently selected saved prompt""" | 776 | """Delete the currently selected saved prompt""" |
| ... | @@ -929,7 +952,7 @@ class ImageGeneratorApp: | ... | @@ -929,7 +952,7 @@ class ImageGeneratorApp: |
| 929 | 952 | ||
| 930 | # Generate | 953 | # Generate |
| 931 | response = client.models.generate_content( | 954 | response = client.models.generate_content( |
| 932 | model=self.model.get(), | 955 | model="gemini-3-pro-image-preview", |
| 933 | contents=content_parts, | 956 | contents=content_parts, |
| 934 | config=config | 957 | config=config |
| 935 | ) | 958 | ) |
| ... | @@ -1028,8 +1051,12 @@ class ImageGeneratorApp: | ... | @@ -1028,8 +1051,12 @@ class ImageGeneratorApp: |
| 1028 | messagebox.showerror("错误", "没有可下载的图片!") | 1051 | messagebox.showerror("错误", "没有可下载的图片!") |
| 1029 | return | 1052 | return |
| 1030 | 1053 | ||
| 1054 | # 生成默认文件名: 时间戳格式 YYYYMMDDHHMMSS.png | ||
| 1055 | default_filename = datetime.now().strftime("%Y%m%d%H%M%S.png") | ||
| 1056 | |||
| 1031 | file_path = filedialog.asksaveasfilename( | 1057 | file_path = filedialog.asksaveasfilename( |
| 1032 | defaultextension=".png", | 1058 | defaultextension=".png", |
| 1059 | initialfile=default_filename, | ||
| 1033 | filetypes=[("PNG 文件", "*.png"), ("JPEG 文件", "*.jpg"), ("所有文件", "*.*")], | 1060 | filetypes=[("PNG 文件", "*.png"), ("JPEG 文件", "*.jpg"), ("所有文件", "*.*")], |
| 1034 | title="保存图片" | 1061 | title="保存图片" |
| 1035 | ) | 1062 | ) | ... | ... |
-
Please register or sign in to post a comment