tech-envision commited on
Commit
2a103fa
·
unverified ·
2 Parent(s): 8f019c2 2d93ee9

Merge pull request #74 from EnvisionMindCa/codex/enhance-frontend-design-with-glassmorphism

Browse files
frontend/src/api.js ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const API_BASE = import.meta.env.VITE_API_BASE || 'http://localhost:8000';
2
+
3
+ export async function fetchStream(user, session, prompt, onChunk) {
4
+ const res = await fetch(`${API_BASE}/chat/stream`, {
5
+ method: 'POST',
6
+ headers: { 'Content-Type': 'application/json' },
7
+ body: JSON.stringify({ user, session, prompt }),
8
+ });
9
+ const reader = res.body.getReader();
10
+ const decoder = new TextDecoder();
11
+ while (true) {
12
+ const { value, done } = await reader.read();
13
+ if (done) break;
14
+ onChunk(decoder.decode(value));
15
+ }
16
+ }
17
+
18
+ export async function fetchSessions(user) {
19
+ const res = await fetch(`${API_BASE}/sessions/${encodeURIComponent(user)}`);
20
+ if (!res.ok) return [];
21
+ const data = await res.json();
22
+ return data.sessions ?? [];
23
+ }
frontend/src/components/ChatWindow.jsx CHANGED
@@ -1,45 +1,61 @@
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
  }
 
1
+ import { useEffect, useState } from 'react'
2
  import MessageList from './MessageList'
3
  import InputBar from './InputBar'
4
+ import SessionList from './SessionList'
5
+ import UsernameForm from './UsernameForm'
6
  import '../styles/chat.css'
7
+ import { fetchStream, fetchSessions } from '../api'
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
 
9
  function ChatWindow() {
10
  const [messages, setMessages] = useState([])
11
+ const [sessionName] = useState(() => crypto.randomUUID())
12
+ const [sessions, setSessions] = useState([])
13
+ const [username, setUsername] = useState(() =>
14
+ localStorage.getItem('username') || ''
15
+ )
16
+
17
+ useEffect(() => {
18
+ if (username) {
19
+ fetchSessions(username).then(setSessions)
20
+ }
21
+ }, [username])
22
+
23
+ const refreshSessions = () => {
24
+ if (username) {
25
+ fetchSessions(username).then(setSessions)
26
+ }
27
+ }
28
 
29
  const sendMessage = async (text) => {
30
  const userMsg = { role: 'user', content: text }
31
  setMessages((prev) => [...prev, userMsg, { role: 'assistant', content: '' }])
32
  const index = messages.length + 1
33
+ await fetchStream(username, sessionName, text, (chunk) => {
34
  setMessages((prev) => {
35
  const copy = [...prev]
36
  copy[index] = { ...copy[index], content: copy[index].content + chunk }
37
  return copy
38
  })
39
  })
40
+ refreshSessions()
41
+ }
42
+
43
+ const handleUsernameSet = (name) => {
44
+ localStorage.setItem('username', name)
45
+ setUsername(name)
46
  }
47
 
48
  return (
49
  <div className="chat-container">
50
+ {!username ? (
51
+ <UsernameForm onSet={handleUsernameSet} />
52
+ ) : (
53
+ <>
54
+ <SessionList sessions={sessions} current={sessionName} />
55
+ <MessageList messages={messages} />
56
+ <InputBar onSend={sendMessage} />
57
+ </>
58
+ )}
59
  </div>
60
  )
61
  }
frontend/src/components/SessionList.jsx ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import PropTypes from 'prop-types'
2
+ import '../styles/chat.css'
3
+
4
+ function SessionList({ sessions, current }) {
5
+ return (
6
+ <div className="session-list">
7
+ {sessions.map((name) => (
8
+ <span
9
+ key={name}
10
+ className={`session-item ${name === current ? 'active' : ''}`}
11
+ >
12
+ {name}
13
+ </span>
14
+ ))}
15
+ </div>
16
+ )
17
+ }
18
+
19
+ SessionList.propTypes = {
20
+ sessions: PropTypes.arrayOf(PropTypes.string).isRequired,
21
+ current: PropTypes.string.isRequired,
22
+ }
23
+
24
+ export default SessionList
frontend/src/components/UsernameForm.jsx ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useState } from 'react'
2
+ import PropTypes from 'prop-types'
3
+ import '../styles/chat.css'
4
+
5
+ function UsernameForm({ onSet }) {
6
+ const [name, setName] = useState('')
7
+
8
+ const handleSubmit = (e) => {
9
+ e.preventDefault()
10
+ const trimmed = name.trim()
11
+ if (!trimmed) return
12
+ onSet(trimmed)
13
+ }
14
+
15
+ return (
16
+ <form className="username-form" onSubmit={handleSubmit}>
17
+ <input
18
+ type="text"
19
+ value={name}
20
+ onChange={(e) => setName(e.target.value)}
21
+ placeholder="Enter your name..."
22
+ />
23
+ <button type="submit">Start Chatting</button>
24
+ </form>
25
+ )
26
+ }
27
+
28
+ UsernameForm.propTypes = {
29
+ onSet: PropTypes.func.isRequired,
30
+ }
31
+
32
+ export default UsernameForm
frontend/src/index.css CHANGED
@@ -2,6 +2,7 @@
2
  --glass-bg: rgba(255, 255, 255, 0.2);
3
  --glass-border: rgba(255, 255, 255, 0.4);
4
  --blur: 16px;
 
5
  }
6
 
7
  * {
@@ -12,9 +13,32 @@ 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
  }
 
2
  --glass-bg: rgba(255, 255, 255, 0.2);
3
  --glass-border: rgba(255, 255, 255, 0.4);
4
  --blur: 16px;
5
+ --radius: 20px;
6
  }
7
 
8
  * {
 
13
  margin: 0;
14
  padding: 0;
15
  font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
16
+ background: linear-gradient(120deg, #89f7fe 0%, #66a6ff 100%);
17
  display: flex;
18
  justify-content: center;
19
  align-items: center;
20
  min-height: 100vh;
21
+ position: relative;
22
+ overflow: hidden;
23
+ }
24
+
25
+ body::before,
26
+ body::after {
27
+ content: '';
28
+ position: absolute;
29
+ width: 300px;
30
+ height: 300px;
31
+ background: radial-gradient(circle at center, rgba(255, 255, 255, 0.5), transparent);
32
+ filter: blur(100px);
33
+ z-index: 0;
34
+ }
35
+
36
+ body::before {
37
+ top: -80px;
38
+ left: -80px;
39
+ }
40
+
41
+ body::after {
42
+ bottom: -80px;
43
+ right: -80px;
44
  }
frontend/src/styles/chat.css CHANGED
@@ -8,7 +8,7 @@
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
  }
@@ -22,16 +22,39 @@
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 {
@@ -49,16 +72,17 @@
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;
@@ -66,6 +90,46 @@
66
  transition: transform 0.2s;
67
  }
68
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
69
  .input-bar button:hover {
70
  transform: scale(1.05);
71
  }
@@ -76,10 +140,13 @@
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);
 
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: var(--radius);
12
  padding: 1rem;
13
  box-shadow: 0 4px 30px rgba(0, 0, 0, 0.1);
14
  }
 
22
  padding-right: 4px;
23
  }
24
 
25
+ .session-list {
26
+ display: flex;
27
+ gap: 0.5rem;
28
+ flex-wrap: wrap;
29
+ margin-bottom: 0.5rem;
30
+ z-index: 1;
31
+ }
32
+
33
+ .session-item {
34
+ padding: 0.25rem 0.5rem;
35
+ background: var(--glass-bg);
36
+ border: 1px solid var(--glass-border);
37
+ border-radius: var(--radius);
38
+ backdrop-filter: blur(var(--blur));
39
+ -webkit-backdrop-filter: blur(var(--blur));
40
+ font-size: 0.75rem;
41
+ }
42
+
43
+ .session-item.active {
44
+ background: rgba(255, 255, 255, 0.4);
45
+ font-weight: bold;
46
+ }
47
+
48
  .message {
49
  width: fit-content;
50
  max-width: 80%;
51
  padding: 0.5rem 1rem;
52
  background: var(--glass-bg);
53
  border: 1px solid var(--glass-border);
54
+ border-radius: var(--radius);
55
  backdrop-filter: blur(var(--blur));
56
  -webkit-backdrop-filter: blur(var(--blur));
57
+ animation: jelly 0.6s ease;
58
  }
59
 
60
  .message.user {
 
72
  padding: 0.5rem 1rem;
73
  border: 1px solid var(--glass-border);
74
  background: var(--glass-bg);
75
+ border-radius: var(--radius);
76
  backdrop-filter: blur(var(--blur));
77
  -webkit-backdrop-filter: blur(var(--blur));
78
  color: #000;
79
+ outline: none;
80
  }
81
 
82
  .input-bar button {
83
  padding: 0.5rem 1rem;
84
  border: none;
85
+ border-radius: var(--radius);
86
  background: rgba(255, 255, 255, 0.6);
87
  color: #000;
88
  font-weight: bold;
 
90
  transition: transform 0.2s;
91
  }
92
 
93
+ .username-form {
94
+ display: flex;
95
+ flex-direction: column;
96
+ gap: 0.5rem;
97
+ flex: 1;
98
+ justify-content: center;
99
+ align-items: center;
100
+ }
101
+
102
+ .username-form input {
103
+ padding: 0.75rem 1rem;
104
+ border: 1px solid var(--glass-border);
105
+ background: var(--glass-bg);
106
+ border-radius: var(--radius);
107
+ backdrop-filter: blur(var(--blur));
108
+ -webkit-backdrop-filter: blur(var(--blur));
109
+ width: 100%;
110
+ max-width: 200px;
111
+ text-align: center;
112
+ outline: none;
113
+ }
114
+
115
+ .username-form button {
116
+ padding: 0.5rem 1.5rem;
117
+ border: none;
118
+ border-radius: var(--radius);
119
+ background: rgba(255, 255, 255, 0.7);
120
+ font-weight: bold;
121
+ cursor: pointer;
122
+ transition: transform 0.2s;
123
+ }
124
+
125
+ .username-form button:hover {
126
+ transform: scale(1.05);
127
+ }
128
+
129
+ .username-form button:active {
130
+ transform: scale(0.95);
131
+ }
132
+
133
  .input-bar button:hover {
134
  transform: scale(1.05);
135
  }
 
140
 
141
  @keyframes jelly {
142
  0% {
143
+ transform: scale(0.9);
144
+ }
145
+ 40% {
146
+ transform: scale(1.05);
147
  }
148
+ 60% {
149
+ transform: scale(0.98);
150
  }
151
  100% {
152
  transform: scale(1);