From 526800393cd74c75e59dd19fdad52f5aa79dc1c0 Mon Sep 17 00:00:00 2001 From: George Song Date: Fri, 22 Nov 2024 11:38:04 -0800 Subject: [PATCH 1/2] build: standardize on 2 space indentation This is the industry standard for TS/JS --- app/frontend/.prettierrc.json | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/app/frontend/.prettierrc.json b/app/frontend/.prettierrc.json index b7d67747c..ea9745216 100644 --- a/app/frontend/.prettierrc.json +++ b/app/frontend/.prettierrc.json @@ -1,6 +1,5 @@ { - "tabWidth": 4, - "printWidth": 160, - "arrowParens": "avoid", - "trailingComma": "none" + "printWidth": 160, + "arrowParens": "avoid", + "trailingComma": "none" } From 3218f8811f5421b5babad77f0c40d65cfbeb0376 Mon Sep 17 00:00:00 2001 From: George Song Date: Fri, 22 Nov 2024 11:40:18 -0800 Subject: [PATCH 2/2] =?UTF-8?q?style:=20format=20according=20to=20prettier?= =?UTF-8?q?=20rules=20=F0=9F=92=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .devcontainer/devcontainer.json | 151 +- .github/ISSUE_TEMPLATE/bug_report.md | 30 +- .github/ISSUE_TEMPLATE/feature_request.md | 7 +- .vscode/extensions.json | 7 +- .vscode/launch.json | 228 +- .vscode/settings.json | 30 +- .vscode/tasks.json | 12 +- CHANGELOG.md | 16 +- CONTRIBUTING.md | 54 +- NOTICE.md | 10 +- README.md | 103 +- SECURITY.md | 18 +- SUPPORT.md | 10 +- app/frontend/index.html | 4 +- app/frontend/src/api/api.ts | 773 +++-- app/frontend/src/api/models.ts | 298 +- .../AnalysisPanel/AnalysisPanel.module.css | 8 +- .../AnalysisPanel/AnalysisPanel.tsx | 436 +-- .../AnalysisPanel/AnalysisPanelTabs.tsx | 6 +- .../src/components/Answer/Answer.module.css | 342 +- app/frontend/src/components/Answer/Answer.tsx | 454 +-- .../src/components/Answer/AnswerError.tsx | 22 +- .../src/components/Answer/AnswerIcon.tsx | 53 +- .../src/components/Answer/AnswerLoading.tsx | 36 +- .../src/components/Answer/AnswerParser.tsx | 433 +-- .../ApproachesButtonGroup.module.css | 324 +- .../ApproachesButtonGroup.tsx | 42 +- .../ApproachesButtonGroup/index.tsx | 2 +- .../CharacterStreamer/CharacterStreamer.tsx | 211 +- .../ChatModeButtonGroup.module.css | 320 +- .../ChatModeButtonGroup.tsx | 112 +- .../components/ChatModeButtonGroup/index.tsx | 2 +- .../ClearChatButton.module.css | 10 +- .../ClearChatButton/ClearChatButton.tsx | 18 +- .../src/components/Example/Example.module.css | 52 +- .../src/components/Example/Example.tsx | 16 +- .../src/components/Example/ExampleList.tsx | 30 +- .../FileStatus/DocumentsDetailList.module.css | 87 +- .../FileStatus/DocumentsDetailList.tsx | 924 +++--- .../FileStatus/FileStatus.module.css | 95 +- .../src/components/FileStatus/FileStatus.tsx | 450 ++- .../FolderPicker/FolderPicker.module.css | 4 +- .../components/FolderPicker/FolderPicker.tsx | 389 ++- .../InfoButton/InfoButton.module.css | 8 +- .../src/components/InfoButton/InfoButton.tsx | 16 +- .../components/InfoContent/InfoContent.tsx | 101 +- .../src/components/InfoContent/index.ts | 2 +- .../QuestionInput/QuestionInput.module.css | 72 +- .../QuestionInput/QuestionInput.tsx | 171 +- .../components/RAIPanel/RAIPanel.module.css | 30 +- .../src/components/RAIPanel/RAIPanel.tsx | 101 +- .../ResponseLengthButtonGroup.module.css | 324 +- .../ResponseLengthButtonGroup.tsx | 44 +- .../ResponseLengthButtonGroup/index.tsx | 2 +- .../ResponseTempButtonGroup.module.css | 324 +- .../ResponseTempButtonGroup.tsx | 34 +- .../ResponseTempButtonGroup/index.tsx | 2 +- .../SettingsButton/SettingsButton.module.css | 8 +- .../SettingsButton/SettingsButton.tsx | 16 +- .../StatusContent/StatusContent.tsx | 67 +- .../SupportingContent.module.css | 34 +- .../SupportingContent/SupportingContent.tsx | 28 +- .../SupportingContentParser.ts | 22 +- .../components/TagPicker/TagPicker.module.css | 27 +- .../src/components/TagPicker/TagPicker.tsx | 263 +- app/frontend/src/components/Title/Title.tsx | 44 +- .../UserChatMessage.module.css | 120 +- .../UserChatMessage/UserChatMessage.tsx | 66 +- .../WarningBanner/WarningBanner.module.css | 10 +- .../WarningBanner/WarningBanner.tsx | 50 +- .../src/components/WarningBanner/index.ts | 2 +- .../src/components/filepicker/check.tsx | 10 +- .../src/components/filepicker/clear.tsx | 10 +- .../filepicker/drop-zone.module.css | 65 +- .../src/components/filepicker/drop-zone.tsx | 23 +- .../filepicker/file-picker.module.css | 149 +- .../src/components/filepicker/file-picker.tsx | 279 +- .../filepicker/files-list.module.css | 121 +- .../src/components/filepicker/files-list.tsx | 21 +- app/frontend/src/index.css | 30 +- app/frontend/src/index.tsx | 36 +- app/frontend/src/pages/NoPage.tsx | 2 +- app/frontend/src/pages/chat/Chat.module.css | 254 +- app/frontend/src/pages/chat/Chat.tsx | 1048 ++++--- .../src/pages/content/Content.module.css | 114 +- app/frontend/src/pages/content/Content.tsx | 179 +- .../src/pages/layout/Layout.module.css | 101 +- app/frontend/src/pages/layout/Layout.tsx | 126 +- app/frontend/src/pages/tda/Tda.module.css | 365 ++- app/frontend/src/pages/tda/Tda.tsx | 510 ++- app/frontend/src/pages/tda/check.tsx | 10 +- app/frontend/src/pages/tda/clear.tsx | 10 +- .../src/pages/tda/drop-zone.module.css | 65 +- app/frontend/src/pages/tda/drop-zone.tsx | 23 +- .../src/pages/tda/file-picker.module.css | 103 +- .../src/pages/tda/files-list.module.css | 121 +- app/frontend/src/pages/tda/files-list.tsx | 21 +- app/frontend/src/pages/tutor/Tutor.module.css | 291 +- app/frontend/src/pages/tutor/Tutor.tsx | 352 ++- app/frontend/version.json | 6 +- app/frontend/vite.config.ts | 60 +- docs/container_webapp_debug.md | 4 +- docs/costestimator.md | 9 +- .../accepting_responsible_ai_notice.md | 10 +- ...ing_responsible_ai_notice_multi_service.md | 10 +- docs/deployment/autoscale_sku.md | 46 +- docs/deployment/client_credentials_flow.md | 70 +- .../configure_local_dev_environment.md | 87 +- docs/deployment/considerations_production.md | 8 +- docs/deployment/deployment.md | 152 +- .../developing_in_a_GitHub_Codespaces.md | 37 +- .../deployment/enable_sovereign_deployment.md | 6 +- docs/deployment/manual_app_registration.md | 40 +- docs/deployment/migrate.md | 20 +- docs/deployment/move_or_migrate.md | 4 +- .../setting_up_sandbox_environment.md | 34 +- docs/deployment/statusdb_cosmos.md | 28 +- docs/deployment/troubleshooting.md | 6 +- docs/deployment/upgrade.md | 26 +- docs/deployment/using_ia_first_time.md | 5 +- docs/deployment/workbook_usage.md | 26 +- docs/features/architectural_decisions.md | 3 +- docs/features/cognitive_search.md | 19 +- .../configuring_language_env_files.md | 14 +- docs/features/document_pre_processing.md | 42 +- .../enable_customer_usage_attribution.md | 4 +- docs/features/features.md | 56 +- docs/features/optional_features.md | 20 +- docs/features/sharepoint.md | 22 +- docs/features/user_experience.md | 48 +- docs/features/ux_analysispanel.md | 27 +- docs/function_debug.md | 8 +- docs/knownissues.md | 51 +- .../secure_deployment/secure_costestimator.md | 6 +- docs/secure_deployment/secure_deployment.md | 301 +- docs/status_log.md | 337 +- docs/transparency.md | 172 +- docs/webapp_debug.md | 6 +- functions/FileDeletion/function.json | 20 +- functions/FileFormRecPollingPDF/function.json | 2 +- .../FileFormRecSubmissionPDF/function.json | 2 +- .../FileLayoutParsingOther/function.json | 2 +- functions/FileUploadedFunc/function.json | 2 +- functions/ImageEnrichment/function.json | 2 +- functions/ImageEnrichment/readme.md | 10 +- functions/TextEnrichment/function.json | 2 +- functions/host.json | 4 +- infra/README.md | 15 +- .../bing_search/bing.template.json | 84 +- .../kv_secret/kv_secret.template.json | 76 +- .../arm_templates/network/vnet.template.json | 571 ++-- .../network/vnet_w_ddos.template.json | 575 ++-- .../storage_container/container.template.json | 81 +- .../storage_queue/queue.template.json | 81 +- infra/azure_roles.json | 940 +++--- ...capp_SharePointFileIngestion_template.json | 986 +++--- pipelines/azdo-gov.yml | 27 +- pipelines/azdo.yml | 27 +- pipelines/demo.yml | 69 +- pipelines/pr-gov.yml | 31 +- pipelines/pr.yml | 33 +- pipelines/templates/deploy-template.yml | 126 +- pipelines/templates/destroy-template.yml | 18 +- pipelines/templates/make-command.yml | 86 +- pipelines/templates/testing-template.yml | 8 +- pipelines/vNext-gov.yml | 27 +- pipelines/vNext.yml | 27 +- scripts/tf-dependencies.json | 2760 ++++++++--------- tests/README.md | 20 +- tests/test_data/test_example.html | 106 +- tests/test_data/test_example.md | 10 +- 171 files changed, 10964 insertions(+), 10752 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index e8e15be35..8ed7d538e 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,79 +1,82 @@ { - "name": "info-asst", - "build": { - "dockerfile": "Dockerfile", - "args": { - // To ensure that the group ID for the docker group in the container - // matches the group ID on the host, add this to your .bash_profile on the host - // export DOCKER_GROUP_ID=$(getent group docker | awk -F ":" '{ print $3 }') - "DOCKER_GROUP_ID": "${localEnv:DOCKER_GROUP_ID}" - } - }, - "forwardPorts": [ 7071 ], - "runArgs": [ - "--network", "host", "--cap-add", "NET_ADMIN" // use host networking so that the dev container can access the API when running the container locally - ], + "name": "info-asst", + "build": { + "dockerfile": "Dockerfile", + "args": { + // To ensure that the group ID for the docker group in the container + // matches the group ID on the host, add this to your .bash_profile on the host + // export DOCKER_GROUP_ID=$(getent group docker | awk -F ":" '{ print $3 }') + "DOCKER_GROUP_ID": "${localEnv:DOCKER_GROUP_ID}" + } + }, + "forwardPorts": [7071], + "runArgs": [ + "--network", + "host", + "--cap-add", + "NET_ADMIN" // use host networking so that the dev container can access the API when running the container locally + ], - "mounts": [ - // Keep command history - "type=volume,source=info-asst-bashhistory,target=/home/vscode/commandhistory", - // Mounts the login details from the host machine to azcli works in the container - "type=bind,source=${env:HOME}${env:USERPROFILE}/.azure,target=/home/vscode/.azure", - // Mount docker socket for docker builds - "type=bind,source=/var/run/docker.sock,target=/var/run/docker.sock" - ], + "mounts": [ + // Keep command history + "type=volume,source=info-asst-bashhistory,target=/home/vscode/commandhistory", + // Mounts the login details from the host machine to azcli works in the container + "type=bind,source=${env:HOME}${env:USERPROFILE}/.azure,target=/home/vscode/.azure", + // Mount docker socket for docker builds + "type=bind,source=/var/run/docker.sock,target=/var/run/docker.sock" + ], - // Set *default* container specific settings values on container create. - "customizations": { - "vscode": { - "settings": { - "python.pythonPath": "/opt/conda/envs/development/bin/python", - "python.languageServer": "Pylance", - "python.formatting.autopep8Path": "/usr/local/py-utils/bin/autopep8", - "python.formatting.blackPath": "/usr/local/py-utils/bin/black", - "python.formatting.yapfPath": "/usr/local/py-utils/bin/yapf", - "files.watcherExclude": { - "**/.git/objects/**": true, - "**/.git/subtree-cache/**": true, - "**/node_modules/*/**": true, - "**/.python_packages/*/**": true - }, - "files.associations": { - "*.workbook": "[jsonc]" - } - }, - // Add extensions you want installed when the container is created into this array - "extensions": [ - "ms-python.python", - "ms-python.vscode-pylance", - "IronGeek.vscode-env", - "ms-azuretools.vscode-docker", - "ms-toolsai.jupyter", - "humao.rest-client", - "ms-dotnettools.csharp", - "ms-vsliveshare.vsliveshare-pack", - "ms-vscode.powershell", - "DavidAnson.vscode-markdownlint", - "redhat.vscode-yaml", - "ms-azure-devops.azure-pipelines", - "k--kato.docomment", - "hediet.vscode-drawio", - "msazurermtools.azurerm-vscode-tools", - "ms-azuretools.vscode-azurestorage", - "GitHub.copilot", - "GitHub.copilot-chat", - "BelkacemBerras.spellcheck", - "ms-azuretools.vscode-azureresourcegroups", - "ms-azuretools.vscode-azurefunctions", - "ms-python.pylint", - "ms-python.mypy", - "HashiCorp.terraform", - "mhutchie.git-graph", - "esbenp.prettier-vscode", - "mutantdino.resourcemonitor" - ] - } - }, + // Set *default* container specific settings values on container create. + "customizations": { + "vscode": { + "settings": { + "python.pythonPath": "/opt/conda/envs/development/bin/python", + "python.languageServer": "Pylance", + "python.formatting.autopep8Path": "/usr/local/py-utils/bin/autopep8", + "python.formatting.blackPath": "/usr/local/py-utils/bin/black", + "python.formatting.yapfPath": "/usr/local/py-utils/bin/yapf", + "files.watcherExclude": { + "**/.git/objects/**": true, + "**/.git/subtree-cache/**": true, + "**/node_modules/*/**": true, + "**/.python_packages/*/**": true + }, + "files.associations": { + "*.workbook": "[jsonc]" + } + }, + // Add extensions you want installed when the container is created into this array + "extensions": [ + "ms-python.python", + "ms-python.vscode-pylance", + "IronGeek.vscode-env", + "ms-azuretools.vscode-docker", + "ms-toolsai.jupyter", + "humao.rest-client", + "ms-dotnettools.csharp", + "ms-vsliveshare.vsliveshare-pack", + "ms-vscode.powershell", + "DavidAnson.vscode-markdownlint", + "redhat.vscode-yaml", + "ms-azure-devops.azure-pipelines", + "k--kato.docomment", + "hediet.vscode-drawio", + "msazurermtools.azurerm-vscode-tools", + "ms-azuretools.vscode-azurestorage", + "GitHub.copilot", + "GitHub.copilot-chat", + "BelkacemBerras.spellcheck", + "ms-azuretools.vscode-azureresourcegroups", + "ms-azuretools.vscode-azurefunctions", + "ms-python.pylint", + "ms-python.mypy", + "HashiCorp.terraform", + "mhutchie.git-graph", + "esbenp.prettier-vscode", + "mutantdino.resourcemonitor" + ] + } + }, - "remoteUser": "vscode" + "remoteUser": "vscode" } diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index d684288ff..313db6588 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -1,19 +1,19 @@ --- name: Bug report about: Create a report to help us improve -title: '' -labels: '' -assignees: '' - +title: "" +labels: "" +assignees: "" --- -*Before you open an issue, please check if a similar issue already exists or has been closed before.* +_Before you open an issue, please check if a similar issue already exists or has been closed before._ - You can also find details on [Troubleshooting Common Issues](https://github.com/microsoft/PubSec-Info-Assistant/blob/main/docs/deployment/troubleshooting.md). You can use these tools to help gather additional logs and details to include in your issue. +You can also find details on [Troubleshooting Common Issues](https://github.com/microsoft/PubSec-Info-Assistant/blob/main/docs/deployment/troubleshooting.md). You can use these tools to help gather additional logs and details to include in your issue. - :warning: Please DO NOT include confidential information in your issue on GitHub. :warning: +:warning: Please DO NOT include confidential information in your issue on GitHub. :warning: ### Bug Details + **Describe the bug** A clear and concise description of what the bug is. @@ -32,19 +32,19 @@ If applicable, add screenshots to help explain your problem. Please provide the following details. You can simply include a screenshot of your Info panel as well. ->GitHub branch: [e.g. main] +> GitHub branch: [e.g. main] > ->Version or Latest commit: [obtained by running `git log -n 1 ` +> Version or Latest commit: [obtained by running `git log -n 1 ` > ->What region is your Azure Open AI Service in? +> What region is your Azure Open AI Service in? > ->What ChatGPT model are you using? +> What ChatGPT model are you using? > ->model name: (i.e. gpt-3.5-turbo, gpt-4) +> model name: (i.e. gpt-3.5-turbo, gpt-4) > ->model version: (i.e. 0613) +> model version: (i.e. 0613) > ->What embeddings model are you using? +> What embeddings model are you using? **Additional context** Add any other context about the problem here. @@ -52,4 +52,4 @@ Add any other context about the problem here. **If the bug is confirmed, would you be willing to submit a PR?** - [ ] Yes -- [ ] No \ No newline at end of file +- [ ] No diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index bbcbbe7d6..2bc5d5f71 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -1,10 +1,9 @@ --- name: Feature request about: Suggest an idea for this project -title: '' -labels: '' -assignees: '' - +title: "" +labels: "" +assignees: "" --- **Is your feature request related to a problem? Please describe.** diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 3f63eb97d..6edd29894 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,6 +1,3 @@ { - "recommendations": [ - "ms-azuretools.vscode-azurefunctions", - "ms-python.python" - ] -} \ No newline at end of file + "recommendations": ["ms-azuretools.vscode-azurefunctions", "ms-python.python"] +} diff --git a/.vscode/launch.json b/.vscode/launch.json index 84a61a14f..204caf2d8 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,123 +1,107 @@ { - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { - "name": "Python: WebApp backend", - "type": "python", - "request": "launch", - "module": "uvicorn", - "args": [ - "app:app", - "--reload", - "--port", - "5000" - ], - "cwd": "${workspaceFolder}/app/backend", - "console": "integratedTerminal", - "justMyCode": true, - "envFile": "${workspaceFolder}/scripts/environments/infrastructure.debug.env", - "preLaunchTask": "pip_install" - }, - { - "name": "Python: Enrichment Webapp", - "type": "python", - "request": "launch", - "module": "uvicorn", - "args": [ - "app:app", - "--reload", - "--port", - "5001" - ], - "cwd": "${workspaceFolder}/app/enrichment", - "console": "integratedTerminal", - "justMyCode": true, - "envFile": "${workspaceFolder}/scripts/environments/infrastructure.debug.env", - "preLaunchTask": "pip_install_enrichment" - }, - { - "name": "Vite: Debug", - "type": "msedge", - "request": "launch", - "url": "http://localhost:5000", - "webRoot": "${workspaceFolder}/app/backend/static", - "sourceMapPathOverrides": { - "webpack:///src/*": "${webRoot}/*" - }, - "skipFiles": ["/**", "**/node_modules/**"] - }, - { - "name": "Frontend: watch", - "type": "node", - "request": "launch", - "cwd": "${workspaceFolder}/app/frontend", - "runtimeExecutable": "npm", - "runtimeArgs": [ - "run-script", - "watch" - ], - "console": "integratedTerminal", - }, - { - "name": "Frontend: build", - "type": "node", - "request": "launch", - "cwd": "${workspaceFolder}/app/frontend", - "runtimeExecutable": "npm", - "runtimeArgs": [ - "run-script", - "build" - ], - "console": "integratedTerminal", - }, - { - "name": "Attach to Python Functions", - "type": "python", - "request": "attach", - "port": 9091, - "preLaunchTask": "func host start" - }, - { - "name": "Debug functional tests", - "type": "python", - "request": "launch", - "program": "debug_tests.py", - "args": [ - "--storage_account_url", - "${env:AZURE_BLOB_STORAGE_ENDPOINT},", - "--search_service_endpoint", - "${env:SEARCH_SERVICE_ENDPOINT}", - "--search_index", - "${env:SEARCH_INDEX}", - "--search_key", - "${env:SEARCH_KEY}", - "--wait_time_seconds", - "60" - ], - "env": { - "storage_account_url": "${env:AZURE_BLOB_STORAGE_ENDPOINT}", - "SEARCH_SERVICE_ENDPOINT": "${env:AZURE_SEARCH_SERVICE_ENDPOINT}", - "SEARCH_INDEX": "${env:AZURE_SEARCH_INDEX}" - }, - "cwd": "${workspaceFolder}/tests", - "envFile": "${workspaceFolder}/scripts/environments/infrastructure.debug.env", - "preLaunchTask": "pip_install_func_tests", - "presentation": { - "hidden": false, - "group": "", - "order": 1, - "panel": "shared" - } - }, - { - "name": "Python: Current File", - "type": "python", - "request": "launch", - "program": "${file}", - "console": "integratedTerminal" - } - ] -} \ No newline at end of file + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Python: WebApp backend", + "type": "python", + "request": "launch", + "module": "uvicorn", + "args": ["app:app", "--reload", "--port", "5000"], + "cwd": "${workspaceFolder}/app/backend", + "console": "integratedTerminal", + "justMyCode": true, + "envFile": "${workspaceFolder}/scripts/environments/infrastructure.debug.env", + "preLaunchTask": "pip_install" + }, + { + "name": "Python: Enrichment Webapp", + "type": "python", + "request": "launch", + "module": "uvicorn", + "args": ["app:app", "--reload", "--port", "5001"], + "cwd": "${workspaceFolder}/app/enrichment", + "console": "integratedTerminal", + "justMyCode": true, + "envFile": "${workspaceFolder}/scripts/environments/infrastructure.debug.env", + "preLaunchTask": "pip_install_enrichment" + }, + { + "name": "Vite: Debug", + "type": "msedge", + "request": "launch", + "url": "http://localhost:5000", + "webRoot": "${workspaceFolder}/app/backend/static", + "sourceMapPathOverrides": { + "webpack:///src/*": "${webRoot}/*" + }, + "skipFiles": ["/**", "**/node_modules/**"] + }, + { + "name": "Frontend: watch", + "type": "node", + "request": "launch", + "cwd": "${workspaceFolder}/app/frontend", + "runtimeExecutable": "npm", + "runtimeArgs": ["run-script", "watch"], + "console": "integratedTerminal" + }, + { + "name": "Frontend: build", + "type": "node", + "request": "launch", + "cwd": "${workspaceFolder}/app/frontend", + "runtimeExecutable": "npm", + "runtimeArgs": ["run-script", "build"], + "console": "integratedTerminal" + }, + { + "name": "Attach to Python Functions", + "type": "python", + "request": "attach", + "port": 9091, + "preLaunchTask": "func host start" + }, + { + "name": "Debug functional tests", + "type": "python", + "request": "launch", + "program": "debug_tests.py", + "args": [ + "--storage_account_url", + "${env:AZURE_BLOB_STORAGE_ENDPOINT},", + "--search_service_endpoint", + "${env:SEARCH_SERVICE_ENDPOINT}", + "--search_index", + "${env:SEARCH_INDEX}", + "--search_key", + "${env:SEARCH_KEY}", + "--wait_time_seconds", + "60" + ], + "env": { + "storage_account_url": "${env:AZURE_BLOB_STORAGE_ENDPOINT}", + "SEARCH_SERVICE_ENDPOINT": "${env:AZURE_SEARCH_SERVICE_ENDPOINT}", + "SEARCH_INDEX": "${env:AZURE_SEARCH_INDEX}" + }, + "cwd": "${workspaceFolder}/tests", + "envFile": "${workspaceFolder}/scripts/environments/infrastructure.debug.env", + "preLaunchTask": "pip_install_func_tests", + "presentation": { + "hidden": false, + "group": "", + "order": 1, + "panel": "shared" + } + }, + { + "name": "Python: Current File", + "type": "python", + "request": "launch", + "program": "${file}", + "console": "integratedTerminal" + } + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json index a59d6ebc1..d5461b6cd 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,21 +1,11 @@ { - "azureFunctions.deploySubpath": "functions", - "azureFunctions.scmDoBuildDuringDeployment": true, - "azureFunctions.pythonVenv": ".venv", - "azureFunctions.projectLanguage": "Python", - "azureFunctions.projectRuntime": "~4", - "debug.internalConsoleOptions": "neverOpen", - "spellright.language": [ - "en" - ], - "spellright.documentTypes": [ - "markdown", - "latex", - "plaintext", - "shellscript" - ], - "cSpell.words": [ - "Codespaces", - "referenceable" - ] -} \ No newline at end of file + "azureFunctions.deploySubpath": "functions", + "azureFunctions.scmDoBuildDuringDeployment": true, + "azureFunctions.pythonVenv": ".venv", + "azureFunctions.projectLanguage": "Python", + "azureFunctions.projectRuntime": "~4", + "debug.internalConsoleOptions": "neverOpen", + "spellright.language": ["en"], + "spellright.documentTypes": ["markdown", "latex", "plaintext", "shellscript"], + "cSpell.words": ["Codespaces", "referenceable"] +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 530155510..78d4fdd1f 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -1,6 +1,6 @@ { "version": "2.0.0", - "tasks": [ + "tasks": [ { "label": "pip_install", "type": "shell", @@ -41,13 +41,9 @@ "label": "pip_install_func_tests", "type": "shell", "command": "pip", - "args": [ - "install", - "-r", - "${workspaceFolder}/tests/requirements.txt" - ], + "args": ["install", "-r", "${workspaceFolder}/tests/requirements.txt"], "options": { - "cwd": "${workspaceFolder}/tests", + "cwd": "${workspaceFolder}/tests" }, "presentation": { "echo": true, @@ -87,4 +83,4 @@ } } ] -} \ No newline at end of file +} diff --git a/CHANGELOG.md b/CHANGELOG.md index 982475272..072d10889 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,13 +1,17 @@ ## [project-title] Changelog + # x.y.z (yyyy-mm-dd) -*Features* -* ... +_Features_ + +- ... + +_Bug Fixes_ + +- ... -*Bug Fixes* -* ... +_Breaking Changes_ -*Breaking Changes* -* ... +- ... diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 58398f8b0..9fd39190d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,6 +1,6 @@ # Contributing to Information Assistant agent template -This project welcomes contributions and suggestions. Most contributions require you to agree to a +This project welcomes contributions and suggestions. Most contributions require you to agree to a Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com. @@ -12,61 +12,67 @@ This project has adopted the [Microsoft Open Source Code of Conduct](https://ope For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. - - [Code of Conduct](#coc) - - [Issues and Bugs](#issue) - - [Feature Requests](#feature) - - [Submission Guidelines](#submit) +- [Code of Conduct](#coc) +- [Issues and Bugs](#issue) +- [Feature Requests](#feature) +- [Submission Guidelines](#submit) ## Code of Conduct + Help us keep this project open and inclusive. Please read and follow our [Code of Conduct](https://opensource.microsoft.com/codeofconduct/). ## Found an Issue? + If you find a bug in the source code or a mistake in the documentation, you can help us by [submitting an issue](#submit-issue) to the GitHub Repository. Even better, you can [submit a Pull Request](#submit-pr) with a fix. ## Want a Feature? -You can *request* a new feature by [submitting an issue](#submit-issue) to the GitHub -Repository. If you would like to *implement* a new feature, please submit an issue with + +You can _request_ a new feature by [submitting an issue](#submit-issue) to the GitHub +Repository. If you would like to _implement_ a new feature, please submit an issue with a proposal for your work first, to be sure that we can use it. -* **Small Features** can be crafted and directly [submitted as a Pull Request](#submit-pr). +- **Small Features** can be crafted and directly [submitted as a Pull Request](#submit-pr). ## Submission Guidelines ### Submitting an Issue + Before you submit an issue, search the archive, maybe your question was already answered. If your issue appears to be a bug, and hasn't been reported, open a new issue. Help us to maximize the effort we can spend fixing issues and adding new -features, by not reporting duplicate issues. Providing the following information will increase the +features, by not reporting duplicate issues. Providing the following information will increase the chances of your issue being dealt with quickly: -* **Overview of the Issue** - if an error is being thrown a non-minified stack trace helps -* **Version** - what version is affected (e.g. 0.1.2) -* **Motivation for or Use Case** - explain what are you trying to do and why the current behavior is a bug for you -* **Browsers and Operating System** - is this a problem with all browsers? -* **Reproduce the Error** - provide a live example or a unambiguous set of steps -* **Related Issues** - has a similar issue been reported before? -* **Suggest a Fix** - if you can't fix the bug yourself, perhaps you can point to what might be +- **Overview of the Issue** - if an error is being thrown a non-minified stack trace helps +- **Version** - what version is affected (e.g. 0.1.2) +- **Motivation for or Use Case** - explain what are you trying to do and why the current behavior is a bug for you +- **Browsers and Operating System** - is this a problem with all browsers? +- **Reproduce the Error** - provide a live example or a unambiguous set of steps +- **Related Issues** - has a similar issue been reported before? +- **Suggest a Fix** - if you can't fix the bug yourself, perhaps you can point to what might be causing the problem (line of code or commit) You can file new issues by providing the above information at the corresponding repository's issues link: https://github.com/microsoft/PubSec-Info-Assistant/issues/new/choose. ### Submitting a Pull Request (PR) + Before you submit your Pull Request (PR) consider the following guidelines: -* Search the repository https://github.com/microsoft/PubSec-Info-Assistant/pulls for an open or closed PR +- Search the repository https://github.com/microsoft/PubSec-Info-Assistant/pulls for an open or closed PR that relates to your submission. You don't want to duplicate effort. -* Make your changes in a new git fork: +- Make your changes in a new git fork: + +- Commit your changes using a descriptive commit message +- Push your fork to GitHub: +- In GitHub, create a pull request +- If we suggest changes then: -* Commit your changes using a descriptive commit message -* Push your fork to GitHub: -* In GitHub, create a pull request -* If we suggest changes then: - * Make the required updates. - * Rebase your fork and force push to your GitHub repository (this will update your Pull Request): + - Make the required updates. + - Rebase your fork and force push to your GitHub repository (this will update your Pull Request): ```shell git rebase master -i diff --git a/NOTICE.md b/NOTICE.md index 5a668cb97..fdfca84c4 100644 --- a/NOTICE.md +++ b/NOTICE.md @@ -1,13 +1,13 @@ # 3rd Party Open Source Software Notice -This project depends on 3rd Party Open Source Software. Below is a list of known 3rd Party Open Source Software dependencies. +This project depends on 3rd Party Open Source Software. Below is a list of known 3rd Party Open Source Software dependencies. ## Python packages From the following locations: -* app/backend/requirements.txt -* functions/requirements.txt +- app/backend/requirements.txt +- functions/requirements.txt ### NodeJS @@ -73,17 +73,15 @@ From the following locations: - ### tenacity - ## NPM Modules From the following locations: -* app/frontend/package.json +- app/frontend/package.json ### @fluentui/react diff --git a/README.md b/README.md index e79903316..01493e3d2 100644 --- a/README.md +++ b/README.md @@ -35,10 +35,9 @@ - [Code of Conduct](#code-of-conduct) - [Reporting security issues](#reporting-security-issues) - [![Open in GitHub Codespaces](https://img.shields.io/static/v1?style=for-the-badge&label=GitHub+Codespaces&message=Open&color=brightgreen&logo=github)](https://github.com/codespaces/new?hide_repo_select=true&ref=main&repo=601652366&machine=basicLinux32gb&devcontainer_path=.devcontainer%2Fdevcontainer.json&location=eastus) -Information Assistant (IA) agent template provides a starting point for organizations to build their own custom generative AI capability to extend the power of Azure OpenAI. It showcases a common scenario using large language models (LLMs) to “chat with your own data” through the [Retrieval Augmented Generation (RAG) pattern](https://learn.microsoft.com/azure/search/retrieval-augmented-generation-overview). This pattern lets you use the reasoning abilities of LLMs to generate responses based on your domain data without fine-tuning the model. +Information Assistant (IA) agent template provides a starting point for organizations to build their own custom generative AI capability to extend the power of Azure OpenAI. It showcases a common scenario using large language models (LLMs) to “chat with your own data” through the [Retrieval Augmented Generation (RAG) pattern](https://learn.microsoft.com/azure/search/retrieval-augmented-generation-overview). This pattern lets you use the reasoning abilities of LLMs to generate responses based on your domain data without fine-tuning the model. Information Assistant agent template is an end-to-end solution which is a comprehensive reference sample including documentation, source code, and deployment to allow you to take and extend for your own purposes. @@ -51,18 +50,21 @@ Please [see this video](https://aka.ms/InfoAssist/video) for use cases that may # Response generation approaches ## Work (Grounded) + It utilizes a Retrieval Augmented Generation (RAG) pattern to generate responses grounded in specific data sourced from your own dataset. By combining retrieval of relevant information with generative capabilities, it can produce responses that are not only contextually relevant but also grounded in verified data. The RAG pipeline accesses your dataset to retrieve relevant information before generating responses, ensuring accuracy and reliability. Additionally, each response includes a citation to the document chunk from which the answer is derived, providing transparency and allowing users to verify the source. This approach is particularly advantageous in domains where precision and factuality are paramount. Users can trust that the responses generated are based on reliable data sources, enhancing the credibility and usefulness of the application. Specific information on our Grounded (RAG) can be found in [RAG](docs/features/cognitive_search.md#azure-ai-search-integration). ## Ungrounded + It leverages the capabilities of a large language model (LLM) to generate responses in an ungrounded manner, without relying on external data sources or retrieval-augmented generation techniques. The LLM has been trained on a vast corpus of text data, enabling it to generate coherent and contextually relevant responses solely based on the input provided. This approach allows for open-ended and creative generation, making it suitable for tasks such as ideation, brainstorming, and exploring hypothetical scenarios. It's important to note that the generated responses are not grounded in specific factual data and should be evaluated critically, especially in domains where accuracy and verifiability are paramount. -## Work and Web +## Work and Web + It offers 2 response options: one generated through our Retrieval Augmented Generation (RAG) pipeline, and the other grounded in content directly from the web. When users opt for the RAG response, they receive a grounded answer sourced from their data, complete with citations to document chunks for transparency and verification. Conversely, selecting the web response provides access to a broader range of sources, potentially offering more diverse perspectives. Each web response is grounded in content from the web accompanied by citations of web links, allowing users to explore the original sources for further context and validation. Upon request, It can also generate a final response that compares and contrasts both responses. This comparative analysis allows users to make informed decisions based on the reliability, relevance, and context of the information provided. Specific information about our Work and Web can be found in [Web](/docs/features/features.md#bing-search-and-compare). -## Assistants -It generates response by using LLM as a reasoning engine. The key strength lies in agent's ability to autonomously reason about tasks, decompose them into steps, and determine the appropriate tools and data sources to leverage, all without the need for predefined task definitions or rigid workflows. This approach allows for a dynamic and adaptive response generation process without predefining set of tasks. It harnesses the capabilities of LLM to understand natural language queries and generate responses tailored to specific tasks. These Agents are being released in preview mode as we continue to evaluate and mitigate the potential risks associated with autonomous reasoning, such as misuse of external tools, lack of transparency, biased outputs, privacy concerns, and remote code execution vulnerabilities. With future releases, we plan to work to enhance the safety and robustness of these autonomous reasoning capabilities. Specific information on our preview agents can be found in [Assistants](/docs/features/features.md#autonomous-reasoning-with-assistants-agents). +## Assistants +It generates response by using LLM as a reasoning engine. The key strength lies in agent's ability to autonomously reason about tasks, decompose them into steps, and determine the appropriate tools and data sources to leverage, all without the need for predefined task definitions or rigid workflows. This approach allows for a dynamic and adaptive response generation process without predefining set of tasks. It harnesses the capabilities of LLM to understand natural language queries and generate responses tailored to specific tasks. These Agents are being released in preview mode as we continue to evaluate and mitigate the potential risks associated with autonomous reasoning, such as misuse of external tools, lack of transparency, biased outputs, privacy concerns, and remote code execution vulnerabilities. With future releases, we plan to work to enhance the safety and robustness of these autonomous reasoning capabilities. Specific information on our preview agents can be found in [Assistants](/docs/features/features.md#autonomous-reasoning-with-assistants-agents). ## Features @@ -87,31 +89,34 @@ For a detailed review see our [Features](./docs/features/features.md) page. **IMPORTANT:** In order to deploy and run this example, you'll need: -* **Azure account**. If you're new to Azure, [get an Azure account for free](https://azure.microsoft.com/free/cognitive-search/) and you'll get some free Azure credits to get started. -* **Azure subscription with Azure OpenAI service**. Learn more about [Azure OpenAI](https://learn.microsoft.com/en-us/azure/ai-services/openai/overview) - * **Access to one of the following Azure OpenAI models**: +- **Azure account**. If you're new to Azure, [get an Azure account for free](https://azure.microsoft.com/free/cognitive-search/) and you'll get some free Azure credits to get started. +- **Azure subscription with Azure OpenAI service**. Learn more about [Azure OpenAI](https://learn.microsoft.com/en-us/azure/ai-services/openai/overview) + + - **Access to one of the following Azure OpenAI models**: - Model Name | Supported Versions - ---|--- - gpt-35-turbo | current version - gpt-35-turbo-16k | current version - gpt-4 | current version - gpt-4-32k | current version - **gpt-4o** | current version + | Model Name | Supported Versions | + | ---------------- | ------------------ | + | gpt-35-turbo | current version | + | gpt-35-turbo-16k | current version | + | gpt-4 | current version | + | gpt-4-32k | current version | + | **gpt-4o** | current version | **Important:** Gpt-4o (2024-05-13) is recommended. The gpt-4 models may achieve better results but slower performance than gpt-35 models when used with Information Assistant. - * (Optional) **Access to the following Azure OpenAI model for embeddings**. Some open source embedding models may perform better for your specific data or use case. For the use case and data Information Assistant was tested for we recommend using the following Azure OpenAI embedding model. - - Model Name | Supported Versions - ---|--- - **text-embedding-ada-002** | current version -* **Azure account permissions**: - * Your Azure account must have `Microsoft.Authorization/roleAssignments/write` permissions, such as [Role Based Access Control Administrator](https://learn.microsoft.com/azure/role-based-access-control/built-in-roles#role-based-access-control-administrator-preview), [User Access Administrator](https://learn.microsoft.com/azure/role-based-access-control/built-in-roles#user-access-administrator), or [Owner](https://learn.microsoft.com/azure/role-based-access-control/built-in-roles#owner) on the subscription. - * Your Azure account also needs `Microsoft.Resources/deployments/write` permissions on the subscription level. - * Your Azure account also needs `microsoft.directory/applications/create` and `microsoft.directory/servicePrincipals/create`, such as [Application Administrator](https://learn.microsoft.com/entra/identity/role-based-access-control/permissions-reference#application-administrator) Entra built-in role. -* **To have accepted the Azure AI Services Responsible AI Notice** for your subscription. If you have not manually accepted this notice please follow our guide at [Accepting Azure AI Service Responsible AI Notice](./docs/deployment/accepting_responsible_ai_notice.md). -* **To have accepted the Azure AI Services Multi-service Account Responsible AI Notice** for your subscription. If you have not manually accepted this notice please follow our guide at [Accepting Azure AI Services Multi-service Account Responsible AI Notice](./docs/deployment/accepting_responsible_ai_notice_multi_service.md). -* (Optional) Have [Visual Studio Code](https://code.visualstudio.com/) installed on your development machine. If your Azure tenant and subscription have conditional access policies or device policies required, you may need to open your GitHub Codespaces in VS Code to satisfy the required polices. + + - (Optional) **Access to the following Azure OpenAI model for embeddings**. Some open source embedding models may perform better for your specific data or use case. For the use case and data Information Assistant was tested for we recommend using the following Azure OpenAI embedding model. + + | Model Name | Supported Versions | + | -------------------------- | ------------------ | + | **text-embedding-ada-002** | current version | + +- **Azure account permissions**: + - Your Azure account must have `Microsoft.Authorization/roleAssignments/write` permissions, such as [Role Based Access Control Administrator](https://learn.microsoft.com/azure/role-based-access-control/built-in-roles#role-based-access-control-administrator-preview), [User Access Administrator](https://learn.microsoft.com/azure/role-based-access-control/built-in-roles#user-access-administrator), or [Owner](https://learn.microsoft.com/azure/role-based-access-control/built-in-roles#owner) on the subscription. + - Your Azure account also needs `Microsoft.Resources/deployments/write` permissions on the subscription level. + - Your Azure account also needs `microsoft.directory/applications/create` and `microsoft.directory/servicePrincipals/create`, such as [Application Administrator](https://learn.microsoft.com/entra/identity/role-based-access-control/permissions-reference#application-administrator) Entra built-in role. +- **To have accepted the Azure AI Services Responsible AI Notice** for your subscription. If you have not manually accepted this notice please follow our guide at [Accepting Azure AI Service Responsible AI Notice](./docs/deployment/accepting_responsible_ai_notice.md). +- **To have accepted the Azure AI Services Multi-service Account Responsible AI Notice** for your subscription. If you have not manually accepted this notice please follow our guide at [Accepting Azure AI Services Multi-service Account Responsible AI Notice](./docs/deployment/accepting_responsible_ai_notice_multi_service.md). +- (Optional) Have [Visual Studio Code](https://code.visualstudio.com/) installed on your development machine. If your Azure tenant and subscription have conditional access policies or device policies required, you may need to open your GitHub Codespaces in VS Code to satisfy the required polices. ## Deployment @@ -139,7 +144,7 @@ By default, the content filters are set to filter out prompts and completions th There are optional binary classifiers/filters that can detect jailbreak risk (trying to bypass filters) as well as existing text or code pulled from public repositories. These are turned off by default, but some scenarios may require enabling the public content detection models to retain coverage under the customer copyright commitment. -The filtering configuration can be customized at the resource level, allowing customers to adjust the severity thresholds for filtering each harm category separately for prompts and completions. +The filtering configuration can be customized at the resource level, allowing customers to adjust the severity thresholds for filtering each harm category separately for prompts and completions. This provides controls for Azure customers to tailor the content filtering behavior to their needs while aiming to prevent potentially harmful generated content and any copyright violations from public content. @@ -165,25 +170,25 @@ To disable data collection, follow the instructions in the [Configure ENV files] This project has the following structure: -File/Folder | Description ----|--- -.devcontainer/ | Dockerfile, devcontainer configuration, and supporting script to enable both GitHub Codespaces and local DevContainers. -app/backend/ | The middleware part of the IA website that contains the prompt engineering and provides an API layer for the client code to pass through when communicating with the various Azure services. This code is python based and hosted as a Flask app. -app/enrichment/ | The text-based file enrichment process that handles language translation, embedding the text chunks, and inserting text chunks into the Azure AI Search hybrid index. This code is python based and is hosted as a Flask app that subscribes to an Azure Storage Queue. -app/frontend/ | The User Experience layer of the IA website. This code is Typescript based and hosted as a Vite app and compiled using npm. -azure_search/ | The configuration of the Azure Search hybrid index that is applied in the deployment scripts. -docs/adoption_workshop/ | PPT files that match what is covered in the Adoption Workshop videos in Discussions. -docs/deployment/ | Detailed documentation on how to deploy and start using Information Assistant. -docs/features/ | Detailed documentation of specific features and development level configuration for Information Assistant. -docs/ | Other supporting documentation that is primarily linked to from the other markdown files. -functions/ | The pipeline of Azure Functions that handle the document extraction and chunking as well as the custom CosmosDB logging. -infra/ | The Terraform scripts that deploy the entire IA agent template. The overall agent template is orchestrated via the `main.tf` file but most of the resource deployments are modularized under the **core** folder. -pipelines/ | Azure DevOps pipelines that can be used to enable CI/CD deployments of the agent template. -scripts/environments/ | Deployment configuration files. This is where all external configuration values will be set. -scripts/ | Supporting scripts that perform the various deployment tasks such as infrastructure deployment, Azure WebApp and Function deployments, building of the webapp and functions source code, etc. These scripts align to the available commands in the `Makefile`. -tests/ | Functional Test scripts that are used to validate a deployed Information Assistant's document processing pipelines are working as expected. -Makefile | Deployment command definitions and configurations. You can use `make help` to get more details on available commands. -README.md | Starting point for this repo. It covers overviews of the agent template, Responsible AI, Environment, Deployment, and Usage of the agent template. +| File/Folder | Description | +| ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| .devcontainer/ | Dockerfile, devcontainer configuration, and supporting script to enable both GitHub Codespaces and local DevContainers. | +| app/backend/ | The middleware part of the IA website that contains the prompt engineering and provides an API layer for the client code to pass through when communicating with the various Azure services. This code is python based and hosted as a Flask app. | +| app/enrichment/ | The text-based file enrichment process that handles language translation, embedding the text chunks, and inserting text chunks into the Azure AI Search hybrid index. This code is python based and is hosted as a Flask app that subscribes to an Azure Storage Queue. | +| app/frontend/ | The User Experience layer of the IA website. This code is Typescript based and hosted as a Vite app and compiled using npm. | +| azure_search/ | The configuration of the Azure Search hybrid index that is applied in the deployment scripts. | +| docs/adoption_workshop/ | PPT files that match what is covered in the Adoption Workshop videos in Discussions. | +| docs/deployment/ | Detailed documentation on how to deploy and start using Information Assistant. | +| docs/features/ | Detailed documentation of specific features and development level configuration for Information Assistant. | +| docs/ | Other supporting documentation that is primarily linked to from the other markdown files. | +| functions/ | The pipeline of Azure Functions that handle the document extraction and chunking as well as the custom CosmosDB logging. | +| infra/ | The Terraform scripts that deploy the entire IA agent template. The overall agent template is orchestrated via the `main.tf` file but most of the resource deployments are modularized under the **core** folder. | +| pipelines/ | Azure DevOps pipelines that can be used to enable CI/CD deployments of the agent template. | +| scripts/environments/ | Deployment configuration files. This is where all external configuration values will be set. | +| scripts/ | Supporting scripts that perform the various deployment tasks such as infrastructure deployment, Azure WebApp and Function deployments, building of the webapp and functions source code, etc. These scripts align to the available commands in the `Makefile`. | +| tests/ | Functional Test scripts that are used to validate a deployed Information Assistant's document processing pipelines are working as expected. | +| Makefile | Deployment command definitions and configurations. You can use `make help` to get more details on available commands. | +| README.md | Starting point for this repo. It covers overviews of the agent template, Responsible AI, Environment, Deployment, and Usage of the agent template. | ### References @@ -206,7 +211,7 @@ This project may contain trademarks or logos for projects, products, or services ## Microsoft Legal Notice -**Notice**. The Information Assistant agent template (the "IA") is PROVIDED "AS-IS," "WITH ALL FAULTS," AND "AS AVAILABLE," AND ARE EXCLUDED FROM THE SERVICE LEVEL AGREEMENTS AND LIMITED WARRANTY. The IA may employ lesser or different privacy and security measures than those typically present in Azure Services. Unless otherwise noted, The IA should not be used to process Personal Data or other data that is subject to legal or regulatory compliance requirements. The following terms in the DPA do not apply to the IA: Processing of Personal Data, GDPR, Data Security, and HIPAA Business Associate. We may change or discontinue the IA at any time without notice. The IA (1) is not designed, intended, or made available as legal services, (2) is not intended to substitute for professional legal counsel or judgment, and (3) should not be used in place of consulting with a qualified professional legal professional for your specific needs. Microsoft makes no warranty that the IA is accurate, up-to-date, or complete. You are wholly responsible for ensuring your own compliance with all applicable laws and regulations. +**Notice**. The Information Assistant agent template (the "IA") is PROVIDED "AS-IS," "WITH ALL FAULTS," AND "AS AVAILABLE," AND ARE EXCLUDED FROM THE SERVICE LEVEL AGREEMENTS AND LIMITED WARRANTY. The IA may employ lesser or different privacy and security measures than those typically present in Azure Services. Unless otherwise noted, The IA should not be used to process Personal Data or other data that is subject to legal or regulatory compliance requirements. The following terms in the DPA do not apply to the IA: Processing of Personal Data, GDPR, Data Security, and HIPAA Business Associate. We may change or discontinue the IA at any time without notice. The IA (1) is not designed, intended, or made available as legal services, (2) is not intended to substitute for professional legal counsel or judgment, and (3) should not be used in place of consulting with a qualified professional legal professional for your specific needs. Microsoft makes no warranty that the IA is accurate, up-to-date, or complete. You are wholly responsible for ensuring your own compliance with all applicable laws and regulations. ## Code of Conduct @@ -214,4 +219,4 @@ This project has adopted the [Microsoft Open Source Code of Conduct](https://ope ## Reporting security issues -For security concerns, please see [Security Guidelines](./SECURITY.md). \ No newline at end of file +For security concerns, please see [Security Guidelines](./SECURITY.md). diff --git a/SECURITY.md b/SECURITY.md index cbbd35b30..eabd45e26 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -12,19 +12,19 @@ If you believe you have found a security vulnerability in any Microsoft-owned re Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/opensource/security/create-report). -If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/opensource/security/pgpkey). +If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/opensource/security/pgpkey). -You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://aka.ms/opensource/security/msrc). +You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://aka.ms/opensource/security/msrc). Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: - * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) - * Full paths of source file(s) related to the manifestation of the issue - * The location of the affected source code (tag/branch/commit or direct URL) - * Any special configuration required to reproduce the issue - * Step-by-step instructions to reproduce the issue - * Proof-of-concept or exploit code (if possible) - * Impact of the issue, including how an attacker might exploit the issue +- Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) +- Full paths of source file(s) related to the manifestation of the issue +- The location of the affected source code (tag/branch/commit or direct URL) +- Any special configuration required to reproduce the issue +- Step-by-step instructions to reproduce the issue +- Proof-of-concept or exploit code (if possible) +- Impact of the issue, including how an attacker might exploit the issue This information will help us triage your report more quickly. diff --git a/SUPPORT.md b/SUPPORT.md index 9edb3faab..62f883c00 100644 --- a/SUPPORT.md +++ b/SUPPORT.md @@ -1,12 +1,12 @@ # Support -## How to file issues and get help +## How to file issues and get help This project uses [GitHub Issues](https://github.com/microsoft/PubSec-Info-Assistant/issues) to track bugs and feature requests. Please search the existing -issues before filing new issues to avoid duplicates. For new issues, file your bug or -feature request as a new Issue. +issues before filing new issues to avoid duplicates. For new issues, file your bug or +feature request as a new Issue. -Please provide as much information as possible when filing an issue (please redact any sensitive information). +Please provide as much information as possible when filing an issue (please redact any sensitive information). For help and questions about using this project, please use the [Discussion](https://github.com/microsoft/PubSec-Info-Assistant/discussions) forums on our GitHub repo page. @@ -16,6 +16,6 @@ For customer support deploying Information Assistant agent template, please reac Please refer to the [Contributing](./CONTRIBUTING.md) guidelines for acceptable methods to provide feedback which are not security related. -## Microsoft Support Policy +## Microsoft Support Policy Support for this **PROJECT** is limited to the resources listed above. diff --git a/app/frontend/index.html b/app/frontend/index.html index 4a150c69c..574c27405 100644 --- a/app/frontend/index.html +++ b/app/frontend/index.html @@ -1,7 +1,7 @@ - + @@ -19,4 +19,4 @@ href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" crossorigin="anonymous" -/> \ No newline at end of file +/> diff --git a/app/frontend/src/api/api.ts b/app/frontend/src/api/api.ts index b9261f88a..a5b24bb1b 100644 --- a/app/frontend/src/api/api.ts +++ b/app/frontend/src/api/api.ts @@ -1,490 +1,483 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -import { ChatResponse, - ChatRequest, - AllFilesUploadStatus, - GetUploadStatusRequest, - GetInfoResponse, - ActiveCitation, - GetWarningBanner, - StatusLogEntry, - StatusLogResponse, - ApplicationTitle, - GetTagsResponse, - DeleteItemRequest, - ResubmitItemRequest, - GetFeatureFlagsResponse, - getMaxCSVFileSizeType, - FetchCitationFileResponse, - } from "./models"; +import { + ChatResponse, + ChatRequest, + AllFilesUploadStatus, + GetUploadStatusRequest, + GetInfoResponse, + ActiveCitation, + GetWarningBanner, + StatusLogEntry, + StatusLogResponse, + ApplicationTitle, + GetTagsResponse, + DeleteItemRequest, + ResubmitItemRequest, + GetFeatureFlagsResponse, + getMaxCSVFileSizeType, + FetchCitationFileResponse +} from "./models"; export async function chatApi(options: ChatRequest, signal: AbortSignal): Promise { - const response = await fetch("/chat", { - method: "POST", - headers: { - "Content-Type": "application/json" - }, - body: JSON.stringify({ - history: options.history, - approach: options.approach, - overrides: { - semantic_ranker: options.overrides?.semanticRanker, - semantic_captions: options.overrides?.semanticCaptions, - top: options.overrides?.top, - temperature: options.overrides?.temperature, - prompt_template: options.overrides?.promptTemplate, - prompt_template_prefix: options.overrides?.promptTemplatePrefix, - prompt_template_suffix: options.overrides?.promptTemplateSuffix, - exclude_category: options.overrides?.excludeCategory, - suggest_followup_questions: options.overrides?.suggestFollowupQuestions, - byPassRAG: options.overrides?.byPassRAG, - user_persona: options.overrides?.userPersona, - system_persona: options.overrides?.systemPersona, - ai_persona: options.overrides?.aiPersona, - response_length: options.overrides?.responseLength, - response_temp: options.overrides?.responseTemp, - selected_folders: options.overrides?.selectedFolders, - selected_tags: options.overrides?.selectedTags - }, - citation_lookup: options.citation_lookup, - thought_chain: options.thought_chain - }), - signal: signal - }); - - if (response.status > 299 || !response.ok) { - throw Error("Unknown error"); - } - - return response; + const response = await fetch("/chat", { + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({ + history: options.history, + approach: options.approach, + overrides: { + semantic_ranker: options.overrides?.semanticRanker, + semantic_captions: options.overrides?.semanticCaptions, + top: options.overrides?.top, + temperature: options.overrides?.temperature, + prompt_template: options.overrides?.promptTemplate, + prompt_template_prefix: options.overrides?.promptTemplatePrefix, + prompt_template_suffix: options.overrides?.promptTemplateSuffix, + exclude_category: options.overrides?.excludeCategory, + suggest_followup_questions: options.overrides?.suggestFollowupQuestions, + byPassRAG: options.overrides?.byPassRAG, + user_persona: options.overrides?.userPersona, + system_persona: options.overrides?.systemPersona, + ai_persona: options.overrides?.aiPersona, + response_length: options.overrides?.responseLength, + response_temp: options.overrides?.responseTemp, + selected_folders: options.overrides?.selectedFolders, + selected_tags: options.overrides?.selectedTags + }, + citation_lookup: options.citation_lookup, + thought_chain: options.thought_chain + }), + signal: signal + }); + + if (response.status > 299 || !response.ok) { + throw Error("Unknown error"); + } + + return response; } export function getCitationFilePath(citation: string): string { - return `${encodeURIComponent(citation)}`; + return `${encodeURIComponent(citation)}`; } export async function getAllUploadStatus(options: GetUploadStatusRequest): Promise { - const response = await fetch("/getalluploadstatus", { - method: "POST", - headers: { - "Content-Type": "application/json" - }, - body: JSON.stringify({ - timeframe: options.timeframe, - state: options.state as string, - folder: options.folder as string, - tag: options.tag as string - }) - }); - - const parsedResponse: any = await response.json(); - if (response.status > 299 || !response.ok) { - throw Error(parsedResponse.error || "Unknown error"); - } - const results: AllFilesUploadStatus = {statuses: parsedResponse}; - return results; + const response = await fetch("/getalluploadstatus", { + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({ + timeframe: options.timeframe, + state: options.state as string, + folder: options.folder as string, + tag: options.tag as string + }) + }); + + const parsedResponse: any = await response.json(); + if (response.status > 299 || !response.ok) { + throw Error(parsedResponse.error || "Unknown error"); + } + const results: AllFilesUploadStatus = { statuses: parsedResponse }; + return results; } export async function deleteItem(options: DeleteItemRequest): Promise { - try { - const response = await fetch("/deleteItems", { - method: "POST", - headers: { - "Content-Type": "application/json" - }, - body: JSON.stringify({ - path: options.path - }) - }); - if (!response.ok) { - // If the response is not ok, throw an error - const errorResponse = await response.json(); - throw new Error(errorResponse.error || "Unknown error"); - } - // If the response is ok, return true - return true; - } catch (error) { - console.error("Error during deleteItem:", error); - return false; + try { + const response = await fetch("/deleteItems", { + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({ + path: options.path + }) + }); + if (!response.ok) { + // If the response is not ok, throw an error + const errorResponse = await response.json(); + throw new Error(errorResponse.error || "Unknown error"); } + // If the response is ok, return true + return true; + } catch (error) { + console.error("Error during deleteItem:", error); + return false; + } } - export async function resubmitItem(options: ResubmitItemRequest): Promise { - try { - const response = await fetch("/resubmitItems", { - method: "POST", - headers: { - "Content-Type": "application/json" - }, - body: JSON.stringify({ - path: options.path - }) - }); - if (!response.ok) { - // If the response is not ok, throw an error - const errorResponse = await response.json(); - throw new Error(errorResponse.error || "Unknown error"); - } - // If the response is ok, return true - return true; - } catch (error) { - console.error("Error during deleteItem:", error); - return false; + try { + const response = await fetch("/resubmitItems", { + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({ + path: options.path + }) + }); + if (!response.ok) { + // If the response is not ok, throw an error + const errorResponse = await response.json(); + throw new Error(errorResponse.error || "Unknown error"); } + // If the response is ok, return true + return true; + } catch (error) { + console.error("Error during deleteItem:", error); + return false; + } } - export async function getFolders(): Promise { - const response = await fetch("/getfolders", { - method: "POST", - headers: { - "Content-Type": "application/json" - }, - body: JSON.stringify({ - }) - }); - - const parsedResponse: any = await response.json(); - if (response.status > 299 || !response.ok) { - throw Error(parsedResponse.error || "Unknown error"); - } - // Assuming parsedResponse is the array of strings (folder names) we want - // Check if it's actually an array and contains strings - if (Array.isArray(parsedResponse) && parsedResponse.every(item => typeof item === 'string')) { - return parsedResponse; - } else { - throw new Error("Invalid response format"); - } + const response = await fetch("/getfolders", { + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({}) + }); + + const parsedResponse: any = await response.json(); + if (response.status > 299 || !response.ok) { + throw Error(parsedResponse.error || "Unknown error"); + } + // Assuming parsedResponse is the array of strings (folder names) we want + // Check if it's actually an array and contains strings + if (Array.isArray(parsedResponse) && parsedResponse.every(item => typeof item === "string")) { + return parsedResponse; + } else { + throw new Error("Invalid response format"); + } } - export async function getTags(): Promise { - const response = await fetch("/gettags", { - method: "POST", - headers: { - "Content-Type": "application/json" - }, - body: JSON.stringify({ - }) - }); - - const parsedResponse: any = await response.json(); - if (response.status > 299 || !response.ok) { - throw Error(parsedResponse.error || "Unknown error"); - } - // Assuming parsedResponse is the array of strings (folder names) we want - // Check if it's actually an array and contains strings - if (Array.isArray(parsedResponse) && parsedResponse.every(item => typeof item === 'string')) { - return parsedResponse; - } else { - throw new Error("Invalid response format"); - } + const response = await fetch("/gettags", { + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({}) + }); + + const parsedResponse: any = await response.json(); + if (response.status > 299 || !response.ok) { + throw Error(parsedResponse.error || "Unknown error"); + } + // Assuming parsedResponse is the array of strings (folder names) we want + // Check if it's actually an array and contains strings + if (Array.isArray(parsedResponse) && parsedResponse.every(item => typeof item === "string")) { + return parsedResponse; + } else { + throw new Error("Invalid response format"); + } } - export async function getHint(question: string): Promise { - const response = await fetch(`/getHint?question=${encodeURIComponent(question)}`, { - method: "GET", - headers: { - "Content-Type": "application/json" - } - }); - - const parsedResponse: String = await response.json(); - if (response.status > 299 || !response.ok) { - throw Error("Unknown error"); + const response = await fetch(`/getHint?question=${encodeURIComponent(question)}`, { + method: "GET", + headers: { + "Content-Type": "application/json" } + }); - return parsedResponse; -} + const parsedResponse: String = await response.json(); + if (response.status > 299 || !response.ok) { + throw Error("Unknown error"); + } + return parsedResponse; +} export function streamData(question: string): EventSource { - const encodedQuestion = encodeURIComponent(question); - const eventSource = new EventSource(`/stream?question=${encodedQuestion}`); - return eventSource; + const encodedQuestion = encodeURIComponent(question); + const eventSource = new EventSource(`/stream?question=${encodedQuestion}`); + return eventSource; } - export async function streamTdData(question: string, file: File): Promise { - let lastError; - const formData = new FormData(); - formData.append('csv', file); + let lastError; + const formData = new FormData(); + formData.append("csv", file); - const response = await fetch('/posttd', { - method: 'POST', - body: formData, - }); + const response = await fetch("/posttd", { + method: "POST", + body: formData + }); - const parsedResponse: String = await response.text(); - if (response.status > 299 || !response.ok) { - throw Error("Unknown error"); - } - - const encodedQuestion = encodeURIComponent(question); - const eventSource = new EventSource(`/tdstream?question=${encodedQuestion}`); + const parsedResponse: String = await response.text(); + if (response.status > 299 || !response.ok) { + throw Error("Unknown error"); + } - return eventSource; + const encodedQuestion = encodeURIComponent(question); + const eventSource = new EventSource(`/tdstream?question=${encodedQuestion}`); + + return eventSource; } export async function refresh(): Promise { - const response = await fetch(`/refresh?`, { - method: "POST", - headers: { - "Content-Type": "application/json" - } - }); - - const parsedResponse: String[] = await response.json(); - if (response.status > 299 || !response.ok) { - throw Error("Unknown error"); + const response = await fetch(`/refresh?`, { + method: "POST", + headers: { + "Content-Type": "application/json" } + }); - return parsedResponse; + const parsedResponse: String[] = await response.json(); + if (response.status > 299 || !response.ok) { + throw Error("Unknown error"); + } + + return parsedResponse; } export async function getTempImages(): Promise { - const response = await fetch(`/getTempImages`, { - method: "GET", - headers: { - "Content-Type": "application/json" - } - }); - - const parsedResponse: { images: string[] } = await response.json(); - if (response.status > 299 || !response.ok) { - throw Error("Unknown error"); + const response = await fetch(`/getTempImages`, { + method: "GET", + headers: { + "Content-Type": "application/json" } - const imgs = parsedResponse.images; - return imgs; + }); + + const parsedResponse: { images: string[] } = await response.json(); + if (response.status > 299 || !response.ok) { + throw Error("Unknown error"); + } + const imgs = parsedResponse.images; + return imgs; } export async function postTd(file: File): Promise { - const formData = new FormData(); - formData.append('csv', file); + const formData = new FormData(); + formData.append("csv", file); - const response = await fetch('/posttd', { - method: 'POST', - body: formData, - }); + const response = await fetch("/posttd", { + method: "POST", + body: formData + }); - const parsedResponse: String = await response.text(); - if (response.status > 299 || !response.ok) { - throw Error("Unknown error"); - } + const parsedResponse: String = await response.text(); + if (response.status > 299 || !response.ok) { + throw Error("Unknown error"); + } - return parsedResponse; + return parsedResponse; } export async function processCsvAgentResponse(question: string, file: File, retries: number = 3): Promise { - let lastError; + let lastError; - const formData = new FormData(); - formData.append('csv', file); + const formData = new FormData(); + formData.append("csv", file); - const response = await fetch('/posttd', { - method: 'POST', - body: formData, - }); + const response = await fetch("/posttd", { + method: "POST", + body: formData + }); - const parsedResponse: String = await response.text(); - if (response.status > 299 || !response.ok) { - throw Error("Unknown error"); - } - for (let i = 0; i < retries; i++) { - try { - const response = await fetch(`/process_td_agent_response?question=${encodeURIComponent(question)}`, { - method: "GET", - headers: { - "Content-Type": "application/json" - } - }); - - const parsedResponse: String = await response.json(); - if (response.status > 299 || !response.ok) { - throw Error("Unknown error"); - } - - return parsedResponse; - } catch (error) { - lastError = error; + const parsedResponse: String = await response.text(); + if (response.status > 299 || !response.ok) { + throw Error("Unknown error"); + } + for (let i = 0; i < retries; i++) { + try { + const response = await fetch(`/process_td_agent_response?question=${encodeURIComponent(question)}`, { + method: "GET", + headers: { + "Content-Type": "application/json" } + }); + + const parsedResponse: String = await response.json(); + if (response.status > 299 || !response.ok) { + throw Error("Unknown error"); + } + + return parsedResponse; + } catch (error) { + lastError = error; } + } - throw lastError; + throw lastError; } export async function processAgentResponse(question: string): Promise { - const response = await fetch(`/process_agent_response?question=${encodeURIComponent(question)}`, { - method: "GET", - headers: { - "Content-Type": "application/json" - } - }); - - const parsedResponse: String = await response.json(); - if (response.status > 299 || !response.ok) { - throw Error("Unknown error"); + const response = await fetch(`/process_agent_response?question=${encodeURIComponent(question)}`, { + method: "GET", + headers: { + "Content-Type": "application/json" } + }); - return parsedResponse; + const parsedResponse: String = await response.json(); + if (response.status > 299 || !response.ok) { + throw Error("Unknown error"); + } + + return parsedResponse; } export async function logStatus(status_log_entry: StatusLogEntry): Promise { - var response = await fetch("/logstatus", { - method: "POST", - headers: { - "Content-Type": "application/json" - }, - body: JSON.stringify({ - "path": status_log_entry.path, - "status": status_log_entry.status, - "status_classification": status_log_entry.status_classification, - "state": status_log_entry.state - }) - }); - - var parsedResponse: StatusLogResponse = await response.json(); - if (response.status > 299 || !response.ok) { - throw Error(parsedResponse.error || "Unknown error"); - } - - var results: StatusLogResponse = {status: parsedResponse.status}; - return results; + var response = await fetch("/logstatus", { + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({ + path: status_log_entry.path, + status: status_log_entry.status, + status_classification: status_log_entry.status_classification, + state: status_log_entry.state + }) + }); + + var parsedResponse: StatusLogResponse = await response.json(); + if (response.status > 299 || !response.ok) { + throw Error(parsedResponse.error || "Unknown error"); + } + + var results: StatusLogResponse = { status: parsedResponse.status }; + return results; } export async function getInfoData(): Promise { - const response = await fetch("/getInfoData", { - method: "GET", - headers: { - "Content-Type": "application/json" - } - }); - const parsedResponse: GetInfoResponse = await response.json(); - if (response.status > 299 || !response.ok) { - console.log(response); - throw Error(parsedResponse.error || "Unknown error"); + const response = await fetch("/getInfoData", { + method: "GET", + headers: { + "Content-Type": "application/json" } - console.log(parsedResponse); - return parsedResponse; + }); + const parsedResponse: GetInfoResponse = await response.json(); + if (response.status > 299 || !response.ok) { + console.log(response); + throw Error(parsedResponse.error || "Unknown error"); + } + console.log(parsedResponse); + return parsedResponse; } export async function getWarningBanner(): Promise { - const response = await fetch("/getWarningBanner", { - method: "GET", - headers: { - "Content-Type": "application/json" - } - }); - const parsedResponse: GetWarningBanner = await response.json(); - if (response.status > 299 || !response.ok) { - console.log(response); - throw Error(parsedResponse.error || "Unknown error"); + const response = await fetch("/getWarningBanner", { + method: "GET", + headers: { + "Content-Type": "application/json" } - console.log(parsedResponse); - return parsedResponse; + }); + const parsedResponse: GetWarningBanner = await response.json(); + if (response.status > 299 || !response.ok) { + console.log(response); + throw Error(parsedResponse.error || "Unknown error"); + } + console.log(parsedResponse); + return parsedResponse; } export async function getMaxCSVFileSize(): Promise { - const response = await fetch("/getMaxCSVFileSize", { - method: "GET", - headers: { - "Content-Type": "application/json" - } - }); - const parsedResponse: getMaxCSVFileSizeType = await response.json(); - if (response.status > 299 || !response.ok) { - console.log(response); - throw Error(parsedResponse.error || "Unknown error"); + const response = await fetch("/getMaxCSVFileSize", { + method: "GET", + headers: { + "Content-Type": "application/json" } - console.log(parsedResponse); - return parsedResponse; + }); + const parsedResponse: getMaxCSVFileSizeType = await response.json(); + if (response.status > 299 || !response.ok) { + console.log(response); + throw Error(parsedResponse.error || "Unknown error"); + } + console.log(parsedResponse); + return parsedResponse; } export async function getCitationObj(citation: string): Promise { - const response = await fetch(`/getcitation`, { - method: "POST", - headers: { - "Content-Type": "application/json" - }, - body: JSON.stringify({ - citation: citation - }) - }); - const parsedResponse: ActiveCitation = await response.json(); - if (response.status > 299 || !response.ok) { - console.log(response); - throw Error(parsedResponse.error || "Unknown error"); - } - return parsedResponse; + const response = await fetch(`/getcitation`, { + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({ + citation: citation + }) + }); + const parsedResponse: ActiveCitation = await response.json(); + if (response.status > 299 || !response.ok) { + console.log(response); + throw Error(parsedResponse.error || "Unknown error"); + } + return parsedResponse; } export async function getApplicationTitle(): Promise { - console.log("fetch Application Titless"); - const response = await fetch("/getApplicationTitle", { - method: "GET", - headers: { - "Content-Type": "application/json" - } - }); - - const parsedResponse: ApplicationTitle = await response.json(); - if (response.status > 299 || !response.ok) { - console.log(response); - throw Error(parsedResponse.error || "Unknown error"); + console.log("fetch Application Titless"); + const response = await fetch("/getApplicationTitle", { + method: "GET", + headers: { + "Content-Type": "application/json" } - console.log(parsedResponse); - return parsedResponse; + }); + + const parsedResponse: ApplicationTitle = await response.json(); + if (response.status > 299 || !response.ok) { + console.log(response); + throw Error(parsedResponse.error || "Unknown error"); + } + console.log(parsedResponse); + return parsedResponse; } export async function getAllTags(): Promise { - const response = await fetch("/getalltags", { - method: "GET", - headers: { - "Content-Type": "application/json" - } - }); - - const parsedResponse: any = await response.json(); - if (response.status > 299 || !response.ok) { - console.log(response); - throw Error(parsedResponse.error || "Unknown error"); + const response = await fetch("/getalltags", { + method: "GET", + headers: { + "Content-Type": "application/json" } - var results: GetTagsResponse = {tags: parsedResponse}; - return results; + }); + + const parsedResponse: any = await response.json(); + if (response.status > 299 || !response.ok) { + console.log(response); + throw Error(parsedResponse.error || "Unknown error"); + } + var results: GetTagsResponse = { tags: parsedResponse }; + return results; } export async function getFeatureFlags(): Promise { - const response = await fetch("/getFeatureFlags", { - method: "GET", - headers: { - "Content-Type": "application/json" - } - }); - const parsedResponse: GetFeatureFlagsResponse = await response.json(); - if (response.status > 299 || !response.ok) { - console.log(response); - throw Error(parsedResponse.error || "Unknown error"); + const response = await fetch("/getFeatureFlags", { + method: "GET", + headers: { + "Content-Type": "application/json" } - console.log(parsedResponse); - return parsedResponse; + }); + const parsedResponse: GetFeatureFlagsResponse = await response.json(); + if (response.status > 299 || !response.ok) { + console.log(response); + throw Error(parsedResponse.error || "Unknown error"); + } + console.log(parsedResponse); + return parsedResponse; } -export async function fetchCitationFile(filePath: string) : Promise { - const response = await fetch('/get-file', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ path: filePath }), - }); - - if (response.status > 299 || !response.ok) { - console.log(response); - throw Error('Failed to fetch file' + response.statusText); - } - const fileResponse : FetchCitationFileResponse = {file_blob : await response.blob()}; - return fileResponse; -} \ No newline at end of file +export async function fetchCitationFile(filePath: string): Promise { + const response = await fetch("/get-file", { + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({ path: filePath }) + }); + + if (response.status > 299 || !response.ok) { + console.log(response); + throw Error("Failed to fetch file" + response.statusText); + } + const fileResponse: FetchCitationFileResponse = { file_blob: await response.blob() }; + return fileResponse; +} diff --git a/app/frontend/src/api/models.ts b/app/frontend/src/api/models.ts index 667916a29..2cc763eec 100644 --- a/app/frontend/src/api/models.ts +++ b/app/frontend/src/api/models.ts @@ -2,222 +2,222 @@ // Licensed under the MIT license. export const enum ChatMode { - WorkOnly = 0, - WorkPlusWeb = 1, - Ungrounded = 2 + WorkOnly = 0, + WorkPlusWeb = 1, + Ungrounded = 2 } export const enum Approaches { - RetrieveThenRead = 0, - ReadRetrieveRead = 1, - ReadDecomposeAsk = 2, - GPTDirect = 3, - ChatWebRetrieveRead = 4, - CompareWorkWithWeb = 5, - CompareWebWithWork = 6 + RetrieveThenRead = 0, + ReadRetrieveRead = 1, + ReadDecomposeAsk = 2, + GPTDirect = 3, + ChatWebRetrieveRead = 4, + CompareWorkWithWeb = 5, + CompareWebWithWork = 6 } export type ChatRequestOverrides = { - semanticRanker?: boolean; - semanticCaptions?: boolean; - excludeCategory?: string; - top?: number; - temperature?: number; - promptTemplate?: string; - promptTemplatePrefix?: string; - promptTemplateSuffix?: string; - suggestFollowupQuestions?: boolean; - byPassRAG?: boolean; - userPersona?: string; - systemPersona?: string; - aiPersona?: string; - responseLength?: number; - responseTemp?: number; - selectedFolders?: string; - selectedTags?: string; + semanticRanker?: boolean; + semanticCaptions?: boolean; + excludeCategory?: string; + top?: number; + temperature?: number; + promptTemplate?: string; + promptTemplatePrefix?: string; + promptTemplateSuffix?: string; + suggestFollowupQuestions?: boolean; + byPassRAG?: boolean; + userPersona?: string; + systemPersona?: string; + aiPersona?: string; + responseLength?: number; + responseTemp?: number; + selectedFolders?: string; + selectedTags?: string; }; export type ChatResponse = { - answer: string; - thoughts: string | null; - data_points: string[]; - approach: Approaches; - thought_chain: { [key: string]: string }; - work_citation_lookup: { [key: string]: { citation: string; source_path: string; page_number: string } }; - web_citation_lookup: { [key: string]: { citation: string; source_path: string; page_number: string } }; - error?: string; + answer: string; + thoughts: string | null; + data_points: string[]; + approach: Approaches; + thought_chain: { [key: string]: string }; + work_citation_lookup: { [key: string]: { citation: string; source_path: string; page_number: string } }; + web_citation_lookup: { [key: string]: { citation: string; source_path: string; page_number: string } }; + error?: string; }; export type ChatTurn = { - user: string; - bot?: string; + user: string; + bot?: string; }; export type Citation = { - citation: string; - source_path: string; - page_number: string; // or number, if page_number is intended to be a numeric value - } + citation: string; + source_path: string; + page_number: string; // or number, if page_number is intended to be a numeric value +}; export type ChatRequest = { - history: ChatTurn[]; - approach: Approaches; - overrides?: ChatRequestOverrides; - citation_lookup: { [key: string]: { citation: string; source_path: string; page_number: string } }; - thought_chain: { [key: string]: string }; + history: ChatTurn[]; + approach: Approaches; + overrides?: ChatRequestOverrides; + citation_lookup: { [key: string]: { citation: string; source_path: string; page_number: string } }; + thought_chain: { [key: string]: string }; }; export type BlobClientUrlResponse = { - url: string; - error?: string; + url: string; + error?: string; }; export type FileUploadBasicStatus = { - id: string; - file_path: string; - file_name: string; - state: string; - start_timestamp: string; - state_description: string; - state_timestamp: string; - status_updates: StatusUpdates[]; - tags: string; -} + id: string; + file_path: string; + file_name: string; + state: string; + start_timestamp: string; + state_description: string; + state_timestamp: string; + status_updates: StatusUpdates[]; + tags: string; +}; export type StatusUpdates = { - status: string; - status_timestamp: string; - status_classification: string; -} + status: string; + status_timestamp: string; + status_classification: string; +}; export type AllFilesUploadStatus = { - statuses: FileUploadBasicStatus[]; -} + statuses: FileUploadBasicStatus[]; +}; export type AllFolders = { - folders: string; -} + folders: string; +}; export type GetUploadStatusRequest = { - timeframe: number; - state: FileState; - folder: string; - tag: string -} + timeframe: number; + state: FileState; + folder: string; + tag: string; +}; export type DeleteItemRequest = { - path: string -} + path: string; +}; export type ResubmitItemRequest = { - path: string -} + path: string; +}; -// These keys need to match case with the defined Enum in the +// These keys need to match case with the defined Enum in the // shared code (functions/shared_code/status_log.py) export const enum FileState { - All = "ALL", - Processing = "PROCESSING", - Indexing = "INDEXING", - Skipped = "SKIPPED", - Queued = "QUEUED", - Complete = "COMPLETE", - Error = "ERROR", - THROTTLED = "THROTTLED", - UPLOADED = "UPLOADED", - DELETING = "DELETING", - DELETED = "DELETED" + All = "ALL", + Processing = "PROCESSING", + Indexing = "INDEXING", + Skipped = "SKIPPED", + Queued = "QUEUED", + Complete = "COMPLETE", + Error = "ERROR", + THROTTLED = "THROTTLED", + UPLOADED = "UPLOADED", + DELETING = "DELETING", + DELETED = "DELETED" } export type GetInfoResponse = { - AZURE_OPENAI_SERVICE: string; - AZURE_OPENAI_CHATGPT_DEPLOYMENT: string; - AZURE_OPENAI_MODEL_NAME: string; - AZURE_OPENAI_MODEL_VERSION: string; - AZURE_SEARCH_SERVICE: string; - AZURE_SEARCH_INDEX: string; - TARGET_LANGUAGE: string; - USE_AZURE_OPENAI_EMBEDDINGS: boolean; - EMBEDDINGS_DEPLOYMENT: string; - EMBEDDINGS_MODEL_NAME: string; - EMBEDDINGS_MODEL_VERSION: string; - error?: string; + AZURE_OPENAI_SERVICE: string; + AZURE_OPENAI_CHATGPT_DEPLOYMENT: string; + AZURE_OPENAI_MODEL_NAME: string; + AZURE_OPENAI_MODEL_VERSION: string; + AZURE_SEARCH_SERVICE: string; + AZURE_SEARCH_INDEX: string; + TARGET_LANGUAGE: string; + USE_AZURE_OPENAI_EMBEDDINGS: boolean; + EMBEDDINGS_DEPLOYMENT: string; + EMBEDDINGS_MODEL_NAME: string; + EMBEDDINGS_MODEL_VERSION: string; + error?: string; }; export type ActiveCitation = { - file_name: string; - file_uri: string; - processed_datetime: string; - title: string; - section: string; - pages: number[]; - token_count: number; - content: string; - error?: string; -} + file_name: string; + file_uri: string; + processed_datetime: string; + title: string; + section: string; + pages: number[]; + token_count: number; + content: string; + error?: string; +}; export type GetWarningBanner = { - WARNING_BANNER_TEXT: string; - error?: string; + WARNING_BANNER_TEXT: string; + error?: string; }; export type getMaxCSVFileSizeType = { - MAX_CSV_FILE_SIZE: string; - error?: string; + MAX_CSV_FILE_SIZE: string; + error?: string; }; -// These keys need to match case with the defined Enum in the +// These keys need to match case with the defined Enum in the // shared code (functions/shared_code/status_log.py) export const enum StatusLogClassification { - Debug = "Debug", - Info = "Info", - Error = "Error" + Debug = "Debug", + Info = "Info", + Error = "Error" } -// These keys need to match case with the defined Enum in the +// These keys need to match case with the defined Enum in the // shared code (functions/shared_code/status_log.py) export const enum StatusLogState { - Processing = "Processing", - Indexing = "Indexing", - Skipped = "Skipped", - Queued = "Queued", - Complete = "Complete", - Error = "Error", - Throttled = "Throttled", - Uploaded = "Uploaded", - All = "All" + Processing = "Processing", + Indexing = "Indexing", + Skipped = "Skipped", + Queued = "Queued", + Complete = "Complete", + Error = "Error", + Throttled = "Throttled", + Uploaded = "Uploaded", + All = "All" } export type StatusLogEntry = { - path: string; - status: string; - status_classification: StatusLogClassification; - state: StatusLogState; -} + path: string; + status: string; + status_classification: StatusLogClassification; + state: StatusLogState; +}; export type StatusLogResponse = { - status: number; - error?: string; -} + status: number; + error?: string; +}; export type ApplicationTitle = { - APPLICATION_TITLE: string; - error?: string; + APPLICATION_TITLE: string; + error?: string; }; export type GetTagsResponse = { - tags: string; - error?: string; -} + tags: string; + error?: string; +}; export type GetFeatureFlagsResponse = { - ENABLE_WEB_CHAT: boolean; - ENABLE_UNGROUNDED_CHAT: boolean; - ENABLE_MATH_ASSISTANT: boolean; - ENABLE_TABULAR_DATA_ASSISTANT: boolean; - error?: string; -} + ENABLE_WEB_CHAT: boolean; + ENABLE_UNGROUNDED_CHAT: boolean; + ENABLE_MATH_ASSISTANT: boolean; + ENABLE_TABULAR_DATA_ASSISTANT: boolean; + error?: string; +}; export type FetchCitationFileResponse = { - file_blob: Blob; - error?: string; -} \ No newline at end of file + file_blob: Blob; + error?: string; +}; diff --git a/app/frontend/src/components/AnalysisPanel/AnalysisPanel.module.css b/app/frontend/src/components/AnalysisPanel/AnalysisPanel.module.css index 9e14a748e..9903c2a39 100644 --- a/app/frontend/src/components/AnalysisPanel/AnalysisPanel.module.css +++ b/app/frontend/src/components/AnalysisPanel/AnalysisPanel.module.css @@ -2,8 +2,8 @@ Licensed under the MIT license. */ .thoughtProcess { - font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace; - word-wrap: break-word; - padding-top: 12px; - padding-bottom: 12px; + font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace; + word-wrap: break-word; + padding-top: 12px; + padding-bottom: 12px; } diff --git a/app/frontend/src/components/AnalysisPanel/AnalysisPanel.tsx b/app/frontend/src/components/AnalysisPanel/AnalysisPanel.tsx index 99fa65ae2..928ea4b12 100644 --- a/app/frontend/src/components/AnalysisPanel/AnalysisPanel.tsx +++ b/app/frontend/src/components/AnalysisPanel/AnalysisPanel.tsx @@ -2,11 +2,11 @@ // Licensed under the MIT license. import { useEffect, useState } from "react"; -import { IPivotItemProps, IRefObject, ITooltipHost, Pivot, PivotItem, Text, TooltipHost} from "@fluentui/react"; -import { Label } from '@fluentui/react/lib/Label'; -import { Separator } from '@fluentui/react/lib/Separator'; +import { IPivotItemProps, IRefObject, ITooltipHost, Pivot, PivotItem, Text, TooltipHost } from "@fluentui/react"; +import { Label } from "@fluentui/react/lib/Label"; +import { Separator } from "@fluentui/react/lib/Separator"; import DOMPurify from "dompurify"; -import ReactMarkdown from 'react-markdown'; +import ReactMarkdown from "react-markdown"; import styles from "./AnalysisPanel.module.css"; @@ -16,233 +16,241 @@ import { AnalysisPanelTabs } from "./AnalysisPanelTabs"; import React from "react"; interface Props { - className: string; - activeTab: AnalysisPanelTabs; - onActiveTabChanged: (tab: AnalysisPanelTabs) => void; - activeCitation: string | undefined; - sourceFile: string | undefined; - pageNumber: string | undefined; - citationHeight: string; - answer: ChatResponse; + className: string; + activeTab: AnalysisPanelTabs; + onActiveTabChanged: (tab: AnalysisPanelTabs) => void; + activeCitation: string | undefined; + sourceFile: string | undefined; + pageNumber: string | undefined; + citationHeight: string; + answer: ChatResponse; } const pivotItemDisabledStyle: React.CSSProperties = { - color: 'grey' - + color: "grey" }; export const AnalysisPanel = ({ answer, activeTab, activeCitation, sourceFile, pageNumber, citationHeight, className, onActiveTabChanged }: Props) => { - - const [innerPivotTab, setInnerPivotTab] = useState('indexedFile'); - const [activeCitationObj, setActiveCitationObj] = useState(); - const [markdownContent, setMarkdownContent] = useState(''); - const [plainTextContent, setPlainTextContent] = useState(''); - const [sourceFileBlob, setSourceFileBlob] = useState(); - const [sourceFileUrl, setSourceFileUrl] = useState(''); - const [isFetchingSourceFileBlob, setIsFetchingSourceFileBlob] = useState(false); - const isDisabledThoughtProcessTab: boolean = !answer.thoughts; - const isDisabledSupportingContentTab: boolean = !answer.data_points?.length; - const isDisabledCitationTab: boolean = !activeCitation; - // the first split on ? separates the file from the sas token, then the second split on . separates the file extension - const sourceFileExt: any = sourceFile?.split(".").pop(); - const sanitizedThoughts = DOMPurify.sanitize(answer.thoughts!); - - const tooltipRef2 = React.useRef(null); - const tooltipRef3 = React.useRef(null); - - const onRenderItemLink = (content: string | JSX.Element | JSX.Element[] | undefined, tooltipRef: IRefObject | undefined, shouldRender: boolean) => (properties: IPivotItemProps | undefined, - nullableDefaultRenderer?: (props: IPivotItemProps) => JSX.Element | null) => { - if (!properties || !nullableDefaultRenderer) { - return null; // or handle the undefined case appropriately - } - return shouldRender ? ( - - {nullableDefaultRenderer(properties)} - - ) : ( - nullableDefaultRenderer(properties) - ); + const [innerPivotTab, setInnerPivotTab] = useState("indexedFile"); + const [activeCitationObj, setActiveCitationObj] = useState(); + const [markdownContent, setMarkdownContent] = useState(""); + const [plainTextContent, setPlainTextContent] = useState(""); + const [sourceFileBlob, setSourceFileBlob] = useState(); + const [sourceFileUrl, setSourceFileUrl] = useState(""); + const [isFetchingSourceFileBlob, setIsFetchingSourceFileBlob] = useState(false); + const isDisabledThoughtProcessTab: boolean = !answer.thoughts; + const isDisabledSupportingContentTab: boolean = !answer.data_points?.length; + const isDisabledCitationTab: boolean = !activeCitation; + // the first split on ? separates the file from the sas token, then the second split on . separates the file extension + const sourceFileExt: any = sourceFile?.split(".").pop(); + const sanitizedThoughts = DOMPurify.sanitize(answer.thoughts!); + + const tooltipRef2 = React.useRef(null); + const tooltipRef3 = React.useRef(null); + + const onRenderItemLink = + (content: string | JSX.Element | JSX.Element[] | undefined, tooltipRef: IRefObject | undefined, shouldRender: boolean) => + (properties: IPivotItemProps | undefined, nullableDefaultRenderer?: (props: IPivotItemProps) => JSX.Element | null) => { + if (!properties || !nullableDefaultRenderer) { + return null; // or handle the undefined case appropriately + } + return shouldRender ? ( + + {nullableDefaultRenderer(properties)} + + ) : ( + nullableDefaultRenderer(properties) + ); }; - - let sourceFileBlobPromise: Promise | null = null; - async function fetchCitationSourceFile(): Promise { - if (sourceFile) { - const results = await fetchCitationFile(sourceFile); - setSourceFileBlob(results.file_blob); - setSourceFileUrl(URL.createObjectURL(results.file_blob)); + + let sourceFileBlobPromise: Promise | null = null; + async function fetchCitationSourceFile(): Promise { + if (sourceFile) { + const results = await fetchCitationFile(sourceFile); + setSourceFileBlob(results.file_blob); + setSourceFileUrl(URL.createObjectURL(results.file_blob)); + } + } + + function getCitationURL() { + const fetchSourceFileBlob = async () => { + if (sourceFileBlob === undefined) { + if (!isFetchingSourceFileBlob) { + setIsFetchingSourceFileBlob(true); + sourceFileBlobPromise = fetchCitationSourceFile().finally(() => { + setIsFetchingSourceFileBlob(false); + }); } + await sourceFileBlobPromise; + } + }; + fetchSourceFileBlob(); + return sourceFileUrl; + } + + async function fetchActiveCitationObj() { + try { + const citationObj = await getCitationObj(activeCitation as string); + setActiveCitationObj(citationObj); + console.log(citationObj); + } catch (error) { + // Handle the error here + console.log(error); } + } - function getCitationURL() { - const fetchSourceFileBlob = async () => { - if (sourceFileBlob === undefined) { - if (!isFetchingSourceFileBlob) { - setIsFetchingSourceFileBlob(true); - sourceFileBlobPromise = fetchCitationSourceFile().finally(() => { - setIsFetchingSourceFileBlob(false); - }); - } - await sourceFileBlobPromise; - } - }; - fetchSourceFileBlob(); - return sourceFileUrl; + useEffect(() => { + if (!sourceFile) { + return; } + const fetchMarkdownContent = async () => { + try { + const response = await fetch(getCitationURL()); + const content = await response.text(); + setMarkdownContent(content); + } catch (error) { + console.error("Error fetching Markdown content:", error); + } + }; - async function fetchActiveCitationObj() { - try { - const citationObj = await getCitationObj(activeCitation as string); - setActiveCitationObj(citationObj); - console.log(citationObj); - } catch (error) { - // Handle the error here - console.log(error); - } + fetchMarkdownContent(); + }, [sourceFileBlob, sourceFileExt]); + + useEffect(() => { + const fetchPlainTextContent = async () => { + try { + const response = await fetch(getCitationURL()); + const content = await response.text(); + setPlainTextContent(content); + } catch (error) { + console.error("Error fetching plain text content:", error); + } + }; + + if (["json", "txt", "xml"].includes(sourceFileExt)) { + fetchPlainTextContent(); } + }, [sourceFileBlob, sourceFileExt]); + useEffect(() => { + if (activeCitation) { + setInnerPivotTab("indexedFile"); + } + fetchActiveCitationObj(); + const fetchSourceFileBlob = async () => { + if (!isFetchingSourceFileBlob) { + setIsFetchingSourceFileBlob(true); + sourceFileBlobPromise = fetchCitationSourceFile().finally(() => { + setIsFetchingSourceFileBlob(false); + }); + } + await sourceFileBlobPromise; + }; + fetchSourceFileBlob(); + }, [activeCitation]); - useEffect(() => { - if (!sourceFile) { - return; - } - const fetchMarkdownContent = async () => { - try { - const response = await fetch(getCitationURL()); - const content = await response.text(); - setMarkdownContent(content); - } catch (error) { - console.error('Error fetching Markdown content:', error); - } - }; - - fetchMarkdownContent(); - }, [sourceFileBlob, sourceFileExt]); - - useEffect(() => { - const fetchPlainTextContent = async () => { - try { - const response = await fetch(getCitationURL()); - const content = await response.text(); - setPlainTextContent(content); - } catch (error) { - console.error('Error fetching plain text content:', error); - } - }; - - if (["json", "txt", "xml"].includes(sourceFileExt)) { - fetchPlainTextContent(); - } - }, [sourceFileBlob, sourceFileExt]); + return ( + pivotItem && onActiveTabChanged(pivotItem.props.itemKey! as AnalysisPanelTabs)} + > + +
+
- useEffect(() => { - if (activeCitation) { - setInnerPivotTab('indexedFile'); - } - fetchActiveCitationObj(); - const fetchSourceFileBlob = async () => { - - if (!isFetchingSourceFileBlob) { - setIsFetchingSourceFileBlob(true); - sourceFileBlobPromise = fetchCitationSourceFile().finally(() => { - setIsFetchingSourceFileBlob(false); - }); - } - await sourceFileBlobPromise; - - }; - fetchSourceFileBlob(); - - }, [activeCitation]); - - return ( + + + + + pivotItem && onActiveTabChanged(pivotItem.props.itemKey! as AnalysisPanelTabs)} + className={className} + selectedKey={innerPivotTab} + onLinkClick={item => { + if (item) { + setInnerPivotTab(item.props.itemKey!); + } else { + // Handle the case where item is undefined + console.warn("Item is undefined"); + } + }} > - -
-
- - - - - - - - - { - if (item) { - setInnerPivotTab(item.props.itemKey!); - } else { - // Handle the case where item is undefined - console.warn('Item is undefined'); - } - }}> - - {activeCitationObj === undefined ? ( - Loading... - ) : - ( -
- Metadata - {activeCitationObj.file_name} - {activeCitationObj.file_uri} - {activeCitationObj.title} - {activeCitationObj.section} - {activeCitationObj.pages?.join(",")} - {activeCitationObj.token_count} - Content - {activeCitationObj.content} -
- )} -
- - {getCitationURL() === '' ? ( - Loading... - ) : ["docx", "xlsx", "pptx"].includes(sourceFileExt) ? ( - // Treat other Office formats like "xlsx" for the Office Online Viewer -