First commit.
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
import type { Message, OpenAIMessage, OpenAIStreamChunk } from '@/types';
|
||||
|
||||
export async function* streamChatCompletion(
|
||||
messages: Message[],
|
||||
apiUrl: string,
|
||||
apiKey: string,
|
||||
model: string,
|
||||
temperature: number,
|
||||
maxTokens: number,
|
||||
systemPrompt: string,
|
||||
abortSignal: AbortSignal
|
||||
): AsyncGenerator<OpenAIStreamChunk, void, unknown> {
|
||||
const formattedMessages: OpenAIMessage[] = [
|
||||
{ role: 'system', content: systemPrompt },
|
||||
...messages.map((m) => ({ role: m.role, content: m.content }) as OpenAIMessage),
|
||||
];
|
||||
|
||||
const response = await fetch('/api/v1/chat/completions', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
apiUrl,
|
||||
apiKey,
|
||||
model,
|
||||
temperature,
|
||||
max_tokens: maxTokens,
|
||||
messages: formattedMessages,
|
||||
stream: true,
|
||||
}),
|
||||
signal: abortSignal,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json().catch(() => ({}));
|
||||
throw new Error(errorData.error?.message || `HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const reader = response.body?.getReader();
|
||||
if (!reader) {
|
||||
throw new Error('No response body');
|
||||
}
|
||||
|
||||
const decoder = new TextDecoder();
|
||||
let buffer = '';
|
||||
|
||||
try {
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) break;
|
||||
|
||||
buffer += decoder.decode(value, { stream: true });
|
||||
const lines = buffer.split('\n');
|
||||
buffer = lines.pop() || '';
|
||||
|
||||
for (const line of lines) {
|
||||
const trimmed = line.trim();
|
||||
if (!trimmed || trimmed === 'data: [DONE]') continue;
|
||||
if (trimmed.startsWith('data: ')) {
|
||||
try {
|
||||
const data: OpenAIStreamChunk = JSON.parse(trimmed.slice(6));
|
||||
yield data;
|
||||
} catch {
|
||||
// Ignore parse errors for incomplete chunks
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
reader.releaseLock();
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchModels(
|
||||
apiUrl: string,
|
||||
apiKey: string
|
||||
): Promise<string[]> {
|
||||
const response = await fetch('/api/v1/models', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ apiUrl, apiKey }),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json().catch(() => ({}));
|
||||
throw new Error(errorData.error?.message || `HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return data.models || [];
|
||||
}
|
||||
Reference in New Issue
Block a user