k96beni commited on
Commit
09db822
·
verified ·
1 Parent(s): aef1ab8

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +317 -269
app.py CHANGED
@@ -32,21 +32,68 @@ log_folder = "logs"
32
  os.makedirs(log_folder, exist_ok=True)
33
  log_file_path = os.path.join(log_folder, "conversation_log_v2.txt")
34
 
 
 
 
 
 
 
35
  hf_token = os.environ.get("HF_TOKEN")
36
  if not hf_token:
37
  raise ValueError("HF_TOKEN saknas")
 
 
38
  scheduler = CommitScheduler(
39
  repo_id="ChargeNodeEurope/logfiles",
40
  repo_type="dataset",
41
  folder_path=log_folder,
42
  path_in_repo="logs_v2",
43
- every=60,
44
- token=hf_token
 
 
 
 
 
 
 
45
  )
46
 
47
  # --- Globala variabler ---
48
  last_log = None # Sparar loggdata från senaste svar för feedback
49
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50
  # --- Laddar textkällor ---
51
  def load_local_files():
52
  uploaded_text = ""
@@ -111,22 +158,37 @@ def prepare_chunks(text_data):
111
  sources.append(source)
112
  return chunks, sources
113
 
114
- # Ladda och förbered lokal data
115
- print("Laddar textdata...")
116
- text_data = {"local_files": load_local_files()}
117
- print("Förbereder textsegment...")
118
- chunks, chunk_sources = prepare_chunks(text_data)
119
- print(f"{len(chunks)} segment laddade")
120
-
121
- print("Skapar embeddings...")
122
- embedder = SentenceTransformer('all-MiniLM-L6-v2')
123
- embeddings = embedder.encode(chunks, convert_to_numpy=True)
124
- embeddings /= np.linalg.norm(embeddings, axis=1, keepdims=True)
125
- index = faiss.IndexFlatIP(embeddings.shape[1])
126
- index.add(embeddings)
127
- print("FAISS-index klart")
 
 
 
 
 
 
 
 
 
 
 
128
 
129
  def retrieve_context(query, k=RETRIEVAL_K):
 
 
 
 
130
  query_embedding = embedder.encode([query], convert_to_numpy=True)
131
  query_embedding /= np.linalg.norm(query_embedding)
132
  D, I = index.search(query_embedding, k)
@@ -138,6 +200,7 @@ def retrieve_context(query, k=RETRIEVAL_K):
138
  return " ".join(retrieved), list(sources)
139
 
140
  def generate_answer(query):
 
141
  context, sources = retrieve_context(query)
142
  if not context.strip():
143
  return "Jag hittar ingen relevant information i mina källor.\n\nDetta är ett AI genererat svar."
@@ -163,6 +226,51 @@ Svar (baserat enbart på den indexerade datan):"""
163
  except Exception as e:
164
  return f"Tekniskt fel: {str(e)}\n\nDetta är ett AI genererat svar."
165
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
166
  # --- Feedback & Like-funktion ---
167
  def vote(data: gr.LikeData):
168
  """
@@ -183,42 +291,23 @@ def vote(data: gr.LikeData):
183
  "session_id": last_log.get("session_id"),
184
  "user_message": last_log.get("user_message"),
185
  })
186
- with scheduler.lock:
187
- with open(log_file_path, "a", encoding="utf-8") as log_file:
188
- log_file.write(json.dumps(log_entry) + "\n")
189
 
190
  # Skicka feedback till Slack
191
  try:
192
- webhook_url = os.environ.get("SLACK_WEBHOOK_URL")
193
- if webhook_url and feedback_type == "down": # Skicka bara negativ feedback
194
- feedback_message = {
195
- "blocks": [
196
- {
197
- "type": "header",
198
- "text": {
199
- "type": "plain_text",
200
- "text": "⚠️ Negativ feedback registrerad"
201
- }
202
- },
203
- {
204
- "type": "section",
205
- "text": {
206
- "type": "mrkdwn",
207
- "text": f"*Fråga:* {last_log.get('user_message', 'Okänd fråga')}"
208
- }
209
- },
210
- {
211
- "type": "section",
212
- "text": {
213
- "type": "mrkdwn",
214
- "text": f"*Svar:* {log_entry.get('bot_reply', 'Okänt svar')[:300]}{'...' if len(log_entry.get('bot_reply', '')) > 300 else ''}"
215
- }
216
- }
217
- ]
218
- }
219
  # Skicka asynkront
