Skip to content

Commit

Permalink
Merge pull request #190 from Roopan-Microsoft/main
Browse files Browse the repository at this point in the history
build: Merging psl main to dev
  • Loading branch information
Roopan-Microsoft authored Jan 2, 2025
2 parents 9621cf4 + 833a628 commit d84d6e4
Show file tree
Hide file tree
Showing 16 changed files with 198 additions and 55 deletions.
5 changes: 3 additions & 2 deletions .github/workflows/docker-build-and-push.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,5 +57,6 @@ jobs:
- name: Build and push Docker image (Dev/Demo)
if: ${{ github.ref_name == 'dev' || github.ref_name == 'demo' }}
run: |
docker build -t ${{ secrets.ACR_DEV_LOGIN_SERVER }}/webapp:${{ env.TAG }} -f WebApp.Dockerfile .
docker push ${{ secrets.ACR_DEV_LOGIN_SERVER }}/webapp:${{ env.TAG }}
docker build -t ${{ secrets.ACR_LOGIN_SERVER }}/webapp:latest -f WebApp.Dockerfile .
docker push ${{ secrets.ACR_LOGIN_SERVER }}/webapp:latest
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,4 +139,4 @@ You acknowledge that the Software and Microsoft Products and Services (1) are no

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.

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.
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.
2 changes: 1 addition & 1 deletion app.py
Original file line number Diff line number Diff line change
Expand Up @@ -694,7 +694,7 @@ async def rename_conversation():

