fuck it we do it live
This commit is contained in:
parent
7f9bdee7f4
commit
be2968aabc
@ -1,37 +0,0 @@
|
||||
import { forgotPasswordAction } from "@/app/actions";
|
||||
import { FormMessage, Message } from "@/components/form-message";
|
||||
import { SubmitButton } from "@/components/submit-button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import Link from "next/link";
|
||||
import { SmtpMessage } from "../smtp-message";
|
||||
|
||||
export default async function ForgotPassword(props: {
|
||||
searchParams: Promise<Message>;
|
||||
}) {
|
||||
const searchParams = await props.searchParams;
|
||||
return (
|
||||
<>
|
||||
<form className="flex-1 flex flex-col w-full gap-2 text-foreground [&>input]:mb-6 min-w-64 max-w-64 mx-auto">
|
||||
<div>
|
||||
<h1 className="text-2xl font-medium">Reset Password</h1>
|
||||
<p className="text-sm text-secondary-foreground">
|
||||
Already have an account?{" "}
|
||||
<Link className="text-primary underline" href="/sign-in">
|
||||
Sign in
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2 [&>input]:mb-3 mt-8">
|
||||
<Label htmlFor="email">Email</Label>
|
||||
<Input name="email" placeholder="you@example.com" required />
|
||||
<SubmitButton formAction={forgotPasswordAction}>
|
||||
Reset Password
|
||||
</SubmitButton>
|
||||
<FormMessage message={searchParams} />
|
||||
</div>
|
||||
</form>
|
||||
<SmtpMessage />
|
||||
</>
|
||||
);
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
export default async function Layout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<div className="max-w-7xl flex flex-col gap-12 items-start">{children}</div>
|
||||
);
|
||||
}
|
@ -1,44 +0,0 @@
|
||||
import { signInAction } from "@/app/actions";
|
||||
import { FormMessage, Message } from "@/components/form-message";
|
||||
import { SubmitButton } from "@/components/submit-button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import Link from "next/link";
|
||||
|
||||
export default async function Login(props: { searchParams: Promise<Message> }) {
|
||||
const searchParams = await props.searchParams;
|
||||
return (
|
||||
<form className="flex-1 flex flex-col min-w-64">
|
||||
<h1 className="text-2xl font-medium">Sign in</h1>
|
||||
<p className="text-sm text-foreground">
|
||||
Don't have an account?{" "}
|
||||
<Link className="text-foreground font-medium underline" href="/sign-up">
|
||||
Sign up
|
||||
</Link>
|
||||
</p>
|
||||
<div className="flex flex-col gap-2 [&>input]:mb-3 mt-8">
|
||||
<Label htmlFor="email">Email</Label>
|
||||
<Input name="email" placeholder="you@example.com" required />
|
||||
<div className="flex justify-between items-center">
|
||||
<Label htmlFor="password">Password</Label>
|
||||
<Link
|
||||
className="text-xs text-foreground underline"
|
||||
href="/forgot-password"
|
||||
>
|
||||
Forgot Password?
|
||||
</Link>
|
||||
</div>
|
||||
<Input
|
||||
type="password"
|
||||
name="password"
|
||||
placeholder="Your password"
|
||||
required
|
||||
/>
|
||||
<SubmitButton pendingText="Signing In..." formAction={signInAction}>
|
||||
Sign in
|
||||
</SubmitButton>
|
||||
<FormMessage message={searchParams} />
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
}
|
@ -1,51 +0,0 @@
|
||||
import { signUpAction } from "@/app/actions";
|
||||
import { FormMessage, Message } from "@/components/form-message";
|
||||
import { SubmitButton } from "@/components/submit-button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import Link from "next/link";
|
||||
import { SmtpMessage } from "../smtp-message";
|
||||
|
||||
export default async function Signup(props: {
|
||||
searchParams: Promise<Message>;
|
||||
}) {
|
||||
const searchParams = await props.searchParams;
|
||||
if ("message" in searchParams) {
|
||||
return (
|
||||
<div className="w-full flex-1 flex items-center h-screen sm:max-w-md justify-center gap-2 p-4">
|
||||
<FormMessage message={searchParams} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<form className="flex flex-col min-w-64 max-w-64 mx-auto">
|
||||
<h1 className="text-2xl font-medium">Sign up</h1>
|
||||
<p className="text-sm text text-foreground">
|
||||
Already have an account?{" "}
|
||||
<Link className="text-primary font-medium underline" href="/sign-in">
|
||||
Sign in
|
||||
</Link>
|
||||
</p>
|
||||
<div className="flex flex-col gap-2 [&>input]:mb-3 mt-8">
|
||||
<Label htmlFor="email">Email</Label>
|
||||
<Input name="email" placeholder="you@example.com" required />
|
||||
<Label htmlFor="password">Password</Label>
|
||||
<Input
|
||||
type="password"
|
||||
name="password"
|
||||
placeholder="Your password"
|
||||
minLength={6}
|
||||
required
|
||||
/>
|
||||
<SubmitButton formAction={signUpAction} pendingText="Signing up...">
|
||||
Sign up
|
||||
</SubmitButton>
|
||||
<FormMessage message={searchParams} />
|
||||
</div>
|
||||
</form>
|
||||
<SmtpMessage />
|
||||
</>
|
||||
);
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
import { ArrowUpRight, InfoIcon } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
|
||||
export function SmtpMessage() {
|
||||
return (
|
||||
<div className="bg-muted/50 px-5 py-3 border rounded-md flex gap-4">
|
||||
<InfoIcon size={16} className="mt-0.5" />
|
||||
<div className="flex flex-col gap-1">
|
||||
<small className="text-sm text-secondary-foreground">
|
||||
<strong> Note:</strong> Emails are rate limited. Enable Custom SMTP to
|
||||
increase the rate limit.
|
||||
</small>
|
||||
<div>
|
||||
<Link
|
||||
href="https://supabase.com/docs/guides/auth/auth-smtp"
|
||||
target="_blank"
|
||||
className="text-primary/50 hover:text-primary flex items-center text-sm gap-1"
|
||||
>
|
||||
Learn more <ArrowUpRight size={14} />
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -21,7 +21,7 @@ Do not return the Markdown as a code block, only as a raw string, without any ne
|
||||
|
||||
No data or information should ever be removed, it should only be processed and formatted.
|
||||
|
||||
There are in-text citations/references in the text, remove them from the text (**but most importantly, keep the reference number in the text. use a <sup></sup> tag**) and put them into an object where the key is the reference number and the value is the text. If any citations contain JSON-breaking characters, ensure they are properly escaped. This includes characters like double quotes, backslashes, and newlines.
|
||||
There are in-text citations/references in the text, remove them from the text (**but most importantly, keep the reference number in the text. use a <sup></sup> tag**) and put them into an object where the key is the reference number and the value is the text. (**Note that there may be multiple citations, usually split by commas. Ensure these are added too.**) If any citations contain JSON-breaking characters, ensure they are properly escaped. This includes characters like double quotes, backslashes, and newlines.
|
||||
|
||||
The Markdown should be human-readable and well-formatted. The markdown string should properly sanitized and should not break a JSON parser when returned as the final format.
|
||||
|
||||
@ -263,7 +263,11 @@ export async function POST(req: NextRequest) {
|
||||
],
|
||||
});
|
||||
|
||||
const split = response.choices[0].message.content.split("---------");
|
||||
const contentData = response.choices?.[0]?.message?.content;
|
||||
const split =
|
||||
typeof contentData === "string"
|
||||
? contentData.split("---------")
|
||||
: ["", "{}"];
|
||||
const content = split[0].trim();
|
||||
const citationsStr = split[1]?.trim() || "{}";
|
||||
console.log("Citations string:", citationsStr);
|
||||
|
@ -19,7 +19,11 @@ import { redirect } from "next/navigation";
|
||||
import { TTSProvider } from "@/components/TTSProvider";
|
||||
import MarkdownRenderer from "@/components/MarkdownRenderer";
|
||||
|
||||
export default async function DocumentPage(props: { params: { id: string } }) {
|
||||
export default async function DocumentPage({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ id: string }>;
|
||||
}) {
|
||||
const supabase = await createClient();
|
||||
|
||||
const {
|
||||
@ -35,7 +39,7 @@ export default async function DocumentPage(props: { params: { id: string } }) {
|
||||
return redirect("/login");
|
||||
}
|
||||
|
||||
const { id } = await props.params;
|
||||
const { id } = await params;
|
||||
|
||||
// Fetch the document details based on the ID from params
|
||||
const { data: document, error } = await supabase
|
||||
|
@ -73,9 +73,6 @@ export default async function Page() {
|
||||
</BreadcrumbList>
|
||||
</Breadcrumb>
|
||||
</div>
|
||||
<div className="ml-auto px-3">
|
||||
<NavActions />
|
||||
</div>
|
||||
</header>
|
||||
<div className="flex flex-1 flex-col gap-4 px-4 py-10">
|
||||
<div className="bg-muted/50 mx-auto h-24 w-full max-w-3xl rounded-xl" />
|
||||
|
@ -10,6 +10,7 @@ import rehypeRaw from "rehype-raw";
|
||||
import { useTTS } from "./TTSProvider";
|
||||
import rehypeHighlight from "@/lib/utils";
|
||||
import { Database } from "@/utils/supabase/types";
|
||||
import remarkGfm from "remark-gfm";
|
||||
|
||||
// Utility to escape regex special characters:
|
||||
function escapeRegExp(text: string) {
|
||||
@ -18,7 +19,14 @@ function escapeRegExp(text: string) {
|
||||
|
||||
export type OCRData = {
|
||||
index: number;
|
||||
images: string[];
|
||||
images: {
|
||||
id: string;
|
||||
topLeftX: number;
|
||||
topLeftY: number;
|
||||
bottomRightX: number;
|
||||
bottomRightY: number;
|
||||
imageBase64: string;
|
||||
}[];
|
||||
markdown: string;
|
||||
citations: {
|
||||
text: string;
|
||||
@ -54,13 +62,22 @@ export default function MarkdownRenderer({
|
||||
|
||||
const ocr = document?.ocr_data as OCRData[];
|
||||
|
||||
const rawContent = ocr.map((page) => page.markdown).join("\n") || "";
|
||||
// Join all markdown content from the OCR data into a single string. Add page separators.
|
||||
const rawContent = ocr
|
||||
.map((page) => page.markdown)
|
||||
.map((page, index) => {
|
||||
const pageIndex = index + 1; // 1-based index for pages
|
||||
const pageSeparator = `\n\n###### Page ${pageIndex}\n\n`;
|
||||
return `${page}${pageSeparator}`;
|
||||
})
|
||||
.join("\n\n");
|
||||
|
||||
const citations: {
|
||||
text: string;
|
||||
page: number;
|
||||
index: string;
|
||||
number: number;
|
||||
inTextNumber: string;
|
||||
}[] = [];
|
||||
const totalPages = ocr.length;
|
||||
const totalSentences = sentences.length;
|
||||
@ -75,6 +92,7 @@ export default function MarkdownRenderer({
|
||||
page: page.index,
|
||||
index: (totalCitations + index).toString(), // unique index across all pages
|
||||
number: totalCitations + index + 1, // 1-based numbering
|
||||
inTextNumber: citation.number,
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -92,16 +110,61 @@ export default function MarkdownRenderer({
|
||||
h4: ({ node, ...props }) => (
|
||||
<h4 className="text-lg font-bold mb-2 text-gray-300" {...props} />
|
||||
),
|
||||
h6: ({ node, ...props }) => {
|
||||
const text = props.children!.toString().split(" ")[1];
|
||||
const pageIndex = parseInt(text) - 1; // Convert to 0-based index
|
||||
const page = ocr[pageIndex];
|
||||
|
||||
return (
|
||||
<div className="relative flex py-5 items-center">
|
||||
<div className="flex-grow border-t border-slate-800"></div>
|
||||
<span className="flex-shrink mx-4 text-gray-400">
|
||||
Page {pageIndex + 1} of {totalPages}
|
||||
</span>
|
||||
<div className="flex-grow border-t border-slate-800"></div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
p: ({ node, ...props }) => (
|
||||
<p className="leading-7 text-gray-200" {...props} />
|
||||
),
|
||||
img: ({ node, ...props }) => (
|
||||
<img
|
||||
className="rounded-lg shadow-sm"
|
||||
style={{ maxWidth: "100%", height: "auto" }}
|
||||
{...props}
|
||||
/>
|
||||
<p className="leading-7 text-gray-200 mb-3" {...props} />
|
||||
),
|
||||
img: ({ node, ...props }) => {
|
||||
const { src, alt } = props as any;
|
||||
|
||||
const pageIndex = ocr.findIndex((p) =>
|
||||
p.images.find((image) => image.id === src)
|
||||
);
|
||||
if (pageIndex === -1) return null; // Handle the case where the page is not found
|
||||
|
||||
const page = ocr.find((p) => p.index === pageIndex);
|
||||
if (!page) return null; // Handle the case where the page is not found
|
||||
|
||||
const img = page.images.find((image) => {
|
||||
if (image.id === src) {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
if (!img) return null; // Handle the case where the image is not found
|
||||
|
||||
// Calculate the width and height based on the image dimensions and DPI
|
||||
const dpi = page.dimensions.dpi;
|
||||
const width = ((img.bottomRightX - img.topLeftX) / dpi) * 96; // Convert to pixels
|
||||
const height = ((img.bottomRightY - img.topLeftY) / dpi) * 96; // Convert to pixels
|
||||
|
||||
return (
|
||||
<div className="w-full flex justify-center mb-4">
|
||||
<img
|
||||
className="rounded-lg shadow-sm"
|
||||
data-image
|
||||
src={img.imageBase64}
|
||||
alt={alt}
|
||||
width={width}
|
||||
height={height}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
a: ({ node, ...props }) => (
|
||||
<a className="text-blue-400 hover:underline" {...props} />
|
||||
),
|
||||
@ -122,6 +185,10 @@ export default function MarkdownRenderer({
|
||||
),
|
||||
sup: ({ node, ...props }) => {
|
||||
// Check if the text contains a reference number
|
||||
if (!props.children) {
|
||||
return <sup {...props} />;
|
||||
}
|
||||
|
||||
const text = props.children!.toString();
|
||||
|
||||
const referenceNumber = text;
|
||||
@ -139,7 +206,6 @@ export default function MarkdownRenderer({
|
||||
}
|
||||
|
||||
return (
|
||||
// TODO: get the references from the document and display them in a popover
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<sup
|
||||
@ -150,11 +216,43 @@ export default function MarkdownRenderer({
|
||||
<PopoverContent className="w-auto max-w-3xl bg-gray-900 overflow-hidden rounded-lg p-0">
|
||||
<div className="p-4">
|
||||
<p>{citation.text}</p>
|
||||
<p className="text-sm text-gray-500">
|
||||
Page {citation.page}, Reference {citation.inTextNumber}
|
||||
</p>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
);
|
||||
},
|
||||
table: ({ node, ...props }) => (
|
||||
<div className="relative overflow-x-auto shadow-md sm:rounded-lg">
|
||||
<table
|
||||
{...props}
|
||||
className="w-full text-sm text-left rtl:text-right text-gray-500 dark:text-gray-400"
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
thead: ({ node, ...props }) => (
|
||||
<thead
|
||||
{...props}
|
||||
className="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400"
|
||||
/>
|
||||
),
|
||||
tbody: ({ node, ...props }) => <tbody {...props} />,
|
||||
tr: ({ node, ...props }) => (
|
||||
<tr
|
||||
{...props}
|
||||
className="odd:bg-white odd:dark:bg-gray-900 even:bg-gray-50 even:dark:bg-gray-800 border-b dark:border-gray-700 border-gray-200"
|
||||
/>
|
||||
),
|
||||
th: ({ node, ...props }) => (
|
||||
<th
|
||||
{...props}
|
||||
scope="col"
|
||||
className="px-6 py-3 font-medium text-gray-900 whitespace-nowrap dark:text-white"
|
||||
/>
|
||||
),
|
||||
td: ({ node, ...props }) => <td {...props} className="px-6 py-4" />,
|
||||
};
|
||||
|
||||
return (
|
||||
@ -162,6 +260,7 @@ export default function MarkdownRenderer({
|
||||
children={rawContent}
|
||||
components={components}
|
||||
rehypePlugins={rehypePlugins}
|
||||
remarkPlugins={[remarkGfm]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -85,6 +85,8 @@ export const TTSProvider = ({
|
||||
audioCache.current.set(i, audioUrl); // Cache the audio URL
|
||||
} catch (error) {
|
||||
console.error(`Error preloading audio for sentence ${i}:`, error);
|
||||
} finally {
|
||||
setProcessing((prev) => prev.filter((item) => item !== i)); // Remove from processing
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -112,6 +114,7 @@ export const TTSProvider = ({
|
||||
}
|
||||
|
||||
const playSentence = async (index: number) => {
|
||||
if (index === currentSentence) return; // Prevent redundant updates
|
||||
setCurrentSentence(index);
|
||||
|
||||
const sentence = removeMarkdown(sentences[index]);
|
||||
@ -142,13 +145,12 @@ export const TTSProvider = ({
|
||||
playSentence(index);
|
||||
};
|
||||
|
||||
let shouldContinue = true;
|
||||
|
||||
const playInOrder = async (index: number) => {
|
||||
if (index < 0 || index >= sentences.length) return;
|
||||
setCurrentSentence(index);
|
||||
|
||||
// Introduce a flag to track whether playback should continue
|
||||
let shouldContinue = true;
|
||||
|
||||
for (let i = index; i < sentences.length; i++) {
|
||||
if (!shouldContinue) {
|
||||
console.log("Playback stopped or paused.");
|
||||
@ -158,14 +160,13 @@ export const TTSProvider = ({
|
||||
console.log("Playing sentence:", i, sentences[i]);
|
||||
try {
|
||||
await playSentence(i);
|
||||
preloadAudio(i + 1); // Preload the next sentence after playing
|
||||
await preloadAudio(i + 1); // Preload the next sentence after playing
|
||||
} catch (error) {
|
||||
console.error("Error playing sentence:", error);
|
||||
break; // Stop playback on error
|
||||
}
|
||||
}
|
||||
|
||||
// Reset the playback state when done
|
||||
setStatus("ready");
|
||||
};
|
||||
|
||||
@ -198,6 +199,23 @@ export const TTSProvider = ({
|
||||
preloadAudio(currentSentence);
|
||||
}, [currentSentence]);
|
||||
|
||||
useEffect(() => {
|
||||
if (audioRef.current) {
|
||||
const handleEnded = () => {
|
||||
console.log("Audio playback ended.");
|
||||
if (status === "running") {
|
||||
playInOrder(currentSentence + 1); // Play the next sentence
|
||||
}
|
||||
};
|
||||
|
||||
audioRef.current.addEventListener("ended", handleEnded);
|
||||
|
||||
return () => {
|
||||
audioRef.current?.removeEventListener("ended", handleEnded);
|
||||
};
|
||||
}
|
||||
}, [currentSentence, status]);
|
||||
|
||||
const value: TTSContextType = {
|
||||
sentences,
|
||||
currentSentence,
|
||||
|
@ -39,7 +39,7 @@ export default function UploadZone({ user }: { user?: { id: string } }) {
|
||||
},
|
||||
});
|
||||
|
||||
eventSource.addEventListener("status", (event) => {
|
||||
eventSource.addEventListener("status", (event: any) => {
|
||||
const data = JSON.parse(event.data);
|
||||
console.log("Status Event:", data);
|
||||
supabase.auth.setSession;
|
||||
@ -47,7 +47,7 @@ export default function UploadZone({ user }: { user?: { id: string } }) {
|
||||
setStatus(data.message);
|
||||
});
|
||||
|
||||
eventSource.addEventListener("error", (event) => {
|
||||
eventSource.addEventListener("error", (event: any) => {
|
||||
console.error("SSE Error:", event);
|
||||
toast.error("An error occurred while processing the document", {
|
||||
description: event.data || "Unknown error",
|
||||
@ -56,7 +56,7 @@ export default function UploadZone({ user }: { user?: { id: string } }) {
|
||||
eventSource.close();
|
||||
});
|
||||
|
||||
eventSource.addEventListener("complete", (event) => {
|
||||
eventSource.addEventListener("complete", (event: any) => {
|
||||
const data = JSON.parse(event.data);
|
||||
console.log("Processing Complete:", data);
|
||||
toast.success("Document processing complete!");
|
||||
|
@ -9,6 +9,7 @@ export function cn(...inputs: ClassValue[]) {
|
||||
|
||||
export async function detectWebGPU() {
|
||||
try {
|
||||
// @ts-ignore
|
||||
const adapter = await navigator.gpu.requestAdapter();
|
||||
return !!adapter;
|
||||
} catch (e) {
|
||||
|
@ -41,6 +41,7 @@
|
||||
"react-markdown": "^10.1.0",
|
||||
"rehype-raw": "^7.0.0",
|
||||
"remark": "^15.0.1",
|
||||
"remark-gfm": "^4.0.1",
|
||||
"remark-html": "^16.0.1",
|
||||
"remove-markdown": "^0.6.0",
|
||||
"sonner": "^2.0.3",
|
||||
|
8
supabase/.gitignore
vendored
8
supabase/.gitignore
vendored
@ -1,8 +0,0 @@
|
||||
# Supabase
|
||||
.branches
|
||||
.temp
|
||||
|
||||
# dotenvx
|
||||
.env.keys
|
||||
.env.local
|
||||
.env.*.local
|
@ -1,319 +0,0 @@
|
||||
# For detailed configuration reference documentation, visit:
|
||||
# https://supabase.com/docs/guides/local-development/cli/config
|
||||
# A string used to distinguish different Supabase projects on the same host. Defaults to the
|
||||
# working directory name when running `supabase init`.
|
||||
project_id = "neuro-read"
|
||||
|
||||
[api]
|
||||
enabled = true
|
||||
# Port to use for the API URL.
|
||||
port = 54321
|
||||
# Schemas to expose in your API. Tables, views and stored procedures in this schema will get API
|
||||
# endpoints. `public` and `graphql_public` schemas are included by default.
|
||||
schemas = ["public", "graphql_public"]
|
||||
# Extra schemas to add to the search_path of every request.
|
||||
extra_search_path = ["public", "extensions"]
|
||||
# The maximum number of rows returns from a view, table, or stored procedure. Limits payload size
|
||||
# for accidental or malicious requests.
|
||||
max_rows = 1000
|
||||
|
||||
[api.tls]
|
||||
# Enable HTTPS endpoints locally using a self-signed certificate.
|
||||
enabled = false
|
||||
|
||||
[db]
|
||||
# Port to use for the local database URL.
|
||||
port = 54322
|
||||
# Port used by db diff command to initialize the shadow database.
|
||||
shadow_port = 54320
|
||||
# The database major version to use. This has to be the same as your remote database's. Run `SHOW
|
||||
# server_version;` on the remote database to check.
|
||||
major_version = 15
|
||||
|
||||
[db.pooler]
|
||||
enabled = false
|
||||
# Port to use for the local connection pooler.
|
||||
port = 54329
|
||||
# Specifies when a server connection can be reused by other clients.
|
||||
# Configure one of the supported pooler modes: `transaction`, `session`.
|
||||
pool_mode = "transaction"
|
||||
# How many server connections to allow per user/database pair.
|
||||
default_pool_size = 20
|
||||
# Maximum number of client connections allowed.
|
||||
max_client_conn = 100
|
||||
|
||||
# [db.vault]
|
||||
# secret_key = "env(SECRET_VALUE)"
|
||||
|
||||
[db.migrations]
|
||||
# Specifies an ordered list of schema files that describe your database.
|
||||
# Supports glob patterns relative to supabase directory: "./schemas/*.sql"
|
||||
schema_paths = []
|
||||
|
||||
[db.seed]
|
||||
# If enabled, seeds the database after migrations during a db reset.
|
||||
enabled = true
|
||||
# Specifies an ordered list of seed files to load during db reset.
|
||||
# Supports glob patterns relative to supabase directory: "./seeds/*.sql"
|
||||
sql_paths = ["./seed.sql"]
|
||||
|
||||
[realtime]
|
||||
enabled = true
|
||||
# Bind realtime via either IPv4 or IPv6. (default: IPv4)
|
||||
# ip_version = "IPv6"
|
||||
# The maximum length in bytes of HTTP request headers. (default: 4096)
|
||||
# max_header_length = 4096
|
||||
|
||||
[studio]
|
||||
enabled = true
|
||||
# Port to use for Supabase Studio.
|
||||
port = 54323
|
||||
# External URL of the API server that frontend connects to.
|
||||
api_url = "http://127.0.0.1"
|
||||
# OpenAI API Key to use for Supabase AI in the Supabase Studio.
|
||||
openai_api_key = "env(OPENAI_API_KEY)"
|
||||
|
||||
# Email testing server. Emails sent with the local dev setup are not actually sent - rather, they
|
||||
# are monitored, and you can view the emails that would have been sent from the web interface.
|
||||
[inbucket]
|
||||
enabled = true
|
||||
# Port to use for the email testing server web interface.
|
||||
port = 54324
|
||||
# Uncomment to expose additional ports for testing user applications that send emails.
|
||||
# smtp_port = 54325
|
||||
# pop3_port = 54326
|
||||
# admin_email = "admin@email.com"
|
||||
# sender_name = "Admin"
|
||||
|
||||
[storage]
|
||||
enabled = true
|
||||
# The maximum file size allowed (e.g. "5MB", "500KB").
|
||||
file_size_limit = "50MiB"
|
||||
|
||||
# Image transformation API is available to Supabase Pro plan.
|
||||
# [storage.image_transformation]
|
||||
# enabled = true
|
||||
|
||||
# Uncomment to configure local storage buckets
|
||||
# [storage.buckets.images]
|
||||
# public = false
|
||||
# file_size_limit = "50MiB"
|
||||
# allowed_mime_types = ["image/png", "image/jpeg"]
|
||||
# objects_path = "./images"
|
||||
|
||||
[auth]
|
||||
enabled = true
|
||||
# The base URL of your website. Used as an allow-list for redirects and for constructing URLs used
|
||||
# in emails.
|
||||
site_url = "http://127.0.0.1:3000"
|
||||
# A list of *exact* URLs that auth providers are permitted to redirect to post authentication.
|
||||
additional_redirect_urls = ["https://127.0.0.1:3000"]
|
||||
# How long tokens are valid for, in seconds. Defaults to 3600 (1 hour), maximum 604,800 (1 week).
|
||||
jwt_expiry = 3600
|
||||
# If disabled, the refresh token will never expire.
|
||||
enable_refresh_token_rotation = true
|
||||
# Allows refresh tokens to be reused after expiry, up to the specified interval in seconds.
|
||||
# Requires enable_refresh_token_rotation = true.
|
||||
refresh_token_reuse_interval = 10
|
||||
# Allow/disallow new user signups to your project.
|
||||
enable_signup = true
|
||||
# Allow/disallow anonymous sign-ins to your project.
|
||||
enable_anonymous_sign_ins = false
|
||||
# Allow/disallow testing manual linking of accounts
|
||||
enable_manual_linking = false
|
||||
# Passwords shorter than this value will be rejected as weak. Minimum 6, recommended 8 or more.
|
||||
minimum_password_length = 6
|
||||
# Passwords that do not meet the following requirements will be rejected as weak. Supported values
|
||||
# are: `letters_digits`, `lower_upper_letters_digits`, `lower_upper_letters_digits_symbols`
|
||||
password_requirements = ""
|
||||
|
||||
[auth.rate_limit]
|
||||
# Number of emails that can be sent per hour. Requires auth.email.smtp to be enabled.
|
||||
email_sent = 2
|
||||
# Number of SMS messages that can be sent per hour. Requires auth.sms to be enabled.
|
||||
sms_sent = 30
|
||||
# Number of anonymous sign-ins that can be made per hour per IP address. Requires enable_anonymous_sign_ins = true.
|
||||
anonymous_users = 30
|
||||
# Number of sessions that can be refreshed in a 5 minute interval per IP address.
|
||||
token_refresh = 150
|
||||
# Number of sign up and sign-in requests that can be made in a 5 minute interval per IP address (excludes anonymous users).
|
||||
sign_in_sign_ups = 30
|
||||
# Number of OTP / Magic link verifications that can be made in a 5 minute interval per IP address.
|
||||
token_verifications = 30
|
||||
|
||||
# Configure one of the supported captcha providers: `hcaptcha`, `turnstile`.
|
||||
# [auth.captcha]
|
||||
# enabled = true
|
||||
# provider = "hcaptcha"
|
||||
# secret = ""
|
||||
|
||||
[auth.email]
|
||||
# Allow/disallow new user signups via email to your project.
|
||||
enable_signup = true
|
||||
# If enabled, a user will be required to confirm any email change on both the old, and new email
|
||||
# addresses. If disabled, only the new email is required to confirm.
|
||||
double_confirm_changes = true
|
||||
# If enabled, users need to confirm their email address before signing in.
|
||||
enable_confirmations = false
|
||||
# If enabled, users will need to reauthenticate or have logged in recently to change their password.
|
||||
secure_password_change = false
|
||||
# Controls the minimum amount of time that must pass before sending another signup confirmation or password reset email.
|
||||
max_frequency = "1s"
|
||||
# Number of characters used in the email OTP.
|
||||
otp_length = 6
|
||||
# Number of seconds before the email OTP expires (defaults to 1 hour).
|
||||
otp_expiry = 3600
|
||||
|
||||
# Use a production-ready SMTP server
|
||||
# [auth.email.smtp]
|
||||
# enabled = true
|
||||
# host = "smtp.sendgrid.net"
|
||||
# port = 587
|
||||
# user = "apikey"
|
||||
# pass = "env(SENDGRID_API_KEY)"
|
||||
# admin_email = "admin@email.com"
|
||||
# sender_name = "Admin"
|
||||
|
||||
# Uncomment to customize email template
|
||||
# [auth.email.template.invite]
|
||||
# subject = "You have been invited"
|
||||
# content_path = "./supabase/templates/invite.html"
|
||||
|
||||
[auth.sms]
|
||||
# Allow/disallow new user signups via SMS to your project.
|
||||
enable_signup = false
|
||||
# If enabled, users need to confirm their phone number before signing in.
|
||||
enable_confirmations = false
|
||||
# Template for sending OTP to users
|
||||
template = "Your code is {{ .Code }}"
|
||||
# Controls the minimum amount of time that must pass before sending another sms otp.
|
||||
max_frequency = "5s"
|
||||
|
||||
# Use pre-defined map of phone number to OTP for testing.
|
||||
# [auth.sms.test_otp]
|
||||
# 4152127777 = "123456"
|
||||
|
||||
# Configure logged in session timeouts.
|
||||
# [auth.sessions]
|
||||
# Force log out after the specified duration.
|
||||
# timebox = "24h"
|
||||
# Force log out if the user has been inactive longer than the specified duration.
|
||||
# inactivity_timeout = "8h"
|
||||
|
||||
# This hook runs before a token is issued and allows you to add additional claims based on the authentication method used.
|
||||
# [auth.hook.custom_access_token]
|
||||
# enabled = true
|
||||
# uri = "pg-functions://<database>/<schema>/<hook_name>"
|
||||
|
||||
# Configure one of the supported SMS providers: `twilio`, `twilio_verify`, `messagebird`, `textlocal`, `vonage`.
|
||||
[auth.sms.twilio]
|
||||
enabled = false
|
||||
account_sid = ""
|
||||
message_service_sid = ""
|
||||
# DO NOT commit your Twilio auth token to git. Use environment variable substitution instead:
|
||||
auth_token = "env(SUPABASE_AUTH_SMS_TWILIO_AUTH_TOKEN)"
|
||||
|
||||
# Multi-factor-authentication is available to Supabase Pro plan.
|
||||
[auth.mfa]
|
||||
# Control how many MFA factors can be enrolled at once per user.
|
||||
max_enrolled_factors = 10
|
||||
|
||||
# Control MFA via App Authenticator (TOTP)
|
||||
[auth.mfa.totp]
|
||||
enroll_enabled = false
|
||||
verify_enabled = false
|
||||
|
||||
# Configure MFA via Phone Messaging
|
||||
[auth.mfa.phone]
|
||||
enroll_enabled = false
|
||||
verify_enabled = false
|
||||
otp_length = 6
|
||||
template = "Your code is {{ .Code }}"
|
||||
max_frequency = "5s"
|
||||
|
||||
# Configure MFA via WebAuthn
|
||||
# [auth.mfa.web_authn]
|
||||
# enroll_enabled = true
|
||||
# verify_enabled = true
|
||||
|
||||
# Use an external OAuth provider. The full list of providers are: `apple`, `azure`, `bitbucket`,
|
||||
# `discord`, `facebook`, `github`, `gitlab`, `google`, `keycloak`, `linkedin_oidc`, `notion`, `twitch`,
|
||||
# `twitter`, `slack`, `spotify`, `workos`, `zoom`.
|
||||
[auth.external.apple]
|
||||
enabled = false
|
||||
client_id = ""
|
||||
# DO NOT commit your OAuth provider secret to git. Use environment variable substitution instead:
|
||||
secret = "env(SUPABASE_AUTH_EXTERNAL_APPLE_SECRET)"
|
||||
# Overrides the default auth redirectUrl.
|
||||
redirect_uri = ""
|
||||
# Overrides the default auth provider URL. Used to support self-hosted gitlab, single-tenant Azure,
|
||||
# or any other third-party OIDC providers.
|
||||
url = ""
|
||||
# If enabled, the nonce check will be skipped. Required for local sign in with Google auth.
|
||||
skip_nonce_check = false
|
||||
|
||||
# Use Firebase Auth as a third-party provider alongside Supabase Auth.
|
||||
[auth.third_party.firebase]
|
||||
enabled = false
|
||||
# project_id = "my-firebase-project"
|
||||
|
||||
# Use Auth0 as a third-party provider alongside Supabase Auth.
|
||||
[auth.third_party.auth0]
|
||||
enabled = false
|
||||
# tenant = "my-auth0-tenant"
|
||||
# tenant_region = "us"
|
||||
|
||||
# Use AWS Cognito (Amplify) as a third-party provider alongside Supabase Auth.
|
||||
[auth.third_party.aws_cognito]
|
||||
enabled = false
|
||||
# user_pool_id = "my-user-pool-id"
|
||||
# user_pool_region = "us-east-1"
|
||||
|
||||
# Use Clerk as a third-party provider alongside Supabase Auth.
|
||||
[auth.third_party.clerk]
|
||||
enabled = false
|
||||
# Obtain from https://clerk.com/setup/supabase
|
||||
# domain = "example.clerk.accounts.dev"
|
||||
|
||||
[edge_runtime]
|
||||
enabled = true
|
||||
# Configure one of the supported request policies: `oneshot`, `per_worker`.
|
||||
# Use `oneshot` for hot reload, or `per_worker` for load testing.
|
||||
policy = "oneshot"
|
||||
# Port to attach the Chrome inspector for debugging edge functions.
|
||||
inspector_port = 8083
|
||||
# The Deno major version to use.
|
||||
deno_version = 1
|
||||
|
||||
# [edge_runtime.secrets]
|
||||
# secret_key = "env(SECRET_VALUE)"
|
||||
|
||||
[analytics]
|
||||
enabled = true
|
||||
port = 54327
|
||||
# Configure one of the supported backends: `postgres`, `bigquery`.
|
||||
backend = "postgres"
|
||||
|
||||
# Experimental features may be deprecated any time
|
||||
[experimental]
|
||||
# Configures Postgres storage engine to use OrioleDB (S3)
|
||||
orioledb_version = ""
|
||||
# Configures S3 bucket URL, eg. <bucket_name>.s3-<region>.amazonaws.com
|
||||
s3_host = "env(S3_HOST)"
|
||||
# Configures S3 bucket region, eg. us-east-1
|
||||
s3_region = "env(S3_REGION)"
|
||||
# Configures AWS_ACCESS_KEY_ID for S3 bucket
|
||||
s3_access_key = "env(S3_ACCESS_KEY)"
|
||||
# Configures AWS_SECRET_ACCESS_KEY for S3 bucket
|
||||
s3_secret_key = "env(S3_SECRET_KEY)"
|
||||
|
||||
[functions.generate-tts]
|
||||
enabled = true
|
||||
verify_jwt = true
|
||||
import_map = "./functions/generate-tts/deno.json"
|
||||
# Uncomment to specify a custom file path to the entrypoint.
|
||||
# Supported file extensions are: .ts, .js, .mjs, .jsx, .tsx
|
||||
entrypoint = "./functions/generate-tts/index.ts"
|
||||
# Specifies static files to be bundled with the function. Supports glob patterns.
|
||||
# For example, if you want to serve static HTML pages in your function:
|
||||
# static_files = [ "./functions/generate-tts/*.html" ]
|
@ -1,3 +0,0 @@
|
||||
# Configuration for private npm package dependencies
|
||||
# For more information on using private registries with Edge Functions, see:
|
||||
# https://supabase.com/docs/guides/functions/import-maps#importing-from-private-registries
|
@ -1,3 +0,0 @@
|
||||
{
|
||||
"imports": {}
|
||||
}
|
664
supabase/functions/generate-tts/deno.lock
generated
664
supabase/functions/generate-tts/deno.lock
generated
@ -1,664 +0,0 @@
|
||||
{
|
||||
"version": "4",
|
||||
"specifiers": {
|
||||
"jsr:@supabase/functions-js@*": "2.4.4",
|
||||
"npm:@mistralai/mistralai@*": "1.6.0_zod@3.24.4",
|
||||
"npm:@types/node@*": "22.12.0",
|
||||
"npm:kokoro-js@*": "1.2.1",
|
||||
"npm:kokoro-tts@*": "0.0.1-security",
|
||||
"npm:openai@^4.52.5": "4.97.0",
|
||||
"npm:p-limit@*": "6.2.0"
|
||||
},
|
||||
"jsr": {
|
||||
"@supabase/functions-js@2.4.4": {
|
||||
"integrity": "38456509a6e22fb116b118464cbb36357256f9048d3580632b63af91f63769f7",
|
||||
"dependencies": [
|
||||
"npm:openai"
|
||||
]
|
||||
}
|
||||
},
|
||||
"npm": {
|
||||
"@emnapi/runtime@1.4.3": {
|
||||
"integrity": "sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ==",
|
||||
"dependencies": [
|
||||
"tslib"
|
||||
]
|
||||
},
|
||||
"@huggingface/jinja@0.4.1": {
|
||||
"integrity": "sha512-3WXbMFaPkk03LRCM0z0sylmn8ddDm4ubjU7X+Hg4M2GOuMklwoGAFXp9V2keq7vltoB/c7McE5aHUVVddAewsw=="
|
||||
},
|
||||
"@huggingface/transformers@3.5.1": {
|
||||
"integrity": "sha512-qWsPoJMBPYcrGuzRMVL//3dwcLXED9r+j+Hzw+hZpBfrMPdyq57ehNs7lew0D34Paj0DO0E0kZQjOZcIVN17hQ==",
|
||||
"dependencies": [
|
||||
"@huggingface/jinja",
|
||||
"onnxruntime-node",
|
||||
"onnxruntime-web",
|
||||
"sharp"
|
||||
]
|
||||
},
|
||||
"@img/sharp-darwin-arm64@0.34.1": {
|
||||
"integrity": "sha512-pn44xgBtgpEbZsu+lWf2KNb6OAf70X68k+yk69Ic2Xz11zHR/w24/U49XT7AeRwJ0Px+mhALhU5LPci1Aymk7A==",
|
||||
"dependencies": [
|
||||
"@img/sharp-libvips-darwin-arm64"
|
||||
]
|
||||
},
|
||||
"@img/sharp-darwin-x64@0.34.1": {
|
||||
"integrity": "sha512-VfuYgG2r8BpYiOUN+BfYeFo69nP/MIwAtSJ7/Zpxc5QF3KS22z8Pvg3FkrSFJBPNQ7mmcUcYQFBmEQp7eu1F8Q==",
|
||||
"dependencies": [
|
||||
"@img/sharp-libvips-darwin-x64"
|
||||
]
|
||||
},
|
||||
"@img/sharp-libvips-darwin-arm64@1.1.0": {
|
||||
"integrity": "sha512-HZ/JUmPwrJSoM4DIQPv/BfNh9yrOA8tlBbqbLz4JZ5uew2+o22Ik+tHQJcih7QJuSa0zo5coHTfD5J8inqj9DA=="
|
||||
},
|
||||
"@img/sharp-libvips-darwin-x64@1.1.0": {
|
||||
"integrity": "sha512-Xzc2ToEmHN+hfvsl9wja0RlnXEgpKNmftriQp6XzY/RaSfwD9th+MSh0WQKzUreLKKINb3afirxW7A0fz2YWuQ=="
|
||||
},
|
||||
"@img/sharp-libvips-linux-arm64@1.1.0": {
|
||||
"integrity": "sha512-IVfGJa7gjChDET1dK9SekxFFdflarnUB8PwW8aGwEoF3oAsSDuNUTYS+SKDOyOJxQyDC1aPFMuRYLoDInyV9Ew=="
|
||||
},
|
||||
"@img/sharp-libvips-linux-arm@1.1.0": {
|
||||
"integrity": "sha512-s8BAd0lwUIvYCJyRdFqvsj+BJIpDBSxs6ivrOPm/R7piTs5UIwY5OjXrP2bqXC9/moGsyRa37eYWYCOGVXxVrA=="
|
||||
},
|
||||
"@img/sharp-libvips-linux-ppc64@1.1.0": {
|
||||
"integrity": "sha512-tiXxFZFbhnkWE2LA8oQj7KYR+bWBkiV2nilRldT7bqoEZ4HiDOcePr9wVDAZPi/Id5fT1oY9iGnDq20cwUz8lQ=="
|
||||
},
|
||||
"@img/sharp-libvips-linux-s390x@1.1.0": {
|
||||
"integrity": "sha512-xukSwvhguw7COyzvmjydRb3x/09+21HykyapcZchiCUkTThEQEOMtBj9UhkaBRLuBrgLFzQ2wbxdeCCJW/jgJA=="
|
||||
},
|
||||
"@img/sharp-libvips-linux-x64@1.1.0": {
|
||||
"integrity": "sha512-yRj2+reB8iMg9W5sULM3S74jVS7zqSzHG3Ol/twnAAkAhnGQnpjj6e4ayUz7V+FpKypwgs82xbRdYtchTTUB+Q=="
|
||||
},
|
||||
"@img/sharp-libvips-linuxmusl-arm64@1.1.0": {
|
||||
"integrity": "sha512-jYZdG+whg0MDK+q2COKbYidaqW/WTz0cc1E+tMAusiDygrM4ypmSCjOJPmFTvHHJ8j/6cAGyeDWZOsK06tP33w=="
|
||||
},
|
||||
"@img/sharp-libvips-linuxmusl-x64@1.1.0": {
|
||||
"integrity": "sha512-wK7SBdwrAiycjXdkPnGCPLjYb9lD4l6Ze2gSdAGVZrEL05AOUJESWU2lhlC+Ffn5/G+VKuSm6zzbQSzFX/P65A=="
|
||||
},
|
||||
"@img/sharp-linux-arm64@0.34.1": {
|
||||
"integrity": "sha512-kX2c+vbvaXC6vly1RDf/IWNXxrlxLNpBVWkdpRq5Ka7OOKj6nr66etKy2IENf6FtOgklkg9ZdGpEu9kwdlcwOQ==",
|
||||
"dependencies": [
|
||||
"@img/sharp-libvips-linux-arm64"
|
||||
]
|
||||
},
|
||||
"@img/sharp-linux-arm@0.34.1": {
|
||||
"integrity": "sha512-anKiszvACti2sGy9CirTlNyk7BjjZPiML1jt2ZkTdcvpLU1YH6CXwRAZCA2UmRXnhiIftXQ7+Oh62Ji25W72jA==",
|
||||
"dependencies": [
|
||||
"@img/sharp-libvips-linux-arm"
|
||||
]
|
||||
},
|
||||
"@img/sharp-linux-s390x@0.34.1": {
|
||||
"integrity": "sha512-7s0KX2tI9mZI2buRipKIw2X1ufdTeaRgwmRabt5bi9chYfhur+/C1OXg3TKg/eag1W+6CCWLVmSauV1owmRPxA==",
|
||||
"dependencies": [
|
||||
"@img/sharp-libvips-linux-s390x"
|
||||
]
|
||||
},
|
||||
"@img/sharp-linux-x64@0.34.1": {
|
||||
"integrity": "sha512-wExv7SH9nmoBW3Wr2gvQopX1k8q2g5V5Iag8Zk6AVENsjwd+3adjwxtp3Dcu2QhOXr8W9NusBU6XcQUohBZ5MA==",
|
||||
"dependencies": [
|
||||
"@img/sharp-libvips-linux-x64"
|
||||
]
|
||||
},
|
||||
"@img/sharp-linuxmusl-arm64@0.34.1": {
|
||||
"integrity": "sha512-DfvyxzHxw4WGdPiTF0SOHnm11Xv4aQexvqhRDAoD00MzHekAj9a/jADXeXYCDFH/DzYruwHbXU7uz+H+nWmSOQ==",
|
||||
"dependencies": [
|
||||
"@img/sharp-libvips-linuxmusl-arm64"
|
||||
]
|
||||
},
|
||||
"@img/sharp-linuxmusl-x64@0.34.1": {
|
||||
"integrity": "sha512-pax/kTR407vNb9qaSIiWVnQplPcGU8LRIJpDT5o8PdAx5aAA7AS3X9PS8Isw1/WfqgQorPotjrZL3Pqh6C5EBg==",
|
||||
"dependencies": [
|
||||
"@img/sharp-libvips-linuxmusl-x64"
|
||||
]
|
||||
},
|
||||
"@img/sharp-wasm32@0.34.1": {
|
||||
"integrity": "sha512-YDybQnYrLQfEpzGOQe7OKcyLUCML4YOXl428gOOzBgN6Gw0rv8dpsJ7PqTHxBnXnwXr8S1mYFSLSa727tpz0xg==",
|
||||
"dependencies": [
|
||||
"@emnapi/runtime"
|
||||
]
|
||||
},
|
||||
"@img/sharp-win32-ia32@0.34.1": {
|
||||
"integrity": "sha512-WKf/NAZITnonBf3U1LfdjoMgNO5JYRSlhovhRhMxXVdvWYveM4kM3L8m35onYIdh75cOMCo1BexgVQcCDzyoWw=="
|
||||
},
|
||||
"@img/sharp-win32-x64@0.34.1": {
|
||||
"integrity": "sha512-hw1iIAHpNE8q3uMIRCgGOeDoz9KtFNarFLQclLxr/LK1VBkj8nby18RjFvr6aP7USRYAjTZW6yisnBWMX571Tw=="
|
||||
},
|
||||
"@isaacs/fs-minipass@4.0.1": {
|
||||
"integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==",
|
||||
"dependencies": [
|
||||
"minipass"
|
||||
]
|
||||
},
|
||||
"@mistralai/mistralai@1.6.0_zod@3.24.4": {
|
||||
"integrity": "sha512-PQwGV3+n7FbE7Dp3Vnd8DAa3ffx6WuVV966Gfmf4QvzwcO3Mvxpz0SnJ/PjaZcsCwApBCZpNyQzvarAKEQLKeQ==",
|
||||
"dependencies": [
|
||||
"zod",
|
||||
"zod-to-json-schema"
|
||||
]
|
||||
},
|
||||
"@protobufjs/aspromise@1.1.2": {
|
||||
"integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ=="
|
||||
},
|
||||
"@protobufjs/base64@1.1.2": {
|
||||
"integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg=="
|
||||
},
|
||||
"@protobufjs/codegen@2.0.4": {
|
||||
"integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg=="
|
||||
},
|
||||
"@protobufjs/eventemitter@1.1.0": {
|
||||
"integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q=="
|
||||
},
|
||||
"@protobufjs/fetch@1.1.0": {
|
||||
"integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==",
|
||||
"dependencies": [
|
||||
"@protobufjs/aspromise",
|
||||
"@protobufjs/inquire"
|
||||
]
|
||||
},
|
||||
"@protobufjs/float@1.0.2": {
|
||||
"integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ=="
|
||||
},
|
||||
"@protobufjs/inquire@1.1.0": {
|
||||
"integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q=="
|
||||
},
|
||||
"@protobufjs/path@1.1.2": {
|
||||
"integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA=="
|
||||
},
|
||||
"@protobufjs/pool@1.1.0": {
|
||||
"integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw=="
|
||||
},
|
||||
"@protobufjs/utf8@1.1.0": {
|
||||
"integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw=="
|
||||
},
|
||||
"@types/node-fetch@2.6.12": {
|
||||
"integrity": "sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==",
|
||||
"dependencies": [
|
||||
"@types/node@22.12.0",
|
||||
"form-data"
|
||||
]
|
||||
},
|
||||
"@types/node@18.19.87": {
|
||||
"integrity": "sha512-OIAAu6ypnVZHmsHCeJ+7CCSub38QNBS9uceMQeg7K5Ur0Jr+wG9wEOEvvMbhp09pxD5czIUy/jND7s7Tb6Nw7A==",
|
||||
"dependencies": [
|
||||
"undici-types@5.26.5"
|
||||
]
|
||||
},
|
||||
"@types/node@22.12.0": {
|
||||
"integrity": "sha512-Fll2FZ1riMjNmlmJOdAyY5pUbkftXslB5DgEzlIuNaiWhXd00FhWxVC/r4yV/4wBb9JfImTu+jiSvXTkJ7F/gA==",
|
||||
"dependencies": [
|
||||
"undici-types@6.20.0"
|
||||
]
|
||||
},
|
||||
"abort-controller@3.0.0": {
|
||||
"integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==",
|
||||
"dependencies": [
|
||||
"event-target-shim"
|
||||
]
|
||||
},
|
||||
"agentkeepalive@4.6.0": {
|
||||
"integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==",
|
||||
"dependencies": [
|
||||
"humanize-ms"
|
||||
]
|
||||
},
|
||||
"asynckit@0.4.0": {
|
||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
|
||||
},
|
||||
"boolean@3.2.0": {
|
||||
"integrity": "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw=="
|
||||
},
|
||||
"call-bind-apply-helpers@1.0.2": {
|
||||
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
|
||||
"dependencies": [
|
||||
"es-errors",
|
||||
"function-bind"
|
||||
]
|
||||
},
|
||||
"chownr@3.0.0": {
|
||||
"integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g=="
|
||||
},
|
||||
"color-convert@2.0.1": {
|
||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||
"dependencies": [
|
||||
"color-name"
|
||||
]
|
||||
},
|
||||
"color-name@1.1.4": {
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
|
||||
},
|
||||
"color-string@1.9.1": {
|
||||
"integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
|
||||
"dependencies": [
|
||||
"color-name",
|
||||
"simple-swizzle"
|
||||
]
|
||||
},
|
||||
"color@4.2.3": {
|
||||
"integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==",
|
||||
"dependencies": [
|
||||
"color-convert",
|
||||
"color-string"
|
||||
]
|
||||
},
|
||||
"combined-stream@1.0.8": {
|
||||
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||
"dependencies": [
|
||||
"delayed-stream"
|
||||
]
|
||||
},
|
||||
"define-data-property@1.1.4": {
|
||||
"integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
|
||||
"dependencies": [
|
||||
"es-define-property",
|
||||
"es-errors",
|
||||
"gopd"
|
||||
]
|
||||
},
|
||||
"define-properties@1.2.1": {
|
||||
"integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==",
|
||||
"dependencies": [
|
||||
"define-data-property",
|
||||
"has-property-descriptors",
|
||||
"object-keys"
|
||||
]
|
||||
},
|
||||
"delayed-stream@1.0.0": {
|
||||
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="
|
||||
},
|
||||
"detect-libc@2.0.4": {
|
||||
"integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA=="
|
||||
},
|
||||
"detect-node@2.1.0": {
|
||||
"integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g=="
|
||||
},
|
||||
"dunder-proto@1.0.1": {
|
||||
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
|
||||
"dependencies": [
|
||||
"call-bind-apply-helpers",
|
||||
"es-errors",
|
||||
"gopd"
|
||||
]
|
||||
},
|
||||
"es-define-property@1.0.1": {
|
||||
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="
|
||||
},
|
||||
"es-errors@1.3.0": {
|
||||
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="
|
||||
},
|
||||
"es-object-atoms@1.1.1": {
|
||||
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
|
||||
"dependencies": [
|
||||
"es-errors"
|
||||
]
|
||||
},
|
||||
"es-set-tostringtag@2.1.0": {
|
||||
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
|
||||
"dependencies": [
|
||||
"es-errors",
|
||||
"get-intrinsic",
|
||||
"has-tostringtag",
|
||||
"hasown"
|
||||
]
|
||||
},
|
||||
"es6-error@4.1.1": {
|
||||
"integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg=="
|
||||
},
|
||||
"escape-string-regexp@4.0.0": {
|
||||
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="
|
||||
},
|
||||
"event-target-shim@5.0.1": {
|
||||
"integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ=="
|
||||
},
|
||||
"flatbuffers@25.2.10": {
|
||||
"integrity": "sha512-7JlN9ZvLDG1McO3kbX0k4v+SUAg48L1rIwEvN6ZQl/eCtgJz9UylTMzE9wrmYrcorgxm3CX/3T/w5VAub99UUw=="
|
||||
},
|
||||
"form-data-encoder@1.7.2": {
|
||||
"integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A=="
|
||||
},
|
||||
"form-data@4.0.2": {
|
||||
"integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==",
|
||||
"dependencies": [
|
||||
"asynckit",
|
||||
"combined-stream",
|
||||
"es-set-tostringtag",
|
||||
"mime-types"
|
||||
]
|
||||
},
|
||||
"formdata-node@4.4.1": {
|
||||
"integrity": "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==",
|
||||
"dependencies": [
|
||||
"node-domexception",
|
||||
"web-streams-polyfill"
|
||||
]
|
||||
},
|
||||
"function-bind@1.1.2": {
|
||||
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="
|
||||
},
|
||||
"get-intrinsic@1.3.0": {
|
||||
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
|
||||
"dependencies": [
|
||||
"call-bind-apply-helpers",
|
||||
"es-define-property",
|
||||
"es-errors",
|
||||
"es-object-atoms",
|
||||
"function-bind",
|
||||
"get-proto",
|
||||
"gopd",
|
||||
"has-symbols",
|
||||
"hasown",
|
||||
"math-intrinsics"
|
||||
]
|
||||
},
|
||||
"get-proto@1.0.1": {
|
||||
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
|
||||
"dependencies": [
|
||||
"dunder-proto",
|
||||
"es-object-atoms"
|
||||
]
|
||||
},
|
||||
"global-agent@3.0.0": {
|
||||
"integrity": "sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==",
|
||||
"dependencies": [
|
||||
"boolean",
|
||||
"es6-error",
|
||||
"matcher",
|
||||
"roarr",
|
||||
"semver",
|
||||
"serialize-error"
|
||||
]
|
||||
},
|
||||
"globalthis@1.0.4": {
|
||||
"integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==",
|
||||
"dependencies": [
|
||||
"define-properties",
|
||||
"gopd"
|
||||
]
|
||||
},
|
||||
"gopd@1.2.0": {
|
||||
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="
|
||||
},
|
||||
"guid-typescript@1.0.9": {
|
||||
"integrity": "sha512-Y8T4vYhEfwJOTbouREvG+3XDsjr8E3kIr7uf+JZ0BYloFsttiHU0WfvANVsR7TxNUJa/WpCnw/Ino/p+DeBhBQ=="
|
||||
},
|
||||
"has-property-descriptors@1.0.2": {
|
||||
"integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
|
||||
"dependencies": [
|
||||
"es-define-property"
|
||||
]
|
||||
},
|
||||
"has-symbols@1.1.0": {
|
||||
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="
|
||||
},
|
||||
"has-tostringtag@1.0.2": {
|
||||
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
|
||||
"dependencies": [
|
||||
"has-symbols"
|
||||
]
|
||||
},
|
||||
"hasown@2.0.2": {
|
||||
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
||||
"dependencies": [
|
||||
"function-bind"
|
||||
]
|
||||
},
|
||||
"humanize-ms@1.2.1": {
|
||||
"integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==",
|
||||
"dependencies": [
|
||||
"ms"
|
||||
]
|
||||
},
|
||||
"is-arrayish@0.3.2": {
|
||||
"integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ=="
|
||||
},
|
||||
"json-stringify-safe@5.0.1": {
|
||||
"integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA=="
|
||||
},
|
||||
"kokoro-js@1.2.1": {
|
||||
"integrity": "sha512-oq0HZJWis3t8lERkMJh84WLU86dpYD0EuBPtqYnLlQzyFP1OkyBRDcweAqCfhNOpltyN9j/azp1H6uuC47gShw==",
|
||||
"dependencies": [
|
||||
"@huggingface/transformers",
|
||||
"phonemizer"
|
||||
]
|
||||
},
|
||||
"kokoro-tts@0.0.1-security": {
|
||||
"integrity": "sha512-VK7ILDqP0J88kiXdFrq7ymugipw42xLGmAU3kqpyFwJKlBqcsiTXVbvdTtehwYhYPw5QKJPmN2j7tD6fhPe6qw=="
|
||||
},
|
||||
"long@5.3.2": {
|
||||
"integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA=="
|
||||
},
|
||||
"matcher@3.0.0": {
|
||||
"integrity": "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==",
|
||||
"dependencies": [
|
||||
"escape-string-regexp"
|
||||
]
|
||||
},
|
||||
"math-intrinsics@1.1.0": {
|
||||
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="
|
||||
},
|
||||
"mime-db@1.52.0": {
|
||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="
|
||||
},
|
||||
"mime-types@2.1.35": {
|
||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||
"dependencies": [
|
||||
"mime-db"
|
||||
]
|
||||
},
|
||||
"minipass@7.1.2": {
|
||||
"integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="
|
||||
},
|
||||
"minizlib@3.0.2": {
|
||||
"integrity": "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==",
|
||||
"dependencies": [
|
||||
"minipass"
|
||||
]
|
||||
},
|
||||
"mkdirp@3.0.1": {
|
||||
"integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg=="
|
||||
},
|
||||
"ms@2.1.3": {
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
|
||||
},
|
||||
"node-domexception@1.0.0": {
|
||||
"integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="
|
||||
},
|
||||
"node-fetch@2.7.0": {
|
||||
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
|
||||
"dependencies": [
|
||||
"whatwg-url"
|
||||
]
|
||||
},
|
||||
"object-keys@1.1.1": {
|
||||
"integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA=="
|
||||
},
|
||||
"onnxruntime-common@1.21.0": {
|
||||
"integrity": "sha512-Q632iLLrtCAVOTO65dh2+mNbQir/QNTVBG3h/QdZBpns7mZ0RYbLRBgGABPbpU9351AgYy7SJf1WaeVwMrBFPQ=="
|
||||
},
|
||||
"onnxruntime-common@1.22.0-dev.20250409-89f8206ba4": {
|
||||
"integrity": "sha512-vDJMkfCfb0b1A836rgHj+ORuZf4B4+cc2bASQtpeoJLueuFc5DuYwjIZUBrSvx/fO5IrLjLz+oTrB3pcGlhovQ=="
|
||||
},
|
||||
"onnxruntime-node@1.21.0": {
|
||||
"integrity": "sha512-NeaCX6WW2L8cRCSqy3bInlo5ojjQqu2fD3D+9W5qb5irwxhEyWKXeH2vZ8W9r6VxaMPUan+4/7NDwZMtouZxEw==",
|
||||
"dependencies": [
|
||||
"global-agent",
|
||||
"onnxruntime-common@1.21.0",
|
||||
"tar"
|
||||
]
|
||||
},
|
||||
"onnxruntime-web@1.22.0-dev.20250409-89f8206ba4": {
|
||||
"integrity": "sha512-0uS76OPgH0hWCPrFKlL8kYVV7ckM7t/36HfbgoFw6Nd0CZVVbQC4PkrR8mBX8LtNUFZO25IQBqV2Hx2ho3FlbQ==",
|
||||
"dependencies": [
|
||||
"flatbuffers",
|
||||
"guid-typescript",
|
||||
"long",
|
||||
"onnxruntime-common@1.22.0-dev.20250409-89f8206ba4",
|
||||
"platform",
|
||||
"protobufjs"
|
||||
]
|
||||
},
|
||||
"openai@4.97.0": {
|
||||
"integrity": "sha512-LRoiy0zvEf819ZUEJhgfV8PfsE8G5WpQi4AwA1uCV8SKvvtXQkoWUFkepD6plqyJQRghy2+AEPQ07FrJFKHZ9Q==",
|
||||
"dependencies": [
|
||||
"@types/node@18.19.87",
|
||||
"@types/node-fetch",
|
||||
"abort-controller",
|
||||
"agentkeepalive",
|
||||
"form-data-encoder",
|
||||
"formdata-node",
|
||||
"node-fetch"
|
||||
]
|
||||
},
|
||||
"p-limit@6.2.0": {
|
||||
"integrity": "sha512-kuUqqHNUqoIWp/c467RI4X6mmyuojY5jGutNU0wVTmEOOfcuwLqyMVoAi9MKi2Ak+5i9+nhmrK4ufZE8069kHA==",
|
||||
"dependencies": [
|
||||
"yocto-queue"
|
||||
]
|
||||
},
|
||||
"phonemizer@1.2.1": {
|
||||
"integrity": "sha512-v0KJ4mi2T4Q7eJQ0W15Xd4G9k4kICSXE8bpDeJ8jisL4RyJhNWsweKTOi88QXFc4r4LZlz5jVL5lCHhkpdT71A=="
|
||||
},
|
||||
"platform@1.3.6": {
|
||||
"integrity": "sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg=="
|
||||
},
|
||||
"protobufjs@7.5.0": {
|
||||
"integrity": "sha512-Z2E/kOY1QjoMlCytmexzYfDm/w5fKAiRwpSzGtdnXW1zC88Z2yXazHHrOtwCzn+7wSxyE8PYM4rvVcMphF9sOA==",
|
||||
"dependencies": [
|
||||
"@protobufjs/aspromise",
|
||||
"@protobufjs/base64",
|
||||
"@protobufjs/codegen",
|
||||
"@protobufjs/eventemitter",
|
||||
"@protobufjs/fetch",
|
||||
"@protobufjs/float",
|
||||
"@protobufjs/inquire",
|
||||
"@protobufjs/path",
|
||||
"@protobufjs/pool",
|
||||
"@protobufjs/utf8",
|
||||
"@types/node@22.12.0",
|
||||
"long"
|
||||
]
|
||||
},
|
||||
"roarr@2.15.4": {
|
||||
"integrity": "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==",
|
||||
"dependencies": [
|
||||
"boolean",
|
||||
"detect-node",
|
||||
"globalthis",
|
||||
"json-stringify-safe",
|
||||
"semver-compare",
|
||||
"sprintf-js"
|
||||
]
|
||||
},
|
||||
"semver-compare@1.0.0": {
|
||||
"integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow=="
|
||||
},
|
||||
"semver@7.7.1": {
|
||||
"integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA=="
|
||||
},
|
||||
"serialize-error@7.0.1": {
|
||||
"integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==",
|
||||
"dependencies": [
|
||||
"type-fest"
|
||||
]
|
||||
},
|
||||
"sharp@0.34.1": {
|
||||
"integrity": "sha512-1j0w61+eVxu7DawFJtnfYcvSv6qPFvfTaqzTQ2BLknVhHTwGS8sc63ZBF4rzkWMBVKybo4S5OBtDdZahh2A1xg==",
|
||||
"dependencies": [
|
||||
"@img/sharp-darwin-arm64",
|
||||
"@img/sharp-darwin-x64",
|
||||
"@img/sharp-libvips-darwin-arm64",
|
||||
"@img/sharp-libvips-darwin-x64",
|
||||
"@img/sharp-libvips-linux-arm",
|
||||
"@img/sharp-libvips-linux-arm64",
|
||||
"@img/sharp-libvips-linux-ppc64",
|
||||
"@img/sharp-libvips-linux-s390x",
|
||||
"@img/sharp-libvips-linux-x64",
|
||||
"@img/sharp-libvips-linuxmusl-arm64",
|
||||
"@img/sharp-libvips-linuxmusl-x64",
|
||||
"@img/sharp-linux-arm",
|
||||
"@img/sharp-linux-arm64",
|
||||
"@img/sharp-linux-s390x",
|
||||
"@img/sharp-linux-x64",
|
||||
"@img/sharp-linuxmusl-arm64",
|
||||
"@img/sharp-linuxmusl-x64",
|
||||
"@img/sharp-wasm32",
|
||||
"@img/sharp-win32-ia32",
|
||||
"@img/sharp-win32-x64",
|
||||
"color",
|
||||
"detect-libc",
|
||||
"semver"
|
||||
]
|
||||
},
|
||||
"simple-swizzle@0.2.2": {
|
||||
"integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==",
|
||||
"dependencies": [
|
||||
"is-arrayish"
|
||||
]
|
||||
},
|
||||
"sprintf-js@1.1.3": {
|
||||
"integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA=="
|
||||
},
|
||||
"tar@7.4.3": {
|
||||
"integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==",
|
||||
"dependencies": [
|
||||
"@isaacs/fs-minipass",
|
||||
"chownr",
|
||||
"minipass",
|
||||
"minizlib",
|
||||
"mkdirp",
|
||||
"yallist"
|
||||
]
|
||||
},
|
||||
"tr46@0.0.3": {
|
||||
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
|
||||
},
|
||||
"tslib@2.8.1": {
|
||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="
|
||||
},
|
||||
"type-fest@0.13.1": {
|
||||
"integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg=="
|
||||
},
|
||||
"undici-types@5.26.5": {
|
||||
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="
|
||||
},
|
||||
"undici-types@6.20.0": {
|
||||
"integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="
|
||||
},
|
||||
"web-streams-polyfill@4.0.0-beta.3": {
|
||||
"integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug=="
|
||||
},
|
||||
"webidl-conversions@3.0.1": {
|
||||
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
|
||||
},
|
||||
"whatwg-url@5.0.0": {
|
||||
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
|
||||
"dependencies": [
|
||||
"tr46",
|
||||
"webidl-conversions"
|
||||
]
|
||||
},
|
||||
"yallist@5.0.0": {
|
||||
"integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="
|
||||
},
|
||||
"yocto-queue@1.2.1": {
|
||||
"integrity": "sha512-AyeEbWOu/TAXdxlV9wmGcR0+yh2j3vYPGOECcIj2S7MkrLyC7ne+oye2BKTItt0ii2PHk4cDy+95+LshzbXnGg=="
|
||||
},
|
||||
"zod-to-json-schema@3.24.5_zod@3.24.4": {
|
||||
"integrity": "sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g==",
|
||||
"dependencies": [
|
||||
"zod"
|
||||
]
|
||||
},
|
||||
"zod@3.24.4": {
|
||||
"integrity": "sha512-OdqJE9UDRPwWsrHjLN2F8bPxvwJBK22EHLWtanu0LSYr5YqzsaaW3RMgmjwr8Rypg5k+meEJdSPXJZXE/yqOMg=="
|
||||
}
|
||||
},
|
||||
"remote": {
|
||||
"https://cdn.jsdelivr.net/npm/@huggingface/transformers@3.5.1/+esm": "fb0a2ba5088bbde44cddff091a0d1844147cd08c75c75985df70672c7151efd5",
|
||||
"https://cdn.jsdelivr.net/npm/kokoro-js@1.2.1/+esm": "cf3368de4ad22103fe3083600aa9a1bb764d12a0795b2994110836fb5b9091e4",
|
||||
"https://cdn.jsdelivr.net/npm/kokoro-js@1.2.1/dist/kokoro.js/+esm": "cf3368de4ad22103fe3083600aa9a1bb764d12a0795b2994110836fb5b9091e4",
|
||||
"https://cdn.jsdelivr.net/npm/kokoro-js@1.2.1/dist/kokoro.web.js": "6067712fe4c43cdb36c762f860a5ab8d96ea1d9c8882466438eb9f3d0d20be8f",
|
||||
"https://cdn.jsdelivr.net/npm/onnxruntime-common/+esm": "54aea20a2aaa80823ca7c15c084008e838fe74b61f3026d7d5e0b0187b6b9fcb",
|
||||
"https://cdn.jsdelivr.net/npm/onnxruntime-web@1.22.0-dev.20250409-89f8206ba4/+esm": "53bf11d80531b341f46d4dc695a3d143d8b4c71d30e43905f9588db9804832db",
|
||||
"https://cdn.jsdelivr.net/npm/phonemizer@1.2.1/+esm": "b8b4327641797c57274be04e4c82a561dd1e63ec7d37bcbb9820f8be1e332696"
|
||||
}
|
||||
}
|
@ -1,72 +0,0 @@
|
||||
// Follow this setup guide to integrate the Deno language server with your editor:
|
||||
// https://deno.land/manual/getting_started/setup_your_environment
|
||||
// This enables autocomplete, go to definition, etc.
|
||||
|
||||
// Import necessary modules
|
||||
import "jsr:@supabase/functions-js/edge-runtime.d.ts";
|
||||
import { KokoroTTS } from "https://cdn.jsdelivr.net/npm/kokoro-js@1.2.1/dist/kokoro.js/+esm";
|
||||
|
||||
console.log("generate-tts function initialized");
|
||||
|
||||
Deno.serve(async (req) => {
|
||||
if (req.method === "OPTIONS") {
|
||||
// Handle preflight requests
|
||||
return new Response(null, {
|
||||
headers: {
|
||||
"Access-Control-Allow-Origin": "*",
|
||||
"Access-Control-Allow-Headers":
|
||||
"authorization, x-client-info, apikey, content-type",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
console.log("Received request for TTS generation");
|
||||
// Parse the incoming request body
|
||||
const { text, voice, index } = await req.json();
|
||||
console.log("Parsed request body:", { text, voice, index });
|
||||
|
||||
// Validate the input
|
||||
if (!text || !voice) {
|
||||
return new Response(
|
||||
JSON.stringify({ error: "Missing required parameters: text or voice" }),
|
||||
{ status: 400, headers: { "Content-Type": "application/json" } }
|
||||
);
|
||||
}
|
||||
|
||||
console.log(`Generating TTS for text: "${text}" with voice: "${voice}"`);
|
||||
|
||||
// Initialize KokoroTTS
|
||||
const model_id = "onnx-community/Kokoro-82M-v1.0-ONNX";
|
||||
const tts = await KokoroTTS.from_pretrained(model_id, {
|
||||
dtype: "fp32",
|
||||
});
|
||||
|
||||
// Generate the speech audio
|
||||
const audio = await tts.generate(text, {
|
||||
voice,
|
||||
});
|
||||
|
||||
const arrayBuffer = await audio.toWav();
|
||||
|
||||
// audioUrl should be the base64 encoded audio blob
|
||||
const audioUrl = `data:audio/wav;base64,${btoa(
|
||||
String.fromCharCode(...new Uint8Array(arrayBuffer))
|
||||
)}`;
|
||||
|
||||
console.log(`TTS generated successfully for index: ${index}`);
|
||||
|
||||
// Return the audio URL
|
||||
return new Response(JSON.stringify({ audioUrl }), {
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error generating TTS:", error);
|
||||
|
||||
// Return an error response
|
||||
return new Response(JSON.stringify({ error: "Failed to generate TTS" }), {
|
||||
status: 500,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
}
|
||||
});
|
@ -1,510 +0,0 @@
|
||||
import "jsr:@supabase/functions-js/edge-runtime.d.ts";
|
||||
import { createClient } from "jsr:@supabase/supabase-js@2";
|
||||
import { Mistral } from "npm:@mistralai/mistralai";
|
||||
import pLimit from "npm:p-limit";
|
||||
export const corsHeaders = {
|
||||
"Access-Control-Allow-Origin": "*",
|
||||
"Access-Control-Allow-Headers":
|
||||
"authorization, x-client-info, apikey, content-type",
|
||||
};
|
||||
const apiKey = Deno.env.get("MISTRAL_API_KEY");
|
||||
const client = new Mistral({
|
||||
apiKey: apiKey,
|
||||
});
|
||||
const PROCESSING_PROMPT = `
|
||||
You are a document processing AI. Your task is to process the Markdown text scanned from a document page and return it in a clean and structured format.
|
||||
|
||||
The textual page data should only be returned in valid Markdown format. Use proper headings and subheadings to structure the content. **Do not add headings if they do not exist in the original text.** If there is a title to the document, it should be the first heading.
|
||||
Any images should be included.
|
||||
Do not return the Markdown as a code block, only as a raw string, without any new lines.
|
||||
|
||||
No data or information should ever be removed, it should only be processed and formatted.
|
||||
|
||||
There are in-text citations/references in the text, remove them from the text (**but most importantly, keep the reference number in the text. use a <sup></sup> tag**) and put them into an object where the key is the reference number and the value is the text.
|
||||
|
||||
The Markdown should be human-readable and well-formatted. The markdown string should properly sanitized and should not break a JSON parser when returned as the final format.
|
||||
|
||||
Return the final result as a text object with the following structure (without code block formatting):
|
||||
|
||||
"""
|
||||
<processed markdown text>
|
||||
|
||||
---------
|
||||
|
||||
{
|
||||
"citations": [
|
||||
{
|
||||
"number": 1, // The number as it appears in the text
|
||||
"text": "Citation text 1" // Ensure any JSON-breaking characters are properly escaped
|
||||
},
|
||||
{
|
||||
"number": 2,
|
||||
"text": "Citation text 2"
|
||||
}
|
||||
]
|
||||
}
|
||||
"""
|
||||
|
||||
Do not return the text object as a code block, only as a raw string.
|
||||
`;
|
||||
Deno.serve(async (req) => {
|
||||
if (req.method === "OPTIONS") {
|
||||
console.log("Handling OPTIONS request...");
|
||||
return new Response(null, {
|
||||
headers: {
|
||||
...corsHeaders,
|
||||
"Access-Control-Allow-Methods": "POST, OPTIONS",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (req.method === "POST") {
|
||||
console.log("Processing POST request...");
|
||||
const { body, writable } = new TransformStream();
|
||||
const writer = writable.getWriter();
|
||||
|
||||
// Set up the SSE response
|
||||
const headers = new Headers({
|
||||
"Content-Type": "text/event-stream",
|
||||
"Cache-Control": "no-cache",
|
||||
Connection: "keep-alive",
|
||||
...corsHeaders,
|
||||
});
|
||||
|
||||
let activeOperations = 0; // Track active operations
|
||||
let streamClosed = false; // Track if the stream is closed
|
||||
|
||||
const sendEvent = async (event, data) => {
|
||||
if (streamClosed) {
|
||||
console.warn("Attempted to write to a closed stream.");
|
||||
return;
|
||||
}
|
||||
const message = `event: ${event}\ndata: ${JSON.stringify(data)}\n\n`;
|
||||
console.log("Sending event:", message);
|
||||
try {
|
||||
activeOperations++;
|
||||
await writer.write(new TextEncoder().encode(message));
|
||||
} catch (error) {
|
||||
console.error("Error writing to stream:", error);
|
||||
} finally {
|
||||
activeOperations--;
|
||||
}
|
||||
};
|
||||
|
||||
// Start streaming updates
|
||||
sendEvent("status", {
|
||||
message: "Initializing...",
|
||||
});
|
||||
|
||||
try {
|
||||
const supabase = createClient(
|
||||
Deno.env.get("SUPABASE_URL"),
|
||||
Deno.env.get("SUPABASE_ANON_KEY")
|
||||
);
|
||||
|
||||
const supabaseServer = createClient(
|
||||
Deno.env.get("SUPABASE_URL"),
|
||||
Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")
|
||||
);
|
||||
|
||||
const formData = await req.formData();
|
||||
const accessToken = formData.get("access_token");
|
||||
const refreshToken = formData.get("refresh_token");
|
||||
var reprocessing = false;
|
||||
var uuid = crypto.randomUUID();
|
||||
|
||||
const {
|
||||
data: { user },
|
||||
error: sessionError,
|
||||
} = await supabase.auth.setSession({
|
||||
access_token: accessToken,
|
||||
refresh_token: refreshToken,
|
||||
});
|
||||
|
||||
if (sessionError) {
|
||||
console.error("Error setting session:", sessionError);
|
||||
sendEvent("error", {
|
||||
message: "Error setting session",
|
||||
error: sessionError,
|
||||
});
|
||||
throw new Error("Setting session failed");
|
||||
}
|
||||
|
||||
if (formData.has("id")) {
|
||||
console.log("Reprocessing document...");
|
||||
reprocessing = true;
|
||||
console.log("File ID found in form data.");
|
||||
sendEvent("status", {
|
||||
message: "File ID found in form data.",
|
||||
});
|
||||
const docId = formData.get("id");
|
||||
console.log("Document ID:", docId, formData);
|
||||
const { data: documentData, error: documentError } = await supabase
|
||||
.from("documents")
|
||||
.select("*")
|
||||
.eq("id", docId)
|
||||
.single();
|
||||
|
||||
if (documentError) {
|
||||
console.error("Error fetching document record:", documentError);
|
||||
sendEvent("error", {
|
||||
message: "Error fetching document record",
|
||||
error: documentError,
|
||||
});
|
||||
throw new Error("Document record fetch failed");
|
||||
}
|
||||
|
||||
if (documentData) {
|
||||
await supabase
|
||||
.from("documents")
|
||||
.update({
|
||||
is_processing: true,
|
||||
})
|
||||
.eq("id", documentData.id);
|
||||
uuid = documentData.id;
|
||||
} else {
|
||||
console.error("Document record not found.");
|
||||
sendEvent("error", {
|
||||
message: "Document record not found",
|
||||
});
|
||||
throw new Error("Document record not found");
|
||||
}
|
||||
|
||||
const { data: fileData, error: fileError } = await supabase.storage
|
||||
.from("documents")
|
||||
.download(`${user.id}/${uuid}.pdf`);
|
||||
|
||||
if (fileError) {
|
||||
console.error("Error downloading file from storage:", fileError);
|
||||
sendEvent("error", {
|
||||
message: "Error downloading file from storage",
|
||||
error: fileError,
|
||||
});
|
||||
throw new Error("File download failed");
|
||||
}
|
||||
|
||||
console.log("File downloaded from storage:", fileData);
|
||||
sendEvent("status", {
|
||||
message: "File downloaded from storage",
|
||||
fileData,
|
||||
});
|
||||
|
||||
formData.set("file", fileData);
|
||||
}
|
||||
|
||||
if (!formData.has("file")) {
|
||||
console.error("File not found in form data.");
|
||||
sendEvent("error", {
|
||||
message: "File not found in form data",
|
||||
});
|
||||
throw new Error("File not found");
|
||||
}
|
||||
if (!formData.has("access_token") || !formData.has("refresh_token")) {
|
||||
console.error("Access token or refresh token not found in form data.");
|
||||
sendEvent("error", {
|
||||
message: "Access token or refresh token not found in form data",
|
||||
});
|
||||
throw new Error("Tokens not found");
|
||||
}
|
||||
|
||||
const file = formData.get("file") as File;
|
||||
const fileName = file.name;
|
||||
|
||||
console.log("Generated UUID:", uuid);
|
||||
sendEvent("status", {
|
||||
message: "Generated UUID",
|
||||
uuid,
|
||||
});
|
||||
|
||||
console.log("Authenticated user:", user);
|
||||
sendEvent("status", {
|
||||
message: "Authenticated user",
|
||||
user,
|
||||
});
|
||||
|
||||
if (!reprocessing) {
|
||||
const { data: storageData, error: storageError } =
|
||||
await supabase.storage
|
||||
.from("documents")
|
||||
.upload(`${user.id}/${uuid}.pdf`, file);
|
||||
|
||||
if (storageError) {
|
||||
console.error("Error uploading file to storage:", storageError);
|
||||
sendEvent("error", {
|
||||
message: "Error uploading file to storage",
|
||||
error: storageError,
|
||||
});
|
||||
throw new Error("File upload failed");
|
||||
}
|
||||
|
||||
console.log("File uploaded to storage:", storageData);
|
||||
sendEvent("status", {
|
||||
message: "File uploaded to storage",
|
||||
storageData,
|
||||
});
|
||||
|
||||
const { error: docError } = await supabase.from("documents").insert({
|
||||
id: uuid,
|
||||
file_name: file.name,
|
||||
owner: user.id,
|
||||
raw_file: storageData.id,
|
||||
is_processing: true,
|
||||
});
|
||||
|
||||
if (docError) {
|
||||
console.error("Error inserting document record:", docError);
|
||||
sendEvent("error", {
|
||||
message: "Error inserting document record",
|
||||
error: docError,
|
||||
});
|
||||
throw new Error("Document record insertion failed");
|
||||
}
|
||||
|
||||
console.log("Document record inserted successfully.");
|
||||
sendEvent("status", {
|
||||
message: "Document record inserted successfully",
|
||||
});
|
||||
} else {
|
||||
console.log("Reprocessing document...");
|
||||
sendEvent("status", {
|
||||
message: "Reprocessing document",
|
||||
});
|
||||
|
||||
const { error: docError } = await supabase
|
||||
.from("documents")
|
||||
.update({
|
||||
is_processing: true,
|
||||
})
|
||||
.eq("id", uuid);
|
||||
if (docError) {
|
||||
console.error("Error updating document record:", docError);
|
||||
sendEvent("error", {
|
||||
message: "Error updating document record",
|
||||
error: docError,
|
||||
});
|
||||
throw new Error("Document record update failed");
|
||||
}
|
||||
console.log("Document record updated successfully.");
|
||||
sendEvent("status", {
|
||||
message: "Document record updated successfully",
|
||||
});
|
||||
}
|
||||
|
||||
console.log("Uploading file to Mistral...");
|
||||
sendEvent("status", {
|
||||
message: "Uploading file to Mistral...",
|
||||
});
|
||||
|
||||
const uploaded_pdf = await client.files.upload({
|
||||
file: {
|
||||
fileName,
|
||||
content: file,
|
||||
},
|
||||
purpose: "ocr",
|
||||
});
|
||||
|
||||
console.log("File uploaded to Mistral:", uploaded_pdf);
|
||||
sendEvent("status", {
|
||||
message: "File uploaded to Mistral",
|
||||
uploaded_pdf,
|
||||
});
|
||||
|
||||
const signedUrl = await client.files.getSignedUrl({
|
||||
fileId: uploaded_pdf.id,
|
||||
});
|
||||
|
||||
console.log("Generated signed URL:", signedUrl);
|
||||
sendEvent("status", {
|
||||
message: "Generated signed URL",
|
||||
signedUrl,
|
||||
});
|
||||
|
||||
console.log("Processing OCR...");
|
||||
sendEvent("status", {
|
||||
message: "Processing OCR...",
|
||||
});
|
||||
|
||||
const ocrResponse = await client.ocr.process({
|
||||
model: "mistral-ocr-latest",
|
||||
document: {
|
||||
type: "document_url",
|
||||
documentUrl: signedUrl.url,
|
||||
},
|
||||
});
|
||||
|
||||
console.log("OCR response received:", ocrResponse);
|
||||
sendEvent("status", {
|
||||
message: "OCR response received",
|
||||
ocrResponse,
|
||||
});
|
||||
|
||||
const limit = pLimit(2);
|
||||
const promises = [];
|
||||
|
||||
for (const page of ocrResponse.pages) {
|
||||
console.log("Processing page:", page.index);
|
||||
sendEvent("status", {
|
||||
message: `Processing page ${page.index}`,
|
||||
});
|
||||
|
||||
const pagePromise = limit(async () => {
|
||||
console.log(`Processing page ${page.index} with Mistral...`);
|
||||
|
||||
const response = await client.chat.complete({
|
||||
model: "mistral-small-latest",
|
||||
messages: [
|
||||
{
|
||||
role: "system",
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: PROCESSING_PROMPT,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: page.markdown,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
if (!response.choices) {
|
||||
console.error("No choices in response for page:", page.index);
|
||||
sendEvent("error", {
|
||||
message: `No choices in response for page ${page.index}`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("Response received for page:", page.index);
|
||||
sendEvent("status", {
|
||||
message: `Response received for page ${page.index}`,
|
||||
});
|
||||
|
||||
const imageData = {};
|
||||
if (page.images.length > 0) {
|
||||
console.log(
|
||||
`Processing ${page.images.length} images for page ${page.index}...`
|
||||
);
|
||||
sendEvent("status", {
|
||||
message: `Processing images for page ${page.index}`,
|
||||
});
|
||||
for (const img of page.images) {
|
||||
imageData[img.id] = img.imageBase64;
|
||||
}
|
||||
}
|
||||
|
||||
if (response.choices[0].message.content) {
|
||||
// remove any potential code block formatting from the content
|
||||
console.log(
|
||||
`[${page.index}] ${response.choices[0].message.content}`
|
||||
);
|
||||
const split =
|
||||
response.choices[0].message.content.split("---------");
|
||||
|
||||
const content = split[0].trim();
|
||||
const citationsStr = split[1]?.trim() || "{}";
|
||||
console.log(`[${page.index}] Citations: ${citationsStr}`);
|
||||
const citations = JSON.parse(citationsStr).citations || {};
|
||||
|
||||
console.log("Generating Markdown for page:", page.index);
|
||||
sendEvent("status", {
|
||||
message: `Generating Markdown for page ${page.index}`,
|
||||
});
|
||||
const markdown = replaceImagesInMarkdown(content, imageData);
|
||||
|
||||
return {
|
||||
...page,
|
||||
markdown,
|
||||
citations,
|
||||
};
|
||||
} else {
|
||||
console.error("Message content is undefined for page:", page.index);
|
||||
sendEvent("error", {
|
||||
message: `Message content is undefined for page ${page.index}`,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
promises.push(pagePromise);
|
||||
}
|
||||
|
||||
console.log("Waiting for all pages to be processed...");
|
||||
sendEvent("status", {
|
||||
message: "Waiting for all pages to be processed...",
|
||||
});
|
||||
|
||||
const results = await Promise.all(promises);
|
||||
|
||||
console.log("All pages processed. Results:", results);
|
||||
sendEvent("status", {
|
||||
message: "All pages processed",
|
||||
results,
|
||||
});
|
||||
|
||||
const sortedResults = results.sort((a, b) => a.index - b.index);
|
||||
console.log("Sorted results:", sortedResults);
|
||||
sendEvent("status", { message: "Sorted results", sortedResults });
|
||||
|
||||
const { data, error } = await supabase
|
||||
.from("documents")
|
||||
.update({
|
||||
ocr_data: sortedResults,
|
||||
is_processing: false,
|
||||
})
|
||||
.eq("id", uuid);
|
||||
|
||||
if (error) {
|
||||
console.error("Error updating document record:", error);
|
||||
sendEvent("error", {
|
||||
message: "Error updating document record",
|
||||
error,
|
||||
});
|
||||
throw new Error("Document record update failed");
|
||||
}
|
||||
|
||||
console.log("Closing SSE stream...");
|
||||
} catch (error) {
|
||||
console.error("Error during processing:", error);
|
||||
sendEvent("error", {
|
||||
message: "Error during processing",
|
||||
error,
|
||||
});
|
||||
} finally {
|
||||
// Wait for all active operations to complete before closing the stream
|
||||
const interval = setInterval(() => {
|
||||
if (activeOperations === 0) {
|
||||
clearInterval(interval);
|
||||
streamClosed = true;
|
||||
writer.close();
|
||||
}
|
||||
}, 100); // Check every 100ms
|
||||
}
|
||||
|
||||
return new Response(body, {
|
||||
headers,
|
||||
});
|
||||
}
|
||||
|
||||
console.error("Method not allowed:", req.method);
|
||||
return new Response("Method not allowed", {
|
||||
status: 405,
|
||||
});
|
||||
});
|
||||
function replaceImagesInMarkdown(markdownStr, imagesDict) {
|
||||
console.log("Replacing images in Markdown...");
|
||||
for (const [imgName, base64Str] of Object.entries(imagesDict)) {
|
||||
markdownStr = markdownStr.replace(
|
||||
new RegExp(`!\\[${imgName}\\]\\(${imgName}\\)`, "g"),
|
||||
``
|
||||
);
|
||||
}
|
||||
console.log("Image replacement complete.");
|
||||
return markdownStr;
|
||||
}
|
@ -1,7 +1,5 @@
|
||||
import type { Config } from "tailwindcss";
|
||||
|
||||
const config = {
|
||||
darkMode: ["class"],
|
||||
darkMode: "class",
|
||||
content: [
|
||||
"./pages/**/*.{ts,tsx}",
|
||||
"./components/**/*.{ts,tsx}",
|
||||
@ -75,6 +73,6 @@ const config = {
|
||||
},
|
||||
},
|
||||
plugins: [require("tailwindcss-animate")],
|
||||
} satisfies Config;
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
Loading…
x
Reference in New Issue
Block a user