Files
twenty/packages/twenty-front/src/modules/ai/components/MarkdownCodeBlock.tsx
T
Etienne b625bd1995 fix(ai-chat) - improvements (#22193)
- remove flickering at assistant message streamed end
- add copy code
- leave chat history when navigating to settings

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22193?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
2026-06-26 10:26:40 +02:00

81 lines
2.0 KiB
TypeScript

import { styled } from '@linaria/react';
import { useLingui } from '@lingui/react/macro';
import { isNumber, isString } from '@sniptt/guards';
import { Children, isValidElement } from 'react';
import { IconCopy } from 'twenty-ui/icon';
import { LightIconButton } from 'twenty-ui/input';
import { themeCssVariables } from 'twenty-ui/theme-constants';
import { useCopyToClipboard } from '~/hooks/useCopyToClipboard';
const StyledContainer = styled.div`
position: relative;
.markdown-block-code {
margin: 0;
}
`;
const StyledCopyButtonContainer = styled.div`
align-items: center;
border-radius: ${themeCssVariables.border.radius.sm};
display: flex;
opacity: 0;
position: absolute;
right: ${themeCssVariables.spacing[2]};
top: ${themeCssVariables.spacing[2]};
transition: opacity calc(${themeCssVariables.animation.duration.fast} * 1s)
ease;
${StyledContainer}:hover & {
opacity: 1;
}
&:hover {
background: ${themeCssVariables.background.primary};
}
`;
const extractTextFromNode = (node: React.ReactNode): string => {
if (isString(node) || isNumber(node)) {
return node.toString();
}
if (Array.isArray(node)) {
return node.map(extractTextFromNode).join('');
}
if (isValidElement<{ children?: React.ReactNode }>(node)) {
return Children.toArray(node.props.children)
.map(extractTextFromNode)
.join('');
}
return '';
};
export const MarkdownCodeBlock = ({
children,
}: {
children: React.ReactNode;
}) => {
const { t } = useLingui();
const { copyToClipboard } = useCopyToClipboard();
const codeText = extractTextFromNode(children);
return (
<StyledContainer className="markdown-code-outer-container">
<StyledCopyButtonContainer>
<LightIconButton
Icon={IconCopy}
onClick={() => copyToClipboard(codeText, t`Code copied to clipboard`)}
title={t`Copy code`}
size="small"
accent="tertiary"
/>
</StyledCopyButtonContainer>
<pre className="markdown-block-code">{children}</pre>
</StyledContainer>
);
};