feat: integrate ClaudeUsageService and update API routes for usage tracking

This commit is contained in:
Mohamad Yahia
2025-12-21 08:46:11 +04:00
parent f2582c4453
commit ab0487664a
6 changed files with 25 additions and 21 deletions

View File

@@ -1,4 +1,4 @@
import { useState, useEffect, useMemo } from "react";
import { useState, useEffect, useMemo, useCallback } from "react";
import {
Popover,
PopoverContent,
@@ -29,7 +29,7 @@ export function ClaudeUsagePopover() {
return !claudeUsageLastUpdated || Date.now() - claudeUsageLastUpdated > 2 * 60 * 1000;
}, [claudeUsageLastUpdated]);
const fetchUsage = async (isAutoRefresh = false) => {
const fetchUsage = useCallback(async (isAutoRefresh = false) => {
if (!isAutoRefresh) setLoading(true);
setError(null);
try {
@@ -38,7 +38,7 @@ export function ClaudeUsagePopover() {
throw new Error("Claude API not available");
}
const data = await api.claude.getUsage();
if (data.error) {
if ("error" in data) {
throw new Error(data.message || data.error);
}
setClaudeUsage(data);
@@ -47,26 +47,20 @@ export function ClaudeUsagePopover() {
} finally {
if (!isAutoRefresh) setLoading(false);
}
};
}, [setClaudeUsage]);
// Auto-fetch on mount if data is stale
useEffect(() => {
if (isStale) {
fetchUsage(true);
}
}, []);
}, [isStale, fetchUsage]);
useEffect(() => {
// Initial fetch when opened
if (open) {
if (!claudeUsage) {
if (!claudeUsage || isStale) {
fetchUsage();
} else {
const now = Date.now();
const stale = !claudeUsageLastUpdated || now - claudeUsageLastUpdated > 2 * 60 * 1000;
if (stale) {
fetchUsage(false);
}
}
}
@@ -81,7 +75,7 @@ export function ClaudeUsagePopover() {
return () => {
if (intervalId) clearInterval(intervalId);
};
}, [open]);
}, [open, claudeUsage, isStale, claudeRefreshInterval, fetchUsage]);
// Derived status color/icon helper
const getStatusInfo = (percentage: number) => {

View File

@@ -1,5 +1,6 @@
// Type definitions for Electron IPC API
import type { SessionListItem, Message } from "@/types/electron";
import type { ClaudeUsageResponse } from "@/store/app-store";
import { getJSON, setJSON, removeItem } from "./storage";
export interface FileEntry {
@@ -483,7 +484,7 @@ export interface ElectronAPI {
) => Promise<{ success: boolean; error?: string }>;
};
claude?: {
getUsage: () => Promise<ClaudeUsage>;
getUsage: () => Promise<ClaudeUsageResponse>;
};
}

View File

@@ -24,7 +24,7 @@ import type {
SuggestionType,
} from "./electron";
import type { Message, SessionListItem } from "@/types/electron";
import type { Feature } from "@/store/app-store";
import type { Feature, ClaudeUsageResponse } from "@/store/app-store";
import type {
WorktreeAPI,
GitAPI,
@@ -1019,7 +1019,7 @@ export class HttpApiClient implements ElectronAPI {
// Claude API
claude = {
getUsage: (): Promise<any> => this.get("/api/claude/usage"),
getUsage: (): Promise<ClaudeUsageResponse> => this.get("/api/claude/usage"),
};
}

View File

@@ -515,7 +515,7 @@ export interface AppState {
}
// Claude Usage interface matching the server response
export interface ClaudeUsage {
export type ClaudeUsage = {
sessionTokensUsed: number;
sessionLimit: number;
sessionPercentage: number;
@@ -535,7 +535,15 @@ export interface ClaudeUsage {
costUsed: number | null;
costLimit: number | null;
costCurrency: string | null;
}
lastUpdated: string;
userTimezone: string;
};
// Response type for Claude usage API (can be success or error)
export type ClaudeUsageResponse =
| ClaudeUsage
| { error: string; message?: string };
/**
* Check if Claude usage is at its limit (any of: session >= 100%, weekly >= 100%, OR cost >= limit)