@baseai/core
Version:
The Web AI Framework's core - BaseAI.dev
261 lines (257 loc) • 7.85 kB
JavaScript
'use client'
// src/react/use-pipe.ts
import { useCallback, useMemo, useRef, useState } from "react";
import { z } from "zod";
// src/helpers/stream.ts
import { ChatCompletionStream } from "openai/lib/ChatCompletionStream";
import { Stream } from "openai/streaming";
var fromReadableStream = (readableStream) => {
return ChatCompletionStream.fromReadableStream(readableStream);
};
var getRunner = (readableStream) => {
return fromReadableStream(readableStream);
};
// src/utils/is-prod.ts
var FORCE_PROD = false;
var TEST_PROD_LOCALLY = FORCE_PROD;
function isProd() {
if (TEST_PROD_LOCALLY) return true;
return process.env.NODE_ENV === "production";
}
// src/react/use-pipe.ts
var uuidSchema = z.string().uuid();
var externalThreadIdSchema = uuidSchema.optional();
function usePipe({
apiRoute = "/langbase/pipes/run-stream",
onResponse,
onFinish,
onConnect,
onError,
threadId: initialThreadId,
initialMessages = [],
stream = true
} = {}) {
const [messages, setMessages] = useState(initialMessages);
const [input, setInput] = useState("");
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState(null);
const abortControllerRef = useRef(null);
const threadIdRef = useRef(
initialThreadId || void 0
);
const messagesRef = useRef(initialMessages);
const isFirstRequestRef = useRef(true);
const updateMessages = useCallback((newMessages) => {
messagesRef.current = newMessages;
setMessages(newMessages);
}, []);
const processStreamResponse = useCallback(
async (runner) => {
var _a, _b, _c;
let assistantMessage = { role: "assistant", content: "" };
updateMessages([...messagesRef.current, assistantMessage]);
for await (const chunk of runner) {
if ((_a = abortControllerRef.current) == null ? void 0 : _a.signal.aborted) break;
const content = ((_c = (_b = chunk.choices[0]) == null ? void 0 : _b.delta) == null ? void 0 : _c.content) || "";
assistantMessage.content += content;
updateMessages([
...messagesRef.current.slice(0, -1),
{ ...assistantMessage }
]);
onResponse == null ? void 0 : onResponse({ ...assistantMessage });
}
onFinish == null ? void 0 : onFinish(messagesRef.current);
},
[updateMessages, onResponse, onFinish]
);
const processNonStreamResponse = useCallback(
(result) => {
const assistantMessage = {
role: "assistant",
content: result.completion
};
const newMessages = [...messagesRef.current, assistantMessage];
updateMessages(newMessages);
onResponse == null ? void 0 : onResponse(assistantMessage);
onFinish == null ? void 0 : onFinish(newMessages);
},
[updateMessages, onResponse, onFinish]
);
const setThreadId = useCallback((newThreadId) => {
const isValidThreadId = externalThreadIdSchema.safeParse(newThreadId).success;
if (isValidThreadId) {
threadIdRef.current = newThreadId;
} else {
throw new Error("Invalid thread ID");
}
}, []);
const getMessagesToSend = useCallback(
(updatedMessages) => {
const isInitialRequest = isFirstRequestRef.current;
isFirstRequestRef.current = false;
if (!isProd()) {
return [updatedMessages, false];
}
if (isInitialRequest) {
return [updatedMessages, false];
} else {
const lastMessageOnly = updatedMessages.length > initialMessages.length;
return [
lastMessageOnly ? [updatedMessages[updatedMessages.length - 1]] : updatedMessages,
lastMessageOnly
];
}
},
[initialMessages]
);
const sendRequest = useCallback(
async (content, options = {}) => {
abortControllerRef.current = new AbortController();
const { signal } = abortControllerRef.current;
try {
setIsLoading(true);
setError(null);
onConnect == null ? void 0 : onConnect();
let updatedMessages = messagesRef.current;
const hasContent = content && content.trim();
if (hasContent) {
updatedMessages = [
...messagesRef.current,
{ role: "user", content }
];
}
updateMessages(updatedMessages);
const [messagesToSend, lastMessageOnly] = getMessagesToSend(updatedMessages);
if (messagesToSend.length === 0 && !options.allowEmptySubmit) {
throw new Error(
"At least one message or initial message is required"
);
}
const requestBody = {
messages: messagesToSend,
stream,
lastMessageOnly,
...options.body
};
if (threadIdRef.current && uuidSchema.safeParse(threadIdRef.current).success) {
requestBody.threadId = threadIdRef.current;
}
const response = await fetch(apiRoute, {
method: "POST",
headers: {
"Content-Type": "application/json",
...options.headers || {}
},
body: JSON.stringify(requestBody),
signal
});
if (!response.ok) await processErrorResponse(response);
const newThreadId = response.headers.get("lb-thread-id");
if (newThreadId) threadIdRef.current = newThreadId;
if (stream && response.body) {
await processStreamResponse(getRunner(response.body));
} else {
const result = await response.json();
processNonStreamResponse(result);
}
} catch (err) {
if (err instanceof Error && err.name !== "AbortError") {
setError(err);
onError == null ? void 0 : onError(err);
} else if (err.name !== "AbortError") {
throw new Error("Failed to send message");
}
} finally {
setIsLoading(false);
}
},
[
apiRoute,
stream,
processStreamResponse,
processNonStreamResponse,
updateMessages,
onConnect,
onError,
getMessagesToSend
]
);
const handleSubmit = useCallback(
(event, options = {}) => {
var _a;
(_a = event == null ? void 0 : event.preventDefault) == null ? void 0 : _a.call(event);
const currentInput = input.trim();
setInput("");
return sendRequest(currentInput, options);
},
[input, sendRequest]
);
const handleInputChange = useCallback(
(e) => {
setInput(e.target.value);
},
[]
);
const sendMessage = useCallback(
async (content, options = {}) => {
await sendRequest(content.trim(), options);
},
[sendRequest]
);
const regenerate = useCallback(
async (options = {}) => {
const lastUserMessage = messagesRef.current.findLast(
(m) => m.role === "user"
);
if (!lastUserMessage) return;
await sendRequest(lastUserMessage.content, options);
},
[sendRequest]
);
const stop = useCallback(() => {
var _a;
(_a = abortControllerRef.current) == null ? void 0 : _a.abort();
setIsLoading(false);
}, []);
const processErrorResponse = async (response) => {
const res = await response.json();
if (res.error.error) {
throw new Error(res.error.error.message);
} else {
throw new Error("Failed to send message");
}
};
return useMemo(
() => ({
messages,
input,
handleInputChange,
handleSubmit,
isLoading,
error,
regenerate,
stop,
setMessages: updateMessages,
threadId: threadIdRef.current,
sendMessage,
setInput,
setThreadId
}),
[
messages,
input,
handleInputChange,
handleSubmit,
isLoading,
error,
regenerate,
stop,
updateMessages,
sendMessage
]
);
}
export {
usePipe
};
//# sourceMappingURL=index.mjs.map