├── .env.example
├── .gitignore
├── LICENSE
├── README.md
├── app
├── [page]
│ └── page.tsx
├── api
│ ├── og-image
│ │ └── route.ts
│ ├── query
│ │ └── route.ts
│ ├── rsearch
│ │ └── route.ts
│ └── search
│ │ └── route.ts
├── globals.css
├── layout.tsx
├── page.tsx
├── rsearch
│ └── page.tsx
└── settings
│ └── page.tsx
├── components.json
├── components
├── rSearch
│ ├── query.tsx
│ ├── results.tsx
│ ├── sources-sidebar.tsx
│ ├── sources.tsx
│ ├── sources
│ │ ├── sources-academic.tsx
│ │ ├── sources-images.tsx
│ │ ├── sources-knowledge.tsx
│ │ ├── sources-news.tsx
│ │ ├── sources-patents.tsx
│ │ ├── sources-places.tsx
│ │ ├── sources-scholar.tsx
│ │ ├── sources-search.tsx
│ │ ├── sources-shopping.tsx
│ │ ├── sources-videos.tsx
│ │ └── sources-web.tsx
│ └── thinking.tsx
└── ui
│ ├── button.tsx
│ ├── card.tsx
│ ├── carousel.tsx
│ ├── dialog.tsx
│ ├── drawer.tsx
│ ├── dropdown-menu.tsx
│ ├── input.tsx
│ ├── label.tsx
│ ├── logo.tsx
│ ├── meteors.tsx
│ ├── mobile-header.tsx
│ ├── radio-group.tsx
│ ├── select.tsx
│ ├── separator.tsx
│ ├── sheet.tsx
│ ├── sidebar.tsx
│ ├── skeleton.tsx
│ ├── switch.tsx
│ ├── tabs.tsx
│ ├── textarea.tsx
│ ├── toggle.tsx
│ └── tooltip.tsx
├── data
├── serper_samples
│ ├── apple inc-us-en-true-1-10-images--.json
│ ├── apple inc-us-en-true-1-10-maps-----.json
│ ├── apple inc-us-en-true-1-10-news---------.json
│ ├── apple inc-us-en-true-1-10-places--.json
│ ├── apple inc-us-en-true-1-10-search--.json
│ ├── apple inc-us-en-true-1-10-videos--.json
│ ├── apple inc-us-en-true-1-20-patents---------.json
│ ├── apple inc-us-en-true-1-20-scholar---------.json
│ └── apple inc-us-en-true-1-20-shopping---------.json
└── static.json
├── demo.png
├── eslint.config.mjs
├── hooks
├── use-media-query.ts
└── use-mobile.tsx
├── lib
├── imageLoader.ts
├── prompts.ts
└── utils.ts
├── next.config.js
├── next.config.ts
├── package-lock.json
├── package.json
├── postcss.config.mjs
├── public
├── apple-touch-icon-114x114.png
├── apple-touch-icon-120x120.png
├── apple-touch-icon-144x144.png
├── apple-touch-icon-152x152.png
├── apple-touch-icon-180x180.png
├── apple-touch-icon-57x57.png
├── apple-touch-icon-72x72.png
├── apple-touch-icon-76x76.png
├── apple-touch-icon.png
├── favicon.ico
├── file.svg
├── globe.svg
├── logo.png
├── next.svg
├── og.png
├── og.svg
├── robots.txt
├── sitemap.xml
├── vercel.svg
└── window.svg
├── tailwind.config.ts
├── tsconfig.json
└── types
└── search.ts
/.env.example:
--------------------------------------------------------------------------------
1 | # Serper API Key (Google Search API) - https://serper.dev/api-key
2 | SERPER_API_KEY=
3 |
4 | # You can either use OpenAI, OpenRouter or DeepSeek as your AI provider.
5 | # OpenAI API Key (AI Provider) - https://platform.openai.com/api-keys
6 | # OpenRouter API Key (AI Provider) - https://openrouter.ai/settings/keys
7 | # DeepSeek API Key (AI Provider) - https://platform.deepseek.com/api_keys
8 | NEXT_PUBLIC_AI_PROVIDER_API_KEY=
9 |
10 | # AI Provider Base URL (AI Provider)
11 | # DeepSeek Base URL (AI Provider) - https://api.deepseek.com
12 | # OpenAI Base URL (AI Provider) - https://api.openai.com
13 | # OpenRouter Base URL (AI Provider) - https://openrouter.ai/api/v1
14 | NEXT_PUBLIC_AI_PROVIDER_BASE_URL=https://api.deepseek.com
15 |
16 | # AI Query Refiner Model (AI Provider)
17 | # DeepSeek Refiner Model (AI Provider) - deepseek-chat
18 | # OpenAI Refiner Model (AI Provider) - gpt-4o-mini
19 | # OpenRouter Refiner Model (AI Provider) - openai/gpt-4o-mini
20 | NEXT_PUBLIC_AI_REFINER_MODEL=gpt-4o-mini
21 |
22 | # AI Reasoning Model (AI Provider)
23 | # DeepSeek Reasoning Model (AI Provider) - deepseek-reasoner
24 | # OpenAI Reasoning Model (AI Provider) - gpt-4o
25 | # OpenRouter Reasoning Model (AI Provider) - openai/gpt-4o
26 | NEXT_PUBLIC_AI_REASONING_MODEL=gpt-4o
27 |
28 | # Landing Page Copy Text
29 | NEXT_PUBLIC_LANDING_PAGE_COPY_TEXT="AI-powered search with advanced reasoning capabilities"
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.*
7 | .yarn/*
8 | !.yarn/patches
9 | !.yarn/plugins
10 | !.yarn/releases
11 | !.yarn/versions
12 |
13 | # testing
14 | /coverage
15 |
16 | # next.js
17 | /.next/
18 | /out/
19 |
20 | # production
21 | /build
22 |
23 | # misc
24 | .DS_Store
25 | *.pem
26 |
27 | # debug
28 | npm-debug.log*
29 | yarn-debug.log*
30 | yarn-error.log*
31 | .pnpm-debug.log*
32 |
33 | # env files (can opt-in for committing if needed)
34 | .env.local
35 | .env
36 |
37 | # vercel
38 | .vercel
39 |
40 | # typescript
41 | *.tsbuildinfo
42 | next-env.d.ts
43 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2025 Malhar Ujawane
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # rSearch: AI-Powered Reasoning Engine
2 |
3 | 
4 |
5 |
6 | A cutting-edge research assistant powered by artificial intelligence that harnesses the advanced reasoning capabilities of [DeepSeek R1](https://huggingface.co/deepseek-ai/DeepSeek-R1) combined with comprehensive internet search functionality. **rSearch** delivers precise, well-reasoned responses to complex research queries by:
7 |
8 | - Leveraging state-of-the-art language models for refining queries
9 | - Performing intelligent web searches across multiple data sources
10 | - Synthesizing information through sophisticated chain-of-thought reasoning processes
11 | - Providing clear, actionable insights backed by reliable sources
12 | - Customizable AI models from [OpenAI](https://openai.com), [OpenRouter](https://openrouter.ai) or [DeepSeek](https://deepseek.com)
13 | - Self hosted alternative to [Perplexity](https://perplexity.ai)
14 |
15 | ## Demo
16 |
17 |
18 | 
19 |
20 | 🔍 **Try rSearch for free** → [rsearch.app](https://rsearch.app) (Points to Beta branch)
21 |
22 | 🚀 **Get the latest features**: Clone the **beta** branch from github:
23 |
24 | ```bash
25 | git clone -b beta https://github.com/Justmalhar/rsearch.git
26 | ```
27 |
28 | ## Overview
29 |
30 | **rSearch** leverages [DeepSeek's Reasoner - R1 model](https://huggingface.co/deepseek-ai/DeepSeek-R1) to perform Chain-of-Thought reasoning on search results. The platform first refines your query, searches the internet using [Serper.dev](https://serper.dev) API, and then applies advanced reasoning to synthesize a comprehensive, well-thought-out response. Built with [Next.js](https://nextjs.org/) and [TypeScript](https://www.typescriptlang.org/), it offers a seamless research experience while maintaining high performance and code quality.
31 |
32 | ### AI Models and Reasoning
33 |
34 | - **DeepSeek-R1**: State-of-the-art reasoning model that powers rSearch's analytical capabilities
35 | - Trained via large-scale reinforcement learning
36 | - Exceptional performance in math, code, and reasoning tasks
37 | - Comparable performance to [OpenAI-o1](https://openai.com)
38 | - Open-source availability for research and development
39 |
40 | - **Customizable AI Models**: Added support to use any AI models from [OpenAI](https://openai.com), [OpenRouter](https://openrouter.ai) or [DeepSeek](https://deepseek.com) as your AI provider.
41 |
42 | - **Chain-of-Thought Process**:
43 | 1. Query Refinement: Intelligent processing of user input
44 | 2. Internet Search: Comprehensive data gathering via [Serper.dev](https://serper.dev) API
45 | 3. Reasoning Analysis: Deep analysis using DeepSeek's reasoning capabilities
46 | 4. Response Synthesis: Well-structured, logical conclusions
47 |
48 | ## Key Features
49 |
50 | - **Multiple Search Modes**: Search across diverse content categories:
51 | - **Web Search**: Broad and efficient web search capability
52 | - **Images**: Visual content search
53 | - **Videos**: Video content discovery
54 | - **News**: Latest headlines and articles
55 | - **Places**: Local information and geographical insights
56 | - **Shopping**: Product information and price comparisons
57 | - **Scholar**: Academic content and research papers
58 | - **Patents**: Patent database search
59 |
60 | - **Modern UI Components**:
61 | - Responsive sidebar navigation
62 | - Mobile-optimized header
63 | - Dynamic search results display
64 | - Interactive loading states
65 | - Customizable themes
66 |
67 | ## Deploy
68 |
69 | Deploying **rSearch** is simple and fast with Vercel's one-click deployment option. Vercel provides a powerful and scalable environment for your project.
70 |
71 | [](https://vercel.com/new/clone?repository-url=https://github.com/justmalhar/rsearch&env=SERPER_API_KEY&env=NEXT_PUBLIC_AI_PROVIDER_API_KEY&env=NEXT_PUBLIC_AI_PROVIDER_BASE_URL&env=NEXT_PUBLIC_AI_REFINER_MODEL&env=NEXT_PUBLIC_AI_REASONING_MODEL&env=NEXT_PUBLIC_LANDING_PAGE_COPY_TEXT)
72 |
73 |
74 | ## Tech Stack
75 |
76 | - **Framework**: Next.js with App Router
77 | - **Language**: TypeScript
78 | - **Styling**: TailwindCSS
79 | - **Components**: ShadCN UI components
80 | - **State Management**: React Hooks
81 | - **Code Quality**: ESLint
82 |
83 | ## Getting Started
84 |
85 | ### Prerequisites
86 |
87 | - Node.js 18.x or later
88 | - npm or yarn package manager
89 |
90 | ### Installation
91 |
92 | 1. Clone the repository:
93 | ```bash
94 | git clone https://github.com/justmalhar/rsearch.git
95 | cd rsearch
96 | ```
97 |
98 | 2. Install dependencies:
99 | ```bash
100 | npm install
101 | # or
102 | yarn install
103 | ```
104 |
105 | 3. Set up environment variables:
106 | ```bash
107 | cp .env.example .env.local
108 | ```
109 |
110 | Create a `.env.local` file in the root directory with the following variables:
111 |
112 | ```bash
113 | # Serper API Key (Google Search API) - https://serper.dev/api-key
114 | SERPER_API_KEY=
115 |
116 | # You can either use OpenAI, OpenRouter or DeepSeek as your AI provider.
117 | # OpenAI API Key (AI Provider) - https://platform.openai.com/api-keys
118 | # OpenRouter API Key (AI Provider) - https://openrouter.ai/settings/keys
119 | # DeepSeek API Key (AI Provider) - https://platform.deepseek.com/api_keys
120 | NEXT_PUBLIC_AI_PROVIDER_API_KEY=
121 |
122 | # AI Provider Base URL (AI Provider)
123 | # DeepSeek Base URL (AI Provider) - https://api.deepseek.com
124 | # OpenAI Base URL (AI Provider) - https://api.openai.com
125 | # OpenRouter Base URL (AI Provider) - https://openrouter.ai/api/v1
126 | NEXT_PUBLIC_AI_PROVIDER_BASE_URL=https://api.deepseek.com
127 |
128 | # AI Query Refiner Model (AI Provider)
129 | # DeepSeek Refiner Model (AI Provider) - deepseek-chat
130 | # OpenAI Refiner Model (AI Provider) - gpt-4o-mini
131 | # OpenRouter Refiner Model (AI Provider) - openai/gpt-4o-mini
132 | NEXT_PUBLIC_AI_REFINER_MODEL=gpt-4o-mini
133 |
134 | # AI Reasoning Model (AI Provider)
135 | # DeepSeek Reasoning Model (AI Provider) - deepseek-reasoner
136 | # OpenAI Reasoning Model (AI Provider) - gpt-4o
137 | # OpenRouter Reasoning Model (AI Provider) - openai/gpt-4o
138 | NEXT_PUBLIC_AI_REASONING_MODEL=gpt-4o
139 |
140 | # Landing Page Copy Text
141 | NEXT_PUBLIC_LANDING_PAGE_COPY_TEXT="AI-powered search with advanced reasoning capabilities"
142 | ```
143 |
144 | 5. Start the development server:
145 | ```bash
146 | npm run dev
147 | # or
148 | yarn dev
149 | ```
150 |
151 | The application will be available at `http://localhost:3000`
152 |
153 | ## Development Commands
154 |
155 | - `npm run dev`: Start development server
156 | - `npm run build`: Create production build
157 | - `npm run start`: Start production server
158 | - `npm run lint`: Run ESLint for code quality checks
159 |
160 | ## Project Structure
161 |
162 | ```
163 | rsearch/
164 | ├── app/ # Next.js app directory
165 | │ ├── api/ # API routes
166 | │ ├── layout.tsx # Root layout
167 | │ ├── page.tsx # Home page
168 | │ └── rSearch/ # Search page
169 | ├── components/ # React components
170 | │ ├── rSearch/ # Search-specific components
171 | │ └── ui/ # Reusable ShadCN UI components
172 | ├── hooks/ # Custom React hooks
173 | ├── lib/ # Utility functions
174 | ├── public/ # Static assets
175 | └── types/ # TypeScript type definitions
176 | ```
177 |
178 | ## Contributing
179 |
180 | 1. Fork the repository
181 | 2. Create your feature branch (`git checkout -b feature/AmazingFeature`)
182 | 3. Commit your changes (`git commit -m 'Add some AmazingFeature'`)
183 | 4. Push to the branch (`git push origin feature/AmazingFeature`)
184 | 5. Open a Pull Request
185 |
186 | ## License
187 |
188 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
189 |
190 | ## Acknowledgments
191 |
192 | - Built with [Next.js](https://nextjs.org/)
193 | - Powered by [DeepSeek-R1](https://huggingface.co/deepseek-ai/DeepSeek-R1)
194 | - Search capabilities by [Serper.dev](https://serper.dev)
195 | - Styled with [TailwindCSS](https://tailwindcss.com/)
196 | - Icons from various sources including custom SVGs
197 |
198 |
199 | ## Stay Connected
200 | - **Twitter/X**: [@justmalhar](https://twitter.com/justmalhar) 🛠
201 | - **LinkedIn**: [Malhar Ujawane](https://linkedin.com/in/justmalhar) 💻
202 | - **GitHub**: [justmalhar](https://github.com/justmalhar) 💻
203 |
--------------------------------------------------------------------------------
/app/[page]/page.tsx:
--------------------------------------------------------------------------------
1 | import { notFound } from "next/navigation";
2 | import staticData from "@/data/static.json";
3 | import { Logo } from "@/components/ui/logo";
4 |
5 | type StaticPage = "terms" | "privacy" | "about";
6 |
7 | export async function generateStaticParams() {
8 | return [
9 | { page: "terms" },
10 | { page: "privacy" },
11 | { page: "about" },
12 | ];
13 | }
14 |
15 | export default async function StaticPage({
16 | params
17 | }: {
18 | params: Promise<{ page: StaticPage }>
19 | }) {
20 | const resolvedParams = await params;
21 | const pageData = staticData[resolvedParams.page];
22 |
23 | if (!pageData) {
24 | notFound();
25 | }
26 |
27 | return (
28 |
29 |
34 |
35 |
36 |
37 | {pageData.title}
38 |
39 |
40 | {"lastUpdated" in pageData && (
41 |
42 | Last updated: {pageData.lastUpdated}
43 |
44 | )}
45 |
46 |
47 | {pageData.sections.map((section: { heading: string; content: string }) => (
48 |
49 |
50 | {section.heading}
51 |
52 |
53 | {section.content}
54 |
55 |
56 | ))}
57 |
58 |
59 |
60 | );
61 | }
62 |
--------------------------------------------------------------------------------
/app/api/og-image/route.ts:
--------------------------------------------------------------------------------
1 | import { NextResponse } from 'next/server';
2 |
3 | async function fetchOGImage(url: string): Promise {
4 | try {
5 | const response = await fetch(url);
6 | const html = await response.text();
7 |
8 | // Extract og:image meta tag
9 | const ogImageMatch = html.match(/]*property=["']og:image["'][^>]*content=["']([^"']+)["'][^>]*>/i)
10 | ?? html.match(/]*content=["']([^"']+)["'][^>]*property=["']og:image["'][^>]*>/i);
11 |
12 | if (ogImageMatch?.[1]) {
13 | return ogImageMatch[1];
14 | }
15 |
16 | // Fallback to twitter:image
17 | const twitterImageMatch = html.match(/]*name=["']twitter:image["'][^>]*content=["']([^"']+)["'][^>]*>/i)
18 | ?? html.match(/]*content=["']([^"']+)["'][^>]*name=["']twitter:image["'][^>]*>/i);
19 |
20 | if (twitterImageMatch?.[1]) {
21 | return twitterImageMatch[1];
22 | }
23 |
24 | return null;
25 | } catch (error) {
26 | console.error('Error fetching OG image:', error);
27 | return null;
28 | }
29 | }
30 |
31 | export async function GET(request: Request) {
32 | const { searchParams } = new URL(request.url);
33 | const url = searchParams.get('url');
34 |
35 | if (!url) {
36 | return NextResponse.json({ error: 'URL parameter is required' }, { status: 400 });
37 | }
38 |
39 | try {
40 | const ogImage = await fetchOGImage(url);
41 |
42 | if (!ogImage) {
43 | return NextResponse.json({ error: 'No OG image found' }, { status: 404 });
44 | }
45 |
46 | return NextResponse.json({ url: ogImage });
47 | } catch {
48 | return NextResponse.json({ error: 'Failed to fetch OG image' }, { status: 500 });
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/app/api/query/route.ts:
--------------------------------------------------------------------------------
1 | import { refineSearchQueryPrompt } from '@/lib/prompts';
2 | import OpenAI from 'openai';
3 | import { z } from 'zod';
4 |
5 | const openai = new OpenAI({
6 | apiKey: process.env.NEXT_PUBLIC_AI_PROVIDER_API_KEY,
7 | baseURL: process.env.NEXT_PUBLIC_AI_PROVIDER_BASE_URL,
8 | });
9 |
10 | export const runtime = 'edge';
11 | export const dynamic = 'force-dynamic';
12 | export const revalidate = 0;
13 |
14 | const RefinedSearchSchema = z.object({
15 | refined_query: z.string(),
16 | explanation: z.string(),
17 | });
18 |
19 | export async function POST(req: Request) {
20 | try {
21 | const { searchTerm, mode } = await req.json();
22 |
23 | const currentDate = new Date().toISOString().split('T')[0];
24 |
25 | const prompt = refineSearchQueryPrompt(searchTerm, mode, currentDate);
26 |
27 | const model = process.env.NEXT_PUBLIC_AI_REFINER_MODEL;
28 |
29 | const response = await openai.chat.completions.create({
30 | model: model as string,
31 | messages: [
32 | { role: 'system', content: prompt },
33 | { role: 'user', content: searchTerm }
34 | ],
35 | response_format: { type: "json_object" },
36 | temperature: 0.6,
37 | max_tokens: 500,
38 | });
39 |
40 | const content = response.choices[0].message.content;
41 | if (!content) {
42 | throw new Error('No content received from OpenAI');
43 | }
44 |
45 | const refinedSearch = JSON.parse(content);
46 |
47 | // Validate the response against our schema
48 | const validatedResponse = RefinedSearchSchema.parse(refinedSearch);
49 |
50 | return new Response(JSON.stringify(validatedResponse), {
51 | headers: { 'Content-Type': 'application/json' },
52 | });
53 |
54 | } catch (error) {
55 | console.error('Search refinement error:', error);
56 | return new Response(
57 | JSON.stringify({
58 | error: 'Failed to refine search query',
59 | details: error instanceof Error ? error.message : 'Unknown error'
60 | }),
61 | {
62 | status: 500,
63 | headers: { 'Content-Type': 'application/json' },
64 | }
65 | );
66 | }
67 | }
--------------------------------------------------------------------------------
/app/api/rsearch/route.ts:
--------------------------------------------------------------------------------
1 | import OpenAI from 'openai';
2 | import type {
3 | SerperResponse,
4 | WebSearchResult,
5 | ImageSearchResult,
6 | VideoSearchResult,
7 | NewsSearchResult,
8 | ShoppingSearchResult
9 | } from '@/types/search';
10 | import { rSearchPrompt } from '@/lib/prompts';
11 |
12 | const openai = new OpenAI({
13 | apiKey: process.env.NEXT_PUBLIC_AI_PROVIDER_API_KEY,
14 | baseURL: process.env.NEXT_PUBLIC_AI_PROVIDER_BASE_URL,
15 | });
16 |
17 |
18 | // Make sure to export these properly for Next.js API routes
19 | export const runtime = 'edge';
20 | export const dynamic = 'force-dynamic';
21 | export const revalidate = 0;
22 |
23 | export async function POST(req: Request) {
24 | try {
25 | const {
26 | searchTerm,
27 | searchResults,
28 | mode,
29 | refinedQuery
30 | }: {
31 | searchTerm: string;
32 | searchResults: SerperResponse;
33 | mode: string;
34 | refinedQuery?: {
35 | query: string;
36 | explanation: string;
37 | };
38 | } = await req.json();
39 |
40 | // Create a comprehensive context from search results
41 | let context = '';
42 |
43 | // Add knowledge graph info if available
44 | if (searchResults.knowledgeGraph) {
45 | const kg = searchResults.knowledgeGraph;
46 | context += `### Knowledge Graph\nTitle: ${kg.title}\nType: ${kg.type}${kg.description ? `\nDescription: ${kg.description}` : ''}\n`;
47 | if (kg.attributes) {
48 | context += 'Attributes:\n';
49 | for (const [key, value] of Object.entries(kg.attributes)) {
50 | context += `- ${key}: ${value}\n`;
51 | }
52 | }
53 | // Add any images from knowledge graph
54 | if (kg.images?.length) {
55 | context += 'Images:\n';
56 | for (const image of kg.images) {
57 | context += `- ${image.title || 'Image'}: ${image.imageUrl}\n`;
58 | }
59 | }
60 | context += '\n';
61 | }
62 |
63 | // Add organic search results
64 | if (searchResults.organic?.length) {
65 | context += '### Organic Results\n';
66 | context += searchResults.organic.map((result: WebSearchResult, index: number) => {
67 | return `[${index + 1}] ${result.title}
68 | Source: ${result.link}
69 | ${result.snippet}
70 | ${result.date ? `Date: ${result.date}\n` : ''}${result.attributes ? `Attributes:
71 | ${Object.entries(result.attributes).map(([key, value]) => `- ${key}: ${value}`).join('\n')}\n` : ''}${result.imageUrl ? `Image: ${result.imageUrl}\n` : ''}${result.thumbnailUrl ? `Thumbnail: ${result.thumbnailUrl}\n` : ''}\n`;
72 | }).join('');
73 | }
74 |
75 | // Add top stories if available
76 | if (searchResults.news?.length) {
77 | context += '### Top Stories\n';
78 | context += searchResults.news.map((story: NewsSearchResult, index: number) => {
79 | return `[${index + 1}] ${story.title}
80 | Source: ${story.source}
81 | Link: ${story.link}
82 | ${story.date ? `Date: ${story.date}\n` : ''}${story.imageUrl ? `Image: ${story.imageUrl}\n` : ''}${story.snippet ? `Summary: ${story.snippet}\n` : ''}\n`;
83 | }).join('');
84 | }
85 |
86 | // Add people also ask if available
87 | if (searchResults.peopleAlsoAsk?.length) {
88 | context += '### People Also Ask\n';
89 | context += searchResults.peopleAlsoAsk.map((item: { question: string; snippet: string; link: string; title?: string }, index: number) => {
90 | return `[${index + 1}] Q: ${item.question}
91 | A: ${item.snippet}
92 | Source: ${item.link}
93 | ${item.title ? `Title: ${item.title}\n` : ''}\n`;
94 | }).join('');
95 | }
96 |
97 | // Add related searches if available
98 | if (searchResults.relatedSearches?.length) {
99 | context += '### Related Searches\n';
100 | context += searchResults.relatedSearches.map((item: { query: string }, index: number) =>
101 | `[${index + 1}] ${item.query}\n`
102 | ).join('');
103 | context += '\n';
104 | }
105 |
106 | // Add images section if available
107 | if (searchResults.images?.length) {
108 | context += '### Images\n';
109 | context += searchResults.images.map((image: ImageSearchResult, index: number) => {
110 | return `[${index + 1}] ${image.title || 'Image'}
111 | URL: ${image.imageUrl}
112 | ${image.source ? `Source: ${image.source}\n` : ''}\n`;
113 | }).join('');
114 | }
115 |
116 | // Add shopping results if available
117 | if (searchResults.shopping?.length) {
118 | context += '### Shopping Results\n';
119 | context += searchResults.shopping.map((item: ShoppingSearchResult, index: number) => {
120 | return `[${index + 1}] ${item.title}
121 | Price: ${item.price || 'N/A'}
122 | ${item.rating ? `Rating: ${item.rating}\n` : ''}${item.source ? `Source: ${item.source}\n` : ''}${item.link ? `Link: ${item.link}\n` : ''}${item.imageUrl ? `Image: ${item.imageUrl}\n` : ''}\n`;
123 | }).join('');
124 | }
125 |
126 | // Add videos section if available
127 | if (searchResults.videos?.length) {
128 | context += '### Videos\n';
129 | context += searchResults.videos.map((video: VideoSearchResult, index: number) => {
130 | return `[${index + 1}] ${video.title}
131 | Link: ${video.link}
132 | ${video.date ? `Date: ${video.date}\n` : ''}${video.duration ? `Duration: ${video.duration}\n` : ''}${video.imageUrl ? `Image: ${video.imageUrl}\n` : ''}\n`;
133 | }).join('');
134 | }
135 |
136 | const currentDate = new Date().toISOString().split('T')[0];
137 |
138 | // Include mode and refined query in context
139 | let searchContext = '';
140 | if (refinedQuery) {
141 | searchContext += `### Search Context\nOriginal Query: ${searchTerm}\nRefined Query: ${refinedQuery.query}\nRefinement Explanation: ${refinedQuery.explanation}\nSearch Mode: ${mode}\n\n`;
142 | } else {
143 | searchContext += `### Search Context\nQuery: ${searchTerm}\nSearch Mode: ${mode}\n\n`;
144 | }
145 |
146 | const prompt = rSearchPrompt(searchTerm, searchContext + context, currentDate);
147 |
148 | const model = process.env.NEXT_PUBLIC_AI_REASONING_MODEL;
149 |
150 |
151 | const response = await openai.chat.completions.create({
152 | model: model as string, // Make sure to use the correct model name
153 | messages: [
154 | { role: "system", content: prompt },
155 | { role: 'user', content: searchTerm }
156 | ],
157 | stream: true,
158 | });
159 |
160 | // Create a streaming response
161 | const stream = new ReadableStream({
162 | async start(controller) {
163 | try {
164 | for await (const chunk of response) {
165 | const newChunk = chunk.choices[0]?.delta;
166 |
167 | // Send the entire chunk object as JSON
168 | controller.enqueue(new TextEncoder().encode(`${JSON.stringify(newChunk)}\n`));
169 | }
170 | controller.close();
171 | } catch (error) {
172 | controller.error(error);
173 | }
174 | },
175 | });
176 |
177 | return new Response(stream, {
178 | headers: {
179 | 'Content-Type': 'text/plain; charset=utf-8',
180 | 'Cache-Control': 'no-cache',
181 | },
182 | });
183 | } catch (error) {
184 | console.error('OpenAI API error:', error);
185 | return new Response(
186 | JSON.stringify({
187 | error: 'Failed to generate AI response',
188 | details: error instanceof Error ? error.message : 'Unknown error'
189 | }),
190 | {
191 | status: 500,
192 | headers: { 'Content-Type': 'application/json' },
193 | }
194 | );
195 | }
196 | }
197 |
--------------------------------------------------------------------------------
/app/globals.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | body {
6 | font-family: Arial, Helvetica, sans-serif;
7 | }
8 |
9 | @layer base {
10 | :root {
11 | --background: 0 0% 100%;
12 | --foreground: 0 0% 3.9%;
13 | --card: 0 0% 100%;
14 | --card-foreground: 0 0% 3.9%;
15 | --popover: 0 0% 100%;
16 | --popover-foreground: 0 0% 3.9%;
17 | --primary: 0 0% 9%;
18 | --primary-foreground: 0 0% 98%;
19 | --secondary: 0 0% 96.1%;
20 | --secondary-foreground: 0 0% 9%;
21 | --muted: 0 0% 96.1%;
22 | --muted-foreground: 0 0% 45.1%;
23 | --accent: 0 0% 96.1%;
24 | --accent-foreground: 0 0% 9%;
25 | --destructive: 0 84.2% 60.2%;
26 | --destructive-foreground: 0 0% 98%;
27 | --border: 0 0% 89.8%;
28 | --input: 0 0% 89.8%;
29 | --ring: 0 0% 3.9%;
30 | --chart-1: 12 76% 61%;
31 | --chart-2: 173 58% 39%;
32 | --chart-3: 197 37% 24%;
33 | --chart-4: 43 74% 66%;
34 | --chart-5: 27 87% 67%;
35 | --radius: 0.5rem;
36 | --sidebar-background: 0 0% 98%;
37 | --sidebar-foreground: 240 5.3% 26.1%;
38 | --sidebar-primary: 240 5.9% 10%;
39 | --sidebar-primary-foreground: 0 0% 98%;
40 | --sidebar-accent: 240 4.8% 95.9%;
41 | --sidebar-accent-foreground: 240 5.9% 10%;
42 | --sidebar-border: 220 13% 91%;
43 | --sidebar-ring: 217.2 91.2% 59.8%;
44 | }
45 | .dark {
46 | --background: 0 0% 3.9%;
47 | --foreground: 0 0% 98%;
48 | --card: 0 0% 3.9%;
49 | --card-foreground: 0 0% 98%;
50 | --popover: 0 0% 3.9%;
51 | --popover-foreground: 0 0% 98%;
52 | --primary: 0 0% 98%;
53 | --primary-foreground: 0 0% 9%;
54 | --secondary: 0 0% 14.9%;
55 | --secondary-foreground: 0 0% 98%;
56 | --muted: 0 0% 14.9%;
57 | --muted-foreground: 0 0% 63.9%;
58 | --accent: 0 0% 14.9%;
59 | --accent-foreground: 0 0% 98%;
60 | --destructive: 0 62.8% 30.6%;
61 | --destructive-foreground: 0 0% 98%;
62 | --border: 0 0% 14.9%;
63 | --input: 0 0% 14.9%;
64 | --ring: 0 0% 83.1%;
65 | --chart-1: 220 70% 50%;
66 | --chart-2: 160 60% 45%;
67 | --chart-3: 30 80% 55%;
68 | --chart-4: 280 65% 60%;
69 | --chart-5: 340 75% 55%;
70 | --sidebar-background: 240 5.9% 10%;
71 | --sidebar-foreground: 240 4.8% 95.9%;
72 | --sidebar-primary: 224.3 76.3% 48%;
73 | --sidebar-primary-foreground: 0 0% 100%;
74 | --sidebar-accent: 240 3.7% 15.9%;
75 | --sidebar-accent-foreground: 240 4.8% 95.9%;
76 | --sidebar-border: 240 3.7% 15.9%;
77 | --sidebar-ring: 217.2 91.2% 59.8%;
78 | }
79 | }
80 |
81 | @layer base {
82 | * {
83 | @apply border-border;
84 | }
85 | body {
86 | @apply bg-background text-foreground;
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import type { Metadata } from "next";
2 | import { Inter } from "next/font/google";
3 | import "./globals.css";
4 | import { Sidebar } from "@/components/ui/sidebar";
5 | import { MobileHeader } from "@/components/ui/mobile-header";
6 | import { Analytics } from '@vercel/analytics/next';
7 |
8 | const inter = Inter({ subsets: ["latin"] });
9 | export const metadata: Metadata = {
10 | title: "rSearch: AI-Powered Reasoning Engine",
11 | description: "A cutting-edge reasoning engine powered by artificial intelligence that harnesses advanced reasoning capabilities combined with comprehensive internet search functionality.",
12 | applicationName: "rSearch",
13 | authors: [{ name: "Malhar Ujawane", url: "https://twitter.com/justmalhar" }],
14 | keywords: ["rsearch", "deepseek", "reasoning engine", "AI search", "semantic search", "research assistant", "AI powered search", "deepseek coder", "deepseek chat", "deepseek coder 2", "deepseek chat 2"],
15 | creator: "Malhar Ujawane",
16 | publisher: "rSearch",
17 | metadataBase: new URL('https://rsearch.app'),
18 | alternates: {
19 | canonical: 'https://rsearch.app',
20 | },
21 | robots: {
22 | index: true,
23 | follow: true,
24 | googleBot: {
25 | index: true,
26 | follow: true,
27 | 'max-video-preview': -1,
28 | 'max-image-preview': 'large',
29 | 'max-snippet': -1,
30 | },
31 | },
32 | openGraph: {
33 | title: "rSearch: AI-Powered Reasoning Engine",
34 | description: "Discover Insights, Not Just Results. AI-powered reasoning engine that thinks just like you do.",
35 | url: 'https://rsearch.app',
36 | siteName: 'rSearch',
37 | images: [
38 | {
39 | url: '/og.png',
40 | width: 1200,
41 | height: 630,
42 | alt: 'rSearch - AI-Powered Research Assistant'
43 | }
44 | ],
45 | locale: 'en_US',
46 | type: 'website',
47 | },
48 | twitter: {
49 | card: 'summary_large_image',
50 | title: 'rSearch: AI-Powered Research Assistant',
51 | description: 'Discover Insights, Not Just Results. AI-powered reasoning engine that thinks just like you do.',
52 | creator: '@justmalhar',
53 | site: '@justmalhar',
54 | images: ['/og.png'],
55 | },
56 | icons: {
57 | icon: {
58 | url: `data:image/svg+xml,${encodeURIComponent(``)}`,
59 | type: "image/svg+xml"
60 | },
61 | shortcut: '/favicon.ico',
62 | apple: '/apple-touch-icon.png',
63 | },
64 | category: 'technology',
65 | };
66 |
67 | export default function RootLayout({
68 | children,
69 | }: {
70 | children: React.ReactNode;
71 | }) {
72 | return (
73 |
74 |
75 | {/* Twitter Meta Tags */}
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 | {/* Open Graph Meta Tags */}
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 | {children}
102 |
103 |
104 |
105 |
106 |
107 |
108 | );
109 | }
110 |
--------------------------------------------------------------------------------
/app/settings/page.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
4 | import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
5 | import { Label } from "@/components/ui/label";
6 | import { Settings, Search, ChevronDown } from "lucide-react";
7 | import { Button } from "@/components/ui/button";
8 | import { useEffect, useState } from "react";
9 | import { Switch } from "@/components/ui/switch";
10 |
11 | const aiProviders = [
12 | {
13 | id: "deepseek",
14 | name: "DeepSeek",
15 | description: "Use DeepSeek's language models deepseek-chat and deepseek-reasoner with reasoning",
16 | disabled: false
17 | },
18 | {
19 | id: "openai",
20 | name: "OpenAI",
21 | description: "Coming soon - o1 and o1-mini reasoning mode not available",
22 | disabled: true
23 | },
24 | {
25 | id: "openrouter",
26 | name: "OpenRouter",
27 | description: "Coming soon - Access multiple AI models through OpenRouter",
28 | disabled: true
29 | }
30 | ];
31 |
32 | const searchProviders = [
33 | {
34 | id: "serper",
35 | name: "Serper.dev",
36 | description: "Google Search API via Serper.dev",
37 | disabled: false
38 | },
39 | {
40 | id: "tavily",
41 | name: "Tavily",
42 | description: "Coming soon",
43 | disabled: true
44 | },
45 | {
46 | id: "searxng",
47 | name: "SearXNG",
48 | description: "Coming soon",
49 | disabled: true
50 | }
51 | ];
52 |
53 | export default function SettingsPage() {
54 | const [selectedAIProvider, setSelectedAIProvider] = useState("deepseek");
55 | const [selectedSearchProvider, setSelectedSearchProvider] = useState("serper");
56 | const [autoExpandSections, setAutoExpandSections] = useState(true);
57 | const [isSaved, setIsSaved] = useState(false);
58 |
59 | useEffect(() => {
60 | // Load saved settings on mount
61 | const savedSettings = localStorage.getItem("rSearch_settings");
62 | if (savedSettings) {
63 | const settings = JSON.parse(savedSettings);
64 | setSelectedAIProvider(settings.aiProvider);
65 | setSelectedSearchProvider(settings.searchProvider);
66 | setAutoExpandSections(settings.autoExpandSections ?? true);
67 | }
68 | }, []);
69 |
70 | const handleSave = () => {
71 | const settings = {
72 | aiProvider: selectedAIProvider,
73 | searchProvider: selectedSearchProvider,
74 | autoExpandSections
75 | };
76 | localStorage.setItem("rSearch_settings", JSON.stringify(settings));
77 | setIsSaved(true);
78 | setTimeout(() => setIsSaved(false), 2000);
79 | };
80 |
81 | return (
82 |
83 |
84 |
88 |
89 |
90 |
91 |
92 |
93 |
94 | Display Settings
95 |
96 |
97 |
98 |
99 |
105 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 | AI Provider
120 |
121 |
122 |
123 |
128 | {aiProviders.map((provider) => (
129 |
130 |
136 |
147 |
148 | ))}
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 | Search Provider
158 |
159 |
160 |
161 |
166 | {searchProviders.map((provider) => (
167 |
168 |
174 |
185 |
186 | ))}
187 |
188 |
189 |
190 |
191 |
192 |
198 |
199 |
200 |
201 |
202 | );
203 | }
204 |
--------------------------------------------------------------------------------
/components.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://ui.shadcn.com/schema.json",
3 | "style": "new-york",
4 | "rsc": true,
5 | "tsx": true,
6 | "tailwind": {
7 | "config": "tailwind.config.ts",
8 | "css": "app/globals.css",
9 | "baseColor": "neutral",
10 | "cssVariables": true,
11 | "prefix": ""
12 | },
13 | "aliases": {
14 | "components": "@/components",
15 | "utils": "@/lib/utils",
16 | "ui": "@/components/ui",
17 | "lib": "@/lib",
18 | "hooks": "@/hooks"
19 | },
20 | "iconLibrary": "lucide"
21 | }
--------------------------------------------------------------------------------
/components/rSearch/query.tsx:
--------------------------------------------------------------------------------
1 |
2 | import {
3 | Globe, BookText, Video, ShoppingBag, MapPin, Newspaper, GraduationCap, Lightbulb
4 | } from "lucide-react";
5 |
6 |
7 |
8 | interface QueryProps {
9 | searchTerm: string;
10 | mode?: string;
11 | }
12 |
13 | export default function Query({ searchTerm, mode = 'web' }: QueryProps) {
14 | const searchModes = {
15 | web: Globe,
16 | images: BookText,
17 | videos: Video,
18 | news: Newspaper,
19 | places: MapPin,
20 | shopping: ShoppingBag,
21 | scholar: GraduationCap,
22 | patents: Lightbulb
23 | };
24 |
25 | const Icon = searchModes[mode as keyof typeof searchModes] || Globe;
26 |
27 | return (
28 |
29 |
30 |
31 |
32 | {searchTerm}
33 |
34 |
35 |
36 | );
37 | }
38 |
--------------------------------------------------------------------------------
/components/rSearch/sources-sidebar.tsx:
--------------------------------------------------------------------------------
1 | import { SearchResult, PlaceSearchResult } from '@/types/search';
2 |
3 | interface SourcesSidebarProps {
4 | showSidebar: boolean;
5 | sources: SearchResult[];
6 | getWebsiteName: (url: string) => string;
7 | }
8 |
9 | const isPlaceResult = (source: SearchResult): source is PlaceSearchResult =>
10 | 'address' in source;
11 |
12 | export default function SourcesSidebar({ showSidebar, sources, getWebsiteName }: SourcesSidebarProps) {
13 | const getSourceUrl = (source: SearchResult): string => {
14 | if (isPlaceResult(source)) {
15 | return source.website || '';
16 | }
17 | return source.link;
18 | };
19 | return (
20 |
21 |
22 |
23 |
24 |
25 | Sources
26 |
27 |
28 | {sources.length}
29 |
30 |
31 |
32 |
33 |
99 |
100 |
101 | );
102 | }
103 |
--------------------------------------------------------------------------------
/components/rSearch/sources/sources-academic.tsx:
--------------------------------------------------------------------------------
1 | import { AcademicSearchResult } from '@/types/search';
2 |
3 | interface AcademicSourcesProps {
4 | sources: AcademicSearchResult[];
5 | displayCount: number;
6 | }
7 |
8 | export default function AcademicSources({ sources, displayCount }: AcademicSourcesProps) {
9 | return (
10 |
11 | {sources.slice(0, displayCount).map((source) => (
12 |
13 |
{source.title}
14 |
{source.snippet}
15 | {source.authors && (
16 |
Authors: {source.authors.join(', ')}
17 | )}
18 |
19 | ))}
20 |
21 | );
22 | }
--------------------------------------------------------------------------------
/components/rSearch/sources/sources-images.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import type { ImageSearchResult } from '@/types/search';
4 | import Image from 'next/image';
5 | import {
6 | Carousel,
7 | CarouselContent,
8 | CarouselItem,
9 | CarouselNext,
10 | CarouselPrevious,
11 | } from "@/components/ui/carousel";
12 |
13 | interface ImageSourcesProps {
14 | sources: ImageSearchResult[];
15 | displayCount: number;
16 | }
17 |
18 | export default function ImageSources({ sources, displayCount }: ImageSourcesProps) {
19 | return (
20 |
67 | );
68 | }
69 |
--------------------------------------------------------------------------------
/components/rSearch/sources/sources-knowledge.tsx:
--------------------------------------------------------------------------------
1 | import type { SerperResponse } from '@/types/search';
2 |
3 | interface KnowledgeGraphProps {
4 | data: SerperResponse['knowledgeGraph'];
5 | }
6 |
7 | export default function KnowledgeGraph({ data }: KnowledgeGraphProps) {
8 | if (!data) return null;
9 |
10 | return (
11 |
12 |
13 |
14 |
15 |
16 | {/* Image Column */}
17 | {data.imageUrl && (
18 |
19 |

24 |
25 | )}
26 |
27 | {/* Content Column */}
28 |
29 | {/* Header */}
30 |
31 |
32 | {data.title}
33 |
34 |
35 | {data.type}
36 |
37 |
38 |
39 | {/* Attributes Grid */}
40 | {data.attributes && Object.entries(data.attributes).length > 0 && (
41 |
42 | {Object.entries(data.attributes).map(([key, value]) => (
43 |
47 |
48 | {key}
49 |
50 |
51 | {value}
52 |
53 |
54 | ))}
55 |
56 | )}
57 |
58 |
59 |
60 |
61 |
62 |
63 | );
64 | }
65 |
--------------------------------------------------------------------------------
/components/rSearch/sources/sources-news.tsx:
--------------------------------------------------------------------------------
1 | import type { NewsSearchResult } from '@/types/search';
2 | import { useEffect, useState } from 'react';
3 |
4 | interface NewsSourcesProps {
5 | sources: NewsSearchResult[];
6 | displayCount: number;
7 | }
8 |
9 | export default function NewsSources({ sources, displayCount }: NewsSourcesProps) {
10 | const [imageUrls, setImageUrls] = useState>({});
11 |
12 | useEffect(() => {
13 | const fetchOGImages = async () => {
14 | const newImageUrls: Record = {};
15 |
16 | for (const source of sources) {
17 | if (!source.imageUrl || source.imageUrl.includes('favicon')) {
18 | try {
19 | const response = await fetch(`/api/og-image?url=${encodeURIComponent(source.link)}`);
20 | if (response.ok) {
21 | const data = await response.json();
22 | if (data.url) {
23 | newImageUrls[source.link] = data.url;
24 | }
25 | }
26 | } catch (error) {
27 | console.error('Error fetching OG image:', error);
28 | }
29 | }
30 | }
31 |
32 | setImageUrls(newImageUrls);
33 | };
34 |
35 | fetchOGImages();
36 | }, [sources]);
37 |
38 | return (
39 |
40 | {sources.slice(0, displayCount).map((source, index) => (
41 |
110 | ))}
111 |
112 | );
113 | }
114 |
--------------------------------------------------------------------------------
/components/rSearch/sources/sources-patents.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import type { AcademicSearchResult } from '@/types/search';
3 |
4 | interface PatentSourcesProps {
5 | sources: (AcademicSearchResult & {
6 | priorityDate?: string;
7 | filingDate?: string;
8 | grantDate?: string;
9 | publicationDate?: string;
10 | inventor?: string;
11 | assignee?: string;
12 | publicationNumber?: string;
13 | figures?: Array<{
14 | imageUrl: string;
15 | thumbnailUrl: string;
16 | }>;
17 | })[];
18 | displayCount?: number;
19 | }
20 |
21 | export default function PatentSources({ sources, displayCount = 10 }: PatentSourcesProps) {
22 |
23 | return (
24 |
25 |
26 | {sources.slice(0, displayCount).map((source) => (
27 |
28 | {/* Title and Link */}
29 |
30 |
31 |
32 |
33 |

40 |
41 |
42 |
43 |
44 |
50 | {source.title}
51 |
52 |
53 | {/* Patent Info */}
54 |
55 | {source.publicationNumber && (
56 | {source.publicationNumber}
57 | )}
58 | {source.inventor && (
59 | <>
60 | •
61 | Inventor: {source.inventor}
62 | >
63 | )}
64 | {source.assignee && (
65 | <>
66 | •
67 | Assignee: {source.assignee}
68 | >
69 | )}
70 |
71 |
72 | {/* Dates */}
73 |
74 | {source.priorityDate && (
75 | Priority: {new Date(source.priorityDate).toLocaleDateString()}
76 | )}
77 | {source.filingDate && (
78 | <>
79 | •
80 | Filed: {new Date(source.filingDate).toLocaleDateString()}
81 | >
82 | )}
83 | {source.grantDate && (
84 | <>
85 | •
86 | Granted: {new Date(source.grantDate).toLocaleDateString()}
87 | >
88 | )}
89 |
90 |
91 | {/* Snippet/Abstract */}
92 | {source.snippet && (
93 |
94 | {source.snippet}
95 |
96 | )}
97 |
98 | {/* Patent Figures */}
99 | {source.figures && source.figures.length > 0 && (
100 |
101 | {source.figures.slice(0, 5).map((figure, index) => (
102 |
109 |
114 |
115 | ))}
116 | {source.figures.length > 5 && (
117 |
118 | +{source.figures.length - 5} more
119 |
120 | )}
121 |
122 | )}
123 |
124 | {/* PDF Link if available */}
125 | {'pdfUrl' in source && source.pdfUrl && (
126 |
153 | )}
154 |
155 |
156 |
157 | ))}
158 |
159 |
160 | );
161 | }
162 |
--------------------------------------------------------------------------------
/components/rSearch/sources/sources-places.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { PlaceSearchResult } from '@/types/search';
4 | import { MapPin, Phone, Star } from 'lucide-react';
5 | import {
6 | Carousel,
7 | CarouselContent,
8 | CarouselItem,
9 | CarouselNext,
10 | CarouselPrevious,
11 | } from "@/components/ui/carousel";
12 |
13 | interface PlaceSourcesProps {
14 | sources: PlaceSearchResult[];
15 | displayCount: number;
16 | }
17 |
18 | export default function PlaceSources({ sources = [], displayCount }: PlaceSourcesProps) {
19 | const validSources = sources?.filter(source =>
20 | source?.title &&
21 | source?.address
22 | ) ?? [];
23 |
24 | return (
25 |
26 |
33 |
34 | {!validSources.length ? (
35 |
36 |
37 |
No place results found
38 |
39 |
40 | ) : validSources.slice(0, displayCount).map((source) => (
41 |
42 |
43 |
44 |
{source.title}
45 |
{source.category}
46 |
47 |
48 |
49 |
50 | {source.rating}
51 | ({source.ratingCount} reviews)
52 |
53 |
54 |
55 |
56 |
57 |
{source.address}
58 |
59 |
60 | {source.phoneNumber && (
61 |
70 | )}
71 |
72 | {source.website && (
73 |
96 | )}
97 |
98 |
99 |
100 | ))}
101 |
102 | {validSources.length > 1 && (
103 | <>
104 |
105 |
106 | >
107 | )}
108 |
109 |
110 | );
111 | }
112 |
--------------------------------------------------------------------------------
/components/rSearch/sources/sources-scholar.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import type { AcademicSearchResult } from '@/types/search';
3 |
4 | interface ScholarSourcesProps {
5 | sources: (AcademicSearchResult & {
6 | pdfUrl?: string;
7 | citations?: number;
8 | publicationInfo?: string;
9 | })[];
10 | displayCount?: number;
11 | }
12 |
13 | export default function ScholarSources({ sources, displayCount = 10 }: ScholarSourcesProps) {
14 |
15 | return (
16 |
17 |
18 | {sources.slice(0, displayCount).map((source) => (
19 |
20 | {/* Title and Link */}
21 |
22 |
23 |
24 |
25 |

32 |
33 |
34 |
35 |
36 |
42 | {source.title}
43 |
44 |
45 | {/* Publication Info */}
46 |
47 | {source.publicationInfo ? (
48 | {source.publicationInfo}
49 | ) : (
50 | <>
51 | {source.authors && source.authors.length > 0 && (
52 | <>
53 | Authors:
54 | {source.authors.join(', ')}
55 | >
56 | )}
57 | {source.publisher && (
58 | <>
59 | •
60 | Publisher:
61 | {source.publisher}
62 | >
63 | )}
64 | >
65 | )}
66 | {source.year && (
67 | <>
68 | •
69 | Year:
70 | {source.year}
71 | >
72 | )}
73 | {source.citations && (
74 | <>
75 | •
76 | Citations:
77 | {source.citations}
78 | >
79 | )}
80 |
81 |
82 | {/* Snippet/Abstract */}
83 | {source.snippet && (
84 |
85 | {source.snippet}
86 |
87 | )}
88 |
89 | {/* PDF Link if available */}
90 | {'pdfUrl' in source && source.pdfUrl && (
91 |
118 | )}
119 |
120 |
121 |
122 | ))}
123 |
124 |
125 | );
126 | }
127 |
--------------------------------------------------------------------------------
/components/rSearch/sources/sources-search.tsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Justmalhar/rsearch/0515631cceadba32cc610959c3e819bf9b86e2ba/components/rSearch/sources/sources-search.tsx
--------------------------------------------------------------------------------
/components/rSearch/sources/sources-shopping.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import type { ShoppingSearchResult } from '@/types/search';
4 | import Image from 'next/image';
5 | import Link from 'next/link';
6 | import {
7 | Carousel,
8 | CarouselContent,
9 | CarouselItem,
10 | CarouselNext,
11 | CarouselPrevious,
12 | } from "@/components/ui/carousel";
13 |
14 | interface ShoppingSourcesProps {
15 | sources: ShoppingSearchResult[];
16 | displayCount: number;
17 | }
18 |
19 | export default function ShoppingSources({ sources, displayCount }: ShoppingSourcesProps) {
20 | const displayedSources = sources.slice(0, displayCount);
21 |
22 | return (
23 |
24 |
31 |
32 | {!displayedSources.length ? (
33 |
34 |
35 |
No shopping results found
36 |
37 |
38 | ) : displayedSources.map((source) => (
39 |
40 |
45 |
46 |
47 |
48 | {source.imageUrl && (
49 |
56 | )}
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |

71 |
72 |
73 |
74 |
{source.source}
75 |
76 |
77 |
78 | {source.title}
79 |
80 |
81 |
82 |
83 |
84 |
85 | {source.price}
86 |
87 | {source.delivery && (
88 |
89 | {source.delivery}
90 |
91 | )}
92 |
93 |
94 |
95 | {source.rating && (
96 |
97 | ★
98 |
99 | {source.rating}
100 | {source.ratingCount && (
101 |
102 | ({source.ratingCount})
103 |
104 | )}
105 |
106 |
107 | )}
108 | {source.offers && (
109 |
110 | {source.offers} offers
111 |
112 | )}
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 | ))}
121 |
122 | {displayedSources.length > 1 && (
123 | <>
124 |
125 |
126 | >
127 | )}
128 |
129 |
130 | );
131 | }
132 |
--------------------------------------------------------------------------------
/components/rSearch/sources/sources-videos.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import type { VideoSearchResult } from '@/types/search';
4 | import Image from 'next/image';
5 | import {
6 | Carousel,
7 | CarouselContent,
8 | CarouselItem,
9 | CarouselNext,
10 | CarouselPrevious,
11 | } from "@/components/ui/carousel";
12 |
13 | interface VideoSourcesProps {
14 | sources: VideoSearchResult[];
15 | displayCount: number;
16 | }
17 |
18 | export default function VideoSources({ sources = [], displayCount }: VideoSourcesProps) {
19 | const validSources = sources?.filter(source =>
20 | source?.link &&
21 | source?.title
22 | ) ?? [];
23 |
24 | return (
25 |
110 | );
111 | }
112 |
--------------------------------------------------------------------------------
/components/rSearch/sources/sources-web.tsx:
--------------------------------------------------------------------------------
1 | import type { WebSearchResult } from '@/types/search';
2 |
3 | interface WebSourcesProps {
4 | sources: WebSearchResult[];
5 | displayCount: number;
6 | getWebsiteName: (url: string) => string;
7 | }
8 |
9 | export default function WebSources({ sources, displayCount, getWebsiteName }: WebSourcesProps) {
10 | return (
11 |
12 | {sources.slice(0, displayCount).map((source, index) => (
13 |
65 | ))}
66 |
67 | );
68 | }
69 |
--------------------------------------------------------------------------------
/components/rSearch/thinking.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import Markdown from 'react-markdown';
4 |
5 | interface ThinkingProps {
6 | reasoningContent?: string | null;
7 | }
8 |
9 | export default function Thinking({ reasoningContent }: ThinkingProps) {
10 |
11 | return (
12 | <>
13 | {/* Thinking Section */}
14 | {reasoningContent && (
15 |
16 |
17 |
(
20 |
21 | ),
22 | h2: (props) => (
23 |
24 | ),
25 | h3: (props) => (
26 |
27 | ),
28 | h4: (props) => (
29 |
30 | ),
31 | h5: (props) => (
32 |
33 | ),
34 | h6: (props) => (
35 |
36 | ),
37 | table: (props) => (
38 |
39 | ),
40 | thead: (props) => (
41 |
42 | ),
43 | tbody: (props) => (
44 |
45 | ),
46 | tr: (props) => (
47 |
48 | ),
49 | th: (props) => (
50 | |
51 | ),
52 | td: (props) => (
53 | |
54 | ),
55 | img: (props) => (
56 |
57 | ),
58 | p: (props) => (
59 |
60 | ),
61 | ul: (props) => (
62 |
63 | ),
64 | ol: (props) => (
65 |
66 | ),
67 | li: (props) => (
68 |
69 | ),
70 | a: (props) => (
71 |
77 | ),
78 | blockquote: (props) => (
79 |
80 | ),
81 | strong: (props) => (
82 |
83 | ),
84 | em: (props) => (
85 |
86 | ),
87 | code: (props) => (
88 |
89 | ),
90 | }}
91 | >
92 | {reasoningContent}
93 |
94 |
95 |
96 | )}
97 | >
98 | );
99 | }
100 |
--------------------------------------------------------------------------------
/components/ui/button.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { Slot } from "@radix-ui/react-slot"
3 | import { cva, type VariantProps } from "class-variance-authority"
4 |
5 | import { cn } from "@/lib/utils"
6 |
7 | const buttonVariants = cva(
8 | "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
9 | {
10 | variants: {
11 | variant: {
12 | default:
13 | "bg-primary text-primary-foreground shadow hover:bg-primary/90",
14 | destructive:
15 | "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
16 | outline:
17 | "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
18 | secondary:
19 | "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
20 | ghost: "hover:bg-accent hover:text-accent-foreground",
21 | link: "text-primary underline-offset-4 hover:underline",
22 | },
23 | size: {
24 | default: "h-9 px-4 py-2",
25 | sm: "h-8 rounded-md px-3 text-xs",
26 | lg: "h-10 rounded-md px-8",
27 | icon: "h-9 w-9",
28 | },
29 | },
30 | defaultVariants: {
31 | variant: "default",
32 | size: "default",
33 | },
34 | }
35 | )
36 |
37 | export interface ButtonProps
38 | extends React.ButtonHTMLAttributes,
39 | VariantProps {
40 | asChild?: boolean
41 | }
42 |
43 | const Button = React.forwardRef(
44 | ({ className, variant, size, asChild = false, ...props }, ref) => {
45 | const Comp = asChild ? Slot : "button"
46 | return (
47 |
52 | )
53 | }
54 | )
55 | Button.displayName = "Button"
56 |
57 | export { Button, buttonVariants }
58 |
--------------------------------------------------------------------------------
/components/ui/card.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | const Card = React.forwardRef<
6 | HTMLDivElement,
7 | React.HTMLAttributes
8 | >(({ className, ...props }, ref) => (
9 |
17 | ))
18 | Card.displayName = "Card"
19 |
20 | const CardHeader = React.forwardRef<
21 | HTMLDivElement,
22 | React.HTMLAttributes
23 | >(({ className, ...props }, ref) => (
24 |
29 | ))
30 | CardHeader.displayName = "CardHeader"
31 |
32 | const CardTitle = React.forwardRef<
33 | HTMLDivElement,
34 | React.HTMLAttributes
35 | >(({ className, ...props }, ref) => (
36 |
41 | ))
42 | CardTitle.displayName = "CardTitle"
43 |
44 | const CardDescription = React.forwardRef<
45 | HTMLDivElement,
46 | React.HTMLAttributes
47 | >(({ className, ...props }, ref) => (
48 |
53 | ))
54 | CardDescription.displayName = "CardDescription"
55 |
56 | const CardContent = React.forwardRef<
57 | HTMLDivElement,
58 | React.HTMLAttributes
59 | >(({ className, ...props }, ref) => (
60 |
61 | ))
62 | CardContent.displayName = "CardContent"
63 |
64 | const CardFooter = React.forwardRef<
65 | HTMLDivElement,
66 | React.HTMLAttributes
67 | >(({ className, ...props }, ref) => (
68 |
73 | ))
74 | CardFooter.displayName = "CardFooter"
75 |
76 | export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
77 |
--------------------------------------------------------------------------------
/components/ui/carousel.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import useEmblaCarousel, {
5 | type UseEmblaCarouselType,
6 | } from "embla-carousel-react"
7 | import { ArrowLeft, ArrowRight } from "lucide-react"
8 |
9 | import { cn } from "@/lib/utils"
10 | import { Button } from "@/components/ui/button"
11 |
12 | type CarouselApi = UseEmblaCarouselType[1]
13 | type UseCarouselParameters = Parameters
14 | type CarouselOptions = UseCarouselParameters[0]
15 | type CarouselPlugin = UseCarouselParameters[1]
16 |
17 | type CarouselProps = {
18 | opts?: CarouselOptions
19 | plugins?: CarouselPlugin
20 | orientation?: "horizontal" | "vertical"
21 | setApi?: (api: CarouselApi) => void
22 | }
23 |
24 | type CarouselContextProps = {
25 | carouselRef: ReturnType[0]
26 | api: ReturnType[1]
27 | scrollPrev: () => void
28 | scrollNext: () => void
29 | canScrollPrev: boolean
30 | canScrollNext: boolean
31 | } & CarouselProps
32 |
33 | const CarouselContext = React.createContext(null)
34 |
35 | function useCarousel() {
36 | const context = React.useContext(CarouselContext)
37 |
38 | if (!context) {
39 | throw new Error("useCarousel must be used within a ")
40 | }
41 |
42 | return context
43 | }
44 |
45 | const Carousel = React.forwardRef<
46 | HTMLDivElement,
47 | React.HTMLAttributes & CarouselProps
48 | >(
49 | (
50 | {
51 | orientation = "horizontal",
52 | opts,
53 | setApi,
54 | plugins,
55 | className,
56 | children,
57 | ...props
58 | },
59 | ref
60 | ) => {
61 | const [carouselRef, api] = useEmblaCarousel(
62 | {
63 | ...opts,
64 | axis: orientation === "horizontal" ? "x" : "y",
65 | },
66 | plugins
67 | )
68 | const [canScrollPrev, setCanScrollPrev] = React.useState(false)
69 | const [canScrollNext, setCanScrollNext] = React.useState(false)
70 |
71 | const onSelect = React.useCallback((api: CarouselApi) => {
72 | if (!api) {
73 | return
74 | }
75 |
76 | setCanScrollPrev(api.canScrollPrev())
77 | setCanScrollNext(api.canScrollNext())
78 | }, [])
79 |
80 | const scrollPrev = React.useCallback(() => {
81 | api?.scrollPrev()
82 | }, [api])
83 |
84 | const scrollNext = React.useCallback(() => {
85 | api?.scrollNext()
86 | }, [api])
87 |
88 | const handleKeyDown = React.useCallback(
89 | (event: React.KeyboardEvent) => {
90 | if (event.key === "ArrowLeft") {
91 | event.preventDefault()
92 | scrollPrev()
93 | } else if (event.key === "ArrowRight") {
94 | event.preventDefault()
95 | scrollNext()
96 | }
97 | },
98 | [scrollPrev, scrollNext]
99 | )
100 |
101 | React.useEffect(() => {
102 | if (!api || !setApi) {
103 | return
104 | }
105 |
106 | setApi(api)
107 | }, [api, setApi])
108 |
109 | React.useEffect(() => {
110 | if (!api) {
111 | return
112 | }
113 |
114 | onSelect(api)
115 | api.on("reInit", onSelect)
116 | api.on("select", onSelect)
117 |
118 | return () => {
119 | api?.off("select", onSelect)
120 | }
121 | }, [api, onSelect])
122 |
123 | return (
124 |
137 |
145 | {children}
146 |
147 |
148 | )
149 | }
150 | )
151 | Carousel.displayName = "Carousel"
152 |
153 | const CarouselContent = React.forwardRef<
154 | HTMLDivElement,
155 | React.HTMLAttributes
156 | >(({ className, ...props }, ref) => {
157 | const { carouselRef, orientation } = useCarousel()
158 |
159 | return (
160 |
171 | )
172 | })
173 | CarouselContent.displayName = "CarouselContent"
174 |
175 | const CarouselItem = React.forwardRef<
176 | HTMLDivElement,
177 | React.HTMLAttributes
178 | >(({ className, ...props }, ref) => {
179 | const { orientation } = useCarousel()
180 |
181 | return (
182 |
193 | )
194 | })
195 | CarouselItem.displayName = "CarouselItem"
196 |
197 | const CarouselPrevious = React.forwardRef<
198 | HTMLButtonElement,
199 | React.ComponentProps
200 | >(({ className, variant = "outline", size = "icon", ...props }, ref) => {
201 | const { orientation, scrollPrev, canScrollPrev } = useCarousel()
202 |
203 | return (
204 |
222 | )
223 | })
224 | CarouselPrevious.displayName = "CarouselPrevious"
225 |
226 | const CarouselNext = React.forwardRef<
227 | HTMLButtonElement,
228 | React.ComponentProps
229 | >(({ className, variant = "outline", size = "icon", ...props }, ref) => {
230 | const { orientation, scrollNext, canScrollNext } = useCarousel()
231 |
232 | return (
233 |
251 | )
252 | })
253 | CarouselNext.displayName = "CarouselNext"
254 |
255 | export {
256 | type CarouselApi,
257 | Carousel,
258 | CarouselContent,
259 | CarouselItem,
260 | CarouselPrevious,
261 | CarouselNext,
262 | }
263 |
--------------------------------------------------------------------------------
/components/ui/dialog.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as DialogPrimitive from "@radix-ui/react-dialog"
5 | import { X } from "lucide-react"
6 |
7 | import { cn } from "@/lib/utils"
8 |
9 | const Dialog = DialogPrimitive.Root
10 |
11 | const DialogTrigger = DialogPrimitive.Trigger
12 |
13 | const DialogPortal = DialogPrimitive.Portal
14 |
15 | const DialogClose = DialogPrimitive.Close
16 |
17 | const DialogOverlay = React.forwardRef<
18 | React.ElementRef,
19 | React.ComponentPropsWithoutRef
20 | >(({ className, ...props }, ref) => (
21 |
29 | ))
30 | DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
31 |
32 | const DialogContent = React.forwardRef<
33 | React.ElementRef,
34 | React.ComponentPropsWithoutRef
35 | >(({ className, children, ...props }, ref) => (
36 |
37 |
38 |
46 | {children}
47 |
48 |
49 | Close
50 |
51 |
52 |
53 | ))
54 | DialogContent.displayName = DialogPrimitive.Content.displayName
55 |
56 | const DialogHeader = ({
57 | className,
58 | ...props
59 | }: React.HTMLAttributes) => (
60 |
67 | )
68 | DialogHeader.displayName = "DialogHeader"
69 |
70 | const DialogFooter = ({
71 | className,
72 | ...props
73 | }: React.HTMLAttributes) => (
74 |
81 | )
82 | DialogFooter.displayName = "DialogFooter"
83 |
84 | const DialogTitle = React.forwardRef<
85 | React.ElementRef,
86 | React.ComponentPropsWithoutRef
87 | >(({ className, ...props }, ref) => (
88 |
96 | ))
97 | DialogTitle.displayName = DialogPrimitive.Title.displayName
98 |
99 | const DialogDescription = React.forwardRef<
100 | React.ElementRef,
101 | React.ComponentPropsWithoutRef
102 | >(({ className, ...props }, ref) => (
103 |
108 | ))
109 | DialogDescription.displayName = DialogPrimitive.Description.displayName
110 |
111 | export {
112 | Dialog,
113 | DialogPortal,
114 | DialogOverlay,
115 | DialogTrigger,
116 | DialogClose,
117 | DialogContent,
118 | DialogHeader,
119 | DialogFooter,
120 | DialogTitle,
121 | DialogDescription,
122 | }
123 |
--------------------------------------------------------------------------------
/components/ui/drawer.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import { Drawer as DrawerPrimitive } from "vaul"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const Drawer = ({
9 | shouldScaleBackground = true,
10 | ...props
11 | }: React.ComponentProps) => (
12 |
16 | )
17 | Drawer.displayName = "Drawer"
18 |
19 | const DrawerTrigger = DrawerPrimitive.Trigger
20 |
21 | const DrawerPortal = DrawerPrimitive.Portal
22 |
23 | const DrawerClose = DrawerPrimitive.Close
24 |
25 | const DrawerOverlay = React.forwardRef<
26 | React.ElementRef,
27 | React.ComponentPropsWithoutRef
28 | >(({ className, ...props }, ref) => (
29 |
34 | ))
35 | DrawerOverlay.displayName = DrawerPrimitive.Overlay.displayName
36 |
37 | const DrawerContent = React.forwardRef<
38 | React.ElementRef,
39 | React.ComponentPropsWithoutRef
40 | >(({ className, children, ...props }, ref) => (
41 |
42 |
43 |
51 |
52 | {children}
53 |
54 |
55 | ))
56 | DrawerContent.displayName = "DrawerContent"
57 |
58 | const DrawerHeader = ({
59 | className,
60 | ...props
61 | }: React.HTMLAttributes) => (
62 |
66 | )
67 | DrawerHeader.displayName = "DrawerHeader"
68 |
69 | const DrawerFooter = ({
70 | className,
71 | ...props
72 | }: React.HTMLAttributes) => (
73 |
77 | )
78 | DrawerFooter.displayName = "DrawerFooter"
79 |
80 | const DrawerTitle = React.forwardRef<
81 | React.ElementRef,
82 | React.ComponentPropsWithoutRef
83 | >(({ className, ...props }, ref) => (
84 |
92 | ))
93 | DrawerTitle.displayName = DrawerPrimitive.Title.displayName
94 |
95 | const DrawerDescription = React.forwardRef<
96 | React.ElementRef,
97 | React.ComponentPropsWithoutRef
98 | >(({ className, ...props }, ref) => (
99 |
104 | ))
105 | DrawerDescription.displayName = DrawerPrimitive.Description.displayName
106 |
107 | export {
108 | Drawer,
109 | DrawerPortal,
110 | DrawerOverlay,
111 | DrawerTrigger,
112 | DrawerClose,
113 | DrawerContent,
114 | DrawerHeader,
115 | DrawerFooter,
116 | DrawerTitle,
117 | DrawerDescription,
118 | }
119 |
--------------------------------------------------------------------------------
/components/ui/dropdown-menu.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
5 | import { Check, ChevronRight, Circle } from "lucide-react"
6 |
7 | import { cn } from "@/lib/utils"
8 |
9 | const DropdownMenu = DropdownMenuPrimitive.Root
10 |
11 | const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
12 |
13 | const DropdownMenuGroup = DropdownMenuPrimitive.Group
14 |
15 | const DropdownMenuPortal = DropdownMenuPrimitive.Portal
16 |
17 | const DropdownMenuSub = DropdownMenuPrimitive.Sub
18 |
19 | const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
20 |
21 | const DropdownMenuSubTrigger = React.forwardRef<
22 | React.ElementRef,
23 | React.ComponentPropsWithoutRef & {
24 | inset?: boolean
25 | }
26 | >(({ className, inset, children, ...props }, ref) => (
27 |
36 | {children}
37 |
38 |
39 | ))
40 | DropdownMenuSubTrigger.displayName =
41 | DropdownMenuPrimitive.SubTrigger.displayName
42 |
43 | const DropdownMenuSubContent = React.forwardRef<
44 | React.ElementRef,
45 | React.ComponentPropsWithoutRef
46 | >(({ className, ...props }, ref) => (
47 |
55 | ))
56 | DropdownMenuSubContent.displayName =
57 | DropdownMenuPrimitive.SubContent.displayName
58 |
59 | const DropdownMenuContent = React.forwardRef<
60 | React.ElementRef,
61 | React.ComponentPropsWithoutRef
62 | >(({ className, sideOffset = 4, ...props }, ref) => (
63 |
64 |
74 |
75 | ))
76 | DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
77 |
78 | const DropdownMenuItem = React.forwardRef<
79 | React.ElementRef,
80 | React.ComponentPropsWithoutRef & {
81 | inset?: boolean
82 | }
83 | >(({ className, inset, ...props }, ref) => (
84 | svg]:size-4 [&>svg]:shrink-0",
88 | inset && "pl-8",
89 | className
90 | )}
91 | {...props}
92 | />
93 | ))
94 | DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
95 |
96 | const DropdownMenuCheckboxItem = React.forwardRef<
97 | React.ElementRef,
98 | React.ComponentPropsWithoutRef
99 | >(({ className, children, checked, ...props }, ref) => (
100 |
109 |
110 |
111 |
112 |
113 |
114 | {children}
115 |
116 | ))
117 | DropdownMenuCheckboxItem.displayName =
118 | DropdownMenuPrimitive.CheckboxItem.displayName
119 |
120 | const DropdownMenuRadioItem = React.forwardRef<
121 | React.ElementRef,
122 | React.ComponentPropsWithoutRef
123 | >(({ className, children, ...props }, ref) => (
124 |
132 |
133 |
134 |
135 |
136 |
137 | {children}
138 |
139 | ))
140 | DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName
141 |
142 | const DropdownMenuLabel = React.forwardRef<
143 | React.ElementRef,
144 | React.ComponentPropsWithoutRef & {
145 | inset?: boolean
146 | }
147 | >(({ className, inset, ...props }, ref) => (
148 |
157 | ))
158 | DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
159 |
160 | const DropdownMenuSeparator = React.forwardRef<
161 | React.ElementRef,
162 | React.ComponentPropsWithoutRef
163 | >(({ className, ...props }, ref) => (
164 |
169 | ))
170 | DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
171 |
172 | const DropdownMenuShortcut = ({
173 | className,
174 | ...props
175 | }: React.HTMLAttributes) => {
176 | return (
177 |
181 | )
182 | }
183 | DropdownMenuShortcut.displayName = "DropdownMenuShortcut"
184 |
185 | export {
186 | DropdownMenu,
187 | DropdownMenuTrigger,
188 | DropdownMenuContent,
189 | DropdownMenuItem,
190 | DropdownMenuCheckboxItem,
191 | DropdownMenuRadioItem,
192 | DropdownMenuLabel,
193 | DropdownMenuSeparator,
194 | DropdownMenuShortcut,
195 | DropdownMenuGroup,
196 | DropdownMenuPortal,
197 | DropdownMenuSub,
198 | DropdownMenuSubContent,
199 | DropdownMenuSubTrigger,
200 | DropdownMenuRadioGroup,
201 | }
202 |
--------------------------------------------------------------------------------
/components/ui/input.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | const Input = React.forwardRef>(
6 | ({ className, type, ...props }, ref) => {
7 | return (
8 |
17 | )
18 | }
19 | )
20 | Input.displayName = "Input"
21 |
22 | export { Input }
23 |
--------------------------------------------------------------------------------
/components/ui/label.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as LabelPrimitive from "@radix-ui/react-label"
5 | import { cva, type VariantProps } from "class-variance-authority"
6 |
7 | import { cn } from "@/lib/utils"
8 |
9 | const labelVariants = cva(
10 | "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
11 | )
12 |
13 | const Label = React.forwardRef<
14 | React.ElementRef,
15 | React.ComponentPropsWithoutRef &
16 | VariantProps
17 | >(({ className, ...props }, ref) => (
18 |
23 | ))
24 | Label.displayName = LabelPrimitive.Root.displayName
25 |
26 | export { Label }
27 |
--------------------------------------------------------------------------------
/components/ui/logo.tsx:
--------------------------------------------------------------------------------
1 | import Link from "next/link";
2 |
3 | interface LogoProps {
4 | className?: string;
5 | }
6 |
7 | export function Logo({ className = "" }: LogoProps) {
8 | return (
9 |
10 |
11 |
12 | rSearch
13 |
27 |
28 |
29 |
30 | );
31 | }
--------------------------------------------------------------------------------
/components/ui/meteors.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { useEffect, useState } from "react";
4 |
5 | import { cn } from "@/lib/utils";
6 |
7 | interface MeteorsProps extends React.HTMLAttributes {
8 | number?: number;
9 | }
10 |
11 | export const Meteors = ({ number = 20, ...props }: MeteorsProps) => {
12 | const [meteorStyles, setMeteorStyles] = useState>([]);
13 |
14 | useEffect(() => {
15 | const styles = [...new Array(number)].map(() => ({
16 | top: Math.floor(Math.random() * 50) + "vh",
17 | left: Math.floor(Math.random() * 100) + "vw",
18 | animationDelay: `${Math.random() * 1 + 0.2}s`,
19 | animationDuration: `${Math.floor(Math.random() * 8 + 2)}s`,
20 | }));
21 | setMeteorStyles(styles);
22 | }, [number]);
23 |
24 | return (
25 |
26 | {meteorStyles.map((style, idx) => (
27 |
35 | {/* Meteor Tail */}
36 |
37 |
38 | ))}
39 |
40 | );
41 | };
42 |
--------------------------------------------------------------------------------
/components/ui/mobile-header.tsx:
--------------------------------------------------------------------------------
1 | import { X } from "lucide-react";
2 | import { Button } from "./button";
3 | import {
4 | Sheet,
5 | SheetContent,
6 | SheetTrigger,
7 | SheetClose,
8 | SheetTitle,
9 | SheetDescription,
10 | } from "./sheet";
11 | import { Sidebar } from "./sidebar";
12 |
13 | export function MobileHeader() {
14 | return (
15 |
16 |
17 |
18 |
24 |
25 |
26 | Navigation Menu
27 | Access navigation options
28 |
29 |
30 | Close menu
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 | );
39 | }
40 |
--------------------------------------------------------------------------------
/components/ui/radio-group.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as RadioGroupPrimitive from "@radix-ui/react-radio-group"
5 | import { Circle } from "lucide-react"
6 |
7 | import { cn } from "@/lib/utils"
8 |
9 | const RadioGroup = React.forwardRef<
10 | React.ElementRef,
11 | React.ComponentPropsWithoutRef
12 | >(({ className, ...props }, ref) => {
13 | return (
14 |
19 | )
20 | })
21 | RadioGroup.displayName = RadioGroupPrimitive.Root.displayName
22 |
23 | const RadioGroupItem = React.forwardRef<
24 | React.ElementRef,
25 | React.ComponentPropsWithoutRef
26 | >(({ className, ...props }, ref) => {
27 | return (
28 |
36 |
37 |
38 |
39 |
40 | )
41 | })
42 | RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName
43 |
44 | export { RadioGroup, RadioGroupItem }
45 |
--------------------------------------------------------------------------------
/components/ui/select.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as SelectPrimitive from "@radix-ui/react-select"
5 | import { Check, ChevronDown, ChevronUp } from "lucide-react"
6 |
7 | import { cn } from "@/lib/utils"
8 |
9 | const Select = SelectPrimitive.Root
10 |
11 | const SelectGroup = SelectPrimitive.Group
12 |
13 | const SelectValue = SelectPrimitive.Value
14 |
15 | const SelectTrigger = React.forwardRef<
16 | React.ElementRef,
17 | React.ComponentPropsWithoutRef
18 | >(({ className, children, ...props }, ref) => (
19 | span]:line-clamp-1",
23 | className
24 | )}
25 | {...props}
26 | >
27 | {children}
28 |
29 |
30 |
31 |
32 | ))
33 | SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
34 |
35 | const SelectScrollUpButton = React.forwardRef<
36 | React.ElementRef,
37 | React.ComponentPropsWithoutRef
38 | >(({ className, ...props }, ref) => (
39 |
47 |
48 |
49 | ))
50 | SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName
51 |
52 | const SelectScrollDownButton = React.forwardRef<
53 | React.ElementRef,
54 | React.ComponentPropsWithoutRef
55 | >(({ className, ...props }, ref) => (
56 |
64 |
65 |
66 | ))
67 | SelectScrollDownButton.displayName =
68 | SelectPrimitive.ScrollDownButton.displayName
69 |
70 | const SelectContent = React.forwardRef<
71 | React.ElementRef,
72 | React.ComponentPropsWithoutRef
73 | >(({ className, children, position = "popper", ...props }, ref) => (
74 |
75 |
86 |
87 |
94 | {children}
95 |
96 |
97 |
98 |
99 | ))
100 | SelectContent.displayName = SelectPrimitive.Content.displayName
101 |
102 | const SelectLabel = React.forwardRef<
103 | React.ElementRef,
104 | React.ComponentPropsWithoutRef
105 | >(({ className, ...props }, ref) => (
106 |
111 | ))
112 | SelectLabel.displayName = SelectPrimitive.Label.displayName
113 |
114 | const SelectItem = React.forwardRef<
115 | React.ElementRef,
116 | React.ComponentPropsWithoutRef
117 | >(({ className, children, ...props }, ref) => (
118 |
126 |
127 |
128 |
129 |
130 |
131 | {children}
132 |
133 | ))
134 | SelectItem.displayName = SelectPrimitive.Item.displayName
135 |
136 | const SelectSeparator = React.forwardRef<
137 | React.ElementRef,
138 | React.ComponentPropsWithoutRef
139 | >(({ className, ...props }, ref) => (
140 |
145 | ))
146 | SelectSeparator.displayName = SelectPrimitive.Separator.displayName
147 |
148 | export {
149 | Select,
150 | SelectGroup,
151 | SelectValue,
152 | SelectTrigger,
153 | SelectContent,
154 | SelectLabel,
155 | SelectItem,
156 | SelectSeparator,
157 | SelectScrollUpButton,
158 | SelectScrollDownButton,
159 | }
160 |
--------------------------------------------------------------------------------
/components/ui/separator.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as SeparatorPrimitive from "@radix-ui/react-separator"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const Separator = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(
12 | (
13 | { className, orientation = "horizontal", decorative = true, ...props },
14 | ref
15 | ) => (
16 |
27 | )
28 | )
29 | Separator.displayName = SeparatorPrimitive.Root.displayName
30 |
31 | export { Separator }
32 |
--------------------------------------------------------------------------------
/components/ui/sheet.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as SheetPrimitive from "@radix-ui/react-dialog"
5 | import { cva, type VariantProps } from "class-variance-authority"
6 | import { X } from "lucide-react"
7 |
8 | import { cn } from "@/lib/utils"
9 |
10 | const Sheet = SheetPrimitive.Root
11 |
12 | const SheetTrigger = SheetPrimitive.Trigger
13 |
14 | const SheetClose = SheetPrimitive.Close
15 |
16 | const SheetPortal = SheetPrimitive.Portal
17 |
18 | const SheetOverlay = React.forwardRef<
19 | React.ElementRef,
20 | React.ComponentPropsWithoutRef
21 | >(({ className, ...props }, ref) => (
22 |
30 | ))
31 | SheetOverlay.displayName = SheetPrimitive.Overlay.displayName
32 |
33 | const sheetVariants = cva(
34 | "fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500 data-[state=open]:animate-in data-[state=closed]:animate-out",
35 | {
36 | variants: {
37 | side: {
38 | top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",
39 | bottom:
40 | "inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
41 | left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm",
42 | right:
43 | "inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm",
44 | },
45 | },
46 | defaultVariants: {
47 | side: "right",
48 | },
49 | }
50 | )
51 |
52 | interface SheetContentProps
53 | extends React.ComponentPropsWithoutRef,
54 | VariantProps {}
55 |
56 | const SheetContent = React.forwardRef<
57 | React.ElementRef,
58 | SheetContentProps
59 | >(({ side = "right", className, children, ...props }, ref) => (
60 |
61 |
62 |
67 |
68 |
69 | Close
70 |
71 | {children}
72 |
73 |
74 | ))
75 | SheetContent.displayName = SheetPrimitive.Content.displayName
76 |
77 | const SheetHeader = ({
78 | className,
79 | ...props
80 | }: React.HTMLAttributes) => (
81 |
88 | )
89 | SheetHeader.displayName = "SheetHeader"
90 |
91 | const SheetFooter = ({
92 | className,
93 | ...props
94 | }: React.HTMLAttributes) => (
95 |
102 | )
103 | SheetFooter.displayName = "SheetFooter"
104 |
105 | const SheetTitle = React.forwardRef<
106 | React.ElementRef,
107 | React.ComponentPropsWithoutRef
108 | >(({ className, ...props }, ref) => (
109 |
114 | ))
115 | SheetTitle.displayName = SheetPrimitive.Title.displayName
116 |
117 | const SheetDescription = React.forwardRef<
118 | React.ElementRef,
119 | React.ComponentPropsWithoutRef
120 | >(({ className, ...props }, ref) => (
121 |
126 | ))
127 | SheetDescription.displayName = SheetPrimitive.Description.displayName
128 |
129 | export {
130 | Sheet,
131 | SheetPortal,
132 | SheetOverlay,
133 | SheetTrigger,
134 | SheetClose,
135 | SheetContent,
136 | SheetHeader,
137 | SheetFooter,
138 | SheetTitle,
139 | SheetDescription,
140 | }
141 |
--------------------------------------------------------------------------------
/components/ui/sidebar.tsx:
--------------------------------------------------------------------------------
1 | import Link from "next/link";
2 | import { Search, Settings } from "lucide-react";
3 | import { cn } from "@/lib/utils";
4 |
5 | interface SidebarProps {
6 | className?: string;
7 | isMobile?: boolean;
8 | }
9 |
10 | export function Sidebar({ className, isMobile }: SidebarProps) {
11 | return (
12 |
17 | {/* Top - Logo */}
18 |
19 |
33 |
34 |
35 | {/* Navigation Links */}
36 |
37 |
41 |
42 | Search
43 |
44 |
45 |
46 | {/* Bottom - Settings */}
47 |
51 |
52 |
Settings
53 |
54 |
55 | );
56 | }
57 |
--------------------------------------------------------------------------------
/components/ui/skeleton.tsx:
--------------------------------------------------------------------------------
1 | import { cn } from "@/lib/utils"
2 |
3 | function Skeleton({
4 | className,
5 | ...props
6 | }: React.HTMLAttributes) {
7 | return (
8 |
12 | )
13 | }
14 |
15 | export { Skeleton }
16 |
--------------------------------------------------------------------------------
/components/ui/switch.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as SwitchPrimitives from "@radix-ui/react-switch"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const Switch = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(({ className, ...props }, ref) => (
12 |
20 |
25 |
26 | ))
27 | Switch.displayName = SwitchPrimitives.Root.displayName
28 |
29 | export { Switch }
30 |
--------------------------------------------------------------------------------
/components/ui/tabs.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as TabsPrimitive from "@radix-ui/react-tabs"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const Tabs = TabsPrimitive.Root
9 |
10 | const TabsList = React.forwardRef<
11 | React.ElementRef,
12 | React.ComponentPropsWithoutRef
13 | >(({ className, ...props }, ref) => (
14 |
22 | ))
23 | TabsList.displayName = TabsPrimitive.List.displayName
24 |
25 | const TabsTrigger = React.forwardRef<
26 | React.ElementRef,
27 | React.ComponentPropsWithoutRef
28 | >(({ className, ...props }, ref) => (
29 |
37 | ))
38 | TabsTrigger.displayName = TabsPrimitive.Trigger.displayName
39 |
40 | const TabsContent = React.forwardRef<
41 | React.ElementRef,
42 | React.ComponentPropsWithoutRef
43 | >(({ className, ...props }, ref) => (
44 |
52 | ))
53 | TabsContent.displayName = TabsPrimitive.Content.displayName
54 |
55 | export { Tabs, TabsList, TabsTrigger, TabsContent }
56 |
--------------------------------------------------------------------------------
/components/ui/textarea.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | const Textarea = React.forwardRef<
6 | HTMLTextAreaElement,
7 | React.ComponentProps<"textarea">
8 | >(({ className, ...props }, ref) => {
9 | return (
10 |
18 | )
19 | })
20 | Textarea.displayName = "Textarea"
21 |
22 | export { Textarea }
23 |
--------------------------------------------------------------------------------
/components/ui/toggle.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as TogglePrimitive from "@radix-ui/react-toggle"
5 | import { cva, type VariantProps } from "class-variance-authority"
6 |
7 | import { cn } from "@/lib/utils"
8 |
9 | const toggleVariants = cva(
10 | "inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
11 | {
12 | variants: {
13 | variant: {
14 | default: "bg-transparent",
15 | outline:
16 | "border border-input bg-transparent shadow-sm hover:bg-accent hover:text-accent-foreground",
17 | },
18 | size: {
19 | default: "h-9 px-2 min-w-9",
20 | sm: "h-8 px-1.5 min-w-8",
21 | lg: "h-10 px-2.5 min-w-10",
22 | },
23 | },
24 | defaultVariants: {
25 | variant: "default",
26 | size: "default",
27 | },
28 | }
29 | )
30 |
31 | const Toggle = React.forwardRef<
32 | React.ElementRef,
33 | React.ComponentPropsWithoutRef &
34 | VariantProps
35 | >(({ className, variant, size, ...props }, ref) => (
36 |
41 | ))
42 |
43 | Toggle.displayName = TogglePrimitive.Root.displayName
44 |
45 | export { Toggle, toggleVariants }
46 |
--------------------------------------------------------------------------------
/components/ui/tooltip.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as TooltipPrimitive from "@radix-ui/react-tooltip"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const TooltipProvider = TooltipPrimitive.Provider
9 |
10 | const Tooltip = TooltipPrimitive.Root
11 |
12 | const TooltipTrigger = TooltipPrimitive.Trigger
13 |
14 | const TooltipContent = React.forwardRef<
15 | React.ElementRef,
16 | React.ComponentPropsWithoutRef
17 | >(({ className, sideOffset = 4, ...props }, ref) => (
18 |
19 |
28 |
29 | ))
30 | TooltipContent.displayName = TooltipPrimitive.Content.displayName
31 |
32 | export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
33 |
--------------------------------------------------------------------------------
/data/serper_samples/apple inc-us-en-true-1-10-news---------.json:
--------------------------------------------------------------------------------
1 | {
2 | "searchParameters": {
3 | "q": "apple inc",
4 | "type": "news",
5 | "engine": "google"
6 | },
7 | "news": [
8 | {
9 | "title": "Apple stock hit with two downgrades on weak iPhone sales, AI outlook",
10 | "link": "https://finance.yahoo.com/news/apple-stock-hit-with-two-downgrades-on-weak-iphone-sales-ai-outlook-180001454.html",
11 | "snippet": "Jefferies analyst Edison Lee downgraded the investment bank's rating on Apple's (AAPL) stock to Underperform and decreased his price target...",
12 | "date": "2 days ago",
13 | "source": "Yahoo Finance",
14 | "imageUrl": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcS0j_5Cp4H8z0xnE9IITkkNMgGrCVj0tCRLDry5JLfTACmr7Bvm4ZPaMLI&usqp=CAI&s",
15 | "position": 1
16 | },
17 | {
18 | "title": "Trump Blasts EU Regulators for Targeting Apple, Google, Meta",
19 | "link": "https://www.bloomberg.com/news/articles/2025-01-23/trump-blasts-eu-regulators-for-targeting-apple-google-meta",
20 | "snippet": "US President Donald Trump blasted European Union regulators for targeting Apple Inc., Alphabet Inc.'s Google and Meta Platforms Inc.,...",
21 | "date": "3 hours ago",
22 | "source": "Bloomberg.com",
23 | "imageUrl": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQZYBcHjjtxEzf86x0E-ey4TY_8Bxp7BG0s-4xMrljBvBsIrcRwG6hcAzk&usqp=CAI&s",
24 | "position": 2
25 | },
26 | {
27 | "title": "Apple, Google get India's request to open app stores for government-backed app suite - report",
28 | "link": "https://seekingalpha.com/news/4397743-apple-google-get-indias-request-to-open-app-stores-for-government-backed-app-suite",
29 | "snippet": "Indian government requests Apple and Google to include GOV.in app store in their online marketplaces.",
30 | "date": "10 hours ago",
31 | "source": "Seeking Alpha",
32 | "imageUrl": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQWl-tKtltsYJQa5nb5FpXlU1sxw66hcN9mDHxTbzYSHA15U7CHgwDbafY&usqp=CAI&s",
33 | "position": 3
34 | },
35 | {
36 | "title": "Apple fear overdone with turnaround in the cards, Wedbush says",
37 | "link": "https://www.investing.com/news/stock-market-news/apple-fear-overdone-with-turnaround-in-the-cards-wedbush-says-3825949",
38 | "snippet": "Investing.com-- Wedbush said on Wednesday that recent weakness in Apple Inc (NASDAQ:AAPL) shares- amid a flurry of ratings downgrades and...",
39 | "date": "21 hours ago",
40 | "source": "Investing.com",
41 | "imageUrl": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcT3bqupxXgxTenKbKDGE0mB1pH2wef5AWQgl7lwzbLRKkEN4LLITZa53DI&usqp=CAI&s",
42 | "position": 4
43 | },
44 | {
45 | "title": "Apple Fails to Escape Class Action Over Gender Pay Disparity",
46 | "link": "https://news.bloomberglaw.com/litigation/apple-fails-to-escape-class-action-over-gender-pay-disparity",
47 | "snippet": "Apple Inc. was unable to convince a California state court judge to toss out a proposed class action alleging the tech giant systematically...",
48 | "date": "23 hours ago",
49 | "source": "Bloomberg Law News",
50 | "imageUrl": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSnwHzygTZg1avhKP4jpaWAf-aSH_09F75sRrH2U0lePcl_O1IzFJHkGns&usqp=CAI&s",
51 | "position": 5
52 | },
53 | {
54 | "title": "Apple Made a Major Change to Apple Intelligence that Some People Aren’t Going to Like",
55 | "link": "https://www.inc.com/jason-aten/major-change-to-apple-intelligence-that-some-people-arent-going-to-like/91111009",
56 | "snippet": "Apple is enabling its AI features for everyone with a compatible device, like it or not.",
57 | "date": "9 hours ago",
58 | "source": "Inc.com",
59 | "imageUrl": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTmXDKfgC2xhvv5UeeQk0VYtDSSg5cZdW4oeoYheZ_PlNLXC81nQvE0FGE&usqp=CAI&s",
60 | "position": 6
61 | },
62 | {
63 | "title": "Greenlight Capital’s Updates on Apple (AAPL)",
64 | "link": "https://finance.yahoo.com/news/greenlight-capital-updates-apple-aapl-144042852.html",
65 | "snippet": "Greenlight Capital, an investment management firm, released its fourth-quarter 2024 investor letter. A copy of the letter can be downloaded...",
66 | "date": "6 hours ago",
67 | "source": "Yahoo Finance",
68 | "imageUrl": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTRrhnZodSzXvxaDxBM9MhAElSbEo1w5ffWKpbUu_ROC3pp9gdr2LKIsBw&usqp=CAI&s",
69 | "position": 7
70 | },
71 | {
72 | "title": "Samsung to Beat Apple to Ultrathin Phone With New S25 Edge",
73 | "link": "https://finance.yahoo.com/news/samsung-beat-apple-ultrathin-phone-191115119.html",
74 | "snippet": "(Bloomberg) -- Samsung Electronics Co. plans to launch an ultrathin version of its Galaxy S25 phone in the first half of this year,...",
75 | "date": "19 hours ago",
76 | "source": "Yahoo Finance",
77 | "imageUrl": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQD2edTzl2FrdZm-SQxOkmxGiDVVJAgZKWiGhTX92E11vp9EGnuhPPwEF8&usqp=CAI&s",
78 | "position": 8
79 | },
80 | {
81 | "title": "Jefferies Downgrades Apple Inc. (AAPL) to Underperform, Citing Weak Revenue Outlook and Subdued AI Focus",
82 | "link": "https://finance.yahoo.com/news/jefferies-downgrades-apple-inc-aapl-025432388.html",
83 | "snippet": "We recently compiled a list of the 11 AI Stocks That Should Be On Your Watchlist. In this article, we are going to take a look at where...",
84 | "date": "18 hours ago",
85 | "source": "Yahoo Finance",
86 | "imageUrl": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSmL-dvK_jQTtB7IHHlqNQs8LUlJSbKzKcKVt_-9MAuyt7OS_AAzV-3sFA&usqp=CAI&s",
87 | "position": 9
88 | },
89 | {
90 | "title": "Could Nvidia Unseat Apple as the Largest Company in the World in 2025?",
91 | "link": "https://finance.yahoo.com/news/could-nvidia-unseat-apple-largest-081200114.html",
92 | "snippet": "Nvidia (NASDAQ: NVDA) and Apple (NASDAQ: AAPL) are the two largest companies in the world by market capitalization. While Nvidia is valued...",
93 | "date": "13 hours ago",
94 | "source": "Yahoo Finance",
95 | "imageUrl": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSM38N61g_4BRsE4crQ31Rch9MYy2H89Crd4xByAB0dmYQnZddKeADGQrs&usqp=CAI&s",
96 | "position": 10
97 | }
98 | ],
99 | "credits": 1
100 | }
--------------------------------------------------------------------------------
/data/serper_samples/apple inc-us-en-true-1-10-places--.json:
--------------------------------------------------------------------------------
1 | {
2 | "searchParameters": {
3 | "q": "apple inc",
4 | "type": "places",
5 | "engine": "google"
6 | },
7 | "places": [
8 | {
9 | "position": 1,
10 | "title": "Apple Inc",
11 | "address": "2511 Laguna Blvd, Elk Grove, CA 95758",
12 | "latitude": 38.4293061,
13 | "longitude": -121.4791781,
14 | "rating": 3.8,
15 | "ratingCount": 143,
16 | "category": "Corporate office",
17 | "phoneNumber": "(916) 399-7173",
18 | "website": "http://apple.com/",
19 | "cid": "3088679940157317522"
20 | },
21 | {
22 | "position": 2,
23 | "title": "Apple Inc.",
24 | "address": "1070 E Arques Ave, Sunnyvale, CA 94085",
25 | "latitude": 37.378546899999996,
26 | "longitude": -122.00035210000001,
27 | "rating": 3.9,
28 | "ratingCount": 176,
29 | "category": "Corporate office",
30 | "phoneNumber": "(800) 692-7753",
31 | "website": "http://apple.com/",
32 | "cid": "14129532238805838308"
33 | },
34 | {
35 | "position": 3,
36 | "title": "Apple",
37 | "address": "10500 N De Anza Blvd, Cupertino, CA 95014",
38 | "latitude": 37.329240899999995,
39 | "longitude": -122.0310416,
40 | "rating": 3.5,
41 | "ratingCount": 181,
42 | "category": "Corporate office",
43 | "phoneNumber": "(408) 996-1010",
44 | "website": "http://apple.com/",
45 | "cid": "15878603320944575477"
46 | },
47 | {
48 | "position": 4,
49 | "title": "Apple Inc (SD01)",
50 | "address": "825 Stewart Dr, Sunnyvale, CA 94085",
51 | "latitude": 37.3842661,
52 | "longitude": -122.0093141,
53 | "website": "https://www.apple.com/",
54 | "cid": "14812030798474159337"
55 | },
56 | {
57 | "position": 5,
58 | "title": "Apple Corporation",
59 | "address": "2080 N Loop Rd, Alameda, CA 94502",
60 | "latitude": 37.7286218,
61 | "longitude": -122.24425589999998,
62 | "rating": 1,
63 | "ratingCount": 1,
64 | "category": "Corporate campus",
65 | "cid": "7770158157602578607"
66 | },
67 | {
68 | "position": 6,
69 | "title": "Apple Inc",
70 | "address": "2845 Bowers Ave, Santa Clara, CA 95051",
71 | "latitude": 37.3731279,
72 | "longitude": -121.97731660000001,
73 | "cid": "6422183385564473213"
74 | },
75 | {
76 | "position": 7,
77 | "title": "Apple Fashion Place",
78 | "address": "6191 S State St, Murray, UT 84107",
79 | "latitude": 40.6362464,
80 | "longitude": -111.8875216,
81 | "rating": 3.4,
82 | "ratingCount": 1700,
83 | "category": "Electronics store",
84 | "phoneNumber": "(801) 905-4410",
85 | "website": "https://www.apple.com/retail/fashionplace?cid=aos-us-seo-maps",
86 | "cid": "5255230387588482741"
87 | },
88 | {
89 | "position": 8,
90 | "title": "Apple Washington Square",
91 | "address": "9585 SW Washington Square Rd, Tigard, OR 97223",
92 | "latitude": 45.450642099999996,
93 | "longitude": -122.7809877,
94 | "rating": 3.5,
95 | "ratingCount": 1500,
96 | "category": "Electronics store",
97 | "phoneNumber": "(503) 495-2080",
98 | "website": "https://www.apple.com/retail/washingtonsquare?cid=aos-us-seo-maps",
99 | "cid": "4939156926991346432"
100 | },
101 | {
102 | "position": 9,
103 | "title": "Apple Arden Fair",
104 | "address": "1689 Arden Wy, Sacramento, CA 95815",
105 | "latitude": 38.6016246,
106 | "longitude": -121.4269831,
107 | "rating": 3.6,
108 | "ratingCount": 1400,
109 | "category": "Electronics store",
110 | "phoneNumber": "(916) 830-5610",
111 | "website": "https://www.apple.com/retail/ardenfair?cid=aos-us-seo-maps",
112 | "cid": "17024247645621449706"
113 | },
114 | {
115 | "position": 10,
116 | "title": "Apple",
117 | "address": "455 De Guigne Dr, Sunnyvale, CA 94085",
118 | "latitude": 37.384988899999996,
119 | "longitude": -122.0078603,
120 | "rating": 3.7,
121 | "ratingCount": 3,
122 | "category": "Corporate campus",
123 | "cid": "440065625417431977"
124 | }
125 | ],
126 | "credits": 1
127 | }
--------------------------------------------------------------------------------
/data/serper_samples/apple inc-us-en-true-1-10-videos--.json:
--------------------------------------------------------------------------------
1 | {
2 | "searchParameters": {
3 | "q": "apple inc",
4 | "type": "videos",
5 | "engine": "google"
6 | },
7 | "videos": [
8 | {
9 | "title": "🔥😧 APPLE Inc. The TRUTH 😨🔥",
10 | "link": "https://www.youtube.com/watch?v=P7slEUBcP0s",
11 | "snippet": "IF YOU LIKE THESE VIDEOS, YOU CAN MAKE A SMALL DONATION VIA PAYPAL or BITCOIN PAYPAL LINK: ...",
12 | "imageUrl": "https://i.ytimg.com/vi/P7slEUBcP0s/mqdefault.jpg?sqp=-oaymwEFCJQBEFM&rs=AMzJL3nb_ePHVd-QQzaoaRAWEkKdlEvDFw",
13 | "duration": "27:57",
14 | "source": "YouTube",
15 | "channel": "Theoria Apophasis",
16 | "date": "Feb 18, 2024",
17 | "position": 1
18 | },
19 | {
20 | "title": "An honest message to Apple inc",
21 | "link": "https://www.youtube.com/watch?v=QiClmSLuLXs",
22 | "snippet": "My email is louis@fighttorepair.org , and is always open. https://www.youtube.com/watch?v=3tRq5niOM7Q https://youtu.be/NVAmnV65_zw?t=254 ...",
23 | "imageUrl": "https://i.ytimg.com/vi/QiClmSLuLXs/mqdefault.jpg?sqp=-oaymwEFCJQBEFM&rs=AMzJL3kxSsNA-nX8yJQ3vgOGToVtRm6IJw",
24 | "duration": "10:07",
25 | "source": "YouTube",
26 | "channel": "Louis Rossmann",
27 | "date": "Jul 17, 2023",
28 | "position": 2
29 | },
30 | {
31 | "title": "Apple",
32 | "link": "https://www.youtube.com/apple",
33 | "snippet": "Apple Watch | Dear Apple | Apple. Apple. 1.5M views. 2 years ago. CC · iPhone ... Apple TV+ subscription: apple.co/_Apple-TV-Plus · 1:01 · Eva the Owlet — Season ...",
34 | "position": 3
35 | },
36 | {
37 | "title": "Apple at Work - Success Stories - Rituals",
38 | "link": "https://www.apple.com/business/enterprise/success-stories/retail/rituals/",
39 | "snippet": "As the company expands, every employee is given an Apple device to use at work. The Apple ecosystem is easy to deploy and manage at scale — and allows the team ...",
40 | "imageUrl": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcT2fQUt18rZ6bNh517ZXrBhglc_V-v41PM9eeZkLm2iYZwe&s",
41 | "source": "Apple",
42 | "date": "Apr 3, 2024",
43 | "position": 4
44 | },
45 | {
46 | "title": "Q&A With Phill - Who is the \"APPLE Inc.\" of the Keyboard ...",
47 | "link": "https://www.youtube.com/watch?v=9pC5_J3Xifs",
48 | "snippet": "Thinking of buying a keyboard or other gear after watching this video? Go here: http://azexperiment.com (if you cannot find what you are ...",
49 | "imageUrl": "https://i.ytimg.com/vi/9pC5_J3Xifs/mqdefault.jpg?sqp=-oaymwEFCJQBEFM&rs=AMzJL3kWLFCM_qypMbmnM7OhdPi6JCAQ7Q",
50 | "duration": "18:08",
51 | "source": "YouTube",
52 | "channel": "Phill C - azexperiment",
53 | "date": "Aug 10, 2024",
54 | "position": 5
55 | },
56 | {
57 | "title": "Apple - Great Company, Does it offer a great return?",
58 | "link": "https://www.youtube.com/watch?v=9MKNi9kJfyY",
59 | "snippet": "Apple is a great company, but does it offer a great return? Apple Inc. designs, manufactures, and markets smartphones, personal computers, ...",
60 | "imageUrl": "https://i.ytimg.com/vi/9MKNi9kJfyY/mqdefault.jpg?sqp=-oaymwEFCJQBEFM&rs=AMzJL3nVdStUv6TBPLdIKL-nPWunY5XEbQ",
61 | "duration": "9:33",
62 | "source": "YouTube",
63 | "channel": "Victor H Investing",
64 | "date": "May 18, 2024",
65 | "position": 6
66 | },
67 | {
68 | "title": "Education - Higher Education - Ideation & Development",
69 | "link": "https://www.apple.com/education/higher-education/success-stories/san-diego-state-university/",
70 | "snippet": "“And we're talking about students that aren't coming from computer science backgrounds.” Since the start of ZIP Launchpad, more than 400 student teams ...",
71 | "imageUrl": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTPN-iI1WpOyS-bZP-m__k8OcqFQKJg3g0bE1kvgEm-j2g7&s",
72 | "duration": "0:15",
73 | "source": "Apple",
74 | "date": "Aug 15, 2024",
75 | "position": 7
76 | },
77 | {
78 | "title": "Apple Watch Ultra 2",
79 | "link": "https://www.apple.com/apple-watch-ultra-2/",
80 | "snippet": "Designed in partnership with Huish Outdoors, the Oceanic+ app turns Ultra 2 into a fully capable dive computer running a Bühlmann decompression algorithm.",
81 | "imageUrl": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSvZ58COtZS4zIIJDd1-Tl0GIKbFqoffFlhLa20OtLcILDR&s",
82 | "source": "Apple",
83 | "date": "Sep 12, 2023",
84 | "position": 8
85 | },
86 | {
87 | "title": "Apple is poised to become the first publicly traded company ...",
88 | "link": "https://www.youtube.com/watch?v=5WCb5idu54c",
89 | "snippet": "Apple is poised to become the first publicly traded company worth four trillion dollars as it could be about to make a new bid to bring AI ...",
90 | "imageUrl": "https://i.ytimg.com/vi/5WCb5idu54c/mqdefault.jpg?sqp=-oaymwEFCJQBEFM&rs=AMzJL3kyvIB_D-YPNZDxDuL8Pca9azXCPQ",
91 | "duration": "1:38",
92 | "source": "YouTube",
93 | "channel": "NBC News",
94 | "date": "1 month ago",
95 | "position": 9
96 | },
97 | {
98 | "title": "Dear, Apple Inc. #viral #fyp",
99 | "link": "https://www.tiktok.com/@im.markk/video/7361996293503339818",
100 | "snippet": "1148 Likes, 34 Comments. TikTok video from Mark (@im.markk): “Dear, Apple Inc. #viral #fyp”. original sound - Mark.",
101 | "imageUrl": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcS2BESL371BdmqkSCNlavNGg0YHupiXmIprqYO1je_vEKBA&s",
102 | "duration": "0:15",
103 | "source": "TikTok",
104 | "channel": "im.markk",
105 | "date": "Apr 25, 2024",
106 | "position": 10
107 | }
108 | ],
109 | "credits": 1
110 | }
--------------------------------------------------------------------------------
/data/serper_samples/apple inc-us-en-true-1-20-scholar---------.json:
--------------------------------------------------------------------------------
1 | {
2 | "searchParameters": {
3 | "q": "apple inc",
4 | "type": "scholar",
5 | "engine": "google-scholar"
6 | },
7 | "organic": [
8 | {
9 | "title": "Apple Inc. in 2012",
10 | "link": "https://www.academia.edu/download/49560432/caso_Apple.pdf",
11 | "publicationInfo": "DB Yoffie, P Rossano - 2012 - academia.edu",
12 | "snippet": "… , Apple rozó la bancarrota en 1996. Entonces Jobs convirtió Apple Computer en Apple Inc., … En efecto, Apple se veía a sí misma como una empresa dedicada a los “dispositivos móviles…",
13 | "year": 2012,
14 | "citedBy": 81,
15 | "pdfUrl": "https://www.academia.edu/download/49560432/caso_Apple.pdf",
16 | "id": "yxbEyzVD4yYJ"
17 | },
18 | {
19 | "title": "Apple Inc.",
20 | "link": "https://books.google.com/books?hl=en&lr=&id=VILDEAAAQBAJ&oi=fnd&pg=PR7&dq=apple+inc&ots=UKWfvwa7tk&sig=MnADMv2H5bR0z5fNjutuROSV5ms",
21 | "publicationInfo": "JD O'Grady - 2008 - books.google.com",
22 | "snippet": "… Apple decided that the next Apple computer, the Apple III, would be designed for … Apple III computers were recalled, and Apple had to replace as many as 14,000 of the first Apple IIIs for …",
23 | "year": 2008,
24 | "citedBy": 36,
25 | "id": "srAwHfQ8-D0J"
26 | },
27 | {
28 | "title": "The Innovative Success that is Apple, Inc.",
29 | "link": "https://mds.marshall.edu/etd/418/",
30 | "publicationInfo": "K Johnson, Y Li, H Phan, J Singer, H Trinh - 2012 - mds.marshall.edu",
31 | "snippet": "… The company had been previously known as Apple Computer Inc.; however, Apple Inc. … towards consumer electronics and digital distribution (Apple, Inc., 2012). Apple Inc. now is in the …",
32 | "year": 2012,
33 | "citedBy": 50,
34 | "pdfUrl": "https://mds.marshall.edu/cgi/viewcontent.cgi?article=1420&context=etd",
35 | "id": "TgMnvBZ3gQAJ"
36 | },
37 | {
38 | "title": "Quantum strategy at apple inc",
39 | "link": "https://www.heracleous.org/uploads/1/1/2/9/11299865/quantum_strategy_-_org_dynamics.pdf",
40 | "publicationInfo": "L Heracleous - Organizational Dynamics, 2013 - heracleous.org",
41 | "snippet": "… at Apple would last, or gradually fizzle out. In this article, I take an in-depth look at the strategy and organization of Apple … In the next section we analyze how Apple Inc has accomplished …",
42 | "year": 2013,
43 | "citedBy": 64,
44 | "pdfUrl": "https://www.heracleous.org/uploads/1/1/2/9/11299865/quantum_strategy_-_org_dynamics.pdf",
45 | "id": "MNN--nqhxRkJ"
46 | },
47 | {
48 | "title": "Apple Inc., 2008",
49 | "link": "https://www.academia.edu/download/37818967/2008_-_Apple_Inc.__2008.pdf",
50 | "publicationInfo": "DB Yoffie, M Slind - 2008 - academia.edu",
51 | "snippet": "… , Apple Computer se desprendió de la segunda palabra de su nombre y se convirtió en Apple Inc.. Con … Las ventas del Mac seguían siendo vitales para el futuro de Apple, pero ahora …",
52 | "year": 2008,
53 | "citedBy": 32,
54 | "pdfUrl": "https://www.academia.edu/download/37818967/2008_-_Apple_Inc.__2008.pdf",
55 | "id": "wdF50XKO2vAJ"
56 | },
57 | {
58 | "title": "Apple Inc. strategic marketing analysis and evaluation",
59 | "link": "https://www.atlantis-press.com/proceedings/icemci-21/125965812",
60 | "publicationInfo": "X Chen, Y Liu, H Gong - 2021 3rd International Conference on …, 2021 - atlantis-press.com",
61 | "snippet": "… of the analysis we did and the future outlook of the Apple Inc on its sales performance. … figures and tables, we will analyze the current status and future sales leads for Apple Inc. …",
62 | "year": 2021,
63 | "citedBy": 14,
64 | "pdfUrl": "https://www.atlantis-press.com/article/125965812.pdf",
65 | "id": "femPSvqG0gEJ"
66 | },
67 | {
68 | "title": "STEVE JOBS AND APPLE, INC.",
69 | "link": "https://search.proquest.com/openview/cac8a3e3c7085c050d56c8254b537d9f/1?pq-origsite=gscholar&cbl=38869",
70 | "publicationInfo": "TA Finkle, ML Mallin - Journal of the International Academy for …, 2010 - search.proquest.com",
71 | "snippet": "… Apple Computer Company is arguably one of the most innovative technology companies to emerge in the last three decades. Apple, Inc… Steve Wozniak founded and built Apple into a 32 …",
72 | "year": 2010,
73 | "citedBy": 34,
74 | "id": "Myn_TvHVTFAJ"
75 | },
76 | {
77 | "title": "Apple Inc. industry analysis business policy and strategy",
78 | "link": "https://down.documentine.com/334efa922f97739e75944aeaeaca159c.pdf",
79 | "publicationInfo": "A Aljafari - International Journal of Scientific & Engineering …, 2016 - down.documentine.com",
80 | "snippet": "… In the following analysis we will be discussing Apple Inc.’s performance relative to their … well Apple is performing comparatively. We will be providing recommendations on how Apple …",
81 | "year": 2016,
82 | "citedBy": 15,
83 | "pdfUrl": "https://down.documentine.com/334efa922f97739e75944aeaeaca159c.pdf",
84 | "id": "A5OtbPW4If0J"
85 | },
86 | {
87 | "title": "Apple Inc.",
88 | "link": "https://link.springer.com/chapter/10.1007/978-3-319-21464-1_5",
89 | "publicationInfo": "G O'Regan, G O'Regan - Pillars of Computing: A Compendium of Select …, 2015 - Springer",
90 | "snippet": "… Apple Inc. is a well-known American corporation that was … and software such as the Apple and Macintosh computers, … to download applications developed for Apple’s iOS to their …",
91 | "year": 2015,
92 | "citedBy": 2,
93 | "id": "tm3HBD578sIJ"
94 | },
95 | {
96 | "title": "A critical analysis of internal and external environment of Apple Inc",
97 | "link": "https://www.researchgate.net/profile/Md-Alam-5/publication/282274921_A_CRITICAL_ANALYSIS_OF_INTERNAL_AND_EXTERNAL_ENVIRONMENT_OF_APPLE_INC/links/56ff003908aea6b77468d502/A-CRITICAL-ANALYSIS-OF-INTERNAL-AND-EXTERNAL-ENVIRONMENT-OF-APPLE-INC.pdf",
98 | "publicationInfo": "UA Khan, MN Alam, S Alam - International Journal of Economics …, 2015 - researchgate.net",
99 | "snippet": "… Apple had made our life even easier than time when Apple was just a fruit. The … Apple. For achieving the objectives, we analyses the external as well as internal environment of Apple Inc…",
100 | "year": 2015,
101 | "citedBy": 29,
102 | "pdfUrl": "https://www.researchgate.net/profile/Md-Alam-5/publication/282274921_A_CRITICAL_ANALYSIS_OF_INTERNAL_AND_EXTERNAL_ENVIRONMENT_OF_APPLE_INC/links/56ff003908aea6b77468d502/A-CRITICAL-ANALYSIS-OF-INTERNAL-AND-EXTERNAL-ENVIRONMENT-OF-APPLE-INC.pdf",
103 | "id": "ypGFhY1RPZ0J"
104 | }
105 | ],
106 | "credits": 1
107 | }
--------------------------------------------------------------------------------
/data/static.json:
--------------------------------------------------------------------------------
1 | {
2 | "terms": {
3 | "title": "Terms of Service",
4 | "lastUpdated": "January 24, 2025",
5 | "sections": [
6 | {
7 | "heading": "1. Acceptance of Terms",
8 | "content": "By accessing and using rSearch, you agree to be bound by these Terms of Service. Our AI-powered research assistant provides comprehensive search capabilities across multiple data sources, leveraging advanced reasoning through DeepSeek R1 technology."
9 | },
10 | {
11 | "heading": "2. Service Description",
12 | "content": "rSearch is a cutting-edge research assistant that harnesses artificial intelligence to deliver precise, well-reasoned responses to complex queries. We combine state-of-the-art language models, intelligent web searches, and sophisticated chain-of-thought reasoning to provide actionable insights backed by reliable sources."
13 | },
14 | {
15 | "heading": "3. User Responsibilities",
16 | "content": "Users agree to use the service responsibly and respect intellectual property rights. The service provides access to diverse content categories including web search, images, videos, news, places, shopping, academic content, and patent databases. Users must not attempt to automate queries without permission."
17 | },
18 | {
19 | "heading": "4. Data Usage",
20 | "content": "We process queries through our Chain-of-Thought process: query refinement, comprehensive data gathering via Serper.dev API, reasoning analysis using DeepSeek capabilities, and response synthesis. Your usage helps improve our AI models while maintaining privacy standards."
21 | }
22 | ]
23 | },
24 | "privacy": {
25 | "title": "Privacy Policy",
26 | "lastUpdated": "January 24, 2025",
27 | "sections": [
28 | {
29 | "heading": "Data Collection",
30 | "content": "We collect search queries and interaction patterns to power our AI-driven research assistant. This data helps train our DeepSeek R1 model for enhanced reasoning capabilities. We do not sell personal data to third parties."
31 | },
32 | {
33 | "heading": "How We Use Your Data",
34 | "content": "Your data improves our query refinement, search accuracy, and reasoning processes. We use aggregated, anonymized data to enhance our Chain-of-Thought processing and develop new research capabilities."
35 | },
36 | {
37 | "heading": "Data Protection",
38 | "content": "We employ industry-standard security measures to protect your data. All searches and AI processing are encrypted, and we regularly audit our security practices to maintain the highest standards."
39 | },
40 | {
41 | "heading": "Your Rights",
42 | "content": "You maintain full control over your data, with rights to access, opt-out of non-essential collection, and delete account information at any time."
43 | }
44 | ]
45 | },
46 | "about": {
47 | "title": "About rSearch",
48 | "lastUpdated": "January 24, 2025",
49 | "sections": [
50 | {
51 | "heading": "Our Mission",
52 | "content": "rSearch aims to revolutionize research by delivering precise, well-reasoned responses to complex queries through advanced AI technology. We help users discover insights, not just results, through sophisticated reasoning capabilities."
53 | },
54 | {
55 | "heading": "How It Works",
56 | "content": "Our platform leverages DeepSeek's Reasoner - R1 model for Chain-of-Thought reasoning on search results. We refine queries intelligently, gather comprehensive data via Serper.dev API, and synthesize information through sophisticated reasoning processes."
57 | },
58 | {
59 | "heading": "Features",
60 | "content": "rSearch offers multiple search modes including web search, images, videos, news, places, shopping, scholar, and patents. Our modern UI features responsive navigation, mobile optimization, and dynamic result displays, all powered by Next.js and TypeScript."
61 | },
62 | {
63 | "heading": "Technology",
64 | "content": "Built with cutting-edge technology including Next.js, TypeScript, and TailwindCSS, rSearch harnesses the DeepSeek R1 model's advanced reasoning capabilities. Our platform offers exceptional performance in math, code, and reasoning tasks, comparable to leading AI models."
65 | }
66 | ]
67 | }
68 | }
--------------------------------------------------------------------------------
/demo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Justmalhar/rsearch/0515631cceadba32cc610959c3e819bf9b86e2ba/demo.png
--------------------------------------------------------------------------------
/eslint.config.mjs:
--------------------------------------------------------------------------------
1 | import { FlatCompat } from '@eslint/eslintrc'
2 | import js from '@eslint/js'
3 |
4 | const compat = new FlatCompat({
5 | baseDirectory: import.meta.dirname,
6 | recommendedConfig: js.configs.recommended,
7 | })
8 |
9 | export default [
10 | ...compat.config({
11 | extends: [
12 | 'next/core-web-vitals',
13 | 'plugin:@typescript-eslint/recommended'
14 | ],
15 | parser: '@typescript-eslint/parser',
16 | plugins: ['@typescript-eslint'],
17 | rules: {
18 | '@next/next/no-img-element': 'off',
19 | '@typescript-eslint/no-unused-vars': 'error'
20 | }
21 | })
22 | ]
23 |
--------------------------------------------------------------------------------
/hooks/use-media-query.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from "react";
2 |
3 | export function useMediaQuery(query: string) {
4 | const [matches, setMatches] = useState(false);
5 |
6 | useEffect(() => {
7 | const media = window.matchMedia(query);
8 | if (media.matches !== matches) {
9 | setMatches(media.matches);
10 | }
11 | const listener = () => setMatches(media.matches);
12 | media.addEventListener("change", listener);
13 | return () => media.removeEventListener("change", listener);
14 | }, [matches, query]);
15 |
16 | return matches;
17 | }
--------------------------------------------------------------------------------
/hooks/use-mobile.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | const MOBILE_BREAKPOINT = 768
4 |
5 | export function useIsMobile() {
6 | const [isMobile, setIsMobile] = React.useState(undefined)
7 |
8 | React.useEffect(() => {
9 | const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`)
10 | const onChange = () => {
11 | setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
12 | }
13 | mql.addEventListener("change", onChange)
14 | setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
15 | return () => mql.removeEventListener("change", onChange)
16 | }, [])
17 |
18 | return !!isMobile
19 | }
20 |
--------------------------------------------------------------------------------
/lib/imageLoader.ts:
--------------------------------------------------------------------------------
1 | export const customImageLoader = ({ src }: { src: string }) => {
2 | return src;
3 | };
--------------------------------------------------------------------------------
/lib/prompts.ts:
--------------------------------------------------------------------------------
1 |
2 | export const refineSearchQueryPrompt = (searchTerm: string, mode: string, currentDate: string) => `You are an expert at refining search queries to get the most relevant and comprehensive results. Your task is to analyze the given search query and provide a refined version that will yield better search results.
3 |
4 | Guidelines for refinement:
5 | - Add relevant context and specific terms that would improve search accuracy
6 | - Remove any ambiguous or unnecessary terms
7 | - Consider including synonyms for important terms
8 | - Ensure the query remains focused on the user's original intent
9 | - Keep the refined query concise but comprehensive
10 | - Add current year if time-sensitive
11 |
12 | Original query: "${searchTerm}"
13 |
14 | User is trying to search using search mode: ${mode}
15 |
16 | Current date & time in ISO format (UTC timezone) is: ${currentDate}. Avoid using it unnecessarily if it is not relevant to the search.
17 |
18 | Example search modes:
19 | - "web"
20 | - "images"
21 | - "videos"
22 | - "news"
23 | - "shopping"
24 |
25 |
26 | How should you refine the search query:
27 | - If the user is searching for a specific topic, add relevant keywords or phrases to the query.
28 | - If the user is searching for a specific date related topic, add the date to the query.
29 | - If the user is searching for a specific location, add the location to the query.
30 | - If the user is searching for a specific person, add the person's full name to the query.
31 | - If the user is searching for a specific product, add the product name to the query.
32 |
33 | Respond with a JSON object containing:
34 | 1. refined_query: The improved search query
35 | 2. explanation: A brief explanation of why this refinement will yield better results
36 |
37 | Example response:
38 | {
39 | "refined_query": "latest artificial intelligence developments 2024 research breakthroughs",
40 | "explanation": "Added year and specific focus areas to get more recent and relevant results"
41 | }`;
42 |
43 | export const rSearchPrompt = (searchTerm: string, context: string, currentDate: string) => `You are rSearch, an AI model skilled in web search and crafting detailed, engaging, and well-structured answers. You excel at summarizing web pages and extracting relevant information to create professional, blog-style responses.
44 |
45 | Your task is to provide answers that are:
46 | - **Informative and relevant**: Thoroughly address the user's query using the given context.
47 | - **Well-structured**: Include clear headings and subheadings, and use a professional tone to present information concisely and logically.
48 | - **Engaging and detailed**: Write responses that read like a high-quality blog post, including extra details and relevant insights.
49 | - **Cited and credible**: Use inline citations with [Website Name](URL) notation to refer to the context source(s) for each fact or detail included.
50 | - **Explanatory and Comprehensive**: Strive to explain the topic in depth, offering detailed analysis, insights, and clarifications wherever applicable.
51 |
52 | ### Formatting Instructions
53 | - Always add a title to the response like a SEO Optimized blog post title.
54 | - **Structure**: Use a well-organized format with proper headings (e.g., "## Example heading 1" or "## Example heading 2"). Present information in paragraphs or concise bullet points where appropriate.
55 | - **Tone and Style**: Maintain a neutral, journalistic tone with engaging narrative flow. Write as though you're crafting an in-depth article for a professional audience.
56 | - **Markdown Usage**: Format your response with Markdown for clarity. Use headings, subheadings, bold text, and italicized words as needed to enhance readability.
57 | - **Length and Depth**: Provide comprehensive coverage of the topic. Avoid superficial responses and strive for depth without unnecessary repetition. Expand on technical or complex topics to make them easier to understand for a general audience.
58 | - **No main heading/title**: Start your response directly with the introduction unless asked to provide a specific title.
59 | - **Conclusion or Summary**: Include a concluding paragraph that synthesizes the provided information or suggests potential next steps, where appropriate.
60 | - Link to the sources in the context using [Website Name](URL) notation. If the source is not a website, use the name of the source.
61 | - If the source has images, include them in the response along with a caption and source to make it more engaging.
62 | Example: 
63 | *Modi campaigning for the 2024 elections (Source: [PBS](https://www.pbs.org/newshour/world/modi-vows-to-turn-india-into-global-manufacturing-hub-as-he-seeks-3rd-term-in-2024-election))*
64 | - Double check if the image is a valid image. If not do not include it in the response.
65 |
66 | ### Markdown Formatting
67 | Write in Github flavored markdown format using various sections such as tables, >, *italics*, **bold**, headings from # to ######, blockquotes, inline citation links, —- horizontal divider for the sections, only valid images from sources, language specific code in language specific markdown blocks. Try to maintain consistent structure section by section with hierarchy.
68 | Use bold for the most important information, italics for the keywords information, and normal text for the rest. Maximize the use of bold and italics to make the response more engaging and readable.
69 |
70 | ### In-line Citation Requirements
71 | - Link to the sources in the context using [Website Name](URL) notation. If the source is not a website, use the name of the source.
72 | - Cite every single fact, statement, or sentence using [Website Name](URL) notation corresponding to the source from the provided context.
73 | - Integrate citations naturally at the end of sentences or clauses as appropriate.
74 | - Ensure that **every sentence in your response includes at least one citation**, even when information is inferred or connected to general knowledge available in the provided context.
75 | - Use multiple sources for a single detail if applicable.
76 | - Always prioritize credibility and accuracy by linking all statements back to their respective context sources.
77 | - Avoid citing unsupported assumptions or personal interpretations; if no source supports a statement, clearly indicate the limitation.
78 | - Never cite the search query as a source.
79 | - Never write it as [Apple.com](https://Apple.com). It should be [Apple](https://Apple.com). No need to mention the .com, .org, .net, etc.
80 |
81 | - Example In-Line Citations:
82 | - "According to [TechCrunch](https://techcrunch.com), OpenAI has made significant breakthroughs in language models"
83 | - "The latest MacBook Pro features impressive battery life, as detailed on [Apple Support](https://support.apple.com/macbook)"
84 | - "Prime members can expect faster delivery times in urban areas, according to [Amazon Prime](https://www.amazon.com/prime)"
85 | - "Developers can find the documentation on [GitHub Docs](https://docs.github.com)"
86 | - "The course is available for free on [MIT OpenCourseWare](https://ocw.mit.edu/courses)"
87 | - "Users reported the issue on [Stack Overflow](https://stackoverflow.com/questions)"
88 |
89 | ### Special Instructions
90 | - If the query involves technical, historical, or complex topics, provide detailed background and explanatory sections to ensure clarity.
91 | - If the user provides vague input or if relevant information is missing, explain what additional details might help refine the search.
92 | - If no relevant information is found, say: "Hmm, sorry I could not find any relevant information on this topic. Would you like me to search again or ask something else?"
93 |
94 | Answer based on the following search query:
95 |
96 |
97 | ${searchTerm}
98 |
99 |
100 |
101 | ${context}
102 |
103 |
104 | Current date & time in ISO format (UTC timezone) is: ${currentDate}.
105 | `;
--------------------------------------------------------------------------------
/lib/utils.ts:
--------------------------------------------------------------------------------
1 | import { clsx, type ClassValue } from "clsx"
2 | import { createHash } from "crypto";
3 | import { twMerge } from "tailwind-merge"
4 |
5 | export function cn(...inputs: ClassValue[]) {
6 | return twMerge(clsx(inputs))
7 | }
8 |
9 | export function generateSearchId(searchTerm: string, searchMode: string | null): string {
10 | // Don't decode here since the input should already be decoded by the caller
11 | const input = `${searchTerm.toLowerCase().trim()}-${searchMode || 'web'}`;
12 | return createHash('sha256')
13 | .update(input)
14 | .digest('hex')
15 | .slice(0, 24); // Taking first 24 chars for a cleaner URL
16 | }
17 |
18 |
19 | export function getWebsiteName(url: string): string {
20 | try {
21 | const hostname = new URL(url).hostname;
22 | // Common domain mappings
23 | const domainMappings: Record = {
24 | 'youtube.com': 'YouTube',
25 | 'music.youtube.com': 'YouTube Music',
26 | 'amazon.com': 'Amazon',
27 | 'music.amazon.com': 'Amazon Music',
28 | 'spotify.com': 'Spotify',
29 | 'apple.com': 'Apple',
30 | 'music.apple.com': 'Apple Music',
31 | 'wikipedia.org': 'Wikipedia',
32 | 'github.com': 'GitHub',
33 | 'medium.com': 'Medium',
34 | 'stackoverflow.com': 'Stack Overflow'
35 | };
36 |
37 | // Check for exact matches first
38 | if (domainMappings[hostname]) {
39 | return domainMappings[hostname];
40 | }
41 |
42 | // Check for partial matches (e.g., subdomain.youtube.com)
43 | for (const [domain, name] of Object.entries(domainMappings)) {
44 | if (hostname.endsWith(domain)) {
45 | return name;
46 | }
47 | }
48 |
49 | // Default: take the main domain name and capitalize it
50 | const mainDomain = hostname.replace('www.', '').split('.')[0];
51 | return mainDomain.charAt(0).toUpperCase() + mainDomain.slice(1);
52 | } catch {
53 | return url;
54 | }
55 | }
--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {
3 | images: {
4 | unoptimized: true,
5 | remotePatterns: [
6 | {
7 | protocol: 'https',
8 | hostname: '**',
9 | },
10 | ],
11 | },
12 | }
13 |
14 | module.exports = nextConfig;
15 |
--------------------------------------------------------------------------------
/next.config.ts:
--------------------------------------------------------------------------------
1 | import type { NextConfig } from "next";
2 |
3 | const nextConfig: NextConfig = {
4 | /* config options here */
5 | };
6 |
7 | export default nextConfig;
8 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "rsearch",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint"
10 | },
11 | "dependencies": {
12 | "@radix-ui/react-dialog": "^1.1.5",
13 | "@radix-ui/react-dropdown-menu": "^2.1.5",
14 | "@radix-ui/react-label": "^2.1.1",
15 | "@radix-ui/react-radio-group": "^1.2.2",
16 | "@radix-ui/react-select": "^2.1.5",
17 | "@radix-ui/react-separator": "^1.1.1",
18 | "@radix-ui/react-slot": "^1.1.1",
19 | "@radix-ui/react-switch": "^1.1.2",
20 | "@radix-ui/react-tabs": "^1.1.2",
21 | "@radix-ui/react-toggle": "^1.1.1",
22 | "@radix-ui/react-tooltip": "^1.1.7",
23 | "@vercel/analytics": "^1.4.1",
24 | "class-variance-authority": "^0.7.1",
25 | "clsx": "^2.1.1",
26 | "crypto": "^1.0.1",
27 | "embla-carousel-react": "^8.5.2",
28 | "framer-motion": "^12.0.1",
29 | "lucide-react": "^0.473.0",
30 | "next": "15.1.5",
31 | "openai": "^4.79.4",
32 | "react": "^19.0.0",
33 | "react-dom": "^19.0.0",
34 | "react-icons": "^5.4.0",
35 | "react-markdown": "^9.0.3",
36 | "tailwind-merge": "^2.6.0",
37 | "tailwindcss-animate": "^1.0.7",
38 | "vaul": "^1.1.2",
39 | "zod": "^3.24.1"
40 | },
41 | "devDependencies": {
42 | "@eslint/eslintrc": "^3",
43 | "@types/node": "^20",
44 | "@types/react": "^19",
45 | "@types/react-dom": "^19",
46 | "@typescript-eslint/eslint-plugin": "^8.21.0",
47 | "@typescript-eslint/parser": "^8.21.0",
48 | "eslint": "^9",
49 | "eslint-config-next": "15.1.5",
50 | "postcss": "^8",
51 | "tailwindcss": "^3.4.1",
52 | "typescript": "^5"
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/postcss.config.mjs:
--------------------------------------------------------------------------------
1 | /** @type {import('postcss-load-config').Config} */
2 | const config = {
3 | plugins: {
4 | tailwindcss: {},
5 | },
6 | };
7 |
8 | export default config;
9 |
--------------------------------------------------------------------------------
/public/apple-touch-icon-114x114.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Justmalhar/rsearch/0515631cceadba32cc610959c3e819bf9b86e2ba/public/apple-touch-icon-114x114.png
--------------------------------------------------------------------------------
/public/apple-touch-icon-120x120.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Justmalhar/rsearch/0515631cceadba32cc610959c3e819bf9b86e2ba/public/apple-touch-icon-120x120.png
--------------------------------------------------------------------------------
/public/apple-touch-icon-144x144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Justmalhar/rsearch/0515631cceadba32cc610959c3e819bf9b86e2ba/public/apple-touch-icon-144x144.png
--------------------------------------------------------------------------------
/public/apple-touch-icon-152x152.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Justmalhar/rsearch/0515631cceadba32cc610959c3e819bf9b86e2ba/public/apple-touch-icon-152x152.png
--------------------------------------------------------------------------------
/public/apple-touch-icon-180x180.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Justmalhar/rsearch/0515631cceadba32cc610959c3e819bf9b86e2ba/public/apple-touch-icon-180x180.png
--------------------------------------------------------------------------------
/public/apple-touch-icon-57x57.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Justmalhar/rsearch/0515631cceadba32cc610959c3e819bf9b86e2ba/public/apple-touch-icon-57x57.png
--------------------------------------------------------------------------------
/public/apple-touch-icon-72x72.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Justmalhar/rsearch/0515631cceadba32cc610959c3e819bf9b86e2ba/public/apple-touch-icon-72x72.png
--------------------------------------------------------------------------------
/public/apple-touch-icon-76x76.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Justmalhar/rsearch/0515631cceadba32cc610959c3e819bf9b86e2ba/public/apple-touch-icon-76x76.png
--------------------------------------------------------------------------------
/public/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Justmalhar/rsearch/0515631cceadba32cc610959c3e819bf9b86e2ba/public/apple-touch-icon.png
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Justmalhar/rsearch/0515631cceadba32cc610959c3e819bf9b86e2ba/public/favicon.ico
--------------------------------------------------------------------------------
/public/file.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/globe.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Justmalhar/rsearch/0515631cceadba32cc610959c3e819bf9b86e2ba/public/logo.png
--------------------------------------------------------------------------------
/public/next.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/og.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Justmalhar/rsearch/0515631cceadba32cc610959c3e819bf9b86e2ba/public/og.png
--------------------------------------------------------------------------------
/public/og.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | User-agent: *
2 | Allow: /
3 | Disallow: /api/
4 |
5 | # Block access to internal routes
6 | Disallow: /_next/
7 | Disallow: /_error/
8 |
9 | # Allow static assets
10 | Allow: /static/
11 | Allow: /images/
12 | Allow: /*.js
13 | Allow: /*.css
14 | Allow: /favicon.ico
15 |
16 | # Sitemap
17 | Sitemap: https://rsearch.app/sitemap.xml
18 |
--------------------------------------------------------------------------------
/public/sitemap.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | https://rsearch.app
5 | daily
6 | 1.0
7 |
8 |
9 | https://rsearch.app/settings
10 | weekly
11 | 0.8
12 |
13 |
14 | https://rsearch.app/rsearch
15 | daily
16 | 0.9
17 |
18 |
19 |
--------------------------------------------------------------------------------
/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/window.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tailwind.config.ts:
--------------------------------------------------------------------------------
1 | import type { Config } from "tailwindcss";
2 | import animate from "tailwindcss-animate";
3 |
4 | export default {
5 | darkMode: ["class"],
6 | content: [
7 | "./pages/**/*.{js,ts,jsx,tsx,mdx}",
8 | "./components/**/*.{js,ts,jsx,tsx,mdx}",
9 | "./app/**/*.{js,ts,jsx,tsx,mdx}",
10 | ],
11 | theme: {
12 | extend: {
13 | colors: {
14 | background: 'hsl(var(--background))',
15 | foreground: 'hsl(var(--foreground))',
16 | card: {
17 | DEFAULT: 'hsl(var(--card))',
18 | foreground: 'hsl(var(--card-foreground))'
19 | },
20 | popover: {
21 | DEFAULT: 'hsl(var(--popover))',
22 | foreground: 'hsl(var(--popover-foreground))'
23 | },
24 | primary: {
25 | DEFAULT: 'hsl(var(--primary))',
26 | foreground: 'hsl(var(--primary-foreground))'
27 | },
28 | secondary: {
29 | DEFAULT: 'hsl(var(--secondary))',
30 | foreground: 'hsl(var(--secondary-foreground))'
31 | },
32 | muted: {
33 | DEFAULT: 'hsl(var(--muted))',
34 | foreground: 'hsl(var(--muted-foreground))'
35 | },
36 | accent: {
37 | DEFAULT: 'hsl(var(--accent))',
38 | foreground: 'hsl(var(--accent-foreground))'
39 | },
40 | destructive: {
41 | DEFAULT: 'hsl(var(--destructive))',
42 | foreground: 'hsl(var(--destructive-foreground))'
43 | },
44 | border: 'hsl(var(--border))',
45 | input: 'hsl(var(--input))',
46 | ring: 'hsl(var(--ring))',
47 | chart: {
48 | '1': 'hsl(var(--chart-1))',
49 | '2': 'hsl(var(--chart-2))',
50 | '3': 'hsl(var(--chart-3))',
51 | '4': 'hsl(var(--chart-4))',
52 | '5': 'hsl(var(--chart-5))'
53 | },
54 | sidebar: {
55 | DEFAULT: 'hsl(var(--sidebar-background))',
56 | foreground: 'hsl(var(--sidebar-foreground))',
57 | primary: 'hsl(var(--sidebar-primary))',
58 | 'primary-foreground': 'hsl(var(--sidebar-primary-foreground))',
59 | accent: 'hsl(var(--sidebar-accent))',
60 | 'accent-foreground': 'hsl(var(--sidebar-accent-foreground))',
61 | border: 'hsl(var(--sidebar-border))',
62 | ring: 'hsl(var(--sidebar-ring))'
63 | }
64 | },
65 | borderRadius: {
66 | lg: 'var(--radius)',
67 | md: 'calc(var(--radius) - 2px)',
68 | sm: 'calc(var(--radius) - 4px)'
69 | },
70 | animation: {
71 | meteor: 'meteor 5s linear infinite',
72 | 'gradient-x': 'gradient-x 15s ease infinite'
73 | },
74 | keyframes: {
75 | meteor: {
76 | '0%': {
77 | transform: 'rotate(215deg) translateX(0)',
78 | opacity: '1'
79 | },
80 | '70%': {
81 | opacity: '1'
82 | },
83 | '100%': {
84 | transform: 'rotate(215deg) translateX(-500px)',
85 | opacity: '0'
86 | }
87 | },
88 | 'gradient-x': {
89 | '0%, 100%': {
90 | 'background-size': '200% 200%',
91 | 'background-position': 'left center'
92 | },
93 | '50%': {
94 | 'background-size': '200% 200%',
95 | 'background-position': 'right center'
96 | }
97 | }
98 | }
99 | }
100 | },
101 | plugins: [animate],
102 | } satisfies Config;
103 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2017",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "strict": true,
8 | "noEmit": true,
9 | "esModuleInterop": true,
10 | "module": "esnext",
11 | "moduleResolution": "bundler",
12 | "resolveJsonModule": true,
13 | "isolatedModules": true,
14 | "jsx": "preserve",
15 | "incremental": true,
16 | "plugins": [
17 | {
18 | "name": "next"
19 | }
20 | ],
21 | "paths": {
22 | "@/*": ["./*"]
23 | }
24 | },
25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
26 | "exclude": ["node_modules"]
27 | }
28 |
--------------------------------------------------------------------------------
/types/search.ts:
--------------------------------------------------------------------------------
1 | export type SearchSource = 'web' | 'images' | 'videos' | 'places' | 'news' | 'shopping' | 'scholar' | 'patents' | 'academic';
2 |
3 | // Base interface for common properties
4 | interface BaseSearchResult {
5 | title: string;
6 | link: string;
7 | }
8 |
9 | // Web search results
10 | export interface WebSearchResult extends BaseSearchResult {
11 | snippet: string;
12 | position?: number;
13 | date?: string;
14 | attributes?: Record;
15 | imageUrl?: string;
16 | thumbnailUrl?: string;
17 | }
18 |
19 | // Image search results
20 | export interface ImageSearchResult extends BaseSearchResult {
21 | imageUrl: string;
22 | source?: string;
23 | }
24 |
25 | // Video search results
26 | export interface VideoSearchResult extends BaseSearchResult {
27 | imageUrl?: string;
28 | snippet?: string;
29 | duration?: string;
30 | channel?: string;
31 | views?: string;
32 | date?: string;
33 | source?: string;
34 | }
35 |
36 | // Place search results
37 | export interface PlaceSearchResult extends BaseSearchResult {
38 | position: number;
39 | address: string;
40 | latitude: number;
41 | longitude: number;
42 | rating?: number;
43 | ratingCount?: number;
44 | category?: string;
45 | phoneNumber?: string;
46 | website?: string;
47 | cid: string;
48 | }
49 |
50 | // News search results
51 | export interface NewsSearchResult extends BaseSearchResult {
52 | snippet: string;
53 | imageUrl?: string;
54 | date?: string;
55 | source?: string;
56 | position?: number;
57 | attributes?: Record;
58 | }
59 |
60 | // Shopping search results
61 | export interface ShoppingSearchResult extends BaseSearchResult {
62 | price?: string;
63 | rating?: number;
64 | ratingCount?: number;
65 | imageUrl?: string;
66 | source?: string;
67 | delivery?: string;
68 | offers?: string;
69 | productId?: string;
70 | position?: number;
71 | }
72 |
73 | // Academic search results (for both scholar and patents)
74 | export interface AcademicSearchResult extends BaseSearchResult {
75 | snippet: string;
76 | authors?: string[];
77 | year?: string | number;
78 | publisher?: string;
79 | citations?: number;
80 | publicationInfo?: string;
81 | pdfUrl?: string;
82 | id?: string;
83 | }
84 |
85 | // Union type for all possible result types
86 | export type SearchResult =
87 | | WebSearchResult
88 | | ImageSearchResult
89 | | VideoSearchResult
90 | | PlaceSearchResult
91 | | NewsSearchResult
92 | | ShoppingSearchResult
93 | | AcademicSearchResult;
94 |
95 | // Response interface
96 | export interface SerperResponse {
97 | searchParameters: {
98 | q: string;
99 | gl: string;
100 | hl: string;
101 | };
102 | organic?: WebSearchResult[];
103 | images?: ImageSearchResult[];
104 | videos?: VideoSearchResult[];
105 | places?: PlaceSearchResult[];
106 | news?: NewsSearchResult[];
107 | shopping?: ShoppingSearchResult[];
108 | academic?: AcademicSearchResult[];
109 | knowledgeGraph?: {
110 | title: string;
111 | type: string;
112 | description?: string;
113 | imageUrl?: string;
114 | attributes?: Record;
115 | images?: Array<{
116 | title?: string;
117 | imageUrl: string;
118 | }>;
119 | };
120 | relatedSearches?: Array<{ query: string }>;
121 | peopleAlsoAsk?: Array<{
122 | question: string;
123 | snippet: string;
124 | link: string;
125 | }>;
126 | }
127 |
128 | export interface SearchError {
129 | error: string;
130 | }
131 |
132 | export interface SearchParams {
133 | searchTerm: string;
134 | searchMode: SearchSource;
135 | }
136 |
--------------------------------------------------------------------------------