Skip to content

Commit

Permalink
Markdown formatter for chatbot responses (#12)
Browse files Browse the repository at this point in the history
* Feat: adding showdownjs markdown formatter

* fix: prettier

* feat: moving formatter to composable

* refactor: method to get expected response format

* fix: use responseFormat

* Fix: lists not formatting as they should

* feat: markdown recognizes lists now

* fix: prettier

* Feat: using regex formatter instead of showdownjs

* fix: prettier + linter

* fix: removing unused packages

* fix: adding headers style

* Feat: '\n' now become '<br />' and history style fix

* fix: prettier

* fix: changing break-spaces to break-words

* chore: downsize titles sizes

* chore: changing text-md to text-base

* chore: removing unused dependencies

---------

Co-authored-by: stefanorosanelli <[email protected]>
  • Loading branch information
BuonDavide and stefanorosanelli authored Nov 13, 2024
1 parent 454cf47 commit 52c6bc1
Show file tree
Hide file tree
Showing 6 changed files with 103 additions and 15 deletions.
31 changes: 30 additions & 1 deletion assets/components/_components.css
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,41 @@
}

ol {
@apply list-outside;
@apply list-outside list-decimal;
}

ol ul,
ul ul {
@apply list-disc list-outside ml-4;
}

li {
@apply list-item ml-4;
}

h1 {
@apply text-4xl font-extrabold;
}

h2 {
@apply text-3xl font-bold;
}

h3 {
@apply text-2xl font-bold;
}

h4 {
@apply text-xl font-bold;
}

h5 {
@apply text-lg font-bold;
}

h6 {
@apply text-base font-bold;
}
}

