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.
Files changed (124) hide show
  1. package/claudmax-1.0.16.tgz +0 -0
  2. package/{packages/cli/index.js → index.js} +2 -0
  3. package/package.json +27 -55
  4. package/.claude/settings.local.json +0 -7
  5. package/.env.example +0 -24
  6. package/.github/workflows/publish.yml +0 -31
  7. package/README.md +0 -178
  8. package/claudmax-mcp-1.0.2.tgz +0 -0
  9. package/help +0 -0
  10. package/help-wal +0 -0
  11. package/next-env.d.ts +0 -6
  12. package/next.config.mjs +0 -43
  13. package/packages/cli/claudmax-1.0.16.tgz +0 -0
  14. package/packages/cli/package.json +0 -33
  15. package/packages/mcp/claudmax-mcp-1.0.0.tgz +0 -0
  16. package/packages/mcp/claudmax-mcp-1.0.1.tgz +0 -0
  17. package/packages/mcp/claudmax-mcp-1.0.2.tgz +0 -0
  18. package/packages/mcp/claudmax-mcp-1.0.3.tgz +0 -0
  19. package/packages/mcp/index.js +0 -129
  20. package/packages/mcp/package-lock.json +0 -1146
  21. package/packages/mcp/package.json +0 -32
  22. package/postcss.config.mjs +0 -6
  23. package/prisma/schema.prisma +0 -130
  24. package/prisma/seed.ts +0 -27
  25. package/public/favicon.svg +0 -10
  26. package/public/robots.txt +0 -10
  27. package/run_build.sh +0 -4
  28. package/scripts/migrate-plans.js +0 -98
  29. package/scripts/seed-blog.ts +0 -1014
  30. package/src/app/admin/dashboard/AdminDashboardClient.tsx +0 -1546
  31. package/src/app/admin/dashboard/page.tsx +0 -13
  32. package/src/app/admin/page.tsx +0 -132
  33. package/src/app/api/admin/auth/me/route.ts +0 -34
  34. package/src/app/api/admin/health/route.ts +0 -110
  35. package/src/app/api/admin/keys/[id]/route.ts +0 -116
  36. package/src/app/api/admin/keys/route.ts +0 -192
  37. package/src/app/api/admin/keys-list/route.ts +0 -81
  38. package/src/app/api/admin/login/route.ts +0 -72
  39. package/src/app/api/admin/logout/route.ts +0 -8
  40. package/src/app/api/admin/migrate/route.ts +0 -133
  41. package/src/app/api/admin/plans/[id]/route.ts +0 -65
  42. package/src/app/api/admin/plans/route.ts +0 -66
  43. package/src/app/api/admin/posts/[id]/route.ts +0 -81
  44. package/src/app/api/admin/posts/route.ts +0 -83
  45. package/src/app/api/admin/seed/route.ts +0 -145
  46. package/src/app/api/admin/settings/route.ts +0 -44
  47. package/src/app/api/admin/stats/route.ts +0 -74
  48. package/src/app/api/admin/users/[id]/route.ts +0 -166
  49. package/src/app/api/admin/users/plans/route.ts +0 -45
  50. package/src/app/api/admin/users/route.ts +0 -202
  51. package/src/app/api/blog/[slug]/route.ts +0 -22
  52. package/src/app/api/blog/route.ts +0 -40
  53. package/src/app/api/cron/daily-status/route.ts +0 -208
  54. package/src/app/api/support/chat/route.ts +0 -55
  55. package/src/app/api/support/chat/session/route.ts +0 -62
  56. package/src/app/api/support/chat/stream/route.ts +0 -44
  57. package/src/app/api/support/email/route.ts +0 -63
  58. package/src/app/api/tools/understand_image/route.ts +0 -113
  59. package/src/app/api/tools/upload/route.ts +0 -179
  60. package/src/app/api/tools/web_search/route.ts +0 -99
  61. package/src/app/api/v1/audio/route.ts +0 -67
  62. package/src/app/api/v1/audio/speech/route.ts +0 -73
  63. package/src/app/api/v1/chat/completions/route.ts +0 -3
  64. package/src/app/api/v1/chat/route.ts +0 -1079
  65. package/src/app/api/v1/images/generations/route.ts +0 -93
  66. package/src/app/api/v1/info/route.ts +0 -30
  67. package/src/app/api/v1/key-status/route.ts +0 -109
  68. package/src/app/api/v1/key-status/stream/route.ts +0 -135
  69. package/src/app/api/v1/messages/count_tokens/route.ts +0 -22
  70. package/src/app/api/v1/messages/route.ts +0 -807
  71. package/src/app/api/v1/models/route.ts +0 -14
  72. package/src/app/api/v1/route.ts +0 -18
  73. package/src/app/blog/BlogClient.tsx +0 -193
  74. package/src/app/blog/[slug]/page.tsx +0 -117
  75. package/src/app/blog/page.tsx +0 -20
  76. package/src/app/check-usage/CheckUsageClient.tsx +0 -186
  77. package/src/app/check-usage/layout.tsx +0 -11
  78. package/src/app/check-usage/page.tsx +0 -15
  79. package/src/app/docs/layout.tsx +0 -16
  80. package/src/app/docs/page.tsx +0 -1055
  81. package/src/app/faq/FAQClient.tsx +0 -227
  82. package/src/app/faq/page.tsx +0 -21
  83. package/src/app/globals.css +0 -75
  84. package/src/app/layout.tsx +0 -80
  85. package/src/app/page.tsx +0 -256
  86. package/src/app/reseller/ResellerClient.tsx +0 -435
  87. package/src/app/reseller/page.tsx +0 -15
  88. package/src/app/setup.ps1/route.ts +0 -79
  89. package/src/app/setup.sh/route.ts +0 -113
  90. package/src/app/sitemap.ts +0 -50
  91. package/src/app/status/StatusClient.tsx +0 -103
  92. package/src/app/status/layout.tsx +0 -11
  93. package/src/app/status/page.tsx +0 -15
  94. package/src/app/support/SupportClient.tsx +0 -411
  95. package/src/app/support/page.tsx +0 -25
  96. package/src/app/v1/chat/completions/route.ts +0 -3
  97. package/src/app/v1/chat/route.ts +0 -4
  98. package/src/app/v1/messages/route.ts +0 -3
  99. package/src/components/Footer.tsx +0 -120
  100. package/src/components/Header.tsx +0 -131
  101. package/src/components/landing/features.tsx +0 -99
  102. package/src/components/ui/badge.tsx +0 -32
  103. package/src/components/ui/button.tsx +0 -46
  104. package/src/components/ui/card.tsx +0 -50
  105. package/src/components/ui/dialog.tsx +0 -97
  106. package/src/components/ui/dropdown-menu.tsx +0 -156
  107. package/src/components/ui/input.tsx +0 -21
  108. package/src/components/ui/label.tsx +0 -15
  109. package/src/components/ui/separator.tsx +0 -22
  110. package/src/components/ui/switch.tsx +0 -27
  111. package/src/components/ui/tabs.tsx +0 -51
  112. package/src/components/ui/toast.tsx +0 -103
  113. package/src/lib/auth.ts +0 -45
  114. package/src/lib/prisma.ts +0 -20
  115. package/src/lib/providers.ts +0 -158
  116. package/src/lib/security.ts +0 -165
  117. package/src/lib/utils.ts +0 -14
  118. package/src/middleware.ts +0 -30
  119. package/tailwind.config.ts +0 -53
  120. package/tsconfig.json +0 -41
  121. package/tsconfig.tsbuildinfo +0 -1
  122. package/vercel.json +0 -8
  123. /package/{packages/cli/bin → bin}/claudmax.js +0 -0
  124. /package/{packages/cli/claudmax-1.0.17.tgz → claudmax-1.0.17.tgz} +0 -0
