feat(playground): image & video generation tabs + real chat metrics

The infosave collection now ships media models (Lumina-Image-2.0, MiniMax-H3
Turbo) that cortiq runs via `imagine`/`animate` CLI, not `cortiq serve`. The
gateway grows a media API: local .cmf files are classified by `cortiq info`
(chat/image/video, cached), generations run as tracked jobs with step progress
parsed from stdout, one at a time, cancellable, outputs under <data>/media.

The playground becomes three tabs. Image: width/height/steps/CFG/seed, live
progress, result rendered from P6 PPM to a canvas in the browser (PNG export
via toBlob). Video: frames/steps/quality/seed plus optional first/last
keyframes (rasterized to PPM client-side); the MJPEG+PCM AVI is decoded in JS
and played on a canvas synced to the wav — no ffmpeg on either side (mp4 remux
happens opportunistically when ffmpeg exists). A metrics card shows resolution,
steps, seed, total time, time per step/frame and file size.

Chat metrics: the local cortiq server sends no usage chunk in streams, so
token counts showed 0/0. tap_stream now injects one OpenAI-shaped usage event
ahead of [DONE] with the gateway's ~4-chars/token estimate, flagged
`cortiq_estimated`; the UI renders it as "≈ N / M" and adds tok/s and
time-to-first-token. Registration skips media packs (a `cortiq serve` entry
for a diffusion model would only fail) in both Register and the wizard's
Finish.

36 new UI strings translated across all 7 languages.
This commit is contained in:
infosave2007
2026-08-07 15:55:06 +03:00
parent 32ce2f7ed7
commit a77beeabab
16 changed files with 1814 additions and 30 deletions
+38
View File
@@ -468,4 +468,42 @@ export default {
"onboarding.deleteFile": "Die .cmf-Datei von der Festplatte löschen",
"onboarding.confirmDelete": "„{id}“ löschen? Die .cmf-Datei wird von der Festplatte entfernt.",
"onboarding.unregistered": "Ebenfalls abgemeldet: {ids}",
// playground: media generation + streaming metrics
"pg.tab.chat": "Chat",
"pg.tab.image": "Bild",
"pg.tab.video": "Video",
"pg.generate": "Generieren",
"pg.generating": "Wird generiert…",
"pg.genFailed": "Generierung fehlgeschlagen — siehe Log.",
"pg.result": "Ergebnis",
"pg.genMetrics": "Generierungsmetriken",
"pg.imagePromptPlaceholder": "ein Rotfuchs im Schnee bei Sonnenuntergang, fotorealistisch, detailliertes Fell",
"pg.videoPromptPlaceholder": "ein Corgi mit Kochmütze wirft einen Pfannkuchen, Brutzeln und ein fröhliches Bellen",
"pg.imageEmpty": "Das generierte Bild erscheint hier.",
"pg.videoEmpty": "Der generierte Clip (mit Ton) erscheint hier.",
"pg.width": "Breite",
"pg.height": "Höhe",
"pg.steps": "Schritte",
"pg.seed": "Seed",
"pg.randomSeed": "Zufälliger Seed",
"pg.cfgHint": "Guidance-Stärke; ≤ 1 schaltet CFG ab und halbiert die Arbeit.",
"pg.frames": "Frames",
"pg.framesHint": "Bei 24 fps; auf das Raster des Modells gerundet.",
"pg.quality": "JPEG-Qualität",
"pg.videoSizeHint": "Vielfache von 32; die trainierte kurze Kante ist 768.",
"pg.videoStepsHint": "Die Turbo-LoRA ist auf 4 Schritte trainiert.",
"pg.firstFrame": "Erstes Frame (optional)",
"pg.lastFrame": "Letztes Frame (optional)",
"pg.keyframeHint": "Macht den Lauf zu Keyframe-to-Video.",
"pg.resolution": "Auflösung",
"pg.genTime": "Generierungszeit",
"pg.timePerStep": "Pro Schritt",
"pg.timePerFrame": "Pro Frame",
"pg.fileSize": "Dateigröße",
"pg.duration": "Dauer",
"pg.play": "Abspielen",
"pg.noMediaModels": "Noch kein lokales {kind}-Modell. Lade eines (z. B. aus der infosave-Sammlung) unter Import herunter:",
"pg.tokSpeed": "Geschwindigkeit",
"pg.ttft": "Erstes Token",
};
+38
View File
@@ -461,4 +461,42 @@ export default {
"onboarding.deleteFile": "Delete the .cmf file from disk",
"onboarding.confirmDelete": "Delete '{id}'? The .cmf file will be removed from disk.",
"onboarding.unregistered": "Also unregistered: {ids}",
// playground: media generation + streaming metrics
"pg.tab.chat": "Chat",
"pg.tab.image": "Image",
"pg.tab.video": "Video",
"pg.generate": "Generate",
"pg.generating": "Generating…",
"pg.genFailed": "Generation failed — see the log.",
"pg.result": "Result",
"pg.genMetrics": "Generation metrics",
"pg.imagePromptPlaceholder": "a red fox sitting in snow at sunset, photorealistic, detailed fur",
"pg.videoPromptPlaceholder": "a corgi in a chef hat flipping a pancake, sizzling sounds and a cheerful bark",
"pg.imageEmpty": "The generated image will appear here.",
"pg.videoEmpty": "The generated clip (with audio) will appear here.",
"pg.width": "Width",
"pg.height": "Height",
"pg.steps": "Steps",
"pg.seed": "Seed",
"pg.randomSeed": "Random seed",
"pg.cfgHint": "Guidance scale; ≤ 1 disables CFG and halves the work.",
"pg.frames": "Frames",
"pg.framesHint": "At 24 fps; snapped to the model's grid.",
"pg.quality": "JPEG quality",
"pg.videoSizeHint": "Multiples of 32; the trained short edge is 768.",
"pg.videoStepsHint": "The Turbo LoRA is trained for 4 steps.",
"pg.firstFrame": "First frame (optional)",
"pg.lastFrame": "Last frame (optional)",
"pg.keyframeHint": "Turns the run into keyframe-to-video.",
"pg.resolution": "Resolution",
"pg.genTime": "Generation time",
"pg.timePerStep": "Per step",
"pg.timePerFrame": "Per frame",
"pg.fileSize": "File size",
"pg.duration": "Duration",
"pg.play": "Play",
"pg.noMediaModels": "No local {kind} model yet. Download one (e.g. from the infosave collection) in Import:",
"pg.tokSpeed": "Speed",
"pg.ttft": "First token",
};
+38
View File
@@ -468,4 +468,42 @@ export default {
"onboarding.deleteFile": "Eliminar el archivo .cmf del disco",
"onboarding.confirmDelete": "¿Eliminar «{id}»? El archivo .cmf se borrará del disco.",
"onboarding.unregistered": "También se dio de baja: {ids}",
// playground: media generation + streaming metrics
"pg.tab.chat": "Chat",
"pg.tab.image": "Imagen",
"pg.tab.video": "Vídeo",
"pg.generate": "Generar",
"pg.generating": "Generando…",
"pg.genFailed": "La generación falló — revisa el registro.",
"pg.result": "Resultado",
"pg.genMetrics": "Métricas de generación",
"pg.imagePromptPlaceholder": "un zorro rojo en la nieve al atardecer, fotorrealista, pelaje detallado",
"pg.videoPromptPlaceholder": "un corgi con gorro de chef voltea una tortita, chisporroteo y un ladrido alegre",
"pg.imageEmpty": "La imagen generada aparecerá aquí.",
"pg.videoEmpty": "El clip generado (con audio) aparecerá aquí.",
"pg.width": "Ancho",
"pg.height": "Alto",
"pg.steps": "Pasos",
"pg.seed": "Seed",
"pg.randomSeed": "Seed aleatorio",
"pg.cfgHint": "Escala de guidance; ≤ 1 desactiva el CFG y reduce el trabajo a la mitad.",
"pg.frames": "Fotogramas",
"pg.framesHint": "A 24 fps; se ajusta a la rejilla del modelo.",
"pg.quality": "Calidad JPEG",
"pg.videoSizeHint": "Múltiplos de 32; el lado corto entrenado es 768.",
"pg.videoStepsHint": "La LoRA Turbo está entrenada para 4 pasos.",
"pg.firstFrame": "Primer fotograma (opcional)",
"pg.lastFrame": "Último fotograma (opcional)",
"pg.keyframeHint": "Convierte la ejecución en keyframe-to-video.",
"pg.resolution": "Resolución",
"pg.genTime": "Tiempo de generación",
"pg.timePerStep": "Por paso",
"pg.timePerFrame": "Por fotograma",
"pg.fileSize": "Tamaño del archivo",
"pg.duration": "Duración",
"pg.play": "Reproducir",
"pg.noMediaModels": "Aún no hay un modelo {kind} local. Descarga uno (p. ej. de la colección infosave) en Importar:",
"pg.tokSpeed": "Velocidad",
"pg.ttft": "Primer token",
};
+38
View File
@@ -468,4 +468,42 @@ export default {
"onboarding.deleteFile": "Supprimer le fichier .cmf du disque",
"onboarding.confirmDelete": "Supprimer « {id} » ? Le fichier .cmf sera retiré du disque.",
"onboarding.unregistered": "Également désenregistré : {ids}",
// playground: media generation + streaming metrics
"pg.tab.chat": "Chat",
"pg.tab.image": "Image",
"pg.tab.video": "Vidéo",
"pg.generate": "Générer",
"pg.generating": "Génération…",
"pg.genFailed": "Échec de la génération — voir le journal.",
"pg.result": "Résultat",
"pg.genMetrics": "Métriques de génération",
"pg.imagePromptPlaceholder": "un renard roux dans la neige au coucher du soleil, photoréaliste, fourrure détaillée",
"pg.videoPromptPlaceholder": "un corgi en toque de chef fait sauter une crêpe, grésillements et aboiement joyeux",
"pg.imageEmpty": "L'image générée apparaîtra ici.",
"pg.videoEmpty": "Le clip généré (avec le son) apparaîtra ici.",
"pg.width": "Largeur",
"pg.height": "Hauteur",
"pg.steps": "Étapes",
"pg.seed": "Seed",
"pg.randomSeed": "Seed aléatoire",
"pg.cfgHint": "Force du guidance ; ≤ 1 désactive le CFG et divise le travail par deux.",
"pg.frames": "Images",
"pg.framesHint": "À 24 fps ; arrondi à la grille du modèle.",
"pg.quality": "Qualité JPEG",
"pg.videoSizeHint": "Multiples de 32 ; le petit côté entraîné est 768.",
"pg.videoStepsHint": "La LoRA Turbo est entraînée pour 4 étapes.",
"pg.firstFrame": "Première image (optionnel)",
"pg.lastFrame": "Dernière image (optionnel)",
"pg.keyframeHint": "Transforme la génération en keyframe-to-video.",
"pg.resolution": "Résolution",
"pg.genTime": "Temps de génération",
"pg.timePerStep": "Par étape",
"pg.timePerFrame": "Par image",
"pg.fileSize": "Taille du fichier",
"pg.duration": "Durée",
"pg.play": "Lire",
"pg.noMediaModels": "Pas encore de modèle {kind} local. Téléchargez-en un (par ex. de la collection infosave) dans Importer :",
"pg.tokSpeed": "Vitesse",
"pg.ttft": "Premier token",
};
+38
View File
@@ -460,4 +460,42 @@ export default {
"onboarding.deleteFile": "Удалить файл .cmf с диска",
"onboarding.confirmDelete": "Удалить «{id}»? Файл .cmf будет удалён с диска.",
"onboarding.unregistered": "Также отключено из моделей: {ids}",
// playground: media generation + streaming metrics
"pg.tab.chat": "Чат",
"pg.tab.image": "Изображение",
"pg.tab.video": "Видео",
"pg.generate": "Сгенерировать",
"pg.generating": "Генерация…",
"pg.genFailed": "Генерация не удалась — смотрите лог.",
"pg.result": "Результат",
"pg.genMetrics": "Метрики генерации",
"pg.imagePromptPlaceholder": "рыжая лиса в снегу на закате, фотореалистично, детальный мех",
"pg.videoPromptPlaceholder": "корги в поварском колпаке подбрасывает блин, шипение сковороды и весёлый лай",
"pg.imageEmpty": "Сгенерированное изображение появится здесь.",
"pg.videoEmpty": "Сгенерированный ролик (со звуком) появится здесь.",
"pg.width": "Ширина",
"pg.height": "Высота",
"pg.steps": "Шаги",
"pg.seed": "Seed",
"pg.randomSeed": "Случайный seed",
"pg.cfgHint": "Сила guidance; ≤ 1 отключает CFG и вдвое ускоряет генерацию.",
"pg.frames": "Кадры",
"pg.framesHint": "При 24 fps; округляется до сетки модели.",
"pg.quality": "Качество JPEG",
"pg.videoSizeHint": "Кратно 32; обученная короткая сторона — 768.",
"pg.videoStepsHint": "Turbo LoRA обучена на 4 шага.",
"pg.firstFrame": "Первый кадр (необязательно)",
"pg.lastFrame": "Последний кадр (необязательно)",
"pg.keyframeHint": "Превращает запуск в keyframe-to-video.",
"pg.resolution": "Разрешение",
"pg.genTime": "Время генерации",
"pg.timePerStep": "На шаг",
"pg.timePerFrame": "На кадр",
"pg.fileSize": "Размер файла",
"pg.duration": "Длительность",
"pg.play": "Играть",
"pg.noMediaModels": "Локальной модели ({kind}) ещё нет. Скачайте её (например, из коллекции infosave) во вкладке Импорт:",
"pg.tokSpeed": "Скорость",
"pg.ttft": "Первый токен",
};
+38
View File
@@ -468,4 +468,42 @@ export default {
"onboarding.deleteFile": ".cmf dosyasını diskten sil",
"onboarding.confirmDelete": "“{id}” silinsin mi? .cmf dosyası diskten kaldırılacak.",
"onboarding.unregistered": "Ayrıca kaydı kaldırıldı: {ids}",
// playground: media generation + streaming metrics
"pg.tab.chat": "Sohbet",
"pg.tab.image": "Görsel",
"pg.tab.video": "Video",
"pg.generate": "Oluştur",
"pg.generating": "Oluşturuluyor…",
"pg.genFailed": "Oluşturma başarısız — günlüğe bakın.",
"pg.result": "Sonuç",
"pg.genMetrics": "Oluşturma metrikleri",
"pg.imagePromptPlaceholder": "gün batımında karda oturan kızıl tilki, fotogerçekçi, ayrıntılı tüyler",
"pg.videoPromptPlaceholder": "aşçı şapkalı bir corgi krep çeviriyor, cızırtı sesleri ve neşeli bir havlama",
"pg.imageEmpty": "Oluşturulan görsel burada görünecek.",
"pg.videoEmpty": "Oluşturulan klip (sesiyle) burada görünecek.",
"pg.width": "Genişlik",
"pg.height": "Yükseklik",
"pg.steps": "Adım",
"pg.seed": "Seed",
"pg.randomSeed": "Rastgele seed",
"pg.cfgHint": "Guidance gücü; ≤ 1 CFG'yi kapatır ve işi yarıya indirir.",
"pg.frames": "Kare",
"pg.framesHint": "24 fps'te; modelin ızgarasına yuvarlanır.",
"pg.quality": "JPEG kalitesi",
"pg.videoSizeHint": "32'nin katları; eğitilen kısa kenar 768.",
"pg.videoStepsHint": "Turbo LoRA 4 adım için eğitildi.",
"pg.firstFrame": "İlk kare (isteğe bağlı)",
"pg.lastFrame": "Son kare (isteğe bağlı)",
"pg.keyframeHint": "Çalışmayı keyframe-to-video'ya çevirir.",
"pg.resolution": "Çözünürlük",
"pg.genTime": "Oluşturma süresi",
"pg.timePerStep": "Adım başına",
"pg.timePerFrame": "Kare başına",
"pg.fileSize": "Dosya boyutu",
"pg.duration": "Süre",
"pg.play": "Oynat",
"pg.noMediaModels": "Henüz yerel bir {kind} modeli yok. İçe aktarma sekmesinden bir tane indirin (ör. infosave koleksiyonundan):",
"pg.tokSpeed": "Hız",
"pg.ttft": "İlk token",
};
+38
View File
@@ -468,4 +468,42 @@ export default {
"onboarding.deleteFile": "从磁盘删除该 .cmf 文件",
"onboarding.confirmDelete": "删除 “{id}”?该 .cmf 文件将从磁盘移除。",
"onboarding.unregistered": "同时已取消注册:{ids}",
// playground: media generation + streaming metrics
"pg.tab.chat": "对话",
"pg.tab.image": "图像",
"pg.tab.video": "视频",
"pg.generate": "生成",
"pg.generating": "生成中……",
"pg.genFailed": "生成失败 — 请查看日志。",
"pg.result": "结果",
"pg.genMetrics": "生成指标",
"pg.imagePromptPlaceholder": "夕阳下雪地里的红狐狸,照片级真实感,毛发细节丰富",
"pg.videoPromptPlaceholder": "戴厨师帽的柯基翻煎饼,滋滋声和欢快的犬吠",
"pg.imageEmpty": "生成的图像将显示在这里。",
"pg.videoEmpty": "生成的短片(含音频)将显示在这里。",
"pg.width": "宽度",
"pg.height": "高度",
"pg.steps": "步数",
"pg.seed": "Seed",
"pg.randomSeed": "随机 seed",
"pg.cfgHint": "引导强度;≤ 1 关闭 CFG,工作量减半。",
"pg.frames": "帧数",
"pg.framesHint": "24 fps;按模型网格取整。",
"pg.quality": "JPEG 质量",
"pg.videoSizeHint": "32 的倍数;训练短边为 768。",
"pg.videoStepsHint": "Turbo LoRA 按 4 步训练。",
"pg.firstFrame": "首帧(可选)",
"pg.lastFrame": "末帧(可选)",
"pg.keyframeHint": "使运行变为关键帧生视频。",
"pg.resolution": "分辨率",
"pg.genTime": "生成耗时",
"pg.timePerStep": "每步",
"pg.timePerFrame": "每帧",
"pg.fileSize": "文件大小",
"pg.duration": "时长",
"pg.play": "播放",
"pg.noMediaModels": "还没有本地{kind}模型。请在“导入模型”中下载一个(例如 infosave 合集):",
"pg.tokSpeed": "速度",
"pg.ttft": "首个 token",
};