k96beni commited on
Commit
8eaf509
·
verified ·
1 Parent(s): b7ddf00

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +244 -767
app.py CHANGED
@@ -14,9 +14,6 @@ import uuid
14
  from user_agents import parse as parse_ua
15
  import schedule
16
  import threading
17
- import io
18
- import atexit
19
- import re
20
 
21
  # --- Konfiguration ---
22
  CHARGENODE_URL = "https://www.chargenode.eu"
@@ -58,42 +55,14 @@ scheduler = CommitScheduler(
58
 
59
  # --- Globala variabler ---
60
  last_log = None # Sparar loggdata från senaste svar för feedback
61
- # Lägg till en lock för att skydda åtkomst till last_log
62
- last_log_lock = threading.Lock()
63
 
64
  # --- Förbättrad loggfunktion ---
65
  def safe_append_to_log(log_entry):
66
  """Säker metod för att lägga till loggdata utan att förlora historisk information."""
67
  try:
68
- # Kontrollera att loggmappen finns
69
- os.makedirs(os.path.dirname(log_file_path), exist_ok=True)
70
-
71
- # Kontrollera loggfilens storlek
72
- try:
73
- log_size = os.path.getsize(log_file_path)
74
- # Om loggfilen är större än 10MB, rotera den
75
- if log_size > 10 * 1024 * 1024: # 10MB
76
- backup_path = f"{log_file_path}.bak"
77
- if os.path.exists(backup_path):
78
- os.remove(backup_path)
79
- os.rename(log_file_path, backup_path)
80
- print(f"Loggfil roterad: {log_file_path} -> {backup_path}")
81
- except FileNotFoundError:
82
- # Filen finns inte än, inget att rotera
83
- pass
84
-
85
- # Sanitera loggdata för att undvika potentiella injektioner
86
- sanitized_entry = {}
87
- for k, v in log_entry.items():
88
- if isinstance(v, str):
89
- # Ta bort null-bytes och begränsa längden
90
- sanitized_entry[k] = v.replace('\0', '')[:10000] # Begränsa till 10000 tecken
91
- else:
92
- sanitized_entry[k] = v
93
-
94
  # Öppna filen i append-läge
95
  with open(log_file_path, "a", encoding="utf-8") as log_file:
96
- log_json = json.dumps(sanitized_entry)
97
  log_file.write(log_json + "\n")
98
  log_file.flush() # Säkerställ att data skrivs till disk omedelbart
99
 
@@ -103,22 +72,16 @@ def safe_append_to_log(log_entry):
103
  except Exception as e:
104
  print(f"Fel vid loggning: {e}")
105
 
106
- # Försök skapa mappen om den inte finns (detta bör redan ha gjorts ovan)
107
  try:
108
  os.makedirs(os.path.dirname(log_file_path), exist_ok=True)
109
 
110
- # Försök igen med minimal loggdata för att undvika fel
111
- minimal_entry = {
112
- "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
113
- "error": f"Fel vid loggning: {str(e)[:200]}",
114
- "recovery": True
115
- }
116
-
117
  with open(log_file_path, "a", encoding="utf-8") as log_file:
118
- log_json = json.dumps(minimal_entry)
119
  log_file.write(log_json + "\n")
120
 
121
- print("Minimal loggpost tillagd efter återhämtning")
122
  return True
123
 
124
  except Exception as retry_error:
@@ -189,281 +152,52 @@ def prepare_chunks(text_data):
189
  sources.append(source)
190
  return chunks, sources
191
 
192
- # Lazy-laddning av SentenceTransformer
193
  embedder = None
194
  embeddings = None
195
  index = None
196
- chunks = []
197
- chunk_sources = []
198
- embedding_lock = threading.Lock()
199
 
200
  def initialize_embeddings():
201
  """Initierar SentenceTransformer och FAISS-index vid första anrop."""
202
  global embedder, embeddings, index, chunks, chunk_sources
203
 
204
- # Använd en lock för att förhindra att flera trådar initierar samtidigt
205
- with embedding_lock:
206
- if embedder is None:
207
- try:
208
- print("Initierar SentenceTransformer och FAISS-index...")
209
- # Ladda och förbered lokal data
210
- print("Laddar textdata...")
211
- text_data = {"local_files": load_local_files()}
212
- print("Förbereder textsegment...")
213
- chunks, chunk_sources = prepare_chunks(text_data)
214
- print(f"{len(chunks)} segment laddade")
215
-
216
- if not chunks:
217
- print("Varning: Inga textsegment hittades. Kontrollera textdata.")
218
- # Skapa tomma listor för att undvika fel
219
- chunks = [""]
220
- chunk_sources = ["empty"]
221
 
222
- print("Skapar embeddings...")
223
- embedder = SentenceTransformer('all-MiniLM-L6-v2')
224
- embeddings = embedder.encode(chunks, convert_to_numpy=True)
225
-
226
- # Kontrollera att embeddings inte är tomma
227
- if embeddings.size > 0:
228
- embeddings /= np.linalg.norm(embeddings, axis=1, keepdims=True)
229
- index = faiss.IndexFlatIP(embeddings.shape[1])
230
- index.add(embeddings)
231
- print("FAISS-index klart")
232
- else:
233
- print("Varning: Tomma embeddings. Skapar ett tomt index.")
234
- # Skapa ett tomt index med rätt dimensioner
235
- index = faiss.IndexFlatIP(384) # Standard dimension för all-MiniLM-L6-v2
236
- except Exception as e:
237
- print(f"Fel vid initiering av embeddings: {e}")
238
- # Sätt upp grundläggande värden för att undvika fel
239
- if embedder is None:
240
- print("Försöker återhämta från fel...")
241
- try:
242
- embedder = SentenceTransformer('all-MiniLM-L6-v2')
243
- if not chunks:
244
- chunks = ["Fel vid laddning av data"]
245
- chunk_sources = ["error"]
246
- embeddings = embedder.encode(chunks, convert_to_numpy=True)
247
- index = faiss.IndexFlatIP(embeddings.shape[1])
248
- index.add(embeddings)
249
- print("Återhämtning lyckades")
250
- except Exception as recovery_error:
251
- print(f"Kunde inte återhämta: {recovery_error}")
252
- # Sätt upp dummy-värden som sista utväg
253
- embedder = None
254
- embeddings = np.zeros((1, 384))
255
- index = faiss.IndexFlatIP(384)
256
  def retrieve_context(query, k=RETRIEVAL_K):
257
  """Hämtar relevant kontext för frågor."""
258
  # Säkerställ att modeller är laddade
259
  initialize_embeddings()
260
 
261
- try:
262
- if embedder is None:
263
- print("Varning: Embedder är fortfarande None efter initiering")
264
- return "Kunde inte ladda kontext", ["error"]
265
-
266
- query_embedding = embedder.encode([query], convert_to_numpy=True)
267
-
268
- # Kontrollera att embedding inte är tom
269
- if query_embedding.size == 0:
270
- print("Varning: Tom query embedding")
271
- return "Kunde inte bearbeta frågan", ["error"]
272
-
273
- query_embedding /= np.linalg.norm(query_embedding)
274
-
275
- # Kontrollera att index finns
276
- if index is None:
277
- print("Varning: FAISS-index är None")
278
- return "Kunde inte söka i kontext", ["error"]
279
-
280
- # Säkerställ att k inte är större än antalet element i index
281
- actual_k = min(k, index.ntotal)
282
- if actual_k < k:
283
- print(f"Varning: Justerade k från {k} till {actual_k} baserat på index.ntotal")
284
-
285
- D, I = index.search(query_embedding, actual_k)
286
-
287
- retrieved, sources = [], set()
288
- for idx in I[0]:
289
- if 0 <= idx < len(chunks):
290
- retrieved.append(chunks[idx])
291
- sources.add(chunk_sources[idx])
292
-
293
- if not retrieved:
294
- print("Varning: Ingen relevant kontext hittades")
295
- return "Ingen relevant kontext hittades", ["no_context"]
296
-
297
- return " ".join(retrieved), list(sources)
298
- except Exception as e:
299
- print(f"Fel vid hämtning av kontext: {e}")
300
- return f"Fel vid kontexthämtning: {str(e)[:200]}", ["error"]
301
-
302
- # --- Förbättringsfunktioner ---
303
-
304
- def identify_user_role(message, chat_history):
305
- """Identifierar användarens roll baserat på meddelande och chatthistorik."""
306
- # Nyckelord för olika roller
307
- anlaggningsagare_keywords = ["självfaktura", "portal", "anläggning", "äger", "lastbalansering",
308
- "prissättning", "statistik", "effekt", "laddtjänst", "anläggningsägare"]
309
- laddkund_keywords = ["ladda", "app", "starta laddning", "debitering", "registrering",
310
- "betalning", "kabel", "laddkund", "elbilist", "problem med laddning", "rfid"]
311
- foretagskonto_keywords = ["företagskonto", "faktura", "företag", "portaladmin", "företagets"]
312
-
313
- # Analysera meddelande och chatthistorik (senaste 4 meddelanden)
314
- all_text = message.lower()
315
- history_limit = 4
316
- start_index = max(0, len(chat_history) - history_limit)
317
- for msg in chat_history[start_index:]:
318
- if msg["role"] == "user":
319
- all_text += " " + msg["content"].lower()
320
-
321
- # Räkna förekomster av nyckelord
322
- anlaggningsagare_score = sum(1 for word in anlaggningsagare_keywords if word in all_text)
323
- laddkund_score = sum(1 for word in laddkund_keywords if word in all_text)
324
- foretagskonto_score = sum(1 for word in foretagskonto_keywords if word in all_text)
325
-
326
- # Bestäm roll baserat på högsta poäng (med Laddkund som default)
327
- if anlaggningsagare_score > laddkund_score and anlaggningsagare_score > foretagskonto_score:
328
- return "Anläggningsägare"
329
- elif foretagskonto_score > laddkund_score and foretagskonto_score > anlaggningsagare_score:
330
- return "Företagskonto kund"
331
- else:
332
- # Om laddkund har högst poäng eller om det är oavgjort/inga träffar
333
- return "Laddkund"
334
-
335
- def format_response(response, user_role):
336
- """Formaterar svaret för bättre läsbarhet baserat på användarroll."""
337
-
338
- # Hantera specialfall för "kabeln har fastnat"
339
- if "kabeln har fastnat i bilen:" in response.lower() or "kabeln har fastnat i uttaget:" in response.lower():
340
- formatted = ""
341
- if "kabeln har fastnat i bilen:" in response:
342
- # Extrahera och formatera "fastnat i bilen" delen
343
- start_bil = response.lower().find("kabeln har fastnat i bilen:")
344
- end_bil = response.lower().find("kabeln har fastnat i uttaget:") if "kabeln har fastnat i uttaget:" in response.lower() else len(response)
345
- bil_part = response[start_bil:end_bil].strip()
346
-
347
- formatted += "**" + bil_part.splitlines()[0] + "**\n" # Rubrik
348
- steps = "\n".join(line.strip() for line in bil_part.splitlines()[1:] if line.strip())
349
- # Ersätt nummer med punktlista
350
- steps = re.sub(r"^\s*\d+\.\s*", "- ", steps, flags=re.MULTILINE)
351
- formatted += steps + "\n\n"
352
-
353
- if "kabeln har fastnat i uttaget:" in response:
354
- # Extrahera och formatera "fastnat i uttaget" delen
355
- start_uttag = response.lower().find("kabeln har fastnat i uttaget:")
356
- uttag_part = response[start_uttag:].strip()
357
-
358
- formatted += "**" + uttag_part.splitlines()[0] + "**\n" # Rubrik
359
- steps = "\n".join(line.strip() for line in uttag_part.splitlines()[1:] if line.strip())
360
- # Ersätt nummer med punktlista
361
- steps = re.sub(r"^\s*\d+\.\s*", "- ", steps, flags=re.MULTILINE)
362
- formatted += steps + "\n\n"
363
-
364
- # Lägg till generell kontaktinfo om den inte redan finns
365
- if "010-205 10 55" not in formatted:
366
- formatted += "Om detta inte fungerar är ni välkomna att kontakta oss på 010-205 10 55."
367
-
368
- return formatted.strip()
369
-
370
- # Hantera specialfall för "starta laddning"
371
- if "kontrollera betalning:" in response.lower() and "felsökningssteg:" in response.lower():
372
- formatted = ""
373
- parts = response.split("🔁 Felsökningssteg:")
374
-
375
- # Betalningsdel
376
- payment_part = parts[0].replace("✅ Kontrollera betalning:", "**Kontrollera betalning:**").strip()
377
- payment_part = re.sub(r"^\s*-\s*", "\n- ", payment_part, flags=re.MULTILINE)
378
- formatted += payment_part + "\n\n"
379
-
380
- # Felsökningsdel
381
- troubleshooting_part = parts[1].split("📞 Vid fortsatta problem:")[0]
382
- formatted += "**Felsökningssteg:**" + troubleshooting_part.strip() + "\n\n"
383
- formatted = re.sub(r"^\s*-\s*", "\n- ", formatted, flags=re.MULTILINE)
384
-
385
- # Kontaktinfo-del
386
- contact_part = parts[1].split("📞 Vid fortsatta problem:")[1]
387
- formatted += "**Vid fortsatta problem:**" + contact_part.strip()
388
-
389
- return formatted.strip()
390
-
391
- # Dela upp långa stycken för bättre läsbarhet (om det inte redan är punktlistor)
392
- if "\n-" not in response and "\n*" not in response and len(response) > 250:
393
- # Försök dela vid punkter följt av stor bokstav för att behålla meningar
394
- sentences = re.split(r'(?<=\.)\s+(?=[A-ZÅÄÖ])', response)
395
- formatted_response = ""
396
- current_paragraph = ""
397
- for sentence in sentences:
398
- if len(current_paragraph) + len(sentence) < 200:
399
- current_paragraph += sentence + " "
400
- else:
401
- formatted_response += current_paragraph.strip() + "\n\n"
402
- current_paragraph = sentence + " "
403
- formatted_response += current_paragraph.strip()
404
- response = formatted_response
405
-
406
- # Ta bort eventuell inledande "Svar:" om det finns kvar
407
- if response.lower().startswith("svar:"):
408
- response = response[len("svar:"):].strip()
409
-
410
- return response
411
-
412
- def handle_error_cases(query):
413
- """Identifierar och hanterar vanliga felfall baserat på regex."""
414
- # Notera: Prompten hanterar redan dessa specialfall,
415
- # så denna funktion är mest för framtida bruk eller om prompten misslyckas.
416
- # Den används inte aktivt i generate_answer just nu.
417
- error_patterns = {
418
- "kabeln sitter fast|fastnat|kommer inte loss": "specialfall_kabel",
419
- "kan inte starta|startar inte|problem.*starta": "specialfall_starta",
420
- "system.*problem|nedtid|fungerar inte|appen.*nere": "specialfall_system",
421
- "tillåtelse|får jag ladda|tillåtet": "specialfall_tillatelse",
422
- "logga in|inloggning|lösenord": "specialfall_login"
423
- }
424
-
425
- query_lower = query.lower()
426
-
427
- for pattern, case in error_patterns.items():
428
- if re.search(pattern, query_lower):
429
- print(f"Identifierade specialfall: {case}")
430
- return case # Returnerar typen av specialfall
431
-
432
- return None # Inget specialfall identifierat
433
-
434
- # --- Huvudfunktion för svar ---
435
-
436
- def generate_answer(query, chat_history):
437
- """Genererar svar baserat på fråga och kontextinformation, med rollidentifiering och formatering."""
438
-
439
- # Identifiera användarroll
440
- user_role = identify_user_role(query, chat_history)
441
- print(f"Identifierad användarroll: {user_role}") # För felsökning
442
-
443
- # Hämta relevant kontext
444
  context, sources = retrieve_context(query)
445
-
446
- # Hantera fall där ingen kontext hittas
447
- if not context.strip() or context == "Ingen relevant kontext hittades":
448
- # Använd standard "vet inte"-svaret från prompten
449
- fallback_response = """Hej! Tyvärr har jag inte tillgång till uppgiften just nu och kan därför inte ge dig ett direkt svar.
450
- Om du vill ha hjälp inom kort, mejla support@chargenode.eu – vi svarar vanligtvis inom 24 timmar.
451
- Vid akuta problem, ring 010-205 10 55 (24/7). Om vi har system fel, så rapporteras detta via https://chargenode.statuspage.io/
452
- Jag beklagar att jag inte kunde lösa detta omedelbart, men vårt supportteam står redo att hjälpa dig!"""
453
- return fallback_response # Returnera direkt, ingen formatering behövs
454
-
455
- # Skapa prompten med användarroll inkluderad
456
- role_prompt_addition = f"Tänk på att användaren troligen är en {user_role}."
457
-
458
- prompt = f"""{prompt_template}
459
-
460
- {role_prompt_addition}
461
-
462
- Relevant kontext:
463
- {context}
464
-
465
- Fråga: {query}
466
- Svar (baserat enbart på den indexerade datan):"""
467
  prompt = f"""{prompt_template}
468
 
469
  Relevant kontext:
@@ -474,30 +208,16 @@ Svar (baserat enbart på den indexerade datan):"""
474
  response = client.chat.completions.create(
475
  model="gpt-3.5-turbo",
476
  messages=[
477
- {"role": "system", "content": "Du är en expert på ChargeNodes produkter och tjänster. Svara enbart baserat på den information som finns i den indexerade datan. Anpassa ditt svar till användarens troliga roll."},
478
  {"role": "user", "content": prompt}
479
  ],
480
  temperature=0.2,
481
  max_tokens=500
482
  )
483
  answer = response.choices[0].message.content
484
-
485
- # Formatera svaret
486
- formatted_answer = format_response(answer, user_role)
487
-
488
- # Lägg till standardavslutning om den inte redan finns
489
- standard_ending = "AI-genererat. Otillräcklig hjälp? Kontakta support@chargenode.eu eller 010-2051055"
490
- if standard_ending not in formatted_answer:
491
- # Undvik att lägga till om det är ett "vet inte"-svar som redan har kontaktinfo
492
- if "support@chargenode.eu" not in formatted_answer and "010-205 10 55" not in formatted_answer:
493
- formatted_answer += "\n\n" + standard_ending
494
-
495
- return formatted_answer
496
-
497
  except Exception as e:
498
- print(f"Fel vid anrop till OpenAI: {e}")
499
- # Returnera ett mer informativt felmeddelande till användaren
500
- return f"Ett tekniskt fel uppstod när jag försökte generera ett svar. Vänligen försök igen om en liten stund.\n\nOm problemet kvarstår, kontakta support@chargenode.eu eller ring 010-2051055.\nFelkod: API_ERROR"
501
 
502
  # --- Slack Integration ---
503
  def send_to_slack(subject, content, color="#2a9d8f"):
@@ -552,27 +272,18 @@ def vote(data: gr.LikeData):
552
  data.value innehåller information om meddelandet.
553
  """
554
  feedback_type = "up" if data.liked else "down"
555
- global last_log, last_log_lock
556
-
557
- # Skapa en kopia av data.value för att undvika potentiella race conditions
558
- bot_reply = data.value if not isinstance(data.value, dict) else data.value.get("value", "")
559
- if bot_reply is None:
560
- bot_reply = ""
561
-
562
  log_entry = {
563
  "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
564
  "feedback": feedback_type,
565
- "bot_reply": bot_reply
566
  }
567
-
568
- # Använd lock för att säkert komma åt last_log
569
- with last_log_lock:
570
- # Om global logdata finns, lägg till ytterligare metadata.
571
- if last_log:
572
- log_entry.update({
573
- "session_id": last_log.get("session_id", "unknown"),
574
- "user_message": last_log.get("user_message", "Okänd fråga"),
575
- })
576
 
577
  # Använd den förbättrade loggfunktionen
578
  safe_append_to_log(log_entry)
@@ -580,16 +291,10 @@ def vote(data: gr.LikeData):
580
  # Skicka feedback till Slack
581
  try:
582
  if feedback_type == "down": # Skicka bara negativ feedback
583
- # Hämta user_message säkert
584
- user_message = "Okänd fråga"
585
- with last_log_lock:
586
- if last_log:
587
- user_message = last_log.get("user_message", "Okänd fråga")
588
-
589
  feedback_message = f"""
590
  *⚠️ Negativ feedback registrerad*
591
 
592
- *Fråga:* {user_message}
593
 
594
  *Svar:* {log_entry.get('bot_reply', 'Okänt svar')[:300]}{'...' if len(log_entry.get('bot_reply', '')) > 300 else ''}
595
  """
@@ -829,72 +534,29 @@ def send_support_to_slack(områdeskod, uttagsnummer, email, chat_history):
829
  print(f"Fel vid sändning av support till Slack: {type(e).__name__}: {e}")
830
  return False
831
 
832
- # --- Schemaläggning av rapporter ---
833
  def run_scheduler():
834
  """Kör schemaläggaren i en separat tråd med förenklad statusrapportering."""
835
- # Flagga för att kontrollera om tråden ska fortsätta köra
836
- running = True
837
-
838
- try:
839
- # Använd den förenklade funktionen för rapportering
840
- schedule.every().day.at("08:00").do(simple_status_report)
841
- schedule.every().day.at("12:00").do(simple_status_report)
842
- schedule.every().day.at("17:00").do(simple_status_report)
843
-
844
- # Veckorapport på måndagar
845
- schedule.every().monday.at("09:00").do(lambda: send_to_slack(
846
- "Veckostatistik",
847
- f"*ChargeNode AI Bot - Veckostatistik*\n\n{json.dumps(generate_monthly_stats(7), indent=2)}",
848
- "#3498db"
849
- ))
850
-
851
- while running:
852
- try:
853
- schedule.run_pending()
854
- time.sleep(60) # Kontrollera varje minut
855
- except Exception as e:
856
- print(f"Fel i schemaläggaren: {e}")
857
- time.sleep(300) # Vänta 5 minuter vid fel
858
- except Exception as e:
859
- print(f"Kritiskt fel i schemaläggaren: {e}")
860
- finally:
861
- print("Schemaläggaren avslutad")
862
 
863
  # Starta schemaläggaren i en separat tråd
864
  scheduler_thread = threading.Thread(target=run_scheduler, daemon=True)
865
  scheduler_thread.start()
866
 
867
- # Registrera en atexit-funktion för att städa upp vid avslut
868
-
869
- def cleanup():
870
- """Städa upp resurser vid avslut."""
871
- print("Städar upp resurser...")
872
- # Stäng scheduler om möjligt
873
- schedule.clear()
874
-
875
- # Stäng CommitScheduler om den är aktiv
876
- if 'scheduler' in globals() and scheduler:
877
- try:
878
- scheduler.stop()
879
- print("CommitScheduler stoppad")
880
- except Exception as e:
881
- print(f"Kunde inte stoppa CommitScheduler: {e}")
882
-
883
- # Stäng eventuella öppna filer
884
- try:
885
- # Försök att stänga alla öppna filer
886
- import gc
887
- for obj in gc.get_objects():
888
- if isinstance(obj, io.IOBase) and not obj.closed:
889
- try:
890
- obj.close()
891
- except:
892
- pass
893
- except Exception as e:
894
- print(f"Fel vid stängning av filer: {e}")
895
-
896
- atexit.register(cleanup)
897
-
898
  # Kör en statusrapport vid uppstart för att verifiera att allt fungerar
899
  try:
900
  print("Skickar en inledande statusrapport för att verifiera Slack-integrationen...")
@@ -902,314 +564,6 @@ try:
902
  except Exception as e:
903
  print(f"Information: Statusrapport kommer att skickas enligt schema: {e}")
904
 
905
- # Definiera respond och chat-relaterade funktioner före Gradio UI
906
- def respond(message, chat_history, request: gr.Request):
907
- global last_log, last_log_lock
908
-
909
- # Validera indata
910
- if not message or not isinstance(message, str):
911
- print(f"Varning: Ogiltigt meddelande: {type(message)}")
912
- message = str(message) if message is not None else ""
913
-
914
- # Begränsa meddelandets längd för att förhindra överbelastning
915
- if len(message) > 1000:
916
- message = message[:1000] + "..."
917
- print("Meddelande trunkerat på grund av längd")
918
-
919
- start = time.time()
920
- try:
921
- # Anropa den uppdaterade generate_answer som nu inkluderar chat_history
922
- response = generate_answer(message, chat_history)
923
- except Exception as e:
924
- print(f"Fel vid generering av svar: {e}")
925
- response = f"Tyvärr uppstod ett tekniskt fel. Vänligen försök igen eller kontakta support@chargenode.eu. Felkod: RESPOND_ERROR"
926
-
927
- elapsed = round(time.time() - start, 2)
928
-
929
- timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
930
- session_id = str(uuid.uuid4())
931
-
932
- # Använd session_id från tidigare logg om det finns
933
- with last_log_lock:
934
- if last_log and 'session_id' in last_log:
935
- session_id = last_log.get('session_id')
936
-
937
- # Säker hantering av request-objektet
938
- user_id = "okänd"
939
- ua_str = ""
940
- ref = ""
941
- ip = ""
942
-
943
- if request:
944
- try:
945
- user_id = request.client.host if hasattr(request.client, 'host') else "okänd"
946
- ua_str = request.headers.get("user-agent", "") if hasattr(request, 'headers') else ""
947
- ref = request.headers.get("referer", "") if hasattr(request, 'headers') else ""
948
- ip = request.headers.get("x-forwarded-for", user_id).split(",")[0] if hasattr(request, 'headers') else user_id
949
- except Exception as e:
950
- print(f"Fel vid läsning av request-data: {e}")
951
-
952
- # Säker parsing av user agent
953
- try:
954
- ua = parse_ua(ua_str)
955
- browser = f"{ua.browser.family} {ua.browser.version_string}"
956
- osys = f"{ua.os.family} {ua.os.version_string}"
957
- except Exception as e:
958
- print(f"Fel vid parsing av user agent: {e}")
959
- browser = "okänd"
960
- osys = "okänd"
961
-
962
- # Bestäm plattform
963
- platform = "webb"
964
- try:
965
- if ref:
966
- if "chargenode.eu" in ref:
967
- platform = "chargenode.eu"
968
- elif "localhost" in ref:
969
- platform = "test"
970
- elif "app" in ref:
971
- platform = "app"
972
- except Exception as e:
973
- print(f"Fel vid bestämning av plattform: {e}")
974
-
975
- # Skapa loggdata
976
- log_data = {
977
- "timestamp": timestamp,
978
- "user_id": user_id,
979
- "session_id": session_id,
980
- "user_message": message,
981
- "bot_reply": response,
982
- "response_time": elapsed,
983
- "ip": ip,
984
- "browser": browser,
985
- "os": osys,
986
- "platform": platform
987
- }
988
-
989
- # Använd den förbättrade loggfunktionen
990
- safe_append_to_log(log_data)
991
-
992
- # Uppdatera last_log säkert
993
- with last_log_lock:
994
- last_log = log_data.copy() # Använd en kopia för att undvika race conditions
995
-
996
- # Skicka varje konversation direkt till Slack
997
- try:
998
- # Konversationsinnehåll
999
- conversation_content = f"""
1000
- *Ny konversation {timestamp}*
1001
-
1002
- *Användare:* {message}
1003
-
1004
- *Bot:* {response[:300]}{'...' if len(response) > 300 else ''}
1005
-
1006
- *Sessionsinfo:* {session_id[:8]}... | {browser} | {platform}
1007
- """
1008
- # Skicka asynkront för att inte blockera svarstiden
1009
- threading.Thread(
1010
- target=lambda: send_to_slack(f"Ny konversation", conversation_content),
1011
- daemon=True
1012
- ).start()
1013
- except Exception as e:
1014
- print(f"Kunde inte skicka konversation till Slack: {e}")
1015
-
1016
- # Uppdatera chatthistorik
1017
- try:
1018
- chat_history.append({"role": "user", "content": message})
1019
- chat_history.append({"role": "assistant", "content": response})
1020
- except Exception as e:
1021
- print(f"Fel vid uppdatering av chatthistorik: {e}")
1022
- # Försök återställa chatthistoriken om något går fel
1023
- if not chat_history:
1024
- chat_history = []
1025
- chat_history.append({"role": "user", "content": message})
1026
- chat_history.append({"role": "assistant", "content": response})
1027
-
1028
- # Skapa JavaScript för att scrolla till senaste botmeddelandet
1029
- scroll_js = """
1030
- setTimeout(function() {
1031
- console.log("Executing scroll script");
1032
- const messages = document.querySelectorAll('.message');
1033
- if (messages.length > 0) {
1034
- console.log("Found messages:", messages.length);
1035
- const lastMessage = messages[messages.length - 1];
1036
- console.log("Scrolling to last message");
1037
- lastMessage.scrollIntoView({ behavior: 'smooth', block: 'start' });
1038
- } else {
1039
- console.log("No messages found, trying alternative selectors");
1040
- // Alternativa selektorer för Hugging Face
1041
- const altMessages = document.querySelectorAll('.bot, [data-testid="bot"], .message-wrap > div:last-child');
1042
- if (altMessages.length > 0) {
1043
- console.log("Found messages with alt selector:", altMessages.length);
1044
- const lastAltMessage = altMessages[altMessages.length - 1];
1045
- console.log("Scrolling to last alt message");
1046
- lastAltMessage.scrollIntoView({ behavior: 'smooth', block: 'start' });
1047
- } else {
1048
- console.log("No messages found with any selector");
1049
- }
1050
- }
1051
- }, 100); // Vänta 100 millisekunder innan scroll
1052
-
1053
- // Försök igen efter lite längre tid om första försöket misslyckas
1054
- setTimeout(function() {
1055
- console.log("Executing delayed scroll script");
1056
- const messages = document.querySelectorAll('.message, .bot, [data-testid="bot"], .message-wrap > div:last-child');
1057
- if (messages.length > 0) {
1058
- console.log("Found messages in delayed script:", messages.length);
1059
- const lastMessage = messages[messages.length - 1];
1060
- console.log("Scrolling to last message in delayed script");
1061
- lastMessage.scrollIntoView({ behavior: 'smooth', block: 'start' });
1062
- }
1063
- }, 500); // Vänta 500 millisekunder innan andra försöket
1064
- """
1065
-
1066
- # Skapa JavaScript för att scrolla till senaste botmeddelandet (behåller den tidigare fungerande logiken)
1067
- scroll_js = """
1068
- setTimeout(function() {
1069
- console.log("Executing scroll script");
1070
- const messages = document.querySelectorAll('.message');
1071
- if (messages.length > 0) {
1072
- console.log("Found messages:", messages.length);
1073
- const lastMessage = messages[messages.length - 1];
1074
- console.log("Scrolling to last message");
1075
- lastMessage.scrollIntoView({ behavior: 'smooth', block: 'start' });
1076
- } else {
1077
- console.log("No messages found, trying alternative selectors");
1078
- // Alternativa selektorer för Hugging Face
1079
- const altMessages = document.querySelectorAll('.bot, [data-testid="bot"], .message-wrap > div:last-child');
1080
- if (altMessages.length > 0) {
1081
- console.log("Found messages with alt selector:", altMessages.length);
1082
- const lastAltMessage = altMessages[altMessages.length - 1];
1083
- console.log("Scrolling to last alt message");
1084
- lastAltMessage.scrollIntoView({ behavior: 'smooth', block: 'start' });
1085
- } else {
1086
- console.log("No messages found with any selector");
1087
- }
1088
- }
1089
- }, 100); // Vänta 100 millisekunder innan scroll
1090
-
1091
- // Försök igen efter lite längre tid om första försöket misslyckas
1092
- setTimeout(function() {
1093
- console.log("Executing delayed scroll script");
1094
- const messages = document.querySelectorAll('.message, .bot, [data-testid="bot"], .message-wrap > div:last-child');
1095
- if (messages.length > 0) {
1096
- console.log("Found messages in delayed script:", messages.length);
1097
- const lastMessage = messages[messages.length - 1];
1098
- console.log("Scrolling to last message in delayed script");
1099
- lastMessage.scrollIntoView({ behavior: 'smooth', block: 'start' });
1100
- }
1101
- }, 500); // Vänta 500 millisekunder innan andra försöket
1102
- """
1103
-
1104
- # Returnera tomt meddelande och den uppdaterade chatthistoriken
1105
- return "", chat_history
1106
-
1107
- def format_chat_preview(chat_history):
1108
- if not chat_history:
1109
- return "Ingen chatthistorik att visa."
1110
-
1111
- preview = ""
1112
- for msg in chat_history:
1113
- sender = "Användare" if msg["role"] == "user" else "Bot"
1114
- content = msg["content"]
1115
- if len(content) > 100: # Truncate long messages
1116
- content = content[:100] + "..."
1117
- preview += f"**{sender}:** {content}\n\n"
1118
-
1119
- return preview
1120
-
1121
- def show_support_form(chat_history):
1122
- preview = format_chat_preview(chat_history)
1123
- return {
1124
- chat_interface: gr.Group(visible=False),
1125
- support_interface: gr.Group(visible=True),
1126
- success_interface: gr.Group(visible=False),
1127
- chat_preview: preview
1128
- }
1129
-
1130
- def back_to_chat():
1131
- return {
1132
- chat_interface: gr.Group(visible=True),
1133
- support_interface: gr.Group(visible=False),
1134
- success_interface: gr.Group(visible=False)
1135
- }
1136
-
1137
- def submit_support_form(områdeskod, uttagsnummer, email, chat_history):
1138
- """Hanterar formulärinskickningen med bättre felhantering."""
1139
- print(f"Support-förfrågan: områdeskod={områdeskod}, uttagsnummer={uttagsnummer}, email={email}")
1140
-
1141
- # Validera input med tydligare loggning
1142
- validation_errors = []
1143
-
1144
- if områdeskod and not områdeskod.isdigit():
1145
- print(f"Validerar områdeskod: '{områdeskod}' (felaktig)")
1146
- validation_errors.append("Områdeskod måste vara numerisk.")
1147
- else:
1148
- print(f"Validerar områdeskod: '{områdeskod}' (ok)")
1149
-
1150
- if uttagsnummer and not uttagsnummer.isdigit():
1151
- print(f"Validerar uttagsnummer: '{uttagsnummer}' (felaktig)")
1152
- validation_errors.append("Uttagsnummer måste vara numerisk.")
1153
- else:
1154
- print(f"Validerar uttagsnummer: '{uttagsnummer}' (ok)")
1155
-
1156
- if not email:
1157
- print("Validerar email: (saknas)")
1158
- validation_errors.append("En giltig e-postadress krävs.")
1159
- elif '@' not in email or '.' not in email.split('@')[1]:
1160
- print(f"Validerar email: '{email}' (felaktigt format)")
1161
- validation_errors.append("En giltig e-postadress krävs.")
1162
- else:
1163
- print(f"Validerar email: '{email}' (ok)")
1164
-
1165
- # Om det finns valideringsfel
1166
- if validation_errors:
1167
- print(f"Valideringsfel: {validation_errors}")
1168
- return {
1169
- chat_interface: gr.Group(visible=False),
1170
- support_interface: gr.Group(visible=True),
1171
- success_interface: gr.Group(visible=False),
1172
- chat_preview: "\n".join(["**Fel:**"] + validation_errors)
1173
- }
1174
-
1175
- # Om formuläret klarade valideringen, försök skicka till Slack
1176
- try:
1177
- print("Försöker skicka supportförfrågan till Slack...")
1178
-
1179
- # Skapa en förenklad chathistorik för loggning
1180
- chat_summary = []
1181
- for msg in chat_history:
1182
- if 'role' in msg and 'content' in msg:
1183
- chat_summary.append(f"{msg['role']}: {msg['content'][:30]}...")
1184
- print(f"Chatthistorik att skicka: {chat_summary}")
1185
-
1186
- # Skicka till Slack
1187
- success = send_support_to_slack(områdeskod, uttagsnummer, email, chat_history)
1188
-
1189
- if success:
1190
- print("Support-förfrågan skickad till Slack framgångsrikt")
1191
- return {
1192
- chat_interface: gr.Group(visible=False),
1193
- support_interface: gr.Group(visible=False),
1194
- success_interface: gr.Group(visible=True)
1195
- }
1196
- else:
1197
- print("Support-förfrågan till Slack misslyckades")
1198
- return {
1199
- chat_interface: gr.Group(visible=False),
1200
- support_interface: gr.Group(visible=True),
1201
- success_interface: gr.Group(visible=False),
1202
- chat_preview: "**Ett fel uppstod när meddelandet skulle skickas. Vänligen försök igen senare.**"
1203
- }
1204
- except Exception as e:
1205
- print(f"Oväntat fel vid hantering av support-formulär: {e}")
1206
- return {
1207
- chat_interface: gr.Group(visible=False),
1208
- support_interface: gr.Group(visible=True),
1209
- success_interface: gr.Group(visible=False),
1210
- chat_preview: f"**Ett fel uppstod: {str(e)}**"
1211
- }
1212
-
1213
  # --- Gradio UI ---
1214
  initial_chat = [{"role": "assistant", "content": "Detta är ChargeNode's AI bot. Hur kan jag hjälpa dig idag?"}]
1215
 
@@ -1218,10 +572,6 @@ body {background-color: #f7f7f7; font-family: Arial, sans-serif; margin: 0; padd
1218
  h1 {font-family: Helvetica, sans-serif; color: #2a9d8f; text-align: center; margin-bottom: 0.5em;}
1219
  .gradio-container {max-width: 400px; margin: 0; padding: 10px; position: fixed; bottom: 20px; right: 20px; box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.1); border-radius: 10px; background-color: #fff;}
1220
  #chatbot_conversation { max-height: 300px; overflow-y: auto; }
1221
- .message-wrap { scroll-behavior: smooth; }
1222
- .message.bot:last-child { scroll-margin-top: 100px; }
1223
- .support-form-container { margin-top: 40px; }
1224
- .support-form-container .gr-form { margin-top: 15px; }
1225
  .gr-button {background-color: #2a9d8f; color: #fff; border: none; border-radius: 4px; padding: 8px 16px; margin: 5px;}
1226
  .gr-button:hover {background-color: #264653;}
1227
  .support-btn {background-color: #000000; color: #ffffff; margin-top: 5px; margin-bottom: 10px;}
@@ -1239,46 +589,6 @@ footer {display: none !important;}
1239
  .gradio-container .gr-footer {display: none !important;}
1240
  """
1241
 
1242
- # --- JavaScript för scrollning ---
1243
- # Definiera JS-koden som en sträng här för att kunna använda den i _js parametern
1244
- scroll_js = """
1245
- setTimeout(function() {
1246
- console.log("Executing scroll script");
1247
- const messages = document.querySelectorAll('.message');
1248
- if (messages.length > 0) {
1249
- console.log("Found messages:", messages.length);
1250
- const lastMessage = messages[messages.length - 1];
1251
- console.log("Scrolling to last message");
1252
- lastMessage.scrollIntoView({ behavior: 'smooth', block: 'start' });
1253
- } else {
1254
- console.log("No messages found, trying alternative selectors");
1255
- // Alternativa selektorer för Hugging Face
1256
- const altMessages = document.querySelectorAll('.bot, [data-testid="bot"], .message-wrap > div:last-child');
1257
- if (altMessages.length > 0) {
1258
- console.log("Found messages with alt selector:", altMessages.length);
1259
- const lastAltMessage = altMessages[altMessages.length - 1];
1260
- console.log("Scrolling to last alt message");
1261
- lastAltMessage.scrollIntoView({ behavior: 'smooth', block: 'start' });
1262
- } else {
1263
- console.log("No messages found with any selector");
1264
- }
1265
- }
1266
- }, 100); // Vänta 100 millisekunder innan scroll
1267
-
1268
- // Försök igen efter lite längre tid om första försöket misslyckas
1269
- setTimeout(function() {
1270
- console.log("Executing delayed scroll script");
1271
- const messages = document.querySelectorAll('.message, .bot, [data-testid="bot"], .message-wrap > div:last-child');
1272
- if (messages.length > 0) {
1273
- console.log("Found messages in delayed script:", messages.length);
1274
- const lastMessage = messages[messages.length - 1];
1275
- console.log("Scrolling to last message in delayed script");
1276
- lastMessage.scrollIntoView({ behavior: 'smooth', block: 'start' });
1277
- }
1278
- }, 500); // Vänta 500 millisekunder innan andra försöket
1279
- """
1280
-
1281
- # VIKTIGT: Alla komponenter och eventkopplingar definieras inuti Blocks-kontexten
1282
  with gr.Blocks(css=custom_css, title="ChargeNode Kundtjänst") as app:
1283
  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")
1284
 
@@ -1296,11 +606,8 @@ with gr.Blocks(css=custom_css, title="ChargeNode Kundtjänst") as app:
1296
  with gr.Column(scale=1):
1297
  support_btn = gr.Button("Behöver du mer hjälp?", elem_classes="support-btn")
1298
 
1299
- # Ta bort den gamla app.load(js=...) eftersom scrollningen nu hanteras via gr.update
1300
- # app.load(js=js_code)
1301
-
1302
  # Support form interface (initially hidden)
1303
- with gr.Group(visible=False, elem_classes="support-form-container") as support_interface:
1304
  gr.Markdown("### Vänligen fyll i din områdeskod, uttagsnummer och din email adress")
1305
 
1306
  with gr.Group(elem_classes="gr-form"):
@@ -1320,15 +627,185 @@ with gr.Blocks(css=custom_css, title="ChargeNode Kundtjänst") as app:
1320
  gr.Markdown("Tack för att du kontaktar support@chargenode.eu. Vi återkommer inom kort", elem_classes="success-message")
1321
  back_to_chat_btn = gr.Button("Tillbaka till chatten")
1322
 
1323
- # VIKTIGT: Händelsehanterare definieras INOM gr.Blocks-kontexten, efter att komponenterna definierats
1324
- # Uppdatera msg.submit för att hantera de två returvärdena från respond
1325
- # Använd _js parametern för att köra scroll-skriptet efter uppdateringen
1326
- msg.submit(respond,
1327
- inputs=[msg, chatbot],
1328
- outputs=[msg, chatbot], # msg, chatbot history
1329
- _js=scroll_js)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1330
 
1331
- clear.click(lambda: (None, []), None, [msg, chatbot], queue=False) # Rensa både textbox och historik
 
1332
  support_btn.click(show_support_form, chatbot, [chat_interface, support_interface, success_interface, chat_preview])
1333
  back_btn.click(back_to_chat, None, [chat_interface, support_interface, success_interface])
1334
  back_to_chat_btn.click(back_to_chat, None, [chat_interface, support_interface, success_interface])
@@ -1339,4 +816,4 @@ with gr.Blocks(css=custom_css, title="ChargeNode Kundtjänst") as app:
1339
  )
