feat(webui): render video media attachments
Add signed media URLs to live WebSocket replies and teach the WebUI to classify and render video attachments, so bot-sent videos can play inline in both live chats and session history. Made-with: Cursor
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
import type { UIMediaAttachment, UIMediaKind } from "@/lib/types";
|
||||
|
||||
const IMAGE_EXTENSIONS = new Set([
|
||||
".png",
|
||||
".jpg",
|
||||
".jpeg",
|
||||
".gif",
|
||||
".webp",
|
||||
".bmp",
|
||||
".ico",
|
||||
".tif",
|
||||
".tiff",
|
||||
]);
|
||||
|
||||
const VIDEO_EXTENSIONS = new Set([
|
||||
".mp4",
|
||||
".webm",
|
||||
".mov",
|
||||
".m4v",
|
||||
".avi",
|
||||
".mkv",
|
||||
".3gp",
|
||||
]);
|
||||
|
||||
function cleanPath(value: string): string {
|
||||
return value.split(/[?#]/, 1)[0]?.toLowerCase() ?? "";
|
||||
}
|
||||
|
||||
function extensionOf(value?: string): string {
|
||||
if (!value) return "";
|
||||
const path = cleanPath(value);
|
||||
const dot = path.lastIndexOf(".");
|
||||
if (dot < 0) return "";
|
||||
return path.slice(dot);
|
||||
}
|
||||
|
||||
export function inferMediaKind(media: { url?: string; name?: string }): UIMediaKind {
|
||||
const url = media.url ?? "";
|
||||
if (url.startsWith("data:image/")) return "image";
|
||||
if (url.startsWith("data:video/")) return "video";
|
||||
|
||||
const ext = extensionOf(media.name) || extensionOf(url);
|
||||
if (IMAGE_EXTENSIONS.has(ext)) return "image";
|
||||
if (VIDEO_EXTENSIONS.has(ext)) return "video";
|
||||
return "file";
|
||||
}
|
||||
|
||||
export function toMediaAttachment(media: {
|
||||
url?: string;
|
||||
name?: string;
|
||||
kind?: UIMediaKind;
|
||||
}): UIMediaAttachment {
|
||||
return {
|
||||
kind: media.kind ?? inferMediaKind(media),
|
||||
url: media.url,
|
||||
name: media.name,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -22,6 +22,14 @@ export interface UIImage {
|
||||
name?: string;
|
||||
}
|
||||
|
||||
export type UIMediaKind = "image" | "video" | "file";
|
||||
|
||||
export interface UIMediaAttachment {
|
||||
kind: UIMediaKind;
|
||||
url?: string;
|
||||
name?: string;
|
||||
}
|
||||
|
||||
export interface UIMessage {
|
||||
id: string;
|
||||
role: Role;
|
||||
@@ -34,6 +42,8 @@ export interface UIMessage {
|
||||
traces?: string[];
|
||||
/** User turn: optimistic blob URLs for preview. Replay: placeholder chips. */
|
||||
images?: UIImage[];
|
||||
/** Signed or local UI-renderable media attachments. */
|
||||
media?: UIMediaAttachment[];
|
||||
}
|
||||
|
||||
export interface ChatSummary {
|
||||
@@ -71,6 +81,7 @@ export type InboundEvent =
|
||||
text: string;
|
||||
reply_to?: string;
|
||||
media?: string[];
|
||||
media_urls?: Array<{ url: string; name?: string }>;
|
||||
/** Present when the frame is an agent breadcrumb (e.g. tool hint,
|
||||
* generic progress line) rather than a conversational reply. */
|
||||
kind?: "tool_hint" | "progress";
|
||||
|
||||
Reference in New Issue
Block a user