tech-envision commited on
Commit
8f019c2
·
unverified ·
2 Parent(s): 1ccf526 03e545a

Merge pull request #73 from EnvisionMindCa/codex/code-modern-react.js-front-end-with-glassmorphism

Browse files
frontend/.gitignore ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Logs
2
+ logs
3
+ *.log
4
+ npm-debug.log*
5
+ yarn-debug.log*
6
+ yarn-error.log*
7
+ pnpm-debug.log*
8
+ lerna-debug.log*
9
+
10
+ node_modules
11
+ dist
12
+ dist-ssr
13
+ *.local
14
+
15
+ # Editor directories and files
16
+ .vscode/*
17
+ !.vscode/extensions.json
18
+ .idea
19
+ .DS_Store
20
+ *.suo
21
+ *.ntvs*
22
+ *.njsproj
23
+ *.sln
24
+ *.sw?
frontend/README.md ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # React + Vite
2
+
3
+ This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
4
+
5
+ Currently, two official plugins are available:
6
+
7
+ - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) for Fast Refresh
8
+ - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
9
+
10
+ ## Expanding the ESLint configuration
11
+
12
+ If you are developing a production application, we recommend using TypeScript with type-aware lint rules enabled. Check out the [TS template](https://github.com/vitejs/vite/tree/main/packages/create-vite/template-react-ts) for information on how to integrate TypeScript and [`typescript-eslint`](https://typescript-eslint.io) in your project.
frontend/eslint.config.js ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import js from '@eslint/js'
2
+ import globals from 'globals'
3
+ import reactHooks from 'eslint-plugin-react-hooks'
4
+ import reactRefresh from 'eslint-plugin-react-refresh'
5
+
6
+ export default [
7
+ { ignores: ['dist'] },
8
+ {
9
+ files: ['**/*.{js,jsx}'],
10
+ languageOptions: {
11
+ ecmaVersion: 2020,
12
+ globals: globals.browser,
13
+ parserOptions: {
14
+ ecmaVersion: 'latest',
15
+ ecmaFeatures: { jsx: true },
16
+ sourceType: 'module',
17
+ },
18
+ },
19
+ plugins: {
20
+ 'react-hooks': reactHooks,
21
+ 'react-refresh': reactRefresh,
22
+ },
23
+ rules: {
24
+ ...js.configs.recommended.rules,
25
+ ...reactHooks.configs.recommended.rules,
26
+ 'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }],
27
+ 'react-refresh/only-export-components': [
28
+ 'warn',
29
+ { allowConstantExport: true },
30
+ ],
31
+ },
32
+ },
33
+ ]
frontend/index.html ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <link rel="icon" type="image/svg+xml" href="/vite.svg" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>LLM Chat</title>
8
+ </head>
9
+ <body>
10
+ <div id="root"></div>
11
+ <script type="module" src="/src/main.jsx"></script>
12
+ </body>
13
+ </html>
frontend/package-lock.json ADDED
The diff for this file is too large to render. See raw diff
 
