Skip to content

Commit 20f4314

Browse files
Merge pull request #171 from flocko-motion/flo-dev
Mistral works, Mistral images work, Openai Audio works
2 parents 871f3dd + f214c26 commit 20f4314

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+2002
-321
lines changed

.vscode/launch.json

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,5 +103,19 @@
103103
],
104104
"buildFlags": "-tags=ai_tests"
105105
},
106+
{
107+
"name": "Debug TestAudioPlaythroughOpenai",
108+
"type": "go",
109+
"request": "launch",
110+
"mode": "test",
111+
"program": "${workspaceFolder}/testing",
112+
"cwd": "${workspaceFolder}/testing",
113+
"args": [
114+
"-test.run",
115+
"TestGameEngineTestSuite/TestAudioPlaythroughOpenai",
116+
"-test.v"
117+
],
118+
"buildFlags": "-tags=ai_tests"
119+
},
106120
]
107121
}

server/api/routes/apikeys.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package routes
33
import (
44
"net/http"
55
"strings"
6+
"time"
67

78
"cgl/api/httpx"
89
"cgl/db"
@@ -108,7 +109,14 @@ func CreateApiKey(w http.ResponseWriter, r *http.Request) {
108109
return
109110
}
110111

111-
share, err := db.CreateApiKeyWithSelfShare(r.Context(), user.ID, req.Name, req.Platform, req.Key)
112+
// Default name: capitalize platform + today's date, e.g. "Mistral 12.02.26"
113+
name := strings.TrimSpace(req.Name)
114+
if name == "" {
115+
platform := strings.ToUpper(req.Platform[:1]) + req.Platform[1:]
116+
name = platform + " " + time.Now().Format("02.01.06")
117+
}
118+
119+
share, err := db.CreateApiKeyWithSelfShare(r.Context(), user.ID, name, req.Platform, req.Key)
112120
if err != nil {
113121
// Check if it's a platform validation error
114122
if strings.Contains(err.Error(), "unknown platform") {

server/api/routes/guest_play.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ func PlayGuestCreateSession(w http.ResponseWriter, r *http.Request) {
9797

9898
responseMessage := *firstMessage
9999
responseMessage.Image = nil
100+
responseMessage.Audio = nil
100101

101102
httpx.WriteJSON(w, http.StatusOK, GuestSessionResponse{
102103
GameSession: &responseSession,
@@ -174,6 +175,7 @@ func PlayGuestSendAction(w http.ResponseWriter, r *http.Request) {
174175
}
175176

176177
response.Image = nil
178+
response.Audio = nil
177179
httpx.WriteJSON(w, http.StatusOK, response)
178180
}
179181

@@ -238,6 +240,7 @@ type SessionMessageResponse struct {
238240

239241
func toSessionMessageResponse(msg obj.GameSessionMessage) SessionMessageResponse {
240242
msg.Image = nil
243+
msg.Audio = nil
241244
return SessionMessageResponse{msg}
242245
}
243246

server/api/routes/router.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ func NewMux() *http.ServeMux {
135135
mux.HandleFunc("GET /api/messages/{id}/status", GetMessageStatus)
136136
mux.HandleFunc("GET /api/messages/{id}/image", GetMessageImage)
137137
mux.HandleFunc("GET /api/messages/{id}/image/status", GetMessageImageStatus)
138+
mux.HandleFunc("GET /api/messages/{id}/audio", GetMessageAudio)
138139

139140
// Admin
140141
mux.Handle("POST /api/restart", httpx.RequireAuth(PostRestart))

server/api/routes/sessions.go

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -195,8 +195,9 @@ func PostSessionAction(w http.ResponseWriter, r *http.Request) {
195195
}
196196
log.Debug("session action completed", "session_id", session.ID, "response_id", response.ID)
197197

198-
// Return full message (without image bytes)
198+
// Return full message (without image/audio bytes - served via separate endpoints)
199199
response.Image = nil
200+
response.Audio = nil
200201
httpx.WriteJSON(w, http.StatusOK, response)
201202
}
202203

@@ -272,6 +273,7 @@ func CreateGameSession(w http.ResponseWriter, r *http.Request) {
272273

273274
responseMessage := *firstMessage
274275
responseMessage.Image = nil
276+
responseMessage.Audio = nil
275277

276278
httpx.WriteJSON(w, http.StatusOK, SessionResponse{
277279
GameSession: &responseSession,
@@ -565,6 +567,42 @@ func GetMessageImage(w http.ResponseWriter, r *http.Request) {
565567
w.Write(msg.Image)
566568
}
567569

570+
// GetMessageAudio godoc
571+
//
572+
// @Summary Get message audio
573+
// @Description Returns the audio narration for a message (MP3 format).
574+
// @Description No authentication required - message UUIDs are random and unguessable.
575+
// @Tags messages
576+
// @Produce audio/mpeg
577+
// @Param id path string true "Message ID (UUID)"
578+
// @Success 200 {file} binary
579+
// @Failure 400 {object} httpx.ErrorResponse "Invalid message ID"
580+
// @Failure 404 {object} httpx.ErrorResponse "Message or audio not found"
581+
// @Router /messages/{id}/audio [get]
582+
func GetMessageAudio(w http.ResponseWriter, r *http.Request) {
583+
messageID, err := httpx.PathParamUUID(r, "id")
584+
if err != nil {
585+
httpx.WriteError(w, http.StatusBadRequest, "Invalid message ID")
586+
return
587+
}
588+
589+
audio, err := db.GetGameSessionMessageAudioByID(r.Context(), messageID)
590+
if err != nil {
591+
httpx.WriteError(w, http.StatusNotFound, "Message not found")
592+
return
593+
}
594+
595+
if len(audio) == 0 {
596+
httpx.WriteError(w, http.StatusNotFound, "Audio not found")
597+
return
598+
}
599+
600+
w.Header().Set("Content-Type", "audio/mpeg")
601+
w.Header().Set("Cache-Control", "public, max-age=31536000")
602+
w.WriteHeader(http.StatusOK)
603+
w.Write(audio)
604+
}
605+
568606
// GetMessageStream godoc
569607
//
570608
// @Summary Stream message updates (SSE)
@@ -609,12 +647,17 @@ func GetMessageStream(w http.ResponseWriter, r *http.Request) {
609647
return
610648
}
611649

612-
// Stream chunks to client until both text and image are done
650+
// Stream chunks to client until text, image, and audio are all done
651+
log.Debug("[SSE] client connected", "message_id", messageID)
613652
textDone := false
614653
imageDone := false
654+
audioDone := false
655+
chunkCount := 0
615656

616657
for chunk := range s.Chunks {
658+
chunkCount++
617659
data, _ := json.Marshal(chunk)
660+
log.Debug("[SSE] sending chunk to client", "message_id", messageID, "chunk_num", chunkCount, "text_len", len(chunk.Text), "textDone", chunk.TextDone, "imageDone", chunk.ImageDone, "audioDone", chunk.AudioDone)
618661
fmt.Fprintf(w, "data: %s\n\n", data)
619662
flusher.Flush()
620663

@@ -624,10 +667,14 @@ func GetMessageStream(w http.ResponseWriter, r *http.Request) {
624667
if chunk.ImageDone {
625668
imageDone = true
626669
}
670+
if chunk.AudioDone {
671+
audioDone = true
672+
}
627673
if chunk.Error != "" {
628674
break
629675
}
630-
if textDone && imageDone {
676+
// Stream is complete when all active channels are done
677+
if textDone && imageDone && audioDone {
631678
break
632679
}
633680
}

server/db/api_key_shares.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -497,6 +497,7 @@ func GetApiKeysWithShares(ctx context.Context, userID uuid.UUID) ([]obj.ApiKey,
497497
seenKeys[s.ApiKeyID] = true
498498
apiKeys = append(apiKeys, obj.ApiKey{
499499
ID: s.ApiKeyID,
500+
Meta: obj.Meta{CreatedAt: &s.CreatedAt},
500501
UserID: s.OwnerID,
501502
UserName: s.OwnerName,
502503
Name: s.ApiKeyName,

0 commit comments

Comments
 (0)