@lobehub/chat 1.36.12 → 1.36.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.36.14](https://github.com/lobehub/lobe-chat/compare/v1.36.13...v1.36.14)
6
+
7
+ <sup>Released on **2024-12-12**</sup>
8
+
9
+ #### ♻ Code Refactoring
10
+
11
+ - **misc**: Refactor database file model to remove server env.
12
+
13
+ <br/>
14
+
15
+ <details>
16
+ <summary><kbd>Improvements and Fixes</kbd></summary>
17
+
18
+ #### Code refactoring
19
+
20
+ - **misc**: Refactor database file model to remove server env, closes [#4990](https://github.com/lobehub/lobe-chat/issues/4990) ([284f790](https://github.com/lobehub/lobe-chat/commit/284f790))
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.36.13](https://github.com/lobehub/lobe-chat/compare/v1.36.12...v1.36.13)
31
+
32
+ <sup>Released on **2024-12-11**</sup>
33
+
34
+ #### 💄 Styles
35
+
36
+ - **misc**: Add Gemini 2.0 Flash Exp model.
37
+
38
+ <br/>
39
+
40
+ <details>
41
+ <summary><kbd>Improvements and Fixes</kbd></summary>
42
+
43
+ #### Styles
44
+
45
+ - **misc**: Add Gemini 2.0 Flash Exp model, closes [#4981](https://github.com/lobehub/lobe-chat/issues/4981) ([aab0c53](https://github.com/lobehub/lobe-chat/commit/aab0c53))
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.36.12](https://github.com/lobehub/lobe-chat/compare/v1.36.11...v1.36.12)
6
56
 
7
57
  <sup>Released on **2024-12-11**</sup>
package/changelog/v1.json CHANGED
@@ -1,4 +1,22 @@
1
1
  [
2
+ {
3
+ "children": {
4
+ "improvements": [
5
+ "Refactor database file model to remove server env."
6
+ ]
7
+ },
8
+ "date": "2024-12-12",
9
+ "version": "1.36.14"
10
+ },
11
+ {
12
+ "children": {
13
+ "improvements": [
14
+ "Add Gemini 2.0 Flash Exp model."
15
+ ]
16
+ },
17
+ "date": "2024-12-11",
18
+ "version": "1.36.13"
19
+ },
2
20
  {
3
21
  "children": {
4
22
  "improvements": [
@@ -81,3 +81,18 @@ Assuming the S3 service provider's domain is s3.example.net, the bucket is mybuc
81
81
  - virtual-host: `mybucket.s3.example.net/config.env`
82
82
 
83
83
  </Callout>
84
+
85
+ ### `LLM_VISION_IMAGE_USE_BASE64`
86
+
87
+ - Type: Optional
88
+ - Description: Set to `1` to use base64 encoding for image upload.
89
+ - Default: undefined
90
+ - Example: `1`
91
+
92
+ When set to `1`, LobeChat will convert images to base64 encoding before
93
+ uploading them to the LLM model. When encountering the following error,
94
+ please consider configuring this environment variable to `1`:
95
+
96
+ ```log
97
+ Route: [xai] ProviderBizError: Fetching images over plain http:// is not supported.
98
+ ```
@@ -80,3 +80,16 @@ LobeChat 支持多模态的 AI 会话,包括将图片、文件等非结构化
80
80
  - virtual-host : `mybucket.s3.example.net/config.env`
81
81
 
82
82
  </Callout>
83
+
84
+ ### `LLM_VISION_IMAGE_USE_BASE64`
85
+
86
+ - 类型:可选
87
+ - 描述:设置为 1 则使用 base64 编码上传图片
88
+ - 默认值:undefined
89
+ - 示例:`1`
90
+
91
+ 当设置为 `1` 时,LobeChat 会将图片转换为 base64 编码后上传到 LLM 模型中,当遇到如下错误时请考虑配置该环境变量为 1
92
+
93
+ ```log
94
+ Route: [xai] ProviderBizError: Fetching images over plain http:// is not supported.
95
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/chat",
3
- "version": "1.36.12",
3
+ "version": "1.36.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",
package/src/config/db.ts CHANGED
@@ -11,20 +11,20 @@ export const getServerDBConfig = () => {
11
11
  DATABASE_TEST_URL: process.env.DATABASE_TEST_URL,
12
12
  DATABASE_URL: process.env.DATABASE_URL,
13
13
 
14
- DISABLE_REMOVE_GLOBAL_FILE: process.env.DISABLE_REMOVE_GLOBAL_FILE === '1',
15
-
16
14
  KEY_VAULTS_SECRET: process.env.KEY_VAULTS_SECRET,
17
15
 
18
16
  NEXT_PUBLIC_ENABLED_SERVER_SERVICE: process.env.NEXT_PUBLIC_SERVICE_MODE === 'server',
17
+
18
+ REMOVE_GLOBAL_FILE: process.env.DISABLE_REMOVE_GLOBAL_FILE !== '0',
19
19
  },
20
20
  server: {
21
21
  DATABASE_DRIVER: z.enum(['neon', 'node']),
22
22
  DATABASE_TEST_URL: z.string().optional(),
23
23
  DATABASE_URL: z.string().optional(),
24
24
 
25
- DISABLE_REMOVE_GLOBAL_FILE: z.boolean().optional(),
26
-
27
25
  KEY_VAULTS_SECRET: z.string().optional(),
26
+
27
+ REMOVE_GLOBAL_FILE: z.boolean().optional(),
28
28
  },
29
29
  });
30
30
  };
@@ -3,6 +3,23 @@ import { ModelProviderCard } from '@/types/llm';
3
3
  // ref: https://ai.google.dev/gemini-api/docs/models/gemini
4
4
  const Google: ModelProviderCard = {
5
5
  chatModels: [
6
+ {
7
+ description:
8
+ 'Gemini 2.0 Flash Exp 是 Google 最新的实验性多模态AI模型,拥有下一代特性,卓越的速度,原生工具调用以及多模态生成。',
9
+ displayName: 'Gemini 2.0 Flash Experimental',
10
+ enabled: true,
11
+ functionCall: true,
12
+ id: 'gemini-2.0-flash-exp',
13
+ maxOutput: 8192,
14
+ pricing: {
15
+ cachedInput: 0,
16
+ input: 0,
17
+ output: 0,
18
+ },
19
+ releasedAt: '2024-12-11',
20
+ tokens: 1_048_576 + 8192,
21
+ vision: true,
22
+ },
6
23
  {
7
24
  description:
8
25
  'Gemini Exp 1206 是 Google 最新的实验性多模态AI模型,与历史版本相比有一定的质量提升。',
@@ -8,21 +8,7 @@ import { FilesTabs, SortType } from '@/types/files';
8
8
  import { files, globalFiles, knowledgeBaseFiles, knowledgeBases, users } from '../../../schemas';
9
9
  import { FileModel } from '../file';
10
10
 
11
- let serverDB = await getTestDBInstance();
12
-
13
- let DISABLE_REMOVE_GLOBAL_FILE = false;
14
-
15
- vi.mock('@/config/db', async () => ({
16
- get serverDBEnv() {
17
- return {
18
- get DISABLE_REMOVE_GLOBAL_FILE() {
19
- return DISABLE_REMOVE_GLOBAL_FILE;
20
- },
21
- DATABASE_TEST_URL: process.env.DATABASE_TEST_URL,
22
- DATABASE_DRIVER: 'node',
23
- };
24
- },
25
- }));
11
+ const serverDB = await getTestDBInstance();
26
12
 
27
13
  const userId = 'file-model-test-user-id';
28
14
  const fileModel = new FileModel(serverDB, userId);
@@ -146,7 +132,6 @@ describe('FileModel', () => {
146
132
  expect(globalFile).toBeUndefined();
147
133
  });
148
134
  it('should delete a file by id but global file not removed ', async () => {
149
- DISABLE_REMOVE_GLOBAL_FILE = true;
150
135
  await fileModel.createGlobalFile({
151
136
  hashId: '1',
152
137
  url: 'https://example.com/file1.txt',
@@ -162,7 +147,7 @@ describe('FileModel', () => {
162
147
  fileHash: '1',
163
148
  });
164
149
 
165
- await fileModel.delete(id);
150
+ await fileModel.delete(id, false);
166
151
 
167
152
  const file = await serverDB.query.files.findFirst({ where: eq(files.id, id) });
168
153
  const globalFile = await serverDB.query.globalFiles.findFirst({
@@ -171,7 +156,6 @@ describe('FileModel', () => {
171
156
 
172
157
  expect(file).toBeUndefined();
173
158
  expect(globalFile).toBeDefined();
174
- DISABLE_REMOVE_GLOBAL_FILE = false;
175
159
  });
176
160
  });
177
161
 
@@ -225,7 +209,6 @@ describe('FileModel', () => {
225
209
  expect(globalFilesResult2).toHaveLength(0);
226
210
  });
227
211
  it('should delete multiple files but not remove global files if DISABLE_REMOVE_GLOBAL_FILE=true', async () => {
228
- DISABLE_REMOVE_GLOBAL_FILE = true;
229
212
  await fileModel.createGlobalFile({
230
213
  hashId: '1',
231
214
  url: 'https://example.com/file1.txt',
@@ -260,7 +243,7 @@ describe('FileModel', () => {
260
243
 
261
244
  expect(globalFilesResult).toHaveLength(2);
262
245
 
263
- await fileModel.deleteMany([file1.id, file2.id]);
246
+ await fileModel.deleteMany([file1.id, file2.id], false);
264
247
 
265
248
  const remainingFiles = await serverDB.query.files.findMany({
266
249
  where: eq(files.userId, userId),
@@ -271,7 +254,6 @@ describe('FileModel', () => {
271
254
 
272
255
  expect(remainingFiles).toHaveLength(0);
273
256
  expect(globalFilesResult2).toHaveLength(2);
274
- DISABLE_REMOVE_GLOBAL_FILE = false;
275
257
  });
276
258
  });
277
259
 
@@ -1,5 +1,4 @@
1
- import { eq } from 'drizzle-orm';
2
- import { and, desc } from 'drizzle-orm/expressions';
1
+ import { and, desc, eq } from 'drizzle-orm/expressions';
3
2
 
4
3
  import { LobeChatDatabase } from '@/database/type';
5
4
 
@@ -1,7 +1,4 @@
1
- import { inArray } from 'drizzle-orm';
2
- import { and, desc, eq } from 'drizzle-orm/expressions';
3
-
4
- import { LobeChatDatabase } from '@/database/type';
1
+ import { and, desc, eq, inArray } from 'drizzle-orm/expressions';
5
2
 
6
3
  import {
7
4
  agents,
@@ -11,6 +8,7 @@ import {
11
8
  files,
12
9
  knowledgeBases,
13
10
  } from '@/database/schemas';
11
+ import { LobeChatDatabase } from '@/database/type';
14
12
 
15
13
  export class AgentModel {
16
14
  private userId: string;
@@ -1,5 +1,4 @@
1
- import { eq, inArray, lt } from 'drizzle-orm';
2
- import { and } from 'drizzle-orm/expressions';
1
+ import { and, eq, inArray, lt } from 'drizzle-orm/expressions';
3
2
 
4
3
  import { LobeChatDatabase } from '@/database/type';
5
4
  import {
@@ -1,5 +1,5 @@
1
- import { asc, cosineDistance, count, eq, inArray, sql } from 'drizzle-orm';
2
- import { and, desc, isNull } from 'drizzle-orm/expressions';
1
+ import { cosineDistance, count, sql } from 'drizzle-orm';
2
+ import { and, asc, desc, eq, inArray, isNull } from 'drizzle-orm/expressions';
3
3
  import { chunk } from 'lodash-es';
4
4
 
5
5
  import { LobeChatDatabase } from '@/database/type';
@@ -1,5 +1,5 @@
1
- import { count, eq } from 'drizzle-orm';
2
- import { and } from 'drizzle-orm/expressions';
1
+ import { count } from 'drizzle-orm';
2
+ import { and, eq } from 'drizzle-orm/expressions';
3
3
 
4
4
  import { LobeChatDatabase } from '@/database/type';
5
5
 
@@ -2,7 +2,6 @@ import { asc, count, eq, ilike, inArray, notExists, or, sum } from 'drizzle-orm'
2
2
  import { and, desc, like } from 'drizzle-orm/expressions';
3
3
  import type { PgTransaction } from 'drizzle-orm/pg-core';
4
4
 
5
- import { serverDBEnv } from '@/config/db';
6
5
  import { LobeChatDatabase } from '@/database/type';
7
6
  import { FilesTabs, QueryFileListParams, SortType } from '@/types/files';
8
7
 
@@ -67,7 +66,7 @@ export class FileModel {
67
66
  };
68
67
  };
69
68
 
70
- delete = async (id: string) => {
69
+ delete = async (id: string, removeGlobalFile: boolean = true) => {
71
70
  const file = await this.findById(id);
72
71
  if (!file) return;
73
72
 
@@ -89,7 +88,7 @@ export class FileModel {
89
88
 
90
89
  // delete the file from global file if it is not used by other files
91
90
  // if `DISABLE_REMOVE_GLOBAL_FILE` is true, we will not remove the global file
92
- if (fileCount === 0 && !serverDBEnv.DISABLE_REMOVE_GLOBAL_FILE) {
91
+ if (fileCount === 0 && removeGlobalFile) {
93
92
  await trx.delete(globalFiles).where(eq(globalFiles.hashId, fileHash));
94
93
 
95
94
  return file;
@@ -112,7 +111,7 @@ export class FileModel {
112
111
  return parseInt(result[0].totalSize!) || 0;
113
112
  };
114
113
 
115
- deleteMany = async (ids: string[]) => {
114
+ deleteMany = async (ids: string[], removeGlobalFile: boolean = true) => {
116
115
  const fileList = await this.findByIds(ids);
117
116
  const hashList = fileList.map((file) => file.fileHash!);
118
117
 
@@ -144,7 +143,7 @@ export class FileModel {
144
143
 
145
144
  const needToDeleteList = fileHashCounts.filter((item) => item.count === 0);
146
145
 
147
- if (needToDeleteList.length === 0 || serverDBEnv.DISABLE_REMOVE_GLOBAL_FILE) return;
146
+ if (needToDeleteList.length === 0 || !removeGlobalFile) return;
148
147
 
149
148
  // delete the file from global file if it is not used by other files
150
149
  await trx.delete(globalFiles).where(
@@ -1,5 +1,4 @@
1
- import { eq, inArray } from 'drizzle-orm';
2
- import { and, desc } from 'drizzle-orm/expressions';
1
+ import { and, desc, eq, inArray } from 'drizzle-orm/expressions';
3
2
 
4
3
  import { LobeChatDatabase } from '@/database/type';
5
4
  import { KnowledgeBaseItem } from '@/types/knowledgeBase';
@@ -1,8 +1,8 @@
1
1
  import { count } from 'drizzle-orm';
2
2
  import { and, asc, desc, eq, gte, inArray, isNull, like, lt } from 'drizzle-orm/expressions';
3
3
 
4
- import { idGenerator } from '@/database/utils/idGenerator';
5
4
  import { LobeChatDatabase } from '@/database/type';
5
+ import { idGenerator } from '@/database/utils/idGenerator';
6
6
  import { getFullFileUrl } from '@/server/utils/files';
7
7
  import {
8
8
  ChatFileItem,
@@ -1,5 +1,5 @@
1
- import { Column, asc, count, inArray, like, sql } from 'drizzle-orm';
2
- import { and, desc, eq, not, or } from 'drizzle-orm/expressions';
1
+ import { Column, count, sql } from 'drizzle-orm';
2
+ import { and, asc, desc, eq, inArray, like, not, or } from 'drizzle-orm/expressions';
3
3
 
4
4
  import { appEnv } from '@/config/app';
5
5
  import { INBOX_SESSION_ID } from '@/const/session';
@@ -1,5 +1,4 @@
1
- import { eq } from 'drizzle-orm';
2
- import { and, asc, desc } from 'drizzle-orm/expressions';
1
+ import { and, asc, desc, eq } from 'drizzle-orm/expressions';
3
2
 
4
3
  import { LobeChatDatabase } from '@/database/type';
5
4
  import { idGenerator } from '@/database/utils/idGenerator';
@@ -1,5 +1,4 @@
1
- import { eq } from 'drizzle-orm';
2
- import { and, desc } from 'drizzle-orm/expressions';
1
+ import { and, desc, eq } from 'drizzle-orm/expressions';
3
2
 
4
3
  import { LobeChatDatabase } from '@/database/type';
5
4
  import { CreateThreadParams, ThreadStatus } from '@/types/topic';
@@ -1,5 +1,5 @@
1
- import { Column, count, inArray, sql } from 'drizzle-orm';
2
- import { and, desc, eq, exists, isNull, like, or } from 'drizzle-orm/expressions';
1
+ import { Column, count, sql } from 'drizzle-orm';
2
+ import { and, desc, eq, exists, inArray, isNull, like, or } from 'drizzle-orm/expressions';
3
3
 
4
4
  import { LobeChatDatabase } from '@/database/type';
5
5
  import { idGenerator } from '@/database/utils/idGenerator';
@@ -1,5 +1,5 @@
1
1
  import { TRPCError } from '@trpc/server';
2
- import { eq } from 'drizzle-orm';
2
+ import { eq } from 'drizzle-orm/expressions';
3
3
  import { DeepPartial } from 'utility-types';
4
4
 
5
5
  import { LobeChatDatabase } from '@/database/type';
@@ -3,14 +3,15 @@ import { chunk } from 'lodash-es';
3
3
  import pMap from 'p-map';
4
4
  import { z } from 'zod';
5
5
 
6
+ import { serverDBEnv } from '@/config/db';
6
7
  import { fileEnv } from '@/config/file';
7
8
  import { DEFAULT_EMBEDDING_MODEL } from '@/const/settings';
9
+ import { NewChunkItem, NewEmbeddingsItem } from '@/database/schemas';
8
10
  import { serverDB } from '@/database/server';
9
11
  import { ASYNC_TASK_TIMEOUT, AsyncTaskModel } from '@/database/server/models/asyncTask';
10
12
  import { ChunkModel } from '@/database/server/models/chunk';
11
13
  import { EmbeddingModel } from '@/database/server/models/embedding';
12
14
  import { FileModel } from '@/database/server/models/file';
13
- import { NewChunkItem, NewEmbeddingsItem } from '@/database/schemas';
14
15
  import { ModelProvider } from '@/libs/agent-runtime';
15
16
  import { asyncAuthedProcedure, asyncRouter as router } from '@/libs/trpc/async';
16
17
  import { initAgentRuntimeWithUserPayload } from '@/server/modules/AgentRuntime';
@@ -175,7 +176,7 @@ export const fileRouter = router({
175
176
  console.error(e);
176
177
  // if file not found, delete it from db
177
178
  if ((e as any).Code === 'NoSuchKey') {
178
- await ctx.fileModel.delete(input.fileId);
179
+ await ctx.fileModel.delete(input.fileId, serverDBEnv.REMOVE_GLOBAL_FILE);
179
180
  throw new TRPCError({ code: 'BAD_REQUEST', message: 'File not found' });
180
181
  }
181
182
  }
@@ -1,6 +1,7 @@
1
1
  import { TRPCError } from '@trpc/server';
2
2
  import { z } from 'zod';
3
3
 
4
+ import { serverDBEnv } from '@/config/db';
4
5
  import { serverDB } from '@/database/server';
5
6
  import { AsyncTaskModel } from '@/database/server/models/asyncTask';
6
7
  import { ChunkModel } from '@/database/server/models/chunk';
@@ -153,7 +154,7 @@ export const fileRouter = router({
153
154
  }),
154
155
 
155
156
  removeFile: fileProcedure.input(z.object({ id: z.string() })).mutation(async ({ input, ctx }) => {
156
- const file = await ctx.fileModel.delete(input.id);
157
+ const file = await ctx.fileModel.delete(input.id, serverDBEnv.REMOVE_GLOBAL_FILE);
157
158
 
158
159
  if (!file) return;
159
160
 
@@ -184,7 +185,10 @@ export const fileRouter = router({
184
185
  removeFiles: fileProcedure
185
186
  .input(z.object({ ids: z.array(z.string()) }))
186
187
  .mutation(async ({ input, ctx }) => {
187
- const needToRemoveFileList = await ctx.fileModel.deleteMany(input.ids);
188
+ const needToRemoveFileList = await ctx.fileModel.deleteMany(
189
+ input.ids,
190
+ serverDBEnv.REMOVE_GLOBAL_FILE,
191
+ );
188
192
 
189
193
  if (!needToRemoveFileList || needToRemoveFileList.length === 0) return;
190
194