ui: some design fixes for kai

This commit is contained in:
nick-delirium 2025-05-28 13:59:05 +02:00
parent a8d0de4e98
commit 3460a65b79
No known key found for this signature in database
GPG key ID: 93ABD695DF5FDBA0
13 changed files with 113 additions and 56 deletions

View file

@ -38,6 +38,7 @@ function KaiChat() {
showModal( showModal(
<ChatsModal <ChatsModal
projectId={activeSiteId!} projectId={activeSiteId!}
onHide={hideModal}
onSelect={(threadId: string, title: string) => { onSelect={(threadId: string, title: string) => {
setTitle(title); setTitle(title);
setThreadId(threadId); setThreadId(threadId);
@ -118,7 +119,9 @@ function KaiChat() {
style={{ maxWidth: PANEL_SIZES.maxWidth }} style={{ maxWidth: PANEL_SIZES.maxWidth }}
> >
<div <div
className={'w-full rounded-lg overflow-hidden bg-white relative h-full'} className={
'w-full rounded-lg overflow-hidden bg-white relative h-full reset'
}
> >
<ChatHeader <ChatHeader
chatTitle={chatTitle} chatTitle={chatTitle}
@ -147,7 +150,11 @@ function KaiChat() {
userName={userName} userName={userName}
/> />
</div> </div>
<div className={'text-disabled-text absolute bottom-4 left-0 right-0 text-center text-sm'}> <div
className={
'text-disabled-text absolute bottom-4 left-0 right-0 text-center text-sm'
}
>
OpenReplay AI can make mistakes. Verify its outputs. OpenReplay AI can make mistakes. Verify its outputs.
</div> </div>
</> </>

View file

@ -49,7 +49,7 @@ function ChatHeader({
<div className={'flex-1 justify-end flex items-center gap-4'}> <div className={'flex-1 justify-end flex items-center gap-4'}>
{goBack ? ( {goBack ? (
<div <div
onClick={onCreate} onClick={() => onCreate()}
className="font-semibold w-fit cursor-pointer hover:text-main flex items-center gap-2" className="font-semibold w-fit cursor-pointer hover:text-main flex items-center gap-2"
> >
<SquarePen size={14} /> <SquarePen size={14} />

View file

@ -101,7 +101,7 @@ function ChatInput({
<Tooltip title={'Cancel Editing'}> <Tooltip title={'Cancel Editing'}>
<Button <Button
onClick={cancelReplace} onClick={cancelReplace}
icon={<X size={16} />} icon={<X className="reset" size={16} />}
size={'small'} size={'small'}
shape={'circle'} shape={'circle'}
disabled={limited} disabled={limited}
@ -143,10 +143,10 @@ function SendButton({
disabled={limited} disabled={limited}
icon={ icon={
isProcessing ? ( isProcessing ? (
<X size={16} strokeWidth={2} /> <X size={16} strokeWidth={2} className="reset" />
) : ( ) : (
<div className="bg-[#fff] text-main rounded-full"> <div className="bg-[#fff] text-main rounded-full">
<ArrowUp size={14} strokeWidth={2} /> <ArrowUp size={14} strokeWidth={2} className="reset" />
</div> </div>
) )
} }

View file

@ -61,10 +61,10 @@ function ChatLog({
ref={chatRef} ref={chatRef}
style={{ maxHeight: 'calc(100svh - 165px)' }} style={{ maxHeight: 'calc(100svh - 165px)' }}
className={ className={
'overflow-y-auto relative flex flex-col items-center justify-between w-full h-full' 'overflow-y-auto relative flex flex-col items-center justify-between w-full h-full pt-4'
} }
> >
<div className={'flex flex-col gap-4 w-2/3 min-h-max'}> <div className={'flex flex-col gap-2 w-2/3 min-h-max'}>
{messages.map((msg, index) => ( {messages.map((msg, index) => (
<React.Fragment key={msg.messageId ?? index}> <React.Fragment key={msg.messageId ?? index}>
<ChatMsg <ChatMsg
@ -90,6 +90,7 @@ function ChatLog({
onClick={(query) => onSubmit(query)} onClick={(query) => onSubmit(query)}
projectId={projectId} projectId={projectId}
threadId={threadId} threadId={threadId}
inChat
/> />
) : null} ) : null}
</div> </div>

View file

@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import { CopyButton } from 'UI'; import { CopyButton, Icon } from 'UI';
import { observer } from 'mobx-react-lite'; import { observer } from 'mobx-react-lite';
import cn from 'classnames'; import cn from 'classnames';
import Markdown from 'react-markdown'; import Markdown from 'react-markdown';
@ -9,7 +9,6 @@ import {
ThumbsUp, ThumbsUp,
ThumbsDown, ThumbsDown,
SquarePen, SquarePen,
FileDown,
Clock, Clock,
ChartLine, ChartLine,
} from 'lucide-react'; } from 'lucide-react';
@ -203,31 +202,28 @@ function ChatMsg({
</div> </div>
) : null} ) : null}
{isUser ? ( {isUser ? (
<div className="invisible group-hover/actions:visible mt-2"> <div className="invisible group-hover/actions:visible mt-1 ml-auto flex gap-2 items-center">
<Tooltip title={t('Edit')}> {canEdit && !isEditing ? (
<div <IconButton onClick={onEdit} tooltip={t('Edit')}>
onClick={onEdit}
className={cn(
'ml-auto flex items-center gap-2 px-2',
'rounded-lg cursor-pointer',
'hover:text-main w-fit',
canEdit && !isEditing ? '' : 'hidden',
)}
>
<SquarePen size={16} /> <SquarePen size={16} />
</div> </IconButton>
</Tooltip> ) : null}
<div {isEditing ? (
onClick={onCancelEdit} <Button
className={cn( onClick={onCancelEdit}
'ml-auto flex items-center gap-2 px-2', type="text"
'rounded-lg border border-gray-medium text-xs cursor-pointer', size="small"
'hover:border-main hover:text-main w-fit', className={'text-xs'}
isEditing ? '' : 'hidden', >
)} {t('Cancel')}
> </Button>
<div>{t('Cancel')}</div> ) : null}
</div> <CopyButton
getHtml={() => bodyRef.current?.innerHTML}
content={text}
isIcon
format={'text/html'}
/>
</div> </div>
) : ( ) : (
<div className={'flex items-center gap-2'}> <div className={'flex items-center gap-2'}>
@ -238,14 +234,14 @@ function ChatMsg({
tooltip="Like this answer" tooltip="Like this answer"
onClick={() => onFeedback('like', messageId)} onClick={() => onFeedback('like', messageId)}
> >
<ThumbsUp size={16} /> <ThumbsUp strokeWidth={2} size={16} />
</IconButton> </IconButton>
<IconButton <IconButton
active={feedback === false} active={feedback === false}
tooltip="Dislike this answer" tooltip="Dislike this answer"
onClick={() => onFeedback('dislike', messageId)} onClick={() => onFeedback('dislike', messageId)}
> >
<ThumbsDown size={16} /> <ThumbsDown strokeWidth={2} size={16} />
</IconButton> </IconButton>
{supports_visualization ? ( {supports_visualization ? (
<IconButton <IconButton
@ -253,7 +249,7 @@ function ChatMsg({
onClick={getChart} onClick={getChart}
processing={loadingChart} processing={loadingChart}
> >
<ChartLine size={16} /> <ChartLine strokeWidth={2} size={16} />
</IconButton> </IconButton>
) : null} ) : null}
<CopyButton <CopyButton
@ -267,7 +263,7 @@ function ChatMsg({
tooltip="Export as PDF" tooltip="Export as PDF"
onClick={onExport} onClick={onExport}
> >
<FileDown size={16} /> <Icon name="export-pdf" size={16} />
</IconButton> </IconButton>
</div> </div>
)} )}

View file

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import { splitByDate } from '../utils'; import { splitByDate } from '../utils';
import { useQuery } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
import { MessagesSquare, Trash } from 'lucide-react'; import { MessagesSquare, Trash, X } from 'lucide-react';
import { kaiService } from 'App/services'; import { kaiService } from 'App/services';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@ -11,9 +11,11 @@ import { observer } from 'mobx-react-lite';
function ChatsModal({ function ChatsModal({
onSelect, onSelect,
projectId, projectId,
onHide,
}: { }: {
onSelect: (threadId: string, title: string) => void; onSelect: (threadId: string, title: string) => void;
projectId: string; projectId: string;
onHide: () => void;
}) { }) {
const { t } = useTranslation(); const { t } = useTranslation();
const { usage } = kaiStore; const { usage } = kaiStore;
@ -45,12 +47,21 @@ function ChatsModal({
}; };
return ( return (
<div <div
className={'flex flex-col gap-2 p-4 mr-1 rounded-lg bg-white my-auto'} className={'flex flex-col gap-2 p-4 mr-1 rounded-lg bg-white'}
style={{ height: '95svh', width: 310 }} style={{ height: 'calc(-100px + 100svh)', marginTop: 60, width: 310 }}
> >
<div className={'flex items-center font-semibold text-lg gap-2'}> <div className={'flex items-center font-semibold text-lg gap-2'}>
<MessagesSquare size={16} /> <MessagesSquare size={16} />
<span>{t('Previous Chats')}</span> <span>{t('Previous Chats')}</span>
<div className="ml-auto" />
<div>
<X
size={16}
strokeWidth={2}
className="cursor-pointer hover:text-main"
onClick={onHide}
/>
</div>
</div> </div>
{usage.percent > 80 ? ( {usage.percent > 80 ? (
<div className="text-red text-sm"> <div className="text-red text-sm">
@ -65,7 +76,7 @@ function ChatsModal({
{t('Loading chats')}... {t('Loading chats')}...
</div> </div>
) : ( ) : (
<div className="overflow-y-auto flex flex-col gap-2"> <div className="overflow-y-auto flex flex-col gap-4">
{datedCollections.map((col, i) => ( {datedCollections.map((col, i) => (
<React.Fragment key={`${i}_${col.date}`}> <React.Fragment key={`${i}_${col.date}`}>
<ChatCollection <ChatCollection
@ -94,8 +105,8 @@ function ChatCollection({
date: string; date: string;
}) { }) {
return ( return (
<div> <div className="border-b border-b-gray-lighter">
<div className="text-disabled-text">{date}</div> <div className="font-semibold">{date}</div>
<ChatsList data={data} onSelect={onSelect} onDelete={onDelete} /> <ChatsList data={data} onSelect={onSelect} onDelete={onDelete} />
</div> </div>
); );

View file

@ -8,10 +8,12 @@ function Ideas({
onClick, onClick,
projectId, projectId,
threadId = null, threadId = null,
inChat,
}: { }: {
onClick: (query: string) => void; onClick: (query: string) => void;
projectId: string; projectId: string;
threadId?: string | null; threadId?: string | null;
inChat?: boolean;
}) { }) {
const { t } = useTranslation(); const { t } = useTranslation();
const { data: suggestedPromptIdeas = [], isPending } = useQuery({ const { data: suggestedPromptIdeas = [], isPending } = useQuery({
@ -35,7 +37,7 @@ function Ideas({
return ( return (
<div> <div>
<div className={'flex items-center gap-2 mb-1 text-gray-dark'}> <div className={'flex items-center gap-2 mb-1 text-gray-dark'}>
<b>Suggested Ideas:</b> <b>{inChat ? 'Suggested Follow-up Questions' : 'Suggested Ideas:'}</b>
</div> </div>
{isPending ? ( {isPending ? (
<div className="animate-pulse text-disabled-text"> <div className="animate-pulse text-disabled-text">

View file

@ -1,7 +1,7 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import copy from 'copy-to-clipboard'; import copy from 'copy-to-clipboard';
import { Button, Tooltip } from 'antd'; import { Button, Tooltip } from 'antd';
import { ClipboardCopy, ClipboardCheck } from 'lucide-react'; import { Copy, Check } from 'lucide-react';
interface Props { interface Props {
content: string; content: string;
@ -30,10 +30,10 @@ function CopyButton({
setTimeout(() => { setTimeout(() => {
setCopied(false); setCopied(false);
}, 1000); }, 1000);
} };
const copyHandler = () => { const copyHandler = () => {
setCopied(true); setCopied(true);
const contentIsGetter = !!getHtml const contentIsGetter = !!getHtml;
const textContent = contentIsGetter ? getHtml() : content; const textContent = contentIsGetter ? getHtml() : content;
const isHttps = window.location.protocol === 'https:'; const isHttps = window.location.protocol === 'https:';
if (!isHttps) { if (!isHttps) {
@ -43,15 +43,16 @@ function CopyButton({
} }
const blob = new Blob([textContent], { type: format }); const blob = new Blob([textContent], { type: format });
const cbItem = new ClipboardItem({ const cbItem = new ClipboardItem({
[format]: blob [format]: blob,
}) });
navigator.clipboard.write([cbItem]) navigator.clipboard
.catch(e => { .write([cbItem])
.catch((e) => {
copy(textContent); copy(textContent);
}) })
.finally(() => { .finally(() => {
reset() reset();
}) });
}; };
if (isIcon) { if (isIcon) {
@ -62,7 +63,11 @@ function CopyButton({
onClick={copyHandler} onClick={copyHandler}
size={size} size={size}
icon={ icon={
copied ? <ClipboardCheck size={16} /> : <ClipboardCopy size={16} /> copied ? (
<Check strokeWidth={2} size={16} />
) : (
<Copy strokeWidth={2} size={16} />
)
} }
/> />
</Tooltip> </Tooltip>

View file

@ -0,0 +1,18 @@
/* Auto-generated, do not edit */
import React from 'react';
interface Props {
size?: number | string;
width?: number | string;
height?: number | string;
fill?: string;
}
function Export_pdf(props: Props) {
const { size = 14, width = size, height = size, fill = '' } = props;
return (
<svg viewBox="0 0 21 21" fill="none" width={ `${ width }px` } height={ `${ height }px` } ><path d="M11.496 3H5.454c-.805 0-1.458.653-1.458 1.458V10.5h13.333V8.833h-4.375a1.458 1.458 0 0 1-1.458-1.458V3Z" fill="#1C1B1F"/><path d="M17.33 7.583v-.071c0-.387-.154-.758-.428-1.031L13.85 3.427A1.458 1.458 0 0 0 12.817 3h-.071v4.375c0 .115.093.208.208.208h4.375Z" fill="#1C1B1F"/><path clipRule="evenodd" d="M3.163 12.792c0-.345.28-.625.625-.625h1.666a1.875 1.875 0 1 1 0 3.75H4.413v1.458a.625.625 0 1 1-1.25 0v-4.583Zm1.25 1.875h1.041a.625.625 0 0 0 0-1.25H4.413v1.25ZM8.163 12.792c0-.345.28-.625.625-.625h1.25c.54 0 1.258.134 1.858.582.63.471 1.058 1.237 1.058 2.334 0 1.098-.427 1.863-1.058 2.334-.6.449-1.319.583-1.858.583h-1.25a.625.625 0 0 1-.625-.625v-4.583Zm1.25.625v3.333h.625c.363 0 .79-.095 1.11-.334.29-.216.556-.597.556-1.333s-.267-1.116-.556-1.332c-.32-.24-.747-.334-1.11-.334h-.625ZM13.996 12.792c0-.345.28-.625.625-.625h2.917a.625.625 0 1 1 0 1.25h-2.292v1.25h1.458a.625.625 0 0 1 0 1.25h-1.458v1.458a.625.625 0 1 1-1.25 0v-4.583Z" fill="#1C1B1F"/></svg>
);
}
export default Export_pdf;

View file

@ -211,6 +211,7 @@ export { default as Exclamation_circle_fill } from './exclamation_circle_fill';
export { default as Exclamation_circle } from './exclamation_circle'; export { default as Exclamation_circle } from './exclamation_circle';
export { default as Exclamation_triangle } from './exclamation_triangle'; export { default as Exclamation_triangle } from './exclamation_triangle';
export { default as Explosion } from './explosion'; export { default as Explosion } from './explosion';
export { default as Export_pdf } from './export_pdf';
export { default as External_link_alt } from './external_link_alt'; export { default as External_link_alt } from './external_link_alt';
export { default as Eye_slash_fill } from './eye_slash_fill'; export { default as Eye_slash_fill } from './eye_slash_fill';
export { default as Eye_slash } from './eye_slash'; export { default as Eye_slash } from './eye_slash';

File diff suppressed because one or more lines are too long

View file

@ -25,6 +25,11 @@ img {
stroke-width: 1.5px; stroke-width: 1.5px;
} }
.reset .lucide {
stroke-width: revert-layer;
}
.ant-pagination-simple-pager input { .ant-pagination-simple-pager input {
min-width: 80px; min-width: 80px;
} }

View file

@ -0,0 +1,7 @@
<svg viewBox="0 0 21 21" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11.4959 3H5.45426C4.64885 3 3.99593 3.65292 3.99593 4.45833V10.5H17.3293V8.83333H12.9543C12.1488 8.83333 11.4959 8.18042 11.4959 7.375V3Z" fill="#1C1B1F"/>
<path d="M17.3293 7.58333V7.51184C17.3293 7.12507 17.1756 6.75414 16.9021 6.48065L13.8486 3.42714C13.5751 3.15365 13.2042 3 12.8174 3H12.7459V7.375C12.7459 7.49006 12.8392 7.58333 12.9543 7.58333H17.3293Z" fill="#1C1B1F"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.1626 12.7917C3.1626 12.4465 3.44242 12.1667 3.7876 12.1667H5.45426C6.4898 12.1667 7.32926 13.0061 7.32926 14.0417C7.32926 15.0772 6.4898 15.9167 5.45426 15.9167H4.4126V17.375C4.4126 17.7202 4.13278 18 3.7876 18C3.44242 18 3.1626 17.7202 3.1626 17.375V12.7917ZM4.4126 14.6667H5.45426C5.79944 14.6667 6.07926 14.3868 6.07926 14.0417C6.07926 13.6965 5.79944 13.4167 5.45426 13.4167H4.4126V14.6667Z" fill="#1C1B1F"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.1626 12.7917C8.1626 12.4465 8.44242 12.1667 8.7876 12.1667H10.0376C10.5774 12.1667 11.2959 12.3009 11.8961 12.7493C12.5267 13.2205 12.9543 13.9858 12.9543 15.0833C12.9543 16.1808 12.5267 16.9462 11.8961 17.4173C11.2959 17.8658 10.5774 18 10.0376 18H8.7876C8.44242 18 8.1626 17.7202 8.1626 17.375V12.7917ZM9.4126 13.4167V16.75H10.0376C10.4006 16.75 10.8279 16.6551 11.1479 16.416C11.4374 16.1997 11.7043 15.8192 11.7043 15.0833C11.7043 14.3475 11.4374 13.967 11.1479 13.7507C10.8279 13.5116 10.4006 13.4167 10.0376 13.4167H9.4126Z" fill="#1C1B1F"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.9959 12.7917C13.9959 12.4465 14.2758 12.1667 14.6209 12.1667H17.5376C17.8828 12.1667 18.1626 12.4465 18.1626 12.7917C18.1626 13.1368 17.8828 13.4167 17.5376 13.4167H15.2459V14.6667H16.7043C17.0494 14.6667 17.3293 14.9465 17.3293 15.2917C17.3293 15.6368 17.0494 15.9167 16.7043 15.9167H15.2459V17.375C15.2459 17.7202 14.9661 18 14.6209 18C14.2758 18 13.9959 17.7202 13.9959 17.375V12.7917Z" fill="#1C1B1F"/>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB