@lobehub/chat 1.140.0 → 1.141.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 (125) hide show
  1. package/CHANGELOG.md +68 -0
  2. package/changelog/v1.json +24 -0
  3. package/locales/ar/chat.json +13 -0
  4. package/locales/ar/common.json +1 -0
  5. package/locales/ar/components.json +4 -0
  6. package/locales/ar/file.json +2 -2
  7. package/locales/bg-BG/chat.json +13 -0
  8. package/locales/bg-BG/common.json +1 -0
  9. package/locales/bg-BG/components.json +4 -0
  10. package/locales/bg-BG/file.json +2 -2
  11. package/locales/de-DE/chat.json +13 -0
  12. package/locales/de-DE/common.json +1 -0
  13. package/locales/de-DE/components.json +4 -0
  14. package/locales/de-DE/file.json +2 -2
  15. package/locales/en-US/chat.json +13 -0
  16. package/locales/en-US/common.json +1 -0
  17. package/locales/en-US/components.json +4 -0
  18. package/locales/en-US/file.json +2 -2
  19. package/locales/es-ES/chat.json +13 -0
  20. package/locales/es-ES/common.json +1 -0
  21. package/locales/es-ES/components.json +4 -0
  22. package/locales/es-ES/file.json +2 -2
  23. package/locales/fa-IR/chat.json +13 -0
  24. package/locales/fa-IR/common.json +1 -0
  25. package/locales/fa-IR/components.json +4 -0
  26. package/locales/fa-IR/file.json +2 -2
  27. package/locales/fr-FR/chat.json +13 -0
  28. package/locales/fr-FR/common.json +1 -0
  29. package/locales/fr-FR/components.json +4 -0
  30. package/locales/fr-FR/file.json +2 -2
  31. package/locales/it-IT/chat.json +13 -0
  32. package/locales/it-IT/common.json +1 -0
  33. package/locales/it-IT/components.json +4 -0
  34. package/locales/it-IT/file.json +2 -2
  35. package/locales/ja-JP/chat.json +13 -0
  36. package/locales/ja-JP/common.json +1 -0
  37. package/locales/ja-JP/components.json +4 -0
  38. package/locales/ja-JP/file.json +2 -2
  39. package/locales/ko-KR/chat.json +13 -0
  40. package/locales/ko-KR/common.json +1 -0
  41. package/locales/ko-KR/components.json +4 -0
  42. package/locales/ko-KR/file.json +2 -2
  43. package/locales/nl-NL/chat.json +13 -0
  44. package/locales/nl-NL/common.json +1 -0
  45. package/locales/nl-NL/components.json +4 -0
  46. package/locales/nl-NL/file.json +2 -2
  47. package/locales/pl-PL/chat.json +13 -0
  48. package/locales/pl-PL/common.json +1 -0
  49. package/locales/pl-PL/components.json +4 -0
  50. package/locales/pl-PL/file.json +2 -2
  51. package/locales/pt-BR/chat.json +13 -0
  52. package/locales/pt-BR/common.json +1 -0
  53. package/locales/pt-BR/components.json +4 -0
  54. package/locales/pt-BR/file.json +2 -2
  55. package/locales/ru-RU/chat.json +13 -0
  56. package/locales/ru-RU/common.json +1 -0
  57. package/locales/ru-RU/components.json +4 -0
  58. package/locales/ru-RU/file.json +2 -2
  59. package/locales/tr-TR/chat.json +13 -0
  60. package/locales/tr-TR/common.json +1 -0
  61. package/locales/tr-TR/components.json +4 -0
  62. package/locales/tr-TR/file.json +2 -2
  63. package/locales/vi-VN/chat.json +13 -0
  64. package/locales/vi-VN/common.json +1 -0
  65. package/locales/vi-VN/components.json +4 -0
  66. package/locales/vi-VN/file.json +2 -2
  67. package/locales/zh-CN/chat.json +13 -0
  68. package/locales/zh-CN/common.json +1 -0
  69. package/locales/zh-CN/components.json +4 -0
  70. package/locales/zh-CN/file.json +2 -2
  71. package/locales/zh-TW/chat.json +13 -0
  72. package/locales/zh-TW/common.json +1 -0
  73. package/locales/zh-TW/components.json +4 -0
  74. package/locales/zh-TW/file.json +2 -2
  75. package/next.config.ts +5 -6
  76. package/package.json +8 -2
  77. package/packages/context-engine/src/__tests__/pipeline.test.ts +7 -27
  78. package/packages/context-engine/src/pipeline.ts +5 -21
  79. package/packages/context-engine/src/types.ts +2 -2
  80. package/packages/database/src/models/__tests__/message.test.ts +200 -2
  81. package/packages/database/src/models/message.ts +13 -0
  82. package/packages/model-runtime/src/core/openaiCompatibleFactory/index.test.ts +313 -0
  83. package/packages/model-runtime/src/core/openaiCompatibleFactory/index.ts +21 -5
  84. package/packages/model-runtime/src/providers/azureai/index.test.ts +12 -2
  85. package/packages/model-runtime/src/providers/groq/index.test.ts +449 -0
  86. package/packages/model-runtime/src/providers/groq/index.ts +46 -0
  87. package/src/app/[variants]/(main)/_layout/Desktop/SideBar/TopActions.tsx +3 -2
  88. package/src/app/[variants]/(main)/chat/(workspace)/_layout/Desktop/ChatHeader/Tags/index.tsx +1 -1
  89. package/src/app/[variants]/(main)/files/(content)/@menu/features/KnowledgeBase/Item/index.tsx +10 -2
  90. package/src/features/ChatInput/InputEditor/index.tsx +2 -0
  91. package/src/features/Conversation/Messages/User/index.tsx +7 -17
  92. package/src/features/Conversation/components/ChatItem/ShareMessageModal/SharePdf/PdfPreview.tsx +361 -0
  93. package/src/features/Conversation/components/ChatItem/ShareMessageModal/SharePdf/index.tsx +119 -0
  94. package/src/features/Conversation/components/ChatItem/ShareMessageModal/SharePdf/style.ts +63 -0
  95. package/src/features/Conversation/components/ChatItem/ShareMessageModal/SharePdf/template.ts +24 -0
  96. package/src/features/Conversation/components/ChatItem/ShareMessageModal/SharePdf/usePdfGeneration.ts +93 -0
  97. package/src/features/Conversation/components/ShareMessageModal/ShareImage/Preview.tsx +1 -1
  98. package/src/features/Conversation/components/ShareMessageModal/index.tsx +39 -14
  99. package/src/features/FileManager/FileList/MasonryFileItem/MasonryItemWrapper.tsx +44 -0
  100. package/src/features/FileManager/FileList/MasonryFileItem/index.tsx +553 -0
  101. package/src/features/FileManager/FileList/MasonrySkeleton.tsx +57 -0
  102. package/src/features/FileManager/FileList/ToolBar/ViewSwitcher.tsx +45 -0
  103. package/src/features/FileManager/FileList/ToolBar/index.tsx +9 -1
  104. package/src/features/FileManager/FileList/index.tsx +83 -13
  105. package/src/features/FileManager/Header/FilesSearchBar.tsx +7 -2
  106. package/src/features/ShareModal/ShareImage/Preview.tsx +1 -1
  107. package/src/features/ShareModal/SharePdf/PdfPreview.tsx +361 -0
  108. package/src/features/ShareModal/SharePdf/index.tsx +194 -0
  109. package/src/features/ShareModal/SharePdf/usePdfGeneration.ts +90 -0
  110. package/src/features/ShareModal/index.tsx +40 -14
  111. package/src/features/ShareModal/style.ts +8 -5
  112. package/src/helpers/toolEngineering/index.ts +7 -1
  113. package/src/helpers/toolFilters.ts +35 -0
  114. package/src/libs/trpc/client/lambda.ts +7 -1
  115. package/src/locales/default/chat.ts +13 -0
  116. package/src/locales/default/common.ts +1 -0
  117. package/src/locales/default/components.ts +4 -0
  118. package/src/locales/default/file.ts +2 -2
  119. package/src/server/globalConfig/parseSystemAgent.ts +4 -2
  120. package/src/server/routers/lambda/exporter.ts +173 -3
  121. package/src/server/routers/lambda/message.ts +11 -0
  122. package/src/services/chat/contextEngineering.ts +1 -9
  123. package/src/store/agent/slices/chat/selectors/agent.ts +16 -6
  124. package/src/store/global/initialState.ts +2 -0
  125. package/src/store/tool/slices/builtin/selectors.ts +15 -5
@@ -2048,6 +2048,319 @@ describe('LobeOpenAICompatibleFactory', () => {
2048
2048
  });
2049
2049
  });
2050
2050
 
2051
+ describe('handleSchema option', () => {
2052
+ let instanceWithSchemaHandler: any;
2053
+ const mockSchemaHandler = vi.fn((schema: any) => {
2054
+ const filtered: any = {};
2055
+ for (const [key, value] of Object.entries(schema)) {
2056
+ if (key !== 'maxLength' && key !== 'pattern') {
2057
+ filtered[key] = value;
2058
+ }
2059
+ }
2060
+ return filtered;
2061
+ });
2062
+
2063
+ beforeEach(() => {
2064
+ mockSchemaHandler.mockClear();
2065
+ const RuntimeClass = createOpenAICompatibleRuntime({
2066
+ baseURL: 'https://api.test.com',
2067
+ generateObject: {
2068
+ handleSchema: mockSchemaHandler,
2069
+ },
2070
+ provider: 'test-provider',
2071
+ });
2072
+
2073
+ instanceWithSchemaHandler = new RuntimeClass({ apiKey: 'test-key' });
2074
+ });
2075
+
2076
+ it('should apply schema transformation with Responses API', async () => {
2077
+ const mockResponse = {
2078
+ output_text: '{"name":"Alice","age":30}',
2079
+ };
2080
+
2081
+ vi.spyOn(instanceWithSchemaHandler['client'].responses, 'create').mockResolvedValue(
2082
+ mockResponse as any,
2083
+ );
2084
+
2085
+ const payload = {
2086
+ messages: [{ content: 'Extract person', role: 'user' as const }],
2087
+ model: 'gpt-4o',
2088
+ responseApi: true,
2089
+ schema: {
2090
+ name: 'person',
2091
+ schema: {
2092
+ maxLength: 100,
2093
+ pattern: '^[a-z]+$',
2094
+ properties: {
2095
+ age: { type: 'number' },
2096
+ name: { type: 'string' },
2097
+ },
2098
+ type: 'object' as const,
2099
+ },
2100
+ },
2101
+ };
2102
+
2103
+ await instanceWithSchemaHandler.generateObject(payload);
2104
+
2105
+ expect(mockSchemaHandler).toHaveBeenCalledWith(payload.schema.schema);
2106
+ expect(instanceWithSchemaHandler['client'].responses.create).toHaveBeenCalledWith(
2107
+ expect.objectContaining({
2108
+ text: expect.objectContaining({
2109
+ format: expect.objectContaining({
2110
+ schema: {
2111
+ properties: {
2112
+ age: { type: 'number' },
2113
+ name: { type: 'string' },
2114
+ },
2115
+ type: 'object',
2116
+ },
2117
+ }),
2118
+ }),
2119
+ }),
2120
+ expect.any(Object),
2121
+ );
2122
+ });
2123
+
2124
+ it('should apply schema transformation with Chat Completions API', async () => {
2125
+ const mockResponse = {
2126
+ choices: [
2127
+ {
2128
+ message: {
2129
+ content: '{"name":"Bob","age":25}',
2130
+ },
2131
+ },
2132
+ ],
2133
+ };
2134
+
2135
+ vi.spyOn(instanceWithSchemaHandler['client'].chat.completions, 'create').mockResolvedValue(
2136
+ mockResponse as any,
2137
+ );
2138
+
2139
+ const payload = {
2140
+ messages: [{ content: 'Extract person', role: 'user' as const }],
2141
+ model: 'test-model',
2142
+ schema: {
2143
+ name: 'person',
2144
+ schema: {
2145
+ maxLength: 100,
2146
+ pattern: '^[a-z]+$',
2147
+ properties: {
2148
+ age: { type: 'number' },
2149
+ name: { type: 'string' },
2150
+ },
2151
+ type: 'object' as const,
2152
+ },
2153
+ },
2154
+ };
2155
+
2156
+ await instanceWithSchemaHandler.generateObject(payload);
2157
+
2158
+ expect(mockSchemaHandler).toHaveBeenCalledWith(payload.schema.schema);
2159
+ expect(instanceWithSchemaHandler['client'].chat.completions.create).toHaveBeenCalledWith(
2160
+ expect.objectContaining({
2161
+ response_format: expect.objectContaining({
2162
+ json_schema: expect.objectContaining({
2163
+ schema: {
2164
+ properties: {
2165
+ age: { type: 'number' },
2166
+ name: { type: 'string' },
2167
+ },
2168
+ type: 'object',
2169
+ },
2170
+ }),
2171
+ }),
2172
+ }),
2173
+ expect.any(Object),
2174
+ );
2175
+ });
2176
+
2177
+ it('should apply schema transformation with tool calling fallback', async () => {
2178
+ const RuntimeClass = createOpenAICompatibleRuntime({
2179
+ baseURL: 'https://api.test.com',
2180
+ generateObject: {
2181
+ handleSchema: mockSchemaHandler,
2182
+ useToolsCalling: true,
2183
+ },
2184
+ provider: 'test-provider',
2185
+ });
2186
+
2187
+ const instance = new RuntimeClass({ apiKey: 'test-key' });
2188
+
2189
+ const mockResponse = {
2190
+ choices: [
2191
+ {
2192
+ message: {
2193
+ tool_calls: [
2194
+ {
2195
+ function: {
2196
+ arguments: '{"name":"Charlie","age":35}',
2197
+ name: 'person',
2198
+ },
2199
+ type: 'function' as const,
2200
+ },
2201
+ ],
2202
+ },
2203
+ },
2204
+ ],
2205
+ };
2206
+
2207
+ vi.spyOn(instance['client'].chat.completions, 'create').mockResolvedValue(
2208
+ mockResponse as any,
2209
+ );
2210
+
2211
+ const payload = {
2212
+ messages: [{ content: 'Extract person', role: 'user' as const }],
2213
+ model: 'test-model',
2214
+ schema: {
2215
+ name: 'person',
2216
+ schema: {
2217
+ maxLength: 100,
2218
+ pattern: '^[a-z]+$',
2219
+ properties: {
2220
+ age: { type: 'number' },
2221
+ name: { type: 'string' },
2222
+ },
2223
+ type: 'object' as const,
2224
+ },
2225
+ },
2226
+ };
2227
+
2228
+ await instance.generateObject(payload);
2229
+
2230
+ expect(mockSchemaHandler).toHaveBeenCalledWith(payload.schema.schema);
2231
+ expect(instance['client'].chat.completions.create).toHaveBeenCalledWith(
2232
+ expect.objectContaining({
2233
+ tools: [
2234
+ expect.objectContaining({
2235
+ function: expect.objectContaining({
2236
+ parameters: {
2237
+ properties: {
2238
+ age: { type: 'number' },
2239
+ name: { type: 'string' },
2240
+ },
2241
+ type: 'object',
2242
+ },
2243
+ }),
2244
+ }),
2245
+ ],
2246
+ }),
2247
+ expect.any(Object),
2248
+ );
2249
+ });
2250
+
2251
+ it('should not apply schema transformation when handleSchema is not configured', async () => {
2252
+ const RuntimeClass = createOpenAICompatibleRuntime({
2253
+ baseURL: 'https://api.test.com',
2254
+ provider: 'test-provider',
2255
+ });
2256
+
2257
+ const instance = new RuntimeClass({ apiKey: 'test-key' });
2258
+
2259
+ const mockResponse = {
2260
+ choices: [
2261
+ {
2262
+ message: {
2263
+ content: '{"name":"Test"}',
2264
+ },
2265
+ },
2266
+ ],
2267
+ };
2268
+
2269
+ vi.spyOn(instance['client'].chat.completions, 'create').mockResolvedValue(
2270
+ mockResponse as any,
2271
+ );
2272
+
2273
+ const payload = {
2274
+ messages: [{ content: 'Extract data', role: 'user' as const }],
2275
+ model: 'test-model',
2276
+ schema: {
2277
+ name: 'test',
2278
+ schema: {
2279
+ maxLength: 100,
2280
+ properties: {
2281
+ name: { type: 'string' },
2282
+ },
2283
+ type: 'object' as const,
2284
+ },
2285
+ },
2286
+ };
2287
+
2288
+ await instance.generateObject(payload);
2289
+
2290
+ expect(instance['client'].chat.completions.create).toHaveBeenCalledWith(
2291
+ expect.objectContaining({
2292
+ response_format: expect.objectContaining({
2293
+ json_schema: expect.objectContaining({
2294
+ schema: {
2295
+ maxLength: 100,
2296
+ properties: {
2297
+ name: { type: 'string' },
2298
+ },
2299
+ type: 'object',
2300
+ },
2301
+ }),
2302
+ }),
2303
+ }),
2304
+ expect.any(Object),
2305
+ );
2306
+ });
2307
+
2308
+ it('should preserve original schema properties while filtering', async () => {
2309
+ const mockResponse = {
2310
+ output_text: '{"result":"success"}',
2311
+ };
2312
+
2313
+ vi.spyOn(instanceWithSchemaHandler['client'].responses, 'create').mockResolvedValue(
2314
+ mockResponse as any,
2315
+ );
2316
+
2317
+ const payload = {
2318
+ messages: [{ content: 'Test', role: 'user' as const }],
2319
+ model: 'gpt-4o',
2320
+ responseApi: true,
2321
+ schema: {
2322
+ description: 'Test schema',
2323
+ name: 'test',
2324
+ schema: {
2325
+ description: 'Inner schema description',
2326
+ maxLength: 100,
2327
+ pattern: '^test$',
2328
+ properties: {
2329
+ result: { type: 'string' },
2330
+ },
2331
+ required: ['result'],
2332
+ type: 'object' as const,
2333
+ },
2334
+ strict: true,
2335
+ },
2336
+ };
2337
+
2338
+ await instanceWithSchemaHandler.generateObject(payload);
2339
+
2340
+ expect(mockSchemaHandler).toHaveBeenCalledWith(payload.schema.schema);
2341
+ expect(instanceWithSchemaHandler['client'].responses.create).toHaveBeenCalledWith(
2342
+ expect.objectContaining({
2343
+ text: expect.objectContaining({
2344
+ format: expect.objectContaining({
2345
+ description: 'Test schema',
2346
+ name: 'test',
2347
+ schema: {
2348
+ description: 'Inner schema description',
2349
+ properties: {
2350
+ result: { type: 'string' },
2351
+ },
2352
+ required: ['result'],
2353
+ type: 'object',
2354
+ },
2355
+ strict: true,
2356
+ }),
2357
+ }),
2358
+ }),
2359
+ expect.any(Object),
2360
+ );
2361
+ });
2362
+ });
2363
+
2051
2364
  describe('tool calling fallback', () => {
2052
2365
  let instanceWithToolCalling: any;
2053
2366
 
@@ -119,6 +119,10 @@ export interface OpenAICompatibleFactoryOptions<T extends Record<string, any> =
119
119
  invalidAPIKey: ILobeAgentRuntimeErrorType;
120
120
  };
121
121
  generateObject?: {
122
+ /**
123
+ * Transform schema before sending to the provider (e.g., filter unsupported properties)
124
+ */
125
+ handleSchema?: (schema: any) => any;
122
126
  /**
123
127
  * If true, route generateObject requests to Responses API path directly
124
128
  */
@@ -454,12 +458,19 @@ export const createOpenAICompatibleRuntime = <T extends Record<string, any> = an
454
458
  // Use tool calling fallback if configured
455
459
  if (generateObjectConfig?.useToolsCalling) {
456
460
  log('using tool calling fallback for structured output');
461
+
462
+ // Apply schema transformation if configured
463
+ const processedSchema = generateObjectConfig.handleSchema
464
+ ? { ...schema, schema: generateObjectConfig.handleSchema(schema.schema) }
465
+ : schema;
466
+
457
467
  const tool: ChatCompletionTool = {
458
468
  function: {
459
469
  description:
460
- schema.description || 'Generate structured output according to the provided schema',
461
- name: schema.name || 'structured_output',
462
- parameters: schema.schema,
470
+ processedSchema.description ||
471
+ 'Generate structured output according to the provided schema',
472
+ name: processedSchema.name || 'structured_output',
473
+ parameters: processedSchema.schema,
463
474
  },
464
475
  type: 'function',
465
476
  };
@@ -531,13 +542,18 @@ export const createOpenAICompatibleRuntime = <T extends Record<string, any> = an
531
542
  return false;
532
543
  })();
533
544
 
545
+ // Apply schema transformation if configured
546
+ const processedSchema = generateObjectConfig?.handleSchema
547
+ ? { ...schema, schema: generateObjectConfig.handleSchema(schema.schema) }
548
+ : schema;
549
+
534
550
  if (shouldUseResponses) {
535
551
  log('calling responses.create for structured output');
536
552
  const res = await this.client!.responses.create(
537
553
  {
538
554
  input: messages,
539
555
  model,
540
- text: { format: { strict: true, type: 'json_schema', ...schema } },
556
+ text: { format: { strict: true, type: 'json_schema', ...processedSchema } },
541
557
  user: options?.user,
542
558
  },
543
559
  { headers: options?.headers, signal: options?.signal },
@@ -561,7 +577,7 @@ export const createOpenAICompatibleRuntime = <T extends Record<string, any> = an
561
577
  {
562
578
  messages,
563
579
  model,
564
- response_format: { json_schema: schema, type: 'json_schema' },
580
+ response_format: { json_schema: processedSchema, type: 'json_schema' },
565
581
  user: options?.user,
566
582
  },
567
583
  { headers: options?.headers, signal: options?.signal },
@@ -57,8 +57,12 @@ describe('LobeAzureAI', () => {
57
57
  model: 'gpt-4',
58
58
  };
59
59
 
60
- vi.spyOn(instance.client.path('/chat/completions'), 'post').mockResolvedValue({
60
+ const mockPost = vi.fn().mockResolvedValue({
61
61
  body: mockResponse,
62
+ });
63
+
64
+ vi.spyOn(instance.client, 'path').mockReturnValue({
65
+ post: mockPost,
62
66
  } as any);
63
67
 
64
68
  const result = await instance.chat({
@@ -68,12 +72,18 @@ describe('LobeAzureAI', () => {
68
72
  });
69
73
 
70
74
  expect(result).toBeDefined();
75
+ expect(instance.client.path).toHaveBeenCalledWith('/chat/completions');
76
+ expect(mockPost).toHaveBeenCalled();
71
77
  });
72
78
 
73
79
  it('should handle generic errors', async () => {
74
80
  const mockError = new Error('Network error');
75
81
 
76
- vi.spyOn(instance.client.path('/chat/completions'), 'post').mockRejectedValue(mockError);
82
+ const mockPost = vi.fn().mockRejectedValue(mockError);
83
+
84
+ vi.spyOn(instance.client, 'path').mockReturnValue({
85
+ post: mockPost,
86
+ } as any);
77
87
 
78
88
  try {
79
89
  await instance.chat({