diff --git a/packages/twenty-front/package.json b/packages/twenty-front/package.json
index d77cf8e0372..0600f18ff60 100644
--- a/packages/twenty-front/package.json
+++ b/packages/twenty-front/package.json
@@ -56,7 +56,9 @@
"docx": "^9.1.0",
"file-saver": "^2.0.5",
"input-otp": "^1.4.2",
+ "react-markdown": "^10.1.0",
"react-qr-code": "^2.0.18",
+ "remark-gfm": "^4.0.1",
"transliteration": "^2.3.5",
"twenty-shared": "workspace:*",
"twenty-ui": "workspace:*"
diff --git a/packages/twenty-front/src/modules/ai/components/AIChatMessage.tsx b/packages/twenty-front/src/modules/ai/components/AIChatMessage.tsx
index 3cf0d370a55..16fb7f34237 100644
--- a/packages/twenty-front/src/modules/ai/components/AIChatMessage.tsx
+++ b/packages/twenty-front/src/modules/ai/components/AIChatMessage.tsx
@@ -5,6 +5,7 @@ import { Avatar, IconDotsVertical, IconSparkles } from 'twenty-ui/display';
import { LightCopyIconButton } from '@/object-record/record-field/components/LightCopyIconButton';
import { AgentChatFilePreview } from '@/ai/components/internal/AgentChatFilePreview';
import { AgentChatMessageRole } from '@/ai/constants/agent-chat-message-role';
+import { LazyMarkdownRenderer } from '@/ai/components/LazyMarkdownRenderer';
import { AgentChatMessage } from '~/generated/graphql';
import { beautifyPastDateRelativeToNow } from '~/utils/date-utils';
@@ -124,13 +125,17 @@ export const AIChatMessage = ({
}) => {
const theme = useTheme();
+ const markdownRender = (text: string) => {
+ return ;
+ };
+
const getAssistantMessageContent = (message: AgentChatMessage) => {
if (message.content !== '') {
- return message.content;
+ return markdownRender(message.content);
}
if (agentStreamingMessage.streamingText !== '') {
- return agentStreamingMessage.streamingText;
+ return markdownRender(agentStreamingMessage.streamingText);
}
if (agentStreamingMessage.toolCall !== '') {
diff --git a/packages/twenty-front/src/modules/ai/components/LazyMarkdownRenderer.tsx b/packages/twenty-front/src/modules/ai/components/LazyMarkdownRenderer.tsx
new file mode 100644
index 00000000000..5142d09328d
--- /dev/null
+++ b/packages/twenty-front/src/modules/ai/components/LazyMarkdownRenderer.tsx
@@ -0,0 +1,68 @@
+import { lazy, Suspense } from 'react';
+import { useTheme } from '@emotion/react';
+import styled from '@emotion/styled';
+import Skeleton, { SkeletonTheme } from 'react-loading-skeleton';
+import { SKELETON_LOADER_HEIGHT_SIZES } from '@/activities/components/SkeletonLoader';
+
+const MarkdownRenderer = lazy(async () => {
+ const [{ default: Markdown }, { default: remarkGfm }] = await Promise.all([
+ import('react-markdown'),
+ import('remark-gfm'),
+ ]);
+
+ return {
+ default: ({ children }: { children: string }) => (
+ {children}
+ ),
+ };
+});
+
+const StyledSkeletonContainer = styled.div`
+ display: flex;
+ flex-direction: column;
+ gap: ${({ theme }) => theme.spacing(2)};
+ width: 100%;
+`;
+
+const LoadingSkeleton = () => {
+ const theme = useTheme();
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export const LazyMarkdownRenderer = ({ text }: { text: string }) => {
+ return (
+ }>
+ {text}
+
+ );
+};
diff --git a/packages/twenty-server/src/engine/metadata-modules/agent/agent-execution.service.ts b/packages/twenty-server/src/engine/metadata-modules/agent/agent-execution.service.ts
index f008bec09ec..6ebfeb1ff4e 100644
--- a/packages/twenty-server/src/engine/metadata-modules/agent/agent-execution.service.ts
+++ b/packages/twenty-server/src/engine/metadata-modules/agent/agent-execution.service.ts
@@ -41,6 +41,7 @@ import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.
import { OutputSchema } from 'src/modules/workflow/workflow-builder/workflow-schema/types/output-schema.type';
import { resolveInput } from 'src/modules/workflow/workflow-executor/utils/variable-resolver.util';
import { streamToBuffer } from 'src/utils/stream-to-buffer';
+import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
import { AgentEntity } from './agent.entity';
import { AgentException, AgentExceptionCode } from './agent.exception';
@@ -62,6 +63,7 @@ export class AgentExecutionService {
private readonly twentyConfigService: TwentyConfigService,
private readonly agentToolService: AgentToolService,
private readonly fileService: FileService,
+ private readonly domainManagerService: DomainManagerService,
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
private readonly workspacePermissionsCacheService: WorkspacePermissionsCacheService,
private readonly aiModelRegistryService: AiModelRegistryService,
@@ -245,7 +247,7 @@ export class AgentExecutionService {
const contextObject = (
await Promise.all(
recordIdsByObjectMetadataNameSingular.map(
- (recordsWithObjectMetadataNameSingular) => {
+ async (recordsWithObjectMetadataNameSingular) => {
if (recordsWithObjectMetadataNameSingular.recordIds.length === 0) {
return [];
}
@@ -256,10 +258,20 @@ export class AgentExecutionService {
roleId,
);
- return repository.find({
- where: {
- id: In(recordsWithObjectMetadataNameSingular.recordIds),
- },
+ return (
+ await repository.find({
+ where: {
+ id: In(recordsWithObjectMetadataNameSingular.recordIds),
+ },
+ })
+ ).map((record) => {
+ return {
+ ...record,
+ resourceUrl: this.domainManagerService.buildWorkspaceURL({
+ workspace,
+ pathname: `object/${recordsWithObjectMetadataNameSingular.objectMetadataNameSingular}/${record.id}`,
+ }),
+ };
});
},
),
diff --git a/packages/twenty-server/src/engine/metadata-modules/agent/agent.module.ts b/packages/twenty-server/src/engine/metadata-modules/agent/agent.module.ts
index edaae25b35c..713a55b25d4 100644
--- a/packages/twenty-server/src/engine/metadata-modules/agent/agent.module.ts
+++ b/packages/twenty-server/src/engine/metadata-modules/agent/agent.module.ts
@@ -16,6 +16,7 @@ import { RoleTargetsEntity } from 'src/engine/metadata-modules/role/role-targets
import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
import { WorkspacePermissionsCacheModule } from 'src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache.module';
import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/workspace-cache-storage.module';
+import { DomainManagerModule } from 'src/engine/core-modules/domain-manager/domain-manager.module';
import { AgentChatMessageEntity } from './agent-chat-message.entity';
import { AgentChatThreadEntity } from './agent-chat-thread.entity';
@@ -53,6 +54,7 @@ import { AgentService } from './agent.service';
WorkspacePermissionsCacheModule,
WorkspaceCacheStorageModule,
TokenModule,
+ DomainManagerModule,
],
controllers: [AgentChatController],
providers: [
diff --git a/packages/twenty-server/src/engine/metadata-modules/agent/constants/agent-system-prompts.const.ts b/packages/twenty-server/src/engine/metadata-modules/agent/constants/agent-system-prompts.const.ts
index 58ee569f648..a3ca58b693e 100644
--- a/packages/twenty-server/src/engine/metadata-modules/agent/constants/agent-system-prompts.const.ts
+++ b/packages/twenty-server/src/engine/metadata-modules/agent/constants/agent-system-prompts.const.ts
@@ -56,5 +56,12 @@ Permissions and capabilities:
If you need more information to answer a question, ask follow-up questions. Always be transparent about your capabilities and limitations.
+When formatting responses:
+- Use markdown syntax to improve readability of long responses
+- Add appropriate headings, lists, bold/italic text where it enhances understanding
+- Include code blocks with proper language tags when showing code examples
+- Create tables when presenting structured data
+- Use blockquotes for important notes or callouts
+
Note: This base system prompt will be combined with the agent's specific instructions and context to provide you with complete guidance for your role.`,
};
diff --git a/yarn.lock b/yarn.lock
index 3de9a184068..f44fdbb1fb9 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -38716,6 +38716,29 @@ __metadata:
languageName: node
linkType: hard
+"hast-util-to-jsx-runtime@npm:^2.0.0":
+ version: 2.3.6
+ resolution: "hast-util-to-jsx-runtime@npm:2.3.6"
+ dependencies:
+ "@types/estree": "npm:^1.0.0"
+ "@types/hast": "npm:^3.0.0"
+ "@types/unist": "npm:^3.0.0"
+ comma-separated-tokens: "npm:^2.0.0"
+ devlop: "npm:^1.0.0"
+ estree-util-is-identifier-name: "npm:^3.0.0"
+ hast-util-whitespace: "npm:^3.0.0"
+ mdast-util-mdx-expression: "npm:^2.0.0"
+ mdast-util-mdx-jsx: "npm:^3.0.0"
+ mdast-util-mdxjs-esm: "npm:^2.0.0"
+ property-information: "npm:^7.0.0"
+ space-separated-tokens: "npm:^2.0.0"
+ style-to-js: "npm:^1.0.0"
+ unist-util-position: "npm:^5.0.0"
+ vfile-message: "npm:^4.0.0"
+ checksum: 10c0/27297e02848fe37ef219be04a26ce708d17278a175a807689e94a821dcffc88aa506d62c3a85beed1f9a8544f7211bdcbcde0528b7b456a57c2e342c3fd11056
+ languageName: node
+ linkType: hard
+
"hast-util-to-mdast@npm:^10.0.0":
version: 10.1.2
resolution: "hast-util-to-mdast@npm:10.1.2"
@@ -39081,6 +39104,13 @@ __metadata:
languageName: node
linkType: hard
+"html-url-attributes@npm:^3.0.0":
+ version: 3.0.1
+ resolution: "html-url-attributes@npm:3.0.1"
+ checksum: 10c0/496e4908aa8b77665f348b4b03521901794f648b8ac34a581022cd6f2c97934d5c910cd91bc6593bbf2994687549037bc2520fcdc769b31484f29ffdd402acd0
+ languageName: node
+ linkType: hard
+
"html-void-elements@npm:^2.0.0":
version: 2.0.1
resolution: "html-void-elements@npm:2.0.1"
@@ -39712,6 +39742,13 @@ __metadata:
languageName: node
linkType: hard
+"inline-style-parser@npm:0.2.4":
+ version: 0.2.4
+ resolution: "inline-style-parser@npm:0.2.4"
+ checksum: 10c0/ddc0b210eaa03e0f98d677b9836242c583c7c6051e84ce0e704ae4626e7871c5b78f8e30853480218b446355745775df318d4f82d33087ff7e393245efa9a881
+ languageName: node
+ linkType: hard
+
"inline-style-prefixer@npm:^7.0.1":
version: 7.0.1
resolution: "inline-style-prefixer@npm:7.0.1"
@@ -51278,6 +51315,28 @@ __metadata:
languageName: node
linkType: hard
+"react-markdown@npm:^10.1.0":
+ version: 10.1.0
+ resolution: "react-markdown@npm:10.1.0"
+ dependencies:
+ "@types/hast": "npm:^3.0.0"
+ "@types/mdast": "npm:^4.0.0"
+ devlop: "npm:^1.0.0"
+ hast-util-to-jsx-runtime: "npm:^2.0.0"
+ html-url-attributes: "npm:^3.0.0"
+ mdast-util-to-hast: "npm:^13.0.0"
+ remark-parse: "npm:^11.0.0"
+ remark-rehype: "npm:^11.0.0"
+ unified: "npm:^11.0.0"
+ unist-util-visit: "npm:^5.0.0"
+ vfile: "npm:^6.0.0"
+ peerDependencies:
+ "@types/react": ">=18"
+ react: ">=18"
+ checksum: 10c0/4a5dc7d15ca6d05e9ee95318c1904f83b111a76f7588c44f50f1d54d4c97193b84e4f64c4b592057c989228238a2590306cedd0c4d398e75da49262b2b5ae1bf
+ languageName: node
+ linkType: hard
+
"react-number-format@npm:^5.3.1":
version: 5.4.0
resolution: "react-number-format@npm:5.4.0"
@@ -52532,6 +52591,19 @@ __metadata:
languageName: node
linkType: hard
+"remark-rehype@npm:^11.0.0, remark-rehype@npm:^11.1.1":
+ version: 11.1.2
+ resolution: "remark-rehype@npm:11.1.2"
+ dependencies:
+ "@types/hast": "npm:^3.0.0"
+ "@types/mdast": "npm:^4.0.0"
+ mdast-util-to-hast: "npm:^13.0.0"
+ unified: "npm:^11.0.0"
+ vfile: "npm:^6.0.0"
+ checksum: 10c0/f9eccacfb596d9605581dc05bfad28635d6ded5dd0a18e88af5fd4df0d3fcf9612e1501d4513bc2164d833cfe9636dab20400080b09e53f155c6e1442a1231fb
+ languageName: node
+ linkType: hard
+
"remark-rehype@npm:^11.1.0":
version: 11.1.1
resolution: "remark-rehype@npm:11.1.1"
@@ -52545,19 +52617,6 @@ __metadata:
languageName: node
linkType: hard
-"remark-rehype@npm:^11.1.1":
- version: 11.1.2
- resolution: "remark-rehype@npm:11.1.2"
- dependencies:
- "@types/hast": "npm:^3.0.0"
- "@types/mdast": "npm:^4.0.0"
- mdast-util-to-hast: "npm:^13.0.0"
- unified: "npm:^11.0.0"
- vfile: "npm:^6.0.0"
- checksum: 10c0/f9eccacfb596d9605581dc05bfad28635d6ded5dd0a18e88af5fd4df0d3fcf9612e1501d4513bc2164d833cfe9636dab20400080b09e53f155c6e1442a1231fb
- languageName: node
- linkType: hard
-
"remark-slug@npm:^6.0.0":
version: 6.1.0
resolution: "remark-slug@npm:6.1.0"
@@ -55444,6 +55503,24 @@ __metadata:
languageName: node
linkType: hard
+"style-to-js@npm:^1.0.0":
+ version: 1.1.17
+ resolution: "style-to-js@npm:1.1.17"
+ dependencies:
+ style-to-object: "npm:1.0.9"
+ checksum: 10c0/429b9d5593a238d73761324e2c12f75b238f6964e12e4ecf7ea02b44c0ec1940b45c1c1fa8fac9a58637b753aa3ce973a2413b2b6da679584117f27a79e33ba3
+ languageName: node
+ linkType: hard
+
+"style-to-object@npm:1.0.9":
+ version: 1.0.9
+ resolution: "style-to-object@npm:1.0.9"
+ dependencies:
+ inline-style-parser: "npm:0.2.4"
+ checksum: 10c0/acc89a291ac348a57fa1d00b8eb39973ea15a6c7d7fe4b11339ea0be3b84acea3670c98aa22e166be20ca3d67e12f68f83cf114dde9d43ebb692593e859a804f
+ languageName: node
+ linkType: hard
+
"style-to-object@npm:^0.4.1":
version: 0.4.4
resolution: "style-to-object@npm:0.4.4"
@@ -57057,7 +57134,9 @@ __metadata:
file-saver: "npm:^2.0.5"
input-otp: "npm:^1.4.2"
optionator: "npm:^0.9.1"
+ react-markdown: "npm:^10.1.0"
react-qr-code: "npm:^2.0.18"
+ remark-gfm: "npm:^4.0.1"
rollup-plugin-visualizer: "npm:^5.14.0"
transliteration: "npm:^2.3.5"
twenty-shared: "workspace:*"