Minor UX improvements + Link to community call recording
This commit is contained in:
parent
71b915f188
commit
dfcc527938
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
## Summary
|
## 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.
|
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)
|
![./assets/react-azure-openai-connector.gif](./assets/react-azure-openai-connector.gif)
|
||||||
|
|
|
@ -49,8 +49,6 @@ export default class OurHotelsFinderWebPart extends BaseClientSideWebPart<IOurHo
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private _getEnvironmentMessage(): Promise<string> {
|
private _getEnvironmentMessage(): Promise<string> {
|
||||||
if (!!this.context.sdks.microsoftTeams) { // running in Teams, office.com or Outlook
|
if (!!this.context.sdks.microsoftTeams) { // running in Teams, office.com or Outlook
|
||||||
return this.context.sdks.microsoftTeams.teamsJs.app.getContext()
|
return this.context.sdks.microsoftTeams.teamsJs.app.getContext()
|
||||||
|
|
|
@ -6,7 +6,7 @@ export interface IAssistantResponseProps {
|
||||||
message: string;
|
message: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class AssitantResponse extends React.Component<
|
export default class AssistantResponse extends React.Component<
|
||||||
IAssistantResponseProps,
|
IAssistantResponseProps,
|
||||||
{}
|
{}
|
||||||
> {
|
> {
|
||||||
|
|
|
@ -3,4 +3,5 @@ import { IChatMessage } from "../models/IChatMessage";
|
||||||
export interface IOurHotelsFinderState {
|
export interface IOurHotelsFinderState {
|
||||||
userQuery: string;
|
userQuery: string;
|
||||||
sessionMessages: IChatMessage[];
|
sessionMessages: IChatMessage[];
|
||||||
|
findingHotels: boolean;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import UserQuestion from "./UserQuestion";
|
import UserQuestion from "./UserQuestion";
|
||||||
import AssitantResponse from "./AssistantResponse";
|
import AssistantResponse from "./AssistantResponse";
|
||||||
import { IChatMessage } from "../models/IChatMessage";
|
import { IChatMessage } from "../models/IChatMessage";
|
||||||
|
|
||||||
import { ScrollablePane, ScrollbarVisibility } from '@fluentui/react';
|
import { ScrollablePane, ScrollbarVisibility } from '@fluentui/react';
|
||||||
|
@ -11,12 +11,22 @@ export interface IMessagesListProps {
|
||||||
|
|
||||||
export default class MessagesList extends React.Component<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> {
|
public render(): React.ReactElement<IMessagesListProps> {
|
||||||
const output = this.props.messages.map((m, i) => {
|
const output = this.props.messages.map((m, i) => {
|
||||||
if (m.role === 'user') {
|
if (m.role === 'user') {
|
||||||
return <UserQuestion key={i} message={m.text} />
|
return <UserQuestion key={i} message={m.text} />
|
||||||
}
|
}
|
||||||
return <AssitantResponse key={i} message={m.text} />
|
return <AssistantResponse key={i} message={m.text} />
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { IOurHotelsFinderProps } from "./IOurHotelsFinderProps";
|
import { IOurHotelsFinderProps } from "./IOurHotelsFinderProps";
|
||||||
import MessagesList from "./MessagesList";
|
import MessagesList from "./MessagesList";
|
||||||
import { Stack } from "@fluentui/react";
|
import { Spinner, SpinnerSize, Stack } from "@fluentui/react";
|
||||||
import UserMessage from "./UserMessage";
|
import UserMessage from "./UserMessage";
|
||||||
import { IOurHotelsFinderState } from "./IOurHotelsFinderState";
|
import { IOurHotelsFinderState } from "./IOurHotelsFinderState";
|
||||||
import CompletionsService from "../services/CompletionsService";
|
import CompletionsService from "../services/CompletionsService";
|
||||||
|
@ -14,7 +14,8 @@ export default class OurHotelsFinder extends React.Component<IOurHotelsFinderPro
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
userQuery: '',
|
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.userQuery);
|
||||||
console.log(this.state.sessionMessages);
|
console.log(this.state.sessionMessages);
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
findingHotels: true
|
||||||
|
});
|
||||||
|
|
||||||
const completionsService: CompletionsService = new CompletionsService(this.props.httpClient);
|
const completionsService: CompletionsService = new CompletionsService(this.props.httpClient);
|
||||||
|
|
||||||
const response: ICompletionsResponse =
|
const response: ICompletionsResponse =
|
||||||
|
@ -50,7 +55,9 @@ export default class OurHotelsFinder extends React.Component<IOurHotelsFinderPro
|
||||||
});
|
});
|
||||||
|
|
||||||
this.setState({
|
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} />
|
<MessagesList messages={this.state.sessionMessages} />
|
||||||
</Stack.Item>
|
</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>
|
<Stack.Item>
|
||||||
<UserMessage onMessageChange={this._onUserQueryChange} sendQuery={this._onQuerySent} />
|
<UserMessage
|
||||||
|
textFieldValue={this.state.userQuery}
|
||||||
|
onMessageChange={this._onUserQueryChange}
|
||||||
|
sendQuery={this._onQuerySent}
|
||||||
|
/>
|
||||||
</Stack.Item>
|
</Stack.Item>
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
|
|
|
@ -4,28 +4,57 @@ import * as React from 'react';
|
||||||
export interface IUserMessageProps {
|
export interface IUserMessageProps {
|
||||||
onMessageChange: (query: string) => void;
|
onMessageChange: (query: string) => void;
|
||||||
sendQuery: () => Promise<void>;
|
sendQuery: () => Promise<void>;
|
||||||
|
textFieldValue: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class UserMessage extends React.Component<IUserMessageProps, {}> {
|
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 => {
|
private _handleClick = async (): Promise<void> => {
|
||||||
this.props.onMessageChange(newText);
|
await this.props.sendQuery();
|
||||||
}
|
};
|
||||||
|
|
||||||
private _handleClick = async (): Promise<void> => {
|
private _keyDownHandler = async (e: KeyboardEvent): Promise<void> => {
|
||||||
await this.props.sendQuery();
|
if (e.ctrlKey && e.code === "Enter") {
|
||||||
|
await this._handleClick();
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
public render(): React.ReactElement<IUserMessageProps> {
|
public componentDidMount(): void {
|
||||||
return (
|
window.addEventListener("keydown", this._keyDownHandler);
|
||||||
<Stack horizontal tokens={{ childrenGap: 5 }}>
|
}
|
||||||
<Stack.Item grow={1}>
|
|
||||||
<TextField multiline autoAdjustHeight onChange={this._onChange} label="User message" placeholder="Type user query here." />
|
public componentWillUnmount(): void {
|
||||||
</Stack.Item>
|
window.removeEventListener("keydown", this._keyDownHandler);
|
||||||
<Stack.Item align="end">
|
}
|
||||||
<IconButton iconProps={{ iconName: 'Send' }} title="Send" ariaLabel="Send" onClick={this._handleClick} />
|
|
||||||
</Stack.Item>
|
public render(): React.ReactElement<IUserMessageProps> {
|
||||||
</Stack>
|
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>
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -13,17 +13,19 @@ export default class CompletionsRequestBuilder {
|
||||||
endpoint: Constants.AzureSearchEndpoint,
|
endpoint: Constants.AzureSearchEndpoint,
|
||||||
key: Constants.AzureSearchKey,
|
key: Constants.AzureSearchKey,
|
||||||
indexName: Constants.AzureSearchIndexName,
|
indexName: Constants.AzureSearchIndexName,
|
||||||
semanticConfiguration: "",
|
//semanticConfiguration: "hotels-index-semantic-config",
|
||||||
|
//queryType: "semantic",
|
||||||
queryType: "simple",
|
queryType: "simple",
|
||||||
fieldsMapping: {
|
fieldsMapping: {
|
||||||
contentFieldsSeparator: "\n",
|
contentFieldsSeparator: "\n",
|
||||||
contentFields: ["Description", "HotelName", "Category"],
|
contentFields: ["Description", "HotelName", "Description_fr", "Category"],
|
||||||
filepathField: "HotelName",
|
filepathField: "HotelId",
|
||||||
titleField: "HotelName",
|
titleField: "HotelName",
|
||||||
urlField: "HotelName"
|
urlField: undefined
|
||||||
},
|
},
|
||||||
inScope: true,
|
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."
|
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: [{
|
messages: [{
|
||||||
|
|
|
@ -16,7 +16,7 @@ export interface ICompletionsDataSourceParameters {
|
||||||
endpoint: string;
|
endpoint: string;
|
||||||
key: string;
|
key: string;
|
||||||
indexName: string;
|
indexName: string;
|
||||||
semanticConfiguration: string;
|
semanticConfiguration?: string;
|
||||||
queryType: string;
|
queryType: string;
|
||||||
fieldsMapping: ICompletionsDataSourceFieldsMapping;
|
fieldsMapping: ICompletionsDataSourceFieldsMapping;
|
||||||
inScope: boolean;
|
inScope: boolean;
|
||||||
|
@ -28,7 +28,7 @@ export interface ICompletionsDataSourceFieldsMapping {
|
||||||
contentFields: string[];
|
contentFields: string[];
|
||||||
filepathField: string;
|
filepathField: string;
|
||||||
titleField: string;
|
titleField: string;
|
||||||
urlField: string;
|
urlField: string | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ICompletionsMessage {
|
export interface ICompletionsMessage {
|
||||||
|
|
Loading…
Reference in New Issue