@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.
- package/CHANGELOG.md +50 -0
- package/README.md +93 -45
- package/README.zh-CN.md +92 -44
- package/changelog/v1.json +18 -0
- package/contributing/Others/Lighthouse.md +2 -5
- package/contributing/Others/Lighthouse.zh-CN.md +3 -4
- package/docker-compose/local/docker-compose.yml +16 -0
- package/docs/self-hosting/advanced/auth/next-auth/casdoor.mdx +2 -1
- package/docs/self-hosting/advanced/auth/next-auth/casdoor.zh-CN.mdx +2 -1
- package/docs/self-hosting/advanced/auth/next-auth/logto.mdx +1 -1
- package/docs/self-hosting/advanced/auth/next-auth/logto.zh-CN.mdx +1 -1
- package/docs/self-hosting/server-database/docker-compose.zh-CN.mdx +1 -1
- package/docs/self-hosting/server-database.mdx +1 -1
- package/docs/usage/features/agent-market.mdx +1 -1
- package/docs/usage/features/agent-market.zh-CN.mdx +1 -1
- package/docs/usage/features/artifacts.mdx +23 -0
- package/docs/usage/features/artifacts.zh-CN.mdx +22 -0
- package/docs/usage/features/auth.mdx +1 -1
- package/docs/usage/features/auth.zh-CN.mdx +1 -1
- package/docs/usage/features/branching-conversations.mdx +21 -0
- package/docs/usage/features/branching-conversations.zh-CN.mdx +21 -0
- package/docs/usage/features/cot.mdx +18 -0
- package/docs/usage/features/cot.zh-CN.mdx +18 -0
- package/docs/usage/features/database.mdx +1 -1
- package/docs/usage/features/database.zh-CN.mdx +1 -1
- package/docs/usage/features/knowledge-base.mdx +24 -0
- package/docs/usage/features/knowledge-base.zh-CN.mdx +21 -0
- package/docs/usage/features/local-llm.mdx +1 -1
- package/docs/usage/features/local-llm.zh-CN.mdx +1 -1
- package/docs/usage/features/mobile.mdx +1 -1
- package/docs/usage/features/mobile.zh-CN.mdx +1 -1
- package/docs/usage/features/multi-ai-providers.mdx +1 -1
- package/docs/usage/features/multi-ai-providers.zh-CN.mdx +1 -1
- package/docs/usage/features/plugin-system.mdx +1 -1
- package/docs/usage/features/plugin-system.zh-CN.mdx +1 -1
- package/docs/usage/features/pwa.mdx +1 -1
- package/docs/usage/features/pwa.zh-CN.mdx +1 -1
- package/docs/usage/features/text-to-image.mdx +1 -1
- package/docs/usage/features/text-to-image.zh-CN.mdx +1 -1
- package/docs/usage/features/theme.mdx +1 -1
- package/docs/usage/features/theme.zh-CN.mdx +1 -1
- package/docs/usage/features/tts.mdx +1 -1
- package/docs/usage/features/tts.zh-CN.mdx +1 -1
- package/docs/usage/features/vision.mdx +1 -1
- package/docs/usage/features/vision.zh-CN.mdx +1 -1
- package/package.json +1 -1
- package/scripts/readmeWorkflow/syncAgentIndex.ts +0 -1
- package/scripts/readmeWorkflow/syncPluginIndex.ts +0 -1
- package/scripts/readmeWorkflow/syncProviderIndex.ts +0 -1
- package/scripts/vercelIgnoredBuildStep.js +1 -1
- package/src/app/[variants]/(main)/chat/(workspace)/features/AgentSettings/CategoryContent/useCategory.tsx +39 -33
- package/src/app/[variants]/(main)/chat/(workspace)/features/AgentSettings/index.tsx +3 -1
- package/src/app/[variants]/(main)/chat/(workspace)/features/SettingButton.tsx +3 -1
- package/src/config/knowledge.ts +14 -16
- package/src/hooks/useInterceptingRoutes.ts +2 -1
- package/src/server/modules/ContentChunk/index.ts +44 -6
- package/src/server/modules/ContentChunk/rules.test.ts +81 -0
- package/src/server/modules/ContentChunk/rules.ts +23 -0
- package/README.ja-JP.md +0 -844
- package/README.zh-TW.md +0 -887
@@ -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('
|
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
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
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
|
});
|
package/src/config/knowledge.ts
CHANGED
@@ -1,19 +1,17 @@
|
|
1
1
|
import { createEnv } from '@t3-oss/env-nextjs';
|
2
2
|
import { z } from 'zod';
|
3
3
|
|
4
|
-
export const
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
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 {
|
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
|
-
|
29
|
-
|
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
|
-
|
34
|
-
|
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;
|