@lobehub/chat 1.90.4 → 1.91.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 (297) hide show
  1. package/CHANGELOG.md +58 -0
  2. package/changelog/v1.json +21 -0
  3. package/docs/changelog/2023-09-09-plugin-system.mdx +2 -3
  4. package/docs/changelog/2023-11-14-gpt4-vision.mdx +4 -6
  5. package/docs/changelog/2023-11-19-tts-stt.mdx +2 -3
  6. package/docs/changelog/2023-12-22-dalle-3.mdx +2 -5
  7. package/docs/changelog/2023-12-22-dalle-3.zh-CN.mdx +2 -2
  8. package/docs/changelog/2024-02-08-sso-oauth.mdx +2 -2
  9. package/docs/changelog/2024-06-19-lobe-chat-v1.mdx +2 -3
  10. package/docs/changelog/2024-06-19-lobe-chat-v1.zh-CN.mdx +2 -2
  11. package/docs/changelog/2024-07-19-gpt-4o-mini.mdx +2 -3
  12. package/docs/changelog/2024-07-19-gpt-4o-mini.zh-CN.mdx +2 -2
  13. package/docs/changelog/2024-08-02-lobe-chat-database-docker.mdx +2 -3
  14. package/docs/changelog/2024-08-21-file-upload-and-knowledge-base.mdx +4 -5
  15. package/docs/changelog/2024-09-13-openai-o1-models.mdx +2 -2
  16. package/docs/changelog/2024-09-20-artifacts.mdx +2 -3
  17. package/docs/changelog/2024-09-20-artifacts.zh-CN.mdx +2 -2
  18. package/docs/changelog/2024-10-27-pin-assistant.mdx +2 -3
  19. package/docs/changelog/2024-11-06-share-text-json.mdx +2 -4
  20. package/docs/changelog/2024-11-06-share-text-json.zh-CN.mdx +2 -2
  21. package/docs/changelog/2024-11-25-november-providers.mdx +2 -2
  22. package/docs/changelog/2024-11-27-forkable-chat.mdx +2 -2
  23. package/docs/changelog/2025-01-03-user-profile.mdx +2 -2
  24. package/docs/changelog/2025-01-22-new-ai-provider.mdx +2 -2
  25. package/docs/changelog/2025-02-02-deepseek-r1.mdx +4 -4
  26. package/docs/development/basic/add-new-authentication-providers.zh-CN.mdx +1 -2
  27. package/docs/development/basic/chat-api.mdx +2 -4
  28. package/docs/development/basic/chat-api.zh-CN.mdx +2 -4
  29. package/docs/development/internationalization/internationalization-implementation.mdx +10 -10
  30. package/docs/development/internationalization/internationalization-implementation.zh-CN.mdx +10 -10
  31. package/docs/self-hosting/advanced/analytics.mdx +2 -2
  32. package/docs/self-hosting/advanced/auth/clerk.mdx +2 -2
  33. package/docs/self-hosting/advanced/auth/next-auth/auth0.mdx +2 -3
  34. package/docs/self-hosting/advanced/auth/next-auth/authelia.mdx +2 -3
  35. package/docs/self-hosting/advanced/auth/next-auth/authentik.mdx +2 -3
  36. package/docs/self-hosting/advanced/auth/next-auth/casdoor.mdx +4 -7
  37. package/docs/self-hosting/advanced/auth/next-auth/casdoor.zh-CN.mdx +0 -3
  38. package/docs/self-hosting/advanced/auth/next-auth/cloudflare-zero-trust.mdx +2 -3
  39. package/docs/self-hosting/advanced/auth/next-auth/cloudflare-zero-trust.zh-CN.mdx +2 -2
  40. package/docs/self-hosting/advanced/auth/next-auth/github.mdx +2 -3
  41. package/docs/self-hosting/advanced/auth/next-auth/keycloak.mdx +12 -6
  42. package/docs/self-hosting/advanced/auth/next-auth/keycloak.zh-CN.mdx +5 -1
  43. package/docs/self-hosting/advanced/auth/next-auth/logto.mdx +8 -14
  44. package/docs/self-hosting/advanced/auth/next-auth/logto.zh-CN.mdx +6 -12
  45. package/docs/self-hosting/advanced/auth/next-auth/microsoft-entra-id.mdx +2 -3
  46. package/docs/self-hosting/advanced/auth/next-auth/wechat.mdx +2 -2
  47. package/docs/self-hosting/advanced/auth/next-auth/zitadel.mdx +2 -3
  48. package/docs/self-hosting/advanced/auth.mdx +2 -3
  49. package/docs/self-hosting/advanced/desktop.mdx +2 -1
  50. package/docs/self-hosting/advanced/desktop.zh-CN.mdx +1 -3
  51. package/docs/self-hosting/advanced/feature-flags.mdx +2 -3
  52. package/docs/self-hosting/advanced/knowledge-base.mdx +4 -3
  53. package/docs/self-hosting/advanced/model-list.mdx +11 -10
  54. package/docs/self-hosting/advanced/model-list.zh-CN.mdx +10 -9
  55. package/docs/self-hosting/advanced/observability/langfuse.mdx +2 -3
  56. package/docs/self-hosting/advanced/online-search.mdx +11 -10
  57. package/docs/self-hosting/advanced/online-search.zh-CN.mdx +7 -7
  58. package/docs/self-hosting/advanced/s3/tencent-cloud.mdx +2 -2
  59. package/docs/self-hosting/advanced/settings-url-share.mdx +2 -3
  60. package/docs/self-hosting/advanced/upstream-sync.mdx +2 -3
  61. package/docs/self-hosting/advanced/webrtc.mdx +2 -2
  62. package/docs/self-hosting/environment-variables/analytics.mdx +2 -3
  63. package/docs/self-hosting/environment-variables/auth.mdx +2 -3
  64. package/docs/self-hosting/environment-variables/basic.mdx +4 -5
  65. package/docs/self-hosting/environment-variables/basic.zh-CN.mdx +2 -2
  66. package/docs/self-hosting/environment-variables/model-provider.mdx +2 -3
  67. package/docs/self-hosting/environment-variables/s3.mdx +3 -5
  68. package/docs/self-hosting/environment-variables.mdx +2 -2
  69. package/docs/self-hosting/examples/azure-openai.mdx +2 -2
  70. package/docs/self-hosting/examples/ollama.mdx +2 -3
  71. package/docs/self-hosting/faq/no-v1-suffix.mdx +4 -4
  72. package/docs/self-hosting/faq/proxy-with-unable-to-verify-leaf-signature.mdx +2 -3
  73. package/docs/self-hosting/platform/alibaba-cloud.mdx +2 -3
  74. package/docs/self-hosting/platform/btpanel.mdx +5 -5
  75. package/docs/self-hosting/platform/btpanel.zh-CN.mdx +4 -3
  76. package/docs/self-hosting/platform/docker-compose.mdx +2 -3
  77. package/docs/self-hosting/platform/docker-compose.zh-CN.mdx +0 -2
  78. package/docs/self-hosting/platform/docker.mdx +2 -2
  79. package/docs/self-hosting/platform/netlify.mdx +2 -4
  80. package/docs/self-hosting/platform/netlify.zh-CN.mdx +2 -2
  81. package/docs/self-hosting/platform/railway.mdx +2 -3
  82. package/docs/self-hosting/platform/repocloud.mdx +2 -3
  83. package/docs/self-hosting/platform/sealos.mdx +2 -2
  84. package/docs/self-hosting/platform/tencentcloud-lighthouse.mdx +2 -3
  85. package/docs/self-hosting/platform/vercel.mdx +2 -3
  86. package/docs/self-hosting/platform/zeabur.mdx +2 -2
  87. package/docs/self-hosting/server-database/docker-compose.mdx +65 -44
  88. package/docs/self-hosting/server-database/docker-compose.zh-CN.mdx +48 -55
  89. package/docs/self-hosting/server-database/docker.mdx +2 -2
  90. package/docs/self-hosting/server-database/docker.zh-CN.mdx +2 -2
  91. package/docs/self-hosting/server-database/dokploy.mdx +4 -5
  92. package/docs/self-hosting/server-database/dokploy.zh-CN.mdx +137 -138
  93. package/docs/self-hosting/server-database/netlify.mdx +2 -2
  94. package/docs/self-hosting/server-database/netlify.zh-CN.mdx +2 -2
  95. package/docs/self-hosting/server-database/railway.mdx +2 -2
  96. package/docs/self-hosting/server-database/repocloud.mdx +2 -2
  97. package/docs/self-hosting/server-database/sealos.mdx +4 -5
  98. package/docs/self-hosting/server-database/sealos.zh-CN.mdx +18 -20
  99. package/docs/self-hosting/server-database/vercel.mdx +5 -3
  100. package/docs/self-hosting/server-database/vercel.zh-CN.mdx +2 -2
  101. package/docs/self-hosting/server-database/zeabur.mdx +2 -2
  102. package/docs/self-hosting/server-database.mdx +1 -1
  103. package/docs/self-hosting/server-database.zh-CN.mdx +2 -1
  104. package/docs/self-hosting/start.mdx +2 -2
  105. package/docs/self-hosting/start.zh-CN.mdx +2 -2
  106. package/docs/usage/agents/agent-organization.mdx +2 -2
  107. package/docs/usage/agents/concepts.mdx +4 -5
  108. package/docs/usage/agents/concepts.zh-CN.mdx +2 -2
  109. package/docs/usage/agents/custom-agent.mdx +3 -4
  110. package/docs/usage/agents/custom-agent.zh-CN.mdx +1 -1
  111. package/docs/usage/agents/model.mdx +5 -5
  112. package/docs/usage/agents/model.zh-CN.mdx +3 -5
  113. package/docs/usage/agents/prompt.mdx +4 -5
  114. package/docs/usage/agents/topics.mdx +3 -4
  115. package/docs/usage/agents/topics.zh-CN.mdx +1 -1
  116. package/docs/usage/features/agent-market.mdx +3 -11
  117. package/docs/usage/features/agent-market.zh-CN.mdx +2 -7
  118. package/docs/usage/features/artifacts.mdx +2 -2
  119. package/docs/usage/features/auth.mdx +2 -3
  120. package/docs/usage/features/cot.mdx +2 -2
  121. package/docs/usage/features/database.mdx +2 -2
  122. package/docs/usage/features/knowledge-base.mdx +4 -3
  123. package/docs/usage/features/knowledge-base.zh-CN.mdx +2 -1
  124. package/docs/usage/features/local-llm.mdx +2 -3
  125. package/docs/usage/features/mobile.mdx +2 -2
  126. package/docs/usage/features/more.mdx +2 -3
  127. package/docs/usage/features/multi-ai-providers.mdx +2 -3
  128. package/docs/usage/features/plugin-system.mdx +3 -11
  129. package/docs/usage/features/plugin-system.zh-CN.mdx +1 -8
  130. package/docs/usage/features/pwa.mdx +4 -4
  131. package/docs/usage/features/pwa.zh-CN.mdx +2 -1
  132. package/docs/usage/features/text-to-image.mdx +3 -11
  133. package/docs/usage/features/text-to-image.zh-CN.mdx +3 -10
  134. package/docs/usage/features/theme.mdx +2 -3
  135. package/docs/usage/features/tts.mdx +3 -11
  136. package/docs/usage/features/tts.zh-CN.mdx +1 -8
  137. package/docs/usage/features/vision.mdx +3 -11
  138. package/docs/usage/features/vision.zh-CN.mdx +1 -8
  139. package/docs/usage/foundation/basic.mdx +2 -3
  140. package/docs/usage/foundation/share.mdx +2 -3
  141. package/docs/usage/foundation/text2image.mdx +2 -2
  142. package/docs/usage/foundation/translate.mdx +2 -2
  143. package/docs/usage/foundation/tts-stt.mdx +2 -2
  144. package/docs/usage/foundation/vision.mdx +2 -3
  145. package/docs/usage/plugins/basic-usage.mdx +2 -3
  146. package/docs/usage/plugins/custom-plugin.mdx +2 -2
  147. package/docs/usage/plugins/development.mdx +2 -4
  148. package/docs/usage/plugins/store.mdx +2 -2
  149. package/docs/usage/providers/ai21.mdx +2 -2
  150. package/docs/usage/providers/anthropic.mdx +2 -3
  151. package/docs/usage/providers/anthropic.zh-CN.mdx +2 -2
  152. package/docs/usage/providers/azure.mdx +2 -3
  153. package/docs/usage/providers/azureai.mdx +4 -2
  154. package/docs/usage/providers/azureai.zh-CN.mdx +2 -1
  155. package/docs/usage/providers/baichuan.mdx +2 -3
  156. package/docs/usage/providers/bedrock.mdx +2 -3
  157. package/docs/usage/providers/cloudflare.mdx +3 -2
  158. package/docs/usage/providers/deepseek.mdx +2 -2
  159. package/docs/usage/providers/fireworksai.mdx +2 -2
  160. package/docs/usage/providers/giteeai.mdx +2 -2
  161. package/docs/usage/providers/github.mdx +1 -1
  162. package/docs/usage/providers/github.zh-CN.mdx +1 -1
  163. package/docs/usage/providers/google.mdx +2 -3
  164. package/docs/usage/providers/groq.mdx +2 -2
  165. package/docs/usage/providers/hunyuan.mdx +2 -2
  166. package/docs/usage/providers/infiniai.zh-CN.mdx +3 -1
  167. package/docs/usage/providers/internlm.mdx +2 -2
  168. package/docs/usage/providers/jina.mdx +4 -3
  169. package/docs/usage/providers/jina.zh-CN.mdx +2 -2
  170. package/docs/usage/providers/lmstudio.mdx +2 -2
  171. package/docs/usage/providers/lmstudio.zh-CN.mdx +2 -4
  172. package/docs/usage/providers/minimax.mdx +2 -3
  173. package/docs/usage/providers/minimax.zh-CN.mdx +2 -2
  174. package/docs/usage/providers/mistral.mdx +2 -3
  175. package/docs/usage/providers/modelscope.mdx +4 -0
  176. package/docs/usage/providers/modelscope.zh-CN.mdx +4 -0
  177. package/docs/usage/providers/moonshot.mdx +2 -3
  178. package/docs/usage/providers/novita.mdx +2 -3
  179. package/docs/usage/providers/novita.zh-CN.mdx +2 -2
  180. package/docs/usage/providers/nvidia.mdx +3 -2
  181. package/docs/usage/providers/ollama/gemma.mdx +2 -3
  182. package/docs/usage/providers/ollama/gemma.zh-CN.mdx +2 -2
  183. package/docs/usage/providers/ollama.mdx +2 -2
  184. package/docs/usage/providers/openai.mdx +5 -5
  185. package/docs/usage/providers/openai.zh-CN.mdx +3 -3
  186. package/docs/usage/providers/openrouter.mdx +2 -3
  187. package/docs/usage/providers/perplexity.mdx +2 -2
  188. package/docs/usage/providers/ppio.mdx +5 -6
  189. package/docs/usage/providers/ppio.zh-CN.mdx +6 -6
  190. package/docs/usage/providers/qiniu.mdx +6 -6
  191. package/docs/usage/providers/qiniu.zh-CN.mdx +2 -1
  192. package/docs/usage/providers/qwen.mdx +2 -4
  193. package/docs/usage/providers/sambanova.mdx +2 -1
  194. package/docs/usage/providers/sensenova.mdx +2 -2
  195. package/docs/usage/providers/siliconcloud.mdx +2 -2
  196. package/docs/usage/providers/stepfun.mdx +2 -3
  197. package/docs/usage/providers/taichu.mdx +2 -3
  198. package/docs/usage/providers/togetherai.mdx +2 -2
  199. package/docs/usage/providers/vllm.mdx +15 -12
  200. package/docs/usage/providers/vllm.zh-CN.mdx +9 -7
  201. package/docs/usage/providers/volcengine.mdx +16 -14
  202. package/docs/usage/providers/wenxin.mdx +2 -2
  203. package/docs/usage/providers/xai.mdx +2 -2
  204. package/docs/usage/providers/zeroone.mdx +2 -3
  205. package/docs/usage/providers/zeroone.zh-CN.mdx +2 -2
  206. package/docs/usage/providers/zhipu.mdx +2 -3
  207. package/docs/usage/providers/zhipu.zh-CN.mdx +1 -1
  208. package/docs/usage/providers.mdx +2 -3
  209. package/docs/usage/start.mdx +2 -3
  210. package/docs/usage/tools-calling/anthropic.mdx +2 -2
  211. package/docs/usage/tools-calling/anthropic.zh-CN.mdx +2 -2
  212. package/docs/usage/tools-calling/google.mdx +2 -2
  213. package/docs/usage/tools-calling/google.zh-CN.mdx +4 -4
  214. package/docs/usage/tools-calling/groq.zh-CN.mdx +2 -2
  215. package/docs/usage/tools-calling/openai.mdx +2 -2
  216. package/docs/usage/tools-calling/openai.zh-CN.mdx +2 -2
  217. package/package.json +1 -1
  218. package/packages/web-crawler/src/crawImpl/exa.ts +93 -0
  219. package/packages/web-crawler/src/crawImpl/firecrawl.ts +97 -0
  220. package/packages/web-crawler/src/crawImpl/index.ts +6 -0
  221. package/packages/web-crawler/src/crawImpl/tavily.ts +94 -0
  222. package/src/app/(backend)/webapi/user/avatar/[id]/[image]/route.ts +1 -1
  223. package/src/app/[variants]/(main)/repos/[id]/_layout/Mobile.tsx +7 -7
  224. package/src/config/aiModels/deepseek.ts +1 -0
  225. package/src/config/aiModels/hunyuan.ts +1 -0
  226. package/src/config/aiModels/mistral.ts +1 -2
  227. package/src/config/aiModels/modelscope.ts +3 -3
  228. package/src/config/aiModels/novita.ts +23 -22
  229. package/src/config/aiModels/openrouter.ts +1 -0
  230. package/src/config/aiModels/qwen.ts +11 -11
  231. package/src/config/aiModels/siliconcloud.ts +7 -6
  232. package/src/config/aiModels/vertexai.ts +2 -2
  233. package/src/config/aiModels/wenxin.ts +1 -2
  234. package/src/config/modelProviders/baichuan.ts +3 -0
  235. package/src/config/modelProviders/fireworksai.ts +3 -0
  236. package/src/config/modelProviders/giteeai.ts +3 -0
  237. package/src/config/modelProviders/github.ts +1 -2
  238. package/src/config/modelProviders/groq.ts +0 -3
  239. package/src/config/modelProviders/hunyuan.ts +3 -0
  240. package/src/config/modelProviders/infiniai.ts +0 -3
  241. package/src/config/modelProviders/internlm.ts +3 -0
  242. package/src/config/modelProviders/minimax.ts +3 -4
  243. package/src/config/modelProviders/modelscope.ts +3 -3
  244. package/src/config/modelProviders/moonshot.ts +0 -7
  245. package/src/config/modelProviders/novita.ts +3 -0
  246. package/src/config/modelProviders/openrouter.ts +0 -4
  247. package/src/config/modelProviders/perplexity.ts +0 -3
  248. package/src/config/modelProviders/qiniu.ts +0 -3
  249. package/src/config/modelProviders/qwen.ts +0 -3
  250. package/src/config/modelProviders/sensenova.ts +3 -0
  251. package/src/config/modelProviders/siliconcloud.ts +0 -3
  252. package/src/config/modelProviders/spark.ts +0 -5
  253. package/src/config/modelProviders/stepfun.ts +3 -6
  254. package/src/config/modelProviders/taichu.ts +3 -0
  255. package/src/config/modelProviders/tencentcloud.ts +3 -0
  256. package/src/config/modelProviders/togetherai.ts +3 -0
  257. package/src/config/modelProviders/upstage.ts +3 -0
  258. package/src/config/modelProviders/wenxin.ts +3 -4
  259. package/src/config/modelProviders/xai.ts +0 -3
  260. package/src/config/modelProviders/zhipu.ts +3 -0
  261. package/src/database/client/migrations.json +10 -0
  262. package/src/database/migrations/0023_remove_param_and_doubao.sql +6 -0
  263. package/src/database/migrations/meta/0014_snapshot.json +182 -539
  264. package/src/database/migrations/meta/0016_snapshot.json +182 -539
  265. package/src/database/migrations/meta/0023_snapshot.json +5340 -0
  266. package/src/database/migrations/meta/_journal.json +7 -0
  267. package/src/database/repositories/dataImporter/__tests__/fixtures/with-client-id.json +13 -58
  268. package/src/features/ChatInput/ActionBar/Model/ControlsForm.tsx +1 -8
  269. package/src/features/ChatInput/ActionBar/Model/index.tsx +8 -16
  270. package/src/features/ChatInput/ActionBar/Search/Controls.tsx +4 -12
  271. package/src/features/ChatInput/ActionBar/Search/FCSearchModel.tsx +1 -7
  272. package/src/features/ChatInput/ActionBar/Search/index.tsx +2 -4
  273. package/src/features/ModelSwitchPanel/index.tsx +1 -4
  274. package/src/libs/model-runtime/anthropic/index.test.ts +4 -2
  275. package/src/libs/model-runtime/utils/streams/anthropic.ts +12 -11
  276. package/src/libs/model-runtime/utils/streams/openai.ts +6 -4
  277. package/src/libs/model-runtime/utils/streams/protocol.ts +1 -1
  278. package/src/libs/model-runtime/utils/streams/spark.test.ts +1 -1
  279. package/src/libs/model-runtime/utils/streams/spark.ts +1 -2
  280. package/src/middleware.ts +1 -1
  281. package/src/server/services/search/impls/bocha/index.ts +124 -0
  282. package/src/server/services/search/impls/bocha/type.ts +47 -0
  283. package/src/server/services/search/impls/exa/index.ts +129 -0
  284. package/src/server/services/search/impls/exa/type.ts +39 -0
  285. package/src/server/services/search/impls/firecrawl/index.ts +128 -0
  286. package/src/server/services/search/impls/firecrawl/type.ts +35 -0
  287. package/src/server/services/search/impls/index.ts +31 -0
  288. package/src/server/services/search/impls/jina/index.ts +109 -0
  289. package/src/server/services/search/impls/jina/type.ts +26 -0
  290. package/src/server/services/search/impls/tavily/index.ts +124 -0
  291. package/src/server/services/search/impls/tavily/type.ts +36 -0
  292. package/src/server/services/user/index.ts +3 -4
  293. package/src/services/__tests__/assistant.test.ts +4 -6
  294. package/src/services/__tests__/tool.test.ts +3 -1
  295. package/src/store/user/slices/auth/selectors.ts +1 -1
  296. package/src/store/user/slices/common/action.test.ts +1 -1
  297. package/src/tools/web-browsing/index.ts +1 -7
