1 line
80 KiB
JSON
1 line
80 KiB
JSON
{"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"}}}} |