Spaces:
Running
Running
Upload 7 files
Browse files- Dockerfile +25 -23
- app.py +89 -18
- login.py +100 -0
- main.py +6 -0
- src/story_gen.py +11 -5
Dockerfile
CHANGED
|
@@ -1,23 +1,25 @@
|
|
| 1 |
-
FROM python:3.13-slim
|
| 2 |
-
|
| 3 |
-
WORKDIR /app
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
# Install PortAudio + tools
|
| 7 |
-
RUN apt-get update && apt-get install -y \
|
| 8 |
-
portaudio19-dev \
|
| 9 |
-
libportaudio2 \
|
| 10 |
-
libportaudiocpp0 \
|
| 11 |
-
&& apt-get clean && rm -rf /var/lib/apt/lists/*
|
| 12 |
-
|
| 13 |
-
COPY requirements.txt ./
|
| 14 |
-
COPY src/ ./src/
|
| 15 |
-
COPY app.py ./
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM python:3.13-slim
|
| 2 |
+
|
| 3 |
+
WORKDIR /app
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
# Install PortAudio + tools
|
| 7 |
+
RUN apt-get update && apt-get install -y \
|
| 8 |
+
portaudio19-dev \
|
| 9 |
+
libportaudio2 \
|
| 10 |
+
libportaudiocpp0 \
|
| 11 |
+
&& apt-get clean && rm -rf /var/lib/apt/lists/*
|
| 12 |
+
|
| 13 |
+
COPY requirements.txt ./
|
| 14 |
+
COPY src/ ./src/
|
| 15 |
+
COPY app.py ./
|
| 16 |
+
COPY main.py ./
|
| 17 |
+
COPY login.py ./
|
| 18 |
+
|
| 19 |
+
RUN pip3 install -r requirements.txt
|
| 20 |
+
|
| 21 |
+
EXPOSE 8501
|
| 22 |
+
|
| 23 |
+
#HEALTHCHECK CMD curl --fail http://localhost:8501/_stcore/health
|
| 24 |
+
|
| 25 |
+
ENTRYPOINT ["streamlit", "run", "login.py"]
|
app.py
CHANGED
|
@@ -1,27 +1,98 @@
|
|
| 1 |
import streamlit as st
|
| 2 |
from src.story_gen import generate_story
|
| 3 |
from pathlib import Path
|
|
|
|
|
|
|
|
|
|
| 4 |
|
| 5 |
-
|
|
|
|
|
|
|
| 6 |
|
| 7 |
-
st.title("📚 AI Story Generator")
|
| 8 |
-
st.write("Enter a story plot and let AI turn it into a full story!")
|
| 9 |
|
| 10 |
-
|
|
|
|
|
|
|
|
|
|
| 11 |
|
| 12 |
-
|
| 13 |
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
else:
|
| 18 |
-
with st.spinner("Generating story... please wait ⏳"):
|
| 19 |
-
response = generate_story(story_plot=plot)
|
| 20 |
-
story = response.get('story')
|
| 21 |
-
fileName=response.get('fileName')
|
| 22 |
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
import streamlit as st
|
| 2 |
from src.story_gen import generate_story
|
| 3 |
from pathlib import Path
|
| 4 |
+
import threading
|
| 5 |
+
import time
|
| 6 |
+
import os
|
| 7 |
|
| 8 |
+
# ------------------- WAV CLEANUP CONFIG -------------------
|
| 9 |
+
WAV_FOLDER = "." # Change to your folder name
|
| 10 |
+
DELETE_AFTER_SECONDS = 300
|
| 11 |
|
|
|
|
|
|
|
| 12 |
|
| 13 |
+
def delete_wav_files():
|
| 14 |
+
"""Delete .wav files older than 1 hour."""
|
| 15 |
+
if not os.path.exists(WAV_FOLDER):
|
| 16 |
+
return
|
| 17 |
|
| 18 |
+
current_time = time.time()
|
| 19 |
|
| 20 |
+
for filename in os.listdir(WAV_FOLDER):
|
| 21 |
+
if filename.endswith(".wav"):
|
| 22 |
+
file_path = os.path.join(WAV_FOLDER, filename)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
|
| 24 |
+
try:
|
| 25 |
+
file_age = current_time - os.path.getmtime(file_path)
|
| 26 |
+
|
| 27 |
+
if file_age > DELETE_AFTER_SECONDS:
|
| 28 |
+
os.remove(file_path)
|
| 29 |
+
print(f"[CLEANER] Deleted (older than 1 hr): {file_path}")
|
| 30 |
+
|
| 31 |
+
except Exception as e:
|
| 32 |
+
print(f"[CLEANER] Error deleting {file_path}: {e}")
|
| 33 |
+
|
| 34 |
+
def background_cleaner():
|
| 35 |
+
"""Runs every 1 minute."""
|
| 36 |
+
while True:
|
| 37 |
+
delete_wav_files()
|
| 38 |
+
time.sleep(60)
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
def load_app():
|
| 42 |
+
# Start background cleaner only once
|
| 43 |
+
if "bg_started" not in st.session_state:
|
| 44 |
+
thread = threading.Thread(target=background_cleaner, daemon=True)
|
| 45 |
+
thread.start()
|
| 46 |
+
st.session_state.bg_started = True
|
| 47 |
+
print("[CLEANER] Background WAV cleaner started.")
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
# ------------------- STREAMLIT APP -------------------
|
| 51 |
+
|
| 52 |
+
st.set_page_config(page_title="AI Story Generator", page_icon="📚", layout="centered")
|
| 53 |
+
|
| 54 |
+
st.title("📚 AI Story Generator")
|
| 55 |
+
st.write("Enter a story plot and let AI turn it into a full story!")
|
| 56 |
+
|
| 57 |
+
# 🔽 Language dropdown
|
| 58 |
+
language = st.selectbox(
|
| 59 |
+
"Select language for audio:",
|
| 60 |
+
[
|
| 61 |
+
("English", "en"),
|
| 62 |
+
("Hindi", "hi"),
|
| 63 |
+
("Tamil", "ta"),
|
| 64 |
+
("Telugu", "te"),
|
| 65 |
+
("Bengali", "bn"),
|
| 66 |
+
("Gujarati", "gu"),
|
| 67 |
+
("Kannada", "kn"),
|
| 68 |
+
("Malayalam", "ml")
|
| 69 |
+
],
|
| 70 |
+
format_func=lambda x: x[0]
|
| 71 |
+
)
|
| 72 |
+
|
| 73 |
+
plot = st.text_area("Enter your story plot:", height=150)
|
| 74 |
+
|
| 75 |
+
display_story=st.checkbox(label="Display Story")
|
| 76 |
+
|
| 77 |
+
generate = st.button("Generate Story")
|
| 78 |
+
|
| 79 |
+
if generate:
|
| 80 |
+
if not plot.strip():
|
| 81 |
+
st.warning("Please enter a story plot.")
|
| 82 |
+
else:
|
| 83 |
+
with st.spinner("Generating story... please wait ⏳"):
|
| 84 |
+
|
| 85 |
+
# Pass language to your story generator
|
| 86 |
+
response = generate_story(story_plot=plot, language=language)
|
| 87 |
+
|
| 88 |
+
story = response.get('story')
|
| 89 |
+
fileName = response.get('fileName')
|
| 90 |
+
|
| 91 |
+
st.subheader("✨ Generated Story")
|
| 92 |
+
|
| 93 |
+
f = Path(fileName)
|
| 94 |
+
audio_bytes = f.read_bytes()
|
| 95 |
+
st.audio(audio_bytes, format="audio/wav")
|
| 96 |
+
|
| 97 |
+
if display_story:
|
| 98 |
+
st.write(story)
|
login.py
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import streamlit as st
|
| 2 |
+
from hashlib import sha256
|
| 3 |
+
from app import load_app
|
| 4 |
+
import os
|
| 5 |
+
import json
|
| 6 |
+
from dotenv import load_dotenv
|
| 7 |
+
|
| 8 |
+
load_dotenv()
|
| 9 |
+
|
| 10 |
+
# -----------------------
|
| 11 |
+
# Mock user database (replace with DB in production)
|
| 12 |
+
# -----------------------
|
| 13 |
+
|
| 14 |
+
USER_CONFIG = json.loads(os.getenv('USER_CONFIG'))
|
| 15 |
+
|
| 16 |
+
print("********************************************")
|
| 17 |
+
print(type(USER_CONFIG))
|
| 18 |
+
print(USER_CONFIG)
|
| 19 |
+
# -----------------------
|
| 20 |
+
# Authentication Utilities
|
| 21 |
+
# -----------------------
|
| 22 |
+
def hash_password(password: str) -> str:
|
| 23 |
+
return sha256(password.encode()).hexdigest()
|
| 24 |
+
|
| 25 |
+
def is_authenticated(username: str, password: str) -> bool:
|
| 26 |
+
if username in USER_CONFIG:
|
| 27 |
+
return USER_CONFIG[username] == password
|
| 28 |
+
return False
|
| 29 |
+
|
| 30 |
+
def login_page():
|
| 31 |
+
st.markdown(
|
| 32 |
+
"""
|
| 33 |
+
<h2 style="text-align:center;margin-bottom:30px;">🔐 Secure Login</h2>
|
| 34 |
+
""",
|
| 35 |
+
unsafe_allow_html=True,
|
| 36 |
+
)
|
| 37 |
+
|
| 38 |
+
with st.form("login_form"):
|
| 39 |
+
username = st.text_input("Username", placeholder="Enter username")
|
| 40 |
+
password = st.text_input("Password", type="password", placeholder="Enter password")
|
| 41 |
+
submit = st.form_submit_button("Login")
|
| 42 |
+
|
| 43 |
+
if submit:
|
| 44 |
+
if is_authenticated(username, password):
|
| 45 |
+
st.session_state["auth"] = True
|
| 46 |
+
st.session_state["user"] = username
|
| 47 |
+
st.success("Login successful! Redirecting...")
|
| 48 |
+
st.rerun()
|
| 49 |
+
else:
|
| 50 |
+
st.error("Invalid username or password")
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
def logout_button():
|
| 54 |
+
# --- TOP CENTER WARNING ---
|
| 55 |
+
st.markdown(
|
| 56 |
+
"""
|
| 57 |
+
<div style="text-align:center; margin-top:-20px;">
|
| 58 |
+
<p style="color:#5cb85c; font-size:20px; font-weight:bold;">
|
| 59 |
+
Logged in as: <span style="color:black;">{}</span>
|
| 60 |
+
</p>
|
| 61 |
+
</div>
|
| 62 |
+
""".format(st.session_state['user']),
|
| 63 |
+
unsafe_allow_html=True
|
| 64 |
+
)
|
| 65 |
+
|
| 66 |
+
# --- TOP RIGHT LOGOUT BUTTON ---
|
| 67 |
+
logout_col = st.columns([8, 2]) # Push button to the right
|
| 68 |
+
with logout_col[1]:
|
| 69 |
+
if st.button("Logout", key="logout_btn"):
|
| 70 |
+
st.session_state["auth"] = False
|
| 71 |
+
st.session_state["user"] = None
|
| 72 |
+
st.rerun()
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
# -----------------------
|
| 76 |
+
# Main App (Protected)
|
| 77 |
+
# -----------------------
|
| 78 |
+
def main_app():
|
| 79 |
+
logout_button()
|
| 80 |
+
load_app()
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
# -----------------------
|
| 84 |
+
# Streamlit Entry Point
|
| 85 |
+
# -----------------------
|
| 86 |
+
def main():
|
| 87 |
+
# Initialize session state
|
| 88 |
+
if "auth" not in st.session_state:
|
| 89 |
+
st.session_state["auth"] = False
|
| 90 |
+
st.session_state["user"] = None
|
| 91 |
+
|
| 92 |
+
# Render login or app
|
| 93 |
+
if not st.session_state["auth"]:
|
| 94 |
+
login_page()
|
| 95 |
+
else:
|
| 96 |
+
main_app()
|
| 97 |
+
|
| 98 |
+
|
| 99 |
+
if __name__ == "__main__":
|
| 100 |
+
main()
|
main.py
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
def main():
|
| 2 |
+
print("Hello from story-app!")
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
if __name__ == "__main__":
|
| 6 |
+
main()
|
src/story_gen.py
CHANGED
|
@@ -20,7 +20,8 @@ chat = ChatGroq(
|
|
| 20 |
|
| 21 |
|
| 22 |
sys_prompt="""
|
| 23 |
-
|
|
|
|
| 24 |
|
| 25 |
Your goal:
|
| 26 |
- Expand the user's plot into a complete story.
|
|
@@ -52,6 +53,9 @@ Your output:
|
|
| 52 |
|
| 53 |
When ready, ask the user:
|
| 54 |
“Please share your plot or idea for the story.”
|
|
|
|
|
|
|
|
|
|
| 55 |
"""
|
| 56 |
|
| 57 |
simple_prompt="""
|
|
@@ -63,15 +67,17 @@ Write the story in STYLE style with:
|
|
| 63 |
"""
|
| 64 |
|
| 65 |
|
| 66 |
-
def generate_story(story_plot):
|
| 67 |
messages=[
|
| 68 |
-
SystemMessage(content=sys_prompt),
|
| 69 |
HumanMessage(content=story_plot)
|
| 70 |
]
|
| 71 |
response = chat.invoke(messages)
|
| 72 |
fileName=f"file_gtt_{uuid.uuid4().hex}.wav"
|
| 73 |
|
| 74 |
-
|
|
|
|
|
|
|
| 75 |
tts.save(fileName)
|
| 76 |
|
| 77 |
return {
|
|
@@ -80,5 +86,5 @@ def generate_story(story_plot):
|
|
| 80 |
}
|
| 81 |
|
| 82 |
|
| 83 |
-
# res=generate_story("write a story on
|
| 84 |
# print(res.get('story'))
|
|
|
|
| 20 |
|
| 21 |
|
| 22 |
sys_prompt="""
|
| 23 |
+
You are a professional story writer who creates engaging, vivid, emotionally rich stories
|
| 24 |
+
based on a user's plot or idea.
|
| 25 |
|
| 26 |
Your goal:
|
| 27 |
- Expand the user's plot into a complete story.
|
|
|
|
| 53 |
|
| 54 |
When ready, ask the user:
|
| 55 |
“Please share your plot or idea for the story.”
|
| 56 |
+
|
| 57 |
+
Note: Write the story on LANGUAGE only and this story will be given to gtts using LANG for audio
|
| 58 |
+
generation so consider this while creating story
|
| 59 |
"""
|
| 60 |
|
| 61 |
simple_prompt="""
|
|
|
|
| 67 |
"""
|
| 68 |
|
| 69 |
|
| 70 |
+
def generate_story(story_plot,language):
|
| 71 |
messages=[
|
| 72 |
+
SystemMessage(content=sys_prompt.replace("LANGUAGE",language[0]).replace('LANG',language[1])),
|
| 73 |
HumanMessage(content=story_plot)
|
| 74 |
]
|
| 75 |
response = chat.invoke(messages)
|
| 76 |
fileName=f"file_gtt_{uuid.uuid4().hex}.wav"
|
| 77 |
|
| 78 |
+
|
| 79 |
+
|
| 80 |
+
tts = gTTS(text=response.content, lang=language[1])
|
| 81 |
tts.save(fileName)
|
| 82 |
|
| 83 |
return {
|
|
|
|
| 86 |
}
|
| 87 |
|
| 88 |
|
| 89 |
+
# res=generate_story(story_plot="write a story on lion and river",language="assamese")
|
| 90 |
# print(res.get('story'))
|