Skip to content

Commit 3c5ace3

Browse files
authored
Create Story-Generator.py
1 parent 5b0ac30 commit 3c5ace3

1 file changed

Lines changed: 309 additions & 0 deletions

File tree

Lines changed: 309 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,309 @@
1+
import sys
2+
import os
3+
import threading
4+
import tkinter as tk
5+
from tkinter import ttk, messagebox, filedialog
6+
import sv_ttk
7+
import random
8+
import time
9+
10+
# =========================
11+
# Helpers
12+
# =========================
13+
def resource_path(file_name):
14+
base_path = getattr(sys, "_MEIPASS", os.path.dirname(os.path.abspath(__file__)))
15+
return os.path.join(base_path, file_name)
16+
17+
def set_status(msg):
18+
status_var.set(msg)
19+
root.update_idletasks()
20+
21+
# =========================
22+
# App Setup
23+
# =========================
24+
root = tk.Tk()
25+
root.title("Personalized AI-Assisted Story Generator")
26+
root.geometry("850x720")
27+
root.minsize(850, 720)
28+
sv_ttk.set_theme("light")
29+
30+
# =========================
31+
# Globals
32+
# =========================
33+
dark_mode_var = tk.BooleanVar(value=False)
34+
ai_mode_var = tk.BooleanVar(value=True)
35+
story_title_var = tk.StringVar()
36+
story_prompt_var = tk.StringVar()
37+
story_characters_var = tk.StringVar()
38+
story_settings_var = tk.StringVar()
39+
story_plot_var = tk.StringVar()
40+
story_result_var = tk.StringVar(value="")
41+
story_history = [] # List of tuples: (title, story)
42+
43+
# =========================
44+
# Theme Toggle
45+
# =========================
46+
def toggle_theme():
47+
bg = "#2E2E2E" if dark_mode_var.get() else "#FFFFFF"
48+
fg = "white" if dark_mode_var.get() else "black"
49+
root.configure(bg=bg)
50+
for w in ["TFrame", "TLabel", "TLabelframe", "TLabelframe.Label", "TCheckbutton"]:
51+
style.configure(w, background=bg, foreground=fg)
52+
for entry in [title_entry, prompt_entry, characters_entry, settings_entry, plot_entry]:
53+
entry.configure(background=bg, foreground=fg)
54+
55+
# =========================
56+
# AI-Assisted Story Generation
57+
# =========================
58+
DEFAULT_CHARACTERS = ["a brave knight", "a curious child", "an old wizard", "a fearless explorer", "a clever detective"]
59+
DEFAULT_SETTINGS = ["in a distant kingdom", "on a mysterious island", "in a bustling city", "inside a magical forest", "under the deep sea"]
60+
DEFAULT_PLOTS = ["faces a great challenge", "discovers a hidden secret", "must save a friend", "encounters a mysterious enemy", "uncovers an ancient artifact"]
61+
RESOLUTIONS = ["overcomes the obstacle", "finds a surprising ally", "learns an important lesson", "achieves their dream", "restores peace to the land"]
62+
DETAILS = [
63+
"The sky was painted with shades of orange and violet as the sun set.",
64+
"A chilling wind whispered secrets through the trees.",
65+
"Footsteps echoed in the empty hallways.",
66+
"A mysterious artifact glowed faintly in the moonlight.",
67+
"Laughter and music filled the distant town."
68+
]
69+
DIALOGUE = [
70+
'"I never thought this day would come," said the character.',
71+
'"We must act quickly before it is too late!"',
72+
'"Do not fear, together we can face anything."',
73+
'"What lies beyond that door may change everything..."'
74+
]
75+
76+
def parse_user_input(input_str, default_list):
77+
items = [i.strip() for i in input_str.split(",") if i.strip()]
78+
return items if items else default_list
79+
80+
def generate_ai_paragraph(title, prompt, characters, settings, plots):
81+
char = random.choice(characters)
82+
setting = random.choice(settings)
83+
plot = random.choice(plots)
84+
resolution = random.choice(RESOLUTIONS)
85+
detail = random.choice(DETAILS)
86+
dialogue = random.choice(DIALOGUE)
87+
88+
paragraph = (
89+
f"{title} begins with {char} {setting}. {detail} "
90+
f"One day, {char} {plot} related to {prompt}. "
91+
f"{dialogue} Finally, {char} {resolution}."
92+
)
93+
return paragraph
94+
95+
def generate_ai_story(title, prompt, characters_input, settings_input, plot_input, paragraphs=4):
96+
characters = parse_user_input(characters_input, DEFAULT_CHARACTERS)
97+
settings = parse_user_input(settings_input, DEFAULT_SETTINGS)
98+
plots = parse_user_input(plot_input, DEFAULT_PLOTS)
99+
100+
story = []
101+
for _ in range(paragraphs):
102+
story.append(generate_ai_paragraph(title, prompt, characters, settings, plots))
103+
return "\n\n".join(story)
104+
105+
def generate_basic_story(title, prompt, paragraphs=3):
106+
templates = [
107+
"Once upon a time, {title} {prompt} ... And then something unexpected happened!",
108+
"In a land far away, {title} faced a challenge: {prompt}. What happens next will amaze you!",
109+
"{title} was just an ordinary character until {prompt}. The adventure begins.",
110+
"Long ago, in a world unknown, {title} discovered that {prompt}. This changed everything."
111+
]
112+
story = []
113+
for _ in range(paragraphs):
114+
template = random.choice(templates)
115+
story.append(template.format(title=title, prompt=prompt))
116+
return "\n\n".join(story)
117+
118+
# =========================
119+
# Story Generation & History
120+
# =========================
121+
def create_story():
122+
title = story_title_var.get().strip()
123+
prompt = story_prompt_var.get().strip()
124+
characters_input = story_characters_var.get()
125+
settings_input = story_settings_var.get()
126+
plot_input = story_plot_var.get()
127+
128+
if not title or not prompt:
129+
messagebox.showwarning("Missing Input", "Please enter at least a story title and a prompt.")
130+
return
131+
132+
set_status("Generating story...")
133+
threading.Thread(target=_generate_story_thread, args=(title, prompt, characters_input, settings_input, plot_input), daemon=True).start()
134+
135+
def _generate_story_thread(title, prompt, characters_input, settings_input, plot_input):
136+
if ai_mode_var.get():
137+
story = generate_ai_story(
138+
title, prompt, characters_input, settings_input, plot_input, paragraphs=random.randint(4, 7)
139+
)
140+
else:
141+
story = generate_basic_story(title, prompt, paragraphs=random.randint(3, 5))
142+
143+
story_result_var.set(story)
144+
add_to_history(title, story)
145+
set_status("Story generated!")
146+
147+
def add_to_history(title, story):
148+
story_history.append((title, story))
149+
preview = story.split("\n\n")[0][:100].replace("\n", " ") # first 100 chars
150+
history_list.insert(tk.END, f"{title}: {preview}...")
151+
152+
def export_history_txt():
153+
if not story_history:
154+
messagebox.showinfo("Empty History", "No stories to export.")
155+
return
156+
157+
file_path = filedialog.asksaveasfilename(
158+
defaultextension=".txt",
159+
filetypes=[("Text Files", "*.txt")],
160+
title="Export Story History"
161+
)
162+
if not file_path:
163+
return
164+
165+
try:
166+
with open(file_path, "w", encoding="utf-8") as f:
167+
f.write("Personalized AI-Assisted Story Generator History\n")
168+
f.write("=" * 50 + "\n\n")
169+
for i, (title, story) in enumerate(story_history, 1):
170+
f.write(f"{i}. Title: {title}\n")
171+
f.write(f" Story:\n{story}\n\n")
172+
set_status("Story history exported")
173+
messagebox.showinfo("Export Successful", "Story history saved successfully.")
174+
except Exception as e:
175+
messagebox.showerror("Export Failed", str(e))
176+
177+
# =========================
178+
# Scrollable Story Viewer with Typing Effect
179+
# =========================
180+
def view_selected_story(event=None):
181+
selection = history_list.curselection()
182+
if not selection:
183+
messagebox.showinfo("No Selection", "Please select a story from the history vault.")
184+
return
185+
index = selection[0]
186+
title, story = story_history[index]
187+
188+
story_window = tk.Toplevel(root)
189+
story_window.title(f"Full Story - {title}")
190+
story_window.geometry("800x600")
191+
story_window.rowconfigure(0, weight=1)
192+
story_window.columnconfigure(0, weight=1)
193+
194+
# Make it modal
195+
story_window.transient(root) # Keep above main window
196+
story_window.grab_set() # Block interaction with main window
197+
198+
frame = ttk.Frame(story_window, padding=10)
199+
frame.grid(row=0, column=0, sticky="nsew")
200+
frame.rowconfigure(0, weight=1)
201+
frame.columnconfigure(0, weight=1)
202+
203+
text_widget = tk.Text(frame, wrap="word", font=("Segoe UI", 12))
204+
text_widget.grid(row=0, column=0, sticky="nsew")
205+
206+
scrollbar = ttk.Scrollbar(frame, orient="vertical", command=text_widget.yview)
207+
scrollbar.grid(row=0, column=1, sticky="ns")
208+
text_widget.configure(yscrollcommand=scrollbar.set)
209+
text_widget.configure(state="normal")
210+
text_widget.delete("1.0", tk.END)
211+
212+
paragraphs = story.split("\n\n")
213+
214+
def type_paragraph(idx=0):
215+
if idx >= len(paragraphs):
216+
text_widget.configure(state="disabled")
217+
# Release modal behavior when typing finished
218+
story_window.grab_release()
219+
return
220+
221+
text = paragraphs[idx]
222+
char_idx = 0
223+
224+
def type_char():
225+
nonlocal char_idx
226+
if char_idx < len(text):
227+
text_widget.insert(tk.END, text[char_idx])
228+
text_widget.see(tk.END)
229+
char_idx += 1
230+
text_widget.after(5, type_char)
231+
else:
232+
text_widget.insert(tk.END, "\n\n")
233+
text_widget.after(200, lambda: type_paragraph(idx+1))
234+
235+
type_char()
236+
237+
type_paragraph()
238+
story_window.focus()
239+
240+
# =========================
241+
# Styles
242+
# =========================
243+
style = ttk.Style()
244+
style.theme_use("clam")
245+
style.configure("Action.TButton", font=("Segoe UI", 11, "bold"), padding=8)
246+
247+
# =========================
248+
# Status Bar
249+
# =========================
250+
status_var = tk.StringVar(value="Ready")
251+
ttk.Label(root, textvariable=status_var, anchor="w").pack(side=tk.BOTTOM, fill="x")
252+
253+
# =========================
254+
# UI Layout
255+
# =========================
256+
main = ttk.Frame(root, padding=20)
257+
main.pack(expand=True, fill="both")
258+
main.columnconfigure(0, weight=1)
259+
260+
ttk.Label(main, text="Personalized AI-Assisted Story Generator", font=("Segoe UI", 22, "bold")).grid(row=0, column=0, sticky="ew", pady=(0,10))
261+
262+
# Inputs
263+
ttk.Label(main, text="Story Title:", font=("Segoe UI", 12)).grid(row=1, column=0, sticky="w")
264+
title_entry = ttk.Entry(main, textvariable=story_title_var, font=("Segoe UI", 14))
265+
title_entry.grid(row=2, column=0, sticky="ew", pady=2)
266+
267+
ttk.Label(main, text="Story Prompt/Theme:", font=("Segoe UI", 12)).grid(row=3, column=0, sticky="w")
268+
prompt_entry = ttk.Entry(main, textvariable=story_prompt_var, font=("Segoe UI", 14))
269+
prompt_entry.grid(row=4, column=0, sticky="ew", pady=2)
270+
271+
ttk.Label(main, text="Characters (comma-separated, optional):", font=("Segoe UI", 12)).grid(row=5, column=0, sticky="w")
272+
characters_entry = ttk.Entry(main, textvariable=story_characters_var, font=("Segoe UI", 12))
273+
characters_entry.grid(row=6, column=0, sticky="ew", pady=2)
274+
275+
ttk.Label(main, text="Settings/Locations (comma-separated, optional):", font=("Segoe UI", 12)).grid(row=7, column=0, sticky="w")
276+
settings_entry = ttk.Entry(main, textvariable=story_settings_var, font=("Segoe UI", 12))
277+
settings_entry.grid(row=8, column=0, sticky="ew", pady=2)
278+
279+
ttk.Label(main, text="Plot Elements (comma-separated, optional):", font=("Segoe UI", 12)).grid(row=9, column=0, sticky="w")
280+
plot_entry = ttk.Entry(main, textvariable=story_plot_var, font=("Segoe UI", 12))
281+
plot_entry.grid(row=10, column=0, sticky="ew", pady=2)
282+
283+
# Controls
284+
controls = ttk.Frame(main)
285+
controls.grid(row=11, column=0, sticky="ew", pady=8)
286+
controls.columnconfigure((0,1), weight=1)
287+
288+
ttk.Button(controls, text="📖 Generate Story", command=create_story, style="Action.TButton").grid(row=0, column=0, padx=4, sticky="ew")
289+
ttk.Button(controls, text="📤 Export History", command=export_history_txt, style="Action.TButton").grid(row=0, column=1, padx=4, sticky="ew")
290+
291+
# Story History Vault
292+
vault = ttk.LabelFrame(main, text="Story History Vault", padding=10)
293+
vault.grid(row=12, column=0, sticky="nsew", pady=10)
294+
main.rowconfigure(12, weight=1)
295+
296+
history_list = tk.Listbox(vault, font=("Segoe UI", 10), height=7)
297+
history_list.pack(fill="both", expand=True)
298+
history_list.bind("<Double-Button-1>", view_selected_story)
299+
300+
# Options
301+
options_frame = ttk.Frame(main)
302+
options_frame.grid(row=13, column=0, sticky="w", pady=6)
303+
ttk.Checkbutton(options_frame, text="Dark Mode", variable=dark_mode_var, command=toggle_theme).pack(side="left", padx=(0,10))
304+
ttk.Checkbutton(options_frame, text="AI-Assisted Mode", variable=ai_mode_var).pack(side="left")
305+
306+
# =========================
307+
# Run App
308+
# =========================
309+
root.mainloop()

0 commit comments

Comments
 (0)