Skip to content

Commit a2d2e83

Browse files
committed
feat: 对话导航栏添加搜索功能
1 parent b4b9ede commit a2d2e83

File tree

4 files changed

+111
-20
lines changed

4 files changed

+111
-20
lines changed

app/components/chat.module.scss

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1812,21 +1812,49 @@
18121812
}
18131813
}
18141814

1815-
.chat-navigator:hover .chat-navigator-panel {
1815+
.chat-navigator:hover .chat-navigator-panel,
1816+
.chat-navigator-active .chat-navigator-panel {
18161817
opacity: 1;
18171818
visibility: visible;
18181819
pointer-events: auto;
18191820
transform: translateY(-50%) translateX(0);
18201821
}
18211822

18221823
.chat-navigator-header {
1823-
padding: 10px 14px 10px 4px;
1824+
padding: 10px 10px 10px 6px;
18241825
border-bottom: var(--border-in-light);
1826+
flex-shrink: 0;
1827+
display: flex;
1828+
align-items: center;
1829+
justify-content: space-between;
1830+
gap: 12px;
1831+
}
1832+
1833+
.chat-navigator-title {
18251834
font-weight: 600;
18261835
font-size: 13px;
18271836
color: var(--black);
1837+
white-space: nowrap;
18281838
flex-shrink: 0;
1829-
text-align: center;
1839+
}
1840+
1841+
.chat-navigator-search-input {
1842+
width: 120px;
1843+
padding: 5px 10px;
1844+
border: 1px solid var(--border-in-light);
1845+
border-radius: 6px;
1846+
font-size: 12px;
1847+
outline: none;
1848+
background: var(--white);
1849+
color: var(--black);
1850+
1851+
&:focus {
1852+
border-color: var(--primary);
1853+
}
1854+
1855+
&::placeholder {
1856+
color: #999;
1857+
}
18301858
}
18311859

18321860
.chat-navigator-list {
@@ -1847,8 +1875,9 @@
18471875
padding: 8px 14px 8px 4px;
18481876
cursor: pointer;
18491877
display: flex;
1850-
flex-direction: column;
1851-
gap: 2px;
1878+
flex-direction: row;
1879+
align-items: center;
1880+
gap: 6px;
18521881
transition: all 0.15s ease;
18531882
border-left: 2px solid transparent;
18541883

@@ -1857,6 +1886,11 @@
18571886
}
18581887
}
18591888

1889+
.chat-navigator-item-role {
1890+
font-size: 12px;
1891+
flex-shrink: 0;
1892+
}
1893+
18601894
.chat-navigator-item-active {
18611895
background-color: rgba(0, 0, 0, 0.04);
18621896
border-left-color: var(--primary);
@@ -1868,6 +1902,8 @@
18681902
}
18691903

