fix: prompt caching, conversation length management and token usage display

This commit is contained in:
2026-02-26 20:07:06 +01:00
parent daf8addb53
commit 9149c21bdf
20 changed files with 317 additions and 7 deletions

View File

@@ -123,11 +123,24 @@ export const AssistantSidebar: React.FC = () => {
}
});
const unsubTokenUsage = window.electronAPI?.chat.onTokenUsage((data) => {
if (data.conversationId === conversationId) {
useAppStore.getState().setChatTokenUsage(conversationId, {
inputTokens: data.cumulativeInputTokens,
outputTokens: data.cumulativeOutputTokens,
cacheReadTokens: data.cumulativeCacheReadTokens,
cacheWriteTokens: data.cumulativeCacheWriteTokens,
totalTokens: data.cumulativeTotalTokens,
});
}
});
return () => {
unsubDelta?.();
unsubToolCall?.();
unsubToolResult?.();
unsubTitle?.();
unsubTokenUsage?.();
};
}, [conversationId, appendStreamDelta, recordToolCall, recordToolResult]);

View File

@@ -148,11 +148,24 @@ export const ChatPanel: React.FC<ChatPanelProps> = ({ conversationId }) => {
}
});
const unsubTokenUsage = window.electronAPI?.chat.onTokenUsage((data) => {
if (data.conversationId === conversationId) {
useAppStore.getState().setChatTokenUsage(conversationId, {
inputTokens: data.cumulativeInputTokens,
outputTokens: data.cumulativeOutputTokens,
cacheReadTokens: data.cumulativeCacheReadTokens,
cacheWriteTokens: data.cumulativeCacheWriteTokens,
totalTokens: data.cumulativeTotalTokens,
});
}
});
return () => {
unsubDelta?.();
unsubToolCall?.();
unsubToolResult?.();
unsubTitle?.();
unsubTokenUsage?.();
};
}, [conversationId, loadData, scrollToBottom, checkReady, appendStreamDelta, recordToolCall, recordToolResult]);

View File

