Skip to content

Commit 9a6c866

Browse files
authored
feat: simple og image (#176)
* feat: simple og image * feat: simple og image * fix: redirect to thread landing on homepage * fix: link to terms
1 parent b8df4fb commit 9a6c866

File tree

11 files changed

+129
-10
lines changed

11 files changed

+129
-10
lines changed

apps/masterbots.ai/.env.local

-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ NEXT_PUBLIC_APP_ENV=test
88
# AUTH_REDIRECT_PROXY_URL=https://YOURAPP.vercel.app/api/auth
99

1010
# https://hasura.io/learn/graphql/hasura-authentication/integrations/nextjs-auth/
11-
NEXTAUTH_URL=http://localhost:3000
1211
AUTH_SECRET=bb755cba466058b2e6a195541468e84c
1312

1413
JWT_TOKEN_EXPIRATION=2630016

apps/masterbots.ai/app/(browse)/[category]/[threadId]/page.tsx

+2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import { ThreadAccordion } from '@/components/shared/thread-accordion'
33
import { CategoryTabs } from '@/components/shared/category-tabs/category-tabs'
44
import { SearchInput } from '@/components/shared/search-input'
55

6+
export { generateMbMetadata as generateMetadata } from '@/lib/metadata'
7+
68
export default async function ThreadPage({ params }: ThreadPageProps) {
79
const categories = await getCategories()
810
const thread = await getThread({

apps/masterbots.ai/app/(browse)/page.tsx

+14-2
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,18 @@
11
import { ThreadList } from '@/components/shared/thread-list'
22
import { CategoryTabs } from '@/components/shared/category-tabs/category-tabs'
33
import { SearchInput } from '@/components/shared/search-input'
4-
import { getBrowseThreads, getCategories } from '@/services/hasura'
4+
import { getBrowseThreads, getCategories, getThread } from '@/services/hasura'
55
import { Card } from '@/components/ui/card'
66
import { decodeQuery } from '@/lib/url'
7+
import { permanentRedirect } from 'next/navigation'
8+
import { getThreadLink } from '@/lib/threads'
79

810
export default async function HomePage({ searchParams }: HomePageProps) {
11+
if (searchParams.threadId) {
12+
const thread = await getThread({ threadId: searchParams.threadId })
13+
permanentRedirect(getThreadLink({ thread }))
14+
}
15+
916
const categories = await getCategories()
1017
const query = searchParams.query ? decodeQuery(searchParams.query) : null
1118
const limit = searchParams.limit ? parseInt(searchParams.limit) : 20
@@ -44,5 +51,10 @@ export default async function HomePage({ searchParams }: HomePageProps) {
4451
}
4552

4653
interface HomePageProps {
47-
searchParams?: { query: string; page: string; limit: string }
54+
searchParams?: {
55+
query: string
56+
page: string
57+
limit: string
58+
threadId: string
59+
}
4860
}

apps/masterbots.ai/app/og/route.tsx

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { ImageResponse } from '@vercel/og'
2+
import { NextRequest } from 'next/server'
3+
import { GeistMono } from 'geist/font/mono' // Import the GeistMono font
4+
5+
export const runtime = 'edge'
6+
7+
export async function GET(req: NextRequest) {
8+
const { searchParams } = req.nextUrl
9+
const postTitle = searchParams.get('title')
10+
11+
// You may need to convert GeistMono or fetch it as ArrayBuffer if needed
12+
// const font = GeistMono; // Assuming GeistMono can be directly used, modify as needed
13+
14+
return new ImageResponse(
15+
(
16+
<div
17+
style={{
18+
height: '100%',
19+
width: '100%',
20+
display: 'flex',
21+
flexDirection: 'column',
22+
alignItems: 'flex-start',
23+
justifyContent: 'center',
24+
background: '#110f0f'
25+
}}
26+
>
27+
<div
28+
style={{
29+
marginLeft: 190,
30+
marginRight: 190,
31+
display: 'flex',
32+
fontSize: 130,
33+
fontFamily: GeistMono.className,
34+
letterSpacing: '-0.05em',
35+
fontStyle: 'normal',
36+
color: 'white',
37+
lineHeight: '120px',
38+
whiteSpace: 'pre-wrap'
39+
}}
40+
>
41+
{postTitle}
42+
</div>
43+
</div>
44+
),
45+
{
46+
width: 1920,
47+
height: 1080
48+
// Optionally, if font needs to be loaded as data
49+
// fonts: [
50+
// {
51+
// name: 'GeistMono',
52+
// data: font, // Assuming font data is handled accordingly
53+
// style: 'normal'
54+
// }
55+
// ]
56+
}
57+
)
58+
}

apps/masterbots.ai/components/layout/footer-ct.tsx

+5-4
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import Link from 'next/link'
12
import type { ElementType } from 'react'
23

34
export default function FooterCT({ nonFooterTag }: { nonFooterTag?: boolean }) {
@@ -24,13 +25,13 @@ export default function FooterCT({ nonFooterTag }: { nonFooterTag?: boolean }) {
2425
>
2526
robohash.org
2627
</a>
27-
{' • '}
28-
<a
28+
<Link
29+
shallow
2930
className="text-primary underline focus-within:underline"
30-
href="/terms-n-policies"
31+
href="/terms"
3132
>
3233
terms & policies
33-
</a>
34+
</Link>
3435
</span>
3536
</Footer>
3637
)

apps/masterbots.ai/components/routes/c/chat-input-new.tsx

-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ export interface ChatInputProps
2525
showReload?: boolean
2626
placeholder: string
2727
className?: string
28-
showSubmitButton: boolean
2928
}
3029

3130
export function ChatInputNew({

apps/masterbots.ai/lib/metadata.ts

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { getThread } from '@/services/hasura';
2+
import type { Metadata } from 'next';
3+
import { format } from 'date-fns';
4+
import { getThreadLink } from './threads';
5+
6+
export async function generateMbMetadata({
7+
params,
8+
}): Promise<Metadata | undefined> {
9+
const thread = await getThread({threadId: params.threadId})
10+
if (!thread) return
11+
12+
13+
const firstQuestion=
14+
thread.messages.find(m => m.role === 'user')?.content || 'not found'
15+
const firstResponse =
16+
thread.messages.find(m => m.role === 'assistant')?.content || 'not found'
17+
18+
const data = {
19+
title: firstQuestion,
20+
publishedAt: thread.updatedAt, // format(thread.updatedAt, 'MMMM dd, yyyy'),
21+
summary: firstResponse,
22+
image: `https://alpha.masterbots.ai/og?title=${encodeURIComponent(firstQuestion)}`,
23+
pathname: getThreadLink({thread:thread, chat:false})
24+
}
25+
26+
return {
27+
title:data.title,
28+
description:data.summary,
29+
openGraph: {
30+
title:data.title,
31+
description:data.summary,
32+
type: 'article',
33+
publishedTime: data.publishedAt,
34+
url: `https://alpha.masterbots.ai/${data.pathname}`,
35+
images: [
36+
{
37+
url: data.image,
38+
},
39+
],
40+
},
41+
twitter: {
42+
card: 'summary_large_image',
43+
title:data.title,
44+
description:data.summary,
45+
images: [data.image],
46+
},
47+
};
48+
}

apps/masterbots.ai/lib/threads.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,6 @@ export function getAllUserMessagesAsStringArray(
8282
return cleanMessages.join(', ')
8383
}
8484

85-
export function getThreadLink({chat=false, thread}:{chat:boolean, thread: Thread}){
85+
export function getThreadLink({chat=false, thread}:{chat?:boolean, thread: Thread}){
8686
return chat ? `/c/${toSlug(thread.chatbot.name)}/${thread.threadId}` : `/${toSlug(thread.chatbot.categories[0]?.category.name)}/${thread.threadId}`
8787
}

apps/masterbots.ai/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
"@supabase/ssr": "^0.1.0",
4040
"@tanstack/react-query": "^5.29.0",
4141
"@vercel/analytics": "^1.1.1",
42-
"@vercel/og": "^0.5.20",
42+
"@vercel/og": "^0.6.2",
4343
"ai": "^2.2.25",
4444
"class-variance-authority": "^0.7.0",
4545
"clsx": "^2.0.0",

bun.lockb

-52.7 KB
Binary file not shown.

0 commit comments

Comments
 (0)