Skip to content

Commit 44ab6a0

Browse files
authored
Merge pull request #1538 from scroll-tech/ai-assistant
Ai assistant
2 parents 47299e6 + 7c960e0 commit 44ab6a0

35 files changed

+1364
-14
lines changed

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,12 @@
4343
"immer": "^9.0.7",
4444
"lodash": "^4.17.21",
4545
"motion": "^11.18.1",
46+
"nanoid": "^5.1.5",
4647
"next": "15.1.4",
4748
"notistack": "^3.0.1",
4849
"number-flip": "^1.2.3",
4950
"numbro": "^2.3.6",
51+
"openai": "^4.94.0",
5052
"path-to-regexp": "^6.2.1",
5153
"react": "19.0.0",
5254
"react-awesome-reveal": "^4.2.5",

src/app/globals.css

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55

66
@config "../../tailwind.config.ts";
77

8+
@import "../assets/css/assistant-message.css";
9+
810
@theme {
911
--breakpoint-sm: 600px;
1012
--breakpoint-md: 900px;
@@ -47,7 +49,7 @@ body {
4749
background-color: var(--theme-bg);
4850
}
4951

50-
body.mobile-top-nav-open {
52+
body.disable-body-scroll {
5153
overflow: hidden !important;
5254
}
5355

src/app/layout.tsx

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { GoogleAnalytics } from "@next/third-parties/google"
22
import { SpeedInsights } from "@vercel/speed-insights/next"
33
import clsx from "clsx"
44
import { Metadata } from "next"
5-
import { Roboto } from "next/font/google"
5+
import { Inter, Roboto } from "next/font/google"
66
import localFont from "next/font/local"
77
import React, { Suspense } from "react"
88

@@ -22,6 +22,8 @@ import "./globals.css"
2222

2323
export const metadata: Metadata = ROOT_METADATA
2424

25+
export const maxDuration = 90
26+
2527
// same as scroll documnet
2628
const robotoFont = Roboto({
2729
variable: "--font-developer",
@@ -30,6 +32,13 @@ const robotoFont = Roboto({
3032
subsets: ["latin"],
3133
})
3234

35+
const interFont = Inter({
36+
variable: "--font-inter",
37+
weight: ["400", "500", "700"],
38+
display: "swap",
39+
subsets: ["latin"],
40+
})
41+
3342
const titleFont = localFont({
3443
src: [
3544
{
@@ -55,7 +64,7 @@ export default function RootLayout({ children }: { children: React.ReactNode })
5564
<link rel="dns-prefetch" href={process.env.NEXT_PUBLIC_API_BASE_URI} crossOrigin="anonymous" />
5665
{/* <link href="https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,400;0,500;0,700;1,400;1,500;1,700&display=swap" rel="stylesheet" /> */}
5766
</head>
58-
<body className={clsx(titleFont.variable, robotoFont.variable)}>
67+
<body className={clsx(titleFont.variable, robotoFont.variable, interFont.variable)}>
5968
<InitColorSchemeScript attribute="class"></InitColorSchemeScript>
6069
<AppRouterCacheProvider options={{ key: "css" }}>
6170
<ScrollThemeProvider>

src/assets/css/assistant-message.css

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
.assistant-message {
2+
font-size: 1.6rem;
3+
line-height: 2.4rem;
4+
font-family: var(--font-inter);
5+
}
6+
7+
.assistant-message p {
8+
margin: 0 0 16px;
9+
max-width: 100%;
10+
line-height: 2.6rem;
11+
}
12+
13+
.assistant-message h1,
14+
.assistant-message h2,
15+
.assistant-message h3 {
16+
font-weight: 700;
17+
margin-bottom: 1.6rem;
18+
}
19+
20+
.assistant-message ol {
21+
list-style-type: decimal;
22+
margin-left: 1.8rem;
23+
margin-bottom: 1.6rem;
24+
}
25+
.assistant-message > ol > li {
26+
margin-bottom: 1.6rem;
27+
}
28+
.assistant-message > ol > li::marker {
29+
font-weight: 700;
30+
}
31+
.assistant-message > ol > li p {
32+
margin-bottom: 0;
33+
}
34+
35+
.assistant-message ul {
36+
list-style-type: disc;
37+
margin-left: 1.5rem;
38+
margin-bottom: 1.6rem;
39+
}
40+
41+
.assistant-message li {
42+
margin-bottom: 0.8rem;
43+
}
44+
45+
.assistant-message blockquote:not(:empty) {
46+
border-left: 2px solid #9b9b9b;
47+
font-style: italic;
48+
font-size: 15px;
49+
color: #777;
50+
padding: 8px 8px 8px 16px;
51+
margin-bottom: 1.6rem;
52+
}
53+
.assistant-message blockquote p:last-child {
54+
margin-bottom: 0;
55+
}
56+
57+
.assistant-message pre {
58+
background-color: #1010100d;
59+
padding: 0.8rem 1.6rem;
60+
border-radius: 0.4rem;
61+
margin-bottom: 1.6rem;
62+
}
63+
64+
.assistant-message pre code {
65+
background-color: transparent;
66+
padding: 0;
67+
margin: 0;
68+
font-size: inherit;
69+
line-height: inherit;
70+
}
71+
72+
.assistant-message code {
73+
background-color: #1010100d;
74+
padding: 0.2rem 0.4rem;
75+
border-radius: 0.2rem;
76+
}
77+
78+
.assistant-message a {
79+
color: #ff684b;
80+
font-weight: 500;
81+
text-decoration: underline;
82+
text-underline-position: from-font;
83+
}
84+
85+
.assistant-message img {
86+
background-color: transparent;
87+
}
88+
89+
.assistant-message strong {
90+
font-weight: 700;
91+
}

src/assets/images/common/ai-bot.png

25.5 KB
Loading
53.2 KB
Loading

src/assets/svgs/header/checked.svg

Lines changed: 3 additions & 0 deletions
Loading

src/assets/svgs/header/close.svg

Lines changed: 3 additions & 0 deletions
Loading

src/assets/svgs/header/copy.svg

Lines changed: 3 additions & 0 deletions
Loading

src/assets/svgs/header/enter.svg

Lines changed: 3 additions & 0 deletions
Loading

src/assets/svgs/header/re-send.svg

Lines changed: 3 additions & 0 deletions
Loading

src/assets/svgs/header/send.svg

Lines changed: 4 additions & 0 deletions
Loading

src/assets/svgs/header/spin.svg

Lines changed: 11 additions & 0 deletions
Loading

src/assets/svgs/header/thumb-down.svg

Lines changed: 3 additions & 0 deletions
Loading

src/assets/svgs/header/thumb-up.svg

Lines changed: 3 additions & 0 deletions
Loading

src/components/AIModal/AIInput.tsx

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { IconButton, Stack, TextareaAutosize } from "@mui/material"
2+
3+
import SendSvg from "@/assets/svgs/header/send.svg"
4+
5+
const AIInput = props => {
6+
const { value, disabled, onChat, ...restProps } = props
7+
8+
const handleKeyDown = e => {
9+
if (e.key === "Enter" && !e.shiftKey && !disabled) {
10+
e.preventDefault()
11+
onChat(value.trim())
12+
}
13+
}
14+
15+
return (
16+
<Stack
17+
direction="row"
18+
sx={{
19+
borderRadius: "2.8rem",
20+
border: disabled ? "2px solid #1010101A" : "2px solid #101010",
21+
p: "0.6rem",
22+
gap: "1.6rem",
23+
alignItems: "center",
24+
}}
25+
>
26+
<TextareaAutosize
27+
maxRows={4}
28+
placeholder="Message"
29+
autoFocus
30+
style={{
31+
fontSize: "1.6rem",
32+
lineHeight: "2.4rem",
33+
border: "none",
34+
outline: "none",
35+
resize: "none",
36+
width: "100%",
37+
paddingLeft: "1.6rem",
38+
scrollbarColor: "#ececec transparent",
39+
scrollbarWidth: "thin",
40+
}}
41+
value={value}
42+
{...restProps}
43+
onKeyDown={handleKeyDown}
44+
></TextareaAutosize>
45+
<IconButton sx={{ p: 0, opacity: disabled ? "0.4" : "1" }} aria-label="Enter" onClick={() => onChat(value)}>
46+
<SendSvg></SendSvg>
47+
</IconButton>
48+
</Stack>
49+
)
50+
}
51+
52+
export default AIInput
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import React, { useState } from "react"
2+
import ReactMarkdown from "react-markdown"
3+
import remarkGfm from "remark-gfm"
4+
5+
import { Box } from "@mui/material"
6+
7+
import Operation from "./Operation"
8+
9+
const AssistantMessage = props => {
10+
const { children, feedback, allowOperation, isLast, onRetry, onThumbUp, onThumbDown } = props
11+
12+
const [operationVisible, setOperationVisible] = useState<boolean>(false)
13+
14+
const handlePopoverOpen = (event: React.MouseEvent<HTMLElement>) => {
15+
setOperationVisible(true)
16+
}
17+
18+
const handlePopoverClose = () => {
19+
setOperationVisible(false)
20+
}
21+
22+
return (
23+
<>
24+
<Box onMouseEnter={handlePopoverOpen} onMouseLeave={handlePopoverClose}>
25+
<ReactMarkdown
26+
children={children as string}
27+
remarkPlugins={[remarkGfm]}
28+
components={{
29+
a: ({ node, ...props }) => <a {...props} target="_blank" rel="noopener noreferrer" />,
30+
}}
31+
className="assistant-message"
32+
/>
33+
<Box sx={{ height: "2rem", marginTop: "-0.4rem", marginBottom: "1.6rem" }}>
34+
<Operation
35+
visible={allowOperation && (isLast || operationVisible)}
36+
message={children as string}
37+
feedback={feedback}
38+
onThumbUp={onThumbUp}
39+
onThumbDown={onThumbDown}
40+
onRetry={onRetry}
41+
></Operation>
42+
</Box>
43+
</Box>
44+
</>
45+
)
46+
}
47+
48+
export default AssistantMessage
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { AnimatePresence, motion } from "motion/react"
2+
import { useEffect } from "react"
3+
4+
import { Box } from "@mui/material"
5+
6+
const MotionBox = motion(Box)
7+
const FeedbackAlert = props => {
8+
const { sx, open, duration, children, onClose } = props
9+
10+
useEffect(() => {
11+
if (open) {
12+
const timer = setTimeout(() => {
13+
onClose?.()
14+
}, duration || 2e3) // Auto close after 3 seconds
15+
return () => clearTimeout(timer)
16+
}
17+
}, [open])
18+
19+
return (
20+
<AnimatePresence>
21+
{open && (
22+
<MotionBox
23+
initial={{ opacity: 0, y: -20 }}
24+
animate={{ opacity: 1, y: 0 }}
25+
exit={{ opacity: 0, y: 20 }}
26+
transition={{ duration: 0.3 }}
27+
sx={{
28+
borderRadius: "0.4rem",
29+
backgroundColor: "#101010",
30+
color: "#fff",
31+
fontSize: "1.2rem",
32+
padding: "0.4rem 1.2rem",
33+
34+
...sx,
35+
}}
36+
>
37+
{children}
38+
</MotionBox>
39+
)}
40+
</AnimatePresence>
41+
)
42+
}
43+
44+
export default FeedbackAlert

0 commit comments

Comments
 (0)