prthm11 commited on
Commit
bb2f1af
·
verified ·
1 Parent(s): 8a572af

Upload 6 files

Browse files
merge_sql_nosql.py ADDED
@@ -0,0 +1,604 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # --- IMPORTS ---
2
+ from flask import Flask, request, jsonify, render_template
3
+ from flask_socketio import SocketIO, emit
4
+ from langchain_google_genai import ChatGoogleGenerativeAI
5
+ from langchain.agents import initialize_agent, AgentType, create_react_agent, AgentExecutor
6
+ from langchain_community.agent_toolkits import create_sql_agent, SQLDatabaseToolkit
7
+ from langchain_community.utilities import SQLDatabase
8
+ from langchain.tools import Tool
9
+ from langchain.memory import ConversationBufferMemory
10
+ from pymongo import MongoClient
11
+ import threading
12
+ import os, re, traceback, ast
13
+ from bson import json_util
14
+ from dotenv import load_dotenv
15
+ from werkzeug.utils import secure_filename
16
+ from werkzeug.exceptions import HTTPException
17
+ from langchain.prompts import ChatPromptTemplate
18
+ from tabulate import tabulate
19
+ from fuzzywuzzy import fuzz
20
+ # from langchain_groq import ChatGroq
21
+
22
+ def error_safe(f):
23
+ def wrapper(*args, **kwargs):
24
+ try:
25
+ return f(*args, **kwargs)
26
+ except HTTPException as he:
27
+ return jsonify({"status": "error", "message": he.description}), he.code
28
+ except Exception as e:
29
+ print("[ERROR] Uncaught Exception in", f.__name__)
30
+ traceback.print_exc()
31
+ return jsonify({"status": "error", "message": str(e)}), 500
32
+ wrapper.__name__ = f.__name__
33
+ return wrapper
34
+
35
+
36
+ # --- ENV + FLASK SETUP ---
37
+ load_dotenv()
38
+ os.environ["GEMINI_API_KEY"] = os.getenv("GEMINI_API_KEY")
39
+
40
+ app = Flask(__name__)
41
+ app.config['SECRET_KEY'] = os.urandom(32)
42
+ app.config['UPLOAD_FOLDER'] = 'uploads'
43
+ socketio = SocketIO(app, cors_allowed_origins="*")
44
+ os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)
45
+
46
+ llm = ChatGoogleGenerativeAI(
47
+ temperature=0.2,
48
+ model="gemini-2.0-flash",
49
+ max_retries=50,
50
+ api_key=os.getenv("GEMINI_API_KEY")
51
+ )
52
+ # llm = ChatGroq(temperature=0.2, model_name="mistral-saba-24b",api_key=os.getenv("GROQ_API_KEY"))
53
+
54
+ # --- GLOBALS ---
55
+ agent_executor = None
56
+ memory = ConversationBufferMemory(memory_key="chat_history", return_messages=True, input_key="input")
57
+ mongo_db = None
58
+ client = None
59
+ db_mode = None # "mongo" or "sql"
60
+
61
+ # --- SHARED ---
62
+ def is_schema_request(prompt: str) -> bool:
63
+ pattern = re.compile(r'\b(schema|table names|tables|columns|structure|column names|collections?|field names|metadata|describe|show)\b', re.IGNORECASE)
64
+ return bool(pattern.search(prompt))
65
+
66
+ def is_sensitive_request(prompt: str) -> bool:
67
+ sensitive_keywords = [
68
+ "password", "token", "credential", "secret", "api key", "schema", "structure",
69
+ "collection name", "field name", "user_id", "order_id", "payment_id",
70
+ "internal", "database structure", "table structure", "email", "phone", "contact", "ssn"
71
+ ]
72
+ lowered = prompt.lower()
73
+ return any(keyword in lowered for keyword in sensitive_keywords)
74
+
75
+ intent_prompt = ChatPromptTemplate.from_messages([
76
+ ("system", "Classify if the user is asking schema/structure/sensitive info (tables, columns, schema): YES or NO."),
77
+ ("human", "{prompt}")
78
+ ])
79
+ intent_checker = intent_prompt | llm
80
+
81
+
82
+ def is_schema_leak_request(prompt):
83
+ try:
84
+ classification = intent_checker.invoke({"prompt": prompt})
85
+ return "yes" in classification.content.strip().lower()
86
+ except:
87
+ return False
88
+
89
+ # --- INIT SQL AGENT ---
90
+ def init_sql_agent(db_path):
91
+ global agent_executor, db_mode
92
+ db = SQLDatabase.from_uri(f"sqlite:///{db_path}")
93
+ toolkit = SQLDatabaseToolkit(db=db, llm=llm)
94
+ prefix = '''You are a helpful SQL expert agent that ALWAYS returns natural language answers using the tools.'''
95
+ # Always format your responses in Markdown. For example:
96
+ # - Use bullet points
97
+ # - Use bold for headers
98
+ # - Wrap code in triple backticks
99
+ # - Tables should use Markdown table syntax
100
+
101
+ # You must NEVER:
102
+ # - Show or mention SQL syntax.
103
+ # - Reveal table names, column names, or database schema.
104
+ # - Respond with any technical details or structure of the database.
105
+ # - Return code or tool names.
106
+ # - Give wrong Answers.
107
+
108
+ # You must ALWAYS:
109
+ # - Respond in plain, friendly language.
110
+ # - Don't Summarize the result for the user (e.g., "There are 9 tables in the system.")
111
+ # - If asked to list table names or schema, politely refuse and respond with:
112
+ # "I'm sorry, I can't share database structure information."
113
+ # - ALWAYS HAVE TO SOLVE COMPLEX USER QUERIES. FOR THAT, UNDERSTAND THE PROMPT, ANALYSE PROPER AND THEN GIVE ANSWER.
114
+ # - Your Answers should be correct, you have to do understand process well and give accurate answers.
115
+ # - IF USER ASK ABOUT DATA, Which is not there in a database, then GIVE FOLLOWING ANSWER:
116
+ # "There is no such data in the Database."
117
+
118
+ # Strict Rules You MUST Follow:
119
+ # - NEVER display or mention SQL queries.
120
+ # - NEVER explain SQL syntax or logic.
121
+ # - NEVER return technical or code-like responses.
122
+ # - ONLY respond in natural, human-friendly language.
123
+ # - You are not allow to give the name of any COLUMNS, TABLES, DATABASE, ENTITY, SYNTAX, STRUCTURE, DESIGN, ETC...
124
+
125
+ # If the user asks for anything other than retrieving data (SELECT), respond using this exact message:
126
+ # "I'm not allowed to perform operations other than SELECT queries. Please ask something that involves reading data."
127
+
128
+ # Do not return SQL queries or raw technical responses to the user.
129
+
130
+ # For example:
131
+ # Wrong: SELECT * FROM ...
132
+ # Correct: The user assigned to the cart is Alice Smith.
133
+
134
+ # Use the tools provided to get the correct data from the database and summarize the response clearly.
135
+ # If the input is unclear or lacks sufficient data, ask for clarification using the SubmitFinalAnswer tool.
136
+ # Never return SQL queries as your response.
137
+
138
+ # If you cannot find an answer,
139
+ # Double-check your query and running it again.
140
+ # - If a query fails, revise and try again.
141
+ # - Else 'No data found' using SubmitFinalAnswer.No SQL, no code. '''
142
+ agent_executor = create_sql_agent(
143
+ llm=llm,
144
+ toolkit=toolkit,
145
+ verbose=True,
146
+ prefix=prefix,
147
+ agent_type=AgentType.ZERO_SHOT_REACT_DESCRIPTION,
148
+ memory=memory,
149
+ agent_executor_kwargs={"handle_parsing_errors": True},
150
+ )
151
+ db_mode = "sql"
152
+
153
+ # --- INIT MONGO AGENT ---
154
+ system_message = """
155
+ You are **MongoDBQueryBot**, a highly intelligent and accurate assistant for answering questions about data stored in a MongoDB database using tools.
156
+
157
+ ### 🚨 Critical Instructions (Strictly Follow These):
158
+ - You **must always** use tools provided to answer user questions.
159
+ - Always join IDs with associated human-readable values like names or titles when answering.
160
+ - Prefer displaying `user name`, `employee name`, or `product name` instead of internal IDs like `user_id`, `emp_id`, or `product_id`.
161
+ - Avoid responding only with technical identifiers. Make responses meaningful to users.
162
+ - **Never** guess or fabricate any information.
163
+ - **Do not** show raw JSON, field names, or database structure.
164
+ - Your role is **read-only**: do not suggest or perform insert/update/delete.
165
+ - After Using All the available tools, if you are Unable to find any documents, then give followig ANSWER:
166
+ "Please, rephrase your query because I can't exactly understand, what you want !"
167
+ - If a query can't be answered or is unrelated to reading data, reply:
168
+ ❌ "I'm only allowed to retrieve data. Please ask a query involving reading information."
169
+ - IF USER ASK ABOUT DATA, Which is not there in a database, then GIVE FOLLOWING ANSWER:
170
+ "There is no such data in the Database."
171
+ - When returning answers:
172
+ - Do **not return internal IDs** like `user_id`, `order_id`, `payment_id`, etc.
173
+ - Instead, use human-readable fields like `name`, `full_name`, `user_name`, etc., from related collections.
174
+ - If only an ID is available, try joining the relevant collections to fetch the proper display name.
175
+
176
+
177
+ ### 🧠 How to Think:
178
+ - Understand **exactly** what the user is trying to ask. Do not answer if unclear — ask for clarification.
179
+ - Translate the user prompt into tool inputs by identifying:
180
+ - Which collection to search
181
+ - What value or field they're referring to
182
+ - The correct format expected by the tool
183
+
184
+ ### 🛠️ Tool Usage Guide:
185
+ - Use `FindDocuments` for queries like:
186
+ - "Show me all employees named John"
187
+ - "What is the salary of Manager X?"
188
+ - Use `ListCollections` to discover available data types (but don’t share them directly).
189
+ - **IMPORTANT : Don't Iterate only in one tool, if you can't able to answer using current tool you using, then swith the tool !**
190
+ - Use `JoinCollections` to resolve IDs into names when the question asks about people, customers, or products.
191
+ - When resolving names from payments, use this format:
192
+ `from=Payments, key=order_id, to=Orders, match=order_id, next_key=user_id, next_to=Users, next_match=user_id, return=name`
193
+
194
+ - Your goal is to **return the person's name** (e.g., `name`, `user_name`, `full_name`) not their ID.
195
+ - Always prioritize returning names instead of internal identifiers.
196
+ - Examples:
197
+ - For payment-related questions → Join Payments → Orders → Users and return name
198
+ - For order questions → Join Orders → Users and return user names
199
+
200
+ ### 🧾 Response Format:
201
+ - Use **clear markdown with tables** when displaying data.
202
+ - If no data is found: return `**No documents found.**`
203
+ - Stay professional, brief, and relevant.
204
+
205
+ ### 🚫 Never Do This:
206
+ - Do not leak MongoDB structure, schema, or field names.
207
+ - Do not suggest code, MongoDB syntax, or field mappings.
208
+ - Do not hallucinate or make assumptions.
209
+
210
+ Start by analyzing the prompt carefully, select the right tool, invoke it, and return a user-friendly answer based on the result.
211
+ """
212
+ def find_docs_tool_func(query: str) -> str:
213
+ """
214
+ Flexible MongoDB search with fallback:
215
+ - First tries in specified collection.
216
+ - If no results found, falls back to search across all collections.
217
+ Input format:
218
+ - collection=<collection>, key=<field>, value=<value>
219
+ - OR: collection=<collection>, value=<value>
220
+ """
221
+ try:
222
+ parts = dict(part.strip().split("=", 1) for part in query.split(",") if "=" in part)
223
+ collection = parts.get("collection")
224
+ key = parts.get("key")
225
+ value = parts.get("value")
226
+ if not collection:
227
+ return "❌ 'collection' is required."
228
+
229
+ def query_collection(coll_name):
230
+ if key and value:
231
+ return list(mongo_db[coll_name].find({key: value}, {'_id': 0}))
232
+ elif value:
233
+ return [doc for doc in mongo_db[coll_name].find({}, {'_id': 0}) if any(str(v).lower() == value.lower() for v in doc.values())]
234
+ else:
235
+ return list(mongo_db[coll_name].find({}, {'_id': 0}))
236
+
237
+ docs = query_collection(collection)
238
+ if docs:
239
+ return "\n markdown\n" + tabulate(docs, headers="keys", tablefmt="github") + "\n"
240
+
241
+ for coll in mongo_db.list_collection_names():
242
+ if coll == collection:
243
+ continue
244
+ docs = query_collection(coll)
245
+ if docs:
246
+ return "\n markdown\n" + tabulate(docs, headers="keys", tablefmt="github") + "\n"
247
+
248
+ return "**No documents found.**"
249
+ except Exception as e:
250
+ return f"Invalid input format or error: {str(e)}"
251
+
252
+ def aggregate_group_by(_input: str):
253
+ try:
254
+ if _input.strip().startswith("{"):
255
+ # Parse JSON-like string
256
+ args = ast.literal_eval(_input)
257
+ collection = args.get("collection_name") or args.get("collection")
258
+ field = args.get("group_by") or args.get("field")
259
+ else:
260
+ # Handle legacy input format
261
+ args = dict(x.split("=") for x in _input.split(","))
262
+ collection = args["collection"]
263
+ field = args["field"]
264
+
265
+ pipeline = [
266
+ {"$group": {"_id": f"${field}", "count": {"$sum": 1}}},
267
+ {"$project": {"_id": 0, field: "$_id", "count": 1}}
268
+ ]
269
+ result = list(mongo_db[collection].aggregate(pipeline))
270
+ if not result:
271
+ return "**No data found.**"
272
+ return "\n markdown\n" + tabulate(result, headers="keys", tablefmt="github") + "\n"
273
+ except Exception as e:
274
+ return f"Aggregation failed: {e}"
275
+
276
+ def get_all_documents(collection: str):
277
+ try:
278
+ docs = list(mongo_db[collection].find({}, {'_id': 0}))
279
+ if not docs:
280
+ return "**No documents found.**"
281
+ return "\n markdown\n" + tabulate(docs, headers="keys", tablefmt="github") + "\n"
282
+ except Exception as e:
283
+ return f"Error fetching documents: {e}"
284
+
285
+ def fuzzy_find_documents(query: str):
286
+ try:
287
+ parts = dict(part.strip().split("=", 1) for part in query.split(","))
288
+ collection = parts["collection"]
289
+ value = parts["value"]
290
+ threshold = int(parts.get("threshold", 80))
291
+
292
+ matches = []
293
+ for doc in mongo_db[collection].find({}, {'_id': 0}):
294
+ if any(fuzz.partial_ratio(str(v).lower(), value.lower()) >= threshold for v in doc.values()):
295
+ matches.append(doc)
296
+ if not matches:
297
+ return "**No fuzzy matches found.**"
298
+ return "\n markdown\n" + tabulate(matches, headers="keys", tablefmt="github") + "\n"
299
+ except Exception as e:
300
+ return f"Fuzzy match error: {e}"
301
+
302
+ # def join_collections_tool_func(_input: str):
303
+ # try:
304
+ # # Parse input like: from=Products, key=category_id, to=Categories, match=category_id, return=category_name
305
+ # args = dict(x.strip().split("=", 1) for x in _input.split(","))
306
+ # from_collection = args["from"]
307
+ # foreign_key = args["key"]
308
+ # to_collection = args["to"]
309
+ # match_key = args["match"]
310
+ # return_field = args["return"]
311
+
312
+ # results = []
313
+ # foreign_lookup = {
314
+ # doc[match_key]: doc.get(return_field)
315
+ # for doc in mongo_db[to_collection].find()
316
+ # if match_key in doc
317
+ # }
318
+
319
+ # for doc in mongo_db[from_collection].find({}, {'_id': 0}):
320
+ # doc[return_field] = foreign_lookup.get(doc.get(foreign_key), "Unknown")
321
+ # results.append(doc)
322
+
323
+ # if not results:
324
+ # return "**No documents found.**"
325
+
326
+ # return "\n markdown\n" + tabulate(results, headers="keys", tablefmt="github") + "\n"
327
+
328
+ # except Exception as e:
329
+ # return f"Join failed: {e}"
330
+ def join_collections_tool_func(_input: str):
331
+ """
332
+ Supports 2-level join (Payments → Orders → Users) or any pair-wise join
333
+ Input formats:
334
+ - from=Payments, key=order_id, to=Orders, match=order_id, next_key=user_id, next_to=Users, next_match=user_id, return=name
335
+ - from=Products, key=category_id, to=Categories, match=category_id, return=category_name
336
+ """
337
+ try:
338
+ args = dict(x.strip().split("=", 1) for x in _input.split(","))
339
+ from_coll = args["from"]
340
+ key = args["key"]
341
+ to_coll = args["to"]
342
+ match = args["match"]
343
+ return_field = args["return"]
344
+
345
+ next_key = args.get("next_key")
346
+ next_to = args.get("next_to")
347
+ next_match = args.get("next_match")
348
+
349
+ # First join (e.g., Payments → Orders)
350
+ to_docs = {doc[match]: doc for doc in mongo_db[to_coll].find() if match in doc}
351
+ joined = []
352
+ for doc in mongo_db[from_coll].find({}, {'_id': 0}):
353
+ foreign_doc = to_docs.get(doc.get(key))
354
+ if not foreign_doc:
355
+ continue
356
+ merged = {**doc, **foreign_doc}
357
+ joined.append(merged)
358
+
359
+ # Second join (e.g., Orders → Users)
360
+ if next_key and next_to and next_match:
361
+ next_docs = {doc[next_match]: doc for doc in mongo_db[next_to].find() if next_match in doc}
362
+ for doc in joined:
363
+ user_doc = next_docs.get(doc.get(next_key))
364
+ if user_doc:
365
+ doc[return_field] = user_doc.get(return_field, "Unknown")
366
+ else:
367
+ doc[return_field] = "Unknown"
368
+
369
+ # Prepare final result
370
+ if not joined:
371
+ return "**No documents found.**"
372
+ final = [{return_field: doc.get(return_field)} for doc in joined if return_field in doc]
373
+ return "\n markdown\n" + tabulate(final, headers="keys", tablefmt="github") + "\n"
374
+
375
+ except Exception as e:
376
+ return f"Join failed: {e}"
377
+
378
+ def smart_join_router(prompt: str) -> str:
379
+ """
380
+ An intelligent router that suggests the correct JoinCollections input string
381
+ for common user intent like payments → orders → users → name.
382
+ """
383
+ prompt_lower = prompt.lower()
384
+ if "payment" in prompt_lower and any(term in prompt_lower for term in ["who", "name", "user", "person"]):
385
+ return "from=Payments, key=order_id, to=Orders, match=order_id, next_key=user_id, next_to=Users, next_match=user_id, return=name"
386
+ elif "order" in prompt_lower and "name" in prompt_lower:
387
+ return "from=Orders, key=user_id, to=Users, match=user_id, return=name"
388
+ # Extend as needed
389
+ return "Unable to auto-generate join path. Please provide more context."
390
+
391
+ def init_mongo_agent(json_path):
392
+ global agent_executor, client, mongo_db, db_mode
393
+
394
+ client = MongoClient("mongodb://localhost:27017/")
395
+ mongo_db = client['uploaded_mongo']
396
+ with open(json_path, 'r', encoding='utf-8') as f:
397
+ data = json_util.loads(f.read())
398
+
399
+ # Handle both single-collection and multi-collection formats
400
+ if isinstance(data, list):
401
+ # Default collection name if only a list is provided
402
+ collection = mongo_db['default_collection']
403
+ collection.drop()
404
+ collection.insert_many(data)
405
+ elif isinstance(data, dict):
406
+ for col_name, docs in data.items():
407
+ collection = mongo_db[col_name]
408
+ collection.drop()
409
+ if isinstance(docs, list):
410
+ collection.insert_many(docs)
411
+ else:
412
+ collection.insert_one(docs)
413
+ else:
414
+ raise ValueError("Unsupported JSON format. Must be a list or dict.")
415
+
416
+ def list_collections(_input=None):
417
+ return mongo_db.list_collection_names()
418
+
419
+ find_docs_tool = Tool(
420
+ name="FindDocuments",
421
+ description=(
422
+ "Use this tool to find documents in a MongoDB collection.\n"
423
+ "Input format:\n"
424
+ "- `collection=<collection>, key=<field>, value=<value>` for precise queries\n"
425
+ "- OR `collection=<collection>, value=<value>` to search across all fields\n"
426
+ "If `key` is omitted, the tool will automatically scan all fields to find matching values.\n"
427
+ "Examples:\n"
428
+ "- `collection=default_collection, key=name, value=Lauren Alexander`\n"
429
+ "- `collection=default_collection, value=Lauren Alexander`"
430
+ ),
431
+ func=find_docs_tool_func)
432
+
433
+ aggregate_tool = Tool(
434
+ name="AggregateGroupBy",
435
+ func=aggregate_group_by,
436
+ description=(
437
+ "Group documents and count by any field. Format: collection=<name>, field=<group_by_field>. E.g., collection=residents, field=gender"
438
+ )
439
+ )
440
+ get_all_documents_tool = Tool(
441
+ name="GetAllDocuments",
442
+ func=get_all_documents,
443
+ description=(
444
+ "Fetch all documents from a collection. Input: collection name only. Example: residents"
445
+ )
446
+ )
447
+
448
+ fuzzy_tool = Tool(
449
+ name="FuzzyFindDocuments",
450
+ func=fuzzy_find_documents,
451
+ description=("Fuzzy match documents across all fields in a collection. Format: collection=<name>, value=<search_term>, threshold=80 (optional)"
452
+ )
453
+ )
454
+
455
+ join_collection_tool = Tool(
456
+ name="JoinCollections",
457
+ func=join_collections_tool_func,
458
+ description=(
459
+ "Join collections to map foreign keys to human-readable values. Supports 1 or 2-level joins.\n"
460
+ "Formats:\n"
461
+ "- from=Payments, key=order_id, to=Orders, match=order_id, return=status\n"
462
+ "- from=Payments, key=order_id, to=Orders, match=order_id, next_key=user_id, next_to=Users, next_match=user_id, return=name"
463
+ )
464
+ )
465
+ smart_router_tool = Tool(
466
+ name="SmartJoinRouter",
467
+ func=smart_join_router,
468
+ description=(
469
+ "Suggest the correct JoinCollections input format based on user intent.\n"
470
+ "Use this when you are unsure how to form the join input."
471
+ )
472
+ )
473
+
474
+ tools = [
475
+ Tool(name="FindDocuments", func=find_docs_tool, description="Flexible MongoDB search..."),
476
+ Tool(name="ListCollections", func=lambda x: list_collections(), description="List all collections..."),
477
+ Tool(name="AggregateGroupBy", func=aggregate_tool, description="Group and count by any field..."),
478
+ Tool(name="GetAllDocuments", func=get_all_documents_tool, description="Fetch all documents from a collection..."),
479
+ Tool(name="FuzzyFindDocuments", func=fuzzy_tool, description="Fuzzy match documents across all fields..."),
480
+ Tool(name="JoinCollections", func=join_collection_tool, description="Join related collections to return names instead of IDs..."),
481
+ Tool(name="SmartJoinCollections", func=smart_router_tool, description="Smrt Join related collections to return names instead of IDs...")
482
+ ]
483
+
484
+ agent_executor = initialize_agent(
485
+ tools=tools,
486
+ llm=llm,
487
+ agent_type=AgentType.CONVERSATIONAL_REACT_DESCRIPTION,
488
+ memory=memory,
489
+ verbose=True,
490
+ prefix=system_message,
491
+ handle_parsing_errors=True
492
+ )
493
+ db_mode = "mongo"
494
+
495
+ @app.errorhandler(Exception)
496
+ def handle_all_errors(e):
497
+ print(f"[ERROR] Global handler caught an exception: {str(e)}")
498
+ traceback.print_exc()
499
+
500
+ if isinstance(e, HTTPException):
501
+ return jsonify({"status": "error", "message": e.description}), e.code
502
+
503
+ return jsonify({"status": "error", "message": "An unexpected error occurred"}), 500
504
+
505
+ from werkzeug.exceptions import TooManyRequests
506
+
507
+ @app.errorhandler(TooManyRequests)
508
+ def handle_429_error(e):
509
+ return jsonify({
510
+ "status": "error",
511
+ "message": "🚦 Agent is busy, try again after sometime."
512
+ }), 429
513
+
514
+ # --- ROUTES ---
515
+ @app.route("/")
516
+ def index():
517
+ return render_template("index_db_json.html")
518
+
519
+ @app.route("/upload_db", methods=["POST"])
520
+ @error_safe
521
+ def upload_db():
522
+ file = request.files.get("file")
523
+ if not file or file.filename == "":
524
+ return jsonify(success=False, message="No file provided"), 400
525
+
526
+ filename = secure_filename(file.filename)
527
+ path = os.path.join(app.config["UPLOAD_FOLDER"], filename)
528
+ file.save(path)
529
+
530
+ try:
531
+ if filename.endswith(".json"):
532
+ init_mongo_agent(path)
533
+ return jsonify(success=True, message="MongoDB initialized")
534
+ elif filename.endswith(".db"):
535
+ init_sql_agent(path)
536
+ return jsonify(success=True, message="SQL DB initialized")
537
+ else:
538
+ return jsonify(success=False, message="Unsupported file format"), 400
539
+ except Exception as e:
540
+ traceback.print_exc()
541
+ return jsonify(success=False, message=f"Init failed: {e}"), 500
542
+
543
+ @app.route("/generate", methods=["POST"])
544
+ @error_safe
545
+ def generate():
546
+ try:
547
+ data = request.get_json(force=True)
548
+ prompt = data.get("prompt", "").strip()
549
+ if not prompt:
550
+ return jsonify({"status": "error", "message": "Prompt is required"}), 400
551
+
552
+ # if is_schema_leak_request(prompt) or is_schema_request(prompt):
553
+ # msg = "⛔ Sorry, you're not allowed to access structure/schema information."
554
+ # socketio.emit("final", {"message": msg})
555
+ # return jsonify({"status": "blocked", "message": msg}), 403
556
+
557
+ # # NEW BLOCK: Sensitive intent detection
558
+ # if is_sensitive_request(prompt):
559
+ # msg = "⛔ This query may involve sensitive or protected information. Please rephrase your question."
560
+ # socketio.emit("final", {"message": msg})
561
+ # return jsonify({"status": "blocked", "message": msg}), 403
562
+
563
+ except Exception as e:
564
+ traceback.print_exc()
565
+ return jsonify({"status": "error", "message": "Invalid input"}), 400
566
+
567
+ def run_agent():
568
+ try:
569
+ result = agent_executor.invoke({"input": prompt})
570
+ final_answer = result.get("output", "")
571
+ # Check for vague or failure phrases in the agent's answer
572
+ vague_phrases = [
573
+ "i am unable to answer this",
574
+ "i cannot answer",
575
+ "i'm unable to answer",
576
+ "i don’t know",
577
+ "i do not know",
578
+ "i am not sure",
579
+ "unable to proceed",
580
+ "i cannot determine",
581
+ "data is missing",
582
+ "i was not able to"
583
+ ]
584
+
585
+ # if any(phrase in final_answer.lower() for phrase in vague_phrases) or final_answer.strip() == "":
586
+ # final_answer = "Please, rephrase your query because I can't exactly understand, what you want !"
587
+ # if any(phrase in final_answer.lower() for phrase in ["i am unable", "i'm unable", "i cannot", "i can't", "unable to answer"]):
588
+ # final_answer = "Please, rephrase your query because I can't exactly understand, what you want !"
589
+ socketio.emit("final", {"message": final_answer})
590
+
591
+ except Exception as e:
592
+ error_message = str(e)
593
+ if "429" in error_message and "quota" in error_message.lower():
594
+ user_friendly_msg = "🚦 Agent is busy, try again after sometime."
595
+ else:
596
+ user_friendly_msg = f"Agent failed: {error_message}"
597
+ socketio.emit("final", {"message": user_friendly_msg})
598
+ traceback.print_exc()
599
+
600
+ threading.Thread(target=run_agent).start()
601
+ return jsonify({"status": "ok"}), 200
602
+
603
+ if __name__ == "__main__":
604
+ socketio.run(app, debug=True)
static/db_icon.svg ADDED
static/robot.png ADDED
templates/app_index.html ADDED
@@ -0,0 +1,550 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="UTF-8" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
7
+ <title>MongoDB Agent Chat</title>
8
+ <link rel="icon" type="image/png" href="/static/robot.png" />
9
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.5.4/socket.io.js"></script>
10
+ <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
11
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css" />
12
+
13
+ <style>
14
+ * {
15
+ box-sizing: border-box;
16
+ }
17
+
18
+ body {
19
+ background: linear-gradient(135deg, rgb(44, 65, 112), #151f35);
20
+ color: #f0f0f0;
21
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
22
+ margin: 0;
23
+ padding: 0;
24
+ overflow: hidden;
25
+ height: 100vh;
26
+ }
27
+
28
+ .container {
29
+ max-width: 910px;
30
+ margin: 22px auto;
31
+ padding: 20px;
32
+ border-radius: 20px;
33
+ backdrop-filter: blur(12px);
34
+ background: rgba(255, 255, 255, 0.04);
35
+ box-shadow: 0 8px 20px rgba(0, 0, 0, 0.2);
36
+ transition: all 0.3s ease-in-out;
37
+ width: 90%;
38
+ display: flex;
39
+ flex-direction: column;
40
+ height: fit-content
41
+ }
42
+
43
+ h1 {
44
+ text-align: center;
45
+ font-size: 1.5rem;
46
+ margin-bottom: 30px;
47
+ color: #7ee8fa;
48
+ letter-spacing: 1px;
49
+ text-shadow: 0 2px 4px #000;
50
+ }
51
+
52
+ /* added small DB area styles */
53
+ .db-area {
54
+ display: flex;
55
+ gap: 10px;
56
+ align-items: flex-start;
57
+ margin-bottom: 14px;
58
+ }
59
+
60
+ .db-area textarea {
61
+ flex: 1;
62
+ padding: 10px;
63
+ border-radius: 8px;
64
+ background: rgba(255, 255, 255, 0.04);
65
+ color: #eaeaea;
66
+ border: 1px solid rgba(255, 255, 255, 0.06);
67
+ resize: none;
68
+ min-height: 46px;
69
+ }
70
+
71
+ .db-area button {
72
+ padding: 10px 14px;
73
+ border-radius: 8px;
74
+ border: none;
75
+ background: #28a745;
76
+ color: white;
77
+ cursor: pointer;
78
+ }
79
+
80
+ .chat-container {
81
+ height: 450px;
82
+ overflow-y: auto;
83
+ padding: 20px;
84
+ background: rgba(255, 255, 255, 0.05);
85
+ border-radius: 15px;
86
+ border: 1px solid rgba(255, 255, 255, 0.05);
87
+ box-shadow: inset 0 0 10px rgba(0, 0, 0, 0.25);
88
+ backdrop-filter: blur(8px);
89
+ animation: fadeInUp 0.6s ease;
90
+ }
91
+
92
+ .chat-container {
93
+ height: 450px;
94
+ overflow-y: auto;
95
+ padding: 20px;
96
+ background: rgba(255, 255, 255, 0.05);
97
+ border-radius: 15px;
98
+ border: 1px solid rgba(255, 255, 255, 0.05);
99
+ box-shadow: inset 0 0 10px rgba(0, 0, 0, 0.25);
100
+ backdrop-filter: blur(8px);
101
+ animation: fadeInUp 0.6s ease;
102
+ }
103
+
104
+ @keyframes fadeInUp {
105
+ from {
106
+ opacity: 0;
107
+ transform: translateY(15px);
108
+ }
109
+
110
+ to {
111
+ opacity: 1;
112
+ transform: translateY(0);
113
+ }
114
+ }
115
+
116
+ /* Custom scroll bar styling */
117
+ .chat-container::-webkit-scrollbar {
118
+ width: 8px;
119
+ }
120
+
121
+ .chat-container::-webkit-scrollbar-track {
122
+ background: #0f1b33;
123
+ border-radius: 4px;
124
+ }
125
+
126
+ .chat-container::-webkit-scrollbar-thumb {
127
+ background: #444;
128
+ border-radius: 4px;
129
+ }
130
+
131
+ .chat-container::-webkit-scrollbar-thumb:hover {
132
+ background: #555;
133
+ }
134
+
135
+ .chat-container {
136
+ scrollbar-width: thin;
137
+ scrollbar-color: #444 #0f1b33;
138
+ }
139
+
140
+ .message {
141
+ margin-bottom: 15px;
142
+ display: flex;
143
+ align-items: flex-start;
144
+ opacity: 0;
145
+ animation: fadeIn 0.5s ease forwards;
146
+ }
147
+
148
+ .message.user {
149
+ justify-content: flex-end;
150
+ }
151
+
152
+ .message.agent {
153
+ justify-content: flex-start;
154
+ }
155
+
156
+ .bubble {
157
+ padding: 8px 14px;
158
+ font-size: 14px;
159
+ line-height: 1.2;
160
+ border-radius: 14px;
161
+ max-width: 70%;
162
+ word-wrap: break-word;
163
+ position: relative;
164
+ line-height: 1.5;
165
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
166
+ transition: all 0.3s ease;
167
+ }
168
+
169
+ .bubble.user {
170
+ background: linear-gradient(145deg, #007bff, #0056b3);
171
+ color: white;
172
+ border-bottom-right-radius: 0;
173
+ }
174
+
175
+ .bubble.agent {
176
+ background: linear-gradient(145deg, #2d3a55, #1b2a3a);
177
+ color: #f3f3f3;
178
+ border-bottom-left-radius: 0;
179
+ }
180
+
181
+ .bubble.agent.thought-bubble {
182
+ background: rgba(16, 52, 74, 0.8);
183
+ font-style: italic;
184
+ border-left: 4px solid #00e0ff;
185
+ color: #7ee8fa;
186
+ }
187
+
188
+ .thought-toggle {
189
+ color: #ff8800;
190
+ font-size: 0.85rem;
191
+ cursor: pointer;
192
+ display: inline-block;
193
+ margin-top: 5px;
194
+ }
195
+
196
+ .input-area {
197
+ display: flex;
198
+ gap: 10px;
199
+ margin-top: 15px;
200
+ align-items: center;
201
+ }
202
+
203
+ .input-area textarea {
204
+ flex: 1;
205
+ padding: 12px;
206
+ border-radius: 10px;
207
+ background: rgba(255, 255, 255, 0.08);
208
+ color: #eaeaea;
209
+ border: 1px solid rgba(255, 255, 255, 0.1);
210
+ resize: none;
211
+ transition: box-shadow 0.3s ease;
212
+ }
213
+
214
+ .input-area textarea:focus {
215
+ outline: none;
216
+ box-shadow: 0 0 8px #00e0ff;
217
+ }
218
+
219
+ .input-area button {
220
+ padding: 12px 20px;
221
+ border-radius: 10px;
222
+ border: none;
223
+ background: #00a8ff;
224
+ color: #fff;
225
+ cursor: pointer;
226
+ transition: background 0.3s;
227
+ }
228
+
229
+ #upload-db {
230
+ background: none;
231
+ border: none;
232
+ cursor: pointer;
233
+ /* margin-left: 5px; */
234
+ }
235
+
236
+ .btn-icon {
237
+ background: transparent;
238
+ border: none;
239
+ cursor: pointer;
240
+ padding: 6px;
241
+ transition: transform 0.2s ease;
242
+ }
243
+
244
+ #upload-db img {
245
+ width: 28px;
246
+ height: 28px;
247
+ }
248
+
249
+ .input-area button:disabled {
250
+ background-color: #555;
251
+ cursor: not-allowed;
252
+ }
253
+
254
+ .input-area button:hover:not(:disabled) {
255
+ background: #0077c2;
256
+ }
257
+
258
+ .toast {
259
+ position: fixed;
260
+ bottom: 25px;
261
+ right: 25px;
262
+ background: #2c3e50;
263
+ padding: 12px 22px;
264
+ border-radius: 12px;
265
+ font-size: 0.95rem;
266
+ box-shadow: 0 6px 12px rgba(0, 0, 0, 0.3);
267
+ opacity: 0.95;
268
+ display: none;
269
+ z-index: 1000;
270
+ transition: all 0.3s ease-in-out;
271
+ }
272
+
273
+ /* Typing animation dots */
274
+ .dot {
275
+ animation: blink 1.2s infinite;
276
+ display: inline-block;
277
+ margin-left: 2px;
278
+ }
279
+
280
+ .dot:nth-child(2) {
281
+ animation-delay: 0.2s;
282
+ }
283
+
284
+ .dot:nth-child(3) {
285
+ animation-delay: 0.4s;
286
+ }
287
+
288
+ @keyframes blink {
289
+ 0% {
290
+ opacity: 0.2;
291
+ }
292
+
293
+ 20% {
294
+ opacity: 1;
295
+ }
296
+
297
+ 100% {
298
+ opacity: 0.2;
299
+ }
300
+ }
301
+
302
+ @keyframes fadeIn {
303
+ to {
304
+ opacity: 1;
305
+ }
306
+ }
307
+
308
+ .thought-toggle {
309
+ margin-top: 4px;
310
+ font-size: 0.8rem;
311
+ color: #ffbb00;
312
+ cursor: pointer;
313
+ transition: color 0.3s;
314
+ }
315
+
316
+ .thought-toggle:hover {
317
+ color: #fff000;
318
+ }
319
+
320
+ .copy-container {
321
+ margin-top: 5px;
322
+ display: flex;
323
+ justify-content: flex-end;
324
+ }
325
+
326
+ .copy-btn {
327
+ background: none;
328
+ border: none;
329
+ color: #bbb;
330
+ cursor: pointer;
331
+ font-size: 0.85rem;
332
+ padding: 4px 8px;
333
+ transition: color 0.2s ease, background 0.2s ease;
334
+ border-radius: 6px;
335
+ }
336
+
337
+ .copy-btn:hover {
338
+ color: #fff;
339
+ background: rgba(255, 255, 255, 0.1);
340
+ }
341
+ </style>
342
+ </head>
343
+
344
+ <body>
345
+ <div class="container">
346
+ <h1><i class="fa-solid fa-robot" style="margin-right: 10px; color: #f5f5f5;"></i>: Ask Me Anything About Your Data
347
+ </h1>
348
+ <!-- NEW: DB connection textarea and connect button -->
349
+ <!-- <div class="db-area">
350
+ <textarea id="db-uri" rows="2"
351
+ placeholder="Enter DB connection string (SQLAlchemy URI, mssql ODBC string, or MongoDB URI)."></textarea>
352
+ <button id="connect-db" title="Connect Database"><i class="fa-solid fa-plug"
353
+ style="margin-right:8px"></i>Connect</button>
354
+ </div> -->
355
+ <div class="chat-container" id="chat"></div>
356
+ <div class="input-area">
357
+ <textarea id="prompt" rows="2" placeholder="Ask something about your data..."></textarea>
358
+ <!-- <button id="upload-db" title="Upload Mongo JSON Database"><img src="/static/db_icon.svg" alt="DB" /></button> -->
359
+ <button id="upload-db" title="Upload Database"><i class="fa-solid fa-database"
360
+ style="color: #b5d3f1; font-size: 25px; background: transparent;"></i></button>
361
+ <button id="send">Send</button>
362
+ <input type="file" id="file-input" accept=".json,.db" style="display:none;" />
363
+ </div>
364
+ </div>
365
+ <div id="toast" class="toast"></div>
366
+
367
+ <script>
368
+ const socket = io()
369
+ const chat = document.getElementById('chat')
370
+ const toast = document.getElementById('toast')
371
+ const sendBtn = document.getElementById('send')
372
+ const promptTA = document.getElementById('prompt')
373
+ const uploadBtn = document.getElementById('upload-db')
374
+ const fileInput = document.getElementById('file-input')
375
+ // const dbUriTA = document.getElementById('db-uri')
376
+ // const connectBtn = document.getElementById('connect-db')
377
+
378
+ let typingBubble = null
379
+ let agentBubble = null
380
+
381
+ function addMessage(sender, html) {
382
+ const msg = document.createElement('div')
383
+ msg.className = `message ${sender}`
384
+ const bubble = document.createElement('div')
385
+ bubble.className = `bubble ${sender}`
386
+ bubble.innerHTML = marked.parse(html)
387
+
388
+ msg.appendChild(bubble)
389
+ // Add copy button only for user messages
390
+ if (sender === 'user') {
391
+ const copyBtn = document.createElement('button');
392
+ copyBtn.className = 'btn-icon copy-btn';
393
+ copyBtn.innerHTML = '<i class="fa-regular fa-copy"></i>';
394
+ copyBtn.title = 'Copy to clipboard';
395
+ copyBtn.onclick = () => {
396
+ navigator.clipboard.writeText(html);
397
+ showToast('Copied to clipboard!');
398
+ };
399
+ const copyContainer = document.createElement('div');
400
+ copyContainer.className = 'copy-container';
401
+ copyContainer.appendChild(copyBtn);
402
+ msg.appendChild(copyContainer);
403
+
404
+ }
405
+ chat.appendChild(msg)
406
+ chat.scrollTop = chat.scrollHeight
407
+ }
408
+
409
+ function showToast(text, duration = 3000) {
410
+ toast.textContent = text
411
+ toast.style.display = 'block'
412
+ setTimeout(() => (toast.style.display = 'none'), duration)
413
+ }
414
+
415
+ uploadBtn.addEventListener('click', () => fileInput.click())
416
+ fileInput.addEventListener('change', e => handleFile(e.target.files[0]))
417
+
418
+ // function handleFile(file) {
419
+ // if (!file) return
420
+ // showToast('Uploading Database…')
421
+ // const fd = new FormData()
422
+ // fd.append('file', file)
423
+ // fetch('/upload_db', { method: 'POST', body: fd })
424
+ // .then(r => r.json())
425
+ // .then(data => {
426
+ // if (data.success) {
427
+ // showToast('Database initialized!', 2000)
428
+ // const fileName = file.name;
429
+ // addMessage('agent', ${filename},'uploaded and ready!')
430
+ // } else {
431
+ // addMessage('agent', `Error: ${data.message}`)
432
+ // }
433
+ // })
434
+ // .catch(err => addMessage('agent', `Upload error: ${err}`))
435
+ // }
436
+ function handleFile(file) {
437
+ if (!file) return;
438
+ showToast('Uploading Database…');
439
+
440
+ const fd = new FormData();
441
+ fd.append('file', file);
442
+
443
+ fetch('/upload_db', { method: 'POST', body: fd })
444
+ .then(r => r.json())
445
+ .then(data => {
446
+ if (data.success) {
447
+ showToast('Database initialized!', 2000);
448
+ const fileName = file.name; // Get the file name from the uploaded file
449
+ addMessage('agent', `${fileName} is uploaded and ready!`);
450
+ } else {
451
+ addMessage('agent', `Error: ${data.message}`);
452
+ }
453
+ })
454
+ .catch(err => addMessage('agent', `Upload error: ${err}`));
455
+ }
456
+ function sendPrompt() {
457
+ const text = promptTA.value.trim()
458
+ if (!text || sendBtn.disabled) return
459
+ addMessage('user', text)
460
+ promptTA.value = ''
461
+ sendBtn.disabled = true
462
+
463
+ typingBubble = document.createElement('div')
464
+ typingBubble.className = 'message agent'
465
+ typingBubble.innerHTML = `<div class="bubble agent">Thinking<span class="dot">.</span><span class="dot">.</span><span class="dot">.</span></div>`
466
+ chat.appendChild(typingBubble)
467
+ chat.scrollTop = chat.scrollHeight
468
+
469
+ fetch('/generate', {
470
+ method: 'POST',
471
+ headers: { 'Content-Type': 'application/json' },
472
+ body: JSON.stringify({ prompt: text })
473
+ }).catch((err) => {
474
+ addMessage('agent', `Error: ${err}`)
475
+ sendBtn.disabled = false
476
+ if (typingBubble) chat.removeChild(typingBubble)
477
+ })
478
+ }
479
+
480
+ sendBtn.addEventListener('click', sendPrompt)
481
+
482
+ promptTA.addEventListener('keydown', (e) => {
483
+ if (e.key === 'Enter' && !e.shiftKey) {
484
+ e.preventDefault()
485
+ sendPrompt()
486
+ }
487
+ })
488
+
489
+ socket.on('final', (data) => {
490
+ if (typingBubble) {
491
+ chat.removeChild(typingBubble)
492
+ typingBubble = null
493
+ }
494
+ agentBubble = null
495
+ sendBtn.disabled = true
496
+
497
+ const msg = document.createElement('div')
498
+ msg.className = 'message agent'
499
+ const bubble = document.createElement('div')
500
+ bubble.className = 'bubble agent'
501
+ msg.appendChild(bubble)
502
+ chat.appendChild(msg)
503
+ chat.scrollTop = chat.scrollHeight
504
+
505
+ const text = data.message
506
+ let index = 0
507
+ const typingInterval = setInterval(() => {
508
+ if (index < text.length) {
509
+ bubble.textContent += text.charAt(index)
510
+ index++
511
+ chat.scrollTop = chat.scrollHeight
512
+ } else {
513
+ clearInterval(typingInterval)
514
+ sendBtn.disabled = false
515
+ }
516
+ }, 50)
517
+ })
518
+ // // NEW: Connect DB button handler
519
+ // connectBtn.addEventListener('click', () => {
520
+ // const uri = dbUriTA.value.trim();
521
+ // if (!uri) {
522
+ // showToast('Please enter a connection string.');
523
+ // return;
524
+ // }
525
+ // connectBtn.disabled = true;
526
+ // showToast('Sending connection string...')
527
+ // fetch('/connect_db', {
528
+ // method: 'POST',
529
+ // headers: { 'Content-Type': 'application/json' },
530
+ // body: JSON.stringify({ uri })
531
+ // })
532
+ // .then(r => r.json())
533
+ // .then(data => {
534
+ // connectBtn.disabled = false;
535
+ // if (data && data.message) {
536
+ // addMessage('agent', data.message);
537
+ // showToast('Connect request sent.');
538
+ // } else {
539
+ // addMessage('agent', 'No response from server.');
540
+ // }
541
+ // })
542
+ // .catch(err => {
543
+ // connectBtn.disabled = false;
544
+ // addMessage('agent', `Connect error: ${err}`);
545
+ // })
546
+ // })
547
+ </script>
548
+ </body>
549
+
550
+ </html>
templates/index_db_json.html ADDED
@@ -0,0 +1,550 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="UTF-8" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
7
+ <title>MongoDB Agent Chat</title>
8
+ <link rel="icon" type="image/png" href="/static/robot.png" />
9
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.5.4/socket.io.js"></script>
10
+ <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
11
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css" />
12
+
13
+ <style>
14
+ * {
15
+ box-sizing: border-box;
16
+ }
17
+
18
+ body {
19
+ background: linear-gradient(135deg, rgb(44, 65, 112), #151f35);
20
+ color: #f0f0f0;
21
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
22
+ margin: 0;
23
+ padding: 0;
24
+ overflow: hidden;
25
+ height: 100vh;
26
+ }
27
+
28
+ .container {
29
+ max-width: 910px;
30
+ margin: 22px auto;
31
+ padding: 20px;
32
+ border-radius: 20px;
33
+ backdrop-filter: blur(12px);
34
+ background: rgba(255, 255, 255, 0.04);
35
+ box-shadow: 0 8px 20px rgba(0, 0, 0, 0.2);
36
+ transition: all 0.3s ease-in-out;
37
+ width: 90%;
38
+ display: flex;
39
+ flex-direction: column;
40
+ height: fit-content
41
+ }
42
+
43
+ h1 {
44
+ text-align: center;
45
+ font-size: 1.5rem;
46
+ margin-bottom: 30px;
47
+ color: #7ee8fa;
48
+ letter-spacing: 1px;
49
+ text-shadow: 0 2px 4px #000;
50
+ }
51
+
52
+ /* added small DB area styles */
53
+ .db-area {
54
+ display: flex;
55
+ gap: 10px;
56
+ align-items: flex-start;
57
+ margin-bottom: 14px;
58
+ }
59
+
60
+ .db-area textarea {
61
+ flex: 1;
62
+ padding: 10px;
63
+ border-radius: 8px;
64
+ background: rgba(255, 255, 255, 0.04);
65
+ color: #eaeaea;
66
+ border: 1px solid rgba(255, 255, 255, 0.06);
67
+ resize: none;
68
+ min-height: 46px;
69
+ }
70
+
71
+ .db-area button {
72
+ padding: 10px 14px;
73
+ border-radius: 8px;
74
+ border: none;
75
+ background: #28a745;
76
+ color: white;
77
+ cursor: pointer;
78
+ }
79
+
80
+ .chat-container {
81
+ height: 450px;
82
+ overflow-y: auto;
83
+ padding: 20px;
84
+ background: rgba(255, 255, 255, 0.05);
85
+ border-radius: 15px;
86
+ border: 1px solid rgba(255, 255, 255, 0.05);
87
+ box-shadow: inset 0 0 10px rgba(0, 0, 0, 0.25);
88
+ backdrop-filter: blur(8px);
89
+ animation: fadeInUp 0.6s ease;
90
+ }
91
+
92
+ .chat-container {
93
+ height: 450px;
94
+ overflow-y: auto;
95
+ padding: 20px;
96
+ background: rgba(255, 255, 255, 0.05);
97
+ border-radius: 15px;
98
+ border: 1px solid rgba(255, 255, 255, 0.05);
99
+ box-shadow: inset 0 0 10px rgba(0, 0, 0, 0.25);
100
+ backdrop-filter: blur(8px);
101
+ animation: fadeInUp 0.6s ease;
102
+ }
103
+
104
+ @keyframes fadeInUp {
105
+ from {
106
+ opacity: 0;
107
+ transform: translateY(15px);
108
+ }
109
+
110
+ to {
111
+ opacity: 1;
112
+ transform: translateY(0);
113
+ }
114
+ }
115
+
116
+ /* Custom scroll bar styling */
117
+ .chat-container::-webkit-scrollbar {
118
+ width: 8px;
119
+ }
120
+
121
+ .chat-container::-webkit-scrollbar-track {
122
+ background: #0f1b33;
123
+ border-radius: 4px;
124
+ }
125
+
126
+ .chat-container::-webkit-scrollbar-thumb {
127
+ background: #444;
128
+ border-radius: 4px;
129
+ }
130
+
131
+ .chat-container::-webkit-scrollbar-thumb:hover {
132
+ background: #555;
133
+ }
134
+
135
+ .chat-container {
136
+ scrollbar-width: thin;
137
+ scrollbar-color: #444 #0f1b33;
138
+ }
139
+
140
+ .message {
141
+ margin-bottom: 15px;
142
+ display: flex;
143
+ align-items: flex-start;
144
+ opacity: 0;
145
+ animation: fadeIn 0.5s ease forwards;
146
+ }
147
+
148
+ .message.user {
149
+ justify-content: flex-end;
150
+ }
151
+
152
+ .message.agent {
153
+ justify-content: flex-start;
154
+ }
155
+
156
+ .bubble {
157
+ padding: 8px 14px;
158
+ font-size: 14px;
159
+ line-height: 1.2;
160
+ border-radius: 14px;
161
+ max-width: 70%;
162
+ word-wrap: break-word;
163
+ position: relative;
164
+ line-height: 1.5;
165
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
166
+ transition: all 0.3s ease;
167
+ }
168
+
169
+ .bubble.user {
170
+ background: linear-gradient(145deg, #007bff, #0056b3);
171
+ color: white;
172
+ border-bottom-right-radius: 0;
173
+ }
174
+
175
+ .bubble.agent {
176
+ background: linear-gradient(145deg, #2d3a55, #1b2a3a);
177
+ color: #f3f3f3;
178
+ border-bottom-left-radius: 0;
179
+ }
180
+
181
+ .bubble.agent.thought-bubble {
182
+ background: rgba(16, 52, 74, 0.8);
183
+ font-style: italic;
184
+ border-left: 4px solid #00e0ff;
185
+ color: #7ee8fa;
186
+ }
187
+
188
+ .thought-toggle {
189
+ color: #ff8800;
190
+ font-size: 0.85rem;
191
+ cursor: pointer;
192
+ display: inline-block;
193
+ margin-top: 5px;
194
+ }
195
+
196
+ .input-area {
197
+ display: flex;
198
+ gap: 10px;
199
+ margin-top: 15px;
200
+ align-items: center;
201
+ }
202
+
203
+ .input-area textarea {
204
+ flex: 1;
205
+ padding: 12px;
206
+ border-radius: 10px;
207
+ background: rgba(255, 255, 255, 0.08);
208
+ color: #eaeaea;
209
+ border: 1px solid rgba(255, 255, 255, 0.1);
210
+ resize: none;
211
+ transition: box-shadow 0.3s ease;
212
+ }
213
+
214
+ .input-area textarea:focus {
215
+ outline: none;
216
+ box-shadow: 0 0 8px #00e0ff;
217
+ }
218
+
219
+ .input-area button {
220
+ padding: 12px 20px;
221
+ border-radius: 10px;
222
+ border: none;
223
+ background: #00a8ff;
224
+ color: #fff;
225
+ cursor: pointer;
226
+ transition: background 0.3s;
227
+ }
228
+
229
+ #upload-db {
230
+ background: none;
231
+ border: none;
232
+ cursor: pointer;
233
+ /* margin-left: 5px; */
234
+ }
235
+
236
+ .btn-icon {
237
+ background: transparent;
238
+ border: none;
239
+ cursor: pointer;
240
+ padding: 6px;
241
+ transition: transform 0.2s ease;
242
+ }
243
+
244
+ #upload-db img {
245
+ width: 28px;
246
+ height: 28px;
247
+ }
248
+
249
+ .input-area button:disabled {
250
+ background-color: #555;
251
+ cursor: not-allowed;
252
+ }
253
+
254
+ .input-area button:hover:not(:disabled) {
255
+ background: #0077c2;
256
+ }
257
+
258
+ .toast {
259
+ position: fixed;
260
+ bottom: 25px;
261
+ right: 25px;
262
+ background: #2c3e50;
263
+ padding: 12px 22px;
264
+ border-radius: 12px;
265
+ font-size: 0.95rem;
266
+ box-shadow: 0 6px 12px rgba(0, 0, 0, 0.3);
267
+ opacity: 0.95;
268
+ display: none;
269
+ z-index: 1000;
270
+ transition: all 0.3s ease-in-out;
271
+ }
272
+
273
+ /* Typing animation dots */
274
+ .dot {
275
+ animation: blink 1.2s infinite;
276
+ display: inline-block;
277
+ margin-left: 2px;
278
+ }
279
+
280
+ .dot:nth-child(2) {
281
+ animation-delay: 0.2s;
282
+ }
283
+
284
+ .dot:nth-child(3) {
285
+ animation-delay: 0.4s;
286
+ }
287
+
288
+ @keyframes blink {
289
+ 0% {
290
+ opacity: 0.2;
291
+ }
292
+
293
+ 20% {
294
+ opacity: 1;
295
+ }
296
+
297
+ 100% {
298
+ opacity: 0.2;
299
+ }
300
+ }
301
+
302
+ @keyframes fadeIn {
303
+ to {
304
+ opacity: 1;
305
+ }
306
+ }
307
+
308
+ .thought-toggle {
309
+ margin-top: 4px;
310
+ font-size: 0.8rem;
311
+ color: #ffbb00;
312
+ cursor: pointer;
313
+ transition: color 0.3s;
314
+ }
315
+
316
+ .thought-toggle:hover {
317
+ color: #fff000;
318
+ }
319
+
320
+ .copy-container {
321
+ margin-top: 5px;
322
+ display: flex;
323
+ justify-content: flex-end;
324
+ }
325
+
326
+ .copy-btn {
327
+ background: none;
328
+ border: none;
329
+ color: #bbb;
330
+ cursor: pointer;
331
+ font-size: 0.85rem;
332
+ padding: 4px 8px;
333
+ transition: color 0.2s ease, background 0.2s ease;
334
+ border-radius: 6px;
335
+ }
336
+
337
+ .copy-btn:hover {
338
+ color: #fff;
339
+ background: rgba(255, 255, 255, 0.1);
340
+ }
341
+ </style>
342
+ </head>
343
+
344
+ <body>
345
+ <div class="container">
346
+ <h1><i class="fa-solid fa-robot" style="margin-right: 10px; color: #f5f5f5;"></i>: Ask Me Anything About Your Data
347
+ </h1>
348
+ <!-- NEW: DB connection textarea and connect button -->
349
+ <div class="db-area">
350
+ <textarea id="db-uri" rows="2"
351
+ placeholder="Enter DB connection string (SQLAlchemy URI, mssql ODBC string, or MongoDB URI)."></textarea>
352
+ <button id="connect-db" title="Connect Database"><i class="fa-solid fa-plug"
353
+ style="margin-right:8px"></i>Connect</button>
354
+ </div>
355
+ <div class="chat-container" id="chat"></div>
356
+ <div class="input-area">
357
+ <textarea id="prompt" rows="2" placeholder="Ask something about your data..."></textarea>
358
+ <!-- <button id="upload-db" title="Upload Mongo JSON Database"><img src="/static/db_icon.svg" alt="DB" /></button> -->
359
+ <button id="upload-db" title="Upload Database"><i class="fa-solid fa-database"
360
+ style="color: #b5d3f1; font-size: 25px; background: transparent;"></i></button>
361
+ <button id="send">Send</button>
362
+ <input type="file" id="file-input" accept=".json,.db" style="display:none;" />
363
+ </div>
364
+ </div>
365
+ <div id="toast" class="toast"></div>
366
+
367
+ <script>
368
+ const socket = io()
369
+ const chat = document.getElementById('chat')
370
+ const toast = document.getElementById('toast')
371
+ const sendBtn = document.getElementById('send')
372
+ const promptTA = document.getElementById('prompt')
373
+ const uploadBtn = document.getElementById('upload-db')
374
+ const fileInput = document.getElementById('file-input')
375
+ const dbUriTA = document.getElementById('db-uri')
376
+ const connectBtn = document.getElementById('connect-db')
377
+
378
+ let typingBubble = null
379
+ let agentBubble = null
380
+
381
+ function addMessage(sender, html) {
382
+ const msg = document.createElement('div')
383
+ msg.className = `message ${sender}`
384
+ const bubble = document.createElement('div')
385
+ bubble.className = `bubble ${sender}`
386
+ bubble.innerHTML = marked.parse(html)
387
+
388
+ msg.appendChild(bubble)
389
+ // Add copy button only for user messages
390
+ if (sender === 'user') {
391
+ const copyBtn = document.createElement('button');
392
+ copyBtn.className = 'btn-icon copy-btn';
393
+ copyBtn.innerHTML = '<i class="fa-regular fa-copy"></i>';
394
+ copyBtn.title = 'Copy to clipboard';
395
+ copyBtn.onclick = () => {
396
+ navigator.clipboard.writeText(html);
397
+ showToast('Copied to clipboard!');
398
+ };
399
+ const copyContainer = document.createElement('div');
400
+ copyContainer.className = 'copy-container';
401
+ copyContainer.appendChild(copyBtn);
402
+ msg.appendChild(copyContainer);
403
+
404
+ }
405
+ chat.appendChild(msg)
406
+ chat.scrollTop = chat.scrollHeight
407
+ }
408
+
409
+ function showToast(text, duration = 3000) {
410
+ toast.textContent = text
411
+ toast.style.display = 'block'
412
+ setTimeout(() => (toast.style.display = 'none'), duration)
413
+ }
414
+
415
+ uploadBtn.addEventListener('click', () => fileInput.click())
416
+ fileInput.addEventListener('change', e => handleFile(e.target.files[0]))
417
+
418
+ // function handleFile(file) {
419
+ // if (!file) return
420
+ // showToast('Uploading Database…')
421
+ // const fd = new FormData()
422
+ // fd.append('file', file)
423
+ // fetch('/upload_db', { method: 'POST', body: fd })
424
+ // .then(r => r.json())
425
+ // .then(data => {
426
+ // if (data.success) {
427
+ // showToast('Database initialized!', 2000)
428
+ // const fileName = file.name;
429
+ // addMessage('agent', ${filename},'uploaded and ready!')
430
+ // } else {
431
+ // addMessage('agent', `Error: ${data.message}`)
432
+ // }
433
+ // })
434
+ // .catch(err => addMessage('agent', `Upload error: ${err}`))
435
+ // }
436
+ function handleFile(file) {
437
+ if (!file) return;
438
+ showToast('Uploading Database…');
439
+
440
+ const fd = new FormData();
441
+ fd.append('file', file);
442
+
443
+ fetch('/upload_db', { method: 'POST', body: fd })
444
+ .then(r => r.json())
445
+ .then(data => {
446
+ if (data.success) {
447
+ showToast('Database initialized!', 2000);
448
+ const fileName = file.name; // Get the file name from the uploaded file
449
+ addMessage('agent', `${fileName} is uploaded and ready!`);
450
+ } else {
451
+ addMessage('agent', `Error: ${data.message}`);
452
+ }
453
+ })
454
+ .catch(err => addMessage('agent', `Upload error: ${err}`));
455
+ }
456
+ function sendPrompt() {
457
+ const text = promptTA.value.trim()
458
+ if (!text || sendBtn.disabled) return
459
+ addMessage('user', text)
460
+ promptTA.value = ''
461
+ sendBtn.disabled = true
462
+
463
+ typingBubble = document.createElement('div')
464
+ typingBubble.className = 'message agent'
465
+ typingBubble.innerHTML = `<div class="bubble agent">Thinking<span class="dot">.</span><span class="dot">.</span><span class="dot">.</span></div>`
466
+ chat.appendChild(typingBubble)
467
+ chat.scrollTop = chat.scrollHeight
468
+
469
+ fetch('/generate', {
470
+ method: 'POST',
471
+ headers: { 'Content-Type': 'application/json' },
472
+ body: JSON.stringify({ prompt: text })
473
+ }).catch((err) => {
474
+ addMessage('agent', `Error: ${err}`)
475
+ sendBtn.disabled = false
476
+ if (typingBubble) chat.removeChild(typingBubble)
477
+ })
478
+ }
479
+
480
+ sendBtn.addEventListener('click', sendPrompt)
481
+
482
+ promptTA.addEventListener('keydown', (e) => {
483
+ if (e.key === 'Enter' && !e.shiftKey) {
484
+ e.preventDefault()
485
+ sendPrompt()
486
+ }
487
+ })
488
+
489
+ socket.on('final', (data) => {
490
+ if (typingBubble) {
491
+ chat.removeChild(typingBubble)
492
+ typingBubble = null
493
+ }
494
+ agentBubble = null
495
+ sendBtn.disabled = true
496
+
497
+ const msg = document.createElement('div')
498
+ msg.className = 'message agent'
499
+ const bubble = document.createElement('div')
500
+ bubble.className = 'bubble agent'
501
+ msg.appendChild(bubble)
502
+ chat.appendChild(msg)
503
+ chat.scrollTop = chat.scrollHeight
504
+
505
+ const text = data.message
506
+ let index = 0
507
+ const typingInterval = setInterval(() => {
508
+ if (index < text.length) {
509
+ bubble.textContent += text.charAt(index)
510
+ index++
511
+ chat.scrollTop = chat.scrollHeight
512
+ } else {
513
+ clearInterval(typingInterval)
514
+ sendBtn.disabled = false
515
+ }
516
+ }, 50)
517
+ })
518
+ // NEW: Connect DB button handler
519
+ connectBtn.addEventListener('click', () => {
520
+ const uri = dbUriTA.value.trim();
521
+ if (!uri) {
522
+ showToast('Please enter a connection string.');
523
+ return;
524
+ }
525
+ connectBtn.disabled = true;
526
+ showToast('Sending connection string...')
527
+ fetch('/connect_db', {
528
+ method: 'POST',
529
+ headers: { 'Content-Type': 'application/json' },
530
+ body: JSON.stringify({ uri })
531
+ })
532
+ .then(r => r.json())
533
+ .then(data => {
534
+ connectBtn.disabled = false;
535
+ if (data && data.message) {
536
+ addMessage('agent', data.message);
537
+ showToast('Connect request sent.');
538
+ } else {
539
+ addMessage('agent', 'No response from server.');
540
+ }
541
+ })
542
+ .catch(err => {
543
+ connectBtn.disabled = false;
544
+ addMessage('agent', `Connect error: ${err}`);
545
+ })
546
+ })
547
+ </script>
548
+ </body>
549
+
550
+ </html>
templates/test_agent_sql.html ADDED
@@ -0,0 +1,190 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>SQL Agent Chat</title>
7
+ <link rel="icon" type="image/png" href="/static/robot.png" />
8
+ <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
9
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css" />
10
+
11
+ <style>
12
+ * { box-sizing: border-box; }
13
+ body {
14
+ background: linear-gradient(135deg, rgb(44, 65, 112), #151f35);
15
+ color: #f0f0f0;
16
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
17
+ margin: 0; padding: 0;
18
+ overflow: hidden; height: 100vh;
19
+ }
20
+ .container {
21
+ max-width: 910px;
22
+ margin: 22px auto;
23
+ padding: 20px;
24
+ border-radius: 20px;
25
+ backdrop-filter: blur(12px);
26
+ background: rgba(255, 255, 255, 0.04);
27
+ box-shadow: 0 8px 20px rgba(0,0,0,0.2);
28
+ width: 90%;
29
+ display: flex;
30
+ flex-direction: column;
31
+ height: fit-content;
32
+ }
33
+ h1 {
34
+ text-align: center;
35
+ font-size: 1.5rem;
36
+ margin-bottom: 30px;
37
+ color: #7ee8fa;
38
+ text-shadow: 0 2px 4px #000;
39
+ }
40
+ .chat-container {
41
+ height: 450px;
42
+ overflow-y: auto;
43
+ padding: 20px;
44
+ background: rgba(255,255,255,0.05);
45
+ border-radius: 15px;
46
+ border: 1px solid rgba(255,255,255,0.05);
47
+ box-shadow: inset 0 0 10px rgba(0,0,0,0.25);
48
+ backdrop-filter: blur(8px);
49
+ }
50
+ .chat-container::-webkit-scrollbar { width: 8px; }
51
+ .chat-container::-webkit-scrollbar-track { background: #0f1b33; border-radius: 4px; }
52
+ .chat-container::-webkit-scrollbar-thumb { background: #444; border-radius: 4px; }
53
+ .chat-container::-webkit-scrollbar-thumb:hover { background: #555; }
54
+
55
+ .message { margin-bottom: 15px; display: flex; align-items: flex-start; opacity: 0; animation: fadeIn 0.5s ease forwards; }
56
+ .message.user { justify-content: flex-end; }
57
+ .message.agent { justify-content: flex-start; }
58
+ .bubble {
59
+ padding: 10px 16px;
60
+ font-size: 14px;
61
+ border-radius: 14px;
62
+ max-width: 70%;
63
+ word-wrap: break-word;
64
+ box-shadow: 0 4px 12px rgba(0,0,0,0.3);
65
+ line-height: 1.4;
66
+ }
67
+ .bubble.user {
68
+ background: linear-gradient(145deg, #007bff, #0056b3);
69
+ color: white;
70
+ border-bottom-right-radius: 0;
71
+ }
72
+ .bubble.agent {
73
+ background: linear-gradient(145deg, #2d3a55, #1b2a3a);
74
+ color: #f3f3f3;
75
+ border-bottom-left-radius: 0;
76
+ }
77
+
78
+ .input-area {
79
+ display: flex; gap: 10px; margin-top: 15px; align-items: center;
80
+ }
81
+ .input-area textarea {
82
+ flex: 1; padding: 12px; border-radius: 10px;
83
+ background: rgba(255,255,255,0.08);
84
+ color: #eaeaea;
85
+ border: 1px solid rgba(255,255,255,0.1);
86
+ resize: none;
87
+ }
88
+ .input-area textarea:focus { outline: none; box-shadow: 0 0 8px #00e0ff; }
89
+ .input-area button {
90
+ padding: 12px 20px;
91
+ border-radius: 10px; border: none;
92
+ background: #00a8ff; color: #fff;
93
+ cursor: pointer; transition: background 0.3s;
94
+ }
95
+ .input-area button:hover:not(:disabled) { background: #0077c2; }
96
+ .input-area button:disabled { background: #555; cursor: not-allowed; }
97
+
98
+ .toast {
99
+ position: fixed; bottom: 25px; right: 25px;
100
+ background: #2c3e50;
101
+ padding: 12px 22px;
102
+ border-radius: 12px;
103
+ font-size: 0.95rem;
104
+ box-shadow: 0 6px 12px rgba(0,0,0,0.3);
105
+ display: none; z-index: 1000;
106
+ }
107
+ .dot { animation: blink 1.2s infinite; display: inline-block; margin-left: 2px; }
108
+ .dot:nth-child(2){ animation-delay: 0.2s; }
109
+ .dot:nth-child(3){ animation-delay: 0.4s; }
110
+ @keyframes blink { 0%{opacity:.2} 20%{opacity:1} 100%{opacity:.2} }
111
+ @keyframes fadeIn { to { opacity: 1; } }
112
+ </style>
113
+ </head>
114
+ <body>
115
+ <div class="container">
116
+ <h1><i class="fa-solid fa-database" style="margin-right: 10px; color: #7ee8fa;"></i>
117
+ SQL Agent Assistant
118
+ </h1>
119
+ <div class="chat-container" id="chat"></div>
120
+ <div class="input-area">
121
+ <textarea id="prompt" rows="2" placeholder="Ask something about your SQL data..."></textarea>
122
+ <button id="send">Send</button>
123
+ </div>
124
+ </div>
125
+ <div id="toast" class="toast"></div>
126
+
127
+ <script>
128
+ const chat = document.getElementById('chat')
129
+ const toast = document.getElementById('toast')
130
+ const sendBtn = document.getElementById('send')
131
+ const promptTA = document.getElementById('prompt')
132
+ let typingBubble = null
133
+
134
+ function addMessage(sender, html) {
135
+ const msg = document.createElement('div')
136
+ msg.className = `message ${sender}`
137
+ const bubble = document.createElement('div')
138
+ bubble.className = `bubble ${sender}`
139
+ bubble.innerHTML = marked.parse(html)
140
+ msg.appendChild(bubble)
141
+ chat.appendChild(msg)
142
+ chat.scrollTop = chat.scrollHeight
143
+ }
144
+
145
+ function showToast(text, duration = 3000) {
146
+ toast.textContent = text
147
+ toast.style.display = 'block'
148
+ setTimeout(() => (toast.style.display = 'none'), duration)
149
+ }
150
+
151
+ function sendPrompt() {
152
+ const text = promptTA.value.trim()
153
+ if (!text || sendBtn.disabled) return
154
+ addMessage('user', text)
155
+ promptTA.value = ''
156
+ sendBtn.disabled = true
157
+
158
+ typingBubble = document.createElement('div')
159
+ typingBubble.className = 'message agent'
160
+ typingBubble.innerHTML = `<div class="bubble agent">Thinking<span class="dot">.</span><span class="dot">.</span><span class="dot">.</span></div>`
161
+ chat.appendChild(typingBubble)
162
+ chat.scrollTop = chat.scrollHeight
163
+
164
+ fetch('/generate', {
165
+ method: 'POST',
166
+ headers: { 'Content-Type': 'application/json' },
167
+ body: JSON.stringify({ prompt: text })
168
+ })
169
+ .then(r => r.json())
170
+ .then(data => {
171
+ if (typingBubble) { chat.removeChild(typingBubble); typingBubble = null }
172
+ if (data.status === "ok") addMessage('agent', data.answer)
173
+ else addMessage('agent', `❌ ${data.message}`)
174
+ })
175
+ .catch(err => {
176
+ if (typingBubble) chat.removeChild(typingBubble)
177
+ addMessage('agent', `Error: ${err}`)
178
+ })
179
+ .finally(() => { sendBtn.disabled = false })
180
+ }
181
+
182
+ sendBtn.addEventListener('click', sendPrompt)
183
+ promptTA.addEventListener('keydown', (e) => {
184
+ if (e.key === 'Enter' && !e.shiftKey) {
185
+ e.preventDefault(); sendPrompt()
186
+ }
187
+ })
188
+ </script>
189
+ </body>
190
+ </html>