1340
 
1341
  if __name__ == "__main__":
1342
- app.launch(share=True)
 
14
  from user_agents import parse as parse_ua
15
  import schedule
16
  import threading
 
 
 
17
 
18
  # --- Konfiguration ---
19
  CHARGENODE_URL = "https://www.chargenode.eu"
 
55
 
56
  # --- Globala variabler ---
57
  last_log = None # Sparar loggdata från senaste svar för feedback
 
 
58
 
59
  # --- Förbättrad loggfunktion ---
60
  def safe_append_to_log(log_entry):
61
  """Säker metod för att lägga till loggdata utan att förlora historisk information."""
62
  try:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
63
  # Öppna filen i append-läge
64
  with open(log_file_path, "a", encoding="utf-8") as log_file:
65
+ log_json = json.dumps(log_entry)
66
  log_file.write(log_json + "\n")
67
  log_file.flush() # Säkerställ att data skrivs till disk omedelbart
68
 
 
72
  except Exception as e:
73
  print(f"Fel vid loggning: {e}")
74
 
75
+ # Försök skapa mappen om den inte finns
76
  try:
77
  os.makedirs(os.path.dirname(log_file_path), exist_ok=True)
78
 
79
+ # Försök igen
 
 
 
 
 
 
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
 
84
+ print("Loggpost tillagd efter återhämtning")
85
  return True
