120 lines
3 KiB
Go
120 lines
3 KiB
Go
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})
|
|
}
|