mirror of
https://github.com/twentyhq/twenty
synced 2026-04-21 13:37:22 +00:00
feat(ai): add markdown in ai chat (#13402)
This commit is contained in:
parent
cbf731dba7
commit
40f529f1ac
7 changed files with 195 additions and 20 deletions
|
|
@ -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:*"
|
||||
|
|
|
|||
|
|
@ -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 <LazyMarkdownRenderer text={text} />;
|
||||
};
|
||||
|
||||
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 !== '') {
|
||||
|
|
|
|||
|
|
@ -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 }) => (
|
||||
<Markdown remarkPlugins={[remarkGfm]}>{children}</Markdown>
|
||||
),
|
||||
};
|
||||
});
|
||||
|
||||
const StyledSkeletonContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: ${({ theme }) => theme.spacing(2)};
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const LoadingSkeleton = () => {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<SkeletonTheme
|
||||
baseColor={theme.background.tertiary}
|
||||
highlightColor={theme.background.transparent.lighter}
|
||||
borderRadius={theme.border.radius.sm}
|
||||
>
|
||||
<StyledSkeletonContainer>
|
||||
<Skeleton
|
||||
width="70%"
|
||||
height={SKELETON_LOADER_HEIGHT_SIZES.standard.m}
|
||||
/>
|
||||
|
||||
<Skeleton height={SKELETON_LOADER_HEIGHT_SIZES.standard.s} />
|
||||
<Skeleton height={SKELETON_LOADER_HEIGHT_SIZES.standard.s} />
|
||||
<Skeleton
|
||||
width="90%"
|
||||
height={SKELETON_LOADER_HEIGHT_SIZES.standard.s}
|
||||
/>
|
||||
|
||||
<Skeleton
|
||||
width="85%"
|
||||
height={SKELETON_LOADER_HEIGHT_SIZES.standard.s}
|
||||
/>
|
||||
<Skeleton
|
||||
width="80%"
|
||||
height={SKELETON_LOADER_HEIGHT_SIZES.standard.s}
|
||||
/>
|
||||
</StyledSkeletonContainer>
|
||||
</SkeletonTheme>
|
||||
);
|
||||
};
|
||||
|
||||
export const LazyMarkdownRenderer = ({ text }: { text: string }) => {
|
||||
return (
|
||||
<Suspense fallback={<LoadingSkeleton />}>
|
||||
<MarkdownRenderer>{text}</MarkdownRenderer>
|
||||
</Suspense>
|
||||
);
|
||||
};
|
||||
|
|
@ -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}`,
|
||||
}),
|
||||
};
|
||||
});
|
||||
},
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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: [
|
||||
|
|
|
|||
|
|
@ -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.`,
|
||||
};
|
||||
|
|
|
|||
105
yarn.lock
105
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:*"
|
||||
|
|
|
|||
Loading…
Reference in a new issue