84 | >(({ className, ...props }, ref) => (
85 | | [role=checkbox]]:translate-y-[2px]",
89 | className
90 | )}
91 | {...props}
92 | />
93 | ))
94 | TableCell.displayName = "TableCell"
95 |
96 | const TableCaption = React.forwardRef<
97 | HTMLTableCaptionElement,
98 | React.HTMLAttributes
99 | >(({ className, ...props }, ref) => (
100 |
105 | ))
106 | TableCaption.displayName = "TableCaption"
107 |
108 | export {
109 | Table,
110 | TableHeader,
111 | TableBody,
112 | TableFooter,
113 | TableHead,
114 | TableRow,
115 | TableCell,
116 | TableCaption,
117 | }
118 |
--------------------------------------------------------------------------------
/src/lib/openai.ts:
--------------------------------------------------------------------------------
1 | import { Result } from "./result";
2 | import {
3 | createParser,
4 | ParsedEvent,
5 | ReconnectInterval,
6 | } from "eventsource-parser";
7 |
8 | export type Message = {
9 | role: "system" | "user" | "assistant";
10 | content: string;
11 | };
12 |
13 | export type ChatProps = {
14 | apikey: string;
15 | body: RequestBody;
16 | controller: AbortController;
17 | };
18 |
19 | export type RequestBody = {
20 | model: string;
21 | temperature: number;
22 | messages: Message[];
23 | };
24 |
25 | export type Choice = {
26 | index: number;
27 | message: Message;
28 | finish_reason: string;
29 | };
30 |
31 | export type Usage = {
32 | prompt_tokens: number;
33 | completion_tokens: number;
34 | total_tokens: number;
35 | };
36 |
37 | export type ResponseBody = {
38 | id: string;
39 | object: string;
40 | created: number;
41 | model: string;
42 | choices: Choice[];
43 | usage: Usage;
44 | };
45 |
46 | const openaiUrl = "https://api.openai.com/v1/chat/completions";
47 |
48 | async function request(props: ChatProps, stream: boolean) {
49 | return fetch(openaiUrl, {
50 | signal: props.controller.signal,
51 | headers: {
52 | "Content-Type": "application/json",
53 | Authorization: `Bearer ${props.apikey}`,
54 | },
55 | method: "POST",
56 | body: JSON.stringify({ ...props.body, stream }),
57 | });
58 | }
59 |
60 | export async function fetchChatCompletion(
61 | props: ChatProps
62 | ): Promise> {
63 | return request(props, false)
64 | .then((response) => response.json())
65 | .then((response) => response)
66 | .catch((err) => err);
67 | }
68 |
69 | export async function fetchStreamChat(
70 | props: ChatProps
71 | ): Promise>> {
72 | const resp = await request(props, true);
73 | try {
74 | if (resp.status === 400) {
75 | const data = (await resp.json()) as {
76 | error: { message: string[]; type: string };
77 | };
78 | return Error(data.error.type + ": " + data.error.message.join("\n"));
79 | }
80 | if (resp.status !== 200) {
81 | return Error(
82 | resp.status + ": " + resp.statusText || `OpenAI ${resp.status} error`
83 | );
84 | }
85 | if (!resp.body) {
86 | return Error("Empty response from OpenAI");
87 | }
88 | return resp.body.getReader();
89 | } catch (e: any) {
90 | return Error(e.message);
91 | }
92 | }
93 |
94 | export async function readStream(
95 | reader: ReadableStreamDefaultReader,
96 | onDelta: (delta: { content?: string }) => void
97 | ): Promise> {
98 | const decoder = new TextDecoder();
99 |
100 | const parser = createParser((event: ParsedEvent | ReconnectInterval) => {
101 | if (event.type === "event") {
102 | const data = event.data;
103 | try {
104 | if (data === "[DONE]") {
105 | return;
106 | }
107 | const json = JSON.parse(data);
108 | if (json.choices[0].finish_reason != null) {
109 | return;
110 | }
111 | onDelta(json.choices[0].delta);
112 | } catch (e) {
113 | console.error(e);
114 | }
115 | }
116 | });
117 |
118 | let done, value;
119 | while (!done) {
120 | ({ value, done } = await reader.read());
121 | if (done) {
122 | break;
123 | }
124 | parser.feed(decoder.decode(value));
125 | }
126 | return true;
127 | }
128 |
--------------------------------------------------------------------------------
/src/components/ui/autosize-textarea.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 | import * as React from 'react';
3 | import { cn } from '@/lib/utils';
4 | import { useImperativeHandle } from 'react';
5 |
6 | interface UseAutosizeTextAreaProps {
7 | textAreaRef: HTMLTextAreaElement | null;
8 | minHeight?: number;
9 | maxHeight?: number;
10 | triggerAutoSize: string;
11 | }
12 |
13 | export const useAutosizeTextArea = ({
14 | textAreaRef,
15 | triggerAutoSize,
16 | maxHeight = Number.MAX_SAFE_INTEGER,
17 | minHeight = 0,
18 | }: UseAutosizeTextAreaProps) => {
19 | const [init, setInit] = React.useState(true);
20 | React.useEffect(() => {
21 | // We need to reset the height momentarily to get the correct scrollHeight for the textarea
22 | const offsetBorder = 2;
23 | if (textAreaRef) {
24 | if (init) {
25 | textAreaRef.style.minHeight = `${minHeight + offsetBorder}px`;
26 | if (maxHeight > minHeight) {
27 | textAreaRef.style.maxHeight = `${maxHeight}px`;
28 | }
29 | setInit(false);
30 | }
31 | textAreaRef.style.height = `${minHeight + offsetBorder}px`;
32 | const scrollHeight = textAreaRef.scrollHeight;
33 | // We then set the height directly, outside of the render loop
34 | // Trying to set this with state or a ref will product an incorrect value.
35 | if (scrollHeight > maxHeight) {
36 | textAreaRef.style.height = `${maxHeight}px`;
37 | } else {
38 | textAreaRef.style.height = `${scrollHeight + offsetBorder}px`;
39 | }
40 | }
41 | }, [textAreaRef, triggerAutoSize]);
42 | };
43 |
44 | export type AutosizeTextAreaRef = {
45 | textArea: HTMLTextAreaElement;
46 | maxHeight: number;
47 | minHeight: number;
48 | };
49 |
50 | type AutosizeTextAreaProps = {
51 | maxHeight?: number;
52 | minHeight?: number;
53 | } & React.TextareaHTMLAttributes;
54 |
55 | export const AutosizeTextarea = React.forwardRef(
56 | (
57 | {
58 | maxHeight = Number.MAX_SAFE_INTEGER,
59 | minHeight = 52,
60 | className,
61 | onChange,
62 | value,
63 | ...props
64 | }: AutosizeTextAreaProps,
65 | ref: React.Ref,
66 | ) => {
67 | const textAreaRef = React.useRef(null);
68 | const [triggerAutoSize, setTriggerAutoSize] = React.useState('');
69 |
70 | useAutosizeTextArea({
71 | textAreaRef: textAreaRef.current,
72 | triggerAutoSize: triggerAutoSize,
73 | maxHeight,
74 | minHeight,
75 | });
76 |
77 | useImperativeHandle(ref, () => ({
78 | textArea: textAreaRef.current as HTMLTextAreaElement,
79 | maxHeight,
80 | minHeight,
81 | }));
82 |
83 | React.useEffect(() => {
84 | if (value) {
85 | setTriggerAutoSize(value as string);
86 | }
87 | }, [value]);
88 |
89 | return (
90 | |