frontend/package.json ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "frontend",
3
+ "private": true,
4
+ "version": "0.0.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "vite build",
9
+ "lint": "eslint .",
10
+ "preview": "vite preview"
11
+ },
12
+ "dependencies": {
13
+ "prop-types": "^15.8.1",
14
+ "react": "^19.1.0",
15
+ "react-dom": "^19.1.0"
16
+ },
17
+ "devDependencies": {
18
+ "@eslint/js": "^9.25.0",
19
+ "@types/react": "^19.1.2",
20
+ "@types/react-dom": "^19.1.2",
21
+ "@vitejs/plugin-react": "^4.4.1",
22
+ "eslint": "^9.25.0",
23
+ "eslint-plugin-react-hooks": "^5.2.0",
24
+ "eslint-plugin-react-refresh": "^0.4.19",
25
+ "globals": "^16.0.0",
26
+ "vite": "^6.3.5"
27
+ }
28
+ }
frontend/public/vite.svg ADDED
frontend/src/App.css ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #root {
2
+ max-width: 1280px;
3
+ margin: 0 auto;
4
+ padding: 2rem;
5
+ text-align: center;
6
+ }
7
+
8
+ .logo {
9
+ height: 6em;
10
+ padding: 1.5em;
11
+ will-change: filter;
12
+ transition: filter 300ms;
13
+ }
14
+ .logo:hover {
15
+ filter: drop-shadow(0 0 2em #646cffaa);
16
+ }
17
+ .logo.react:hover {
18
+ filter: drop-shadow(0 0 2em #61dafbaa);
19
+ }
20
+
21
+ @keyframes logo-spin {
22
+ from {
23
+ transform: rotate(0deg);
24
+ }
25
+ to {
26
+ transform: rotate(360deg);
27
+ }
28
+ }
29
+
30
+ @media (prefers-reduced-motion: no-preference) {
31
+ a:nth-of-type(2) .logo {
32
+ animation: logo-spin infinite 20s linear;
33
+ }
34
+ }
35
+
36
+ .card {
37
+ padding: 2em;
38
+ }
39
+
40
+ .read-the-docs {
41
+ color: #888;
42
+ }
frontend/src/App.jsx ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ import ChatWindow from './components/ChatWindow'
2
+ import './index.css'
3
+
4
+ function App() {
5
+ return <ChatWindow />
6
+ }
7
+
8
+ export default App
frontend/src/assets/react.svg ADDED
frontend/src/components/ChatWindow.jsx ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useState } from 'react'
2
+ import MessageList from './MessageList'
3
+ import InputBar from './InputBar'
4
+ import '../styles/chat.css'
5
+
6
+ const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:8000/chat/stream'
7
+
8
+ async function fetchStream(prompt, onChunk) {
9
+ const res = await fetch(API_URL, {
10
+ method: 'POST',
11
+ headers: { 'Content-Type': 'application/json' },
12
+ body: JSON.stringify({ user: 'demo', session: 'default', prompt }),
13
+ })
14
+ const reader = res.body.getReader()
15
+ const decoder = new TextDecoder()
16
+ while (true) {
17
+ const { value, done } = await reader.read()
18
+ if (done) break
19
+ onChunk(decoder.decode(value))
20
+ }
21
+ }
22
+
23
+ function ChatWindow() {
24
+ const [messages, setMessages] = useState([])
25
+
26
+ const sendMessage = async (text) => {
27
+ const userMsg = { role: 'user', content: text }
28
+ setMessages((prev) => [...prev, userMsg, { role: 'assistant', content: '' }])
29
+ const index = messages.length + 1
30
+ await fetchStream(text, (chunk) => {
31
+ setMessages((prev) => {
32
+ const copy = [...prev]
33
+ copy[index] = { ...copy[index], content: copy[index].content + chunk }
34
+ return copy
35
+ })
36
+ })
37
+ }
38
+
39
+ return (
40
+ <div className="chat-container">
41
+ <MessageList messages={messages} />
42
+ <InputBar onSend={sendMessage} />
43
+ </div>
44
+ )
45
+ }
46
+
47
+ export default ChatWindow
frontend/src/components/InputBar.jsx ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useState } from 'react'
2
+ import PropTypes from 'prop-types'
3
+
4
+ function InputBar({ onSend }) {
5
+ const [text, setText] = useState('')
6
+
7
+ const handleSubmit = (e) => {
8
+ e.preventDefault()
9
+ if (!text.trim()) return
10
+ onSend(text)
11
+ setText('')
12
+ }
13
+
14
+ return (
15
+ <form className="input-bar" onSubmit={handleSubmit}>
16
+ <input
17
+ type="text"
18
+ value={text}
19
+ onChange={(e) => setText(e.target.value)}
20
+ placeholder="Type a message..."
21
+ />
22
+ <button type="submit">Send</button>
23
+ </form>
24
+ )
25
+ }
26
+
27
+ InputBar.propTypes = {
28
+ onSend: PropTypes.func.isRequired,
29
+ }
30
+
31
+ export default InputBar
frontend/src/components/MessageBubble.jsx ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import PropTypes from 'prop-types'
2
+ import '../styles/chat.css'
3
+
4
+ function MessageBubble({ role, content }) {
5
+ return <div className={`message ${role}`}>{content}</div>
6
+ }
7
+
8
+ MessageBubble.propTypes = {
9
+ role: PropTypes.string.isRequired,
10
+ content: PropTypes.string.isRequired,
11
+ }
12
+
13
+ export default MessageBubble
frontend/src/components/MessageList.jsx ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import PropTypes from 'prop-types'
2
+ import MessageBubble from './MessageBubble'
3
+
4
+ function MessageList({ messages }) {
5
+ return (
6
+ <div className="message-list">
7
+ {messages.map((m, i) => (
8
+ <MessageBubble key={i} role={m.role} content={m.content} />
9
+ ))}
10
+ </div>
11
+ )
12
+ }
13
+
14
+ MessageList.propTypes = {
15
+ messages: PropTypes.arrayOf(
16
+ PropTypes.shape({
17
+ role: PropTypes.string.isRequired,
18
+ content: PropTypes.string.isRequired,
19
+ })
20
+ ).isRequired,
21
+ }
22
+
23
+ export default MessageList
frontend/src/index.css ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ :root {
2
+ --glass-bg: rgba(255, 255, 255, 0.2);
3
+ --glass-border: rgba(255, 255, 255, 0.4);
4
+ --blur: 16px;
5
+ }
6
+
7
+ * {
8
+ box-sizing: border-box;
9
+ }
10
+
11
+ body {
12
+ margin: 0;
13
+ padding: 0;
14
+ font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
15
+ background: linear-gradient(135deg, #a1c4fd 0%, #c2e9fb 100%);
16
+ display: flex;
17
+ justify-content: center;
18
+ align-items: center;
19
+ min-height: 100vh;
20
+ }
frontend/src/main.jsx ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ import { StrictMode } from 'react'
2
+ import { createRoot } from 'react-dom/client'
3
+ import './index.css'
4
+ import App from './App.jsx'
5
+
6
+ createRoot(document.getElementById('root')).render(
7
+ <StrictMode>
8
+ <App />
9
+ </StrictMode>,
10
+ )
frontend/src/styles/chat.css ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .chat-container {
2
+ width: 420px;
3
+ max-width: 90vw;
4
+ height: 600px;
5
+ display: flex;
6
+ flex-direction: column;
7
+ background: var(--glass-bg);
8
+ border: 1px solid var(--glass-border);
9
+ backdrop-filter: blur(var(--blur)) saturate(180%);
10
+ -webkit-backdrop-filter: blur(var(--blur)) saturate(180%);
11
+ border-radius: 16px;
12
+ padding: 1rem;
13
+ box-shadow: 0 4px 30px rgba(0, 0, 0, 0.1);
14
+ }
15
+
16
+ .message-list {
17
+ flex: 1;
18
+ overflow-y: auto;
19
+ display: flex;
20
+ flex-direction: column;
21
+ gap: 0.5rem;
22
+ padding-right: 4px;
23
+ }
24
+
25
+ .message {
26
+ width: fit-content;
27
+ max-width: 80%;
28
+ padding: 0.5rem 1rem;
29
+ background: var(--glass-bg);
30
+ border: 1px solid var(--glass-border);
31
+ border-radius: 20px;
32
+ backdrop-filter: blur(var(--blur));
33
+ -webkit-backdrop-filter: blur(var(--blur));
34
+ animation: jelly 0.5s ease;
35
+ }
36
+
37
+ .message.user {
38
+ align-self: flex-end;
39
+ }
40
+
41
+ .input-bar {
42
+ display: flex;
43
+ gap: 0.5rem;
44
+ margin-top: 0.5rem;
45
+ }
46
+
47
+ .input-bar input {
48
+ flex: 1;
49
+ padding: 0.5rem 1rem;
50
+ border: 1px solid var(--glass-border);
51
+ background: var(--glass-bg);
52
+ border-radius: 20px;
53
+ backdrop-filter: blur(var(--blur));
54
+ -webkit-backdrop-filter: blur(var(--blur));
55
+ color: #000;
56
+ }
57
+
58
+ .input-bar button {
59
+ padding: 0.5rem 1rem;
60
+ border: none;
61
+ border-radius: 20px;
62
+ background: rgba(255, 255, 255, 0.6);
63
+ color: #000;
64
+ font-weight: bold;
65
+ cursor: pointer;
66
+ transition: transform 0.2s;
67
+ }
68
+
69
+ .input-bar button:hover {
70
+ transform: scale(1.05);
71
+ }
72
+
73
+ .input-bar button:active {
74
+ transform: scale(0.95);
75
+ }
76
+
77
+ @keyframes jelly {
78
+ 0% {
79
+ transform: scale(0.8);
80
+ }
81
+ 50% {
82
+ transform: scale(1.1);
83
+ }
84
+ 100% {
85
+ transform: scale(1);
86
+ }
87
+ }
frontend/vite.config.js ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ import { defineConfig } from 'vite'
2
+ import react from '@vitejs/plugin-react'
3
+
4
+ // https://vite.dev/config/
5
+ export default defineConfig({
6
+ plugins: [react()],
7
+ })