@lobehub/chat 1.90.3 → 1.91.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 (106) hide show
  1. package/.cursor/rules/backend-architecture.mdc +12 -9
  2. package/.cursor/rules/cursor-ux-optimize.mdc +1 -1
  3. package/.cursor/rules/define-database-model.mdc +1 -1
  4. package/.cursor/rules/drizzle-schema-style-guide.mdc +1 -1
  5. package/.cursor/rules/i18n/i18n.mdc +1 -1
  6. package/.cursor/rules/project-introduce.mdc +2 -1
  7. package/.cursor/rules/system-role.mdc +42 -0
  8. package/.cursor/rules/zustand-action-patterns.mdc +318 -0
  9. package/.cursor/rules/zustand-slice-organization.mdc +300 -0
  10. package/CHANGELOG.md +66 -0
  11. package/README.md +2 -2
  12. package/README.zh-CN.md +2 -2
  13. package/changelog/v1.json +24 -0
  14. package/docs/self-hosting/advanced/model-list.mdx +1 -1
  15. package/docs/self-hosting/advanced/model-list.zh-CN.mdx +1 -1
  16. package/docs/self-hosting/environment-variables/model-provider.mdx +2 -2
  17. package/docs/self-hosting/environment-variables/model-provider.zh-CN.mdx +2 -2
  18. package/package.json +44 -44
  19. package/packages/web-crawler/src/crawImpl/exa.ts +93 -0
  20. package/packages/web-crawler/src/crawImpl/firecrawl.ts +97 -0
  21. package/packages/web-crawler/src/crawImpl/index.ts +6 -0
  22. package/packages/web-crawler/src/crawImpl/tavily.ts +94 -0
  23. package/src/config/aiModels/modelscope.ts +3 -3
  24. package/src/config/modelProviders/modelscope.ts +3 -3
  25. package/src/database/client/migrations.json +10 -0
  26. package/src/database/migrations/0023_remove_param_and_doubao.sql +6 -0
  27. package/src/database/migrations/meta/0023_snapshot.json +5340 -0
  28. package/src/database/migrations/meta/_journal.json +7 -0
  29. package/src/features/PluginsUI/Render/utils/iframeOnReady.test.ts +1 -1
  30. package/src/features/PluginsUI/Render/utils/pluginSettings.test.ts +1 -1
  31. package/src/features/PluginsUI/Render/utils/pluginState.test.ts +1 -1
  32. package/src/libs/model-runtime/BaseAI.ts +3 -3
  33. package/src/libs/model-runtime/ModelRuntime.ts +2 -2
  34. package/src/libs/model-runtime/UniformRuntime/index.ts +2 -2
  35. package/src/libs/model-runtime/ai21/index.ts +2 -2
  36. package/src/libs/model-runtime/ai360/index.ts +2 -2
  37. package/src/libs/model-runtime/anthropic/index.ts +15 -11
  38. package/src/libs/model-runtime/azureOpenai/index.ts +2 -2
  39. package/src/libs/model-runtime/azureai/index.ts +4 -4
  40. package/src/libs/model-runtime/baichuan/index.ts +2 -2
  41. package/src/libs/model-runtime/bedrock/index.ts +4 -4
  42. package/src/libs/model-runtime/cloudflare/index.ts +2 -2
  43. package/src/libs/model-runtime/cohere/index.ts +2 -2
  44. package/src/libs/model-runtime/deepseek/index.ts +2 -2
  45. package/src/libs/model-runtime/fireworksai/index.ts +2 -2
  46. package/src/libs/model-runtime/giteeai/index.ts +2 -2
  47. package/src/libs/model-runtime/github/index.ts +2 -2
  48. package/src/libs/model-runtime/google/index.ts +7 -5
  49. package/src/libs/model-runtime/groq/index.ts +2 -2
  50. package/src/libs/model-runtime/higress/index.ts +2 -2
  51. package/src/libs/model-runtime/huggingface/index.ts +2 -2
  52. package/src/libs/model-runtime/hunyuan/index.ts +2 -2
  53. package/src/libs/model-runtime/index.ts +1 -1
  54. package/src/libs/model-runtime/infiniai/index.ts +2 -2
  55. package/src/libs/model-runtime/internlm/index.ts +7 -9
  56. package/src/libs/model-runtime/jina/index.ts +2 -2
  57. package/src/libs/model-runtime/lmstudio/index.ts +2 -2
  58. package/src/libs/model-runtime/minimax/index.ts +2 -2
  59. package/src/libs/model-runtime/mistral/index.ts +2 -2
  60. package/src/libs/model-runtime/modelscope/index.ts +2 -3
  61. package/src/libs/model-runtime/moonshot/index.ts +2 -2
  62. package/src/libs/model-runtime/novita/index.ts +2 -2
  63. package/src/libs/model-runtime/nvidia/index.ts +2 -2
  64. package/src/libs/model-runtime/ollama/index.ts +2 -2
  65. package/src/libs/model-runtime/openai/index.ts +3 -3
  66. package/src/libs/model-runtime/openrouter/index.ts +2 -2
  67. package/src/libs/model-runtime/perplexity/index.ts +2 -2
  68. package/src/libs/model-runtime/ppio/index.ts +2 -2
  69. package/src/libs/model-runtime/qiniu/index.ts +2 -2
  70. package/src/libs/model-runtime/qwen/index.ts +2 -2
  71. package/src/libs/model-runtime/sambanova/index.ts +2 -2
  72. package/src/libs/model-runtime/search1api/index.ts +2 -2
  73. package/src/libs/model-runtime/sensenova/index.ts +2 -2
  74. package/src/libs/model-runtime/siliconcloud/index.ts +2 -2
  75. package/src/libs/model-runtime/spark/index.ts +15 -13
  76. package/src/libs/model-runtime/stepfun/index.ts +2 -2
  77. package/src/libs/model-runtime/taichu/index.ts +2 -2
  78. package/src/libs/model-runtime/tencentcloud/index.ts +2 -2
  79. package/src/libs/model-runtime/togetherai/index.ts +2 -2
  80. package/src/libs/model-runtime/types/chat.ts +1 -1
  81. package/src/libs/model-runtime/upstage/index.ts +2 -2
  82. package/src/libs/model-runtime/utils/openaiCompatibleFactory/index.test.ts +7 -7
  83. package/src/libs/model-runtime/utils/openaiCompatibleFactory/index.ts +3 -3
  84. package/src/libs/model-runtime/vllm/index.ts +2 -2
  85. package/src/libs/model-runtime/volcengine/index.ts +2 -2
  86. package/src/libs/model-runtime/wenxin/index.ts +2 -2
  87. package/src/libs/model-runtime/xai/index.ts +6 -3
  88. package/src/libs/model-runtime/xinference/index.ts +2 -2
  89. package/src/libs/model-runtime/zeroone/index.ts +2 -2
  90. package/src/libs/model-runtime/zhipu/index.ts +2 -2
  91. package/src/middleware.ts +3 -1
  92. package/src/server/routers/tools/search.test.ts +2 -4
  93. package/src/server/services/search/impls/bocha/index.ts +124 -0
  94. package/src/server/services/search/impls/bocha/type.ts +47 -0
  95. package/src/server/services/search/impls/exa/index.ts +129 -0
  96. package/src/server/services/search/impls/exa/type.ts +39 -0
  97. package/src/server/services/search/impls/firecrawl/index.ts +128 -0
  98. package/src/server/services/search/impls/firecrawl/type.ts +35 -0
  99. package/src/server/services/search/impls/index.ts +31 -0
  100. package/src/server/services/search/impls/jina/index.ts +109 -0
  101. package/src/server/services/search/impls/jina/type.ts +26 -0
  102. package/src/server/services/search/impls/tavily/index.ts +124 -0
  103. package/src/server/services/search/impls/tavily/type.ts +36 -0
  104. package/src/store/chat/slices/aiChat/actions/__tests__/generateAIChat.test.ts +4 -2
  105. package/src/store/chat/slices/message/action.test.ts +2 -1
  106. package/src/store/chat/slices/topic/action.test.ts +3 -2
@@ -0,0 +1,26 @@
1
+ export interface JinaSearchParameters {
2
+ q: string;
3
+ }
4
+
5
+ interface JinaUsage {
6
+ tokens: number;
7
+ }
8
+
9
+ interface JinaMeta {
10
+ usage: JinaUsage;
11
+ }
12
+
13
+ interface JinaData {
14
+ content?: string;
15
+ description?: string;
16
+ title: string;
17
+ url: string;
18
+ usage?: JinaUsage;
19
+ }
20
+
21
+ export interface JinaResponse {
22
+ code?: number;
23
+ data: JinaData[];
24
+ meta?: JinaMeta;
25
+ status?: number;
26
+ }
@@ -0,0 +1,124 @@
1
+ import { TRPCError } from '@trpc/server';
2
+ import debug from 'debug';
3
+ import urlJoin from 'url-join';
4
+
5
+ import { SearchParams, UniformSearchResponse, UniformSearchResult } from '@/types/tool/search';
6
+
7
+ import { SearchServiceImpl } from '../type';
8
+ import { TavilySearchParameters, TavilyResponse } from './type';
9
+
10
+ const log = debug('lobe-search:Tavily');
11
+
12
+ /**
13
+ * Tavily implementation of the search service
14
+ * Primarily used for web crawling
15
+ */
16
+ export class TavilyImpl implements SearchServiceImpl {
17
+ private get apiKey(): string | undefined {
18
+ return process.env.TAVILY_API_KEY;
19
+ }
20
+
21
+ private get baseUrl(): string {
22
+ // Assuming the base URL is consistent with the crawl endpoint
23
+ return 'https://api.tavily.com';
24
+ }
25
+
26
+ async query(query: string, params: SearchParams = {}): Promise<UniformSearchResponse> {
27
+ log('Starting Tavily query with query: "%s", params: %o', query, params);
28
+ const endpoint = urlJoin(this.baseUrl, '/search');
29
+
30
+ const defaultQueryParams: TavilySearchParameters = {
31
+ include_answer: false,
32
+ include_image_descriptions: true,
33
+ include_images: false,
34
+ include_raw_content: false,
35
+ max_results: 15,
36
+ query,
37
+ search_depth: process.env.TAVILY_SEARCH_DEPTH || 'basic' // basic or advanced
38
+ };
39
+
40
+ let body: TavilySearchParameters = {
41
+ ...defaultQueryParams,
42
+ time_range:
43
+ params?.searchTimeRange && params.searchTimeRange !== 'anytime'
44
+ ? params.searchTimeRange
45
+ : undefined,
46
+ topic:
47
+ // Tavily 只支持 news 和 general 两种类型
48
+ params?.searchCategories?.filter(cat => ['news', 'general'].includes(cat))?.[0],
49
+ };
50
+
51
+ log('Constructed request body: %o', body);
52
+
53
+ let response: Response;
54
+ const startAt = Date.now();
55
+ let costTime = 0;
56
+ try {
57
+ log('Sending request to endpoint: %s', endpoint);
58
+ response = await fetch(endpoint, {
59
+ body: JSON.stringify(body),
60
+ headers: {
61
+ 'Authorization': this.apiKey ? `Bearer ${this.apiKey}` : '',
62
+ 'Content-Type': 'application/json',
63
+ },
64
+ method: 'POST',
65
+ });
66
+ log('Received response with status: %d', response.status);
67
+ costTime = Date.now() - startAt;
68
+ } catch (error) {
69
+ log.extend('error')('Tavily fetch error: %o', error);
70
+ throw new TRPCError({
71
+ cause: error,
72
+ code: 'SERVICE_UNAVAILABLE',
73
+ message: 'Failed to connect to Tavily.',
74
+ });
75
+ }
76
+
77
+ if (!response.ok) {
78
+ const errorBody = await response.text();
79
+ log.extend('error')(
80
+ `Tavily request failed with status ${response.status}: %s`,
81
+ errorBody.length > 200 ? `${errorBody.slice(0, 200)}...` : errorBody,
82
+ );
83
+ throw new TRPCError({
84
+ cause: errorBody,
85
+ code: 'SERVICE_UNAVAILABLE',
86
+ message: `Tavily request failed: ${response.statusText}`,
87
+ });
88
+ }
89
+
90
+ try {
91
+ const tavilyResponse = (await response.json()) as TavilyResponse;
92
+
93
+ log('Parsed Tavily response: %o', tavilyResponse);
94
+
95
+ const mappedResults = (tavilyResponse.results || []).map(
96
+ (result): UniformSearchResult => ({
97
+ category: body.topic || 'general', // Default category
98
+ content: result.content || '', // Prioritize content, fallback to snippet
99
+ engines: ['tavily'], // Use 'tavily' as the engine name
100
+ parsedUrl: result.url ? new URL(result.url).hostname : '', // Basic URL parsing
101
+ score: result.score || 0, // Default score to 0 if undefined
102
+ title: result.title || '',
103
+ url: result.url,
104
+ }),
105
+ );
106
+
107
+ log('Mapped %d results to SearchResult format', mappedResults.length);
108
+
109
+ return {
110
+ costTime,
111
+ query: query,
112
+ resultNumbers: mappedResults.length,
113
+ results: mappedResults,
114
+ };
115
+ } catch (error) {
116
+ log.extend('error')('Error parsing Tavily response: %o', error);
117
+ throw new TRPCError({
118
+ cause: error,
119
+ code: 'INTERNAL_SERVER_ERROR',
120
+ message: 'Failed to parse Tavily response.',
121
+ });
122
+ }
123
+ }
124
+ }
@@ -0,0 +1,36 @@
1
+ export interface TavilySearchParameters {
2
+ chunks_per_source?: number;
3
+ days?: number;
4
+ exclude_domains?: string[];
5
+ include_answer?: boolean | string;
6
+ include_domains?: string[];
7
+ include_image_descriptions?: boolean;
8
+ include_images?: boolean;
9
+ include_raw_content?: boolean;
10
+ max_results?: number;
11
+ query: string;
12
+ search_depth?: string;
13
+ time_range?: string;
14
+ topic?: string;
15
+ }
16
+
17
+ interface TavilyImages {
18
+ description?: string;
19
+ url: string;
20
+ }
21
+
22
+ interface TavilyResults {
23
+ content?: string;
24
+ raw_content?: string | null;
25
+ score?: number;
26
+ title?: string;
27
+ url: string;
28
+ }
29
+
30
+ export interface TavilyResponse {
31
+ answer?: string;
32
+ images?: TavilyImages[];
33
+ query: string;
34
+ response_time: number;
35
+ results: TavilyResults[];
36
+ }
@@ -671,14 +671,16 @@ describe('chatMessage actions', () => {
671
671
  (chatService.createAssistantMessage as Mock).mockResolvedValue(aiResponse);
672
672
  const spy = vi.spyOn(chatService, 'createAssistantMessageStream');
673
673
  // 模拟消息创建
674
- (messageService.createMessage as Mock).mockResolvedValue('assistant-message-id');
674
+ const createMessageSpyOn = vi
675
+ .spyOn(messageService, 'createMessage')
676
+ .mockResolvedValue('assistant-message-id');
675
677
 
676
678
  await act(async () => {
677
679
  await result.current.internal_coreProcessMessage(messages, userMessage.id);
678
680
  });
679
681
 
680
682
  // 验证是否创建了代表 AI 响应的消息
681
- expect(messageService.createMessage).toHaveBeenCalledWith(
683
+ expect(createMessageSpyOn).toHaveBeenCalledWith(
682
684
  expect.objectContaining({
683
685
  role: 'assistant',
684
686
  content: LOADING_FLAT,
@@ -369,11 +369,12 @@ describe('chatMessage actions', () => {
369
369
  const messageId = 'message-id';
370
370
  const newContent = 'Updated content';
371
371
 
372
+ const spy = vi.spyOn(messageService, 'updateMessage');
372
373
  await act(async () => {
373
374
  await result.current.internal_updateMessageContent(messageId, newContent);
374
375
  });
375
376
 
376
- expect(messageService.updateMessage).toHaveBeenCalledWith(messageId, { content: newContent });
377
+ expect(spy).toHaveBeenCalledWith(messageId, { content: newContent });
377
378
  });
378
379
 
379
380
  it('should dispatch message update action', async () => {
@@ -264,7 +264,8 @@ describe('topic action', () => {
264
264
  const topicId = 'topic-id';
265
265
  const newTitle = 'Updated Topic Title';
266
266
  // Mock the topicService.updateTitle to resolve immediately
267
- (topicService.updateTopic as Mock).mockResolvedValue(undefined);
267
+
268
+ const spyOn = vi.spyOn(topicService, 'updateTopic');
268
269
 
269
270
  const { result } = renderHook(() => useChatStore());
270
271
 
@@ -276,7 +277,7 @@ describe('topic action', () => {
276
277
  });
277
278
 
278
279
  // Verify that the topicService.updateTitle was called with correct parameters
279
- expect(topicService.updateTopic).toHaveBeenCalledWith(topicId, {
280
+ expect(spyOn).toHaveBeenCalledWith(topicId, {
280
281
  title: 'Updated Topic Title',
281
282
  });
282
283