diff --git a/Word-Add-in-AI-Assistant/README.md b/Word-Add-in-AI-Assistant/README.md index 717d5e8..a3d67a2 100644 --- a/Word-Add-in-AI-Assistant/README.md +++ b/Word-Add-in-AI-Assistant/README.md @@ -26,7 +26,7 @@ This add-in demonstrates Word add-in capabilities to insert content generated by - Generate picture. Select the 'Generate Picture' option and select the source words from document, submit, insert the picture into the cusor pisiton inside document. - Switch to chat mode. You can send whatever text words to the chat box, insert the generated text into Document/Comment/Footnote/Header. - You can configure the drop down list to whatever prompt prefix you like. Clone the project and open config.tsx. Change the AssistanceOption and GenerateOption following the format of the existing GenerateText and GeneratePicture. -- You need to input the Azure OpenAI account once you relaunch/reload the add-in. You can persist the account details: apiKey in config.tsx or just refill in it whenever the add in ask. +- You need to input the Azure OpenAI account once you relaunch/reload the add-in. You can persist the account details: apiKey endpoint deployment in AIKeyConfigDialog.tsx or just refill in it whenever the add in ask. ## Applies to - Word on Windows, Mac, and in a browser. diff --git a/Word-Add-in-AI-Assistant/src/taskpane/components/AIGenerate.tsx b/Word-Add-in-AI-Assistant/src/taskpane/components/AIGenerate.tsx index dfcf77d..6f0c2b2 100644 --- a/Word-Add-in-AI-Assistant/src/taskpane/components/AIGenerate.tsx +++ b/Word-Add-in-AI-Assistant/src/taskpane/components/AIGenerate.tsx @@ -3,6 +3,7 @@ import React from "react"; import TextArea from "antd/es/input/TextArea"; import { Page } from "./Home"; import { generate, GenerateOption, GenerateType } from "./utility/config"; +import { _apiKey, _deployment, _endPoint } from "./AIKeyConfigDialog"; export interface AIGenerateState { sourceWords: string; @@ -12,6 +13,7 @@ export interface AIGenerateState { export interface AIGenerateProps { setCurrentPage: (page: Page, generatedContent: string) => void; + openConfigDialog: (isOpen: boolean) => void; generateOption: GenerateOption; } @@ -51,6 +53,8 @@ export default class AIGenerateText extends React.Component { if (this.state.sourceWords.length == 0) { message.warning("please select source content."); + } else if (_apiKey === "" || _endPoint === "" || _deployment === "") { + this.props.openConfigDialog(true); } else { this.setState({ loading: true }); await generate(this.state.sourceWords, this.props.generateOption) @@ -61,6 +65,9 @@ export default class AIGenerateText extends React.Component { + message.error(err.message); + this.setState({ loading: false }); }) } }; diff --git a/Word-Add-in-AI-Assistant/src/taskpane/components/AIKeyConfigDialog.tsx b/Word-Add-in-AI-Assistant/src/taskpane/components/AIKeyConfigDialog.tsx index 26c005f..05370a7 100644 --- a/Word-Add-in-AI-Assistant/src/taskpane/components/AIKeyConfigDialog.tsx +++ b/Word-Add-in-AI-Assistant/src/taskpane/components/AIKeyConfigDialog.tsx @@ -1,46 +1,100 @@ import { Input, Modal } from "antd"; import React from "react"; +// global variable to store the api key/endpoint/deployment, configrued by developer +export let _apiKey = ""; +export let _endPoint = ""; +export let _deployment = ""; + export interface ApiKeyConfigProps { isOpen: boolean; - apiKey: string; - setKey: (key: string) => void; setOpen: (isOpen: boolean) => void; } export interface ApiKeyConfigState { inputKey: string; + endPoint: string; + deployment: string; } export default class AIKeyConfigDialog extends React.Component { constructor(props, context) { super(props, context); + this.state = { + inputKey: _apiKey, + endPoint: _endPoint, + deployment: _deployment, + }; + } + + componentDidMount(): void { + this.setState({ + inputKey: _apiKey, + endPoint: _endPoint, + deployment: _deployment, + }); + } + + setKey = (key: string) => { + _apiKey = key; + } + + setEndpoint = (endpoint: string) => { + _endPoint = endpoint; + } + + setDeployment = (deployment: string) => { + _deployment = deployment; } handleOk = () => { - if (this.state != null && this.state.inputKey != null && this.state.inputKey.length > 0) { - this.props.setKey(this.state.inputKey); - } else { - this.props.setOpen(false); + if (this.state != null && this.state.inputKey != null) { + this.setKey(this.state.inputKey); + } + if (this.state != null && this.state.endPoint != null) { + this.setEndpoint(this.state.endPoint); } + if (this.state != null && this.state.deployment != null) { + this.setDeployment(this.state.deployment); + } + this.props.setOpen(false); }; handleCancel = () => { + //back to the global variable + this.setState({ + inputKey: _apiKey, + endPoint: _endPoint, + deployment: _deployment, + }) this.props.setOpen(false); }; - inputChange = (e) => { + keyChange = (e) => { this.setState({ inputKey: e.target.value }); } + ednpointChange = (e) => { + this.setState({ endPoint: e.target.value }); + } + + deploymentChange = (e) => { + this.setState({ deployment: e.target.value }); + } + render() { return <> - + + + + + + ; } diff --git a/Word-Add-in-AI-Assistant/src/taskpane/components/AIWelcome.tsx b/Word-Add-in-AI-Assistant/src/taskpane/components/AIWelcome.tsx index b1c1eef..360c322 100644 --- a/Word-Add-in-AI-Assistant/src/taskpane/components/AIWelcome.tsx +++ b/Word-Add-in-AI-Assistant/src/taskpane/components/AIWelcome.tsx @@ -23,7 +23,7 @@ export default class AIWelcome extends React.Component {
diff --git a/Word-Add-in-AI-Assistant/src/taskpane/components/Chat.tsx b/Word-Add-in-AI-Assistant/src/taskpane/components/Chat.tsx index 4534180..963512b 100644 --- a/Word-Add-in-AI-Assistant/src/taskpane/components/Chat.tsx +++ b/Word-Add-in-AI-Assistant/src/taskpane/components/Chat.tsx @@ -1,15 +1,14 @@ import { LeftOutlined, SendOutlined } from "@ant-design/icons"; -import { Input, Segmented } from "antd"; -import React from "react"; -import { Page, _apiKey } from "./Home"; -import { apiKey, generateText } from "./utility/config"; -import AIKeyConfigDialog from "./AIKeyConfigDialog"; - -// global variable to store the api key, configrued by developer -export let chatKey = ""; +import { Input, Segmented, message } from "antd"; +import React, { Children } from "react"; +import { Page } from "./Home"; +import { generateText } from "./utility/config"; +import { _apiKey, _deployment, _endPoint } from "./AIKeyConfigDialog"; export interface IChatProps { + children?: React.ReactNode; back: (page: Page, generatedContent: string) => void; + setOpen: (isOpen: boolean) => void; } export default class Chat extends React.Component { @@ -21,7 +20,6 @@ export default class Chat extends React.Component { greeting: "please input whatever you want to say to the AI.", content: <>, input: "", - openKeyConfigDialog: false, selectedMessage: "", }; @@ -63,15 +61,6 @@ export default class Chat extends React.Component { } }; - setOpen = (open: boolean) => { - this.setState({ openKeyConfigDialog: open }); - }; - - setKey = (key: string) => { - chatKey = key; - this.setState({ openKeyConfigDialog: false }); - }; - onChange = async (option) => { await Word.run(async (context) => { const selRange = context.document.getSelection(); @@ -122,15 +111,15 @@ export default class Chat extends React.Component {
); - if (apiKey === "" && chatKey === "" && _apiKey === "") { + if (_apiKey === "" || _endPoint === "" || _deployment === "") { const alertMessage = ( <>
Please config the{" "} - this.setOpen(true)} className="configKey"> - Azure Open AI Key.{" "} + this.props.setOpen(true)} className="configKey"> + Azure OpenAI Account.{" "}
@@ -161,6 +150,9 @@ export default class Chat extends React.Component { const ret = await generateText(input, 50).then((res) => { return res.replace("\n\r\n", "").replace("\n", "").replace("\n", ""); + }).catch((err) => { + message.error(err.message); + throw Error(err); }); const responseMessage = (
@@ -199,7 +191,7 @@ export default class Chat extends React.Component { inputChange = (e) => { this.setState({ input: e.target.value }); - }; + } render() { return ( @@ -213,12 +205,6 @@ export default class Chat extends React.Component {
-
{this.state.content}
@@ -233,6 +219,9 @@ export default class Chat extends React.Component { />
+
+ {Children.only(this.props.children)} +
); diff --git a/Word-Add-in-AI-Assistant/src/taskpane/components/Home.tsx b/Word-Add-in-AI-Assistant/src/taskpane/components/Home.tsx index 019cef3..d4086dc 100644 --- a/Word-Add-in-AI-Assistant/src/taskpane/components/Home.tsx +++ b/Word-Add-in-AI-Assistant/src/taskpane/components/Home.tsx @@ -3,15 +3,12 @@ import { DownOutlined, InteractionOutlined, RightOutlined, SettingOutlined } fro import type { MenuProps } from 'antd'; import { Button, Dropdown, message, Space, Tooltip } from 'antd'; import AIWelcome from "./AIWelcome"; -import { apiKey, AssistanceOption, GenerateOptionList } from "./utility/config"; +import { AssistanceOption, GenerateOptionList } from "./utility/config"; import AIGenerate from "./AIGenerate"; import AIKeyConfigDialog from "./AIKeyConfigDialog"; import AITextDisplay from "./AITextDisplay"; import AIPictureDisplay from "./AIPictureDisplay"; -import Chat, { chatKey } from "./Chat"; - -// global variable to store the api key, configrued by developer -export let _apiKey = ""; +import Chat from "./Chat"; export enum Page { Home = "Home", @@ -25,7 +22,7 @@ export default class Home extends React.Component { } state = { - selectedOption: AssistanceOption.SelectAnOption, + selectedOption: AssistanceOption.Welcome, content: , curPage: Page.Home, generatedContent: "", @@ -44,20 +41,11 @@ export default class Home extends React.Component { this.setState({ openKeyConfigDialog: isOpen }); }; - setKey = (key: string) => { - _apiKey = key; - this.setState({ openKeyConfigDialog: false }); - } - generateAssistanceContent: MenuProps['onClick'] = (e) => { - if (_apiKey === "" && apiKey === "" && chatKey === "") { - this.setState({ openKeyConfigDialog: true }); - return; - } if (e.key !== this.state.selectedOption) { var content: ReactElement = <> switch (e.key) { - case AssistanceOption.SelectAnOption: + case AssistanceOption.Welcome: content = break; default: @@ -81,7 +69,7 @@ export default class Home extends React.Component { var areaGenerated: ReactElement = <>; GenerateOptionList.forEach((item) => { if (item.dropDownOption === key) { - areaGenerated = + areaGenerated = } }) return areaGenerated; @@ -101,7 +89,16 @@ export default class Home extends React.Component { }; if(this.state.curPage === Page.Chat) { - return + return ( + <> + + + + + ); } return ( @@ -109,7 +106,10 @@ export default class Home extends React.Component {
@@ -122,9 +122,7 @@ export default class Home extends React.Component { Welcome:
diff --git a/Word-Add-in-AI-Assistant/src/taskpane/components/utility/config.tsx b/Word-Add-in-AI-Assistant/src/taskpane/components/utility/config.tsx index fa57e75..482fac0 100644 --- a/Word-Add-in-AI-Assistant/src/taskpane/components/utility/config.tsx +++ b/Word-Add-in-AI-Assistant/src/taskpane/components/utility/config.tsx @@ -1,11 +1,7 @@ import { AxiosRequestConfig } from "axios"; import { get, post } from "./request"; import { message } from "antd"; -import { _apiKey } from "../Home"; -import { chatKey } from "../Chat"; - -//change the apiKey of Azure AI service to yours -export const apiKey = "" +import { _apiKey, _deployment, _endPoint } from "../AIKeyConfigDialog"; export enum GenerateType { Text = "Text", @@ -13,7 +9,7 @@ export enum GenerateType { } export enum AssistanceOption { - SelectAnOption = "Select an option", + Welcome = "Welcome", GenerateText = "Generate Text", GeneratePicture = "Generate Picture", } @@ -42,9 +38,7 @@ export const GenerateOptionList: GenerateOption[] = [ ]; export const AzureAI = { - baseUrl: "https://augloop-cs-test-scus-shared-open-ai-0.openai.azure.com/openai/deployments/text-davinci-003/completions", - apiversion: "2023-05-15", - apikey: apiKey, + apiversion: "2023-05-15" }; export interface AzureTextGenAPI { @@ -84,14 +78,17 @@ export const generateText = (content: string, maxTokens: number = 1000) => { let requestBody: AzureTextGenAPI = { prompt: content, max_tokens: maxTokens }; let config: AxiosRequestConfig = { headers: { - "api-key": AzureAI.apikey === "" ? (_apiKey === "" ? chatKey : _apiKey) : AzureAI.apikey, + "api-key": _apiKey, "Content-Type": "application/json", }, params: { "api-version": AzureAI.apiversion, }, }; - return post(AzureAI.baseUrl, requestBody, config).then((res) => { + + let url = _endPoint + "/openai/deployments/" + _deployment + "/completions"; + + return post(url, requestBody, config).then((res) => { if (res.status == 200 && res.data != null) { let resObj: AzureTextGenRes = res.data; if (resObj.choices == null || resObj.choices.length == 0) { @@ -101,15 +98,15 @@ export const generateText = (content: string, maxTokens: number = 1000) => { } else { throw Error(res.data.error); } + }).catch((err) => { + throw Error(err); }); }; ////////////////////////////////////////////Generate Picture////////////////////////////////////////////// export const DallE = { - baseUrl: "https://augloop-cs-test-scus-shared-open-ai-0.openai.azure.com/openai/images/generations:submit", - apiKey: apiKey, - apiVersion: "2023-06-01-preview", + apiVersion: "2023-06-01-preview" }; export const generatePicture = (prompt: string) => { @@ -120,14 +117,16 @@ export const generatePicture = (prompt: string) => { }; let config: AxiosRequestConfig = { headers: { - "api-key": DallE.apiKey === "" ? _apiKey : DallE.apiKey, + "api-key": _apiKey, responseType: "blob", }, params: { "api-version": DallE.apiVersion, }, }; - return post(DallE.baseUrl, requestBody, config).then(async (res) => { + let imageUrl = _endPoint + "/openai/images/generations:submit"; + + return post(imageUrl, requestBody, config).then(async (res) => { if (res.status == 202 && res.headers["operation-location"] != null) { const operationLocation = res.headers["operation-location"]; var status = "notRunning"; @@ -137,7 +136,7 @@ export const generatePicture = (prompt: string) => { while (status != "succeeded" && count < maxRetry) { await get(operationLocation, { headers: { - "api-key": DallE.apiKey === "" ? _apiKey : DallE.apiKey, + "api-key": _apiKey, }, }).then((r) => { if (r.status == 200 && r.data.status == "succeeded") { @@ -156,5 +155,7 @@ export const generatePicture = (prompt: string) => { } else { throw Error(res); } + }).catch((err) => { + throw Error(err); }); }; diff --git a/Word-Add-in-AI-Assistant/src/taskpane/components/utility/request.tsx b/Word-Add-in-AI-Assistant/src/taskpane/components/utility/request.tsx index 5782ed2..6af7d4f 100644 --- a/Word-Add-in-AI-Assistant/src/taskpane/components/utility/request.tsx +++ b/Word-Add-in-AI-Assistant/src/taskpane/components/utility/request.tsx @@ -1,10 +1,11 @@ +import { message } from "antd"; import axios, { AxiosRequestConfig } from "axios"; import qs = require("qs"); /* global process, console */ axios.defaults.timeout = 20000; export const get = (url: string, config: AxiosRequestConfig = {}): Promise => - new Promise((resolve) => { + new Promise((resolve, reject) => { axios .get(url, { params: config.params, @@ -17,12 +18,14 @@ export const get = (url: string, config: AxiosRequestConfig = {}): Promise .catch((error) => { console.log(error); errorHandler(error); + reject(error); + throw error; }); }); // post request export const post = (url: string, data = {}, config: AxiosRequestConfig = {}): Promise => - new Promise((resolve) => { + new Promise((resolve, reject) => { axios .post(url, data, config) .then((res) => { @@ -30,6 +33,9 @@ export const post = (url: string, data = {}, config: AxiosRequestConfig = {}): P }) .catch((error) => { console.error(error); + errorHandler(error) + reject(error) + throw Error(error) }); }); diff --git a/Word-Add-in-AI-Assistant/src/taskpane/css/Home.css b/Word-Add-in-AI-Assistant/src/taskpane/css/Home.css index b911232..da5fb81 100644 --- a/Word-Add-in-AI-Assistant/src/taskpane/css/Home.css +++ b/Word-Add-in-AI-Assistant/src/taskpane/css/Home.css @@ -14,6 +14,8 @@ body, height: 100%; margin: 0; padding: 0; + overflow-y: hidden; + overflow-x: hidden; } .f1i3iumi { @@ -63,8 +65,8 @@ body, align-items: center; } -.switchIcon { - margin-right: 0.5rem; +.setting { + font-size: 20px; } .setting:hover { @@ -72,6 +74,10 @@ body, border-color: #0066ff; } +.switchIcon { + margin-right: 0.5rem; +} + .option_selection { width: 100%; height: 40px; diff --git a/Word-Add-in-AI-Assistant/src/taskpane/taskpane.html b/Word-Add-in-AI-Assistant/src/taskpane/taskpane.html index 4e475a0..50f41a9 100644 --- a/Word-Add-in-AI-Assistant/src/taskpane/taskpane.html +++ b/Word-Add-in-AI-Assistant/src/taskpane/taskpane.html @@ -21,7 +21,7 @@
- +