diff --git a/core/amber/src/main/resources/application.conf b/core/amber/src/main/resources/application.conf index 69e0c00188e..460a60a9310 100644 --- a/core/amber/src/main/resources/application.conf +++ b/core/amber/src/main/resources/application.conf @@ -91,3 +91,11 @@ region-plan-generator { enable-cost-based-region-plan-generator = false use-global-search = false } + +ai-assistant-server{ + assistant = "none" + # Put your Ai Service authentication key here + ai-service-key = "" + # Put your Ai service url here (If you are using OpenAI, then the url should be "https://api.openai.com/v1") + ai-service-url = "" +} \ No newline at end of file diff --git a/core/amber/src/main/scala/edu/uci/ics/amber/engine/common/AmberConfig.scala b/core/amber/src/main/scala/edu/uci/ics/amber/engine/common/AmberConfig.scala index 6b1c04981ee..f5691b5d402 100644 --- a/core/amber/src/main/scala/edu/uci/ics/amber/engine/common/AmberConfig.scala +++ b/core/amber/src/main/scala/edu/uci/ics/amber/engine/common/AmberConfig.scala @@ -105,4 +105,9 @@ object AmberConfig { // JDBC configuration val jdbcConfig: Config = getConfSource.getConfig("jdbc") + // Python language server configuration + var aiAssistantConfig: Option[Config] = None + if (getConfSource.hasPath("ai-assistant-server")) { + aiAssistantConfig = Some(getConfSource.getConfig("ai-assistant-server")) + } } diff --git a/core/amber/src/main/scala/edu/uci/ics/texera/web/TexeraWebApplication.scala b/core/amber/src/main/scala/edu/uci/ics/texera/web/TexeraWebApplication.scala index 99b8bfd19e7..e05e7e23fd3 100644 --- a/core/amber/src/main/scala/edu/uci/ics/texera/web/TexeraWebApplication.scala +++ b/core/amber/src/main/scala/edu/uci/ics/texera/web/TexeraWebApplication.scala @@ -258,6 +258,7 @@ class TexeraWebApplication environment.jersey.register(classOf[AdminExecutionResource]) environment.jersey.register(classOf[UserQuotaResource]) environment.jersey.register(classOf[UserDiscussionResource]) + environment.jersey.register(classOf[AiAssistantResource]) } /** diff --git a/core/amber/src/main/scala/edu/uci/ics/texera/web/resource/aiassistant/AiAssistantManager.scala b/core/amber/src/main/scala/edu/uci/ics/texera/web/resource/aiassistant/AiAssistantManager.scala new file mode 100644 index 00000000000..00e334a17aa --- /dev/null +++ b/core/amber/src/main/scala/edu/uci/ics/texera/web/resource/aiassistant/AiAssistantManager.scala @@ -0,0 +1,51 @@ +package edu.uci.ics.texera.web.resource.aiassistant +import edu.uci.ics.amber.engine.common.AmberConfig +import java.net.{HttpURLConnection, URL} + +object AiAssistantManager { + private val aiAssistantConfig = AmberConfig.aiAssistantConfig.getOrElse( + throw new Exception("ai-assistant-server configuration is missing in application.conf") + ) + val assistantType: String = aiAssistantConfig.getString("assistant") + // The accountKey is the OpenAI authentication key used to authenticate API requests and obtain responses from the OpenAI service. + + val accountKey: String = aiAssistantConfig.getString("ai-service-key") + val sharedUrl: String = aiAssistantConfig.getString("ai-service-url") + + private def initOpenAI(): String = { + var connection: HttpURLConnection = null + try { + val url = new URL(s"${sharedUrl}/models") + connection = url.openConnection().asInstanceOf[HttpURLConnection] + connection.setRequestMethod("GET") + connection.setRequestProperty( + "Authorization", + s"Bearer ${accountKey.trim.replaceAll("^\"|\"$", "")}" + ) + val responseCode = connection.getResponseCode + if (responseCode == 200) { + "OpenAI" + } else { + "NoAiAssistant" + } + } catch { + case e: Exception => + "NoAiAssistant" + } finally { + if (connection != null) { + connection.disconnect() + } + } + } + + val validAIAssistant: String = assistantType match { + case "none" => + "NoAiAssistant" + + case "openai" => + initOpenAI() + + case _ => + "NoAiAssistant" + } +} diff --git a/core/amber/src/main/scala/edu/uci/ics/texera/web/resource/aiassistant/AiAssistantResource.scala b/core/amber/src/main/scala/edu/uci/ics/texera/web/resource/aiassistant/AiAssistantResource.scala new file mode 100644 index 00000000000..4d4c3002d79 --- /dev/null +++ b/core/amber/src/main/scala/edu/uci/ics/texera/web/resource/aiassistant/AiAssistantResource.scala @@ -0,0 +1,13 @@ +package edu.uci.ics.texera.web.resource +import edu.uci.ics.texera.web.resource.aiassistant.AiAssistantManager +import javax.annotation.security.RolesAllowed +import javax.ws.rs._ + +@Path("/aiassistant") +class AiAssistantResource { + final private lazy val isEnabled = AiAssistantManager.validAIAssistant + @GET + @RolesAllowed(Array("REGULAR", "ADMIN")) + @Path("/isenabled") + def isAiAssistantEnable: String = isEnabled +} diff --git a/core/gui/src/app/dashboard/service/user/ai-assistant/ai-assistant.service.ts b/core/gui/src/app/dashboard/service/user/ai-assistant/ai-assistant.service.ts new file mode 100644 index 00000000000..66534152827 --- /dev/null +++ b/core/gui/src/app/dashboard/service/user/ai-assistant/ai-assistant.service.ts @@ -0,0 +1,30 @@ +import { Injectable } from "@angular/core"; +import { firstValueFrom } from "rxjs"; +import { AppSettings } from "../../../../common/app-setting"; +import { HttpClient } from "@angular/common/http"; + +export const AI_ASSISTANT_API_BASE_URL = `${AppSettings.getApiEndpoint()}/aiassistant`; + +@Injectable({ + providedIn: "root", +}) +export class AiAssistantService { + constructor(private http: HttpClient) {} + + public checkAiAssistantEnabled(): Promise { + const apiUrl = `${AI_ASSISTANT_API_BASE_URL}/isenabled`; + return firstValueFrom(this.http.get(apiUrl, { responseType: "text" })) + .then(response => { + const isEnabled = response !== undefined ? response : "NoAiAssistant"; + console.log( + isEnabled === "OpenAI" + ? "AI Assistant successfully started" + : "No AI Assistant or OpenAI authentication key error" + ); + return isEnabled; + }) + .catch(() => { + return "NoAiAssistant"; + }); + } +}