mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-03 21:03:08 +00:00
fix: Update error handling in ClaudeUsagePopover and improve type safety in app-store
This commit is contained in:
@@ -17,37 +17,65 @@ import { cn } from "@/lib/utils";
|
|||||||
import { getElectronAPI } from "@/lib/electron";
|
import { getElectronAPI } from "@/lib/electron";
|
||||||
import { useAppStore } from "@/store/app-store";
|
import { useAppStore } from "@/store/app-store";
|
||||||
|
|
||||||
|
// Error codes for distinguishing failure modes
|
||||||
|
const ERROR_CODES = {
|
||||||
|
API_BRIDGE_UNAVAILABLE: "API_BRIDGE_UNAVAILABLE",
|
||||||
|
AUTH_ERROR: "AUTH_ERROR",
|
||||||
|
UNKNOWN: "UNKNOWN",
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
type ErrorCode = (typeof ERROR_CODES)[keyof typeof ERROR_CODES];
|
||||||
|
|
||||||
|
type UsageError = {
|
||||||
|
code: ErrorCode;
|
||||||
|
message: string;
|
||||||
|
};
|
||||||
|
|
||||||
export function ClaudeUsagePopover() {
|
export function ClaudeUsagePopover() {
|
||||||
const { claudeRefreshInterval, claudeUsage, claudeUsageLastUpdated, setClaudeUsage } =
|
const { claudeRefreshInterval, claudeUsage, claudeUsageLastUpdated, setClaudeUsage } =
|
||||||
useAppStore();
|
useAppStore();
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<UsageError | null>(null);
|
||||||
|
|
||||||
// Check if data is stale (older than 2 minutes) - recalculates when claudeUsageLastUpdated changes
|
// Check if data is stale (older than 2 minutes) - recalculates when claudeUsageLastUpdated changes
|
||||||
const isStale = useMemo(() => {
|
const isStale = useMemo(() => {
|
||||||
return !claudeUsageLastUpdated || Date.now() - claudeUsageLastUpdated > 2 * 60 * 1000;
|
return !claudeUsageLastUpdated || Date.now() - claudeUsageLastUpdated > 2 * 60 * 1000;
|
||||||
}, [claudeUsageLastUpdated]);
|
}, [claudeUsageLastUpdated]);
|
||||||
|
|
||||||
const fetchUsage = useCallback(async (isAutoRefresh = false) => {
|
const fetchUsage = useCallback(
|
||||||
if (!isAutoRefresh) setLoading(true);
|
async (isAutoRefresh = false) => {
|
||||||
setError(null);
|
if (!isAutoRefresh) setLoading(true);
|
||||||
try {
|
setError(null);
|
||||||
const api = getElectronAPI();
|
try {
|
||||||
if (!api.claude) {
|
const api = getElectronAPI();
|
||||||
throw new Error("Claude API not available");
|
if (!api.claude) {
|
||||||
|
setError({
|
||||||
|
code: ERROR_CODES.API_BRIDGE_UNAVAILABLE,
|
||||||
|
message: 'Claude API bridge not available',
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const data = await api.claude.getUsage();
|
||||||
|
if ('error' in data) {
|
||||||
|
setError({
|
||||||
|
code: ERROR_CODES.AUTH_ERROR,
|
||||||
|
message: data.message || data.error,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setClaudeUsage(data);
|
||||||
|
} catch (err) {
|
||||||
|
setError({
|
||||||
|
code: ERROR_CODES.UNKNOWN,
|
||||||
|
message: err instanceof Error ? err.message : 'Failed to fetch usage',
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
if (!isAutoRefresh) setLoading(false);
|
||||||
}
|
}
|
||||||
const data = await api.claude.getUsage();
|
},
|
||||||
if ("error" in data) {
|
[setClaudeUsage]
|
||||||
throw new Error(data.message || data.error);
|
);
|
||||||
}
|
|
||||||
setClaudeUsage(data);
|
|
||||||
} catch (err) {
|
|
||||||
setError(err instanceof Error ? err.message : "Failed to fetch usage");
|
|
||||||
} finally {
|
|
||||||
if (!isAutoRefresh) setLoading(false);
|
|
||||||
}
|
|
||||||
}, [setClaudeUsage]);
|
|
||||||
|
|
||||||
// Auto-fetch on mount if data is stale
|
// Auto-fetch on mount if data is stale
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -79,11 +107,10 @@ export function ClaudeUsagePopover() {
|
|||||||
|
|
||||||
// Derived status color/icon helper
|
// Derived status color/icon helper
|
||||||
const getStatusInfo = (percentage: number) => {
|
const getStatusInfo = (percentage: number) => {
|
||||||
if (percentage >= 80)
|
if (percentage >= 75) return { color: 'text-red-500', icon: XCircle, bg: 'bg-red-500' };
|
||||||
return { color: "text-red-500", icon: XCircle, bg: "bg-red-500" };
|
|
||||||
if (percentage >= 50)
|
if (percentage >= 50)
|
||||||
return { color: "text-orange-500", icon: AlertTriangle, bg: "bg-orange-500" };
|
return { color: 'text-orange-500', icon: AlertTriangle, bg: 'bg-orange-500' };
|
||||||
return { color: "text-green-500", icon: CheckCircle, bg: "bg-green-500" };
|
return { color: 'text-green-500', icon: CheckCircle, bg: 'bg-green-500' };
|
||||||
};
|
};
|
||||||
|
|
||||||
// Helper component for the progress bar
|
// Helper component for the progress bar
|
||||||
@@ -175,8 +202,8 @@ export function ClaudeUsagePopover() {
|
|||||||
: 0;
|
: 0;
|
||||||
|
|
||||||
const getProgressBarColor = (percentage: number) => {
|
const getProgressBarColor = (percentage: number) => {
|
||||||
if (percentage >= 100) return "bg-red-500";
|
if (percentage >= 80) return 'bg-red-500';
|
||||||
if (percentage >= 75) return "bg-yellow-500";
|
if (percentage >= 50) return 'bg-yellow-500';
|
||||||
return "bg-green-500";
|
return "bg-green-500";
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -220,7 +247,7 @@ export function ClaudeUsagePopover() {
|
|||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon"
|
size="icon"
|
||||||
className={cn("h-6 w-6", loading && "opacity-80")}
|
className={cn('h-6 w-6', loading && 'opacity-80')}
|
||||||
onClick={() => !loading && fetchUsage(false)}
|
onClick={() => !loading && fetchUsage(false)}
|
||||||
>
|
>
|
||||||
<RefreshCw className="w-3.5 h-3.5" />
|
<RefreshCw className="w-3.5 h-3.5" />
|
||||||
@@ -233,9 +260,16 @@ export function ClaudeUsagePopover() {
|
|||||||
<div className="flex flex-col items-center justify-center py-6 text-center space-y-3">
|
<div className="flex flex-col items-center justify-center py-6 text-center space-y-3">
|
||||||
<AlertTriangle className="w-8 h-8 text-yellow-500/80" />
|
<AlertTriangle className="w-8 h-8 text-yellow-500/80" />
|
||||||
<div className="space-y-1 flex flex-col items-center">
|
<div className="space-y-1 flex flex-col items-center">
|
||||||
<p className="text-sm font-medium">{error}</p>
|
<p className="text-sm font-medium">{error.message}</p>
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-muted-foreground">
|
||||||
Make sure Claude CLI is installed and authenticated via <code className="font-mono bg-muted px-1 rounded">claude login</code>
|
{error.code === ERROR_CODES.API_BRIDGE_UNAVAILABLE ? (
|
||||||
|
'Ensure the Electron bridge is running or restart the app'
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
Make sure Claude CLI is installed and authenticated via{' '}
|
||||||
|
<code className="font-mono bg-muted px-1 rounded">claude login</code>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -279,7 +313,7 @@ export function ClaudeUsagePopover() {
|
|||||||
{claudeUsage.costLimit && claudeUsage.costLimit > 0 && (
|
{claudeUsage.costLimit && claudeUsage.costLimit > 0 && (
|
||||||
<UsageCard
|
<UsageCard
|
||||||
title="Extra Usage"
|
title="Extra Usage"
|
||||||
subtitle={`${claudeUsage.costUsed ?? 0} / ${claudeUsage.costLimit} ${claudeUsage.costCurrency ?? ""}`}
|
subtitle={`${claudeUsage.costUsed ?? 0} / ${claudeUsage.costLimit} ${claudeUsage.costCurrency ?? ''}`}
|
||||||
percentage={
|
percentage={
|
||||||
claudeUsage.costLimit > 0
|
claudeUsage.costLimit > 0
|
||||||
? ((claudeUsage.costUsed ?? 0) / claudeUsage.costLimit) * 100
|
? ((claudeUsage.costUsed ?? 0) / claudeUsage.costLimit) * 100
|
||||||
|
|||||||
@@ -1230,7 +1230,7 @@ export const useAppStore = create<AppState & AppActions>()(
|
|||||||
const id =
|
const id =
|
||||||
feature.id ||
|
feature.id ||
|
||||||
`feature-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
`feature-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
||||||
const featureWithId = { ...feature, id } as Feature;
|
const featureWithId = { ...feature, id } as unknown as Feature;
|
||||||
set({ features: [...get().features, featureWithId] });
|
set({ features: [...get().features, featureWithId] });
|
||||||
return featureWithId;
|
return featureWithId;
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user