Skip to content

Commit

Permalink
Merge branch '24_2' of https://github.com/DevExpress/DevExtreme into …
Browse files Browse the repository at this point in the history
…T1250405_24_2
  • Loading branch information
tongsonbarbs committed Dec 9, 2024
2 parents ea1eee3 + c2f2b0b commit 80ccbd2
Show file tree
Hide file tree
Showing 1,861 changed files with 24,080 additions and 20,501 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/demos_visual_tests_frameworks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -451,7 +451,7 @@ jobs:
working-directory: apps/demos
env:
CHANGEDFILEINFOSPATH: changed-files.json
BROWSERS: chrome:headless --disable-gpu --window-size=1200,800 --js-flags=--random-seed=2147483647
BROWSERS: chrome:headless --window-size=1200,800 --disable-partial-raster --disable-skia-runtime-opts --run-all-compositor-stages-before-draw --disable-new-content-rendering-timeout --disable-threaded-animation --disable-threaded-scrolling --disable-checker-imaging --disable-image-animation-resync --use-gl="swiftshader" --disable-features=PaintHolding --js-flags=--random-seed=2147483647 --font-render-hinting=none --disable-font-subpixel-positioning
# DEBUG: hammerhead:*,testcafe:*
CONCURRENCY: 4
TCQUARANTINE: true
Expand Down
2 changes: 1 addition & 1 deletion apps/angular/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
"zone.js": "0.14.10"
},
"devDependencies": {
"@angular-devkit/build-angular": "17.3.8",
"@angular-devkit/build-angular": "17.3.11",
"@angular/cli": "17.3.11",
"@angular/language-service": "17.3.12",
"@angular/platform-server": "17.3.12",
Expand Down
182 changes: 182 additions & 0 deletions apps/demos/Demos/Chat/AIAndChatbotIntegration/React/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
import React, { useState } from 'react';
import Chat, { ChatTypes } from 'devextreme-react/chat';
import { AzureOpenAI } from 'openai';
import { MessageEnteredEvent } from 'devextreme/ui/chat';
import CustomStore from 'devextreme/data/custom_store';
import DataSource from 'devextreme/data/data_source';
import { loadMessages } from 'devextreme/localization';
import {
user,
assistant,
AzureOpenAIConfig,
REGENERATION_TEXT,
CHAT_DISABLED_CLASS,
ALERT_TIMEOUT
} from './data.ts';
import Message from './Message.tsx';

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 },
}]);
}

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);
});
});
},
});

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

export default function App() {
const [alerts, setAlerts] = useState<ChatTypes.Alert[]>([]);
const [typingUsers, setTypingUsers] = useState<ChatTypes.User[]>([]);
const [classList, setClassList] = useState<string>('');

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

setTimeout(() => {
setAlerts([]);
}, ALERT_TIMEOUT);
}

function toggleDisabledState(disabled: boolean, 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 });
renderAssistantMessage(aiResponse);
}, 200);
} catch {
setTypingUsers([]);
messages.pop();
alertLimitReached();
} finally {
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 {
updateLastMessage(messages.at(-1).content);
alertLimitReached();
} finally {
toggleDisabledState(false);
}
}

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

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

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

return (
<Chat
className={classList}
dataSource={dataSource}
reloadOnChange={false}
showAvatar={false}
showDayHeaders={false}
user={user}
height={710}
onMessageEntered={onMessageEntered}
alerts={alerts}
typingUsers={typingUsers}
messageRender={(data) => Message(data, onRegenerateButtonClick)}
/>
);
}
63 changes: 63 additions & 0 deletions apps/demos/Demos/Chat/AIAndChatbotIntegration/React/Message.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import React, { useState, useRef } from 'react';
import Button from 'devextreme-react/button';
import { unified } from 'unified';
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.ts';

function convertToHtml(value: string) {
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>
)
}

export default Message;
25 changes: 25 additions & 0 deletions apps/demos/Demos/Chat/AIAndChatbotIntegration/React/data.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { ChatTypes } from 'devextreme-react/chat';

const date = new Date();
date.setHours(0, 0, 0, 0);

export const AzureOpenAIConfig = {
dangerouslyAllowBrowser: true,
deployment: 'gpt-4o-mini',
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: ChatTypes.User = {
id: 'user',
};

export const assistant: ChatTypes.User = {
id: 'assistant',
name: 'Virtual Assistant',
};
24 changes: 24 additions & 0 deletions apps/demos/Demos/Chat/AIAndChatbotIntegration/React/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>DevExtreme Demo</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0" />
<link rel="stylesheet" type="text/css" href="../../../../node_modules/devextreme-dist/css/dx.light.css" />
<link rel="stylesheet" type="text/css" href="styles.css" />

<script src="../../../../node_modules/core-js/client/shim.min.js"></script>
<script src="../../../../node_modules/systemjs/dist/system.js"></script>
<script type="text/javascript" src="config.js"></script>
<script type="text/javascript">
System.import("./index.tsx");
</script>
</head>

<body class="dx-viewport">
<div class="demo-container">
<div id="app"></div>
</div>
</body>
</html>
9 changes: 9 additions & 0 deletions apps/demos/Demos/Chat/AIAndChatbotIntegration/React/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import React from 'react';
import ReactDOM from 'react-dom';

import App from './App.tsx';

ReactDOM.render(
<App />,
document.getElementById('app'),
);
65 changes: 65 additions & 0 deletions apps/demos/Demos/Chat/AIAndChatbotIntegration/React/styles.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
#app {
display: flex;
justify-content: center;
}

.dx-chat {
max-width: 900px;
}

.dx-chat-messagelist-empty-image {
display: none;
}

.dx-chat-messagelist-empty-message {
font-size: var(--dx-font-size-heading-5);
}

.dx-chat-messagebubble-content,
.dx-chat-messagebubble-text {
display: flex;
flex-direction: column;
}

.dx-bubble-button-container {
display: none;
}

.dx-button {
display: inline-block;
color: var(--dx-color-icon);
}

.dx-chat-messagegroup-alignment-start:last-child .dx-chat-messagebubble:last-child .dx-bubble-button-container {
display: flex;
gap: 4px;
margin-top: 8px;
}

.dx-chat-messagebubble-content > div > p:first-child {
margin-top: 0;
}

.dx-chat-messagebubble-content > div > p:last-child {
margin-bottom: 0;
}

.dx-chat-messagebubble-content ol,
.dx-chat-messagebubble-content ul {
white-space: normal;
}

.dx-chat-messagebubble-content h1,
.dx-chat-messagebubble-content h2,
.dx-chat-messagebubble-content h3,
.dx-chat-messagebubble-content h4,
.dx-chat-messagebubble-content h5,
.dx-chat-messagebubble-content h6 {
font-size: revert;
font-weight: revert;
}

.dx-chat-disabled .dx-chat-messagebox {
opacity: 0.5;
pointer-events: none;
}
Loading

0 comments on commit 80ccbd2

Please sign in to comment.