@@ -1,179 +0,0 @@
1
- import { NextResponse } from 'next/server';
2
- import { prisma } from '@/lib/prisma';
3
-
4
- const OPENROUTER_API_URL = 'https://openrouter.ai/api/v1/chat/completions';
5
-
6
- async function validateKey(apiKey: string) {
7
- if (!apiKey?.startsWith('sk-cmx_')) return { error: 'Invalid API key', status: 401 };
8
- const key = await prisma.apiKey.findUnique({ where: { key: apiKey } });
9
- if (!key) return { error: 'Invalid API key', status: 401 };
10
- if (!key.isActive) return { error: 'API key revoked', status: 401 };
11
- return { key };
12
- }
13
-
14
- function toBase64(arrayBuffer: ArrayBuffer): string {
15
- const bytes = new Uint8Array(arrayBuffer);
16
- let binary = '';
17
- for (let i = 0; i < bytes.length; i++) {
18
- binary += String.fromCharCode(bytes[i]);
19
- }
20
- return btoa(binary);
21
- }
22
-
23
- function getMimeType(file: File | Blob): string {
24
- if ('type' in file && file.type) return file.type;
25
- const name = ('name' in file ? file.name : 'file') as string;
26
- const ext = name.split('.').pop()?.toLowerCase() ?? '';
27
- const map: Record<string, string> = {
28
- pdf: 'application/pdf',
29
- txt: 'text/plain',
30
- md: 'text/markdown',
31
- json: 'application/json',
32
- html: 'text/html',
33
- css: 'text/css',
34
- js: 'application/javascript',
35
- csv: 'text/csv',
36
- png: 'image/png',
37
- jpg: 'image/jpeg',
38
- jpeg: 'image/jpeg',
39
- gif: 'image/gif',
40
- webp: 'image/webp',
41
- svg: 'image/svg+xml',
42
- mp3: 'audio/mpeg',
43
- mp4: 'video/mp4',
44
- };
45
- return map[ext] ?? 'application/octet-stream';
46
- }
47
-
48
- function isImage(mimeType: string): boolean {
49
- return ['image/png', 'image/jpeg', 'image/jpg', 'image/gif', 'image/webp', 'image/svg+xml'].includes(mimeType);
50
- }
51
-
52
- function isText(mimeType: string): boolean {
53
- return mimeType.startsWith('text/') || ['application/json', 'application/javascript'].includes(mimeType);
54
- }
55
-
56
- export async function POST(req: Request) {
57
- try {
58
- const apiKey = req.headers.get('x-api-key') ?? req.headers.get('authorization')?.replace('Bearer ', '');
59
- if (!apiKey) {
60
- return NextResponse.json({ error: 'Missing API key' }, { status: 401 });
61
- }
62
-
63
- const val = await validateKey(apiKey);
64
- if ('error' in val) {
65
- return NextResponse.json({ error: val.error }, { status: val.status });
66
- }
67
-
68
- // Support both JSON and multipart
69
- const contentType = req.headers.get('content-type') ?? '';
70
- let files: Array<{ name: string; mimeType: string; base64: string; size: number }> = [];
71
- let prompt = 'Describe or analyze this file.';
72
-
73
- if (contentType.includes('multipart/form-data')) {
74
- const formData = await req.formData();
75
- prompt = (formData.get('prompt') as string | null) ?? prompt;
76
-
77
- for (const [key, value] of formData.entries()) {
78
- if (key === 'prompt') continue;
79
- if (value instanceof File) {
80
- const buffer = await value.arrayBuffer();
81
- const base64 = toBase64(buffer);
82
- files.push({
83
- name: value.name,
84
- mimeType: getMimeType(value),
85
- base64,
86
- size: value.size,
87
- });
88
- }
89
- }
90
- } else {
91
- // JSON mode: {"files": [{"name": "...", "mime_type": "...", "data": "base64..."}]}
92
- const body = await req.json();
93
- files = (body.files ?? []).map((f: Record<string, unknown>) => ({
94
- name: (f.name as string) ?? 'file',
95
- mimeType: (f.mime_type as string) ?? 'application/octet-stream',
96
- base64: f.data as string,
97
- size: (f.data as string).length,
98
- }));
99
- prompt = (body.prompt as string | null) ?? prompt;
100
- }
101
-
102
- if (files.length === 0) {
103
- return NextResponse.json({ error: 'At least one file is required' }, { status: 400 });
104
- }
105
-
106
- const processed: Array<{
107
- name: string;
108
- type: string;
109
- content?: string;
110
- description?: string;
111
- error?: string;
112
- }> = [];
113
-
114
- for (const file of files) {
115
- if (isImage(file.mimeType)) {
116
- // Send to vision model
117
- const visionRes = await fetch(OPENROUTER_API_URL, {
118
- method: 'POST',
119
- headers: {
120
- 'Authorization': `Bearer ${process.env.OPENROUTER_API_KEY}`,
121
- 'Content-Type': 'application/json',
122
- 'HTTP-Referer': process.env.OPENROUTER_SITE_URL ?? 'https://claudmax.pro',
123
- 'X-Title': 'ClaudMax',
124
- },
125
- body: JSON.stringify({
126
- model: 'qwen/qwen3.6-plus:free',
127
- messages: [
128
- {
129
- role: 'user',
130
- content: [
131
- { type: 'image_url', image_url: { url: `data:${file.mimeType};base64,${file.base64}` } },
132
- { type: 'text', text: prompt },
133
- ],
134
- },
135
- ],
136
- max_tokens: 2048,
137
- temperature: 0.3,
138
- }),
139
- });
140
-
141
- if (!visionRes.ok) {
142
- const err = await visionRes.json().catch(() => ({}));
143
- processed.push({ name: file.name, type: 'image', error: (err as any)?.error?.message ?? 'Image analysis failed' });
144
- continue;
145
- }
146
-
147
- const data = await visionRes.json();
148
- const description = data.choices?.[0]?.message?.content ?? 'Unable to analyze image.';
149
- processed.push({ name: file.name, type: 'image', description, content: description });
150
- } else if (isText(file.mimeType)) {
151
- // Decode base64 to text
152
- try {
153
- // Handle base64 with or without data URL prefix
154
- const base64Clean = file.base64.replace(/^data:[^;]+;base64,/, '');
155
- const binary = atob(base64Clean);
156
- const bytes = new Uint8Array(binary.length);
157
- for (let i = 0; i < binary.length; i++) {
158
- bytes[i] = binary.charCodeAt(i);
159
- }
160
- const text = new TextDecoder().decode(bytes);
161
- processed.push({ name: file.name, type: 'text', content: text.slice(0, 10000) });
162
- } catch {
163
- processed.push({ name: file.name, type: 'text', error: 'Failed to decode text file' });
164
- }
165
- } else {
166
- processed.push({ name: file.name, type: 'binary', error: `Unsupported file type: ${file.mimeType}` });
167
- }
168
- }
169
-
170
- return NextResponse.json({
171
- type: 'files_processed',
172
- count: processed.length,
173
- files: processed,
174
- });
175
- } catch (err) {
176
- console.error('[ClaudMax] File upload error:', err);
177
- return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
178
- }
179
- }
@@ -1,99 +0,0 @@
1
- import { NextResponse } from 'next/server';
2
- import { prisma } from '@/lib/prisma';
3
-
4
- const OPENROUTER_API_URL = 'https://openrouter.ai/api/v1/chat/completions';
5
-
6
- async function validateKey(apiKey: string) {
7
- if (!apiKey?.startsWith('sk-cmx_')) return { error: 'Invalid API key', status: 401 };
8
- const key = await prisma.apiKey.findUnique({ where: { key: apiKey } });
9
- if (!key) return { error: 'Invalid API key', status: 401 };
10
- if (!key.isActive) return { error: 'API key revoked', status: 401 };
11
- return { key };
12
- }
13
-
14
- export async function POST(req: Request) {
15
- try {
16
- const apiKey = req.headers.get('x-api-key') ?? req.headers.get('authorization')?.replace('Bearer ', '');
17
- if (!apiKey) {
18
- return NextResponse.json({ error: 'Missing API key' }, { status: 401 });
19
- }
20
-
21
- const val = await validateKey(apiKey);
22
- if ('error' in val) {
23
- return NextResponse.json({ error: val.error }, { status: val.status });
24
- }
25
-
26
- const { query } = await req.json();
27
- if (!query) {
28
- return NextResponse.json({ error: 'Query is required' }, { status: 400 });
29
- }
30
-
31
- // Try Gemini Flash first (fast, great for search summarization), fall back to Qwen
32
- const models = ['google/gemini-2.0-flash-exp:free', 'qwen/qwen2.5-72b-instruct:free'];
33
- let searchRes: Response | null = null;
34
- let lastError = '';
35
-
36
- for (const model of models) {
37
- searchRes = await fetch(OPENROUTER_API_URL, {
38
- method: 'POST',
39
- headers: {
40
- 'Authorization': `Bearer ${process.env.OPENROUTER_API_KEY}`,
41
- 'Content-Type': 'application/json',
42
- 'HTTP-Referer': process.env.OPENROUTER_SITE_URL ?? 'https://claudmax.pro',
43
- 'X-Title': 'ClaudMax',
44
- },
45
- body: JSON.stringify({
46
- model,
47
- messages: [
48
- {
49
- role: 'user',
50
- content: `You are a web search assistant. Search the web for: "${query}". Provide 5 relevant search results with title, URL, and a brief snippet/summary of each result. Format your response as a JSON array with objects containing: title, url, snippet.`,
51
- },
52
- ],
53
- max_tokens: 2048,
54
- temperature: 0.3,
55
- }),
56
- });
57
-
58
- if (searchRes.ok) break;
59
- const err = await searchRes.json().catch(() => ({}));
60
- lastError = (err as any)?.error?.message ?? 'Search failed';
61
- // If rate-limited, try next model; otherwise break
62
- if (searchRes.status !== 429) break;
63
- }
64
-
65
- if (!searchRes?.ok) {
66
- return NextResponse.json(
67
- { error: { type: 'upstream_error', message: lastError || 'Search failed' } },
68
- { status: searchRes?.status ?? 500 }
69
- );
70
- }
71
-
72
- const data = await searchRes.json();
73
- const text = data.choices?.[0]?.message?.content ?? '';
74
-
75
- // Try to parse as JSON, fall back to text
76
- try {
77
- // Strip markdown code blocks if present
78
- const cleaned = text.replace(/^```json\s*/i, '').replace(/```$/i, '').trim();
79
- const parsed = JSON.parse(cleaned);
80
- return NextResponse.json({ results: parsed, query });
81
- } catch {
82
- // Return as formatted text results
83
- return NextResponse.json({
84
- results: [
85
- {
86
- title: `Web search results for: ${query}`,
87
- url: `https://www.google.com/search?q=${encodeURIComponent(query)}`,
88
- snippet: text.slice(0, 500),
89
- },
90
- ],
91
- query,
92
- raw: text,
93
- });
94
- }
95
- } catch (err) {
96
- console.error('[ClaudMax] Web search error:', err);
97
- return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
98
- }
99
- }
@@ -1,67 +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; windowHours: number }> = {
5
- free: { requestsPerWindow: 100, tokensPerWindow: 500_000, windowHours: 5 },
6
- '5x': { requestsPerWindow: 500, tokensPerWindow: 5_000_000, windowHours: 5 },
7
- '20x': { requestsPerWindow: 2000, tokensPerWindow: 20_000_000, windowHours: 5 },
8
- unlimited: { requestsPerWindow: 999_999_999, tokensPerWindow: 999_999_999_999, windowHours: 5 },
9
- };
10
-
11
- const usageStore = new Map<string, { requests: number; tokens: number; windowStart: number }>();
12
-
13
- function getUsage(keyId: string) {
14
- const now = Date.now();
15
- let u = usageStore.get(keyId);
16
- if (!u) {
17
- u = { requests: 0, tokens: 0, windowStart: now };
18
- usageStore.set(keyId, u);
19
- }
20
- if (now - u.windowStart > 5 * 60 * 60 * 1000) {
21
- u.requests = 0;
22
- u.tokens = 0;
23
- u.windowStart = now;
24
- }
25
- return u;
26
- }
27
-
28
- async function validateKey(apiKey: string) {
29
- if (!apiKey?.startsWith('sk-cmx_')) return { error: 'Invalid API key format', status: 401 };
30
- const key = await prisma.apiKey.findUnique({ where: { key: apiKey } });
31
- if (!key) return { error: 'Invalid API key', status: 401 };
32
- if (!key.isActive) return { error: 'API key revoked', status: 401 };
33
- const tier = key.tier ?? 'free';
34
- const limits = TIER_LIMITS[tier] ?? TIER_LIMITS.free;
35
- const usage = getUsage(key.id);
36
- if (usage.requests >= limits.requestsPerWindow) {
37
- return { error: 'Rate limit exceeded', status: 429 };
38
- }
39
- if (usage.tokens >= limits.tokensPerWindow) {
40
- return { error: 'Token limit exceeded', status: 429 };
41
- }
42
- return { key, usage, tier, limits };
43
- }
44
-
45
- export async function POST(req: Request) {
46
- try {
47
- const apiKey = req.headers.get('x-api-key') ?? req.headers.get('authorization')?.replace('Bearer ', '');
48
- if (!apiKey) {
49
- return NextResponse.json({ error: 'Missing API key' }, { status: 401 });
50
- }
51
-
52
- const val = await validateKey(apiKey);
53
- if ('error' in val) {
54
- return NextResponse.json({ error: val.error }, { status: val.status });
55
- }
56
-
57
- return NextResponse.json({
58
- error: {
59
- type: 'not_implemented',
60
- message: 'Audio generation is not yet available. OpenRouter does not expose a TTS generations endpoint. This feature requires a dedicated TTS provider integration.',
61
- },
62
- }, { status: 501 });
63
- } catch (err) {
64
- console.error('[ClaudMax] Audio error:', err);
65
- return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
66
- }
67
- }
@@ -1,73 +0,0 @@
1
- import { NextResponse } from 'next/server';
2
- import { prisma } from '@/lib/prisma';
3
-
4
- const OPENAI_TTS_URL = 'https://api.openai.com/v1/audio/speech';
5
-
6
- export async function POST(req: Request) {
7
- try {
8
- const apiKey = req.headers.get('x-api-key') ?? req.headers.get('authorization')?.replace('Bearer ', '');
9
- if (!apiKey) return NextResponse.json({ error: { message: 'Missing API key', type: 'authentication_error' } }, { status: 401 });
10
- if (!apiKey.startsWith('sk-cmx_')) return NextResponse.json({ error: { message: 'Invalid API key format', type: 'authentication_error' } }, { status: 401 });
11
-
12
- const key = await prisma.apiKey.findUnique({ where: { key: apiKey } });
13
- if (!key) return NextResponse.json({ error: { message: 'Invalid API key', type: 'authentication_error' } }, { status: 401 });
14
- if (!key.isActive) return NextResponse.json({ error: { message: 'API key has been revoked', type: 'authentication_error' } }, { status: 401 });
15
-
16
- const body = await req.json();
17
- const { model = 'tts-1', input, voice = 'alloy', response_format = 'mp3', speed = 1.0 } = body;
18
-
19
- if (!input || typeof input !== 'string') {
20
- return NextResponse.json({ error: { message: 'input is required', type: 'invalid_request_error' } }, { status: 400 });
21
- }
22
-
23
- const openaiKey = process.env.OPENAI_API_KEY;
24
- if (!openaiKey) {
25
- return NextResponse.json({
26
- error: {
27
- message: 'Audio TTS requires OPENAI_API_KEY to be configured. Add it to your .env file: OPENAI_API_KEY=sk-...',
28
- type: 'configuration_error',
29
- },
30
- }, { status: 501 });
31
- }
32
-
33
- const ttsRes = await fetch(OPENAI_TTS_URL, {
34
- method: 'POST',
35
- headers: {
36
- 'Authorization': `Bearer ${openaiKey}`,
37
- 'Content-Type': 'application/json',
38
- },
39
- body: JSON.stringify({
40
- model: model === 'claude-audio-4' ? 'tts-1' : model,
41
- input: input.slice(0, 8000),
42
- voice,
43
- response_format: response_format === 'wav' ? 'wav' : 'mp3',
44
- speed: Math.min(Math.max(speed ?? 1.0, 0.25), 4.0),
45
- }),
46
- signal: AbortSignal.timeout(30000),
47
- });
48
-
49
- if (!ttsRes.ok) {
50
- const err = await ttsRes.json().catch(() => ({}));
51
- return NextResponse.json({ error: { message: (err as any)?.error?.message ?? 'TTS provider error', type: 'upstream_error' } }, { status: 502 });
52
- }
53
-
54
- // Count as 1 request
55
- await prisma.apiKey.update({
56
- where: { id: key.id },
57
- data: { lastUsedAt: new Date(), windowRequestsUsed: { increment: 1 } },
58
- }).catch(() => {});
59
-
60
- const audioBuffer = await ttsRes.arrayBuffer();
61
- const contentType = ttsRes.headers.get('content-type') ?? 'audio/mpeg';
62
-
63
- return new Response(audioBuffer, {
64
- headers: {
65
- 'Content-Type': contentType,
66
- 'Content-Length': String(audioBuffer.byteLength),
67
- },
68
- });
69
- } catch (err) {
70
- console.error('[ClaudMax Audio error]:', err);
71
- return NextResponse.json({ error: { message: 'Internal server error', type: 'internal_error' } }, { status: 500 });
72
- }
73
- }
@@ -1,3 +0,0 @@
1
- // /api/v1/chat/completions — mirrors /api/v1/chat
2
- // OpenAI SDK uses this path for chat completions
3
- export { POST } from '@/app/api/v1/chat/route';