mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-03 21:03:08 +00:00
feat: integrate ClaudeUsageService and update API routes for usage tracking
This commit is contained in:
@@ -45,6 +45,7 @@ import { getTerminalService } from "./services/terminal-service.js";
|
|||||||
import { SettingsService } from "./services/settings-service.js";
|
import { SettingsService } from "./services/settings-service.js";
|
||||||
import { createSpecRegenerationRoutes } from "./routes/app-spec/index.js";
|
import { createSpecRegenerationRoutes } from "./routes/app-spec/index.js";
|
||||||
import { createClaudeRoutes } from "./routes/claude/index.js";
|
import { createClaudeRoutes } from "./routes/claude/index.js";
|
||||||
|
import { ClaudeUsageService } from "./services/claude-usage-service.js";
|
||||||
|
|
||||||
// Load environment variables
|
// Load environment variables
|
||||||
dotenv.config();
|
dotenv.config();
|
||||||
@@ -112,6 +113,7 @@ const agentService = new AgentService(DATA_DIR, events);
|
|||||||
const featureLoader = new FeatureLoader();
|
const featureLoader = new FeatureLoader();
|
||||||
const autoModeService = new AutoModeService(events);
|
const autoModeService = new AutoModeService(events);
|
||||||
const settingsService = new SettingsService(DATA_DIR);
|
const settingsService = new SettingsService(DATA_DIR);
|
||||||
|
const claudeUsageService = new ClaudeUsageService();
|
||||||
|
|
||||||
// Initialize services
|
// Initialize services
|
||||||
(async () => {
|
(async () => {
|
||||||
@@ -142,7 +144,7 @@ app.use("/api/workspace", createWorkspaceRoutes());
|
|||||||
app.use("/api/templates", createTemplatesRoutes());
|
app.use("/api/templates", createTemplatesRoutes());
|
||||||
app.use("/api/terminal", createTerminalRoutes());
|
app.use("/api/terminal", createTerminalRoutes());
|
||||||
app.use("/api/settings", createSettingsRoutes(settingsService));
|
app.use("/api/settings", createSettingsRoutes(settingsService));
|
||||||
app.use("/api/claude", createClaudeRoutes());
|
app.use("/api/claude", createClaudeRoutes(claudeUsageService));
|
||||||
|
|
||||||
// Create HTTP server
|
// Create HTTP server
|
||||||
const server = createServer(app);
|
const server = createServer(app);
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import { Router, Request, Response } from "express";
|
import { Router, Request, Response } from "express";
|
||||||
import { ClaudeUsageService } from "../../services/claude-usage-service.js";
|
import { ClaudeUsageService } from "../../services/claude-usage-service.js";
|
||||||
|
|
||||||
export function createClaudeRoutes(): Router {
|
export function createClaudeRoutes(service: ClaudeUsageService): Router {
|
||||||
const router = Router();
|
const router = Router();
|
||||||
const service = new ClaudeUsageService();
|
|
||||||
|
|
||||||
// Get current usage (fetches from Claude CLI)
|
// Get current usage (fetches from Claude CLI)
|
||||||
router.get("/usage", async (req: Request, res: Response) => {
|
router.get("/usage", async (req: Request, res: Response) => {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useState, useEffect, useMemo } from "react";
|
import { useState, useEffect, useMemo, useCallback } from "react";
|
||||||
import {
|
import {
|
||||||
Popover,
|
Popover,
|
||||||
PopoverContent,
|
PopoverContent,
|
||||||
@@ -29,7 +29,7 @@ export function ClaudeUsagePopover() {
|
|||||||
return !claudeUsageLastUpdated || Date.now() - claudeUsageLastUpdated > 2 * 60 * 1000;
|
return !claudeUsageLastUpdated || Date.now() - claudeUsageLastUpdated > 2 * 60 * 1000;
|
||||||
}, [claudeUsageLastUpdated]);
|
}, [claudeUsageLastUpdated]);
|
||||||
|
|
||||||
const fetchUsage = async (isAutoRefresh = false) => {
|
const fetchUsage = useCallback(async (isAutoRefresh = false) => {
|
||||||
if (!isAutoRefresh) setLoading(true);
|
if (!isAutoRefresh) setLoading(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
try {
|
try {
|
||||||
@@ -38,7 +38,7 @@ export function ClaudeUsagePopover() {
|
|||||||
throw new Error("Claude API not available");
|
throw new Error("Claude API not available");
|
||||||
}
|
}
|
||||||
const data = await api.claude.getUsage();
|
const data = await api.claude.getUsage();
|
||||||
if (data.error) {
|
if ("error" in data) {
|
||||||
throw new Error(data.message || data.error);
|
throw new Error(data.message || data.error);
|
||||||
}
|
}
|
||||||
setClaudeUsage(data);
|
setClaudeUsage(data);
|
||||||
@@ -47,26 +47,20 @@ export function ClaudeUsagePopover() {
|
|||||||
} finally {
|
} finally {
|
||||||
if (!isAutoRefresh) setLoading(false);
|
if (!isAutoRefresh) setLoading(false);
|
||||||
}
|
}
|
||||||
};
|
}, [setClaudeUsage]);
|
||||||
|
|
||||||
// Auto-fetch on mount if data is stale
|
// Auto-fetch on mount if data is stale
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isStale) {
|
if (isStale) {
|
||||||
fetchUsage(true);
|
fetchUsage(true);
|
||||||
}
|
}
|
||||||
}, []);
|
}, [isStale, fetchUsage]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Initial fetch when opened
|
// Initial fetch when opened
|
||||||
if (open) {
|
if (open) {
|
||||||
if (!claudeUsage) {
|
if (!claudeUsage || isStale) {
|
||||||
fetchUsage();
|
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 () => {
|
return () => {
|
||||||
if (intervalId) clearInterval(intervalId);
|
if (intervalId) clearInterval(intervalId);
|
||||||
};
|
};
|
||||||
}, [open]);
|
}, [open, claudeUsage, isStale, claudeRefreshInterval, fetchUsage]);
|
||||||
|
|
||||||
// Derived status color/icon helper
|
// Derived status color/icon helper
|
||||||
const getStatusInfo = (percentage: number) => {
|
const getStatusInfo = (percentage: number) => {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// Type definitions for Electron IPC API
|
// Type definitions for Electron IPC API
|
||||||
import type { SessionListItem, Message } from "@/types/electron";
|
import type { SessionListItem, Message } from "@/types/electron";
|
||||||
|
import type { ClaudeUsageResponse } from "@/store/app-store";
|
||||||
import { getJSON, setJSON, removeItem } from "./storage";
|
import { getJSON, setJSON, removeItem } from "./storage";
|
||||||
|
|
||||||
export interface FileEntry {
|
export interface FileEntry {
|
||||||
@@ -483,7 +484,7 @@ export interface ElectronAPI {
|
|||||||
) => Promise<{ success: boolean; error?: string }>;
|
) => Promise<{ success: boolean; error?: string }>;
|
||||||
};
|
};
|
||||||
claude?: {
|
claude?: {
|
||||||
getUsage: () => Promise<ClaudeUsage>;
|
getUsage: () => Promise<ClaudeUsageResponse>;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import type {
|
|||||||
SuggestionType,
|
SuggestionType,
|
||||||
} from "./electron";
|
} from "./electron";
|
||||||
import type { Message, SessionListItem } from "@/types/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 {
|
import type {
|
||||||
WorktreeAPI,
|
WorktreeAPI,
|
||||||
GitAPI,
|
GitAPI,
|
||||||
@@ -1019,7 +1019,7 @@ export class HttpApiClient implements ElectronAPI {
|
|||||||
|
|
||||||
// Claude API
|
// Claude API
|
||||||
claude = {
|
claude = {
|
||||||
getUsage: (): Promise<any> => this.get("/api/claude/usage"),
|
getUsage: (): Promise<ClaudeUsageResponse> => this.get("/api/claude/usage"),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -515,7 +515,7 @@ export interface AppState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Claude Usage interface matching the server response
|
// Claude Usage interface matching the server response
|
||||||
export interface ClaudeUsage {
|
export type ClaudeUsage = {
|
||||||
sessionTokensUsed: number;
|
sessionTokensUsed: number;
|
||||||
sessionLimit: number;
|
sessionLimit: number;
|
||||||
sessionPercentage: number;
|
sessionPercentage: number;
|
||||||
@@ -535,7 +535,15 @@ export interface ClaudeUsage {
|
|||||||
costUsed: number | null;
|
costUsed: number | null;
|
||||||
costLimit: number | null;
|
costLimit: number | null;
|
||||||
costCurrency: string | 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)
|
* Check if Claude usage is at its limit (any of: session >= 100%, weekly >= 100%, OR cost >= limit)
|
||||||
|
|||||||
Reference in New Issue
Block a user