diff --git a/packages/twenty-front/src/modules/ai/components/AIChatEmptyState.tsx b/packages/twenty-front/src/modules/ai/components/AIChatEmptyState.tsx index 87f51f4aaa..db1d63e7e7 100644 --- a/packages/twenty-front/src/modules/ai/components/AIChatEmptyState.tsx +++ b/packages/twenty-front/src/modules/ai/components/AIChatEmptyState.tsx @@ -24,6 +24,7 @@ const StyledSparkleIcon = styled.div` const StyledTitle = styled.div` font-size: ${({ theme }) => theme.font.size.lg}; + color: ${({ theme }) => theme.font.color.primary}; font-weight: 600; `; diff --git a/packages/twenty-front/src/modules/ai/components/LazyMarkdownRenderer.tsx b/packages/twenty-front/src/modules/ai/components/LazyMarkdownRenderer.tsx index 44be49ea8c..dac9beb99d 100644 --- a/packages/twenty-front/src/modules/ai/components/LazyMarkdownRenderer.tsx +++ b/packages/twenty-front/src/modules/ai/components/LazyMarkdownRenderer.tsx @@ -72,9 +72,11 @@ const MarkdownRenderer = lazy(async () => { default: ({ children, TableScrollContainer, + StyledParagraph, }: { children: string; TableScrollContainer: React.ComponentType<{ children: React.ReactNode }>; + StyledParagraph: React.ComponentType<{ children: React.ReactNode }>; }) => ( { {children}
), - p: ({ children }) =>

{processChildrenForRecordLinks(children)}

, + p: ({ children }) => ( + + {processChildrenForRecordLinks(children)} + + ), li: ({ children }) => (
  • {processChildrenForRecordLinks(children)}
  • ), @@ -116,6 +122,19 @@ const StyledTableScrollContainer = styled.div` } `; +// Using div instead of p to allow RecordLink (which contains div elements) as children +const StyledParagraph = styled.div` + margin-block: 1em; + + &:first-child { + margin-block-start: 0; + } + + &:last-child { + margin-block-end: 0; + } +`; + const StyledSkeletonContainer = styled.div` display: flex; flex-direction: column; @@ -161,7 +180,10 @@ const LoadingSkeleton = () => { export const LazyMarkdownRenderer = ({ text }: { text: string }) => { return ( }> - + {text} diff --git a/packages/twenty-front/src/modules/ai/hooks/useAgentChat.ts b/packages/twenty-front/src/modules/ai/hooks/useAgentChat.ts index 105abc3c0a..197dba7a65 100644 --- a/packages/twenty-front/src/modules/ai/hooks/useAgentChat.ts +++ b/packages/twenty-front/src/modules/ai/hooks/useAgentChat.ts @@ -147,11 +147,10 @@ export const useAgentChat = (uiMessages: ExtendedUIMessage[]) => { const isStreaming = status === 'streaming'; - const isLoading = - !currentAIChatThread || isStreaming || agentChatSelectedFiles.length > 0; + const isLoading = isStreaming || agentChatSelectedFiles.length > 0; const handleSendMessage = async () => { - if (agentChatInput.trim() === '' || isLoading === true) { + if (agentChatInput.trim() === '' || isLoading || !currentAIChatThread) { return; } diff --git a/packages/twenty-front/src/modules/ai/hooks/useAgentChatData.ts b/packages/twenty-front/src/modules/ai/hooks/useAgentChatData.ts index 6daf814b6e..046e8154d4 100644 --- a/packages/twenty-front/src/modules/ai/hooks/useAgentChatData.ts +++ b/packages/twenty-front/src/modules/ai/hooks/useAgentChatData.ts @@ -4,6 +4,7 @@ import { type AgentChatUsageState, } from '@/ai/states/agentChatUsageState'; import { currentAIChatThreadState } from '@/ai/states/currentAIChatThreadState'; +import { isCreatingChatThreadState } from '@/ai/states/isCreatingChatThreadState'; import { mapDBMessagesToUIMessages } from '@/ai/utils/mapDBMessagesToUIMessages'; import { type SetterOrUpdater, @@ -13,6 +14,7 @@ import { import { isDefined } from 'twenty-shared/utils'; import { type AgentChatThread, + useCreateChatThreadMutation, useGetChatMessagesQuery, useGetChatThreadsQuery, } from '~/generated-metadata/graphql'; @@ -43,9 +45,23 @@ export const useAgentChatData = () => { currentAIChatThreadState, ); const setAgentChatUsage = useSetRecoilState(agentChatUsageState); + const [isCreatingChatThread, setIsCreatingChatThread] = useRecoilState( + isCreatingChatThreadState, + ); const { scrollToBottom } = useAgentChatScrollToBottom(); + const [createChatThread] = useCreateChatThreadMutation({ + onCompleted: (data) => { + setIsCreatingChatThread(false); + setCurrentAIChatThread(data.createChatThread.id); + setAgentChatUsage(null); + }, + onError: () => { + setIsCreatingChatThread(false); + }, + }); + const { loading: threadsLoading } = useGetChatThreadsQuery({ skip: isDefined(currentAIChatThread), onCompleted: (data) => { @@ -54,6 +70,9 @@ export const useAgentChatData = () => { setCurrentAIChatThread(firstThread.id); setUsageFromThread(firstThread, setAgentChatUsage); + } else if (!isCreatingChatThread) { + setIsCreatingChatThread(true); + createChatThread(); } }, }); diff --git a/packages/twenty-front/src/modules/ai/states/isCreatingChatThreadState.ts b/packages/twenty-front/src/modules/ai/states/isCreatingChatThreadState.ts new file mode 100644 index 0000000000..1005e99b04 --- /dev/null +++ b/packages/twenty-front/src/modules/ai/states/isCreatingChatThreadState.ts @@ -0,0 +1,6 @@ +import { atom } from 'recoil'; + +export const isCreatingChatThreadState = atom({ + key: 'ai/isCreatingChatThreadState', + default: false, +});