diff --git a/client/src/components/DatasetInformation/DatasetError.vue b/client/src/components/DatasetInformation/DatasetError.vue
index 001bb403e0ac..75ef23f86c5e 100644
--- a/client/src/components/DatasetInformation/DatasetError.vue
+++ b/client/src/components/DatasetInformation/DatasetError.vue
@@ -53,6 +53,10 @@
+ What might have happened?
+
+
+
Issue Report
import DatasetErrorDetails from "./DatasetErrorDetails";
import FormElement from "components/Form/FormElement";
+import GalaxyWizard from "components/GalaxyWizard";
import { DatasetProvider } from "components/providers";
import { JobDetailsProvider, JobProblemProvider } from "components/providers/JobProvider";
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
@@ -105,6 +110,7 @@ export default {
DatasetErrorDetails,
FontAwesomeIcon,
FormElement,
+ GalaxyWizard,
JobDetailsProvider,
JobProblemProvider,
CurrentUser,
diff --git a/client/src/components/GalaxyWizard.vue b/client/src/components/GalaxyWizard.vue
new file mode 100644
index 000000000000..9206db372506
--- /dev/null
+++ b/client/src/components/GalaxyWizard.vue
@@ -0,0 +1,87 @@
+
+
+
+
+
Ask the wizard
+
+
+
+
+ Let our Help Wizard Figure it out!
+
+
+
+
+
+
+
+
+
+
+
+
{{ queryResponse }}
+
+
+
+
diff --git a/client/src/entry/analysis/menu.js b/client/src/entry/analysis/menu.js
index ddbfcb3f0e65..fdb84c2d8fc7 100644
--- a/client/src/entry/analysis/menu.js
+++ b/client/src/entry/analysis/menu.js
@@ -113,6 +113,10 @@ export function fetchMenu(options = {}) {
target: "_blank",
hidden: !options.helpsite_url,
},
+ {
+ title: _l("Help Wizard"),
+ url: "/wizard",
+ },
{
title: _l("Support"),
url: options.support_url,
diff --git a/client/src/entry/analysis/router.js b/client/src/entry/analysis/router.js
index 35f234452422..74317399691e 100644
--- a/client/src/entry/analysis/router.js
+++ b/client/src/entry/analysis/router.js
@@ -66,6 +66,8 @@ import { ExternalIdentities } from "components/User/ExternalIdentities";
import { HistoryExport } from "components/HistoryExport/index";
import HistoryExportTasks from "components/History/Export/HistoryExport";
+import GalaxyWizard from "components/GalaxyWizard";
+
Vue.use(VueRouter);
// patches $router.push() to trigger an event and hide duplication warnings
@@ -318,6 +320,10 @@ export function getRouter(Galaxy) {
path: "tours",
component: TourList,
},
+ {
+ path: "wizard",
+ component: GalaxyWizard,
+ },
{
path: "tours/:tourId",
component: TourRunner,
diff --git a/client/src/schema/schema.ts b/client/src/schema/schema.ts
index c8ac40bba525..4b6046685e29 100644
--- a/client/src/schema/schema.ts
+++ b/client/src/schema/schema.ts
@@ -8,6 +8,13 @@ export interface paths {
/** Returns returns an API key for authenticated user based on BaseAuth headers. */
get: operations["get_api_key_api_authenticate_baseauth_get"];
};
+ "/api/chat": {
+ /**
+ * Query
+ * @description We're off to ask the wizard
+ */
+ post: operations["query_api_chat_post"];
+ };
"/api/configuration": {
/**
* Return an object containing exposable configuration settings
@@ -1600,6 +1607,23 @@ export interface components {
*/
type: "change_dbkey";
};
+ /**
+ * ChatPayload
+ * @description Base model definition with common configuration used by all derived models.
+ */
+ ChatPayload: {
+ /**
+ * Message
+ * @description The message to be sent to the chat.
+ */
+ query: string;
+ /**
+ * Context
+ * @description The context identifier to be used by the chat.
+ * @enum {string}
+ */
+ context?: "username" | "tool_error";
+ };
/**
* CheckForUpdatesResponse
* @description Base model definition with common configuration used by all derived models.
@@ -7630,6 +7654,31 @@ export interface operations {
};
};
};
+ query_api_chat_post: {
+ /**
+ * Query
+ * @description We're off to ask the wizard
+ */
+ requestBody: {
+ content: {
+ "application/json": components["schemas"]["ChatPayload"];
+ };
+ };
+ responses: {
+ /** @description Successful Response */
+ 200: {
+ content: {
+ "application/json": string;
+ };
+ };
+ /** @description Validation Error */
+ 422: {
+ content: {
+ "application/json": components["schemas"]["HTTPValidationError"];
+ };
+ };
+ };
+ };
index_api_configuration_get: {
/**
* Return an object containing exposable configuration settings
diff --git a/doc/source/admin/galaxy_options.rst b/doc/source/admin/galaxy_options.rst
index 3d54bea52166..2df35e9f8f5a 100644
--- a/doc/source/admin/galaxy_options.rst
+++ b/doc/source/admin/galaxy_options.rst
@@ -4944,6 +4944,17 @@
:Type: int
+~~~~~~~~~~~~~~~~~~
+``openai_api_key``
+~~~~~~~~~~~~~~~~~~
+
+:Description:
+ API key for OpenAI (https://openai.com/) to enable the wizard (or
+ more?)
+:Default: ``None``
+:Type: str
+
+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
``enable_tool_recommendations``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/lib/galaxy/config/sample/galaxy.yml.sample b/lib/galaxy/config/sample/galaxy.yml.sample
index fdb8f94fbc7e..59a4d9db2d39 100644
--- a/lib/galaxy/config/sample/galaxy.yml.sample
+++ b/lib/galaxy/config/sample/galaxy.yml.sample
@@ -2648,6 +2648,10 @@ galaxy:
# as threshold (above threshold: regular select fields will be used)
#select_type_workflow_threshold: -1
+ # API key for OpenAI (https://openai.com/) to enable the wizard (or
+ # more?)
+ #openai_api_key: null
+
# Allow the display of tool recommendations in workflow editor and
# after tool execution. If it is enabled and set to true, please
# enable 'tool_recommendation_model_path' as well
diff --git a/lib/galaxy/config/schemas/config_schema.yml b/lib/galaxy/config/schemas/config_schema.yml
index 3e6b1881c627..027d8008ae1c 100644
--- a/lib/galaxy/config/schemas/config_schema.yml
+++ b/lib/galaxy/config/schemas/config_schema.yml
@@ -3609,6 +3609,12 @@ mapping:
use -1 (default) in order to always use the regular select fields,
use any other positive number as threshold (above threshold: regular select fields will be used)
+ openai_api_key:
+ type: str
+ required: false
+ desc: |
+ API key for OpenAI (https://openai.com/) to enable the wizard (or more?)
+
enable_tool_recommendations:
type: bool
default: false
diff --git a/lib/galaxy/dependencies/__init__.py b/lib/galaxy/dependencies/__init__.py
index 79526e490304..42d2ea49a339 100644
--- a/lib/galaxy/dependencies/__init__.py
+++ b/lib/galaxy/dependencies/__init__.py
@@ -280,6 +280,9 @@ def check_influxdb(self):
def check_tensorflow(self):
return asbool(self.config["enable_tool_recommendations"])
+ def check_openai(self):
+ return self.config.get("openai_api_key", None) is not None
+
def check_weasyprint(self):
# See notes in ./conditional-requirements.txt for more information.
return os.environ.get("GALAXY_DEPENDENCIES_INSTALL_WEASYPRINT") == "1"
diff --git a/lib/galaxy/dependencies/conditional-requirements.txt b/lib/galaxy/dependencies/conditional-requirements.txt
index 8d8a5d9bd55c..28335c054a96 100644
--- a/lib/galaxy/dependencies/conditional-requirements.txt
+++ b/lib/galaxy/dependencies/conditional-requirements.txt
@@ -14,6 +14,7 @@ python-pam
galaxycloudrunner
pkce
total-perspective-vortex<3
+openai
# For file sources plugins
fs.webdavfs>=0.4.2 # type: webdav
diff --git a/lib/galaxy/schema/schema.py b/lib/galaxy/schema/schema.py
index f70f7680bb3a..ef63c08d7c46 100644
--- a/lib/galaxy/schema/schema.py
+++ b/lib/galaxy/schema/schema.py
@@ -3191,6 +3191,19 @@ class MaterializeDatasetInstanceRequest(MaterializeDatasetInstanceAPIRequest):
history_id: DecodedDatabaseIdField
+class ChatPayload(Model):
+ query: str = Field(
+ ...,
+ title="Message",
+ description="The message to be sent to the chat.",
+ )
+ context: Optional[str] = Field(
+ default="",
+ title="Context",
+ description="A context identifier to be used by the chat.",
+ )
+
+
class CreatePagePayload(PageSummaryBase):
content_format: PageContentFormat = ContentFormatField
content: Optional[str] = ContentField
diff --git a/lib/galaxy/webapps/galaxy/api/chat.py b/lib/galaxy/webapps/galaxy/api/chat.py
new file mode 100644
index 000000000000..3f78dfecb9e6
--- /dev/null
+++ b/lib/galaxy/webapps/galaxy/api/chat.py
@@ -0,0 +1,73 @@
+"""
+API Controller providing Chat functionality
+"""
+import logging
+
+try:
+ import openai
+except ImportError:
+ openai = None
+
+from galaxy.config import GalaxyAppConfiguration
+from galaxy.managers.context import ProvidesUserContext
+from galaxy.webapps.galaxy.api import (
+ depends,
+ DependsOnTrans,
+)
+from galaxy.exceptions import ConfigurationError
+from galaxy.schema.schema import ChatPayload
+from . import (
+ depends,
+ Router,
+)
+
+log = logging.getLogger(__name__)
+
+router = Router(tags=["chat"])
+
+PROMPT = """
+You are a highly intelligent question answering agent, expert on the Galaxy analysis platform and in the fields of computer science, bioinformatics, and genomics.
+You will try to answer questions about Galaxy, and if you don't know the answer, you will ask the user to rephrase the question.
+"""
+
+
+@router.cbv
+class ChatAPI:
+ config: GalaxyAppConfiguration = depends(GalaxyAppConfiguration)
+
+ @router.post("/api/chat")
+ def query(self, query: ChatPayload, trans: ProvidesUserContext = DependsOnTrans) -> str:
+ """We're off to ask the wizard"""
+
+ if openai is None or self.config.openai_api_key is None:
+ raise ConfigurationError("OpenAI is not configured for this instance.")
+ else:
+ openai.api_key = self.config.openai_api_key
+
+ messages=[
+ {"role": "system", "content": PROMPT},
+ {"role": "user", "content": query.query},
+ ]
+
+ if query.context == "username":
+ user = trans.user
+ if user is not None:
+ log.debug(f"CHATGPTuser: {user.username}")
+ msg = f"You will address the user as {user.username}"
+ else:
+ msg = f"You will address the user as Anonymous User"
+ messages.append({"role": "system", "content": msg})
+ elif query.context == "tool_error":
+ msg = "The user will provide you a Galaxy tool error, and you will try to debug and explain what happened"
+ messages.append({"role": "system", "content": msg})
+
+ log.debug(f"CHATGPTmessages: {messages}")
+
+ response = openai.ChatCompletion.create(
+ model="gpt-3.5-turbo",
+ messages=messages,
+ temperature=0,
+ )
+
+ answer = response["choices"][0]["message"]["content"]
+ return answer
diff --git a/lib/galaxy/webapps/galaxy/buildapp.py b/lib/galaxy/webapps/galaxy/buildapp.py
index a9bcaba11314..557022d50de9 100644
--- a/lib/galaxy/webapps/galaxy/buildapp.py
+++ b/lib/galaxy/webapps/galaxy/buildapp.py
@@ -254,6 +254,7 @@ def app_pair(global_conf, load_app_kwds=None, wsgi_preflight=True, **kwargs):
webapp.add_client_route("/collection/{collection_id}/edit")
webapp.add_client_route("/jobs/submission/success")
webapp.add_client_route("/jobs/{job_id}/view")
+ webapp.add_client_route("/wizard")
webapp.add_client_route("/workflows/list")
webapp.add_client_route("/workflows/list_published")
webapp.add_client_route("/workflows/edit")