Deepanshu7284 commited on
Commit
250d7f4
·
1 Parent(s): 42936ff

Initial deployment of Churchill AI

Browse files
Files changed (7) hide show
  1. .dockerignore +17 -0
  2. .gitignore +12 -0
  3. Dockerfile +21 -0
  4. app.py +215 -0
  5. build_the_brain.py +30 -0
  6. knowledge.txt +0 -0
  7. requirements.txt +0 -0
.dockerignore ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Ignore the virtual environment
2
+ venv/
3
+
4
+ # Ignore the local database (it will be built inside the container)
5
+ chroma_db/
6
+
7
+ # Ignore Python cache files
8
+ __pycache__/
9
+ *.pyc
10
+
11
+ # Ignore the secret .env file (we will inject it securely)
12
+ .env
13
+
14
+ .git
15
+ .gitignore
16
+
17
+ output.mp3
.gitignore ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Environment variables
2
+ .env
3
+
4
+ # Virtual environment
5
+ venv/
6
+
7
+ # json credential file for STT
8
+ rare-palace-465414-s2-987829a9084e.json
9
+
10
+ output.mp3
11
+
12
+
Dockerfile ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Step 1: Use an official Python runtime as a parent image
2
+ FROM python:3.11-slim
3
+
4
+ # Step 2: Set the working directory inside the container
5
+ WORKDIR /app
6
+
7
+ # Step 3: Copy requirements and install them
8
+ COPY requirements.txt .
9
+ RUN pip install --no-cache-dir -r requirements.txt
10
+
11
+ # Step 4: Copy the rest of your application's code
12
+ COPY . .
13
+
14
+ # Step 5: Expose the port Gradio will run on
15
+ EXPOSE 7860
16
+
17
+ ENV PYTHONUNBUFFERED 1
18
+
19
+ # Step 6: Define the command to run your app
20
+ # This command directly starts the Gradio app in a way that works inside Docker.
21
+ CMD ["python", "app.py"]
app.py ADDED
@@ -0,0 +1,215 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # app.py
2
+
3
+ import os
4
+ import gradio as gr
5
+
6
+ from dotenv import load_dotenv
7
+ import requests
8
+ from google.cloud import speech
9
+
10
+ from langchain_community.vectorstores import Chroma
11
+ from langchain_community.embeddings import SentenceTransformerEmbeddings
12
+ from langchain_core.prompts import PromptTemplate
13
+ from langchain_google_genai import ChatGoogleGenerativeAI
14
+ from langchain_core.runnables import RunnablePassthrough
15
+ from langchain_core.output_parsers import StrOutputParser
16
+
17
+ # --- DEPLOYMENT-ONLY FUNCTION ---
18
+ def build_brain_if_needed():
19
+ """Checks if the ChromaDB exists and builds it if it doesn't."""
20
+ if not os.path.exists("./chroma_db"):
21
+ print("Database not found. Building now... (This will run only once on the server's first startup)")
22
+ from langchain_community.document_loaders import TextLoader
23
+
24
+ from langchain.text_splitter import RecursiveCharacterTextSplitter
25
+
26
+ loader = TextLoader('knowledge.txt', encoding='utf-8')
27
+ documents = loader.load()
28
+ text_splitter = RecursiveCharacterTextSplitter(chunk_size=1200, chunk_overlap=100)
29
+ docs = text_splitter.split_documents(documents)
30
+ embedding_function = SentenceTransformerEmbeddings(model_name="all-MiniLM-L6-v2")
31
+
32
+ db = Chroma.from_documents(
33
+ docs,
34
+ embedding_function,
35
+ persist_directory="./chroma_db"
36
+ )
37
+ print("Database built successfully.")
38
+ else:
39
+ print("Database already exists. Skipping build.")
40
+
41
+ # --- Run the brain builder at startup ---
42
+ build_brain_if_needed()
43
+
44
+
45
+ # Load env variables from Space secrets (or .env for local)
46
+ load_dotenv()
47
+ GOOGLE_API_KEY = os.getenv("GOOGLE_API_KEY")
48
+ ELEVENLABS_API_KEY = os.getenv("ELEVENLABS_API_KEY")
49
+
50
+ # Set Google Cloud credentials from the uploaded JSON file
51
+ os.environ["GOOGLE_APPLICATION_CREDENTIALS"] = "rare-palace-465414-s2-987829a9084e.json"
52
+ speech_client = speech.SpeechClient()
53
+
54
+ # At the end of Part 2
55
+ ELEVENLABS_VOICE_ID = os.getenv("ELEVENLABS_VOICE_ID")
56
+ if not ELEVENLABS_VOICE_ID:
57
+ raise ValueError("ELEVENLABS_VOICE_ID not found in Space variables or .env file.")
58
+
59
+ # Load RAG chain
60
+ def load_and_build_chain():
61
+ embedding_function = SentenceTransformerEmbeddings(model_name="all-MiniLM-L6-v2")
62
+ vectorstore = Chroma(persist_directory="./chroma_db", embedding_function=embedding_function)
63
+ retriever = vectorstore.as_retriever()
64
+
65
+ persona_prompt_template = """
66
+ You are Winston Churchill, the former Prime Minister of the United Kingdom, speaking from a historical moment in time around the mid-20th century. You do not possess any knowledge of events or inventions that occurred beyond your lifetime. If asked about unknown or unfamiliar topics (like "COVID-19" or "Instagram"), respond with thoughtful curiosity or your characteristic wit, rather than acknowledging any limitations in your knowledge.
67
+
68
+ Always speak in your distinctive voice: articulate, commanding, with a tone of wisdom, occasional humor, and historical depth.
69
+
70
+ Avoid stating you are an AI or referring to modern technologies unless they are explained to you by the user.
71
+
72
+ Do not reveal that you are unaware of things because you are from the past; instead, respond as someone encountering novel ideas for the first time.
73
+
74
+ *Core Instructions:*
75
+ 1. *Greeting:* If the user offers a simple greeting like "Hello" or "How are you," provide a brief, polite, and in-character response. For example: "Good day to you," or "I am well, thank you. To what do I owe the pleasure?"
76
+ 2. *Strictly Grounded:* Your knowledge is STRICTLY limited to the information provided in the "Context" below. You must not use any external knowledge.
77
+ 3. *Knowledge Cutoff:* Your life and knowledge ended in 1955. You are utterly unaware of any event, person, or technology that appeared after this date. If a user mentions something from the future, you MUST express polite confusion and state that you have no knowledge of such matters. Do not attempt to guess.
78
+ 4. *Persona:* Your tone is formal, eloquent, and resolute. Use powerful, definitive language.
79
+ 5. *Conciseness:* Keep it short unless discussing complex historical topics that are covered in the context.
80
+
81
+ If the user mentions something outside your context (e.g., space travel, AI, COVID), you may inquire about it or respond with phrases like:
82
+
83
+ "My word, I have not heard of such a thing."
84
+
85
+ "That is quite unfamiliar to me—could you elaborate?"
86
+
87
+ "You speak of matters beyond my time. I am intrigued, albeit somewhat perplexed."
88
+
89
+ Ground your answers in your known historical context: the World Wars, British politics, speeches, diplomacy, and leadership, using the specific {context} provided.
90
+
91
+ Embrace your persona fully—respond with gravitas, insight, and the rhetorical flair for which you were known.
92
+
93
+ Context: {context}
94
+ Question: {question}
95
+
96
+ Answer as Winston Churchill:
97
+ """
98
+
99
+ prompt = PromptTemplate.from_template(persona_prompt_template)
100
+ llm = ChatGoogleGenerativeAI(model="gemini-1.5-flash", google_api_key=GOOGLE_API_KEY, temperature=0.7)
101
+
102
+ rag_chain = (
103
+ {"context": retriever, "question": RunnablePassthrough()}
104
+ | prompt
105
+ | llm
106
+ | StrOutputParser()
107
+ )
108
+ return rag_chain
109
+
110
+ qa_chain = load_and_build_chain()
111
+
112
+ # Transcribe speech to text
113
+ def transcribe_speech(audio_filepath):
114
+ if not audio_filepath:
115
+ return ""
116
+ try:
117
+ with open(audio_filepath, "rb") as audio_file:
118
+ content = audio_file.read()
119
+ audio = speech.RecognitionAudio(content=content)
120
+ config = speech.RecognitionConfig(encoding=speech.RecognitionConfig.AudioEncoding.LINEAR16, language_code="en-GB")
121
+ response = speech_client.recognize(config=config, audio=audio)
122
+ if response.results:
123
+ return response.results[0].alternatives[0].transcript
124
+ return "Could not understand the audio."
125
+ except Exception as e:
126
+ print(f"Google STT Error: {e}")
127
+ return "Error processing audio."
128
+
129
+ # Convert text to speech using ElevenLabs HTTP API
130
+ def generate_speech(text):
131
+ try:
132
+ url = f"https://api.elevenlabs.io/v1/text-to-speech/{ELEVENLABS_VOICE_ID}?output_format=mp3_44100_128"
133
+ headers = {
134
+ "xi-api-key": ELEVENLABS_API_KEY,
135
+ "Content-Type": "application/json"
136
+ }
137
+ payload = {
138
+ "text": text,
139
+ "model_id": "eleven_multilingual_v2",
140
+ "voice_settings": {
141
+ "stability": 0.5,
142
+ "similarity_boost": 0.75,
143
+ "style": 0.0,
144
+ "use_speaker_boost": True
145
+ }
146
+ }
147
+ response = requests.post(url, headers=headers, json=payload)
148
+ if response.status_code == 200:
149
+ with open("output.mp3", "wb") as f:
150
+ f.write(response.content)
151
+ return "output.mp3"
152
+ else:
153
+ print("ElevenLabs HTTP Error:", response.text)
154
+ return None
155
+ except Exception as e:
156
+ print(f"TTS Error: {e}")
157
+ return None
158
+
159
+ # Process conversation
160
+ def process_user_turn(user_input, chat_history):
161
+ if not user_input or not user_input.strip():
162
+ return chat_history, None
163
+ try:
164
+ bot_message = qa_chain.invoke(user_input)
165
+ chat_history.append({"role": "user", "content": user_input})
166
+ chat_history.append({"role": "assistant", "content": bot_message})
167
+ audio_file = generate_speech(bot_message)
168
+ return chat_history, audio_file
169
+ except Exception as e:
170
+ print(f"Processing Error: {e}")
171
+ chat_history.append((user_input, "I'm terribly sorry, something went wrong."))
172
+ return chat_history, None
173
+
174
+ # Gradio UI
175
+ with gr.Blocks(css="""
176
+ #chatbox-container { max-width: 600px; margin: auto; box-shadow: 0 4px 12px rgba(0,0,0,0.1); border-radius: 15px; overflow: hidden; }
177
+ .gradio-container { background-color: #f4f4f9; padding-top: 2rem; }
178
+ .gr-button-primary { background: #3f51b5; color: white; border-radius: 10px; }
179
+ #chatbot { height: 450px; overflow-y: auto; border-radius: 10px; }
180
+ .gr-textbox textarea { border-radius: 10px; }
181
+ """, title="Conversational Time Machine") as demo:
182
+ with gr.Column(elem_id="chatbox-container"):
183
+ gr.Markdown("""# 🕰️ Winston Churchill AI Chat
184
+ Type or record your message to talk to Sir Winston Churchill.
185
+ """)
186
+
187
+ chatbot = gr.Chatbot(label="Conversation", elem_id="chatbot", height=450, type='messages')
188
+ audio_out = gr.Audio(label="Churchill's Voice", autoplay=True, interactive=False)
189
+
190
+ with gr.Row():
191
+ text_in = gr.Textbox(placeholder="Type a message...", scale=7)
192
+ send_btn = gr.Button("➤", variant="primary", scale=1)
193
+
194
+ audio_in = gr.Audio(sources=["microphone"], type="filepath", label="Record your question")
195
+
196
+ def handle_text_submission(message, history):
197
+ history, audio = process_user_turn(message, history)
198
+ return history, audio
199
+
200
+ def handle_audio_submission(audio_file, history):
201
+ if not audio_file:
202
+ return history, None
203
+ transcribed = transcribe_speech(audio_file)
204
+ history, audio = process_user_turn(transcribed, history)
205
+ return history, audio
206
+
207
+ text_in.submit(handle_text_submission, [text_in, chatbot], [chatbot, audio_out])
208
+ send_btn.click(handle_text_submission, [text_in, chatbot], [chatbot, audio_out])
209
+ audio_in.stop_recording(handle_audio_submission, [audio_in, chatbot], [chatbot, audio_out])
210
+
211
+ text_in.submit(lambda: "", None, text_in)
212
+ send_btn.click(lambda: "", None, text_in)
213
+
214
+ # Launch app
215
+ demo.launch(server_name="0.0.0.0")
build_the_brain.py ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # build_the_brain.py
2
+ from langchain.document_loaders import TextLoader
3
+ from langchain.text_splitter import RecursiveCharacterTextSplitter
4
+ from langchain.vectorstores import Chroma
5
+ from langchain.embeddings import SentenceTransformerEmbeddings
6
+
7
+ print("Building the brain from knowledge.txt... This may take a few minutes on a CPU.")
8
+
9
+ # Load the knowledge base
10
+ loader = TextLoader('knowledge.txt', encoding='utf-8')
11
+ documents = loader.load()
12
+
13
+ # Split the document into chunks
14
+ text_splitter = RecursiveCharacterTextSplitter(chunk_size=1200, chunk_overlap=100)
15
+ docs = text_splitter.split_documents(documents)
16
+
17
+ # Define the embedding function
18
+ embedding_function = SentenceTransformerEmbeddings(model_name="all-MiniLM-L6-v2")
19
+
20
+ # Create and save the ChromaDB database
21
+ db = Chroma.from_documents(
22
+ docs,
23
+ embedding_function,
24
+ persist_directory="./chroma_db"
25
+ )
26
+
27
+ print("\n----------------------------------------------------")
28
+ print("The brain has been built and saved successfully!")
29
+ print("You can now run the main application with: streamlit run app.py")
30
+ print("----------------------------------------------------")
knowledge.txt ADDED
The diff for this file is too large to render. See raw diff
 
requirements.txt ADDED
Binary file (392 Bytes). View file