Skip to content

Commit 01f4b7f

Browse files
committed
feat: ✨ 在模型选择部分添加搜索功能
resolve #201
1 parent 340c0e3 commit 01f4b7f

File tree

1 file changed

+31
-10
lines changed

1 file changed

+31
-10
lines changed

web/src/components/modules/group/Editor.tsx

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
'use client';
22

33
import { useCallback, useMemo, useState, type FormEvent } from 'react';
4-
import { Check, ChevronDownIcon, Plus, Sparkles, Trash2 } from 'lucide-react';
4+
import { Check, ChevronDownIcon, Plus, Search, Sparkles, Trash2 } from 'lucide-react';
55
import { useTranslations } from 'next-intl';
66
import * as AccordionPrimitive from '@radix-ui/react-accordion';
77
import { useModelChannelList, type LLMChannel } from '@/api/endpoints/model';
@@ -43,8 +43,10 @@ function ModelPickerSection({
4343
autoAddDisabled: boolean;
4444
}) {
4545
const t = useTranslations('group');
46+
const [searchKeyword, setSearchKeyword] = useState('');
4647

4748
const selectedKeys = useMemo(() => new Set(selectedMembers.map(memberKey)), [selectedMembers]);
49+
const normalizedSearch = searchKeyword.trim().toLowerCase();
4850

4951
const channels = useMemo(() => {
5052
const byId = new Map<number, { id: number; name: string; models: LLMChannel[] }>();
@@ -59,21 +61,42 @@ function ModelPickerSection({
5961
.sort((a, b) => a.id - b.id);
6062
}, [modelChannels]);
6163

64+
const filteredChannels = useMemo(() => {
65+
if (!normalizedSearch) return channels;
66+
return channels.reduce<typeof channels>((acc, channel) => {
67+
if (channel.name.toLowerCase().includes(normalizedSearch)) {
68+
acc.push(channel);
69+
return acc;
70+
}
71+
72+
const models = channel.models.filter((model) => model.name.toLowerCase().includes(normalizedSearch));
73+
if (models.length > 0) acc.push({ ...channel, models });
74+
return acc;
75+
}, []);
76+
}, [channels, normalizedSearch]);
77+
6278
return (
6379
<div className="rounded-xl border border-border/50 bg-muted/30 flex flex-col min-h-0">
64-
<div className="flex items-center justify-between px-3 py-2 border-b border-border/30 bg-muted/50">
65-
<span className="text-sm font-medium text-foreground">
80+
<div className="grid grid-cols-[1fr_auto_1fr] items-center gap-2 px-3 py-2 border-b border-border/30 bg-muted/50">
81+
<span className="min-w-0 justify-self-start text-sm font-medium text-foreground">
6682
{t('form.addItem')}
67-
<span className="ml-1.5 text-xs text-muted-foreground font-normal">
68-
({selectedMembers.length})
69-
</span>
7083
</span>
7184

85+
<div className="relative justify-self-center w-30">
86+
<Search className="pointer-events-none absolute left-2 top-1/2 size-3.5 -translate-y-1/2 text-muted-foreground" />
87+
<Input
88+
value={searchKeyword}
89+
onChange={(event) => setSearchKeyword(event.target.value)}
90+
className="h-6 rounded-lg border-border/60 bg-background/70 pl-7 pr-2 text-xs shadow-none focus-visible:border-border/60 focus-visible:ring-0"
91+
aria-label="search"
92+
/>
93+
</div>
94+
7295
<button
7396
type="button"
7497
onClick={onAutoAdd}
7598
className={cn(
76-
'flex items-center gap-1 px-2 py-1 rounded-lg text-xs font-medium transition-colors',
99+
'justify-self-end shrink-0 flex items-center gap-1 px-2 py-1 rounded-lg text-xs font-medium transition-colors',
77100
autoAddDisabled
78101
? 'text-muted-foreground/50 cursor-not-allowed'
79102
: 'hover:bg-muted text-muted-foreground hover:text-foreground'
@@ -88,7 +111,7 @@ function ModelPickerSection({
88111

89112
<div className="flex-1 min-h-0 overflow-y-auto p-2">
90113
<Accordion type="multiple" className="w-full space-y-2">
91-
{channels.map((channel) => {
114+
{filteredChannels.map((channel) => {
92115
const total = channel.models.length;
93116
const selectedCount = channel.models.reduce(
94117
(acc, m) => acc + (selectedKeys.has(memberKey(m)) ? 1 : 0),
@@ -480,5 +503,3 @@ export function GroupEditor({
480503
</form>
481504
);
482505
}
483-
484-

0 commit comments

Comments
 (0)