Skip to content

Commit

Permalink
chore: separate aifilter into subcomponents and make it nicer (#28735)
Browse files Browse the repository at this point in the history
Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Peter Kirkham <[email protected]>
Co-authored-by: Paul D'Ambra <[email protected]>
  • Loading branch information
4 people authored Feb 17, 2025
1 parent 4708bb9 commit 53f5d58
Show file tree
Hide file tree
Showing 22 changed files with 233 additions and 894 deletions.
Binary file modified frontend/__snapshots__/components-playlist--default--dark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified frontend/__snapshots__/components-playlist--default--light.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { LemonCollapse, LemonTag } from '@posthog/lemon-ui'
import { useActions, useMountedLogic, useValues } from 'kea'
import { sessionRecordingsPlaylistLogic } from 'scenes/session-recordings/playlist/sessionRecordingsPlaylistLogic'

import { AiFilterInput } from './AiFilterInput'
import { AiFilterIntro } from './AiFilterIntro'
import { aiFilterLogic } from './aiFilterLogic'
import { AiFilterSuggestions } from './AiFilterSuggestions'
import { AiFilterThread } from './AiFilterThread'
Expand All @@ -14,35 +14,11 @@ export function AiFilter(): JSX.Element {
const { messages } = useValues(filterLogic)

return (
<>
<LemonCollapse
className="mb-2"
panels={[
{
key: 'chat-with-recordings',
header: (
<div className="no-flex py-2">
<h3 className="mb-0 flex items-center gap-1">
Chat with your recording list <LemonTag type="completion">ALPHA</LemonTag>
</h3>
<div className="text-xs font-normal text-muted-alt">
Ask Max AI to find recordings matching your needs - like "show me recordings with
rage clicks" or "find recordings where users visited pricing"
</div>
</div>
),
content: (
<>
<div className="relative flex flex-col gap-3 px-4 items-center grow justify-center">
<AiFilterThread />
<AiFilterInput />
{messages.length === 0 && <AiFilterSuggestions />}
</div>
</>
),
},
]}
/>
</>
<div className="relative flex flex-col gap-3 px-4 items-center grow justify-center">
{messages.length === 0 && <AiFilterIntro />}
<AiFilterThread />
<AiFilterInput />
{messages.length === 0 && <AiFilterSuggestions />}
</div>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ export function AiFilterInput(): JSX.Element {
/>
</div>
</div>
<span className="text-xs text-muted-alt">
* Max AI currently only knows about PostHog default properties added by our SDKs. For your custom
properties, use the filters box below.
</span>
{messages.length > 0 && (
<div>
<LemonButton
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { offset } from '@floating-ui/react'
import { FilmCameraHog } from 'lib/components/hedgehogs'
import { AIConsentPopoverWrapper } from 'scenes/settings/organization/AIConsentPopoverWrapper'

export function AiFilterIntro(): JSX.Element {
return (
<>
<div className="flex">
<AIConsentPopoverWrapper placement="right-end" middleware={[offset(-12)]} showArrow>
<FilmCameraHog className="w-20 h-20" />
</AIConsentPopoverWrapper>
</div>
<div className="text-center mb-3">
<h2 className="text-2xl font-bold mb-2 text-balance">Chat with your recordings</h2>
<div className="text-secondary text-balance">
I'm Max, here to help you to find recordings matching your needs.
</div>
</div>
</>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
ChatCompletionSystemMessageParam,
ChatCompletionUserMessageParam,
} from 'openai/resources/chat/completions'
import posthog from 'posthog-js'

import { RecordingUniversalFilters } from '~/types'

Expand Down Expand Up @@ -67,6 +68,7 @@ export const aiFilterLogic = kea<aiFilterLogicType>([
}),
listeners(({ actions, values, props }) => ({
handleSend: () => {
posthog.capture('ai_filter_send')
const newMessages = [
...values.messages,
{
Expand All @@ -92,6 +94,7 @@ export const aiFilterLogic = kea<aiFilterLogicType>([
if (content.hasOwnProperty('result')) {
if (content.result === 'filter') {
props.setFilters(content.data)
posthog.capture('ai_filter_success')
}

actions.setMessages([
Expand All @@ -110,13 +113,15 @@ export const aiFilterLogic = kea<aiFilterLogicType>([
content: 'Sorry, I was unable to process your request. Please try again.',
} as ChatCompletionAssistantMessageParam,
])
posthog.capture('ai_filter_error')
}

actions.setIsLoading(false)
},
handleReset: () => {
actions.setMessages([])
props.resetFilters()
posthog.capture('ai_filter_reset')
},
})),
])
206 changes: 131 additions & 75 deletions frontend/src/scenes/session-recordings/playlist/Playlist.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
import './Playlist.scss'

import { LemonCollapse, LemonSkeleton, Tooltip } from '@posthog/lemon-ui'
import { IconAIText, IconX } from '@posthog/icons'
import { LemonButton, LemonCollapse, LemonSkeleton, LemonTag, Tooltip } from '@posthog/lemon-ui'
import clsx from 'clsx'
import { FlaggedFeature } from 'lib/components/FlaggedFeature'
import { FEATURE_FLAGS } from 'lib/constants'
import { useResizeBreakpoints } from 'lib/hooks/useResizeObserver'
import { LemonTableLoader } from 'lib/lemon-ui/LemonTable/LemonTableLoader'
import { range } from 'lib/utils'
import posthog from 'posthog-js'
import { ReactNode, useRef, useState } from 'react'
import { DraggableToNotebook } from 'scenes/notebooks/AddToNotebook/DraggableToNotebook'
import { AiFilter } from 'scenes/session-recordings/components/AiFilter/AiFilter'

import { SessionRecordingType } from '~/types'

Expand Down Expand Up @@ -83,6 +88,8 @@ export function Playlist({
750: 'medium',
})

const [isExpanded, setIsExpanded] = useState(false)

const onChangeActiveItem = (item: SessionRecordingType): void => {
setControlledActiveItemId(item.id)
onSelect?.(item)
Expand Down Expand Up @@ -132,90 +139,139 @@ export function Playlist({
.flatMap((s) => s.items).length

return (
<div className="flex flex-col xl:flex-row w-full gap-2 h-full">
<>
<div
ref={playlistRef}
data-attr={dataAttr}
className={clsx('Playlist w-full xl:max-w-80 min-w-60 min-h-96', {
'Playlist--wide': size !== 'small',
'Playlist--embedded': embedded,
className={clsx(`w-full mb-8`, {
hidden: !isExpanded,
})}
>
<div
ref={playlistListRef}
className="Playlist__list flex flex-col relative overflow-hidden h-full w-full"
>
<DraggableToNotebook href={notebooksHref}>{filterActions}</DraggableToNotebook>

<div className="flex flex-col relative w-full bg-bg-light overflow-hidden h-full Playlist__list">
<DraggableToNotebook href={notebooksHref}>
<div className="flex flex-col gap-1">
<div className="shrink-0 bg-bg-3000 relative flex justify-between items-center gap-0.5 whitespace-nowrap border-b">
{title && <TitleWithCount title={title} count={itemsCount} />}
{headerActions}
</div>
<LemonTableLoader loading={loading} />
</div>
</DraggableToNotebook>
<div className="overflow-y-auto flex-1" onScroll={handleScroll} ref={contentRef}>
{sectionCount > 1 ? (
<LemonCollapse
defaultActiveKeys={openSections}
panels={sections.map((s) => {
return {
key: s.key,
header: s.title ?? '',
content: (
<SectionContent
section={s}
loading={!!loading}
setActiveItemId={onChangeActiveItem}
activeItemId={activeItemId}
emptyState={listEmptyState}
/>
),
className: 'p-0',
}
})}
onChange={onChangeOpenSections}
multiple
embedded
size="small"
/>
) : sectionCount === 1 ? (
<SectionContent
section={sections[0]}
loading={!!loading}
setActiveItemId={onChangeActiveItem}
activeItemId={activeItemId}
emptyState={listEmptyState}
/>
) : loading ? (
<LoadingState />
) : (
listEmptyState
)}
</div>
<div className="shrink-0 relative flex justify-between items-center gap-0.5 whitespace-nowrap">
{footerActions}
</div>
</div>
<div className="flex justify-end">
<LemonButton
icon={<IconX />}
onClick={() => {
setIsExpanded(false)
posthog.capture('ai_filter_close')
}}
/>
</div>
<AiFilter />
</div>

<div
className={clsx('Playlist h-full min-h-96 w-full min-w-96 lg:min-w-[560px] order-first xl:order-none', {
'Playlist--wide': size !== 'small',
'Playlist--embedded': embedded,
className={clsx('flex flex-col w-full gap-2 h-full', {
'xl:flex-row': true,
})}
>
{content && (
<div className="Playlist__main h-full">
{' '}
{typeof content === 'function' ? content({ activeItem }) : content}
<div className="flex flex-col gap-2 xl:max-w-80">
{!isExpanded && (
<FlaggedFeature flag={FEATURE_FLAGS.RECORDINGS_AI_FILTER}>
<div className="flex justify-center">
<LemonButton
fullWidth
type="secondary"
className="bg-white"
icon={<IconAIText />}
onClick={() => {
setIsExpanded(true)
posthog.capture('ai_filter_open')
}}
>
Ask Max AI about recordings{' '}
<LemonTag type="completion" className="ml-2">
ALPHA
</LemonTag>
</LemonButton>
</div>
</FlaggedFeature>
)}
<div
ref={playlistRef}
data-attr={dataAttr}
className={clsx('Playlist w-full min-w-60 min-h-96', {
'Playlist--wide': size !== 'small',
'Playlist--embedded': embedded,
})}
>
<div
ref={playlistListRef}
className="Playlist__list flex flex-col relative overflow-hidden h-full w-full"
>
<DraggableToNotebook href={notebooksHref}>{filterActions}</DraggableToNotebook>

<div className="flex flex-col relative w-full bg-bg-light overflow-hidden h-full Playlist__list">
<DraggableToNotebook href={notebooksHref}>
<div className="flex flex-col gap-1">
<div className="shrink-0 bg-bg-3000 relative flex justify-between items-center gap-0.5 whitespace-nowrap border-b">
{title && <TitleWithCount title={title} count={itemsCount} />}
{headerActions}
</div>
<LemonTableLoader loading={loading} />
</div>
</DraggableToNotebook>
<div className="overflow-y-auto flex-1" onScroll={handleScroll} ref={contentRef}>
{sectionCount > 1 ? (
<LemonCollapse
defaultActiveKeys={openSections}
panels={sections.map((s) => {
return {
key: s.key,
header: s.title ?? '',
content: (
<SectionContent
section={s}
loading={!!loading}
setActiveItemId={onChangeActiveItem}
activeItemId={activeItemId}
emptyState={listEmptyState}
/>
),
className: 'p-0',
}
})}
onChange={onChangeOpenSections}
multiple
embedded
size="small"
/>
) : sectionCount === 1 ? (
<SectionContent
section={sections[0]}
loading={!!loading}
setActiveItemId={onChangeActiveItem}
activeItemId={activeItemId}
emptyState={listEmptyState}
/>
) : loading ? (
<LoadingState />
) : (
listEmptyState
)}
</div>
<div className="shrink-0 relative flex justify-between items-center gap-0.5 whitespace-nowrap">
{footerActions}
</div>
</div>
</div>
</div>
)}
</div>
<div
className={clsx(
'Playlist h-full min-h-96 w-full min-w-96 lg:min-w-[560px] order-first xl:order-none',
{
'Playlist--wide': size !== 'small',
'Playlist--embedded': embedded,
}
)}
>
{content && (
<div className="Playlist__main h-full">
{' '}
{typeof content === 'function' ? content({ activeItem }) : content}
</div>
)}
</div>
</div>
</div>
</>
)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { LemonBadge, LemonButton, Link, Spinner } from '@posthog/lemon-ui'
import { BindLogic, useActions, useValues } from 'kea'
import { EmptyMessage } from 'lib/components/EmptyMessage/EmptyMessage'
import { FlaggedFeature } from 'lib/components/FlaggedFeature'
import { PropertyKeyInfo } from 'lib/components/PropertyKeyInfo'
import { FEATURE_FLAGS } from 'lib/constants'
import { LemonBanner } from 'lib/lemon-ui/LemonBanner'
Expand All @@ -12,7 +11,6 @@ import { urls } from 'scenes/urls'

import { ReplayTabs } from '~/types'

import { AiFilter } from '../components/AiFilter/AiFilter'
import { RecordingsUniversalFilters } from '../filters/RecordingsUniversalFilters'
import { SessionRecordingPlayer } from '../player/SessionRecordingPlayer'
import { SessionRecordingPreview } from './SessionRecordingPreview'
Expand Down Expand Up @@ -106,9 +104,6 @@ export function SessionRecordingsPlaylist({

return (
<BindLogic logic={sessionRecordingsPlaylistLogic} props={logicProps}>
<FlaggedFeature flag={FEATURE_FLAGS.RECORDINGS_AI_FILTER}>
<AiFilter />
</FlaggedFeature>
<div className="h-full space-y-2">
<Playlist
data-attr="session-recordings-playlist"
Expand Down
Loading

0 comments on commit 53f5d58

Please sign in to comment.