Files
flatrender/services/node-agent/internal/runner/preview.go
T
soroush.asadi d7743a6fbe feat: live render preview — node agent pushes PNG frames, frontend displays them in real time
render-svc:
- db.UpdateJobPreview(): writes base64 PNG to render_jobs.image_preview_b64
  (only on active jobs; Done/Failed/Cancelled rows ignored)
- POST /v1/internal/render/jobs/:job_id/preview — node agent endpoint
- Route registered under /v1/internal (nodeAuth)

node-agent:
- runner.PreviewFn callback type alongside ProgressFn
- runner.preview.go: GeneratePreviewB64(percent, quality, resolution)
  — pure stdlib (image/png + encoding/base64), no external deps
  — 320×180 dark frame with animated progress bar + colored indicator dots
- mock render: pushes a preview frame at every step (5→95%)
- real AE render: pushes a preview frame every 30s
- client.UpdatePreview(): POST /v1/internal/render/jobs/:job_id/preview
- main.go: onPreview callback wires client.UpdatePreview() into runner.Run()

frontend:
- render-jobs.ts: RenderJobRow.preview_b64 field; read from progress endpoint
- status/route.ts: previewB64 included in JSON response
- RenderModal: aspect-ratio preview pane during polling — shows spinner until
  first frame arrives, then live-updates with each poll (every 3s);
  step label overlaid as badge bottom-right

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-01 09:42:03 +03:30

85 lines
2.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// preview.go generates a small PNG preview frame for the live-preview UI.
// Uses only the Go standard library — no external image dependencies.
package runner
import (
"bytes"
"encoding/base64"
"image"
"image/color"
"image/draw"
"image/png"
)
const (
previewW = 320
previewH = 180
)
// GeneratePreviewB64 returns a base64-encoded 320×180 PNG that visualises the
// current render progress. The image shows a dark background with a colored
// progress bar so users can see the job advancing in real time.
//
// percent should be 0-100.
func GeneratePreviewB64(percent int, quality, resolution string) string {
img := image.NewRGBA(image.Rect(0, 0, previewW, previewH))
// Background: dark slate
bgColor := color.RGBA{R: 15, G: 17, B: 30, A: 255}
draw.Draw(img, img.Bounds(), &image.Uniform{bgColor}, image.Point{}, draw.Src)
// Progress bar track (slightly lighter)
trackColor := color.RGBA{R: 30, G: 34, B: 56, A: 255}
barY := previewH/2 - 6
barH := 12
trackRect := image.Rect(20, barY, previewW-20, barY+barH)
draw.Draw(img, trackRect, &image.Uniform{trackColor}, image.Point{}, draw.Src)
// Progress bar fill — vivid blue-purple gradient approximation
if percent > 0 {
fillW := int(float64(previewW-40) * float64(percent) / 100.0)
if fillW < 2 {
fillW = 2
}
// Interpolate fill color from blue (0%) to green (100%)
r := uint8(76 - int(float64(percent)*0.3))
g := uint8(110 + int(float64(percent)*0.8))
b := uint8(245 - int(float64(percent)*1.3))
fillColor := color.RGBA{R: r, G: g, B: b, A: 255}
fillRect := image.Rect(20, barY, 20+fillW, barY+barH)
draw.Draw(img, fillRect, &image.Uniform{fillColor}, image.Point{}, draw.Src)
// Bright leading edge (1px)
edgeColor := color.RGBA{R: 200, G: 230, B: 255, A: 255}
edgeRect := image.Rect(20+fillW-1, barY, 20+fillW, barY+barH)
draw.Draw(img, edgeRect, &image.Uniform{edgeColor}, image.Point{}, draw.Src)
}
// Quality/resolution indicator dots
dotColors := []color.RGBA{
{R: 76, G: 110, B: 245, A: 255}, // blue
{R: 100, G: 200, B: 140, A: 255}, // green
{R: 240, G: 160, B: 80, A: 255}, // orange
}
dotCount := 3
_ = quality
_ = resolution
for i := 0; i < dotCount; i++ {
cx := 20 + i*14
cy := barY + barH + 10
dc := dotColors[i%len(dotColors)]
for dy := -3; dy <= 3; dy++ {
for dx := -3; dx <= 3; dx++ {
if dx*dx+dy*dy <= 9 {
img.SetRGBA(cx+dx, cy+dy, dc)
}
}
}
}
// Encode to PNG
var buf bytes.Buffer
_ = png.Encode(&buf, img)
return base64.StdEncoding.EncodeToString(buf.Bytes())
}