86
 
87
  except Exception as retry_error:
 
152
  sources.append(source)
153
  return chunks, sources
154
 
155
+ # Lazy-laddning av SentenceTransformer
156
  embedder = None
157
  embeddings = None
158
  index = None
 
 
 
159
 
160
  def initialize_embeddings():
161
  """Initierar SentenceTransformer och FAISS-index vid första anrop."""
162
  global embedder, embeddings, index, chunks, chunk_sources
163
 
164
+ if embedder is None:
165
+ print("Initierar SentenceTransformer och FAISS-index...")
166
+ # Ladda och förbered lokal data
167
+ print("Laddar textdata...")
168
+ text_data = {"local_files": load_local_files()}
169
+ print("Förbereder textsegment...")
170
+ chunks, chunk_sources = prepare_chunks(text_data)
171
+ print(f"{len(chunks)} segment laddade")
172
+
173
+ print("Skapar embeddings...")
174
+ embedder = SentenceTransformer('all-MiniLM-L6-v2')
175
+ embeddings = embedder.encode(chunks, convert_to_numpy=True)
176
+ embeddings /= np.linalg.norm(embeddings, axis=1, keepdims=True)
177
+ index = faiss.IndexFlatIP(embeddings.shape[1])
178
+ index.add(embeddings)
179
+ print("FAISS-index klart")
 
