[feature][ai-chat] Бейдж контекста в шапке: показывать «текущий / максимум»; максимум — из настроек AI #189
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-чата (бейдж справа от имени роли) меняет смысл в зависимости от состояния:
liveTurnTokens— оценка токенов, сгенерированных в текущем ходе (reasoning + output), тикает ~8 Гц, тултип «Tokens generated this turn»;contextTokens— текущий размер контекста (сколько диалог занимает в окне модели), тултип «Current context size».Из-за этого при отправке большого промпта бейдж «сбрасывается на 1» и считает заново: во время хода он считает только генерацию модели (ввод пользователя туда вообще не входит) и стартует с нуля, а после хода снова показывает размер контекста. Одно и то же место без видимой подписи показывает две разные величины — это путает.
Живой фидбек «модель работает / сколько надумала» уже есть внутри тела чата — блок «Thinking · N tokens» (
ReasoningBlock, тикает live), плюс готовится live-reasoning по #178. Значит дублирующий live-счётчик в шапке не нужен.Текущее поведение (где в коде)
apps/client/src/features/ai-chat/components/ai-chat-window.tsx(~строки 497–514) — веткаliveTurnTokens > 0 ? ... : contextTokens > 0 ? ....useMemo~290–307 — берётmetadata.contextTokensиз последней ассистентской строки (фолбэк наusage.totalTokens).apps/client/src/features/ai-chat/utils/count-stream-tokens.ts(liveTurnTokens()), продюсер —apps/client/src/features/ai-chat/components/chat-thread.tsx(эффект ~335–368, пропonLiveTurnTokens), стейт —ai-chat-window.tsx:168.contextTokensна сервере:apps/server/src/core/ai-chat/ai-chat.service.ts—flushAssistant(..., { contextTokens })(~612–619), сборка метаданных вflushAssistant(~1218–1256).chatModelчерез драйверыopenai/gemini/ollama(apps/server/src/integrations/ai/ai.types.ts), может переопределяться на уровне роли (role.modelConfig.chatModel), а клиент-окно вообще не знает, какая модель используется.Желаемое поведение
Бейдж в шапке всегда показывает текущий / максимум контекста, например
572 / 200k:Принятое решение: источник максимума
Единого надёжного способа узнать окно контекста для всех драйверов нет (
/v1/modelsу OpenAI обычно не отдаёт лимит; у Gemini —inputTokenLimit, у Ollama —/api/show, у OpenRouter —context_length; всё несовместимо). Поэтому максимум берётся из нового поля в настройках AI — админ задаёт число (токены) вручную. Плюсы: всегда точно, провайдер-независимо, минимум кода. Минус: ручное заполнение (приемлемо).Отвергнутые на v1 альтернативы: статическая таблица «id модели → окно» (надо поддерживать, не угадывает кастомные id), рантайм-запрос к провайдеру (много провайдер-специфичного кода, не работает для чистого OpenAI/произвольного совместимого), гибрид (больше всего работы). При желании их можно добавить позже поверх этого поля.
Технически число доводится до клиента через метаданные ассистентского сообщения (рядом с
contextTokens): сервер при финализации хода уже имеет резолвнутый конфиг (resolvedвai-chat.service.ts:338) и кладётmaxContextTokensв ту же метадату. Бейдж читает оба числа из последней строки — никакой новой клиентской логики резолва модели, и это автоматически переживёт публичные шары и (в будущем) пер-ролевые модели.План реализации
Сервер
apps/server/src/integrations/ai/ai.types.ts: добавитьchatContextWindow?: numberвAiProviderSettings, вPROVIDER_SETTINGS_KEYS, вMaskedAiSettingsи вResolvedAiConfig.apps/server/src/database/repos/workspace/workspace.repo.ts: добавить ключchatContextWindowв parity-копиюAI_PROVIDER_SETTINGS_ALLOWED(иначе значение молча отбрасывается на SQL-границе; покрытоai-provider-settings-keys.spec.ts).apps/server/src/integrations/ai/dto/update-ai-settings.dto.ts: новое поле@IsOptional() @IsInt() @Min(0) chatContextWindow?: number(0/пусто = очистить лимит).apps/server/src/integrations/ai/ai-settings.service.ts: прокинуть поле вresolve()иgetMasked().apps/server/src/core/ai-chat/ai-chat.service.ts: вflushAssistantрасширитьextraполемmaxContextTokens?: numberи писатьmetadata.maxContextTokens, когда оно > 0; вstreamChatпередатьresolved.chatContextWindowв финализацию завершённого хода (~612–619).Клиент
apps/client/src/features/ai-chat/types/ai-chat.types.ts: добавитьmaxContextTokens?: numberвIAiChatMessageRow.metadata(с комментарием о семантике рядом сcontextTokens).apps/client/src/features/ai-chat/components/ai-chat-window.tsx: читатьmaxContextTokensиз той же последней строки; заменить ре��дер бейджа на «текущий [/ максимум]» (formatTokens(ctx)+ приmax>0—/ formatTokens(max)); убрать веткуliveTurnTokens, стейтliveTurnTokensи пропonLiveTurnTokens; обновить тултип.apps/client/src/features/ai-chat/components/chat-thread.tsx: удалить эффект/пропonLiveTurnTokens(~335–368, 75, 120) и импортliveTurnTokens(строка 23).count-stream-tokens.ts: файл оставить —estimateTokensиспользуется вReasoningBlock; функцияliveTurnTokens()остаётся без вызовов — удалить её вместе с её тестом, либо оставить как util (решить при ревью; рекомендация — удалить мёртвый код).Настройки (UI)
apps/client/src/features/workspace/services/ai-settings-service.ts: добавитьchatContextWindow?: numberвIAiSettingsиIAiSettingsUpdate.apps/client/src/pages/settings/workspace/ai-settings.tsx: числовое поле «Context window (tokens)» с подсказкой «показывает использовано/всего в шапке чата; пусто = скрыть лимит».i18n / Тесты
t()(тултип бейджа, лейбл и подсказка в настройках) — в локали.chatStreamMetadata/flushAssistant(пробросmaxContextTokens), маппинг в settings-сервисе, parity-тест ключей, рендер бейджа «ctx / max» и отсутствие live-режима; обновить существующие тесты, завязанные на старый live-бейдж.Краевые случаи
context > max(дрейф оценки или роль с меньшей реальной моделью) → показываем как есть (210k / 200k), без клампа; можно подсветить — на усмотрение.maxContextTokens→ знаменатель скрыт.Вне скоупа (v1)
RoleModelConfigсейчас несёт только{driver, chatModel}).Критерии приёмки
текущий / максимум(или только текущий, если лимит пуст).Ghost referenced this issue2026-06-25 23:50:05 +03:00
Ghost referenced this issue2026-06-25 23:50:19 +03:00
Ghost referenced this issue2026-06-26 17:39:55 +03:00