Фича: кнопка автогенерации названия заметки через AI #199
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Задача
Добавить в редактор страницы кнопку, по нажатию которой содержимое заметки отправляется AI-агенту с просьбой сгенерировать заголовок, и полученное название применяется к странице.
Зафиксированные продуктовые решения:
generateText(отдельный лёгкий серверный эндпоинт, как существующийgenerateTitleдля чатов), без стриминга, инструментов и истории.updatePage).Что переиспользуем (исследование)
В проекте уже есть зрелая AI-инфраструктура — фича собирается из существующих кирпичей:
apps/server/src/integrations/ai/ai.service.ts—AiService.getChatModel(workspaceId)разбирается с драйвером/ключами/baseURL и кидаетAiNotConfiguredException(HTTP 503), если AI не настроен.generateTitle()вapps/server/src/core/ai-chat/ai-chat.service.ts(~L797) — ровно то, что нужно, только для первого сообщения чата.transcribeвapps/server/src/core/ai-chat/ai-chat.controller.ts(~L254) — аутентифицированный, gated черезsettings.ai.*, throttled черезAI_CHAT_THROTTLER, возвращает чистый JSON и мапит ошибки провайдера черезdescribeProviderError.apps/client/src/features/dictation/services/dictation-service.ts+ мутации вapps/client/src/features/ai-chat/queries/ai-chat-query.ts(React Query +notifications.show).apps/client/src/features/editor/title-editor.tsx— отдельное поле, сохраняется мутациейuseUpdateTitlePageMutation→POST /pages/update(валидацияvalidateCanEdit). Запись названия напрямую не конфликтует с коллаборативным редактированием тела.pageEditorчерезpageEditorAtom; markdown получаетсяhtmlToMarkdown(pageEditor.getHTML())(утилита@docmost/editor-ext).DictationGroupрендерится вPageBylineвнутриapps/client/src/features/editor/full-editor.tsx(~L238), только в режиме редактирования и при включённом флаге воркспейса.Поток данных (end-to-end)
Ключевое решение: серверный эндпоинт только суммаризирует переданный текст и не трогает страницу — он не читает БД и не пишет заголовок. Фактическая запись названия идёт через существующий
POST /pages/update, который сам валидирует право на редактирование (validateCanEdit). Это разделяет ответственность и убирает дублирование проверок доступа.Бэкенд
(a) DTO —
apps/server/src/core/ai-chat/dto/ai-chat.dto.ts(b) Сервисный метод —
apps/server/src/core/ai-chat/ai-chat.service.ts(c) Маршрут контроллера —
apps/server/src/core/ai-chat/ai-chat.controller.tsВыбор фича-флага (мелкое открытое решение): рекомендуется переиспользовать существующий
settings.ai.generative(он уже семантически = «генеративные AI-фичи на странице», им gatedAskAiGroup) — тогда не нужно трогать админ-UI настроек. Альтернатива — отдельныйsettings.ai.titleGenдля независимого включения (потребует правкиai-provider-settings.tsxи схемы настроек).Фронтенд
(a) Сервисная функция —
apps/client/src/features/ai-chat/services/ai-chat-service.ts(b) Хук-обёртка «сгенерировать + применить» — новый
apps/client/src/features/editor/hooks/use-generate-page-title.ts(c) Компонент кнопки — новый
apps/client/src/features/editor/components/fixed-toolbar/groups/generate-title-group.tsx(d) Монтаж в байлайн —
apps/client/src/features/editor/full-editor.tsxПробросить
pageIdвPageByline, добавитьisTitleGenEnabled = workspace?.settings?.ai?.generative === trueи отрисовать кнопку рядом сDictationGroupв той же<Group gap={4}>(~L225–241), под тем же условием видимостиeditable && isEditMode.(e) i18n —
apps/client/public/locales/en-US/translation.jsonиru-RU/translation.jsonКлючи:
"Generate title with AI","Title generated","Failed to generate title","The note is empty","Could not generate a title"(+ русские: «Сгенерировать название через AI», «Название сгенерировано», «Не удалось сгенерировать название», «Заметка пустая», «Не удалось придумать название»).Точные точки изменений
apps/server/src/core/ai-chat/dto/ai-chat.dto.tsGeneratePageTitleDtoapps/server/src/core/ai-chat/ai-chat.service.tsgeneratePageTitle(workspaceId, content)apps/server/src/core/ai-chat/ai-chat.controller.ts@Post('generate-page-title')(gate + throttle + error-map)apps/client/src/features/ai-chat/services/ai-chat-service.tsgeneratePageTitle(content)apps/client/src/features/editor/hooks/use-generate-page-title.tsuseGeneratePageTitle(pageId).../fixed-toolbar/groups/generate-title-group.tsxGenerateTitleGroupapps/client/src/features/editor/full-editor.tsxpageIdвPageByline+ рендер кнопки рядом сDictationGroupapplyPageTitleхелперTitleEditor.saveTitleen-US/ru-RUtranslation.jsonai-provider-settings.tsx+ схема настроекsettings.ai.titleGenКраевые случаи и тонкости
getChatModelкидаетAiNotConfiguredException(503) → дружелюбное уведомление.AI_CHAT_THROTTLER(20/мин на пользователя).getHTML()из живого редактора → модель видит самые свежие правки тела.pageEditor.getHTML()отдаёт только тело (заголовок — отдельный редактор), промпт не «заражается» старым названием.setContentидёт черезHistory-расширение титульного редактора →Ctrl/Cmd+Zоткатывает автозамену.validateCanEdit.