Чаты: бизнес-логика¶
Обзор¶
Чаты предоставляют механизм обмена сообщениями между пользователями вне контекста задач. Технически чат -- это задача (Tasks) в специальной подкатегории, сообщения -- комментарии (Comments), участники -- подписчики (MailSubscribersUsers/Groups). Поддерживаются 4 типа чатов с разными правилами.
Типы чатов (ChatType2)¶
Enum: Valhalla.Integration/Enums/Chats/ChatType2.cs
| Тип | Описание | Определение | Max участников |
|---|---|---|---|
| Personal | Личный чат 1-на-1 | subcatId == settings.ChatSubcatId |
2 |
| Group | Открытый групповой чат | subcatId == settings.GroupChatSubCatID |
Без ограничений |
| Group (закрытый) | Закрытый групповой чат | subcatId == settings.ClosedGroupChatSubcatId |
Без ограничений |
| Channel | Канал (broadcast) | Настраивается в категории | Без ограничений |
Особенности каналов (Channel)¶
Каналы реализуют модель broadcast, аналогичную Telegram. Ключевое ограничение: неподписанные пользователи и пользователи без права писать в чат не могут отвечать на сообщения.
- Если пользователь не подписчик канала И/ИЛИ не имеет права писать в чат (
CanWriteChat= false), в контекстном меню сообщения (ПКМ) скрываются пункты: - Ответить
- Ответить всем
- Начать обсуждение
- Переслать → Текущий чат
Это ограничение применяется на уровне контекстного меню в веб-интерфейсе и мобильных приложениях. Сообщения в канале остаются доступными для чтения всем пользователям (если канал публичный), но взаимодействие с ними разрешено только подписчикам с правами на запись. | SocialNetwork | Публикация в соцсети | Настраивается в категории | Без ограничений |
Как определяется тип чата¶
Файл: TCClassLib/Chat/ChatSettingsBuildService.cs:77-105
if (subcatId == settings.ChatSubcatId) → Personal
if (subcatId == settings.GroupChatSubCatID) → Group
else → определяется по настройкам категории (Channel / SocialNetwork)
SubcatId чата хранится в конфигурации: ConfigurationService.Get().ChatSubcatId, GroupChatSubCatID и ClosedGroupChatSubcatId.
Роли участников чата (ChatRole)¶
Enum: Valhalla.Integration/Enums/Chats/ChatRole.cs
| Роль | Значение | Может назначать |
|---|---|---|
| Owner | 0 | Owner, Admin, Moderator, Author |
| Admin | 1 | Admin, Moderator, Author |
| Moderator | 2 | Author |
| Author | 3 | Никого |
Иерархия: меньшее числовое значение = больше прав. Владелец задачи-чата автоматически получает роль Owner.
Роли хранятся в таблицах ChatRolesUsers и ChatRolesGroups. Пользователь может наследовать роль от замещаемого (кроме Owner).
Матрица возможностей по типу чата¶
Видимость МТФ-панели¶
В чатовых подкатегориях (типы представления: Личный чат, Пространство, Групповой чат, Канал соц. сети) администратор управляет отображением правой боковой панели — МТФ-панели — с карточкой задачи-чата.
Правила видимости:
- Показывать МТФ-панель (showMtfPanel) — определяет начальное состояние панели при открытии чата: включено = раскрыта, выключено = свёрнута.
- Группы доступа (mtfPanelGroupIds) — каким участникам чата панель видна. Пустой список = панель скрыта для всех.
- Для не-чатовых подкатегорий настройка не применяется и в админке не отображается.
Источник настройки: Admin SPA → подкатегория → секция «Настройки МТФ-панели».
Видимость МТФ-панели¶
В чатовых подкатегориях (типы представления: Личный чат, Пространство, Групповой чат, Канал соц. сети) администратор управляет отображением правой боковой панели — МТФ-панели — с карточкой задачи-чата.
Правила видимости:
- Показывать МТФ-панель (showMtfPanel) — определяет начальное состояние панели при открытии чата: включено = раскрыта, выключено = свёрнута.
- Группы доступа (mtfPanelGroupIds) — каким участникам чата панель видна. Пустой список = панель скрыта для всех.
- Для не-чатовых подкатегорий настройка не применяется и в админке не отображается.
Источник настройки: Admin SPA → подкатегория → секция «Настройки МТФ-панели».
Файл: TCClassLib/Chat/ChatSettingsBuildService.cs:233-262 (дефолтные настройки)
Для пользователей, не подписанных на групповой чат или канал (видят кнопку «Подписаться» вместо поля ввода), в контекстном меню сообщения скрываются пункты, требующие роли подписчика: подпункт «В текущий чат» меню «Переслать» и пункт «Удалить» (в каналах «Удалить» может быть отдельным пунктом — тоже скрывается). Пересылка в другие чаты, где у пользователя есть право на публикацию, остаётся доступной. Задача.источник: .
| Возможность | Personal | Group | Channel | SocialNetwork |
|---|---|---|---|---|
| Добавление подписчиков | НЕТ | ДА* | НЕТ | ДА |
| Редактирование профиля | НЕТ | ДА* | ДА | НЕТ |
| Изменение настроек | НЕТ | ДА** | ДА** | ДА** |
| Обсуждения (треды) | ДА | ДА | ДА | НЕТ |
| Корневые сообщения | ДА | ДА | НЕТ | НЕТ |
| Конференция (ВКС) | ДА | ДА | НЕТ | НЕТ |
| Вложения файлов | ДА | ДА | НЕТ | НЕТ |
| Закрепление сообщений | ДА | ДА | ДА | ДА |
| Опросы | ДА | ДА | ДА | ДА |
| Реакции | ДА | ДА | ДА | ДА |
| Голосовые сообщения | ДА | ДА | ДА | ДА |
| Приватность | Всегда private | Настраиваемо | Public | Настраиваемо |
* — если IsAddSubscribersEnabled/IsEditProfileEnabled включено И чат не закрыт.
** — только Owner/Admin.
Права на действия с подписчиками¶
Файл: TCClassLib/Permissions/ChatPermissionsService.cs:120-170
Личный чат (Personal)¶
CanAddSubscribers = false ← ЗАБЛОКИРОВАНО, нельзя переопределить
CanEditAppearance = false ← ЗАБЛОКИРОВАНО
CanEditSettings = false ← ЗАБЛОКИРОВАНО
Ключевое правило для тикетов: в личном чате добавление подписчиков заблокировано на уровне ChatPermissionsService. Это не ошибка, а by design.
Групповой чат (Group, Channel, SocialNetwork)¶
| Действие | Owner/Admin | Подписчик | Не подписчик |
|---|---|---|---|
| Добавить подписчика | ДА | Если IsAddSubscribersEnabled И чат открыт |
НЕТ |
| Изменить профиль | ДА | Если IsEditProfileEnabled И чат открыт |
НЕТ |
| Изменить настройки | ДА | НЕТ | НЕТ |
Управление подписчиками задач/чатов (общее)¶
Файл: TCClassLib/Services/TaskSubscriberService.cs:50-82, Permissions.cs:539-554
| Условие | Кто может менять подписчиков |
|---|---|
| Обычная задача/чат | Любой с доступом к задаче |
| Конфиденциальная задача/чат | Только заказчик (owner) задачи |
| Системный пользователь | Всегда может |
Создание чата¶
Endpoint: POST /api/chats → ChatManageService.CreateChat
Payload¶
{
"chatGuid": "uuid-v4",
"text": "Первое сообщение",
"subscribers": [28918, 28919],
"subscribersGroups": [],
"chatName": "Название чата",
"chatDescription": "",
"chatType": "Personal | Group",
"isPrivate": true,
"avatarFileId": null
}
| Поле | Тип | Обязательно | Описание |
|---|---|---|---|
chatGuid |
string (UUID) | да | Уникальный идентификатор чата (клиент генерирует) |
text |
string | нет | Текст первого сообщения |
subscribers |
int[] | да | User ID участников (без создателя — он добавляется автоматически) |
subscribersGroups |
int[] | нет | ID групп пользователей |
chatName |
string | для Group | Название чата (отображается в UI для групповых) |
chatType |
string | да | "Personal" (1-to-1) или "Group" (многопользовательский) |
isPrivate |
bool | нет | Приватность (default true) |
Response: { "data": { "chatId": N, "commentId": N } } (DTO: CreateChatResponseDto)
Anti-capability: без
chatTypeбэкенд дефолтит в Personal → при 2+ subscribers вернёт 400"В личном чате может быть только два подписчика". Для группового чата обязательно передавать"chatType": "Group".
Ограничения по типу¶
- Personal: максимум 2 подписчика (исключение
OnlyTwoSubscriberInChat) - Group: без ограничений
- Channel/SocialNetwork: без ограничений
Проверка дубликатов¶
При создании личного чата система проверяет, существует ли уже чат между теми же участниками. Если да -- возвращает существующий.
Закрытие и выход¶
| Действие | Кто может | Результат |
|---|---|---|
| Close (закрыть чат) | Owner или с правом на переход по шагу | Задача-чат переводится в финальный шаг |
| Left (выход из чата) | Любой подписчик | Удаление подписки, mark read, дочистка вопросов |
Конфиденциальность чата¶
Настройка на уровне категории (cats-properties-general):
| Режим | Описание |
|---|---|
| Отключено | Нет ограничений |
| Выборочно | Можно флагировать конкретные чаты |
| Обязательно | Все чаты конфиденциальны |
При включённой конфиденциальности: - Только подписчики видят содержимое - Подписчики (кроме owner) не могут приглашать не-подписчиков - Эскалация просроченных чатов отключена
Уведомления (ChatNotificationMode)¶
Персональная настройка каждого участника (кнопка «Выключить уведомления» в меню чата):
| Режим | Описание |
|---|---|
| All | Все уведомления |
| MentionsOnly | Только при упоминании |
| None | Уведомления отключены |
Что контролирует ChatNotificationMode¶
Влияет на: push-уведомления, непрочитанность сообщений, уведомления рабочего стола.
Не влияет на: email-уведомления. Email управляется двумя отдельными механизмами: 1. Настройка категории «Не посылать почтовые сообщения» (per-категория) — см. subcategory-mail-messages-settings 2. Пользовательская настройка «Не присылать почтовые сообщения» (глобальная) — см. уведомления
Особенности групповых чатов¶
Даже при ChatNotificationMode = None уведомления всегда приходят при:
- Личном обращении (сообщение адресовано конкретно пользователю)
- Отправке сообщения «Всем»
Это задокументированное поведение (см. уведомления, тип событий «Сообщения»).
Виртуальные группы «Всем» и «Никому»¶
В групповых чатах поддерживаются две виртуальные группы адресации, которые не имеют физических записей в таблицах групп, но их ID материализуются через AdminGroups.GetVirtualGroupId на основе мнемоник из GroupTypesService:
// core/TCClassLib/Groups/GroupTypesService.cs:36-46
/// <summary>
/// Признак виртуальной группы для отправки комментария "Всем".
/// Используется как персистентный признак, что комментарий был адресован "Всем" в момент отправки.
/// </summary>
public const string VirtualGroupEverybodyMnemo = "VirtalEverybody";
/// <summary>
/// Признак виртуальной группы для отправки комментария "Никому".
/// Используется как персистентный признак, что комментарий был адресован "Никому" в момент отправки.
/// </summary>
public const string VirtualGroupNobodyMnemo = "VirtalNobody";
Обе группы имеют тип VirtualGroupTypeId. Их ID кэшируются в core/TCClassLib/AdminGroups.cs:237-241 через Lazy<int> и читаются как AdminGroups.NobodyVirtualGroupId / AdminGroups.EverybodyVirtualGroupId.
| Выбор в UI | Что происходит | Push-уведомления | Мьют чата |
|---|---|---|---|
Выбрана группа «Всем» (VirtalEverybody) |
Все подписчики чата получают сообщение | Всем подписчикам, без исключений — даже если ChatNotificationMode = Muted |
Пробивает мьют чата |
Никто не выбран (пустой выбор, VirtalNobody) |
Комментарий без адресатов; VirtalNobody сохраняется как persisted-маркер для рендера |
Всем подписчикам, с уважением их персональных настроек мьюта | Уважает мьют |
Бизнес-философия (Денис, #2087311 комментарий 32008044): как в Telegram — все чаты на мьюте, мьют пробивает только адресное обращение и «Всем», чтобы усложнить жизнь спамеру. Обычное сообщение без адресатов рассылается всем подписчикам, но уважает их персональный выбор мьюта.
В Telegram нет явной опции @всем; в Slack/Discord есть @channel/@here. В 1Форме @всем (VirtalEverybody) пробивает мьют так же, как и обращение к конкретному пользователю.
Реализация:
- Расчёт получателей: core/TCClassLib/Comments.cs:1583 (ветка isNobodyGroup) и core/TCClassLib/Comments.cs:2254-2259 (резолв "Никому" через виртуальную группу) — метод AddCommentRecipients.
- Логика push: core/TCClassLib/MobileApps/Push/PushRecipientResolver.cs:155-159 — расчёт ignoreMute/forceMute. Для VirtalNobody ignoreMute = false, мьют гасит push; для VirtalEverybody ignoreMute = true через isSentToEverybody.
- Свежий фикс: коммит 4b1bc12a35 от 05.05.2026 (Шарафутдинов Игорь, ветка feature/2056424-passwork, сборка 2.268 «Скульптор») — добавлена явная ветка if (isNobodyGroup) { BuildCommentRecipients(... customIsUnread: false, isRealRecipient: false) } в Comments.cs:1585-1610. Эффект: для подписчиков комментария «Никому» гарантированно IsUnread = 0 (непрочитанность не растёт). Push-логика этим коммитом не меняется.
- Историческая серия 2024-08: задача #1707893, коммиты 4a64d20a02/73b877c442/bdc706d795/1a20ad23d1/0343e01179/b4fdb4f33a — убрана UI-настройка NobodyCommentsUnread и фиксы поведения «"никому" не должен пробивать пуш».
- Поведение переподтверждено в задаче #2087311 14.05.2026 (видеоконференция Крылов/Тимонов/Денис). Подробнее: domains/notifications/data-flow.md § 7.4.
Legacy-настройка: поле NobodyCommentsUnread в core/TCDataAccess/Kernel/Domain/Entities/SettingEntity.cs:660-661 помечено [Obsolete("Не используется")]. Все 4 точки использования закомментированы (Comments.cs:3555, PushRecipientResolver.cs:155, 158, 182). Раньше эта настройка превращала комментарии «Никому» в «Всем» (со снятием мьюта); сейчас поведение зафиксировано: «Никому» — всегда рассылается всем подписчикам с уважением мьюта.
UI-селектор адресатов: опция «Никому» в селекторе отсутствует — поиск по recipientSelector/RecipientPicker/comment-recipients в SPA пустой. «Никому» — это вычисляемое состояние «пустой выбор адресатов». В SPA VirtalNobody используется только для рендера уже сохранённого комментария — скрытие блока «recipients» в apps/spa/src/app/common/components/feed-comments/feed-comment/feed-comment.component.ts:655 (геттер isNobodyVirtualGroup) и шаблоне .html:84, аналогично feed-task-comments-view.service.ts:681 и feed-task-comments.component.html:348-352.
Типичный тикет: «mute чата не отключает email»¶
Кнопка «Выключить уведомления» в чате не отключает email. Для отключения email из групповых чатов рекомендуется настройка «Не посылать почтовые сообщения» на категории групповых чатов (GroupChatSubCatID). Подробнее: support-guide секция 1.10
Настройки чата (ChatSettings)¶
Таблица dbo.ChatSettings хранит per-chat настройки:
| Настройка | Тип | Описание |
|---|---|---|
IsPrivate |
bool | Приватный/публичный чат |
IsAddSubscribersEnabled |
bool | Можно ли участникам добавлять подписчиков |
IsEditProfileEnabled |
bool | Можно ли участникам менять профиль чата |
IsSubscribersVisibile |
bool | Видимость списка подписчиков |
IsRootCommentsEnabled |
bool | Можно ли писать корневые сообщения. Если отключено — поле ввода скрыто для подписчиков, писать могут только администраторы/модераторы. Для неподписанных пользователей в открытых чатах вместо поля ввода отображается кнопка «Подписаться» |
IsThreadsEnabled |
bool | Включены ли обсуждения (треды) |
IsThreadCommentsEnabled |
bool | Можно ли отвечать в тредах |
IsReactionsEnabled |
bool | Включены ли реакции |
IsMessagePinEnabled |
bool | Закрепление сообщений для всех участников |
IsAttachmentsEnabled |
bool | Вложения файлов |
IsSurveysEnabled |
bool | Опросы |
IsVoiceMessagesEnabled |
bool | Голосовые сообщения |
IsConferenceEnabled |
bool | ВКС |
IsAnonymousAllowed |
bool | Разрешить создавать сообщения анонимно (для чатов типа Group) |
IsMessagePinEnabled(v2.268+): при создании канала параметр устанавливается вfalseпо умолчанию. Это означает, что обычные подписчики канала по умолчанию не могут закреплять сообщения для всех участников — это право остаётся только у администраторов и модераторов, если они явно не включат эту опцию в настройках чата. В редакторе настроек настройка «Закреплять сообщения для всех» скрыта, если = false — отображается только при = true для администраторов старых инсталляций, где значение было проставлено автоматически.
IsAnonymousAllowed(v2.268+): для чатов типа «Группа» настройка «Разрешить создавать сообщения анонимно» теперь корректно сохраняется и применяется. В UI эта опция отображается в настройках группового чата и доступна для изменения администраторами и владельцами чата.
Применение настроек в UI (v2.267+)¶
Настройки чата доступны через меню … → Настройки в шапке группового чата или канала (в личных чатах раздел «Настройки» заблокирован для редактирования). Параметры управляют видимостью UI-элементов для участников с ролью Подписчик. Владелец (Owner) и Администратор (Admin) видят все элементы независимо от этих настроек.
| Название в интерфейсе | Поле ChatSettings |
Что скрывается для подписчика |
|---|---|---|
| Скрыть список подписчиков | IsSubscribersVisibile |
Список участников в окне «Информация» недоступен |
| Добавлять других подписчиков | IsAddSubscribersEnabled |
Кнопка «Добавить» в окне «Информация» скрыта |
| Отправлять сообщения | IsRootCommentsEnabled |
Поле ввода скрыто; корневые сообщения доступны только администраторам и модераторам |
| Звонки | IsConferenceEnabled |
Кнопка ВКС в шапке чата и в окне «Информация» скрыта |
Переключение открытый/закрытый чат¶
Файл: TCClassLib/Chat/ChatSettingsManageService.cs — TryChangeChatSubcategoryOnPrivacyChange()
При изменении настройки IsPrivate у группового чата (Group) или канала (Channel) система автоматически перемещает чат-задачу между подкатегориями:
| Новое значение IsPrivate | Целевая подкатегория | Конфигурация |
|---|---|---|
false (открытый) |
Открытые группы | settings.GroupChatSubCatID |
true (закрытый) |
Закрытые группы | settings.ClosedGroupChatSubcatId |
Если целевая подкатегория не настроена — выбрасывается TCLogicException с сообщением «Подкатегория «Закрытые группы» не настроена. Обратитесь к администратору.»
Редактирование сообщений и быстрые клавиши¶
В чатах поддерживается редактирование собственных сообщений. При переходе в режим редактирования содержимое сообщения переносится в поле ввода внизу, а над полем отображается плашка с превью редактируемого сообщения. По нажатию на плашку интерфейс прокручивается к исходному сообщению. Отправка сохраняет изменения в исходном сообщении, а отмена очищает режим редактирования без сохранения.
В режиме чата (вид баблов) при редактировании в поле ввода подставляется текст сообщения, а также значения полей «Кому» и «Копия». В режиме комментариев в карточке задачи поля формы редактирования подменяются значениями редактируемого сообщения, без отдельного показа исходного сообщения. Для ответа и ответа всем сохраняется прежнее поведение: пользователь видит, на какое сообщение отвечает, и вводит новый текст отдельно.
Если поле ввода пустое и в нём установлен курсор, клавиша вверх (↑) переводит пользователя в режим редактирования последнего сообщения, отправленного им в текущем чате, при наличии прав на редактирование. В режиме комментариев в карточке задачи аналогичную функцию выполняет клавиша вниз (↓). Если в поле ввода уже есть хотя бы один символ, клавиши вверх и вниз не переключают режим редактирования и используются для обычного перемещения курсора по тексту.`
Первоначальная миграция (MoveClosedGroupChatsToNewSubcategory)¶
При первом запуске (если ClosedGroupChatSubcatId ещё не задан) автоматически:
1. Создаётся подкатегория «Закрытые группы» (клон настроек открытых групп)
2. Значение ClosedGroupChatSubcatId записывается в Settings
3. Все существующие приватные (IsPrivate = true) чаты типа Group/Channel переносятся в новую подкатегорию
Представление категории «Чат»¶
В представлении категории Чат задачи отображаются в виде мессенджера.
Структура и вкладки списка¶
Левая панель мессенджера содержит вкладки: Все, Личные, Группы, ИИ (при включённом ИИ), Подписка, Избранное. Вкладка Закрытые скрыта начиная с v2.268 — закрытые чаты не отображаются ни в одной из вкладок. По умолчанию при входе в раздел открывается вкладка «Все» — независимо от ранее выбранной. Это поведение сохраняется и при переходе из результатов поиска или из мини-поиска.
| Вкладка | Что отображается |
|---|---|
| Все | Действующие закреплённые чаты: групповые, личные, избранные. Любой групповой чат можно убрать из вкладки («Убрать из чатов» в контекстном меню) — он останется в «Группах» |
| Личные | Личные чаты 1-на-1 |
| Группы | Все групповые чаты, в которых пользователь подписчик |
| ИИ | Активные AI-сессии пользователя (личные чаты с Анфисой). Показывается только при включённом флаге ИИ на платформе. Закрытые AI-сессии из вкладки исчезают |
| Подписка | Задачи и чаты, на которые пользователь подписан |
| Избранное | Чаты, добавленные в избранное; у каждого чата отображается цвет звёздочки, заданный при добавлении |
| ~~Закрытые~~ | ~~Закрытые групповые чаты, чаты с уволенными сотрудниками, чаты, из которых пользователь вышел~~ — скрыта с v2.268; вкладка и иконка не отображаются |
Системные папки — вкладки-иконки (v2.268)¶
Вкладки «Подписка», «Избранное» и «ИИ» отображаются иконками (без текстовой подписи) — в отличие от текстовых вкладок «Все», «Личные», «Группы». Вкладка «Закрытые» скрыта — закрытые чаты не выводятся в интерфейсе мессенджера.
Особенности отображения строки чата в системных папках: - Показывается превью последнего сообщения (однострочный текст, как в основном списке). - В «Подписке» общий бейдж на вкладке убран; счётчик непрочитанных отображается только в отдельных строках чатов. - Контекстное меню на сообщение содержит полный набор пунктов, включая «Ответить» (ранее часть пунктов отсутствовала).
Вкладка — это фильтрованный список чатов, а не отдельная сущность. Задача может одновременно присутствовать в нескольких вкладках (например, в «Подписке» и в «ИИ», если пользователь подписан на AI-чат).
При первом открытии раздел показывает индикатор загрузки; при повторных открытиях список восстанавливается мгновенно из кеша, фоновое обновление обозначается тонкой полосой прогресса. Если ранее пользователь открывал какой-либо чат, при возврате в раздел автоматически открывается последний просмотренный — адресная строка обновляется до URL конкретного чата.
В период замещения чаты замещаемого пользователя по умолчанию недоступны заместителю.
Отображение p2p-чатов в ленте задач¶
Для личных чатов (тип Personal, подкатегория ChatSubcatId) в API ленты задач поле taskText содержит не полное название задачи-чата, а displayName собеседника — второго участника чата. Один и тот же p2p-чат каждому из двух участников показывается именем «другого человека»: для Иванова чат с Петровой отображается в ленте как «Петрова», для Петровой — как «Иванов». Подмена выполняется на бэкенде в MobileLenta.Tasks через SubscriberInfoService.GetDirectSubscribedUserIds: для записи с subcatId == ChatSubcatId ищется второй подписчик и его displayName подставляется в taskText ответа. Для групповых чатов, каналов и социальных сетей taskText остаётся стандартным — отдаётся название задачи.
При ответе на p2p-сообщение из ленты поля «Кому» и «Копия» скрыты — получателя изменить нельзя, форма ответа ведёт себя как внутри чата (v2.268+).
Лента сообщений¶
При получении нового сообщения лента автоматически прокручивается к нему, если пользователь находится в конце переписки; если пролистаны более ранние сообщения — позиция сохраняется. При повторном открытии чата лента восстанавливается на последней позиции чтения.
Когда пользователь не в конце ленты, отображается кнопка прокрутки вниз с бейджем — числом непрочитанных сообщений. Нажатие переносит к последнему сообщению.
Особенности отображения сообщений (форматирование, реакции, эмодзи-only) описаны в comments/business.md.
Индикатор набора и Анфиса¶
Пока собеседник набирает сообщение, отображается строка «Имя печатает…» с анимированными точками. Если в чате присутствует AI-ассистент Анфиса, индикатор расширяется — например, «Анфиса печатает: turn 3...» или «Анфиса печатает: web_search...». Дополнительно над полем ввода появляются оранжевые информационные плашки с названием текущего действия Анфисы; они накапливаются стопкой, держатся 15 секунд после последнего сигнала и скрываются досрочно при начале набора пользователем или при поступлении ответа.
Шаблоны сообщений¶
Рядом с полем ввода — кнопка шаблонов; список можно также вызвать вводом /. По мере набора результаты фильтруются, выбор клавишами ↑/↓, подтверждение Enter. Текст шаблона только подставляется в поле ввода — отправка не выполняется автоматически. В чатах задач список шаблонов фильтруется под категорию чата и обновляется при переходе.
Вид баблов¶
В настройках интерфейса пользователя есть переключатель «Вид чатов» — переключает чат с отображения «как карточка задачи» на формат баблов. На поведение редактирования сообщения в этом режиме см. раздел «Редактирование сообщений и быстрые клавиши».
Режим выбора сообщений¶
Переход в режим выбора активирует чекбоксы рядом с каждым сообщением ленты (мультиселект для пересылки или удаления).
Расположение чекбоксов (v2.268+): все чекбоксы выровнены по левому краю ленты (отступ 10px), независимо от стороны бабла. На среднем экране (баблы с разных сторон) и на широком экране (все баблы слева) чекбоксы всегда слева.
Ширина сообщений: ширина блоков баблов учитывает пространство, занятое чекбоксами — при открытом левом сайдбаре баблы не перекрывают чекбоксы и не выходят за границу видимой области.
Поиск по сообщениям¶
Строка поиска в верхней части мессенджера ищет не только чаты, но и сообщения по тексту, имени автора, получателям («Кому», «Копия»). Найденные сообщения выводятся в выпадающем списке с фрагментом и подсветкой совпадения. Выбор результата открывает чат и прокручивает ленту к найденному сообщению — без перехода из текущего чата до этого момента.
Создание личного чата¶
Способы инициировать личный чат:
- Из профиля пользователя — кнопка «Чат». Если у инициатора нет права подписывать целевого пользователя к задачам (внешний пользователь из другой корневой оргструктуры), появится ошибка; администратор снимает ограничение спецправом «Можно подписывать всем».
- Из меню «Сотрудники» — поиск по части имени, фамилии, оргединицы, email или логина; кнопка отправки сообщения при наведении.
- Из поиска в чате — поиск без учёта раскладки. Если активного чата нет — открывается пустой чат. Первым сообщением нельзя отправить только файл без текста.
Если активный чат между пользователями уже существует, создать новый личный чат нельзя — будет открыт существующий. Закрытый личный чат недоступен для повторного открытия: повторное создание породит новый чат, без переноса истории.
Информация о собеседнике¶
В шапке личного чата рядом с именем — номер задачи-чата (#12345) с иконкой копирования. Под именем отображается:
- статус «В сети» или время последней активности;
- статус «На видеозвонке», если собеседник в данный момент в ВКС.
Меню действий чата¶
Кнопка … в правом верхнем углу открытого чата раскрывает меню. Состав пунктов зависит от типа чата (личный/групповой/канал) и текущей вкладки:
| Пункт | Описание |
|---|---|
| Информация | Страница с шапкой (аватар, тип, номер), кнопками управления (Добавить, Встреча, ВКС, Колокольчик, Обсуждения), блоком участников и блоком «Файлы» (см. ниже) |
| Настройки | Боковая панель с настройками чата. В личных чатах заблокирована для редактирования |
| Редактировать название | Только групповые чаты. Зависит от прав на редактирование текста задач в категории |
| Изменить аватар | Только групповые. Загрузить изображение или выбрать цвет/иконку из набора |
| Избранное | Закрепить чат вверху со звёздочкой |
| Убрать из чатов | Удалить из вкладки «Все» (только групповые). Чат остаётся доступен в «Группах»; вернуть — пунктом «Добавить в чаты» из карточки задачи |
| Выключить уведомления | Per-chat выключение |
| Вложения | Окно вложений |
| Выйти | Покинуть групповой чат |
| Закрыть чат | Покинуть личный чат (см. «Закрытие и выход») |
| Конфиденциально | Включить режим, если разрешено в категории |
Страница «Информация»¶
Шапка содержит полное имя чата и аватар. Клик по аватару открывает редактор цвета/иконки (для личных — неактивно). Под шапкой — тип чата и номер, далее кнопки: Добавить (групповые), Встреча, ВКС, Колокольчик (уведомления), Обсуждения (счётчик активных).
Под кнопками — блок участников: клик открывает окно со списком и ролями (Владелец, Администратор, Модератор). Через … рядом с пользователем — назначение роли (Владелец/Администратор → могут назначать; обычные участники не видят).
Далее — блок «Файлы»: список всех файлов из сообщений чата. Скрывается, если в категории запрещено вложение файлов. Файлы попадают в блок только через сообщения — добавить напрямую нельзя.
Окно «Все обсуждения»¶
Кнопка обсуждений в шапке (рядом — счётчик активных) открывает модальное окно. Вкладки:
| Вкладка | Содержимое |
|---|---|
| Все | Все созданные обсуждения чата |
| Активные | Только открытые. ПКМ → «Закрыть обсуждение» |
| Закрытые | Завершённые (серым). ПКМ → «Открыть обсуждение» |
Поиск по названиям обсуждений — в строке вверху окна.
Обсуждение, в котором последнее сообщение было более 2 недель назад, автоматически закрывается.
После создания нового обсуждения сразу открывается оно само; вернуться к чату — клик по его имени в шапке (в личных — клик по имени собеседника).
Сортировка результатов поиска по чатам¶
Глобальный поиск по сообщениям сортирует результаты в порядке:
- Активные чаты 1-на-1 с искомым пользователем.
- Контакты.
- Прочие чаты.
Поиск работает без учёта раскладки клавиатуры; ищет также по названиям обсуждений.
Создание группового чата¶
Создание — кнопкой над списком вкладок или через «Создать» в панели навигации. При создании указывается название чата и участники (отдельные пользователи или группы — изменение состава группы автоматически меняет участников чата).
Под полем ввода названия — счётчик символов с лимитом 128 символов (обратный отсчёт; при достижении нуля ввод блокируется).
Создав групповой чат с единственным участником (собой), можно получить персональный «чат-избранное».
Все групповые чаты по умолчанию имеют системный аватар; владелец может загрузить свой или выбрать цвет и иконку из набора через настройки. Если иконка из набора не найдена, показывается заглушка 404.
Сортировка списка чатов¶
Задачи в списке сортируются по времени последнего сообщения (не по дате создания или обновления задачи). Чаты с последними сообщениями отображаются выше.
Превью сообщений¶
Последнее сообщение отображается в формате однострочного превью: - HTML- и Markdown-разметка убирается — служебные теги и символы разметки не показываются, текст отображается в преобразованном виде - Переносы строк не сохраняются — длинное сообщение показывается в одну строку и при необходимости обрезается многоточием
⚠️ Это особенно важно для автоматически сформированных сообщений, где в исходном тексте могут использоваться переносы строк, списки или элементы форматирования.
Закрепление задач как чатов¶
Обычные задачи можно закрепить в списке чатов для быстрого доступа (актуально для мобильного приложения). Способы закрепления: - Из мессенджера — через контекстное меню - При создании задачи — если включён режим «Закрепить как чат по умолчанию» - Из карточки задачи — через окно «Участники задачи»
Автооткрепление при завершении¶
При завершении задачи она автоматически открепляется из списка чатов у всех участников.
Контекстное меню в списке чатов¶
В списке чатов доступно контекстное меню по правой кнопке мыши. Нажатие ПКМ на чат открывает меню с действиями:
- Открыть в новой вкладке — открыть чат в отдельной вкладке браузера.
- Закрепить — добавляет чат в избранное с жёлтым цветом.
- Скопировать ссылку — скопировать прямую ссылку на чат.
- Скопировать номер — скопировать ID задачи.
- Выйти — для участников чата.
- Удалить — для владельца чата.
- Присоединиться к ВКС — пункт появляется, если в чате идёт видеоконференция.
Типичные тикеты и диагностика¶
"Не могу добавить подписчика в личном чате"¶
Ответ: это корректное поведение. Личный чат (Personal) не поддерживает добавление подписчиков by design.
Диагностика:
1. Определить тип чата: subcatId задачи-чата == ChatSubcatId из конфига → Personal
2. Если пользователь хочет групповой чат — нужно создать новый групповой чат с нужными участниками
-- Определить тип чата
select
t.TaskID,
t.SubcatID,
case
when t.SubcatID = (select top 1 cast(v.Value as int) from Settings v where v.[Key] = 'ChatSubcatId') then 'Personal'
when t.SubcatID = (select top 1 cast(v.Value as int) from Settings v where v.[Key] = 'GroupChatSubCatID') then 'Group'
else 'Channel/SocialNetwork'
end as ChatType
from Tasks t
where t.TaskID = @chatTaskId;
-- Подписчики чата
select
s.UserID,
u.FullName,
s.IsDeleted,
s.CreatedDate
from MailSubscribersUsers s
join Users u
on u.UserID = s.UserID
where s.TaskID = @chatTaskId and
s.IsDeleted = cast(0 as bit)
order by s.CreatedDate;
-- Настройки чата
select *
from ChatSettings
where TaskId = @chatTaskId;
"Не вижу чат в списке"¶
Возможные причины: 1. Пользователь не подписчик чата 2. Чат закрыт (IsClosed) 3. Чат конфиденциальный и пользователь не подписчик 4. Фильтр subcatIds в запросе не включает категорию чата
Открытие ленты: позиция и маркер «новые сообщения»¶
Когда пользователь открывает чат или ленту сообщений задачи, стартовая позиция определяется наличием непрочитанных сообщений.
- Если непрочитанных сообщений нет — лента открывается на последних сообщениях (в нижней части экрана).
- Если в ленте есть непрочитанные сообщения — система переносит пользователя к первому непрочитанному сообщению и показывает маркер «новые сообщения» над ним.
- Если непрочитанных много или первое непрочитанное длинное — пользователь видит начало этого сообщения и маркер «новые сообщения» в верхней части экрана.
- Если сообщений мало или одно короткое и прокрутка ограничена — лента остаётся в нижней части экрана, но маркер «новые сообщения» всё равно сохраняется.
Позиция рассчитывается на стороне сервера по реальной непрочитанности (CommentRecipients.IsUnread). Локальная позиция просмотра на клиенте для этого больше не используется — одинаковое поведение в веб-интерфейсе и при переходе между устройствами.
Поведение маркера при появлении новых сообщений¶
Маркер «новые сообщения» позиционирует ленту один раз — в момент открытия чата. После этого новые сообщения (входящие или отправленные самим пользователем) не возвращают скролл к маркеру.
Дальнейшее поведение ленты подчиняется общему правилу прокрутки: - если пользователь находится в конце ленты, новые сообщения довставляются снизу и видны автоматически; - если пользователь пролистал ленту вверх (выше конца), его позиция сохраняется, а о новых сообщениях сигнализирует кнопка прокрутки вниз с бейджем непрочитанных.
Сам маркер «новые сообщения» остаётся на месте — над первым непрочитанным сообщением на момент открытия чата — и исчезает при следующем открытии после прочтения.
Закреплённые сообщения¶
В чатах поддерживается закрепление важных сообщений для всех участников чата. Закреплённые сообщения отображаются в специальной панели под шапкой чата и в отдельном списке закреплённых сообщений.
Закреплять и откреплять сообщения могут: - владелец чата; - администратор чата; - модератор чата; - участник чата, если в настройках включена опция «Закреплять сообщения для всех».
Закрепление и открепление выполняется из контекстного меню сообщения или через меню по кнопке с тремя точками. После закрепления в чате публикуется системное сообщение о том, кто выполнил закрепление. Такое сообщение не отправляет уведомления участникам.
У закреплённого сообщения отображается специальная иконка. Под шапкой чата выводится панель закреплённых сообщений. В панели может отображаться: - превью вложения, если оно есть у сообщения; - текст сообщения; - счётчик, если закреплено несколько сообщений.
При нажатии на панель выполняется переход к закреплённому сообщению в ленте чата. Если закреплённых сообщений несколько, при последовательных переходах счётчик и превью переключаются на следующее сообщение. При повторном открытии чата счётчик начинается заново.
По кнопке закреплённых сообщений открывается отдельный список всех закреплённых сообщений чата.
Пользователь может скрыть закреплённое сообщение из панели и из своего списка закреплённых сообщений. Это скрытие действует только для конкретного пользователя и не снимает само закрепление: у сообщения остаётся иконка закрепления, а другие участники продолжают видеть его в списке. Скрытое сообщение можно вернуть обратно через меню сообщения в чате или через меню в списке закреплённых сообщений.
Если сообщение закреплено внутри треда чата, оно отображается как закреплённое только внутри этого треда и не попадает в общий список закреплённых сообщений основного чата.
Перекрёстные ссылки¶
docs/domains/chat/backend.md— контроллеры, сервисы, SQL-зависимостиdocs/domains/chat/data-flow.md— сквозные сценарии с диагностикойdocs/domains/comments/business.md— бизнес-правила комментариев (сообщения чата = комментарии)архивное руководство администратора: cats-properties-general.md— конфиденциальность категорийdocs/reference/database/comments.md— структура таблиц Comments, CommentRecipients