diff --git a/Makefile b/Makefile index 58d1a0f..fcf1f31 100644 --- a/Makefile +++ b/Makefile @@ -33,12 +33,25 @@ clean-studio: @printf "Clean studio...\n" @cd studio && rm -rf node_modules && rm -rf dist && yarn cache clean && cd .. -build-studio: +build-biomedgps-studio: @printf "Building studio based on openapi...\n" @mkdir -p assets + @cp studio/logo/biomedgps.png studio/public/assets/logo-white.png + @cp studio/logo/biomedgps.png studio/src/assets/logo-white.png + @cp studio/logo/biomedgps.png studio/public/logo.png # @cd studio && yarn && yarn openapi || true @cd studio && yarn - @cd studio && yarn build:embed && cd .. + @cd studio && yarn build:biomedgps-embed && cd .. + +build-rapex-studio: + @printf "Building studio based on openapi...\n" + @mkdir -p assets + @cp studio/logo/rapex.png studio/public/assets/logo-white.png + @cp studio/logo/rapex.png studio/src/assets/logo-white.png + @cp studio/logo/rapex.png studio/public/logo.png + # @cd studio && yarn && yarn openapi || true + @cd studio && yarn + @cd studio && yarn build:rapex-embed && cd .. build-biomedgps: @cargo build --release @@ -46,13 +59,13 @@ build-biomedgps: build-biomedgps-linux: @cargo build --release --target=x86_64-unknown-linux-musl -build-mac: build-studio build-biomedgps +build-mac: build-biomedgps-studio build-biomedgps @printf "\nDone!\n" -build-linux: build-studio build-biomedgps +build-linux: build-biomedgps-studio build-biomedgps @printf "\nDone!\n" -build-linux-on-mac: build-studio build-biomedgps-linux +build-linux-on-mac: build-biomedgps-studio build-biomedgps-linux @printf "\nDone!\n" # You must run `make build-service` to build new api spec for studio when you change the api spec @@ -66,8 +79,16 @@ changelog: @python build/build_changelog.py --repo ../biominer-components --output-file ./studio/public/README/changelog.md --repo-name 'BioMedGPS UI' @python build/build_changelog.py --repo . --output-file ./studio/public/README/changelog.md --repo-name BioMedGPS -deploy: build-studio +deploy: deploy-biomedgps + +deploy-biomedgps: build-biomedgps-studio @docker run --rm -it -v "$(CURDIR)":/home/rust/src messense/rust-musl-cross:x86_64-musl cargo build --release @rsync -avP target/x86_64-unknown-linux-musl/release/biomedgps target/x86_64-unknown-linux-musl/release/biomedgps-cli root@drugs.3steps.cn:/data/biomedgps/bin @rsync -avP --delete assets/index.html root@drugs.3steps.cn:/var/www/html/biomedgps/index.html - @rsync -avP --delete assets root@drugs.3steps.cn:/var/www/html/biomedgps/ \ No newline at end of file + @rsync -avP --delete assets root@drugs.3steps.cn:/var/www/html/biomedgps/ + +deploy-rapex: build-rapex-studio + @docker run --rm -it -v "$(CURDIR)":/home/rust/src messense/rust-musl-cross:x86_64-musl cargo build --release + @rsync -avP target/x86_64-unknown-linux-musl/release/biomedgps target/x86_64-unknown-linux-musl/release/biomedgps-cli root@rapex.prophetdb.org:/data/rapex/bin + @rsync -avP --delete assets/index.html root@rapex.prophetdb.org:/var/www/html/rapex/index.html + @rsync -avP --delete assets root@rapex.prophetdb.org:/var/www/html/rapex/ \ No newline at end of file diff --git a/studio/config/routes.ts b/studio/config/routes.ts index a568d96..6c57563 100644 --- a/studio/config/routes.ts +++ b/studio/config/routes.ts @@ -1,4 +1,4 @@ -import { createElement } from "react"; +import { Children, createElement } from "react"; import * as icons from "@ant-design/icons"; import type { MenuDataItem } from '@ant-design/pro-components'; @@ -10,26 +10,40 @@ export const routes = [ component: './Home', }, { - path: '/knowledge-table', - name: 'Knowledge Table', - icon: 'table', - hideInMenu: true, - component: './KnowledgeTable', - category: 'knowledge-graph' - }, - { - path: '/predict-model', - name: 'Predict Drugs/Targets', - icon: 'history', - component: './ModelConfig', - category: 'predict-model' + path: '/analyze-omics-data', + name: 'Analyze Omics Data', + icon: 'LineChartOutlined', + disabled: true, + component: './Home', }, { - path: '/knowledge-graph', - name: 'Explain Your Results', - icon: 'comment', - component: './KnowledgeGraph', - category: 'knowledge-graph' + path: '/predict-explain', + name: 'Predict & Explain', + icon: 'link', + routes: [ + { + path: '/predict-explain/knowledge-table', + name: 'Knowledge Table', + icon: 'table', + hideInMenu: true, + component: './KnowledgeTable', + category: 'knowledge-graph' + }, + { + path: '/predict-explain/predict-model', + name: 'Predict Drugs/Targets', + icon: 'history', + component: './ModelConfig', + category: 'predict-model' + }, + { + path: '/predict-explain/knowledge-graph', + name: 'Explain Your Results', + icon: 'comment', + component: './KnowledgeGraph', + category: 'knowledge-graph' + }, + ] }, { path: '/mecfs-longcovid', diff --git a/studio/logo/biomedgps.png b/studio/logo/biomedgps.png new file mode 100644 index 0000000..d3a9f9a Binary files /dev/null and b/studio/logo/biomedgps.png differ diff --git a/studio/logo/rapex.png b/studio/logo/rapex.png new file mode 100644 index 0000000..78532c9 Binary files /dev/null and b/studio/logo/rapex.png differ diff --git a/studio/package.json b/studio/package.json index 949bcfd..3e64fec 100644 --- a/studio/package.json +++ b/studio/package.json @@ -7,14 +7,16 @@ "scripts": { "dev": "max dev", "build": "max build", - "build:embed": "cross-env UMI_APP_IS_STATIC=true UMI_ENV=embed UMI_APP_AUTH0_CLIENT_ID=Y08FauV1dAEiocNIZt5LiOifzNgXr6Uo UMI_APP_AUTH0_DOMAIN=biomedgps.jp.auth0.com max build", + "build:biomedgps-embed": "cross-env UMI_APP_IS_STATIC=true UMI_ENV=embed UMI_APP_AUTH0_CLIENT_ID=Y08FauV1dAEiocNIZt5LiOifzNgXr6Uo UMI_APP_AUTH0_DOMAIN=biomedgps.jp.auth0.com max build", + "build:rapex-embed": "cross-env UMI_APP_IS_STATIC=true UMI_ENV=embed max build", "build:analyze": "cross-env ANALYZE=true max build", "format": "prettier --cache --write .", "postinstall": "max setup", "setup": "max setup", "start": "npm run dev", "start:local-dev": "cross-env UMI_APP_API_PREFIX=http://localhost:3000 max dev", - "start:remote-dev": "cross-env UMI_APP_API_PREFIX=https://drugs.3steps.cn UMI_APP_AUTH0_CLIENT_ID=Y08FauV1dAEiocNIZt5LiOifzNgXr6Uo UMI_APP_AUTH0_DOMAIN=biomedgps.jp.auth0.com max dev", + "start:biomedgps-remote-dev": "cross-env UMI_APP_API_PREFIX=https://drugs.3steps.cn UMI_APP_AUTH0_CLIENT_ID=Y08FauV1dAEiocNIZt5LiOifzNgXr6Uo UMI_APP_AUTH0_DOMAIN=biomedgps.jp.auth0.com max dev", + "start:rapex-remote-dev": "cross-env UMI_APP_API_PREFIX=https://rapex.prophetdb.org max dev", "openapi": "max openapi" }, "dependencies": { diff --git a/studio/public/README/about.md b/studio/public/README/about.md index c8c2e45..3d34a4b 100644 --- a/studio/public/README/about.md +++ b/studio/public/README/about.md @@ -16,7 +16,7 @@ Construct and integrate knowledge graph, multi-omics data and d ## Features -### Predict Drug/Target Module +### Predict Drug/Target Module - [x] Predict known drugs for your queried disease (Drug Repurposing). - [x] Predict new indications for your queried drug. @@ -26,7 +26,7 @@ Construct and integrate knowledge graph, multi-omics data and d

-### Explain Your Results Module +### Explain Your Results Module - [x] Knowledge graph studio for graph query, visualization and analysis. - [x] Graph neural network for drug discovery, disease mechanism, biomarker screening and discovering response to toxicant exposure. diff --git a/studio/public/assets/logo-white.png b/studio/public/assets/logo-white.png index d3a9f9a..78532c9 100644 Binary files a/studio/public/assets/logo-white.png and b/studio/public/assets/logo-white.png differ diff --git a/studio/public/logo.png b/studio/public/logo.png index d3a9f9a..78532c9 100644 Binary files a/studio/public/logo.png and b/studio/public/logo.png differ diff --git a/studio/src/app.tsx b/studio/src/app.tsx index f167bbb..75889b9 100644 --- a/studio/src/app.tsx +++ b/studio/src/app.tsx @@ -4,7 +4,7 @@ import { RequestConfig, history, RuntimeConfig, request as UmiRequest } from 'um import { PageLoading, SettingDrawer } from '@ant-design/pro-components'; import { Auth0Provider } from '@auth0/auth0-react'; import { CustomSettings, AppVersion } from '../config/defaultSettings'; -import { getJwtAccessToken, logout, logoutWithRedirect, getUsername } from '@/components/util'; +import { getJwtAccessToken, logout, logoutWithRedirect, getUsername, isAuthEnabled } from '@/components/util'; // import * as Sentry from "@sentry/react"; @@ -141,6 +141,10 @@ export async function getInitialState(): Promise<{ } export function rootContainer(container: React.ReactNode): React.ReactNode { + if (!isAuthEnabled()) { + return container; + } + return ( { // You can modify the css style of the menu item at the global.less file. var spans = document.querySelectorAll('span.ant-pro-base-menu-horizontal-item-text'); + console.log("Add new-tag to ME/CFS: ", spans); spans.forEach(function (span) { + console.log("span.innerHTML: ", span.innerHTML); if (span.innerHTML.startsWith("ME/CFS")) { span.classList.add('new-tag'); } diff --git a/studio/src/assets/logo-white.png b/studio/src/assets/logo-white.png index d3a9f9a..78532c9 100644 Binary files a/studio/src/assets/logo-white.png and b/studio/src/assets/logo-white.png differ diff --git a/studio/src/components/Header/index.tsx b/studio/src/components/Header/index.tsx index cb7ee4d..afb25d4 100644 --- a/studio/src/components/Header/index.tsx +++ b/studio/src/components/Header/index.tsx @@ -1,7 +1,7 @@ import { QuestionCircleOutlined, InfoCircleOutlined, UserOutlined, FieldTimeOutlined, LogoutOutlined } from '@ant-design/icons'; import { Space, Menu, Button, message, Dropdown } from 'antd'; import React, { useEffect, useState } from 'react'; -import { getJwtAccessToken, logoutWithRedirect } from '@/components/util'; +import { getJwtAccessToken, logoutWithRedirect, isAuthEnabled } from '@/components/util'; import { useAuth0 } from "@auth0/auth0-react"; import type { MenuProps } from 'antd'; import { history } from 'umi'; @@ -160,14 +160,17 @@ const GlobalHeaderRight: React.FC = (props) => { { - !isAuthenticated ? ( + isAuthEnabled() && + !isAuthenticated ? ( ) : ( - + isAuthEnabled() ? + - + + : ) } diff --git a/studio/src/components/util.ts b/studio/src/components/util.ts index e568b89..9165548 100644 --- a/studio/src/components/util.ts +++ b/studio/src/components/util.ts @@ -89,7 +89,28 @@ export const logout = () => { localStorage.removeItem('redirectUrl'); } +export const isAuthEnabled = () => { + console.log("isAuthEnabled: ", process.env.UMI_APP_AUTH0_CLIENT_ID); + return process.env.UMI_APP_AUTH0_CLIENT_ID ? true : false +} + +export const isAuthenticated = () => { + if (getUsername()) { + return true + } + + if (!isAuthEnabled()) { + return true + } + + return false +} + export const logoutWithRedirect = () => { + if (!isAuthEnabled()) { + return + } + logout(); // Save the current hash as the redirect url let currentUrl = window.location.hash.split("#").pop(); diff --git a/studio/src/components/webllm.ts b/studio/src/components/webllm.ts index 1eb684f..8af17e4 100644 --- a/studio/src/components/webllm.ts +++ b/studio/src/components/webllm.ts @@ -1,27 +1,6 @@ import { ChatModule, InitProgressReport } from "@mlc-ai/web-llm"; import { message } from "antd"; -export const getJwtAccessToken = (): string | null => { - let jwtToken = null; - // Check if the cookie exists - if (document.cookie && document.cookie.includes("jwt_access_token=")) { - // Retrieve the cookie value - // @ts-ignore - jwtToken = document.cookie - .split("; ") - .find((row) => row.startsWith("jwt_access_token=")) - .split("=")[1]; - } - - if (jwtToken) { - console.log("JWT access token found in the cookie."); - return jwtToken; - } else { - console.log("JWT access token not found in the cookie."); - return null; - } -} - export const initChat = async () => { // const chat = new webllm.ChatWorkerClient(new Worker( // new URL('./assets/web-llm.worker.js', import.meta.url), diff --git a/studio/src/global.less b/studio/src/global.less index 4560c98..8073a97 100644 --- a/studio/src/global.less +++ b/studio/src/global.less @@ -38,6 +38,16 @@ body, .ant-pro-top-nav-header-logo img { height: 45px; } + + // For mix layout + .ant-pro-global-header { + height: 100%; + } + + .ant-pro-global-header-logo a>svg, + .ant-pro-global-header-logo a>img { + height: 45px; + } } .ant-pro-sider.ant-layout-sider.ant-pro-sider-fixed { @@ -71,6 +81,7 @@ ol { &-thead>tr, &-tbody>tr { + >th, >td { white-space: pre; @@ -85,7 +96,7 @@ ol { // Compatible with IE11 @media screen and(-ms-high-contrast: active), - (-ms-high-contrast: none) { +(-ms-high-contrast: none) { body .ant-design-pro>.ant-layout { min-height: 100vh; } @@ -146,7 +157,8 @@ ol { // For Markdown Table .markdown-viewer { tr:nth-child(odd) { - background-color: #f2f2f2; /* 灰色 */ + background-color: #f2f2f2; + /* 灰色 */ } tr:nth-child(even) { @@ -171,4 +183,4 @@ ol { border-radius: 2px; vertical-align: super; line-height: 1; -} +} \ No newline at end of file diff --git a/studio/src/pages/Home/index.tsx b/studio/src/pages/Home/index.tsx index c34d7a0..8efaf76 100644 --- a/studio/src/pages/Home/index.tsx +++ b/studio/src/pages/Home/index.tsx @@ -184,16 +184,16 @@ const HomePage: React.FC = () => { console.log('Search:', value); if (value && name) { - history.push(`/knowledge-table?nodeId=${value}&nodeName=${name}`); + history.push(`/predict-explain/knowledge-table?nodeId=${value}&nodeName=${name}`); return; } const filtered = filter(nodeOptions, (item) => item.value === value); if (filtered.length === 0 || !filtered[0]?.metadata) { - history.push(`/knowledge-table?nodeId=${value}`); + history.push(`/predict-explain/knowledge-table?nodeId=${value}`); } else { const metadata = filtered[0].metadata; - history.push(`/knowledge-table?nodeId=${value}&nodeName=${metadata.name}`); + history.push(`/predict-explain/knowledge-table?nodeId=${value}&nodeName=${metadata.name}`); } }; @@ -236,7 +236,7 @@ const HomePage: React.FC = () => {

