Skip to content

Commit 71418f9

Browse files
authored
feat: allow to download model generated files other than images (#725)
* feat: allow to download model generated files other than images * refactor: move markdown image fix into citations fix function * add file name
1 parent eeec651 commit 71418f9

File tree

11 files changed

+289
-70
lines changed

11 files changed

+289
-70
lines changed

src/interfaces/assistants_web/src/app/(main)/(chat)/Chat.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { useCitationsStore, useConversationStore, useParamsStore } from '@/store
1010
import { OutputFiles } from '@/stores/slices/citationsSlice';
1111
import {
1212
createStartEndKey,
13-
fixCitationsLeadingMarkdown,
13+
fixInlineCitationsForMarkdown,
1414
mapHistoryToMessages,
1515
parsePythonInterpreterToolFields,
1616
} from '@/utils';
@@ -86,7 +86,7 @@ const Chat: React.FC<{ agentId?: string; conversationId?: string }> = ({
8686
}
8787
}
8888
});
89-
fixCitationsLeadingMarkdown(message.citations, message.text)?.forEach((citation) => {
89+
fixInlineCitationsForMarkdown(message.citations, message.text)?.forEach((citation) => {
9090
const startEndKey = createStartEndKey(citation.start ?? 0, citation.end ?? 0);
9191
const documents = citation.document_ids?.map((id) => documentsMap[id]) ?? [];
9292
addCitation(message.generation_id ?? '', startEndKey, documents);

src/interfaces/assistants_web/src/components/Markdown/Markdown.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import remarkMath from 'remark-math';
1111
import { PluggableList } from 'unified';
1212

1313
import {
14+
Anchor,
1415
Iframe,
1516
P,
1617
Pre,
@@ -111,6 +112,7 @@ export const Markdown = ({
111112
references: References,
112113
// @ts-ignore
113114
iframe: Iframe,
115+
a: Anchor,
114116
...customComponents,
115117
}),
116118
[customComponents]
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
'use client';
2+
3+
import type { Component, ExtraProps } from 'hast-util-to-jsx-runtime/lib/components';
4+
import { ComponentPropsWithoutRef } from 'react';
5+
6+
import { Icon } from '@/components/UI';
7+
import { useOutputFiles } from '@/stores';
8+
9+
export const Anchor: Component<ComponentPropsWithoutRef<'a'> & ExtraProps> = ({ children }) => {
10+
const { outputFiles } = useOutputFiles();
11+
12+
if (typeof children === 'string') {
13+
const snakeCaseUrl = children.replace(/\s/g, '_').toLowerCase();
14+
const outputFile = Object.entries(outputFiles).find(([key]) =>
15+
key.startsWith(snakeCaseUrl)
16+
)?.[1];
17+
const downloadUrl = outputFile?.downloadUrl;
18+
19+
if (downloadUrl) {
20+
return (
21+
<a href={downloadUrl} download={snakeCaseUrl} className="flex items-center gap-1">
22+
{children}
23+
<Icon name="download" />
24+
</a>
25+
);
26+
}
27+
}
28+
29+
return <a dir="auto">{children}</a>;
30+
};

src/interfaces/assistants_web/src/components/Markdown/tags/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
export * from './Anchor';
12
export * from './Iframe';
23
export * from './P';
34
export * from './Pre';

src/interfaces/assistants_web/src/hooks/use-chat.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ import {
4242
} from '@/types/message';
4343
import {
4444
createStartEndKey,
45-
fixCitationsLeadingMarkdown,
45+
fixInlineCitationsForMarkdown,
4646
fixMarkdownImagesInText,
4747
isGroundingOn,
4848
parsePythonInterpreterToolFields,
@@ -384,7 +384,7 @@ export const useChat = (config?: { onSend?: (msg: string) => void }) => {
384384
case StreamEvent.CITATION_GENERATION: {
385385
const data = eventData.data;
386386
const newCitations = [...(data?.citations ?? [])];
387-
const fixedCitations = fixCitationsLeadingMarkdown(newCitations, botResponse);
387+
const fixedCitations = fixInlineCitationsForMarkdown(newCitations, botResponse);
388388
citations.push(...fixedCitations);
389389
citations.sort((a, b) => (a.start ?? 0) - (b.start ?? 0));
390390
saveCitations(generationId, fixedCitations, documentsMap);

src/interfaces/assistants_web/src/stores/index.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,4 +78,13 @@ export const useParamsStore = () => {
7878
);
7979
};
8080

81+
export const useOutputFiles = () => {
82+
return useStore(
83+
(state) => ({
84+
outputFiles: state.citations.outputFiles,
85+
}),
86+
shallow
87+
);
88+
};
89+
8190
export { useSettingsStore } from '@/stores/persistedStore';

src/interfaces/assistants_web/src/stores/slices/citationsSlice.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { StateCreator } from 'zustand';
22

33
import { Document } from '@/cohere-client';
4+
import { mapExtensionToMimeType } from '@/utils';
45

56
import { StoreState } from '..';
67

@@ -22,7 +23,9 @@ interface SearchResults {
2223
[documentId: string]: Record<string, any>;
2324
}
2425

25-
export type OutputFiles = { [name: string]: { name: string; data: string; documentId?: string } };
26+
export type OutputFiles = {
27+
[name: string]: { name: string; data: string; documentId?: string; downloadUrl?: string };
28+
};
2629

2730
type State = {
2831
citationReferences: CitationReferences;
@@ -80,6 +83,18 @@ export const createCitationsSlice: StateCreator<StoreState, [], [], CitationsSto
8083
}));
8184
},
8285
saveOutputFiles(outputFiles) {
86+
for (const [name, file] of Object.entries(outputFiles)) {
87+
if (file.downloadUrl) {
88+
continue;
89+
}
90+
const data = file.data;
91+
const fileExtension = file.name.split('.').pop() || '.txt';
92+
const mimeType = mapExtensionToMimeType(fileExtension);
93+
const blob = new Blob([data], { type: mimeType });
94+
const url = URL.createObjectURL(blob);
95+
outputFiles[name] = { ...file, downloadUrl: url };
96+
}
97+
8398
set((state) => ({
8499
citations: {
85100
...state.citations,

src/interfaces/assistants_web/src/utils/citations.test.ts

Lines changed: 125 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
import { describe, expect, test } from 'vitest';
22

33
import { Citation } from '@/cohere-client';
4-
import { fixCitationsLeadingMarkdown, replaceTextWithCitations } from '@/utils';
4+
import {
5+
fixInlineCitationsForMarkdown,
6+
isReferenceBetweenSpecialTags,
7+
replaceTextWithCitations,
8+
} from '@/utils';
59

610
describe('replaceTextWithCitations', () => {
711
test('should replace text with citations', () => {
@@ -44,46 +48,6 @@ describe('replaceTextWithCitations', () => {
4448
);
4549
});
4650

47-
test('should avoid to break markdown images', () => {
48-
const citations: Citation[] = [
49-
{
50-
start: 0,
51-
end: 26,
52-
text: '! [test](https://test.com)',
53-
document_ids: ['12345'],
54-
},
55-
];
56-
const text = '![test](https://test.com)';
57-
const generationId = '12345';
58-
const result = replaceTextWithCitations(text, citations, generationId);
59-
expect(result).toBe(
60-
':cite[![test](https://test.com)]{generationId="12345" start="0" end="26"}'
61-
);
62-
});
63-
64-
test('should handle extra space when fixing markdown images', () => {
65-
const citations: Citation[] = [
66-
{
67-
start: 5,
68-
end: 31,
69-
text: '! [test](https://test.com)',
70-
document_ids: ['12345'],
71-
},
72-
{
73-
start: 32,
74-
end: 39,
75-
text: 'Kadabra',
76-
document_ids: ['44444'],
77-
},
78-
];
79-
const text = 'Abra ![test](https://test.com) Kadabra';
80-
const generationId = '12345';
81-
const result = replaceTextWithCitations(text, citations, generationId);
82-
expect(result).toBe(
83-
'Abra :cite[![test](https://test.com)]{generationId="12345" start="5" end="31"} :cite[Kadabra]{generationId="12345" start="32" end="39"}'
84-
);
85-
});
86-
8751
test('should allow citations as markdown elements', () => {
8852
const citations: Citation[] = [
8953
{
@@ -100,7 +64,7 @@ describe('replaceTextWithCitations', () => {
10064
});
10165
});
10266

103-
describe('fixCitationsLeadingMarkdown', () => {
67+
describe('fixInlineCitationsForMarkdown', () => {
10468
test('should fix leading markdown citations breaking markdown', () => {
10569
const citations: Citation[] = [
10670
{
@@ -142,7 +106,7 @@ describe('fixCitationsLeadingMarkdown', () => {
142106
];
143107
const text =
144108
'The `ENVIRONMENT DIVISION` should be included after the `IDENTIFICATION DIVISION`. The `CONFIGURATION SECTION` and `SPECIAL-NAMES` should be included within the `ENVIRONMENT DIVISION`.';
145-
const result = fixCitationsLeadingMarkdown(citations, text);
109+
const result = fixInlineCitationsForMarkdown(citations, text);
146110

147111
expect(result).toStrictEqual([
148112
{
@@ -183,4 +147,122 @@ describe('fixCitationsLeadingMarkdown', () => {
183147
},
184148
]);
185149
});
150+
151+
test('should push markdown downloable links to the end', () => {
152+
const citations: Citation[] = [
153+
{
154+
text: '[link](https://www.google.com)',
155+
start: 10,
156+
end: 39,
157+
document_ids: ['111111'],
158+
},
159+
{
160+
text: "[link]('.file_path.csv')",
161+
start: 64,
162+
end: 87,
163+
document_ids: ['111111'],
164+
},
165+
];
166+
const text =
167+
"This is a [link](https://www.google.com) and this is downloable [link]('file_path.csv')";
168+
const result = fixInlineCitationsForMarkdown(citations, text);
169+
expect(result).toStrictEqual([
170+
{
171+
text: '[link](https://www.google.com)',
172+
end: 39,
173+
start: 88,
174+
document_ids: ['111111'],
175+
},
176+
{
177+
text: "[link]('.file_path.csv')",
178+
end: 87,
179+
start: 88,
180+
document_ids: ['111111'],
181+
},
182+
]);
183+
});
184+
185+
test('should remove extra blank space on markdown images', () => {
186+
const citations: Citation[] = [
187+
{
188+
start: 0,
189+
end: 25,
190+
text: '! [test](https://test.com)',
191+
document_ids: ['12345'],
192+
},
193+
{
194+
start: 26,
195+
end: 33,
196+
text: 'Kadabra',
197+
document_ids: ['44444'],
198+
},
199+
];
200+
const text = '![test](https://test.com) Kadabra';
201+
const result = fixInlineCitationsForMarkdown(citations, text);
202+
expect(result).toStrictEqual([
203+
{
204+
start: 34,
205+
end: 25,
206+
text: '![test](https://test.com)',
207+
document_ids: ['12345'],
208+
},
209+
{
210+
start: 25,
211+
end: 32,
212+
text: 'Kadabra',
213+
document_ids: ['44444'],
214+
},
215+
]);
216+
});
217+
});
218+
219+
describe('isReferenceBetweenSpecialTags', () => {
220+
test('should return true if the citation is between <iframe> tags', () => {
221+
const matchRegex = /<iframe>.*<\/iframe>/;
222+
const text = '<iframe> This is a citation </iframe>';
223+
const citation: Citation = {
224+
start: 19,
225+
end: 27,
226+
text: 'citation',
227+
document_ids: ['12345'],
228+
};
229+
const result = isReferenceBetweenSpecialTags(matchRegex, text, citation.start);
230+
expect(result).toBe(true);
231+
});
232+
test('should return false if the citation is not between <iframe> tags', () => {
233+
const matchRegex = /<iframe>.*<\/iframe>/;
234+
const text = 'This is a citation <iframe> another test citaiton </iframe>';
235+
const citation: Citation = {
236+
start: 10,
237+
end: 18,
238+
text: 'citation',
239+
document_ids: ['12345'],
240+
};
241+
const result = isReferenceBetweenSpecialTags(matchRegex, text, citation.start);
242+
expect(result).toBe(false);
243+
});
244+
test('should return true if the citation is between ``` tags', () => {
245+
const matchRegex = /```[\s\S]*?```/g;
246+
const text = '``` This is a citation ```';
247+
const citation: Citation = {
248+
start: 14,
249+
end: 22,
250+
text: 'citation',
251+
document_ids: ['12345'],
252+
};
253+
const result = isReferenceBetweenSpecialTags(matchRegex, text, citation.start);
254+
expect(result).toBe(true);
255+
});
256+
test('should return false if the citation is not between ``` tags', () => {
257+
const matchRegex = /```[\s\S]*?```/g;
258+
const text = '``` This is a citation ``` another test citaiton';
259+
const citation: Citation = {
260+
start: 40,
261+
end: 48,
262+
text: 'citation',
263+
document_ids: ['12345'],
264+
};
265+
const result = isReferenceBetweenSpecialTags(matchRegex, text, citation.start);
266+
expect(result).toBe(false);
267+
});
186268
});

0 commit comments

Comments
 (0)