Real use-case of text streaming with async generators using OpenAI API.
1import { type VovkRequest, post, prefix, HttpException, HttpStatus } from 'vovk';
2import OpenAI from 'openai';
3
4@prefix('openai')
5export default class OpenAiController {
6 private static _openai: OpenAI;
7
8 private static get openai() {
9 // to avoid compilation errors if OPENAI_API_KEY is not set
10 return (this._openai ??= new OpenAI());
11 }
12
13 @post('chat', { cors: true, headers: { 'Access-Control-Allow-Origin': 'https://vovk.dev' } })
14 static async *createChatCompletion(
15 req: VovkRequest<{ messages: OpenAI.Chat.Completions.ChatCompletionMessageParam[] }>
16 ) {
17 const { messages } = await req.json();
18 const LIMIT = 5;
19
20 if (messages.filter(({ role }) => role === 'user').length > LIMIT) {
21 throw new HttpException(HttpStatus.BAD_REQUEST, `You can only send ${LIMIT} messages at a time`);
22 }
23
24 yield* await this.openai.chat.completions.create({
25 messages,
26 model: 'gpt-3.5-turbo',
27 stream: true,
28 });
29 }
30}
1import { type VovkRequest, post, prefix, HttpException, HttpStatus } from 'vovk';
2import OpenAI from 'openai';
3
4@prefix('openai')
5export default class OpenAiController {
6 private static _openai: OpenAI;
7
8 private static get openai() {
9 // to avoid compilation errors if OPENAI_API_KEY is not set
10 return (this._openai ??= new OpenAI());
11 }
12
13 @post('chat', { cors: true, headers: { 'Access-Control-Allow-Origin': 'https://vovk.dev' } })
14 static async *createChatCompletion(
15 req: VovkRequest<{ messages: OpenAI.Chat.Completions.ChatCompletionMessageParam[] }>
16 ) {
17 const { messages } = await req.json();
18 const LIMIT = 5;
19
20 if (messages.filter(({ role }) => role === 'user').length > LIMIT) {
21 throw new HttpException(HttpStatus.BAD_REQUEST, `You can only send ${LIMIT} messages at a time`);
22 }
23
24 yield* await this.openai.chat.completions.create({
25 messages,
26 model: 'gpt-3.5-turbo',
27 stream: true,
28 });
29 }
30}
1import { type VovkRequest, post, prefix, HttpException, HttpStatus } from 'vovk';
2import OpenAI from 'openai';
3
4@prefix('openai')
5export default class OpenAiController {
6 private static _openai: OpenAI;
7
8 private static get openai() {
9 // to avoid compilation errors if OPENAI_API_KEY is not set
10 return (this._openai ??= new OpenAI());
11 }
12
13 @post('chat', { cors: true, headers: { 'Access-Control-Allow-Origin': 'https://vovk.dev' } })
14 static async *createChatCompletion(
15 req: VovkRequest<{ messages: OpenAI.Chat.Completions.ChatCompletionMessageParam[] }>
16 ) {
17 const { messages } = await req.json();
18 const LIMIT = 5;
19
20 if (messages.filter(({ role }) => role === 'user').length > LIMIT) {
21 throw new HttpException(HttpStatus.BAD_REQUEST, `You can only send ${LIMIT} messages at a time`);
22 }
23
24 yield* await this.openai.chat.completions.create({
25 messages,
26 model: 'gpt-3.5-turbo',
27 stream: true,
28 });
29 }
30}
1import { type VovkRequest, post, prefix, HttpException, HttpStatus } from 'vovk';
2import OpenAI from 'openai';
3
4@prefix('openai')
5export default class OpenAiController {
6 private static _openai: OpenAI;
7
8 private static get openai() {
9 // to avoid compilation errors if OPENAI_API_KEY is not set
10 return (this._openai ??= new OpenAI());
11 }
12
13 @post('chat', { cors: true, headers: { 'Access-Control-Allow-Origin': 'https://vovk.dev' } })
14 static async *createChatCompletion(
15 req: VovkRequest<{ messages: OpenAI.Chat.Completions.ChatCompletionMessageParam[] }>
16 ) {
17 const { messages } = await req.json();
18 const LIMIT = 5;
19
20 if (messages.filter(({ role }) => role === 'user').length > LIMIT) {
21 throw new HttpException(HttpStatus.BAD_REQUEST, `You can only send ${LIMIT} messages at a time`);
22 }
23
24 yield* await this.openai.chat.completions.create({
25 messages,
26 model: 'gpt-3.5-turbo',
27 stream: true,
28 });
29 }
30}
1'use client';
2import { useState } from 'react';
3import { OpenAiController } from 'vovk-client';
4import type OpenAI from 'openai';
5
6type Message = OpenAI.Chat.Completions.ChatCompletionMessageParam;
7
8export default function OpenAiExample() {
9 const [messages, setMessages] = useState<Message[]>([]);
10 const [userInput, setUserInput] = useState('');
11 const [error, setError] = useState<Error | null>(null);
12
13 const submit = async () => {
14 if (!userInput) return;
15 setUserInput('');
16 const userMessage: Message = { role: 'user', content: userInput };
17
18 setMessages((messages) => [...messages, userMessage]);
19
20 try {
21 using completion = await OpenAiController.createChatCompletion({
22 body: { messages: [...messages, userMessage] },
23 });
24
25 setMessages((mesages) => [...mesages, { role: 'assistant', content: '' } satisfies Message]);
26
27 for await (const chunk of completion) {
28 setMessages((messages) => {
29 const lastMessage = messages[messages.length - 1];
30 return [
31 ...messages.slice(0, -1),
32 { ...lastMessage, content: lastMessage.content + (chunk.choices[0]?.delta?.content ?? '') },
33 ];
34 });
35 }
36 } catch (error) {
37 setError(error as Error);
38 }
39 };
40
41 return (
42 <form
43 onSubmit={(e) => {
44 e.preventDefault();
45 submit();
46 }}
47 >
48 {messages.map((message, index) => (
49 <div key={index}>
50 {message.role === 'assistant' ? 'š¤' : 'š¤'} {(message.content as string) || '...'}
51 </div>
52 ))}
53 {error && <div>ā {error.message}</div>}
54 <div className="input-group">
55 <input
56 type="text"
57 placeholder="Type a message..."
58 value={userInput}
59 onChange={(e) => setUserInput(e.currentTarget.value)}
60 />
61 <button disabled={!userInput}>Send</button>
62 </div>
63 </form>
64 );
65}
1'use client';
2import { useState } from 'react';
3import { OpenAiController } from 'vovk-client';
4import type OpenAI from 'openai';
5
6type Message = OpenAI.Chat.Completions.ChatCompletionMessageParam;
7
8export default function OpenAiExample() {
9 const [messages, setMessages] = useState<Message[]>([]);
10 const [userInput, setUserInput] = useState('');
11 const [error, setError] = useState<Error | null>(null);
12
13 const submit = async () => {
14 if (!userInput) return;
15 setUserInput('');
16 const userMessage: Message = { role: 'user', content: userInput };
17
18 setMessages((messages) => [...messages, userMessage]);
19
20 try {
21 using completion = await OpenAiController.createChatCompletion({
22 body: { messages: [...messages, userMessage] },
23 });
24
25 setMessages((mesages) => [...mesages, { role: 'assistant', content: '' } satisfies Message]);
26
27 for await (const chunk of completion) {
28 setMessages((messages) => {
29 const lastMessage = messages[messages.length - 1];
30 return [
31 ...messages.slice(0, -1),
32 { ...lastMessage, content: lastMessage.content + (chunk.choices[0]?.delta?.content ?? '') },
33 ];
34 });
35 }
36 } catch (error) {
37 setError(error as Error);
38 }
39 };
40
41 return (
42 <form
43 onSubmit={(e) => {
44 e.preventDefault();
45 submit();
46 }}
47 >
48 {messages.map((message, index) => (
49 <div key={index}>
50 {message.role === 'assistant' ? 'š¤' : 'š¤'} {(message.content as string) || '...'}
51 </div>
52 ))}
53 {error && <div>ā {error.message}</div>}
54 <div className="input-group">
55 <input
56 type="text"
57 placeholder="Type a message..."
58 value={userInput}
59 onChange={(e) => setUserInput(e.currentTarget.value)}
60 />
61 <button disabled={!userInput}>Send</button>
62 </div>
63 </form>
64 );
65}
1'use client';
2import { useState } from 'react';
3import { OpenAiController } from 'vovk-client';
4import type OpenAI from 'openai';
5
6type Message = OpenAI.Chat.Completions.ChatCompletionMessageParam;
7
8export default function OpenAiExample() {
9 const [messages, setMessages] = useState<Message[]>([]);
10 const [userInput, setUserInput] = useState('');
11 const [error, setError] = useState<Error | null>(null);
12
13 const submit = async () => {
14 if (!userInput) return;
15 setUserInput('');
16 const userMessage: Message = { role: 'user', content: userInput };
17
18 setMessages((messages) => [...messages, userMessage]);
19
20 try {
21 using completion = await OpenAiController.createChatCompletion({
22 body: { messages: [...messages, userMessage] },
23 });
24
25 setMessages((mesages) => [...mesages, { role: 'assistant', content: '' } satisfies Message]);
26
27 for await (const chunk of completion) {
28 setMessages((messages) => {
29 const lastMessage = messages[messages.length - 1];
30 return [
31 ...messages.slice(0, -1),
32 { ...lastMessage, content: lastMessage.content + (chunk.choices[0]?.delta?.content ?? '') },
33 ];
34 });
35 }
36 } catch (error) {
37 setError(error as Error);
38 }
39 };
40
41 return (
42 <form
43 onSubmit={(e) => {
44 e.preventDefault();
45 submit();
46 }}
47 >
48 {messages.map((message, index) => (
49 <div key={index}>
50 {message.role === 'assistant' ? 'š¤' : 'š¤'} {(message.content as string) || '...'}
51 </div>
52 ))}
53 {error && <div>ā {error.message}</div>}
54 <div className="input-group">
55 <input
56 type="text"
57 placeholder="Type a message..."
58 value={userInput}
59 onChange={(e) => setUserInput(e.currentTarget.value)}
60 />
61 <button disabled={!userInput}>Send</button>
62 </div>
63 </form>
64 );
65}
1'use client';
2import { useState } from 'react';
3import { OpenAiController } from 'vovk-client';
4import type OpenAI from 'openai';
5
6type Message = OpenAI.Chat.Completions.ChatCompletionMessageParam;
7
8export default function OpenAiExample() {
9 const [messages, setMessages] = useState<Message[]>([]);
10 const [userInput, setUserInput] = useState('');
11 const [error, setError] = useState<Error | null>(null);
12
13 const submit = async () => {
14 if (!userInput) return;
15 setUserInput('');
16 const userMessage: Message = { role: 'user', content: userInput };
17
18 setMessages((messages) => [...messages, userMessage]);
19
20 try {
21 using completion = await OpenAiController.createChatCompletion({
22 body: { messages: [...messages, userMessage] },
23 });
24
25 setMessages((mesages) => [...mesages, { role: 'assistant', content: '' } satisfies Message]);
26
27 for await (const chunk of completion) {
28 setMessages((messages) => {
29 const lastMessage = messages[messages.length - 1];
30 return [
31 ...messages.slice(0, -1),
32 { ...lastMessage, content: lastMessage.content + (chunk.choices[0]?.delta?.content ?? '') },
33 ];
34 });
35 }
36 } catch (error) {
37 setError(error as Error);
38 }
39 };
40
41 return (
42 <form
43 onSubmit={(e) => {
44 e.preventDefault();
45 submit();
46 }}
47 >
48 {messages.map((message, index) => (
49 <div key={index}>
50 {message.role === 'assistant' ? 'š¤' : 'š¤'} {(message.content as string) || '...'}
51 </div>
52 ))}
53 {error && <div>ā {error.message}</div>}
54 <div className="input-group">
55 <input
56 type="text"
57 placeholder="Type a message..."
58 value={userInput}
59 onChange={(e) => setUserInput(e.currentTarget.value)}
60 />
61 <button disabled={!userInput}>Send</button>
62 </div>
63 </form>
64 );
65}