diff --git a/apps/customer-portal/backend/config.toml.local b/apps/customer-portal/backend/config.toml.local index be064f470e..49f22e2bf0 100644 --- a/apps/customer-portal/backend/config.toml.local +++ b/apps/customer-portal/backend/config.toml.local @@ -24,3 +24,11 @@ updatesServiceUrl = "" clientId = "" clientSecret = "" scopes = [""] + +[customer_portal.ai_chat_agent] +aiChatAgentBaseUrl = "" +[customer_portal.ai_chat_agent.clientCredentialsOauth2Config] + tokenUrl = "" + clientId = "" + clientSecret = "" + scopes = [""] diff --git a/apps/customer-portal/backend/modules/ai_chat_agent/ai_chat_agent.bal b/apps/customer-portal/backend/modules/ai_chat_agent/ai_chat_agent.bal new file mode 100644 index 0000000000..d0488b771f --- /dev/null +++ b/apps/customer-portal/backend/modules/ai_chat_agent/ai_chat_agent.bal @@ -0,0 +1,25 @@ +// Copyright (c) 2026 WSO2 LLC. (https://www.wso2.com). +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +# Create case classification for the given payload. +# +# + payload - Case classification payload +# + return - Case classification response or error +public isolated function createCaseClassification(CaseClassificationPayload payload) + returns CaseClassificationResponse|error { + + return aiChatAgentClient->/case_classification.post(payload); +} diff --git a/apps/customer-portal/backend/modules/ai_chat_agent/client.bal b/apps/customer-portal/backend/modules/ai_chat_agent/client.bal new file mode 100644 index 0000000000..13c44376a8 --- /dev/null +++ b/apps/customer-portal/backend/modules/ai_chat_agent/client.bal @@ -0,0 +1,37 @@ +// Copyright (c) 2026 WSO2 LLC. (https://www.wso2.com). +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +import ballerina/http; + +configurable string aiChatAgentBaseUrl = ?; +configurable ClientCredentialsOauth2Config clientCredentialsOauth2Config = ?; + +final http:Client aiChatAgentClient = check new (aiChatAgentBaseUrl, { + auth: { + ...clientCredentialsOauth2Config + }, + httpVersion: http:HTTP_1_1, + http1Settings: {keepAlive: http:KEEPALIVE_NEVER}, + timeout: 300.0, + retryConfig: { + count: 3, + interval: 2.0, + statusCodes: [ + http:STATUS_BAD_GATEWAY, + http:STATUS_SERVICE_UNAVAILABLE, + http:STATUS_GATEWAY_TIMEOUT + ] + } +}); diff --git a/apps/customer-portal/backend/modules/ai_chat_agent/types.bal b/apps/customer-portal/backend/modules/ai_chat_agent/types.bal new file mode 100644 index 0000000000..3c895a3232 --- /dev/null +++ b/apps/customer-portal/backend/modules/ai_chat_agent/types.bal @@ -0,0 +1,73 @@ +// Copyright (c) 2026 WSO2 LLC. (https://www.wso2.com). +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +import ballerina/data.jsondata; + +# [Configurable] Client credentials grant type oauth2 configuration. +type ClientCredentialsOauth2Config record {| + # OAuth2 token endpoint + string tokenUrl; + # OAuth2 client ID + string clientId; + # OAuth2 client secret + string clientSecret; + # OAuth2 scopes + string[] scopes = []; +|}; + +# Case classification payload. +public type CaseClassificationPayload record {| + # Chat history + string chat_history; + # Product details + string[] productDetails; + # Environments + string[] environments; + # Region + string region; + # Tier + string tier; +|}; + +# Case classification response. +public type CaseClassificationResponse record {| + # Issue type + string issueType; + # Case information + @jsondata:Name {value: "case_info"} + ChatCaseInfo caseInfo; + # Severity level + string severityLevel; + json...; +|}; + +# Chat case information. +public type ChatCaseInfo record {| + # Description + string description; + # Short description + string shortDescription; + # Product name + string productName; + # Product version + string productVersion; + # Environment + string environment; + # Tier + string tier; + # Region + string region; + json...; +|}; diff --git a/apps/customer-portal/backend/service.bal b/apps/customer-portal/backend/service.bal index 8ff9cb0910..5d16d02dc6 100644 --- a/apps/customer-portal/backend/service.bal +++ b/apps/customer-portal/backend/service.bal @@ -14,6 +14,7 @@ // specific language governing permissions and limitations // under the License. +import customer_portal.ai_chat_agent; import customer_portal.authorization; import customer_portal.entity; import customer_portal.scim; @@ -829,6 +830,44 @@ service http:InterceptableService / on new http:Listener(9090) { return getCaseFilters(caseMetadata); } + # Classify the case using AI chat agent. + # + # + payload - Case classification payload + # + return - Case classification response or an error + resource function post cases/classify(http:RequestContext ctx, CaseClassificationPayload payload) + returns ai_chat_agent:CaseClassificationResponse|http:InternalServerError { + + authorization:UserInfoPayload|error userInfo = ctx.getWithType(authorization:HEADER_USER_INFO); + if userInfo is error { + return { + body: { + message: ERR_MSG_USER_INFO_HEADER_NOT_FOUND + } + }; + } + + // TODO: Need to persist the chat history + ai_chat_agent:CaseClassificationResponse|error classificationResponse = + ai_chat_agent:createCaseClassification( + { + chat_history: payload.chatHistory, + productDetails: payload.productDetails, + environments: payload.environments, + region: payload.region, + tier: payload.tier + }); + if classificationResponse is error { + string customError = "Failed to classify chat message."; + log:printError(customError, classificationResponse); + return { + body: { + message: customError + } + }; + } + return classificationResponse; + } + # Get comments for a specific case. # # + id - ID of the case diff --git a/apps/customer-portal/backend/types.bal b/apps/customer-portal/backend/types.bal index bdaaae548d..3a45b10669 100644 --- a/apps/customer-portal/backend/types.bal +++ b/apps/customer-portal/backend/types.bal @@ -309,3 +309,17 @@ public type AttachmentPayload record {| # Base 64 encoded content string content; |}; + +# Case classification payload. +public type CaseClassificationPayload record {| + # Chat history + string chatHistory; + # Product details + string[] productDetails; + # Environments + string[] environments; + # Region + string region; + # Tier + string tier; +|};