mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-04-30 11:26:24 +02:00
feat: enhance web search tool integration with citation management and UI enhancements
This commit is contained in:
parent
9eab427b56
commit
74826b3714
13 changed files with 133 additions and 209 deletions
|
|
@ -1,19 +0,0 @@
|
|||
import { z } from "zod";
|
||||
import { parseWithSchema, safeParseWithSchema } from "./parse";
|
||||
|
||||
export interface ToolUiContract<T> {
|
||||
schema: z.ZodType<T>;
|
||||
parse: (input: unknown) => T;
|
||||
safeParse: (input: unknown) => T | null;
|
||||
}
|
||||
|
||||
export function defineToolUiContract<T>(
|
||||
componentName: string,
|
||||
schema: z.ZodType<T>,
|
||||
): ToolUiContract<T> {
|
||||
return {
|
||||
schema,
|
||||
parse: (input: unknown) => parseWithSchema(schema, input, componentName),
|
||||
safeParse: (input: unknown) => safeParseWithSchema(schema, input),
|
||||
};
|
||||
}
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
import { z } from "zod";
|
||||
|
||||
export const AspectRatioSchema = z
|
||||
.enum(["auto", "1:1", "4:3", "16:9", "9:16"])
|
||||
.default("auto");
|
||||
|
||||
export type AspectRatio = z.infer<typeof AspectRatioSchema>;
|
||||
|
||||
export const MediaFitSchema = z.enum(["cover", "contain"]).default("cover");
|
||||
|
||||
export type MediaFit = z.infer<typeof MediaFitSchema>;
|
||||
|
||||
export const RATIO_CLASS_MAP: Record<AspectRatio, string> = {
|
||||
auto: "",
|
||||
"1:1": "aspect-square",
|
||||
"4:3": "aspect-[4/3]",
|
||||
"16:9": "aspect-video",
|
||||
"9:16": "aspect-[9/16]",
|
||||
};
|
||||
|
||||
export function getRatioClass(ratio: AspectRatio): string {
|
||||
return RATIO_CLASS_MAP[ratio];
|
||||
}
|
||||
|
||||
export function getFitClass(fit: MediaFit): string {
|
||||
return fit === "cover" ? "object-cover" : "object-contain";
|
||||
}
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
export function formatDuration(durationMs: number): string {
|
||||
const totalSeconds = Math.round(durationMs / 1000);
|
||||
const hours = Math.floor(totalSeconds / 3600);
|
||||
const minutes = Math.floor((totalSeconds % 3600) / 60);
|
||||
const seconds = totalSeconds % 60;
|
||||
|
||||
if (hours > 0) {
|
||||
return `${hours}:${minutes.toString().padStart(2, "0")}:${seconds
|
||||
.toString()
|
||||
.padStart(2, "0")}`;
|
||||
}
|
||||
return `${minutes}:${seconds.toString().padStart(2, "0")}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format file size in bytes to human-readable string.
|
||||
* @example formatFileSize(1024) => "1 KB"
|
||||
* @example formatFileSize(1536000) => "1.5 MB"
|
||||
*/
|
||||
export function formatFileSize(bytes: number): string {
|
||||
if (bytes < 1024) return `${bytes} B`;
|
||||
const units = ["KB", "MB", "GB"];
|
||||
let size = bytes / 1024;
|
||||
let unit = 0;
|
||||
while (size >= 1024 && unit < units.length - 1) {
|
||||
size /= 1024;
|
||||
unit += 1;
|
||||
}
|
||||
return `${size.toFixed(size >= 10 ? 0 : 1)} ${units[unit]}`;
|
||||
}
|
||||
|
|
@ -1,17 +1,3 @@
|
|||
export {
|
||||
AspectRatioSchema,
|
||||
MediaFitSchema,
|
||||
RATIO_CLASS_MAP,
|
||||
getRatioClass,
|
||||
getFitClass,
|
||||
type AspectRatio,
|
||||
type MediaFit,
|
||||
} from "./aspect-ratio";
|
||||
|
||||
export { OVERLAY_GRADIENT } from "./overlay-gradient";
|
||||
|
||||
export { formatDuration, formatFileSize } from "./format-utils";
|
||||
|
||||
export { sanitizeHref } from "./sanitize-href";
|
||||
export {
|
||||
resolveSafeNavigationHref,
|
||||
|
|
|
|||
|
|
@ -1,19 +0,0 @@
|
|||
export const OVERLAY_GRADIENT = `linear-gradient(
|
||||
to bottom,
|
||||
hsl(0, 0%, 0%) 0%,
|
||||
hsla(0, 0%, 0%, 0.987) 8.3%,
|
||||
hsla(0, 0%, 0%, 0.951) 16.6%,
|
||||
hsla(0, 0%, 0%, 0.896) 24.6%,
|
||||
hsla(0, 0%, 0%, 0.825) 32.5%,
|
||||
hsla(0, 0%, 0%, 0.741) 40.1%,
|
||||
hsla(0, 0%, 0%, 0.648) 47.6%,
|
||||
hsla(0, 0%, 0%, 0.55) 54.8%,
|
||||
hsla(0, 0%, 0%, 0.45) 61.7%,
|
||||
hsla(0, 0%, 0%, 0.352) 68.3%,
|
||||
hsla(0, 0%, 0%, 0.259) 74.5%,
|
||||
hsla(0, 0%, 0%, 0.175) 80.4%,
|
||||
hsla(0, 0%, 0%, 0.104) 86%,
|
||||
hsla(0, 0%, 0%, 0.049) 91.1%,
|
||||
hsla(0, 0%, 0%, 0.013) 95.8%,
|
||||
hsla(0, 0%, 0%, 0) 100%
|
||||
)` as const;
|
||||
|
|
@ -1,51 +0,0 @@
|
|||
import { z } from "zod";
|
||||
|
||||
function formatZodPath(path: Array<string | number | symbol>): string {
|
||||
if (path.length === 0) return "root";
|
||||
return path
|
||||
.map((segment) =>
|
||||
typeof segment === "number" ? `[${segment}]` : String(segment),
|
||||
)
|
||||
.join(".");
|
||||
}
|
||||
|
||||
/**
|
||||
* Format Zod errors into a compact `path: message` string.
|
||||
*/
|
||||
export function formatZodError(error: z.ZodError): string {
|
||||
const parts = error.issues.map((issue) => {
|
||||
const path = formatZodPath(issue.path);
|
||||
return `${path}: ${issue.message}`;
|
||||
});
|
||||
|
||||
return Array.from(new Set(parts)).join("; ");
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse unknown input and throw a readable error.
|
||||
*/
|
||||
export function parseWithSchema<T>(
|
||||
schema: z.ZodType<T>,
|
||||
input: unknown,
|
||||
name: string,
|
||||
): T {
|
||||
const res = schema.safeParse(input);
|
||||
if (!res.success) {
|
||||
throw new Error(`Invalid ${name} payload: ${formatZodError(res.error)}`);
|
||||
}
|
||||
return res.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse unknown input, returning `null` instead of throwing on failure.
|
||||
*
|
||||
* Use this in assistant-ui `render` functions where `args` stream in
|
||||
* incrementally and may be incomplete until the tool call finishes.
|
||||
*/
|
||||
export function safeParseWithSchema<T>(
|
||||
schema: z.ZodType<T>,
|
||||
input: unknown,
|
||||
): T | null {
|
||||
const res = schema.safeParse(input);
|
||||
return res.success ? res.data : null;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue