@lobehub/chat 1.115.0 → 1.116.1

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.
Files changed (115) hide show
  1. package/.cursor/rules/add-provider-doc.mdc +183 -0
  2. package/.env.example +8 -0
  3. package/.github/workflows/claude.yml +1 -1
  4. package/.github/workflows/release.yml +3 -3
  5. package/.github/workflows/test.yml +3 -7
  6. package/CHANGELOG.md +42 -0
  7. package/CLAUDE.md +6 -6
  8. package/Dockerfile +5 -1
  9. package/Dockerfile.database +5 -1
  10. package/Dockerfile.pglite +5 -1
  11. package/changelog/v1.json +14 -0
  12. package/docs/development/basic/setup-development.mdx +10 -13
  13. package/docs/development/basic/setup-development.zh-CN.mdx +9 -12
  14. package/docs/development/database-schema.dbml +44 -0
  15. package/docs/self-hosting/environment-variables/model-provider.mdx +27 -2
  16. package/docs/self-hosting/environment-variables/model-provider.zh-CN.mdx +27 -2
  17. package/docs/usage/providers/bfl.mdx +68 -0
  18. package/docs/usage/providers/bfl.zh-CN.mdx +67 -0
  19. package/locales/ar/components.json +11 -0
  20. package/locales/ar/error.json +11 -0
  21. package/locales/ar/models.json +64 -4
  22. package/locales/ar/providers.json +3 -0
  23. package/locales/bg-BG/components.json +11 -0
  24. package/locales/bg-BG/error.json +11 -0
  25. package/locales/bg-BG/models.json +64 -4
  26. package/locales/bg-BG/providers.json +3 -0
  27. package/locales/de-DE/components.json +11 -0
  28. package/locales/de-DE/error.json +11 -12
  29. package/locales/de-DE/models.json +64 -4
  30. package/locales/de-DE/providers.json +3 -0
  31. package/locales/en-US/components.json +6 -0
  32. package/locales/en-US/error.json +11 -12
  33. package/locales/en-US/models.json +64 -4
  34. package/locales/en-US/providers.json +3 -0
  35. package/locales/es-ES/components.json +11 -0
  36. package/locales/es-ES/error.json +11 -0
  37. package/locales/es-ES/models.json +64 -6
  38. package/locales/es-ES/providers.json +3 -0
  39. package/locales/fa-IR/components.json +11 -0
  40. package/locales/fa-IR/error.json +11 -0
  41. package/locales/fa-IR/models.json +64 -4
  42. package/locales/fa-IR/providers.json +3 -0
  43. package/locales/fr-FR/components.json +11 -0
  44. package/locales/fr-FR/error.json +11 -12
  45. package/locales/fr-FR/models.json +64 -4
  46. package/locales/fr-FR/providers.json +3 -0
  47. package/locales/it-IT/components.json +11 -0
  48. package/locales/it-IT/error.json +11 -0
  49. package/locales/it-IT/models.json +64 -4
  50. package/locales/it-IT/providers.json +3 -0
  51. package/locales/ja-JP/components.json +11 -0
  52. package/locales/ja-JP/error.json +11 -12
  53. package/locales/ja-JP/models.json +64 -4
  54. package/locales/ja-JP/providers.json +3 -0
  55. package/locales/ko-KR/components.json +11 -0
  56. package/locales/ko-KR/error.json +11 -12
  57. package/locales/ko-KR/models.json +64 -6
  58. package/locales/ko-KR/providers.json +3 -0
  59. package/locales/nl-NL/components.json +11 -0
  60. package/locales/nl-NL/error.json +11 -0
  61. package/locales/nl-NL/models.json +62 -4
  62. package/locales/nl-NL/providers.json +3 -0
  63. package/locales/pl-PL/components.json +11 -0
  64. package/locales/pl-PL/error.json +11 -0
  65. package/locales/pl-PL/models.json +64 -4
  66. package/locales/pl-PL/providers.json +3 -0
  67. package/locales/pt-BR/components.json +11 -0
  68. package/locales/pt-BR/error.json +11 -0
  69. package/locales/pt-BR/models.json +64 -4
  70. package/locales/pt-BR/providers.json +3 -0
  71. package/locales/ru-RU/components.json +11 -0
  72. package/locales/ru-RU/error.json +11 -0
  73. package/locales/ru-RU/models.json +64 -4
  74. package/locales/ru-RU/providers.json +3 -0
  75. package/locales/tr-TR/components.json +11 -0
  76. package/locales/tr-TR/error.json +11 -0
  77. package/locales/tr-TR/models.json +64 -4
  78. package/locales/tr-TR/providers.json +3 -0
  79. package/locales/vi-VN/components.json +11 -0
  80. package/locales/vi-VN/error.json +11 -0
  81. package/locales/vi-VN/models.json +64 -4
  82. package/locales/vi-VN/providers.json +3 -0
  83. package/locales/zh-CN/components.json +6 -0
  84. package/locales/zh-CN/error.json +11 -0
  85. package/locales/zh-CN/models.json +64 -4
  86. package/locales/zh-CN/providers.json +3 -0
  87. package/locales/zh-TW/components.json +11 -0
  88. package/locales/zh-TW/error.json +11 -12
  89. package/locales/zh-TW/models.json +64 -6
  90. package/locales/zh-TW/providers.json +3 -0
  91. package/package.json +1 -1
  92. package/packages/database/migrations/0030_add_group_chat.sql +36 -0
  93. package/packages/database/migrations/meta/0030_snapshot.json +6417 -0
  94. package/packages/database/migrations/meta/_journal.json +7 -0
  95. package/packages/database/src/core/migrations.json +19 -0
  96. package/packages/database/src/models/__tests__/topic.test.ts +3 -1
  97. package/packages/database/src/repositories/tableViewer/index.test.ts +1 -1
  98. package/packages/database/src/schemas/chatGroup.ts +98 -0
  99. package/packages/database/src/schemas/index.ts +1 -0
  100. package/packages/database/src/schemas/message.ts +4 -1
  101. package/packages/database/src/schemas/relations.ts +26 -0
  102. package/packages/database/src/schemas/topic.ts +2 -0
  103. package/packages/database/src/types/chatGroup.ts +9 -0
  104. package/packages/database/src/utils/idGenerator.ts +1 -0
  105. package/packages/model-runtime/src/google/index.ts +3 -0
  106. package/packages/model-runtime/src/qwen/createImage.test.ts +0 -19
  107. package/packages/model-runtime/src/qwen/createImage.ts +1 -27
  108. package/packages/model-runtime/src/utils/modelParse.ts +17 -8
  109. package/packages/model-runtime/src/utils/streams/google-ai.ts +26 -14
  110. package/packages/types/src/aiModel.ts +2 -1
  111. package/src/config/aiModels/google.ts +22 -1
  112. package/src/config/aiModels/qwen.ts +2 -2
  113. package/src/config/aiModels/vertexai.ts +22 -0
  114. package/src/features/FileViewer/Renderer/PDF/index.tsx +2 -2
  115. package/.cursor/rules/debug.mdc +0 -193
@@ -210,6 +210,13 @@
210
210
  "when": 1753201379817,
211
211
  "tag": "0029_add_apikey_manage",
212
212
  "breakpoints": true
213
+ },
214
+ {
215
+ "idx": 30,
216
+ "version": "7",
217
+ "when": 1756298669289,
218
+ "tag": "0030_add_group_chat",
219
+ "breakpoints": true
213
220
  }
214
221
  ],
215
222
  "version": "6"
@@ -559,5 +559,24 @@
559
559
  "bps": true,
560
560
  "folderMillis": 1753201379817,
561
561
  "hash": "fe5c0d7c2768189771c42ef93693fc1d58586b468c4bdde7fb6f2dc58cc9931c"
562
+ },
563
+ {
564
+ "sql": [
565
+ "CREATE TABLE IF NOT EXISTS \"chat_groups\" (\n\t\"id\" text PRIMARY KEY NOT NULL,\n\t\"title\" text,\n\t\"description\" text,\n\t\"config\" jsonb,\n\t\"client_id\" text,\n\t\"user_id\" text NOT NULL,\n\t\"pinned\" boolean DEFAULT false,\n\t\"accessed_at\" timestamp with time zone DEFAULT now() NOT NULL,\n\t\"created_at\" timestamp with time zone DEFAULT now() NOT NULL,\n\t\"updated_at\" timestamp with time zone DEFAULT now() NOT NULL\n);\n",
566
+ "\nCREATE TABLE IF NOT EXISTS \"chat_groups_agents\" (\n\t\"chat_group_id\" text NOT NULL,\n\t\"agent_id\" text NOT NULL,\n\t\"user_id\" text NOT NULL,\n\t\"enabled\" boolean DEFAULT true,\n\t\"order\" integer DEFAULT 0,\n\t\"role\" text DEFAULT 'participant',\n\t\"accessed_at\" timestamp with time zone DEFAULT now() NOT NULL,\n\t\"created_at\" timestamp with time zone DEFAULT now() NOT NULL,\n\t\"updated_at\" timestamp with time zone DEFAULT now() NOT NULL,\n\tCONSTRAINT \"chat_groups_agents_chat_group_id_agent_id_pk\" PRIMARY KEY(\"chat_group_id\",\"agent_id\")\n);\n",
567
+ "\nALTER TABLE \"messages\" ADD COLUMN IF NOT EXISTS \"group_id\" text;",
568
+ "\nALTER TABLE \"messages\" ADD COLUMN IF NOT EXISTS \"target_id\" text;",
569
+ "\nALTER TABLE \"topics\" ADD COLUMN IF NOT EXISTS \"group_id\" text;",
570
+ "\nALTER TABLE \"chat_groups\" ADD CONSTRAINT \"chat_groups_user_id_users_id_fk\" FOREIGN KEY (\"user_id\") REFERENCES \"public\".\"users\"(\"id\") ON DELETE cascade ON UPDATE no action;",
571
+ "\nALTER TABLE \"chat_groups_agents\" ADD CONSTRAINT \"chat_groups_agents_chat_group_id_chat_groups_id_fk\" FOREIGN KEY (\"chat_group_id\") REFERENCES \"public\".\"chat_groups\"(\"id\") ON DELETE cascade ON UPDATE no action;",
572
+ "\nALTER TABLE \"chat_groups_agents\" ADD CONSTRAINT \"chat_groups_agents_agent_id_agents_id_fk\" FOREIGN KEY (\"agent_id\") REFERENCES \"public\".\"agents\"(\"id\") ON DELETE cascade ON UPDATE no action;",
573
+ "\nALTER TABLE \"chat_groups_agents\" ADD CONSTRAINT \"chat_groups_agents_user_id_users_id_fk\" FOREIGN KEY (\"user_id\") REFERENCES \"public\".\"users\"(\"id\") ON DELETE cascade ON UPDATE no action;",
574
+ "\nCREATE UNIQUE INDEX \"chat_groups_client_id_user_id_unique\" ON \"chat_groups\" USING btree (\"client_id\",\"user_id\");",
575
+ "\nALTER TABLE \"messages\" ADD CONSTRAINT \"messages_group_id_chat_groups_id_fk\" FOREIGN KEY (\"group_id\") REFERENCES \"public\".\"chat_groups\"(\"id\") ON DELETE set null ON UPDATE no action;",
576
+ "\nALTER TABLE \"topics\" ADD CONSTRAINT \"topics_group_id_chat_groups_id_fk\" FOREIGN KEY (\"group_id\") REFERENCES \"public\".\"chat_groups\"(\"id\") ON DELETE cascade ON UPDATE no action;\n"
577
+ ],
578
+ "bps": true,
579
+ "folderMillis": 1756298669289,
580
+ "hash": "3af468ca75761ca4ca4e32ba8704728f0499aa935bfe00ae5aabfa8405a18bd4"
562
581
  }
563
582
  ]
@@ -1,8 +1,8 @@
1
1
  import { eq, inArray } from 'drizzle-orm';
2
2
  import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
3
3
 
4
- import { LobeChatDatabase } from '../../type';
5
4
  import { messages, sessions, topics, users } from '../../schemas';
5
+ import { LobeChatDatabase } from '../../type';
6
6
  import { CreateTopicParams, TopicModel } from '../topic';
7
7
  import { getTestDB } from './_util';
8
8
 
@@ -423,6 +423,7 @@ describe('TopicModel', () => {
423
423
  userId,
424
424
  historySummary: null,
425
425
  metadata: null,
426
+ groupId: null,
426
427
  clientId: null,
427
428
  createdAt: expect.any(Date),
428
429
  updatedAt: expect.any(Date),
@@ -469,6 +470,7 @@ describe('TopicModel', () => {
469
470
  title: 'New Topic',
470
471
  favorite: false,
471
472
  clientId: null,
473
+ groupId: null,
472
474
  historySummary: null,
473
475
  metadata: null,
474
476
  sessionId,
@@ -23,7 +23,7 @@ describe('TableViewerRepo', () => {
23
23
  it('should return all tables with counts', async () => {
24
24
  const result = await repo.getAllTables();
25
25
 
26
- expect(result.length).toEqual(60);
26
+ expect(result.length).toEqual(62);
27
27
  expect(result[0]).toEqual({ name: 'agents', count: 0, type: 'BASE TABLE' });
28
28
  });
29
29
 
@@ -0,0 +1,98 @@
1
+ /* eslint-disable sort-keys-fix/sort-keys-fix */
2
+ import {
3
+ boolean,
4
+ integer,
5
+ jsonb,
6
+ pgTable,
7
+ primaryKey,
8
+ text,
9
+ uniqueIndex,
10
+ varchar,
11
+ } from 'drizzle-orm/pg-core';
12
+ import { createInsertSchema } from 'drizzle-zod';
13
+
14
+ import { idGenerator } from '@/database/utils/idGenerator';
15
+ import type { ChatGroupConfig } from '@/database/types/chatGroup';
16
+
17
+ import { timestamps } from './_helpers';
18
+ import { agents } from './agent';
19
+ import { users } from './user';
20
+
21
+ /**
22
+ * Chat groups table for multi-agent conversations
23
+ * Allows multiple agents to participate in a single chat session
24
+ */
25
+ export const chatGroups = pgTable(
26
+ 'chat_groups',
27
+ {
28
+ id: text('id')
29
+ .primaryKey()
30
+ .$defaultFn(() => idGenerator('chatGroups'))
31
+ .notNull(),
32
+ title: text('title'),
33
+ description: text('description'),
34
+
35
+ /**
36
+ * Group configuration
37
+ */
38
+ config: jsonb('config').$type<ChatGroupConfig>(),
39
+
40
+ clientId: text('client_id'),
41
+
42
+ userId: text('user_id')
43
+ .references(() => users.id, { onDelete: 'cascade' })
44
+ .notNull(),
45
+
46
+ pinned: boolean('pinned').default(false),
47
+
48
+ ...timestamps,
49
+ },
50
+ (t) => [uniqueIndex('chat_groups_client_id_user_id_unique').on(t.clientId, t.userId)],
51
+ );
52
+
53
+ export const insertChatGroupSchema = createInsertSchema(chatGroups);
54
+
55
+ export type NewChatGroup = typeof chatGroups.$inferInsert;
56
+ export type ChatGroupItem = typeof chatGroups.$inferSelect;
57
+
58
+ /**
59
+ * Junction table connecting chat groups with agents
60
+ * Defines which agents participate in each group chat
61
+ */
62
+ export const chatGroupsAgents = pgTable(
63
+ 'chat_groups_agents',
64
+ {
65
+ chatGroupId: text('chat_group_id')
66
+ .references(() => chatGroups.id, { onDelete: 'cascade' })
67
+ .notNull(),
68
+ agentId: text('agent_id')
69
+ .references(() => agents.id, { onDelete: 'cascade' })
70
+ .notNull(),
71
+ userId: text('user_id')
72
+ .references(() => users.id, { onDelete: 'cascade' })
73
+ .notNull(),
74
+
75
+ /**
76
+ * Whether this agent is active in the group
77
+ */
78
+ enabled: boolean('enabled').default(true),
79
+
80
+ /**
81
+ * Display or speaking order of the agent in the group
82
+ */
83
+ order: integer('order').default(0),
84
+
85
+ /**
86
+ * Role of the agent in the group (e.g., 'moderator', 'participant')
87
+ */
88
+ role: text('role').default('participant'),
89
+
90
+ ...timestamps,
91
+ },
92
+ (t) => ({
93
+ pk: primaryKey({ columns: [t.chatGroupId, t.agentId] }),
94
+ }),
95
+ );
96
+
97
+ export type NewChatGroupAgent = typeof chatGroupsAgents.$inferInsert;
98
+ export type ChatGroupAgentItem = typeof agents.$inferInsert
@@ -2,6 +2,7 @@ export * from './agent';
2
2
  export * from './aiInfra';
3
3
  export * from './apiKey';
4
4
  export * from './asyncTask';
5
+ export * from './chatGroup';
5
6
  export * from './document';
6
7
  export * from './file';
7
8
  export * from './generation';
@@ -23,6 +23,7 @@ import { chunks, embeddings } from './rag';
23
23
  import { sessions } from './session';
24
24
  import { threads, topics } from './topic';
25
25
  import { users } from './user';
26
+ import { chatGroups } from './chatGroup';
26
27
 
27
28
  // @ts-ignore
28
29
  export const messages = pgTable(
@@ -64,7 +65,9 @@ export const messages = pgTable(
64
65
 
65
66
  // used for group chat
66
67
  agentId: text('agent_id').references(() => agents.id, { onDelete: 'set null' }),
67
-
68
+ groupId: text('group_id').references(() => chatGroups.id, { onDelete: 'set null' }),
69
+ // targetId can be an agent ID, "user", or null - no FK constraint
70
+ targetId: text('target_id'),
68
71
  ...timestamps,
69
72
  },
70
73
  (table) => ({
@@ -5,6 +5,7 @@ import { pgTable, primaryKey, text, uuid, varchar } from 'drizzle-orm/pg-core';
5
5
  import { createdAt } from './_helpers';
6
6
  import { agents, agentsFiles, agentsKnowledgeBases } from './agent';
7
7
  import { asyncTasks } from './asyncTask';
8
+ import { chatGroups, chatGroupsAgents } from './chatGroup';
8
9
  import { documentChunks, documents } from './document';
9
10
  import { files, knowledgeBases } from './file';
10
11
  import { generationBatches, generationTopics, generations } from './generation';
@@ -109,6 +110,7 @@ export const agentsRelations = relations(agents, ({ many }) => ({
109
110
  agentsToSessions: many(agentsToSessions),
110
111
  knowledgeBases: many(agentsKnowledgeBases),
111
112
  files: many(agentsFiles),
113
+ chatGroups: many(chatGroupsAgents),
112
114
  }));
113
115
 
114
116
  export const agentsToSessionsRelations = relations(agentsToSessions, ({ one }) => ({
@@ -280,3 +282,27 @@ export const generationsRelations = relations(generations, ({ one }) => ({
280
282
  references: [files.id],
281
283
  }),
282
284
  }));
285
+
286
+ // Chat Groups 相关关系定义
287
+ export const chatGroupsRelations = relations(chatGroups, ({ many, one }) => ({
288
+ user: one(users, {
289
+ fields: [chatGroups.userId],
290
+ references: [users.id],
291
+ }),
292
+ agents: many(chatGroupsAgents),
293
+ }));
294
+
295
+ export const chatGroupsAgentsRelations = relations(chatGroupsAgents, ({ one }) => ({
296
+ chatGroup: one(chatGroups, {
297
+ fields: [chatGroupsAgents.chatGroupId],
298
+ references: [chatGroups.id],
299
+ }),
300
+ agent: one(agents, {
301
+ fields: [chatGroupsAgents.agentId],
302
+ references: [agents.id],
303
+ }),
304
+ user: one(users, {
305
+ fields: [chatGroupsAgents.userId],
306
+ references: [users.id],
307
+ }),
308
+ }));
@@ -9,6 +9,7 @@ import { createdAt, timestamps, timestamptz } from './_helpers';
9
9
  import { documents } from './document';
10
10
  import { sessions } from './session';
11
11
  import { users } from './user';
12
+ import { chatGroups } from './chatGroup';
12
13
 
13
14
  export const topics = pgTable(
14
15
  'topics',
@@ -19,6 +20,7 @@ export const topics = pgTable(
19
20
  title: text('title'),
20
21
  favorite: boolean('favorite').default(false),
21
22
  sessionId: text('session_id').references(() => sessions.id, { onDelete: 'cascade' }),
23
+ groupId: text('group_id').references(() => chatGroups.id, { onDelete: 'cascade' }),
22
24
  userId: text('user_id')
23
25
  .references(() => users.id, { onDelete: 'cascade' })
24
26
  .notNull(),
@@ -0,0 +1,9 @@
1
+ export interface ChatGroupConfig {
2
+ maxResponseInRow?: number;
3
+ orchestratorModel?: string;
4
+ orchestratorProvider?: string;
5
+ responseOrder?: 'sequential' | 'natural';
6
+ responseSpeed?: 'slow' | 'medium' | 'fast';
7
+ revealDM?: boolean;
8
+ systemPrompt?: string;
9
+ }
@@ -7,6 +7,7 @@ export const createNanoId = (size = 8) =>
7
7
 
8
8
  const prefixes = {
9
9
  agents: 'agt',
10
+ chatGroups: 'cg',
10
11
  documents: 'docs',
11
12
  files: 'file',
12
13
  generationBatches: 'gb',
@@ -34,12 +34,14 @@ const modelsWithModalities = new Set([
34
34
  'gemini-2.0-flash-exp',
35
35
  'gemini-2.0-flash-exp-image-generation',
36
36
  'gemini-2.0-flash-preview-image-generation',
37
+ 'gemini-2.5-flash-image-preview',
37
38
  ]);
38
39
 
39
40
  const modelsDisableInstuction = new Set([
40
41
  'gemini-2.0-flash-exp',
41
42
  'gemini-2.0-flash-exp-image-generation',
42
43
  'gemini-2.0-flash-preview-image-generation',
44
+ 'gemini-2.5-flash-image-preview',
43
45
  'gemma-3-1b-it',
44
46
  'gemma-3-4b-it',
45
47
  'gemma-3-12b-it',
@@ -211,6 +213,7 @@ export class LobeGoogleAI implements LobeRuntimeAI {
211
213
  };
212
214
 
213
215
  const inputStartAt = Date.now();
216
+
214
217
  const geminiStreamResponse = await this.client.models.generateContentStream({
215
218
  config,
216
219
  contents,
@@ -327,25 +327,6 @@ describe('createQwenImage', () => {
327
327
  });
328
328
 
329
329
  describe('Error scenarios', () => {
330
- it('should handle unsupported model', async () => {
331
- const payload: CreateImagePayload = {
332
- model: 'unsupported-model',
333
- params: {
334
- prompt: 'Test prompt',
335
- },
336
- };
337
-
338
- await expect(createQwenImage(payload, mockOptions)).rejects.toEqual(
339
- expect.objectContaining({
340
- errorType: 'ProviderBizError',
341
- provider: 'qwen',
342
- }),
343
- );
344
-
345
- // Should not make any fetch calls
346
- expect(fetch).not.toHaveBeenCalled();
347
- });
348
-
349
330
  it('should handle task creation failure', async () => {
350
331
  global.fetch = vi.fn().mockResolvedValueOnce({
351
332
  ok: false,
@@ -19,39 +19,13 @@ interface QwenImageTaskResponse {
19
19
  request_id: string;
20
20
  }
21
21
 
22
- const QwenText2ImageModels = [
23
- 'wan2.2-t2i',
24
- 'wanx2.1-t2i',
25
- 'wanx2.0-t2i',
26
- 'wanx-v1',
27
- 'flux',
28
- 'stable-diffusion',
29
- ];
30
-
31
- const getModelType = (model: string): string => {
32
- // 可以添加其他模型类型的判断
33
- // if (QwenImage2ImageModels.some(prefix => model.startsWith(prefix))) {
34
- // return 'image2image';
35
- // }
36
-
37
- if (QwenText2ImageModels.some((prefix) => model.startsWith(prefix))) {
38
- return 'text2image';
39
- }
40
-
41
- throw new Error(`Unsupported model: ${model}`);
42
- };
43
-
44
22
  /**
45
23
  * Create an image generation task with Qwen API
46
24
  */
47
25
  async function createImageTask(payload: CreateImagePayload, apiKey: string): Promise<string> {
48
26
  const { model, params } = payload;
49
27
  // I can only say that the design of Alibaba Cloud's API is really bad; each model has a different endpoint path.
50
- const modelType = getModelType(model);
51
- const endpoint = `https://dashscope.aliyuncs.com/api/v1/services/aigc/${modelType}/image-synthesis`;
52
- if (!endpoint) {
53
- throw new Error(`No endpoint configured for model type: ${modelType}`);
54
- }
28
+ const endpoint = `https://dashscope.aliyuncs.com/api/v1/services/aigc/text2image/image-synthesis`;
55
29
  log('Creating image task with model: %s, endpoint: %s', model, endpoint);
56
30
 
57
31
  const response = await fetch(endpoint, {
@@ -1,4 +1,4 @@
1
- import type { ChatModelCard } from '@/types/llm';
1
+ import type { ChatModelCard } from '@lobechat/types';
2
2
 
3
3
  import type { ModelProviderKey } from '../types';
4
4
 
@@ -168,11 +168,8 @@ const findKnownModelByProvider = async (
168
168
  const lowerModelId = modelId.toLowerCase();
169
169
 
170
170
  try {
171
- // 动态构建导入路径
172
- const modulePath = `@/config/aiModels/${provider}`;
173
-
174
171
  // 尝试动态导入对应的配置文件
175
- const moduleImport = await import(modulePath);
172
+ const moduleImport = await import(`@/config/aiModels/${provider}`);
176
173
  const providerModels = moduleImport.default;
177
174
 
178
175
  // 如果导入成功且有数据,进行查找
@@ -211,9 +208,21 @@ export const detectModelProvider = (modelId: string): keyof typeof MODEL_LIST_CO
211
208
  * @param timestamp 时间戳(秒)
212
209
  * @returns 格式化的日期字符串 (YYYY-MM-DD)
213
210
  */
214
- const formatTimestampToDate = (timestamp: number): string => {
215
- const date = new Date(timestamp * 1000); // 将秒转换为毫秒
216
- return date.toISOString().split('T')[0]; // 返回 YYYY-MM-DD 格式
211
+ const formatTimestampToDate = (timestamp: number): string | undefined => {
212
+ if (timestamp === null || timestamp === undefined || Number.isNaN(timestamp)) return undefined;
213
+
214
+ // 支持秒级或毫秒级时间戳:
215
+ // - 如果是毫秒级(>= 1e12),直接当作毫秒;
216
+ // - 否则视为秒,需要 *1000 转为毫秒
217
+ const msTimestamp = timestamp > 1e12 ? timestamp : timestamp * 1000;
218
+ const date = new Date(msTimestamp);
219
+
220
+ // 验证解析结果和年份范围(只接受 4 位年份,避免超出 varchar(10) 的 YYYY-MM-DD)
221
+ const year = date.getUTCFullYear();
222
+ if (year < 1000 || year > 9999) return undefined;
223
+
224
+ const dateStr = date.toISOString().split('T')[0]; // YYYY-MM-DD
225
+ return dateStr.length === 10 ? dateStr : undefined;
217
226
  };
218
227
 
219
228
  /**
@@ -139,6 +139,31 @@ const transformGoogleGenerativeAIStream = (
139
139
  ];
140
140
  }
141
141
 
142
+ // Check for image data before handling finishReason
143
+ if (Array.isArray(candidate.content?.parts) && candidate.content.parts.length > 0) {
144
+ const part = candidate.content.parts[0];
145
+
146
+ if (part && part.inlineData && part.inlineData.data && part.inlineData.mimeType) {
147
+ const imageChunk = {
148
+ data: `data:${part.inlineData.mimeType};base64,${part.inlineData.data}`,
149
+ id: context.id,
150
+ type: 'base64_image' as const,
151
+ };
152
+
153
+ // If also has finishReason, combine image with finish chunks
154
+ if (candidate.finishReason) {
155
+ const chunks: StreamProtocolChunk[] = [imageChunk];
156
+ if (chunk.usageMetadata) {
157
+ chunks.push(...usageChunks);
158
+ }
159
+ chunks.push({ data: candidate.finishReason, id: context?.id, type: 'stop' });
160
+ return chunks;
161
+ }
162
+
163
+ return imageChunk;
164
+ }
165
+ }
166
+
142
167
  if (candidate.finishReason) {
143
168
  if (chunk.usageMetadata) {
144
169
  return [
@@ -150,23 +175,10 @@ const transformGoogleGenerativeAIStream = (
150
175
  }
151
176
 
152
177
  if (!!text?.trim()) return { data: text, id: context?.id, type: 'text' };
153
-
154
- // streaming the image
155
- if (Array.isArray(candidate.content?.parts) && candidate.content.parts.length > 0) {
156
- const part = candidate.content.parts[0];
157
-
158
- if (part && part.inlineData && part.inlineData.data && part.inlineData.mimeType) {
159
- return {
160
- data: `data:${part.inlineData.mimeType};base64,${part.inlineData.data}`,
161
- id: context.id,
162
- type: 'base64_image',
163
- };
164
- }
165
- }
166
178
  }
167
179
 
168
180
  return {
169
- data: text,
181
+ data: text || '',
170
182
  id: context?.id,
171
183
  type: 'text',
172
184
  };
@@ -121,7 +121,8 @@ export type PricingUnitName =
121
121
  | 'audioInput_cacheRead' // corresponds to ChatModelPricing.cachedAudioInput
122
122
 
123
123
  // Image-based pricing units
124
- | 'imageGeneration'; // for image generation models
124
+ | 'imageGeneration' // for image generation models
125
+ | 'imageOutput';
125
126
 
126
127
  export type PricingUnitType =
127
128
  | 'millionTokens' // per 1M tokens
@@ -189,6 +189,28 @@ const googleChatModels: AIChatModelCard[] = [
189
189
  },
190
190
  type: 'chat',
191
191
  },
192
+ {
193
+ abilities: {
194
+ imageOutput: true,
195
+ vision: true,
196
+ },
197
+ contextWindowTokens: 32_768 + 32_768,
198
+ description:
199
+ 'Gemini 2.5 Flash Image Preview 是 Google 最新、最快、最高效的原生多模态模型,它允许您通过对话生成和编辑图像。',
200
+ displayName: 'Gemini 2.5 Flash Image Preview',
201
+ enabled: true,
202
+ id: 'gemini-2.5-flash-image-preview',
203
+ maxOutput: 32_768,
204
+ pricing: {
205
+ units: [
206
+ { name: 'textInput', rate: 0.3, strategy: 'fixed', unit: 'millionTokens' },
207
+ { name: 'textOutput', rate: 2.5, strategy: 'fixed', unit: 'millionTokens' },
208
+ { name: 'imageOutput', rate: 3, strategy: 'fixed', unit: 'millionTokens' },
209
+ ],
210
+ },
211
+ releasedAt: '2025-08-27',
212
+ type: 'chat',
213
+ },
192
214
  {
193
215
  abilities: {
194
216
  functionCall: true,
@@ -304,7 +326,6 @@ const googleChatModels: AIChatModelCard[] = [
304
326
  contextWindowTokens: 32_768 + 8192,
305
327
  description: 'Gemini 2.0 Flash 预览模型,支持图像生成',
306
328
  displayName: 'Gemini 2.0 Flash Preview Image Generation',
307
- enabled: true,
308
329
  id: 'gemini-2.0-flash-preview-image-generation',
309
330
  maxOutput: 8192,
310
331
  pricing: {
@@ -1357,8 +1357,8 @@ const qwenImageModels: AIImageModelCard[] = [
1357
1357
  },
1358
1358
  seed: { default: null },
1359
1359
  size: {
1360
- default: '1328*1328',
1361
- enum: ['1664*928', '1472*1140', '1328*1328', '1140*1472', '928*1664'],
1360
+ default: '1328x1328',
1361
+ enum: ['1664x928', '1472x1140', '1328x1328', '1140x1472', '928x1664'],
1362
1362
  },
1363
1363
  },
1364
1364
  pricing: {
@@ -121,6 +121,28 @@ const vertexaiChatModels: AIChatModelCard[] = [
121
121
  releasedAt: '2025-04-17',
122
122
  type: 'chat',
123
123
  },
124
+ {
125
+ abilities: {
126
+ imageOutput: true,
127
+ vision: true,
128
+ },
129
+ contextWindowTokens: 32_768 + 32_768,
130
+ description:
131
+ 'Gemini 2.5 Flash Image Preview 是 Google 最新、最快、最高效的原生多模态模型,它允许您通过对话生成和编辑图像。',
132
+ displayName: 'Gemini 2.5 Flash Image Preview',
133
+ enabled: true,
134
+ id: 'gemini-2.5-flash-image-preview',
135
+ maxOutput: 32_768,
136
+ pricing: {
137
+ units: [
138
+ { name: 'textInput', rate: 0.3, strategy: 'fixed', unit: 'millionTokens' },
139
+ { name: 'textOutput', rate: 2.5, strategy: 'fixed', unit: 'millionTokens' },
140
+ { name: 'imageOutput', rate: 3, strategy: 'fixed', unit: 'millionTokens' },
141
+ ],
142
+ },
143
+ releasedAt: '2025-08-27',
144
+ type: 'chat',
145
+ },
124
146
  {
125
147
  abilities: {
126
148
  functionCall: true,
@@ -17,8 +17,8 @@ import useResizeObserver from './useResizeObserver';
17
17
  pdfjs.GlobalWorkerOptions.workerSrc = `https://registry.npmmirror.com/pdfjs-dist/${pdfjs.version}/files/build/pdf.worker.min.mjs`;
18
18
 
19
19
  const options = {
20
- cMapUrl: '/cmaps/',
21
- standardFontDataUrl: '/standard_fonts/',
20
+ cMapUrl: `https://registry.npmmirror.com/pdfjs-dist/${pdfjs.version}/files/cmaps/`,
21
+ standardFontDataUrl: `https://registry.npmmirror.com/pdfjs-dist/${pdfjs.version}/files/standard_fonts/`,
22
22
  };
23
23
 
24
24
  const maxWidth = 1200;