Нормализовать переводы строк в LF
Решение по TD-STYLE-ANALYZERS: LF — инструменты проекта (Python/Node) пишут LF, CRLF-.sh не работают на Linux CI (sh scripts/ci.sh), большинство файлов уже были LF. Добавлен .gitattributes (* text=auto eol=lf, бинарные исключения), .editorconfig переведён на lf, 1029 файлов конвертированы, git add --renormalize. Из индекса убраны закравшиеся archive/**/__pycache__/*.pyc.
This commit is contained in:
@@ -1,253 +1,253 @@
|
||||
"""Базовые константы: палитры, стадии, промпты, валюты, провайдеры AI.
|
||||
|
||||
Значения согласованы с фронтенд-прототипом (frontend/src/data.js),
|
||||
чтобы интеграция была безболезненной.
|
||||
"""
|
||||
|
||||
# Палитра колонок-досок (по умолчанию)
|
||||
PALETTE = ["#818cf8", "#fbbf24", "#22d3ee", "#e879f9", "#34d399", "#fb7185", "#a78bfa", "#f97316"]
|
||||
|
||||
# Колонки не создаются по умолчанию: их создаёт пользователь или предлагает
|
||||
# ИИ (suggested=TRUE — ждёт решения пользователя).
|
||||
|
||||
# Служебные колонки дашборда
|
||||
SERVICE_COLS = {"inbox", "archive", "trash", "taken"}
|
||||
|
||||
# Стадии канбана «Выбранных»
|
||||
PIPELINE_STAGES = [
|
||||
{"id": "planned", "name": "Запланировано", "color": "#818cf8", "terminal": False},
|
||||
{"id": "reply", "name": "Отклик", "color": "#38bdf8", "terminal": False},
|
||||
{"id": "agree", "name": "Согласование", "color": "#a78bfa", "terminal": False},
|
||||
{"id": "work", "name": "В работе", "color": "#fbbf24", "terminal": False},
|
||||
{"id": "review", "name": "Проверка", "color": "#f97316", "terminal": False},
|
||||
{"id": "ready", "name": "Готово", "color": "#4ade80", "terminal": False},
|
||||
{"id": "hold", "name": "Отложено", "color": "#94a3b8", "terminal": False},
|
||||
{"id": "finished", "name": "Выполнено", "color": "#2bd576", "terminal": True},
|
||||
{"id": "rejected", "name": "Отклонено", "color": "#ff6b6b", "terminal": True},
|
||||
]
|
||||
|
||||
# Валюты и мок-курсы (до первого успешного запроса к ЦБ)
|
||||
CURRENCIES = [
|
||||
{"code": "RUB", "name": "Российский рубль", "symbol": "₽"},
|
||||
{"code": "USD", "name": "Доллар США", "symbol": "$"},
|
||||
{"code": "EUR", "name": "Евро", "symbol": "€"},
|
||||
{"code": "CNY", "name": "Китайский юань", "symbol": "¥"},
|
||||
{"code": "KZT", "name": "Казахстанский тенге", "symbol": "₸"},
|
||||
{"code": "BYN", "name": "Белорусский рубль", "symbol": "Br"},
|
||||
{"code": "USDT", "name": "Tether (USDT)", "symbol": "₮"},
|
||||
{"code": "GBP", "name": "Британский фунт", "symbol": "£"},
|
||||
]
|
||||
|
||||
MOCK_RATES = {
|
||||
"RUB": 1.0,
|
||||
"USD": 92.5,
|
||||
"EUR": 99.9,
|
||||
"CNY": 13.1,
|
||||
"KZT": 0.19,
|
||||
"BYN": 28.6,
|
||||
"USDT": 92.5,
|
||||
"GBP": 117.4,
|
||||
}
|
||||
|
||||
CBR_URL = "https://www.cbr-xml-daily.ru/daily_json.js"
|
||||
|
||||
# Стоп-фразы по умолчанию (этап 1 фильтра — без ИИ)
|
||||
DEFAULT_STOP_PHRASES = ["взаимный пиар", "резюме", "ищу работу", "набор в команду"]
|
||||
|
||||
DEFAULT_MIN_LEN = 24
|
||||
|
||||
# Промпты. В тексте можно использовать плейсхолдеры, которые подставляются из
|
||||
# настроек «Сферы и ключей» при каждом вызове ИИ:
|
||||
# {domain} — domainDescription (что для вас заявка/лид, ваша сфера)
|
||||
# {keywords} — domainKeywords (общие слова-маркеры заявок)
|
||||
DEFAULT_AI_PROMPT = (
|
||||
"Ты — классификатор входящих сообщений. Сообщение — ЗАЯВКА (лид), только если в нём есть конкретный"
|
||||
" запрос или предложение по делу: заказ услуги/товара/работы, поиск исполнителя или найм человека."
|
||||
" Направление вашей сферы:\n{domain}\n\n"
|
||||
"Частые слова-маркеры заявок в вашей сфере: {keywords}\n\n"
|
||||
"Определи по тексту:\n"
|
||||
"1. is_spam — true, если это НЕ заявка: служебные сообщения (коды входа/подтверждения, уведомления),"
|
||||
" приветствия и поздравления, флуд и обсуждения без задачи, вопросы без конкретики, реклама, скам,"
|
||||
" фин. пирамиды, резюме соискателей, взаимный пиар, приглашения в чаты, рассылки."
|
||||
" Если сомневаешься — ставь true (лучше пропустить сообщение, чем засорить карточками)\n"
|
||||
"2. board — id подходящей доски из списка. Выбирай по КРИТЕРИЯМ колонки"
|
||||
" (в скобках указаны её направление, тематика, ключевые слова, уровень, бюджет, описание), а не только по названию;"
|
||||
" если ни одна колонка не подходит под текст — верни null (карточка пойдёт в «Неразобранное»)\n"
|
||||
"3. is_vacancy — true, если это НАЙМ/постоянная или проектная занятость: ищут человека в команду"
|
||||
" (признаки: вакансия, грейд/уровень, «в компанию», hr, зарплата за месяц);"
|
||||
" false — разовая сделка: заказ/услуга/товар (фриланс, подряд, «нужно сделать/купить», цена за работу)\n"
|
||||
"4. title — короткий заголовок (4–9 слов, без эмодзи, хэштегов и знаков препинания в конце)."
|
||||
" Запрещено: markdown, ссылки в любом виде ([текст](url), голые url) — только чистый текст\n"
|
||||
"5. company — кто разместил заявку: компания/бренд/частное лицо/заказчик. Только факт из текста;"
|
||||
" не указано — верни пустую строку \"\"\n"
|
||||
"6. format — формат работы/выполнения: удалённо/офис/гибрид, город, график. Коротко; нет — \"\"\n"
|
||||
"7. task — 1–2 предложения: что за задача/роль и в чём суть (для кого, что нужно сделать)."
|
||||
" Ёмко, без пересказа всего объявления и без служебных строк\n"
|
||||
"8. requirements — JSON-массив строк ключевых требований к кандидату/исполнителю"
|
||||
" (что нужно уметь/иметь, пункты «Требования/Обязанности/Нужно»). Нет — []\n"
|
||||
"9. plus — JSON-массив строк «будет плюсом»/«приветствуется»/«желательно». Нет — []\n"
|
||||
"10. conditions — условия одной строкой: оплата/ЗП/вилка, сроки, объём, тип занятости. Нет — \"\"\n"
|
||||
"11. stack — JSON-массив строк (не строка!): технологии/предметы/услуги/материалы из заявки."
|
||||
" Названия пиши слитно как в оригинале: \".NET\", \"C#\", \"Node.js\", \"ASP.NET Core\" — не разбивай на отдельные буквы (2–8)\n"
|
||||
"12. budget — объект { from, to, currency }: одна сумма → from=to=X; «до X» → from=null, to=X;"
|
||||
" диапазон «от X до Y» → from=X, to=Y. Если суммы нет — null\n"
|
||||
"13. contacts — JSON-массив строк контактов ДЛЯ СВЯЗИ: телефон, email, @username (не бот),"
|
||||
" ссылка на профиль человека (LinkedIn, t.me/…). НЕ включай ботов, каналы/группы и ссылки на"
|
||||
" вакансию/пост/форму. Если контакта в тексте нет — пустой массив []\n"
|
||||
"Поля 5–10 — это блок «О заявке» на карточке: заполняй их только фактами из текста, ничего не выдумывай."
|
||||
" Запрещено: markdown-разметка, ссылки в любом виде, эмодзи, хэштеги, «от»/«привет», дословное копирование исходника.\n"
|
||||
"Отвечай строго в формате JSON."
|
||||
)
|
||||
|
||||
DEFAULT_AI_FILTER_PROMPT = (
|
||||
"Ты — страж входящих сообщений каналов. Пропускай только реальные заявки/лиды по вашей сфере"
|
||||
" (заказ, услуга, товар, найм — с конкретикой).\n"
|
||||
"Ваша сфера и что считать заявкой:\n{domain}\n\n"
|
||||
"Слова-маркеры заявок: {keywords}\n\n"
|
||||
"НЕ пропускай:\n"
|
||||
"- служебные сообщения: коды входа/подтверждения, уведомления, приветствия, поздравления\n"
|
||||
"- просто сообщения без задачи и конкретики: флуд, обсуждения, вопросы «кто работал с …»\n"
|
||||
"- рекламу и саморекламу\n"
|
||||
"- скам, фин. пирамиды, «заработок»\n"
|
||||
"- резюме, поиск работы соискателями\n"
|
||||
"- взаимный пиар, приглашения в чаты\n"
|
||||
"- рассылки и дайджесты без прямых заявок\n"
|
||||
"При сомнении — не пропускай.\n"
|
||||
'Верни строго JSON: { "pass": true|false, "reason": "причина отказа или null" }'
|
||||
)
|
||||
|
||||
# Промпт структуры карточки (блок «О заявке»). Отдельный от классификатора:
|
||||
# задаёт, какие поля и как заполнять, чтобы все карточки имели одинаковую
|
||||
# структуру текста (разной длины). Добавляется к промпту классификатора
|
||||
# отдельным блоком (см. services/ai.classify). Редактируется в UI.
|
||||
DEFAULT_AI_CARD_PROMPT = (
|
||||
"Ты также возвращаешь содержимое блока «О заявке» карточки — поля company, format,"
|
||||
" task, requirements, plus, conditions. Карточка всегда собирается из одних и тех же блоков:"
|
||||
" одинаковая структура у всех карточек, различается только длина.\n"
|
||||
"1. company — кто ищет/разместил: компания, бренд, агентство, частное лицо, заказчик. Одно предложение, факт из текста.\n"
|
||||
"2. format — формат работы: удалённо/офис/гибрид/разъездной, город/страна, график (5/2, full-time, part-time). Коротко.\n"
|
||||
"3. task — 1–2 предложения по шаблону: что за задача/роль → для кого → что нужно сделать/какой результат."
|
||||
" Пиши ёмко, не пересказывай объявление дословно.\n"
|
||||
"4. requirements — вынеси сюда только реальные требования/обязанности из текста"
|
||||
" (пункты списков «Требования», «Обязанности», «Что предстоит делать», «Нужно»):"
|
||||
" каждый пункт короткой строкой в JSON-массиве. Не придумывай сверх текста.\n"
|
||||
"5. plus — только то, что отмечено как «будет плюсом»/«приветствуется»/«желательно»."
|
||||
" Нет таких пунктов — пустой массив [].\n"
|
||||
"6. conditions — условия одной строкой: оплата/ЗП/вилка/гонорар, сроки, объём, тип занятости."
|
||||
" Не дублируй budget-числа в других полях.\n"
|
||||
"Для не-IT сфер «стек/требования» означают материалы, услуги, навыки, инструменты — по смыслу заявки."
|
||||
" Запрещено во всех полях: markdown-разметка, ссылки в любом виде ([текст](url), голые url),"
|
||||
" эмодзи, хэштеги, «от»/«привет»/служебные строки канала."
|
||||
)
|
||||
|
||||
# Дефолтные маркеры локального разбора (настраиваются в UI «Сфера и ключи»).
|
||||
DEFAULT_HIRE_MARKERS = [
|
||||
"вакансия", "вакансию", "вакансии", "вакантна", "вакант", "нанимаем", "найм", "full-time",
|
||||
"на постоянную", "занятость", "в офис", "официальное оформление", "пятидневка",
|
||||
"грейд", "в компанию", "полная занятость", "на постоянную работу", "в команду", "нанимает",
|
||||
"на постоянную основу", "в штат",
|
||||
]
|
||||
|
||||
DEFAULT_LEVEL_TERMS = [
|
||||
"junior", "джун", "джуниор", "middle", "мидл", "mid", "senior", "сеньор", "сеньйор",
|
||||
"lead", "тимлид", "тиэмлид", "architect", "архитектор", "стажёр", "стажер", "intern", "trainee",
|
||||
]
|
||||
|
||||
# Маркеры, по которым сообщение опознаётся как резюме/самопрезентация
|
||||
# соискателя («ищу работу», «моё резюме»). По умолчанию такие сообщения
|
||||
# отсекаются на этапе 1 (до ИИ и правил колонок), если пользователь ищет
|
||||
# вакансии и заказы, а не кандидатов. Редактируется в «Сфере и ключах»;
|
||||
# выключите отсев — резюме начнут собираться как обычные лиды.
|
||||
# Слово «резюме» имеет контекстный guard (см. pipeline._resume_reason):
|
||||
# «…вакансия…, присылайте резюме» — это объявление работодателя, его не режем.
|
||||
DEFAULT_RESUME_MARKERS = [
|
||||
"резюме", "#резюме", "моё резюме", "мое резюме", "ищу работу", "ищу вакансию",
|
||||
"рассмотрю предложения", "готов к собеседованию", "в поиске работы",
|
||||
"ищу проект", "ищу подработку",
|
||||
]
|
||||
|
||||
# AI-провайдеры (активен один)
|
||||
AI_PROVIDERS = [
|
||||
{"id": "deepseek", "name": "DeepSeek", "base": "https://api.deepseek.com", "local": False,
|
||||
"models": ["deepseek-v4-flash", "deepseek-v4-pro", "deepseek-v4-flash-vision-exp"]},
|
||||
{"id": "openai", "name": "OpenAI", "base": "https://api.openai.com/v1", "local": False,
|
||||
"models": ["gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna"]},
|
||||
{"id": "openrouter", "name": "OpenRouter", "base": "https://openrouter.ai/api/v1", "local": False,
|
||||
"models": ["deepseek/deepseek-v4-flash", "anthropic/claude-sonnet-5", "openai/gpt-5.6-luna",
|
||||
"google/gemini-3.8-flash", "qwen/qwen3.8-flash"]},
|
||||
{"id": "anthropic", "name": "Anthropic Claude", "base": "https://api.anthropic.com", "local": False,
|
||||
"api_style": "anthropic",
|
||||
"models": ["claude-sonnet-5", "claude-opus-5", "claude-fable-5-1", "claude-haiku-4-5-20251001"]},
|
||||
{"id": "ollama", "name": "Ollama (локально)", "base": "http://localhost:11434/v1", "local": True,
|
||||
"models": ["qwen3-coder:30b", "qwen3-coder:480b", "qwen3:32b"]},
|
||||
{"id": "lmstudio", "name": "LM Studio (локально)", "base": "http://localhost:1234/v1", "local": True,
|
||||
"models": ["qwen3-coder-30b", "qwen3-coder-480b", "llama-3.3-70b"]},
|
||||
{"id": "custom", "name": "Другой (OpenAI-совместимый)", "base": "https://", "local": False, "models": []},
|
||||
]
|
||||
|
||||
# Настройки по умолчанию (ключи -> значение; не секреты)
|
||||
DEFAULT_SETTINGS = {
|
||||
# хранилище
|
||||
"autoArchive": True,
|
||||
"archiveAfterDays": 14, # 1..30
|
||||
"archiveClearDays": 90,
|
||||
"trashClearDays": 7,
|
||||
# фильтры входящих
|
||||
"minLen": DEFAULT_MIN_LEN,
|
||||
"stopPhrases": DEFAULT_STOP_PHRASES,
|
||||
"mlEnabled": True, # локальный ML-слой (обучается на действиях пользователя)
|
||||
"aiEnabled": True, # полный выключатель ИИ: false = только фильтр + ML
|
||||
"aiFilterEnabled": True,
|
||||
"aiFilterPrompt": DEFAULT_AI_FILTER_PROMPT,
|
||||
"aiPrompt": DEFAULT_AI_PROMPT,
|
||||
"cardPrompt": DEFAULT_AI_CARD_PROMPT, # структура карточки/блока «О заявке» (отдельный промпт)
|
||||
# сфера и ключи (универсальный классификатор: редактируется в UI)
|
||||
"domainDescription": "",
|
||||
"domainKeywords": [],
|
||||
"hireMarkers": DEFAULT_HIRE_MARKERS,
|
||||
"levelTerms": DEFAULT_LEVEL_TERMS,
|
||||
"resumeMarkers": DEFAULT_RESUME_MARKERS,
|
||||
"blockResumes": True, # True = ищем вакансии/заказы, резюме соискателей отсекаем
|
||||
# тип заявок, которые собираем: both | vacancy | freelance (этап 1, до ИИ)
|
||||
"wantedType": "both",
|
||||
# «без указания суммы карточку не создаём»: отдельно для найма и заказов
|
||||
"budgetRequiredHire": False,
|
||||
"budgetRequiredOrder": False,
|
||||
# подписи типов на карточках (по ситуации/сфере пользователя)
|
||||
"hireLabel": "вакансия",
|
||||
"orderLabel": "фриланс",
|
||||
# личная библиотека промптов пользователя: [{id, name, description, prompt}]
|
||||
"myPrompts": [],
|
||||
# напоминания
|
||||
"remindersEnabled": True,
|
||||
# валюта и курсы
|
||||
"conversionOn": True,
|
||||
"targetCurrency": "RUB",
|
||||
"rateSource": "cbr", # cbr | mock
|
||||
# UI-состояния колонок
|
||||
"colState": {},
|
||||
# AI: активный провайдер и конфиги (ключи хранятся здесь же, наружу маскируются)
|
||||
"aiProvider": "deepseek",
|
||||
"aiConfigs": {
|
||||
p["id"]: {"apiKey": "", "baseUrl": p["base"], "model": (p["models"] or [""])[0]}
|
||||
for p in AI_PROVIDERS
|
||||
},
|
||||
# telegram-ключи: {api_id, api_hash, account}
|
||||
"tgKeys": {"apiId": "", "apiHash": ""},
|
||||
# авто-мониторинг новых чатов/каналов (добавлены с другого клиента)
|
||||
"autoMonitorNew": True,
|
||||
# поиск каналов (Discovery)
|
||||
"discJoinLimit": 50, # суточный лимит авто-вступлений (общий)
|
||||
"discJoinDelayMin": 50, # сек, нижняя граница паузы между авто-вступлениями
|
||||
"discJoinDelayMax": 70, # сек, верхняя граница
|
||||
"discEvalSample": 10, # размер выборки сообщений при оценке
|
||||
"discEvalThreshold": 40, # % подходящих сообщений
|
||||
}
|
||||
|
||||
# Диалоговые цвета (детерминированно по имени)
|
||||
DIALOG_HUES = ["#3b82f6", "#38bdf8", "#f472b6", "#f59e0b", "#a78bfa", "#34d399", "#fb7185", "#22c55e"]
|
||||
|
||||
# Множители единиц времени
|
||||
DAY_MS = 86_400_000
|
||||
HOUR_MS = 3_600_000
|
||||
MIN_MS = 60_000
|
||||
"""Базовые константы: палитры, стадии, промпты, валюты, провайдеры AI.
|
||||
|
||||
Значения согласованы с фронтенд-прототипом (frontend/src/data.js),
|
||||
чтобы интеграция была безболезненной.
|
||||
"""
|
||||
|
||||
# Палитра колонок-досок (по умолчанию)
|
||||
PALETTE = ["#818cf8", "#fbbf24", "#22d3ee", "#e879f9", "#34d399", "#fb7185", "#a78bfa", "#f97316"]
|
||||
|
||||
# Колонки не создаются по умолчанию: их создаёт пользователь или предлагает
|
||||
# ИИ (suggested=TRUE — ждёт решения пользователя).
|
||||
|
||||
# Служебные колонки дашборда
|
||||
SERVICE_COLS = {"inbox", "archive", "trash", "taken"}
|
||||
|
||||
# Стадии канбана «Выбранных»
|
||||
PIPELINE_STAGES = [
|
||||
{"id": "planned", "name": "Запланировано", "color": "#818cf8", "terminal": False},
|
||||
{"id": "reply", "name": "Отклик", "color": "#38bdf8", "terminal": False},
|
||||
{"id": "agree", "name": "Согласование", "color": "#a78bfa", "terminal": False},
|
||||
{"id": "work", "name": "В работе", "color": "#fbbf24", "terminal": False},
|
||||
{"id": "review", "name": "Проверка", "color": "#f97316", "terminal": False},
|
||||
{"id": "ready", "name": "Готово", "color": "#4ade80", "terminal": False},
|
||||
{"id": "hold", "name": "Отложено", "color": "#94a3b8", "terminal": False},
|
||||
{"id": "finished", "name": "Выполнено", "color": "#2bd576", "terminal": True},
|
||||
{"id": "rejected", "name": "Отклонено", "color": "#ff6b6b", "terminal": True},
|
||||
]
|
||||
|
||||
# Валюты и мок-курсы (до первого успешного запроса к ЦБ)
|
||||
CURRENCIES = [
|
||||
{"code": "RUB", "name": "Российский рубль", "symbol": "₽"},
|
||||
{"code": "USD", "name": "Доллар США", "symbol": "$"},
|
||||
{"code": "EUR", "name": "Евро", "symbol": "€"},
|
||||
{"code": "CNY", "name": "Китайский юань", "symbol": "¥"},
|
||||
{"code": "KZT", "name": "Казахстанский тенге", "symbol": "₸"},
|
||||
{"code": "BYN", "name": "Белорусский рубль", "symbol": "Br"},
|
||||
{"code": "USDT", "name": "Tether (USDT)", "symbol": "₮"},
|
||||
{"code": "GBP", "name": "Британский фунт", "symbol": "£"},
|
||||
]
|
||||
|
||||
MOCK_RATES = {
|
||||
"RUB": 1.0,
|
||||
"USD": 92.5,
|
||||
"EUR": 99.9,
|
||||
"CNY": 13.1,
|
||||
"KZT": 0.19,
|
||||
"BYN": 28.6,
|
||||
"USDT": 92.5,
|
||||
"GBP": 117.4,
|
||||
}
|
||||
|
||||
CBR_URL = "https://www.cbr-xml-daily.ru/daily_json.js"
|
||||
|
||||
# Стоп-фразы по умолчанию (этап 1 фильтра — без ИИ)
|
||||
DEFAULT_STOP_PHRASES = ["взаимный пиар", "резюме", "ищу работу", "набор в команду"]
|
||||
|
||||
DEFAULT_MIN_LEN = 24
|
||||
|
||||
# Промпты. В тексте можно использовать плейсхолдеры, которые подставляются из
|
||||
# настроек «Сферы и ключей» при каждом вызове ИИ:
|
||||
# {domain} — domainDescription (что для вас заявка/лид, ваша сфера)
|
||||
# {keywords} — domainKeywords (общие слова-маркеры заявок)
|
||||
DEFAULT_AI_PROMPT = (
|
||||
"Ты — классификатор входящих сообщений. Сообщение — ЗАЯВКА (лид), только если в нём есть конкретный"
|
||||
" запрос или предложение по делу: заказ услуги/товара/работы, поиск исполнителя или найм человека."
|
||||
" Направление вашей сферы:\n{domain}\n\n"
|
||||
"Частые слова-маркеры заявок в вашей сфере: {keywords}\n\n"
|
||||
"Определи по тексту:\n"
|
||||
"1. is_spam — true, если это НЕ заявка: служебные сообщения (коды входа/подтверждения, уведомления),"
|
||||
" приветствия и поздравления, флуд и обсуждения без задачи, вопросы без конкретики, реклама, скам,"
|
||||
" фин. пирамиды, резюме соискателей, взаимный пиар, приглашения в чаты, рассылки."
|
||||
" Если сомневаешься — ставь true (лучше пропустить сообщение, чем засорить карточками)\n"
|
||||
"2. board — id подходящей доски из списка. Выбирай по КРИТЕРИЯМ колонки"
|
||||
" (в скобках указаны её направление, тематика, ключевые слова, уровень, бюджет, описание), а не только по названию;"
|
||||
" если ни одна колонка не подходит под текст — верни null (карточка пойдёт в «Неразобранное»)\n"
|
||||
"3. is_vacancy — true, если это НАЙМ/постоянная или проектная занятость: ищут человека в команду"
|
||||
" (признаки: вакансия, грейд/уровень, «в компанию», hr, зарплата за месяц);"
|
||||
" false — разовая сделка: заказ/услуга/товар (фриланс, подряд, «нужно сделать/купить», цена за работу)\n"
|
||||
"4. title — короткий заголовок (4–9 слов, без эмодзи, хэштегов и знаков препинания в конце)."
|
||||
" Запрещено: markdown, ссылки в любом виде ([текст](url), голые url) — только чистый текст\n"
|
||||
"5. company — кто разместил заявку: компания/бренд/частное лицо/заказчик. Только факт из текста;"
|
||||
" не указано — верни пустую строку \"\"\n"
|
||||
"6. format — формат работы/выполнения: удалённо/офис/гибрид, город, график. Коротко; нет — \"\"\n"
|
||||
"7. task — 1–2 предложения: что за задача/роль и в чём суть (для кого, что нужно сделать)."
|
||||
" Ёмко, без пересказа всего объявления и без служебных строк\n"
|
||||
"8. requirements — JSON-массив строк ключевых требований к кандидату/исполнителю"
|
||||
" (что нужно уметь/иметь, пункты «Требования/Обязанности/Нужно»). Нет — []\n"
|
||||
"9. plus — JSON-массив строк «будет плюсом»/«приветствуется»/«желательно». Нет — []\n"
|
||||
"10. conditions — условия одной строкой: оплата/ЗП/вилка, сроки, объём, тип занятости. Нет — \"\"\n"
|
||||
"11. stack — JSON-массив строк (не строка!): технологии/предметы/услуги/материалы из заявки."
|
||||
" Названия пиши слитно как в оригинале: \".NET\", \"C#\", \"Node.js\", \"ASP.NET Core\" — не разбивай на отдельные буквы (2–8)\n"
|
||||
"12. budget — объект { from, to, currency }: одна сумма → from=to=X; «до X» → from=null, to=X;"
|
||||
" диапазон «от X до Y» → from=X, to=Y. Если суммы нет — null\n"
|
||||
"13. contacts — JSON-массив строк контактов ДЛЯ СВЯЗИ: телефон, email, @username (не бот),"
|
||||
" ссылка на профиль человека (LinkedIn, t.me/…). НЕ включай ботов, каналы/группы и ссылки на"
|
||||
" вакансию/пост/форму. Если контакта в тексте нет — пустой массив []\n"
|
||||
"Поля 5–10 — это блок «О заявке» на карточке: заполняй их только фактами из текста, ничего не выдумывай."
|
||||
" Запрещено: markdown-разметка, ссылки в любом виде, эмодзи, хэштеги, «от»/«привет», дословное копирование исходника.\n"
|
||||
"Отвечай строго в формате JSON."
|
||||
)
|
||||
|
||||
DEFAULT_AI_FILTER_PROMPT = (
|
||||
"Ты — страж входящих сообщений каналов. Пропускай только реальные заявки/лиды по вашей сфере"
|
||||
" (заказ, услуга, товар, найм — с конкретикой).\n"
|
||||
"Ваша сфера и что считать заявкой:\n{domain}\n\n"
|
||||
"Слова-маркеры заявок: {keywords}\n\n"
|
||||
"НЕ пропускай:\n"
|
||||
"- служебные сообщения: коды входа/подтверждения, уведомления, приветствия, поздравления\n"
|
||||
"- просто сообщения без задачи и конкретики: флуд, обсуждения, вопросы «кто работал с …»\n"
|
||||
"- рекламу и саморекламу\n"
|
||||
"- скам, фин. пирамиды, «заработок»\n"
|
||||
"- резюме, поиск работы соискателями\n"
|
||||
"- взаимный пиар, приглашения в чаты\n"
|
||||
"- рассылки и дайджесты без прямых заявок\n"
|
||||
"При сомнении — не пропускай.\n"
|
||||
'Верни строго JSON: { "pass": true|false, "reason": "причина отказа или null" }'
|
||||
)
|
||||
|
||||
# Промпт структуры карточки (блок «О заявке»). Отдельный от классификатора:
|
||||
# задаёт, какие поля и как заполнять, чтобы все карточки имели одинаковую
|
||||
# структуру текста (разной длины). Добавляется к промпту классификатора
|
||||
# отдельным блоком (см. services/ai.classify). Редактируется в UI.
|
||||
DEFAULT_AI_CARD_PROMPT = (
|
||||
"Ты также возвращаешь содержимое блока «О заявке» карточки — поля company, format,"
|
||||
" task, requirements, plus, conditions. Карточка всегда собирается из одних и тех же блоков:"
|
||||
" одинаковая структура у всех карточек, различается только длина.\n"
|
||||
"1. company — кто ищет/разместил: компания, бренд, агентство, частное лицо, заказчик. Одно предложение, факт из текста.\n"
|
||||
"2. format — формат работы: удалённо/офис/гибрид/разъездной, город/страна, график (5/2, full-time, part-time). Коротко.\n"
|
||||
"3. task — 1–2 предложения по шаблону: что за задача/роль → для кого → что нужно сделать/какой результат."
|
||||
" Пиши ёмко, не пересказывай объявление дословно.\n"
|
||||
"4. requirements — вынеси сюда только реальные требования/обязанности из текста"
|
||||
" (пункты списков «Требования», «Обязанности», «Что предстоит делать», «Нужно»):"
|
||||
" каждый пункт короткой строкой в JSON-массиве. Не придумывай сверх текста.\n"
|
||||
"5. plus — только то, что отмечено как «будет плюсом»/«приветствуется»/«желательно»."
|
||||
" Нет таких пунктов — пустой массив [].\n"
|
||||
"6. conditions — условия одной строкой: оплата/ЗП/вилка/гонорар, сроки, объём, тип занятости."
|
||||
" Не дублируй budget-числа в других полях.\n"
|
||||
"Для не-IT сфер «стек/требования» означают материалы, услуги, навыки, инструменты — по смыслу заявки."
|
||||
" Запрещено во всех полях: markdown-разметка, ссылки в любом виде ([текст](url), голые url),"
|
||||
" эмодзи, хэштеги, «от»/«привет»/служебные строки канала."
|
||||
)
|
||||
|
||||
# Дефолтные маркеры локального разбора (настраиваются в UI «Сфера и ключи»).
|
||||
DEFAULT_HIRE_MARKERS = [
|
||||
"вакансия", "вакансию", "вакансии", "вакантна", "вакант", "нанимаем", "найм", "full-time",
|
||||
"на постоянную", "занятость", "в офис", "официальное оформление", "пятидневка",
|
||||
"грейд", "в компанию", "полная занятость", "на постоянную работу", "в команду", "нанимает",
|
||||
"на постоянную основу", "в штат",
|
||||
]
|
||||
|
||||
DEFAULT_LEVEL_TERMS = [
|
||||
"junior", "джун", "джуниор", "middle", "мидл", "mid", "senior", "сеньор", "сеньйор",
|
||||
"lead", "тимлид", "тиэмлид", "architect", "архитектор", "стажёр", "стажер", "intern", "trainee",
|
||||
]
|
||||
|
||||
# Маркеры, по которым сообщение опознаётся как резюме/самопрезентация
|
||||
# соискателя («ищу работу», «моё резюме»). По умолчанию такие сообщения
|
||||
# отсекаются на этапе 1 (до ИИ и правил колонок), если пользователь ищет
|
||||
# вакансии и заказы, а не кандидатов. Редактируется в «Сфере и ключах»;
|
||||
# выключите отсев — резюме начнут собираться как обычные лиды.
|
||||
# Слово «резюме» имеет контекстный guard (см. pipeline._resume_reason):
|
||||
# «…вакансия…, присылайте резюме» — это объявление работодателя, его не режем.
|
||||
DEFAULT_RESUME_MARKERS = [
|
||||
"резюме", "#резюме", "моё резюме", "мое резюме", "ищу работу", "ищу вакансию",
|
||||
"рассмотрю предложения", "готов к собеседованию", "в поиске работы",
|
||||
"ищу проект", "ищу подработку",
|
||||
]
|
||||
|
||||
# AI-провайдеры (активен один)
|
||||
AI_PROVIDERS = [
|
||||
{"id": "deepseek", "name": "DeepSeek", "base": "https://api.deepseek.com", "local": False,
|
||||
"models": ["deepseek-v4-flash", "deepseek-v4-pro", "deepseek-v4-flash-vision-exp"]},
|
||||
{"id": "openai", "name": "OpenAI", "base": "https://api.openai.com/v1", "local": False,
|
||||
"models": ["gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna"]},
|
||||
{"id": "openrouter", "name": "OpenRouter", "base": "https://openrouter.ai/api/v1", "local": False,
|
||||
"models": ["deepseek/deepseek-v4-flash", "anthropic/claude-sonnet-5", "openai/gpt-5.6-luna",
|
||||
"google/gemini-3.8-flash", "qwen/qwen3.8-flash"]},
|
||||
{"id": "anthropic", "name": "Anthropic Claude", "base": "https://api.anthropic.com", "local": False,
|
||||
"api_style": "anthropic",
|
||||
"models": ["claude-sonnet-5", "claude-opus-5", "claude-fable-5-1", "claude-haiku-4-5-20251001"]},
|
||||
{"id": "ollama", "name": "Ollama (локально)", "base": "http://localhost:11434/v1", "local": True,
|
||||
"models": ["qwen3-coder:30b", "qwen3-coder:480b", "qwen3:32b"]},
|
||||
{"id": "lmstudio", "name": "LM Studio (локально)", "base": "http://localhost:1234/v1", "local": True,
|
||||
"models": ["qwen3-coder-30b", "qwen3-coder-480b", "llama-3.3-70b"]},
|
||||
{"id": "custom", "name": "Другой (OpenAI-совместимый)", "base": "https://", "local": False, "models": []},
|
||||
]
|
||||
|
||||
# Настройки по умолчанию (ключи -> значение; не секреты)
|
||||
DEFAULT_SETTINGS = {
|
||||
# хранилище
|
||||
"autoArchive": True,
|
||||
"archiveAfterDays": 14, # 1..30
|
||||
"archiveClearDays": 90,
|
||||
"trashClearDays": 7,
|
||||
# фильтры входящих
|
||||
"minLen": DEFAULT_MIN_LEN,
|
||||
"stopPhrases": DEFAULT_STOP_PHRASES,
|
||||
"mlEnabled": True, # локальный ML-слой (обучается на действиях пользователя)
|
||||
"aiEnabled": True, # полный выключатель ИИ: false = только фильтр + ML
|
||||
"aiFilterEnabled": True,
|
||||
"aiFilterPrompt": DEFAULT_AI_FILTER_PROMPT,
|
||||
"aiPrompt": DEFAULT_AI_PROMPT,
|
||||
"cardPrompt": DEFAULT_AI_CARD_PROMPT, # структура карточки/блока «О заявке» (отдельный промпт)
|
||||
# сфера и ключи (универсальный классификатор: редактируется в UI)
|
||||
"domainDescription": "",
|
||||
"domainKeywords": [],
|
||||
"hireMarkers": DEFAULT_HIRE_MARKERS,
|
||||
"levelTerms": DEFAULT_LEVEL_TERMS,
|
||||
"resumeMarkers": DEFAULT_RESUME_MARKERS,
|
||||
"blockResumes": True, # True = ищем вакансии/заказы, резюме соискателей отсекаем
|
||||
# тип заявок, которые собираем: both | vacancy | freelance (этап 1, до ИИ)
|
||||
"wantedType": "both",
|
||||
# «без указания суммы карточку не создаём»: отдельно для найма и заказов
|
||||
"budgetRequiredHire": False,
|
||||
"budgetRequiredOrder": False,
|
||||
# подписи типов на карточках (по ситуации/сфере пользователя)
|
||||
"hireLabel": "вакансия",
|
||||
"orderLabel": "фриланс",
|
||||
# личная библиотека промптов пользователя: [{id, name, description, prompt}]
|
||||
"myPrompts": [],
|
||||
# напоминания
|
||||
"remindersEnabled": True,
|
||||
# валюта и курсы
|
||||
"conversionOn": True,
|
||||
"targetCurrency": "RUB",
|
||||
"rateSource": "cbr", # cbr | mock
|
||||
# UI-состояния колонок
|
||||
"colState": {},
|
||||
# AI: активный провайдер и конфиги (ключи хранятся здесь же, наружу маскируются)
|
||||
"aiProvider": "deepseek",
|
||||
"aiConfigs": {
|
||||
p["id"]: {"apiKey": "", "baseUrl": p["base"], "model": (p["models"] or [""])[0]}
|
||||
for p in AI_PROVIDERS
|
||||
},
|
||||
# telegram-ключи: {api_id, api_hash, account}
|
||||
"tgKeys": {"apiId": "", "apiHash": ""},
|
||||
# авто-мониторинг новых чатов/каналов (добавлены с другого клиента)
|
||||
"autoMonitorNew": True,
|
||||
# поиск каналов (Discovery)
|
||||
"discJoinLimit": 50, # суточный лимит авто-вступлений (общий)
|
||||
"discJoinDelayMin": 50, # сек, нижняя граница паузы между авто-вступлениями
|
||||
"discJoinDelayMax": 70, # сек, верхняя граница
|
||||
"discEvalSample": 10, # размер выборки сообщений при оценке
|
||||
"discEvalThreshold": 40, # % подходящих сообщений
|
||||
}
|
||||
|
||||
# Диалоговые цвета (детерминированно по имени)
|
||||
DIALOG_HUES = ["#3b82f6", "#38bdf8", "#f472b6", "#f59e0b", "#a78bfa", "#34d399", "#fb7185", "#22c55e"]
|
||||
|
||||
# Множители единиц времени
|
||||
DAY_MS = 86_400_000
|
||||
HOUR_MS = 3_600_000
|
||||
MIN_MS = 60_000
|
||||
|
||||
Reference in New Issue
Block a user