@gmickel/gno 0.7.0 → 0.8.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/LICENSE +21 -0
- package/README.md +90 -50
- package/THIRD_PARTY_NOTICES.md +22 -0
- package/assets/screenshots/webui-ask-answer.png +0 -0
- package/assets/screenshots/webui-collections.png +0 -0
- package/assets/screenshots/webui-editor.png +0 -0
- package/assets/screenshots/webui-home.png +0 -0
- package/assets/skill/SKILL.md +12 -12
- package/assets/skill/cli-reference.md +59 -57
- package/assets/skill/examples.md +8 -7
- package/assets/skill/mcp-reference.md +8 -4
- package/package.json +32 -25
- package/src/app/constants.ts +43 -42
- package/src/cli/colors.ts +1 -1
- package/src/cli/commands/ask.ts +44 -43
- package/src/cli/commands/cleanup.ts +9 -8
- package/src/cli/commands/collection/add.ts +12 -12
- package/src/cli/commands/collection/index.ts +4 -4
- package/src/cli/commands/collection/list.ts +26 -25
- package/src/cli/commands/collection/remove.ts +10 -10
- package/src/cli/commands/collection/rename.ts +10 -10
- package/src/cli/commands/context/add.ts +1 -1
- package/src/cli/commands/context/check.ts +17 -17
- package/src/cli/commands/context/index.ts +4 -4
- package/src/cli/commands/context/list.ts +11 -11
- package/src/cli/commands/context/rm.ts +1 -1
- package/src/cli/commands/doctor.ts +86 -84
- package/src/cli/commands/embed.ts +30 -28
- package/src/cli/commands/get.ts +27 -26
- package/src/cli/commands/index-cmd.ts +9 -9
- package/src/cli/commands/index.ts +16 -16
- package/src/cli/commands/init.ts +13 -12
- package/src/cli/commands/ls.ts +20 -19
- package/src/cli/commands/mcp/config.ts +30 -28
- package/src/cli/commands/mcp/index.ts +4 -4
- package/src/cli/commands/mcp/install.ts +17 -17
- package/src/cli/commands/mcp/paths.ts +133 -133
- package/src/cli/commands/mcp/status.ts +21 -21
- package/src/cli/commands/mcp/uninstall.ts +13 -13
- package/src/cli/commands/mcp.ts +2 -2
- package/src/cli/commands/models/clear.ts +12 -11
- package/src/cli/commands/models/index.ts +5 -5
- package/src/cli/commands/models/list.ts +31 -30
- package/src/cli/commands/models/path.ts +1 -1
- package/src/cli/commands/models/pull.ts +19 -18
- package/src/cli/commands/models/use.ts +4 -4
- package/src/cli/commands/multi-get.ts +38 -36
- package/src/cli/commands/query.ts +21 -20
- package/src/cli/commands/ref-parser.ts +10 -10
- package/src/cli/commands/reset.ts +40 -39
- package/src/cli/commands/search.ts +14 -13
- package/src/cli/commands/serve.ts +4 -4
- package/src/cli/commands/shared.ts +11 -10
- package/src/cli/commands/skill/index.ts +5 -5
- package/src/cli/commands/skill/install.ts +18 -17
- package/src/cli/commands/skill/paths-cmd.ts +11 -10
- package/src/cli/commands/skill/paths.ts +23 -23
- package/src/cli/commands/skill/show.ts +13 -12
- package/src/cli/commands/skill/uninstall.ts +16 -15
- package/src/cli/commands/status.ts +25 -24
- package/src/cli/commands/update.ts +3 -3
- package/src/cli/commands/vsearch.ts +17 -16
- package/src/cli/context.ts +5 -5
- package/src/cli/errors.ts +3 -3
- package/src/cli/format/search-results.ts +37 -37
- package/src/cli/options.ts +43 -43
- package/src/cli/program.ts +455 -459
- package/src/cli/progress.ts +1 -1
- package/src/cli/run.ts +24 -23
- package/src/collection/add.ts +9 -8
- package/src/collection/index.ts +3 -3
- package/src/collection/remove.ts +7 -6
- package/src/collection/types.ts +6 -6
- package/src/config/defaults.ts +1 -1
- package/src/config/index.ts +5 -5
- package/src/config/loader.ts +19 -18
- package/src/config/paths.ts +9 -8
- package/src/config/saver.ts +14 -13
- package/src/config/types.ts +53 -52
- package/src/converters/adapters/markitdownTs/adapter.ts +21 -19
- package/src/converters/adapters/officeparser/adapter.ts +18 -16
- package/src/converters/canonicalize.ts +12 -12
- package/src/converters/errors.ts +26 -22
- package/src/converters/index.ts +8 -8
- package/src/converters/mime.ts +25 -25
- package/src/converters/native/markdown.ts +10 -9
- package/src/converters/native/plaintext.ts +8 -7
- package/src/converters/path.ts +2 -2
- package/src/converters/pipeline.ts +11 -10
- package/src/converters/registry.ts +8 -8
- package/src/converters/types.ts +14 -14
- package/src/converters/versions.ts +4 -4
- package/src/index.ts +4 -4
- package/src/ingestion/chunker.ts +10 -9
- package/src/ingestion/index.ts +6 -6
- package/src/ingestion/language.ts +62 -62
- package/src/ingestion/sync.ts +50 -49
- package/src/ingestion/types.ts +10 -10
- package/src/ingestion/walker.ts +14 -13
- package/src/llm/cache.ts +51 -49
- package/src/llm/errors.ts +40 -36
- package/src/llm/index.ts +9 -9
- package/src/llm/lockfile.ts +6 -6
- package/src/llm/nodeLlamaCpp/adapter.ts +13 -12
- package/src/llm/nodeLlamaCpp/embedding.ts +9 -8
- package/src/llm/nodeLlamaCpp/generation.ts +7 -6
- package/src/llm/nodeLlamaCpp/lifecycle.ts +11 -10
- package/src/llm/nodeLlamaCpp/rerank.ts +6 -5
- package/src/llm/policy.ts +5 -5
- package/src/llm/registry.ts +6 -5
- package/src/llm/types.ts +2 -2
- package/src/mcp/resources/index.ts +15 -13
- package/src/mcp/server.ts +25 -23
- package/src/mcp/tools/get.ts +25 -23
- package/src/mcp/tools/index.ts +32 -29
- package/src/mcp/tools/multi-get.ts +34 -32
- package/src/mcp/tools/query.ts +29 -27
- package/src/mcp/tools/search.ts +14 -12
- package/src/mcp/tools/status.ts +12 -11
- package/src/mcp/tools/vsearch.ts +26 -24
- package/src/pipeline/answer.ts +9 -9
- package/src/pipeline/chunk-lookup.ts +1 -1
- package/src/pipeline/contextual.ts +4 -4
- package/src/pipeline/expansion.ts +23 -21
- package/src/pipeline/explain.ts +21 -21
- package/src/pipeline/fusion.ts +9 -9
- package/src/pipeline/hybrid.ts +41 -42
- package/src/pipeline/index.ts +10 -10
- package/src/pipeline/query-language.ts +39 -39
- package/src/pipeline/rerank.ts +8 -7
- package/src/pipeline/search.ts +22 -22
- package/src/pipeline/types.ts +8 -8
- package/src/pipeline/vsearch.ts +21 -24
- package/src/serve/CLAUDE.md +21 -15
- package/src/serve/config-sync.ts +9 -8
- package/src/serve/context.ts +19 -18
- package/src/serve/index.ts +1 -1
- package/src/serve/jobs.ts +7 -7
- package/src/serve/public/app.tsx +79 -25
- package/src/serve/public/components/AddCollectionDialog.tsx +382 -0
- package/src/serve/public/components/CaptureButton.tsx +60 -0
- package/src/serve/public/components/CaptureModal.tsx +365 -0
- package/src/serve/public/components/IndexingProgress.tsx +333 -0
- package/src/serve/public/components/ShortcutHelpModal.tsx +106 -0
- package/src/serve/public/components/ai-elements/code-block.tsx +42 -32
- package/src/serve/public/components/ai-elements/conversation.tsx +16 -14
- package/src/serve/public/components/ai-elements/inline-citation.tsx +33 -32
- package/src/serve/public/components/ai-elements/loader.tsx +5 -4
- package/src/serve/public/components/ai-elements/message.tsx +39 -37
- package/src/serve/public/components/ai-elements/prompt-input.tsx +97 -95
- package/src/serve/public/components/ai-elements/sources.tsx +12 -10
- package/src/serve/public/components/ai-elements/suggestion.tsx +10 -9
- package/src/serve/public/components/editor/CodeMirrorEditor.tsx +142 -0
- package/src/serve/public/components/editor/MarkdownPreview.tsx +311 -0
- package/src/serve/public/components/editor/index.ts +6 -0
- package/src/serve/public/components/preset-selector.tsx +29 -28
- package/src/serve/public/components/ui/badge.tsx +13 -12
- package/src/serve/public/components/ui/button-group.tsx +13 -12
- package/src/serve/public/components/ui/button.tsx +23 -22
- package/src/serve/public/components/ui/card.tsx +16 -16
- package/src/serve/public/components/ui/carousel.tsx +36 -35
- package/src/serve/public/components/ui/collapsible.tsx +1 -1
- package/src/serve/public/components/ui/command.tsx +17 -15
- package/src/serve/public/components/ui/dialog.tsx +13 -12
- package/src/serve/public/components/ui/dropdown-menu.tsx +13 -12
- package/src/serve/public/components/ui/hover-card.tsx +6 -5
- package/src/serve/public/components/ui/input-group.tsx +45 -43
- package/src/serve/public/components/ui/input.tsx +6 -6
- package/src/serve/public/components/ui/progress.tsx +5 -4
- package/src/serve/public/components/ui/scroll-area.tsx +11 -10
- package/src/serve/public/components/ui/select.tsx +19 -18
- package/src/serve/public/components/ui/separator.tsx +6 -5
- package/src/serve/public/components/ui/table.tsx +18 -18
- package/src/serve/public/components/ui/textarea.tsx +4 -4
- package/src/serve/public/components/ui/tooltip.tsx +5 -4
- package/src/serve/public/globals.css +27 -4
- package/src/serve/public/hooks/use-api.ts +8 -8
- package/src/serve/public/hooks/useCaptureModal.tsx +83 -0
- package/src/serve/public/hooks/useKeyboardShortcuts.ts +85 -0
- package/src/serve/public/index.html +4 -4
- package/src/serve/public/lib/utils.ts +6 -0
- package/src/serve/public/pages/Ask.tsx +27 -26
- package/src/serve/public/pages/Browse.tsx +28 -27
- package/src/serve/public/pages/Collections.tsx +439 -0
- package/src/serve/public/pages/Dashboard.tsx +166 -40
- package/src/serve/public/pages/DocView.tsx +258 -73
- package/src/serve/public/pages/DocumentEditor.tsx +510 -0
- package/src/serve/public/pages/Search.tsx +80 -58
- package/src/serve/routes/api.ts +272 -155
- package/src/serve/security.ts +4 -4
- package/src/serve/server.ts +66 -48
- package/src/store/index.ts +5 -5
- package/src/store/migrations/001-initial.ts +24 -23
- package/src/store/migrations/002-documents-fts.ts +7 -6
- package/src/store/migrations/index.ts +4 -4
- package/src/store/migrations/runner.ts +17 -15
- package/src/store/sqlite/adapter.ts +123 -121
- package/src/store/sqlite/fts5-snowball.ts +24 -23
- package/src/store/sqlite/index.ts +1 -1
- package/src/store/sqlite/setup.ts +12 -12
- package/src/store/sqlite/types.ts +4 -4
- package/src/store/types.ts +19 -19
- package/src/store/vector/index.ts +3 -3
- package/src/store/vector/sqlite-vec.ts +23 -20
- package/src/store/vector/stats.ts +10 -8
- package/src/store/vector/types.ts +2 -2
- package/vendor/fts5-snowball/README.md +6 -6
- package/assets/screenshots/webui-ask-answer.jpg +0 -0
- package/assets/screenshots/webui-home.jpg +0 -0
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import { ArrowLeft, FileText, Search as SearchIcon, Zap } from
|
|
2
|
-
import { useEffect, useState } from
|
|
3
|
-
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
1
|
+
import { ArrowLeft, FileText, Search as SearchIcon, Zap } from "lucide-react";
|
|
2
|
+
import { useEffect, useState } from "react";
|
|
3
|
+
|
|
4
|
+
import { Loader } from "../components/ai-elements/loader";
|
|
5
|
+
import { Badge } from "../components/ui/badge";
|
|
6
|
+
import { Button } from "../components/ui/button";
|
|
7
|
+
import { ButtonGroup } from "../components/ui/button-group";
|
|
8
|
+
import { Card, CardContent } from "../components/ui/card";
|
|
9
|
+
import { Input } from "../components/ui/input";
|
|
10
|
+
import { apiFetch } from "../hooks/use-api";
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* Render snippet with <mark> tags as highlighted spans.
|
|
@@ -18,7 +19,7 @@ function renderSnippet(snippet: string): React.ReactNode {
|
|
|
18
19
|
let key = 0;
|
|
19
20
|
|
|
20
21
|
while (remaining.length > 0) {
|
|
21
|
-
const markStart = remaining.indexOf(
|
|
22
|
+
const markStart = remaining.indexOf("<mark>");
|
|
22
23
|
if (markStart === -1) {
|
|
23
24
|
parts.push(remaining);
|
|
24
25
|
break;
|
|
@@ -28,7 +29,7 @@ function renderSnippet(snippet: string): React.ReactNode {
|
|
|
28
29
|
parts.push(remaining.slice(0, markStart));
|
|
29
30
|
}
|
|
30
31
|
|
|
31
|
-
const markEnd = remaining.indexOf(
|
|
32
|
+
const markEnd = remaining.indexOf("</mark>", markStart);
|
|
32
33
|
if (markEnd === -1) {
|
|
33
34
|
parts.push(remaining.slice(markStart));
|
|
34
35
|
break;
|
|
@@ -84,13 +85,13 @@ interface Capabilities {
|
|
|
84
85
|
answer: boolean;
|
|
85
86
|
}
|
|
86
87
|
|
|
87
|
-
type SearchMode =
|
|
88
|
+
type SearchMode = "bm25" | "hybrid";
|
|
88
89
|
|
|
89
90
|
export default function Search({ navigate }: PageProps) {
|
|
90
|
-
const [query, setQuery] = useState(
|
|
91
|
-
const [mode, setMode] = useState<SearchMode>(
|
|
91
|
+
const [query, setQuery] = useState("");
|
|
92
|
+
const [mode, setMode] = useState<SearchMode>("bm25");
|
|
92
93
|
const [results, setResults] = useState<SearchResult[]>([]);
|
|
93
|
-
const [meta, setMeta] = useState<SearchResponse[
|
|
94
|
+
const [meta, setMeta] = useState<SearchResponse["meta"] | null>(null);
|
|
94
95
|
const [loading, setLoading] = useState(false);
|
|
95
96
|
const [error, setError] = useState<string | null>(null);
|
|
96
97
|
const [searched, setSearched] = useState(false);
|
|
@@ -99,16 +100,16 @@ export default function Search({ navigate }: PageProps) {
|
|
|
99
100
|
// Fetch capabilities on mount
|
|
100
101
|
useEffect(() => {
|
|
101
102
|
async function fetchCapabilities() {
|
|
102
|
-
const { data } = await apiFetch<Capabilities>(
|
|
103
|
+
const { data } = await apiFetch<Capabilities>("/api/capabilities");
|
|
103
104
|
if (data) {
|
|
104
105
|
setCapabilities(data);
|
|
105
106
|
// Auto-select hybrid if available
|
|
106
107
|
if (data.hybrid) {
|
|
107
|
-
setMode(
|
|
108
|
+
setMode("hybrid");
|
|
108
109
|
}
|
|
109
110
|
}
|
|
110
111
|
}
|
|
111
|
-
fetchCapabilities();
|
|
112
|
+
void fetchCapabilities();
|
|
112
113
|
}, []);
|
|
113
114
|
|
|
114
115
|
const handleSearch = async (e: React.FormEvent) => {
|
|
@@ -122,10 +123,10 @@ export default function Search({ navigate }: PageProps) {
|
|
|
122
123
|
setSearched(true);
|
|
123
124
|
|
|
124
125
|
// Use /api/query for hybrid, /api/search for bm25
|
|
125
|
-
const endpoint = mode ===
|
|
126
|
+
const endpoint = mode === "hybrid" ? "/api/query" : "/api/search";
|
|
126
127
|
|
|
127
128
|
const { data, error } = await apiFetch<SearchResponse>(endpoint, {
|
|
128
|
-
method:
|
|
129
|
+
method: "POST",
|
|
129
130
|
body: JSON.stringify({ query, limit: 20 }),
|
|
130
131
|
});
|
|
131
132
|
|
|
@@ -163,23 +164,27 @@ export default function Search({ navigate }: PageProps) {
|
|
|
163
164
|
<main className="mx-auto max-w-4xl p-8">
|
|
164
165
|
{/* Search Form */}
|
|
165
166
|
<form className="mb-8" onSubmit={handleSearch}>
|
|
166
|
-
<div className="relative">
|
|
167
|
-
|
|
168
|
-
<
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
167
|
+
<div className="group relative">
|
|
168
|
+
{/* Gradient border effect on focus */}
|
|
169
|
+
<div className="pointer-events-none absolute -inset-[1px] rounded-lg bg-gradient-to-r from-primary/50 via-primary to-primary/50 opacity-0 blur-sm transition-opacity duration-300 group-focus-within:opacity-100" />
|
|
170
|
+
<div className="relative">
|
|
171
|
+
<SearchIcon className="absolute top-1/2 left-4 size-5 -translate-y-1/2 text-muted-foreground transition-colors duration-200 group-focus-within:text-primary" />
|
|
172
|
+
<Input
|
|
173
|
+
className="border-border/50 bg-card py-6 pr-4 pl-12 text-lg transition-all duration-200 focus:border-primary focus:bg-card/80 focus:shadow-[0_0_20px_-5px_hsl(var(--primary)/0.3)]"
|
|
174
|
+
onChange={(e) => setQuery(e.target.value)}
|
|
175
|
+
placeholder="Search your documents..."
|
|
176
|
+
type="text"
|
|
177
|
+
value={query}
|
|
178
|
+
/>
|
|
179
|
+
<Button
|
|
180
|
+
className="absolute top-1/2 right-2 -translate-y-1/2"
|
|
181
|
+
disabled={loading || !query.trim()}
|
|
182
|
+
size="sm"
|
|
183
|
+
type="submit"
|
|
184
|
+
>
|
|
185
|
+
{loading ? <Loader size={16} /> : "Search"}
|
|
186
|
+
</Button>
|
|
187
|
+
</div>
|
|
183
188
|
</div>
|
|
184
189
|
|
|
185
190
|
{/* Mode selector */}
|
|
@@ -188,42 +193,42 @@ export default function Search({ navigate }: PageProps) {
|
|
|
188
193
|
<ButtonGroup>
|
|
189
194
|
<Button
|
|
190
195
|
className={
|
|
191
|
-
mode ===
|
|
192
|
-
?
|
|
193
|
-
:
|
|
196
|
+
mode === "bm25"
|
|
197
|
+
? "bg-primary text-primary-foreground hover:bg-primary/90"
|
|
198
|
+
: ""
|
|
194
199
|
}
|
|
195
|
-
onClick={() => setMode(
|
|
200
|
+
onClick={() => setMode("bm25")}
|
|
196
201
|
size="sm"
|
|
197
202
|
type="button"
|
|
198
|
-
variant={mode ===
|
|
203
|
+
variant={mode === "bm25" ? "default" : "outline"}
|
|
199
204
|
>
|
|
200
205
|
BM25
|
|
201
206
|
</Button>
|
|
202
207
|
<Button
|
|
203
208
|
className={
|
|
204
|
-
mode ===
|
|
205
|
-
?
|
|
206
|
-
:
|
|
209
|
+
mode === "hybrid"
|
|
210
|
+
? "bg-primary text-primary-foreground hover:bg-primary/90"
|
|
211
|
+
: ""
|
|
207
212
|
}
|
|
208
213
|
disabled={!hybridAvailable}
|
|
209
|
-
onClick={() => setMode(
|
|
214
|
+
onClick={() => setMode("hybrid")}
|
|
210
215
|
size="sm"
|
|
211
216
|
title={
|
|
212
217
|
hybridAvailable
|
|
213
|
-
?
|
|
214
|
-
:
|
|
218
|
+
? "Hybrid search with vector + reranking"
|
|
219
|
+
: "Hybrid search not available (no embedding model)"
|
|
215
220
|
}
|
|
216
221
|
type="button"
|
|
217
|
-
variant={mode ===
|
|
222
|
+
variant={mode === "hybrid" ? "default" : "outline"}
|
|
218
223
|
>
|
|
219
224
|
<Zap className="mr-1 size-3" />
|
|
220
225
|
Hybrid
|
|
221
226
|
</Button>
|
|
222
227
|
</ButtonGroup>
|
|
223
228
|
<span className="text-muted-foreground/70 text-xs">
|
|
224
|
-
{mode ===
|
|
225
|
-
?
|
|
226
|
-
:
|
|
229
|
+
{mode === "bm25"
|
|
230
|
+
? "Keyword-based full-text search"
|
|
231
|
+
: "BM25 + vector + query expansion + reranking"}
|
|
227
232
|
</span>
|
|
228
233
|
</div>
|
|
229
234
|
</form>
|
|
@@ -246,11 +251,28 @@ export default function Search({ navigate }: PageProps) {
|
|
|
246
251
|
{/* Empty state */}
|
|
247
252
|
{!loading && searched && results.length === 0 && !error && (
|
|
248
253
|
<div className="py-20 text-center">
|
|
249
|
-
<
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
254
|
+
<div className="relative mx-auto mb-6 size-20">
|
|
255
|
+
<div className="absolute inset-0 animate-pulse rounded-full bg-primary/10" />
|
|
256
|
+
<div className="absolute inset-2 rounded-full bg-card" />
|
|
257
|
+
<FileText className="absolute inset-0 m-auto size-8 text-muted-foreground" />
|
|
258
|
+
</div>
|
|
259
|
+
<h3 className="mb-2 font-semibold text-xl">No matches found</h3>
|
|
260
|
+
<p className="mx-auto mb-6 max-w-sm text-muted-foreground">
|
|
261
|
+
We couldn't find any documents matching "{query}". Try different
|
|
262
|
+
keywords or check your spelling.
|
|
253
263
|
</p>
|
|
264
|
+
<div className="flex justify-center gap-3">
|
|
265
|
+
<Button onClick={() => setQuery("")} size="sm" variant="outline">
|
|
266
|
+
Clear search
|
|
267
|
+
</Button>
|
|
268
|
+
<Button
|
|
269
|
+
onClick={() => setMode(mode === "bm25" ? "hybrid" : "bm25")}
|
|
270
|
+
size="sm"
|
|
271
|
+
variant="ghost"
|
|
272
|
+
>
|
|
273
|
+
Try {mode === "bm25" ? "hybrid" : "keyword"} mode
|
|
274
|
+
</Button>
|
|
275
|
+
</div>
|
|
254
276
|
</div>
|
|
255
277
|
)}
|
|
256
278
|
|
|
@@ -259,7 +281,7 @@ export default function Search({ navigate }: PageProps) {
|
|
|
259
281
|
<div className="space-y-4">
|
|
260
282
|
<div className="mb-6 flex items-center justify-between">
|
|
261
283
|
<p className="text-muted-foreground text-sm">
|
|
262
|
-
{results.length} result{results.length !== 1 ?
|
|
284
|
+
{results.length} result{results.length !== 1 ? "s" : ""}
|
|
263
285
|
</p>
|
|
264
286
|
{meta && (
|
|
265
287
|
<div className="flex items-center gap-2">
|
|
@@ -302,7 +324,7 @@ export default function Search({ navigate }: PageProps) {
|
|
|
302
324
|
<CardContent className="py-4">
|
|
303
325
|
<div className="mb-2 flex items-start justify-between gap-4">
|
|
304
326
|
<h3 className="font-medium text-primary underline-offset-2 group-hover:underline">
|
|
305
|
-
{r.title || r.uri.split(
|
|
327
|
+
{r.title || r.uri.split("/").pop()}
|
|
306
328
|
</h3>
|
|
307
329
|
<Badge
|
|
308
330
|
className="shrink-0 font-mono text-xs"
|