diff --git a/.github/workflows/CAdeploy.yml b/.github/workflows/CAdeploy.yml new file mode 100644 index 00000000..9dc156ed --- /dev/null +++ b/.github/workflows/CAdeploy.yml @@ -0,0 +1,128 @@ +name: CI-Validate Deployment-Client Advisor + +on: + push: + branches: + - main + paths: + - 'ClientAdvisor/**' + +jobs: + deploy: + runs-on: ubuntu-latest + + steps: + - name: Checkout Code + uses: actions/checkout@v3 + + - name: Setup Azure CLI + run: | + curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash + az --version # Verify installation + + - name: Login to Azure + run: | + az login --service-principal -u ${{ secrets.AZURE_CLIENT_ID }} -p ${{ secrets.AZURE_CLIENT_SECRET }} --tenant ${{ secrets.AZURE_TENANT_ID }} + + - name: Install Bicep CLI + run: az bicep install + + - name: Generate Resource Group Name + id: generate_rg_name + run: | + echo "Generating a unique resource group name..." + TIMESTAMP=$(date +%Y%m%d%H%M%S) + COMMON_PART="pslautomationCli" + UNIQUE_RG_NAME="${COMMON_PART}${TIMESTAMP}" + echo "RESOURCE_GROUP_NAME=${UNIQUE_RG_NAME}" >> $GITHUB_ENV + echo "Generated RESOURCE_GROUP_PREFIX: ${UNIQUE_RG_NAME}" + + - name: Check and Create Resource Group + id: check_create_rg + run: | + echo "RESOURCE_GROUP: ${{ env.RESOURCE_GROUP_NAME }}" + set -e + echo "Checking if resource group exists..." + rg_exists=$(az group exists --name ${{ env.RESOURCE_GROUP_NAME }}) + if [ "$rg_exists" = "false" ]; then + echo "Resource group does not exist. Creating..." + az group create --name ${{ env.RESOURCE_GROUP_NAME }} --location uksouth || { echo "Error creating resource group"; exit 1; } + else + echo "Resource group already exists." + fi + + - name: Generate Unique Solution Prefix + id: generate_solution_prefix + run: | + set -e + COMMON_PART="pslc" + TIMESTAMP=$(date +%s) + UPDATED_TIMESTAMP=$(echo $TIMESTAMP | tail -c 3) + UNIQUE_SOLUTION_PREFIX="${COMMON_PART}${UPDATED_TIMESTAMP}" + echo "SOLUTION_PREFIX=${UNIQUE_SOLUTION_PREFIX}" >> $GITHUB_ENV + echo "Generated SOLUTION_PREFIX: ${UNIQUE_SOLUTION_PREFIX}" + + - name: Deploy Bicep Template + id: deploy + run: | + set -e + az deployment group create \ + --resource-group ${{ env.RESOURCE_GROUP_NAME }} \ + --template-file ClientAdvisor/Deployment/bicep/main.bicep \ + --parameters solutionPrefix=${{ env.SOLUTION_PREFIX }} cosmosLocation=eastus2 + + - name: Update PowerBI URL + if: success() + run: | + set -e + + COMMON_PART="-app-service" + application_name="${{ env.SOLUTION_PREFIX }}${COMMON_PART}" + echo "Updating application: $application_name" + + # Log the Power BI URL being set + echo "Setting Power BI URL: ${{ vars.VITE_POWERBI_EMBED_URL }}" + + # Update the application settings + az webapp config appsettings set --name "$application_name" --resource-group "${{ env.RESOURCE_GROUP_NAME }}" --settings VITE_POWERBI_EMBED_URL="${{ vars.VITE_POWERBI_EMBED_URL }}" + + # Restart the web app + az webapp restart --resource-group "${{ env.RESOURCE_GROUP_NAME }}" --name "$application_name" + + echo "Power BI URL updated successfully for application: $application_name." + + - name: Delete Bicep Deployment + if: success() + run: | + set -e + echo "Checking if resource group exists..." + rg_exists=$(az group exists --name ${{ env.RESOURCE_GROUP_NAME }}) + if [ "$rg_exists" = "true" ]; then + echo "Resource group exist. Cleaning..." + az group delete \ + --name ${{ env.RESOURCE_GROUP_NAME }} \ + --yes \ + --no-wait + echo "Resource group deleted... ${{ env.RESOURCE_GROUP_NAME }}" + else + echo "Resource group does not exists." + fi + + - name: Send Notification on Failure + if: failure() + run: | + RUN_URL="https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" + + # Construct the email body + EMAIL_BODY=$(cat <Dear Team,

We would like to inform you that the Client Advisor Automation process has encountered an issue and has failed to complete successfully.

Build URL: ${RUN_URL}
${OUTPUT}

Please investigate the matter at your earliest convenience.

Best regards,
Your Automation Team

" + } + EOF + ) + + # Send the notification + curl -X POST "${{ secrets.LOGIC_APP_URL }}" \ + -H "Content-Type: application/json" \ + -d "$EMAIL_BODY" || echo "Failed to send notification" + diff --git a/.github/workflows/RAdeploy.yml b/.github/workflows/RAdeploy.yml new file mode 100644 index 00000000..61bdf0e7 --- /dev/null +++ b/.github/workflows/RAdeploy.yml @@ -0,0 +1,105 @@ +name: CI-Validate Deployment-Research Assistant + +on: + push: + branches: + - main + paths: + - 'ResearchAssistant/**' + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - name: Checkout Code + uses: actions/checkout@v3 + + - name: Setup Azure CLI + run: | + curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash + az --version # Verify installation + + - name: Login to Azure + run: | + az login --service-principal -u ${{ secrets.AZURE_CLIENT_ID }} -p ${{ secrets.AZURE_CLIENT_SECRET }} --tenant ${{ secrets.AZURE_TENANT_ID }} + + - name: Install Bicep CLI + run: az bicep install + + - name: Generate Resource Group Name + id: generate_rg_name + run: | + echo "Generating a unique resource group name..." + TIMESTAMP=$(date +%Y%m%d%H%M%S) + COMMON_PART="pslautomationRes" + UNIQUE_RG_NAME="${COMMON_PART}${TIMESTAMP}" + echo "RESOURCE_GROUP_NAME=${UNIQUE_RG_NAME}" >> $GITHUB_ENV + echo "Generated Resource_GROUP_PREFIX: ${UNIQUE_RG_NAME}" + + - name: Check and Create Resource Group + id: check_create_rg + run: | + set -e + echo "Checking if resource group exists..." + rg_exists=$(az group exists --name ${{ env.RESOURCE_GROUP_NAME }}) + if [ "$rg_exists" = "false" ]; then + echo "Resource group does not exist. Creating..." + az group create --name ${{ env.RESOURCE_GROUP_NAME }} --location eastus2 || { echo "Error creating resource group"; exit 1; } + else + echo "Resource group already exists." + fi + + - name: Generate Unique Solution Prefix + id: generate_solution_prefix + run: | + set -e + COMMON_PART="pslr" + TIMESTAMP=$(date +%s) + UPDATED_TIMESTAMP=$(echo $TIMESTAMP | tail -c 3) + UNIQUE_SOLUTION_PREFIX="${COMMON_PART}${UPDATED_TIMESTAMP}" + echo "SOLUTION_PREFIX=${UNIQUE_SOLUTION_PREFIX}" >> $GITHUB_ENV + echo "Generated SOLUTION_PREFIX: ${UNIQUE_SOLUTION_PREFIX}" + + - name: Deploy Bicep Template + id: deploy + run: | + set -e + az deployment group create \ + --resource-group ${{ env.RESOURCE_GROUP_NAME }} \ + --template-file ResearchAssistant/Deployment/bicep/main.bicep \ + --parameters solutionPrefix=${{ env.SOLUTION_PREFIX }} + + - name: Delete Bicep Deployment + if: success() + run: | + set -e + echo "Checking if resource group exists..." + rg_exists=$(az group exists --name ${{ env.RESOURCE_GROUP_NAME }}) + if [ "$rg_exists" = "true" ]; then + echo "Resource group exist. Cleaning..." + az group delete \ + --name ${{ env.RESOURCE_GROUP_NAME }} \ + --yes \ + --no-wait + echo "Resource group deleted... ${{ env.RESOURCE_GROUP_NAME }}" + else + echo "Resource group does not exists." + fi + + - name: Send Notification on Failure + if: failure() + run: | + RUN_URL="https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" + + # Construct the email body + EMAIL_BODY=$(cat <Dear Team,

We would like to inform you that the Research Assistant Automation process has encountered an issue and has failed to complete successfully.

Build URL: ${RUN_URL}
${OUTPUT}

Please investigate the matter at your earliest convenience.

Best regards,
Your Automation Team

" + } + EOF + ) + + # Send the notification + curl -X POST "${{ secrets.LOGIC_APP_URL }}" \ + -H "Content-Type: application/json" \ + -d "$EMAIL_BODY" || echo "Failed to send notification" \ No newline at end of file diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 00000000..5f6ba622 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,94 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL Advanced" + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + schedule: + - cron: '22 13 * * 0' + +jobs: + analyze: + name: Analyze (${{ matrix.language }}) + # Runner size impacts CodeQL analysis time. To learn more, please see: + # - https://gh.io/recommended-hardware-resources-for-running-codeql + # - https://gh.io/supported-runners-and-hardware-resources + # - https://gh.io/using-larger-runners (GitHub.com only) + # Consider using larger runners or machines with greater resources for possible analysis time improvements. + runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} + permissions: + # required for all workflows + security-events: write + + # required to fetch internal or private CodeQL packs + packages: read + + # only required for workflows in private repositories + actions: read + contents: read + + strategy: + fail-fast: false + matrix: + include: + - language: javascript-typescript + build-mode: none + - language: python + build-mode: none + # CodeQL supports the following values keywords for 'language': 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift' + # Use `c-cpp` to analyze code written in C, C++ or both + # Use 'java-kotlin' to analyze code written in Java, Kotlin or both + # Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both + # To learn more about changing the languages that are analyzed or customizing the build mode for your analysis, + # see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning. + # If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how + # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: ${{ matrix.language }} + build-mode: ${{ matrix.build-mode }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + + # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + # queries: security-extended,security-and-quality + + # If the analyze step fails for one of the languages you are analyzing with + # "We were unable to automatically build your code", modify the matrix above + # to set the build mode to "manual" for that language. Then modify this step + # to build your code. + # ℹ️ Command-line programs to run using the OS shell. + # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun + - if: matrix.build-mode == 'manual' + shell: bash + run: | + echo 'If you are using a "manual" build mode for one or more of the' \ + 'languages you are analyzing, replace this with the commands to build' \ + 'your code, for example:' + echo ' make bootstrap' + echo ' make release' + exit 1 + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 + with: + category: "/language:${{matrix.language}}" diff --git a/.github/workflows/pylint.yml b/.github/workflows/pylint.yml new file mode 100644 index 00000000..989f7387 --- /dev/null +++ b/.github/workflows/pylint.yml @@ -0,0 +1,22 @@ +name: Pylint + +on: [push] + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.11"] + steps: + - uses: actions/checkout@v4 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v3 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r ClientAdvisor/App/requirements.txt + - name: Run flake8 + run: flake8 --config=ClientAdvisor/App/.flake8 ClientAdvisor/App diff --git a/.github/workflows/test_client_advisor.yml b/.github/workflows/test_client_advisor.yml new file mode 100644 index 00000000..f9a29716 --- /dev/null +++ b/.github/workflows/test_client_advisor.yml @@ -0,0 +1,55 @@ +name: Tests + +on: + push: + branches: main + # Trigger on changes in these specific paths + paths: + - 'ClientAdvisor/**' + pull_request: + branches: main + types: + - opened + - ready_for_review + - reopened + - synchronize + paths: + - 'ClientAdvisor/**' + +jobs: + test_client_advisor: + + name: Client Advisor Tests + runs-on: ubuntu-latest + # The if condition ensures that this job only runs if changes are in the ClientAdvisor folder + + steps: + - uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + - name: Install Backend Dependencies + run: | + cd ClientAdvisor/App + python -m pip install -r requirements.txt + python -m pip install coverage pytest-cov + + - name: Set up Node.js + uses: actions/setup-node@v3 + with: + node-version: '20' + - name: Install Frontend Dependencies + run: | + cd ClientAdvisor/App/frontend + npm install + - name: Run Frontend Tests with Coverage + run: | + cd ClientAdvisor/App/frontend + npm run test -- --coverage + - uses: actions/upload-artifact@v4 + with: + name: client-advisor-frontend-coverage + path: | + ClientAdvisor/App/frontend/coverage/ + ClientAdvisor/App/frontend/coverage/lcov-report/ \ No newline at end of file diff --git a/.github/workflows/test_research_assistant.yml b/.github/workflows/test_research_assistant.yml new file mode 100644 index 00000000..ec31819b --- /dev/null +++ b/.github/workflows/test_research_assistant.yml @@ -0,0 +1,54 @@ +name: Tests + +on: + push: + branches: main + # Trigger on changes in these specific paths + paths: + - 'ResearchAssistant/**' + pull_request: + branches: main + types: + - opened + - ready_for_review + - reopened + - synchronize + paths: + - 'ResearchAssistant/**' + +jobs: + test_research_assistant: + name: Research Assistant Tests + runs-on: ubuntu-latest + # The if condition ensures that this job only runs if changes are in the ResearchAssistant folder + + steps: + - uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + - name: Install Backend Dependencies + run: | + cd ResearchAssistant/App + python -m pip install -r requirements.txt + python -m pip install coverage pytest-cov + + - name: Set up Node.js + uses: actions/setup-node@v3 + with: + node-version: '20' + - name: Install Frontend Dependencies + run: | + cd ResearchAssistant/App/frontend + npm install + - name: Run Frontend Tests with Coverage + run: | + cd ResearchAssistant/App/frontend + npm run test -- --coverage + - uses: actions/upload-artifact@v4 + with: + name: research-assistant-frontend-coverage + path: | + ResearchAssistant/App/frontend/coverage/ + ResearchAssistant/App/frontend/coverage/lcov-report/ \ No newline at end of file diff --git a/ClientAdvisor/App/app.py b/ClientAdvisor/App/app.py index 4bafe554..90f97ab7 100644 --- a/ClientAdvisor/App/app.py +++ b/ClientAdvisor/App/app.py @@ -970,7 +970,8 @@ async def stream_chat_request(request_body, request_headers): if client_id is None: return jsonify({"error": "No client ID provided"}), 400 query = request_body.get("messages")[-1].get("content") - + query = query.strip() + async def generate(): deltaText = '' #async for completionChunk in response: @@ -1549,12 +1550,12 @@ def get_users(): ClientSummary, CAST(LastMeeting AS DATE) AS LastMeetingDate, FORMAT(CAST(LastMeeting AS DATE), 'dddd MMMM d, yyyy') AS LastMeetingDateFormatted, -      FORMAT(LastMeeting, 'hh:mm tt') AS LastMeetingStartTime, - FORMAT(LastMeetingEnd, 'hh:mm tt') AS LastMeetingEndTime, +       FORMAT(LastMeeting, 'HH:mm ') AS LastMeetingStartTime, + FORMAT(LastMeetingEnd, 'HH:mm') AS LastMeetingEndTime, CAST(NextMeeting AS DATE) AS NextMeetingDate, FORMAT(CAST(NextMeeting AS DATE), 'dddd MMMM d, yyyy') AS NextMeetingFormatted, - FORMAT(NextMeeting, 'hh:mm tt') AS NextMeetingStartTime, - FORMAT(NextMeetingEnd, 'hh:mm tt') AS NextMeetingEndTime + FORMAT(NextMeeting, 'HH:mm') AS NextMeetingStartTime, + FORMAT(NextMeetingEnd, 'HH:mm') AS NextMeetingEndTime FROM ( SELECT ca.ClientId, Client, Email, AssetValue, ClientSummary, LastMeeting, LastMeetingEnd, NextMeeting, NextMeetingEnd FROM ( @@ -1641,4 +1642,4 @@ def get_users(): if conn: conn.close() -app = create_app() \ No newline at end of file +app = create_app() diff --git a/ClientAdvisor/App/frontend/src/components/Answer/Answer.tsx b/ClientAdvisor/App/frontend/src/components/Answer/Answer.tsx index 744a003d..76868bc3 100644 --- a/ClientAdvisor/App/frontend/src/components/Answer/Answer.tsx +++ b/ClientAdvisor/App/frontend/src/components/Answer/Answer.tsx @@ -16,6 +16,7 @@ import { AppStateContext } from '../../state/AppProvider' import { parseAnswer } from './AnswerParser' import styles from './Answer.module.css' +import rehypeRaw from 'rehype-raw' interface Props { answer: AskResponse @@ -250,6 +251,7 @@ export const Answer = ({ answer, onCitationClicked }: Props) => { = ({ onCardClick }) => { const [selectedClientId, setSelectedClientId] = useState(null); const [loadingUsers, setLoadingUsers] = useState(true); + + useEffect(() => { + if(selectedClientId != null && appStateContext?.state.clientId == ''){ + setSelectedClientId('') + } + },[appStateContext?.state.clientId]); + useEffect(() => { const fetchUsers = async () => { try { diff --git a/ClientAdvisor/App/frontend/src/components/ChatHistory/ChatHistoryPanel.module.css b/ClientAdvisor/App/frontend/src/components/ChatHistory/ChatHistoryPanel.module.css index 784838fe..abb30159 100644 --- a/ClientAdvisor/App/frontend/src/components/ChatHistory/ChatHistoryPanel.module.css +++ b/ClientAdvisor/App/frontend/src/components/ChatHistory/ChatHistoryPanel.module.css @@ -77,3 +77,11 @@ width: 100%; } } + +@media screen and (-ms-high-contrast: active), (forced-colors: active) { + .container{ + border: 2px solid WindowText; + background-color: Window; + color: WindowText; + } +} \ No newline at end of file diff --git a/ClientAdvisor/App/frontend/src/components/ChatHistory/ChatHistoryPanel.tsx b/ClientAdvisor/App/frontend/src/components/ChatHistory/ChatHistoryPanel.tsx index 7a23f4d5..3232293f 100644 --- a/ClientAdvisor/App/frontend/src/components/ChatHistory/ChatHistoryPanel.tsx +++ b/ClientAdvisor/App/frontend/src/components/ChatHistory/ChatHistoryPanel.tsx @@ -111,7 +111,7 @@ export function ChatHistoryPanel(_props: ChatHistoryPanelProps) { = ({
{ if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); // Prevent the default action like scrolling. - handleShowMoreClick(e); // Call the same function as onClick. + onCardClick(); // Call the same function as onClick. } }}>
{ClientName}
diff --git a/ClientAdvisor/App/frontend/src/components/common/Button.module.css b/ClientAdvisor/App/frontend/src/components/common/Button.module.css index 14c1ecb7..dc5df4d5 100644 --- a/ClientAdvisor/App/frontend/src/components/common/Button.module.css +++ b/ClientAdvisor/App/frontend/src/components/common/Button.module.css @@ -25,6 +25,7 @@ .historyButtonRoot { width: 180px; border: 1px solid #d1d1d1; + border-radius: 5px; } .historyButtonRoot:hover { diff --git a/ClientAdvisor/App/frontend/src/pages/chat/Chat.module.css b/ClientAdvisor/App/frontend/src/pages/chat/Chat.module.css index 988f96ec..1282b82c 100644 --- a/ClientAdvisor/App/frontend/src/pages/chat/Chat.module.css +++ b/ClientAdvisor/App/frontend/src/pages/chat/Chat.module.css @@ -35,7 +35,7 @@ html, body { 0px 0px 2px rgba(0, 0, 0, 0.12); border-radius: 8px; overflow-y: auto; - max-height: calc(100vh - 300px); + max-height: calc(100vh - 240px); height: 100vh; width: 100%; } diff --git a/ClientAdvisor/App/frontend/src/pages/chat/Chat.tsx b/ClientAdvisor/App/frontend/src/pages/chat/Chat.tsx index b39e1560..22cc7e53 100644 --- a/ClientAdvisor/App/frontend/src/pages/chat/Chat.tsx +++ b/ClientAdvisor/App/frontend/src/pages/chat/Chat.tsx @@ -751,7 +751,7 @@ const Chat = (props: any) => {

{ui?.chat_title}

-

{ui?.chat_description}

+

{ui?.chat_description}

) : (
diff --git a/ClientAdvisor/App/frontend/src/pages/layout/Layout.module.css b/ClientAdvisor/App/frontend/src/pages/layout/Layout.module.css index abcbbfab..754ef979 100644 --- a/ClientAdvisor/App/frontend/src/pages/layout/Layout.module.css +++ b/ClientAdvisor/App/frontend/src/pages/layout/Layout.module.css @@ -179,6 +179,7 @@ height: 100%; */ display: flex; flex-direction: column; + padding-top : 10px ; } .pivotContainer > div { @@ -316,4 +317,11 @@ background-color: Window; color: WindowText; } + + .selectedName{ + border-radius:25px; + border: 2px solid WindowText; + background-color: Window; + color: WindowText; + } } \ No newline at end of file diff --git a/ClientAdvisor/App/frontend/src/pages/layout/Layout.tsx b/ClientAdvisor/App/frontend/src/pages/layout/Layout.tsx index 7681c263..3c650d07 100644 --- a/ClientAdvisor/App/frontend/src/pages/layout/Layout.tsx +++ b/ClientAdvisor/App/frontend/src/pages/layout/Layout.tsx @@ -52,6 +52,11 @@ const Layout = () => { fetchpbi() }, []) + const resetClientId= ()=>{ + appStateContext?.dispatch({ type: 'RESET_CLIENT_ID' }); + setSelectedUser(null); + setShowWelcomeCard(true); + } const closePopup = () => { setIsVisible(!isVisible); @@ -157,7 +162,7 @@ const Layout = () => { />
-

Upcoming meetings

+

Upcoming meetings

@@ -167,9 +172,9 @@ const Layout = () => { - -

{ui?.title}

- +
(e.key === 'Enter' || e.key === ' ' ? resetClientId() : null)} tabIndex={-1}> +

{ui?.title}

+
{appStateContext?.state.isCosmosDBAvailable?.status !== CosmosDBStatus.NotConfigured && ( @@ -212,7 +217,7 @@ const Layout = () => { {selectedUser ? selectedUser.ClientName : 'None'}
)} - + diff --git a/ClientAdvisor/App/frontend/src/state/AppProvider.tsx b/ClientAdvisor/App/frontend/src/state/AppProvider.tsx index d0166462..2ae54afe 100644 --- a/ClientAdvisor/App/frontend/src/state/AppProvider.tsx +++ b/ClientAdvisor/App/frontend/src/state/AppProvider.tsx @@ -51,7 +51,8 @@ export type Action = | { type: 'GET_FEEDBACK_STATE'; payload: string } | { type: 'UPDATE_CLIENT_ID'; payload: string } | { type: 'SET_IS_REQUEST_INITIATED'; payload: boolean } - | { type: 'TOGGLE_LOADER' }; + | { type: 'TOGGLE_LOADER' } + | { type: 'RESET_CLIENT_ID'}; const initialState: AppState = { isChatHistoryOpen: false, diff --git a/ClientAdvisor/App/frontend/src/state/AppReducer.tsx b/ClientAdvisor/App/frontend/src/state/AppReducer.tsx index 21a126da..03a778cc 100644 --- a/ClientAdvisor/App/frontend/src/state/AppReducer.tsx +++ b/ClientAdvisor/App/frontend/src/state/AppReducer.tsx @@ -80,6 +80,8 @@ export const appStateReducer = (state: AppState, action: Action): AppState => { return {...state, isRequestInitiated : action.payload} case 'TOGGLE_LOADER': return {...state, isLoader : !state.isLoader} + case 'RESET_CLIENT_ID': + return {...state, clientId: ''} default: return state } diff --git a/ClientAdvisor/AzureFunction/function_app.py b/ClientAdvisor/AzureFunction/function_app.py index f9bfd8dc..9f6368cd 100644 --- a/ClientAdvisor/AzureFunction/function_app.py +++ b/ClientAdvisor/AzureFunction/function_app.py @@ -18,7 +18,6 @@ from semantic_kernel.functions.kernel_function_decorator import kernel_function from semantic_kernel.kernel import Kernel import pymssql - # Azure Function App app = func.FunctionApp(http_auth_level=func.AuthLevel.ANONYMOUS) @@ -40,7 +39,7 @@ def greeting(self, input: Annotated[str, "the question"]) -> Annotated[str, "The client = openai.AzureOpenAI( azure_endpoint=endpoint, api_key=api_key, - api_version="2023-09-01-preview" + api_version=api_version ) deployment = os.environ.get("AZURE_OPEN_AI_DEPLOYMENT_MODEL") try: @@ -75,7 +74,7 @@ def get_SQL_Response( client = openai.AzureOpenAI( azure_endpoint=endpoint, api_key=api_key, - api_version="2023-09-01-preview" + api_version=api_version ) deployment = os.environ.get("AZURE_OPEN_AI_DEPLOYMENT_MODEL") @@ -100,6 +99,17 @@ def get_SQL_Response( Do not include assets values unless asked for. Always use ClientId = {clientid} in the query filter. Always return client name in the query. + If a question involves date and time, always use FORMAT(YourDateTimeColumn, 'yyyy-MM-dd HH:mm:ss') in the query. + If asked, provide information about client meetings according to the requested timeframe: give details about upcoming meetings if asked for "next" or "upcoming" meetings, and provide details about past meetings if asked for "previous" or "last" meetings including the scheduled time and don't filter with "LIMIT 1" in the query. + If asked about the number of past meetings with this client, provide the count of records where the ConversationId is neither null nor an empty string and the EndTime is before the current date in the query. + If asked, provide information on the client's investment risk tolerance level in the query. + If asked, provide information on the client's portfolio performance in the query. + If asked, provide information about the client's top-performing investments in the query. + If asked, provide information about any recent changes in the client's investment allocations in the query. + If asked about the client's portfolio performance over the last quarter, calculate the total investment by summing the investment amounts where AssetDate is greater than or equal to the date from one quarter ago using DATEADD(QUARTER, -1, GETDATE()) in the query. + If asked about upcoming important dates or deadlines for the client, always ensure that StartTime is greater than the current date. Do not convert the formats of StartTime and EndTime and consistently provide the upcoming dates along with the scheduled times in the query. + To determine the asset value, sum the investment values for the most recent available date. If asked for the asset types in the portfolio and the present of each, provide a list of each asset type with its most recent investment value. + If the user inquires about asset on a specific date ,sum the investment values for the specific date avoid summing values from all dates prior to the requested date.If asked for the asset types in the portfolio and the value of each for specific date , provide a list of each asset type with specific date investment value avoid summing values from all dates prior to the requested date. Only return the generated sql query. do not return anything else''' try: @@ -152,13 +162,16 @@ def get_answers_from_calltranscripts( client = openai.AzureOpenAI( azure_endpoint= endpoint, #f"{endpoint}/openai/deployments/{deployment}/extensions", api_key=apikey, - api_version="2024-02-01" + api_version=api_version ) query = question - system_message = '''You are an assistant who provides wealth advisors with helpful information to prepare for client meetings. - You have access to the client’s meeting call transcripts. - You can use this information to answer questions about the clients''' + system_message = '''You are an assistant who provides wealth advisors with helpful information to prepare for client meetings and provide details on the call transcripts. + You have access to the client’s meetings and call transcripts + When asked about action items from previous meetings with the client, **ALWAYS provide information only for the most recent dates**. + Always return time in "HH:mm" format for the client in response. + If requested for call transcript(s), the response for each transcript should be summarized separately and Ensure all transcripts for the specified client are retrieved and format **must** follow as First Call Summary,Second Call Summary etc. + Your answer must **not** include any client identifiers or ids or numbers or ClientId in the final response.''' completion = client.chat.completions.create( model = deployment, @@ -182,7 +195,6 @@ def get_answers_from_calltranscripts( "parameters": { "endpoint": search_endpoint, "index_name": index_name, - "semantic_configuration": "default", "query_type": "vector_simple_hybrid", #"vector_semantic_hybrid" "fields_mapping": { "content_fields_separator": "\n", @@ -259,14 +271,20 @@ async def stream_openai_text(req: Request) -> StreamingResponse: settings.max_tokens = 800 settings.temperature = 0 + # Read the HTML file + with open("table.html", "r") as file: + html_content = file.read() + system_message = '''you are a helpful assistant to a wealth advisor. Do not answer any questions not related to wealth advisors queries. - If the client name and client id do not match, only return - Please only ask questions about the selected client or select another client to inquire about their details. do not return any other information. - Only use the client name returned from database in the response. + **If the client name in the question does not match the selected client's name**, always return: "Please ask questions only about the selected client." Do not provide any other information. + Always consider to give selected client full name only in response and do not use other example names also consider my client means currently selected client. If you cannot answer the question, always return - I cannot answer this question from the data available. Please rephrase or add more details. ** Remove any client identifiers or ids or numbers or ClientId in the final response. + Client name **must be** same as retrieved from database. ''' - + system_message += html_content + user_query = query.replace('?',' ') user_query_prompt = f'''{user_query}. Always send clientId as {user_query.split(':::')[-1]} ''' @@ -280,4 +298,4 @@ async def stream_openai_text(req: Request) -> StreamingResponse: settings=settings ) - return StreamingResponse(stream_processor(sk_response), media_type="text/event-stream") \ No newline at end of file + return StreamingResponse(stream_processor(sk_response), media_type="text/event-stream") diff --git a/ClientAdvisor/AzureFunction/table.html b/ClientAdvisor/AzureFunction/table.html new file mode 100644 index 00000000..51ded0be --- /dev/null +++ b/ClientAdvisor/AzureFunction/table.html @@ -0,0 +1,11 @@ + + + + + + + + + + +
Header 1Header 2
Data 1Data 2
\ No newline at end of file diff --git a/ClientAdvisor/Deployment/bicep/deploy_app_service.bicep b/ClientAdvisor/Deployment/bicep/deploy_app_service.bicep index d2dbeb9a..1b66e034 100644 --- a/ClientAdvisor/Deployment/bicep/deploy_app_service.bicep +++ b/ClientAdvisor/Deployment/bicep/deploy_app_service.bicep @@ -172,7 +172,7 @@ param VITE_POWERBI_EMBED_URL string = '' // var WebAppImageName = 'DOCKER|ncwaappcontainerreg1.azurecr.io/ncqaappimage:v1.0.0' -var WebAppImageName = 'DOCKER|bycwacontainerreg.azurecr.io/byc-wa-app:latest' +var WebAppImageName = 'DOCKER|bycwacontainerreg.azurecr.io/byc-wa-app:dev' resource HostingPlan 'Microsoft.Web/serverfarms@2020-06-01' = { name: HostingPlanName diff --git a/ClientAdvisor/Deployment/bicep/deploy_azure_function_script.bicep b/ClientAdvisor/Deployment/bicep/deploy_azure_function_script.bicep index cdda6395..2ad7ff55 100644 --- a/ClientAdvisor/Deployment/bicep/deploy_azure_function_script.bicep +++ b/ClientAdvisor/Deployment/bicep/deploy_azure_function_script.bicep @@ -17,6 +17,7 @@ param sqlDbName string param sqlDbUser string @secure() param sqlDbPwd string +param functionAppVersion string resource deploy_azure_function 'Microsoft.Resources/deploymentScripts@2020-10-01' = { kind:'AzureCLI' @@ -31,7 +32,7 @@ resource deploy_azure_function 'Microsoft.Resources/deploymentScripts@2020-10-01 properties: { azCliVersion: '2.50.0' primaryScriptUri: '${baseUrl}Deployment/scripts/create_azure_functions.sh' // deploy-azure-synapse-pipelines.sh - arguments: '${solutionName} ${solutionLocation} ${resourceGroupName} ${baseUrl} ${azureOpenAIApiKey} ${azureOpenAIApiVersion} ${azureOpenAIEndpoint} ${azureSearchAdminKey} ${azureSearchServiceEndpoint} ${azureSearchIndex} ${sqlServerName} ${sqlDbName} ${sqlDbUser} ${sqlDbPwd}' // Specify any arguments for the script + arguments: '${solutionName} ${solutionLocation} ${resourceGroupName} ${baseUrl} ${azureOpenAIApiKey} ${azureOpenAIApiVersion} ${azureOpenAIEndpoint} ${azureSearchAdminKey} ${azureSearchServiceEndpoint} ${azureSearchIndex} ${sqlServerName} ${sqlDbName} ${sqlDbUser} ${sqlDbPwd} ${functionAppVersion}' // Specify any arguments for the script timeout: 'PT1H' // Specify the desired timeout duration retentionInterval: 'PT1H' // Specify the desired retention interval cleanupPreference:'OnSuccess' diff --git a/ClientAdvisor/Deployment/bicep/main.bicep b/ClientAdvisor/Deployment/bicep/main.bicep index cb99dc11..6c0f3a29 100644 --- a/ClientAdvisor/Deployment/bicep/main.bicep +++ b/ClientAdvisor/Deployment/bicep/main.bicep @@ -17,7 +17,8 @@ var resourceGroupName = resourceGroup().name // var subscriptionId = subscription().subscriptionId var solutionLocation = resourceGroupLocation -var baseUrl = 'https://raw.githubusercontent.com/microsoft/Build-your-own-copilot-Solution-Accelerator/main/ClientAdvisor/' +var baseUrl = 'https://raw.githubusercontent.com/Roopan-Microsoft/psl-byo-main/main/ClientAdvisor/' +var functionAppversion = 'dev' // ========== Managed Identity ========== // module managedIdentityModule 'deploy_managed_identity.bicep' = { @@ -120,6 +121,7 @@ module azureFunctions 'deploy_azure_function_script.bicep' = { sqlDbPwd:sqlDBModule.outputs.sqlDbOutput.sqlDbPwd identity:managedIdentityModule.outputs.managedIdentityOutput.id baseUrl:baseUrl + functionAppVersion: functionAppversion } dependsOn:[storageAccountModule] } diff --git a/ClientAdvisor/Deployment/bicep/main.json b/ClientAdvisor/Deployment/bicep/main.json index dc3f5f85..6f50a220 100644 --- a/ClientAdvisor/Deployment/bicep/main.json +++ b/ClientAdvisor/Deployment/bicep/main.json @@ -5,7 +5,7 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "13616077515444443649" + "templateHash": "5062834210065422729" } }, "parameters": { @@ -28,7 +28,8 @@ "resourceGroupLocation": "[resourceGroup().location]", "resourceGroupName": "[resourceGroup().name]", "solutionLocation": "[variables('resourceGroupLocation')]", - "baseUrl": "https://raw.githubusercontent.com/microsoft/Build-your-own-copilot-Solution-Accelerator/main/ClientAdvisor/" + "baseUrl": "https://raw.githubusercontent.com/Roopan-Microsoft/psl-byo-main/main/ClientAdvisor/", + "functionAppversion": "dev" }, "resources": [ { @@ -1046,6 +1047,9 @@ }, "baseUrl": { "value": "[variables('baseUrl')]" + }, + "functionAppVersion": { + "value": "[variables('functionAppversion')]" } }, "template": { @@ -1055,7 +1059,7 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "3863583258880925565" + "templateHash": "12686893977991317957" } }, "parameters": { @@ -1106,6 +1110,9 @@ }, "sqlDbPwd": { "type": "securestring" + }, + "functionAppVersion": { + "type": "string" } }, "resources": [ @@ -1124,7 +1131,7 @@ "properties": { "azCliVersion": "2.50.0", "primaryScriptUri": "[format('{0}Deployment/scripts/create_azure_functions.sh', parameters('baseUrl'))]", - "arguments": "[format('{0} {1} {2} {3} {4} {5} {6} {7} {8} {9} {10} {11} {12} {13}', parameters('solutionName'), parameters('solutionLocation'), parameters('resourceGroupName'), parameters('baseUrl'), parameters('azureOpenAIApiKey'), parameters('azureOpenAIApiVersion'), parameters('azureOpenAIEndpoint'), parameters('azureSearchAdminKey'), parameters('azureSearchServiceEndpoint'), parameters('azureSearchIndex'), parameters('sqlServerName'), parameters('sqlDbName'), parameters('sqlDbUser'), parameters('sqlDbPwd'))]", + "arguments": "[format('{0} {1} {2} {3} {4} {5} {6} {7} {8} {9} {10} {11} {12} {13} {14}', parameters('solutionName'), parameters('solutionLocation'), parameters('resourceGroupName'), parameters('baseUrl'), parameters('azureOpenAIApiKey'), parameters('azureOpenAIApiVersion'), parameters('azureOpenAIEndpoint'), parameters('azureSearchAdminKey'), parameters('azureSearchServiceEndpoint'), parameters('azureSearchIndex'), parameters('sqlServerName'), parameters('sqlDbName'), parameters('sqlDbUser'), parameters('sqlDbPwd'), parameters('functionAppVersion'))]", "timeout": "PT1H", "retentionInterval": "PT1H", "cleanupPreference": "OnSuccess" @@ -1992,7 +1999,7 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "5513270017559796037" + "templateHash": "10803780472687780653" } }, "parameters": { @@ -2377,7 +2384,7 @@ } }, "variables": { - "WebAppImageName": "DOCKER|bycwacontainerreg.azurecr.io/byc-wa-app:latest" + "WebAppImageName": "DOCKER|bycwacontainerreg.azurecr.io/byc-wa-app:dev" }, "resources": [ { diff --git a/ClientAdvisor/Deployment/scripts/create_azure_functions.sh b/ClientAdvisor/Deployment/scripts/create_azure_functions.sh index 89f4d90a..3d96ffe3 100644 --- a/ClientAdvisor/Deployment/scripts/create_azure_functions.sh +++ b/ClientAdvisor/Deployment/scripts/create_azure_functions.sh @@ -15,6 +15,7 @@ sqlServerName="${11}" sqlDbName="${12}" sqlDbUser="${13}" sqlDbPwd="${14}" +functionAppVersion="${15}" azureOpenAIDeploymentModel="gpt-4" azureOpenAIEmbeddingDeployment="text-embedding-ada-002" @@ -36,7 +37,7 @@ az storage account create --name $storageAccount --location eastus --resource-gr az functionapp create --resource-group $resourceGroupName --name $functionappname \ --environment $env_name --storage-account $storageAccount \ --functions-version 4 --runtime python \ - --image bycwacontainerreg.azurecr.io/byc-wa-fn:latest + --image bycwacontainerreg.azurecr.io/byc-wa-fn:$functionAppVersion # Sleep for 120 seconds echo "Waiting for 120 seconds to ensure the Function App is properly created..." diff --git a/ClientAdvisor/PowerBIReport/WealthAdvisor-Client360Report.pbix b/ClientAdvisor/PowerBIReport/WealthAdvisor-Client360Report.pbix index 69c6ba92..5bfefa92 100644 Binary files a/ClientAdvisor/PowerBIReport/WealthAdvisor-Client360Report.pbix and b/ClientAdvisor/PowerBIReport/WealthAdvisor-Client360Report.pbix differ diff --git a/ClientAdvisor/README.md b/ClientAdvisor/README.md index 949041e6..1b4e6f96 100644 --- a/ClientAdvisor/README.md +++ b/ClientAdvisor/README.md @@ -69,7 +69,7 @@ https://azure.microsoft.com/en-us/explore/global-infrastructure/products-by-regi 2. Click the following deployment button to create the required resources for this accelerator in your Azure Subscription. - [![Deploy to Azure](https://aka.ms/deploytoazurebutton)](https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2Fmicrosoft%2FBuild-your-own-copilot-Solution-Accelerator%2Fmain%2FClientAdvisor%2FDeployment%2Fbicep%2Fmain.json) + [![Deploy to Azure](https://aka.ms/deploytoazurebutton)](https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2FRoopan-Microsoft%2Frp0907%2Fmain%2FClientAdvisor%2FDeployment%2Fbicep%2Fmain.json) 3. You will need to select an Azure Subscription, create/select a Resource group, Region, a unique Solution Prefix and an Azure location for Cosmos DB. diff --git a/ResearchAssistant/Deployment/bicep/deploy_app_service.bicep b/ResearchAssistant/Deployment/bicep/deploy_app_service.bicep index 69bc0c1e..f733d9f0 100644 --- a/ResearchAssistant/Deployment/bicep/deploy_app_service.bicep +++ b/ResearchAssistant/Deployment/bicep/deploy_app_service.bicep @@ -162,7 +162,7 @@ param AIStudioDraftFlowDeploymentName string = '' param AIStudioUse string = 'False' -var WebAppImageName = 'DOCKER|byoaiacontainerreg.azurecr.io/byoaia-app:latest' +var WebAppImageName = 'DOCKER|byoaiacontainerreg.azurecr.io/byoaia-app:dev' resource HostingPlan 'Microsoft.Web/serverfarms@2020-06-01' = { name: HostingPlanName diff --git a/ResearchAssistant/Deployment/bicep/main.bicep b/ResearchAssistant/Deployment/bicep/main.bicep index c81d1962..ea5f564c 100644 --- a/ResearchAssistant/Deployment/bicep/main.bicep +++ b/ResearchAssistant/Deployment/bicep/main.bicep @@ -14,7 +14,7 @@ var resourceGroupName = resourceGroup().name var subscriptionId = subscription().subscriptionId var solutionLocation = resourceGroupLocation -var baseUrl = 'https://raw.githubusercontent.com/microsoft/Build-your-own-copilot-Solution-Accelerator/main/' +var baseUrl = 'https://raw.githubusercontent.com/Roopan-Microsoft/Build-your-own-copilot-Solution-Accelerator/main/' // ========== Managed Identity ========== // module managedIdentityModule 'deploy_managed_identity.bicep' = { diff --git a/ResearchAssistant/Deployment/bicep/main.json b/ResearchAssistant/Deployment/bicep/main.json index 6d4cacd0..a64e3bfd 100644 --- a/ResearchAssistant/Deployment/bicep/main.json +++ b/ResearchAssistant/Deployment/bicep/main.json @@ -5,7 +5,7 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "7163812400877459703" + "templateHash": "10711406236308727919" } }, "parameters": { @@ -23,7 +23,7 @@ "resourceGroupName": "[resourceGroup().name]", "subscriptionId": "[subscription().subscriptionId]", "solutionLocation": "[variables('resourceGroupLocation')]", - "baseUrl": "https://raw.githubusercontent.com/microsoft/Build-your-own-copilot-Solution-Accelerator/main/" + "baseUrl": "https://raw.githubusercontent.com/Roopan-Microsoft/Build-your-own-copilot-Solution-Accelerator/main/" }, "resources": [ { @@ -1508,7 +1508,7 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "7109834445090495169" + "templateHash": "1558876662595106054" } }, "parameters": { @@ -1878,7 +1878,7 @@ } }, "variables": { - "WebAppImageName": "DOCKER|byoaiacontainerreg.azurecr.io/byoaia-app:latest" + "WebAppImageName": "DOCKER|byoaiacontainerreg.azurecr.io/byoaia-app:dev" }, "resources": [ {