Skip to main content
Glama

MCP-RSS-Crawler

by mshk
FeedItemDetail.tsx7.95 kB
import { useEffect, useState } from 'react'; import { FeedItem } from '@/lib/db'; import Link from 'next/link'; import ReactMarkdown from 'react-markdown'; interface FeedItemDetailProps { itemId: string; } interface ArticleContent { id: string; url: string; title: string; content: string; html: string; author: string; published_date: string; image_url: string; summary: string; fetched_at: number; } export default function FeedItemDetail({ itemId }: FeedItemDetailProps) { const [item, setItem] = useState<FeedItem | null>(null); const [article, setArticle] = useState<ArticleContent | null>(null); const [loading, setLoading] = useState(true); const [error, setError] = useState<string | null>(null); const [fetchingContent, setFetchingContent] = useState(false); useEffect(() => { const fetchItem = async () => { try { setLoading(true); setArticle(null); console.log('Fetching item with ID:', itemId); const response = await fetch(`/api/items?id=${encodeURIComponent(itemId)}`); console.log('fetchItem url', itemId, 'response:', response); if (!response.ok) { if (response.status === 404) { throw new Error('Item not found'); } throw new Error('Failed to fetch item'); } const data = await response.json(); console.log('fetchItem data:', data); console.log('Item content:', data.content); console.log('Item summary:', data.summary); console.log('Item link:', data.link); setItem(data); setLoading(false); // After getting the item, fetch the full content only if a valid link is available if (data && data.link) { console.log('Calling fetchArticleContent with link:', data.link); fetchArticleContent(data.link); } else { console.log('No valid link available for item, cannot fetch article content'); setFetchingContent(false); } } catch (err) { console.error('Error in fetchItem:', err); setError(err instanceof Error ? err.message : 'An error occurred'); setLoading(false); } }; fetchItem(); }, [itemId]); // Function to fetch article content via firecrawl const fetchArticleContent = async (url: string) => { console.log('Fetching article content for:', url); try { setFetchingContent(true); // Check if we already have the article in the database console.log('Checking if article exists in database'); const articleResponse = await fetch(`/api/articles?url=${encodeURIComponent(url)}`); console.log('Article response status:', articleResponse.status); if (articleResponse.ok) { const articleData = await articleResponse.json(); console.log('Article data from database:', articleData); if (articleData && (articleData.html || articleData.content)) { console.log('Fetched article from database:', articleData); setArticle(articleData); setFetchingContent(false); return; } } // If article is not in the database, fetch it using the API console.log('Article not found in database, fetching from API'); const fetchResponse = await fetch('/api/articles/fetch', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ url }), }); if (fetchResponse.ok) { const fetchedArticle = await fetchResponse.json(); console.log('Fetched article from API:', fetchedArticle); setArticle(fetchedArticle); } else { console.error('Failed to fetch article from API'); } setFetchingContent(false); } catch (err) { console.error('Error fetching article content:', err); setFetchingContent(false); } }; if (loading) { return ( <div className="p-6 max-w-4xl mx-auto"> <div className="animate-pulse"> <div className="h-8 bg-gray-300 rounded w-3/4 mb-4"></div> <div className="h-4 bg-gray-300 rounded w-1/4 mb-6"></div> <div className="h-4 bg-gray-300 rounded w-full mb-2"></div> <div className="h-4 bg-gray-300 rounded w-full mb-2"></div> <div className="h-4 bg-gray-300 rounded w-full mb-2"></div> <div className="h-4 bg-gray-300 rounded w-3/4 mb-2"></div> </div> </div> ); } if (error) { return ( <div className="p-6 max-w-4xl mx-auto"> <div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded"> <p>Error: {error}</p> </div> </div> ); } if (!item) { return ( <div className="p-6 max-w-4xl mx-auto"> <div className="bg-yellow-100 border border-yellow-400 text-yellow-700 px-4 py-3 rounded"> <p>Item not found.</p> </div> </div> ); } // Format the published date const formattedDate = new Date(item.published * 1000).toLocaleString(); return ( <div className="p-6 max-w-4xl mx-auto"> <div className="mb-6"> <h1 className="text-2xl font-bold mb-2">{item.title}</h1> <div className="flex justify-between items-center mb-4"> <div className="text-gray-600"> {item.feed_title && ( <span> From: <Link href={`/feed/${encodeURIComponent(item.feed_id)}`} className="text-blue-600 hover:underline"> {item.feed_title} </Link> </span> )} {item.author && <span> • By {item.author}</span>} </div> <div className="text-gray-500">{formattedDate}</div> </div> {item.categories && item.categories.length > 0 && ( <div className="flex gap-2 mb-4"> {item.categories.map(category => ( <span key={category} className="bg-gray-200 px-2 py-1 rounded text-sm"> {category} </span> ))} </div> )} {item.link && ( <div className="mb-6"> <a href={item.link} target="_blank" rel="noopener noreferrer" className="bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700 inline-block" > Read Original Article </a> </div> )} </div> <div className="prose prose-lg max-w-none"> {fetchingContent && ( <div className="flex flex-col items-center justify-center p-6"> <div className="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-blue-500 mb-4"></div> <p className="text-lg text-gray-600">コンテンツをロード中</p> </div> )} {!fetchingContent && article && article.html ? ( <div dangerouslySetInnerHTML={{ __html: article.html }} /> ) : !fetchingContent && article && article.content ? ( <ReactMarkdown>{article.content}</ReactMarkdown> ) : !fetchingContent && (item.content || item.summary) ? ( <div dangerouslySetInnerHTML={{ __html: item.content || item.summary || '' }} /> ) : !fetchingContent ? ( <div className="text-center p-6"> <p className="text-gray-600 mb-4">この記事のコンテンツは利用できません。</p> {item.link ? ( <p className="text-gray-600">元の記事を読むには、上の「Read Original Article」ボタンをクリックしてください。</p> ) : ( <p className="text-gray-600">この記事の元のURLは利用できません。</p> )} </div> ) : null} </div> </div> ); }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/mshk/mcp-rss-crawler'

If you have feedback or need assistance with the MCP directory API, please join our Discord server