220
  threading.Thread(
221
- target=lambda: requests.post(webhook_url, json=feedback_message),
222
  daemon=True
223
  ).start()
224
  except Exception as e:
@@ -231,13 +320,20 @@ def read_logs():
231
  """Läs alla loggposter från loggfilen."""
232
  logs = []
233
  try:
234
- with open(log_file_path, "r", encoding="utf-8") as file:
235
- for line in file:
236
- try:
237
- log_entry = json.loads(line.strip())
238
- logs.append(log_entry)
239
- except json.JSONDecodeError:
240
- continue
 
 
 
 
 
 
 
241
  except Exception as e:
242
  print(f"Fel vid läsning av loggfil: {e}")
243
  return logs
@@ -276,131 +372,143 @@ def get_feedback_stats(logs):
276
 
277
  return feedback_count, negative_feedback_examples
278
 
279
- # --- Slack-integration ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
280
  def simple_status_report():
281
- """Förenklad statusrapport som garanterar att data skickas."""
282
- print("Skickar förenklad statusrapport till Slack...")
283
 
284
  try:
285
- # Läs loggfilen rad för rad för att undvika JSON-parsningsproblem
286
- messages = []
287
- sessions = set()
288
- users = set()
289
- feedback_up = 0
290
- feedback_down = 0
291
 
292
- try:
293
- with open(log_file_path, "r", encoding="utf-8") as f:
294
- for line in f:
295
- try:
296
- # Försök tolka JSON-data
297
- data = json.loads(line.strip())
298
-
299
- # Samla grundläggande statistik direkt från rå data
300
- if 'user_message' in data:
301
- messages.append(data)
302
- if 'session_id' in data:
303
- sessions.add(data['session_id'])
304
- if 'user_id' in data:
305
- users.add(data['user_id'])
306
- if 'feedback' in data:
307
- if data['feedback'] == 'up':
308
- feedback_up += 1
309
- elif data['feedback'] == 'down':
310
- feedback_down += 1
311
- except json.JSONDecodeError:
312
- continue # Hoppa över ogiltiga rader
313
- except Exception as e:
314
- # Om filläsning misslyckas helt, rapportera detta
315
- webhook_url = os.environ.get("SLACK_WEBHOOK_URL")
316
- error_payload = {
317
- "text": f"⚠️ *Fel vid läsning av loggfil:* {str(e)}"
318
- }
319
- requests.post(webhook_url, json=error_payload)
320
- return False
321
-
322
- # Skapa enkel statistik som garanterat fungerar
323
  now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
324
  subject = f"ChargeNode AI Bot - Status {now}"
325
 
 
 
 
 
 
 
 
 
 
326
  content = f"""
327
- *ChargeNode AI Bot - Statusrapport*
328
-
329
- *Basstatistik* (direkt från loggfiler)
330
- - Tidpunkt: {now}
331
- - Totalt antal meddelanden: {len(messages)}
332
- - Unika sessioner: {len(sessions)}
333
- - Unika användare: {len(users)}
334
- - Tumme upp: {feedback_up}
335
- - Tumme ned: {feedback_down}
336
-
337
- *Senaste aktivitet*
 
338
  """
339
 
340
- # Lägg till de 3 senaste meddelandena (om det finns några)
341
- recent_messages = sorted(messages, key=lambda x: x.get('timestamp', ''), reverse=True)[:3]
342
- if recent_messages:
343
- for msg in recent_messages:
344
- user_message = msg.get('user_message', 'Okänd fråga')
345
- bot_reply = msg.get('bot_reply', 'Okänt svar')
346
- timestamp = msg.get('timestamp', 'Okänd tid')
347
-
348
  content += f"""
349
- > *Tid:* {timestamp}
350
- > *Fråga:* {user_message[:100]}{"..." if len(user_message) > 100 else ""}
351
- > *Svar:* {bot_reply[:100]}{"..." if len(bot_reply) > 100 else ""}
352
  """
