import os import json import pandas as pd import torch import gradio as gr from huggingface_hub import login, Repository, hf_hub_download from sentence_transformers import SentenceTransformer, util # ------------------------------- # Global Git-konfiguration # ------------------------------- os.system('git config --global user.email "niklas.berg@chargenode.eu"') os.system('git config --global user.name "Niklas Berg"') # ------------------------------- # Miljövariabler & Inloggning # ------------------------------- HF_TOKEN = os.getenv("HUGGINGFACE_TOKEN") APP_USERNAME = os.getenv("APP_USERNAME") APP_PASSWORD = os.getenv("APP_PASSWORD") if not HF_TOKEN: raise ValueError("HUGGINGFACE_TOKEN miljövariabel saknas!") if not APP_USERNAME or not APP_PASSWORD: raise ValueError("APP_USERNAME eller APP_PASSWORD miljövariabel saknas!") login(token=HF_TOKEN) # ------------------------------- # Repo & FAQ-konfiguration # ------------------------------- REPO_ID = "ChargeNodeEurope/Chatbot_4o_mini" # Exempel REPO_LOCAL_PATH = "chatbot_faq_repo" FAQ_XLSX_PATH = "FAQ stadat.xlsx" repo = Repository( local_dir=REPO_LOCAL_PATH, clone_from=REPO_ID, repo_type="space", # Viktigt om det är en Space use_auth_token=HF_TOKEN ) faq_path = os.path.join(REPO_LOCAL_PATH, FAQ_XLSX_PATH) try: df = pd.read_excel(faq_path) df.dropna(subset=["Fråga", "Svar"], inplace=True) except Exception as e: raise FileNotFoundError(f"Kunde inte ladda FAQ-filen: {str(e)}") # ------------------------------- # Modell & embeddings # ------------------------------- model = SentenceTransformer("sentence-transformers/all-MiniLM-L6-v2") faq_questions = df["Fråga"].tolist() faq_embeddings = model.encode(faq_questions, convert_to_tensor=True) # ------------------------------- # Hjälpfunktioner # ------------------------------- def uppdatera_embeddings(): """ Uppdatera embeddings efter att FAQ-DataFrame har ändrats. """ global faq_questions, faq_embeddings, df faq_questions = df["Fråga"].tolist() faq_embeddings = model.encode(faq_questions, convert_to_tensor=True) def sök_faq(fråga): """ Gör en enkel semantisk sökning i FAQ och returnerar topp 3 resultat. """ fråga = fråga.strip() if not fråga: return pd.DataFrame(columns=["Liknande fråga", "Svar", "Kategori", "Confidence"]) # Skapa embedding för query query_emb = model.encode(fråga, convert_to_tensor=True) cos_scores = util.cos_sim(query_emb, faq_embeddings)[0] top_results = torch.topk(cos_scores, k=3) indices = top_results.indices.tolist() scores = top_results.values.tolist() data = [] for idx, score in zip(indices, scores): row = df.iloc[idx] data.append({ "Liknande fråga": row["Fråga"], "Svar": row["Svar"], "Kategori": row["Kategori"], "Confidence": round(float(score), 3) }) return pd.DataFrame(data) def lägg_till_faq(fråga, svar, kategori): """ Lägger till en ny FAQ-post, sparar lokalt och pushar till Hugging Face. """ global df fråga = fråga.strip() svar = svar.strip() kategori = kategori.strip() if not fråga or not svar or not kategori: return "Fråga, svar och kategori får inte vara tomma!" try: ny_rad = pd.DataFrame([[fråga, svar, kategori]], columns=["Fråga", "Svar", "Kategori"]) df = pd.concat([df, ny_rad], ignore_index=True) df.to_excel(faq_path, index=False) uppdatera_embeddings() repo.git_add() repo.git_commit(f"Lade till FAQ: {fråga[:50]}...") repo.git_push() return "Fråga tillagd och synkad med Hugging Face!" except Exception as e: return f"Fel vid uppdatering: {str(e)}" def visa_senaste_faq(antal=10): """Returnerar de 10 senaste FAQ-posterna.""" return df.tail(antal) def uppdatera_faq(gammal_fråga, nytt_svar, ny_kategori): global df gammal_fråga = gammal_fråga.strip() nytt_svar = nytt_svar.strip() ny_kategori = ny_kategori.strip() if not gammal_fråga: return "Ingen fråga vald." match_index = df.index[df["Fråga"] == gammal_fråga] if len(match_index) == 0: return "Ingen matchande FAQ-fråga hittad." if nytt_svar: df.loc[match_index, "Svar"] = nytt_svar if ny_kategori: df.loc[match_index, "Kategori"] = ny_kategori try: df.to_excel(faq_path, index=False) uppdatera_embeddings() repo.git_add() repo.git_commit(f"Uppdaterade FAQ: {gammal_fråga[:50]}...") repo.git_push() return "FAQ uppdaterad!" except Exception as e: return f"Fel vid uppdatering: {str(e)}" def ta_bort_faq(fråga_att_radera): global df fråga_att_radera = fråga_att_radera.strip() if not fråga_att_radera: return "Ingen fråga vald." match_index = df.index[df["Fråga"] == fråga_att_radera] if len(match_index) == 0: return "Ingen matchande FAQ-fråga hittad." df.drop(match_index, inplace=True) try: df.to_excel(faq_path, index=False) uppdatera_embeddings() repo.git_add() repo.git_commit(f"Raderade FAQ: {fråga_att_radera[:50]}...") repo.git_push() return f"FAQ borttagen: {fråga_att_radera}" except Exception as e: return f"Fel vid borttagning: {str(e)}" def visa_logfil(): """ Hämtar loggfilen från Hugging Face Hub och returnerar en DataFrame med de senaste 10 posterna: Datum, UserID, Fråga (user_message) och Svar (bot_reply). """ try: # Ange repo- och filsökväg enligt Hugging Face Hub repo_id = "ChargeNodeEurope/logfiles" filename = "logs_v2/conversation_log_v2.txt" local_log_path = hf_hub_download(repo_id=repo_id, filename=filename, repo_type="dataset") logs = [] with open(local_log_path, "r", encoding="utf-8") as f: for line in f: line = line.strip() if line: log_entry = json.loads(line) logs.append(log_entry) # Sortera loggarna med nyaste först baserat på timestamp logs_sorted = sorted(logs, key=lambda x: x["timestamp"], reverse=True) latest10 = logs_sorted[:10] df_logs = pd.DataFrame(latest10) if not df_logs.empty: df_logs = df_logs[["timestamp", "user_id", "user_message", "bot_reply"]] df_logs.columns = ["Datum", "UserID", "Fråga", "Svar"] return df_logs except Exception as e: return pd.DataFrame({"Fel": [f"Fel vid inläsning av loggfil: {e}"]}) def logga_in(user, pwd): """ Verifierar inloggningsuppgifter mot APP_USERNAME och APP_PASSWORD. """ if user == APP_USERNAME and pwd == APP_PASSWORD: return "Inloggning lyckades!" else: return "Felaktiga inloggningsuppgifter!" # ------------------------------- # Gradio - Användargränssnitt med anpassad look & feel # ------------------------------- # Anpassad CSS – Ändrat "max-width" till 1800px för att sidan ska vara dubbelt så bred. custom_css = """ body {background-color: #f7f7f7; font-family: Arial, sans-serif;} h1, h2, h3 {font-family: Helvetica, sans-serif; color: #2a9d8f; text-align: center;} .gradio-container {max-width: 1800px; margin: auto; padding: 20px;} .gr-button {background-color: #264653; color: #fff;} """ with gr.Blocks(css=custom_css, title="Enkel FAQ Admin") as demo: gr.Markdown("

Enkel FAQ Admin

") gr.Markdown("

Administrera FAQ-poster: sök, lägg till, uppdatera, ta bort och visa loggfil.

") with gr.Tabs(): # Flik: Inloggning with gr.TabItem("Inloggning"): with gr.Row(): username_input = gr.Textbox(label="Användarnamn", placeholder="Ange användarnamn") with gr.Row(): password_input = gr.Textbox(label="Lösenord", placeholder="Ange lösenord", type="password") login_button = gr.Button("Logga in") login_status = gr.Textbox(label="Inloggningsstatus") login_button.click(fn=logga_in, inputs=[username_input, password_input], outputs=login_status) # Flik: Sök i FAQ with gr.TabItem("Sök i FAQ"): with gr.Row(): inp_question = gr.Textbox(label="Din fråga", placeholder="Ex: Hur startar jag en laddning?") btn_search = gr.Button("Sök") out_search = gr.Dataframe(label="Topp 3 resultat") btn_search.click(fn=sök_faq, inputs=inp_question, outputs=out_search) # Flik: Lägg till FAQ with gr.TabItem("Lägg till FAQ"): with gr.Row(): add_question = gr.Textbox(label="Ny fråga") with gr.Row(): add_answer = gr.Textbox(label="Nytt svar") with gr.Row(): add_cat = gr.Textbox(label="Ny kategori") btn_add = gr.Button("Lägg till") out_add = gr.Textbox(label="Status") btn_add.click(fn=lägg_till_faq, inputs=[add_question, add_answer, add_cat], outputs=out_add) # Flik: Redigera / Ta bort FAQ with gr.TabItem("Redigera / Ta bort FAQ"): with gr.Row(): existing_quests = gr.Dropdown(choices=df["Fråga"].tolist(), label="Befintliga frågor") with gr.Row(): new_answer = gr.Textbox(label="Nytt svar (valfritt)") with gr.Row(): new_cat = gr.Textbox(label="Ny kategori (valfritt)") btn_update = gr.Button("Uppdatera") out_update = gr.Textbox(label="Status") btn_update.click(fn=uppdatera_faq, inputs=[existing_quests, new_answer, new_cat], outputs=out_update) gr.Markdown("#### Eller ta bort en FAQ-post:") btn_delete = gr.Button("Ta bort") out_delete = gr.Textbox(label="Status") btn_delete.click(fn=ta_bort_faq, inputs=existing_quests, outputs=out_delete) # Flik: Loggfil with gr.TabItem("Loggfil"): btn_show_log = gr.Button("Visa senaste 10 poster") out_log = gr.Dataframe(label="Loggfil") btn_show_log.click(fn=visa_logfil, inputs=[], outputs=out_log) if __name__ == "__main__": demo.launch()