feat: Add plugin configuration editing with template variable parsing

## Backend Changes:
- Enhanced PluginTemplate.save() to auto-parse template variables from plugin code
- Updated PluginInstallationSerializer to expose template metadata (description, category, version, author, logo, template_variables)
- Fixed template variable parser to handle nested {{ }} braces in default values
- Added brace-counting algorithm to properly extract variables with insertion codes
- Fixed explicit type parameter detection (textarea, text, email, etc.)
- Made scheduled_task optional on PluginInstallation model
- Added EventPlugin through model for event-plugin relationships
- Added Event.execute_plugins() method for plugin automation

## Frontend Changes:
- Created Tasks.tsx page for managing scheduled tasks
- Enhanced MyPlugins page with clickable plugin cards
- Added edit configuration modal with dynamic form generation
- Implemented escape sequence handling (convert \n, \', etc. for display)
- Added plugin logos to My Plugins page
- Updated type definitions for PluginInstallation interface
- Added insertion code documentation to Plugin Docs

## Plugin System:
- All platform plugins now have editable email templates with textarea support
- Template variables properly parsed with full default values
- Insertion codes ({{CUSTOMER_NAME}}, {{BUSINESS_NAME}}, etc.) documented
- Plugin logos displayed in marketplace and My Plugins

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
poduck
2025-11-28 23:45:55 -05:00
parent 0f46862125
commit 9b106bf129
38 changed files with 2859 additions and 116 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 993 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-bell"><path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"></path><path d="M13.73 21a2 2 0 0 1-3.46 0"></path></svg>

After

Width:  |  Height:  |  Size: 299 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-database"><ellipse cx="12" cy="5" rx="9" ry="3"></ellipse><path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3"></path><path d="M3 5v14c0 1.66 4 3 9 3s 9-1.34 9-3V5"></path></svg>

After

Width:  |  Height:  |  Size: 351 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 840 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-trash-2"><polyline points="3 6 5 6 21 6"></polyline><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path><line x1="10" y1="11" x2="10" y2="17"></line><line x1="14" y1="11" x2="14" y2="17"></line></svg>

After

Width:  |  Height:  |  Size: 426 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file-text"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline><line x1="16" y1="13" x2="8" y2="13"></line><line x1="16" y1="17" x2="8" y2="17"></line><polyline points="10 9 9 9 8 9"></polyline></svg>

After

Width:  |  Height:  |  Size: 451 B

View File

@@ -0,0 +1,132 @@
#!/usr/bin/env python3
from google import genai
from google.genai import types
from PIL import Image
from io import BytesIO
import os
import time
# Setup Client
client = genai.Client(api_key="AIzaSyB-nR0nkeftKrd42NrNIDcFCj3yFP8JLtw")
OUTPUT_DIR = "/home/poduck/Desktop/smoothschedule2/frontend/public/plugin-logos"
# Plugin configurations with detailed prompts
plugins = [
{
'filename': 'daily-appointment-summary.png',
'prompt': '''Create a modern, minimalist app icon in square format with rounded corners.
Design: Indigo blue gradient background (#4f46e5 to lighter blue).
Icon: Simple white envelope overlaid with a small calendar icon in the corner.
Style: Flat design, clean geometric shapes, professional SaaS aesthetic.
The icon should be crisp and recognizable at 48x48 pixels.'''
},
{
'filename': 'no-show-tracker.png',
'prompt': '''Create a modern, minimalist app icon in square format with rounded corners.
Design: Red gradient background (#dc2626 to darker red).
Icon: Simple white person silhouette with a bold white X symbol overlaid.
Style: Flat design, clean geometric shapes, professional warning aesthetic.
The icon should clearly convey "missed appointment" at small sizes.'''
},
{
'filename': 'birthday-greetings.png',
'prompt': '''Create a modern, minimalist app icon in square format with rounded corners.
Design: Pink gradient background (#ec4899 to lighter pink).
Icon: Simple white birthday cake with 3 candles or a white gift box with a bow.
Style: Flat design, clean geometric shapes, cheerful yet professional aesthetic.
The icon should feel celebratory and friendly at small sizes.'''
},
{
'filename': 'monthly-revenue-report.png',
'prompt': '''Create a modern, minimalist app icon in square format with rounded corners.
Design: Green gradient background (#10b981 to darker green).
Icon: Simple white upward trending line chart or ascending bar graph.
Style: Flat design, clean geometric shapes, professional business analytics aesthetic.
The icon should convey growth and success at small sizes.'''
},
{
'filename': 'appointment-reminder-24hr.png',
'prompt': '''Create a modern, minimalist app icon in square format with rounded corners.
Design: Amber/orange gradient background (#f59e0b to darker orange).
Icon: Simple white notification bell with a small red circular alert badge.
Style: Flat design, clean geometric shapes, attention-grabbing yet professional aesthetic.
The icon should clearly indicate alerts and reminders at small sizes.'''
},
{
'filename': 'inactive-customer-reengagement.png',
'prompt': '''Create a modern, minimalist app icon in square format with rounded corners.
Design: Purple gradient background (#8b5cf6 to darker purple).
Icon: Simple white heart symbol with a circular white refresh/return arrow around it.
Style: Flat design, clean geometric shapes, warm and welcoming professional aesthetic.
The icon should convey customer care and returning customers at small sizes.'''
}
]
def generate_logo(prompt, filename):
"""Generate a logo using Gemini 2.5 Flash Image"""
print(f"\nGenerating {filename}...")
try:
response = client.models.generate_content(
model='gemini-2.5-flash-image',
contents=prompt,
config=types.GenerateContentConfig(
response_modalities=["IMAGE"]
)
)
# Save the Image
if response.candidates[0].content.parts:
for part in response.candidates[0].content.parts:
if part.inline_data:
image_data = part.inline_data.data
image = Image.open(BytesIO(image_data))
# Save to output directory
output_path = os.path.join(OUTPUT_DIR, filename)
image.save(output_path)
print(f"✓ Saved: {output_path}")
return True
else:
print(f"✗ Model returned text instead of image: {part.text[:100]}")
return False
else:
print("✗ No content parts in response")
return False
except Exception as e:
error_msg = str(e)
if "RESOURCE_EXHAUSTED" in error_msg or "quota" in error_msg.lower():
print(f"✗ Quota exceeded. Please try again tomorrow when quota resets.")
print(f" Error: {error_msg[:200]}...")
else:
print(f"✗ Error: {type(e).__name__}: {error_msg[:200]}")
return False
def main():
os.makedirs(OUTPUT_DIR, exist_ok=True)
print("=" * 70)
print("Generating Plugin Logos with Gemini 2.5 Flash Image")
print("=" * 70)
success_count = 0
for i, plugin in enumerate(plugins):
if i > 0:
# Wait 3 seconds between requests to avoid rate limiting
print("\nWaiting 3 seconds before next request...")
time.sleep(3)
if generate_logo(plugin['prompt'], plugin['filename']):
success_count += 1
print("\n" + "=" * 70)
print(f"Generation complete: {success_count}/{len(plugins)} successful")
print("=" * 70)
if success_count == 0:
print("\nNote: If you hit quota limits, try again tomorrow when the quota resets!")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,77 @@
import os
import sys
try:
from PIL import Image, ImageDraw
except ImportError:
print("Error: Pillow library not found.")
print("Please install it using: pip install Pillow")
sys.exit(1)
# Configuration
OUTPUT_PATH = "/home/poduck/Desktop/smoothschedule2/frontend/public/plugin-logos/daily-appointment-summary.png"
SIZE = (144, 144) # Generated at 3x resolution (144px) for high quality on 48px displays
BG_COLOR = "#4f46e5" # Indigo/Blue
ICON_COLOR = "white"
def create_logo():
# Create a new image with a transparent background
img = Image.new('RGBA', SIZE, (0, 0, 0, 0))
draw = ImageDraw.Draw(img)
# 1. Draw Background (Rounded Square)
radius = 30
rect_bounds = [0, 0, SIZE[0], SIZE[1]]
draw.rounded_rectangle(rect_bounds, radius=radius, fill=BG_COLOR)
# 2. Draw Envelope Icon (Email concept)
# Centered, slightly offset up to make room for calendar
env_w = 90
env_h = 60
env_x = (SIZE[0] - env_w) // 2
env_y = (SIZE[1] - env_h) // 2 - 10
# Envelope body
draw.rectangle([env_x, env_y, env_x + env_w, env_y + env_h], outline=ICON_COLOR, width=5)
# Envelope flap (V shape)
draw.line([env_x, env_y, env_x + env_w // 2, env_y + env_h // 2 + 5], fill=ICON_COLOR, width=5)
draw.line([env_x + env_w, env_y, env_x + env_w // 2, env_y + env_h // 2 + 5], fill=ICON_COLOR, width=5)
# 3. Draw Calendar Badge (Scheduling concept)
# Bottom right corner
cal_size = 50
cal_x = env_x + env_w - (cal_size // 2)
cal_y = env_y + env_h - (cal_size // 2)
# Clear background behind calendar for separation
border = 4
draw.rounded_rectangle(
[cal_x - border, cal_y - border, cal_x + cal_size + border, cal_y + cal_size + border],
radius=10, fill=BG_COLOR
)
# Calendar body
draw.rounded_rectangle([cal_x, cal_y, cal_x + cal_size, cal_y + cal_size], radius=8, fill="white")
# Calendar red header (using a lighter indigo/blue to match theme)
header_h = 14
draw.rounded_rectangle(
[cal_x, cal_y, cal_x + cal_size, cal_y + header_h],
radius=8, corners=(True, True, False, False), fill="#818cf8"
)
# Calendar 'lines'
line_x = cal_x + 10
line_w = cal_size - 20
draw.line([line_x, cal_y + 22, line_x + line_w, cal_y + 22], fill=BG_COLOR, width=3)
draw.line([line_x, cal_y + 32, line_x + line_w, cal_y + 32], fill=BG_COLOR, width=3)
draw.line([line_x, cal_y + 42, line_x + line_w * 0.6, cal_y + 42], fill=BG_COLOR, width=3)
# Save the file
os.makedirs(os.path.dirname(OUTPUT_PATH), exist_ok=True)
img.save(OUTPUT_PATH)
print(f"Success! Logo saved to: {OUTPUT_PATH}")
if __name__ == "__main__":
create_logo()

View File

@@ -0,0 +1,155 @@
import os
import sys
try:
from PIL import Image, ImageDraw, ImageFont
except ImportError:
print("Error: Pillow library not found.")
print("Please install it using: pip install Pillow")
sys.exit(1)
# Configuration
OUTPUT_DIR = "/home/poduck/Desktop/smoothschedule2/frontend/public/plugin-logos"
SIZE = (144, 144)
RADIUS = 30
def create_base_logo(bg_color):
"""Creates the base image with rounded corners and background color."""
img = Image.new('RGBA', SIZE, (0, 0, 0, 0))
draw = ImageDraw.Draw(img)
rect_bounds = [0, 0, SIZE[0], SIZE[1]]
draw.rounded_rectangle(rect_bounds, radius=RADIUS, fill=bg_color)
return img, draw
def save_logo(img, filename):
"""Saves the image to the output directory."""
path = os.path.join(OUTPUT_DIR, filename)
os.makedirs(os.path.dirname(path), exist_ok=True)
img.save(path)
print(f"Saved: {path}")
# 1. No-Show Customer Tracker (Red, Person with X)
def create_no_show_logo():
img, draw = create_base_logo("#dc2626") # Red
# Person Silhouette
cx, cy = 72, 72
# Head
draw.ellipse([cx-15, 35, cx+15, 65], fill="white")
# Shoulders/Body
draw.rounded_rectangle([cx-30, 70, cx+30, 130], radius=10, fill="white")
# Cancel Symbol (Red X on white circle or just overlaid)
# Let's use a thick Red X overlay
x_center_y = 85
half = 20
width = 8
# Draw X
draw.line([cx - half, x_center_y - half, cx + half, x_center_y + half], fill="#dc2626", width=width)
draw.line([cx + half, x_center_y - half, cx - half, x_center_y + half], fill="#dc2626", width=width)
save_logo(img, "no-show-tracker.png")
# 2. Birthday Greeting Campaign (Pink, Gift)
def create_birthday_logo():
img, draw = create_base_logo("#ec4899") # Pink
# Gift Box
box_w, box_h = 70, 60
box_x = (SIZE[0] - box_w) // 2
box_y = (SIZE[1] - box_h) // 2 + 10
# Box body
draw.rectangle([box_x, box_y, box_x + box_w, box_y + box_h], fill="white")
# Ribbons (Lighter Pink)
r_w = 12
ribbon_color = "#fbcfe8"
# Vertical
draw.rectangle([box_x + (box_w - r_w)//2, box_y, box_x + (box_w + r_w)//2, box_y + box_h], fill=ribbon_color)
# Horizontal
draw.rectangle([box_x, box_y + (box_h - r_w)//2, box_x + box_w, box_y + (box_h + r_w)//2], fill=ribbon_color)
# Bow
bow_w = 20
draw.ellipse([72 - bow_w, box_y - 15, 72, box_y], fill="white")
draw.ellipse([72, box_y - 15, 72 + bow_w, box_y], fill="white")
save_logo(img, "birthday-greetings.png")
# 3. Monthly Revenue Report (Green, Chart)
def create_revenue_logo():
img, draw = create_base_logo("#10b981") # Green
# Bar Chart
margin_bottom = 110
bar_w = 20
gap = 10
start_x = (144 - (3 * bar_w + 2 * gap)) // 2
# Bars
heights = [30, 50, 75]
for i, h in enumerate(heights):
x = start_x + i * (bar_w + gap)
draw.rectangle([x, margin_bottom - h, x + bar_w, margin_bottom], fill="white")
# Trend Arrow (Rising)
arrow_start = (start_x - 5, margin_bottom - 10)
arrow_end = (start_x + 3 * bar_w + 2 * gap + 5, margin_bottom - 90)
# Simple line for trend
# draw.line([arrow_start, arrow_end], fill="white", width=4)
save_logo(img, "monthly-revenue-report.png")
# 4. Appointment Reminder (24hr) (Amber, Bell)
def create_reminder_logo():
img, draw = create_base_logo("#f59e0b") # Amber
cx, cy = 72, 70
r = 30
# Bell Dome
draw.chord([cx - r, cy - r, cx + r, cy + r], 180, 0, fill="white")
draw.rectangle([cx - r, cy, cx + r, cy + 20], fill="white")
# Bell Flare
draw.polygon([(cx - r, cy + 20), (cx + r, cy + 20), (cx + r + 5, cy + 30), (cx - r - 5, cy + 30)], fill="white")
# Clapper
draw.ellipse([cx - 8, cy + 28, cx + 8, cy + 42], fill="white")
# Notification Dot
draw.ellipse([cx + 15, cy - 25, cx + 35, cy - 5], fill="#dc2626")
save_logo(img, "appointment-reminder-24hr.png")
# 5. Inactive Customer Re-engagement (Purple, Heart)
def create_inactive_logo():
img, draw = create_base_logo("#8b5cf6") # Purple
hx, hy = 72, 75
size = 18
# Heart Shape
# Left circle
draw.ellipse([hx - size * 2, hy - size, hx, hy + size], fill="white")
# Right circle
draw.ellipse([hx, hy - size, hx + size * 2, hy + size], fill="white")
# Bottom triangle
draw.polygon([(hx - size * 2 + 1, hy + 4), (hx + size * 2 - 1, hy + 4), (hx, hy + size * 2 + 8)], fill="white")
# Refresh/Loop Arrow (Subtle arc above)
bbox = [25, 25, 119, 119]
draw.arc(bbox, start=180, end=0, fill="#ddd6fe", width=5)
draw.polygon([(119, 72), (110, 60), (128, 60)], fill="#ddd6fe")
save_logo(img, "inactive-customer-reengagement.png")
if __name__ == "__main__":
create_no_show_logo()
create_birthday_logo()
create_revenue_logo()
create_reminder_logo()
create_inactive_logo()

View File

@@ -0,0 +1,39 @@
#!/usr/bin/env python3
from google import genai
from google.genai import types
from PIL import Image
from io import BytesIO
# Setup Client
client = genai.Client(api_key="AIzaSyB-nR0nkeftKrd42NrNIDcFCj3yFP8JLtw")
# Test with a simple prompt
prompt = "Create a simple modern app icon with a blue background and white envelope symbol, square format, flat design, minimalist"
print(f"Testing gemini-2.0-flash-exp...")
print(f"Prompt: '{prompt}'...")
try:
response = client.models.generate_content(
model='gemini-2.0-flash-exp',
contents=prompt,
config=types.GenerateContentConfig(
response_modalities=["IMAGE"]
)
)
# Save the Image
if response.candidates[0].content.parts:
for part in response.candidates[0].content.parts:
if part.inline_data:
image_data = part.inline_data.data
image = Image.open(BytesIO(image_data))
image.save("test_2_0_flash.png")
print("✓ Success! Saved test_2_0_flash.png")
else:
print(f"✗ Model returned text: {part.text[:100]}")
else:
print("✗ No content parts in response")
except Exception as e:
print(f"✗ Error: {type(e).__name__}: {str(e)[:300]}")

View File

@@ -0,0 +1,194 @@
#!/usr/bin/env python3
import os
import sys
import requests
import base64
from pathlib import Path
# Gemini API configuration
API_KEY = "AIzaSyB-nR0nkeftKrd42NrNIDcFCj3yFP8JLtw"
API_URL = "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash-exp:generateContent"
OUTPUT_DIR = "/home/poduck/Desktop/smoothschedule2/frontend/public/plugin-logos"
# Plugin configurations
plugins = [
{
'filename': 'daily-appointment-summary.png',
'prompt': '''Create a modern, professional icon/logo for a plugin called "Daily Appointment Summary Email".
Design requirements:
- Square icon, 512x512 pixels
- Flat design style with a slight gradient
- Primary color: Indigo/blue (#4f46e5)
- Icon should combine email and calendar/scheduling concepts
- Clean, minimalist design suitable for a SaaS application
- White or light elements on the colored background
- Rounded corners (similar to modern app icons)
- Professional and trustworthy appearance
The icon represents a plugin that sends daily email summaries of appointments to staff members.'''
},
{
'filename': 'no-show-tracker.png',
'prompt': '''Create a modern, professional icon/logo for a plugin called "No-Show Customer Tracker".
Design requirements:
- Square icon, 512x512 pixels
- Flat design style with a slight gradient
- Primary color: Red (#dc2626)
- Icon should represent missed appointments or absent customers
- Could show a person silhouette with an X, slash, or cancel symbol
- Clean, minimalist design suitable for a SaaS application
- White or light elements on the colored background
- Rounded corners (similar to modern app icons)
- Professional appearance
The icon represents a plugin that tracks customers who miss their appointments.'''
},
{
'filename': 'birthday-greetings.png',
'prompt': '''Create a modern, professional icon/logo for a plugin called "Birthday Greeting Campaign".
Design requirements:
- Square icon, 512x512 pixels
- Flat design style with a slight gradient
- Primary color: Pink (#ec4899)
- Icon should represent birthdays and celebrations
- Could show a birthday cake, gift, party hat, or balloon
- Clean, minimalist design suitable for a SaaS application
- White or light elements on the colored background
- Rounded corners (similar to modern app icons)
- Friendly and celebratory appearance
The icon represents a plugin that sends birthday emails with special offers to customers.'''
},
{
'filename': 'monthly-revenue-report.png',
'prompt': '''Create a modern, professional icon/logo for a plugin called "Monthly Revenue Report".
Design requirements:
- Square icon, 512x512 pixels
- Flat design style with a slight gradient
- Primary color: Green (#10b981)
- Icon should represent business growth, analytics, and financial reporting
- Could show an upward trending chart, graph, or money symbol
- Clean, minimalist design suitable for a SaaS application
- White or light elements on the colored background
- Rounded corners (similar to modern app icons)
- Professional and successful appearance
The icon represents a plugin that generates comprehensive monthly business statistics and revenue reports.'''
},
{
'filename': 'appointment-reminder-24hr.png',
'prompt': '''Create a modern, professional icon/logo for a plugin called "Appointment Reminder (24hr)".
Design requirements:
- Square icon, 512x512 pixels
- Flat design style with a slight gradient
- Primary color: Amber/Orange (#f59e0b)
- Icon should represent notifications, alerts, and reminders
- Could show a bell with a notification badge, clock, or alarm
- Clean, minimalist design suitable for a SaaS application
- White or light elements on the colored background
- Rounded corners (similar to modern app icons)
- Attention-grabbing but professional appearance
The icon represents a plugin that sends reminder emails to customers 24 hours before their appointments.'''
},
{
'filename': 'inactive-customer-reengagement.png',
'prompt': '''Create a modern, professional icon/logo for a plugin called "Inactive Customer Re-engagement".
Design requirements:
- Square icon, 512x512 pixels
- Flat design style with a slight gradient
- Primary color: Purple (#8b5cf6)
- Icon should represent customer retention, returning customers, or re-engagement
- Could show a heart, person with return arrow, refresh symbol, or comeback concept
- Clean, minimalist design suitable for a SaaS application
- White or light elements on the colored background
- Rounded corners (similar to modern app icons)
- Warm and welcoming appearance
The icon represents a plugin that wins back customers who haven't booked appointments recently.'''
}
]
def generate_image(prompt, filename):
"""Generate an image using Gemini API"""
print(f"\nGenerating {filename}...")
headers = {
"Content-Type": "application/json"
}
payload = {
"contents": [{
"parts": [{
"text": prompt
}]
}],
"generationConfig": {
"temperature": 1,
"topK": 40,
"topP": 0.95,
"maxOutputTokens": 8192,
}
}
try:
response = requests.post(
f"{API_URL}?key={API_KEY}",
headers=headers,
json=payload,
timeout=120
)
if response.status_code == 200:
result = response.json()
# Check if there's an image in the response
if 'candidates' in result and len(result['candidates']) > 0:
candidate = result['candidates'][0]
if 'content' in candidate and 'parts' in candidate['content']:
for part in candidate['content']['parts']:
if 'inlineData' in part:
# Extract and save the image
image_data = base64.b64decode(part['inlineData']['data'])
output_path = os.path.join(OUTPUT_DIR, filename)
with open(output_path, 'wb') as f:
f.write(image_data)
print(f"✓ Saved: {output_path}")
return True
elif 'text' in part:
print(f"Response: {part['text'][:200]}...")
print(f"✗ No image generated. Response: {result}")
return False
else:
print(f"✗ API Error ({response.status_code}): {response.text}")
return False
except Exception as e:
print(f"✗ Error: {str(e)}")
return False
def main():
# Create output directory
os.makedirs(OUTPUT_DIR, exist_ok=True)
print("Starting logo generation with Gemini API...")
print("=" * 60)
success_count = 0
for plugin in plugins:
if generate_image(plugin['prompt'], plugin['filename']):
success_count += 1
print("\n" + "=" * 60)
print(f"Generation complete: {success_count}/{len(plugins)} successful")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,112 @@
#!/usr/bin/env python3
import google.generativeai as genai
import os
from PIL import Image
import io
# Configure API Key
genai.configure(api_key="AIzaSyB-nR0nkeftKrd42NrNIDcFCj3yFP8JLtw")
OUTPUT_DIR = "/home/poduck/Desktop/smoothschedule2/frontend/public/plugin-logos"
# Plugin configurations
plugins = [
{
'filename': 'daily-appointment-summary.png',
'prompt': '''Create a modern, minimalist app icon in a square format with rounded corners.
Design: Indigo blue gradient background (#4f46e5). White simple envelope icon combined with a small calendar symbol.
Style: Flat design, clean geometric shapes, professional SaaS application aesthetic.
The icon should be instantly recognizable at 48x48 pixels.'''
},
{
'filename': 'no-show-tracker.png',
'prompt': '''Create a modern, minimalist app icon in a square format with rounded corners.
Design: Red gradient background (#dc2626). White simple person silhouette with a bold X or cancel symbol overlay.
Style: Flat design, clean geometric shapes, professional SaaS application aesthetic.
The icon should clearly convey "missed appointment" at small sizes.'''
},
{
'filename': 'birthday-greetings.png',
'prompt': '''Create a modern, minimalist app icon in a square format with rounded corners.
Design: Pink gradient background (#ec4899). White simple birthday cake with candles or gift box with bow.
Style: Flat design, clean geometric shapes, cheerful yet professional aesthetic.
The icon should feel celebratory and friendly at small sizes.'''
},
{
'filename': 'monthly-revenue-report.png',
'prompt': '''Create a modern, minimalist app icon in a square format with rounded corners.
Design: Green gradient background (#10b981). White simple upward trending line chart or bar graph showing growth.
Style: Flat design, clean geometric shapes, professional business analytics aesthetic.
The icon should convey success and growth at small sizes.'''
},
{
'filename': 'appointment-reminder-24hr.png',
'prompt': '''Create a modern, minimalist app icon in a square format with rounded corners.
Design: Amber/orange gradient background (#f59e0b). White simple notification bell with a small red alert dot or badge.
Style: Flat design, clean geometric shapes, attention-grabbing yet professional aesthetic.
The icon should clearly indicate alerts and reminders at small sizes.'''
},
{
'filename': 'inactive-customer-reengagement.png',
'prompt': '''Create a modern, minimalist app icon in a square format with rounded corners.
Design: Purple gradient background (#8b5cf6). White simple heart symbol with a circular refresh/return arrow around it.
Style: Flat design, clean geometric shapes, warm and welcoming professional aesthetic.
The icon should convey customer care and comeback at small sizes.'''
}
]
def generate_image(prompt, filename):
"""Generate an image using Gemini 2.0 Flash Image Generation"""
print(f"\nGenerating {filename}...")
try:
# Use the image generation model
model = genai.GenerativeModel('gemini-2.0-flash-exp-image-generation')
# Generate the image
response = model.generate_content(prompt)
# Check for generated image
if hasattr(response, 'parts') and response.parts:
for part in response.parts:
if hasattr(part, 'inline_data') and part.inline_data:
# Extract image data
image_data = part.inline_data.data
# Save the image
output_path = os.path.join(OUTPUT_DIR, filename)
with open(output_path, 'wb') as f:
f.write(image_data)
print(f"✓ Saved: {output_path} ({len(image_data)} bytes)")
return True
# If no image data found, check if there's text response
if hasattr(response, 'text'):
print(f"✗ No image generated. Response text: {response.text[:200]}")
else:
print(f"✗ No image generated. Response: {response}")
return False
except Exception as e:
print(f"✗ Error: {type(e).__name__}: {str(e)}")
return False
def main():
# Create output directory
os.makedirs(OUTPUT_DIR, exist_ok=True)
print("Starting logo generation with Gemini 2.0 Flash Image Generation...")
print("=" * 60)
success_count = 0
for plugin in plugins:
if generate_image(plugin['prompt'], plugin['filename']):
success_count += 1
print("\n" + "=" * 60)
print(f"Generation complete: {success_count}/{len(plugins)} successful")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,146 @@
#!/usr/bin/env python3
import os
import sys
import requests
import base64
import time
from pathlib import Path
# Imagen API configuration
API_KEY = "AIzaSyB-nR0nkeftKrd42NrNIDcFCj3yFP8JLtw"
MODEL = "imagen-4.0-generate-preview-06-06"
API_URL = f"https://generativelanguage.googleapis.com/v1beta/models/{MODEL}:predict"
OUTPUT_DIR = "/home/poduck/Desktop/smoothschedule2/frontend/public/plugin-logos"
# Plugin configurations
plugins = [
{
'filename': 'daily-appointment-summary.png',
'prompt': '''A modern, minimalist app icon for "Daily Appointment Summary Email" plugin.
Square format with rounded corners. Indigo blue gradient background (#4f46e5).
White icon showing a combination of an envelope and a calendar.
Flat design, clean lines, professional SaaS application style.
The icon should be simple and recognizable at small sizes.'''
},
{
'filename': 'no-show-tracker.png',
'prompt': '''A modern, minimalist app icon for "No-Show Customer Tracker" plugin.
Square format with rounded corners. Red gradient background (#dc2626).
White icon showing a person silhouette with an X or cancel symbol overlay.
Flat design, clean lines, professional SaaS application style.
The icon should convey missed appointments clearly at small sizes.'''
},
{
'filename': 'birthday-greetings.png',
'prompt': '''A modern, minimalist app icon for "Birthday Greeting Campaign" plugin.
Square format with rounded corners. Pink gradient background (#ec4899).
White icon showing a birthday cake or gift box with a bow.
Flat design, clean lines, cheerful yet professional style.
The icon should be celebratory and friendly at small sizes.'''
},
{
'filename': 'monthly-revenue-report.png',
'prompt': '''A modern, minimalist app icon for "Monthly Revenue Report" plugin.
Square format with rounded corners. Green gradient background (#10b981).
White icon showing an upward trending chart or bar graph.
Flat design, clean lines, professional business analytics style.
The icon should convey growth and success at small sizes.'''
},
{
'filename': 'appointment-reminder-24hr.png',
'prompt': '''A modern, minimalist app icon for "Appointment Reminder" plugin.
Square format with rounded corners. Amber/orange gradient background (#f59e0b).
White icon showing a notification bell with a small red badge or alert dot.
Flat design, clean lines, attention-grabbing yet professional style.
The icon should convey alerts and reminders clearly at small sizes.'''
},
{
'filename': 'inactive-customer-reengagement.png',
'prompt': '''A modern, minimalist app icon for "Inactive Customer Re-engagement" plugin.
Square format with rounded corners. Purple gradient background (#8b5cf6).
White icon showing a heart with a circular refresh arrow around it.
Flat design, clean lines, warm and welcoming professional style.
The icon should convey customer care and return at small sizes.'''
}
]
def generate_image(prompt, filename):
"""Generate an image using Imagen API"""
print(f"\nGenerating {filename}...")
headers = {
"Content-Type": "application/json"
}
payload = {
"instances": [{
"prompt": prompt
}],
"parameters": {
"sampleCount": 1
}
}
try:
response = requests.post(
f"{API_URL}?key={API_KEY}",
headers=headers,
json=payload,
timeout=120
)
if response.status_code == 200:
result = response.json()
# Check if there's an image in the predictions
if 'predictions' in result and len(result['predictions']) > 0:
prediction = result['predictions'][0]
if 'bytesBase64Encoded' in prediction:
# Extract and save the image
image_data = base64.b64decode(prediction['bytesBase64Encoded'])
output_path = os.path.join(OUTPUT_DIR, filename)
with open(output_path, 'wb') as f:
f.write(image_data)
print(f"✓ Saved: {output_path} ({len(image_data)} bytes)")
return True
elif 'image' in prediction and 'bytesBase64Encoded' in prediction['image']:
image_data = base64.b64decode(prediction['image']['bytesBase64Encoded'])
output_path = os.path.join(OUTPUT_DIR, filename)
with open(output_path, 'wb') as f:
f.write(image_data)
print(f"✓ Saved: {output_path} ({len(image_data)} bytes)")
return True
print(f"✗ No image generated. Response: {str(result)[:300]}...")
return False
else:
print(f"✗ API Error ({response.status_code}): {response.text[:500]}")
return False
except Exception as e:
print(f"✗ Error: {str(e)}")
return False
def main():
# Create output directory
os.makedirs(OUTPUT_DIR, exist_ok=True)
print("Starting logo generation with Imagen 4.0 API...")
print("=" * 60)
success_count = 0
for i, plugin in enumerate(plugins):
if i > 0:
# Add delay between requests to avoid rate limiting
print("Waiting 2 seconds before next request...")
time.sleep(2)
if generate_image(plugin['prompt'], plugin['filename']):
success_count += 1
print("\n" + "=" * 60)
print(f"Generation complete: {success_count}/{len(plugins)} successful")
if __name__ == "__main__":
main()

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 706 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-mail"><path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"></path><polyline points="22,6 12,13 2,6"></polyline></svg>

After

Width:  |  Height:  |  Size: 332 B

View File

@@ -0,0 +1,43 @@
import google.generativeai as genai
import os
from PIL import Image
import io
# Configure API Key
genai.configure(api_key="AIzaSyB-nR0nkeftKrd42NrNIDcFCj3yFP8JLtw")
# Try to initialize the model
try:
generation_model = genai.GenerativeModel('gemini-pro-vision')
print("Model initialized successfully")
# Define prompt
prompt = "A modern app icon with a blue background and white envelope symbol"
print(f"Generating image for: '{prompt}'...")
# Try to generate
response = generation_model.generate_content(prompt)
print(f"Response type: {type(response)}")
print(f"Response: {response}")
if hasattr(response, 'parts') and response.parts:
print(f"Parts: {response.parts}")
if hasattr(response.parts[0], 'inline_data'):
print("Has inline_data!")
image_data = response.parts[0].inline_data.data
image = Image.open(io.BytesIO(image_data))
image.save("test_output.png")
print("Success! Image saved to test_output.png")
else:
print("No inline_data attribute")
print(f"Part attributes: {dir(response.parts[0])}")
else:
print("No parts in response or response.parts doesn't exist")
if hasattr(response, 'text'):
print(f"Response text: {response.text}")
except Exception as e:
print(f"Error: {type(e).__name__}: {str(e)}")
import traceback
traceback.print_exc()

View File

@@ -0,0 +1,41 @@
from google import genai
from google.genai import types
from PIL import Image
from io import BytesIO
import os
# Setup Client
client = genai.Client(api_key="AIzaSyB-nR0nkeftKrd42NrNIDcFCj3yFP8JLtw")
# Define the Prompt
prompt = "Create a simple modern app icon with a blue background and white envelope symbol, square format, flat design, minimalist"
print(f"Asking Gemini to generate: '{prompt}'...")
# Call the Gemini Model
try:
response = client.models.generate_content(
model='gemini-2.5-flash-image',
contents=prompt,
config=types.GenerateContentConfig(
response_modalities=["IMAGE"] # Tell Gemini to draw, not talk
)
)
# Save the Image
if response.candidates[0].content.parts:
for part in response.candidates[0].content.parts:
if part.inline_data:
image_data = part.inline_data.data
image = Image.open(BytesIO(image_data))
image.save("test_gemini_native.png")
print("Success! Saved test_gemini_native.png")
else:
print("Model returned text instead of image:", part.text)
else:
print("No content parts in response")
except Exception as e:
print(f"Error: {type(e).__name__}: {e}")
import traceback
traceback.print_exc()

View File

@@ -0,0 +1,41 @@
from google import genai
from google.genai import types
from PIL import Image
from io import BytesIO
import os
# Initialize the Client
client = genai.Client(api_key="AIzaSyB-nR0nkeftKrd42NrNIDcFCj3yFP8JLtw")
# Define the prompt
prompt = "A simple modern app icon with a blue background and white envelope symbol, square format, flat design"
print(f"Generating image for: '{prompt}'...")
# Call the API
try:
response = client.models.generate_images(
model='gemini-2.5-flash-image',
prompt=prompt,
config=types.GenerateImagesConfig(
number_of_images=1,
aspect_ratio="1:1",
safety_filter_level="BLOCK_LOW_AND_ABOVE",
person_generation="ALLOW_ADULT"
)
)
# Handle the response
for i, generated_image in enumerate(response.generated_images):
# Convert raw bytes to an image
image = Image.open(BytesIO(generated_image.image.image_bytes))
# Save to disk
filename = f"test_generated_image_{i}.png"
image.save(filename)
print(f"Success! Saved {filename}")
except Exception as e:
print(f"Error occurred: {type(e).__name__}: {e}")
import traceback
traceback.print_exc()

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-link"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg>

After

Width:  |  Height:  |  Size: 349 B