Skip to content

Commit 9cd4714

Browse files
Merge pull request #29 from ModusCreateOrg/ADE-30-manual-steps
[ADE-30] - AI Health Assistant on Home Screen
2 parents 6706845 + bfd047f commit 9cd4714

File tree

29 files changed

+1369
-48
lines changed

29 files changed

+1369
-48
lines changed

.cursor/rules/general.mdc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,3 +123,5 @@ AWS architecture: [aws architecture.pdf](mdc:docs/assets/aws architecture.pdf)
123123
```
124124

125125
This rule provides clear guidelines on what units to use, how to convert between units, and why it's important for your project. You can add this to your general rules to ensure consistency across the codebase.
126+
127+
Prefer using nullish coalescing operator (`??`) instead of a logical or (`||`), as it is a safer operator.
70.2 KB
Loading
110 KB
Loading
187 KB
Loading

frontend/src/assets/img/ai-icon.svg

Lines changed: 9 additions & 0 deletions
Loading
5.5 KB
Loading
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
.ai-assistant-modal {
2+
--height: auto;
3+
--max-height: 70vh;
4+
--border-radius: 1rem;
5+
--box-shadow: 0 -0.25rem 1rem rgba(0, 0, 0, 0.1);
6+
--backdrop-opacity: 0.3;
7+
align-items: flex-end;
8+
transition: --height 0.3s ease-out, --max-height 0.3s ease-out;
9+
10+
&.expanded {
11+
--height: 85vh;
12+
--max-height: 85vh;
13+
14+
&::part(content) {
15+
margin: 2rem 1rem 5rem 1rem;
16+
}
17+
}
18+
19+
&::part(content) {
20+
border-radius: var(--border-radius);
21+
margin: 0 1rem 5rem 1rem;
22+
display: flex;
23+
flex-direction: column;
24+
transition: margin 0.3s ease-out;
25+
}
26+
27+
.ai-assistant-header {
28+
ion-toolbar {
29+
--padding-top: 0.5rem;
30+
--padding-bottom: 0.5rem;
31+
}
32+
}
33+
34+
.ai-assistant-toolbar {
35+
--background: transparent;
36+
--border-color: transparent;
37+
--border-width: 0;
38+
--padding-start: 1rem;
39+
}
40+
41+
.ai-assistant-title-container {
42+
display: flex;
43+
align-items: center;
44+
width: 100%;
45+
text-align: left;
46+
}
47+
48+
.ai-assistant-title-icon {
49+
height: 2.5rem;
50+
width: 2.5rem;
51+
font-size: 2rem;
52+
margin-right: 0.75rem;
53+
color: var(--ion-color-primary);
54+
}
55+
56+
.ai-assistant-title-text {
57+
font-weight: 600;
58+
font-size: 1.125rem;
59+
}
60+
61+
.ai-assistant-content {
62+
--padding: 0;
63+
flex: 1;
64+
overflow: hidden;
65+
}
66+
67+
.ai-assistant-footer {
68+
background: transparent;
69+
position: relative;
70+
display: flex;
71+
justify-content: center;
72+
align-items: center;
73+
padding: 0.5rem 0;
74+
}
75+
}
76+
77+
// Screen reader only class
78+
.sr-only {
79+
position: absolute;
80+
width: 1px;
81+
height: 1px;
82+
padding: 0;
83+
margin: -1px;
84+
overflow: hidden;
85+
clip: rect(0, 0, 0, 0);
86+
border: 0;
87+
}
88+
89+
// Additional animation for modal entrance
90+
ion-modal.ai-assistant-modal {
91+
&.show-modal {
92+
transition: all 0.3s ease-in-out;
93+
}
94+
95+
&::part(content) {
96+
transition: transform 0.3s cubic-bezier(0.36, 0.66, 0.04, 1);
97+
}
98+
}
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
import {
2+
IonButton,
3+
IonButtons,
4+
IonContent,
5+
IonHeader,
6+
IonIcon,
7+
IonModal,
8+
IonToolbar,
9+
IonFooter,
10+
} from '@ionic/react';
11+
import { useState, useRef, useEffect } from 'react';
12+
import { closeOutline, expandOutline, contractOutline } from 'ionicons/icons';
13+
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
14+
import { faRobot } from '@fortawesome/free-solid-svg-icons';
15+
import ChatContainer from '../Chat/ChatContainer';
16+
import ChatInput from '../Chat/ChatInput';
17+
import { chatService } from '../../services/ChatService';
18+
import { ChatMessageData } from '../Chat/ChatMessage';
19+
import './AIAssistantModal.scss';
20+
21+
interface AIAssistantModalProps {
22+
isOpen: boolean;
23+
setIsOpen: (isOpen: boolean) => void;
24+
testid?: string;
25+
}
26+
27+
const AIAssistantModal: React.FC<AIAssistantModalProps> = ({
28+
isOpen,
29+
setIsOpen,
30+
testid = 'ai-assistant-modal'
31+
}) => {
32+
const [isExpanded, setIsExpanded] = useState<boolean>(false);
33+
const [messages, setMessages] = useState<ChatMessageData[]>([]);
34+
const modalRef = useRef<HTMLIonModalElement>(null);
35+
36+
// Reset expanded state whenever modal opens
37+
useEffect(() => {
38+
if (isOpen) {
39+
setIsExpanded(false);
40+
}
41+
}, [isOpen]);
42+
43+
const handleClose = () => {
44+
setIsOpen(false);
45+
};
46+
47+
const handleExpand = () => {
48+
setIsExpanded(!isExpanded);
49+
};
50+
51+
const handleSendMessage = async (text: string) => {
52+
// Always expand the modal on any message
53+
if (!isExpanded) {
54+
setIsExpanded(true);
55+
}
56+
57+
const userMessage = chatService.createUserMessage(text);
58+
setMessages(prevMessages => [...prevMessages, userMessage]);
59+
60+
try {
61+
// Get AI response
62+
const responseText = await chatService.sendMessage(text);
63+
const assistantMessage = chatService.createAssistantMessage(responseText);
64+
setMessages(prevMessages => [...prevMessages, assistantMessage]);
65+
} catch (error) {
66+
console.error('Error getting AI response:', error);
67+
// You could add error handling here, like showing an error message
68+
}
69+
};
70+
71+
return (
72+
<IonModal
73+
isOpen={isOpen}
74+
onDidDismiss={() => setIsOpen(false)}
75+
ref={modalRef}
76+
className={`ai-assistant-modal ${isExpanded ? 'expanded' : ''}`}
77+
data-testid={testid}
78+
aria-labelledby="ai-assistant-title"
79+
>
80+
<IonHeader className="ai-assistant-header">
81+
<IonToolbar className="ai-assistant-toolbar">
82+
<div className="ai-assistant-title-container">
83+
<FontAwesomeIcon icon={faRobot} className="ai-assistant-title-icon" />
84+
<span className="ai-assistant-title-text">AI Assistant</span>
85+
</div>
86+
<IonButtons slot="end">
87+
<IonButton
88+
onClick={handleExpand}
89+
aria-label={isExpanded ? "Collapse chat" : "Expand chat"}
90+
data-testid={`${testid}-expand-button`}
91+
>
92+
<IonIcon icon={isExpanded ? contractOutline : expandOutline} aria-hidden="true" />
93+
</IonButton>
94+
<IonButton
95+
onClick={handleClose}
96+
aria-label="Close AI Assistant"
97+
data-testid={`${testid}-close-button`}
98+
>
99+
<IonIcon icon={closeOutline} aria-hidden="true" />
100+
</IonButton>
101+
</IonButtons>
102+
</IonToolbar>
103+
</IonHeader>
104+
105+
<IonContent className="ai-assistant-content">
106+
<ChatContainer
107+
messages={messages}
108+
robotIcon={faRobot}
109+
testid={`${testid}-chat-container`}
110+
/>
111+
</IonContent>
112+
113+
<IonFooter className="ai-assistant-footer">
114+
<ChatInput
115+
onSendMessage={handleSendMessage}
116+
testid={`${testid}-input`}
117+
/>
118+
</IonFooter>
119+
</IonModal>
120+
);
121+
};
122+
123+
export default AIAssistantModal;

0 commit comments

Comments
 (0)