Skip to content

Commit

Permalink
Merge pull request #1 from Ramakrishnan24689/beta
Browse files Browse the repository at this point in the history
Beta
  • Loading branch information
Ramakrishnan24689 authored Aug 12, 2023
2 parents 8e5068f + 569bdce commit fe2add7
Show file tree
Hide file tree
Showing 15 changed files with 2,360 additions and 1,440 deletions.
16 changes: 13 additions & 3 deletions PowerChatbot/.eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
},
"extends": [
"eslint:recommended",
"plugin:react/recommended"
"plugin:react/recommended",
"plugin:@typescript-eslint/recommended"
],
"globals": {
"ComponentFramework": true
Expand All @@ -17,10 +18,19 @@
},
"plugins": [
"@microsoft/power-apps",
"@typescript-eslint"
"@typescript-eslint",
"react-hooks",
"prettier"
],
"rules": {
"no-unused-vars": "off"
"eqeqeq": [2, "smart"],
"arrow-body-style": "off",
"react-hooks/exhaustive-deps": "warn",
"no-unused-vars": "off",
"semi": [
"error",
"always"
]
},
"settings": {
"react": {
Expand Down
4 changes: 3 additions & 1 deletion PowerChatbot/PowerChatbot/ControlManifest.Input.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8" ?>
<manifest>
<control namespace="CustomControl" constructor="PowerChatbot" version="1.0.0" display-name-key="PowerChatbotPCF_key" description-key="PowerChatbotPCF_desc" control-type="virtual">
<control namespace="CustomControl" constructor="PowerChatbot" version="1.0.1" display-name-key="PowerChatbotPCF_key" description-key="PowerChatbotPCF_desc" control-type="virtual">
<property name="SubmittedText" display-name-key="PowerChatbot_SubmittedText_Key" description-key="PowerChatbot_SubmittedText_Desc_Key" of-type="SingleLine.Text" usage="output" required="true" />
<property name="BotName" display-name-key="PowerChatbot_BotName_Key" description-key="PowerChatbot_BotName_Desc_Key" of-type="SingleLine.Text" usage="input" default-value="Assistance" required="false" />
<property name="LoadingText" display-name-key="PowerChatbot_LoadingText_Key" description-key="PowerChatbot_LoadingText_Desc_Key" of-type="SingleLine.Text" usage="input" default-value="Working on it..." required="false" />
Expand All @@ -9,12 +9,14 @@
<data-set name="Items" display-name-key="PowerChatbot_Items_Display_Key" description-key="PowerChatbot_Items_Desc_Key" allow-default-selected-items="true" resettable="true"/>
<property name="Useplatformtheme" display-name-key="PowerChatbot_Useplatformtheme_Display_Key" description-key="PowerChatbot_Useplatformtheme_Desc_Key" of-type="TwoOptions" default-value="false" usage="input" required="false"/>
<property name="ShowIcon" display-name-key="PowerChatbot_ShowIcon_Display_Key" description-key="PowerChatbot_ShowIcon_Desc_Key" of-type="TwoOptions" default-value="false" usage="input" required="false"/>
<property name="DarkMode" display-name-key="PowerChatbot_DarkMode_Display_Key" description-key="PowerChatbot_DarkMode_Desc_Key" of-type="TwoOptions" default-value="false" usage="input" required="false"/>
<common-property name="Height" default-value="675" />
<common-property name="Width" default-value="320" />
<event name="OnSubmit"/>
<resources>
<code path="index.ts" order="1"/>
<platform-library name="React" version="16.8.6" />
<platform-library name="Fluent" version="9.4.0"/>
<resx path="strings/PowerChatbot.1033.resx" version="1.0.0"/>
</resources>
</control>
Expand Down
14 changes: 7 additions & 7 deletions PowerChatbot/PowerChatbot/ManifestConstants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@ export const enum ManifestPropertyNames {
}

export const AssistanceIcon = {
width : '24px',
height : '24px',
width: '24px',
height: '24px',
color: "#19c37d",
}
};

export const WelcomeIconInfo = {
width : '80px',
height : '80px',
padding : 100,
width: '80px',
height: '80px',
padding: 100,
color: "#19c37d",
}
};