@@ -0,0 +1,129 @@
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 { ExaSearchParameters, ExaResponse } from './type';
9
+
10
+ const log = debug('lobe-search:Exa');
11
+
12
+ /**
13
+ * Exa implementation of the search service
14
+ * Primarily used for web crawling
15
+ */
16
+ export class ExaImpl implements SearchServiceImpl {
17
+ private get apiKey(): string | undefined {
18
+ return process.env.EXA_API_KEY;
19
+ }
20
+
21
+ private get baseUrl(): string {
22
+ // Assuming the base URL is consistent with the crawl endpoint
23
+ return 'https://api.exa.ai';
24
+ }
25
+
26
+ async query(query: string, params: SearchParams = {}): Promise<UniformSearchResponse> {
27
+ log('Starting Exa query with query: "%s", params: %o', query, params);
28
+ const endpoint = urlJoin(this.baseUrl, '/search');
29
+
30
+ const defaultQueryParams: ExaSearchParameters = {
31
+ numResults: 15,
32
+ query,
33
+ type: 'auto',
34
+ };
35
+
36
+ let body: ExaSearchParameters = {
37
+ ...defaultQueryParams,
38
+ ...(params?.searchTimeRange && params.searchTimeRange !== 'anytime'
39
+ ? (() => {
40
+ const now = Date.now();
41
+ const days = { day: 1, month: 30, week: 7, year: 365 }[params.searchTimeRange!];
42
+
43
+ if (days === undefined) return {};
44
+
45
+ return {
46
+ endPublishedDate: new Date(now).toISOString(),
47
+ startPublishedDate: new Date(now - days * 86_400 * 1000).toISOString(),
48
+ };
49
+ })()
50
+ : {}),
51
+ category:
52
+ // Exa 只支持 news 类型
53
+ params?.searchCategories?.filter(cat => ['news'].includes(cat))?.[0],
54
+ };
55
+
56
+ log('Constructed request body: %o', body);
57
+
58
+ let response: Response;
59
+ const startAt = Date.now();
60
+ let costTime = 0;
61
+ try {
62
+ log('Sending request to endpoint: %s', endpoint);
63
+ response = await fetch(endpoint, {
64
+ body: JSON.stringify(body),
65
+ headers: {
66
+ 'Content-Type': 'application/json',
67
+ 'x-api-key': this.apiKey ? this.apiKey : '',
68
+ },
69
+ method: 'POST',
70
+ });
71
+ log('Received response with status: %d', response.status);
72
+ costTime = Date.now() - startAt;
73
+ } catch (error) {
74
+ log.extend('error')('Exa fetch error: %o', error);
75
+ throw new TRPCError({
76
+ cause: error,
77
+ code: 'SERVICE_UNAVAILABLE',
78
+ message: 'Failed to connect to Exa.',
79
+ });
80
+ }
81
+
82
+ if (!response.ok) {
83
+ const errorBody = await response.text();
84
+ log.extend('error')(
85
+ `Exa request failed with status ${response.status}: %s`,
86
+ errorBody.length > 200 ? `${errorBody.slice(0, 200)}...` : errorBody,
87
+ );
88
+ throw new TRPCError({
89
+ cause: errorBody,
90
+ code: 'SERVICE_UNAVAILABLE',
91
+ message: `Exa request failed: ${response.statusText}`,
92
+ });
93
+ }
94
+
95
+ try {
96
+ const exaResponse = (await response.json()) as ExaResponse;
97
+
98
+ log('Parsed Exa response: %o', exaResponse);
99
+
100
+ const mappedResults = (exaResponse.results || []).map(
101
+ (result): UniformSearchResult => ({
102
+ category: body.category || 'general', // Default category
103
+ content: result.text || '', // Prioritize content, fallback to snippet
104
+ engines: ['exa'], // Use 'exa' as the engine name
105
+ parsedUrl: result.url ? new URL(result.url).hostname : '', // Basic URL parsing
106
+ score: result.score || 0, // Default score to 0 if undefined
107
+ title: result.title || '',
108
+ url: result.url,
109
+ }),
110
+ );
111
+
112
+ log('Mapped %d results to SearchResult format', mappedResults.length);
113
+
114
+ return {
115
+ costTime,
116
+ query: query,
117
+ resultNumbers: mappedResults.length,
118
+ results: mappedResults,
119
+ };
120
+ } catch (error) {
121
+ log.extend('error')('Error parsing Exa response: %o', error);
122
+ throw new TRPCError({
123
+ cause: error,
124
+ code: 'INTERNAL_SERVER_ERROR',
125
+ message: 'Failed to parse Exa response.',
126
+ });
127
+ }
128
+ }
129
+ }
@@ -0,0 +1,39 @@
1
+ export interface ExaSearchParameters {
2
+ category?: string;
3
+ endCrawlDate?: string;
4
+ endPublishedDate?: string;
5
+ excludeDomains?: string[];
6
+ excludeText?: string[];
7
+ includeDomains?: string[];
8
+ includeText?: string[];
9
+ numResults?: number;
10
+ query: string;
11
+ startCrawlDate?: string;
12
+ startPublishedDate?: string;
13
+ type?: string;
14
+ }
15
+
16
+ interface ExaCostDollars {
17
+ total: number;
18
+ }
19
+
20
+ interface ExaResults {
21
+ author?: string | null;
22
+ favicon?: string;
23
+ id?: string;
24
+ image?: string;
25
+ publishedDate?: string | null;
26
+ score?: number | null;
27
+ summery?: string;
28
+ text: string;
29
+ title: string;
30
+ url: string;
31
+ }
32
+
33
+ export interface ExaResponse {
34
+ costDollars?: ExaCostDollars;
35
+ requestId?: string;
36
+ resolvedSearchType?: string;
37
+ results: ExaResults[];
38
+ searchType?: string;
39
+ }
@@ -0,0 +1,128 @@
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 { FirecrawlSearchParameters, FirecrawlResponse } from './type';
9
+
10
+ const log = debug('lobe-search:Firecrawl');
11
+
12
+ const timeRangeMapping = {
13
+ day: 'qdr:d',
14
+ month: 'qdr:m',
15
+ week: 'qdr:w',
16
+ year: 'qdr:y',
17
+ };
18
+
19
+ /**
20
+ * Firecrawl implementation of the search service
21
+ * Primarily used for web crawling
22
+ */
23
+ export class FirecrawlImpl implements SearchServiceImpl {
24
+ private get apiKey(): string | undefined {
25
+ return process.env.FIRECRAWL_API_KEY;
26
+ }
27
+
28
+ private get baseUrl(): string {
29
+ // Assuming the base URL is consistent with the crawl endpoint
30
+ return process.env.FIRECRAWL_URL || 'https://api.firecrawl.dev/v1';
31
+ }
32
+
33
+ async query(query: string, params: SearchParams = {}): Promise<UniformSearchResponse> {
34
+ log('Starting Firecrawl query with query: "%s", params: %o', query, params);
35
+ const endpoint = urlJoin(this.baseUrl, '/search');
36
+
37
+ const defaultQueryParams: FirecrawlSearchParameters = {
38
+ limit: 15,
39
+ query,
40
+ /*
41
+ scrapeOptions: {
42
+ formats: ["markdown"]
43
+ },
44
+ */
45
+ };
46
+
47
+ let body: FirecrawlSearchParameters = {
48
+ ...defaultQueryParams,
49
+ tbs:
50
+ params?.searchTimeRange && params.searchTimeRange !== 'anytime'
51
+ ? timeRangeMapping[params.searchTimeRange as keyof typeof timeRangeMapping] ?? undefined
52
+ : undefined,
53
+ };
54
+
55
+ log('Constructed request body: %o', body);
56
+
57
+ let response: Response;
58
+ const startAt = Date.now();
59
+ let costTime = 0;
60
+ try {
61
+ log('Sending request to endpoint: %s', endpoint);
62
+ response = await fetch(endpoint, {
63
+ body: JSON.stringify(body),
64
+ headers: {
65
+ 'Authorization': this.apiKey ? `Bearer ${this.apiKey}` : '',
66
+ 'Content-Type': 'application/json',
67
+ },
68
+ method: 'POST',
69
+ });
70
+ log('Received response with status: %d', response.status);
71
+ costTime = Date.now() - startAt;
72
+ } catch (error) {
73
+ log.extend('error')('Firecrawl fetch error: %o', error);
74
+ throw new TRPCError({
75
+ cause: error,
76
+ code: 'SERVICE_UNAVAILABLE',
77
+ message: 'Failed to connect to Firecrawl.',
78
+ });
79
+ }
80
+
81
+ if (!response.ok) {
82
+ const errorBody = await response.text();
83
+ log.extend('error')(
84
+ `Firecrawl request failed with status ${response.status}: %s`,
85
+ errorBody.length > 200 ? `${errorBody.slice(0, 200)}...` : errorBody,
86
+ );
87
+ throw new TRPCError({
88
+ cause: errorBody,
89
+ code: 'SERVICE_UNAVAILABLE',
90
+ message: `Firecrawl request failed: ${response.statusText}`,
91
+ });
92
+ }
93
+
94
+ try {
95
+ const firecrawlResponse = (await response.json()) as FirecrawlResponse;
96
+
97
+ log('Parsed Firecrawl response: %o', firecrawlResponse);
98
+
99
+ const mappedResults = (firecrawlResponse.data || []).map(
100
+ (result): UniformSearchResult => ({
101
+ category: 'general', // Default category
102
+ content: result.description || '', // Prioritize content, fallback to snippet
103
+ engines: ['firecrawl'], // Use 'firecrawl' as the engine name
104
+ parsedUrl: result.url ? new URL(result.url).hostname : '', // Basic URL parsing
105
+ score: 1, // Default score to 1
106
+ title: result.title || '',
107
+ url: result.url,
108
+ }),
109
+ );
110
+
111
+ log('Mapped %d results to SearchResult format', mappedResults.length);
112
+
113
+ return {
114
+ costTime,
115
+ query: query,
116
+ resultNumbers: mappedResults.length,
117
+ results: mappedResults,
118
+ };
119
+ } catch (error) {
120
+ log.extend('error')('Error parsing Firecrawl response: %o', error);
121
+ throw new TRPCError({
122
+ cause: error,
123
+ code: 'INTERNAL_SERVER_ERROR',
124
+ message: 'Failed to parse Firecrawl response.',
125
+ });
126
+ }
127
+ }
128
+ }
@@ -0,0 +1,35 @@
1
+ interface FirecrawlScrapeOptions {
2
+ formats: string[];
3
+ }
4
+
5
+ export interface FirecrawlSearchParameters {
6
+ country?: string;
7
+ lang?: string;
8
+ limit?: number;
9
+ query: string;
10
+ scrapeOptions?: FirecrawlScrapeOptions;
11
+ tbs?: string;
12
+ timeout?: number;
13
+ }
14
+
15
+ interface FirecrawlMetadata {
16
+ description?: string;
17
+ sourceURL?: string;
18
+ statusCode?: number;
19
+ title: string;
20
+ }
21
+
22
+ interface FirecrawlData {
23
+ description?: string;
24
+ html?: string;
25
+ links?: string[];
26
+ markdown?: string;
27
+ metadata?: FirecrawlMetadata;
28
+ title?: string;
29
+ url: string;
30
+ }
31
+
32
+ export interface FirecrawlResponse {
33
+ data: FirecrawlData[];
34
+ success?: boolean;
35
+ }
@@ -1,13 +1,24 @@
1
+ import { BochaImpl } from './bocha';
2
+ import { ExaImpl } from './exa';
3
+ import { FirecrawlImpl } from './firecrawl';
4
+ import { JinaImpl } from './jina';
1
5
  import { Search1APIImpl } from './search1api';
