claudmax 2.0.0 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/claudmax-1.0.16.tgz +0 -0
- package/{packages/cli/index.js → index.js} +2 -0
- package/package.json +27 -55
- package/.claude/settings.local.json +0 -7
- package/.env.example +0 -24
- package/.github/workflows/publish.yml +0 -31
- package/README.md +0 -178
- package/claudmax-mcp-1.0.2.tgz +0 -0
- package/help +0 -0
- package/help-wal +0 -0
- package/next-env.d.ts +0 -6
- package/next.config.mjs +0 -43
- package/packages/cli/claudmax-1.0.16.tgz +0 -0
- package/packages/cli/package.json +0 -33
- package/packages/mcp/claudmax-mcp-1.0.0.tgz +0 -0
- package/packages/mcp/claudmax-mcp-1.0.1.tgz +0 -0
- package/packages/mcp/claudmax-mcp-1.0.2.tgz +0 -0
- package/packages/mcp/claudmax-mcp-1.0.3.tgz +0 -0
- package/packages/mcp/index.js +0 -129
- package/packages/mcp/package-lock.json +0 -1146
- package/packages/mcp/package.json +0 -32
- package/postcss.config.mjs +0 -6
- package/prisma/schema.prisma +0 -130
- package/prisma/seed.ts +0 -27
- package/public/favicon.svg +0 -10
- package/public/robots.txt +0 -10
- package/run_build.sh +0 -4
- package/scripts/migrate-plans.js +0 -98
- package/scripts/seed-blog.ts +0 -1014
- package/src/app/admin/dashboard/AdminDashboardClient.tsx +0 -1546
- package/src/app/admin/dashboard/page.tsx +0 -13
- package/src/app/admin/page.tsx +0 -132
- package/src/app/api/admin/auth/me/route.ts +0 -34
- package/src/app/api/admin/health/route.ts +0 -110
- package/src/app/api/admin/keys/[id]/route.ts +0 -116
- package/src/app/api/admin/keys/route.ts +0 -192
- package/src/app/api/admin/keys-list/route.ts +0 -81
- package/src/app/api/admin/login/route.ts +0 -72
- package/src/app/api/admin/logout/route.ts +0 -8
- package/src/app/api/admin/migrate/route.ts +0 -133
- package/src/app/api/admin/plans/[id]/route.ts +0 -65
- package/src/app/api/admin/plans/route.ts +0 -66
- package/src/app/api/admin/posts/[id]/route.ts +0 -81
- package/src/app/api/admin/posts/route.ts +0 -83
- package/src/app/api/admin/seed/route.ts +0 -145
- package/src/app/api/admin/settings/route.ts +0 -44
- package/src/app/api/admin/stats/route.ts +0 -74
- package/src/app/api/admin/users/[id]/route.ts +0 -166
- package/src/app/api/admin/users/plans/route.ts +0 -45
- package/src/app/api/admin/users/route.ts +0 -202
- package/src/app/api/blog/[slug]/route.ts +0 -22
- package/src/app/api/blog/route.ts +0 -40
- package/src/app/api/cron/daily-status/route.ts +0 -208
- package/src/app/api/support/chat/route.ts +0 -55
- package/src/app/api/support/chat/session/route.ts +0 -62
- package/src/app/api/support/chat/stream/route.ts +0 -44
- package/src/app/api/support/email/route.ts +0 -63
- package/src/app/api/tools/understand_image/route.ts +0 -113
- package/src/app/api/tools/upload/route.ts +0 -179
- package/src/app/api/tools/web_search/route.ts +0 -99
- package/src/app/api/v1/audio/route.ts +0 -67
- package/src/app/api/v1/audio/speech/route.ts +0 -73
- package/src/app/api/v1/chat/completions/route.ts +0 -3
- package/src/app/api/v1/chat/route.ts +0 -1079
- package/src/app/api/v1/images/generations/route.ts +0 -93
- package/src/app/api/v1/info/route.ts +0 -30
- package/src/app/api/v1/key-status/route.ts +0 -109
- package/src/app/api/v1/key-status/stream/route.ts +0 -135
- package/src/app/api/v1/messages/count_tokens/route.ts +0 -22
- package/src/app/api/v1/messages/route.ts +0 -807
- package/src/app/api/v1/models/route.ts +0 -14
- package/src/app/api/v1/route.ts +0 -18
- package/src/app/blog/BlogClient.tsx +0 -193
- package/src/app/blog/[slug]/page.tsx +0 -117
- package/src/app/blog/page.tsx +0 -20
- package/src/app/check-usage/CheckUsageClient.tsx +0 -186
- package/src/app/check-usage/layout.tsx +0 -11
- package/src/app/check-usage/page.tsx +0 -15
- package/src/app/docs/layout.tsx +0 -16
- package/src/app/docs/page.tsx +0 -1055
- package/src/app/faq/FAQClient.tsx +0 -227
- package/src/app/faq/page.tsx +0 -21
- package/src/app/globals.css +0 -75
- package/src/app/layout.tsx +0 -80
- package/src/app/page.tsx +0 -256
- package/src/app/reseller/ResellerClient.tsx +0 -435
- package/src/app/reseller/page.tsx +0 -15
- package/src/app/setup.ps1/route.ts +0 -79
- package/src/app/setup.sh/route.ts +0 -113
- package/src/app/sitemap.ts +0 -50
- package/src/app/status/StatusClient.tsx +0 -103
- package/src/app/status/layout.tsx +0 -11
- package/src/app/status/page.tsx +0 -15
- package/src/app/support/SupportClient.tsx +0 -411
- package/src/app/support/page.tsx +0 -25
- package/src/app/v1/chat/completions/route.ts +0 -3
- package/src/app/v1/chat/route.ts +0 -4
- package/src/app/v1/messages/route.ts +0 -3
- package/src/components/Footer.tsx +0 -120
- package/src/components/Header.tsx +0 -131
- package/src/components/landing/features.tsx +0 -99
- package/src/components/ui/badge.tsx +0 -32
- package/src/components/ui/button.tsx +0 -46
- package/src/components/ui/card.tsx +0 -50
- package/src/components/ui/dialog.tsx +0 -97
- package/src/components/ui/dropdown-menu.tsx +0 -156
- package/src/components/ui/input.tsx +0 -21
- package/src/components/ui/label.tsx +0 -15
- package/src/components/ui/separator.tsx +0 -22
- package/src/components/ui/switch.tsx +0 -27
- package/src/components/ui/tabs.tsx +0 -51
- package/src/components/ui/toast.tsx +0 -103
- package/src/lib/auth.ts +0 -45
- package/src/lib/prisma.ts +0 -20
- package/src/lib/providers.ts +0 -158
- package/src/lib/security.ts +0 -165
- package/src/lib/utils.ts +0 -14
- package/src/middleware.ts +0 -30
- package/tailwind.config.ts +0 -53
- package/tsconfig.json +0 -41
- package/tsconfig.tsbuildinfo +0 -1
- package/vercel.json +0 -8
- /package/{packages/cli/bin → bin}/claudmax.js +0 -0
- /package/{packages/cli/claudmax-1.0.17.tgz → claudmax-1.0.17.tgz} +0 -0
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
import { NextResponse } from 'next/server';
|
|
2
|
-
import { prisma } from '@/lib/prisma';
|
|
3
|
-
|
|
4
|
-
const TIER_LIMITS: Record<string, { requestsPerWindow: number; tokensPerWindow: number }> = {
|
|
5
|
-
free: { requestsPerWindow: 100, tokensPerWindow: 500_000 },
|
|
6
|
-
'5x': { requestsPerWindow: 500, tokensPerWindow: 5_000_000 },
|
|
7
|
-
'20x': { requestsPerWindow: 2000, tokensPerWindow: 20_000_000 },
|
|
8
|
-
unlimited: { requestsPerWindow: 999_999_999, tokensPerWindow: 999_999_999_999 },
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
export async function POST(req: Request) {
|
|
12
|
-
try {
|
|
13
|
-
const apiKey = req.headers.get('x-api-key') ?? req.headers.get('authorization')?.replace('Bearer ', '');
|
|
14
|
-
if (!apiKey) return NextResponse.json({ error: { message: 'Missing API key', type: 'authentication_error' } }, { status: 401 });
|
|
15
|
-
if (!apiKey.startsWith('sk-cmx_')) return NextResponse.json({ error: { message: 'Invalid API key format', type: 'authentication_error' } }, { status: 401 });
|
|
16
|
-
|
|
17
|
-
const key = await prisma.apiKey.findUnique({ where: { key: apiKey } });
|
|
18
|
-
if (!key) return NextResponse.json({ error: { message: 'Invalid API key', type: 'authentication_error' } }, { status: 401 });
|
|
19
|
-
if (!key.isActive) return NextResponse.json({ error: { message: 'API key has been revoked', type: 'authentication_error' } }, { status: 401 });
|
|
20
|
-
|
|
21
|
-
const tier = key.tier ?? 'free';
|
|
22
|
-
const limits = TIER_LIMITS[tier] ?? TIER_LIMITS.free;
|
|
23
|
-
if (key.windowRequestsUsed >= limits.requestsPerWindow) {
|
|
24
|
-
return NextResponse.json({ error: { message: 'Rate limit exceeded', type: 'rate_limit_exceeded' } }, { status: 429 });
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const body = await req.json();
|
|
28
|
-
const { prompt, n = 1, size = '1024x1024', model = 'claude-image-4' } = body;
|
|
29
|
-
|
|
30
|
-
if (!prompt || typeof prompt !== 'string') {
|
|
31
|
-
return NextResponse.json({ error: { message: 'prompt is required', type: 'invalid_request_error' } }, { status: 400 });
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// Parse size (e.g. "1024x1024" or "512x512")
|
|
35
|
-
const [width, height] = (size || '1024x1024').split('x').map(Number);
|
|
36
|
-
const w = Math.min(Math.max(width || 1024, 256), 1440);
|
|
37
|
-
const h = Math.min(Math.max(height || 1024, 256), 1440);
|
|
38
|
-
|
|
39
|
-
const results = [];
|
|
40
|
-
const numImages = Math.min(Math.max(n || 1, 1), 4);
|
|
41
|
-
|
|
42
|
-
for (let i = 0; i < numImages; i++) {
|
|
43
|
-
const encodedPrompt = encodeURIComponent(prompt.slice(0, 1000));
|
|
44
|
-
const imageUrl = `https://image.pollinations.ai/prompt/${encodedPrompt}?width=${w}&height=${h}&seed=${Math.floor(Math.random() * 999999)}&nologo=true`;
|
|
45
|
-
|
|
46
|
-
try {
|
|
47
|
-
const imageRes = await fetch(imageUrl, {
|
|
48
|
-
headers: { 'Accept': 'image/*' },
|
|
49
|
-
signal: AbortSignal.timeout(30000),
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
if (!imageRes.ok) {
|
|
53
|
-
results.push({
|
|
54
|
-
url: null,
|
|
55
|
-
revised_prompt: prompt,
|
|
56
|
-
error: `Image generation failed: ${imageRes.status}`,
|
|
57
|
-
});
|
|
58
|
-
continue;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const imageBuffer = await imageRes.arrayBuffer();
|
|
62
|
-
const base64 = Buffer.from(imageBuffer).toString('base64');
|
|
63
|
-
const contentType = imageRes.headers.get('content-type') ?? 'image/jpeg';
|
|
64
|
-
|
|
65
|
-
results.push({
|
|
66
|
-
url: `data:${contentType};base64,${base64}`,
|
|
67
|
-
revised_prompt: prompt,
|
|
68
|
-
});
|
|
69
|
-
} catch (err) {
|
|
70
|
-
results.push({
|
|
71
|
-
url: null,
|
|
72
|
-
revised_prompt: prompt,
|
|
73
|
-
error: 'Image generation timed out',
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// Count as 1 request
|
|
79
|
-
await prisma.apiKey.update({
|
|
80
|
-
where: { id: key.id },
|
|
81
|
-
data: { lastUsedAt: new Date(), windowRequestsUsed: { increment: 1 } },
|
|
82
|
-
}).catch(() => {});
|
|
83
|
-
|
|
84
|
-
return NextResponse.json({
|
|
85
|
-
created: Math.floor(Date.now() / 1000),
|
|
86
|
-
model: model || 'claude-image-4',
|
|
87
|
-
data: results,
|
|
88
|
-
});
|
|
89
|
-
} catch (err) {
|
|
90
|
-
console.error('[ClaudMax Images error]:', err);
|
|
91
|
-
return NextResponse.json({ error: { message: 'Internal server error', type: 'internal_error' } }, { status: 500 });
|
|
92
|
-
}
|
|
93
|
-
}
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import { NextResponse } from 'next/server';
|
|
2
|
-
|
|
3
|
-
export async function GET() {
|
|
4
|
-
return NextResponse.json({
|
|
5
|
-
name: 'ClaudMax',
|
|
6
|
-
description: 'High-performance Claude API gateway powered by Claude Enterprise. Access Opus 4.6, Sonnet 4.6, and Haiku 4.5 through a unified endpoint.',
|
|
7
|
-
provider: 'ClaudMax',
|
|
8
|
-
version: '1.0.0',
|
|
9
|
-
status: 'operational',
|
|
10
|
-
timestamp: new Date().toISOString(),
|
|
11
|
-
endpoints: {
|
|
12
|
-
messages: '/v1/messages',
|
|
13
|
-
chat: '/v1/chat/completions',
|
|
14
|
-
models: '/v1/models',
|
|
15
|
-
keyStatus: '/v1/key-status',
|
|
16
|
-
},
|
|
17
|
-
models: [
|
|
18
|
-
{ id: 'claude-opus-4-6', name: 'Opus 4.6', context_length: 200000 },
|
|
19
|
-
{ id: 'claude-sonnet-4-6', name: 'Sonnet 4.6', context_length: 200000 },
|
|
20
|
-
{ id: 'claude-haiku-4-5-20251001', name: 'Haiku 4.5', context_length: 200000 },
|
|
21
|
-
{ id: 'claude-sonnet-4-vision', name: 'Sonnet 4 Vision', context_length: 200000 },
|
|
22
|
-
{ id: 'claude-image-4', name: 'Image 4', context_length: 200000 },
|
|
23
|
-
{ id: 'claude-audio-4', name: 'Audio 4', context_length: 200000 },
|
|
24
|
-
],
|
|
25
|
-
});
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export async function HEAD() {
|
|
29
|
-
return new NextResponse(null, { status: 200 });
|
|
30
|
-
}
|
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
import { NextResponse } from 'next/server';
|
|
2
|
-
import { prisma } from '@/lib/prisma';
|
|
3
|
-
|
|
4
|
-
const WINDOW_MS = 5 * 60 * 60 * 1000;
|
|
5
|
-
|
|
6
|
-
const TIER_LIMITS: Record<string, { requestsPerWindow: number; tokensPerWindow: number }> = {
|
|
7
|
-
'5x': { requestsPerWindow: 18000, tokensPerWindow: 5_000_000 },
|
|
8
|
-
'20x': { requestsPerWindow: 18000, tokensPerWindow: 20_000_000 },
|
|
9
|
-
unlimited: { requestsPerWindow: 999_999_999, tokensPerWindow: 999_999_999_999 },
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
export async function POST(req: Request) {
|
|
13
|
-
try {
|
|
14
|
-
const { apiKey } = await req.json();
|
|
15
|
-
if (!apiKey) {
|
|
16
|
-
return NextResponse.json({ error: 'API key is required' }, { status: 400 });
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const key = await prisma.apiKey.findUnique({
|
|
20
|
-
where: { key: apiKey },
|
|
21
|
-
select: {
|
|
22
|
-
id: true,
|
|
23
|
-
name: true,
|
|
24
|
-
prefix: true,
|
|
25
|
-
tier: true,
|
|
26
|
-
isActive: true,
|
|
27
|
-
displayMultiplier: true,
|
|
28
|
-
lastUsedAt: true,
|
|
29
|
-
windowStartAt: true,
|
|
30
|
-
windowTokensUsed: true,
|
|
31
|
-
windowRequestsUsed: true,
|
|
32
|
-
totalTokensUsed: true,
|
|
33
|
-
createdAt: true,
|
|
34
|
-
},
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
if (!key) {
|
|
38
|
-
return NextResponse.json({ error: 'Invalid API key' }, { status: 401 });
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const tier = key.tier ?? '5x';
|
|
42
|
-
const limits = TIER_LIMITS[tier] ?? TIER_LIMITS['5x'];
|
|
43
|
-
const multiplier = key.displayMultiplier ?? 3.0;
|
|
44
|
-
const effectiveTokenLimit = Math.floor(limits.tokensPerWindow * multiplier);
|
|
45
|
-
|
|
46
|
-
const now = Date.now();
|
|
47
|
-
let windowStart = key.windowStartAt?.getTime() ?? now;
|
|
48
|
-
let windowTokens = Number(key.windowTokensUsed);
|
|
49
|
-
let windowRequests = key.windowRequestsUsed;
|
|
50
|
-
|
|
51
|
-
// Window reset check
|
|
52
|
-
if (now - windowStart > WINDOW_MS) {
|
|
53
|
-
windowStart = now;
|
|
54
|
-
windowTokens = 0;
|
|
55
|
-
windowRequests = 0;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
const windowResetAt = windowStart + WINDOW_MS;
|
|
59
|
-
const tokensUsedDisplay = Math.floor(windowTokens * multiplier);
|
|
60
|
-
const tokensLeftDisplay = Math.max(0, effectiveTokenLimit - tokensUsedDisplay);
|
|
61
|
-
const requestsLeft = Math.max(0, limits.requestsPerWindow - windowRequests);
|
|
62
|
-
const windowResetMs = Math.max(0, windowResetAt - now);
|
|
63
|
-
|
|
64
|
-
return NextResponse.json({
|
|
65
|
-
key: key.id,
|
|
66
|
-
name: key.name,
|
|
67
|
-
prefix: key.prefix,
|
|
68
|
-
tier,
|
|
69
|
-
isActive: key.isActive,
|
|
70
|
-
displayMultiplier: multiplier,
|
|
71
|
-
|
|
72
|
-
// Window state
|
|
73
|
-
windowStartAt: new Date(windowStart).toISOString(),
|
|
74
|
-
windowResetAt: new Date(windowResetAt).toISOString(),
|
|
75
|
-
windowResetMs,
|
|
76
|
-
|
|
77
|
-
// Requests
|
|
78
|
-
requestsUsed: windowRequests,
|
|
79
|
-
requestsLimit: limits.requestsPerWindow,
|
|
80
|
-
requestsLeft,
|
|
81
|
-
requestsPerMinute: 60,
|
|
82
|
-
|
|
83
|
-
// Tokens — displayed to customer (multiplied)
|
|
84
|
-
tokensUsed: tokensUsedDisplay,
|
|
85
|
-
tokensLimit: effectiveTokenLimit,
|
|
86
|
-
tokensLeft: tokensLeftDisplay,
|
|
87
|
-
|
|
88
|
-
// Tokens — actual backend usage (hidden from customer, for admin)
|
|
89
|
-
tokensUsedActual: windowTokens,
|
|
90
|
-
tokensLimitActual: limits.tokensPerWindow,
|
|
91
|
-
|
|
92
|
-
// Lifetime
|
|
93
|
-
totalTokensUsed: Number(key.totalTokensUsed),
|
|
94
|
-
|
|
95
|
-
createdAt: key.createdAt.toISOString(),
|
|
96
|
-
lastUsedAt: key.lastUsedAt?.toISOString() ?? null,
|
|
97
|
-
});
|
|
98
|
-
} catch (err) {
|
|
99
|
-
// If DB is unavailable, return a graceful fallback response
|
|
100
|
-
console.error('[ClaudMax] Key status error:', err);
|
|
101
|
-
try {
|
|
102
|
-
const { apiKey } = await req.clone().json();
|
|
103
|
-
if (apiKey) {
|
|
104
|
-
return NextResponse.json({ error: 'Service temporarily unavailable. Please try again.', code: 'DB_UNAVAILABLE' }, { status: 503 });
|
|
105
|
-
}
|
|
106
|
-
} catch { /* ignore */ }
|
|
107
|
-
return NextResponse.json({ error: 'Invalid API key' }, { status: 401 });
|
|
108
|
-
}
|
|
109
|
-
}
|
|
@@ -1,135 +0,0 @@
|
|
|
1
|
-
import { NextResponse } from 'next/server';
|
|
2
|
-
import { prisma } from '@/lib/prisma';
|
|
3
|
-
|
|
4
|
-
const WINDOW_MS = 5 * 60 * 60 * 1000;
|
|
5
|
-
|
|
6
|
-
const TIER_LIMITS: Record<string, { requestsPerWindow: number; tokensPerWindow: number }> = {
|
|
7
|
-
'5x': { requestsPerWindow: 18000, tokensPerWindow: 5_000_000 },
|
|
8
|
-
'20x': { requestsPerWindow: 18000, tokensPerWindow: 20_000_000 },
|
|
9
|
-
unlimited: { requestsPerWindow: 999_999_999, tokensPerWindow: 999_999_999_999 },
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
async function getKeyStatus(apiKeyId: string) {
|
|
13
|
-
const key = await prisma.apiKey.findUnique({
|
|
14
|
-
where: { id: apiKeyId },
|
|
15
|
-
select: {
|
|
16
|
-
id: true, name: true, prefix: true, tier: true, isActive: true,
|
|
17
|
-
displayMultiplier: true, lastUsedAt: true, windowStartAt: true,
|
|
18
|
-
windowTokensUsed: true, windowRequestsUsed: true, totalTokensUsed: true,
|
|
19
|
-
},
|
|
20
|
-
});
|
|
21
|
-
if (!key) return null;
|
|
22
|
-
|
|
23
|
-
const tier = key.tier ?? '5x';
|
|
24
|
-
const limits = TIER_LIMITS[tier] ?? TIER_LIMITS['5x'];
|
|
25
|
-
const multiplier = key.displayMultiplier ?? 3.0;
|
|
26
|
-
const effectiveTokenLimit = Math.floor(limits.tokensPerWindow * multiplier);
|
|
27
|
-
|
|
28
|
-
const now = Date.now();
|
|
29
|
-
let windowStart = key.windowStartAt?.getTime() ?? now;
|
|
30
|
-
let windowTokens = Number(key.windowTokensUsed);
|
|
31
|
-
let windowRequests = key.windowRequestsUsed;
|
|
32
|
-
|
|
33
|
-
if (now - windowStart > WINDOW_MS) {
|
|
34
|
-
windowStart = now;
|
|
35
|
-
windowTokens = 0;
|
|
36
|
-
windowRequests = 0;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const windowResetAt = windowStart + WINDOW_MS;
|
|
40
|
-
const tokensUsedDisplay = Math.floor(windowTokens * multiplier);
|
|
41
|
-
const tokensLeftDisplay = Math.max(0, effectiveTokenLimit - tokensUsedDisplay);
|
|
42
|
-
const requestsLeft = Math.max(0, limits.requestsPerWindow - windowRequests);
|
|
43
|
-
const windowResetMs = Math.max(0, windowResetAt - now);
|
|
44
|
-
|
|
45
|
-
return {
|
|
46
|
-
windowStartAt: new Date(windowStart).toISOString(),
|
|
47
|
-
windowResetAt: new Date(windowResetAt).toISOString(),
|
|
48
|
-
windowResetMs,
|
|
49
|
-
requestsUsed: windowRequests,
|
|
50
|
-
requestsLimit: limits.requestsPerWindow,
|
|
51
|
-
requestsLeft,
|
|
52
|
-
tokensUsed: tokensUsedDisplay,
|
|
53
|
-
tokensLimit: effectiveTokenLimit,
|
|
54
|
-
tokensLeft: tokensLeftDisplay,
|
|
55
|
-
tokensUsedActual: windowTokens,
|
|
56
|
-
lastUsedAt: key.lastUsedAt?.toISOString() ?? null,
|
|
57
|
-
displayMultiplier: multiplier,
|
|
58
|
-
isActive: key.isActive,
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export async function POST(req: Request) {
|
|
63
|
-
try {
|
|
64
|
-
const { apiKey } = await req.json();
|
|
65
|
-
if (!apiKey || !apiKey.startsWith('sk-cmx_')) {
|
|
66
|
-
return NextResponse.json({ error: 'Invalid API key' }, { status: 401 });
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const key = await prisma.apiKey.findUnique({ where: { key: apiKey }, select: { id: true } });
|
|
70
|
-
if (!key) return NextResponse.json({ error: 'Invalid API key' }, { status: 401 });
|
|
71
|
-
|
|
72
|
-
let closed = false;
|
|
73
|
-
let lastStatus = '';
|
|
74
|
-
|
|
75
|
-
const stream = new ReadableStream({
|
|
76
|
-
async start(controller) {
|
|
77
|
-
const send = (data: object) => {
|
|
78
|
-
if (closed) return;
|
|
79
|
-
const line = `data: ${JSON.stringify(data)}\n\n`;
|
|
80
|
-
controller.enqueue(new TextEncoder().encode(line));
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
// Send initial state immediately
|
|
84
|
-
const initial = await getKeyStatus(key.id);
|
|
85
|
-
if (initial) send({ type: 'update', ...initial });
|
|
86
|
-
lastStatus = initial ? JSON.stringify(initial) : '';
|
|
87
|
-
|
|
88
|
-
// Poll every 2 seconds for up to 60 seconds
|
|
89
|
-
let count = 0;
|
|
90
|
-
const interval = setInterval(async () => {
|
|
91
|
-
if (closed || count >= 30) {
|
|
92
|
-
clearInterval(interval);
|
|
93
|
-
if (!closed) {
|
|
94
|
-
controller.enqueue(new TextEncoder().encode('data: {"type":"done"}\n\n'));
|
|
95
|
-
controller.close();
|
|
96
|
-
}
|
|
97
|
-
return;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const status = await getKeyStatus(key.id);
|
|
101
|
-
if (!status) {
|
|
102
|
-
clearInterval(interval);
|
|
103
|
-
if (!closed) {
|
|
104
|
-
controller.enqueue(new TextEncoder().encode('data: {"type":"error","message":"Key not found"}\n\n'));
|
|
105
|
-
controller.close();
|
|
106
|
-
}
|
|
107
|
-
return;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
const statusStr = JSON.stringify(status);
|
|
111
|
-
if (statusStr !== lastStatus) {
|
|
112
|
-
lastStatus = statusStr;
|
|
113
|
-
send({ type: 'update', ...status });
|
|
114
|
-
}
|
|
115
|
-
count++;
|
|
116
|
-
}, 2000);
|
|
117
|
-
},
|
|
118
|
-
cancel() {
|
|
119
|
-
closed = true;
|
|
120
|
-
},
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
return new Response(stream, {
|
|
124
|
-
headers: {
|
|
125
|
-
'Content-Type': 'text/event-stream',
|
|
126
|
-
'Cache-Control': 'no-cache',
|
|
127
|
-
'Connection': 'keep-alive',
|
|
128
|
-
'X-Accel-Buffering': 'no',
|
|
129
|
-
},
|
|
130
|
-
});
|
|
131
|
-
} catch (err) {
|
|
132
|
-
console.error('[ClaudMax] Key status stream error:', err);
|
|
133
|
-
return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
|
|
134
|
-
}
|
|
135
|
-
}
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { NextResponse } from 'next/server';
|
|
2
|
-
|
|
3
|
-
export async function POST(req: Request) {
|
|
4
|
-
try {
|
|
5
|
-
const { model, messages } = await req.json();
|
|
6
|
-
|
|
7
|
-
// Simple token estimation (≈ 4 chars per token for English)
|
|
8
|
-
let totalChars = 0;
|
|
9
|
-
for (const msg of messages ?? []) {
|
|
10
|
-
totalChars += (msg.content?.toString() ?? '').length;
|
|
11
|
-
}
|
|
12
|
-
const estimatedTokens = Math.ceil(totalChars / 4);
|
|
13
|
-
|
|
14
|
-
return NextResponse.json({
|
|
15
|
-
tokens: estimatedTokens,
|
|
16
|
-
model,
|
|
17
|
-
cached_tokens: 0,
|
|
18
|
-
});
|
|
19
|
-
} catch {
|
|
20
|
-
return NextResponse.json({ error: 'Invalid request' }, { status: 400 });
|
|
21
|
-
}
|
|
22
|
-
}
|