Skip to content

Commit e9414e4

Browse files
authored
Merge pull request #13 from DevExpress-Examples/React-changes
React - Fix TS declarations
2 parents d8acb81 + 427669f commit e9414e4

File tree

5 files changed

+78
-78
lines changed

5 files changed

+78
-78
lines changed

React/src/App.css

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,3 +58,8 @@
5858
font-size: revert;
5959
font-weight: revert;
6060
}
61+
62+
.chat-disabled .dx-chat-messagebox {
63+
opacity: 0.5;
64+
pointer-events: none;
65+
}

React/src/ChatService.tsx

Lines changed: 25 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,38 @@
1-
import {
2-
type User, type Alert, type MessageEnteredEvent, type Message,
3-
} from 'devextreme/ui/chat';
4-
import DataSource from 'devextreme/data/data_source';
5-
import CustomStore from 'devextreme/data/custom_store';
1+
import { type ChatTypes } from 'devextreme-react/chat';
2+
import { DataSource, CustomStore } from 'devextreme-react/common/data';
63
import { OpenAI } from 'openai';
74
import { BehaviorSubject, Observable } from 'rxjs';
8-
import TextArea from 'devextreme/ui/text_area';
5+
import { ALERT_TIMEOUT, assistant, OpenAIConfig } from './data.ts';
96

