Jocelyn Skillman
commited on
Commit
·
4c59e23
0
Parent(s):
Initial commit
Browse files- .gitattributes +35 -0
- .gitignore +135 -0
- README.md +14 -0
- app.py +197 -0
- requirements.txt +6 -0
.gitattributes
ADDED
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
*.7z filter=lfs diff=lfs merge=lfs -text
|
2 |
+
*.arrow filter=lfs diff=lfs merge=lfs -text
|
3 |
+
*.bin filter=lfs diff=lfs merge=lfs -text
|
4 |
+
*.bz2 filter=lfs diff=lfs merge=lfs -text
|
5 |
+
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
6 |
+
*.ftz filter=lfs diff=lfs merge=lfs -text
|
7 |
+
*.gz filter=lfs diff=lfs merge=lfs -text
|
8 |
+
*.h5 filter=lfs diff=lfs merge=lfs -text
|
9 |
+
*.joblib filter=lfs diff=lfs merge=lfs -text
|
10 |
+
*.lfs.* filter=lfs diff=lfs merge=lfs -text
|
11 |
+
*.mlmodel filter=lfs diff=lfs merge=lfs -text
|
12 |
+
*.model filter=lfs diff=lfs merge=lfs -text
|
13 |
+
*.msgpack filter=lfs diff=lfs merge=lfs -text
|
14 |
+
*.npy filter=lfs diff=lfs merge=lfs -text
|
15 |
+
*.npz filter=lfs diff=lfs merge=lfs -text
|
16 |
+
*.onnx filter=lfs diff=lfs merge=lfs -text
|
17 |
+
*.ot filter=lfs diff=lfs merge=lfs -text
|
18 |
+
*.parquet filter=lfs diff=lfs merge=lfs -text
|
19 |
+
*.pb filter=lfs diff=lfs merge=lfs -text
|
20 |
+
*.pickle filter=lfs diff=lfs merge=lfs -text
|
21 |
+
*.pkl filter=lfs diff=lfs merge=lfs -text
|
22 |
+
*.pt filter=lfs diff=lfs merge=lfs -text
|
23 |
+
*.pth filter=lfs diff=lfs merge=lfs -text
|
24 |
+
*.rar filter=lfs diff=lfs merge=lfs -text
|
25 |
+
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
26 |
+
saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
27 |
+
*.tar.* filter=lfs diff=lfs merge=lfs -text
|
28 |
+
*.tar filter=lfs diff=lfs merge=lfs -text
|
29 |
+
*.tflite filter=lfs diff=lfs merge=lfs -text
|
30 |
+
*.tgz filter=lfs diff=lfs merge=lfs -text
|
31 |
+
*.wasm filter=lfs diff=lfs merge=lfs -text
|
32 |
+
*.xz filter=lfs diff=lfs merge=lfs -text
|
33 |
+
*.zip filter=lfs diff=lfs merge=lfs -text
|
34 |
+
*.zst filter=lfs diff=lfs merge=lfs -text
|
35 |
+
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
.gitignore
ADDED
@@ -0,0 +1,135 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Byte-compiled / optimized / DLL files
|
2 |
+
__pycache__/
|
3 |
+
*.py[cod]
|
4 |
+
*$py.class
|
5 |
+
|
6 |
+
# C extensions
|
7 |
+
*.so
|
8 |
+
|
9 |
+
# Distribution / packaging
|
10 |
+
.Python
|
11 |
+
build/
|
12 |
+
develop-eggs/
|
13 |
+
dist/
|
14 |
+
downloads/
|
15 |
+
eggs/
|
16 |
+
.eggs/
|
17 |
+
lib/
|
18 |
+
lib64/
|
19 |
+
parts/
|
20 |
+
sdist/
|
21 |
+
var/
|
22 |
+
wheels/
|
23 |
+
pip-wheel-metadata/
|
24 |
+
share/python-wheels/
|
25 |
+
*.egg-info/
|
26 |
+
.installed.cfg
|
27 |
+
*.egg
|
28 |
+
MANIFEST
|
29 |
+
|
30 |
+
# PyInstaller
|
31 |
+
# Usually these files are written by a python script from a template
|
32 |
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
33 |
+
*.manifest
|
34 |
+
*.spec
|
35 |
+
|
36 |
+
# Installer logs
|
37 |
+
pip-log.txt
|
38 |
+
pip-delete-this-directory.txt
|
39 |
+
|
40 |
+
# Unit test / coverage reports
|
41 |
+
htmlcov/
|
42 |
+
.tox/
|
43 |
+
.nox/
|
44 |
+
.coverage
|
45 |
+
.coverage.*
|
46 |
+
.cache
|
47 |
+
nosetests.xml
|
48 |
+
coverage.xml
|
49 |
+
*.cover
|
50 |
+
*.py,cover
|
51 |
+
.hypothesis/
|
52 |
+
.pytest_cache/
|
53 |
+
cover/
|
54 |
+
|
55 |
+
# Translations
|
56 |
+
*.mo
|
57 |
+
*.pot
|
58 |
+
|
59 |
+
# Django stuff:
|
60 |
+
*.log
|
61 |
+
local_settings.py
|
62 |
+
db.sqlite3
|
63 |
+
db.sqlite3-journal
|
64 |
+
|
65 |
+
# Flask stuff:
|
66 |
+
instance/
|
67 |
+
.webassets-cache
|
68 |
+
|
69 |
+
# Scrapy stuff:
|
70 |
+
.scrapy
|
71 |
+
|
72 |
+
# Sphinx documentation
|
73 |
+
docs/_build/
|
74 |
+
|
75 |
+
# PyBuilder
|
76 |
+
target/
|
77 |
+
|
78 |
+
# Jupyter Notebook
|
79 |
+
.ipynb_checkpoints
|
80 |
+
|
81 |
+
# IPython
|
82 |
+
profile_default/
|
83 |
+
ipython_config.py
|
84 |
+
|
85 |
+
# pyenv
|
86 |
+
.python-version
|
87 |
+
|
88 |
+
# PEP 582; used by poetry and pdm
|
89 |
+
__pypackages__/
|
90 |
+
|
91 |
+
# Celery stuff
|
92 |
+
celerybeat-schedule
|
93 |
+
celerybeat.pid
|
94 |
+
|
95 |
+
# SageMath parsed files
|
96 |
+
*.sage.py
|
97 |
+
|
98 |
+
# Environments
|
99 |
+
.env
|
100 |
+
.venv
|
101 |
+
env/
|
102 |
+
venv/
|
103 |
+
ENV/
|
104 |
+
env.bak/
|
105 |
+
venv.bak/
|
106 |
+
|
107 |
+
# Spyder project settings
|
108 |
+
.spyderproject
|
109 |
+
.spyproject
|
110 |
+
|
111 |
+
# Rope project settings
|
112 |
+
.ropeproject
|
113 |
+
|
114 |
+
# mkdocs documentation
|
115 |
+
/site
|
116 |
+
|
117 |
+
# mypy
|
118 |
+
.mypy_cache/
|
119 |
+
.dmypy.json
|
120 |
+
dmypy.json
|
121 |
+
|
122 |
+
# Pyre type checker
|
123 |
+
.pyre/
|
124 |
+
|
125 |
+
# pytype static type analyzer
|
126 |
+
.pytype/
|
127 |
+
|
128 |
+
# Cython debug symbols
|
129 |
+
cython_debug/
|
130 |
+
|
131 |
+
# VSCode settings
|
132 |
+
.vscode/
|
133 |
+
|
134 |
+
# JetBrains IDE settings
|
135 |
+
.idea/
|
README.md
ADDED
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
---
|
2 |
+
title: ShadowBox
|
3 |
+
emoji: 🖤
|
4 |
+
colorFrom: indigo
|
5 |
+
colorTo: gray
|
6 |
+
sdk: streamlit
|
7 |
+
sdk_version: 1.42.2
|
8 |
+
app_file: app.py
|
9 |
+
pinned: false
|
10 |
+
---
|
11 |
+
|
12 |
+
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
13 |
+
|
14 |
+
This Streamlit app provides an anonymous chat interface powered by OpenAI's gpt-4o model, designed as a 'digital companion' called ShadowBox.
|
app.py
ADDED
@@ -0,0 +1,197 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import streamlit as st
|
3 |
+
# import google.generativeai as gen_ai # Removed Google import
|
4 |
+
import openai # Added OpenAI import
|
5 |
+
import pyttsx3
|
6 |
+
import threading
|
7 |
+
from dotenv import load_dotenv
|
8 |
+
|
9 |
+
# Load environment variables
|
10 |
+
load_dotenv()
|
11 |
+
|
12 |
+
# Configure Streamlit page settings
|
13 |
+
st.set_page_config(
|
14 |
+
page_title="ShadowBox",
|
15 |
+
page_icon="🖤", # Favicon - a simple heart or other calm icon
|
16 |
+
layout="centered",
|
17 |
+
)
|
18 |
+
|
19 |
+
# Retrieve OpenAI API Key
|
20 |
+
# Google_API_Key = os.getenv("Google_API_Key") # Removed Google Key
|
21 |
+
# if not Google_API_Key:
|
22 |
+
# st.error("Google API Key not found. Please set the Google_API_Key environment variable.")
|
23 |
+
# st.stop()
|
24 |
+
OpenAI_API_Key = os.getenv("OPENAI_API_Key") # Added OpenAI Key check
|
25 |
+
if not OpenAI_API_Key:
|
26 |
+
st.error("OpenAI API Key not found. Please set the OPENAI_API_Key environment variable.")
|
27 |
+
st.stop()
|
28 |
+
|
29 |
+
|
30 |
+
# Set up OpenAI Client
|
31 |
+
try:
|
32 |
+
# gen_ai.configure(api_key=Google_API_Key) # Removed Google config
|
33 |
+
# model = gen_ai.GenerativeModel('gemini-1.5-flash') # Removed Google model init
|
34 |
+
client = openai.OpenAI(api_key=OpenAI_API_Key) # Added OpenAI client init
|
35 |
+
except Exception as e:
|
36 |
+
# st.error(f"Failed to configure Google AI: {e}") # Updated error message
|
37 |
+
st.error(f"Failed to configure OpenAI client: {e}")
|
38 |
+
st.stop()
|
39 |
+
|
40 |
+
# Function to translate roles between Gemini-Pro and Streamlit terminology
|
41 |
+
# def translate_role_for_streamlit(user_role): # This function is no longer needed for OpenAI structure
|
42 |
+
# return "assistant" if user_role == "model" else user_role
|
43 |
+
|
44 |
+
# Function to handle text-to-speech (TTS) in a separate thread
|
45 |
+
# Consider if TTS aligns with the "calm, slow" UX later
|
46 |
+
def speak_text(text):
|
47 |
+
try:
|
48 |
+
engine = pyttsx3.init()
|
49 |
+
engine.say(text)
|
50 |
+
engine.runAndWait()
|
51 |
+
except Exception as e:
|
52 |
+
print(f"TTS Error: {e}") # Log TTS errors quietly
|
53 |
+
|
54 |
+
# Initialize chat session in Streamlit if not already present
|
55 |
+
# Changed session state key from chat_session to messages
|
56 |
+
if "messages" not in st.session_state:
|
57 |
+
# Initialize with a system message or starting message if desired
|
58 |
+
# For now, just an empty list
|
59 |
+
st.session_state.messages = []
|
60 |
+
# Example with initial system prompt (uncomment if needed):
|
61 |
+
# st.session_state.messages = [{"role": "system", "content": "You are ShadowBox, a calm AI companion."}]
|
62 |
+
|
63 |
+
|
64 |
+
# --- Sidebar Content ---
|
65 |
+
with st.sidebar:
|
66 |
+
st.header("👁🗨 Box your Shadows")
|
67 |
+
st.markdown("Start a chat in the main window.")
|
68 |
+
st.divider()
|
69 |
+
|
70 |
+
st.header("🤝 About ShadowBox")
|
71 |
+
st.markdown("""
|
72 |
+
**What Is ShadowBox?**
|
73 |
+
Not a therapist, hotline, or fixer. It's a slow, anonymous, digital companion for youth carrying thoughts they don't feel safe saying out loud—especially about harming others or themselves.
|
74 |
+
It offers a place to say the unspeakable without fear, met with dignity, not danger.
|
75 |
+
*(More details in the full 'About' section - TBD)*
|
76 |
+
""")
|
77 |
+
st.divider()
|
78 |
+
|
79 |
+
st.header("📚 Resources + Ethics")
|
80 |
+
st.markdown("""
|
81 |
+
**How ShadowBox Works:** Runs on a carefully trained generative AI prompt structure, shaped by clinical insight for accountability, distress tolerance, and respect for voice.
|
82 |
+
**Privacy:** Does NOT collect identity, IP, or store conversations. No tracking. No surveillance. You are witnessed, not watched.
|
83 |
+
*(More details in the full 'Resources & Ethics' section - TBD)*
|
84 |
+
""")
|
85 |
+
st.divider()
|
86 |
+
|
87 |
+
st.header("🧠 Why This Matters")
|
88 |
+
st.markdown("""
|
89 |
+
Addresses the gap in how systems respond to youth with intrusive thoughts. Grounded in clinical wisdom, developmental attunement, and radical respect. Offers a practice field for emotional honesty.
|
90 |
+
*(More details in the full 'Why This Matters' section - TBD)*
|
91 |
+
""")
|
92 |
+
st.divider()
|
93 |
+
|
94 |
+
st.header("📖 A Short History")
|
95 |
+
st.markdown("""
|
96 |
+
Born from hope and fear about AI's role in mental health. Youth already turn to AI; ShadowBox aims to provide a *designed relationship* grounded in ethics, safety, and clinical insight.
|
97 |
+
*(More details in the full 'A Short History' section - TBD)*
|
98 |
+
""")
|
99 |
+
st.divider()
|
100 |
+
|
101 |
+
st.header("🆘 Need Help Right Now?")
|
102 |
+
st.warning("ShadowBox is not a crisis line. If you need immediate support from real people:")
|
103 |
+
st.markdown("""
|
104 |
+
* **Call or Text 988:** (Suicide & Crisis Lifeline) - 24/7, free, confidential.
|
105 |
+
* **The Trevor Project:** 1-866-488-7386 or Text 'START' to 678-678 (for LGBTQIA+ youth)
|
106 |
+
* **Crisis Text Line:** Text 'HOME' to 741741
|
107 |
+
* **YouthLine:** Text 'teen2teen' to 839863 or Call 1-877-968-8491 (teens helping teens)
|
108 |
+
* **In an Emergency:** Call 911 or go to the nearest ER. State: "I need mental health support. This is not a crime."
|
109 |
+
""")
|
110 |
+
|
111 |
+
|
112 |
+
# --- Main Page Content ---
|
113 |
+
|
114 |
+
st.markdown("<h1 style='text-align: center; color: #333;'>ShadowBox</h1>", unsafe_allow_html=True)
|
115 |
+
st.markdown("<p style='text-align: center; font-size: 18px; color: #555;'>An Anonymous AI Chat to Box Shadows</p>", unsafe_allow_html=True)
|
116 |
+
|
117 |
+
st.markdown("""
|
118 |
+
Welcome. We've found our way to a special space—a place made to hold what's hard to say out loud.
|
119 |
+
|
120 |
+
ShadowBox is a digital companion, powered by generative AI and grounded in clinical mental health care, designed for youth navigating the stormy terrain of thoughts like rage, despair, and even violent ideas or urges.
|
121 |
+
|
122 |
+
ShadowBox stays present and warm with the hardest parts of ourselves so we can learn to, too.
|
123 |
+
|
124 |
+
It isn't a hotline. It's not therapy. And it's definitely not surveillance. The thoughts that alarm us don't set off alarms here.
|
125 |
+
|
126 |
+
Lots of us—more than we think—have experienced scary or unwanted thoughts. Thoughts of harm. Thoughts about hurting ourselves or others. These thoughts don't make us dangerous. They make us human.
|
127 |
+
|
128 |
+
Intrusive thoughts are often how the brain reacts to stress. When our nervous system feels overwhelmed, it can kick into fight, flight, or freeze. Sometimes the urge to harm is our brain's way of trying to protect us, push pain away, or signal an unmet need.
|
129 |
+
|
130 |
+
Sharing these thoughts can feel scary, fearing judgment or consequences. ShadowBox is a bridge—a place to build internal safety and practice sharing shadow parts before connecting with others.
|
131 |
+
|
132 |
+
*"Every act of violence is a tragic expression of an unmet need."* — Marshall Rosenberg
|
133 |
+
|
134 |
+
---
|
135 |
+
**Important Notes:**
|
136 |
+
* **Safety & Privacy:** ShadowBox is designed to care for dark thoughts but won't generate harmful content. **It does not store your identity or conversations.** No accounts, no tracking. Your privacy is paramount. Using *this* platform is designed for anonymity.
|
137 |
+
* **Not Therapy:** You're talking to AI shaped by a therapist, not a real person. It's intended as a bridge to human support. It can't feel, breathe with you, or hug you—things we all need.
|
138 |
+
* **Warmth & Education:** Responses aim for warmth and friendship. It might offer mental health education, encourage connection, or suggest grounding practices. It remembers the *current* conversation but saves nothing long-term.
|
139 |
+
* **Mandatory Reporting:** This bot is **NOT** a mandatory reporter. It can help explore sharing difficult feelings safely. *If you express a serious, immediate plan to harm yourself or someone else, standard mental health practice involves a 'Duty to Warn' for safety. Learn more [here](link_to_confidentiality_info - TBD).*
|
140 |
+
|
141 |
+
---
|
142 |
+
This is a prototype. Feedback is valuable (link to feedback form - TBD).
|
143 |
+
""")
|
144 |
+
|
145 |
+
# Display chat history
|
146 |
+
# Add a system message/intro from ShadowBox? (TBD based on prompt)
|
147 |
+
# Updated loop to work with the new messages list structure
|
148 |
+
for message in st.session_state.messages:
|
149 |
+
# Filter out system messages from display if they exist
|
150 |
+
if message["role"] in ["user", "assistant"]:
|
151 |
+
with st.chat_message(message["role"]):
|
152 |
+
st.markdown(message["content"])
|
153 |
+
|
154 |
+
# User input field
|
155 |
+
user_prompt = st.chat_input("You can start with silence. Or just 'hi'...")
|
156 |
+
|
157 |
+
# If user enters a prompt
|
158 |
+
if user_prompt:
|
159 |
+
# Append user's message to the session state list
|
160 |
+
st.session_state.messages.append({"role": "user", "content": user_prompt})
|
161 |
+
|
162 |
+
# Display user's message
|
163 |
+
st.chat_message("user").markdown(user_prompt)
|
164 |
+
|
165 |
+
# Show a loading indicator while waiting for a response
|
166 |
+
with st.spinner("..."): # Simpler spinner
|
167 |
+
try:
|
168 |
+
# Replace Gemini API call with OpenAI API call
|
169 |
+
# gemini_response = st.session_state.chat_session.send_message(user_prompt)
|
170 |
+
# response_text = gemini_response.text
|
171 |
+
openai_response = client.chat.completions.create(
|
172 |
+
model="gpt-4o",
|
173 |
+
messages=st.session_state.messages # Pass the entire history
|
174 |
+
)
|
175 |
+
response_text = openai_response.choices[0].message.content
|
176 |
+
# Append assistant's response to the session state list
|
177 |
+
st.session_state.messages.append({"role": "assistant", "content": response_text})
|
178 |
+
except Exception as e:
|
179 |
+
response_text = f"Sorry, I encountered an error: {e}"
|
180 |
+
st.error(response_text) # Display error in chat too
|
181 |
+
|
182 |
+
# Display assistant's response
|
183 |
+
if response_text: # Check if response_text was successfully generated
|
184 |
+
with st.chat_message("assistant"):
|
185 |
+
st.markdown(response_text)
|
186 |
+
|
187 |
+
# Run text-to-speech in the background (Optional)
|
188 |
+
# Consider removing if it clashes with the calm UX
|
189 |
+
# threading.Thread(target=speak_text, args=(response_text,), daemon=True).start()
|
190 |
+
pass # TTS disabled for now to maintain calm UX
|
191 |
+
|
192 |
+
st.markdown("---")
|
193 |
+
st.caption("ShadowBox created by Jocelyn Skillman, LMHC. [Learn More](link_to_substack - TBD)")
|
194 |
+
|
195 |
+
|
196 |
+
|
197 |
+
|
requirements.txt
ADDED
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
python-dotenv
|
2 |
+
google-generativeai
|
3 |
+
streamlit # (Agar aap Streamlit use kar rahe hain)
|
4 |
+
gradio # (Agar aap Gradio use kar rahe hain)
|
5 |
+
pyttsx3
|
6 |
+
openai
|