Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Experimental galactic wizard #15860

Merged
merged 33 commits into from
Nov 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
4a04033
Set up basic chat endpoint
dannon Mar 16, 2023
944ac9c
Make openai key configurable
dannon Mar 17, 2023
075b4ca
Basic wizard interface
dannon Mar 21, 2023
531f74e
Disable button, use enter instead with a smaller box
dannon Mar 21, 2023
8276f19
Incremental tweaks
dannon Mar 21, 2023
b74d8ad
Swap to ChatCompletion & gpt-3.5-turbo
dannon Mar 28, 2023
b950040
Prompt tweaks, slightly more tailored towards Galaxy answers
dannon Mar 28, 2023
0cfdcfe
Preserve text response formatting with pre-wrap, dump unused query bu…
dannon Mar 28, 2023
00c5a56
Black
dannon Mar 28, 2023
3f9205c
Add openai conditional dependency
dannon Mar 28, 2023
a96ee53
Update api schema
dannon Mar 28, 2023
4dd95d0
Add wizard response to DatasetErrorDetails
ahmedhamidawan Apr 13, 2023
54d5397
add button to only allow api request on demand
ahmedhamidawan Apr 16, 2023
e5119a8
Add chat exchange storage migration
dannon Jun 5, 2023
7370f58
Models for chat
dannon Jun 5, 2023
775984f
Fix relationships -- needs to be defined on both sides (?)
dannon Jun 22, 2024
6b914e1
Drop user name directive, tweak error prompt.
dannon Jun 13, 2023
9526dd4
Migration adjustments, api tweaks
dannon Jun 27, 2024
17ebe0f
openai library update, model swap
dannon Jun 27, 2024
bb15f04
Add disclaimer display, adjust prompts
dannon Jun 27, 2024
316af21
added chat prompts json and a function to load them in the config
tcollins2011 Oct 24, 2024
aed59a9
Fix linting
dannon Nov 18, 2024
4ef4b01
modified the mardown composable to process list items
tcollins2011 Oct 23, 2024
83b79a2
Merging disparate chatgxy changes, bugfixes and code cleanup.
dannon Nov 18, 2024
0134a4e
Fix/adjust typing in chat manager.
dannon Nov 18, 2024
e858bf0
Make config-rebuild
dannon Nov 18, 2024
c98216d
Update lib/galaxy/model/__init__.py
dannon Nov 19, 2024
8aadf1a
Ensure accessibility of job, scope get to user. (e.g. -- admins coul…
dannon Nov 19, 2024
4de179a
Refactor typing annotations in ChatManager and ChatAPI to use Union f…
dannon Nov 19, 2024
bb7c6c9
Drop cascade_backrefs in ChatExchange message relationship.
dannon Nov 19, 2024
2832052
Drop outdated sqlalchemy2.0 related comment
dannon Nov 19, 2024
00536ce
Use appropriate typing instead of nullable=True in the mapped column …
dannon Nov 19, 2024
706c8b5
Address create_time nullable issue -- these will always exist.
dannon Nov 19, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading