feat: implement og image

This commit is contained in:
shadcn 2023-06-16 18:41:22 +04:00
parent a9e4956909
commit be867d3fba
6 changed files with 317 additions and 3 deletions

View file

@ -0,0 +1,189 @@
import { ImageResponse } from 'next/server'
import { getSharedChat } from '@/app/actions'
import { formatDate } from '@/lib/utils'
export const runtime = 'edge'
export const alt = 'AI Chatbot'
export const size = {
width: 1200,
height: 630
}
export const contentType = 'image/png'
const interRegular = fetch(
new URL('../../../assets/fonts/Inter-Regular.ttf', import.meta.url)
).then(res => res.arrayBuffer())
const interBold = fetch(
new URL('../../../assets/fonts/Inter-Bold.ttf', import.meta.url)
).then(res => res.arrayBuffer())
interface ImageProps {
params: {
id: string
}
}
export default async function Image({ params }: ImageProps) {
const chat = await getSharedChat(params.id)
if (!chat || !chat?.sharePath) {
return null
}
return new ImageResponse(
(
<div tw="flex w-full items-start h-full flex-col bg-black text-white py-[120px] px-[80px]">
<div tw="flex flex-col">
<div tw="flex text-white font-bold text-5xl leading-tight truncate line-clamp-3">
{chat.title.length > 120
? `${chat.title.slice(0, 120)}...`
: chat.title}
</div>
<div tw="flex text-zinc-400 text-2xl mt-4">
{formatDate(chat.createdAt)} · {chat.messages.length} messages
</div>
</div>
<div tw="flex items-center justify-between w-full mt-auto">
<div tw="flex items-center">
<svg
xmlns="http://www.w3.org/2000/svg"
x="0"
y="0"
version="1.1"
viewBox="0 0 33 33"
width={40}
height={40}
>
<path
d="M5 .5h20c2.5 0 4.5 2 4.5 4.5v20c0 2.5-2 4.5-4.5 4.5H5c-2.5 0-4.5-2-4.5-4.5V5C.5 2.5 2.5.5 5 .5z"
fill="black"
/>
<circle
cx="17.5"
cy="21.5"
r="4.5"
fill="transparent"
strokeWidth="1.15"
stroke="white"
/>
<path
d="m18.2 4.8 6.4 1.6c.3.1.4.3.4.6l-1.6 6.4c-.1.3-.3.4-.6.4l-6.4-1.6c-.3-.1-.4-.3-.4-.6l1.6-6.4c0-.3.3-.5.6-.4zM7 9c.1-.4.5-.5.8-.3l6.6 5.6c.3.2.2.7-.2.9l-8.1 3c-.4.1-.7-.2-.7-.6L7 9z"
fill="transparent"
strokeWidth="1.15"
stroke="white"
/>
<path
d="M17.7 18.3v11.3c0 1.3 3.2 2.4 7.3 2.4s7.3-1.1 7.3-2.4V18.3"
fill="black"
/>
<path
d="M17.7 18.3c0-1.3 3.3-2.4 7.3-2.4s7.3 1.1 7.3 2.4v11.3c0 1.3-3.2 2.4-7.3 2.4s-7.3-1.1-7.3-2.4V18.3z"
fill="black"
/>
<path
fill="none"
stroke="white"
d="M5 .5h20c2.5 0 4.5 2 4.5 4.5v20c0 2.5-2 4.5-4.5 4.5H5c-2.5 0-4.5-2-4.5-4.5V5C.5 2.5 2.5.5 5 .5z"
/>
<path
fill="black"
stroke="white"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="1.25"
d="M32.3 18.3c0 1.3-3.3 2.4-7.3 2.4s-7.3-1.1-7.3-2.4m14.6 0c0-1.3-3.3-2.4-7.3-2.4s-7.3 1.1-7.3 2.4m14.6 0v11.3c0 1.3-3.2 2.4-7.3 2.4s-7.3-1.1-7.3-2.4V18.3M32.3 24c0 1.3-3.2 2.4-7.3 2.4s-7.3-1.1-7.3-2.4"
/>
</svg>
<div tw="text-2xl ml-4 text-zinc-300">Built with Vercel KV</div>
</div>
<svg
viewBox="0 0 17 17"
fill="none"
xmlns="http://www.w3.org/2000/svg"
width={40}
height={40}
>
<defs>
<linearGradient
id="gradient-1"
x1="10.6889"
y1="10.3556"
x2="13.8445"
y2="14.2667"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="black" />
<stop offset={1} stopColor="black" stopOpacity={0} />
</linearGradient>
<linearGradient
id="gradient-2"
x1="11.7555"
y1="4.8"
x2="11.7376"
y2="9.50002"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="black" />
<stop offset={1} stopColor="black" stopOpacity={0} />
</linearGradient>
</defs>
<path
d="M1 16L2.58314 11.2506C1.83084 9.74642 1.63835 8.02363 2.04013 6.39052C2.4419 4.75741 3.41171 3.32057 4.776 2.33712C6.1403 1.35367 7.81003 0.887808 9.4864 1.02289C11.1628 1.15798 12.7364 1.8852 13.9256 3.07442C15.1148 4.26363 15.842 5.83723 15.9771 7.5136C16.1122 9.18997 15.6463 10.8597 14.6629 12.224C13.6794 13.5883 12.2426 14.5581 10.6095 14.9599C8.97637 15.3616 7.25358 15.1692 5.74942 14.4169L1 16Z"
fill="white"
stroke="white"
strokeWidth={2}
strokeLinecap="round"
strokeLinejoin="round"
/>
<mask
id="mask0_91_2047"
style={{ maskType: 'alpha' }}
maskUnits="userSpaceOnUse"
x={1}
y={0}
width={16}
height={16}
>
<circle cx={9} cy={8} r={8} fill="white" />
</mask>
<g mask="url(#mask0_91_2047)">
<circle cx={9} cy={8} r={8} fill="white" />
<path
d="M14.2896 14.0018L7.146 4.8H5.80005V11.1973H6.87681V6.16743L13.4444 14.6529C13.7407 14.4545 14.0231 14.2369 14.2896 14.0018Z"
fill={`url(#gradient-1)`}
/>
<rect
x="11.2222"
y="4.8"
width="1.06667"
height="6.4"
fill={`url(#gradient-2)`}
/>
</g>
</svg>
</div>
</div>
),
{
...size,
fonts: [
{
name: 'Inter',
data: await interRegular,
style: 'normal',
weight: 400
},
{
name: 'Inter',
data: await interBold,
style: 'normal',
weight: 700
}
]
}
)
}

View file

@ -9,7 +9,7 @@ import { FooterText } from '@/components/footer'
// export const runtime = 'edge'
export const preferredRegion = 'home'
export interface SharePageProps {
interface SharePageProps {
params: {
id: string
}

BIN
assets/fonts/Inter-Bold.ttf Normal file

Binary file not shown.

Binary file not shown.

View file

@ -24,6 +24,7 @@
"@radix-ui/react-tooltip": "^1.0.6",
"@vercel/analytics": "^1.0.0",
"@vercel/kv": "^0.2.1",
"@vercel/og": "^0.5.7",
"ai-connector": "^0.0.12",
"body-scroll-lock": "4.0.0-beta.0",
"class-variance-authority": "^0.4.0",

128
pnpm-lock.yaml generated
View file

@ -38,6 +38,9 @@ dependencies:
'@vercel/kv':
specifier: ^0.2.1
version: 0.2.1
'@vercel/og':
specifier: ^0.5.7
version: 0.5.7
ai-connector:
specifier: ^0.0.12
version: 0.0.12(react@18.2.0)(svelte@3.59.1)
@ -1054,10 +1057,24 @@ packages:
'@babel/runtime': 7.21.5
dev: false
/@resvg/resvg-wasm@2.4.1:
resolution: {integrity: sha512-yi6R0HyHtsoWTRA06Col4WoDs7SvlXU3DLMNP2bdAgs7HK18dTEVl1weXgxRzi8gwLteGUbIg29zulxIB3GSdg==}
engines: {node: '>= 10'}
dev: false
/@rushstack/eslint-patch@1.2.0:
resolution: {integrity: sha512-sXo/qW2/pAcmT43VoRKOJbDOfV3cYpq3szSVfIThQXNt+E4DfKj361vaAt3c88U5tPUxzEswam7GW48PJqtKAg==}
dev: true
/@shuding/opentype.js@1.4.0-beta.0:
resolution: {integrity: sha512-3NgmNyH3l/Hv6EvsWJbsvpcpUba6R8IREQ83nH83cyakCw7uM1arZKNfHwv1Wz6jgqrF/j4x5ELvR6PnK9nTcA==}
engines: {node: '>= 8.0.0'}
hasBin: true
dependencies:
fflate: 0.7.4
string.prototype.codepointat: 0.2.1
dev: false
/@swc/helpers@0.5.1:
resolution: {integrity: sha512-sJ902EfIzn1Fa+qYmjdQqh8tPsoxyBz+8yBKC2HKUxyezKJFwPGOn7pv4WY6QuQW//ySQi5lJjA/ZT9sNWWNTg==}
dependencies:
@ -1305,6 +1322,15 @@ packages:
- encoding
dev: false
/@vercel/og@0.5.7:
resolution: {integrity: sha512-KdhPHiLodj2n0Z0yg9feF/onMPlrimhBvCRJOwTpOjvNxefBqM1VIJW5+Ou9mFSIa37Gg0DGBDAoSOJLH2FqxA==}
engines: {node: '>=16'}
dependencies:
'@resvg/resvg-wasm': 2.4.1
satori: 0.10.1
yoga-wasm-web: 0.3.3
dev: false
/acorn-jsx@5.3.2(acorn@8.8.2):
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
peerDependencies:
@ -1503,6 +1529,11 @@ packages:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
dev: true
/base64-js@0.0.8:
resolution: {integrity: sha512-3XSA2cR/h/73EzlXXdU6YNycmYI7+kicTxks4eJg2g39biHR84slg2+des+p7iHYhbRg/udIS4TD53WabcOUkw==}
engines: {node: '>= 0.4'}
dev: false
/big-integer@1.6.51:
resolution: {integrity: sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==}
engines: {node: '>=0.6'}
@ -1605,6 +1636,10 @@ packages:
engines: {node: '>=14.16'}
dev: true
/camelize@1.0.1:
resolution: {integrity: sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==}
dev: false
/caniuse-lite@1.0.30001486:
resolution: {integrity: sha512-uv7/gXuHi10Whlj0pp5q/tsK/32J2QSqVRKQhs2j8VsDCjgyruAh/eEXHF822VqO9yT6iZKw3nRwZRSPBE9OQg==}
@ -1696,7 +1731,6 @@ packages:
/color-name@1.1.4:
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
dev: true
/combined-stream@1.0.8:
resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
@ -1746,6 +1780,27 @@ packages:
which: 2.0.2
dev: true
/css-background-parser@0.1.0:
resolution: {integrity: sha512-2EZLisiZQ+7m4wwur/qiYJRniHX4K5Tc9w93MT3AS0WS1u5kaZ4FKXlOTBhOjc+CgEgPiGY+fX1yWD8UwpEqUA==}
dev: false
/css-box-shadow@1.0.0-3:
resolution: {integrity: sha512-9jaqR6e7Ohds+aWwmhe6wILJ99xYQbfmK9QQB9CcMjDbTxPZjwEmUQpU91OG05Xgm8BahT5fW+svbsQGjS/zPg==}
dev: false
/css-color-keywords@1.0.0:
resolution: {integrity: sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==}
engines: {node: '>=4'}
dev: false
/css-to-react-native@3.2.0:
resolution: {integrity: sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==}
dependencies:
camelize: 1.0.1
css-color-keywords: 1.0.0
postcss-value-parser: 4.2.0
dev: false
/cssesc@3.0.0:
resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==}
engines: {node: '>=4'}
@ -1956,6 +2011,10 @@ packages:
resolution: {integrity: sha512-WDgWUOK8ROR7sDFyYmxCUOoDc50lPgYAHAHwnnD1iN3SKO/mpqftb9iIPiEkMKmqYdkrR0j3N/O+YB/U7lSxwg==}
dev: true
/emoji-regex@10.2.1:
resolution: {integrity: sha512-97g6QgOk8zlDRdgq1WxwgTMgEWGVAQvB5Fdpgc1MkNy56la5SKP9GsMXKDOdqwn90/41a8yPwIGk1Y6WVbeMQA==}
dev: false
/emoji-regex@9.2.2:
resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==}
dev: true
@ -2306,6 +2365,10 @@ packages:
engines: {node: '>=6'}
dev: true
/escape-html@1.0.3:
resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==}
dev: false
/escape-string-regexp@4.0.0:
resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
engines: {node: '>=10'}
@ -2698,6 +2761,10 @@ packages:
format: 0.2.2
dev: false
/fflate@0.7.4:
resolution: {integrity: sha512-5u2V/CDW15QM1XbbgS+0DfPxVB+jUKhWEKuuFuHncbk3tEEqzmoXL+2KyOFuKGqOnmdIy0/davWF1CkuwtibCw==}
dev: false
/file-entry-cache@6.0.1:
resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==}
engines: {node: ^10.12.0 || >=12.0.0}
@ -3024,6 +3091,11 @@ packages:
resolution: {integrity: sha512-2bsegYkkHO+h/9MGbn6KWcE45cHZgPANo5LXF7EvWdT0yT2EguSVO1nDgU5c8+ZOPwp2vMNa7YFsJhVcDR9Sdg==}
dev: true
/hex-rgb@4.3.0:
resolution: {integrity: sha512-Ox1pJVrDCyGHMG9CFg1tmrRUMRPRsAWYc/PinY0XzJU4K7y7vjNoLKIQ7BR5UJMCxNN8EM1MNDmHWA/B3aZUuw==}
engines: {node: '>=6'}
dev: false
/highlight.js@10.7.3:
resolution: {integrity: sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==}
dev: false
@ -3417,6 +3489,13 @@ packages:
engines: {node: '>=10'}
dev: true
/linebreak@1.1.0:
resolution: {integrity: sha512-MHp03UImeVhB7XZtjd0E4n6+3xr5Dq/9xI/5FptGk5FrbDR3zagPa2DS6U8ks/3HjbKWG9Q1M2ufOzxV2qLYSQ==}
dependencies:
base64-js: 0.0.8
unicode-trie: 2.0.0
dev: false
/lines-and-columns@1.2.4:
resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
dev: true
@ -4240,6 +4319,10 @@ packages:
p-limit: 3.1.0
dev: true
/pako@0.2.9:
resolution: {integrity: sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==}
dev: false
/parent-module@1.0.1:
resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
engines: {node: '>=6'}
@ -4247,6 +4330,13 @@ packages:
callsites: 3.1.0
dev: true
/parse-css-color@0.2.1:
resolution: {integrity: sha512-bwS/GGIFV3b6KS4uwpzCFj4w297Yl3uqnSgIPsoQkx7GMLROXfMnWvxfNkL0oh8HVhZA4hvJoEoEIqonfJ3BWg==}
dependencies:
color-name: 1.1.4
hex-rgb: 4.3.0
dev: false
/parse-entities@2.0.0:
resolution: {integrity: sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==}
dependencies:
@ -4376,7 +4466,6 @@ packages:
/postcss-value-parser@4.2.0:
resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==}
dev: true
/postcss@8.4.14:
resolution: {integrity: sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==}
@ -4746,6 +4835,22 @@ packages:
is-regex: 1.1.4
dev: true
/satori@0.10.1:
resolution: {integrity: sha512-F4bTCkDp931tLb7+UCNPBuSQwXhikrUkI4fBQo6fA8lF0Evqqgg3nDyUpRktQpR5Ry1DIiIVqLyEwkAms87ykg==}
engines: {node: '>=16'}
dependencies:
'@shuding/opentype.js': 1.4.0-beta.0
css-background-parser: 0.1.0
css-box-shadow: 1.0.0-3
css-to-react-native: 3.2.0
emoji-regex: 10.2.1
escape-html: 1.0.3
linebreak: 1.1.0
parse-css-color: 0.2.1
postcss-value-parser: 4.2.0
yoga-wasm-web: 0.3.3
dev: false
/scheduler@0.23.0:
resolution: {integrity: sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==}
dependencies:
@ -4860,6 +4965,10 @@ packages:
engines: {node: '>=10.0.0'}
dev: false
/string.prototype.codepointat@0.2.1:
resolution: {integrity: sha512-2cBVCj6I4IOvEnjgO/hWqXjqBGsY+zwPmHl12Srk9IXSZ56Jwwmy+66XO5Iut/oQVR7t5ihYdLB0GMa4alEUcg==}
dev: false
/string.prototype.matchall@4.0.8:
resolution: {integrity: sha512-6zOCOcJ+RJAQshcTvXPHoxoQGONa3e/Lqx90wUA+wEzX78sg5Bo+1tQo4N0pohS0erG9qtCqJDjNCQBjeWVxyg==}
dependencies:
@ -5085,6 +5194,10 @@ packages:
next-tick: 1.1.0
dev: true
/tiny-inflate@1.0.3:
resolution: {integrity: sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==}
dev: false
/titleize@3.0.0:
resolution: {integrity: sha512-KxVu8EYHDPBdUYdKZdKtU2aj2XfEx9AfjXxE/Aj0vT06w2icA09Vus1rh6eSu1y01akYg6BjIK/hxyLJINoMLQ==}
engines: {node: '>=12'}
@ -5206,6 +5319,13 @@ packages:
which-boxed-primitive: 1.0.2
dev: true
/unicode-trie@2.0.0:
resolution: {integrity: sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==}
dependencies:
pako: 0.2.9
tiny-inflate: 1.0.3
dev: false
/unified@10.1.2:
resolution: {integrity: sha512-pUSWAi/RAnVy1Pif2kAoeWNBa3JVrx0MId2LASj8G+7AiHWoKZNTomq6LG326T68U7/e263X6fTdcXIy7XnF7Q==}
dependencies:
@ -5486,6 +5606,10 @@ packages:
engines: {node: '>=10'}
dev: true
/yoga-wasm-web@0.3.3:
resolution: {integrity: sha512-N+d4UJSJbt/R3wqY7Coqs5pcV0aUj2j9IaQ3rNj9bVCLld8tTGKRa2USARjnvZJWVx1NDmQev8EknoczaOQDOA==}
dev: false
/zod@3.21.4:
resolution: {integrity: sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw==}