Sachin B M commited on
Commit
67c26fd
·
1 Parent(s): 52e6ad6

Convert Streamlit app to Gradio

Browse files
Files changed (1) hide show
  1. app.py +521 -144
app.py CHANGED
@@ -1,28 +1,25 @@
1
  import os
2
- import streamlit as st # type: ignore
3
-
4
  from dotenv import load_dotenv
5
  from PyPDF2 import PdfReader
6
 
7
- from langchain_classic.text_splitter import RecursiveCharacterTextSplitter
8
  from langchain_cohere import CohereEmbeddings, ChatCohere
9
  from langchain_community.vectorstores import FAISS
10
  from langchain_classic.chains import ConversationalRetrievalChain
11
- from langchain_classic.memory import ConversationBufferMemory
12
 
13
  load_dotenv()
14
 
15
- st.set_page_config(
16
- page_title="Chat with Documents",
17
- page_icon="📚"
18
- )
19
 
 
 
 
20
 
21
- def get_pdf_text(pdf_docs):
22
  text = ""
23
 
24
- for pdf in pdf_docs:
25
- pdf_reader = PdfReader(pdf)
26
 
27
  for page in pdf_reader.pages:
28
  text += page.extract_text() or ""
@@ -30,6 +27,10 @@ def get_pdf_text(pdf_docs):
30
  return text
31
 
32
 
 
 
 
 
33
  def get_text_chunks(text):
