From 0a5d4413ca71782a9295307c255a3d1877b19391 Mon Sep 17 00:00:00 2001 From: nick-delirium Date: Tue, 13 May 2025 17:17:18 +0200 Subject: [PATCH] ui: fixes for kai and network tooltips --- frontend/app/components/Kai/KaiChat.tsx | 4 +- frontend/app/components/Kai/KaiService.ts | 10 +- frontend/app/components/Kai/KaiStore.ts | 77 ++++++---- frontend/app/components/Kai/SocketManager.ts | 1 + .../app/components/Kai/components/ChatLog.tsx | 17 ++- .../app/components/Kai/components/ChatMsg.tsx | 12 +- .../DevTools/NetworkPanel/NetworkPanel.tsx | 141 ++++++++++-------- .../shared/DevTools/TimeTable/TimeTable.tsx | 4 +- .../FetchBasicDetails/FetchBasicDetails.tsx | 7 +- frontend/app/styles/main.css | 4 + 10 files changed, 176 insertions(+), 101 deletions(-) diff --git a/frontend/app/components/Kai/KaiChat.tsx b/frontend/app/components/Kai/KaiChat.tsx index 1d855aa00..904181460 100644 --- a/frontend/app/components/Kai/KaiChat.tsx +++ b/frontend/app/components/Kai/KaiChat.tsx @@ -50,7 +50,8 @@ function KaiChat() { React.useEffect(() => { if ( activeSiteId && - parseInt(activeSiteId, 10) !== parseInt(location.pathname.split('/')[1], 10) + parseInt(activeSiteId, 10) !== + parseInt(location.pathname.split('/')[1], 10) ) { return; } @@ -120,7 +121,6 @@ function KaiChat() { => { + ): Promise<{ title: string; thread_id: string }[]> => { const r = await this.client.get(`/kai/${projectId}/chats`); if (!r.ok) { throw new Error('Failed to fetch chats'); @@ -27,7 +27,13 @@ export default class KaiService extends AiService { projectId: string, threadId: string, ): Promise< - { role: string; content: string; message_id: any; duration?: number }[] + { + role: string; + content: string; + message_id: any; + duration?: number; + feedback: boolean | null; + }[] > => { const r = await this.client.get(`/kai/${projectId}/chats/${threadId}`); if (!r.ok) { diff --git a/frontend/app/components/Kai/KaiStore.ts b/frontend/app/components/Kai/KaiStore.ts index 9e9f79add..6c84d5a62 100644 --- a/frontend/app/components/Kai/KaiStore.ts +++ b/frontend/app/components/Kai/KaiStore.ts @@ -81,11 +81,10 @@ class KaiStore { deleteAtIndex = (indexes: number[]) => { if (!indexes.length) return; const messages = this.messages.filter((_, i) => !indexes.includes(i)); - console.log(messages, indexes) runInAction(() => { this.messages = messages; - }) - } + }); + }; getChat = async (projectId: string, threadId: string) => { this.setLoadingChat(true); @@ -100,6 +99,7 @@ class KaiStore { isUser: isUser, messageId: m.message_id, duration: m.duration, + feedback: m.feedback, }; }), ); @@ -117,7 +117,11 @@ class KaiStore { setTitle: (title: string) => void, initialMsg: string | null, ) => { - const token = kaiService.client.getJwt() + const token = kaiService.client.getJwt(); + if (!token) { + console.error('No token found'); + return; + } this.chatManager = new ChatManager({ ...settings, token }); this.chatManager.setOnMsgHook({ msgCallback: (msg) => { @@ -127,10 +131,10 @@ class KaiStore { content: 'Processing your request...', stage: 'chart', messageId: Date.now().toPrecision(), - duration: msg.start_time ? Date.now() - msg.start_time : 0 - }) + duration: msg.start_time ? Date.now() - msg.start_time : 0, + }); } else { - this.setProcessingStage(null) + this.setProcessingStage(null); } } else { if (msg.stage === 'start') { @@ -147,8 +151,9 @@ class KaiStore { text: msg.content, isUser: false, messageId: msg.messageId, - duration: msg.duration - } + duration: msg.duration, + feedback: null, + }; this.addMessage(msgObj); this.setProcessingStage(null); } @@ -171,32 +176,46 @@ class KaiStore { this.chatManager.sendMessage(message, this.replacing); } if (this.replacing) { - console.log(this.lastHumanMessage, this.lastKaiMessage, 'replacing these two') - const deleting = [] + console.log( + this.lastHumanMessage, + this.lastKaiMessage, + 'replacing these two', + ); + const deleting = []; if (this.lastHumanMessage.index !== null) { deleting.push(this.lastHumanMessage.index); } if (this.lastKaiMessage.index !== null) { - deleting.push(this.lastKaiMessage.index) + deleting.push(this.lastKaiMessage.index); } this.deleteAtIndex(deleting); - this.setReplacing(false) + this.setReplacing(false); } this.addMessage({ text: message, isUser: true, messageId: Date.now().toString(), + feedback: null, + duration: 0, }); }; - sendMsgFeedback = (feedback: string, messageId: string) => { - const settings = { projectId: '2325', userId: '0' }; + sendMsgFeedback = ( + feedback: string, + messageId: string, + projectId: string, + ) => { + this.messages = this.messages.map((msg) => { + if (msg.messageId === messageId) { + return { + ...msg, + feedback: feedback === 'like' ? true : false, + }; + } + return msg; + }); aiService - .feedback( - feedback === 'like', - messageId, - settings.projectId, - ) + .feedback(feedback === 'like', messageId, projectId) .then(() => { toast.success('Feedback saved.'); }) @@ -206,15 +225,21 @@ class KaiStore { }); }; - cancelGeneration = async (settings: { projectId: string; userId: string; threadId: string }) => { + cancelGeneration = async (settings: { + projectId: string; + userId: string; + threadId: string; + }) => { try { - await kaiService.cancelGeneration(settings.projectId, settings.threadId, settings.userId) - this.setProcessingStage(null) + await kaiService.cancelGeneration(settings.projectId, settings.threadId); + this.setProcessingStage(null); } catch (e) { - console.error(e) - toast.error('Failed to cancel the response generation, please try again later.') + console.error(e); + toast.error( + 'Failed to cancel the response generation, please try again later.', + ); } - } + }; clearChat = () => { this.setMessages([]); diff --git a/frontend/app/components/Kai/SocketManager.ts b/frontend/app/components/Kai/SocketManager.ts index d16c3ec69..e7711b6fe 100644 --- a/frontend/app/components/Kai/SocketManager.ts +++ b/frontend/app/components/Kai/SocketManager.ts @@ -100,6 +100,7 @@ export interface Message { isUser: boolean; messageId: string; duration?: number; + feedback: boolean | null; } export interface SentMessage extends Message { diff --git a/frontend/app/components/Kai/components/ChatLog.tsx b/frontend/app/components/Kai/components/ChatLog.tsx index fa981f027..5609642b5 100644 --- a/frontend/app/components/Kai/components/ChatLog.tsx +++ b/frontend/app/components/Kai/components/ChatLog.tsx @@ -2,12 +2,11 @@ import React from 'react'; import ChatInput from './ChatInput'; import { ChatMsg, ChatNotice } from './ChatMsg'; import { Loader } from 'UI'; -import { kaiStore } from '../KaiStore' +import { kaiStore } from '../KaiStore'; import { observer } from 'mobx-react-lite'; function ChatLog({ projectId, - userId, threadId, userLetter, onTitleChange, @@ -15,7 +14,6 @@ function ChatLog({ setInitialMsg, }: { projectId: string; - userId: string; threadId: any; userLetter: string; onTitleChange: (title: string | null) => void; @@ -30,10 +28,10 @@ function ChatLog({ React.useEffect(() => { const settings = { projectId, threadId }; if (threadId && !initialMsg) { - void kaiStore.getChat(settings.projectId, threadId) + void kaiStore.getChat(settings.projectId, threadId); } if (threadId) { - kaiStore.createChatManager(settings, onTitleChange, initialMsg) + kaiStore.createChatManager(settings, onTitleChange, initialMsg); } return () => { kaiStore.clearChat(); @@ -42,7 +40,7 @@ function ChatLog({ }, [threadId]); const onSubmit = (text: string) => { - kaiStore.sendMessage(text) + kaiStore.sendMessage(text); }; React.useEffect(() => { @@ -71,10 +69,15 @@ function ChatLog({ messageId={msg.messageId} isLast={index === lastHumanMsgInd} duration={msg.duration} + feedback={msg.feedback} + siteId={projectId} /> ))} {processingStage ? ( - + ) : null}
diff --git a/frontend/app/components/Kai/components/ChatMsg.tsx b/frontend/app/components/Kai/components/ChatMsg.tsx index bf39433cf..17ab1a110 100644 --- a/frontend/app/components/Kai/components/ChatMsg.tsx +++ b/frontend/app/components/Kai/components/ChatMsg.tsx @@ -23,6 +23,8 @@ export function ChatMsg({ messageId, isLast, duration, + feedback, + siteId, }: { text: string; isUser: boolean; @@ -30,6 +32,8 @@ export function ChatMsg({ userName?: string; isLast?: boolean; duration?: number; + feedback: boolean | null; + siteId: string; }) { const [isProcessing, setIsProcessing] = React.useState(false); const bodyRef = React.useRef(null); @@ -37,7 +41,7 @@ export function ChatMsg({ kaiStore.editMessage(text); }; const onFeedback = (feedback: 'like' | 'dislike', messageId: string) => { - kaiStore.sendMsgFeedback(feedback, messageId); + kaiStore.sendMsgFeedback(feedback, messageId, siteId); }; const onExport = () => { @@ -115,12 +119,14 @@ export function ChatMsg({ {duration ? : null}
onFeedback('like', messageId)} > onFeedback('dislike', messageId)} > @@ -151,17 +157,19 @@ function IconButton({ onClick, tooltip, processing, + active, }: { children: React.ReactNode; onClick?: () => void; tooltip?: string; processing?: boolean; + active?: boolean; }) { return (
}> -
{r.name}
+ {tooltipUrl}
} + > +
+ {r.name} +
); } @@ -94,7 +104,7 @@ export function renderName(r: any) { function renderSize(r: any) { const t = i18n.t; const notCaptured = t('Not captured'); - const resSizeStr = t('Resource size') + const resSizeStr = t('Resource size'); let triggerText; let content; if (r.responseBodySize) { @@ -185,7 +195,6 @@ function renderStatus({ ); } - // Main component for Network Panel function NetworkPanelCont({ panelHeight }: { panelHeight: number }) { const { player, store } = React.useContext(PlayerContext); @@ -433,11 +442,11 @@ export const NetworkPanelComp = observer( // Heaviest operation here, will create a final merged network list const processData = async () => { - const fetchUrlMap: Record = {} + const fetchUrlMap: Record = {}; const len = fetchList.length; for (let i = 0; i < len; i++) { const ft = fetchList[i] as any; - const key = `${ft.name}-${Math.round(ft.time / 10)}-${Math.round(ft.duration / 10)}` + const key = `${ft.name}-${Math.round(ft.time / 10)}-${Math.round(ft.duration / 10)}`; if (fetchUrlMap[key]) { fetchUrlMap[key].push(i); } @@ -445,21 +454,23 @@ export const NetworkPanelComp = observer( } // We want to get resources that aren't in fetch list - const filteredResources = await processInChunks(resourceList, (chunk) => { - const clearChunk = []; - for (const res of chunk) { - const key = `${res.name}-${Math.floor(res.time / 10)}-${Math.floor(res.duration / 10)}`; - const possibleRequests = fetchUrlMap[key] - if (possibleRequests && possibleRequests.length) { - for (const i of possibleRequests) { - fetchList[i].timings = res.timings; + const filteredResources = await processInChunks( + resourceList, + (chunk) => { + const clearChunk = []; + for (const res of chunk) { + const key = `${res.name}-${Math.floor(res.time / 10)}-${Math.floor(res.duration / 10)}`; + const possibleRequests = fetchUrlMap[key]; + if (possibleRequests && possibleRequests.length) { + for (const i of possibleRequests) { + fetchList[i].timings = res.timings; + } + fetchUrlMap[key] = []; + } else { + clearChunk.push(res); } - fetchUrlMap[key] = []; - } else { - clearChunk.push(res); } - } - return clearChunk; + return clearChunk; }, // chunk.filter((res: any) => { // const key = `${res.name}-${Math.floor(res.time / 100)}-${Math.floor(res.duration / 100)}`; @@ -484,8 +495,12 @@ export const NetworkPanelComp = observer( filteredResources as Timed[], fetchList, processedSockets as Timed[], - { enabled: Boolean(zoomEnabled), start: zoomStartTs ?? 0, end: zoomEndTs ?? 0 } - ) + { + enabled: Boolean(zoomEnabled), + start: zoomStartTs ?? 0, + end: zoomEndTs ?? 0, + }, + ); originalListRef.current = mergedList; setTotalItems(mergedList.length); @@ -509,19 +524,21 @@ export const NetworkPanelComp = observer( const calculateResourceStats = (resourceList: Record) => { setTimeout(() => { - let resourcesSize = 0 - let transferredSize = 0 - resourceList.forEach(({ decodedBodySize, headerSize, encodedBodySize }: any) => { - resourcesSize += decodedBodySize || 0 - transferredSize += (headerSize || 0) + (encodedBodySize || 0) - }) + let resourcesSize = 0; + let transferredSize = 0; + resourceList.forEach( + ({ decodedBodySize, headerSize, encodedBodySize }: any) => { + resourcesSize += decodedBodySize || 0; + transferredSize += (headerSize || 0) + (encodedBodySize || 0); + }, + ); setSummaryStats({ resourcesSize, transferredSize, }); }, 0); - } + }; useEffect(() => { if (originalListRef.current.length === 0) return; @@ -530,27 +547,33 @@ export const NetworkPanelComp = observer( let filteredItems: any[] = originalListRef.current; filteredItems = await processInChunks(filteredItems, (chunk) => - chunk.filter( - (it) => { - let valid = true; - if (showOnlyErrors) { - valid = parseInt(it.status) >= 400 || !it.success || it.error - } - if (filter) { - try { - const regex = new RegExp(filter, 'i'); - valid = valid && regex.test(it.status) || regex.test(it.name) || regex.test(it.type) || regex.test(it.method); - } catch (e) { - valid = valid && String(it.status).includes(filter) || it.name.includes(filter) || it.type.includes(filter) || (it.method && it.method.includes(filter)); - } - } - if (activeTab !== ALL) { - valid = valid && TYPE_TO_TAB[it.type] === activeTab; + chunk.filter((it) => { + let valid = true; + if (showOnlyErrors) { + valid = parseInt(it.status) >= 400 || !it.success || it.error; + } + if (filter) { + try { + const regex = new RegExp(filter, 'i'); + valid = + (valid && regex.test(it.status)) || + regex.test(it.name) || + regex.test(it.type) || + regex.test(it.method); + } catch (e) { + valid = + (valid && String(it.status).includes(filter)) || + it.name.includes(filter) || + it.type.includes(filter) || + (it.method && it.method.includes(filter)); } + } + if (activeTab !== ALL) { + valid = valid && TYPE_TO_TAB[it.type] === activeTab; + } - return valid; - }, - ), + return valid; + }), ); // Update displayed items @@ -587,7 +610,7 @@ export const NetworkPanelComp = observer( }; const onFilterChange = ({ target: { value } }) => { - setInputFilterValue(value) + setInputFilterValue(value); debouncedFilter(value); }; @@ -855,11 +878,11 @@ export const NetworkPanelComp = observer( ref={loadingRef} className="flex justify-center items-center text-xs text-gray-500" > -
-
- Loading more data ({totalItems - displayedItems.length}{' '} - remaining) -
+
+
+ Loading more data ({totalItems - displayedItems.length}{' '} + remaining) +
)} diff --git a/frontend/app/components/shared/DevTools/TimeTable/TimeTable.tsx b/frontend/app/components/shared/DevTools/TimeTable/TimeTable.tsx index 50c132f6f..263325502 100644 --- a/frontend/app/components/shared/DevTools/TimeTable/TimeTable.tsx +++ b/frontend/app/components/shared/DevTools/TimeTable/TimeTable.tsx @@ -355,7 +355,7 @@ function RowRenderer({ ); } -const RowColumns = React.memo(({ columns, row }: any) => { +const RowColumns = ({ columns, row }: any) => { const { t } = useTranslation(); return columns.map(({ dataKey, render, width, label }: any) => ( @@ -371,6 +371,6 @@ const RowColumns = React.memo(({ columns, row }: any) => { )} )); -}); +}; export default observer(TimeTable); diff --git a/frontend/app/components/shared/FetchDetailsModal/components/FetchBasicDetails/FetchBasicDetails.tsx b/frontend/app/components/shared/FetchDetailsModal/components/FetchBasicDetails/FetchBasicDetails.tsx index 09cdeafa3..2b138537d 100644 --- a/frontend/app/components/shared/FetchDetailsModal/components/FetchBasicDetails/FetchBasicDetails.tsx +++ b/frontend/app/components/shared/FetchDetailsModal/components/FetchBasicDetails/FetchBasicDetails.tsx @@ -13,6 +13,11 @@ function FetchBasicDetails({ resource, timestamp }: Props) { const _duration = parseInt(resource.duration); const { t } = useTranslation(); + const maxUrlLength = 800; + const displayUrl = + resource.url && resource.url.length > maxUrlLength + ? `${resource.url.slice(0, maxUrlLength / 2)}......${resource.url.slice(-maxUrlLength / 2)}` + : resource.url; return (
@@ -22,7 +27,7 @@ function FetchBasicDetails({ resource, timestamp }: Props) { bordered={false} style={{ maxWidth: '300px' }} > -
{resource.url}
+
{displayUrl}
diff --git a/frontend/app/styles/main.css b/frontend/app/styles/main.css index 018b6066a..b2b936e51 100644 --- a/frontend/app/styles/main.css +++ b/frontend/app/styles/main.css @@ -425,3 +425,7 @@ svg { width: 1em; margin-left: -1em; } + +.ant-tooltip { + max-width: 640px; +}