-
-
Notifications
You must be signed in to change notification settings - Fork 359
Oql assistant #5281
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
Open
FlorisVleugels
wants to merge
8
commits into
cBioPortal:master
Choose a base branch
from
FlorisVleugels:oql-assistant
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Oql assistant #5281
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
11c9831
initial chatbox
FlorisVleugels f2c3c8e
Add query page only chatbot
FlorisVleugels 788a5db
Update userMessage field
FlorisVleugels 26fc469
Add button and fix error messages
FlorisVleugels edbf842
Change to new X icon since twitter icon depr in new font awesome
FlorisVleugels 3822ec9
Change 'use oql' icon and example queries
FlorisVleugels b5862b0
Only show gene assistant when spring.ai.enabled
FlorisVleugels 68e8497
fix property name
FlorisVleugels File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,288 @@ | ||
| import * as React from 'react'; | ||
| import ReactMarkdown from 'react-markdown'; | ||
| import { observer } from 'mobx-react'; | ||
| import { action, observable, makeObservable } from 'mobx'; | ||
| import styles from './styles/styles.module.scss'; | ||
| import internalClient from '../../../shared/api/cbioportalInternalClientInstance'; | ||
| import { UserMessage } from 'cbioportal-ts-api-client/dist/generated/CBioPortalAPIInternal'; | ||
| import { QueryStoreComponent } from './QueryStore'; | ||
|
|
||
| enum OQLError { | ||
| io = 'Something went wrong, please try again', | ||
| invalid = 'Please submit a valid OQL question', | ||
| } | ||
|
|
||
| const ErrorMessage: React.FC<{ message: string }> = ({ message }) => ( | ||
| <div className={styles.errorMessage}>{message}</div> | ||
| ); | ||
|
|
||
| @observer | ||
| export default class GeneAssistant extends QueryStoreComponent<{}, {}> { | ||
| constructor(props: any) { | ||
| super(props); | ||
| makeObservable(this); | ||
| } | ||
| @observable private userMessage = ''; | ||
| @observable private pending = false; | ||
| @observable private showErrorMessage = false; | ||
| @observable private errorMessage = OQLError.io; | ||
| private examples = { | ||
| 'Find mutations in tumor suppressor genes': | ||
| 'TP53 RB1 CDKN2A PTEN SMAD4 ARID1A...', | ||
| 'Somatic missense mutations in PIK3CA': 'PIK3CA: MISSENSE_SOMATIC', | ||
| 'Find KRAS mutations excluding silent ones': 'KRAS: MUT', | ||
| }; | ||
|
|
||
| @action.bound | ||
| private toggleSupport() { | ||
| this.store.showSupport = !this.store.showSupport; | ||
| } | ||
|
|
||
| @action.bound | ||
| private submitOQL(oql: string) { | ||
| this.store.geneQuery = oql; | ||
| } | ||
|
|
||
| @action.bound | ||
| private queryExample(example: string) { | ||
| this.userMessage = example; | ||
| } | ||
|
|
||
| @action.bound | ||
| private handleInputChange(event: React.ChangeEvent<HTMLInputElement>) { | ||
| this.userMessage = event.target.value; | ||
| } | ||
|
|
||
| @action.bound | ||
| private handleSendMessage(event: React.FormEvent<HTMLFormElement>) { | ||
| event.preventDefault(); | ||
|
|
||
| if (!this.userMessage.trim()) return; | ||
|
|
||
| this.store.messages.push({ | ||
| speaker: 'User', | ||
| text: this.userMessage, | ||
| }); | ||
| this.getResponse(); | ||
| this.userMessage = ''; | ||
| } | ||
|
|
||
| @action.bound | ||
| private async getResponse() { | ||
| this.showErrorMessage = false; | ||
| this.pending = true; | ||
|
|
||
| let userMessage = { | ||
| message: this.userMessage, | ||
| } as UserMessage; | ||
|
|
||
| try { | ||
| const response = await internalClient.getSupportUsingPOST({ | ||
| userMessage, | ||
| }); | ||
| const parts = response.aiResponse.split('OQL: ', 2); | ||
|
|
||
| if (parts.length < 2 || parts[1].trim().toUpperCase() === 'FALSE') { | ||
| this.showErrorMessage = true; | ||
| this.errorMessage = OQLError.invalid; | ||
| } else { | ||
| this.store.messages.push({ | ||
| speaker: 'AI', | ||
| text: parts[0].trim(), | ||
| }); | ||
| } | ||
| this.pending = false; | ||
| } catch (error) { | ||
| this.pending = false; | ||
| this.showErrorMessage = true; | ||
| this.errorMessage = OQLError.io; | ||
| } | ||
| } | ||
|
|
||
| renderButton() { | ||
| return ( | ||
| <button | ||
| style={{ borderRadius: '8px', fontSize: '13px' }} | ||
| className="btn btn-primary btn-lg" | ||
| data-test="aiButton" | ||
| onClick={this.toggleSupport} | ||
| > | ||
| {!this.store.showSupport ? ( | ||
| <div> | ||
| <i | ||
| className="fa-solid fa-robot" | ||
| style={{ paddingRight: '5px' }} | ||
| /> | ||
| Gene Assistant | ||
| </div> | ||
| ) : ( | ||
| <div> | ||
| <i | ||
| className="fa-solid fa-robot" | ||
| style={{ paddingRight: '5px' }} | ||
| /> | ||
| Hide Assistant | ||
| </div> | ||
| )} | ||
| </button> | ||
| ); | ||
| } | ||
|
|
||
| renderThinking() { | ||
| return ( | ||
| <div className={styles.thinking}> | ||
| <span className={styles.dots}> | ||
| <span className={styles.dot} /> | ||
| <span className={styles.dot} /> | ||
| <span className={styles.dot} /> | ||
| </span> | ||
| </div> | ||
| ); | ||
| } | ||
|
|
||
| renderErrorMessage(error: string) { | ||
| return <div className={styles.errorMessage}>{error}</div>; | ||
| } | ||
|
|
||
| renderMessages() { | ||
| return ( | ||
| <div> | ||
| {this.store.messages.map((msg, index) => { | ||
| const isUser = msg.speaker === 'User'; | ||
| return ( | ||
| <div | ||
| key={index} | ||
| className={ | ||
| styles.messageRow + | ||
| (isUser ? ' ' + styles.messageRowRight : '') | ||
| } | ||
| > | ||
| <div | ||
| className={ | ||
| isUser ? styles.question : styles.message | ||
| } | ||
| > | ||
| {msg.text.split('\n').map((line, i) => ( | ||
| <p key={i} className={styles.messageLine}> | ||
| <ReactMarkdown key={i}> | ||
| {line} | ||
| </ReactMarkdown> | ||
| </p> | ||
| ))} | ||
| </div> | ||
| {!isUser && index !== 0 && ( | ||
| <div> | ||
| <button | ||
| onClick={() => this.submitOQL(msg.text)} | ||
| style={{ | ||
| fontSize: '20px', | ||
| color: '#3498db', | ||
| marginRight: '8px', | ||
| border: 0, | ||
| background: 'none', | ||
| }} | ||
| > | ||
| <i className="fa-solid fa-right-to-bracket"></i> | ||
| </button> | ||
| </div> | ||
| )} | ||
| </div> | ||
| ); | ||
| })} | ||
| </div> | ||
| ); | ||
| } | ||
|
|
||
| renderExamples() { | ||
| return ( | ||
| <div className={styles.examplesarea}> | ||
| <h2> | ||
| <i | ||
| className="fa-solid fa-lightbulb" | ||
| style={{ paddingRight: '10px' }} | ||
| /> | ||
| Quick Examples: | ||
| </h2> | ||
|
|
||
| <div className={styles.examplestext}> | ||
| {Object.entries(this.examples).map(([example, genes]) => ( | ||
| <div | ||
| className={styles.exampleitem} | ||
| onClick={() => this.queryExample(example)} | ||
| > | ||
| <strong className={styles.exampletitle}> | ||
| {example} | ||
| </strong> | ||
| <span className={styles.exampledescription}> | ||
| {genes} | ||
| </span> | ||
| </div> | ||
| ))} | ||
| </div> | ||
| </div> | ||
| ); | ||
| } | ||
|
|
||
| render() { | ||
| return ( | ||
| <div className={styles.supportContainer}> | ||
| {this.renderButton()} | ||
| {this.store.showSupport && ( | ||
| <div className={styles.chatWindow}> | ||
| <section className={styles.titlearea}> | ||
| <img | ||
| src={require('../../../globalStyles/images/cbioportal_icon.png')} | ||
| className={styles.titleIcon} | ||
| alt="cBioPortal icon" | ||
| /> | ||
| <span>cBioPortal Gene Assistant</span> | ||
| </section> | ||
|
|
||
| {this.renderExamples()} | ||
|
|
||
| <div className={styles.textarea}> | ||
| <div className={styles.textheader}> | ||
| Please ask your cBioPortal querying questions | ||
| here, for example how to correctly format a | ||
| query using Onco Query Language (OQL). | ||
| </div> | ||
| {this.renderMessages()} | ||
| {this.pending && this.renderThinking()} | ||
| {this.showErrorMessage && ( | ||
| <ErrorMessage message={this.errorMessage} /> | ||
| )} | ||
| </div> | ||
|
|
||
| <div className={styles.inputarea}> | ||
| <form | ||
| className={styles.form} | ||
| onSubmit={this.handleSendMessage} | ||
| > | ||
| <input | ||
| className={styles.input} | ||
| type="text" | ||
| value={this.userMessage} | ||
| onChange={this.handleInputChange} | ||
| placeholder="Ask me about genes, cancer types or OQL syntax!" | ||
| /> | ||
| <button | ||
| type="submit" | ||
| aria-hidden="true" | ||
| style={{ | ||
| fontSize: '20px', | ||
| color: '#3498db', | ||
| marginRight: '8px', | ||
| border: 0, | ||
| background: 'none', | ||
| }} | ||
| > | ||
| <i className="fa-solid fa-paper-plane"></i> | ||
| </button> | ||
| </form> | ||
| </div> | ||
| </div> | ||
| )} | ||
| </div> | ||
| ); | ||
| } | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
need to figure out where this is synced with the react version