diff --git a/app/components/Header.tsx b/app/components/Header.tsx index 8a930e3..5a02425 100644 --- a/app/components/Header.tsx +++ b/app/components/Header.tsx @@ -1,54 +1,85 @@ -import React, { useState } from 'react'; +import React, { useState, useRef, useEffect } from 'react'; import Login from './Login'; interface HeaderProps { - onViewChange: (view: 'AI' | 'FAQ' | 'Documentation' | 'Credits') => void; // Include 'Credits' - showDivs: boolean; - toggleDivs: () => void; - showHistoryModelsToggle: boolean; - showToggle: boolean; + onViewChange: (view: 'AI' | 'FAQ' | 'Documentation' | 'Credits') => void; + showDivs: boolean; + toggleDivs: () => void; + showHistoryModelsToggle: boolean; + showToggle: boolean; } -const Header: React.FC<HeaderProps> = ({ onViewChange, showDivs, toggleDivs, showHistoryModelsToggle, showToggle }) => { - const [menuOpen, setMenuOpen] = useState(false); +const Header: React.FC<HeaderProps> = ({ + onViewChange, + showDivs, + toggleDivs, + showHistoryModelsToggle, + showToggle, +}) => { + const [menuOpen, setMenuOpen] = useState(false); + const dropdownRef = useRef<HTMLDivElement | null>(null); + const toggleRef = useRef<HTMLDivElement | null>(null); - const toggleMenu = () => { - setMenuOpen(!menuOpen); - }; + // Toggle menu state + const toggleMenu = () => { + setMenuOpen((prevMenuOpen) => !prevMenuOpen); + }; - const buttonClicked = (page: "AI" | "Documentation" | "FAQ" | "Credits") => { // Add 'Credits' to the options here - onViewChange(page); - toggleMenu(); - }; + // Handle button click + const buttonClicked = (page: "AI" | "Documentation" | "FAQ" | "Credits") => { + onViewChange(page); + setMenuOpen(false); // Close the menu when a button is clicked + }; - return ( - <> - <header> - <div className={`hamburger ${menuOpen ? "open" : ""}`} onClick={toggleMenu}> - <span></span> - <span></span> - <span></span> - </div> - <nav className={`nav-links ${menuOpen ? "active" : ""}`}> - <button onClick={() => buttonClicked("AI")} className="nav-btn">Chat</button> - <button onClick={() => buttonClicked("FAQ")} className="nav-btn">FAQ</button> - <button onClick={() => buttonClicked("Documentation")} className="nav-btn">Documentation</button> - <button onClick={() => buttonClicked("Credits")} className="nav-btn">Credits</button> {/* Add Credits tab */} - {showToggle && showHistoryModelsToggle && ( - <button onClick={toggleDivs} className="nav-btn"> - {showDivs ? 'Hide History/Models' : 'Show History/Models'} - </button> - )} - </nav> - <div className="header-button header-logo"> - {/* AI logo or text */} - </div> - <div className="login-button-container"> - <Login /> - </div> - </header> - </> - ); + // Effect to handle clicks outside of the dropdown + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + // Check if the click is outside both the dropdown and the hamburger menu + if ( + dropdownRef.current && + !dropdownRef.current.contains(event.target as Node) && + toggleRef.current && + !toggleRef.current.contains(event.target as Node) + ) { + setMenuOpen(false); + } + }; + + document.addEventListener('mousedown', handleClickOutside); + + return () => { + document.removeEventListener('mousedown', handleClickOutside); + }; + }, []); + + return ( + <> + <header> + {showToggle && showHistoryModelsToggle && ( + <button onClick={toggleDivs} className="nav-btn show-hide-btn"> + {showDivs ? 'Hide' : 'Show'} + </button> + )} + <nav ref={dropdownRef} className={`nav-links ${menuOpen ? "active" : ""}`}> + <button onClick={() => buttonClicked("AI")} className="nav-btn">Chat</button> + <button onClick={() => buttonClicked("FAQ")} className="nav-btn">FAQ</button> + <button onClick={() => buttonClicked("Documentation")} className="nav-btn">Documentation</button> + <button onClick={() => buttonClicked("Credits")} className="nav-btn">Credits</button> + </nav> + <div ref={toggleRef} className={`hamburger ${menuOpen ? "open" : ""}`} onClick={toggleMenu}> + <span></span> + <span></span> + <span></span> + </div> + <div className="header-logo"> + {/* AI logo or text */} + </div> + <div className="login-button-container"> + <Login /> + </div> + </header> + </> + ); }; export default Header; diff --git a/app/components/InputFrontend.tsx b/app/components/InputFrontend.tsx index 9589096..fec7713 100644 --- a/app/components/InputFrontend.tsx +++ b/app/components/InputFrontend.tsx @@ -47,6 +47,7 @@ const InputFrontend = React.forwardRef<HTMLDivElement, InputProps>( value={inputValue} onChange={handleInputChange} onKeyDown={handleKeyDown} + className='textInputField' /> <button type="button" onClick={handleSendClick} disabled={inputDisabled ? true : false}> <svg style={{ fill: "var(--text-color)" }} viewBox="0 0 512 512" width={20}><path d="M498.1 5.6c10.1 7 15.4 19.1 13.5 31.2l-64 416c-1.5 9.7-7.4 18.2-16 23s-18.9 5.4-28 1.6L284 427.7l-68.5 74.1c-8.9 9.7-22.9 12.9-35.2 8.1S160 493.2 160 480l0-83.6c0-4 1.5-7.8 4.2-10.8L331.8 202.8c5.8-6.3 5.6-16-.4-22s-15.7-6.4-22-.7L106 360.8 17.7 316.6C7.1 311.3 .3 300.7 0 288.9s5.9-22.8 16.1-28.7l448-256c10.7-6.1 23.9-5.5 34 1.4z" /></svg> diff --git a/app/components/Login.tsx b/app/components/Login.tsx index a2c0f7f..146e86c 100644 --- a/app/components/Login.tsx +++ b/app/components/Login.tsx @@ -79,12 +79,16 @@ const Login: React.FC = () => { // Function to handle account creation const handleCreateAccount = async () => { - const success = await createAccount(newAccountName, newAccountEmail, newAccountPassword); - if (success) { - alert('Account created successfully! You can now log in.'); - toggleSignUpPopup(); // Close sign-up popup + if (newAccountName !== "" && newAccountEmail !== "" && newAccountPassword !== "") { + const success = await createAccount(newAccountName, newAccountEmail, newAccountPassword); + if (success) { + alert('Account created successfully! You can now log in.'); + toggleSignUpPopup(); // Close sign-up popup + } else { + alert('Account creation failed. Please try again.'); + } } else { - alert('Account creation failed. Please try again.'); + alert('Account creation failed. Please do not leave any field empty.'); } }; diff --git a/app/components/settings/TextSettings.tsx b/app/components/settings/TextSettings.tsx index eca3071..d7ffdf1 100644 --- a/app/components/settings/TextSettings.tsx +++ b/app/components/settings/TextSettings.tsx @@ -22,6 +22,7 @@ const TextSetting: React.FC<TextSettingProps> = ({ label, value, setValue, type, value={value} // Set the current value onChange={handleTextChange} // Handle input changes placeholder={placeholder} // Set the placeholder text + className='textInputField' /> </div> ); diff --git a/app/styles/global.css b/app/styles/global.css index e3d771c..94ed660 100644 --- a/app/styles/global.css +++ b/app/styles/global.css @@ -66,3 +66,14 @@ input:hover { select{ background-color: var(--input-background-color); } + +h1, h2, h3, h4, p{ + color: var(--text-color); + font-family: var(--font-family); + font-weight: 500; +} + +p{ + font-weight: 400; + font-size: var(--font-size) +} diff --git a/app/styles/header.css b/app/styles/header.css index 9f30d31..5c3a2e5 100644 --- a/app/styles/header.css +++ b/app/styles/header.css @@ -1,4 +1,4 @@ -header{ +header { position: relative; padding: 0 20px; width: 100%; @@ -10,15 +10,16 @@ header{ z-index: 999; } -.hamburger{ +/* Hamburger button styles */ +.hamburger { position: absolute; left: 5vw; - display: none; + display: flex; /* Always show hamburger button */ flex-direction: column; cursor: pointer; } -.hamburger span{ +.hamburger span { width: 25px; height: 3px; background-color: white; @@ -26,43 +27,53 @@ header{ transition: 0.3s; } -.hamburger.open span:nth-child(1){ +.hamburger.open span:nth-child(1) { transform: rotate(45deg) translate(5px, 10px); } -.hamburger.open span:nth-child(2){ +.hamburger.open span:nth-child(2) { opacity: 0; } -.hamburger.open span:nth-child(3){ +.hamburger.open span:nth-child(3) { transform: rotate(-45deg) translate(5px, -10px); } - - -.nav-links{ +/* Navigation links (hidden in header, shown in dropdown) */ +.nav-links { + display: none; /* Default hidden */ position: absolute; - left: 1vw; - display: flex; - gap: 0.5vw; - width:max-content; - height: 100%; - align-items: center; + top: 10vh; /* Adjust as needed */ + left: 0; + background-color: var(--burger-menu-background-color); + width: 100%; + flex-direction: column; + align-items: flex-start; + padding: 10px; } -.nav-btn{ +.nav-links.active { + display: flex; /* Show when active */ + height: fit-content; +} + +.nav-btn { background-color: var(--input-button-color); border: none; font-size: 0.9em; - height: 50%; + height: 50px; /* Consistent height */ border-radius: 5px; - padding: 2px 15px; + padding: 10px; font-family: var(--font-family); + width: 100%; /* Full width */ + text-align: center; /* Center text */ + margin: 4px; } -.nav-btn:hover{ +.nav-btn:hover { background-color: var(--input-button-hover-color); } -.header-logo{ +/* Logo styles */ +.header-logo { width: 250px; height: 5vh; background-image: url(../../public/img/logo.png); @@ -73,7 +84,8 @@ header{ border: none; } -.login-button-container{ +/* Login button styles */ +.login-button-container { position: absolute; top: 0.1vh; right: 1vw; @@ -82,9 +94,9 @@ header{ align-items: center; } -.header-login-button{ +.header-login-button { height: 100%; - width:max-content; + width: max-content; font-size: var(--font-size); padding: 0.5vw 1vw; background-color: var(--input-button-color); @@ -101,4 +113,12 @@ header{ .header-login-button:hover { background-color: var(--input-button-hover-color); +} + +.show-hide-btn{ + width: fit-content; + align-self: left; + position: absolute; + left: 10vw; + cursor: pointer; } \ No newline at end of file diff --git a/app/styles/input.css b/app/styles/input.css index dad1d56..7e7456b 100644 --- a/app/styles/input.css +++ b/app/styles/input.css @@ -33,6 +33,11 @@ height: 7vh; } +.textInputField::placeholder { + color: var(--text-color); /* Change to desired placeholder color */ + opacity: 1; /* Ensures full opacity (optional) */ +} + .input input:focus { border-color: var(--input-button-hover-color); } diff --git a/app/styles/responsive.css b/app/styles/responsive.css index ae65dfd..938106e 100644 --- a/app/styles/responsive.css +++ b/app/styles/responsive.css @@ -22,7 +22,7 @@ width: 100vw; overflow: hidden; margin: 0; - padding: 1dvh 0 0 0 ; + padding: 1dvh 0 0 0; } /* Left panel styles */ @@ -100,20 +100,19 @@ margin: 0; } - .header-logo{ + .header-logo { position: relative; - margin-left: -40px; } - .hamburger.open{ + .hamburger.open { margin-top: 0.5vh; padding-left: 1vw; } - .nav-links{ - display: none; + .nav-links { + display: none; /* Hidden by default */ position: absolute; - top: 10vh; + top: 10vh; /* Adjust as needed */ left: 0; background-color: var(--burger-menu-background-color); width: 100%; @@ -123,24 +122,42 @@ height: fit-content; } - .nav-links.active{ - display: flex; + .nav-links.active { + display: flex; /* Show when active */ height: fit-content; } - .nav-btn{ + .nav-btn { width: 100%; text-align: center; padding: 10px; height: 50px; - } - - .hamburger { - display: flex; + background-color: var(--input-button-color); + border: none; + font-size: 0.9em; + border-radius: 5px; } - .header-login-button{ - right: 5vh; + .nav-btn:hover { + background-color: var(--input-button-hover-color); + } + + .hamburger { + display: flex; /* Always show hamburger button */ + } + + .header-login-button { + right: 5vh; /* Keep login button visible */ + } + + .show-hide-btn{ + width: fit-content; + left: 20vw; + } + + .header-logo { + background-image: url(../../public/img/logo-small.png); + width: 4em; } } diff --git a/deployment_scripts/linux/NOTE.txt b/deployment_scripts/linux/NOTE.txt new file mode 100644 index 0000000..537fc36 --- /dev/null +++ b/deployment_scripts/linux/NOTE.txt @@ -0,0 +1,3 @@ +on linux it is slightly easier + +i'd say to create a preparation script for dnf/yum and apt based systems, which basically downloads the dependencies via the package manager, and ollama via the usual script diff --git a/deployment_scripts/windows/NOTE.txt b/deployment_scripts/windows/NOTE.txt new file mode 100644 index 0000000..efcc53d --- /dev/null +++ b/deployment_scripts/windows/NOTE.txt @@ -0,0 +1,7 @@ +You will need to make three folders: + +node-bin - contains nodejs portable +python-bin - contains python3 portable - don't forget to add the .pth file and adjust it accordingly, because import site is normally missing. +ollama.bin - contains ollama portable + +you also need vc redist 2019 \ No newline at end of file diff --git a/deployment_scripts/windows/prepare.bat b/deployment_scripts/windows/prepare.bat new file mode 100644 index 0000000..163e99d --- /dev/null +++ b/deployment_scripts/windows/prepare.bat @@ -0,0 +1,3 @@ +start /b scripts\prepare_py.bat +start /b scripts\prepare_npm.bat +start /b scripts\prepare_ollama.bat \ No newline at end of file diff --git a/deployment_scripts/windows/python313._pth b/deployment_scripts/windows/python313._pth new file mode 100644 index 0000000..4b5ab01 --- /dev/null +++ b/deployment_scripts/windows/python313._pth @@ -0,0 +1,5 @@ +python313.zip +. + +# Uncomment to run site.main() automatically +import site diff --git a/deployment_scripts/windows/run.bat b/deployment_scripts/windows/run.bat new file mode 100644 index 0000000..93e1277 --- /dev/null +++ b/deployment_scripts/windows/run.bat @@ -0,0 +1,3 @@ +start /b scripts\run_ollama.bat +start /b scripts\run_electron.bat +start /b scripts\run_py.bat \ No newline at end of file diff --git a/deployment_scripts/windows/scripts/prepare_npm.bat b/deployment_scripts/windows/scripts/prepare_npm.bat new file mode 100644 index 0000000..2fac437 --- /dev/null +++ b/deployment_scripts/windows/scripts/prepare_npm.bat @@ -0,0 +1,6 @@ +cd node-bin +set PATH=%PATH%;"%CD%"; +cd .. + +npm install +npm run build diff --git a/deployment_scripts/windows/scripts/prepare_ollama.bat b/deployment_scripts/windows/scripts/prepare_ollama.bat new file mode 100644 index 0000000..6dafabf --- /dev/null +++ b/deployment_scripts/windows/scripts/prepare_ollama.bat @@ -0,0 +1,19 @@ +cd ollama-bin +start /b ollama.exe serve +timeout 5 +ollama.exe pull phi3.5 +ollama.exe pull qwen2-math:1.5b +ollama.exe pull starcoder2 +ollama.exe pull llava-phi3 +ollama.exe pull qwen2.5-coder:1.5b +ollama.exe pull starcoder2:7b +ollama.exe pull wizard-math +ollama.exe pull llama3.1 +ollama.exe pull llama3.2 +ollama.exe pull dolphin-phi +ollama.exe pull dolphin-llama3 +ollama.exe pull dolphin-mistral +ollama.exe pull llava +ollama.exe pull qwen2.5 +ollama.exe pull mathstral +ollama.exe pull qwen2.5-coder \ No newline at end of file diff --git a/deployment_scripts/windows/scripts/prepare_py.bat b/deployment_scripts/windows/scripts/prepare_py.bat new file mode 100644 index 0000000..ab23e50 --- /dev/null +++ b/deployment_scripts/windows/scripts/prepare_py.bat @@ -0,0 +1,12 @@ +cd python-bin +set PATH=%PATH%;"%CD%"; +curl -O https://bootstrap.pypa.io/get-pip.py +ren python py +py get-pip.py +cd Scripts +set PATH=%PATH%;"%CD%"; +cd .. +cd .. + +cd py +py -m pip install -r requirements.txt \ No newline at end of file diff --git a/deployment_scripts/windows/scripts/run_electron.bat b/deployment_scripts/windows/scripts/run_electron.bat new file mode 100644 index 0000000..3a97ac3 --- /dev/null +++ b/deployment_scripts/windows/scripts/run_electron.bat @@ -0,0 +1,6 @@ +cd node-bin +set PATH=%PATH%;"%CD%"; +cd .. + +start /b npm start +npx electron . diff --git a/deployment_scripts/windows/scripts/run_ollama.bat b/deployment_scripts/windows/scripts/run_ollama.bat new file mode 100644 index 0000000..87d1459 --- /dev/null +++ b/deployment_scripts/windows/scripts/run_ollama.bat @@ -0,0 +1,2 @@ +cd ollama-bin +start /b ollama.exe serve \ No newline at end of file diff --git a/deployment_scripts/windows/scripts/run_py.bat b/deployment_scripts/windows/scripts/run_py.bat new file mode 100644 index 0000000..d54c44e --- /dev/null +++ b/deployment_scripts/windows/scripts/run_py.bat @@ -0,0 +1,9 @@ +cd python-bin +set PATH=%PATH%;"%CD%"; +cd Scripts +set PATH=%PATH%;"%CD%"; +cd .. +cd .. + +cd py +python api.py \ No newline at end of file diff --git a/public/img/logo-small.png b/public/img/logo-small.png new file mode 100644 index 0000000..bc31fbd Binary files /dev/null and b/public/img/logo-small.png differ