353
- else:
354
- content += "_Inga meddelanden hittades_"
355
-
356
- # Skicka till Slack med enkel webhook
357
- webhook_url = os.environ.get("SLACK_WEBHOOK_URL")
358
- if not webhook_url:
359
- print("Slack webhook URL saknas")
360
- return False
361
-
362
- payload = {
363
- "blocks": [
364
- {
365
- "type": "header",
366
- "text": {
367
- "type": "plain_text",
368
- "text": subject
369
- }
370
- },
371
- {
372
- "type": "section",
373
- "text": {
374
- "type": "mrkdwn",
375
- "text": content
376
- }
377
- }
378
- ]
379
- }
380
 
381
- response = requests.post(webhook_url, json=payload)
 
382
 
383
- if response.status_code == 200:
384
- print(f"Förenklad statusrapport skickad till Slack: {now}")
385
- return True
386
- else:
387
- print(f"Slack-anrop misslyckades: {response.status_code}, {response.text}")
388
- return False
389
-
390
  except Exception as e:
391
- print(f"Fel vid sändning av förenklad statusrapport: {e}")
392
 
393
- # Skicka felmeddelande till Slack som sista utväg
394
- try:
395
- webhook_url = os.environ.get("SLACK_WEBHOOK_URL")
396
- error_payload = {
397
- "text": f"⚠️ *Fel vid generering av statusrapport:* {str(e)}"
398
- }
399
- requests.post(webhook_url, json=error_payload)
400
- except:
401
- pass
402
-
403
- return False
404
 
405
  def send_support_to_slack(områdeskod, uttagsnummer, email, chat_history):
406
  """Skickar en supportförfrågan till Slack."""
@@ -413,64 +521,27 @@ def send_support_to_slack(områdeskod, uttagsnummer, email, chat_history):
413
  elif msg['role'] == 'assistant':
414
  chat_content += f">*Bot:* {msg['content'][:300]}{'...' if len(msg['content']) > 300 else ''}\n\n"
415
 
416
- # Skapa rubrik och innehåll
417
- webhook_url = os.environ.get("SLACK_WEBHOOK_URL")
418
- if not webhook_url:
419
- print("Slack webhook URL saknas")
420
- return False
421
-
422
- payload = {
423
- "blocks": [
424
- {
425
- "type": "header",
426
- "text": {
427
- "type": "plain_text",
428
- "text": f"Support förfrågan - {datetime.now().strftime('%Y-%m-%d %H:%M')}"
429
- }
430
- },
431
- {
432
- "type": "section",
433
- "fields": [
434
- {
435
- "type": "mrkdwn",
436
- "text": f"*Områdeskod:* {områdeskod or 'Ej angiven'}"
437
- },
438
- {
439
- "type": "mrkdwn",
440
- "text": f"*Uttagsnummer:* {uttagsnummer or 'Ej angiven'}"
441
- },
442
- {
443
- "type": "mrkdwn",
444
- "text": f"*Email:* {email}"
445
- },
446
- {
447
- "type": "mrkdwn",
448
- "text": f"*Tidpunkt:* {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
449
- }
450
- ]
451
- },
452
- {
453
- "type": "section",
454
- "text": {
455
- "type": "mrkdwn",
456
- "text": "*Chatthistorik:*\n" + chat_content
457
- }
458
- }
459
- ]
460
- }
461
 
462
- response = requests.post(webhook_url, json=payload)
463
- if response.status_code == 200:
464
- print("Support-förfrågan skickad till Slack")
465
- return True
466
- else:
467
- print(f"Slack-anrop för support misslyckades: {response.status_code}, {response.text}")
468
- return False
 
 
 
 
 
 
469
  except Exception as e:
470
  print(f"Fel vid sändning av support till Slack: {type(e).__name__}: {e}")
471
  return False
472
 
473
- # --- Schemaläggning av rapporter ---
474
  def run_scheduler():
475
  """Kör schemaläggaren i en separat tråd med förenklad statusrapportering."""
476
  # Använd den förenklade funktionen för rapportering
@@ -478,20 +549,27 @@ def run_scheduler():
478
  schedule.every().day.at("12:00").do(simple_status_report)
479
  schedule.every().day.at("17:00").do(simple_status_report)
480
 
 
 
 
 
 
 
 
481
  while True:
482
  schedule.run_pending()
483
  time.sleep(60) # Kontrollera varje minut
484
 
485
- # Starta schemaläggaren i en separat tråd
486
  scheduler_thread = threading.Thread(target=run_scheduler, daemon=True)
487
  scheduler_thread.start()
488
 
489
- # Kör en statusrapport direkt vid uppstart för att verifiera att allt fungerar
490
  try:
491
- print("Försöker skicka startrapport till Slack...")
492
- simple_status_report()
493
  except Exception as e:
494
- print(f"Kunde inte skicka startrapport: {e}")
495
 
496
  # --- Gradio UI ---
497
  initial_chat = [{"role": "assistant", "content": "Detta är ChargeNode's AI bot. Hur kan jag hjälpa dig idag?"}]
@@ -533,7 +611,7 @@ with gr.Blocks(css=custom_css, title="ChargeNode Kundtjänst") as app:
533
  with gr.Column(scale=1):
534
  clear = gr.Button("Rensa")
535
  with gr.Column(scale=1):
536
- support_btn = gr.Button("Skicka Chat till support", elem_classes="support-btn")
537
 
538
  # Support form interface (initially hidden)
539
  with gr.Group(visible=False) as support_interface:
@@ -599,57 +677,27 @@ with gr.Blocks(css=custom_css, title="ChargeNode Kundtjänst") as app:
599
  "platform": platform
600
  }
601
 
602
- with scheduler.lock:
603
- with open(log_file_path, "a", encoding="utf-8") as log_file:
604
- log_file.write(json.dumps(log_data) + "\n")
605
  last_log = log_data
606
 
607
  # Skicka varje konversation direkt till Slack
608
  try:
609
- webhook_url = os.environ.get("SLACK_WEBHOOK_URL")
610
- if webhook_url:
611
- # Förenkla innehållet för att säkerställa att det fungerar
612
- slack_message = {
613
- "blocks": [
614
- {
615
- "type": "header",
616
- "text": {
617
- "type": "plain_text",
618
- "text": f"Ny konversation {timestamp}"
619
- }
620
- },
621
- {
622
- "type": "section",
623
- "text": {
624
- "type": "mrkdwn",
625
- "text": f"*Användare:* {message}"
626
- }
627
- },
628
- {
629
- "type": "section",
630
- "text": {
631
- "type": "mrkdwn",
632
- "text": f"*Bot:* {response[:300]}{'...' if len(response) > 300 else ''}"
633
- }
634
- },
635
- {
636
- "type": "context",
637
- "elements": [
638
- {
639
- "type": "mrkdwn",
640
- "text": f"Session: {session_id[:8]}... | Browser: {browser} | Platform: {platform}"
641
- }
642
- ]
643
- }
644
- ]
645
- }
646
-
647
- # Använd asynkront anrop för att inte blockera svarstiden
648
- # Detta kräver att requests körs i en separat tråd
649
- threading.Thread(
650
- target=lambda: requests.post(webhook_url, json=slack_message),
651
- daemon=True
652
- ).start()
653
  except Exception as e:
654
  print(f"Kunde inte skicka konversation till Slack: {e}")
655
 
 
32
  os.makedirs(log_folder, exist_ok=True)
33
  log_file_path = os.path.join(log_folder, "conversation_log_v2.txt")
34
 
35
+ # Skapa en tom loggfil om den inte finns
36
+ if not os.path.exists(log_file_path):
37
+ with open(log_file_path, "w", encoding="utf-8") as f:
38
+ f.write("") # Skapa en tom fil
39
+ print(f"Skapade tom loggfil: {log_file_path}")
40
+
41
  hf_token = os.environ.get("HF_TOKEN")
42
  if not hf_token:
43
  raise ValueError("HF_TOKEN saknas")
44
+
45
+ # Initiera CommitScheduler med förbättrad konfiguration
46
  scheduler = CommitScheduler(
47
  repo_id="ChargeNodeEurope/logfiles",
48
  repo_type="dataset",
49
  folder_path=log_folder,
50
  path_in_repo="logs_v2",
51
+ every=300, # Vänta 5 minuter för att minska risken för konflikter
52
+ token=hf_token,
53
+ # Viktiga parametrar för att bevara historik:
54
+ push_to_hub=True,
55
+ # Använd force=False för att undvika att skriva över historik
56
+ force=False,
57
+ commit_message="Append new conversation logs",
58
+ # Använd huvudbranchen utan att skapa PR
59
+ create_pr=False
60
  )
61
 
62
  # --- Globala variabler ---
63
  last_log = None # Sparar loggdata från senaste svar för feedback
64
 
