| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174 |
- package controller
- import (
- "fmt"
- "net/http"
- "strings"
- "github.com/QuantumNous/new-api/common"
- "github.com/QuantumNous/new-api/constant"
- "github.com/QuantumNous/new-api/logger"
- "github.com/QuantumNous/new-api/relay/channel/gemini"
- "github.com/gin-gonic/gin"
- )
- // RelayGeminiFileUpload handles file upload to Gemini File API
- // Supports both simple multipart upload and resumable upload protocol
- func RelayGeminiFileUpload(c *gin.Context) {
- // Get API key from channel context
- apiKey := common.GetContextKeyString(c, constant.ContextKeyChannelKey)
- if apiKey == "" {
- logger.LogError(c, "Failed to get Gemini channel API key")
- c.JSON(http.StatusServiceUnavailable, gin.H{
- "error": gin.H{
- "message": "No available Gemini channel found",
- "type": "service_unavailable_error",
- "code": "no_available_channel",
- },
- })
- return
- }
- // Check if this is a resumable upload request
- uploadProtocol := c.GetHeader("X-Goog-Upload-Protocol")
- if uploadProtocol == "resumable" {
- // Handle resumable upload
- handleResumableUpload(c, apiKey)
- return
- }
- // Handle standard multipart upload
- handleMultipartUpload(c, apiKey)
- }
- // handleResumableUpload handles Gemini's resumable upload protocol
- func handleResumableUpload(c *gin.Context, apiKey string) {
- // Build upstream URL
- url := gemini.BuildGeminiFileURL("/upload/v1beta/files")
- // Prepare headers - forward all X-Goog-Upload-* headers
- headers := map[string]string{
- "x-goog-api-key": apiKey,
- }
- // Forward all resumable upload headers
- resumableHeaders := []string{
- "X-Goog-Upload-Protocol",
- "X-Goog-Upload-Command",
- "X-Goog-Upload-Header-Content-Length",
- "X-Goog-Upload-Header-Content-Type",
- "X-Goog-Upload-Offset",
- "Content-Type",
- "Content-Length",
- }
- for _, header := range resumableHeaders {
- if value := c.GetHeader(header); value != "" {
- headers[header] = value
- }
- }
- // Get request body (for start command, it's JSON metadata)
- body := c.Request.Body
- // Forward request to Gemini
- err := gemini.ForwardGeminiFileRequest(c, http.MethodPost, url, body, headers)
- if err != nil {
- logger.LogError(c, fmt.Sprintf("failed to forward resumable upload request: %s", err.Error()))
- return
- }
- }
- // handleMultipartUpload handles standard multipart/form-data upload
- func handleMultipartUpload(c *gin.Context, apiKey string) {
- // Parse multipart form
- form, err := common.ParseMultipartFormReusable(c)
- if err != nil {
- logger.LogError(c, fmt.Sprintf("failed to parse multipart form: %s", err.Error()))
- c.JSON(http.StatusBadRequest, gin.H{
- "error": gin.H{
- "message": fmt.Sprintf("failed to parse multipart form: %s", err.Error()),
- "type": "invalid_request_error",
- "code": "invalid_multipart_form",
- },
- })
- return
- }
- defer form.RemoveAll()
- // Rebuild multipart form for upstream request
- body, contentType, err := gemini.RebuildMultipartForm(form)
- if err != nil {
- logger.LogError(c, fmt.Sprintf("failed to rebuild multipart form: %s", err.Error()))
- c.JSON(http.StatusInternalServerError, gin.H{
- "error": gin.H{
- "message": fmt.Sprintf("failed to rebuild multipart form: %s", err.Error()),
- "type": "internal_error",
- "code": "form_rebuild_error",
- },
- })
- return
- }
- // Build upstream URL
- url := gemini.BuildGeminiFileURL("/upload/v1beta/files")
- // Prepare headers
- headers := map[string]string{
- "Content-Type": contentType,
- "x-goog-api-key": apiKey,
- }
- // Forward request to Gemini
- err = gemini.ForwardGeminiFileRequest(c, http.MethodPost, url, body, headers)
- if err != nil {
- logger.LogError(c, fmt.Sprintf("failed to forward file upload request: %s", err.Error()))
- return
- }
- }
- // RelayGeminiFileList lists files from Gemini File API
- func RelayGeminiFileList(c *gin.Context) {
- // Get API key from channel context
- apiKey := common.GetContextKeyString(c, constant.ContextKeyChannelKey)
- if apiKey == "" {
- logger.LogError(c, "API key not found in context")
- c.JSON(http.StatusUnauthorized, gin.H{
- "error": gin.H{
- "message": "API key not found",
- "type": "authentication_error",
- "code": "invalid_api_key",
- },
- })
- return
- }
- // Build upstream URL with query parameters
- url := gemini.BuildGeminiFileURL("/v1beta/files")
- // Add query parameters if present
- queryParams := []string{}
- if pageSize := c.Query("pageSize"); pageSize != "" {
- queryParams = append(queryParams, fmt.Sprintf("pageSize=%s", pageSize))
- }
- if pageToken := c.Query("pageToken"); pageToken != "" {
- queryParams = append(queryParams, fmt.Sprintf("pageToken=%s", pageToken))
- }
- if len(queryParams) > 0 {
- url = fmt.Sprintf("%s?%s", url, strings.Join(queryParams, "&"))
- }
- // Prepare headers
- headers := map[string]string{
- "x-goog-api-key": apiKey,
- }
- // Forward request to Gemini
- err := gemini.ForwardGeminiFileRequest(c, http.MethodGet, url, nil, headers)
- if err != nil {
- logger.LogError(c, fmt.Sprintf("failed to forward file list request: %s", err.Error()))
- return
- }
- }
|