feat: improve simulation media tasks
This commit is contained in:
@@ -248,10 +248,11 @@ VALUES ($1, 5, '{"purpose":"core-flow"}'::jsonb)`, inviteCode); err != nil {
|
||||
} `json:"task"`
|
||||
}
|
||||
doJSON(t, server.URL, http.MethodPost, "/api/v1/chat/completions", apiKeyResponse.Secret, map[string]any{
|
||||
"model": "gpt-4o-mini",
|
||||
"runMode": "simulation",
|
||||
"simulation": true,
|
||||
"messages": []map[string]any{{"role": "user", "content": "ping"}},
|
||||
"model": "gpt-4o-mini",
|
||||
"runMode": "simulation",
|
||||
"simulation": true,
|
||||
"simulationDurationMs": 5,
|
||||
"messages": []map[string]any{{"role": "user", "content": "ping"}},
|
||||
}, http.StatusAccepted, &taskResponse)
|
||||
if taskResponse.Task.ID == "" || taskResponse.Task.Status != "succeeded" || taskResponse.Task.RunMode != "simulation" {
|
||||
t.Fatalf("unexpected task response: %+v", taskResponse.Task)
|
||||
@@ -271,10 +272,11 @@ VALUES ($1, 5, '{"purpose":"core-flow"}'::jsonb)`, inviteCode); err != nil {
|
||||
|
||||
var compatChat map[string]any
|
||||
doJSON(t, server.URL, http.MethodPost, "/v1/chat/completions", apiKeyResponse.Secret, map[string]any{
|
||||
"model": "gpt-4o-mini",
|
||||
"runMode": "simulation",
|
||||
"messages": []map[string]any{{"role": "user", "content": "ping"}},
|
||||
"simulation": true,
|
||||
"model": "gpt-4o-mini",
|
||||
"runMode": "simulation",
|
||||
"messages": []map[string]any{{"role": "user", "content": "ping"}},
|
||||
"simulation": true,
|
||||
"simulationDurationMs": 5,
|
||||
}, http.StatusOK, &compatChat)
|
||||
if compatChat["object"] != "chat.completion" {
|
||||
t.Fatalf("unexpected compatible chat response: %+v", compatChat)
|
||||
@@ -288,12 +290,13 @@ VALUES ($1, 5, '{"purpose":"core-flow"}'::jsonb)`, inviteCode); err != nil {
|
||||
} `json:"task"`
|
||||
}
|
||||
doJSON(t, server.URL, http.MethodPost, "/api/v1/images/generations", apiKeyResponse.Secret, map[string]any{
|
||||
"model": "gpt-image-1",
|
||||
"runMode": "simulation",
|
||||
"prompt": "a tiny gateway console",
|
||||
"size": "1024x1024",
|
||||
"quality": "medium",
|
||||
"simulation": true,
|
||||
"model": "gpt-image-1",
|
||||
"runMode": "simulation",
|
||||
"prompt": "a tiny gateway console",
|
||||
"size": "1024x1024",
|
||||
"quality": "medium",
|
||||
"simulation": true,
|
||||
"simulationDurationMs": 5,
|
||||
}, http.StatusAccepted, &imageResponse)
|
||||
if imageResponse.Task.Status != "succeeded" || imageResponse.Task.Result["id"] == "" {
|
||||
t.Fatalf("unexpected image generation task: %+v", imageResponse.Task)
|
||||
@@ -307,12 +310,13 @@ VALUES ($1, 5, '{"purpose":"core-flow"}'::jsonb)`, inviteCode); err != nil {
|
||||
} `json:"task"`
|
||||
}
|
||||
doJSON(t, server.URL, http.MethodPost, "/api/v1/images/edits", apiKeyResponse.Secret, map[string]any{
|
||||
"model": "gpt-image-1",
|
||||
"runMode": "simulation",
|
||||
"prompt": "replace background with clean studio light",
|
||||
"image": "https://example.com/source.png",
|
||||
"mask": "https://example.com/mask.png",
|
||||
"simulation": true,
|
||||
"model": "gpt-image-1",
|
||||
"runMode": "simulation",
|
||||
"prompt": "replace background with clean studio light",
|
||||
"image": "https://example.com/source.png",
|
||||
"mask": "https://example.com/mask.png",
|
||||
"simulation": true,
|
||||
"simulationDurationMs": 5,
|
||||
}, http.StatusAccepted, &imageEditResponse)
|
||||
if imageEditResponse.Task.Status != "succeeded" || imageEditResponse.Task.Result["id"] == "" {
|
||||
t.Fatalf("unexpected image edit task: %+v", imageEditResponse.Task)
|
||||
|
||||
@@ -32,6 +32,7 @@ func NewServer(cfg config.Config, db *store.Store, logger *slog.Logger) http.Han
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("GET /healthz", server.health)
|
||||
mux.HandleFunc("GET /readyz", server.ready)
|
||||
mux.HandleFunc("GET /static/simulation/{asset}", serveSimulationAsset)
|
||||
|
||||
mux.Handle("POST /api/v1/auth/register", server.auth.Require(auth.PermissionPublic, http.HandlerFunc(server.register)))
|
||||
mux.Handle("POST /api/v1/auth/login", server.auth.Require(auth.PermissionPublic, http.HandlerFunc(server.login)))
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
package httpapi
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const simulationImageSVG = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024"><defs><linearGradient id="g" x1="0" x2="1" y1="0" y2="1"><stop stop-color="#111827"/><stop offset="0.52" stop-color="#2563eb"/><stop offset="1" stop-color="#14b8a6"/></linearGradient></defs><rect width="1024" height="1024" rx="96" fill="url(#g)"/><circle cx="784" cy="220" r="104" fill="#ffffff" opacity="0.16"/><rect x="168" y="260" width="688" height="504" rx="48" fill="#ffffff" opacity="0.15"/><path d="M232 672 392 504l120 120 96-104 184 152v48H232z" fill="#ffffff" opacity="0.72"/><circle cx="354" cy="398" r="58" fill="#ffffff" opacity="0.82"/><text x="512" y="832" text-anchor="middle" font-family="Inter,Arial,sans-serif" font-size="64" font-weight="700" fill="#ffffff">EasyAI Demo</text></svg>`
|
||||
|
||||
const simulationImageEditSVG = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024"><defs><linearGradient id="g" x1="0" x2="1" y1="0" y2="1"><stop stop-color="#0f172a"/><stop offset="0.48" stop-color="#7c3aed"/><stop offset="1" stop-color="#f97316"/></linearGradient></defs><rect width="1024" height="1024" rx="96" fill="url(#g)"/><rect x="168" y="208" width="456" height="608" rx="44" fill="#ffffff" opacity="0.16"/><rect x="400" y="304" width="456" height="512" rx="44" fill="#ffffff" opacity="0.22"/><path d="M248 678 382 532l92 100 78-84 232 198H248z" fill="#ffffff" opacity="0.76"/><circle cx="342" cy="384" r="54" fill="#ffffff" opacity="0.84"/><text x="512" y="884" text-anchor="middle" font-family="Inter,Arial,sans-serif" font-size="58" font-weight="700" fill="#ffffff">EasyAI Edit Demo</text></svg>`
|
||||
|
||||
const simulationVideoPosterSVG = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1280 720"><defs><linearGradient id="g" x1="0" x2="1" y1="0" y2="1"><stop stop-color="#111827"/><stop offset="0.5" stop-color="#0ea5e9"/><stop offset="1" stop-color="#22c55e"/></linearGradient></defs><rect width="1280" height="720" fill="url(#g)"/><circle cx="1000" cy="170" r="120" fill="#fff" opacity="0.16"/><path d="M548 250v220l190-110z" fill="#fff" opacity="0.9"/><text x="640" y="602" text-anchor="middle" font-family="Inter,Arial,sans-serif" font-size="64" font-weight="700" fill="#ffffff">EasyAI Video Demo</text></svg>`
|
||||
|
||||
const simulationVideoMP4Base64 = "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"
|
||||
|
||||
var simulationVideoMP4 = mustDecodeSimulationAsset(simulationVideoMP4Base64)
|
||||
|
||||
func serveSimulationAsset(w http.ResponseWriter, r *http.Request) {
|
||||
asset := strings.ToLower(strings.TrimSpace(r.PathValue("asset")))
|
||||
switch asset {
|
||||
case "image.svg", "image.png":
|
||||
serveSimulationContent(w, r, "image.svg", "image/svg+xml; charset=utf-8", []byte(simulationImageSVG))
|
||||
case "image-edit.svg", "image-edit.png":
|
||||
serveSimulationContent(w, r, "image-edit.svg", "image/svg+xml; charset=utf-8", []byte(simulationImageEditSVG))
|
||||
case "video-poster.svg":
|
||||
serveSimulationContent(w, r, "video-poster.svg", "image/svg+xml; charset=utf-8", []byte(simulationVideoPosterSVG))
|
||||
case "video.mp4":
|
||||
serveSimulationContent(w, r, "video.mp4", "video/mp4", simulationVideoMP4)
|
||||
default:
|
||||
http.NotFound(w, r)
|
||||
}
|
||||
}
|
||||
|
||||
func serveSimulationContent(w http.ResponseWriter, r *http.Request, name string, contentType string, payload []byte) {
|
||||
w.Header().Set("Cache-Control", "public, max-age=3600")
|
||||
w.Header().Set("Content-Type", contentType)
|
||||
http.ServeContent(w, r, name, time.Time{}, bytes.NewReader(payload))
|
||||
}
|
||||
|
||||
func mustDecodeSimulationAsset(value string) []byte {
|
||||
decoded, err := base64.StdEncoding.DecodeString(value)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return decoded
|
||||
}
|
||||
Reference in New Issue
Block a user