|
|
@@ -0,0 +1,212 @@
|
|
|
+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
|
|
|
+func RelayGeminiFileUpload(c *gin.Context) {
|
|
|
+ // 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()
|
|
|
+
|
|
|
+ // Get API key from channel context (set by middleware)
|
|
|
+ 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
|
|
|
+ }
|
|
|
+
|
|
|
+ // 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()))
|
|
|
+ // Error response already sent by ForwardGeminiFileRequest
|
|
|
+ return
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// RelayGeminiFileGet retrieves file metadata from Gemini File API
|
|
|
+func RelayGeminiFileGet(c *gin.Context) {
|
|
|
+ // Get file name from URL parameter
|
|
|
+ fileName := c.Param("name")
|
|
|
+ if fileName == "" {
|
|
|
+ c.JSON(http.StatusBadRequest, gin.H{
|
|
|
+ "error": gin.H{
|
|
|
+ "message": "file name is required",
|
|
|
+ "type": "invalid_request_error",
|
|
|
+ "code": "missing_file_name",
|
|
|
+ },
|
|
|
+ })
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ // 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 - fileName already includes "files/" prefix from route
|
|
|
+ url := gemini.BuildGeminiFileURL(fmt.Sprintf("/v1beta/%s", fileName))
|
|
|
+
|
|
|
+ // 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 get request: %s", err.Error()))
|
|
|
+ return
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// RelayGeminiFileDelete deletes a file from Gemini File API
|
|
|
+func RelayGeminiFileDelete(c *gin.Context) {
|
|
|
+ // Get file name from URL parameter
|
|
|
+ fileName := c.Param("name")
|
|
|
+ if fileName == "" {
|
|
|
+ c.JSON(http.StatusBadRequest, gin.H{
|
|
|
+ "error": gin.H{
|
|
|
+ "message": "file name is required",
|
|
|
+ "type": "invalid_request_error",
|
|
|
+ "code": "missing_file_name",
|
|
|
+ },
|
|
|
+ })
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ // 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 - fileName already includes "files/" prefix from route
|
|
|
+ url := gemini.BuildGeminiFileURL(fmt.Sprintf("/v1beta/%s", fileName))
|
|
|
+
|
|
|
+ // Prepare headers
|
|
|
+ headers := map[string]string{
|
|
|
+ "x-goog-api-key": apiKey,
|
|
|
+ }
|
|
|
+
|
|
|
+ // Forward request to Gemini
|
|
|
+ err := gemini.ForwardGeminiFileRequest(c, http.MethodDelete, url, nil, headers)
|
|
|
+ if err != nil {
|
|
|
+ logger.LogError(c, fmt.Sprintf("failed to forward file delete 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
|
|
|
+ }
|
|
|
+}
|