@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.
- package/.cursor/rules/add-provider-doc.mdc +183 -0
- package/.env.example +8 -0
- package/.github/workflows/claude.yml +1 -1
- package/.github/workflows/release.yml +3 -3
- package/.github/workflows/test.yml +3 -7
- package/CHANGELOG.md +42 -0
- package/CLAUDE.md +6 -6
- package/Dockerfile +5 -1
- package/Dockerfile.database +5 -1
- package/Dockerfile.pglite +5 -1
- package/changelog/v1.json +14 -0
- package/docs/development/basic/setup-development.mdx +10 -13
- package/docs/development/basic/setup-development.zh-CN.mdx +9 -12
- package/docs/development/database-schema.dbml +44 -0
- package/docs/self-hosting/environment-variables/model-provider.mdx +27 -2
- package/docs/self-hosting/environment-variables/model-provider.zh-CN.mdx +27 -2
- package/docs/usage/providers/bfl.mdx +68 -0
- package/docs/usage/providers/bfl.zh-CN.mdx +67 -0
- package/locales/ar/components.json +11 -0
- package/locales/ar/error.json +11 -0
- package/locales/ar/models.json +64 -4
- package/locales/ar/providers.json +3 -0
- package/locales/bg-BG/components.json +11 -0
- package/locales/bg-BG/error.json +11 -0
- package/locales/bg-BG/models.json +64 -4
- package/locales/bg-BG/providers.json +3 -0
- package/locales/de-DE/components.json +11 -0
- package/locales/de-DE/error.json +11 -12
- package/locales/de-DE/models.json +64 -4
- package/locales/de-DE/providers.json +3 -0
- package/locales/en-US/components.json +6 -0
- package/locales/en-US/error.json +11 -12
- package/locales/en-US/models.json +64 -4
- package/locales/en-US/providers.json +3 -0
- package/locales/es-ES/components.json +11 -0
- package/locales/es-ES/error.json +11 -0
- package/locales/es-ES/models.json +64 -6
- package/locales/es-ES/providers.json +3 -0
- package/locales/fa-IR/components.json +11 -0
- package/locales/fa-IR/error.json +11 -0
- package/locales/fa-IR/models.json +64 -4
- package/locales/fa-IR/providers.json +3 -0
- package/locales/fr-FR/components.json +11 -0
- package/locales/fr-FR/error.json +11 -12
- package/locales/fr-FR/models.json +64 -4
- package/locales/fr-FR/providers.json +3 -0
- package/locales/it-IT/components.json +11 -0
- package/locales/it-IT/error.json +11 -0
- package/locales/it-IT/models.json +64 -4
- package/locales/it-IT/providers.json +3 -0
- package/locales/ja-JP/components.json +11 -0
- package/locales/ja-JP/error.json +11 -12
- package/locales/ja-JP/models.json +64 -4
- package/locales/ja-JP/providers.json +3 -0
- package/locales/ko-KR/components.json +11 -0
- package/locales/ko-KR/error.json +11 -12
- package/locales/ko-KR/models.json +64 -6
- package/locales/ko-KR/providers.json +3 -0
- package/locales/nl-NL/components.json +11 -0
- package/locales/nl-NL/error.json +11 -0
- package/locales/nl-NL/models.json +62 -4
- package/locales/nl-NL/providers.json +3 -0
- package/locales/pl-PL/components.json +11 -0
- package/locales/pl-PL/error.json +11 -0
- package/locales/pl-PL/models.json +64 -4
- package/locales/pl-PL/providers.json +3 -0
- package/locales/pt-BR/components.json +11 -0
- package/locales/pt-BR/error.json +11 -0
- package/locales/pt-BR/models.json +64 -4
- package/locales/pt-BR/providers.json +3 -0
- package/locales/ru-RU/components.json +11 -0
- package/locales/ru-RU/error.json +11 -0
- package/locales/ru-RU/models.json +64 -4
- package/locales/ru-RU/providers.json +3 -0
- package/locales/tr-TR/components.json +11 -0
- package/locales/tr-TR/error.json +11 -0
- package/locales/tr-TR/models.json +64 -4
- package/locales/tr-TR/providers.json +3 -0
- package/locales/vi-VN/components.json +11 -0
- package/locales/vi-VN/error.json +11 -0
- package/locales/vi-VN/models.json +64 -4
- package/locales/vi-VN/providers.json +3 -0
- package/locales/zh-CN/components.json +6 -0
- package/locales/zh-CN/error.json +11 -0
- package/locales/zh-CN/models.json +64 -4
- package/locales/zh-CN/providers.json +3 -0
- package/locales/zh-TW/components.json +11 -0
- package/locales/zh-TW/error.json +11 -12
- package/locales/zh-TW/models.json +64 -6
- package/locales/zh-TW/providers.json +3 -0
- package/package.json +1 -1
- package/packages/database/migrations/0030_add_group_chat.sql +36 -0
- package/packages/database/migrations/meta/0030_snapshot.json +6417 -0
- package/packages/database/migrations/meta/_journal.json +7 -0
- package/packages/database/src/core/migrations.json +19 -0
- package/packages/database/src/models/__tests__/topic.test.ts +3 -1
- package/packages/database/src/repositories/tableViewer/index.test.ts +1 -1
- package/packages/database/src/schemas/chatGroup.ts +98 -0
- package/packages/database/src/schemas/index.ts +1 -0
- package/packages/database/src/schemas/message.ts +4 -1
- package/packages/database/src/schemas/relations.ts +26 -0
- package/packages/database/src/schemas/topic.ts +2 -0
- package/packages/database/src/types/chatGroup.ts +9 -0
- package/packages/database/src/utils/idGenerator.ts +1 -0
- package/packages/model-runtime/src/google/index.ts +3 -0
- package/packages/model-runtime/src/qwen/createImage.test.ts +0 -19
- package/packages/model-runtime/src/qwen/createImage.ts +1 -27
- package/packages/model-runtime/src/utils/modelParse.ts +17 -8
- package/packages/model-runtime/src/utils/streams/google-ai.ts +26 -14
- package/packages/types/src/aiModel.ts +2 -1
- package/src/config/aiModels/google.ts +22 -1
- package/src/config/aiModels/qwen.ts +2 -2
- package/src/config/aiModels/vertexai.ts +22 -0
- package/src/features/FileViewer/Renderer/PDF/index.tsx +2 -2
- 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(
|
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
|
@@ -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
|
+
}
|
@@ -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
|
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 '
|
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(
|
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
|
-
|
216
|
-
|
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'
|
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: '
|
1361
|
-
enum: ['
|
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:
|
21
|
-
standardFontDataUrl:
|
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;
|