Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Max AI chatbot #10284

Open
wants to merge 61 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
1bd74aa
max chat
smallbrownbike Dec 20, 2024
23f1a88
use MatterVF in Inkeep chat
corywatilo Dec 30, 2024
36703ae
💅
smallbrownbike Dec 31, 2024
514c9f5
colors
smallbrownbike Dec 31, 2024
8107328
close disclaimer
smallbrownbike Dec 31, 2024
4c544d8
remove random header
smallbrownbike Jan 1, 2025
88b7cc5
revert react upgrade/use diff inkeep version
smallbrownbike Jan 1, 2025
77aba21
add loading state
smallbrownbike Jan 1, 2025
1f53dcf
mvp max ai callout in docs
corywatilo Jan 7, 2025
438c7ee
add docs page count
smallbrownbike Jan 7, 2025
951eb36
add forum suggestion
smallbrownbike Jan 7, 2025
bd57460
copy
corywatilo Jan 7, 2025
9473152
keyboard shortcut
smallbrownbike Jan 7, 2025
a64aa1e
styling
corywatilo Jan 7, 2025
0418cf0
copy
corywatilo Jan 7, 2025
f597538
kbd shrtct
corywatilo Jan 7, 2025
1293760
Merge branch 'chat' of https://github.com/PostHog/posthog.com into chat
corywatilo Jan 7, 2025
8505668
alignment
corywatilo Jan 7, 2025
6a00267
make bolding work
corywatilo Jan 7, 2025
82d2981
better text
corywatilo Jan 7, 2025
72b51ca
move chat
smallbrownbike Jan 7, 2025
147567a
styling
corywatilo Jan 7, 2025
c565b29
Merge branch 'chat' of https://github.com/PostHog/posthog.com into chat
corywatilo Jan 7, 2025
763a807
add stylesheet
smallbrownbike Jan 7, 2025
070ff4b
restored product analytics header
corywatilo Jan 7, 2025
fb38cb8
secure images
smallbrownbike Jan 7, 2025
ef06727
updated formatting
corywatilo Jan 7, 2025
504e47d
Merge branch 'chat' of https://github.com/PostHog/posthog.com into chat
corywatilo Jan 7, 2025
eeef987
absolute position the disclaimer
smallbrownbike Jan 8, 2025
6f91c03
mobile fixes
smallbrownbike Jan 8, 2025
c01e68e
bg color/remove bg blur
smallbrownbike Jan 8, 2025
9a03e40
auto update dark mode
smallbrownbike Jan 8, 2025
f975f13
chat notification indication
smallbrownbike Jan 8, 2025
dffbb95
new questions
corywatilo Jan 8, 2025
dc4cfc3
add initial logEventCallback
smallbrownbike Jan 8, 2025
483d851
Merge remote-tracking branch 'refs/remotes/origin/chat' into chat
smallbrownbike Jan 8, 2025
78e1fd1
open chat search param
smallbrownbike Jan 8, 2025
4a84cf6
better message ui (light mode)
corywatilo Jan 8, 2025
7b9ccb1
Merge branch 'chat' of https://github.com/PostHog/posthog.com into chat
corywatilo Jan 8, 2025
4508b8a
true -> open
smallbrownbike Jan 8, 2025
b85d18d
polished input colors
corywatilo Jan 8, 2025
3533c7f
moved ask max to a component
corywatilo Jan 8, 2025
495f89a
design polish
corywatilo Jan 8, 2025
fd00185
componentized intro
corywatilo Jan 8, 2025
68cc389
added some variables
corywatilo Jan 8, 2025
6cecb91
Merge branch 'master' into chat
corywatilo Jan 8, 2025
e152ec7
Update yarn.lock
corywatilo Jan 8, 2025
8de9607
added max to all docs index pages
corywatilo Jan 8, 2025
e7ea4c4
removed placeholder images
corywatilo Jan 8, 2025
0fa41ea
added max to docs pages
corywatilo Jan 8, 2025
7fbee7e
link product names
corywatilo Jan 8, 2025
49c20d8
conditional ask max cta
smallbrownbike Jan 8, 2025
df9281d
disable cta in app
smallbrownbike Jan 8, 2025
02230c6
move everything into chat provider
smallbrownbike Jan 9, 2025
14ff49a
added events for opening/closing maxai chat
corywatilo Jan 9, 2025
9095e33
Merge branch 'chat' of https://github.com/PostHog/posthog.com into chat
corywatilo Jan 9, 2025
c933f18
remove border
smallbrownbike Jan 9, 2025
944e3be
Merge remote-tracking branch 'refs/remotes/origin/chat' into chat
smallbrownbike Jan 9, 2025
5d15f8d
use usePostHog
smallbrownbike Jan 9, 2025
19a798d
setChat -> openChat/closeChat
smallbrownbike Jan 9, 2025
e3fdb3f
allow quick question updates
smallbrownbike Jan 9, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions gatsby-browser.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,15 @@ import Posts from './src/components/Edition/Posts'
import { Provider as ToastProvider } from './src/context/toast'
import { RouteUpdateArgs } from 'gatsby'
import { UserProvider } from './src/hooks/useUser'

import { ChatProvider } from './src/hooks/useChat'
import Chat from './src/components/Chat'
initKea(false)

export const wrapRootElement = ({ element }) => (
<UserProvider>
<ToastProvider>{wrapElement({ element })}</ToastProvider>
<ToastProvider>
<ChatProvider>{wrapElement({ element })}</ChatProvider>
</ToastProvider>
</UserProvider>
)
export const onRouteUpdate = ({ location, prevLocation }: RouteUpdateArgs) => {
Expand Down
59 changes: 31 additions & 28 deletions gatsby-ssr.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,39 +13,42 @@ import Job from './src/templates/Job'
import { UserProvider } from './src/hooks/useUser'
import Posts from './src/components/Edition/Posts'
import { Provider as ToastProvider } from './src/context/toast'

import { ChatProvider } from './src/hooks/useChat'
import Chat from './src/components/Chat'
export const wrapPageElement = ({ element, props }) => {
const slug = props.location.pathname.substring(1)
initKea(true, props.location)
return (
<UserProvider>
{wrapElement({
element:
!/^posts\/new|^posts\/(.*)\/edit/.test(slug) &&
(props.pageContext.post || /^posts|^changelog\/(.*?)\//.test(slug)) ? (
<Posts {...props}>{element}</Posts>
) : props.custom404 || !props.data || props.pageContext.ignoreWrapper ? (
element
) : /^handbook|^docs\/(?!api)|^manual/.test(slug) &&
![
'docs/api/post-only-endpoints',
'docs/api/user',
'docs/integrations',
'docs/product-analytics',
'docs/session-replay',
'docs/feature-flags',
'docs/experiments',
'docs/data',
].includes(slug) ? (
<HandbookLayout {...props} />
) : /^session-replay|^product-analytics|^feature-flags|^experiments|^product-os/.test(slug) ? (
<Product {...props} />
) : /^careers\//.test(slug) ? (
<Job {...props} />
) : (
element
),
})}
<ChatProvider>
{wrapElement({
element:
!/^posts\/new|^posts\/(.*)\/edit/.test(slug) &&
(props.pageContext.post || /^posts|^changelog\/(.*?)\//.test(slug)) ? (
<Posts {...props}>{element}</Posts>
) : props.custom404 || !props.data || props.pageContext.ignoreWrapper ? (
element
) : /^handbook|^docs\/(?!api)|^manual/.test(slug) &&
![
'docs/api/post-only-endpoints',
'docs/api/user',
'docs/integrations',
'docs/product-analytics',
'docs/session-replay',
'docs/feature-flags',
'docs/experiments',
'docs/data',
].includes(slug) ? (
<HandbookLayout {...props} />
) : /^session-replay|^product-analytics|^feature-flags|^experiments|^product-os/.test(slug) ? (
<Product {...props} />
) : /^careers\//.test(slug) ? (
<Job {...props} />
) : (
element
),
})}
</ChatProvider>
</UserProvider>
)
}
Expand Down
1 change: 1 addition & 0 deletions gatsby/createPages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -554,6 +554,7 @@ export const createPages: GatsbyNode['createPages'] = async ({ actions: { create
slug,
post: true,
article: true,
askMax: true,
},
})
})
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,10 @@
"@headlessui/tailwindcss": "^0.1.1",
"@heroicons/react": "^1.0.6",
"@hubspot/api-client": "^7.1.2",
"@inkeep/uikit": "^0.3.19",
"@inkeep/uikit-js": "^0.3.19",
"@popperjs/core": "^2.11.2",
"@posthog/icons": "0.9.4",
"@posthog/icons": "0.9.5",
"@tailwindcss/container-queries": "^0.1.1",
"@types/lodash.groupby": "^4.6.7",
"@types/react-slick": "^0.23.10",
Expand Down
73 changes: 73 additions & 0 deletions src/components/AskMax/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import React from 'react'
import { useChat } from 'hooks/useChat'
import { useStaticQuery } from 'gatsby'
import { graphql } from 'gatsby'
import { IconLightBulb, IconSidebarOpen } from '@posthog/icons'
import { CallToAction } from 'components/CallToAction'
import { useLayoutData } from 'components/Layout/hooks'
import usePostHog from 'hooks/usePostHog'

