Bobby Collins Claude Opus 4.6 commited on
Commit
fbe4bfc
·
1 Parent(s): b61b078

Add Windows installer packaging (v1.1.0)

Browse files

Package 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>

Files changed (9) hide show
  1. .gitignore +10 -0
  2. CHANGELOG.md +22 -0
  3. app.py +125 -117
  4. build.bat +122 -0
  5. desktop_app.py +44 -0
  6. hooks/hook-gradio.py +7 -0
  7. installer.iss +67 -0
  8. requirements.txt +3 -0
  9. 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
- client = OpenAI(
33
- base_url="https://openrouter.ai/api/v1",
34
- api_key=OPENROUTER_API_KEY,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- # UI
117
  # ─────────────────────────────────────────────
118
 
119
- theme = gr.themes.Base(
120
- primary_hue=gr.themes.colors.orange,
121
- secondary_hue=gr.themes.colors.neutral,
122
- neutral_hue=gr.themes.colors.gray,
123
- font=gr.themes.GoogleFont("Inter"),
124
- ).set(
125
- body_background_fill="#1a1a1a",
126
- body_background_fill_dark="#1a1a1a",
127
- body_text_color="#e0e0e0",
128
- body_text_color_dark="#e0e0e0",
129
- block_background_fill="#2a2a2a",
130
- block_background_fill_dark="#2a2a2a",
131
- block_border_color="#444",
132
- block_border_color_dark="#444",
133
- block_label_text_color="#ccc",
134
- block_label_text_color_dark="#ccc",
135
- block_title_text_color="#fff",
136
- block_title_text_color_dark="#fff",
137
- input_background_fill="#333",
138
- input_background_fill_dark="#333",
139
- input_border_color="#555",
140
- input_border_color_dark="#555",
141
- button_primary_background_fill="#e67e22",
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
- custom_model_input = gr.Textbox(
160
- label="Custom Model ID",
161
- placeholder="e.g. meta-llama/llama-4-maverick",
162
- visible=False,
163
- scale=2,
 
 
164
  )
165
 
166
- song_input = gr.Textbox(
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
- weirdness_slider = gr.Slider(
173
- minimum=0,
174
- maximum=100,
175
- value=30,
176
- step=1,
177
- label="Weirdness (0 = conventional, 100 = maximum creative hallucination)",
178
- )
179
 
180
- generate_btn = gr.Button("Generate Suno Prompt", variant="primary", size="lg")
 
 
 
 
 
181
 
182
- gr.Markdown("---")
 
 
 
 
 
183
 
184
- style_output = gr.Textbox(
185
- label="Style Prompt (paste into Suno's Style Prompt field)",
186
- lines=5,
187
- buttons=["copy"],
188
- interactive=False,
189
- )
190
 
191
- title_output = gr.Textbox(
192
- label="Song Title",
193
- lines=1,
194
- buttons=["copy"],
195
- interactive=False,
196
- )
 
197
 
198
- lyrics_output = gr.Textbox(
199
- label="Lyrics with Tags (paste into Suno's Lyrics field)",
200
- lines=20,
201
- buttons=["copy"],
202
- interactive=False,
203
- )
204
 
205
- with gr.Row():
206
- settings_output = gr.Textbox(
207
- label="Suno UI Settings",
208
- lines=5,
209
  interactive=False,
210
- scale=1,
211
  )
212
 
213
- gr.Markdown("---")
 
 
 
 
 
214
 
215
- cover_art_output = gr.Textbox(
216
- label="Cover Art Image Prompt (paste into Grok or image generator)",
217
- lines=6,
218
- buttons=["copy"],
219
- interactive=False,
220
- )
221
 
222
- # Events
223
- model_dropdown.change(
224
- fn=toggle_custom_visibility,
225
- inputs=model_dropdown,
226
- outputs=custom_model_input,
227
- )
 
 
 
 
 
228
 
229
- outputs = [title_output, style_output, lyrics_output, settings_output, cover_art_output]
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
- song_input.submit(
238
- fn=generate_prompt,
239
- inputs=[song_input, model_dropdown, custom_model_input, weirdness_slider],
240
- outputs=outputs,
241
- )
242
 
243
  if __name__ == "__main__":
244
- app.launch(inbrowser=True, theme=theme)
 
 
 
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()