karwanjiru commited on
Commit
b768c6b
1 Parent(s): 2c943de

files added

Browse files
.gitignore ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ data
2
+ research
3
+ .env
Dockerfile ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.9
2
+
3
+ RUN useradd -m -u 1000 user
4
+
5
+ WORKDIR /app
6
+
7
+ COPY --chown=user ./requirements.txt requirements.txt
8
+
9
+ RUN pip install -r requirements.txt
10
+
11
+ RUN pip install --upgrade sentence_transformers
12
+
13
+ RUN pip install --upgrade langchain
14
+
15
+ COPY --chown=user . /app
16
+
17
+ CMD ["gunicorn", "app:app","-b","0.0.0.0:7860"]
app.py ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import time
3
+ from flask import Flask, render_template, jsonify, request
4
+ from src.helper import download_hugging_face_embeddings
5
+ from langchain.llms import Replicate
6
+ from dotenv import load_dotenv
7
+ from PyPDF2 import PdfReader
8
+ from langchain.schema import Document
9
+ from langchain.text_splitter import CharacterTextSplitter
10
+
11
+ # Initialize Flask app
12
+ app = Flask(__name__)
13
+
14
+ # Load environment variables
15
+ load_dotenv()
16
+
17
+ # Optional PDF processing functions
18
+ # def load_pdf(file_path):
19
+ # all_text = ""
20
+ # with open(file_path, 'rb') as file:
21
+ # reader = PdfReader(file)
22
+ # for page in reader.pages:
23
+ # all_text += page.extract_text() + "\n"
24
+ # return all_text if all_text else None
25
+
26
+ # def text_split(text):
27
+ # text_splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=0)
28
+ # document = Document(page_content=text)
29
+ # return text_splitter.split_documents([document])
30
+
31
+ # Load and process data
32
+ # pdf_file_path = "data/Okelloetal.2008TourismanalysisManka.pdf"
33
+ # extracted_data = load_pdf(pdf_file_path)
34
+ # if extracted_data is None:
35
+ # raise ValueError("The extracted data is None. Please check the load_pdf function.")
36
+ # print(f"Extracted Data: {extracted_data}")
37
+
38
+ # Split the extracted text into chunks
39
+ # text_chunks = text_split(extracted_data)
40
+ # if not text_chunks:
41
+ # raise ValueError("The text_chunks is None or empty. Please check the text_split function.")
42
+ # print(f"Text Chunks: {text_chunks}")
43
+
44
+ embeddings = download_hugging_face_embeddings()
45
+ if embeddings is None:
46
+ raise ValueError("The embeddings is None. Please check the download_hugging_face_embeddings function.")
47
+ print(f"Embeddings: {embeddings}")
48
+
49
+ os.environ["REPLICATE_API_TOKEN"] = "r8_3eWT6qNBwq8r7zNknWKxsyNyOQ6WMGS2WWRay"
50
+
51
+ # Initialize the Replicate model
52
+ llm = Replicate(
53
+ model="a16z-infra/llama7b-v2-chat:4f0a4744c7295c024a1de15e1a63c880d3da035fa1f49bfd344fe076074c8eea",
54
+ config={
55
+ 'max_new_tokens': 100, # Maximum number of tokens to generate in response
56
+ 'temperature': 0.7, # Optimal temperature for balanced randomness and coherence
57
+ 'top_k': 50 # Optimal top-k value for considering the top 50 predictions
58
+ }
59
+ )
60
+ # Flask routes
61
+ @app.route("/")
62
+ def index():
63
+ return render_template('chat.html')
64
+
65
+ @app.route("/get", methods=["GET", "POST"])
66
+ def chat():
67
+ try:
68
+ msg = request.form["msg"]
69
+ input_text = msg
70
+ print(f"Received message: {input_text}")
71
+
72
+ # Display spinner
73
+ result = {"generated_text": "Thinking..."}
74
+
75
+ # Simulate processing delay
76
+ time.sleep(1)
77
+
78
+ # Retrieve response from the model
79
+ result = llm.generate([input_text])
80
+ print(f"LLMResult: {result}")
81
+
82
+ # Access the generated text from the result object
83
+ if result.generations and result.generations[0]:
84
+ generated_text = result.generations[0][0].text
85
+ else:
86
+ generated_text = "No response generated."
87
+
88
+ print(f"Response: {generated_text}")
89
+
90
+ return str(generated_text)
91
+ except Exception as e:
92
+ print(f"Error: {e}")
93
+ return jsonify({"error": str(e)}), 500
94
+
95
+ if __name__ == '__main__':
96
+ app.run(host="0.0.0.0", port=8080, debug=True)
requirements.txt ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ctransformers
2
+ sentence-transformers==2.2.2
3
+ pinecone-client
4
+ langchain==0.0.225
5
+ flask
6
+ pypdf
7
+ python-dotenv
8
+ pinecone
9
+ langchain_community
10
+ sentence_transformers
11
+ langchain_pinecone
12
+ gunicorn
13
+ PyPDF2
setup.py ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from setuptools import find_packages, setup
2
+
3
+ setup(
4
+ name = 'VoyageVirtuoso',
5
+ version= '0.0.0',
6
+ author= 'Diana Wanjiru',
7
+ author_email= '[email protected]',
8
+ packages= find_packages(),
9
+ install_requires = []
10
+
11
+ )
src/__init__.py ADDED
File without changes
src/__pycache__/__init__.cpython-311.pyc ADDED
Binary file (179 Bytes). View file
 
