Backend API
<div class="demo-container">
<dx-chat
[class.chat-disabled]="isDisabled"
[dataSource]="dataSource"
[reloadOnChange]="false"
[showAvatar]="false"
[showDayHeaders]="false"
[user]="user"
height="520"
[typingUsers]="typingUsers$ | async"
[alerts]="alerts$ | async"
[speechToTextEnabled]="true"
[inputFieldText]="inputFieldText"
[suggestions]="suggestions"
(onMessageEntered)="onMessageEntered($event)"
(onInputFieldTextChanged)="onInputFieldTextChanged($event)"
messageTemplate="messageTemplate"
>
<div *dxTemplate="let data of 'messageTemplate'">
<div
class="chat-messagebubble-text"
[innerHTML]="convertToHtml(data.message)"
></div>
</div>
</dx-chat>
<div class="options">
<div class="caption">Suggestion Options</div>
<div class="suggestions-options">
<div class="option">
<dx-switch
[value]="sendImmediately"
(onValueChanged)="onSendImmediatelyChanged($event)"
></dx-switch>
<span>Send Immediately</span>
</div>
<div class="option">
<dx-switch
[value]="hideAfterUse"
(onValueChanged)="onHideAfterUseChanged($event)"
></dx-switch>
<span>Hide After Use</span>
</div>
</div>
</div>
</div>
import { bootstrapApplication } from '@angular/platform-browser';
import { Component, enableProdMode, provideZoneChangeDetection } from '@angular/core';
import { AsyncPipe } from '@angular/common';
import { DxChatModule, DxSwitchModule } from 'devextreme-angular';
import type { DxChatTypes } from 'devextreme-angular/ui/chat';
import type { DxSwitchTypes } from 'devextreme-angular/ui/switch';
import { Observable } from 'rxjs';
import { loadMessages } from 'devextreme-angular/common/core/localization';
import { DataSource } from 'devextreme-angular/common/data';
import { AppService } from './app.service';
import { AiService } from './ai/ai.service';
if (!/localhost/.test(document.location.host)) {
enableProdMode();
}
let modulePrefix = '';
// @ts-ignore
if (window && window.config?.packageConfigPaths) {
modulePrefix = '/app';
}
@Component({
selector: 'demo-app',
templateUrl: `app/app.component.html`,
styleUrls: [`app/app.component.css`],
imports: [
DxChatModule,
DxSwitchModule,
AsyncPipe,
],
})
export class AppComponent {
dataSource: DataSource;
user: DxChatTypes.User;
typingUsers$: Observable<DxChatTypes.User[]>;
alerts$: Observable<DxChatTypes.Alert[]>;
isDisabled = false;
inputFieldText = '';
sendImmediately = false;
hideAfterUse = false;
suggestions: DxChatTypes.Properties['suggestions'];
constructor(private readonly appService: AppService) {
loadMessages(this.appService.getDictionary());
this.dataSource = this.appService.dataSource;
this.user = this.appService.user;
this.alerts$ = this.appService.alerts$;
this.typingUsers$ = this.appService.typingUsers$;
this.suggestions = {
items: this.appService.suggestionItems,
onItemClick: this.onSuggestionItemClick.bind(this),
};
}
convertToHtml(message: DxChatTypes.Message): string {
return this.appService.convertToHtml(message.text);
}
toggleDisabledState(disabled: boolean, event = undefined) {
this.isDisabled = disabled;
this.suggestions = { ...this.suggestions, disabled };
if (disabled) {
event?.target.blur();
} else {
event?.target.focus();
}
}
async onMessageEntered(e: DxChatTypes.MessageEnteredEvent): Promise<void> {
if (this.isDisabled) return;
if (!this.appService.alerts.length) {
this.toggleDisabledState(true, e.event);
}
try {
await this.appService.onMessageEntered(e);
} finally {
this.toggleDisabledState(false, e.event);
}
}
async onSuggestionItemClick(e: { itemData?: { text: string; prompt: string } }): Promise<void> {
const { text = '', prompt = '' } = e.itemData ?? {};
if (this.hideAfterUse) {
const currentItems = (this.suggestions?.items ?? []) as { text: string; prompt: string }[];
this.suggestions = {
items: currentItems.filter((item) => item.text !== text),
onItemClick: this.onSuggestionItemClick.bind(this),
};
}
if (this.sendImmediately) {
const message: DxChatTypes.Message = {
id: Date.now(),
timestamp: new Date(),
author: this.user,
text: prompt,
};
this.appService.insertMessage(message);
if (!this.appService.alerts.length) {
this.toggleDisabledState(true);
try {
await this.appService.processMessageSending(message);
} finally {
this.toggleDisabledState(false);
}
}
} else {
this.inputFieldText = prompt;
}
}
onInputFieldTextChanged(e: DxChatTypes.InputFieldTextChangedEvent): void {
this.inputFieldText = e.value ?? '';
}
onSendImmediatelyChanged(e: DxSwitchTypes.ValueChangedEvent): void {
this.sendImmediately = e.value;
}
onHideAfterUseChanged(e: DxSwitchTypes.ValueChangedEvent): void {
this.hideAfterUse = e.value;
}
}
bootstrapApplication(AppComponent, {
providers: [
provideZoneChangeDetection({ eventCoalescing: true }),
AppService,
AiService,
],
});
.demo-container {
display: flex;
flex-direction: column;
align-items: center;
gap: 20px;
}
.options {
padding: 20px;
display: flex;
flex-direction: column;
min-width: 280px;
background-color: rgba(191, 191, 191, 0.15);
gap: 16px;
width: 100%;
max-width: 900px;
box-sizing: border-box;
}
.suggestions-options {
display: flex;
align-items: center;
gap: 24px;
}
.option {
display: flex;
align-items: center;
gap: 8px;
}
.caption {
font-size: var(--dx-font-size-sm);
font-weight: 500;
}
::ng-deep .dx-chat {
max-width: 900px;
}
::ng-deep .dx-chat-messagelist-empty-image {
display: none;
}
::ng-deep .dx-chat-messagelist-empty-message {
font-size: var(--dx-font-size-heading-5);
}
::ng-deep .dx-chat-messagelist-empty-prompt {
max-width: 462px;
line-height: 20px;
}
::ng-deep .dx-template-wrapper > div > p:first-child {
margin-top: 0;
}
::ng-deep .dx-template-wrapper > div > p:last-child {
margin-bottom: 0;
}
::ng-deep .dx-chat-messagebubble-content h1,
::ng-deep .dx-chat-messagebubble-content h2,
::ng-deep .dx-chat-messagebubble-content h3,
::ng-deep .dx-chat-messagebubble-content h4,
::ng-deep .dx-chat-messagebubble-content h5,
::ng-deep .dx-chat-messagebubble-content h6 {
font-size: revert;
font-weight: revert;
}
::ng-deep .chat-disabled .dx-chat-messagebox {
opacity: 0.5;
pointer-events: none;
}
::ng-deep .dx-chat-suggestions .dx-button {
max-width: 255px;
}
import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs';
import { unified } from 'unified';
import remarkParse from 'remark-parse';
import remarkRehype from 'remark-rehype';
import rehypeStringify from 'rehype-stringify';
import rehypeMinifyWhitespace from 'rehype-minify-whitespace';
import { type DxChatTypes } from 'devextreme-angular/ui/chat';
import { DataSource, CustomStore } from 'devextreme-angular/common/data';
import { AiService, type AIMessage } from './ai/ai.service';
const ALERT_TIMEOUT = 1000 * 60;
const suggestionItems = [
{ text: '📦 Track my orders', prompt: 'Track my orders' },
{ text: '⭐ Check in-stock favorites', prompt: 'Check in-stock favorites' },
{ text: '🔄 Start a return', prompt: 'Start a return' },
];
const SYSTEM_PROMPT = `
You are a logistics support assistant for an online marketplace.
The user is logged into their account.
If asked about orders, generate realistic and consistent mock data.
Use plausible order IDs, dates within the last 30 days, and the following order status values: Processing, Shipped, In Transit, Out for Delivery, Delivered.
Never add details outside of given parameters. Do NOT create links.
Keep responses structured and professional.
When appropriate, use bullet points.
The user has exactly 3 recent orders:
- #A48291 (In Transit)
- #A47903 (Delivered Feb 8)
- #A47188 (Processing)
Favorite items (example, do NOT use real brand names):
VoltEdge 65W Fast Wall Charger (Black)
✅ Back in stock — Ships in 1-2 business days
Nimbus Pro Wireless Mouse (Graphite)
❌ Still out of stock — Restock expected Feb 26
PaperLite E-Reader (16 GB, Midnight)
✅ Back in stock — Estimated delivery Feb 20-21
LumaArc Minimal Desk Lamp (Matte Black)
⚠️ Limited stock — Only 3 left
WaveTune Over-Ear Wireless Headphones (Ocean Blue)
❌ Out of stock — No restock date available
AquaCarry Stainless Steel Bottle (20 oz, Sage)
✅ In stock — Available for same-day pick-up
Marketplace Return Policy (Mock)
1. Return Window
a. Items can be returned within 30 days of delivery.
b. Returns cannot be started before an item is delivered.
2. Condition Requirements
a. Items must be unused and in original packaging.
b. Opened electronics are eligible if returned within 14 days.
c. Digital products are non-refundable.
3. Refund Method
a. Refunds are issued to the original payment method.
b. Processing time: 3-5 business days after inspection.
4. Shipping Fees
a. Returns due to defective or incorrect items: free.
b. Returns for change of mind: $4.99 return fee deducted.
5. Exchanges
a. Exchanges are available for size or color variants only.
b. If replacement is unavailable, refund is issued instead.
`;
@Injectable({
providedIn: 'root',
})
export class AppService {
aiService: AiService;
user: DxChatTypes.User = { id: 'user' };
assistant: DxChatTypes.User = {
id: 'assistant',
name: 'AI Assistant',
};
store: any[] = [];
messages: AIMessage[] = [
{ role: 'system', content: SYSTEM_PROMPT },
];
alerts: DxChatTypes.Alert[] = [];
suggestionItems = suggestionItems;
customStore: CustomStore;
dataSource: DataSource;
private typingUsersSubject: BehaviorSubject<DxChatTypes.User[]> = new BehaviorSubject([]);
private alertsSubject: BehaviorSubject<DxChatTypes.Alert[]> = new BehaviorSubject([]);
constructor(aiService: AiService) {
this.aiService = aiService;
this.initDataSource();
this.typingUsersSubject.next([]);
this.alertsSubject.next([]);
}
get typingUsers$(): Observable<DxChatTypes.User[]> {
return this.typingUsersSubject.asObservable();
}
get alerts$(): Observable<DxChatTypes.Alert[]> {
return this.alertsSubject.asObservable();
}
getDictionary() {
return {
en: {
'dxChat-emptyListMessage': 'Chat is Empty',
'dxChat-emptyListPrompt': 'Your Shopping AI Assistant is ready to help. Ask a question or choose one of the suggested prompts to get started.',
'dxChat-textareaPlaceholder': 'Ask AI Assistant...',
},
};
}
initDataSource() {
this.customStore = new CustomStore({
key: 'id',
load: () => new Promise((resolve) => {
setTimeout(() => {
resolve([...this.store]);
}, 0);
}),
insert: (message) => new Promise((resolve) => {
setTimeout(() => {
this.store.push(message);
resolve(message);
});
}),
});
this.dataSource = new DataSource({
store: this.customStore,
paginate: false,
});
}
insertMessage(message: DxChatTypes.Message): void {
this.dataSource.store().push([{ type: 'insert', data: message }]);
}
async processMessageSending(message: DxChatTypes.Message): Promise<void> {
this.messages.push({ role: 'user', content: message.text });
this.typingUsersSubject.next([this.assistant]);
try {
const aiResponse = await this.aiService.getAIResponse(this.messages) as string;
setTimeout(() => {
this.typingUsersSubject.next([]);
this.messages.push({ role: 'assistant', content: aiResponse });
this.renderAssistantMessage(aiResponse);
}, 200);
} catch {
this.typingUsersSubject.next([]);
this.messages.pop();
this.alertLimitReached();
}
}
renderAssistantMessage(text: string): void {
this.insertMessage({
id: Date.now(),
timestamp: new Date(),
author: this.assistant,
text,
});
}
alertLimitReached(): void {
this.setAlerts([{ message: 'Request limit reached, try again in a minute.' }]);
setTimeout(() => {
this.setAlerts([]);
}, ALERT_TIMEOUT);
}
setAlerts(alerts: DxChatTypes.Alert[]): void {
this.alerts = alerts;
this.alertsSubject.next(alerts);
}
async onMessageEntered({ message }: DxChatTypes.MessageEnteredEvent): Promise<void> {
this.insertMessage({ id: Date.now(), ...message });
if (!this.alerts.length) {
await this.processMessageSending(message);
}
}
convertToHtml(value: string): string {
return unified()
.use(remarkParse)
.use(remarkRehype)
.use(rehypeMinifyWhitespace)
.use(rehypeStringify)
.processSync(value)
.toString();
}
}
// In real applications, you should not transpile code in the browser.
// You can see how to create your own application with Angular and DevExtreme here:
// https://js.devexpress.com/Documentation/Guide/Angular_Components/Getting_Started/Create_a_DevExtreme_Application/
const componentNames = [
'accordion',
'action-sheet',
'autocomplete',
'bar-gauge',
'box',
'bullet',
'button-group',
'button',
'calendar',
'card-view',
'chart',
'chat',
'check-box',
'circular-gauge',
'color-box',
'context-menu',
'data-grid',
'date-box',
'date-range-box',
'defer-rendering',
'diagram',
'draggable',
'drawer',
'drop-down-box',
'drop-down-button',
'file-manager',
'file-uploader',
'filter-builder',
'form',
'funnel',
'gallery',
'gantt',
'html-editor',
'linear-gauge',
'list',
'load-indicator',
'load-panel',
'lookup',
'map',
'menu',
'multi-view',
'nested',
'number-box',
'pagination',
'pie-chart',
'pivot-grid-field-chooser',
'pivot-grid',
'polar-chart',
'popover',
'popup',
'progress-bar',
'radio-group',
'range-selector',
'range-slider',
'recurrence-editor',
'resizable',
'responsive-box',
'sankey',
'scheduler',
'scroll-view',
'select-box',
'slider',
'sortable',
'sparkline',
'speech-to-text',
'speed-dial-action',
'splitter',
'stepper',
'switch',
'tab-panel',
'tabs',
'tag-box',
'text-area',
'text-box',
'tile-view',
'toast',
'toolbar',
'tooltip',
'tree-list',
'tree-map',
'tree-view',
'validation-group',
'validation-summary',
'validator',
'vector-map',
];
window.exports = window.exports || {};
window.config = {
transpiler: 'ts',
typescriptOptions: {
module: 'system',
emitDecoratorMetadata: true,
experimentalDecorators: true,
},
meta: {
'typescript': {
'exports': 'ts',
},
'devextreme/time_zone_utils.js': {
'esModule': true,
},
'devextreme/localization.js': {
'esModule': true,
},
'devextreme/viz/palette.js': {
'esModule': true,
},
'@angular/platform-browser-dynamic': {
'esModule': true,
},
'@angular/platform-browser': {
'esModule': true,
},
'@angular/core': {
'esModule': true,
},
'@angular/common': {
'esModule': true,
},
'@angular/common/http': {
'esModule': true,
},
'@angular/animations': {
'esModule': true,
},
'@angular/forms': {
'esModule': true,
},
'openai': {
'esModule': true,
},
'zod': {
'esModule': true,
},
'zod-to-json-schema': {
'esModule': true,
},
},
paths: {
'npm:': 'https://cdn.jsdelivr.net/npm/',
'bundles:': '../../../../bundles/',
'externals:': '../../../../bundles/externals/',
'anti-forgery:': '../../../../shared/anti-forgery/',
},
map: {
'anti-forgery': 'anti-forgery:fetch-override.js',
'ts': 'npm:plugin-typescript@8.0.0/lib/plugin.js',
'typescript': 'npm:typescript@4.2.4/lib/typescript.js',
'jszip': 'npm:jszip@3.10.1/dist/jszip.min.js',
/* @angular */
'@angular/compiler': 'bundles:@angular/compiler.umd.js',
'@angular/platform-browser-dynamic': 'bundles:@angular/platform-browser-dynamic.umd.js',
'@angular/core': 'bundles:@angular/core.umd.js',
'@angular/core/primitives/signals': 'bundles:@angular/core.primitives.signals.umd.js',
'@angular/core/primitives/di': 'bundles:@angular/core.primitives.di.umd.js',
'@angular/common': 'bundles:@angular/common.umd.js',
'@angular/common/http': 'bundles:@angular/common-http.umd.js',
'@angular/platform-browser': 'bundles:@angular/platform-browser.umd.js',
'@angular/platform-browser/animations': 'bundles:@angular/platform-browser.umd.js',
'@angular/forms': 'bundles:@angular/forms.umd.js',
/* devextreme */
'devextreme': 'npm:devextreme@link:../../packages/devextreme/artifacts/npm/devextreme/cjs',
'devextreme-quill': 'npm:devextreme-quill@1.7.9/dist/dx-quill.min.js',
'devexpress-diagram': 'npm:devexpress-diagram@2.2.29',
'devexpress-gantt': 'npm:devexpress-gantt@4.1.69',
/* devextreme-angular umd maps */
'devextreme-angular': 'bundles:devextreme-angular/devextreme-angular.umd.js',
'devextreme-angular/common/ai-integration': 'bundles:devextreme-angular/devextreme-angular-common-ai-integration.umd.js',
'devextreme-angular/core': 'bundles:devextreme-angular/devextreme-angular-core.umd.js',
'devextreme-angular/common/charts': 'bundles:devextreme-angular/devextreme-angular-common-charts.umd.js',
'devextreme-angular/common/core/animation': 'bundles:devextreme-angular/devextreme-angular-common-core-animation.umd.js',
'devextreme-angular/common/core/environment': 'bundles:devextreme-angular/devextreme-angular-common-core-environment.umd.js',
'devextreme-angular/common/core/events': 'bundles:devextreme-angular/devextreme-angular-common-core-events.umd.js',
'devextreme-angular/common/core/localization': 'bundles:devextreme-angular/devextreme-angular-common-core-localization.umd.js',
'devextreme-angular/common/core': 'bundles:devextreme-angular/devextreme-angular-common-core.umd.js',
'devextreme-angular/common/data/custom-store': 'bundles:devextreme-angular/devextreme-angular-common-data-custom-store.umd.js',
'devextreme-angular/common/data': 'bundles:devextreme-angular/devextreme-angular-common-data.umd.js',
'devextreme-angular/common/export/excel': 'bundles:devextreme-angular/devextreme-angular-common-export-excel.umd.js',
'devextreme-angular/common/export/pdf': 'bundles:devextreme-angular/devextreme-angular-common-export-pdf.umd.js',
'devextreme-angular/common/export': 'bundles:devextreme-angular/devextreme-angular-common-export.umd.js',
'devextreme-angular/common/grids': 'bundles:devextreme-angular/devextreme-angular-common-grids.umd.js',
'devextreme-angular/common': 'bundles:devextreme-angular/devextreme-angular-common.umd.js',
'devextreme-angular/http': 'bundles:devextreme-angular/devextreme-angular-http.umd.js',
'devextreme-angular/core/tokens': 'bundles:devextreme-angular/devextreme-angular-core-tokens.umd.js',
...componentNames.reduce((acc, name) => {
acc[`devextreme-angular/ui/${name}`] = `bundles:devextreme-angular/devextreme-angular-ui-${name}.umd.js`;
acc[`devextreme-angular/ui/${name}/nested`] = `bundles:devextreme-angular/devextreme-angular-ui-${name}-nested.umd.js`;
return acc;
}, {}),
'unified': 'externals:unified/unified.bundle.js',
'remark-parse': 'externals:unified/remark-parse.bundle.js',
'remark-rehype': 'externals:unified/remark-rehype.bundle.js',
'remark-stringify': 'externals:unified/remark-stringify.bundle.js',
'rehype-parse': 'externals:unified/rehype-parse.bundle.js',
'rehype-remark': 'externals:unified/rehype-remark.bundle.js',
'rehype-stringify': 'externals:unified/rehype-stringify.bundle.js',
'rehype-minify-whitespace': 'externals:unified/rehype-minify-whitespace.bundle.js',
'openai': 'externals:openai.bundle.js',
'zod': 'externals:zod.bundle.js',
'zod-to-json-schema': 'externals:zod-to-json-schema.bundle.js',
'tslib': 'npm:tslib/tslib.js',
'rxjs': 'npm:rxjs@7.5.3/dist/bundles/rxjs.umd.js',
'rxjs/operators': 'npm:rxjs@7.5.3/dist/cjs/operators/index.js',
'rrule': 'npm:rrule@2.6.4/dist/es5/rrule.js',
'luxon': 'npm:luxon@3.4.4/build/global/luxon.min.js',
'es6-object-assign': 'npm:es6-object-assign',
'inferno': 'npm:inferno@8.2.3/dist/inferno.min.js',
'inferno-compat': 'npm:inferno-compat/dist/inferno-compat.min.js',
'inferno-create-element': 'npm:inferno-create-element@8.2.3/dist/inferno-create-element.min.js',
'inferno-dom': 'npm:inferno-dom/dist/inferno-dom.min.js',
'inferno-hydrate': 'npm:inferno-hydrate/dist/inferno-hydrate.min.js',
'inferno-clone-vnode': 'npm:inferno-clone-vnode/dist/inferno-clone-vnode.min.js',
'inferno-create-class': 'npm:inferno-create-class/dist/inferno-create-class.min.js',
'inferno-extras': 'npm:inferno-extras/dist/inferno-extras.min.js',
'@preact/signals-core': 'npm:@preact/signals-core@1.14.1/dist/signals-core.min.js',
// Prettier
'prettier/standalone': 'npm:prettier@2.8.8/standalone.js',
'prettier/parser-html': 'npm:prettier@2.8.8/parser-html.js',
'zone.js': 'npm:zone.js@0.15.1/bundles/zone.umd.js',
},
packages: {
'app': {
main: './app.component.ts',
defaultExtension: 'ts',
},
'devextreme': {
defaultExtension: 'js',
},
'devextreme/events/utils': {
main: 'index',
},
'devextreme/common/core/events/utils': {
main: 'index',
},
'devextreme/events': {
main: 'index',
},
'es6-object-assign': {
main: './index.js',
defaultExtension: 'js',
},
'rxjs': {
defaultExtension: 'js',
},
'rxjs/operators': {
defaultExtension: 'js',
},
},
packageConfigPaths: [
'npm:@devextreme/*/package.json',
'npm:rxjs@7.5.3/package.json',
'npm:rxjs@7.5.3/operators/package.json',
'npm:devexpress-diagram@2.2.29/package.json',
'npm:devexpress-gantt@4.1.69/package.json',
],
};
window.process = {
env: {
NODE_ENV: 'production',
},
};
System.config(window.config);
// eslint-disable-next-line no-console
// System.import('@angular/compiler').catch(console.error.bind(console));
// eslint-disable-next-line
const useTgzInCSB = ['openai'];
let packagesInfo = {
"openai": {
"version": "4.73.1"
},
"rehype-minify-whitespace": {
"version": "5.0.1"
},
"rehype-parse": {
"version": "8.0.5"
},
"rehype-remark": {
"version": "9.0.0"
},
"rehype-stringify": {
"version": "9.0.4"
},
"remark-parse": {
"version": "10.0.2"
},
"remark-rehype": {
"version": "10.1.0"
},
"remark-stringify": {
"version": "10.0.3"
},
"unified": {
"version": "10.1.2"
},
"zod": {
"version": "3.24.4"
},
"zod-to-json-schema": {
"version": "3.24.6"
},
"@angular/core": {
"version": "21.2.17"
},
"core-js": {
"version": "2.6.12"
},
"typescript": {
"version": "5.9.3"
},
"zone.js": {
"version": "0.15.1"
}
};
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" 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="https://cdn3.devexpress.com/jslib/26.1.3/css/dx.light.css" />
<script src="https://cdn.jsdelivr.net/npm/core-js@2.6.12/client/shim.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/zone.js@0.15.1/bundles/zone.umd.js"></script>
<script src="https://cdn.jsdelivr.net/npm/reflect-metadata@0.1.13/Reflect.js"></script>
<script src="https://cdn.jsdelivr.net/npm/systemjs@0.21.3/dist/system.js"></script>
<script src="config.js"></script>
<script>
System.import("app").catch(console.error.bind(console));
</script>
</head>
<body class="dx-viewport">
<div class="demo-container">
<demo-app>Loading...</demo-app>
</div>
</body>
</html>
import { Injectable } from '@angular/core';
import { AzureOpenAI, OpenAI } from 'openai';
import { type AIResponse } from 'devextreme-angular/common/ai-integration';
export type AIMessage = (
OpenAI.ChatCompletionUserMessageParam
| OpenAI.ChatCompletionSystemMessageParam
| OpenAI.ChatCompletionAssistantMessageParam) & {
content: string;
};
const AzureOpenAIConfig = {
dangerouslyAllowBrowser: true,
deployment: 'demo-mini',
apiVersion: '2024-02-01',
endpoint: 'https://public-api.devexpress.com/demo-openai',
apiKey: 'DEMO',
};
@Injectable()
export class AiService {
chatService: AzureOpenAI;
constructor() {
this.chatService = new AzureOpenAI(AzureOpenAIConfig);
}
async getAIResponse(messages: AIMessage[]): Promise<AIResponse> {
const params = {
messages,
model: AzureOpenAIConfig.deployment,
max_completion_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;
}
}
This demo allows you to configure suggestions as follows:
- Specify whether the Chat displays a predefined prompt in the input field or immediately sends a message with that prompt.
- Enable single-use suggestions.