-
Notifications
You must be signed in to change notification settings - Fork 51
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Co-authored-by: Alex Hancock <[email protected]>
- Loading branch information
1 parent
0e1368a
commit 5eec775
Showing
11 changed files
with
320 additions
and
26 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,158 @@ | ||
import React, { useEffect, useState } from 'react'; | ||
import { Card } from './ui/card'; | ||
|
||
interface Metadata { | ||
title?: string; | ||
description?: string; | ||
favicon?: string; | ||
image?: string; | ||
url: string; | ||
} | ||
|
||
interface LinkPreviewProps { | ||
url: string; | ||
} | ||
|
||
async function fetchMetadata(url: string): Promise<Metadata> { | ||
console.log('🔄 Fetching metadata for URL:', url); | ||
|
||
try { | ||
// Fetch the HTML content using the main process | ||
const html = await window.electron.fetchMetadata(url); | ||
const parser = new DOMParser(); | ||
const doc = parser.parseFromString(html, 'text/html'); | ||
const baseUrl = new URL(url); | ||
|
||
// Extract title | ||
const title = | ||
doc.querySelector('title')?.textContent || | ||
doc.querySelector('meta[property="og:title"]')?.getAttribute('content'); | ||
|
||
// Extract description | ||
const description = | ||
doc.querySelector('meta[name="description"]')?.getAttribute('content') || | ||
doc.querySelector('meta[property="og:description"]')?.getAttribute('content'); | ||
|
||
// Extract favicon | ||
const faviconLink = | ||
doc.querySelector('link[rel="icon"]') || | ||
doc.querySelector('link[rel="shortcut icon"]') || | ||
doc.querySelector('link[rel="apple-touch-icon"]') || | ||
doc.querySelector('link[rel="apple-touch-icon-precomposed"]'); | ||
|
||
let favicon = faviconLink?.getAttribute('href'); | ||
if (favicon) { | ||
favicon = new URL(favicon, baseUrl).toString(); | ||
} else { | ||
// Fallback to /favicon.ico | ||
favicon = new URL('/favicon.ico', baseUrl).toString(); | ||
} | ||
|
||
// Extract OpenGraph image | ||
let image = doc.querySelector('meta[property="og:image"]')?.getAttribute('content'); | ||
if (image) { | ||
image = new URL(image, baseUrl).toString(); | ||
} | ||
|
||
console.log('✨ Extracted metadata:', { title, description, favicon, image }); | ||
|
||
return { | ||
title: title || url, | ||
description, | ||
favicon, | ||
image, | ||
url | ||
}; | ||
} catch (error) { | ||
console.error('❌ Error fetching metadata:', error); | ||
return { | ||
title: url, | ||
description: undefined, | ||
favicon: undefined, | ||
image: undefined, | ||
url | ||
}; | ||
} | ||
} | ||
|
||
export default function LinkPreview({ url }: LinkPreviewProps) { | ||
const [metadata, setMetadata] = useState<Metadata | null>(null); | ||
const [loading, setLoading] = useState(true); | ||
const [error, setError] = useState<string | null>(null); | ||
|
||
useEffect(() => { | ||
console.log('🔄 LinkPreview mounting for URL:', url); | ||
let mounted = true; | ||
|
||
const fetchData = async () => { | ||
try { | ||
const data = await fetchMetadata(url); | ||
if (mounted) { | ||
console.log('✨ Received metadata:', data); | ||
setMetadata(data); | ||
} | ||
} catch (error) { | ||
if (mounted) { | ||
console.error('❌ Failed to fetch metadata:', error); | ||
setError(error.message || 'Failed to fetch metadata'); | ||
} | ||
} finally { | ||
if (mounted) { | ||
setLoading(false); | ||
} | ||
} | ||
}; | ||
|
||
fetchData(); | ||
return () => { mounted = false; }; | ||
}, [url]); | ||
|
||
if (loading) { | ||
return null; | ||
} | ||
|
||
if (error) { | ||
return null; | ||
} | ||
|
||
if (!metadata || !metadata.title) { | ||
return null; | ||
} | ||
|
||
return ( | ||
<Card | ||
className="max-w-[300px] truncate flex items-center bg-link-preview p-3 transition-colors cursor-pointer" | ||
onClick={() => { | ||
console.log('🔗 Opening URL in Chrome:', url); | ||
window.electron.openInChrome(url); | ||
}} | ||
> | ||
{metadata.favicon && ( | ||
<img | ||
src={metadata.favicon} | ||
alt="Site favicon" | ||
className="w-4 h-4 mr-2" | ||
onError={(e) => { | ||
e.currentTarget.style.display = 'none'; | ||
}} | ||
/> | ||
)} | ||
<div className="flex-1 min-w-0"> | ||
<h4 className="text-sm font-medium truncate">{metadata.title || url}</h4> | ||
{metadata.description && ( | ||
<p className="text-xs text-gray-500 truncate">{metadata.description}</p> | ||
)} | ||
</div> | ||
{metadata.image && ( | ||
<img | ||
src={metadata.image} | ||
alt="Preview" | ||
className="w-16 h-16 object-cover rounded ml-3" | ||
onError={(e) => { | ||
e.currentTarget.style.display = 'none'; | ||
}} | ||
/> | ||
)} | ||
</Card> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,13 +1,27 @@ | ||
|
||
import React from 'react' | ||
import ReactMarkdown from 'react-markdown' | ||
import LinkPreview from './LinkPreview' | ||
import { extractUrls } from '../utils/urlUtils' | ||
|
||
export default function UserMessage({ message }) { | ||
// Extract URLs from current message | ||
const urls = extractUrls(message.content, []); // No previous URLs to check against | ||
console.log('User message URLs:', urls); | ||
|
||
return ( | ||
<div className="flex justify-end mb-[16px]"> | ||
<div className="bg-user-bubble text-white rounded-2xl p-4"> | ||
<ReactMarkdown>{message.content}</ReactMarkdown> | ||
<div className="flex flex-col"> | ||
<div className="bg-user-bubble text-white rounded-2xl p-4"> | ||
<ReactMarkdown>{message.content}</ReactMarkdown> | ||
</div> | ||
{urls.length > 0 && ( | ||
<div className="mt-2"> | ||
{urls.map((url, index) => ( | ||
<LinkPreview key={index} url={url} /> | ||
))} | ||
</div> | ||
)} | ||
</div> | ||
</div> | ||
) | ||
}; | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.