Files
hack/tools/scripts/llm-key-hunter/results/_cache/content/annenkovlabs_girl-agent.json
T

1 line
80 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{"repo": "annenkovlabs/girl-agent", "files": {"desktop-rs/installer/src/data.rs": {"6e40d834c1ff1d614ca2c450c5d66285956c85e9": {"t": 1785746221.2695518, "n": 22012, "h": "c2fd0847f8d36087", "c": "//! Static catalogues mirrored from the TS wizard.\n//!\n//! Kept in sync with `src/presets/llm.ts`, `src/presets/stages.ts`,\n//! `src/presets/communication.ts` and `src/data/timezones.ts`.\n\n#[derive(Debug, Clone)]\npub struct LlmPreset {\n pub id: &'static str,\n pub label: &'static str,\n pub proto: &'static str, // \"openai\" | \"anthropic\"\n pub base_url: Option<&'static str>,\n pub default_model: &'static str,\n pub default_api_key: Option<&'static str>,\n pub api_key_required: bool,\n pub custom: bool,\n pub models: &'static [&'static str],\n pub hint: &'static str,\n pub recommended: bool,\n pub referral_url: Option<&'static str>,\n pub referral_label: Option<&'static str>,\n}\n\npub const LLM_PRESETS: &[LlmPreset] = &[\n LlmPreset {\n id: \"claudehub\",\n label: \"ClaudeHub\",\n proto: \"anthropic\",\n base_url: Some(\"https://api.claudehub.fun\"),\n default_model: \"claude-sonnet-4.6\",\n default_api_key: None,\n api_key_required: true,\n custom: false,\n models: &[\n \"claude-opus-4.7\", \"claude-opus-4.6\", \"claude-opus-4.5\",\n \"claude-sonnet-4.6\", \"claude-sonnet-4.5\", \"claude-haiku-4.5\",\n \"gpt-5.5\", \"gpt-5.4\",\n ],\n hint: \"рекомендуем · прокси для Claude / GPT · РФ, СБП, крипта · по реферальной ссылке +бонус\",\n recommended: true,\n referral_url: Some(\"https://app.claudehub.fun/r/7BXGRY\"),\n referral_label: Some(\"открыть claudehub.fun (реферал)\"),\n },\n LlmPreset {\n id: \"openai\",\n label: \"OpenAI\",\n proto: \"openai\",\n base_url: None,\n default_model: \"gpt-5.5\",\n default_api_key: None,\n api_key_required: true,\n custom: false,\n models: &[\n \"gpt-5.5\", \"gpt-5.5-thinking\", \"gpt-5.5-pro\",\n \"gpt-5.4\", \"gpt-5.4-pro\", \"gpt-5.4-thinking\",\n \"gpt-5.3-chat-latest\", \"gpt-5.4-mini\", \"gpt-5.4-nano\",\n \"gpt-4o\", \"gpt-4o-mini\", \"gpt-4.1\", \"gpt-4.1-mini\",\n ],\n hint: \"ChatGPT API · нужен ключ из platform.openai.com\",\n recommended: false,\n referral_url: None,\n referral_label: None,\n },\n LlmPreset {\n id: \"lmstudio\",\n label: \"LM Studio\",\n proto: \"openai\",\n base_url: Some(\"http://localhost:1234/v1\"),\n default_model: \"\",\n default_api_key: Some(\"lm-studio\"),\n api_key_required: false,\n custom: true,\n models: &[],\n hint: \"локально, OpenAI-совместимый эндпоинт; ключ не нужен\",\n recommended: false,\n referral_url: None,\n referral_label: None,\n },\n LlmPreset {\n id: \"ollama\",\n label: \"Ollama\",\n proto: \"openai\",\n base_url: Some(\"http://localhost:11434/v1\"),\n default_model: \"llama3.1\",\n default_api_key: Some(\"ollama\"),\n api_key_required: false,\n custom: true,\n models: &[],\n hint: \"локально через /v1; ключ не нужен\",\n recommended: false,\n referral_url: None,\n referral_label: None,\n },\n LlmPreset {\n id: \"anthropic\",\n label: \"Anthropic\",\n proto: \"anthropic\",\n base_url: None,\n default_model: \"claude-sonnet-4-6\",\n default_api_key: None,\n api_key_required: true,\n custom: false,\n models: &[\n \"claude-opus-4-7\", \"claude-sonnet-4-6\", \"claude-haiku-4-5-20251001\",\n \"claude-opus-4-6\", \"claude-sonnet-4-5\", \"claude-opus-4-1\",\n ],\n hint: \"Claude · нужен ключ из console.anthropic.com\",\n recommended: false,\n referral_url: None,\n referral_label: None,\n },\n LlmPreset {\n id: \"openrouter\",\n label: \"OpenRouter\",\n proto: \"openai\",\n base_url: Some(\"https://openrouter.ai/api/v1\"),\n default_model: \"openai/gpt-5.3-chat-latest\",\n default_api_key: None,\n api_key_required: true,\n custom: false,\n models: &[\n \"openai/gpt-5.3-chat-latest\", \"openai/gpt-5.5\",\n \"openai/gpt-5.5-thinking\", \"openai/gpt-5.5-pro\",\n \"anthropic/claude-sonnet-4.6\", \"anthropic/claude-opus-4.7\",\n \"google/gemini-3.1-pro\", \"deepseek/deepseek-v4-pro\", \"x-ai/grok-4.3\",\n ],\n hint: \"агрегатор моделей · openrouter.ai · приём в крипте\",\n recommended: false,\n referral_url: None,\n referral_label: None,\n },\n LlmPreset {\n id: \"groq\",\n label: \"Groq\",\n proto: \"openai\",\n base_url: Some(\"https://api.groq.com/openai/v1\"),\n default_model: \"llama-3.3-70b-versatile\",\n default_api_key: None,\n api_key_required: true,\n custom: false,\n models: &[\n \"llama-3.3-70b-versatile\", \"llama-3.1-8b-instant\",\n \"llama-4-scout-17b-16e-instruct\", \"qwen-3-32b\", \"mixtral-8x7b-32768\",\n ],\n hint: \"очень быстрый инференс на open-source моделях\",\n recommended: false,\n referral_url: None,\n referral_label: None,\n },\n LlmPreset {\n id: \"deepseek\",\n label: \"DeepSeek\",\n proto: \"openai\",\n base_url: Some(\"https://api.deepseek.com\"),\n default_model: \"deepseek-v4-flash\",\n default_api_key: None,\n api_key_required: true,\n custom: false,\n models: &[\"deepseek-v4-pro\", \"deepseek-v4-flash\", \"deepseek-chat\", \"deepseek-reasoner\"],\n hint: \"deepseek-chat/reasoner deprecated 2026-07-24, бери V4\",\n recommended: false,\n referral_url: None,\n referral_label: None,\n },\n LlmPreset {\n id: \"mistral\",\n label: \"Mistral\",\n proto: \"openai\",\n base_url: Some(\"https://api.mistral.ai/v1\"),\n default_model: \"mistral-large-2512\",\n default_api_key: None,\n api_key_required: true,\n custom: false,\n models: &[\n \"mistral-large-2512\", \"mistral-small-2603\",\n \"ministral-8b-2512\", \"ministral-14b-2512\",\n \"mistral-large-latest\", \"mistral-small-latest\",\n ],\n hint: \"французский провайдер, Le Chat и API\",\n recommended: false,\n referral_url: None,\n referral_label: None,\n },\n LlmPreset {\n id: \"google\",\n label: \"Google Gemini\",\n proto: \"openai\",\n base_url: Some(\"https://generativelanguage.googleapis.com/v1beta/openai\"),\n default_model: \"gemini-3.1-pro\",\n default_api_key: None,\n api_key_required: true,\n custom: false,\n models: &[\"gemini-3.1-pro\", \"gemini-3-flash\", \"gemini-3.1-flash-lite\", \"gemini-2.5-pro\", \"gemini-2.5-flash\"],\n hint: \"Gemini через OpenAI-совместимый эндпоинт\",\n recommended: false,\n referral_url: None,\n referral_label: None,\n },\n LlmPreset {\n id: \"xai\",\n label: \"xAI Grok\",\n proto: \"openai\",\n base_url: Some(\"https://api.x.ai/v1\"),\n default_model: \"grok-4.3\",\n default_api_key: None,\n api_key_required: true,\n custom: false,\n models: &[\"grok-4.3\", \"grok-4.20-reasoning\", \"grok-4.20-non-reasoning\", \"grok-4\", \"grok-3\", \"grok-3-mini\"],\n hint: \"Grok от xAI · ключ из console.x.ai\",\n recommended: false,\n referral_url: None,\n referral_label: None,\n },\n LlmPreset {\n id: \"together\",\n label: \"Together AI\",\n proto: \"openai\",\n base_url: Some(\"https://api.together.xyz/v1\"),\n default_model: \"meta-llama/Llama-3.3-70B-Instruct-Turbo\",\n default_api_key: None,\n api_key_required: true,\n custom: false,\n models: &[\n \"meta-llama/Llama-3.3-70B-Instruct-Turbo\",\n \"meta-llama/Llama-4-scout-17b-instruct\",\n \"Qwen/Qwen2.5-72B-Instruct-Turbo\",\n \"deepseek-ai/DeepSeek-V3\",\n ],\n hint: \"хостинг open-source моделей\",\n recommended: false,\n referral_url: None,\n referral_label: None,\n },\n LlmPreset {\n id: \"fireworks\",\n label: \"Fireworks\",\n proto: \"openai\",\n base_url: Some(\"https://api.fireworks.ai/inference/v1\"),\n default_model: \"accounts/fireworks/models/llama-v3p3-70b-instruct\",\n default_api_key: None,\n api_key_required: true,\n custom: false,\n models: &[\n \"accounts/fireworks/models/llama-v3p3-70b-instruct\",\n \"accounts/fireworks/models/llama-4-scout-17b-16e-instruct\",\n \"accounts/fireworks/models/qwen2p5-72b-instruct\",\n \"accounts/fireworks/models/deepseek-v3\",\n ],\n hint: \"хостинг open-source моделей\",\n recommended: false,\n referral_url: None,\n referral_label: None,\n },\n LlmPreset {\n id: \"perplexity\",\n label: \"Perplexity\",\n proto: \"openai\",\n base_url: Some(\"https://api.perplexity.ai\"),\n default_model: \"sonar-pro\",\n default_api_key: None,\n api_key_required: true,\n custom: false,\n models: &[\"sonar-pro\", \"sonar\", \"sonar-reasoning\"],\n hint: \"встроенный поиск в инференсе\",\n recommended: false,\n referral_url: None,\n referral_label: None,\n },\n LlmPreset {\n id: \"cerebras\",\n label: \"Cerebras\",\n proto: \"openai\",\n base_url: Some(\"https://api.cerebras.ai/v1\"),\n default_model: \"llama-3.3-70b\",\n default_api_key: None,\n api_key_required: true,\n custom: false,\n models: &[\"llama-3.3-70b\", \"llama-4-scout-17b-16e-instruct\", \"qwen-3-32b\"],\n hint: \"ультра-быстрый инференс на чипах cerebras\",\n recommended: false,\n referral_url: None,\n referral_label: None,\n },\n LlmPreset {\n id: \"custom-openai\",\n label: \"Custom (OpenAI-compatible)\",\n proto: \"openai\",\n base_url: None,\n default_model: \"\",\n default_api_key: None,\n api_key_required: false,\n custom: true,\n models: &[],\n hint: \"укажи свой base URL и модель\",\n recommended: false,\n referral_url: None,\n referral_label: None,\n },\n LlmPreset {\n id: \"custom-anthropic\",\n label: \"Custom (Anthropic-compatible)\",\n proto: \"anthropic\",\n base_url: None,\n default_model: \"\",\n default_api_key: None,\n api_key_required: false,\n custom: true,\n models: &[],\n hint: \"укажи свой base URL и модель\",\n recommended: false,\n referral_url: None,\n referral_label: None,\n },\n];\n\npub fn find_llm_preset(id: &str) -> Option<&'static LlmPreset> {\n LLM_PRESETS.iter().find(|p| p.id == id)\n}\n\n#[derive(Debug, Clone)]\npub struct StagePreset {\n pub id: &'static str,\n pub label: &'static str,\n pub description: &'static str,\n}\n\npub const STAGE_PRESETS: &[StagePreset] = &[\n StagePreset { id: \"met-irl-got-tg\", label: \"встретились в реале — дала тг\", description: \"только что обменялись тг. помнит лицо, голос. лёгкий интерес.\" },\n StagePreset { id: \"tg-given-cold\", label: \"дала тг, но не убедил отвечать\", description: \"сомневается. часто игнорит, отвечает односложно. нужно добиваться.\" },\n StagePreset { id: \"tg-given-warming\", label: \"дала тг, отвечает осторожно\", description: \"оттаивает. отвечает, но коротко. тестит тебя.\" },\n StagePreset { id: \"convinced\", label: \"убедил отвечать стабильно\", description: \"общаетесь регулярно, флиртует, ещё не виделись после знакомства.\" },\n StagePreset { id: \"first-date-done\", label: \"сходили один раз\", description: \"первое свидание было, в подвешенном состоянии — нравится, но не пара.\" },\n StagePreset { id: \"dating-early\", label: \"только начали встречаться\", description: \"около месяца вместе. бабочки, всё внове, но границы ещё хрупкие.\" },\n StagePreset { id: \"dating-stable\", label: \"пара, общаетесь свободно\", description: \"стабильные отношения, шутки, бытовуха, доверие.\" },\n StagePreset { id: \"long-term\", label: \"давно вместе\", description: \"год+ вместе. иногда раздражение, рутина, глубокое доверие.\" },\n];\n\n#[derive(Debug, Clone)]\npub struct CommunicationPreset {\n pub id: &'static str,\n pub label: &'static str,\n pub description: &'static str,\n pub notifications: &'static str, // muted|normal|priority\n pub message_style: &'static str, // one-liners|balanced|bursty|longform\n pub initiative: &'static str, // low|medium|high\n pub life_sharing: &'static str, // low|medium|high\n}\n\npub const COMMUNICATION_PRESETS: &[CommunicationPreset] = &[\n CommunicationPreset {\n id: \"normal\", label: \"нормальная\",\n description: \"золотая середина — отвечает нормально, не липнет, иногда сама пишет\",\n notifications: \"normal\", message_style: \"balanced\", initiative: \"medium\", life_sharing: \"medium\",\n },\n CommunicationPreset {\n id: \"cute\", label: \"милая\",\n description: \"тёплая и заботливая, часто отвечает, пишет первой, делится моментами\",\n notifications: \"priority\", message_style: \"balanced\", initiative: \"high\", life_sharing: \"high\",\n },\n CommunicationPreset {\n id: \"alt\", label: \"альтушка\",\n description: \"холодная, сухая, короткие ответы, почти не пишет первой, личным не делится\",\n notifications: \"normal\", message_style: \"one-liners\", initiative: \"low\", life_sharing: \"low\",\n },\n CommunicationPreset {\n id: \"clingy\", label: \"залипала\",\n description: \"очень липучая, спамит пузырями, всегда онлайн, пишет первой постоянно\",\n notifications: \"priority\", message_style: \"bursty\", initiative: \"high\", life_sharing: \"high\",\n },\n CommunicationPreset {\n id: \"chatty\", label: \"болтушка\",\n description: \"любит рассказывать истории, пишет длинные тексты, часто делится бытовым\",\n notifications: \"priority\", message_style: \"longform\", initiative: \"medium\", life_sharing: \"high\",\n },\n];\n\n#[derive(Debug, Clone)]\npub struct TzEntry {\n pub iana: &'static str,\n pub gmt_winter: &'static str,\n pub city: &'static str,\n pub country: &'static str,\n pub aliases: &'static [&'static str],\n}\n\npub const TIMEZONES: &[TzEntry] = &[\n TzEntry { iana: \"Europe/Kaliningrad\", gmt_winter: \"GMT+2\", city: \"Калининград\", country: \"Россия\", aliases: &[\"калининград\", \"kaliningrad\", \"rus\"] },\n TzEntry { iana: \"Europe/Moscow\", gmt_winter: \"GMT+3\", city: \"Москва\", country: \"Россия\", aliases: &[\"москва\", \"msk\", \"moscow\", \"питер\", \"санкт-петербург\", \"spb\", \"rus\"] },\n TzEntry { iana: \"Europe/Samara\", gmt_winter: \"GMT+4\", city: \"Самара\", country: \"Россия\", aliases: &[\"самара\", \"samara\", \"ижевск\"] },\n TzEntry { iana: \"Asia/Yekaterinburg\", gmt_winter: \"GMT+5\", city: \"Екатеринбург\", country: \"Россия\", aliases: &[\"екб\", \"yekaterinburg\", \"пермь\", \"уфа\", \"челябинск\"] },\n TzEntry { iana: \"Asia/Omsk\", gmt_winter: \"GMT+6\", city: \"Омск\", country: \"Россия\", aliases: &[\"омск\", \"omsk\"] },\n TzEntry { iana: \"Asia/Novosibirsk\", gmt_winter: \"GMT+7\", city: \"Новосибирск\", country: \"Россия\", aliases: &[\"нск\", \"новосибирск\", \"novosibirsk\", \"томск\", \"красноярск\"] },\n TzEntry { iana: \"Asia/Irkutsk\", gmt_winter: \"GMT+8\", city: \"Иркутск\", country: \"Россия\", aliases: &[\"иркутск\", \"irkutsk\", \"улан-удэ\"] },\n TzEntry { iana: \"Asia/Yakutsk\", gmt_winter: \"GMT+9\", city: \"Якутск\", country: \"Россия\", aliases: &[\"якутск\", \"yakutsk\", \"чита\"] },\n TzEntry { iana: \"Asia/Vladivostok\", gmt_winter: \"GMT+10\", city: \"Владивосток\", country: \"Россия\", aliases: &[\"владивосток\", \"vladivostok\", \"хабаровск\"] },\n TzEntry { iana: \"Asia/Magadan\", gmt_winter: \"GMT+11\", city: \"Магадан\", country: \"Россия\", aliases: &[\"магадан\", \"magadan\", \"сахалин\"] },\n TzEntry { iana: \"Asia/Kamchatka\", gmt_winter: \"GMT+12\", city: \"Камчатка\", country: \"Россия\", aliases: &[\"камчатка\", \"kamchatka\", \"петропавловск\"] },\n TzEntry { iana: \"Europe/Kyiv\", gmt_winter: \"GMT+2\", city: \"Київ\", country: \"Україна\", aliases: &[\"киев\", \"київ\", \"kyiv\", \"kiev\", \"ua\", \"украина\", \"україна\", \"львов\", \"львів\", \"одесса\", \"одеса\", \"харьков\", \"харків\"] },\n TzEntry { iana: \"Europe/Minsk\", gmt_winter: \"GMT+3\", city: \"Минск\", country: \"Беларусь\", aliases: &[\"минск\", \"minsk\", \"бел\", \"беларусь\", \"by\"] },\n TzEntry { iana: \"Asia/Almaty\", gmt_winter: \"GMT+5\", city: \"Алматы\", country: \"Казахстан\", aliases: &[\"алматы\", \"almaty\", \"kz\", \"казахстан\", \"астана\", \"нур-султан\"] },\n TzEntry { iana: \"Asia/Tashkent\", gmt_winter: \"GMT+5\", city: \"Ташкент\", country: \"Узбекистан\", aliases: &[\"ташкент\", \"tashkent\", \"uz\", \"узбекистан\"] },\n TzEntry { iana: \"Asia/Bishkek\", gmt_winter: \"GMT+6\", city: \"Бишкек\", country: \"Кыргызстан\", aliases: &[\"бишкек\", \"bishkek\", \"kg\"] },\n TzEntry { iana: \"Asia/Tbilisi\", gmt_winter: \"GMT+4\", city: \"Тбилиси\", country: \"Грузия\", aliases: &[\"тбилиси\", \"tbilisi\", \"ge\", \"грузия\"] },\n TzEntry { iana: \"Asia/Yerevan\", gmt_winter: \"GMT+4\", city: \"Ереван\", country: \"Армения\", aliases: &[\"ереван\", \"yerevan\", \"am\", \"армения\"] },\n TzEntry { iana: \"Asia/Baku\", gmt_winter: \"GMT+4\", city: \"Баку\", country: \"Азербайджан\", aliases: &[\"баку\", \"baku\", \"az\", \"азербайджан\"] },\n];\n\npub fn search_tz(query: &str) -> Vec<&'static TzEntry> {\n let q = query.trim().to_lowercase();\n if q.is_empty() {\n return TIMEZONES.iter().collect();\n }\n TIMEZONES\n .iter()\n .filter(|tz| {\n tz.iana.to_lowercase().contains(&q)\n || tz.city.to_lowercase().contains(&q)\n || tz.country.to_lowercase().contains(&q)\n || tz.aliases.iter().any(|a| a.contains(&q))\n })\n .collect()\n}\n\npub fn default_tz_for_nationality(nationality: &str) -> &'static str {\n match nationality {\n \"UA\" => \"Europe/Kyiv\",\n _ => \"Europe/Moscow\",\n }\n}\n\npub const NATIONALITIES: &[(&str, &str)] = &[\n (\"RU\", \"русская\"),\n (\"UA\", \"украинка\"),\n];\n\npub const NAMES_RU: &[&str] = &[\n \"Аня\", \"Алина\", \"Алёна\", \"Алиса\", \"Альбина\", \"Ася\",\n \"Арина\", \"Ангелина\", \"Настя\",\n \"Варя\", \"Лера\", \"Ника\", \"Вика\", \"Виолетта\", \"Виталина\",\n \"Даша\", \"Диана\", \"Дина\", \"Дарина\", \"Доминика\",\n \"Ева\", \"Женя\", \"Катя\", \"Лена\", \"Лиза\", \"Злата\",\n \"Ира\", \"Инна\", \"Ксюша\", \"Кира\", \"Кристина\", \"Карина\", \"Камилла\",\n \"Лиля\", \"Маша\", \"Марина\", \"Рита\", \"Милана\", \"Милена\", \"Мила\", \"Мира\", \"Майя\",\n \"Надя\", \"Наташа\", \"Настя\", \"Нина\",\n \"Оля\", \"Оксана\", \"Полина\", \"Поля\", \"Олеся\", \"Олена\",\n \"Соня\", \"София\", \"Стася\", \"Снежана\",\n \"Таня\", \"Тоня\",\n \"Ульяна\", \"Уля\",\n \"Юля\", \"Юлиана\",\n \"Яна\", \"Яся\",\n];\n\npub const NAMES_UA: &[&str] = &[\n \"Анна\", \"Альона\", \"Аліна\", \"Аліса\", \"Анастасія\",\n \"Богдана\", \"Валерія\", \"Вікторія\", \"Віолетта\",\n \"Дарина\", \"Діана\", \"Дарія\",\n \"Євгенія\", \"Єлизавета\",\n \"Злата\", \"Зоряна\",\n \"Ірина\", \"Інна\",\n \"Катерина\", \"Карина\", \"Кіра\", \"Ксенія\",\n \"Лілія\", \"Лариса\", \"Леся\",\n \"Марія\", \"Маріанна\", \"Мілана\", \"Мілена\",\n \"Надія\", \"Наталія\", \"Наталка\", \"Ніна\",\n \"Оксана\", \"Олена\", \"Олеся\", \"Ольга\",\n \"Поліна\", \"Перелесниця\",\n \"Світлана\", \"Софія\", \"Соломія\",\n \"Таїсія\", \"Тетяна\",\n \"Уляна\",\n \"Христина\",\n \"Юлія\", \"Юліана\",\n \"Яна\", \"Ярина\",\n];\n\npub fn pick_random_name(nationality: &str, seed: u64) -> &'static str {\n let pool: &[&str] = if nationality == \"UA\" { NAMES_UA } else { NAMES_RU };\n let idx = (seed as usize) % pool.len();\n pool[idx]\n}\n\n#[derive(Debug, Clone)]\npub struct SleepPreset {\n pub id: &'static str,\n pub label: &'static str,\n pub description: &'static str,\n pub from_h: u8,\n pub to_h: u8,\n pub wake_chance: f32,\n}\n\npub const SLEEP_PRESETS: &[SleepPreset] = &[\n SleepPreset { id: \"standard\", label: \"обычный\", description: \"00:00 — 09:00 · ~5% что разбудит сообщение\", from_h: 0, to_h: 9, wake_chance: 0.05 },\n SleepPreset { id: \"late\", label: \"сова\", description: \"02:00 — 11:00 · поздно ложится, поздно встаёт\", from_h: 2, to_h: 11, wake_chance: 0.05 },\n SleepPreset { id: \"early\", label: \"жаворонок\", description: \"22:00 — 07:00 · рано спать, рано на работу\", from_h: 22, to_h: 7, wake_chance: 0.04 },\n SleepPreset { id: \"owl\", label: \"не спит до утра\", description: \"04:00 — 13:00 · ночной образ жизни\", from_h: 4, to_h: 13, wake_chance: 0.08 },\n SleepPreset { id: \"custom\", label: \"кастом\", description: \"задай свой режим сна\", from_h: 0, to_h: 0, wake_chance: 0.0 },\n];\n\npub const PRIVACY_OPTIONS: &[(&str, &str, &str)] = &[\n (\"owner-only\", \"только владельцу\", \"отвечает только тебе. незнакомцам — игнор.\"),\n (\"allow-strangers\", \"всем, кто пишет\", \"отвечает кому угодно — нужно для bot-режима в группах.\"),\n];\n\npub fn slugify(name: &str) -> String {\n let mapped: String = name\n .chars()\n .map(|c| {\n if c.is_ascii_alphanumeric() {\n c.to_ascii_lowercase()\n } else {\n match c {\n 'а'..='я' | 'А'..='Я' | 'ё' | 'Ё' | 'і' | 'І' | 'ї' | 'Ї' | 'є' | 'Є' | 'ґ' | 'Ґ' => {\n translit_ru(c)\n }\n _ => '-',\n }\n }\n })\n .collect();\n let mut prev_dash = false;\n let mut out = String::new();\n for c in mapped.chars() {\n if c == '-' {\n if !prev_dash && !out.is_empty() {\n out.push('-');\n prev_dash = true;\n }\n } else {\n out.push(c);\n prev_dash = false;\n }\n }\n let trimmed = out.trim_matches('-').to_string();\n if trimmed.is_empty() {\n \"persona\".to_string()\n } else {\n trimmed\n }\n}\n\nfn translit_ru(c: char) -> char {\n let lower = c.to_lowercase().next().unwrap_or(c);\n match lower {\n 'а' => 'a', 'б' => 'b', 'в' => 'v', 'г' => 'g', 'д' => 'd', 'е' | 'ё' | 'є' => 'e',\n 'ж' => 'z', 'з' => 'z', 'и' | 'і' | 'ї' => 'i', 'й' => 'y', 'к' => 'k', 'л' => 'l',\n 'м' => 'm', 'н' => 'n', 'о' => 'o', 'п' => 'p', 'р' => 'r', 'с' => 's', 'т' => 't',\n 'у' => 'u', 'ф' => 'f', 'х' => 'h', 'ц' => 'c', 'ч' => 'c', 'ш' => 's', 'щ' => 's',\n 'ъ' | 'ь' => '-', 'ы' => 'y', 'э' => 'e', 'ю' => 'u', 'я' => 'a', 'ґ' => 'g',\n _ => '-',\n }\n}\n"}, "95d64917b689138729d2f85f30d50f70a6e318d9": {"t": 1785760727.0976696, "n": 22012, "h": "c2fd0847f8d36087", "c": "//! Static catalogues mirrored from the TS wizard.\n//!\n//! Kept in sync with `src/presets/llm.ts`, `src/presets/stages.ts`,\n//! `src/presets/communication.ts` and `src/data/timezones.ts`.\n\n#[derive(Debug, Clone)]\npub struct LlmPreset {\n pub id: &'static str,\n pub label: &'static str,\n pub proto: &'static str, // \"openai\" | \"anthropic\"\n pub base_url: Option<&'static str>,\n pub default_model: &'static str,\n pub default_api_key: Option<&'static str>,\n pub api_key_required: bool,\n pub custom: bool,\n pub models: &'static [&'static str],\n pub hint: &'static str,\n pub recommended: bool,\n pub referral_url: Option<&'static str>,\n pub referral_label: Option<&'static str>,\n}\n\npub const LLM_PRESETS: &[LlmPreset] = &[\n LlmPreset {\n id: \"claudehub\",\n label: \"ClaudeHub\",\n proto: \"anthropic\",\n base_url: Some(\"https://api.claudehub.fun\"),\n default_model: \"claude-sonnet-4.6\",\n default_api_key: None,\n api_key_required: true,\n custom: false,\n models: &[\n \"claude-opus-4.7\", \"claude-opus-4.6\", \"claude-opus-4.5\",\n \"claude-sonnet-4.6\", \"claude-sonnet-4.5\", \"claude-haiku-4.5\",\n \"gpt-5.5\", \"gpt-5.4\",\n ],\n hint: \"рекомендуем · прокси для Claude / GPT · РФ, СБП, крипта · по реферальной ссылке +бонус\",\n recommended: true,\n referral_url: Some(\"https://app.claudehub.fun/r/7BXGRY\"),\n referral_label: Some(\"открыть claudehub.fun (реферал)\"),\n },\n LlmPreset {\n id: \"openai\",\n label: \"OpenAI\",\n proto: \"openai\",\n base_url: None,\n default_model: \"gpt-5.5\",\n default_api_key: None,\n api_key_required: true,\n custom: false,\n models: &[\n \"gpt-5.5\", \"gpt-5.5-thinking\", \"gpt-5.5-pro\",\n \"gpt-5.4\", \"gpt-5.4-pro\", \"gpt-5.4-thinking\",\n \"gpt-5.3-chat-latest\", \"gpt-5.4-mini\", \"gpt-5.4-nano\",\n \"gpt-4o\", \"gpt-4o-mini\", \"gpt-4.1\", \"gpt-4.1-mini\",\n ],\n hint: \"ChatGPT API · нужен ключ из platform.openai.com\",\n recommended: false,\n referral_url: None,\n referral_label: None,\n },\n LlmPreset {\n id: \"lmstudio\",\n label: \"LM Studio\",\n proto: \"openai\",\n base_url: Some(\"http://localhost:1234/v1\"),\n default_model: \"\",\n default_api_key: Some(\"lm-studio\"),\n api_key_required: false,\n custom: true,\n models: &[],\n hint: \"локально, OpenAI-совместимый эндпоинт; ключ не нужен\",\n recommended: false,\n referral_url: None,\n referral_label: None,\n },\n LlmPreset {\n id: \"ollama\",\n label: \"Ollama\",\n proto: \"openai\",\n base_url: Some(\"http://localhost:11434/v1\"),\n default_model: \"llama3.1\",\n default_api_key: Some(\"ollama\"),\n api_key_required: false,\n custom: true,\n models: &[],\n hint: \"локально через /v1; ключ не нужен\",\n recommended: false,\n referral_url: None,\n referral_label: None,\n },\n LlmPreset {\n id: \"anthropic\",\n label: \"Anthropic\",\n proto: \"anthropic\",\n base_url: None,\n default_model: \"claude-sonnet-4-6\",\n default_api_key: None,\n api_key_required: true,\n custom: false,\n models: &[\n \"claude-opus-4-7\", \"claude-sonnet-4-6\", \"claude-haiku-4-5-20251001\",\n \"claude-opus-4-6\", \"claude-sonnet-4-5\", \"claude-opus-4-1\",\n ],\n hint: \"Claude · нужен ключ из console.anthropic.com\",\n recommended: false,\n referral_url: None,\n referral_label: None,\n },\n LlmPreset {\n id: \"openrouter\",\n label: \"OpenRouter\",\n proto: \"openai\",\n base_url: Some(\"https://openrouter.ai/api/v1\"),\n default_model: \"openai/gpt-5.3-chat-latest\",\n default_api_key: None,\n api_key_required: true,\n custom: false,\n models: &[\n \"openai/gpt-5.3-chat-latest\", \"openai/gpt-5.5\",\n \"openai/gpt-5.5-thinking\", \"openai/gpt-5.5-pro\",\n \"anthropic/claude-sonnet-4.6\", \"anthropic/claude-opus-4.7\",\n \"google/gemini-3.1-pro\", \"deepseek/deepseek-v4-pro\", \"x-ai/grok-4.3\",\n ],\n hint: \"агрегатор моделей · openrouter.ai · приём в крипте\",\n recommended: false,\n referral_url: None,\n referral_label: None,\n },\n LlmPreset {\n id: \"groq\",\n label: \"Groq\",\n proto: \"openai\",\n base_url: Some(\"https://api.groq.com/openai/v1\"),\n default_model: \"llama-3.3-70b-versatile\",\n default_api_key: None,\n api_key_required: true,\n custom: false,\n models: &[\n \"llama-3.3-70b-versatile\", \"llama-3.1-8b-instant\",\n \"llama-4-scout-17b-16e-instruct\", \"qwen-3-32b\", \"mixtral-8x7b-32768\",\n ],\n hint: \"очень быстрый инференс на open-source моделях\",\n recommended: false,\n referral_url: None,\n referral_label: None,\n },\n LlmPreset {\n id: \"deepseek\",\n label: \"DeepSeek\",\n proto: \"openai\",\n base_url: Some(\"https://api.deepseek.com\"),\n default_model: \"deepseek-v4-flash\",\n default_api_key: None,\n api_key_required: true,\n custom: false,\n models: &[\"deepseek-v4-pro\", \"deepseek-v4-flash\", \"deepseek-chat\", \"deepseek-reasoner\"],\n hint: \"deepseek-chat/reasoner deprecated 2026-07-24, бери V4\",\n recommended: false,\n referral_url: None,\n referral_label: None,\n },\n LlmPreset {\n id: \"mistral\",\n label: \"Mistral\",\n proto: \"openai\",\n base_url: Some(\"https://api.mistral.ai/v1\"),\n default_model: \"mistral-large-2512\",\n default_api_key: None,\n api_key_required: true,\n custom: false,\n models: &[\n \"mistral-large-2512\", \"mistral-small-2603\",\n \"ministral-8b-2512\", \"ministral-14b-2512\",\n \"mistral-large-latest\", \"mistral-small-latest\",\n ],\n hint: \"французский провайдер, Le Chat и API\",\n recommended: false,\n referral_url: None,\n referral_label: None,\n },\n LlmPreset {\n id: \"google\",\n label: \"Google Gemini\",\n proto: \"openai\",\n base_url: Some(\"https://generativelanguage.googleapis.com/v1beta/openai\"),\n default_model: \"gemini-3.1-pro\",\n default_api_key: None,\n api_key_required: true,\n custom: false,\n models: &[\"gemini-3.1-pro\", \"gemini-3-flash\", \"gemini-3.1-flash-lite\", \"gemini-2.5-pro\", \"gemini-2.5-flash\"],\n hint: \"Gemini через OpenAI-совместимый эндпоинт\",\n recommended: false,\n referral_url: None,\n referral_label: None,\n },\n LlmPreset {\n id: \"xai\",\n label: \"xAI Grok\",\n proto: \"openai\",\n base_url: Some(\"https://api.x.ai/v1\"),\n default_model: \"grok-4.3\",\n default_api_key: None,\n api_key_required: true,\n custom: false,\n models: &[\"grok-4.3\", \"grok-4.20-reasoning\", \"grok-4.20-non-reasoning\", \"grok-4\", \"grok-3\", \"grok-3-mini\"],\n hint: \"Grok от xAI · ключ из console.x.ai\",\n recommended: false,\n referral_url: None,\n referral_label: None,\n },\n LlmPreset {\n id: \"together\",\n label: \"Together AI\",\n proto: \"openai\",\n base_url: Some(\"https://api.together.xyz/v1\"),\n default_model: \"meta-llama/Llama-3.3-70B-Instruct-Turbo\",\n default_api_key: None,\n api_key_required: true,\n custom: false,\n models: &[\n \"meta-llama/Llama-3.3-70B-Instruct-Turbo\",\n \"meta-llama/Llama-4-scout-17b-instruct\",\n \"Qwen/Qwen2.5-72B-Instruct-Turbo\",\n \"deepseek-ai/DeepSeek-V3\",\n ],\n hint: \"хостинг open-source моделей\",\n recommended: false,\n referral_url: None,\n referral_label: None,\n },\n LlmPreset {\n id: \"fireworks\",\n label: \"Fireworks\",\n proto: \"openai\",\n base_url: Some(\"https://api.fireworks.ai/inference/v1\"),\n default_model: \"accounts/fireworks/models/llama-v3p3-70b-instruct\",\n default_api_key: None,\n api_key_required: true,\n custom: false,\n models: &[\n \"accounts/fireworks/models/llama-v3p3-70b-instruct\",\n \"accounts/fireworks/models/llama-4-scout-17b-16e-instruct\",\n \"accounts/fireworks/models/qwen2p5-72b-instruct\",\n \"accounts/fireworks/models/deepseek-v3\",\n ],\n hint: \"хостинг open-source моделей\",\n recommended: false,\n referral_url: None,\n referral_label: None,\n },\n LlmPreset {\n id: \"perplexity\",\n label: \"Perplexity\",\n proto: \"openai\",\n base_url: Some(\"https://api.perplexity.ai\"),\n default_model: \"sonar-pro\",\n default_api_key: None,\n api_key_required: true,\n custom: false,\n models: &[\"sonar-pro\", \"sonar\", \"sonar-reasoning\"],\n hint: \"встроенный поиск в инференсе\",\n recommended: false,\n referral_url: None,\n referral_label: None,\n },\n LlmPreset {\n id: \"cerebras\",\n label: \"Cerebras\",\n proto: \"openai\",\n base_url: Some(\"https://api.cerebras.ai/v1\"),\n default_model: \"llama-3.3-70b\",\n default_api_key: None,\n api_key_required: true,\n custom: false,\n models: &[\"llama-3.3-70b\", \"llama-4-scout-17b-16e-instruct\", \"qwen-3-32b\"],\n hint: \"ультра-быстрый инференс на чипах cerebras\",\n recommended: false,\n referral_url: None,\n referral_label: None,\n },\n LlmPreset {\n id: \"custom-openai\",\n label: \"Custom (OpenAI-compatible)\",\n proto: \"openai\",\n base_url: None,\n default_model: \"\",\n default_api_key: None,\n api_key_required: false,\n custom: true,\n models: &[],\n hint: \"укажи свой base URL и модель\",\n recommended: false,\n referral_url: None,\n referral_label: None,\n },\n LlmPreset {\n id: \"custom-anthropic\",\n label: \"Custom (Anthropic-compatible)\",\n proto: \"anthropic\",\n base_url: None,\n default_model: \"\",\n default_api_key: None,\n api_key_required: false,\n custom: true,\n models: &[],\n hint: \"укажи свой base URL и модель\",\n recommended: false,\n referral_url: None,\n referral_label: None,\n },\n];\n\npub fn find_llm_preset(id: &str) -> Option<&'static LlmPreset> {\n LLM_PRESETS.iter().find(|p| p.id == id)\n}\n\n#[derive(Debug, Clone)]\npub struct StagePreset {\n pub id: &'static str,\n pub label: &'static str,\n pub description: &'static str,\n}\n\npub const STAGE_PRESETS: &[StagePreset] = &[\n StagePreset { id: \"met-irl-got-tg\", label: \"встретились в реале — дала тг\", description: \"только что обменялись тг. помнит лицо, голос. лёгкий интерес.\" },\n StagePreset { id: \"tg-given-cold\", label: \"дала тг, но не убедил отвечать\", description: \"сомневается. часто игнорит, отвечает односложно. нужно добиваться.\" },\n StagePreset { id: \"tg-given-warming\", label: \"дала тг, отвечает осторожно\", description: \"оттаивает. отвечает, но коротко. тестит тебя.\" },\n StagePreset { id: \"convinced\", label: \"убедил отвечать стабильно\", description: \"общаетесь регулярно, флиртует, ещё не виделись после знакомства.\" },\n StagePreset { id: \"first-date-done\", label: \"сходили один раз\", description: \"первое свидание было, в подвешенном состоянии — нравится, но не пара.\" },\n StagePreset { id: \"dating-early\", label: \"только начали встречаться\", description: \"около месяца вместе. бабочки, всё внове, но границы ещё хрупкие.\" },\n StagePreset { id: \"dating-stable\", label: \"пара, общаетесь свободно\", description: \"стабильные отношения, шутки, бытовуха, доверие.\" },\n StagePreset { id: \"long-term\", label: \"давно вместе\", description: \"год+ вместе. иногда раздражение, рутина, глубокое доверие.\" },\n];\n\n#[derive(Debug, Clone)]\npub struct CommunicationPreset {\n pub id: &'static str,\n pub label: &'static str,\n pub description: &'static str,\n pub notifications: &'static str, // muted|normal|priority\n pub message_style: &'static str, // one-liners|balanced|bursty|longform\n pub initiative: &'static str, // low|medium|high\n pub life_sharing: &'static str, // low|medium|high\n}\n\npub const COMMUNICATION_PRESETS: &[CommunicationPreset] = &[\n CommunicationPreset {\n id: \"normal\", label: \"нормальная\",\n description: \"золотая середина — отвечает нормально, не липнет, иногда сама пишет\",\n notifications: \"normal\", message_style: \"balanced\", initiative: \"medium\", life_sharing: \"medium\",\n },\n CommunicationPreset {\n id: \"cute\", label: \"милая\",\n description: \"тёплая и заботливая, часто отвечает, пишет первой, делится моментами\",\n notifications: \"priority\", message_style: \"balanced\", initiative: \"high\", life_sharing: \"high\",\n },\n CommunicationPreset {\n id: \"alt\", label: \"альтушка\",\n description: \"холодная, сухая, короткие ответы, почти не пишет первой, личным не делится\",\n notifications: \"normal\", message_style: \"one-liners\", initiative: \"low\", life_sharing: \"low\",\n },\n CommunicationPreset {\n id: \"clingy\", label: \"залипала\",\n description: \"очень липучая, спамит пузырями, всегда онлайн, пишет первой постоянно\",\n notifications: \"priority\", message_style: \"bursty\", initiative: \"high\", life_sharing: \"high\",\n },\n CommunicationPreset {\n id: \"chatty\", label: \"болтушка\",\n description: \"любит рассказывать истории, пишет длинные тексты, часто делится бытовым\",\n notifications: \"priority\", message_style: \"longform\", initiative: \"medium\", life_sharing: \"high\",\n },\n];\n\n#[derive(Debug, Clone)]\npub struct TzEntry {\n pub iana: &'static str,\n pub gmt_winter: &'static str,\n pub city: &'static str,\n pub country: &'static str,\n pub aliases: &'static [&'static str],\n}\n\npub const TIMEZONES: &[TzEntry] = &[\n TzEntry { iana: \"Europe/Kaliningrad\", gmt_winter: \"GMT+2\", city: \"Калининград\", country: \"Россия\", aliases: &[\"калининград\", \"kaliningrad\", \"rus\"] },\n TzEntry { iana: \"Europe/Moscow\", gmt_winter: \"GMT+3\", city: \"Москва\", country: \"Россия\", aliases: &[\"москва\", \"msk\", \"moscow\", \"питер\", \"санкт-петербург\", \"spb\", \"rus\"] },\n TzEntry { iana: \"Europe/Samara\", gmt_winter: \"GMT+4\", city: \"Самара\", country: \"Россия\", aliases: &[\"самара\", \"samara\", \"ижевск\"] },\n TzEntry { iana: \"Asia/Yekaterinburg\", gmt_winter: \"GMT+5\", city: \"Екатеринбург\", country: \"Россия\", aliases: &[\"екб\", \"yekaterinburg\", \"пермь\", \"уфа\", \"челябинск\"] },\n TzEntry { iana: \"Asia/Omsk\", gmt_winter: \"GMT+6\", city: \"Омск\", country: \"Россия\", aliases: &[\"омск\", \"omsk\"] },\n TzEntry { iana: \"Asia/Novosibirsk\", gmt_winter: \"GMT+7\", city: \"Новосибирск\", country: \"Россия\", aliases: &[\"нск\", \"новосибирск\", \"novosibirsk\", \"томск\", \"красноярск\"] },\n TzEntry { iana: \"Asia/Irkutsk\", gmt_winter: \"GMT+8\", city: \"Иркутск\", country: \"Россия\", aliases: &[\"иркутск\", \"irkutsk\", \"улан-удэ\"] },\n TzEntry { iana: \"Asia/Yakutsk\", gmt_winter: \"GMT+9\", city: \"Якутск\", country: \"Россия\", aliases: &[\"якутск\", \"yakutsk\", \"чита\"] },\n TzEntry { iana: \"Asia/Vladivostok\", gmt_winter: \"GMT+10\", city: \"Владивосток\", country: \"Россия\", aliases: &[\"владивосток\", \"vladivostok\", \"хабаровск\"] },\n TzEntry { iana: \"Asia/Magadan\", gmt_winter: \"GMT+11\", city: \"Магадан\", country: \"Россия\", aliases: &[\"магадан\", \"magadan\", \"сахалин\"] },\n TzEntry { iana: \"Asia/Kamchatka\", gmt_winter: \"GMT+12\", city: \"Камчатка\", country: \"Россия\", aliases: &[\"камчатка\", \"kamchatka\", \"петропавловск\"] },\n TzEntry { iana: \"Europe/Kyiv\", gmt_winter: \"GMT+2\", city: \"Київ\", country: \"Україна\", aliases: &[\"киев\", \"київ\", \"kyiv\", \"kiev\", \"ua\", \"украина\", \"україна\", \"львов\", \"львів\", \"одесса\", \"одеса\", \"харьков\", \"харків\"] },\n TzEntry { iana: \"Europe/Minsk\", gmt_winter: \"GMT+3\", city: \"Минск\", country: \"Беларусь\", aliases: &[\"минск\", \"minsk\", \"бел\", \"беларусь\", \"by\"] },\n TzEntry { iana: \"Asia/Almaty\", gmt_winter: \"GMT+5\", city: \"Алматы\", country: \"Казахстан\", aliases: &[\"алматы\", \"almaty\", \"kz\", \"казахстан\", \"астана\", \"нур-султан\"] },\n TzEntry { iana: \"Asia/Tashkent\", gmt_winter: \"GMT+5\", city: \"Ташкент\", country: \"Узбекистан\", aliases: &[\"ташкент\", \"tashkent\", \"uz\", \"узбекистан\"] },\n TzEntry { iana: \"Asia/Bishkek\", gmt_winter: \"GMT+6\", city: \"Бишкек\", country: \"Кыргызстан\", aliases: &[\"бишкек\", \"bishkek\", \"kg\"] },\n TzEntry { iana: \"Asia/Tbilisi\", gmt_winter: \"GMT+4\", city: \"Тбилиси\", country: \"Грузия\", aliases: &[\"тбилиси\", \"tbilisi\", \"ge\", \"грузия\"] },\n TzEntry { iana: \"Asia/Yerevan\", gmt_winter: \"GMT+4\", city: \"Ереван\", country: \"Армения\", aliases: &[\"ереван\", \"yerevan\", \"am\", \"армения\"] },\n TzEntry { iana: \"Asia/Baku\", gmt_winter: \"GMT+4\", city: \"Баку\", country: \"Азербайджан\", aliases: &[\"баку\", \"baku\", \"az\", \"азербайджан\"] },\n];\n\npub fn search_tz(query: &str) -> Vec<&'static TzEntry> {\n let q = query.trim().to_lowercase();\n if q.is_empty() {\n return TIMEZONES.iter().collect();\n }\n TIMEZONES\n .iter()\n .filter(|tz| {\n tz.iana.to_lowercase().contains(&q)\n || tz.city.to_lowercase().contains(&q)\n || tz.country.to_lowercase().contains(&q)\n || tz.aliases.iter().any(|a| a.contains(&q))\n })\n .collect()\n}\n\npub fn default_tz_for_nationality(nationality: &str) -> &'static str {\n match nationality {\n \"UA\" => \"Europe/Kyiv\",\n _ => \"Europe/Moscow\",\n }\n}\n\npub const NATIONALITIES: &[(&str, &str)] = &[\n (\"RU\", \"русская\"),\n (\"UA\", \"украинка\"),\n];\n\npub const NAMES_RU: &[&str] = &[\n \"Аня\", \"Алина\", \"Алёна\", \"Алиса\", \"Альбина\", \"Ася\",\n \"Арина\", \"Ангелина\", \"Настя\",\n \"Варя\", \"Лера\", \"Ника\", \"Вика\", \"Виолетта\", \"Виталина\",\n \"Даша\", \"Диана\", \"Дина\", \"Дарина\", \"Доминика\",\n \"Ева\", \"Женя\", \"Катя\", \"Лена\", \"Лиза\", \"Злата\",\n \"Ира\", \"Инна\", \"Ксюша\", \"Кира\", \"Кристина\", \"Карина\", \"Камилла\",\n \"Лиля\", \"Маша\", \"Марина\", \"Рита\", \"Милана\", \"Милена\", \"Мила\", \"Мира\", \"Майя\",\n \"Надя\", \"Наташа\", \"Настя\", \"Нина\",\n \"Оля\", \"Оксана\", \"Полина\", \"Поля\", \"Олеся\", \"Олена\",\n \"Соня\", \"София\", \"Стася\", \"Снежана\",\n \"Таня\", \"Тоня\",\n \"Ульяна\", \"Уля\",\n \"Юля\", \"Юлиана\",\n \"Яна\", \"Яся\",\n];\n\npub const NAMES_UA: &[&str] = &[\n \"Анна\", \"Альона\", \"Аліна\", \"Аліса\", \"Анастасія\",\n \"Богдана\", \"Валерія\", \"Вікторія\", \"Віолетта\",\n \"Дарина\", \"Діана\", \"Дарія\",\n \"Євгенія\", \"Єлизавета\",\n \"Злата\", \"Зоряна\",\n \"Ірина\", \"Інна\",\n \"Катерина\", \"Карина\", \"Кіра\", \"Ксенія\",\n \"Лілія\", \"Лариса\", \"Леся\",\n \"Марія\", \"Маріанна\", \"Мілана\", \"Мілена\",\n \"Надія\", \"Наталія\", \"Наталка\", \"Ніна\",\n \"Оксана\", \"Олена\", \"Олеся\", \"Ольга\",\n \"Поліна\", \"Перелесниця\",\n \"Світлана\", \"Софія\", \"Соломія\",\n \"Таїсія\", \"Тетяна\",\n \"Уляна\",\n \"Христина\",\n \"Юлія\", \"Юліана\",\n \"Яна\", \"Ярина\",\n];\n\npub fn pick_random_name(nationality: &str, seed: u64) -> &'static str {\n let pool: &[&str] = if nationality == \"UA\" { NAMES_UA } else { NAMES_RU };\n let idx = (seed as usize) % pool.len();\n pool[idx]\n}\n\n#[derive(Debug, Clone)]\npub struct SleepPreset {\n pub id: &'static str,\n pub label: &'static str,\n pub description: &'static str,\n pub from_h: u8,\n pub to_h: u8,\n pub wake_chance: f32,\n}\n\npub const SLEEP_PRESETS: &[SleepPreset] = &[\n SleepPreset { id: \"standard\", label: \"обычный\", description: \"00:00 — 09:00 · ~5% что разбудит сообщение\", from_h: 0, to_h: 9, wake_chance: 0.05 },\n SleepPreset { id: \"late\", label: \"сова\", description: \"02:00 — 11:00 · поздно ложится, поздно встаёт\", from_h: 2, to_h: 11, wake_chance: 0.05 },\n SleepPreset { id: \"early\", label: \"жаворонок\", description: \"22:00 — 07:00 · рано спать, рано на работу\", from_h: 22, to_h: 7, wake_chance: 0.04 },\n SleepPreset { id: \"owl\", label: \"не спит до утра\", description: \"04:00 — 13:00 · ночной образ жизни\", from_h: 4, to_h: 13, wake_chance: 0.08 },\n SleepPreset { id: \"custom\", label: \"кастом\", description: \"задай свой режим сна\", from_h: 0, to_h: 0, wake_chance: 0.0 },\n];\n\npub const PRIVACY_OPTIONS: &[(&str, &str, &str)] = &[\n (\"owner-only\", \"только владельцу\", \"отвечает только тебе. незнакомцам — игнор.\"),\n (\"allow-strangers\", \"всем, кто пишет\", \"отвечает кому угодно — нужно для bot-режима в группах.\"),\n];\n\npub fn slugify(name: &str) -> String {\n let mapped: String = name\n .chars()\n .map(|c| {\n if c.is_ascii_alphanumeric() {\n c.to_ascii_lowercase()\n } else {\n match c {\n 'а'..='я' | 'А'..='Я' | 'ё' | 'Ё' | 'і' | 'І' | 'ї' | 'Ї' | 'є' | 'Є' | 'ґ' | 'Ґ' => {\n translit_ru(c)\n }\n _ => '-',\n }\n }\n })\n .collect();\n let mut prev_dash = false;\n let mut out = String::new();\n for c in mapped.chars() {\n if c == '-' {\n if !prev_dash && !out.is_empty() {\n out.push('-');\n prev_dash = true;\n }\n } else {\n out.push(c);\n prev_dash = false;\n }\n }\n let trimmed = out.trim_matches('-').to_string();\n if trimmed.is_empty() {\n \"persona\".to_string()\n } else {\n trimmed\n }\n}\n\nfn translit_ru(c: char) -> char {\n let lower = c.to_lowercase().next().unwrap_or(c);\n match lower {\n 'а' => 'a', 'б' => 'b', 'в' => 'v', 'г' => 'g', 'д' => 'd', 'е' | 'ё' | 'є' => 'e',\n 'ж' => 'z', 'з' => 'z', 'и' | 'і' | 'ї' => 'i', 'й' => 'y', 'к' => 'k', 'л' => 'l',\n 'м' => 'm', 'н' => 'n', 'о' => 'o', 'п' => 'p', 'р' => 'r', 'с' => 's', 'т' => 't',\n 'у' => 'u', 'ф' => 'f', 'х' => 'h', 'ц' => 'c', 'ч' => 'c', 'ш' => 's', 'щ' => 's',\n 'ъ' | 'ь' => '-', 'ы' => 'y', 'э' => 'e', 'ю' => 'u', 'я' => 'a', 'ґ' => 'g',\n _ => '-',\n }\n}\n"}, "ef591b0d78be6247b14f130296ab78e8c1fcc518": {"t": 1785760729.7182252, "n": 20196, "h": "579facd84d2eb817", "c": "//! Static catalogues mirrored from the TS wizard.\n//!\n//! Kept in sync with `src/presets/llm.ts`, `src/presets/stages.ts`,\n//! `src/presets/communication.ts` and `src/data/timezones.ts`.\n\n#[derive(Debug, Clone)]\npub struct LlmPreset {\n pub id: &'static str,\n pub label: &'static str,\n pub proto: &'static str, // \"openai\" | \"anthropic\"\n pub base_url: Option<&'static str>,\n pub default_model: &'static str,\n pub default_api_key: Option<&'static str>,\n pub api_key_required: bool,\n pub custom: bool,\n pub models: &'static [&'static str],\n pub hint: &'static str,\n}\n\npub const LLM_PRESETS: &[LlmPreset] = &[\n LlmPreset {\n id: \"openai\",\n label: \"OpenAI\",\n proto: \"openai\",\n base_url: None,\n default_model: \"gpt-5.5\",\n default_api_key: None,\n api_key_required: true,\n custom: false,\n models: &[\n \"gpt-5.5\", \"gpt-5.5-thinking\", \"gpt-5.5-pro\",\n \"gpt-5.4\", \"gpt-5.4-pro\", \"gpt-5.4-thinking\",\n \"gpt-5.3-chat-latest\", \"gpt-5.4-mini\", \"gpt-5.4-nano\",\n \"gpt-4o\", \"gpt-4o-mini\", \"gpt-4.1\", \"gpt-4.1-mini\",\n ],\n hint: \"ChatGPT API · нужен ключ из platform.openai.com\",\n },\n LlmPreset {\n id: \"lmstudio\",\n label: \"LM Studio\",\n proto: \"openai\",\n base_url: Some(\"http://localhost:1234/v1\"),\n default_model: \"\",\n default_api_key: Some(\"lm-studio\"),\n api_key_required: false,\n custom: true,\n models: &[],\n hint: \"локально, OpenAI-совместимый эндпоинт; ключ не нужен\",\n },\n LlmPreset {\n id: \"ollama\",\n label: \"Ollama\",\n proto: \"openai\",\n base_url: Some(\"http://localhost:11434/v1\"),\n default_model: \"llama3.1\",\n default_api_key: Some(\"ollama\"),\n api_key_required: false,\n custom: true,\n models: &[],\n hint: \"локально через /v1; ключ не нужен\",\n },\n LlmPreset {\n id: \"anthropic\",\n label: \"Anthropic\",\n proto: \"anthropic\",\n base_url: None,\n default_model: \"claude-sonnet-4-6\",\n default_api_key: None,\n api_key_required: true,\n custom: false,\n models: &[\n \"claude-opus-4-7\", \"claude-sonnet-4-6\", \"claude-haiku-4-5-20251001\",\n \"claude-opus-4-6\", \"claude-sonnet-4-5\", \"claude-opus-4-1\",\n ],\n hint: \"Claude · нужен ключ из console.anthropic.com\",\n },\n LlmPreset {\n id: \"openrouter\",\n label: \"OpenRouter\",\n proto: \"openai\",\n base_url: Some(\"https://openrouter.ai/api/v1\"),\n default_model: \"openai/gpt-5.3-chat-latest\",\n default_api_key: None,\n api_key_required: true,\n custom: false,\n models: &[\n \"openai/gpt-5.3-chat-latest\", \"openai/gpt-5.5\",\n \"openai/gpt-5.5-thinking\", \"openai/gpt-5.5-pro\",\n \"anthropic/claude-sonnet-4.6\", \"anthropic/claude-opus-4.7\",\n \"google/gemini-3.1-pro\", \"deepseek/deepseek-v4-pro\", \"x-ai/grok-4.3\",\n ],\n hint: \"агрегатор моделей · openrouter.ai · приём в крипте\",\n },\n LlmPreset {\n id: \"groq\",\n label: \"Groq\",\n proto: \"openai\",\n base_url: Some(\"https://api.groq.com/openai/v1\"),\n default_model: \"llama-3.3-70b-versatile\",\n default_api_key: None,\n api_key_required: true,\n custom: false,\n models: &[\n \"llama-3.3-70b-versatile\", \"llama-3.1-8b-instant\",\n \"llama-4-scout-17b-16e-instruct\", \"qwen-3-32b\", \"mixtral-8x7b-32768\",\n ],\n hint: \"очень быстрый инференс на open-source моделях\",\n },\n LlmPreset {\n id: \"deepseek\",\n label: \"DeepSeek\",\n proto: \"openai\",\n base_url: Some(\"https://api.deepseek.com\"),\n default_model: \"deepseek-v4-flash\",\n default_api_key: None,\n api_key_required: true,\n custom: false,\n models: &[\"deepseek-v4-pro\", \"deepseek-v4-flash\", \"deepseek-chat\", \"deepseek-reasoner\"],\n hint: \"deepseek-chat/reasoner deprecated 2026-07-24, бери V4\",\n },\n LlmPreset {\n id: \"mistral\",\n label: \"Mistral\",\n proto: \"openai\",\n base_url: Some(\"https://api.mistral.ai/v1\"),\n default_model: \"mistral-large-2512\",\n default_api_key: None,\n api_key_required: true,\n custom: false,\n models: &[\n \"mistral-large-2512\", \"mistral-small-2603\",\n \"ministral-8b-2512\", \"ministral-14b-2512\",\n \"mistral-large-latest\", \"mistral-small-latest\",\n ],\n hint: \"французский провайдер, Le Chat и API\",\n },\n LlmPreset {\n id: \"google\",\n label: \"Google Gemini\",\n proto: \"openai\",\n base_url: Some(\"https://generativelanguage.googleapis.com/v1beta/openai\"),\n default_model: \"gemini-3.1-pro\",\n default_api_key: None,\n api_key_required: true,\n custom: false,\n models: &[\"gemini-3.1-pro\", \"gemini-3-flash\", \"gemini-3.1-flash-lite\", \"gemini-2.5-pro\", \"gemini-2.5-flash\"],\n hint: \"Gemini через OpenAI-совместимый эндпоинт\",\n },\n LlmPreset {\n id: \"xai\",\n label: \"xAI Grok\",\n proto: \"openai\",\n base_url: Some(\"https://api.x.ai/v1\"),\n default_model: \"grok-4.3\",\n default_api_key: None,\n api_key_required: true,\n custom: false,\n models: &[\"grok-4.3\", \"grok-4.20-reasoning\", \"grok-4.20-non-reasoning\", \"grok-4\", \"grok-3\", \"grok-3-mini\"],\n hint: \"Grok от xAI · ключ из console.x.ai\",\n },\n LlmPreset {\n id: \"together\",\n label: \"Together AI\",\n proto: \"openai\",\n base_url: Some(\"https://api.together.xyz/v1\"),\n default_model: \"meta-llama/Llama-3.3-70B-Instruct-Turbo\",\n default_api_key: None,\n api_key_required: true,\n custom: false,\n models: &[\n \"meta-llama/Llama-3.3-70B-Instruct-Turbo\",\n \"meta-llama/Llama-4-scout-17b-instruct\",\n \"Qwen/Qwen2.5-72B-Instruct-Turbo\",\n \"deepseek-ai/DeepSeek-V3\",\n ],\n hint: \"хостинг open-source моделей\",\n },\n LlmPreset {\n id: \"fireworks\",\n label: \"Fireworks\",\n proto: \"openai\",\n base_url: Some(\"https://api.fireworks.ai/inference/v1\"),\n default_model: \"accounts/fireworks/models/llama-v3p3-70b-instruct\",\n default_api_key: None,\n api_key_required: true,\n custom: false,\n models: &[\n \"accounts/fireworks/models/llama-v3p3-70b-instruct\",\n \"accounts/fireworks/models/llama-4-scout-17b-16e-instruct\",\n \"accounts/fireworks/models/qwen2p5-72b-instruct\",\n \"accounts/fireworks/models/deepseek-v3\",\n ],\n hint: \"хостинг open-source моделей\",\n },\n LlmPreset {\n id: \"perplexity\",\n label: \"Perplexity\",\n proto: \"openai\",\n base_url: Some(\"https://api.perplexity.ai\"),\n default_model: \"sonar-pro\",\n default_api_key: None,\n api_key_required: true,\n custom: false,\n models: &[\"sonar-pro\", \"sonar\", \"sonar-reasoning\"],\n hint: \"встроенный поиск в инференсе\",\n },\n LlmPreset {\n id: \"cerebras\",\n label: \"Cerebras\",\n proto: \"openai\",\n base_url: Some(\"https://api.cerebras.ai/v1\"),\n default_model: \"llama-3.3-70b\",\n default_api_key: None,\n api_key_required: true,\n custom: false,\n models: &[\"llama-3.3-70b\", \"llama-4-scout-17b-16e-instruct\", \"qwen-3-32b\"],\n hint: \"ультра-быстрый инференс на чипах cerebras\",\n },\n LlmPreset {\n id: \"claudehub\",\n label: \"ClaudeHub\",\n proto: \"anthropic\",\n base_url: Some(\"https://api.claudehub.fun\"),\n default_model: \"claude-sonnet-4.6\",\n default_api_key: None,\n api_key_required: true,\n custom: false,\n models: &[\n \"claude-opus-4.7\", \"claude-opus-4.6\", \"claude-opus-4.5\",\n \"claude-sonnet-4.6\", \"claude-sonnet-4.5\", \"claude-haiku-4.5\",\n \"gpt-5.5\", \"gpt-5.4\",\n ],\n hint: \"прокси для Anthropic / OpenAI · РФ, СБП, крипта\",\n },\n LlmPreset {\n id: \"custom-openai\",\n label: \"Custom (OpenAI-compatible)\",\n proto: \"openai\",\n base_url: None,\n default_model: \"\",\n default_api_key: None,\n api_key_required: false,\n custom: true,\n models: &[],\n hint: \"укажи свой base URL и модель\",\n },\n LlmPreset {\n id: \"custom-anthropic\",\n label: \"Custom (Anthropic-compatible)\",\n proto: \"anthropic\",\n base_url: None,\n default_model: \"\",\n default_api_key: None,\n api_key_required: false,\n custom: true,\n models: &[],\n hint: \"укажи свой base URL и модель\",\n },\n];\n\npub fn find_llm_preset(id: &str) -> Option<&'static LlmPreset> {\n LLM_PRESETS.iter().find(|p| p.id == id)\n}\n\n#[derive(Debug, Clone)]\npub struct StagePreset {\n pub id: &'static str,\n pub label: &'static str,\n pub description: &'static str,\n}\n\npub const STAGE_PRESETS: &[StagePreset] = &[\n StagePreset { id: \"met-irl-got-tg\", label: \"встретились в реале — дала тг\", description: \"только что обменялись тг. помнит лицо, голос. лёгкий интерес.\" },\n StagePreset { id: \"tg-given-cold\", label: \"дала тг, но не убедил отвечать\", description: \"сомневается. часто игнорит, отвечает односложно. нужно добиваться.\" },\n StagePreset { id: \"tg-given-warming\", label: \"дала тг, отвечает осторожно\", description: \"оттаивает. отвечает, но коротко. тестит тебя.\" },\n StagePreset { id: \"convinced\", label: \"убедил отвечать стабильно\", description: \"общаетесь регулярно, флиртует, ещё не виделись после знакомства.\" },\n StagePreset { id: \"first-date-done\", label: \"сходили один раз\", description: \"первое свидание было, в подвешенном состоянии — нравится, но не пара.\" },\n StagePreset { id: \"dating-early\", label: \"только начали встречаться\", description: \"около месяца вместе. бабочки, всё внове, но границы ещё хрупкие.\" },\n StagePreset { id: \"dating-stable\", label: \"пара, общаетесь свободно\", description: \"стабильные отношения, шутки, бытовуха, доверие.\" },\n StagePreset { id: \"long-term\", label: \"давно вместе\", description: \"год+ вместе. иногда раздражение, рутина, глубокое доверие.\" },\n];\n\n#[derive(Debug, Clone)]\npub struct CommunicationPreset {\n pub id: &'static str,\n pub label: &'static str,\n pub description: &'static str,\n pub notifications: &'static str, // muted|normal|priority\n pub message_style: &'static str, // one-liners|balanced|bursty|longform\n pub initiative: &'static str, // low|medium|high\n pub life_sharing: &'static str, // low|medium|high\n}\n\npub const COMMUNICATION_PRESETS: &[CommunicationPreset] = &[\n CommunicationPreset {\n id: \"normal\", label: \"нормальная\",\n description: \"золотая середина — отвечает нормально, не липнет, иногда сама пишет\",\n notifications: \"normal\", message_style: \"balanced\", initiative: \"medium\", life_sharing: \"medium\",\n },\n CommunicationPreset {\n id: \"cute\", label: \"милая\",\n description: \"тёплая и заботливая, часто отвечает, пишет первой, делится моментами\",\n notifications: \"priority\", message_style: \"balanced\", initiative: \"high\", life_sharing: \"high\",\n },\n CommunicationPreset {\n id: \"alt\", label: \"альтушка\",\n description: \"холодная, сухая, короткие ответы, почти не пишет первой, личным не делится\",\n notifications: \"normal\", message_style: \"one-liners\", initiative: \"low\", life_sharing: \"low\",\n },\n CommunicationPreset {\n id: \"clingy\", label: \"залипала\",\n description: \"очень липучая, спамит пузырями, всегда онлайн, пишет первой постоянно\",\n notifications: \"priority\", message_style: \"bursty\", initiative: \"high\", life_sharing: \"high\",\n },\n CommunicationPreset {\n id: \"chatty\", label: \"болтушка\",\n description: \"любит рассказывать истории, пишет длинные тексты, часто делится бытовым\",\n notifications: \"priority\", message_style: \"longform\", initiative: \"medium\", life_sharing: \"high\",\n },\n];\n\n#[derive(Debug, Clone)]\npub struct TzEntry {\n pub iana: &'static str,\n pub gmt_winter: &'static str,\n pub city: &'static str,\n pub country: &'static str,\n pub aliases: &'static [&'static str],\n}\n\npub const TIMEZONES: &[TzEntry] = &[\n TzEntry { iana: \"Europe/Kaliningrad\", gmt_winter: \"GMT+2\", city: \"Калининград\", country: \"Россия\", aliases: &[\"калининград\", \"kaliningrad\", \"rus\"] },\n TzEntry { iana: \"Europe/Moscow\", gmt_winter: \"GMT+3\", city: \"Москва\", country: \"Россия\", aliases: &[\"москва\", \"msk\", \"moscow\", \"питер\", \"санкт-петербург\", \"spb\", \"rus\"] },\n TzEntry { iana: \"Europe/Samara\", gmt_winter: \"GMT+4\", city: \"Самара\", country: \"Россия\", aliases: &[\"самара\", \"samara\", \"ижевск\"] },\n TzEntry { iana: \"Asia/Yekaterinburg\", gmt_winter: \"GMT+5\", city: \"Екатеринбург\", country: \"Россия\", aliases: &[\"екб\", \"yekaterinburg\", \"пермь\", \"уфа\", \"челябинск\"] },\n TzEntry { iana: \"Asia/Omsk\", gmt_winter: \"GMT+6\", city: \"Омск\", country: \"Россия\", aliases: &[\"омск\", \"omsk\"] },\n TzEntry { iana: \"Asia/Novosibirsk\", gmt_winter: \"GMT+7\", city: \"Новосибирск\", country: \"Россия\", aliases: &[\"нск\", \"новосибирск\", \"novosibirsk\", \"томск\", \"красноярск\"] },\n TzEntry { iana: \"Asia/Irkutsk\", gmt_winter: \"GMT+8\", city: \"Иркутск\", country: \"Россия\", aliases: &[\"иркутск\", \"irkutsk\", \"улан-удэ\"] },\n TzEntry { iana: \"Asia/Yakutsk\", gmt_winter: \"GMT+9\", city: \"Якутск\", country: \"Россия\", aliases: &[\"якутск\", \"yakutsk\", \"чита\"] },\n TzEntry { iana: \"Asia/Vladivostok\", gmt_winter: \"GMT+10\", city: \"Владивосток\", country: \"Россия\", aliases: &[\"владивосток\", \"vladivostok\", \"хабаровск\"] },\n TzEntry { iana: \"Asia/Magadan\", gmt_winter: \"GMT+11\", city: \"Магадан\", country: \"Россия\", aliases: &[\"магадан\", \"magadan\", \"сахалин\"] },\n TzEntry { iana: \"Asia/Kamchatka\", gmt_winter: \"GMT+12\", city: \"Камчатка\", country: \"Россия\", aliases: &[\"камчатка\", \"kamchatka\", \"петропавловск\"] },\n TzEntry { iana: \"Europe/Kyiv\", gmt_winter: \"GMT+2\", city: \"Київ\", country: \"Україна\", aliases: &[\"киев\", \"київ\", \"kyiv\", \"kiev\", \"ua\", \"украина\", \"україна\", \"львов\", \"львів\", \"одесса\", \"одеса\", \"харьков\", \"харків\"] },\n TzEntry { iana: \"Europe/Minsk\", gmt_winter: \"GMT+3\", city: \"Минск\", country: \"Беларусь\", aliases: &[\"минск\", \"minsk\", \"бел\", \"беларусь\", \"by\"] },\n TzEntry { iana: \"Asia/Almaty\", gmt_winter: \"GMT+5\", city: \"Алматы\", country: \"Казахстан\", aliases: &[\"алматы\", \"almaty\", \"kz\", \"казахстан\", \"астана\", \"нур-султан\"] },\n TzEntry { iana: \"Asia/Tashkent\", gmt_winter: \"GMT+5\", city: \"Ташкент\", country: \"Узбекистан\", aliases: &[\"ташкент\", \"tashkent\", \"uz\", \"узбекистан\"] },\n TzEntry { iana: \"Asia/Bishkek\", gmt_winter: \"GMT+6\", city: \"Бишкек\", country: \"Кыргызстан\", aliases: &[\"бишкек\", \"bishkek\", \"kg\"] },\n TzEntry { iana: \"Asia/Tbilisi\", gmt_winter: \"GMT+4\", city: \"Тбилиси\", country: \"Грузия\", aliases: &[\"тбилиси\", \"tbilisi\", \"ge\", \"грузия\"] },\n TzEntry { iana: \"Asia/Yerevan\", gmt_winter: \"GMT+4\", city: \"Ереван\", country: \"Армения\", aliases: &[\"ереван\", \"yerevan\", \"am\", \"армения\"] },\n TzEntry { iana: \"Asia/Baku\", gmt_winter: \"GMT+4\", city: \"Баку\", country: \"Азербайджан\", aliases: &[\"баку\", \"baku\", \"az\", \"азербайджан\"] },\n];\n\npub fn search_tz(query: &str) -> Vec<&'static TzEntry> {\n let q = query.trim().to_lowercase();\n if q.is_empty() {\n return TIMEZONES.iter().collect();\n }\n TIMEZONES\n .iter()\n .filter(|tz| {\n tz.iana.to_lowercase().contains(&q)\n || tz.city.to_lowercase().contains(&q)\n || tz.country.to_lowercase().contains(&q)\n || tz.aliases.iter().any(|a| a.contains(&q))\n })\n .collect()\n}\n\npub fn default_tz_for_nationality(nationality: &str) -> &'static str {\n match nationality {\n \"UA\" => \"Europe/Kyiv\",\n _ => \"Europe/Moscow\",\n }\n}\n\npub const NATIONALITIES: &[(&str, &str)] = &[\n (\"RU\", \"русская\"),\n (\"UA\", \"украинка\"),\n];\n\npub const NAMES_RU: &[&str] = &[\n \"Аня\", \"Алина\", \"Алёна\", \"Алиса\", \"Альбина\", \"Ася\",\n \"Арина\", \"Ангелина\", \"Настя\",\n \"Варя\", \"Лера\", \"Ника\", \"Вика\", \"Виолетта\", \"Виталина\",\n \"Даша\", \"Диана\", \"Дина\", \"Дарина\", \"Доминика\",\n \"Ева\", \"Женя\", \"Катя\", \"Лена\", \"Лиза\", \"Злата\",\n \"Ира\", \"Инна\", \"Ксюша\", \"Кира\", \"Кристина\", \"Карина\", \"Камилла\",\n \"Лиля\", \"Маша\", \"Марина\", \"Рита\", \"Милана\", \"Милена\", \"Мила\", \"Мира\", \"Майя\",\n \"Надя\", \"Наташа\", \"Настя\", \"Нина\",\n \"Оля\", \"Оксана\", \"Полина\", \"Поля\", \"Олеся\", \"Олена\",\n \"Соня\", \"София\", \"Стася\", \"Снежана\",\n \"Таня\", \"Тоня\",\n \"Ульяна\", \"Уля\",\n \"Юля\", \"Юлиана\",\n \"Яна\", \"Яся\",\n];\n\npub const NAMES_UA: &[&str] = &[\n \"Анна\", \"Альона\", \"Аліна\", \"Аліса\", \"Анастасія\",\n \"Богдана\", \"Валерія\", \"Вікторія\", \"Віолетта\",\n \"Дарина\", \"Діана\", \"Дарія\",\n \"Євгенія\", \"Єлизавета\",\n \"Злата\", \"Зоряна\",\n \"Ірина\", \"Інна\",\n \"Катерина\", \"Карина\", \"Кіра\", \"Ксенія\",\n \"Лілія\", \"Лариса\", \"Леся\",\n \"Марія\", \"Маріанна\", \"Мілана\", \"Мілена\",\n \"Надія\", \"Наталія\", \"Наталка\", \"Ніна\",\n \"Оксана\", \"Олена\", \"Олеся\", \"Ольга\",\n \"Поліна\", \"Перелесниця\",\n \"Світлана\", \"Софія\", \"Соломія\",\n \"Таїсія\", \"Тетяна\",\n \"Уляна\",\n \"Христина\",\n \"Юлія\", \"Юліана\",\n \"Яна\", \"Ярина\",\n];\n\npub fn pick_random_name(nationality: &str, seed: u64) -> &'static str {\n let pool: &[&str] = if nationality == \"UA\" { NAMES_UA } else { NAMES_RU };\n let idx = (seed as usize) % pool.len();\n pool[idx]\n}\n\n#[derive(Debug, Clone)]\npub struct SleepPreset {\n pub id: &'static str,\n pub label: &'static str,\n pub description: &'static str,\n pub from_h: u8,\n pub to_h: u8,\n pub wake_chance: f32,\n}\n\npub const SLEEP_PRESETS: &[SleepPreset] = &[\n SleepPreset { id: \"standard\", label: \"обычный\", description: \"00:00 — 09:00 · ~5% что разбудит сообщение\", from_h: 0, to_h: 9, wake_chance: 0.05 },\n SleepPreset { id: \"late\", label: \"сова\", description: \"02:00 — 11:00 · поздно ложится, поздно встаёт\", from_h: 2, to_h: 11, wake_chance: 0.05 },\n SleepPreset { id: \"early\", label: \"жаворонок\", description: \"22:00 — 07:00 · рано спать, рано на работу\", from_h: 22, to_h: 7, wake_chance: 0.04 },\n SleepPreset { id: \"owl\", label: \"не спит до утра\", description: \"04:00 — 13:00 · ночной образ жизни\", from_h: 4, to_h: 13, wake_chance: 0.08 },\n];\n\npub const PRIVACY_OPTIONS: &[(&str, &str, &str)] = &[\n (\"owner-only\", \"только владельцу\", \"отвечает только тебе. незнакомцам — игнор.\"),\n (\"allow-strangers\", \"всем, кто пишет\", \"отвечает кому угодно — нужно для bot-режима в группах.\"),\n];\n\npub fn slugify(name: &str) -> String {\n let mapped: String = name\n .chars()\n .map(|c| {\n if c.is_ascii_alphanumeric() {\n c.to_ascii_lowercase()\n } else {\n match c {\n 'а'..='я' | 'А'..='Я' | 'ё' | 'Ё' | 'і' | 'І' | 'ї' | 'Ї' | 'є' | 'Є' | 'ґ' | 'Ґ' => {\n translit_ru(c)\n }\n _ => '-',\n }\n }\n })\n .collect();\n let mut prev_dash = false;\n let mut out = String::new();\n for c in mapped.chars() {\n if c == '-' {\n if !prev_dash && !out.is_empty() {\n out.push('-');\n prev_dash = true;\n }\n } else {\n out.push(c);\n prev_dash = false;\n }\n }\n let trimmed = out.trim_matches('-').to_string();\n if trimmed.is_empty() {\n \"persona\".to_string()\n } else {\n trimmed\n }\n}\n\nfn translit_ru(c: char) -> char {\n let lower = c.to_lowercase().next().unwrap_or(c);\n match lower {\n 'а' => 'a', 'б' => 'b', 'в' => 'v', 'г' => 'g', 'д' => 'd', 'е' | 'ё' | 'є' => 'e',\n 'ж' => 'z', 'з' => 'z', 'и' | 'і' | 'ї' => 'i', 'й' => 'y', 'к' => 'k', 'л' => 'l',\n 'м' => 'm', 'н' => 'n', 'о' => 'o', 'п' => 'p', 'р' => 'r', 'с' => 's', 'т' => 't',\n 'у' => 'u', 'ф' => 'f', 'х' => 'h', 'ц' => 'c', 'ч' => 'c', 'ш' => 's', 'щ' => 's',\n 'ъ' | 'ь' => '-', 'ы' => 'y', 'э' => 'e', 'ю' => 'u', 'я' => 'a', 'ґ' => 'g',\n _ => '-',\n }\n}\n"}, "350ff3cb5679ef5b7801650168ba915e1914c12d": {"t": 1785760731.9239786, "n": 3060, "h": "eb7d5ddb5be987e2", "c": "//! Hard-coded option lists mirrored from `src/presets/*.ts`.\n//!\n//! The installer doesn't need the full preset metadata — only enough to show\n//! a label in a dropdown and emit the right `presetId` into the config file.\n//! When the bot starts, it looks the preset up by id and pulls in the rest.\n\n#[derive(Debug, Clone, Copy)]\npub struct Choice {\n pub id: &'static str,\n pub label: &'static str,\n pub hint: &'static str,\n}\n\npub const LLM_PRESETS: &[Choice] = &[\n Choice { id: \"openai\", label: \"OpenAI\", hint: \"ChatGPT API · нужен ключ\" },\n Choice { id: \"anthropic\", label: \"Anthropic\", hint: \"Claude · нужен ключ\" },\n Choice { id: \"openrouter\", label: \"OpenRouter\", hint: \"много моделей одним ключом\" },\n Choice { id: \"deepseek\", label: \"DeepSeek\", hint: \"дёшево, нужен ключ\" },\n Choice { id: \"groq\", label: \"Groq\", hint: \"быстро, нужен ключ\" },\n Choice { id: \"google\", label: \"Google AI\", hint: \"Gemini · нужен ключ\" },\n Choice { id: \"xai\", label: \"xAI (Grok)\", hint: \"нужен ключ\" },\n Choice { id: \"ollama\", label: \"Ollama (локально)\", hint: \"ключ не нужен\" },\n Choice { id: \"lmstudio\", label: \"LM Studio (локально)\", hint: \"ключ не нужен\" },\n];\n\npub const STAGES: &[Choice] = &[\n Choice { id: \"met-irl-got-tg\", label: \"встретились ирл, дала тг\", hint: \"самый ранний этап\" },\n Choice { id: \"tg-given-cold\", label: \"тг есть, ещё холодно\", hint: \"пишет нечасто, осторожна\" },\n Choice { id: \"tg-given-warming\", label: \"тг, теплеет\", hint: \"интерес растёт\" },\n Choice { id: \"convinced\", label: \"уговорил/а\", hint: \"согласилась встретиться\" },\n Choice { id: \"first-date-done\", label: \"первое свидание было\", hint: \"увиделись\" },\n Choice { id: \"dating-early\", label: \"встречаемся, ранний этап\", hint: \"несколько недель\" },\n Choice { id: \"dating-stable\", label: \"стабильные отношения\", hint: \"\" },\n Choice { id: \"long-term\", label: \"долгие отношения\", hint: \"\" },\n];\n\npub const COMMUNICATION: &[Choice] = &[\n Choice { id: \"normal\", label: \"обычное\", hint: \"среднее по больнице\" },\n Choice { id: \"cute\", label: \"милая\", hint: \"мягкая, нежная\" },\n Choice { id: \"alt\", label: \"альт\", hint: \"сухо, без лишнего\" },\n Choice { id: \"clingy\", label: \"прилипчивая\", hint: \"пишет первой\" },\n Choice { id: \"chatty\", label: \"болтливая\", hint: \"длинные сообщения\" },\n];\n\npub const MODES: &[Choice] = &[\n Choice { id: \"bot\", label: \"бот\", hint: \"Telegram Bot API, проще\" },\n Choice { id: \"userbot\", label: \"юзербот\", hint: \"MTProto, нужны api id/hash\" },\n];\n\npub fn slugify(name: &str) -> String {\n let lower: String = name.trim().to_lowercase();\n let mut out = String::new();\n let mut prev_dash = false;\n for ch in lower.chars() {\n if ch.is_ascii_alphanumeric() {\n out.push(ch);\n prev_dash = false;\n } else if !out.is_empty() && !prev_dash {\n out.push('-');\n prev_dash = true;\n }\n }\n while out.ends_with('-') {\n out.pop();\n }\n if out.is_empty() {\n out = \"persona\".to_string();\n }\n out\n}\n"}}}}