Skip to content

Commit

Permalink
add image analysis to note form
Browse files Browse the repository at this point in the history
  • Loading branch information
shawnmclean committed Dec 30, 2024
1 parent 1036234 commit f887614
Show file tree
Hide file tree
Showing 5 changed files with 121 additions and 143 deletions.

This file was deleted.

This file was deleted.

44 changes: 42 additions & 2 deletions apps/sovoli.com/src/app/(dashboard)/new/components/NoteForm.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use client";

import { useActionState } from "react";
import { useActionState, useState } from "react";
import { Alert } from "@sovoli/ui/components/alert";
import { Button } from "@sovoli/ui/components/button";
import { Form } from "@sovoli/ui/components/form";
Expand All @@ -22,8 +22,49 @@ export const NoteForm = ({ title, description, content }: NoteFormProps) => {
null,
);

const [fileUploadStatus, setFileUploadStatus] = useState<
"idle" | "uploading" | "success" | "error"
>("idle");

const handleFileUpload = async (
event: React.ChangeEvent<HTMLInputElement>,
) => {
const file = event.target.files?.[0];
if (!file) return;

setFileUploadStatus("uploading");

try {
const formData = new FormData();
formData.append("image", file);

const response = await fetch("/api/ai/images/analyze", {
method: "POST",
body: formData,
});

if (response.ok) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const data = await response.json();
console.log("File uploaded successfully:", data);
setFileUploadStatus("success");
} else {
console.error("File upload failed:", response.statusText);
setFileUploadStatus("error");
}
} catch (error) {
console.error("Error uploading file:", error);
setFileUploadStatus("error");
}
};

return (
<Form className="w-full" action={formAction}>
{" "}
<input type="file" id="image" name="image" onChange={handleFileUpload} />
{fileUploadStatus === "uploading" && <p>Uploading...</p>}
{fileUploadStatus === "success" && <p>File uploaded successfully!</p>}
{fileUploadStatus === "error" && <p>Failed to upload file.</p>}
<Input
placeholder="Title"
name="title"
Expand All @@ -35,7 +76,6 @@ export const NoteForm = ({ title, description, content }: NoteFormProps) => {
}}
defaultValue={title}
/>

<Input
name="description"
placeholder="Description"
Expand Down
2 changes: 0 additions & 2 deletions apps/sovoli.com/src/app/(dashboard)/new/page.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { HighlightForm } from "./components/HighlightForm";
import { NoteForm } from "./components/NoteForm";

export default function NewPage() {
Expand Down Expand Up @@ -27,7 +26,6 @@ export default function NewPage() {
}`;
return (
<div className="mx-auto max-w-7xl p-4">
<HighlightForm />
<NoteForm
title="Hello World"
description="This is a test"
Expand Down
79 changes: 79 additions & 0 deletions apps/sovoli.com/src/app/api/ai/images/analyze/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import type { NextRequest } from "next/server";
import { google } from "@ai-sdk/google";
import { withZod } from "@rvf/zod";
import { generateObject } from "ai";
import { z } from "zod";

const model = google("gemini-1.5-flash");

const imageFileSchema = z.instanceof(File).refine(
(file) => {
return file.type === "image/png" || file.type === "image/jpeg";
},
{
message: "File must be an image",
},
);

export const formRequestBodySchema = z.object({
image: imageFileSchema,
});

const validator = withZod(formRequestBodySchema);

export async function POST(req: NextRequest): Promise<Response> {
const formData = await req.formData();

const result = await validator.validate(formData);

if (result.error) {
return new Response(JSON.stringify(result.error), {
status: 400,
headers: {
"Content-Type": "application/json",
},
});
}

const image = result.data.image;

const fileBuffer = await image.arrayBuffer();

const { object } = await generateObject({
model: model,
schema: z.object({
page: z.number().optional(),
chapter: z.string().optional(),
highlights: z.string().array(),
}),
messages: [
{
role: "user",
content: [
{
type: "text",
text: `
This is an image of a page from a book.
The chapter is displayed at the top of the page. If you cannot determine page or chapter, leave it.
It should contain highlighted text, which may include a word, a phrase, a sentence, multiple sentences, or a paragraph.
Separate each highlight into distinct entries if there is a natural or logical boundary, such as the end of a sentence or a clear change in context.
If a highlight spans multiple sentences, group them together into one entry only if they are part of the same continuous thought or context, and there is no visible separation.
`,
},
{
type: "image",
image: fileBuffer,
},
],
},
],
});
console.log(object);

return new Response(JSON.stringify(object), {
status: 200,
headers: {
"Content-Type": "application/json",
},
});
}

0 comments on commit f887614

Please sign in to comment.