Skip to content

Commit

Permalink
Merge pull request #1616 from solliancenet/sc-handle-new-message-resp…
Browse files Browse the repository at this point in the history
…onse-formats-cherry-pick

(0.8.1) Handle new message response formats in UI
  • Loading branch information
ciprianjichici authored Aug 26, 2024
2 parents 7cd1cee + 13e79f5 commit 97ec1af
Show file tree
Hide file tree
Showing 2 changed files with 238 additions and 197 deletions.
257 changes: 60 additions & 197 deletions src/ui/UserPortal/components/ChatMessage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -35,99 +35,37 @@

<!-- Message text -->
<div class="message__body">
<!-- Attachments -->
<AttachmentList
v-if="message.sender === 'User'"
:attachments="message.attachmentDetails ?? []"
:attachmentIds="message.attachments"
/>

<!-- Message loading -->
<template v-if="message.sender === 'Assistant' && message.type === 'LoadingMessage'">
<i class="pi pi-spin pi-spinner"></i>
</template>

<template v-if="!messageContent || messageContent.length === 0">
<div v-html="compiledVueTemplate"></div>
</template>

<template v-else>
<!-- Render the html content and any vue components within -->
<div v-for="content in messageContent" :key="content.fileName" class="message-content">
<div v-if="content.type === 'text'">
<component :is="renderMarkdownComponent(content.value)"></component>
</div>

<div v-else-if="content.type === 'image_file'">
<template v-if="content.loading || (!content.error && !content.blobUrl)">
<div class="loading-content-container">
<i class="pi pi-image loading-content-icon" style="font-size: 2rem"></i>
<i
class="pi pi-spin pi-spinner loading-content-icon"
style="font-size: 1rem"
></i>
<span class="loading-content-text">Loading image...</span>
</div>
</template>
<Image
v-if="content.blobUrl"
:src="content.blobUrl"
:alt="content.fileName"
@load="content.loading = false"
@error="
content.loading = false;
content.error = true;
"
width="45%"
preview
/>
<div v-if="content.error" class="loading-content-error">
<i
class="pi pi-times-circle loading-content-error-icon"
style="font-size: 2rem"
></i>
<span class="loading-content-error-text">Could not load image</span>
</div>
</div>

<div v-else-if="content.type === 'html'">
<template v-if="content.loading || (!content.error && !content.blobUrl)">
<div class="loading-content-container">
<i class="pi pi-chart-line loading-content-icon" style="font-size: 2rem"></i>
<i
class="pi pi-spin pi-spinner loading-content-icon"
style="font-size: 1rem"
></i>
<span class="loading-content-text">Loading visualization...</span>
</div>
</template>
<iframe v-if="content.blobUrl" :src="content.blobUrl" frameborder="0"> </iframe>
</div>

<div v-else-if="content.type === 'file_path'">
Download <i :class="$getFileIconClass(content.fileName, true)" class="attachment-icon"></i>
<a
:href="content.blobUrl"
:download="content.fileName ?? content.blobUrl ?? content.value"
target="_blank"
>
{{ content.fileName ?? content.blobUrl }}
</a>
</div>
</div>

<Button
v-if="message.analysisResults && message.analysisResults.length > 0"
class="message__button"
:disabled="message.type === 'LoadingMessage'"
size="small"
text
icon="pi pi-window-maximize"
label="Analysis"
@click.stop="showAnalysisModal"
/>
</template>
<!-- Render the html content and any vue components within -->
<component v-else :is="compiledMarkdownComponent" />

<!-- Analysis button -->
<Button
v-if="message.analysisResults && message.analysisResults.length > 0"
class="message__button"
:disabled="message.type === 'LoadingMessage'"
size="small"
text
icon="pi pi-window-maximize"
label="Analysis"
@click.stop="showAnalysisModal"
/>
</div>

<!-- Assistant message footer -->
<div v-if="message.sender !== 'User'" class="message__footer">
<!-- Citations -->
<div v-if="message.citations?.length" class="citations">
<span><b>Citations: </b></span>
<span
Expand All @@ -140,6 +78,8 @@
{{ citation.title.split('/').pop() }}
</span>
</div>

<!-- Rating -->
<span class="ratings">
<!-- Like -->
<span>
Expand Down Expand Up @@ -211,6 +151,7 @@
{{ $filters.timeAgo(new Date(message.timeStamp)) }}
</Divider>

