streamlit-launcher 2.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,368 @@
1
+ import tkinter as tk
2
+ from tkinter import ttk, messagebox, scrolledtext
3
+ import subprocess
4
+ import threading
5
+ import sys
6
+ import os
7
+ from PIL import Image, ImageTk
8
+
9
+
10
+ class StreamlitLauncher:
11
+ def __init__(self, root):
12
+ self.root = root
13
+ self.root.title("Streamlit Launcher")
14
+ self.root.geometry("400x540")
15
+ self.root.minsize(400, 540)
16
+ self.root.resizable(False, False)
17
+ try:
18
+ icon_path = r"C:\Users\User\Downloads\streamlit_launcher\streamlit_launcher\gambar.ico"
19
+
20
+ # Pastikan file ada
21
+ if not os.path.exists(icon_path):
22
+ raise FileNotFoundError("Icon file tidak ditemukan.")
23
+
24
+ # Cek apakah file valid dengan PIL
25
+ with Image.open(icon_path) as img_test:
26
+ img_test.verify() # validasi file ico
27
+
28
+ # Set icon ke Tkinter window
29
+ self.root.iconbitmap(icon_path)
30
+
31
+ except Exception as e:
32
+ print(f"[WARNING] Gagal load icon: {e}")
33
+
34
+ self.process = None
35
+ self.is_running = False
36
+
37
+ # Init speech
38
+ self.setup_speech()
39
+
40
+ # Default language
41
+ self.current_language = "English"
42
+ self.languages = {
43
+ "English": self.english_texts(),
44
+ "Chinese": self.chinese_texts(),
45
+ "Japanese": self.japanese_texts()
46
+ }
47
+
48
+ # Selalu pakai Female Voice
49
+ self.voice_gender = "Female"
50
+ self.set_voice_gender("Female")
51
+
52
+ # Find dashboard.py
53
+ self.script_path = self.find_dashboard()
54
+
55
+ # Load image
56
+ self.load_image()
57
+
58
+ # Setup UI
59
+ self.setup_ui()
60
+ self.update_ui_text()
61
+
62
+ # Welcome
63
+ self.play_welcome_speech()
64
+
65
+ # ============ SPEECH SETUP ==============
66
+ def setup_speech(self):
67
+ try:
68
+ import pyttsx3
69
+ self.client = pyttsx3.init()
70
+ self.voices = self.client.getProperty("voices")
71
+ self.speech_available = True
72
+
73
+ # Setel langsung ke suara wanita
74
+ self.set_voice_gender("Female")
75
+ except Exception as e:
76
+ print(f"Text-to-speech unavailable: {e}")
77
+ self.client = None
78
+ self.voices = []
79
+ self.speech_available = False
80
+
81
+ def set_voice_gender(self, gender):
82
+ if not self.speech_available:
83
+ return
84
+
85
+ self.voice_gender = gender
86
+
87
+ # Daftar kata kunci untuk female voice
88
+ female_keywords = ["zira", "samantha", "hazel", "eva", "anna", "helen", "linda", "susan"]
89
+
90
+ selected_voice = None
91
+ for voice in self.voices:
92
+ name = voice.name.lower()
93
+ if gender == "Female":
94
+ if any(key in name for key in female_keywords):
95
+ selected_voice = voice
96
+ break
97
+ else:
98
+ if "david" in name or "mark" in name or "george" in name:
99
+ selected_voice = voice
100
+ break
101
+
102
+ # Jika ketemu, pakai itu
103
+ if selected_voice:
104
+ self.client.setProperty("voice", selected_voice.id)
105
+ else:
106
+ # fallback → pakai voice pertama saja
107
+ self.client.setProperty("voice", self.voices[0].id)
108
+
109
+
110
+ def speak_text(self, text):
111
+ if self.speech_available and self.client:
112
+ def speak():
113
+ try:
114
+ # Pastikan selalu menggunakan suara wanita
115
+ self.set_voice_gender("Female")
116
+ self.client.say(text)
117
+ self.client.runAndWait()
118
+ except Exception as e:
119
+ print("Speech error:", e)
120
+ threading.Thread(target=speak, daemon=True).start()
121
+
122
+ # ============ LANGUAGE PACKS ==============
123
+ def english_texts(self):
124
+ return {
125
+ "title": "Streamlit Launcher",
126
+ "port": "Port:",
127
+ "start": "Start Server",
128
+ "stop": "Stop Server",
129
+ "log": "Log",
130
+ "status_ready": "Ready",
131
+ "language": "Language:",
132
+ "welcome_speech": "Welcome to Streamlit Launcher",
133
+ "start_speech": "Starting Streamlit Launcher",
134
+ "stop_speech": "Stopping Streamlit Launcher"
135
+ }
136
+
137
+ def chinese_texts(self):
138
+ return {
139
+ "title": "Streamlit 启动器",
140
+ "port": "端口:",
141
+ "start": "启动服务器",
142
+ "stop": "停止服务器",
143
+ "log": "日志",
144
+ "status_ready": "准备就绪",
145
+ "language": "语言:",
146
+ "welcome_speech": "欢迎使用 Streamlit 启动器",
147
+ "start_speech": "正在启动服务器",
148
+ "stop_speech": "正在停止服务器"
149
+ }
150
+
151
+ def japanese_texts(self):
152
+ return {
153
+ "title": "Streamlit ランチャー",
154
+ "port": "ポート:",
155
+ "start": "サーバー起動",
156
+ "stop": "サーバー停止",
157
+ "log": "ログ",
158
+ "status_ready": "準備完了",
159
+ "language": "言語:",
160
+ "welcome_speech": "ストリームリットランチャーへようこそ",
161
+ "start_speech": "サーバーを起動しています",
162
+ "stop_speech": "サーバーを停止しています"
163
+ }
164
+
165
+ # ============ IMAGE ==============
166
+ def load_image(self):
167
+ self.photo = None
168
+ if os.path.exists(r"C:\Users\User\Downloads\streamlit_launcher\streamlit_launcher\gambar.jpg"):
169
+ try:
170
+ img_path = r"C:\Users\User\Downloads\streamlit_launcher\streamlit_launcher\gambar.jpg"
171
+
172
+ # buka gambar dengan context manager supaya file otomatis ditutup
173
+ with Image.open(img_path) as img:
174
+ img_resized = img.resize((500, 250), Image.LANCZOS)
175
+ self.photo = ImageTk.PhotoImage(img_resized)
176
+
177
+ except Exception as e:
178
+ print("[WARNING] Gagal load gambar:", e)
179
+ else:
180
+ print("[WARNING] File gambar tidak ditemukan.")
181
+
182
+ def find_dashboard(self):
183
+ base_dir = r"C:\Users\User\Downloads\streamlit_launcher\streamlit_launcher"
184
+ dashboard_path = os.path.join(base_dir, "dashboard.py")
185
+ app_path = os.path.join(base_dir, "app.py")
186
+
187
+ if os.path.exists(dashboard_path):
188
+ return os.path.abspath(dashboard_path)
189
+ elif os.path.exists(app_path):
190
+ return os.path.abspath(app_path)
191
+ return dashboard_path # default kalau tidak ada
192
+
193
+
194
+ # ============ UI ==============
195
+ def setup_ui(self):
196
+ self.root.rowconfigure(0, weight=1)
197
+ self.root.columnconfigure(0, weight=1)
198
+
199
+ main_frame = ttk.Frame(self.root, padding=10)
200
+ main_frame.grid(row=0, column=0, sticky="nsew")
201
+ for i in range(8):
202
+ main_frame.rowconfigure(i, weight=0)
203
+ main_frame.rowconfigure(6, weight=1)
204
+ main_frame.columnconfigure(0, weight=1)
205
+
206
+ # Title
207
+ self.title_label = ttk.Label(main_frame, font=("Arial", 18, "bold"))
208
+ self.title_label.grid(row=0, column=0, pady=5, sticky="n")
209
+
210
+ # Language
211
+ lang_frame = ttk.Frame(main_frame)
212
+ lang_frame.grid(row=1, column=0, sticky="ew", pady=5)
213
+ lang_frame.columnconfigure(1, weight=1)
214
+ ttk.Label(lang_frame, text="Language:").grid(row=0, column=0, sticky="w")
215
+ self.lang_var = tk.StringVar(value=self.current_language)
216
+ lang_combo = ttk.Combobox(
217
+ lang_frame, textvariable=self.lang_var,
218
+ values=list(self.languages.keys()), state="readonly"
219
+ )
220
+ lang_combo.grid(row=0, column=1, sticky="ew")
221
+ lang_combo.bind("<<ComboboxSelected>>", self.change_language)
222
+
223
+ # Image
224
+ if self.photo:
225
+ ttk.Label(main_frame, image=self.photo).grid(row=2, column=0, pady=10, sticky="n")
226
+
227
+ # Port
228
+ port_frame = ttk.Frame(main_frame)
229
+ port_frame.grid(row=3, column=0, sticky="ew", pady=5)
230
+ port_frame.columnconfigure(1, weight=1)
231
+ self.port_label = ttk.Label(port_frame, text="Port:")
232
+ self.port_label.grid(row=0, column=0, sticky="w")
233
+ self.port_var = tk.StringVar(value="8501")
234
+ ttk.Entry(port_frame, textvariable=self.port_var, width=10).grid(row=0, column=1, sticky="w")
235
+
236
+ # Buttons
237
+ button_frame = ttk.Frame(main_frame)
238
+ button_frame.grid(row=4, column=0, pady=10, sticky="ew")
239
+ button_frame.columnconfigure((0, 1), weight=1)
240
+ self.start_btn = ttk.Button(button_frame, command=self.start_server)
241
+ self.start_btn.grid(row=0, column=0, padx=5, sticky="ew")
242
+ self.stop_btn = ttk.Button(button_frame, command=self.stop_server, state=tk.DISABLED)
243
+ self.stop_btn.grid(row=0, column=1, padx=5, sticky="ew")
244
+
245
+ # Log
246
+ log_frame = ttk.LabelFrame(main_frame, text="Log", padding=5)
247
+ log_frame.grid(row=6, column=0, sticky="nsew", pady=5)
248
+ log_frame.rowconfigure(0, weight=1)
249
+ log_frame.columnconfigure(0, weight=1)
250
+ self.log_text = scrolledtext.ScrolledText(log_frame, wrap=tk.WORD, font=("Consolas", 9))
251
+ self.log_text.grid(row=0, column=0, sticky="nsew")
252
+ self.log_text.config(state=tk.DISABLED)
253
+
254
+ # Status bar
255
+ self.status_var = tk.StringVar(value="Ready")
256
+ status_bar = ttk.Label(main_frame, textvariable=self.status_var,
257
+ relief=tk.SUNKEN, anchor="w")
258
+ status_bar.grid(row=7, column=0, sticky="ew")
259
+
260
+ def update_ui_text(self):
261
+ texts = self.languages[self.current_language]
262
+ self.root.title(texts["title"])
263
+ self.title_label.config(text=texts["title"])
264
+ self.port_label.config(text=texts["port"])
265
+ self.start_btn.config(text=texts["start"])
266
+ self.stop_btn.config(text=texts["stop"])
267
+ self.log_text.master.master.config(text=texts["log"])
268
+ self.status_var.set(texts["status_ready"])
269
+
270
+ # ============ EVENTS ==============
271
+ def change_language(self, event):
272
+ self.current_language = self.lang_var.get()
273
+ self.update_ui_text()
274
+
275
+ def play_welcome_speech(self):
276
+ self.speak_text(self.languages[self.current_language]["welcome_speech"])
277
+
278
+ def play_start_speech(self):
279
+ self.speak_text(self.languages[self.current_language]["start_speech"])
280
+
281
+ def play_stop_speech(self):
282
+ self.speak_text(self.languages[self.current_language]["stop_speech"])
283
+
284
+ # ============ SERVER CONTROL ==============
285
+ def start_server(self):
286
+ filename = self.script_path
287
+ port = self.port_var.get()
288
+ if not os.path.exists(filename):
289
+ messagebox.showerror("Error", f"File '{filename}' not found")
290
+ return
291
+ self.play_start_speech()
292
+ thread = threading.Thread(target=self.run_streamlit, args=(filename, port), daemon=True)
293
+ thread.start()
294
+ self.is_running = True
295
+ self.start_btn.config(state=tk.DISABLED)
296
+ self.stop_btn.config(state=tk.NORMAL)
297
+ self.status_var.set(f"Running on port {port}")
298
+ self.log_message(f"Starting server on port {port}...")
299
+
300
+ def run_streamlit(self, filename, port):
301
+ try:
302
+ cmd = [sys.executable, "-m", "streamlit", "run", filename, "--server.port", port]
303
+ self.process = subprocess.Popen(
304
+ cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
305
+ text=True, bufsize=1, universal_newlines=True,
306
+ encoding="utf-8", errors="replace"
307
+ )
308
+ for line in self.process.stdout:
309
+ if not self.is_running:
310
+ break
311
+ self.log_message(line.strip())
312
+ except Exception as e:
313
+ self.status_var.set(f"Error: {str(e)}")
314
+ self.log_message(f"Error: {str(e)}")
315
+
316
+ def log_message(self, message):
317
+ def update():
318
+ self.log_text.config(state=tk.NORMAL)
319
+ self.log_text.insert(tk.END, message + "\n")
320
+ self.log_text.see(tk.END)
321
+ self.log_text.config(state=tk.DISABLED)
322
+ self.root.after(0, update)
323
+
324
+ def stop_server(self):
325
+ self.play_stop_speech()
326
+ if self.process:
327
+ try:
328
+ self.is_running = False
329
+ if os.name == "nt":
330
+ subprocess.run(
331
+ ["taskkill", "/F", "/T", "/PID", str(self.process.pid)],
332
+ stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL
333
+ )
334
+ else:
335
+ import signal
336
+ os.killpg(os.getpgid(self.process.pid), signal.SIGTERM)
337
+ self.process = None
338
+ self.log_message("Server stopped")
339
+ except Exception as e:
340
+ self.status_var.set(f"Error stopping: {e}")
341
+ self.log_message(f"Error stopping: {e}")
342
+ self.is_running = False
343
+ self.start_btn.config(state=tk.NORMAL)
344
+ self.stop_btn.config(state=tk.DISABLED)
345
+ self.status_var.set(self.languages[self.current_language]["status_ready"])
346
+
347
+ def on_closing(self):
348
+ if self.is_running:
349
+ if messagebox.askokcancel("Quit", "Server is running. Stop and quit?"):
350
+ self.stop_server()
351
+ self.root.destroy()
352
+
353
+
354
+ if __name__ == "__main__":
355
+ root = tk.Tk()
356
+
357
+ # Set Windows style if available
358
+ if sys.platform == "win32":
359
+ from ctypes import windll
360
+ windll.shcore.SetProcessDpiAwareness(1)
361
+
362
+ app = StreamlitLauncher(root)
363
+ root.mainloop()
364
+
365
+ def run_gui():
366
+ root = tk.Tk()
367
+ app = StreamlitLauncher(root)
368
+ root.mainloop()
Binary file
Binary file