65
+ # --- Förbättrad loggfunktion ---
66
+ def safe_append_to_log(log_entry):
67
+ """Säker metod för att lägga till loggdata utan att förlora historisk information."""
68
+ try:
69
+ # Öppna filen i append-läge
70
+ with open(log_file_path, "a", encoding="utf-8") as log_file:
71
+ log_json = json.dumps(log_entry)
72
+ log_file.write(log_json + "\n")
73
+ log_file.flush() # Säkerställ att data skrivs till disk omedelbart
74
+
75
+ print(f"Loggpost tillagd: {log_entry.get('timestamp', 'okänd tid')}")
76
+ return True
77
+
78
+ except Exception as e:
79
+ print(f"Fel vid loggning: {e}")
80
+
81
+ # Försök skapa mappen om den inte finns
82
+ try:
83
+ os.makedirs(os.path.dirname(log_file_path), exist_ok=True)
84
+
85
+ # Försök igen
86
+ with open(log_file_path, "a", encoding="utf-8") as log_file:
87
+ log_json = json.dumps(log_entry)
88
+ log_file.write(log_json + "\n")
89
+
90
+ print("Loggpost tillagd efter återhämtning")
91
+ return True
92
+
93
+ except Exception as retry_error:
94
+ print(f"Kritiskt fel vid loggning: {retry_error}")
95
+ return False
96
+
97
  # --- Laddar textkällor ---
98
  def load_local_files():
99
  uploaded_text = ""
 
158
  sources.append(source)
159
  return chunks, sources
160
 
161
+ # Lazy-laddning av SentenceTransformer
162
+ embedder = None
163
+ embeddings = None
164
+ index = None
165
+
166
+ def initialize_embeddings():
167
+ """Initierar SentenceTransformer och FAISS-index vid första anrop."""
168
+ global embedder, embeddings, index, chunks, chunk_sources
169
+
170
+ if embedder is None:
171
+ print("Initierar SentenceTransformer och FAISS-index...")
172
+ # Ladda och förbered lokal data
173
+ print("Laddar textdata...")
174
+ text_data = {"local_files": load_local_files()}
175
+ print("Förbereder textsegment...")
176
+ chunks, chunk_sources = prepare_chunks(text_data)
177
+ print(f"{len(chunks)} segment laddade")
178
+
179
+ print("Skapar embeddings...")
180
+ embedder = SentenceTransformer('all-MiniLM-L6-v2')
181
+ embeddings = embedder.encode(chunks, convert_to_numpy=True)
182
+ embeddings /= np.linalg.norm(embeddings, axis=1, keepdims=True)
183
+ index = faiss.IndexFlatIP(embeddings.shape[1])
184
+ index.add(embeddings)
185
+ print("FAISS-index klart")
186
 
187
  def retrieve_context(query, k=RETRIEVAL_K):
188
+ """Hämtar relevant kontext för frågor."""
189
+ # Säkerställ att modeller är laddade
190
+ initialize_embeddings()
191
+
192
  query_embedding = embedder.encode([query], convert_to_numpy=True)
193
  query_embedding /= np.linalg.norm(query_embedding)
194
  D, I = index.search(query_embedding, k)
 
200
  return " ".join(retrieved), list(sources)
201
 
202
  def generate_answer(query):
203
+ """Genererar svar baserat på fråga och kontextinformation."""
204
  context, sources = retrieve_context(query)
205
  if not context.strip():
206
  return "Jag hittar ingen relevant information i mina källor.\n\nDetta är ett AI genererat svar."
 
226
  except Exception as e:
227
  return f"Tekniskt fel: {str(e)}\n\nDetta är ett AI genererat svar."
228
 
