@lobehub/chat 1.116.0 → 1.116.2
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/.github/workflows/claude.yml +1 -1
- package/CHANGELOG.md +34 -0
- package/CLAUDE.md +1 -1
- package/changelog/v1.json +10 -0
- package/docs/development/database-schema.dbml +44 -0
- package/docs/usage/providers/bfl.mdx +1 -1
- package/docs/usage/providers/bfl.zh-CN.mdx +1 -1
- package/package.json +1 -1
- package/packages/database/migrations/0030_add_group_chat.sql +36 -0
- package/packages/database/migrations/meta/0030_snapshot.json +6417 -0
- package/packages/database/migrations/meta/_journal.json +7 -0
- package/packages/database/src/core/migrations.json +19 -0
- package/packages/database/src/models/__tests__/topic.test.ts +3 -1
- package/packages/database/src/repositories/tableViewer/index.test.ts +1 -1
- package/packages/database/src/schemas/chatGroup.ts +98 -0
- package/packages/database/src/schemas/index.ts +1 -0
- package/packages/database/src/schemas/message.ts +4 -1
- package/packages/database/src/schemas/relations.ts +26 -0
- package/packages/database/src/schemas/topic.ts +2 -0
- package/packages/database/src/types/chatGroup.ts +9 -0
- package/packages/database/src/utils/idGenerator.ts +1 -0
- package/packages/model-runtime/src/utils/modelParse.ts +17 -8
- package/src/app/[variants]/(main)/_layout/Desktop/DesktopLayoutContainer.tsx +30 -0
- package/src/app/[variants]/(main)/_layout/Desktop/index.tsx +6 -19
- package/src/app/[variants]/(main)/chat/_layout/Desktop/SessionPanel.tsx +25 -21
- package/src/app/[variants]/(main)/settings/_layout/Desktop/Header.tsx +28 -3
- package/src/app/[variants]/(main)/settings/_layout/Desktop/index.tsx +17 -24
- package/src/features/FileViewer/Renderer/PDF/index.tsx +2 -2
@@ -210,6 +210,13 @@
|
|
210
210
|
"when": 1753201379817,
|
211
211
|
"tag": "0029_add_apikey_manage",
|
212
212
|
"breakpoints": true
|
213
|
+
},
|
214
|
+
{
|
215
|
+
"idx": 30,
|
216
|
+
"version": "7",
|
217
|
+
"when": 1756298669289,
|
218
|
+
"tag": "0030_add_group_chat",
|
219
|
+
"breakpoints": true
|
213
220
|
}
|
214
221
|
],
|
215
222
|
"version": "6"
|
@@ -559,5 +559,24 @@
|
|
559
559
|
"bps": true,
|
560
560
|
"folderMillis": 1753201379817,
|
561
561
|
"hash": "fe5c0d7c2768189771c42ef93693fc1d58586b468c4bdde7fb6f2dc58cc9931c"
|
562
|
+
},
|
563
|
+
{
|
564
|
+
"sql": [
|
565
|
+
"CREATE TABLE IF NOT EXISTS \"chat_groups\" (\n\t\"id\" text PRIMARY KEY NOT NULL,\n\t\"title\" text,\n\t\"description\" text,\n\t\"config\" jsonb,\n\t\"client_id\" text,\n\t\"user_id\" text NOT NULL,\n\t\"pinned\" boolean DEFAULT false,\n\t\"accessed_at\" timestamp with time zone DEFAULT now() NOT NULL,\n\t\"created_at\" timestamp with time zone DEFAULT now() NOT NULL,\n\t\"updated_at\" timestamp with time zone DEFAULT now() NOT NULL\n);\n",
|
566
|
+
"\nCREATE TABLE IF NOT EXISTS \"chat_groups_agents\" (\n\t\"chat_group_id\" text NOT NULL,\n\t\"agent_id\" text NOT NULL,\n\t\"user_id\" text NOT NULL,\n\t\"enabled\" boolean DEFAULT true,\n\t\"order\" integer DEFAULT 0,\n\t\"role\" text DEFAULT 'participant',\n\t\"accessed_at\" timestamp with time zone DEFAULT now() NOT NULL,\n\t\"created_at\" timestamp with time zone DEFAULT now() NOT NULL,\n\t\"updated_at\" timestamp with time zone DEFAULT now() NOT NULL,\n\tCONSTRAINT \"chat_groups_agents_chat_group_id_agent_id_pk\" PRIMARY KEY(\"chat_group_id\",\"agent_id\")\n);\n",
|
567
|
+
"\nALTER TABLE \"messages\" ADD COLUMN IF NOT EXISTS \"group_id\" text;",
|
568
|
+
"\nALTER TABLE \"messages\" ADD COLUMN IF NOT EXISTS \"target_id\" text;",
|
569
|
+
"\nALTER TABLE \"topics\" ADD COLUMN IF NOT EXISTS \"group_id\" text;",
|
570
|
+
"\nALTER TABLE \"chat_groups\" ADD CONSTRAINT \"chat_groups_user_id_users_id_fk\" FOREIGN KEY (\"user_id\") REFERENCES \"public\".\"users\"(\"id\") ON DELETE cascade ON UPDATE no action;",
|
571
|
+
"\nALTER TABLE \"chat_groups_agents\" ADD CONSTRAINT \"chat_groups_agents_chat_group_id_chat_groups_id_fk\" FOREIGN KEY (\"chat_group_id\") REFERENCES \"public\".\"chat_groups\"(\"id\") ON DELETE cascade ON UPDATE no action;",
|
572
|
+
"\nALTER TABLE \"chat_groups_agents\" ADD CONSTRAINT \"chat_groups_agents_agent_id_agents_id_fk\" FOREIGN KEY (\"agent_id\") REFERENCES \"public\".\"agents\"(\"id\") ON DELETE cascade ON UPDATE no action;",
|
573
|
+
"\nALTER TABLE \"chat_groups_agents\" ADD CONSTRAINT \"chat_groups_agents_user_id_users_id_fk\" FOREIGN KEY (\"user_id\") REFERENCES \"public\".\"users\"(\"id\") ON DELETE cascade ON UPDATE no action;",
|
574
|
+
"\nCREATE UNIQUE INDEX \"chat_groups_client_id_user_id_unique\" ON \"chat_groups\" USING btree (\"client_id\",\"user_id\");",
|
575
|
+
"\nALTER TABLE \"messages\" ADD CONSTRAINT \"messages_group_id_chat_groups_id_fk\" FOREIGN KEY (\"group_id\") REFERENCES \"public\".\"chat_groups\"(\"id\") ON DELETE set null ON UPDATE no action;",
|
576
|
+
"\nALTER TABLE \"topics\" ADD CONSTRAINT \"topics_group_id_chat_groups_id_fk\" FOREIGN KEY (\"group_id\") REFERENCES \"public\".\"chat_groups\"(\"id\") ON DELETE cascade ON UPDATE no action;\n"
|
577
|
+
],
|
578
|
+
"bps": true,
|
579
|
+
"folderMillis": 1756298669289,
|
580
|
+
"hash": "3af468ca75761ca4ca4e32ba8704728f0499aa935bfe00ae5aabfa8405a18bd4"
|
562
581
|
}
|
563
582
|
]
|
@@ -1,8 +1,8 @@
|
|
1
1
|
import { eq, inArray } from 'drizzle-orm';
|
2
2
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
3
3
|
|
4
|
-
import { LobeChatDatabase } from '../../type';
|
5
4
|
import { messages, sessions, topics, users } from '../../schemas';
|
5
|
+
import { LobeChatDatabase } from '../../type';
|
6
6
|
import { CreateTopicParams, TopicModel } from '../topic';
|
7
7
|
import { getTestDB } from './_util';
|
8
8
|
|
@@ -423,6 +423,7 @@ describe('TopicModel', () => {
|
|
423
423
|
userId,
|
424
424
|
historySummary: null,
|
425
425
|
metadata: null,
|
426
|
+
groupId: null,
|
426
427
|
clientId: null,
|
427
428
|
createdAt: expect.any(Date),
|
428
429
|
updatedAt: expect.any(Date),
|
@@ -469,6 +470,7 @@ describe('TopicModel', () => {
|
|
469
470
|
title: 'New Topic',
|
470
471
|
favorite: false,
|
471
472
|
clientId: null,
|
473
|
+
groupId: null,
|
472
474
|
historySummary: null,
|
473
475
|
metadata: null,
|
474
476
|
sessionId,
|
@@ -23,7 +23,7 @@ describe('TableViewerRepo', () => {
|
|
23
23
|
it('should return all tables with counts', async () => {
|
24
24
|
const result = await repo.getAllTables();
|
25
25
|
|
26
|
-
expect(result.length).toEqual(
|
26
|
+
expect(result.length).toEqual(62);
|
27
27
|
expect(result[0]).toEqual({ name: 'agents', count: 0, type: 'BASE TABLE' });
|
28
28
|
});
|
29
29
|
|
@@ -0,0 +1,98 @@
|
|
1
|
+
/* eslint-disable sort-keys-fix/sort-keys-fix */
|
2
|
+
import {
|
3
|
+
boolean,
|
4
|
+
integer,
|
5
|
+
jsonb,
|
6
|
+
pgTable,
|
7
|
+
primaryKey,
|
8
|
+
text,
|
9
|
+
uniqueIndex,
|
10
|
+
varchar,
|
11
|
+
} from 'drizzle-orm/pg-core';
|
12
|
+
import { createInsertSchema } from 'drizzle-zod';
|
13
|
+
|
14
|
+
import { idGenerator } from '@/database/utils/idGenerator';
|
15
|
+
import type { ChatGroupConfig } from '@/database/types/chatGroup';
|
16
|
+
|
17
|
+
import { timestamps } from './_helpers';
|
18
|
+
import { agents } from './agent';
|
19
|
+
import { users } from './user';
|
20
|
+
|
21
|
+
/**
|
22
|
+
* Chat groups table for multi-agent conversations
|
23
|
+
* Allows multiple agents to participate in a single chat session
|
24
|
+
*/
|
25
|
+
export const chatGroups = pgTable(
|
26
|
+
'chat_groups',
|
27
|
+
{
|
28
|
+
id: text('id')
|
29
|
+
.primaryKey()
|
30
|
+
.$defaultFn(() => idGenerator('chatGroups'))
|
31
|
+
.notNull(),
|
32
|
+
title: text('title'),
|
33
|
+
description: text('description'),
|
34
|
+
|
35
|
+
/**
|
36
|
+
* Group configuration
|
37
|
+
*/
|
38
|
+
config: jsonb('config').$type<ChatGroupConfig>(),
|
39
|
+
|
40
|
+
clientId: text('client_id'),
|
41
|
+
|
42
|
+
userId: text('user_id')
|
43
|
+
.references(() => users.id, { onDelete: 'cascade' })
|
44
|
+
.notNull(),
|
45
|
+
|
46
|
+
pinned: boolean('pinned').default(false),
|
47
|
+
|
48
|
+
...timestamps,
|
49
|
+
},
|
50
|
+
(t) => [uniqueIndex('chat_groups_client_id_user_id_unique').on(t.clientId, t.userId)],
|
51
|
+
);
|
52
|
+
|
53
|
+
export const insertChatGroupSchema = createInsertSchema(chatGroups);
|
54
|
+
|
55
|
+
export type NewChatGroup = typeof chatGroups.$inferInsert;
|
56
|
+
export type ChatGroupItem = typeof chatGroups.$inferSelect;
|
57
|
+
|
58
|
+
/**
|
59
|
+
* Junction table connecting chat groups with agents
|
60
|
+
* Defines which agents participate in each group chat
|
61
|
+
*/
|
62
|
+
export const chatGroupsAgents = pgTable(
|
63
|
+
'chat_groups_agents',
|
64
|
+
{
|
65
|
+
chatGroupId: text('chat_group_id')
|
66
|
+
.references(() => chatGroups.id, { onDelete: 'cascade' })
|
67
|
+
.notNull(),
|
68
|
+
agentId: text('agent_id')
|
69
|
+
.references(() => agents.id, { onDelete: 'cascade' })
|
70
|
+
.notNull(),
|
71
|
+
userId: text('user_id')
|
72
|
+
.references(() => users.id, { onDelete: 'cascade' })
|
73
|
+
.notNull(),
|
74
|
+
|
75
|
+
/**
|
76
|
+
* Whether this agent is active in the group
|
77
|
+
*/
|
78
|
+
enabled: boolean('enabled').default(true),
|
79
|
+
|
80
|
+
/**
|
81
|
+
* Display or speaking order of the agent in the group
|
82
|
+
*/
|
83
|
+
order: integer('order').default(0),
|
84
|
+
|
85
|
+
/**
|
86
|
+
* Role of the agent in the group (e.g., 'moderator', 'participant')
|
87
|
+
*/
|
88
|
+
role: text('role').default('participant'),
|
89
|
+
|
90
|
+
...timestamps,
|
91
|
+
},
|
92
|
+
(t) => ({
|
93
|
+
pk: primaryKey({ columns: [t.chatGroupId, t.agentId] }),
|
94
|
+
}),
|
95
|
+
);
|
96
|
+
|
97
|
+
export type NewChatGroupAgent = typeof chatGroupsAgents.$inferInsert;
|
98
|
+
export type ChatGroupAgentItem = typeof agents.$inferInsert
|
@@ -23,6 +23,7 @@ import { chunks, embeddings } from './rag';
|
|
23
23
|
import { sessions } from './session';
|
24
24
|
import { threads, topics } from './topic';
|
25
25
|
import { users } from './user';
|
26
|
+
import { chatGroups } from './chatGroup';
|
26
27
|
|
27
28
|
// @ts-ignore
|
28
29
|
export const messages = pgTable(
|
@@ -64,7 +65,9 @@ export const messages = pgTable(
|
|
64
65
|
|
65
66
|
// used for group chat
|
66
67
|
agentId: text('agent_id').references(() => agents.id, { onDelete: 'set null' }),
|
67
|
-
|
68
|
+
groupId: text('group_id').references(() => chatGroups.id, { onDelete: 'set null' }),
|
69
|
+
// targetId can be an agent ID, "user", or null - no FK constraint
|
70
|
+
targetId: text('target_id'),
|
68
71
|
...timestamps,
|
69
72
|
},
|
70
73
|
(table) => ({
|
@@ -5,6 +5,7 @@ import { pgTable, primaryKey, text, uuid, varchar } from 'drizzle-orm/pg-core';
|
|
5
5
|
import { createdAt } from './_helpers';
|
6
6
|
import { agents, agentsFiles, agentsKnowledgeBases } from './agent';
|
7
7
|
import { asyncTasks } from './asyncTask';
|
8
|
+
import { chatGroups, chatGroupsAgents } from './chatGroup';
|
8
9
|
import { documentChunks, documents } from './document';
|
9
10
|
import { files, knowledgeBases } from './file';
|
10
11
|
import { generationBatches, generationTopics, generations } from './generation';
|
@@ -109,6 +110,7 @@ export const agentsRelations = relations(agents, ({ many }) => ({
|
|
109
110
|
agentsToSessions: many(agentsToSessions),
|
110
111
|
knowledgeBases: many(agentsKnowledgeBases),
|
111
112
|
files: many(agentsFiles),
|
113
|
+
chatGroups: many(chatGroupsAgents),
|
112
114
|
}));
|
113
115
|
|
114
116
|
export const agentsToSessionsRelations = relations(agentsToSessions, ({ one }) => ({
|
@@ -280,3 +282,27 @@ export const generationsRelations = relations(generations, ({ one }) => ({
|
|
280
282
|
references: [files.id],
|
281
283
|
}),
|
282
284
|
}));
|
285
|
+
|
286
|
+
// Chat Groups 相关关系定义
|
287
|
+
export const chatGroupsRelations = relations(chatGroups, ({ many, one }) => ({
|
288
|
+
user: one(users, {
|
289
|
+
fields: [chatGroups.userId],
|
290
|
+
references: [users.id],
|
291
|
+
}),
|
292
|
+
agents: many(chatGroupsAgents),
|
293
|
+
}));
|
294
|
+
|
295
|
+
export const chatGroupsAgentsRelations = relations(chatGroupsAgents, ({ one }) => ({
|
296
|
+
chatGroup: one(chatGroups, {
|
297
|
+
fields: [chatGroupsAgents.chatGroupId],
|
298
|
+
references: [chatGroups.id],
|
299
|
+
}),
|
300
|
+
agent: one(agents, {
|
301
|
+
fields: [chatGroupsAgents.agentId],
|
302
|
+
references: [agents.id],
|
303
|
+
}),
|
304
|
+
user: one(users, {
|
305
|
+
fields: [chatGroupsAgents.userId],
|
306
|
+
references: [users.id],
|
307
|
+
}),
|
308
|
+
}));
|
@@ -9,6 +9,7 @@ import { createdAt, timestamps, timestamptz } from './_helpers';
|
|
9
9
|
import { documents } from './document';
|
10
10
|
import { sessions } from './session';
|
11
11
|
import { users } from './user';
|
12
|
+
import { chatGroups } from './chatGroup';
|
12
13
|
|
13
14
|
export const topics = pgTable(
|
14
15
|
'topics',
|
@@ -19,6 +20,7 @@ export const topics = pgTable(
|
|
19
20
|
title: text('title'),
|
20
21
|
favorite: boolean('favorite').default(false),
|
21
22
|
sessionId: text('session_id').references(() => sessions.id, { onDelete: 'cascade' }),
|
23
|
+
groupId: text('group_id').references(() => chatGroups.id, { onDelete: 'cascade' }),
|
22
24
|
userId: text('user_id')
|
23
25
|
.references(() => users.id, { onDelete: 'cascade' })
|
24
26
|
.notNull(),
|
@@ -0,0 +1,9 @@
|
|
1
|
+
export interface ChatGroupConfig {
|
2
|
+
maxResponseInRow?: number;
|
3
|
+
orchestratorModel?: string;
|
4
|
+
orchestratorProvider?: string;
|
5
|
+
responseOrder?: 'sequential' | 'natural';
|
6
|
+
responseSpeed?: 'slow' | 'medium' | 'fast';
|
7
|
+
revealDM?: boolean;
|
8
|
+
systemPrompt?: string;
|
9
|
+
}
|
@@ -1,4 +1,4 @@
|
|
1
|
-
import type { ChatModelCard } from '
|
1
|
+
import type { ChatModelCard } from '@lobechat/types';
|
2
2
|
|
3
3
|
import type { ModelProviderKey } from '../types';
|
4
4
|
|
@@ -168,11 +168,8 @@ const findKnownModelByProvider = async (
|
|
168
168
|
const lowerModelId = modelId.toLowerCase();
|
169
169
|
|
170
170
|
try {
|
171
|
-
// 动态构建导入路径
|
172
|
-
const modulePath = `@/config/aiModels/${provider}`;
|
173
|
-
|
174
171
|
// 尝试动态导入对应的配置文件
|
175
|
-
const moduleImport = await import(
|
172
|
+
const moduleImport = await import(`@/config/aiModels/${provider}`);
|
176
173
|
const providerModels = moduleImport.default;
|
177
174
|
|
178
175
|
// 如果导入成功且有数据,进行查找
|
@@ -211,9 +208,21 @@ export const detectModelProvider = (modelId: string): keyof typeof MODEL_LIST_CO
|
|
211
208
|
* @param timestamp 时间戳(秒)
|
212
209
|
* @returns 格式化的日期字符串 (YYYY-MM-DD)
|
213
210
|
*/
|
214
|
-
const formatTimestampToDate = (timestamp: number): string => {
|
215
|
-
|
216
|
-
|
211
|
+
const formatTimestampToDate = (timestamp: number): string | undefined => {
|
212
|
+
if (timestamp === null || timestamp === undefined || Number.isNaN(timestamp)) return undefined;
|
213
|
+
|
214
|
+
// 支持秒级或毫秒级时间戳:
|
215
|
+
// - 如果是毫秒级(>= 1e12),直接当作毫秒;
|
216
|
+
// - 否则视为秒,需要 *1000 转为毫秒
|
217
|
+
const msTimestamp = timestamp > 1e12 ? timestamp : timestamp * 1000;
|
218
|
+
const date = new Date(msTimestamp);
|
219
|
+
|
220
|
+
// 验证解析结果和年份范围(只接受 4 位年份,避免超出 varchar(10) 的 YYYY-MM-DD)
|
221
|
+
const year = date.getUTCFullYear();
|
222
|
+
if (year < 1000 || year > 9999) return undefined;
|
223
|
+
|
224
|
+
const dateStr = date.toISOString().split('T')[0]; // YYYY-MM-DD
|
225
|
+
return dateStr.length === 10 ? dateStr : undefined;
|
217
226
|
};
|
218
227
|
|
219
228
|
/**
|
@@ -0,0 +1,30 @@
|
|
1
|
+
import { useTheme } from 'antd-style';
|
2
|
+
import { usePathname } from 'next/navigation';
|
3
|
+
import { PropsWithChildren, memo } from 'react';
|
4
|
+
import { Flexbox } from 'react-layout-kit';
|
5
|
+
|
6
|
+
import SideBar from './SideBar';
|
7
|
+
|
8
|
+
const DesktopLayoutContainer = memo<PropsWithChildren>(({ children }) => {
|
9
|
+
const theme = useTheme();
|
10
|
+
const pathname = usePathname();
|
11
|
+
const hideSideBar = pathname.startsWith('/settings');
|
12
|
+
return (
|
13
|
+
<>
|
14
|
+
{!hideSideBar && <SideBar />}
|
15
|
+
<Flexbox
|
16
|
+
style={{
|
17
|
+
background: theme.colorBgLayout,
|
18
|
+
borderInlineStart: `1px solid ${theme.colorBorderSecondary}`,
|
19
|
+
borderStartStartRadius: !hideSideBar ? 12 : undefined,
|
20
|
+
borderTop: `1px solid ${theme.colorBorderSecondary}`,
|
21
|
+
overflow: 'hidden',
|
22
|
+
}}
|
23
|
+
width={'100%'}
|
24
|
+
>
|
25
|
+
{children}
|
26
|
+
</Flexbox>
|
27
|
+
</>
|
28
|
+
);
|
29
|
+
});
|
30
|
+
export default DesktopLayoutContainer;
|
@@ -2,7 +2,6 @@
|
|
2
2
|
|
3
3
|
import { useTheme } from 'antd-style';
|
4
4
|
import dynamic from 'next/dynamic';
|
5
|
-
import { usePathname } from 'next/navigation';
|
6
5
|
import { PropsWithChildren, Suspense, memo } from 'react';
|
7
6
|
import { HotkeysProvider } from 'react-hotkeys-hook';
|
8
7
|
import { Flexbox } from 'react-layout-kit';
|
@@ -15,6 +14,7 @@ import { usePlatform } from '@/hooks/usePlatform';
|
|
15
14
|
import { featureFlagsSelectors, useServerConfigStore } from '@/store/serverConfig';
|
16
15
|
import { HotkeyScopeEnum } from '@/types/hotkey';
|
17
16
|
|
17
|
+
import DesktopLayoutContainer from './DesktopLayoutContainer';
|
18
18
|
import RegisterHotkeys from './RegisterHotkeys';
|
19
19
|
import SideBar from './SideBar';
|
20
20
|
|
@@ -24,11 +24,7 @@ const Layout = memo<PropsWithChildren>(({ children }) => {
|
|
24
24
|
const { isPWA } = usePlatform();
|
25
25
|
const theme = useTheme();
|
26
26
|
|
27
|
-
const pathname = usePathname();
|
28
27
|
const { showCloudPromotion } = useServerConfigStore(featureFlagsSelectors);
|
29
|
-
|
30
|
-
// setting page not show sidebar
|
31
|
-
const hideSideBar = isDesktop && pathname.startsWith('/settings');
|
32
28
|
return (
|
33
29
|
<HotkeysProvider initiallyActiveScopes={[HotkeyScopeEnum.Global]}>
|
34
30
|
{isDesktop && <TitleBar />}
|
@@ -48,22 +44,13 @@ const Layout = memo<PropsWithChildren>(({ children }) => {
|
|
48
44
|
}}
|
49
45
|
width={'100%'}
|
50
46
|
>
|
51
|
-
{!hideSideBar && <SideBar />}
|
52
47
|
{isDesktop ? (
|
53
|
-
<
|
54
|
-
style={{
|
55
|
-
background: theme.colorBgLayout,
|
56
|
-
borderInlineStart: `1px solid ${theme.colorBorderSecondary}`,
|
57
|
-
borderStartStartRadius: !hideSideBar ? 12 : undefined,
|
58
|
-
borderTop: `1px solid ${theme.colorBorderSecondary}`,
|
59
|
-
overflow: 'hidden',
|
60
|
-
}}
|
61
|
-
width={'100%'}
|
62
|
-
>
|
63
|
-
{children}
|
64
|
-
</Flexbox>
|
48
|
+
<DesktopLayoutContainer>{children}</DesktopLayoutContainer>
|
65
49
|
) : (
|
66
|
-
|
50
|
+
<>
|
51
|
+
<SideBar />
|
52
|
+
{children}
|
53
|
+
</>
|
67
54
|
)}
|
68
55
|
</Flexbox>
|
69
56
|
<HotkeyHelperPanel />
|
@@ -3,7 +3,7 @@
|
|
3
3
|
import { DraggablePanel, DraggablePanelContainer, type DraggablePanelProps } from '@lobehub/ui';
|
4
4
|
import { createStyles, useResponsive } from 'antd-style';
|
5
5
|
import isEqual from 'fast-deep-equal';
|
6
|
-
import { PropsWithChildren, memo, useEffect, useState } from 'react';
|
6
|
+
import { PropsWithChildren, memo, useEffect, useMemo, useState } from 'react';
|
7
7
|
|
8
8
|
import { withSuspense } from '@/components/withSuspense';
|
9
9
|
import { FOLDER_WIDTH } from '@/const/layoutTokens';
|
@@ -69,26 +69,30 @@ const SessionPanel = memo<PropsWithChildren>(({ children }) => {
|
|
69
69
|
if (!md) updatePreference({ showSessionPanel: false });
|
70
70
|
}, [md, cacheExpand]);
|
71
71
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
{
|
89
|
-
|
90
|
-
|
91
|
-
|
72
|
+
const SessionPanel = useMemo(() => {
|
73
|
+
return (
|
74
|
+
<DraggablePanel
|
75
|
+
className={styles.panel}
|
76
|
+
defaultSize={{ width: tmpWidth }}
|
77
|
+
// 当进入 pin 模式下,不可展开
|
78
|
+
expand={!isPinned && sessionExpandable}
|
79
|
+
expandable={!isPinned}
|
80
|
+
maxWidth={400}
|
81
|
+
minWidth={FOLDER_WIDTH}
|
82
|
+
mode={md ? 'fixed' : 'float'}
|
83
|
+
onExpandChange={handleExpand}
|
84
|
+
onSizeChange={handleSizeChange}
|
85
|
+
placement="left"
|
86
|
+
size={{ height: '100%', width: sessionsWidth }}
|
87
|
+
>
|
88
|
+
<DraggablePanelContainer style={{ flex: 'none', height: '100%', minWidth: FOLDER_WIDTH }}>
|
89
|
+
{children}
|
90
|
+
</DraggablePanelContainer>
|
91
|
+
</DraggablePanel>
|
92
|
+
);
|
93
|
+
}, [sessionsWidth, md, isPinned, sessionExpandable, tmpWidth]);
|
94
|
+
|
95
|
+
return SessionPanel;
|
92
96
|
});
|
93
97
|
|
94
98
|
export default withSuspense(SessionPanel);
|
@@ -1,14 +1,20 @@
|
|
1
1
|
'use client';
|
2
2
|
|
3
|
-
import { ActionIcon } from '@lobehub/ui';
|
3
|
+
import { ActionIcon, Tag } from '@lobehub/ui';
|
4
4
|
import { ChatHeader } from '@lobehub/ui/chat';
|
5
5
|
import { Drawer, type DrawerProps } from 'antd';
|
6
6
|
import { createStyles } from 'antd-style';
|
7
7
|
import { Menu } from 'lucide-react';
|
8
|
+
import { usePathname } from 'next/navigation';
|
8
9
|
import { ReactNode, memo, useState } from 'react';
|
10
|
+
import { useTranslation } from 'react-i18next';
|
9
11
|
import { Flexbox } from 'react-layout-kit';
|
10
12
|
|
11
13
|
import BrandWatermark from '@/components/BrandWatermark';
|
14
|
+
// 新增:引入 SettingsTabs
|
15
|
+
import { useActiveSettingsKey } from '@/hooks/useActiveTabKey';
|
16
|
+
import { useProviderName } from '@/hooks/useProviderName';
|
17
|
+
import { SettingsTabs } from '@/store/global/initialState';
|
12
18
|
|
13
19
|
const useStyles = createStyles(({ token, css }) => ({
|
14
20
|
container: css`
|
@@ -26,12 +32,31 @@ const useStyles = createStyles(({ token, css }) => ({
|
|
26
32
|
|
27
33
|
interface HeaderProps extends Pick<DrawerProps, 'getContainer'> {
|
28
34
|
children: ReactNode;
|
29
|
-
title
|
35
|
+
title?: ReactNode;
|
30
36
|
}
|
31
37
|
|
32
38
|
const Header = memo<HeaderProps>(({ children, getContainer, title }) => {
|
33
39
|
const [open, setOpen] = useState(false);
|
34
40
|
const { styles, theme } = useStyles();
|
41
|
+
const activeKey = useActiveSettingsKey();
|
42
|
+
const providerName = useProviderName(activeKey);
|
43
|
+
|
44
|
+
const pathname = usePathname();
|
45
|
+
const { t } = useTranslation('setting');
|
46
|
+
|
47
|
+
const isProvider = pathname.includes('/settings/provider/');
|
48
|
+
const dynamicTitle = title ? (
|
49
|
+
title
|
50
|
+
) : (
|
51
|
+
<>
|
52
|
+
{isProvider ? providerName : t(`tab.${activeKey}`)}
|
53
|
+
{activeKey === SettingsTabs.Sync && (
|
54
|
+
<Tag bordered={false} color={'gold'}>
|
55
|
+
{t('tab.experiment')}
|
56
|
+
</Tag>
|
57
|
+
)}
|
58
|
+
</>
|
59
|
+
);
|
35
60
|
|
36
61
|
return (
|
37
62
|
<>
|
@@ -47,7 +72,7 @@ const Header = memo<HeaderProps>(({ children, getContainer, title }) => {
|
|
47
72
|
onClick={() => setOpen(true)}
|
48
73
|
size={{ blockSize: 32, size: 18 }}
|
49
74
|
/>
|
50
|
-
{
|
75
|
+
{dynamicTitle}
|
51
76
|
</Flexbox>
|
52
77
|
}
|
53
78
|
/>
|
@@ -1,18 +1,13 @@
|
|
1
1
|
'use client';
|
2
2
|
|
3
|
-
import { Tag } from '@lobehub/ui';
|
4
3
|
import { useResponsive, useTheme } from 'antd-style';
|
5
4
|
import { usePathname } from 'next/navigation';
|
6
|
-
import { memo, useRef } from 'react';
|
7
|
-
import { useTranslation } from 'react-i18next';
|
5
|
+
import { PropsWithChildren, memo, useEffect, useRef } from 'react';
|
8
6
|
import { Flexbox } from 'react-layout-kit';
|
9
7
|
|
10
8
|
import InitClientDB from '@/features/InitClientDB';
|
11
9
|
import Footer from '@/features/Setting/Footer';
|
12
10
|
import SettingContainer from '@/features/Setting/SettingContainer';
|
13
|
-
import { useActiveSettingsKey } from '@/hooks/useActiveTabKey';
|
14
|
-
import { useProviderName } from '@/hooks/useProviderName';
|
15
|
-
import { SettingsTabs } from '@/store/global/initialState';
|
16
11
|
|
17
12
|
import { LayoutProps } from '../type';
|
18
13
|
import Header from './Header';
|
@@ -20,17 +15,25 @@ import SideBar from './SideBar';
|
|
20
15
|
|
21
16
|
const SKIP_PATHS = ['/settings/provider', '/settings/agent'];
|
22
17
|
|
18
|
+
const ContentContainer = memo<PropsWithChildren>(({ children }) => {
|
19
|
+
const pathname = usePathname();
|
20
|
+
const isSkip = SKIP_PATHS.some((path) => pathname.includes(path));
|
21
|
+
|
22
|
+
return isSkip ? (
|
23
|
+
children
|
24
|
+
) : (
|
25
|
+
<SettingContainer addonAfter={<Footer />}>{children}</SettingContainer>
|
26
|
+
);
|
27
|
+
});
|
28
|
+
|
23
29
|
const Layout = memo<LayoutProps>(({ children, category }) => {
|
24
30
|
const ref = useRef<any>(null);
|
25
31
|
const { md = true } = useResponsive();
|
26
|
-
const { t } = useTranslation('setting');
|
27
|
-
const activeKey = useActiveSettingsKey();
|
28
32
|
const theme = useTheme();
|
29
|
-
const pathname = usePathname();
|
30
33
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
+
useEffect(() => {
|
35
|
+
console.log('settings render');
|
36
|
+
});
|
34
37
|
|
35
38
|
return (
|
36
39
|
<Flexbox
|
@@ -42,19 +45,9 @@ const Layout = memo<LayoutProps>(({ children, category }) => {
|
|
42
45
|
{md ? (
|
43
46
|
<SideBar>{category}</SideBar>
|
44
47
|
) : (
|
45
|
-
<Header
|
46
|
-
getContainer={() => ref.current}
|
47
|
-
title={
|
48
|
-
<>
|
49
|
-
{isProvider ? providerName : t(`tab.${activeKey}`)}
|
50
|
-
{activeKey === SettingsTabs.Sync && <Tag color={'gold'}>{t('tab.experiment')}</Tag>}
|
51
|
-
</>
|
52
|
-
}
|
53
|
-
>
|
54
|
-
{category}
|
55
|
-
</Header>
|
48
|
+
<Header getContainer={() => ref.current!}>{category}</Header>
|
56
49
|
)}
|
57
|
-
|
50
|
+
<ContentContainer>{children}</ContentContainer>
|
58
51
|
<InitClientDB />
|
59
52
|
</Flexbox>
|
60
53
|
);
|
@@ -17,8 +17,8 @@ import useResizeObserver from './useResizeObserver';
|
|
17
17
|
pdfjs.GlobalWorkerOptions.workerSrc = `https://registry.npmmirror.com/pdfjs-dist/${pdfjs.version}/files/build/pdf.worker.min.mjs`;
|
18
18
|
|
19
19
|
const options = {
|
20
|
-
cMapUrl:
|
21
|
-
standardFontDataUrl:
|
20
|
+
cMapUrl: `https://registry.npmmirror.com/pdfjs-dist/${pdfjs.version}/files/cmaps/`,
|
21
|
+
standardFontDataUrl: `https://registry.npmmirror.com/pdfjs-dist/${pdfjs.version}/files/standard_fonts/`,
|
22
22
|
};
|
23
23
|
|
24
24
|
const maxWidth = 1200;
|