Skip to content

Commit 0c2add5

Browse files
committed
initial frontend revamp
1 parent bf83645 commit 0c2add5

14 files changed

Lines changed: 1531 additions & 249 deletions

File tree

‎backend/routes/clipsterRoutes.py‎

Lines changed: 55 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -115,9 +115,44 @@ async def bulk_upload_media(
115115
):
116116
user_id = request.state.user_id
117117
print(f"Starting bulk upload for user {user_id}, files: {[f.filename for f in files]}")
118+
119+
# Check for duplicate file uploads by querying existing videos for the user
120+
existing_videos = supabase.table("broll_videos").select("*").eq("user_id", user_id).execute()
121+
122+
# Create a dictionary of file_name -> video record for quick lookup
123+
existing_videos_map = {video["file_name"]: video for video in existing_videos.data}
124+
uploaded_file_names = {file.filename for file in files}
125+
126+
# Find duplicate files that are already fully processed (ready and with summary)
127+
duplicate_files = []
128+
new_files = []
129+
130+
for file in files:
131+
if file.filename in existing_videos_map:
132+
existing_video = existing_videos_map[file.filename]
133+
# Check if the video is ready and has a summary (fully processed)
134+
if existing_video.get("twelve_labs_task_status") == "ready" and existing_video.get("summary"):
135+
duplicate_files.append({
136+
"filename": file.filename,
137+
"video_id": existing_video["id"],
138+
"twelve_labs_task_id": existing_video.get("twelve_labs_task_id"),
139+
"twelve_labs_task_status": existing_video.get("twelve_labs_task_status"),
140+
"is_duplicate": True
141+
})
142+
else:
143+
# If file exists but not fully processed, treat as new file
144+
new_files.append(file)
145+
else:
146+
new_files.append(file)
118147

119-
120-
# Create a temporary directory to store files
148+
# If all files are duplicates and fully processed, return the duplicate info
149+
if not new_files and duplicate_files:
150+
return {
151+
"message": "All files were previously uploaded and processed",
152+
"details": duplicate_files
153+
}
154+
155+
# Create a temporary directory to store files that need processing
121156
with tempfile.TemporaryDirectory() as temp_dir:
122157
async def process_single_upload(file: UploadFile):
123158
try:
@@ -246,29 +281,36 @@ async def process_single_upload(file: UploadFile):
246281
return {
247282
"filename": file.filename,
248283
"twelve_labs_task_id": task.id,
249-
"video_id": insert_result.data[0]["id"]
284+
"video_id": insert_result.data[0]["id"],
285+
"is_duplicate": False
250286
}
251287

252288
except Exception as e:
253289
print(f"Error processing {file.filename}: {str(e)}")
254-
# supabase.table("broll_videos").upsert({
255-
# "twelve_labs_task_status": "failed",
256-
# "id": insert_result.data[0]["id"]
257-
# }).execute()
290+
supabase.table("broll_videos").upsert({
291+
"twelve_labs_task_status": "failed",
292+
"id": insert_result.data[0]["id"]
293+
}).execute()
258294
return {
259295
"filename": file.filename,
260296
"status": "error",
261-
"error": str(e)
297+
"error": str(e),
298+
"is_duplicate": False
262299
}
263300

264301
print("Starting concurrent file processing")
265-
tasks = [process_single_upload(file) for file in files]
266-
results = await asyncio.gather(*tasks)
267302

268-
print(f"Bulk upload complete. Results: {results}")
303+
# Only process new files, not duplicates
304+
tasks = [process_single_upload(file) for file in new_files]
305+
results = await asyncio.gather(*tasks) if tasks else []
306+
307+
# Combine the results with duplicate files
308+
all_results = results + duplicate_files
309+
310+
print(f"Bulk upload complete. Results: {all_results}")
269311
return {
270-
"message": f"Processed {len(results)} files",
271-
"details": results
312+
"message": f"Processed {len(all_results)} files ({len(duplicate_files)} already existed)",
313+
"details": all_results
272314
}
273315

274316

‎backend/workers/TaskPoller.py‎

Lines changed: 49 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,6 @@ async def check_pending_tasks(self):
121121
if isinstance(result, Exception):
122122
logger.error(f"Task check failed: {str(result)}")
123123

124-
print(f"Results: {results}")
125124

126125
except Exception as e:
127126
logger.error(f"Error in check_pending_tasks: {str(e)}")
@@ -139,56 +138,51 @@ async def generate_video_summary(self, video: dict) -> dict:
139138

140139
print(f"Generating summary for video {video['twelve_labs_video_id']}")
141140

142-
# # Generate summary
143-
# structured_output = ''' {
144-
# "chapters": [
145-
# {
146-
# "start": int,
147-
# "end": int,
148-
# "summary": str
149-
# }
150-
# ]
151-
# }
152-
# '''
153-
# response = await asyncio.get_event_loop().run_in_executor(
154-
# self.executor,
155-
# # lambda: twelve_labs_client.generate.summarize(
156-
# # video["twelve_labs_video_id"],
157-
# # type="highlight",
158-
# # prompt="Generate chapters for every single scene change. Describe people, objects, and actions in detail. Granularity should be at the level of a single shot. No Chapter should be longer than 10 seconds.",
159-
# # temperature=0.
160-
# # )
161-
# lambda: twelve_labs_client.generate.text(
162-
# video["twelve_labs_video_id"],
163-
# # For regex patterns, use raw strings to avoid Python interpreting the backslashes
164-
# prompt = 'Generate chapters for every single scene change. Describe people, objects, and actions in detail. Granularity should be at the level of a single shot. Output should be in non-markdown JSON format with the following fields: start, end, summary. Example Output:' + structured_output,
165-
# temperature=0.1,
166-
# )
167-
# )
168-
169-
# print(f"Summary response: {response}")
170-
171-
# # Check if response contains markdown JSON format
172-
# if response.data:
173-
# if isinstance(response.data, str) and "```json" in response.data:
174-
# # If it's markdown JSON format, use the parser
175-
# summary = parse_json_from_markdown(response.data)
176-
# else:
177-
# # If it's direct JSON, parse it directly
178-
# try:
179-
# summary = json.loads(response.data)
180-
# except json.JSONDecodeError as e:
181-
# logger.error(f"Error parsing JSON response for video {video['id']}: {str(e)}")
182-
# summary = None
183-
# else:
184-
# summary = None
185-
186-
summary = supabase.table("broll_videos").select("summary").eq("id", video["id"]).execute()
187-
if summary.data:
188-
summary = summary.data[0]["summary"]
141+
# Generate summary
142+
structured_output = ''' {
143+
"chapters": [
144+
{
145+
"start": int,
146+
"end": int,
147+
"summary": str
148+
}
149+
]
150+
}
151+
'''
152+
response = await asyncio.get_event_loop().run_in_executor(
153+
self.executor,
154+
# lambda: twelve_labs_client.generate.summarize(
155+
# video["twelve_labs_video_id"],
156+
# type="highlight",
157+
# prompt="Generate chapters for every single scene change. Describe people, objects, and actions in detail. Granularity should be at the level of a single shot. No Chapter should be longer than 10 seconds.",
158+
# temperature=0.
159+
# )
160+
lambda: twelve_labs_client.generate.text(
161+
video["twelve_labs_video_id"],
162+
# For regex patterns, use raw strings to avoid Python interpreting the backslashes
163+
prompt = 'Generate chapters for every single scene change. Describe people, objects, and actions in detail. Granularity should be at the level of a single shot. The longest a single chapter should be 10 seconds. Output should be in non-markdown JSON format with the following fields: start, end, summary. Example Output:' + structured_output,
164+
temperature=0.1,
165+
)
166+
)
167+
168+
print(f"Summary response: {response}")
169+
170+
# Check if response contains markdown JSON format
171+
if response.data:
172+
if isinstance(response.data, str) and "```json" in response.data:
173+
# If it's markdown JSON format, use the parser
174+
summary = parse_json_from_markdown(response.data)
175+
else:
176+
# If it's direct JSON, parse it directly
177+
try:
178+
summary = json.loads(response.data)
179+
except json.JSONDecodeError as e:
180+
logger.error(f"Error parsing JSON response for video {video['id']}: {str(e)}")
181+
summary = None
189182
else:
190183
summary = None
191184

