mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-04-26 01:06:23 +02:00
refactor: enhance link preview functionality with Chromium fallback
- Added a fallback mechanism using headless Chromium to fetch page content when standard HTTP requests fail. - Introduced utility functions for unescaping HTML entities and converting relative URLs to absolute. - Updated HTTP request headers to mimic a browser for better compatibility with web servers. - Improved error handling and logging for better debugging and user feedback. - Made various properties in Zod schemas nullable for better type safety and flexibility in handling optional data.
This commit is contained in:
parent
4c2de73694
commit
bea18960a4
7 changed files with 271 additions and 86 deletions
|
|
@ -19,20 +19,20 @@ import { cn } from "@/lib/utils";
|
|||
*/
|
||||
const SerializableArticleSchema = z.object({
|
||||
id: z.string().default("article-unknown"),
|
||||
assetId: z.string().optional(),
|
||||
kind: z.literal("article").optional(),
|
||||
assetId: z.string().nullish(),
|
||||
kind: z.literal("article").nullish(),
|
||||
title: z.string().default("Untitled Article"),
|
||||
description: z.string().optional(),
|
||||
content: z.string().optional(),
|
||||
href: z.string().url().optional(),
|
||||
domain: z.string().optional(),
|
||||
author: z.string().optional(),
|
||||
date: z.string().optional(),
|
||||
word_count: z.number().optional(),
|
||||
wordCount: z.number().optional(),
|
||||
was_truncated: z.boolean().optional(),
|
||||
wasTruncated: z.boolean().optional(),
|
||||
error: z.string().optional(),
|
||||
description: z.string().nullish(),
|
||||
content: z.string().nullish(),
|
||||
href: z.string().url().nullish(),
|
||||
domain: z.string().nullish(),
|
||||
author: z.string().nullish(),
|
||||
date: z.string().nullish(),
|
||||
word_count: z.number().nullish(),
|
||||
wordCount: z.number().nullish(),
|
||||
was_truncated: z.boolean().nullish(),
|
||||
wasTruncated: z.boolean().nullish(),
|
||||
error: z.string().nullish(),
|
||||
});
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -70,12 +70,12 @@ const ThinkingStepSchema = z.object({
|
|||
});
|
||||
|
||||
const DeepAgentThinkingArgsSchema = z.object({
|
||||
query: z.string().optional(),
|
||||
context: z.string().optional(),
|
||||
query: z.string().nullish(),
|
||||
context: z.string().nullish(),
|
||||
});
|
||||
|
||||
const DeepAgentThinkingResultSchema = z.object({
|
||||
steps: z.array(ThinkingStepSchema).optional(),
|
||||
steps: z.array(ThinkingStepSchema).nullish(),
|
||||
status: z
|
||||
.enum([
|
||||
THINKING_STATUS.THINKING,
|
||||
|
|
@ -83,8 +83,8 @@ const DeepAgentThinkingResultSchema = z.object({
|
|||
THINKING_STATUS.SYNTHESIZING,
|
||||
THINKING_STATUS.COMPLETED,
|
||||
])
|
||||
.optional(),
|
||||
summary: z.string().optional(),
|
||||
.nullish(),
|
||||
summary: z.string().nullish(),
|
||||
});
|
||||
|
||||
/** Types derived from Zod schemas */
|
||||
|
|
@ -325,7 +325,7 @@ export const DeepAgentThinkingToolUI = makeAssistantToolUI<
|
|||
render: function DeepAgentThinkingUI({ result, status }) {
|
||||
// Loading state - tool is still running
|
||||
if (status.type === "running" || status.type === "requires-action") {
|
||||
return <ThinkingLoadingState status={result?.status} />;
|
||||
return <ThinkingLoadingState status={result?.status ?? undefined} />;
|
||||
}
|
||||
|
||||
// Incomplete/cancelled state
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ interface DisplayImageResult {
|
|||
id: string;
|
||||
assetId: string;
|
||||
src: string;
|
||||
alt: string;
|
||||
alt?: string; // Made optional - parseSerializableImage provides fallback
|
||||
title?: string;
|
||||
description?: string;
|
||||
domain?: string;
|
||||
|
|
|
|||
|
|
@ -14,27 +14,27 @@ import { clearActivePodcastTaskId, setActivePodcastTaskId } from "@/lib/chat/pod
|
|||
*/
|
||||
const GeneratePodcastArgsSchema = z.object({
|
||||
source_content: z.string(),
|
||||
podcast_title: z.string().optional(),
|
||||
user_prompt: z.string().optional(),
|
||||
podcast_title: z.string().nullish(),
|
||||
user_prompt: z.string().nullish(),
|
||||
});
|
||||
|
||||
const GeneratePodcastResultSchema = z.object({
|
||||
status: z.enum(["processing", "already_generating", "success", "error"]),
|
||||
task_id: z.string().optional(),
|
||||
podcast_id: z.number().optional(),
|
||||
title: z.string().optional(),
|
||||
transcript_entries: z.number().optional(),
|
||||
message: z.string().optional(),
|
||||
error: z.string().optional(),
|
||||
task_id: z.string().nullish(),
|
||||
podcast_id: z.number().nullish(),
|
||||
title: z.string().nullish(),
|
||||
transcript_entries: z.number().nullish(),
|
||||
message: z.string().nullish(),
|
||||
error: z.string().nullish(),
|
||||
});
|
||||
|
||||
const TaskStatusResponseSchema = z.object({
|
||||
status: z.enum(["processing", "success", "error"]),
|
||||
podcast_id: z.number().optional(),
|
||||
title: z.string().optional(),
|
||||
transcript_entries: z.number().optional(),
|
||||
state: z.string().optional(),
|
||||
error: z.string().optional(),
|
||||
podcast_id: z.number().nullish(),
|
||||
title: z.string().nullish(),
|
||||
transcript_entries: z.number().nullish(),
|
||||
state: z.string().nullish(),
|
||||
error: z.string().nullish(),
|
||||
});
|
||||
|
||||
const PodcastTranscriptEntrySchema = z.object({
|
||||
|
|
@ -43,7 +43,7 @@ const PodcastTranscriptEntrySchema = z.object({
|
|||
});
|
||||
|
||||
const PodcastDetailsSchema = z.object({
|
||||
podcast_transcript: z.array(PodcastTranscriptEntrySchema).optional(),
|
||||
podcast_transcript: z.array(PodcastTranscriptEntrySchema).nullish(),
|
||||
});
|
||||
|
||||
/**
|
||||
|
|
@ -75,7 +75,9 @@ function parsePodcastDetails(data: unknown): { podcast_transcript?: PodcastTrans
|
|||
console.warn("Invalid podcast details:", result.error.issues);
|
||||
return {};
|
||||
}
|
||||
return result.data;
|
||||
return {
|
||||
podcast_transcript: result.data.podcast_transcript ?? undefined,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -11,26 +11,26 @@ import { cn } from "@/lib/utils";
|
|||
/**
|
||||
* Zod schemas for runtime validation
|
||||
*/
|
||||
const AspectRatioSchema = z.enum(["1:1", "4:3", "16:9", "9:16", "auto"]);
|
||||
const AspectRatioSchema = z.enum(["1:1", "4:3", "16:9", "9:16", "21:9", "auto"]);
|
||||
const ImageFitSchema = z.enum(["cover", "contain"]);
|
||||
|
||||
const ImageSourceSchema = z.object({
|
||||
label: z.string(),
|
||||
iconUrl: z.string().optional(),
|
||||
url: z.string().optional(),
|
||||
iconUrl: z.string().nullish(),
|
||||
url: z.string().nullish(),
|
||||
});
|
||||
|
||||
const SerializableImageSchema = z.object({
|
||||
id: z.string(),
|
||||
assetId: z.string(),
|
||||
src: z.string(),
|
||||
alt: z.string(),
|
||||
title: z.string().optional(),
|
||||
description: z.string().optional(),
|
||||
href: z.string().optional(),
|
||||
domain: z.string().optional(),
|
||||
ratio: AspectRatioSchema.optional(),
|
||||
source: ImageSourceSchema.optional(),
|
||||
alt: z.string().nullish(), // Made optional - will use fallback if missing
|
||||
title: z.string().nullish(),
|
||||
description: z.string().nullish(),
|
||||
href: z.string().nullish(),
|
||||
domain: z.string().nullish(),
|
||||
ratio: AspectRatioSchema.nullish(),
|
||||
source: ImageSourceSchema.nullish(),
|
||||
});
|
||||
|
||||
/**
|
||||
|
|
@ -48,7 +48,7 @@ export interface ImageProps {
|
|||
id: string;
|
||||
assetId: string;
|
||||
src: string;
|
||||
alt: string;
|
||||
alt?: string; // Optional with default fallback
|
||||
title?: string;
|
||||
description?: string;
|
||||
href?: string;
|
||||
|
|
@ -62,18 +62,45 @@ export interface ImageProps {
|
|||
|
||||
/**
|
||||
* Parse and validate serializable image from tool result
|
||||
* Returns a valid SerializableImage with fallback values for missing optional fields
|
||||
*/
|
||||
export function parseSerializableImage(result: unknown): SerializableImage {
|
||||
export function parseSerializableImage(result: unknown): SerializableImage & { alt: string } {
|
||||
const parsed = SerializableImageSchema.safeParse(result);
|
||||
|
||||
if (!parsed.success) {
|
||||
console.warn("Invalid image data:", parsed.error.issues);
|
||||
// Try to extract basic info for error display
|
||||
|
||||
// Try to extract basic info and return a fallback object
|
||||
const obj = (result && typeof result === "object" ? result : {}) as Record<string, unknown>;
|
||||
|
||||
// If we have at least id, assetId, and src, we can still render the image
|
||||
if (
|
||||
typeof obj.id === "string" &&
|
||||
typeof obj.assetId === "string" &&
|
||||
typeof obj.src === "string"
|
||||
) {
|
||||
return {
|
||||
id: obj.id,
|
||||
assetId: obj.assetId,
|
||||
src: obj.src,
|
||||
alt: typeof obj.alt === "string" ? obj.alt : "Image",
|
||||
title: typeof obj.title === "string" ? obj.title : undefined,
|
||||
description: typeof obj.description === "string" ? obj.description : undefined,
|
||||
href: typeof obj.href === "string" ? obj.href : undefined,
|
||||
domain: typeof obj.domain === "string" ? obj.domain : undefined,
|
||||
ratio: undefined, // Use default ratio
|
||||
source: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
throw new Error(`Invalid image: ${parsed.error.issues.map((i) => i.message).join(", ")}`);
|
||||
}
|
||||
|
||||
return parsed.data;
|
||||
// Provide fallback for alt if it's null/undefined
|
||||
return {
|
||||
...parsed.data,
|
||||
alt: parsed.data.alt ?? "Image",
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -89,6 +116,8 @@ function getAspectRatioClass(ratio?: AspectRatio): string {
|
|||
return "aspect-video";
|
||||
case "9:16":
|
||||
return "aspect-[9/16]";
|
||||
case "21:9":
|
||||
return "aspect-[21/9]";
|
||||
case "auto":
|
||||
default:
|
||||
return "aspect-[4/3]";
|
||||
|
|
@ -172,7 +201,7 @@ export function ImageLoading({ title = "Loading image..." }: { title?: string })
|
|||
export function Image({
|
||||
id,
|
||||
src,
|
||||
alt,
|
||||
alt = "Image",
|
||||
title,
|
||||
description,
|
||||
href,
|
||||
|
|
|
|||
|
|
@ -13,27 +13,27 @@ import { cn } from "@/lib/utils";
|
|||
/**
|
||||
* Zod schemas for runtime validation
|
||||
*/
|
||||
const AspectRatioSchema = z.enum(["1:1", "4:3", "16:9", "21:9", "auto"]);
|
||||
const AspectRatioSchema = z.enum(["1:1", "4:3", "16:9", "9:16", "21:9", "auto"]);
|
||||
const MediaCardKindSchema = z.enum(["link", "image", "video", "audio"]);
|
||||
|
||||
const ResponseActionSchema = z.object({
|
||||
id: z.string(),
|
||||
label: z.string(),
|
||||
variant: z.enum(["default", "secondary", "outline", "destructive", "ghost"]).optional(),
|
||||
confirmLabel: z.string().optional(),
|
||||
variant: z.enum(["default", "secondary", "outline", "destructive", "ghost"]).nullish(),
|
||||
confirmLabel: z.string().nullish(),
|
||||
});
|
||||
|
||||
const SerializableMediaCardSchema = z.object({
|
||||
id: z.string(),
|
||||
assetId: z.string(),
|
||||
kind: MediaCardKindSchema,
|
||||
href: z.string().optional(),
|
||||
src: z.string().optional(),
|
||||
href: z.string().nullish(),
|
||||
src: z.string().nullish(),
|
||||
title: z.string(),
|
||||
description: z.string().optional(),
|
||||
thumb: z.string().optional(),
|
||||
ratio: AspectRatioSchema.optional(),
|
||||
domain: z.string().optional(),
|
||||
description: z.string().nullish(),
|
||||
thumb: z.string().nullish(),
|
||||
ratio: AspectRatioSchema.nullish(),
|
||||
domain: z.string().nullish(),
|
||||
});
|
||||
|
||||
/**
|
||||
|
|
@ -90,6 +90,8 @@ function getAspectRatioClass(ratio?: AspectRatio): string {
|
|||
return "aspect-[4/3]";
|
||||
case "16:9":
|
||||
return "aspect-video";
|
||||
case "9:16":
|
||||
return "aspect-[9/16]";
|
||||
case "21:9":
|
||||
return "aspect-[21/9]";
|
||||
case "auto":
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue