tech-envision commited on
Commit
9d53f47
·
unverified ·
2 Parent(s): 48b4388 6a317c1

Merge pull request #79 from EnvisionMindCa/codex/implement-glassmorphism-design

Browse files
frontend/src/app/globals.css CHANGED
@@ -8,8 +8,8 @@
8
  @theme inline {
9
  --color-background: var(--background);
10
  --color-foreground: var(--foreground);
11
- --font-sans: var(--font-geist-sans);
12
- --font-mono: var(--font-geist-mono);
13
  }
14
 
15
  @media (prefers-color-scheme: dark) {
@@ -24,3 +24,12 @@ body {
24
  color: var(--foreground);
25
  font-family: Arial, Helvetica, sans-serif;
26
  }
 
 
 
 
 
 
 
 
 
 
8
  @theme inline {
9
  --color-background: var(--background);
10
  --color-foreground: var(--foreground);
11
+ --font-sans: ui-sans-serif, system-ui, sans-serif;
12
+ --font-mono: ui-monospace, monospace;
13
  }
14
 
15
  @media (prefers-color-scheme: dark) {
 
24
  color: var(--foreground);
25
  font-family: Arial, Helvetica, sans-serif;
26
  }
27
+
28
+ @keyframes fadeIn {
29
+ from { opacity: 0; transform: translateY(5px); }
30
+ to { opacity: 1; transform: translateY(0); }
31
+ }
32
+
33
+ .animate-fadeIn {
34
+ animation: fadeIn 0.3s ease-in-out;
35
+ }
frontend/src/app/layout.tsx CHANGED
@@ -1,17 +1,6 @@
1
  import type { Metadata } from "next";
2
- import { Geist, Geist_Mono } from "next/font/google";
3
  import "./globals.css";
4
 
5
- const geistSans = Geist({
6
- variable: "--font-geist-sans",
7
- subsets: ["latin"],
8
- });
9
-
10
- const geistMono = Geist_Mono({
11
- variable: "--font-geist-mono",
12
- subsets: ["latin"],
13
- });
14
-
15
  export const metadata: Metadata = {
16
  title: "Create Next App",
17
  description: "Generated by create next app",
@@ -24,9 +13,7 @@ export default function RootLayout({
24
  }>) {
25
  return (
26
  <html lang="en">
27
- <body
28
- className={`${geistSans.variable} ${geistMono.variable} antialiased`}
29
- >
30
  {children}
31
  </body>
32
  </html>
 
1
  import type { Metadata } from "next";
 
2
  import "./globals.css";
3
 
 
 
 
 
 
 
 
 
 
 
4
  export const metadata: Metadata = {
5
  title: "Create Next App",
6
  description: "Generated by create next app",
 
13
  }>) {
14
  return (
15
  <html lang="en">
16
+ <body className="antialiased">
 
 
17
  {children}
18
  </body>
19
  </html>
frontend/src/components/ChatApp.tsx CHANGED
@@ -1,26 +1,27 @@
1
  'use client';
2
 
3
- import { useState, useRef } from 'react';
4
  import { streamChat, ChatRequest } from '../utils/api';
5
-
6
- interface Message {
7
- role: 'user' | 'assistant';
8
- content: string;
9
- }
10
 
11
  export default function ChatApp() {
12
  const [messages, setMessages] = useState<Message[]>([]);
13
  const [input, setInput] = useState('');
14
  const [loading, setLoading] = useState(false);
15
- const endRef = useRef<HTMLDivElement>(null);
16
 
17
  const scrollToEnd = () => {
18
- endRef.current?.scrollIntoView({ behavior: 'smooth' });
19
  };
20
 
 
 
 
 
21
  const sendMessage = async () => {
22
  if (!input.trim() || loading) return;
23
-
24
  const prompt = input;
25
  setInput('');
26
  setMessages((prev) => [...prev, { role: 'user', content: prompt }, { role: 'assistant', content: '' }]);
@@ -36,57 +37,25 @@ export default function ChatApp() {
36
  };
37
  return msgs;
38
  });
39
- scrollToEnd();
40
  }
41
  } catch (err) {
42
  console.error(err);
43
  setMessages((prev) => {
44
  const msgs = [...prev];
45
- msgs[msgs.length - 1] = {
46
- role: 'assistant',
47
- content: 'Error retrieving response',
48
- };
49
  return msgs;
50
  });
51
  } finally {
52
  setLoading(false);
53
- scrollToEnd();
54
  }
55
  };
56
 
57
- const onSubmit = (e: React.FormEvent) => {
58
- e.preventDefault();
59
- void sendMessage();
60
- };
61
-
62
  return (
63
- <div className="flex flex-col max-w-2xl mx-auto h-screen p-4">
64
- <div className="flex-1 overflow-y-auto space-y-4">
65
- {messages.map((msg, idx) => (
66
- <div key={idx} className={msg.role === 'user' ? 'text-right' : 'text-left'}>
67
- <span className="px-3 py-2 inline-block rounded bg-gray-200 dark:bg-gray-700">
68
- {msg.content}
69
- </span>
70
- </div>
71
- ))}
72
- <div ref={endRef} />
73
  </div>
74
- <form onSubmit={onSubmit} className="mt-4 flex gap-2">
75
- <input
76
- type="text"
77
- className="flex-1 border rounded px-3 py-2 text-black"
78
- value={input}
79
- onChange={(e) => setInput(e.target.value)}
80
- placeholder="Type your message..."
81
- />
82
- <button
83
- type="submit"
84
- className="bg-blue-600 text-white px-4 py-2 rounded disabled:opacity-50"
85
- disabled={loading}
86
- >
87
- Send
88
- </button>
89
- </form>
90
  </div>
91
  );
92
  }
 
1
  'use client';
2
 
3
+ import { useState, useRef, useEffect } from 'react';
4
  import { streamChat, ChatRequest } from '../utils/api';
5
+ import MessageList from './MessageList';
6
+ import MessageInput from './MessageInput';
7
+ import { Message } from './MessageItem';
 
 
8
 
9
  export default function ChatApp() {
10
  const [messages, setMessages] = useState<Message[]>([]);
11
  const [input, setInput] = useState('');
12
  const [loading, setLoading] = useState(false);
13
+ const listRef = useRef<HTMLDivElement>(null);
14
 
15
  const scrollToEnd = () => {
16
+ listRef.current?.scrollTo({ top: listRef.current.scrollHeight, behavior: 'smooth' });
17
  };
18
 
19
+ useEffect(() => {
20
+ scrollToEnd();
21
+ }, [messages]);
22
+
23
  const sendMessage = async () => {
24
  if (!input.trim() || loading) return;
 
25
  const prompt = input;
26
  setInput('');
27
  setMessages((prev) => [...prev, { role: 'user', content: prompt }, { role: 'assistant', content: '' }]);
 
37
  };
38
  return msgs;
39
  });
 
