Skip to content

Commit

Permalink
Merge pull request #15860 from dannon/galactic-wizardry
Browse files Browse the repository at this point in the history
Experimental galactic wizard
  • Loading branch information
dannon authored Nov 19, 2024
2 parents e93e70b + 706c8b5 commit 20c8e12
Show file tree
Hide file tree
Showing 19 changed files with 809 additions and 8 deletions.
146 changes: 146 additions & 0 deletions client/src/api/schema/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,46 @@ export interface paths {
patch?: never;
trace?: never;
};
"/api/chat": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
put?: never;
/**
* Query
* @description We're off to ask the wizard
*/
post: operations["query_api_chat_post"];
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/api/chat/{job_id}/feedback": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
/**
* Feedback
* @description Provide feedback on the chatbot response.
*/
put: operations["feedback_api_chat__job_id__feedback_put"];
post?: never;
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/api/configuration": {
parameters: {
query?: never;
Expand Down Expand Up @@ -6541,6 +6581,20 @@ export interface components {
*/
type: "change_dbkey";
};
/** ChatPayload */
ChatPayload: {
/**
* Context
* @description The context for the chatbot.
* @default
*/
context: string | null;
/**
* Query
* @description The query to be sent to the chatbot.
*/
query: string;
};
/** CheckForUpdatesResponse */
CheckForUpdatesResponse: {
/**
Expand Down Expand Up @@ -18658,6 +18712,98 @@ export interface operations {
};
};
};
query_api_chat_post: {
parameters: {
query: {
job_id: string | null;
};
header?: {
/** @description The user ID that will be used to effectively make this API call. Only admins and designated users can make API calls on behalf of other users. */
"run-as"?: string | null;
};
path?: never;
cookie?: never;
};
requestBody: {
content: {
"application/json": components["schemas"]["ChatPayload"];
};
};
responses: {
/** @description Successful Response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": string;
};
};
/** @description Request Error */
"4XX": {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["MessageExceptionModel"];
};
};
/** @description Server Error */
"5XX": {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["MessageExceptionModel"];
};
};
};
};
feedback_api_chat__job_id__feedback_put: {
parameters: {
query: {
feedback: number;
};
header?: {
/** @description The user ID that will be used to effectively make this API call. Only admins and designated users can make API calls on behalf of other users. */
"run-as"?: string | null;
};
path: {
job_id: string | null;
};
cookie?: never;
};
requestBody?: never;
responses: {
/** @description Successful Response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": number | null;
};
};
/** @description Request Error */
"4XX": {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["MessageExceptionModel"];
};
};
/** @description Server Error */
"5XX": {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["MessageExceptionModel"];
};
};
};
};
index_api_configuration_get: {
parameters: {
query?: {
Expand Down
19 changes: 17 additions & 2 deletions client/src/components/DatasetInformation/DatasetError.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import { library } from "@fortawesome/fontawesome-svg-core";
import { faBug } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
import { BAlert, BButton } from "bootstrap-vue";
import { BAlert, BButton, BCard } from "bootstrap-vue";
import { storeToRefs } from "pinia";
import { computed, onMounted, ref } from "vue";
Expand All @@ -16,6 +16,7 @@ import { errorMessageAsString } from "@/utils/simple-error";
import DatasetErrorDetails from "@/components/DatasetInformation/DatasetErrorDetails.vue";
import FormElement from "@/components/Form/FormElement.vue";
import GalaxyWizard from "@/components/GalaxyWizard.vue";
library.add(faBug);
Expand Down Expand Up @@ -154,6 +155,21 @@ onMounted(async () => {
>.
</p>

<h4 class="mb-3 h-md">Possible Causes</h4>
<p>
<span>
We can use AI to analyze the issue and suggest possible fixes. Please note that the diagnosis may
not always be accurate.
</span>
</p>
<BCard class="mb-2">
<GalaxyWizard
view="error"
:query="jobDetails.tool_stderr"
context="tool_error"
:job-id="jobDetails.id" />
</BCard>

<DatasetErrorDetails
:tool-stderr="jobDetails.tool_stderr"
:job-stderr="jobDetails.job_stderr"
Expand Down Expand Up @@ -189,7 +205,6 @@ onMounted(async () => {
</p>

<h4 class="mb-3 h-md">Issue Report</h4>

<BAlert v-for="(resultMessage, index) in resultMessages" :key="index" :variant="resultMessage[1]" show>
<span v-html="renderMarkdown(resultMessage[0])" />
</BAlert>
Expand Down
146 changes: 146 additions & 0 deletions client/src/components/GalaxyWizard.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
<script setup lang="ts">
import { library } from "@fortawesome/fontawesome-svg-core";
import { faThumbsDown, faThumbsUp } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
import { BAlert, BButton, BSkeleton } from "bootstrap-vue";
import { ref } from "vue";
import { GalaxyApi } from "@/api";
import { useMarkdown } from "@/composables/markdown";
import { errorMessageAsString } from "@/utils/simple-error";
import LoadingSpan from "./LoadingSpan.vue";
library.add(faThumbsUp, faThumbsDown);
interface Props {
context?: string;
jobId: string;
query: string;
view?: "wizard" | "error";
}
const props = withDefaults(defineProps<Props>(), {
view: "error",
context: "username",
});
const query = ref(props.query);
const queryResponse = ref("");
const errorMessage = ref("");
const busy = ref(false);
const feedback = ref<null | "up" | "down">(null);
const { renderMarkdown } = useMarkdown({ openLinksInNewPage: true, removeNewlinesAfterList: true });
/** On submit, query the server and put response in display box **/
async function submitQuery() {
busy.value = true;
if (query.value === "") {
errorMessage.value = "There is no context to provide a response.";
busy.value = false;
return;
}
/**
* Note: We are using a POST request here, which at the backend checks if a response exists
* for the given job_id and returns it if it does. If it doesn't, it will create a new response.
* Curious whether this is better done by using a separate GET and then a POST?
* TODO: Remove this comment after discussion.
*/
const { data, error } = await GalaxyApi().POST("/api/chat", {
params: {
query: { job_id: props.jobId },
},
body: {
query: query.value,
context: props.context,
},
});
if (error) {
errorMessage.value = errorMessageAsString(error, "Failed to get response from the server.");
} else {
queryResponse.value = data;
}
busy.value = false;
}
/** Send feedback to the server **/
async function sendFeedback(value: "up" | "down") {
feedback.value = value;
// up is 1 and down is 0
const feedbackValue = value === "up" ? 1 : 0;
const { error } = await GalaxyApi().PUT("/api/chat/{job_id}/feedback", {
params: {
path: { job_id: props.jobId },
query: { feedback: feedbackValue },
},
});
if (error) {
errorMessage.value = errorMessageAsString(error, "Failed to send feedback to the server.");
}
}
</script>

<template>
<div>
<!-- <Heading v-if="props.view == 'wizard'" inline h2>Ask the wizard</Heading>
<div :class="props.view == 'wizard' && 'mt-2'">
<b-input
v-if="props.query == ''"
id="wizardinput"
v-model="query"
style="width: 100%"
placeholder="What's the difference in fasta and fastq files?"
@keyup.enter="submitQuery" /> -->
<BAlert v-if="errorMessage" variant="danger" show>
{{ errorMessage }}
</BAlert>
<BButton v-else-if="!queryResponse" class="w-100" variant="info" :disabled="busy" @click="submitQuery">
<span v-if="!busy"> Let our Help Wizard Figure it out! </span>
<LoadingSpan v-else message="Thinking" />
</BButton>
<div :class="props.view == 'wizard' && 'mt-4'">
<div v-if="busy">
<BSkeleton animation="wave" width="85%" />
<BSkeleton animation="wave" width="55%" />
<BSkeleton animation="wave" width="70%" />
</div>
<!-- eslint-disable-next-line vue/no-v-html -->
<div v-else class="chatResponse" v-html="renderMarkdown(queryResponse)" />

<div v-if="queryResponse" class="feedback-buttons mt-2">
<hr class="w-100" />
<h4>Was this answer helpful?</h4>
<BButton
variant="success"
:disabled="feedback !== null"
:class="{ submitted: feedback === 'up' }"
@click="sendFeedback('up')">
<FontAwesomeIcon :icon="faThumbsUp" fixed-width />
</BButton>
<BButton
variant="danger"
:disabled="feedback !== null"
:class="{ submitted: feedback === 'down' }"
@click="sendFeedback('down')">
<FontAwesomeIcon :icon="faThumbsDown" fixed-width />
</BButton>
<i v-if="!feedback">This feedback helps us improve our responses.</i>
<i v-else>Thank you for your feedback!</i>
</div>
</div>
</div>
</template>
<style lang="scss" scoped>
.chatResponse {
white-space: pre-wrap;
}
.submitted svg {
animation: swoosh-up 1s forwards;
}
@keyframes swoosh-up {
0% {
transform: translateY(0);
}
50% {
transform: translateY(-20px);
}
100% {
transform: translateY(0);
}
}
</style>
Loading

0 comments on commit 20c8e12

Please sign in to comment.