import json from datetime import datetime from sqlalchemy.orm import Session from app.db import AuditLogORM, TelegramUserProfileORM from app.models import UserProfileRecord SKIP_TOKENS = {"pas", "gec", "geç", "skip", "-"} ONBOARDING_QUESTIONS: list[dict[str, str]] = [ {"field": "display_name", "prompt": "1/12 Sana nasıl hitap etmeliyim?"}, {"field": "bio", "prompt": "2/12 Kısaca kendini nasıl tanıtırsın?"}, {"field": "occupation", "prompt": "3/12 En çok hangi işle uğraşıyorsun?"}, {"field": "primary_use_cases", "prompt": "4/12 WiseClaw'ı en çok hangi işler için kullanacaksın? Virgülle ayırabilirsin."}, {"field": "answer_priorities", "prompt": "5/12 Cevaplarımda en çok neye önem veriyorsun? Örnek: hız, detay, yaratıcılık, teknik doğruluk."}, {"field": "tone_preference", "prompt": "6/12 Nasıl bir tonda konuşayım?"}, {"field": "response_length", "prompt": "7/12 Cevaplar kısa mı, orta mı, detaylı mı olsun?"}, {"field": "language_preference", "prompt": "8/12 Hangi dilde konuşalım?"}, {"field": "workflow_preference", "prompt": "9/12 İşlerde önce plan mı istersin, yoksa direkt aksiyon mu?"}, {"field": "interests", "prompt": "10/12 Özellikle ilgilendiğin konular veya hobilerin neler? Virgülle ayırabilirsin."}, {"field": "approval_preferences", "prompt": "11/12 Onay almadan yapmamamı istediğin şeyler neler? Virgülle ayırabilirsin."}, {"field": "avoid_preferences", "prompt": "12/12 Özellikle kaçınmamı istediğin bir üslup veya davranış var mı?"}, ] class UserProfileService: def __init__(self, session: Session) -> None: self.session = session def get_profile(self, telegram_user_id: int) -> UserProfileRecord | None: record = self.session.get(TelegramUserProfileORM, telegram_user_id) if record is None: return None return self._to_record(record) def start_onboarding(self, telegram_user_id: int) -> str: record = self._get_or_create_profile(telegram_user_id) record.onboarding_completed = False record.last_onboarding_step = 0 record.updated_at = datetime.utcnow() self.session.add( AuditLogORM(category="profile", message=f"profile:onboarding-started:{telegram_user_id}") ) self.session.flush() intro = ( "Ben WiseClaw. Seni daha iyi tanimak ve cevaplarimi sana gore ayarlamak icin 12 kisa soru soracagim.\n" "Istersen herhangi bir soruya `pas` diyerek gecebilirsin.\n\n" ) return intro + ONBOARDING_QUESTIONS[0]["prompt"] def reset_onboarding(self, telegram_user_id: int) -> str: record = self._get_or_create_profile(telegram_user_id) record.display_name = None record.bio = None record.occupation = None record.primary_use_cases = "[]" record.answer_priorities = "[]" record.tone_preference = None record.response_length = None record.language_preference = None record.workflow_preference = None record.interests = "[]" record.approval_preferences = "[]" record.avoid_preferences = None record.onboarding_completed = False record.last_onboarding_step = 0 record.updated_at = datetime.utcnow() self.session.add( AuditLogORM(category="profile", message=f"profile:onboarding-reset:{telegram_user_id}") ) self.session.flush() return "Profil sifirlandi. /tanisalim yazarak tekrar baslayabiliriz." def is_onboarding_active(self, telegram_user_id: int) -> bool: record = self.session.get(TelegramUserProfileORM, telegram_user_id) if record is None: return False return not record.onboarding_completed and record.last_onboarding_step < len(ONBOARDING_QUESTIONS) def answer_onboarding(self, telegram_user_id: int, text: str) -> tuple[str, bool]: record = self._get_or_create_profile(telegram_user_id) step = min(record.last_onboarding_step, len(ONBOARDING_QUESTIONS) - 1) question = ONBOARDING_QUESTIONS[step] self._apply_answer(record, question["field"], text) record.last_onboarding_step = step + 1 record.updated_at = datetime.utcnow() if record.last_onboarding_step >= len(ONBOARDING_QUESTIONS): record.onboarding_completed = True self.session.add( AuditLogORM(category="profile", message=f"profile:onboarding-completed:{telegram_user_id}") ) self.session.flush() return self.render_completion_message(record), True self.session.add( AuditLogORM( category="profile", message=f"profile:onboarding-step:{telegram_user_id}:{record.last_onboarding_step}", ) ) self.session.flush() return ONBOARDING_QUESTIONS[record.last_onboarding_step]["prompt"], False def render_profile_summary(self, telegram_user_id: int) -> str: record = self.session.get(TelegramUserProfileORM, telegram_user_id) if record is None: return "Henuz bir profilin yok. /tanisalim yazarak baslayabiliriz." profile = self._to_record(record) lines = [ "Profil ozetin:", f"- Hitap: {profile.display_name or 'belirtilmedi'}", f"- Kisa tanitim: {profile.bio or 'belirtilmedi'}", f"- Ugras alani: {profile.occupation or 'belirtilmedi'}", f"- Kullanim amaci: {', '.join(profile.primary_use_cases) if profile.primary_use_cases else 'belirtilmedi'}", f"- Oncelikler: {', '.join(profile.answer_priorities) if profile.answer_priorities else 'belirtilmedi'}", f"- Ton: {profile.tone_preference or 'belirtilmedi'}", f"- Cevap uzunlugu: {profile.response_length or 'belirtilmedi'}", f"- Dil: {profile.language_preference or 'belirtilmedi'}", f"- Calisma bicimi: {profile.workflow_preference or 'belirtilmedi'}", f"- Ilgi alanlari: {', '.join(profile.interests) if profile.interests else 'belirtilmedi'}", f"- Onay beklentileri: {', '.join(profile.approval_preferences) if profile.approval_preferences else 'belirtilmedi'}", f"- Kacinmami istedigin seyler: {profile.avoid_preferences or 'belirtilmedi'}", ] if not profile.onboarding_completed: lines.append( f"- Durum: onboarding devam ediyor, sira {profile.last_onboarding_step + 1}/{len(ONBOARDING_QUESTIONS)}" ) return "\n".join(lines) def render_preferences_summary(self, telegram_user_id: int) -> str: record = self.session.get(TelegramUserProfileORM, telegram_user_id) if record is None: return "Henuz tercihlerin kayitli degil. /tanisalim ile baslayabiliriz." profile = self._to_record(record) return "\n".join( [ "Tercihlerin:", f"- Ton: {profile.tone_preference or 'belirtilmedi'}", f"- Cevap uzunlugu: {profile.response_length or 'belirtilmedi'}", f"- Dil: {profile.language_preference or 'belirtilmedi'}", f"- Calisma bicimi: {profile.workflow_preference or 'belirtilmedi'}", f"- Oncelikler: {', '.join(profile.answer_priorities) if profile.answer_priorities else 'belirtilmedi'}", f"- Onay beklentileri: {', '.join(profile.approval_preferences) if profile.approval_preferences else 'belirtilmedi'}", f"- Kacinmami istedigin seyler: {profile.avoid_preferences or 'belirtilmedi'}", ] ) def build_prompt_profile(self, telegram_user_id: int) -> str: record = self.session.get(TelegramUserProfileORM, telegram_user_id) if record is None: return "" profile = self._to_record(record) instructions: list[str] = [] if profile.display_name: instructions.append(f"Kullaniciya `{profile.display_name}` diye hitap edebilirsin.") if profile.language_preference: instructions.append(f"Varsayilan dili `{profile.language_preference}` olarak kullan.") if profile.tone_preference: instructions.append(f"Cevap tonunu su tercihe uydur: {profile.tone_preference}.") if profile.response_length: instructions.append(f"Varsayilan cevap uzunlugu tercihi: {profile.response_length}.") if profile.workflow_preference: instructions.append(f"Is yapis tarzinda su tercihe uy: {profile.workflow_preference}.") if profile.answer_priorities: instructions.append( "Kullanici su niteliklere oncelik veriyor: " + ", ".join(profile.answer_priorities) + "." ) if profile.primary_use_cases: instructions.append( "WiseClaw'i en cok su isler icin kullaniyor: " + ", ".join(profile.primary_use_cases) + "." ) if profile.interests: instructions.append( "Gerekirse ornekleri su ilgi alanlarina yaklastir: " + ", ".join(profile.interests) + "." ) if profile.approval_preferences: instructions.append( "Su konularda once onay bekle: " + ", ".join(profile.approval_preferences) + "." ) if profile.avoid_preferences: instructions.append(f"Su uslup veya davranislardan kacin: {profile.avoid_preferences}.") return "\n".join(f"- {item}" for item in instructions) def profile_memory_summary(self, telegram_user_id: int) -> str: record = self.session.get(TelegramUserProfileORM, telegram_user_id) if record is None: return "" profile = self._to_record(record) parts = [] if profile.display_name: parts.append(f"hitap={profile.display_name}") if profile.language_preference: parts.append(f"dil={profile.language_preference}") if profile.tone_preference: parts.append(f"ton={profile.tone_preference}") if profile.response_length: parts.append(f"uzunluk={profile.response_length}") if profile.workflow_preference: parts.append(f"calisma={profile.workflow_preference}") if profile.primary_use_cases: parts.append("amac=" + ",".join(profile.primary_use_cases[:3])) return "profile_summary:" + "; ".join(parts) def _get_or_create_profile(self, telegram_user_id: int) -> TelegramUserProfileORM: record = self.session.get(TelegramUserProfileORM, telegram_user_id) if record is None: record = TelegramUserProfileORM( telegram_user_id=telegram_user_id, primary_use_cases="[]", answer_priorities="[]", interests="[]", approval_preferences="[]", onboarding_completed=False, last_onboarding_step=0, created_at=datetime.utcnow(), updated_at=datetime.utcnow(), ) self.session.add(record) self.session.flush() return record def _apply_answer(self, record: TelegramUserProfileORM, field: str, answer: str) -> None: cleaned = answer.strip() if cleaned.lower() in SKIP_TOKENS: return if field in {"primary_use_cases", "answer_priorities", "interests", "approval_preferences"}: setattr(record, field, json.dumps(self._split_list(cleaned), ensure_ascii=False)) return setattr(record, field, cleaned) def _split_list(self, value: str) -> list[str]: parts = [item.strip() for item in value.replace("\n", ",").split(",")] return [item for item in parts if item] def _decode_list(self, value: str) -> list[str]: try: payload = json.loads(value) except json.JSONDecodeError: return [] if not isinstance(payload, list): return [] return [str(item).strip() for item in payload if str(item).strip()] def _to_record(self, record: TelegramUserProfileORM) -> UserProfileRecord: return UserProfileRecord( telegram_user_id=record.telegram_user_id, display_name=record.display_name, bio=record.bio, occupation=record.occupation, primary_use_cases=self._decode_list(record.primary_use_cases), answer_priorities=self._decode_list(record.answer_priorities), tone_preference=record.tone_preference, response_length=record.response_length, language_preference=record.language_preference, workflow_preference=record.workflow_preference, interests=self._decode_list(record.interests), approval_preferences=self._decode_list(record.approval_preferences), avoid_preferences=record.avoid_preferences, onboarding_completed=record.onboarding_completed, last_onboarding_step=record.last_onboarding_step, ) def render_completion_message(self, record: TelegramUserProfileORM) -> str: profile = self._to_record(record) summary = [ "Seni tanidim ve tercihlerini kaydettim.", f"- Hitap: {profile.display_name or 'belirtilmedi'}", f"- Ton: {profile.tone_preference or 'belirtilmedi'}", f"- Dil: {profile.language_preference or 'belirtilmedi'}", f"- Cevap uzunlugu: {profile.response_length or 'belirtilmedi'}", f"- Calisma bicimi: {profile.workflow_preference or 'belirtilmedi'}", ] return "\n".join(summary)