@umituz/react-native-ai-groq-provider 1.0.20 → 1.0.22
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/package.json +1 -4
- package/src/application/use-cases/chat-session.usecase.ts +137 -0
- package/src/application/use-cases/index.ts +19 -0
- package/src/application/use-cases/streaming.usecase.ts +59 -0
- package/src/application/use-cases/structured-generation.usecase.ts +94 -0
- package/src/application/use-cases/text-generation.usecase.ts +50 -0
- package/src/index.ts +49 -29
- package/src/infrastructure/http/groq-http-client.ts +151 -0
- package/src/infrastructure/http/index.ts +7 -0
- package/src/infrastructure/http/streaming-client.ts +125 -0
- package/src/presentation/hooks/index.ts +7 -0
- package/src/presentation/hooks/use-groq.hook.ts +184 -0
- package/src/presentation/index.ts +6 -0
- package/src/shared/index.ts +16 -0
- package/src/shared/logger.ts +63 -0
- package/src/shared/request-builder.ts +64 -0
- package/src/shared/timer.ts +43 -0
- package/src/infrastructure/services/ChatSession.ts +0 -316
- package/src/infrastructure/services/GroqClient.ts +0 -351
- package/src/infrastructure/services/Streaming.ts +0 -104
- package/src/infrastructure/services/StructuredText.ts +0 -245
- package/src/infrastructure/services/TextGeneration.ts +0 -163
- package/src/infrastructure/services/index.ts +0 -19
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Streaming Client
|
|
3
|
+
* Handles SSE streaming from Groq API
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { GroqChatRequest, GroqChatChunk } from "../../domain/entities";
|
|
7
|
+
import { GroqError, GroqErrorType, mapHttpStatusToErrorType } from "../../domain/entities/error.types";
|
|
8
|
+
import { logger } from "../../shared/logger";
|
|
9
|
+
import { calculateSafeBufferSize } from "../../utils/calculation.util";
|
|
10
|
+
|
|
11
|
+
const DEFAULT_TIMEOUT = 60000;
|
|
12
|
+
|
|
13
|
+
export async function* streamChatCompletion(
|
|
14
|
+
request: GroqChatRequest,
|
|
15
|
+
config: { apiKey: string; baseUrl: string; timeoutMs?: number }
|
|
16
|
+
): AsyncGenerator<GroqChatChunk> {
|
|
17
|
+
const url = `${config.baseUrl}/chat/completions`;
|
|
18
|
+
const timeout = config.timeoutMs || DEFAULT_TIMEOUT;
|
|
19
|
+
|
|
20
|
+
logger.debug("StreamingClient", "Starting stream", {
|
|
21
|
+
model: request.model,
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const controller = new AbortController();
|
|
25
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
const response = await fetch(url, {
|
|
29
|
+
method: "POST",
|
|
30
|
+
headers: {
|
|
31
|
+
"Content-Type": "application/json",
|
|
32
|
+
"Authorization": `Bearer ${config.apiKey}`,
|
|
33
|
+
},
|
|
34
|
+
body: JSON.stringify({ ...request, stream: true }),
|
|
35
|
+
signal: controller.signal,
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
clearTimeout(timeoutId);
|
|
39
|
+
|
|
40
|
+
if (!response.ok) {
|
|
41
|
+
await handleErrorResponse(response);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (!response.body) {
|
|
45
|
+
throw new GroqError(GroqErrorType.NETWORK_ERROR, "Response body is null");
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
yield* parseSSE(response.body);
|
|
49
|
+
|
|
50
|
+
} catch (error) {
|
|
51
|
+
throw handleRequestError(error);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async function* parseSSE(body: ReadableStream<Uint8Array>): AsyncGenerator<GroqChatChunk> {
|
|
56
|
+
const reader = body.getReader();
|
|
57
|
+
const decoder = new TextDecoder();
|
|
58
|
+
let buffer = "";
|
|
59
|
+
const MAX_BUFFER_SIZE = 1024 * 1024;
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
while (true) {
|
|
63
|
+
const { done, value } = await reader.read();
|
|
64
|
+
if (done) break;
|
|
65
|
+
|
|
66
|
+
buffer += decoder.decode(value, { stream: true });
|
|
67
|
+
|
|
68
|
+
const safeSize = calculateSafeBufferSize(buffer.length, MAX_BUFFER_SIZE);
|
|
69
|
+
if (safeSize < buffer.length) {
|
|
70
|
+
buffer = buffer.slice(-safeSize);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const lines = buffer.split("\n");
|
|
74
|
+
buffer = lines.pop() || "";
|
|
75
|
+
|
|
76
|
+
for (const line of lines) {
|
|
77
|
+
const trimmed = line.trim();
|
|
78
|
+
if (!trimmed || trimmed === "data: [DONE]") continue;
|
|
79
|
+
|
|
80
|
+
if (trimmed.startsWith("data: ")) {
|
|
81
|
+
try {
|
|
82
|
+
const chunk = JSON.parse(trimmed.slice(6)) as GroqChatChunk;
|
|
83
|
+
yield chunk;
|
|
84
|
+
} catch (error) {
|
|
85
|
+
logger.error("StreamingClient", "Failed to parse SSE chunk", { error });
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
} finally {
|
|
91
|
+
reader.releaseLock();
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async function handleErrorResponse(response: Response): Promise<never> {
|
|
96
|
+
let errorMessage = `HTTP ${response.status}`;
|
|
97
|
+
const errorType = mapHttpStatusToErrorType(response.status);
|
|
98
|
+
|
|
99
|
+
try {
|
|
100
|
+
const errorData = (await response.json()) as { error?: { message?: string } };
|
|
101
|
+
if (errorData.error?.message) errorMessage = errorData.error.message;
|
|
102
|
+
} catch {
|
|
103
|
+
// Use default
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
throw new GroqError(errorType, errorMessage);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function handleRequestError(error: unknown): GroqError {
|
|
110
|
+
if (error instanceof GroqError) return error;
|
|
111
|
+
|
|
112
|
+
if (error instanceof Error) {
|
|
113
|
+
if (error.name === "AbortError") {
|
|
114
|
+
return new GroqError(GroqErrorType.ABORT_ERROR, "Request aborted", error);
|
|
115
|
+
}
|
|
116
|
+
if (error.message.includes("network")) {
|
|
117
|
+
return new GroqError(GroqErrorType.NETWORK_ERROR, "Network error", error);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return new GroqError(
|
|
122
|
+
GroqErrorType.UNKNOWN_ERROR,
|
|
123
|
+
error instanceof Error ? error.message : "Unknown error"
|
|
124
|
+
);
|
|
125
|
+
}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useGroq Hook
|
|
3
|
+
* Main React hook for Groq text generation
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { useState, useCallback, useMemo } from "react";
|
|
7
|
+
import type { GroqGenerationConfig } from "../../domain/entities";
|
|
8
|
+
import { generateText, generateStructured, streamText } from "../../application/use-cases";
|
|
9
|
+
import { getUserFriendlyError } from "../../utils/error-mapper.util";
|
|
10
|
+
|
|
11
|
+
export interface UseGroqOptions {
|
|
12
|
+
model?: string;
|
|
13
|
+
generationConfig?: GroqGenerationConfig;
|
|
14
|
+
onStart?: () => void;
|
|
15
|
+
onSuccess?: (result: string) => void;
|
|
16
|
+
onError?: (error: string) => void;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface UseGroqReturn {
|
|
20
|
+
isLoading: boolean;
|
|
21
|
+
error: string | null;
|
|
22
|
+
result: string | null;
|
|
23
|
+
generate: (prompt: string, config?: GroqGenerationConfig) => Promise<string>;
|
|
24
|
+
generateJSON: <T = Record<string, unknown>>(
|
|
25
|
+
prompt: string,
|
|
26
|
+
config?: GroqGenerationConfig & { schema?: Record<string, unknown> }
|
|
27
|
+
) => Promise<T>;
|
|
28
|
+
stream: (
|
|
29
|
+
prompt: string,
|
|
30
|
+
onChunk: (chunk: string) => void,
|
|
31
|
+
config?: GroqGenerationConfig
|
|
32
|
+
) => Promise<void>;
|
|
33
|
+
reset: () => void;
|
|
34
|
+
clearError: () => void;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function useGroq(options: UseGroqOptions = {}): UseGroqReturn {
|
|
38
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
39
|
+
const [error, setError] = useState<string | null>(null);
|
|
40
|
+
const [result, setResult] = useState<string | null>(null);
|
|
41
|
+
|
|
42
|
+
const stableOptions = useMemo(
|
|
43
|
+
() => ({
|
|
44
|
+
model: options.model,
|
|
45
|
+
generationConfig: options.generationConfig,
|
|
46
|
+
onStart: options.onStart,
|
|
47
|
+
onSuccess: options.onSuccess,
|
|
48
|
+
onError: options.onError,
|
|
49
|
+
}),
|
|
50
|
+
[
|
|
51
|
+
options.model,
|
|
52
|
+
options.generationConfig?.temperature,
|
|
53
|
+
options.generationConfig?.maxTokens,
|
|
54
|
+
options.generationConfig?.topP,
|
|
55
|
+
options.onStart,
|
|
56
|
+
options.onSuccess,
|
|
57
|
+
options.onError,
|
|
58
|
+
]
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
const generate = useCallback(
|
|
62
|
+
async (prompt: string, config?: GroqGenerationConfig): Promise<string> => {
|
|
63
|
+
setIsLoading(true);
|
|
64
|
+
setError(null);
|
|
65
|
+
setResult(null);
|
|
66
|
+
|
|
67
|
+
stableOptions.onStart?.();
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
const response = await generateText(prompt, {
|
|
71
|
+
model: stableOptions.model,
|
|
72
|
+
generationConfig: { ...stableOptions.generationConfig, ...config },
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
setResult(response);
|
|
76
|
+
stableOptions.onSuccess?.(response);
|
|
77
|
+
return response;
|
|
78
|
+
} catch (err) {
|
|
79
|
+
const errorMessage = getUserFriendlyError(err);
|
|
80
|
+
setError(errorMessage);
|
|
81
|
+
stableOptions.onError?.(errorMessage);
|
|
82
|
+
throw err;
|
|
83
|
+
} finally {
|
|
84
|
+
setIsLoading(false);
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
[stableOptions]
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
const generateJSON = useCallback(
|
|
91
|
+
async <T = Record<string, unknown>,>(
|
|
92
|
+
prompt: string,
|
|
93
|
+
config?: GroqGenerationConfig & { schema?: Record<string, unknown> }
|
|
94
|
+
): Promise<T> => {
|
|
95
|
+
setIsLoading(true);
|
|
96
|
+
setError(null);
|
|
97
|
+
setResult(null);
|
|
98
|
+
|
|
99
|
+
stableOptions.onStart?.();
|
|
100
|
+
|
|
101
|
+
try {
|
|
102
|
+
const response = await generateStructured<T>(prompt, {
|
|
103
|
+
model: stableOptions.model,
|
|
104
|
+
generationConfig: { ...stableOptions.generationConfig, ...config },
|
|
105
|
+
schema: config?.schema,
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
const jsonStr = JSON.stringify(response, null, 2);
|
|
109
|
+
setResult(jsonStr);
|
|
110
|
+
stableOptions.onSuccess?.(jsonStr);
|
|
111
|
+
return response;
|
|
112
|
+
} catch (err) {
|
|
113
|
+
const errorMessage = getUserFriendlyError(err);
|
|
114
|
+
setError(errorMessage);
|
|
115
|
+
stableOptions.onError?.(errorMessage);
|
|
116
|
+
throw err;
|
|
117
|
+
} finally {
|
|
118
|
+
setIsLoading(false);
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
[stableOptions]
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
const stream = useCallback(
|
|
125
|
+
async (
|
|
126
|
+
prompt: string,
|
|
127
|
+
onChunk: (chunk: string) => void,
|
|
128
|
+
config?: GroqGenerationConfig
|
|
129
|
+
): Promise<void> => {
|
|
130
|
+
setIsLoading(true);
|
|
131
|
+
setError(null);
|
|
132
|
+
setResult(null);
|
|
133
|
+
|
|
134
|
+
let fullContent = "";
|
|
135
|
+
|
|
136
|
+
stableOptions.onStart?.();
|
|
137
|
+
|
|
138
|
+
try {
|
|
139
|
+
for await (const chunk of streamText(prompt, {
|
|
140
|
+
model: stableOptions.model,
|
|
141
|
+
generationConfig: { ...stableOptions.generationConfig, ...config },
|
|
142
|
+
callbacks: { onChunk: (c) => {
|
|
143
|
+
fullContent += c;
|
|
144
|
+
onChunk(c);
|
|
145
|
+
}},
|
|
146
|
+
})) {
|
|
147
|
+
// Consume iterator
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
setResult(fullContent);
|
|
151
|
+
stableOptions.onSuccess?.(fullContent);
|
|
152
|
+
} catch (err) {
|
|
153
|
+
const errorMessage = getUserFriendlyError(err);
|
|
154
|
+
setError(errorMessage);
|
|
155
|
+
stableOptions.onError?.(errorMessage);
|
|
156
|
+
throw err;
|
|
157
|
+
} finally {
|
|
158
|
+
setIsLoading(false);
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
[stableOptions]
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
const reset = useCallback(() => {
|
|
165
|
+
setIsLoading(false);
|
|
166
|
+
setError(null);
|
|
167
|
+
setResult(null);
|
|
168
|
+
}, []);
|
|
169
|
+
|
|
170
|
+
const clearError = useCallback(() => {
|
|
171
|
+
setError(null);
|
|
172
|
+
}, []);
|
|
173
|
+
|
|
174
|
+
return {
|
|
175
|
+
isLoading,
|
|
176
|
+
error,
|
|
177
|
+
result,
|
|
178
|
+
generate,
|
|
179
|
+
generateJSON,
|
|
180
|
+
stream,
|
|
181
|
+
reset,
|
|
182
|
+
clearError,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared Utilities
|
|
3
|
+
* Common utilities used across all layers
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export { logger, LogLevel } from "./logger";
|
|
7
|
+
export type { LogContext } from "./logger";
|
|
8
|
+
|
|
9
|
+
export { Timer } from "./timer";
|
|
10
|
+
export type { TimerResult } from "./timer";
|
|
11
|
+
|
|
12
|
+
export { RequestBuilder } from "./request-builder";
|
|
13
|
+
export type { RequestBuilderOptions } from "./request-builder";
|
|
14
|
+
|
|
15
|
+
export { ResponseHandler } from "./response-handler";
|
|
16
|
+
export type { ResponseHandlerResult } from "./response-handler";
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Logger Utility
|
|
3
|
+
* Centralized logging for the entire application
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export enum LogLevel {
|
|
7
|
+
DEBUG = "DEBUG",
|
|
8
|
+
INFO = "INFO",
|
|
9
|
+
WARN = "WARN",
|
|
10
|
+
ERROR = "ERROR",
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface LogContext {
|
|
14
|
+
[key: string]: unknown;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
class Logger {
|
|
18
|
+
private enabled: boolean;
|
|
19
|
+
|
|
20
|
+
constructor() {
|
|
21
|
+
this.enabled = typeof __DEV__ !== "undefined" && __DEV__;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
private log(level: LogLevel, tag: string, message: string, context?: LogContext): void {
|
|
25
|
+
if (!this.enabled) return;
|
|
26
|
+
|
|
27
|
+
const timestamp = new Date().toISOString();
|
|
28
|
+
const logMessage = `[${timestamp}] [${level}] [${tag}] ${message}`;
|
|
29
|
+
|
|
30
|
+
switch (level) {
|
|
31
|
+
case LogLevel.ERROR:
|
|
32
|
+
console.error(logMessage, context || "");
|
|
33
|
+
break;
|
|
34
|
+
case LogLevel.WARN:
|
|
35
|
+
console.warn(logMessage, context || "");
|
|
36
|
+
break;
|
|
37
|
+
default:
|
|
38
|
+
console.log(logMessage, context || "");
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
debug(tag: string, message: string, context?: LogContext): void {
|
|
43
|
+
this.log(LogLevel.DEBUG, tag, message, context);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
info(tag: string, message: string, context?: LogContext): void {
|
|
47
|
+
this.log(LogLevel.INFO, tag, message, context);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
warn(tag: string, message: string, context?: LogContext): void {
|
|
51
|
+
this.log(LogLevel.WARN, tag, message, context);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
error(tag: string, message: string, context?: LogContext): void {
|
|
55
|
+
this.log(LogLevel.ERROR, tag, message, context);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
isEnabled(): boolean {
|
|
59
|
+
return this.enabled;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export const logger = new Logger();
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Request Builder Utility
|
|
3
|
+
* Centralized request building logic
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type {
|
|
7
|
+
GroqChatRequest,
|
|
8
|
+
GroqGenerationConfig,
|
|
9
|
+
GroqMessage,
|
|
10
|
+
} from "../domain/entities";
|
|
11
|
+
import { DEFAULT_MODELS } from "../domain/entities";
|
|
12
|
+
|
|
13
|
+
export interface RequestBuilderOptions {
|
|
14
|
+
model?: string;
|
|
15
|
+
generationConfig?: GroqGenerationConfig;
|
|
16
|
+
defaultTemperature?: number;
|
|
17
|
+
defaultMaxTokens?: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export class RequestBuilder {
|
|
21
|
+
static buildChatRequest(
|
|
22
|
+
messages: GroqMessage[],
|
|
23
|
+
options: RequestBuilderOptions = {}
|
|
24
|
+
): GroqChatRequest {
|
|
25
|
+
const {
|
|
26
|
+
model = DEFAULT_MODELS.TEXT,
|
|
27
|
+
generationConfig = {},
|
|
28
|
+
defaultTemperature = 0.7,
|
|
29
|
+
defaultMaxTokens = 1024,
|
|
30
|
+
} = options;
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
model,
|
|
34
|
+
messages,
|
|
35
|
+
temperature: generationConfig.temperature ?? defaultTemperature,
|
|
36
|
+
max_tokens: generationConfig.maxTokens ?? defaultMaxTokens,
|
|
37
|
+
top_p: generationConfig.topP,
|
|
38
|
+
n: generationConfig.n,
|
|
39
|
+
stop: generationConfig.stop,
|
|
40
|
+
frequency_penalty: generationConfig.frequencyPenalty,
|
|
41
|
+
presence_penalty: generationConfig.presencePenalty,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
static buildPromptRequest(
|
|
46
|
+
prompt: string,
|
|
47
|
+
options: RequestBuilderOptions = {}
|
|
48
|
+
): GroqChatRequest {
|
|
49
|
+
const messages: GroqMessage[] = [{ role: "user", content: prompt }];
|
|
50
|
+
return this.buildChatRequest(messages, options);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
static buildSystemPromptRequest(
|
|
54
|
+
systemPrompt: string,
|
|
55
|
+
userPrompt: string,
|
|
56
|
+
options: RequestBuilderOptions = {}
|
|
57
|
+
): GroqChatRequest {
|
|
58
|
+
const messages: GroqMessage[] = [
|
|
59
|
+
{ role: "system", content: systemPrompt },
|
|
60
|
+
{ role: "user", content: userPrompt },
|
|
61
|
+
];
|
|
62
|
+
return this.buildChatRequest(messages, options);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Timer Utility
|
|
3
|
+
* Performance measurement helper
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export interface TimerResult {
|
|
7
|
+
totalMs: number;
|
|
8
|
+
apiMs: number;
|
|
9
|
+
processingMs: number;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export class Timer {
|
|
13
|
+
private startTime: number;
|
|
14
|
+
private apiStartTime?: number;
|
|
15
|
+
private apiEndTime?: number;
|
|
16
|
+
|
|
17
|
+
constructor() {
|
|
18
|
+
this.startTime = Date.now();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
startApiCall(): void {
|
|
22
|
+
this.apiStartTime = Date.now();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
endApiCall(): void {
|
|
26
|
+
this.apiEndTime = Date.now();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
getResult(): TimerResult {
|
|
30
|
+
const endTime = Date.now();
|
|
31
|
+
const totalMs = endTime - this.startTime;
|
|
32
|
+
const apiMs = this.apiEndTime && this.apiStartTime
|
|
33
|
+
? this.apiEndTime - this.apiStartTime
|
|
34
|
+
: 0;
|
|
35
|
+
const processingMs = totalMs - apiMs;
|
|
36
|
+
|
|
37
|
+
return { totalMs, apiMs, processingMs };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
static format(ms: number): string {
|
|
41
|
+
return `${ms}ms`;
|
|
42
|
+
}
|
|
43
|
+
}
|