From 89c050f411df794e4540d68dfee435a1a1db8313 Mon Sep 17 00:00:00 2001 From: Freddy Ayala Date: Sun, 6 Oct 2024 15:15:30 +0200 Subject: [PATCH 01/12] Added new feature to itnegrate azure services using managed identities --- .gitignore | 1 + infra/main.bicep | 5 + infra/main.json | 250 ++++++++++++++++-- infra/resources.bicep | 150 ++++++++++- src/features/common/services/ai-search.ts | 51 ++-- src/features/common/services/azure-storage.ts | 27 +- src/features/common/services/cosmos.ts | 30 ++- .../common/services/document-intelligence.ts | 26 +- src/features/common/services/openai.ts | 127 +++++---- src/package.json | 8 +- 10 files changed, 550 insertions(+), 125 deletions(-) diff --git a/.gitignore b/.gitignore index 83208207f..0909d22b1 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,4 @@ next-env.d.ts .azure/ infra/aad_setup.sh .vscode +infra/main.parameters.example.json diff --git a/infra/main.bicep b/infra/main.bicep index f15740ad5..13771442a 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -60,6 +60,10 @@ resource rg 'Microsoft.Resources/resourceGroups@2021-04-01' = { tags: tags } +//Activates/Deactivates Authentication by key, if true it will enforce RBAC using managed identities +param disableLocalAuth bool = false + + module resources 'resources.bicep' = { name: 'all-resources' scope: rg @@ -88,6 +92,7 @@ module resources 'resources.bicep' = { storageServiceSku: storageServiceSku storageServiceImageContainerName: storageServiceImageContainerName location: location + disableLocalAuth:disableLocalAuth } } diff --git a/infra/main.json b/infra/main.json index 397308da6..5965ae490 100644 --- a/infra/main.json +++ b/infra/main.json @@ -4,8 +4,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "18214004695586675733" + "version": "0.30.23.60470", + "templateHash": "3256939710395371274" } }, "parameters": { @@ -28,12 +28,27 @@ "type": "string", "allowedValues": [ "australiaeast", + "brazilsouth", "canadaeast", + "eastus", + "eastus2", "francecentral", + "germanywestcentral", + "japaneast", + "koreacentral", + "northcentralus", + "norwayeast", + "polandcentral", + "spaincentral", + "southafricanorth", + "southcentralus", "southindia", - "uksouth", "swedencentral", - "westus" + "switzerlandnorth", + "uksouth", + "westeurope", + "westus", + "westus3" ], "metadata": { "azd": { @@ -48,7 +63,7 @@ }, "openAIApiVersion": { "type": "string", - "defaultValue": "2024-05-13" + "defaultValue": "2024-08-01-preview" }, "chatGptDeploymentCapacity": { "type": "int", @@ -81,7 +96,9 @@ "dalleLocation": { "type": "string", "allowedValues": [ - "swedencentral" + "swedencentral", + "eastus", + "australiaeast" ], "metadata": { "description": "Location for the OpenAI DALL-E 3 instance resource group" @@ -128,6 +145,10 @@ "resourceGroupName": { "type": "string", "defaultValue": "" + }, + "disableLocalAuth": { + "type": "bool", + "defaultValue": false } }, "variables": { @@ -226,6 +247,9 @@ }, "location": { "value": "[parameters('location')]" + }, + "disableLocalAuth": { + "value": "[parameters('disableLocalAuth')]" } }, "template": { @@ -234,8 +258,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "18109441359842852578" + "version": "0.30.23.60470", + "templateHash": "10505282472173598922" } }, "parameters": { @@ -317,6 +341,10 @@ "type": "string", "defaultValue": "[resourceGroup().location]" }, + "disableLocalAuth": { + "type": "bool", + "defaultValue": false + }, "nextAuthHash": { "type": "securestring", "defaultValue": "[uniqueString(newGuid())]" @@ -324,6 +352,13 @@ "tags": { "type": "object", "defaultValue": {} + }, + "roleDefinitionName": { + "type": "string", + "defaultValue": "Azure Cosmos DB for NoSQL Data Plane Owner", + "metadata": { + "description": "Name of the role definition." + } } }, "variables": { @@ -335,7 +370,8 @@ "search_name": "[toLower(format('{0}search{1}', parameters('name'), parameters('resourceToken')))]", "webapp_name": "[toLower(format('{0}-webapp-{1}', parameters('name'), parameters('resourceToken')))]", "appservice_name": "[toLower(format('{0}-app-{1}', parameters('name'), parameters('resourceToken')))]", - "storage_prefix": "[take(parameters('name'), 8)]", + "clean_name": "[replace(replace(parameters('name'), '-', ''), '_', '')]", + "storage_prefix": "[take(variables('clean_name'), 8)]", "storage_name": "[toLower(format('{0}sto{1}', variables('storage_prefix'), parameters('resourceToken')))]", "kv_prefix": "[take(parameters('name'), 7)]", "keyVaultName": "[toLower(format('{0}-kv-{1}', variables('kv_prefix'), parameters('resourceToken')))]", @@ -368,7 +404,15 @@ }, "capacity": "[parameters('embeddingDeploymentCapacity')]" } - ] + ], + "cosmosDbContributorRoleId": "5bd9cd88-fe45-4216-938b-f97437e15450", + "cosmosDbOperatorRoleId": "230815da-be43-4aae-9cb4-875f7bd000aa", + "cognitiveServicesContributorRoleId": "25fbc0a9-bd7c-42a3-aa1a-3b75d497ee68", + "cognitiveServicesUserRoleId": "a97b65f3-24c7-4388-baec-2e87135dc908", + "storageBlobDataContributorRoleId": "ba92f5b4-2d11-453d-a403-e96b0029c9fe", + "searchServiceContributorRoleId": "7ca78c08-252a-4471-8644-bb5ff32d4ba0", + "cognitiveServicesOpenAIContributorRoleId": "a001fd3d-188f-4b5d-821b-7da978bf7442", + "searchIndexDataContributorRoleId": "8ebe5a00-799e-43f5-93ac-243d3dce84a7" }, "resources": [ { @@ -573,6 +617,10 @@ "ftpsState": "Disabled", "minTlsVersion": "1.2", "appSettings": [ + { + "name": "USE_MANAGED_IDENTITIES", + "value": "[parameters('disableLocalAuth')]" + }, { "name": "AZURE_KEY_VAULT_NAME", "value": "[variables('keyVaultName')]" @@ -583,7 +631,7 @@ }, { "name": "AZURE_OPENAI_API_KEY", - "value": "[format('@Microsoft.KeyVault(VaultName={0};SecretName={1})', variables('keyVaultName'), 'AZURE-OPENAI-API-KEY')]" + "value": "[if(parameters('disableLocalAuth'), '', format('@Microsoft.KeyVault(VaultName={0};SecretName={1})', variables('keyVaultName'), 'AZURE-OPENAI-API-KEY'))]" }, { "name": "AZURE_OPENAI_API_INSTANCE_NAME", @@ -756,6 +804,7 @@ "kind": "GlobalDocumentDB", "properties": { "databaseAccountOfferType": "Standard", + "disableLocalAuth": "[parameters('disableLocalAuth')]", "locations": [ { "locationName": "[parameters('location')]", @@ -825,7 +874,8 @@ "kind": "FormRecognizer", "properties": { "customSubDomainName": "[variables('form_recognizer_name')]", - "publicNetworkAccess": "Enabled" + "publicNetworkAccess": "Enabled", + "disableLocalAuth": "[parameters('disableLocalAuth')]" }, "sku": { "name": "[parameters('formRecognizerSkuName')]" @@ -840,7 +890,8 @@ "properties": { "partitionCount": 1, "publicNetworkAccess": "enabled", - "replicaCount": 1 + "replicaCount": 1, + "disableLocalAuth": "[parameters('disableLocalAuth')]" }, "sku": { "name": "[parameters('searchServiceSkuName')]" @@ -855,7 +906,8 @@ "kind": "OpenAI", "properties": { "customSubDomainName": "[variables('openai_name')]", - "publicNetworkAccess": "Enabled" + "publicNetworkAccess": "Enabled", + "disableLocalAuth": "[parameters('disableLocalAuth')]" }, "sku": { "name": "[parameters('openAiSkuName')]" @@ -872,8 +924,7 @@ "apiVersion": "2023-05-01", "name": "[format('{0}/{1}', variables('openai_name'), variables('llmDeployments')[copyIndex()].name)]", "properties": { - "model": "[variables('llmDeployments')[copyIndex()].model]", - "raiPolicyName": "[if(contains(variables('llmDeployments')[copyIndex()], 'raiPolicyName'), variables('llmDeployments')[copyIndex()].raiPolicyName, null())]" + "model": "[variables('llmDeployments')[copyIndex()].model]" }, "sku": "[if(contains(variables('llmDeployments')[copyIndex()], 'sku'), variables('llmDeployments')[copyIndex()].sku, createObject('name', 'Standard', 'capacity', variables('llmDeployments')[copyIndex()].capacity))]", "dependsOn": [ @@ -889,7 +940,8 @@ "kind": "OpenAI", "properties": { "customSubDomainName": "[variables('openai_dalle_name')]", - "publicNetworkAccess": "Enabled" + "publicNetworkAccess": "Enabled", + "disableLocalAuth": "[parameters('disableLocalAuth')]" }, "sku": { "name": "[parameters('openAiSkuName')]" @@ -917,7 +969,169 @@ "location": "[parameters('location')]", "tags": "[parameters('tags')]", "kind": "StorageV2", - "sku": "[parameters('storageServiceSku')]" + "sku": "[parameters('storageServiceSku')]", + "properties": { + "allowSharedKeyAccess": "[not(parameters('disableLocalAuth'))]" + } + }, + { + "condition": "[parameters('disableLocalAuth')]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2020-04-01-preview", + "scope": "[format('Microsoft.DocumentDB/databaseAccounts/{0}', variables('cosmos_name'))]", + "name": "[guid(resourceId('Microsoft.DocumentDB/databaseAccounts', variables('cosmos_name')), variables('cosmosDbContributorRoleId'), 'role-assignment-cosmosDb')]", + "properties": { + "principalId": "[reference(resourceId('Microsoft.Web/sites', variables('webapp_name')), '2020-06-01', 'full').identity.principalId]", + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('cosmosDbContributorRoleId'))]" + }, + "dependsOn": [ + "[resourceId('Microsoft.DocumentDB/databaseAccounts', variables('cosmos_name'))]", + "[resourceId('Microsoft.Web/sites', variables('webapp_name'))]" + ] + }, + { + "condition": "[parameters('disableLocalAuth')]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2020-04-01-preview", + "scope": "[format('Microsoft.DocumentDB/databaseAccounts/{0}', variables('cosmos_name'))]", + "name": "[guid(resourceId('Microsoft.DocumentDB/databaseAccounts', variables('cosmos_name')), variables('cosmosDbOperatorRoleId'), 'role-assignment-cosmosDb')]", + "properties": { + "principalId": "[reference(resourceId('Microsoft.Web/sites', variables('webapp_name')), '2020-06-01', 'full').identity.principalId]", + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('cosmosDbOperatorRoleId'))]" + }, + "dependsOn": [ + "[resourceId('Microsoft.DocumentDB/databaseAccounts', variables('cosmos_name'))]", + "[resourceId('Microsoft.Web/sites', variables('webapp_name'))]" + ] + }, + { + "condition": "[parameters('disableLocalAuth')]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2020-04-01-preview", + "name": "[guid(resourceId('Microsoft.CognitiveServices/accounts', variables('openai_name')), variables('cognitiveServicesContributorRoleId'), 'role-assignment-cognitiveServices')]", + "properties": { + "principalId": "[reference(resourceId('Microsoft.Web/sites', variables('webapp_name')), '2020-06-01', 'full').identity.principalId]", + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('cognitiveServicesContributorRoleId'))]" + }, + "dependsOn": [ + "[resourceId('Microsoft.CognitiveServices/accounts', variables('openai_name'))]", + "[resourceId('Microsoft.Web/sites', variables('webapp_name'))]" + ] + }, + { + "condition": "[parameters('disableLocalAuth')]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2020-04-01-preview", + "scope": "[format('Microsoft.CognitiveServices/accounts/{0}', variables('openai_name'))]", + "name": "[guid(resourceId('Microsoft.CognitiveServices/accounts', variables('openai_name')), variables('cognitiveServicesOpenAIContributorRoleId'), 'role-assignment-cognitiveServices')]", + "properties": { + "principalId": "[reference(resourceId('Microsoft.Web/sites', variables('webapp_name')), '2020-06-01', 'full').identity.principalId]", + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('cognitiveServicesOpenAIContributorRoleId'))]" + }, + "dependsOn": [ + "[resourceId('Microsoft.CognitiveServices/accounts', variables('openai_name'))]", + "[resourceId('Microsoft.Web/sites', variables('webapp_name'))]" + ] + }, + { + "condition": "[parameters('disableLocalAuth')]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2020-04-01-preview", + "name": "[guid(resourceId('Microsoft.CognitiveServices/accounts', variables('form_recognizer_name')), variables('cognitiveServicesUserRoleId'), 'role-assignment-cognitiveServices')]", + "properties": { + "principalId": "[reference(resourceId('Microsoft.Web/sites', variables('webapp_name')), '2020-06-01', 'full').identity.principalId]", + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('cognitiveServicesUserRoleId'))]" + }, + "dependsOn": [ + "[resourceId('Microsoft.CognitiveServices/accounts', variables('form_recognizer_name'))]", + "[resourceId('Microsoft.Web/sites', variables('webapp_name'))]" + ] + }, + { + "condition": "[parameters('disableLocalAuth')]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2020-04-01-preview", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}', variables('storage_name'))]", + "name": "[guid(resourceId('Microsoft.Storage/storageAccounts', variables('storage_name')), variables('storageBlobDataContributorRoleId'), 'role-assignment-storage')]", + "properties": { + "principalId": "[reference(resourceId('Microsoft.Web/sites', variables('webapp_name')), '2020-06-01', 'full').identity.principalId]", + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('storageBlobDataContributorRoleId'))]" + }, + "dependsOn": [ + "[resourceId('Microsoft.Storage/storageAccounts', variables('storage_name'))]", + "[resourceId('Microsoft.Web/sites', variables('webapp_name'))]" + ] + }, + { + "condition": "[parameters('disableLocalAuth')]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2020-04-01-preview", + "scope": "[format('Microsoft.Search/searchServices/{0}', variables('search_name'))]", + "name": "[guid(resourceId('Microsoft.Search/searchServices', variables('search_name')), variables('searchServiceContributorRoleId'), 'role-assignment-searchService')]", + "properties": { + "principalId": "[reference(resourceId('Microsoft.Web/sites', variables('webapp_name')), '2020-06-01', 'full').identity.principalId]", + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('searchServiceContributorRoleId'))]" + }, + "dependsOn": [ + "[resourceId('Microsoft.Search/searchServices', variables('search_name'))]", + "[resourceId('Microsoft.Web/sites', variables('webapp_name'))]" + ] + }, + { + "condition": "[parameters('disableLocalAuth')]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2020-04-01-preview", + "scope": "[format('Microsoft.Search/searchServices/{0}', variables('search_name'))]", + "name": "[guid(resourceId('Microsoft.Search/searchServices', variables('search_name')), variables('searchIndexDataContributorRoleId'), 'role-assignment-searchService')]", + "properties": { + "principalId": "[reference(resourceId('Microsoft.Web/sites', variables('webapp_name')), '2020-06-01', 'full').identity.principalId]", + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('searchIndexDataContributorRoleId'))]" + }, + "dependsOn": [ + "[resourceId('Microsoft.Search/searchServices', variables('search_name'))]", + "[resourceId('Microsoft.Web/sites', variables('webapp_name'))]" + ] + }, + { + "condition": "[parameters('disableLocalAuth')]", + "type": "Microsoft.DocumentDB/databaseAccounts/sqlRoleDefinitions", + "apiVersion": "2024-05-15", + "name": "[format('{0}/{1}', variables('cosmos_name'), guid(resourceId('Microsoft.DocumentDB/databaseAccounts', variables('cosmos_name')), parameters('roleDefinitionName')))]", + "properties": { + "roleName": "[parameters('roleDefinitionName')]", + "type": "CustomRole", + "assignableScopes": [ + "[resourceId('Microsoft.DocumentDB/databaseAccounts', variables('cosmos_name'))]" + ], + "permissions": [ + { + "dataActions": [ + "Microsoft.DocumentDB/databaseAccounts/readMetadata", + "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/*", + "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/items/*" + ] + } + ] + }, + "dependsOn": [ + "[resourceId('Microsoft.DocumentDB/databaseAccounts', variables('cosmos_name'))]" + ] + }, + { + "condition": "[parameters('disableLocalAuth')]", + "type": "Microsoft.DocumentDB/databaseAccounts/sqlRoleAssignments", + "apiVersion": "2024-05-15", + "name": "[format('{0}/{1}', variables('cosmos_name'), guid(resourceId('Microsoft.DocumentDB/databaseAccounts/sqlRoleDefinitions', variables('cosmos_name'), guid(resourceId('Microsoft.DocumentDB/databaseAccounts', variables('cosmos_name')), parameters('roleDefinitionName'))), variables('webapp_name'), resourceId('Microsoft.DocumentDB/databaseAccounts', variables('cosmos_name'))))]", + "properties": { + "principalId": "[reference(resourceId('Microsoft.Web/sites', variables('webapp_name')), '2020-06-01', 'full').identity.principalId]", + "roleDefinitionId": "[resourceId('Microsoft.DocumentDB/databaseAccounts/sqlRoleDefinitions', variables('cosmos_name'), guid(resourceId('Microsoft.DocumentDB/databaseAccounts', variables('cosmos_name')), parameters('roleDefinitionName')))]", + "scope": "[resourceId('Microsoft.DocumentDB/databaseAccounts', variables('cosmos_name'))]" + }, + "dependsOn": [ + "[resourceId('Microsoft.DocumentDB/databaseAccounts', variables('cosmos_name'))]", + "[resourceId('Microsoft.DocumentDB/databaseAccounts/sqlRoleDefinitions', variables('cosmos_name'), guid(resourceId('Microsoft.DocumentDB/databaseAccounts', variables('cosmos_name')), parameters('roleDefinitionName')))]", + "[resourceId('Microsoft.Web/sites', variables('webapp_name'))]" + ] } ], "outputs": { diff --git a/infra/resources.bicep b/infra/resources.bicep index 48be71c66..e30702914 100644 --- a/infra/resources.bicep +++ b/infra/resources.bicep @@ -31,6 +31,8 @@ param storageServiceImageContainerName string param location string = resourceGroup().location +param disableLocalAuth bool= false + @secure() param nextAuthHash string = uniqueString(newGuid()) @@ -118,6 +120,11 @@ resource webApp 'Microsoft.Web/sites@2020-06-01' = { ftpsState: 'Disabled' minTlsVersion: '1.2' appSettings: [ + { + name: 'USE_MANAGED_IDENTITIES' + value: disableLocalAuth + } + { name: 'AZURE_KEY_VAULT_NAME' value: keyVaultName @@ -128,7 +135,7 @@ resource webApp 'Microsoft.Web/sites@2020-06-01' = { } { name: 'AZURE_OPENAI_API_KEY' - value: '@Microsoft.KeyVault(VaultName=${kv.name};SecretName=${kv::AZURE_OPENAI_API_KEY.name})' + value: disableLocalAuth ? '' :'@Microsoft.KeyVault(VaultName=${kv.name};SecretName=${kv::AZURE_OPENAI_API_KEY.name})' } { name: 'AZURE_OPENAI_API_INSTANCE_NAME' @@ -254,7 +261,7 @@ resource kvFunctionAppPermissions 'Microsoft.Authorization/roleAssignments@2020- name: guid(kv.id, webApp.name, keyVaultSecretsOfficerRole) scope: kv properties: { - principalId: webApp.identity.principalId + principalId: targetUserPrincipal principalType: 'ServicePrincipal' roleDefinitionId: keyVaultSecretsOfficerRole } @@ -347,6 +354,7 @@ resource cosmosDbAccount 'Microsoft.DocumentDB/databaseAccounts@2023-04-15' = { kind: 'GlobalDocumentDB' properties: { databaseAccountOfferType: 'Standard' + disableLocalAuth: disableLocalAuth locations: [ { locationName: location @@ -407,6 +415,7 @@ resource formRecognizer 'Microsoft.CognitiveServices/accounts@2023-05-01' = { properties: { customSubDomainName: form_recognizer_name publicNetworkAccess: 'Enabled' + disableLocalAuth: disableLocalAuth } sku: { name: formRecognizerSkuName @@ -421,6 +430,7 @@ resource searchService 'Microsoft.Search/searchServices@2022-09-01' = { partitionCount: 1 publicNetworkAccess: 'enabled' replicaCount: 1 + disableLocalAuth: disableLocalAuth } sku: { name: searchServiceSkuName @@ -435,6 +445,7 @@ resource azureopenai 'Microsoft.CognitiveServices/accounts@2023-05-01' = { properties: { customSubDomainName: openai_name publicNetworkAccess: 'Enabled' + disableLocalAuth: disableLocalAuth } sku: { name: openAiSkuName @@ -447,7 +458,7 @@ resource llmdeployment 'Microsoft.CognitiveServices/accounts/deployments@2023-05 name: deployment.name properties: { model: deployment.model - raiPolicyName: contains(deployment, 'raiPolicyName') ? deployment.raiPolicyName : null + /*raiPolicyName: contains(deployment, 'raiPolicyName') ? deployment.raiPolicyName : null*/ } sku: contains(deployment, 'sku') ? deployment.sku : { name: 'Standard' @@ -463,6 +474,7 @@ resource azureopenaidalle 'Microsoft.CognitiveServices/accounts@2023-05-01' = { properties: { customSubDomainName: openai_dalle_name publicNetworkAccess: 'Enabled' + disableLocalAuth: disableLocalAuth } sku: { name: openAiSkuName @@ -493,6 +505,7 @@ resource speechService 'Microsoft.CognitiveServices/accounts@2023-05-01' = { properties: { customSubDomainName: speech_service_name publicNetworkAccess: 'Enabled' + /* TODO: disableLocalAuth: disableLocalAuth*/ } sku: { name: speechServiceSkuName @@ -506,6 +519,9 @@ resource storage 'Microsoft.Storage/storageAccounts@2022-05-01' = { tags: tags kind: 'StorageV2' sku: storageServiceSku + properties:{ + allowSharedKeyAccess: !disableLocalAuth + } resource blobServices 'blobServices' = { name: 'default' @@ -518,4 +534,132 @@ resource storage 'Microsoft.Storage/storageAccounts@2022-05-01' = { } } + +//RBAC Roles for managed identity authentication + +var cosmosDbContributorRoleId = '5bd9cd88-fe45-4216-938b-f97437e15450' // Replace with actual role ID for Cosmos DB. +var cosmosDbOperatorRoleId= '230815da-be43-4aae-9cb4-875f7bd000aa' +var cognitiveServicesContributorRoleId = '25fbc0a9-bd7c-42a3-aa1a-3b75d497ee68' // Replace with actual role ID for Cognitive Services. +var cognitiveServicesUserRoleId='a97b65f3-24c7-4388-baec-2e87135dc908' +var storageBlobDataContributorRoleId = 'ba92f5b4-2d11-453d-a403-e96b0029c9fe' // Replace with actual role ID for Blob Data Contributor. +var searchServiceContributorRoleId = '7ca78c08-252a-4471-8644-bb5ff32d4ba0' // Replace with actual role ID for Azure Search. +var cognitiveServicesOpenAIContributorRoleId='a001fd3d-188f-4b5d-821b-7da978bf7442' +var searchIndexDataContributorRoleId='8ebe5a00-799e-43f5-93ac-243d3dce84a7' + +var targetUserPrincipal = webApp.identity.principalId +// These are only deployed if local authentication has been disabled in the parameters + +resource cosmosDbRoleAssignment 'Microsoft.Authorization/roleAssignments@2020-04-01-preview' = if (disableLocalAuth) { + name: guid(cosmosDbAccount.id, cosmosDbContributorRoleId, 'role-assignment-cosmosDb') + scope: cosmosDbAccount + properties: { + principalId: targetUserPrincipal + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', cosmosDbContributorRoleId) + } +} + + +resource cosmosDbRoleAssignmentOpperator 'Microsoft.Authorization/roleAssignments@2020-04-01-preview' = if (disableLocalAuth) { + name: guid(cosmosDbAccount.id, cosmosDbOperatorRoleId, 'role-assignment-cosmosDb') + scope: cosmosDbAccount + properties: { + principalId: targetUserPrincipal + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', cosmosDbOperatorRoleId) + } +} + +resource cognitiveServicesRoleAssignment 'Microsoft.Authorization/roleAssignments@2020-04-01-preview' = if (disableLocalAuth) { + name: guid(azureopenai.id, cognitiveServicesContributorRoleId, 'role-assignment-cognitiveServices') + scope: resourceGroup() + properties: { + principalId: targetUserPrincipal + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', cognitiveServicesContributorRoleId) + } +} + + +resource cognitivbeServicesOpenAIcONTRIBUTORRoleAssignment 'Microsoft.Authorization/roleAssignments@2020-04-01-preview' = if (disableLocalAuth) { + name: guid(azureopenai.id, cognitiveServicesOpenAIContributorRoleId, 'role-assignment-cognitiveServices') + scope: azureopenai + properties: { + principalId: targetUserPrincipal + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', cognitiveServicesOpenAIContributorRoleId) + } +} + +resource cognitiveServicesUserRoleAssignment 'Microsoft.Authorization/roleAssignments@2020-04-01-preview' = if (disableLocalAuth) { + name: guid(formRecognizer.id, cognitiveServicesUserRoleId, 'role-assignment-cognitiveServices') + scope: resourceGroup() + properties: { + principalId: targetUserPrincipal + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', cognitiveServicesUserRoleId) + } +} + + + +resource storageBlobDataContributorRole 'Microsoft.Authorization/roleAssignments@2020-04-01-preview' = if (disableLocalAuth) { + name: guid(storage.id, storageBlobDataContributorRoleId, 'role-assignment-storage') + scope: storage + properties: { + principalId: targetUserPrincipal + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', storageBlobDataContributorRoleId) + } +} + +resource searchServiceContributorRoleAssignment 'Microsoft.Authorization/roleAssignments@2020-04-01-preview' = if (disableLocalAuth) { + name: guid(searchService.id, searchServiceContributorRoleId, 'role-assignment-searchService') + scope: searchService + properties: { + principalId: targetUserPrincipal + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', searchServiceContributorRoleId) + } +} +resource searchServiceIndexDataContributorRoleAssignment 'Microsoft.Authorization/roleAssignments@2020-04-01-preview' = if (disableLocalAuth) { + name: guid(searchService.id, searchIndexDataContributorRoleId, 'role-assignment-searchService') + scope: searchService + properties: { + principalId: targetUserPrincipal + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', searchIndexDataContributorRoleId) + } +} +//Special case for cosmosdb + + +@description('Name of the role definition.') +param roleDefinitionName string = 'Azure Cosmos DB for NoSQL Data Plane Owner' + + +resource definition 'Microsoft.DocumentDB/databaseAccounts/sqlRoleDefinitions@2024-05-15'= if (disableLocalAuth) { + name: guid(cosmosDbAccount.id, roleDefinitionName) + parent: cosmosDbAccount + properties: { + roleName: roleDefinitionName + type: 'CustomRole' + assignableScopes: [ + cosmosDbAccount.id + ] + permissions: [ + { + dataActions: [ + 'Microsoft.DocumentDB/databaseAccounts/readMetadata' + 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/*' + 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/items/*' + ] + } + ] + } +} + +resource assignment 'Microsoft.DocumentDB/databaseAccounts/sqlRoleAssignments@2024-05-15'= if (disableLocalAuth) { + name: guid(definition.id, webApp.name, cosmosDbAccount.id) + parent: cosmosDbAccount + properties: { + principalId: targetUserPrincipal + roleDefinitionId: definition.id + scope: cosmosDbAccount.id + } + +} + output url string = 'https://${webApp.properties.defaultHostName}' diff --git a/src/features/common/services/ai-search.ts b/src/features/common/services/ai-search.ts index 86b669e04..0dc2a175b 100644 --- a/src/features/common/services/ai-search.ts +++ b/src/features/common/services/ai-search.ts @@ -4,57 +4,56 @@ import { SearchIndexClient, SearchIndexerClient, } from "@azure/search-documents"; +import { DefaultAzureCredential } from "@azure/identity"; -export const AzureAISearchCredentials = () => { - const apiKey = process.env.AZURE_SEARCH_API_KEY; - const searchName = process.env.AZURE_SEARCH_NAME; - const indexName = process.env.AZURE_SEARCH_INDEX_NAME; - - if (!apiKey || !searchName || !indexName) { - throw new Error( - "One or more Azure AI Search environment variables are not set" - ); - } - const endpointSuffix = process.env.AZURE_SEARCH_ENDPOINT_SUFFIX || "search.windows.net"; - - const endpoint = `https://${searchName}.${endpointSuffix}`; - return { - apiKey, - endpoint, - indexName, - }; -}; +const USE_MANAGED_IDENTITIES = process.env.USE_MANAGED_IDENTITIES === "true"; +const endpointSuffix = process.env.AZURE_SEARCH_ENDPOINT_SUFFIX || "search.windows.net"; +const apiKey = process.env.AZURE_SEARCH_API_KEY; +const searchName = process.env.AZURE_SEARCH_NAME; +const indexName = process.env.AZURE_SEARCH_INDEX_NAME; +const endpoint = `https://${searchName}.${endpointSuffix}`; + + +export const GetCredential = () => { + return USE_MANAGED_IDENTITIES + ? new DefaultAzureCredential() + : new AzureKeyCredential(apiKey); +} export const AzureAISearchInstance = () => { - const { apiKey, endpoint, indexName } = AzureAISearchCredentials(); + const credential = GetCredential(); const searchClient = new SearchClient( endpoint, indexName, - new AzureKeyCredential(apiKey) + credential ); return searchClient; + + }; export const AzureAISearchIndexClientInstance = () => { - const { apiKey, endpoint } = AzureAISearchCredentials(); + + const credential = GetCredential(); const searchClient = new SearchIndexClient( endpoint, - new AzureKeyCredential(apiKey) + credential ); return searchClient; }; export const AzureAISearchIndexerClientInstance = () => { - const { apiKey, endpoint } = AzureAISearchCredentials(); + + const credential = GetCredential(); const client = new SearchIndexerClient( endpoint, - new AzureKeyCredential(apiKey) + credential ); return client; -}; +}; \ No newline at end of file diff --git a/src/features/common/services/azure-storage.ts b/src/features/common/services/azure-storage.ts index 42e0eaffe..13f5dc962 100644 --- a/src/features/common/services/azure-storage.ts +++ b/src/features/common/services/azure-storage.ts @@ -1,22 +1,28 @@ import { BlobServiceClient, RestError } from "@azure/storage-blob"; import { ServerActionResponse } from "../server-action-response"; +import { DefaultAzureCredential } from "@azure/identity"; // initialize the blobServiceClient +const USE_MANAGED_IDENTITIES = process.env.USE_MANAGED_IDENTITIES === "true"; + const InitBlobServiceClient = () => { - const acc = process.env.AZURE_STORAGE_ACCOUNT_NAME; - const key = process.env.AZURE_STORAGE_ACCOUNT_KEY; + const accountName = process.env.AZURE_STORAGE_ACCOUNT_NAME; + const endpointSuffix = process.env.AZURE_STORAGE_ENDPOINT_SUFFIX || "core.windows.net"; + const endpoint = `https://${accountName}.blob.${endpointSuffix}`; + + if (USE_MANAGED_IDENTITIES) { + return new BlobServiceClient(endpoint, new DefaultAzureCredential()); + } - if (!acc || !key) + const accountKey = process.env.AZURE_STORAGE_ACCOUNT_KEY; + if (!accountName || !accountKey) { throw new Error( "Azure Storage Account not configured correctly, check environment variables." ); - const endpointSuffix = process.env.AZURE_STORAGE_ENDPOINT_SUFFIX || "core.windows.net"; - - const connectionString = `DefaultEndpointsProtocol=https;AccountName=${acc};AccountKey=${key};EndpointSuffix=${endpointSuffix}`; + } - const blobServiceClient = - BlobServiceClient.fromConnectionString(connectionString); - return blobServiceClient; + const connectionString = `DefaultEndpointsProtocol=https;AccountName=${accountName};AccountKey=${accountKey};EndpointSuffix=${endpointSuffix}`; + return BlobServiceClient.fromConnectionString(connectionString); }; export const UploadBlob = async ( @@ -79,7 +85,8 @@ export const GetBlob = async ( }; } catch (error) { if (error instanceof RestError) { - if (error.statusCode === 404) { + const restError = error as RestError; + if (restError.statusCode === 404) { return { status: "NOT_FOUND", errors: [ diff --git a/src/features/common/services/cosmos.ts b/src/features/common/services/cosmos.ts index 82f4933ea..2c6d59674 100644 --- a/src/features/common/services/cosmos.ts +++ b/src/features/common/services/cosmos.ts @@ -1,22 +1,38 @@ import { CosmosClient } from "@azure/cosmos"; +import { DefaultAzureCredential } from "@azure/identity"; -// Read Cosmos DB_NAME and CONTAINER_NAME from .env +// Configure Cosmos DB details const DB_NAME = process.env.AZURE_COSMOSDB_DB_NAME || "chat"; const CONTAINER_NAME = process.env.AZURE_COSMOSDB_CONTAINER_NAME || "history"; -const CONFIG_CONTAINER_NAME = - process.env.AZURE_COSMOSDB_CONFIG_CONTAINER_NAME || "config"; +const CONFIG_CONTAINER_NAME = process.env.AZURE_COSMOSDB_CONFIG_CONTAINER_NAME || "config"; +const USE_MANAGED_IDENTITIES = process.env.USE_MANAGED_IDENTITIES === "true"; + +const getCosmosCredential = () => { + if (USE_MANAGED_IDENTITIES) { + return new DefaultAzureCredential(); + } + const key = process.env.AZURE_COSMOSDB_KEY; + if (!key) { + throw new Error("Azure Cosmos DB key is not provided in environment variables."); + } + return key; +}; export const CosmosInstance = () => { const endpoint = process.env.AZURE_COSMOSDB_URI; - const key = process.env.AZURE_COSMOSDB_KEY; - if (!endpoint || !key) { + if (!endpoint) { throw new Error( - "Azure Cosmos DB is not configured. Please configure it in the .env file." + "Azure Cosmos DB endpoint is not configured. Please configure it in the .env file." ); } - return new CosmosClient({ endpoint, key }); + const credential = getCosmosCredential(); + if (credential instanceof DefaultAzureCredential) { + return new CosmosClient({ endpoint, aadCredentials: credential }); + } else { + return new CosmosClient({ endpoint, key: credential }); + } }; export const ConfigContainer = () => { diff --git a/src/features/common/services/document-intelligence.ts b/src/features/common/services/document-intelligence.ts index 163f3e1a4..99eb1de16 100644 --- a/src/features/common/services/document-intelligence.ts +++ b/src/features/common/services/document-intelligence.ts @@ -2,21 +2,31 @@ import { AzureKeyCredential, DocumentAnalysisClient, } from "@azure/ai-form-recognizer"; +import { DefaultAzureCredential } from "@azure/identity"; + + +const USE_MANAGED_IDENTITIES = process.env.USE_MANAGED_IDENTITIES === "true"; export const DocumentIntelligenceInstance = () => { const endpoint = process.env.AZURE_DOCUMENT_INTELLIGENCE_ENDPOINT; - const key = process.env.AZURE_DOCUMENT_INTELLIGENCE_KEY; + + if (!endpoint) { + throw new Error( + "Document Intelligence environment variable for the endpoint is not set" + ); + } + + const credential = USE_MANAGED_IDENTITIES + ? new DefaultAzureCredential() + : new AzureKeyCredential(process.env.AZURE_DOCUMENT_INTELLIGENCE_KEY); - if (!endpoint || !key) { + if (!USE_MANAGED_IDENTITIES && !process.env.AZURE_DOCUMENT_INTELLIGENCE_KEY) { throw new Error( - "One or more Document Intelligence environment variables are not set" + "Document Intelligence environment variable for the key is not set" ); } - const client = new DocumentAnalysisClient( - endpoint, - new AzureKeyCredential(key) - ); + const client = new DocumentAnalysisClient(endpoint, credential); return client; -}; +}; \ No newline at end of file diff --git a/src/features/common/services/openai.ts b/src/features/common/services/openai.ts index 0e44a4523..b9962858d 100644 --- a/src/features/common/services/openai.ts +++ b/src/features/common/services/openai.ts @@ -1,60 +1,87 @@ import { OpenAI } from "openai"; +import { DefaultAzureCredential, getBearerTokenProvider } from "@azure/identity"; +import { AzureOpenAI } from "openai"; -export const OpenAIInstance = () => { +const USE_MANAGED_IDENTITIES = process.env.USE_MANAGED_IDENTITIES === "true"; + +export const OpenAIInstance = () => { const endpointSuffix = process.env.AZURE_OPENAI_API_ENDPOINT_SUFFIX || "openai.azure.com"; - const openai = new OpenAI({ - apiKey: process.env.AZURE_OPENAI_API_KEY, - baseURL: `https://${process.env.AZURE_OPENAI_API_INSTANCE_NAME}.${endpointSuffix}/openai/deployments/${process.env.AZURE_OPENAI_API_DEPLOYMENT_NAME}`, - defaultQuery: { "api-version": process.env.AZURE_OPENAI_API_VERSION }, - defaultHeaders: { "api-key": process.env.AZURE_OPENAI_API_KEY }, - }); - return openai; + let token = process.env.AZURE_OPENAI_API_KEY; + if (USE_MANAGED_IDENTITIES) { + const credential = new DefaultAzureCredential(); + const scope = "https://cognitiveservices.azure.com/.default"; + const azureADTokenProvider = getBearerTokenProvider(credential, scope); + const deployment = process.env.AZURE_OPENAI_API_DEPLOYMENT_NAME; + const apiVersion = process.env.AZURE_OPENAI_API_VERSION; + const client = new AzureOpenAI({ + azureADTokenProvider, + deployment, + apiVersion, + baseURL: `https://${process.env.AZURE_OPENAI_API_INSTANCE_NAME}.${endpointSuffix}/openai/deployments/${process.env.AZURE_OPENAI_API_DEPLOYMENT_NAME}` + }); + return client; + } else { + const openai = new OpenAI({ + apiKey: token, + baseURL: `https://${process.env.AZURE_OPENAI_API_INSTANCE_NAME}.${endpointSuffix}/openai/deployments/${process.env.AZURE_OPENAI_API_DEPLOYMENT_NAME}`, + defaultQuery: { "api-version": process.env.AZURE_OPENAI_API_VERSION }, + defaultHeaders: { "api-key": process.env.AZURE_OPENAI_API_KEY }, + }); + return openai; + } }; -export const OpenAIEmbeddingInstance = () => { - if ( - !process.env.AZURE_OPENAI_API_KEY || - !process.env.AZURE_OPENAI_API_EMBEDDINGS_DEPLOYMENT_NAME || - !process.env.AZURE_OPENAI_API_INSTANCE_NAME - ) { - throw new Error( - "Azure OpenAI Embeddings endpoint config is not set, check environment variables." - ); - } +export const OpenAIEmbeddingInstance = () => { const endpointSuffix = process.env.AZURE_OPENAI_API_ENDPOINT_SUFFIX || "openai.azure.com"; - - const openai = new OpenAI({ - apiKey: process.env.AZURE_OPENAI_API_KEY, - baseURL: `https://${process.env.AZURE_OPENAI_API_INSTANCE_NAME}.${endpointSuffix}/openai/deployments/${process.env.AZURE_OPENAI_API_EMBEDDINGS_DEPLOYMENT_NAME}`, - defaultQuery: { "api-version": process.env.AZURE_OPENAI_API_VERSION }, - defaultHeaders: { "api-key": process.env.AZURE_OPENAI_API_KEY }, - }); - return openai; + let token = process.env.AZURE_OPENAI_API_KEY; + if (USE_MANAGED_IDENTITIES) { + const credential = new DefaultAzureCredential(); + const scope = "https://cognitiveservices.azure.com/.default"; + const azureADTokenProvider = getBearerTokenProvider(credential, scope); + const deployment = process.env.AZURE_OPENAI_API_EMBEDDINGS_DEPLOYMENT_NAME; + const apiVersion = process.env.AZURE_OPENAI_API_VERSION; + const client = new AzureOpenAI({ + azureADTokenProvider, + deployment, + apiVersion, + baseURL: `https://${process.env.AZURE_OPENAI_API_INSTANCE_NAME}.${endpointSuffix}/openai/deployments/${process.env.AZURE_OPENAI_API_EMBEDDINGS_DEPLOYMENT_NAME}` + }); + return client; + } else { + const openai = new OpenAI({ + apiKey: token, + baseURL: `https://${process.env.AZURE_OPENAI_API_INSTANCE_NAME}.${endpointSuffix}/openai/deployments/${process.env.AZURE_OPENAI_API_EMBEDDINGS_DEPLOYMENT_NAME}`, + defaultQuery: { "api-version": process.env.AZURE_OPENAI_API_VERSION }, + defaultHeaders: { "api-key": token }, + }); + return openai; + } }; -// a new instance definition for DALL-E image generation -export const OpenAIDALLEInstance = () => { - if ( - !process.env.AZURE_OPENAI_DALLE_API_KEY || - !process.env.AZURE_OPENAI_DALLE_API_DEPLOYMENT_NAME || - !process.env.AZURE_OPENAI_DALLE_API_INSTANCE_NAME - ) { - throw new Error( - "Azure OpenAI DALLE endpoint config is not set, check environment variables." - ); - } +// A new instance definition for DALL-E image generation +export const OpenAIDALLEInstance = () => { const endpointSuffix = process.env.AZURE_OPENAI_API_ENDPOINT_SUFFIX || "openai.azure.com"; - - const openai = new OpenAI({ - apiKey: process.env.AZURE_OPENAI_DALLE_API_KEY, - baseURL: `https://${process.env.AZURE_OPENAI_DALLE_API_INSTANCE_NAME}.${endpointSuffix}/openai/deployments/${process.env.AZURE_OPENAI_DALLE_API_DEPLOYMENT_NAME}`, - defaultQuery: { - "api-version": - process.env.AZURE_OPENAI_DALLE_API_VERSION || "2023-12-01-preview", - }, - defaultHeaders: { - "api-key": process.env.AZURE_OPENAI_DALLE_API_KEY, - }, - }); - return openai; + let token = process.env.AZURE_OPENAI_DALLE_API_KEY; + if (USE_MANAGED_IDENTITIES) { + const credential = new DefaultAzureCredential(); + const scope = "https://cognitiveservices.azure.com/.default"; + const azureADTokenProvider = getBearerTokenProvider(credential, scope); + const deployment = process.env.AZURE_OPENAI_DALLE_API_DEPLOYMENT_NAME; + const apiVersion = process.env.AZURE_OPENAI_DALLE_API_VERSION || "2023-12-01-preview"; + const client = new AzureOpenAI({ + azureADTokenProvider, + deployment, + apiVersion, + baseURL: `https://${process.env.AZURE_OPENAI_DALLE_API_INSTANCE_NAME}.${endpointSuffix}/openai/deployments/${process.env.AZURE_OPENAI_DALLE_API_DEPLOYMENT_NAME}` + }); + return client; + } else { + const openai = new OpenAI({ + apiKey: token, + baseURL: `https://${process.env.AZURE_OPENAI_DALLE_API_INSTANCE_NAME}.${endpointSuffix}/openai/deployments/${process.env.AZURE_OPENAI_DALLE_API_DEPLOYMENT_NAME}`, + defaultQuery: { "api-version": process.env.AZURE_OPENAI_DALLE_API_VERSION || "2023-12-01-preview" }, + defaultHeaders: { "api-key": token }, + }); + return openai; + } }; diff --git a/src/package.json b/src/package.json index 59375f410..9267d5cc4 100644 --- a/src/package.json +++ b/src/package.json @@ -6,12 +6,13 @@ "dev": "next dev", "build": "next build", "start": "next start", - "lint": "next lint" + "lint": "next lint", + "debug": "set NODE_OPTIONS=--inspect && next dev" }, "dependencies": { "@azure/ai-form-recognizer": "^5.0.0", "@azure/cosmos": "^4.0.0", - "@azure/identity": "^4.0.0", + "@azure/identity": "^4.4.1", "@azure/keyvault-secrets": "^4.7.0", "@azure/search-documents": "^12.0.0", "@azure/storage-blob": "^12.17.0", @@ -42,7 +43,8 @@ "next": "14.0.4", "next-auth": "^4.24.5", "next-themes": "^0.2.1", - "openai": "^4.26.0", + "openai": "^4.67.1", + "@azure/openai":"^2.0.0-beta.2", "react": "^18", "react-dom": "^18", "react-syntax-highlighter": "^15.5.0", From 85eb0138057690d43a45a4e0acd84d70618d7c53 Mon Sep 17 00:00:00 2001 From: Freddy Ayala Date: Sun, 6 Oct 2024 15:59:09 +0200 Subject: [PATCH 02/12] added doc --- docs/10.managed-identities.md | 52 +++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 docs/10.managed-identities.md diff --git a/docs/10.managed-identities.md b/docs/10.managed-identities.md new file mode 100644 index 000000000..ba604c0c1 --- /dev/null +++ b/docs/10.managed-identities.md @@ -0,0 +1,52 @@ +# Using Managed Identities for Azure Chat Solution Accelerator + +### Introduction + +The Azure Chat Solution Accelerator powered by Azure OpenAI Service allows organizations to deploy a private chat tenant with enhanced security and control over their data. One of the new features is the support for Managed Identities, adding a layer of security by eliminating the need for managing service principals and secrets manually. + +### Security Advantages of Managed Identities + +**Managed Identities** for Azure resources provide the following benefits: + +1. **Improved Security**: + - **No Secret Management**: Eliminates the need to manually store and manage credentials or keys. + - **Automatic Rotation**: Managed Identities’ credentials are rotated automatically, eliminating potential security risk from non-rotated credentials. + - **Scope Limited Access**: Access to Azure resources can be fine-grained, allowing least-privilege access policies. + +2. **Simplified Management**: + - **Platform Managed**: The Azure platform handles identity creation and lifecycle management. + - **Simplified Resource Access**: Applications can request tokens to access resources without handling secrets. + +### List of Services Using Managed Identities + +The following services within the Azure Chat Solution Accelerator use Managed Identities for authentication: + +1. **Azure OpenAI Service** +2. **Azure Cosmos DB** +3. **Azure Cognitive Services (e.g., Document Intelligence, Azure OpenAI Dalle)** +4. **Azure Search Service** +5. **Azure Storage Account** + +> **Note:** Currently, due to compatibility issues, the Speech Service does not utilize Managed Identities. There is no available documentation for using Entra ID authentication with the Speech Service, making it a `TODO` item. + +### Preferred Production Deployment + +Using Managed Identities is preferred for production deployments due to: + +1. **Enhanced Security**: Eliminates risks associated with secret management such as accidental exposure or non-rotation of credentials. +2. **Compliance and Governance**: Managed Identities integrate with Azure's role-based access control (RBAC), facilitating easier audits and compliance management. +3. **Operational Efficiency**: Reduces the operational overhead of managing secrets, while also providing a more straightforward implementation. + +### Deploy to Azure with Managed Identities + +To deploy the application to Azure App Service with Managed Identities, follow the standard deployment instructions available in the [Deploy to Azure - GitHub Actions](https://github.com/microsoft/azurechat) section of the repository. Ensure to: + +1. **Update the Parameter**: + - Set the parameter `disableLocalAuth` to `true` to use Managed Identities. +1. **Remove Parameter**: +After deployment remove the value in AZURE_OPENAI_API_KEY (if there is any) in the Environment Variables of the WebApp, the openai npm package requires this to be empty if using EntraID Authentication. + + +### Conclusion + +By leveraging Managed Identities, you enhance the security posture of your Azure Chat deployment while simplifying secret management and access control. This guide outlines the security advantages and highlights the necessary parameter changes to ensure a secure and efficient production setup. For more details, review the complete code and configurations available in the repository's `infra` directory. From 06b7e3c23f4a0f014461a9bcd3e1408e2166f37c Mon Sep 17 00:00:00 2001 From: Freddy Ayala Date: Tue, 15 Oct 2024 16:35:12 +0200 Subject: [PATCH 03/12] added mermaid rendering --- src/features/chat-page/MermaidDiagram.tsx | 47 + src/features/chat-page/message-content.tsx | 20 +- src/package-lock.json | 1215 ++++++++++++++++++-- src/package.json | 3 +- 4 files changed, 1174 insertions(+), 111 deletions(-) create mode 100644 src/features/chat-page/MermaidDiagram.tsx diff --git a/src/features/chat-page/MermaidDiagram.tsx b/src/features/chat-page/MermaidDiagram.tsx new file mode 100644 index 000000000..21044cd55 --- /dev/null +++ b/src/features/chat-page/MermaidDiagram.tsx @@ -0,0 +1,47 @@ + +import { useEffect, useRef, useState } from 'react'; +import mermaid from 'mermaid'; + +interface MermaidDiagramProps { + chartCode: string; + } + + const MermaidDiagram: React.FC = ({ chartCode }) => { + const mermaidRef = useRef(null); + const [loaded, setLoaded] = useState(false); + + useEffect(() => { + const renderMermaid = async () => { + mermaid.initialize({ + startOnLoad: true, + logLevel: 'warn', // Use log level string for better error handling + }); + + if (mermaidRef.current) { + try { + const svgCode = await mermaid.render('mermaidChartId', chartCode); + if (mermaidRef.current) { + mermaidRef.current.innerHTML = typeof svgCode === 'string' ? svgCode : ''; + setLoaded(true); + } + } catch (err) { + console.error('Mermaid diagram rendering failed', err); + } + } + }; + + renderMermaid(); + }, [chartCode]); + + if (!loaded) { + return
Loading Mermaid diagram...
; + } + + return ( +
+
+
+ ); + }; + + export default MermaidDiagram; \ No newline at end of file diff --git a/src/features/chat-page/message-content.tsx b/src/features/chat-page/message-content.tsx index 757613a4c..05d9479f7 100644 --- a/src/features/chat-page/message-content.tsx +++ b/src/features/chat-page/message-content.tsx @@ -1,6 +1,7 @@ import { Markdown } from "@/features/ui/markdown/markdown"; import { FunctionSquare } from "lucide-react"; import React from "react"; +import MermaidDiagram from "@/features/chat-page/MermaidDiagram"; // Adjust the import path as necessary import { Accordion, AccordionContent, @@ -20,14 +21,23 @@ interface MessageContentProps { } const MessageContent: React.FC = ({ message }) => { + const getMermaidCode = (content: string): string | null => { + const mermaidRegex = /```mermaid((.*\n)+?)```/; // Adjust regex based on how Mermaid diagrams are identified + const match = content.match(mermaidRegex); + return match ? match[1] : null; + }; + if (message.role === "assistant" || message.role === "user") { + const mermaidCode = getMermaidCode(message.content); + return ( <> - - {message.multiModalImage && } + {mermaidCode ? ( + + ) : ( + + )} + {message.multiModalImage && Multimodal} ); } diff --git a/src/package-lock.json b/src/package-lock.json index 2fdc29900..58ac749c2 100644 --- a/src/package-lock.json +++ b/src/package-lock.json @@ -10,8 +10,9 @@ "dependencies": { "@azure/ai-form-recognizer": "^5.0.0", "@azure/cosmos": "^4.0.0", - "@azure/identity": "^4.0.0", + "@azure/identity": "^4.4.1", "@azure/keyvault-secrets": "^4.7.0", + "@azure/openai": "^2.0.0-beta.2", "@azure/search-documents": "^12.0.0", "@azure/storage-blob": "^12.17.0", "@codemirror/lang-javascript": "^6.2.1", @@ -36,12 +37,13 @@ "clsx": "^2.0.0", "eventsource-parser": "^1.1.1", "lucide-react": "^0.309.0", + "mermaid": "^11.3.0", "microsoft-cognitiveservices-speech-sdk": "^1.34.0", "nanoid": "^5.0.4", "next": "14.0.4", "next-auth": "^4.24.5", "next-themes": "^0.2.1", - "openai": "^4.26.0", + "openai": "^4.67.1", "react": "^18", "react-dom": "^18", "react-syntax-highlighter": "^15.5.0", @@ -84,6 +86,57 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@antfu/install-pkg": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@antfu/install-pkg/-/install-pkg-0.4.1.tgz", + "integrity": "sha512-T7yB5QNG29afhWVkVq7XeIMBa5U/vs9mX69YqayXypPRmYzUmzwnYltplHmPtZ4HPCn+sQKeXW8I47wCbuBOjw==", + "license": "MIT", + "dependencies": { + "package-manager-detector": "^0.2.0", + "tinyexec": "^0.3.0" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@antfu/utils": { + "version": "0.7.10", + "resolved": "https://registry.npmjs.org/@antfu/utils/-/utils-0.7.10.tgz", + "integrity": "sha512-+562v9k4aI80m1+VuMHehNJWLOFjBnXn3tdOitzD0il5b7smkSBal4+a3oKiQTbrwMmN/TBUMDvbdoWDehgOww==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@azure-rest/core-client": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@azure-rest/core-client/-/core-client-2.3.1.tgz", + "integrity": "sha512-sGTdh2Ln95F/Jqikr9OybQvx00EVvljwgxjfcxTqjID0PBVGDuNR0ie9e9HsTA1vJT23BlVRd/dCIGzJriYw9g==", + "license": "MIT", + "dependencies": { + "@azure/abort-controller": "^2.0.0", + "@azure/core-auth": "^1.3.0", + "@azure/core-rest-pipeline": "^1.5.0", + "@azure/core-tracing": "^1.0.1", + "@azure/core-util": "^1.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@azure-rest/core-client/node_modules/@azure/abort-controller": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.2.tgz", + "integrity": "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@azure/abort-controller": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-1.1.0.tgz", @@ -128,20 +181,33 @@ } }, "node_modules/@azure/core-client": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/@azure/core-client/-/core-client-1.7.3.tgz", - "integrity": "sha512-kleJ1iUTxcO32Y06dH9Pfi9K4U+Tlb111WXEnbt7R/ne+NLRwppZiTGJuTD5VVoxTMK5NTbEtm5t2vcdNCFe2g==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@azure/core-client/-/core-client-1.9.2.tgz", + "integrity": "sha512-kRdry/rav3fUKHl/aDLd/pDLcB+4pOFwPPTVEExuMyaI5r+JBbMWqRbCY1pn5BniDaU3lRxO9eaQ1AmSMehl/w==", + "license": "MIT", "dependencies": { - "@azure/abort-controller": "^1.0.0", + "@azure/abort-controller": "^2.0.0", "@azure/core-auth": "^1.4.0", "@azure/core-rest-pipeline": "^1.9.1", "@azure/core-tracing": "^1.0.0", - "@azure/core-util": "^1.0.0", + "@azure/core-util": "^1.6.1", "@azure/logger": "^1.0.0", - "tslib": "^2.2.0" + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=18.0.0" + } + }, + "node_modules/@azure/core-client/node_modules/@azure/abort-controller": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.2.tgz", + "integrity": "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, "node_modules/@azure/core-http": { @@ -311,19 +377,20 @@ } }, "node_modules/@azure/identity": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@azure/identity/-/identity-4.0.0.tgz", - "integrity": "sha512-gtPYxIL0kI39Dw4t3HvlbfhOdXqKD2MqDgynlklF0j728j51dcKgRo6FLX0QzpBw/1gGfLxjMXqq3nKOSQ2lmA==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@azure/identity/-/identity-4.4.1.tgz", + "integrity": "sha512-DwnG4cKFEM7S3T+9u05NstXU/HN0dk45kPOinUyNKsn5VWwpXd9sbPKEg6kgJzGbm1lMuhx9o31PVbCtM5sfBA==", + "license": "MIT", "dependencies": { "@azure/abort-controller": "^1.0.0", "@azure/core-auth": "^1.5.0", - "@azure/core-client": "^1.4.0", + "@azure/core-client": "^1.9.2", "@azure/core-rest-pipeline": "^1.1.0", "@azure/core-tracing": "^1.0.0", - "@azure/core-util": "^1.0.0", + "@azure/core-util": "^1.3.0", "@azure/logger": "^1.0.0", - "@azure/msal-browser": "^3.5.0", - "@azure/msal-node": "^2.5.1", + "@azure/msal-browser": "^3.14.0", + "@azure/msal-node": "^2.9.2", "events": "^3.0.0", "jws": "^4.0.0", "open": "^8.0.0", @@ -380,30 +447,33 @@ } }, "node_modules/@azure/msal-browser": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/@azure/msal-browser/-/msal-browser-3.7.0.tgz", - "integrity": "sha512-ktDB/Gf7UDgYBJOnoIlh70lxIo4e1/D2UgHuayB4RntN1IlusfTtIVH3k8NpJMdl+38tfTXIaUoR+qlr5voZEg==", + "version": "3.26.1", + "resolved": "https://registry.npmjs.org/@azure/msal-browser/-/msal-browser-3.26.1.tgz", + "integrity": "sha512-y78sr9g61aCAH9fcLO1um+oHFXc1/5Ap88RIsUSuzkm0BHzFnN+PXGaQeuM1h5Qf5dTnWNOd6JqkskkMPAhh7Q==", + "license": "MIT", "dependencies": { - "@azure/msal-common": "14.6.0" + "@azure/msal-common": "14.15.0" }, "engines": { "node": ">=0.8.0" } }, "node_modules/@azure/msal-common": { - "version": "14.6.0", - "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-14.6.0.tgz", - "integrity": "sha512-AGusT/JvxdzJIYi5u0n97cmhd3pUT6UuI6rEkT5iDeT2FGcV0/EB8pk+dy6GLPpYg9vhDCuyoYrEZGd+2UeCCQ==", + "version": "14.15.0", + "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-14.15.0.tgz", + "integrity": "sha512-ImAQHxmpMneJ/4S8BRFhjt1MZ3bppmpRPYYNyzeQPeFN288YKbb8TmmISQEbtfkQ1BPASvYZU5doIZOPBAqENQ==", + "license": "MIT", "engines": { "node": ">=0.8.0" } }, "node_modules/@azure/msal-node": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-2.6.1.tgz", - "integrity": "sha512-wYwz83pWatTNWUCkTi3cAOXbchad5FnZz/pbZz7b8Z6FuEqohXcTtg6BLip9SmcjN6FlbwUdJIZYOof2v1Gnrg==", + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-2.15.0.tgz", + "integrity": "sha512-gVPW8YLz92ZeCibQH2QUw96odJoiM3k/ZPH3f2HxptozmH6+OnyyvKXo/Egg39HAM230akarQKHf0W74UHlh0Q==", + "license": "MIT", "dependencies": { - "@azure/msal-common": "14.6.0", + "@azure/msal-common": "14.15.0", "jsonwebtoken": "^9.0.0", "uuid": "^8.3.0" }, @@ -415,10 +485,24 @@ "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "license": "MIT", "bin": { "uuid": "dist/bin/uuid" } }, + "node_modules/@azure/openai": { + "version": "2.0.0-beta.2", + "resolved": "https://registry.npmjs.org/@azure/openai/-/openai-2.0.0-beta.2.tgz", + "integrity": "sha512-cElfZcBno4h3OWxZPvqqqtDUQ7jcGANlzF1oC9bigRiKe/0bAfBmOSYqPyb6Gaf+ngBVo9IWJs/5ZWNAVSvkqQ==", + "license": "MIT", + "dependencies": { + "@azure-rest/core-client": "^2.2.0", + "tslib": "^2.6.3" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@azure/search-documents": { "version": "12.0.0", "resolved": "https://registry.npmjs.org/@azure/search-documents/-/search-documents-12.0.0.tgz", @@ -479,6 +563,51 @@ "node": ">=6.9.0" } }, + "node_modules/@braintree/sanitize-url": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-7.1.0.tgz", + "integrity": "sha512-o+UlMLt49RvtCASlOMW0AkHnabN9wR9rwCCherxO0yG4Npy34GkvrAqdXQvrhNs+jh+gkK8gB8Lf05qL/O7KWg==", + "license": "MIT" + }, + "node_modules/@chevrotain/cst-dts-gen": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-11.0.3.tgz", + "integrity": "sha512-BvIKpRLeS/8UbfxXxgC33xOumsacaeCKAjAeLyOn7Pcp95HiRbrpl14S+9vaZLolnbssPIUuiUd8IvgkRyt6NQ==", + "license": "Apache-2.0", + "dependencies": { + "@chevrotain/gast": "11.0.3", + "@chevrotain/types": "11.0.3", + "lodash-es": "4.17.21" + } + }, + "node_modules/@chevrotain/gast": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-11.0.3.tgz", + "integrity": "sha512-+qNfcoNk70PyS/uxmj3li5NiECO+2YKZZQMbmjTqRI3Qchu8Hig/Q9vgkHpI3alNjr7M+a2St5pw5w5F6NL5/Q==", + "license": "Apache-2.0", + "dependencies": { + "@chevrotain/types": "11.0.3", + "lodash-es": "4.17.21" + } + }, + "node_modules/@chevrotain/regexp-to-ast": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/@chevrotain/regexp-to-ast/-/regexp-to-ast-11.0.3.tgz", + "integrity": "sha512-1fMHaBZxLFvWI067AVbGJav1eRY7N8DDvYCTwGBiE/ytKBgP8azTdgyrKyWZ9Mfh09eHWb5PgTSO8wi7U824RA==", + "license": "Apache-2.0" + }, + "node_modules/@chevrotain/types": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-11.0.3.tgz", + "integrity": "sha512-gsiM3G8b58kZC2HaWR50gu6Y1440cHiJ+i3JUvcp/35JchYejb2+5MVeJK0iKThYpAa/P2PYFV4hoi44HD+aHQ==", + "license": "Apache-2.0" + }, + "node_modules/@chevrotain/utils": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-11.0.3.tgz", + "integrity": "sha512-YslZMgtJUyuMbZ+aKvfF3x1f5liK4mWNxghFRv7jqRR9C3R3fAOGTTKvxXDa2Y1s9zSbcpuO0cAxDYsc9SrXoQ==", + "license": "Apache-2.0" + }, "node_modules/@codemirror/autocomplete": { "version": "6.11.1", "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.11.1.tgz", @@ -712,6 +841,27 @@ "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==", "dev": true }, + "node_modules/@iconify/types": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz", + "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==", + "license": "MIT" + }, + "node_modules/@iconify/utils": { + "version": "2.1.33", + "resolved": "https://registry.npmjs.org/@iconify/utils/-/utils-2.1.33.tgz", + "integrity": "sha512-jP9h6v/g0BIZx0p7XGJJVtkVnydtbgTgt9mVNcGDYwaa7UhdHdI9dvoq+gKj9sijMSJKxUPEG2JyjsgXjxL7Kw==", + "license": "MIT", + "dependencies": { + "@antfu/install-pkg": "^0.4.0", + "@antfu/utils": "^0.7.10", + "@iconify/types": "^2.0.0", + "debug": "^4.3.6", + "kolorist": "^1.8.0", + "local-pkg": "^0.5.0", + "mlly": "^1.7.1" + } + }, "node_modules/@img/sharp-darwin-arm64": { "version": "0.33.2", "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.2.tgz", @@ -1239,6 +1389,15 @@ } } }, + "node_modules/@mermaid-js/parser": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-0.3.0.tgz", + "integrity": "sha512-HsvL6zgE5sUPGgkIDlmAWR1HTNHz2Iy11BAWPTa4Jjabkpguy4Ze2gzfLrg6pdRuBvFwgUYyxiaNqZwrEEXepA==", + "license": "MIT", + "dependencies": { + "langium": "3.0.0" + } + }, "node_modules/@next/env": { "version": "14.0.4", "resolved": "https://registry.npmjs.org/@next/env/-/env-14.0.4.tgz", @@ -2715,10 +2874,10 @@ } }, "node_modules/acorn": { - "version": "8.11.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", - "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==", - "dev": true, + "version": "8.12.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", + "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", + "license": "MIT", "bin": { "acorn": "bin/acorn" }, @@ -3067,11 +3226,6 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, - "node_modules/base-64": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/base-64/-/base-64-0.1.0.tgz", - "integrity": "sha512-Y5gU45svrR5tI2Vt/X9GPd3L0HNIKzGu202EjxrXMpuc2V2CiKgemAbUUsqYmZJvPtCXoUKjNZwBJzsNScUbXA==" - }, "node_modules/bent": { "version": "7.3.12", "resolved": "https://registry.npmjs.org/bent/-/bent-7.3.12.tgz", @@ -3261,12 +3415,30 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/charenc": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", - "integrity": "sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==", - "engines": { - "node": "*" + "node_modules/chevrotain": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-11.0.3.tgz", + "integrity": "sha512-ci2iJH6LeIkvP9eJW6gpueU8cnZhv85ELY8w8WiFtNjMHA5ad6pQLaJo9mEly/9qUyCpvqX8/POVUTf18/HFdw==", + "license": "Apache-2.0", + "dependencies": { + "@chevrotain/cst-dts-gen": "11.0.3", + "@chevrotain/gast": "11.0.3", + "@chevrotain/regexp-to-ast": "11.0.3", + "@chevrotain/types": "11.0.3", + "@chevrotain/utils": "11.0.3", + "lodash-es": "4.17.21" + } + }, + "node_modules/chevrotain-allstar": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/chevrotain-allstar/-/chevrotain-allstar-0.3.1.tgz", + "integrity": "sha512-b7g+y9A0v4mxCW1qUhf3BSVPg+/NvGErk/dOkrDaHA0nQIQGAtrOjlX//9OQtRlSCy+x9rfB5N8yC71lH1nvMw==", + "license": "MIT", + "dependencies": { + "lodash-es": "^4.17.21" + }, + "peerDependencies": { + "chevrotain": "^11.0.0" } }, "node_modules/chokidar": { @@ -3414,6 +3586,12 @@ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, + "node_modules/confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", + "license": "MIT" + }, "node_modules/cookie": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", @@ -3422,6 +3600,15 @@ "node": ">= 0.6" } }, + "node_modules/cose-base": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-1.0.3.tgz", + "integrity": "sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==", + "license": "MIT", + "dependencies": { + "layout-base": "^1.0.0" + } + }, "node_modules/crelt": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", @@ -3441,14 +3628,6 @@ "node": ">= 8" } }, - "node_modules/crypt": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz", - "integrity": "sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==", - "engines": { - "node": "*" - } - }, "node_modules/cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", @@ -3465,18 +3644,533 @@ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" }, + "node_modules/cytoscape": { + "version": "3.30.2", + "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.30.2.tgz", + "integrity": "sha512-oICxQsjW8uSaRmn4UK/jkczKOqTrVqt5/1WL0POiJUT2EKNc9STM4hYFHv917yu55aTBMFNRzymlJhVAiWPCxw==", + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/cytoscape-cose-bilkent": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cytoscape-cose-bilkent/-/cytoscape-cose-bilkent-4.1.0.tgz", + "integrity": "sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==", + "license": "MIT", + "dependencies": { + "cose-base": "^1.0.0" + }, + "peerDependencies": { + "cytoscape": "^3.2.0" + } + }, + "node_modules/cytoscape-fcose": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cytoscape-fcose/-/cytoscape-fcose-2.2.0.tgz", + "integrity": "sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==", + "license": "MIT", + "dependencies": { + "cose-base": "^2.2.0" + }, + "peerDependencies": { + "cytoscape": "^3.2.0" + } + }, + "node_modules/cytoscape-fcose/node_modules/cose-base": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-2.2.0.tgz", + "integrity": "sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==", + "license": "MIT", + "dependencies": { + "layout-base": "^2.0.0" + } + }, + "node_modules/cytoscape-fcose/node_modules/layout-base": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-2.0.1.tgz", + "integrity": "sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==", + "license": "MIT" + }, + "node_modules/d3": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/d3/-/d3-7.9.0.tgz", + "integrity": "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==", + "license": "ISC", + "dependencies": { + "d3-array": "3", + "d3-axis": "3", + "d3-brush": "3", + "d3-chord": "3", + "d3-color": "3", + "d3-contour": "4", + "d3-delaunay": "6", + "d3-dispatch": "3", + "d3-drag": "3", + "d3-dsv": "3", + "d3-ease": "3", + "d3-fetch": "3", + "d3-force": "3", + "d3-format": "3", + "d3-geo": "3", + "d3-hierarchy": "3", + "d3-interpolate": "3", + "d3-path": "3", + "d3-polygon": "3", + "d3-quadtree": "3", + "d3-random": "3", + "d3-scale": "4", + "d3-scale-chromatic": "3", + "d3-selection": "3", + "d3-shape": "3", + "d3-time": "3", + "d3-time-format": "4", + "d3-timer": "3", + "d3-transition": "3", + "d3-zoom": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-axis": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz", + "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-brush": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", + "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "3", + "d3-transition": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-chord": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz", + "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", + "license": "ISC", + "dependencies": { + "d3-path": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-contour": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz", + "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==", + "license": "ISC", + "dependencies": { + "d3-array": "^3.2.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", + "license": "ISC", + "dependencies": { + "delaunator": "5" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-drag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-selection": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", + "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", + "license": "ISC", + "dependencies": { + "commander": "7", + "iconv-lite": "0.6", + "rw": "1" + }, + "bin": { + "csv2json": "bin/dsv2json.js", + "csv2tsv": "bin/dsv2dsv.js", + "dsv2dsv": "bin/dsv2dsv.js", + "dsv2json": "bin/dsv2json.js", + "json2csv": "bin/json2dsv.js", + "json2dsv": "bin/json2dsv.js", + "json2tsv": "bin/json2dsv.js", + "tsv2csv": "bin/dsv2dsv.js", + "tsv2json": "bin/dsv2json.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-fetch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz", + "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", + "license": "ISC", + "dependencies": { + "d3-dsv": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-force": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", + "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", + "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-geo": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz", + "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2.5.0 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-hierarchy": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", + "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-polygon": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz", + "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-quadtree": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", + "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-random": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz", + "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-sankey": { + "version": "0.12.3", + "resolved": "https://registry.npmjs.org/d3-sankey/-/d3-sankey-0.12.3.tgz", + "integrity": "sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-array": "1 - 2", + "d3-shape": "^1.2.0" + } + }, + "node_modules/d3-sankey/node_modules/d3-array": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz", + "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==", + "license": "BSD-3-Clause", + "dependencies": { + "internmap": "^1.0.0" + } + }, + "node_modules/d3-sankey/node_modules/d3-path": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz", + "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==", + "license": "BSD-3-Clause" + }, + "node_modules/d3-sankey/node_modules/d3-shape": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz", + "integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-path": "1" + } + }, + "node_modules/d3-sankey/node_modules/internmap": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz", + "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==", + "license": "ISC" + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "license": "ISC", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-interpolate": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-selection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "license": "ISC", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "license": "ISC", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "d3-selection": "2 - 3" + } + }, + "node_modules/d3-zoom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "2 - 3", + "d3-transition": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/dagre-d3-es": { + "version": "7.0.10", + "resolved": "https://registry.npmjs.org/dagre-d3-es/-/dagre-d3-es-7.0.10.tgz", + "integrity": "sha512-qTCQmEhcynucuaZgY5/+ti3X/rnszKZhEQH/ZdWdtP1tA/y3VoHJzcVrO9pjjJCNpigfscAtoUB5ONcd2wNn0A==", + "license": "MIT", + "dependencies": { + "d3": "^7.8.2", + "lodash-es": "^4.17.21" + } + }, "node_modules/damerau-levenshtein": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", "dev": true }, + "node_modules/dayjs": { + "version": "1.11.13", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", + "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==", + "license": "MIT" + }, "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", "dependencies": { - "ms": "2.1.2" + "ms": "^2.1.3" }, "engines": { "node": ">=6.0" @@ -3532,6 +4226,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/delaunator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz", + "integrity": "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==", + "license": "ISC", + "dependencies": { + "robust-predicates": "^3.0.2" + } + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -3575,15 +4278,6 @@ "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==" }, - "node_modules/digest-fetch": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/digest-fetch/-/digest-fetch-1.3.0.tgz", - "integrity": "sha512-CGJuv6iKNM7QyZlM2T3sPAdZWd/p9zQiRNS9G+9COUCwzWFTs0Xp8NF5iePx7wtvhDykReiRRrSeNb4oMmB8lA==", - "dependencies": { - "base-64": "^0.1.0", - "md5": "^2.3.0" - } - }, "node_modules/dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -3613,6 +4307,12 @@ "node": ">=6.0.0" } }, + "node_modules/dompurify": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.1.6.tgz", + "integrity": "sha512-cTOAhc36AalkjtBpfG6O8JimdTMWNXjiePT2xQH/ppBGi/4uIpmj8eKyIkMJErXWARyINV/sB38yf8JCLF5pbQ==", + "license": "(MPL-2.0 OR Apache-2.0)" + }, "node_modules/ecdsa-sig-formatter": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", @@ -4619,6 +5319,12 @@ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", "dev": true }, + "node_modules/hachure-fill": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/hachure-fill/-/hachure-fill-0.5.2.tgz", + "integrity": "sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==", + "license": "MIT" + }, "node_modules/has-bigints": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", @@ -4773,6 +5479,18 @@ "ms": "^2.0.0" } }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/ignore": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz", @@ -4835,6 +5553,15 @@ "node": ">= 0.4" } }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/invariant": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", @@ -4938,11 +5665,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" - }, "node_modules/is-callable": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", @@ -5347,6 +6069,7 @@ "version": "9.0.2", "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "license": "MIT", "dependencies": { "jws": "^3.2.2", "lodash.includes": "^4.3.0", @@ -5368,6 +6091,7 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "license": "MIT", "dependencies": { "buffer-equal-constant-time": "1.0.1", "ecdsa-sig-formatter": "1.0.11", @@ -5378,6 +6102,7 @@ "version": "3.2.2", "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "license": "MIT", "dependencies": { "jwa": "^1.4.1", "safe-buffer": "^5.0.1" @@ -5417,6 +6142,31 @@ "safe-buffer": "^5.0.1" } }, + "node_modules/katex": { + "version": "0.16.11", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.11.tgz", + "integrity": "sha512-RQrI8rlHY92OLf3rho/Ts8i/XvjgguEjOkO1BEXcU3N8BqPpSzBNwV/G0Ukr+P/l3ivvJUE/Fa/CwbS6HesGNQ==", + "funding": [ + "https://opencollective.com/katex", + "https://github.com/sponsors/katex" + ], + "license": "MIT", + "dependencies": { + "commander": "^8.3.0" + }, + "bin": { + "katex": "cli.js" + } + }, + "node_modules/katex/node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -5426,6 +6176,33 @@ "json-buffer": "3.0.1" } }, + "node_modules/khroma": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/khroma/-/khroma-2.1.0.tgz", + "integrity": "sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==" + }, + "node_modules/kolorist": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/kolorist/-/kolorist-1.8.0.tgz", + "integrity": "sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==", + "license": "MIT" + }, + "node_modules/langium": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/langium/-/langium-3.0.0.tgz", + "integrity": "sha512-+Ez9EoiByeoTu/2BXmEaZ06iPNXM6thWJp02KfBO/raSMyCJ4jw7AkWWa+zBCTm0+Tw1Fj9FOxdqSskyN5nAwg==", + "license": "MIT", + "dependencies": { + "chevrotain": "~11.0.3", + "chevrotain-allstar": "~0.3.0", + "vscode-languageserver": "~9.0.1", + "vscode-languageserver-textdocument": "~1.0.11", + "vscode-uri": "~3.0.8" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/language-subtag-registry": { "version": "0.3.22", "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.22.tgz", @@ -5444,6 +6221,12 @@ "node": ">=0.10" } }, + "node_modules/layout-base": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-1.0.2.tgz", + "integrity": "sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==", + "license": "MIT" + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -5470,6 +6253,22 @@ "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" }, + "node_modules/local-pkg": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.5.0.tgz", + "integrity": "sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==", + "license": "MIT", + "dependencies": { + "mlly": "^1.4.2", + "pkg-types": "^1.0.3" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -5485,6 +6284,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", + "license": "MIT" + }, "node_modules/lodash.castarray": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.castarray/-/lodash.castarray-4.4.0.tgz", @@ -5493,22 +6298,26 @@ "node_modules/lodash.includes": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", - "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==" + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "license": "MIT" }, "node_modules/lodash.isboolean": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", - "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==" + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "license": "MIT" }, "node_modules/lodash.isinteger": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", - "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==" + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "license": "MIT" }, "node_modules/lodash.isnumber": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", - "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==" + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "license": "MIT" }, "node_modules/lodash.isplainobject": { "version": "4.0.6", @@ -5518,7 +6327,8 @@ "node_modules/lodash.isstring": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", - "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==" + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "license": "MIT" }, "node_modules/lodash.merge": { "version": "4.6.2", @@ -5528,7 +6338,8 @@ "node_modules/lodash.once": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", - "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "license": "MIT" }, "node_modules/loose-envify": { "version": "1.4.0", @@ -5573,14 +6384,16 @@ "react": "^16.5.1 || ^17.0.0 || ^18.0.0" } }, - "node_modules/md5": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz", - "integrity": "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==", - "dependencies": { - "charenc": "0.0.2", - "crypt": "0.0.2", - "is-buffer": "~1.1.6" + "node_modules/marked": { + "version": "13.0.3", + "resolved": "https://registry.npmjs.org/marked/-/marked-13.0.3.tgz", + "integrity": "sha512-rqRix3/TWzE9rIoFGIn8JmsVfhiuC8VIQ8IdX5TfzmeBucdY05/0UlzKaw0eVtpcN/OdVFpBk7CjKGo9iHJ/zA==", + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 18" } }, "node_modules/merge2": { @@ -5591,6 +6404,33 @@ "node": ">= 8" } }, + "node_modules/mermaid": { + "version": "11.3.0", + "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.3.0.tgz", + "integrity": "sha512-fFmf2gRXLtlGzug4wpIGN+rQdZ30M8IZEB1D3eZkXNqC7puhqeURBcD/9tbwXsqBO+A6Nzzo3MSSepmnw5xSeg==", + "license": "MIT", + "dependencies": { + "@braintree/sanitize-url": "^7.0.1", + "@iconify/utils": "^2.1.32", + "@mermaid-js/parser": "^0.3.0", + "cytoscape": "^3.29.2", + "cytoscape-cose-bilkent": "^4.1.0", + "cytoscape-fcose": "^2.2.0", + "d3": "^7.9.0", + "d3-sankey": "^0.12.3", + "dagre-d3-es": "7.0.10", + "dayjs": "^1.11.10", + "dompurify": "^3.0.11 <3.1.7", + "katex": "^0.16.9", + "khroma": "^2.1.0", + "lodash-es": "^4.17.21", + "marked": "^13.0.2", + "roughjs": "^4.6.6", + "stylis": "^4.3.1", + "ts-dedent": "^2.2.0", + "uuid": "^9.0.1" + } + }, "node_modules/micromatch": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", @@ -5655,10 +6495,23 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/mlly": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.7.2.tgz", + "integrity": "sha512-tN3dvVHYVz4DhSXinXIk7u9syPYaJvio118uomkovAtWBT+RdbP6Lfh/5Lvo519YMmwBafwlh20IPTXIStscpA==", + "license": "MIT", + "dependencies": { + "acorn": "^8.12.1", + "pathe": "^1.1.2", + "pkg-types": "^1.2.0", + "ufo": "^1.5.4" + } + }, "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" }, "node_modules/mz": { "version": "2.7.0", @@ -6056,22 +6909,29 @@ } }, "node_modules/openai": { - "version": "4.26.0", - "resolved": "https://registry.npmjs.org/openai/-/openai-4.26.0.tgz", - "integrity": "sha512-HPC7tgYdeP38F3uHA5WgnoXZyGbAp9jgcIo23p6It+q/07u4C+NZ8xHKlMShsPbDDmFRpPsa3vdbXYpbhJH3eg==", + "version": "4.67.3", + "resolved": "https://registry.npmjs.org/openai/-/openai-4.67.3.tgz", + "integrity": "sha512-HT2tZgjLgRqbLQNKmYtjdF/4TQuiBvg1oGvTDhwpSEQzxo6/oM1us8VQ53vBK2BiKvCxFuq6gKGG70qfwrNhKg==", + "license": "Apache-2.0", "dependencies": { "@types/node": "^18.11.18", "@types/node-fetch": "^2.6.4", "abort-controller": "^3.0.0", "agentkeepalive": "^4.2.1", - "digest-fetch": "^1.3.0", "form-data-encoder": "1.7.2", "formdata-node": "^4.3.2", - "node-fetch": "^2.6.7", - "web-streams-polyfill": "^3.2.1" + "node-fetch": "^2.6.7" }, "bin": { "openai": "bin/cli" + }, + "peerDependencies": { + "zod": "^3.23.8" + }, + "peerDependenciesMeta": { + "zod": { + "optional": true + } } }, "node_modules/openai/node_modules/@types/node": { @@ -6151,6 +7011,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/package-manager-detector": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-0.2.2.tgz", + "integrity": "sha512-VgXbyrSNsml4eHWIvxxG/nTL4wgybMTXCV2Un/+yEc3aDKKU6nQBZjbeP3Pl3qm9Qg92X/1ng4ffvCeD/zwHgg==", + "license": "MIT" + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -6180,6 +7046,12 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/path-data-parser": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/path-data-parser/-/path-data-parser-0.1.0.tgz", + "integrity": "sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w==", + "license": "MIT" + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -6220,6 +7092,12 @@ "node": ">=8" } }, + "node_modules/pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "license": "MIT" + }, "node_modules/picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", @@ -6252,6 +7130,33 @@ "node": ">= 6" } }, + "node_modules/pkg-types": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.2.1.tgz", + "integrity": "sha512-sQoqa8alT3nHjGuTjuKgOnvjo4cljkufdtLMnO2LBP/wRwuDlo1tkaEdMxCRhyGRPacv/ztlZgDPm2b7FAmEvw==", + "license": "MIT", + "dependencies": { + "confbox": "^0.1.8", + "mlly": "^1.7.2", + "pathe": "^1.1.2" + } + }, + "node_modules/points-on-curve": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/points-on-curve/-/points-on-curve-0.2.0.tgz", + "integrity": "sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==", + "license": "MIT" + }, + "node_modules/points-on-path": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/points-on-path/-/points-on-path-0.2.1.tgz", + "integrity": "sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g==", + "license": "MIT", + "dependencies": { + "path-data-parser": "0.1.0", + "points-on-curve": "0.2.0" + } + }, "node_modules/postcss": { "version": "8.4.32", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.32.tgz", @@ -6770,6 +7675,24 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/robust-predicates": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz", + "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==", + "license": "Unlicense" + }, + "node_modules/roughjs": { + "version": "4.6.6", + "resolved": "https://registry.npmjs.org/roughjs/-/roughjs-4.6.6.tgz", + "integrity": "sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ==", + "license": "MIT", + "dependencies": { + "hachure-fill": "^0.5.2", + "path-data-parser": "^0.1.0", + "points-on-curve": "^0.2.0", + "points-on-path": "^0.2.1" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -6792,6 +7715,12 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==", + "license": "BSD-3-Clause" + }, "node_modules/safe-array-concat": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.0.1.tgz", @@ -6843,6 +7772,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, "node_modules/sax": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/sax/-/sax-1.3.0.tgz", @@ -7162,6 +8097,12 @@ } } }, + "node_modules/stylis": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.4.tgz", + "integrity": "sha512-osIBl6BGUmSfDkyH2mB7EFvCJntXDrLhKjHTRj/rK6xLH0yuPrHULDRQzKokSOD4VoorhtKpfcfW1GAntu8now==", + "license": "MIT" + }, "node_modules/sucrase": { "version": "3.34.0", "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.34.0.tgz", @@ -7315,6 +8256,12 @@ "node": ">=0.8" } }, + "node_modules/tinyexec": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.0.tgz", + "integrity": "sha512-tVGE0mVJPGb0chKhqmsoosjsS+qUnJVGJpZgsHYQcGoPlG3B51R3PouqTgEGH2Dc9jjFyOqOpix6ZHNMXp1FZg==", + "license": "MIT" + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -7343,6 +8290,15 @@ "typescript": ">=4.2.0" } }, + "node_modules/ts-dedent": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.2.0.tgz", + "integrity": "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==", + "license": "MIT", + "engines": { + "node": ">=6.10" + } + }, "node_modules/ts-interface-checker": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", @@ -7361,9 +8317,10 @@ } }, "node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", + "license": "0BSD" }, "node_modules/tunnel": { "version": "0.0.6", @@ -7475,6 +8432,12 @@ "node": ">=14.17" } }, + "node_modules/ufo": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.5.4.tgz", + "integrity": "sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==", + "license": "MIT" + }, "node_modules/unbox-primitive": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", @@ -7630,6 +8593,55 @@ } } }, + "node_modules/vscode-jsonrpc": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz", + "integrity": "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/vscode-languageserver": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-9.0.1.tgz", + "integrity": "sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==", + "license": "MIT", + "dependencies": { + "vscode-languageserver-protocol": "3.17.5" + }, + "bin": { + "installServerIntoExtension": "bin/installServerIntoExtension" + } + }, + "node_modules/vscode-languageserver-protocol": { + "version": "3.17.5", + "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.5.tgz", + "integrity": "sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==", + "license": "MIT", + "dependencies": { + "vscode-jsonrpc": "8.2.0", + "vscode-languageserver-types": "3.17.5" + } + }, + "node_modules/vscode-languageserver-textdocument": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.12.tgz", + "integrity": "sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==", + "license": "MIT" + }, + "node_modules/vscode-languageserver-types": { + "version": "3.17.5", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz", + "integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==", + "license": "MIT" + }, + "node_modules/vscode-uri": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.8.tgz", + "integrity": "sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==", + "license": "MIT" + }, "node_modules/w3c-keyname": { "version": "2.2.8", "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", @@ -7647,14 +8659,6 @@ "node": ">=10.13.0" } }, - "node_modules/web-streams-polyfill": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz", - "integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==", - "engines": { - "node": ">= 8" - } - }, "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", @@ -7839,9 +8843,10 @@ } }, "node_modules/zod": { - "version": "3.22.4", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.4.tgz", - "integrity": "sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==", + "version": "3.23.8", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz", + "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==", + "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/src/package.json b/src/package.json index 9267d5cc4..74a5b248f 100644 --- a/src/package.json +++ b/src/package.json @@ -53,7 +53,8 @@ "tailwind-merge": "^2.1.0", "tailwindcss-animate": "^1.0.7", "valtio": "^1.12.1", - "zod": "^3.22.4" + "zod": "^3.22.4", + "mermaid":"^11.3.0" }, "devDependencies": { "@types/node": "^20", From 055f5ece2b453b91050a46b999a63447c1338382 Mon Sep 17 00:00:00 2001 From: Freddy Ayala Date: Tue, 15 Oct 2024 16:55:17 +0200 Subject: [PATCH 04/12] fix --- src/features/chat-page/MermaidDiagram.tsx | 83 +++++++++++----------- src/features/chat-page/message-content.tsx | 2 +- 2 files changed, 44 insertions(+), 41 deletions(-) diff --git a/src/features/chat-page/MermaidDiagram.tsx b/src/features/chat-page/MermaidDiagram.tsx index 21044cd55..f0e6f7834 100644 --- a/src/features/chat-page/MermaidDiagram.tsx +++ b/src/features/chat-page/MermaidDiagram.tsx @@ -1,47 +1,50 @@ - -import { useEffect, useRef, useState } from 'react'; +import React, { useEffect, useRef, useState } from 'react'; import mermaid from 'mermaid'; interface MermaidDiagramProps { - chartCode: string; - } - - const MermaidDiagram: React.FC = ({ chartCode }) => { - const mermaidRef = useRef(null); - const [loaded, setLoaded] = useState(false); - - useEffect(() => { - const renderMermaid = async () => { - mermaid.initialize({ - startOnLoad: true, - logLevel: 'warn', // Use log level string for better error handling - }); - - if (mermaidRef.current) { - try { - const svgCode = await mermaid.render('mermaidChartId', chartCode); + chartCode: string; +} + +const MermaidDiagram: React.FC = ({ chartCode }) => { + const mermaidRef = useRef(null); + const [loaded, setLoaded] = useState(false); + + useEffect(() => { + const renderMermaid = async () => { + mermaid.initialize({ + startOnLoad: false, + logLevel: mermaid.LogLevel.Warn, // Use LogLevel for better error handling + }); + + const elementId = `mermaid-${Math.random().toString(36).substr(2, 9)}`; // Unique ID for the diagram + + if (mermaidRef.current) { + try { + // Using callback method according to Mermaid's latest documentation at the time of writing + mermaid.render(elementId, chartCode, (svgCode) => { if (mermaidRef.current) { - mermaidRef.current.innerHTML = typeof svgCode === 'string' ? svgCode : ''; + mermaidRef.current.innerHTML = svgCode; setLoaded(true); } - } catch (err) { - console.error('Mermaid diagram rendering failed', err); - } + }, mermaidRef.current); + } catch (err) { + console.error('Mermaid diagram rendering failed', err); } - }; - - renderMermaid(); - }, [chartCode]); - - if (!loaded) { - return
Loading Mermaid diagram...
; - } - - return ( -
-
-
- ); - }; - - export default MermaidDiagram; \ No newline at end of file + } + }; + + renderMermaid(); + }, [chartCode]); + + if (!loaded) { + return
Loading Mermaid diagram...
; + } + + return ( +
+
+
+ ); +}; + +export default MermaidDiagram; diff --git a/src/features/chat-page/message-content.tsx b/src/features/chat-page/message-content.tsx index 05d9479f7..3f5998391 100644 --- a/src/features/chat-page/message-content.tsx +++ b/src/features/chat-page/message-content.tsx @@ -33,7 +33,7 @@ const MessageContent: React.FC = ({ message }) => { return ( <> {mermaidCode ? ( - + <>

) : ( )} From efbf5dc265516a6b01cefb080a188c1e7bd59436 Mon Sep 17 00:00:00 2001 From: Freddy Ayala Date: Tue, 15 Oct 2024 16:59:02 +0200 Subject: [PATCH 05/12] correct --- src/features/chat-page/MermaidDiagram.tsx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/features/chat-page/MermaidDiagram.tsx b/src/features/chat-page/MermaidDiagram.tsx index f0e6f7834..7e289ba48 100644 --- a/src/features/chat-page/MermaidDiagram.tsx +++ b/src/features/chat-page/MermaidDiagram.tsx @@ -13,7 +13,7 @@ const MermaidDiagram: React.FC = ({ chartCode }) => { const renderMermaid = async () => { mermaid.initialize({ startOnLoad: false, - logLevel: mermaid.LogLevel.Warn, // Use LogLevel for better error handling + logLevel: 'warn', // Use logLevel for better error handling }); const elementId = `mermaid-${Math.random().toString(36).substr(2, 9)}`; // Unique ID for the diagram @@ -21,12 +21,12 @@ const MermaidDiagram: React.FC = ({ chartCode }) => { if (mermaidRef.current) { try { // Using callback method according to Mermaid's latest documentation at the time of writing - mermaid.render(elementId, chartCode, (svgCode) => { - if (mermaidRef.current) { - mermaidRef.current.innerHTML = svgCode; - setLoaded(true); - } - }, mermaidRef.current); + const svgContainer = document.createElement('div'); + mermaid.render(elementId, chartCode, svgContainer); + if (mermaidRef.current) { + mermaidRef.current.innerHTML = svgContainer.innerHTML; + setLoaded(true); + } } catch (err) { console.error('Mermaid diagram rendering failed', err); } From e6cabe5c605fb1e9200908f1ef82b3571bacbaa9 Mon Sep 17 00:00:00 2001 From: Freddy Ayala Date: Tue, 15 Oct 2024 17:17:27 +0200 Subject: [PATCH 06/12] mermaid update --- src/features/chat-page/MermaidDiagram.tsx | 76 +++---- src/features/chat-page/message-content.tsx | 21 +- src/package-lock.json | 232 ++++++++++++++++++++- 3 files changed, 273 insertions(+), 56 deletions(-) diff --git a/src/features/chat-page/MermaidDiagram.tsx b/src/features/chat-page/MermaidDiagram.tsx index 7e289ba48..314a73122 100644 --- a/src/features/chat-page/MermaidDiagram.tsx +++ b/src/features/chat-page/MermaidDiagram.tsx @@ -1,50 +1,30 @@ -import React, { useEffect, useRef, useState } from 'react'; -import mermaid from 'mermaid'; - -interface MermaidDiagramProps { - chartCode: string; -} - -const MermaidDiagram: React.FC = ({ chartCode }) => { - const mermaidRef = useRef(null); - const [loaded, setLoaded] = useState(false); - - useEffect(() => { - const renderMermaid = async () => { - mermaid.initialize({ - startOnLoad: false, - logLevel: 'warn', // Use logLevel for better error handling - }); - - const elementId = `mermaid-${Math.random().toString(36).substr(2, 9)}`; // Unique ID for the diagram - - if (mermaidRef.current) { - try { - // Using callback method according to Mermaid's latest documentation at the time of writing - const svgContainer = document.createElement('div'); - mermaid.render(elementId, chartCode, svgContainer); - if (mermaidRef.current) { - mermaidRef.current.innerHTML = svgContainer.innerHTML; - setLoaded(true); - } - } catch (err) { - console.error('Mermaid diagram rendering failed', err); - } - } - }; - - renderMermaid(); - }, [chartCode]); - - if (!loaded) { - return
Loading Mermaid diagram...
; - } - - return ( -
-
-
- ); +import mermaid from "mermaid"; +import { useEffect, useRef } from "react"; + +mermaid.initialize({}); + +const MermaidComponent = ({ source, id }: { source: string; id: string }) => { + const mermaidRef = useRef(null); + + useEffect(() => { + const initializeMermaid = async () => { + if (mermaidRef.current) { + mermaidRef.current.innerHTML = source; + const { svg, bindFunctions } = await mermaid.render(`mermaid-diagram-${id}`, source); + mermaidRef.current.innerHTML = svg; + bindFunctions?.(mermaidRef.current); + } + }; + + initializeMermaid(); + + // Clean up mermaid instance when unmounting; doing nothing at the momemt + return () => { + + }; + }, [source]); + + return
; }; -export default MermaidDiagram; +export default MermaidComponent; \ No newline at end of file diff --git a/src/features/chat-page/message-content.tsx b/src/features/chat-page/message-content.tsx index 3f5998391..24c68e803 100644 --- a/src/features/chat-page/message-content.tsx +++ b/src/features/chat-page/message-content.tsx @@ -1,7 +1,7 @@ import { Markdown } from "@/features/ui/markdown/markdown"; import { FunctionSquare } from "lucide-react"; import React from "react"; -import MermaidDiagram from "@/features/chat-page/MermaidDiagram"; // Adjust the import path as necessary + import { Accordion, AccordionContent, @@ -10,6 +10,8 @@ import { } from "../ui/accordion"; import { RecursiveUI } from "../ui/recursive-ui"; import { CitationAction } from "./citation/citation-action"; +import MermaidComponent from "./MermaidDiagram"; + interface MessageContentProps { message: { @@ -29,13 +31,18 @@ const MessageContent: React.FC = ({ message }) => { if (message.role === "assistant" || message.role === "user") { const mermaidCode = getMermaidCode(message.content); + const mermaidDiagramId = useMemo(() => `mermaid-${Date.now()}`, []); // Generate a unique ID return ( <> - {mermaidCode ? ( - <>

- ) : ( - + {/* Render message content and optionally a mermaid diagram */} + + {mermaidCode && ( + <> +
+ {/* Render Mermaid diagram with the extracted code and unique ID */} + + )} {message.multiModalImage && Multimodal} @@ -82,3 +89,7 @@ const toJson = (value: string) => { }; export default MessageContent; + function useMemo(arg0: () => string, arg1: never[]) { + throw new Error("Function not implemented."); + } + diff --git a/src/package-lock.json b/src/package-lock.json index 58ac749c2..3581c2e61 100644 --- a/src/package-lock.json +++ b/src/package-lock.json @@ -46,6 +46,7 @@ "openai": "^4.67.1", "react": "^18", "react-dom": "^18", + "react-mermaid": "^0.1.3", "react-syntax-highlighter": "^15.5.0", "server-only": "^0.0.1", "sharp": "^0.33.2", @@ -4142,6 +4143,28 @@ "node": ">=12" } }, + "node_modules/dagre": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/dagre/-/dagre-0.7.4.tgz", + "integrity": "sha512-XvkbOx1bjJABBaE2WKSAB29rOT96M3fpq1C1n/hBLSNzwrPi06LelkU4OFA+s25Ua5yhsQvhp7+JgDdC//pM8A==", + "license": "MIT", + "dependencies": { + "graphlib": "^1.0.5", + "lodash": "^3.10.0" + } + }, + "node_modules/dagre-d3": { + "version": "0.4.10", + "resolved": "https://registry.npmjs.org/dagre-d3/-/dagre-d3-0.4.10.tgz", + "integrity": "sha512-lUI2Ur2YlpYqE/BI4Wqy6luWykXEIv01pHNgFQqzRTNjtedR9IFXFPacHa+tVN2E0dHOzrV0Mi3jRbskOlFzZw==", + "license": "MIT", + "dependencies": { + "d3": "^3.3.8", + "dagre": "^0.7.3", + "graphlib": "^1.0.5", + "lodash": "^3.10.0" + } + }, "node_modules/dagre-d3-es": { "version": "7.0.10", "resolved": "https://registry.npmjs.org/dagre-d3-es/-/dagre-d3-es-7.0.10.tgz", @@ -4152,6 +4175,12 @@ "lodash-es": "^4.17.21" } }, + "node_modules/dagre-d3/node_modules/d3": { + "version": "3.5.17", + "resolved": "https://registry.npmjs.org/d3/-/d3-3.5.17.tgz", + "integrity": "sha512-yFk/2idb8OHPKkbAL8QaOaqENNoMhIaSHZerk3oQsECwkObkCpJyjYwCe+OHiq6UEdhe1m8ZGARRRO3ljFjlKg==", + "license": "BSD-3-Clause" + }, "node_modules/damerau-levenshtein": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", @@ -5319,12 +5348,45 @@ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", "dev": true }, + "node_modules/graphlib": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/graphlib/-/graphlib-1.0.7.tgz", + "integrity": "sha512-jNb7RbqTIRyZRmcVCxGefOlGWjNdbjcT2tFj36zhnRa8yhAlOvydh9nBixfLQIqSU+DwCx47tg3ysw8NIYhpsA==", + "license": "MIT", + "dependencies": { + "lodash": "^3.10.0" + } + }, "node_modules/hachure-fill": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/hachure-fill/-/hachure-fill-0.5.2.tgz", "integrity": "sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==", "license": "MIT" }, + "node_modules/has-ansi": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-0.1.0.tgz", + "integrity": "sha512-1YsTg1fk2/6JToQhtZkArMkurq8UoWU1Qe0aR3VUHjgij4nOylSWLWAtBXoZ4/dXOmugfLGm1c+QhuD0JyedFA==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^0.2.0" + }, + "bin": { + "has-ansi": "cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-ansi/node_modules/ansi-regex": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-0.2.1.tgz", + "integrity": "sha512-sGwIGMjhYdW26/IhwK2gkWWI8DRCVO6uj3hYgHT+zD+QL1pa37tM3ujhyfcJIYSbsxp7Gxhy7zrRW/1AHm4BmA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/has-bigints": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", @@ -5430,6 +5492,15 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/he": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/he/-/he-0.5.0.tgz", + "integrity": "sha512-DoufbNNOFzwRPy8uecq+j+VCPQ+JyDelHTmSgygrA5TsR8Cbw4Qcir5sGtWiusB4BdT89nmlaVDhSJOqC/33vw==", + "license": "MIT", + "bin": { + "he": "bin/he" + } + }, "node_modules/highlight.js": { "version": "10.7.3", "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", @@ -5981,8 +6052,7 @@ "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" }, "node_modules/iterator.prototype": { "version": "1.1.2", @@ -6284,6 +6354,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lodash": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", + "integrity": "sha512-9mDDwqVIma6OZX79ZlDACZl8sBm0TEnkf99zV3iMA4GzkIT/9hiqP5mY0HoT1iNLCrKc/R1HByV+yJfRWVJryQ==", + "license": "MIT" + }, "node_modules/lodash-es": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", @@ -6490,11 +6566,22 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "license": "MIT", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, "node_modules/mlly": { "version": "1.7.2", "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.7.2.tgz", @@ -6507,6 +6594,15 @@ "ufo": "^1.5.4" } }, + "node_modules/moment": { + "version": "2.30.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", + "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -7452,6 +7548,136 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "dev": true }, + "node_modules/react-mermaid": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/react-mermaid/-/react-mermaid-0.1.3.tgz", + "integrity": "sha512-0mSpf6Zr4yYr0hMcys/g6h+/CLhOeTQS0JS3FbNjQ1ztjn6WrmCtpkI5cU08QWb9G12ibx5WOkZ8lpXkmeJ2oQ==", + "license": "MIT", + "dependencies": { + "mermaid": "^0.5.1" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/react-mermaid/node_modules/ansi-regex": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-0.2.1.tgz", + "integrity": "sha512-sGwIGMjhYdW26/IhwK2gkWWI8DRCVO6uj3hYgHT+zD+QL1pa37tM3ujhyfcJIYSbsxp7Gxhy7zrRW/1AHm4BmA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-mermaid/node_modules/ansi-styles": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-1.1.0.tgz", + "integrity": "sha512-f2PKUkN5QngiSemowa6Mrk9MPCdtFiOSmibjZ+j1qhLGHHYsqZwmBMRF3IRMVXo8sybDqx2fJl2d/8OphBoWkA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-mermaid/node_modules/chalk": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-0.5.1.tgz", + "integrity": "sha512-bIKA54hP8iZhyDT81TOsJiQvR1gW+ZYSXFaZUAvoD4wCHdbHY2actmpTE4x344ZlFqHbvoxKOaESULTZN2gstg==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^1.1.0", + "escape-string-regexp": "^1.0.0", + "has-ansi": "^0.1.0", + "strip-ansi": "^0.3.0", + "supports-color": "^0.2.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-mermaid/node_modules/d3": { + "version": "3.5.6", + "resolved": "https://registry.npmjs.org/d3/-/d3-3.5.6.tgz", + "integrity": "sha512-i1x8Q3lGerBazuvWsImnUKrjfCdBnRnk8aq7hqOK/5+CAWJTt/zr9CaR1mlJf17oH8l/v4mOaDLU+F/l2dq1Vg==", + "license": "BSD-3-Clause" + }, + "node_modules/react-mermaid/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/react-mermaid/node_modules/mermaid": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-0.5.8.tgz", + "integrity": "sha512-zCgjHIeeY6AFbT5NiXRsHyNqurPcF8tKEhFT/RNdyyzIFdP2kWOakFczRDaQGl1eO5S7TyquUr9URMKVjJobug==", + "license": "MIT", + "dependencies": { + "chalk": "^0.5.1", + "d3": "3.5.6", + "dagre": "^0.7.4", + "dagre-d3": "0.4.10", + "he": "^0.5.0", + "minimist": "^1.1.0", + "mkdirp": "^0.5.0", + "moment": "^2.9.0", + "semver": "^4.1.1", + "which": "^1.0.8" + }, + "bin": { + "mermaid": "bin/mermaid.js" + } + }, + "node_modules/react-mermaid/node_modules/semver": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/semver/-/semver-4.3.6.tgz", + "integrity": "sha512-IrpJ+yoG4EOH8DFWuVg+8H1kW1Oaof0Wxe7cPcXW3x9BjkN/eVo54F15LyqemnDIUYskQWr9qvl/RihmSy6+xQ==", + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/react-mermaid/node_modules/strip-ansi": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-0.3.0.tgz", + "integrity": "sha512-DerhZL7j6i6/nEnVG0qViKXI0OKouvvpsAiaj7c+LfqZZZxdwZtv8+UiA/w4VUJpT8UzX0pR1dcHOii1GbmruQ==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^0.2.1" + }, + "bin": { + "strip-ansi": "cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-mermaid/node_modules/supports-color": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-0.2.0.tgz", + "integrity": "sha512-tdCZ28MnM7k7cJDJc7Eq80A9CsRFAAOZUy41npOZCs++qSjfIy7o5Rh46CBk+Dk5FbKJ33X3Tqg4YrV07N5RaA==", + "license": "MIT", + "bin": { + "supports-color": "cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-mermaid/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, "node_modules/react-remove-scroll": { "version": "2.5.5", "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.5.tgz", From 7102d509a9e1eb251bc9fe0908913c79d3aae675 Mon Sep 17 00:00:00 2001 From: Freddy Ayala Date: Tue, 15 Oct 2024 17:20:56 +0200 Subject: [PATCH 07/12] fix --- src/features/chat-page/message-content.tsx | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/features/chat-page/message-content.tsx b/src/features/chat-page/message-content.tsx index 24c68e803..6df846e63 100644 --- a/src/features/chat-page/message-content.tsx +++ b/src/features/chat-page/message-content.tsx @@ -1,6 +1,6 @@ import { Markdown } from "@/features/ui/markdown/markdown"; import { FunctionSquare } from "lucide-react"; -import React from "react"; +import React, { useMemo } from "react"; import { Accordion, @@ -89,7 +89,4 @@ const toJson = (value: string) => { }; export default MessageContent; - function useMemo(arg0: () => string, arg1: never[]) { - throw new Error("Function not implemented."); - } From e49ab7a97f5efc0b4d095310505af568db6008ef Mon Sep 17 00:00:00 2001 From: Freddy Ayala Date: Wed, 16 Oct 2024 16:05:34 +0200 Subject: [PATCH 08/12] added button --- src/features/chat-page/MermaidDiagram.tsx | 30 ------------- src/features/chat-page/mermaid-diagram.tsx | 50 ++++++++++++++++++++++ src/features/chat-page/message-content.tsx | 2 +- 3 files changed, 51 insertions(+), 31 deletions(-) delete mode 100644 src/features/chat-page/MermaidDiagram.tsx create mode 100644 src/features/chat-page/mermaid-diagram.tsx diff --git a/src/features/chat-page/MermaidDiagram.tsx b/src/features/chat-page/MermaidDiagram.tsx deleted file mode 100644 index 314a73122..000000000 --- a/src/features/chat-page/MermaidDiagram.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import mermaid from "mermaid"; -import { useEffect, useRef } from "react"; - -mermaid.initialize({}); - -const MermaidComponent = ({ source, id }: { source: string; id: string }) => { - const mermaidRef = useRef(null); - - useEffect(() => { - const initializeMermaid = async () => { - if (mermaidRef.current) { - mermaidRef.current.innerHTML = source; - const { svg, bindFunctions } = await mermaid.render(`mermaid-diagram-${id}`, source); - mermaidRef.current.innerHTML = svg; - bindFunctions?.(mermaidRef.current); - } - }; - - initializeMermaid(); - - // Clean up mermaid instance when unmounting; doing nothing at the momemt - return () => { - - }; - }, [source]); - - return
; -}; - -export default MermaidComponent; \ No newline at end of file diff --git a/src/features/chat-page/mermaid-diagram.tsx b/src/features/chat-page/mermaid-diagram.tsx new file mode 100644 index 000000000..0c542b931 --- /dev/null +++ b/src/features/chat-page/mermaid-diagram.tsx @@ -0,0 +1,50 @@ +import mermaid from "mermaid"; +import { useEffect, useRef } from "react"; + +mermaid.initialize({}); + +const MermaidComponent = ({ source, id }: { source: string; id: string }) => { + const mermaidRef = useRef(null); + + useEffect(() => { + const initializeMermaid = async () => { + if (mermaidRef.current) { + mermaidRef.current.innerHTML = source; + const { svg, bindFunctions } = await mermaid.render(`mermaid-diagram-${id}`, source); + mermaidRef.current.innerHTML = svg; + bindFunctions?.(mermaidRef.current); + } + }; + + initializeMermaid(); + + // Clean up mermaid instance when unmounting; doing nothing at the moment + return () => {}; + }, [source]); + + const downloadSVG = () => { + if (mermaidRef.current) { + const svgContent = mermaidRef.current.innerHTML; + const blob = new Blob([svgContent], { type: "image/svg+xml" }); + const url = URL.createObjectURL(blob); + + const downloadLink = document.createElement("a"); + downloadLink.href = url; + downloadLink.download = `${id}.svg`; + document.body.appendChild(downloadLink); + downloadLink.click(); + document.body.removeChild(downloadLink); + + URL.revokeObjectURL(url); + } + }; + + return ( +
+
+ +
+ ); +}; + +export default MermaidComponent; diff --git a/src/features/chat-page/message-content.tsx b/src/features/chat-page/message-content.tsx index 6df846e63..9df7df191 100644 --- a/src/features/chat-page/message-content.tsx +++ b/src/features/chat-page/message-content.tsx @@ -10,7 +10,7 @@ import { } from "../ui/accordion"; import { RecursiveUI } from "../ui/recursive-ui"; import { CitationAction } from "./citation/citation-action"; -import MermaidComponent from "./MermaidDiagram"; +import MermaidComponent from "./mermaid-diagram"; interface MessageContentProps { From f6aa0b99f091465bd059f0b3133fc7b8c110164a Mon Sep 17 00:00:00 2001 From: Freddy Ayala Date: Wed, 16 Oct 2024 16:15:44 +0200 Subject: [PATCH 09/12] styled button --- src/features/chat-page/mermaid-diagram.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/features/chat-page/mermaid-diagram.tsx b/src/features/chat-page/mermaid-diagram.tsx index 0c542b931..178241d45 100644 --- a/src/features/chat-page/mermaid-diagram.tsx +++ b/src/features/chat-page/mermaid-diagram.tsx @@ -1,5 +1,6 @@ import mermaid from "mermaid"; import { useEffect, useRef } from "react"; +import { Button } from "@/features/ui/button"; // Import your custom Button component mermaid.initialize({}); @@ -42,7 +43,9 @@ const MermaidComponent = ({ source, id }: { source: string; id: string }) => { return (
- +
+ +
); }; From b161fe123e0fb95480828b757dc10ffdd6fac4f0 Mon Sep 17 00:00:00 2001 From: Freddy Ayala Date: Wed, 16 Oct 2024 16:26:22 +0200 Subject: [PATCH 10/12] removed managed identities for publish just mermaid code --- infra/main.bicep | 5 - infra/main.json | 250 +++--------------------------------------- infra/resources.bicep | 150 +------------------------ 3 files changed, 21 insertions(+), 384 deletions(-) diff --git a/infra/main.bicep b/infra/main.bicep index 13771442a..f15740ad5 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -60,10 +60,6 @@ resource rg 'Microsoft.Resources/resourceGroups@2021-04-01' = { tags: tags } -//Activates/Deactivates Authentication by key, if true it will enforce RBAC using managed identities -param disableLocalAuth bool = false - - module resources 'resources.bicep' = { name: 'all-resources' scope: rg @@ -92,7 +88,6 @@ module resources 'resources.bicep' = { storageServiceSku: storageServiceSku storageServiceImageContainerName: storageServiceImageContainerName location: location - disableLocalAuth:disableLocalAuth } } diff --git a/infra/main.json b/infra/main.json index 5965ae490..397308da6 100644 --- a/infra/main.json +++ b/infra/main.json @@ -4,8 +4,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "3256939710395371274" + "version": "0.29.47.4906", + "templateHash": "18214004695586675733" } }, "parameters": { @@ -28,27 +28,12 @@ "type": "string", "allowedValues": [ "australiaeast", - "brazilsouth", "canadaeast", - "eastus", - "eastus2", "francecentral", - "germanywestcentral", - "japaneast", - "koreacentral", - "northcentralus", - "norwayeast", - "polandcentral", - "spaincentral", - "southafricanorth", - "southcentralus", "southindia", - "swedencentral", - "switzerlandnorth", "uksouth", - "westeurope", - "westus", - "westus3" + "swedencentral", + "westus" ], "metadata": { "azd": { @@ -63,7 +48,7 @@ }, "openAIApiVersion": { "type": "string", - "defaultValue": "2024-08-01-preview" + "defaultValue": "2024-05-13" }, "chatGptDeploymentCapacity": { "type": "int", @@ -96,9 +81,7 @@ "dalleLocation": { "type": "string", "allowedValues": [ - "swedencentral", - "eastus", - "australiaeast" + "swedencentral" ], "metadata": { "description": "Location for the OpenAI DALL-E 3 instance resource group" @@ -145,10 +128,6 @@ "resourceGroupName": { "type": "string", "defaultValue": "" - }, - "disableLocalAuth": { - "type": "bool", - "defaultValue": false } }, "variables": { @@ -247,9 +226,6 @@ }, "location": { "value": "[parameters('location')]" - }, - "disableLocalAuth": { - "value": "[parameters('disableLocalAuth')]" } }, "template": { @@ -258,8 +234,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "10505282472173598922" + "version": "0.29.47.4906", + "templateHash": "18109441359842852578" } }, "parameters": { @@ -341,10 +317,6 @@ "type": "string", "defaultValue": "[resourceGroup().location]" }, - "disableLocalAuth": { - "type": "bool", - "defaultValue": false - }, "nextAuthHash": { "type": "securestring", "defaultValue": "[uniqueString(newGuid())]" @@ -352,13 +324,6 @@ "tags": { "type": "object", "defaultValue": {} - }, - "roleDefinitionName": { - "type": "string", - "defaultValue": "Azure Cosmos DB for NoSQL Data Plane Owner", - "metadata": { - "description": "Name of the role definition." - } } }, "variables": { @@ -370,8 +335,7 @@ "search_name": "[toLower(format('{0}search{1}', parameters('name'), parameters('resourceToken')))]", "webapp_name": "[toLower(format('{0}-webapp-{1}', parameters('name'), parameters('resourceToken')))]", "appservice_name": "[toLower(format('{0}-app-{1}', parameters('name'), parameters('resourceToken')))]", - "clean_name": "[replace(replace(parameters('name'), '-', ''), '_', '')]", - "storage_prefix": "[take(variables('clean_name'), 8)]", + "storage_prefix": "[take(parameters('name'), 8)]", "storage_name": "[toLower(format('{0}sto{1}', variables('storage_prefix'), parameters('resourceToken')))]", "kv_prefix": "[take(parameters('name'), 7)]", "keyVaultName": "[toLower(format('{0}-kv-{1}', variables('kv_prefix'), parameters('resourceToken')))]", @@ -404,15 +368,7 @@ }, "capacity": "[parameters('embeddingDeploymentCapacity')]" } - ], - "cosmosDbContributorRoleId": "5bd9cd88-fe45-4216-938b-f97437e15450", - "cosmosDbOperatorRoleId": "230815da-be43-4aae-9cb4-875f7bd000aa", - "cognitiveServicesContributorRoleId": "25fbc0a9-bd7c-42a3-aa1a-3b75d497ee68", - "cognitiveServicesUserRoleId": "a97b65f3-24c7-4388-baec-2e87135dc908", - "storageBlobDataContributorRoleId": "ba92f5b4-2d11-453d-a403-e96b0029c9fe", - "searchServiceContributorRoleId": "7ca78c08-252a-4471-8644-bb5ff32d4ba0", - "cognitiveServicesOpenAIContributorRoleId": "a001fd3d-188f-4b5d-821b-7da978bf7442", - "searchIndexDataContributorRoleId": "8ebe5a00-799e-43f5-93ac-243d3dce84a7" + ] }, "resources": [ { @@ -617,10 +573,6 @@ "ftpsState": "Disabled", "minTlsVersion": "1.2", "appSettings": [ - { - "name": "USE_MANAGED_IDENTITIES", - "value": "[parameters('disableLocalAuth')]" - }, { "name": "AZURE_KEY_VAULT_NAME", "value": "[variables('keyVaultName')]" @@ -631,7 +583,7 @@ }, { "name": "AZURE_OPENAI_API_KEY", - "value": "[if(parameters('disableLocalAuth'), '', format('@Microsoft.KeyVault(VaultName={0};SecretName={1})', variables('keyVaultName'), 'AZURE-OPENAI-API-KEY'))]" + "value": "[format('@Microsoft.KeyVault(VaultName={0};SecretName={1})', variables('keyVaultName'), 'AZURE-OPENAI-API-KEY')]" }, { "name": "AZURE_OPENAI_API_INSTANCE_NAME", @@ -804,7 +756,6 @@ "kind": "GlobalDocumentDB", "properties": { "databaseAccountOfferType": "Standard", - "disableLocalAuth": "[parameters('disableLocalAuth')]", "locations": [ { "locationName": "[parameters('location')]", @@ -874,8 +825,7 @@ "kind": "FormRecognizer", "properties": { "customSubDomainName": "[variables('form_recognizer_name')]", - "publicNetworkAccess": "Enabled", - "disableLocalAuth": "[parameters('disableLocalAuth')]" + "publicNetworkAccess": "Enabled" }, "sku": { "name": "[parameters('formRecognizerSkuName')]" @@ -890,8 +840,7 @@ "properties": { "partitionCount": 1, "publicNetworkAccess": "enabled", - "replicaCount": 1, - "disableLocalAuth": "[parameters('disableLocalAuth')]" + "replicaCount": 1 }, "sku": { "name": "[parameters('searchServiceSkuName')]" @@ -906,8 +855,7 @@ "kind": "OpenAI", "properties": { "customSubDomainName": "[variables('openai_name')]", - "publicNetworkAccess": "Enabled", - "disableLocalAuth": "[parameters('disableLocalAuth')]" + "publicNetworkAccess": "Enabled" }, "sku": { "name": "[parameters('openAiSkuName')]" @@ -924,7 +872,8 @@ "apiVersion": "2023-05-01", "name": "[format('{0}/{1}', variables('openai_name'), variables('llmDeployments')[copyIndex()].name)]", "properties": { - "model": "[variables('llmDeployments')[copyIndex()].model]" + "model": "[variables('llmDeployments')[copyIndex()].model]", + "raiPolicyName": "[if(contains(variables('llmDeployments')[copyIndex()], 'raiPolicyName'), variables('llmDeployments')[copyIndex()].raiPolicyName, null())]" }, "sku": "[if(contains(variables('llmDeployments')[copyIndex()], 'sku'), variables('llmDeployments')[copyIndex()].sku, createObject('name', 'Standard', 'capacity', variables('llmDeployments')[copyIndex()].capacity))]", "dependsOn": [ @@ -940,8 +889,7 @@ "kind": "OpenAI", "properties": { "customSubDomainName": "[variables('openai_dalle_name')]", - "publicNetworkAccess": "Enabled", - "disableLocalAuth": "[parameters('disableLocalAuth')]" + "publicNetworkAccess": "Enabled" }, "sku": { "name": "[parameters('openAiSkuName')]" @@ -969,169 +917,7 @@ "location": "[parameters('location')]", "tags": "[parameters('tags')]", "kind": "StorageV2", - "sku": "[parameters('storageServiceSku')]", - "properties": { - "allowSharedKeyAccess": "[not(parameters('disableLocalAuth'))]" - } - }, - { - "condition": "[parameters('disableLocalAuth')]", - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2020-04-01-preview", - "scope": "[format('Microsoft.DocumentDB/databaseAccounts/{0}', variables('cosmos_name'))]", - "name": "[guid(resourceId('Microsoft.DocumentDB/databaseAccounts', variables('cosmos_name')), variables('cosmosDbContributorRoleId'), 'role-assignment-cosmosDb')]", - "properties": { - "principalId": "[reference(resourceId('Microsoft.Web/sites', variables('webapp_name')), '2020-06-01', 'full').identity.principalId]", - "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('cosmosDbContributorRoleId'))]" - }, - "dependsOn": [ - "[resourceId('Microsoft.DocumentDB/databaseAccounts', variables('cosmos_name'))]", - "[resourceId('Microsoft.Web/sites', variables('webapp_name'))]" - ] - }, - { - "condition": "[parameters('disableLocalAuth')]", - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2020-04-01-preview", - "scope": "[format('Microsoft.DocumentDB/databaseAccounts/{0}', variables('cosmos_name'))]", - "name": "[guid(resourceId('Microsoft.DocumentDB/databaseAccounts', variables('cosmos_name')), variables('cosmosDbOperatorRoleId'), 'role-assignment-cosmosDb')]", - "properties": { - "principalId": "[reference(resourceId('Microsoft.Web/sites', variables('webapp_name')), '2020-06-01', 'full').identity.principalId]", - "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('cosmosDbOperatorRoleId'))]" - }, - "dependsOn": [ - "[resourceId('Microsoft.DocumentDB/databaseAccounts', variables('cosmos_name'))]", - "[resourceId('Microsoft.Web/sites', variables('webapp_name'))]" - ] - }, - { - "condition": "[parameters('disableLocalAuth')]", - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2020-04-01-preview", - "name": "[guid(resourceId('Microsoft.CognitiveServices/accounts', variables('openai_name')), variables('cognitiveServicesContributorRoleId'), 'role-assignment-cognitiveServices')]", - "properties": { - "principalId": "[reference(resourceId('Microsoft.Web/sites', variables('webapp_name')), '2020-06-01', 'full').identity.principalId]", - "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('cognitiveServicesContributorRoleId'))]" - }, - "dependsOn": [ - "[resourceId('Microsoft.CognitiveServices/accounts', variables('openai_name'))]", - "[resourceId('Microsoft.Web/sites', variables('webapp_name'))]" - ] - }, - { - "condition": "[parameters('disableLocalAuth')]", - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2020-04-01-preview", - "scope": "[format('Microsoft.CognitiveServices/accounts/{0}', variables('openai_name'))]", - "name": "[guid(resourceId('Microsoft.CognitiveServices/accounts', variables('openai_name')), variables('cognitiveServicesOpenAIContributorRoleId'), 'role-assignment-cognitiveServices')]", - "properties": { - "principalId": "[reference(resourceId('Microsoft.Web/sites', variables('webapp_name')), '2020-06-01', 'full').identity.principalId]", - "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('cognitiveServicesOpenAIContributorRoleId'))]" - }, - "dependsOn": [ - "[resourceId('Microsoft.CognitiveServices/accounts', variables('openai_name'))]", - "[resourceId('Microsoft.Web/sites', variables('webapp_name'))]" - ] - }, - { - "condition": "[parameters('disableLocalAuth')]", - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2020-04-01-preview", - "name": "[guid(resourceId('Microsoft.CognitiveServices/accounts', variables('form_recognizer_name')), variables('cognitiveServicesUserRoleId'), 'role-assignment-cognitiveServices')]", - "properties": { - "principalId": "[reference(resourceId('Microsoft.Web/sites', variables('webapp_name')), '2020-06-01', 'full').identity.principalId]", - "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('cognitiveServicesUserRoleId'))]" - }, - "dependsOn": [ - "[resourceId('Microsoft.CognitiveServices/accounts', variables('form_recognizer_name'))]", - "[resourceId('Microsoft.Web/sites', variables('webapp_name'))]" - ] - }, - { - "condition": "[parameters('disableLocalAuth')]", - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2020-04-01-preview", - "scope": "[format('Microsoft.Storage/storageAccounts/{0}', variables('storage_name'))]", - "name": "[guid(resourceId('Microsoft.Storage/storageAccounts', variables('storage_name')), variables('storageBlobDataContributorRoleId'), 'role-assignment-storage')]", - "properties": { - "principalId": "[reference(resourceId('Microsoft.Web/sites', variables('webapp_name')), '2020-06-01', 'full').identity.principalId]", - "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('storageBlobDataContributorRoleId'))]" - }, - "dependsOn": [ - "[resourceId('Microsoft.Storage/storageAccounts', variables('storage_name'))]", - "[resourceId('Microsoft.Web/sites', variables('webapp_name'))]" - ] - }, - { - "condition": "[parameters('disableLocalAuth')]", - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2020-04-01-preview", - "scope": "[format('Microsoft.Search/searchServices/{0}', variables('search_name'))]", - "name": "[guid(resourceId('Microsoft.Search/searchServices', variables('search_name')), variables('searchServiceContributorRoleId'), 'role-assignment-searchService')]", - "properties": { - "principalId": "[reference(resourceId('Microsoft.Web/sites', variables('webapp_name')), '2020-06-01', 'full').identity.principalId]", - "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('searchServiceContributorRoleId'))]" - }, - "dependsOn": [ - "[resourceId('Microsoft.Search/searchServices', variables('search_name'))]", - "[resourceId('Microsoft.Web/sites', variables('webapp_name'))]" - ] - }, - { - "condition": "[parameters('disableLocalAuth')]", - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2020-04-01-preview", - "scope": "[format('Microsoft.Search/searchServices/{0}', variables('search_name'))]", - "name": "[guid(resourceId('Microsoft.Search/searchServices', variables('search_name')), variables('searchIndexDataContributorRoleId'), 'role-assignment-searchService')]", - "properties": { - "principalId": "[reference(resourceId('Microsoft.Web/sites', variables('webapp_name')), '2020-06-01', 'full').identity.principalId]", - "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('searchIndexDataContributorRoleId'))]" - }, - "dependsOn": [ - "[resourceId('Microsoft.Search/searchServices', variables('search_name'))]", - "[resourceId('Microsoft.Web/sites', variables('webapp_name'))]" - ] - }, - { - "condition": "[parameters('disableLocalAuth')]", - "type": "Microsoft.DocumentDB/databaseAccounts/sqlRoleDefinitions", - "apiVersion": "2024-05-15", - "name": "[format('{0}/{1}', variables('cosmos_name'), guid(resourceId('Microsoft.DocumentDB/databaseAccounts', variables('cosmos_name')), parameters('roleDefinitionName')))]", - "properties": { - "roleName": "[parameters('roleDefinitionName')]", - "type": "CustomRole", - "assignableScopes": [ - "[resourceId('Microsoft.DocumentDB/databaseAccounts', variables('cosmos_name'))]" - ], - "permissions": [ - { - "dataActions": [ - "Microsoft.DocumentDB/databaseAccounts/readMetadata", - "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/*", - "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/items/*" - ] - } - ] - }, - "dependsOn": [ - "[resourceId('Microsoft.DocumentDB/databaseAccounts', variables('cosmos_name'))]" - ] - }, - { - "condition": "[parameters('disableLocalAuth')]", - "type": "Microsoft.DocumentDB/databaseAccounts/sqlRoleAssignments", - "apiVersion": "2024-05-15", - "name": "[format('{0}/{1}', variables('cosmos_name'), guid(resourceId('Microsoft.DocumentDB/databaseAccounts/sqlRoleDefinitions', variables('cosmos_name'), guid(resourceId('Microsoft.DocumentDB/databaseAccounts', variables('cosmos_name')), parameters('roleDefinitionName'))), variables('webapp_name'), resourceId('Microsoft.DocumentDB/databaseAccounts', variables('cosmos_name'))))]", - "properties": { - "principalId": "[reference(resourceId('Microsoft.Web/sites', variables('webapp_name')), '2020-06-01', 'full').identity.principalId]", - "roleDefinitionId": "[resourceId('Microsoft.DocumentDB/databaseAccounts/sqlRoleDefinitions', variables('cosmos_name'), guid(resourceId('Microsoft.DocumentDB/databaseAccounts', variables('cosmos_name')), parameters('roleDefinitionName')))]", - "scope": "[resourceId('Microsoft.DocumentDB/databaseAccounts', variables('cosmos_name'))]" - }, - "dependsOn": [ - "[resourceId('Microsoft.DocumentDB/databaseAccounts', variables('cosmos_name'))]", - "[resourceId('Microsoft.DocumentDB/databaseAccounts/sqlRoleDefinitions', variables('cosmos_name'), guid(resourceId('Microsoft.DocumentDB/databaseAccounts', variables('cosmos_name')), parameters('roleDefinitionName')))]", - "[resourceId('Microsoft.Web/sites', variables('webapp_name'))]" - ] + "sku": "[parameters('storageServiceSku')]" } ], "outputs": { diff --git a/infra/resources.bicep b/infra/resources.bicep index e30702914..48be71c66 100644 --- a/infra/resources.bicep +++ b/infra/resources.bicep @@ -31,8 +31,6 @@ param storageServiceImageContainerName string param location string = resourceGroup().location -param disableLocalAuth bool= false - @secure() param nextAuthHash string = uniqueString(newGuid()) @@ -120,11 +118,6 @@ resource webApp 'Microsoft.Web/sites@2020-06-01' = { ftpsState: 'Disabled' minTlsVersion: '1.2' appSettings: [ - { - name: 'USE_MANAGED_IDENTITIES' - value: disableLocalAuth - } - { name: 'AZURE_KEY_VAULT_NAME' value: keyVaultName @@ -135,7 +128,7 @@ resource webApp 'Microsoft.Web/sites@2020-06-01' = { } { name: 'AZURE_OPENAI_API_KEY' - value: disableLocalAuth ? '' :'@Microsoft.KeyVault(VaultName=${kv.name};SecretName=${kv::AZURE_OPENAI_API_KEY.name})' + value: '@Microsoft.KeyVault(VaultName=${kv.name};SecretName=${kv::AZURE_OPENAI_API_KEY.name})' } { name: 'AZURE_OPENAI_API_INSTANCE_NAME' @@ -261,7 +254,7 @@ resource kvFunctionAppPermissions 'Microsoft.Authorization/roleAssignments@2020- name: guid(kv.id, webApp.name, keyVaultSecretsOfficerRole) scope: kv properties: { - principalId: targetUserPrincipal + principalId: webApp.identity.principalId principalType: 'ServicePrincipal' roleDefinitionId: keyVaultSecretsOfficerRole } @@ -354,7 +347,6 @@ resource cosmosDbAccount 'Microsoft.DocumentDB/databaseAccounts@2023-04-15' = { kind: 'GlobalDocumentDB' properties: { databaseAccountOfferType: 'Standard' - disableLocalAuth: disableLocalAuth locations: [ { locationName: location @@ -415,7 +407,6 @@ resource formRecognizer 'Microsoft.CognitiveServices/accounts@2023-05-01' = { properties: { customSubDomainName: form_recognizer_name publicNetworkAccess: 'Enabled' - disableLocalAuth: disableLocalAuth } sku: { name: formRecognizerSkuName @@ -430,7 +421,6 @@ resource searchService 'Microsoft.Search/searchServices@2022-09-01' = { partitionCount: 1 publicNetworkAccess: 'enabled' replicaCount: 1 - disableLocalAuth: disableLocalAuth } sku: { name: searchServiceSkuName @@ -445,7 +435,6 @@ resource azureopenai 'Microsoft.CognitiveServices/accounts@2023-05-01' = { properties: { customSubDomainName: openai_name publicNetworkAccess: 'Enabled' - disableLocalAuth: disableLocalAuth } sku: { name: openAiSkuName @@ -458,7 +447,7 @@ resource llmdeployment 'Microsoft.CognitiveServices/accounts/deployments@2023-05 name: deployment.name properties: { model: deployment.model - /*raiPolicyName: contains(deployment, 'raiPolicyName') ? deployment.raiPolicyName : null*/ + raiPolicyName: contains(deployment, 'raiPolicyName') ? deployment.raiPolicyName : null } sku: contains(deployment, 'sku') ? deployment.sku : { name: 'Standard' @@ -474,7 +463,6 @@ resource azureopenaidalle 'Microsoft.CognitiveServices/accounts@2023-05-01' = { properties: { customSubDomainName: openai_dalle_name publicNetworkAccess: 'Enabled' - disableLocalAuth: disableLocalAuth } sku: { name: openAiSkuName @@ -505,7 +493,6 @@ resource speechService 'Microsoft.CognitiveServices/accounts@2023-05-01' = { properties: { customSubDomainName: speech_service_name publicNetworkAccess: 'Enabled' - /* TODO: disableLocalAuth: disableLocalAuth*/ } sku: { name: speechServiceSkuName @@ -519,9 +506,6 @@ resource storage 'Microsoft.Storage/storageAccounts@2022-05-01' = { tags: tags kind: 'StorageV2' sku: storageServiceSku - properties:{ - allowSharedKeyAccess: !disableLocalAuth - } resource blobServices 'blobServices' = { name: 'default' @@ -534,132 +518,4 @@ resource storage 'Microsoft.Storage/storageAccounts@2022-05-01' = { } } - -//RBAC Roles for managed identity authentication - -var cosmosDbContributorRoleId = '5bd9cd88-fe45-4216-938b-f97437e15450' // Replace with actual role ID for Cosmos DB. -var cosmosDbOperatorRoleId= '230815da-be43-4aae-9cb4-875f7bd000aa' -var cognitiveServicesContributorRoleId = '25fbc0a9-bd7c-42a3-aa1a-3b75d497ee68' // Replace with actual role ID for Cognitive Services. -var cognitiveServicesUserRoleId='a97b65f3-24c7-4388-baec-2e87135dc908' -var storageBlobDataContributorRoleId = 'ba92f5b4-2d11-453d-a403-e96b0029c9fe' // Replace with actual role ID for Blob Data Contributor. -var searchServiceContributorRoleId = '7ca78c08-252a-4471-8644-bb5ff32d4ba0' // Replace with actual role ID for Azure Search. -var cognitiveServicesOpenAIContributorRoleId='a001fd3d-188f-4b5d-821b-7da978bf7442' -var searchIndexDataContributorRoleId='8ebe5a00-799e-43f5-93ac-243d3dce84a7' - -var targetUserPrincipal = webApp.identity.principalId -// These are only deployed if local authentication has been disabled in the parameters - -resource cosmosDbRoleAssignment 'Microsoft.Authorization/roleAssignments@2020-04-01-preview' = if (disableLocalAuth) { - name: guid(cosmosDbAccount.id, cosmosDbContributorRoleId, 'role-assignment-cosmosDb') - scope: cosmosDbAccount - properties: { - principalId: targetUserPrincipal - roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', cosmosDbContributorRoleId) - } -} - - -resource cosmosDbRoleAssignmentOpperator 'Microsoft.Authorization/roleAssignments@2020-04-01-preview' = if (disableLocalAuth) { - name: guid(cosmosDbAccount.id, cosmosDbOperatorRoleId, 'role-assignment-cosmosDb') - scope: cosmosDbAccount - properties: { - principalId: targetUserPrincipal - roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', cosmosDbOperatorRoleId) - } -} - -resource cognitiveServicesRoleAssignment 'Microsoft.Authorization/roleAssignments@2020-04-01-preview' = if (disableLocalAuth) { - name: guid(azureopenai.id, cognitiveServicesContributorRoleId, 'role-assignment-cognitiveServices') - scope: resourceGroup() - properties: { - principalId: targetUserPrincipal - roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', cognitiveServicesContributorRoleId) - } -} - - -resource cognitivbeServicesOpenAIcONTRIBUTORRoleAssignment 'Microsoft.Authorization/roleAssignments@2020-04-01-preview' = if (disableLocalAuth) { - name: guid(azureopenai.id, cognitiveServicesOpenAIContributorRoleId, 'role-assignment-cognitiveServices') - scope: azureopenai - properties: { - principalId: targetUserPrincipal - roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', cognitiveServicesOpenAIContributorRoleId) - } -} - -resource cognitiveServicesUserRoleAssignment 'Microsoft.Authorization/roleAssignments@2020-04-01-preview' = if (disableLocalAuth) { - name: guid(formRecognizer.id, cognitiveServicesUserRoleId, 'role-assignment-cognitiveServices') - scope: resourceGroup() - properties: { - principalId: targetUserPrincipal - roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', cognitiveServicesUserRoleId) - } -} - - - -resource storageBlobDataContributorRole 'Microsoft.Authorization/roleAssignments@2020-04-01-preview' = if (disableLocalAuth) { - name: guid(storage.id, storageBlobDataContributorRoleId, 'role-assignment-storage') - scope: storage - properties: { - principalId: targetUserPrincipal - roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', storageBlobDataContributorRoleId) - } -} - -resource searchServiceContributorRoleAssignment 'Microsoft.Authorization/roleAssignments@2020-04-01-preview' = if (disableLocalAuth) { - name: guid(searchService.id, searchServiceContributorRoleId, 'role-assignment-searchService') - scope: searchService - properties: { - principalId: targetUserPrincipal - roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', searchServiceContributorRoleId) - } -} -resource searchServiceIndexDataContributorRoleAssignment 'Microsoft.Authorization/roleAssignments@2020-04-01-preview' = if (disableLocalAuth) { - name: guid(searchService.id, searchIndexDataContributorRoleId, 'role-assignment-searchService') - scope: searchService - properties: { - principalId: targetUserPrincipal - roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', searchIndexDataContributorRoleId) - } -} -//Special case for cosmosdb - - -@description('Name of the role definition.') -param roleDefinitionName string = 'Azure Cosmos DB for NoSQL Data Plane Owner' - - -resource definition 'Microsoft.DocumentDB/databaseAccounts/sqlRoleDefinitions@2024-05-15'= if (disableLocalAuth) { - name: guid(cosmosDbAccount.id, roleDefinitionName) - parent: cosmosDbAccount - properties: { - roleName: roleDefinitionName - type: 'CustomRole' - assignableScopes: [ - cosmosDbAccount.id - ] - permissions: [ - { - dataActions: [ - 'Microsoft.DocumentDB/databaseAccounts/readMetadata' - 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/*' - 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/items/*' - ] - } - ] - } -} - -resource assignment 'Microsoft.DocumentDB/databaseAccounts/sqlRoleAssignments@2024-05-15'= if (disableLocalAuth) { - name: guid(definition.id, webApp.name, cosmosDbAccount.id) - parent: cosmosDbAccount - properties: { - principalId: targetUserPrincipal - roleDefinitionId: definition.id - scope: cosmosDbAccount.id - } - -} - output url string = 'https://${webApp.properties.defaultHostName}' From 05c9e50116e3b291c51781944f7384bb65f2145f Mon Sep 17 00:00:00 2001 From: Freddy Ayala Date: Wed, 16 Oct 2024 16:28:54 +0200 Subject: [PATCH 11/12] removed docs --- docs/10.managed-identities.md | 52 ----------------------------------- 1 file changed, 52 deletions(-) delete mode 100644 docs/10.managed-identities.md diff --git a/docs/10.managed-identities.md b/docs/10.managed-identities.md deleted file mode 100644 index ba604c0c1..000000000 --- a/docs/10.managed-identities.md +++ /dev/null @@ -1,52 +0,0 @@ -# Using Managed Identities for Azure Chat Solution Accelerator - -### Introduction - -The Azure Chat Solution Accelerator powered by Azure OpenAI Service allows organizations to deploy a private chat tenant with enhanced security and control over their data. One of the new features is the support for Managed Identities, adding a layer of security by eliminating the need for managing service principals and secrets manually. - -### Security Advantages of Managed Identities - -**Managed Identities** for Azure resources provide the following benefits: - -1. **Improved Security**: - - **No Secret Management**: Eliminates the need to manually store and manage credentials or keys. - - **Automatic Rotation**: Managed Identities’ credentials are rotated automatically, eliminating potential security risk from non-rotated credentials. - - **Scope Limited Access**: Access to Azure resources can be fine-grained, allowing least-privilege access policies. - -2. **Simplified Management**: - - **Platform Managed**: The Azure platform handles identity creation and lifecycle management. - - **Simplified Resource Access**: Applications can request tokens to access resources without handling secrets. - -### List of Services Using Managed Identities - -The following services within the Azure Chat Solution Accelerator use Managed Identities for authentication: - -1. **Azure OpenAI Service** -2. **Azure Cosmos DB** -3. **Azure Cognitive Services (e.g., Document Intelligence, Azure OpenAI Dalle)** -4. **Azure Search Service** -5. **Azure Storage Account** - -> **Note:** Currently, due to compatibility issues, the Speech Service does not utilize Managed Identities. There is no available documentation for using Entra ID authentication with the Speech Service, making it a `TODO` item. - -### Preferred Production Deployment - -Using Managed Identities is preferred for production deployments due to: - -1. **Enhanced Security**: Eliminates risks associated with secret management such as accidental exposure or non-rotation of credentials. -2. **Compliance and Governance**: Managed Identities integrate with Azure's role-based access control (RBAC), facilitating easier audits and compliance management. -3. **Operational Efficiency**: Reduces the operational overhead of managing secrets, while also providing a more straightforward implementation. - -### Deploy to Azure with Managed Identities - -To deploy the application to Azure App Service with Managed Identities, follow the standard deployment instructions available in the [Deploy to Azure - GitHub Actions](https://github.com/microsoft/azurechat) section of the repository. Ensure to: - -1. **Update the Parameter**: - - Set the parameter `disableLocalAuth` to `true` to use Managed Identities. -1. **Remove Parameter**: -After deployment remove the value in AZURE_OPENAI_API_KEY (if there is any) in the Environment Variables of the WebApp, the openai npm package requires this to be empty if using EntraID Authentication. - - -### Conclusion - -By leveraging Managed Identities, you enhance the security posture of your Azure Chat deployment while simplifying secret management and access control. This guide outlines the security advantages and highlights the necessary parameter changes to ensure a secure and efficient production setup. For more details, review the complete code and configurations available in the repository's `infra` directory. From 2c8e44e9dc79aa332ac3189c785f8ac2a614f7de Mon Sep 17 00:00:00 2001 From: Freddy Ayala Date: Wed, 16 Oct 2024 16:30:38 +0200 Subject: [PATCH 12/12] removed modified services --- src/features/common/services/ai-search.ts | 51 +++---- src/features/common/services/azure-storage.ts | 27 ++-- src/features/common/services/cosmos.ts | 30 +---- .../common/services/document-intelligence.ts | 26 ++-- src/features/common/services/openai.ts | 127 +++++++----------- 5 files changed, 101 insertions(+), 160 deletions(-) diff --git a/src/features/common/services/ai-search.ts b/src/features/common/services/ai-search.ts index 0dc2a175b..86b669e04 100644 --- a/src/features/common/services/ai-search.ts +++ b/src/features/common/services/ai-search.ts @@ -4,56 +4,57 @@ import { SearchIndexClient, SearchIndexerClient, } from "@azure/search-documents"; -import { DefaultAzureCredential } from "@azure/identity"; -const USE_MANAGED_IDENTITIES = process.env.USE_MANAGED_IDENTITIES === "true"; -const endpointSuffix = process.env.AZURE_SEARCH_ENDPOINT_SUFFIX || "search.windows.net"; -const apiKey = process.env.AZURE_SEARCH_API_KEY; -const searchName = process.env.AZURE_SEARCH_NAME; -const indexName = process.env.AZURE_SEARCH_INDEX_NAME; -const endpoint = `https://${searchName}.${endpointSuffix}`; - - -export const GetCredential = () => { - return USE_MANAGED_IDENTITIES - ? new DefaultAzureCredential() - : new AzureKeyCredential(apiKey); -} +export const AzureAISearchCredentials = () => { + const apiKey = process.env.AZURE_SEARCH_API_KEY; + const searchName = process.env.AZURE_SEARCH_NAME; + const indexName = process.env.AZURE_SEARCH_INDEX_NAME; + + if (!apiKey || !searchName || !indexName) { + throw new Error( + "One or more Azure AI Search environment variables are not set" + ); + } + const endpointSuffix = process.env.AZURE_SEARCH_ENDPOINT_SUFFIX || "search.windows.net"; + + const endpoint = `https://${searchName}.${endpointSuffix}`; + return { + apiKey, + endpoint, + indexName, + }; +}; export const AzureAISearchInstance = () => { - const credential = GetCredential(); + const { apiKey, endpoint, indexName } = AzureAISearchCredentials(); const searchClient = new SearchClient( endpoint, indexName, - credential + new AzureKeyCredential(apiKey) ); return searchClient; - - }; export const AzureAISearchIndexClientInstance = () => { - - const credential = GetCredential(); + const { apiKey, endpoint } = AzureAISearchCredentials(); const searchClient = new SearchIndexClient( endpoint, - credential + new AzureKeyCredential(apiKey) ); return searchClient; }; export const AzureAISearchIndexerClientInstance = () => { - - const credential = GetCredential(); + const { apiKey, endpoint } = AzureAISearchCredentials(); const client = new SearchIndexerClient( endpoint, - credential + new AzureKeyCredential(apiKey) ); return client; -}; \ No newline at end of file +}; diff --git a/src/features/common/services/azure-storage.ts b/src/features/common/services/azure-storage.ts index 13f5dc962..42e0eaffe 100644 --- a/src/features/common/services/azure-storage.ts +++ b/src/features/common/services/azure-storage.ts @@ -1,28 +1,22 @@ import { BlobServiceClient, RestError } from "@azure/storage-blob"; import { ServerActionResponse } from "../server-action-response"; -import { DefaultAzureCredential } from "@azure/identity"; // initialize the blobServiceClient -const USE_MANAGED_IDENTITIES = process.env.USE_MANAGED_IDENTITIES === "true"; - const InitBlobServiceClient = () => { - const accountName = process.env.AZURE_STORAGE_ACCOUNT_NAME; - const endpointSuffix = process.env.AZURE_STORAGE_ENDPOINT_SUFFIX || "core.windows.net"; - const endpoint = `https://${accountName}.blob.${endpointSuffix}`; - - if (USE_MANAGED_IDENTITIES) { - return new BlobServiceClient(endpoint, new DefaultAzureCredential()); - } + const acc = process.env.AZURE_STORAGE_ACCOUNT_NAME; + const key = process.env.AZURE_STORAGE_ACCOUNT_KEY; - const accountKey = process.env.AZURE_STORAGE_ACCOUNT_KEY; - if (!accountName || !accountKey) { + if (!acc || !key) throw new Error( "Azure Storage Account not configured correctly, check environment variables." ); - } + const endpointSuffix = process.env.AZURE_STORAGE_ENDPOINT_SUFFIX || "core.windows.net"; + + const connectionString = `DefaultEndpointsProtocol=https;AccountName=${acc};AccountKey=${key};EndpointSuffix=${endpointSuffix}`; - const connectionString = `DefaultEndpointsProtocol=https;AccountName=${accountName};AccountKey=${accountKey};EndpointSuffix=${endpointSuffix}`; - return BlobServiceClient.fromConnectionString(connectionString); + const blobServiceClient = + BlobServiceClient.fromConnectionString(connectionString); + return blobServiceClient; }; export const UploadBlob = async ( @@ -85,8 +79,7 @@ export const GetBlob = async ( }; } catch (error) { if (error instanceof RestError) { - const restError = error as RestError; - if (restError.statusCode === 404) { + if (error.statusCode === 404) { return { status: "NOT_FOUND", errors: [ diff --git a/src/features/common/services/cosmos.ts b/src/features/common/services/cosmos.ts index 2c6d59674..82f4933ea 100644 --- a/src/features/common/services/cosmos.ts +++ b/src/features/common/services/cosmos.ts @@ -1,38 +1,22 @@ import { CosmosClient } from "@azure/cosmos"; -import { DefaultAzureCredential } from "@azure/identity"; -// Configure Cosmos DB details +// Read Cosmos DB_NAME and CONTAINER_NAME from .env const DB_NAME = process.env.AZURE_COSMOSDB_DB_NAME || "chat"; const CONTAINER_NAME = process.env.AZURE_COSMOSDB_CONTAINER_NAME || "history"; -const CONFIG_CONTAINER_NAME = process.env.AZURE_COSMOSDB_CONFIG_CONTAINER_NAME || "config"; -const USE_MANAGED_IDENTITIES = process.env.USE_MANAGED_IDENTITIES === "true"; - -const getCosmosCredential = () => { - if (USE_MANAGED_IDENTITIES) { - return new DefaultAzureCredential(); - } - const key = process.env.AZURE_COSMOSDB_KEY; - if (!key) { - throw new Error("Azure Cosmos DB key is not provided in environment variables."); - } - return key; -}; +const CONFIG_CONTAINER_NAME = + process.env.AZURE_COSMOSDB_CONFIG_CONTAINER_NAME || "config"; export const CosmosInstance = () => { const endpoint = process.env.AZURE_COSMOSDB_URI; + const key = process.env.AZURE_COSMOSDB_KEY; - if (!endpoint) { + if (!endpoint || !key) { throw new Error( - "Azure Cosmos DB endpoint is not configured. Please configure it in the .env file." + "Azure Cosmos DB is not configured. Please configure it in the .env file." ); } - const credential = getCosmosCredential(); - if (credential instanceof DefaultAzureCredential) { - return new CosmosClient({ endpoint, aadCredentials: credential }); - } else { - return new CosmosClient({ endpoint, key: credential }); - } + return new CosmosClient({ endpoint, key }); }; export const ConfigContainer = () => { diff --git a/src/features/common/services/document-intelligence.ts b/src/features/common/services/document-intelligence.ts index 99eb1de16..163f3e1a4 100644 --- a/src/features/common/services/document-intelligence.ts +++ b/src/features/common/services/document-intelligence.ts @@ -2,31 +2,21 @@ import { AzureKeyCredential, DocumentAnalysisClient, } from "@azure/ai-form-recognizer"; -import { DefaultAzureCredential } from "@azure/identity"; - - -const USE_MANAGED_IDENTITIES = process.env.USE_MANAGED_IDENTITIES === "true"; export const DocumentIntelligenceInstance = () => { const endpoint = process.env.AZURE_DOCUMENT_INTELLIGENCE_ENDPOINT; - - if (!endpoint) { - throw new Error( - "Document Intelligence environment variable for the endpoint is not set" - ); - } - - const credential = USE_MANAGED_IDENTITIES - ? new DefaultAzureCredential() - : new AzureKeyCredential(process.env.AZURE_DOCUMENT_INTELLIGENCE_KEY); + const key = process.env.AZURE_DOCUMENT_INTELLIGENCE_KEY; - if (!USE_MANAGED_IDENTITIES && !process.env.AZURE_DOCUMENT_INTELLIGENCE_KEY) { + if (!endpoint || !key) { throw new Error( - "Document Intelligence environment variable for the key is not set" + "One or more Document Intelligence environment variables are not set" ); } - const client = new DocumentAnalysisClient(endpoint, credential); + const client = new DocumentAnalysisClient( + endpoint, + new AzureKeyCredential(key) + ); return client; -}; \ No newline at end of file +}; diff --git a/src/features/common/services/openai.ts b/src/features/common/services/openai.ts index b9962858d..0e44a4523 100644 --- a/src/features/common/services/openai.ts +++ b/src/features/common/services/openai.ts @@ -1,87 +1,60 @@ import { OpenAI } from "openai"; -import { DefaultAzureCredential, getBearerTokenProvider } from "@azure/identity"; -import { AzureOpenAI } from "openai"; -const USE_MANAGED_IDENTITIES = process.env.USE_MANAGED_IDENTITIES === "true"; - -export const OpenAIInstance = () => { +export const OpenAIInstance = () => { const endpointSuffix = process.env.AZURE_OPENAI_API_ENDPOINT_SUFFIX || "openai.azure.com"; - let token = process.env.AZURE_OPENAI_API_KEY; - if (USE_MANAGED_IDENTITIES) { - const credential = new DefaultAzureCredential(); - const scope = "https://cognitiveservices.azure.com/.default"; - const azureADTokenProvider = getBearerTokenProvider(credential, scope); - const deployment = process.env.AZURE_OPENAI_API_DEPLOYMENT_NAME; - const apiVersion = process.env.AZURE_OPENAI_API_VERSION; - const client = new AzureOpenAI({ - azureADTokenProvider, - deployment, - apiVersion, - baseURL: `https://${process.env.AZURE_OPENAI_API_INSTANCE_NAME}.${endpointSuffix}/openai/deployments/${process.env.AZURE_OPENAI_API_DEPLOYMENT_NAME}` - }); - return client; - } else { - const openai = new OpenAI({ - apiKey: token, - baseURL: `https://${process.env.AZURE_OPENAI_API_INSTANCE_NAME}.${endpointSuffix}/openai/deployments/${process.env.AZURE_OPENAI_API_DEPLOYMENT_NAME}`, - defaultQuery: { "api-version": process.env.AZURE_OPENAI_API_VERSION }, - defaultHeaders: { "api-key": process.env.AZURE_OPENAI_API_KEY }, - }); - return openai; - } + const openai = new OpenAI({ + apiKey: process.env.AZURE_OPENAI_API_KEY, + baseURL: `https://${process.env.AZURE_OPENAI_API_INSTANCE_NAME}.${endpointSuffix}/openai/deployments/${process.env.AZURE_OPENAI_API_DEPLOYMENT_NAME}`, + defaultQuery: { "api-version": process.env.AZURE_OPENAI_API_VERSION }, + defaultHeaders: { "api-key": process.env.AZURE_OPENAI_API_KEY }, + }); + return openai; }; -export const OpenAIEmbeddingInstance = () => { - const endpointSuffix = process.env.AZURE_OPENAI_API_ENDPOINT_SUFFIX || "openai.azure.com"; - let token = process.env.AZURE_OPENAI_API_KEY; - if (USE_MANAGED_IDENTITIES) { - const credential = new DefaultAzureCredential(); - const scope = "https://cognitiveservices.azure.com/.default"; - const azureADTokenProvider = getBearerTokenProvider(credential, scope); - const deployment = process.env.AZURE_OPENAI_API_EMBEDDINGS_DEPLOYMENT_NAME; - const apiVersion = process.env.AZURE_OPENAI_API_VERSION; - const client = new AzureOpenAI({ - azureADTokenProvider, - deployment, - apiVersion, - baseURL: `https://${process.env.AZURE_OPENAI_API_INSTANCE_NAME}.${endpointSuffix}/openai/deployments/${process.env.AZURE_OPENAI_API_EMBEDDINGS_DEPLOYMENT_NAME}` - }); - return client; - } else { - const openai = new OpenAI({ - apiKey: token, - baseURL: `https://${process.env.AZURE_OPENAI_API_INSTANCE_NAME}.${endpointSuffix}/openai/deployments/${process.env.AZURE_OPENAI_API_EMBEDDINGS_DEPLOYMENT_NAME}`, - defaultQuery: { "api-version": process.env.AZURE_OPENAI_API_VERSION }, - defaultHeaders: { "api-key": token }, - }); - return openai; +export const OpenAIEmbeddingInstance = () => { + if ( + !process.env.AZURE_OPENAI_API_KEY || + !process.env.AZURE_OPENAI_API_EMBEDDINGS_DEPLOYMENT_NAME || + !process.env.AZURE_OPENAI_API_INSTANCE_NAME + ) { + throw new Error( + "Azure OpenAI Embeddings endpoint config is not set, check environment variables." + ); } + const endpointSuffix = process.env.AZURE_OPENAI_API_ENDPOINT_SUFFIX || "openai.azure.com"; + + const openai = new OpenAI({ + apiKey: process.env.AZURE_OPENAI_API_KEY, + baseURL: `https://${process.env.AZURE_OPENAI_API_INSTANCE_NAME}.${endpointSuffix}/openai/deployments/${process.env.AZURE_OPENAI_API_EMBEDDINGS_DEPLOYMENT_NAME}`, + defaultQuery: { "api-version": process.env.AZURE_OPENAI_API_VERSION }, + defaultHeaders: { "api-key": process.env.AZURE_OPENAI_API_KEY }, + }); + return openai; }; -// A new instance definition for DALL-E image generation -export const OpenAIDALLEInstance = () => { - const endpointSuffix = process.env.AZURE_OPENAI_API_ENDPOINT_SUFFIX || "openai.azure.com"; - let token = process.env.AZURE_OPENAI_DALLE_API_KEY; - if (USE_MANAGED_IDENTITIES) { - const credential = new DefaultAzureCredential(); - const scope = "https://cognitiveservices.azure.com/.default"; - const azureADTokenProvider = getBearerTokenProvider(credential, scope); - const deployment = process.env.AZURE_OPENAI_DALLE_API_DEPLOYMENT_NAME; - const apiVersion = process.env.AZURE_OPENAI_DALLE_API_VERSION || "2023-12-01-preview"; - const client = new AzureOpenAI({ - azureADTokenProvider, - deployment, - apiVersion, - baseURL: `https://${process.env.AZURE_OPENAI_DALLE_API_INSTANCE_NAME}.${endpointSuffix}/openai/deployments/${process.env.AZURE_OPENAI_DALLE_API_DEPLOYMENT_NAME}` - }); - return client; - } else { - const openai = new OpenAI({ - apiKey: token, - baseURL: `https://${process.env.AZURE_OPENAI_DALLE_API_INSTANCE_NAME}.${endpointSuffix}/openai/deployments/${process.env.AZURE_OPENAI_DALLE_API_DEPLOYMENT_NAME}`, - defaultQuery: { "api-version": process.env.AZURE_OPENAI_DALLE_API_VERSION || "2023-12-01-preview" }, - defaultHeaders: { "api-key": token }, - }); - return openai; +// a new instance definition for DALL-E image generation +export const OpenAIDALLEInstance = () => { + if ( + !process.env.AZURE_OPENAI_DALLE_API_KEY || + !process.env.AZURE_OPENAI_DALLE_API_DEPLOYMENT_NAME || + !process.env.AZURE_OPENAI_DALLE_API_INSTANCE_NAME + ) { + throw new Error( + "Azure OpenAI DALLE endpoint config is not set, check environment variables." + ); } + const endpointSuffix = process.env.AZURE_OPENAI_API_ENDPOINT_SUFFIX || "openai.azure.com"; + + const openai = new OpenAI({ + apiKey: process.env.AZURE_OPENAI_DALLE_API_KEY, + baseURL: `https://${process.env.AZURE_OPENAI_DALLE_API_INSTANCE_NAME}.${endpointSuffix}/openai/deployments/${process.env.AZURE_OPENAI_DALLE_API_DEPLOYMENT_NAME}`, + defaultQuery: { + "api-version": + process.env.AZURE_OPENAI_DALLE_API_VERSION || "2023-12-01-preview", + }, + defaultHeaders: { + "api-key": process.env.AZURE_OPENAI_DALLE_API_KEY, + }, + }); + return openai; };