Skip to content

Commit e7dda46

Browse files
committed
STT/TSS exposed in API
1 parent 0c39b25 commit e7dda46

4 files changed

Lines changed: 124 additions & 12 deletions

File tree

api-docs.md

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ curl -X POST "http://localhost:3432/api/generate-message" \
104104
#### POST `/api/tts`
105105
Proxies requests to NanoGPT TTS API for speech synthesis.
106106

107-
**Authentication**: Session
107+
**Authentication**: Session or API Key
108108

109109
**Request Body**:
110110
```json
@@ -129,18 +129,48 @@ curl -X POST "http://localhost:3432/api/tts" \
129129
--output speech.mp3
130130
```
131131

132+
#### GET `/api/tts/status`
133+
Polls the status of an asynchronous TTS request.
134+
135+
**Authentication**: Session or API Key
136+
137+
**Query Parameters**:
138+
- `runId`: (Required) The run ID returned by `/api/tts`.
139+
- `model`: (Required) The TTS model ID.
140+
- `cost`: (Optional) Estimated cost from the submit response.
141+
- `paymentSource`: (Optional) Payment source from the submit response.
142+
- `isApiRequest`: (Optional) Whether the request was an API request.
143+
144+
**Response**:
145+
```json
146+
{
147+
"status": "pending" | "completed" | "error",
148+
"audioUrl": "string (when completed)",
149+
"contentType": "string (optional)",
150+
"model": "string",
151+
"error": "string (optional)"
152+
}
153+
```
154+
155+
**CURL Example**:
156+
```bash
157+
curl "http://localhost:3432/api/tts/status?runId=RUN_ID&model=Elevenlabs-Turbo-V2.5" \
158+
-H "Authorization: Bearer your_api_key"
159+
```
160+
132161
---
133162

134163
### Speech-to-Text
135164

136165
#### POST `/api/stt`
137166
Proxies audio files to NanoGPT STT API for transcription.
138167

139-
**Authentication**: Session
168+
**Authentication**: Session or API Key
140169

141170
**Request Body** (FormData):
142171
- `audio`: Binary audio file (webm, mp4, etc).
143172
- `model`: "string (optional, default: 'Whisper-Large-V3')"
173+
- `language`: "string (optional, default: 'auto')"
144174

