Skip to content

Commit 06b9421

Browse files
Merge pull request #29 from microsoft/adesousa_microsoft/template-generation-update
generate template loading state fix
2 parents 9697b1a + 8f6ac4d commit 06b9421

File tree

7 files changed

+109
-44
lines changed

7 files changed

+109
-44
lines changed

frontend/src/components/DraftCards/SectionCard.tsx

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import React, { useState } from 'react';
1+
import React from 'react'
22
import { Stack } from "@fluentui/react"
3+
import { AppStateContext } from '../../state/AppProvider'
34
import { sectionGenerate, SectionGenerateRequest } from '../../api';
45
import { Section } from '../../api/models'
56
import { Spinner } from "@fluentui/react";
@@ -10,9 +11,7 @@ import { Textarea, makeStyles, Text, Popover, PopoverSurface, PopoverTrigger, Bu
1011

1112

1213
interface SectionCardProps {
13-
section: Section;
14-
onValueChange: (key: number, value: string) => void;
15-
index: number;
14+
sectionIdx: number
1615
}
1716

1817
const useStyles = makeStyles({
@@ -83,34 +82,41 @@ const useStyles = makeStyles({
8382

8483
});
8584

86-
const SectionCard: React.FC<SectionCardProps> = ({ section, onValueChange, index }) => {
85+
const SectionCard = ({ sectionIdx }: SectionCardProps) => {
8786
const classes = useStyles()
8887
const [isLoading, setIsLoading] = React.useState(false)
8988
const [isPopoverOpen, setIsPopoverOpen] = React.useState(false)
90-
const [textareaValue, setTextareaValue] = useState(section.content);
89+
const appStateContext = React.useContext(AppStateContext)
90+
91+
if (!appStateContext) { throw new Error('useAppState must be used within a AppStateProvider') }
92+
93+
const section = appStateContext.state.draftedDocument?.sections[sectionIdx]
94+
if (!section) { throw new Error('Section not found') }
9195

9296
const sectionTitle = section.title
9397
const sectionDescription = section.description
9498
const sectionContent = section.content
9599

96-
const handleChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => {
97-
onValueChange(index, event.target.value);
98-
setTextareaValue(event.target.value);
99-
};
100-
101100
const handleOpenChange: PopoverProps["onOpenChange"] = (e, data) => setIsPopoverOpen(data.open || false);
102101

103102
async function fetchSectionContent(sectionTitle: string, sectionDescription: string) {
104103
setIsLoading(true)
105104
const sectionGenerateRequest: SectionGenerateRequest = { sectionTitle, sectionDescription }
105+
106106
const response = await sectionGenerate(sectionGenerateRequest)
107107
const responseBody = await response.json()
108-
setTextareaValue(responseBody.section_content)
108+
109+
const updatedSection: Section = { title: sectionTitle, description: sectionDescription, content: responseBody.section_content }
110+
appStateContext?.dispatch({ type: 'UPDATE_SECTION', payload: { sectionIdx: sectionIdx, section: updatedSection } })
111+
109112
setIsLoading(false)
110113
}
111114

115+
if (sectionContent === '' && !isLoading) { fetchSectionContent(sectionTitle, sectionDescription) }
116+
112117
return (
113118
<Stack
119+
// add margin bottom unless it's the last section
114120
style={{ marginBottom: '1rem' }}
115121
>
116122
<Stack horizontal horizontalAlign="space-between" verticalAlign="center" style={{ marginBottom: '1rem' }} >
@@ -171,8 +177,12 @@ const SectionCard: React.FC<SectionCardProps> = ({ section, onValueChange, index
171177
appearance="outline"
172178
size="large"
173179
defaultValue={sectionContent}
174-
value={textareaValue}
175-
onChange={handleChange}
180+
181+
onChange={(e, data) => {
182+
const updatedSection: Section = { title: sectionTitle, description: sectionDescription, content: data.value || '' }
183+
appStateContext?.dispatch({ type: 'UPDATE_SECTION', payload: { sectionIdx: sectionIdx, section: updatedSection } })
184+
}}
185+
176186
textarea={{ className: classes.sectionContentTextarea }}
177187
style={{ width: '100%', height: '100%' }}
178188
/>
@@ -183,4 +193,4 @@ const SectionCard: React.FC<SectionCardProps> = ({ section, onValueChange, index
183193
)
184194
}
185195

186-
export default SectionCard
196+
export default SectionCard

frontend/src/components/Sidebar/Sidebar.module.css

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,19 @@
3939
padding: 8px, 8px, 8px, 8px;
4040
}
4141

42+
.navigationButtonDisabled {
43+
justify-content: center;
44+
align-items: center;
45+
border-radius: 8px;
46+
margin: 8px;
47+
48+
padding: 8px, 8px, 8px, 8px;
49+
background: var(--Colors-Alpha-Black-5, #0000000D);
50+
51+
box-shadow: 0px 0px 2px 0px #0000003D;
52+
box-shadow: 0px 2px 4px 0px #00000047;
53+
}
54+
4255
.navigationButton:hover {
4356
background-color: #EDEBE9;
4457
cursor: pointer;

frontend/src/components/Sidebar/Sidebar.tsx

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,24 +7,42 @@ import { AppStateContext } from '../../state/AppProvider'
77
import { getUserInfo } from '../../api'
88
import { useNavigate, useLocation } from 'react-router-dom'
99

10+
11+
enum NavigationButtonStates {
12+
Active = 'active',
13+
Inactive = 'inactive',
14+
Disabled = 'disabled'
15+
}
16+
1017
interface NavigationButtonProps {
1118
text: string
12-
isActive: boolean
19+
buttonState: NavigationButtonStates
1320
onClick: () => void
1421
}
1522

16-
const NavigationButton = ({ text, isActive, onClick }: NavigationButtonProps) => {
17-
const fontColor = isActive ? '#367AF6' : '#BEBBB8'
23+
const NavigationButton = ({ text, buttonState, onClick }: NavigationButtonProps) => {
24+
const fontColor = {
25+
[NavigationButtonStates.Active]: '#367AF6',
26+
[NavigationButtonStates.Inactive]: '#BEBBB8',
27+
[NavigationButtonStates.Disabled]: '#797775'
28+
}[buttonState]
29+
1830
const iconElements: { [key: string]: JSX.Element } = {
1931
'Browse': <News28Regular color={fontColor}/>,
2032
'Generate': <Book28Regular color={fontColor}/>,
2133
'Draft': <Notepad28Regular color={fontColor}/>
2234
}
2335

36+
const buttonStyle = {
37+
[NavigationButtonStates.Active]: styles.navigationButtonActive,
38+
[NavigationButtonStates.Inactive]: styles.navigationButton,
39+
[NavigationButtonStates.Disabled]: styles.navigationButtonDisabled
40+
}[buttonState]
41+
2442
const icon = iconElements[text]
2543

2644
return (
27-
<Stack onClick={onClick} className={isActive ? styles.navigationButtonActive : styles.navigationButton}>
45+
<Stack onClick={buttonState === NavigationButtonStates.Inactive ? onClick : () => {}} className={buttonStyle}>
2846
<Button appearance="transparent"
2947
size="large"
3048
icon={icon}
@@ -65,15 +83,19 @@ const Sidebar = (): JSX.Element => {
6583

6684
const currentView = determineView()
6785

86+
// inactive, disabled, active
87+
var draftButtonState = NavigationButtonStates.Disabled
88+
if (appStateContext?.state.draftedDocument) { draftButtonState = currentView === 'draft' ? NavigationButtonStates.Active : NavigationButtonStates.Inactive }
89+
6890
return (
6991
<Stack className={styles.sidebarContainer}>
7092
<Stack horizontal className={styles.avatarContainer}>
7193
<Avatar color="colorful" name={name} />
7294
</Stack>
7395
<Stack className={styles.sidebarNavigationContainer}>
74-
<NavigationButton text={"Browse"} isActive={currentView === 'chat'} onClick={() => { navigate("/chat") }} />
75-
<NavigationButton text={"Generate"} isActive={currentView === 'generate'} onClick={() => { navigate("/generate") }} />
76-
<NavigationButton text={"Draft"} isActive={currentView === 'draft'} onClick={() => { navigate("/draft") }} />
96+
<NavigationButton text={"Browse"} buttonState={currentView === 'chat' ? NavigationButtonStates.Active : NavigationButtonStates.Inactive} onClick={() => { navigate("/chat") }} />
97+
<NavigationButton text={"Generate"} buttonState={currentView === 'generate' ? NavigationButtonStates.Active : NavigationButtonStates.Inactive} onClick={() => { navigate("/generate") }} />
98+
<NavigationButton text={"Draft"} buttonState={draftButtonState} onClick={() => { navigate("/draft") }} />
7799
</Stack>
78100
</Stack>
79101
)

frontend/src/pages/chat/Chat.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,9 @@ const Chat = ({ type = ChatType.Browse }: Props) => {
147147
const navigate = useNavigate();
148148

149149
const navigateToDraftPage = (parameter: DraftedDocument) => {
150-
navigate('/draft', { state: { parameter } });
150+
// update DraftedDocument in the state
151+
appStateContext?.dispatch({ type: 'UPDATE_DRAFTED_DOCUMENT', payload: parameter });
152+
navigate('/draft');
151153
};
152154

153155
let assistantMessage = {} as ChatMessage
@@ -666,11 +668,9 @@ const Chat = ({ type = ChatType.Browse }: Props) => {
666668
setDraftDocument(draftDocument)
667669
}
668670
}
671+
669672
const generateDocument = async () => {
670673
if (draftDocument !== undefined) {
671-
setIsProcessingTemplate(true)
672-
await getTemplateContent()
673-
setIsProcessingTemplate(false)
674674
navigateToDraftPage(draftDocument);
675675
}
676676
}
@@ -1023,8 +1023,8 @@ const Chat = ({ type = ChatType.Browse }: Props) => {
10231023
background: '#F0F0F0'
10241024
}
10251025
}}
1026-
className={styles.generateDocumentIcon
1027-
}
1026+
1027+
className={styles.generateDocumentIcon}
10281028
iconProps={{ iconName: 'Edit' }}
10291029
onClick={generateDocument} //Update for Document Generation
10301030
disabled={disabledButton()}

frontend/src/pages/draft/Draft.tsx

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,24 @@
1-
import React, { useState } from 'react';
1+
import React, { useState, useContext } from 'react';
22
import styles from './Draft.module.css'
33
import { useLocation } from 'react-router-dom';
44
import TitleCard from '../../components/DraftCards/TitleCard'
55
import SectionCard from '../../components/DraftCards/SectionCard'
66
import { DraftedDocument } from '../../api'
77
import { Document, Packer, Paragraph, TextRun } from 'docx';
88
import { saveAs } from 'file-saver';
9+
import { AppStateContext } from "../../state/AppProvider";
910

10-
const Draft = (): JSX.Element => {
1111

12+
const Draft = (): JSX.Element => {
13+
const appStateContext = useContext(AppStateContext)
1214
const location = useLocation();
13-
const { parameter } = location.state as { parameter: DraftedDocument };
14-
const [sections, setSections] = useState(parameter.sections);
15+
const { generateContentOnLoad } = location.state as { generateContentOnLoad: Boolean };
1516
const [title, setTitle] = useState('');
1617

18+
// get draftedDocument from context
19+
const draftedDocument = appStateContext?.state.draftedDocument;
20+
const sections = draftedDocument?.sections ?? [];
21+
1722
const exportToWord = () => {
1823
const doc = new Document({
1924
sections: [
@@ -64,13 +69,6 @@ const Draft = (): JSX.Element => {
6469
});
6570
};
6671

67-
const handleValueChange = (key: number, value: string) => {
68-
const updatedSections = sections.map((section, index) =>
69-
index === key ? { ...section, content: value } : section
70-
);
71-
setSections(updatedSections);
72-
};
73-
7472
const handleTitleChange = (newTitle: string) => {
7573
setTitle(newTitle);
7674
};
@@ -83,10 +81,7 @@ const Draft = (): JSX.Element => {
8381
</div>
8482

8583
<TitleCard onTitleChange={handleTitleChange} />
86-
87-
{sections.map((section, index) => (
88-
<SectionCard key={index} section={section} onValueChange={handleValueChange} index={index} />
89-
))}
84+
{(sections ?? []).map((_, index) => (<SectionCard key={index} sectionIdx={index} />))}
9085
</div>
9186
)
9287
}

frontend/src/state/AppProvider.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
Conversation,
77
CosmosDBHealth,
88
CosmosDBStatus,
9+
DraftedDocument,
910
Section,
1011
Feedback,
1112
FrontendSettings,
@@ -25,6 +26,7 @@ export interface AppState {
2526
currentChat: Conversation | null
2627
frontendSettings: FrontendSettings | null
2728
feedbackState: { [answerId: string]: Feedback.Neutral | Feedback.Positive | Feedback.Negative }
29+
draftedDocument: DraftedDocument | null
2830
}
2931

3032
export type Action =
@@ -46,6 +48,7 @@ export type Action =
4648
}
4749
| { type: 'GET_FEEDBACK_STATE'; payload: string }
4850
| { type: 'UPDATE_SECTION'; payload: { sectionIdx: number; section: Section } }
51+
| { type: 'UPDATE_DRAFTED_DOCUMENT'; payload: DraftedDocument }
4952

5053
const initialState: AppState = {
5154
isChatHistoryOpen: false,
@@ -58,7 +61,8 @@ const initialState: AppState = {
5861
status: CosmosDBStatus.NotConfigured
5962
},
6063
frontendSettings: null,
61-
feedbackState: {}
64+
feedbackState: {},
65+
draftedDocument: null
6266
}
6367

6468
export const AppStateContext = createContext<

frontend/src/state/AppReducer.tsx

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,27 @@ export const appStateReducer = (state: AppState, action: Action): AppState => {
7474
[action.payload.answerId]: action.payload.feedback
7575
}
7676
}
77+
case 'UPDATE_SECTION':
78+
const sectionIdx = action.payload.sectionIdx
79+
80+
if (!state.draftedDocument?.sections || sectionIdx >= state.draftedDocument.sections.length) {
81+
console.error('Section not found')
82+
return state
83+
}
84+
85+
// create new sections list
86+
const updatedSections = [...state.draftedDocument.sections]
87+
updatedSections[sectionIdx] = action.payload.section
88+
89+
return {
90+
...state,
91+
draftedDocument: {
92+
...state.draftedDocument,
93+
sections: updatedSections
94+
}
95+
}
96+
case 'UPDATE_DRAFTED_DOCUMENT':
97+
return { ...state, draftedDocument: action.payload }
7798
default:
7899
return state
79100
}

0 commit comments

Comments
 (0)