diff --git a/samples/react-azure-openai-connector/.nvmrc b/samples/react-azure-openai-connector/.nvmrc new file mode 100644 index 000000000..53a42214a --- /dev/null +++ b/samples/react-azure-openai-connector/.nvmrc @@ -0,0 +1 @@ +v16.13.2 diff --git a/samples/react-azure-openai-connector/README.md b/samples/react-azure-openai-connector/README.md index 250ed792c..a5c9c93ba 100644 --- a/samples/react-azure-openai-connector/README.md +++ b/samples/react-azure-openai-connector/README.md @@ -6,7 +6,7 @@ This (__experimental__) web part, shows how to use the new feature (_in preview ![./assets/react-azure-openai-connector.gif](./assets/react-azure-openai-connector.gif) -The web part is calling the Azure OpenAI API, with a specific Data source configured to an existing Azure Search service that contains the Hotels index provided by Microsoft. The following screenshots shows how you can configure the connector through the new Azure OpenAI Studio. This is **NOT required** for the sample, but helps to understand what the web part is doing behind the scenes. +The web part is calling the Azure OpenAI API, with a specific Data source configured to an existing Azure Search service that contains the Hotels index provided by Microsoft. The following screenshots shows how you can configure the connector through the new Azure OpenAI Studio. This is __NOT required__ for the sample, but helps to understand what the web part is doing behind the scenes. First, you have configured your Search service (any tier except the free one) importing the Hotels data sample provided by Microsoft @@ -42,39 +42,37 @@ __Note__: this is an experimental sample, based on the preview of the Azure Open ## Prerequisites - - Your Azure Subscription has the Azure OpenAI service available. You will see this message if you try to add Azure OpenAI service in your Subscription (follow the link in the Azure portal to request access). +- Your Azure Subscription has the Azure OpenAI service available. You will see this message if you try to add Azure OpenAI service in your Subscription (follow the link in the Azure portal to request access). > Azure OpenAI Service is currently available to customers via an application form. The selected subscription has not been enabled for use of the service and does not have quota for any pricing tiers. Click here to request access to Azure OpenAI service. - - You have created an Azure OpenAI service in your subscription (create it in a US region, as some features may not be available in other regions) - - Grab the Azure OpenAI Key (go to your _Azure OpenAI service -> Keys and Endpoint_) - - You have configured an Azure Search service with at least a Basic plan (Free tier is not working with the Azure OpenAI Connectors feature), and have deployed the "Hotels" index that is provided as sample by Microsoft. - - Grab the Azure Search endpoint URL (you can see it in the _Overview_ section) - - Grab the Azure Search API key (_Keys_ section) +- You have created an Azure OpenAI service in your subscription (create it in a US region, as some features may not be available in other regions) +- Grab the Azure OpenAI Key (go to your _Azure OpenAI service -> Keys and Endpoint_) +- You have configured an Azure Search service with at least a Basic plan (Free tier is not working with the Azure OpenAI Connectors feature), and have deployed the "Hotels" index that is provided as sample by Microsoft. +- Grab the Azure Search endpoint URL (you can see it in the _Overview_ section) +- Grab the Azure Search API key (_Keys_ section) ## Contributors -- [Luis Mañez](https://github.com/luismanez) - +- [Luis Mañez](https://github.com/luismanez) ## Version history | Version | Date | Comments | | ------- | ---------------- | --------------- | | 1.0 | July 26, 2023 | Initial release | - +| 1.1 | October 6, 2023 | Ctrl + enter to submit the query. Auto-scroll in the Messages list. | ## Minimal Path to Awesome - Install the MS Graph Toolkit for SPFx package. [Follow this](https://learn.microsoft.com/graph/toolkit/get-started/mgt-spfx) - Clone this repository - Ensure that you are at the solution folder -- Edit the file __Constants.ts__ with your values (see _prerequisites_ section) +- Edit the file `Constants.ts` with your values (see _prerequisites_ section) - in the command-line run: - `npm install` - `gulp serve` -- Add the web part in the SharePoint workbench or any SharePoint page (appending _?debug=true&noredir=true&debugManifestsFile=https://localhost:4321/temp/manifests.js_ to the page URL) - +- Add the web part in the SharePoint workbench or any SharePoint page (appending _?debug=true&noredir=true&debugManifestsFile=_ to the page URL) ## Features @@ -94,7 +92,9 @@ This extension illustrates the following concepts: - [Publish SharePoint Framework applications to the Marketplace](https://docs.microsoft.com/sharepoint/dev/spfx/publish-to-marketplace-overview) - [Microsoft 365 Patterns and Practices](https://aka.ms/m365pnp) - Guidance, tooling, samples and open-source controls for your Microsoft 365 development -## Help +## Video + +[![Using Azure Open AI custom data sources with SPFx](./assets/video-thumbnail.png)](https://youtu.be/VVjNkwh2W3U?si=tYSpsBdcn5abiC-m&t=1750 "Using Azure Open AI custom data sources with SPFx") We do not support samples, but this community is always willing to help, and we want to improve these samples. We use GitHub to track issues, which makes it easy for community members to volunteer their time and help resolve issues. diff --git a/samples/react-azure-openai-connector/assets/sample.json b/samples/react-azure-openai-connector/assets/sample.json index 46485d9d0..1a472c646 100644 --- a/samples/react-azure-openai-connector/assets/sample.json +++ b/samples/react-azure-openai-connector/assets/sample.json @@ -10,7 +10,7 @@ "Shows how to use the new feature of Azure OpenAI Connectors API in a SharePoint Framework web part." ], "creationDateTime": "2023-07-26", - "updateDateTime": "2023-07-26", + "updateDateTime": "2023-10-06", "products": [ "SharePoint" ], @@ -30,6 +30,12 @@ "order": 100, "url": "https://github.com/pnp/sp-dev-fx-webparts/raw/main/samples/react-azure-openai-connector/assets/react-azure-openai-connector.gif", "alt": "Web Part Preview" + }, + { + "type": "video", + "order": 101, + "url": "https://youtu.be/VVjNkwh2W3U?si=tYSpsBdcn5abiC-m&t=1750", + "alt": "Community demo of the web part" } ], "authors": [ diff --git a/samples/react-azure-openai-connector/assets/video-thumbnail.png b/samples/react-azure-openai-connector/assets/video-thumbnail.png new file mode 100644 index 000000000..d939e30b8 Binary files /dev/null and b/samples/react-azure-openai-connector/assets/video-thumbnail.png differ diff --git a/samples/react-azure-openai-connector/src/webparts/ourHotelsFinder/OurHotelsFinderWebPart.ts b/samples/react-azure-openai-connector/src/webparts/ourHotelsFinder/OurHotelsFinderWebPart.ts index acf589447..66c786571 100644 --- a/samples/react-azure-openai-connector/src/webparts/ourHotelsFinder/OurHotelsFinderWebPart.ts +++ b/samples/react-azure-openai-connector/src/webparts/ourHotelsFinder/OurHotelsFinderWebPart.ts @@ -49,8 +49,6 @@ export default class OurHotelsFinderWebPart extends BaseClientSideWebPart { if (!!this.context.sdks.microsoftTeams) { // running in Teams, office.com or Outlook return this.context.sdks.microsoftTeams.teamsJs.app.getContext() diff --git a/samples/react-azure-openai-connector/src/webparts/ourHotelsFinder/components/AssistantResponse.tsx b/samples/react-azure-openai-connector/src/webparts/ourHotelsFinder/components/AssistantResponse.tsx index 10e0bcbf3..19a74d74d 100644 --- a/samples/react-azure-openai-connector/src/webparts/ourHotelsFinder/components/AssistantResponse.tsx +++ b/samples/react-azure-openai-connector/src/webparts/ourHotelsFinder/components/AssistantResponse.tsx @@ -6,7 +6,7 @@ export interface IAssistantResponseProps { message: string; } -export default class AssitantResponse extends React.Component< +export default class AssistantResponse extends React.Component< IAssistantResponseProps, {} > { diff --git a/samples/react-azure-openai-connector/src/webparts/ourHotelsFinder/components/IOurHotelsFinderState.ts b/samples/react-azure-openai-connector/src/webparts/ourHotelsFinder/components/IOurHotelsFinderState.ts index cdb663679..40fcd37ed 100644 --- a/samples/react-azure-openai-connector/src/webparts/ourHotelsFinder/components/IOurHotelsFinderState.ts +++ b/samples/react-azure-openai-connector/src/webparts/ourHotelsFinder/components/IOurHotelsFinderState.ts @@ -3,4 +3,5 @@ import { IChatMessage } from "../models/IChatMessage"; export interface IOurHotelsFinderState { userQuery: string; sessionMessages: IChatMessage[]; + findingHotels: boolean; } diff --git a/samples/react-azure-openai-connector/src/webparts/ourHotelsFinder/components/MessagesList.tsx b/samples/react-azure-openai-connector/src/webparts/ourHotelsFinder/components/MessagesList.tsx index 07658b036..791f1c4f1 100644 --- a/samples/react-azure-openai-connector/src/webparts/ourHotelsFinder/components/MessagesList.tsx +++ b/samples/react-azure-openai-connector/src/webparts/ourHotelsFinder/components/MessagesList.tsx @@ -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 { + 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 { const output = this.props.messages.map((m, i) => { if (m.role === 'user') { return } - return + return }); return ( diff --git a/samples/react-azure-openai-connector/src/webparts/ourHotelsFinder/components/OurHotelsFinder.tsx b/samples/react-azure-openai-connector/src/webparts/ourHotelsFinder/components/OurHotelsFinder.tsx index 28cdfbad9..b2e9adb55 100644 --- a/samples/react-azure-openai-connector/src/webparts/ourHotelsFinder/components/OurHotelsFinder.tsx +++ b/samples/react-azure-openai-connector/src/webparts/ourHotelsFinder/components/OurHotelsFinder.tsx @@ -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 + {this.state.findingHotels && ( + + + + )} - + ); diff --git a/samples/react-azure-openai-connector/src/webparts/ourHotelsFinder/components/UserMessage.tsx b/samples/react-azure-openai-connector/src/webparts/ourHotelsFinder/components/UserMessage.tsx index c4782123d..504139e94 100644 --- a/samples/react-azure-openai-connector/src/webparts/ourHotelsFinder/components/UserMessage.tsx +++ b/samples/react-azure-openai-connector/src/webparts/ourHotelsFinder/components/UserMessage.tsx @@ -4,28 +4,57 @@ import * as React from 'react'; export interface IUserMessageProps { onMessageChange: (query: string) => void; sendQuery: () => Promise; + textFieldValue: string; } export default class UserMessage extends React.Component { + private _onChange = ( + ev: React.FormEvent, + newText: string + ): void => { + this.props.onMessageChange(newText); + }; - private _onChange = (ev: React.FormEvent, newText: string): void => { - this.props.onMessageChange(newText); - } + private _handleClick = async (): Promise => { + await this.props.sendQuery(); + }; - private _handleClick = async (): Promise => { - await this.props.sendQuery(); + private _keyDownHandler = async (e: KeyboardEvent): Promise => { + if (e.ctrlKey && e.code === "Enter") { + await this._handleClick(); } + }; - public render(): React.ReactElement { - return ( - - - - - - - - - ); - } + public componentDidMount(): void { + window.addEventListener("keydown", this._keyDownHandler); + } + + public componentWillUnmount(): void { + window.removeEventListener("keydown", this._keyDownHandler); + } + + public render(): React.ReactElement { + return ( + + + + + + + + + ); + } } \ No newline at end of file diff --git a/samples/react-azure-openai-connector/src/webparts/ourHotelsFinder/models/CompletionsRequestBuilder.ts b/samples/react-azure-openai-connector/src/webparts/ourHotelsFinder/models/CompletionsRequestBuilder.ts index 4b8f39679..d552a08b0 100644 --- a/samples/react-azure-openai-connector/src/webparts/ourHotelsFinder/models/CompletionsRequestBuilder.ts +++ b/samples/react-azure-openai-connector/src/webparts/ourHotelsFinder/models/CompletionsRequestBuilder.ts @@ -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: [{ diff --git a/samples/react-azure-openai-connector/src/webparts/ourHotelsFinder/models/ICompletionsRequest.ts b/samples/react-azure-openai-connector/src/webparts/ourHotelsFinder/models/ICompletionsRequest.ts index 3a5d52502..580b2d345 100644 --- a/samples/react-azure-openai-connector/src/webparts/ourHotelsFinder/models/ICompletionsRequest.ts +++ b/samples/react-azure-openai-connector/src/webparts/ourHotelsFinder/models/ICompletionsRequest.ts @@ -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 {