Bobby Collins Claude Opus 4.6 commited on
Commit ·
fbe4bfc
1
Parent(s): b61b078
Add Windows installer packaging (v1.1.0)
Browse filesPackage the app as a distributable Windows installer using PyInstaller
and Inno Setup. Refactored app.py to expose create_app() for reuse by
the new desktop_app.py entry point. Installer prompts for OpenRouter
API key during setup and creates Start Menu/desktop shortcuts.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- .gitignore +10 -0
- CHANGELOG.md +22 -0
- app.py +125 -117
- build.bat +122 -0
- desktop_app.py +44 -0
- hooks/hook-gradio.py +7 -0
- installer.iss +67 -0
- requirements.txt +3 -0
- runtime_hook.py +3 -0
.gitignore
CHANGED
|
@@ -3,3 +3,13 @@ __pycache__/
|
|
| 3 |
*.pyc
|
| 4 |
.env
|
| 5 |
CLAUDE.md
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
*.pyc
|
| 4 |
.env
|
| 5 |
CLAUDE.md
|
| 6 |
+
|
| 7 |
+
# Build artifacts
|
| 8 |
+
build/
|
| 9 |
+
dist/
|
| 10 |
+
Output/
|
| 11 |
+
*.spec
|
| 12 |
+
|
| 13 |
+
# Source reference documents (local only)
|
| 14 |
+
*.pdf
|
| 15 |
+
Suno Database.txt
|
CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
| 1 |
# Changelog
|
| 2 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
## [1.0.1] - 2025-02-18
|
| 4 |
|
| 5 |
### Changed
|
|
|
|
| 1 |
# Changelog
|
| 2 |
|
| 3 |
+
## [1.1.0] - 2026-02-18
|
| 4 |
+
|
| 5 |
+
### Added
|
| 6 |
+
- **Windows installer packaging** via PyInstaller + Inno Setup
|
| 7 |
+
- `desktop_app.py` — Packaged entry point that launches Gradio in default browser with no console window
|
| 8 |
+
- `build.bat` — One-click build script that runs PyInstaller and optionally Inno Setup
|
| 9 |
+
- `installer.iss` — Inno Setup script with custom API key prompt during installation
|
| 10 |
+
- `hooks/hook-gradio.py` — PyInstaller hook for Gradio source collection
|
| 11 |
+
- `runtime_hook.py` — Multiprocessing freeze support for PyInstaller
|
| 12 |
+
- Installer features:
|
| 13 |
+
- Professional setup wizard with custom API key input page
|
| 14 |
+
- Start Menu and optional desktop shortcuts
|
| 15 |
+
- Add/Remove Programs entry
|
| 16 |
+
- Writes `.env` file with API key to install directory
|
| 17 |
+
- Option to launch app after installation
|
| 18 |
+
- Build produces both a portable exe (`dist/SunoPromptGenerator/`) and an installer (`Output/SunoPromptGenerator_Setup.exe`)
|
| 19 |
+
|
| 20 |
+
### Changed
|
| 21 |
+
- Refactored `app.py` to expose `create_app()` function, allowing reuse by both dev mode and desktop packaging
|
| 22 |
+
- Updated `.gitignore` to exclude build artifacts (`build/`, `dist/`, `Output/`, `*.spec`) and source reference documents
|
| 23 |
+
- Removed `pywebview` from requirements (pythonnet incompatible with Python 3.14; using browser-based approach instead)
|
| 24 |
+
|
| 25 |
## [1.0.1] - 2025-02-18
|
| 26 |
|
| 27 |
### Changed
|
app.py
CHANGED
|
@@ -16,10 +16,6 @@ from knowledge_base import build_system_prompt
|
|
| 16 |
# CONFIG
|
| 17 |
# ─────────────────────────────────────────────
|
| 18 |
|
| 19 |
-
load_dotenv()
|
| 20 |
-
|
| 21 |
-
OPENROUTER_API_KEY = os.getenv("OPENROUTER_API_KEY", "")
|
| 22 |
-
|
| 23 |
MODELS = {
|
| 24 |
"Google Gemini 3 Pro": "google/gemini-3-pro-preview",
|
| 25 |
"Google Gemini 3 Flash": "google/gemini-3-flash-preview",
|
|
@@ -29,9 +25,34 @@ MODELS = {
|
|
| 29 |
"Custom": "custom",
|
| 30 |
}
|
| 31 |
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 35 |
)
|
| 36 |
|
| 37 |
|
|
@@ -39,6 +60,14 @@ client = OpenAI(
|
|
| 39 |
# CORE LOGIC
|
| 40 |
# ─────────────────────────────────────────────
|
| 41 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 42 |
def generate_prompt(song_idea: str, model_choice: str, custom_model: str, weirdness: int):
|
| 43 |
"""Call OpenRouter and parse the structured response."""
|
| 44 |
if not song_idea.strip():
|
|
@@ -53,6 +82,7 @@ def generate_prompt(song_idea: str, model_choice: str, custom_model: str, weirdn
|
|
| 53 |
model_id = MODELS.get(model_choice, "google/gemini-3-flash-preview")
|
| 54 |
|
| 55 |
system_prompt = build_system_prompt(weirdness)
|
|
|
|
| 56 |
|
| 57 |
try:
|
| 58 |
response = client.chat.completions.create(
|
|
@@ -70,7 +100,6 @@ def generate_prompt(song_idea: str, model_choice: str, custom_model: str, weirdn
|
|
| 70 |
# Strip markdown code fences if present
|
| 71 |
if raw.startswith("```"):
|
| 72 |
lines = raw.split("\n")
|
| 73 |
-
# Remove first line (```json or ```) and last line (```)
|
| 74 |
if lines[-1].strip() == "```":
|
| 75 |
lines = lines[1:-1]
|
| 76 |
else:
|
|
@@ -83,7 +112,6 @@ def generate_prompt(song_idea: str, model_choice: str, custom_model: str, weirdn
|
|
| 83 |
style_prompt = data.get("style_prompt", "")
|
| 84 |
lyrics = data.get("lyrics", "")
|
| 85 |
|
| 86 |
-
# Build settings display
|
| 87 |
w = data.get("weirdness", "N/A")
|
| 88 |
w_reason = data.get("weirdness_reasoning", "")
|
| 89 |
si = data.get("style_influence", "N/A")
|
|
@@ -95,7 +123,6 @@ def generate_prompt(song_idea: str, model_choice: str, custom_model: str, weirdn
|
|
| 95 |
return song_title, style_prompt, lyrics, settings, cover_art
|
| 96 |
|
| 97 |
except json.JSONDecodeError:
|
| 98 |
-
# If JSON parsing fails, show raw response
|
| 99 |
return (
|
| 100 |
"",
|
| 101 |
f"[JSON parse error - raw response below]\n\n{raw}",
|
|
@@ -113,132 +140,113 @@ def toggle_custom_visibility(choice):
|
|
| 113 |
|
| 114 |
|
| 115 |
# ─────────────────────────────────────────────
|
| 116 |
-
#
|
| 117 |
# ─────────────────────────────────────────────
|
| 118 |
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
button_primary_background_fill_dark="#e67e22",
|
| 143 |
-
button_primary_background_fill_hover="#d35400",
|
| 144 |
-
button_primary_background_fill_hover_dark="#d35400",
|
| 145 |
-
button_primary_text_color="#fff",
|
| 146 |
-
button_primary_text_color_dark="#fff",
|
| 147 |
-
)
|
| 148 |
-
|
| 149 |
-
with gr.Blocks(title="Suno Prompt Generator") as app:
|
| 150 |
-
gr.Markdown("# Suno Prompt Generator\nDescribe your song idea in natural language. Get back structured Suno prompts.")
|
| 151 |
-
|
| 152 |
-
with gr.Row():
|
| 153 |
-
model_dropdown = gr.Dropdown(
|
| 154 |
-
choices=list(MODELS.keys()),
|
| 155 |
-
value="Google Gemini 3 Flash",
|
| 156 |
-
label="AI Model",
|
| 157 |
-
scale=2,
|
| 158 |
)
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
|
|
|
|
|
|
|
| 164 |
)
|
| 165 |
|
| 166 |
-
|
| 167 |
-
label="Song Idea",
|
| 168 |
-
placeholder="A melancholy song about driving alone at night on empty highways, with a female vocal that sounds tired but hopeful...",
|
| 169 |
-
lines=4,
|
| 170 |
-
)
|
| 171 |
|
| 172 |
-
|
| 173 |
-
minimum=0,
|
| 174 |
-
maximum=100,
|
| 175 |
-
value=30,
|
| 176 |
-
step=1,
|
| 177 |
-
label="Weirdness (0 = conventional, 100 = maximum creative hallucination)",
|
| 178 |
-
)
|
| 179 |
|
| 180 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 181 |
|
| 182 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 183 |
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
|
| 187 |
-
|
| 188 |
-
|
| 189 |
-
|
| 190 |
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
|
|
|
|
| 197 |
|
| 198 |
-
|
| 199 |
-
label="Lyrics with Tags (paste into Suno's Lyrics field)",
|
| 200 |
-
lines=20,
|
| 201 |
-
buttons=["copy"],
|
| 202 |
-
interactive=False,
|
| 203 |
-
)
|
| 204 |
|
| 205 |
-
|
| 206 |
-
|
| 207 |
-
|
| 208 |
-
|
| 209 |
interactive=False,
|
| 210 |
-
scale=1,
|
| 211 |
)
|
| 212 |
|
| 213 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 214 |
|
| 215 |
-
|
| 216 |
-
label="Cover Art Image Prompt (paste into Grok or image generator)",
|
| 217 |
-
lines=6,
|
| 218 |
-
buttons=["copy"],
|
| 219 |
-
interactive=False,
|
| 220 |
-
)
|
| 221 |
|
| 222 |
-
|
| 223 |
-
|
| 224 |
-
|
| 225 |
-
|
| 226 |
-
|
| 227 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 228 |
|
| 229 |
-
|
| 230 |
|
| 231 |
-
generate_btn.click(
|
| 232 |
-
fn=generate_prompt,
|
| 233 |
-
inputs=[song_input, model_dropdown, custom_model_input, weirdness_slider],
|
| 234 |
-
outputs=outputs,
|
| 235 |
-
)
|
| 236 |
|
| 237 |
-
|
| 238 |
-
|
| 239 |
-
|
| 240 |
-
outputs=outputs,
|
| 241 |
-
)
|
| 242 |
|
| 243 |
if __name__ == "__main__":
|
| 244 |
-
|
|
|
|
|
|
|
|
|
| 16 |
# CONFIG
|
| 17 |
# ─────────────────────────────────────────────
|
| 18 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
MODELS = {
|
| 20 |
"Google Gemini 3 Pro": "google/gemini-3-pro-preview",
|
| 21 |
"Google Gemini 3 Flash": "google/gemini-3-flash-preview",
|
|
|
|
| 25 |
"Custom": "custom",
|
| 26 |
}
|
| 27 |
|
| 28 |
+
THEME = gr.themes.Base(
|
| 29 |
+
primary_hue=gr.themes.colors.orange,
|
| 30 |
+
secondary_hue=gr.themes.colors.neutral,
|
| 31 |
+
neutral_hue=gr.themes.colors.gray,
|
| 32 |
+
font=gr.themes.GoogleFont("Inter"),
|
| 33 |
+
).set(
|
| 34 |
+
body_background_fill="#1a1a1a",
|
| 35 |
+
body_background_fill_dark="#1a1a1a",
|
| 36 |
+
body_text_color="#e0e0e0",
|
| 37 |
+
body_text_color_dark="#e0e0e0",
|
| 38 |
+
block_background_fill="#2a2a2a",
|
| 39 |
+
block_background_fill_dark="#2a2a2a",
|
| 40 |
+
block_border_color="#444",
|
| 41 |
+
block_border_color_dark="#444",
|
| 42 |
+
block_label_text_color="#ccc",
|
| 43 |
+
block_label_text_color_dark="#ccc",
|
| 44 |
+
block_title_text_color="#fff",
|
| 45 |
+
block_title_text_color_dark="#fff",
|
| 46 |
+
input_background_fill="#333",
|
| 47 |
+
input_background_fill_dark="#333",
|
| 48 |
+
input_border_color="#555",
|
| 49 |
+
input_border_color_dark="#555",
|
| 50 |
+
button_primary_background_fill="#e67e22",
|
| 51 |
+
button_primary_background_fill_dark="#e67e22",
|
| 52 |
+
button_primary_background_fill_hover="#d35400",
|
| 53 |
+
button_primary_background_fill_hover_dark="#d35400",
|
| 54 |
+
button_primary_text_color="#fff",
|
| 55 |
+
button_primary_text_color_dark="#fff",
|
| 56 |
)
|
| 57 |
|
| 58 |
|
|
|
|
| 60 |
# CORE LOGIC
|
| 61 |
# ─────────────────────────────────────────────
|
| 62 |
|
| 63 |
+
def _get_client():
|
| 64 |
+
"""Create OpenAI client using current env var."""
|
| 65 |
+
return OpenAI(
|
| 66 |
+
base_url="https://openrouter.ai/api/v1",
|
| 67 |
+
api_key=os.getenv("OPENROUTER_API_KEY", ""),
|
| 68 |
+
)
|
| 69 |
+
|
| 70 |
+
|
| 71 |
def generate_prompt(song_idea: str, model_choice: str, custom_model: str, weirdness: int):
|
| 72 |
"""Call OpenRouter and parse the structured response."""
|
| 73 |
if not song_idea.strip():
|
|
|
|
| 82 |
model_id = MODELS.get(model_choice, "google/gemini-3-flash-preview")
|
| 83 |
|
| 84 |
system_prompt = build_system_prompt(weirdness)
|
| 85 |
+
client = _get_client()
|
| 86 |
|
| 87 |
try:
|
| 88 |
response = client.chat.completions.create(
|
|
|
|
| 100 |
# Strip markdown code fences if present
|
| 101 |
if raw.startswith("```"):
|
| 102 |
lines = raw.split("\n")
|
|
|
|
| 103 |
if lines[-1].strip() == "```":
|
| 104 |
lines = lines[1:-1]
|
| 105 |
else:
|
|
|
|
| 112 |
style_prompt = data.get("style_prompt", "")
|
| 113 |
lyrics = data.get("lyrics", "")
|
| 114 |
|
|
|
|
| 115 |
w = data.get("weirdness", "N/A")
|
| 116 |
w_reason = data.get("weirdness_reasoning", "")
|
| 117 |
si = data.get("style_influence", "N/A")
|
|
|
|
| 123 |
return song_title, style_prompt, lyrics, settings, cover_art
|
| 124 |
|
| 125 |
except json.JSONDecodeError:
|
|
|
|
| 126 |
return (
|
| 127 |
"",
|
| 128 |
f"[JSON parse error - raw response below]\n\n{raw}",
|
|
|
|
| 140 |
|
| 141 |
|
| 142 |
# ─────────────────────────────────────────────
|
| 143 |
+
# APP BUILDER
|
| 144 |
# ─────────────────────────────────────────────
|
| 145 |
|
| 146 |
+
def create_app():
|
| 147 |
+
"""Build and return the Gradio Blocks app and theme."""
|
| 148 |
+
with gr.Blocks(title="Suno Prompt Generator") as demo:
|
| 149 |
+
gr.Markdown("# Suno Prompt Generator\nDescribe your song idea in natural language. Get back structured Suno prompts.")
|
| 150 |
+
|
| 151 |
+
with gr.Row():
|
| 152 |
+
model_dropdown = gr.Dropdown(
|
| 153 |
+
choices=list(MODELS.keys()),
|
| 154 |
+
value="Google Gemini 3 Flash",
|
| 155 |
+
label="AI Model",
|
| 156 |
+
scale=2,
|
| 157 |
+
)
|
| 158 |
+
custom_model_input = gr.Textbox(
|
| 159 |
+
label="Custom Model ID",
|
| 160 |
+
placeholder="e.g. meta-llama/llama-4-maverick",
|
| 161 |
+
visible=False,
|
| 162 |
+
scale=2,
|
| 163 |
+
)
|
| 164 |
+
|
| 165 |
+
song_input = gr.Textbox(
|
| 166 |
+
label="Song Idea",
|
| 167 |
+
placeholder="A melancholy song about driving alone at night on empty highways, with a female vocal that sounds tired but hopeful...",
|
| 168 |
+
lines=4,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 169 |
)
|
| 170 |
+
|
| 171 |
+
weirdness_slider = gr.Slider(
|
| 172 |
+
minimum=0,
|
| 173 |
+
maximum=100,
|
| 174 |
+
value=30,
|
| 175 |
+
step=1,
|
| 176 |
+
label="Weirdness (0 = conventional, 100 = maximum creative hallucination)",
|
| 177 |
)
|
| 178 |
|
| 179 |
+
generate_btn = gr.Button("Generate Suno Prompt", variant="primary", size="lg")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 180 |
|
| 181 |
+
gr.Markdown("---")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 182 |
|
| 183 |
+
style_output = gr.Textbox(
|
| 184 |
+
label="Style Prompt (paste into Suno's Style Prompt field)",
|
| 185 |
+
lines=5,
|
| 186 |
+
buttons=["copy"],
|
| 187 |
+
interactive=False,
|
| 188 |
+
)
|
| 189 |
|
| 190 |
+
title_output = gr.Textbox(
|
| 191 |
+
label="Song Title",
|
| 192 |
+
lines=1,
|
| 193 |
+
buttons=["copy"],
|
| 194 |
+
interactive=False,
|
| 195 |
+
)
|
| 196 |
|
| 197 |
+
lyrics_output = gr.Textbox(
|
| 198 |
+
label="Lyrics with Tags (paste into Suno's Lyrics field)",
|
| 199 |
+
lines=20,
|
| 200 |
+
buttons=["copy"],
|
| 201 |
+
interactive=False,
|
| 202 |
+
)
|
| 203 |
|
| 204 |
+
with gr.Row():
|
| 205 |
+
settings_output = gr.Textbox(
|
| 206 |
+
label="Suno UI Settings",
|
| 207 |
+
lines=5,
|
| 208 |
+
interactive=False,
|
| 209 |
+
scale=1,
|
| 210 |
+
)
|
| 211 |
|
| 212 |
+
gr.Markdown("---")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 213 |
|
| 214 |
+
cover_art_output = gr.Textbox(
|
| 215 |
+
label="Cover Art Image Prompt (paste into Grok or image generator)",
|
| 216 |
+
lines=6,
|
| 217 |
+
buttons=["copy"],
|
| 218 |
interactive=False,
|
|
|
|
| 219 |
)
|
| 220 |
|
| 221 |
+
# Events
|
| 222 |
+
model_dropdown.change(
|
| 223 |
+
fn=toggle_custom_visibility,
|
| 224 |
+
inputs=model_dropdown,
|
| 225 |
+
outputs=custom_model_input,
|
| 226 |
+
)
|
| 227 |
|
| 228 |
+
outputs = [title_output, style_output, lyrics_output, settings_output, cover_art_output]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 229 |
|
| 230 |
+
generate_btn.click(
|
| 231 |
+
fn=generate_prompt,
|
| 232 |
+
inputs=[song_input, model_dropdown, custom_model_input, weirdness_slider],
|
| 233 |
+
outputs=outputs,
|
| 234 |
+
)
|
| 235 |
+
|
| 236 |
+
song_input.submit(
|
| 237 |
+
fn=generate_prompt,
|
| 238 |
+
inputs=[song_input, model_dropdown, custom_model_input, weirdness_slider],
|
| 239 |
+
outputs=outputs,
|
| 240 |
+
)
|
| 241 |
|
| 242 |
+
return demo, THEME
|
| 243 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 244 |
|
| 245 |
+
# ─────────────────────────────────────────────
|
| 246 |
+
# DEV MODE ENTRY POINT
|
| 247 |
+
# ─────────────────────────────────────────────
|
|
|
|
|
|
|
| 248 |
|
| 249 |
if __name__ == "__main__":
|
| 250 |
+
load_dotenv()
|
| 251 |
+
demo, theme = create_app()
|
| 252 |
+
demo.launch(inbrowser=True, theme=theme)
|
build.bat
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
@echo off
|
| 2 |
+
setlocal
|
| 3 |
+
|
| 4 |
+
echo ============================================================
|
| 5 |
+
echo Suno Prompt Generator - Build Script
|
| 6 |
+
echo ============================================================
|
| 7 |
+
echo.
|
| 8 |
+
|
| 9 |
+
:: Set project directory to wherever this script lives
|
| 10 |
+
cd /d "%~dp0"
|
| 11 |
+
|
| 12 |
+
:: ─────────────────────────────────────────────
|
| 13 |
+
:: STEP 1: Check prerequisites
|
| 14 |
+
:: ─────────────────────────────────────────────
|
| 15 |
+
echo [1/4] Checking prerequisites...
|
| 16 |
+
|
| 17 |
+
:: Check Python venv
|
| 18 |
+
if not exist "venv\Scripts\python.exe" (
|
| 19 |
+
echo ERROR: Python venv not found. Run: python -m venv venv
|
| 20 |
+
echo Then: venv\Scripts\pip install -r requirements.txt pyinstaller
|
| 21 |
+
pause
|
| 22 |
+
exit /b 1
|
| 23 |
+
)
|
| 24 |
+
|
| 25 |
+
:: Check PyInstaller
|
| 26 |
+
venv\Scripts\python.exe -c "import PyInstaller" 2>nul
|
| 27 |
+
if errorlevel 1 (
|
| 28 |
+
echo ERROR: PyInstaller not installed. Run: venv\Scripts\pip install pyinstaller
|
| 29 |
+
pause
|
| 30 |
+
exit /b 1
|
| 31 |
+
)
|
| 32 |
+
|
| 33 |
+
echo Python venv: OK
|
| 34 |
+
echo PyInstaller: OK
|
| 35 |
+
|
| 36 |
+
:: Check for Inno Setup (optional — only needed for installer)
|
| 37 |
+
set "ISCC="
|
| 38 |
+
where iscc.exe >nul 2>&1 && set "ISCC=iscc.exe"
|
| 39 |
+
if "%ISCC%"=="" (
|
| 40 |
+
if exist "C:\Program Files (x86)\Inno Setup 6\ISCC.exe" set "ISCC=C:\Program Files (x86)\Inno Setup 6\ISCC.exe"
|
| 41 |
+
)
|
| 42 |
+
if "%ISCC%"=="" (
|
| 43 |
+
if exist "C:\Program Files\Inno Setup 6\ISCC.exe" set "ISCC=C:\Program Files\Inno Setup 6\ISCC.exe"
|
| 44 |
+
)
|
| 45 |
+
if "%ISCC%"=="" (
|
| 46 |
+
echo Inno Setup: NOT FOUND - will skip installer creation
|
| 47 |
+
echo Download from: https://jrsoftware.org/isdl.php
|
| 48 |
+
) else (
|
| 49 |
+
echo Inno Setup: OK
|
| 50 |
+
)
|
| 51 |
+
echo.
|
| 52 |
+
|
| 53 |
+
:: ─────────────────────────────────────────────
|
| 54 |
+
:: STEP 2: Clean previous build
|
| 55 |
+
:: ─────────────────────────────────────────────
|
| 56 |
+
echo [2/4] Cleaning previous build artifacts...
|
| 57 |
+
if exist "dist" rmdir /s /q "dist"
|
| 58 |
+
if exist "build" rmdir /s /q "build"
|
| 59 |
+
if exist "SunoPromptGenerator.spec" del /q "SunoPromptGenerator.spec"
|
| 60 |
+
echo Done.
|
| 61 |
+
echo.
|
| 62 |
+
|
| 63 |
+
:: ─────────────────────────────────────────────
|
| 64 |
+
:: STEP 3: Run PyInstaller
|
| 65 |
+
:: ─────────────────────────────────────────────
|
| 66 |
+
echo [3/4] Running PyInstaller...
|
| 67 |
+
echo This may take a few minutes...
|
| 68 |
+
echo.
|
| 69 |
+
|
| 70 |
+
venv\Scripts\pyinstaller.exe ^
|
| 71 |
+
--noconfirm ^
|
| 72 |
+
--windowed ^
|
| 73 |
+
--name "SunoPromptGenerator" ^
|
| 74 |
+
--additional-hooks-dir hooks ^
|
| 75 |
+
--runtime-hook runtime_hook.py ^
|
| 76 |
+
--collect-data gradio ^
|
| 77 |
+
--collect-data gradio_client ^
|
| 78 |
+
--add-data "knowledge_base.py;." ^
|
| 79 |
+
desktop_app.py
|
| 80 |
+
|
| 81 |
+
if errorlevel 1 (
|
| 82 |
+
echo.
|
| 83 |
+
echo ERROR: PyInstaller build failed!
|
| 84 |
+
pause
|
| 85 |
+
exit /b 1
|
| 86 |
+
)
|
| 87 |
+
|
| 88 |
+
echo.
|
| 89 |
+
echo PyInstaller build complete: dist\SunoPromptGenerator\
|
| 90 |
+
echo.
|
| 91 |
+
|
| 92 |
+
:: ─────────────────────────────────────────────
|
| 93 |
+
:: STEP 4: Run Inno Setup (if available)
|
| 94 |
+
:: ─────────────────────────────────────────────
|
| 95 |
+
if "%ISCC%"=="" (
|
| 96 |
+
echo [4/4] Skipping installer creation (Inno Setup not found).
|
| 97 |
+
echo You can still run the app directly from: dist\SunoPromptGenerator\SunoPromptGenerator.exe
|
| 98 |
+
echo To create an installer, install Inno Setup 6 and re-run this script.
|
| 99 |
+
) else (
|
| 100 |
+
echo [4/4] Creating Windows installer with Inno Setup...
|
| 101 |
+
"%ISCC%" installer.iss
|
| 102 |
+
if errorlevel 1 (
|
| 103 |
+
echo.
|
| 104 |
+
echo ERROR: Inno Setup build failed!
|
| 105 |
+
pause
|
| 106 |
+
exit /b 1
|
| 107 |
+
)
|
| 108 |
+
echo.
|
| 109 |
+
echo Installer created: Output\SunoPromptGenerator_Setup.exe
|
| 110 |
+
)
|
| 111 |
+
|
| 112 |
+
echo.
|
| 113 |
+
echo ============================================================
|
| 114 |
+
echo BUILD COMPLETE!
|
| 115 |
+
echo ============================================================
|
| 116 |
+
echo.
|
| 117 |
+
if not "%ISCC%"=="" (
|
| 118 |
+
echo Installer: Output\SunoPromptGenerator_Setup.exe
|
| 119 |
+
)
|
| 120 |
+
echo Portable: dist\SunoPromptGenerator\SunoPromptGenerator.exe
|
| 121 |
+
echo.
|
| 122 |
+
pause
|
desktop_app.py
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Desktop entry point for packaged Suno Prompt Generator.
|
| 3 |
+
Launches Gradio and opens it in the default browser.
|
| 4 |
+
Runs without a visible console window (PyInstaller --windowed).
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
import multiprocessing
|
| 8 |
+
import os
|
| 9 |
+
import signal
|
| 10 |
+
import sys
|
| 11 |
+
import webbrowser
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
def main():
|
| 15 |
+
# Determine the install/exe directory for .env loading
|
| 16 |
+
if getattr(sys, "frozen", False):
|
| 17 |
+
# Running as a PyInstaller bundle
|
| 18 |
+
app_dir = os.path.dirname(sys.executable)
|
| 19 |
+
else:
|
| 20 |
+
# Running as a script (dev mode)
|
| 21 |
+
app_dir = os.path.dirname(os.path.abspath(__file__))
|
| 22 |
+
|
| 23 |
+
# Load .env from the install directory
|
| 24 |
+
env_path = os.path.join(app_dir, ".env")
|
| 25 |
+
from dotenv import load_dotenv
|
| 26 |
+
load_dotenv(env_path)
|
| 27 |
+
|
| 28 |
+
# Import and build the Gradio app
|
| 29 |
+
from app import create_app
|
| 30 |
+
demo, theme = create_app()
|
| 31 |
+
|
| 32 |
+
# Launch Gradio — opens in default browser, blocks until closed
|
| 33 |
+
demo.launch(
|
| 34 |
+
server_name="127.0.0.1",
|
| 35 |
+
server_port=0, # Random free port
|
| 36 |
+
share=False,
|
| 37 |
+
inbrowser=True,
|
| 38 |
+
theme=theme,
|
| 39 |
+
)
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
if __name__ == "__main__":
|
| 43 |
+
multiprocessing.freeze_support()
|
| 44 |
+
main()
|
hooks/hook-gradio.py
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# PyInstaller hook for Gradio.
|
| 2 |
+
# Gradio reads its own .py source files at runtime for component metadata,
|
| 3 |
+
# so we must collect them as source rather than compiled bytecode.
|
| 4 |
+
|
| 5 |
+
module_collection_mode = {
|
| 6 |
+
"gradio": "py",
|
| 7 |
+
}
|
installer.iss
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
; Inno Setup Script for Suno Prompt Generator
|
| 2 |
+
|
| 3 |
+
[Setup]
|
| 4 |
+
AppName=Suno Prompt Generator
|
| 5 |
+
AppVersion=1.1.0
|
| 6 |
+
AppPublisher=AnimalMonk
|
| 7 |
+
AppPublisherURL=https://github.com/AnimalMonk/Suno-Prompting-App
|
| 8 |
+
DefaultDirName={autopf}\SunoPromptGenerator
|
| 9 |
+
DefaultGroupName=Suno Prompt Generator
|
| 10 |
+
OutputBaseFilename=SunoPromptGenerator_Setup
|
| 11 |
+
OutputDir=Output
|
| 12 |
+
Compression=lzma2
|
| 13 |
+
SolidCompression=yes
|
| 14 |
+
WizardStyle=modern
|
| 15 |
+
PrivilegesRequired=lowest
|
| 16 |
+
DisableProgramGroupPage=yes
|
| 17 |
+
|
| 18 |
+
[Files]
|
| 19 |
+
Source: "dist\SunoPromptGenerator\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
|
| 20 |
+
|
| 21 |
+
[Icons]
|
| 22 |
+
Name: "{group}\Suno Prompt Generator"; Filename: "{app}\SunoPromptGenerator.exe"
|
| 23 |
+
Name: "{autodesktop}\Suno Prompt Generator"; Filename: "{app}\SunoPromptGenerator.exe"; Tasks: desktopicon
|
| 24 |
+
|
| 25 |
+
[Tasks]
|
| 26 |
+
Name: "desktopicon"; Description: "Create a desktop shortcut"; GroupDescription: "Additional shortcuts:"
|
| 27 |
+
|
| 28 |
+
[Run]
|
| 29 |
+
Filename: "{app}\SunoPromptGenerator.exe"; Description: "Launch Suno Prompt Generator"; Flags: nowait postinstall skipifsilent
|
| 30 |
+
|
| 31 |
+
[Code]
|
| 32 |
+
var
|
| 33 |
+
ApiKeyPage: TInputQueryWizardPage;
|
| 34 |
+
|
| 35 |
+
procedure InitializeWizard();
|
| 36 |
+
begin
|
| 37 |
+
ApiKeyPage := CreateInputQueryPage(wpSelectDir,
|
| 38 |
+
'API Configuration',
|
| 39 |
+
'Enter your OpenRouter API key to use the application.',
|
| 40 |
+
'You need an OpenRouter API key. Get one at https://openrouter.ai/keys'#13#10 +
|
| 41 |
+
'This will be saved locally in the install directory.');
|
| 42 |
+
ApiKeyPage.Add('OpenRouter API Key:', False);
|
| 43 |
+
end;
|
| 44 |
+
|
| 45 |
+
function NextButtonClick(CurPageID: Integer): Boolean;
|
| 46 |
+
begin
|
| 47 |
+
Result := True;
|
| 48 |
+
if CurPageID = ApiKeyPage.ID then
|
| 49 |
+
begin
|
| 50 |
+
if Trim(ApiKeyPage.Values[0]) = '' then
|
| 51 |
+
begin
|
| 52 |
+
MsgBox('Please enter your OpenRouter API key to continue.', mbError, MB_OK);
|
| 53 |
+
Result := False;
|
| 54 |
+
end;
|
| 55 |
+
end;
|
| 56 |
+
end;
|
| 57 |
+
|
| 58 |
+
procedure CurStepChanged(CurStep: TSetupStep);
|
| 59 |
+
var
|
| 60 |
+
EnvContent: String;
|
| 61 |
+
begin
|
| 62 |
+
if CurStep = ssPostInstall then
|
| 63 |
+
begin
|
| 64 |
+
EnvContent := 'OPENROUTER_API_KEY=' + Trim(ApiKeyPage.Values[0]);
|
| 65 |
+
SaveStringToFile(ExpandConstant('{app}\.env'), EnvContent, False);
|
| 66 |
+
end;
|
| 67 |
+
end;
|
requirements.txt
CHANGED
|
@@ -1,3 +1,6 @@
|
|
| 1 |
gradio
|
| 2 |
openai
|
| 3 |
python-dotenv
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
gradio
|
| 2 |
openai
|
| 3 |
python-dotenv
|
| 4 |
+
|
| 5 |
+
# Build-time only (not needed at runtime):
|
| 6 |
+
# pip install pyinstaller
|
runtime_hook.py
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# PyInstaller runtime hook for multiprocessing freeze support.
|
| 2 |
+
import multiprocessing
|
| 3 |
+
multiprocessing.freeze_support()
|