ui: change kai design, add sessions to list, swap to new icon

This commit is contained in:
nick-delirium 2025-05-27 17:12:22 +02:00
parent bce3d6232d
commit 57c0bdd075
No known key found for this signature in database
GPG key ID: 93ABD695DF5FDBA0
17 changed files with 298 additions and 165 deletions

View file

@ -18,7 +18,7 @@ function KaiChat() {
const chatTitle = kaiStore.chatTitle;
const setTitle = kaiStore.setTitle;
const userId = userStore.account.id;
const userLetter = userStore.account.name[0].toUpperCase();
const userName = userStore.account.name;
const { activeSiteId } = projectsStore;
const [section, setSection] = React.useState<'intro' | 'chat'>('intro');
const [threadId, setThreadId] = React.useState<string | null>(null);
@ -44,7 +44,11 @@ function KaiChat() {
hideModal();
}}
/>,
{ right: true, width: 300 },
{
right: true,
width: 320,
className: 'bg-none flex items-center h-screen',
},
);
};
@ -93,40 +97,70 @@ function KaiChat() {
const newThread = await kaiService.createKaiChat(activeSiteId);
if (newThread) {
setThreadId(newThread.toString());
kaiStore.setTitle(null);
setSection('chat');
} else {
toast.error("Something wen't wrong. Please try again later.");
}
};
const onCancel = () => {
if (!threadId) return;
void kaiStore.cancelGeneration({
projectId: activeSiteId,
threadId,
});
};
return (
<div className="w-full mx-auto" style={{ maxWidth: PANEL_SIZES.maxWidth }}>
<div className={'w-full rounded-lg overflow-hidden bg-white relative'}>
<div
className="w-full mx-auto h-full"
style={{ maxWidth: PANEL_SIZES.maxWidth }}
>
<div
className={'w-full rounded-lg overflow-hidden bg-white relative h-full'}
>
<ChatHeader
chatTitle={chatTitle}
openChats={openChats}
goBack={goBack}
onCreate={onCreate}
/>
<div
className={
'w-full flex flex-col items-center justify-center py-4 relative'
}
style={{
height: '70svh',
}}
>
{section === 'intro' ? (
<IntroSection onAsk={onCreate} projectId={activeSiteId} />
) : (
<ChatLog
threadId={threadId}
projectId={activeSiteId}
userLetter={userLetter}
chatTitle={chatTitle}
initialMsg={initialMsg}
setInitialMsg={setInitialMsg}
/>
)}
</div>
{section === 'intro' ? (
<>
<div
className={
'flex flex-col items-center justify-center py-4 relative'
}
style={{
position: 'absolute',
top: '50%',
left: 0,
width: '100%',
transform: 'translateY(-50%)',
}}
>
<IntroSection
onCancel={onCancel}
onAsk={onCreate}
projectId={activeSiteId}
userName={userName}
/>
</div>
<div className={'text-disabled-text absolute bottom-4 left-0 right-0 text-center text-sm'}>
OpenReplay AI can make mistakes. Verify its outputs.
</div>
</>
) : (
<ChatLog
threadId={threadId}
projectId={activeSiteId}
chatTitle={chatTitle}
initialMsg={initialMsg}
setInitialMsg={setInitialMsg}
onCancel={onCancel}
/>
)}
</div>
</div>
);

View file

