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 6437ec28e..acf589447 100644 --- a/samples/react-azure-openai-connector/src/webparts/ourHotelsFinder/OurHotelsFinderWebPart.ts +++ b/samples/react-azure-openai-connector/src/webparts/ourHotelsFinder/OurHotelsFinderWebPart.ts @@ -31,7 +31,8 @@ export default class OurHotelsFinderWebPart extends BaseClientSideWebPart { + + constructor(props: IOurHotelsFinderProps) { + super(props); + + this.state = { + userQuery: '', + sessionMessages: [] + }; + } + + private _onUserQueryChange = (newQuery: string): void => { + this.setState({ + userQuery: newQuery + }); + } + + private _onQuerySent = async (): Promise => { + console.log(this.state.userQuery); + console.log(this.state.sessionMessages); + + const completionsService: CompletionsService = new CompletionsService(this.props.httpClient); + + const response: ICompletionsResponse = + await completionsService.getCompletions(this.state.sessionMessages, this.state.userQuery); + + console.log(response); + + const responseMessages = response.choices[0].messages.filter(m => { + return m.role === 'assistant'; + }); + + const message = responseMessages[0]; + + const tempMessages = this.state.sessionMessages; + tempMessages.push({ + role: 'user', text: this.state.userQuery + }); + tempMessages.push({ + role: 'assistant', text: message.content + }); + + this.setState({ + sessionMessages: tempMessages + }); + } -export default class OurHotelsFinder extends React.Component< - IOurHotelsFinderProps, - {} -> { public render(): React.ReactElement { - // const { - // hasTeamsContext, - // } = this.props; - - const fakeMessages: IChatMessage[] = [ - { - role: "user", - text: "Do you know if using Fluent UI components, you can implement like the usual Chat interface? like the one uses ChatGPT website, or WhatsApp?", - }, - { - role: "assistant", - text: "Glad you asked. This is a common question that I have no idea how to do... bye!", - }, - { - role: "user", - text: "Icon looks a bit small, can I make it a bit bigger?", - }, - { - role: "assistant", - text: "In this example, I have increased the size of the icon to 24 pixels. You can adjust the fontSize value to increase or decrease the size of the icon as needed.", - }, - { role: "user", text: "Another question" }, - { role: "assistant", text: "In this example alksfh ks" }, - ]; return ( @@ -69,10 +90,10 @@ export default class OurHotelsFinder extends React.Component< root: { minHeight: "200px", height: "100%", position: "relative" }, }} > - + - + ); 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 98be0f246..c4782123d 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 @@ -2,35 +2,28 @@ import { IconButton, Stack, TextField } from '@fluentui/react'; import * as React from 'react'; export interface IUserMessageProps { + onMessageChange: (query: string) => void; + sendQuery: () => Promise; } -export interface IUserMessageState { - message: string; -} - -export default class UserMessage extends React.Component { - - constructor(props: IUserMessageProps) { - super(props); - this.state = { - message: '' - }; - } +export default class UserMessage extends React.Component { private _onChange = (ev: React.FormEvent, newText: string): void => { - this.setState({ - message: newText - }); + this.props.onMessageChange(newText); + } + + private _handleClick = async (): Promise => { + await this.props.sendQuery(); } public render(): React.ReactElement { return ( - + - + ); 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 new file mode 100644 index 000000000..7b0acc619 --- /dev/null +++ b/samples/react-azure-openai-connector/src/webparts/ourHotelsFinder/models/CompletionsRequestBuilder.ts @@ -0,0 +1,61 @@ +import { ICompletionsRequest } from "./ICompletionsRequest"; +import Constants from '../Constants'; + +export default class CompletionsRequestBuilder { + + private _completionsRequest: ICompletionsRequest + + public constructor(deployment: "gpt-35-turbo-model-deployment" | "gpt-4") { + this._completionsRequest = { + dataSources: [{ + type: "AzureCognitiveSearch", + parameters: { + endpoint: "https://srch-atlas-cp-dev.search.windows.net", + key: Constants.AzureSearchKey, + indexName: "hotels-openai-test-index-lml", + semanticConfiguration: "", + queryType: "simple", + fieldsMapping: { + contentFieldsSeparator: "\n", + contentFields: ["Description", "HotelName", "Category"], + filepathField: "HotelName", + titleField: "HotelName", + urlField: "HotelName" + }, + 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." + } + }], + messages: [{ + role: "system", + content: "You are an AI assistant that helps users of a travel agency to find Hotels in our internal company database for our customers."} + ], + deployment: deployment, + temperature: 0, + top_p: 1, + max_tokens: 800 + } + } + + public addUserMessage(content: string): void { + this._completionsRequest.messages.push({ + role: "user", + content: content + }); + } + + public addAssistantMessage(content: string): void { + this._completionsRequest.messages.push({ + role: "assistant", + content: content + }); + } + + public build(): ICompletionsRequest { + return this._completionsRequest; + } + + public buildAsJson(): string { + return JSON.stringify(this._completionsRequest); + } +} \ No newline at end of file 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 new file mode 100644 index 000000000..3a5d52502 --- /dev/null +++ b/samples/react-azure-openai-connector/src/webparts/ourHotelsFinder/models/ICompletionsRequest.ts @@ -0,0 +1,37 @@ +export interface ICompletionsRequest { + dataSources: ICompletionsDataSource[]; + messages: ICompletionsMessage[]; + deployment: "gpt-35-turbo-model-deployment" | "gpt-4"; + temperature: number; + top_p: number; + max_tokens: number; +} + +export interface ICompletionsDataSource { + type: "AzureCognitiveSearch"; + parameters: ICompletionsDataSourceParameters; +} + +export interface ICompletionsDataSourceParameters { + endpoint: string; + key: string; + indexName: string; + semanticConfiguration: string; + queryType: string; + fieldsMapping: ICompletionsDataSourceFieldsMapping; + inScope: boolean; + roleInformation: string; +} + +export interface ICompletionsDataSourceFieldsMapping { + contentFieldsSeparator: string; + contentFields: string[]; + filepathField: string; + titleField: string; + urlField: string; +} + +export interface ICompletionsMessage { + role: "user" | "assistant" | "system"; + content: string; +} diff --git a/samples/react-azure-openai-connector/src/webparts/ourHotelsFinder/models/ICompletionsResponse.ts b/samples/react-azure-openai-connector/src/webparts/ourHotelsFinder/models/ICompletionsResponse.ts new file mode 100644 index 000000000..95b52d115 --- /dev/null +++ b/samples/react-azure-openai-connector/src/webparts/ourHotelsFinder/models/ICompletionsResponse.ts @@ -0,0 +1,19 @@ +export interface ICompletionsResponse { + id: string + model: string + created: number + object: string + choices: Choice[] + } + + export interface Choice { + index: number + messages: Message[] + } + + export interface Message { + index: number + role: "assistant" | "tool" + content: string + end_turn: boolean + } diff --git a/samples/react-azure-openai-connector/src/webparts/ourHotelsFinder/services/CompletionsService.ts b/samples/react-azure-openai-connector/src/webparts/ourHotelsFinder/services/CompletionsService.ts new file mode 100644 index 000000000..923ca925f --- /dev/null +++ b/samples/react-azure-openai-connector/src/webparts/ourHotelsFinder/services/CompletionsService.ts @@ -0,0 +1,52 @@ +import { HttpClient, IHttpClientOptions, HttpClientResponse } from '@microsoft/sp-http'; +import { ICompletionsResponse } from '../models/ICompletionsResponse'; +import { IChatMessage } from '../models/IChatMessage'; +import CompletionsRequestBuilder from '../models/CompletionsRequestBuilder'; +import Constants from '../Constants'; + +export default class CompletionsService { + private readonly _httpClient: HttpClient; + + constructor(httpClient: HttpClient) { + this._httpClient = httpClient; + } + + public async getCompletions( + sessionMessageHistory: IChatMessage[], + query: string, + deployment: "gpt-35-turbo-model-deployment" | "gpt-4" = "gpt-4") : Promise { + const requestBuilder: CompletionsRequestBuilder = new CompletionsRequestBuilder(deployment); + + sessionMessageHistory.map(m => { + if (m.role === 'assistant') { + requestBuilder.addAssistantMessage(m.text); + } else { + requestBuilder.addUserMessage(m.text); + } + }); + requestBuilder.addUserMessage(query); + + const requestHeaders: Headers = new Headers(); + requestHeaders.append('Content-type', 'application/json'); + requestHeaders.append('Api-Key', `${Constants.AzureOpenAiApiKey}`); + + const httpClientOptions: IHttpClientOptions = { + body: requestBuilder.buildAsJson(), + headers: requestHeaders + }; + + const response: HttpClientResponse = + await this._httpClient.post( + this._compose_AzureOpenAiApiUrl(deployment), + HttpClient.configurations.v1, + httpClientOptions); + + const completionsResponse: ICompletionsResponse = await response.json(); + + return completionsResponse; + } + + private _compose_AzureOpenAiApiUrl(deployment: "gpt-35-turbo-model-deployment" | "gpt-4"): string { + return `https://oai-atlas-dev-eus.openai.azure.com/openai/deployments/${deployment}/extensions/chat/completions?api-version=2023-06-01-preview`; + } +} \ No newline at end of file