Skip to content

Commit d84d6e4

Browse files
Merge pull request microsoft#190 from Roopan-Microsoft/main
build: Merging psl main to dev
2 parents 9621cf4 + 833a628 commit d84d6e4

File tree

16 files changed

+198
-55
lines changed

16 files changed

+198
-55
lines changed

.github/workflows/docker-build-and-push.yml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,5 +57,6 @@ jobs:
5757
- name: Build and push Docker image (Dev/Demo)
5858
if: ${{ github.ref_name == 'dev' || github.ref_name == 'demo' }}
5959
run: |
60-
docker build -t ${{ secrets.ACR_DEV_LOGIN_SERVER }}/webapp:${{ env.TAG }} -f WebApp.Dockerfile .
61-
docker push ${{ secrets.ACR_DEV_LOGIN_SERVER }}/webapp:${{ env.TAG }}
60+
docker build -t ${{ secrets.ACR_LOGIN_SERVER }}/webapp:latest -f WebApp.Dockerfile .
61+
docker push ${{ secrets.ACR_LOGIN_SERVER }}/webapp:latest
62+

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,4 +139,4 @@ You acknowledge that the Software and Microsoft Products and Services (1) are no
139139

140140
You acknowledge the Software is not subject to SOC 1 and SOC 2 compliance audits. No Microsoft technology, nor any of its component technologies, including the Software, is intended or made available as a substitute for the professional advice, opinion, or judgement of a certified financial services professional. Do not use the Software to replace, substitute, or provide professional financial advice or judgment.
141141

142-
BY ACCESSING OR USING THE SOFTWARE, YOU ACKNOWLEDGE THAT THE SOFTWARE IS NOT DESIGNED OR INTENDED TO SUPPORT ANY USE IN WHICH A SERVICE INTERRUPTION, DEFECT, ERROR, OR OTHER FAILURE OF THE SOFTWARE COULD RESULT IN THE DEATH OR SERIOUS BODILY INJURY OF ANY PERSON OR IN PHYSICAL OR ENVIRONMENTAL DAMAGE (COLLECTIVELY, “HIGH-RISK USE”), AND THAT YOU WILL ENSURE THAT, IN THE EVENT OF ANY INTERRUPTION, DEFECT, ERROR, OR OTHER FAILURE OF THE SOFTWARE, THE SAFETY OF PEOPLE, PROPERTY, AND THE ENVIRONMENT ARE NOT REDUCED BELOW A LEVEL THAT IS REASONABLY, APPROPRIATE, AND LEGAL, WHETHER IN GENERAL OR IN A SPECIFIC INDUSTRY. BY ACCESSING THE SOFTWARE, YOU FURTHER ACKNOWLEDGE THAT YOUR HIGH-RISK USE OF THE SOFTWARE IS AT YOUR OWN RISK.
142+
BY ACCESSING OR USING THE SOFTWARE, YOU ACKNOWLEDGE THAT THE SOFTWARE IS NOT DESIGNED OR INTENDED TO SUPPORT ANY USE IN WHICH A SERVICE INTERRUPTION, DEFECT, ERROR, OR OTHER FAILURE OF THE SOFTWARE COULD RESULT IN THE DEATH OR SERIOUS BODILY INJURY OF ANY PERSON OR IN PHYSICAL OR ENVIRONMENTAL DAMAGE (COLLECTIVELY, “HIGH-RISK USE”), AND THAT YOU WILL ENSURE THAT, IN THE EVENT OF ANY INTERRUPTION, DEFECT, ERROR, OR OTHER FAILURE OF THE SOFTWARE, THE SAFETY OF PEOPLE, PROPERTY, AND THE ENVIRONMENT ARE NOT REDUCED BELOW A LEVEL THAT IS REASONABLY, APPROPRIATE, AND LEGAL, WHETHER IN GENERAL OR IN A SPECIFIC INDUSTRY. BY ACCESSING THE SOFTWARE, YOU FURTHER ACKNOWLEDGE THAT YOUR HIGH-RISK USE OF THE SOFTWARE IS AT YOUR OWN RISK.

app.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -694,7 +694,7 @@ async def rename_conversation():
694694

695695
# update the title
696696
title = request_json.get("title", None)
697-
if not title:
697+
if not title or title.strip() == "":
698698
return jsonify({"error": "title is required"}), 400
699699
conversation["title"] = title
700700
updated_conversation = await cosmos_conversation_client.upsert_conversation(

backend/settings.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,8 @@ class _AzureSearchSettings(BaseSettings, DatasourcePayloadConstructor):
236236
_type: Literal["azure_search"] = PrivateAttr(default="azure_search")
237237
top_k: int = Field(default=5, serialization_alias="top_n_documents")
238238
strictness: int = 3
239-
enable_in_domain: bool = Field(default=True, serialization_alias="in_scope")
239+
enable_in_domain: bool = Field(
240+
default=True, serialization_alias="in_scope")
240241
service: str = Field(exclude=True)
241242
endpoint_suffix: str = Field(default="search.windows.net", exclude=True)
242243
index: str = Field(serialization_alias="index_name")
@@ -308,7 +309,8 @@ def set_query_type(self) -> Self:
308309
def _set_filter_string(self, request: Request) -> str:
309310
if self.permitted_groups_column:
310311
user_token = request.headers.get("X-MS-TOKEN-AAD-ACCESS-TOKEN", "")
311-
logging.debug(f"USER TOKEN is {'present' if user_token else 'not present'}")
312+
logging.debug(
313+
f"USER TOKEN is {'present' if user_token else 'not present'}")
312314
if not user_token:
313315
raise ValueError(
314316
"Document-level access control is enabled, but user access token could not be fetched."

backend/utils.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ async def format_as_ndjson(r):
3333
async for event in r:
3434
yield json.dumps(event, cls=JSONEncoder) + "\n"
3535
except Exception as error:
36-
logging.exception("Exception while generating response stream: %s", error)
36+
logging.exception(
37+
"Exception while generating response stream: %s", error)
3738
yield json.dumps({"error": str(error)})
3839

3940

@@ -55,7 +56,8 @@ def fetchUserGroups(userToken, nextLink=None):
5556
try:
5657
r = requests.get(endpoint, headers=headers)
5758
if r.status_code != 200:
58-
logging.error(f"Error fetching user groups: {r.status_code} {r.text}")
59+
logging.error(
60+
f"Error fetching user groups: {r.status_code} {r.text}")
5961
return []
6062

6163
r = r.json()
@@ -128,7 +130,8 @@ def format_stream_response(chatCompletionChunk, history_metadata, apim_request_i
128130
delta = chatCompletionChunk.choices[0].delta
129131
if delta:
130132
if hasattr(delta, "context"):
131-
messageObj = {"role": "tool", "content": json.dumps(delta.context)}
133+
messageObj = {"role": "tool",
134+
"content": json.dumps(delta.context)}
132135
response_obj["choices"][0]["messages"].append(messageObj)
133136
return response_obj
134137
if delta.role == "assistant" and hasattr(delta, "context"):

frontend/src/components/ChatHistory/ChatHistoryListItem.tsx

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import { AppStateContext } from '../../state/AppProvider'
2525
import { GroupedChatHistory } from './ChatHistoryList'
2626

2727
import styles from './ChatHistoryPanel.module.css'
28+
import _ from 'lodash'
2829

2930
interface ChatHistoryListItemCellProps {
3031
item?: Conversation
@@ -59,7 +60,8 @@ export const ChatHistoryListItemCell: React.FC<ChatHistoryListItemCellProps> = (
5960
const [errorRename, setErrorRename] = useState<string | undefined>(undefined)
6061
const [textFieldFocused, setTextFieldFocused] = useState(false)
6162
const textFieldRef = useRef<ITextField | null>(null)
62-
63+
const [isButtonDisabled, setIsButtonDisabled] = useState<boolean>(false);
64+
6365
const appStateContext = React.useContext(AppStateContext)
6466
const isSelected = item?.id === appStateContext?.state.currentChat?.id
6567
const dialogContentProps = {
@@ -94,6 +96,12 @@ export const ChatHistoryListItemCell: React.FC<ChatHistoryListItemCellProps> = (
9496
}
9597
}, [appStateContext?.state.currentChat?.id, item?.id])
9698

99+
useEffect(()=>{
100+
let v = appStateContext?.state.isRequestInitiated;
101+
if(v!=undefined)
102+
setIsButtonDisabled(v && isSelected)
103+
},[appStateContext?.state.isRequestInitiated])
104+
97105
const onDelete = async () => {
98106
const response = await historyDelete(item.id)
99107
if (!response.ok) {
@@ -125,6 +133,17 @@ export const ChatHistoryListItemCell: React.FC<ChatHistoryListItemCellProps> = (
125133
if (errorRename || renameLoading) {
126134
return
127135
}
136+
if (_.trim(editTitle) === "") {
137+
setErrorRename('Error: Title is required.')
138+
setTimeout(() => {
139+
setErrorRename(undefined)
140+
setTextFieldFocused(true)
141+
if (textFieldRef.current) {
142+
textFieldRef.current.focus()
143+
}
144+
}, 5000)
145+
return
146+
}
128147
if (editTitle == item.title) {
129148
setErrorRename('Error: Enter a new title to proceed.')
130149
setTimeout(() => {
@@ -255,13 +274,15 @@ export const ChatHistoryListItemCell: React.FC<ChatHistoryListItemCellProps> = (
255274
iconProps={{ iconName: 'Delete' }}
256275
title="Delete"
257276
onClick={toggleDeleteDialog}
277+
disabled={isButtonDisabled}
258278
onKeyDown={e => (e.key === ' ' ? toggleDeleteDialog() : null)}
259279
/>
260280
<IconButton
261281
className={styles.itemButton}
262282
iconProps={{ iconName: 'Edit' }}
263283
title="Edit"
264284
onClick={onEdit}
285+
disabled={isButtonDisabled}
265286
onKeyDown={e => (e.key === ' ' ? onEdit() : null)}
266287
/>
267288
</Stack>

frontend/src/components/ChatHistory/ChatHistoryPanel.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ export function ChatHistoryPanel(_props: ChatHistoryPanelProps) {
4949
const [hideClearAllDialog, { toggle: toggleClearAllDialog }] = useBoolean(true)
5050
const [clearing, setClearing] = React.useState(false)
5151
const [clearingError, setClearingError] = React.useState(false)
52-
52+
const hasChatHistory = appStateContext?.state.chatHistory && appStateContext.state.chatHistory.length > 0;
5353
const clearAllDialogContentProps = {
5454
type: DialogType.close,
5555
title: !clearingError ? 'Are you sure you want to clear all chat history?' : 'Error deleting all of chat history',
@@ -67,7 +67,7 @@ export function ChatHistoryPanel(_props: ChatHistoryPanelProps) {
6767
}
6868

6969
const menuItems: IContextualMenuItem[] = [
70-
{ key: 'clearAll', text: 'Clear all chat history', iconProps: { iconName: 'Delete' } }
70+
{ key: 'clearAll', text: 'Clear all chat history',disabled: !hasChatHistory, iconProps: { iconName: 'Delete' }}
7171
]
7272

7373
const handleHistoryClick = () => {

frontend/src/components/Sidebar/Sidebar.tsx

Lines changed: 107 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,20 @@
11
import React, { useEffect, useState, useContext } from 'react'
22
import { Stack, Text } from '@fluentui/react'
3-
import { Book28Regular, Book32Regular, BookRegular, News28Regular, NewsRegular, Notepad28Regular, Notepad32Regular } from '@fluentui/react-icons'
3+
import {
4+
Book28Regular,
5+
Book32Regular,
6+
BookRegular,
7+
News28Regular,
8+
NewsRegular,
9+
Notepad28Regular,
10+
Notepad32Regular
11+
} from '@fluentui/react-icons'
412
import { Button, Avatar } from '@fluentui/react-components'
513
import styles from './Sidebar.module.css'
614
import { AppStateContext } from '../../state/AppProvider'
715
import { getUserInfo } from '../../api'
816
import { useNavigate, useLocation } from 'react-router-dom'
917

10-
1118
enum NavigationButtonStates {
1219
Active = 'active',
1320
Inactive = 'inactive',
@@ -28,9 +35,24 @@ const NavigationButton = ({ text, buttonState, onClick }: NavigationButtonProps)
2835
}[buttonState]
2936

3037
const iconElements: { [key: string]: JSX.Element } = {
31-
'Browse': <News28Regular color={fontColor}/>,
32-
'Generate': <Book28Regular color={fontColor}/>,
33-
'Draft': <Notepad28Regular color={fontColor}/>
38+
Browse: (
39+
<News28Regular
40+
color={fontColor}
41+
cursor={buttonState === NavigationButtonStates.Disabled ? 'not-allowed' : 'pointer'}
42+
/>
43+
),
44+
Generate: (
45+
<Book28Regular
46+
color={fontColor}
47+
cursor={buttonState === NavigationButtonStates.Disabled ? 'not-allowed' : 'pointer'}
48+
/>
49+
),
50+
Draft: (
51+
<Notepad28Regular
52+
color={fontColor}
53+
cursor={buttonState === NavigationButtonStates.Disabled ? 'not-allowed' : 'pointer'}
54+
/>
55+
)
3456
}
3557

3658
const buttonStyle = {
@@ -42,37 +64,54 @@ const NavigationButton = ({ text, buttonState, onClick }: NavigationButtonProps)
4264
const icon = iconElements[text]
4365

4466
return (
45-
<Stack onClick={buttonState === NavigationButtonStates.Inactive ? onClick : () => {}} className={buttonStyle}>
46-
<Button appearance="transparent"
47-
size="large"
48-
icon={icon}
49-
style={{ padding: '0' }}
50-
/>
51-
<Text style={{ color: fontColor }}>{text}</Text>
67+
<Stack
68+
onClick={buttonState === NavigationButtonStates.Inactive ? onClick : () => {}}
69+
className={buttonStyle}
70+
style={{ cursor: buttonState === NavigationButtonStates.Disabled ? 'not-allowed' : 'pointer' }}>
71+
<Button appearance="transparent" size="large" icon={icon} style={{ padding: '0' }} />
72+
<Text
73+
style={{
74+
color: fontColor,
75+
cursor: buttonState === NavigationButtonStates.Disabled ? 'not-allowed' : 'pointer'
76+
}}>
77+
{text}
78+
</Text>
5279
</Stack>
5380
)
5481
}
5582

5683
const Sidebar = (): JSX.Element => {
5784
const appStateContext = useContext(AppStateContext)
5885
const navigate = useNavigate()
59-
const location = useLocation();
60-
const [name, setName] = useState<string>("")
86+
const location = useLocation()
87+
const [name, setName] = useState<string>('')
88+
useEffect(() => {
89+
if(appStateContext?.state.isRequestInitiated == true){
90+
NavigationButtonStates.Disabled
91+
}
92+
else{
93+
NavigationButtonStates.Active
94+
}
95+
})
6196

6297
useEffect(() => {
63-
if (!appStateContext) { throw new Error('useAppState must be used within a AppStateProvider') }
98+
if (!appStateContext) {
99+
throw new Error('useAppState must be used within a AppStateProvider')
100+
}
64101

65102
if (appStateContext.state.frontendSettings?.auth_enabled) {
66-
getUserInfo().then((res) => {
67-
const name: string = res[0].user_claims.find((claim: any) => claim.typ === 'name')?.val ?? ''
68-
setName(name)
69-
}).catch((err) => {
70-
console.error('Error fetching user info: ', err)
71-
})
103+
getUserInfo()
104+
.then(res => {
105+
const name: string = res[0].user_claims.find((claim: any) => claim.typ === 'name')?.val ?? ''
106+
setName(name)
107+
})
108+
.catch(err => {
109+
console.error('Error fetching user info: ', err)
110+
})
72111
}
73-
}, [])
112+
}, [appStateContext])
74113

75-
// determine url from react-router-dom
114+
// determine url from react-router-dom
76115
const determineView = () => {
77116
const url = location.pathname
78117

@@ -82,20 +121,58 @@ const Sidebar = (): JSX.Element => {
82121
}
83122

84123
const currentView = determineView()
85-
86-
// inactive, disabled, active
87-
var draftButtonState = NavigationButtonStates.Disabled
88-
if (appStateContext?.state.draftedDocument) { draftButtonState = currentView === 'draft' ? NavigationButtonStates.Active : NavigationButtonStates.Inactive }
124+
// inactive, disabled, active
125+
var draftButtonState = NavigationButtonStates.Disabled
126+
if (appStateContext?.state.draftedDocument) {
127+
draftButtonState = currentView === 'draft' ? NavigationButtonStates.Active : NavigationButtonStates.Inactive
128+
}
129+
const isGenerating = appStateContext?.state.isRequestInitiated
89130

90131
return (
91132
<Stack className={styles.sidebarContainer}>
92133
<Stack horizontal className={styles.avatarContainer}>
93134
<Avatar color="colorful" name={name} />
94135
</Stack>
95136
<Stack className={styles.sidebarNavigationContainer}>
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") }} />
137+
<NavigationButton
138+
text={'Browse'}
139+
buttonState={
140+
currentView === 'chat'
141+
? NavigationButtonStates.Active
142+
: appStateContext?.state.isRequestInitiated
143+
? NavigationButtonStates.Disabled
144+
: NavigationButtonStates.Inactive
145+
}
146+
onClick={() => {
147+
if (!isGenerating) {
148+
navigate('/chat')
149+
}
150+
}}
151+
/>
152+
<NavigationButton
153+
text={'Generate'}
154+
buttonState={
155+
currentView === 'generate'
156+
? NavigationButtonStates.Active
157+
: appStateContext?.state.isRequestInitiated
158+
? NavigationButtonStates.Disabled
159+
: NavigationButtonStates.Inactive
160+
161+
}
162+
onClick={() => {
163+
if (!isGenerating) {
164+
navigate('/generate')
165+
}
166+
}}
167+
/>
168+
<NavigationButton
169+
text={'Draft'}
170+
buttonState={draftButtonState}
171+
172+
onClick={() => {
173+
navigate('/draft')
174+
}}
175+
/>
99176
</Stack>
100177
</Stack>
101178
)

0 commit comments

Comments
 (0)