@lobehub/chat 1.12.12 → 1.12.14

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
@@ -2,6 +2,56 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ### [Version 1.12.14](https://github.com/lobehub/lobe-chat/compare/v1.12.13...v1.12.14)
6
+
7
+ <sup>Released on **2024-08-24**</sup>
8
+
9
+ #### 🐛 Bug Fixes
10
+
11
+ - **misc**: Fix tts file saving in server mode.
12
+
13
+ <br/>
14
+
15
+ <details>
16
+ <summary><kbd>Improvements and Fixes</kbd></summary>
17
+
18
+ #### What's fixed
19
+
20
+ - **misc**: Fix tts file saving in server mode, closes [#3585](https://github.com/lobehub/lobe-chat/issues/3585) ([ab1cb47](https://github.com/lobehub/lobe-chat/commit/ab1cb47))
21
+
22
+ </details>
23
+
24
+ <div align="right">
25
+
26
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
27
+
28
+ </div>
29
+
30
+ ### [Version 1.12.13](https://github.com/lobehub/lobe-chat/compare/v1.12.12...v1.12.13)
31
+
32
+ <sup>Released on **2024-08-24**</sup>
33
+
34
+ #### 💄 Styles
35
+
36
+ - **misc**: Update 01.AI models.
37
+
38
+ <br/>
39
+
40
+ <details>
41
+ <summary><kbd>Improvements and Fixes</kbd></summary>
42
+
43
+ #### Styles
44
+
45
+ - **misc**: Update 01.AI models, closes [#3586](https://github.com/lobehub/lobe-chat/issues/3586) ([c4a7f70](https://github.com/lobehub/lobe-chat/commit/c4a7f70))
46
+
47
+ </details>
48
+
49
+ <div align="right">
50
+
51
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
52
+
53
+ </div>
54
+
5
55
  ### [Version 1.12.12](https://github.com/lobehub/lobe-chat/compare/v1.12.11...v1.12.12)
6
56
 
7
57
  <sup>Released on **2024-08-24**</sup>
package/README.md CHANGED
@@ -268,12 +268,12 @@ Our marketplace is not just a showcase platform but also a collaborative space.
268
268
 
269
269
  | Recent Submits | Description |
270
270
  | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
271
+ | [TypeScript Solution Architect](https://chat-preview.lobehub.com/market?agent=typescript-developer)<br/><sup>By **[swarfte](https://github.com/swarfte)** on **2024-08-24**</sup> | Expert in TypeScript, Node.js, Vue.js 3, Nuxt.js 3, Express.js, React.js, and modern UI libraries.<br/>`type-script` `java-script` `web-development` `coding-standards` `best-practices` |
271
272
  | [Variable Name Conversion Expert](https://chat-preview.lobehub.com/market?agent=variable-name-conversion)<br/><sup>By **[zengyishou](https://github.com/zengyishou)** on **2024-08-21**</sup> | In software development, naming variables is a common yet relatively time-consuming task. This assistant can automatically convert Chinese variable names into English variable names that conform to camelCase, PascalCase, snake_case, kebab-case, and constant naming conventions based on specific naming rules. This not only improves code readability but also alleviates the frustration of variable naming.<br/>`software-development` `variable-naming` `chinese-to-english` `code-standards` `automatic-conversion` |
272
273
  | [Prompt Engineering Expert](https://chat-preview.lobehub.com/market?agent=ai-prompts-assistant)<br/><sup>By **[cyicz123](https://github.com/cyicz123)** on **2024-08-12**</sup> | Specializing in prompt optimization and design<br/>`prompt-engineering` `ai-interaction` `writing` `optimization` `consulting` |
273
274
  | [Commit Message Generator](https://chat-preview.lobehub.com/market?agent=commit-assistant)<br/><sup>By **[cyicz123](https://github.com/cyicz123)** on **2024-08-12**</sup> | Expert at generating precise Git commit messages<br/>`programming` `git` `commit-message` `code-review` |
274
- | [RO-SCIRAW Prompt Word Expert](https://chat-preview.lobehub.com/market?agent=rosciraw)<br/><sup>By **[kirklin](https://github.com/kirklin)** on **2024-08-06**</sup> | The RO-SCIRAW framework, created by Kirk Lin, is a methodology for prompt words that provides a new paradigm for building highly precise and efficient prompt words. Please enter the information for the persona you want to create.<br/>`prompt-word-framework` |
275
275
 
276
- > 📊 Total agents: [<kbd>**315**</kbd> ](https://github.com/lobehub/lobe-chat-agents)
276
+ > 📊 Total agents: [<kbd>**316**</kbd> ](https://github.com/lobehub/lobe-chat-agents)
277
277
 
278
278
  <!-- AGENT LIST -->
279
279
 
package/README.zh-CN.md CHANGED
@@ -256,12 +256,12 @@ LobeChat 的插件生态系统是其核心功能的重要扩展,它极大地
256
256
 
257
257
  | 最近新增 | 助手说明 |
258
258
  | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
259
+ | [TypeScript 解决方案架构师](https://chat-preview.lobehub.com/market?agent=typescript-developer)<br/><sup>By **[swarfte](https://github.com/swarfte)** on **2024-08-24**</sup> | 精通 TypeScript、Node.js、Vue.js 3、Nuxt.js 3、Express.js、React.js 和现代 UI 库。<br/>`类型脚本` `java-script` `网页开发` `编码标准` `最佳实践` |
259
260
  | [开发变量名转换专家](https://chat-preview.lobehub.com/market?agent=variable-name-conversion)<br/><sup>By **[zengyishou](https://github.com/zengyishou)** on **2024-08-21**</sup> | 在软件开发过程中,命名变量是一项常见却相对耗时的任务。本助手能够根据特定的命名规则自动将中文变量名转换为符合小驼峰、大驼峰、下划线、横线以及常量命名规范的英文变量名。这不仅提高了代码的可读性,还解决了变量命名的苦恼。<br/>`软件开发` `变量命名` `中文转英文` `代码规范` `自动转换` |
260
261
  | [提示工程专家](https://chat-preview.lobehub.com/market?agent=ai-prompts-assistant)<br/><sup>By **[cyicz123](https://github.com/cyicz123)** on **2024-08-12**</sup> | 专精 Prompt 优化与设计<br/>`提示工程` `ai交互` `写作` `优化` `咨询` |
261
262
  | [提交信息生成器](https://chat-preview.lobehub.com/market?agent=commit-assistant)<br/><sup>By **[cyicz123](https://github.com/cyicz123)** on **2024-08-12**</sup> | 擅长生成精准的 Git 提交信息<br/>`编程` `git` `提交信息` `代码审查` |
262
- | [RO-SCIRAW 提示词专家](https://chat-preview.lobehub.com/market?agent=rosciraw)<br/><sup>By **[kirklin](https://github.com/kirklin)** on **2024-08-06**</sup> | RO-SCIRAW 框架是由 Kirk Lin 开创的提示词方法论,为构建高度精确和高效的提示词提供了一个全新的范式。请输入你要创建的分身信息。<br/>`提示词框架` |
263
263
 
264
- > 📊 Total agents: [<kbd>**315**</kbd> ](https://github.com/lobehub/lobe-chat-agents)
264
+ > 📊 Total agents: [<kbd>**316**</kbd> ](https://github.com/lobehub/lobe-chat-agents)
265
265
 
266
266
  <!-- AGENT LIST -->
267
267
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/chat",
3
- "version": "1.12.12",
3
+ "version": "1.12.14",
4
4
  "description": "Lobe Chat - an open-source, high-performance chatbot framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.",
5
5
  "keywords": [
6
6
  "framework",
@@ -10,15 +10,6 @@ const ZeroOne: ModelProviderCard = {
10
10
  id: 'yi-large',
11
11
  tokens: 32_768,
12
12
  },
13
- {
14
- description:
15
- '在 yi-large 模型的基础上支持并强化了工具调用的能力,适用于各种需要搭建 agent 或 workflow 的业务场景。',
16
- displayName: 'Yi Large FC',
17
- enabled: true,
18
- functionCall: true,
19
- id: 'yi-large-fc',
20
- tokens: 32_768,
21
- },
22
13
  {
23
14
  description: '中型尺寸模型升级微调,能力均衡,性价比高。深度优化指令遵循能力。',
24
15
  displayName: 'Yi Medium',
@@ -32,6 +23,7 @@ const ZeroOne: ModelProviderCard = {
32
23
  enabled: true,
33
24
  id: 'yi-vision',
34
25
  tokens: 16_384,
26
+ vision: true,
35
27
  },
36
28
  {
37
29
  description: '200K 超长上下文窗口,提供长文本深度理解和生成能力。',
@@ -43,17 +35,26 @@ const ZeroOne: ModelProviderCard = {
43
35
  {
44
36
  description: '小而精悍,轻量极速模型。提供强化数学运算和代码编写能力。',
45
37
  displayName: 'Yi Spark',
46
- enabled: true,
47
38
  id: 'yi-spark',
48
39
  tokens: 16_384,
49
40
  },
50
41
  {
51
42
  description:
52
- '基于Yi-Large超强模型的高阶服务,结合检索与生成技术提供精准答案,支持客⼾私有知识库(请联系客服申请)。',
43
+ '基于 yi-large 超强模型的高阶服务,结合检索与生成技术提供精准答案,实时全网检索信息服务。',
53
44
  displayName: 'Yi Large RAG',
45
+ enabled: true,
54
46
  id: 'yi-large-rag',
55
47
  tokens: 16_384,
56
48
  },
49
+ {
50
+ description:
51
+ '在 yi-large 模型的基础上支持并强化了工具调用的能力,适用于各种需要搭建 agent 或 workflow 的业务场景。',
52
+ displayName: 'Yi Large FC',
53
+ enabled: true,
54
+ functionCall: true,
55
+ id: 'yi-large-fc',
56
+ tokens: 32_768,
57
+ },
57
58
  {
58
59
  description: '超高性价比、卓越性能。根据性能和推理速度、成本,进行平衡性高精度调优。',
59
60
  displayName: 'Yi Large Turbo',
@@ -62,15 +63,13 @@ const ZeroOne: ModelProviderCard = {
62
63
  tokens: 16_384,
63
64
  },
64
65
  {
65
- description: '「兼容版本模型」文本推理能力增强。',
66
+ description: '初期版本,推荐使用 yi-large(新版本)',
66
67
  displayName: 'Yi Large Preview',
67
- enabled: true,
68
68
  id: 'yi-large-preview',
69
69
  tokens: 16_384,
70
70
  },
71
71
  ],
72
72
  checkModel: 'yi-large',
73
- disableBrowserRequest: true,
74
73
  id: 'zeroone',
75
74
  name: '01.AI',
76
75
  };
@@ -248,9 +248,10 @@ describe('MessageModel', () => {
248
248
 
249
249
  // 断言结果
250
250
  expect(result[0].extra.translate).toEqual({ content: 'translated', from: 'en', to: 'zh' });
251
- // TODO: 确认是否需要包含 tts 字段
252
251
  expect(result[0].extra.tts).toEqual({
253
- // contentMd5: 'md5', file: 'f1', voice: 'voice1'
252
+ contentMd5: 'md5',
253
+ file: 'f1',
254
+ voice: 'voice1',
254
255
  });
255
256
  });
256
257
 
@@ -89,11 +89,9 @@ export class MessageModel {
89
89
  },
90
90
 
91
91
  ttsId: messageTTS.id,
92
-
93
- // TODO: 确认下如何处理 TTS 的读取
94
- // ttsContentMd5: messageTTS.contentMd5,
95
- // ttsFile: messageTTS.fileId,
96
- // ttsVoice: messageTTS.voice,
92
+ ttsContentMd5: messageTTS.contentMd5,
93
+ ttsFile: messageTTS.fileId,
94
+ ttsVoice: messageTTS.voice,
97
95
  /* eslint-enable */
98
96
  })
99
97
  .from(messages)
@@ -113,7 +111,7 @@ export class MessageModel {
113
111
 
114
112
  const messageIds = result.map((message) => message.id as string);
115
113
 
116
- if (messageIds.length === 0) return result;
114
+ if (messageIds.length === 0) return [];
117
115
 
118
116
  // 2. get relative files
119
117
  const rawRelatedFileList = await serverDB
@@ -166,14 +164,7 @@ export class MessageModel {
166
164
  .where(inArray(messageQueries.messageId, messageIds));
167
165
 
168
166
  return result.map(
169
- ({
170
- model,
171
- provider,
172
- translate,
173
- ttsId,
174
- // ttsFile, ttsId, ttsContentMd5, ttsVoice,
175
- ...item
176
- }) => {
167
+ ({ model, provider, translate, ttsId, ttsFile, ttsContentMd5, ttsVoice, ...item }) => {
177
168
  const messageQuery = messageQueriesList.find((relation) => relation.messageId === item.id);
178
169
  return {
179
170
  ...item,
@@ -185,9 +176,9 @@ export class MessageModel {
185
176
  translate,
186
177
  tts: ttsId
187
178
  ? {
188
- // contentMd5: ttsContentMd5,
189
- // file: ttsFile,
190
- // voice: ttsVoice,
179
+ contentMd5: ttsContentMd5,
180
+ file: ttsFile,
181
+ voice: ttsVoice,
191
182
  }
192
183
  : undefined,
193
184
  },
@@ -150,7 +150,7 @@ export const messageRouter = router({
150
150
  value: z
151
151
  .object({
152
152
  contentMd5: z.string().optional(),
153
- fileId: z.string().optional(),
153
+ file: z.string().optional(),
154
154
  voice: z.string().optional(),
155
155
  })
156
156
  .or(z.literal(false)),
@@ -52,38 +52,6 @@ describe('TTSFileAction', () => {
52
52
  expect(fileService.removeFile).toHaveBeenCalledWith(fileId);
53
53
  });
54
54
 
55
- // Test for uploadTTSFile
56
- it('uploadTTSFile should upload the file and return the file id', async () => {
57
- const testFile = new File(['content'], 'test.mp3', { type: 'audio/mp3' });
58
- const uploadedFileData = {
59
- id: 'new-tts-file-id',
60
- createdAt: testFile.lastModified,
61
- data: await testFile.arrayBuffer(),
62
- fileType: testFile.type,
63
- name: testFile.name,
64
- saveMode: 'local',
65
- size: testFile.size,
66
- };
67
-
68
- // Mock the fileService.uploadFile to resolve with uploadedFileData
69
- vi.spyOn(fileService, 'createFile').mockResolvedValue({ id: uploadedFileData.id, url: '' });
70
-
71
- let fileId;
72
- await act(async () => {
73
- fileId = await useStore.getState().uploadTTSFile(testFile);
74
- });
75
-
76
- expect(fileService.createFile).toHaveBeenCalledWith({
77
- createdAt: testFile.lastModified,
78
- data: await testFile.arrayBuffer(),
79
- fileType: testFile.type,
80
- name: testFile.name,
81
- saveMode: 'local',
82
- size: testFile.size,
83
- });
84
- expect(fileId).toBe(uploadedFileData.id);
85
- });
86
-
87
55
  // Test for uploadTTSByArrayBuffers
88
56
  it('uploadTTSByArrayBuffers should create a file and call uploadTTSFile', async () => {
89
57
  const messageId = 'message-id';
@@ -93,8 +61,8 @@ describe('TTSFileAction', () => {
93
61
 
94
62
  // Spy on uploadTTSFile to simulate a successful upload
95
63
  const uploadTTSFileSpy = vi
96
- .spyOn(useStore.getState(), 'uploadTTSFile')
97
- .mockResolvedValue('new-tts-file-id');
64
+ .spyOn(useStore.getState(), 'uploadWithProgress')
65
+ .mockResolvedValue({ id: 'new-tts-file-id', url: '1' });
98
66
 
99
67
  let fileId;
100
68
  await act(async () => {
@@ -1,12 +1,14 @@
1
- import useSWR, { SWRResponse } from 'swr';
1
+ import { SWRResponse } from 'swr';
2
2
  import { StateCreator } from 'zustand/vanilla';
3
3
 
4
+ import { useClientDataSWR } from '@/libs/swr';
4
5
  import { fileService } from '@/services/file';
5
- import { legacyUploadService } from '@/services/upload_legacy';
6
6
  import { FileItem } from '@/types/files';
7
7
 
8
8
  import { FileStore } from '../../store';
9
9
 
10
+ const FETCH_TTS_FILE = 'fetchTTSFile';
11
+
10
12
  export interface TTSFileAction {
11
13
  removeTTSFile: (id: string) => Promise<void>;
12
14
 
@@ -15,8 +17,6 @@ export interface TTSFileAction {
15
17
  arrayBuffers: ArrayBuffer[],
16
18
  ) => Promise<string | undefined>;
17
19
 
18
- uploadTTSFile: (file: File) => Promise<string | undefined>;
19
-
20
20
  useFetchTTSFile: (id: string | null) => SWRResponse<FileItem>;
21
21
  }
22
22
 
@@ -38,26 +38,11 @@ export const createTTSFileSlice: StateCreator<
38
38
  type: fileType,
39
39
  };
40
40
  const file = new File([blob], fileName, fileOptions);
41
- return get().uploadTTSFile(file);
42
- },
43
- uploadTTSFile: async (file) => {
44
- try {
45
- const res = await legacyUploadService.uploadFile({
46
- createdAt: file.lastModified,
47
- data: await file.arrayBuffer(),
48
- fileType: file.type,
49
- name: file.name,
50
- saveMode: 'local',
51
- size: file.size,
52
- });
53
-
54
- const data = await fileService.createFile(res);
55
-
56
- return data.id;
57
- } catch (error) {
58
- // 提示用户上传失败
59
- console.error('upload error:', error);
60
- }
41
+
42
+ const res = await get().uploadWithProgress({ file });
43
+
44
+ return res?.id;
61
45
  },
62
- useFetchTTSFile: (id) => useSWR(id, fileService.getFile),
46
+ useFetchTTSFile: (id) =>
47
+ useClientDataSWR(!!id ? [FETCH_TTS_FILE, id] : null, () => fileService.getFile(id!)),
63
48
  });
@@ -16,7 +16,7 @@ const serverFileService = new ServerService();
16
16
  interface UploadWithProgressParams {
17
17
  file: File;
18
18
  knowledgeBaseId?: string;
19
- onStatusUpdate: (
19
+ onStatusUpdate?: (
20
20
  data:
21
21
  | {
22
22
  id: string;
@@ -53,7 +53,7 @@ export const createFileUploadSlice: StateCreator<
53
53
  > = (set, get) => ({
54
54
  internal_uploadToClientDB: async ({ file, onStatusUpdate }) => {
55
55
  if (!file.type.startsWith('image')) {
56
- onStatusUpdate({ id: file.name, type: 'removeFile' });
56
+ onStatusUpdate?.({ id: file.name, type: 'removeFile' });
57
57
  message.info({
58
58
  content: t('upload.fileOnlySupportInServerMode', {
59
59
  ext: file.name.split('.').pop(),
@@ -73,7 +73,7 @@ export const createFileUploadSlice: StateCreator<
73
73
  file,
74
74
  );
75
75
 
76
- onStatusUpdate({
76
+ onStatusUpdate?.({
77
77
  id: file.name,
78
78
  type: 'updateFile',
79
79
  value: {
@@ -99,7 +99,7 @@ export const createFileUploadSlice: StateCreator<
99
99
  // 2. if file exist, just skip upload
100
100
  if (checkStatus.isExist) {
101
101
  metadata = checkStatus.metadata as FileMetadata;
102
- onStatusUpdate({
102
+ onStatusUpdate?.({
103
103
  id: file.name,
104
104
  type: 'updateFile',
105
105
  value: { status: 'processing', uploadState: { progress: 100, restTime: 0, speed: 0 } },
@@ -107,7 +107,7 @@ export const createFileUploadSlice: StateCreator<
107
107
  } else {
108
108
  // 2. if file don't exist, need upload files
109
109
  metadata = await uploadService.uploadWithProgress(file, (status, upload) => {
110
- onStatusUpdate({
110
+ onStatusUpdate?.({
111
111
  id: file.name,
112
112
  type: 'updateFile',
113
113
  value: { status: status === 'success' ? 'processing' : status, uploadState: upload },
@@ -140,7 +140,7 @@ export const createFileUploadSlice: StateCreator<
140
140
  knowledgeBaseId,
141
141
  );
142
142
 
143
- onStatusUpdate({
143
+ onStatusUpdate?.({
144
144
  id: file.name,
145
145
  type: 'updateFile',
146
146
  value: {
@@ -1,72 +0,0 @@
1
- import { beforeAll, describe, expect, it, vi } from 'vitest';
2
-
3
- import { DB_File } from '@/database/client/schemas/files';
4
- import { edgeClient } from '@/libs/trpc/client';
5
- import { API_ENDPOINTS } from '@/services/_url';
6
- import { serverConfigSelectors } from '@/store/serverConfig/selectors';
7
- import { createServerConfigStore } from '@/store/serverConfig/store';
8
-
9
- import { legacyUploadService as uploadService } from '../upload_legacy';
10
-
11
- vi.mock('@/store/serverConfig/selectors');
12
- vi.mock('@/libs/trpc/client', () => {
13
- return {
14
- edgeClient: {
15
- upload: {
16
- createS3PreSignedUrl: { mutate: vi.fn() },
17
- },
18
- },
19
- };
20
- });
21
-
22
- beforeAll(() => {
23
- createServerConfigStore();
24
- });
25
-
26
- describe('UploadService', () => {
27
- describe('uploadFile', () => {
28
- it('should upload file to server when enableServer is true', async () => {
29
- // Arrange
30
- const file: DB_File = {
31
- data: new ArrayBuffer(10),
32
- fileType: 'text/plain',
33
- metadata: {},
34
- name: 'test.txt',
35
- saveMode: 'local',
36
- size: 10,
37
- };
38
- const mockCreateS3Url = vi.fn().mockResolvedValue('https://example.com');
39
- vi.mocked(edgeClient.upload.createS3PreSignedUrl.mutate).mockImplementation(mockCreateS3Url);
40
- vi.spyOn(serverConfigSelectors, 'enableUploadFileToServer').mockReturnValue(true);
41
- global.fetch = vi.fn().mockResolvedValue({ ok: true } as Response);
42
-
43
- // Act
44
- const result = await uploadService.uploadFile(file);
45
-
46
- // Assert
47
- expect(mockCreateS3Url).toHaveBeenCalledTimes(1);
48
- expect(fetch).toHaveBeenCalledTimes(1);
49
- expect(result.url).toMatch(/\/\d+\/[a-f0-9-]+\.txt/);
50
- expect(result.saveMode).toBe('url');
51
- });
52
-
53
- it('should save file locally when enableServer is false', async () => {
54
- // Arrange
55
- const file: DB_File = {
56
- data: new ArrayBuffer(10),
57
- fileType: 'text/plain',
58
- metadata: {},
59
- name: 'test.txt',
60
- saveMode: 'local',
61
- size: 10,
62
- };
63
- vi.spyOn(serverConfigSelectors, 'enableUploadFileToServer').mockReturnValue(false);
64
-
65
- // Act
66
- const result = await uploadService.uploadFile(file);
67
-
68
- // Assert
69
- expect(result).toEqual(file);
70
- });
71
- });
72
- });
@@ -1,104 +0,0 @@
1
- import { fileEnv } from '@/config/file';
2
- import { DB_File } from '@/database/client/schemas/files';
3
- import { edgeClient } from '@/libs/trpc/client';
4
- import { API_ENDPOINTS } from '@/services/_url';
5
- import { serverConfigSelectors } from '@/store/serverConfig/selectors';
6
- import compressImage from '@/utils/compressImage';
7
- import { uuid } from '@/utils/uuid';
8
-
9
- class UploadService {
10
- async uploadFile(file: DB_File) {
11
- if (this.enableServer) {
12
- const { data, ...params } = file;
13
- const filename = `${uuid()}.${file.name.split('.').at(-1)}`;
14
-
15
- // 精确到以 h 为单位的 path
16
- const date = (Date.now() / 1000 / 60 / 60).toFixed(0);
17
- const dirname = `${fileEnv.NEXT_PUBLIC_S3_FILE_PATH}/${date}`;
18
- const pathname = `${dirname}/${filename}`;
19
-
20
- const url = await edgeClient.upload.createS3PreSignedUrl.mutate({ pathname });
21
-
22
- const res = await fetch(url, {
23
- body: data,
24
- headers: { 'Content-Type': file.fileType },
25
- method: 'PUT',
26
- });
27
-
28
- if (res.ok) {
29
- return {
30
- ...params,
31
- metadata: { date, dirname: dirname, filename: filename, path: pathname },
32
- name: file.name,
33
- saveMode: 'url',
34
- url: pathname,
35
- } as DB_File;
36
- } else {
37
- throw new Error('Upload Error');
38
- }
39
- }
40
-
41
- // 跳过图片上传测试
42
- const isTestData = file.size === 1;
43
- if (this.isImage(file.fileType) && !isTestData) {
44
- return this.uploadImageFile(file);
45
- }
46
-
47
- // save to local storage
48
- // we may want to save to a remote server later
49
- return file;
50
- }
51
-
52
- /**
53
- * @deprecated
54
- * @param url
55
- * @param file
56
- */
57
- async uploadImageByUrl(url: string, file: Pick<DB_File, 'name' | 'metadata'>) {
58
- const res = await fetch(API_ENDPOINTS.proxy, { body: url, method: 'POST' });
59
- const data = await res.arrayBuffer();
60
- const fileType = res.headers.get('content-type') || 'image/webp';
61
-
62
- return this.uploadFile({
63
- data,
64
- fileType,
65
- metadata: file.metadata,
66
- name: file.name,
67
- saveMode: 'local',
68
- size: data.byteLength,
69
- });
70
- }
71
-
72
- private isImage(fileType: string) {
73
- const imageRegex = /^image\//;
74
- return imageRegex.test(fileType);
75
- }
76
-
77
- private async uploadImageFile(file: DB_File) {
78
- // 加载图片
79
- const url = file.url || URL.createObjectURL(new Blob([file.data!]));
80
-
81
- const img = new Image();
82
- img.src = url;
83
- await (() =>
84
- new Promise((resolve) => {
85
- img.addEventListener('load', resolve);
86
- }))();
87
-
88
- // 压缩图片
89
- const base64String = compressImage({ img, type: file.fileType });
90
- const binaryString = atob(base64String.split('base64,')[1]);
91
- const uint8Array = Uint8Array.from(binaryString, (char) => char.charCodeAt(0));
92
- file.data = uint8Array.buffer;
93
-
94
- return file;
95
- }
96
-
97
- private get enableServer() {
98
- return serverConfigSelectors.enableUploadFileToServer(
99
- window.global_serverConfigStore.getState(),
100
- );
101
- }
102
- }
103
-
104
- export const legacyUploadService = new UploadService();