@lobehub/chat 1.55.4 → 1.56.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 (60) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/README.md +93 -45
  3. package/README.zh-CN.md +92 -44
  4. package/changelog/v1.json +18 -0
  5. package/contributing/Others/Lighthouse.md +2 -5
  6. package/contributing/Others/Lighthouse.zh-CN.md +3 -4
  7. package/docker-compose/local/docker-compose.yml +16 -0
  8. package/docs/self-hosting/advanced/auth/next-auth/casdoor.mdx +2 -1
  9. package/docs/self-hosting/advanced/auth/next-auth/casdoor.zh-CN.mdx +2 -1
  10. package/docs/self-hosting/advanced/auth/next-auth/logto.mdx +1 -1
  11. package/docs/self-hosting/advanced/auth/next-auth/logto.zh-CN.mdx +1 -1
  12. package/docs/self-hosting/server-database/docker-compose.zh-CN.mdx +1 -1
  13. package/docs/self-hosting/server-database.mdx +1 -1
  14. package/docs/usage/features/agent-market.mdx +1 -1
  15. package/docs/usage/features/agent-market.zh-CN.mdx +1 -1
  16. package/docs/usage/features/artifacts.mdx +23 -0
  17. package/docs/usage/features/artifacts.zh-CN.mdx +22 -0
  18. package/docs/usage/features/auth.mdx +1 -1
  19. package/docs/usage/features/auth.zh-CN.mdx +1 -1
  20. package/docs/usage/features/branching-conversations.mdx +21 -0
  21. package/docs/usage/features/branching-conversations.zh-CN.mdx +21 -0
  22. package/docs/usage/features/cot.mdx +18 -0
  23. package/docs/usage/features/cot.zh-CN.mdx +18 -0
  24. package/docs/usage/features/database.mdx +1 -1
  25. package/docs/usage/features/database.zh-CN.mdx +1 -1
  26. package/docs/usage/features/knowledge-base.mdx +24 -0
  27. package/docs/usage/features/knowledge-base.zh-CN.mdx +21 -0
  28. package/docs/usage/features/local-llm.mdx +1 -1
  29. package/docs/usage/features/local-llm.zh-CN.mdx +1 -1
  30. package/docs/usage/features/mobile.mdx +1 -1
  31. package/docs/usage/features/mobile.zh-CN.mdx +1 -1
  32. package/docs/usage/features/multi-ai-providers.mdx +1 -1
  33. package/docs/usage/features/multi-ai-providers.zh-CN.mdx +1 -1
  34. package/docs/usage/features/plugin-system.mdx +1 -1
  35. package/docs/usage/features/plugin-system.zh-CN.mdx +1 -1
  36. package/docs/usage/features/pwa.mdx +1 -1
  37. package/docs/usage/features/pwa.zh-CN.mdx +1 -1
  38. package/docs/usage/features/text-to-image.mdx +1 -1
  39. package/docs/usage/features/text-to-image.zh-CN.mdx +1 -1
  40. package/docs/usage/features/theme.mdx +1 -1
  41. package/docs/usage/features/theme.zh-CN.mdx +1 -1
  42. package/docs/usage/features/tts.mdx +1 -1
  43. package/docs/usage/features/tts.zh-CN.mdx +1 -1
  44. package/docs/usage/features/vision.mdx +1 -1
  45. package/docs/usage/features/vision.zh-CN.mdx +1 -1
  46. package/package.json +1 -1
  47. package/scripts/readmeWorkflow/syncAgentIndex.ts +0 -1
  48. package/scripts/readmeWorkflow/syncPluginIndex.ts +0 -1
  49. package/scripts/readmeWorkflow/syncProviderIndex.ts +0 -1
  50. package/scripts/vercelIgnoredBuildStep.js +1 -1
  51. package/src/app/[variants]/(main)/chat/(workspace)/features/AgentSettings/CategoryContent/useCategory.tsx +39 -33
  52. package/src/app/[variants]/(main)/chat/(workspace)/features/AgentSettings/index.tsx +3 -1
  53. package/src/app/[variants]/(main)/chat/(workspace)/features/SettingButton.tsx +3 -1
  54. package/src/config/knowledge.ts +14 -16
  55. package/src/hooks/useInterceptingRoutes.ts +2 -1
  56. package/src/server/modules/ContentChunk/index.ts +44 -6
  57. package/src/server/modules/ContentChunk/rules.test.ts +81 -0
  58. package/src/server/modules/ContentChunk/rules.ts +23 -0
  59. package/README.ja-JP.md +0 -844
  60. package/README.zh-TW.md +0 -887
@@ -47,5 +47,4 @@ const runProviderTable = async (lang?: string) => {
47
47
  export default async () => {
48
48
  await runProviderTable();
49
49
  await runProviderTable('zh-CN');
50
- await runProviderTable('ja-JP');
51
50
  };
@@ -5,7 +5,7 @@ const branchName = process.env.VERCEL_GIT_COMMIT_REF || '';
5
5
 
6
6
  function shouldProceedBuild() {
7
7
  // 如果是 lighthouse 分支或以 testgru 开头的分支,取消构建
8
- if (branchName === 'lighthouse' || branchName.startsWith('testgru')) {
8
+ if (branchName === 'lighthouse' || branchName.startsWith('gru/')) {
9
9
  return false;
10
10
  }
11
11
 
@@ -1,10 +1,13 @@
1
1
  import { Icon } from '@lobehub/ui';
2
+ import { MenuItemType } from 'antd/es/menu/interface';
2
3
  import { Blocks, Bot, BrainCog, MessagesSquare, Mic2, UserCircle } from 'lucide-react';
3
4
  import { useMemo } from 'react';
4
5
  import { useTranslation } from 'react-i18next';
5
6
 
6
7
  import type { MenuProps } from '@/components/Menu';
8
+ import { INBOX_SESSION_ID } from '@/const/session';
7
9
  import { ChatSettingsTabs } from '@/store/global/initialState';
10
+ import { useSessionStore } from '@/store/session';
8
11
 
9
12
  interface UseCategoryOptions {
10
13
  mobile?: boolean;
@@ -13,41 +16,44 @@ interface UseCategoryOptions {
13
16
  export const useCategory = ({ mobile }: UseCategoryOptions = {}) => {
14
17
  const { t } = useTranslation('setting');
15
18
  const iconSize = mobile ? { fontSize: 20 } : undefined;
19
+ const id = useSessionStore((s) => s.activeId);
20
+ const isInbox = id === INBOX_SESSION_ID;
16
21
 
17
22
  const cateItems: MenuProps['items'] = useMemo(
18
- () => [
19
- {
20
- icon: <Icon icon={UserCircle} size={iconSize} />,
21
- key: ChatSettingsTabs.Meta,
22
- label: t('agentTab.meta'),
23
- },
24
- {
25
- icon: <Icon icon={Bot} size={iconSize} />,
26
- key: ChatSettingsTabs.Prompt,
27
- label: t('agentTab.prompt'),
28
- },
29
- {
30
- icon: <Icon icon={MessagesSquare} size={iconSize} />,
31
- key: ChatSettingsTabs.Chat,
32
- label: t('agentTab.chat'),
33
- },
34
- {
35
- icon: <Icon icon={BrainCog} size={iconSize} />,
36
- key: ChatSettingsTabs.Modal,
37
- label: t('agentTab.modal'),
38
- },
39
- {
40
- icon: <Icon icon={Mic2} size={iconSize} />,
41
- key: ChatSettingsTabs.TTS,
42
- label: t('agentTab.tts'),
43
- },
44
- {
45
- icon: <Icon icon={Blocks} size={iconSize} />,
46
- key: ChatSettingsTabs.Plugin,
47
- label: t('agentTab.plugin'),
48
- },
49
- ],
50
- [t],
23
+ () =>
24
+ [
25
+ (!isInbox && {
26
+ icon: <Icon icon={UserCircle} size={iconSize} />,
27
+ key: ChatSettingsTabs.Meta,
28
+ label: t('agentTab.meta'),
29
+ }) as MenuItemType,
30
+ (!isInbox && {
31
+ icon: <Icon icon={Bot} size={iconSize} />,
32
+ key: ChatSettingsTabs.Prompt,
33
+ label: t('agentTab.prompt'),
34
+ }) as MenuItemType,
35
+ {
36
+ icon: <Icon icon={MessagesSquare} size={iconSize} />,
37
+ key: ChatSettingsTabs.Chat,
38
+ label: t('agentTab.chat'),
39
+ },
40
+ {
41
+ icon: <Icon icon={BrainCog} size={iconSize} />,
42
+ key: ChatSettingsTabs.Modal,
43
+ label: t('agentTab.modal'),
44
+ },
45
+ {
46
+ icon: <Icon icon={Mic2} size={iconSize} />,
47
+ key: ChatSettingsTabs.TTS,
48
+ label: t('agentTab.tts'),
49
+ },
50
+ {
51
+ icon: <Icon icon={Blocks} size={iconSize} />,
52
+ key: ChatSettingsTabs.Plugin,
53
+ label: t('agentTab.plugin'),
54
+ },
55
+ ].filter(Boolean),
56
+ [t, isInbox],
51
57
  );
52
58
 
53
59
  return cateItems;
@@ -8,6 +8,7 @@ import { useTranslation } from 'react-i18next';
8
8
  import { Flexbox } from 'react-layout-kit';
9
9
 
10
10
  import Header from '@/app/[variants]/(main)/settings/_layout/Desktop/Header';
11
+ import { INBOX_SESSION_ID } from '@/const/session';
11
12
  import AgentChat from '@/features/AgentSetting/AgentChat';
12
13
  import AgentMeta from '@/features/AgentSetting/AgentMeta';
13
14
  import AgentModal from '@/features/AgentSetting/AgentModal';
@@ -38,8 +39,9 @@ const AgentSettings = memo(() => {
38
39
  s.updateSessionMeta,
39
40
  sessionMetaSelectors.currentAgentTitle(s),
40
41
  ]);
42
+ const isInbox = id === INBOX_SESSION_ID;
41
43
 
42
- const [tab, setTab] = useState(ChatSettingsTabs.Meta);
44
+ const [tab, setTab] = useState(isInbox ? ChatSettingsTabs.Chat : ChatSettingsTabs.Meta);
43
45
 
44
46
  const ref = useRef<any>(null);
45
47
  const theme = useTheme();
@@ -8,6 +8,7 @@ import { useTranslation } from 'react-i18next';
8
8
 
9
9
  import { DESKTOP_HEADER_ICON_SIZE, MOBILE_HEADER_ICON_SIZE } from '@/const/layoutTokens';
10
10
  import { useOpenChatSettings } from '@/hooks/useInterceptingRoutes';
11
+ import { useSessionStore } from '@/store/session';
11
12
 
12
13
  const AgentSettings = dynamic(() => import('./AgentSettings'), {
13
14
  ssr: false,
@@ -16,6 +17,7 @@ const AgentSettings = dynamic(() => import('./AgentSettings'), {
16
17
  const SettingButton = memo<{ mobile?: boolean }>(({ mobile }) => {
17
18
  const { t } = useTranslation('common');
18
19
  const openChatSettings = useOpenChatSettings();
20
+ const id = useSessionStore((s) => s.activeId);
19
21
 
20
22
  return (
21
23
  <>
@@ -25,7 +27,7 @@ const SettingButton = memo<{ mobile?: boolean }>(({ mobile }) => {
25
27
  size={mobile ? MOBILE_HEADER_ICON_SIZE : DESKTOP_HEADER_ICON_SIZE}
26
28
  title={t('header.session', { ns: 'setting' })}
27
29
  />
28
- <AgentSettings />
30
+ <AgentSettings key={id} />
29
31
  </>
30
32
  );
31
33
  });
@@ -1,19 +1,17 @@
1
1
  import { createEnv } from '@t3-oss/env-nextjs';
2
2
  import { z } from 'zod';
3
3
 
4
- export const getKnowledgeConfig = () => {
5
- return createEnv({
6
- runtimeEnv: {
7
- DEFAULT_FILES_CONFIG: process.env.DEFAULT_FILES_CONFIG,
8
- UNSTRUCTURED_API_KEY: process.env.UNSTRUCTURED_API_KEY,
9
- UNSTRUCTURED_SERVER_URL: process.env.UNSTRUCTURED_SERVER_URL,
10
- },
11
- server: {
12
- DEFAULT_FILES_CONFIG: z.string().optional(),
13
- UNSTRUCTURED_API_KEY: z.string().optional(),
14
- UNSTRUCTURED_SERVER_URL: z.string().optional(),
15
- },
16
- });
17
- };
18
-
19
- export const knowledgeEnv = getKnowledgeConfig();
4
+ export const knowledgeEnv = createEnv({
5
+ runtimeEnv: {
6
+ DEFAULT_FILES_CONFIG: process.env.DEFAULT_FILES_CONFIG,
7
+ FILE_TYPE_CHUNKING_RULES: process.env.FILE_TYPE_CHUNKING_RULES,
8
+ UNSTRUCTURED_API_KEY: process.env.UNSTRUCTURED_API_KEY,
9
+ UNSTRUCTURED_SERVER_URL: process.env.UNSTRUCTURED_SERVER_URL,
10
+ },
11
+ server: {
12
+ DEFAULT_FILES_CONFIG: z.string().optional(),
13
+ FILE_TYPE_CHUNKING_RULES: z.string().optional(),
14
+ UNSTRUCTURED_API_KEY: z.string().optional(),
15
+ UNSTRUCTURED_SERVER_URL: z.string().optional(),
16
+ },
17
+ });
@@ -2,6 +2,7 @@ import { useMemo } from 'react';
2
2
  import urlJoin from 'url-join';
3
3
 
4
4
  import { INBOX_SESSION_ID } from '@/const/session';
5
+ import { isDeprecatedEdition } from '@/const/version';
5
6
  import { useIsMobile } from '@/hooks/useIsMobile';
6
7
  import { useQueryRoute } from '@/hooks/useQueryRoute';
7
8
  import { useAgentStore } from '@/store/agent';
@@ -15,7 +16,7 @@ export const useOpenChatSettings = (tab: ChatSettingsTabs = ChatSettingsTabs.Met
15
16
  const router = useQueryRoute();
16
17
 
17
18
  return useMemo(() => {
18
- if (activeId === INBOX_SESSION_ID) {
19
+ if (isDeprecatedEdition && activeId === INBOX_SESSION_ID) {
19
20
  return () => router.push(urlJoin('/settings', SettingsTabs.Agent));
20
21
  }
21
22
 
@@ -1,9 +1,13 @@
1
1
  import { ChunkingLoader } from 'src/libs/langchain';
2
2
  import { Strategy } from 'unstructured-client/sdk/models/shared';
3
3
 
4
- import { NewChunkItem, NewUnstructuredChunkItem } from '@/database/schemas';
4
+ import { knowledgeEnv } from '@/config/knowledge';
5
+ import type { NewChunkItem, NewUnstructuredChunkItem } from '@/database/schemas';
5
6
  import { ChunkingStrategy, Unstructured } from '@/libs/unstructured';
6
7
 
8
+ import { ChunkingRuleParser } from './rules';
9
+ import type { ChunkingService } from './rules';
10
+
7
11
  export interface ChunkContentParams {
8
12
  content: Uint8Array;
9
13
  fileType: string;
@@ -19,23 +23,57 @@ interface ChunkResult {
19
23
  export class ContentChunk {
20
24
  private unstructuredClient: Unstructured;
21
25
  private langchainClient: ChunkingLoader;
26
+ private chunkingRules: Record<string, ChunkingService[]>;
22
27
 
23
28
  constructor() {
24
29
  this.unstructuredClient = new Unstructured();
25
30
  this.langchainClient = new ChunkingLoader();
31
+ this.chunkingRules = ChunkingRuleParser.parse(knowledgeEnv.FILE_TYPE_CHUNKING_RULES || '');
26
32
  }
27
33
 
28
- isUsingUnstructured(params: ChunkContentParams) {
29
- return params.fileType === 'application/pdf' && params.mode === 'hi-res';
34
+ private getChunkingServices(fileType: string): ChunkingService[] {
35
+ const ext = fileType.split('/').pop()?.toLowerCase() || '';
36
+ return this.chunkingRules[ext] || ['default'];
30
37
  }
31
38
 
32
39
  async chunkContent(params: ChunkContentParams): Promise<ChunkResult> {
33
- if (this.isUsingUnstructured(params))
34
- return await this.chunkByUnstructured(params.filename, params.content);
35
-
40
+ const services = this.getChunkingServices(params.fileType);
41
+
42
+ for (const service of services) {
43
+ try {
44
+ switch (service) {
45
+ case 'unstructured': {
46
+ if (this.canUseUnstructured()) {
47
+ return await this.chunkByUnstructured(params.filename, params.content);
48
+ }
49
+ break;
50
+ }
51
+
52
+ case 'doc2x': {
53
+ // Future implementation
54
+ break;
55
+ }
56
+
57
+ default: {
58
+ return await this.chunkByLangChain(params.filename, params.content);
59
+ }
60
+ }
61
+ } catch (error) {
62
+ // If this is the last service, throw the error
63
+ if (service === services.at(-1)) throw error;
64
+ // Otherwise continue to next service
65
+ console.error(`Chunking failed with service ${service}:`, error);
66
+ }
67
+ }
68
+
69
+ // Fallback to langchain if no service succeeded
36
70
  return await this.chunkByLangChain(params.filename, params.content);
37
71
  }
38
72
 
73
+ private canUseUnstructured(): boolean {
74
+ return !!(knowledgeEnv.UNSTRUCTURED_API_KEY && knowledgeEnv.UNSTRUCTURED_SERVER_URL);
75
+ }
76
+
39
77
  private chunkByUnstructured = async (
40
78
  filename: string,
41
79
  content: Uint8Array,
@@ -0,0 +1,81 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { ChunkingRuleParser } from './rules';
3
+
4
+ describe('ChunkingRuleParser', () => {
5
+ describe('parse', () => {
6
+ it('should parse a single file type rule correctly', () => {
7
+ const input = 'pdf=unstructured,default';
8
+ const result = ChunkingRuleParser.parse(input);
9
+
10
+ expect(result).toEqual({
11
+ pdf: ['unstructured', 'default'],
12
+ });
13
+ });
14
+
15
+ it('should parse multiple file type rules correctly', () => {
16
+ const input = 'pdf=unstructured,default;doc=doc2x,default;txt=default';
17
+ const result = ChunkingRuleParser.parse(input);
18
+
19
+ expect(result).toEqual({
20
+ pdf: ['unstructured', 'default'],
21
+ doc: ['doc2x', 'default'],
22
+ txt: ['default'],
23
+ });
24
+ });
25
+
26
+ it('should convert file types to lowercase', () => {
27
+ const input = 'PDF=unstructured;DOC=doc2x';
28
+ const result = ChunkingRuleParser.parse(input);
29
+
30
+ expect(result).toEqual({
31
+ pdf: ['unstructured'],
32
+ doc: ['doc2x'],
33
+ });
34
+ });
35
+
36
+ it('should filter out invalid service names', () => {
37
+ const input = 'pdf=unstructured,invalid,default,wrongservice';
38
+ const result = ChunkingRuleParser.parse(input);
39
+
40
+ expect(result).toEqual({
41
+ pdf: ['unstructured', 'default'],
42
+ });
43
+ });
44
+
45
+ it('should handle empty string input', () => {
46
+ const input = '';
47
+ const result = ChunkingRuleParser.parse(input);
48
+
49
+ expect(result).toEqual({});
50
+ });
51
+
52
+ it('should skip invalid rule formats', () => {
53
+ const input = 'pdf=unstructured;invalid;doc=doc2x;=default;txt';
54
+ const result = ChunkingRuleParser.parse(input);
55
+
56
+ expect(result).toEqual({
57
+ pdf: ['unstructured'],
58
+ doc: ['doc2x'],
59
+ });
60
+ });
61
+
62
+ it('should handle whitespace in service names', () => {
63
+ const input = 'pdf= unstructured , default ;doc=doc2x';
64
+ const result = ChunkingRuleParser.parse(input);
65
+
66
+ expect(result).toEqual({
67
+ pdf: ['unstructured', 'default'],
68
+ doc: ['doc2x'],
69
+ });
70
+ });
71
+
72
+ it('should handle duplicate services for same file type', () => {
73
+ const input = 'pdf=unstructured,default,unstructured';
74
+ const result = ChunkingRuleParser.parse(input);
75
+
76
+ expect(result).toEqual({
77
+ pdf: ['unstructured', 'default', 'unstructured'],
78
+ });
79
+ });
80
+ });
81
+ });
@@ -0,0 +1,23 @@
1
+ export type ChunkingService = 'unstructured' | 'doc2x' | 'default';
2
+
3
+ export const ChunkingRuleParser = {
4
+ parse(rulesStr: string): Record<string, ChunkingService[]> {
5
+ const rules: Record<string, ChunkingService[]> = {};
6
+
7
+ // Split by semicolon for different file types
8
+ const fileTypeRules = rulesStr.split(';');
9
+
10
+ for (const rule of fileTypeRules) {
11
+ const [fileType, services] = rule.split('=');
12
+ if (!fileType || !services) continue;
13
+
14
+ // Split services by comma and validate each service
15
+ rules[fileType.toLowerCase()] = services
16
+ .split(',')
17
+ .map((s) => s.trim().toLowerCase())
18
+ .filter((s): s is ChunkingService => ['unstructured', 'doc2x', 'default'].includes(s));
19
+ }
20
+
21
+ return rules;
22
+ },
23
+ } as const;