@fe-free/ai 6.0.11 → 6.0.12

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,14 @@
1
1
  # @fe-free/ai
2
2
 
3
+ ## 6.0.12
4
+
5
+ ### Patch Changes
6
+
7
+ - ai
8
+ - @fe-free/core@6.0.12
9
+ - @fe-free/icons@6.0.12
10
+ - @fe-free/tool@6.0.12
11
+
3
12
  ## 6.0.11
4
13
 
5
14
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fe-free/ai",
3
- "version": "6.0.11",
3
+ "version": "6.0.12",
4
4
  "description": "",
5
5
  "license": "ISC",
6
6
  "author": "",
@@ -17,7 +17,7 @@
17
17
  "lodash-es": "^4.17.21",
18
18
  "uuid": "^13.0.0",
19
19
  "zustand": "^4.5.7",
20
- "@fe-free/core": "6.0.11"
20
+ "@fe-free/core": "6.0.12"
21
21
  },
22
22
  "peerDependencies": {
23
23
  "antd": "^6.2.1",
@@ -27,8 +27,8 @@
27
27
  "i18next-icu": "^2.4.1",
28
28
  "react": "^19.2.0",
29
29
  "react-i18next": "^16.4.0",
30
- "@fe-free/icons": "6.0.11",
31
- "@fe-free/tool": "6.0.11"
30
+ "@fe-free/icons": "6.0.12",
31
+ "@fe-free/tool": "6.0.12"
32
32
  },