2
6
  import { SearXNGImpl } from './searxng';
7
+ import { TavilyImpl } from './tavily';
8
+
3
9
  import { SearchServiceImpl } from './type';
4
10
 
5
11
  /**
6
12
  * Available search service implementations
7
13
  */
8
14
  export enum SearchImplType {
15
+ Bocha = 'bocha',
16
+ Exa = 'exa',
17
+ Firecrawl = 'firecrawl',
18
+ Jina = 'jina',
9
19
  SearXNG = 'searxng',
10
20
  Search1API = 'search1api',
21
+ Tavily = 'tavily',
11
22
  }
12
23
 
13
24
  /**
@@ -17,10 +28,30 @@ export const createSearchServiceImpl = (
17
28
  type: SearchImplType = SearchImplType.SearXNG,
18
29
  ): SearchServiceImpl => {
19
30
  switch (type) {
31
+ case SearchImplType.Bocha: {
32
+ return new BochaImpl();
33
+ }
34
+
35
+ case SearchImplType.Exa: {
36
+ return new ExaImpl();
37
+ }
38
+
39
+ case SearchImplType.Firecrawl: {
40
+ return new FirecrawlImpl();
41
+ }
42
+
43
+ case SearchImplType.Jina: {
44
+ return new JinaImpl();
45
+ }
46
+
20
47
  case SearchImplType.SearXNG: {
21
48
  return new SearXNGImpl();
22
49
  }
23
50
 
51
+ case SearchImplType.Tavily: {
52
+ return new TavilyImpl();
53
+ }
54
+
24
55
  default: {
25
56
  return new Search1APIImpl();
26
57
  }
@@ -0,0 +1,109 @@
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 { JinaSearchParameters, JinaResponse } from './type';
9
+
10
+ const log = debug('lobe-search:Jina');
11
+
12
+ /**
13
+ * Jina implementation of the search service
14
+ * Primarily used for web crawling
15
+ */
16
+ export class JinaImpl implements SearchServiceImpl {
17
+ private get apiKey(): string | undefined {
18
+ return process.env.JINA_READER_API_KEY || process.env.JINA_API_KEY;
19
+ }
20
+
21
+ private get baseUrl(): string {
22
+ // Assuming the base URL is consistent with the crawl endpoint
23
+ return 'https://s.jina.ai';
24
+ }
25
+
26
+ async query(query: string, params: SearchParams = {}): Promise<UniformSearchResponse> {
27
+ log('Starting Jina query with query: "%s", params: %o', query, params);
28
+ const endpoint = urlJoin(this.baseUrl, '/');
29
+
30
+ let body: JinaSearchParameters = {
31
+ q: query,
32
+ };
33
+
34
+ log('Constructed request body: %o', body);
35
+
36
+ let response: Response;
37
+ const startAt = Date.now();
38
+ let costTime = 0;
39
+ try {
40
+ log('Sending request to endpoint: %s', endpoint);
41
+ response = await fetch(endpoint, {
42
+ body: JSON.stringify(body),
43
+ headers: {
44
+ 'Accept': 'application/json',
45
+ 'Authorization': this.apiKey ? `Bearer ${this.apiKey}` : '',
46
+ 'Content-Type': 'application/json',
47
+ 'X-Respond-With': 'no-content',
48
+ },
49
+ method: 'POST',
50
+ });
51
+ log('Received response with status: %d', response.status);
52
+ costTime = Date.now() - startAt;
53
+ } catch (error) {
54
+ log.extend('error')('Jina fetch error: %o', error);
55
+ throw new TRPCError({
56
+ cause: error,
57
+ code: 'SERVICE_UNAVAILABLE',
58
+ message: 'Failed to connect to Jina.',
59
+ });
60
+ }
61
+
62
+ if (!response.ok) {
63
+ const errorBody = await response.text();
64
+ log.extend('error')(
65
+ `Jina request failed with status ${response.status}: %s`,
66
+ errorBody.length > 200 ? `${errorBody.slice(0, 200)}...` : errorBody,
67
+ );
68
+ throw new TRPCError({
69
+ cause: errorBody,
70
+ code: 'SERVICE_UNAVAILABLE',
71
+ message: `Jina request failed: ${response.statusText}`,
72
+ });
73
+ }
74
+
75
+ try {
76
+ const jinaResponse = (await response.json()) as JinaResponse;
77
+
78
+ log('Parsed Jina response: %o', jinaResponse);
79
+
80
+ const mappedResults = (jinaResponse.data || []).map(
81
+ (result): UniformSearchResult => ({
82
+ category: 'general', // Default category
83
+ content: result.description || '', // Prioritize content, fallback to snippet
84
+ engines: ['jina'], // Use 'jina' as the engine name
85
+ parsedUrl: result.url ? new URL(result.url).hostname : '', // Basic URL parsing
86
+ score: 1, // Default score to 1
87
+ title: result.title || '',
88
+ url: result.url,
89
+ }),
90
+ );
91
+
92
+ log('Mapped %d results to SearchResult format', mappedResults.length);
93
+
94
+ return {
95
+ costTime,
96
+ query: query,
97
+ resultNumbers: mappedResults.length,
98
+ results: mappedResults,
99
+ };
100
+ } catch (error) {
101
+ log.extend('error')('Error parsing Jina response: %o', error);
102
+ throw new TRPCError({
103
+ cause: error,
104
+ code: 'INTERNAL_SERVER_ERROR',
105
+ message: 'Failed to parse Jina response.',
106
+ });
107
+ }
108
+ }
109
+ }
@@ -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
+ }