40
  }
41
  } catch (err) {
42
  console.error(err);
43
  setMessages((prev) => {
44
  const msgs = [...prev];
45
+ msgs[msgs.length - 1] = { role: 'assistant', content: 'Error retrieving response' };
 
 
 
46
  return msgs;
47
  });
48
  } finally {
49
  setLoading(false);
 
50
  }
51
  };
52
 
 
 
 
 
 
53
  return (
54
+ <div className="flex flex-col items-center h-screen bg-gradient-to-br from-gray-950/60 to-gray-800/60 text-white p-4">
55
+ <div className="w-full max-w-3xl flex flex-col flex-1 backdrop-blur-lg rounded-lg border border-white/20 bg-white/10 shadow-xl overflow-hidden">
56
+ <MessageList ref={listRef} messages={messages} />
57
+ <MessageInput value={input} onChange={setInput} onSend={sendMessage} disabled={loading} />
 
 
 
 
 
 
58
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
  </div>
60
  );
61
  }
frontend/src/components/MessageInput.tsx ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from 'react';
2
+ import GlassButton from './ui/GlassButton';
3
+
4
+ interface MessageInputProps {
5
+ value: string;
6
+ onChange: (value: string) => void;
7
+ onSend: () => void;
8
+ disabled?: boolean;
9
+ }
10
+
11
+ export default function MessageInput({ value, onChange, onSend, disabled }: MessageInputProps) {
12
+ const handleSubmit = (e: React.FormEvent) => {
13
+ e.preventDefault();
14
+ onSend();
15
+ };
16
+
17
+ return (
18
+ <form onSubmit={handleSubmit} className="flex gap-2 p-2">
19
+ <input
20
+ type="text"
21
+ className="flex-1 bg-white/20 backdrop-blur-md border border-white/30 text-white px-3 py-2 rounded-md focus:outline-none"
22
+ value={value}
23
+ onChange={(e) => onChange(e.target.value)}
24
+ placeholder="Type your message..."
25
+ />
26
+ <GlassButton type="submit" disabled={disabled}>
27
+ Send
28
+ </GlassButton>
29
+ </form>
30
+ );
31
+ }
frontend/src/components/MessageItem.tsx ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from 'react';
2
+
3
+ export interface Message {
4
+ role: 'user' | 'assistant';
5
+ content: string;
6
+ }
7
+
8
+ interface MessageItemProps {
9
+ message: Message;
10
+ }
11
+
12
+ export default function MessageItem({ message }: MessageItemProps) {
13
+ const alignment = message.role === 'user' ? 'items-end' : 'items-start';
14
+ return (
15
+ <div className={`flex flex-col ${alignment} animate-fadeIn`}>
16
+ <div className="w-full max-w-xl p-3 my-2 bg-white/10 border border-white/30 backdrop-blur-lg rounded-lg shadow-md">
17
+ <p className="whitespace-pre-wrap">{message.content}</p>
18
+ </div>
19
+ </div>
20
+ );
21
+ }
frontend/src/components/MessageList.tsx ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { forwardRef } from 'react';
2
+ import MessageItem, { Message } from './MessageItem';
3
+
4
+ interface MessageListProps {
5
+ messages: Message[];
6
+ }
7
+
8
+ const MessageList = forwardRef<HTMLDivElement, MessageListProps>(({ messages }, ref) => {
9
+ return (
10
+ <div className="flex-1 overflow-y-auto p-2" ref={ref}>
11
+ {messages.map((msg, idx) => (
12
+ <MessageItem key={idx} message={msg} />
13
+ ))}
14
+ </div>
15
+ );
16
+ });
17
+
18
+ MessageList.displayName = 'MessageList';
19
+
20
+ export default MessageList;
frontend/src/components/ui/GlassButton.tsx ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from 'react';
2
+
3
+ interface GlassButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
4
+ children: React.ReactNode;
5
+ }
6
+
7
+ export default function GlassButton({ children, className = '', ...props }: GlassButtonProps) {
8
+ return (
9
+ <button
10
+ className={`bg-white/20 backdrop-blur-md border border-white/30 text-white px-4 py-2 rounded-md shadow-lg transition-all hover:bg-white/30 active:scale-95 disabled:opacity-50 disabled:cursor-not-allowed ${className}`}
11
+ {...props}
12
+ >
13
+ {children}
14
+ </button>
15
+ );
16
+ }