interface AskMaxProps {
border?: boolean
className?: string
quickQuestions?: string[]
}

export default function AskMax({ border = false, className = '', quickQuestions }: AskMaxProps) {
const posthog = usePostHog()
const { compact } = useLayoutData()
const { openChat, setQuickQuestions } = useChat()
const {
allDocsPages: { totalDocsCount },
} = useStaticQuery(graphql`
query {
allDocsPages: allMdx(filter: { slug: { regex: "^/docs/" } }) {
totalDocsCount: totalCount
}
}
`)

const borderClasses = border ? 'py-6 mt-4 border-y border-light dark:border-dark' : 'mb-8'

const handleChatOpen = () => {
posthog?.capture('Opened MaxAI chat')
if (quickQuestions) {
setQuickQuestions(quickQuestions)
}
openChat()
}

return compact ? null : (
<div className="@container">
<div
className={`flex flex-col @lg:flex-row items-center justify-center @3xl:justify-start gap-4 @2xl:!gap-8 relative py-2 w-full @2xl:w-auto ${borderClasses} ${className}`}
>
<div className="flex-1 @2xl:flex-[0_0_auto] flex flex-col @lg:flex-row items-center justify-center gap-4">
<div>
<IconLightBulb className="size-10 inline-block bg-accent dark:bg-accent-dark rounded p-2 text-primary/50 dark:text-primary-dark/50" />
</div>

<div className="flex flex-col text-center @lg:text-left">
<h3 className="mb-0 !text-2xl @lg:!text-xl leading-tight">
Questions? <span className="text-red dark:text-yellow">Ask Max AI.</span>
</h3>
<p className="text-[15px] mb-0 opacity-75 text-balance">
It's easier than reading through <strong>{totalDocsCount} docs articles</strong>.
</p>
</div>
</div>
<div>
<CallToAction
type="secondary"
size="md"
className="group [&>span]:flex [&>span]:items-center [&>span]:gap-1.5 [&>span]:px-3"
onClick={handleChatOpen}
>
Chat with Max AI
<IconSidebarOpen className="size-6 inline-block opacity-75 group-hover:opacity-100" />
</CallToAction>
</div>
</div>
</div>
)
}
25 changes: 25 additions & 0 deletions src/components/Chat/Inkeep.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import React, { useEffect, useRef } from 'react'
import { DotLottiePlayer } from '@dotlottie/react-player'
import { useChat } from 'hooks/useChat'

