ui: improvements for kai file exports
This commit is contained in:
parent
235364b968
commit
be9ef3bd18
5 changed files with 120 additions and 24 deletions
|
|
@ -10,11 +10,13 @@ import { useStore } from 'App/mstore';
|
|||
import { observer } from 'mobx-react-lite';
|
||||
import { useHistory, useLocation } from 'react-router-dom';
|
||||
import ChatsModal from './components/ChatsModal';
|
||||
import { kaiStore } from './KaiStore';
|
||||
|
||||
function KaiChat() {
|
||||
const { userStore, projectsStore } = useStore();
|
||||
const history = useHistory();
|
||||
const [chatTitle, setTitle] = React.useState<string | null>(null);
|
||||
const chatTitle = kaiStore.chatTitle;
|
||||
const setTitle = kaiStore.setTitle;
|
||||
const userId = userStore.account.id;
|
||||
const userLetter = userStore.account.name[0].toUpperCase();
|
||||
const { activeSiteId } = projectsStore;
|
||||
|
|
@ -123,7 +125,7 @@ function KaiChat() {
|
|||
threadId={threadId}
|
||||
projectId={activeSiteId}
|
||||
userLetter={userLetter}
|
||||
onTitleChange={setTitle}
|
||||
chatTitle={chatTitle}
|
||||
initialMsg={initialMsg}
|
||||
setInitialMsg={setInitialMsg}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -26,8 +26,8 @@ export default class KaiService extends AiService {
|
|||
getKaiChat = async (
|
||||
projectId: string,
|
||||
threadId: string,
|
||||
): Promise<
|
||||
{
|
||||
): Promise<{
|
||||
messages: {
|
||||
role: string;
|
||||
content: string;
|
||||
message_id: any;
|
||||
|
|
@ -36,8 +36,9 @@ export default class KaiService extends AiService {
|
|||
supports_visualization: boolean;
|
||||
chart: string;
|
||||
chart_data: string;
|
||||
}[]
|
||||
> => {
|
||||
}[];
|
||||
title: string;
|
||||
}> => {
|
||||
const r = await this.client.get(`/kai/${projectId}/chats/${threadId}`);
|
||||
if (!r.ok) {
|
||||
throw new Error('Failed to fetch chat');
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ class KaiStore {
|
|||
processingStage: BotChunk | null = null;
|
||||
messages: Array<Message> = [];
|
||||
queryText = '';
|
||||
chatTitle: string | null = null;
|
||||
loadingChat = false;
|
||||
replacing: string | null = null;
|
||||
usage = {
|
||||
|
|
@ -56,6 +57,20 @@ class KaiStore {
|
|||
return { msg, index };
|
||||
}
|
||||
|
||||
get firstHumanMessage() {
|
||||
let msg = null;
|
||||
let index = null;
|
||||
for (let i = 0; i < this.messages.length; i++) {
|
||||
const message = this.messages[i];
|
||||
if (message.isUser) {
|
||||
msg = message;
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return { msg, index };
|
||||
}
|
||||
|
||||
get lastKaiMessage() {
|
||||
let msg = null;
|
||||
let index = null;
|
||||
|
|
@ -70,6 +85,14 @@ class KaiStore {
|
|||
return { msg, index };
|
||||
}
|
||||
|
||||
getPreviousMessage = (messageId: string) => {
|
||||
const index = this.messages.findIndex((msg) => msg.messageId === messageId);
|
||||
if (index > 0) {
|
||||
return this.messages[index - 1];
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
setQueryText = (text: string) => {
|
||||
this.queryText = text;
|
||||
};
|
||||
|
|
@ -113,13 +136,21 @@ class KaiStore {
|
|||
});
|
||||
};
|
||||
|
||||
setTitle = (title: string | null) => {
|
||||
this.chatTitle = title;
|
||||
};
|
||||
|
||||
getChat = async (projectId: string, threadId: string) => {
|
||||
this.setLoadingChat(true);
|
||||
try {
|
||||
const res = await aiService.getKaiChat(projectId, threadId);
|
||||
if (res && res.length) {
|
||||
const { messages, title } = await aiService.getKaiChat(
|
||||
projectId,
|
||||
threadId,
|
||||
);
|
||||
if (messages && messages.length) {
|
||||
this.setTitle(title);
|
||||
this.setMessages(
|
||||
res.map((m) => {
|
||||
messages.map((m) => {
|
||||
const isUser = m.role === 'human';
|
||||
return {
|
||||
text: m.content,
|
||||
|
|
@ -144,7 +175,6 @@ class KaiStore {
|
|||
|
||||
createChatManager = (
|
||||
settings: { projectId: string; threadId: string },
|
||||
setTitle: (title: string) => void,
|
||||
initialMsg: string | null,
|
||||
) => {
|
||||
const token = kaiService.client.getJwt();
|
||||
|
|
@ -197,7 +227,7 @@ class KaiStore {
|
|||
}
|
||||
}
|
||||
},
|
||||
titleCallback: setTitle,
|
||||
titleCallback: this.setTitle,
|
||||
});
|
||||
|
||||
if (initialMsg) {
|
||||
|
|
@ -315,6 +345,9 @@ class KaiStore {
|
|||
});
|
||||
try {
|
||||
const filtersStr = await kaiService.getMsgChart(msgId, projectId);
|
||||
if (!filtersStr.length) {
|
||||
throw new Error('No filters found for the message');
|
||||
}
|
||||
const filters = JSON.parse(filtersStr);
|
||||
const data = {
|
||||
...filters,
|
||||
|
|
|
|||
|
|
@ -9,16 +9,16 @@ function ChatLog({
|
|||
projectId,
|
||||
threadId,
|
||||
userLetter,
|
||||
onTitleChange,
|
||||
initialMsg,
|
||||
chatTitle,
|
||||
setInitialMsg,
|
||||
}: {
|
||||
projectId: string;
|
||||
threadId: any;
|
||||
userLetter: string;
|
||||
onTitleChange: (title: string | null) => void;
|
||||
initialMsg: string | null;
|
||||
setInitialMsg: (msg: string | null) => void;
|
||||
chatTitle: string | null;
|
||||
}) {
|
||||
const messages = kaiStore.messages;
|
||||
const loading = kaiStore.loadingChat;
|
||||
|
|
@ -31,7 +31,7 @@ function ChatLog({
|
|||
void kaiStore.getChat(settings.projectId, threadId);
|
||||
}
|
||||
if (threadId) {
|
||||
kaiStore.createChatManager(settings, onTitleChange, initialMsg);
|
||||
kaiStore.createChatManager(settings, initialMsg);
|
||||
}
|
||||
return () => {
|
||||
kaiStore.clearChat();
|
||||
|
|
@ -66,7 +66,12 @@ function ChatLog({
|
|||
userName={userLetter}
|
||||
siteId={projectId}
|
||||
message={msg}
|
||||
canEdit={processingStage === null && msg.isUser && index === lastHumanMsgInd}
|
||||
chatTitle={chatTitle}
|
||||
canEdit={
|
||||
processingStage === null &&
|
||||
msg.isUser &&
|
||||
index === lastHumanMsgInd
|
||||
}
|
||||
/>
|
||||
</React.Fragment>
|
||||
))}
|
||||
|
|
|
|||
|
|
@ -26,11 +26,13 @@ function ChatMsg({
|
|||
siteId,
|
||||
canEdit,
|
||||
message,
|
||||
chatTitle,
|
||||
}: {
|
||||
message: Message;
|
||||
userName?: string;
|
||||
canEdit?: boolean;
|
||||
siteId: string;
|
||||
chatTitle: string | null;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const [metric, setMetric] = React.useState<Widget | null>(null);
|
||||
|
|
@ -47,6 +49,7 @@ function ChatMsg({
|
|||
const isEditing = kaiStore.replacing && messageId === kaiStore.replacing;
|
||||
const [isProcessing, setIsProcessing] = React.useState(false);
|
||||
const bodyRef = React.useRef<HTMLDivElement>(null);
|
||||
const chartRef = React.useRef<HTMLDivElement>(null);
|
||||
const onEdit = () => {
|
||||
kaiStore.editMessage(text, messageId);
|
||||
};
|
||||
|
|
@ -65,19 +68,68 @@ function ChatMsg({
|
|||
setIsProcessing(false);
|
||||
return;
|
||||
}
|
||||
const userPrompt = kaiStore.getPreviousMessage(message.messageId);
|
||||
import('jspdf')
|
||||
.then(({ jsPDF }) => {
|
||||
.then(async ({ jsPDF }) => {
|
||||
const doc = new jsPDF();
|
||||
doc.addImage('/assets/img/logo-img.png', 80, 3, 30, 5);
|
||||
doc.html(bodyRef.current!, {
|
||||
const blockWidth = 170; // mm
|
||||
doc.addImage('/assets/img/logo-img.png', 20, 15, 30, 5);
|
||||
const content = bodyRef.current!.cloneNode(true) as HTMLElement;
|
||||
if (userPrompt) {
|
||||
const titleHeader = document.createElement('h2');
|
||||
titleHeader.textContent = userPrompt.text;
|
||||
titleHeader.style.marginBottom = '10px';
|
||||
content.prepend(titleHeader);
|
||||
}
|
||||
content.querySelectorAll('ul').forEach((ul) => {
|
||||
const frag = document.createDocumentFragment();
|
||||
ul.querySelectorAll('li').forEach((li) => {
|
||||
const div = document.createElement('div');
|
||||
div.textContent = '• ' + li.textContent;
|
||||
frag.appendChild(div);
|
||||
});
|
||||
ul.replaceWith(frag);
|
||||
});
|
||||
content.querySelectorAll('h1,h2,h3,h4,h5,h6').forEach((el) => {
|
||||
(el as HTMLElement).style.letterSpacing = '0.5px';
|
||||
});
|
||||
content.querySelectorAll('*').forEach((node) => {
|
||||
node.childNodes.forEach((child) => {
|
||||
if (child.nodeType === Node.TEXT_NODE) {
|
||||
const txt = child.textContent || '';
|
||||
const replaced = txt.replace(/-/g, '–');
|
||||
if (replaced !== txt) child.textContent = replaced;
|
||||
}
|
||||
});
|
||||
});
|
||||
if (metric && chartRef.current) {
|
||||
const { default: html2canvas } = await import('html2canvas');
|
||||
const metricContainer = chartRef.current;
|
||||
const image = await html2canvas(metricContainer, {
|
||||
backgroundColor: null,
|
||||
scale: 2,
|
||||
});
|
||||
const imgData = image.toDataURL('image/png');
|
||||
const imgHeight = (image.height * blockWidth) / image.width;
|
||||
content.appendChild(
|
||||
Object.assign(document.createElement('img'), {
|
||||
src: imgData,
|
||||
style: `width: ${blockWidth}mm; height: ${imgHeight}mm; margin-top: 10px;`,
|
||||
}),
|
||||
);
|
||||
}
|
||||
doc.html(content, {
|
||||
callback: function (doc) {
|
||||
doc.save('document.pdf');
|
||||
doc.save((chatTitle ?? 'document') + '.pdf');
|
||||
},
|
||||
margin: [10, 10, 10, 10],
|
||||
// top, bottom, ?, left
|
||||
margin: [5, 10, 20, 20],
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 190, // Target width
|
||||
windowWidth: 675, // Window width for rendering
|
||||
y: 15,
|
||||
// Target width
|
||||
width: blockWidth,
|
||||
// Window width for rendering
|
||||
windowWidth: 675,
|
||||
});
|
||||
})
|
||||
.catch((e) => {
|
||||
|
|
@ -138,7 +190,10 @@ function ChatMsg({
|
|||
<Markdown remarkPlugins={[remarkGfm]}>{text}</Markdown>
|
||||
</div>
|
||||
{metric ? (
|
||||
<div className="p-2 border-gray-light rounded-lg shadow bg-glassWhite mb-2">
|
||||
<div
|
||||
ref={chartRef}
|
||||
className="p-2 border-gray-light rounded-lg shadow bg-glassWhite mb-2"
|
||||
>
|
||||
<WidgetChart metric={metric} isPreview height={360} />
|
||||
</div>
|
||||
) : null}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue