@@ -63,7 +63,8 @@ def _save_chat_log() -> None:
6363 CHAT_LOG_FILE .parent .mkdir (parents = True , exist_ok = True )
6464 persistent = [m for m in _chat_log if m .get ("role" ) != "thinking" ]
6565 CHAT_LOG_FILE .write_text (
66- json .dumps (persistent , ensure_ascii = False , indent = 2 )
66+ json .dumps (persistent , ensure_ascii = False , indent = 2 ),
67+ encoding = "utf-8" ,
6768 )
6869 except Exception as e :
6970 logger .warning ("Failed to save chat log: %s" , e )
@@ -74,7 +75,7 @@ def _load_chat_log() -> None:
7475 if not CHAT_LOG_FILE .exists ():
7576 return
7677 try :
77- data = json .loads (CHAT_LOG_FILE .read_text ())
78+ data = json .loads (CHAT_LOG_FILE .read_text (encoding = "utf-8" ))
7879 if isinstance (data , list ):
7980 _chat_log .extend (data [- 200 :])
8081 logger .info ("Restored %d chat messages from disk" , len (_chat_log ))
@@ -87,7 +88,8 @@ def _save_activity_log() -> None:
8788 try :
8889 ACTIVITY_LOG_FILE .parent .mkdir (parents = True , exist_ok = True )
8990 ACTIVITY_LOG_FILE .write_text (
90- json .dumps (list (_activity_log ), ensure_ascii = False , indent = 2 )
91+ json .dumps (list (_activity_log ), ensure_ascii = False , indent = 2 ),
92+ encoding = "utf-8" ,
9193 )
9294 except Exception as e :
9395 logger .warning ("Failed to save activity log: %s" , e )
@@ -98,7 +100,7 @@ def _load_activity_log() -> None:
98100 if not ACTIVITY_LOG_FILE .exists ():
99101 return
100102 try :
101- data = json .loads (ACTIVITY_LOG_FILE .read_text ())
103+ data = json .loads (ACTIVITY_LOG_FILE .read_text (encoding = "utf-8" ))
102104 if isinstance (data , list ):
103105 _activity_log .extend (data [- 200 :])
104106 logger .info ("Restored %d activity events from disk" , len (_activity_log ))
@@ -134,6 +136,32 @@ def _chat_reply(message: str, **extra: Any) -> dict:
134136 return entry
135137
136138
139+ def _build_status_summary (snapshots : dict [str , PortalSnapshot ]) -> str :
140+ """Build a deterministic status summary from persisted snapshots."""
141+ if not snapshots :
142+ return "Es liegen noch keine Scan-Daten vor. Starte einen Scan mit **scan all**."
143+
144+ portal_names = sorted (snapshots .keys ())
145+ with_dates = [name for name in portal_names if snapshots [name ].datum_options ]
146+ without_dates = [name for name in portal_names if not snapshots [name ].datum_options ]
147+ with_errors = [name for name in portal_names if snapshots [name ].error ]
148+
149+ lines = [
150+ f"**{ len (with_dates )} von { len (portal_names )} Zelten** haben aktuell Termine verfΓΌgbar: "
151+ f"{ ', ' .join (with_dates )} ."
152+ ]
153+ if without_dates :
154+ lines .append (
155+ f"**{ len (without_dates )} Zelte** sind geschlossen oder haben keine Termine: "
156+ f"{ ', ' .join (without_dates )} ."
157+ )
158+ if with_errors :
159+ lines .append (
160+ f"Hinweis: Bei folgenden Zelten gab es zuletzt Scan-Fehler: { ', ' .join (with_errors )} ."
161+ )
162+ return "\n \n " .join (lines )
163+
164+
137165async def _scan_portals (portals : list , config : WiesnConfig ) -> list [dict ]:
138166 """Shared scan logic used by background scanner, chat scan, and API trigger.
139167
@@ -568,6 +596,7 @@ def _classify_intent(text: str) -> str:
568596@app .post ("/api/chat" )
569597async def post_chat (body : ChatMessage ):
570598 """Handle a user chat message β LLM agent with keyword fallback."""
599+ global _thinking_status
571600 text = body .message .strip ()
572601 if not text :
573602 raise HTTPException (400 , "Empty message" )
@@ -581,6 +610,14 @@ async def post_chat(body: ChatMessage):
581610 _chat_log .append (user_entry )
582611 _save_chat_log ()
583612
613+ intent = _classify_intent (text )
614+
615+ # Status answers are deterministic and history-independent by design.
616+ if intent == "status" :
617+ snapshots = load_snapshots ()
618+ reply = _chat_reply (_build_status_summary (snapshots ))
619+ return {"user" : user_entry , "reply" : reply }
620+
584621 # ββ Try LLM agent first βββββββββββββββββββββββ
585622 try :
586623 from wiesn_agent .chat_agent import chat as llm_chat
@@ -694,9 +731,6 @@ def _on_tool_progress(tool_name: str, tool_args: dict) -> None:
694731 reply = _chat_reply (f"**{ mentioned_portal } ** wurde noch nicht gescannt." )
695732 return {"user" : user_entry , "reply" : reply }
696733
697- # Fall back to keyword classification for generic intents
698- intent = _classify_intent (text )
699-
700734 # ββ Intent: Scan ββββββββββββββββββββββββββββββ
701735 if intent == "scan" :
702736 config = _load_config ()
@@ -712,27 +746,6 @@ def _on_tool_progress(tool_name: str, tool_args: dict) -> None:
712746
713747 return {"user" : user_entry , "reply" : reply }
714748
715- # ββ Intent: Status / Summary ββββββββββββββββββ
716- if intent == "status" :
717- snapshots = load_snapshots ()
718- config = _load_config ()
719- with_dates = [n for n , s in snapshots .items () if s .datum_options ]
720- matching = [
721- n for n , s in snapshots .items ()
722- if any (matches_wunsch (d .get ("text" , d .get ("value" , "" )), config ) for d in s .datum_options )
723- ]
724-
725- lines = [
726- f"**{ len (snapshots )} ** portals scanned, **{ len (with_dates )} ** have open dates." ,
727- ]
728- if matching :
729- lines .append (f"**{ len (matching )} ** match your preferred days: { ', ' .join (matching )} " )
730- else :
731- lines .append ("No matches on your preferred days yet." )
732-
733- reply = _chat_reply ("\n " .join (lines ))
734- return {"user" : user_entry , "reply" : reply }
735-
736749 # ββ Intent: Show matches / evening ββββββββββββ
737750 if intent == "matches" :
738751 snapshots = load_snapshots ()
0 commit comments