@lobehub/chat 1.47.22 → 1.48.0

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 (98) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/changelog/v1.json +18 -0
  3. package/locales/ar/chat.json +4 -0
  4. package/locales/ar/components.json +1 -0
  5. package/locales/ar/models.json +6 -0
  6. package/locales/bg-BG/chat.json +4 -0
  7. package/locales/bg-BG/components.json +1 -0
  8. package/locales/bg-BG/models.json +6 -0
  9. package/locales/de-DE/chat.json +4 -0
  10. package/locales/de-DE/components.json +1 -0
  11. package/locales/de-DE/models.json +6 -0
  12. package/locales/en-US/chat.json +4 -0
  13. package/locales/en-US/components.json +1 -0
  14. package/locales/en-US/models.json +6 -0
  15. package/locales/es-ES/chat.json +4 -0
  16. package/locales/es-ES/components.json +1 -0
  17. package/locales/es-ES/models.json +6 -0
  18. package/locales/fa-IR/chat.json +4 -0
  19. package/locales/fa-IR/components.json +1 -0
  20. package/locales/fa-IR/models.json +6 -0
  21. package/locales/fr-FR/chat.json +4 -0
  22. package/locales/fr-FR/components.json +1 -0
  23. package/locales/fr-FR/models.json +6 -0
  24. package/locales/it-IT/chat.json +4 -0
  25. package/locales/it-IT/components.json +1 -0
  26. package/locales/it-IT/models.json +6 -0
  27. package/locales/ja-JP/chat.json +4 -0
  28. package/locales/ja-JP/components.json +1 -0
  29. package/locales/ja-JP/models.json +6 -0
  30. package/locales/ko-KR/chat.json +4 -0
  31. package/locales/ko-KR/components.json +1 -0
  32. package/locales/ko-KR/models.json +6 -0
  33. package/locales/nl-NL/chat.json +4 -0
  34. package/locales/nl-NL/components.json +1 -0
  35. package/locales/nl-NL/models.json +6 -0
  36. package/locales/pl-PL/chat.json +4 -0
  37. package/locales/pl-PL/components.json +1 -0
  38. package/locales/pl-PL/models.json +6 -0
  39. package/locales/pt-BR/chat.json +4 -0
  40. package/locales/pt-BR/components.json +1 -0
  41. package/locales/pt-BR/models.json +6 -0
  42. package/locales/ru-RU/chat.json +4 -0
  43. package/locales/ru-RU/components.json +1 -0
  44. package/locales/ru-RU/models.json +6 -0
  45. package/locales/tr-TR/chat.json +4 -0
  46. package/locales/tr-TR/components.json +1 -0
  47. package/locales/tr-TR/models.json +6 -0
  48. package/locales/vi-VN/chat.json +4 -0
  49. package/locales/vi-VN/components.json +1 -0
  50. package/locales/vi-VN/models.json +6 -0
  51. package/locales/zh-CN/chat.json +4 -0
  52. package/locales/zh-CN/components.json +1 -0
  53. package/locales/zh-CN/modelProvider.json +2 -2
  54. package/locales/zh-CN/models.json +7 -1
  55. package/locales/zh-TW/chat.json +4 -0
  56. package/locales/zh-TW/components.json +1 -0
  57. package/locales/zh-TW/models.json +6 -0
  58. package/package.json +1 -1
  59. package/src/components/ModelSelect/index.tsx +16 -1
  60. package/src/config/aiModels/deepseek.ts +3 -0
  61. package/src/config/aiModels/hunyuan.ts +132 -12
  62. package/src/config/aiModels/qwen.ts +19 -2
  63. package/src/config/modelProviders/hunyuan.ts +2 -0
  64. package/src/database/client/migrations.json +13 -2
  65. package/src/database/migrations/0014_add_message_reasoning.sql +1 -0
  66. package/src/database/migrations/meta/0014_snapshot.json +3961 -0
  67. package/src/database/migrations/meta/_journal.json +7 -0
  68. package/src/database/schemas/message.ts +2 -3
  69. package/src/database/server/models/__tests__/message.test.ts +5 -4
  70. package/src/database/server/models/message.ts +35 -13
  71. package/src/database/server/models/topic.ts +3 -2
  72. package/src/features/Conversation/Messages/Assistant/Reasoning/index.tsx +123 -0
  73. package/src/features/Conversation/Messages/Assistant/index.tsx +8 -1
  74. package/src/features/Conversation/components/MarkdownElements/LobeThinking/index.ts +2 -2
  75. package/src/libs/agent-runtime/deepseek/index.ts +1 -1
  76. package/src/libs/agent-runtime/google/index.ts +7 -5
  77. package/src/libs/agent-runtime/hunyuan/index.ts +24 -0
  78. package/src/libs/agent-runtime/qwen/index.ts +8 -3
  79. package/src/libs/agent-runtime/stepfun/index.ts +7 -1
  80. package/src/libs/agent-runtime/utils/streams/openai.test.ts +203 -0
  81. package/src/libs/agent-runtime/utils/streams/openai.ts +8 -1
  82. package/src/libs/agent-runtime/utils/streams/protocol.ts +1 -1
  83. package/src/locales/default/chat.ts +4 -0
  84. package/src/locales/default/components.ts +1 -0
  85. package/src/server/routers/lambda/message.ts +4 -2
  86. package/src/services/message/client.test.ts +1 -1
  87. package/src/services/message/type.ts +1 -1
  88. package/src/store/chat/selectors.ts +1 -0
  89. package/src/store/chat/slices/aiChat/actions/generateAIChat.ts +60 -14
  90. package/src/store/chat/slices/aiChat/initialState.ts +5 -0
  91. package/src/store/chat/slices/aiChat/selectors.ts +9 -0
  92. package/src/store/chat/slices/message/action.ts +4 -1
  93. package/src/types/aiModel.ts +5 -14
  94. package/src/types/message/base.ts +59 -0
  95. package/src/types/message/chat.ts +136 -0
  96. package/src/types/message/index.ts +2 -135
  97. package/src/utils/fetch/__tests__/fetchSSE.test.ts +34 -0
  98. package/src/utils/fetch/fetchSSE.ts +38 -3
@@ -98,6 +98,13 @@
98
98
  "when": 1735834653361,
99
99
  "tag": "0013_add_ai_infra",
100
100
  "breakpoints": true
101
+ },
102
+ {
103
+ "idx": 14,
104
+ "version": "7",
105
+ "when": 1737609172353,
106
+ "tag": "0014_add_message_reasoning",
107
+ "breakpoints": true
101
108
  }
102
109
  ],
103
110
  "version": "6"
@@ -13,6 +13,7 @@ import {
13
13
  import { createSelectSchema } from 'drizzle-zod';
14
14
 
15
15
  import { idGenerator } from '@/database/utils/idGenerator';
16
+ import { ModelReasoning } from '@/types/message';
16
17
 
17
18
  import { timestamps } from './_helpers';
18
19
  import { agents } from './agent';
@@ -32,6 +33,7 @@ export const messages = pgTable(
32
33
 
33
34
  role: text('role', { enum: ['user', 'system', 'assistant', 'tool'] }).notNull(),
34
35
  content: text('content'),
36
+ reasoning: jsonb('reasoning').$type<ModelReasoning>(),
35
37
 
36
38
  model: text('model'),
37
39
  provider: text('provider'),
@@ -71,9 +73,6 @@ export const messages = pgTable(
71
73
  }),
72
74
  );
73
75
 
74
- export type NewMessage = typeof messages.$inferInsert;
75
- export type MessageItem = typeof messages.$inferSelect;
76
-
77
76
  // if the message container a plugin
