From 30f98e3a6cae4355203541b836ed5bfe5c699c11 Mon Sep 17 00:00:00 2001 From: Michael Ryaboy Date: Mon, 18 Dec 2023 13:12:28 -0800 Subject: [PATCH 001/127] adds words to tabs, improves skeleton, changes to openrouter --- app/proxy/page.tsx | 55 +++++++++++++++++++++++++++++++---- components/arrow-tabs.tsx | 42 ++++++++++++++++++++++++-- components/article-length.tsx | 14 +++++++++ package.json | 2 ++ pnpm-lock.yaml | 10 +++++++ 5 files changed, 115 insertions(+), 8 deletions(-) create mode 100644 components/article-length.tsx diff --git a/app/proxy/page.tsx b/app/proxy/page.tsx index bd9deaad..7cae3d97 100644 --- a/app/proxy/page.tsx +++ b/app/proxy/page.tsx @@ -12,6 +12,8 @@ import ArrowTabs from "@/components/arrow-tabs"; import { ArticleContent } from "@/components/article-content"; export const runtime = "edge"; import { Source, getData } from "@/lib/data"; +import { ArticleLength } from "@/components/article-length"; +import Loading from "./loading"; const apiConfig = new Configuration({ @@ -105,18 +107,33 @@ export default async function Page({ + + + } + lengthWayback={ + + + + } + lengthGoogle={ + + + + } innerHTMLDirect={ - Loading...}> + }> } innerHTMLWayback={ - Loading...}> + }> } innerHTMLGoogle={ - Loading...}> + }> } @@ -227,15 +244,41 @@ async function Wrapper({ const tokens = encode(prompt).splice(0, tokenLimit); const decodedText = decode(tokens); - const response = await fetch("https://api.perplexity.ai/chat/completions", { + // const response = await fetch("https://api.perplexity.ai/chat/completions", { + // method: "POST", + // headers: { + // Accept: "application/json", + // "Content-Type": "application/json", + // Authorization: `Bearer ${process.env.PERPLEXITY_API_KEY}`, + // }, + // body: JSON.stringify({ + // model: pickRandomModel(), + // stream: true, + // max_tokens: 580, + // frequency_penalty: 1, + // temperature: 1, + // messages: [ + // { + // role: "system", + // content: "Be precise and concise in your responses.", + // }, + // { + // role: "user", + // content: decodedText, + // }, + // ], + // }), + // }); + + const response = await fetch("https://openrouter.ai/api/v1/chat/completions", { method: "POST", headers: { Accept: "application/json", "Content-Type": "application/json", - Authorization: `Bearer ${process.env.PERPLEXITY_API_KEY}`, + Authorization: `Bearer ${process.env.OPENROUTER_API_KEY}`, }, body: JSON.stringify({ - model: pickRandomModel(), + model: "mistralai/mixtral-8x7b-instruct", stream: true, max_tokens: 580, frequency_penalty: 1, diff --git a/components/arrow-tabs.tsx b/components/arrow-tabs.tsx index 97b2172c..c8458e19 100644 --- a/components/arrow-tabs.tsx +++ b/components/arrow-tabs.tsx @@ -4,12 +4,19 @@ import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs"; import { useCallback, useEffect, useRef, useState, Suspense } from "react"; import { motion } from "framer-motion"; import { ChevronLeft, ChevronRight } from "lucide-react"; +import React from "react"; +import { count } from "console"; +import {convert} from 'html-to-text' +import ReactDOMServer from 'react-dom/server'; const EnhancedTabsList: React.FC<{ sources: TabProps["sources"]; activeTabIndex: number; setActiveTabIndex: (tabIndex: number) => void; -}> = ({ sources, activeTabIndex, setActiveTabIndex }) => { + lengthGoogle: React.ReactNode; + lengthWayback: React.ReactNode; + lengthDirect: React.ReactNode; +}> = ({ sources, activeTabIndex, setActiveTabIndex, lengthDirect, lengthGoogle, lengthWayback }) => { const tabsContainerRef = useRef(null); const scrollToActiveTab = useCallback(() => { @@ -58,6 +65,25 @@ const EnhancedTabsList: React.FC<{ } }; + const getSourceLength = (source: string): React.ReactNode => { + let content; + switch (source) { + case "smry": + content = lengthDirect; + break; + case "wayback": + content = lengthWayback; + break; + case "google": + content = lengthGoogle; + break; + default: + content = null; + } + + return content; + } + return (
{activeTabIndex > 0 && ( // Only display the left button if it's not the first tab @@ -101,7 +127,10 @@ const EnhancedTabsList: React.FC<{ {sources.map((source, index) => ( - {source} + + {source} · {" "} + {getSourceLength(source)} + ))} @@ -149,6 +178,9 @@ interface TabProps { innerHTMLGoogle: React.ReactNode; innerHTMLWayback: React.ReactNode; innerHTMLDirect: React.ReactNode; + lengthGoogle: React.ReactNode; + lengthWayback: React.ReactNode; + lengthDirect: React.ReactNode; } const ArrowTabs: React.FC = ({ @@ -156,6 +188,9 @@ const ArrowTabs: React.FC = ({ innerHTMLGoogle, innerHTMLDirect, innerHTMLWayback, + lengthDirect, + lengthGoogle, + lengthWayback }) => { const initialTabIndex = 0; const [activeTabIndex, setActiveTabIndex] = useState(initialTabIndex); @@ -170,6 +205,9 @@ const ArrowTabs: React.FC = ({ sources={sources} activeTabIndex={activeTabIndex} setActiveTabIndex={setActiveTabIndex} + lengthDirect={lengthDirect} + lengthGoogle={lengthGoogle} + lengthWayback={lengthWayback} /> {innerHTMLDirect} {innerHTMLWayback} diff --git a/components/article-length.tsx b/components/article-length.tsx new file mode 100644 index 00000000..03a2555f --- /dev/null +++ b/components/article-length.tsx @@ -0,0 +1,14 @@ +import React, { Suspense } from "react"; +import { ResponseItem } from "@/app/proxy/page"; +import { Source, getData } from "@/lib/data"; + +interface ArticleLengthProps { + url: string; + source: Source; +} + +export const ArticleLength = async ({ url, source }: ArticleLengthProps) => { + const content: ResponseItem = await getData(url, source); + + return {(content.article?.length ?? 0 ) + " words"}; +}; diff --git a/package.json b/package.json index 93430a15..7d1e4317 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "geist": "^1.1.0", "gpt-tokenizer": "^2.1.2", "html-react-parser": "^5.0.6", + "html-to-text": "^9.0.5", "https-proxy-agent": "^7.0.2", "jsdom": "^22.1.0", "lucide-react": "^0.290.0", @@ -49,6 +50,7 @@ }, "devDependencies": { "@tailwindcss/typography": "^0.5.10", + "@types/html-to-text": "^9.0.4", "@types/jsdom": "^21.1.5", "@types/node": "^20.9.0", "@types/react": "^18.2.37", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 15d4eb4f..9e38915f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -59,6 +59,9 @@ dependencies: html-react-parser: specifier: ^5.0.6 version: 5.0.6(react@18.2.0) + html-to-text: + specifier: ^9.0.5 + version: 9.0.5 https-proxy-agent: specifier: ^7.0.2 version: 7.0.2 @@ -121,6 +124,9 @@ devDependencies: '@tailwindcss/typography': specifier: ^0.5.10 version: 0.5.10(tailwindcss@3.3.5) + '@types/html-to-text': + specifier: ^9.0.4 + version: 9.0.4 '@types/jsdom': specifier: ^21.1.5 version: 21.1.5 @@ -1024,6 +1030,10 @@ packages: resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} dev: false + /@types/html-to-text@9.0.4: + resolution: {integrity: sha512-pUY3cKH/Nm2yYrEmDlPR1mR7yszjGx4DrwPjQ702C4/D5CwHuZTgZdIdwPkRbcuhs7BAh2L5rg3CL5cbRiGTCQ==} + dev: true + /@types/jsdom@21.1.5: resolution: {integrity: sha512-sBK/3YjS3uuPj+HzZyhB4GGTnFmk0mdyQfhzZ/sqs9ciyG41QJdZZdwcPa6OfW97OTNTwl5tBAsfEOm/dui9pQ==} dependencies: From 99fcb24c481ca97fd6ef1a7232fdaf58b8a1ba35 Mon Sep 17 00:00:00 2001 From: Michael Ryaboy Date: Mon, 18 Dec 2023 13:20:26 -0800 Subject: [PATCH 002/127] small formatting change for tabs --- components/arrow-tabs.tsx | 2 +- components/article-length.tsx | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/components/arrow-tabs.tsx b/components/arrow-tabs.tsx index c8458e19..dd22ffc5 100644 --- a/components/arrow-tabs.tsx +++ b/components/arrow-tabs.tsx @@ -128,7 +128,7 @@ const EnhancedTabsList: React.FC<{ {sources.map((source, index) => ( - {source} · {" "} + {source} {getSourceLength(source)} diff --git a/components/article-length.tsx b/components/article-length.tsx index 03a2555f..0289324c 100644 --- a/components/article-length.tsx +++ b/components/article-length.tsx @@ -10,5 +10,9 @@ interface ArticleLengthProps { export const ArticleLength = async ({ url, source }: ArticleLengthProps) => { const content: ResponseItem = await getData(url, source); - return {(content.article?.length ?? 0 ) + " words"}; + return ( + + {" · " + (content.article?.length ?? 0) + " words"} + + ); }; From 7dd0057415724601675ec6b7c1e53b2813ec87a1 Mon Sep 17 00:00:00 2001 From: Michael Ryaboy Date: Thu, 28 Dec 2023 22:24:36 -0800 Subject: [PATCH 003/127] updates to free mistral version from openrouter --- app/proxy/page.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/proxy/page.tsx b/app/proxy/page.tsx index 7cae3d97..23f9f5f8 100644 --- a/app/proxy/page.tsx +++ b/app/proxy/page.tsx @@ -278,7 +278,7 @@ async function Wrapper({ Authorization: `Bearer ${process.env.OPENROUTER_API_KEY}`, }, body: JSON.stringify({ - model: "mistralai/mixtral-8x7b-instruct", + model: "mistralai/mistral-7b-instruct", stream: true, max_tokens: 580, frequency_penalty: 1, From ecf935fd01cf76b4fabb596062a01dc2a4588eaf Mon Sep 17 00:00:00 2001 From: Michael Ryaboy Date: Thu, 28 Dec 2023 22:31:32 -0800 Subject: [PATCH 004/127] article summary --- app/proxy/page.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/proxy/page.tsx b/app/proxy/page.tsx index 23f9f5f8..eed2c628 100644 --- a/app/proxy/page.tsx +++ b/app/proxy/page.tsx @@ -184,7 +184,7 @@ async function Wrapper({ const siteText = directSiteText?.length > waybackSiteText.length ? direct.article?.content : wayback.article?.content; const prompt = - "Summarize the following in under 300 words: " + siteText + "Summary:"; + "Could you please provide a concise and comprehensive summary of the given text? The summary should capture the main points and key details of the text while conveying the author's intended meaning accurately. Please ensure that the summary is well-organized and easy to read, with clear headings and subheadings to guide the reader through each section. The length of the summary should be appropriate to capture the main points and key details of the text, without including unnecessary information or becoming overly long.\n\n" + siteText + "\n\nSummary:"; // See https://sdk.vercel.ai/docs/concepts/caching From 01d46eab68bf1a94b73f1ed41857ea08c348abe3 Mon Sep 17 00:00:00 2001 From: Michael Ryaboy Date: Tue, 30 Jan 2024 15:18:38 -0800 Subject: [PATCH 005/127] test --- app/layout.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/layout.tsx b/app/layout.tsx index 996b47d7..37b53803 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -7,7 +7,7 @@ import { Analytics } from "@vercel/analytics/react"; const inter = Inter({ subsets: ["latin"] }); export const metadata: Metadata = { - title: "smry", + title: "Smry: AI Summarizer and Free Paywall Remover", description: "Discover SMRY: an AI tool that not only summarizes long articles for quick comprehension but also skillfully navigates through paywalls, offering rapid access to restricted content.", }; From c33367a586686d26f5397fc15c79efbd465fdb06 Mon Sep 17 00:00:00 2001 From: Michael Ryaboy Date: Tue, 30 Jan 2024 15:21:54 -0800 Subject: [PATCH 006/127] title --- app/layout.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/layout.tsx b/app/layout.tsx index 37b53803..e47707cd 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -7,9 +7,9 @@ import { Analytics } from "@vercel/analytics/react"; const inter = Inter({ subsets: ["latin"] }); export const metadata: Metadata = { - title: "Smry: AI Summarizer and Free Paywall Remover", + title: "Smry | AI Summarizer and Free Paywall Remover", description: - "Discover SMRY: an AI tool that not only summarizes long articles for quick comprehension but also skillfully navigates through paywalls, offering rapid access to restricted content.", + "SMRY, remove paywalls and summarize articles for free with no login. Supports NYT, Washington Post, and thousands more.", }; export default function RootLayout({ From 8148ac7b1a59e181cebb3c57e41777ef5498f5ce Mon Sep 17 00:00:00 2001 From: Michael Ryaboy Date: Tue, 30 Jan 2024 15:30:42 -0800 Subject: [PATCH 007/127] prompt improve + rate limit increase --- app/proxy/page.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/proxy/page.tsx b/app/proxy/page.tsx index eed2c628..75e50854 100644 --- a/app/proxy/page.tsx +++ b/app/proxy/page.tsx @@ -184,7 +184,7 @@ async function Wrapper({ const siteText = directSiteText?.length > waybackSiteText.length ? direct.article?.content : wayback.article?.content; const prompt = - "Could you please provide a concise and comprehensive summary of the given text? The summary should capture the main points and key details of the text while conveying the author's intended meaning accurately. Please ensure that the summary is well-organized and easy to read, with clear headings and subheadings to guide the reader through each section. The length of the summary should be appropriate to capture the main points and key details of the text, without including unnecessary information or becoming overly long.\n\n" + siteText + "\n\nSummary:"; + "Provide a concise and comprehensive summary of the given text. The summary should capture the main points and key details of the text while conveying the author's intended meaning accurately. Please ensure that the summary is well-organized and easy to read, with clear headings and subheadings to guide the reader through each section. The length of the summary should be appropriate to capture the main points and key details of the text, without including unnecessary information or becoming overly long.\n\n" + siteText + "\n\nSummary:"; // See https://sdk.vercel.ai/docs/concepts/caching @@ -206,13 +206,13 @@ async function Wrapper({ ) { const dailyRatelimit = new Ratelimit({ redis: kv, - limiter: Ratelimit.slidingWindow(10, "1 d"), // 10 requests per day + limiter: Ratelimit.slidingWindow(20, "1 d"), // 10 requests per day }); // New rate limit for 4 requests per minute const minuteRatelimit = new Ratelimit({ redis: kv, - limiter: Ratelimit.slidingWindow(4, "1 m"), // 4 requests per minute + limiter: Ratelimit.slidingWindow(6, "1 m"), // 4 requests per minute }); // Usage for daily rate limit @@ -232,10 +232,10 @@ async function Wrapper({ } = await minuteRatelimit.limit(`13ft_ratelimit_minute_${ip}`); if (!dailySuccess) { - return "Your daily limit of 10 summaries has been reached. Although you can continue using smry.ai for reading, additional summaries are not available today. Please return tomorrow for more summaries. If this limit is inconvenient for you, your feedback is welcome at contact@smry.ai."; + return "Your daily limit of 20 summaries has been reached. Although you can continue using smry.ai for reading, additional summaries are not available today. Please return tomorrow for more summaries. If this limit is inconvenient for you, your feedback is welcome at contact@smry.ai."; } if (!minuteSuccess) { - return "Your limit of 4 summaries per minute has been reached. Pretty sure you are a bot. Stop it!"; + return "Your limit of 6 summaries per minute has been reached. Pretty sure you are a bot. Stop it!"; } } @@ -286,7 +286,7 @@ async function Wrapper({ messages: [ { role: "system", - content: "Be precise and concise in your responses.", + content: "You are an AI specializing in summarizing articles in a digestable way. Be precise and concise in your responses.", }, { role: "user", From ac8c639fcf57f155e4fcc1c4202f9558dd4e4f34 Mon Sep 17 00:00:00 2001 From: Michael Ryaboy Date: Tue, 30 Jan 2024 15:39:50 -0800 Subject: [PATCH 008/127] api change mixtral --- app/proxy/page.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/proxy/page.tsx b/app/proxy/page.tsx index 75e50854..d0f587a7 100644 --- a/app/proxy/page.tsx +++ b/app/proxy/page.tsx @@ -206,13 +206,13 @@ async function Wrapper({ ) { const dailyRatelimit = new Ratelimit({ redis: kv, - limiter: Ratelimit.slidingWindow(20, "1 d"), // 10 requests per day + limiter: Ratelimit.slidingWindow(20, "1 d"), // 20 requests per day }); // New rate limit for 4 requests per minute const minuteRatelimit = new Ratelimit({ redis: kv, - limiter: Ratelimit.slidingWindow(6, "1 m"), // 4 requests per minute + limiter: Ratelimit.slidingWindow(6, "1 m"), // 6 requests per minute }); // Usage for daily rate limit @@ -278,7 +278,7 @@ async function Wrapper({ Authorization: `Bearer ${process.env.OPENROUTER_API_KEY}`, }, body: JSON.stringify({ - model: "mistralai/mistral-7b-instruct", + model: "mistralai/mistral-7b-instruct:free", stream: true, max_tokens: 580, frequency_penalty: 1, @@ -286,7 +286,7 @@ async function Wrapper({ messages: [ { role: "system", - content: "You are an AI specializing in summarizing articles in a digestable way. Be precise and concise in your responses.", + content: "You are an AI summarizer specializing in summarizing articles in a digestable way. Be precise and concise in your responses.", }, { role: "user", From 6213870c4193ecf379ee13d0ff9d965b5c19bfa6 Mon Sep 17 00:00:00 2001 From: Michael Ryaboy Date: Tue, 30 Jan 2024 16:12:21 -0800 Subject: [PATCH 009/127] updates prompt --- app/proxy/page.tsx | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/app/proxy/page.tsx b/app/proxy/page.tsx index d0f587a7..c722ccfd 100644 --- a/app/proxy/page.tsx +++ b/app/proxy/page.tsx @@ -39,6 +39,26 @@ export type ResponseItem = { cacheURL: string; }; +async function fetchWithRetry(url: string, options: RequestInit | undefined, maxRetries = 3): Promise { + let lastError: Error | undefined; + + for (let i = 0; i < maxRetries; i++) { + try { + const response = await fetch(url, options); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + return response; // If the fetch is successful, return the response + } catch (error) { + lastError = error as Error; + console.log(`Retrying fetch (${i + 1}/${maxRetries})...`); + // Optional: You may want to implement a delay here + } + } + + throw lastError || new Error("Fetch failed after retrying"); +} + @@ -184,7 +204,7 @@ async function Wrapper({ const siteText = directSiteText?.length > waybackSiteText.length ? direct.article?.content : wayback.article?.content; const prompt = - "Provide a concise and comprehensive summary of the given text. The summary should capture the main points and key details of the text while conveying the author's intended meaning accurately. Please ensure that the summary is well-organized and easy to read, with clear headings and subheadings to guide the reader through each section. The length of the summary should be appropriate to capture the main points and key details of the text, without including unnecessary information or becoming overly long.\n\n" + siteText + "\n\nSummary:"; + "Provide a concise and comprehensive summary of the given text. The summary should capture the main points and key details of the text while conveying the author's intended meaning accurately. Please ensure that the summary is well-organized and easy to read. The length of the summary should be appropriate to capture the main points and key details of the text, without including unnecessary information or becoming overly long.\n\n" + siteText + "\n\nSummary:"; // See https://sdk.vercel.ai/docs/concepts/caching @@ -270,7 +290,9 @@ async function Wrapper({ // }), // }); - const response = await fetch("https://openrouter.ai/api/v1/chat/completions", { + + + const response = await fetchWithRetry("https://openrouter.ai/api/v1/chat/completions", { method: "POST", headers: { Accept: "application/json", @@ -278,7 +300,7 @@ async function Wrapper({ Authorization: `Bearer ${process.env.OPENROUTER_API_KEY}`, }, body: JSON.stringify({ - model: "mistralai/mistral-7b-instruct:free", + model: "huggingfaceh4/zephyr-7b-beta:free", stream: true, max_tokens: 580, frequency_penalty: 1, From aa7b317bc1fb76ebc409ce393a6e01f6ee2120d1 Mon Sep 17 00:00:00 2001 From: Michael Ryaboy Date: Fri, 2 Feb 2024 02:01:35 -0800 Subject: [PATCH 010/127] cleans up generative ai --- app/proxy/page.tsx | 141 ++++++++++++----------- components/responsiveDrawer.tsx | 98 ++++++++++++++++ components/top-bar.tsx | 2 +- components/ui/dialog.tsx | 122 ++++++++++++++++++++ components/ui/drawer.tsx | 118 +++++++++++++++++++ components/ui/sheet.tsx | 140 +++++++++++++++++++++++ lib/hooks/use-media-query.ts | 19 ++++ package.json | 2 + pnpm-lock.yaml | 196 ++++++++++++++++++++++++++++++++ 9 files changed, 772 insertions(+), 66 deletions(-) create mode 100644 components/responsiveDrawer.tsx create mode 100644 components/ui/dialog.tsx create mode 100644 components/ui/drawer.tsx create mode 100644 components/ui/sheet.tsx create mode 100644 lib/hooks/use-media-query.ts diff --git a/app/proxy/page.tsx b/app/proxy/page.tsx index c722ccfd..06104bcd 100644 --- a/app/proxy/page.tsx +++ b/app/proxy/page.tsx @@ -14,7 +14,8 @@ export const runtime = "edge"; import { Source, getData } from "@/lib/data"; import { ArticleLength } from "@/components/article-length"; import Loading from "./loading"; - +import { ResponsiveDrawer } from "@/components/responsiveDrawer"; +import { Button } from "@/components/ui/button"; const apiConfig = new Configuration({ apiKey: process.env.OPENAI_API_KEY!, @@ -39,29 +40,30 @@ export type ResponseItem = { cacheURL: string; }; -async function fetchWithRetry(url: string, options: RequestInit | undefined, maxRetries = 3): Promise { +async function fetchWithRetry( + url: string, + options: RequestInit | undefined, + maxRetries = 3 +): Promise { let lastError: Error | undefined; for (let i = 0; i < maxRetries; i++) { - try { - const response = await fetch(url, options); - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - return response; // If the fetch is successful, return the response - } catch (error) { - lastError = error as Error; - console.log(`Retrying fetch (${i + 1}/${maxRetries})...`); - // Optional: You may want to implement a delay here + try { + const response = await fetch(url, options); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); } + return response; // If the fetch is successful, return the response + } catch (error) { + lastError = error as Error; + console.log(`Retrying fetch (${i + 1}/${maxRetries})...`); + // Optional: You may want to implement a delay here + } } throw lastError || new Error("Fetch failed after retrying"); } - - - export default async function Page({ params, searchParams, @@ -88,7 +90,7 @@ export default async function Page({
-
- {"smry -
+ } > - + -
+
+
*/} +
+

+ Get AI-powered key points +

+ + + } + > + + +
waybackSiteText.length ? direct.article?.content : wayback.article?.content; + const siteText = + directSiteText?.length > waybackSiteText.length + ? direct.article?.content + : wayback.article?.content; const prompt = - "Provide a concise and comprehensive summary of the given text. The summary should capture the main points and key details of the text while conveying the author's intended meaning accurately. Please ensure that the summary is well-organized and easy to read. The length of the summary should be appropriate to capture the main points and key details of the text, without including unnecessary information or becoming overly long.\n\n" + siteText + "\n\nSummary:"; + "Provide a concise and comprehensive summary of the given text. The summary should capture the main points and key details of the text while conveying the author's intended meaning accurately. Please ensure that the summary is well-organized and easy to read. The length of the summary should be appropriate to capture the main points and key details of the text, without including unnecessary information or becoming overly long.\n\n" + + siteText + + "\n\nSummary:"; // See https://sdk.vercel.ai/docs/concepts/caching @@ -290,33 +299,35 @@ async function Wrapper({ // }), // }); - - - const response = await fetchWithRetry("https://openrouter.ai/api/v1/chat/completions", { - method: "POST", - headers: { - Accept: "application/json", - "Content-Type": "application/json", - Authorization: `Bearer ${process.env.OPENROUTER_API_KEY}`, - }, - body: JSON.stringify({ - model: "huggingfaceh4/zephyr-7b-beta:free", - stream: true, - max_tokens: 580, - frequency_penalty: 1, - temperature: 1, - messages: [ - { - role: "system", - content: "You are an AI summarizer specializing in summarizing articles in a digestable way. Be precise and concise in your responses.", - }, - { - role: "user", - content: decodedText, - }, - ], - }), - }); + const response = await fetchWithRetry( + "https://openrouter.ai/api/v1/chat/completions", + { + method: "POST", + headers: { + Accept: "application/json", + "Content-Type": "application/json", + Authorization: `Bearer ${process.env.OPENROUTER_API_KEY}`, + }, + body: JSON.stringify({ + model: "huggingfaceh4/zephyr-7b-beta:free", + stream: true, + max_tokens: 580, + frequency_penalty: 1, + temperature: 1, + messages: [ + { + role: "system", + content: + "You are an AI summarizer specializing in summarizing articles in a digestable way. Be precise and concise in your responses.", + }, + { + role: "user", + content: decodedText, + }, + ], + }), + } + ); // Convert the response into a friendly text-stream const stream = OpenAIStream(response, { diff --git a/components/responsiveDrawer.tsx b/components/responsiveDrawer.tsx new file mode 100644 index 00000000..cd92d1e2 --- /dev/null +++ b/components/responsiveDrawer.tsx @@ -0,0 +1,98 @@ +"use client"; + +import * as React from "react"; + +import { cn } from "@/lib/utils"; +import { useMediaQuery } from "@/lib/hooks/use-media-query"; +import { Button } from "@/components/ui/button"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog"; +import { + Drawer, + DrawerClose, + DrawerContent, + DrawerDescription, + DrawerFooter, + DrawerHeader, + DrawerTitle, + DrawerTrigger, +} from "@/components/ui/drawer"; +import { + Sheet, + SheetClose, + SheetContent, + SheetDescription, + SheetFooter, + SheetHeader, + SheetTitle, + SheetTrigger, +} from "@/components/ui/sheet"; + +export function ResponsiveDrawer({ children }: { children: React.ReactNode }) { + const [open, setOpen] = React.useState(false); + const isDesktop = useMediaQuery("(min-width: 1583px)"); + + if (isDesktop) { + return ( + + + + + + + Key Points From Page + Generative AI is Experimental + +
{children}
+
+
+ + // + // + // + // + // + // + // Key Points From Page + // Generative AI is Experimental + // + // {children} + // + // + ); + } + + return ( + + + + + + + Key Points From Page + Generative AI is Experimental + +
+ {children} +
+ + + + + +
+
+ ); +} diff --git a/components/top-bar.tsx b/components/top-bar.tsx index b61cac26..dd112ef6 100644 --- a/components/top-bar.tsx +++ b/components/top-bar.tsx @@ -51,7 +51,7 @@ const TopBar = () => {
diff --git a/components/ui/dialog.tsx b/components/ui/dialog.tsx new file mode 100644 index 00000000..01ff19c7 --- /dev/null +++ b/components/ui/dialog.tsx @@ -0,0 +1,122 @@ +"use client" + +import * as React from "react" +import * as DialogPrimitive from "@radix-ui/react-dialog" +import { X } from "lucide-react" + +import { cn } from "@/lib/utils" + +const Dialog = DialogPrimitive.Root + +const DialogTrigger = DialogPrimitive.Trigger + +const DialogPortal = DialogPrimitive.Portal + +const DialogClose = DialogPrimitive.Close + +const DialogOverlay = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DialogOverlay.displayName = DialogPrimitive.Overlay.displayName + +const DialogContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + {children} + + + Close + + + +)) +DialogContent.displayName = DialogPrimitive.Content.displayName + +const DialogHeader = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +DialogHeader.displayName = "DialogHeader" + +const DialogFooter = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +DialogFooter.displayName = "DialogFooter" + +const DialogTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DialogTitle.displayName = DialogPrimitive.Title.displayName + +const DialogDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DialogDescription.displayName = DialogPrimitive.Description.displayName + +export { + Dialog, + DialogPortal, + DialogOverlay, + DialogClose, + DialogTrigger, + DialogContent, + DialogHeader, + DialogFooter, + DialogTitle, + DialogDescription, +} diff --git a/components/ui/drawer.tsx b/components/ui/drawer.tsx new file mode 100644 index 00000000..6a0ef53d --- /dev/null +++ b/components/ui/drawer.tsx @@ -0,0 +1,118 @@ +"use client" + +import * as React from "react" +import { Drawer as DrawerPrimitive } from "vaul" + +import { cn } from "@/lib/utils" + +const Drawer = ({ + shouldScaleBackground = true, + ...props +}: React.ComponentProps) => ( + +) +Drawer.displayName = "Drawer" + +const DrawerTrigger = DrawerPrimitive.Trigger + +const DrawerPortal = DrawerPrimitive.Portal + +const DrawerClose = DrawerPrimitive.Close + +const DrawerOverlay = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DrawerOverlay.displayName = DrawerPrimitive.Overlay.displayName + +const DrawerContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + +
+ {children} + + +)) +DrawerContent.displayName = "DrawerContent" + +const DrawerHeader = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +DrawerHeader.displayName = "DrawerHeader" + +const DrawerFooter = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +DrawerFooter.displayName = "DrawerFooter" + +const DrawerTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DrawerTitle.displayName = DrawerPrimitive.Title.displayName + +const DrawerDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DrawerDescription.displayName = DrawerPrimitive.Description.displayName + +export { + Drawer, + DrawerPortal, + DrawerOverlay, + DrawerTrigger, + DrawerClose, + DrawerContent, + DrawerHeader, + DrawerFooter, + DrawerTitle, + DrawerDescription, +} diff --git a/components/ui/sheet.tsx b/components/ui/sheet.tsx new file mode 100644 index 00000000..ab52b7d7 --- /dev/null +++ b/components/ui/sheet.tsx @@ -0,0 +1,140 @@ +"use client" + +import * as React from "react" +import * as SheetPrimitive from "@radix-ui/react-dialog" +import { cva, type VariantProps } from "class-variance-authority" +import { X } from "lucide-react" + +import { cn } from "@/lib/utils" + +const Sheet = SheetPrimitive.Root + +const SheetTrigger = SheetPrimitive.Trigger + +const SheetClose = SheetPrimitive.Close + +const SheetPortal = SheetPrimitive.Portal + +const SheetOverlay = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +SheetOverlay.displayName = SheetPrimitive.Overlay.displayName + +const sheetVariants = cva( + "fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500", + { + variants: { + side: { + top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top", + bottom: + "inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom", + 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", + right: + "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", + }, + }, + defaultVariants: { + side: "right", + }, + } +) + +interface SheetContentProps + extends React.ComponentPropsWithoutRef, + VariantProps {} + +const SheetContent = React.forwardRef< + React.ElementRef, + SheetContentProps +>(({ side = "right", className, children, ...props }, ref) => ( + + {/* */} + + {children} + + + Close + + + +)) +SheetContent.displayName = SheetPrimitive.Content.displayName + +const SheetHeader = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +SheetHeader.displayName = "SheetHeader" + +const SheetFooter = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +SheetFooter.displayName = "SheetFooter" + +const SheetTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +SheetTitle.displayName = SheetPrimitive.Title.displayName + +const SheetDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +SheetDescription.displayName = SheetPrimitive.Description.displayName + +export { + Sheet, + SheetPortal, + SheetOverlay, + SheetTrigger, + SheetClose, + SheetContent, + SheetHeader, + SheetFooter, + SheetTitle, + SheetDescription, +} diff --git a/lib/hooks/use-media-query.ts b/lib/hooks/use-media-query.ts new file mode 100644 index 00000000..1eecad27 --- /dev/null +++ b/lib/hooks/use-media-query.ts @@ -0,0 +1,19 @@ +import * as React from "react" + +export function useMediaQuery(query: string) { + const [value, setValue] = React.useState(false) + + React.useEffect(() => { + function onChange(event: MediaQueryListEvent) { + setValue(event.matches) + } + + const result = matchMedia(query) + result.addEventListener("change", onChange) + setValue(result.matches) + + return () => result.removeEventListener("change", onChange) + }, [query]) + + return value +} \ No newline at end of file diff --git a/package.json b/package.json index 7d1e4317..39d70028 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "dependencies": { "@heroicons/react": "^2.0.18", "@mozilla/readability": "^0.4.4", + "@radix-ui/react-dialog": "^1.0.5", "@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-label": "^2.0.2", "@radix-ui/react-slot": "^1.0.2", @@ -46,6 +47,7 @@ "tailwind-merge": "^2.0.0", "tailwindcss-animate": "^1.0.7", "turndown": "^7.1.2", + "vaul": "^0.9.0", "zod": "^3.22.4" }, "devDependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9e38915f..e68538a7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,6 +11,9 @@ dependencies: '@mozilla/readability': specifier: ^0.4.4 version: 0.4.4 + '@radix-ui/react-dialog': + specifier: ^1.0.5 + version: 1.0.5(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-icons': specifier: ^1.3.0 version: 1.3.0(react@18.2.0) @@ -116,6 +119,9 @@ dependencies: turndown: specifier: ^7.1.2 version: 7.1.2 + vaul: + specifier: ^0.9.0 + version: 0.9.0(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0) zod: specifier: ^3.22.4 version: 3.22.4 @@ -582,6 +588,40 @@ packages: react: 18.2.0 dev: false + /@radix-ui/react-dialog@1.0.5(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-GjWJX/AUpB703eEBanuBnIWdIXg6NvJFCXcNlSZk4xdszCdhrJgBoUd1cGk67vFO+WdA2pfI/plOpqz/5GUP6Q==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@babel/runtime': 7.23.2 + '@radix-ui/primitive': 1.0.1 + '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.37)(react@18.2.0) + '@radix-ui/react-context': 1.0.1(@types/react@18.2.37)(react@18.2.0) + '@radix-ui/react-dismissable-layer': 1.0.5(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-focus-guards': 1.0.1(@types/react@18.2.37)(react@18.2.0) + '@radix-ui/react-focus-scope': 1.0.4(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-id': 1.0.1(@types/react@18.2.37)(react@18.2.0) + '@radix-ui/react-portal': 1.0.4(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-presence': 1.0.1(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-slot': 1.0.2(@types/react@18.2.37)(react@18.2.0) + '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.37)(react@18.2.0) + '@types/react': 18.2.37 + '@types/react-dom': 18.2.15 + aria-hidden: 1.2.3 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + react-remove-scroll: 2.5.5(@types/react@18.2.37)(react@18.2.0) + dev: false + /@radix-ui/react-direction@1.0.1(@types/react@18.2.37)(react@18.2.0): resolution: {integrity: sha512-RXcvnXgyvYvBEOhCBuddKecVkoMiI10Jcm5cTI7abJRAHYfFxeu+FBQs/DvdxSYucxR5mna0dNsL6QFlds5TMA==} peerDependencies: @@ -621,6 +661,43 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /@radix-ui/react-focus-guards@1.0.1(@types/react@18.2.37)(react@18.2.0): + resolution: {integrity: sha512-Rect2dWbQ8waGzhMavsIbmSVCgYxkXLxxR3ZvCX79JOglzdEy4JXMb98lq4hPxUbLr77nP0UOGf4rcMU+s1pUA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.23.2 + '@types/react': 18.2.37 + react: 18.2.0 + dev: false + + /@radix-ui/react-focus-scope@1.0.4(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-sL04Mgvf+FmyvZeYfNu1EPAaaxD+aw7cYeIB9L9Fvq8+urhltTRaEo5ysKOpHuKPclsZcSUMKlN05x4u+CINpA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@babel/runtime': 7.23.2 + '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.37)(react@18.2.0) + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.2.37)(react@18.2.0) + '@types/react': 18.2.37 + '@types/react-dom': 18.2.15 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /@radix-ui/react-icons@1.3.0(react@18.2.0): resolution: {integrity: sha512-jQxj/0LKgp+j9BiTXz3O3sgs26RNet2iLWmsPyRz2SIcR4q/4SbazXfnYwbAr+vLYKSfc7qxzyGQA1HLlYiuNw==} peerDependencies: @@ -1414,6 +1491,13 @@ packages: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} dev: true + /aria-hidden@1.2.3: + resolution: {integrity: sha512-xcLxITLe2HYa1cnYnwCjkOO1PqUHQpozB8x9AR0OgWN2woOBi5kSDVxKfd0b7sb1hw5qFeJhXm9H1nu3xSfLeQ==} + engines: {node: '>=10'} + dependencies: + tslib: 2.6.2 + dev: false + /aria-query@5.3.0: resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} dependencies: @@ -1938,6 +2022,10 @@ packages: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} + /detect-node-es@1.1.0: + resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} + dev: false + /devtools-protocol@0.0.1203626: resolution: {integrity: sha512-nEzHZteIUZfGCZtTiS1fRpC8UZmsfD1SiyPvaUNvS13dvKf666OAm8YTi0+Ca3n1nLEyu49Cy4+dPWpaHFJk9g==} dev: false @@ -2693,6 +2781,11 @@ packages: hasown: 2.0.0 dev: true + /get-nonce@1.0.1: + resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==} + engines: {node: '>=6'} + dev: false + /get-stream@5.2.0: resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==} engines: {node: '>=8'} @@ -3053,6 +3146,12 @@ packages: side-channel: 1.0.4 dev: true + /invariant@2.2.4: + resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==} + dependencies: + loose-envify: 1.4.0 + dev: false + /ip@1.1.8: resolution: {integrity: sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg==} dev: false @@ -4170,6 +4269,58 @@ packages: resolution: {integrity: sha512-+PbtI3VuDV0l6CleQMsx2gtK0JZbZKbpdu5ynr+lbsuvtmgbNcS3VM0tuY2QjFNOcWxvXeHjDpy42RO+4U2rug==} dev: false + /react-remove-scroll-bar@2.3.4(@types/react@18.2.37)(react@18.2.0): + resolution: {integrity: sha512-63C4YQBUt0m6ALadE9XV56hV8BgJWDmmTPY758iIJjfQKt2nYwoUrPk0LXRXcB/yIj82T1/Ixfdpdk68LwIB0A==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@types/react': 18.2.37 + react: 18.2.0 + react-style-singleton: 2.2.1(@types/react@18.2.37)(react@18.2.0) + tslib: 2.6.2 + dev: false + + /react-remove-scroll@2.5.5(@types/react@18.2.37)(react@18.2.0): + resolution: {integrity: sha512-ImKhrzJJsyXJfBZ4bzu8Bwpka14c/fQt0k+cyFp/PBhTfyDnU5hjOtM4AG/0AMyy8oKzOTR0lDgJIM7pYXI0kw==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@types/react': 18.2.37 + react: 18.2.0 + react-remove-scroll-bar: 2.3.4(@types/react@18.2.37)(react@18.2.0) + react-style-singleton: 2.2.1(@types/react@18.2.37)(react@18.2.0) + tslib: 2.6.2 + use-callback-ref: 1.3.1(@types/react@18.2.37)(react@18.2.0) + use-sidecar: 1.1.2(@types/react@18.2.37)(react@18.2.0) + dev: false + + /react-style-singleton@2.2.1(@types/react@18.2.37)(react@18.2.0): + resolution: {integrity: sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@types/react': 18.2.37 + get-nonce: 1.0.1 + invariant: 2.2.4 + react: 18.2.0 + tslib: 2.6.2 + dev: false + /react@18.2.0: resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==} engines: {node: '>=0.10.0'} @@ -4971,6 +5122,37 @@ packages: resolution: {integrity: sha512-WHN8KDQblxd32odxeIgo83rdVDE2bvdkb86it7bMhYZwWKJz0+O0RK/eZiHYnM+zgt/U7hAHOlCQGfjjvSkw2g==} dev: false + /use-callback-ref@1.3.1(@types/react@18.2.37)(react@18.2.0): + resolution: {integrity: sha512-Lg4Vx1XZQauB42Hw3kK7JM6yjVjgFmFC5/Ab797s79aARomD2nEErc4mCgM8EZrARLmmbWpi5DGCadmK50DcAQ==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@types/react': 18.2.37 + react: 18.2.0 + tslib: 2.6.2 + dev: false + + /use-sidecar@1.1.2(@types/react@18.2.37)(react@18.2.0): + resolution: {integrity: sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': ^16.9.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@types/react': 18.2.37 + detect-node-es: 1.1.0 + react: 18.2.0 + tslib: 2.6.2 + dev: false + /use-sync-external-store@1.2.0(react@18.2.0): resolution: {integrity: sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==} peerDependencies: @@ -4982,6 +5164,20 @@ packages: /util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + /vaul@0.9.0(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-bZSySGbAHiTXmZychprnX/dE0EsSige88xtyyL3/MCRbrFotRPQZo7UdydGXZWw+CKbNOw5Ow8gwAo93/nB/Cg==} + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + dependencies: + '@radix-ui/react-dialog': 1.0.5(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + transitivePeerDependencies: + - '@types/react' + - '@types/react-dom' + dev: false + /vue@3.3.8(typescript@5.2.2): resolution: {integrity: sha512-5VSX/3DabBikOXMsxzlW8JyfeLKlG9mzqnWgLQLty88vdZL7ZJgrdgBOmrArwxiLtmS+lNNpPcBYqrhE6TQW5w==} peerDependencies: From 5899a39820bb70fe5feb1607095bab8d8d5999aa Mon Sep 17 00:00:00 2001 From: Michael Ryaboy Date: Fri, 2 Feb 2024 02:08:16 -0800 Subject: [PATCH 011/127] track --- components/responsiveDrawer.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/components/responsiveDrawer.tsx b/components/responsiveDrawer.tsx index cd92d1e2..a2bc7d4c 100644 --- a/components/responsiveDrawer.tsx +++ b/components/responsiveDrawer.tsx @@ -33,6 +33,7 @@ import { SheetTitle, SheetTrigger, } from "@/components/ui/sheet"; +import { track } from "@vercel/analytics"; export function ResponsiveDrawer({ children }: { children: React.ReactNode }) { const [open, setOpen] = React.useState(false); @@ -42,7 +43,10 @@ export function ResponsiveDrawer({ children }: { children: React.ReactNode }) { return ( - From c2e913c4206a9bc123cc9f326745379c0cfb99b8 Mon Sep 17 00:00:00 2001 From: Michael Ryaboy Date: Fri, 2 Feb 2024 13:26:06 -0800 Subject: [PATCH 012/127] fixes drawer on mobile --- components/responsiveDrawer.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/responsiveDrawer.tsx b/components/responsiveDrawer.tsx index a2bc7d4c..035eba7e 100644 --- a/components/responsiveDrawer.tsx +++ b/components/responsiveDrawer.tsx @@ -88,7 +88,7 @@ export function ResponsiveDrawer({ children }: { children: React.ReactNode }) { Key Points From Page Generative AI is Experimental -
+
{children}
From 041af2684b63306ed0fb7315dce1d6dfcb8f2170 Mon Sep 17 00:00:00 2001 From: Michael Ryaboy Date: Sun, 11 Feb 2024 11:11:03 -0800 Subject: [PATCH 013/127] adds better error handling --- app/page.tsx | 34 +++++++++++++++++++++++++++++++++- lib/data.ts | 6 ++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/app/page.tsx b/app/page.tsx index 84713df5..1dbd8371 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -68,7 +68,39 @@ export default function Home() { return ( <> -
+
+
+ {" "} + {/* Add padding to the container */} + {/* Built by @michael_chomsky */} + Built by{" "} + + {" "} + {/* Adjust font size */} + @michael_chomsky. + {" "} + {/* Updated text and link */} + + {" "} + {/* Add margin to the text */} + Have feedback? + + + {" "} + {/* Adjust font size */} + Reach out here so we can improve + + . +
+
+
diff --git a/lib/data.ts b/lib/data.ts index 612f4a94..1d720ec6 100644 --- a/lib/data.ts +++ b/lib/data.ts @@ -10,6 +10,12 @@ export async function getData(url: string, source: Source) { url )}&source=${source}` ); + + if (!res.ok) { + // This will activate the closest `error.js` Error Boundary, but first we log it to analytics + track("Error", { urlBase: urlBase, fullUrl: url, status: res.status, source: source, error: res.statusText }); + throw new Error(`Error fetching data: ${res.statusText} for ${url} from ${source}`); + } return res.json(); } From becde49ca4857411c00f908e04658902e4b4ee9f Mon Sep 17 00:00:00 2001 From: Michael Ryaboy Date: Sun, 11 Feb 2024 11:25:18 -0800 Subject: [PATCH 014/127] adds better error handling --- app/proxy/page.tsx | 1 - lib/data.ts | 11 ++++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/app/proxy/page.tsx b/app/proxy/page.tsx index 06104bcd..45107983 100644 --- a/app/proxy/page.tsx +++ b/app/proxy/page.tsx @@ -15,7 +15,6 @@ import { Source, getData } from "@/lib/data"; import { ArticleLength } from "@/components/article-length"; import Loading from "./loading"; import { ResponsiveDrawer } from "@/components/responsiveDrawer"; -import { Button } from "@/components/ui/button"; const apiConfig = new Configuration({ apiKey: process.env.OPENAI_API_KEY!, diff --git a/lib/data.ts b/lib/data.ts index 1d720ec6..562137ca 100644 --- a/lib/data.ts +++ b/lib/data.ts @@ -1,7 +1,9 @@ import { track } from "@vercel/analytics/server"; +import { ResponseItem } from "@/app/proxy/page"; export type Source = "direct" | "google" | "wayback"; + export async function getData(url: string, source: Source) { const urlBase = new URL(url).hostname; track("Search", { urlBase: urlBase, fullUrl: url }); @@ -14,7 +16,14 @@ export async function getData(url: string, source: Source) { if (!res.ok) { // This will activate the closest `error.js` Error Boundary, but first we log it to analytics track("Error", { urlBase: urlBase, fullUrl: url, status: res.status, source: source, error: res.statusText }); - throw new Error(`Error fetching data: ${res.statusText} for ${url} from ${source}`); + return { + source: source, + article: undefined, + status: res.status.toString(), + error: res.statusText, + cacheURL: url + } as ResponseItem; + // throw new Error(`Error fetching data: ${res.statusText} for ${url} from ${source}`); } return res.json(); From 9350b550d7e88e7607cdae2c9a0ebff3e196bf4f Mon Sep 17 00:00:00 2001 From: Michael Ryaboy Date: Thu, 15 Feb 2024 16:05:42 -0800 Subject: [PATCH 015/127] removes banner --- app/page.tsx | 32 -------------------------------- 1 file changed, 32 deletions(-) diff --git a/app/page.tsx b/app/page.tsx index 1dbd8371..78f4a181 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -68,38 +68,6 @@ export default function Home() { return ( <> -
-
- {" "} - {/* Add padding to the container */} - {/* Built by @michael_chomsky */} - Built by{" "} - - {" "} - {/* Adjust font size */} - @michael_chomsky. - {" "} - {/* Updated text and link */} - - {" "} - {/* Add margin to the text */} - Have feedback? - - - {" "} - {/* Adjust font size */} - Reach out here so we can improve - - . -
-
From b2f4ec784f44168ec49ab9a0e4ba62a99738a50f Mon Sep 17 00:00:00 2001 From: Michael Ryaboy Date: Sun, 18 Feb 2024 21:25:45 -0800 Subject: [PATCH 016/127] changes background --- app/page.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/page.tsx b/app/page.tsx index 78f4a181..f3e1f1cc 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -68,8 +68,7 @@ export default function Home() { return ( <> -
-
+
Date: Tue, 5 Mar 2024 14:51:12 -0800 Subject: [PATCH 017/127] comments out all vercel analytics events --- app/api/direct/route.ts | 39 +++++++++++++++------------- app/proxy/error.tsx | 8 +++--- components/article-content.tsx | 2 +- components/responsiveDrawer.tsx | 2 +- lib/data.ts | 4 +-- lib/fetch-with-timeout.ts | 45 +++++++++++++++++++-------------- 6 files changed, 56 insertions(+), 44 deletions(-) diff --git a/app/api/direct/route.ts b/app/api/direct/route.ts index a9833699..52d857cf 100644 --- a/app/api/direct/route.ts +++ b/app/api/direct/route.ts @@ -1,7 +1,7 @@ -import { JSDOM } from "jsdom"; import { Readability } from "@mozilla/readability"; import { fetchWithTimeout } from "@/lib/fetch-with-timeout"; import { safeError } from "@/lib/safe-error"; +import jsdom from 'jsdom'; function createErrorResponse(message: string, status: number, details = {}) { return new Response(JSON.stringify({ message, details }), { @@ -48,29 +48,34 @@ export async function GET(request: Request) { if (!response.ok) { // throw new Error(`HTTP error! status: ${response.status}`); + console.log(`HTTP error! status: ${response.status} url: ${urlWithSource} statusText: ${JSON.stringify(response.statusText)}`); - return new Response(JSON.stringify({ - url: urlWithSource, - cacheURL: url, - error: `HTTP error! status: ${response.status}`, - status: "error", - contentLength: 0, - }), { - headers: { "Content-Type": "application/json" }, - status: response.status, - }); + return new Response( + JSON.stringify({ + url: urlWithSource, + cacheURL: url, + error: `HTTP error! status: ${response.status}`, + status: "error", + contentLength: 0, + }), + { + headers: { "Content-Type": "application/json" }, + status: response.status, + } + ); } const html = await response.text(); - const doc = new JSDOM(html); + const { JSDOM } = jsdom; + const virtualConsole = new jsdom.VirtualConsole(); + virtualConsole.on("error", () => { + // No-op to skip console errors. + }); + const doc = new JSDOM(html, { virtualConsole }); + const reader = new Readability(doc.window.document); const article = reader.parse(); - // // if source is 'wayback', then await a timeout of 10s - // if (source === 'wayback') { - // await new Promise(resolve => setTimeout(resolve, 10000)); - // } - const resp = { source, cacheURL: url, diff --git a/app/proxy/error.tsx b/app/proxy/error.tsx index 8bb23ac0..b7f87b4f 100644 --- a/app/proxy/error.tsx +++ b/app/proxy/error.tsx @@ -16,10 +16,10 @@ export default function Error({ const pathname = usePathname() - useEffect(() => { - // Log the error to an error reporting service - track('Proxy error', { location: pathname }); - }, [error, pathname]) + // useEffect(() => { + // // Log the error to an error reporting service + // track('Proxy error', { location: pathname }); + // }, [error, pathname]) return (
diff --git a/components/article-content.tsx b/components/article-content.tsx index 9a5213c0..e38a44b2 100644 --- a/components/article-content.tsx +++ b/components/article-content.tsx @@ -31,7 +31,7 @@ export const ArticleContent = async ({ url, source }: ArticleContentProps) => { /> } > -

{content.article?.title || "No Title"}

+

{content.article?.title || "Title Not Found"}

diff --git a/components/responsiveDrawer.tsx b/components/responsiveDrawer.tsx index 035eba7e..6d801cad 100644 --- a/components/responsiveDrawer.tsx +++ b/components/responsiveDrawer.tsx @@ -44,7 +44,7 @@ export function ResponsiveDrawer({ children }: { children: React.ReactNode }) { +

+ smry.ai -{" "} + Writing doesn't have to suck. Checkout{" "} + + Jotbot, an AI writing assistant + + +

+
+
+ ) : null + ); +}; + +export default Ad; \ No newline at end of file From 7d69df5cb3037b71fdb8967b69a897397fd7bb03 Mon Sep 17 00:00:00 2001 From: Michael Ryaboy Date: Sun, 24 Mar 2024 06:18:46 -0700 Subject: [PATCH 020/127] adds ad --- components/ad.tsx | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/components/ad.tsx b/components/ad.tsx index 35cddbfb..c4a2ab48 100644 --- a/components/ad.tsx +++ b/components/ad.tsx @@ -1,4 +1,5 @@ "use client"; +import { track } from '@vercel/analytics'; import React from "react"; import { XIcon } from "lucide-react"; @@ -7,9 +8,15 @@ const Ad = () => { const [showAd, setShowAd] = React.useState(true); return ( showAd ? ( -