36 changes: 19 additions & 17 deletions PowerChatbot/PowerChatbot/PowerChatbot.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { IInputs, IOutputs } from "./generated/ManifestTypes";
import { ChatComponent } from "./components/Chat";
import * as React from "react";
import { ChatComponent } from "./components/Chat";
import { ManifestPropertyNames } from "./ManifestConstants";
import { Column, RecordSet } from "./components/Component.types";
import { IChatInputProps, IChatMessage } from "./interface/IChatProps";
Expand Down Expand Up @@ -47,33 +47,34 @@ export class PowerChatbot implements ComponentFramework.ReactControl<IInputs, IO
const loadingText = context.parameters.LoadingText.raw ?? '';
const placeholdertext = context.parameters.PlaceholderText.raw ?? '';
const usePlatformtheme = context.parameters.Useplatformtheme.raw;
const darkMode = context.parameters.DarkMode.raw;
const allocatedWidth = parseInt(context.mode.allocatedWidth as unknown as string);
const allocatedHeight = parseInt(context.mode.allocatedHeight as unknown as string);
if (datasetChanged) {
this.items = this.createOptionItems(columns, records, sortedRecordIds);
}
this.eventtobeTriggered && this.triggerOnSubmit();
const chatComponentProps = {
items:this.items,
botName: botName,
showIcon: showIcon,
placeholdertext:placeholdertext,
allocatedWidth: allocatedWidth,
allocatedHeight: allocatedHeight,
loadingText: loadingText,
accessibleLabel: accessibleLabel,
onSubmit:this._onSubmit,
disabledState: this.disableChatComponent(records, sortedRecordIds),
usePlatformtheme: usePlatformtheme
} as IChatInputProps;
items: this.items,
botName: botName,
darkMode: darkMode,
showIcon: showIcon,
placeholdertext: placeholdertext,
allocatedWidth: allocatedWidth,
allocatedHeight: allocatedHeight,
loadingText: loadingText,
accessibleLabel: accessibleLabel,
onSubmit: this._onSubmit,
disabledState: this.disableChatComponent(records, sortedRecordIds),
usePlatformtheme: usePlatformtheme
} as IChatInputProps;
return React.createElement(
ChatComponent, chatComponentProps
);
}

private triggerOnSubmit() {
this.eventtobeTriggered = false;
console.log("OnSubmit");
this.submittedText = "";
this.context.events.OnSubmit();
}
Expand All @@ -84,10 +85,11 @@ export class PowerChatbot implements ComponentFramework.ReactControl<IInputs, IO
this.notifyOutputChanged();
}

private disableChatComponent(records: RecordSet, sortedRecordIds: string[]): Boolean {
private disableChatComponent(records: RecordSet, sortedRecordIds: string[]): boolean {
if (sortedRecordIds.length > 0) {
// false - if last record is from Open AI - 'assistance'
return records[sortedRecordIds[sortedRecordIds.length-1]].getFormattedValue('role') === 'user'
// this acts as promise for the component
return records[sortedRecordIds[sortedRecordIds.length - 1]].getFormattedValue('role') === 'user';
}
return false;
}
Expand All @@ -101,7 +103,7 @@ export class PowerChatbot implements ComponentFramework.ReactControl<IInputs, IO
let rolecolumnAlias = columns[1] && columns[1].alias;
// if no column is specified and the first record has a value column, use that.
if (!contentcolumnAlias && records[sortedRecordIds[0]].getFormattedValue('content') !== null &&
!contentcolumnAlias && records[sortedRecordIds[0]].getFormattedValue('role') !== null) {
!contentcolumnAlias && records[sortedRecordIds[0]].getFormattedValue('role') !== null) {
contentcolumnAlias = 'content';
rolecolumnAlias = 'role';
}
Expand Down
149 changes: 74 additions & 75 deletions PowerChatbot/PowerChatbot/components/Chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,11 @@ import * as React from "react";
import {
Button,
Textarea,
BrandVariants,
createLightTheme,
createDarkTheme,
Theme,
TextareaProps,
tokens,
Avatar
Avatar,
makeStyles,
shorthands
} from "@fluentui/react-components";
import { SendRegular, Bot24Regular } from "@fluentui/react-icons";
import { Chat, ChatMessage, ChatMyMessage } from '@fluentui-contrib/react-chat';
Expand All @@ -17,106 +15,107 @@ import { IChatInputProps, IChatMessage } from "../interface/IChatProps";
import { Loader } from "./Loader";
import { ChatGPTIcon, WelcomeIcon } from "./OpenAIIcon";
import { WelcomeIconInfo } from "../ManifestConstants";
import { powerappsTheme, powerappsdarkTheme } from "./theme";

// Powerapps purple color brand
const powerappsBrand: BrandVariants = {
10: "#050205",
20: "#221221",
30: "#3A193A",
40: "#4E1E4E",
50: "#632363",
60: "#762B76",
70: "#823C81",
80: "#8D4D8B",
90: "#985D96",
100: "#A36EA0",
110: "#AE7EAB",
120: "#B98FB6",
130: "#C49FC1",
140: "#CFB0CC",
150: "#D9C1D7",
160: "#E4D3E3"
};

const powerappsTheme: Theme = {
...createLightTheme(powerappsBrand),
};

const powerappsdarkTheme: Theme = {
...createDarkTheme(powerappsBrand),
};


powerappsdarkTheme.colorBrandForeground1 = powerappsBrand[110];
powerappsdarkTheme.colorBrandForeground2 = powerappsBrand[120];

const useStyles = makeStyles({
root: {
display: "relative",
...shorthands.padding("8px"),
paddingTop: "1px",
paddingBottom: "2px",
},
textareaStyle: {
...shorthands.borderStyle("none"),
},
});

export const ChatComponent = React.memo((props:IChatInputProps) => {
const {items, placeholdertext, showIcon, loadingText, accessibleLabel, allocatedWidth, allocatedHeight, onSubmit, usePlatformtheme, disabledState} = props;
export const ChatComponent = React.memo((props: IChatInputProps) => {
const { items, placeholdertext, showIcon, loadingText, accessibleLabel, allocatedWidth, allocatedHeight, onSubmit, usePlatformtheme, disabledState, darkMode } = props;
const [sendBoxValue, setSendBoxValue] = React.useState<string>("");
const textElement = React.createRef<HTMLTextAreaElement>();
const messagesEndRef = React.createRef<HTMLDivElement>()
const textElement = React.createRef<HTMLTextAreaElement>();
const messagesEndRef = React.createRef<HTMLDivElement>();
const styles = useStyles();
const brandForeground1 = usePlatformtheme ? tokens.colorBrandForeground1 : powerappsTheme.colorBrandForeground1;
const brandForeground2 = usePlatformtheme ? tokens.colorBrandForeground2 : powerappsTheme.colorBrandForeground2;
const onSubmitKey = (event: React.KeyboardEvent ) => {
if (!event.shiftKey && event.key === "Enter") {
const onSubmitKey = (event: React.KeyboardEvent) => {
if (!event.shiftKey && event.key === "Enter") {
onSendSubmit(event);
event.preventDefault();
}
}

const onChange: TextareaProps["onChange"] = (ev, data) => {
if (data.value.length <= 5000) {
setSendBoxValue(data.value);
}
};
};

const onSendSubmit = React.useCallback(
(event: React.KeyboardEvent | React.MouseEvent) => {
event.preventDefault();
if (sendBoxValue.length > 0) {
onSubmit(sendBoxValue);
// Reset after submit state
setSendBoxValue("");
const onChange: TextareaProps["onChange"] = (ev, data) => {
if (data.value.length <= 5000) {
setSendBoxValue(data.value);
}
},[sendBoxValue]);
};

const onSendSubmit = React.useCallback(
(event: React.KeyboardEvent | React.MouseEvent) => {
event.preventDefault();
if (sendBoxValue.length > 0) {
onSubmit(sendBoxValue);
// Reset after submit state
setSendBoxValue("");
}
}, [sendBoxValue, onSubmit]);

const chatMessages = React.useMemo(() => {
// Reset submit state on every item collection change
return items.map((i: IChatMessage) => {
return i.role === "assistant" ? <ChatMessage avatar={showIcon ? <Avatar color={"brand"} icon={<Bot24Regular/>} /> : <ChatGPTIcon color={brandForeground1}/>}>{i.content}</ChatMessage> :
<ChatMyMessage>{i.content}</ChatMyMessage>
return i.role === "assistant" ? <ChatMessage avatar={showIcon ? <Avatar color={"brand"} icon={<Bot24Regular />} /> : <ChatGPTIcon color={brandForeground1} />}>{i.content}</ChatMessage> :
<ChatMyMessage>{i.content}</ChatMyMessage>;
});
}, [items]);
}, [items, brandForeground1, showIcon]);

// When chatmessage changes, scroll to bottom to view latest
React.useEffect(()=>{
React.useEffect(() => {
messagesEndRef.current && (messagesEndRef.current.scrollTop = messagesEndRef.current.scrollHeight);
},[chatMessages]);
}, [chatMessages, messagesEndRef, textElement]);

const rootStyles = React.useMemo(() => {
return {
outerDiv: {
height: allocatedHeight, width: allocatedWidth
} as React.CSSProperties,
chat: {
overflowY: 'auto',
maxHeight: allocatedHeight * 0.7,
height: allocatedHeight * 0.7,
} as React.CSSProperties,
card: {
height: 135, //fixed height
} as React.CSSProperties,
textArea: { border: "none" } as React.CSSProperties,
};
}, [allocatedHeight, allocatedWidth]);

// Adding style directly instead of Griffel based to support test harness activity
const chatbotComponent =
<div style={{height:allocatedHeight,width:allocatedWidth}}>
<Chat ref={messagesEndRef} style={{ overflowY: 'auto', height:allocatedHeight * 0.7, maxHeight:allocatedHeight * 0.7}}>
{chatMessages.length < 1 ? <WelcomeIcon color={brandForeground2} width={(allocatedWidth - WelcomeIconInfo.padding).toString()} height={(allocatedWidth - WelcomeIconInfo.padding).toString()}/> : chatMessages}
</Chat>
<br/>
{disabledState && <Loader loadingText={loadingText}/>}
<br/>
<Card style={{ height : allocatedHeight * 0.2}} tabIndex={-1}>
<div style={rootStyles.outerDiv}>
{chatMessages.length < 1 ? <div style={rootStyles.chat}><WelcomeIcon color={brandForeground2} width={(allocatedWidth * 0.7).toString()} height={(allocatedHeight * 0.7 - WelcomeIconInfo.padding).toString()} /></div> :
<Chat ref={messagesEndRef} style={rootStyles.chat}>
{chatMessages}
</Chat>}
<br />
{disabledState && <><Loader loadingText={loadingText} /><br /></>}
<div className={styles.root}>
<Card style={rootStyles.card} tabIndex={-1}>
<Textarea
style={{border:"none"}}
autoFocus={true}
className={styles.textareaStyle}
aria-label={accessibleLabel}
placeholder={placeholdertext}
ref={textElement}
value={sendBoxValue}
onChange={onChange}
disabled={disabledState}
onKeyDown={onSubmitKey}/>
<Button disabled={disabledState} onClick={onSendSubmit} style={{marginLeft: "auto", paddingLeft:5}} appearance="transparent" icon={<SendRegular style={{color:brandForeground1}}/>} size="small" />
onKeyDown={onSubmitKey} />
<Button disabled={disabledState} onClick={onSendSubmit} style={{ marginLeft: "auto", paddingLeft: 5 }} appearance="transparent" icon={<SendRegular style={{ color: brandForeground1 }} />} size="small" />
</Card>
</div>
</div>;

return usePlatformtheme ? chatbotComponent : <FluentProvider theme={powerappsTheme}>{chatbotComponent}</FluentProvider> ;
return usePlatformtheme ? chatbotComponent : <FluentProvider theme={darkMode ? powerappsdarkTheme : powerappsTheme}>{chatbotComponent}</FluentProvider>;
});
ChatComponent.displayName = "ChatComponent";
22 changes: 11 additions & 11 deletions PowerChatbot/PowerChatbot/components/Loader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { ILoaderProps } from "../interface/IChatProps";
const useStyles = makeStyles({
root: {
display: "relative",
...shorthands.padding("12px"),
...shorthands.padding("8px"),
paddingTop: "1px",
paddingBottom: "2px",
},
Expand All @@ -20,14 +20,14 @@ const useStyles = makeStyles({
},
});

export const Loader = (props:ILoaderProps) => {
const styles = useStyles();
return (<div className={styles.root}>
<Card className={styles.cardStyle}>
<div className={styles.cardInnerStyle} aria-live="polite">
<Label weight="semibold">{props.loadingText}</Label>
</div>
<ProgressBar />
</Card>
</div>)
export const Loader = (props: ILoaderProps) => {
const styles = useStyles();
return (<div className={styles.root}>
<Card className={styles.cardStyle}>
<div className={styles.cardInnerStyle} aria-live="polite">
<Label weight="semibold">{props.loadingText}</Label>
</div>
<ProgressBar />
</Card>
</div>);
};
Loading

0 comments on commit fe2add7

Please sign in to comment.