export default function InkeepEmbeddedChat(): JSX.Element {
const lottieRef = useRef(null)
const { loading, renderChat } = useChat()

useEffect(() => {
renderChat('#embedded-chat-target')
}, [])

return (
<>
{loading && (
<div className="flex-grow size-full flex items-center justify-center">
<div className="size-18">
<DotLottiePlayer loop lottieRef={lottieRef} src="/lotties/loading.lottie" autoplay />
</div>
</div>
)}
<div id="embedded-chat-target" className={loading ? 'hidden' : 'flex-grow h-full'} />
</>
)
}
106 changes: 106 additions & 0 deletions src/components/Chat/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import React, { useEffect, useRef, useState } from 'react'
import { AnimatePresence, motion } from 'framer-motion'
import InkeepEmbeddedChat from './Inkeep'
import { useChat } from 'hooks/useChat'
import { IconChevronDown, IconX } from '@posthog/icons'
import usePostHog from 'hooks/usePostHog'
import { defaultQuickQuestions } from 'hooks/useInkeepSettings'

export default function Chat(): JSX.Element | null {
const posthog = usePostHog()
const { chatOpen, closeChat, chatting, setQuickQuestions } = useChat()
const [height, setHeight] = useState<string | number>('100%')
const [showDisclaimer, setShowDisclaimer] = useState(true)
const chatRef = useRef<HTMLDivElement>(null)

const handleAnimationComplete = () => {
if (!chatOpen) {
setQuickQuestions(defaultQuickQuestions)
}
}

useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (chatRef.current && !chatRef.current.contains(event.target as Node)) {
closeChat()
}
}

if (chatOpen) {
document.addEventListener('mousedown', handleClickOutside)
}

return () => {
document.removeEventListener('mousedown', handleClickOutside)
}
}, [chatOpen])

useEffect(() => {
const handleEscKey = (event: KeyboardEvent) => {
if (event.key === 'Escape' && chatOpen) {
closeChat()
}
}

document.addEventListener('keydown', handleEscKey)
return () => document.removeEventListener('keydown', handleEscKey)
}, [chatOpen])

useEffect(() => {
const mobileNav = document?.getElementById('mobile-nav')
const height = mobileNav?.clientHeight
setHeight(`calc(100% - ${height ?? 0}px)`)
}, [chatOpen])

