mirror of
https://github.com/andy-stack/vaultkeeper-ai.git
synced 2026-07-22 16:30:27 +00:00
refactor: remove unused apiError429UserInfo property and simplify error handling Remove apiError429UserInfo from IAIClass interface and all implementations (Claude, Gemini, OpenAI). Simplify StreamingService error handling by removing custom 429 error messages. Clean up ChatService formatting and ChatArea display logic.
88 lines
No EOL
2.4 KiB
TypeScript
88 lines
No EOL
2.4 KiB
TypeScript
import type { AIFunctionCall } from "AIClasses/AIFunctionCall";
|
|
import type { IAIClass } from "AIClasses/IAIClass";
|
|
import { Selector } from "Enums/Selector";
|
|
|
|
export interface StreamChunk {
|
|
content: string;
|
|
isComplete: boolean;
|
|
error?: string;
|
|
functionCall?: AIFunctionCall;
|
|
shouldContinue?: boolean;
|
|
}
|
|
|
|
export class StreamingService {
|
|
public async* streamRequest(
|
|
url: string,
|
|
requestBody: unknown,
|
|
parseStreamChunk: (chunk: string) => StreamChunk,
|
|
abortSignal?: AbortSignal,
|
|
additionalHeaders?: Record<string, string>
|
|
): AsyncGenerator<StreamChunk, void, unknown> {
|
|
try {
|
|
const response = await fetch(
|
|
url,
|
|
{
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
...additionalHeaders,
|
|
},
|
|
body: JSON.stringify(requestBody),
|
|
signal: abortSignal,
|
|
}
|
|
);
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`API request failed: ${response.status} - ${response.statusText} ${await response.text()}`);
|
|
}
|
|
|
|
const reader = response.body?.getReader();
|
|
if (!reader) {
|
|
throw new Error("Response body is not readable");
|
|
}
|
|
|
|
const decoder = new TextDecoder();
|
|
let buffer = "";
|
|
let lastChunkWasComplete = false;
|
|
|
|
while (true) {
|
|
const { done, value } = await reader.read();
|
|
|
|
buffer += decoder.decode(value, { stream: true });
|
|
const lines = buffer.split("\n");
|
|
buffer = lines.pop() || ""; // Keep potentially incomplete line in buffer
|
|
|
|
for (const line of lines) {
|
|
if (line.trim().startsWith("data:")) {
|
|
const jsonStr = line.trim().substring(5);
|
|
const chunk = parseStreamChunk(jsonStr);
|
|
lastChunkWasComplete = chunk.isComplete;
|
|
yield chunk;
|
|
}
|
|
}
|
|
|
|
if (done) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!lastChunkWasComplete) {
|
|
yield { content: "", isComplete: true };
|
|
}
|
|
} catch (error) {
|
|
// Don't log abort errors as they're intentional
|
|
if (error instanceof Error && error.name === 'AbortError') {
|
|
yield {
|
|
content: Selector.ApiRequestAborted,
|
|
isComplete: true
|
|
};
|
|
} else {
|
|
yield {
|
|
content: "",
|
|
isComplete: true,
|
|
error: error instanceof Error ? error.message : "Unknown error",
|
|
};
|
|
}
|
|
}
|
|
}
|
|
} |