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.
- streamlit_launcher/.env +5 -0
- streamlit_launcher/.pypirc +8 -0
- streamlit_launcher/LICENSE +31 -0
- streamlit_launcher/README.md +281 -0
- streamlit_launcher/Screenshot 2025-09-12 185242.png +0 -0
- streamlit_launcher/Screenshot 2025-09-12 185705.png +0 -0
- streamlit_launcher/Screenshot 2025-09-12 185725.png +0 -0
- streamlit_launcher/Screenshot 2025-09-12 185806.png +0 -0
- streamlit_launcher/__init__.py +2 -0
- streamlit_launcher/anime.png +0 -0
- streamlit_launcher/cli.py +8 -0
- streamlit_launcher/dashboard.py +848 -0
- streamlit_launcher/gui.py +368 -0
- streamlit_launcher/img.ico +0 -0
- streamlit_launcher/kemas.png +0 -0
- streamlit_launcher/mas.py +1938 -0
- streamlit_launcher/requirements.txt +3 -0
- streamlit_launcher/setup.py +45 -0
- streamlit_launcher/treamlit.jpg +0 -0
- streamlit_launcher/ui.png +0 -0
- streamlit_launcher/upload_to_pypi.bat +18 -0
- streamlit_launcher-2.0.0.dist-info/METADATA +313 -0
- streamlit_launcher-2.0.0.dist-info/RECORD +27 -0
- streamlit_launcher-2.0.0.dist-info/WHEEL +5 -0
- streamlit_launcher-2.0.0.dist-info/entry_points.txt +2 -0
- streamlit_launcher-2.0.0.dist-info/licenses/LICENSE +31 -0
- streamlit_launcher-2.0.0.dist-info/top_level.txt +1 -0
|
@@ -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
|