feat: add session titles and migration
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from collections.abc import Iterator
|
||||
from dataclasses import dataclass
|
||||
|
||||
@@ -17,6 +18,39 @@ class SessionProviderMismatchError(RuntimeError):
|
||||
"""The active provider cannot safely continue the stored session."""
|
||||
|
||||
|
||||
_SENSITIVE_TITLE_PATTERN = re.compile(
|
||||
r"(?ix)"
|
||||
r"\b(?:api[-_ ]?key|passwort|password|token|secret|iban|kreditkarte|"
|
||||
r"private[-_ ]?key|ssh[-_ ]?key)\b|"
|
||||
r"-----BEGIN [A-Z ]+PRIVATE KEY-----|"
|
||||
r"\b[A-Z]{2}\d{2}(?:[ ]?[A-Z0-9]){11,30}\b|"
|
||||
r"\b[\w.+-]+@[\w.-]+\.[A-Z]{2,}\b"
|
||||
)
|
||||
_MEDICAL_TITLE_PATTERN = re.compile(
|
||||
r"(?ix)\b(?:arzt|ärzt|blut(?:e|en|ung)?|diagnos|gesundheit|krank|"
|
||||
r"medikament|notfall|schmerz|symptom|therap|verletz|wunde)\w*\b"
|
||||
)
|
||||
|
||||
|
||||
def derive_session_title(text: str, *, limit: int = 60) -> str:
|
||||
"""Create a short local title without retaining obvious sensitive values."""
|
||||
|
||||
normalized = " ".join(text.split())
|
||||
if _SENSITIVE_TITLE_PATTERN.search(normalized):
|
||||
return "Sensible Anfrage"
|
||||
if _MEDICAL_TITLE_PATTERN.search(normalized):
|
||||
return "Gesundheitsfrage"
|
||||
cleaned = re.sub(r"[`*_#~]+", "", normalized).strip(" -:;,.!?")
|
||||
if not cleaned:
|
||||
return "Neue Unterhaltung"
|
||||
if len(cleaned) <= limit:
|
||||
return cleaned
|
||||
shortened = cleaned[: limit - 1].rsplit(" ", 1)[0].rstrip(" -:;,.!?")
|
||||
if not shortened:
|
||||
shortened = cleaned[: limit - 1].rstrip()
|
||||
return f"{shortened}…"
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class LoadedSession:
|
||||
session: ChatSession
|
||||
@@ -61,7 +95,13 @@ class ChatService:
|
||||
loaded = self.load_session(session_id)
|
||||
messages = [*loaded.messages, ChatMessage("user", normalized)]
|
||||
response = self.provider.chat(messages)
|
||||
self.store.append_exchange(session_id, normalized, response)
|
||||
self.store.append_exchange(
|
||||
session_id,
|
||||
normalized,
|
||||
response,
|
||||
title_if_first=derive_session_title(normalized),
|
||||
last_provider=self._last_provider_name(),
|
||||
)
|
||||
return response
|
||||
|
||||
def stream_send(self, session_id: str, text: str) -> Iterator[str]:
|
||||
@@ -100,7 +140,18 @@ class ChatService:
|
||||
raise InvalidProviderResponseError(
|
||||
"Der Provider lieferte keine verwendbare Streaming-Antwort."
|
||||
)
|
||||
self.store.append_exchange(session_id, normalized, response)
|
||||
self.store.append_exchange(
|
||||
session_id,
|
||||
normalized,
|
||||
response,
|
||||
title_if_first=derive_session_title(normalized),
|
||||
last_provider=self._last_provider_name(),
|
||||
)
|
||||
|
||||
def clear_session(self, session_id: str) -> None:
|
||||
self.store.clear_messages(session_id)
|
||||
|
||||
def _last_provider_name(self) -> str:
|
||||
route = getattr(self.provider, "last_route", None)
|
||||
provider_name = getattr(route, "provider", None)
|
||||
return provider_name if isinstance(provider_name, str) else self.provider.name
|
||||
|
||||
Reference in New Issue
Block a user