webpart working

This commit is contained in:
“luismanez” 2023-07-25 18:24:40 +02:00
parent c5e92576da
commit 09c45abe08
9 changed files with 242 additions and 49 deletions

View File

@ -31,7 +31,8 @@ export default class OurHotelsFinderWebPart extends BaseClientSideWebPart<IOurHo
isDarkTheme: this._isDarkTheme,
environmentMessage: this._environmentMessage,
hasTeamsContext: !!this.context.sdks.microsoftTeams,
userDisplayName: this.context.pageContext.user.displayName
userDisplayName: this.context.pageContext.user.displayName,
httpClient: this.context.httpClient
}
);

View File

@ -1,7 +1,10 @@
import {HttpClient} from '@microsoft/sp-http';
export interface IOurHotelsFinderProps {
description: string;
isDarkTheme: boolean;
environmentMessage: string;
hasTeamsContext: boolean;
userDisplayName: string;
httpClient: HttpClient;
}

View File

@ -0,0 +1,6 @@
import { IChatMessage } from "../models/IChatMessage";
export interface IOurHotelsFinderState {
userQuery: string;
sessionMessages: IChatMessage[];
}

View File

@ -2,39 +2,60 @@ import * as React from "react";
//import styles from './OurHotelsFinder.module.scss';
import { IOurHotelsFinderProps } from "./IOurHotelsFinderProps";
import MessagesList from "./MessagesList";
import { IChatMessage } from "../models/IChatMessage";
import { DirectionalHint, Icon, Stack, Text, TooltipHost } from "@fluentui/react";
import UserMessage from "./UserMessage";
import { IOurHotelsFinderState } from "./IOurHotelsFinderState";
import CompletionsService from "../services/CompletionsService";
import { ICompletionsResponse } from "../models/ICompletionsResponse";
export default class OurHotelsFinder extends React.Component<IOurHotelsFinderProps, IOurHotelsFinderState> {
constructor(props: IOurHotelsFinderProps) {
super(props);
this.state = {
userQuery: '',
sessionMessages: []
};
}
private _onUserQueryChange = (newQuery: string): void => {
this.setState({
userQuery: newQuery
});
}
private _onQuerySent = async (): Promise<void> => {
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<IOurHotelsFinderProps> {
// 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 (
<Stack tokens={{ childrenGap: 20 }} style={{ minHeight: "100%" }}>
@ -69,10 +90,10 @@ export default class OurHotelsFinder extends React.Component<
root: { minHeight: "200px", height: "100%", position: "relative" },
}}
>
<MessagesList messages={fakeMessages} />
<MessagesList messages={this.state.sessionMessages} />
</Stack.Item>
<Stack.Item>
<UserMessage />
<UserMessage onMessageChange={this._onUserQueryChange} sendQuery={this._onQuerySent} />
</Stack.Item>
</Stack>
);

View File

@ -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<void>;
}
export interface IUserMessageState {
message: string;
}
export default class UserMessage extends React.Component<IUserMessageProps, IUserMessageState> {
constructor(props: IUserMessageProps) {
super(props);
this.state = {
message: ''
};
}
export default class UserMessage extends React.Component<IUserMessageProps, {}> {
private _onChange = (ev: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>, newText: string): void => {
this.setState({
message: newText
});
this.props.onMessageChange(newText);
}
private _handleClick = async (): Promise<void> => {
await this.props.sendQuery();
}
public render(): React.ReactElement<IUserMessageProps> {
return (
<Stack horizontal tokens={{ childrenGap: 20 }}>
<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" />
<IconButton iconProps={{ iconName: 'Send' }} title="Send" ariaLabel="Send" onClick={this._handleClick} />
</Stack.Item>
</Stack>
);

View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -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
}

View File

@ -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<ICompletionsResponse> {
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`;
}
}