k96beni commited on
Commit
5726f3b
·
verified ·
1 Parent(s): bc1676d

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +161 -450
app.py CHANGED
@@ -15,11 +15,13 @@ import threading
15
  from sentence_transformers import SentenceTransformer
16
  import numpy as np
17
  import faiss
 
18
 
19
  # --- Konfiguration ---
20
  CHARGENODE_URL = "https://www.chargenode.eu"
21
- MAX_CHUNK_SIZE = 1024 # Storlek chunker för indexering
22
- RETRIEVAL_K = 8 # Antal chunker att hämta vid varje sökning
 
23
 
24
  # Kontrollera om vi kör i Hugging Face-miljön
25
  IS_HUGGINGFACE = os.environ.get("SPACE_ID") is not None
@@ -69,6 +71,7 @@ embeddings = None
69
  index = None
70
  chunks = []
71
  chunk_sources = []
 
72
 
73
  # --- Förbättrad loggfunktion ---
74
  def safe_append_to_log(log_entry):
@@ -163,29 +166,117 @@ def load_prompt():
163
  print(f"Fel vid inläsning av prompt.txt: {e}, använder standardprompt")
164
  return "Du är ChargeNode's AI-assistent. Svara på frågor om ChargeNode's produkter och tjänster baserat på den tillhandahållna informationen."
165
 
166
- # Förbered textsegment
167
  def prepare_chunks(text_data):
168
- """Delar upp texten i mindre segment för embedding och sökning."""
169
  chunks, sources = [], []
 
 
170
  for source, text in text_data.items():
 
171
  paragraphs = [p for p in text.split("\n") if p.strip()]
172
- chunk = ""
173
- for para in paragraphs:
174
- if len(chunk) + len(para) + 1 <= MAX_CHUNK_SIZE:
175
- chunk += " " + para
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
176
  else:
177
- if chunk.strip():
178
- chunks.append(chunk.strip())
179
- sources.append(source)
180
- chunk = para
181
- if chunk.strip():
182
- chunks.append(chunk.strip())
183
- sources.append(source)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
184
  return chunks, sources
185
 
186
  def initialize_embeddings():
187
  """Initierar SentenceTransformer och FAISS-index vid första anrop."""
188
- global embedder, embeddings, index, chunks, chunk_sources
189
 
190
  if embedder is None:
191
  print("Initierar SentenceTransformer och FAISS-index...")
@@ -203,12 +294,62 @@ def initialize_embeddings():
203
  index = faiss.IndexFlatIP(embeddings.shape[1])
204
  index.add(embeddings)
205
  print("FAISS-index klart")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
206
 
207
  def retrieve_context(query, k=RETRIEVAL_K):
208
- """Hämtar relevant kontext för frågor."""
209
  # Säkerställ att modeller är laddade
210
  initialize_embeddings()
211
 
 
 
 
 
 
 
 
212
  query_embedding = embedder.encode([query], convert_to_numpy=True)
213
  query_embedding /= np.linalg.norm(query_embedding)
214
  D, I = index.search(query_embedding, k)
@@ -244,7 +385,7 @@ Min fråga är: {query}"""
244
  try:
245
  # Använd Claude Haiku med RAG-baserad kontext
246
  response = anthropic_client.messages.create(
247
- model="claude-3-haiku-20240307",
248
  max_tokens=500,
249
  temperature=0.3,
250
  system=system_prompt,
@@ -605,9 +746,7 @@ except Exception as e:
605
  # --- Gradio UI ---
606
  initial_chat = [{"role": "assistant", "content": "Detta är ChargeNode's AI bot. Hur kan jag hjälpa dig idag?"}]
607
 
608
- # BEHÅLL DESKTOP LAYOUT - FIXA MOBIL-SCROLLING OCH LAYOUT
609
  custom_css = """