229
+ # --- Slack Integration ---
230
+ def send_to_slack(subject, content, color="#2a9d8f"):
231
+ """Basfunktion för att skicka meddelanden till Slack."""
232
+ webhook_url = os.environ.get("SLACK_WEBHOOK_URL")
233
+ if not webhook_url:
234
+ print("Slack webhook URL saknas")
235
+ return False
236
+
237
+ try:
238
+ # Formatera meddelandet för Slack
239
+ payload = {
240
+ "blocks": [
241
+ {
242
+ "type": "header",
243
+ "text": {
244
+ "type": "plain_text",
245
+ "text": subject
246
+ }
247
+ },
248
+ {
249
+ "type": "section",
250
+ "text": {
251
+ "type": "mrkdwn",
252
+ "text": content
253
+ }
254
+ }
255
+ ]
256
+ }
257
+
258
+ response = requests.post(
259
+ webhook_url,
260
+ json=payload,
261
+ headers={"Content-Type": "application/json"}
262
+ )
263
+
264
+ if response.status_code == 200:
265
+ print(f"Slack-meddelande skickat: {subject}")
266
+ return True
267
+ else:
268
+ print(f"Slack-anrop misslyckades: {response.status_code}, {response.text}")
269
+ return False
270
+ except Exception as e:
271
+ print(f"Fel vid sändning till Slack: {type(e).__name__}: {e}")
272
+ return False
273
+
274
  # --- Feedback & Like-funktion ---
275
  def vote(data: gr.LikeData):
276
  """
 
291
  "session_id": last_log.get("session_id"),
292
  "user_message": last_log.get("user_message"),
293
  })
294
+
295
+ # Använd den förbättrade loggfunktionen
296
+ safe_append_to_log(log_entry)
297
 
298
  # Skicka feedback till Slack
299
  try:
300
+ if feedback_type == "down": # Skicka bara negativ feedback
301
+ feedback_message = f"""
302
+ *⚠️ Negativ feedback registrerad*
303
+
304
+ *Fråga:* {last_log.get('user_message', 'Okänd fråga')}
305
+
306
+ *Svar:* {log_entry.get('bot_reply', 'Okänt svar')[:300]}{'...' if len(log_entry.get('bot_reply', '')) > 300 else ''}
307
+ """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
308
  # Skicka asynkront
309
  threading.Thread(
310
+ target=lambda: send_to_slack("Negativ feedback", feedback_message, "#ff0000"),
311
  daemon=True
312
  ).start()
313
  except Exception as e:
 
320
  """Läs alla loggposter från loggfilen."""
321
  logs = []
322
  try:
323
+ if os.path.exists(log_file_path):
324
+ with open(log_file_path, "r", encoding="utf-8") as file:
325
+ line_count = 0
326
+ for line in file:
327
+ line_count += 1
328
+ try:
329
+ log_entry = json.loads(line.strip())
330
+ logs.append(log_entry)
331
+ except json.JSONDecodeError as e:
332
+ print(f"Varning: Kunde inte tolka rad {line_count}: {e}")
333
+ continue
334
+ print(f"Läste {len(logs)} av {line_count} loggposter")
335
+ else:
336
+ print(f"Loggfil saknas: {log_file_path}")
337
  except Exception as e:
338
  print(f"Fel vid läsning av loggfil: {e}")
339
  return logs
 
372
 
373
  return feedback_count, negative_feedback_examples
374
 
375
+ def generate_monthly_stats(days=30):
376
+ """Genererar omfattande statistik över botanvändning för den senaste månaden."""
377
+ print(f"Genererar statistik för de senaste {days} dagarna...")
378
+
379
+ # Hämta loggar
380
+ logs = read_logs()
381
+
382
+ if not logs:
383
+ return {"error": "Inga loggar hittades för den angivna perioden"}
384
+
385
+ # Filtrera på datumintervall
386
+ now = datetime.now()
387
+ cutoff_date = now - timedelta(days=days)
388
+ filtered_logs = []
389
+
390
+ for log in logs:
391
+ if 'timestamp' in log:
392
+ try:
393
+ log_date = datetime.strptime(log['timestamp'], "%Y-%m-%d %H:%M:%S")
394
+ if log_date >= cutoff_date:
395
+ filtered_logs.append(log)
396
+ except:
397
+ pass # Hoppa över poster med ogiltigt datum
398
+
399
+ logs = filtered_logs
400
+
401
+ # Basstatistik
402
+ total_conversations = sum(1 for log in logs if 'user_message' in log)
403
+ unique_sessions = len(set(log.get('session_id', 'unknown') for log in logs if 'session_id' in log))
404
+ unique_users = len(set(log.get('user_id', 'unknown') for log in logs if 'user_id' in log))
405
+
406
+ # Feedback-statistik
407
+ feedback_logs = [log for log in logs if 'feedback' in log]
408
+ positive_feedback = sum(1 for log in feedback_logs if log.get('feedback') == 'up')
409
+ negative_feedback = sum(1 for log in feedback_logs if log.get('feedback') == 'down')
410
+ feedback_ratio = (positive_feedback / len(feedback_logs) * 100) if feedback_logs else 0
411
+
412
+ # Svarstidsstatistik
413
+ response_times = [log.get('response_time', 0) for log in logs if 'response_time' in log]
414
+ avg_response_time = sum(response_times) / len(response_times) if response_times else 0
415
+
416
+ # Plattformsstatistik
417
+ platforms = {}
418
+ browsers = {}
419
+ operating_systems = {}
420
+ for log in logs:
421
+ if 'platform' in log:
422
+ platforms[log['platform']] = platforms.get(log['platform'], 0) + 1
423
+ if 'browser' in log:
424
+ browsers[log['browser']] = browsers.get(log['browser'], 0) + 1
425
+ if 'os' in log:
426
+ operating_systems[log['os']] = operating_systems.get(log['os'], 0) + 1
427
+
428
+ # Skapa rapport
429
+ report = {
430
+ "period": f"Senaste {days} dagarna",
431
+ "generated_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
432
+ "basic_stats": {
433
+ "total_conversations": total_conversations,
434
+ "unique_sessions": unique_sessions,
435
+ "unique_users": unique_users,
436
+ "messages_per_user": round(total_conversations / unique_users, 2) if unique_users else 0
437
+ },
438
+ "feedback": {
439
+ "positive": positive_feedback,
440
+ "negative": negative_feedback,
441
+ "ratio_percent": round(feedback_ratio, 1)
442
+ },
443
+ "performance": {
444
+ "avg_response_time": round(avg_response_time, 2)
445
+ },
446
+ "platform_distribution": platforms,
447
+ "browser_distribution": browsers,
448
+ "os_distribution": operating_systems
449
+ }
450
+
451
+ return report
452
+
453
  def simple_status_report():
454
+ """Skickar en förenklad statusrapport till Slack."""
455
+ print("Genererar statusrapport för Slack...")
456
 
457
  try:
458
+ # Generera statistik
459
+ stats = generate_monthly_stats(days=7) # Senaste veckan
 
 
 
 
460
 
461
+ # Skapa innehåll för Slack
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
462
  now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
463
  subject = f"ChargeNode AI Bot - Status {now}"
464
 
465
+ if 'error' in stats:
466
+ content = f"*Fel vid generering av statistik:* {stats['error']}"
467
+ return send_to_slack(subject, content, "#ff0000")
468
+
469
+ # Formatera statistik
470
+ basic = stats["basic_stats"]
471
+ feedback = stats["feedback"]
472
+ perf = stats["performance"]
473
+
474
  content = f"""
