Skip to content

Commit f5c88ba

Browse files
committed
fix(chat): preserve tilde-fenced citations
1 parent 6ba39c3 commit f5c88ba

2 files changed

Lines changed: 48 additions & 11 deletions

File tree

apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/chat-content/chat-content.test.ts

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -60,19 +60,50 @@ describe('sanitizeChatDisplayContent', () => {
6060
expect(sanitizeChatDisplayContent(`\`${tag}\``)).toBe(tag)
6161
})
6262

63-
it('preserves fences closed by a longer run and unwraps citations after them', () => {
64-
const tag = '<source>{"url":"https://example.com"}</source>'
65-
const block = `\`\`\`json\n\`${tag}\`\n\`\`\`\`\n`
63+
it.each(['```', '~~~'])(
64+
'preserves a %s fence closed by a longer run and unwraps citations after it',
65+
(fence) => {
66+
const tag = '<source>{"url":"https://example.com"}</source>'
67+
const block = `${fence}json\n\`${tag}\`\n${fence}${fence[0]}\n`
6668

67-
expect(sanitizeChatDisplayContent(`${block}\`${tag}\``)).toBe(`${block}${tag}`)
68-
})
69+
expect(sanitizeChatDisplayContent(`${block}\`${tag}\``)).toBe(`${block}${tag}`)
70+
}
71+
)
6972

70-
it('leaves an unclosed streaming fence literal', () => {
71-
const content = '```json\n`<source>{"url":"https://example.com"}</source>`'
73+
it.each(['```', '~~~'])('leaves an unclosed %s streaming fence literal', (fence) => {
74+
const content = `${fence}json\n\`<source>{"url":"https://example.com"}</source>\``
7275

7376
expect(sanitizeChatDisplayContent(content)).toBe(content)
7477
})
7578

79+
it.each(['source', 'workspace_resource'])(
80+
'preserves tilde-fenced %s chips with backticks in the info string',
81+
(name) => {
82+
const tag = `<${name}>{"title":"Example"}</${name}>`
83+
const block = `~~~example \`code\`\n\`${tag}\`\n~~~\n`
84+
85+
expect(sanitizeChatDisplayContent(`${block}\`${tag}\``)).toBe(`${block}${tag}`)
86+
}
87+
)
88+
89+
it.each(['```', '~~~'])(
90+
'does not close a %s fence with a different character or a shorter run',
91+
(fence) => {
92+
const tag = '<source>{"url":"https://example.com"}</source>'
93+
const otherFence = fence === '```' ? '~~~~' : '````'
94+
const block = `${fence}${fence[0]}\n${otherFence}\n\`${tag}\`\n${fence}\n\`${tag}\`\n${fence}${fence[0]}\n`
95+
96+
expect(sanitizeChatDisplayContent(`${block}\`${tag}\``)).toBe(`${block}${tag}`)
97+
}
98+
)
99+
100+
it('does not open a backtick fence with backticks in its info string', () => {
101+
const prefix = '```example `code`\n\n'
102+
const tag = '<source>{"url":"https://example.com"}</source>'
103+
104+
expect(sanitizeChatDisplayContent(`${prefix}\`${tag}\``)).toBe(`${prefix}${tag}`)
105+
})
106+
76107
it('unwraps workspace resource tags from inline code spans', () => {
77108
const content =
78109
'`I updated <workspace_resource>{"type":"workflow","id":"wf-1","title":"Workflow"}</workspace_resource>.`'

apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/chat-content/chat-sanitize.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -97,13 +97,19 @@ export function sanitizeChatDisplayContent(content: string): string {
9797
const parts: string[] = []
9898
let cursor = 0
9999
let fenceStart: number | null = null
100-
let fenceLength = 0
100+
let fence = ''
101101

102-
for (const line of content.matchAll(/^ {0,3}(`{3,})([^`\n]*)(?:\n|$)/gm)) {
102+
for (const line of content.matchAll(/^ {0,3}(`{3,}|~{3,})([^\n]*)(?:\n|$)/gm)) {
103+
const [, delimiter, info] = line
103104
if (fenceStart === null) {
105+
if (delimiter[0] === '`' && info.includes('`')) continue
104106
fenceStart = line.index
105-
fenceLength = line[1].length
106-
} else if (line[1].length >= fenceLength && line[2].trim() === '') {
107+
fence = delimiter
108+
} else if (
109+
delimiter[0] === fence[0] &&
110+
delimiter.length >= fence.length &&
111+
/^[\t \r]*$/.test(info)
112+
) {
107113
const end = line.index + line[0].length
108114
parts.push(
109115
unwrapInlineChips(content.slice(cursor, fenceStart)),

0 commit comments

Comments
 (0)