185+
192186
# TODO: Matching timestamped words from transcriptto chapters in summary
193187
# append the transcript to the summary in DB
194188
transcript = await asyncio.get_event_loop().run_in_executor(
@@ -218,16 +212,15 @@ async def generate_video_summary(self, video: dict) -> dict:
218212

219213
chapter["summary"] += " Spoken Content:" + " ".join(chapter_summary).strip()
220214

221-
print(f"Summary: {summary}")
222215

223216

224-
# # Update with summary
225-
# await asyncio.get_event_loop().run_in_executor(
226-
# self.executor,
227-
# lambda: supabase.table("broll_videos").update({
228-
# "summary": summary
229-
# }).eq("id", video["id"]).execute()
230-
# )
217+
# Update with summary
218+
await asyncio.get_event_loop().run_in_executor(
219+
self.executor,
220+
lambda: supabase.table("broll_videos").update({
221+
"summary": summary
222+
}).eq("id", video["id"]).execute()
223+
)
231224

232225
# # Upsert vectors to Pinecone if summary was generated successfully
233226
# if summary and "chapters" in summary and len(summary["chapters"]) > 0:
@@ -265,12 +258,10 @@ async def check_ready_videos_for_summary(self):
265258
.execute()
266259
)
267260

268-
print(f"Ready Videos: {response}")
269261

270262
if not response.data:
271263
return
272264

273-
print(f"Ready Videos: {response}")
274265
# Process summaries in chunks to control concurrency
275266
videos = response.data
276267
chunk_size = self.max_concurrent_summaries

‎frontend/package-lock.json‎

Lines changed: 43 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎frontend/package.json‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
"clsx": "^2.1.1",
5252
"date-fns": "^4.1.0",
5353
"express": "^4.21.2",
54+
"framer-motion": "^12.6.3",
5455
"lucide-react": "^0.344.0",
5556
"react": "^18.3.1",
5657
"react-datepicker": "^8.0.0",

‎frontend/src/App.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ const ProtectedRoute = ({ children }: { children: React.ReactNode }) => {
3636
const NavigationWrapper = () => {
3737
const location = useLocation();
3838
// Don't show navigation on the create page
39-
if (location.pathname === "/create") {
39+
if (location.pathname === "/create" || location.pathname === "/oneshot") {
4040
return null;
4141
}
4242
return <Navigation />;

‎frontend/src/api/clipster-api.ts‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ import { http } from "./http-common";
1111
const API_URL = `/api/clipster`;
1212

1313
interface BulkUploadResponse {
14-
message: string;
14+
message?: string;
15+
error?: boolean;
16+
duplicate_file_names?: string[];
1517
details: {
1618
filename: string;
1719
twelve_labs_task_status: "success" | "error";

‎frontend/src/components/caster/contentEditor/BRollLibrary.tsx‎

Lines changed: 1 addition & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
DialogClose,
2020
} from "../../ui/dialog";
2121
import { useToast } from "../../../hooks/use-toast";
22+
import { UploadedVideo } from "../../../types/types";
2223

2324
// Helper function to format time
2425
const formatTime = (seconds: number) => {
@@ -53,27 +54,6 @@ const formatTotalDuration = (seconds: number) => {
5354
};
5455

5556
// Type definition for uploaded video
56-
interface UploadedVideo {
57-
id: string;
58-
name: string;
59-
s3Url?: string;
60-
twelveLabsId?: string;
61-
twelveLabsTaskId?: string;
62-
status:
63-
| "uploading"
64-
| "pending"
65-
| "processing"
66-
| "ready"
67-
| "error"
68-
| "failed"
69-
| "indexing";
70-
errorMessage?: string;
71-
summary?: any;
72-
summaryStarted?: boolean;
73-
duration?: number;
74-
thumbnail?: string;
75-
progress?: number;
76-
}
7757

7858
interface BRollLibraryProps {
7959
onMediaUpload: (url: string, type: string) => void;

0 commit comments

Comments
 (0)