zamal commited on
Commit
6d3678b
·
verified ·
1 Parent(s): a5216ce

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +109 -204
app.py CHANGED
@@ -54,8 +54,9 @@ vision_model = LlavaNextForConditionalGeneration.from_pretrained(
54
 
55
 
56
  # Add at the top of your module, alongside your other globals
57
- CURRENT_VDB = None
58
-
 
59
 
60
  @spaces.GPU()
61
  def get_image_description(image: Image.Image) -> str:
@@ -97,61 +98,60 @@ SHARED_EMB_FN = embedding_functions.SentenceTransformerEmbeddingFunction(
97
 
98
  def get_vectordb(text: str, images: list[Image.Image], img_names: list[str]):
99
  """
100
- Build an in-memory ChromaDB instance with two collections:
101
  • text_db (chunks of the PDF text)
102
  • image_db (image descriptions + raw image bytes)
103
- Returns the Chroma client for later querying.
104
  """
105
- # ——— 1) Init & wipe old ————————————————
106
- client = chromadb.EphemeralClient()
 
 
 
 
 
 
 
 
 
107
  for col in ("text_db", "image_db"):
108
  if col in [c.name for c in client.list_collections()]:
109
  client.delete_collection(col)
110
 
111
- # ——— 2) Create fresh collections —————————
112
  text_col = client.get_or_create_collection(
113
  name="text_db",
114
- embedding_function=SHARED_EMB_FN,
115
- data_loader=ImageLoader(), # loader only matters for images, benign here
116
  )
117
  img_col = client.get_or_create_collection(
118
  name="image_db",
119
  embedding_function=SHARED_EMB_FN,
120
- metadata={"hnsw:space": "cosine"},
121
- data_loader=ImageLoader(),
122
  )
123
 
124
- # ——— 3) Add images if any ———————————————
125
  if images:
126
- descs = []
127
- metas = []
128
  for idx, img in enumerate(images):
129
- # build one-line caption (or fallback)
130
  try:
131
- caption = get_image_description(img)
132
- except Exception:
133
- caption = "⚠️ could not describe image"
134
- descs.append(f"{img_names[idx]}: {caption}")
135
  metas.append({"image": image_to_bytes(img)})
 
 
 
136
 
137
- img_col.add(
138
- ids=[str(i) for i in range(len(images))],
139
- documents=descs,
140
- metadatas=metas,
141
- )
142
-
143
- # ——— 4) Chunk & add text ———————————————
144
  splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=50)
145
  docs = splitter.create_documents([text])
146
- text_col.add(
147
- ids=[str(i) for i in range(len(docs))],
148
- documents=[d.page_content for d in docs],
149
- )
150
 
151
  return client
152
 
153
 
154
 
 
155
  # Text extraction
156
  def result_to_text(result, as_text=False):
157
  pages = []
@@ -169,18 +169,12 @@ OCR_CHOICES = {
169
  def extract_data_from_pdfs(
170
  docs: list[str],
171
  session: dict,
172
- include_images: str, # "Include Images" or "Exclude Images"
173
- do_ocr: str, # "Get Text With OCR" or "Get Available Text Only"
174
- ocr_choice: str, # key into OCR_CHOICES
175
- vlm_choice: str, # HF repo ID for LlavaNext
176
  progress=gr.Progress()
177
  ):
178
- """
179
- 1) (Optional) OCR setup
180
- 2) Vision+Lang model setup & monkey-patch get_image_description
181
- 3) Extract text & images
182
- 4) Build and stash vector DB in CURRENT_VDB
183
- """
184
  if not docs:
185
  raise gr.Error("No documents to process")
186
 
@@ -193,60 +187,57 @@ def extract_data_from_pdfs(
193
 
194
  # 2) Vision–language model
195
  proc = LlavaNextProcessor.from_pretrained(vlm_choice)
196
- vis = (
197
- LlavaNextForConditionalGeneration
198
- .from_pretrained(vlm_choice, torch_dtype=torch.float16, low_cpu_mem_usage=True)
199
- .to("cuda")
200
- )
201
 
202
- # Monkey-patch our pipeline for image captions
203
- def describe(img: Image.Image) -> str:
204
- torch.cuda.empty_cache()
205
- gc.collect()
206
  prompt = "[INST] <image>\nDescribe the image in a sentence [/INST]"
207
- inputs = proc(prompt, img, return_tensors="pt").to("cuda")
208
- output = vis.generate(**inputs, max_new_tokens=100)
209
- return proc.decode(output[0], skip_special_tokens=True)
210
 
211
- global get_image_description, CURRENT_VDB
212
  get_image_description = describe
213
 
214
- # 3) Extract text + images
215
  progress(0.2, "Extracting text and images…")
216
  all_text = ""
217
  images, names = [], []
218
-
219
  for path in docs:
220
  if local_ocr:
221
  pdf = DocumentFile.from_pdf(path)
222
  res = local_ocr(pdf)
223
  all_text += result_to_text(res, as_text=True) + "\n\n"
224
  else:
225
- txt = PdfReader(path).pages[0].extract_text() or ""
226
- all_text += txt + "\n\n"
227
 
228
  if include_images == "Include Images":
229
  imgs = extract_images([path])
230
  images.extend(imgs)
231
  names.extend([os.path.basename(path)] * len(imgs))
232
 
233
- # 4) Build + store the vector DB
234
  progress(0.6, "Indexing in vector DB…")
235
- CURRENT_VDB = get_vectordb(all_text, images, names)
236
 
 
237
  session["processed"] = True
 
238
  sample_imgs = images[:4] if include_images == "Include Images" else []
239
 
240
- # ─── return *exactly four* picklable outputs ───
241
  return (
242
- session, # gr.State: so UI knows we're ready
243
- all_text[:2000] + "...", # preview text
244
- sample_imgs, # preview images
245
- "<h3>Done!</h3>" # Done message
246
  )
247
 
248
 
249
 
 
250
  # Chat function
251
  def conversation(
252
  session: dict,
@@ -258,46 +249,44 @@ def conversation(
258
  max_tok: int,
259
  model_id: str
260
  ):
261
- """
262
- Uses the global CURRENT_VDB (set by extract_data_from_pdfs) to answer.
263
- """
264
- global CURRENT_VDB
265
- if not session.get("processed") or CURRENT_VDB is None:
266
  raise gr.Error("Please extract data first")
267
 
268
- llm = HuggingFaceEndpoint(
269
- repo_id=model_id,
270
- temperature=temp,
271
- max_new_tokens=max_tok,
272
- huggingfacehub_api_token=HF_TOKEN
273
- )
274
-
275
- # 1) Text retrieval
276
- text_col = CURRENT_VDB.get_collection("text_db")
277
- docs = text_col.query(
278
- query_texts=[question],
279
- n_results=int(num_ctx),
280
- include=["documents"]
281
- )["documents"][0]
282
-
283
- # 2) Image retrieval
284
- img_col = CURRENT_VDB.get_collection("image_db")
285
- img_q = img_col.query(
286
- query_texts=[question],
287
- n_results=int(img_ctx),
288
- include=["metadatas", "documents"]
289
- )
290
  img_descs = img_q["documents"][0] or ["No images found"]
291
  images = []
292
  for meta in img_q["metadatas"][0]:
293
- b64 = meta.get("image", "")
294
  try:
295
  images.append(Image.open(io.BytesIO(base64.b64decode(b64))))
296
  except:
297
  pass
298
  img_desc = "\n".join(img_descs)
299
 
300
- # 3) Build prompt & call LLM
 
 
 
 
 
 
301
  prompt = PromptTemplate(
302
  template="""
303
  Context:
@@ -310,34 +299,27 @@ Question:
310
  {q}
311
 
312
  Answer:
313
- """,
314
- input_variables=["text", "img_desc", "q"],
315
- )
316
- user_input = prompt.format(
317
- text="\n\n".join(docs),
318
- img_desc=img_desc,
319
- q=question
320
  )
 
321
 
322
  try:
323
- answer = llm.invoke(user_input)
324
  except HfHubHTTPError as e:
325
- if e.response.status_code == 404:
326
- answer = f"❌ Model `{model_id}` not hosted on HF Inference API."
327
- else:
328
- answer = f"⚠️ HF API error: {e}"
329
  except Exception as e:
330
  answer = f"⚠️ Unexpected error: {e}"
331
 
332
  new_history = history + [
333
- {"role": "user", "content": question},
334
- {"role": "assistant", "content": answer}
335
  ]
336
  return new_history, docs, images
337
 
338
 
339
 
340
 
 
341
  # ─────────────────────────────────────────────────────────────────────────────
342
  # Gradio UI
343
  CSS = """
@@ -359,128 +341,54 @@ MODEL_OPTIONS = [
359
  ]
360
 
361
  with gr.Blocks(css=CSS, theme=gr.themes.Soft()) as demo:
362
- # State to track that extraction completed (and carry any metadata)
363
  session_state = gr.State({})
364
 
365
- # ─── Welcome Screen ─────────────────────────────────────────────
366
  with gr.Column(visible=True) as welcome_col:
367
- gr.Markdown(
368
- f"<div style='text-align: center'>\n{WELCOME_INTRO}\n</div>",
369
- elem_id="welcome_md"
370
- )
371
  start_btn = gr.Button("🚀 Start")
372
 
373
- # ─── Main App (hidden until Start is clicked) ───────────────────
374
  with gr.Column(visible=False) as app_col:
375
  gr.Markdown("## 📚 Multimodal Chat-PDF Playground")
376
-
377
- # We need to capture the extract‐event so we can chain the “show chat tab” later
378
  extract_event = None
379
 
380
  with gr.Tabs() as tabs:
381
- # ── Tab 1: Upload & Extract ───────────────────────────────
382
  with gr.TabItem("1. Upload & Extract"):
383
- docs = gr.File(
384
- file_count="multiple",
385
- file_types=[".pdf"],
386
- label="Upload PDFs"
387
- )
388
- include_dd = gr.Radio(
389
- ["Include Images", "Exclude Images"],
390
- value="Exclude Images",
391
- label="Images"
392
- )
393
- ocr_radio = gr.Radio(
394
- ["Get Text With OCR", "Get Available Text Only"],
395
- value="Get Available Text Only",
396
- label="OCR"
397
- )
398
- ocr_dd = gr.Dropdown(
399
- choices=list(OCR_CHOICES.keys()),
400
- value=list(OCR_CHOICES.keys())[0],
401
- label="OCR Model"
402
- )
403
- vlm_dd = gr.Dropdown(
404
- choices=[
405
- "llava-hf/llava-v1.6-mistral-7b-hf",
406
- "llava-hf/llava-v1.5-mistral-7b"
407
- ],
408
- value="llava-hf/llava-v1.6-mistral-7b-hf",
409
- label="Vision-Language Model"
410
- )
411
- extract_btn = gr.Button("Extract")
412
- preview_text = gr.Textbox(
413
- lines=10,
414
- label="Sample Text",
415
- interactive=False
416
- )
417
- preview_img = gr.Gallery(
418
- label="Sample Images",
419
- rows=2,
420
- value=[]
421
- )
422
  preview_html = gr.HTML()
423
 
424
- # Kick off extraction and capture the event
425
  extract_event = extract_btn.click(
426
  fn=extract_data_from_pdfs,
427
- inputs=[
428
- docs,
429
- session_state,
430
- include_dd,
431
- ocr_radio,
432
- ocr_dd,
433
- vlm_dd
434
- ],
435
- outputs=[
436
- session_state, # sets session["processed"]=True
437
- preview_text, # shows first bits of text
438
- preview_img, # shows first images
439
- preview_html # shows “<h3>Done!</h3>”
440
- ]
441
  )
442
 
443
- # ── Tab 2: Chat (initially hidden) ──────────────────────────
444
  with gr.TabItem("2. Chat", visible=False) as chat_tab:
445
  with gr.Row():
446
  with gr.Column(scale=3):
447
  chat = gr.Chatbot(type="messages", label="Chat")
448
- msg = gr.Textbox(
449
- placeholder="Ask about your PDF...",
450
- label="Your question"
451
- )
452
  send = gr.Button("Send")
453
  with gr.Column(scale=1):
454
- model_dd = gr.Dropdown(
455
- MODEL_OPTIONS,
456
- value=MODEL_OPTIONS[0],
457
- label="Choose Chat Model"
458
- )
459
- num_ctx = gr.Slider(1, 20, value=3, label="Text Contexts")
460
- img_ctx = gr.Slider(1, 10, value=2, label="Image Contexts")
461
- temp = gr.Slider(0.1, 1.0, step=0.1, value=0.4, label="Temperature")
462
- max_tok = gr.Slider(10, 1000, step=10, value=200, label="Max Tokens")
463
 
464
  send.click(
465
  fn=conversation,
466
- inputs=[
467
- session_state,
468
- msg,
469
- num_ctx,
470
- img_ctx,
471
- chat,
472
- temp,
473
- max_tok,
474
- model_dd
475
- ],
476
- outputs=[
477
- chat,
478
- gr.Dataframe(), # shows retrieved text chunks
479
- gr.Gallery(label="Relevant Images", rows=2, value=[])
480
- ]
481
  )
482
 
483
- # After both tabs are defined, chain the “unhide chat tab” event
484
  extract_event.then(
485
  fn=lambda: gr.update(visible=True),
486
  inputs=[],
@@ -489,13 +397,10 @@ with gr.Blocks(css=CSS, theme=gr.themes.Soft()) as demo:
489
 
490
  gr.HTML("<center>Made with ❤️ by Zamal</center>")
491
 
492
- # ─── Wire the Start button ───────────────────────────────────────
493
  start_btn.click(
494
  fn=lambda: (gr.update(visible=False), gr.update(visible=True)),
495
- inputs=[],
496
  outputs=[welcome_col, app_col]
497
  )
498
 
499
  if __name__ == "__main__":
500
  demo.launch()
501
-
 
54
 
55
 
56
  # Add at the top of your module, alongside your other globals
57
+ PERSIST_DIR = "./chroma_db"
58
+ if os.path.exists(PERSIST_DIR):
59
+ shutil.rmtree(PERSIST_DIR)
60
 
61
  @spaces.GPU()
62
  def get_image_description(image: Image.Image) -> str:
 
98
 
99
  def get_vectordb(text: str, images: list[Image.Image], img_names: list[str]):
100
  """
101
+ Build a *persistent* ChromaDB instance on disk, with two collections:
102
  • text_db (chunks of the PDF text)
103
  • image_db (image descriptions + raw image bytes)
 
104
  """
105
+ # 1) Make or clean the on-disk folder
106
+ shutil.rmtree(PERSIST_DIR, ignore_errors=True)
107
+ os.makedirs(PERSIST_DIR, exist_ok=True)
108
+
109
+ # 2) Persistent client
110
+ client = chromadb.Client(Settings(
111
+ chroma_db_impl="duckdb+parquet",
112
+ persist_directory=PERSIST_DIR
113
+ ))
114
+
115
+ # 3) Create / wipe collections
116
  for col in ("text_db", "image_db"):
117
  if col in [c.name for c in client.list_collections()]:
118
  client.delete_collection(col)
119
 
 
120
  text_col = client.get_or_create_collection(
121
  name="text_db",
122
+ embedding_function=SHARED_EMB_FN
 
123
  )
124
  img_col = client.get_or_create_collection(
125
  name="image_db",
126
  embedding_function=SHARED_EMB_FN,
127
+ metadata={"hnsw:space": "cosine"}
 
128
  )
129
 
130
+ # 4) Add images
131
  if images:
132
+ descs, metas = [], []
 
133
  for idx, img in enumerate(images):
 
134
  try:
135
+ cap = get_image_description(img)
136
+ except:
137
+ cap = "⚠️ could not describe image"
138
+ descs.append(f"{img_names[idx]}: {cap}")
139
  metas.append({"image": image_to_bytes(img)})
140
+ img_col.add(ids=[str(i) for i in range(len(images))],
141
+ documents=descs,
142
+ metadatas=metas)
143
 
144
+ # 5) Chunk & add text
 
 
 
 
 
 
145
  splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=50)
146
  docs = splitter.create_documents([text])
147
+ text_col.add(ids=[str(i) for i in range(len(docs))],
148
+ documents=[d.page_content for d in docs])
 
 
149
 
150
  return client
151
 
152
 
153
 
154
+
155
  # Text extraction
156
  def result_to_text(result, as_text=False):
157
  pages = []
 
169
  def extract_data_from_pdfs(
170
  docs: list[str],
171
  session: dict,
172
+ include_images: str,
173
+ do_ocr: str,
174
+ ocr_choice: str,
175
+ vlm_choice: str,
176
  progress=gr.Progress()
177
  ):
 
 
 
 
 
 
178
  if not docs:
179
  raise gr.Error("No documents to process")
180
 
 
187
 
188
  # 2) Vision–language model
189
  proc = LlavaNextProcessor.from_pretrained(vlm_choice)
190
+ vis = (LlavaNextForConditionalGeneration
191
+ .from_pretrained(vlm_choice, torch_dtype=torch.float16, low_cpu_mem_usage=True)
192
+ .to("cuda"))
 
 
193
 
194
+ # 3) Monkey-patch caption fn
195
+ def describe(img):
196
+ torch.cuda.empty_cache(); gc.collect()
 
197
  prompt = "[INST] <image>\nDescribe the image in a sentence [/INST]"
198
+ inp = proc(prompt, img, return_tensors="pt").to("cuda")
199
+ out = vis.generate(**inp, max_new_tokens=100)
200
+ return proc.decode(out[0], skip_special_tokens=True)
201
 
202
+ global get_image_description
203
  get_image_description = describe
204
 
205
+ # 4) Extract text & images
206
  progress(0.2, "Extracting text and images…")
207
  all_text = ""
208
  images, names = [], []
 
209
  for path in docs:
210
  if local_ocr:
211
  pdf = DocumentFile.from_pdf(path)
212
  res = local_ocr(pdf)
213
  all_text += result_to_text(res, as_text=True) + "\n\n"
214
  else:
215
+ all_text += (PdfReader(path).pages[0].extract_text() or "") + "\n\n"
 
216
 
217
  if include_images == "Include Images":
218
  imgs = extract_images([path])
219
  images.extend(imgs)
220
  names.extend([os.path.basename(path)] * len(imgs))
221
 
222
+ # 5) Build + persist the vectordb
223
  progress(0.6, "Indexing in vector DB…")
224
+ client = get_vectordb(all_text, images, names)
225
 
226
+ # 6) Mark session and return UI outputs
227
  session["processed"] = True
228
+ session["persist_directory"] = PERSIST_DIR
229
  sample_imgs = images[:4] if include_images == "Include Images" else []
230
 
 
231
  return (
232
+ session, # gr.State
233
+ all_text[:2000] + "...",
234
+ sample_imgs,
235
+ "<h3>Done!</h3>"
236
  )
237
 
238
 
239
 
240
+
241
  # Chat function
242
  def conversation(
243
  session: dict,
 
249
  max_tok: int,
250
  model_id: str
251
  ):
252
+ pd = session.get("persist_directory")
253
+ if not session.get("processed") or not pd:
 
 
 
254
  raise gr.Error("Please extract data first")
255
 
256
+ # 1) Reopen the same persistent client
257
+ client = chromadb.Client(Settings(
258
+ chroma_db_impl="duckdb+parquet",
259
+ persist_directory=pd
260
+ ))
261
+
262
+ # 2) Text retrieval
263
+ text_col = client.get_collection("text_db")
264
+ docs = text_col.query(query_texts=[question],
265
+ n_results=int(num_ctx),
266
+ include=["documents"])["documents"][0]
267
+
268
+ # 3) Image retrieval
269
+ img_col = client.get_collection("image_db")
270
+ img_q = img_col.query(query_texts=[question],
271
+ n_results=int(img_ctx),
272
+ include=["metadatas","documents"])
 
 
 
 
 
273
  img_descs = img_q["documents"][0] or ["No images found"]
274
  images = []
275
  for meta in img_q["metadatas"][0]:
276
+ b64 = meta.get("image","")
277
  try:
278
  images.append(Image.open(io.BytesIO(base64.b64decode(b64))))
279
  except:
280
  pass
281
  img_desc = "\n".join(img_descs)
282
 
283
+ # 4) Build prompt & call LLM
284
+ llm = HuggingFaceEndpoint(
285
+ repo_id=model_id,
286
+ temperature=temp,
287
+ max_new_tokens=max_tok,
288
+ huggingfacehub_api_token=HF_TOKEN
289
+ )
290
  prompt = PromptTemplate(
291
  template="""
292
  Context:
 
299
  {q}
300
 
301
  Answer:
302
+ """, input_variables=["text","img_desc","q"]
 
 
 
 
 
 
303
  )
304
+ inp = prompt.format(text="\n\n".join(docs), img_desc=img_desc, q=question)
305
 
306
  try:
307
+ answer = llm.invoke(inp)
308
  except HfHubHTTPError as e:
309
+ answer = "❌ Model not hosted" if e.response.status_code==404 else f"⚠️ HF error: {e}"
 
 
 
310
  except Exception as e:
311
  answer = f"⚠️ Unexpected error: {e}"
312
 
313
  new_history = history + [
314
+ {"role":"user", "content":question},
315
+ {"role":"assistant","content":answer}
316
  ]
317
  return new_history, docs, images
318
 
319
 
320
 
321
 
322
+
323
  # ─────────────────────────────────────────────────────────────────────────────
324
  # Gradio UI
325
  CSS = """
 
341
  ]
342
 
343
  with gr.Blocks(css=CSS, theme=gr.themes.Soft()) as demo:
 
344
  session_state = gr.State({})
345
 
 
346
  with gr.Column(visible=True) as welcome_col:
347
+ gr.Markdown(f"<div style='text-align:center'>{WELCOME_INTRO}</div>")
 
 
 
348
  start_btn = gr.Button("🚀 Start")
349
 
 
350
  with gr.Column(visible=False) as app_col:
351
  gr.Markdown("## 📚 Multimodal Chat-PDF Playground")
 
 
352
  extract_event = None
353
 
354
  with gr.Tabs() as tabs:
 
355
  with gr.TabItem("1. Upload & Extract"):
356
+ docs = gr.File(file_count="multiple", file_types=[".pdf"], label="Upload PDFs")
357
+ include_dd = gr.Radio(["Include Images","Exclude Images"],"Exclude Images","Images")
358
+ ocr_radio = gr.Radio(["Get Text With OCR","Get Available Text Only"],"Get Available Text Only","OCR")
359
+ ocr_dd = gr.Dropdown(list(OCR_CHOICES.keys()), list(OCR_CHOICES.keys())[0], "OCR Model")
360
+ vlm_dd = gr.Dropdown(["llava-hf/llava-v1.6-mistral-7b-hf","llava-hf/llava-v1.5-mistral-7b"], "llava-hf/llava-v1.6-mistral-7b-hf", "Vision-Language Model")
361
+ extract_btn = gr.Button("Extract")
362
+ preview_text = gr.Textbox(lines=10, label="Sample Text", interactive=False)
363
+ preview_img = gr.Gallery(label="Sample Images", rows=2, value=[])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
364
  preview_html = gr.HTML()
365
 
 
366
  extract_event = extract_btn.click(
367
  fn=extract_data_from_pdfs,
368
+ inputs=[docs, session_state, include_dd, ocr_radio, ocr_dd, vlm_dd],
369
+ outputs=[session_state, preview_text, preview_img, preview_html]
 
 
 
 
 
 
 
 
 
 
 
 
370
  )
371
 
 
372
  with gr.TabItem("2. Chat", visible=False) as chat_tab:
373
  with gr.Row():
374
  with gr.Column(scale=3):
375
  chat = gr.Chatbot(type="messages", label="Chat")
376
+ msg = gr.Textbox(placeholder="Ask about your PDF...", label="Your question")
 
 
 
377
  send = gr.Button("Send")
378
  with gr.Column(scale=1):
379
+ model_dd = gr.Dropdown(MODEL_OPTIONS, MODEL_OPTIONS[0], "Choose Chat Model")
380
+ num_ctx = gr.Slider(1,20, value=3, label="Text Contexts")
381
+ img_ctx = gr.Slider(1,10, value=2, label="Image Contexts")
382
+ temp = gr.Slider(0.1,1.0, step=0.1, value=0.4, label="Temperature")
383
+ max_tok = gr.Slider(10,1000, step=10, value=200, label="Max Tokens")
 
 
 
 
384
 
385
  send.click(
386
  fn=conversation,
387
+ inputs=[session_state, msg, num_ctx, img_ctx, chat, temp, max_tok, model_dd],
388
+ outputs=[chat, gr.Dataframe(), gr.Gallery(label="Relevant Images", rows=2, value=[])]
 
 
 
 
 
 
 
 
 
 
 
 
 
389
  )
390
 
391
+ # Unhide the Chat tab once extraction completes
392
  extract_event.then(
393
  fn=lambda: gr.update(visible=True),
394
  inputs=[],
 
397
 
398
  gr.HTML("<center>Made with ❤️ by Zamal</center>")
399
 
 
400
  start_btn.click(
401
  fn=lambda: (gr.update(visible=False), gr.update(visible=True)),
 
402
  outputs=[welcome_col, app_col]
403
  )
404
 
405
  if __name__ == "__main__":
406
  demo.launch()