610
- /* DESKTOP LAYOUT - BEHÅLLS EXAKT SOM URSPRUNGLIG */
611
  body {background-color: #f7f7f7; font-family: Arial, sans-serif; margin: 0; padding: 0;}
612
  h1 {font-family: Helvetica, sans-serif; color: #2a9d8f; text-align: center; margin-bottom: 0.5em;}
613
  .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;}
@@ -620,7 +759,6 @@ h1 {font-family: Helvetica, sans-serif; color: #2a9d8f; text-align: center; marg
620
  .gr-form {padding: 10px; border: 1px solid #eee; border-radius: 4px; margin-bottom: 10px;}
621
  .chat-preview {max-height: 150px; overflow-y: auto; border: 1px solid #eee; padding: 8px; margin-top: 10px; font-size: 12px; background-color: #f9f9f9;}
622
  .success-message {font-size: 16px; font-weight: normal; margin-bottom: 15px;}
623
-
624
  /* Dölj Gradio-footer */
625
  footer {display: none !important;}
626
  .footer {display: none !important;}
@@ -628,427 +766,9 @@ footer {display: none !important;}
628
  .gradio-footer {display: none !important;}
629
  .gradio-container .footer {display: none !important;}
630
  .gradio-container .gr-footer {display: none !important;}
631
-
632
- /* MOBIL-ANPASSNINGAR - FOKUS PÅ SCROLLNING OCH KOMPAKT LAYOUT */
633
- @media (max-width: 768px) {
634
- /* Aggressiv scrollning-fix för mobil iframe */
635
- html {
636
- width: 100% !important;
637
- height: 100% !important;
638
- margin: 0 !important;
639
- padding: 0 !important;
640
- overflow: auto !important;
641
- -webkit-overflow-scrolling: touch !important;
642
- -ms-overflow-style: -ms-autohiding-scrollbar !important;
643
- }
644
-
645
- body {
646
- width: 100% !important;
647
- height: 100% !important;
648
- margin: 0 !important;
649
- padding: 0 !important;
650
- overflow-y: auto !important;
651
- overflow-x: hidden !important;
652
- background-color: #ffffff !important;
653
- -webkit-overflow-scrolling: touch !important;
654
- -ms-overflow-style: -ms-autohiding-scrollbar !important;
655
- position: relative !important;
656
- }
657
-
658
- /* Gradio app ska tillåta scrollning */
659
- .gradio-app {
660
- width: 100% !important;
661
- min-height: 100vh !important;
662
- margin: 0 !important;
663
- padding: 0 !important;
664
- overflow: visible !important;
665
- position: relative !important;
666
- }
667
-
668
- /* Container för mobil - naturlig scroll layout */
669
- .gradio-container {
670
- position: static !important; /* Viktigt: static för naturlig scroll */
671
- width: 100% !important;
672
- min-height: 100vh !important;
673
- max-width: none !important;
674
- margin: 0 !important;
675
- padding: 8px !important;
676
- box-shadow: none !important;
677
- border-radius: 0 !important;
678
- background-color: #ffffff !important;
679
- display: block !important; /* Block istället för flex för bättre scroll */
680
- box-sizing: border-box !important;
681
- overflow: visible !important;
682
- }
683
-
684
- /* Minimera header-utrymme */
685
- .gradio-container > .gr-markdown:first-child {
686
- display: block !important;
687
- font-size: 12px !important;
688
- line-height: 1.2 !important;
689
- margin: 0 0 4px 0 !important;
690
- padding: 4px 8px !important;
691
- background-color: #f8f9fa !important;
692
- border-radius: 4px !important;
693
- text-align: center !important;
694
- }
695
-
696
- /* Chat-grupper med naturlig höjd */
697
- .gradio-container > .gr-group {
698
- display: block !important;
699
- margin: 4px 0 !important;
700
- padding: 0 !important;
701
- overflow: visible !important;
702
- }
703
-
704
- /* Chatbot med fast höjd för scroll och mindre text */
705
- #chatbot_conversation {
706
- display: block !important;
707
- height: 300px !important; /* Fast höjd istället för flex */
708
- min-height: 300px !important;
709
- max-height: 300px !important;
710
- margin: 0 0 8px 0 !important;
711
- padding: 6px !important;
712
- overflow-y: auto !important;
713
- overflow-x: hidden !important;
714
- border: 1px solid #e9ecef !important;
715
- border-radius: 4px !important;
716
- background-color: #ffffff !important;
717
- -webkit-overflow-scrolling: touch !important;
718
- -ms-overflow-style: -ms-autohiding-scrollbar !important;
719
- font-size: 13px !important; /* Mindre text för mer innehåll */
720
- line-height: 1.3 !important; /* Kompaktare rader */
721
- }
722
-
723
- /* Specifik styling för meddelanden i chatbot */
724
- #chatbot_conversation .message,
725
- #chatbot_conversation .bot,
726
- #chatbot_conversation .user,
727
- #chatbot_conversation div,
728
- #chatbot_conversation p {
729
- font-size: 13px !important;
730
- line-height: 1.3 !important;
731
- margin: 2px 0 !important;
732
- padding: 3px !important;
733
- }
734
-
735
- /* Användarmeddelanden */
736
- #chatbot_conversation .user {
737
- background-color: #e3f2fd !important;
738
- border-radius: 8px 8px 2px 8px !important;
739
- margin: 2px 0 2px 20px !important;
740
- padding: 6px 8px !important;
741
- font-size: 13px !important;
742
- }
743
-
744
- /* Bot-meddelanden */
745
- #chatbot_conversation .bot {
746
- background-color: #f5f5f5 !important;
747
- border-radius: 8px 8px 8px 2px !important;
748
- margin: 2px 20px 2px 0 !important;
749
- padding: 6px 8px !important;
750
- font-size: 13px !important;
751
- }
752
-
753
- /* Alla text-element i chatbot */
754
- #chatbot_conversation * {
755
- font-size: 13px !important;
756
- line-height: 1.3 !important;
757
- }
758
-
759
- /* Input och knappar */
760
- .gradio-container .gr-row {
761
- display: block !important;
762
- margin: 4px 0 !important;
763
- overflow: visible !important;
764
- }
765
-
766
- /* Textbox för mobil */
767
- .gr-textbox {
768
- margin: 0 0 4px 0 !important;
769
- display: block !important;
770
- }
771
-
772
- .gr-textbox textarea {
773
- font-size: 16px !important;
774
- padding: 8px !important;
775
- border: 1px solid #e9ecef !important;
776
- border-radius: 4px !important;
777
- resize: none !important;
778
- width: 100% !important;
779
- box-sizing: border-box !important;
780
- min-height: 38px !important;
781
- max-height: 76px !important;
782
- overflow-y: auto !important;
783
- -webkit-overflow-scrolling: touch !important;
784
- }
785
-
786
- .gr-textbox textarea:focus {
787
- border-color: #2a9d8f !important;
788
- outline: none !important;
789
- }
790
-
791
- /* Knappar med block layout och bättre touch */
792
- .gr-button {
793
- display: block !important;
794
- width: 100% !important;
795
- padding: 12px 16px !important; /* Större padding för touch */
796
- margin: 6px 0 !important; /* Mer utrymme mellan knappar */
797
- font-size: 16px !important; /* Större text för läsbarhet */
798
- min-height: 48px !important; /* Minst 48px för touch targets */
799
- border-radius: 6px !important;
800
- box-sizing: border-box !important;
801
- white-space: nowrap !important;
802
- overflow: hidden !important;
803
- text-overflow: ellipsis !important;
804
- cursor: pointer !important;
805
- user-select: none !important; /* Förhindra textmarkering */
806
- -webkit-user-select: none !important;
807
- -webkit-tap-highlight-color: rgba(0,0,0,0.1) !important; /* Touch feedback */
808
- touch-action: manipulation !important; /* Förbättra touch responsiveness */
809
- }
810
-
811
- /* Active state för knappar */
812
- .gr-button:active {
813
- background-color: #1a7068 !important;
814
- transform: scale(0.98) !important;
815
- }
816
-
817
- /* Support button specifik styling */
818
- .support-btn {
819
- background-color: #000000 !important;
820
- color: #ffffff !important;
821
- }
822
-
823
- .support-btn:active {
824
- background-color: #333333 !important;
825
- }
826
-
827
- /* Knapp-kolumner */
828
- .gradio-container .gr-column {
829
- display: block !important;
830
- width: 100% !important;
831
- margin: 0 !important;
832
- padding: 2px 0 !important;
833
- }
834
-
835
- /* Support-formulär med scrolling */
836
- .gr-form {
837
- display: block !important;
838
- padding: 8px !important;
839
- margin: 4px 0 !important;
840
- border-radius: 4px !important;
841
- background-color: #f8f9fa !important;
842
- overflow: visible !important;
843
- }
844
-
845
- .gr-form .gr-textbox {
846
- margin: 4px 0 !important;
847
- }
848
-
849
- .gr-form .gr-textbox input,
850
- .gr-form .gr-textbox textarea {
851
- font-size: 16px !important;
852
- padding: 8px !important;
853
- border-radius: 4px !important;
854
- }
855
-
856
- /* Chat preview med scrolling */
857
- .chat-preview {
858
- max-height: 80px !important;
859
- font-size: 11px !important;
860
- padding: 6px !important;
861
- margin: 4px 0 !important;
862
- border-radius: 4px !important;
863
- background-color: #f1f3f4 !important;
864
- overflow-y: auto !important;
865
- -webkit-overflow-scrolling: touch !important;
866
- }
867
-
868
- /* Success message */
869
- .success-message {
870
- font-size: 14px !important;
871
- padding: 8px !important;
872
- text-align: center !important;
873
- margin: 8px 0 !important;
874
- background-color: #d1e7dd !important;
875
- border-radius: 4px !important;
876
- color: #0f5132 !important;
877
- }
878
-
879
- /* Förhindra zoom på iOS */
880
- input, textarea, select {
881
- font-size: 16px !important;
882
- }
883
- }
884
-
885
- /* Extra kompakt för mycket små skärmar */
886
- @media (max-width: 480px) {
887
- .gradio-container {
888
- padding: 4px !important;
889
- }
890
-
891
- .gradio-container > .gr-markdown:first-child {
892
- font-size: 11px !important;
893
- padding: 3px 6px !important;
894
- margin: 0 0 3px 0 !important;
895
- }
896
-
897
- #chatbot_conversation {
898
- padding: 4px !important;
899
- min-height: 250px !important;
900
- }
901
-
902
- .gr-button {
903
- padding: 8px 10px !important;
904
- font-size: 13px !important;
905
- min-height: 36px !important;
906
- }
907
-
908
- .gr-textbox textarea {
909
- padding: 6px !important;
910
- min-height: 32px !important;
911
- }
912
- }
913
  """
914
 
915
- with gr.Blocks(
916
- css=custom_css,
917
- title="ChargeNode Kundtjänst",
918
- head='<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">',
919
- js="""
920
- function initMobileEventFix() {
921
- // Framtvinga scrollning på mobil i iframe
922
- if (/iPhone|iPad|iPod|Android/i.test(navigator.userAgent)) {
923
- document.body.style.overflow = 'auto';
924
- document.documentElement.style.overflow = 'auto';
925
- document.body.style.webkitOverflowScrolling = 'touch';
926
-
927
- // Fix för touch events på knappar
928
- const buttons = document.querySelectorAll('.gr-button, button');
929
- buttons.forEach(button => {
930
- // Lägg till touch events för bättre responsiveness
931
- button.addEventListener('touchstart', function(e) {
932
- this.style.backgroundColor = '#1a7068';
933
- }, {passive: true});
934
-
935
- button.addEventListener('touchend', function(e) {
936
- this.style.backgroundColor = '';
937
- // Forcera click event
938
- setTimeout(() => {
939
- this.click();
940
- }, 50);
941
- }, {passive: true});
942
-
943
- // Förhindra dubbel-hantering
944
- button.addEventListener('click', function(e) {
945
- if (e.isTrusted === false) return; // Skippa syntetiska events
946
- e.stopPropagation();
947
- }, true);
948
- });
949
-
950
- // Fix för Enter-key på mobil keyboard
951
- const textareas = document.querySelectorAll('textarea');
952
- textareas.forEach(textarea => {
953
- textarea.addEventListener('keydown', function(e) {
954
- if (e.key === 'Enter' && !e.shiftKey) {
955
- e.preventDefault();
956
- // Hitta submit-knappen och trigga den
957
- const submitBtn = document.querySelector('button[type="submit"], .gr-button');
958
- if (submitBtn) {
959
- submitBtn.click();
960
- }
961
- }
962
- });
963
-
964
- // Lägg till input event för bättre reaktivitet
965
- textarea.addEventListener('input', function(e) {
966
- // Trigga Gradio's interna events
967
- const event = new Event('input', { bubbles: true });
968
- this.dispatchEvent(event);
969
- });
970
- });
971
-
972
- // Observer för dynamiskt tillagda element
973
- const observer = new MutationObserver(function(mutations) {
974
- mutations.forEach(function(mutation) {
975
- if (mutation.type === 'childList') {
976
- mutation.addedNodes.forEach(function(node) {
977
- if (node.nodeType === 1) { // Element node
978
- // Lägg till event listeners på nya knappar
979
- const newButtons = node.querySelectorAll('.gr-button, button');
980
- newButtons.forEach(button => {
981
- button.addEventListener('touchstart', function(e) {
982
- this.style.backgroundColor = '#1a7068';
983
- }, {passive: true});
984
-
985
- button.addEventListener('touchend', function(e) {
986
- this.style.backgroundColor = '';
987
- setTimeout(() => {
988
- this.click();
989
- }, 50);
990
- }, {passive: true});
991
- });
992
-
993
- // Lägg till event listeners på nya textareas
994
- const newTextareas = node.querySelectorAll('textarea');
995
- newTextareas.forEach(textarea => {
996
- textarea.addEventListener('keydown', function(e) {
997
- if (e.key === 'Enter' && !e.shiftKey) {
998
- e.preventDefault();
999
- const submitBtn = document.querySelector('button[type="submit"], .gr-button');
1000
- if (submitBtn) {
1001
- submitBtn.click();
1002
- }
1003
- }
1004
- });
1005
- });
1006
- }
1007
- });
1008
- }
1009
- });
1010
- });
1011
-
1012
- // Starta observer
1013
- observer.observe(document.body, {
1014
- childList: true,
1015
- subtree: true
1016
- });
1017
-
1018
- // Skicka meddelande till parent om scrollning
1019
- window.addEventListener('scroll', function() {
1020
- if (window.parent !== window) {
1021
- window.parent.postMessage({
1022
- type: 'gradio-scroll',
1023
- scrollTop: document.documentElement.scrollTop
1024
- }, '*');
1025
- }
1026
- });
1027
-
1028
- // Lyssna på meddelanden från parent
1029
- window.addEventListener('message', function(event) {
1030
- if (event.data.type === 'scrollToTop') {
1031
- document.documentElement.scrollTop = 0;
1032
- document.body.scrollTop = 0;
1033
- }
1034
- });
1035
-
1036
- console.log('Mobile event fixes initialized');
1037
- }
1038
- }
1039
-
1040
- // Kör när sidan laddas och när DOM uppdateras
1041
- if (document.readyState === 'loading') {
1042
- document.addEventListener('DOMContentLoaded', initMobileEventFix);
1043
- } else {
1044
- initMobileEventFix();
1045
- }
1046
-
1047
- // Kör även efter en kort delay för att fånga Gradio-element
1048
- setTimeout(initMobileEventFix, 1000);
1049
- setTimeout(initMobileEventFix, 3000);
1050
- """
1051
- ) as app:
1052
  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")
1053
 
1054
  # Chat interface
@@ -1280,13 +1000,4 @@ initialize_embeddings()
1280
  print("Embedding-modell och index redo!")
1281
 
1282
  if __name__ == "__main__":
1283
- app.launch(
1284
- share=IS_HUGGINGFACE,
1285
- server_name="0.0.0.0" if IS_HUGGINGFACE else "127.0.0.1",
1286
- show_error=True,
1287
- show_api=False,
1288
- height=600, # Fast höjd för iframe
1289
- inbrowser=False, # Förhindra automatisk öppning
1290
- favicon_path=None,
1291
- app_kwargs={"docs_url": None, "redoc_url": None} # Rensa onödiga endpoints
1292
- )
 
15
  from sentence_transformers import SentenceTransformer
16
  import numpy as np
17
  import faiss
18
+ import re
19
 
20
  # --- Konfiguration ---
21
  CHARGENODE_URL = "https://www.chargenode.eu"
22
+ MAX_CHUNK_SIZE = 2000 # Ökad chunkstorleken för att bättre hantera FAQ-svar
23
+ CHUNK_OVERLAP = 200 # Nytt: Overlapping chunks för att inte tappa kontext
24
+ RETRIEVAL_K = 5 # Antal chunker att hämta vid varje sökning
25
 
26
  # Kontrollera om vi kör i Hugging Face-miljön
27
  IS_HUGGINGFACE = os.environ.get("SPACE_ID") is not None
 
71
  index = None
72
  chunks = []
73
  chunk_sources = []
74
+ faq_dict = {} # Ny: Dictionary för direktmatchning av vanliga frågor
75
 
76
  # --- Förbättrad loggfunktion ---
77
  def safe_append_to_log(log_entry):
 
166
  print(f"Fel vid inläsning av prompt.txt: {e}, använder standardprompt")
167
  return "Du är ChargeNode's AI-assistent. Svara på frågor om ChargeNode's produkter och tjänster baserat på den tillhandahållna informationen."
168
 
169
+ # --- Förbättrad chunking ---
170
  def prepare_chunks(text_data):
171
+ """Delar upp texten i mindre segment för embedding och sökning med särskild hänsyn till FAQ-format."""
172
  chunks, sources = [], []
173
+ global faq_dict
174
+
175
  for source, text in text_data.items():
176
+ # Split text into paragraph-sized chunks
177
  paragraphs = [p for p in text.split("\n") if p.strip()]
178
+
179
+ # Process FAQ-specific content better
180
+ i = 0
181
+ while i < len(paragraphs):
182
+ # Start a new chunk
183
+ current_chunk = ""
184
+ start_idx = i
185
+
186
+ # Check for FAQ format
187
+ if i < len(paragraphs) and paragraphs[i].startswith("Fråga:"):
188
+ question = paragraphs[i][7:].strip() # Extract the question text
189
+ current_chunk = paragraphs[i]
190
+ i += 1
191
+
192
+ # Add content until we reach the next question or MAX_CHUNK_SIZE
193
+ while i < len(paragraphs) and not paragraphs[i].startswith("Fråga:"):
194
+ # Add this paragraph if it doesn't exceed chunk size
195
+ if len(current_chunk) + len(paragraphs[i]) + 1 <= MAX_CHUNK_SIZE:
196
+ current_chunk += "\n" + paragraphs[i]
197
+ else:
198
+ # If we're already processing a FAQ answer, don't break mid-answer
199
+ if "Svar:" in current_chunk:
200
+ # We prefer to keep whole answers together, so let's break only if answer is too long
201
+ if len(current_chunk) > MAX_CHUNK_SIZE * 1.5: # Allow some overflow
202
+ break
203
+ else:
204
+ current_chunk += "\n" + paragraphs[i]
205
+ else:
206
+ break
207
+ i += 1
208
+
209
+ # Store FAQ pairs in the dictionary for direct lookup
210
+ if "Svar:" in current_chunk:
211
+ answer_start = current_chunk.find("Svar:")
212
+ answer_text = current_chunk[answer_start + 5:].strip()
213
+
214
+ # Add variations with common synonyms for payment-related questions
215
+ if any(term in question.lower() for term in ["betalsätt", "betalmetod", "betalmedel", "kort",
216
+ "betalkort", "betalning", "betala"]):
217
+ payment_variations = [
218
+ "hur ändrar jag betalmedel",
219
+ "hur byter jag betalsätt",
220
+ "hur uppdaterar jag mitt betalkort",
221
+ "hur ändrar jag betalmetod",
222
+ "hur byter jag betalningsmetod",
223
+ "hur ändrar jag betalkort"
224
+ ]
225
+ for variation in payment_variations:
226
+ faq_dict[variation] = answer_text
227
+
228
+ # Add the original question to the dictionary
229
+ faq_dict[question.lower()] = answer_text
230
  else:
231
+ # Handle non-FAQ text using sliding window
232
+ while i < len(paragraphs) and len(current_chunk) + len(paragraphs[i]) + 1 <= MAX_CHUNK_SIZE:
233
+ if current_chunk:
234
+ current_chunk += " " + paragraphs[i]
235
+ else:
236
+ current_chunk = paragraphs[i]
237
+ i += 1
238
+
239
+ # Save the chunk if it has content
240
+ if current_chunk.strip():
241
+ chunks.append(current_chunk.strip())
242
+ sources.append(source)
243
+
244
+ # If we've added a chunk but haven't advanced, we need to move forward
245
+ if i == start_idx:
246
+ i += 1
247
+
248
+ # Create overlapping chunks for better context preservation
249
+ overlap_chunks = []
250
+ overlap_sources = []
251
+
252
+ for j in range(0, len(chunks)):
253
+ overlap_chunks.append(chunks[j])
254
+ overlap_sources.append(sources[j])
255
+
256
+ # Create an overlapping chunk with the next chunk if it exists
257
+ if j < len(chunks) - 1 and chunks[j].endswith(chunks[j+1][:CHUNK_OVERLAP]):
258
+ # Skip if there's already significant overlap
259
+ continue
260
+
261
+ if j < len(chunks) - 1:
262
+ # Calculate available space in the current chunk
263
+ space_left = MAX_CHUNK_SIZE - len(chunks[j])
264
+
265
+ # If there's enough space, add part of the next chunk
266
+ if space_left >= CHUNK_OVERLAP:
267
+ overlap_text = chunks[j] + " " + chunks[j+1][:CHUNK_OVERLAP]
268
+ overlap_chunks.append(overlap_text)
269
+ overlap_sources.append(sources[j])
270
+
271
+ chunks = overlap_chunks
272
+ sources = overlap_sources
273
+
274
+ print(f"Genererade {len(chunks)} chunks med {len(faq_dict)} FAQ-par")
275
  return chunks, sources
276
 
277
  def initialize_embeddings():
278
  """Initierar SentenceTransformer och FAISS-index vid första anrop."""
279
+ global embedder, embeddings, index, chunks, chunk_sources, faq_dict
280
 
281
  if embedder is None:
282
  print("Initierar SentenceTransformer och FAISS-index...")
 
294
  index = faiss.IndexFlatIP(embeddings.shape[1])
295
  index.add(embeddings)
296
  print("FAISS-index klart")
297
+
298
+ # Print FAQ dictionary keys for debugging
299
+ print(f"FAQ Dictionary innehåller {len(faq_dict)} nycklar")
300
+ if len(faq_dict) > 0:
301
+ payment_keys = [k for k in faq_dict.keys() if any(term in k for term in ["betalsätt", "betalmetod", "betalmedel"])]
302
+ print(f"Betalningsrelaterade FAQ-nycklar: {payment_keys[:5]}")
303
+
304
+ # Direkt matchningsfunktion för vanliga frågor
305
+ def check_direct_match(query):
306
+ """Kontrollerar om frågan matchar någon av våra fördefinierade FAQ-svar."""
307
+ query_lower = query.lower().strip('?').strip()
308
+
309
+ # Explicit check for payment method question
310
+ if any(query_lower.startswith(prefix) for prefix in ["hur ändrar jag", "hur byter jag", "hur uppdaterar jag"]) and \
311
+ any(term in query_lower for term in ["betalsätt", "betalmetod", "betalmedel", "betalkort", "kort"]):
312
+ payment_answer = """Så här gör du om du vill byta betalkort:
313
+ 1. Gå in i appen.
314
+ 2. Tryck på meny och mina betalsätt
315
+ 3. Tryck på ersätt kort.
316
+ 4. Godkänn våra villkor
317
+ 5. Tryck på kortbetalning under "bekräfta för auktorisering"
318
+ 6. Lägg in dina nya kort uppgifter
319
+ 7. Bekräfta med BankID.
320
+
321
+ OBS! Se till att kortet har pengar och att det är upplåst för internetbetalningar."""
322
+ return payment_answer
323
+
324
+ # Check if query directly matches a FAQ
325
+ if query_lower in faq_dict:
326
+ return faq_dict[query_lower]
327
+
328
+ # Check for close matches using pattern matching
329
+ for key, value in faq_dict.items():
330
+ # Find questions about changing things with synonyms
331
+ if ("ändra" in query_lower or "byta" in query_lower or "uppdatera" in query_lower) and \
332
+ ("ändra" in key or "byta" in key or "uppdatera" in key):
333
+ # Check if key and query share important terms
334
+ query_terms = set(query_lower.split())
335
+ key_terms = set(key.split())
336
+ if len(query_terms.intersection(key_terms)) >= 2: # At least 2 words in common
337
+ return value
338
+
339
+ return None
340
 
341
  def retrieve_context(query, k=RETRIEVAL_K):
342
+ """Hämtar relevant kontext för frågor med direkt matchning för vanliga frågor."""
343
  # Säkerställ att modeller är laddade
344
  initialize_embeddings()
345
 
346
+ # Först, kolla efter direktmatchningar för vanliga frågor
347
+ direct_match = check_direct_match(query)
348
+ if direct_match:
349
+ print(f"Direkt matchning hittad för frågan: {query}")
350
+ return f"Fråga: {query}\nSvar: {direct_match}", ["direct_match"]
351
+
352
+ # Om ingen direktmatchning, använd vanlig embedding-sökning
353
  query_embedding = embedder.encode([query], convert_to_numpy=True)
354
  query_embedding /= np.linalg.norm(query_embedding)
355
  D, I = index.search(query_embedding, k)
 
385
  try:
386
  # Använd Claude Haiku med RAG-baserad kontext
387
  response = anthropic_client.messages.create(
388
+ model="claude-3-7-sonnet-20250219",
389
  max_tokens=500,
390
  temperature=0.3,
391
  system=system_prompt,
 
746
  # --- Gradio UI ---
747
  initial_chat = [{"role": "assistant", "content": "Detta är ChargeNode's AI bot. Hur kan jag hjälpa dig idag?"}]
748
 
 
749
  custom_css = """
 
