summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/actions/lang.ts23
-rw-r--r--src/components/Main.tsx128
-rw-r--r--src/components/zoom/Entry.tsx14
-rw-r--r--src/pages.gen.ts3
-rw-r--r--src/pages/api/formdata/[slug].ts50
-rw-r--r--src/pages/api/nlp.ts32
-rw-r--r--src/pages/api/proxy.ts23
-rw-r--r--src/pages/form.tsx27
8 files changed, 221 insertions, 79 deletions
diff --git a/src/actions/lang.ts b/src/actions/lang.ts
index d54a220..5242c7a 100644
--- a/src/actions/lang.ts
+++ b/src/actions/lang.ts
@@ -1,12 +1,17 @@
"use server";
import { AsyncRes } from "@/lib/types";
-import db from "../lib/db";
+import { NLP } from "sortug-ai";
+// import db from "../lib/db";
-export async function spacy(text: string, lang: string): AsyncRes<any> {
- const res = await call(formData, true);
- console.log("reg res", res);
- if ("error" in res) return { error: "Something went wrong" };
- else {
- return { success: true };
- }
-}
+// export async function textAction(
+// text: string,
+// lang: string,
+// ): AsyncRes<NLP.Spacy.SpacyRes> {
+// const res = await NLP.Spacy.run(text, lang);
+// return res;
+// }
+
+// export async function ocrAction(file: File): AsyncRes<string[]> {
+// const res = await NLP.ocr(file);
+// return res;
+// }
diff --git a/src/components/Main.tsx b/src/components/Main.tsx
index fc587e2..2157a91 100644
--- a/src/components/Main.tsx
+++ b/src/components/Main.tsx
@@ -24,10 +24,8 @@ const SorlangPage: React.FC = () => {
const [textValue, setTextValue] = useState<string>("");
const [pastedImageUrl, setPastedImageUrl] = useState<string | null>(null);
const [pastedImageFile, setPastedImageFile] = useState<File | null>(null); // Store the file for extraction
+ const [isAnalyzing, setIsAnalyzing] = useState<boolean>(false);
const [isExtracting, setIsExtracting] = useState<boolean>(false);
- const [extractedTextResult, setExtractedTextResult] = useState<string | null>(
- null,
- );
const textareaRef = useRef<HTMLTextAreaElement>(null);
@@ -41,15 +39,13 @@ const SorlangPage: React.FC = () => {
}, [pastedImageUrl]);
const handlePaste = useCallback(
- (event: React.ClipboardEvent<HTMLTextAreaElement>) => {
+ (event: ClipboardEvent) => {
const items = event.clipboardData?.items;
console.log({ items });
if (!items) return;
let imageFound = false;
- for (let i = 0; i < items.length; i++) {
- const item = items[i];
- if (!item) return;
+ for (const item of items) {
if (item.kind === "file" && item.type.startsWith("image/")) {
event.preventDefault(); // Prevent pasting image data as text
const file = item.getAsFile();
@@ -60,90 +56,85 @@ const SorlangPage: React.FC = () => {
const newImageUrl = URL.createObjectURL(file);
setPastedImageUrl(newImageUrl);
setPastedImageFile(file);
- setTextValue(""); // Clear textarea when image is pasted, or decide on desired behavior
- setExtractedTextResult(null); // Clear previous extraction results
imageFound = true;
}
break; // Handle first image found
}
}
- // If no image was found, let the default text paste happen
- // Or, if you want to explicitly handle text paste:
- if (!imageFound) {
- // Let the default textarea paste handle it, or:
- // event.preventDefault();
- // const text = event.clipboardData.getData('text/plain');
- // setTextValue(prev => prev + text); // Or replace, depending on desired behavior
- // setPastedImageUrl(null); // Clear image if text is pasted
- // setPastedImageFile(null);
- }
+ // // If no image was found, let the default text paste happen
+ // // Or, if you want to explicitly handle text paste:
+ // if (!imageFound) {
+ // // Let the default textarea paste handle it, or:
+ // // event.preventDefault();
+ // // const text = event.clipboardData.getData('text/plain');
+ // // setTextValue(prev => prev + text); // Or replace, depending on desired behavior
+ // // setPastedImageUrl(null); // Clear image if text is pasted
+ // // setPastedImageFile(null);
+ // }
},
[pastedImageUrl],
);
+ useEffect(() => {
+ window.addEventListener("paste", handlePaste);
+ return () => {
+ window.removeEventListener("paste", handlePaste);
+ };
+ }, [handlePaste]);
- const handleProcessText = () => {
- if (!textValue.trim()) {
+ const handleProcessText = async () => {
+ setIsAnalyzing(true);
+ const text = textValue.trim();
+ if (!text) {
alert("Text area is empty!");
return;
}
- console.log("Processing text:", textValue);
- // Add your text processing logic here
- alert(
- `Text submitted: "${textValue.substring(0, 50)}${textValue.length > 50 ? "..." : ""}"`,
- );
+ const opts = {
+ method: "POST",
+ headers: { "Content-type": "application/json" },
+ body: JSON.stringify({ text, app: "spacy" }),
+ };
+ const res = await fetch("/api/nlp", opts);
+ const j = await res.json();
+ console.log("j", j);
+ if ("ok" in j) {
+ console.log("good");
+ }
+ setIsAnalyzing(false);
};
- const [isPending, startTransition] = useTransition();
- const onClick = () => {
- startTransition(async () => {
- const lol = "lmao";
- });
- };
+ // const [isPending, startTransition] = useTransition();
const handleExtractTextFromImage = async () => {
if (!pastedImageFile) {
alert("No image to extract text from!");
return;
}
setIsExtracting(true);
- setExtractedTextResult(null);
+ const formData = new FormData();
+ formData.append("file", pastedImageFile, pastedImageFile.name);
console.log("Extracting text from image:", pastedImageFile.name);
-
- // --- SIMULATE OCR API CALL ---
- // In a real app, you would send `pastedImageFile` to a backend
- // or use a client-side OCR library like Tesseract.js
- await new Promise((resolve) => setTimeout(resolve, 2000)); // Simulate network delay
-
- // Example: Simulate successful extraction
- const mockExtractedText = `This is simulated extracted text from "${pastedImageFile.name}".\nIt could be multiple lines.`;
-
- // Example: Simulate an error
- // const mockExtractedText = null;
- // alert("Failed to extract text (simulated).");
-
- if (mockExtractedText) {
- setTextValue(mockExtractedText); // Put extracted text into the textarea
- setExtractedTextResult(
- `Successfully extracted text and placed it in the textarea.`,
- );
- } else {
- setExtractedTextResult("Failed to extract text (simulated).");
+ const res = await fetch("/api/formdata/ocr", {
+ method: "POST",
+ body: formData,
+ });
+ const j = await res.json();
+ console.log("ocr res", j);
+ if ("ok" in j) {
+ setTextValue((t) => t + j.ok.join("\n"));
}
- // --- END SIMULATION ---
-
setIsExtracting(false);
- // Optionally clear the image after attempting extraction
- // setPastedImageUrl(null);
- // setPastedImageFile(null);
+ // handleClearImage();
};
+ // setPastedImageUrl(null);
+ // setPastedImageFile(null);
+
const handleClearImage = () => {
if (pastedImageUrl) {
URL.revokeObjectURL(pastedImageUrl);
}
setPastedImageUrl(null);
setPastedImageFile(null);
- setExtractedTextResult(null);
};
return (
@@ -159,9 +150,8 @@ const SorlangPage: React.FC = () => {
ref={textareaRef}
value={textValue}
onChange={(e) => setTextValue(e.target.value)}
- onPaste={handlePaste}
placeholder="Paste text here, or paste an image..."
- className="min-h-[200px] text-base"
+ className="min-h-[200px] w-full text-base"
aria-label="Input text area"
/>
@@ -185,14 +175,6 @@ const SorlangPage: React.FC = () => {
/>
</div>
)}
-
- {extractedTextResult && (
- <p
- className={`mt-2 text-sm ${extractedTextResult.startsWith("Failed") ? "text-destructive" : "text-green-600"}`}
- >
- {extractedTextResult}
- </p>
- )}
</CardContent>
<CardFooter className="flex flex-col sm:flex-row justify-center gap-4">
{pastedImageUrl ? (
@@ -212,7 +194,13 @@ const SorlangPage: React.FC = () => {
</Button>
) : (
<Button onClick={handleProcessText} className="w-full sm:w-auto">
- Process Text
+ {isAnalyzing ? (
+ <>
+ <Loader2 className="mr-2 h-4 w-4 animate-spin" /> Analyzing...
+ </>
+ ) : (
+ "Process Text"
+ )}
</Button>
)}
</CardFooter>
diff --git a/src/components/zoom/Entry.tsx b/src/components/zoom/Entry.tsx
new file mode 100644
index 0000000..a60c75c
--- /dev/null
+++ b/src/components/zoom/Entry.tsx
@@ -0,0 +1,14 @@
+"use client";
+import { Zoom } from "prosody-ui";
+import { NLP } from "sortug-ai";
+
+type Props = { text: string; doc: NLP.Spacy.SpacyRes };
+function ZoomText(props: Props) {
+ return (
+ <div>
+ <Zoom.FullText {...props} />
+ </div>
+ );
+}
+
+export default ZoomText;
diff --git a/src/pages.gen.ts b/src/pages.gen.ts
index 94e0a4a..9e2fcfb 100644
--- a/src/pages.gen.ts
+++ b/src/pages.gen.ts
@@ -8,6 +8,8 @@ import type { getConfig as Login_getConfig } from './pages/login';
// prettier-ignore
import type { getConfig as Db_getConfig } from './pages/db';
// prettier-ignore
+import type { getConfig as Form_getConfig } from './pages/form';
+// prettier-ignore
import type { getConfig as About_getConfig } from './pages/about';
// prettier-ignore
import type { getConfig as Index_getConfig } from './pages/index';
@@ -16,6 +18,7 @@ import type { getConfig as Index_getConfig } from './pages/index';
type Page =
| ({ path: '/login' } & GetConfigResponse<typeof Login_getConfig>)
| ({ path: '/db' } & GetConfigResponse<typeof Db_getConfig>)
+| ({ path: '/form' } & GetConfigResponse<typeof Form_getConfig>)
| ({ path: '/about' } & GetConfigResponse<typeof About_getConfig>)
| ({ path: '/' } & GetConfigResponse<typeof Index_getConfig>);
diff --git a/src/pages/api/formdata/[slug].ts b/src/pages/api/formdata/[slug].ts
new file mode 100644
index 0000000..3580e6a
--- /dev/null
+++ b/src/pages/api/formdata/[slug].ts
@@ -0,0 +1,50 @@
+// import db from "../../lib/db";
+import { NLP } from "sortug-ai";
+
+type Req = { endpoint: "nlp" | "llm"; app: string; body: any };
+export const POST = async (request: Request): Promise<Response> => {
+ const url = URL.parse(request.url);
+ const path = url!.pathname.replace("/api/formdata", "");
+ const body = await request.formData();
+ if (path === "/ocr") return postOCR(body);
+ // TODO audio etc. a lot of stuff goes through here
+
+ // if (!body.name || !body.creds) {
+ return Response.json({ message: "Invalid" }, { status: 400 });
+ // }
+
+ // try {
+ // const res = db.loginUser(body.name, body.creds);
+ // console.log({ res });
+
+ // return Response.json(res, { status: 200 });
+ // } catch (error) {
+ // return Response.json({ message: "Failure" }, { status: 500 });
+ // }
+};
+export const GET = async (request: Request): Promise<Response> => {
+ console.log({ request });
+
+ // if (!body.name || !body.creds) {
+ return Response.json({ message: "Invalid" }, { status: 400 });
+ // }
+
+ // try {
+ // const res = db.loginUser(body.name, body.creds);
+ // console.log({ res });
+
+ // return Response.json(res, { status: 200 });
+ // } catch (error) {
+ // return Response.json({ message: "Failure" }, { status: 500 });
+ // }
+};
+
+async function postOCR(formData: FormData) {
+ try {
+ const res = await NLP.ocr(formData);
+ console.log({ res });
+ return Response.json(res, { status: 200 });
+ } catch (error) {
+ return Response.json({ message: "Failure" }, { status: 500 });
+ }
+}
diff --git a/src/pages/api/nlp.ts b/src/pages/api/nlp.ts
new file mode 100644
index 0000000..0e5eacb
--- /dev/null
+++ b/src/pages/api/nlp.ts
@@ -0,0 +1,32 @@
+// import db from "../../lib/db";
+import { z } from "zod";
+import { NLP } from "sortug-ai";
+
+const schema = z.object({
+ app: z.enum(["stanza", "spacy"]),
+ text: z.string().min(3, "minimum 3 characters"),
+ lang: z
+ .custom<string>((val) => {
+ const check = NLP.ISO.BCP47.parse(val);
+ if (!check.language) return false;
+ const twochars = Object.values(NLP.ISO.iso6393To1);
+ return twochars.includes(check.language);
+ })
+ .optional(),
+});
+
+export const POST = async (request: Request): Promise<Response> => {
+ const bod = await request.json();
+ const { app, text, lang } = await schema.parseAsync(bod);
+
+ try {
+ const res =
+ app === "stanza"
+ ? NLP.Stanza.segmenter(text, lang)
+ : NLP.Spacy.run(text, lang);
+ const r = await res;
+ return Response.json(r, { status: 200 });
+ } catch (error) {
+ return Response.json({ message: "Failure" }, { status: 500 });
+ }
+};
diff --git a/src/pages/api/proxy.ts b/src/pages/api/proxy.ts
new file mode 100644
index 0000000..3114f6b
--- /dev/null
+++ b/src/pages/api/proxy.ts
@@ -0,0 +1,23 @@
+// import db from "../../lib/db";
+import { z } from "zod";
+
+export const proxySchema = z.object({
+ path: z.string().startsWith("/").optional(),
+ url: z.string().url("Invalid urladdress"),
+ body: z.any().optional(),
+ headers: z.record(z.string(), z.string()).optional(),
+});
+
+export const POST = async (request: Request): Promise<Response> => {
+ const bod = await request.json();
+ const parsedBody = await proxySchema.parseAsync(bod);
+
+ try {
+ const res = await fetch(parsedBody.url, parsedBody as any);
+ console.log({ res });
+
+ return Response.json(res, { status: 200 });
+ } catch (error) {
+ return Response.json({ message: "Failure" }, { status: 500 });
+ }
+};
diff --git a/src/pages/form.tsx b/src/pages/form.tsx
new file mode 100644
index 0000000..82ffd99
--- /dev/null
+++ b/src/pages/form.tsx
@@ -0,0 +1,27 @@
+import { Link } from "waku";
+
+import { Counter } from "../components/counter";
+import { getContextData } from "waku/middleware/context";
+import Main from "../components/Main";
+
+export default async function HomePage() {
+ const { user } = getContextData();
+
+ return <Main />;
+}
+
+const getData = async () => {
+ const data = {
+ title: "Waku",
+ headline: "Waku",
+ body: "Hello world!",
+ };
+
+ return data;
+};
+
+export const getConfig = async () => {
+ return {
+ render: "dynamic",
+ } as const;
+};