Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
37 changes: 29 additions & 8 deletions assets/src/components/ai/AI.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,14 @@ import { RectangleSkeleton } from 'components/utils/SkeletonLoaders'
import { StretchedFlex } from 'components/utils/StretchedFlex'
import { SubtabDirectory, SubTabs } from 'components/utils/SubTabs'
import { useMemo, useState } from 'react'
import { Link, Outlet, useMatch } from 'react-router-dom'
import { Link, Navigate, Outlet, useMatch } from 'react-router-dom'
import {
AI_ABS_PATH,
AI_AGENT_RUNS_REL_PATH,
AI_SENTINELS_REL_PATH,
AI_THREADS_REL_PATH,
} from 'routes/aiRoutesConsts'
import { WORKBENCHES_ABS_PATH } from 'routes/workbenchesRoutesConsts'
import {
AI_SETTINGS_ABS_PATH,
AI_SETTINGS_AI_PROVIDER_ABS_PATH,
Expand All @@ -28,16 +29,11 @@ import {
useAIEnabled,
useLoadingDeploymentSettings,
} from '../contexts/DeploymentSettingsContext'
import { useWorkbenchOptions } from '../workbenches/useWorkbenchOptions'
import { AIDisabledState } from './AIThreads'

const DISMISSED_AI_ENABLED_DIALOG_KEY = 'dismissedAIEnabledDialog'

const directory: SubtabDirectory = [
{ label: 'Agent runs', path: AI_AGENT_RUNS_REL_PATH },
{ label: 'Sentinels', path: AI_SENTINELS_REL_PATH },
{ label: 'Chat threads', path: AI_THREADS_REL_PATH },
]

export const getAIBreadcrumbs = (tab: string = '') => [
{ label: 'plural ai', url: AI_ABS_PATH },
...getTabCrumb(AI_ABS_PATH, tab),
Expand All @@ -46,6 +42,8 @@ export const getAIBreadcrumbs = (tab: string = '') => [
export function AI() {
const tab = useMatch(`${AI_ABS_PATH}/:tab/*`)?.params.tab
const aiEnabled = useAIEnabled()
const { confirmedNoWorkbenches, loading: workbenchesLoading } =
useWorkbenchOptions()
const [dismissedAIDialog, setDismissedAIDialog] = usePersistedState(
DISMISSED_AI_ENABLED_DIALOG_KEY,
false
Expand All @@ -56,6 +54,29 @@ export function AI() {
aiEnabled === false && !dismissedAIDialog && !sessionDismissed
const loading = useLoadingDeploymentSettings()
useSetBreadcrumbs(useMemo(() => getAIBreadcrumbs(tab), [tab]))
const directory: SubtabDirectory = useMemo(
() => [
{ label: 'Agent runs', path: AI_AGENT_RUNS_REL_PATH },
{ label: 'Sentinels', path: AI_SENTINELS_REL_PATH },
...(confirmedNoWorkbenches
? [{ label: 'Chat threads', path: AI_THREADS_REL_PATH }]
: []),
],
[confirmedNoWorkbenches]
)

if (
tab === AI_THREADS_REL_PATH &&
!workbenchesLoading &&
!confirmedNoWorkbenches
) {
return (
<Navigate
replace
to={WORKBENCHES_ABS_PATH}
/>
)
}

const handleDismiss = () => {
if (dontShowAgain) setDismissedAIDialog(true)
Expand All @@ -78,7 +99,7 @@ export function AI() {
/>
</StretchedFlex>
</HeaderSC>
{loading ? (
{loading || (tab === AI_THREADS_REL_PATH && workbenchesLoading) ? (
<RectangleSkeleton
$height="100%"
$width="100%"
Expand Down
8 changes: 8 additions & 0 deletions assets/src/components/ai/AIContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {
} from 'react'
import { useTheme } from 'styled-components'
import usePersistedState from '../hooks/usePersistedState.tsx'
import { useLegacyAiChatEnabled } from './useLegacyAiChatEnabled.ts'

export enum AIVerbosityLevel {
High = 'High',
Expand Down Expand Up @@ -105,7 +106,13 @@ function ChatbotContextProvider({ children }: { children: ReactNode }) {
const { spacing } = useTheme()
const { setIsExpanded: setSidebarExpanded } = use(SidebarContext)
const { sidePanel, setSidePanel } = useTopLevelSidePanel()
const legacyChatEnabled = useLegacyAiChatEnabled()
const open = sidePanel === SIDE_PANEL_TYPE

useEffect(() => {
if (!legacyChatEnabled && open) setSidePanel(null)
}, [legacyChatEnabled, open, setSidePanel])

const [viewType, setViewType] = usePersistedState<AIViewTypes>(
'plural-ai-view-type',
AIViewTypes.ChatThread
Expand Down Expand Up @@ -175,6 +182,7 @@ function ChatbotContextProvider({ children }: { children: ReactNode }) {
open,
setOpen: (open) => {
if (open) {
if (!legacyChatEnabled) return
setSidePanel(SIDE_PANEL_TYPE)
setSidebarExpanded(false)
} else setSidePanel(null)
Expand Down
2 changes: 1 addition & 1 deletion assets/src/components/ai/chatbot/AISuggestFix.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import { useLogin } from '../../contexts.tsx'
import { GqlError } from '../../utils/Alert.tsx'
import { hasAccess } from '../../utils/persona.tsx'
import { AIPanel } from '../AIPanel.tsx'
import { useWorkbenchOptions } from '../insights/SendInsightToWorkbench.tsx'
import { useWorkbenchOptions } from '../../workbenches/useWorkbenchOptions.ts'
import { ChatWithAIButton, insightMessage } from './ChatbotButton.tsx'
import { useStreamBuffer } from './useStreamBuffer.tsx'

Expand Down
6 changes: 3 additions & 3 deletions assets/src/components/ai/chatbot/Chatbot.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { usePrevious } from '@pluralsh/design-system'

import { useDeploymentSettings } from 'components/contexts/DeploymentSettingsContext.tsx'
import { isEmpty } from 'lodash'
import { useEffect, useMemo } from 'react'
import { useChatThreadMessagesQuery } from '../../../generated/graphql.ts'
import { mapExistingNodes } from '../../../utils/graphql.ts'
import { useFetchPaginatedData } from '../../utils/table/useFetchPaginatedData.tsx'
import { AIViewTypes, useChatbot, useChatbotContext } from '../AIContext.tsx'
import { useLegacyAiChatEnabled } from '../useLegacyAiChatEnabled.ts'

import { ChatbotActionsPanel } from './actions-panel/ChatbotActionsPanel.tsx'
import { MainChatbotButton } from './ChatbotButton.tsx'
Expand All @@ -18,9 +18,9 @@ import { McpServerShelf } from './tools/McpServerShelf.tsx'

export function ChatbotLauncher() {
const { open, setOpen } = useChatbotContext()
const settings = useDeploymentSettings()
const legacyChatEnabled = useLegacyAiChatEnabled()

if (!settings.ai?.enabled || open) return null
if (!legacyChatEnabled || open) return null

return <MainChatbotButton onClick={() => setOpen(true)} />
}
Expand Down
3 changes: 3 additions & 0 deletions assets/src/components/ai/chatbot/ChatbotButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
ChatOutlineIcon,
} from '@pluralsh/design-system'
import { useAIEnabled } from 'components/contexts/DeploymentSettingsContext.tsx'
import { useWorkbenchOptions } from 'components/workbenches/useWorkbenchOptions.ts'
import { AiInsightFragment, AiRole, ChatMessage } from 'generated/graphql.ts'
import { ComponentPropsWithRef, useEffect, useState } from 'react'
import { Link } from 'react-router-dom'
Expand Down Expand Up @@ -68,6 +69,7 @@ export function ChatWithAIButton({
closeChatbot,
} = useChatbot()
const aiEnabled = useAIEnabled()
const { confirmedNoWorkbenches } = useWorkbenchOptions()
const bodyText = aiEnabled ? bodyTextProp : 'Enable AI to chat'

const handleClick = () => {
Expand All @@ -80,6 +82,7 @@ export function ChatWithAIButton({
})
}

if (!confirmedNoWorkbenches) return null
if (!alwaysShow && !insightId && !flowId) return null

return (
Expand Down
34 changes: 2 additions & 32 deletions assets/src/components/ai/insights/SendInsightToWorkbench.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,19 +31,16 @@ import { RectangleSkeleton } from 'components/utils/SkeletonLoaders'
import { StretchedFlex } from 'components/utils/StretchedFlex'
import { Body1P } from 'components/utils/typography/Text'
import { WorkbenchStartedJobPanel } from 'components/workbenches/common/WorkbenchStartedJobPanel'
import { useWorkbenchOptions } from 'components/workbenches/useWorkbenchOptions'
import {
AgentRuntimeType,
AiInsightFragment,
useCreateWorkbenchJobMutation,
useFlowWorkbenchesQuery,
useWorkbenchesQuery,
WorkbenchJobFragment,
WorkbenchTinyFragment,
} from 'generated/graphql'
import { useEffect, useMemo, useRef, useState } from 'react'
import { useEffect, useRef, useState } from 'react'
import styled, { useTheme } from 'styled-components'
import { mapExistingNodes } from 'utils/graphql'
import { isNonNullable } from 'utils/isNonNullable'
import { buildInsightWorkbenchPrompt } from './insightWorkbenchPrompt'

const POPOVER_WIDTH = 568
Expand Down Expand Up @@ -308,33 +305,6 @@ export function SendToWorkbenchForm({
)
}

export function useWorkbenchOptions(flowId?: Nullable<string>, enabled = true) {
const { data: flowData, loading: flowLoading } = useFlowWorkbenchesQuery({
variables: { id: flowId ?? '' },
skip: !enabled || !flowId,
fetchPolicy: 'cache-first',
})
const { data: allWorkbenchesData, loading: allWorkbenchesLoading } =
useWorkbenchesQuery({
skip: !enabled || !!flowId,
fetchPolicy: 'cache-first',
})

const workbenches = useMemo(() => {
if (flowId) return (flowData?.flow?.workbenches ?? []).filter(isNonNullable)

return mapExistingNodes(allWorkbenchesData?.workbenches)
}, [allWorkbenchesData?.workbenches, flowData?.flow?.workbenches, flowId])

return {
workbenches,
hasWorkbenches: workbenches.length > 0,
loading: flowId
? flowLoading && !flowData
: allWorkbenchesLoading && !allWorkbenchesData,
}
}

function WorkbenchSelector({
workbenchId,
setWorkbenchId,
Expand Down
9 changes: 9 additions & 0 deletions assets/src/components/ai/useLegacyAiChatEnabled.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { useAIEnabled } from 'components/contexts/DeploymentSettingsContext'
import { useWorkbenchOptions } from 'components/workbenches/useWorkbenchOptions'

export function useLegacyAiChatEnabled() {
const aiEnabled = useAIEnabled()
const { confirmedNoWorkbenches } = useWorkbenchOptions()

return !!aiEnabled && confirmedNoWorkbenches
}
28 changes: 19 additions & 9 deletions assets/src/components/commandpalette/CommandPalette.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { AITableActions } from '../ai/AITableActions.tsx'
import { AIEntryLabel, getThreadTimestamp } from '../ai/AITableEntry.tsx'
import { ChatInput } from '../ai/chatbot/input/ChatInput.tsx'
import { useAIEnabled } from '../contexts/DeploymentSettingsContext.tsx'
import { useWorkbenchOptions } from '../workbenches/useWorkbenchOptions.ts'
import { ButtonGroup } from '../utils/ButtonGroup.tsx'
import { Body1BoldP, Body2P, CaptionP } from '../utils/typography/Text.tsx'
import {
Expand All @@ -47,13 +48,19 @@ export default function CommandPalette({
openServiceAccountImpersonation: () => void
}) {
const aiEnabled = useAIEnabled()
const { confirmedNoWorkbenches } = useWorkbenchOptions()
const threadsEnabled = (aiEnabled ?? false) && confirmedNoWorkbenches
const theme = useTheme()
const { setCmdkOpen, initialTab } = use(CommandPaletteContext)
const onCmdKClose = () => setCmdkOpen(false)

const [tab, setTab] = useState<CommandPaletteTab>(initialTab)
const [cmdValue, setCmdValue] = useState('')
const [historyValue, setHistoryValue] = useState('')
const activeTab =
tab === CommandPaletteTab.Threads && !threadsEnabled
? CommandPaletteTab.Commands
: tab

// only show hidden commands if the user has typed something
const commands = useCommands({
Expand All @@ -64,6 +71,7 @@ export default function CommandPalette({

const { loading, history, fetchNextPage, pageInfo } = useHistory({
filter: historyValue,
skip: !threadsEnabled,
component: (thread) => (
<HistoryItem
key={thread.id}
Expand All @@ -78,7 +86,7 @@ export default function CommandPalette({
{
path: CommandPaletteTab.Threads,
label: CommandPaletteTab.Threads,
enabled: aiEnabled ?? false,
enabled: threadsEnabled,
},
{
path: CommandPaletteTab.Commands,
Expand All @@ -87,7 +95,7 @@ export default function CommandPalette({
enabled: true,
},
].filter((d) => d.enabled),
[aiEnabled]
[threadsEnabled]
)

const onScroll = useCallback(
Expand All @@ -97,47 +105,49 @@ export default function CommandPalette({
e.currentTarget.scrollHeight - 100
if (
isNearBottom &&
tab === CommandPaletteTab.Threads &&
activeTab === CommandPaletteTab.Threads &&
pageInfo?.hasNextPage
) {
fetchNextPage()
}
},
[fetchNextPage, pageInfo?.hasNextPage, tab]
[fetchNextPage, pageInfo?.hasNextPage, activeTab]
)

return (
<>
<div id="cmdk-input-wrapper">
<CommandAdvancedInput
curTab={tab}
curTab={activeTab}
cmdValue={cmdValue}
onCmdValueChange={setCmdValue}
onHistoryValueChange={setHistoryValue}
onCmdKClose={onCmdKClose}
numItems={
tab === CommandPaletteTab.Threads ? history.length : commands.length
activeTab === CommandPaletteTab.Threads
? history.length
: commands.length
}
/>
{directory.length > 1 && (
<div id="cmdk-input-tabs">
<ButtonGroup
directory={directory}
tab={tab}
tab={activeTab}
onClick={(t) => setTab(t)}
/>
</div>
)}
</div>
<Command.List onScroll={onScroll}>
{tab === CommandPaletteTab.Threads && (
{activeTab === CommandPaletteTab.Threads && (
<CommandPaletteHistory
history={history}
loading={loading}
value={historyValue}
/>
)}
{tab === CommandPaletteTab.Commands && (
{activeTab === CommandPaletteTab.Commands && (
<CommandPaletteCommands
value={cmdValue}
items={commands}
Expand Down
3 changes: 3 additions & 0 deletions assets/src/components/commandpalette/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -454,9 +454,11 @@ export function useCommands({
export function useHistory({
filter,
component,
skip,
}: {
filter: string
component?: (thread: ChatThreadTinyFragment) => ReactElement
skip?: boolean
}): {
loading: boolean
history: Command[]
Expand All @@ -472,6 +474,7 @@ export function useHistory({
queryHook: useChatThreadsQuery,
keyPath: ['chatThreads'],
pageSize: 50,
skip,
},
{ q: isEmpty(throttledFilter) ? undefined : throttledFilter }
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
PrOpenIcon,
WorkbenchIcon,
} from '@pluralsh/design-system'
import { useWorkbenchOptions } from 'components/ai/insights/SendInsightToWorkbench'
import { useWorkbenchOptions } from 'components/workbenches/useWorkbenchOptions'
import { createColumnHelper } from '@tanstack/react-table'
import { DistroProviderIconFrame } from 'components/utils/ClusterDistro'
import { StackedText } from 'components/utils/table/StackedText'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,10 @@ import {
useApplyScalingRecommendationMutation,
} from 'generated/graphql'

import {
SendToWorkbenchForm,
useWorkbenchOptions,
} from 'components/ai/insights/SendInsightToWorkbench'
import { SendToWorkbenchForm } from 'components/ai/insights/SendInsightToWorkbench'
import { GqlError } from 'components/utils/Alert'
import { WorkbenchStartedJobPanel } from 'components/workbenches/common/WorkbenchStartedJobPanel'
import { useWorkbenchOptions } from 'components/workbenches/useWorkbenchOptions'

import { PrStepKey } from 'components/self-service/pr/automations/CreatePrModal'
import { usePrAutomationForm } from 'components/self-service/pr/automations/prConfigurationUtils'
Expand Down
Loading
Loading