Skip to content

Commit 539bb76

Browse files
authored
update
1 parent c7170c0 commit 539bb76

File tree

17 files changed

+469
-55
lines changed

17 files changed

+469
-55
lines changed

src/app/(backend)/middleware/auth/index.ts

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import { AuthObject } from '@clerk/backend';
2-
import { getAuth } from '@clerk/nextjs/server';
32
import { NextRequest } from 'next/server';
43

54
import { JWTPayload, LOBE_CHAT_AUTH_HEADER, OAUTH_AUTHORIZED, enableClerk } from '@/const/auth';
65
import { AgentRuntime, AgentRuntimeError, ChatCompletionErrorPayload } from '@/libs/agent-runtime';
6+
import { ClerkAuth } from '@/libs/clerk-auth';
77
import { ChatErrorType } from '@/types/fetch';
88
import { createErrorResponse } from '@/utils/errorResponse';
99
import { getJWTPayload } from '@/utils/server/jwt';
@@ -41,8 +41,11 @@ export const checkAuth =
4141
// check the Auth With payload and clerk auth
4242
let clerkAuth = {} as AuthObject;
4343

44+
// TODO: V2 完整移除 client 模式下的 clerk 集成代码
4445
if (enableClerk) {
45-
clerkAuth = getAuth(req as NextRequest);
46+
const auth = new ClerkAuth();
47+
const data = auth.getAuthFromRequest(req as NextRequest);
48+
clerkAuth = data.clerkAuth;
4649
}
4750

4851
jwtPayload = await getJWTPayload(authorization);

src/app/[variants]/(main)/chat/(workspace)/@topic/features/SkeletonList.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ const useStyles = createStyles(({ css, prefixCls }) => ({
2323

2424
paragraph: css`
2525
> li {
26-
height: 24px !important;
26+
height: 20px !important;
2727
}
2828
`,
2929
}));
@@ -49,7 +49,7 @@ export const Placeholder = memo(() => {
4949

5050
export const SkeletonList = memo(() => (
5151
<Flexbox style={{ paddingTop: 6 }}>
52-
{Array.from({ length: 6 }).map((_, i) => (
52+
{Array.from({ length: 4 }).map((_, i) => (
5353
<Placeholder key={i} />
5454
))}
5555
</Flexbox>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
'use client';
2+
3+
import { Typography } from 'antd';
4+
import isEqual from 'fast-deep-equal';
5+
import React, { memo, useCallback, useRef } from 'react';
6+
import { useTranslation } from 'react-i18next';
7+
import { Center } from 'react-layout-kit';
8+
import { Virtuoso, VirtuosoHandle } from 'react-virtuoso';
9+
10+
import { useChatStore } from '@/store/chat';
11+
import { topicSelectors } from '@/store/chat/selectors';
12+
import { ChatTopic } from '@/types/topic';
13+
14+
import { SkeletonList } from '../../SkeletonList';
15+
import TopicItem from '../TopicItem';
16+
17+
const SearchResult = memo(() => {
18+
const { t } = useTranslation('topic');
19+
const virtuosoRef = useRef<VirtuosoHandle>(null);
20+
const [activeTopicId, isSearchingTopic] = useChatStore((s) => [
21+
s.activeTopicId,
22+
topicSelectors.isSearchingTopic(s),
23+
]);
24+
const topics = useChatStore(topicSelectors.searchTopics, isEqual);
25+
26+
const itemContent = useCallback(
27+
(index: number, { id, favorite, title }: ChatTopic) => (
28+
<TopicItem active={activeTopicId === id} fav={favorite} id={id} key={id} title={title} />
29+
),
30+
[activeTopicId],
31+
);
32+
33+
const activeIndex = topics.findIndex((topic) => topic.id === activeTopicId);
34+
35+
if (isSearchingTopic) return <SkeletonList />;
36+
37+
if (topics.length === 0)
38+
return (
39+
<Center paddingBlock={12}>
40+
<Typography.Text type={'secondary'}>{t('searchResultEmpty')}</Typography.Text>
41+
</Center>
42+
);
43+
44+
return (
45+
<Virtuoso
46+
computeItemKey={(_, item) => item.id}
47+
data={topics}
48+
defaultItemHeight={44}
49+
initialTopMostItemIndex={Math.max(activeIndex, 0)}
50+
itemContent={itemContent}
51+
overscan={44 * 10}
52+
ref={virtuosoRef}
53+
/>
54+
);
55+
});
56+
57+
SearchResult.displayName = 'SearchResult';
58+
59+
export default SearchResult;

src/app/[variants]/(main)/chat/(workspace)/@topic/features/TopicListContent/index.tsx

+8-3
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import { EmptyCard } from '@lobehub/ui';
44
import { useThemeMode } from 'antd-style';
5-
import isEqual from 'fast-deep-equal';
65
import React, { memo } from 'react';
76
import { useTranslation } from 'react-i18next';
87
import { Flexbox } from 'react-layout-kit';
@@ -18,6 +17,7 @@ import { TopicDisplayMode } from '@/types/topic';
1817
import { SkeletonList } from '../SkeletonList';
1918
import ByTimeMode from './ByTimeMode';
2019
import FlatMode from './FlatMode';
20+
import SearchResult from './SearchResult';
2121

2222
const TopicListContent = memo(() => {
2323
const { t } = useTranslation('topic');
@@ -26,7 +26,10 @@ const TopicListContent = memo(() => {
2626
s.topicsInit,
2727
topicSelectors.currentTopicLength(s),
2828
]);
29-
const activeTopicList = useChatStore(topicSelectors.displayTopics, isEqual);
29+
const [isUndefinedTopics, isInSearchMode] = useChatStore((s) => [
30+
topicSelectors.isUndefinedTopics(s),
31+
topicSelectors.isInSearchMode(s),
32+
]);
3033

3134
const [visible, updateGuideState, topicDisplayMode] = useUserStore((s) => [
3235
s.preference.guide?.topic,
@@ -36,8 +39,10 @@ const TopicListContent = memo(() => {
3639

3740
useFetchTopics();
3841

42+
if (isInSearchMode) return <SearchResult />;
43+
3944
// first time loading or has no data
40-
if (!topicsInit || !activeTopicList) return <SkeletonList />;
45+
if (!topicsInit || isUndefinedTopics) return <SkeletonList />;
4146

4247
return (
4348
<>

src/app/[variants]/(main)/chat/(workspace)/@topic/features/TopicSearchBar/index.tsx

+23-9
Original file line numberDiff line numberDiff line change
@@ -11,30 +11,44 @@ import { useServerConfigStore } from '@/store/serverConfig';
1111
const TopicSearchBar = memo<{ onClear?: () => void }>(({ onClear }) => {
1212
const { t } = useTranslation('topic');
1313

14-
const [keywords, setKeywords] = useState('');
14+
const [tempValue, setTempValue] = useState('');
15+
const [searchKeyword, setSearchKeywords] = useState('');
1516
const mobile = useServerConfigStore((s) => s.isMobile);
1617
const [activeSessionId, useSearchTopics] = useChatStore((s) => [s.activeId, s.useSearchTopics]);
1718

18-
useSearchTopics(keywords, activeSessionId);
19+
useSearchTopics(searchKeyword, activeSessionId);
20+
1921
useUnmount(() => {
20-
useChatStore.setState({ isSearchingTopic: false });
22+
useChatStore.setState({ inSearchingMode: false, isSearchingTopic: false });
2123
});
24+
25+
const startSearchTopic = () => {
26+
if (tempValue === searchKeyword) return;
27+
28+
setSearchKeywords(tempValue);
29+
useChatStore.setState({ inSearchingMode: !!tempValue, isSearchingTopic: !!tempValue });
30+
};
31+
2232
return (
2333
<SearchBar
2434
autoFocus
2535
onBlur={() => {
26-
if (keywords === '') onClear?.();
36+
if (tempValue === '') {
37+
onClear?.();
38+
39+
return;
40+
}
41+
42+
startSearchTopic();
2743
}}
2844
onChange={(e) => {
29-
const value = e.target.value;
30-
31-
setKeywords(value);
32-
useChatStore.setState({ isSearchingTopic: !!value });
45+
setTempValue(e.target.value);
3346
}}
47+
onPressEnter={startSearchTopic}
3448
placeholder={t('searchPlaceholder')}
3549
spotlight={!mobile}
3650
type={mobile ? 'block' : 'ghost'}
37-
value={keywords}
51+
value={tempValue}
3852
/>
3953
);
4054
});

src/database/schemas/user.ts

-3
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,6 @@ export const userSettings = pgTable('user_settings', {
3939
.primaryKey(),
4040

4141
tts: jsonb('tts'),
42-
/**
43-
* @deprecated
44-
*/
4542
keyVaults: text('key_vaults'),
4643
general: jsonb('general'),
4744
languageModel: jsonb('language_model'),

src/database/server/models/topic.ts

+46-16
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { Column, count, sql } from 'drizzle-orm';
2-
import { and, desc, eq, exists, gt, inArray, isNull, like, or } from 'drizzle-orm/expressions';
1+
import { count, sql } from 'drizzle-orm';
2+
import { and, desc, eq, gt, inArray, isNull, like } from 'drizzle-orm/expressions';
33

44
import { LobeChatDatabase } from '@/database/type';
55
import {
@@ -79,27 +79,57 @@ export class TopicModel {
7979

8080
const keywordLowerCase = keyword.toLowerCase();
8181

82-
const matchKeyword = (field: any) =>
83-
like(sql`lower(${field})` as unknown as Column, `%${keywordLowerCase}%`);
84-
85-
return this.db.query.topics.findMany({
82+
// 查询标题匹配的主题
83+
const topicsByTitle = await this.db.query.topics.findMany({
8684
orderBy: [desc(topics.updatedAt)],
8785
where: and(
8886
eq(topics.userId, this.userId),
8987
this.matchSession(sessionId),
90-
or(
91-
matchKeyword(topics.title),
92-
exists(
93-
this.db
94-
.select()
95-
.from(messages)
96-
.where(and(eq(messages.topicId, topics.id), matchKeyword(messages.content))),
97-
),
98-
),
88+
like(topics.title, `%${keywordLowerCase}%`),
9989
),
10090
});
101-
};
10291

92+
// 查询消息内容匹配的主题ID
93+
const topicIdsByMessages = await this.db
94+
.select({ topicId: messages.topicId })
95+
.from(messages)
96+
.innerJoin(topics, eq(messages.topicId, topics.id))
97+
.where(
98+
and(
99+
eq(messages.userId, this.userId),
100+
like(messages.content, `%${keywordLowerCase}%`),
101+
eq(topics.userId, this.userId),
102+
this.matchSession(sessionId),
103+
),
104+
)
105+
.groupBy(messages.topicId);
106+
// 如果没有通过消息内容找到主题,直接返回标题匹配的主题
107+
if (topicIdsByMessages.length === 0) {
108+
return topicsByTitle;
109+
}
110+
111+
// 查询通过消息内容找到的主题
112+
const topicIds = topicIdsByMessages.map((t) => t.topicId);
113+
const topicsByMessages = await this.db.query.topics.findMany({
114+
orderBy: [desc(topics.updatedAt)],
115+
where: and(eq(topics.userId, this.userId), inArray(topics.id, topicIds)),
116+
});
117+
118+
// 合并结果并去重
119+
const allTopics = [...topicsByTitle];
120+
const existingIds = new Set(topicsByTitle.map((t) => t.id));
121+
122+
for (const topic of topicsByMessages) {
123+
if (!existingIds.has(topic.id)) {
124+
allTopics.push(topic);
125+
}
126+
}
127+
128+
// 按更新时间排序
129+
return allTopics.sort(
130+
(a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime(),
131+
);
132+
};
103133
count = async (params?: {
104134
endDate?: string;
105135
range?: [string, string];

src/database/server/models/user.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -174,15 +174,15 @@ export class UserModel {
174174
// if user already exists, skip creation
175175
if (params.id) {
176176
const user = await db.query.users.findFirst({ where: eq(users.id, params.id) });
177-
if (!!user) return;
177+
if (!!user) return { duplicate: true };
178178
}
179179

180180
const [user] = await db
181181
.insert(users)
182182
.values({ ...params })
183183
.returning();
184184

185-
return user;
185+
return { duplicate: false, user };
186186
};
187187

188188
static deleteUser = async (db: LobeChatDatabase, id: string) => {

0 commit comments

Comments
 (0)