78
77
  export const messagePlugins = pgTable('message_plugins', {
79
78
  id: text('id')
@@ -3,6 +3,7 @@ import { eq } from 'drizzle-orm/expressions';
3
3
  import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
4
4
 
5
5
  import { getTestDBInstance } from '@/database/server/core/dbForTest';
6
+ import { MessageItem } from '@/types/message';
6
7
  import { uuid } from '@/utils/uuid';
7
8
 
8
9
  import {
@@ -253,8 +254,8 @@ describe('MessageModel', () => {
253
254
  const result = await messageModel.query();
254
255
 
255
256
  // 断言结果
256
- expect(result[0].extra.translate).toEqual({ content: 'translated', from: 'en', to: 'zh' });
257
- expect(result[0].extra.tts).toEqual({
257
+ expect(result[0].extra!.translate).toEqual({ content: 'translated', from: 'en', to: 'zh' });
258
+ expect(result[0].extra!.tts).toEqual({
258
259
  contentMd5: 'md5',
259
260
  file: 'f1',
260
261
  voice: 'voice1',
@@ -345,7 +346,7 @@ describe('MessageModel', () => {
345
346
 
346
347
  expect(result).toHaveLength(1);
347
348
  expect(result[0].chunksList).toHaveLength(1);
348
- expect(result[0].chunksList[0]).toMatchObject({
349
+ expect(result[0].chunksList![0]).toMatchObject({
349
350
  text: 'chunk content',
350
351
  similarity: 0.95,
351
352
  });
@@ -655,7 +656,7 @@ describe('MessageModel', () => {
655
656
  const newMessages = [
656
657
  { id: '1', role: 'user', content: 'message 1' },
657
658
  { id: '2', role: 'assistant', content: 'message 2' },
658
- ];
659
+ ] as MessageItem[];
659
660
 
660
661
  // 调用 batchCreateMessages 方法
661
662
  await messageModel.batchCreate(newMessages);
@@ -14,16 +14,18 @@ import { idGenerator } from '@/database/utils/idGenerator';
14
14
  import {
15
15
  ChatFileItem,
16
16
  ChatImageItem,
17
+ ChatMessage,
17
18
  ChatTTS,
18
19
  ChatToolPayload,
20
+ ChatTranslate,
19
21
  CreateMessageParams,
22
+ MessageItem,
20
23
  ModelRankItem,
21
24
  } from '@/types/message';
22
25
  import { merge } from '@/utils/merge';
23
26
  import { today } from '@/utils/time';
24
27
 
25
28
  import {
26
- MessageItem,
27
29
  MessagePluginItem,
28
30
  NewMessageQuery,
29
31
  chunks,
@@ -61,7 +63,7 @@ export class MessageModel {
61
63
  options: {
62
64
  postProcessUrl?: (path: string | null, file: { fileType: string }) => Promise<string>;
63
65
  } = {},
64
- ): Promise<MessageItem[]> => {
66
+ ) => {
65
67
  const offset = current * pageSize;
66
68
 
67
69
  // 1. get basic messages
@@ -71,6 +73,7 @@ export class MessageModel {
71
73
  id: messages.id,
72
74
  role: messages.role,
73
75
  content: messages.content,
76
+ reasoning: messages.reasoning,
74
77
  error: messages.error,
75
78
 
76
79
  model: messages.model,
@@ -220,10 +223,11 @@ export class MessageModel {
220
223
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
221
224
  .map<ChatImageItem>(({ id, url, name }) => ({ alt: name!, id, url })),
222
225
 
226
+ meta: {},
223
227
  ragQuery: messageQuery?.rewriteQuery,
224
228
  ragQueryId: messageQuery?.id,
225
229
  ragRawQuery: messageQuery?.userQuery,
226
- };
230
+ } as unknown as ChatMessage;
227
231
  },
228
232
  );
229
233
  };
@@ -252,27 +256,33 @@ export class MessageModel {
252
256
  return result[0];
253
257
  };
254
258
 
255
- queryAll = async (): Promise<MessageItem[]> => {
256
- return this.db
259
+ queryAll = async () => {
260
+ const result = await this.db
257
261
  .select()
258
262
  .from(messages)
259
263
  .orderBy(messages.createdAt)
260
264
  .where(eq(messages.userId, this.userId));
265
+
266
+ return result as MessageItem[];
261
267
  };
262
268
 
263
- queryBySessionId = async (sessionId?: string | null): Promise<MessageItem[]> => {
264
- return this.db.query.messages.findMany({
269
+ queryBySessionId = async (sessionId?: string | null) => {
270
+ const result = await this.db.query.messages.findMany({
265
271
  orderBy: [asc(messages.createdAt)],
266
272
  where: and(eq(messages.userId, this.userId), this.matchSession(sessionId)),
267
273
  });
274
+
275
+ return result as MessageItem[];
268
276
  };
269
277
 
270
- queryByKeyword = async (keyword: string): Promise<MessageItem[]> => {
278
+ queryByKeyword = async (keyword: string) => {
271
279
  if (!keyword) return [];
272
- return this.db.query.messages.findMany({
280
+ const result = await this.db.query.messages.findMany({
273
281
  orderBy: [desc(messages.createdAt)],
274
282
  where: and(eq(messages.userId, this.userId), like(messages.content, `%${keyword}%`)),
275
283
  });
284
+
285
+ return result as MessageItem[];
276
286
  };
277
287
 
278
288
  count = async (params?: {
@@ -414,6 +424,8 @@ export class MessageModel {
414
424
  pluginState,
415
425
  fileChunks,
416
426
  ragQueryId,
427
+ updatedAt,
428
+ createdAt,
417
429
  ...message
418
430
  }: CreateMessageParams,
419
431
  id: string = this.genId(),
@@ -423,9 +435,12 @@ export class MessageModel {
423
435
  .insert(messages)
424
436
  .values({
425
437
  ...message,
438
+ // TODO: remove this when the client is updated
439
+ createdAt: createdAt ? new Date(createdAt) : undefined,
426
440
  id,
427
441
  model: fromModel,
428
442
  provider: fromProvider,
443
+ updatedAt: updatedAt ? new Date(updatedAt) : undefined,
429
444
  userId: this.userId,
430
445
  })
431
446
  .returning()) as MessageItem[];
@@ -466,7 +481,8 @@ export class MessageModel {
466
481
 
467
482
  batchCreate = async (newMessages: MessageItem[]) => {
468
483
  const messagesToInsert = newMessages.map((m) => {
469
- return { ...m, userId: this.userId };
484
+ // TODO: need a better way to handle this
485
+ return { ...m, role: m.role as any, userId: this.userId };
470
486
  });
471
487
 
472
488
  return this.db.insert(messages).values(messagesToInsert);
@@ -482,7 +498,11 @@ export class MessageModel {
482
498
  update = async (id: string, message: Partial<MessageItem>) => {
483
499
  return this.db
484
500
  .update(messages)
485
- .set(message)
501
+ .set({
502
+ ...message,
503
+ // TODO: need a better way to handle this
504
+ role: message.role as any,
505
+ })
486
506
  .where(and(eq(messages.id, id), eq(messages.userId, this.userId)));
487
507
  };
488
508
 
@@ -507,7 +527,7 @@ export class MessageModel {
507
527
  return this.db.update(messagePlugins).set(value).where(eq(messagePlugins.id, id));
508
528
  };
509
529
 
510
- updateTranslate = async (id: string, translate: Partial<MessageItem>) => {
530
+ updateTranslate = async (id: string, translate: Partial<ChatTranslate>) => {
511
531
  const result = await this.db.query.messageTranslates.findFirst({
512
532
  where: and(eq(messageTranslates.id, id)),
513
533
  });
@@ -555,7 +575,9 @@ export class MessageModel {
555
575
  if (message.length === 0) return;
556
576
 
557
577
  // 2. 检查 message 是否包含 tools
558
- const toolCallIds = message[0].tools?.map((tool: ChatToolPayload) => tool.id).filter(Boolean);
578
+ const toolCallIds = (message[0].tools as ChatToolPayload[])
579
+ ?.map((tool) => tool.id)
580
+ .filter(Boolean);
559
581
 
560
582
  let relatedMessageIds: string[] = [];
561
583
 
@@ -9,9 +9,10 @@ import {
9
9
  genWhere,
10
10
  } from '@/database/utils/genWhere';
11
11
  import { idGenerator } from '@/database/utils/idGenerator';
12
+ import { MessageItem } from '@/types/message';
12
13
  import { TopicRankItem } from '@/types/topic';
13
14
 
14
- import { NewMessage, TopicItem, messages, topics } from '../../schemas';
15
+ import { TopicItem, messages, topics } from '../../schemas';
15
16
 
16
17
  export interface CreateTopicParams {
17
18
  favorite?: boolean;
@@ -244,7 +245,7 @@ export class TopicModel {
244
245
  id: idGenerator('messages'),
245
246
  topicId: duplicatedTopic.id,
246
247
  })
247
- .returning()) as NewMessage[];
248
+ .returning()) as MessageItem[];
248
249
 
249
250
  return result[0];
250
251
  }),
@@ -0,0 +1,123 @@
1
+ import { Icon, Markdown } from '@lobehub/ui';
2
+ import { createStyles } from 'antd-style';
3
+ import { AtomIcon, ChevronDown, ChevronRight } from 'lucide-react';
4
+ import { rgba } from 'polished';
5
+ import { memo, useEffect, useState } from 'react';
6
+ import { useTranslation } from 'react-i18next';
7
+ import { Flexbox } from 'react-layout-kit';
8
+
9
+ import { useChatStore } from '@/store/chat';
10
+ import { aiChatSelectors } from '@/store/chat/selectors';
11
+
12
+ const useStyles = createStyles(({ css, token, isDarkMode }) => ({
13
+ container: css`
14
+ cursor: pointer;
15
+
16
+ width: fit-content;
17
+ padding-block: 4px;
18
+ padding-inline: 8px;
19
+ border-radius: 6px;
20
+
21
+ color: ${token.colorTextTertiary};
22
+
23
+ &:hover {
24
+ background: ${isDarkMode ? token.colorFillQuaternary : token.colorFillTertiary};
25
+ }
26
+ `,
27
+ expand: css`
28
+ background: ${isDarkMode ? token.colorFillQuaternary : token.colorFillTertiary} !important;
29
+ `,
30
+ shinyText: css`
31
+ color: ${rgba(token.colorText, 0.45)};
32
+
33
+ background: linear-gradient(
34
+ 120deg,
35
+ ${rgba(token.colorTextBase, 0)} 40%,
36
+ ${token.colorTextSecondary} 50%,
37
+ ${rgba(token.colorTextBase, 0)} 60%
38
+ );
39
+ background-clip: text;
40
+ background-size: 200% 100%;
41
+
42
+ animation: shine 1.5s linear infinite;
43
+
44
+ @keyframes shine {
45
+ 0% {
46
+ background-position: 100%;
47
+ }
48
+
49
+ 100% {
50
+ background-position: -100%;
51
+ }
52
+ }
53
+ `,
54
+ title: css`
55
+ overflow: hidden;
56
+ display: -webkit-box;
57
+ -webkit-box-orient: vertical;
58
+ -webkit-line-clamp: 1;
59
+
60
+ font-size: 12px;
61
+ text-overflow: ellipsis;
62
+ `,
63
+ }));
64
+
65
+ interface ThinkingProps {
66
+ content?: string;
67
+ duration?: number;
68
+ id: string;
69
+ }
70
+
71
+ const Thinking = memo<ThinkingProps>(({ content = '', duration, id }) => {
72
+ const { t } = useTranslation('chat');
73
+ const { styles, cx } = useStyles();
74
+
75
+ const [showDetail, setShowDetail] = useState(false);
76
+
77
+ const isReasoning = useChatStore(aiChatSelectors.isMessageInReasoning(id));
78
+
79
+ useEffect(() => {
80
+ if (isReasoning && !content) {
81
+ setShowDetail(true);
82
+ }
83
+
84
+ if (!isReasoning) {
85
+ setShowDetail(false);
86
+ }
87
+ }, [isReasoning, content]);
88
+
89
+ return (
90
+ <Flexbox
91
+ className={cx(styles.container, showDetail && styles.expand)}
92
+ gap={16}
93
+ onClick={() => {
94
+ setShowDetail(!showDetail);
95
+ }}
96
+ >
97
+ <Flexbox distribution={'space-between'} flex={1} horizontal>
98
+ {isReasoning ? (
99
+ <Flexbox gap={8} horizontal>
100
+ <Icon icon={AtomIcon} />
101
+ <Flexbox className={styles.shinyText} horizontal>
102
+ {t('reasoning.thinking')}
103
+ </Flexbox>
104
+ </Flexbox>
105
+ ) : (
106
+ <Flexbox gap={8} horizontal>
107
+ <Icon icon={AtomIcon} />
108
+ {t('reasoning.thought', { duration: ((duration || 0) / 1000).toFixed(1) })}
109
+ </Flexbox>
110
+ )}
111
+ <Icon icon={showDetail ? ChevronDown : ChevronRight} />
112
+ </Flexbox>
113
+
114
+ {showDetail && (
115
+ <Flexbox>
116
+ <Markdown variant={'chat'}>{content}</Markdown>
117
+ </Flexbox>
118
+ )}
119
+ </Flexbox>
120
+ );
121
+ });
122
+
123
+ export default Thinking;
@@ -3,13 +3,15 @@ import { ReactNode, Suspense, memo, useContext } from 'react';
3
3
  import { Flexbox } from 'react-layout-kit';
4
4
 
5
5
  import { LOADING_FLAT } from '@/const/message';
6
- import { InPortalThreadContext } from '@/features/Conversation/components/ChatItem/InPortalThreadContext';
7
6
  import { useChatStore } from '@/store/chat';
8
7
  import { chatSelectors } from '@/store/chat/selectors';
8
+ import { aiChatSelectors } from '@/store/chat/slices/aiChat/selectors';
9
9
  import { ChatMessage } from '@/types/message';
10
10
 
11
+ import { InPortalThreadContext } from '../../components/ChatItem/InPortalThreadContext';
11
12
  import { DefaultMessage } from '../Default';
12
13
  import FileChunks from './FileChunks';
14
+ import Thinking from './Reasoning';
13
15
  import ToolCall from './ToolCallItem';
14
16
 
15
17
  export const AssistantMessage = memo<
@@ -23,6 +25,10 @@ export const AssistantMessage = memo<
23
25
  const inThread = useContext(InPortalThreadContext);
24
26
  const isToolCallGenerating = generating && (content === LOADING_FLAT || !content) && !!tools;
25
27
 
28
+ const isReasoning = useChatStore(aiChatSelectors.isMessageInReasoning(id));
29
+
30
+ const showReasoning = !!props.reasoning || (!props.reasoning && isReasoning);
31
+
26
32
  return editing ? (
27
33
  <DefaultMessage
28
34
  content={content}
@@ -33,6 +39,7 @@ export const AssistantMessage = memo<
33
39
  ) : (
34
40
  <Flexbox gap={8} id={id}>
35
41
  {!!chunksList && chunksList.length > 0 && <FileChunks data={chunksList} />}
42
+ {showReasoning && <Thinking {...props.reasoning} id={id} />}
36
43
  {content && (
37
44
  <DefaultMessage
38
45
  addIdOnDOM={false}
@@ -3,10 +3,10 @@ import { ARTIFACT_THINKING_TAG } from '@/const/plugin';
3
3
  import Component from './Render';
4
4
  import rehypePlugin from './rehypePlugin';
5
5
 
6
- const AntThinkingElement = {
6
+ const LobeThinkingElement = {
7
7
  Component,
8
8
  rehypePlugin,
9
9
  tag: ARTIFACT_THINKING_TAG,
10
10
  };
11
11
 
12
- export default AntThinkingElement;
12
+ export default LobeThinkingElement;
@@ -40,7 +40,7 @@ export const LobeDeepSeekAI = LobeOpenAICompatibleFactory({
40
40
 
41
41
  return {
42
42
  enabled: LOBE_DEFAULT_MODEL_LIST.find((m) => model.id.endsWith(m.id))?.enabled || false,
43
- functionCall: true,
43
+ functionCall: !model.id.toLowerCase().includes('deepseek-reasoner'),
44
44
  id: model.id,
45
45
  };
46
46
  },
@@ -8,6 +8,8 @@ import {
8
8
  SchemaType,
9
9
  } from '@google/generative-ai';
10
10
 
11
+ import { LOBE_DEFAULT_MODEL_LIST } from '@/config/aiModels';
12
+ import type { ChatModelCard } from '@/types/llm';
11
13
  import { imageUrlToBase64 } from '@/utils/imageToBase64';
12
14
  import { safeParseJSON } from '@/utils/safeParseJSON';
13
15
 
@@ -27,9 +29,6 @@ import { StreamingResponse } from '../utils/response';
27
29
  import { GoogleGenerativeAIStream, convertIterableToStream } from '../utils/streams';
28
30
  import { parseDataUri } from '../utils/uriParser';
29
31
 
30
- import { LOBE_DEFAULT_MODEL_LIST } from '@/config/aiModels';
31
- import type { ChatModelCard } from '@/types/llm';
32
-
33
32
  export interface GoogleModelCard {
34
33
  displayName: string;
35
34
  inputTokenLimit: number;
@@ -143,7 +142,7 @@ export class LobeGoogleAI implements LobeRuntimeAI {
143
142
  method: 'GET',
144
143
  });
145
144
  const json = await response.json();
146
-
145
+
147
146
  const modelList: GoogleModelCard[] = json['models'];
148
147
 
149
148
  return modelList
@@ -156,7 +155,10 @@ export class LobeGoogleAI implements LobeRuntimeAI {
156
155
  enabled: LOBE_DEFAULT_MODEL_LIST.find((m) => modelName.endsWith(m.id))?.enabled || false,
157
156
  functionCall: modelName.toLowerCase().includes('gemini'),
158
157
  id: modelName,
159
- vision: modelName.toLowerCase().includes('vision') || modelName.toLowerCase().includes('gemini') && !modelName.toLowerCase().includes('gemini-1.0'),
158
+ vision:
159
+ modelName.toLowerCase().includes('vision') ||
160
+ (modelName.toLowerCase().includes('gemini') &&
161
+ !modelName.toLowerCase().includes('gemini-1.0')),
160
162
  };
161
163
  })
162
164
  .filter(Boolean) as ChatModelCard[];
@@ -1,10 +1,34 @@
1
1
  import { ModelProvider } from '../types';
2
2
  import { LobeOpenAICompatibleFactory } from '../utils/openaiCompatibleFactory';
3
3
 
4
+ import { LOBE_DEFAULT_MODEL_LIST } from '@/config/aiModels';
5
+
6
+ export interface HunyuanModelCard {
7
+ id: string;
8
+ }
9
+
4
10
  export const LobeHunyuanAI = LobeOpenAICompatibleFactory({
5
11
  baseURL: 'https://api.hunyuan.cloud.tencent.com/v1',
6
12
  debug: {
7
13
  chatCompletion: () => process.env.DEBUG_HUNYUAN_CHAT_COMPLETION === '1',
8
14
  },
15
+ models: {
16
+ transformModel: (m) => {
17
+ const functionCallKeywords = [
18
+ 'hunyuan-functioncall',
19
+ 'hunyuan-turbo',
20
+ 'hunyuan-pro',
21
+ ];
22
+
23
+ const model = m as unknown as HunyuanModelCard;
24
+
25
+ return {
26
+ enabled: LOBE_DEFAULT_MODEL_LIST.find((m) => model.id.endsWith(m.id))?.enabled || false,
27
+ functionCall: functionCallKeywords.some(keyword => model.id.toLowerCase().includes(keyword)) && !model.id.toLowerCase().includes('vision'),
28
+ id: model.id,
29
+ vision: model.id.toLowerCase().includes('vision'),
30
+ };
31
+ },
32
+ },
9
33
  provider: ModelProvider.Hunyuan,
10
34
  });
@@ -49,7 +49,7 @@ export const LobeQwenAI = LobeOpenAICompatibleFactory({
49
49
  : undefined,
50
50
  stream: !payload.tools,
51
51
  temperature: (temperature !== undefined && temperature >= 0 && temperature < 2) ? temperature : undefined,
52
- ...(model.startsWith('qwen-vl') ? {
52
+ ...(model.startsWith('qvq') || model.startsWith('qwen-vl') ? {
53
53
  top_p: (top_p !== undefined && top_p > 0 && top_p <= 1) ? top_p : undefined,
54
54
  } : {
55
55
  top_p: (top_p !== undefined && top_p > 0 && top_p < 1) ? top_p : undefined,
@@ -67,7 +67,7 @@ export const LobeQwenAI = LobeOpenAICompatibleFactory({
67
67
  debug: {
68
68
  chatCompletion: () => process.env.DEBUG_QWEN_CHAT_COMPLETION === '1',
69
69
  },
70
- models: {
70
+ models: {
71
71
  transformModel: (m) => {
72
72
  const functionCallKeywords = [
73
73
  'qwen-max',
@@ -76,13 +76,18 @@ export const LobeQwenAI = LobeOpenAICompatibleFactory({
76
76
  'qwen2.5',
77
77
  ];
78
78
 
79
+ const visionKeywords = [
80
+ 'qvq',
81
+ 'vl',
82
+ ];
83
+
79
84
  const model = m as unknown as QwenModelCard;
80
85
 
81
86
  return {
82
87
  enabled: LOBE_DEFAULT_MODEL_LIST.find((m) => model.id.endsWith(m.id))?.enabled || false,
83
88
  functionCall: functionCallKeywords.some(keyword => model.id.toLowerCase().includes(keyword)),
84
89
  id: model.id,
85
- vision: model.id.toLowerCase().includes('vl'),
90
+ vision: visionKeywords.some(keyword => model.id.toLowerCase().includes(keyword)),
86
91
  };
87
92
  },
88
93
  },
@@ -25,7 +25,13 @@ export const LobeStepfunAI = LobeOpenAICompatibleFactory({
25
25
  // ref: https://platform.stepfun.com/docs/llm/modeloverview
26
26
  const functionCallKeywords = [
27
27
  'step-1-',
28
+ 'step-1o-',
29
+ 'step-1v-',
28
30
  'step-2-',
31
+ ];
32
+
33
+ const visionKeywords = [
34
+ 'step-1o-',
29
35
  'step-1v-',
30
36
  ];
31
37
 
@@ -35,7 +41,7 @@ export const LobeStepfunAI = LobeOpenAICompatibleFactory({
35
41
  enabled: LOBE_DEFAULT_MODEL_LIST.find((m) => model.id.endsWith(m.id))?.enabled || false,
36
42
  functionCall: functionCallKeywords.some(keyword => model.id.toLowerCase().includes(keyword)),
37
43
  id: model.id,
38
- vision: model.id.toLowerCase().includes('v'),
44
+ vision: visionKeywords.some(keyword => model.id.toLowerCase().includes(keyword)),
39
45
  };
40
46
  },
41
47
  },