@@ -95,6 +95,11 @@
border-radius: 3px;
}
.status-bar-item.token-usage {
font-variant-numeric: tabular-nums;
opacity: 0.85;
}
.status-bar-item.language-badge {
border: 1px solid var(--vscode-statusBar-border, transparent);
border-radius: 3px;

View File

@@ -21,6 +21,9 @@ export const StatusBar: React.FC = () => {
selectedPostId,
totalPosts,
picoTheme,
tabs,
activeTabId,
chatTokenUsage,
} = useAppStore();
const [selectedPostStatus, setSelectedPostStatus] = useState<string | null>(null);
@@ -39,6 +42,10 @@ export const StatusBar: React.FC = () => {
const runningTasks = tasks.filter(t => t.status === 'running');
const activeTheme = getRendererPicoTheme(picoTheme);
// Detect active chat tab and its token usage
const activeTab = tabs.find(tab => tab.id === activeTabId);
const activeChatUsage = activeTab?.type === 'chat' ? chatTokenUsage[activeTab.id] : null;
return (
<div className="status-bar">
<div className="status-bar-left">
@@ -74,6 +81,17 @@ export const StatusBar: React.FC = () => {
<span>{t('statusBar.media', { count: media.length })}</span>
</div>
{/* Token Usage (visible when chat tab is active) */}
{activeChatUsage && (
<div className="status-bar-item token-usage">
<span>{t('statusBar.tokens', {
input: activeChatUsage.inputTokens.toLocaleString(),
output: activeChatUsage.outputTokens.toLocaleString(),
cached: activeChatUsage.cacheReadTokens.toLocaleString(),
})}</span>
</div>
)}
<div className="status-bar-item theme-badge">
<span>{t('statusBar.theme', { theme: activeTheme })}</span>
</div>

View File

@@ -817,6 +817,7 @@
"statusBar.posts": "{count} Beiträge",
"statusBar.media": "{count} Medien",
"statusBar.more": "+{count} weitere",
"statusBar.tokens": "Token: {input} ein / {output} aus ({cached} zwischengesp.)",
"statusBar.theme": "Theme: {theme}",
"statusBar.ui": "UI",
"statusBar.uiLanguage": "UI-Sprache",

View File

@@ -817,6 +817,7 @@
"statusBar.posts": "{count} posts",
"statusBar.media": "{count} media",
"statusBar.more": "+{count} more",
"statusBar.tokens": "Tokens: {input} in / {output} out ({cached} cached)",
"statusBar.theme": "Theme: {theme}",
"statusBar.ui": "UI",
"statusBar.uiLanguage": "UI language",

View File

@@ -817,6 +817,7 @@
"statusBar.posts": "Publicaciones",
"statusBar.media": "Medios",
"statusBar.more": "+{count} más",
"statusBar.tokens": "Tokens: {input} entr. / {output} sal. ({cached} en caché)",
"statusBar.theme": "Tema: {theme}",
"statusBar.ui": "UI",
"statusBar.uiLanguage": "Idioma de la interfaz",

View File

@@ -815,6 +815,7 @@
"statusBar.posts": "Articles",
"statusBar.media": "Médias",
"statusBar.more": "+{count} en plus",
"statusBar.tokens": "Tokens : {input} entr. / {output} sort. ({cached} en cache)",
"statusBar.theme": "Thème : {theme}",
"statusBar.ui": "UI",
"statusBar.uiLanguage": "Langue de linterface",

View File

@@ -815,6 +815,7 @@
"statusBar.posts": "Articoli",
"statusBar.media": "Media",
"statusBar.more": "+{count} in più",
"statusBar.tokens": "Token: {input} ingr. / {output} usc. ({cached} in cache)",
"statusBar.theme": "Tema: {theme}",
"statusBar.ui": "UI",
"statusBar.uiLanguage": "Lingua interfaccia",

View File

@@ -100,6 +100,15 @@ interface AppState {
isLoading: boolean;
error: string | null;
// Chat token usage (keyed by conversationId, ephemeral - not persisted)
chatTokenUsage: Record<string, {
inputTokens: number;
outputTokens: number;
cacheReadTokens: number;
cacheWriteTokens: number;
totalTokens: number;
}>;
// Project Actions
setProjects: (projects: ProjectData[]) => void;
setActiveProject: (project: ProjectData | null) => void;
@@ -160,6 +169,16 @@ interface AppState {
// Loading Actions
setLoading: (loading: boolean) => void;
setError: (error: string | null) => void;
// Chat token usage actions
setChatTokenUsage: (conversationId: string, usage: {
inputTokens: number;
outputTokens: number;
cacheReadTokens: number;
cacheWriteTokens: number;
totalTokens: number;
}) => void;
clearChatTokenUsage: (conversationId: string) => void;
}
export const useAppStore = create<AppState>()(
@@ -212,6 +231,9 @@ export const useAppStore = create<AppState>()(
isLoading: false,
error: null,
// Chat token usage (ephemeral, not persisted)
chatTokenUsage: {},
// Project Actions
setProjects: (projects) => set({ projects }),
setActiveProject: (activeProject) => set({ activeProject }),
@@ -388,6 +410,15 @@ export const useAppStore = create<AppState>()(
// Loading Actions
setLoading: (isLoading) => set({ isLoading }),
setError: (error) => set({ error }),
// Chat token usage actions
setChatTokenUsage: (conversationId, usage) => set((state) => ({
chatTokenUsage: { ...state.chatTokenUsage, [conversationId]: usage },
})),
clearChatTokenUsage: (conversationId) => set((state) => {
const { [conversationId]: _, ...rest } = state.chatTokenUsage;
return { chatTokenUsage: rest };
}),
}),
{
name: STORAGE_KEY,