145175
**Response**:
146176
```json

src/routes/api/stt/+server.ts

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,26 @@ import type { RequestHandler } from './$types';
44
import { db } from '$lib/db';
55
import { modelPerformanceStats } from '$lib/db/schema';
66
import { eq, and, sql } from 'drizzle-orm';
7-
import { auth } from '$lib/auth';
7+
import { tryGetAuthenticatedUserId } from '$lib/backend/auth-utils';
88

99
const COST_PER_MINUTE: Record<string, number> = {
1010
'Whisper-Large-V3': 0.01,
1111
Wizper: 0.01,
1212
'Elevenlabs-STT': 0.03,
1313
};
1414

15+
const getApiKey = (request: Request): string | null => {
16+
const authHeader = request.headers.get('Authorization');
17+
if (authHeader?.startsWith('Bearer ')) {
18+
const token = authHeader.slice(7).trim();
19+
if (token.length > 0) {
20+
return token;
21+
}
22+
}
23+
24+
return request.headers.get('x-api-key') || env.NANOGPT_API_KEY || null;
25+
};
26+
1527
export const POST: RequestHandler = async ({ request, fetch }) => {
1628
try {
1729
const formData = await request.formData();
@@ -31,7 +43,7 @@ export const POST: RequestHandler = async ({ request, fetch }) => {
3143
formData.set('language', language);
3244
}
3345

34-
const apiKey = request.headers.get('x-api-key') || env.NANOGPT_API_KEY;
46+
const apiKey = getApiKey(request);
3547

3648
if (!apiKey) {
3749
return json({ error: 'API key is required' }, { status: 401 });
@@ -70,9 +82,8 @@ export const POST: RequestHandler = async ({ request, fetch }) => {
7082
// Track analytics asynchronously
7183
(async () => {
7284
try {
73-
const session = await auth.api.getSession({ headers: request.headers });
74-
if (session?.user?.id) {
75-
const userId = session.user.id;
85+
const userId = await tryGetAuthenticatedUserId(request);
86+
if (userId) {
7687
const durationMs = Date.now() - start;
7788

7889
// Try to get cost from various response headers

src/routes/api/tts/+server.ts

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import type { RequestHandler } from './$types';
44
import { db } from '$lib/db';
55
import { modelPerformanceStats } from '$lib/db/schema';
66
import { eq, and, sql } from 'drizzle-orm';
7-
import { auth } from '$lib/auth';
7+
import { tryGetAuthenticatedUserId } from '$lib/backend/auth-utils';
88

99
const COST_PER_1K_CHARS: Record<string, number> = {
1010
'gpt-4o-mini-tts': 0.0125,
@@ -17,6 +17,18 @@ const COST_PER_1K_CHARS: Record<string, number> = {
1717
// Default to standard price if unknown
1818
const DEFAULT_COST = 0.015;
1919

20+
const getApiKey = (request: Request): string | null => {
21+
const authHeader = request.headers.get('Authorization');
22+
if (authHeader?.startsWith('Bearer ')) {
23+
const token = authHeader.slice(7).trim();
24+
if (token.length > 0) {
25+
return token;
26+
}
27+
}
28+
29+
return request.headers.get('x-api-key') || env.NANOGPT_API_KEY || null;
30+
};
31+
2032
export const POST: RequestHandler = async ({ request, fetch }) => {
2133
let textStr = '';
2234
let modelId = 'tts-1';
@@ -31,7 +43,7 @@ export const POST: RequestHandler = async ({ request, fetch }) => {
3143
return json({ error: 'Text is required' }, { status: 400 });
3244
}
3345

34-
const apiKey = request.headers.get('x-api-key') || env.NANOGPT_API_KEY;
46+
const apiKey = getApiKey(request);
3547

3648
if (!apiKey) {
3749
return json({ error: 'API key is required' }, { status: 401 });
@@ -61,6 +73,12 @@ export const POST: RequestHandler = async ({ request, fetch }) => {
6173
);
6274
}
6375

76+
const contentType = response.headers.get('Content-Type') || '';
77+
if (response.status === 202 || contentType.includes('application/json')) {
78+
const data = await response.json();
79+
return json(data, { status: response.status });
80+
}
81+
6482
// Return the audio blob directly
6583
console.log(
6684
`[TTS] API response status: ${response.status}, headers:`,
@@ -71,9 +89,8 @@ export const POST: RequestHandler = async ({ request, fetch }) => {
7189
// Track analytics asynchronously
7290
(async () => {
7391
try {
74-
const session = await auth.api.getSession({ headers: request.headers });
75-
if (session?.user?.id) {
76-
const userId = session.user.id;
92+
const userId = await tryGetAuthenticatedUserId(request);
93+
if (userId) {
7794
console.log(`[TTS] Tracking analytics for user: ${userId}, model: ${model}`);
7895
const duration = Date.now() - start;
7996

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { json } from '@sveltejs/kit';
2+
import type { RequestHandler } from './$types';
3+
4+
const getApiKey = (request: Request): string | null => {
5+
const authHeader = request.headers.get('Authorization');
6+
if (authHeader?.startsWith('Bearer ')) {
7+
const token = authHeader.slice(7).trim();
8+
if (token.length > 0) {
9+
return token;
10+
}
11+
}
12+
13+
return request.headers.get('x-api-key') || env.NANOGPT_API_KEY || null;
14+
};
15+
16+
export const GET: RequestHandler = async ({ request, fetch, url }) => {
17+
const apiKey = getApiKey(request);
18+
19+
if (!apiKey) {
20+
return json({ error: 'API key is required' }, { status: 401 });
21+
}
22+
23+
const runId = url.searchParams.get('runId');
24+
const model = url.searchParams.get('model');
25+
26+
if (!runId || !model) {
27+
return json({ error: 'runId and model are required' }, { status: 400 });
28+
}
29+
30+
const proxyUrl = new URL('https://nano-gpt.com/api/tts/status');
31+
for (const [key, value] of url.searchParams.entries()) {
32+
proxyUrl.searchParams.set(key, value);
33+
}
34+
35+
const response = await fetch(proxyUrl.toString(), {
36+
headers: {
37+
'x-api-key': apiKey,
38+
},
39+
});
40+
41+
const contentType = response.headers.get('Content-Type') || '';
42+
if (contentType.includes('application/json')) {
43+
const data = await response.json();
44+
return json(data, { status: response.status });
45+
}
46+
47+
const text = await response.text();
48+
return new Response(text, {
49+
status: response.status,
50+
headers: {
51+
'Content-Type': contentType,
52+
},
53+
});
54+
};

0 commit comments

Comments
 (0)