@fe-free/ai 4.1.4 → 4.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # @fe-free/ai
2
2
 
3
+ ## 4.1.6
4
+
5
+ ### Patch Changes
6
+
7
+ - feat: ai
8
+ - @fe-free/core@4.1.6
9
+ - @fe-free/icons@4.1.6
10
+ - @fe-free/tool@4.1.6
11
+
12
+ ## 4.1.5
13
+
14
+ ### Patch Changes
15
+
16
+ - feat: ai
17
+ - @fe-free/core@4.1.5
18
+ - @fe-free/icons@4.1.5
19
+ - @fe-free/tool@4.1.5
20
+
3
21
  ## 4.1.4
4
22
 
5
23
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fe-free/ai",
3
- "version": "4.1.4",
3
+ "version": "4.1.6",
4
4
  "description": "",
5
5
  "main": "./src/index.ts",
6
6
  "author": "",
@@ -12,7 +12,8 @@
12
12
  "dependencies": {
13
13
  "ahooks": "^3.7.8",
14
14
  "classnames": "^2.5.1",
15
- "@fe-free/core": "4.1.4"
15
+ "lodash-es": "^4.17.21",
16
+ "@fe-free/core": "4.1.6"
16
17
  },
17
18
  "peerDependencies": {
18
19
  "antd": "^5.27.1",
@@ -22,8 +23,8 @@
22
23
  "i18next-icu": "^2.4.1",
23
24
  "react": "^19.2.0",
24
25
  "react-i18next": "^16.4.0",
25
- "@fe-free/icons": "4.1.4",
26
- "@fe-free/tool": "4.1.4"
26
+ "@fe-free/icons": "4.1.6",
27
+ "@fe-free/tool": "4.1.6"
27
28
  },
28
29
  "scripts": {
29
30
  "test": "echo \"Error: no test specified\" && exit 1",
@@ -0,0 +1,22 @@
1
+ import { FileViewList } from '@fe-free/ai';
2
+ import type { Meta, StoryObj } from '@storybook/react-vite';
3
+
4
+ const meta: Meta<typeof FileViewList> = {
5
+ title: '@fe-free/ai/FileViewList',
6
+ component: FileViewList,
7
+ tags: ['autodocs'],
8
+ };
9
+
10
+ type Story = StoryObj<typeof FileViewList>;
11
+
12
+ export const Default: Story = {
13
+ args: {
14
+ urls: [
15
+ 'https://www.baidu.com/img/PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png',
16
+ 'https://minio-api-dev.pre-ai.pivotecho.cn/ai-agent/YWIW_%E5%BE%AE%E4%BF%A1%E5%9B%BE%E7%89%87_20260108121456_230_1.png',
17
+ 'https://minio-api-dev.pre-ai.pivotecho.cn/ai-agent/3Uck_%E5%8D%8E%E4%BD%8FAI%E9%A2%84%E8%AE%A2%E7%AC%AC%E4%BA%8C%E8%BD%AEPOC%E8%A6%81%E6%B1%82%282%29.pdf',
18
+ ],
19
+ },
20
+ };
21
+
22
+ export default meta;
@@ -0,0 +1,41 @@
1
+ import { FileCard } from '@fe-free/core';
2
+ import { Image } from 'antd';
3
+ import './style.scss';
4
+
5
+ function isUrl(url: string) {
6
+ return url.startsWith('http') || url.startsWith('https');
7
+ }
8
+
9
+ function FileView({ url, isImage: propsIsImage }: { url: string; isImage?: boolean }) {
10
+ const isImage = propsIsImage ?? FileCard.isImage(url);
11
+
12
+ // 判断是 url 才 decodeURIComponent
13
+ const decodedUrl = isUrl(url) ? decodeURIComponent(url) : url;
14
+ const name = decodedUrl.split('/').pop() || decodedUrl;
15
+
16
+ return (
17
+ <div className="fea-file-view">
18
+ {isImage ? (
19
+ <Image width={60} height={60} src={url} />
20
+ ) : (
21
+ <div className="flex h-[60px] w-[250px] items-center rounded bg-01 px-1">
22
+ <div className="min-w-0">
23
+ <FileCard name={name} />
24
+ </div>
25
+ </div>
26
+ )}
27
+ </div>
28
+ );
29
+ }
30
+
31
+ function FileViewList({ urls }: { urls: string[] }) {
32
+ return (
33
+ <div className="flex flex-wrap gap-2">
34
+ {urls.map((url) => (
35
+ <FileView key={url} url={url} />
36
+ ))}
37
+ </div>
38
+ );
39
+ }
40
+
41
+ export { FileView, FileViewList };
@@ -0,0 +1,7 @@
1
+ .fea-file-view {
2
+ .ant-image {
3
+ .ant-image-img {
4
+ object-fit: cover;
5
+ }
6
+ }
7
+ }
package/src/helper.tsx ADDED
@@ -0,0 +1,31 @@
1
+ import classNames from 'classnames';
2
+ import { range } from 'lodash-es';
3
+
4
+ function RecordLoading({
5
+ count = 4,
6
+ gap = 2,
7
+ color = 'white',
8
+ }: {
9
+ count?: number;
10
+ gap?: number;
11
+ color?: 'white' | 'primary';
12
+ }) {
13
+ return (
14
+ <div className="flex h-0 gap-[2px]" style={{ gap: `${gap}px` }}>
15
+ {range(count).map((index) => (
16
+ <div
17
+ key={index}
18
+ className={classNames('h-[4px] w-[2px]', {
19
+ 'bg-white': color === 'white',
20
+ 'bg-primary': color === 'primary',
21
+ })}
22
+ style={{
23
+ animation: `sender-rectangle-${color} infinite 1s ease-in-out ${((index * 1.5) / count) % 1}s`,
24
+ }}
25
+ />
26
+ ))}
27
+ </div>
28
+ );
29
+ }
30
+
31
+ export { RecordLoading };
package/src/index.ts CHANGED
@@ -1,3 +1,7 @@
1
+ export { FileView, FileViewList } from './files';
2
+ export { MSender } from './m_sender';
3
+ export type { MSenderProps, MSenderRef } from './m_sender';
1
4
  export { Sender } from './sender';
2
5
  export type { SenderProps, SenderRef } from './sender';
3
6
  export { Tip } from './tip';
7
+ import './style.scss';
@@ -0,0 +1,53 @@
1
+ import Icons from '@fe-free/icons';
2
+ import { Button } from 'antd';
3
+ import { useCallback, type RefObject } from 'react';
4
+ import SendIcon from '../svgs/send.svg?react';
5
+ import type { MSenderProps } from './types';
6
+
7
+ function Actions(
8
+ props: MSenderProps & {
9
+ refText: RefObject<HTMLTextAreaElement>;
10
+ },
11
+ ) {
12
+ const { refText, loading, onSubmit, value, onChange } = props;
13
+
14
+ const isLoading = loading;
15
+
16
+ const handleSubmit = useCallback(async () => {
17
+ if (isLoading) {
18
+ return;
19
+ }
20
+
21
+ const newValue = {
22
+ ...value,
23
+ text: value?.text?.trim(),
24
+ };
25
+
26
+ // 有内容才提交
27
+ if (newValue.text || (newValue.files && newValue.files.length > 0)) {
28
+ await Promise.resolve(onSubmit?.(newValue));
29
+
30
+ // reset
31
+ onChange?.({});
32
+
33
+ // focus
34
+ refText.current?.focus();
35
+ }
36
+ }, [isLoading, value, onSubmit, onChange, refText]);
37
+
38
+ return (
39
+ <div className="flex items-center gap-2">
40
+ <Button
41
+ shape="circle"
42
+ size="small"
43
+ type="primary"
44
+ icon={<Icons component={SendIcon} />}
45
+ loading={isLoading}
46
+ disabled={!value?.text}
47
+ onClick={handleSubmit}
48
+ />
49
+ </div>
50
+ );
51
+ }
52
+
53
+ export { Actions };
File without changes
@@ -0,0 +1,79 @@
1
+ import { Input } from 'antd';
2
+ import classNames from 'classnames';
3
+ import { useMemo, useRef, useState, type RefObject } from 'react';
4
+ import { useTranslation } from 'react-i18next';
5
+ import { Actions } from './actions';
6
+ import { InputRecordSwitch, RecordAction } from './record';
7
+ import type { MSenderProps, MSenderRef } from './types';
8
+
9
+ function Text(props: MSenderProps & { refText: RefObject<HTMLTextAreaElement> }) {
10
+ const { value, onChange, placeholder, refText } = props;
11
+
12
+ return (
13
+ <Input.TextArea
14
+ ref={refText}
15
+ value={value?.text}
16
+ onChange={(e) => {
17
+ onChange?.({ ...value, text: e.target.value });
18
+ }}
19
+ placeholder={placeholder}
20
+ autoSize={{ minRows: 1, maxRows: 3 }}
21
+ className="mb-[1px] px-2 py-0"
22
+ variant="borderless"
23
+ />
24
+ );
25
+ }
26
+
27
+ function useProps(originProps: MSenderProps) {
28
+ const { t } = useTranslation();
29
+
30
+ return useMemo(() => {
31
+ return {
32
+ ...originProps,
33
+ placeholder:
34
+ originProps.placeholder ?? t('@fe-free/ai.sender.describeYourQuestion', '描述你的问题'),
35
+ statement:
36
+ originProps.statement ??
37
+ t(
38
+ '@fe-free/ai.sender.aiGeneratedDisclaimer',
39
+ '内容由 AI 生成,无法确保信息的真实准确,仅供参考',
40
+ ),
41
+ defaultType: originProps.defaultType ?? 'input',
42
+ };
43
+ }, [originProps, t]);
44
+ }
45
+
46
+ function MSender(originProps: MSenderProps) {
47
+ const refText = useRef<HTMLTextAreaElement>(null);
48
+
49
+ const props = useProps(originProps);
50
+ const { value, statement, allowSpeech, defaultType } = props;
51
+
52
+ const [type, setType] = useState<'input' | 'record'>(defaultType);
53
+
54
+ const refContainer = useRef<HTMLDivElement>(null);
55
+
56
+ return (
57
+ <div className="fea-m-sender-wrap">
58
+ <div
59
+ ref={refContainer}
60
+ className={classNames(
61
+ 'fea-m-sender relative flex items-end rounded-full border border-01 bg-white p-2',
62
+ )}
63
+ >
64
+ {allowSpeech && !value?.text && (
65
+ <InputRecordSwitch {...props} type={type} setType={setType} />
66
+ )}
67
+ <div className="flex flex-1">
68
+ <Text {...props} refText={refText} />
69
+ </div>
70
+ <Actions {...props} refText={refText} />
71
+ {type === 'record' && <RecordAction {...props} setType={setType} />}
72
+ </div>
73
+ {statement && <div className="mt-1 text-center text-xs text-04">*{statement}</div>}
74
+ </div>
75
+ );
76
+ }
77
+
78
+ export { MSender };
79
+ export type { MSenderProps, MSenderRef };
@@ -0,0 +1,114 @@
1
+ import { MSender } from '@fe-free/ai';
2
+ import { sleep } from '@fe-free/tool';
3
+ import type { Meta, StoryObj } from '@storybook/react-vite';
4
+ import { useCallback, useState } from 'react';
5
+ import type { MSenderProps, MSenderValue } from './types';
6
+
7
+ const meta: Meta<typeof MSender> = {
8
+ title: '@fe-free/ai/MSender',
9
+ component: MSender,
10
+ tags: ['autodocs'],
11
+ };
12
+
13
+ type Story = StoryObj<typeof MSender>;
14
+
15
+ function Component(props: Omit<MSenderProps, 'value' | 'onChange' | 'onSubmit'>) {
16
+ const [v, setV] = useState<MSenderValue | undefined>(undefined);
17
+
18
+ return (
19
+ <MSender
20
+ value={v}
21
+ onChange={(v) => {
22
+ console.log('newValue', v);
23
+ setV(v);
24
+ }}
25
+ onSubmit={(value) => {
26
+ console.log('onSubmit', value);
27
+ }}
28
+ {...props}
29
+ />
30
+ );
31
+ }
32
+
33
+ export const Default: Story = {
34
+ render: (props) => <Component {...props} />,
35
+ args: {
36
+ onSubmit: (value) => {
37
+ console.log(value);
38
+ },
39
+ },
40
+ };
41
+
42
+ export const Loading: Story = {
43
+ args: {
44
+ loading: true,
45
+ onSubmit: (value) => {
46
+ console.log(value);
47
+ },
48
+ },
49
+ render: (props) => <Component {...props} />,
50
+ };
51
+
52
+ export const AllowSpeech: Story = {
53
+ render: (props) => {
54
+ // 假设是字符串,实则是 buffer
55
+ const [recordVoice, setRecordVoice] = useState<string | undefined>(undefined);
56
+
57
+ const handleSubmit = (value: MSenderValue) => {
58
+ console.log('handleSubmit', value);
59
+ };
60
+
61
+ const handleRecordStart = useCallback(async () => {
62
+ // 假设这是录音的文本
63
+ setRecordVoice('这是录音的文本');
64
+
65
+ return;
66
+ }, []);
67
+
68
+ const handleRecordEnd = useCallback(
69
+ async (isSend: boolean) => {
70
+ console.log('handleRecordEnd isSend', isSend);
71
+ if (isSend) {
72
+ await sleep(1000);
73
+ const recordResult = recordVoice;
74
+
75
+ handleSubmit({ ...(props.value || {}), text: recordResult });
76
+ alert('submit');
77
+ }
78
+ },
79
+ [props.value, recordVoice],
80
+ );
81
+
82
+ return (
83
+ <div>
84
+ <Component
85
+ {...props}
86
+ allowSpeech={{
87
+ onRecordStart: handleRecordStart,
88
+ onRecordEnd: handleRecordEnd,
89
+ }}
90
+ />
91
+
92
+ <Component
93
+ {...props}
94
+ defaultType="record"
95
+ allowSpeech={{
96
+ onRecordStart: handleRecordStart,
97
+ onRecordEnd: handleRecordEnd,
98
+ }}
99
+ />
100
+
101
+ <Component
102
+ {...props}
103
+ defaultType="record"
104
+ allowSpeech={{
105
+ onRecordStart: () => Promise.reject(new Error('no permission')),
106
+ onRecordEnd: handleRecordEnd,
107
+ }}
108
+ />
109
+ </div>
110
+ );
111
+ },
112
+ };
113
+
114
+ export default meta;
@@ -0,0 +1,196 @@
1
+ import Icons from '@fe-free/icons';
2
+ import { Button } from 'antd';
3
+ import classNames from 'classnames';
4
+ import { useCallback, useEffect, useRef, useState } from 'react';
5
+ import { RecordLoading } from '../helper';
6
+ import IconKeyboard from '../svgs/keyboard.svg?react';
7
+ import IconRecord from '../svgs/record.svg?react';
8
+ import type { MSenderProps } from './types';
9
+
10
+ function InputRecordSwitch(
11
+ props: MSenderProps & { type: 'input' | 'record'; setType: (type: 'input' | 'record') => void },
12
+ ) {
13
+ const { allowSpeech, type, setType } = props;
14
+
15
+ if (!allowSpeech) {
16
+ return null;
17
+ }
18
+
19
+ if (type === 'record') {
20
+ return (
21
+ <Button
22
+ type="text"
23
+ shape="circle"
24
+ size="small"
25
+ icon={<Icons component={IconKeyboard} className="!text-[24px]" />}
26
+ onClick={() => setType('input')}
27
+ />
28
+ );
29
+ }
30
+
31
+ return (
32
+ <Button
33
+ type="text"
34
+ shape="circle"
35
+ size="small"
36
+ icon={<Icons component={IconRecord} className="!text-[24px]" />}
37
+ onClick={() => setType('record')}
38
+ />
39
+ );
40
+ }
41
+
42
+ function RecordAction(props: MSenderProps & { setType }) {
43
+ const { allowSpeech, setType } = props;
44
+
45
+ const containerRef = useRef<HTMLDivElement>(null);
46
+ const touchStartYRef = useRef<number>(0);
47
+
48
+ const [isRecording, setIsRecording] = useState(false);
49
+
50
+ const isCancelledRef = useRef(false);
51
+ const [isCancel, setIsCancel] = useState(false);
52
+
53
+ const handleTouchStart = useCallback(
54
+ async (e: TouchEvent) => {
55
+ // 阻止默认行为,避免触发文本选择、上下文菜单等
56
+ e.preventDefault();
57
+
58
+ await allowSpeech?.onRecordStart?.();
59
+
60
+ const touch = e.touches[0];
61
+ touchStartYRef.current = touch.clientY;
62
+
63
+ isCancelledRef.current = false;
64
+ setIsCancel(false);
65
+
66
+ setIsRecording(true);
67
+ },
68
+ [allowSpeech],
69
+ );
70
+
71
+ const handleTouchMove = useCallback(
72
+ (e: TouchEvent) => {
73
+ // 阻止默认行为,避免页面滚动
74
+ e.preventDefault();
75
+
76
+ // 没有录音,不继续
77
+ if (!isRecording) {
78
+ return;
79
+ }
80
+
81
+ const touch = e.touches[0];
82
+ const deltaY = touchStartYRef.current - touch.clientY; // 向上移动为正
83
+
84
+ // 如果上移超过 50px,则判定为取消
85
+ if (deltaY > 50) {
86
+ if (!isCancelledRef.current) {
87
+ isCancelledRef.current = true;
88
+ setIsCancel(true);
89
+ }
90
+ } else {
91
+ if (isCancelledRef.current) {
92
+ isCancelledRef.current = false;
93
+ setIsCancel(false);
94
+ }
95
+ }
96
+ },
97
+ [isRecording],
98
+ );
99
+
100
+ const handleTouchEnd = useCallback(
101
+ async (e: TouchEvent) => {
102
+ e.preventDefault();
103
+
104
+ // 没有考试,不继续
105
+ if (!isRecording) {
106
+ return;
107
+ }
108
+
109
+ try {
110
+ await allowSpeech?.onRecordEnd?.(!isCancelledRef.current);
111
+ } catch (err) {
112
+ // nothing
113
+ console.error(err);
114
+ }
115
+
116
+ // 重置状态
117
+ setIsRecording(false);
118
+ setIsCancel(false);
119
+ isCancelledRef.current = false;
120
+ touchStartYRef.current = 0;
121
+ },
122
+ [allowSpeech, isRecording],
123
+ );
124
+
125
+ const handleTouchCancel = useCallback(
126
+ async (e: TouchEvent) => {
127
+ // touchcancel 在触摸被中断时触发(如系统手势、来电等)
128
+ e.preventDefault();
129
+
130
+ await allowSpeech?.onRecordEnd?.(false);
131
+
132
+ // 重置状态
133
+ setIsRecording(false);
134
+ setIsCancel(false);
135
+ isCancelledRef.current = false;
136
+ touchStartYRef.current = 0;
137
+ },
138
+ [allowSpeech],
139
+ );
140
+
141
+ // 使用原生事件监听器,设置 passive: false 以支持 preventDefault
142
+ useEffect(() => {
143
+ const container = containerRef.current;
144
+ if (!container) return;
145
+
146
+ // 使用 { passive: false } 选项,允许 preventDefault
147
+ container.addEventListener('touchstart', handleTouchStart, { passive: false });
148
+ container.addEventListener('touchmove', handleTouchMove, { passive: false });
149
+ container.addEventListener('touchend', handleTouchEnd, { passive: false });
150
+ container.addEventListener('touchcancel', handleTouchCancel, { passive: false });
151
+
152
+ return () => {
153
+ container.removeEventListener('touchstart', handleTouchStart);
154
+ container.removeEventListener('touchmove', handleTouchMove);
155
+ container.removeEventListener('touchend', handleTouchEnd);
156
+ container.removeEventListener('touchcancel', handleTouchCancel);
157
+ };
158
+ }, [handleTouchStart, handleTouchMove, handleTouchEnd, handleTouchCancel]);
159
+
160
+ return (
161
+ <div
162
+ className={classNames('absolute inset-0 flex items-center justify-center rounded-full', {
163
+ 'bg-white': !isRecording,
164
+ 'bg-red-500': isRecording && isCancel,
165
+ 'bg-primary': isRecording && !isCancel,
166
+ })}
167
+ >
168
+ {isRecording ? (
169
+ <>
170
+ <RecordLoading count={30} gap={4} />
171
+ {isCancel && <div className="absolute top-0 -mt-[2em] text-red08">松手取消</div>}
172
+ {!isCancel && (
173
+ <div className="absolute top-0 -mt-[2em] text-primary">松开发送,上移取消</div>
174
+ )}
175
+ </>
176
+ ) : (
177
+ <div>按住说话</div>
178
+ )}
179
+ <div className="absolute inset-0" ref={containerRef} />
180
+ {!isRecording && (
181
+ <Button
182
+ type="text"
183
+ shape="circle"
184
+ size="small"
185
+ icon={<Icons component={IconKeyboard} className="!text-[24px]" />}
186
+ onClick={() => {
187
+ setType('input');
188
+ }}
189
+ className="absolute left-2"
190
+ />
191
+ )}
192
+ </div>
193
+ );
194
+ }
195
+
196
+ export { InputRecordSwitch, RecordAction };
@@ -0,0 +1,31 @@
1
+ interface MSenderRef {
2
+ focus: () => void;
3
+ }
4
+
5
+ interface MSenderValue {
6
+ text?: string;
7
+ files?: string[];
8
+ }
9
+
10
+ interface MSenderProps {
11
+ value?: MSenderValue;
12
+ onChange: (value?: MSenderValue) => void;
13
+
14
+ loading?: boolean;
15
+ onSubmit: (value?: MSenderValue) => void | Promise<void>;
16
+
17
+ placeholder?: string;
18
+
19
+ /** 是否允许语音输入 */
20
+ allowSpeech?: {
21
+ /** 录音开始时回调,如果没权限,则 reject */
22
+ onRecordStart?: () => Promise<void>;
23
+ /** 录音结束时回调, isSend 为 true 则发送,否则取消 */
24
+ onRecordEnd?: (isSend: boolean) => Promise<void>;
25
+ };
26
+
27
+ defaultType: 'input' | 'record';
28
+ statement?: string | false;
29
+ }
30
+
31
+ export type { MSenderProps, MSenderRef, MSenderValue };
@@ -5,7 +5,6 @@ import { useCallback, type RefObject } from 'react';
5
5
  import SendIcon from '../svgs/send.svg?react';
6
6
  import { FileAction } from './files';
7
7
  import { RecordAction } from './record';
8
- import './style.scss';
9
8
  import type { SenderProps } from './types';
10
9
 
11
10
  function Actions(
@@ -5,6 +5,7 @@ import { App, Button, Dropdown, Input, Modal, Upload } from 'antd';
5
5
  import type { RefObject } from 'react';
6
6
  import { useState } from 'react';
7
7
  import { useTranslation } from 'react-i18next';
8
+ import { FileView } from '../files';
8
9
  import FilesIcon from '../svgs/files.svg?react';
9
10
  import type { SenderProps } from './types';
10
11
 
@@ -136,16 +137,9 @@ function UploadFileItem({ file, onDelete }: { file: UploadFile; onDelete: () =>
136
137
  return (
137
138
  <div className="group relative">
138
139
  {isImage ? (
139
- <img
140
- src={file.originFileObj && URL.createObjectURL(file.originFileObj)}
141
- className="h-[53px] w-[53px] rounded-lg border border-01 bg-01 object-cover"
142
- />
140
+ <FileView url={URL.createObjectURL(file.originFileObj!)} isImage={isImage} />
143
141
  ) : (
144
- <div className="flex h-[53px] w-[200px] items-center rounded bg-01 px-1">
145
- <div className="min-w-0">
146
- <FileCard name={file.name} size={file.size} />
147
- </div>
148
- </div>
142
+ <FileView url={file.name} />
149
143
  )}
150
144
  {!isDone && (
151
145
  <div className="absolute inset-0 flex items-center justify-center bg-01/80">
@@ -163,7 +157,7 @@ function UploadFileItem({ file, onDelete }: { file: UploadFile; onDelete: () =>
163
157
  function UrlFileItem({ url, onDelete }: { url: string; onDelete: () => void }) {
164
158
  return (
165
159
  <div className="group relative">
166
- <div className="flex h-[53px] w-[200px] items-center rounded bg-01 px-2">
160
+ <div className="flex h-[60px] w-[250px] items-center rounded bg-01 px-2">
167
161
  <div className="line-clamp-2">{url}</div>
168
162
  </div>
169
163
  <CloseOutlined
@@ -186,7 +180,7 @@ function Files(
186
180
 
187
181
  return (
188
182
  <>
189
- {fileList && fileList.length > 0 && (
183
+ {((fileList && fileList.length > 0) || (fileUrls && fileUrls.length > 0)) && (
190
184
  <div className="scrollbar-hide mb-2 flex gap-2 overflow-x-auto">
191
185
  {fileList.map((file) => (
192
186
  <UploadFileItem
@@ -1,5 +1,6 @@
1
1
  import { AudioOutlined } from '@fe-free/icons';
2
2
  import { Button } from 'antd';
3
+ import { RecordLoading } from '../helper';
3
4
  import type { SenderProps } from './types';
4
5
 
5
6
  function RecordAction(props: SenderProps) {
@@ -9,12 +10,7 @@ function RecordAction(props: SenderProps) {
9
10
  if (recording) {
10
11
  return (
11
12
  <Button type="text" shape="circle" onClick={() => onRecordingChange?.(false)}>
12
- <div className="fea-sender-spinner">
13
- <div className="fea-sender-spinner-line fea-sender-spinner-line1" />
14
- <div className="fea-sender-spinner-line fea-sender-spinner-line2" />
15
- <div className="fea-sender-spinner-line fea-sender-spinner-line3" />
16
- <div className="fea-sender-spinner-line fea-sender-spinner-line4" />
17
- </div>
13
+ <RecordLoading count={4} color="primary" />
18
14
  </Button>
19
15
  );
20
16
  }
@@ -23,53 +23,4 @@
23
23
  }
24
24
  }
25
25
  }
26
-
27
- .fea-sender-spinner {
28
- display: block;
29
- position: relative;
30
- width: 2px;
31
- height: 0;
32
- }
33
-
34
- .fea-sender-spinner .fea-sender-spinner-line {
35
- position: absolute;
36
- width: 2px;
37
- height: 4px;
38
- content: '';
39
- background-color: theme('colors.primary');
40
- }
41
-
42
- .fea-sender-spinner .fea-sender-spinner-line1 {
43
- left: -6px;
44
- animation: rectangle infinite 1s ease-in-out 0s;
45
- }
46
-
47
- .fea-sender-spinner .fea-sender-spinner-line2 {
48
- left: -2px;
49
- animation: rectangle infinite 1s ease-in-out 0.25s;
50
- }
51
-
52
- .fea-sender-spinner .fea-sender-spinner-line3 {
53
- right: -2px;
54
- animation: rectangle infinite 1s ease-in-out 0.5s;
55
- }
56
-
57
- .fea-sender-spinner .fea-sender-spinner-line4 {
58
- right: -6px;
59
- animation: rectangle infinite 1s ease-in-out 0.75s;
60
- }
61
-
62
- @keyframes rectangle {
63
- 0%,
64
- 80%,
65
- 100% {
66
- height: 4px;
67
- box-shadow: 0 0 theme('colors.primary');
68
- }
69
-
70
- 40% {
71
- height: 6px;
72
- box-shadow: 0 -6px theme('colors.primary');
73
- }
74
- }
75
26
  }
package/src/style.scss ADDED
@@ -0,0 +1,27 @@
1
+ @keyframes sender-rectangle-white {
2
+ 0%,
3
+ 80%,
4
+ 100% {
5
+ height: 4px;
6
+ box-shadow: 0 0 theme('colors.white');
7
+ }
8
+
9
+ 40% {
10
+ height: 6px;
11
+ box-shadow: 0 -6px theme('colors.white');
12
+ }
13
+ }
14
+
15
+ @keyframes sender-rectangle-primary {
16
+ 0%,
17
+ 80%,
18
+ 100% {
19
+ height: 4px;
20
+ box-shadow: 0 0 theme('colors.primary');
21
+ }
22
+
23
+ 40% {
24
+ height: 6px;
25
+ box-shadow: 0 -6px theme('colors.primary');
26
+ }
27
+ }
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="20.70703125" height="20.70703125" viewBox="0 0 20.70703125 20.70703125"><g><path d="M4.66015625,7.7204833984375L4.66015625,7.1204833984375Q4.66015625,7.0712376784375,4.6697636088,7.0229382184375Q4.679370968,6.9746387584375,4.698216479,6.9291416384375Q4.717061989,6.8836445484375,4.744421437,6.8426982584375Q4.77178088,6.8017519684375,4.80660285,6.7669299984375Q4.84142482,6.7321080284375,4.88237113,6.7047485854375Q4.9233174,6.6773891374375,4.96881449,6.6585436274375Q5.01431161,6.6396981164375,5.06261107,6.6300907572375Q5.11091053,6.6204833984375,5.16015625,6.6204833984375L5.76015625,6.6204833984375Q5.80940185,6.6204833984375,5.85770135,6.6300907572375Q5.9060007500000005,6.6396981164375,5.95149775,6.6585436274375Q5.9969949499999995,6.6773891374375,6.03794125,6.7047485854375Q6.07888745,6.7321080284375,6.11370955,6.7669299984375Q6.14853145,6.8017519684375,6.17589095,6.8426982784375Q6.20325025,6.8836445484375,6.2220958500000005,6.9291416384375Q6.24094145,6.9746387584375,6.2505488499999995,7.0229382184375Q6.26015615,7.0712376784375,6.26015625,7.1204833984375L6.26015625,7.7204833984375Q6.26015615,7.7697289984375,6.2505488499999995,7.8180284984375Q6.24094145,7.8663278984375005,6.2220958500000005,7.9118248984375Q6.20325025,7.9573220984374995,6.17589095,7.9982683984375Q6.14853145,8.0392145984375,6.11370955,8.0740366984375Q6.07888745,8.1088585984375,6.03794125,8.1362180984375Q5.9969949499999995,8.1635773984375,5.95149795,8.1824229984375Q5.90600085,8.2012685984375,5.85770135,8.2108759984375Q5.80940185,8.2204832984375,5.76015625,8.2204833984375L5.16015625,8.2204833984375Q5.11091053,8.2204832984375,5.06261107,8.2108759984375Q5.01431161,8.2012685984375,4.96881449,8.1824229984375Q4.9233174,8.1635773984375,4.88237111,8.1362180984375Q4.84142482,8.1088585984375,4.80660285,8.0740366984375Q4.77178088,8.0392145984375,4.744421437,7.9982683984375Q4.717061989,7.9573220984374995,4.698216479,7.9118250984375Q4.679370968,7.8663279984375,4.6697636088,7.8180284984375Q4.66015625,7.7697289984375,4.66015625,7.7204833984375ZM4.66015625,10.8207640984375L4.66015625,10.2207641984375Q4.66015625,10.1715182984375,4.6697636088,10.1232187984375Q4.679370968,10.0749189984375,4.698216479,10.0294217984375Q4.717061989,9.9839245984375,4.744421437,9.9429783984375Q4.77178088,9.9020323984375,4.80660285,9.8672103984375Q4.84142482,9.8323883984375,4.88237113,9.805029398437501Q4.9233174,9.7776698984375,4.96881449,9.7588243984375Q5.01431161,9.7399787984375,5.06261107,9.7303714984375Q5.11091053,9.7207641984375,5.16015625,9.7207641984375L5.76015625,9.7207641984375Q5.80940185,9.7207641984375,5.85770135,9.7303714984375Q5.9060007500000005,9.7399787984375,5.95149775,9.7588240984375Q5.9969949499999995,9.7776696984375,6.03794125,9.8050291984375Q6.07888745,9.8323883984375,6.11370955,9.8672103984375Q6.14853145,9.9020323984375,6.17589095,9.9429783984375Q6.20325025,9.9839245984375,6.2220958500000005,10.0294217984375Q6.24094145,10.0749189984375,6.2505488499999995,10.1232184984375Q6.26015615,10.1715182984375,6.26015625,10.2207641984375L6.26015625,10.8207640984375Q6.26015615,10.8700093984375,6.2505488499999995,10.9183086984375Q6.24094145,10.9666079984375,6.2220958500000005,11.0121054984375Q6.20325025,11.0576028984375,6.17589095,11.0985488984375Q6.14853145,11.1394953984375,6.11370955,11.1743173984375Q6.07888745,11.2091388984375,6.03794125,11.2364983984375Q5.9969949499999995,11.263857798437499,5.95149795,11.282702898437499Q5.90600085,11.3015489984375,5.85770135,11.3111562984375Q5.80940185,11.3207635984375,5.76015625,11.3207640984375L5.16015625,11.3207640984375Q5.11091053,11.3207635984375,5.06261107,11.3111562984375Q5.01431161,11.3015489984375,4.96881449,11.282702898437499Q4.9233174,11.263857798437499,4.88237111,11.2364983984375Q4.84142482,11.2091388984375,4.80660285,11.1743173984375Q4.77178088,11.1394953984375,4.744421437,11.0985488984375Q4.717061989,11.0576028984375,4.698216479,11.0121054984375Q4.679370968,10.9666079984375,4.6697636088,10.9183086984375Q4.66015625,10.8700093984375,4.66015625,10.8207640984375ZM7.92285155,7.7204833984375L7.92285155,7.1204833984375Q7.92285155,7.0712376784375,7.93245885,7.0229382184375Q7.9420661500000005,6.9746387584375,7.96091175,6.9291416384375Q7.97975735,6.8836445484375,8.00711655,6.8426982584375Q8.03447585,6.8017519684375,8.06929775,6.7669299984375Q8.10411975,6.7321080284375,8.14506575,6.7047485854375Q8.186012250000001,6.6773891374375,8.23150945,6.6585436274375Q8.27700665,6.6396981164375,8.32530615,6.6300907572375Q8.37360575,6.6204833984375,8.42285155,6.6204833984375L9.022851450000001,6.6204833984375Q9.072096850000001,6.6204833984375,9.120396150000001,6.6300907572375Q9.168695450000001,6.6396981164375,9.21419285,6.6585436274375Q9.25968985,6.6773891374375,9.30063625,6.7047485854375Q9.34158225,6.7321080284375,9.376404749999999,6.7669299984375Q9.41122625,6.8017519684375,9.43858625,6.8426982784375Q9.46594525,6.8836445484375,9.48479085,6.9291416384375Q9.50363635,6.9746387584375,9.51324365,7.0229382184375Q9.522850949999999,7.0712376784375,9.522851450000001,7.1204833984375L9.522851450000001,7.7204833984375Q9.522850949999999,7.7697289984375,9.51324365,7.8180284984375Q9.50363635,7.8663278984375005,9.48479085,7.9118248984375Q9.46594525,7.9573220984374995,9.43858575,7.9982683984375Q9.41122625,8.0392145984375,9.376404749999999,8.0740366984375Q9.34158225,8.1088585984375,9.30063625,8.1362180984375Q9.25968935,8.1635773984375,9.214192350000001,8.1824229984375Q9.168695450000001,8.2012685984375,9.120396150000001,8.2108759984375Q9.072096850000001,8.2204832984375,9.022851450000001,8.2204833984375L8.42285155,8.2204833984375Q8.37360575,8.2204832984375,8.32530615,8.2108759984375Q8.27700665,8.2012685984375,8.23150925,8.1824229984375Q8.186012250000001,8.1635773984375,8.14506605,8.1362180984375Q8.10411975,8.1088585984375,8.06929775,8.0740366984375Q8.03447585,8.0392145984375,8.00711655,7.9982683984375Q7.97975705,7.9573220984374995,7.9609115500000005,7.9118250984375Q7.9420661500000005,7.8663279984375,7.93245885,7.8180284984375Q7.92285155,7.7697289984375,7.92285155,7.7204833984375ZM7.92285155,10.8207640984375L7.92285155,10.2207641984375Q7.92285155,10.1715182984375,7.93245885,10.1232187984375Q7.9420661500000005,10.0749189984375,7.96091175,10.0294217984375Q7.97975735,9.9839245984375,8.00711655,9.9429783984375Q8.03447585,9.9020323984375,8.06929775,9.8672103984375Q8.10411975,9.8323883984375,8.14506575,9.805029398437501Q8.186012250000001,9.7776698984375,8.23150945,9.7588243984375Q8.27700665,9.7399787984375,8.32530615,9.7303714984375Q8.37360575,9.7207641984375,8.42285155,9.7207641984375L9.022851450000001,9.7207641984375Q9.072096850000001,9.7207641984375,9.120396150000001,9.7303714984375Q9.168695450000001,9.7399787984375,9.21419285,9.7588240984375Q9.25968985,9.7776696984375,9.30063625,9.8050291984375Q9.34158225,9.8323883984375,9.376404749999999,9.8672103984375Q9.41122625,9.9020323984375,9.43858625,9.9429783984375Q9.46594525,9.9839245984375,9.48479085,10.0294217984375Q9.50363635,10.0749189984375,9.51324365,10.1232184984375Q9.522850949999999,10.1715182984375,9.522851450000001,10.2207641984375L9.522851450000001,10.8207640984375Q9.522850949999999,10.8700093984375,9.51324365,10.9183086984375Q9.50363635,10.9666079984375,9.48479085,11.0121054984375Q9.46594525,11.0576028984375,9.43858575,11.0985488984375Q9.41122625,11.1394953984375,9.376404749999999,11.1743173984375Q9.34158225,11.2091388984375,9.30063625,11.2364983984375Q9.25968935,11.263857798437499,9.214192350000001,11.282702898437499Q9.168695450000001,11.3015489984375,9.120396150000001,11.3111562984375Q9.072096850000001,11.3207635984375,9.022851450000001,11.3207640984375L8.42285155,11.3207640984375Q8.37360575,11.3207635984375,8.32530615,11.3111562984375Q8.27700665,11.3015489984375,8.23150925,11.282702898437499Q8.186012250000001,11.263857798437499,8.14506605,11.2364983984375Q8.10411975,11.2091388984375,8.06929775,11.1743173984375Q8.03447585,11.1394953984375,8.00711655,11.0985488984375Q7.97975705,11.0576028984375,7.9609115500000005,11.0121054984375Q7.9420661500000005,10.9666079984375,7.93245885,10.9183086984375Q7.92285155,10.8700093984375,7.92285155,10.8207640984375ZM11.18554685,7.7204833984375L11.18554685,7.1204833984375Q11.18554685,7.0712376784375,11.19515415,7.0229382184375Q11.20476155,6.9746387584375,11.22360705,6.9291416384375Q11.24245265,6.8836445484375,11.269811650000001,6.8426982584375Q11.29717115,6.8017519684375,11.331993149999999,6.7669299984375Q11.36681505,6.7321080284375,11.40776155,6.7047485854375Q11.44870755,6.6773891374375,11.49420455,6.6585436274375Q11.539701449999999,6.6396981164375,11.58800125,6.6300907572375Q11.63630105,6.6204833984375,11.68554685,6.6204833984375L12.28554675,6.6204833984375Q12.33479215,6.6204833984375,12.38309195,6.6300907572375Q12.43139075,6.6396981164375,12.47688775,6.6585436274375Q12.52238465,6.6773891374375,12.56333165,6.7047485854375Q12.60427765,6.7321080284375,12.63910005,6.7669299984375Q12.67392155,6.8017519684375,12.70128055,6.8426982784375Q12.72864055,6.8836445484375,12.74748615,6.9291416384375Q12.76633265,6.9746387584375,12.77593995,7.0229382184375Q12.78554725,7.0712376784375,12.78554725,7.1204833984375L12.78554725,7.7204833984375Q12.78554725,7.7697289984375,12.77593995,7.8180284984375Q12.76633265,7.8663278984375005,12.74748615,7.9118248984375Q12.72864055,7.9573220984374995,12.70128155,7.9982683984375Q12.67392155,8.0392145984375,12.63910005,8.0740366984375Q12.60427765,8.1088585984375,12.56333165,8.1362180984375Q12.52238465,8.1635773984375,12.47688775,8.1824229984375Q12.43139075,8.2012685984375,12.38309095,8.2108759984375Q12.33479215,8.2204832984375,12.28554675,8.2204833984375L11.68554685,8.2204833984375Q11.63630105,8.2204832984375,11.58800125,8.2108759984375Q11.539701449999999,8.2012685984375,11.49420455,8.1824229984375Q11.44870755,8.1635773984375,11.407761050000001,8.1362180984375Q11.36681505,8.1088585984375,11.331993149999999,8.0740366984375Q11.29717115,8.0392145984375,11.269811650000001,7.9982683984375Q11.24245215,7.9573220984374995,11.22360655,7.9118250984375Q11.20476155,7.8663279984375,11.19515415,7.8180284984375Q11.18554685,7.7697289984375,11.18554685,7.7204833984375ZM14.44726565,7.7204833984375L14.44726565,7.1204833984375Q14.44726565,7.0712376784375,14.45687295,7.0229382184375Q14.46648025,6.9746387584375,14.48532485,6.9291416384375Q14.50417045,6.8836445484375,14.53153035,6.8426982584375Q14.55888935,6.8017519684375,14.59371185,6.7669299984375Q14.62853435,6.7321080284375,14.66948025,6.7047485854375Q14.71042625,6.6773891374375,14.75592325,6.6585436274375Q14.80142025,6.6396981164375,14.84972025,6.6300907572375Q14.89802025,6.6204833984375,14.94726525,6.6204833984375L15.54726625,6.6204833984375Q15.59651125,6.6204833984375,15.64480925,6.6300907572375Q15.69310825,6.6396981164375,15.73860525,6.6585436274375Q15.78410325,6.6773891374375,15.82505025,6.7047485854375Q15.86599625,6.7321080284375,15.90081925,6.7669299984375Q15.93564025,6.8017519684375,15.96299925,6.8426982784375Q15.99035825,6.8836445484375,16.00920525,6.9291416384375Q16.028049250000002,6.9746387584375,16.037657250000002,7.0229382184375Q16.047265250000002,7.0712376784375,16.04726625,7.1204833984375L16.04726625,7.7204833984375Q16.047265250000002,7.7697289984375,16.037657250000002,7.8180284984375Q16.028049250000002,7.8663278984375005,16.00920525,7.9118248984375Q15.99035825,7.9573220984374995,15.96299925,7.9982683984375Q15.93564025,8.0392145984375,15.90081925,8.0740366984375Q15.86599625,8.1088585984375,15.82504925,8.1362180984375Q15.78410325,8.1635773984375,15.73860725,8.1824229984375Q15.69310925,8.2012685984375,15.64481025,8.2108759984375Q15.59651125,8.2204832984375,15.54726625,8.2204833984375L14.94726525,8.2204833984375Q14.89802025,8.2204832984375,14.84972025,8.2108759984375Q14.80142025,8.2012685984375,14.75592325,8.1824229984375Q14.71042625,8.1635773984375,14.66947925,8.1362180984375Q14.62853435,8.1088585984375,14.59371185,8.0740366984375Q14.55888935,8.0392145984375,14.53153035,7.9982683984375Q14.50417045,7.9573220984374995,14.48532485,7.9118250984375Q14.46648025,7.8663279984375,14.45687295,7.8180284984375Q14.44726565,7.7697289984375,14.44726565,7.7204833984375ZM11.18554685,10.8207640984375L11.18554685,10.2207641984375Q11.18554685,10.1715182984375,11.19515415,10.1232187984375Q11.20476155,10.0749189984375,11.22360705,10.0294217984375Q11.24245265,9.9839245984375,11.269811650000001,9.9429783984375Q11.29717115,9.9020323984375,11.331993149999999,9.8672103984375Q11.36681505,9.8323883984375,11.40776155,9.805029398437501Q11.44870755,9.7776698984375,11.49420455,9.7588243984375Q11.539701449999999,9.7399787984375,11.58800125,9.7303714984375Q11.63630105,9.7207641984375,11.68554685,9.7207641984375L12.28554675,9.7207641984375Q12.33479215,9.7207641984375,12.38309195,9.7303714984375Q12.43139075,9.7399787984375,12.47688775,9.7588240984375Q12.52238465,9.7776696984375,12.56333165,9.8050291984375Q12.60427765,9.8323883984375,12.63910005,9.8672103984375Q12.67392155,9.9020323984375,12.70128055,9.9429783984375Q12.72864055,9.9839245984375,12.74748615,10.0294217984375Q12.76633265,10.0749189984375,12.77593995,10.1232184984375Q12.78554725,10.1715182984375,12.78554725,10.2207641984375L12.78554725,10.8207640984375Q12.78554725,10.8700093984375,12.77593995,10.9183086984375Q12.76633265,10.9666079984375,12.74748615,11.0121054984375Q12.72864055,11.0576028984375,12.70128155,11.0985488984375Q12.67392155,11.1394953984375,12.63910005,11.1743173984375Q12.60427765,11.2091388984375,12.56333165,11.2364983984375Q12.52238465,11.263857798437499,12.47688775,11.282702898437499Q12.43139075,11.3015489984375,12.38309095,11.3111562984375Q12.33479215,11.3207635984375,12.28554675,11.3207640984375L11.68554685,11.3207640984375Q11.63630105,11.3207635984375,11.58800125,11.3111562984375Q11.539701449999999,11.3015489984375,11.49420455,11.282702898437499Q11.44870755,11.263857798437499,11.407761050000001,11.2364983984375Q11.36681505,11.2091388984375,11.331993149999999,11.1743173984375Q11.29717115,11.1394953984375,11.269811650000001,11.0985488984375Q11.24245215,11.0576028984375,11.22360655,11.0121054984375Q11.20476155,10.9666079984375,11.19515415,10.9183086984375Q11.18554685,10.8700093984375,11.18554685,10.8207640984375ZM14.44726565,10.8207640984375L14.44726565,10.2207641984375Q14.44726565,10.1715182984375,14.45687295,10.1232187984375Q14.46648025,10.0749189984375,14.48532485,10.0294217984375Q14.50417045,9.9839245984375,14.53153035,9.9429783984375Q14.55888935,9.9020323984375,14.59371185,9.8672103984375Q14.62853435,9.8323883984375,14.66948025,9.805029398437501Q14.71042625,9.7776698984375,14.75592325,9.7588243984375Q14.80142025,9.7399787984375,14.84972025,9.7303714984375Q14.89802025,9.7207641984375,14.94726525,9.7207641984375L15.54726625,9.7207641984375Q15.59651125,9.7207641984375,15.64480925,9.7303714984375Q15.69310825,9.7399787984375,15.73860525,9.7588240984375Q15.78410325,9.7776696984375,15.82505025,9.8050291984375Q15.86599625,9.8323883984375,15.90081925,9.8672103984375Q15.93564025,9.9020323984375,15.96299925,9.9429783984375Q15.99035825,9.9839245984375,16.00920525,10.0294217984375Q16.028049250000002,10.0749189984375,16.037657250000002,10.1232184984375Q16.047265250000002,10.1715182984375,16.04726625,10.2207641984375L16.04726625,10.8207640984375Q16.047265250000002,10.8700093984375,16.037657250000002,10.9183086984375Q16.028049250000002,10.9666079984375,16.00920525,11.0121054984375Q15.99035825,11.0576028984375,15.96299925,11.0985488984375Q15.93564025,11.1394953984375,15.90081925,11.1743173984375Q15.86599625,11.2091388984375,15.82504925,11.2364983984375Q15.78410325,11.263857798437499,15.73860725,11.282702898437499Q15.69310925,11.3015489984375,15.64481025,11.3111562984375Q15.59651125,11.3207635984375,15.54726625,11.3207640984375L14.94726525,11.3207640984375Q14.89802025,11.3207635984375,14.84972025,11.3111562984375Q14.80142025,11.3015489984375,14.75592325,11.282702898437499Q14.71042625,11.263857798437499,14.66947925,11.2364983984375Q14.62853435,11.2091388984375,14.59371185,11.1743173984375Q14.55888935,11.1394953984375,14.53153035,11.0985488984375Q14.50417045,11.0576028984375,14.48532485,11.0121054984375Q14.46648025,10.9666079984375,14.45687295,10.9183086984375Q14.44726565,10.8700093984375,14.44726565,10.8207640984375Z" fill="#222222" fill-opacity="1"/></g><rect x="6.4091796875" y="13.35443115234375" width="7.887460708618164" height="1.399999976158142" rx="0.5" fill="#222222" fill-opacity="1"/><ellipse cx="10.353515625" cy="10.353515625" rx="9.228515625" ry="9.228515625" fill-opacity="0" stroke-opacity="1" stroke="#222222" fill="none" stroke-width="1.5" stroke-linecap="ROUND" stroke-linejoin="round"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="20.70703125" height="20.70703125" viewBox="0 0 20.70703125 20.70703125"><g transform="matrix(0,1,-1,0,20.70703125,-19.20703125)"><g transform="matrix(0,-1,1,0,0,39.9140625)"><ellipse cx="29.560546875" cy="29.560546875" rx="9.228515625" ry="9.228515625" fill-opacity="0" stroke-opacity="1" stroke="#222222" fill="none" stroke-width="1.5" stroke-linecap="ROUND" stroke-linejoin="round"/></g><path d="M26.49383952421875,6.692684515Q25.07902628421875,7.284493835,23.98794138421875,8.363533925L23.98804682421875,8.363640525000001Q23.881225644218752,8.469282625,23.82334768421875,8.607922825Q23.76546979421875,8.746563125,23.76546979421875,8.896799325Q23.76546979421875,8.970653025,23.77987802421875,9.043087725Q23.79428613421875,9.115522425,23.82254874421875,9.183754225Q23.85081136421875,9.251986025,23.89184236421875,9.313393325Q23.93287330421875,9.374800725,23.98509579421875,9.427023125Q24.03731831421875,9.479245625,24.09872549421875,9.520276525Q24.16013264421875,9.561307424999999,24.22836464421875,9.589570325Q24.29659664421875,9.617832925,24.36903134421875,9.632241225Q24.44146604121875,9.646649325,24.51531982421875,9.646649325Q24.66339293421875,9.646649325,24.80035138421875,9.590364025Q24.937309834218752,9.534078825,25.04259282421875,9.429958125L25.04269826421875,9.430064725Q25.92663822421875,8.555882825,27.07268192421875,8.076498525Q28.25945302421875,7.580078125,29.56052212421875,7.580078125Q30.86159232421875,7.580078125,32.04836272421875,8.076498525Q33.19440652421875,8.555882725,34.07834622421875,9.430064725L34.07845212421875,9.429957824999999Q34.183734924218754,9.534078625,34.32069392421875,9.590364025Q34.457652124218754,9.646649325,34.605724824218754,9.646649325Q34.67957882421875,9.646649325,34.75201282421875,9.632241225Q34.82444782421875,9.617832625,34.89267882421875,9.589570025Q34.96091082421875,9.561307424999999,35.02231882421875,9.520276525Q35.083725824218746,9.479245625,35.13594882421875,9.427023125Q35.18817182421875,9.374800725,35.22920182421875,9.313393625Q35.27023282421875,9.251986225,35.29849482421875,9.183754425Q35.32675882421875,9.115522425,35.341166824218746,9.043087925Q35.35557582421875,8.970653025,35.35557582421875,8.896799325Q35.35557582421875,8.746562925,35.29769782421875,8.607922525Q35.239820824218754,8.469282225,35.13299882421875,8.363640325L35.13310382421875,8.363533925Q34.04201892421875,7.284493715,32.62720582421875,6.692684515Q31.16267392421875,6.080078125,29.56052212421875,6.080078125Q27.95837112421875,6.080078125,26.49383952421875,6.692684515Z" fill-rule="evenodd" fill="#222222" fill-opacity="1"/><path d="M30.88091327109375,11.87667225Q31.48886827109375,12.13097655,31.95781417109375,12.59474595L31.95791957109375,12.594639149999999Q32.06320287109375,12.69876005,32.20016097109375,12.75504545Q32.33712007109375,12.81133075,32.48519327109375,12.81133075Q32.55904677109375,12.81133075,32.63148117109375,12.79692245Q32.70391557109375,12.78251435,32.77214767109375,12.75425175Q32.84037967109375,12.72598915,32.901786771093754,12.68495815Q32.96319437109375,12.64392725,33.01541707109375,12.59170475Q33.06763937109375,12.53948225,33.10867027109375,12.47807505Q33.14970107109375,12.41666785,33.17796377109375,12.34843575Q33.20622637109375,12.28020385,33.22063447109375,12.20776915Q33.23504307109375,12.13533445,33.23504357109375,12.06148075Q33.23504397109375,11.91124435,33.17716597109375,11.77260423Q33.11928847109375,11.63396394,33.01246647109375,11.52832204L33.01257177109375,11.52821547Q32.33648017109375,10.8595876407,31.45975537109375,10.49285811Q30.55231717109375,10.11328143,29.56019207109375,10.11328137Q28.56806807109375,10.11328131,27.66062997109375,10.49285814Q26.78390618109375,10.8595873907,26.10781443109375,11.52821541L26.10791987109375,11.52832204Q26.001098691093752,11.63396394,25.94322073109375,11.77260423Q25.88534284109375,11.91124435,25.88534284109375,12.06148075Q25.88534284109375,12.13533445,25.89975107109375,12.20776915Q25.91415918109375,12.28020385,25.94242179109375,12.34843585Q25.97068441109375,12.41666795,26.01171541109375,12.47807505Q26.05274635109375,12.53948225,26.10496884109375,12.59170475Q26.15719136109375,12.64392725,26.21859854109375,12.68495825Q26.28000569109375,12.72598915,26.34823769109375,12.75425175Q26.41646969109375,12.78251435,26.48890439109375,12.79692245Q26.56133908809375,12.81133075,26.63519287109375,12.81133075Q26.78326601109375,12.81133075,26.92022449109375,12.75504565Q27.05718294109375,12.69876025,27.16246593109375,12.59463945L27.16257131109375,12.59474595Q27.63151717109375,12.13097695,28.23947267109375,11.876672150000001Q28.86915017109375,11.61328119,29.56019207109375,11.61328131Q30.25123577109375,11.61328131,30.88091327109375,11.87667225Z" fill-rule="evenodd" fill="#222222" fill-opacity="1"/><path d="M28.2385588909375,14.4939614825C28.1481483279375,14.567035732499999,28.1570268349375,14.6987590825,28.2446259009375,14.7751811125L29.4286651609375,15.8081450125C29.5040001609375,15.8738679125,29.6162943609375,15.8738665125,29.6916277609375,15.8081418125L30.8756027609375,14.7751816525C30.963198460937498,14.6987587225,30.9720709609375,14.5670344825,30.8816618609375,14.4939614525C30.5215592609375,14.2029087725,30.0614510609375,14.0283203125,29.5601104609375,14.0283203125C29.0587695809375,14.0283203125,28.5986614809375,14.2029087725,28.2385588909375,14.4939614825Z" fill="#222222" fill-opacity="1"/></g></svg>