src/__pycache__/helper.cpython-311.pyc ADDED
Binary file (2.12 kB). View file
 
src/helper.py ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from langchain import PromptTemplate
2
+ from langchain.chains import RetrievalQA
3
+ from langchain.embeddings import HuggingFaceEmbeddings
4
+ from langchain_community.vectorstores import Pinecone
5
+ from dotenv import load_dotenv
6
+ import os
7
+ from pinecone import Pinecone
8
+ from langchain.document_loaders import PyPDFLoader, DirectoryLoader
9
+ from langchain.text_splitter import RecursiveCharacterTextSplitter
10
+ from langchain.prompts import PromptTemplate
11
+ from langchain.llms import CTransformers
12
+ from unittest import loader
13
+
14
+
15
+ load_dotenv()
16
+
17
+ PINECONE_API_KEY = os.environ.get('PINECONE_API_KEY')
18
+ PINECONE_API_ENV = os.environ.get('PINECONE_API_ENV')
19
+
20
+ # Extract pdf data
21
+
22
+
23
+
24
+ def load_pdf(data):
25
+ directory_loader = DirectoryLoader(data,
26
+ glob="*.pdf",
27
+ loader_cls=PyPDFLoader)
28
+
29
+ documents = directory_loader.load()
30
+
31
+ def text_split(extracted_data):
32
+ text_splitter = RecursiveCharacterTextSplitter(chunk_size = 500, chunk_overlap = 20)
33
+ text_chunks = text_splitter.split_documents(extracted_data)
34
+
35
+ return text_chunks
36
+
37
+ def download_hugging_face_embeddings():
38
+ embeddings = HuggingFaceEmbeddings(model_name="sentence-transformers/all-MiniLM-L6-v2")
39
+ return embeddings
src/prompt.py ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ prompt_template= """
2
+ As VoyageVirtuoso, your role is to serve as a wildlife guide AI, providing factual and informative responses about wildlife and nature to tourists. Your responses should strictly adhere to providing helpful information based on the given context and questions. If you do not have the information, simply state that you don't know without fabricating an answer. Your responses should avoid anthropomorphizing or expressing personal opinions. Please refrain from mimicking the tourist's language or style.
3
+
4
+ Context: {context}
5
+ Question: {question}
6
+ Helpful answer:
7
+ """
static/.gitkeep ADDED
File without changes
store_index.py ADDED
@@ -0,0 +1,106 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import time
3
+ from src.helper import PINECONE_API_KEY, text_split, download_hugging_face_embeddings
4
+ from langchain.vectorstores import Pinecone as LangchainPinecone # Alias to avoid confusion
5
+ from dotenv import load_dotenv
6
+ from pinecone import Pinecone, ServerlessSpec
7
+ from langchain_pinecone import PineconeVectorStore
8
+ from PyPDF2 import PdfReader
9
+
10
+ # Define the load_pdf function
11
+ def load_pdf(file_path):
12
+ all_text = ""
13
+ with open(file_path, 'rb') as file:
14
+ reader = PdfReader(file)
15
+ for page in reader.pages:
16
+ all_text += page.extract_text() + "\n"
17
+ return all_text if all_text else None
18
+
19
+ # Define the text_split function
20
+ def text_split(text):
21
+ from langchain.text_splitter import CharacterTextSplitter
22
+ text_splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=0)
23
+ return text_splitter.split_text(text)
24
+
25
+ # Load environment variables if not already set
26
+ load_dotenv()
27
+
28
+ # Load and process data
29
+ pdf_file_path = "data/Okelloetal.2008TourismanalysisManka.pdf" # Update this path to your single PDF file
30
+ extracted_data = load_pdf(pdf_file_path)
31
+ if extracted_data is None:
32
+ raise ValueError("The extracted data is None. Please check the load_pdf function.")
33
+
34
+ print(f"Extracted Data: {extracted_data}")
35
+
36
+ # Split the extracted text into chunks
37
+ text_chunks = text_split(extracted_data)
38
+ if text_chunks is None:
39
+ raise ValueError("The text_chunks is None. Please check the text_split function.")
40
+
41
+ print(f"Text Chunks: {text_chunks}")
42
+
43
+ embeddings = download_hugging_face_embeddings()
44
+ if embeddings is None:
45
+ raise ValueError("The embeddings is None. Please check the download_hugging_face_embeddings function.")
46
+
47
+ print(f"Embeddings: {embeddings}")
48
+
49
+ # Ensure Pinecone API key is available
50
+ api_key = os.environ.get("PINECONE_API_KEY")
51
+ if not api_key:
52
+ raise ValueError("PINECONE_API_KEY environment variable not set.")
53
+
54
+ # Initialize Pinecone client
55
+ pc = Pinecone(api_key=api_key)
56
+
57
+ # Specify cloud and region for the serverless index
58
+ cloud = os.environ.get('PINECONE_CLOUD') or 'aws'
59
+ region = os.environ.get('PINECONE_REGION') or 'us-east-1'
60
+ spec = ServerlessSpec(cloud=cloud, region=region)
61
+
62
+ # Define the index name
63
+ index_name = "healthbot"
64
+
65
+ # Create the index if it does not exist
66
+ if index_name not in pc.list_indexes().names():
67
+ pc.create_index(
68
+ name=index_name,
69
+ dimension=384,
70
+ metric="cosine",
71
+ spec=spec
72
+ )
73
+ # Wait for the index to be ready
74
+ while not pc.describe_index(index_name).status['ready']:
75
+ time.sleep(1)
76
+
77
+ # Connect to the created index
78
+ index = pc.Index(index_name)
79
+ time.sleep(1)
80
+
81
+ # Example: Add data to the index with reduced metadata
82
+ # Create a dictionary to simulate external storage of text chunks
83
+ text_chunk_store = {}
84
+
85
+ # Function to simulate storing text chunk and returning a reference ID
86
+ def store_text_chunk(text_chunk):
87
+ chunk_id = f"chunk_{len(text_chunk_store)}"
88
+ text_chunk_store[chunk_id] = text_chunk
89
+ return chunk_id
90
+
91
+ # Add text chunks to Pinecone with reference IDs
92
+ for i, text_chunk in enumerate(text_chunks):
93
+ chunk_id = store_text_chunk(text_chunk)
94
+ embedding = embeddings.embed_query(text_chunk) # Embed the text chunk
95
+ index.upsert(
96
+ vectors=[
97
+ {
98
+ "id": f"vec_{i}",
99
+ "values": embedding,
100
+ "metadata": {"chunk_id": chunk_id} # Only store the reference ID as metadata
101
+ }
102
+ ],
103
+ namespace="ns1"
104
+ )
105
+
106
+ print("Indexing completed successfully.")
template.py ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from pathlib import Path
3
+ import logging
4
+
5
+ logging.basicConfig(
6
+ level=logging.INFO,
7
+ format='[%(asctime)s]: %(message)s:'
8
+ )
9
+
10
+
11
+ list_of_files = [
12
+ "src/__init__.py",
13
+ "src/helper.py",
14
+ "src/prompt.py",
15
+ ".env",
16
+ "setup.py",
17
+ "research/trials.ipynb",
18
+ "app.py",
19
+ "store_index.py",
20
+ "static/.gitkeep",
21
+ "templates/chat.html"
22
+
23
+ ]
24
+
25
+
26
+ for filepath in list_of_files:
27
+ filepath = Path(filepath)
28
+ filedir, filename = os.path.split(filepath)
29
+
30
+ if filedir !="":
31
+ os.makedirs(filedir, exist_ok=True)
32
+ logging.info(f"Creating directory; {filedir} for the file {filename}")
33
+
34
+ if (not os.path.exists(filepath)) or (os.path.getsize(filepath) == 0):
35
+ with open(filepath, 'w') as f:
36
+ pass
37
+ logging.info(f"Creating empty file: {filepath}")
38
+
39
+ else:
40
+ logging.info(f"{filename} is already created")
templates/chat.html ADDED
@@ -0,0 +1,295 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>VoyageVirtuoso</title>
5
+ <link href="//maxcdn.bootstrapcdn.com/bootstrap/4.1.1/css/bootstrap.min.css" rel="stylesheet" id="bootstrap-css">
6
+ <script src="//maxcdn.bootstrapcdn.com/bootstrap/4.1.1/js/bootstrap.min.js"></script>
7
+ <script src="//cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
8
+ <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
9
+ <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.5.0/css/all.css" integrity="sha384-B4dIYHKNBt8Bc12p+WXckhzcICo0wtJAoU8YZTY5qE0Id1GSseTk6S+L3BlXeVIU" crossorigin="anonymous">
10
+ <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
11
+ <link rel="stylesheet" href="static/style.css"/>
12
+ <style>
13
+ body,html {
14
+ height: 100%;
15
+ margin: 0;
16
+ background: rgb(168, 69, 163);
17
+ background: -webkit-linear-gradient(to right, rgb(40, 59, 34), rgb(54, 60, 70), rgb(32, 32, 43));
18
+ background: linear-gradient(to right, rgb(56, 109, 150), rgb(79, 98, 136), rgb(33, 33, 78));
19
+ }
20
+
21
+ .chat {
22
+ margin-top: auto;
23
+ margin-bottom: auto;
24
+ }
25
+ .card {
26
+ height: 500px;
27
+ border-radius: 15px !important;
28
+ background-color: rgba(0,0,0,0.4) !important;
29
+ }
30
+ .contacts_body {
31
+ padding: 0.75rem 0 !important;
32
+ overflow-y: auto;
33
+ white-space: nowrap;
34
+ }
35
+ .msg_card_body {
36
+ overflow-y: auto;
37
+ }
38
+ .card-header {
39
+ border-radius: 15px 15px 0 0 !important;
40
+ border-bottom: 0 !important;
41
+ }
42
+ .card-footer {
43
+ border-radius: 0 0 15px 15px !important;
44
+ border-top: 0 !important;
45
+ }
46
+ .container {
47
+ align-content: center;
48
+ }
49
+ .search {
50
+ border-radius: 15px 0 0 15px !important;
51
+ background-color: rgba(0,0,0,0.3) !important;
52
+ border:0 !important;
53
+ color:white !important;
54
+ }
55
+ .search:focus {
56
+ box-shadow:none !important;
57
+ outline:0px !important;
58
+ }
59
+ .type_msg {
60
+ background-color: rgba(0,0,0,0.3) !important;
61
+ border:0 !important;
62
+ color:rgb(216, 206, 113) !important;
63
+ height: 60px !important;
64
+ overflow-y: auto;
65
+ }
66
+ .type_msg:focus {
67
+ box-shadow:none !important;
68
+ outline:0px !important;
69
+ }
70
+ .attach_btn {
71
+ border-radius: 15px 0 0 15px !important;
72
+ background-color: rgba(0,0,0,0.3) !important;
73
+ border:0 !important;
74
+ color: white !important;
75
+ cursor: pointer;
76
+ }
77
+ .send_btn {
78
+ border-radius: 0 15px 15px 0 !important;
79
+ background-color: rgba(0,0,0,0.3) !important;
80
+ border:0 !important;
81
+ color: white !important;
82
+ cursor: pointer;
83
+ }
84
+ .search_btn {
85
+ border-radius: 0 15px 15px 0 !important;
86
+ background-color: rgba(0,0,0,0.3) !important;
87
+ border:0 !important;
88
+ color: white !important;
89
+ cursor: pointer;
90
+ }
91
+ .contacts {
92
+ list-style: none;
93
+ padding: 0;
94
+ }
95
+ .contacts li {
96
+ width: 100% !important;
97
+ padding: 5px 10px;
98
+ margin-bottom: 15px !important;
99
+ }
100
+ .active {
101
+ background-color: rgba(0,0,0,0.3);
102
+ }
103
+ .user_img {
104
+ height: 70px;
105
+ width: 70px;
106
+ border:1.5px solid #f5f6fa;
107
+ }
108
+ .user_img_msg {
109
+ height: 40px;
110
+ width: 40px;
111
+ border:1.5px solid #f5f6fa;
112
+ }
113
+ .img_cont {
114
+ position: relative;
115
+ height: 70px;
116
+ width: 70px;
117
+ }
118
+ .img_cont_msg {
119
+ height: 40px;
120
+ width: 40px;
121
+ }
122
+ .online_icon {
123
+ position: absolute;
124
+ height: 15px;
125
+ width:15px;
126
+ background-color: #4cd137;
127
+ border-radius: 50%;
128
+ bottom: 0.2em;
129
+ right: 0.4em;
130
+ border:1.5px solid white;
131
+ }
132
+ .offline {
133
+ background-color: #c23616 !important;
134
+ }
135
+ .user_info {
136
+ margin-top: auto;
137
+ margin-bottom: auto;
138
+ margin-left: 15px;
139
+ }
140
+ .user_info span {
141
+ font-size: 20px;
142
+ color: white;
143
+ }
144
+ .user_info p {
145
+ font-size: 10px;
146
+ color: rgba(255,255,255,0.6);
147
+ }
148
+ .video_cam {
149
+ margin-left: 50px;
150
+ margin-top: 5px;
151
+ }
152
+ .video_cam span {
153
+ color: white;
154
+ font-size: 20px;
155
+ cursor: pointer;
156
+ margin-right: 20px;
157
+ }
158
+ .msg_cotainer {
159
+ margin-top: auto;
160
+ margin-bottom: auto;
161
+ margin-left: 10px;
162
+ border-radius: 25px;
163
+ background-color: rgb(82, 172, 255);
164
+ padding: 10px;
165
+ position: relative;
166
+ }
167
+ .msg_cotainer_send {
168
+ margin-top: auto;
169
+ margin-bottom: auto;
170
+ margin-right: 10px;
171
+ border-radius: 25px;
172
+ background-color: #58cc71;
173
+ padding: 10px;
174
+ position: relative;
175
+ }
176
+ .msg_time {
177
+ position: absolute;
178
+ left: 0;
179
+ bottom: -15px;
180
+ color: rgba(255,255,255,0.5);
181
+ font-size: 10px;
182
+ }
183
+ .msg_time_send {
184
+ position: absolute;
185
+ right:0;
186
+ bottom: -15px;
187
+ color: rgba(255,255,255,0.5);
188
+ font-size: 10px;
189
+ }
190
+ .msg_head {
191
+ position: relative;
192
+ }
193
+ #action_menu_btn {
194
+ position: absolute;
195
+ right: 10px;
196
+ top: 10px;
197
+ color: white;
198
+ cursor: pointer;
199
+ font-size: 20px;
200
+ }
201
+ .action_menu {
202
+ z-index: 1;
203
+ position: absolute;
204
+ padding: 15px 0;
205
+ background-color: rgba(0,0,0,0.5);
206
+ color: white;
207
+ border-radius: 15px;
208
+ top: 30px;
209
+ right: 15px;
210
+ display: none;
211
+ }
212
+ .action_menu ul {
213
+ list-style: none;
214
+ padding: 0;
215
+ margin: 0;
216
+ }
217
+ .action_menu ul li {
218
+ width: 100%;
219
+ padding: 10px 15px;
220
+ margin-bottom: 5px;
221
+ }
222
+ .action_menu ul li i {
223
+ padding-right: 10px;
224
+ }
225
+ .action_menu ul li:hover {
226
+ cursor: pointer;
227
+ background-color: rgba(0,0,0,0.2);
228
+ }
229
+ @media(max-width: 576px) {
230
+ .contacts_card {
231
+ margin-bottom: 15px !important;
232
+ }
233
+ }
234
+ </style>
235
+ </head>
236
+ <body>
237
+ <div class="container-fluid h-100">
238
+ <div class="row justify-content-center h-100">
239
+ <div class="col-md-8 col-xl-6 chat">
240
+ <div class="card">
241
+ <div class="card-header msg_head">
242
+ <div class="d-flex bd-highlight">
243
+ <div class="img_cont">
244
+ <img src="https://img.freepik.com/premium-photo/3d-render-robot-airport-terminal-3d-illustration_847439-956.jpg?w=740" class="rounded-circle user_img">
245
+ <span class="online_icon"></span>
246
+ </div>
247
+ <div class="user_info">
248
+ <span>VoyageVirtuoso</span>
249
+ <p>Welcome to VoyageVirtuoso, your AI wildlife guide. Explore nature with accurate information and insights about wildlife and habitats. Ask any question about wildlife and nature, and VoyageVirtuoso will provide factual and informative answers to enrich your journey.</p>
250
+ </div>
251
+ </div>
252
+ </div>
253
+ <div id="messageFormeight" class="card-body msg_card_body">
254
+ </div>
255
+ <div class="card-footer">
256
+ <form id="messageArea" class="input-group">
257
+ <input type="text" id="text" name="msg" placeholder="What would you like to know?" autocomplete="off" class="form-control type_msg" required/>
258
+ <div class="input-group-append">
259
+ <button type="submit" id="send" class="input-group-text send_btn"><i class="fas fa-location-arrow"></i></button>
260
+ </div>
261
+ </form>
262
+ </div>
263
+ </div>
264
+ </div>
265
+ </div>
266
+ </div>
267
+
268
+ <script>
269
+ $(document).ready(function() {
270
+ $("#messageArea").on("submit", function(event) {
271
+ const date = new Date();
272
+ const hour = date.getHours();
273
+ const minute = date.getMinutes();
274
+ const str_time = hour+":"+minute;
275
+ var rawText = $("#text").val();
276
+ var userHtml = '<div class="d-flex justify-content-end mb-4"><div class="msg_cotainer_send">' + rawText + '<span class="msg_time_send">'+ str_time + '</span></div><div class="img_cont_msg"><img src="https://img.freepik.com/free-photo/portrait-young-student-education-day_23-2150980074.jpg?t=st=1718541721~exp=1718545321~hmac=1560e26c9f7f8c5266c235dae982fd2b0e13267f24035f0a6e5fa545436a99d9&w=826" class="rounded-circle user_img_msg"></div></div>';
277
+
278
+ $("#text").val("");
279
+ $("#messageFormeight").append(userHtml);
280
+ $.ajax({
281
+ data: {
282
+ msg: rawText,
283
+ },
284
+ type: "POST",
285
+ url: "/get",
286
+ }).done(function(data) {
287
+ var botHtml = '<div class="d-flex justify-content-start mb-4"><div class="img_cont_msg"><img src="https://www.prdistribution.com/spirit/uploads/pressreleases/2019/newsreleases/--waterless-geothermal-gets-installed-in-historic-breakers-mansion-.png" class="rounded-circle user_img_msg"></div><div class="msg_cotainer">' + data + '<span class="msg_time">' + str_time + '</span></div></div>';
288
+ $("#messageFormeight").append($.parseHTML(botHtml));
289
+ });
290
+ event.preventDefault();
291
+ });
292
+ });
293
+ </script>
294
+ </body>
295
+ </html>