package main import ( "context" "encoding/json" "log" "net/http" "os" "google.golang.org/genai" ) var geminiClient *genai.Client func main() { loadDotEnv(".env") ctx := context.Background() client, err := genai.NewClient(ctx, &genai.ClientConfig{ APIKey: os.Getenv("GEMINI_API_KEY"), Backend: genai.BackendGeminiAPI, }) if err != nil { log.Fatalf("failed to create genai client: %v", err) } geminiClient = client port := os.Getenv("PORT") if port == "" { port = "8080" } http.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) w.Write([]byte("ok")) }) http.HandleFunc("/api/video-meta", handleVideoMeta) http.HandleFunc("/api/chat", handleChat) http.HandleFunc("/api/unlock", handleUnlock) http.Handle("/", http.FileServer(http.Dir("web"))) log.Printf("listening on :%s", port) if err := http.ListenAndServe(":"+port, nil); err != nil { log.Fatal(err) } } func handleVideoMeta(w http.ResponseWriter, r *http.Request) { id := r.URL.Query().Get("id") if id == "" { http.Error(w, "missing id", http.StatusBadRequest) return } meta, err := fetchVideoMeta(id, os.Getenv("YOUTUBE_API_KEY")) if err != nil { http.Error(w, err.Error(), http.StatusBadGateway) return } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(meta) } type chatRequest struct { VideoTitle string `json:"videoTitle"` VideoDescription string `json:"videoDescription"` History []chatTurn `json:"history"` Message string `json:"message"` UnlockCode string `json:"unlockCode"` } type chatResponse struct { Reply string `json:"reply"` Model string `json:"model"` } func handleChat(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "method not allowed", http.StatusMethodNotAllowed) return } var req chatRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, "bad request", http.StatusBadRequest) return } model := selectModel(req.UnlockCode, os.Getenv("UNLOCK_CODE")) reply, err := chatReply(r.Context(), geminiClient, model, req.VideoTitle, req.VideoDescription, req.History, req.Message) if err != nil { http.Error(w, err.Error(), http.StatusBadGateway) return } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(chatResponse{Reply: reply, Model: model}) } type unlockRequest struct { Code string `json:"code"` } type unlockResponse struct { Ok bool `json:"ok"` Model string `json:"model"` } func handleUnlock(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "method not allowed", http.StatusMethodNotAllowed) return } var req unlockRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, "bad request", http.StatusBadRequest) return } model := selectModel(req.Code, os.Getenv("UNLOCK_CODE")) w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(unlockResponse{Ok: model == modelGemini, Model: model}) }