Skip to content

Commit

Permalink
feat(ai): Sleeker Max (#25324)
Browse files Browse the repository at this point in the history
Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
  • Loading branch information
Twixes and github-actions[bot] authored Oct 3, 2024
1 parent 8c4b061 commit a978be4
Show file tree
Hide file tree
Showing 22 changed files with 460 additions and 153 deletions.
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.
1 change: 1 addition & 0 deletions frontend/src/layout/navigation-3000/Navigation.scss
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@

&.Navigation3000__scene--raw {
--scene-padding: 0px;
--scene-padding-bottom: 0px;

display: flex;
flex-direction: column;
Expand Down
4 changes: 3 additions & 1 deletion frontend/src/lib/components/HedgehogBuddy/HedgehogBuddy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -838,9 +838,11 @@ export class HedgehogActor {
))}
{this.overlayAnimation ? (
<div
className={`absolute top-0 left-0 w-[${SPRITE_SIZE}px] h-[${SPRITE_SIZE}px] rendering-pixelated`}
className="absolute top-0 left-0 rendering-pixelated"
// eslint-disable-next-line react/forbid-dom-props
style={{
width: SPRITE_SIZE,
height: SPRITE_SIZE,
backgroundImage: `url(${spriteOverlayUrl(this.overlayAnimation.spriteInfo.img)})`,
backgroundPosition: `-${
(this.overlayAnimation.frame % X_FRAMES) * SPRITE_SIZE
Expand Down
45 changes: 37 additions & 8 deletions frontend/src/lib/lemon-ui/LemonTextArea/LemonTextArea.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import clsx from 'clsx'
import React, { useRef } from 'react'
import TextareaAutosize from 'react-textarea-autosize'

export interface LemonTextAreaProps
interface LemonTextAreaPropsBase
extends Pick<
React.TextareaHTMLAttributes<HTMLTextAreaElement>,
'onFocus' | 'onBlur' | 'maxLength' | 'autoFocus' | 'onKeyDown'
Expand All @@ -18,9 +18,6 @@ export interface LemonTextAreaProps
disabled?: boolean
ref?: React.Ref<HTMLTextAreaElement>
onChange?: (newValue: string) => void
/** Callback called when Cmd + Enter (or Ctrl + Enter) is pressed.
* This checks for Cmd/Ctrl, as opposed to LemonInput, to avoid blocking multi-line input. */
onPressCmdEnter?: (newValue: string) => void
minRows?: number
maxRows?: number
rows?: number
Expand All @@ -29,9 +26,22 @@ export interface LemonTextAreaProps
'data-attr'?: string
}

interface LemonTextAreaWithCmdEnterProps extends LemonTextAreaPropsBase {
/** Callback for when Cmd/Ctrl + Enter is pressed. In this case, the user adds new lines with Enter like always. */
onPressCmdEnter?: (currentValue: string) => void
onPressEnter?: never
}

interface LemonTextAreaWithEnterProps extends LemonTextAreaPropsBase {
/** Callback for when Enter is pressed. In this case, to add a new line the user must press Cmd + Enter. */
onPressEnter: (currentValue: string) => void
onPressCmdEnter?: never
}
export type LemonTextAreaProps = LemonTextAreaWithEnterProps | LemonTextAreaWithCmdEnterProps

/** A `textarea` component for multi-line text. */
export const LemonTextArea = React.forwardRef<HTMLTextAreaElement, LemonTextAreaProps>(function _LemonTextArea(
{ className, onChange, onPressCmdEnter: onPressEnter, minRows = 3, onKeyDown, stopPropagation, ...textProps },
{ className, onChange, onPressEnter, onPressCmdEnter, minRows = 3, onKeyDown, stopPropagation, ...textProps },
ref
): JSX.Element {
const _ref = useRef<HTMLTextAreaElement | null>(null)
Expand All @@ -46,10 +56,29 @@ export const LemonTextArea = React.forwardRef<HTMLTextAreaElement, LemonTextArea
if (stopPropagation) {
e.stopPropagation()
}
if (onPressEnter && e.key === 'Enter' && (e.metaKey || e.ctrlKey)) {
onPressEnter(textProps.value?.toString() ?? '')
if (e.key === 'Enter') {
const target = e.currentTarget
if (e.metaKey || e.ctrlKey) {
if (onPressEnter) {
// When onPressEnter is defined, Cmd/Ctrl + Enter adds a new line, like Enter normally does.
// This does not happen by default for Enter presses with Cmd/Ctrl, so we need to simulate it.
const selectionStartBeforeChange = target.selectionStart
const selectionEndBeforeChange = target.selectionEnd
target.value =
target.value.substring(0, selectionStartBeforeChange) +
'\n' +
target.value.substring(selectionEndBeforeChange)
target.selectionStart = target.selectionEnd = selectionStartBeforeChange + 1
onChange?.(target.value)
} else if (onPressCmdEnter) {
onPressCmdEnter(target.value)
e.preventDefault()
}
} else if (onPressEnter) {
onPressEnter?.(target.value)
e.preventDefault()
}
}

onKeyDown?.(e)
}}
onChange={(event) => {
Expand Down
5 changes: 5 additions & 0 deletions frontend/src/lib/lemon-ui/LoadingBar/LoadingBar.scss
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,9 @@
left: 0;
height: 100%;
background: var(--primary-3000-active);

.storybook-test-runner & {
// When taking UI snapshots, we hard-code progress width to 50%
width: 50% !important;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -130,11 +130,7 @@ export const LongLoading: StoryFn = () => {
],
},
post: {
'/api/projects/:team_id/insights/trend/': (_, __, ctx) => [
ctx.delay(86400000),
ctx.status(200),
ctx.json({ result: insight.result }),
],
'/api/projects/:team_id/query/': (_, __, ctx) => [ctx.delay('infinite')],
},
})
useEffect(() => {
Expand Down
51 changes: 51 additions & 0 deletions frontend/src/scenes/max/Intro.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { useValues } from 'kea'
import { HedgehogBuddy } from 'lib/components/HedgehogBuddy/HedgehogBuddy'
import { hedgehogBuddyLogic } from 'lib/components/HedgehogBuddy/hedgehogBuddyLogic'
import { useMemo } from 'react'

import { maxLogic } from './maxLogic'

const HEADLINES = [
'How can I help you build?',
'What are you curious about?',
'How can I help you understand users?',
'What do you want to know today?',
]

export function Intro(): JSX.Element {
const { hedgehogConfig } = useValues(hedgehogBuddyLogic)
const { sessionId } = useValues(maxLogic)

const headline = useMemo(() => {
return HEADLINES[parseInt(sessionId.split('-').at(-1) as string, 16) % HEADLINES.length]
}, [])

return (
<>
<div className="flex">
<HedgehogBuddy
static
hedgehogConfig={{
...hedgehogConfig,
walking_enabled: false,
controls_enabled: false,
}}
onClick={(actor) => {
if (Math.random() < 0.01) {
actor.setOnFire()
} else {
actor.setRandomAnimation()
}
}}
onActorLoaded={(actor) => setTimeout(() => actor.setAnimation('wave'), 100)}
/>
</div>
<div className="text-center mb-2">
<h2 className="text-2xl font-bold mb-2 text-balance">{headline}</h2>
<span className="text-muted">
I'm Max, here to help you build a succesful product. Ask me about your product and your users.
</span>
</div>
</>
)
}
74 changes: 74 additions & 0 deletions frontend/src/scenes/max/Max.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { Meta, StoryFn } from '@storybook/react'
import { BindLogic, useActions } from 'kea'
import { useEffect } from 'react'

import { mswDecorator, useStorybookMocks } from '~/mocks/browser'

import chatResponse from './__mocks__/chatResponse.json'
import { MaxInstance } from './Max'
import { maxLogic } from './maxLogic'

const meta: Meta = {
title: 'Scenes-App/Max AI',
decorators: [
mswDecorator({
post: {
'/api/projects/:team_id/query/chat/': chatResponse,
},
}),
],
parameters: {
layout: 'fullscreen',
viewMode: 'story',
mockDate: '2023-01-28', // To stabilize relative dates
},
}
export default meta

const Template = ({ sessionId }: { sessionId: string }): JSX.Element => {
return (
<div className="relative flex flex-col h-fit">
<BindLogic logic={maxLogic} props={{ sessionId }}>
<MaxInstance />
</BindLogic>
</div>
)
}

export const Welcome: StoryFn = () => {
const sessionId = 'd210b263-8521-4c5b-b3c4-8e0348df574b'
return <Template sessionId={sessionId} />
}

export const Thread: StoryFn = () => {
const sessionId = 'd210b263-8521-4c5b-b3c4-8e0348df574b'

const { askMax } = useActions(maxLogic({ sessionId }))
useEffect(() => {
askMax('What are my most popular pages?')
}, [])

return <Template sessionId={sessionId} />
}

export const EmptyThreadLoading: StoryFn = () => {
useStorybookMocks({
post: {
'/api/projects/:team_id/query/chat/': (_req, _res, ctx) => [ctx.delay('infinite')],
},
})

const sessionId = 'd210b263-8521-4c5b-b3c4-8e0348df574b'

const { askMax } = useActions(maxLogic({ sessionId }))
useEffect(() => {
askMax('What are my most popular pages?')
}, [])

return <Template sessionId={sessionId} />
}
EmptyThreadLoading.parameters = {
testOptions: {
waitForLoadersToDisappear: false,
},
}
Loading

0 comments on commit a978be4

Please sign in to comment.