<!-- Analysis Modal -->
<AnalysisModal
:visible="isAnalysisModalVisible"
:analysisResults="message.analysisResults ?? []"
Expand All @@ -231,12 +172,12 @@ import type { PropType, ref } from 'vue';
import type { Message, CompletionPrompt } from '@/js/types';
import api from '@/js/api';
import CodeBlockHeader from '@/components/CodeBlockHeader.vue';
import ChatMessageContentBlock from '@/components/ChatMessageContentBlock.vue';
import AttachmentList from '@/components/AttachmentList.vue';
import AnalysisModal from '@/components/AnalysisModal.vue';
import AgentIcon from '@/components/AgentIcon.vue';
const renderer = new marked.Renderer();
renderer.code = (code, language) => {
const sourceCode = code.raw || code;
const validLanguage = !!(language && hljs.getLanguage(language));
Expand All @@ -247,6 +188,8 @@ renderer.code = (code, language) => {
const encodedCode = encodeURIComponent(sourceCode);
return `<pre><code class="${languageClass}" data-code="${encodedCode}" data-language="${highlighted.language}">${highlighted.value}</code></pre>`;
};
marked.use({ renderer });
function processLatex(html) {
const blockLatexPattern = /\\\[([^\]]+)\\\]/g;
const inlineLatexPattern = /\\\(([^\)]+)\\\)/g;
Expand All @@ -266,15 +209,13 @@ function processLatex(html) {
});
// If LaTeX was found, render the content again with marked
if (hasBlockLatex || hasInlineLatex) {
html = marked(html);
}
// if (hasBlockLatex || hasInlineLatex) {
// html = marked(html);
// }
return html;
}
marked.use({ renderer });
function addCodeHeaderComponents(htmlString) {
const parser = new DOMParser();
const doc = parser.parseFromString(htmlString, 'text/html');
Expand Down Expand Up @@ -351,16 +292,45 @@ export default {
computed: {
compiledMarkdown() {
let htmlContent = processLatex(this.message.text ?? '');
htmlContent = marked(htmlContent);
return DOMPurify.sanitize(htmlContent);
function processContentBlock(contentToProcess) {
let htmlContent = processLatex(contentToProcess ?? '');
htmlContent = marked(htmlContent);
return DOMPurify.sanitize(htmlContent);
};
let content = '';
if (this.messageContent && this.messageContent?.length > 0) {
this.messageContent.forEach((contentBlock) => {
switch (contentBlock.type) {
case 'text':
content += processContentBlock(contentBlock.value);
break;
// case 'image_file':
// break;
// case 'html':
// break;
// case 'file_path':
// break;
default:
// Maybe just pass invidual values directly as primitives instead of full object
const contentBlockEncoded = encodeURIComponent(JSON.stringify(contentBlock));
content += `<chat-message-content-block contentencoded="${contentBlockEncoded}"></chat-message-content-block>`;
break;
}
});
} else {
content = processContentBlock(this.message.text);
}
return content;
},
compiledMarkdownComponent() {
return {
template: `<div>${this.compiledVueTemplate}</div>`,
components: {
CodeBlockHeader,
ChatMessageContentBlock,
},
};
},
Expand All @@ -374,22 +344,7 @@ export default {
}
},
mounted() {
this.fetchContentFiles();
},
methods: {
renderMarkdownComponent(contentValue: string) {
let htmlContent = processLatex(contentValue ?? '');
htmlContent = DOMPurify.sanitize(marked(htmlContent));
return {
template: `<div>${htmlContent}</div>`,
components: {
CodeBlockHeader,
},
};
},
displayWordByWord() {
const words = this.compiledMarkdown.split(/\s+/);
if (this.currentWordIndex >= words.length) {
Expand All @@ -404,7 +359,7 @@ export default {
stripTags: false,
ellipsis: '',
decodeEntities: false,
excludes: '',
excludes: ['code-block-header', 'chat-message-content-block'],
reserveLastWord: false,
keepWhitespaces: true,
});
Expand Down Expand Up @@ -443,34 +398,6 @@ export default {
this.prompt = prompt;
this.viewPrompt = true;
},
// Add this method to fetch content files securely
async fetchContentFiles() {
if (!this.messageContent || this.messageContent.length === 0) return;
const fetchPromises = this.messageContent.map(async (content) => {
if (['image_file', 'html', 'file_path'].includes(content.type)) {
content.loading = true;
content.error = false;
try {
const response = await api.fetchDirect(content.value);
if (content.type === 'html') {
const blob = new Blob([response], { type: 'text/html' });
content.blobUrl = URL.createObjectURL(blob);
} else {
content.blobUrl = URL.createObjectURL(response);
}
content.fileName = content.fileName?.split('/').pop();
} catch (error) {
console.error(`Failed to fetch content from ${content.value}`, error);
content.error = true;
}
content.loading = false;
}
});
await Promise.all(fetchPromises);
},
},
};
</script>
Expand Down Expand Up @@ -619,70 +546,6 @@ export default {
border-color: var(--primary-button-bg) !important;
color: var(--primary-button-text) !important;
}
.message-content {
margin-top: 5px;
margin-bottom: 5px;
}
img {
max-width: 100%;
height: auto;
border-radius: 8px;
}
iframe {
width: 100%;
height: 600px;
border-radius: 8px;
}
.loading-content-container {
display: flex;
align-items: center;
.loading-content-icon {
margin-right: 8px;
vertical-align: middle;
line-height: 1;
}
.loading-content-text {
font-size: 0.75rem;
font-style: italic;
line-height: 1.5;
}
}
.loading-content-error {
display: flex;
align-items: center;
width: 200px;
padding: 8px 12px;
border-radius: 0.75rem;
border-color: rgb(182, 2, 2);
color: rgb(182, 2, 2);
box-shadow: 0 1px 3px rgba(182, 2, 2, 0.664);
.loading-content-error-icon {
margin-right: 8px;
vertical-align: middle;
line-height: 1;
}
.loading-content-error-text {
font-size: 0.85rem;
font-style: italic;
line-height: 1.5;
}
}
.attachment-icon {
width: 24px;
margin-right: 4px;
vertical-align: middle;
line-height: 1;
}
</style>

<style lang="scss">
Expand Down
Loading

0 comments on commit 97ec1af

Please sign in to comment.