Files
twenty/packages/twenty-front/src/modules/ai/utils/formatChatReference.ts
T
Raphaël Bosi 5848c9bd30 Display object, field and view links as chips in the AI chat (#23573)
<img width="3840" height="1876" alt="CleanShot 2026-07-30 at 15 37
46@2x"
src="https://github.com/user-attachments/assets/9fe178b9-c2fa-4b05-9c9d-0cdc80270b67"
/>



https://github.com/user-attachments/assets/34c4e486-c462-4300-ae98-da99f614f069



The AI chat already renders record chips from a `[[record:...]]` marker
the model writes in its prose, but naming an object, field or view
produced plain text. This adds three sibling markers so those render as
chips too, as in the [Figma
design](https://www.figma.com/design/xt8O9mFeLl46C5InWwoMrN/Twenty?node-id=104416-116261).

- `[[object:<nameSingular>:<label>[[/object]]` links to the record index
page. It is name-keyed rather than id-keyed so an object the assistant
only *proposes* to create still renders as a chip, just without a link.
- `[[field:<id>:<label>[[/field]]` links to the field's settings page,
gated on the `DATA_MODEL` permission.
- `[[view:<id>:<label>[[/view]]` links to the object index page for that
view.

Field and view ids must come from a tool, so an unresolvable one falls
back to plain text rather than a chip that goes nowhere.

The record-only parser becomes one scan over all four kinds. Alternative
order is load-bearing: `[[view:<uuid>:` is shaped exactly like the
legacy prefix-less record marker, so metadata kinds are tried first and
only records keep the legacy `]]` terminator.

Server side is prompt-only. The metadata and view tools return bare
objects rather than `ToolOutput`, so there is nowhere to hang a
structured reference array without wrapping every factory, and the names
and ids the markers need are already in those results verbatim.

Also fixes a pre-existing issue in `LazyMarkdownRenderer`: its
`components` map was rebuilt on every render, and react-markdown uses
each entry as the JSX element type, so every node remounted on every
streamed chunk. Harmless before, expensive once the model is told to
chip every metadata name it writes.


<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/23573?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-07-30 15:08:21 +00:00

9 lines
505 B
TypeScript

import { type ChatReferenceIdentity } from '@/ai/types/ChatReferenceIdentity';
import { getChatReferenceCloseTag } from '@/ai/utils/getChatReferenceCloseTag';
import { getChatReferenceIdentitySegment } from '@/ai/utils/getChatReferenceIdentitySegment';
export const formatChatReference = (
reference: ChatReferenceIdentity & { displayName: string },
): string =>
`[[${reference.kind}:${getChatReferenceIdentitySegment(reference)}:${reference.displayName}${getChatReferenceCloseTag(reference.kind)}`;