750
  body {background-color: #f7f7f7; font-family: Arial, sans-serif; margin: 0; padding: 0;}
751
  h1 {font-family: Helvetica, sans-serif; color: #2a9d8f; text-align: center; margin-bottom: 0.5em;}
752
  .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;}
 
759
  .gr-form {padding: 10px; border: 1px solid #eee; border-radius: 4px; margin-bottom: 10px;}
760
  .chat-preview {max-height: 150px; overflow-y: auto; border: 1px solid #eee; padding: 8px; margin-top: 10px; font-size: 12px; background-color: #f9f9f9;}
761
  .success-message {font-size: 16px; font-weight: normal; margin-bottom: 15px;}
 
762
  /* Dölj Gradio-footer */
763
  footer {display: none !important;}
764
  .footer {display: none !important;}
 
766
  .gradio-footer {display: none !important;}
767
  .gradio-container .footer {display: none !important;}
768
  .gradio-container .gr-footer {display: none !important;}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
769
  """
770
 
771
+ with gr.Blocks(css=custom_css, title="ChargeNode Kundtjänst") as app:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
772
  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")
773
 
774
  # Chat interface
 
1000
  print("Embedding-modell och index redo!")
1001
 
1002
  if __name__ == "__main__":
1003
+ app.launch(share=True)