Composer restructure: - MessageInput internal footer now only contains: file button, model selector, slash command button, and send button - Removed mode toggle (Code/Plan) from composer area (logic preserved) - New ChatComposerActionBar below input with three slots: left (Design Agent), center (permission selector), right (context indicator) New components: - SlashCommandButton: inserts `/` at cursor, reuses existing slash command detection and popover logic - ChatPermissionSelector: session-level permission toggle (default / full_access) with danger confirmation dialog - ContextUsageIndicator: SVG donut chart showing context usage ratio with HoverCard displaying model, tokens, cache stats - ChatComposerActionBar: slot-based container for action bar items Session permission (permission_profile): - DB: new column on chat_sessions, default 'default' - API: PATCH sessions supports updating permission_profile - Chat API: bypassPermissions = global setting OR session full_access - PermissionPrompt: auto-approves and hides when full_access - Claude client: combines global and per-session bypass flags Bridge integration: - permission-broker: auto-approves permissions for full_access sessions without sending IM notifications - conversation-engine: passes bypassPermissions to SDK for full_access - PATCH route: auto-approves pending bridge permissions on switch to full_access Other: - ImageGenToggle relabeled to "Design Agent" / "设计 Agent" - New model-context.ts with MODEL_CONTEXT_WINDOWS lookup - Provider models API returns optional contextWindow field - New useContextUsage hook for token usage calculation - i18n: added composer.*, permission.*, context.* keys (en + zh) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
64 lines
1.7 KiB
TypeScript
64 lines
1.7 KiB
TypeScript
import { useMemo } from 'react';
|
|
import type { Message } from '@/types';
|
|
import { getContextWindow } from '@/lib/model-context';
|
|
|
|
export interface ContextUsageData {
|
|
modelName: string;
|
|
contextWindow: number | null;
|
|
used: number;
|
|
ratio: number;
|
|
cacheReadTokens: number;
|
|
cacheCreationTokens: number;
|
|
outputTokens: number;
|
|
hasData: boolean;
|
|
}
|
|
|
|
export function useContextUsage(messages: Message[], modelName: string): ContextUsageData {
|
|
return useMemo(() => {
|
|
const contextWindow = getContextWindow(modelName);
|
|
const noData: ContextUsageData = {
|
|
modelName,
|
|
contextWindow,
|
|
used: 0,
|
|
ratio: 0,
|
|
cacheReadTokens: 0,
|
|
cacheCreationTokens: 0,
|
|
outputTokens: 0,
|
|
hasData: false,
|
|
};
|
|
|
|
// Find the last assistant message with token_usage
|
|
for (let i = messages.length - 1; i >= 0; i--) {
|
|
const msg = messages[i];
|
|
if (msg.role !== 'assistant' || !msg.token_usage) continue;
|
|
|
|
try {
|
|
const usage = typeof msg.token_usage === 'string'
|
|
? JSON.parse(msg.token_usage)
|
|
: msg.token_usage;
|
|
|
|
const inputTokens = usage.input_tokens || 0;
|
|
const cacheRead = usage.cache_read_input_tokens || 0;
|
|
const cacheCreation = usage.cache_creation_input_tokens || 0;
|
|
const outputTokens = usage.output_tokens || 0;
|
|
const used = inputTokens + cacheRead + cacheCreation;
|
|
|
|
return {
|
|
modelName,
|
|
contextWindow,
|
|
used,
|
|
ratio: contextWindow ? used / contextWindow : 0,
|
|
cacheReadTokens: cacheRead,
|
|
cacheCreationTokens: cacheCreation,
|
|
outputTokens,
|
|
hasData: true,
|
|
};
|
|
} catch {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
return noData;
|
|
}, [messages, modelName]);
|
|
}
|