diff --git a/frontend/app/components/Kai/KaiService.ts b/frontend/app/components/Kai/KaiService.ts
index 0363aa1b3..633d4c3a5 100644
--- a/frontend/app/components/Kai/KaiService.ts
+++ b/frontend/app/components/Kai/KaiService.ts
@@ -34,7 +34,7 @@ export default class KaiService extends AiService {
return true;
}
- getKaiChat = async (projectId: string, userId: string, threadId: string): Promise<{ role: string, content: string, message_id: any }[]> => {
+ getKaiChat = async (projectId: string, userId: string, threadId: string): Promise<{ role: string, content: string, message_id: any, duration?: number }[]> => {
const jwt = window.env.KAI_TESTING // this.client.getJwt()
const r = await fetch(`http://localhost:8700/kai/${projectId}/chats/${threadId}?user_id=${userId}`, {
method: 'GET',
diff --git a/frontend/app/components/Kai/KaiStore.ts b/frontend/app/components/Kai/KaiStore.ts
index 4bb04399d..a3a1583c6 100644
--- a/frontend/app/components/Kai/KaiStore.ts
+++ b/frontend/app/components/Kai/KaiStore.ts
@@ -99,6 +99,7 @@ class KaiStore {
text: m.content,
isUser: isUser,
messageId: m.message_id,
+ duration: m.duration,
};
}),
);
@@ -121,7 +122,12 @@ class KaiStore {
msgCallback: (msg) => {
if ('state' in msg) {
if (msg.state === 'running') {
- this.setProcessingStage({ content: 'Processing your request...', stage: 'chart', messageId: Date.now().toPrecision() })
+ this.setProcessingStage({
+ content: 'Processing your request...',
+ stage: 'chart',
+ messageId: Date.now().toPrecision(),
+ duration: msg.start_time ? Date.now() - msg.start_time : 0
+ })
} else {
this.setProcessingStage(null)
}
@@ -140,6 +146,7 @@ class KaiStore {
text: msg.content,
isUser: false,
messageId: msg.messageId,
+ duration: msg.duration
}
this.addMessage(msgObj);
this.setProcessingStage(null);
diff --git a/frontend/app/components/Kai/SocketManager.ts b/frontend/app/components/Kai/SocketManager.ts
index 7014f4f29..fd4c409e1 100644
--- a/frontend/app/components/Kai/SocketManager.ts
+++ b/frontend/app/components/Kai/SocketManager.ts
@@ -52,7 +52,7 @@ export class ChatManager {
msgCallback,
titleCallback,
}: {
- msgCallback: (msg: BotChunk | { state: string, type: 'state' }) => void;
+ msgCallback: (msg: BotChunk | { state: string, type: 'state', start_time?: number }) => void;
titleCallback: (title: string) => void;
}) => {
this.socket.on('chunk', (msg: BotChunk) => {
@@ -63,8 +63,8 @@ export class ChatManager {
console.log('Received title:', msg);
titleCallback(msg.content);
});
- this.socket.on('state', (state: { message: 'idle' | 'running' }) => {
- msgCallback({ state: state.message, type: 'state' })
+ this.socket.on('state', (state: { message: 'idle' | 'running', start_time: number }) => {
+ msgCallback({ state: state.message, type: 'state', start_time: state.start_time })
})
};
@@ -77,11 +77,13 @@ export interface BotChunk {
stage: 'start' | 'chart' | 'final' | 'title';
content: string;
messageId: string;
+ duration?: number;
}
export interface Message {
text: string;
isUser: boolean;
messageId: string;
+ duration?: number;
}
export interface SentMessage extends Message {
diff --git a/frontend/app/components/Kai/components/ChatLog.tsx b/frontend/app/components/Kai/components/ChatLog.tsx
index 283e37f43..8876af12b 100644
--- a/frontend/app/components/Kai/components/ChatLog.tsx
+++ b/frontend/app/components/Kai/components/ChatLog.tsx
@@ -72,10 +72,11 @@ function ChatLog({
userName={userLetter}
messageId={msg.messageId}
isLast={index === lastHumanMsgInd}
+ duration={msg.duration}
/>
))}
{processingStage ? (
-
+
) : null}
diff --git a/frontend/app/components/Kai/components/ChatMsg.tsx b/frontend/app/components/Kai/components/ChatMsg.tsx
index db9e249cc..cc8668369 100644
--- a/frontend/app/components/Kai/components/ChatMsg.tsx
+++ b/frontend/app/components/Kai/components/ChatMsg.tsx
@@ -3,10 +3,11 @@ import { Icon, CopyButton } from 'UI';
import cn from 'classnames';
import Markdown from 'react-markdown';
import remarkGfm from 'remark-gfm'
-import { Loader, ThumbsUp, ThumbsDown, ListRestart, FileDown } from 'lucide-react';
+import { Loader, ThumbsUp, ThumbsDown, ListRestart, FileDown, Clock } from 'lucide-react';
import { Button, Tooltip } from 'antd';
import { kaiStore } from '../KaiStore';
import { toast } from 'react-toastify';
+import { durationFormatted } from 'App/date';
export function ChatMsg({
text,
@@ -14,12 +15,14 @@ export function ChatMsg({
userName,
messageId,
isLast,
+ duration,
}: {
text: string;
isUser: boolean;
messageId: string;
userName?: string;
isLast?: boolean;
+ duration?: number;
}) {
const [isProcessing, setIsProcessing] = React.useState(false);
const bodyRef = React.useRef(null);
@@ -78,7 +81,7 @@ export function ChatMsg({
)}
-
+
{text}
@@ -96,6 +99,10 @@ export function ChatMsg({
) : null
) : (
+ {duration ? (
+
+ ) : null}
+
onFeedback('like', messageId)}>
@@ -131,13 +138,36 @@ function IconButton({
);
}
-export function ChatNotice({ content }: { content: string }) {
+export function ChatNotice({ content, duration }: { content: string, duration?: number }) {
+ const startTime = React.useRef(duration ? Date.now() - duration : Date.now());
+ const [activeDuration, setDuration] = React.useState(duration ?? 0);
+
+ React.useEffect(() => {
+ const interval = setInterval(() => {
+ setDuration(Math.round((Date.now() - startTime.current)));
+ }, 250);
+ return () => clearInterval(interval);
+ }, []);
return (
-
-
-
+
);
}
+
+function MsgDuration({ duration }: { duration: number }) {
+ return (
+
+
+
+ {durationFormatted(duration)}
+
+
+ )
+}
diff --git a/frontend/app/styles/colors-autogen.css b/frontend/app/styles/colors-autogen.css
index b323c9adc..148df49f3 100644
--- a/frontend/app/styles/colors-autogen.css
+++ b/frontend/app/styles/colors-autogen.css
@@ -52,6 +52,7 @@
.fill-glassLavander { fill: var(--color-glassLavander) }
.fill-blueLight { fill: var(--color-blueLight) }
.fill-offWhite { fill: var(--color-offWhite) }
+.fill-disabled-text { fill: var(--color-disabled-text) }
.fill-figmaColors-accent-secondary { fill: var(--color-figmaColors-accent-secondary) }
.fill-figmaColors-main { fill: var(--color-figmaColors-main) }
.fill-figmaColors-primary-outlined-hover-background { fill: var(--color-figmaColors-primary-outlined-hover-background) }
@@ -112,6 +113,7 @@
.hover-fill-glassLavander:hover svg { fill: var(--color-glassLavander) }
.hover-fill-blueLight:hover svg { fill: var(--color-blueLight) }
.hover-fill-offWhite:hover svg { fill: var(--color-offWhite) }
+.hover-fill-disabled-text:hover svg { fill: var(--color-disabled-text) }
.hover-fill-figmaColors-accent-secondary:hover svg { fill: var(--color-figmaColors-accent-secondary) }
.hover-fill-figmaColors-main:hover svg { fill: var(--color-figmaColors-main) }
.hover-fill-figmaColors-primary-outlined-hover-background:hover svg { fill: var(--color-figmaColors-primary-outlined-hover-background) }
@@ -174,6 +176,7 @@
.color-glassLavander { color: var(--color-glassLavander) }
.color-blueLight { color: var(--color-blueLight) }
.color-offWhite { color: var(--color-offWhite) }
+.color-disabled-text { color: var(--color-disabled-text) }
.color-figmaColors-accent-secondary { color: var(--color-figmaColors-accent-secondary) }
.color-figmaColors-main { color: var(--color-figmaColors-main) }
.color-figmaColors-primary-outlined-hover-background { color: var(--color-figmaColors-primary-outlined-hover-background) }
@@ -236,6 +239,7 @@
.hover-glassLavander:hover { color: var(--color-glassLavander) }
.hover-blueLight:hover { color: var(--color-blueLight) }
.hover-offWhite:hover { color: var(--color-offWhite) }
+.hover-disabled-text:hover { color: var(--color-disabled-text) }
.hover-figmaColors-accent-secondary:hover { color: var(--color-figmaColors-accent-secondary) }
.hover-figmaColors-main:hover { color: var(--color-figmaColors-main) }
.hover-figmaColors-primary-outlined-hover-background:hover { color: var(--color-figmaColors-primary-outlined-hover-background) }
@@ -298,6 +302,7 @@
.border-glassLavander { border-color: var(--color-glassLavander) }
.border-blueLight { border-color: var(--color-blueLight) }
.border-offWhite { border-color: var(--color-offWhite) }
+.border-disabled-text { border-color: var(--color-disabled-text) }
.border-figmaColors-accent-secondary { border-color: var(--color-figmaColors-accent-secondary) }
.border-figmaColors-main { border-color: var(--color-figmaColors-main) }
.border-figmaColors-primary-outlined-hover-background { border-color: var(--color-figmaColors-primary-outlined-hover-background) }
@@ -360,6 +365,7 @@
.bg-glassLavander { background-color: var(--color-glassLavander) }
.bg-blueLight { background-color: var(--color-blueLight) }
.bg-offWhite { background-color: var(--color-offWhite) }
+.bg-disabled-text { background-color: var(--color-disabled-text) }
.bg-figmaColors-accent-secondary { background-color: var(--color-figmaColors-accent-secondary) }
.bg-figmaColors-main { background-color: var(--color-figmaColors-main) }
.bg-figmaColors-primary-outlined-hover-background { background-color: var(--color-figmaColors-primary-outlined-hover-background) }
diff --git a/frontend/app/theme/colors.js b/frontend/app/theme/colors.js
index c8dd23eb6..17d472398 100644
--- a/frontend/app/theme/colors.js
+++ b/frontend/app/theme/colors.js
@@ -51,6 +51,7 @@ module.exports = {
glassLavander: 'rgba(243, 241, 255, 0.5)',
blueLight: 'rgba(235, 235, 255, 1)',
offWhite: 'rgba(250, 250, 255, 1)',
+ 'disabled-text': 'rgba(0,0,0, 0.38)',
figmaColors: {
'accent-secondary': 'rgba(62, 170, 175, 1)',