Skip to content

Commit

Permalink
#20 Add customizations, avatar image upload - Part 3
Browse files Browse the repository at this point in the history
  • Loading branch information
santthosh committed Apr 12, 2024
1 parent 7c643c4 commit 4e4534c
Show file tree
Hide file tree
Showing 4 changed files with 105 additions and 24 deletions.
2 changes: 2 additions & 0 deletions prisma/migrations/20240412165627_add_avatar_url/migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "Assistant" ADD COLUMN "avatar" TEXT;
1 change: 1 addition & 0 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ model Assistant {
accountOwner String?
accountOwnerType String?
account Account? @relation(fields: [accountOwner, accountOwnerType], references: [owner, ownerType])
avatar String?
created_at DateTime @default(now())
updated_at DateTime @updatedAt
Thread Thread[]
Expand Down
90 changes: 76 additions & 14 deletions src/app/api/openai/assistants/[id]/avatar/route.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,21 @@
import { handleUpload, type HandleUploadBody } from '@vercel/blob/client';
import { NextResponse } from 'next/server';
import { NextRequest, NextResponse } from 'next/server';
import { getToken } from 'next-auth/jwt';
import OpenAI from 'openai';
import { PrismaClient } from '@prisma/client';

export async function POST(request: Request): Promise<NextResponse> {
const body = (await request.json()) as HandleUploadBody;
const prisma = new PrismaClient();

console.log('coming here', body);
const getId = (req: Request) => {
const url = new URL(req.url);
return url.pathname.split('/').splice(-2, 1)[0];
};

export async function POST(req: NextRequest): Promise<NextResponse> {
const body = (await req.json()) as HandleUploadBody;

let id = getId(req);
let request = req;

try {
const jsonResponse = await handleUpload({
Expand All @@ -17,26 +28,77 @@ export async function POST(request: Request): Promise<NextResponse> {
// Generate a client token for the browser to upload the file
// ⚠️ Authenticate and authorize users before generating the token.
// Otherwise, you're allowing anonymous uploads.
const token = await getToken({ req });

if (token) {
let account = await prisma.account.findFirst({
where: {
owner: token.sub,
ownerType: 'personal',
},
});

if (account) {
const openai = new OpenAI({
apiKey: account.openAIApiKey,
});

return {
allowedContentTypes: ['image/jpeg', 'image/png', 'image/gif'],
tokenPayload: JSON.stringify({
// optional, sent to your server on upload completion
// you could pass a user id from auth, or a value from clientPayload
}),
};
try {
// Check if assistant exists and if the user is the owner
let assistant = await prisma.assistant.findFirst({
where: {
id: id,
},
select: {
id: true,
object: true,
accountOwner: true,
accountOwnerType: true,
},
});

if (
!assistant ||
assistant.accountOwner !== token.sub ||
assistant.accountOwnerType !== 'personal'
) {
throw new Error('Unauthenticated');
}

return {
allowedContentTypes: ['image/jpeg', 'image/png', 'image/gif'],
tokenPayload: JSON.stringify({
// optional, sent to your server on upload completion
// you could pass a user id from auth, or a value from clientPayload
}),
};
} catch (err: any) {
throw err;
}
} else {
throw new Error('OpenAI API Key does not exist');
}
} else {
// Not Signed in
throw new Error('Unauthenticated');
}
},
onUploadCompleted: async ({ blob, tokenPayload }) => {
// Get notified of client upload completion
// ⚠️ This will not work on `localhost` websites,
// Use ngrok or similar to get the full upload flow

console.log('blob upload completed', blob, tokenPayload);

try {
// Run any logic after the file upload completed
// const { userId } = JSON.parse(tokenPayload);
// await db.update({ avatar: blob.url, userId });
await prisma.assistant.update({
where: {
id: id,
},
data: {
avatar: blob.url,
},
});
} catch (error) {
throw new Error('Could not update user');
}
Expand Down
36 changes: 26 additions & 10 deletions src/app/assistants/[id]/customize/ImageCropUpload.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@ import React, { useRef, useState } from 'react';
import ReactCrop, { Crop, centerCrop, makeAspectCrop } from 'react-image-crop';
import 'react-image-crop/dist/ReactCrop.css';

import { Button, FileInput, Label, Modal } from 'flowbite-react';
import { Avatar, Button, FileInput, Label, Modal } from 'flowbite-react';
import { Assistant } from '@/app/types/assistant';

import { type PutBlobResult } from '@vercel/blob';
import { upload } from '@vercel/blob/client';
import { toast } from 'react-hot-toast';
import { getImageHash } from '@/app/utils/hash';

function centerAspectCrop(
mediaWidth: number,
Expand Down Expand Up @@ -136,16 +137,31 @@ const ImageCropUpload = (props: { assistant: Assistant }) => {

return (
<div className='space-y-6 p-6'>
<div id='fileUpload' className='max-w-md'>
<div className='mb-2 block'>
<Label htmlFor='file' value='Agent Avatar' />
<div id='fileUpload' className='grid max-w-2xl grid-cols-3'>
<div className={'col-span-2'}>
<div className='mb-2 block'>
<Label htmlFor='file' value='Agent Avatar' />
</div>
<FileInput
id='file'
accept='image/*'
onChange={onSelectFile}
helperText='This is the avatar image that appears in conversations and external users. Use 512x512 pixel images for best fit and quality. '
/>
</div>
<FileInput
id='file'
accept='image/*'
onChange={onSelectFile}
helperText='Use 512x512 pixel images for best fit and quality. '
/>
{blob && (
<div className={'col-span-1 flex justify-end'}>
<Avatar
img={blob.url}
alt='avatar'
size='lg'
status='online'
statusPosition='top-right'
color='success'
rounded
/>
</div>
)}
</div>
<Modal show={openModal} size='md' onClose={() => setOpenModal(false)}>
<Modal.Header>Resize & Crop</Modal.Header>
Expand Down

0 comments on commit 4e4534c

Please sign in to comment.