diff --git a/src/frontend/components/buttons/sidebar-menu-navigation-button.tsx b/src/frontend/components/buttons/sidebar-menu-navigation-button.tsx
new file mode 100644
index 0000000..a69ddaf
--- /dev/null
+++ b/src/frontend/components/buttons/sidebar-menu-navigation-button.tsx
@@ -0,0 +1,48 @@
+import Styled from 'styled-components';
+import { Link, useLocation } from 'react-router-dom';
+
+interface ButtonProps {
+ active: 'active' | '';
+}
+
+interface MainNavButtonProps {
+ text: string;
+ href: string;
+ exact: boolean;
+}
+
+const SidebarMenuNavigationButton = ({ text, href, exact }: MainNavButtonProps) => {
+ const path = useLocation();
+ const active = exact ? path.pathname === href : path.pathname.includes(href);
+
+ return (
+
+ {text}
+
+ );
+};
+
+const MenuNavItem = {
+ Wrapper: Styled(Link)`
+ display: flex;
+ flex-direction: column;
+ padding: 24px 0 24px 12px;
+ color: ${(props) => (props.active === 'active' ? props.theme.colors.black : props.theme.colors.balance)};
+ text-decoration: none;
+ background-color: ${(props) => props.active && props.theme.colors.balance};
+ &:hover span {
+ color: ${(props) => !props.active && props.theme.colors.gray};
+ }
+ `,
+ Icon: Styled.img`
+ display: flex;
+ width: 30px;
+ height: 30px;
+ `,
+ ButtonText: Styled.span`
+ font-family: ${(props) => props.theme.fonts.family.primary.regular};
+ font-size: ${(props) => props.theme.fonts.size.medium};
+ `,
+};
+
+export default SidebarMenuNavigationButton;
diff --git a/src/frontend/components/dropdowns/network-selection-dropdown.tsx b/src/frontend/components/dropdowns/network-selection-dropdown.tsx
new file mode 100644
index 0000000..fea98c8
--- /dev/null
+++ b/src/frontend/components/dropdowns/network-selection-dropdown.tsx
@@ -0,0 +1,84 @@
+import React from 'react';
+import Styled from 'styled-components';
+
+import { getChainInfoByChainId } from '../../utils/chain';
+import { useSDK } from '@metamask/sdk-react';
+import { useAIMessagesContext } from '../../contexts';
+
+const NetworkSelectionDropdown = () => {
+ const { provider, account } = useSDK();
+ const [dialogueEntries, setDialogueEntries] = useAIMessagesContext();
+
+ const handleNetworkChange = async (e: React.ChangeEvent) => {
+ const selectedChain = e.target.value;
+
+ // Check if the default option is selected
+ if (!selectedChain) {
+ console.log('No network selected.');
+ return; // Early return to avoid further execution
+ }
+
+ // Sanity Checks:
+ if (!account || !provider) {
+ const errorMessage = `Error: Please connect to MetaMask`;
+ setDialogueEntries([
+ ...dialogueEntries,
+ { question: '', answer: errorMessage, answered: true },
+ ]);
+ return;
+ }
+
+ try {
+ const response = await provider.request({
+ method: 'wallet_switchEthereumChain',
+ params: [{ chainId: selectedChain }],
+ });
+ console.log(response);
+ } catch (error) {
+ //if switch chain fails then add the chain
+ try {
+ const chainInfo = getChainInfoByChainId(selectedChain);
+ const response = await provider.request({
+ method: 'wallet_addEthereumChain',
+ params: [chainInfo],
+ });
+ } catch (error) {
+ console.error('Failed to switch networks:', error);
+ }
+ }
+ };
+
+ return (
+
+
+
+
+
+
+
+ );
+};
+
+const Dropdown = Styled.select`
+ padding: 8px 10px;
+ margin: 12px 0 12px 12px;
+
+ border-radius: 10px;
+ background-color: ${(props) => props.theme.colors.core};
+ color: ${(props) => props.theme.colors.notice};
+ border: 2px solid ${(props) => props.theme.colors.hunter};
+ font-family: ${(props) => props.theme.fonts.family.primary.regular};
+ font-size: ${(props) => props.theme.fonts.size.small};
+ cursor: pointer;
+
+ &:hover {
+ border: 2px solid ${(props) => props.theme.colors.emerald};
+ }
+
+ option {
+ background-color: ${(props) => props.theme.colors.core};
+ color: ${(props) => props.theme.colors.emerald};
+ }
+`;
+
+export default NetworkSelectionDropdown;
diff --git a/src/frontend/components/layout/main.tsx b/src/frontend/components/layout/main.tsx
index 3d1f9df..03427ba 100644
--- a/src/frontend/components/layout/main.tsx
+++ b/src/frontend/components/layout/main.tsx
@@ -9,18 +9,25 @@ import BottomBar from './bottom-bar';
// router
import { MainRouter } from '../../router';
+import SidebarComponent from './sidebar';
+import SidebarToggle from './sidebar-toggle';
+
export default () => {
return (
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
);
};
@@ -28,12 +35,19 @@ export default () => {
const Main = {
Layout: Styled.div`
display: flex;
- flex-direction: column;
align-items: center;
width: 100%;
height: 100%;
background: ${(props) => props.theme.colors.core};
`,
+ Content: Styled.div`
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ flex-grow: 1;
+ height: 100%;
+ background: ${(props) => props.theme.colors.core};
+ `,
TopWrapper: Styled.div`
display: flex;
width: 100%;
@@ -48,6 +62,7 @@ const Main = {
border-radius: 30px;
border: 5px solid ${(props) => props.theme.colors.hunter};
padding: 10px;
+ overflow: hidden;
`,
BottomWrapper: Styled.div`
display: flex;
diff --git a/src/frontend/components/layout/sidebar-toggle.tsx b/src/frontend/components/layout/sidebar-toggle.tsx
new file mode 100644
index 0000000..84df333
--- /dev/null
+++ b/src/frontend/components/layout/sidebar-toggle.tsx
@@ -0,0 +1,46 @@
+// libs
+import React from 'react';
+import Styled from 'styled-components';
+import { useSidebarContext } from '../../contexts';
+
+export default () => {
+ const { isOpen, setIsOpen } = useSidebarContext();
+ return (
+
+ {
+ setIsOpen((isOpen) => !isOpen);
+ }}
+ isOpen={isOpen}
+ />
+
+ );
+};
+
+type SidebarToggleCarat = {
+ isOpen: boolean;
+};
+
+const SidebarToggle = {
+ Layout: Styled.div`
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 40px;
+ height: 100%;
+ background: ${(props) => props.theme.colors.core};
+ padding-left: 8px;
+ `,
+ Carat: Styled.div`
+ display: inline-block;
+ width: 20px;
+ height: 20px;
+ border-top: 2px solid ${(props) => props.theme.colors.emerald};
+ border-left: 2px solid ${(props) => props.theme.colors.emerald};
+ transform: rotate(${(props) => (props.isOpen ? '-45deg' : '135deg')});
+ &:hover {
+ cursor: pointer;
+ opacity: 0.8;
+ }
+ `,
+};
diff --git a/src/frontend/components/layout/sidebar.tsx b/src/frontend/components/layout/sidebar.tsx
new file mode 100644
index 0000000..8b51672
--- /dev/null
+++ b/src/frontend/components/layout/sidebar.tsx
@@ -0,0 +1,78 @@
+import Styled from 'styled-components';
+
+import Logo from './../../assets/images/logo_white.png';
+import SidebarMenuNavigationButton from '../buttons/sidebar-menu-navigation-button';
+import NetworkSelectionDropdown from '../dropdowns/network-selection-dropdown';
+import { useSidebarContext } from '../../contexts';
+
+export default (): JSX.Element => {
+ const { isOpen } = useSidebarContext();
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ MorpheusAI.org
+
+ );
+};
+
+type SidebarLayoutProps = {
+ isOpen: boolean;
+};
+
+const Sidebar = {
+ Layout: Styled.div`
+ width: ${(props) => (props.isOpen ? '250px' : '0px')};
+ height: 100%;
+ background: black;
+ display: flex;
+ flex-direction: column;
+ flex-shrink: 0;
+ transition: 0.2s;
+ `,
+ Top: Styled.div`
+ height: 50px;
+ display: flex;
+ justify-content: center;
+ flex-shrink: 0;
+ `,
+ Logo: Styled.img`
+ height: 50px;
+ width: 50px;
+ `,
+ Menu: Styled.div`
+ flex-grow: 1;
+ overflow-y: auto;
+ `,
+ MenuItem: Styled.div`
+ display: flex;
+ align-items: center;
+ `,
+
+ Bottom: Styled.div`
+ height: 50px;
+ flex-shrink: 0;
+ color: ${(props) => props.theme.colors.balance};
+ font-family: ${(props) => props.theme.fonts.family.primary.regular};
+ font-size: ${(props) => props.theme.fonts.size.small};
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ `,
+ SeparatorLine: Styled.div`
+ height: 1px;
+ background: ${(props) => props.theme.colors.gray};
+ `,
+};
diff --git a/src/frontend/components/layout/top-bar.tsx b/src/frontend/components/layout/top-bar.tsx
index 92035df..345fa39 100644
--- a/src/frontend/components/layout/top-bar.tsx
+++ b/src/frontend/components/layout/top-bar.tsx
@@ -69,7 +69,7 @@ export default () => {
*/}
-
+ {/* */}
Morpheus
@@ -150,11 +150,6 @@ const TopBar = {
height: 25px;
cursor: pointer;
`,
- Logo: Styled.img`
- display: flex;
- height: 100px;
- width: 100px;
- `,
Header: Styled.h2`
font-size: ${(props) => props.theme.fonts.size.medium};
font-family: ${(props) => props.theme.fonts.family.primary.regular};
diff --git a/src/frontend/contexts.tsx b/src/frontend/contexts.tsx
index 8645e3b..78f1198 100644
--- a/src/frontend/contexts.tsx
+++ b/src/frontend/contexts.tsx
@@ -1,6 +1,13 @@
/* eslint-disable @typescript-eslint/no-empty-function */
/* eslint-disable @typescript-eslint/no-unused-vars */
-import { createContext, PropsWithChildren, useContext, useState } from 'react';
+import {
+ createContext,
+ PropsWithChildren,
+ useContext,
+ useState,
+ SetStateAction,
+ Dispatch,
+} from 'react';
import { AIMessage } from './types';
export type AIMessagesContextType = [Array, (messages: Array) => void];
@@ -29,3 +36,38 @@ export const useAIMessagesContext = () => {
return context;
};
+
+export type SidebarContextType = {
+ isOpen: boolean;
+ setIsOpen: Dispatch>;
+};
+
+export const SidebarContext = createContext({
+ isOpen: false,
+ setIsOpen: (isOpen: boolean) => {},
+});
+
+export const SidebarProvider = ({ children }: PropsWithChildren) => {
+ const [isOpen, setIsOpen] = useState(false);
+
+ return (
+
+ {children}
+
+ );
+};
+
+export const useSidebarContext = () => {
+ const context = useContext(SidebarContext);
+
+ if (!context) {
+ throw new Error(`useSidebarContext must be used within SidebarProvider`);
+ }
+
+ return context;
+};
diff --git a/src/frontend/index.tsx b/src/frontend/index.tsx
index 1970e9d..b8b424e 100644
--- a/src/frontend/index.tsx
+++ b/src/frontend/index.tsx
@@ -18,7 +18,7 @@ import GlobalStyle from './theme/index';
import './index.css';
// context
-import { AIMessagesProvider } from './contexts';
+import { AIMessagesProvider, SidebarProvider } from './contexts';
// constants
import { LOGO_METAMASK_BASE64 } from './constants';
@@ -96,68 +96,70 @@ const AppRoot = () => {
return (
-
- {
- let modalContainer: HTMLElement;
-
- return {
- mount: () => {
- if (modalContainer) return;
-
- modalContainer = document.createElement('div');
-
- modalContainer.id = 'meta-mask-modal-container';
-
- document.body.appendChild(modalContainer);
-
- ReactDOM.render(
- {
- ReactDOM.unmountComponentAtNode(modalContainer);
- modalContainer.remove();
- }}
- />,
- modalContainer,
- );
-
- setTimeout(() => {
- updateQrCode(link);
- }, 100);
- },
- unmount: () => {
- if (modalContainer) {
- ReactDOM.unmountComponentAtNode(modalContainer);
-
- modalContainer.remove();
- }
- },
- };
+
+
+
- {!isInitialized && }
- {isInitialized && }
- {/* {modelsPathFetched && !isModelsPathSet && await handleSelectFolderClicked()} />} */}
-
-
+ communicationServerUrl: 'https://metamask-sdk-socket.metafi.codefi.network/',
+ checkInstallationImmediately: false,
+ i18nOptions: {
+ enabled: true,
+ },
+ dappMetadata: {
+ name: 'Morpheus Node',
+ url: 'https://mor.org',
+ base64Icon: LOGO_METAMASK_BASE64,
+ },
+ modals: {
+ install: ({ link }) => {
+ let modalContainer: HTMLElement;
+
+ return {
+ mount: () => {
+ if (modalContainer) return;
+
+ modalContainer = document.createElement('div');
+
+ modalContainer.id = 'meta-mask-modal-container';
+
+ document.body.appendChild(modalContainer);
+
+ ReactDOM.render(
+ {
+ ReactDOM.unmountComponentAtNode(modalContainer);
+ modalContainer.remove();
+ }}
+ />,
+ modalContainer,
+ );
+
+ setTimeout(() => {
+ updateQrCode(link);
+ }, 100);
+ },
+ unmount: () => {
+ if (modalContainer) {
+ ReactDOM.unmountComponentAtNode(modalContainer);
+
+ modalContainer.remove();
+ }
+ },
+ };
+ },
+ },
+ }}
+ >
+ {!isInitialized && }
+ {isInitialized && }
+ {/* {modelsPathFetched && !isModelsPathSet && await handleSelectFolderClicked()} />} */}
+
+
+
);
diff --git a/src/frontend/router.tsx b/src/frontend/router.tsx
index c22cea9..d5c1f99 100644
--- a/src/frontend/router.tsx
+++ b/src/frontend/router.tsx
@@ -5,15 +5,18 @@ import Styled from 'styled-components';
// views
// import HomeView from './views/home';
-import SettingsView from './views/settings';
+import UnderConstruction from './views/underConstruction';
import ChatView from './views/chat';
export const RoutesWrapper = () => {
return (
- } />
-
+
+
+
+
+
);
};
diff --git a/src/frontend/theme/theme.ts b/src/frontend/theme/theme.ts
index 0f0feb2..8865de5 100644
--- a/src/frontend/theme/theme.ts
+++ b/src/frontend/theme/theme.ts
@@ -5,6 +5,8 @@ export interface ITheme {
hunter: string;
notice: string;
balance: string;
+ black: string;
+ gray: string;
};
layout: {
topBarHeight: number;
@@ -37,6 +39,8 @@ const common = {
hunter: '#106F48',
notice: '#FDB366',
balance: '#FFFFFF',
+ black: '#000000',
+ gray: '#B0B0B0',
},
layout: {
topBarHeight: 130,
diff --git a/src/frontend/views/chat.tsx b/src/frontend/views/chat.tsx
index 37c8b5a..95d416e 100644
--- a/src/frontend/views/chat.tsx
+++ b/src/frontend/views/chat.tsx
@@ -233,13 +233,13 @@ const ChatView = (): JSX.Element => {
return (
-
+ {/*
-
+ */}
{dialogueEntries.map((entry, index) => {
return (
diff --git a/src/frontend/views/settings.tsx b/src/frontend/views/underConstruction.tsx
similarity index 94%
rename from src/frontend/views/settings.tsx
rename to src/frontend/views/underConstruction.tsx
index 02c14bf..8978657 100644
--- a/src/frontend/views/settings.tsx
+++ b/src/frontend/views/underConstruction.tsx
@@ -5,7 +5,7 @@ import Styled from 'styled-components';
// helpers
// import { truncateString } from '../helpers';
-const SettingsView = (): JSX.Element => {
+const UnderConstructionView = (): JSX.Element => {
// const { ready, sdk, connected, connecting, provider, chainId, account, balance } =
// useSDK();
@@ -53,4 +53,4 @@ const Settings = {
`,
};
-export default SettingsView;
+export default UnderConstructionView;