IProject-10 commited on
Commit
88d43e1
Β·
verified Β·
1 Parent(s): 1b5af59

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +320 -147
app.py CHANGED
@@ -1,182 +1,355 @@
1
- import os
2
- import zipfile
3
- import chromadb
4
  import gradio as gr
5
- from langchain_core.prompts import ChatPromptTemplate
6
- from langchain_core.runnables import RunnableLambda, RunnablePassthrough
7
- from langchain_core.output_parsers import StrOutputParser
8
- from langchain_together import ChatTogether
9
- from langchain_community.vectorstores import Chroma
10
- from langchain_community.embeddings import HuggingFaceEmbeddings
11
-
12
- # Log: Check if chroma_store exists
13
- if not os.path.exists("chroma_store"):
14
- print("πŸ” chroma_store folder not found. Attempting to unzip...")
15
- try:
16
- with zipfile.ZipFile("chroma_store.zip", "r") as zip_ref:
17
- zip_ref.extractall("chroma_store")
18
- print("βœ… Successfully extracted chroma_store.zip.")
19
- except Exception as e:
20
- print(f"❌ Failed to unzip chroma_store.zip: {e}")
21
- else:
22
- print("βœ… chroma_store folder already exists. Skipping unzip.")
23
-
24
- # Initialize ChromaDB client
25
- chroma_client = chromadb.PersistentClient(path="./chroma_store")
26
-
27
- # Vector store and retriever
28
- embedding_function = HuggingFaceEmbeddings(model_name="BAAI/bge-base-en-v1.5")
29
- vectorstore = Chroma(
30
- client=chroma_client,
31
- collection_name="imageonline_chunks",
32
- embedding_function=embedding_function
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  )
34
 
35
- retriever = vectorstore.as_retriever(search_kwargs={"k": 3, "filter": {"site": "imageonline"}})
36
-
37
- # Retrieval logic
38
- def retrieve_with_metadata(query, k=5):
39
- docs = retriever.get_relevant_documents(query)
40
- if not docs:
41
- return {"context": "No relevant context found.", "references": []}
42
- top_doc = docs[0]
43
- return {
44
- "context": top_doc.page_content,
45
- "references": [{
46
- "section": top_doc.metadata.get("section", "Unknown"),
47
- "source": top_doc.metadata.get("source", "Unknown")
48
- }]
49
- }
50
-
51
- # LLM setup
52
- llm = ChatTogether(
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
  model="meta-llama/Llama-3-8b-chat-hf",
54
  temperature=0.3,
55
- max_tokens=1024,
56
- top_p=0.7,
57
- together_api_key="a36246d65d8290f43667350b364c5b6bb8562eb50a4b947eec5bd7e79f2dffc6"
58
  )
59
 
60
- # Prompt template
61
- prompt = ChatPromptTemplate.from_template("""
62
- You are an expert assistant for ImageOnline Web Solutions.
63
-
64
- Answer the user's query based ONLY on the following context:
65
-
66
- {context}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
67
 
68
- Query: {question}
69
- """)
 
 
 
 
 
 
 
 
 
 
70
 
71
- rag_chain = (
72
- {
73
- "context": lambda x: retrieve_with_metadata(x)["context"],
74
- "question": RunnablePassthrough()
75
- }
76
- | prompt
77
- | llm
78
- | StrOutputParser()
79
- )
80
 
81
- def get_references(query):
82
- return retrieve_with_metadata(query)["references"]
83
-
84
- # Gradio UI
85
- # def chat_interface(message, history):
86
- # history = history or []
87
- # history.append(("πŸ§‘ You: " + message, "⏳ Generating response..."))
88
- # try:
89
- # answer = rag_chain.invoke(message)
90
- # references = get_references(message)
91
- # if references:
92
- # ref = references[0]
93
- # ref_string = f"\n\nπŸ“š **Reference:**\nSection: {ref['section']}\nURL: {ref['source']}"
94
- # else:
95
- # ref_string = "\n\nπŸ“š **Reference:**\n_None available_"
96
- # full_response = answer + ref_string
97
- # history[-1] = ("πŸ§‘ You: " + message, "πŸ€– Bot: " + full_response)
98
- # except Exception as e:
99
- # history[-1] = ("πŸ§‘ You: " + message, f"πŸ€– Bot: ⚠️ {str(e)}")
100
- # return history, history
101
- from datetime import datetime
102
- import time
103
 
104
- def chat_interface(message, history):
 
105
  history = history or []
 
 
 
106
 
107
- # πŸ•’ Timestamp for user
108
- timestamp = datetime.now().strftime("%H:%M:%S")
109
- user_msg = f"πŸ§‘ **You**\n{message}\n\n<span style='font-size: 0.8em; color: gray;'>⏱️ {timestamp}</span>"
110
-
111
- # ⏳ Show typing indicator
112
  bot_msg = "⏳ _Bot is typing..._"
113
  history.append((user_msg, bot_msg))
114
 
115
  try:
116
- # πŸ’¬ Optional: simulate typing delay (cosmetic only)
117
  time.sleep(0.5)
118
-
119
- # RAG response generation
120
- answer = rag_chain.invoke(message)
121
- references = get_references(message)
122
-
 
 
 
123
  if references:
124
- ref = references[0]
125
- ref_string = f"\n\nπŸ“š **Reference:**\nSection: {ref['section']}\nURL: {ref['source']}"
126
- else:
127
- ref_string = "\n\nπŸ“š **Reference:**\n_None available_"
128
 
129
- full_response = answer + ref_string
130
-
131
- # πŸ•’ Timestamp for bot
132
  timestamp_bot = datetime.now().strftime("%H:%M:%S")
133
- bot_response = f"πŸ€– **Bot**\n{full_response}\n\n<span style='font-size: 0.8em; color: gray;'>⏱️ {timestamp_bot}</span>"
134
-
135
- # Replace typing placeholder
136
- history[-1] = (user_msg, bot_response)
137
-
138
  except Exception as e:
139
  timestamp_bot = datetime.now().strftime("%H:%M:%S")
140
- error_msg = f"πŸ€– **Bot**\n⚠️ {str(e)}\n\n<span style='font-size: 0.8em; color: gray;'>⏱️ {timestamp_bot}</span>"
141
  history[-1] = (user_msg, error_msg)
142
 
143
- return history, history, "" # clear input box
144
-
145
-
146
- # def launch_gradio():
147
- # with gr.Blocks() as demo:
148
- # gr.Markdown("# πŸ’¬ ImageOnline RAG Chatbot")
149
- # gr.Markdown("Ask about Website Designing, Web Development, App Development, About Us, Testimonials etc.")
150
- # chatbot = gr.Chatbot()
151
- # state = gr.State([])
152
- # with gr.Row():
153
- # msg = gr.Textbox(placeholder="Ask your question here...", show_label=False, scale=8)
154
- # send_btn = gr.Button("πŸ“¨ Send", scale=1)
155
- # msg.submit(chat_interface, inputs=[msg, state], outputs=[chatbot, state])
156
- # send_btn.click(chat_interface, inputs=[msg, state], outputs=[chatbot, state])
157
- # with gr.Row():
158
- # clear_btn = gr.Button("🧹 Clear Chat")
159
- # clear_btn.click(fn=lambda: ([], []), outputs=[chatbot, state])
160
- # return demo
161
  def launch_gradio():
162
  with gr.Blocks() as demo:
163
- gr.Markdown("# πŸ’¬ ImageOnline RAG Chatbot")
164
- gr.Markdown("Ask about Website Designing, Web Development, App Development, About Us, Testimonials etc.")
 
 
 
 
 
165
 
166
  chatbot = gr.Chatbot()
167
  state = gr.State([])
168
 
169
- with gr.Row():
170
- msg = gr.Textbox(
171
- placeholder="Ask your question here...",
172
- show_label=False,
173
- scale=8
174
- )
175
- send_btn = gr.Button("πŸ“¨ Send", scale=1)
 
 
176
 
177
- # πŸ”„ Trigger chat and clear input
178
- msg.submit(chat_interface, inputs=[msg, state], outputs=[chatbot, state, msg])
179
- send_btn.click(chat_interface, inputs=[msg, state], outputs=[chatbot, state, msg])
180
 
181
  with gr.Row():
182
  clear_btn = gr.Button("🧹 Clear Chat")
 
1
+ from datetime import datetime, timedelta
2
+ import time
 
3
  import gradio as gr
4
+ import numpy as np
5
+ from llama_index.core import VectorStoreIndex, StorageContext, Settings
6
+ from llama_index.core.node_parser import SimpleNodeParser
7
+ from llama_index.core.prompts import PromptTemplate
8
+ from llama_index.vector_stores.qdrant import QdrantVectorStore
9
+ from llama_index.embeddings.huggingface import HuggingFaceEmbedding
10
+ from llama_index.llms.together import TogetherLLM
11
+ from qdrant_client import QdrantClient
12
+ from sentence_transformers import CrossEncoder
13
+ from typing import Generator, Iterable, Tuple, Any
14
+
15
+ # === Config ===
16
+ MAX_OUTPUT_TOKENS = 300 # hard cap for concise answers
17
+ QDRANT_API_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhY2Nlc3MiOiJtIn0.9Pj8v4ACpX3m5U3SZUrG_jzrjGF-T41J5icZ6EPMxnc"
18
+ QDRANT_URL = "https://d36718f0-be68-4040-b276-f1f39bc1aeb9.us-east4-0.gcp.cloud.qdrant.io"
19
+
20
+ qdrant_client = QdrantClient(url=QDRANT_URL, api_key=QDRANT_API_KEY)
21
+ AVAILABLE_COLLECTIONS = ["ImageOnline", "tezjet-site", "anish-pharma"]
22
+ index_cache = {}
23
+ active_state = {"collection": None, "query_engine": None}
24
+
25
+ # === Normalized Embedding Wrapper ===
26
+ def normalize_vector(vec):
27
+ vec = np.array(vec)
28
+ return vec / np.linalg.norm(vec)
29
+
30
+ class NormalizedEmbedding(HuggingFaceEmbedding):
31
+ def get_text_embedding(self, text: str):
32
+ vec = super().get_text_embedding(text)
33
+ return normalize_vector(vec)
34
+
35
+ def get_query_embedding(self, query: str):
36
+ vec = super().get_query_embedding(query)
37
+ return normalize_vector(vec)
38
+
39
+ embed_model = NormalizedEmbedding(model_name="BAAI/bge-base-en-v1.5")
40
+
41
+ # === LLM (kept for compatibility; streaming uses Together SDK directly) ===
42
+ llm = TogetherLLM(
43
+ model="meta-llama/Llama-3-8b-chat-hf",
44
+ api_key="a36246d65d8290f43667350b364c5b6bb8562eb50a4b947eec5bd7e79f2dffc6",
45
+ temperature=0.3,
46
+ max_tokens=MAX_OUTPUT_TOKENS,
47
+ top_p=0.7
48
+ )
49
+ Settings.embed_model = embed_model
50
+ Settings.llm = llm
51
+
52
+ # === Cross-Encoder for Reranking ===
53
+ reranker = CrossEncoder("cross-encoder/ms-marco-MiniLM-L-6-v2")
54
+
55
+ # === Prompt Template (Optimized for Conciseness & Token Limit) ===
56
+ custom_prompt = PromptTemplate(
57
+ "You are an expert assistant for ImageOnline Pvt Ltd.\n"
58
+ "Instructions:\n"
59
+ "- Be concise, factual, and to the point.\n"
60
+ "- Use bullet points where possible.\n"
61
+ "- Do not repeat previous answers unless asked.\n"
62
+ "- Stop once the question is addressed.\n"
63
+ "- If user may need more detail, invite follow-up questions.\n"
64
+ f"- Keep the answer within {MAX_OUTPUT_TOKENS} tokens.\n\n"
65
+ "Context (summarize if long):\n{context_str}\n\n"
66
+ "Query: {query_str}\n\n"
67
+ "Answer:\n"
68
  )
69
 
70
+ # === Load Index ===
71
+ def load_index_for_collection(collection_name: str) -> VectorStoreIndex:
72
+ vector_store = QdrantVectorStore(
73
+ client=qdrant_client,
74
+ collection_name=collection_name,
75
+ enable_hnsw=True
76
+ )
77
+ storage_context = StorageContext.from_defaults(vector_store=vector_store)
78
+ return VectorStoreIndex.from_vector_store(vector_store=vector_store, storage_context=storage_context)
79
+
80
+ # === Reference Renderer ===
81
+ def get_clickable_references_from_response(source_nodes, max_refs=2):
82
+ seen = set()
83
+ links = []
84
+ for node in source_nodes:
85
+ metadata = node.node.metadata
86
+ section = metadata.get("section") or metadata.get("title") or "Unknown"
87
+ source = metadata.get("source") or "Unknown"
88
+ key = (section, source)
89
+ if key not in seen:
90
+ seen.add(key)
91
+ if source.startswith("http"):
92
+ links.append(f"- [{section}]({source})")
93
+ else:
94
+ links.append(f"- {section}: {source}")
95
+ if len(links) >= max_refs:
96
+ break
97
+ return links
98
+
99
+ # === Safe Streaming Adapter for Together API (True Streaming) ===
100
+ from together import Together
101
+
102
+ def _extract_event_text(event: Any) -> str:
103
+ try:
104
+ choices = getattr(event, "choices", None)
105
+ if choices:
106
+ first = choices[0]
107
+ delta = getattr(first, "delta", None)
108
+ if delta:
109
+ text = getattr(delta, "content", None)
110
+ if text:
111
+ return text
112
+ text = getattr(first, "text", None)
113
+ if text:
114
+ return text
115
+ except Exception:
116
+ pass
117
+ try:
118
+ if isinstance(event, dict):
119
+ choices = event.get("choices")
120
+ if choices and len(choices) > 0:
121
+ first = choices[0]
122
+ delta = first.get("delta") if isinstance(first, dict) else None
123
+ if isinstance(delta, dict):
124
+ return delta.get("content", "") or delta.get("text", "") or ""
125
+ message = first.get("message") or {}
126
+ if isinstance(message, dict):
127
+ return message.get("content", "") or ""
128
+ return first.get("text", "") or ""
129
+ except Exception:
130
+ pass
131
+ return ""
132
+
133
+ def _extract_response_text(resp: Any) -> str:
134
+ try:
135
+ choices = getattr(resp, "choices", None)
136
+ if choices and len(choices) > 0:
137
+ first = choices[0]
138
+ message = getattr(first, "message", None)
139
+ if message:
140
+ content = getattr(message, "content", None)
141
+ if content:
142
+ return content
143
+ if isinstance(message, dict):
144
+ return message.get("content", "") or ""
145
+ text = getattr(first, "text", None)
146
+ if text:
147
+ return text
148
+ except Exception:
149
+ pass
150
+ try:
151
+ if isinstance(resp, dict):
152
+ choices = resp.get("choices", [])
153
+ if choices:
154
+ first = choices[0]
155
+ message = first.get("message") or {}
156
+ if isinstance(message, dict):
157
+ return message.get("content", "") or ""
158
+ return first.get("text", "") or ""
159
+ except Exception:
160
+ pass
161
+ return str(resp)
162
+
163
+ class StreamingLLMAdapter:
164
+ def __init__(self, api_key: str, model: str, temperature: float = 0.3, top_p: float = 0.7, chunk_size: int = 64):
165
+ self.client = Together(api_key=api_key)
166
+ self.model = model
167
+ self.temperature = temperature
168
+ self.top_p = top_p
169
+ self.chunk_size = chunk_size
170
+
171
+ def stream_complete(self, prompt: str, max_tokens: int = MAX_OUTPUT_TOKENS, **kwargs) -> Generator[str, None, None]:
172
+ try:
173
+ events = self.client.chat.completions.create(
174
+ model=self.model,
175
+ messages=[{"role": "user", "content": prompt}],
176
+ max_tokens=max_tokens,
177
+ temperature=self.temperature,
178
+ top_p=self.top_p,
179
+ stream=True
180
+ )
181
+ for event in events:
182
+ text_piece = _extract_event_text(event)
183
+ if text_piece:
184
+ yield text_piece
185
+ except Exception:
186
+ yield from self._sync_fallback(prompt, max_tokens, **kwargs)
187
+
188
+ def _sync_fallback(self, prompt: str, max_tokens: int = MAX_OUTPUT_TOKENS, **kwargs) -> Generator[str, None, None]:
189
+ try:
190
+ resp = self.client.chat.completions.create(
191
+ model=self.model,
192
+ messages=[{"role": "user", "content": prompt}],
193
+ max_tokens=max_tokens,
194
+ temperature=self.temperature,
195
+ top_p=self.top_p
196
+ )
197
+ text = _extract_response_text(resp)
198
+ except Exception as e:
199
+ text = f"[Error from LLM: {e}]"
200
+ for i in range(0, len(text), self.chunk_size):
201
+ yield text[i:i + self.chunk_size]
202
+
203
+ streaming_llm = StreamingLLMAdapter(
204
+ api_key="a36246d65d8290f43667350b364c5b6bb8562eb50a4b947eec5bd7e79f2dffc6",
205
  model="meta-llama/Llama-3-8b-chat-hf",
206
  temperature=0.3,
207
+ top_p=0.7
 
 
208
  )
