1- import React , { useState } from 'react' ;
1+ import React , { useCallback , useState } from 'react' ;
22import Chat , { ChatTypes } from 'devextreme-react/chat' ;
3- import { AzureOpenAI } from 'openai' ;
43import { MessageEnteredEvent } from 'devextreme/ui/chat' ;
5- import CustomStore from 'devextreme/data/custom_store' ;
6- import DataSource from 'devextreme/data/data_source' ;
74import { loadMessages } from 'devextreme/localization' ;
8- import {
5+ import {
96 user ,
107 assistant ,
11- AzureOpenAIConfig ,
12- REGENERATION_TEXT ,
138 CHAT_DISABLED_CLASS ,
14- ALERT_TIMEOUT
159} from './data.ts' ;
1610import Message from './Message.tsx' ;
17-
18- const store = [ ] ;
19- const messages = [ ] ;
11+ import { dataSource , useApi } from './useApi.ts' ;
2012
2113loadMessages ( {
2214 en : {
@@ -26,148 +18,49 @@ loadMessages({
2618 } ,
2719} ) ;
2820
29- const chatService = new AzureOpenAI ( AzureOpenAIConfig ) ;
30-
31- async function getAIResponse ( messages ) {
32- const params = {
33- messages,
34- model : AzureOpenAIConfig . deployment ,
35- max_tokens : 1000 ,
36- temperature : 0.7 ,
37- } ;
38-
39- const response = await chatService . chat . completions . create ( params ) ;
40- const data = { choices : response . choices } ;
41-
42- return data . choices [ 0 ] . message ?. content ;
43- }
44-
45- function updateLastMessage ( text = REGENERATION_TEXT ) {
46- const items = dataSource . items ( ) ;
47- const lastMessage = items . at ( - 1 ) ;
48-
49- dataSource . store ( ) . push ( [ {
50- type : 'update' ,
51- key : lastMessage . id ,
52- data : { text } ,
53- } ] ) ;
54- }
55-
56- function renderAssistantMessage ( text ) {
57- const message = {
58- id : Date . now ( ) ,
59- timestamp : new Date ( ) ,
60- author : assistant ,
61- text,
62- } ;
63-
64- dataSource . store ( ) . push ( [ { type : 'insert' , data : message } ] ) ;
65- }
21+ export default function App ( ) {
22+ const {
23+ alerts, insertMessage, fetchAIResponse, regenerateLastAIResponse,
24+ } = useApi ( ) ;
6625
67- const customStore = new CustomStore ( {
68- key : 'id' ,
69- load : ( ) => {
70- return new Promise ( ( resolve ) => {
71- setTimeout ( ( ) => {
72- resolve ( [ ...store ] ) ;
73- } , 0 ) ;
74- } ) ;
75- } ,
76- insert : ( message ) => {
77- return new Promise ( ( resolve ) => {
78- setTimeout ( ( ) => {
79- store . push ( message ) ;
80- resolve ( message ) ;
81- } ) ;
82- } ) ;
83- } ,
84- } ) ;
26+ const [ typingUsers , setTypingUsers ] = useState < ChatTypes . User [ ] > ( [ ] ) ;
27+ const [ isProcessing , setIsProcessing ] = useState ( false ) ;
8528
86- const dataSource = new DataSource ( {
87- store : customStore ,
88- paginate : false ,
89- } )
29+ const processAIRequest = useCallback ( async ( message : ChatTypes . Message ) : Promise < void > => {
30+ setIsProcessing ( true ) ;
31+ setTypingUsers ( [ assistant ] ) ;
9032
91- export default function App ( ) {
92- const [ alerts , setAlerts ] = useState < ChatTypes . Alert [ ] > ( [ ] ) ;
93- const [ typingUsers , setTypingUsers ] = useState < ChatTypes . User [ ] > ( [ ] ) ;
94- const [ classList , setClassList ] = useState < string > ( '' ) ;
33+ await fetchAIResponse ( message ) ;
9534
96- function alertLimitReached ( ) {
97- setAlerts ( [ {
98- message : 'Request limit reached, try again in a minute.'
99- } ] ) ;
100-
101- setTimeout ( ( ) => {
102- setAlerts ( [ ] ) ;
103- } , ALERT_TIMEOUT ) ;
104- }
35+ setTypingUsers ( [ ] ) ;
36+ setIsProcessing ( false ) ;
37+ } , [ fetchAIResponse ] ) ;
10538
106- function toggleDisabledState ( disabled : boolean , event = undefined ) {
107- setClassList ( disabled ? CHAT_DISABLED_CLASS : '' ) ;
39+ const onMessageEntered = useCallback ( async ( { message , event } : MessageEnteredEvent ) : Promise < void > => {
40+ insertMessage ( { id : Date . now ( ) , ... message } ) ;
10841
109- if ( disabled ) {
110- event ?. target . blur ( ) ;
111- } else {
112- event ?. target . focus ( ) ;
113- }
114- } ;
42+ if ( ! alerts . length ) {
43+ ( event . target as HTMLElement ) . blur ( ) ;
11544
116- async function processMessageSending ( message , event ) {
117- toggleDisabledState ( true , event ) ;
45+ await processAIRequest ( message ) ;
11846
119- messages . push ( { role : 'user' , content : message . text } ) ;
120- setTypingUsers ( [ assistant ] ) ;
121-
122- try {
123- const aiResponse = await getAIResponse ( messages ) ;
124-
125- setTimeout ( ( ) => {
126- setTypingUsers ( [ ] ) ;
127- messages . push ( { role : 'assistant' , content : aiResponse } ) ;
128- renderAssistantMessage ( aiResponse ) ;
129- } , 200 ) ;
130- } catch {
131- setTypingUsers ( [ ] ) ;
132- messages . pop ( ) ;
133- alertLimitReached ( ) ;
134- } finally {
135- toggleDisabledState ( false , event ) ;
47+ ( event . target as HTMLElement ) . focus ( ) ;
13648 }
137- }
138-
139- async function regenerate ( ) {
140- toggleDisabledState ( true ) ;
49+ } , [ insertMessage , alerts . length , processAIRequest ] ) ;
14150
142- try {
143- const aiResponse = await getAIResponse ( messages . slice ( 0 , - 1 ) ) ;
51+ const onRegenerateButtonClick = useCallback ( async ( ) : Promise < void > => {
52+ setIsProcessing ( true ) ;
14453
145- updateLastMessage ( aiResponse ) ;
146- messages . at ( - 1 ) . content = aiResponse ;
147- } catch {
148- updateLastMessage ( messages . at ( - 1 ) . content ) ;
149- alertLimitReached ( ) ;
150- } finally {
151- toggleDisabledState ( false ) ;
152- }
153- }
54+ await regenerateLastAIResponse ( ) ;
15455
155- function onMessageEntered ( { message, event } : MessageEnteredEvent ) {
156- dataSource . store ( ) . push ( [ { type : 'insert' , data : { id : Date . now ( ) , ...message } } ] ) ;
157-
158- if ( ! alerts . length ) {
159- processMessageSending ( message , event ) ;
160- }
161- }
56+ setIsProcessing ( false ) ;
57+ } , [ regenerateLastAIResponse ] ) ;
16258
163- function onRegenerateButtonClick ( ) {
164- updateLastMessage ( ) ;
165- regenerate ( ) ;
166- }
59+ const messageRender = useCallback ( ( { message } : { message : ChatTypes . Message } ) => < Message text = { message . text } onRegenerateButtonClick = { onRegenerateButtonClick } /> , [ onRegenerateButtonClick ] ) ;
16760
16861 return (
16962 < Chat
170- className = { classList }
63+ className = { isProcessing ? CHAT_DISABLED_CLASS : '' }
17164 dataSource = { dataSource }
17265 reloadOnChange = { false }
17366 showAvatar = { false }
@@ -177,7 +70,7 @@ export default function App() {
17770 onMessageEntered = { onMessageEntered }
17871 alerts = { alerts }
17972 typingUsers = { typingUsers }
180- messageRender = { ( data ) => Message ( data , onRegenerateButtonClick ) }
73+ messageRender = { messageRender }
18174 />
18275 ) ;
18376}
0 commit comments