返回 presentation-ai
Searched.tsx
根目录 / src / components / ai / generative-ui / Searched.tsx
1 import { ChevronsUpDownIcon, Loader2, SearchIcon } from "lucide-react";
2 import Image from "next/image";
3
4 import { Button } from "@/components/ui/button";
5 import {
6 Collapsible,
7 CollapsibleContent,
8 CollapsibleTrigger,
9 } from "@/components/ui/collapsible";
10
11 export interface SearchResult {
12 url: string;
13 title: string;
14 published_date: string;
15 content: string;
16 }
17
18 export function Searching({ query }: { query: string }) {
19 return (
20 <div className="mb-2 w-full rounded-lg border border-primary/20 bg-background">
21 <div className="flex h-12 items-center gap-3 px-4 text-muted-foreground">
22 <Loader2 className="size-5 animate-spin" />
23 <div className="min-w-0 flex-1">
24 <p className="truncate text-sm font-medium">
25 Searching the web for &quot;{query}&quot;
26 </p>
27 </div>
28 </div>
29 </div>
30 );
31 }
32
33 export function Searched({
34 results,
35 query,
36 }: {
37 results: SearchResult[];
38 query: string;
39 }) {
40 return (
41 <Collapsible className="mb-2 w-full rounded-lg border border-primary/20 bg-background">
42 <CollapsibleTrigger asChild>
43 <Button variant="ghost" className="h-12 w-full justify-between px-4">
44 <div className="flex w-[90%] min-w-0 items-center gap-3">
45 <SearchIcon className="size-5 shrink-0" />
46 <div className="flex min-w-0 flex-col items-start overflow-hidden">
47 <span className="w-full truncate text-sm font-medium">
48 {query}
49 </span>
50 <span className="text-xs text-muted-foreground">
51 {results.length} result{results.length === 1 ? "" : "s"} found
52 </span>
53 </div>
54 </div>
55 <ChevronsUpDownIcon className="size-5 shrink-0 transition-transform duration-300 data-[state=open]:rotate-180" />
56 </Button>
57 </CollapsibleTrigger>
58 <CollapsibleContent className="grid max-w-full grid-cols-1 gap-y-2 p-4">
59 {results.map((result) => {
60 let domain = "allweone.com";
61 try {
62 domain = new URL(result.url || "https://allweone.com").hostname;
63 } catch {
64 domain = "allweone.com";
65 }
66 const faviconUrl = `https://www.google.com/s2/favicons?domain=${domain}&sz=32`;
67
68 return (
69 <div
70 key={result.url || `${query}-${result.title}`}
71 className="flex max-w-full items-start gap-3 rounded-lg border border-primary/20 p-3"
72 >
73 <Image
74 unoptimized
75 width={32}
76 height={32}
77 src={faviconUrl}
78 alt={domain}
79 className="mt-1 size-4"
80 />
81 <div className="min-w-0 flex-1 overflow-hidden">
82 <h4 className="truncate text-sm font-medium">
83 {result.title}
84 </h4>
85 <p className="line-clamp-2 text-xs text-muted-foreground">
86 {result.content}
87 </p>
88 {result.url ? (
89 <a
90 href={result.url}
91 target="_blank"
92 rel="noopener noreferrer"
93 className="block truncate text-xs text-primary hover:underline"
94 >
95 {result.url}
96 </a>
97 ) : null}
98 </div>
99 </div>
100 );
101 })}
102 </CollapsibleContent>
103 </Collapsible>
104 );
105 }
106
106 lines Plain Text