@ -36,6 +36,7 @@ export default class KaiService extends AiService {
supports_visualization: boolean;
chart: string;
chart_data: string;
sessions?: Record<string, any>[];
}[];
title: string;
}> => {

View file

@ -3,6 +3,7 @@ import { BotChunk, ChatManager } from './SocketManager';
import { kaiService as aiService, kaiService } from 'App/services';
import { toast } from 'react-toastify';
import Widget from 'App/mstore/types/widget';
import Session, { ISession } from '@/types/session/session';
export interface Message {
text: string;
@ -15,6 +16,7 @@ export interface Message {
supports_visualization: boolean;
feedback: boolean | null;
duration: number;
sessions?: Session[];
}
export interface SentMessage
extends Omit<
@ -161,6 +163,9 @@ class KaiStore {
chart: m.chart,
supports_visualization: m.supports_visualization,
chart_data: m.chart_data,
sessions: m.sessions
? m.sessions.map((s) => new Session(s))
: undefined,
};
}),
);
@ -220,6 +225,9 @@ class KaiStore {
chart: '',
supports_visualization: msg.supports_visualization,
chart_data: '',
sessions: msg.sessions
? msg.sessions.map((s) => new Session(s))
: undefined,
};
this.bumpUsage();
this.addMessage(msgObj);
@ -268,7 +276,7 @@ class KaiStore {
deleting.push(this.lastKaiMessage.index);
}
this.deleteAtIndex(deleting);
this.setReplacing(false);
this.setReplacing(null);
}
this.addMessage({
text: message,
@ -309,7 +317,6 @@ class KaiStore {
cancelGeneration = async (settings: {
projectId: string;
userId: string;
threadId: string;
}) => {
try {

View file

@ -1,5 +1,6 @@
import io from 'socket.io-client';
import { toast } from 'react-toastify';
import { ISession } from '@/types/session/session';
export class ChatManager {
socket: ReturnType<typeof io>;
@ -77,9 +78,7 @@ export class ChatManager {
msgCallback,
titleCallback,
}: {
msgCallback: (
msg: StateEvent | BotChunk,
) => void;
msgCallback: (msg: StateEvent | BotChunk) => void;
titleCallback: (title: string) => void;
}) => {
this.socket.on('chunk', (msg: BotChunk) => {
@ -111,7 +110,8 @@ export interface BotChunk {
messageId: string;
duration: number;
supports_visualization: boolean;
type: 'chunk'
sessions?: ISession[];
type: 'chunk';
}
interface StateEvent {

View file

@ -7,14 +7,17 @@ function ChatHeader({
openChats = () => {},
goBack,
chatTitle,
onCreate,
}: {
goBack?: () => void;
openChats?: () => void;
chatTitle: string | null;
onCreate: () => void;
}) {
const { t } = useTranslation();
//absolute top-0 left-0 right-0 z-10
return (
<div className="p-4">
<div className="p-4 pb-0 w-full">
<div
className={'px-4 py-2 flex items-center bg-gray-lightest rounded-lg'}
>
@ -22,7 +25,7 @@ function ChatHeader({
{goBack ? (
<div
className={
'w-fit flex items-center gap-2 font-semibold cursor-pointer'
'w-fit flex items-center gap-2 font-semibold cursor-pointer hover:text-main'
}
onClick={goBack}
>
@ -31,7 +34,7 @@ function ChatHeader({
</div>
) : (
<div className="flex items-center gap-2">
<Icon name={'kai_colored'} size={18} />
<Icon name={'kai-mono'} size={18} />
<div className={'font-semibold text-xl'}>Kai</div>
</div>
)}
@ -43,15 +46,18 @@ function ChatHeader({
</div>
) : null}
</div>
<div className={'flex-1 justify-end flex items-center gap-2'}>
<div className={'flex-1 justify-end flex items-center gap-4'}>
{goBack ? (
<div className="font-semibold w-fit cursor-pointer flex items-center gap-2">
<div
onClick={onCreate}
className="font-semibold w-fit cursor-pointer hover:text-main flex items-center gap-2"
>
<SquarePen size={14} />
<div>{t('New Chat')}</div>
</div>
) : null}
<div
className="font-semibold w-fit cursor-pointer flex items-center gap-2"
className="font-semibold w-fit cursor-pointer hover:text-main flex items-center gap-2"
onClick={openChats}
>
<MessagesSquare size={14} />

View file

@ -1,6 +1,6 @@
import React from 'react';
import { Button, Input, Tooltip } from 'antd';
import { SendHorizonal, OctagonX } from 'lucide-react';
import { X, ArrowUp } from 'lucide-react';
import { kaiStore } from '../KaiStore';
import { observer } from 'mobx-react-lite';
import Usage from './Usage';
@ -8,12 +8,12 @@ import Usage from './Usage';
function ChatInput({
isLoading,
onSubmit,
threadId,
isArea,
onCancel,
}: {
isLoading?: boolean;
onSubmit: (str: string) => void;
threadId: string;
onCancel: () => void;
isArea?: boolean;
}) {
const inputRef = React.useRef<typeof Input>(null);
@ -30,8 +30,7 @@ function ChatInput({
return;
}
if (isProcessing) {
const settings = { projectId: '2325', userId: '0', threadId };
void kaiStore.cancelGeneration(settings);
onCancel();
} else {
if (inputValue.length > 0) {
onSubmit(inputValue);
@ -57,35 +56,27 @@ function ChatInput({
: 'Ask anything about your product and users...';
if (isArea) {
return (
<Input.TextArea
rows={3}
onPressEnter={submit}
ref={inputRef}
placeholder={placeholder}
size={'large'}
disabled={limited}
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
suffix={
<Tooltip title={'Send message'}>
<Button
loading={isLoading}
onClick={submit}
disabled={limited}
icon={
isProcessing ? (
<OctagonX size={16} />
) : (
<SendHorizonal size={16} />
)
}
type={'text'}
size={'small'}
shape={'circle'}
/>
</Tooltip>
}
/>
<div className="relative">
<Input.TextArea
rows={3}
className="!resize-none rounded-lg shadow"
onPressEnter={submit}
ref={inputRef}
placeholder={placeholder}
size={'large'}
disabled={limited}
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
/>
<div className="absolute bottom-2 right-2">
<SendButton
isLoading={isLoading}
submit={submit}
limited={limited}
isProcessing={isProcessing}
/>
</div>
</div>
);
}
return (
@ -100,6 +91,7 @@ function ChatInput({
ref={inputRef}
placeholder={placeholder}
size={'large'}
className="rounded-lg shadow"
disabled={limited}
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
@ -109,31 +101,19 @@ function ChatInput({
<Tooltip title={'Cancel Editing'}>
<Button
onClick={cancelReplace}
icon={<OctagonX size={16} />}
type={'text'}
icon={<X size={16} />}
size={'small'}
shape={'circle'}
disabled={limited}
/>
</Tooltip>
) : null}
<Tooltip title={'Send message'}>
<Button
loading={isLoading}
onClick={submit}
disabled={limited}
icon={
isProcessing ? (
<OctagonX size={16} />
) : (
<SendHorizonal size={16} />
)
}
type={'text'}
size={'small'}
shape={'circle'}
/>
</Tooltip>
<SendButton
isLoading={isLoading}
submit={submit}
limited={limited}
isProcessing={isProcessing}
/>
</>
}
/>
@ -144,4 +124,42 @@ function ChatInput({
);
}
function SendButton({
isLoading,
submit,
limited,
isProcessing,
}: {
isLoading?: boolean;
submit: () => void;
limited: boolean;
isProcessing?: boolean;
}) {
return (
<Tooltip title={isProcessing ? 'Cancel processing' : 'Send message'}>
<Button
loading={isLoading}
onClick={submit}
disabled={limited}
icon={
isProcessing ? (
<X size={16} strokeWidth={2} />
) : (
<div className="bg-[#fff] text-main rounded-full">
<ArrowUp size={14} strokeWidth={2} />
</div>
)
}
type={'primary'}
size={'small'}
shape={isProcessing ? 'circle' : 'round'}
iconPosition={'end'}
className="font-semibold text-[#fff]"
>
{isProcessing ? null : 'Ask'}
</Button>
</Tooltip>
);
}
export default observer(ChatInput);

View file

@ -9,17 +9,17 @@ import { observer } from 'mobx-react-lite';
function ChatLog({
projectId,
threadId,
userLetter,
initialMsg,
chatTitle,
setInitialMsg,
onCancel,
}: {
projectId: string;
threadId: any;
userLetter: string;
initialMsg: string | null;
setInitialMsg: (msg: string | null) => void;
chatTitle: string | null;
onCancel: () => void;
}) {
const messages = kaiStore.messages;
const loading = kaiStore.loadingChat;
@ -56,6 +56,7 @@ function ChatLog({
<Loader loading={loading} className={'w-full h-full'}>
<div
ref={chatRef}
style={{ maxHeight: 'calc(100svh - 165px)' }}
className={
'overflow-y-auto relative flex flex-col items-center justify-between w-full h-full'
}
@ -64,7 +65,6 @@ function ChatLog({
{messages.map((msg, index) => (
<React.Fragment key={msg.messageId ?? index}>
<ChatMsg
userName={userLetter}
siteId={projectId}
message={msg}
chatTitle={chatTitle}
@ -84,8 +84,8 @@ function ChatLog({
) : null}
{(!processingStage && lastHumanMsgInd && messages.length == lastHumanMsgInd + 2) ? <Ideas onClick={(query) => onSubmit(query)} projectId={projectId} threadId={threadId}/> : null}
</div>
<div className={'sticky bottom-0 pt-6 w-2/3'}>
<ChatInput onSubmit={onSubmit} threadId={threadId} />
<div className={'sticky bottom-0 pt-6 w-2/3 z-50'}>
<ChatInput onCancel={onCancel} onSubmit={onSubmit} />
</div>
</div>
</Loader>

View file

@ -1,5 +1,5 @@
import React from 'react';
import { Icon, CopyButton } from 'UI';
import { CopyButton } from 'UI';
import { observer } from 'mobx-react-lite';
import cn from 'classnames';
import Markdown from 'react-markdown';
@ -8,7 +8,7 @@ import {
Loader,
ThumbsUp,
ThumbsDown,
ListRestart,
SquarePen,
FileDown,
Clock,
ChartLine,
@ -20,6 +20,7 @@ import { durationFormatted } from 'App/date';
import WidgetChart from '@/components/Dashboard/components/WidgetChart';
import Widget from 'App/mstore/types/widget';
import { useTranslation } from 'react-i18next';
import SessionItem from 'Shared/SessionItem';
function ChatMsg({
userName,
@ -168,28 +169,12 @@ function ChatMsg({
}, [metricData, chart_data]);
return (
<div className={cn('flex gap-2', isUser ? 'flex-row-reverse' : 'flex-row')}>
{isUser ? (
<div
className={
'rounded-full bg-main text-white min-w-8 min-h-8 max-h-8 max-w-8 flex items-center justify-center sticky top-0 mt-2 shadow'
}
>
<span className={'font-semibold'}>{userName}</span>
</div>
) : (
<div
className={
'rounded-full bg-gray-lightest shadow min-w-8 min-h-8 max-h-8 max-w-8 flex items-center justify-center sticky top-0 mt-2'
}
>
<Icon name={'kai_colored'} size={18} />
</div>
)}
<div className={'mt-1 flex flex-col'}>
<div className={'mt-1 flex flex-col group/actions'}>
<div
className={cn(
'markdown-body',
isEditing ? 'border-l border-l-main pl-2' : '',
isUser ? 'bg-gray-lighter px-4 rounded-full' : '',
isEditing ? '!bg-active-blue' : '',
)}
data-openreplay-obscured
ref={bodyRef}
@ -204,32 +189,42 @@ function ChatMsg({
<WidgetChart metric={metric} isPreview height={360} />
</div>
) : null}
{message.sessions ? (
<div className="flex flex-col">
{message.sessions.map((session) => (
<div className="shadow border rounded-xl overflow-hidden mb-2">
<SessionItem key={session.sessionId} session={session} slim />
</div>
))}
</div>
) : null}
{isUser ? (
<>
<div
onClick={onEdit}
className={cn(
'ml-auto flex items-center gap-2 px-2',
'rounded-lg border border-gray-medium text-sm cursor-pointer',
'hover:border-main hover:text-main w-fit',
canEdit && !isEditing ? '' : 'hidden',
)}
>
<ListRestart size={16} />
<div>{t('Edit')}</div>
</div>
<div className="invisible group-hover/actions:visible mt-2">
<Tooltip title={t('Edit')}>
<div
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} />
</div>
</Tooltip>
<div
onClick={onCancelEdit}
className={cn(
'ml-auto flex items-center gap-2 px-2',
'rounded-lg border border-gray-medium text-sm cursor-pointer',
'rounded-lg border border-gray-medium text-xs cursor-pointer',
'hover:border-main hover:text-main w-fit',
isEditing ? '' : 'hidden',
)}
>
<div>{t('Cancel')}</div>
</div>
</>
</div>
) : (
<div className={'flex items-center gap-2'}>
{duration ? <MsgDuration duration={duration} /> : null}

View file

@ -44,10 +44,13 @@ function ChatsModal({
refetch();
};
return (
<div className={'h-screen w-full flex flex-col gap-2 p-4'}>
<div
className={'flex flex-col gap-2 p-4 mr-1 rounded-lg bg-white my-auto'}
style={{ height: '95svh', width: 310 }}
>
<div className={'flex items-center font-semibold text-lg gap-2'}>
<MessagesSquare size={16} />
<span>{t('Chats')}</span>
<span>{t('Previous Chats')}</span>
</div>
{usage.percent > 80 ? (
<div className="text-red text-sm">

View file

@ -4,7 +4,15 @@ import { useQuery } from '@tanstack/react-query';
import { kaiService } from 'App/services';
import { useTranslation } from 'react-i18next';
function Ideas({ onClick, projectId, threadId = null }: { onClick: (query: string) => void, projectId: string, threadId?: string | null }) {
function Ideas({
onClick,
projectId,
threadId = null,
}: {
onClick: (query: string) => void;
projectId: string,
threadId?: string | null;
}) {
const { t } = useTranslation();
const {
data: suggestedPromptIdeas = [],
@ -15,16 +23,16 @@ function Ideas({ onClick, projectId, threadId = null }: { onClick: (query: strin
staleTime: 1000 * 60,
});
const ideas = React.useMemo(() => {
const defaultPromptIdeas = [
'Top user journeys',
'Where do users drop off',
'Failed network requests today',
];
const result = suggestedPromptIdeas;
const targetSize = 3;
while (result.length < targetSize && defaultPromptIdeas.length) {
result.push(defaultPromptIdeas.pop())
}
const defaultPromptIdeas = [
'Top user journeys',
'Where do users drop off',
'Failed network requests today',
];
const result = suggestedPromptIdeas;
const targetSize = 3;
while (result.length < targetSize && defaultPromptIdeas.length) {
result.push(defaultPromptIdeas.pop());
}
return result;
}, [suggestedPromptIdeas.length]);
return (
@ -32,25 +40,36 @@ function Ideas({ onClick, projectId, threadId = null }: { onClick: (query: strin
<div className={'flex items-center gap-2 mb-1 text-gray-dark'}>
<b>Suggested Ideas:</b>
</div>
{
isPending ?
(<div className="animate-pulse text-disabled-text">{t('Generating ideas')}...</div>) :
(<div>{ideas.map(title => (<IdeaItem key={title} onClick={onClick} title={title} />))}</div>)
}
{isPending ? (
<div className="animate-pulse text-disabled-text">
{t('Generating ideas')}...
</div>
) : (
<div className="flex gap-4 flex-wrap">
{ideas.map((title) => (
<IdeaItem key={title} onClick={onClick} title={title} />
))}
</div>
)}
</>
);
}
function IdeaItem({ title, onClick }: { title: string, onClick: (query: string) => void }) {
function IdeaItem({
title,
onClick,
}: {
title: string;
onClick: (query: string) => void;
}) {
return (
<div
onClick={() => onClick(title)}
className={
'flex items-center gap-2 cursor-pointer text-gray-dark hover:text-black'
'cursor-pointer text-gray-dark hover:text-black rounded-full px-4 py-2 shadow border'
}
>
<MoveRight size={16} />
<span>{title}</span>
{title}
</div>
);
}

View file

@ -2,22 +2,34 @@ import React from 'react';
import ChatInput from './ChatInput';
import Ideas from './Ideas';
function IntroSection({ onAsk, projectId }: { onAsk: (query: string) => void, projectId: string }) {
function IntroSection({
onAsk,
onCancel,
userName,
projectId,
}: {
onAsk: (query: string) => void;
projectId: string;
onCancel: () => void;
userName: string;
}) {
const isLoading = false;
return (
<>
<div className={'relative w-2/3'}>
<div className={'relative w-2/3 flex flex-col gap-4'}>
<div className="font-semibold text-lg">
Hey userName, how can I help you?
Hey {userName}, how can I help you?
</div>
<ChatInput isLoading={isLoading} onSubmit={onAsk} isArea />
<ChatInput
onCancel={onCancel}
isLoading={isLoading}
onSubmit={onAsk}
isArea
/>
<div className={'absolute top-full flex flex-col gap-2 mt-4'}>
<Ideas onClick={(query) => onAsk(query)} projectId={projectId} />
</div>
</div>
<div className={'text-disabled-text absolute bottom-4'}>
OpenReplay AI can make mistakes. Verify its outputs.
</div>
</>
);
}

View file

@ -1,6 +1,7 @@
// @ts-nocheck
import React, { Component, createContext } from 'react';
import Modal from './Modal';
import { className } from '@medv/finder';
const ModalContext = createContext({
component: null,
@ -29,6 +30,7 @@ export class ModalProvider extends Component {
this.setState({
component,
props,
className: props.className || undefined,
});
document.addEventListener('keydown', this.handleKeyDown);
document.querySelector('body').style.overflow = 'hidden';

View file

@ -71,6 +71,7 @@ interface Props {
bookmarked?: boolean;
toggleFavorite?: (sessionId: string) => void;
query?: string;
slim?: boolean;
}
const PREFETCH_STATE = {
@ -99,6 +100,7 @@ function SessionItem(props: RouteComponentProps & Props) {
isDisabled,
live: propsLive,
isAdd,
slim,
} = props;
const {
@ -261,7 +263,7 @@ function SessionItem(props: RouteComponentProps & Props) {
}
>
<div
className={cn(stl.sessionItem, 'flex flex-col p-4')}
className={cn(stl.sessionItem, 'flex flex-col', slim ? 'px-4 py-2' : 'p-4')}
id="session-item"
onClick={(e) => e.stopPropagation()}
onMouseEnter={handleHover}
@ -343,7 +345,7 @@ function SessionItem(props: RouteComponentProps & Props) {
: 'Event'}
</span>
</div>
<Icon name="circle-fill" size={3} className="mx-4" />
<Icon name="circle-fill" size={3} className="mx-2" />
</>
)}
<div>
@ -373,7 +375,7 @@ function SessionItem(props: RouteComponentProps & Props) {
</span>
)}
{userOs && userBrowser && (
<Icon name="circle-fill" size={3} className="mx-4" />
<Icon name="circle-fill" size={3} className="mx-2" />
)}
{userOs && (
<span
@ -387,7 +389,7 @@ function SessionItem(props: RouteComponentProps & Props) {
</span>
)}
{userOs && (
<Icon name="circle-fill" size={3} className="mx-4" />
<Icon name="circle-fill" size={3} className="mx-2" />
)}
<span className="capitalize" style={{ maxWidth: '70px' }}>
<TextEllipsis

View file

@ -353,6 +353,7 @@ export { default as Integrations_teams } from './integrations_teams';
export { default as Integrations_vuejs } from './integrations_vuejs';
export { default as Integrations_zustand } from './integrations_zustand';
export { default as Journal_code } from './journal_code';
export { default as Kai_mono } from './kai_mono';
export { default as Kai } from './kai';
export { default as Kai_colored } from './kai_colored';
export { default as Key } from './key';

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 Kai_mono(props: Props) {
const { size = 14, width = size, height = size, fill = '' } = props;
return (
<svg viewBox="0 0 20 20" fill="none" width={ `${ width }px` } height={ `${ height }px` } ><g fill="#394DFE"><path d="M13.125 6.458a.625.625 0 1 0-1.25 0c0 2.022-.447 3.335-1.264 4.153-.818.817-2.131 1.264-4.153 1.264a.625.625 0 1 0 0 1.25c2.022 0 3.335.447 4.152 1.264.818.818 1.265 2.131 1.265 4.153a.625.625 0 1 0 1.25 0c0-2.022.447-3.335 1.264-4.153.818-.817 2.131-1.264 4.153-1.264a.625.625 0 1 0 0-1.25c-2.022 0-3.335-.447-4.153-1.264-.817-.818-1.264-2.131-1.264-4.153ZM6.042 1.458a.625.625 0 1 0-1.25 0c0 1.298-.288 2.09-.766 2.568-.478.478-1.27.766-2.568.766a.625.625 0 1 0 0 1.25c1.298 0 2.09.287 2.568.765.478.478.766 1.27.766 2.568a.625.625 0 0 0 1.25 0c0-1.298.287-2.09.765-2.568.478-.478 1.27-.765 2.568-.765a.625.625 0 0 0 0-1.25c-1.298 0-2.09-.288-2.568-.766-.478-.478-.765-1.27-.765-2.568Z"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h20v20H0z"/></clipPath></defs></svg>
);
}
export default Kai_mono;

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,11 @@
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_8_72)">
<path d="M13.1249 6.45831C13.1249 6.11313 12.8451 5.83331 12.4999 5.83331C12.1547 5.83331 11.8749 6.11313 11.8749 6.45831C11.8749 8.47982 11.4283 9.7927 10.6105 10.6106C9.79264 11.4284 8.47976 11.875 6.45825 11.875C6.11307 11.875 5.83325 12.1548 5.83325 12.5C5.83325 12.8452 6.11307 13.125 6.45825 13.125C8.47976 13.125 9.79264 13.5716 10.6105 14.3894C11.4283 15.2073 11.8749 16.5201 11.8749 18.5416C11.8749 18.8868 12.1547 19.1666 12.4999 19.1666C12.8451 19.1666 13.1249 18.8868 13.1249 18.5416C13.1249 16.5201 13.5715 15.2073 14.3893 14.3894C15.2072 13.5716 16.5201 13.125 18.5416 13.125C18.8868 13.125 19.1666 12.8452 19.1666 12.5C19.1666 12.1548 18.8868 11.875 18.5416 11.875C16.5201 11.875 15.2072 11.4284 14.3893 10.6106C13.5715 9.7927 13.1249 8.47982 13.1249 6.45831Z" fill="#394DFE"/>
<path d="M6.04158 1.45831C6.04158 1.11313 5.76176 0.833313 5.41658 0.833313C5.07141 0.833313 4.79158 1.11313 4.79158 1.45831C4.79158 2.75644 4.50416 3.54849 4.02629 4.02635C3.54843 4.50422 2.75638 4.79165 1.45825 4.79165C1.11307 4.79165 0.833252 5.07147 0.833252 5.41665C0.833252 5.76182 1.11307 6.04165 1.45825 6.04165C2.75638 6.04165 3.54843 6.32907 4.02629 6.80694C4.50416 7.2848 4.79158 8.07685 4.79158 9.37498C4.79158 9.72016 5.07141 9.99998 5.41658 9.99998C5.76176 9.99998 6.04158 9.72016 6.04158 9.37498C6.04158 8.07685 6.32901 7.2848 6.80688 6.80694C7.28474 6.32907 8.07679 6.04165 9.37492 6.04165C9.7201 6.04165 9.99992 5.76182 9.99992 5.41665C9.99992 5.07147 9.7201 4.79165 9.37492 4.79165C8.07679 4.79165 7.28474 4.50422 6.80688 4.02635C6.32901 3.54849 6.04158 2.75644 6.04158 1.45831Z" fill="#394DFE"/>
</g>
<defs>
<clipPath id="clip0_8_72">
<rect width="20" height="20" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB