Spaces:
Runtime error
Runtime error
| 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("<h1>Enkel FAQ Admin</h1>") | |
| gr.Markdown("<p style='text-align: center;'>Administrera FAQ-poster: sök, lägg till, uppdatera, ta bort och visa loggfil.</p>") | |
| 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() | |