Skip to content

Commit 11914a0

Browse files
committed
wip
1 parent e2f590e commit 11914a0

File tree

22 files changed

+409
-210
lines changed

22 files changed

+409
-210
lines changed

webapp/chat-app/package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
"version": "0.1.0",
44
"private": true,
55
"dependencies": {
6+
"@fortawesome/free-solid-svg-icons": "^6.7.1",
7+
"@fortawesome/react-fontawesome": "^0.2.2",
68
"@reduxjs/toolkit": "^1.9.7",
79
"@testing-library/jest-dom": "^5.17.0",
810
"@testing-library/react": "^13.4.0",
@@ -24,16 +26,14 @@
2426
"@types/react-router-dom": "^5.3.3",
2527
"@types/styled-components": "^5.1.34",
2628
"eslint": "^8.0.0",
27-
"eslint-config-prettier": "^8.8.0",
28-
"eslint-plugin-prettier": "^4.2.1",
29-
"prettier": "^2.8.8",
3029
"typescript": "^4.9.5"
3130
},
3231
"scripts": {
3332
"start": "react-scripts start",
3433
"build": "react-scripts build",
3534
"test": "react-scripts test",
3635
"eject": "react-scripts eject",
36+
"lint": "eslint \"src/**/*.{ts,tsx,js,jsx}\" --fix",
3737
"type-check": "tsc --noEmit"
3838
},
3939
"eslintConfig": {

webapp/chat-app/src/App.tsx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,8 @@ import {store} from './store';
44
import websocket from './services/websocket';
55
import {GlobalStyles} from './styles/GlobalStyles';
66
import ChatInterface from './components/ChatInterface';
7-
import Header from "./components/Header";
8-
import {setTheme} from "./store/slices/uiSlice";
97
import ThemeProvider from './themes/ThemeProvider';
8+
import {Menu} from "./components/Menu/Menu";
109

1110
const App: React.FC = () => {
1211
const sessionId = websocket.getSessionId();
@@ -16,7 +15,7 @@ const App: React.FC = () => {
1615
<ThemeProvider>
1716
<GlobalStyles/>
1817
<div className="App">
19-
<Header onThemeChange={setTheme}/>
18+
<Menu/>
2019
<ChatInterface
2120
sessionId={sessionId}
2221
websocket={websocket}
@@ -28,4 +27,4 @@ const App: React.FC = () => {
2827
);
2928
};
3029

31-
export default App;
30+
export default App;

webapp/chat-app/src/components/ChatInterface.tsx

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,7 @@ import {useWebSocket} from '../hooks/useWebSocket';
55
import {addMessage} from '../store/slices/messageSlice';
66
import MessageList from './MessageList';
77
import InputArea from './InputArea';
8-
import Header from './Header';
9-
import websocket from "@services/websocket";
8+
import websocket from '@services/websocket';
109

1110
interface ChatInterfaceProps {
1211
sessionId?: string;
@@ -20,22 +19,29 @@ const ChatContainer = styled.div`
2019
height: 100vh;
2120
`;
2221

23-
const ChatInterface: React.FC<ChatInterfaceProps> = ({sessionId: propSessionId, websocket, isConnected}) => {
22+
const ChatInterface: React.FC<ChatInterfaceProps> = ({
23+
sessionId: propSessionId,
24+
websocket,
25+
isConnected,
26+
}) => {
2427
const [sessionId] = useState(() => propSessionId || window.location.hash.slice(1) || 'new');
2528
const dispatch = useDispatch();
2629
const ws = useWebSocket(sessionId);
2730

2831
useEffect(() => {
2932
const handleMessage = (data: string) => {
3033
const [id, version, content] = data.split(',');
31-
32-
dispatch(addMessage({
33-
id,
34-
content: content,
35-
version,
36-
type: id.startsWith('u') ? 'user' : 'response',
37-
timestamp: Date.now()
38-
}));
34+
const timestamp = Date.now();
35+
36+
dispatch(
37+
addMessage({
38+
id: `${id}-${timestamp}`, // Make IDs more unique
39+
content: content,
40+
version,
41+
type: id.startsWith('u') ? 'user' : 'response',
42+
timestamp,
43+
})
44+
);
3945
};
4046

4147
websocket.addMessageHandler(handleMessage);
@@ -44,8 +50,6 @@ const ChatInterface: React.FC<ChatInterfaceProps> = ({sessionId: propSessionId,
4450

4551
return (
4652
<ChatContainer>
47-
<Header onThemeChange={(theme) => {
48-
}}/>
4953
<MessageList/>
5054
<InputArea onSendMessage={(msg) => ws.send(msg)}/>
5155
</ChatContainer>

webapp/chat-app/src/components/Header.tsx

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@ import {ThemeName} from '../types';
66
const HeaderContainer = styled.header`
77
background-color: ${({theme}) => theme.colors.surface};
88
padding: 1rem;
9-
border-bottom: 1px solid ${props => props.theme.colors.border};
9+
border-bottom: 1px solid ${(props) => props.theme.colors.border};
1010
`;
1111

1212
const ThemeSelect = styled.select`
1313
padding: 0.5rem;
14-
border-radius: ${props => props.theme.sizing.borderRadius.sm};
15-
border: 1px solid ${props => props.theme.colors.border};
14+
border-radius: ${(props) => props.theme.sizing.borderRadius.sm};
15+
border: 1px solid ${(props) => props.theme.colors.border};
1616
`;
1717

1818
interface HeaderProps {
@@ -24,10 +24,7 @@ const Header: React.FC<HeaderProps> = ({onThemeChange}) => {
2424

2525
return (
2626
<HeaderContainer>
27-
<ThemeSelect
28-
value={currentTheme}
29-
onChange={(e) => setTheme(e.target.value as any)}
30-
>
27+
<ThemeSelect value={currentTheme} onChange={(e) => setTheme(e.target.value as any)}>
3128
<option value="main">Day</option>
3229
<option value="night">Night</option>
3330
<option value="forest">Forest</option>

webapp/chat-app/src/components/InputArea.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,15 @@ import styled from 'styled-components';
33

44
const InputContainer = styled.div`
55
padding: 1rem;
6-
background-color: ${props => props.theme.colors.surface};
7-
border-top: 1px solid ${props => props.theme.colors.border};
6+
background-color: ${(props) => props.theme.colors.surface};
7+
border-top: 1px solid ${(props) => props.theme.colors.border};
88
`;
99

1010
const TextArea = styled.textarea`
1111
width: 100%;
1212
padding: 0.5rem;
13-
border-radius: ${props => props.theme.sizing.borderRadius.md};
14-
border: 1px solid ${props => props.theme.colors.border};
13+
border-radius: ${(props) => props.theme.sizing.borderRadius.md};
14+
border: 1px solid ${(props) => props.theme.colors.border};
1515
font-family: inherit;
1616
resize: vertical;
1717
`;
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
import React from 'react';
2+
import styled from 'styled-components';
3+
import {useDispatch} from 'react-redux';
4+
import {useTheme} from '../../hooks/useTheme';
5+
import {showModal} from '../../store/slices/uiSlice';
6+
import {ThemeName} from '../../themes/themes';
7+
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
8+
import {faCog, faHome, faSignInAlt, faSignOutAlt} from '@fortawesome/free-solid-svg-icons';
9+
import {ThemeMenu} from "./ThemeMenu";
10+
11+
const MenuContainer = styled.div`
12+
display: flex;
13+
justify-content: space-between;
14+
padding: ${({theme}) => theme.sizing.spacing.sm};
15+
background-color: ${({theme}) => theme.colors.surface};
16+
border-bottom: 1px solid ${({theme}) => theme.colors.border};
17+
`;
18+
19+
const ToolbarLeft = styled.div`
20+
display: flex;
21+
gap: ${({theme}) => theme.sizing.spacing.md};
22+
`;
23+
24+
const Dropdown = styled.div`
25+
position: relative;
26+
display: inline-block;
27+
28+
&:hover .dropdown-content {
29+
display: block;
30+
}
31+
`;
32+
33+
const DropButton = styled.a`
34+
color: ${({theme}) => theme.colors.text.primary};
35+
padding: ${({theme}) => theme.sizing.spacing.sm};
36+
text-decoration: none;
37+
cursor: pointer;
38+
39+
&:hover {
40+
background-color: ${({theme}) => theme.colors.primary};
41+
color: white;
42+
}
43+
`;
44+
45+
const DropdownContent = styled.div`
46+
display: none;
47+
position: absolute;
48+
background-color: ${({theme}) => theme.colors.surface};
49+
min-width: 160px;
50+
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2);
51+
z-index: 1;
52+
`;
53+
54+
const DropdownItem = styled.a`
55+
color: ${({theme}) => theme.colors.text.primary};
56+
padding: ${({theme}) => theme.sizing.spacing.sm};
57+
text-decoration: none;
58+
display: block;
59+
cursor: pointer;
60+
61+
&:hover {
62+
background-color: ${({theme}) => theme.colors.primary};
63+
color: white;
64+
}
65+
`;
66+
67+
export const Menu: React.FC = () => {
68+
const dispatch = useDispatch();
69+
const [, setTheme] = useTheme();
70+
71+
const handleThemeChange = (theme: ThemeName) => {
72+
setTheme(theme);
73+
};
74+
75+
const handleModalOpen = (modalType: string) => {
76+
dispatch(showModal(modalType));
77+
};
78+
79+
return (
80+
<MenuContainer>
81+
<ToolbarLeft>
82+
<DropButton href="/">
83+
<FontAwesomeIcon icon={faHome}/> Home
84+
</DropButton>
85+
86+
<Dropdown>
87+
<DropButton>App</DropButton>
88+
<DropdownContent>
89+
<DropdownItem onClick={() => handleModalOpen('sessions')}>Session List</DropdownItem>
90+
<DropdownItem href="">New</DropdownItem>
91+
</DropdownContent>
92+
</Dropdown>
93+
94+
<Dropdown>
95+
<DropButton>
96+
<FontAwesomeIcon icon={faCog}/> Session
97+
</DropButton>
98+
<DropdownContent>
99+
<DropdownItem onClick={() => handleModalOpen('settings')}>Settings</DropdownItem>
100+
<DropdownItem onClick={() => handleModalOpen('files')}>Files</DropdownItem>
101+
<DropdownItem onClick={() => handleModalOpen('usage')}>Usage</DropdownItem>
102+
<DropdownItem onClick={() => handleModalOpen('threads')}>Threads</DropdownItem>
103+
<DropdownItem onClick={() => handleModalOpen('share')}>Share</DropdownItem>
104+
<DropdownItem onClick={() => handleModalOpen('cancel')}>Cancel</DropdownItem>
105+
<DropdownItem onClick={() => handleModalOpen('delete')}>Delete</DropdownItem>
106+
<DropdownItem onClick={() => handleModalOpen('verbose')}>Show Verbose</DropdownItem>
107+
</DropdownContent>
108+
</Dropdown>
109+
110+
<ThemeMenu/>
111+
112+
<Dropdown>
113+
<DropButton>About</DropButton>
114+
<DropdownContent>
115+
<DropdownItem onClick={() => handleModalOpen('privacy')}>Privacy Policy</DropdownItem>
116+
<DropdownItem onClick={() => handleModalOpen('tos')}>Terms of Service</DropdownItem>
117+
</DropdownContent>
118+
</Dropdown>
119+
</ToolbarLeft>
120+
121+
<Dropdown>
122+
<DropButton>
123+
<FontAwesomeIcon icon={faSignInAlt}/> Login
124+
</DropButton>
125+
<DropdownContent>
126+
<DropdownItem onClick={() => handleModalOpen('user-settings')}>Settings</DropdownItem>
127+
<DropdownItem onClick={() => handleModalOpen('user-usage')}>Usage</DropdownItem>
128+
<DropdownItem>
129+
<FontAwesomeIcon icon={faSignOutAlt}/> Logout
130+
</DropdownItem>
131+
</DropdownContent>
132+
</Dropdown>
133+
</MenuContainer>
134+
);
135+
};
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import React from 'react';
2+
import styled from 'styled-components';
3+
import {useTheme} from '../../hooks/useTheme';
4+
import {themes} from '../../themes/themes';
5+
6+
const ThemeMenuContainer = styled.div`
7+
position: relative;
8+
display: inline-block;
9+
`;
10+
11+
const ThemeButton = styled.button`
12+
padding: ${({theme}) => theme.sizing.spacing.sm};
13+
color: ${({theme}) => theme.colors.text.primary};
14+
background: ${({theme}) => theme.colors.surface};
15+
border: 1px solid ${({theme}) => theme.colors.border};
16+
border-radius: ${({theme}) => theme.sizing.borderRadius.sm};
17+
18+
&:hover {
19+
background: ${({theme}) => theme.colors.primary};
20+
color: ${({theme}) => theme.colors.background};
21+
}
22+
`;
23+
24+
const ThemeList = styled.div`
25+
position: absolute;
26+
top: 100%;
27+
right: 0;
28+
background: ${({theme}) => theme.colors.surface};
29+
border: 1px solid ${({theme}) => theme.colors.border};
30+
border-radius: ${({theme}) => theme.sizing.borderRadius.sm};
31+
padding: ${({theme}) => theme.sizing.spacing.xs};
32+
z-index: 10;
33+
min-width: 150px;
34+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
35+
`;
36+
37+
const ThemeOption = styled.button`
38+
width: 100%;
39+
padding: ${({theme}) => theme.sizing.spacing.sm};
40+
text-align: left;
41+
color: ${({theme}) => theme.colors.text.primary};
42+
background: none;
43+
border: none;
44+
border-radius: ${({theme}) => theme.sizing.borderRadius.sm};
45+
46+
&:hover {
47+
background: ${({theme}) => theme.colors.primary};
48+
color: ${({theme}) => theme.colors.background};
49+
}
50+
`;
51+
52+
export const ThemeMenu: React.FC = () => {
53+
const [currentTheme, setTheme] = useTheme();
54+
const [isOpen, setIsOpen] = React.useState(false);
55+
56+
const handleThemeChange = (themeName: keyof typeof themes) => {
57+
setTheme(themeName);
58+
setIsOpen(false);
59+
};
60+
61+
return (
62+
<ThemeMenuContainer>
63+
<ThemeButton onClick={() => setIsOpen(!isOpen)}>
64+
Theme: {currentTheme}
65+
</ThemeButton>
66+
{isOpen && (
67+
<ThemeList>
68+
{Object.keys(themes).map((themeName) => (
69+
<ThemeOption
70+
key={themeName}
71+
onClick={() => handleThemeChange(themeName as keyof typeof themes)}
72+
>
73+
{themeName}
74+
</ThemeOption>
75+
))}
76+
</ThemeList>
77+
)}
78+
</ThemeMenuContainer>
79+
);
80+
};
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export {ThemeMenu} from './ThemeMenu';

webapp/chat-app/src/components/MessageList.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React from 'react';
22
import styled from 'styled-components';
3-
import { useSelector } from 'react-redux';
4-
import { RootState } from '../store';
3+
import {useSelector} from 'react-redux';
4+
import {RootState} from '../store';
55

66
const MessageListContainer = styled.div`
77
flex: 1;
@@ -36,7 +36,7 @@ const MessageList: React.FC = () => {
3636
return (
3737
<MessageListContainer>
3838
{messages.map((message) => (
39-
<MessageItem key={message.id} type={message.type}>
39+
<MessageItem key={`${message.id}-${message.timestamp}`} type={message.type}>
4040
{message.content}
4141
</MessageItem>
4242
))}

0 commit comments

Comments
 (0)