Skip to content

Commit

Permalink
WIP: add ai chat UI (#1534)
Browse files Browse the repository at this point in the history
Co-authored-by: michaeljguarino <[email protected]>
Co-authored-by: Sebastian Florek <[email protected]>
  • Loading branch information
3 people authored Nov 2, 2024
1 parent 4edc484 commit 1864096
Show file tree
Hide file tree
Showing 116 changed files with 3,721 additions and 850 deletions.
2 changes: 1 addition & 1 deletion AGENT_VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v0.4.52
v0.4.53
3 changes: 2 additions & 1 deletion assets/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
"@nivo/pie": "0.87.0",
"@nivo/radial-bar": "0.87.0",
"@nivo/tooltip": "0.87.0",
"@pluralsh/design-system": "3.76.0",
"@pluralsh/design-system": "3.77.0",
"@react-hooks-library/core": "0.6.0",
"@saas-ui/use-hotkeys": "1.1.3",
"@tanstack/react-table": "8.20.5",
Expand All @@ -63,6 +63,7 @@
"classnames": "2.3.2",
"cmdk": "1.0.0",
"country-code-lookup": "0.0.23",
"dayjs": "1.11.13",
"emoji-mart": "5.5.2",
"encrypt-storage": "2.13.2",
"escape-carriage": "1.3.1",
Expand Down
169 changes: 169 additions & 0 deletions assets/src/components/ai/AI.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
import { Button, Flex, GearTrainIcon } from '@pluralsh/design-system'
import { StackedText } from 'components/utils/table/StackedText.tsx'
import {
FetchPaginatedDataResult,
useFetchPaginatedData,
} from 'components/utils/table/useFetchPaginatedData.tsx'
import {
AiPinFragment,
AiPinsQuery,
ChatThreadTinyFragment,
ChatThreadsQuery,
useAiPinsQuery,
useChatThreadsQuery,
} from 'generated/graphql.ts'
import { useCallback, useMemo } from 'react'
import { useNavigate } from 'react-router-dom'
import { GLOBAL_SETTINGS_ABS_PATH } from '../../routes/settingsRoutesConst.tsx'
import { AIPinsTable } from './AIPinsTable.tsx'
import { AIThreadsTable } from './AIThreadsTable.tsx'

export default function AI() {
const threadsQuery = useFetchPaginatedData({
queryHook: useChatThreadsQuery,
keyPath: ['chatThreads'],
})

const pinsQuery = useFetchPaginatedData({
queryHook: useAiPinsQuery,
keyPath: ['aiPins'],
})

const refetchAll = useCallback(() => {
threadsQuery.refetch().then(() => pinsQuery.refetch())
}, [threadsQuery, pinsQuery])

const filteredPins = useMemo(
() =>
pinsQuery.data?.aiPins?.edges
?.map((edge) => edge?.node)
?.filter((pin): pin is AiPinFragment => Boolean(pin)) ?? [],
[pinsQuery.data?.aiPins?.edges]
)

const filteredThreads = useMemo(
() =>
threadsQuery.data?.chatThreads?.edges
?.map((edge) => edge?.node)
?.filter(
(thread) => !filteredPins.some((pin) => pin.thread?.id === thread?.id)
)
?.filter((thread): thread is ChatThreadTinyFragment =>
Boolean(thread)
) ?? [],
[filteredPins, threadsQuery.data?.chatThreads?.edges]
)

return (
<Flex
direction="column"
gap="medium"
padding="large"
marginBottom={30}
height="100%"
overflow="hidden"
>
<Header />
<Flex
direction="column"
gap="xlarge"
height="100%"
>
<PinnedSection
refetch={refetchAll}
filteredPins={filteredPins}
pinsQuery={pinsQuery}
/>
<AllThreadsSection
refetch={refetchAll}
filteredThreads={filteredThreads}
threadsQuery={threadsQuery}
/>
</Flex>
</Flex>
)
}

function Header() {
const navigate = useNavigate()
return (
<Flex
justify="space-between"
align="center"
>
<StackedText
first="Plural AI"
second="View ongoing threads and saved insights at a glance."
firstPartialType="subtitle1"
secondPartialType="body2"
/>
<Button
secondary
startIcon={<GearTrainIcon />}
onClick={() => navigate(`${GLOBAL_SETTINGS_ABS_PATH}/ai-provider`)}
>
Settings
</Button>
</Flex>
)
}

function PinnedSection({
filteredPins,
pinsQuery,
refetch,
}: {
filteredPins: AiPinFragment[]
pinsQuery: FetchPaginatedDataResult<AiPinsQuery>
refetch: () => void
}) {
return (
<Flex
direction="column"
gap="medium"
maxHeight="40%"
>
<StackedText
first="Pinned"
second="Pin important threads and insights"
firstPartialType="subtitle2"
secondPartialType="body2"
/>
<AIPinsTable
refetch={refetch}
filteredPins={filteredPins}
pinsQuery={pinsQuery}
/>
</Flex>
)
}

function AllThreadsSection({
filteredThreads,
threadsQuery,
refetch,
}: {
filteredThreads: ChatThreadTinyFragment[]
threadsQuery: FetchPaginatedDataResult<ChatThreadsQuery>
refetch: () => void
}) {
return (
<Flex
direction="column"
gap="medium"
flex={1}
overflow="hidden"
paddingBottom={36} // this is a magic number to make the table fit
>
<StackedText
first="All threads"
firstPartialType="subtitle2"
/>
<AIThreadsTable
refetch={refetch}
filteredThreads={filteredThreads}
threadsQuery={threadsQuery}
/>
</Flex>
)
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
import {
ChatThreadAttributes,
ChatThreadFragment,
useCreateChatThreadMutation,
} from 'generated/graphql.ts'
import {
createContext,
Dispatch,
Expand All @@ -24,33 +29,55 @@ type ExplainWithAIContextT = {
system: string
}

export const verbosityLevelToSystem = {
[AIVerbosityLevel.Low]: `You're a seasoned DevOps engineer with experience in Kubernetes, GitOps and Infrastructure As Code,
and need to give a concise but clear explanation of an infrastructure problem that will likely involve either Kubernetes or Terraform.
The user is not necessarily an expert in the domain, so please documentation and evidence to explain what issue they're facing.
Give a short overview of the resource they are mentioning and any guidance on how they can learn more about how it works.
Keep your response to 1-2 sections.`,
[AIVerbosityLevel.Medium]: `You're a seasoned DevOps engineer with experience in Kubernetes, GitOps and Infrastructure As Code,
and need to give a concise but clear explanation of an infrastructure problem that will likely involve either Kubernetes or Terraform.
The user is not necessarily an expert in the domain, so please documentation and evidence to explain what issue they're facing.
Give a short overview of the resource they are mentioning and any guidance on how they can learn more about how it works.
Keep your response to 3-5 sections.`,
[AIVerbosityLevel.High]: `You're a seasoned DevOps engineer with experience in Kubernetes, GitOps and Infrastructure As Code,
and need to give a concise but clear explanation of an infrastructure problem that will likely involve either Kubernetes or Terraform.
The user is not necessarily an expert in the domain, so please provide as much documentation
and evidence as is necessary to explain what issue they're facing. Give a descriptive overview of the resource they are mentioning
and any guidance on how they can learn more about how it works.`,
} as const satisfies Record<AIVerbosityLevel, string>
type ChatbotContextT = {
open: boolean
setOpen: (open: boolean) => void
fullscreen: boolean
setFullscreen: Dispatch<SetStateAction<boolean>>
currentThread: Nullable<ChatThreadFragment>
setCurrentThread: (thread: Nullable<ChatThreadFragment>) => void
}

const ExplainWithAIContext = createContext<ExplainWithAIContextT | undefined>(
undefined
)

export function ExplainWithAIContextProvider({
children,
}: {
children: ReactNode
}) {
const ChatbotContext = createContext<ChatbotContextT | undefined>(undefined)

export function AIContextProvider({ children }: { children: ReactNode }) {
return (
<ChatbotContextProvider>
<ExplainWithAIContextProvider>{children}</ExplainWithAIContextProvider>
</ChatbotContextProvider>
)
}

function ChatbotContextProvider({ children }: { children: ReactNode }) {
const [open, setOpen] = useState(false)
const [fullscreen, setFullscreen] = useState(false)
const [currentThread, setCurrentThread] =
useState<Nullable<ChatThreadFragment>>()

const context = useMemo(
() => ({
open,
setOpen,
currentThread,
setCurrentThread,
fullscreen,
setFullscreen,
}),
[open, currentThread, fullscreen]
)

return (
<ChatbotContext.Provider value={context}>
{children}
</ChatbotContext.Provider>
)
}

function ExplainWithAIContextProvider({ children }: { children: ReactNode }) {
const [prompt, setPrompt] = useState<string | undefined>()
const [verbosityLevel, setVerbosityLevel] = usePersistedState(
'plural-ai-verbosity-level',
Expand All @@ -75,6 +102,44 @@ export function ExplainWithAIContextProvider({
)
}

export function useChatbotContext() {
const context = useContext(ChatbotContext)
if (!context) {
throw new Error('useChatbot must be used within a ChatbotProvider')
}
return context
}

export function useChatbot() {
const { setOpen, setCurrentThread, fullscreen, setFullscreen } =
useChatbotContext()
const [mutation, { loading, error }] = useCreateChatThreadMutation()

return {
createNewThread: (attributes: ChatThreadAttributes) => {
mutation({
variables: { attributes },
onCompleted: (data) => {
setCurrentThread(data.createThread)
setOpen(true)
},
})
},
goToThread: (thread: ChatThreadFragment) => {
setCurrentThread(thread)
setOpen(true)
},
goToThreadList: () => {
setCurrentThread(null)
setOpen(true)
},
fullscreen,
setFullscreen,
loading,
error,
}
}

export const useExplainWithAIContext = () => {
const ctx = useContext(ExplainWithAIContext)

Expand All @@ -98,3 +163,21 @@ export const useExplainWithAI = (prompt?: string) => {
return () => setPrompt?.(undefined)
}, [setPrompt, prompt])
}

const verbosityLevelToSystem = {
[AIVerbosityLevel.Low]: `You're a seasoned DevOps engineer with experience in Kubernetes, GitOps and Infrastructure As Code,
and need to give a concise but clear explanation of an infrastructure problem that will likely involve either Kubernetes or Terraform.
The user is not necessarily an expert in the domain, so please documentation and evidence to explain what issue they're facing.
Give a short overview of the resource they are mentioning and any guidance on how they can learn more about how it works.
Keep your response to 1-2 sections.`,
[AIVerbosityLevel.Medium]: `You're a seasoned DevOps engineer with experience in Kubernetes, GitOps and Infrastructure As Code,
and need to give a concise but clear explanation of an infrastructure problem that will likely involve either Kubernetes or Terraform.
The user is not necessarily an expert in the domain, so please documentation and evidence to explain what issue they're facing.
Give a short overview of the resource they are mentioning and any guidance on how they can learn more about how it works.
Keep your response to 3-5 sections.`,
[AIVerbosityLevel.High]: `You're a seasoned DevOps engineer with experience in Kubernetes, GitOps and Infrastructure As Code,
and need to give a concise but clear explanation of an infrastructure problem that will likely involve either Kubernetes or Terraform.
The user is not necessarily an expert in the domain, so please provide as much documentation
and evidence as is necessary to explain what issue they're facing. Give a descriptive overview of the resource they are mentioning
and any guidance on how they can learn more about how it works.`,
} as const satisfies Record<AIVerbosityLevel, string>
9 changes: 8 additions & 1 deletion assets/src/components/ai/AIPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export default function AIPanel({
showClosePanel = false,
header,
subheader,
footer,
children,
...props
}: {
Expand All @@ -25,6 +26,7 @@ export default function AIPanel({
showClosePanel?: boolean
header: string
subheader: string
footer?: ReactNode
children: ReactNode
} & CardProps) {
const theme = useTheme()
Expand Down Expand Up @@ -91,14 +93,19 @@ export default function AIPanel({
display: 'flex',
gap: theme.spacing.small,
padding: theme.spacing.large,
'> *': {
flexGrow: 1,
},
}}
>
<Button
flexGrow={1}
onClick={onClose}
secondary={!!footer}
floating={!!footer}
>
Got it, thanks!
</Button>
{footer && footer}
</div>
)}
</Card>
Expand Down
Loading

0 comments on commit 1864096

Please sign in to comment.