feat: enable video uploads for Gemini 2.5 Pro models

- Add supportsVideo property to ModelInfo schema
- Mark Gemini 2.5 Flash and Pro models as supporting video
- Update ChatTextArea to accept video files (mp4, mov, avi, webm) when model supports video
- Pass supportsVideo prop from ChatView to ChatTextArea component

Fixes #6144
This commit is contained in:
Roo Code 2025-07-23 23:37:38 +00:00
parent d48be230a2
commit d5cbfea5b9
4 changed files with 34 additions and 15 deletions

View file

@ -32,6 +32,7 @@ export const modelInfoSchema = z.object({
maxThinkingTokens: z.number().nullish(),
contextWindow: z.number(),
supportsImages: z.boolean().optional(),
supportsVideo: z.boolean().optional(),
supportsComputerUse: z.boolean().optional(),
supportsPromptCache: z.boolean(),
supportsReasoningBudget: z.boolean().optional(),

View file

@ -52,6 +52,7 @@ export const geminiModels = {
maxTokens: 64_000,
contextWindow: 1_048_576,
supportsImages: true,
supportsVideo: true,
supportsPromptCache: true,
inputPrice: 0.3,
outputPrice: 2.5,
@ -64,6 +65,7 @@ export const geminiModels = {
maxTokens: 65_535,
contextWindow: 1_048_576,
supportsImages: true,
supportsVideo: true,
supportsPromptCache: false,
inputPrice: 0,
outputPrice: 0,
@ -72,6 +74,7 @@ export const geminiModels = {
maxTokens: 65_535,
contextWindow: 1_048_576,
supportsImages: true,
supportsVideo: true,
supportsPromptCache: true,
inputPrice: 2.5, // This is the pricing for prompts above 200k tokens.
outputPrice: 15,
@ -96,6 +99,7 @@ export const geminiModels = {
maxTokens: 65_535,
contextWindow: 1_048_576,
supportsImages: true,
supportsVideo: true,
supportsPromptCache: true,
inputPrice: 2.5, // This is the pricing for prompts above 200k tokens.
outputPrice: 15,
@ -120,6 +124,7 @@ export const geminiModels = {
maxTokens: 65_535,
contextWindow: 1_048_576,
supportsImages: true,
supportsVideo: true,
supportsPromptCache: true,
inputPrice: 2.5, // This is the pricing for prompts above 200k tokens.
outputPrice: 15,
@ -146,6 +151,7 @@ export const geminiModels = {
maxTokens: 64_000,
contextWindow: 1_048_576,
supportsImages: true,
supportsVideo: true,
supportsPromptCache: true,
inputPrice: 2.5, // This is the pricing for prompts above 200k tokens.
outputPrice: 15,

View file

@ -42,6 +42,7 @@ interface ChatTextAreaProps {
onSend: () => void
onSelectImages: () => void
shouldDisableImages: boolean
supportsVideo?: boolean
onHeightChange?: (height: number) => void
mode: Mode
setMode: (value: Mode) => void
@ -64,6 +65,7 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
onSend,
onSelectImages,
shouldDisableImages,
supportsVideo = false,
onHeightChange,
mode,
setMode,
@ -598,17 +600,21 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
return
}
const acceptedTypes = ["png", "jpeg", "webp"]
const acceptedImageTypes = ["png", "jpeg", "webp"]
const acceptedVideoTypes = supportsVideo ? ["mp4", "mov", "avi", "webm"] : []
const imageItems = Array.from(items).filter((item) => {
const mediaItems = Array.from(items).filter((item) => {
const [type, subtype] = item.type.split("/")
return type === "image" && acceptedTypes.includes(subtype)
return (
(type === "image" && acceptedImageTypes.includes(subtype)) ||
(type === "video" && acceptedVideoTypes.includes(subtype))
)
})
if (!shouldDisableImages && imageItems.length > 0) {
if (!shouldDisableImages && mediaItems.length > 0) {
e.preventDefault()
const imagePromises = imageItems.map((item) => {
const mediaPromises = mediaItems.map((item) => {
return new Promise<string | null>((resolve) => {
const blob = item.getAsFile()
@ -633,8 +639,8 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
})
})
const imageDataArray = await Promise.all(imagePromises)
const dataUrls = imageDataArray.filter((dataUrl): dataUrl is string => dataUrl !== null)
const mediaDataArray = await Promise.all(mediaPromises)
const dataUrls = mediaDataArray.filter((dataUrl): dataUrl is string => dataUrl !== null)
if (dataUrls.length > 0) {
setSelectedImages((prevImages) => [...prevImages, ...dataUrls].slice(0, MAX_IMAGES_PER_MESSAGE))
@ -643,7 +649,7 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
}
}
},
[shouldDisableImages, setSelectedImages, cursorPosition, setInputValue, inputValue, t],
[shouldDisableImages, setSelectedImages, cursorPosition, setInputValue, inputValue, t, supportsVideo],
)
const handleMenuMouseDown = useCallback(() => {
@ -732,15 +738,19 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
const files = Array.from(e.dataTransfer.files)
if (files.length > 0) {
const acceptedTypes = ["png", "jpeg", "webp"]
const acceptedImageTypes = ["png", "jpeg", "webp"]
const acceptedVideoTypes = supportsVideo ? ["mp4", "mov", "avi", "webm"] : []
const imageFiles = files.filter((file) => {
const mediaFiles = files.filter((file) => {
const [type, subtype] = file.type.split("/")
return type === "image" && acceptedTypes.includes(subtype)
return (
(type === "image" && acceptedImageTypes.includes(subtype)) ||
(type === "video" && acceptedVideoTypes.includes(subtype))
)
})
if (!shouldDisableImages && imageFiles.length > 0) {
const imagePromises = imageFiles.map((file) => {
if (!shouldDisableImages && mediaFiles.length > 0) {
const mediaPromises = mediaFiles.map((file) => {
return new Promise<string | null>((resolve) => {
const reader = new FileReader()
@ -758,8 +768,8 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
})
})
const imageDataArray = await Promise.all(imagePromises)
const dataUrls = imageDataArray.filter((dataUrl): dataUrl is string => dataUrl !== null)
const mediaDataArray = await Promise.all(mediaPromises)
const dataUrls = mediaDataArray.filter((dataUrl): dataUrl is string => dataUrl !== null)
if (dataUrls.length > 0) {
setSelectedImages((prevImages) =>
@ -785,6 +795,7 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
shouldDisableImages,
setSelectedImages,
t,
supportsVideo,
],
)

View file

@ -1848,6 +1848,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
onSend={() => handleSendMessage(inputValue, selectedImages)}
onSelectImages={selectImages}
shouldDisableImages={shouldDisableImages}
supportsVideo={model?.supportsVideo || false}
onHeightChange={() => {
if (isAtBottom) {
scrollToBottomAuto()