new Kai design (#3443)
* redesign is inevitable * ui: change kai design, add sessions to list, swap to new icon * ui: some changes for suggestions in thread
This commit is contained in:
parent
517fe6c99e
commit
4e2158ab64
17 changed files with 365 additions and 192 deletions
|
|
@ -18,7 +18,7 @@ function KaiChat() {
|
||||||
const chatTitle = kaiStore.chatTitle;
|
const chatTitle = kaiStore.chatTitle;
|
||||||
const setTitle = kaiStore.setTitle;
|
const setTitle = kaiStore.setTitle;
|
||||||
const userId = userStore.account.id;
|
const userId = userStore.account.id;
|
||||||
const userLetter = userStore.account.name[0].toUpperCase();
|
const userName = userStore.account.name;
|
||||||
const { activeSiteId } = projectsStore;
|
const { activeSiteId } = projectsStore;
|
||||||
const [section, setSection] = React.useState<'intro' | 'chat'>('intro');
|
const [section, setSection] = React.useState<'intro' | 'chat'>('intro');
|
||||||
const [threadId, setThreadId] = React.useState<string | null>(null);
|
const [threadId, setThreadId] = React.useState<string | null>(null);
|
||||||
|
|
@ -44,7 +44,11 @@ function KaiChat() {
|
||||||
hideModal();
|
hideModal();
|
||||||
}}
|
}}
|
||||||
/>,
|
/>,
|
||||||
{ right: true, width: 300 },
|
{
|
||||||
|
right: true,
|
||||||
|
width: 320,
|
||||||
|
className: 'bg-none flex items-center h-screen',
|
||||||
|
},
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -93,46 +97,72 @@ function KaiChat() {
|
||||||
const newThread = await kaiService.createKaiChat(activeSiteId);
|
const newThread = await kaiService.createKaiChat(activeSiteId);
|
||||||
if (newThread) {
|
if (newThread) {
|
||||||
setThreadId(newThread.toString());
|
setThreadId(newThread.toString());
|
||||||
|
kaiStore.setTitle(null);
|
||||||
setSection('chat');
|
setSection('chat');
|
||||||
} else {
|
} else {
|
||||||
toast.error("Something wen't wrong. Please try again later.");
|
toast.error("Something wen't wrong. Please try again later.");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onCancel = () => {
|
||||||
|
if (!threadId) return;
|
||||||
|
void kaiStore.cancelGeneration({
|
||||||
|
projectId: activeSiteId,
|
||||||
|
threadId,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full mx-auto" style={{ maxWidth: PANEL_SIZES.maxWidth }}>
|
|
||||||
<div
|
<div
|
||||||
className={'w-full rounded-lg overflow-hidden border shadow relative'}
|
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
|
<ChatHeader
|
||||||
chatTitle={chatTitle}
|
chatTitle={chatTitle}
|
||||||
openChats={openChats}
|
openChats={openChats}
|
||||||
goBack={goBack}
|
goBack={goBack}
|
||||||
|
onCreate={onCreate}
|
||||||
/>
|
/>
|
||||||
|
{section === 'intro' ? (
|
||||||
|
<>
|
||||||
<div
|
<div
|
||||||
className={
|
className={
|
||||||
'w-full bg-active-blue flex flex-col items-center justify-center py-4 relative'
|
'flex flex-col items-center justify-center py-4 relative'
|
||||||
}
|
}
|
||||||
style={{
|
style={{
|
||||||
height: '70svh',
|
position: 'absolute',
|
||||||
background:
|
top: '50%',
|
||||||
'radial-gradient(50% 50% at 50% 50%, var(--color-glassWhite) 0%, var(--color-glassMint) 46%, var(--color-glassLavander) 100%)',
|
left: 0,
|
||||||
|
width: '100%',
|
||||||
|
transform: 'translateY(-50%)',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{section === 'intro' ? (
|
<IntroSection
|
||||||
<IntroSection onAsk={onCreate} projectId={activeSiteId} />
|
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
|
<ChatLog
|
||||||
threadId={threadId}
|
threadId={threadId}
|
||||||
projectId={activeSiteId}
|
projectId={activeSiteId}
|
||||||
userLetter={userLetter}
|
|
||||||
chatTitle={chatTitle}
|
chatTitle={chatTitle}
|
||||||
initialMsg={initialMsg}
|
initialMsg={initialMsg}
|
||||||
setInitialMsg={setInitialMsg}
|
setInitialMsg={setInitialMsg}
|
||||||
|
onCancel={onCancel}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,7 @@ export default class KaiService extends AiService {
|
||||||
supports_visualization: boolean;
|
supports_visualization: boolean;
|
||||||
chart: string;
|
chart: string;
|
||||||
chart_data: string;
|
chart_data: string;
|
||||||
|
sessions?: Record<string, any>[];
|
||||||
}[];
|
}[];
|
||||||
title: string;
|
title: string;
|
||||||
}> => {
|
}> => {
|
||||||
|
|
@ -128,7 +129,9 @@ export default class KaiService extends AiService {
|
||||||
projectId: string,
|
projectId: string,
|
||||||
threadId?: string | null,
|
threadId?: string | null,
|
||||||
): Promise<string[]> => {
|
): Promise<string[]> => {
|
||||||
const endpoint = (threadId) ? `/kai/${projectId}/chats/${threadId}/prompt-suggestions` : `/kai/${projectId}/prompt-suggestions`;
|
const endpoint = threadId
|
||||||
|
? `/kai/${projectId}/chats/${threadId}/prompt-suggestions`
|
||||||
|
: `/kai/${projectId}/prompt-suggestions`;
|
||||||
const r = await this.client.get(endpoint);
|
const r = await this.client.get(endpoint);
|
||||||
if (!r.ok) {
|
if (!r.ok) {
|
||||||
throw new Error('Failed to fetch prompt suggestions');
|
throw new Error('Failed to fetch prompt suggestions');
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import { BotChunk, ChatManager } from './SocketManager';
|
||||||
import { kaiService as aiService, kaiService } from 'App/services';
|
import { kaiService as aiService, kaiService } from 'App/services';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
import Widget from 'App/mstore/types/widget';
|
import Widget from 'App/mstore/types/widget';
|
||||||
|
import Session, { ISession } from '@/types/session/session';
|
||||||
|
|
||||||
export interface Message {
|
export interface Message {
|
||||||
text: string;
|
text: string;
|
||||||
|
|
@ -15,6 +16,7 @@ export interface Message {
|
||||||
supports_visualization: boolean;
|
supports_visualization: boolean;
|
||||||
feedback: boolean | null;
|
feedback: boolean | null;
|
||||||
duration: number;
|
duration: number;
|
||||||
|
sessions?: Session[];
|
||||||
}
|
}
|
||||||
export interface SentMessage
|
export interface SentMessage
|
||||||
extends Omit<
|
extends Omit<
|
||||||
|
|
@ -161,6 +163,9 @@ class KaiStore {
|
||||||
chart: m.chart,
|
chart: m.chart,
|
||||||
supports_visualization: m.supports_visualization,
|
supports_visualization: m.supports_visualization,
|
||||||
chart_data: m.chart_data,
|
chart_data: m.chart_data,
|
||||||
|
sessions: m.sessions
|
||||||
|
? m.sessions.map((s) => new Session(s))
|
||||||
|
: undefined,
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
@ -220,6 +225,9 @@ class KaiStore {
|
||||||
chart: '',
|
chart: '',
|
||||||
supports_visualization: msg.supports_visualization,
|
supports_visualization: msg.supports_visualization,
|
||||||
chart_data: '',
|
chart_data: '',
|
||||||
|
sessions: msg.sessions
|
||||||
|
? msg.sessions.map((s) => new Session(s))
|
||||||
|
: undefined,
|
||||||
};
|
};
|
||||||
this.bumpUsage();
|
this.bumpUsage();
|
||||||
this.addMessage(msgObj);
|
this.addMessage(msgObj);
|
||||||
|
|
@ -268,7 +276,7 @@ class KaiStore {
|
||||||
deleting.push(this.lastKaiMessage.index);
|
deleting.push(this.lastKaiMessage.index);
|
||||||
}
|
}
|
||||||
this.deleteAtIndex(deleting);
|
this.deleteAtIndex(deleting);
|
||||||
this.setReplacing(false);
|
this.setReplacing(null);
|
||||||
}
|
}
|
||||||
this.addMessage({
|
this.addMessage({
|
||||||
text: message,
|
text: message,
|
||||||
|
|
@ -309,7 +317,6 @@ class KaiStore {
|
||||||
|
|
||||||
cancelGeneration = async (settings: {
|
cancelGeneration = async (settings: {
|
||||||
projectId: string;
|
projectId: string;
|
||||||
userId: string;
|
|
||||||
threadId: string;
|
threadId: string;
|
||||||
}) => {
|
}) => {
|
||||||
try {
|
try {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import io from 'socket.io-client';
|
import io from 'socket.io-client';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
|
import { ISession } from '@/types/session/session';
|
||||||
|
|
||||||
export class ChatManager {
|
export class ChatManager {
|
||||||
socket: ReturnType<typeof io>;
|
socket: ReturnType<typeof io>;
|
||||||
|
|
@ -77,9 +78,7 @@ export class ChatManager {
|
||||||
msgCallback,
|
msgCallback,
|
||||||
titleCallback,
|
titleCallback,
|
||||||
}: {
|
}: {
|
||||||
msgCallback: (
|
msgCallback: (msg: StateEvent | BotChunk) => void;
|
||||||
msg: StateEvent | BotChunk,
|
|
||||||
) => void;
|
|
||||||
titleCallback: (title: string) => void;
|
titleCallback: (title: string) => void;
|
||||||
}) => {
|
}) => {
|
||||||
this.socket.on('chunk', (msg: BotChunk) => {
|
this.socket.on('chunk', (msg: BotChunk) => {
|
||||||
|
|
@ -111,7 +110,8 @@ export interface BotChunk {
|
||||||
messageId: string;
|
messageId: string;
|
||||||
duration: number;
|
duration: number;
|
||||||
supports_visualization: boolean;
|
supports_visualization: boolean;
|
||||||
type: 'chunk'
|
sessions?: ISession[];
|
||||||
|
type: 'chunk';
|
||||||
}
|
}
|
||||||
|
|
||||||
interface StateEvent {
|
interface StateEvent {
|
||||||
|
|
|
||||||
|
|
@ -1,52 +1,63 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Icon } from 'UI';
|
import { Icon } from 'UI';
|
||||||
import { MessagesSquare, ArrowLeft } from 'lucide-react';
|
import { MessagesSquare, ArrowLeft, SquarePen } from 'lucide-react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
function ChatHeader({
|
function ChatHeader({
|
||||||
openChats = () => {},
|
openChats = () => {},
|
||||||
goBack,
|
goBack,
|
||||||
chatTitle,
|
chatTitle,
|
||||||
|
onCreate,
|
||||||
}: {
|
}: {
|
||||||
goBack?: () => void;
|
goBack?: () => void;
|
||||||
openChats?: () => void;
|
openChats?: () => void;
|
||||||
chatTitle: string | null;
|
chatTitle: string | null;
|
||||||
|
onCreate: () => void;
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
//absolute top-0 left-0 right-0 z-10
|
||||||
return (
|
return (
|
||||||
|
<div className="p-4 pb-0 w-full">
|
||||||
<div
|
<div
|
||||||
className={
|
className={'px-4 py-2 flex items-center bg-gray-lightest rounded-lg'}
|
||||||
'px-4 py-2 flex items-center bg-white border-b border-b-gray-lighter'
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<div className={'flex-1'}>
|
<div className={'flex-1'}>
|
||||||
{goBack ? (
|
{goBack ? (
|
||||||
<div
|
<div
|
||||||
className={
|
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}
|
onClick={goBack}
|
||||||
>
|
>
|
||||||
<ArrowLeft size={14} />
|
<ArrowLeft size={14} />
|
||||||
<div>{t('Back')}</div>
|
<div>{t('Back')}</div>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : (
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Icon name={'kai-mono'} size={18} />
|
||||||
|
<div className={'font-semibold text-xl'}>Kai</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className={'flex items-center gap-2 mx-auto max-w-[80%]'}>
|
<div className={'flex items-center gap-2 mx-auto max-w-[80%]'}>
|
||||||
{chatTitle ? (
|
{chatTitle ? (
|
||||||
<div className="font-semibold text-xl whitespace-nowrap truncate">
|
<div className="font-semibold text-xl whitespace-nowrap truncate">
|
||||||
{chatTitle}
|
{chatTitle}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : null}
|
||||||
<>
|
|
||||||
<Icon name={'kai_colored'} size={18} />
|
|
||||||
<div className={'font-semibold text-xl'}>Kai</div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
</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
|
<div
|
||||||
className="font-semibold w-fit cursor-pointer flex items-center gap-2"
|
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 hover:text-main flex items-center gap-2"
|
||||||
onClick={openChats}
|
onClick={openChats}
|
||||||
>
|
>
|
||||||
<MessagesSquare size={14} />
|
<MessagesSquare size={14} />
|
||||||
|
|
@ -54,6 +65,7 @@ function ChatHeader({
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Button, Input, Tooltip } from 'antd';
|
import { Button, Input, Tooltip } from 'antd';
|
||||||
import { SendHorizonal, OctagonX } from 'lucide-react';
|
import { X, ArrowUp } from 'lucide-react';
|
||||||
import { kaiStore } from '../KaiStore';
|
import { kaiStore } from '../KaiStore';
|
||||||
import { observer } from 'mobx-react-lite';
|
import { observer } from 'mobx-react-lite';
|
||||||
import Usage from './Usage';
|
import Usage from './Usage';
|
||||||
|
|
@ -8,11 +8,13 @@ import Usage from './Usage';
|
||||||
function ChatInput({
|
function ChatInput({
|
||||||
isLoading,
|
isLoading,
|
||||||
onSubmit,
|
onSubmit,
|
||||||
threadId,
|
isArea,
|
||||||
|
onCancel,
|
||||||
}: {
|
}: {
|
||||||
isLoading?: boolean;
|
isLoading?: boolean;
|
||||||
onSubmit: (str: string) => void;
|
onSubmit: (str: string) => void;
|
||||||
threadId: string;
|
onCancel: () => void;
|
||||||
|
isArea?: boolean;
|
||||||
}) {
|
}) {
|
||||||
const inputRef = React.useRef<typeof Input>(null);
|
const inputRef = React.useRef<typeof Input>(null);
|
||||||
const usage = kaiStore.usage;
|
const usage = kaiStore.usage;
|
||||||
|
|
@ -28,8 +30,7 @@ function ChatInput({
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (isProcessing) {
|
if (isProcessing) {
|
||||||
const settings = { projectId: '2325', userId: '0', threadId };
|
onCancel();
|
||||||
void kaiStore.cancelGeneration(settings);
|
|
||||||
} else {
|
} else {
|
||||||
if (inputValue.length > 0) {
|
if (inputValue.length > 0) {
|
||||||
onSubmit(inputValue);
|
onSubmit(inputValue);
|
||||||
|
|
@ -50,7 +51,34 @@ function ChatInput({
|
||||||
}, [inputValue]);
|
}, [inputValue]);
|
||||||
|
|
||||||
const isReplacing = kaiStore.replacing !== null;
|
const isReplacing = kaiStore.replacing !== null;
|
||||||
|
const placeholder = limited
|
||||||
|
? `You've reached the daily limit for queries, come again tomorrow!`
|
||||||
|
: 'Ask anything about your product and users...';
|
||||||
|
if (isArea) {
|
||||||
|
return (
|
||||||
|
<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 (
|
return (
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<Input
|
<Input
|
||||||
|
|
@ -61,12 +89,9 @@ function ChatInput({
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
placeholder={
|
placeholder={placeholder}
|
||||||
limited
|
|
||||||
? `You've reached the daily limit for queries, come again tomorrow!`
|
|
||||||
: 'Ask anything about your product and users...'
|
|
||||||
}
|
|
||||||
size={'large'}
|
size={'large'}
|
||||||
|
className="rounded-lg shadow"
|
||||||
disabled={limited}
|
disabled={limited}
|
||||||
value={inputValue}
|
value={inputValue}
|
||||||
onChange={(e) => setInputValue(e.target.value)}
|
onChange={(e) => setInputValue(e.target.value)}
|
||||||
|
|
@ -76,31 +101,19 @@ function ChatInput({
|
||||||
<Tooltip title={'Cancel Editing'}>
|
<Tooltip title={'Cancel Editing'}>
|
||||||
<Button
|
<Button
|
||||||
onClick={cancelReplace}
|
onClick={cancelReplace}
|
||||||
icon={<OctagonX size={16} />}
|
icon={<X size={16} />}
|
||||||
type={'text'}
|
|
||||||
size={'small'}
|
size={'small'}
|
||||||
shape={'circle'}
|
shape={'circle'}
|
||||||
disabled={limited}
|
disabled={limited}
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
) : null}
|
) : null}
|
||||||
<Tooltip title={'Send message'}>
|
<SendButton
|
||||||
<Button
|
isLoading={isLoading}
|
||||||
loading={isLoading}
|
submit={submit}
|
||||||
onClick={submit}
|
limited={limited}
|
||||||
disabled={limited}
|
isProcessing={isProcessing}
|
||||||
icon={
|
|
||||||
isProcessing ? (
|
|
||||||
<OctagonX size={16} />
|
|
||||||
) : (
|
|
||||||
<SendHorizonal size={16} />
|
|
||||||
)
|
|
||||||
}
|
|
||||||
type={'text'}
|
|
||||||
size={'small'}
|
|
||||||
shape={'circle'}
|
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
@ -111,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);
|
export default observer(ChatInput);
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ChatInput from './ChatInput';
|
import ChatInput from './ChatInput';
|
||||||
import ChatMsg, { ChatNotice } from './ChatMsg';
|
import ChatMsg, { ChatNotice } from './ChatMsg';
|
||||||
import Ideas from "./Ideas";
|
import Ideas from './Ideas';
|
||||||
import { Loader } from 'UI';
|
import { Loader } from 'UI';
|
||||||
import { kaiStore } from '../KaiStore';
|
import { kaiStore } from '../KaiStore';
|
||||||
import { observer } from 'mobx-react-lite';
|
import { observer } from 'mobx-react-lite';
|
||||||
|
|
@ -9,17 +9,17 @@ import { observer } from 'mobx-react-lite';
|
||||||
function ChatLog({
|
function ChatLog({
|
||||||
projectId,
|
projectId,
|
||||||
threadId,
|
threadId,
|
||||||
userLetter,
|
|
||||||
initialMsg,
|
initialMsg,
|
||||||
chatTitle,
|
chatTitle,
|
||||||
setInitialMsg,
|
setInitialMsg,
|
||||||
|
onCancel,
|
||||||
}: {
|
}: {
|
||||||
projectId: string;
|
projectId: string;
|
||||||
threadId: any;
|
threadId: any;
|
||||||
userLetter: string;
|
|
||||||
initialMsg: string | null;
|
initialMsg: string | null;
|
||||||
setInitialMsg: (msg: string | null) => void;
|
setInitialMsg: (msg: string | null) => void;
|
||||||
chatTitle: string | null;
|
chatTitle: string | null;
|
||||||
|
onCancel: () => void;
|
||||||
}) {
|
}) {
|
||||||
const messages = kaiStore.messages;
|
const messages = kaiStore.messages;
|
||||||
const loading = kaiStore.loadingChat;
|
const loading = kaiStore.loadingChat;
|
||||||
|
|
@ -51,11 +51,15 @@ function ChatLog({
|
||||||
});
|
});
|
||||||
}, [messages.length, processingStage]);
|
}, [messages.length, processingStage]);
|
||||||
|
|
||||||
const lastHumanMsgInd: null | number = kaiStore.lastHumanMessage.index;
|
const lastKaiMessageInd: null | number = kaiStore.lastKaiMessage.index;
|
||||||
|
const lastHumanMsgInd: number | null = kaiStore.lastHumanMessage.index;
|
||||||
|
const showIdeas =
|
||||||
|
!processingStage && lastKaiMessageInd === messages.length - 1;
|
||||||
return (
|
return (
|
||||||
<Loader loading={loading} className={'w-full h-full'}>
|
<Loader loading={loading} className={'w-full h-full'}>
|
||||||
<div
|
<div
|
||||||
ref={chatRef}
|
ref={chatRef}
|
||||||
|
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'
|
||||||
}
|
}
|
||||||
|
|
@ -64,7 +68,6 @@ function ChatLog({
|
||||||
{messages.map((msg, index) => (
|
{messages.map((msg, index) => (
|
||||||
<React.Fragment key={msg.messageId ?? index}>
|
<React.Fragment key={msg.messageId ?? index}>
|
||||||
<ChatMsg
|
<ChatMsg
|
||||||
userName={userLetter}
|
|
||||||
siteId={projectId}
|
siteId={projectId}
|
||||||
message={msg}
|
message={msg}
|
||||||
chatTitle={chatTitle}
|
chatTitle={chatTitle}
|
||||||
|
|
@ -82,10 +85,16 @@ function ChatLog({
|
||||||
duration={processingStage.duration}
|
duration={processingStage.duration}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
{(!processingStage && lastHumanMsgInd && messages.length == lastHumanMsgInd + 2) ? <Ideas onClick={(query) => onSubmit(query)} projectId={projectId} threadId={threadId}/> : null}
|
{showIdeas ? (
|
||||||
|
<Ideas
|
||||||
|
onClick={(query) => onSubmit(query)}
|
||||||
|
projectId={projectId}
|
||||||
|
threadId={threadId}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
<div className={'sticky bottom-0 pt-6 w-2/3'}>
|
<div className={'sticky bottom-0 pt-6 w-2/3 z-50'}>
|
||||||
<ChatInput onSubmit={onSubmit} threadId={threadId} />
|
<ChatInput onCancel={onCancel} onSubmit={onSubmit} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Loader>
|
</Loader>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Icon, CopyButton } from 'UI';
|
import { CopyButton } 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';
|
||||||
|
|
@ -8,7 +8,7 @@ import {
|
||||||
Loader,
|
Loader,
|
||||||
ThumbsUp,
|
ThumbsUp,
|
||||||
ThumbsDown,
|
ThumbsDown,
|
||||||
ListRestart,
|
SquarePen,
|
||||||
FileDown,
|
FileDown,
|
||||||
Clock,
|
Clock,
|
||||||
ChartLine,
|
ChartLine,
|
||||||
|
|
@ -20,6 +20,7 @@ import { durationFormatted } from 'App/date';
|
||||||
import WidgetChart from '@/components/Dashboard/components/WidgetChart';
|
import WidgetChart from '@/components/Dashboard/components/WidgetChart';
|
||||||
import Widget from 'App/mstore/types/widget';
|
import Widget from 'App/mstore/types/widget';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import SessionItem from 'Shared/SessionItem';
|
||||||
|
|
||||||
function ChatMsg({
|
function ChatMsg({
|
||||||
userName,
|
userName,
|
||||||
|
|
@ -168,28 +169,12 @@ function ChatMsg({
|
||||||
}, [metricData, chart_data]);
|
}, [metricData, chart_data]);
|
||||||
return (
|
return (
|
||||||
<div className={cn('flex gap-2', isUser ? 'flex-row-reverse' : 'flex-row')}>
|
<div className={cn('flex gap-2', isUser ? 'flex-row-reverse' : 'flex-row')}>
|
||||||
{isUser ? (
|
<div className={'mt-1 flex flex-col group/actions'}>
|
||||||
<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
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'markdown-body',
|
'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
|
data-openreplay-obscured
|
||||||
ref={bodyRef}
|
ref={bodyRef}
|
||||||
|
|
@ -204,32 +189,42 @@ function ChatMsg({
|
||||||
<WidgetChart metric={metric} isPreview height={360} />
|
<WidgetChart metric={metric} isPreview height={360} />
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : 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 ? (
|
{isUser ? (
|
||||||
<>
|
<div className="invisible group-hover/actions:visible mt-2">
|
||||||
|
<Tooltip title={t('Edit')}>
|
||||||
<div
|
<div
|
||||||
onClick={onEdit}
|
onClick={onEdit}
|
||||||
className={cn(
|
className={cn(
|
||||||
'ml-auto flex items-center gap-2 px-2',
|
'ml-auto flex items-center gap-2 px-2',
|
||||||
'rounded-lg border border-gray-medium text-sm cursor-pointer',
|
'rounded-lg cursor-pointer',
|
||||||
'hover:border-main hover:text-main w-fit',
|
'hover:text-main w-fit',
|
||||||
canEdit && !isEditing ? '' : 'hidden',
|
canEdit && !isEditing ? '' : 'hidden',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<ListRestart size={16} />
|
<SquarePen size={16} />
|
||||||
<div>{t('Edit')}</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
</Tooltip>
|
||||||
<div
|
<div
|
||||||
onClick={onCancelEdit}
|
onClick={onCancelEdit}
|
||||||
className={cn(
|
className={cn(
|
||||||
'ml-auto flex items-center gap-2 px-2',
|
'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',
|
'hover:border-main hover:text-main w-fit',
|
||||||
isEditing ? '' : 'hidden',
|
isEditing ? '' : 'hidden',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div>{t('Cancel')}</div>
|
<div>{t('Cancel')}</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className={'flex items-center gap-2'}>
|
<div className={'flex items-center gap-2'}>
|
||||||
{duration ? <MsgDuration duration={duration} /> : null}
|
{duration ? <MsgDuration duration={duration} /> : null}
|
||||||
|
|
|
||||||
|
|
@ -44,10 +44,13 @@ function ChatsModal({
|
||||||
refetch();
|
refetch();
|
||||||
};
|
};
|
||||||
return (
|
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'}>
|
<div className={'flex items-center font-semibold text-lg gap-2'}>
|
||||||
<MessagesSquare size={16} />
|
<MessagesSquare size={16} />
|
||||||
<span>{t('Chats')}</span>
|
<span>{t('Previous Chats')}</span>
|
||||||
</div>
|
</div>
|
||||||
{usage.percent > 80 ? (
|
{usage.percent > 80 ? (
|
||||||
<div className="text-red text-sm">
|
<div className="text-red text-sm">
|
||||||
|
|
|
||||||
|
|
@ -4,12 +4,17 @@ import { useQuery } from '@tanstack/react-query';
|
||||||
import { kaiService } from 'App/services';
|
import { kaiService } from 'App/services';
|
||||||
import { useTranslation } from 'react-i18next';
|
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 { t } = useTranslation();
|
||||||
const {
|
const { data: suggestedPromptIdeas = [], isPending } = useQuery({
|
||||||
data: suggestedPromptIdeas = [],
|
|
||||||
isPending,
|
|
||||||
} = useQuery({
|
|
||||||
queryKey: ['kai', projectId, 'chats', threadId, 'prompt-suggestions'],
|
queryKey: ['kai', projectId, 'chats', threadId, 'prompt-suggestions'],
|
||||||
queryFn: () => kaiService.getPromptSuggestions(projectId, threadId),
|
queryFn: () => kaiService.getPromptSuggestions(projectId, threadId),
|
||||||
staleTime: 1000 * 60,
|
staleTime: 1000 * 60,
|
||||||
|
|
@ -23,35 +28,45 @@ function Ideas({ onClick, projectId, threadId = null }: { onClick: (query: strin
|
||||||
const result = suggestedPromptIdeas;
|
const result = suggestedPromptIdeas;
|
||||||
const targetSize = 3;
|
const targetSize = 3;
|
||||||
while (result.length < targetSize && defaultPromptIdeas.length) {
|
while (result.length < targetSize && defaultPromptIdeas.length) {
|
||||||
result.push(defaultPromptIdeas.pop())
|
result.push(defaultPromptIdeas.pop());
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}, [suggestedPromptIdeas.length]);
|
}, [suggestedPromptIdeas.length]);
|
||||||
return (
|
return (
|
||||||
<>
|
<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'}>
|
||||||
<Lightbulb size={16} />
|
<b>Suggested Ideas:</b>
|
||||||
<b>Ideas:</b>
|
</div>
|
||||||
|
{isPending ? (
|
||||||
|
<div className="animate-pulse text-disabled-text">
|
||||||
|
{t('Generating ideas')}...
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="flex gap-2 flex-wrap">
|
||||||
|
{ideas.map((title) => (
|
||||||
|
<IdeaItem key={title} onClick={onClick} title={title} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</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>)
|
|
||||||
}
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function IdeaItem({ title, onClick }: { title: string, onClick: (query: string) => void }) {
|
function IdeaItem({
|
||||||
|
title,
|
||||||
|
onClick,
|
||||||
|
}: {
|
||||||
|
title: string;
|
||||||
|
onClick: (query: string) => void;
|
||||||
|
}) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
onClick={() => onClick(title)}
|
onClick={() => onClick(title)}
|
||||||
className={
|
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} />
|
{title}
|
||||||
<span>{title}</span>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,24 +2,34 @@ import React from 'react';
|
||||||
import ChatInput from './ChatInput';
|
import ChatInput from './ChatInput';
|
||||||
import Ideas from './Ideas';
|
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;
|
const isLoading = false;
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className={'text-disabled-text text-xl absolute top-4'}>
|
<div className={'relative w-2/3 flex flex-col gap-4'}>
|
||||||
Kai is your AI assistant, delivering smart insights in response to your
|
<div className="font-semibold text-lg">
|
||||||
queries.
|
Hey {userName}, how can I help you?
|
||||||
</div>
|
</div>
|
||||||
<div className={'relative w-2/3'} style={{ height: 44 }}>
|
<ChatInput
|
||||||
{/*<GradientBorderInput placeholder={'Ask anything about your product and users...'} onButtonClick={() => null} />*/}
|
onCancel={onCancel}
|
||||||
<ChatInput isLoading={isLoading} onSubmit={onAsk} />
|
isLoading={isLoading}
|
||||||
|
onSubmit={onAsk}
|
||||||
|
isArea
|
||||||
|
/>
|
||||||
<div className={'absolute top-full flex flex-col gap-2 mt-4'}>
|
<div className={'absolute top-full flex flex-col gap-2 mt-4'}>
|
||||||
<Ideas onClick={(query) => onAsk(query)} projectId={projectId} />
|
<Ideas onClick={(query) => onAsk(query)} projectId={projectId} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={'text-disabled-text absolute bottom-4'}>
|
|
||||||
OpenReplay AI can make mistakes. Verify its outputs.
|
|
||||||
</div>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
import React, { Component, createContext } from 'react';
|
import React, { Component, createContext } from 'react';
|
||||||
import Modal from './Modal';
|
import Modal from './Modal';
|
||||||
|
import { className } from '@medv/finder';
|
||||||
|
|
||||||
const ModalContext = createContext({
|
const ModalContext = createContext({
|
||||||
component: null,
|
component: null,
|
||||||
|
|
@ -29,6 +30,7 @@ export class ModalProvider extends Component {
|
||||||
this.setState({
|
this.setState({
|
||||||
component,
|
component,
|
||||||
props,
|
props,
|
||||||
|
className: props.className || undefined,
|
||||||
});
|
});
|
||||||
document.addEventListener('keydown', this.handleKeyDown);
|
document.addEventListener('keydown', this.handleKeyDown);
|
||||||
document.querySelector('body').style.overflow = 'hidden';
|
document.querySelector('body').style.overflow = 'hidden';
|
||||||
|
|
|
||||||
|
|
@ -71,6 +71,7 @@ interface Props {
|
||||||
bookmarked?: boolean;
|
bookmarked?: boolean;
|
||||||
toggleFavorite?: (sessionId: string) => void;
|
toggleFavorite?: (sessionId: string) => void;
|
||||||
query?: string;
|
query?: string;
|
||||||
|
slim?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const PREFETCH_STATE = {
|
const PREFETCH_STATE = {
|
||||||
|
|
@ -99,6 +100,7 @@ function SessionItem(props: RouteComponentProps & Props) {
|
||||||
isDisabled,
|
isDisabled,
|
||||||
live: propsLive,
|
live: propsLive,
|
||||||
isAdd,
|
isAdd,
|
||||||
|
slim,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
|
@ -261,7 +263,7 @@ function SessionItem(props: RouteComponentProps & Props) {
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div
|
<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"
|
id="session-item"
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
onMouseEnter={handleHover}
|
onMouseEnter={handleHover}
|
||||||
|
|
@ -343,7 +345,7 @@ function SessionItem(props: RouteComponentProps & Props) {
|
||||||
: 'Event'}
|
: 'Event'}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<Icon name="circle-fill" size={3} className="mx-4" />
|
<Icon name="circle-fill" size={3} className="mx-2" />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -373,7 +375,7 @@ function SessionItem(props: RouteComponentProps & Props) {
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{userOs && userBrowser && (
|
{userOs && userBrowser && (
|
||||||
<Icon name="circle-fill" size={3} className="mx-4" />
|
<Icon name="circle-fill" size={3} className="mx-2" />
|
||||||
)}
|
)}
|
||||||
{userOs && (
|
{userOs && (
|
||||||
<span
|
<span
|
||||||
|
|
@ -387,7 +389,7 @@ function SessionItem(props: RouteComponentProps & Props) {
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{userOs && (
|
{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' }}>
|
<span className="capitalize" style={{ maxWidth: '70px' }}>
|
||||||
<TextEllipsis
|
<TextEllipsis
|
||||||
|
|
|
||||||
|
|
@ -353,6 +353,7 @@ export { default as Integrations_teams } from './integrations_teams';
|
||||||
export { default as Integrations_vuejs } from './integrations_vuejs';
|
export { default as Integrations_vuejs } from './integrations_vuejs';
|
||||||
export { default as Integrations_zustand } from './integrations_zustand';
|
export { default as Integrations_zustand } from './integrations_zustand';
|
||||||
export { default as Journal_code } from './journal_code';
|
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 } from './kai';
|
||||||
export { default as Kai_colored } from './kai_colored';
|
export { default as Kai_colored } from './kai_colored';
|
||||||
export { default as Key } from './key';
|
export { default as Key } from './key';
|
||||||
|
|
|
||||||
18
frontend/app/components/ui/Icons/kai_mono.tsx
Normal file
18
frontend/app/components/ui/Icons/kai_mono.tsx
Normal 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
11
frontend/app/svg/icons/kai-mono.svg
Normal file
11
frontend/app/svg/icons/kai-mono.svg
Normal 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 |
Loading…
Add table
Reference in a new issue