Syuhra Ganiswari commited on
Commit
39ff7ae
·
1 Parent(s): f86ad4d

update FE

Browse files
Files changed (8) hide show
  1. .gitignore +2 -0
  2. app.py +41 -42
  3. contoh_struktur.json +1 -1
  4. load_data.py +24 -25
  5. static/script.js +21 -21
  6. static/style.css +34 -34
  7. templates/chat.html +2 -2
  8. templates/index.html +4 -4
.gitignore CHANGED
@@ -2,7 +2,9 @@
2
  venv/
3
 
4
  # Database ChromaDB lokal (wajib)
 
5
  my_chroma_db/
 
6
 
7
  # File rahasia API keys (wajib)
8
  .env
 
2
  venv/
3
 
4
  # Database ChromaDB lokal (wajib)
5
+ chroma/
6
  my_chroma_db/
7
+ .chroma/
8
 
9
  # File rahasia API keys (wajib)
10
  .env
app.py CHANGED
@@ -10,12 +10,12 @@ from dotenv import load_dotenv
10
  from bson.objectid import ObjectId
11
  from datetime import datetime
12
 
13
- # --- 1. Muat .env dan Inisialisasi ---
14
  load_dotenv()
15
  app = Flask(__name__)
16
  CORS(app)
17
 
18
- # --- 2. Inisialisasi Klien (Global) ---
19
  hf_token = os.getenv("HF_TOKEN")
20
  hf_client = InferenceClient(
21
  "meta-llama/Meta-Llama-3-8B-Instruct",
@@ -26,20 +26,20 @@ mongo_url = os.getenv("MONGO_URL")
26
  mongo_client = MongoClient(mongo_url)
27
  db = mongo_client.get_database("chatbot_db")
28
  chat_history_collection = db.get_collection("conversations")
29
- print("✅ Berhasil terkoneksi ke MongoDB Atlas.")
30
 
31
- print("Memuat model embedding (ini mungkin butuh waktu)...")
32
  embedding_model = SentenceTransformer('all-MiniLM-L6-v2')
33
- print("✅ Model embedding ('all-MiniLM-L6-v2') berhasil dimuat.")
34
 
35
  try:
36
  chroma_client = HttpClient(host='localhost', port=8000)
37
  knowledge_collection = chroma_client.get_collection(
38
  name="website_knowledge"
39
  )
40
- print("✅ Berhasil terkoneksi ke ChromaDB (localhost:8000).")
41
  except Exception as e:
42
- print(f"❌ GAGAL terhubung ke ChromaDB. Pastikan server chroma run... berjalan. Error: {e}")
43
 
44
  # --- 3. Endpoint Frontend ---
45
  # @app.route("/")
@@ -48,20 +48,20 @@ except Exception as e:
48
 
49
  @app.route("/")
50
  def home():
51
- """Sajikan landing page (index.html)"""
52
  return render_template("index.html")
53
 
54
  @app.route("/chat")
55
  def chat_page():
56
- """Sajikan halaman chat utama (chat.html)"""
57
  return render_template("chat.html")
58
 
59
- # --- 4. Endpoint API - Ambil SEMUA History (untuk F5) ---
60
  @app.route("/api/conversations", methods=["GET"])
61
  def get_conversations():
62
  user_id = request.args.get("userId")
63
  if not user_id:
64
- return jsonify({"error": "userId dibutuhkan"}), 400
65
 
66
  try:
67
  convos = list(chat_history_collection.find(
@@ -72,58 +72,58 @@ def get_conversations():
72
  for convo in convos:
73
  conversations_list.append({
74
  "id": str(convo.get('_id')),
75
- "title": convo.get("title", "Obrolan Baru"),
76
  "messages": convo.get("messages", []),
77
  "createdAt": convo.get("createdAt")
78
  })
79
  return jsonify(conversations_list)
80
  except Exception as e:
81
  print(f"Error di /api/conversations GET: {e}")
82
- return jsonify({"error": "Gagal mengambil percakapan"}), 500
83
 
84
- # --- 5. ENDPOINT API BARU (UNTUK "CLEAR ALL") ---
85
  @app.route("/api/conversations", methods=["DELETE"])
86
  def clear_conversations():
87
  """
88
- Menghapus SEMUA percakapan untuk satu userId.
89
  """
90
  data = request.json
91
  user_id = data.get("userId")
92
  if not user_id:
93
- return jsonify({"error": "userId dibutuhkan"}), 400
94
 
95
  try:
96
- # Hapus semua dokumen di MongoDB yang cocok dengan userId
97
  result = chat_history_collection.delete_many({"userId": user_id})
98
 
99
- print(f"Berhasil menghapus {result.deleted_count} percakapan untuk userId {user_id}.")
100
  return jsonify({
101
- "message": "History berhasil dihapus",
102
  "deleted_count": result.deleted_count
103
  })
104
 
105
  except Exception as e:
106
  print(f"Error di /api/conversations DELETE: {e}")
107
- return jsonify({"error": "Gagal menghapus history"}), 500
108
 
109
- # --- 6. Endpoint API Chat (Utama) ---
110
  @app.route("/api/chat", methods = ["POST"])
111
  def handle_chat():
112
  try:
113
  data = request.json
114
  user_message = data.get("message")
115
  user_id = data.get("userId")
116
- conversation_id = data.get("conversationId") # ID chat yang aktif, bisa null
117
 
118
  if not user_message or not user_id:
119
- return jsonify({"error": "Parameter 'message' dan 'userId' dibutuhkan."}), 400
120
 
121
  history = []
122
  user_message_doc = {"role": "user", "content": user_message, "timestamp": datetime.now()}
123
 
124
- # --- 1. (CRUD) Ambil/Buat Percakapan ---
125
  if conversation_id:
126
- # Ini adalah obrolan yang sudah ada
127
  current_convo = chat_history_collection.find_one({
128
  "_id": ObjectId(conversation_id),
129
  "userId": user_id
@@ -131,18 +131,17 @@ def handle_chat():
131
  if current_convo:
132
  history = current_convo.get("messages", [])[-6:]
133
 
134
- # --- 2. (RAG) - Lakukan RAG (Sama seperti sebelumnya) ---
135
- print(f"Mencari konteks untuk: \"{user_message}\"")
136
  query_embedding = embedding_model.encode(user_message).tolist()
137
  results = knowledge_collection.query(
138
  query_embeddings=[query_embedding],
139
- n_results=1 # <-- Anda bisa ganti ini ke 1 nanti
140
  )
141
  context = "\n\n".join(results['documents'][0])
142
  print("Konteks ditemukan.")
143
 
144
- # --- 3. (RAG) Buat prompt ---
145
- # <-- Anda bisa perketat prompt ini nanti
146
  system_prompt = """You are a precise assistant. Answer *strictly* and *only* based on the context provided.
147
  Do not add any information, pollutants, or applications that are not *explicitly* mentioned in the text.
148
  If the context provides conflicting information (like for different products), only use the information from the *single most relevant* chunk."""
@@ -153,15 +152,15 @@ def handle_chat():
153
  {"role": "user", "content": user_prompt}
154
  ]
155
 
156
- # --- 4. (RAG) Panggil API Llama 3 ---
157
- print("Memanggil Hugging Face API...")
158
  response = hf_client.chat_completion(messages=messages, max_tokens=250, temperature=0.1)
159
  ai_response = response.choices[0].message.content
160
 
161
- # --- 5. (CRUD) Simpan balasan AI ke MongoDB ---
162
  ai_message_doc = {"role": "assistant", "content": ai_response, "timestamp": datetime.now()}
163
 
164
- if conversation_id: # Obrolan lama
165
  chat_history_collection.update_one(
166
  {"_id": ObjectId(conversation_id)},
167
  {
@@ -169,27 +168,27 @@ def handle_chat():
169
  "$set": {"updatedAt": datetime.now()}
170
  }
171
  )
172
- else: # Obrolan baru
173
  title = user_message[:30] + "..." if len(user_message) > 30 else user_message
174
  new_convo_doc = {
175
  "userId": user_id,
176
  "title": title,
177
- "messages": [user_message_doc, ai_message_doc], # Langsung tambahkan user & AI
178
  "createdAt": datetime.now(),
179
  "updatedAt": datetime.now()
180
  }
181
  insert_result = chat_history_collection.insert_one(new_convo_doc)
182
- conversation_id = insert_result.inserted_id # Ambil _id baru
183
 
184
- print("Percakapan berhasil disimpan ke MongoDB.")
185
 
186
- # --- 6. Kirim balasan ---
187
  return jsonify({"answer": ai_response, "conversationId": str(conversation_id)})
188
 
189
  except Exception as e:
190
  print(f"Error di /api/chat: {e}")
191
- return jsonify({"error": "Terjadi kesalahan di server"}), 500
192
 
193
- # --- 7. Jalankan Server ---
194
  if __name__ == "__main__":
195
- app.run(port=3001, debug=True)
 
10
  from bson.objectid import ObjectId
11
  from datetime import datetime
12
 
13
+ # --- 1. Load .env and Initialize ---
14
  load_dotenv()
15
  app = Flask(__name__)
16
  CORS(app)
17
 
18
+ # --- 2. Client Initialization (Global) ---
19
  hf_token = os.getenv("HF_TOKEN")
20
  hf_client = InferenceClient(
21
  "meta-llama/Meta-Llama-3-8B-Instruct",
 
26
  mongo_client = MongoClient(mongo_url)
27
  db = mongo_client.get_database("chatbot_db")
28
  chat_history_collection = db.get_collection("conversations")
29
+ print("✅ Successfully connected to MongoDB Atlas.")
30
 
31
+ print("Loading model embedding (this may take a while)...")
32
  embedding_model = SentenceTransformer('all-MiniLM-L6-v2')
33
+ print("✅ The embedding model (all-MiniLM-L6-v2) has been successfully loaded.")
34
 
35
  try:
36
  chroma_client = HttpClient(host='localhost', port=8000)
37
  knowledge_collection = chroma_client.get_collection(
38
  name="website_knowledge"
39
  )
40
+ print("✅ Successfully connected to ChromaDB (localhost:8000).")
41
  except Exception as e:
42
+ print(f"❌ Failed to connect to ChromaDB. Make sure the chroma run... server is running. Error: {e}")
43
 
44
  # --- 3. Endpoint Frontend ---
45
  # @app.route("/")
 
48
 
49
  @app.route("/")
50
  def home():
51
+ """Display the landing page (index.html)"""
52
  return render_template("index.html")
53
 
54
  @app.route("/chat")
55
  def chat_page():
56
+ """Display the main chat page (chat.html)"""
57
  return render_template("chat.html")
58
 
59
+ # --- 4. Endpoint API - Take ALL History (untuk F5) ---
60
  @app.route("/api/conversations", methods=["GET"])
61
  def get_conversations():
62
  user_id = request.args.get("userId")
63
  if not user_id:
64
+ return jsonify({"error": "userId is required"}), 400
65
 
66
  try:
67
  convos = list(chat_history_collection.find(
 
72
  for convo in convos:
73
  conversations_list.append({
74
  "id": str(convo.get('_id')),
75
+ "title": convo.get("title", "New Chat"),
76
  "messages": convo.get("messages", []),
77
  "createdAt": convo.get("createdAt")
78
  })
79
  return jsonify(conversations_list)
80
  except Exception as e:
81
  print(f"Error di /api/conversations GET: {e}")
82
+ return jsonify({"error": "Failed to retrieve conversation"}), 500
83
 
84
+ # --- 5. NEW API ENDPOINT (FOR CLEAR ALL) ---
85
  @app.route("/api/conversations", methods=["DELETE"])
86
  def clear_conversations():
87
  """
88
+ Delete ALL conversations for one user ID.
89
  """
90
  data = request.json
91
  user_id = data.get("userId")
92
  if not user_id:
93
+ return jsonify({"error": "userId is required"}), 400
94
 
95
  try:
96
+ # Delete all documents in MongoDB that match the userId
97
  result = chat_history_collection.delete_many({"userId": user_id})
98
 
99
+ print(f"Successfully deleted {result.deleted_count} conversation for userId {user_id}.")
100
  return jsonify({
101
+ "message": "History successfully deleted",
102
  "deleted_count": result.deleted_count
103
  })
104
 
105
  except Exception as e:
106
  print(f"Error di /api/conversations DELETE: {e}")
107
+ return jsonify({"error": "Failed to delete history"}), 500
108
 
109
+ # --- 6. Endpoint API Chat (Main) ---
110
  @app.route("/api/chat", methods = ["POST"])
111
  def handle_chat():
112
  try:
113
  data = request.json
114
  user_message = data.get("message")
115
  user_id = data.get("userId")
116
+ conversation_id = data.get("conversationId") # Active chat ID, can be null
117
 
118
  if not user_message or not user_id:
119
+ return jsonify({"error": "The 'message' dan 'userId' parameters are required."}), 400
120
 
121
  history = []
122
  user_message_doc = {"role": "user", "content": user_message, "timestamp": datetime.now()}
123
 
124
+ # --- 1. (CRUD) Retrieve/Create Conversation ---
125
  if conversation_id:
126
+ # This is an existing chat
127
  current_convo = chat_history_collection.find_one({
128
  "_id": ObjectId(conversation_id),
129
  "userId": user_id
 
131
  if current_convo:
132
  history = current_convo.get("messages", [])[-6:]
133
 
134
+ # --- 2. (RAG) - Perform RAG (Same as before) ---
135
+ print(f"Searching for context for: \"{user_message}\"")
136
  query_embedding = embedding_model.encode(user_message).tolist()
137
  results = knowledge_collection.query(
138
  query_embeddings=[query_embedding],
139
+ n_results=1
140
  )
141
  context = "\n\n".join(results['documents'][0])
142
  print("Konteks ditemukan.")
143
 
144
+ # --- 3. (RAG) For prompt ---
 
145
  system_prompt = """You are a precise assistant. Answer *strictly* and *only* based on the context provided.
146
  Do not add any information, pollutants, or applications that are not *explicitly* mentioned in the text.
147
  If the context provides conflicting information (like for different products), only use the information from the *single most relevant* chunk."""
 
152
  {"role": "user", "content": user_prompt}
153
  ]
154
 
155
+ # --- 4. (RAG) Call the Llama 3 API ---
156
+ print("Calling Hugging Face API...")
157
  response = hf_client.chat_completion(messages=messages, max_tokens=250, temperature=0.1)
158
  ai_response = response.choices[0].message.content
159
 
160
+ # --- 5. (CRUD) Save the AI response to MongoDB ---
161
  ai_message_doc = {"role": "assistant", "content": ai_response, "timestamp": datetime.now()}
162
 
163
+ if conversation_id: # Existing chat
164
  chat_history_collection.update_one(
165
  {"_id": ObjectId(conversation_id)},
166
  {
 
168
  "$set": {"updatedAt": datetime.now()}
169
  }
170
  )
171
+ else: # New chat
172
  title = user_message[:30] + "..." if len(user_message) > 30 else user_message
173
  new_convo_doc = {
174
  "userId": user_id,
175
  "title": title,
176
+ "messages": [user_message_doc, ai_message_doc], # Directly append user & AI
177
  "createdAt": datetime.now(),
178
  "updatedAt": datetime.now()
179
  }
180
  insert_result = chat_history_collection.insert_one(new_convo_doc)
181
+ conversation_id = insert_result.inserted_id # Get the new _id
182
 
183
+ print("Conversation successfully saved to MongoDB.")
184
 
185
+ # --- 6. Send the response ---
186
  return jsonify({"answer": ai_response, "conversationId": str(conversation_id)})
187
 
188
  except Exception as e:
189
  print(f"Error di /api/chat: {e}")
190
+ return jsonify({"error": "Server error occurred"}), 500
191
 
192
+ # --- 7. Run the Server ---
193
  if __name__ == "__main__":
194
+ app.run(port=5000, debug=True)
contoh_struktur.json CHANGED
@@ -3,7 +3,7 @@
3
  "userId": "anon-uuid-12345-abcde", // Anonymous User ID Anda
4
  "createdAt": "2025-10-28T12:00:00Z",
5
  "updatedAt": "2025-10-28T12:05:00Z",
6
- "title": "Percakapan tentang Air Quality", // (Opsional)
7
  "messages": [
8
  {
9
  "role": "user",
 
3
  "userId": "anon-uuid-12345-abcde", // Anonymous User ID Anda
4
  "createdAt": "2025-10-28T12:00:00Z",
5
  "updatedAt": "2025-10-28T12:05:00Z",
6
+ "title": "Conversation about Air Quality", // (Opsional)
7
  "messages": [
8
  {
9
  "role": "user",
load_data.py CHANGED
@@ -3,57 +3,56 @@ from sentence_transformers import SentenceTransformer
3
  from chromadb import HttpClient
4
  import sys
5
 
6
- # --- 1. Inisialisasi Klien & Model ---
7
- print("Menghubungkan ke ChromaDB di localhost:8000...")
8
  try:
9
- # Kita terhubung ke server yang sama dengan app.py
10
  client = HttpClient(host='localhost', port=8000)
11
  collection = client.get_or_create_collection(name="website_knowledge")
12
- print("✅ Berhasil terhubung ke ChromaDB.")
13
  except Exception as e:
14
- print(f"❌ GAGAL terhubung ke ChromaDB. Pastikan server ChromaDB berjalan.")
15
  print(f"Error: {e}")
16
  sys.exit(1)
17
 
18
- print("Memuat model embedding 'all-MiniLM-L6-v2' (ini mungkin butuh waktu)...")
19
  model = SentenceTransformer('all-MiniLM-L6-v2')
20
- print("✅ Model embedding berhasil dimuat.")
21
 
22
- # --- 2. Baca File CSV ---
23
- file_path = "knowledge_base_FINAL_COMBINED.csv" # Pastikan nama file ini benar
24
  try:
25
  df = pd.read_csv(file_path)
26
  df = df.fillna('')
27
- print(f"✅ Berhasil memuat {len(df)} chunk dari {file_path}.")
28
  except FileNotFoundError:
29
- print(f"❌ Error: File {file_path} tidak ditemukan.")
30
  sys.exit(1)
31
 
32
- # --- 3. Siapkan Data ---
33
  documents = df['chunk_text'].tolist()
34
  metadatas = df[['main_topic', 'chunk_title', 'src_url']].to_dict('records')
35
  ids = df['chunk_id'].tolist()
36
 
37
- # --- 4. BUAT EMBEDDING (Bagian Paling Penting) ---
38
- # app.py membuat embedding 'on-the-fly' untuk 1 pertanyaan
39
- # Script ini membuat embedding untuk SEMUA dokumen sekaligus
40
- print(f"Memulai proses embedding untuk {len(documents)} dokumen...")
41
  embeddings = model.encode(documents, show_progress_bar=True)
42
- print("✅ Embedding selesai.")
43
 
44
- # --- 5. Tambahkan ke ChromaDB ---
45
- # Hapus data lama (jika ada) agar tidak duplikat
46
  try:
47
  collection.delete(ids=ids)
48
- print("Data lama di collection berhasil dihapus.")
49
  except Exception as e:
50
- print("Tidak ada data lama untuk dihapus, melanjutkan...")
51
 
52
- print("Menambahkan data baru ke ChromaDB (dalam batch)...")
53
  batch_size = 100
54
  for i in range(0, len(ids), batch_size):
55
- print(f" Menambahkan batch {i//batch_size + 1}...")
56
-
57
  collection.add(
58
  embeddings=embeddings[i:i+batch_size].tolist(),
59
  documents=documents[i:i+batch_size],
@@ -61,4 +60,4 @@ for i in range(0, len(ids), batch_size):
61
  ids=ids[i:i+batch_size]
62
  )
63
 
64
- print(f"🎉 SEMUA SELESAI! {collection.count()} dokumen berhasil disimpan di ChromaDB.")
 
3
  from chromadb import HttpClient
4
  import sys
5
 
6
+ # --- 1. Initialize Client & Model ---
7
+ print("Connecting to ChromaDB at localhost:8000...")
8
  try:
9
+ # We connect to the same server as app.py
10
  client = HttpClient(host='localhost', port=8000)
11
  collection = client.get_or_create_collection(name="website_knowledge")
12
+ print("✅ Successfully connected to ChromaDB.")
13
  except Exception as e:
14
+ print("❌ FAILED to connect to ChromaDB. Ensure the ChromaDB server is running.")
15
  print(f"Error: {e}")
16
  sys.exit(1)
17
 
18
+ print("Loading 'all-MiniLM-L6-v2' embedding model (this might take a moment)...")
19
  model = SentenceTransformer('all-MiniLM-L6-v2')
20
+ print("✅ Embedding model successfully loaded.")
21
 
22
+ # --- 2. Read CSV File ---
23
+ file_path = "knowledge_base_FINAL_COMBINED.csv" # Ensure this file name is correct
24
  try:
25
  df = pd.read_csv(file_path)
26
  df = df.fillna('')
27
+ print(f"✅ Successfully loaded {len(df)} chunks from {file_path}.")
28
  except FileNotFoundError:
29
+ print(f"❌ Error: File {file_path} not found.")
30
  sys.exit(1)
31
 
32
+ # --- 3. Prepare Data ---
33
  documents = df['chunk_text'].tolist()
34
  metadatas = df[['main_topic', 'chunk_title', 'src_url']].to_dict('records')
35
  ids = df['chunk_id'].tolist()
36
 
37
+ # --- 4. CREATE EMBEDDINGS (The Most Important Part) ---
38
+ # app.py creates embeddings 'on-the-fly' for 1 question
39
+ # This script creates embeddings for ALL documents at once
40
+ print(f"Starting embedding process for {len(documents)} documents...")
41
  embeddings = model.encode(documents, show_progress_bar=True)
42
+ print("✅ Embeddings complete.")
43
 
44
+ # --- 5. Add to ChromaDB ---
45
+ # Delete old data (if any) to prevent duplication
46
  try:
47
  collection.delete(ids=ids)
48
+ print("Old data in the collection successfully deleted.")
49
  except Exception as e:
50
+ print("No old data to delete, continuing...")
51
 
52
+ print("Adding new data to ChromaDB (in batches)...")
53
  batch_size = 100
54
  for i in range(0, len(ids), batch_size):
55
+ print(f" Adding batch {i//batch_size + 1}...")
 
56
  collection.add(
57
  embeddings=embeddings[i:i+batch_size].tolist(),
58
  documents=documents[i:i+batch_size],
 
60
  ids=ids[i:i+batch_size]
61
  )
62
 
63
+ print(f"🎉 ALL DONE! {collection.count()} documents successfully stored in ChromaDB.")
static/script.js CHANGED
@@ -1,20 +1,20 @@
1
  document.addEventListener("DOMContentLoaded", () => {
2
- // === 1. MENANGKAP ELEMEN DOM ===
3
  const sendButton = document.getElementById("send-button");
4
  const chatInput = document.getElementById("chat-input");
5
  const historyList = document.getElementById("history-list");
6
  const chatLog = document.getElementById("chat-log-area");
7
  const newChatButton = document.querySelector(".new-chat-btn");
8
- const clearAllButton = document.querySelector(".clear-all"); // <-- TAMBAHAN BARU
9
 
10
  const emptyChatPlaceholder = `
11
  <div class="empty-chat-placeholder">
12
  <i class="fa-solid fa-robot"></i>
13
- <h2>Hello Name</h2>
14
  </div>
15
  `;
16
 
17
- // === 2. DATA UTAMA APLIKASI (STATE) ===
18
  let conversations = {};
19
  let currentConversationId = null;
20
  let currentUserId = getOrCreateUserId();
@@ -39,46 +39,46 @@ document.addEventListener("DOMContentLoaded", () => {
39
  }
40
  });
41
 
42
- // <-- LISTENER BARU UNTUK "CLEAR ALL" ---
43
  clearAllButton.addEventListener("click", (e) => {
44
  e.preventDefault();
45
  clearAllConversations();
46
  });
47
 
48
- // === 4. FUNGSI-FUNGSI UTAMA ===
49
 
50
  /**
51
- * FUNGSI BARU: Menghapus semua percakapan
52
  */
53
  async function clearAllConversations() {
54
- if (!confirm("Apakah Anda yakin ingin menghapus semua percakapan? Tindakan ini tidak bisa dibatalkan.")) {
55
  return;
56
  }
57
 
58
  try {
59
- const response = await fetch("http://localhost:3001/api/conversations", {
60
  method: "DELETE",
61
  headers: { "Content-Type": "application/json" },
62
- body: JSON.stringify({ userId: currentUserId }) // Kirim userId untuk dihapus
63
  });
64
 
65
  if (!response.ok) {
66
- throw new Error("Gagal menghapus history di server.");
67
  }
68
 
69
- // Jika server berhasil, bersihkan state frontend
70
  conversations = {};
71
- startNewChat(); // Ini akan membersihkan UI (merender history & chat log)
72
 
73
- console.log("Semua percakapan berhasil dihapus.");
74
 
75
  } catch (error) {
76
  console.error("Error clearing conversations:", error);
77
- alert("Terjadi kesalahan saat menghapus history.");
78
  }
79
  }
80
 
81
- // ... (Fungsi getOrCreateUserId, startNewChat, sendMessage, dll. tetap sama persis) ...
82
 
83
  function getOrCreateUserId() {
84
  let userId = localStorage.getItem('anonymousUserId');
@@ -127,7 +127,7 @@ document.addEventListener("DOMContentLoaded", () => {
127
  chatLog.scrollTop = chatLog.scrollHeight;
128
 
129
  try {
130
- const response = await fetch("http://localhost:3001/api/chat", {
131
  method: "POST",
132
  headers: { "Content-Type": "application/json" },
133
  body: JSON.stringify({
@@ -159,7 +159,7 @@ document.addEventListener("DOMContentLoaded", () => {
159
  } catch (error) {
160
  console.error("Error sending message:", error);
161
  const p = loadingDiv.querySelector(".message-content p");
162
- p.textContent = "Maaf, terjadi kesalahan. Coba lagi.";
163
  }
164
 
165
  chatLog.scrollTop = chatLog.scrollHeight;
@@ -229,13 +229,13 @@ document.addEventListener("DOMContentLoaded", () => {
229
  return messageDiv;
230
  }
231
 
232
- // --- Inisialisasi Aplikasi ---
233
  async function initializeApp() {
234
  if (!currentUserId) return;
235
  try {
236
- const response = await fetch(`http://localhost:3001/api/conversations?userId=${currentUserId}`);
237
  if (!response.ok) {
238
- throw new Error("Gagal memuat history");
239
  }
240
  const data = await response.json();
241
  conversations = {};
 
1
  document.addEventListener("DOMContentLoaded", () => {
2
+ // === 1. CATCHING DOM ELEMENTS ===
3
  const sendButton = document.getElementById("send-button");
4
  const chatInput = document.getElementById("chat-input");
5
  const historyList = document.getElementById("history-list");
6
  const chatLog = document.getElementById("chat-log-area");
7
  const newChatButton = document.querySelector(".new-chat-btn");
8
+ const clearAllButton = document.querySelector(".clear-all");
9
 
10
  const emptyChatPlaceholder = `
11
  <div class="empty-chat-placeholder">
12
  <i class="fa-solid fa-robot"></i>
13
+ <h2>Hello! I am your Enviro-Edu Assistant. How may I assist you today?</h2>
14
  </div>
15
  `;
16
 
17
+ // === 2. APPLICATION MAIN DATA (STATE) ===
18
  let conversations = {};
19
  let currentConversationId = null;
20
  let currentUserId = getOrCreateUserId();
 
39
  }
40
  });
41
 
42
+ // <-- NEW LISTENER FOR CLEAR ALL ---
43
  clearAllButton.addEventListener("click", (e) => {
44
  e.preventDefault();
45
  clearAllConversations();
46
  });
47
 
48
+ // === 4. MAIN FUNCTIONS ===
49
 
50
  /**
51
+ * NEW FEATURE: Delete all conversations
52
  */
53
  async function clearAllConversations() {
54
+ if (!confirm("Are you sure you want to delete all conversations? This action cannot be undone.")) {
55
  return;
56
  }
57
 
58
  try {
59
+ const response = await fetch("http://localhost:5000/api/conversations", {
60
  method: "DELETE",
61
  headers: { "Content-Type": "application/json" },
62
+ body: JSON.stringify({ userId: currentUserId }) // Send userID to be deleted
63
  });
64
 
65
  if (!response.ok) {
66
+ throw new Error("Failed to delete history on the server.");
67
  }
68
 
69
+ // If the server is successful, clear the frontend state
70
  conversations = {};
71
+ startNewChat(); // This will clear the UI (render history & chat log).
72
 
73
+ console.log("All conversations have been successfully deleted.");
74
 
75
  } catch (error) {
76
  console.error("Error clearing conversations:", error);
77
+ alert("An error occurred while deleting history.");
78
  }
79
  }
80
 
81
+ // ... (The functions getOrCreateUserId, startNewChat, sendMessage, etc. remain exactly the same) ...
82
 
83
  function getOrCreateUserId() {
84
  let userId = localStorage.getItem('anonymousUserId');
 
127
  chatLog.scrollTop = chatLog.scrollHeight;
128
 
129
  try {
130
+ const response = await fetch("http://localhost:5000/api/chat", {
131
  method: "POST",
132
  headers: { "Content-Type": "application/json" },
133
  body: JSON.stringify({
 
159
  } catch (error) {
160
  console.error("Error sending message:", error);
161
  const p = loadingDiv.querySelector(".message-content p");
162
+ p.textContent = "Sorry, an error occurred. Please try again.";
163
  }
164
 
165
  chatLog.scrollTop = chatLog.scrollHeight;
 
229
  return messageDiv;
230
  }
231
 
232
+ // --- Application Initialization ---
233
  async function initializeApp() {
234
  if (!currentUserId) return;
235
  try {
236
+ const response = await fetch(`http://localhost:5000/api/conversations?userId=${currentUserId}`);
237
  if (!response.ok) {
238
+ throw new Error("Failed to load history");
239
  }
240
  const data = await response.json();
241
  conversations = {};
static/style.css CHANGED
@@ -1,31 +1,31 @@
1
- /* Reset CSS dasar */
2
  * {
3
  margin: 0;
4
  padding: 0;
5
  box-sizing: border-box;
6
  }
7
 
8
- /* 1. STYLE BODY UNIVERSAL (Dasar) */
9
  body {
10
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
11
  background-color: #f7f7f8;
12
  color: #1f2328;
13
  /* display: flex; */
14
  /* height: 100vh; */
15
- /* overflow: hidden; Mencegah body scroll */
16
  }
17
 
18
- /* 2. STYLE BODY KHUSUS UNTUK CHAT PAGE */
19
- /* Selektor ini memastikan style hanya berlaku jika body BUKAN landing-page */
20
  body:not(.landing-page) {
21
  display: flex;
22
  height: 100vh;
23
- overflow: hidden; /* Mencegah body scroll */
24
  }
25
 
26
- /* --- 3. STYLE UNTUK LANDING PAGE (index.html) --- */
27
 
28
- .landing-page { /* Menggunakan class yang ditambahkan di index.html */
29
  display: flex;
30
  align-items: center;
31
  justify-content: center;
@@ -55,10 +55,10 @@ body:not(.landing-page) {
55
  font-size: 1.1em;
56
  font-weight: bold;
57
  border-radius: 8px;
58
- /* Tambahkan hover atau style lain yang Anda suka */
59
  }
60
 
61
- /* --- 1. Sidebar (Kiri) --- */
62
  .sidebar {
63
  width: 260px;
64
  background-color: #ffffff;
@@ -78,7 +78,7 @@ body:not(.landing-page) {
78
  .new-chat-btn {
79
  flex-grow: 1;
80
  padding: 12px;
81
- background-color: #4a4aef; /* Biru seperti di gambar */
82
  color: white;
83
  border: none;
84
  border-radius: 8px;
@@ -86,7 +86,7 @@ body:not(.landing-page) {
86
  font-weight: 500;
87
  cursor: pointer;
88
  text-align: left;
89
- text-decoration: none; /* Ditambahkan agar link tidak ada garis bawah */
90
  }
91
 
92
  .new-chat-btn i {
@@ -102,8 +102,8 @@ body:not(.landing-page) {
102
  }
103
 
104
  .chat-history {
105
- flex-grow: 1; /* Membuat history mengisi ruang tersisa */
106
- overflow-y: auto; /* Scroll jika history penuh */
107
  }
108
 
109
  .history-header {
@@ -182,9 +182,9 @@ body:not(.landing-page) {
182
  background-color: #ddd;
183
  }
184
 
185
- /* --- 2. Chat Area (Kanan) --- */
186
  .chat-area {
187
- flex-grow: 1; /* Mengisi sisa ruang */
188
  display: flex;
189
  flex-direction: column;
190
  background-color: #ffffff;
@@ -196,21 +196,21 @@ body:not(.landing-page) {
196
  border-bottom: 1px solid #e0e0e0;
197
  font-size: 1.1em;
198
  font-weight: 600;
199
- flex-shrink: 0; /* Mencegah header mengecil */
200
  }
201
 
202
  .chat-log {
203
- flex-grow: 1; /* Mengisi ruang tengah */
204
- overflow-y: auto; /* Ini adalah area chat yang bisa di-scroll */
205
  padding: 24px;
206
  display: flex;
207
  flex-direction: column;
208
- gap: 20px; /* Jarak antar pesan NANTINYA */
209
  }
210
 
211
- /* --- STYLE UNTUK EMPTY STATE --- */
212
  .empty-chat-placeholder {
213
- flex-grow: 1; /* Memastikan placeholder mengisi ruang chat-log */
214
  display: flex;
215
  flex-direction: column;
216
  align-items: center;
@@ -232,11 +232,11 @@ body:not(.landing-page) {
232
  /* ------------------------------- */
233
 
234
 
235
- /* Styling untuk setiap pesan (Disimpan untuk nanti saat Anda tambahkan chat) */
236
  .chat-message {
237
  display: flex;
238
  gap: 16px;
239
- max-width: 90%; /* Agar tidak terlalu lebar */
240
  }
241
 
242
  .chat-message .avatar {
@@ -263,7 +263,7 @@ body:not(.landing-page) {
263
  padding-top: 8px;
264
  }
265
 
266
- .message-content strong { /* Untuk nama "CHAT A.I+" */
267
  display: block;
268
  margin-bottom: 4px;
269
  font-size: 1.05em;
@@ -275,7 +275,7 @@ body:not(.landing-page) {
275
  }
276
 
277
  .message-content ol {
278
- padding-left: 20px; /* Indentasi untuk ordered list */
279
  margin-top: 10px;
280
  }
281
 
@@ -283,24 +283,24 @@ body:not(.landing-page) {
283
  margin-bottom: 8px;
284
  }
285
 
286
- /* Area Input di Bawah */
287
  .chat-input-area {
288
  padding: 24px;
289
  border-top: 1px solid #e0e0e0;
290
  background-color: #fff;
291
- flex-shrink: 0; /* Mencegah input area mengecil */
292
  }
293
 
294
  .input-wrapper {
295
  display: flex;
296
  position: relative;
297
- max-width: 900px; /* Batasi lebar input */
298
- margin: 0 auto; /* tengahkan */
299
  }
300
 
301
  .chat-input-area input {
302
  flex-grow: 1;
303
- padding: 16px 50px 16px 20px; /* Beri ruang untuk tombol send */
304
  border: 1px solid #ccc;
305
  border-radius: 8px;
306
  font-size: 1em;
@@ -322,7 +322,7 @@ body:not(.landing-page) {
322
  border: none;
323
  width: 40px;
324
  height: 40px;
325
- border-radius: 50%; /* Buat jadi lingkaran */
326
  font-size: 1.1em;
327
  cursor: pointer;
328
  display: flex;
@@ -330,11 +330,11 @@ body:not(.landing-page) {
330
  justify-content: center;
331
  }
332
 
333
- /* Tab "Upgrade" di sisi kanan */
334
  .upgrade-tab {
335
  position: fixed;
336
  top: 50%;
337
- right: -60px; /* Atur posisi agar sedikit keluar */
338
  transform: translateY(-50%) rotate(-90deg);
339
  background-color: #4a4aef;
340
  color: white;
 
1
+ /* Reset basic CSS */
2
  * {
3
  margin: 0;
4
  padding: 0;
5
  box-sizing: border-box;
6
  }
7
 
8
+ /* 1. UNIVERSAL BODY STYLE (Basic) */
9
  body {
10
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
11
  background-color: #f7f7f8;
12
  color: #1f2328;
13
  /* display: flex; */
14
  /* height: 100vh; */
15
+ /* overflow: hidden; Preventing body scroll */
16
  }
17
 
18
+ /* 2. SPECIAL BODY STYLE FOR CHAT PAGE */
19
+ /* This selector ensures that the style only applies if the body is NOT a landing page */
20
  body:not(.landing-page) {
21
  display: flex;
22
  height: 100vh;
23
+ overflow: hidden; /* Preventing body scroll */
24
  }
25
 
26
+ /* --- 3. STYLE FOR LANDING PAGE (index.html) --- */
27
 
28
+ .landing-page { /* Using the class added in index.html */
29
  display: flex;
30
  align-items: center;
31
  justify-content: center;
 
55
  font-size: 1.1em;
56
  font-weight: bold;
57
  border-radius: 8px;
58
+ /* Add hover or other styles if needed */
59
  }
60
 
61
+ /* --- 1. Sidebar (Left) --- */
62
  .sidebar {
63
  width: 260px;
64
  background-color: #ffffff;
 
78
  .new-chat-btn {
79
  flex-grow: 1;
80
  padding: 12px;
81
+ background-color: #4a4aef;
82
  color: white;
83
  border: none;
84
  border-radius: 8px;
 
86
  font-weight: 500;
87
  cursor: pointer;
88
  text-align: left;
89
+ text-decoration: none; /* Added so that the link is not underlined */
90
  }
91
 
92
  .new-chat-btn i {
 
102
  }
103
 
104
  .chat-history {
105
+ flex-grow: 1; /* Filling the remaining space with history */
106
+ overflow-y: auto; /* Scroll if history is full */
107
  }
108
 
109
  .history-header {
 
182
  background-color: #ddd;
183
  }
184
 
185
+ /* --- 2. Chat Area (Right) --- */
186
  .chat-area {
187
+ flex-grow: 1; /* Filling the remaining space */
188
  display: flex;
189
  flex-direction: column;
190
  background-color: #ffffff;
 
196
  border-bottom: 1px solid #e0e0e0;
197
  font-size: 1.1em;
198
  font-weight: 600;
199
+ flex-shrink: 0; /* Preventing headers from shrinking */
200
  }
201
 
202
  .chat-log {
203
+ flex-grow: 1; /* Filling the center space */
204
+ overflow-y: auto; /* This is a scrollable chat area. */
205
  padding: 24px;
206
  display: flex;
207
  flex-direction: column;
208
+ gap: 20px; /* Distance between messages */
209
  }
210
 
211
+ /* --- STYLE FOR EMPTY STATE --- */
212
  .empty-chat-placeholder {
213
+ flex-grow: 1; /* Ensuring placeholders fill the chat log space */
214
  display: flex;
215
  flex-direction: column;
216
  align-items: center;
 
232
  /* ------------------------------- */
233
 
234
 
235
+ /* Styling for each message (Saved for later when you add a chat) */
236
  .chat-message {
237
  display: flex;
238
  gap: 16px;
239
+ max-width: 90%; /* So that it is not too wide */
240
  }
241
 
242
  .chat-message .avatar {
 
263
  padding-top: 8px;
264
  }
265
 
266
+ .message-content strong { /* For title Enviro Education Tools Chatbot */
267
  display: block;
268
  margin-bottom: 4px;
269
  font-size: 1.05em;
 
275
  }
276
 
277
  .message-content ol {
278
+ padding-left: 20px; /* Indentation for ordered lists */
279
  margin-top: 10px;
280
  }
281
 
 
283
  margin-bottom: 8px;
284
  }
285
 
286
+ /* Input Area Below */
287
  .chat-input-area {
288
  padding: 24px;
289
  border-top: 1px solid #e0e0e0;
290
  background-color: #fff;
291
+ flex-shrink: 0; /* Preventing the input area from shrinking */
292
  }
293
 
294
  .input-wrapper {
295
  display: flex;
296
  position: relative;
297
+ max-width: 900px; /* Limit input width */
298
+ margin: 0 auto;
299
  }
300
 
301
  .chat-input-area input {
302
  flex-grow: 1;
303
+ padding: 16px 50px 16px 20px; /* Make space for the send button */
304
  border: 1px solid #ccc;
305
  border-radius: 8px;
306
  font-size: 1em;
 
322
  border: none;
323
  width: 40px;
324
  height: 40px;
325
+ border-radius: 50%; /* Make a circle */
326
  font-size: 1.1em;
327
  cursor: pointer;
328
  display: flex;
 
330
  justify-content: center;
331
  }
332
 
333
+ /* The Upgrade tab on the right side */
334
  .upgrade-tab {
335
  position: fixed;
336
  top: 50%;
337
+ right: -60px; /* Adjust the position so that it sticks out slightly. */
338
  transform: translateY(-50%) rotate(-90deg);
339
  background-color: #4a4aef;
340
  color: white;
templates/chat.html CHANGED
@@ -47,7 +47,7 @@
47
  <section class="chat-area">
48
 
49
  <header class="chat-header">
50
- CHAT A.I+
51
  </header>
52
 
53
  <div class="chat-log" id="chat-log-area"> <div class="empty-chat-placeholder">
@@ -59,7 +59,7 @@
59
 
60
  <footer class="chat-input-area">
61
  <div class="input-wrapper">
62
- <input type="text" id="chat-input" placeholder="What's in your mind...">
63
  <button class="send-btn" id="send-button">
64
  <i class="fa-solid fa-paper-plane"></i>
65
  </button>
 
47
  <section class="chat-area">
48
 
49
  <header class="chat-header">
50
+ Enviro Education Tools Chatbot
51
  </header>
52
 
53
  <div class="chat-log" id="chat-log-area"> <div class="empty-chat-placeholder">
 
59
 
60
  <footer class="chat-input-area">
61
  <div class="input-wrapper">
62
+ <input type="text" id="chat-input" placeholder="Type your question...">
63
  <button class="send-btn" id="send-button">
64
  <i class="fa-solid fa-paper-plane"></i>
65
  </button>
templates/index.html CHANGED
@@ -25,12 +25,12 @@
25
  </head>
26
  <body class="landing-page">
27
  <div class="landing-container">
28
- <h1>Selamat Datang di Enviro Education Chatbot</h1>
29
  <p>
30
- Asisten AI ini dilatih berdasarkan data produk kami.
31
- Tanyakan apa saja tentang Air, Water, dan Soil Quality Testers.
32
  </p>
33
- <a href="/chat" class="start-chat-btn">Mulai Chat Sekarang</a>
34
  </div>
35
  </body>
36
  </html>
 
25
  </head>
26
  <body class="landing-page">
27
  <div class="landing-container">
28
+ <h1>Welcome to the Enviro Education Chatbot!</h1>
29
  <p>
30
+ This AI assistant is trained based on our product data.
31
+ Ask anything about Air, Water, and Soil Quality Testers.
32
  </p>
33
+ <a href="/chat" class="start-chat-btn">Start Chat Now</a>
34
  </div>
35
  </body>
36
  </html>