Spaces:
Running
Running
wuyiqunLu
commited on
feat: use video cover as thumbnail (#35)
Browse files<img width="1131" alt="image"
src="https://github.com/landing-ai/vision-agent-ui/assets/132986242/4c981213-59a1-47a3-831e-a0cf529ad865">
- app/api/vision-agent/route.ts +10 -11
- components/ui/Img.tsx +23 -2
- package.json +1 -0
- pnpm-lock.yaml +0 -0
app/api/vision-agent/route.ts
CHANGED
@@ -1,8 +1,4 @@
|
|
1 |
-
import {
|
2 |
-
StreamingTextResponse,
|
3 |
-
experimental_StreamData,
|
4 |
-
createStreamDataTransformer,
|
5 |
-
} from 'ai';
|
6 |
|
7 |
// import { auth } from '@/auth';
|
8 |
import { MessageBase } from '../../../lib/types';
|
@@ -76,11 +72,15 @@ export const POST = withLogging(
|
|
76 |
return new StreamingTextResponse(
|
77 |
new ReadableStream({
|
78 |
async start(controller) {
|
79 |
-
|
80 |
-
|
81 |
-
|
82 |
-
|
83 |
-
|
|
|
|
|
|
|
|
|
84 |
}
|
85 |
},
|
86 |
}),
|
@@ -110,7 +110,6 @@ export const POST = withLogging(
|
|
110 |
if (fetchResponse.body) {
|
111 |
const encoder = new TextEncoder();
|
112 |
const decoder = new TextDecoder();
|
113 |
-
|
114 |
const stream = fetchResponse.body.pipeThrough(
|
115 |
new TransformStream({
|
116 |
transform: async (chunk, controller) => {
|
|
|
1 |
+
import { StreamingTextResponse } from 'ai';
|
|
|
|
|
|
|
|
|
2 |
|
3 |
// import { auth } from '@/auth';
|
4 |
import { MessageBase } from '../../../lib/types';
|
|
|
72 |
return new StreamingTextResponse(
|
73 |
new ReadableStream({
|
74 |
async start(controller) {
|
75 |
+
try {
|
76 |
+
const { done, value } = await reader.read();
|
77 |
+
if (!done) {
|
78 |
+
const errorText = new TextDecoder().decode(value);
|
79 |
+
logger.error(session, errorText, request);
|
80 |
+
controller.error(new Error(`Response error: ${errorText}`));
|
81 |
+
}
|
82 |
+
} catch (e) {
|
83 |
+
logger.error(session, (e as Error).message, request);
|
84 |
}
|
85 |
},
|
86 |
}),
|
|
|
110 |
if (fetchResponse.body) {
|
111 |
const encoder = new TextEncoder();
|
112 |
const decoder = new TextDecoder();
|
|
|
113 |
const stream = fetchResponse.body.pipeThrough(
|
114 |
new TransformStream({
|
115 |
transform: async (chunk, controller) => {
|
components/ui/Img.tsx
CHANGED
@@ -1,8 +1,10 @@
|
|
1 |
'use client';
|
2 |
|
3 |
-
import React from 'react';
|
4 |
import Image from 'next/image';
|
5 |
import { cn } from '@/lib/utils';
|
|
|
|
|
6 |
|
7 |
const placeholder =
|
8 |
'data:image/jpeg;base64,/9j/4AAQSkZJRgABAgEASABIAAD/4QDWRXhpZgAATU0AKgAAAAgABwEGAAMAAAABAAIAAAESAAMAAAABAAEAAAEaAAUAAAABAAAAYgEbAAUAAAABAAAAagEoAAMAAAABAAIAAAITAAMAAAABAAEAAIdpAAQAAAABAAAAcgAAAAAAAABIAAAAAQAAAEgAAAABAAeQAAAHAAAABDAyMjGRAQAHAAAABAECAwCgAAAHAAAABDAxMDCgAQADAAAAAQABAACgAgAEAAAAAQAAADKgAwAEAAAAAQAAADKkBgADAAAAAQAAAAAAAAAAAAD/wAARCAAyADIDASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9sAQwABAQEBAQECAQECAwICAgMEAwMDAwQGBAQEBAQGBwYGBgYGBgcHBwcHBwcHCAgICAgICQkJCQkLCwsLCwsLCwsL/9sAQwECAgIDAwMFAwMFCwgGCAsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsL/90ABAAE/9oADAMBAAIRAxEAPwD+nbSfDst/NnB5Ne5+G/htJMqsVOD610fw/wDCS3Lq7L3r6y0Lw7b20SgqMiluB4hpvwti2Asn51fufhVbsmQlfSCQQQjHFOIt24OKLAfEXiL4XtCjMiGvnrxH4SlsJG+UjFfqZf6Va3UZUgV84/ETwTGY3kRaVrbAfAp058//AK6T+zn/AM5/wr1uTw0BIwx3P+elM/4Roen+fyo5gP/Q/tp+G1vGIVJFer6n4lstGizM4XFeH/DzVUW3HPavmL9pX4sXvhmKV0Yqq5qbgfXeq/GjRLJipmH51yMn7Qvh2NtrTL+dfzYfGL9uqTw1PLGZjkE96+M7j/gotqtzf+WkrYz60rsD+1bwt8YtD12ZYYZlJPvXY+KlivdOMy8giv5q/wBjD9prxB8QNcgXezKxHOa/obsdZa48KRPMTuKiqA8dm09PNbjuaj/s9PSp5rtDM5B7nvUf2tfX9f8A69QB/9H+v3wLrW1RHuxmvJv2iPh43jLRZhEuSynpWN4X8Rm0lCs2BnivoKy1iy1W1ENyQcjFQB/J7+0x+x34yvtVmlsY3OScYBr418KfsLfES81xFmgkALDPBr+1rXPhR4d15jJJEj59qwtP+BXhiznE4t0BHsKEugH54fsJfsqS/Dqzt7m/TDgAnIr9j9Q1BNN0kW4OAq1yun2mk+G7fZAFG0dq868Y+LwyMit+FFtAEl1tfNb5x1NR/wBtr/fH+fwrxptbnLE5703+2p6QH//S/pMs+JjivYfDjv5Q5NePWn+uNev+HP8AVCkwPWdPdtvU1fnkk2n5j09aztP+7V6f7p+lLoBxeuu4jOCa8N1xmMjEnNe4a7/qzXh2t/6xqfQDlqKKKgD/2Q==';
|
@@ -18,9 +20,28 @@ const Img = React.forwardRef<
|
|
18 |
});
|
19 |
// const [isLoading, setIsLoading] = React.useState(true);
|
20 |
const [_, startTransition] = React.useTransition();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
21 |
return (
|
22 |
<Image
|
23 |
-
src={src}
|
24 |
placeholder={placeholder}
|
25 |
width={dimensions.width}
|
26 |
height={dimensions.height}
|
|
|
1 |
'use client';
|
2 |
|
3 |
+
import React, { useEffect, useState } from 'react';
|
4 |
import Image from 'next/image';
|
5 |
import { cn } from '@/lib/utils';
|
6 |
+
import { toast } from 'react-hot-toast';
|
7 |
+
import { getVideoCover } from '@rajesh896/video-thumbnails-generator';
|
8 |
|
9 |
const placeholder =
|
10 |
'data:image/jpeg;base64,/9j/4AAQSkZJRgABAgEASABIAAD/4QDWRXhpZgAATU0AKgAAAAgABwEGAAMAAAABAAIAAAESAAMAAAABAAEAAAEaAAUAAAABAAAAYgEbAAUAAAABAAAAagEoAAMAAAABAAIAAAITAAMAAAABAAEAAIdpAAQAAAABAAAAcgAAAAAAAABIAAAAAQAAAEgAAAABAAeQAAAHAAAABDAyMjGRAQAHAAAABAECAwCgAAAHAAAABDAxMDCgAQADAAAAAQABAACgAgAEAAAAAQAAADKgAwAEAAAAAQAAADKkBgADAAAAAQAAAAAAAAAAAAD/wAARCAAyADIDASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9sAQwABAQEBAQECAQECAwICAgMEAwMDAwQGBAQEBAQGBwYGBgYGBgcHBwcHBwcHCAgICAgICQkJCQkLCwsLCwsLCwsL/9sAQwECAgIDAwMFAwMFCwgGCAsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsL/90ABAAE/9oADAMBAAIRAxEAPwD+nbSfDst/NnB5Ne5+G/htJMqsVOD610fw/wDCS3Lq7L3r6y0Lw7b20SgqMiluB4hpvwti2Asn51fufhVbsmQlfSCQQQjHFOIt24OKLAfEXiL4XtCjMiGvnrxH4SlsJG+UjFfqZf6Va3UZUgV84/ETwTGY3kRaVrbAfAp058//AK6T+zn/AM5/wr1uTw0BIwx3P+elM/4Roen+fyo5gP/Q/tp+G1vGIVJFer6n4lstGizM4XFeH/DzVUW3HPavmL9pX4sXvhmKV0Yqq5qbgfXeq/GjRLJipmH51yMn7Qvh2NtrTL+dfzYfGL9uqTw1PLGZjkE96+M7j/gotqtzf+WkrYz60rsD+1bwt8YtD12ZYYZlJPvXY+KlivdOMy8giv5q/wBjD9prxB8QNcgXezKxHOa/obsdZa48KRPMTuKiqA8dm09PNbjuaj/s9PSp5rtDM5B7nvUf2tfX9f8A69QB/9H+v3wLrW1RHuxmvJv2iPh43jLRZhEuSynpWN4X8Rm0lCs2BnivoKy1iy1W1ENyQcjFQB/J7+0x+x34yvtVmlsY3OScYBr418KfsLfES81xFmgkALDPBr+1rXPhR4d15jJJEj59qwtP+BXhiznE4t0BHsKEugH54fsJfsqS/Dqzt7m/TDgAnIr9j9Q1BNN0kW4OAq1yun2mk+G7fZAFG0dq868Y+LwyMit+FFtAEl1tfNb5x1NR/wBtr/fH+fwrxptbnLE5703+2p6QH//S/pMs+JjivYfDjv5Q5NePWn+uNev+HP8AVCkwPWdPdtvU1fnkk2n5j09aztP+7V6f7p+lLoBxeuu4jOCa8N1xmMjEnNe4a7/qzXh2t/6xqfQDlqKKKgD/2Q==';
|
|
|
20 |
});
|
21 |
// const [isLoading, setIsLoading] = React.useState(true);
|
22 |
const [_, startTransition] = React.useTransition();
|
23 |
+
const [thumbnail, setThumbnail] = useState<string>('');
|
24 |
+
const isVideo =
|
25 |
+
typeof src === 'string' ? src.toLowerCase().endsWith('.mp4') : false;
|
26 |
+
|
27 |
+
useEffect(() => {
|
28 |
+
if (!isVideo) {
|
29 |
+
return;
|
30 |
+
}
|
31 |
+
const generateThumbnail = async () => {
|
32 |
+
try {
|
33 |
+
const cover = await getVideoCover(src as string);
|
34 |
+
setThumbnail(cover);
|
35 |
+
} catch (e) {
|
36 |
+
toast.error((e as Error).message);
|
37 |
+
}
|
38 |
+
};
|
39 |
+
generateThumbnail();
|
40 |
+
}, [isVideo, src]);
|
41 |
+
|
42 |
return (
|
43 |
<Image
|
44 |
+
src={isVideo ? thumbnail : src}
|
45 |
placeholder={placeholder}
|
46 |
width={dimensions.width}
|
47 |
height={dimensions.height}
|
package.json
CHANGED
@@ -23,6 +23,7 @@
|
|
23 |
"@radix-ui/react-slot": "^1.0.2",
|
24 |
"@radix-ui/react-switch": "^1.0.3",
|
25 |
"@radix-ui/react-tooltip": "^1.0.7",
|
|
|
26 |
"@vercel/kv": "^1.0.1",
|
27 |
"ai": "^2.2.31",
|
28 |
"class-variance-authority": "^0.7.0",
|
|
|
23 |
"@radix-ui/react-slot": "^1.0.2",
|
24 |
"@radix-ui/react-switch": "^1.0.3",
|
25 |
"@radix-ui/react-tooltip": "^1.0.7",
|
26 |
+
"@rajesh896/video-thumbnails-generator": "^2.3.9",
|
27 |
"@vercel/kv": "^1.0.1",
|
28 |
"ai": "^2.2.31",
|
29 |
"class-variance-authority": "^0.7.0",
|
pnpm-lock.yaml
CHANGED
The diff for this file is too large to render.
See raw diff
|
|