Files
gitmost/apps/server/src/integrations/ai/dto/update-ai-settings.dto.ts
claude_code 9b61024b95 feat(ai-chat): header badge shows current/max context, max from AI settings (#189)
The floating chat window's header badge flipped meaning — a live per-turn token
counter while streaming, the persisted context size at rest — so it "reset to 1"
on each prompt and conflated two different numbers. Replace it with a stable
"current / max" context badge (e.g. `572 / 200k`). The live "Thinking · N tokens"
inside the chat body stays; only the duplicate live counter is removed from the
header.

Max comes from a new admin setting "Context window (tokens)". The server resolves
it and attaches `maxContextTokens` to the completed assistant turn's metadata
(next to contextTokens), so the badge needs no client-side model resolution and
this survives public shares / per-role models.

Server:
- ai.types: chatContextWindow on AiProviderSettings + PROVIDER_SETTINGS_KEYS +
  ResolvedAiConfig + MaskedAiSettings.
- workspace.repo: chatContextWindow in AI_PROVIDER_SETTINGS_ALLOWED (parity).
- update-ai-settings.dto: @IsInt @Min(0) chatContextWindow.
- ai-settings.service: coerce the ::text-stored value to a positive int in
  resolve()/getMasked().
- ai-chat.service: flushAssistant writes metadata.maxContextTokens (>0); the
  completed turn passes resolved.chatContextWindow.

Client:
- ai-chat.types: maxContextTokens on the message-row metadata.
- ai-chat-window: read maxContextTokens; render "current [/ max]"; drop the
  liveTurnTokens state/branch and the onLiveTurnTokens prop; new tooltip.
- chat-thread: remove the live-turn-token throttle effect and plumbing.
- count-stream-tokens: drop the now-dead liveTurnTokens()/types; keep
  estimateTokens.
- settings: chatContextWindow on IAiSettings(+Update) + a NumberInput in the AI
  provider settings form.

i18n: add the badge/settings keys (en, ru); remove the two now-unused keys.
Tests: flushAssistant maxContextTokens, DTO validation, trim token tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 22:39:09 +03:00

96 lines
2.0 KiB
TypeScript

import { IsIn, IsInt, IsOptional, IsString, Min } from 'class-validator';
import {
AI_DRIVERS,
AiDriver,
CHAT_API_STYLES,
ChatApiStyle,
STT_API_STYLES,
SttApiStyle,
} from '../ai.types';
/**
* Admin update payload for the workspace AI provider settings.
*
* `apiKey` / `embeddingApiKey` / `sttApiKey` are write-only (§8.2): provided →
* stored encrypted, '' → cleared, absent → left untouched. They are NEVER
* returned by any endpoint. The global ValidationPipe runs with
* `whitelist: true`, so unknown fields are stripped.
*/
export class UpdateAiSettingsDto {
@IsOptional()
@IsIn(AI_DRIVERS)
driver?: AiDriver;
@IsOptional()
@IsString()
chatModel?: string;
// Max context window in tokens shown in the chat header badge. 0/empty =
// clear the limit (no denominator shown).
@IsOptional()
@IsInt()
@Min(0)
chatContextWindow?: number;
@IsOptional()
@IsIn(CHAT_API_STYLES)
chatApiStyle?: ChatApiStyle;
@IsOptional()
@IsString()
embeddingModel?: string;
@IsOptional()
@IsString()
baseUrl?: string;
@IsOptional()
@IsString()
embeddingBaseUrl?: string;
@IsOptional()
@IsString()
systemPrompt?: string;
@IsOptional()
@IsString()
apiKey?: string;
@IsOptional()
@IsString()
embeddingApiKey?: string;
@IsOptional()
@IsString()
sttModel?: string;
@IsOptional()
@IsString()
sttBaseUrl?: string;
@IsOptional()
@IsIn(STT_API_STYLES)
sttApiStyle?: SttApiStyle;
// ISO-639-1 dictation language hint (e.g. 'en', 'ru'). Empty = auto-detect.
@IsOptional()
@IsString()
sttLanguage?: string;
@IsOptional()
@IsString()
sttApiKey?: string;
// Cheap model id for the anonymous public-share assistant; reuses the chat
// driver/baseUrl/apiKey. Empty → the assistant falls back to chatModel.
@IsOptional()
@IsString()
publicShareChatModel?: string;
// Agent-role id whose persona the anonymous public-share assistant adopts;
// empty/unset = built-in locked persona.
@IsOptional()
@IsString()
publicShareAssistantRoleId?: string;
}