107
class AppService {
118
chatService: OpenAI;
129

13-
OpenAIConfig = {
14-
dangerouslyAllowBrowser: true,
15-
apiKey: 'OPEN_AI_KEY',
16-
deployment: 'gpt-4o-mini',
17-
};
18-
19-
ALERT_TIMEOUT = 10000;
20-
21-
user: User = {
22-
id: 'user',
23-
};
24-
25-
assistant: User = {
26-
id: 'assistant',
27-
name: 'Virtual Assistant',
28-
};
29-
30-
store: Message[] = [];
10+
store: ChatTypes.Message[] = [];
3111

3212
messages: { role: 'user' | 'assistant' | 'system'; content: string }[] = [];
3313

34-
alerts: Alert[] = [];
14+
alerts: ChatTypes.Alert[] = [];
3515

3616
customStore?: CustomStore;
3717

3818
dataSource?: DataSource;
3919

40-
private readonly typingUsersSubject: BehaviorSubject<User[]> = new BehaviorSubject<User[]>([]);
20+
private readonly typingUsersSubject: BehaviorSubject<ChatTypes.User[]> = new BehaviorSubject<ChatTypes.User[]>([]);
4121

42-
private readonly alertsSubject: BehaviorSubject<Alert[]> = new BehaviorSubject<Alert[]>([]);
22+
private readonly alertsSubject: BehaviorSubject<ChatTypes.Alert[]> = new BehaviorSubject<ChatTypes.Alert[]>([]);
4323

4424
constructor() {
45-
this.chatService = new OpenAI(this.OpenAIConfig);
25+
this.chatService = new OpenAI(OpenAIConfig);
4626
this.initDataSource();
4727
this.typingUsersSubject.next([]);
4828
this.alertsSubject.next([]);
4929
}
5030

51-
get typingUsers$(): Observable<User[]> {
31+
get typingUsers$(): Observable<ChatTypes.User[]> {
5232
return this.typingUsersSubject.asObservable();
5333
}
5434

55-
get alerts$(): Observable<Alert[]> {
35+
get alerts$(): Observable<ChatTypes.Alert[]> {
5636
return this.alertsSubject.asObservable();
5737
}
5838

@@ -74,7 +54,7 @@ class AppService {
7454
resolve([...this.store]);
7555
}, 0);
7656
}),
77-
insert: (message: Message) => new Promise((resolve): void => {
57+
insert: (message: ChatTypes.Message) => new Promise((resolve): void => {
7858
setTimeout(() => {
7959
this.store.push(message);
8060
resolve(message);
@@ -94,17 +74,18 @@ class AppService {
9474
role: msg.role,
9575
content: msg.content,
9676
})),
97-
model: this.OpenAIConfig.deployment,
77+
model: OpenAIConfig.deployment,
9878
};
9979

10080
const response = await this.chatService.chat.completions.create(params);
10181
const data = { choices: response.choices };
10282
return data.choices[0].message?.content;
10383
}
10484

105-
async processMessageSending(): Promise<void> {
106-
this.toggleDisabledState(true);
107-
this.typingUsersSubject.next([this.assistant]);
85+
async processMessageSending(setDisabled: Function, event: Event | undefined): Promise<void> {
86+
setDisabled(true);
87+
(event?.target as HTMLElement).blur();
88+
this.typingUsersSubject.next([assistant]);
10889

10990
try {
11091
const aiResponse = await this.getAIResponse(this.messages);
@@ -114,21 +95,12 @@ class AppService {
11495
this.renderAssistantMessage(aiResponse ?? '');
11596
}, 200);
11697
} catch {
98+
(event?.target as HTMLElement).focus();
11799
this.typingUsersSubject.next([]);
118100
this.alertLimitReached();
119101
} finally {
120-
this.toggleDisabledState(false);
121-
}
122-
}
123-
124-
toggleDisabledState(disabled: boolean): void {
125-
let element = document.querySelector('.dx-chat-messagebox-textarea');
126-
let textAreaInstance = element ? TextArea.getInstance(element) as TextArea : null;
127-
128-
textAreaInstance?.option({ disabled });
129-
130-
if (!disabled) {
131-
textAreaInstance?.focus();
102+
(event?.target as HTMLElement).focus();
103+
setDisabled(false);
132104
}
133105
}
134106

@@ -152,7 +124,7 @@ class AppService {
152124
const message = {
153125
id: Date.now(),
154126
timestamp: new Date(),
155-
author: this.assistant,
127+
author: assistant,
156128
text,
157129
};
158130

@@ -168,10 +140,10 @@ class AppService {
168140

169141
setTimeout((): void => {
170142
this.setAlerts([]);
171-
}, this.ALERT_TIMEOUT);
143+
}, ALERT_TIMEOUT);
172144
}
173145

174-
setAlerts(alerts: Alert[]): void {
146+
setAlerts(alerts: ChatTypes.Alert[]): void {
175147
this.alerts = alerts;
176148
this.alertsSubject.next(alerts);
177149
}
@@ -194,13 +166,14 @@ class AppService {
194166
}
195167
}
196168

197-
onMessageEntered({ message }: MessageEnteredEvent): void {
169+
onMessageEntered(event: ChatTypes.MessageEnteredEvent, setDisabled: Function): void {
170+
let { message } = event;
198171
this.dataSource
199172
?.store()
200173
.push([{ type: 'insert', data: { id: Date.now(), ...message } }]);
201174

202175
this.messages.push({ role: 'user', content: message?.text ?? '' });
203-
void this.processMessageSending();
176+
void this.processMessageSending(setDisabled, event.event);
204177
}
205178
}
206179

React/src/components/ChatApp.tsx

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,15 @@
11
import { useState, useEffect, useCallback } from 'react';
22
import { loadMessages } from 'devextreme/localization';
33
import Chat, { type ChatTypes } from 'devextreme-react/chat';
4-
import {
5-
type User, type Alert, type MessageEnteredEvent,
6-
} from 'devextreme/ui/chat';
74
import { appService } from '../ChatService';
85
import MessageTemplate from './MessageTemplate';
6+
import { CHAT_DISABLED_CLASS, user as chatUser } from '../data.ts';
97

108
export default function ChatApp(): JSX.Element {
11-
const user = appService.user;
12-
const [typingUsers, setTypingUsers] = useState<User[]>([]);
13-
const [alerts, setAlerts] = useState<Alert[]>([]);
9+
const user = chatUser;
10+
const [isDisabled, setDisabled] = useState(false);
11+
const [typingUsers, setTypingUsers] = useState<ChatTypes.User[]>([]);
12+
const [alerts, setAlerts] = useState<ChatTypes.Alert[]>([]);
1413

1514
useEffect(() => {
1615
const typingSubscription = appService.typingUsers$.subscribe(setTypingUsers);
@@ -21,18 +20,18 @@ export default function ChatApp(): JSX.Element {
2120
};
2221
}, []);
2322

24-
const onMessageEntered = useCallback(async (e: MessageEnteredEvent): Promise<void> => {
25-
await appService.onMessageEntered(e);
26-
}, []);
23+
const onMessageEntered = useCallback((e: ChatTypes.MessageEnteredEvent): void => {
24+
appService.onMessageEntered(e, setDisabled);
25+
}, [isDisabled]);
2726

2827
const onRegenerateButtonClick = useCallback(async (): Promise<void> => {
28+
setDisabled(true);
2929
appService.updateLastMessage();
30-
appService.toggleDisabledState(true);
3130

3231
try {
3332
await appService.regenerate();
3433
} finally {
35-
appService.toggleDisabledState(false);
34+
setDisabled(false);
3635
}
3736
}, []);
3837

@@ -44,6 +43,7 @@ export default function ChatApp(): JSX.Element {
4443
return (
4544
<div className="demo-container">
4645
<Chat
46+
className={isDisabled ? CHAT_DISABLED_CLASS : ''}
4747
dataSource={appService.dataSource}
4848
reloadOnChange={false}
4949
showAvatar={false}
@@ -52,7 +52,7 @@ export default function ChatApp(): JSX.Element {
5252
height={710}
5353
typingUsers={typingUsers}
5454
alerts={alerts}
55-
onMessageEntered={(e: MessageEnteredEvent): void => void onMessageEntered(e)}
55+
onMessageEntered={onMessageEntered}
5656
messageRender={messageRender}
5757
/>
5858
</div>

React/src/components/MessageTemplate.tsx

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -41,20 +41,22 @@ function MessageTemplate({ text, onRegenerateButtonClick }: MessageProps): JSX.E
4141
<div className='dx-chat-messagebubble-text'>
4242
{parsedHtml}
4343
</div>
44-
<div className='dx-bubble-button-container'>
45-
<Button
46-
icon={icon}
47-
stylingMode='text'
48-
hint='Copy'
49-
onClick={onCopyButtonClick}
50-
/>
51-
<Button
52-
icon='refresh'
53-
stylingMode='text'
54-
hint='Regenerate'
55-
onClick={onRegenerateButtonClick}
56-
/>
57-
</div>
44+
{text !== 'Regeneration...' && (
45+
<div className='dx-bubble-button-container'>
46+
<Button
47+
icon={icon}
48+
stylingMode='text'
49+
hint='Copy'
50+
onClick={onCopyButtonClick}
51+
/>
52+
<Button
53+
icon='refresh'
54+
stylingMode='text'
55+
hint='Regenerate'
56+
onClick={onRegenerateButtonClick}
57+
/>
58+
</div>)
59+
}
5860
</React.Fragment>
5961
);
6062
}

React/src/data.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { type ChatTypes } from 'devextreme-react/chat';
2+
3+
export const OpenAIConfig = {
4+
dangerouslyAllowBrowser: true,
5+
apiKey: 'OPEN_AI_KEY',
6+
deployment: 'gpt-4o-mini',
7+
};
8+
9+
export const ALERT_TIMEOUT = 10000;
10+
11+
export const CHAT_DISABLED_CLASS = 'chat-disabled';
12+
13+
export const user: ChatTypes.User = {
14+
id: 'user',
15+
};
16+
17+
export const assistant: ChatTypes.User = {
18+
id: 'assistant',
19+
name: 'Virtual Assistant',
20+
};

0 commit comments

Comments
 (0)