From dca85e1137ad42d67d9c1a61994eb462494d2ba3 Mon Sep 17 00:00:00 2001 From: Cosmo Date: Thu, 16 Apr 2026 15:42:24 +0000 Subject: [PATCH] feat: Claude subscription usage widget with real OAuth data --- src/app/(dashboard)/page.tsx | 3 +- src/app/api/claude-sub/route.ts | 57 +++++++++------------------------ 2 files changed, 17 insertions(+), 43 deletions(-) diff --git a/src/app/(dashboard)/page.tsx b/src/app/(dashboard)/page.tsx index 8d486c9..33d4513 100644 --- a/src/app/(dashboard)/page.tsx +++ b/src/app/(dashboard)/page.tsx @@ -1,10 +1,10 @@ export const dynamic = "force-dynamic"; - import { WeatherWidget } from "@/components/widgets/WeatherWidget"; import { CalendarWidget } from "@/components/widgets/CalendarWidget"; import { DashboardHeader } from "@/components/widgets/DashboardHeader"; import { SavingsWidget } from "@/components/widgets/SavingsWidget"; import { GitActivityWidget } from "@/components/widgets/GitActivityWidget"; +import { ClaudeUsageWidget } from "@/components/widgets/ClaudeUsageWidget"; export default function DashboardPage() { return ( @@ -18,6 +18,7 @@ export default function DashboardPage() {
+
diff --git a/src/app/api/claude-sub/route.ts b/src/app/api/claude-sub/route.ts index 0c3a82a..ffa3c7a 100644 --- a/src/app/api/claude-sub/route.ts +++ b/src/app/api/claude-sub/route.ts @@ -1,61 +1,34 @@ export const dynamic = "force-dynamic"; import { NextResponse } from "next/server"; -// Bridge HTTP API (cosmo-studio bridge в той же сети coolify) -const BRIDGE_URL = "http://172.18.0.5:3402/api/usage"; +const PROXY_URL = "http://192.168.31.103:3301/claude-oauth-usage"; export async function GET() { try { - const res = await fetch(BRIDGE_URL, { - signal: AbortSignal.timeout(5000), + const res = await fetch(PROXY_URL, { + signal: AbortSignal.timeout(8000), cache: "no-store", }); - - if (!res.ok) throw new Error(`Bridge HTTP ${res.status}`); + if (!res.ok) throw new Error(`Proxy HTTP ${res.status}`); const data = await res.json(); - const usage = data.usage; - if (!usage) { - return NextResponse.json({ ok: false, error: "No usage data yet" }); - } - - // Найти данные Anthropic - const anthropicProvider = usage.planLimits?.providers?.find( - (p: any) => p.provider === "anthropic" - ); - const windows = anthropicProvider?.windows || []; - const window5h = windows.find((w: any) => w.label?.includes("5") || w.label === "5h"); - const windowWeek = windows.find((w: any) => w.label?.toLowerCase().includes("week")); - const windowSonnet = windows.find((w: any) => w.label?.toLowerCase().includes("sonnet")); - - // Стоимость сегодня - const todayEntry = usage.cost?.daily?.find((d: any) => { - const today = new Date().toISOString().split("T")[0]; - return d.date === today; - }); + const toWindow = (obj: any) => obj ? { + label: obj.label || "", + usedPercent: obj.utilization ?? 0, + resetAt: obj.resets_at ? new Date(obj.resets_at).getTime() : undefined, + } : null; return NextResponse.json({ ok: true, anthropic: { - window5h: window5h || null, - windowWeek: windowWeek || null, - windowSonnet: windowSonnet || null, - error: anthropicProvider?.error || null, - plan: anthropicProvider?.plan || null, + window5h: toWindow(data.five_hour), + windowWeek: toWindow(data.seven_day), + windowSonnet: toWindow(data.seven_day_sonnet), + error: null, }, - cost: { - today: todayEntry?.totalCost || 0, - todayTokens: todayEntry?.totalTokens || 0, - total: usage.cost?.totals?.totalCost || 0, - }, - updatedAt: usage.updatedAt, - }); - } catch (e) { - return NextResponse.json({ - ok: false, - error: String(e), - anthropic: null, cost: null, }); + } catch (e) { + return NextResponse.json({ ok: false, error: String(e), anthropic: null, cost: null }); } }