180
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
181
  def retrieve_context(query, k=RETRIEVAL_K):
182
  """Hämtar relevant kontext för frågor."""
183
  # Säkerställ att modeller är laddade
184
  initialize_embeddings()
185
 
186
+ query_embedding = embedder.encode([query], convert_to_numpy=True)
187
+ query_embedding /= np.linalg.norm(query_embedding)
188
+ D, I = index.search(query_embedding, k)
189
+ retrieved, sources = [], set()
190
+ for idx in I[0]:
191
+ if idx < len(chunks):
192
+ retrieved.append(chunks[idx])
193
+ sources.add(chunk_sources[idx])
194
+ return " ".join(retrieved), list(sources)
195
+
196
+ def generate_answer(query):
197
+ """Genererar svar baserat på fråga och kontextinformation."""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
198
  context, sources = retrieve_context(query)
199
+ if not context.strip():
200
+ return "Jag hittar ingen relevant information i mina källor.\n\nDetta är ett AI genererat svar."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
201
  prompt = f"""{prompt_template}
202
 
203
  Relevant kontext:
 
208
  response = client.chat.completions.create(
209
  model="gpt-3.5-turbo",
210
  messages=[
211
+ {"role": "system", "content": "Du är en expert på ChargeNodes produkter och tjänster. Svara enbart baserat på den information som finns i den indexerade datan."},
212
  {"role": "user", "content": prompt}
213
  ],
214
  temperature=0.2,
215
  max_tokens=500
216
  )
217
  answer = response.choices[0].message.content
218
+ return answer + "\n\nAI-genererat. Otillräcklig hjälp? Kontakta support@chargenode.eu eller 010-2051055"
 
 
 
 
 
 
 
 
 
 
 
 
219
  except Exception as e:
220
+ return f"Tekniskt fel: {str(e)}\n\nAI-genererat. Kontakta support@chargenode.eu eller 010-2051055"
 
 
221
 
222
  # --- Slack Integration ---
223
  def send_to_slack(subject, content, color="#2a9d8f"):
 
272
  data.value innehåller information om meddelandet.
273
  """
