Skip to content

Commit

Permalink
Chat - Ai chat demo fixes for React and Vue
Browse files Browse the repository at this point in the history
  • Loading branch information
Zedwag committed Dec 11, 2024
1 parent d37b38b commit 3753310
Show file tree
Hide file tree
Showing 6 changed files with 71 additions and 113 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ async function getAIResponse(messages) {
temperature: 0.7,
};

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

return data.choices[0].message?.content;
Expand Down
79 changes: 26 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,102 @@ 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,
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 +113,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 +126,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
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
import React from 'react';
import ReactDOM from 'react-dom';

import App from './App.js';

ReactDOM.render(
<App />,
document.getElementById('app'),
);
ReactDOM.render(<App />, document.getElementById('app'));
4 changes: 3 additions & 1 deletion apps/demos/Demos/Chat/AIAndChatbotIntegration/Vue/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ import {
ALERT_TIMEOUT,
} from './data.ts';
const chatService = new AzureOpenAI(AzureOpenAIConfig);
let chatService;
const typingUsers = ref([]);
const alerts = ref([]);
Expand All @@ -76,6 +76,8 @@ const copyButtonIcon = ref('copy');
onBeforeMount(() => {
loadMessages(dictionary);
chatService = new AzureOpenAI(AzureOpenAIConfig);
});
async function getAIResponse(messages) {
Expand Down

0 comments on commit 3753310

Please sign in to comment.