18701904
.chat-navigator-item-preview {
1905+
flex: 1;
1906+
min-width: 0;
18711907
font-size: 13px;
18721908
color: var(--black);
18731909
overflow: hidden;

app/components/chat.tsx

Lines changed: 66 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2315,18 +2315,51 @@ function ChatNavigator(props: {
23152315
const PREVIEW_LENGTH = 20;
23162316
const listRef = useRef<HTMLDivElement>(null);
23172317
const activeItemRef = useRef<HTMLDivElement>(null);
2318+
const navigatorRef = useRef<HTMLDivElement>(null);
2319+
const [searchQuery, setSearchQuery] = useState("");
2320+
const [isSearchFocused, setIsSearchFocused] = useState(false);
23182321

2319-
// 过滤用户消息并生成缩略列表
2320-
const userMessages = useMemo(() => {
2321-
return props.messages
2322-
.map((msg, index) => ({
2323-
id: msg.id,
2324-
index,
2325-
preview: getMessageTextContent(msg).slice(0, PREVIEW_LENGTH),
2326-
role: msg.role,
2327-
}))
2328-
.filter((msg) => msg.role === "user");
2329-
}, [props.messages]);
2322+
// 面板是否应该保持展开(搜索框聚焦或有搜索内容时)
2323+
const shouldKeepOpen = isSearchFocused || searchQuery.trim().length > 0;
2324+
2325+
// 点击导航区外部时清空搜索
2326+
useEffect(() => {
2327+
if (!shouldKeepOpen) return;
2328+
2329+
const handleClickOutside = (e: MouseEvent) => {
2330+
if (
2331+
navigatorRef.current &&
2332+
!navigatorRef.current.contains(e.target as Node)
2333+
) {
2334+
setSearchQuery("");
2335+
}
2336+
};
2337+
2338+
document.addEventListener("click", handleClickOutside);
2339+
return () => document.removeEventListener("click", handleClickOutside);
2340+
}, [shouldKeepOpen]);
2341+
2342+
// 生成消息列表(用户消息 or 搜索结果)
2343+
const displayMessages = useMemo(() => {
2344+
const allMessages = props.messages.map((msg, index) => ({
2345+
id: msg.id,
2346+
index,
2347+
content: getMessageTextContent(msg),
2348+
preview: getMessageTextContent(msg).slice(0, PREVIEW_LENGTH),
2349+
role: msg.role,
2350+
}));
2351+
2352+
// 如果有搜索词,搜索所有消息
2353+
if (searchQuery.trim()) {
2354+
const query = searchQuery.toLowerCase();
2355+
return allMessages.filter((msg) =>
2356+
msg.content.toLowerCase().includes(query),
2357+
);
2358+
}
2359+
2360+
// 否则只显示用户消息
2361+
return allMessages.filter((msg) => msg.role === "user");
2362+
}, [props.messages, searchQuery]);
23302363

23312364
// 当 hover 面板时,滚动到当前高亮项
23322365
const scrollToActiveItem = useCallback(() => {
@@ -2340,9 +2373,11 @@ function ChatNavigator(props: {
23402373

23412374
return (
23422375
<div
2376+
ref={navigatorRef}
23432377
className={clsx(
23442378
styles["chat-navigator"],
23452379
props.inPanel && styles["chat-navigator-in-panel"],
2380+
shouldKeepOpen && styles["chat-navigator-active"],
23462381
)}
23472382
onMouseEnter={scrollToActiveItem}
23482383
>
@@ -2351,15 +2386,28 @@ function ChatNavigator(props: {
23512386
</div>
23522387
<div className={styles["chat-navigator-panel"]}>
23532388
<div className={styles["chat-navigator-header"]}>
2354-
{Locale.Chat.Navigator.Title}
2389+
<span className={styles["chat-navigator-title"]}>
2390+
{Locale.Chat.Navigator.Title}
2391+
</span>
2392+
<input
2393+
type="text"
2394+
placeholder={Locale.Chat.Navigator.Search}
2395+
value={searchQuery}
2396+
onChange={(e) => setSearchQuery(e.target.value)}
2397+
onFocus={() => setIsSearchFocused(true)}
2398+
onBlur={() => setIsSearchFocused(false)}
2399+
className={styles["chat-navigator-search-input"]}
2400+
/>
23552401
</div>
23562402
<div className={styles["chat-navigator-list"]} ref={listRef}>
2357-
{userMessages.length === 0 ? (
2403+
{displayMessages.length === 0 ? (
23582404
<div className={styles["chat-navigator-empty"]}>
2359-
{Locale.Chat.Navigator.Empty}
2405+
{searchQuery.trim()
2406+
? Locale.Chat.Navigator.NoResults
2407+
: Locale.Chat.Navigator.Empty}
23602408
</div>
23612409
) : (
2362-
userMessages.map((item) => {
2410+
displayMessages.map((item) => {
23632411
const isActive = props.currentIndex === item.index;
23642412
return (
23652413
<div
@@ -2371,6 +2419,9 @@ function ChatNavigator(props: {
23712419
)}
23722420
onClick={() => props.onJumpTo(item.index)}
23732421
>
2422+
<div className={styles["chat-navigator-item-role"]}>
2423+
{item.role === "user" ? "👨" : "💡"}
2424+
</div>
23742425
<div className={styles["chat-navigator-item-preview"]}>
23752426
{item.preview || "(空消息)"}
23762427
</div>

app/locales/cn.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,8 @@ const cn = {
261261
Navigator: {
262262
Title: "对话导航",
263263
Empty: "暂无消息",
264+
Search: "搜索消息...",
265+
NoResults: "无匹配结果",
264266
},
265267
Typing: "正在输入…",
266268
GoToCustomProviderConfig: "点击跳转对应的渠道配置",

app/locales/en.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,8 @@ const en: LocaleType = {
281281
Navigator: {
282282
Title: "Chat Navigator",
283283
Empty: "No messages",
284+
Search: "Search messages...",
285+
NoResults: "No matches found",
284286
},
285287
Typing: "Typing…",
286288
GoToCustomProviderConfig: "Go to Custom AI Provider Config",

0 commit comments

Comments
 (0)