diff --git a/apps/masterbots.ai/app/b/[id]/[threadId]/page.tsx b/apps/masterbots.ai/app/b/[id]/[threadId]/page.tsx new file mode 100644 index 00000000..f75f4276 --- /dev/null +++ b/apps/masterbots.ai/app/b/[id]/[threadId]/page.tsx @@ -0,0 +1,12 @@ +import { BrowseThread } from '@/components/browse-thread' +import { ChatPageProps } from '@/app/chat/[chatbot]/[threadId]/page' +import { getThread } from '@/app/actions' + +export { generateMbMetadata as generateMetadata } from '@/lib/metadata' + +export default async function ChatPage({ params }: ChatPageProps) { + const thread = await getThread({ + threadId: params.threadId, + }) + return +} diff --git a/apps/masterbots.ai/app/og/route.tsx b/apps/masterbots.ai/app/og/route.tsx index 68bcc501..17c1d641 100644 --- a/apps/masterbots.ai/app/og/route.tsx +++ b/apps/masterbots.ai/app/og/route.tsx @@ -1,119 +1,48 @@ -/* eslint-disable @next/next/no-img-element */ - import { ImageResponse } from '@vercel/og' import { NextRequest } from 'next/server' -import { GeistMono } from 'geist/font/mono' // Import the GeistMono font import '@/app/globals.css' -import { getThread } from '../actions' - +import { getThread } from '@/app/actions' +import OgImage from '@/components/og-image' export const runtime = 'edge' export async function GET(req: NextRequest) { try { const { searchParams } = req.nextUrl - const threadId = searchParams.get('threadId') - const thread = await getThread({ threadId }) - - // You may need to convert GeistMono or fetch it as ArrayBuffer if needed - // const font = GeistMono; // Assuming GeistMono can be directly used, modify as needed + const threadId = searchParams.get('threadId'); + const thread = await getThread({ threadId },) + const question = thread.firstMessage.content + const answer = thread.firstAnswer.content + const username = thread.account?.username + const user_avatar = thread.account?.avatar || '' + let theme = 'dark' + if (typeof window !== 'undefined') { + theme = localStorage.getItem('theme') || 'dark' + } + const isLightTheme = theme === 'light' return new ImageResponse( ( -
-
-
-

- {thread.chatbot.name} -

-

- {thread.firstMessage.content} -

-

- {thread.chatbot.categories[0].name} -