274
  feedback_type = "up" if data.liked else "down"
275
+ global last_log
 
 
 
 
 
 
276
  log_entry = {
277
  "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
278
  "feedback": feedback_type,
279
+ "bot_reply": data.value if not isinstance(data.value, dict) else data.value.get("value")
280
  }
281
+ # Om global logdata finns, lägg till ytterligare metadata.
282
+ if last_log:
283
+ log_entry.update({
284
+ "session_id": last_log.get("session_id"),
285
+ "user_message": last_log.get("user_message"),
286
+ })
 
 
 
287
 
288
  # Använd den förbättrade loggfunktionen
289
  safe_append_to_log(log_entry)
 
291
  # Skicka feedback till Slack
292
  try:
293
  if feedback_type == "down": # Skicka bara negativ feedback
 
 
 
 
 
 
294
  feedback_message = f"""
295
  *⚠️ Negativ feedback registrerad*
296
 
297
+ *Fråga:* {last_log.get('user_message', 'Okänd fråga')}
298
 
299
  *Svar:* {log_entry.get('bot_reply', 'Okänt svar')[:300]}{'...' if len(log_entry.get('bot_reply', '')) > 300 else ''}
300
  """
 
534
  print(f"Fel vid sändning av support till Slack: {type(e).__name__}: {e}")