34
  text_splitter = RecursiveCharacterTextSplitter(
35
  separators=["\n\n", "\n", " ", ""],
@@ -41,180 +42,556 @@ def get_text_chunks(text):
41
  return text_splitter.split_text(text)
42
 
43
 
 
 
 
 
44
  def get_embeddings():
45
  return CohereEmbeddings(
46
- model='embed-english-v3.0',
47
  user_agent="langchain"
48
  )
49
 
50
 
 
 
 
 
51
  def get_conversational_chain(vectorstore):
52
  llm = ChatCohere(
53
  model="command-r-08-2024",
54
  temperature=0
55
  )
56
 
57
- memory = ConversationBufferMemory(
58
- memory_key="chat_history",
59
- return_messages=True,
60
- output_key="answer"
61
- )
62
-
63
  conversational_chain = ConversationalRetrievalChain.from_llm(
64
  llm=llm,
65
  retriever=vectorstore.as_retriever(),
66
- memory=memory,
67
- return_source_documents=True
68
  )
69
 
70
  return conversational_chain
71
 
72
 
73
- def handle_userinput(user_question):
74
- response = st.session_state.conversational_chain.invoke(
75
- {"question": user_question}
76
- )
77
 
78
- answer = response["answer"]
79
 
80
- st.session_state.chat_history = (
81
- st.session_state.conversational_chain.memory
82
- .load_memory_variables({})["chat_history"]
83
- )
 
 
84
 
85
- for i, message in enumerate(st.session_state.chat_history):
86
- if message.type == "human":
87
- with st.chat_message("user"):
88
- st.write(message.content)
89
- else:
90
- with st.chat_message("assistant"):
91
- st.write(message.content)
92
-
93
-
94
- def main():
95
- st.markdown(
96
- """
97
- <style>
98
- .main {
99
- background: linear-gradient(135deg, #f8fbff 0%, #eef5ff 100%);
100
- }
101
- .block-container {
102
- padding-top: 2rem;
103
- padding-bottom: 2rem;
104
- }
105
- .hero {
106
- background: linear-gradient(135deg, #0f172a 0%, #1d4ed8 100%);
107
- padding: 2rem 2rem;
108
- border-radius: 22px;
109
- box-shadow: 0 12px 30px rgba(29, 78, 216, 0.18);
110
- margin-bottom: 1.5rem;
111
- margin-top: 1.5rem;
112
- }
113
- .hero h1 {
114
- color: white !important;
115
- font-size: 2.5rem !important;
116
- font-weight: 800 !important;
117
- margin: 0 !important;
118
- }
119
- .hero p {
120
- color: #dbeafe !important;
121
- font-size: 1.05rem !important;
122
- margin-top: 0.5rem !important;
123
- margin-bottom: 0 !important;
124
- }
125
- </style>
126
- """,
127
- unsafe_allow_html=True,
128
- )
129
 
130
- st.markdown(
131
- """
132
- <div class="hero">
133
- <h1> AI Chatbot for Your Documents</h1>
134
- <p>Upload a PDF and ask questions in natural language.</p>
135
- </div>
136
- """,
137
- unsafe_allow_html=True,
138
- )
 
 
139
 
140
- if "conversational_chain" not in st.session_state:
141
- st.session_state.conversational_chain = None
 
 
 
 
142
 
143
- if "chat_history" not in st.session_state:
144
- st.session_state.chat_history = []
145
 
146
- with st.sidebar:
147
- st.header("Upload Documents")
 
 
 
 
148
 
149
- pdf_docs = st.file_uploader(
150
- "Upload your PDF files",
151
- type=["pdf"],
152
- accept_multiple_files=True
153
  )
154
 
155
- if st.button("Process Documents"):
156
- if not pdf_docs:
157
- st.warning("Please upload at least one PDF.")
158
- elif not os.getenv("COHERE_API_KEY"):
159
- st.error("Cohere API key is missing.")
160
- else:
161
- try:
162
- with st.spinner("Processing documents..."):
163
- raw_text = get_pdf_text(pdf_docs)
 
 
164
 
165
- if not raw_text.strip():
166
- st.error("No extractable text found in the PDFs.")
167
- return
168
 
169
- chunks = get_text_chunks(raw_text)
170
 
171
- embeddings = get_embeddings()
 
 
172
 
173
- vectorstore = FAISS.from_texts(
174
- texts=chunks,
175
- embedding=embeddings
176
- )
 
 
 
 
177
 
178
- st.session_state.conversational_chain = (
179
- get_conversational_chain(vectorstore)
180
- )
181
 
182
- st.session_state.chat_history = []
 
183
 
184
- st.success("Documents processed successfully!")
 
 
 
 
185
 
186
- except Exception as e:
187
- st.error(f"Error processing documents: {e}")
188
 
189
- user_question = st.chat_input("Ask a question about your PDFs")
 
 
 
 
 
 
190
 
191
- if user_question:
192
- if st.session_state.conversational_chain is None:
193
- st.warning("Please upload and process your documents first.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
194
  else:
195
- with st.chat_message("user"):
196
- st.write(user_question)
197
-
198
- with st.chat_message("assistant"):
199
- with st.spinner("Thinking..."):
200
- try:
201
- response = (
202
- st.session_state.conversational_chain.invoke(
203
- {"question": user_question}
204
- )
205
- )
206
- st.write(response["answer"])
207
-
208
- st.session_state.chat_history = (
209
- st.session_state.conversational_chain
210
- .memory.load_memory_variables(
211
- {}
212
- )["chat_history"]
213
- )
214
-
215
- except Exception as e:
216
- st.error(f"Error generating answer: {e}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
217
 
 
 
 
218
 
219
  if __name__ == "__main__":
220
- main()
 
1
  import os
2
+ import gradio as gr
 
3
  from dotenv import load_dotenv
4
  from PyPDF2 import PdfReader
5
 
6
+ from langchain_text_splitters import RecursiveCharacterTextSplitter
7
  from langchain_cohere import CohereEmbeddings, ChatCohere
8
  from langchain_community.vectorstores import FAISS
9
  from langchain_classic.chains import ConversationalRetrievalChain
 
10
 
11
  load_dotenv()
12
 
 
 
 
 
13
 
14
+ # =========================================================
15
+ # 1. EXTRACT TEXT FROM PDF FILES
16
+ # =========================================================
17
 
18
+ def get_pdf_text(pdf_files):
19
  text = ""
20
 
21
+ for pdf_file in pdf_files:
22
+ pdf_reader = PdfReader(pdf_file)
23
 
24
  for page in pdf_reader.pages:
25
  text += page.extract_text() or ""
 
27
  return text
28
 
29
 
30
+ # =========================================================
31
+ # 2. SPLIT TEXT INTO CHUNKS
32
+ # =========================================================
33
+
34
  def get_text_chunks(text):
35
  text_splitter = RecursiveCharacterTextSplitter(
36
  separators=["\n\n", "\n", " ", ""],
 
42
  return text_splitter.split_text(text)
43
 
44
 
45
+ # =========================================================
46
+ # 3. CREATE COHERE EMBEDDINGS
47
+ # =========================================================
48
+
49
  def get_embeddings():
50
  return CohereEmbeddings(
51
+ model="embed-english-v3.0",
52
  user_agent="langchain"
53
  )
54
 
55
 
56
+ # =========================================================
57
+ # 4. CREATE CONVERSATIONAL RETRIEVAL CHAIN
58
+ # =========================================================
59
+
60
  def get_conversational_chain(vectorstore):
61
  llm = ChatCohere(
62
  model="command-r-08-2024",
63
  temperature=0
64
  )
65
 
 
 
 
 
 
 
66
  conversational_chain = ConversationalRetrievalChain.from_llm(
67
  llm=llm,
68
  retriever=vectorstore.as_retriever(),
69
+ return_source_documents=False
 
70
  )
71
 
72
  return conversational_chain
73
 
74
 
75
+ # =========================================================
76
+ # 5. PROCESS UPLOADED DOCUMENTS
77
+ # =========================================================
 
78
 
79
+ def process_documents(pdf_files):
80
 
81
+ if not pdf_files:
82
+ return (
83
+ None,
84
+ [],
85
+ "Please upload at least one PDF."
86
+ )
87
 
88
+ if not os.getenv("COHERE_API_KEY"):
89
+ return (
90
+ None,
91
+ [],
92
+ "Error: Cohere API key is missing."
93
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
94
 
95
+ try:
96
+ raw_text = get_pdf_text(pdf_files)
97
+
98
+ if not raw_text.strip():
99
+ return (
100
+ None,
101
+ [],
102
+ "No extractable text found in the PDFs."
103
+ )
104
+
105
+ chunks = get_text_chunks(raw_text)
106
 
107
+ if not chunks:
108
+ return (
109
+ None,
110
+ [],
111
+ "No text chunks were created."
112
+ )
113
 
114
+ embeddings = get_embeddings()
 
115
 
116
+ vectorstore = FAISS.from_texts(
117
+ texts=chunks,
118
+ embedding=embeddings
119
+ )
120
+
121
+ chain = get_conversational_chain(vectorstore)
122
 
123
+ return (
124
+ chain,
125
+ [],
126
+ "Documents processed successfully! You can now ask questions."
127
  )
128
 
129
+ except Exception as e:
130
+ return (
131
+ None,
132
+ [],
133
+ f"Error processing documents: {str(e)}"
134
+ )
135
+
136
+
137
+ # =========================================================
138
+ # 6. CONVERT GRADIO CHAT HISTORY TO LANGCHAIN FORMAT
139
+ # =========================================================
140
 
141
+ def convert_chat_history(history):
142
+ chat_history = []
143
+ pending_question = None
144
 
145
+ for message in history or []:
146
 
147
+ if isinstance(message, dict):
148
+ role = message.get("role")
149
+ content = message.get("content", "")
150
 
151
+ # Convert list-based content into a string
152
+ if isinstance(content, list):
153
+ content = " ".join(
154
+ str(item.get("text", ""))
155
+ if isinstance(item, dict)
156
+ else str(item)
157
+ for item in content
158
+ )
159
 
160
+ content = str(content)
 
 
161
 
162
+ if role == "user":
163
+ pending_question = content
164
 
165
+ elif role == "assistant" and pending_question is not None:
166
+ chat_history.append(
167
+ (pending_question, content)
168
+ )
169
+ pending_question = None
170
 
171
+ elif isinstance(message, (list, tuple)) and len(message) == 2:
172
+ user_message, assistant_message = message
173
 
174
+ if user_message:
175
+ chat_history.append(
176
+ (
177
+ str(user_message),
178
+ str(assistant_message or "")
179
+ )
180
+ )
181
 
182
+ return chat_history
183
+
184
+
185
+ # =========================================================
186
+ # 7. HANDLE USER QUESTIONS
187
+ # =========================================================
188
+
189
+ def handle_user_question(question, history, chain):
190
+
191
+ history = list(history or [])
192
+
193
+ if not question or not question.strip():
194
+ return history, ""
195
+
196
+ question = question.strip()
197
+
198
+ if chain is None:
199
+ history.append({
200
+ "role": "user",
201
+ "content": question
202
+ })
203
+
204
+ history.append({
205
+ "role": "assistant",
206
+ "content": "Please upload and process your PDFs first."
207
+ })
208
+
209
+ return history, ""
210
+
211
+ try:
212
+ # Convert previous messages into LangChain format
213
+ chat_history = convert_chat_history(history)
214
+
215
+ # Ask the question
216
+ response = chain.invoke({
217
+ "question": question,
218
+ "chat_history": chat_history
219
+ })
220
+
221
+ answer = response.get(
222
+ "answer",
223
+ "No answer was generated."
224
+ )
225
+
226
+ # Ensure the answer is a string
227
+ if isinstance(answer, list):
228
+ answer = " ".join(
229
+ str(item.get("text", ""))
230
+ if isinstance(item, dict)
231
+ else str(item)
232
+ for item in answer
233
+ )
234
  else:
235
+ answer = str(answer)
236
+
237
+ # Add the new conversation to Gradio history
238
+ history.append({
239
+ "role": "user",
240
+ "content": question
241
+ })
242
+
243
+ history.append({
244
+ "role": "assistant",
245
+ "content": answer
246
+ })
247
+
248
+ return history, ""
249
+
250
+ except Exception as e:
251
+ history.append({
252
+ "role": "user",
253
+ "content": question
254
+ })
255
+
256
+ history.append({
257
+ "role": "assistant",
258
+ "content": f"Error generating answer: {str(e)}"
259
+ })
260
+
261
+ return history, ""
262
+
263
+
264
+ # =========================================================
265
+ # 8. CLEAR CHAT
266
+ # =========================================================
267
+
268
+ def clear_chat():
269
+ return []
270
+
271
+
272
+ # =========================================================
273
+ # 9. CUSTOM CSS - RESPONSIVE DESIGN
274
+ # =========================================================
275
+
276
+ custom_css = """
277
+ /* Overall application */
278
+ .gradio-container {
279
+ background: #0e0f14 !important;
280
+ color: #f8fafc !important;
281
+ max-width: 100% !important;
282
+ min-height: 100vh;
283
+ padding: 16px !important;
284
+ }
285
+
286
+ /* Main row */
287
+ #main-row {
288
+ display: flex !important;
289
+ align-items: flex-start !important;
290
+ gap: 20px !important;
291
+ max-width: 1500px;
292
+ margin: 0 auto !important;
293
+ }
294
+
295
+ /* Sidebar */
296
+ #sidebar {
297
+ background: #24252f !important;
298
+ border: 1px solid #343642 !important;
299
+ border-radius: 14px !important;
300
+ padding: 16px !important;
301
+ height: fit-content !important;
302
+ min-height: 0 !important;
303
+ align-self: flex-start !important;
304
+ margin-top: 40px;
305
+ }
306
+
307
+ /* Sidebar heading */
308
+ #sidebar h2 {
309
+ font-size: 1.15rem !important;
310
+ margin: 0 0 12px 0 !important;
311
+ color: #ffffff !important;
312
+ }
313
+
314
+ /* Compact PDF uploader */
315
+ #sidebar [data-testid="file-upload"] {
316
+ min-height: 110px !important;
317
+ }
318
+
319
+ /* Process button */
320
+ #process-button {
321
+ margin-top: 10px !important;
322
+ border-radius: 8px !important;
323
+ transition: transform 0.2s ease,
324
+ box-shadow 0.2s ease;
325
+ }
326
+
327
+ /* Button hover effect */
328
+ #process-button:hover {
329
+ transform: translateY(-1px);
330
+ box-shadow: 0 4px 12px rgba(37, 99, 235, 0.3);
331
+ }
332
+
333
+ /* Status box */
334
+ #status {
335
+ margin-top: 10px !important;
336
+ }
337
+
338
+ /* Main content */
339
+ #main-panel {
340
+ padding: 0 8px !important;
341
+ min-width: 0 !important;
342
+ }
343
+
344
+ /* Hero header */
345
+ #hero {
346
+ background: linear-gradient(
347
+ 120deg,
348
+ #0f172a 0%,
349
+ #1d4ed8 100%
350
+ );
351
+ padding: 28px 32px;
352
+ border-radius: 18px;
353
+ box-shadow: 0 10px 30px rgba(29, 78, 216, 0.18);
354
+ margin: 24px 0 18px 0;
355
+ transition: box-shadow 0.2s ease;
356
+ }
357
+
358
+ /* Hero hover effect */
359
+ #hero:hover {
360
+ box-shadow: 0 12px 34px rgba(29, 78, 216, 0.28);
361
+ }
362
+
363
+ /* Hero title */
364
+ #hero h1 {
365
+ color: white !important;
366
+ font-size: clamp(1.5rem, 2.5vw, 2.3rem) !important;
367
+ font-weight: 800 !important;
368
+ line-height: 1.25 !important;
369
+ margin: 0 !important;
370
+ }
371
+
372
+ /* Hero description */
373
+ #hero p {
374
+ color: #dbeafe !important;
375
+ font-size: 1rem !important;
376
+ margin: 10px 0 0 0 !important;
377
+ }
378
+
379
+ /* Chat window */
380
+ #chatbot {
381
+ border-radius: 12px !important;
382
+ min-height: 280px !important;
383
+ height: calc(100vh - 390px) !important;
384
+ max-height: 650px !important;
385
+ overflow: auto !important;
386
+ }
387
+
388
+ /* Question input */
389
+ #question-box {
390
+ margin-top: 12px !important;
391
+ }
392
+
393
+ /* Input field */
394
+ #question-box textarea {
395
+ min-height: 48px !important;
396
+ border-radius: 10px !important;
397
+ }
398
+
399
+ /* Clear button */
400
+ #main-panel button {
401
+ border-radius: 8px !important;
402
+ transition: background 0.2s ease;
403
+ }
404
+
405
+ /* Hide Gradio footer */
406
+ footer {
407
+ display: none !important;
408
+ }
409
+
410
+ /* Tablet screens */
411
+ @media (max-width: 900px) {
412
+ .gradio-container {
413
+ padding: 12px !important;
414
+ }
415
+
416
+ #main-row {
417
+ gap: 14px !important;
418
+ }
419
+
420
+ #sidebar {
421
+ margin-top: 30px;
422
+ padding: 12px !important;
423
+ }
424
+
425
+ #hero {
426
+ padding: 22px;
427
+ }
428
+
429
+ #chatbot {
430
+ height: 55vh !important;
431
+ }
432
+ }
433
+
434
+ /* Mobile screens */
435
+ @media (max-width: 700px) {
436
+ .gradio-container {
437
+ padding: 8px !important;
438
+ }
439
+
440
+ #main-row {
441
+ flex-direction: column !important;
442
+ gap: 12px !important;
443
+ }
444
+
445
+ #sidebar {
446
+ width: 100% !important;
447
+ max-width: 100% !important;
448
+ margin-top: 0 !important;
449
+ padding: 14px !important;
450
+ }
451
+
452
+ #main-panel {
453
+ width: 100% !important;
454
+ max-width: 100% !important;
455
+ padding: 0 !important;
456
+ }
457
+
458
+ #hero {
459
+ margin: 8px 0 14px 0;
460
+ padding: 20px;
461
+ border-radius: 14px;
462
+ }
463
+
464
+ #hero h1 {
465
+ font-size: 1.5rem !important;
466
+ }
467
+
468
+ #hero p {
469
+ font-size: 0.9rem !important;
470
+ }
471
+
472
+ #chatbot {
473
+ height: 55vh !important;
474
+ min-height: 300px !important;
475
+ max-height: none !important;
476
+ }
477
+
478
+ #question-box {
479
+ margin-top: 10px !important;
480
+ }
481
+ }
482
+ """
483
+
484
+
485
+ # =========================================================
486
+ # 10. BUILD GRADIO INTERFACE
487
+ # =========================================================
488
+
489
+ with gr.Blocks(
490
+ title="Chat with Documents",
491
+ theme=gr.themes.Base(),
492
+ css=custom_css
493
+ ) as demo:
494
+
495
+ # Store the conversational chain
496
+ chain_state = gr.State(None)
497
+
498
+ with gr.Row(
499
+ elem_id="main-row",
500
+ equal_height=False
501
+ ):
502
+
503
+ # LEFT SIDEBAR
504
+ with gr.Column(
505
+ scale=1,
506
+ min_width=250,
507
+ elem_id="sidebar"
508
+ ):
509
+
510
+ gr.Markdown("## Upload Documents")
511
+
512
+ pdf_upload = gr.File(
513
+ file_types=[".pdf"],
514
+ file_count="multiple",
515
+ type="filepath",
516
+ height=120
517
+ )
518
+
519
+ process_button = gr.Button(
520
+ "Process Documents",
521
+ variant="primary",
522
+ elem_id="process-button"
523
+ )
524
+
525
+ status = gr.Textbox(
526
+ label="Status",
527
+ interactive=False,
528
+ lines=2,
529
+ elem_id="status"
530
+ )
531
+
532
+ # RIGHT CHAT AREA
533
+ with gr.Column(
534
+ scale=4,
535
+ min_width=300,
536
+ elem_id="main-panel"
537
+ ):
538
+
539
+ # Header
540
+ gr.HTML("""
541
+ <div id="hero">
542
+ <h1>AI Chatbot for Your Documents</h1>
543
+ <p>
544
+ Upload a PDF and ask questions in natural language.
545
+ </p>
546
+ </div>
547
+ """)
548
+
549
+ # Chatbot
550
+ chatbot = gr.Chatbot(
551
+ label="Chat with your PDFs",
552
+ height=500,
553
+ elem_id="chatbot"
554
+ )
555
+
556
+ # Question input
557
+ question = gr.Textbox(
558
+ label="Ask a question",
559
+ placeholder="Ask a question about your PDFs",
560
+ lines=1,
561
+ max_lines=1,
562
+ elem_id="question-box"
563
+ )
564
+
565
+ # Clear chat button
566
+ clear_button = gr.Button(
567
+ "Clear Chat"
568
+ )
569
+
570
+ # Process PDF documents
571
+ process_button.click(
572
+ fn=process_documents,
573
+ inputs=[pdf_upload],
574
+ outputs=[chain_state, chatbot, status]
575
+ )
576
+
577
+ # Press Enter to send a question
578
+ question.submit(
579
+ fn=handle_user_question,
580
+ inputs=[question, chatbot, chain_state],
581
+ outputs=[chatbot, question]
582
+ )
583
+
584
+ # Clear conversation
585
+ clear_button.click(
586
+ fn=clear_chat,
587
+ inputs=[],
588
+ outputs=[chatbot]
589
+ )
590
+
591
 
592
+ # =========================================================
593
+ # 11. LAUNCH APPLICATION
594
+ # =========================================================
595
 
596
  if __name__ == "__main__":
597
+ demo.queue().launch()