nhunapenguyen commited on
Commit
41e57de
·
1 Parent(s): 563f29b
.DS_Store ADDED
Binary file (8.2 kB). View file
 
Dockerfile CHANGED
@@ -1,60 +1,23 @@
1
- FROM ghcr.io/huggingface/chat-ui:latest AS base
 
2
 
3
- FROM ghcr.io/huggingface/text-generation-inference:latest AS final
 
4
 
5
- ARG MODEL_NAME
6
- ENV MODEL_NAME=${MODEL_NAME}
7
 
8
- ENV TZ=Europe/Paris \
9
- PORT=3000
10
 
11
- # mongo installation
12
- RUN curl -fsSL https://www.mongodb.org/static/pgp/server-7.0.asc | \
13
- gpg -o /usr/share/keyrings/mongodb-server-7.0.gpg \
14
- --dearmor
15
 
16
- RUN echo "deb [ arch=amd64,arm64 signed-by=/usr/share/keyrings/mongodb-server-7.0.gpg ] https://repo.mongodb.org/apt/ubuntu jammy/mongodb-org/7.0 multiverse" | tee /etc/apt/sources.list.d/mongodb-org-7.0.list
 
17
 
18
- RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
19
- mongodb-org && \
20
- rm -rf /var/lib/apt/lists/*
21
 
22
- # node installation
23
- RUN curl -fsSL https://deb.nodesource.com/setup_20.x | /bin/bash -
24
-
25
- RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
26
- nodejs && \
27
- rm -rf /var/lib/apt/lists/*
28
-
29
- # image setup
30
- RUN useradd -m -u 1000 user
31
-
32
- RUN mkdir /app
33
- RUN chown -R 1000:1000 /app
34
- RUN mkdir /data
35
- RUN chown -R 1000:1000 /data
36
-
37
- # Switch to the "user" user
38
- USER user
39
-
40
- ENV HOME=/home/user \
41
- PATH=/home/user/.local/bin:$PATH
42
-
43
- RUN npm config set prefix /home/user/.local
44
- RUN npm install -g dotenv-cli
45
-
46
-
47
- # copy chat-ui from base image
48
- COPY --from=base --chown=1000 /app/node_modules /app/node_modules
49
- COPY --from=base --chown=1000 /app/package.json /app/package.json
50
- COPY --from=base --chown=1000 /app/build /app/build
51
-
52
- COPY --from=base --chown=1000 /app/.env /app/.env
53
- COPY --chown=1000 .env.local /app/.env.local
54
-
55
- COPY --chown=1000 entrypoint.sh /app/entrypoint.sh
56
-
57
- RUN chmod +x /app/entrypoint.sh
58
-
59
- # entrypoint
60
- ENTRYPOINT [ "/app/entrypoint.sh" ]
 
1
+ # Use the official Python 3.9 image from the Docker Hub
2
+ FROM python:3.9-slim
3
 
4
+ # Set the working directory in the container
5
+ WORKDIR /app
6
 
7
+ # Copy the requirements.txt file into the container at /app
8
+ COPY requirements.txt /app/
9
 
10
+ # Install the dependencies from requirements.txt
11
+ RUN pip install --no-cache-dir -r requirements.txt
12
 
13
+ # Copy the rest of the application code into the container
14
+ COPY . /app/
 
 
15
 
16
+ # Expose the port your app will run on (assuming Flask/Django app listens on port 5000)
17
+ EXPOSE 5000
18
 
19
+ # Command to run the app with Gunicorn (for production use)
20
+ CMD ["gunicorn", "app:app", "--bind", "0.0.0.0:$PORT"]
 
21
 
22
+ # For development (uncomment the following line if you're in development mode):
23
+ # CMD ["python", "app.py"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Procfile ADDED
@@ -0,0 +1 @@
 
 
1
+ web: gunicorn app:app --bind 0.0.0.0:$PORT
README.md CHANGED
@@ -1,13 +1,4 @@
1
- ---
2
- title: DrugRecommendation
3
- emoji: 🚀
4
- colorFrom: indigo
5
- colorTo: blue
6
- sdk: docker
7
- pinned: false
8
- app_port: 3000
9
- suggested_hardware: a10g-small
10
- short_description: This is a system support to recommend drugs based on symptom
11
- ---
12
 
13
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
+ RUN APP:
2
+ pip install -r requirements.txt
 
 
 
 
 
 
 
 
 
3
 
4
+ python app.py
__pycache__/gemini_handler.cpython-312.pyc ADDED
Binary file (27.1 kB). View file
 
__pycache__/gemini_handler.cpython-313.pyc ADDED
Binary file (27.7 kB). View file
 
app.py ADDED
@@ -0,0 +1,394 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Flask, request, jsonify, render_template,make_response,redirect, url_for, session
2
+ from flask_pymongo import PyMongo
3
+ from bson.objectid import ObjectId
4
+ import bcrypt
5
+ from flask_cors import CORS
6
+ from sentence_transformers import SentenceTransformer, util
7
+ import faiss
8
+ import numpy as np
9
+ import json
10
+ import os
11
+ import pickle
12
+ import pandas as pd
13
+ import google.generativeai as genai
14
+ from datetime import datetime
15
+ import uuid
16
+ from gemini_handler import GeminiHandler, GenerationConfig, Strategy, KeyRotationStrategy
17
+ from dotenv import load_dotenv
18
+ # Load biến môi trường từ file .env
19
+ load_dotenv()
20
+ GEMINI_API_KEYS = os.getenv("GEMINI_API_KEYS").split(",")
21
+
22
+ # Khởi tạo Flask app
23
+ app = Flask(__name__)
24
+
25
+ app.secret_key = 'AIzaSyCvlZ63Nkt5NpjdmxYPAsG8Qskex6usCFw'
26
+ app.config['MONGO_URI'] = 'mongodb+srv://admin:[email protected]/drug_recom'
27
+ mongo = PyMongo(app)
28
+
29
+
30
+
31
+
32
+ CORS(app)
33
+
34
+ genai.configure(api_key="AIzaSyCvlZ63Nkt5NpjdmxYPAsG8Qskex6usCFw")
35
+
36
+ # Tải FAISS index
37
+ faiss_index = faiss.read_index("source/faiss_index_vn.bin")
38
+
39
+ # Tải embeddings
40
+ with open("source/sentence_embeddings_vn.pkl", "rb") as f:
41
+ disease_embeddings = pickle.load(f)
42
+
43
+ # Khởi tạo mô hình Sentence Transformer
44
+ model_em = SentenceTransformer('hiieu/halong_embedding')
45
+ # Đọc dữ liệu bệnh
46
+ merged_df = pd.read_csv('source/merged_df_vn.csv')
47
+
48
+
49
+ def get_disease_and_generate_prompt(symptoms_input, faiss_index, model_em, merged_df, top_k=5):
50
+ # 1. Mã hóa triệu chứng đầu vào
51
+ input_embedding = model_em.encode([symptoms_input], convert_to_tensor=True)
52
+
53
+ # 2. Tìm kiếm top_k trong FAISS Index
54
+ distances, indices = faiss_index.search(np.array(input_embedding.cpu().numpy()), k=top_k)
55
+
56
+ # Lấy danh sách các bệnh và điểm tương ứng
57
+ top_diseases = [(merged_df.iloc[idx], score) for idx, score in zip(indices[0], distances[0])]
58
+
59
+ # 3. Mã hóa thông tin của top_k bệnh để đánh giá lại
60
+ candidate_embeddings = model_em.encode(
61
+ [disease['Information'] for disease, _ in top_diseases],
62
+ convert_to_tensor=True
63
+ )
64
+
65
+ # 4. Tính độ tương đồng cosine giữa triệu chứng đầu vào và các ứng viên
66
+ scores = util.cos_sim(input_embedding, candidate_embeddings).squeeze()
67
+
68
+ # 5. Sắp xếp lại danh sách ứng viên dựa trên điểm similarity
69
+ ranked_indices = scores.argsort(descending=True)
70
+ best_match = top_diseases[ranked_indices[0].item()][0] # Ứng viên tốt nhất sau re-ranking
71
+
72
+ # 6. Chuyển thông tin bệnh tốt nhất thành danh sách
73
+ result_list = [
74
+ f"Patient Symptoms: {symptoms_input}",
75
+ f"similarity: {scores}",
76
+ # f"disease: {best_match['Disease']}",
77
+ f"symptoms: {best_match['Symptoms']}",
78
+ f"medications: {best_match['Medication']}",
79
+ f"diets: {best_match['Diet']}",
80
+ f"workouts: {best_match['workout']}",
81
+ f"precautions: {best_match.get('Precaution_1', '')}, {best_match.get('Precaution_2', '')}, {best_match.get('Precaution_3', '')}, {best_match.get('Precaution_4', '')}",
82
+ ]
83
+ #print(result_list)
84
+ return result_list
85
+
86
+
87
+
88
+ def parse_contexts(raw_contexts):
89
+ """Convert raw context strings into structured dictionaries."""
90
+ structured_contexts = []
91
+ temp_context = {}
92
+ for context in raw_contexts:
93
+ # Check for each expected piece of information and assign it to the dictionary
94
+ if context.startswith("Patient Symptoms:"):
95
+ temp_context["patient_symptoms"] = context.replace("Patient Symptoms:", "").strip()
96
+ elif context.startswith("disease"):
97
+ temp_context["disease"] = context.replace("disease", "").strip()
98
+ elif context.startswith("symptoms:"):
99
+ temp_context["symptoms"] = context.replace("symptoms:", "").strip().split(", ")
100
+ elif context.startswith("medications:"):
101
+ temp_context["medications"] = context.replace("medications:", "").strip().split(", ")
102
+ elif context.startswith("diets:"):
103
+ temp_context["diets"] = context.replace("diets:", "").strip().split(", ")
104
+ elif context.startswith("workouts:"):
105
+ temp_context["workouts"] = context.replace("workouts:", "").strip().split(", ")
106
+ elif context.startswith("precautions:"):
107
+ temp_context["precautions"] = context.replace("precautions:", "").strip().split(", ")
108
+
109
+ # When all necessary fields are collected, add to the list and reset temp_context
110
+ if len(temp_context) == 7:
111
+ structured_contexts.append(temp_context)
112
+ temp_context = {}
113
+
114
+ return structured_contexts
115
+
116
+ prompt_template = (
117
+ "### Hệ thống:"
118
+ "Bạn đang nhận được một yêu cầu tư vấn y tế. Dưới đây là thông tin cần thiết để bạn đưa ra câu trả lời chính xác, dễ hiểu và hữu ích cho người dùng."
119
+ "Hãy tập trung vào việc cung cấp tên thuốc phù hợp và hướng dẫn rõ ràng để giúp người dùng áp dụng dễ dàng."
120
+ "### Hướng dẫn:"
121
+ "{instruction}\n\n"
122
+
123
+ "### Thông tin y tế:\n"
124
+ "{input}\n\n"
125
+
126
+ "### Câu trả lời:\n"
127
+ "{output}"
128
+ )
129
+
130
+ def get_prompt(question, raw_contexts):
131
+ if not raw_contexts:
132
+ raise ValueError("Danh sách thông tin y tế không được để trống.")
133
+
134
+ # Xử lý dữ liệu đầu vào thành dạng dễ đọc
135
+ contexts = parse_contexts(raw_contexts)
136
+
137
+ context = "".join([
138
+ f"\n<b>📌 Trường hợp {i+1}:</b>\n"
139
+ #f"- <b>Bệnh:</b> {x.get('disease', 'Chưa xác định')}\n"
140
+ f"- <b>Triệu chứng:</b> {', '.join(map(str, x.get('symptoms', [])))}\n"
141
+ f"- <b>Thuốc đề xuất:</b> <i>{', '.join(map(str, x.get('medications', [])))}</i>\n"
142
+ f"- <b>Chế độ ăn uống:</b> {', '.join(map(str, x.get('diets', [])))}\n"
143
+ f"- <b>Bài tập hỗ trợ:</b> {', '.join(map(str, x.get('workouts', [])))}\n"
144
+ f"- <b>Lưu ý quan trọng:</b> {', '.join(map(str, x.get('precautions', [])))}\n"
145
+ for i, x in enumerate(contexts)
146
+ ])
147
+
148
+ instruction = (
149
+ "💊 Bạn là một dược sĩ có kinh nghiệm lâu năm. Hãy cung cấp câu trả lời <b>đầy đủ, chính xác</b>, "
150
+ "dễ hiểu và tập trung vào <b>tư vấn thuốc</b> cho người dùng.\n"
151
+ "🔹 Trình bày câu trả lời theo danh sách số thứ tự (1️⃣, 2️⃣, 3️⃣...) để dễ đọc.\n"
152
+ "🔹 Định dạng rõ ràng: <b>in đậm</b> những điểm quan trọng, <i>in nghiêng</i> tên thuốc, <b>in đậm</b> các lưu ý quan trọng, "
153
+ "sử dụng dấu gạch đầu dòng (-) để liệt kê thông tin."
154
+ )
155
+
156
+ input_text = (
157
+ "🩺 Dựa trên thông tin y tế sau đây, hãy trả lời câu hỏi của người dùng:\n"
158
+ f"{context}\n"
159
+ "❓ <b>Câu hỏi:</b> " + question + "\n"
160
+ "📌 <b>Yêu cầu:</b> Hãy trả lời theo bố cục số thứ tự, dễ đọc, ngắn gọn nhưng đầy đủ, giúp người dùng dễ áp dụng.\n"
161
+ "📋 <b>Định dạng:</b>\n"
162
+ "- <b>In đậm</b> cho thông tin quan trọng\n"
163
+ "- <i>In nghiêng</i> cho tên thuốc\n"
164
+ "- <b>In đậm</b> cho các lưu ý đặc biệt\n"
165
+ "- Dấu gạch đầu dòng (-) để trình bày rõ ràng khi liệt kê"
166
+ )
167
+
168
+ prompt = prompt_template.format(
169
+ instruction=instruction,
170
+ input=input_text,
171
+ output='' # AI sẽ tự điền câu trả lời
172
+ )
173
+
174
+ return prompt
175
+
176
+ @app.route("/send_message", methods=["POST"])
177
+ def send_message():
178
+ try:
179
+ data = request.json
180
+ print("📩 Received Data:", data) # Debug log
181
+
182
+ # Kiểm tra request JSON hợp lệ
183
+ if not data or "message" not in data:
184
+ return jsonify({"error": "Dữ liệu không hợp lệ!"}), 400
185
+
186
+ message = data.get("message", "").strip()
187
+ conversation_id = data.get("conversation_id", "")
188
+
189
+ if not message:
190
+ return jsonify({"error": "Câu hỏi không hợp lệ!"}), 400
191
+
192
+ # Nếu không có conversation_id, tạo cuộc trò chuyện mới
193
+ if not conversation_id:
194
+ conversation_id = str(uuid.uuid4())
195
+ session["conversation_id"] = conversation_id
196
+ mongo.db.chat_history.insert_one({
197
+ "conversation_id": conversation_id,
198
+ "name": "Chưa có tên",
199
+ "messages": [],
200
+ "created_at": datetime.now().strftime("%d/%m/%Y %H:%M:%S")
201
+ })
202
+
203
+ # Gọi hàm lấy dữ liệu context
204
+ context_data = get_disease_and_generate_prompt(message, faiss_index, model_em, merged_df, top_k=2)
205
+ prompt = get_prompt(message, context_data)
206
+
207
+ # Get API keys từ environment
208
+ api_keys = os.getenv('GEMINI_API_KEYS')
209
+ if not api_keys:
210
+ raise ValueError("GEMINI_API_KEYS environment variable is not set")
211
+
212
+
213
+ # Round-robin strategy
214
+ handler = GeminiHandler(
215
+ config_path="config.yaml",
216
+ content_strategy=Strategy.ROUND_ROBIN,
217
+ key_strategy=KeyRotationStrategy.SMART_COOLDOWN
218
+ )
219
+
220
+ # Generate content
221
+ response_new = handler.generate_content(
222
+ prompt=prompt,
223
+ model_name="gemini-2.0-flash-thinking-exp-1219",
224
+ return_stats=True # Get key usage stats
225
+ )
226
+
227
+ # Process response
228
+ # if response_new['success']:
229
+ # print(response_new['text'])
230
+ # else:
231
+ # print(f"Generation failed: {response_new['error']}")
232
+
233
+ # # Gửi prompt đến model Gemini
234
+ # model = genai.GenerativeModel("gemini-1.5-flash", generation_config=generation_config)
235
+ # chat_session = model.start_chat(history=[])
236
+ # response = chat_session.send_message(prompt)
237
+
238
+ # Lấy nội dung phản hồi từ bot
239
+ try:
240
+ text_response = response_new['text']
241
+ except Exception as e:
242
+ print(f"⚠️ Error getting response: {e}")
243
+ return jsonify({"error": "Lỗi khi lấy phản hồi từ mô hình!"}), 500
244
+
245
+ # Cấu trúc tin nhắn
246
+ timestamp = datetime.now().strftime("%d/%m/%Y %H:%M:%S")
247
+ user_message = {"text": message, "timestamp": timestamp, "sender": "user"}
248
+ bot_message = {"text": text_response, "timestamp": timestamp, "sender": "bot"}
249
+
250
+ # Cập nhật cuộc trò chuyện
251
+ mongo.db.chat_history.update_one(
252
+ {"conversation_id": conversation_id},
253
+ {"$push": {"messages": {"$each": [user_message, bot_message]}}},
254
+ upsert=True
255
+ )
256
+
257
+ # Trả về phản hồi JSON
258
+ return jsonify({
259
+ "conversation_id": conversation_id,
260
+ "status": "sent",
261
+ "bot_reply": text_response,
262
+ "timestamp": timestamp
263
+ })
264
+
265
+ except Exception as e:
266
+ print(f"🚨 Error in /query: {str(e)}")
267
+ return jsonify({"error": f"Lỗi xử lý: {str(e)}"}), 500
268
+
269
+
270
+ @app.route("/start_conversation", methods=["POST"])
271
+ def start_conversation():
272
+ try:
273
+ data = request.json
274
+ message = data.get("message", "").strip()
275
+
276
+ if not message:
277
+ return jsonify({"error": "Tin nhắn không hợp lệ!"}), 400
278
+
279
+ conversation_id = str(uuid.uuid4()) # Tạo conversation_id mới
280
+ session["conversation_id"] = conversation_id
281
+
282
+ mongo.db.chat_history.insert_one({
283
+ "conversation_id": conversation_id,
284
+ "name": message,
285
+ "messages": [],
286
+ "created_at": datetime.now().strftime("%d/%m/%Y %H:%M:%S")
287
+ })
288
+
289
+ return jsonify({"conversation_id": conversation_id})
290
+
291
+ except Exception as e:
292
+ print(f"🚨 Error in /start_conversation: {str(e)}")
293
+ return jsonify({"error": f"Lỗi xử lý: {str(e)}"}), 500
294
+
295
+
296
+ @app.route("/all_history", methods=["GET"])
297
+ def get_all_history():
298
+ """Lấy danh sách tất cả cuộc trò chuyện"""
299
+ chats = list(mongo.db.chat_history.find({}, {"_id": 0, "messages": 0}))
300
+ return jsonify(chats)
301
+
302
+
303
+ @app.route("/conversation/<conversation_id>", methods=["GET"])
304
+ def get_conversation(conversation_id):
305
+ """Lấy nội dung cuộc trò chuyện theo ID"""
306
+ chat = mongo.db.chat_history.find_one({"conversation_id": conversation_id}, {"_id": 0})
307
+ if not chat:
308
+ return jsonify({"error": "Cuộc trò chuyện không tồn tại"}), 404
309
+ return jsonify(chat)
310
+
311
+ @app.route('/get_conversations', methods=['GET'])
312
+ def get_conversations():
313
+ # Lấy tất cả cuộc trò chuyện từ MongoDB
314
+ conversations = list(mongo.db.chat_history.find({}, {"_id": 0}))
315
+
316
+ return jsonify([{
317
+ "conversation_id": conv.get("conversation_id", "N/A"),
318
+ "name": conv.get("name", "Chưa có tên"),
319
+ "bot_messages": [msg["text"] for msg in conv.get("messages", []) if msg.get("sender") == "bot"], # Chỉ lấy text của bot
320
+ "created_at": conv.get("created_at", "Không rõ ngày")
321
+ } for conv in conversations])
322
+
323
+ @app.route('/get_messages', methods=['GET'])
324
+ def get_messages():
325
+ conversation_id = request.args.get("conversation_id")
326
+
327
+ if not conversation_id:
328
+ return jsonify({"error": "Thiếu conversation_id"}), 400
329
+
330
+ # Truy vấn cuộc trò chuyện theo conversation_id
331
+ conversation = mongo.db.chat_history.find_one(
332
+ {"conversation_id": conversation_id},
333
+ {"_id": 0, "messages": 1,"name":1, "timestamp":1}
334
+ )
335
+
336
+ if conversation:
337
+ return jsonify(conversation) # Trả về danh sách tin nhắn
338
+ else:
339
+ return jsonify({"error": "Không tìm thấy cuộc trò chuyện"}), 404
340
+
341
+
342
+
343
+ @app.route("/")
344
+ def index():
345
+ return render_template('index.html')
346
+
347
+ @app.route('/register', methods=['GET', 'POST'])
348
+ def register():
349
+ if request.method == 'POST':
350
+ fullname = request.form['fullname']
351
+ username = request.form['username']
352
+ password = request.form['password']
353
+ hash_password = bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt())
354
+
355
+ # Insert user into the 'user' collection
356
+ mongo.db.user.insert_one({'fullname': fullname,'username': username, 'password': hash_password})
357
+ return redirect(url_for('login'))
358
+ return render_template('register.html')
359
+
360
+ @app.route('/login', methods=['GET', 'POST'])
361
+ def login():
362
+ if request.method == 'POST':
363
+ username = request.form['username']
364
+ password = request.form['password']
365
+
366
+ # Find user in the 'user' collection
367
+ user = mongo.db.user.find_one({'username': username})
368
+
369
+ # Kiểm tra nếu người dùng tồn tại và mật khẩu đúng
370
+ if user and bcrypt.checkpw(password.encode('utf-8'), user['password']):
371
+ # Lưu fullname của người dùng vào session
372
+ session['user'] = user.get('fullname', 'Unknown User') # Đảm bảo lấy giá trị 'fullname' nếu có
373
+ return redirect(url_for('dashboard'))
374
+ else:
375
+ return 'Invalid username or password'
376
+ return render_template('login.html')
377
+
378
+ @app.route("/dashboard")
379
+ def dashboard():
380
+ if 'user' in session:
381
+ current_time = datetime.now().strftime("%d/%m/%Y %H:%M:%S")
382
+ username = session["user"]
383
+ return render_template('dashboard.html',username=username,current_time=current_time)
384
+ return redirect(url_for('login'))
385
+
386
+ @app.route('/logout')
387
+ def logout():
388
+ session.pop('user', None)
389
+ return redirect(url_for('login'))
390
+
391
+
392
+
393
+ if __name__ == "__main__":
394
+ app.run(debug=True)
apps.py ADDED
@@ -0,0 +1,372 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Flask, request, jsonify, render_template,make_response,redirect, url_for, session
2
+ from flask_pymongo import PyMongo
3
+ from bson.objectid import ObjectId
4
+ import bcrypt
5
+ from flask_cors import CORS
6
+ from sentence_transformers import SentenceTransformer, util
7
+ import faiss
8
+ import numpy as np
9
+ import json
10
+ import os
11
+ import pickle
12
+ import pandas as pd
13
+ import google.generativeai as genai
14
+ from datetime import datetime
15
+ import uuid
16
+
17
+ # Khởi tạo Flask app
18
+ app = Flask(__name__)
19
+
20
+ app.secret_key = 'AIzaSyCvlZ63Nkt5NpjdmxYPAsG8Qskex6usCFw'
21
+ app.config['MONGO_URI'] = 'mongodb+srv://hoangsontruonghcm:[email protected]/drug_recom'
22
+ mongo = PyMongo(app)
23
+
24
+
25
+
26
+
27
+ CORS(app)
28
+
29
+ genai.configure(api_key="AIzaSyCvlZ63Nkt5NpjdmxYPAsG8Qskex6usCFw")
30
+
31
+ # Tải FAISS index
32
+ faiss_index = faiss.read_index("source/faiss_index_vn.bin")
33
+
34
+ # Tải embeddings
35
+ with open("source/sentence_embeddings_vn.pkl", "rb") as f:
36
+ disease_embeddings = pickle.load(f)
37
+
38
+ # Khởi tạo mô hình Sentence Transformer
39
+ model_em = SentenceTransformer('hiieu/halong_embedding')
40
+ # Đọc dữ liệu bệnh
41
+ merged_df = pd.read_csv('source/merged_df_vn.csv')
42
+
43
+
44
+ def get_disease_and_generate_prompt(symptoms_input, faiss_index, model_em, merged_df, top_k=5):
45
+ # 1. Mã hóa triệu chứng đầu vào
46
+ input_embedding = model_em.encode([symptoms_input], convert_to_tensor=True)
47
+
48
+ # 2. Tìm kiếm top_k trong FAISS Index
49
+ distances, indices = faiss_index.search(np.array(input_embedding.cpu().numpy()), k=top_k)
50
+
51
+ # Lấy danh sách các bệnh và điểm tương ứng
52
+ top_diseases = [(merged_df.iloc[idx], score) for idx, score in zip(indices[0], distances[0])]
53
+
54
+ # 3. Mã hóa thông tin của top_k bệnh để đánh giá lại
55
+ candidate_embeddings = model_em.encode(
56
+ [disease['Information'] for disease, _ in top_diseases],
57
+ convert_to_tensor=True
58
+ )
59
+
60
+ # 4. Tính độ tương đồng cosine giữa triệu chứng đầu vào và các ứng viên
61
+ scores = util.cos_sim(input_embedding, candidate_embeddings).squeeze()
62
+
63
+ # 5. Sắp xếp lại danh sách ứng viên dựa trên điểm similarity
64
+ ranked_indices = scores.argsort(descending=True)
65
+ best_match = top_diseases[ranked_indices[0].item()][0] # Ứng viên tốt nhất sau re-ranking
66
+
67
+ # 6. Chuyển thông tin bệnh tốt nhất thành danh sách
68
+ result_list = [
69
+ f"Patient Symptoms: {symptoms_input}",
70
+ f"similarity: {scores}",
71
+ f"disease: {best_match['Disease']}",
72
+ f"symptoms: {best_match['Symptoms']}",
73
+ f"medications: {best_match['Medication']}",
74
+ f"diets: {best_match['Diet']}",
75
+ f"workouts: {best_match['workout']}",
76
+ f"precautions: {best_match.get('Precaution_1', '')}, {best_match.get('Precaution_2', '')}, {best_match.get('Precaution_3', '')}, {best_match.get('Precaution_4', '')}",
77
+ ]
78
+
79
+ return result_list
80
+
81
+
82
+
83
+ def parse_contexts(raw_contexts):
84
+ """Convert raw context strings into structured dictionaries."""
85
+ structured_contexts = []
86
+ temp_context = {}
87
+ for context in raw_contexts:
88
+ # Check for each expected piece of information and assign it to the dictionary
89
+ if context.startswith("Patient Symptoms:"):
90
+ temp_context["patient_symptoms"] = context.replace("Patient Symptoms:", "").strip()
91
+ elif context.startswith("disease"):
92
+ temp_context["disease"] = context.replace("disease", "").strip()
93
+ elif context.startswith("symptoms:"):
94
+ temp_context["symptoms"] = context.replace("symptoms:", "").strip().split(", ")
95
+ elif context.startswith("medications:"):
96
+ temp_context["medications"] = context.replace("medications:", "").strip().split(", ")
97
+ elif context.startswith("diets:"):
98
+ temp_context["diets"] = context.replace("diets:", "").strip().split(", ")
99
+ elif context.startswith("workouts:"):
100
+ temp_context["workouts"] = context.replace("workouts:", "").strip().split(", ")
101
+ elif context.startswith("precautions:"):
102
+ temp_context["precautions"] = context.replace("precautions:", "").strip().split(", ")
103
+
104
+ # When all necessary fields are collected, add to the list and reset temp_context
105
+ if len(temp_context) == 7:
106
+ structured_contexts.append(temp_context)
107
+ temp_context = {}
108
+
109
+ return structured_contexts
110
+
111
+ prompt_template = (
112
+ "### Hệ thống:"
113
+ "Bạn đang nhận được một yêu cầu tư vấn y tế. Dưới đây là thông tin cần thiết để bạn đưa ra câu trả lời chính xác, dễ hiểu và hữu ích cho người dùng."
114
+ "Hãy tập trung vào việc cung cấp tên thuốc phù hợp và hướng dẫn rõ ràng để giúp người dùng áp dụng dễ dàng."
115
+ "### Hướng dẫn:"
116
+ "{instruction}\n\n"
117
+
118
+ "### Thông tin y tế:\n"
119
+ "{input}\n\n"
120
+
121
+ "### Câu trả lời:\n"
122
+ "{output}"
123
+ )
124
+
125
+ def get_prompt(question, raw_contexts):
126
+ if not raw_contexts:
127
+ raise ValueError("Danh sách thông tin y tế không được để trống.")
128
+
129
+ # Xử lý dữ liệu đầu vào thành dạng dễ đọc
130
+ contexts = parse_contexts(raw_contexts)
131
+
132
+ context = "".join([
133
+ f"\n<b>📌 Trường hợp {i+1}:</b>\n"
134
+ f"- <b>Bệnh:</b> {x.get('disease', 'Chưa xác định')}\n"
135
+ f"- <b>Triệu chứng:</b> {', '.join(map(str, x.get('symptoms', [])))}\n"
136
+ f"- <b>Thuốc đề xuất:</b> <i>{', '.join(map(str, x.get('medications', [])))}</i>\n"
137
+ f"- <b>Chế độ ăn uống:</b> {', '.join(map(str, x.get('diets', [])))}\n"
138
+ f"- <b>Bài tập hỗ trợ:</b> {', '.join(map(str, x.get('workouts', [])))}\n"
139
+ f"- <b>Lưu ý quan trọng:</b> {', '.join(map(str, x.get('precautions', [])))}\n"
140
+ for i, x in enumerate(contexts)
141
+ ])
142
+
143
+ instruction = (
144
+ "💊 Bạn là một dược sĩ có kinh nghiệm lâu năm. Hãy cung cấp câu trả lời <b>đầy đủ, chính xác</b>, "
145
+ "dễ hiểu và tập trung vào <b>tư vấn thuốc</b> cho người dùng.\n"
146
+ "🔹 Trình bày câu trả lời theo danh sách số thứ tự (1️⃣, 2️⃣, 3️⃣...) để dễ đọc.\n"
147
+ "🔹 Định dạng rõ ràng: <b>in đậm</b> những điểm quan trọng, <i>in nghiêng</i> tên thuốc, <b>in đậm</b> các lưu ý quan trọng, "
148
+ "sử dụng dấu gạch đầu dòng (-) để liệt kê thông tin."
149
+ )
150
+
151
+ input_text = (
152
+ "🩺 Dựa trên thông tin y tế sau đây, hãy trả lời câu hỏi của người dùng:\n"
153
+ f"{context}\n"
154
+ "❓ <b>Câu hỏi:</b> " + question + "\n"
155
+ "📌 <b>Yêu cầu:</b> Hãy trả lời theo bố cục số thứ tự, dễ đọc, ngắn gọn nhưng đầy đủ, giúp người dùng dễ áp dụng.\n"
156
+ "📋 <b>Định dạng:</b>\n"
157
+ "- <b>In đậm</b> cho thông tin quan trọng\n"
158
+ "- <i>In nghiêng</i> cho tên thuốc\n"
159
+ "- <b>In đậm</b> cho các lưu ý đặc biệt\n"
160
+ "- Dấu gạch đầu dòng (-) để trình bày rõ ràng khi liệt kê"
161
+ )
162
+
163
+ prompt = prompt_template.format(
164
+ instruction=instruction,
165
+ input=input_text,
166
+ output='' # AI sẽ tự điền câu trả lời
167
+ )
168
+
169
+ return prompt
170
+
171
+ @app.route("/send_message", methods=["POST"])
172
+ def send_message():
173
+ try:
174
+ data = request.json
175
+ print("📩 Received Data:", data) # Debug log
176
+
177
+ # Kiểm tra request JSON hợp lệ
178
+ if not data or "message" not in data:
179
+ return jsonify({"error": "Dữ liệu không hợp lệ!"}), 400
180
+
181
+ message = data.get("message", "").strip()
182
+ conversation_id = data.get("conversation_id", "")
183
+
184
+ if not message:
185
+ return jsonify({"error": "Câu hỏi không hợp lệ!"}), 400
186
+
187
+ # Nếu không có conversation_id, tạo cuộc trò chuyện mới
188
+ if not conversation_id:
189
+ conversation_id = str(uuid.uuid4())
190
+ session["conversation_id"] = conversation_id
191
+ mongo.db.chat_history.insert_one({
192
+ "conversation_id": conversation_id,
193
+ "name": "Chưa có tên",
194
+ "messages": [],
195
+ "created_at": datetime.now().strftime("%d/%m/%Y %H:%M:%S")
196
+ })
197
+
198
+ # Gọi hàm lấy dữ liệu context
199
+ context_data = get_disease_and_generate_prompt(message, faiss_index, model_em, merged_df, top_k=5)
200
+ prompt = get_prompt(message, context_data)
201
+
202
+ # Cấu hình model Gemini
203
+ generation_config = {
204
+ "temperature": 0.2,
205
+ "top_p": 0.6,
206
+ "top_k": 40,
207
+ "max_output_tokens": 3000,
208
+ "response_mime_type": "text/plain",
209
+ }
210
+
211
+ # Gửi prompt đến model Gemini
212
+ model = genai.GenerativeModel("gemini-1.5-flash", generation_config=generation_config)
213
+ chat_session = model.start_chat(history=[])
214
+ response = chat_session.send_message(prompt)
215
+
216
+ # Lấy nội dung phản hồi từ bot
217
+ try:
218
+ text_response = response.candidates[0].content.parts[0].text
219
+ except Exception as e:
220
+ print(f"⚠️ Error getting response: {e}")
221
+ return jsonify({"error": "Lỗi khi lấy phản hồi từ mô hình!"}), 500
222
+
223
+ # Cấu trúc tin nhắn
224
+ timestamp = datetime.now().strftime("%d/%m/%Y %H:%M:%S")
225
+ user_message = {"text": message, "timestamp": timestamp, "sender": "user"}
226
+ bot_message = {"text": text_response, "timestamp": timestamp, "sender": "bot"}
227
+
228
+ # Cập nhật cuộc trò chuyện
229
+ mongo.db.chat_history.update_one(
230
+ {"conversation_id": conversation_id},
231
+ {"$push": {"messages": {"$each": [user_message, bot_message]}}},
232
+ upsert=True
233
+ )
234
+
235
+ # Trả về phản hồi JSON
236
+ return jsonify({
237
+ "conversation_id": conversation_id,
238
+ "status": "sent",
239
+ "bot_reply": text_response,
240
+ "timestamp": timestamp
241
+ })
242
+
243
+ except Exception as e:
244
+ print(f"🚨 Error in /query: {str(e)}")
245
+ return jsonify({"error": f"Lỗi xử lý: {str(e)}"}), 500
246
+
247
+
248
+ @app.route("/start_conversation", methods=["POST"])
249
+ def start_conversation():
250
+ try:
251
+ data = request.json
252
+ message = data.get("message", "").strip()
253
+
254
+ if not message:
255
+ return jsonify({"error": "Tin nhắn không hợp lệ!"}), 400
256
+
257
+ conversation_id = str(uuid.uuid4()) # Tạo conversation_id mới
258
+ session["conversation_id"] = conversation_id
259
+
260
+ mongo.db.chat_history.insert_one({
261
+ "conversation_id": conversation_id,
262
+ "name": message,
263
+ "messages": [],
264
+ "created_at": datetime.now().strftime("%d/%m/%Y %H:%M:%S")
265
+ })
266
+
267
+ return jsonify({"conversation_id": conversation_id})
268
+
269
+ except Exception as e:
270
+ print(f"🚨 Error in /start_conversation: {str(e)}")
271
+ return jsonify({"error": f"Lỗi xử lý: {str(e)}"}), 500
272
+
273
+
274
+ @app.route("/all_history", methods=["GET"])
275
+ def get_all_history():
276
+ """Lấy danh sách tất cả cuộc trò chuyện"""
277
+ chats = list(mongo.db.chat_history.find({}, {"_id": 0, "messages": 0}))
278
+ return jsonify(chats)
279
+
280
+
281
+ @app.route("/conversation/<conversation_id>", methods=["GET"])
282
+ def get_conversation(conversation_id):
283
+ """Lấy nội dung cuộc trò chuyện theo ID"""
284
+ chat = mongo.db.chat_history.find_one({"conversation_id": conversation_id}, {"_id": 0})
285
+ if not chat:
286
+ return jsonify({"error": "Cuộc trò chuyện không tồn tại"}), 404
287
+ return jsonify(chat)
288
+
289
+ @app.route('/get_conversations', methods=['GET'])
290
+ def get_conversations():
291
+ # Lấy tất cả cuộc trò chuyện từ MongoDB
292
+ conversations = list(mongo.db.chat_history.find({}, {"_id": 0}))
293
+
294
+ return jsonify([{
295
+ "conversation_id": conv.get("conversation_id", "N/A"),
296
+ "name": conv.get("name", "Chưa có tên"),
297
+ "bot_messages": [msg["text"] for msg in conv.get("messages", []) if msg.get("sender") == "bot"], # Chỉ lấy text của bot
298
+ "created_at": conv.get("created_at", "Không rõ ngày")
299
+ } for conv in conversations])
300
+
301
+ @app.route('/get_messages', methods=['GET'])
302
+ def get_messages():
303
+ conversation_id = request.args.get("conversation_id")
304
+
305
+ if not conversation_id:
306
+ return jsonify({"error": "Thiếu conversation_id"}), 400
307
+
308
+ # Truy vấn cuộc trò chuyện theo conversation_id
309
+ conversation = mongo.db.chat_history.find_one(
310
+ {"conversation_id": conversation_id},
311
+ {"_id": 0, "messages": 1,"name":1, "timestamp":1}
312
+ )
313
+
314
+ if conversation:
315
+ return jsonify(conversation) # Trả về danh sách tin nhắn
316
+ else:
317
+ return jsonify({"error": "Không tìm thấy cuộc trò chuyện"}), 404
318
+
319
+
320
+
321
+ @app.route("/")
322
+ def index():
323
+ return render_template('index.html')
324
+
325
+ @app.route('/register', methods=['GET', 'POST'])
326
+ def register():
327
+ if request.method == 'POST':
328
+ fullname = request.form['fullname']
329
+ username = request.form['username']
330
+ password = request.form['password']
331
+ hash_password = bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt())
332
+
333
+ # Insert user into the 'user' collection
334
+ mongo.db.user.insert_one({'fullname': fullname,'username': username, 'password': hash_password})
335
+ return redirect(url_for('login'))
336
+ return render_template('register.html')
337
+
338
+ @app.route('/login', methods=['GET', 'POST'])
339
+ def login():
340
+ if request.method == 'POST':
341
+ username = request.form['username']
342
+ password = request.form['password']
343
+
344
+ # Find user in the 'user' collection
345
+ user = mongo.db.user.find_one({'username': username})
346
+
347
+ # Kiểm tra nếu người dùng tồn tại và mật khẩu đúng
348
+ if user and bcrypt.checkpw(password.encode('utf-8'), user['password']):
349
+ # Lưu fullname của người dùng vào session
350
+ session['user'] = user.get('fullname', 'Unknown User') # Đảm bảo lấy giá trị 'fullname' nếu có
351
+ return redirect(url_for('dashboard'))
352
+ else:
353
+ return 'Invalid username or password'
354
+ return render_template('login.html')
355
+
356
+ @app.route("/dashboard")
357
+ def dashboard():
358
+ if 'user' in session:
359
+ current_time = datetime.now().strftime("%d/%m/%Y %H:%M:%S")
360
+ username = session["user"]
361
+ return render_template('dashboard.html',username=username,current_time=current_time)
362
+ return redirect(url_for('login'))
363
+
364
+ @app.route('/logout')
365
+ def logout():
366
+ session.pop('user', None)
367
+ return redirect(url_for('login'))
368
+
369
+
370
+
371
+ if __name__ == "__main__":
372
+ app.run(debug=True)
config.yaml ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ gemini:
2
+ # Required: API Keys
3
+ api_keys:
4
+ - "AIzaSyBZy7wnuLbRpngTyuplRz1FNjpP8uxttVw"
5
+ - "AIzaSyCvlZ63Nkt5NpjdmxYPAsG8Qskex6usCFw"
6
+ - "AIzaSyB4BlhbVDHupKtExi59btmX5Y5Nkm0eN7g"
7
+
8
+ # Optional: Generation Settings
9
+ generation:
10
+ temperature: 0.7
11
+ top_p: 1.0
12
+ top_k: 40
13
+ max_output_tokens: 8192
14
+ stop_sequences: []
15
+ response_mime_type: "text/plain"
16
+
17
+ # Optional: Rate Limiting
18
+ rate_limits:
19
+ requests_per_minute: 60
20
+ reset_window: 60 # seconds
21
+
22
+ # Optional: Strategies
23
+ strategies:
24
+ content: "fallback" # round_robin, fallback, retry
25
+ key_rotation: "smart_cooldown" # smart_cooldown, sequential, round_robin, least_used
26
+
27
+ # Optional: Retry Settings
28
+ retry:
29
+ max_attempts: 3
30
+ delay: 30 # seconds
31
+
32
+ # Optional: Model Settings
33
+ default_model: "gemini-2.0-flash-exp"
34
+ system_instruction: null # Custom system prompt
gemini_handler.py ADDED
@@ -0,0 +1,559 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from abc import ABC, abstractmethod
2
+ import google.generativeai as genai
3
+ import time
4
+ import os
5
+ import yaml
6
+ from typing import List, Dict, Any, Optional, Tuple, Union
7
+ from enum import Enum
8
+ from dataclasses import dataclass
9
+ from itertools import cycle
10
+ from pathlib import Path
11
+
12
+ @dataclass
13
+ class GenerationConfig:
14
+ """Configuration for model generation parameters."""
15
+ temperature: float = 1.0
16
+ top_p: float = 1.0
17
+ top_k: int = 40
18
+ max_output_tokens: int = 8192
19
+ stop_sequences: Optional[List[str]] = None
20
+ response_mime_type: str = "text/plain"
21
+
22
+ def to_dict(self) -> Dict[str, Any]:
23
+ """Convert config to dictionary, excluding None values."""
24
+ return {k: v for k, v in self.__dict__.items() if v is not None}
25
+
26
+
27
+ @dataclass
28
+ class ModelResponse:
29
+ """Represents a standardized response from any model."""
30
+ success: bool
31
+ model: str
32
+ text: str = ""
33
+ error: str = ""
34
+ time: float = 0.0
35
+ attempts: int = 1
36
+ api_key_index: int = 0
37
+
38
+
39
+ class Strategy(Enum):
40
+ """Available content generation strategies."""
41
+ ROUND_ROBIN = "round_robin"
42
+ FALLBACK = "fallback"
43
+ RETRY = "retry"
44
+
45
+
46
+ class KeyRotationStrategy(Enum):
47
+ """Available key rotation strategies."""
48
+ SEQUENTIAL = "sequential"
49
+ ROUND_ROBIN = "round_robin"
50
+ LEAST_USED = "least_used"
51
+ SMART_COOLDOWN = "smart_cooldown"
52
+
53
+
54
+ @dataclass
55
+ class KeyStats:
56
+ """Track usage statistics for each API key."""
57
+ uses: int = 0
58
+ last_used: float = 0
59
+ failures: int = 0
60
+ rate_limited_until: float = 0
61
+
62
+
63
+ class ConfigLoader:
64
+ """Handles loading configuration from various sources."""
65
+
66
+ @staticmethod
67
+ def load_api_keys(config_path: Optional[Union[str, Path]] = None) -> List[str]:
68
+ """
69
+ Load API keys from multiple sources in priority order:
70
+ 1. YAML config file if provided
71
+ 2. Environment variables (GEMINI_API_KEYS as comma-separated string)
72
+ 3. Single GEMINI_API_KEY environment variable
73
+ """
74
+ # Try loading from YAML config
75
+ if config_path:
76
+ try:
77
+ with open(config_path, 'r') as f:
78
+ config = yaml.safe_load(f)
79
+ if config and 'gemini' in config and 'api_keys' in config['gemini']:
80
+ keys = config['gemini']['api_keys']
81
+ if isinstance(keys, list) and all(isinstance(k, str) for k in keys):
82
+ return keys
83
+ except Exception as e:
84
+ print(f"Warning: Failed to load config from {config_path}: {e}")
85
+
86
+ # Try loading from GEMINI_API_KEYS environment variable
87
+ api_keys_str = os.getenv('GEMINI_API_KEYS')
88
+ if api_keys_str:
89
+ keys = [k.strip() for k in api_keys_str.split(',') if k.strip()]
90
+ if keys:
91
+ return keys
92
+
93
+ # Try loading single API key
94
+ single_key = os.getenv('GEMINI_API_KEY')
95
+ if single_key:
96
+ return [single_key]
97
+
98
+ raise ValueError(
99
+ "No API keys found. Please provide keys via config file, "
100
+ "GEMINI_API_KEYS environment variable (comma-separated), "
101
+ "or GEMINI_API_KEY environment variable."
102
+ )
103
+
104
+
105
+ class ModelConfig:
106
+ """Configuration for model settings."""
107
+ def __init__(self):
108
+ self.models = [
109
+ "gemini-2.0-flash-exp",
110
+ "gemini-1.5-pro",
111
+ "learnlm-1.5-pro-experimental",
112
+ "gemini-exp-1206",
113
+ "gemini-exp-1121",
114
+ "gemini-exp-1114",
115
+ "gemini-2.0-flash-thinking-exp-1219",
116
+ "gemini-1.5-flash"
117
+ ]
118
+ self.max_retries = 3
119
+ self.retry_delay = 30
120
+ self.default_model = "gemini-2.0-flash-exp"
121
+
122
+
123
+ class KeyRotationManager:
124
+ """Enhanced key rotation manager with multiple strategies."""
125
+ def __init__(
126
+ self,
127
+ api_keys: List[str],
128
+ strategy: KeyRotationStrategy = KeyRotationStrategy.ROUND_ROBIN,
129
+ rate_limit: int = 60,
130
+ reset_window: int = 60
131
+ ):
132
+ if not api_keys:
133
+ raise ValueError("At least one API key must be provided")
134
+
135
+ self.api_keys = api_keys
136
+ self.strategy = strategy
137
+ self.rate_limit = rate_limit
138
+ self.reset_window = reset_window
139
+
140
+ # Initialize tracking
141
+ self.key_stats = {i: KeyStats() for i in range(len(api_keys))}
142
+ self._key_cycle = cycle(range(len(api_keys)))
143
+ self.current_index = 0
144
+
145
+ def _is_key_available(self, key_index: int) -> bool:
146
+ """Check if a key is available based on rate limits and cooldown."""
147
+ stats = self.key_stats[key_index]
148
+ current_time = time.time()
149
+
150
+ if current_time < stats.rate_limited_until:
151
+ return False
152
+
153
+ if current_time - stats.last_used > self.reset_window:
154
+ stats.uses = 0
155
+
156
+ return stats.uses < self.rate_limit
157
+
158
+ def _get_sequential_key(self) -> Tuple[str, int]:
159
+ """Get next key using sequential strategy."""
160
+ start_index = self.current_index
161
+
162
+ while True:
163
+ if self._is_key_available(self.current_index):
164
+ key_index = self.current_index
165
+ self.current_index = (self.current_index + 1) % len(self.api_keys)
166
+ return self.api_keys[key_index], key_index
167
+
168
+ self.current_index = (self.current_index + 1) % len(self.api_keys)
169
+ if self.current_index == start_index:
170
+ self._handle_all_keys_busy()
171
+
172
+ def _get_round_robin_key(self) -> Tuple[str, int]:
173
+ """Get next key using round-robin strategy."""
174
+ start_index = next(self._key_cycle)
175
+ current_index = start_index
176
+
177
+ while True:
178
+ if self._is_key_available(current_index):
179
+ return self.api_keys[current_index], current_index
180
+
181
+ current_index = next(self._key_cycle)
182
+ if current_index == start_index:
183
+ self._handle_all_keys_busy()
184
+
185
+ def _get_least_used_key(self) -> Tuple[str, int]:
186
+ """Get key with lowest usage count."""
187
+ while True:
188
+ available_keys = [
189
+ (idx, stats) for idx, stats in self.key_stats.items()
190
+ if self._is_key_available(idx)
191
+ ]
192
+
193
+ if available_keys:
194
+ key_index, _ = min(available_keys, key=lambda x: x[1].uses)
195
+ return self.api_keys[key_index], key_index
196
+
197
+ self._handle_all_keys_busy()
198
+
199
+ def _get_smart_cooldown_key(self) -> Tuple[str, int]:
200
+ """Get key using smart cooldown strategy."""
201
+ while True:
202
+ current_time = time.time()
203
+ available_keys = [
204
+ (idx, stats) for idx, stats in self.key_stats.items()
205
+ if current_time >= stats.rate_limited_until and self._is_key_available(idx)
206
+ ]
207
+
208
+ if available_keys:
209
+ key_index, _ = min(
210
+ available_keys,
211
+ key=lambda x: (x[1].failures, -(current_time - x[1].last_used))
212
+ )
213
+ return self.api_keys[key_index], key_index
214
+
215
+ self._handle_all_keys_busy()
216
+
217
+ def _handle_all_keys_busy(self) -> None:
218
+ """Handle situation when all keys are busy."""
219
+ current_time = time.time()
220
+ any_reset = False
221
+
222
+ for idx, stats in self.key_stats.items():
223
+ if current_time - stats.last_used > self.reset_window:
224
+ stats.uses = 0
225
+ any_reset = True
226
+
227
+ if not any_reset:
228
+ time.sleep(1)
229
+
230
+ def get_next_key(self) -> Tuple[str, int]:
231
+ """Get next available API key based on selected strategy."""
232
+ strategy_methods = {
233
+ KeyRotationStrategy.SEQUENTIAL: self._get_sequential_key,
234
+ KeyRotationStrategy.ROUND_ROBIN: self._get_round_robin_key,
235
+ KeyRotationStrategy.LEAST_USED: self._get_least_used_key,
236
+ KeyRotationStrategy.SMART_COOLDOWN: self._get_smart_cooldown_key
237
+ }
238
+
239
+ method = strategy_methods.get(self.strategy)
240
+ if not method:
241
+ raise ValueError(f"Unknown strategy: {self.strategy}")
242
+
243
+ api_key, key_index = method()
244
+
245
+ stats = self.key_stats[key_index]
246
+ stats.uses += 1
247
+ stats.last_used = time.time()
248
+
249
+ return api_key, key_index
250
+
251
+ def mark_success(self, key_index: int) -> None:
252
+ """Mark successful API call."""
253
+ if 0 <= key_index < len(self.api_keys):
254
+ self.key_stats[key_index].failures = 0
255
+
256
+ def mark_rate_limited(self, key_index: int) -> None:
257
+ """Mark API key as rate limited."""
258
+ if 0 <= key_index < len(self.api_keys):
259
+ stats = self.key_stats[key_index]
260
+ stats.failures += 1
261
+ stats.rate_limited_until = time.time() + self.reset_window
262
+ stats.uses = self.rate_limit
263
+
264
+
265
+ class ResponseHandler:
266
+ """Handles and processes model responses."""
267
+ @staticmethod
268
+ def process_response(
269
+ response: Any,
270
+ model_name: str,
271
+ start_time: float,
272
+ key_index: int
273
+ ) -> ModelResponse:
274
+ """Process and validate model response."""
275
+ try:
276
+ if hasattr(response, 'candidates') and response.candidates:
277
+ finish_reason = response.candidates[0].finish_reason
278
+ if finish_reason == 4: # Copyright material
279
+ return ModelResponse(
280
+ success=False,
281
+ model=model_name,
282
+ error='Copyright material detected in response',
283
+ time=time.time() - start_time,
284
+ api_key_index=key_index
285
+ )
286
+
287
+ return ModelResponse(
288
+ success=True,
289
+ model=model_name,
290
+ text=response.text,
291
+ time=time.time() - start_time,
292
+ api_key_index=key_index
293
+ )
294
+ except Exception as e:
295
+ if "The `response.text` quick accessor requires the response to contain a valid `Part`" in str(e):
296
+ return ModelResponse(
297
+ success=False,
298
+ model=model_name,
299
+ error='No valid response parts available',
300
+ time=time.time() - start_time,
301
+ api_key_index=key_index
302
+ )
303
+ raise
304
+
305
+
306
+ class ContentStrategy(ABC):
307
+ """Abstract base class for content generation strategies."""
308
+ def __init__(
309
+ self,
310
+ config: ModelConfig,
311
+ key_manager: KeyRotationManager,
312
+ system_instruction: Optional[str] = None,
313
+ generation_config: Optional[GenerationConfig] = None
314
+ ):
315
+ self.config = config
316
+ self.key_manager = key_manager
317
+ self.system_instruction = system_instruction
318
+ self.generation_config = generation_config or GenerationConfig()
319
+
320
+ @abstractmethod
321
+ def generate(self, prompt: str, model_name: str) -> ModelResponse:
322
+ """Generate content using the specific strategy."""
323
+ pass
324
+
325
+ def _try_generate(self, model_name: str, prompt: str, start_time: float) -> ModelResponse:
326
+ """Helper method for generating content with key rotation."""
327
+ api_key, key_index = self.key_manager.get_next_key()
328
+ try:
329
+ genai.configure(api_key=api_key)
330
+ model = genai.GenerativeModel(
331
+ model_name=model_name,
332
+ generation_config=self.generation_config.to_dict(),
333
+ system_instruction=self.system_instruction
334
+ )
335
+ response = model.generate_content(prompt)
336
+
337
+ result = ResponseHandler.process_response(response, model_name, start_time, key_index)
338
+ if result.success:
339
+ self.key_manager.mark_success(key_index)
340
+ return result
341
+
342
+ except Exception as e:
343
+ if "429" in str(e):
344
+ self.key_manager.mark_rate_limited(key_index)
345
+ return ModelResponse(
346
+ success=False,
347
+ model=model_name,
348
+ error=str(e),
349
+ time=time.time() - start_time,
350
+ api_key_index=key_index
351
+ )
352
+
353
+
354
+ class RoundRobinStrategy(ContentStrategy):
355
+ """Round robin implementation of content generation."""
356
+ def __init__(self, *args, **kwargs):
357
+ super().__init__(*args, **kwargs)
358
+ self._current_index = 0
359
+
360
+ def _get_next_model(self) -> str:
361
+ """Get next model in round-robin fashion."""
362
+ model = self.config.models[self._current_index]
363
+ self._current_index = (self._current_index + 1) % len(self.config.models)
364
+ return model
365
+
366
+ def generate(self, prompt: str, _: str) -> ModelResponse:
367
+ start_time = time.time()
368
+
369
+ for _ in range(len(self.config.models)):
370
+ model_name = self._get_next_model()
371
+ result = self._try_generate(model_name, prompt, start_time)
372
+ if result.success or 'Copyright' in result.error:
373
+ return result
374
+
375
+ return ModelResponse(
376
+ success=False,
377
+ model='all_models_failed',
378
+ error='All models failed (rate limited or copyright issues)',
379
+ time=time.time() - start_time
380
+ )
381
+
382
+
383
+ class FallbackStrategy(ContentStrategy):
384
+ """Fallback implementation of content generation."""
385
+ def generate(self, prompt: str, start_model: str) -> ModelResponse:
386
+ start_time = time.time()
387
+
388
+ try:
389
+ start_index = self.config.models.index(start_model)
390
+ except ValueError:
391
+ return ModelResponse(
392
+ success=False,
393
+ model=start_model,
394
+ error=f"Model {start_model} not found in available models",
395
+ time=time.time() - start_time
396
+ )
397
+
398
+ for model_name in self.config.models[start_index:]:
399
+ result = self._try_generate(model_name, prompt, start_time)
400
+ if result.success or 'Copyright' in result.error:
401
+ return result
402
+
403
+ return ModelResponse(
404
+ success=False,
405
+ model='all_models_failed',
406
+ error='All models failed (rate limited or copyright issues)',
407
+ time=time.time() - start_time
408
+ )
409
+
410
+
411
+ class RetryStrategy(ContentStrategy):
412
+ """Retry implementation of content generation."""
413
+ def generate(self, prompt: str, model_name: str) -> ModelResponse:
414
+ start_time = time.time()
415
+
416
+ for attempt in range(self.config.max_retries):
417
+ result = self._try_generate(model_name, prompt, start_time)
418
+ result.attempts = attempt + 1
419
+
420
+ if result.success or 'Copyright' in result.error:
421
+ return result
422
+
423
+ if attempt < self.config.max_retries - 1:
424
+ print(f"Error encountered. Waiting {self.config.retry_delay}s... "
425
+ f"(Attempt {attempt + 1}/{self.config.max_retries})")
426
+ time.sleep(self.config.retry_delay)
427
+
428
+ return ModelResponse(
429
+ success=False,
430
+ model=model_name,
431
+ error='Max retries exceeded',
432
+ time=time.time() - start_time,
433
+ attempts=self.config.max_retries
434
+ )
435
+
436
+
437
+ class GeminiHandler:
438
+ """Main handler class for Gemini API interactions."""
439
+ def __init__(
440
+ self,
441
+ api_keys: Optional[List[str]] = None,
442
+ config_path: Optional[Union[str, Path]] = None,
443
+ content_strategy: Strategy = Strategy.ROUND_ROBIN,
444
+ key_strategy: KeyRotationStrategy = KeyRotationStrategy.ROUND_ROBIN,
445
+ system_instruction: Optional[str] = None,
446
+ generation_config: Optional[GenerationConfig] = None
447
+ ):
448
+ """
449
+ Initialize GeminiHandler with flexible configuration options.
450
+
451
+ Args:
452
+ api_keys: Optional list of API keys
453
+ config_path: Optional path to YAML config file
454
+ content_strategy: Strategy for content generation
455
+ key_strategy: Strategy for key rotation
456
+ system_instruction: Optional system instruction
457
+ generation_config: Optional generation configuration
458
+ """
459
+ # Load API keys from provided list or config sources
460
+ self.api_keys = api_keys or ConfigLoader.load_api_keys(config_path)
461
+
462
+ self.config = ModelConfig()
463
+ self.key_manager = KeyRotationManager(
464
+ api_keys=self.api_keys,
465
+ strategy=key_strategy,
466
+ rate_limit=60,
467
+ reset_window=60
468
+ )
469
+ self.system_instruction = system_instruction
470
+ self.generation_config = generation_config
471
+ self._strategy = self._create_strategy(content_strategy)
472
+
473
+ def _create_strategy(self, strategy: Strategy) -> ContentStrategy:
474
+ """Factory method to create appropriate strategy."""
475
+ strategies = {
476
+ Strategy.ROUND_ROBIN: RoundRobinStrategy,
477
+ Strategy.FALLBACK: FallbackStrategy,
478
+ Strategy.RETRY: RetryStrategy
479
+ }
480
+
481
+ strategy_class = strategies.get(strategy)
482
+ if not strategy_class:
483
+ raise ValueError(f"Unknown strategy: {strategy}")
484
+
485
+ return strategy_class(
486
+ config=self.config,
487
+ key_manager=self.key_manager,
488
+ system_instruction=self.system_instruction,
489
+ generation_config=self.generation_config
490
+ )
491
+
492
+ def generate_content(
493
+ self,
494
+ prompt: str,
495
+ model_name: Optional[str] = None,
496
+ return_stats: bool = False
497
+ ) -> Dict[str, Any]:
498
+ """
499
+ Generate content using the selected strategies.
500
+
501
+ Args:
502
+ prompt: The input prompt for content generation
503
+ model_name: Optional specific model to use (default: None)
504
+ return_stats: Whether to include key usage statistics (default: False)
505
+
506
+ Returns:
507
+ Dictionary containing generation results and optionally key statistics
508
+ """
509
+ if not model_name:
510
+ model_name = self.config.default_model
511
+
512
+ response = self._strategy.generate(prompt, model_name)
513
+ result = response.__dict__
514
+
515
+ if return_stats:
516
+ result["key_stats"] = {
517
+ idx: {
518
+ "uses": stats.uses,
519
+ "last_used": stats.last_used,
520
+ "failures": stats.failures,
521
+ "rate_limited_until": stats.rate_limited_until
522
+ }
523
+ for idx, stats in self.key_manager.key_stats.items()
524
+ }
525
+
526
+ return result
527
+
528
+ def get_key_stats(self, key_index: Optional[int] = None) -> Dict[int, Dict[str, Any]]:
529
+ """
530
+ Get current key usage statistics.
531
+
532
+ Args:
533
+ key_index: Optional specific key index to get stats for
534
+
535
+ Returns:
536
+ Dictionary of key statistics
537
+ """
538
+ if key_index is not None:
539
+ if 0 <= key_index < len(self.key_manager.api_keys):
540
+ stats = self.key_manager.key_stats[key_index]
541
+ return {
542
+ key_index: {
543
+ "uses": stats.uses,
544
+ "last_used": stats.last_used,
545
+ "failures": stats.failures,
546
+ "rate_limited_until": stats.rate_limited_until
547
+ }
548
+ }
549
+ raise ValueError(f"Invalid key index: {key_index}")
550
+
551
+ return {
552
+ idx: {
553
+ "uses": stats.uses,
554
+ "last_used": stats.last_used,
555
+ "failures": stats.failures,
556
+ "rate_limited_until": stats.rate_limited_until
557
+ }
558
+ for idx, stats in self.key_manager.key_stats.items()
559
+ }
requirements.txt ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Flask
2
+ flask_pymongo
3
+ flask_cors
4
+ bcrypt
5
+ sentence-transformers
6
+ faiss-cpu
7
+ numpy
8
+ pandas
9
+ google-generativeai
10
+ python-dotenv
11
+ pymongo
12
+ gunicorn
source/faiss_index_drug.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:ea0955d2743718823cbf92570fc5ba7004bc0d6ba9457ed88df8df65becc2c66
3
+ size 568365
source/faiss_index_vn.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:40ea00c1b969a484fa7c4f0eae61d569f8f663c1140b5fd6bbb950eb08184d31
3
+ size 1136685
source/merged_df.csv ADDED
The diff for this file is too large to render. See raw diff
 
source/merged_df_vn.csv ADDED
The diff for this file is too large to render. See raw diff
 
source/sentence_embeddings.pkl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:a561406ed04d8a7227231108dfd66ba8a7cf5a9da4a5a9d6e598de7438ab67c4
3
+ size 568483
source/sentence_embeddings_vn.pkl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:38fad112ffbfd8eb359e55b5e308ffb9feb5e19ee8391779db8619ab6a3a87e4
3
+ size 1136803
static/style.css ADDED
@@ -0,0 +1,267 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ :root {
2
+ --primary: #202123;
3
+ --overhover: hsla(240, 9%, 59%, 0.1);
4
+ }
5
+
6
+ * {
7
+ margin: 0;
8
+ padding: 0;
9
+ font-family: courier;
10
+ }
11
+
12
+ nav {
13
+ height: 50px;
14
+ background: var(--primary);
15
+ color: white;
16
+ display: none;
17
+ justify-content: space-between;
18
+ align-items: center;
19
+ position: fixed;
20
+ width: 100%;
21
+ }
22
+
23
+ nav div {
24
+ margin: 0 20px;
25
+ display: inline-block;
26
+ overflow: hidden;
27
+ white-space: nowrap;
28
+ text-overflow: ellipsis;
29
+ }
30
+
31
+ .ham-menu {
32
+ background: var(--primary);
33
+ color: white;
34
+ border: none;
35
+ cursor: pointer;
36
+ }
37
+
38
+ .ham-menu span {
39
+ display: block;
40
+ width: 25px;
41
+ height: 3px;
42
+ margin-bottom: 5px;
43
+ position: relative;
44
+ background: black;
45
+ border-radius: 3px;
46
+ z-index: 1;
47
+ transform-origin: 4px 0px;
48
+ transition: transform 0.5s cubic-bezier(0.77, 0.2, 0.05, 1),
49
+ background 0.5s cubic-bezier(0.77, 0.2, 0.05, 1), opacity 0.55s ease;
50
+ }
51
+
52
+ .ham-menu:hover span {
53
+ background: white;
54
+ }
55
+
56
+ #main {
57
+ display: flex;
58
+ }
59
+
60
+ /*Side Nav*/
61
+ #sidenav {
62
+ height: 100vh;
63
+ width: 20rem;
64
+ background-color: var(--primary);
65
+ font-size: 1rem;
66
+ color: white;
67
+ position: fixed;
68
+ left: 0;
69
+ top: 0;
70
+ margin: 0;
71
+ z-index: 11;
72
+ }
73
+
74
+ #sidenav .sidenav-content {
75
+ padding: 0.5rem;
76
+ }
77
+
78
+ #close {
79
+ position: absolute;
80
+ right: -40px;
81
+ background: var(--primary);
82
+ color: white;
83
+ border: 1px solid grey;
84
+ border-radius: 5px;
85
+ cursor: pointer;
86
+ display: none;
87
+ width: 40px;
88
+ height: 40px;
89
+ }
90
+ #close span {
91
+ width: 40px;
92
+ height: 40px;
93
+ position: relative;
94
+ }
95
+ #close span:before,
96
+ #close span:after {
97
+ position: absolute;
98
+ content: " ";
99
+ top: -16px;
100
+ left: -1px;
101
+ height: 33px;
102
+ width: 3px;
103
+ background-color: black;
104
+ }
105
+ #close span:before {
106
+ transform: rotate(45deg);
107
+ }
108
+ #close span:after {
109
+ transform: rotate(-45deg);
110
+ }
111
+
112
+ #close:hover span:after,
113
+ #close:hover span:before {
114
+ background: white;
115
+ }
116
+
117
+ #new-chat-btn {
118
+ background: var(--primary);
119
+ color: white;
120
+ border: 1px solid grey;
121
+ border-radius: 5px;
122
+ width: 100%;
123
+ cursor: pointer;
124
+ text-align: start;
125
+ padding: 0.7rem;
126
+ font-size: inherit;
127
+ }
128
+
129
+ #new-chat-btn:hover {
130
+ background: var(--overhover);
131
+ }
132
+
133
+ .new-chat {
134
+ cursor: pointer;
135
+ }
136
+
137
+ .saved-chats {
138
+ margin: 0.5rem 0;
139
+ }
140
+
141
+ .saved-chats p {
142
+ padding: 0.8rem;
143
+ margin: 3px 0;
144
+ border-radius: 5px;
145
+ cursor: pointer;
146
+ }
147
+
148
+ .saved-chats p:hover {
149
+ background: var(--overhover);
150
+ }
151
+
152
+ .saved-chats .selected {
153
+ padding: 0.8rem;
154
+ background: var(--overhover);
155
+ margin: 3px 0;
156
+ border-radius: 5px;
157
+ }
158
+
159
+ .config {
160
+ margin: 0.5rem 0;
161
+ position: absolute;
162
+ bottom: 0;
163
+ width: inherit;
164
+ background: var(--primary);
165
+ }
166
+
167
+ .config p {
168
+ padding: 0.7rem;
169
+ cursor: pointer;
170
+ margin: 5px 0;
171
+ border-radius: 5px;
172
+ }
173
+
174
+ .config p:hover {
175
+ background: var(--overhover);
176
+ }
177
+
178
+ .config hr {
179
+ width: 19rem;
180
+ }
181
+
182
+ /*Main Content Body*/
183
+ #content-body {
184
+ height: 100vh;
185
+ background-color: white;
186
+ text-align: center;
187
+ margin-left: 20rem;
188
+ flex: 1;
189
+ }
190
+
191
+ #messages {
192
+ padding-bottom: 100px;
193
+ }
194
+
195
+ .message-div {
196
+ display: flex;
197
+ margin: 10px auto;
198
+ max-width: 800px;
199
+ justify-content: start;
200
+ }
201
+
202
+ .user-message {
203
+ background: ;
204
+ padding: 10px;
205
+ }
206
+
207
+ .gpt-message {
208
+ background: rgba(247, 247, 248);
209
+ padding: 10px;
210
+ }
211
+
212
+ .message-profile-pic {
213
+ margin-right: 20px;
214
+ }
215
+
216
+ .message-content {
217
+ text-align: start;
218
+ margin-top: 5px;
219
+ }
220
+
221
+ .message-content p {
222
+ margin-bottom: 20px;
223
+ }
224
+
225
+ #chat-section {
226
+ width: -webkit-fill-available;
227
+ height: 130px;
228
+ background-image: linear-gradient(to bottom, transparent 10%, white 90%);
229
+ position: fixed;
230
+ bottom: 0;
231
+ }
232
+
233
+ #chat-section div {
234
+ max-width: 800px;
235
+ margin: 2rem auto;
236
+ padding: 0 20px;
237
+ }
238
+
239
+ #chat-section input {
240
+ padding: 0.9rem;
241
+ border-radius: 5px;
242
+ border: 0.1px solid grey;
243
+ width: 90%;
244
+ font-size: 20px;
245
+ box-shadow: 0 0 7px 0px grey;
246
+ }
247
+
248
+ #chat-section input:focus {
249
+ outline: none;
250
+ }
251
+
252
+ @media screen and (max-width: 800px) {
253
+ nav {
254
+ display: flex;
255
+ }
256
+ #close {
257
+ display: block;
258
+ }
259
+ #sidenav {
260
+ position: fixed;
261
+ left: -400px;
262
+ }
263
+ #content-body {
264
+ margin: 50px 0 0 0;
265
+ }
266
+ }
267
+
templates/dashboard.html ADDED
The diff for this file is too large to render. See raw diff
 
templates/demo.html ADDED
The diff for this file is too large to render. See raw diff
 
templates/index.html ADDED
@@ -0,0 +1,356 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ <!DOCTYPE html>
3
+ <html>
4
+
5
+ <head>
6
+ <!-- Basic -->
7
+ <meta charset="utf-8" />
8
+ <meta http-equiv="X-UA-Compatible" content="IE=edge" />
9
+ <!-- Mobile Metas -->
10
+ <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
11
+ <!-- Site Metas -->
12
+ <meta name="keywords" content="" />
13
+ <meta name="description" content="" />
14
+ <meta name="author" content="" />
15
+ <base href="https://themewagon.github.io/orthoc/">
16
+ <link rel="shortcut icon" href="images/favicon.png" type="">
17
+
18
+ <title> Orthoc </title>
19
+
20
+ <!-- bootstrap core css -->
21
+ <link rel="stylesheet" type="text/css" href="css/bootstrap.css" />
22
+
23
+ <!-- fonts style -->
24
+ <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700;900&display=swap" rel="stylesheet">
25
+
26
+ <!--owl slider stylesheet -->
27
+ <link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/OwlCarousel2/2.3.4/assets/owl.carousel.min.css" />
28
+
29
+ <!-- font awesome style -->
30
+ <link href="css/font-awesome.min.css" rel="stylesheet" />
31
+
32
+ <!-- Custom styles for this template -->
33
+ <link href="css/style.css" rel="stylesheet" />
34
+ <!-- responsive style -->
35
+ <link href="css/responsive.css" rel="stylesheet" />
36
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css" integrity="sha512-Evv84Mr4kqVGRNSgIGL/F/aIDqQb7xQ2vcrdIwxfjThSH8CSR7PBEakCr51Ck+w+/U6swU2Im1vVX0SVk9ABhg==" crossorigin="anonymous" referrerpolicy="no-referrer" />
37
+
38
+ </head>
39
+
40
+ <body>
41
+
42
+ <div class="hero_area">
43
+
44
+ <div class="hero_bg_box">
45
+ <img src="images/hero-bg.png" alt="">
46
+ </div>
47
+
48
+ <!-- header section strats -->
49
+ <header class="header_section">
50
+ <div class="container">
51
+ <nav class="navbar navbar-expand-lg custom_nav-container ">
52
+ <a class="navbar-brand" href="index.html">
53
+ <span>
54
+ Orthoc
55
+ </span>
56
+ </a>
57
+
58
+ <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
59
+ <span class=""> </span>
60
+ </button>
61
+
62
+ <div class="collapse navbar-collapse" id="navbarSupportedContent">
63
+ <ul class="navbar-nav">
64
+ <li class="nav-item active">
65
+ <a class="nav-link" href="http://127.0.0.1:5000/">Trang chủ <span class="sr-only">(current)</span></a>
66
+ </li>
67
+ <li class="nav-item">
68
+ <a class="nav-link" href="http://127.0.0.1:5000/register"> Đăng ký </a>
69
+ </li>
70
+ <li class="nav-item">
71
+ <a class="nav-link" href="http://127.0.0.1:5000/login">Đăng nhập</a>
72
+ </li>
73
+ <form class="form-inline">
74
+ <button class="btn my-2 my-sm-0 nav_search-btn" type="submit">
75
+ <i class="fa fa-search" aria-hidden="true"></i>
76
+ </button>
77
+ </form>
78
+ </ul>
79
+ </div>
80
+ </nav>
81
+ </div>
82
+ </header>
83
+ <!-- end header section -->
84
+ <!-- slider section -->
85
+ <section class="slider_section ">
86
+ <div id="customCarousel1" class="carousel slide" data-ride="carousel">
87
+ <div class="carousel-inner">
88
+ <div class="carousel-item active">
89
+ <div class="container ">
90
+ <div class="row">
91
+ <div class="col-md-7">
92
+ <div class="detail-box">
93
+ <h1>
94
+ Hệ Thống Gợi Ý Thuốc Dựa Trên Triệu Chứng
95
+ </h1>
96
+ <p>
97
+ Hệ thống được phát triển hướng tới việc cải thiện khả năng cá nhân hóa trong kê đơn thuốc, vốn là một thách thức lớn đối với các phương pháp truyền thống dựa trên triệu chứng bề mặt. Thay vì chỉ dừng lại ở việc phân tích các triệu chứng đơn lẻ, hệ thống sẽ tích hợp thông tin từ các nguồn dữ liệu đa chiều để đưa ra những khuyến nghị thuốc tối ưu, phù hợp với từng trường hợp cụ thể.
98
+ </p>
99
+ <div class="btn-box">
100
+ <a href="http://127.0.0.1:5000/login" class="btn1">
101
+ Đăng nhập
102
+ </a>
103
+ </div>
104
+ </div>
105
+ </div>
106
+ </div>
107
+ </div>
108
+ </div>
109
+ <!-- <div class="carousel-item ">
110
+ <div class="container ">
111
+ <div class="row">
112
+ <div class="col-md-7">
113
+ <div class="detail-box">
114
+ <h1>
115
+ We Provide Best Healthcare
116
+ </h1>
117
+ <p>
118
+ Explicabo esse amet tempora quibusdam laudantium, laborum eaque magnam fugiat hic? Esse dicta aliquid error repudiandae earum suscipit fugiat molestias, veniam, vel architecto veritatis delectus repellat modi impedit sequi.
119
+ </p>
120
+ <div class="btn-box">
121
+ <a href="" class="btn1">
122
+ Read More
123
+ </a>
124
+ </div>
125
+ </div>
126
+ </div>
127
+ </div>
128
+ </div>
129
+ </div>
130
+ <div class="carousel-item">
131
+ <div class="container ">
132
+ <div class="row">
133
+ <div class="col-md-7">
134
+ <div class="detail-box">
135
+ <h1>
136
+ We Provide Best Healthcare
137
+ </h1>
138
+ <p>
139
+ Explicabo esse amet tempora quibusdam laudantium, laborum eaque magnam fugiat hic? Esse dicta aliquid error repudiandae earum suscipit fugiat molestias, veniam, vel architecto veritatis delectus repellat modi impedit sequi.
140
+ </p>
141
+ <div class="btn-box">
142
+ <a href="" class="btn1">
143
+ Read More
144
+ </a>
145
+ </div>
146
+ </div>
147
+ </div>
148
+ </div>
149
+ </div>
150
+ </div> -->
151
+ </div>
152
+ <!-- <ol class="carousel-indicators">
153
+ <li data-target="#customCarousel1" data-slide-to="0" class="active"></li>
154
+ <li data-target="#customCarousel1" data-slide-to="1"></li>
155
+ <li data-target="#customCarousel1" data-slide-to="2"></li>
156
+ </ol> -->
157
+ </div>
158
+
159
+ </section>
160
+ <!-- end slider section -->
161
+ </div>
162
+
163
+
164
+ <!-- department section -->
165
+
166
+ <section class="department_section layout_padding">
167
+ <div class="department_container">
168
+ <div class="container ">
169
+ <div class="heading_container heading_center">
170
+ <h2>
171
+ CHUYÊN ĐỀ NGHIÊN CỨU 2
172
+ </h2>
173
+ <p>
174
+ Mô hình gợi ý thuốc dựa trên triệu chứng
175
+ </p>
176
+ </div>
177
+ <div class="row">
178
+ <div class="col-md-3">
179
+ <div class="box ">
180
+ <div class="img-box">
181
+ <img src="images/s1.png" alt="">
182
+ </div>
183
+ <div class="detail-box">
184
+ <h5>
185
+ Mô tả đề tài
186
+ </h5>
187
+ <p>
188
+ Mô hình gợi ý thuốc dựa trên triệu chứng giúp hỗ trợ chẩn đoán và tối ưu hóa điều trị bằng trí tuệ nhân tạo.
189
+ </p>
190
+ </div>
191
+ </div>
192
+ </div>
193
+ <div class="col-md-3">
194
+ <div class="box ">
195
+ <div class="img-box">
196
+ <img src="images/s2.png" alt="">
197
+ </div>
198
+ <div class="detail-box">
199
+ <h5>
200
+ Mục tiêu thực hiện
201
+ </h5>
202
+ <p>
203
+ Tạo ra mô hình gợi ý thuốc dựa trên triệu chứng bằng trí tuệ nhân tạo, nhằm hỗ trợ bác sĩ và bệnh nhân.
204
+ </p>
205
+ </div>
206
+ </div>
207
+ </div>
208
+ <div class="col-md-3">
209
+ <div class="box ">
210
+ <div class="img-box">
211
+ <img src="images/s3.png" alt="">
212
+ </div>
213
+ <div class="detail-box">
214
+ <h5>
215
+ Đối tượng nghiên cứu
216
+ </h5>
217
+ <p>
218
+ Mô hình gợi ý thuốc dựa trên triệu chứng, sử dụng trí tuệ nhân tạo trong hỗ trợ chẩn đoán và điều trị.
219
+ </p>
220
+ </div>
221
+ </div>
222
+ </div>
223
+ <div class="col-md-3">
224
+ <div class="box ">
225
+ <div class="img-box">
226
+ <img src="images/s4.png" alt="">
227
+ </div>
228
+ <div class="detail-box">
229
+ <h5>
230
+ Phạm vi nghiên cứu
231
+ </h5>
232
+ <p>
233
+ Tập trung vào việc thu thập và xử lý dữ liệu triệu chứng - thuốc từ nguồn y khoa đáng tin.
234
+ </p>
235
+ </div>
236
+ </div>
237
+ </div>
238
+ </div>
239
+ <!-- <div class="btn-box">
240
+ <a href="">
241
+ View All
242
+ </a>
243
+ </div> -->
244
+ </div>
245
+ </div>
246
+ </section>
247
+
248
+ <!-- end department section -->
249
+
250
+ <!-- footer section -->
251
+ <footer class="footer_section">
252
+ <div class="container">
253
+ <div class="row">
254
+ <div class="col-md-6 col-lg-4 footer_col">
255
+ <div class="footer_contact">
256
+ <h4>
257
+ Thông tin chuyên đề
258
+ </h4>
259
+ <div class="contact_link_box">
260
+ <a href="">
261
+ <i class="fa fa-user" aria-hidden="true"></i>
262
+ <span>
263
+ TS Trần Thanh Phước
264
+ </span>
265
+ </a>
266
+ <a href="">
267
+ <i class="fa fa-users" aria-hidden="true"></i>
268
+ <span>
269
+ Nguyễn Trần Quỳnh Như - 241805005
270
+ </span>
271
+ </a>
272
+ <a href="">
273
+ <i class="fa fa-users" aria-hidden="true"></i>
274
+ <span>
275
+ Trần Thành Trung - 241805005
276
+ </span>
277
+ </a>
278
+ <a href="">
279
+ <i class="fa-solid fa-school" aria-hidden="true"></i>
280
+ <span>
281
+ Lớp: 24180501
282
+ </span>
283
+ </a>
284
+ <a href="">
285
+ <i class="fa-solid fa-calendar-days" aria-hidden="true"></i>
286
+ <span>
287
+ Khóa: 2024-2026
288
+ </span>
289
+ </a>
290
+ </div>
291
+ </div>
292
+
293
+ </div>
294
+ <div class="col-md-6 col-lg-4 footer_col">
295
+ <div class="footer_detail">
296
+ <h4>
297
+ Giới thiệu
298
+ </h4>
299
+ <p>
300
+ Hệ thống hướng tới cải thiện khả năng cá nhân hóa trong kê đơn thuốc bằng cách tích hợp dữ liệu đa chiều, thay vì chỉ dựa trên triệu chứng bề mặt, nhằm đưa ra khuyến nghị tối ưu cho từng trường hợp.
301
+ </p>
302
+ </div>
303
+ </div>
304
+ <div class="col-md-6 col-lg-3 mx-auto footer_col">
305
+ <div class="footer_link_box">
306
+ <h4>
307
+ Links
308
+ </h4>
309
+ <div class="footer_links">
310
+ <a class="active" href="http://127.0.0.1:5000/">
311
+ Trang chủ
312
+ </a>
313
+ <a class="" href="http://127.0.0.1:5000/register">
314
+ Đăng ký
315
+ </a>
316
+ <a class="" href="http://127.0.0.1:5000/login">
317
+ Đăng nhập
318
+ </a>
319
+ </div>
320
+ </div>
321
+ </div>
322
+
323
+ </div>
324
+ <div class="footer-info">
325
+ <p>
326
+ &copy; <span id="displayYear"></span> All Rights Reserved By
327
+ <a href="">QuynhNhu<br><br></a>
328
+ &copy; <span id="displayYear"></span> Distributed By
329
+ <a href="">QuynhNhu</a>
330
+ </p>
331
+
332
+ </div>
333
+ </div>
334
+ </footer>
335
+ <!-- footer section -->
336
+
337
+ <!-- jQery -->
338
+ <script type="text/javascript" src="js/jquery-3.4.1.min.js"></script>
339
+ <!-- popper js -->
340
+ <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous">
341
+ </script>
342
+ <!-- bootstrap js -->
343
+ <script type="text/javascript" src="js/bootstrap.js"></script>
344
+ <!-- owl slider -->
345
+ <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/OwlCarousel2/2.3.4/owl.carousel.min.js">
346
+ </script>
347
+ <!-- custom js -->
348
+ <script type="text/javascript" src="js/custom.js"></script>
349
+ <!-- Google Map -->
350
+ <script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyCh39n5U-4IoWpsVGUHWdqB6puEkhRLdmI&callback=myMap">
351
+ </script>
352
+ <!-- End Google Map -->
353
+
354
+ </body>
355
+
356
+ </html>
templates/login.html ADDED
@@ -0,0 +1,101 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ <!doctype html>
3
+ <html lang="en">
4
+
5
+ <head>
6
+
7
+ <meta charset="utf-8" />
8
+ <title>Log in | Chaton - Responsive Bootstrap 5 Chat App</title>
9
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
10
+ <meta content="Chaton - Responsive Chat App Template in HTML. A fully featured HTML chat messenger template in Bootstrap 5" name="description" />
11
+ <meta name="keywords" content="Chaton chat template, chat, web chat template, chat status, chat template, communication, discussion, group chat, message, messenger template, status" />
12
+ <meta content="Themesdesign" name="author" />
13
+ <!-- App favicon -->
14
+ <link rel="shortcut icon" href="https://themesdesign.in/chaton/layouts/assets/images/favicon.ico" id="tabIcon">
15
+
16
+ <!-- Bootstrap Css -->
17
+ <link href="https://themesdesign.in/chaton/layouts/assets/css/bootstrap.min.css" id="bootstrap-style" rel="stylesheet" type="text/css" />
18
+ <!-- Icons Css -->
19
+ <link href="https://themesdesign.in/chaton/layouts/assets/css/icons.min.css" rel="stylesheet" type="text/css" />
20
+ <!-- App Css-->
21
+ <link href="https://themesdesign.in/chaton/layouts/assets/css/app.min.css" id="app-style" rel="stylesheet" type="text/css" />
22
+
23
+ </head>
24
+
25
+ <body>
26
+ <div class="auth-bg">
27
+ <div class="container p-0">
28
+ <div class="row justify-content-center g-0">
29
+ <div class="col-xl-9 col-lg-8">
30
+ <div class="authentication-page-content shadow-lg">
31
+ <div class="d-flex flex-column h-100 px-4 pt-4">
32
+ <div class="row justify-content-center">
33
+ <div class="col-sm-8 col-lg-6 col-xl-6">
34
+
35
+ <div class="py-md-5 py-4">
36
+
37
+ <div class="text-center mb-5">
38
+ <h3>Xin chào!</h3>
39
+ <p class="text-muted">Chào mừng đến với trang đăng nhập</p>
40
+ </div>
41
+
42
+ <form action="http://127.0.0.1:5000/login" method="POST">
43
+ <div class="mb-3">
44
+ <label for="username" class="form-label">Tên đăng nhập</label>
45
+ <input type="text" name="username" class="form-control" id="username" placeholder="Enter username">
46
+ </div>
47
+
48
+ <div class="mb-3">
49
+ <label for="userpassword" class="form-label">Mật khẩu</label>
50
+ <div class="position-relative auth-pass-inputgroup mb-3">
51
+ <input type="password" name="password" class="form-control pe-5" placeholder="Enter Password" id="password-input">
52
+ <button class="btn btn-link position-absolute end-0 top-0 text-decoration-none text-muted" type="button" id="password-addon"><i class="ri-eye-fill align-middle"></i></button>
53
+ </div>
54
+ </div>
55
+
56
+
57
+ <div class="text-center mt-4">
58
+ <button class="btn btn-primary w-100" type="submit">Đăng nhập</button>
59
+ </div>
60
+
61
+ </form><!-- end form -->
62
+ </div>
63
+ </div><!-- end col -->
64
+ </div><!-- end row -->
65
+
66
+ <div class="row">
67
+ <div class="col-xl-12">
68
+ <div class="text-center text-muted p-4">
69
+ <p class="mb-0">&copy;
70
+ <script>document.write(new Date().getFullYear())</script> Chaton. Crafted with <i class="mdi mdi-heart text-danger"></i> by Themesdesign
71
+ </p>
72
+ </div>
73
+ </div><!-- end col -->
74
+ </div><!-- end row -->
75
+
76
+ </div>
77
+ </div>
78
+ </div>
79
+ <!-- end col -->
80
+ </div>
81
+ <!-- end row -->
82
+ </div>
83
+ <!-- end container-fluid -->
84
+ </div>
85
+ <!-- end auth bg -->
86
+
87
+ <!-- JAVASCRIPT -->
88
+ <script src="https://themesdesign.in/chaton/layouts/assets/libs/bootstrap/js/bootstrap.bundle.min.js"></script>
89
+ <script src="https://themesdesign.in/chaton/layouts/assets/libs/simplebar/simplebar.min.js"></script>
90
+ <script src="https://themesdesign.in/chaton/layouts/assets/libs/node-waves/waves.min.js"></script>
91
+ <!-- password addon -->
92
+ <script src="https://themesdesign.in/chaton/layouts/assets/js/pages/password-addon.init.js"></script>
93
+
94
+ <!-- theme-style init -->
95
+ <script src="https://themesdesign.in/chaton/layouts/assets/js/pages/theme-style.init.js"></script>
96
+
97
+ <script src="https://themesdesign.in/chaton/layouts/assets/js/app.js"></script>
98
+
99
+ </body>
100
+
101
+ </html>
templates/register.html ADDED
@@ -0,0 +1,106 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ <!doctype html>
3
+ <html lang="en">
4
+
5
+ <head>
6
+
7
+ <meta charset="utf-8" />
8
+ <title>Log in | Chaton - Responsive Bootstrap 5 Chat App</title>
9
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
10
+ <meta content="Chaton - Responsive Chat App Template in HTML. A fully featured HTML chat messenger template in Bootstrap 5" name="description" />
11
+ <meta name="keywords" content="Chaton chat template, chat, web chat template, chat status, chat template, communication, discussion, group chat, message, messenger template, status" />
12
+ <meta content="Themesdesign" name="author" />
13
+ <!-- App favicon -->
14
+ <link rel="shortcut icon" href="https://themesdesign.in/chaton/layouts/assets/images/favicon.ico" id="tabIcon">
15
+
16
+ <!-- Bootstrap Css -->
17
+ <link href="https://themesdesign.in/chaton/layouts/assets/css/bootstrap.min.css" id="bootstrap-style" rel="stylesheet" type="text/css" />
18
+ <!-- Icons Css -->
19
+ <link href="https://themesdesign.in/chaton/layouts/assets/css/icons.min.css" rel="stylesheet" type="text/css" />
20
+ <!-- App Css-->
21
+ <link href="https://themesdesign.in/chaton/layouts/assets/css/app.min.css" id="app-style" rel="stylesheet" type="text/css" />
22
+
23
+ </head>
24
+
25
+ <body>
26
+ <div class="auth-bg">
27
+ <div class="container p-0">
28
+ <div class="row justify-content-center g-0">
29
+ <div class="col-xl-9 col-lg-8">
30
+ <div class="authentication-page-content shadow-lg">
31
+ <div class="d-flex flex-column h-100 px-4 pt-4">
32
+ <div class="row justify-content-center">
33
+ <div class="col-sm-8 col-lg-6 col-xl-6">
34
+
35
+ <div class="py-md-5 py-4">
36
+
37
+ <div class="text-center mb-5">
38
+ <h3>Xin chào!</h3>
39
+ <p class="text-muted">Hãy đăng ký ngay</p>
40
+ </div>
41
+
42
+ <form action="http://127.0.0.1:5000/register" method="POST">
43
+
44
+ <div class="mb-3">
45
+ <label for="fullname" class="form-label">Họ và tên</label>
46
+ <input type="text" name="fullname" class="form-control" id="username" placeholder="Enter fullname">
47
+ </div>
48
+
49
+ <div class="mb-3">
50
+ <label for="username" class="form-label">Tên đăng nhập</label>
51
+ <input type="text" name="username" class="form-control" id="username" placeholder="Enter username">
52
+ </div>
53
+
54
+ <div class="mb-3">
55
+ <label for="userpassword" class="form-label">Mật khẩu</label>
56
+ <div class="position-relative auth-pass-inputgroup mb-3">
57
+ <input type="password" name="password" class="form-control pe-5" placeholder="Enter Password" id="password-input">
58
+ <button class="btn btn-link position-absolute end-0 top-0 text-decoration-none text-muted" type="button" id="password-addon"><i class="ri-eye-fill align-middle"></i></button>
59
+ </div>
60
+ </div>
61
+
62
+ <div class="text-center mt-4">
63
+ <button class="btn btn-primary w-100" type="submit">Đăng ký</button>
64
+ </div>
65
+
66
+ </form><!-- end form -->
67
+ </div>
68
+ </div><!-- end col -->
69
+ </div><!-- end row -->
70
+
71
+ <div class="row">
72
+ <div class="col-xl-12">
73
+ <div class="text-center text-muted p-4">
74
+ <p class="mb-0">&copy;
75
+ <script>document.write(new Date().getFullYear())</script> Chaton. Crafted with <i class="mdi mdi-heart text-danger"></i> by Themesdesign
76
+ </p>
77
+ </div>
78
+ </div><!-- end col -->
79
+ </div><!-- end row -->
80
+
81
+ </div>
82
+ </div>
83
+ </div>
84
+ <!-- end col -->
85
+ </div>
86
+ <!-- end row -->
87
+ </div>
88
+ <!-- end container-fluid -->
89
+ </div>
90
+ <!-- end auth bg -->
91
+
92
+ <!-- JAVASCRIPT -->
93
+ <script src="https://themesdesign.in/chaton/layouts/assets/libs/bootstrap/js/bootstrap.bundle.min.js"></script>
94
+ <script src="https://themesdesign.in/chaton/layouts/assets/libs/simplebar/simplebar.min.js"></script>
95
+ <script src="https://themesdesign.in/chaton/layouts/assets/libs/node-waves/waves.min.js"></script>
96
+ <!-- password addon -->
97
+ <script src="https://themesdesign.in/chaton/layouts/assets/js/pages/password-addon.init.js"></script>
98
+
99
+ <!-- theme-style init -->
100
+ <script src="https://themesdesign.in/chaton/layouts/assets/js/pages/theme-style.init.js"></script>
101
+
102
+ <script src="https://themesdesign.in/chaton/layouts/assets/js/app.js"></script>
103
+
104
+ </body>
105
+
106
+ </html>