-
- {thread.chatbot.avatar ? ( -
- - - - - - - -
- ) : null} -
-
+ ), { width: 1200, height: 627 } ) - } catch (e) { + } catch (e: any) { console.log(`${e.message}`) return new Response(`Failed to generate the image`, { status: 500 }) } } +function useTheme() { + throw new Error('Function not implemented.') +} diff --git a/apps/masterbots.ai/components/og-bg-image.tsx b/apps/masterbots.ai/components/og-bg-image.tsx new file mode 100644 index 00000000..63a45ece --- /dev/null +++ b/apps/masterbots.ai/components/og-bg-image.tsx @@ -0,0 +1,392 @@ +export default function OgBgImage({ isLightTheme }: { isLightTheme: boolean }) { + return ( +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ ) +} diff --git a/apps/masterbots.ai/components/og-image.tsx b/apps/masterbots.ai/components/og-image.tsx new file mode 100644 index 00000000..08929d0d --- /dev/null +++ b/apps/masterbots.ai/components/og-image.tsx @@ -0,0 +1,150 @@ +import OgBgImage from '@/components/og-bg-image' +interface OgImageProps { + thread: any + question: string + answer: string + username: string | undefined + user_avatar: string + isLightTheme: boolean +} + +export default function OgImage({ thread, question, answer, username, user_avatar, isLightTheme }: OgImageProps) { + + return ( +
+ +
+
+

+ {thread.chatbot.name} +

+

+ {' '} + {thread.chatbot.categories[0]?.category.name} +

+

+ {question} +

+

+ {answer} +

+ +
+ +

+ {username} +

+
+
+ {thread.chatbot.avatar ? ( +
+
+ +
+
+ ) : null} +
+
+ ) +} diff --git a/apps/masterbots.ai/lib/metadata.ts b/apps/masterbots.ai/lib/metadata.ts index 72baded9..0f509c5b 100644 --- a/apps/masterbots.ai/lib/metadata.ts +++ b/apps/masterbots.ai/lib/metadata.ts @@ -1,19 +1,27 @@ +import { getThread } from '@/services/hasura' import type { Metadata } from 'next' -import { getThread } from '@/app/actions' import { getThreadLink } from './threads' export async function generateMbMetadata({ params +}: { + params: any; }): Promise { - const thread = await getThread({ threadId: params.threadId }) + const threadId = params?.threadId + const thread = await getThread({ threadId, jwt: '' }) if (!thread) return + const firstQuestion = + thread.messages.find(m => m.role === 'user')?.content || 'not found' + const firstResponse = + thread.messages.find(m => m.role === 'assistant')?.content || 'not found' + const data = { - title: thread.firstMessage.content, + title: firstQuestion, publishedAt: thread.updatedAt, // format(thread.updatedAt, 'MMMM dd, yyyy'), - summary: thread.firstAnswer.content, - image: `https://alpha.masterbots.ai/og?threadId=${thread.threadId}`, - pathname: getThreadLink({ thread, chat: false }) + summary: firstResponse, + image: `${process.env.VERCEL_URL}/og?threadId=${thread.threadId}`, + pathname: getThreadLink({ thread: thread, chat: false }) } return { @@ -25,7 +33,7 @@ export async function generateMbMetadata({ description: data.summary, type: 'article', publishedTime: data.publishedAt, - url: `https://alpha.masterbots.ai/${data.pathname}`, + url: `${process.env.VERCEL_URL}${data.pathname}`, images: [ { url: data.image diff --git a/apps/masterbots.ai/lib/threads.ts b/apps/masterbots.ai/lib/threads.ts index 2472c94e..56e93b6d 100644 --- a/apps/masterbots.ai/lib/threads.ts +++ b/apps/masterbots.ai/lib/threads.ts @@ -1,6 +1,7 @@ import type * as AI from 'ai' import { MB } from '@repo/supabase' import { toSlug } from './url-params' +import { extractBetweenMarkers } from '@/lib/utils' export function createMessagePairs(messages: AI.Message[]): MB.MessagePair[] { const messagePairs: MB.MessagePair[] = [] @@ -41,19 +42,38 @@ export function cleanPrompt(str: string) { return extracted || str } +export interface MessagePair { + userMessage: MB.Message | AI.Message + chatGptMessage: MB.Message[] +} + +export function convertMessage(message: MB.Message) { + return { + id: message.messageId, + content: message.content, + createAt: message.createdAt, + role: message.role + } as AI.Message +} + +export function getAllUserMessagesAsStringArray( + allMessages: MB.Message[] | AI.Message[] +) { + const userMessages = allMessages.filter(m => m.role === 'user') + const cleanMessages = userMessages.map(m => + extractBetweenMarkers(m.content, 'Then answer this question:') + ) + return cleanMessages.join(', ') +} + export function getThreadLink({ chat = false, - param = false, thread }: { chat?: boolean - param?: boolean - thread: MB.ThreadFull + thread: MB.Thread }) { - console.log('getThreadLink', thread.chatbot?.categories) - if (param) - return `/${toSlug(thread.chatbot.categories[0].name)}?threadId=${thread.threadId.trim()}` return chat - ? `/c/${toSlug(thread.chatbot.name)}/${thread.threadId.trim()}` - : `/${toSlug(thread.chatbot.categories[0].name)}/${thread.threadId.trim()}` + ? `/c/${toSlug(thread.chatbot.name)}/${thread.threadId}` + : `/${toSlug(thread.chatbot.categories[0]?.category.name)}/${thread.threadId}}` } diff --git a/apps/masterbots.ai/lib/url.ts b/apps/masterbots.ai/lib/url.ts new file mode 100644 index 00000000..6fca4411 --- /dev/null +++ b/apps/masterbots.ai/lib/url.ts @@ -0,0 +1,29 @@ +import { z, ZodSchema } from 'zod' + +// Zod schema for validating slug strings +export const SlugSchema: ZodSchema = z + .string() + .min(1) + .regex(/^[a-z0-9]+[a-z0-9+_-]*[a-z0-9]+$/, 'Invalid slug format.') + +// Function to convert a username into a slug +export const toSlug = (username: string, separator = '_'): string => { + return username + .toLowerCase() + .replace(/ & /g, '_n_') + .replace(/&/g, '_') + .replace(/[^a-z0-9_+-]/g, separator) +} + + + +//Encodes a string for use in a URL, replacing spaces with the '+' character. +export const encodeQuery = (input: string): string => { + return encodeURIComponent(input).replace(/%20/g, '+').replace(/ /g, '+') +} + +//Decodes a URL-encoded string, converting '+' back into spaces. + +export const decodeQuery = (input: string): string => { + return decodeURIComponent(input.replace(/\+/g, ' ')) +} \ No newline at end of file diff --git a/apps/masterbots.ai/package.json b/apps/masterbots.ai/package.json index 8c26c901..afc0f2e1 100644 --- a/apps/masterbots.ai/package.json +++ b/apps/masterbots.ai/package.json @@ -68,7 +68,7 @@ "remark-math": "^5.1.1", "ts-case-convert": "^2.0.7", "use-debounce": "^10.0.0", - "zod": "^3.22.4", + "zod": "^3.23.8", "@repo/supabase": "workspace:*" }, "devDependencies": { diff --git a/apps/masterbots.ai/public/og-background-image.svg b/apps/masterbots.ai/public/og-background-image.svg new file mode 100644 index 00000000..e69de29b diff --git a/bun.lockb b/bun.lockb index 5ab3f772..00f71681 100755 Binary files a/bun.lockb and b/bun.lockb differ