Skip to content

Commit

Permalink
chore: separate aifilter into subcomponents and make it nicer (#28607)
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]>
  • Loading branch information
3 people authored Feb 14, 2025
1 parent aedb73a commit 75a2b82
Show file tree
Hide file tree
Showing 9 changed files with 234 additions and 137 deletions.
141 changes: 14 additions & 127 deletions frontend/src/scenes/session-recordings/components/AiFilter/AiFilter.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,17 @@
/**
* @fileoverview A component that allows user to "chat with recordings" using Max AI
*/
import { IconAIText, IconPerson, IconTrash } from '@posthog/icons'
import { LemonButton, LemonCollapse, LemonInput, LemonTag } from '@posthog/lemon-ui'
import { BuiltLogic, useActions, useValues } from 'kea'
import { maxGlobalLogic } from 'scenes/max/maxGlobalLogic'
import { sessionRecordingsPlaylistLogicType } from 'scenes/session-recordings/playlist/sessionRecordingsPlaylistLogicType'
import { LemonCollapse, LemonTag } from '@posthog/lemon-ui'
import { useActions, useMountedLogic, useValues } from 'kea'
import { sessionRecordingsPlaylistLogic } from 'scenes/session-recordings/playlist/sessionRecordingsPlaylistLogic'

import { AiConsentPopover } from '../AiConsentPopover'
import { AiFilterInput } from './AiFilterInput'
import { aiFilterLogic } from './aiFilterLogic'
import { AiFilterSuggestions } from './AiFilterSuggestions'
import { AiFilterThread } from './AiFilterThread'

export function AiFilter({ logic }: { logic: BuiltLogic<sessionRecordingsPlaylistLogicType> }): JSX.Element {
const { setFilters, resetFilters } = useActions(logic)
export function AiFilter(): JSX.Element {
const mountedLogic = useMountedLogic(sessionRecordingsPlaylistLogic)
const { setFilters, resetFilters } = useActions(mountedLogic)
const filterLogic = aiFilterLogic({ setFilters, resetFilters })
const { messages, input, isLoading } = useValues(filterLogic)
const { setInput, handleReset, handleSend } = useActions(filterLogic)
const { dataProcessingAccepted } = useValues(maxGlobalLogic)
const { messages } = useValues(filterLogic)

return (
<>
Expand All @@ -37,119 +33,10 @@ export function AiFilter({ logic }: { logic: BuiltLogic<sessionRecordingsPlaylis
),
content: (
<>
<div className="gap-2 justify-center flex flex-col">
<div>
{messages.length > 0 && (
<div className="max-w-1/2 min-w-96">
{messages
.filter((message) => message.role !== 'system')
.map((message, index) => (
<div
key={index}
className="border rounded border-gray-200 p-2 bg-white my-2"
>
{message.role === 'user' ? (
<>
<strong>
<IconPerson />
You:
</strong>{' '}
{message.content}
</>
) : (
<>
<strong className="text-accent-primary">
<IconAIText />
Max AI:
</strong>{' '}
{message.content}
</>
)}
</div>
))}
</div>
)}
{messages.length > 0 && (
<div>
<LemonButton
icon={<IconTrash />}
onClick={handleReset}
disabled={isLoading}
type="tertiary"
size="xsmall"
>
Reset
</LemonButton>
</div>
)}
<div className="flex items-center gap-x-2">
<div>
<LemonInput
value={input}
onChange={(value) => setInput(value)}
placeholder="Show me recordings of people who ..."
className="my-2 w-96"
onKeyDown={(e) => {
if (e.key === 'Enter') {
handleSend()
}
}}
/>
</div>
<div>
<LemonButton
onClick={handleSend}
disabled={
isLoading || input.length === 0 || !dataProcessingAccepted
}
loading={isLoading}
type="primary"
size="small"
>
{isLoading ? '' : 'Send'}
</LemonButton>
</div>
<AiConsentPopover />
</div>
</div>
{messages.length === 0 && (
<div>
<strong className="text-sm">People usually ask Max AI:</strong>
<LemonButton
className="mb-1"
type="secondary"
onClick={() =>
setInput(
'Show me recordings of people who visited sign up page in the last 24 hours'
)
}
>
<span className="font-normal text-sx italic">
Show me recordings of people who visited sign up page in the last 24
hours
</span>
</LemonButton>
<LemonButton
className="mb-1"
type="secondary"
onClick={() =>
setInput('Show me recordings of people who are frustrated')
}
>
<span className="font-normal text-sx italic">
Show me recordings of people who are frustrated
</span>
</LemonButton>
<LemonButton
type="secondary"
onClick={() => setInput('Show me recordings of people who facing bugs')}
>
<span className="font-normal text-sx italic">
Show me recordings of people who facing bugs
</span>
</LemonButton>
</div>
)}
<div className="relative flex flex-col gap-3 px-4 items-center grow justify-center">
<AiFilterThread />
<AiFilterInput />
{messages.length === 0 && <AiFilterSuggestions />}
</div>
</>
),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { IconArrowRight, IconRewind } from '@posthog/icons'
import { LemonButton, LemonTextArea } from '@posthog/lemon-ui'
import { useActions, useMountedLogic, useValues } from 'kea'
import { maxGlobalLogic } from 'scenes/max/maxGlobalLogic'
import { sessionRecordingsPlaylistLogic } from 'scenes/session-recordings/playlist/sessionRecordingsPlaylistLogic'

import { AiConsentPopover } from '../AiConsentPopover'
import { aiFilterLogic } from './aiFilterLogic'

export function AiFilterInput(): JSX.Element {
const mountedLogic = useMountedLogic(sessionRecordingsPlaylistLogic)
const { setFilters, resetFilters } = useActions(mountedLogic)
const filterLogic = aiFilterLogic({ setFilters, resetFilters })
const { messages, input, isLoading } = useValues(filterLogic)
const { setInput, handleSend, handleReset } = useActions(filterLogic)
const { dataProcessingAccepted } = useValues(maxGlobalLogic)

return (
<>
<div className="w-[min(44rem,100%)] relative">
<LemonTextArea
value={input}
onChange={(value) => setInput(value)}
placeholder={
isLoading
? 'Thinking…'
: messages.length === 0
? 'Show me recordings of people who ...'
: 'Ask follow-up'
}
onPressEnter={() => {
if (input) {
handleSend()
}
}}
minRows={1}
maxRows={10}
className="p-3"
autoFocus
disabled={isLoading}
/>
<div className="absolute top-0 bottom-0 flex items-center right-2">
<LemonButton
type={messages.length === 0 ? 'primary' : 'secondary'}
onClick={handleSend}
tooltip="Let's go!"
disabled={isLoading || input.length === 0 || !dataProcessingAccepted}
size="small"
icon={<IconArrowRight />}
/>
</div>
</div>
{messages.length > 0 && (
<div>
<LemonButton
icon={<IconRewind />}
onClick={handleReset}
disabled={isLoading}
type="tertiary"
size="xsmall"
>
Start over
</LemonButton>
</div>
)}
<AiConsentPopover />
</>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { IconArrowUpRight } from '@posthog/icons'
import { LemonButton } from '@posthog/lemon-ui'
import { useActions } from 'kea'

import { aiFilterLogic } from './aiFilterLogic'

export function AiFilterSuggestions(): JSX.Element {
const filterLogic = aiFilterLogic()
const { setInput } = useActions(filterLogic)

const suggestions = [
'Show me recordings of people who visited sign up page in the last 24 hours',
'Show me recordings of people who are frustrated',
'Show me recordings of people who are facing bugs',
]

return (
<div className="flex items-center justify-center flex-wrap gap-x-2 gap-y-1.5 w-[min(48rem,100%)]">
{suggestions.map((suggestion) => (
<LemonButton
key={suggestion}
className="mb-1"
type="secondary"
size="xsmall"
sideIcon={<IconArrowUpRight />}
onClick={() => setInput(suggestion)}
>
{suggestion}
</LemonButton>
))}
</div>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { ProfilePicture, Spinner, Tooltip } from '@posthog/lemon-ui'
import { useActions, useMountedLogic, useValues } from 'kea'
import { LemonMarkdown } from 'lib/lemon-ui/LemonMarkdown'
import { sessionRecordingsPlaylistLogic } from 'scenes/session-recordings/playlist/sessionRecordingsPlaylistLogic'
import { userLogic } from 'scenes/userLogic'

import { aiFilterLogic } from './aiFilterLogic'

export function AiFilterThread(): JSX.Element {
const mountedLogic = useMountedLogic(sessionRecordingsPlaylistLogic)
const { setFilters, resetFilters } = useActions(mountedLogic)
const filterLogic = aiFilterLogic({ setFilters, resetFilters })
const { messages, isLoading } = useValues(filterLogic)
const { user } = useValues(userLogic)

return (
<>
{(messages.length > 0 || isLoading) && (
<div className="w-[min(44rem,100%)] relative">
{messages
.filter((message) => message.role !== 'system')
.map((message, index) => (
<div key={index} className=" my-2">
{message.role === 'user' ? (
<>
<div className="relative flex gap-2 flex-row-reverse ml-10 items-center">
<Tooltip placement="right" title="You">
<ProfilePicture
user={{ ...user, hedgehog_config: undefined }}
size="lg"
className="mt-1 border"
/>
</Tooltip>
<div className="border py-2 px-3 rounded-lg bg-surface-primary font-medium">
<LemonMarkdown>{message.content}</LemonMarkdown>
</div>
</div>
</>
) : (
<>
<div className="relative flex gap-2 mr-10 items-center">
<Tooltip placement="left" title="Max">
<ProfilePicture
user={{
hedgehog_config: {
...user?.hedgehog_config,
use_as_profile: true,
},
}}
size="lg"
className="mt-1 border"
/>
</Tooltip>
<div className="border py-2 px-3 rounded-lg bg-surface-primary font-medium">
<LemonMarkdown>
{message.content.length > 0 && message.content[0] === '{'
? 'Done! Filters have been updated'
: message.content}
</LemonMarkdown>
</div>
</div>
</>
)}
</div>
))}
{isLoading && (
<div className="relative flex gap-2 mr-10 items-center">
<Tooltip placement="left" title="Max">
<ProfilePicture
user={{ hedgehog_config: { ...user?.hedgehog_config, use_as_profile: true } }}
size="lg"
className="mt-1 border"
/>
</Tooltip>
<div className="border py-2 px-3 rounded-lg bg-surface-primary font-medium">
Thinking...
<Spinner />
</div>
</div>
)}
</div>
)}
</>
)
}
Loading

0 comments on commit 75a2b82

Please sign in to comment.