475
+ *ChargeNode AI Bot - Statusrapport {now}*
476
+
477
+ *Basstatistik* (senaste 7 dagarna)
478
+ - Totalt antal konversationer: {basic['total_conversations']}
479
+ - Unika sessioner: {basic['unique_sessions']}
480
+ - Unika användare: {basic['unique_users']}
481
+ - Genomsnittlig svarstid: {perf['avg_response_time']} sekunder
482
+
483
+ *Feedback*
484
+ - 👍 Tumme upp: {feedback['positive']}
485
+ - 👎 Tumme ned: {feedback['negative']}
486
+ - Nöjdhet: {feedback['ratio_percent']}%
487
  """
488
 
489
+ # Lägg till de senaste konversationerna
490
+ logs = read_logs()
491
+ conversations = get_latest_conversations(logs, 3)
492
+
493
+ if conversations:
494
+ content += "\n*Senaste konversationer*\n"
495
+ for conv in conversations:
 
496
  content += f"""
497
+ > *Tid:* {conv['timestamp']}
498
+ > *Fråga:* {conv['user_message'][:100]}{'...' if len(conv['user_message']) > 100 else ''}
499
+ > *Svar:* {conv['bot_reply'][:100]}{'...' if len(conv['bot_reply']) > 100 else ''}
500
  """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
501
 
502
+ # Skicka till Slack
503
+ return send_to_slack(subject, content, "#2a9d8f")
504
 
 
 
 
 
 
 
 
505
  except Exception as e:
506
+ print(f"Fel vid generering av statusrapport: {e}")
507
 
508
+ # Skicka felmeddelande till Slack
509
+ error_subject = f"ChargeNode AI Bot - Fel vid statusrapport"
510
+ error_content = f"*Fel vid generering av statusrapport:* {str(e)}"
511
+ return send_to_slack(error_subject, error_content, "#ff0000")
 
 
 
 
 
 
 
512
 
513
  def send_support_to_slack(områdeskod, uttagsnummer, email, chat_history):
514
  """Skickar en supportförfrågan till Slack."""
 
521
  elif msg['role'] == 'assistant':
522
  chat_content += f">*Bot:* {msg['content'][:300]}{'...' if len(msg['content']) > 300 else ''}\n\n"
523
 
524
+ # Skapa innehåll
525
+ subject = f"Support förfrågan - {datetime.now().strftime('%Y-%m-%d %H:%M')}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
526
 
527
+ content = f"""
528
+ *Användarinformation*
529
+ - *Områdeskod:* {områdeskod or 'Ej angiven'}
530
+ - *Uttagsnummer:* {uttagsnummer or 'Ej angiven'}
531
+ - *Email:* {email}
532
+ - *Tidpunkt:* {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
533
+
534
+ *Chatthistorik:*
535
+ {chat_content}
536
+ """
537
+
538
+ # Skicka till Slack
539
+ return send_to_slack(subject, content, "#e76f51")
540
  except Exception as e:
541
  print(f"Fel vid sändning av support till Slack: {type(e).__name__}: {e}")
542
  return False
543
 
544
+ # --- Schemaläggning av rapporter ---
545
  def run_scheduler():
546
  """Kör schemaläggaren i en separat tråd med förenklad statusrapportering."""
547
  # Använd den förenklade funktionen för rapportering
 
549
  schedule.every().day.at("12:00").do(simple_status_report)
550
  schedule.every().day.at("17:00").do(simple_status_report)
551
 
552
+ # Veckorapport på måndagar
553
+ schedule.every().monday.at("09:00").do(lambda: send_to_slack(
554
+ "Veckostatistik",
555
+ f"*ChargeNode AI Bot - Veckostatistik*\n\n{json.dumps(generate_monthly_stats(7), indent=2)}",
556
+ "#3498db"
557
+ ))
558
+
559
  while True:
560
  schedule.run_pending()
561
  time.sleep(60) # Kontrollera varje minut
562
 
563
+ # Starta schemaläggaren i en separat tråd
564
  scheduler_thread = threading.Thread(target=run_scheduler, daemon=True)
565
  scheduler_thread.start()
566
 
567
+ # Kör en statusrapport vid uppstart för att verifiera att allt fungerar
568
  try:
569
+ print("Skickar en inledande statusrapport för att verifiera Slack-integrationen...")
570
+ # Anropa inte direkt här - sker i schemaläggaren
571
  except Exception as e:
572
+ print(f"Information: Statusrapport kommer att skickas enligt schema: {e}")
573
 
574
  # --- Gradio UI ---
575
  initial_chat = [{"role": "assistant", "content": "Detta är ChargeNode's AI bot. Hur kan jag hjälpa dig idag?"}]
 
611
  with gr.Column(scale=1):
612
  clear = gr.Button("Rensa")
613
  with gr.Column(scale=1):
614
+ support_btn = gr.Button("Behöver du mer hjälp?", elem_classes="support-btn")
615
 
616
  # Support form interface (initially hidden)
617
  with gr.Group(visible=False) as support_interface:
 
677
  "platform": platform
678
  }
679
 
680
+ # Använd den förbättrade loggfunktionen
681
+ safe_append_to_log(log_data)
 
682
  last_log = log_data
683
 
684
  # Skicka varje konversation direkt till Slack
685
  try:
686
+ # Konversationsinnehåll
687
+ conversation_content = f"""
688
+ *Ny konversation {timestamp}*
689
+
690
+ *Användare:* {message}
691
+
692
+ *Bot:* {response[:300]}{'...' if len(response) > 300 else ''}
693
+
694
+ *Sessionsinfo:* {session_id[:8]}... | {browser} | {platform}
695
+ """
696
+ # Skicka asynkront för att inte blockera svarstiden
697
+ threading.Thread(
698
+ target=lambda: send_to_slack(f"Ny konversation", conversation_content),
699
+ daemon=True
700
+ ).start()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
701
  except Exception as e:
702
  print(f"Kunde inte skicka konversation till Slack: {e}")
703