From 4cdb7d61b2ef2e882c2f75b4fce860f836017517 Mon Sep 17 00:00:00 2001
From: "Abdullah." <125115953+mabdullahabaid@users.noreply.github.com>
Date: Mon, 9 Feb 2026 19:29:32 +0500
Subject: [PATCH] Redesign AI chat and add pre-existing prompts. (#17787)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Redesigned AI-chat based on the following provided design.
---------
Co-authored-by: Félix Malfait
---
.../ai/components/AIChatEmptyState.tsx | 43 +---
.../src/modules/ai/components/AIChatTab.tsx | 116 ++++++----
.../internal/AIChatContextUsageButton.tsx | 9 +-
.../internal/AgentChatFileUploadButton.tsx | 12 +-
.../internal/ContextUsageProgressRing.tsx | 6 +-
.../components/internal/SendMessageButton.tsx | 12 +-
.../AIChatSuggestedPrompts.tsx | 58 +++++
.../default-suggested-prompts.ts | 48 ++++
.../CommandMenuTopBarRightCornerIcon.tsx | 24 +-
.../utils/parse-clickhouse-filter.util.ts | 209 ++++++++++++++++++
.../utils/parse-clickhouse-order-by.util.ts | 28 +++
.../validate-allowed-value.util.spec.ts | 30 +++
.../utils/validate-allowed-value.util.ts | 12 +
13 files changed, 494 insertions(+), 113 deletions(-)
create mode 100644 packages/twenty-front/src/modules/ai/components/suggested-prompts/AIChatSuggestedPrompts.tsx
create mode 100644 packages/twenty-front/src/modules/ai/components/suggested-prompts/default-suggested-prompts.ts
create mode 100644 packages/twenty-server/src/engine/api/clickhouse-query-runners/utils/parse-clickhouse-filter.util.ts
create mode 100644 packages/twenty-server/src/engine/api/clickhouse-query-runners/utils/parse-clickhouse-order-by.util.ts
create mode 100644 packages/twenty-server/src/engine/core-modules/sql-sanitization/utils/__tests__/validate-allowed-value.util.spec.ts
create mode 100644 packages/twenty-server/src/engine/core-modules/sql-sanitization/utils/validate-allowed-value.util.ts
diff --git a/packages/twenty-front/src/modules/ai/components/AIChatEmptyState.tsx b/packages/twenty-front/src/modules/ai/components/AIChatEmptyState.tsx
index db1d63e7e7..fc83b72db1 100644
--- a/packages/twenty-front/src/modules/ai/components/AIChatEmptyState.tsx
+++ b/packages/twenty-front/src/modules/ai/components/AIChatEmptyState.tsx
@@ -1,52 +1,19 @@
-import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
-import { t } from '@lingui/core/macro';
-import { IconSparkles } from 'twenty-ui/display';
+
+import { AIChatSuggestedPrompts } from '@/ai/components/suggested-prompts/AIChatSuggestedPrompts';
+
const StyledEmptyState = styled.div`
display: flex;
flex-direction: column;
flex: 1;
- gap: ${({ theme }) => theme.spacing(2)};
- align-items: center;
- justify-content: center;
+ justify-content: flex-end;
height: 100%;
`;
-const StyledSparkleIcon = styled.div`
- align-items: center;
- background: ${({ theme }) => theme.background.transparent.blue};
- border-radius: ${({ theme }) => theme.border.radius.sm};
- padding: ${({ theme }) => theme.spacing(2.5)};
- display: flex;
- justify-content: center;
- margin-bottom: ${({ theme }) => theme.spacing(2)};
-`;
-
-const StyledTitle = styled.div`
- font-size: ${({ theme }) => theme.font.size.lg};
- color: ${({ theme }) => theme.font.color.primary};
- font-weight: 600;
-`;
-
-const StyledDescription = styled.div`
- color: ${({ theme }) => theme.font.color.secondary};
- text-align: center;
- max-width: 85%;
- font-size: ${({ theme }) => theme.font.size.md};
-`;
-
export const AIChatEmptyState = () => {
- const theme = useTheme();
-
return (
-
-
-
- {t`Chat`}
-
- {t`Start a conversation with your AI agent to get workflow insights, task assistance, and process guidance`}
-
+
);
};
diff --git a/packages/twenty-front/src/modules/ai/components/AIChatTab.tsx b/packages/twenty-front/src/modules/ai/components/AIChatTab.tsx
index e5294d2bc3..64d64c9bef 100644
--- a/packages/twenty-front/src/modules/ai/components/AIChatTab.tsx
+++ b/packages/twenty-front/src/modules/ai/components/AIChatTab.tsx
@@ -1,15 +1,14 @@
import { TextArea } from '@/ui/input/components/TextArea';
import styled from '@emotion/styled';
-import { IconHistory, IconMessageCirclePlus } from 'twenty-ui/display';
+import { IconHistory } from 'twenty-ui/display';
+import { IconButton } from 'twenty-ui/input';
import { DropZone } from '@/activities/files/components/DropZone';
import { AgentChatFileUploadButton } from '@/ai/components/internal/AgentChatFileUploadButton';
-import { useCreateNewAIChatThread } from '@/ai/hooks/useCreateNewAIChatThread';
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
-import { AgentMessageRole } from '@/ai/constants/AgentMessageRole';
import { AIChatEmptyState } from '@/ai/components/AIChatEmptyState';
import { AIChatMessage } from '@/ai/components/AIChatMessage';
import { AIChatStandaloneError } from '@/ai/components/AIChatStandaloneError';
@@ -17,6 +16,7 @@ import { AIChatContextUsageButton } from '@/ai/components/internal/AIChatContext
import { AIChatSkeletonLoader } from '@/ai/components/internal/AIChatSkeletonLoader';
import { AgentChatContextPreview } from '@/ai/components/internal/AgentChatContextPreview';
import { SendMessageButton } from '@/ai/components/internal/SendMessageButton';
+import { AgentMessageRole } from '@/ai/constants/AgentMessageRole';
import { AI_CHAT_INPUT_ID } from '@/ai/constants/AiChatInputId';
import { AI_CHAT_SCROLL_WRAPPER_ID } from '@/ai/constants/AiChatScrollWrapperId';
import { useAIChatFileUpload } from '@/ai/hooks/useAIChatFileUpload';
@@ -25,7 +25,6 @@ import { agentChatInputState } from '@/ai/states/agentChatInputState';
import { t } from '@lingui/core/macro';
import { useState } from 'react';
import { useRecoilState } from 'recoil';
-import { Button } from 'twenty-ui/input';
const StyledContainer = styled.div<{ isDraggingFile: boolean }>`
background: ${({ theme }) => theme.background.primary};
@@ -46,6 +45,46 @@ const StyledInputArea = styled.div`
background: ${({ theme }) => theme.background.primary};
`;
+const StyledInputBox = styled.div`
+ background-color: ${({ theme }) => theme.background.transparent.lighter};
+ border: 1px solid ${({ theme }) => theme.border.color.medium};
+ border-radius: ${({ theme }) => theme.border.radius.sm};
+ display: flex;
+ flex-direction: column;
+ gap: ${({ theme }) => theme.spacing(2)};
+ min-height: 140px;
+ padding: ${({ theme }) => theme.spacing(2)};
+ width: 100%;
+ box-sizing: border-box;
+
+ &:focus-within {
+ border-color: ${({ theme }) => theme.color.blue};
+ box-shadow: 0px 0px 0px 3px ${({ theme }) => theme.color.transparent.blue2};
+ }
+`;
+
+const StyledTextAreaWrapper = styled.div`
+ display: flex;
+ flex: 1;
+ flex-direction: column;
+ min-height: 0;
+`;
+
+const StyledChatTextArea = styled(TextArea)`
+ && {
+ background: transparent;
+ border: none;
+ border-radius: 0;
+ box-shadow: none;
+ padding: 0;
+ }
+
+ &&:focus {
+ border: none;
+ box-shadow: none;
+ }
+`;
+
const StyledScrollWrapper = styled(ScrollWrapper)`
display: flex;
flex: 1;
@@ -59,7 +98,8 @@ const StyledScrollWrapper = styled(ScrollWrapper)`
const StyledButtonsContainer = styled.div`
display: flex;
flex-direction: row;
- gap: ${({ theme }) => theme.spacing(2)};
+ gap: ${({ theme }) => theme.spacing(1)};
+ justify-content: flex-end;
`;
export const AIChatTab = () => {
@@ -72,7 +112,6 @@ export const AIChatTab = () => {
useRecoilState(agentChatInputState);
const { uploadFiles } = useAIChatFileUpload();
- const { createChatThread } = useCreateNewAIChatThread();
const { navigateCommandMenu } = useCommandMenu();
return (
@@ -115,7 +154,9 @@ export const AIChatTab = () => {
)}
)}
- {messages.length === 0 && !error && }
+ {messages.length === 0 && !error && !isLoading && (
+
+ )}
{messages.length === 0 && error && !isLoading && (
)}
@@ -123,37 +164,36 @@ export const AIChatTab = () => {
-
>
)}
diff --git a/packages/twenty-front/src/modules/ai/components/internal/AIChatContextUsageButton.tsx b/packages/twenty-front/src/modules/ai/components/internal/AIChatContextUsageButton.tsx
index 2070f6f603..60821915ff 100644
--- a/packages/twenty-front/src/modules/ai/components/internal/AIChatContextUsageButton.tsx
+++ b/packages/twenty-front/src/modules/ai/components/internal/AIChatContextUsageButton.tsx
@@ -19,14 +19,11 @@ const StyledContainer = styled.div`
const StyledTrigger = styled.div<{ hasUsage: boolean }>`
align-items: center;
- background: transparent;
- border: 1px solid ${({ theme }) => theme.background.transparent.medium};
- border-radius: ${({ theme }) => theme.border.radius.sm};
cursor: ${({ hasUsage }) => (hasUsage ? 'pointer' : 'default')};
display: flex;
- gap: ${({ theme }) => theme.spacing(1)};
height: 24px;
- padding: 0 ${({ theme }) => theme.spacing(2)};
+ justify-content: center;
+ min-width: 24px;
transition: background 0.1s ease;
&:hover {
@@ -137,7 +134,6 @@ export const AIChatContextUsageButton = () => {
- 0%
);
@@ -160,7 +156,6 @@ export const AIChatContextUsageButton = () => {
>
- {formattedPercentage}%
{isHovered && (
diff --git a/packages/twenty-front/src/modules/ai/components/internal/AgentChatFileUploadButton.tsx b/packages/twenty-front/src/modules/ai/components/internal/AgentChatFileUploadButton.tsx
index dd76b658bf..e76858840d 100644
--- a/packages/twenty-front/src/modules/ai/components/internal/AgentChatFileUploadButton.tsx
+++ b/packages/twenty-front/src/modules/ai/components/internal/AgentChatFileUploadButton.tsx
@@ -1,10 +1,11 @@
import { useAIChatFileUpload } from '@/ai/hooks/useAIChatFileUpload';
import { agentChatSelectedFilesState } from '@/ai/states/agentChatSelectedFilesState';
import styled from '@emotion/styled';
+import { t } from '@lingui/core/macro';
import React, { useRef } from 'react';
import { useSetRecoilState } from 'recoil';
-import { IconPaperclip } from 'twenty-ui/display';
-import { Button } from 'twenty-ui/input';
+import { IconPlus } from 'twenty-ui/display';
+import { IconButton } from 'twenty-ui/input';
const StyledFileUploadContainer = styled.div`
display: flex;
@@ -44,13 +45,14 @@ export const AgentChatFileUploadButton = () => {
onChange={handleFileInputChange}
/>
-