k96beni commited on
Commit
0cb7395
·
verified ·
1 Parent(s): 8543eeb

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +37 -112
app.py CHANGED
@@ -2,7 +2,7 @@ import os
2
  import json
3
  import time
4
  import requests
5
- from anthropic import Anthropic
6
  from openai import OpenAI
7
  import gradio as gr
8
  import pandas as pd
@@ -19,9 +19,9 @@ import re
19
 
20
  # --- Konfiguration ---
21
  CHARGENODE_URL = "https://www.chargenode.eu"
22
- MAX_CHUNK_SIZE = 2000 # Ökad chunkstorleken för att bättre hantera FAQ-svar
23
- CHUNK_OVERLAP = 200 # Nytt: Overlapping chunks för att inte tappa kontext
24
- RETRIEVAL_K = 5 # Antal chunker att hämta vid varje sökning
25
 
26
  # Kontrollera om vi kör i Hugging Face-miljön
27
  IS_HUGGINGFACE = os.environ.get("SPACE_ID") is not None
@@ -45,7 +45,7 @@ log_file_path = os.path.join(log_folder, "conversation_log_v2.txt")
45
  # Skapa en tom loggfil om den inte finns
46
  if not os.path.exists(log_file_path):
47
  with open(log_file_path, "w", encoding="utf-8") as f:
48
- f.write("") # Skapa en tom fil
49
  print(f"Skapade tom loggfil: {log_file_path}")
50
 
51
  hf_token = os.environ.get("HF_TOKEN")
@@ -58,12 +58,12 @@ scheduler = CommitScheduler(
58
  repo_type="dataset",
59
  folder_path=log_folder,
60
  path_in_repo="logs_v2",
61
- every=300, # Vänta 5 minuter
62
  token=hf_token
63
  )
64
 
65
  # --- Globala variabler ---
66
- last_log = None # Sparar loggdata från senaste svar för feedback
67
 
68
  # Globala variabler för embeddings
69
  embedder = None
@@ -71,17 +71,16 @@ embeddings = None
71
  index = None
72
  chunks = []
73
  chunk_sources = []
74
- faq_dict = {} # Ny: Dictionary för direktmatchning av vanliga frågor
75
 
76
  # --- Förbättrad loggfunktion ---
77
  def safe_append_to_log(log_entry):
78
  """Säker metod för att lägga till loggdata utan att förlora historisk information."""
79
  try:
80
- # Öppna filen i append-läge
81
  with open(log_file_path, "a", encoding="utf-8") as log_file:
82
  log_json = json.dumps(log_entry)
83
  log_file.write(log_json + "\n")
84
- log_file.flush() # Säkerställ att data skrivs till disk omedelbart
85
 
86
  print(f"Loggpost tillagd: {log_entry.get('timestamp', 'okänd tid')}")
87
  return True
@@ -89,11 +88,9 @@ def safe_append_to_log(log_entry):
89
  except Exception as e:
90
  print(f"Fel vid loggning: {e}")
91
 
92
- # Försök skapa mappen om den inte finns
93
  try:
94
  os.makedirs(os.path.dirname(log_file_path), exist_ok=True)
95
 
96
- # Försök igen
97
  with open(log_file_path, "a", encoding="utf-8") as log_file:
98
  log_json = json.dumps(log_entry)
99
  log_file.write(log_json + "\n")
@@ -118,10 +115,10 @@ def load_local_files():
118
  with open(file, "r", encoding="utf-8") as f:
119
  content = f.read()
120
  elif file.endswith(".docx"):
121
- from docx import Document # Import sker vid behov
122
  content = "\n".join([p.text for p in Document(file).paragraphs])
123
  elif file.endswith(".pdf"):
124
- import PyPDF2 # Import sker vid behov
125
  with open(file, "rb") as f:
126
  reader = PyPDF2.PdfReader(f)
127
  content = "\n".join([p.extract_text() or "" for p in reader.pages])
@@ -132,13 +129,11 @@ def load_local_files():
132
  df = pd.read_excel(file)
133
  rows = []
134
  for index, row in df.iterrows():
135
- # Start with the required fields
136
  row_text = f"Fråga: {row['Fråga']}\nSvar: {row['Svar']}"
137
 
138
- # Add kategori if it exists in the dataframe
139
  if 'kategori' in df.columns:
140
  row_text += f"\nKategori: {row['kategori']}"
141
- elif 'Kategori' in df.columns: # Also check for capitalized version
142
  row_text += f"\nKategori: {row['Kategori']}"
143
 
144
  rows.append(row_text)
@@ -173,32 +168,24 @@ def prepare_chunks(text_data):
173
  global faq_dict
174
 
175
  for source, text in text_data.items():
176
- # Split text into paragraph-sized chunks
177
  paragraphs = [p for p in text.split("\n") if p.strip()]
178
 
179
- # Process FAQ-specific content better
180
  i = 0
181
  while i < len(paragraphs):
182
- # Start a new chunk
183
  current_chunk = ""
184
  start_idx = i
185
 
186
- # Check for FAQ format
187
  if i < len(paragraphs) and paragraphs[i].startswith("Fråga:"):
188
- question = paragraphs[i][7:].strip() # Extract the question text
189
  current_chunk = paragraphs[i]
190
  i += 1
191
 
192
- # Add content until we reach the next question or MAX_CHUNK_SIZE
193
  while i < len(paragraphs) and not paragraphs[i].startswith("Fråga:"):
194
- # Add this paragraph if it doesn't exceed chunk size
195
  if len(current_chunk) + len(paragraphs[i]) + 1 <= MAX_CHUNK_SIZE:
196
  current_chunk += "\n" + paragraphs[i]
197
  else:
198
- # If we're already processing a FAQ answer, don't break mid-answer
199
  if "Svar:" in current_chunk:
200
- # We prefer to keep whole answers together, so let's break only if answer is too long
201
- if len(current_chunk) > MAX_CHUNK_SIZE * 1.5: # Allow some overflow
202
  break
203
  else:
204
  current_chunk += "\n" + paragraphs[i]
@@ -206,12 +193,10 @@ def prepare_chunks(text_data):
206
  break
207
  i += 1
208
 
209
- # Store FAQ pairs in the dictionary for direct lookup
210
  if "Svar:" in current_chunk:
211
  answer_start = current_chunk.find("Svar:")
212
  answer_text = current_chunk[answer_start + 5:].strip()