33
33
  "scripts": {
34
34
  "test": "echo \"Error: no test specified\" && exit 1",
@@ -3,6 +3,7 @@ import type { UploadFile } from 'antd';
3
3
  import { Button } from 'antd';
4
4
  import type { TextAreaRef } from 'antd/es/input/TextArea';
5
5
  import type { RefObject } from 'react';
6
+
6
7
  import SendIcon from '../svgs/send.svg?react';
7
8
  import { FileAction } from './files';
8
9
  import { RecordAction } from './record';
@@ -21,6 +22,7 @@ function Actions(
21
22
  },
22
23
  ) {
23
24
  const {
25
+ value,
24
26
  refUpload,
25
27
  isUploading,
26
28
  fileUrls,
@@ -36,7 +38,7 @@ function Actions(
36
38
  return (
37
39
  <div className="flex items-center gap-2">
38
40
  <div className="flex flex-1 gap-1">
39
- {allowUpload && (
41
+ {allowUpload && !allowUpload.renderUpload && (
40
42
  <FileAction
41
43
  {...props}
42
44
  refUpload={refUpload}
@@ -44,6 +46,12 @@ function Actions(
44
46
  setFileUrls={setFileUrls}
45
47
  />
46
48
  )}
49
+ {allowUpload &&
50
+ allowUpload.renderUpload &&
51
+ allowUpload.renderUpload({
52
+ value,
53
+ refUpload,
54
+ })}
47
55
  </div>
48
56
  {/* <Divider type="vertical" /> */}
49
57
  <div className="flex items-center gap-2">
@@ -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
+
8
9
  import { FileView } from '../files';
9
10
  import FilesIcon from '../svgs/files.svg?react';
10
11
  import type { SenderProps } from './types';
@@ -53,7 +54,10 @@ function FileAction(
53
54
  ],
54
55
  }}
55
56
  >
56
- <Button shape="circle" icon={<Icons component={PlusOutlined} className="!text-lg" />} />
57
+ <Button
58
+ shape="circle"
59
+ icon={<Icons component={PlusOutlined} className="h-[28px]! text-lg!" />}
60
+ />
57
61
  </Dropdown>
58
62
  {open && (
59
63
  <Modal
@@ -96,7 +100,7 @@ function FileUpload(
96
100
  },
97
101
  ) {
98
102
  const { allowUpload, refUpload, fileList, setFileList, uploadMaxCount } = props;
99
- const { uploadAction, filesMaxCount } = allowUpload || {};
103
+ const { uploadAction, filesMaxCount, accept } = allowUpload || {};
100
104
 
101
105
  const { message } = App.useApp();
102
106
  const { t } = useTranslation();
@@ -104,6 +108,7 @@ function FileUpload(
104
108
  return (
105
109
  <Upload.Dragger
106
110
  action={uploadAction}
111
+ accept={accept}
107
112
  fileList={fileList}
108
113
  multiple
109
114
  pastable
@@ -142,12 +147,12 @@ function UploadFileItem({ file, onDelete }: { file: UploadFile; onDelete: () =>
142
147
  <FileView url={file.name} />
143
148
  )}
144
149
  {!isDone && (
145
- <div className="absolute inset-0 flex items-center justify-center bg-01/80">
150
+ <div className="bg-01/80 absolute inset-0 flex items-center justify-center">
146
151
  {(file.percent ?? 0).toFixed(0)}%
147
152
  </div>
148
153
  )}
149
154
  <CloseOutlined
150
- className="absolute right-1 top-1 hidden cursor-pointer rounded-full bg-04 text-white group-hover:block"
155
+ className="bg-04 absolute top-1 right-1 hidden cursor-pointer rounded-full text-white group-hover:block"
151
156
  onClick={onDelete}
152
157
  />
153
158
  </div>
@@ -157,11 +162,11 @@ function UploadFileItem({ file, onDelete }: { file: UploadFile; onDelete: () =>
157
162
  function UrlFileItem({ url, onDelete }: { url: string; onDelete: () => void }) {
158
163
  return (
159
164
  <div className="group relative">
160
- <div className="flex h-[60px] w-[250px] items-center rounded bg-01 px-2">
165
+ <div className="bg-01 flex h-[60px] w-[250px] items-center rounded px-2">
161
166
  <div className="line-clamp-2">{url}</div>
162
167
  </div>
163
168
  <CloseOutlined
164
- className="absolute right-1 top-1 hidden cursor-pointer rounded-full bg-04 text-white group-hover:block"
169
+ className="bg-04 absolute top-1 right-1 hidden cursor-pointer rounded-full text-white group-hover:block"
165
170
  onClick={onDelete}
166
171
  />
167
172
  </div>
@@ -6,6 +6,7 @@ import classNames from 'classnames';
6
6
  import type { RefObject } from 'react';
7
7
  import { useCallback, useMemo, useRef, useState } from 'react';
8
8
  import { useTranslation } from 'react-i18next';
9
+
9
10
  import { Actions } from './actions';
10
11
  import { FileUpload, Files } from './files';
11
12
  import type { SenderProps, SenderRef } from './types';
@@ -28,7 +29,8 @@ function Text(
28
29
  // Enter: 提交
29
30
  if (e.key === 'Enter' && !e.shiftKey) {
30
31
  e.preventDefault();
31
- onSubmit?.();
32
+ // 兼容 onSubmit 返回 Promise 的情况:在 keydown 事件里不需要等待结果
33
+ void onSubmit?.();
32
34
  }
33
35
  },
34
36
  [onSubmit],
@@ -88,27 +90,27 @@ function Sender(originProps: SenderProps) {
88
90
  const [fileList, originSetFileList] = useState<UploadFile[]>([]);
89
91
 
90
92
  const handleFilesChange = useCallback(
91
- ({ fileUrls, fileList }) => {
93
+ ({ fileUrls: urls, fileList: list }) => {
92
94
  onChange?.({
93
95
  ...value,
94
- files: [...(fileList.map((file) => file.response?.data?.url) || []), ...fileUrls],
96
+ files: [...(list.map((file) => file.response?.data?.url) || []), ...urls],
95
97
  });
96
98
  },
97
99
  [value, onChange],
98
100
  );
99
101
 
100
102
  const setFileUrls = useCallback(
101
- (fileUrls: string[]) => {
102
- originSetFileUrls(fileUrls);
103
- handleFilesChange({ fileUrls, fileList });
103
+ (urls: string[]) => {
104
+ originSetFileUrls(urls);
105
+ handleFilesChange({ fileUrls: urls, fileList });
104
106
  },
105
107
  [fileList, handleFilesChange],
106
108
  );
107
109
 
108
110
  const setFileList = useCallback(
109
- (fileList: UploadFile[]) => {
110
- originSetFileList(fileList);
111
- handleFilesChange({ fileUrls, fileList });
111
+ (list: UploadFile[]) => {
112
+ originSetFileList(list);
113
+ handleFilesChange({ fileUrls, fileList: list });
112
114
  },
113
115
  [fileUrls, handleFilesChange],
114
116
  );
@@ -1,6 +1,9 @@
1
1
  import { Sender } from '@fe-free/ai';
2
+ import Icons, { PlusOutlined } from '@fe-free/icons';
2
3
  import type { Meta, StoryObj } from '@storybook/react-vite';
4
+ import { Button } from 'antd';
3
5
  import { useState } from 'react';
6
+
4
7
  import type { SenderProps, SenderValue } from './types';
5
8
 
6
9
  const meta: Meta<typeof Sender> = {
@@ -17,9 +20,9 @@ function Component(props: Omit<SenderProps, 'value' | 'onChange' | 'onSubmit'>)
17
20
  return (
18
21
  <Sender
19
22
  value={v}
20
- onChange={(v) => {
21
- console.log('newValue', v);
22
- setV(v);
23
+ onChange={(nextValue) => {
24
+ console.log('newValue', nextValue);
25
+ setV(nextValue);
23
26
  }}
24
27
  onSubmit={(value) => {
25
28
  console.log('onSubmit', value);
@@ -58,6 +61,34 @@ export const AllowUpload: Story = {
58
61
  render: (props) => <Component {...props} />,
59
62
  };
60
63
 
64
+ export const AllowUploadWithAccept: Story = {
65
+ args: {
66
+ allowUpload: {
67
+ uploadAction: '/api/ai-service/v1/file_upload/upload',
68
+ filesMaxCount: 3,
69
+ accept: 'image/*,.pdf',
70
+ },
71
+ },
72
+ render: (props) => <Component {...props} />,
73
+ };
74
+
75
+ export const RenderUpload: Story = {
76
+ args: {
77
+ allowUpload: {
78
+ uploadAction: '/api/ai-service/v1/file_upload/upload',
79
+ accept: 'image/*',
80
+ renderUpload: (props) => (
81
+ <Button
82
+ type="text"
83
+ shape="circle"
84
+ icon={<Icons component={PlusOutlined} onClick={() => props.refUpload.current?.click()} />}
85
+ />
86
+ ),
87
+ },
88
+ },
89
+ render: (props) => <Component {...props} />,
90
+ };
91
+
61
92
  export const AllowSpeech: Story = {
62
93
  render: (props) => {
63
94
  const [recording, setRecording] = useState(true);
@@ -1,3 +1,6 @@
1
+ import type { ReactNode } from 'react';
2
+ import { RefObject } from 'react';
3
+
1
4
  interface SenderRef {
2
5
  focus: () => void;
3
6
  }
@@ -19,8 +22,16 @@ interface SenderProps {
19
22
  allowUpload?: {
20
23
  /** 上传文件的接口地址,约定返回的 {data: {url: string}} */
21
24
  uploadAction?: string;
25
+ /** 限制可上传文件类型,语法同 input.accept,如 image/*,.pdf */
26
+ accept?: string;
22
27
  /** files 最大数量 */
23
28
  filesMaxCount?: number;
29
+ /** 自定义上传按钮 */
30
+ renderUpload?: (props: {
31
+ value?: SenderValue;
32
+ /** refUpload.current?.click() 触发上传 */
33
+ refUpload: RefObject<HTMLDivElement | null>;
34
+ }) => ReactNode;
24
35
  };
25
36
 
26
37
  /** 是否允许语音输入 */