# update the title
title = request_json.get("title", None)
if not title:
if not title or title.strip() == "":
return jsonify({"error": "title is required"}), 400
conversation["title"] = title
updated_conversation = await cosmos_conversation_client.upsert_conversation(
Expand Down
6 changes: 4 additions & 2 deletions backend/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,8 @@ class _AzureSearchSettings(BaseSettings, DatasourcePayloadConstructor):
_type: Literal["azure_search"] = PrivateAttr(default="azure_search")
top_k: int = Field(default=5, serialization_alias="top_n_documents")
strictness: int = 3
enable_in_domain: bool = Field(default=True, serialization_alias="in_scope")
enable_in_domain: bool = Field(
default=True, serialization_alias="in_scope")
service: str = Field(exclude=True)
endpoint_suffix: str = Field(default="search.windows.net", exclude=True)
index: str = Field(serialization_alias="index_name")
Expand Down Expand Up @@ -308,7 +309,8 @@ def set_query_type(self) -> Self:
def _set_filter_string(self, request: Request) -> str:
if self.permitted_groups_column:
user_token = request.headers.get("X-MS-TOKEN-AAD-ACCESS-TOKEN", "")
logging.debug(f"USER TOKEN is {'present' if user_token else 'not present'}")
logging.debug(
f"USER TOKEN is {'present' if user_token else 'not present'}")
if not user_token:
raise ValueError(
"Document-level access control is enabled, but user access token could not be fetched."
Expand Down
9 changes: 6 additions & 3 deletions backend/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ async def format_as_ndjson(r):
async for event in r:
yield json.dumps(event, cls=JSONEncoder) + "\n"
except Exception as error:
logging.exception("Exception while generating response stream: %s", error)
logging.exception(
"Exception while generating response stream: %s", error)
yield json.dumps({"error": str(error)})


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

r = r.json()
Expand Down Expand Up @@ -128,7 +130,8 @@ def format_stream_response(chatCompletionChunk, history_metadata, apim_request_i
delta = chatCompletionChunk.choices[0].delta
if delta:
if hasattr(delta, "context"):
messageObj = {"role": "tool", "content": json.dumps(delta.context)}
messageObj = {"role": "tool",
"content": json.dumps(delta.context)}
response_obj["choices"][0]["messages"].append(messageObj)
return response_obj
if delta.role == "assistant" and hasattr(delta, "context"):
Expand Down
23 changes: 22 additions & 1 deletion frontend/src/components/ChatHistory/ChatHistoryListItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { AppStateContext } from '../../state/AppProvider'
import { GroupedChatHistory } from './ChatHistoryList'

import styles from './ChatHistoryPanel.module.css'
import _ from 'lodash'

interface ChatHistoryListItemCellProps {
item?: Conversation
Expand Down Expand Up @@ -59,7 +60,8 @@ export const ChatHistoryListItemCell: React.FC<ChatHistoryListItemCellProps> = (
const [errorRename, setErrorRename] = useState<string | undefined>(undefined)
const [textFieldFocused, setTextFieldFocused] = useState(false)
const textFieldRef = useRef<ITextField | null>(null)

const [isButtonDisabled, setIsButtonDisabled] = useState<boolean>(false);

const appStateContext = React.useContext(AppStateContext)
const isSelected = item?.id === appStateContext?.state.currentChat?.id
const dialogContentProps = {
Expand Down Expand Up @@ -94,6 +96,12 @@ export const ChatHistoryListItemCell: React.FC<ChatHistoryListItemCellProps> = (
}
}, [appStateContext?.state.currentChat?.id, item?.id])

useEffect(()=>{
let v = appStateContext?.state.isRequestInitiated;
if(v!=undefined)
setIsButtonDisabled(v && isSelected)
},[appStateContext?.state.isRequestInitiated])

const onDelete = async () => {
const response = await historyDelete(item.id)
if (!response.ok) {
Expand Down Expand Up @@ -125,6 +133,17 @@ export const ChatHistoryListItemCell: React.FC<ChatHistoryListItemCellProps> = (
if (errorRename || renameLoading) {
return
}
if (_.trim(editTitle) === "") {
setErrorRename('Error: Title is required.')
setTimeout(() => {
setErrorRename(undefined)
setTextFieldFocused(true)
if (textFieldRef.current) {
textFieldRef.current.focus()
}
}, 5000)
return
}
if (editTitle == item.title) {
setErrorRename('Error: Enter a new title to proceed.')
setTimeout(() => {
Expand Down Expand Up @@ -255,13 +274,15 @@ export const ChatHistoryListItemCell: React.FC<ChatHistoryListItemCellProps> = (
iconProps={{ iconName: 'Delete' }}
title="Delete"
onClick={toggleDeleteDialog}
disabled={isButtonDisabled}
onKeyDown={e => (e.key === ' ' ? toggleDeleteDialog() : null)}
/>
<IconButton
className={styles.itemButton}
iconProps={{ iconName: 'Edit' }}
title="Edit"
onClick={onEdit}
disabled={isButtonDisabled}
onKeyDown={e => (e.key === ' ' ? onEdit() : null)}
/>
</Stack>
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/components/ChatHistory/ChatHistoryPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export function ChatHistoryPanel(_props: ChatHistoryPanelProps) {
const [hideClearAllDialog, { toggle: toggleClearAllDialog }] = useBoolean(true)
const [clearing, setClearing] = React.useState(false)
const [clearingError, setClearingError] = React.useState(false)

const hasChatHistory = appStateContext?.state.chatHistory && appStateContext.state.chatHistory.length > 0;
const clearAllDialogContentProps = {
type: DialogType.close,
title: !clearingError ? 'Are you sure you want to clear all chat history?' : 'Error deleting all of chat history',
Expand All @@ -67,7 +67,7 @@ export function ChatHistoryPanel(_props: ChatHistoryPanelProps) {
}

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

const handleHistoryClick = () => {
Expand Down
137 changes: 107 additions & 30 deletions frontend/src/components/Sidebar/Sidebar.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
import React, { useEffect, useState, useContext } from 'react'
import { Stack, Text } from '@fluentui/react'
import { Book28Regular, Book32Regular, BookRegular, News28Regular, NewsRegular, Notepad28Regular, Notepad32Regular } from '@fluentui/react-icons'
import {
Book28Regular,
Book32Regular,
BookRegular,
News28Regular,
NewsRegular,
Notepad28Regular,
Notepad32Regular
} from '@fluentui/react-icons'
import { Button, Avatar } from '@fluentui/react-components'
import styles from './Sidebar.module.css'
import { AppStateContext } from '../../state/AppProvider'
import { getUserInfo } from '../../api'
import { useNavigate, useLocation } from 'react-router-dom'


enum NavigationButtonStates {
Active = 'active',
Inactive = 'inactive',
Expand All @@ -28,9 +35,24 @@ const NavigationButton = ({ text, buttonState, onClick }: NavigationButtonProps)
}[buttonState]

const iconElements: { [key: string]: JSX.Element } = {
'Browse': <News28Regular color={fontColor}/>,
'Generate': <Book28Regular color={fontColor}/>,
'Draft': <Notepad28Regular color={fontColor}/>
Browse: (
<News28Regular
color={fontColor}
cursor={buttonState === NavigationButtonStates.Disabled ? 'not-allowed' : 'pointer'}
/>
),
Generate: (
<Book28Regular
color={fontColor}
cursor={buttonState === NavigationButtonStates.Disabled ? 'not-allowed' : 'pointer'}
/>
),
Draft: (
<Notepad28Regular
color={fontColor}
cursor={buttonState === NavigationButtonStates.Disabled ? 'not-allowed' : 'pointer'}
/>
)
}

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

return (
<Stack onClick={buttonState === NavigationButtonStates.Inactive ? onClick : () => {}} className={buttonStyle}>
<Button appearance="transparent"
size="large"
icon={icon}
style={{ padding: '0' }}
/>
<Text style={{ color: fontColor }}>{text}</Text>
<Stack
onClick={buttonState === NavigationButtonStates.Inactive ? onClick : () => {}}
className={buttonStyle}
style={{ cursor: buttonState === NavigationButtonStates.Disabled ? 'not-allowed' : 'pointer' }}>
<Button appearance="transparent" size="large" icon={icon} style={{ padding: '0' }} />
<Text
style={{
color: fontColor,
cursor: buttonState === NavigationButtonStates.Disabled ? 'not-allowed' : 'pointer'
}}>
{text}
</Text>
</Stack>
)
}

const Sidebar = (): JSX.Element => {
const appStateContext = useContext(AppStateContext)
const navigate = useNavigate()
const location = useLocation();
const [name, setName] = useState<string>("")
const location = useLocation()
const [name, setName] = useState<string>('')
useEffect(() => {
if(appStateContext?.state.isRequestInitiated == true){
NavigationButtonStates.Disabled
}
else{
NavigationButtonStates.Active
}
})

useEffect(() => {
if (!appStateContext) { throw new Error('useAppState must be used within a AppStateProvider') }
if (!appStateContext) {
throw new Error('useAppState must be used within a AppStateProvider')
}

if (appStateContext.state.frontendSettings?.auth_enabled) {
getUserInfo().then((res) => {
const name: string = res[0].user_claims.find((claim: any) => claim.typ === 'name')?.val ?? ''
setName(name)
}).catch((err) => {
console.error('Error fetching user info: ', err)
})
getUserInfo()
.then(res => {
const name: string = res[0].user_claims.find((claim: any) => claim.typ === 'name')?.val ?? ''
setName(name)
})
.catch(err => {
console.error('Error fetching user info: ', err)
})
}
}, [])
}, [appStateContext])

// determine url from react-router-dom
// determine url from react-router-dom
const determineView = () => {
const url = location.pathname

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

const currentView = determineView()

// inactive, disabled, active
var draftButtonState = NavigationButtonStates.Disabled
if (appStateContext?.state.draftedDocument) { draftButtonState = currentView === 'draft' ? NavigationButtonStates.Active : NavigationButtonStates.Inactive }
// inactive, disabled, active
var draftButtonState = NavigationButtonStates.Disabled
if (appStateContext?.state.draftedDocument) {
draftButtonState = currentView === 'draft' ? NavigationButtonStates.Active : NavigationButtonStates.Inactive
}
const isGenerating = appStateContext?.state.isRequestInitiated

return (
<Stack className={styles.sidebarContainer}>
<Stack horizontal className={styles.avatarContainer}>
<Avatar color="colorful" name={name} />
</Stack>
<Stack className={styles.sidebarNavigationContainer}>
<NavigationButton text={"Browse"} buttonState={currentView === 'chat' ? NavigationButtonStates.Active : NavigationButtonStates.Inactive} onClick={() => { navigate("/chat") }} />
<NavigationButton text={"Generate"} buttonState={currentView === 'generate' ? NavigationButtonStates.Active : NavigationButtonStates.Inactive} onClick={() => { navigate("/generate") }} />
<NavigationButton text={"Draft"} buttonState={draftButtonState} onClick={() => { navigate("/draft") }} />
<NavigationButton
text={'Browse'}
buttonState={
currentView === 'chat'
? NavigationButtonStates.Active
: appStateContext?.state.isRequestInitiated
? NavigationButtonStates.Disabled
: NavigationButtonStates.Inactive
}
onClick={() => {
if (!isGenerating) {
navigate('/chat')
}
}}
/>
<NavigationButton
text={'Generate'}
buttonState={
currentView === 'generate'
? NavigationButtonStates.Active
: appStateContext?.state.isRequestInitiated
? NavigationButtonStates.Disabled
: NavigationButtonStates.Inactive

}
onClick={() => {
if (!isGenerating) {
navigate('/generate')
}
}}
/>
<NavigationButton
text={'Draft'}
buttonState={draftButtonState}

onClick={() => {
navigate('/draft')
}}
/>
</Stack>
</Stack>
)
Expand Down
Loading

0 comments on commit d84d6e4

Please sign in to comment.