Spaces:
Sleeping
Sleeping
| import os | |
| import json | |
| import time | |
| import requests | |
| from anthropic import Anthropic | |
| from openai import OpenAI | |
| import gradio as gr | |
| import pandas as pd | |
| from huggingface_hub import CommitScheduler | |
| from datetime import datetime, timedelta | |
| import uuid | |
| from user_agents import parse as parse_ua | |
| import schedule | |
| import threading | |
| # --- Konfiguration --- | |
| CHARGENODE_URL = "https://www.chargenode.eu" | |
| # Kontrollera om vi kör i Hugging Face-miljön | |
| IS_HUGGINGFACE = os.environ.get("SPACE_ID") is not None | |
| # OpenAI-klient behålls för bakåtkompatibilitet | |
| OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY") | |
| if not OPENAI_API_KEY: | |
| raise ValueError("OPENAI_API_KEY saknas") | |
| client = OpenAI(api_key=OPENAI_API_KEY) | |
| # Lägg till Anthropic API-nyckel och klient | |
| ANTHROPIC_API_KEY = os.environ.get("ANTHROPIC_API_KEY") | |
| if not ANTHROPIC_API_KEY: | |
| raise ValueError("ANTHROPIC_API_KEY saknas") | |
| anthropic_client = Anthropic(api_key=ANTHROPIC_API_KEY) | |
| log_folder = "logs" | |
| os.makedirs(log_folder, exist_ok=True) | |
| log_file_path = os.path.join(log_folder, "conversation_log_v2.txt") | |
| # Skapa en tom loggfil om den inte finns | |
| if not os.path.exists(log_file_path): | |
| with open(log_file_path, "w", encoding="utf-8") as f: | |
| f.write("") # Skapa en tom fil | |
| print(f"Skapade tom loggfil: {log_file_path}") | |
| hf_token = os.environ.get("HF_TOKEN") | |
| if not hf_token: | |
| raise ValueError("HF_TOKEN saknas") | |
| # Minsta möjliga konfiguration som bör fungera | |
| scheduler = CommitScheduler( | |
| repo_id="ChargeNodeEurope/logfiles", | |
| repo_type="dataset", | |
| folder_path=log_folder, | |
| path_in_repo="logs_v2", | |
| every=300, # Vänta 5 minuter | |
| token=hf_token | |
| ) | |
| # --- Globala variabler --- | |
| last_log = None # Sparar loggdata från senaste svar för feedback | |
| full_context = None # Används för att spara hela kontexten | |
| # --- Förbättrad loggfunktion --- | |
| def safe_append_to_log(log_entry): | |
| """Säker metod för att lägga till loggdata utan att förlora historisk information.""" | |
| try: | |
| # Öppna filen i append-läge | |
| with open(log_file_path, "a", encoding="utf-8") as log_file: | |
| log_json = json.dumps(log_entry) | |
| log_file.write(log_json + "\n") | |
| log_file.flush() # Säkerställ att data skrivs till disk omedelbart | |
| print(f"Loggpost tillagd: {log_entry.get('timestamp', 'okänd tid')}") | |
| return True | |
| except Exception as e: | |
| print(f"Fel vid loggning: {e}") | |
| # Försök skapa mappen om den inte finns | |
| try: | |
| os.makedirs(os.path.dirname(log_file_path), exist_ok=True) | |
| # Försök igen | |
| with open(log_file_path, "a", encoding="utf-8") as log_file: | |
| log_json = json.dumps(log_entry) | |
| log_file.write(log_json + "\n") | |
| print("Loggpost tillagd efter återhämtning") | |
| return True | |
| except Exception as retry_error: | |
| print(f"Kritiskt fel vid loggning: {retry_error}") | |
| return False | |
| # --- Laddar textkällor --- | |
| def load_local_files(): | |
| """Laddar alla lokala filer och returnerar som en sammanhängande text.""" | |
| uploaded_text = "" | |
| allowed = [".txt", ".docx", ".pdf", ".csv", ".xls", ".xlsx"] | |
| excluded = ["requirements.txt", "app.py", "conversation_log.txt", "conversation_log_v2.txt", "secrets", "prompt.txt"] | |
| for file in os.listdir("."): | |
| if file.lower().endswith(tuple(allowed)) and file not in excluded: | |
| try: | |
| if file.endswith(".txt"): | |
| with open(file, "r", encoding="utf-8") as f: | |
| content = f.read() | |
| elif file.endswith(".docx"): | |
| from docx import Document # Import sker vid behov | |
| content = "\n".join([p.text for p in Document(file).paragraphs]) | |
| elif file.endswith(".pdf"): | |
| import PyPDF2 # Import sker vid behov | |
| with open(file, "rb") as f: | |
| reader = PyPDF2.PdfReader(f) | |
| content = "\n".join([p.extract_text() or "" for p in reader.pages]) | |
| elif file.endswith(".csv"): | |
| content = pd.read_csv(file).to_string() | |
| elif file.endswith((".xls", ".xlsx")): | |
| if file == "FAQ stadat.xlsx": | |
| df = pd.read_excel(file) | |
| rows = [] | |
| for index, row in df.iterrows(): | |
| rows.append(f"Fråga: {row['Fråga']}\nSvar: {row['Svar']}") | |
| content = "\n\n".join(rows) | |
| else: | |
| content = pd.read_excel(file).to_string() | |
| uploaded_text += f"\n\nFIL: {file}\n{content}" | |
| except Exception as e: | |
| print(f"Fel vid läsning av {file}: {str(e)}") | |
| return uploaded_text.strip() | |
| def load_prompt(): | |
| """Läser in system-prompts från prompt.txt med bättre felhantering.""" | |
| try: | |
| with open("prompt.txt", "r", encoding="utf-8") as f: | |
| prompt_content = f.read().strip() | |
| if not prompt_content: | |
| print("Varning: prompt.txt är tom, använder standardprompt") | |
| return "Du är ChargeNode's AI-assistent. Svara på frågor om ChargeNode's produkter och tjänster baserat på den tillhandahållna informationen." | |
| return prompt_content | |
| except FileNotFoundError: | |
| print("Varning: prompt.txt hittades inte, använder standardprompt") | |
| return "Du är ChargeNode's AI-assistent. Svara på frågor om ChargeNode's produkter och tjänster baserat på den tillhandahållna informationen." | |
| except Exception as e: | |
| print(f"Fel vid inläsning av prompt.txt: {e}, använder standardprompt") | |
| return "Du är ChargeNode's AI-assistent. Svara på frågor om ChargeNode's produkter och tjänster baserat på den tillhandahållna informationen." | |
| def load_full_context(): | |
| """Laddar hela kontexten en gång och cachar resultatet.""" | |
| global full_context | |
| if full_context is None: | |
| print("Laddar alla textfiler till fullständig kontext...") | |
| full_context = load_local_files() | |
| print(f"Laddade {len(full_context)} tecken till kontext") | |
| return full_context | |
| # Ladda prompt template | |
| prompt_template = load_prompt() | |
| def generate_answer(query): | |
| """Genererar svar baserat på fråga med hela kontexten.""" | |
| # Hämta hela kontexten | |
| context = load_full_context() | |
| if not context.strip(): | |
| return "Jag hittar ingen relevant information i mina källor.\n\nDetta är ett AI genererat svar." | |
| # System-prompts och användarfråga | |
| system_prompt = prompt_template | |
| # Skapa ett renare användarmeddelande | |
| user_message = f"""Jag har en fråga om ChargeNode. | |
| Hela dataunderlaget du kan använda för att svara: | |
| {context} | |
| Min fråga är: {query}""" | |
| try: | |
| # Använd Claude Haiku med hela kontexten | |
| response = anthropic_client.messages.create( | |
| model="claude-3-haiku-20240307", | |
| max_tokens=500, | |
| temperature=0.2, | |
| system=system_prompt, | |
| messages=[ | |
| {"role": "user", "content": user_message} | |
| ] | |
| ) | |
| answer = response.content[0].text | |
| return answer + "\n\nAI-genererat. Otillräcklig hjälp? Kontakta support@chargenode.eu eller 010-2051055" | |
| except Exception as e: | |
| return f"Tekniskt fel: {str(e)}\n\nAI-genererat. Kontakta support@chargenode.eu eller 010-2051055" | |
| # --- Slack Integration --- | |
| def send_to_slack(subject, content, color="#2a9d8f"): | |
| """Basfunktion för att skicka meddelanden till Slack.""" | |
| webhook_url = os.environ.get("SLACK_WEBHOOK_URL") | |
| if not webhook_url: | |
| print("Slack webhook URL saknas") | |
| return False | |
| try: | |
| # Formatera meddelandet för Slack | |
| payload = { | |
| "blocks": [ | |
| { | |
| "type": "header", | |
| "text": { | |
| "type": "plain_text", | |
| "text": subject | |
| } | |
| }, | |
| { | |
| "type": "section", | |
| "text": { | |
| "type": "mrkdwn", | |
| "text": content | |
| } | |
| } | |
| ] | |
| } | |
| response = requests.post( | |
| webhook_url, | |
| json=payload, | |
| headers={"Content-Type": "application/json"} | |
| ) | |
| if response.status_code == 200: | |
| print(f"Slack-meddelande skickat: {subject}") | |
| return True | |
| else: | |
| print(f"Slack-anrop misslyckades: {response.status_code}, {response.text}") | |
| return False | |
| except Exception as e: | |
| print(f"Fel vid sändning till Slack: {type(e).__name__}: {e}") | |
| return False | |
| # --- Feedback & Like-funktion --- | |
| def vote(data: gr.LikeData): | |
| """ | |
| Hanterar feedback från Gradio's inbyggda like-funktion. | |
| data.liked är True om uppvote, annars False. | |
| data.value innehåller information om meddelandet. | |
| """ | |
| feedback_type = "up" if data.liked else "down" | |
| global last_log | |
| log_entry = { | |
| "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"), | |
| "feedback": feedback_type, | |
| "bot_reply": data.value if not isinstance(data.value, dict) else data.value.get("value") | |
| } | |
| # Om global logdata finns, lägg till ytterligare metadata. | |
| if last_log: | |
| log_entry.update({ | |
| "session_id": last_log.get("session_id"), | |
| "user_message": last_log.get("user_message"), | |
| }) | |
| # Använd den förbättrade loggfunktionen | |
| safe_append_to_log(log_entry) | |
| # Skicka feedback till Slack | |
| try: | |
| if feedback_type == "down": # Skicka bara negativ feedback | |
| feedback_message = f""" | |
| *⚠️ Negativ feedback registrerad* | |
| *Fråga:* {last_log.get('user_message', 'Okänd fråga')} | |
| *Svar:* {log_entry.get('bot_reply', 'Okänt svar')[:300]}{'...' if len(log_entry.get('bot_reply', '')) > 300 else ''} | |
| """ | |
| # Skicka asynkront | |
| threading.Thread( | |
| target=lambda: send_to_slack("Negativ feedback", feedback_message, "#ff0000"), | |
| daemon=True | |
| ).start() | |
| except Exception as e: | |
| print(f"Kunde inte skicka feedback till Slack: {e}") | |
| return | |
| # --- Rapportering --- | |
| def read_logs(): | |
| """Läs alla loggposter från loggfilen.""" | |
| logs = [] | |
| try: | |
| if os.path.exists(log_file_path): | |
| with open(log_file_path, "r", encoding="utf-8") as file: | |
| line_count = 0 | |
| for line in file: | |
| line_count += 1 | |
| try: | |
| log_entry = json.loads(line.strip()) | |
| logs.append(log_entry) | |
| except json.JSONDecodeError as e: | |
| print(f"Varning: Kunde inte tolka rad {line_count}: {e}") | |
| continue | |
| print(f"Läste {len(logs)} av {line_count} loggposter") | |
| else: | |
| print(f"Loggfil saknas: {log_file_path}") | |
| except Exception as e: | |
| print(f"Fel vid läsning av loggfil: {e}") | |
| return logs | |
| def get_latest_conversations(logs, limit=50): | |
| """Hämta de senaste frågorna och svaren.""" | |
| conversations = [] | |
| for log in reversed(logs): | |
| if 'user_message' in log and 'bot_reply' in log: | |
| conversations.append({ | |
| 'user_message': log['user_message'], | |
| 'bot_reply': log['bot_reply'], | |
| 'timestamp': log.get('timestamp', '') | |
| }) | |
| if len(conversations) >= limit: | |
| break | |
| return conversations | |
| def get_feedback_stats(logs): | |
| """Sammanfatta feedback (tumme upp/ned).""" | |
| feedback_count = {"up": 0, "down": 0} | |
| negative_feedback_examples = [] | |
| for log in logs: | |
| if 'feedback' in log: | |
| feedback = log.get('feedback') | |
| if feedback in feedback_count: | |
| feedback_count[feedback] += 1 | |
| # Samla exempel på negativ feedback | |
| if feedback == "down" and 'user_message' in log and len(negative_feedback_examples) < 10: | |
| negative_feedback_examples.append({ | |
| 'user_message': log.get('user_message', 'Okänd fråga'), | |
| 'bot_reply': log.get('bot_reply', 'Okänt svar') | |
| }) | |
| return feedback_count, negative_feedback_examples | |
| def generate_monthly_stats(days=30): | |
| """Genererar omfattande statistik över botanvändning för den senaste månaden.""" | |
| print(f"Genererar statistik för de senaste {days} dagarna...") | |
| # Hämta loggar | |
| logs = read_logs() | |
| if not logs: | |
| return {"error": "Inga loggar hittades för den angivna perioden"} | |
| # Filtrera på datumintervall | |
| now = datetime.now() | |
| cutoff_date = now - timedelta(days=days) | |
| filtered_logs = [] | |
| for log in logs: | |
| if 'timestamp' in log: | |
| try: | |
| log_date = datetime.strptime(log['timestamp'], "%Y-%m-%d %H:%M:%S") | |
| if log_date >= cutoff_date: | |
| filtered_logs.append(log) | |
| except: | |
| pass # Hoppa över poster med ogiltigt datum | |
| logs = filtered_logs | |
| # Basstatistik | |
| total_conversations = sum(1 for log in logs if 'user_message' in log) | |
| unique_sessions = len(set(log.get('session_id', 'unknown') for log in logs if 'session_id' in log)) | |
| unique_users = len(set(log.get('user_id', 'unknown') for log in logs if 'user_id' in log)) | |
| # Feedback-statistik | |
| feedback_logs = [log for log in logs if 'feedback' in log] | |
| positive_feedback = sum(1 for log in feedback_logs if log.get('feedback') == 'up') | |
| negative_feedback = sum(1 for log in feedback_logs if log.get('feedback') == 'down') | |
| feedback_ratio = (positive_feedback / len(feedback_logs) * 100) if feedback_logs else 0 | |
| # Svarstidsstatistik | |
| response_times = [log.get('response_time', 0) for log in logs if 'response_time' in log] | |
| avg_response_time = sum(response_times) / len(response_times) if response_times else 0 | |
| # Plattformsstatistik | |
| platforms = {} | |
| browsers = {} | |
| operating_systems = {} | |
| for log in logs: | |
| if 'platform' in log: | |
| platforms[log['platform']] = platforms.get(log['platform'], 0) + 1 | |
| if 'browser' in log: | |
| browsers[log['browser']] = browsers.get(log['browser'], 0) + 1 | |
| if 'os' in log: | |
| operating_systems[log['os']] = operating_systems.get(log['os'], 0) + 1 | |
| # Skapa rapport | |
| report = { | |
| "period": f"Senaste {days} dagarna", | |
| "generated_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S"), | |
| "basic_stats": { | |
| "total_conversations": total_conversations, | |
| "unique_sessions": unique_sessions, | |
| "unique_users": unique_users, | |
| "messages_per_user": round(total_conversations / unique_users, 2) if unique_users else 0 | |
| }, | |
| "feedback": { | |
| "positive": positive_feedback, | |
| "negative": negative_feedback, | |
| "ratio_percent": round(feedback_ratio, 1) | |
| }, | |
| "performance": { | |
| "avg_response_time": round(avg_response_time, 2) | |
| }, | |
| "platform_distribution": platforms, | |
| "browser_distribution": browsers, | |
| "os_distribution": operating_systems | |
| } | |
| return report | |
| def simple_status_report(): | |
| """Skickar en förenklad statusrapport till Slack.""" | |
| print("Genererar statusrapport för Slack...") | |
| try: | |
| # Generera statistik | |
| stats = generate_monthly_stats(days=7) # Senaste veckan | |
| # Skapa innehåll för Slack | |
| now = datetime.now().strftime("%Y-%m-%d %H:%M:%S") | |
| subject = f"ChargeNode AI Bot - Status {now}" | |
| if 'error' in stats: | |
| content = f"*Fel vid generering av statistik:* {stats['error']}" | |
| return send_to_slack(subject, content, "#ff0000") | |
| # Formatera statistik | |
| basic = stats["basic_stats"] | |
| feedback = stats["feedback"] | |
| perf = stats["performance"] | |
| content = f""" | |
| *ChargeNode AI Bot - Statusrapport {now}* | |
| *Basstatistik* (senaste 7 dagarna) | |
| - Totalt antal konversationer: {basic['total_conversations']} | |
| - Unika sessioner: {basic['unique_sessions']} | |
| - Unika användare: {basic['unique_users']} | |
| - Genomsnittlig svarstid: {perf['avg_response_time']} sekunder | |
| *Feedback* | |
| - 👍 Tumme upp: {feedback['positive']} | |
| - 👎 Tumme ned: {feedback['negative']} | |
| - Nöjdhet: {feedback['ratio_percent']}% | |
| """ | |
| # Lägg till de senaste konversationerna | |
| logs = read_logs() | |
| conversations = get_latest_conversations(logs, 3) | |
| if conversations: | |
| content += "\n*Senaste konversationer*\n" | |
| for conv in conversations: | |
| content += f""" | |
| > *Tid:* {conv['timestamp']} | |
| > *Fråga:* {conv['user_message'][:100]}{'...' if len(conv['user_message']) > 100 else ''} | |
| > *Svar:* {conv['bot_reply'][:100]}{'...' if len(conv['bot_reply']) > 100 else ''} | |
| """ | |
| # Skicka till Slack | |
| return send_to_slack(subject, content, "#2a9d8f") | |
| except Exception as e: | |
| print(f"Fel vid generering av statusrapport: {e}") | |
| # Skicka felmeddelande till Slack | |
| error_subject = f"ChargeNode AI Bot - Fel vid statusrapport" | |
| error_content = f"*Fel vid generering av statusrapport:* {str(e)}" | |
| return send_to_slack(error_subject, error_content, "#ff0000") | |
| def send_support_to_slack(områdeskod, uttagsnummer, email, chat_history): | |
| """Skickar en supportförfrågan till Slack.""" | |
| try: | |
| # Formatera chat-historiken | |
| chat_content = "" | |
| for msg in chat_history: | |
| if msg['role'] == 'user': | |
| chat_content += f">*Användare:* {msg['content']}\n\n" | |
| elif msg['role'] == 'assistant': | |
| chat_content += f">*Bot:* {msg['content'][:300]}{'...' if len(msg['content']) > 300 else ''}\n\n" | |
| # Skapa innehåll | |
| subject = f"Support förfrågan - {datetime.now().strftime('%Y-%m-%d %H:%M')}" | |
| content = f""" | |
| *Användarinformation* | |
| - *Områdeskod:* {områdeskod or 'Ej angiven'} | |
| - *Uttagsnummer:* {uttagsnummer or 'Ej angiven'} | |
| - *Email:* {email} | |
| - *Tidpunkt:* {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} | |
| *Chatthistorik:* | |
| {chat_content} | |
| """ | |
| # Skicka till Slack | |
| return send_to_slack(subject, content, "#e76f51") | |
| except Exception as e: | |
| print(f"Fel vid sändning av support till Slack: {type(e).__name__}: {e}") | |
| return False | |
| # --- Schemaläggning av rapporter --- | |
| def run_scheduler(): | |
| """Kör schemaläggaren i en separat tråd med förenklad statusrapportering.""" | |
| # Använd den förenklade funktionen för rapportering | |
| schedule.every().day.at("08:00").do(simple_status_report) | |
| schedule.every().day.at("12:00").do(simple_status_report) | |
| schedule.every().day.at("17:00").do(simple_status_report) | |
| # Veckorapport på måndagar | |
| schedule.every().monday.at("09:00").do(lambda: send_to_slack( | |
| "Veckostatistik", | |
| f"*ChargeNode AI Bot - Veckostatistik*\n\n{json.dumps(generate_monthly_stats(7), indent=2)}", | |
| "#3498db" | |
| )) | |
| while True: | |
| schedule.run_pending() | |
| time.sleep(60) # Kontrollera varje minut | |
| # Starta schemaläggaren i en separat tråd | |
| scheduler_thread = threading.Thread(target=run_scheduler, daemon=True) | |
| scheduler_thread.start() | |
| # Kör en statusrapport vid uppstart för att verifiera att allt fungerar | |
| try: | |
| print("Skickar en inledande statusrapport för att verifiera Slack-integrationen...") | |
| # Anropa inte direkt här - sker i schemaläggaren | |
| except Exception as e: | |
| print(f"Information: Statusrapport kommer att skickas enligt schema: {e}") | |
| # --- Gradio UI --- | |
| initial_chat = [{"role": "assistant", "content": "Detta är ChargeNode's AI bot. Hur kan jag hjälpa dig idag?"}] | |
| custom_css = """ | |
| body {background-color: #f7f7f7; font-family: Arial, sans-serif; margin: 0; padding: 0;} | |
| h1 {font-family: Helvetica, sans-serif; color: #2a9d8f; text-align: center; margin-bottom: 0.5em;} | |
| .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;} | |
| #chatbot_conversation { max-height: 300px; overflow-y: auto; } | |
| .gr-button {background-color: #2a9d8f; color: #fff; border: none; border-radius: 4px; padding: 8px 16px; margin: 5px;} | |
| .gr-button:hover {background-color: #264653;} | |
| .support-btn {background-color: #000000; color: #ffffff; margin-top: 5px; margin-bottom: 10px;} | |
| .support-btn:hover {background-color: #333333;} | |
| .flex-row {display: flex; flex-direction: row; gap: 5px;} | |
| .gr-form {padding: 10px; border: 1px solid #eee; border-radius: 4px; margin-bottom: 10px;} | |
| .chat-preview {max-height: 150px; overflow-y: auto; border: 1px solid #eee; padding: 8px; margin-top: 10px; font-size: 12px; background-color: #f9f9f9;} | |
| .success-message {font-size: 16px; font-weight: normal; margin-bottom: 15px;} | |
| /* Dölj Gradio-footer */ | |
| footer {display: none !important;} | |
| .footer {display: none !important;} | |
| .gr-footer {display: none !important;} | |
| .gradio-footer {display: none !important;} | |
| .gradio-container .footer {display: none !important;} | |
| .gradio-container .gr-footer {display: none !important;} | |
| """ | |
| with gr.Blocks(css=custom_css, title="ChargeNode Kundtjänst") as app: | |
| 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") | |
| # Chat interface | |
| with gr.Group(visible=True) as chat_interface: | |
| chatbot = gr.Chatbot(value=initial_chat, type="messages", elem_id="chatbot_conversation") | |
| chatbot.like(vote, None, None) | |
| with gr.Row(): | |
| msg = gr.Textbox(label="Meddelande", placeholder="Ange din fråga...") | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| clear = gr.Button("Rensa") | |
| with gr.Column(scale=1): | |
| support_btn = gr.Button("Behöver du mer hjälp?", elem_classes="support-btn") | |
| # Support form interface (initially hidden) | |
| with gr.Group(visible=False) as support_interface: | |
| gr.Markdown("### Vänligen fyll i din områdeskod, uttagsnummer och din email adress") | |
| with gr.Group(elem_classes="gr-form"): | |
| områdeskod = gr.Textbox(label="Områdeskod", placeholder="Områdeskod (valfritt)", info="Numeriskt värde") | |
| uttagsnummer = gr.Textbox(label="Uttagsnummer", placeholder="Uttagsnummer (valfritt)", info="Numeriskt värde") | |
| email = gr.Textbox(label="Din email adress", placeholder="din@email.se", info="Email adress krävs") | |
| gr.Markdown("### Chat som skickas till support:") | |
| chat_preview = gr.Markdown(elem_classes="chat-preview") | |
| with gr.Row(): | |
| back_btn = gr.Button("Tillbaka") | |
| send_support_btn = gr.Button("Skicka") | |
| # Success message (initially hidden) | |
| with gr.Group(visible=False) as success_interface: | |
| gr.Markdown("Tack för att du kontaktar support@chargenode.eu. Vi återkommer inom kort", elem_classes="success-message") | |
| back_to_chat_btn = gr.Button("Tillbaka till chatten") | |
| def respond(message, chat_history, request: gr.Request): | |
| global last_log | |
| start = time.time() | |
| response = generate_answer(message) | |
| elapsed = round(time.time() - start, 2) | |
| timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") | |
| session_id = str(uuid.uuid4()) | |
| # Använd session_id från tidigare logg om det finns | |
| if last_log and 'session_id' in last_log: | |
| session_id = last_log.get('session_id') | |
| user_id = request.client.host if request else "okänd" | |
| ua_str = request.headers.get("user-agent", "") | |
| ref = request.headers.get("referer", "") | |
| ip = request.headers.get("x-forwarded-for", user_id).split(",")[0] | |
| ua = parse_ua(ua_str) | |
| browser = f"{ua.browser.family} {ua.browser.version_string}" | |
| osys = f"{ua.os.family} {ua.os.version_string}" | |
| platform = "webb" | |
| if "chargenode.eu" in ref: | |
| platform = "chargenode.eu" | |
| elif "localhost" in ref: | |
| platform = "test" | |
| elif "app" in ref: | |
| platform = "app" | |
| log_data = { | |
| "timestamp": timestamp, | |
| "user_id": user_id, | |
| "session_id": session_id, | |
| "user_message": message, | |
| "bot_reply": response, | |
| "response_time": elapsed, | |
| "ip": ip, | |
| "browser": browser, | |
| "os": osys, | |
| "platform": platform | |
| } | |
| # Använd den förbättrade loggfunktionen | |
| safe_append_to_log(log_data) | |
| last_log = log_data | |
| # Skicka varje konversation direkt till Slack | |
| try: | |
| # Konversationsinnehåll | |
| conversation_content = f""" | |
| *Ny konversation {timestamp}* | |
| *Användare:* {message} | |
| *Bot:* {response[:300]}{'...' if len(response) > 300 else ''} | |
| *Sessionsinfo:* {session_id[:8]}... | {browser} | {platform} | |
| """ | |
| # Skicka asynkront för att inte blockera svarstiden | |
| threading.Thread( | |
| target=lambda: send_to_slack(f"Ny konversation", conversation_content), | |
| daemon=True | |
| ).start() | |
| except Exception as e: | |
| print(f"Kunde inte skicka konversation till Slack: {e}") | |
| chat_history.append({"role": "user", "content": message}) | |
| chat_history.append({"role": "assistant", "content": response}) | |
| return "", chat_history | |
| def format_chat_preview(chat_history): | |
| if not chat_history: | |
| return "Ingen chatthistorik att visa." | |
| preview = "" | |
| for msg in chat_history: | |
| sender = "Användare" if msg["role"] == "user" else "Bot" | |
| content = msg["content"] | |
| if len(content) > 100: # Truncate long messages | |
| content = content[:100] + "..." | |
| preview += f"**{sender}:** {content}\n\n" | |
| return preview | |
| def show_support_form(chat_history): | |
| preview = format_chat_preview(chat_history) | |
| return { | |
| chat_interface: gr.Group(visible=False), | |
| support_interface: gr.Group(visible=True), | |
| success_interface: gr.Group(visible=False), | |
| chat_preview: preview | |
| } | |
| def back_to_chat(): | |
| return { | |
| chat_interface: gr.Group(visible=True), | |
| support_interface: gr.Group(visible=False), | |
| success_interface: gr.Group(visible=False) | |
| } | |
| def submit_support_form(områdeskod, uttagsnummer, email, chat_history): | |
| """Hanterar formulärinskickningen med bättre felhantering.""" | |
| print(f"Support-förfrågan: områdeskod={områdeskod}, uttagsnummer={uttagsnummer}, email={email}") | |
| # Validera input med tydligare loggning | |
| validation_errors = [] | |
| if områdeskod and not områdeskod.isdigit(): | |
| print(f"Validerar områdeskod: '{områdeskod}' (felaktig)") | |
| validation_errors.append("Områdeskod måste vara numerisk.") | |
| else: | |
| print(f"Validerar områdeskod: '{områdeskod}' (ok)") | |
| if uttagsnummer and not uttagsnummer.isdigit(): | |
| print(f"Validerar uttagsnummer: '{uttagsnummer}' (felaktig)") | |
| validation_errors.append("Uttagsnummer måste vara numerisk.") | |
| else: | |
| print(f"Validerar uttagsnummer: '{uttagsnummer}' (ok)") | |
| if not email: | |
| print("Validerar email: (saknas)") | |
| validation_errors.append("En giltig e-postadress krävs.") | |
| elif '@' not in email or '.' not in email.split('@')[1]: | |
| print(f"Validerar email: '{email}' (felaktigt format)") | |
| validation_errors.append("En giltig e-postadress krävs.") | |
| else: | |
| print(f"Validerar email: '{email}' (ok)") | |
| # Om det finns valideringsfel | |
| if validation_errors: | |
| print(f"Valideringsfel: {validation_errors}") | |
| return { | |
| chat_interface: gr.Group(visible=False), | |
| support_interface: gr.Group(visible=True), | |
| success_interface: gr.Group(visible=False), | |
| chat_preview: "\n".join(["**Fel:**"] + validation_errors) | |
| } | |
| # Om formuläret klarade valideringen, försök skicka till Slack | |
| try: | |
| print("Försöker skicka supportförfrågan till Slack...") | |
| # Skapa en förenklad chathistorik för loggning | |
| chat_summary = [] | |
| for msg in chat_history: | |
| if 'role' in msg and 'content' in msg: | |
| chat_summary.append(f"{msg['role']}: {msg['content'][:30]}...") | |
| print(f"Chatthistorik att skicka: {chat_summary}") | |
| # Skicka till Slack | |
| success = send_support_to_slack(områdeskod, uttagsnummer, email, chat_history) | |
| if success: | |
| print("Support-förfrågan skickad till Slack framgångsrikt") | |
| return { | |
| chat_interface: gr.Group(visible=False), | |
| support_interface: gr.Group(visible=False), | |
| success_interface: gr.Group(visible=True) | |
| } | |
| else: | |
| print("Support-förfrågan till Slack misslyckades") | |
| return { | |
| chat_interface: gr.Group(visible=False), | |
| support_interface: gr.Group(visible=True), | |
| success_interface: gr.Group(visible=False), | |
| chat_preview: "**Ett fel uppstod när meddelandet skulle skickas. Vänligen försök igen senare.**" | |
| } | |
| except Exception as e: | |
| print(f"Oväntat fel vid hantering av support-formulär: {e}") | |
| return { | |
| chat_interface: gr.Group(visible=False), | |
| support_interface: gr.Group(visible=True), | |
| success_interface: gr.Group(visible=False), | |
| chat_preview: f"**Ett fel uppstod: {str(e)}**" | |
| } | |
| msg.submit(respond, [msg, chatbot], [msg, chatbot]) | |
| clear.click(lambda: None, None, chatbot, queue=False) | |
| support_btn.click(show_support_form, chatbot, [chat_interface, support_interface, success_interface, chat_preview]) | |
| back_btn.click(back_to_chat, None, [chat_interface, support_interface, success_interface]) | |
| back_to_chat_btn.click(back_to_chat, None, [chat_interface, support_interface, success_interface]) | |
| send_support_btn.click( | |
| submit_support_form, | |
| [områdeskod, uttagsnummer, email, chatbot], | |
| [chat_interface, support_interface, success_interface, chat_preview] | |
| ) | |
| # Ladda kontexten direkt vid uppstart | |
| print("Förbereder hela kontexten vid uppstart...") | |
| load_full_context() | |
| print("Kontext laddad och redo!") | |
| if __name__ == "__main__": | |
| app.launch(share=True) |