535
  return False
536
 
537
+ # --- Schemaläggning av rapporter ---
538
  def run_scheduler():
539
  """Kör schemaläggaren i en separat tråd med förenklad statusrapportering."""
540
+ # Använd den förenklade funktionen för rapportering
541
+ schedule.every().day.at("08:00").do(simple_status_report)
542
+ schedule.every().day.at("12:00").do(simple_status_report)
543
+ schedule.every().day.at("17:00").do(simple_status_report)
544
+
545
+ # Veckorapport på måndagar
546
+ schedule.every().monday.at("09:00").do(lambda: send_to_slack(
547
+ "Veckostatistik",
548
+ f"*ChargeNode AI Bot - Veckostatistik*\n\n{json.dumps(generate_monthly_stats(7), indent=2)}",
549
+ "#3498db"
550
+ ))
551
+
552
+ while True:
553
+ schedule.run_pending()
554
+ time.sleep(60) # Kontrollera varje minut
 
 
 
 
 
 
 
 
 
 
 
 
555
 
556
  # Starta schemaläggaren i en separat tråd
557
  scheduler_thread = threading.Thread(target=run_scheduler, daemon=True)
558
  scheduler_thread.start()
559
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
560
  # Kör en statusrapport vid uppstart för att verifiera att allt fungerar
