Minor UX improvements + Link to community call recording
This commit is contained in:
parent
71b915f188
commit
dfcc527938
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -6,7 +6,7 @@ export interface IAssistantResponseProps {
|
|||
message: string;
|
||||
}
|
||||
|
||||
export default class AssitantResponse extends React.Component<
|
||||
export default class AssistantResponse extends React.Component<
|
||||
IAssistantResponseProps,
|
||||
{}
|
||||
> {
|
||||
|
|
|
@ -3,4 +3,5 @@ import { IChatMessage } from "../models/IChatMessage";
|
|||
export interface IOurHotelsFinderState {
|
||||
userQuery: string;
|
||||
sessionMessages: IChatMessage[];
|
||||
findingHotels: boolean;
|
||||
}
|
||||
|
|
|
@ -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 (
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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: [{
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in New Issue