ui: adjust styling and rm logs
This commit is contained in:
parent
dca5e54811
commit
9fec22319b
7 changed files with 129 additions and 75 deletions
|
|
@ -103,7 +103,8 @@ const HIGHLIGHTS_PATH = routes.highlights();
|
||||||
const KAI_PATH = routes.kai();
|
const KAI_PATH = routes.kai();
|
||||||
|
|
||||||
function PrivateRoutes() {
|
function PrivateRoutes() {
|
||||||
const { projectsStore, userStore, integrationsStore, searchStore } = useStore();
|
const { projectsStore, userStore, integrationsStore, searchStore } =
|
||||||
|
useStore();
|
||||||
const onboarding = userStore.onboarding;
|
const onboarding = userStore.onboarding;
|
||||||
const scope = userStore.scopeState;
|
const scope = userStore.scopeState;
|
||||||
const { tenantId } = userStore.account;
|
const { tenantId } = userStore.account;
|
||||||
|
|
@ -127,8 +128,12 @@ function PrivateRoutes() {
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (!searchStore.urlParsed) return;
|
if (!searchStore.urlParsed) return;
|
||||||
debounceCall(() => searchStore.fetchSessions(true), 250)()
|
debounceCall(() => searchStore.fetchSessions(true), 250)();
|
||||||
}, [searchStore.urlParsed, searchStore.instance.filters, searchStore.instance.eventsOrder]);
|
}, [
|
||||||
|
searchStore.urlParsed,
|
||||||
|
searchStore.instance.filters,
|
||||||
|
searchStore.instance.eventsOrder,
|
||||||
|
]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Suspense fallback={<Loader loading className="flex-1" />}>
|
<Suspense fallback={<Loader loading className="flex-1" />}>
|
||||||
|
|
@ -166,13 +171,13 @@ function PrivateRoutes() {
|
||||||
case '/integrations/slack':
|
case '/integrations/slack':
|
||||||
client.post('integrations/slack/add', {
|
client.post('integrations/slack/add', {
|
||||||
code: location.search.split('=')[1],
|
code: location.search.split('=')[1],
|
||||||
state: tenantId
|
state: tenantId,
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case '/integrations/msteams':
|
case '/integrations/msteams':
|
||||||
client.post('integrations/msteams/add', {
|
client.post('integrations/msteams/add', {
|
||||||
code: location.search.split('=')[1],
|
code: location.search.split('=')[1],
|
||||||
state: tenantId
|
state: tenantId,
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -197,7 +202,7 @@ function PrivateRoutes() {
|
||||||
withSiteId(DASHBOARD_PATH, siteIdList),
|
withSiteId(DASHBOARD_PATH, siteIdList),
|
||||||
withSiteId(DASHBOARD_SELECT_PATH, siteIdList),
|
withSiteId(DASHBOARD_SELECT_PATH, siteIdList),
|
||||||
withSiteId(DASHBOARD_METRIC_CREATE_PATH, siteIdList),
|
withSiteId(DASHBOARD_METRIC_CREATE_PATH, siteIdList),
|
||||||
withSiteId(DASHBOARD_METRIC_DETAILS_PATH, siteIdList)
|
withSiteId(DASHBOARD_METRIC_DETAILS_PATH, siteIdList),
|
||||||
]}
|
]}
|
||||||
component={enhancedComponents.Dashboard}
|
component={enhancedComponents.Dashboard}
|
||||||
/>
|
/>
|
||||||
|
|
@ -258,7 +263,7 @@ function PrivateRoutes() {
|
||||||
withSiteId(FFLAG_READ_PATH, siteIdList),
|
withSiteId(FFLAG_READ_PATH, siteIdList),
|
||||||
withSiteId(FFLAG_CREATE_PATH, siteIdList),
|
withSiteId(FFLAG_CREATE_PATH, siteIdList),
|
||||||
withSiteId(NOTES_PATH, siteIdList),
|
withSiteId(NOTES_PATH, siteIdList),
|
||||||
withSiteId(BOOKMARKS_PATH, siteIdList)
|
withSiteId(BOOKMARKS_PATH, siteIdList),
|
||||||
]}
|
]}
|
||||||
component={enhancedComponents.SessionsOverview}
|
component={enhancedComponents.SessionsOverview}
|
||||||
/>
|
/>
|
||||||
|
|
@ -274,12 +279,14 @@ function PrivateRoutes() {
|
||||||
path={withSiteId(LIVE_SESSION_PATH, siteIdList)}
|
path={withSiteId(LIVE_SESSION_PATH, siteIdList)}
|
||||||
component={enhancedComponents.LiveSession}
|
component={enhancedComponents.LiveSession}
|
||||||
/>
|
/>
|
||||||
{hasAi ? <Route
|
{hasAi ? (
|
||||||
exact
|
<Route
|
||||||
strict
|
exact
|
||||||
path={withSiteId(KAI_PATH, siteIdList)}
|
strict
|
||||||
component={enhancedComponents.Kai}
|
path={withSiteId(KAI_PATH, siteIdList)}
|
||||||
/> : null}
|
component={enhancedComponents.Kai}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
{Object.entries(routes.redirects).map(([fr, to]) => (
|
{Object.entries(routes.redirects).map(([fr, to]) => (
|
||||||
<Redirect key={fr} exact strict from={fr} to={to} />
|
<Redirect key={fr} exact strict from={fr} to={to} />
|
||||||
))}
|
))}
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,13 @@ function KaiChat() {
|
||||||
const params = new URLSearchParams(location.search);
|
const params = new URLSearchParams(location.search);
|
||||||
const threadIdFromUrl = params.get('threadId');
|
const threadIdFromUrl = params.get('threadId');
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
// Reset chat state and clear URL params when project changes
|
||||||
|
setSection('intro');
|
||||||
|
setThreadId(null);
|
||||||
|
history.replace({ search: '' });
|
||||||
|
}, [activeSiteId]);
|
||||||
|
|
||||||
const openChats = () => {
|
const openChats = () => {
|
||||||
showModal(
|
showModal(
|
||||||
<ChatsModal
|
<ChatsModal
|
||||||
|
|
|
||||||
|
|
@ -1,53 +1,66 @@
|
||||||
import AiService from "@/services/AiService";
|
import AiService from '@/services/AiService';
|
||||||
|
|
||||||
export default class KaiService extends AiService {
|
export default class KaiService extends AiService {
|
||||||
getKaiChats = async (projectId: string): Promise<{ title: string, threadId: string }[]> => {
|
getKaiChats = async (
|
||||||
|
projectId: string,
|
||||||
|
): Promise<{ title: string; threadId: string }[]> => {
|
||||||
const r = await this.client.get(`/kai/${projectId}/chats`);
|
const r = await this.client.get(`/kai/${projectId}/chats`);
|
||||||
if (!r.ok) {
|
if (!r.ok) {
|
||||||
throw new Error('Failed to fetch chats');
|
throw new Error('Failed to fetch chats');
|
||||||
}
|
}
|
||||||
const data = await r.json();
|
const data = await r.json();
|
||||||
return data;
|
return data;
|
||||||
}
|
};
|
||||||
|
|
||||||
deleteKaiChat = async (projectId: string, threadId: string): Promise<boolean> => {
|
deleteKaiChat = async (
|
||||||
|
projectId: string,
|
||||||
|
threadId: string,
|
||||||
|
): Promise<boolean> => {
|
||||||
const r = await this.client.delete(`/kai/${projectId}/chats/${threadId}`);
|
const r = await this.client.delete(`/kai/${projectId}/chats/${threadId}`);
|
||||||
if (!r.ok) {
|
if (!r.ok) {
|
||||||
throw new Error('Failed to delete chat');
|
throw new Error('Failed to delete chat');
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
};
|
||||||
|
|
||||||
getKaiChat = async (projectId: string, threadId: string): Promise<{ role: string, content: string, message_id: any, duration?: number }[]> => {
|
getKaiChat = async (
|
||||||
|
projectId: string,
|
||||||
|
threadId: string,
|
||||||
|
): Promise<
|
||||||
|
{ role: string; content: string; message_id: any; duration?: number }[]
|
||||||
|
> => {
|
||||||
const r = await this.client.get(`/kai/${projectId}/chats/${threadId}`);
|
const r = await this.client.get(`/kai/${projectId}/chats/${threadId}`);
|
||||||
if (!r.ok) {
|
if (!r.ok) {
|
||||||
throw new Error('Failed to fetch chat');
|
throw new Error('Failed to fetch chat');
|
||||||
}
|
}
|
||||||
const data = await r.json();
|
const data = await r.json();
|
||||||
return data;
|
return data;
|
||||||
}
|
};
|
||||||
|
|
||||||
createKaiChat = async (projectId: string): Promise<number> => {
|
createKaiChat = async (projectId: string): Promise<number> => {
|
||||||
const r = await this.client.get(`/kai/${projectId}/chat/new`)
|
const r = await this.client.get(`/kai/${projectId}/chat/new`);
|
||||||
if (!r.ok) {
|
if (!r.ok) {
|
||||||
throw new Error('Failed to create chat');
|
throw new Error('Failed to create chat');
|
||||||
}
|
}
|
||||||
const data = await r.json();
|
const data = await r.json();
|
||||||
return data;
|
return data;
|
||||||
}
|
};
|
||||||
|
|
||||||
feedback = async (positive: boolean | null, messageId: string, projectId: string) => {
|
feedback = async (
|
||||||
|
positive: boolean | null,
|
||||||
|
messageId: string,
|
||||||
|
projectId: string,
|
||||||
|
) => {
|
||||||
const r = await this.client.post(`/kai/${projectId}/messages/feedback`, {
|
const r = await this.client.post(`/kai/${projectId}/messages/feedback`, {
|
||||||
message_id: messageId,
|
message_id: messageId,
|
||||||
value: positive,
|
value: positive,
|
||||||
user_id: userId,
|
|
||||||
});
|
});
|
||||||
if (!r.ok) {
|
if (!r.ok) {
|
||||||
throw new Error('Failed to send feedback');
|
throw new Error('Failed to send feedback');
|
||||||
}
|
}
|
||||||
|
|
||||||
return await r.json()
|
return await r.json();
|
||||||
}
|
};
|
||||||
|
|
||||||
cancelGeneration = async (projectId: string, threadId: string) => {
|
cancelGeneration = async (projectId: string, threadId: string) => {
|
||||||
const r = await this.client.post(`/kai/${projectId}/cancel/${threadId}`);
|
const r = await this.client.post(`/kai/${projectId}/cancel/${threadId}`);
|
||||||
|
|
@ -57,5 +70,5 @@ export default class KaiService extends AiService {
|
||||||
|
|
||||||
const data = await r.json();
|
const data = await r.json();
|
||||||
return data;
|
return data;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,6 @@ export class ChatManager {
|
||||||
socket.on('error', (err) => {
|
socket.on('error', (err) => {
|
||||||
console.error('Socket error:', err);
|
console.error('Socket error:', err);
|
||||||
});
|
});
|
||||||
socket.onAny((e) => console.log('event', e));
|
|
||||||
|
|
||||||
this.socket = socket;
|
this.socket = socket;
|
||||||
}
|
}
|
||||||
|
|
@ -68,11 +67,9 @@ export class ChatManager {
|
||||||
titleCallback: (title: string) => void;
|
titleCallback: (title: string) => void;
|
||||||
}) => {
|
}) => {
|
||||||
this.socket.on('chunk', (msg: BotChunk) => {
|
this.socket.on('chunk', (msg: BotChunk) => {
|
||||||
console.log('Received message:', msg);
|
|
||||||
msgCallback(msg);
|
msgCallback(msg);
|
||||||
});
|
});
|
||||||
this.socket.on('title', (msg: { content: string }) => {
|
this.socket.on('title', (msg: { content: string }) => {
|
||||||
console.log('Received title:', msg);
|
|
||||||
titleCallback(msg.content);
|
titleCallback(msg.content);
|
||||||
});
|
});
|
||||||
this.socket.on(
|
this.socket.on(
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,15 @@ import React from 'react';
|
||||||
import { Icon, CopyButton } from 'UI';
|
import { Icon, CopyButton } from 'UI';
|
||||||
import cn from 'classnames';
|
import cn from 'classnames';
|
||||||
import Markdown from 'react-markdown';
|
import Markdown from 'react-markdown';
|
||||||
import remarkGfm from 'remark-gfm'
|
import remarkGfm from 'remark-gfm';
|
||||||
import { Loader, ThumbsUp, ThumbsDown, ListRestart, FileDown, Clock } from 'lucide-react';
|
import {
|
||||||
|
Loader,
|
||||||
|
ThumbsUp,
|
||||||
|
ThumbsDown,
|
||||||
|
ListRestart,
|
||||||
|
FileDown,
|
||||||
|
Clock,
|
||||||
|
} from 'lucide-react';
|
||||||
import { Button, Tooltip } from 'antd';
|
import { Button, Tooltip } from 'antd';
|
||||||
import { kaiStore } from '../KaiStore';
|
import { kaiStore } from '../KaiStore';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
|
|
@ -27,36 +34,37 @@ export function ChatMsg({
|
||||||
const [isProcessing, setIsProcessing] = React.useState(false);
|
const [isProcessing, setIsProcessing] = React.useState(false);
|
||||||
const bodyRef = React.useRef<HTMLDivElement>(null);
|
const bodyRef = React.useRef<HTMLDivElement>(null);
|
||||||
const onRetry = () => {
|
const onRetry = () => {
|
||||||
kaiStore.editMessage(text)
|
kaiStore.editMessage(text);
|
||||||
}
|
};
|
||||||
const onFeedback = (feedback: 'like' | 'dislike', messageId: string) => {
|
const onFeedback = (feedback: 'like' | 'dislike', messageId: string) => {
|
||||||
kaiStore.sendMsgFeedback(feedback, messageId);
|
kaiStore.sendMsgFeedback(feedback, messageId);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onExport = () => {
|
const onExport = () => {
|
||||||
setIsProcessing(true);
|
setIsProcessing(true);
|
||||||
import('jspdf').then(({ jsPDF }) => {
|
import('jspdf')
|
||||||
const doc = new jsPDF();
|
.then(({ jsPDF }) => {
|
||||||
|
const doc = new jsPDF();
|
||||||
|
|
||||||
doc.html(bodyRef.current, {
|
doc.html(bodyRef.current, {
|
||||||
callback: function(doc) {
|
callback: function (doc) {
|
||||||
doc.save('document.pdf');
|
doc.save('document.pdf');
|
||||||
},
|
},
|
||||||
margin: [10, 10, 10, 10],
|
margin: [10, 10, 10, 10],
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0,
|
y: 0,
|
||||||
width: 190, // Target width
|
width: 190, // Target width
|
||||||
windowWidth: 675 // Window width for rendering
|
windowWidth: 675, // Window width for rendering
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
console.error('Error exporting message:', e);
|
||||||
|
toast.error('Failed to export message');
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
setIsProcessing(false);
|
||||||
});
|
});
|
||||||
})
|
};
|
||||||
.catch(e => {
|
|
||||||
console.error('Error exporting message:', e);
|
|
||||||
toast.error('Failed to export message');
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
setIsProcessing(false);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
|
|
@ -82,7 +90,7 @@ export function ChatMsg({
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className={'mt-1 flex flex-col'}>
|
<div className={'mt-1 flex flex-col'}>
|
||||||
<div className='markdown-body' ref={bodyRef}>
|
<div className="markdown-body" ref={bodyRef}>
|
||||||
<Markdown remarkPlugins={[remarkGfm]}>{text}</Markdown>
|
<Markdown remarkPlugins={[remarkGfm]}>{text}</Markdown>
|
||||||
</div>
|
</div>
|
||||||
{isUser ? (
|
{isUser ? (
|
||||||
|
|
@ -99,18 +107,31 @@ export function ChatMsg({
|
||||||
) : null
|
) : null
|
||||||
) : (
|
) : (
|
||||||
<div className={'flex items-center gap-2'}>
|
<div className={'flex items-center gap-2'}>
|
||||||
{duration ? (
|
{duration ? <MsgDuration duration={duration} /> : null}
|
||||||
<MsgDuration duration={duration} />
|
<div className="ml-auto" />
|
||||||
) : null}
|
<IconButton
|
||||||
<div className='ml-auto' />
|
tooltip="Like this answer"
|
||||||
<IconButton tooltip="Like this answer" onClick={() => onFeedback('like', messageId)}>
|
onClick={() => onFeedback('like', messageId)}
|
||||||
|
>
|
||||||
<ThumbsUp size={16} />
|
<ThumbsUp size={16} />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<IconButton tooltip="Dislike this answer" onClick={() => onFeedback('dislike', messageId)}>
|
<IconButton
|
||||||
|
tooltip="Dislike this answer"
|
||||||
|
onClick={() => onFeedback('dislike', messageId)}
|
||||||
|
>
|
||||||
<ThumbsDown size={16} />
|
<ThumbsDown size={16} />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<CopyButton getHtml={() => bodyRef.current?.innerHTML} content={text} isIcon format={'text/html'} />
|
<CopyButton
|
||||||
<IconButton processing={isProcessing} tooltip="Export as PDF" onClick={onExport}>
|
getHtml={() => bodyRef.current?.innerHTML}
|
||||||
|
content={text}
|
||||||
|
isIcon
|
||||||
|
format={'text/html'}
|
||||||
|
/>
|
||||||
|
<IconButton
|
||||||
|
processing={isProcessing}
|
||||||
|
tooltip="Export as PDF"
|
||||||
|
onClick={onExport}
|
||||||
|
>
|
||||||
<FileDown size={16} />
|
<FileDown size={16} />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -133,23 +154,35 @@ function IconButton({
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<Tooltip title={tooltip}>
|
<Tooltip title={tooltip}>
|
||||||
<Button onClick={onClick} type="text" icon={children} size='small' loading={processing} />
|
<Button
|
||||||
|
onClick={onClick}
|
||||||
|
type="text"
|
||||||
|
icon={children}
|
||||||
|
size="small"
|
||||||
|
loading={processing}
|
||||||
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ChatNotice({ content, duration }: { content: string, duration?: number }) {
|
export function ChatNotice({
|
||||||
|
content,
|
||||||
|
duration,
|
||||||
|
}: {
|
||||||
|
content: string;
|
||||||
|
duration?: number;
|
||||||
|
}) {
|
||||||
const startTime = React.useRef(duration ? Date.now() - duration : Date.now());
|
const startTime = React.useRef(duration ? Date.now() - duration : Date.now());
|
||||||
const [activeDuration, setDuration] = React.useState(duration ?? 0);
|
const [activeDuration, setDuration] = React.useState(duration ?? 0);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
const interval = setInterval(() => {
|
const interval = setInterval(() => {
|
||||||
setDuration(Math.round((Date.now() - startTime.current)));
|
setDuration(Math.round(Date.now() - startTime.current));
|
||||||
}, 250);
|
}, 250);
|
||||||
return () => clearInterval(interval);
|
return () => clearInterval(interval);
|
||||||
}, []);
|
}, []);
|
||||||
return (
|
return (
|
||||||
<div className='flex flex-col gap-1 items-start p-2 rounded-lg bg-gray-lightest border-gray-light w-fit '>
|
<div className="flex flex-col gap-1 items-start p-2 rounded-lg bg-gray-lightest border-gray-light w-fit ">
|
||||||
<div className="flex gap-2 items-start">
|
<div className="flex gap-2 items-start">
|
||||||
<div className={'animate-spin mt-1'}>
|
<div className={'animate-spin mt-1'}>
|
||||||
<Loader size={14} />
|
<Loader size={14} />
|
||||||
|
|
@ -165,9 +198,7 @@ function MsgDuration({ duration }: { duration: number }) {
|
||||||
return (
|
return (
|
||||||
<div className="text-disabled-text text-sm flex items-center gap-1">
|
<div className="text-disabled-text text-sm flex items-center gap-1">
|
||||||
<Clock size={14} />
|
<Clock size={14} />
|
||||||
<span className="leading-none">
|
<span className="leading-none">{durationFormatted(duration)}</span>
|
||||||
{durationFormatted(duration)}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ import {
|
||||||
import { MODULES } from 'Components/Client/Modules';
|
import { MODULES } from 'Components/Client/Modules';
|
||||||
import { Icon } from 'UI';
|
import { Icon } from 'UI';
|
||||||
import SVG from 'UI/SVG';
|
import SVG from 'UI/SVG';
|
||||||
import { hasAi } from 'App/utils/split-utils'
|
import { hasAi } from 'App/utils/split-utils';
|
||||||
|
|
||||||
import { useStore } from 'App/mstore';
|
import { useStore } from 'App/mstore';
|
||||||
import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG';
|
import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG';
|
||||||
|
|
|
||||||
|
|
@ -398,7 +398,6 @@ class SearchStore {
|
||||||
force: boolean = false,
|
force: boolean = false,
|
||||||
bookmarked: boolean = false,
|
bookmarked: boolean = false,
|
||||||
): Promise<void> => {
|
): Promise<void> => {
|
||||||
console.log(this.searchInProgress)
|
|
||||||
if (this.searchInProgress) return;
|
if (this.searchInProgress) return;
|
||||||
const filter = this.instance.toSearch();
|
const filter = this.instance.toSearch();
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue