@fe-free/ai 4.0.0

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 ADDED
@@ -0,0 +1,13 @@
1
+ # @fe-free/ai
2
+
3
+ ## 4.0.0
4
+
5
+ ### Minor Changes
6
+
7
+ - feat: ai
8
+
9
+ ### Patch Changes
10
+
11
+ - @fe-free/core@4.0.0
12
+ - @fe-free/icons@4.0.0
13
+ - @fe-free/tool@4.0.0
package/package.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "@fe-free/ai",
3
+ "version": "4.0.0",
4
+ "description": "",
5
+ "main": "./src/index.ts",
6
+ "author": "",
7
+ "license": "ISC",
8
+ "publishConfig": {
9
+ "access": "public",
10
+ "registry": "https://registry.npmjs.org/"
11
+ },
12
+ "dependencies": {
13
+ "ahooks": "^3.7.8",
14
+ "classnames": "^2.5.1",
15
+ "@fe-free/core": "4.0.0"
16
+ },
17
+ "peerDependencies": {
18
+ "antd": "^5.27.1",
19
+ "dayjs": "~1.11.10",
20
+ "react": "^19.2.0",
21
+ "@fe-free/icons": "4.0.0",
22
+ "@fe-free/tool": "4.0.0"
23
+ },
24
+ "scripts": {
25
+ "test": "echo \"Error: no test specified\" && exit 1"
26
+ }
27
+ }
package/src/index.ts ADDED
@@ -0,0 +1,3 @@
1
+ export { Sender } from './sender';
2
+ export type { SenderProps, SenderRef } from './sender';
3
+ export { Tip } from './tip';
@@ -0,0 +1,74 @@
1
+ import Icons from '@fe-free/icons';
2
+ import { Button, Divider } from 'antd';
3
+ import type { RefObject } from 'react';
4
+ import SendIcon from '../svgs/send.svg?react';
5
+ import { FileAction } from './files';
6
+ import { RecordAction } from './record';
7
+ import './style.scss';
8
+ import type { SenderProps } from './types';
9
+
10
+ function Actions(
11
+ props: SenderProps & {
12
+ refUpload: RefObject<HTMLDivElement>;
13
+ isUploading: boolean;
14
+ fileUrls: string[];
15
+ setFileUrls: (fileUrls: string[]) => void;
16
+ },
17
+ ) {
18
+ const {
19
+ loading,
20
+ onSubmit,
21
+ value,
22
+ refUpload,
23
+ isUploading,
24
+ fileUrls,
25
+ setFileUrls,
26
+ allowUpload,
27
+ allowSpeech,
28
+ } = props;
29
+
30
+ const isLoading = loading || isUploading;
31
+
32
+ return (
33
+ <div className="flex items-center gap-2">
34
+ <div className="flex flex-1 gap-1">
35
+ {allowUpload && (
36
+ <FileAction
37
+ {...props}
38
+ refUpload={refUpload}
39
+ fileUrls={fileUrls}
40
+ setFileUrls={setFileUrls}
41
+ />
42
+ )}
43
+ </div>
44
+ <Divider type="vertical" />
45
+ <div className="flex items-center gap-2">
46
+ {allowSpeech && <RecordAction {...props} />}
47
+ <Button
48
+ type="primary"
49
+ shape="circle"
50
+ icon={<Icons component={SendIcon} className="!text-lg" />}
51
+ loading={isLoading}
52
+ // disabled={loading}
53
+ onClick={() => {
54
+ if (isLoading) {
55
+ return;
56
+ }
57
+
58
+ const newValue = {
59
+ ...value,
60
+ text: value?.text?.trim(),
61
+ };
62
+
63
+ // 有内容才提交
64
+ if (newValue.text || (newValue.files && newValue.files.length > 0)) {
65
+ onSubmit?.(newValue);
66
+ }
67
+ }}
68
+ />
69
+ </div>
70
+ </div>
71
+ );
72
+ }
73
+
74
+ export { Actions };
@@ -0,0 +1,202 @@
1
+ import { FileCard } from '@fe-free/core';
2
+ import Icons, { CloseOutlined, LinkOutlined, PlusOutlined } from '@fe-free/icons';
3
+ import type { UploadFile } from 'antd';
4
+ import { App, Button, Dropdown, Input, Modal, Upload } from 'antd';
5
+ import type { RefObject } from 'react';
6
+ import { useState } from 'react';
7
+ import FilesIcon from '../svgs/files.svg?react';
8
+ import type { SenderProps } from './types';
9
+
10
+ function FileAction(
11
+ props: SenderProps & {
12
+ refUpload: RefObject<HTMLDivElement>;
13
+ fileUrls: string[];
14
+ setFileUrls: (fileUrls: string[]) => void;
15
+ },
16
+ ) {
17
+ const { value, refUpload, fileUrls, setFileUrls, allowUpload } = props;
18
+ const { filesMaxCount } = allowUpload || {};
19
+
20
+ const { message } = App.useApp();
21
+
22
+ const [url, setUrl] = useState<string>('');
23
+ const [open, setOpen] = useState<boolean>(false);
24
+
25
+ return (
26
+ <>
27
+ <Dropdown
28
+ trigger={['click']}
29
+ placement="topLeft"
30
+ menu={{
31
+ items: [
32
+ {
33
+ key: 'add-file',
34
+ label: '添加图片或文件',
35
+ icon: <Icons component={FilesIcon} />,
36
+ onClick: () => {
37
+ refUpload.current?.click();
38
+ },
39
+ },
40
+ {
41
+ key: 'add-file-url',
42
+ label: '添加文件URL',
43
+ icon: <Icons component={LinkOutlined} />,
44
+ onClick: () => {
45
+ setUrl('');
46
+
47
+ setOpen(true);
48
+ },
49
+ },
50
+ ],
51
+ }}
52
+ >
53
+ <Button shape="circle" icon={<Icons component={PlusOutlined} className="!text-lg" />} />
54
+ </Dropdown>
55
+ {open && (
56
+ <Modal
57
+ title="添加文件URL"
58
+ open
59
+ onCancel={() => setOpen(false)}
60
+ onOk={() => {
61
+ if (filesMaxCount && value?.files && value.files.length >= filesMaxCount) {
62
+ message.warning(`超过最大上传数量${filesMaxCount}`);
63
+ return;
64
+ }
65
+
66
+ if (url.trim()) {
67
+ setFileUrls([...fileUrls, url]);
68
+ }
69
+ setOpen(false);
70
+ }}
71
+ >
72
+ <Input.TextArea
73
+ placeholder="请输入文件URL"
74
+ value={url}
75
+ onChange={(e) => setUrl(e.target.value)}
76
+ />
77
+ </Modal>
78
+ )}
79
+ </>
80
+ );
81
+ }
82
+
83
+ function FileUpload(
84
+ props: SenderProps & {
85
+ refUpload: RefObject<HTMLDivElement>;
86
+ fileList: UploadFile[];
87
+ setFileList: (fileList: UploadFile[]) => void;
88
+ uploadMaxCount?: number;
89
+ },
90
+ ) {
91
+ const { allowUpload, refUpload, fileList, setFileList, uploadMaxCount } = props;
92
+ const { uploadAction, filesMaxCount } = allowUpload || {};
93
+
94
+ const { message } = App.useApp();
95
+
96
+ return (
97
+ <Upload.Dragger
98
+ action={uploadAction}
99
+ fileList={fileList}
100
+ multiple
101
+ pastable
102
+ maxCount={uploadMaxCount ? uploadMaxCount + 1 : undefined}
103
+ onChange={(info) => {
104
+ if (uploadMaxCount && info.fileList.length > uploadMaxCount) {
105
+ message.warning(`超过最大上传数量${filesMaxCount}`);
106
+
107
+ setFileList(info.fileList.slice(-uploadMaxCount));
108
+ return;
109
+ }
110
+
111
+ setFileList(info.fileList);
112
+ }}
113
+ >
114
+ <div ref={refUpload}>在此处拖放文件</div>
115
+ </Upload.Dragger>
116
+ );
117
+ }
118
+
119
+ function UploadFileItem({ file, onDelete }: { file: UploadFile; onDelete: () => void }) {
120
+ const isImage = FileCard.isImage(file.name);
121
+
122
+ // 先写死这样
123
+ const isDone = file.response?.data?.url;
124
+
125
+ return (
126
+ <div className="group relative">
127
+ {isImage ? (
128
+ <img
129
+ src={file.originFileObj && URL.createObjectURL(file.originFileObj)}
130
+ className="h-[53px] w-[53px] rounded-lg border border-01 bg-01 object-cover"
131
+ />
132
+ ) : (
133
+ <div className="flex h-[53px] w-[200px] items-center rounded bg-01 px-1">
134
+ <FileCard name={file.name} size={file.size} />
135
+ </div>
136
+ )}
137
+ {!isDone && (
138
+ <div className="absolute inset-0 flex items-center justify-center bg-01/80">
139
+ {(file.percent ?? 0).toFixed(0)}%
140
+ </div>
141
+ )}
142
+ <CloseOutlined
143
+ className="absolute right-1 top-1 hidden cursor-pointer rounded-full bg-04 text-white group-hover:block"
144
+ onClick={onDelete}
145
+ />
146
+ </div>
147
+ );
148
+ }
149
+
150
+ function UrlFileItem({ url, onDelete }: { url: string; onDelete: () => void }) {
151
+ return (
152
+ <div className="group relative">
153
+ <div className="flex h-[53px] w-[200px] items-center rounded bg-01 px-2">
154
+ <div className="line-clamp-2">{url}</div>
155
+ </div>
156
+ <CloseOutlined
157
+ className="absolute right-1 top-1 hidden cursor-pointer rounded-full bg-04 text-white group-hover:block"
158
+ onClick={onDelete}
159
+ />
160
+ </div>
161
+ );
162
+ }
163
+
164
+ function Files(
165
+ props: SenderProps & {
166
+ fileList: UploadFile[];
167
+ setFileList: (fileList: UploadFile[]) => void;
168
+ fileUrls: string[];
169
+ setFileUrls: (fileUrls: string[]) => void;
170
+ },
171
+ ) {
172
+ const { fileList, setFileList, fileUrls, setFileUrls } = props;
173
+
174
+ return (
175
+ <>
176
+ {fileList && fileList.length > 0 && (
177
+ <div className="scrollbar-hide mb-2 flex gap-2 overflow-x-auto">
178
+ {fileList.map((file) => (
179
+ <UploadFileItem
180
+ key={file.uid}
181
+ file={file}
182
+ onDelete={() => {
183
+ setFileList(fileList.filter((f) => f.uid !== file.uid));
184
+ }}
185
+ />
186
+ ))}
187
+ {fileUrls.map((url) => (
188
+ <UrlFileItem
189
+ key={url}
190
+ url={url}
191
+ onDelete={() => {
192
+ setFileUrls(fileUrls.filter((u) => u !== url));
193
+ }}
194
+ />
195
+ ))}
196
+ </div>
197
+ )}
198
+ </>
199
+ );
200
+ }
201
+
202
+ export { FileAction, Files, FileUpload };
File without changes
@@ -0,0 +1,135 @@
1
+ import { useDrop } from 'ahooks';
2
+ import { Input } from 'antd';
3
+ import type { UploadFile } from 'antd/lib';
4
+ import classNames from 'classnames';
5
+ import { useCallback, useMemo, useRef, useState } from 'react';
6
+ import { Actions } from './actions';
7
+ import { FileUpload, Files } from './files';
8
+ import './style.scss';
9
+ import type { SenderProps, SenderRef } from './types';
10
+
11
+ function Text(props: SenderProps) {
12
+ const { value, onChange, placeholder } = props;
13
+
14
+ return (
15
+ <Input.TextArea
16
+ value={value?.text}
17
+ onChange={(e) => {
18
+ onChange?.({ ...value, text: e.target.value });
19
+ }}
20
+ placeholder={placeholder}
21
+ autoSize={{ minRows: 2, maxRows: 8 }}
22
+ className="mb-1 px-1 py-0"
23
+ variant="borderless"
24
+ />
25
+ );
26
+ }
27
+
28
+ const defaultProps = {
29
+ placeholder: '描述你的问题',
30
+ };
31
+
32
+ function Sender(originProps: SenderProps) {
33
+ const props = useMemo(() => {
34
+ return {
35
+ ...defaultProps,
36
+ ...originProps,
37
+ };
38
+ }, [originProps]);
39
+
40
+ const { value, onChange, filesMaxCount } = props;
41
+
42
+ const refContainer = useRef<HTMLDivElement>(null);
43
+ const refUpload = useRef<HTMLDivElement>(null);
44
+ const [dragHover, setDragHover] = useState(false);
45
+
46
+ useDrop(refContainer, {
47
+ onDragEnter: () => {
48
+ setDragHover(true);
49
+ },
50
+ onDragLeave: () => {
51
+ setDragHover(false);
52
+ },
53
+ onDrop: () => {
54
+ setDragHover(false);
55
+ },
56
+ });
57
+
58
+ // 手动输入的 url file
59
+ const [fileUrls, originSetFileUrls] = useState<string[]>([]);
60
+ // 上传的 upload file
61
+ const [fileList, originSetFileList] = useState<UploadFile[]>([]);
62
+
63
+ const handleFilesChange = useCallback(
64
+ ({ fileUrls, fileList }) => {
65
+ onChange?.({
66
+ ...value,
67
+ files: [...(fileList.map((file) => file.response?.data?.url) || []), ...fileUrls],
68
+ });
69
+ },
70
+ [value, onChange],
71
+ );
72
+
73
+ const setFileUrls = useCallback(
74
+ (fileUrls: string[]) => {
75
+ originSetFileUrls(fileUrls);
76
+ handleFilesChange({ fileUrls, fileList });
77
+ },
78
+ [fileList],
79
+ );
80
+
81
+ const setFileList = useCallback(
82
+ (fileList: UploadFile[]) => {
83
+ originSetFileList(fileList);
84
+ handleFilesChange({ fileUrls, fileList });
85
+ },
86
+ [fileUrls],
87
+ );
88
+
89
+ const isUploading = useMemo(() => {
90
+ // 存在没有 url 的
91
+ return fileList.some((file) => !file.response?.data?.url);
92
+ }, [fileList]);
93
+
94
+ return (
95
+ <div className="fea-sender-wrap">
96
+ <div
97
+ ref={refContainer}
98
+ className={classNames('fea-sender relative flex flex-col rounded-lg border border-01 p-2', {
99
+ 'fea-sender-drag-hover': dragHover,
100
+ })}
101
+ >
102
+ <Files
103
+ {...props}
104
+ fileList={fileList}
105
+ setFileList={setFileList}
106
+ fileUrls={fileUrls}
107
+ setFileUrls={setFileUrls}
108
+ />
109
+ <div className="flex">
110
+ <Text {...props} />
111
+ </div>
112
+ <Actions
113
+ {...props}
114
+ refUpload={refUpload}
115
+ isUploading={isUploading}
116
+ fileUrls={fileUrls}
117
+ setFileUrls={setFileUrls}
118
+ />
119
+ <FileUpload
120
+ {...props}
121
+ refUpload={refUpload}
122
+ fileList={fileList}
123
+ setFileList={setFileList}
124
+ uploadMaxCount={filesMaxCount ? filesMaxCount - fileUrls.length : undefined}
125
+ />
126
+ </div>
127
+ <div className="mt-1 text-center text-xs text-03">
128
+ 内容由 AI 生成,无法确保信息的真实准确,仅供参考
129
+ </div>
130
+ </div>
131
+ );
132
+ }
133
+
134
+ export { Sender };
135
+ export type { SenderProps, SenderRef };
@@ -0,0 +1,32 @@
1
+ import { AudioOutlined } from '@fe-free/icons';
2
+ import { Button } from 'antd';
3
+ import type { SenderProps } from './types';
4
+
5
+ function RecordAction(props: SenderProps) {
6
+ const { allowSpeech } = props;
7
+ const { recording, onRecordingChange } = allowSpeech || {};
8
+
9
+ if (recording) {
10
+ return (
11
+ <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>
18
+ </Button>
19
+ );
20
+ }
21
+
22
+ return (
23
+ <Button
24
+ type="text"
25
+ shape="circle"
26
+ icon={<AudioOutlined className="!text-lg" />}
27
+ onClick={() => onRecordingChange?.(true)}
28
+ />
29
+ );
30
+ }
31
+
32
+ export { RecordAction };
@@ -0,0 +1,65 @@
1
+ import { Sender } from '@fe-free/ai';
2
+ import type { Meta, StoryObj } from '@storybook/react-vite';
3
+ import { useState } from 'react';
4
+ import type { SenderProps, SenderValue } from './types';
5
+
6
+ const meta: Meta<typeof Sender> = {
7
+ title: '@fe-free/ai/Sender',
8
+ component: Sender,
9
+ tags: ['autodocs'],
10
+ };
11
+
12
+ type Story = StoryObj<typeof Sender>;
13
+
14
+ function Component(props: Omit<SenderProps, 'value' | 'onChange'>) {
15
+ const [v, setV] = useState<SenderValue | undefined>(undefined);
16
+
17
+ return (
18
+ <Sender
19
+ value={v}
20
+ onChange={(v) => {
21
+ console.log('newValue', v);
22
+ setV(v);
23
+ }}
24
+ {...props}
25
+ />
26
+ );
27
+ }
28
+
29
+ export const Default: Story = {
30
+ render: (props) => <Component {...props} />,
31
+ args: {
32
+ onSubmit: (value) => {
33
+ console.log(value);
34
+ },
35
+ },
36
+ };
37
+
38
+ export const Loading: Story = {
39
+ args: {
40
+ loading: true,
41
+ onSubmit: (value) => {
42
+ console.log(value);
43
+ },
44
+ },
45
+ render: (props) => <Component {...props} />,
46
+ };
47
+
48
+ export const AllowUpload: Story = {
49
+ args: {
50
+ allowUpload: {
51
+ uploadAction: '/api/ai-service/v1/file_upload/upload',
52
+ filesMaxCount: 3,
53
+ },
54
+ },
55
+ render: (props) => <Component {...props} />,
56
+ };
57
+
58
+ export const AllowSpeech: Story = {
59
+ render: (props) => {
60
+ const [recording, setRecording] = useState(false);
61
+ return <Component {...props} allowSpeech={{ recording, onRecordingChange: setRecording }} />;
62
+ },
63
+ };
64
+
65
+ export default meta;
@@ -0,0 +1,75 @@
1
+ .fea-sender {
2
+ .ant-upload-select {
3
+ display: none !important;
4
+ }
5
+
6
+ .ant-upload-list {
7
+ display: none !important;
8
+ }
9
+
10
+ .ant-upload-wrapper {
11
+ display: none;
12
+ }
13
+
14
+ &.fea-sender-drag-hover {
15
+ .ant-upload-wrapper {
16
+ display: block;
17
+ position: absolute;
18
+ inset: 0;
19
+ z-index: 10;
20
+
21
+ .ant-upload-drag {
22
+ border-color: theme('colors.primary');
23
+ }
24
+ }
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
+ }
@@ -0,0 +1,35 @@
1
+ interface SenderRef {
2
+ focus: () => void;
3
+ }
4
+
5
+ interface SenderValue {
6
+ text?: string;
7
+ files?: string[];
8
+ }
9
+
10
+ interface SenderProps {
11
+ value?: SenderValue;
12
+ onChange: (value?: SenderValue) => void;
13
+
14
+ loading?: boolean;
15
+ onSubmit: (value?: SenderValue) => void;
16
+
17
+ placeholder?: string;
18
+
19
+ allowUpload?: {
20
+ /** 上传文件的接口地址,约定返回的 {data: {url: string}} */
21
+ uploadAction?: string;
22
+ /** files 最大数量 */
23
+ filesMaxCount?: number;
24
+ };
25
+
26
+ /** 是否允许语音输入 */
27
+ allowSpeech?: {
28
+ /** 是否正在录音 */
29
+ recording?: boolean;
30
+ /** 录音状态变化时回调 */
31
+ onRecordingChange?: (recording: boolean) => void;
32
+ };
33
+ }
34
+
35
+ export type { SenderProps, SenderRef, SenderValue };
@@ -0,0 +1 @@
1
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg"><path d="M17.3977 3.9588C15.8361 2.39727 13.3037 2.39727 11.7422 3.9588L5.03365 10.6673C2.60612 13.0952 2.60612 17.0314 5.03365 19.4592C7.46144 21.887 11.3983 21.8875 13.8262 19.4599L20.5348 12.7514C20.8472 12.439 21.3534 12.439 21.6658 12.7514C21.9781 13.0638 21.9782 13.5701 21.6658 13.8825L14.9573 20.591C11.9046 23.6435 6.95518 23.6429 3.90255 20.5903C0.850191 17.5377 0.850191 12.5889 3.90255 9.53624L10.6111 2.82771C12.7975 0.641334 16.3424 0.641334 18.5288 2.82771C20.7149 5.01409 20.7151 8.55906 18.5288 10.7454L11.8699 17.4042C10.5369 18.7372 8.37542 18.7365 7.04241 17.4035C5.70963 16.0705 5.7095 13.9096 7.04241 12.5767L13.7012 5.91785C14.0136 5.60547 14.5199 5.60557 14.8323 5.91785C15.1447 6.23027 15.1447 6.73652 14.8323 7.04894L8.1735 13.7078C7.46543 14.4159 7.46556 15.5642 8.1735 16.2724C8.88167 16.9806 10.03 16.9806 10.7381 16.2724L17.397 9.61358C18.9584 8.05211 18.959 5.52035 17.3977 3.9588Z" fill="currentColor"></path></svg>
@@ -0,0 +1 @@
1
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg" class="size-18 text-dbx-static-white"><path d="M10.6254 20.3752V6.69549L5.47304 11.8478C4.93607 12.3848 4.0647 12.3848 3.52773 11.8478C2.99076 11.3109 2.99076 10.4395 3.52773 9.90252L11.0277 2.40252L11.1322 2.30877C11.6723 1.86801 12.4695 1.89901 12.973 2.40252L20.473 9.90252L20.5668 10.007C21.0075 10.5471 20.9766 11.3443 20.473 11.8478C19.9695 12.3513 19.1723 12.3823 18.6322 11.9416L18.5277 11.8478L13.3754 6.69549V20.3752C13.3754 21.1346 12.7598 21.7502 12.0004 21.7502C11.241 21.7502 10.6254 21.1346 10.6254 20.3752Z" fill="currentColor"></path></svg>
package/src/tip.tsx ADDED
@@ -0,0 +1,5 @@
1
+ function Tip() {
2
+ return <div className="test-xs text-01">内容由 AI 生成,无法确保信息的真实准确,仅供参考</div>;
3
+ }
4
+
5
+ export { Tip };
package/types/svg.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ declare module '*.svg?react' {
2
+ import type { IconComponentProps } from '@ant-design/icons';
3
+ const content: IconComponentProps['component'];
4
+ export default content;
5
+ }