.grid-dashboard {
Expand Down
55 changes: 55 additions & 0 deletions composables/response-formatter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
export const useResponseFormat = () => {
const formatResponse = (textToFormat: string, format: string = 'text') => {
if (format === 'markdown') {
const formattedText = formatText(textToFormat);
return formattedText;
}

return textToFormat;
};

const llmResponseFormat = (llmConf: any) => {
if (!llmConf || llmConf._type === 'openai-chat') {
return 'markdown';
}

return 'text';
};

const formatText = (textToFormat: string) => {
//Regex for recognizing bold text ( **...** e __...__ )
const boldRegex = /\*\*(.+?)\*\*|__(.+?)__/g;

//Regex for recognizing titles ( ###.... )
const titleRegex = /^(#{1,6})\s+(.+)$/gm;

//Regex for code (`...`)
const codeRegex = /`([^`]*)`/g;

const lines = textToFormat.split('\n');
let fortmattedLines;
fortmattedLines = lines.map((line) => {
return line.replace(boldRegex, (_, boldType1, boldType2) => {
const boldText = boldType1 || boldType2;
return `<strong>${boldText}</strong>`;
});
});
fortmattedLines = fortmattedLines.map((line) => {
return line.replace(titleRegex, (_, hashes, titleText) => {
const titleLevel = hashes.length;
return `<h${titleLevel}>${titleText}</h${titleLevel}>`;
});
});
fortmattedLines = fortmattedLines.map((line) => {
return line.replace(codeRegex, (_, code) => {
return `<code>${code}</code>`;
});
});
return fortmattedLines.join('<br />');
};

return {
llmResponseFormat,
formatResponse,
};
};
1 change: 1 addition & 0 deletions locales/en/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"CANCEL": "Cancel",
"CATEGORIZE_CONTENT": "Categorize content",
"CHANGE_PASSWORD": "Change password",
"CHAT_HISTORY_TITLE": "Chatbot history for ",
"CHAT_HISTORY_EMPTY": "There are no chats in the selected date range",
"CHAT_HISTORY_PREVIEW": "Chat history preview",
"CHATBOT_HISTORY": "Chatbot messages history",
Expand Down
1 change: 1 addition & 0 deletions locales/it/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"CANCEL": "Annulla",
"CATEGORIZE_CONTENT": "Categorie principali",
"CHANGE_PASSWORD": "Cambio password",
"CHAT_HISTORY_TITLE": "Archivio messaggi per",
"CHAT_HISTORY_EMPTY": "Non ci sono chat nell'intervallo selezionato",
"CHAT_HISTORY_PREVIEW": "Anteprima delle chat",
"CHATBOT_HISTORY": "Archivio messaggi chatbot",
Expand Down
9 changes: 4 additions & 5 deletions pages/chatbot/[id].vue
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
<p class="text-xs">{{ item.who }}</p>
<div class="chat-balloon-status" :class="{ busy: isBusy && i === dialog.length - 1 }"></div>
</div>
<p class="whitespace-break-spaces" v-html="formatResponse(item.message)"></p>
<div class="break-words rich-text" v-html="formatResponse(item.message, responseFormat)"></div>
<!--MENU CONTESTUALE-->
<div
v-if="canSeeDocs && i === dialog.length - 1 && showResponseMenu && hovered === i"
Expand Down Expand Up @@ -95,6 +95,7 @@
<script lang="ts" setup>
const config = useRuntimeConfig();
const store = useStatesStore();
const { formatResponse, llmResponseFormat } = useResponseFormat();
useHead({ title: `Chatbot | ${config.public.appName}` });
interface DialogItem {
Expand Down Expand Up @@ -125,6 +126,7 @@ const showResponseMenu = ref(true);
let sessionId = '';
let collectionName = '';
let editLevel = ItemEditLevel.None;
const responseFormat = ref('text');
onBeforeMount(async () => {
const route = useRoute();
Expand All @@ -148,6 +150,7 @@ onBeforeMount(async () => {
});
}
responseFormat.value = llmResponseFormat(collection.value.cmetadata?.qa_completion_llm);
sessionId = crypto.randomUUID();
isBusy.value = false;
updateLeftMessages();
Expand Down Expand Up @@ -307,8 +310,4 @@ const updateLeftMessages = async () => {
console.log(error);
}
};
const formatResponse = (response: string) => {
return response.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>');
};
</script>
21 changes: 12 additions & 9 deletions pages/chatbot/history-[id].vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<main>
<div class="space-y-10">
<div class="flex items-start justify-between space-x-4">
<h2 class="text-2xl leading-tight">{{ $t('Chat history') }} “{{ collection.cmetadata?.title }}”</h2>
<h2 class="text-2xl leading-tight">{{ $t('CHAT_HISTORY_TITLE') }} “{{ collection.cmetadata?.title }}”</h2>
</div>

<div class="flex justify-between space-x-10">
Expand Down Expand Up @@ -49,29 +49,29 @@
<select
id="download_format"
v-model="downloadFormat"
class="border rounded border-primary bg-white hover:bg-sky-100 focus:outline-primary text-primary"
class="border rounded border-primary bg-white hover:bg-sky-100 focus:outline-primary text-primary px-2"
name="download_format"
>
<option value="CSV">CSV</option>
<option value="XSLX">XSLX</option>
</select>
<button type="submit" class="button button-primary uppercase" :disabled="historyItems.length === 0" @click="downloadHistory">
{{ $t('Download') }}
{{ $t('DOWNLOAD') }}
</button>
</div>
</div>
<div v-if="!loadingChats && historyItems.length === 0" class="flex flex-col space-y-3">
<span>{{ $t('CHAT_HISTORY_EMPTY') }}</span>
</div>
<div v-else class="flex flex-col space-y-3">
<div class="flex justify-between border-b border-slate-300">
<span>{{ $t('CHAT_HISTORY_PREVIEW') }}</span>
<div class="flex justify-between border-b border-primary pb-1.5 items-center">
<span class="uppercase px-4 font-bold">{{ $t('CHAT_HISTORY_PREVIEW') }}</span>
<div
id="customSelect"
class="w-96 px-1 border rounded border-primary bg-white hover:bg-sky-100 focus:outline-primary text-primary hover:cursor-default"
>
<div class="flex flex-row justify-between" @click="openSelect = !openSelect">
<span>{{ selectedChat.title }}</span>
<div class="flex flex-row justify-between py-3 px-4" @click="openSelect = !openSelect">
<span v-html="formatResponse(selectedChat.title, responseFormat)"></span>
<Icon class="text-xs self-center" name="ph:caret-down-bold" />
</div>
<div v-if="openSelect" class="w-96 -mx-1 max-h-96 absolute z-50 bg-white border border-primary rounded shadow-md overflow-y-scroll">
Expand All @@ -86,7 +86,7 @@
openSelect = !openSelect;
"
>
{{ item.sessionTitle }}
<span v-html="formatResponse(item.sessionTitle, responseFormat)"></span>
<span class="self-center text-xs italic">{{ getLocalCreationDate(item.sessionStart) }}</span>
</div>
</div>
Expand All @@ -104,7 +104,7 @@
<div class="flex space-x-3 justify-between">
<p class="text-xs">{{ item.who }}</p>
</div>
<p class="whitespace-break-spaces">{{ item.message }} &nbsp;</p>
<div class="break-words rich-text" v-html="formatResponse(item.message, responseFormat)"></div>
<div
v-if="item.who === 'ASSISTANT' && item.evaluation !== null"
class="p-2 absolute -bottom-4 right-4 z-20 bg-neutral-700 rounded-full flex flex-row"
Expand Down Expand Up @@ -135,6 +135,7 @@ import { utils, writeFile } from 'xlsx';
import moment from 'moment';
const config = useRuntimeConfig();
const { formatResponse, llmResponseFormat } = useResponseFormat();
useHead({ title: `Chat history | ${config.public.appName}` });
interface DialogItem {
Expand Down Expand Up @@ -176,6 +177,8 @@ if (!collection.value?.uuid) {
});
}
const responseFormat = llmResponseFormat(collection.value.cmetadata?.qa_completion_llm);
const csvKeys = ['question', 'answer', 'session_id', 'created', 'user_evaluation', 'user_feedback', 'chat_source'];
onBeforeMount(async () => showHistory());
Expand Down

0 comments on commit 52c6bc1

Please sign in to comment.