@gmickel/gno 0.17.0 → 0.19.0
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/README.md +25 -1
- package/package.json +1 -1
- package/src/cli/commands/ask.ts +7 -0
- package/src/cli/commands/models/use.ts +1 -0
- package/src/cli/program.ts +42 -0
- package/src/config/types.ts +2 -0
- package/src/llm/nodeLlamaCpp/generation.ts +3 -1
- package/src/llm/registry.ts +1 -0
- package/src/llm/types.ts +2 -0
- package/src/mcp/tools/index.ts +7 -0
- package/src/mcp/tools/query.ts +6 -0
- package/src/mcp/tools/search.ts +4 -0
- package/src/mcp/tools/vsearch.ts +4 -0
- package/src/pipeline/exclude.ts +69 -0
- package/src/pipeline/expansion.ts +39 -4
- package/src/pipeline/hybrid.ts +59 -18
- package/src/pipeline/intent.ts +152 -0
- package/src/pipeline/rerank.ts +81 -44
- package/src/pipeline/search.ts +34 -1
- package/src/pipeline/types.ts +15 -0
- package/src/pipeline/vsearch.ts +41 -1
- package/src/serve/public/lib/retrieval-filters.ts +10 -0
- package/src/serve/public/pages/Ask.tsx +189 -1
- package/src/serve/public/pages/Search.tsx +78 -2
- package/src/serve/routes/api.ts +161 -48
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
FileText,
|
|
7
7
|
SlidersHorizontal,
|
|
8
8
|
Sparkles,
|
|
9
|
+
XIcon,
|
|
9
10
|
} from "lucide-react";
|
|
10
11
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
11
12
|
|
|
@@ -40,7 +41,12 @@ import {
|
|
|
40
41
|
import { Textarea } from "../components/ui/textarea";
|
|
41
42
|
import { apiFetch } from "../hooks/use-api";
|
|
42
43
|
import { useKeyboardShortcuts } from "../hooks/useKeyboardShortcuts";
|
|
43
|
-
import {
|
|
44
|
+
import {
|
|
45
|
+
parseTagsCsv,
|
|
46
|
+
type QueryModeEntry,
|
|
47
|
+
type QueryModeType,
|
|
48
|
+
type TagMode,
|
|
49
|
+
} from "../lib/retrieval-filters";
|
|
44
50
|
import { cn } from "../lib/utils";
|
|
45
51
|
|
|
46
52
|
interface PageProps {
|
|
@@ -79,6 +85,11 @@ interface AskResponse {
|
|
|
79
85
|
vectorsUsed: boolean;
|
|
80
86
|
answerGenerated: boolean;
|
|
81
87
|
totalResults: number;
|
|
88
|
+
queryModes?: {
|
|
89
|
+
term: number;
|
|
90
|
+
intent: number;
|
|
91
|
+
hyde: boolean;
|
|
92
|
+
};
|
|
82
93
|
};
|
|
83
94
|
}
|
|
84
95
|
|
|
@@ -103,6 +114,12 @@ interface Collection {
|
|
|
103
114
|
|
|
104
115
|
const THOROUGHNESS_ORDER: Thoroughness[] = ["fast", "balanced", "thorough"];
|
|
105
116
|
|
|
117
|
+
const QUERY_MODE_LABEL: Record<QueryModeType, string> = {
|
|
118
|
+
term: "Term",
|
|
119
|
+
intent: "Intent",
|
|
120
|
+
hyde: "HyDE",
|
|
121
|
+
};
|
|
122
|
+
|
|
106
123
|
/**
|
|
107
124
|
* Render answer text with clickable citation badges.
|
|
108
125
|
* Citations like [1] become clickable to navigate to source.
|
|
@@ -164,12 +181,19 @@ export default function Ask({ navigate }: PageProps) {
|
|
|
164
181
|
|
|
165
182
|
const [showAdvanced, setShowAdvanced] = useState(false);
|
|
166
183
|
const [selectedCollection, setSelectedCollection] = useState("");
|
|
184
|
+
const [intent, setIntent] = useState("");
|
|
185
|
+
const [candidateLimit, setCandidateLimit] = useState("");
|
|
186
|
+
const [exclude, setExclude] = useState("");
|
|
167
187
|
const [since, setSince] = useState("");
|
|
168
188
|
const [until, setUntil] = useState("");
|
|
169
189
|
const [category, setCategory] = useState("");
|
|
170
190
|
const [author, setAuthor] = useState("");
|
|
171
191
|
const [tagMode, setTagMode] = useState<TagMode>("any");
|
|
172
192
|
const [tagsInput, setTagsInput] = useState("");
|
|
193
|
+
const [queryModes, setQueryModes] = useState<QueryModeEntry[]>([]);
|
|
194
|
+
const [queryModeDraft, setQueryModeDraft] = useState<QueryModeType>("term");
|
|
195
|
+
const [queryModeText, setQueryModeText] = useState("");
|
|
196
|
+
const [queryModeError, setQueryModeError] = useState<string | null>(null);
|
|
173
197
|
|
|
174
198
|
const messagesEndRef = useRef<HTMLDivElement>(null);
|
|
175
199
|
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
|
@@ -218,6 +242,28 @@ export default function Ask({ navigate }: PageProps) {
|
|
|
218
242
|
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
|
|
219
243
|
}, [conversation]);
|
|
220
244
|
|
|
245
|
+
const handleAddQueryMode = useCallback(() => {
|
|
246
|
+
const text = queryModeText.trim();
|
|
247
|
+
if (!text) {
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
if (
|
|
251
|
+
queryModeDraft === "hyde" &&
|
|
252
|
+
queryModes.some((queryMode) => queryMode.mode === "hyde")
|
|
253
|
+
) {
|
|
254
|
+
setQueryModeError("Only one HyDE mode is allowed.");
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
setQueryModes((prev) => [...prev, { mode: queryModeDraft, text }]);
|
|
258
|
+
setQueryModeText("");
|
|
259
|
+
setQueryModeError(null);
|
|
260
|
+
}, [queryModeDraft, queryModeText, queryModes]);
|
|
261
|
+
|
|
262
|
+
const handleRemoveQueryMode = useCallback((index: number) => {
|
|
263
|
+
setQueryModes((prev) => prev.filter((_, i) => i !== index));
|
|
264
|
+
setQueryModeError(null);
|
|
265
|
+
}, []);
|
|
266
|
+
|
|
221
267
|
const handleSubmit = useCallback(
|
|
222
268
|
async (e: React.FormEvent) => {
|
|
223
269
|
e.preventDefault();
|
|
@@ -242,6 +288,15 @@ export default function Ask({ navigate }: PageProps) {
|
|
|
242
288
|
if (selectedCollection) {
|
|
243
289
|
requestBody.collection = selectedCollection;
|
|
244
290
|
}
|
|
291
|
+
if (intent.trim()) {
|
|
292
|
+
requestBody.intent = intent.trim();
|
|
293
|
+
}
|
|
294
|
+
if (candidateLimit.trim()) {
|
|
295
|
+
requestBody.candidateLimit = Number(candidateLimit);
|
|
296
|
+
}
|
|
297
|
+
if (exclude.trim()) {
|
|
298
|
+
requestBody.exclude = exclude.trim();
|
|
299
|
+
}
|
|
245
300
|
if (since) {
|
|
246
301
|
requestBody.since = since;
|
|
247
302
|
}
|
|
@@ -274,6 +329,9 @@ export default function Ask({ navigate }: PageProps) {
|
|
|
274
329
|
requestBody.noExpand = false;
|
|
275
330
|
requestBody.noRerank = false;
|
|
276
331
|
}
|
|
332
|
+
if (queryModes.length > 0) {
|
|
333
|
+
requestBody.queryModes = queryModes;
|
|
334
|
+
}
|
|
277
335
|
|
|
278
336
|
const { data, error } = await apiFetch<AskResponse>("/api/ask", {
|
|
279
337
|
method: "POST",
|
|
@@ -295,8 +353,12 @@ export default function Ask({ navigate }: PageProps) {
|
|
|
295
353
|
},
|
|
296
354
|
[
|
|
297
355
|
author,
|
|
356
|
+
candidateLimit,
|
|
298
357
|
category,
|
|
358
|
+
exclude,
|
|
359
|
+
intent,
|
|
299
360
|
query,
|
|
361
|
+
queryModes,
|
|
300
362
|
selectedCollection,
|
|
301
363
|
since,
|
|
302
364
|
tagMode,
|
|
@@ -315,18 +377,28 @@ export default function Ask({ navigate }: PageProps) {
|
|
|
315
377
|
|
|
316
378
|
const clearFilters = () => {
|
|
317
379
|
setSelectedCollection("");
|
|
380
|
+
setIntent("");
|
|
381
|
+
setCandidateLimit("");
|
|
382
|
+
setExclude("");
|
|
318
383
|
setSince("");
|
|
319
384
|
setUntil("");
|
|
320
385
|
setCategory("");
|
|
321
386
|
setAuthor("");
|
|
322
387
|
setTagsInput("");
|
|
323
388
|
setTagMode("any");
|
|
389
|
+
setQueryModes([]);
|
|
390
|
+
setQueryModeText("");
|
|
391
|
+
setQueryModeError(null);
|
|
324
392
|
};
|
|
325
393
|
|
|
326
394
|
const answerAvailable = capabilities?.answer ?? false;
|
|
327
395
|
|
|
328
396
|
const activeFilterPills = [
|
|
329
397
|
selectedCollection ? `collection:${selectedCollection}` : null,
|
|
398
|
+
intent.trim() ? `intent:${intent.trim()}` : null,
|
|
399
|
+
candidateLimit.trim() ? `candidates:${candidateLimit.trim()}` : null,
|
|
400
|
+
exclude.trim() ? `exclude:${exclude.trim()}` : null,
|
|
401
|
+
queryModes.length > 0 ? `${queryModes.length} query mode(s)` : null,
|
|
330
402
|
since ? `since:${since}` : null,
|
|
331
403
|
until ? `until:${until}` : null,
|
|
332
404
|
category.trim() ? `category:${category.trim()}` : null,
|
|
@@ -441,6 +513,28 @@ export default function Ask({ navigate }: PageProps) {
|
|
|
441
513
|
/>
|
|
442
514
|
</div>
|
|
443
515
|
|
|
516
|
+
<div className="md:col-span-2">
|
|
517
|
+
<p className="mb-1 text-muted-foreground text-xs">
|
|
518
|
+
Intent
|
|
519
|
+
</p>
|
|
520
|
+
<Input
|
|
521
|
+
onChange={(e) => setIntent(e.target.value)}
|
|
522
|
+
placeholder="Disambiguate ambiguous questions without searching on this text"
|
|
523
|
+
value={intent}
|
|
524
|
+
/>
|
|
525
|
+
</div>
|
|
526
|
+
|
|
527
|
+
<div className="md:col-span-2">
|
|
528
|
+
<p className="mb-1 text-muted-foreground text-xs">
|
|
529
|
+
Exclude
|
|
530
|
+
</p>
|
|
531
|
+
<Input
|
|
532
|
+
onChange={(e) => setExclude(e.target.value)}
|
|
533
|
+
placeholder="team reviews, hiring, onboarding"
|
|
534
|
+
value={exclude}
|
|
535
|
+
/>
|
|
536
|
+
</div>
|
|
537
|
+
|
|
444
538
|
<div>
|
|
445
539
|
<p className="mb-1 text-muted-foreground text-xs">
|
|
446
540
|
Category
|
|
@@ -475,6 +569,20 @@ export default function Ask({ navigate }: PageProps) {
|
|
|
475
569
|
</div>
|
|
476
570
|
</div>
|
|
477
571
|
|
|
572
|
+
<div>
|
|
573
|
+
<p className="mb-1 text-muted-foreground text-xs">
|
|
574
|
+
Candidate limit
|
|
575
|
+
</p>
|
|
576
|
+
<Input
|
|
577
|
+
inputMode="numeric"
|
|
578
|
+
min="1"
|
|
579
|
+
onChange={(e) => setCandidateLimit(e.target.value)}
|
|
580
|
+
placeholder="20"
|
|
581
|
+
type="number"
|
|
582
|
+
value={candidateLimit}
|
|
583
|
+
/>
|
|
584
|
+
</div>
|
|
585
|
+
|
|
478
586
|
<div className="md:col-span-2">
|
|
479
587
|
<p className="mb-1 text-muted-foreground text-xs">
|
|
480
588
|
Tags (comma separated)
|
|
@@ -517,6 +625,75 @@ export default function Ask({ navigate }: PageProps) {
|
|
|
517
625
|
Clear filters
|
|
518
626
|
</Button>
|
|
519
627
|
</div>
|
|
628
|
+
|
|
629
|
+
<div className="space-y-2">
|
|
630
|
+
<p className="text-muted-foreground text-xs">
|
|
631
|
+
Query modes (term, intent, hyde)
|
|
632
|
+
</p>
|
|
633
|
+
<div className="flex flex-wrap items-center gap-2">
|
|
634
|
+
<Select
|
|
635
|
+
onValueChange={(value) =>
|
|
636
|
+
setQueryModeDraft(value as QueryModeType)
|
|
637
|
+
}
|
|
638
|
+
value={queryModeDraft}
|
|
639
|
+
>
|
|
640
|
+
<SelectTrigger className="w-[120px]">
|
|
641
|
+
<SelectValue />
|
|
642
|
+
</SelectTrigger>
|
|
643
|
+
<SelectContent>
|
|
644
|
+
<SelectItem value="term">Term</SelectItem>
|
|
645
|
+
<SelectItem value="intent">Intent</SelectItem>
|
|
646
|
+
<SelectItem value="hyde">HyDE</SelectItem>
|
|
647
|
+
</SelectContent>
|
|
648
|
+
</Select>
|
|
649
|
+
<Input
|
|
650
|
+
className="min-w-[220px] flex-1"
|
|
651
|
+
onChange={(e) => setQueryModeText(e.target.value)}
|
|
652
|
+
onKeyDown={(e) => {
|
|
653
|
+
if (e.key === "Enter") {
|
|
654
|
+
e.preventDefault();
|
|
655
|
+
handleAddQueryMode();
|
|
656
|
+
}
|
|
657
|
+
}}
|
|
658
|
+
placeholder="Add query mode text"
|
|
659
|
+
value={queryModeText}
|
|
660
|
+
/>
|
|
661
|
+
<Button
|
|
662
|
+
onClick={handleAddQueryMode}
|
|
663
|
+
size="sm"
|
|
664
|
+
type="button"
|
|
665
|
+
variant="outline"
|
|
666
|
+
>
|
|
667
|
+
Add mode
|
|
668
|
+
</Button>
|
|
669
|
+
</div>
|
|
670
|
+
|
|
671
|
+
{queryModeError && (
|
|
672
|
+
<p className="text-destructive text-xs">
|
|
673
|
+
{queryModeError}
|
|
674
|
+
</p>
|
|
675
|
+
)}
|
|
676
|
+
|
|
677
|
+
{queryModes.length > 0 && (
|
|
678
|
+
<div className="flex flex-wrap gap-2">
|
|
679
|
+
{queryModes.map((queryMode, index) => (
|
|
680
|
+
<button
|
|
681
|
+
className={cn(
|
|
682
|
+
"group inline-flex items-center gap-1 rounded-full border border-primary/30 bg-primary/10",
|
|
683
|
+
"px-2.5 py-1 font-mono text-[11px] text-primary transition-all duration-150",
|
|
684
|
+
"hover:border-primary/50 hover:bg-primary/20"
|
|
685
|
+
)}
|
|
686
|
+
key={`${queryMode.mode}:${queryMode.text}:${index}`}
|
|
687
|
+
onClick={() => handleRemoveQueryMode(index)}
|
|
688
|
+
type="button"
|
|
689
|
+
>
|
|
690
|
+
<span>{`${QUERY_MODE_LABEL[queryMode.mode]}: ${queryMode.text}`}</span>
|
|
691
|
+
<XIcon className="size-3 opacity-60 transition-opacity group-hover:opacity-100" />
|
|
692
|
+
</button>
|
|
693
|
+
))}
|
|
694
|
+
</div>
|
|
695
|
+
)}
|
|
696
|
+
</div>
|
|
520
697
|
</CardContent>
|
|
521
698
|
</Card>
|
|
522
699
|
</CollapsibleContent>
|
|
@@ -644,6 +821,17 @@ export default function Ask({ navigate }: PageProps) {
|
|
|
644
821
|
expanded
|
|
645
822
|
</Badge>
|
|
646
823
|
)}
|
|
824
|
+
{entry.response.meta.queryModes &&
|
|
825
|
+
(entry.response.meta.queryModes.term > 0 ||
|
|
826
|
+
entry.response.meta.queryModes.intent > 0 ||
|
|
827
|
+
entry.response.meta.queryModes.hyde) && (
|
|
828
|
+
<Badge
|
|
829
|
+
className="font-mono text-[9px]"
|
|
830
|
+
variant="outline"
|
|
831
|
+
>
|
|
832
|
+
query modes
|
|
833
|
+
</Badge>
|
|
834
|
+
)}
|
|
647
835
|
</div>
|
|
648
836
|
|
|
649
837
|
{!entry.response.answer &&
|
|
@@ -153,6 +153,9 @@ export default function Search({ navigate }: PageProps) {
|
|
|
153
153
|
const [showAdvanced, setShowAdvanced] = useState(
|
|
154
154
|
Boolean(
|
|
155
155
|
initialFilters.collection ||
|
|
156
|
+
initialFilters.intent ||
|
|
157
|
+
initialFilters.candidateLimit ||
|
|
158
|
+
initialFilters.exclude ||
|
|
156
159
|
initialFilters.since ||
|
|
157
160
|
initialFilters.until ||
|
|
158
161
|
initialFilters.category ||
|
|
@@ -166,6 +169,11 @@ export default function Search({ navigate }: PageProps) {
|
|
|
166
169
|
const [selectedCollection, setSelectedCollection] = useState(
|
|
167
170
|
initialFilters.collection
|
|
168
171
|
);
|
|
172
|
+
const [intent, setIntent] = useState(initialFilters.intent);
|
|
173
|
+
const [candidateLimit, setCandidateLimit] = useState(
|
|
174
|
+
initialFilters.candidateLimit
|
|
175
|
+
);
|
|
176
|
+
const [exclude, setExclude] = useState(initialFilters.exclude);
|
|
169
177
|
const [since, setSince] = useState(initialFilters.since);
|
|
170
178
|
const [until, setUntil] = useState(initialFilters.until);
|
|
171
179
|
const [category, setCategory] = useState(initialFilters.category);
|
|
@@ -179,13 +187,18 @@ export default function Search({ navigate }: PageProps) {
|
|
|
179
187
|
const [showMobileTags, setShowMobileTags] = useState(false);
|
|
180
188
|
|
|
181
189
|
const hybridAvailable = capabilities?.hybrid ?? false;
|
|
182
|
-
const forceHybridForModes =
|
|
190
|
+
const forceHybridForModes =
|
|
191
|
+
thoroughness === "fast" &&
|
|
192
|
+
(queryModes.length > 0 || intent.trim().length > 0);
|
|
183
193
|
|
|
184
194
|
// Sync URL as filter state changes.
|
|
185
195
|
useEffect(() => {
|
|
186
196
|
const url = new URL(window.location.href);
|
|
187
197
|
applyFiltersToUrl(url, {
|
|
188
198
|
collection: selectedCollection,
|
|
199
|
+
intent,
|
|
200
|
+
candidateLimit,
|
|
201
|
+
exclude,
|
|
189
202
|
since,
|
|
190
203
|
until,
|
|
191
204
|
category,
|
|
@@ -198,7 +211,10 @@ export default function Search({ navigate }: PageProps) {
|
|
|
198
211
|
}, [
|
|
199
212
|
activeTags,
|
|
200
213
|
author,
|
|
214
|
+
candidateLimit,
|
|
201
215
|
category,
|
|
216
|
+
exclude,
|
|
217
|
+
intent,
|
|
202
218
|
queryModes,
|
|
203
219
|
selectedCollection,
|
|
204
220
|
since,
|
|
@@ -283,7 +299,10 @@ export default function Search({ navigate }: PageProps) {
|
|
|
283
299
|
setError(null);
|
|
284
300
|
setSearched(true);
|
|
285
301
|
|
|
286
|
-
const useBm25 =
|
|
302
|
+
const useBm25 =
|
|
303
|
+
thoroughness === "fast" &&
|
|
304
|
+
queryModes.length === 0 &&
|
|
305
|
+
intent.trim().length === 0;
|
|
287
306
|
const endpoint = useBm25 ? "/api/search" : "/api/query";
|
|
288
307
|
const body: Record<string, unknown> = {
|
|
289
308
|
query,
|
|
@@ -293,6 +312,15 @@ export default function Search({ navigate }: PageProps) {
|
|
|
293
312
|
if (selectedCollection) {
|
|
294
313
|
body.collection = selectedCollection;
|
|
295
314
|
}
|
|
315
|
+
if (intent.trim()) {
|
|
316
|
+
body.intent = intent.trim();
|
|
317
|
+
}
|
|
318
|
+
if (candidateLimit.trim()) {
|
|
319
|
+
body.candidateLimit = Number(candidateLimit);
|
|
320
|
+
}
|
|
321
|
+
if (exclude.trim()) {
|
|
322
|
+
body.exclude = exclude.trim();
|
|
323
|
+
}
|
|
296
324
|
if (since) {
|
|
297
325
|
body.since = since;
|
|
298
326
|
}
|
|
@@ -350,7 +378,10 @@ export default function Search({ navigate }: PageProps) {
|
|
|
350
378
|
[
|
|
351
379
|
activeTags,
|
|
352
380
|
author,
|
|
381
|
+
candidateLimit,
|
|
353
382
|
category,
|
|
383
|
+
exclude,
|
|
384
|
+
intent,
|
|
354
385
|
query,
|
|
355
386
|
queryModes,
|
|
356
387
|
selectedCollection,
|
|
@@ -370,7 +401,10 @@ export default function Search({ navigate }: PageProps) {
|
|
|
370
401
|
}, [
|
|
371
402
|
activeTags,
|
|
372
403
|
author,
|
|
404
|
+
candidateLimit,
|
|
373
405
|
category,
|
|
406
|
+
exclude,
|
|
407
|
+
intent,
|
|
374
408
|
queryModes,
|
|
375
409
|
selectedCollection,
|
|
376
410
|
since,
|
|
@@ -387,6 +421,9 @@ export default function Search({ navigate }: PageProps) {
|
|
|
387
421
|
|
|
388
422
|
const activeFilterPills = [
|
|
389
423
|
selectedCollection ? `collection:${selectedCollection}` : null,
|
|
424
|
+
intent.trim() ? `intent:${intent.trim()}` : null,
|
|
425
|
+
candidateLimit.trim() ? `candidates:${candidateLimit.trim()}` : null,
|
|
426
|
+
exclude.trim() ? `exclude:${exclude.trim()}` : null,
|
|
390
427
|
since ? `since:${since}` : null,
|
|
391
428
|
until ? `until:${until}` : null,
|
|
392
429
|
category.trim() ? `category:${category.trim()}` : null,
|
|
@@ -399,6 +436,9 @@ export default function Search({ navigate }: PageProps) {
|
|
|
399
436
|
|
|
400
437
|
const clearAdvancedFilters = () => {
|
|
401
438
|
setSelectedCollection("");
|
|
439
|
+
setIntent("");
|
|
440
|
+
setCandidateLimit("");
|
|
441
|
+
setExclude("");
|
|
402
442
|
setSince("");
|
|
403
443
|
setUntil("");
|
|
404
444
|
setCategory("");
|
|
@@ -551,6 +591,28 @@ export default function Search({ navigate }: PageProps) {
|
|
|
551
591
|
/>
|
|
552
592
|
</div>
|
|
553
593
|
|
|
594
|
+
<div className="md:col-span-2">
|
|
595
|
+
<p className="mb-1 text-muted-foreground text-xs">
|
|
596
|
+
Intent
|
|
597
|
+
</p>
|
|
598
|
+
<Input
|
|
599
|
+
onChange={(e) => setIntent(e.target.value)}
|
|
600
|
+
placeholder="Disambiguate ambiguous queries without searching on this text"
|
|
601
|
+
value={intent}
|
|
602
|
+
/>
|
|
603
|
+
</div>
|
|
604
|
+
|
|
605
|
+
<div className="md:col-span-2">
|
|
606
|
+
<p className="mb-1 text-muted-foreground text-xs">
|
|
607
|
+
Exclude
|
|
608
|
+
</p>
|
|
609
|
+
<Input
|
|
610
|
+
onChange={(e) => setExclude(e.target.value)}
|
|
611
|
+
placeholder="team reviews, hiring, onboarding"
|
|
612
|
+
value={exclude}
|
|
613
|
+
/>
|
|
614
|
+
</div>
|
|
615
|
+
|
|
554
616
|
<div>
|
|
555
617
|
<p className="mb-1 text-muted-foreground text-xs">
|
|
556
618
|
Category
|
|
@@ -584,6 +646,20 @@ export default function Search({ navigate }: PageProps) {
|
|
|
584
646
|
/>
|
|
585
647
|
</div>
|
|
586
648
|
</div>
|
|
649
|
+
|
|
650
|
+
<div>
|
|
651
|
+
<p className="mb-1 text-muted-foreground text-xs">
|
|
652
|
+
Candidate limit
|
|
653
|
+
</p>
|
|
654
|
+
<Input
|
|
655
|
+
inputMode="numeric"
|
|
656
|
+
min="1"
|
|
657
|
+
onChange={(e) => setCandidateLimit(e.target.value)}
|
|
658
|
+
placeholder="20"
|
|
659
|
+
type="number"
|
|
660
|
+
value={candidateLimit}
|
|
661
|
+
/>
|
|
662
|
+
</div>
|
|
587
663
|
</div>
|
|
588
664
|
|
|
589
665
|
<div className="flex flex-wrap items-center gap-2">
|