Enter a gene/protein, disease, drug or symptom name to find and explain related known knowledges in our platform.
- If you want to predict new knowledges, please go to the { history.push('/predict-model'); }}>Predict Drug/Target page. + If you want to predict new knowledges, please go to the { history.push('/predict-explain/predict-model'); }}>Predict Drug/Target page.
Please click the following examples to see the results.

diff --git a/studio/src/pages/KnowledgeGraph/index.tsx b/studio/src/pages/KnowledgeGraph/index.tsx index 9808830..cbaca75 100644 --- a/studio/src/pages/KnowledgeGraph/index.tsx +++ b/studio/src/pages/KnowledgeGraph/index.tsx @@ -1,4 +1,4 @@ -import { getUsername, logoutWithRedirect } from '@/components/util'; +import { isAuthenticated, logoutWithRedirect } from '@/components/util'; import { Row, Col, Button, message as AntMessage, Empty } from 'antd'; import { KnowledgeGraph } from 'biominer-components'; import React, { useEffect, useState, memo, Suspense } from 'react'; @@ -22,13 +22,10 @@ const KnowledgeGraphWithChatBot: React.FC = () => { const [chatBoxVisible, setChatBoxVisible] = useState(false) const [span, setSpan] = useState(kgFullSpan) const ChatBox = React.lazy(() => import('@/components/ChatBox')); - const [username, setUsername] = useState(undefined); useEffect(() => { - const username = getUsername(); - setUsername(username); - - if (!username) { + console.log("isAuthenticated in KnowledgeGraph: ", isAuthenticated()); + if (!isAuthenticated()) { logoutWithRedirect(); } }, []) @@ -41,7 +38,7 @@ const KnowledgeGraphWithChatBot: React.FC = () => { } }, [chatBoxVisible]) - return username && + return isAuthenticated() && { chatBoxVisible ? ( diff --git a/studio/src/pages/KnowledgeTable/index.tsx b/studio/src/pages/KnowledgeTable/index.tsx index 26686c0..f245891 100644 --- a/studio/src/pages/KnowledgeTable/index.tsx +++ b/studio/src/pages/KnowledgeTable/index.tsx @@ -12,7 +12,7 @@ import type { EdgeInfo } from '@/EdgeInfoPanel/index.t'; import NodeInfoPanel from '@/NodeInfoPanel'; import EdgeInfoPanel from '@/EdgeInfoPanel'; import { sortBy, filter, uniqBy, groupBy, map, sumBy, set } from 'lodash'; -import { guessColor, truncateString, getUsername, logoutWithRedirect } from '@/components/util'; +import { guessColor, truncateString, getUsername, logoutWithRedirect, isAuthEnabled, isAuthenticated } from '@/components/util'; import EntityCard from '@/components/EntityCard'; import './index.less'; @@ -134,14 +134,9 @@ const KnowledgeTable: React.FC = (props) => { const [page, setPage] = useState(1); const [pageSize, setPageSize] = useState(30); const [refreshKey, setRefreshKey] = useState(0); - const [username, setUsername] = useState(undefined); useEffect(() => { - const username = getUsername(); - console.log('Username: ', username); - setUsername(username); - - if (!username) { + if (!isAuthenticated()) { logoutWithRedirect(); } else { @@ -211,7 +206,7 @@ const KnowledgeTable: React.FC = (props) => { edges: selectedEdges, }; pushGraphDataToLocalStorage(selectedGraphData); - history.push('/knowledge-graph'); + history.push('/predict-explain/knowledge-graph'); } const getKnowledgesData = ( @@ -652,16 +647,16 @@ const KnowledgeTable: React.FC = (props) => { const getTitle = (node: GraphNode) => { return - {node?.nlabel} Card - {node?.data.name} - + {node?.nlabel} Card - {node?.data.name} [Click to show more details] + {/* - + */} ; } - return username && (total == 0 ? ( + return (isAuthenticated()) && (total == 0 ? ( = (props) => { {currentNodes.length > 0 && currentNodes.map((node, index) => { return node?.nlabel == 'Gene' ? + extra={getExtraButton(index.toString())} + > : null; })} diff --git a/studio/src/pages/ModelConfig/index.tsx b/studio/src/pages/ModelConfig/index.tsx index 9551684..6b5fcc9 100644 --- a/studio/src/pages/ModelConfig/index.tsx +++ b/studio/src/pages/ModelConfig/index.tsx @@ -15,7 +15,7 @@ import { fetchStatistics } from '@/services/swagger/KnowledgeGraph'; import { makeRelationTypes } from 'biominer-components/dist/utils'; import type { OptionType, RelationStat, ComposeQueryItem, QueryItem, GraphEdge, GraphNode } from 'biominer-components/dist/typings'; import EntityCard from '@/components/EntityCard'; -import { truncateString, getUsername, logoutWithRedirect } from '@/components/util'; +import { truncateString, getUsername, logoutWithRedirect, isAuthenticated } from '@/components/util'; import './index.less'; @@ -248,13 +248,10 @@ const ModelConfig: React.FC = (props) => { const [nodeDataSources, setNodeDataSources] = useState([]); const [relationTypeOptions, setRelationTypeOptions] = useState([]); const [relationStat, setRelationStat] = useState([]); - const [username, setUsername] = useState(undefined); useEffect(() => { - const username = getUsername(); - setUsername(username); - - if (!username) { + console.log("isAuthenticated in ModelConfig: ", isAuthenticated()); + if (!isAuthenticated()) { logoutWithRedirect(); } else { fetchStatistics().then((data) => { @@ -881,7 +878,7 @@ const ModelConfig: React.FC = (props) => { } return ( - username && + isAuthenticated() && {models.map((model, index) => ( @@ -956,7 +953,7 @@ const ModelConfig: React.FC = (props) => { setLoading(false); if (d && d.nodes && d.nodes.length > 0) { pushGraphDataToLocalStorage(d); - history.push('/knowledge-graph'); + history.push('/predict-explain/knowledge-graph'); } else { message.warning("Cannot find an attention subgraph for explaining the predicted relation.", 5) } @@ -970,7 +967,7 @@ const ModelConfig: React.FC = (props) => { console.log('onLoadGraph: ', graph); if (graph && graph.nodes && graph.nodes.length > 0) { pushGraphDataToLocalStorage(graph); - history.push('/knowledge-graph'); + history.push('/predict-explain/knowledge-graph'); } else { message.warning("You need to generate some predicted result and pick up the interested rows first.", 5) } diff --git a/studio/src/wrappers/param.tsx b/studio/src/wrappers/param.tsx index 1d59bd4..fe177f5 100644 --- a/studio/src/wrappers/param.tsx +++ b/studio/src/wrappers/param.tsx @@ -1,5 +1,5 @@ import { Navigate, Outlet } from 'umi' export default (props: any) => { - return ; + return ; } \ No newline at end of file