HomePilot Deploy Bot commited on
fix(community): real persona thumbnails + proxy-route gallery previews
Browse files- .gitattributes +3 -3
- community/sample/david/assets/thumb_avatar_david.webp +2 -2
- community/sample/diana/assets/thumb_avatar_diana.webp +2 -2
- community/sample/elena/assets/thumb_avatar_elena.webp +2 -2
- community/sample/felix/assets/thumb_avatar_felix.webp +2 -2
- community/sample/kai/assets/thumb_avatar_kai.webp +2 -2
- community/sample/luca/assets/thumb_avatar_luca.webp +2 -2
- community/sample/maya/assets/thumb_avatar_maya.webp +2 -2
- community/sample/nora/assets/thumb_avatar_nora.webp +2 -2
- community/sample/priya/assets/thumb_avatar_priya.webp +2 -2
- community/sample/raven/assets/thumb_avatar_raven.webp +2 -2
- community/sample/soren/assets/thumb_avatar_soren.webp +2 -2
- frontend/.cache-bust +1 -1
- frontend/src/ui/CommunityGallery.tsx +39 -3
.gitattributes
CHANGED
|
@@ -1,9 +1,9 @@
|
|
| 1 |
*.png filter=lfs diff=lfs merge=lfs -text
|
| 2 |
*.webp filter=lfs diff=lfs merge=lfs -text
|
| 3 |
-
*.
|
|
|
|
| 4 |
*.hpersona filter=lfs diff=lfs merge=lfs -text
|
| 5 |
*.jpg filter=lfs diff=lfs merge=lfs -text
|
| 6 |
*.jpeg filter=lfs diff=lfs merge=lfs -text
|
| 7 |
-
*.svg filter=lfs diff=lfs merge=lfs -text
|
| 8 |
-
*.woff filter=lfs diff=lfs merge=lfs -text
|
| 9 |
*.woff2 filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
| 1 |
*.png filter=lfs diff=lfs merge=lfs -text
|
| 2 |
*.webp filter=lfs diff=lfs merge=lfs -text
|
| 3 |
+
*.svg filter=lfs diff=lfs merge=lfs -text
|
| 4 |
+
*.woff filter=lfs diff=lfs merge=lfs -text
|
| 5 |
*.hpersona filter=lfs diff=lfs merge=lfs -text
|
| 6 |
*.jpg filter=lfs diff=lfs merge=lfs -text
|
| 7 |
*.jpeg filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
| 8 |
*.woff2 filter=lfs diff=lfs merge=lfs -text
|
| 9 |
+
*.ico filter=lfs diff=lfs merge=lfs -text
|
community/sample/david/assets/thumb_avatar_david.webp
CHANGED
|
|
Git LFS Details
|
|
|
Git LFS Details
|
community/sample/diana/assets/thumb_avatar_diana.webp
CHANGED
|
|
Git LFS Details
|
|
|
Git LFS Details
|
community/sample/elena/assets/thumb_avatar_elena.webp
CHANGED
|
|
Git LFS Details
|
|
|
Git LFS Details
|
community/sample/felix/assets/thumb_avatar_felix.webp
CHANGED
|
|
Git LFS Details
|
|
|
Git LFS Details
|
community/sample/kai/assets/thumb_avatar_kai.webp
CHANGED
|
|
Git LFS Details
|
|
|
Git LFS Details
|
community/sample/luca/assets/thumb_avatar_luca.webp
CHANGED
|
|
Git LFS Details
|
|
|
Git LFS Details
|
community/sample/maya/assets/thumb_avatar_maya.webp
CHANGED
|
|
Git LFS Details
|
|
|
Git LFS Details
|
community/sample/nora/assets/thumb_avatar_nora.webp
CHANGED
|
|
Git LFS Details
|
|
|
Git LFS Details
|
community/sample/priya/assets/thumb_avatar_priya.webp
CHANGED
|
|
Git LFS Details
|
|
|
Git LFS Details
|
community/sample/raven/assets/thumb_avatar_raven.webp
CHANGED
|
|
Git LFS Details
|
|
|
Git LFS Details
|
community/sample/soren/assets/thumb_avatar_soren.webp
CHANGED
|
|
Git LFS Details
|
|
|
Git LFS Details
|
frontend/.cache-bust
CHANGED
|
@@ -1 +1 @@
|
|
| 1 |
-
|
|
|
|
| 1 |
+
1783159161
|
frontend/src/ui/CommunityGallery.tsx
CHANGED
|
@@ -64,17 +64,45 @@ type CommunityGalleryProps = {
|
|
| 64 |
onInstalled?: () => void // refresh project list after install
|
| 65 |
}
|
| 66 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 67 |
// ---------------------------------------------------------------------------
|
| 68 |
// Gallery Card
|
| 69 |
// ---------------------------------------------------------------------------
|
| 70 |
|
| 71 |
function GalleryCard({
|
| 72 |
item,
|
|
|
|
| 73 |
installing,
|
| 74 |
onInstall,
|
| 75 |
onDetail,
|
| 76 |
}: {
|
| 77 |
item: CommunityPersonaItem
|
|
|
|
| 78 |
installing: boolean
|
| 79 |
onInstall: () => void
|
| 80 |
onDetail: () => void
|
|
@@ -90,13 +118,20 @@ function GalleryCard({
|
|
| 90 |
<div className="relative w-full max-w-[340px] bg-white/[0.04] border border-white/[0.08] rounded-xl overflow-hidden hover:border-white/[0.16] transition-all group">
|
| 91 |
{/* Preview */}
|
| 92 |
<div className="relative aspect-[4/5] bg-black/20 overflow-hidden">
|
| 93 |
-
{
|
| 94 |
<img
|
| 95 |
-
src={
|
| 96 |
alt={item.name}
|
| 97 |
loading="lazy"
|
| 98 |
className="block w-full h-full object-cover"
|
| 99 |
onError={(e) => {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 100 |
;(e.target as HTMLImageElement).style.display = 'none'
|
| 101 |
}}
|
| 102 |
/>
|
|
@@ -849,6 +884,7 @@ export function CommunityGallery({ backendUrl, apiKey, onInstalled }: CommunityG
|
|
| 849 |
<GalleryCard
|
| 850 |
key={item.id}
|
| 851 |
item={item}
|
|
|
|
| 852 |
installing={
|
| 853 |
(install.kind === 'downloading' || install.kind === 'previewing' || install.kind === 'installing')
|
| 854 |
&& install.personaId === item.id
|
|
@@ -876,7 +912,7 @@ export function CommunityGallery({ backendUrl, apiKey, onInstalled }: CommunityG
|
|
| 876 |
<DetailModal
|
| 877 |
card={detail.card}
|
| 878 |
item={detail.item}
|
| 879 |
-
previewUrl={detail.item
|
| 880 |
onClose={() => setDetail(null)}
|
| 881 |
onInstall={() => handleInstall(detail.item)}
|
| 882 |
/>
|
|
|
|
| 64 |
onInstalled?: () => void // refresh project list after install
|
| 65 |
}
|
| 66 |
|
| 67 |
+
// ---------------------------------------------------------------------------
|
| 68 |
+
// Preview URL resolution
|
| 69 |
+
// ---------------------------------------------------------------------------
|
| 70 |
+
|
| 71 |
+
/**
|
| 72 |
+
* Resolve the thumbnail URL for a community persona.
|
| 73 |
+
*
|
| 74 |
+
* Always prefer the backend proxy route `/community/preview/{id}/{version}`:
|
| 75 |
+
* the backend serves the bundled local asset first and transparently falls
|
| 76 |
+
* back to the remote gallery, so the frontend never depends on (or leaks) an
|
| 77 |
+
* external Worker/R2 URL that may be missing, stale, or blocked. Only when we
|
| 78 |
+
* can't build the proxy route (no id/version) do we fall back to the raw
|
| 79 |
+
* `preview_url` from the registry.
|
| 80 |
+
*/
|
| 81 |
+
function resolvePreviewUrl(
|
| 82 |
+
backendUrl: string,
|
| 83 |
+
item: CommunityPersonaItem,
|
| 84 |
+
): string | undefined {
|
| 85 |
+
const version = item.latest?.version
|
| 86 |
+
if (item.id && version) {
|
| 87 |
+
const base = backendUrl.replace(/\/+$/, '')
|
| 88 |
+
return `${base}/community/preview/${encodeURIComponent(item.id)}/${encodeURIComponent(version)}`
|
| 89 |
+
}
|
| 90 |
+
return item.latest?.preview_url || undefined
|
| 91 |
+
}
|
| 92 |
+
|
| 93 |
// ---------------------------------------------------------------------------
|
| 94 |
// Gallery Card
|
| 95 |
// ---------------------------------------------------------------------------
|
| 96 |
|
| 97 |
function GalleryCard({
|
| 98 |
item,
|
| 99 |
+
previewUrl,
|
| 100 |
installing,
|
| 101 |
onInstall,
|
| 102 |
onDetail,
|
| 103 |
}: {
|
| 104 |
item: CommunityPersonaItem
|
| 105 |
+
previewUrl?: string
|
| 106 |
installing: boolean
|
| 107 |
onInstall: () => void
|
| 108 |
onDetail: () => void
|
|
|
|
| 118 |
<div className="relative w-full max-w-[340px] bg-white/[0.04] border border-white/[0.08] rounded-xl overflow-hidden hover:border-white/[0.16] transition-all group">
|
| 119 |
{/* Preview */}
|
| 120 |
<div className="relative aspect-[4/5] bg-black/20 overflow-hidden">
|
| 121 |
+
{previewUrl ? (
|
| 122 |
<img
|
| 123 |
+
src={previewUrl}
|
| 124 |
alt={item.name}
|
| 125 |
loading="lazy"
|
| 126 |
className="block w-full h-full object-cover"
|
| 127 |
onError={(e) => {
|
| 128 |
+
if (import.meta.env.DEV) {
|
| 129 |
+
console.warn(
|
| 130 |
+
`[CommunityGallery] preview failed for ${item.id}@${item.latest?.version}:`,
|
| 131 |
+
previewUrl,
|
| 132 |
+
)
|
| 133 |
+
}
|
| 134 |
+
// Last-resort: hide the broken image so the initials placeholder shows.
|
| 135 |
;(e.target as HTMLImageElement).style.display = 'none'
|
| 136 |
}}
|
| 137 |
/>
|
|
|
|
| 884 |
<GalleryCard
|
| 885 |
key={item.id}
|
| 886 |
item={item}
|
| 887 |
+
previewUrl={resolvePreviewUrl(cleanUrl, item)}
|
| 888 |
installing={
|
| 889 |
(install.kind === 'downloading' || install.kind === 'previewing' || install.kind === 'installing')
|
| 890 |
&& install.personaId === item.id
|
|
|
|
| 912 |
<DetailModal
|
| 913 |
card={detail.card}
|
| 914 |
item={detail.item}
|
| 915 |
+
previewUrl={resolvePreviewUrl(cleanUrl, detail.item)}
|
| 916 |
onClose={() => setDetail(null)}
|
| 917 |
onInstall={() => handleInstall(detail.item)}
|
| 918 |
/>
|