return (
<AnimatePresence>
{chatting ? (
<motion.div
ref={chatRef}
initial={{ translateX: '110%' }}
animate={{ translateX: chatOpen ? 0 : '110%', transition: { type: 'tween' } }}
className="fixed bottom-0 right-0 h-full bg-white dark:bg-dark z-[999999] border-l border-border dark:border-dark w-[350px] sm:w-[400px]"
onAnimationComplete={handleAnimationComplete}
>
<button
onClick={() => {
closeChat()
posthog?.capture('Closed MaxAI chat')
}}
className={`absolute left-0 -translate-x-full z-10 rounded-tl rounded-bl py-1 border-l border-t border-b border-border dark:border-dark group transition-colors bg-white dark:bg-[#1c1c1c] pr-0.5 top-[35px]`}
>
<IconChevronDown className="size-8 opacity-60 group-hover:opacity-100 transition-opacity -rotate-90 relative left-1" />
</button>
<div style={{ height }}>
<AnimatePresence>
{showDisclaimer && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1, transition: { duration: 0.2 } }}
exit={{ opacity: 0, transition: { duration: 0.2 } }}
className="absolute top-0 left-0 w-full z-10"
>
<div className="m-2 p-2 flex items-center justify-between bg-[#feedd5] dark:bg-dark border border-light dark:border-dark rounded overflow-hidden flex-shrink-0">
<p className="m-0 pl-4 text-sm opacity-70 flex-1">
Use{' '}
<kbd
className={`box-content p-[5px] border border-b-2 border-border dark:border-dark rounded-[3px] inline-flex text-black/35 dark:text-white/40 text-code text-xs py-0 bg-white dark:bg-accent-dark`}
>
/
</kbd>{' '}
to search PostHog.com
</p>
<button className="" onClick={() => setShowDisclaimer(false)}>
<IconX className="size-4 opacity-60 hover:opacity-100 transition-opacity" />
</button>
</div>
</motion.div>
)}
</AnimatePresence>
<InkeepEmbeddedChat />
</div>
</motion.div>
) : null}
</AnimatePresence>
)
}
1 change: 1 addition & 0 deletions src/components/CloudinaryImage/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export default function CloudinaryImage({ src, width, placeholder, className = '
publicId={cloudinaryPublicId}
cloudName={process.env.GATSBY_CLOUDINARY_CLOUD_NAME}
className={imgClassName}
secure
>
<Transformation width={width} crop="scale" />
</Image>
Expand Down
2 changes: 1 addition & 1 deletion src/components/CommunityQuestions/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export default function CommunityQuestions() {
return (
<div className="max-w-[600px] mt-12">
<h3 id="squeak-questions" className="mb-4">
Questions?
Community questions
</h3>
<Squeak />
</div>
Expand Down
30 changes: 30 additions & 0 deletions src/components/Docs/Intro.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import React from 'react'
import { CallToAction } from '../CallToAction'
import CloudinaryImage from '../CloudinaryImage'

export default function Intro({ subheader, title, description, buttonText, buttonLink, imageColumnClasses, imageUrl, imageClasses}) {
return (
<div className="bg-accent dark:bg-accent-dark border border-light dark:border-dark rounded flex flex-col items-center md:flex-row md:gap-4 mb-8">
<div className="p-4 pb-0 md:p-8 flex-1 w-full">
<p className="text-[15px] text-primary/60 dark:text-primary-dark/75 mb-1">{subheader}</p>
<h1 className="text-3xl md:text-4xl mt-0 mb-1 md:mb-2">{title}</h1>
<h3 className="text-base md:text-lg font-semibold text-primary/60 dark:text-primary-dark/75 !leading-tight"> {description}</h3>
<CallToAction to={buttonLink}>{buttonText}</CallToAction>
</div>

{imageUrl && (
<figure className="m-0 mt-auto p-0 md:pr-8 flex items-end">
<CloudinaryImage
alt=""
placeholder="none"
quality={100}
className={imageColumnClasses}
imgClassName={imageClasses}
src={imageUrl}
/>
</figure>
)}

</div>
)
}
16 changes: 9 additions & 7 deletions src/components/Docs/ResourceItem.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,22 @@ export default function ResourceItem({ title, description, Image, gatsbyImage, u
return (
<li className="list-none bg-accent dark:bg-accent-dark border border-light dark:border-dark rounded relative hover:top-[-2px] active:top-[1px] hover:transition-all overflow-hidden">
<Link to={url} className="block">
<div className="px-4 py-3 pb-0">
<div className={`px-4 py-3 ${Image ? 'pb-0' : ''}`}>
{type && (
<h6 className="text-[13px] text-primary/50 dark:text-primary-dark/50 font-medium m-0 p-0">
<h6 className="text-sm text-primary/50 dark:text-primary-dark/50 font-medium mb-1 p-0">
{type}
</h6>
)}
<h4 className="m-0 text-lg leading-tight text-primary dark:text-primary-dark">{title}</h4>
<p className="text-primary/60 dark:text-primary-dark/60 text-sm m-0">{description}</p>
<p className="text-primary/60 dark:text-primary-dark/60 text-[15px] mt-1 mb-0 !leading-tight">{description}</p>
</div>
<div className="flex justify-end w-full h-24">
<div className="w-48 h-24 md:absolute bottom-0">
{gatsbyImage ? <GatsbyImage image={getImage(gatsbyImage)} /> : Image}
{Image && (
<div className="flex justify-end w-full h-24">
<div className="w-48 h-24 md:absolute bottom-0">
{Image}
</div>
</div>
</div>
)}
</Link>
</li>
)
Expand Down
Loading
Loading