+
+
- );
- }
-
- // Error state
- if (isError) {
- return (
-
-
-
-
-
-
Failed to recall memories
- {result?.error &&
{result.error}
}
-
+
+
+ {query ? `Searching memories for "${query}"...` : "Recalling memories..."}
+
- );
- }
+
+ );
+ }
- // Success state with memories
- if (isComplete && result?.status === "success") {
- const memories = result.memories || [];
- const count = result.count || 0;
-
- if (count === 0) {
- return (
-
-
-
-
-
No memories found
-
- );
- }
-
- return (
-
-
-
-
- Recalled {count} {count === 1 ? "memory" : "memories"}
-
-
-
- {memories.slice(0, 5).map((memory: MemoryItem) => (
-
-
- {memory.memory_text}
-
- ))}
- {memories.length > 5 && (
-
...and {memories.length - 5} more
- )}
-
+ // Error state
+ if (isError) {
+ return (
+
+
+
- );
- }
+
+
Failed to recall memories
+ {result?.error &&
{result.error}
}
+
+
+ );
+ }
- // Default/incomplete state
- if (query) {
+ // Success state with memories
+ if (isComplete && result?.status === "success") {
+ const memories = result.memories || [];
+ const count = result.count || 0;
+
+ if (count === 0) {
return (
-
Searching memories for "{query}"
+
No memories found
);
}
- return null;
+ return (
+
+
+
+
+ Recalled {count} {count === 1 ? "memory" : "memories"}
+
+
+
+ {memories.slice(0, 5).map((memory: MemoryItem) => (
+
+
+ {memory.memory_text}
+
+ ))}
+ {memories.length > 5 && (
+
...and {memories.length - 5} more
+ )}
+
+
+ );
+ }
+
+ // Default/incomplete state
+ if (query) {
+ return (
+
+
+
+
+
Searching memories for "{query}"
+
+ );
+ }
+
+ return null;
};
// ============================================================================
diff --git a/surfsense_web/components/tool-ui/video-presentation/combined-player.tsx b/surfsense_web/components/tool-ui/video-presentation/combined-player.tsx
index 84147e94e..c630008db 100644
--- a/surfsense_web/components/tool-ui/video-presentation/combined-player.tsx
+++ b/surfsense_web/components/tool-ui/video-presentation/combined-player.tsx
@@ -1,9 +1,10 @@
"use client";
-import React, { useMemo } from "react";
-import { Player } from "@remotion/player";
-import { Sequence, AbsoluteFill, useCurrentFrame, useVideoConfig, interpolate } from "remotion";
import { Audio } from "@remotion/media";
+import { Player } from "@remotion/player";
+import type React from "react";
+import { useMemo } from "react";
+import { AbsoluteFill, interpolate, Sequence, useCurrentFrame, useVideoConfig } from "remotion";
import { FPS } from "@/lib/remotion/constants";
export interface CompiledSlide {
@@ -64,9 +65,7 @@ function Watermark() {
);
}
-export function buildSlideWithWatermark(
- SlideComponent: React.ComponentType,
-): React.FC {
+export function buildSlideWithWatermark(SlideComponent: React.ComponentType): React.FC {
const Wrapped: React.FC = () => (
@@ -115,7 +114,7 @@ export function CombinedPlayer({ slides }: CombinedPlayerProps) {
const totalFrames = useMemo(
() => slides.reduce((sum, s) => sum + s.durationInFrames, 0),
- [slides],
+ [slides]
);
return (
diff --git a/surfsense_web/components/tool-ui/video-presentation/errors.ts b/surfsense_web/components/tool-ui/video-presentation/errors.ts
index 75d769522..da006d8c6 100644
--- a/surfsense_web/components/tool-ui/video-presentation/errors.ts
+++ b/surfsense_web/components/tool-ui/video-presentation/errors.ts
@@ -1,7 +1,11 @@
export function getVideoDownloadErrorToast(err: unknown): { title: string; description: string } {
const msg = err instanceof Error ? err.message.toLowerCase() : "";
- if (msg.includes("webcodecs") || msg.includes("canrendermediaonweb") || msg.includes("not support")) {
+ if (
+ msg.includes("webcodecs") ||
+ msg.includes("canrendermediaonweb") ||
+ msg.includes("not support")
+ ) {
return {
title: "Browser Not Supported",
description: "Video rendering requires Chrome, Edge, or Firefox 130+.",
@@ -24,7 +28,11 @@ export function getVideoDownloadErrorToast(err: unknown): { title: string; descr
export function getPptxExportErrorToast(err: unknown): { title: string; description: string } {
const msg = err instanceof Error ? err.message.toLowerCase() : "";
- if (msg.includes("dynamically imported") || msg.includes("failed to fetch") || msg.includes("network")) {
+ if (
+ msg.includes("dynamically imported") ||
+ msg.includes("failed to fetch") ||
+ msg.includes("network")
+ ) {
return {
title: "Export Unavailable",
description: "Could not load the export module. Check your network and try again.",
diff --git a/surfsense_web/components/tool-ui/video-presentation/generate-video-presentation.tsx b/surfsense_web/components/tool-ui/video-presentation/generate-video-presentation.tsx
index 061e0200a..25ff97d20 100644
--- a/surfsense_web/components/tool-ui/video-presentation/generate-video-presentation.tsx
+++ b/surfsense_web/components/tool-ui/video-presentation/generate-video-presentation.tsx
@@ -1,9 +1,9 @@
"use client";
-import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
import type { ToolCallMessagePartProps } from "@assistant-ui/react";
import { Dot, Download, Loader2, Presentation, X } from "lucide-react";
import { useParams, usePathname } from "next/navigation";
+import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { toast } from "sonner";
import { z } from "zod";
import { TextShimmerLoader } from "@/components/prompt-kit/loader";
@@ -13,12 +13,12 @@ import { authenticatedFetch } from "@/lib/auth-utils";
import { compileCheck, compileToComponent } from "@/lib/remotion/compile-check";
import { FPS } from "@/lib/remotion/constants";
import {
- CombinedPlayer,
buildCompositionComponent,
buildSlideWithWatermark,
+ CombinedPlayer,
type CompiledSlide,
} from "./combined-player";
-import { getVideoDownloadErrorToast, getPptxExportErrorToast } from "./errors";
+import { getPptxExportErrorToast, getVideoDownloadErrorToast } from "./errors";
const GenerateVideoPresentationArgsSchema = z.object({
source_content: z.string(),
@@ -50,7 +50,7 @@ const VideoPresentationStatusResponseSchema = z.object({
audio_url: z.string().nullish(),
duration_seconds: z.number().nullish(),
duration_in_frames: z.number().nullish(),
- }),
+ })
)
.nullish(),
scene_codes: z
@@ -59,7 +59,7 @@ const VideoPresentationStatusResponseSchema = z.object({
slide_number: z.number(),
code: z.string(),
title: z.string().nullish(),
- }),
+ })
)
.nullish(),
slide_count: z.number().nullish(),
@@ -69,7 +69,6 @@ type GenerateVideoPresentationArgs = z.infer;
type VideoPresentationStatusResponse = z.infer;
-
function parseStatusResponse(data: unknown): VideoPresentationStatusResponse | null {
const result = VideoPresentationStatusResponseSchema.safeParse(data);
if (!result.success) {
@@ -166,9 +165,7 @@ function VideoPresentationPlayer({
const durationInFrames = slide.duration_in_frames ?? 300;
const check = compileCheck(scene.code);
if (!check.success) {
- console.warn(
- `Slide ${slide.slide_number} failed to compile: ${check.error}`,
- );
+ console.warn(`Slide ${slide.slide_number} failed to compile: ${check.error}`);
continue;
}
@@ -179,9 +176,7 @@ function VideoPresentationPlayer({
title: scene.title ?? slide.title,
code: scene.code,
durationInFrames,
- audioUrl: slide.audio_url
- ? `${backendUrl}${slide.audio_url}`
- : undefined,
+ audioUrl: slide.audio_url ? `${backendUrl}${slide.audio_url}` : undefined,
});
}
@@ -198,17 +193,13 @@ function VideoPresentationPlayer({
try {
let blob: Blob;
if (shareToken) {
- blob = await baseApiService.getBlob(
- new URL(slide.audioUrl).pathname,
- );
+ blob = await baseApiService.getBlob(new URL(slide.audioUrl).pathname);
} else {
const resp = await authenticatedFetch(slide.audioUrl, {
method: "GET",
});
if (!resp.ok) {
- console.warn(
- `Audio fetch ${resp.status} for slide "${slide.title}"`,
- );
+ console.warn(`Audio fetch ${resp.status} for slide "${slide.title}"`);
return { ...slide, audioUrl: undefined };
}
blob = await resp.blob();
@@ -220,7 +211,7 @@ function VideoPresentationPlayer({
console.warn(`Failed to fetch audio for "${slide.title}":`, err);
return { ...slide, audioUrl: undefined };
}
- }),
+ })
);
setCompiledSlides(withBlobs);
@@ -244,7 +235,7 @@ function VideoPresentationPlayer({
const totalDuration = useMemo(
() => compiledSlides.reduce((sum, s) => sum + s.durationInFrames / FPS, 0),
- [compiledSlides],
+ [compiledSlides]
);
const handleDownload = async () => {
@@ -258,9 +249,7 @@ function VideoPresentationPlayer({
abortControllerRef.current = controller;
try {
- const { canRenderMediaOnWeb, renderMediaOnWeb } = await import(
- "@remotion/web-renderer"
- );
+ const { canRenderMediaOnWeb, renderMediaOnWeb } = await import("@remotion/web-renderer");
const formats = [
{ container: "mp4" as const, videoCodec: "h264" as const, ext: "mp4" },
@@ -285,7 +274,7 @@ function VideoPresentationPlayer({
if (!chosen) {
throw new Error(
- "Your browser does not support video rendering (WebCodecs). Please use Chrome, Edge, or Firefox 130+.",
+ "Your browser does not support video rendering (WebCodecs). Please use Chrome, Edge, or Firefox 130+."
);
}
@@ -379,7 +368,7 @@ function VideoPresentationPlayer({
durationInFrames: slide.durationInFrames,
fps: FPS,
style: { width: 1920, height: 1080 },
- }),
+ })
);
});
@@ -419,7 +408,8 @@ function VideoPresentationPlayer({
{title}
- {compiledSlides.length} slides {totalDuration.toFixed(1)}s {FPS}fps
+ {compiledSlides.length} slides {totalDuration.toFixed(1)}s{" "}
+ {FPS}fps
@@ -440,9 +430,7 @@ function VideoPresentationPlayer({
Rendering {renderFormat ?? ""}{" "}
- {renderProgress !== null
- ? `${Math.round(renderProgress * 100)}%`
- : "..."}
+ {renderProgress !== null ? `${Math.round(renderProgress * 100)}%` : "..."}
-
);
}
@@ -564,85 +551,85 @@ function StatusPoller({
return
;
}
-export const GenerateVideoPresentationToolUI = ({ args, result, status }: ToolCallMessagePartProps<
- GenerateVideoPresentationArgs,
- GenerateVideoPresentationResult
->) => {
- const params = useParams();
- const pathname = usePathname();
- const isPublicRoute = pathname?.startsWith("/public/");
- const shareToken =
- isPublicRoute && typeof params?.token === "string" ? params.token : null;
+export const GenerateVideoPresentationToolUI = ({
+ args,
+ result,
+ status,
+}: ToolCallMessagePartProps
) => {
+ const params = useParams();
+ const pathname = usePathname();
+ const isPublicRoute = pathname?.startsWith("/public/");
+ const shareToken = isPublicRoute && typeof params?.token === "string" ? params.token : null;
- const title = args.video_title || "SurfSense Presentation";
+ const title = args.video_title || "SurfSense Presentation";
- if (status.type === "running" || status.type === "requires-action") {
- return ;
- }
+ if (status.type === "running" || status.type === "requires-action") {
+ return ;
+ }
- if (status.type === "incomplete") {
- if (status.reason === "cancelled") {
- return (
-
-
-
Presentation Cancelled
-
- Presentation generation was cancelled
-
-
-
- );
- }
- if (status.reason === "error") {
- return (
-
- );
- }
- }
-
- if (!result) {
- return ;
- }
-
- if (result.status === "failed") {
- return ;
- }
-
- if (result.status === "generating") {
+ if (status.type === "incomplete") {
+ if (status.reason === "cancelled") {
return (
-
Presentation already in progress
+
Presentation Cancelled
- Please wait for the current presentation to complete.
+ Presentation generation was cancelled
);
}
-
- if (result.status === "pending" && result.video_presentation_id) {
+ if (status.reason === "error") {
return (
-
);
}
+ }
- if (result.status === "ready" && result.video_presentation_id) {
- return (
-
- );
- }
+ if (!result) {
+ return ;
+ }
- return ;
+ if (result.status === "failed") {
+ return ;
+ }
+
+ if (result.status === "generating") {
+ return (
+
+
+
Presentation already in progress
+
+ Please wait for the current presentation to complete.
+
+
+
+ );
+ }
+
+ if (result.status === "pending" && result.video_presentation_id) {
+ return (
+
+ );
+ }
+
+ if (result.status === "ready" && result.video_presentation_id) {
+ return (
+
+ );
+ }
+
+ return ;
};
diff --git a/surfsense_web/components/ui/hero-carousel.tsx b/surfsense_web/components/ui/hero-carousel.tsx
index 86e61fb7d..e051ac518 100644
--- a/surfsense_web/components/ui/hero-carousel.tsx
+++ b/surfsense_web/components/ui/hero-carousel.tsx
@@ -19,8 +19,7 @@ const carouselItems = [
},
{
title: "Video Generation",
- description:
- "Create short videos with AI-generated visuals and narration from your sources.",
+ description: "Create short videos with AI-generated visuals and narration from your sources.",
src: "/homepage/hero_tutorial/video_gen_surf.mp4",
},
{
diff --git a/surfsense_web/hooks/use-connectors-electric.ts b/surfsense_web/hooks/use-connectors-electric.ts
index 883186c3c..f420650f5 100644
--- a/surfsense_web/hooks/use-connectors-electric.ts
+++ b/surfsense_web/hooks/use-connectors-electric.ts
@@ -74,7 +74,8 @@ export function useConnectorsElectric(searchSpaceId: number | string | null) {
async function startSync() {
try {
- if (IS_DEV) console.log("[useConnectorsElectric] Starting sync for search space:", searchSpaceId);
+ if (IS_DEV)
+ console.log("[useConnectorsElectric] Starting sync for search space:", searchSpaceId);
const handle = await electricClient.syncShape({
table: "search_source_connectors",
@@ -82,9 +83,10 @@ export function useConnectorsElectric(searchSpaceId: number | string | null) {
primaryKey: ["id"],
});
- if (IS_DEV) console.log("[useConnectorsElectric] Sync started:", {
- isUpToDate: handle.isUpToDate,
- });
+ if (IS_DEV)
+ console.log("[useConnectorsElectric] Sync started:", {
+ isUpToDate: handle.isUpToDate,
+ });
// Wait for initial sync with timeout
if (!handle.isUpToDate && handle.initialSyncPromise) {
diff --git a/surfsense_web/lib/remotion/compile-check.ts b/surfsense_web/lib/remotion/compile-check.ts
index 192d6f48e..de04c153d 100644
--- a/surfsense_web/lib/remotion/compile-check.ts
+++ b/surfsense_web/lib/remotion/compile-check.ts
@@ -2,12 +2,12 @@ import * as Babel from "@babel/standalone";
import React from "react";
import {
AbsoluteFill,
- useCurrentFrame,
- useVideoConfig,
- spring,
+ Easing,
interpolate,
Sequence,
- Easing,
+ spring,
+ useCurrentFrame,
+ useVideoConfig,
} from "remotion";
import { DURATION_IN_FRAMES } from "./constants";
@@ -21,7 +21,7 @@ function createStagger(totalFrames: number) {
frame: number,
fps: number,
index: number,
- total: number,
+ total: number
): { opacity: number; transform: string } {
const enterPhase = Math.floor(totalFrames * 0.2);
const exitStart = Math.floor(totalFrames * 0.8);
@@ -43,9 +43,7 @@ function createStagger(totalFrames: number) {
const opacity = s * (1 - exit);
const translateY =
- interpolate(s, [0, 1], [40, 0]) +
- interpolate(exit, [0, 1], [0, -30]) +
- ambient;
+ interpolate(s, [0, 1], [40, 0]) + interpolate(exit, [0, 1], [0, -30]) + ambient;
const scale = interpolate(s, [0, 1], [0.97, 1]);
return {
@@ -97,7 +95,7 @@ export function prepareSource(code: string): string {
const codeWithoutImports = code.replace(/^import\s+.*$/gm, "").trim();
const match = codeWithoutImports.match(
- /export\s+(?:const|function)\s+(\w+)\s*(?::\s*React\.FC\s*)?=?\s*\(\s*\)\s*=>\s*\{([\s\S]*)\};?\s*$/,
+ /export\s+(?:const|function)\s+(\w+)\s*(?::\s*React\.FC\s*)?=?\s*\(\s*\)\s*=>\s*\{([\s\S]*)\};?\s*$/
);
if (match) {
@@ -137,18 +135,10 @@ export function compileCheck(code: string): CompileResult {
}
}
-export function compileToComponent(
- code: string,
- durationInFrames?: number,
-): React.ComponentType {
- const staggerFn = durationInFrames
- ? createStagger(durationInFrames)
- : defaultStagger;
+export function compileToComponent(code: string, durationInFrames?: number): React.ComponentType {
+ const staggerFn = durationInFrames ? createStagger(durationInFrames) : defaultStagger;
const jsCode = transpile(code);
- const factory = new Function(
- ...INJECTED_NAMES,
- `${jsCode}\nreturn DynamicComponent;`,
- );
+ const factory = new Function(...INJECTED_NAMES, `${jsCode}\nreturn DynamicComponent;`);
return factory(...buildInjectedValues(staggerFn)) as React.ComponentType;
}
diff --git a/surfsense_web/lib/remotion/dom-to-pptx.d.ts b/surfsense_web/lib/remotion/dom-to-pptx.d.ts
index e832eb495..b451c7a33 100644
--- a/surfsense_web/lib/remotion/dom-to-pptx.d.ts
+++ b/surfsense_web/lib/remotion/dom-to-pptx.d.ts
@@ -13,6 +13,6 @@ declare module "dom-to-pptx" {
export function exportToPptx(
elementOrSelector: string | HTMLElement | Array,
- options?: ExportOptions,
+ options?: ExportOptions
): Promise;
}