mirror of
https://github.com/simstudioai/sim.git
synced 2026-04-06 03:00:16 -04:00
improvement: loading and file dropping
This commit is contained in:
@@ -14,7 +14,7 @@ import {
|
||||
Integration,
|
||||
Library,
|
||||
Pencil,
|
||||
Play,
|
||||
PlayOutline,
|
||||
Rocket,
|
||||
Search,
|
||||
Settings,
|
||||
@@ -39,13 +39,13 @@ const TOOL_ICONS: Record<MothershipToolName | SubagentName | 'mothership', IconC
|
||||
manage_skill: Asterisk,
|
||||
user_memory: Database,
|
||||
function_execute: TerminalWindow,
|
||||
superagent: Play,
|
||||
superagent: Blimp,
|
||||
user_table: TableIcon,
|
||||
workspace_file: File,
|
||||
create_workflow: Connections,
|
||||
edit_workflow: Pencil,
|
||||
build: Hammer,
|
||||
run: Play,
|
||||
run: PlayOutline,
|
||||
deploy: Rocket,
|
||||
auth: Integration,
|
||||
knowledge: Database,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use client'
|
||||
|
||||
import type { ElementType, SVGProps } from 'react'
|
||||
import { Button } from '@/components/emcn'
|
||||
import { Button, Tooltip } from '@/components/emcn'
|
||||
import { PanelLeft, Table as TableIcon } from '@/components/emcn/icons'
|
||||
import { WorkflowIcon } from '@/components/icons'
|
||||
import { getDocumentIcon } from '@/components/icons/document-icons'
|
||||
@@ -76,47 +76,67 @@ export function ResourceTabs({
|
||||
}: ResourceTabsProps) {
|
||||
return (
|
||||
<div className='flex shrink-0 items-center border-[var(--border)] border-b px-[16px] py-[8.5px]'>
|
||||
<Button
|
||||
variant='subtle'
|
||||
onClick={onCollapse}
|
||||
className='shrink-0 bg-transparent px-[8px] py-[5px] text-[12px]'
|
||||
aria-label='Collapse resource view'
|
||||
>
|
||||
<PanelLeft className='-scale-x-100 h-[16px] w-[16px] text-[var(--text-icon)]' />
|
||||
</Button>
|
||||
<div className='flex min-w-0 items-center gap-[6px] overflow-x-auto pl-[6px] [scrollbar-width:none] [&::-webkit-scrollbar]:hidden'>
|
||||
<Tooltip.Root>
|
||||
<Tooltip.Trigger asChild>
|
||||
<Button
|
||||
variant='subtle'
|
||||
onClick={onCollapse}
|
||||
className='shrink-0 bg-transparent px-[8px] py-[5px] text-[12px]'
|
||||
aria-label='Collapse resource view'
|
||||
>
|
||||
<PanelLeft className='-scale-x-100 h-[16px] w-[16px] text-[var(--text-icon)]' />
|
||||
</Button>
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Content side='bottom'>
|
||||
<p>Collapse</p>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Root>
|
||||
<div className='mx-[2px] flex min-w-0 items-center gap-[6px] overflow-x-auto px-[6px] [scrollbar-width:none] [&::-webkit-scrollbar]:hidden'>
|
||||
{resources.map((resource) => {
|
||||
const Icon = getResourceIcon(resource)
|
||||
const isActive = activeId === resource.id
|
||||
|
||||
return (
|
||||
<Button
|
||||
key={resource.id}
|
||||
variant='subtle'
|
||||
onClick={() => onSelect(resource.id)}
|
||||
className={cn(
|
||||
'shrink-0 bg-transparent px-[8px] py-[4px] text-[12px]',
|
||||
isActive && 'bg-[var(--surface-4)]'
|
||||
)}
|
||||
>
|
||||
<Icon className={cn('mr-[6px] h-[14px] w-[14px] text-[var(--text-icon)]')} />
|
||||
{resource.title}
|
||||
</Button>
|
||||
<Tooltip.Root key={resource.id}>
|
||||
<Tooltip.Trigger asChild>
|
||||
<Button
|
||||
variant='subtle'
|
||||
onClick={() => onSelect(resource.id)}
|
||||
className={cn(
|
||||
'shrink-0 bg-transparent px-[8px] py-[4px] text-[12px]',
|
||||
isActive && 'bg-[var(--surface-4)]'
|
||||
)}
|
||||
>
|
||||
<Icon className={cn('mr-[6px] h-[14px] w-[14px] text-[var(--text-icon)]')} />
|
||||
{resource.title}
|
||||
</Button>
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Content side='bottom'>
|
||||
<p>{resource.title}</p>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Root>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
{previewMode && onCyclePreviewMode && (
|
||||
<Button
|
||||
variant='subtle'
|
||||
onClick={onCyclePreviewMode}
|
||||
className='ml-auto shrink-0 bg-transparent px-[8px] py-[5px] text-[12px]'
|
||||
aria-label='Cycle preview mode'
|
||||
>
|
||||
<PreviewModeIcon
|
||||
mode={previewMode}
|
||||
className='h-[16px] w-[16px] text-[var(--text-icon)]'
|
||||
/>
|
||||
</Button>
|
||||
<Tooltip.Root>
|
||||
<Tooltip.Trigger asChild>
|
||||
<Button
|
||||
variant='subtle'
|
||||
onClick={onCyclePreviewMode}
|
||||
className='ml-auto shrink-0 bg-transparent px-[8px] py-[5px] text-[12px]'
|
||||
aria-label='Cycle preview mode'
|
||||
>
|
||||
<PreviewModeIcon
|
||||
mode={previewMode}
|
||||
className='h-[16px] w-[16px] text-[var(--text-icon)]'
|
||||
/>
|
||||
</Button>
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Content side='bottom'>
|
||||
<p>Preview mode</p>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Root>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -1,8 +1,20 @@
|
||||
'use client'
|
||||
|
||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { ArrowUp, FileText, Loader2, Mic, Paperclip, X } from 'lucide-react'
|
||||
import { Button } from '@/components/emcn'
|
||||
import { ArrowUp, Loader2, Mic, Paperclip, X } from 'lucide-react'
|
||||
import { Button, Tooltip } from '@/components/emcn'
|
||||
import {
|
||||
AudioIcon,
|
||||
CsvIcon,
|
||||
DocxIcon,
|
||||
getDocumentIcon,
|
||||
JsonIcon,
|
||||
MarkdownIcon,
|
||||
PdfIcon,
|
||||
TxtIcon,
|
||||
VideoIcon,
|
||||
XlsxIcon,
|
||||
} from '@/components/icons/document-icons'
|
||||
import { cn } from '@/lib/core/utils/cn'
|
||||
import { CHAT_ACCEPT_ATTRIBUTE } from '@/lib/uploads/utils/validation'
|
||||
import { useFileAttachments } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/copilot/components/user-input/hooks/use-file-attachments'
|
||||
@@ -23,7 +35,19 @@ const SEND_BUTTON_ACTIVE =
|
||||
'bg-[var(--c-383838)] hover:bg-[var(--c-575757)] dark:bg-[var(--c-E0E0E0)] dark:hover:bg-[var(--c-CFCFCF)]'
|
||||
const SEND_BUTTON_DISABLED = 'bg-[var(--c-808080)] dark:bg-[var(--c-808080)]'
|
||||
|
||||
const MAX_CHAT_TEXTAREA_HEIGHT = 200 // 8 lines × 24px line-height + 8px padding
|
||||
const MAX_CHAT_TEXTAREA_HEIGHT = 200
|
||||
|
||||
const DROP_OVERLAY_ICONS = [
|
||||
PdfIcon,
|
||||
DocxIcon,
|
||||
XlsxIcon,
|
||||
CsvIcon,
|
||||
TxtIcon,
|
||||
MarkdownIcon,
|
||||
JsonIcon,
|
||||
AudioIcon,
|
||||
VideoIcon,
|
||||
] as const
|
||||
|
||||
function autoResizeTextarea(e: React.FormEvent<HTMLTextAreaElement>, maxHeight: number) {
|
||||
const target = e.target as HTMLTextAreaElement
|
||||
@@ -189,9 +213,8 @@ export function UserInput({
|
||||
<div
|
||||
onClick={handleContainerClick}
|
||||
className={cn(
|
||||
'mx-auto w-full max-w-[42rem] cursor-text rounded-[20px] border border-[var(--border-1)] bg-[var(--white)] px-[10px] py-[8px] dark:bg-[var(--surface-4)]',
|
||||
isInitialView && 'shadow-sm',
|
||||
files.isDragging && 'ring-[1.75px] ring-[var(--brand-secondary)]'
|
||||
'relative mx-auto w-full max-w-[42rem] cursor-text rounded-[20px] border border-[var(--border-1)] bg-[var(--white)] px-[10px] py-[8px] dark:bg-[var(--surface-4)]',
|
||||
isInitialView && 'shadow-sm'
|
||||
)}
|
||||
onDragEnter={files.handleDragEnter}
|
||||
onDragLeave={files.handleDragLeave}
|
||||
@@ -204,48 +227,52 @@ export function UserInput({
|
||||
{files.attachedFiles.map((file) => {
|
||||
const isImage = file.type.startsWith('image/')
|
||||
return (
|
||||
<div
|
||||
key={file.id}
|
||||
className='group relative h-[56px] w-[56px] flex-shrink-0 cursor-pointer overflow-hidden rounded-[8px] border border-[var(--border-1)] bg-[var(--surface-5)] hover:bg-[var(--surface-4)]'
|
||||
title={`${file.name} (${files.formatFileSize(file.size)})`}
|
||||
onClick={() => files.handleFileClick(file)}
|
||||
>
|
||||
{isImage && file.previewUrl ? (
|
||||
<img
|
||||
src={file.previewUrl}
|
||||
alt={file.name}
|
||||
className='h-full w-full object-cover'
|
||||
/>
|
||||
) : (
|
||||
<div className='flex h-full w-full flex-col items-center justify-center gap-[2px]'>
|
||||
{file.type.includes('pdf') ? (
|
||||
<FileText className='h-[18px] w-[18px] text-red-500' />
|
||||
) : (
|
||||
<FileText className='h-[18px] w-[18px] text-blue-500' />
|
||||
)}
|
||||
<span className='max-w-[48px] truncate px-[2px] text-[9px] text-[var(--text-muted)]'>
|
||||
{file.name.split('.').pop()}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
{file.uploading && (
|
||||
<div className='absolute inset-0 flex items-center justify-center bg-black/50'>
|
||||
<Loader2 className='h-[14px] w-[14px] animate-spin text-white' />
|
||||
</div>
|
||||
)}
|
||||
{!file.uploading && (
|
||||
<button
|
||||
type='button'
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
files.removeFile(file.id)
|
||||
}}
|
||||
className='absolute top-[2px] right-[2px] flex h-[16px] w-[16px] items-center justify-center rounded-full bg-black/60 opacity-0 group-hover:opacity-100'
|
||||
<Tooltip.Root key={file.id}>
|
||||
<Tooltip.Trigger asChild>
|
||||
<div
|
||||
className='group relative h-[56px] w-[56px] flex-shrink-0 cursor-pointer overflow-hidden rounded-[8px] border border-[var(--border-1)] bg-[var(--surface-5)] hover:bg-[var(--surface-4)]'
|
||||
onClick={() => files.handleFileClick(file)}
|
||||
>
|
||||
<X className='h-[10px] w-[10px] text-white' />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
{isImage && file.previewUrl ? (
|
||||
<img
|
||||
src={file.previewUrl}
|
||||
alt={file.name}
|
||||
className='h-full w-full object-cover'
|
||||
/>
|
||||
) : (
|
||||
<div className='flex h-full w-full flex-col items-center justify-center gap-[2px] text-[var(--text-icon)]'>
|
||||
{(() => {
|
||||
const Icon = getDocumentIcon(file.type, file.name)
|
||||
return <Icon className='h-[18px] w-[18px]' />
|
||||
})()}
|
||||
<span className='max-w-[48px] truncate px-[2px] text-[9px] text-[var(--text-muted)]'>
|
||||
{file.name.split('.').pop()}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
{file.uploading && (
|
||||
<div className='absolute inset-0 flex items-center justify-center bg-black/50'>
|
||||
<Loader2 className='h-[14px] w-[14px] animate-spin text-white' />
|
||||
</div>
|
||||
)}
|
||||
{!file.uploading && (
|
||||
<button
|
||||
type='button'
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
files.removeFile(file.id)
|
||||
}}
|
||||
className='absolute top-[2px] right-[2px] flex h-[16px] w-[16px] items-center justify-center rounded-full bg-black/60 opacity-0 group-hover:opacity-100'
|
||||
>
|
||||
<X className='h-[10px] w-[10px] text-white' />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Content side='top'>
|
||||
<p className='max-w-[200px] truncate'>{file.name}</p>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Root>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
@@ -257,7 +284,7 @@ export function UserInput({
|
||||
onChange={(e) => setValue(e.target.value)}
|
||||
onKeyDown={handleKeyDown}
|
||||
onInput={handleInput}
|
||||
placeholder={files.isDragging ? 'Drop files here...' : placeholder}
|
||||
placeholder={placeholder}
|
||||
rows={1}
|
||||
className={cn(TEXTAREA_BASE_CLASSES, isInitialView ? 'max-h-[30vh]' : 'max-h-[200px]')}
|
||||
/>
|
||||
@@ -319,7 +346,6 @@ export function UserInput({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Hidden file input */}
|
||||
<input
|
||||
ref={files.fileInputRef}
|
||||
type='file'
|
||||
@@ -328,6 +354,19 @@ export function UserInput({
|
||||
accept={CHAT_ACCEPT_ATTRIBUTE}
|
||||
multiple
|
||||
/>
|
||||
|
||||
{files.isDragging && (
|
||||
<div className='pointer-events-none absolute inset-[6px] z-10 flex items-center justify-center rounded-[14px] border-[1.5px] border-[var(--border-1)] border-dashed bg-[var(--white)] dark:bg-[var(--surface-4)]'>
|
||||
<div className='flex flex-col items-center gap-[8px]'>
|
||||
<span className='font-medium text-[13px] text-[var(--text-secondary)]'>Drop files</span>
|
||||
<div className='flex items-center gap-[8px] text-[var(--text-icon)]'>
|
||||
{DROP_OVERLAY_ICONS.map((Icon, i) => (
|
||||
<Icon key={i} className='h-[14px] w-[14px]' />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { FileText } from 'lucide-react'
|
||||
import { useParams, useRouter } from 'next/navigation'
|
||||
import { Skeleton } from '@/components/emcn'
|
||||
import { PanelLeft } from '@/components/emcn/icons'
|
||||
import { useSession } from '@/lib/auth/auth-client'
|
||||
import {
|
||||
@@ -13,6 +14,7 @@ import {
|
||||
LandingWorkflowSeedStorage,
|
||||
} from '@/lib/core/utils/browser-storage'
|
||||
import { persistImportedWorkflow } from '@/lib/workflows/operations/import-export'
|
||||
import { useChatHistory } from '@/hooks/queries/tasks'
|
||||
import { useSidebarStore } from '@/stores/sidebar/store'
|
||||
import { MessageContent, MothershipView, UserInput } from './components'
|
||||
import type { FileAttachmentForApi } from './components/user-input/user-input'
|
||||
@@ -43,6 +45,23 @@ function ThinkingIndicator() {
|
||||
)
|
||||
}
|
||||
|
||||
const SKELETON_LINE_COUNT = 4
|
||||
|
||||
function ChatSkeleton({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<div className='flex h-full flex-col bg-[var(--bg)]'>
|
||||
<div className='min-h-0 flex-1 overflow-hidden px-6 py-4'>
|
||||
<div className='mx-auto max-w-[42rem] space-y-[10px] pt-3'>
|
||||
{Array.from({ length: SKELETON_LINE_COUNT }).map((_, i) => (
|
||||
<Skeleton key={i} className='h-[16px]' style={{ width: `${120 + (i % 4) * 48}px` }} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex-shrink-0 px-[24px] pb-[16px]'>{children}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
interface HomeProps {
|
||||
chatId?: string
|
||||
}
|
||||
@@ -125,6 +144,8 @@ export function Home({ chatId }: HomeProps = {}) {
|
||||
}
|
||||
}, [createWorkflowFromLandingSeed, workspaceId, router])
|
||||
|
||||
const { isLoading: isLoadingHistory } = useChatHistory(chatId)
|
||||
|
||||
const {
|
||||
messages,
|
||||
isSending,
|
||||
@@ -174,6 +195,20 @@ export function Home({ chatId }: HomeProps = {}) {
|
||||
|
||||
const hasMessages = messages.length > 0
|
||||
|
||||
if (!hasMessages && chatId && isLoadingHistory) {
|
||||
return (
|
||||
<ChatSkeleton>
|
||||
<UserInput
|
||||
onSubmit={handleSubmit}
|
||||
isSending={isSending}
|
||||
onStopGeneration={stopGeneration}
|
||||
isInitialView={false}
|
||||
userId={session?.user?.id}
|
||||
/>
|
||||
</ChatSkeleton>
|
||||
)
|
||||
}
|
||||
|
||||
if (!hasMessages) {
|
||||
return (
|
||||
<div className='flex h-full flex-col items-center justify-center bg-[var(--bg)] px-[24px]'>
|
||||
|
||||
@@ -23,8 +23,7 @@ export function Play(props: SVGProps<SVGSVGElement>) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Play icon component (stroke/outline version, matches lucide style)
|
||||
* Uses 24x24 viewBox and strokeWidth 2 for consistency with other icons.
|
||||
* Play icon component (stroke/outline version)
|
||||
* @param props - SVG properties including className, stroke, etc.
|
||||
*/
|
||||
export function PlayOutline(props: SVGProps<SVGSVGElement>) {
|
||||
@@ -32,16 +31,16 @@ export function PlayOutline(props: SVGProps<SVGSVGElement>) {
|
||||
<svg
|
||||
width='24'
|
||||
height='24'
|
||||
viewBox='0 0 24 24'
|
||||
viewBox='-1 -2 24 24'
|
||||
fill='none'
|
||||
stroke='currentColor'
|
||||
strokeWidth='2'
|
||||
strokeWidth='1.75'
|
||||
strokeLinecap='round'
|
||||
strokeLinejoin='round'
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
{...props}
|
||||
>
|
||||
<path d='M14.7175 4.07175C16.6036 5.37051 18.0001 6.39111 19.0000 7.32600C20.0087 8.26733 20.9617 9.25123 21.3031 10.5484C21.5534 11.4996 21.5534 12.5003 21.3031 13.4515C20.9617 14.7487 20.0087 15.7326 19.0000 16.6739C18.0001 17.6088 16.6037 18.6294 14.7176 19.9281C12.9093 21.1827 11.0470 22.2407 9.6333 22.8420C8.2082 23.4482 6.9090 23.7554 5.6463 23.3976C4.6383 23.1346 3.7940 22.6355 3.1138 21.9492C2.1907 21.0179 1.9001 19.7306 1.7248 18.1814C1.5507 16.6436 1.5507 14.6305 1.5508 12.0701V11.9298C1.5507 9.36936 1.5507 7.35626 1.7248 5.81844C1.9001 4.26926 2.1907 2.982 3.1138 2.05063C3.7940 1.36438 4.6383 0.865267 5.6463 0.602306C6.9090 0.244489 8.2082 0.551707 9.6333 1.15785C11.0470 1.75916 12.9092 2.81712 14.7175 4.07175Z' />
|
||||
<path d='M6 2L18 10L6 18Z' />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -7,17 +7,23 @@ import type { SVGProps } from 'react'
|
||||
export function Rocket(props: SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
width='10'
|
||||
height='12'
|
||||
viewBox='0 0 10 12'
|
||||
width='24'
|
||||
height='24'
|
||||
viewBox='-1 -2 24 24'
|
||||
fill='none'
|
||||
stroke='currentColor'
|
||||
strokeWidth='1.75'
|
||||
strokeLinecap='round'
|
||||
strokeLinejoin='round'
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d='M0.859375 11.9483C0.651042 12.0344 0.455833 12.0129 0.27375 11.8837C0.0916668 11.7546 0.000416667 11.577 0 11.3509V8.47686C0 8.26157 0.0495833 8.05705 0.14875 7.8633C0.247917 7.66954 0.385833 7.51346 0.5625 7.39505L1.25 6.92681C1.32292 7.79871 1.435 8.55221 1.58625 9.1873C1.7375 9.82239 1.98479 10.5436 2.32812 11.3509L0.859375 11.9483ZM3.625 11.0118C3.47917 11.0118 3.35417 10.9634 3.25 10.8665C3.14583 10.7696 3.06771 10.6512 3.01562 10.5113C2.73437 9.76857 2.52604 9.08246 2.39062 8.45296C2.25521 7.82347 2.1875 7.09947 2.1875 6.28095C2.1875 5.07536 2.39583 3.92638 2.8125 2.83402C3.22917 1.74167 3.79687 0.856199 4.51562 0.17762C4.57812 0.113035 4.65375 0.0671791 4.7425 0.0400531C4.83125 0.0129272 4.91708 -0.000420479 5 1.00915e-05C5.08292 0.000440662 5.16896 0.0140036 5.25812 0.0406989C5.34729 0.0673943 5.42271 0.113035 5.48437 0.17762C6.20312 0.855768 6.77083 1.74124 7.1875 2.83402C7.60417 3.92681 7.8125 5.07579 7.8125 6.28095C7.8125 7.1098 7.74479 7.83639 7.60937 8.46071C7.47396 9.08504 7.26562 9.76857 6.98437 10.5113C6.93229 10.6512 6.85417 10.7696 6.75 10.8665C6.64583 10.9634 6.52083 11.0118 6.375 11.0118H3.625ZM5 6.49085C5.34375 6.49085 5.63812 6.36448 5.88312 6.11174C6.12812 5.85899 6.25042 5.5548 6.25 5.19914C6.24958 4.84349 6.12729 4.53951 5.88312 4.2872C5.63896 4.03488 5.34458 3.90829 5 3.90743C4.65542 3.90657 4.36125 4.03316 4.1175 4.2872C3.87375 4.54123 3.75125 4.84522 3.75 5.19914C3.74875 5.55307 3.87125 5.85727 4.1175 6.11174C4.36375 6.3662 4.65792 6.49258 5 6.49085ZM9.14062 11.9483L7.67187 11.3509C8.01562 10.5436 8.26312 9.82239 8.41437 9.1873C8.56562 8.55221 8.6775 7.79871 8.75 6.92681L9.4375 7.39505C9.61458 7.51346 9.75271 7.66954 9.85187 7.8633C9.95104 8.05705 10.0004 8.26157 10 8.47686V11.3509C10 11.577 9.90896 11.7546 9.72687 11.8837C9.54479 12.0129 9.34937 12.0344 9.14062 11.9483Z'
|
||||
fill='currentColor'
|
||||
/>
|
||||
<path d='M11 1.75C8.5 4.75 7.25 8.75 7.25 12.75V15.75H14.75V12.75C14.75 8.75 13.5 4.75 11 1.75Z' />
|
||||
<path d='M7.25 12.75L4.25 15.75H7.25' />
|
||||
<path d='M14.75 12.75L17.75 15.75H14.75' />
|
||||
<circle cx='11' cy='9.75' r='1.75' />
|
||||
<path d='M9 15.75V18.25' />
|
||||
<path d='M13 15.75V18.25' />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user