Skip to content

Commit

Permalink
Merge branch '24_2' into grids/cardview/main
Browse files Browse the repository at this point in the history
  • Loading branch information
pomahtri committed Dec 16, 2024
2 parents 967997f + 49f1508 commit b2a9553
Show file tree
Hide file tree
Showing 24 changed files with 263 additions and 138 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
import { Observable } from 'rxjs';
import { AppService } from './app.service';
import { loadMessages } from 'devextreme/localization';
import { DataSource } from 'devextreme/common/data';
import DataSource from 'devextreme/data/data_source';

if (!/localhost/.test(document.location.host)) {
enableProdMode();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import {
Alert,
MessageEnteredEvent
} from 'devextreme/ui/chat';
import { DataSource, CustomStore } from 'devextreme/common/data';
import DataSource from 'devextreme/data/data_source';
import CustomStore from 'devextreme/data/custom_store';

@Injectable({
providedIn: 'root',
Expand All @@ -26,7 +27,7 @@ export class AppService {
endpoint: 'https://public-api.devexpress.com/demo-openai',
apiKey: 'DEMO',
}

REGENERATION_TEXT = 'Regeneration...';
ALERT_TIMEOUT = 1000 * 60;

Expand Down Expand Up @@ -95,7 +96,7 @@ export class AppService {
});
},
});

this.dataSource = new DataSource({
store: this.customStore,
paginate: false,
Expand All @@ -105,20 +106,21 @@ export class AppService {
async getAIResponse(messages) {
const params = {
messages,
model: this.AzureOpenAIConfig.deployment,
max_tokens: 1000,
temperature: 0.7,
};

const response = await this.chatService.chat.completions.create(params);
const data = { choices: response.choices };

return data.choices[0].message?.content;
}

async processMessageSending(message, event) {
this.messages.push({ role: 'user', content: message.text });
this.typingUsersSubject.next([this.assistant]);

try {
const aiResponse = await this.getAIResponse(this.messages);

Expand All @@ -137,7 +139,7 @@ export class AppService {
updateLastMessage(text = this.REGENERATION_TEXT) {
const items = this.dataSource.items();
const lastMessage = items.at(-1);

this.dataSource.store().push([{
type: 'update',
key: lastMessage.id,
Expand All @@ -160,7 +162,7 @@ export class AppService {
this.setAlerts([{
message: 'Request limit reached, try again in a minute.'
}]);

setTimeout(() => {
this.setAlerts([]);
}, this.ALERT_TIMEOUT);
Expand Down Expand Up @@ -193,7 +195,7 @@ export class AppService {

return result;
}

async onMessageEntered({ message, event }: MessageEnteredEvent) {
this.dataSource.store().push([{ type: 'insert', data: { id: Date.now(), ...message } }]);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ const chatService = new AzureOpenAI(AzureOpenAIConfig);
async function getAIResponse(messages) {
const params = {
messages,
model: AzureOpenAIConfig.deployment,
max_tokens: 1000,
temperature: 0.7,
};
Expand Down
80 changes: 27 additions & 53 deletions apps/demos/Demos/Chat/AIAndChatbotIntegration/ReactJs/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,122 +4,103 @@ import { AzureOpenAI } from 'openai';
import CustomStore from 'devextreme/data/custom_store';
import DataSource from 'devextreme/data/data_source';
import { loadMessages } from 'devextreme/localization';
import {
import {
user,
assistant,
AzureOpenAIConfig,
REGENERATION_TEXT,
CHAT_DISABLED_CLASS,
ALERT_TIMEOUT
ALERT_TIMEOUT,
} from './data.js';
import Message from './Message.js';

const store = [];
const messages = [];

loadMessages({
en: {
'dxChat-emptyListMessage': 'Chat is Empty',
'dxChat-emptyListPrompt': 'AI Assistant is ready to answer your questions.',
'dxChat-textareaPlaceholder': 'Ask AI Assistant...',
},
});

const chatService = new AzureOpenAI(AzureOpenAIConfig);

async function getAIResponse(messages) {
const params = {
messages,
model: AzureOpenAIConfig.deployment,
max_tokens: 1000,
temperature: 0.7,
};

const response = await chatService.chat.completions.create(params);
const data = { choices: response.choices };

return data.choices[0].message?.content;
}

function updateLastMessage(text = REGENERATION_TEXT) {
const items = dataSource.items();
const lastMessage = items.at(-1);

dataSource.store().push([{
type: 'update',
key: lastMessage.id,
data: { text },
}]);
dataSource.store().push([
{
type: 'update',
key: lastMessage.id,
data: { text },
},
]);
}

function renderAssistantMessage(text) {
const message = {
id: Date.now(),
timestamp: new Date(),
author: assistant,
text,
};

dataSource.store().push([{ type: 'insert', data: message }]);
}

const customStore = new CustomStore({
key: 'id',
load: () => {
return new Promise((resolve) => {
setTimeout(() => {
resolve([...store]);
}, 0);
});
},
insert: (message) => {
return new Promise((resolve) => {
setTimeout(() => {
store.push(message);
resolve(message);
});
load: () => new Promise((resolve) => {
setTimeout(() => {
resolve([...store]);
}, 0);
}),
insert: (message) => new Promise((resolve) => {
setTimeout(() => {
store.push(message);
resolve(message);
});
},
}),
});

const dataSource = new DataSource({
store: customStore,
paginate: false,
})

});
export default function App() {
const [alerts, setAlerts] = useState([]);
const [typingUsers, setTypingUsers] = useState([]);
const [classList, setClassList] = useState('');

function alertLimitReached() {
setAlerts([{
message: 'Request limit reached, try again in a minute.'
}]);

setAlerts([
{
message: 'Request limit reached, try again in a minute.',
},
]);
setTimeout(() => {
setAlerts([]);
}, ALERT_TIMEOUT);
}

function toggleDisabledState(disabled, event = undefined) {
setClassList(disabled ? CHAT_DISABLED_CLASS : '');

if (disabled) {
event?.target.blur();
} else {
event?.target.focus();
}
};

}
async function processMessageSending(message, event) {
toggleDisabledState(true, event);

messages.push({ role: 'user', content: message.text });
setTypingUsers([assistant]);

try {
const aiResponse = await getAIResponse(messages);

setTimeout(() => {
setTypingUsers([]);
messages.push({ role: 'assistant', content: aiResponse });
Expand All @@ -133,13 +114,10 @@ export default function App() {
toggleDisabledState(false, event);
}
}

async function regenerate() {
toggleDisabledState(true);

try {
const aiResponse = await getAIResponse(messages.slice(0, -1));

updateLastMessage(aiResponse);
messages.at(-1).content = aiResponse;
} catch {
Expand All @@ -149,20 +127,16 @@ export default function App() {
toggleDisabledState(false);
}
}

function onMessageEntered({ message, event }) {
dataSource.store().push([{ type: 'insert', data: { id: Date.now(), ...message } }]);

if (!alerts.length) {
processMessageSending(message, event);
}
}

function onRegenerateButtonClick() {
updateLastMessage();
regenerate();
}

return (
<Chat
className={classList}
Expand Down
88 changes: 39 additions & 49 deletions apps/demos/Demos/Chat/AIAndChatbotIntegration/ReactJs/Message.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,59 +5,49 @@ import remarkParse from 'remark-parse';
import remarkRehype from 'remark-rehype';
import rehypeStringify from 'rehype-stringify';
import HTMLReactParser from 'html-react-parser';

import { REGENERATION_TEXT } from './data.js';

function convertToHtml(value) {
const result = unified()
.use(remarkParse)
.use(remarkRehype)
.use(rehypeStringify)
.processSync(value)
.toString();

return result;
const result = unified()
.use(remarkParse)
.use(remarkRehype)
.use(rehypeStringify)
.processSync(value)
.toString();
return result;
}

function Message({ message }, onRegenerateButtonClick) {
const [icon, setIcon] = useState('copy');

if (message.text === REGENERATION_TEXT) {
return <span>{REGENERATION_TEXT}</span>;
}

function onCopyButtonClick() {
navigator.clipboard?.writeText(message.text);
setIcon('check');

setTimeout(() => {
setIcon('copy');
}, 2500);
}

return (
<React.Fragment>
<div
className='dx-chat-messagebubble-text'
>
{HTMLReactParser(convertToHtml(message.text))}
</div>
<div className='dx-bubble-button-container'>
<Button
icon={icon}
stylingMode='text'
hint='Copy'
onClick={onCopyButtonClick}
/>
<Button
icon='refresh'
stylingMode='text'
hint='Regenerate'
onClick={onRegenerateButtonClick}
/>
</div>
</React.Fragment>
)
const [icon, setIcon] = useState('copy');
if (message.text === REGENERATION_TEXT) {
return <span>{REGENERATION_TEXT}</span>;
}
function onCopyButtonClick() {
navigator.clipboard?.writeText(message.text);
setIcon('check');
setTimeout(() => {
setIcon('copy');
}, 2500);
}
return (
<React.Fragment>
<div className="dx-chat-messagebubble-text">
{HTMLReactParser(convertToHtml(message.text))}
</div>
<div className="dx-bubble-button-container">
<Button
icon={icon}
stylingMode="text"
hint="Copy"
onClick={onCopyButtonClick}
/>
<Button
icon="refresh"
stylingMode="text"
hint="Regenerate"
onClick={onRegenerateButtonClick}
/>
</div>
</React.Fragment>
);
}

export default Message;
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,13 @@ export const AzureOpenAIConfig = {
apiVersion: '2024-02-01',
endpoint: 'https://public-api.devexpress.com/demo-openai',
apiKey: 'DEMO',
}

};
export const REGENERATION_TEXT = 'Regeneration...';
export const CHAT_DISABLED_CLASS = 'dx-chat-disabled';
export const ALERT_TIMEOUT = 1000 * 60;

export const user = {
id: 'user',
};

export const assistant = {
id: 'assistant',
name: 'Virtual Assistant',
Expand Down
Loading

0 comments on commit b2a9553

Please sign in to comment.