diff --git a/.github/workflows/deploy-app.yaml b/.github/workflows/deploy-app.yaml index 7585f28..90d55c3 100644 --- a/.github/workflows/deploy-app.yaml +++ b/.github/workflows/deploy-app.yaml @@ -54,6 +54,8 @@ jobs: - name: Build Frontend Application run: npm run build working-directory: ./frontend/app + env: + REACT_APP_FUNCTION_APP_URL: https://${{ env.ENVIRONMENT }}-alpinebot-func.azurewebsites.net - name: Deploy to Azure Web App uses: azure/webapps-deploy@v2 diff --git a/.github/workflows/deploy-function.yaml b/.github/workflows/deploy-function.yaml index 87d6fbe..e217c4e 100644 --- a/.github/workflows/deploy-function.yaml +++ b/.github/workflows/deploy-function.yaml @@ -56,6 +56,8 @@ jobs: with: app-name: ${{ env.ENVIRONMENT }}-alpinebot-func package: ./backend + scm-do-build-during-deployment: 'true' + enable-oryx-build: 'true' - name: Output deployment information run: | diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml index 53ae9f7..750a6bf 100644 --- a/.github/workflows/deploy.yaml +++ b/.github/workflows/deploy.yaml @@ -230,6 +230,8 @@ jobs: with: app-name: ${{ env.ENVIRONMENT }}-alpinebot-func package: ./backend + scm-do-build-during-deployment: 'true' + enable-oryx-build: 'true' - name: Output deployment information run: | diff --git a/.github/workflows/destroy-infra.yaml b/.github/workflows/destroy-infra.yaml index 3fe42b2..186a091 100644 --- a/.github/workflows/destroy-infra.yaml +++ b/.github/workflows/destroy-infra.yaml @@ -46,6 +46,7 @@ jobs: echo "TF_VAR_az_subscription_id=${{ secrets.AZURE_SUBSCRIPTION_ID }}" >> $GITHUB_ENV echo "TF_VAR_az_container_name=${{ env.ENVIRONMENT }}-bkd-alpinebot-co" >> $GITHUB_ENV echo "TF_VAR_sp_object_id=${{ secrets.AZURE_SP_OBJECT_ID }}" >> $GITHUB_ENV + echo "TF_VAR_az_openai_key_value=${{ secrets.AZURE_OPENAI_KEY }}" >> $GITHUB_ENV echo "TF_VAR_postgresql_admin_password=${{ secrets.POSTGRESQL_ADMIN_PASSWORD }}" >> $GITHUB_ENV echo "TF_VAR_postgresql_admin_username=${{ secrets.POSTGRESQL_ADMIN_USERNAME }}" >> $GITHUB_ENV echo "TF_VAR_google_client_id=${{ secrets.GOOGLE_CLIENT_ID }}" >> $GITHUB_ENV diff --git a/.github/workflows/destroy.yaml b/.github/workflows/destroy.yaml index e318e7b..25df441 100644 --- a/.github/workflows/destroy.yaml +++ b/.github/workflows/destroy.yaml @@ -105,6 +105,7 @@ jobs: echo "TF_VAR_az_subscription_id=${{ secrets.AZURE_SUBSCRIPTION_ID }}" >> $GITHUB_ENV echo "TF_VAR_az_container_name=${{ env.ENVIRONMENT }}-bkd-alpinebot-co" >> $GITHUB_ENV echo "TF_VAR_sp_object_id=${{ secrets.AZURE_SP_OBJECT_ID }}" >> $GITHUB_ENV + echo "TF_VAR_az_openai_key_value=${{ secrets.AZURE_OPENAI_KEY }}" >> $GITHUB_ENV echo "TF_VAR_postgresql_admin_password=${{ secrets.POSTGRESQL_ADMIN_PASSWORD }}" >> $GITHUB_ENV echo "TF_VAR_postgresql_admin_username=${{ secrets.POSTGRESQL_ADMIN_USERNAME }}" >> $GITHUB_ENV echo "TF_VAR_google_client_id=${{ secrets.GOOGLE_CLIENT_ID }}" >> $GITHUB_ENV diff --git a/backend/function_app.py b/backend/function_app.py index fa192a0..66769f7 100644 --- a/backend/function_app.py +++ b/backend/function_app.py @@ -22,7 +22,7 @@ def get_openai_client(): azure_endpoint=api_base ) -@app.route(route="chat", methods=["POST"], auth_level=func.AuthLevel.FUNCTION) +@app.route(route="chat", methods=["POST"], auth_level=func.AuthLevel.ANONYMOUS) def chat(req: func.HttpRequest) -> func.HttpResponse: """ HTTP trigger function for chatbot queries. diff --git a/frontend/app/.env.example b/frontend/app/.env.example new file mode 100644 index 0000000..2ed43d0 --- /dev/null +++ b/frontend/app/.env.example @@ -0,0 +1,4 @@ +# Azure Function App URL for chat API +# Format: https://-alpinebot-func.azurewebsites.net +# Example for dev: https://dev-alpinebot-func.azurewebsites.net +REACT_APP_FUNCTION_APP_URL= diff --git a/frontend/app/README.md b/frontend/app/README.md index 61b6217..4a21beb 100644 --- a/frontend/app/README.md +++ b/frontend/app/README.md @@ -82,6 +82,20 @@ npm start **Note**: When running locally, the Azure App Service authentication endpoints (`/.auth/me`, `/.auth/login/*`, `/.auth/logout`) will not work unless you configure a local development proxy or mock these endpoints. +### Environment Variables + +The application requires the following environment variable to connect to the backend API: + +- `REACT_APP_FUNCTION_APP_URL`: The URL of the Azure Function App (e.g., `https://dev-alpinebot-func.azurewebsites.net`) + +For local development, create a `.env.local` file (see `.env.example` for reference): + +```bash +REACT_APP_FUNCTION_APP_URL=https://dev-alpinebot-func.azurewebsites.net +``` + +This variable is automatically set during CI/CD deployment based on the environment. + ### Building for Production ```bash diff --git a/frontend/app/src/HomePage.js b/frontend/app/src/HomePage.js index 0913b0d..3e73817 100644 --- a/frontend/app/src/HomePage.js +++ b/frontend/app/src/HomePage.js @@ -30,7 +30,7 @@ const HomePage = ({ user, onLogout }) => { scrollToBottom(); }, [messages]); - const handleSend = (e) => { + const handleSend = async (e) => { e.preventDefault(); if (!inputValue.trim()) return; @@ -43,20 +43,62 @@ const HomePage = ({ user, onLogout }) => { }; setMessages((prev) => [...prev, userMessage]); + const currentMessage = inputValue; setInputValue(""); setIsTyping(true); - // Simulate bot response (placeholder for future API integration) - setTimeout(() => { + try { + // Get Function App URL from environment variable + const functionAppUrl = process.env.REACT_APP_FUNCTION_APP_URL || ""; + + if (!functionAppUrl) { + throw new Error("Function App URL not configured"); + } + + // Build conversation history for API + const conversationHistory = messages.map((msg) => ({ + role: msg.type === "user" ? "user" : "assistant", + content: msg.text, + })); + + // Call Azure Function App API + const response = await fetch(`${functionAppUrl}/api/chat`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + message: currentMessage, + conversation_history: conversationHistory, + }), + }); + + if (!response.ok) { + throw new Error(`API error: ${response.status} ${response.statusText}`); + } + + const data = await response.json(); + const botMessage = { id: Date.now() + 1, type: "bot", - text: "I'm a placeholder response. Integration with Azure OpenAI will be implemented in future tasks.", + text: data.response || "I apologize, but I couldn't generate a response.", timestamp: new Date(), }; setMessages((prev) => [...prev, botMessage]); + } catch (error) { + // Log error without sensitive details + console.error("Error calling chat API"); + const errorMessage = { + id: Date.now() + 1, + type: "bot", + text: "I apologize, but I'm having trouble connecting to the service. Please try again later.", + timestamp: new Date(), + }; + setMessages((prev) => [...prev, errorMessage]); + } finally { setIsTyping(false); - }, 1500); + } }; const handleVote = (messageId, vote) => { diff --git a/modules/function_app/main.tf b/modules/function_app/main.tf index b7e4e6f..b6464f1 100644 --- a/modules/function_app/main.tf +++ b/modules/function_app/main.tf @@ -33,6 +33,8 @@ resource "azurerm_linux_function_app" "function_app" { "FUNCTIONS_WORKER_RUNTIME" = "python" "AzureWebJobsFeatureFlags" = "EnableWorkerIndexing" "APPLICATIONINSIGHTS_CONNECTION_STRING" = var.app_insights_connection_string + "SCM_DO_BUILD_DURING_DEPLOYMENT" = "true" + "ENABLE_ORYX_BUILD" = "true" }, var.app_settings )