Minor UX improvements + Link to community call recording

This commit is contained in:
“luismanez” 2023-10-06 11:42:00 +02:00
parent 71b915f188
commit dfcc527938
9 changed files with 90 additions and 32 deletions

View File

@ -2,6 +2,8 @@
## Summary
__UPDATE__: I demo-ed this sample in the Community call. Recording available here: [https://t.co/Ew4XSpU6Yh](https://t.co/Ew4XSpU6Yh)
This (__experimental__) webpart, shows how to use the new feature (_in preview at the moment of building this sample_) Azure OpenAI Data Connectors.
![./assets/react-azure-openai-connector.gif](./assets/react-azure-openai-connector.gif)

View File

@ -49,8 +49,6 @@ export default class OurHotelsFinderWebPart extends BaseClientSideWebPart<IOurHo
});
}
private _getEnvironmentMessage(): Promise<string> {
if (!!this.context.sdks.microsoftTeams) { // running in Teams, office.com or Outlook
return this.context.sdks.microsoftTeams.teamsJs.app.getContext()

View File

@ -6,7 +6,7 @@ export interface IAssistantResponseProps {
message: string;
}
export default class AssitantResponse extends React.Component<
export default class AssistantResponse extends React.Component<
IAssistantResponseProps,
{}
> {

View File

@ -3,4 +3,5 @@ import { IChatMessage } from "../models/IChatMessage";
export interface IOurHotelsFinderState {
userQuery: string;
sessionMessages: IChatMessage[];
findingHotels: boolean;
}

View File

@ -1,6 +1,6 @@
import * as React from "react";
import UserQuestion from "./UserQuestion";
import AssitantResponse from "./AssistantResponse";
import AssistantResponse from "./AssistantResponse";
import { IChatMessage } from "../models/IChatMessage";
import { ScrollablePane, ScrollbarVisibility } from '@fluentui/react';
@ -11,12 +11,22 @@ export interface IMessagesListProps {
export default class MessagesList extends React.Component<IMessagesListProps, {}> {
public componentDidUpdate(): void {
const scrollContainers = document.querySelectorAll(".ms-ScrollablePane--contentContainer");
const lastScrollContainer = scrollContainers[scrollContainers.length - 1] as HTMLElement;
if (lastScrollContainer) {
lastScrollContainer.scrollTop = lastScrollContainer.scrollHeight;
}
}
public render(): React.ReactElement<IMessagesListProps> {
const output = this.props.messages.map((m, i) => {
if (m.role === 'user') {
return <UserQuestion key={i} message={m.text} />
}
return <AssitantResponse key={i} message={m.text} />
return <AssistantResponse key={i} message={m.text} />
});
return (

View File

@ -1,7 +1,7 @@
import * as React from "react";
import { IOurHotelsFinderProps } from "./IOurHotelsFinderProps";
import MessagesList from "./MessagesList";
import { Stack } from "@fluentui/react";
import { Spinner, SpinnerSize, Stack } from "@fluentui/react";
import UserMessage from "./UserMessage";
import { IOurHotelsFinderState } from "./IOurHotelsFinderState";
import CompletionsService from "../services/CompletionsService";
@ -14,7 +14,8 @@ export default class OurHotelsFinder extends React.Component<IOurHotelsFinderPro
this.state = {
userQuery: '',
sessionMessages: []
sessionMessages: [],
findingHotels: false
};
}
@ -28,6 +29,10 @@ export default class OurHotelsFinder extends React.Component<IOurHotelsFinderPro
console.log(this.state.userQuery);
console.log(this.state.sessionMessages);
this.setState({
findingHotels: true
});
const completionsService: CompletionsService = new CompletionsService(this.props.httpClient);
const response: ICompletionsResponse =
@ -50,7 +55,9 @@ export default class OurHotelsFinder extends React.Component<IOurHotelsFinderPro
});
this.setState({
sessionMessages: tempMessages
sessionMessages: tempMessages,
userQuery: '',
findingHotels: false
});
}
@ -66,8 +73,17 @@ export default class OurHotelsFinder extends React.Component<IOurHotelsFinderPro
>
<MessagesList messages={this.state.sessionMessages} />
</Stack.Item>
{this.state.findingHotels && (
<Stack.Item>
<Spinner size={SpinnerSize.large} label="Wait till our super cool AI system is finding you the best hotels..." ariaLive="assertive" labelPosition="right" />
</Stack.Item>
)}
<Stack.Item>
<UserMessage onMessageChange={this._onUserQueryChange} sendQuery={this._onQuerySent} />
<UserMessage
textFieldValue={this.state.userQuery}
onMessageChange={this._onUserQueryChange}
sendQuery={this._onQuerySent}
/>
</Stack.Item>
</Stack>
);

View File

@ -4,28 +4,57 @@ import * as React from 'react';
export interface IUserMessageProps {
onMessageChange: (query: string) => void;
sendQuery: () => Promise<void>;
textFieldValue: string;
}
export default class UserMessage extends React.Component<IUserMessageProps, {}> {
private _onChange = (
ev: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>,
newText: string
): void => {
this.props.onMessageChange(newText);
};
private _onChange = (ev: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>, newText: string): void => {
this.props.onMessageChange(newText);
}
private _handleClick = async (): Promise<void> => {
await this.props.sendQuery();
};
private _handleClick = async (): Promise<void> => {
await this.props.sendQuery();
private _keyDownHandler = async (e: KeyboardEvent): Promise<void> => {
if (e.ctrlKey && e.code === "Enter") {
await this._handleClick();
}
};
public render(): React.ReactElement<IUserMessageProps> {
return (
<Stack horizontal tokens={{ childrenGap: 5 }}>
<Stack.Item grow={1}>
<TextField multiline autoAdjustHeight onChange={this._onChange} label="User message" placeholder="Type user query here." />
</Stack.Item>
<Stack.Item align="end">
<IconButton iconProps={{ iconName: 'Send' }} title="Send" ariaLabel="Send" onClick={this._handleClick} />
</Stack.Item>
</Stack>
);
}
public componentDidMount(): void {
window.addEventListener("keydown", this._keyDownHandler);
}
public componentWillUnmount(): void {
window.removeEventListener("keydown", this._keyDownHandler);
}
public render(): React.ReactElement<IUserMessageProps> {
return (
<Stack horizontal tokens={{ childrenGap: 5 }}>
<Stack.Item grow={1}>
<TextField
multiline
autoAdjustHeight
value={this.props.textFieldValue}
onChange={this._onChange}
label="User message"
placeholder="Type user query here."
/>
</Stack.Item>
<Stack.Item align="end">
<IconButton
iconProps={{ iconName: "Send" }}
title="Send"
ariaLabel="Send"
onClick={this._handleClick}
/>
</Stack.Item>
</Stack>
);
}
}

View File

@ -13,17 +13,19 @@ export default class CompletionsRequestBuilder {
endpoint: Constants.AzureSearchEndpoint,
key: Constants.AzureSearchKey,
indexName: Constants.AzureSearchIndexName,
semanticConfiguration: "",
//semanticConfiguration: "hotels-index-semantic-config",
//queryType: "semantic",
queryType: "simple",
fieldsMapping: {
contentFieldsSeparator: "\n",
contentFields: ["Description", "HotelName", "Category"],
filepathField: "HotelName",
contentFields: ["Description", "HotelName", "Description_fr", "Category"],
filepathField: "HotelId",
titleField: "HotelName",
urlField: "HotelName"
urlField: undefined
},
inScope: true,
roleInformation: "You are an AI assistant that helps users of a travel agency to find Hotels in our internal company database for our customers."
// "filter": "Tags/any(g:search.in(g, 'pool, view'))",
}
}],
messages: [{

View File

@ -16,7 +16,7 @@ export interface ICompletionsDataSourceParameters {
endpoint: string;
key: string;
indexName: string;
semanticConfiguration: string;
semanticConfiguration?: string;
queryType: string;
fieldsMapping: ICompletionsDataSourceFieldsMapping;
inScope: boolean;
@ -28,7 +28,7 @@ export interface ICompletionsDataSourceFieldsMapping {
contentFields: string[];
filepathField: string;
titleField: string;
urlField: string;
urlField: string | undefined;
}
export interface ICompletionsMessage {