fix: prompt caching, conversation length management and token usage display
This commit is contained in:
@@ -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]);
|
||||
|
||||
|
||||
@@ -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]);
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 l’interface",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user