561
  try:
562
  print("Skickar en inledande statusrapport för att verifiera Slack-integrationen...")
 
564
  except Exception as e:
565
  print(f"Information: Statusrapport kommer att skickas enligt schema: {e}")
566
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
567
  # --- Gradio UI ---
568
  initial_chat = [{"role": "assistant", "content": "Detta är ChargeNode's AI bot. Hur kan jag hjälpa dig idag?"}]
569
 
 
572
  h1 {font-family: Helvetica, sans-serif; color: #2a9d8f; text-align: center; margin-bottom: 0.5em;}
573
  .gradio-container {max-width: 400px; margin: 0; padding: 10px; position: fixed; bottom: 20px; right: 20px; box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.1); border-radius: 10px; background-color: #fff;}
574
  #chatbot_conversation { max-height: 300px; overflow-y: auto; }
 
 
 
 
575
  .gr-button {background-color: #2a9d8f; color: #fff; border: none; border-radius: 4px; padding: 8px 16px; margin: 5px;}
576
  .gr-button:hover {background-color: #264653;}
577
  .support-btn {background-color: #000000; color: #ffffff; margin-top: 5px; margin-bottom: 10px;}
 
589
  .gradio-container .gr-footer {display: none !important;}
590
  """
591
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
592
  with gr.Blocks(css=custom_css, title="ChargeNode Kundtjänst") as app:
593
  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")
594
 
 
606
  with gr.Column(scale=1):
607
  support_btn = gr.Button("Behöver du mer hjälp?", elem_classes="support-btn")
608
 
 
 
 
609
  # Support form interface (initially hidden)
610
+ with gr.Group(visible=False) as support_interface:
611
  gr.Markdown("### Vänligen fyll i din områdeskod, uttagsnummer och din email adress")
612
 
613
  with gr.Group(elem_classes="gr-form"):
 
627
  gr.Markdown("Tack för att du kontaktar support@chargenode.eu. Vi återkommer inom kort", elem_classes="success-message")
628
  back_to_chat_btn = gr.Button("Tillbaka till chatten")
629
 
630
+ def respond(message, chat_history, request: gr.Request):
631
+ global last_log
632
+ start = time.time()
633
+ response = generate_answer(message)
634
+ elapsed = round(time.time() - start, 2)
635
+
636
+ timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
637
+ session_id = str(uuid.uuid4())
638
+
639
+ # Använd session_id från tidigare logg om det finns
640
+ if last_log and 'session_id' in last_log:
641
+ session_id = last_log.get('session_id')
642
+
643
+ user_id = request.client.host if request else "okänd"
644
+
645
+ ua_str = request.headers.get("user-agent", "")
646
+ ref = request.headers.get("referer", "")
647
+ ip = request.headers.get("x-forwarded-for", user_id).split(",")[0]
648
+ ua = parse_ua(ua_str)
649
+ browser = f"{ua.browser.family} {ua.browser.version_string}"
650
+ osys = f"{ua.os.family} {ua.os.version_string}"
651
+
652
+ platform = "webb"
653
+ if "chargenode.eu" in ref:
654
+ platform = "chargenode.eu"
655
+ elif "localhost" in ref:
656
+ platform = "test"
657
+ elif "app" in ref:
658
+ platform = "app"
659
+
660
+ log_data = {
661
+ "timestamp": timestamp,
662
+ "user_id": user_id,
663
+ "session_id": session_id,
664
+ "user_message": message,
665
+ "bot_reply": response,
666
+ "response_time": elapsed,
667
+ "ip": ip,
668
+ "browser": browser,
669
+ "os": osys,
670
+ "platform": platform
671
+ }
672
+
673
+ # Använd den förbättrade loggfunktionen
674
+ safe_append_to_log(log_data)
675
+ last_log = log_data
676
+
677
+ # Skicka varje konversation direkt till Slack
678
+ try:
679
+ # Konversationsinnehåll
680
+ conversation_content = f"""
681
+ *Ny konversation {timestamp}*
682
+
683
+ *Användare:* {message}
684
+
685
+ *Bot:* {response[:300]}{'...' if len(response) > 300 else ''}
686
+
687
+ *Sessionsinfo:* {session_id[:8]}... | {browser} | {platform}
688
+ """
689
+ # Skicka asynkront för att inte blockera svarstiden
690
+ threading.Thread(
691
+ target=lambda: send_to_slack(f"Ny konversation", conversation_content),
692
+ daemon=True
693
+ ).start()
694
+ except Exception as e:
695
+ print(f"Kunde inte skicka konversation till Slack: {e}")
696
+
697
+ chat_history.append({"role": "user", "content": message})
698
+ chat_history.append({"role": "assistant", "content": response})
699
+ return "", chat_history
700
+
701
+ def format_chat_preview(chat_history):
702
+ if not chat_history:
703
+ return "Ingen chatthistorik att visa."
704
+
705
+ preview = ""
706
+ for msg in chat_history:
707
+ sender = "Användare" if msg["role"] == "user" else "Bot"
708
+ content = msg["content"]
709
+ if len(content) > 100: # Truncate long messages
710
+ content = content[:100] + "..."
711
+ preview += f"**{sender}:** {content}\n\n"
712
+
713
+ return preview
714
+
715
+ def show_support_form(chat_history):
716
+ preview = format_chat_preview(chat_history)
717
+ return {
718
+ chat_interface: gr.Group(visible=False),
719
+ support_interface: gr.Group(visible=True),
720
+ success_interface: gr.Group(visible=False),
721
+ chat_preview: preview
722
+ }
723
+
724
+ def back_to_chat():
725
+ return {
726
+ chat_interface: gr.Group(visible=True),
727
+ support_interface: gr.Group(visible=False),
728
+ success_interface: gr.Group(visible=False)
729
+ }
730
+
731
+ def submit_support_form(områdeskod, uttagsnummer, email, chat_history):
732
+ """Hanterar formulärinskickningen med bättre felhantering."""
733
+ print(f"Support-förfrågan: områdeskod={områdeskod}, uttagsnummer={uttagsnummer}, email={email}")
734
+
735
+ # Validera input med tydligare loggning
736
+ validation_errors = []
737
+
738
+ if områdeskod and not områdeskod.isdigit():
739
+ print(f"Validerar områdeskod: '{områdeskod}' (felaktig)")
740
+ validation_errors.append("Områdeskod måste vara numerisk.")
741
+ else:
742
+ print(f"Validerar områdeskod: '{områdeskod}' (ok)")
743
+
744
+ if uttagsnummer and not uttagsnummer.isdigit():
745
+ print(f"Validerar uttagsnummer: '{uttagsnummer}' (felaktig)")
746
+ validation_errors.append("Uttagsnummer måste vara numerisk.")
747
+ else:
748
+ print(f"Validerar uttagsnummer: '{uttagsnummer}' (ok)")
749
+
750
+ if not email:
751
+ print("Validerar email: (saknas)")
752
+ validation_errors.append("En giltig e-postadress krävs.")
753
+ elif '@' not in email or '.' not in email.split('@')[1]:
754
+ print(f"Validerar email: '{email}' (felaktigt format)")
755
+ validation_errors.append("En giltig e-postadress krävs.")
756
+ else:
757
+ print(f"Validerar email: '{email}' (ok)")
758
+
759
+ # Om det finns valideringsfel
760
+ if validation_errors:
761
+ print(f"Valideringsfel: {validation_errors}")
762
+ return {
763
+ chat_interface: gr.Group(visible=False),
764
+ support_interface: gr.Group(visible=True),
765
+ success_interface: gr.Group(visible=False),
766
+ chat_preview: "\n".join(["**Fel:**"] + validation_errors)
767
+ }
768
+
769
+ # Om formuläret klarade valideringen, försök skicka till Slack
770
+ try:
771
+ print("Försöker skicka supportförfrågan till Slack...")
772
+
773
+ # Skapa en förenklad chathistorik för loggning
774
+ chat_summary = []
775
+ for msg in chat_history:
776
+ if 'role' in msg and 'content' in msg:
777
+ chat_summary.append(f"{msg['role']}: {msg['content'][:30]}...")
778
+ print(f"Chatthistorik att skicka: {chat_summary}")
779
+
780
+ # Skicka till Slack
781
+ success = send_support_to_slack(områdeskod, uttagsnummer, email, chat_history)
782
+
783
+ if success:
784
+ print("Support-förfrågan skickad till Slack framgångsrikt")
785
+ return {
786
+ chat_interface: gr.Group(visible=False),
787
+ support_interface: gr.Group(visible=False),
788
+ success_interface: gr.Group(visible=True)
789
+ }
790
+ else:
791
+ print("Support-förfrågan till Slack misslyckades")
792
+ return {
793
+ chat_interface: gr.Group(visible=False),
794
+ support_interface: gr.Group(visible=True),
795
+ success_interface: gr.Group(visible=False),
796
+ chat_preview: "**Ett fel uppstod när meddelandet skulle skickas. Vänligen försök igen senare.**"
797
+ }
798
+ except Exception as e:
799
+ print(f"Oväntat fel vid hantering av support-formulär: {e}")
800
+ return {
801
+ chat_interface: gr.Group(visible=False),
802
+ support_interface: gr.Group(visible=True),
803
+ success_interface: gr.Group(visible=False),
804
+ chat_preview: f"**Ett fel uppstod: {str(e)}**"
805
+ }
806
 
807
+ msg.submit(respond, [msg, chatbot], [msg, chatbot])
808
+ clear.click(lambda: None, None, chatbot, queue=False)
809
  support_btn.click(show_support_form, chatbot, [chat_interface, support_interface, success_interface, chat_preview])
810
  back_btn.click(back_to_chat, None, [chat_interface, support_interface, success_interface])
811
  back_to_chat_btn.click(back_to_chat, None, [chat_interface, support_interface, success_interface])
 
816
  )
817
 
818
  if __name__ == "__main__":
819
+ app.launch(share=True)