Skip to content

Commit 78af716

Browse files
authored
AI Widget UI added (#8)
* feat: AI Widget UI added * feat: an update for the header, footer, and brand-opened packages
1 parent 3cfeb3d commit 78af716

File tree

12 files changed

+471
-14
lines changed

12 files changed

+471
-14
lines changed

package-lock.json

+9-9
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Icons.jsx

+34
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,22 @@ export const CheckSquareIcon = (props) => (
3232
</svg>
3333
);
3434

35+
export const ArrowSquareUpIcon = (props) => (
36+
<svg
37+
width="20"
38+
height="20"
39+
viewBox="0 0 20 20"
40+
fill="none"
41+
version="1.1"
42+
{...props}
43+
>
44+
<path
45+
d="M14.34 0C17.729 0 20 2.38 20 5.92V14.09C20 17.62 17.729 20 14.34 20H5.67C2.28 20 0 17.62 0 14.09V5.92C0 2.38 2.28 0 5.67 0H14.34ZM10.53 5.38C10.25 5.1 9.75 5.1 9.47 5.38L5.72 9.15C5.43 9.44 5.43 9.92 5.72 10.21C6.02 10.5 6.49 10.5 6.79 10.21L9.25 7.73V14.08C9.25 14.5 9.59 14.83 10 14.83C10.42 14.83 10.75 14.5 10.75 14.08V7.73L13.22 10.21C13.36 10.35 13.56 10.43 13.75 10.43C13.939 10.43 14.13 10.35 14.28 10.21C14.57 9.92 14.57 9.44 14.28 9.15L10.53 5.38Z"
46+
fill="currentColor"
47+
/>
48+
</svg>
49+
);
50+
3551
export const NotificationIcon = (props) => (
3652
<svg
3753
width="20"
@@ -143,3 +159,21 @@ export const PolygonUpIcon = (props) => (
143159
/>
144160
</svg>
145161
);
162+
163+
export const AIIcon = (props) => (
164+
<svg
165+
width="20"
166+
height="20"
167+
viewBox="0 0 20 20"
168+
fill="none"
169+
version="1.1"
170+
{...props}
171+
>
172+
<path
173+
d="M20 10C20 15.5228 15.5228 20 10 20C4.47715 20 0 15.5228 0 10C0 4.47715 4.47715 0 10 0C15.5228 0 20 4.47715 20 10ZM14.9983 6.25H13.0173V13.5829H14.9983V6.25ZM6.84375 6.25H9.88444L12.041 13.5832H9.85543L9.181 10.9717L8.36364 8.12439L7.55444 10.9717H9.1807L7.26376 12.1498L6.87185 13.5835H4.6875L6.84375 6.25Z"
174+
fill="currentColor"
175+
fillRule="evenodd"
176+
clipRule="evenodd"
177+
/>
178+
</svg>
179+
);

src/assets/images/AI-info.svg

+35
Loading

src/components/ai-widget/AIWidget.jsx

+168
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
import React, { useState } from 'react';
2+
import {
3+
getLocale, injectIntl, intlShape, isRtl,
4+
} from '@edx/frontend-platform/i18n';
5+
6+
import {
7+
Button,
8+
Image,
9+
Form,
10+
ModalPopup,
11+
ModalCloseButton,
12+
useToggle,
13+
Icon,
14+
Avatar,
15+
} from '@edx/paragon';
16+
17+
import { Close } from '@edx/paragon/icons';
18+
import AIInfoImage from '../../assets/images/AI-info.svg';
19+
import { AIIcon, ArrowSquareUpIcon } from '../../Icons';
20+
import messages from '../messages';
21+
22+
const aiData = [
23+
{
24+
id: 'id_1',
25+
request: 'How can I attempt the exams?',
26+
response: `To approach exams successfully,
27+
it's important to adopt effective study strategies and time management skills.
28+
Here are some general tips: Create a Study Plan:
29+
Outline the topics you need to cover.
30+
Allocate specific time slots for studying each subject.
31+
Break down your study sessions into manageable chunks.
32+
Understand the Exam Format:
33+
Know the structure of the exam (multiple choice, essays, etc.).
34+
Understand the point distribution for each section.`,
35+
},
36+
{
37+
id: 'id_2',
38+
request: 'request 2',
39+
response: 'response 2',
40+
},
41+
{
42+
id: 'id_3',
43+
request: 'request 3',
44+
response: '',
45+
},
46+
];
47+
48+
const AIWidget = ({ intl }) => {
49+
const [isOpen, open, close] = useToggle(false);
50+
const [target, setTarget] = useState(null);
51+
const [isFocused, setIsFocused] = useState(false);
52+
const [value, setValue] = useState('');
53+
54+
const inputLength = 200;
55+
const totalPrompts = 3;
56+
57+
const isLocaleRtl = isRtl(getLocale());
58+
59+
const hanldeSubmit = (event) => {
60+
event.preventDefault();
61+
};
62+
63+
const handleInputChange = (event) => {
64+
setValue(event.target.value);
65+
};
66+
67+
return (
68+
<div className="ai-container" hidden>
69+
<Button
70+
ref={setTarget}
71+
className={`btn-ai ${isOpen ? 'btn-active' : ''}`}
72+
onClick={open}
73+
>
74+
<AIIcon />
75+
</Button>
76+
<ModalPopup
77+
positionRef={target}
78+
isBlocking
79+
isOpen={isOpen}
80+
onClose={close}
81+
placement={`top-${isLocaleRtl ? 'start' : 'end'}`}
82+
>
83+
<div className="ai-widget">
84+
<div className="ai-header">
85+
<AIIcon />
86+
<h4>{intl.formatMessage(messages.aiwidgetHeader)}</h4>
87+
<ModalCloseButton
88+
variant="black"
89+
className="ml-2"
90+
><Icon src={Close} />
91+
</ModalCloseButton>
92+
</div>
93+
<div className="ai-body">
94+
{
95+
aiData.length > 0 ? (
96+
<div className="chat-wrapper">
97+
{
98+
aiData.map(({ id, request, response }) => (
99+
<div className="box" key={id}>
100+
<div className="chat-row qestion">
101+
<div className="icon">
102+
<Avatar height="2rem" width="2rem" />
103+
</div>
104+
<div className="text">
105+
<p>{request}</p>
106+
</div>
107+
</div>
108+
{
109+
response.length > 0 && (
110+
<div className="chat-row response">
111+
<div className="icon"><AIIcon height="2rem" width="2rem" /></div>
112+
<div className="text">
113+
<p>{response}</p>
114+
</div>
115+
</div>
116+
)
117+
}
118+
</div>
119+
))
120+
}
121+
</div>
122+
) : (
123+
<div className="no-data">
124+
<Image
125+
src={AIInfoImage}
126+
alt="Image description"
127+
/>
128+
<h4>{intl.formatMessage(messages.aiwidgetLabel)}</h4>
129+
</div>
130+
)
131+
}
132+
</div>
133+
<div className="ai-footer">
134+
<div className="form-info text-right">
135+
<span className="text-prompts">{intl.formatMessage(messages.aiwidgetPrompts, { prompts: totalPrompts - aiData.length })}</span>
136+
</div>
137+
<div className="form-wrapper">
138+
<Form onSubmit={hanldeSubmit} className={`ai-form ${isFocused ? 'is-focused' : ''}`}>
139+
<Form.Control
140+
onFocus={() => { setIsFocused(true); }}
141+
onBlur={() => { setIsFocused(false); }}
142+
value={value}
143+
autoFocus
144+
maxLength={inputLength}
145+
onChange={handleInputChange}
146+
placeholder={`${intl.formatMessage(messages.aiwidgetLabel)}...`}
147+
/>
148+
<span className="word-count">{inputLength - value.length}</span>
149+
<button
150+
className="ai-submit-button"
151+
type="submit"
152+
>
153+
<ArrowSquareUpIcon />
154+
</button>
155+
</Form>
156+
</div>
157+
</div>
158+
</div>
159+
</ModalPopup>
160+
</div>
161+
);
162+
};
163+
164+
AIWidget.propTypes = {
165+
intl: intlShape.isRequired,
166+
};
167+
168+
export default injectIntl(AIWidget);

src/components/index.js

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import AIWidget from './ai-widget/AIWidget';
2+
3+
export default AIWidget;

0 commit comments

Comments
 (0)