diff --git a/app/Conversation.tsx b/app/Conversation.tsx new file mode 100644 index 0000000..1caa836 --- /dev/null +++ b/app/Conversation.tsx @@ -0,0 +1,43 @@ +import React, { ForwardedRef } from 'react'; + +interface ConversationProps { + messages: string[]; + onResendClick: () => void; + onEditClick: () => void; + onCopyClick: () => void; +} + +const Conversation = React.forwardRef( + ({ messages, onResendClick, onEditClick, onCopyClick }, ref: ForwardedRef) => { + return ( +
+
+ {messages.map((message, index) => { + const isUserMessage = message.startsWith('User:'); + return ( +
+ {message} +
+ ); + })} +
+ + + +
+
+
+ ); + } +); + +export default Conversation; diff --git a/app/Header.tsx b/app/Header.tsx new file mode 100644 index 0000000..7b4c1c6 --- /dev/null +++ b/app/Header.tsx @@ -0,0 +1,36 @@ +// Header.tsx +import React from 'react'; + +interface HeaderProps { + toggleDivs: () => void; + showDivs: boolean; +} + +const Header: React.FC = ({ toggleDivs, showDivs }) => { + return ( +
+
+
    +
  • + + logo + +
  • +
  • + Documentation +
  • +
  • + FAQ +
  • +
  • + +
  • +
+
+
+ ); +}; + +export default Header; diff --git a/app/History.tsx b/app/History.tsx new file mode 100644 index 0000000..cd93950 --- /dev/null +++ b/app/History.tsx @@ -0,0 +1,20 @@ +import React from 'react'; + +const History: React.FC = () => { + return ( +
+
+
    + {/* Populate with history items */} + {Array.from({ length: 20 }, (_, index) => ( +
  • + history{index + 1} +
  • + ))} +
+
+
+ ); +}; + +export default History; diff --git a/app/InputForm.tsx b/app/InputForm.tsx new file mode 100644 index 0000000..6abfc64 --- /dev/null +++ b/app/InputForm.tsx @@ -0,0 +1,21 @@ +import React from 'react'; + +const InputForm: React.FC = () => { + return ( +
+ + + +
+ ); +}; + +export default InputForm; diff --git a/app/Models.tsx b/app/Models.tsx new file mode 100644 index 0000000..ee6739b --- /dev/null +++ b/app/Models.tsx @@ -0,0 +1,88 @@ +// Models.tsx +import React from 'react'; + +const Models: React.FC = () => { + return ( +
+
+
+

Different AI models

+
+
+
+ + + + + {/* Example Models */} + + + + + + + + + +
+
+
+
+ ); +}; + +export default Models; diff --git a/app/backend/ai_api.ts b/app/backend/ai_api.ts new file mode 100644 index 0000000..5cafa54 --- /dev/null +++ b/app/backend/ai_api.ts @@ -0,0 +1,12 @@ +import ollama from 'ollama' + +async function name(model: string, prompt: string, system: string,) { + var message = [{ role: 'user', content: prompt }, { role: 'system', content: system }] + var response = await ollama.chat({ model: model, messages: message, stream: true }) + for await (const part of response) { + process.stdout.write(part.message.content) + } +} + + + diff --git a/app/layout.tsx b/app/layout.tsx new file mode 100644 index 0000000..3bf71eb --- /dev/null +++ b/app/layout.tsx @@ -0,0 +1,20 @@ +export const metadata = { + title: 'Next.js', + description: 'Generated by Next.js', +} + +export default function RootLayout({ + children, +}: { + children: React.ReactNode +}) { + return ( + + + + AI Assistant + + {children} + + ) +} diff --git a/app/page.tsx b/app/page.tsx index e69de29..a53edbb 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -0,0 +1,89 @@ +"use client" +// LandingPage.tsx +import React, { useState, useEffect, useRef } from 'react'; +import Header from './Header'; +import History from './History'; +import Models from './Models'; +import Conversation from './Conversation'; +import InputForm from './InputForm'; +import './styles/master.css'; + +const LandingPage: React.FC = () => { + const [showDivs, setShowDivs] = useState(true); + const conversationRef = useRef(null); + + useEffect(() => { + const scrollToBottom = () => { + const conversation = conversationRef.current; + if (conversation) { + conversation.scrollTop = conversation.scrollHeight; + } + }; + + scrollToBottom(); + + const observer = new MutationObserver(scrollToBottom); + if (conversationRef.current) { + observer.observe(conversationRef.current, { + childList: true, + subtree: true, + }); + } + + return () => { + if (conversationRef.current) { + observer.disconnect(); + } + }; + }, []); + + const toggleDivs = () => { + setShowDivs(prevState => !prevState); + }; + + const messages = [ + 'User: Hello!', + 'AI: Hi there!', + 'User: How are you?', + 'AI: I’m good, thank you!' + ]; + + const handleResendClick = () => { + console.log('Resend button clicked'); + // Handle resend action + }; + + const handleEditClick = () => { + console.log('Edit button clicked'); + // Handle edit action + }; + + const handleCopyClick = () => { + console.log('Copy button clicked'); + // Handle copy action + }; + + return ( +
+
+
+
+ + +
+
+ + +
+
+
+ ); +}; + +export default LandingPage; diff --git a/app/styles/container.css b/app/styles/container.css new file mode 100644 index 0000000..0c7316c --- /dev/null +++ b/app/styles/container.css @@ -0,0 +1,34 @@ +/* container.css */ +.container { + display: flex; + height: 100vh; + width: 100vw; + overflow: hidden; + position: relative; /* Ensure relative positioning for proper transitions */ +} + +.left-panel { + width: 30vw; /* Adjust as needed */ + transition: width 0.3s ease, visibility 0.3s ease; /* Transition for width, opacity, and visibility */ +} + +.left-panel.hidden { + opacity: 0; /* Fade out when hidden */ + width: 0; /* Set width to 0 when hidden */ + visibility: hidden; /* Hide the panel while keeping the space occupied */ + margin-right: -30vw; +} + +.conversation-container { + flex: 1; + transition: width 0.3s ease, visibility 0.3s ease; /* Transition for margin-left */ +} + +/* Adjust margin-left when panel is shown or hidden */ +.conversation-container.expanded { + margin-left: 0; +} + +.conversation-container.collapsed { + margin-left: 30vw; /* Same as the width of the left-panel */ +} diff --git a/app/styles/faq.css b/app/styles/faq.css new file mode 100644 index 0000000..e69de29 diff --git a/app/styles/global.css b/app/styles/global.css new file mode 100644 index 0000000..2a2b956 --- /dev/null +++ b/app/styles/global.css @@ -0,0 +1,18 @@ +html, +body { + height: 100vh; + overflow: hidden; + position: relative; + top: 1vh; +} + +body { + margin-top: 2em; + display: flex; + justify-content: center; + align-items: center; + background-color: var(--background-color); + color: var(--text-color); + font-family: var(--font-family); + margin-bottom: 0.5em; +} diff --git a/app/styles/header.css b/app/styles/header.css new file mode 100644 index 0000000..8ecc019 --- /dev/null +++ b/app/styles/header.css @@ -0,0 +1,38 @@ +header { + background-color: var(--background-color); + color: black; + width: 100%; + text-decoration: none; + position: fixed; + top: 0; + left: 0; + padding: 10px 20px; + box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2); + z-index: 1000; + font-family: var(--font-family); +} + +header li { + display: inline-block; + margin: 0 15px; +} + +header img { + height: 2em; + vertical-align: middle; +} + +header a, +header li button { + color: black; + text-decoration: none; + transition: color 0.3s; + border: none; + background-color: transparent; + font-size: 1em; +} + +header a:hover, +header li button:hover { + color: var(--input-button-color); +} diff --git a/app/styles/history.css b/app/styles/history.css new file mode 100644 index 0000000..2aaa58d --- /dev/null +++ b/app/styles/history.css @@ -0,0 +1,38 @@ +.history-background { + grid-column: 1/2; + grid-row: 1/2; + height: 40vh; + overflow: hidden; + background-color: var(--history-background-color); + padding: 1em; + border-radius: 2em; + margin: 1em; +} + +.history { + height: 100%; + overflow-y: scroll; + padding-right: 10px; +} + +.history ul { + list-style: none; +} + +.history ul li { + padding: 10px 0; + border-bottom: 1px solid var(--text-color); + width: 100%; +} + +.history ul li a { + display: block; + text-decoration: none; + color: white; + width: 100%; + padding: 5px; +} + +.history ul li a:hover { + background-color: var(--input-button-hover-color); +} diff --git a/app/styles/input.css b/app/styles/input.css new file mode 100644 index 0000000..12b71e2 --- /dev/null +++ b/app/styles/input.css @@ -0,0 +1,61 @@ +/* Input Section */ +.input { + grid-column: 2/3; + grid-row: 4/5; + border-radius: 20px; + background-color: var(--input-background-color); + padding: 1em; + margin: 1em; + display: flex; + justify-content: space-between; + align-items: center; + height: auto; + gap: 10px; + height: 10vh; +} + +.input input { + flex-grow: 1; + padding: 5px; + font-size: 1.2em; + border-radius: 8px; + border: 2px solid var(--input-button-color); + outline: none; + margin-right: 10px; + background-color: rgba(255, 255, 255, 0.9); + color: #333; + transition: border-color 0.3s ease-in-out; + height: 7vh; +} + +.input input:focus { + border-color: var(--input-button-hover-color); +} + +.input button { + padding: 1em; + margin: 5px; + background-color: var(--input-button-color); + color: white; + border: none; + border-radius: 50%; + font-size: 1.5em; + cursor: pointer; + height: 50px; + width: 50px; + display: flex; + justify-content: center; + align-items: center; + transition: background-color 0.3s ease; + position: relative; + box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1); +} + +.input button img { + height: 1em; +} + +.input button:hover { + background-color: var(--input-button-hover-color); + box-shadow: 0 6px 15px rgba(0, 0, 0, 0.2); +} diff --git a/app/styles/master.css b/app/styles/master.css new file mode 100644 index 0000000..4b21879 --- /dev/null +++ b/app/styles/master.css @@ -0,0 +1,13 @@ +/* Importing all the split CSS files */ +@import './variables.css'; +@import './reset.css'; +@import './global.css'; +@import './header.css'; +@import './container.css'; +@import './history.css'; +@import './models.css'; +@import './output.css'; +@import './user-ai-messages.css'; +@import './input.css'; +@import './faq.css'; +@import './scrollbar.css'; diff --git a/app/styles/models.css b/app/styles/models.css new file mode 100644 index 0000000..18b6777 --- /dev/null +++ b/app/styles/models.css @@ -0,0 +1,124 @@ +.model-background { + grid-column: 1/2; + grid-row: 2/5; + overflow-y: auto; + background-color: var(--models-background-color); + border-radius: 2em; + padding: 1em; + margin: 1em; + height: 50vh; + box-sizing: border-box; + overflow: hidden; +} + +.models { + grid-column: 1/2; + grid-row: 2/5; + overflow-y: auto; + background-color: var(--models-background-color); + border-radius: 2em; + padding: 1em; + height: 100%; + box-sizing: border-box; + overflow: hidden; + overflow-y: scroll; +} + +.models form { + padding-right: 10px; + padding-left: 10px; + display: flex; + align-items: center; + justify-content: center; +} + +.models .titel { + padding-bottom: 1em; + display: flex; + justify-content: center; + align-items: center; +} + +.grid { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 1.5vh; + width: fit-content; +} + +.grid h3 { + font-size: x-large; +} + +.model-box { + display: flex; + align-items: center; + justify-content: center; + color: #fff; + border-radius: 5%; + overflow: hidden; + position: relative; + height: 18vh; + width: 18vh; +} + +.overlay { + z-index: 900; + position: absolute; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.7); + color: white; + display: flex; + justify-content: center; + align-items: center; + font-size: 300%; + transition: opacity 0.5s ease; + pointer-events: none; + opacity: 0; + font-size: xx-large; +} + +.overlay img { + align-self: flex-end; + justify-self: end; + height: 3vh; + width: 3vh; + position: absolute; + right: 15px; + bottom: 15px; +} + +.model-box:hover .overlay { + opacity: 1; +} + +.code-model { + background-image: url(/img/code.jpg); + background-repeat: no-repeat; + background-size: cover; +} + +.math-model { + background-image: url(/img/math.jpg); + background-color: white; + background-position: center; + background-repeat: no-repeat; + background-size: contain; +} + +.language-model { + background-image: url(/img/language.jpg); + background-color: #72cce4; + background-repeat: no-repeat; + background-size: contain; + background-position: center; +} + +.default-model { + background-image: url(/img/default.jpg); + background-repeat: no-repeat; + background-size: cover; + background-position: center; +} diff --git a/app/styles/output.css b/app/styles/output.css new file mode 100644 index 0000000..122122c --- /dev/null +++ b/app/styles/output.css @@ -0,0 +1,74 @@ +/* Output Section */ +.output { + grid-column: 2; + grid-row: 1 / 4; + border-radius: 2em; + background-color: var(--output-background-color); + padding: 1em; + margin: 1em; + display: flex; + flex-direction: column; + justify-content: flex-start; + font-size: 1.2em; + overflow-y: auto; + margin-bottom: 0; + width: 100% -2em; + height: 80vh; + margin-bottom: 1vh; +} + +#conversation { + display: flex; + flex-direction: column; + padding: 10px; + overflow-y: auto; + max-height: 80vh; + background-color: var(--output-background-color); + border-radius: 10px; + scroll-behavior: smooth; +} + +/* Message Bubbles */ +.user-message, .ai-message { + padding: 10px; + border-radius: 10px; + margin: 5px 0; + max-width: 75%; + word-wrap: break-word; +} + +.user-message { + background-color: var(--user-message-background-color); + align-self: flex-end; + color: var(--user-message-text-color); +} + +.ai-message { + background-color: var(--ai-message-background-color); + align-self: flex-start; + color: var(--ai-message-text-color); +} + +/* Button Container */ +.button-container { + display: flex; + padding: 10px 0; +} + +.button-container button { + background: none; + border: none; + cursor: pointer; + background-color: var(--button-background-color); + border-radius: 50%; + padding: 10px; + transition: background-color 0.3s ease; +} + +.button-container button:hover { + background-color: var(--button-hover-background-color); +} + +.button-container img { + height: 1.5em; +} diff --git a/app/styles/reset.css b/app/styles/reset.css new file mode 100644 index 0000000..23d8732 --- /dev/null +++ b/app/styles/reset.css @@ -0,0 +1,5 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} diff --git a/app/styles/scrollbar.css b/app/styles/scrollbar.css new file mode 100644 index 0000000..5e2f6a1 --- /dev/null +++ b/app/styles/scrollbar.css @@ -0,0 +1,17 @@ +/* Scrollbar styling */ +.scrollbar { + overflow-y: scroll; +} + +.scrollbar::-webkit-scrollbar { + width: 8px; +} + +.scrollbar::-webkit-scrollbar-thumb { + background-color: #ccc; + border-radius: 4px; +} + +.scrollbar::-webkit-scrollbar-track { + background-color: #f1f1f1; +} \ No newline at end of file diff --git a/app/styles/user-ai-messages.css b/app/styles/user-ai-messages.css new file mode 100644 index 0000000..ccbd494 --- /dev/null +++ b/app/styles/user-ai-messages.css @@ -0,0 +1,27 @@ +.user-message, +.ai-message { + margin: 10px 0; + padding: 10px 15px; + border-radius: 15px; + max-width: 60%; + width: fit-content; + word-wrap: break-word; + display: block; + box-shadow: 0px 1px 3px rgba(0, 0, 0, 0.1); +} + +.user-message { + background-color: var(--user-message-color); + color: var(--text-color); + border-bottom-right-radius: 0; + margin-left: auto; + text-align: right; +} + +.ai-message { + background-color: var(--ai-message-color); + color: var(--text-color); + border-bottom-left-radius: 0; + margin-right: auto; + text-align: left; +} diff --git a/app/styles/variables.css b/app/styles/variables.css new file mode 100644 index 0000000..3df7f30 --- /dev/null +++ b/app/styles/variables.css @@ -0,0 +1,15 @@ +:root { + --background-color: white; + --text-color: white; + --font-family: Arial, sans-serif; + --history-background-color: rgb(0, 0, 48); + --models-background-color: rgb(0, 0, 48); + --output-background-color: black; + --user-message-color: rgb(0, 128, 255); + --ai-message-color: rgb(100, 100, 255); + --input-background-color: rgb(0, 0, 48); + --input-button-color: rgb(0, 128, 255); + --input-button-hover-color: rgb(0, 100, 200); + --scrollbar-track: rgb(91, 172, 253); + --scrollbar-thumb: rgb(0, 88, 176); +} diff --git a/package-lock.json b/package-lock.json index 6b43faa..ec2f85c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "0.1.0", "dependencies": { "next": "14.2.12", + "ollama": "^0.5.9", "react": "^18", "react-dom": "^18" }, @@ -3703,6 +3704,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/ollama": { + "version": "0.5.9", + "resolved": "https://registry.npmjs.org/ollama/-/ollama-0.5.9.tgz", + "integrity": "sha512-F/KZuDRC+ZsVCuMvcOYuQ6zj42/idzCkkuknGyyGVmNStMZ/sU3jQpvhnl4SyC0+zBzLiKNZJnJeuPFuieWZvQ==", + "license": "MIT", + "dependencies": { + "whatwg-fetch": "^3.6.20" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -5061,6 +5071,12 @@ "dev": true, "license": "MIT" }, + "node_modules/whatwg-fetch": { + "version": "3.6.20", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz", + "integrity": "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==", + "license": "MIT" + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/package.json b/package.json index be692b2..2aea3c5 100644 --- a/package.json +++ b/package.json @@ -9,18 +9,19 @@ "lint": "next lint" }, "dependencies": { + "next": "14.2.12", + "ollama": "^0.5.9", "react": "^18", - "react-dom": "^18", - "next": "14.2.12" + "react-dom": "^18" }, "devDependencies": { - "typescript": "^5", "@types/node": "^20", "@types/react": "^18", "@types/react-dom": "^18", + "eslint": "^8", + "eslint-config-next": "14.2.12", "postcss": "^8", "tailwindcss": "^3.4.1", - "eslint": "^8", - "eslint-config-next": "14.2.12" + "typescript": "^5" } } diff --git a/public/img/code.jpg b/public/img/code.jpg new file mode 100644 index 0000000..416fca4 Binary files /dev/null and b/public/img/code.jpg differ diff --git a/public/img/copy.svg b/public/img/copy.svg new file mode 100644 index 0000000..c8bdf17 --- /dev/null +++ b/public/img/copy.svg @@ -0,0 +1,54 @@ + + + + + + + + + + diff --git a/public/img/default.jpg b/public/img/default.jpg new file mode 100644 index 0000000..266213d Binary files /dev/null and b/public/img/default.jpg differ diff --git a/public/img/edit.svg b/public/img/edit.svg new file mode 100644 index 0000000..ed1f7fe --- /dev/null +++ b/public/img/edit.svg @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + diff --git a/public/img/language.jpg b/public/img/language.jpg new file mode 100644 index 0000000..5ab31b9 Binary files /dev/null and b/public/img/language.jpg differ diff --git a/public/img/logo.png b/public/img/logo.png new file mode 100644 index 0000000..00332a4 Binary files /dev/null and b/public/img/logo.png differ diff --git a/public/img/math.jpg b/public/img/math.jpg new file mode 100644 index 0000000..a417968 Binary files /dev/null and b/public/img/math.jpg differ diff --git a/public/img/microphone.svg b/public/img/microphone.svg new file mode 100644 index 0000000..6dde830 --- /dev/null +++ b/public/img/microphone.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/public/img/nowifi.svg b/public/img/nowifi.svg new file mode 100644 index 0000000..8e7beb7 --- /dev/null +++ b/public/img/nowifi.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/public/img/resend.svg b/public/img/resend.svg new file mode 100644 index 0000000..469d02b --- /dev/null +++ b/public/img/resend.svg @@ -0,0 +1,65 @@ + + + + + + + + + + + + diff --git a/public/img/send.svg b/public/img/send.svg new file mode 100644 index 0000000..dafc2e9 --- /dev/null +++ b/public/img/send.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/public/img/wifi.svg b/public/img/wifi.svg new file mode 100644 index 0000000..350a0eb --- /dev/null +++ b/public/img/wifi.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file