209
 
210
+ # === Query Chain with Reranking ===
211
+ def rag_chain_prompt_and_sources(query: str, top_k: int = 3):
212
+ if not active_state["query_engine"]:
213
+ return None, None, "⚠️ Please select a website collection first."
214
+
215
+ raw_nodes = active_state["query_engine"].retrieve(query)
216
+
217
+ pairs = [(query, n.node.get_content()) for n in raw_nodes]
218
+ scores = reranker.predict(pairs)
219
+ scored_nodes = sorted(zip(raw_nodes, scores), key=lambda x: x[1], reverse=True)
220
+ top_nodes = [n for n, _ in scored_nodes[:top_k]]
221
+
222
+ # Truncate context if too large to save tokens
223
+ context = "\n\n".join([n.node.get_content() for n in top_nodes])
224
+ if len(context) > 4000:
225
+ context = context[:4000] + "...\n[Context truncated for brevity]"
226
+
227
+ prompt = custom_prompt.format(context_str=context, query_str=query)
228
+ return prompt, top_nodes, None
229
+
230
+ # === Collection Switch ===
231
+ def handle_collection_change(selected):
232
+ now = datetime.utcnow()
233
+ cached = index_cache.get(selected)
234
+ if cached:
235
+ query_engine, ts = cached
236
+ if now - ts < timedelta(hours=1):
237
+ active_state["collection"] = selected
238
+ active_state["query_engine"] = query_engine
239
+ return f"βœ… Now chatting with: `{selected}`", [], []
240
+
241
+ index = load_index_for_collection(selected)
242
+ query_engine = index.as_query_engine(similarity_top_k=10, vector_store_query_mode="default")
243
+ index_cache[selected] = (query_engine, now)
244
+ active_state["collection"] = selected
245
+ active_state["query_engine"] = query_engine
246
+ return f"βœ… Now chatting with: `{selected}`", [], []
247
+
248
+ # === Streaming Chat Handler ===
249
+ def chat_interface_stream(message: str, history: list) -> Generator[Tuple[list, list, str], None, None]:
250
+ history = history or []
251
+ message = (message or "").strip()
252
+ if not message:
253
+ yield history, history, ""
254
+ return
255
+
256
+ timestamp_user = datetime.now().strftime("%H:%M:%S")
257
+ user_msg = f"πŸ§‘ **You**\n{message}\n\n⏱️ {timestamp_user}"
258
+ history.append((user_msg, "⏳ _Bot is typing..._"))
259
+ yield history, history, ""
260
+
261
+ prompt, top_nodes, err = rag_chain_prompt_and_sources(message)
262
+ if err:
263
+ history[-1] = (user_msg, f"πŸ€– **Bot**\n{err}")
264
+ yield history, history, ""
265
+ return
266
+
267
+ assistant_text = ""
268
+ chunk_count = 0
269
+ flush_every_n = 3
270
 
271
+ try:
272
+ for chunk in streaming_llm.stream_complete(prompt, max_tokens=MAX_OUTPUT_TOKENS):
273
+ assistant_text += chunk
274
+ chunk_count += 1
275
+ if chunk_count % flush_every_n == 0:
276
+ history[-1] = (user_msg, f"πŸ€– **Bot**\n{assistant_text}")
277
+ yield history, history, ""
278
+ history[-1] = (user_msg, f"πŸ€– **Bot**\n{assistant_text}")
279
+ except Exception as e:
280
+ history[-1] = (user_msg, f"πŸ€– **Bot**\n⚠️ {str(e)}")
281
+ yield history, history, ""
282
+ return
283
 
284
+ references = get_clickable_references_from_response(top_nodes)
285
+ if references:
286
+ assistant_text += "\n\nπŸ“š **Reference(s):**\n" + "\n".join(references)
 
 
 
 
 
 
287
 
288
+ timestamp_bot = datetime.now().strftime("%H:%M:%S")
289
+ history[-1] = (user_msg, f"πŸ€– **Bot**\n{assistant_text.strip()}\n\n⏱️ {timestamp_bot}")
290
+ yield history, history, ""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
291
 
292
+ # Fallback synchronous chat
293
+ def chat_interface_sync(message, history):
294
  history = history or []
295
+ message = message.strip()
296
+ if not message:
297
+ raise ValueError("Please enter a valid question.")
298
 
299
+ timestamp_user = datetime.now().strftime("%H:%M:%S")
300
+ user_msg = f"πŸ§‘ **You**\n{message}\n\n⏱️ {timestamp_user}"
 
 
 
301
  bot_msg = "⏳ _Bot is typing..._"
302
  history.append((user_msg, bot_msg))
303
 
304
  try:
 
305
  time.sleep(0.5)
306
+ prompt, top_nodes, err = rag_chain_prompt_and_sources(message)
307
+ if err:
308
+ timestamp_bot = datetime.now().strftime("%H:%M:%S")
309
+ history[-1] = (user_msg, f"πŸ€– **Bot**\n{err}\n\n⏱️ {timestamp_bot}")
310
+ return history, history, ""
311
+
312
+ resp = llm.complete(prompt, max_tokens=MAX_OUTPUT_TOKENS).text
313
+ references = get_clickable_references_from_response(top_nodes)
314
  if references:
315
+ resp += "\n\nπŸ“š **Reference(s):**\n" + "\n".join(references)
 
 
 
316
 
 
 
 
317
  timestamp_bot = datetime.now().strftime("%H:%M:%S")
318
+ bot_msg = f"πŸ€– **Bot**\n{resp.strip()}\n\n⏱️ {timestamp_bot}"
319
+ history[-1] = (user_msg, bot_msg)
 
 
 
320
  except Exception as e:
321
  timestamp_bot = datetime.now().strftime("%H:%M:%S")
322
+ error_msg = f"πŸ€– **Bot**\n⚠️ {str(e)}\n\n⏱️ {timestamp_bot}"
323
  history[-1] = (user_msg, error_msg)
324
 
325
+ return history, history, ""
326
+
327
+ # === Gradio UI ===
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
328
  def launch_gradio():
329
  with gr.Blocks() as demo:
330
+ gr.Markdown("# πŸ’¬ Demo IOPL Multi-Website Chatbot")
331
+ gr.Markdown("Choose a website to chat with.")
332
+
333
+ with gr.Row():
334
+ collection_dropdown = gr.Dropdown(choices=AVAILABLE_COLLECTIONS, label="Select Website to chat")
335
+ load_button = gr.Button("Load Website")
336
+ collection_status = gr.Markdown("")
337
 
338
  chatbot = gr.Chatbot()
339
  state = gr.State([])
340
 
341
+ with gr.Row(equal_height=True):
342
+ msg = gr.Textbox(placeholder="Ask your question...", show_label=False, scale=9)
343
+ send_btn = gr.Button("πŸš€ Send", scale=1)
344
+
345
+ load_button.click(
346
+ fn=handle_collection_change,
347
+ inputs=collection_dropdown,
348
+ outputs=[collection_status, chatbot, state]
349
+ )
350
 
351
+ msg.submit(chat_interface_stream, inputs=[msg, state], outputs=[chatbot, state, msg])
352
+ send_btn.click(chat_interface_stream, inputs=[msg, state], outputs=[chatbot, state, msg])
 
353
 
354
  with gr.Row():
355
  clear_btn = gr.Button("🧹 Clear Chat")