Skip to content

Commit

Permalink
feat: Forward data using the common proxy interface
Browse files Browse the repository at this point in the history
fix: Fixed the problem of being unable to upload to using the backend server
build: Use multiple rewrites rule hack to solve the problem of invalid rewrites rule variables in standalone mode
  • Loading branch information
Amery2010 committed May 26, 2024
1 parent 6469186 commit d2c99eb
Show file tree
Hide file tree
Showing 6 changed files with 120 additions and 81 deletions.
49 changes: 49 additions & 0 deletions app/api/upload/files/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { NextResponse, type NextRequest } from 'next/server'
import FileManager from '@/utils/FileManager'
import { ErrorType } from '@/constant/errors'
import { isUndefined } from 'lodash-es'

const geminiApiKey = process.env.GEMINI_API_KEY as string
const geminiApiBaseUrl = process.env.GEMINI_API_BASE_URL as string
const geminiUploadProxyUrl = process.env.GEMINI_UPLOAD_BASE_URL || 'https://generativelanguage.googleapis.com'
const mode = process.env.NEXT_PUBLIC_BUILD_MODE

export const preferredRegion = ['sfo1']

export async function GET(req: NextRequest) {
if (mode === 'export') return new NextResponse('Not available under static deployment')

const id = req.url.split('/').pop()

if (isUndefined(id)) {
return NextResponse.json({ code: 40001, message: ErrorType.MissingParam }, { status: 400 })
}

const fileManager = new FileManager({
apiKey: geminiApiKey,
baseUrl: geminiApiBaseUrl,
uploadUrl: geminiUploadProxyUrl,
})
const result = await fileManager.getFileMetadata(id)
return NextResponse.json(result)
}

export async function POST(req: NextRequest) {
req.nextUrl.searchParams.append('key', geminiApiKey)
const blob = await req.blob()
const response = await fetch(`${geminiUploadProxyUrl}/upload/v1beta/files?${req.nextUrl.searchParams.toString()}`, {
method: 'POST',
body: blob,
})
return new NextResponse(response.body)
}

export async function PUT(req: NextRequest) {
req.nextUrl.searchParams.append('key', geminiApiKey)
const blob = await req.blob()
const response = await fetch(`${geminiUploadProxyUrl}/upload/v1beta/files?${req.nextUrl.searchParams.toString()}`, {
method: 'PUT',
body: blob,
})
return new NextResponse(response.body)
}
39 changes: 25 additions & 14 deletions next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,23 +25,34 @@ module.exports = async (phase) => {
if (mode !== 'export') {
nextConfig.rewrites = async () => {
return {
beforeFiles: [
{
source: '/api/google/upload/v1beta/files',
has: [
beforeFiles: apiKey
? [
{
type: 'query',
key: 'uploadType',
value: '(?<uploadType>.*)',
source: '/api/google/upload/v1beta/files',
has: [
{
type: 'query',
key: 'uploadType',
value: '(?<uploadType>.*)',
},
],
destination: `${uploadProxyUrl}/upload/v1beta/files?key=${apiKey}&uploadType=:uploadType`,
},
{
source: '/api/google/v1beta/files/:id',
destination: `${uploadProxyUrl}/v1beta/files/:id?key=${apiKey}`,
},
]
: [
{
source: '/api/google/upload/v1beta/files',
destination: '/api/upload/files',
},
{
source: '/api/google/v1beta/files/:path',
destination: '/api/upload/files?id=:path',
},
],
destination: `${uploadProxyUrl}/upload/v1beta/files?key=${apiKey}&uploadType=:uploadType`,
},
{
source: '/api/google/v1beta/files/:id',
destination: `${uploadProxyUrl}/v1beta/files/:id?key=${apiKey}`,
},
],
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "talk-with-gemini",
"version": "0.10.6",
"version": "0.10.7",
"private": true,
"author": "Amery2010 <[email protected]>",
"license": "GPL-3.0-only",
Expand Down
2 changes: 1 addition & 1 deletion public/sw.js

Large diffs are not rendered by default.

105 changes: 43 additions & 62 deletions utils/FileManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ export type FileManagerOptions = {
onError?: (message: string) => void
}

const inVercelOrNetlify = process.env.VERCEL === '1' || process.env.NETLIFY === 'true'

class FileManager {
options: FileManagerOptions
uploadBaseUrl: string
Expand Down Expand Up @@ -120,72 +118,55 @@ class FileManager {
})
}
async uploadFile(file: File): Promise<{ file: FileMetadata }> {
if (inVercelOrNetlify) {
const generateBoundary = () => {
let str = ''
for (let i = 0; i < 2; i++) {
str = str + Math.random().toString().slice(2)
}
return str
const generateBoundary = () => {
let str = ''
for (let i = 0; i < 2; i++) {
str = str + Math.random().toString().slice(2)
}
const boundary = generateBoundary()
// Multipart formatting code taken from @firebase/storage
const metadataString = JSON.stringify({
file: {
mimeType: file.type,
displayName: file.name,
},
})
const preBlobPart =
'--' +
boundary +
'\r\n' +
'Content-Type: application/json; charset=utf-8\r\n\r\n' +
metadataString +
'\r\n--' +
boundary +
'\r\n' +
'Content-Type: ' +
file.type +
'\r\n\r\n'
const postBlobPart = '\r\n--' + boundary + '--'
const blob = new Blob([preBlobPart, file, postBlobPart])
const response = await fetch(
this.options.token
? `/api/google/upload/v1beta/files?uploadType=multipart`
: `${this.uploadBaseUrl}/upload/v1beta/files?uploadType=multipart&key=${this.options.apiKey}`,
{
method: 'POST',
headers: {
'Content-Type': `multipart/related; boundary=${boundary}`,
},
body: blob,
},
).catch((err) => {
throw new Error(err.message)
})
return await response.json()
} else {
const formData = new FormData()
formData.append('file', file)
const response = await fetch(
this.options.token
? `/api/upload?token=${this.options.token}`
: `${this.uploadBaseUrl}/upload/v1beta/files?key=${this.options.apiKey}`,
{
method: 'POST',
body: formData,
},
).catch((err) => {
throw new Error(err)
})
return await response.json()
return str
}
const boundary = generateBoundary()
// Multipart formatting code taken from @firebase/storage
const metadataString = JSON.stringify({
file: {
mimeType: file.type,
displayName: file.name,
},
})
const preBlobPart =
'--' +
boundary +
'\r\n' +
'Content-Type: application/json; charset=utf-8\r\n\r\n' +
metadataString +
'\r\n--' +
boundary +
'\r\n' +
'Content-Type: ' +
file.type +
'\r\n\r\n'
const postBlobPart = '\r\n--' + boundary + '--'
const blob = new Blob([preBlobPart, file, postBlobPart])
const response = await fetch(
this.options.token
? `/api/google/upload/v1beta/files?uploadType=multipart`
: `${this.uploadBaseUrl}/upload/v1beta/files?uploadType=multipart&key=${this.options.apiKey}`,
{
method: 'POST',
headers: {
'Content-Type': `multipart/related; boundary=${boundary}`,
},
body: blob,
},
).catch((err) => {
throw new Error(err.message)
})
return await response.json()
}
async getFileMetadata(fileID: string) {
const response = await fetch(
this.options.token
? `${inVercelOrNetlify ? `/api/google/v1beta/files/${fileID}` : `/api/files?id=${fileID}&token=${this.options.token}`}`
? `/api/google/v1beta/files/${fileID}`
: `${this.uploadBaseUrl}/v1beta/files/${fileID}?key=${this.options.apiKey}`,
{
method: 'GET',
Expand Down
4 changes: 1 addition & 3 deletions utils/upload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,6 @@ type imageUploadOptions = {
onError?: (error: string) => void
}

const inVercelOrNetlify = process.env.VERCEL === '1' || process.env.NETLIFY === 'true'

const compressionOptions = {
maxSizeMB: 1,
maxWidthOrHeight: 1024,
Expand Down Expand Up @@ -81,7 +79,7 @@ export async function fileUpload({

const fileManager = new FileManager(fileManagerOptions)
// Files smaller than 4MB are uploaded directly
if (file.size <= 4194304 || !inVercelOrNetlify) {
if (file.size <= 4194304) {
fileManager
.uploadFile(uploadFile)
.then((fileMetadata) => {
Expand Down

0 comments on commit d2c99eb

Please sign in to comment.