Update logo & name Add-in. Add language options and improve two features

This commit is contained in:
Trinh Ngoc Tu 2024-12-15 17:32:35 +01:00
parent 1d9584f71f
commit 32b6af5125
17 changed files with 134 additions and 83 deletions

BIN
.DS_Store vendored

Binary file not shown.

8
.hintrc Normal file
View File

@ -0,0 +1,8 @@
{
"extends": [
"development"
],
"hints": {
"typescript-config/strict": "off"
}
}

BIN
assets/.DS_Store vendored Normal file

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 314 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 551 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 488 B

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 830 B

After

Width:  |  Height:  |  Size: 292 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 551 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 551 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.2 KiB

After

Width:  |  Height:  |  Size: 551 KiB

View File

@ -4,11 +4,11 @@
<Version>1.0.0.0</Version> <Version>1.0.0.0</Version>
<ProviderName>OutlookLLM RizLum</ProviderName> <ProviderName>OutlookLLM RizLum</ProviderName>
<DefaultLocale>en-US</DefaultLocale> <DefaultLocale>en-US</DefaultLocale>
<DisplayName DefaultValue="OutlookLLM"/> <DisplayName DefaultValue="Outlook Rizlum"/>
<Description DefaultValue="Add-in for new Outlook that adds Generative AI features (Composition, Summarizing)"/> <Description DefaultValue="Add-in for Outlook that adds Generative AI features (Composition, Summarizing)"/>
<IconUrl DefaultValue="https://localhost:3000/assets/icon-64.png"/> <IconUrl DefaultValue="https://localhost:3000/assets/icon-64.png"/>
<HighResolutionIconUrl DefaultValue="https://localhost:3000/assets/icon-128.png"/> <HighResolutionIconUrl DefaultValue="https://localhost:3000/assets/icon-128.png"/>
<SupportUrl DefaultValue="https://www.certeza.ai/help"/> <SupportUrl DefaultValue="https://rizlum.ai/fr/"/>
<AppDomains> <AppDomains>
<AppDomain>https://localhost</AppDomain> <AppDomain>https://localhost</AppDomain>
</AppDomains> </AppDomains>

View File

@ -1,8 +1,9 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import * as React from "react"; import * as React from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import Header from "./Header"; import Header from "./Header";
import HeroList from "./HeroList"; import HeroList from "./HeroList";
import TextInsertion from "./TextInsertion"; import Summarize from "./Summarize";
import { makeStyles } from "@fluentui/react-components"; import { makeStyles } from "@fluentui/react-components";
import { Ribbon24Regular, LockOpen24Regular, DesignIdeas24Regular } from "@fluentui/react-icons"; import { Ribbon24Regular, LockOpen24Regular, DesignIdeas24Regular } from "@fluentui/react-icons";
import QuickResponse from "./QuickResponse"; import QuickResponse from "./QuickResponse";
@ -18,8 +19,8 @@ const App = (props) => {
return ( return (
<div className={styles.root}> <div className={styles.root}>
<TextInsertion /> <Summarize />
<QuickResponse /> {/* <QuickResponse /> */}
</div> </div>
); );
}; };

View File

@ -45,11 +45,16 @@ const useStyles = makeStyles({
}); });
const TextInsertion = () => { const Summarize = () => {
const [showSpinner, setshowSpinner] = useState(false); const [showSpinner, setshowSpinner] = useState(false);
const [showSpinnerdetails, setshowSpinnerdetails] = useState(false); const [showSpinnerdetails, setshowSpinnerdetails] = useState(false);
const [emailsummary, setEmailsummary] = useState(""); const [emailsummary, setEmailsummary] = useState("");
const [emailsummarydetails, setEmailsummarydetails] = useState(""); const [emailsummarydetails, setEmailsummarydetails] = useState("");
const [language, setLanguage] = useState("English"); // Default language is English
const handleLanguageChange = (event) => {
setLanguage(event.target.value);
};
const getEmailContent = () => const getEmailContent = () =>
new Promise((resolve, reject) => { new Promise((resolve, reject) => {
@ -77,10 +82,10 @@ const TextInsertion = () => {
body: JSON.stringify({ body: JSON.stringify({
model: 'riz-text', model: 'riz-text',
messages: [ messages: [
{ role: "system", content: "You are an email assistant." }, { role: "system", content: "You are an email summarizer." },
{ {
role: 'user', role: 'user',
content: 'Summarize briefly less than 40 words the content of the following email body: ' + emailContent, content: `Summarize less than 50 words in ${language}$ the following email content into a concise summary:\n\n${emailContent}`,
}, },
], ],
}), }),
@ -98,8 +103,7 @@ const TextInsertion = () => {
console.error('Error fetching data:', error); console.error('Error fetching data:', error);
} }
setshowSpinner(false); setshowSpinner(false);
}; };
const handleSummaryLong = async () => { const handleSummaryLong = async () => {
@ -117,10 +121,10 @@ const TextInsertion = () => {
body: JSON.stringify({ body: JSON.stringify({
model: 'riz-text', model: 'riz-text',
messages: [ messages: [
{ role: "system", content: "You are an email assistant." }, { role: "system", content: "You are an email summarizer." },
{ {
role: 'user', role: 'user',
content: 'Summarize in detail less than 200 words the content of the following email body: ' + emailContent_detail, content: `Summarize less than 200 words in ${language}$ the following email content into a concise summary:\n\n${emailContent_detail}$`,
}, },
], ],
}), }),
@ -138,8 +142,7 @@ const TextInsertion = () => {
console.error('Error fetching data:', error); console.error('Error fetching data:', error);
} }
setshowSpinnerdetails(false); setshowSpinnerdetails(false);
}; };
const styles = useStyles(); const styles = useStyles();
@ -147,11 +150,25 @@ const TextInsertion = () => {
return ( return (
<div className={styles.textPromptAndInsertion}> <div className={styles.textPromptAndInsertion}>
<Title3>Summarize with AI</Title3> <Title3>Summarize with AI</Title3>
<div style={{ marginBottom: "15px" }}>
<label htmlFor="language" style={{ display: "block", marginBottom: "5px" }}>
Choose summary language:
</label>
<select
id="language"
value={language}
onChange={handleLanguageChange}
style={{ width: "100%", padding: "8px" }}
>
<option value="English">English</option>
<option value="French">French</option>
</select>
</div>
<Button appearance="primary" disabled={false} size="large" onClick={handleTextInsertion} style={{ marginBottom: '16px' }}> <Button appearance="primary" disabled={false} size="large" onClick={handleTextInsertion} style={{ marginBottom: '16px' }}>
{showSpinner && ( {showSpinner && (
<Spinner id="spinner" appearance="inverted"/> <Spinner id="spinner" appearance="inverted"/>
)} )}
{showSpinner ? ' Summarizing email...' : 'Summarize with AI'} {showSpinner ? ' Summarizing email...' : 'Summarize briefly the email'}
</Button> </Button>
{/* <TextField value={response} multiline rows={10} disabled /> */} {/* <TextField value={response} multiline rows={10} disabled /> */}
{emailsummary && ( {emailsummary && (
@ -167,8 +184,8 @@ const TextInsertion = () => {
fontSize: "16px", // Improves text readability fontSize: "16px", // Improves text readability
fontFamily: "'Segoe UI', Tahoma, Geneva, Verdana, sans-serif", // Uses a modern font fontFamily: "'Segoe UI', Tahoma, Geneva, Verdana, sans-serif", // Uses a modern font
lineHeight: "1.5", // Adds spacing for multiline content lineHeight: "1.5", // Adds spacing for multiline content
padding: "12px", // Adds internal spacing for a spacious feel padding: "0px", // Adds internal spacing for a spacious feel
borderRadius: "8px", // Smooth rounded corners borderRadius: "5px", // Smooth rounded corners
border: "1px solid #e1e4e8", // Subtle border for a clean outline border: "1px solid #e1e4e8", // Subtle border for a clean outline
boxShadow: "0 2px 4px rgba(0, 0, 0, 0.1)", // Adds a soft shadow for depth boxShadow: "0 2px 4px rgba(0, 0, 0, 0.1)", // Adds a soft shadow for depth
overflowY: "auto", // Ensures content is scrollable if it exceeds height overflowY: "auto", // Ensures content is scrollable if it exceeds height
@ -184,7 +201,6 @@ const TextInsertion = () => {
{emailsummarydetails && ( {emailsummarydetails && (
<div> <div>
<TextField value={emailsummarydetails} multiline rows={15} disabled <TextField value={emailsummarydetails} multiline rows={15} disabled
size={30}
appearance="filledLighter" // Applies a modern, softer look appearance="filledLighter" // Applies a modern, softer look
style={{ style={{
width: "100%", // Makes the TextField responsive width: "100%", // Makes the TextField responsive
@ -195,16 +211,17 @@ const TextInsertion = () => {
fontSize: "14px", // Improves text readability fontSize: "14px", // Improves text readability
fontFamily: "'Segoe UI', Tahoma, Geneva, Verdana, sans-serif", // Uses a modern font fontFamily: "'Segoe UI', Tahoma, Geneva, Verdana, sans-serif", // Uses a modern font
lineHeight: "1.5", // Adds spacing for multiline content lineHeight: "1.5", // Adds spacing for multiline content
padding: "12px", // Adds internal spacing for a spacious feel padding: "5px", // Adds internal spacing for a spacious feel
borderRadius: "8px", // Smooth rounded corners borderRadius: "5px", // Smooth rounded corners
border: "1px solid #e1e4e8", // Subtle border for a clean outline border: "1px solid #e1e4e8", // Subtle border for a clean outline
boxShadow: "0 2px 4px rgba(0, 0, 0, 0.1)", // Adds a soft shadow for depth boxShadow: "0 2px 4px rgba(0, 0, 0, 0.1)", // Adds a soft shadow for depth
overflowY: "auto", // Ensures content is scrollable if it exceeds height overflowY: "auto", // Ensures content is scrollable if it exceeds height
}} /> }}
/>
</div> </div>
)} )}
</div> </div>
); );
}; };
export default TextInsertion; export default Summarize;

View File

@ -1,8 +1,10 @@
/* eslint-disable no-undef */
/* eslint-disable prettier/prettier */ /* eslint-disable prettier/prettier */
import * as React from "react"; import * as React from "react";
import { useState } from "react"; import { useState } from "react";
import { Button, Field, Title3, Checkbox, Textarea, Text, Spinner, tokens, makeStyles } from "@fluentui/react-components"; import { Button, Field, Title3, Checkbox, Textarea, Text, Spinner, tokens, makeStyles } from "@fluentui/react-components";
import { insertText } from "../outlook"; //import { insertText } from "../outlook";
import insertText from "../office-document";
const useStyles = makeStyles({ const useStyles = makeStyles({
@ -17,7 +19,7 @@ const useStyles = makeStyles({
marginLeft: "15px", marginLeft: "15px",
marginTop: "30px", marginTop: "30px",
marginRight: "15px", marginRight: "15px",
marginBottom: "10px"
}, },
textAreaField: { textAreaField: {
@ -26,8 +28,15 @@ const useStyles = makeStyles({
marginRight: "15px", marginRight: "15px",
minHeight: "150px", minHeight: "150px",
marginBottom: "15px"
}, },
textArea: {
padding: "8px",
marginBottom: "10px"
},
textCheck: { textCheck: {
marginTop: "10px", marginTop: "10px",
marginLeft: "15px", marginLeft: "15px",
@ -42,19 +51,18 @@ const useStyles = makeStyles({
}); });
const TextInsertion = () => { const TextInsertion = () => {
const [text, setText] = useState("Write an email to make a meeting with client"); const [language, setLanguage] = useState("English"); // Default language is English
const [text, setText] = useState("Give some ideas to write the email");
const [showSpinner, setshowSpinner] = useState(false); const [showSpinner, setshowSpinner] = useState(false);
const [writeSubject, setWriteSubject] = useState(false); const [writeSubject, setWriteSubject] = useState("Subject of emal (optional)");
let data = ""; let data = "";
let subject_email = "";
const handleTextInsertion = async () => { const handleTextInsertion = async () => {
try { try {
setshowSpinner(true);
setshowSpinner(true); console.log("test before");
console.log("test before");
//const fetchChatCompletion = async () => {
try {
const res = await fetch('https://ai.rizlum.com/chat/completions', { const res = await fetch('https://ai.rizlum.com/chat/completions', {
method: 'POST', method: 'POST',
headers: { headers: {
@ -67,7 +75,16 @@ const TextInsertion = () => {
{ role: "system", content: "You are an email assistant." }, { role: "system", content: "You are an email assistant." },
{ {
role: 'user', role: 'user',
content: 'Write an email content without subject with the following content: ' + text, content: `Generate a professionnal email content in ${language} without a subject based on the following ideas: ${text}.
Formatting instructions:
1. Include appropriate line breaks to separate paragraphs.
2. Add a blank line (double line spacing) between paragraphs to improve readability.
3. For example:
- Start with a salutation such as "Hello [Recipient's Name]," followed by a blank line.
- After each paragraph, leave a blank line before starting the next.
- End the email with a closing like "Best regards," followed by the sender's name, with a blank line between them.
Ensure the email is well-structured and visually appealing with clear spacing between sections.`
}, },
], ],
}), }),
@ -77,65 +94,67 @@ const TextInsertion = () => {
throw new Error(`HTTP error! status: ${res.status}`); throw new Error(`HTTP error! status: ${res.status}`);
} }
data = await res.json(); data = await res.json();
console.log(data);
// setResponse(data);
} catch (error) {
console.error('Error fetching chat completion:', error);
//setResponse('Error fetching data');
}
//};
// const response = await fetch("http://localhost:11434/api/generate", {
// method: "POST",
// headers: {
// "Content-Type": "application/json",
// },
// body: JSON.stringify({
// model: "llama3.2",
// prompt: text,
// })
// });
//const response = "Hello World, we are Rizlum";
console.log(data); console.log(data);
// if (!response.ok) {
// throw new Error("Network response was not ok");
// }
// Handle success response if needed // Handle success response if needed
console.log('Data sent successfully'); console.log('Data sent successfully');
const textContent = data.choices[0].message.content; const textContent = data.choices[0].message.content;
console.log(textContent); console.log(textContent);
//const textContent = "Insert this magic text from Riz Lum :D"; // Insert text and subject to the email compose
//await insertText(textContent, writeSubject); if (writeSubject == "Subject of emal (optional)") {
await insertText(textContent); subject_email = "";
}
else {
subject_email = writeSubject;
}
await insertText(textContent,subject_email);
setshowSpinner(false); setshowSpinner(false);
} catch (error) { } catch (error) {
setshowSpinner(false); setshowSpinner(false);
console.error('Error sending data:',error); console.error('Error sending data:',error);
} }
};
const handleLanguageChange = (event) => {
setLanguage(event.target.value);
}; };
const handleTextChange = async (event) => { const handleTextChange = async (event) => {
setText(event.target.value); setText(event.target.value);
}; };
const handleSubjectChange = async (event) => {
setWriteSubject(event.target.value);
};
const styles = useStyles(); const styles = useStyles();
return ( return (
<div className={styles.textPromptAndInsertion}> <div className={styles.textPromptAndInsertion}>
<Title3>Compose with AI</Title3> <Title3>Compose an email</Title3>
<Text className={styles.textField} size="medium" >1. Set the cursor where you want to insert the AI generated email.</Text> <Text className={styles.textField} size="medium" >Describe the email that you need AI help:</Text>
<Field className={styles.textAreaField} size="medium" label="2. Describe the email you need AI help you with:"> <Field className={styles.textAreaField} size="medium" /* label="1. Describe the email:" */>
<Textarea size="medium" className="{styles.textArea}" placeholder={text} onChange={handleTextChange} resize="vertical"/> <div style={{ marginBottom: "15px" }}>
</Field> {/* <label htmlFor="language" style={{ display: "block", marginBottom: "5px" }}>
{/* <Text className={styles.textCheck} size="medium" >3. Check if you want also AI to generate the email subject.</Text> Choose Language:
<Checkbox className={styles.checkStyle} label="Generate Email Subject" onChange={(ev, data) => setWriteSubject(data.checked)} value={writeSubject}/> */} </label> */}
<select
id="language"
value={language}
onChange={handleLanguageChange}
style={{ width: "100%", padding: "8px" }}
>
<option value="English">English</option>
<option value="French">French</option>
</select>
</div>
<Textarea size="small" className={styles.textArea} placeholder={writeSubject} onChange={handleSubjectChange} resize="vertical"/>
<Textarea size="medium" className={styles.textArea} placeholder={text} onChange={handleTextChange} resize="vertical"/>
</Field>
<Text className={styles.textField} size="medium" >Set the cursor where you want to insert the AI generated email.</Text>
<Button appearance="primary" disabled={false} size="large" onClick={handleTextInsertion}> <Button appearance="primary" disabled={false} size="large" onClick={handleTextInsertion}>
{showSpinner && ( {showSpinner && (
<Spinner id="spinner" appearance="inverted"/> <Spinner id="spinner" appearance="inverted"/>

View File

@ -1,11 +1,12 @@
/* eslint-disable prettier/prettier */
/* global Office console */ /* global Office console */
const insertText = async (text, writeSubject) => { const insertText = async (textContent, writeSubject) => {
// Write text to the cursor point in the compose surface.
try { try {
// Insert subject if available
if (writeSubject) { if (writeSubject) {
const respSetSubject = await Office.context.mailbox.item.subject.setAsync( Office.context.mailbox.item.subject.setAsync(
text.subject, writeSubject,
{ coercionType: Office.CoercionType.Text }, { coercionType: Office.CoercionType.Text },
(asyncResult) => { (asyncResult) => {
if (asyncResult.status === Office.AsyncResultStatus.Failed) { if (asyncResult.status === Office.AsyncResultStatus.Failed) {
@ -14,11 +15,15 @@ const insertText = async (text, writeSubject) => {
} }
); );
} }
//console.log('emailbody: '+ text.body); // Write text to the cursor point in the compose surface.
const formattedText = textContent
const respSetBody = await Office.context.mailbox.item.body.setSelectedDataAsync( .split("\n\n") // Split paragraphs by double line breaks
text.body.replace(/\n/g, '<br>'), .map((paragraph) => `<p>${paragraph.trim()}</p>`) // Wrap each paragraph in <p> tags
{ coercionType: Office.CoercionType.Html }, .join(""); // Join paragraphs without extra separators
// Insert the formatted HTML into the email body
Office.context.mailbox.item.body.setSelectedDataAsync(
formattedText,
{ coercionType: Office.CoercionType.Html},
(asyncResult) => { (asyncResult) => {
if (asyncResult.status === Office.AsyncResultStatus.Failed) { if (asyncResult.status === Office.AsyncResultStatus.Failed) {
throw asyncResult.error.message; throw asyncResult.error.message;

View File

@ -1,3 +1,4 @@
/* eslint-disable prettier/prettier */
/* global Office console */ /* global Office console */
export async function insertText(text) { export async function insertText(text) {

View File

@ -6,7 +6,7 @@ const HtmlWebpackPlugin = require("html-webpack-plugin");
const webpack = require("webpack"); const webpack = require("webpack");
const urlDev = "https://localhost:3000/"; const urlDev = "https://localhost:3000/";
const urlProd = "https://www.contoso.com/"; // CHANGE THIS TO YOUR PRODUCTION DEPLOYMENT LOCATION const urlProd = "https://rizlum.ai/fr/"; // CHANGE THIS TO YOUR PRODUCTION DEPLOYMENT LOCATION
async function getHttpsOptions() { async function getHttpsOptions() {
const httpsOptions = await devCerts.getHttpsServerOptions(); const httpsOptions = await devCerts.getHttpsServerOptions();