213
 
214
- # Add variations with common synonyms for payment-related questions
215
  if any(term in question.lower() for term in ["betalsätt", "betalmetod", "betalmedel", "kort",
216
  "betalkort", "betalning", "betala"]):
217
  payment_variations = [
@@ -225,10 +210,8 @@ def prepare_chunks(text_data):
225
  for variation in payment_variations:
226
  faq_dict[variation] = answer_text
227
 
228
- # Add the original question to the dictionary
229
  faq_dict[question.lower()] = answer_text
230
  else:
231
- # Handle non-FAQ text using sliding window
232
  while i < len(paragraphs) and len(current_chunk) + len(paragraphs[i]) + 1 <= MAX_CHUNK_SIZE:
233
  if current_chunk:
234
  current_chunk += " " + paragraphs[i]
@@ -236,16 +219,13 @@ def prepare_chunks(text_data):
236
  current_chunk = paragraphs[i]
237
  i += 1
238
 
239
- # Save the chunk if it has content
240
  if current_chunk.strip():
241
  chunks.append(current_chunk.strip())
242
  sources.append(source)
243
 
244
- # If we've added a chunk but haven't advanced, we need to move forward
245
  if i == start_idx:
246
  i += 1
247
 
248
- # Create overlapping chunks for better context preservation
249
  overlap_chunks = []
250
  overlap_sources = []
251
 
@@ -253,16 +233,12 @@ def prepare_chunks(text_data):
253
  overlap_chunks.append(chunks[j])
254
  overlap_sources.append(sources[j])
255
 
256
- # Create an overlapping chunk with the next chunk if it exists
257
  if j < len(chunks) - 1 and chunks[j].endswith(chunks[j+1][:CHUNK_OVERLAP]):
258
- # Skip if there's already significant overlap
259
  continue
260
 
261
  if j < len(chunks) - 1:
262
- # Calculate available space in the current chunk
263
  space_left = MAX_CHUNK_SIZE - len(chunks[j])
264
 
265
- # If there's enough space, add part of the next chunk
266
  if space_left >= CHUNK_OVERLAP:
267
  overlap_text = chunks[j] + " " + chunks[j+1][:CHUNK_OVERLAP]
268
  overlap_chunks.append(overlap_text)
@@ -280,7 +256,6 @@ def initialize_embeddings():
280
 
281
  if embedder is None:
282
  print("Initierar SentenceTransformer och FAISS-index...")
283
- # Ladda och förbered lokal data
284
  print("Laddar textdata...")
285
  text_data = {"local_files": load_local_files()}
286
  print("Förbereder textsegment...")
@@ -295,18 +270,15 @@ def initialize_embeddings():
295
  index.add(embeddings)
296
  print("FAISS-index klart")
297
 
298
- # Print FAQ dictionary keys for debugging
299
  print(f"FAQ Dictionary innehåller {len(faq_dict)} nycklar")
300
  if len(faq_dict) > 0:
301
  payment_keys = [k for k in faq_dict.keys() if any(term in k for term in ["betalsätt", "betalmetod", "betalmedel"])]
302
  print(f"Betalningsrelaterade FAQ-nycklar: {payment_keys[:5]}")
303
 
304
- # Direkt matchningsfunktion för vanliga frågor
305
  def check_direct_match(query):
306
  """Kontrollerar om frågan matchar någon av våra fördefinierade FAQ-svar."""
307
  query_lower = query.lower().strip('?').strip()
308
 
309
- # Explicit check for payment method question
310
  if any(query_lower.startswith(prefix) for prefix in ["hur ändrar jag", "hur byter jag", "hur uppdaterar jag"]) and \
311
  any(term in query_lower for term in ["betalsätt", "betalmetod", "betalmedel", "betalkort", "kort"]):
312
  payment_answer = """Så här gör du om du vill byta betalkort:
@@ -321,35 +293,28 @@ def check_direct_match(query):
321
  OBS! Se till att kortet har pengar och att det är upplåst för internetbetalningar."""
322
  return payment_answer
323
 
324
- # Check if query directly matches a FAQ
325
  if query_lower in faq_dict:
326
  return faq_dict[query_lower]
327
 
328
- # Check for close matches using pattern matching
329
  for key, value in faq_dict.items():
330
- # Find questions about changing things with synonyms
331
  if ("ändra" in query_lower or "byta" in query_lower or "uppdatera" in query_lower) and \
332
  ("ändra" in key or "byta" in key or "uppdatera" in key):
333
- # Check if key and query share important terms
334
  query_terms = set(query_lower.split())
335
  key_terms = set(key.split())
336
- if len(query_terms.intersection(key_terms)) >= 2: # At least 2 words in common
337
  return value
338
 
339
  return None
340
 
341
  def retrieve_context(query, k=RETRIEVAL_K):
342
  """Hämtar relevant kontext för frågor med direkt matchning för vanliga frågor."""
343
- # Säkerställ att modeller är laddade
344
  initialize_embeddings()
345
 
346
- # Först, kolla efter direktmatchningar för vanliga frågor
347
  direct_match = check_direct_match(query)
348
  if direct_match:
349
  print(f"Direkt matchning hittad för frågan: {query}")
350
  return f"Fråga: {query}\nSvar: {direct_match}", ["direct_match"]
351
 
352
- # Om ingen direktmatchning, använd vanlig embedding-sökning
353
  query_embedding = embedder.encode([query], convert_to_numpy=True)
354
  query_embedding /= np.linalg.norm(query_embedding)
355
  D, I = index.search(query_embedding, k)
@@ -360,21 +325,17 @@ def retrieve_context(query, k=RETRIEVAL_K):
360
  sources.add(chunk_sources[idx])
361
  return " ".join(retrieved), list(sources)
362
 
363
- # Ladda prompt template
364
  prompt_template = load_prompt()
365
 
366
  def generate_answer(query):
367
- """Genererar svar baserat på fråga och retrieval-baserad kontext med Claude Haiku."""
368
- # Hämta relevant kontext via RAG istället för hela databasen
369
  context, sources = retrieve_context(query)
370
 
371
  if not context.strip():
372
- return "Jag hittar ingen relevant information i mina källor.\n\nDetta är ett AI genererat svar."
373
 
374
- # System-prompts och användarfråga
375
  system_prompt = prompt_template
376
 
377
- # Skapa ett renare användarmeddelande med bara den relevanta kontexten
378
  user_message = f"""Jag har en fråga om ChargeNode.
379
 
380
  Relevant kontext för frågan:
@@ -383,10 +344,9 @@ Relevant kontext för frågan:
383
  Min fråga är: {query}"""
384
 
385
  try:
386
- # Använd Claude Haiku med RAG-baserad kontext
387
  response = anthropic_client.messages.create(
388
- model="claude-3-7-sonnet-20250219",
389
- max_tokens=500,
390
  temperature=0.3,
391
  system=system_prompt,
392
  messages=[
@@ -394,9 +354,20 @@ Min fråga är: {query}"""
394
  ]
395
  )
396
  answer = response.content[0].text
 
397
  return answer + "\n\nAI-genererat. Otillräcklig hjälp? Kontakta support@chargenode.eu eller 010-2051055"
 
 
 
 
 
 
 
 
 
398
  except Exception as e:
399
- return f"Tekniskt fel: {str(e)}\n\nAI-genererat. Kontakta support@chargenode.eu eller 010-2051055"
 
400
 
401
  # --- Slack Integration ---
402
  def send_to_slack(subject, content, color="#2a9d8f"):
@@ -407,7 +378,6 @@ def send_to_slack(subject, content, color="#2a9d8f"):
407
  return False
408
 
409
  try:
410
- # Formatera meddelandet för Slack
411
  payload = {
412
  "blocks": [
413
  {
@@ -445,11 +415,7 @@ def send_to_slack(subject, content, color="#2a9d8f"):
445
 
446
  # --- Feedback & Like-funktion ---
447
  def vote(data: gr.LikeData):
448
- """
449
- Hanterar feedback från Gradio's inbyggda like-funktion.
450
- data.liked är True om uppvote, annars False.
451
- data.value innehåller information om meddelandet.
452
- """
453
  feedback_type = "up" if data.liked else "down"
454
  global last_log
455
  log_entry = {
@@ -457,19 +423,16 @@ def vote(data: gr.LikeData):
457
  "feedback": feedback_type,
458
  "bot_reply": data.value if not isinstance(data.value, dict) else data.value.get("value")
459
  }
460
- # Om global logdata finns, lägg till ytterligare metadata.
461
  if last_log:
462
  log_entry.update({
463
  "session_id": last_log.get("session_id"),
464
  "user_message": last_log.get("user_message"),
465
  })
466
 
467
- # Använd den förbättrade loggfunktionen
468
  safe_append_to_log(log_entry)
469
 
470
- # Skicka feedback till Slack
471
  try:
472
- if feedback_type == "down": # Skicka bara negativ feedback
473
  feedback_message = f"""
474
  *⚠️ Negativ feedback registrerad*
475
 
@@ -477,7 +440,6 @@ def vote(data: gr.LikeData):
477
 
478
  *Svar:* {log_entry.get('bot_reply', 'Okänt svar')[:300]}{'...' if len(log_entry.get('bot_reply', '')) > 300 else ''}
479
  """
480
- # Skicka asynkront
481
  threading.Thread(
482
  target=lambda: send_to_slack("Negativ feedback", feedback_message, "#ff0000"),
483
  daemon=True
@@ -535,7 +497,6 @@ def get_feedback_stats(logs):
535
  if feedback in feedback_count:
536
  feedback_count[feedback] += 1
537
 
538
- # Samla exempel på negativ feedback
539
  if feedback == "down" and 'user_message' in log and len(negative_feedback_examples) < 10:
540
  negative_feedback_examples.append({
541
  'user_message': log.get('user_message', 'Okänd fråga'),
@@ -548,13 +509,11 @@ def generate_monthly_stats(days=30):
548
  """Genererar omfattande statistik över botanvändning för den senaste månaden."""
549
  print(f"Genererar statistik för de senaste {days} dagarna...")
550
 
551
- # Hämta loggar
552
  logs = read_logs()
553
 
554
  if not logs:
555
  return {"error": "Inga loggar hittades för den angivna perioden"}
556
 
557
- # Filtrera på datumintervall
558
  now = datetime.now()
559
  cutoff_date = now - timedelta(days=days)
560
  filtered_logs = []
@@ -566,26 +525,22 @@ def generate_monthly_stats(days=30):
566
  if log_date >= cutoff_date:
567
  filtered_logs.append(log)
568
  except:
569
- pass # Hoppa över poster med ogiltigt datum
570
 
571
  logs = filtered_logs
572
 
573
- # Basstatistik
574
  total_conversations = sum(1 for log in logs if 'user_message' in log)
575
  unique_sessions = len(set(log.get('session_id', 'unknown') for log in logs if 'session_id' in log))
576
  unique_users = len(set(log.get('user_id', 'unknown') for log in logs if 'user_id' in log))
577
 
578
- # Feedback-statistik
579
  feedback_logs = [log for log in logs if 'feedback' in log]
580
  positive_feedback = sum(1 for log in feedback_logs if log.get('feedback') == 'up')
581
  negative_feedback = sum(1 for log in feedback_logs if log.get('feedback') == 'down')
582
  feedback_ratio = (positive_feedback / len(feedback_logs) * 100) if feedback_logs else 0
583
 
584
- # Svarstidsstatistik
585
  response_times = [log.get('response_time', 0) for log in logs if 'response_time' in log]
586
  avg_response_time = sum(response_times) / len(response_times) if response_times else 0
587
 
588
- # Plattformsstatistik
589
  platforms = {}
590
  browsers = {}
591
  operating_systems = {}
@@ -597,7 +552,6 @@ def generate_monthly_stats(days=30):
597
  if 'os' in log:
598
  operating_systems[log['os']] = operating_systems.get(log['os'], 0) + 1
599
 
600
- # Skapa rapport
601
  report = {
602
  "period": f"Senaste {days} dagarna",
603
  "generated_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
@@ -627,10 +581,8 @@ def simple_status_report():
627
  print("Genererar statusrapport för Slack...")
628
 
629
  try:
630
- # Generera statistik
631
- stats = generate_monthly_stats(days=7) # Senaste veckan
632
 
633
- # Skapa innehåll för Slack
634
  now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
635
  subject = f"ChargeNode AI Bot - Status {now}"
636
 
@@ -638,7 +590,6 @@ def simple_status_report():
638
  content = f"*Fel vid generering av statistik:* {stats['error']}"
639
  return send_to_slack(subject, content, "#ff0000")
640
 
641
- # Formatera statistik
642
  basic = stats["basic_stats"]
643
  feedback = stats["feedback"]
644
  perf = stats["performance"]
@@ -658,7 +609,6 @@ def simple_status_report():
658
  - Nöjdhet: {feedback['ratio_percent']}%
659
  """
660
 
661
- # Lägg till de senaste konversationerna
662
  logs = read_logs()
663
  conversations = get_latest_conversations(logs, 3)
664
 
@@ -671,13 +621,11 @@ def simple_status_report():
671
  > *Svar:* {conv['bot_reply'][:100]}{'...' if len(conv['bot_reply']) > 100 else ''}
672
  """
673
 
674
- # Skicka till Slack
675
  return send_to_slack(subject, content, "#2a9d8f")
676
 
677
  except Exception as e:
678
  print(f"Fel vid generering av statusrapport: {e}")
679
 
680
- # Skicka felmeddelande till Slack
681
  error_subject = f"ChargeNode AI Bot - Fel vid statusrapport"
682
  error_content = f"*Fel vid generering av statusrapport:* {str(e)}"
683
  return send_to_slack(error_subject, error_content, "#ff0000")
@@ -685,7 +633,6 @@ def simple_status_report():
685
  def send_support_to_slack(områdeskod, uttagsnummer, email, chat_history):
686
  """Skickar en supportförfrågan till Slack."""
687
  try:
688
- # Formatera chat-historiken
689
  chat_content = ""
690
  for msg in chat_history:
691
  if msg['role'] == 'user':
@@ -693,7 +640,6 @@ def send_support_to_slack(områdeskod, uttagsnummer, email, chat_history):
693
  elif msg['role'] == 'assistant':
694
  chat_content += f">*Bot:* {msg['content'][:300]}{'...' if len(msg['content']) > 300 else ''}\n\n"
695
 
696
- # Skapa innehåll
697
  subject = f"Support förfrågan - {datetime.now().strftime('%Y-%m-%d %H:%M')}"
698
 
699
  content = f"""
@@ -707,7 +653,6 @@ def send_support_to_slack(områdeskod, uttagsnummer, email, chat_history):
707
  {chat_content}
708
  """
709
 
710
- # Skicka till Slack
711
  return send_to_slack(subject, content, "#e76f51")
712
  except Exception as e:
713
  print(f"Fel vid sändning av support till Slack: {type(e).__name__}: {e}")
@@ -716,12 +661,10 @@ def send_support_to_slack(områdeskod, uttagsnummer, email, chat_history):
716
  # --- Schemaläggning av rapporter ---
717
  def run_scheduler():
718
  """Kör schemaläggaren i en separat tråd med förenklad statusrapportering."""
719
- # Använd den förenklade funktionen för rapportering
720
  schedule.every().day.at("08:00").do(simple_status_report)
721
  schedule.every().day.at("12:00").do(simple_status_report)
722
  schedule.every().day.at("17:00").do(simple_status_report)
723
 
724
- # Veckorapport på måndagar
725
  schedule.every().monday.at("09:00").do(lambda: send_to_slack(
726
  "Veckostatistik",
727
  f"*ChargeNode AI Bot - Veckostatistik*\n\n{json.dumps(generate_monthly_stats(7), indent=2)}",
@@ -730,16 +673,13 @@ def run_scheduler():
730
 
731
  while True:
732
  schedule.run_pending()
733
- time.sleep(60) # Kontrollera varje minut
734
 
735
- # Starta schemaläggaren i en separat tråd
736
  scheduler_thread = threading.Thread(target=run_scheduler, daemon=True)
737
  scheduler_thread.start()
738
 
739
- # Kör en statusrapport vid uppstart för att verifiera att allt fungerar
740
  try:
741
  print("Skickar en inledande statusrapport för att verifiera Slack-integrationen...")
742
- # Anropa inte direkt här - sker i schemaläggaren
743
  except Exception as e:
744
  print(f"Information: Statusrapport kommer att skickas enligt schema: {e}")
745
 
@@ -759,7 +699,6 @@ h1 {font-family: Helvetica, sans-serif; color: #2a9d8f; text-align: center; marg
759
  .gr-form {padding: 10px; border: 1px solid #eee; border-radius: 4px; margin-bottom: 10px;}
760
  .chat-preview {max-height: 150px; overflow-y: auto; border: 1px solid #eee; padding: 8px; margin-top: 10px; font-size: 12px; background-color: #f9f9f9;}
761
  .success-message {font-size: 16px; font-weight: normal; margin-bottom: 15px;}
762
- /* Dölj Gradio-footer */
763
  footer {display: none !important;}
764
  .footer {display: none !important;}
765
  .gr-footer {display: none !important;}
@@ -771,7 +710,6 @@ footer {display: none !important;}
771
  with gr.Blocks(css=custom_css, title="ChargeNode Kundtjänst") as app:
772
  gr.Markdown("Ställ din fråga om ChargeNodes produkter och tjänster nedan. Om du inte gillar botten, så ring oss gärna på 010 – 205 10 55")
773
 
774
- # Chat interface
775
  with gr.Group(visible=True) as chat_interface:
776
  chatbot = gr.Chatbot(value=initial_chat, type="messages", elem_id="chatbot_conversation")
777
  chatbot.like(vote, None, None)
@@ -785,7 +723,6 @@ with gr.Blocks(css=custom_css, title="ChargeNode Kundtjänst") as app:
785
  with gr.Column(scale=1):
786
  support_btn = gr.Button("Behöver du mer hjälp?", elem_classes="support-btn")
787
 
788
- # Support form interface (initially hidden)
789
  with gr.Group(visible=False) as support_interface:
790
  gr.Markdown("### Vänligen fyll i din områdeskod, uttagsnummer och din email adress")
791
 
@@ -801,7 +738,6 @@ with gr.Blocks(css=custom_css, title="ChargeNode Kundtjänst") as app:
801
  back_btn = gr.Button("Tillbaka")
802
  send_support_btn = gr.Button("Skicka")
803
 
804
- # Success message (initially hidden)
805
  with gr.Group(visible=False) as success_interface:
806
  gr.Markdown("Tack för att du kontaktar support@chargenode.eu. Vi återkommer inom kort", elem_classes="success-message")
807
  back_to_chat_btn = gr.Button("Tillbaka till chatten")
@@ -815,7 +751,6 @@ with gr.Blocks(css=custom_css, title="ChargeNode Kundtjänst") as app:
815
  timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
816
  session_id = str(uuid.uuid4())
817
 
818
- # Använd session_id från tidigare logg om det finns
819
  if last_log and 'session_id' in last_log:
820
  session_id = last_log.get('session_id')
821
 
@@ -849,13 +784,10 @@ with gr.Blocks(css=custom_css, title="ChargeNode Kundtjänst") as app:
849
  "platform": platform
850
  }
851
 
852
- # Använd den förbättrade loggfunktionen
853
  safe_append_to_log(log_data)
854
  last_log = log_data
855
 
856
- # Skicka varje konversation direkt till Slack
857
  try:
858
- # Konversationsinnehåll
859
  conversation_content = f"""
860
  *Ny konversation {timestamp}*
861
 
@@ -865,7 +797,6 @@ with gr.Blocks(css=custom_css, title="ChargeNode Kundtjänst") as app:
865
 
866
  *Sessionsinfo:* {session_id[:8]}... | {browser} | {platform}
867
  """
868
- # Skicka asynkront för att inte blockera svarstiden
869
  threading.Thread(
870
  target=lambda: send_to_slack(f"Ny konversation", conversation_content),
871
  daemon=True
@@ -885,7 +816,7 @@ with gr.Blocks(css=custom_css, title="ChargeNode Kundtjänst") as app:
885
  for msg in chat_history:
886
  sender = "Användare" if msg["role"] == "user" else "Bot"
887
  content = msg["content"]
888
- if len(content) > 100: # Truncate long messages
889
  content = content[:100] + "..."
890
  preview += f"**{sender}:** {content}\n\n"
891
 
@@ -911,7 +842,6 @@ with gr.Blocks(css=custom_css, title="ChargeNode Kundtjänst") as app:
911
  """Hanterar formulärinskickningen med bättre felhantering."""
912
  print(f"Support-förfrågan: områdeskod={områdeskod}, uttagsnummer={uttagsnummer}, email={email}")
913
 
914
- # Validera input med tydligare loggning
915
  validation_errors = []
916
 
917
  if områdeskod and not områdeskod.isdigit():
@@ -935,7 +865,6 @@ with gr.Blocks(css=custom_css, title="ChargeNode Kundtjänst") as app:
935
  else:
936
  print(f"Validerar email: '{email}' (ok)")
937
 
938
- # Om det finns valideringsfel
939
  if validation_errors:
940
  print(f"Valideringsfel: {validation_errors}")
941
  return {
@@ -945,18 +874,15 @@ with gr.Blocks(css=custom_css, title="ChargeNode Kundtjänst") as app:
945
  chat_preview: "\n".join(["**Fel:**"] + validation_errors)
946
  }
947
 
948
- # Om formuläret klarade valideringen, försök skicka till Slack
949
  try:
950
  print("Försöker skicka supportförfrågan till Slack...")
951
 
952
- # Skapa en förenklad chathistorik för loggning
953
  chat_summary = []
954
  for msg in chat_history:
955
  if 'role' in msg and 'content' in msg:
956
  chat_summary.append(f"{msg['role']}: {msg['content'][:30]}...")
957
  print(f"Chatthistorik att skicka: {chat_summary}")
958
 
959
- # Skicka till Slack
960
  success = send_support_to_slack(områdeskod, uttagsnummer, email, chat_history)
961
 
962
  if success:
@@ -994,7 +920,6 @@ with gr.Blocks(css=custom_css, title="ChargeNode Kundtjänst") as app:
994
  [chat_interface, support_interface, success_interface, chat_preview]
995
  )
996
 
997
- # Initialisera embeddings vid uppstart
998
  print("Förbereder embedding-modell och index...")
999
  initialize_embeddings()
1000
  print("Embedding-modell och index redo!")
 
2
  import json
3
  import time
4
  import requests
5
+ from anthropic import Anthropic, RateLimitError, APIError
6
  from openai import OpenAI
7
  import gradio as gr
8
  import pandas as pd
 
19
 
20
  # --- Konfiguration ---
21
  CHARGENODE_URL = "https://www.chargenode.eu"
22
+ MAX_CHUNK_SIZE = 2000
23
+ CHUNK_OVERLAP = 200
24
+ RETRIEVAL_K = 5
25
 
26
  # Kontrollera om vi kör i Hugging Face-miljön
27
  IS_HUGGINGFACE = os.environ.get("SPACE_ID") is not None
 
45
  # Skapa en tom loggfil om den inte finns
46
  if not os.path.exists(log_file_path):
47
  with open(log_file_path, "w", encoding="utf-8") as f:
48
+ f.write("")
49
  print(f"Skapade tom loggfil: {log_file_path}")
50
 
51
  hf_token = os.environ.get("HF_TOKEN")
 
58
  repo_type="dataset",
59
  folder_path=log_folder,
60
  path_in_repo="logs_v2",
61
+ every=300,
62
  token=hf_token
63
  )
64
 
65
  # --- Globala variabler ---
66
+ last_log = None
67
 
68
  # Globala variabler för embeddings
69
  embedder = None
 
71
  index = None
72
  chunks = []
73
  chunk_sources = []
74
+ faq_dict = {}
75
 
76
  # --- Förbättrad loggfunktion ---
77
  def safe_append_to_log(log_entry):
78
  """Säker metod för att lägga till loggdata utan att förlora historisk information."""
79
  try:
 
80
  with open(log_file_path, "a", encoding="utf-8") as log_file:
81
  log_json = json.dumps(log_entry)
82
  log_file.write(log_json + "\n")
83
+ log_file.flush()
84
 
85
  print(f"Loggpost tillagd: {log_entry.get('timestamp', 'okänd tid')}")
86
  return True
 
88
  except Exception as e:
89
  print(f"Fel vid loggning: {e}")
90
 
 
91
  try:
92
  os.makedirs(os.path.dirname(log_file_path), exist_ok=True)
93
 
 
94
  with open(log_file_path, "a", encoding="utf-8") as log_file:
95
  log_json = json.dumps(log_entry)
96
  log_file.write(log_json + "\n")
 
115
  with open(file, "r", encoding="utf-8") as f:
116
  content = f.read()
117
  elif file.endswith(".docx"):
118
+ from docx import Document
119
  content = "\n".join([p.text for p in Document(file).paragraphs])
120
  elif file.endswith(".pdf"):
121
+ import PyPDF2
122
  with open(file, "rb") as f:
123
  reader = PyPDF2.PdfReader(f)
124
  content = "\n".join([p.extract_text() or "" for p in reader.pages])
 
129
  df = pd.read_excel(file)
130
  rows = []
131
  for index, row in df.iterrows():
 
132
  row_text = f"Fråga: {row['Fråga']}\nSvar: {row['Svar']}"
133
 
 
134
  if 'kategori' in df.columns:
135
  row_text += f"\nKategori: {row['kategori']}"
136
+ elif 'Kategori' in df.columns:
137
  row_text += f"\nKategori: {row['Kategori']}"
138
 
139
  rows.append(row_text)
 
168
  global faq_dict
169
 
170
  for source, text in text_data.items():
 
171
  paragraphs = [p for p in text.split("\n") if p.strip()]
172
 
 
173
  i = 0
174
  while i < len(paragraphs):
 
175
  current_chunk = ""
176
  start_idx = i
177
 
 
178
  if i < len(paragraphs) and paragraphs[i].startswith("Fråga:"):
179
+ question = paragraphs[i][7:].strip()
180
  current_chunk = paragraphs[i]
181
  i += 1
182
 
 
183
  while i < len(paragraphs) and not paragraphs[i].startswith("Fråga:"):
 
184
  if len(current_chunk) + len(paragraphs[i]) + 1 <= MAX_CHUNK_SIZE:
185
  current_chunk += "\n" + paragraphs[i]
186
  else:
 
187
  if "Svar:" in current_chunk:
188
+ if len(current_chunk) > MAX_CHUNK_SIZE * 1.5:
 
189
  break
190
  else:
191
  current_chunk += "\n" + paragraphs[i]
 
193
  break
194
  i += 1
195
 
 
196
  if "Svar:" in current_chunk:
197
  answer_start = current_chunk.find("Svar:")
198
  answer_text = current_chunk[answer_start + 5:].strip()
199
 
 
200
  if any(term in question.lower() for term in ["betalsätt", "betalmetod", "betalmedel", "kort",
201
  "betalkort", "betalning", "betala"]):
202
  payment_variations = [
 
210
  for variation in payment_variations:
211
  faq_dict[variation] = answer_text
212
 
 
213
  faq_dict[question.lower()] = answer_text
214
  else:
 
215
  while i < len(paragraphs) and len(current_chunk) + len(paragraphs[i]) + 1 <= MAX_CHUNK_SIZE:
216
  if current_chunk:
217
  current_chunk += " " + paragraphs[i]
 
219
  current_chunk = paragraphs[i]
220
  i += 1
221
 
 
222
  if current_chunk.strip():
223
  chunks.append(current_chunk.strip())
224
  sources.append(source)
225
 
 
226
  if i == start_idx:
227
  i += 1
228
 
 
229
  overlap_chunks = []
230
  overlap_sources = []
231
 
 
233
  overlap_chunks.append(chunks[j])
234
  overlap_sources.append(sources[j])
235
 
 
236
  if j < len(chunks) - 1 and chunks[j].endswith(chunks[j+1][:CHUNK_OVERLAP]):
 
237
  continue
238
 
239
  if j < len(chunks) - 1:
 
240
  space_left = MAX_CHUNK_SIZE - len(chunks[j])
241
 
 
242
  if space_left >= CHUNK_OVERLAP:
243
  overlap_text = chunks[j] + " " + chunks[j+1][:CHUNK_OVERLAP]
244
  overlap_chunks.append(overlap_text)
 
256
 
257
  if embedder is None:
258
  print("Initierar SentenceTransformer och FAISS-index...")
 
259
  print("Laddar textdata...")
260
  text_data = {"local_files": load_local_files()}
261
  print("Förbereder textsegment...")
 
270
  index.add(embeddings)
271
  print("FAISS-index klart")
272
 
 
273
  print(f"FAQ Dictionary innehåller {len(faq_dict)} nycklar")
274
  if len(faq_dict) > 0:
275
  payment_keys = [k for k in faq_dict.keys() if any(term in k for term in ["betalsätt", "betalmetod", "betalmedel"])]
276
  print(f"Betalningsrelaterade FAQ-nycklar: {payment_keys[:5]}")
277
 
 
278
  def check_direct_match(query):
279
  """Kontrollerar om frågan matchar någon av våra fördefinierade FAQ-svar."""
280
  query_lower = query.lower().strip('?').strip()
281
 
 
282
  if any(query_lower.startswith(prefix) for prefix in ["hur ändrar jag", "hur byter jag", "hur uppdaterar jag"]) and \
283
  any(term in query_lower for term in ["betalsätt", "betalmetod", "betalmedel", "betalkort", "kort"]):
284
  payment_answer = """Så här gör du om du vill byta betalkort:
 
293
  OBS! Se till att kortet har pengar och att det är upplåst för internetbetalningar."""
294
  return payment_answer
295
 
 
296
  if query_lower in faq_dict:
297
  return faq_dict[query_lower]
298
 
 
299
  for key, value in faq_dict.items():
 
300
  if ("ändra" in query_lower or "byta" in query_lower or "uppdatera" in query_lower) and \
301
  ("ändra" in key or "byta" in key or "uppdatera" in key):
 
302
  query_terms = set(query_lower.split())
303
  key_terms = set(key.split())
304
+ if len(query_terms.intersection(key_terms)) >= 2:
305
  return value
306
 
307
  return None
308
 
309
  def retrieve_context(query, k=RETRIEVAL_K):
310
  """Hämtar relevant kontext för frågor med direkt matchning för vanliga frågor."""
 
311
  initialize_embeddings()
312
 
 
313
  direct_match = check_direct_match(query)
314
  if direct_match:
315
  print(f"Direkt matchning hittad för frågan: {query}")
316
  return f"Fråga: {query}\nSvar: {direct_match}", ["direct_match"]
317
 
 
318
  query_embedding = embedder.encode([query], convert_to_numpy=True)
319
  query_embedding /= np.linalg.norm(query_embedding)
320
  D, I = index.search(query_embedding, k)
 
325
  sources.add(chunk_sources[idx])
326
  return " ".join(retrieved), list(sources)
327
 
 
328
  prompt_template = load_prompt()
329
 
330
  def generate_answer(query):
331
+ """Genererar svar baserat på fråga och retrieval-baserad kontext med Claude Sonnet 4.5."""
 
332
  context, sources = retrieve_context(query)
333
 
334
  if not context.strip():
335
+ return "Jag hittar ingen relevant information i mina källor.\n\nDetta är ett AI-genererat svar."
336
 
 
337
  system_prompt = prompt_template
338
 
 
339
  user_message = f"""Jag har en fråga om ChargeNode.
340
 
341
  Relevant kontext för frågan:
 
344
  Min fråga är: {query}"""
345
 
346
  try:
 
347
  response = anthropic_client.messages.create(
348
+ model="claude-sonnet-4-5-20250929", # ✅ Claude Sonnet 4.5
349
+ max_tokens=1500, # ✅ Ökat från 500
350
  temperature=0.3,
351
  system=system_prompt,
352
  messages=[
 
354
  ]
355
  )
356
  answer = response.content[0].text
357
+ print("✅ Använder Claude Sonnet 4.5")
358
  return answer + "\n\nAI-genererat. Otillräcklig hjälp? Kontakta support@chargenode.eu eller 010-2051055"
359
+
360
+ except RateLimitError:
361
+ print("⚠️ Rate limit nådd")
362
+ return "För många förfrågningar just nu. Försök igen om några sekunder.\n\nKontakta support@chargenode.eu eller 010-2051055"
363
+
364
+ except APIError as e:
365
+ print(f"⚠️ API-fel: {e}")
366
+ return "Tekniskt fel uppstod. Vänligen försök igen.\n\nKontakta support@chargenode.eu eller 010-2051055"
367
+
368
  except Exception as e:
369
+ print(f" Oväntat fel: {e}")
370
+ return f"Tekniskt fel: {str(e)}\n\nKontakta support@chargenode.eu eller 010-2051055"
371
 
372
  # --- Slack Integration ---
373
  def send_to_slack(subject, content, color="#2a9d8f"):
 
378
  return False
379
 
380
  try:
 
381
  payload = {
382
  "blocks": [
383
  {
 
415
 
416
  # --- Feedback & Like-funktion ---
417
  def vote(data: gr.LikeData):
418
+ """Hanterar feedback från Gradio's inbyggda like-funktion."""
 
 
 
 
419
  feedback_type = "up" if data.liked else "down"
420
  global last_log
421
  log_entry = {
 
423
  "feedback": feedback_type,
424
  "bot_reply": data.value if not isinstance(data.value, dict) else data.value.get("value")
425
  }
 
426
  if last_log:
427
  log_entry.update({
428
  "session_id": last_log.get("session_id"),
429
  "user_message": last_log.get("user_message"),
430
  })
431
 
 
432
  safe_append_to_log(log_entry)
433
 
 
434
  try:
435
+ if feedback_type == "down":
436
  feedback_message = f"""
437
  *⚠️ Negativ feedback registrerad*
438
 
 
440
 
441
  *Svar:* {log_entry.get('bot_reply', 'Okänt svar')[:300]}{'...' if len(log_entry.get('bot_reply', '')) > 300 else ''}
442
  """
 
443
  threading.Thread(
444
  target=lambda: send_to_slack("Negativ feedback", feedback_message, "#ff0000"),
445
  daemon=True
 
497
  if feedback in feedback_count:
498
  feedback_count[feedback] += 1
499
 
 
500
  if feedback == "down" and 'user_message' in log and len(negative_feedback_examples) < 10:
501
  negative_feedback_examples.append({
502
  'user_message': log.get('user_message', 'Okänd fråga'),
 
509
  """Genererar omfattande statistik över botanvändning för den senaste månaden."""
510
  print(f"Genererar statistik för de senaste {days} dagarna...")
511
 
 
512
  logs = read_logs()
513
 
514
  if not logs:
515
  return {"error": "Inga loggar hittades för den angivna perioden"}
516
 
 
517
  now = datetime.now()
518
  cutoff_date = now - timedelta(days=days)
519
  filtered_logs = []
 
525
  if log_date >= cutoff_date:
526
  filtered_logs.append(log)
527
  except:
528
+ pass
529
 
530
  logs = filtered_logs
531
 
 
532
  total_conversations = sum(1 for log in logs if 'user_message' in log)
533
  unique_sessions = len(set(log.get('session_id', 'unknown') for log in logs if 'session_id' in log))
534
  unique_users = len(set(log.get('user_id', 'unknown') for log in logs if 'user_id' in log))
535
 
 
536
  feedback_logs = [log for log in logs if 'feedback' in log]
537
  positive_feedback = sum(1 for log in feedback_logs if log.get('feedback') == 'up')
538
  negative_feedback = sum(1 for log in feedback_logs if log.get('feedback') == 'down')
539
  feedback_ratio = (positive_feedback / len(feedback_logs) * 100) if feedback_logs else 0
540
 
 
541
  response_times = [log.get('response_time', 0) for log in logs if 'response_time' in log]
542
  avg_response_time = sum(response_times) / len(response_times) if response_times else 0
543
 
 
544
  platforms = {}
545
  browsers = {}
546
  operating_systems = {}
 
552
  if 'os' in log:
553
  operating_systems[log['os']] = operating_systems.get(log['os'], 0) + 1
554
 
 
555
  report = {
556
  "period": f"Senaste {days} dagarna",
557
  "generated_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
 
581
  print("Genererar statusrapport för Slack...")
582
 
583
  try:
584
+ stats = generate_monthly_stats(days=7)
 
585
 
 
586
  now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
587
  subject = f"ChargeNode AI Bot - Status {now}"
588
 
 
590
  content = f"*Fel vid generering av statistik:* {stats['error']}"
591
  return send_to_slack(subject, content, "#ff0000")
592
 
 
593
  basic = stats["basic_stats"]
594
  feedback = stats["feedback"]
595
  perf = stats["performance"]
 
609
  - Nöjdhet: {feedback['ratio_percent']}%
610
  """
611
 
 
612
  logs = read_logs()
613
  conversations = get_latest_conversations(logs, 3)
614
 
 
621
  > *Svar:* {conv['bot_reply'][:100]}{'...' if len(conv['bot_reply']) > 100 else ''}
622
  """
623
 
 
624
  return send_to_slack(subject, content, "#2a9d8f")
625
 
626
  except Exception as e:
627
  print(f"Fel vid generering av statusrapport: {e}")
628
 
 
629
  error_subject = f"ChargeNode AI Bot - Fel vid statusrapport"
630
  error_content = f"*Fel vid generering av statusrapport:* {str(e)}"
631
  return send_to_slack(error_subject, error_content, "#ff0000")
 
633
  def send_support_to_slack(områdeskod, uttagsnummer, email, chat_history):
634
  """Skickar en supportförfrågan till Slack."""
635
  try:
 
636
  chat_content = ""
637
  for msg in chat_history:
638
  if msg['role'] == 'user':
 
640
  elif msg['role'] == 'assistant':
641
  chat_content += f">*Bot:* {msg['content'][:300]}{'...' if len(msg['content']) > 300 else ''}\n\n"
642
 
 
643
  subject = f"Support förfrågan - {datetime.now().strftime('%Y-%m-%d %H:%M')}"
644
 
645
  content = f"""
 
653
  {chat_content}
654
  """
655
 
 
656
  return send_to_slack(subject, content, "#e76f51")
657
  except Exception as e:
658
  print(f"Fel vid sändning av support till Slack: {type(e).__name__}: {e}")
 
661
  # --- Schemaläggning av rapporter ---
662
  def run_scheduler():
663
  """Kör schemaläggaren i en separat tråd med förenklad statusrapportering."""
 
664
  schedule.every().day.at("08:00").do(simple_status_report)
665
  schedule.every().day.at("12:00").do(simple_status_report)
666
  schedule.every().day.at("17:00").do(simple_status_report)
667
 
 
668
  schedule.every().monday.at("09:00").do(lambda: send_to_slack(
669
  "Veckostatistik",
670
  f"*ChargeNode AI Bot - Veckostatistik*\n\n{json.dumps(generate_monthly_stats(7), indent=2)}",
 
673
 
674
  while True:
675
  schedule.run_pending()
676
+ time.sleep(60)
677
 
 
678
  scheduler_thread = threading.Thread(target=run_scheduler, daemon=True)
679
  scheduler_thread.start()
680
 
 
681
  try:
682
  print("Skickar en inledande statusrapport för att verifiera Slack-integrationen...")
 
683
  except Exception as e:
684
  print(f"Information: Statusrapport kommer att skickas enligt schema: {e}")
685
 
 
699
  .gr-form {padding: 10px; border: 1px solid #eee; border-radius: 4px; margin-bottom: 10px;}
700
  .chat-preview {max-height: 150px; overflow-y: auto; border: 1px solid #eee; padding: 8px; margin-top: 10px; font-size: 12px; background-color: #f9f9f9;}
701
  .success-message {font-size: 16px; font-weight: normal; margin-bottom: 15px;}
 
702
  footer {display: none !important;}
703
  .footer {display: none !important;}
704
  .gr-footer {display: none !important;}
 
710
  with gr.Blocks(css=custom_css, title="ChargeNode Kundtjänst") as app:
711
  gr.Markdown("Ställ din fråga om ChargeNodes produkter och tjänster nedan. Om du inte gillar botten, så ring oss gärna på 010 – 205 10 55")
712
 
 
713
  with gr.Group(visible=True) as chat_interface:
714
  chatbot = gr.Chatbot(value=initial_chat, type="messages", elem_id="chatbot_conversation")
715
  chatbot.like(vote, None, None)
 
723
  with gr.Column(scale=1):
724
  support_btn = gr.Button("Behöver du mer hjälp?", elem_classes="support-btn")
725
 
 
726
  with gr.Group(visible=False) as support_interface:
727
  gr.Markdown("### Vänligen fyll i din områdeskod, uttagsnummer och din email adress")
728
 
 
738
  back_btn = gr.Button("Tillbaka")
739
  send_support_btn = gr.Button("Skicka")
740
 
 
741
  with gr.Group(visible=False) as success_interface:
742
  gr.Markdown("Tack för att du kontaktar support@chargenode.eu. Vi återkommer inom kort", elem_classes="success-message")
743
  back_to_chat_btn = gr.Button("Tillbaka till chatten")
 
751
  timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
752
  session_id = str(uuid.uuid4())
753
 
 
754
  if last_log and 'session_id' in last_log:
755
  session_id = last_log.get('session_id')
756
 
 
784
  "platform": platform
785
  }
786
 
 
787
  safe_append_to_log(log_data)
788
  last_log = log_data
789
 
 
790
  try:
 
791
  conversation_content = f"""
792
  *Ny konversation {timestamp}*
793
 
 
797
 
798
  *Sessionsinfo:* {session_id[:8]}... | {browser} | {platform}
799
  """
 
800
  threading.Thread(
801
  target=lambda: send_to_slack(f"Ny konversation", conversation_content),
802
  daemon=True
 
816
  for msg in chat_history:
817
  sender = "Användare" if msg["role"] == "user" else "Bot"
818
  content = msg["content"]
819
+ if len(content) > 100:
820
  content = content[:100] + "..."
821
  preview += f"**{sender}:** {content}\n\n"
822
 
 
842
  """Hanterar formulärinskickningen med bättre felhantering."""
843
  print(f"Support-förfrågan: områdeskod={områdeskod}, uttagsnummer={uttagsnummer}, email={email}")
844
 
 
845
  validation_errors = []
846
 
847
  if områdeskod and not områdeskod.isdigit():
 
865
  else:
866
  print(f"Validerar email: '{email}' (ok)")
867
 
 
868
  if validation_errors:
869
  print(f"Valideringsfel: {validation_errors}")
870
  return {
 
874
  chat_preview: "\n".join(["**Fel:**"] + validation_errors)
875
  }
876
 
 
877
  try:
878
  print("Försöker skicka supportförfrågan till Slack...")
879
 
 
880
  chat_summary = []
881
  for msg in chat_history:
882
  if 'role' in msg and 'content' in msg:
883
  chat_summary.append(f"{msg['role']}: {msg['content'][:30]}...")
884
  print(f"Chatthistorik att skicka: {chat_summary}")
885
 
 
886
  success = send_support_to_slack(områdeskod, uttagsnummer, email, chat_history)
887
 
888
  if success:
 
920
  [chat_interface, support_interface, success_interface, chat_preview]
921
  )
922
 
 
923
  print("Förbereder embedding-modell och index...")
924
  initialize_embeddings()
925
  print("Embedding-modell och index redo!")