From 34b97ff9a5e7aab384bcc7148ad1136b2485314b Mon Sep 17 00:00:00 2001 From: Aamir Azad Date: Fri, 5 Jul 2024 21:01:20 -0400 Subject: [PATCH 1/3] feat: add whishper integration --- src/app/actions.ts | 47 +++++++++++ src/app/whishper/page.tsx | 165 ++++++++++++++++++++++++++++++++++++++ src/components/topnav.tsx | 17 ++++ src/server/db/schema.ts | 3 +- 4 files changed, 231 insertions(+), 1 deletion(-) create mode 100644 src/app/whishper/page.tsx diff --git a/src/app/actions.ts b/src/app/actions.ts index 3908076..31b2211 100644 --- a/src/app/actions.ts +++ b/src/app/actions.ts @@ -7,6 +7,14 @@ import type { PaperlessDocumentsType } from "@/types"; import { auth } from "@clerk/nextjs/server"; import type { AdviceAPIType } from "@/types"; +/* +Clerk helpers + ___| | ___ _ __| | __ + / __| |/ _ | '__| |/ / +| (__| | __| | | < + \___|_|\___|_| |_|\_\ +*/ + export async function setUserProperty( propertyName: K, value: UsersTableType[K], @@ -44,6 +52,15 @@ export async function getUserData() { return userData; } +/* +Paperless +| _ \ __ _ _ __ ___ _ __| | ___ ___ ___ +| |_) / _` | '_ \ / _ | '__| |/ _ / __/ __| +| __| (_| | |_) | __| | | | __\__ \__ \ +|_| \__,_| .__/ \___|_| |_|\___|___|___/ + |_| +*/ + export async function getPaperlessDocuments(query: string) { const userData = await getUserData(); @@ -64,3 +81,33 @@ export async function getPaperlessDocuments(query: string) { return data; } + +/* +Whishper + \ \ / (_)___| |__ _ __ ___ _ __ + \ \ /\ / /| / __| '_ \| '_ \ / _ | '__| + \ V V / | \__ | | | | |_) | __| | + \_/\_/ |_|___|_| |_| .__/ \___|_| + |_| +*/ + +export async function getWhishperRecordings(query: string) { + const userData = await getUserData(); + + if (!query || query == "null" || query.length < 3 || !userData) return null; + + const response = await fetch( + `${userData.whishperURL}/api/documents/?query=${query}&page=1&page_size=10&truncate_content=true`, + { + method: "GET", + headers: { + "Content-Type": "application/json", + Authorization: `Token ${userData.paperlessToken}`, + }, + }, + ); + + const data = (await response.json()) as PaperlessDocumentsType; + + return data; +} diff --git a/src/app/whishper/page.tsx b/src/app/whishper/page.tsx new file mode 100644 index 0000000..a669718 --- /dev/null +++ b/src/app/whishper/page.tsx @@ -0,0 +1,165 @@ +"use client"; + +import { SignedIn, SignedOut } from "@clerk/nextjs"; +import { usePathname, useRouter, useSearchParams } from "next/navigation"; +import { useForm } from "react-hook-form"; +import { z } from "zod"; +import { Button } from "@/components/ui/button"; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form"; +import { Input } from "@/components/ui/input"; +import Link from "next/link"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { useCallback } from "react"; +import { useQuery } from "@tanstack/react-query"; +import { getUserData, getWhishperRecordings } from "../actions"; +import LoadingSpinner from "@/components/loading-spinner"; + +function SearchForm() { + const formSchema = z.object({ + query: z.string().min(3).max(256), + }); + const router = useRouter(); + const pathname = usePathname(); + const searchParams = useSearchParams(); + const givenQuery = searchParams.get("query") ?? ""; + const form = useForm>({ + resolver: zodResolver(formSchema), + defaultValues: { + query: givenQuery, + }, + }); + + const createQueryString = useCallback( + (name: string, value: string) => { + const params = new URLSearchParams(searchParams.toString()); + params.set(name, value); + + return params.toString(); + }, + [searchParams], + ); + + function onSubmit(values: z.infer) { + if (values.query) + router.replace(pathname + "?" + createQueryString("query", values.query)); + } + + return ( +
+ + ( + + Search for your voice recordings + + + + + + )} + /> + + + + ); +} + +function RecordingsList() { + const searchParams = useSearchParams(); + const query = searchParams.get("query"); + + const WhishperRecordings = useQuery({ + queryKey: ["key", query], + queryFn: async () => { + if (!query) { + return Promise.resolve(null); + } + const response = await getWhishperRecordings(query); + return response; + }, + // This ensures the query does not run if there's no query string + enabled: !!query, + }); + + const userData = useQuery({ + queryKey: ["userData"], + queryFn: async () => { + const data = await getUserData(); + return data; + }, + }); + + if (!query) { + return

Start Searching!

; + } + + if (WhishperRecordings.isLoading || userData.isLoading) { + return Loading...; + } else if (!userData.data?.whishperURL) { + return ( +

+ You need to set your paperless url in{" "} + settings +

+ ); + } else if (!WhishperRecordings.data || WhishperRecordings.error) { + return ( +

+ Connection failed! Check that the whishper url is set correctly in{" "} + settings +

+ ); + } + + const WhishperRecordingsMap = WhishperRecordings.data.results; + + if (WhishperRecordingsMap.length === 0) { + return

No results!

; + } + + return ( + <> +

Search Results

+
    + {WhishperRecordingsMap.map((recording, index) => ( +
  • + + {recording.title} + +
  • + ))} +
+ + ); +} + +export default function WhishperPage() { + return ( +
+
+ +
+ Please sign in +
+
+ + + +
+
+ ); +} diff --git a/src/components/topnav.tsx b/src/components/topnav.tsx index 15e63f8..7fe365a 100644 --- a/src/components/topnav.tsx +++ b/src/components/topnav.tsx @@ -130,6 +130,13 @@ export function TopNav() { Paperless-ngx + + Whishper + +
@@ -160,6 +167,16 @@ export function TopNav() { Paperless-ngx + + + + Whishper + + + diff --git a/src/server/db/schema.ts b/src/server/db/schema.ts index 5104c6a..022c5e2 100644 --- a/src/server/db/schema.ts +++ b/src/server/db/schema.ts @@ -18,6 +18,7 @@ export const users = createTable("users", { userId: varchar("userId", { length: 256 }).notNull().unique(), paperlessURL: varchar("paperlessURL", { length: 256 }), paperlessToken: varchar("paperlessToken", { length: 256 }), + whishperURL: varchar("whishperURL", { length: 256 }), }); -export type UsersTableType = typeof users.$inferSelect; \ No newline at end of file +export type UsersTableType = typeof users.$inferSelect; From 26ec14e673bbc23e35ed8790f463f4ea977b2b75 Mon Sep 17 00:00:00 2001 From: Aamir Azad Date: Fri, 5 Jul 2024 22:08:36 -0400 Subject: [PATCH 2/3] Whishper works, that was easy --- src/app/actions.ts | 17 +++------- src/app/paperless/page.tsx | 6 ++-- src/app/settings/page.tsx | 69 ++++++++++++++++++++++++++++++++++++++ src/app/whishper/page.tsx | 68 +++++++++++++++++++++++++++---------- src/types/index.ts | 30 +++++++++++++++++ 5 files changed, 159 insertions(+), 31 deletions(-) diff --git a/src/app/actions.ts b/src/app/actions.ts index 31b2211..acac180 100644 --- a/src/app/actions.ts +++ b/src/app/actions.ts @@ -3,7 +3,7 @@ import { db } from "@/server/db"; import type { UsersTableType } from "@/server/db/schema"; import { users } from "@/server/db/schema"; -import type { PaperlessDocumentsType } from "@/types"; +import type { PaperlessDocumentsType, WhishperRecordingsType } from "@/types"; import { auth } from "@clerk/nextjs/server"; import type { AdviceAPIType } from "@/types"; @@ -96,18 +96,11 @@ export async function getWhishperRecordings(query: string) { if (!query || query == "null" || query.length < 3 || !userData) return null; - const response = await fetch( - `${userData.whishperURL}/api/documents/?query=${query}&page=1&page_size=10&truncate_content=true`, - { - method: "GET", - headers: { - "Content-Type": "application/json", - Authorization: `Token ${userData.paperlessToken}`, - }, - }, - ); + const response = await fetch(`${userData.whishperURL}/api/transcriptions`); - const data = (await response.json()) as PaperlessDocumentsType; + const data = (await response.json()) as WhishperRecordingsType; + + console.log(data) return data; } diff --git a/src/app/paperless/page.tsx b/src/app/paperless/page.tsx index fa68ac5..bcdb724 100644 --- a/src/app/paperless/page.tsx +++ b/src/app/paperless/page.tsx @@ -131,7 +131,7 @@ function DocumentsPage() { const paperlessDocumentMap = PaperlessDocuments.data.results; - if (paperlessDocumentMap.length === 0) { + if (!paperlessDocumentMap ?? paperlessDocumentMap.length === 0) { return

No results!

; } @@ -165,7 +165,9 @@ export default function PaperlessPage() {
- +
+ +
diff --git a/src/app/settings/page.tsx b/src/app/settings/page.tsx index c31b715..756bf29 100644 --- a/src/app/settings/page.tsx +++ b/src/app/settings/page.tsx @@ -171,6 +171,70 @@ function PaperlessToken({ setActiveTab, userData }: FormProps) { ); } +function WhishperURL({ setActiveTab, userData }: FormProps) { + const [isAutofilled, setIsAutofilled] = useState(false); + const formSchema = z.object({ + URL: z.string(), + }); + + const form = useForm>({ + resolver: zodResolver(formSchema), + defaultValues: { + URL: "", + }, + }); + + if (userData.whishperURL && !isAutofilled) { + form.setValue("URL", userData.whishperURL); + setIsAutofilled(true); + } + + async function onSubmit(values: z.infer) { + if (values.URL == "") { + setActiveTab((prevTab) => prevTab + 2); // Skip api key form + } else { + setActiveTab((prevTab) => prevTab + 1); // Increment activeTab + } + try { + await setUserProperty("whishperURL", values.URL); + // Operation succeeded, show success toast + toast("Your whishper URL preferences was saved"); + // Optionally, move to a new tab or take another action to indicate success + } catch { + // Operation failed, show error toast + toast("Uh oh! Something went wrong.", { + description: "Your whishper URL preferences were not saved.", + action: { + label: "Go back", + onClick: () => setActiveTab((prevTab) => prevTab - 1), // Go back to try again + }, + }); + } + } + + return ( +
+ + ( + + Whishper URL + + + + Leave empty to disable + + + )} + /> + + + + ); +} + interface ProgressIndicatorProps { activeTab: number; totalTabs: number; @@ -227,6 +291,11 @@ export function Forms() { setActiveTab={setActiveTab} userData={userData} />, + , ]; return ( <> diff --git a/src/app/whishper/page.tsx b/src/app/whishper/page.tsx index a669718..401095a 100644 --- a/src/app/whishper/page.tsx +++ b/src/app/whishper/page.tsx @@ -17,9 +17,30 @@ import { Input } from "@/components/ui/input"; import Link from "next/link"; import { zodResolver } from "@hookform/resolvers/zod"; import { useCallback } from "react"; -import { useQuery } from "@tanstack/react-query"; -import { getUserData, getWhishperRecordings } from "../actions"; +import { + useQuery, + QueryClientProvider, + QueryClient, +} from "@tanstack/react-query"; +import { getUserData } from "../actions"; import LoadingSpinner from "@/components/loading-spinner"; +import { WhishperRecordingsType } from "@/types"; + +async function getWhishperRecordings(query: string) { + const userData = await getUserData(); + + if (!query || query == "null" || query.length < 3 || !userData) return null; + + const response = await fetch(`${userData.whishperURL}/api/transcriptions`); + + const data = (await response.json()) as WhishperRecordingsType; + + console.log(data); + + return data; +} + +const queryClient = new QueryClient(); function SearchForm() { const formSchema = z.object({ @@ -100,20 +121,24 @@ function RecordingsList() { }, }); + if (!userData.data?.whishperURL) { + return ( +

+ You need to set your whishper url in{" "} + settings +

+ ); + } + if (!query) { return

Start Searching!

; } if (WhishperRecordings.isLoading || userData.isLoading) { return Loading...; - } else if (!userData.data?.whishperURL) { - return ( -

- You need to set your paperless url in{" "} - settings -

- ); - } else if (!WhishperRecordings.data || WhishperRecordings.error) { + } + + if (!WhishperRecordings.data || WhishperRecordings.error) { return (

Connection failed! Check that the whishper url is set correctly in{" "} @@ -122,9 +147,9 @@ function RecordingsList() { ); } - const WhishperRecordingsMap = WhishperRecordings.data.results; + const WhishperRecordingsMap = WhishperRecordings.data; - if (WhishperRecordingsMap.length === 0) { + if (!WhishperRecordingsMap ?? WhishperRecordingsMap.length === 0) { return

No results!

; } @@ -134,12 +159,12 @@ function RecordingsList() {
    {WhishperRecordingsMap.map((recording, index) => (
  • - - {recording.title} - + {recording.fileName.split("_WHSHPR_")[1]} +
  • ))}
@@ -157,7 +182,16 @@ export default function WhishperPage() {
- +
+
+ +
+
+ + + +
+
diff --git a/src/types/index.ts b/src/types/index.ts index ed86760..bd1bb75 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -68,6 +68,36 @@ export type PaperlessDocumentsType = { }[]; }; +export type WhishperRecordingsType = { + id: string; + status: number; + language: string; + modelSize: string; + task: string; + device: string; + fileName: string; + sourceUrl: string; + result: { + language: string; + duration: number; + segments: { + end: number; + id: string; + start: number; + score: number; + text: string; + words: { + end: number; + start: number; + word: string; + score: number; + }[]; + }[]; + text: string; + }; + translations: []; +}[]; + export type AdviceAPIType = { slip: { slip_id: number; From 90a2a9c1683305a594f3df02ce1532f27b651b1c Mon Sep 17 00:00:00 2001 From: Aamir Azad Date: Sat, 6 Jul 2024 13:40:19 -0400 Subject: [PATCH 3/3] Start work on whishper search --- src/app/whishper/page.tsx | 36 +++++++++++++++++------------- src/components/document-viewer.tsx | 3 +-- 2 files changed, 21 insertions(+), 18 deletions(-) diff --git a/src/app/whishper/page.tsx b/src/app/whishper/page.tsx index 401095a..a757f36 100644 --- a/src/app/whishper/page.tsx +++ b/src/app/whishper/page.tsx @@ -3,7 +3,7 @@ import { SignedIn, SignedOut } from "@clerk/nextjs"; import { usePathname, useRouter, useSearchParams } from "next/navigation"; import { useForm } from "react-hook-form"; -import { z } from "zod"; +import { array, z } from "zod"; import { Button } from "@/components/ui/button"; import { Form, @@ -24,7 +24,11 @@ import { } from "@tanstack/react-query"; import { getUserData } from "../actions"; import LoadingSpinner from "@/components/loading-spinner"; -import { WhishperRecordingsType } from "@/types"; +import type { WhishperRecordingsType } from "@/types"; +import OpenInternalLink from "@/components/internal-link"; +import OpenExternalLInk from "@/components/external-link"; + +const queryClient = new QueryClient(); async function getWhishperRecordings(query: string) { const userData = await getUserData(); @@ -34,14 +38,14 @@ async function getWhishperRecordings(query: string) { const response = await fetch(`${userData.whishperURL}/api/transcriptions`); const data = (await response.json()) as WhishperRecordingsType; - - console.log(data); - - return data; + const lowerCaseQuery = query.toLowerCase(); + return data.filter( + (item) => + item.fileName.toLowerCase().includes(lowerCaseQuery) || + item.result.text.toLowerCase().includes(lowerCaseQuery), + ); } -const queryClient = new QueryClient(); - function SearchForm() { const formSchema = z.object({ query: z.string().min(3).max(256), @@ -121,6 +125,10 @@ function RecordingsList() { }, }); + if (!query) { + return

Start Searching!

; + } + if (!userData.data?.whishperURL) { return (

@@ -130,10 +138,6 @@ function RecordingsList() { ); } - if (!query) { - return

Start Searching!

; - } - if (WhishperRecordings.isLoading || userData.isLoading) { return Loading...; } @@ -159,12 +163,12 @@ function RecordingsList() { @@ -177,8 +181,8 @@ export default function WhishperPage() {
-
- Please sign in +
+ Please sign in
diff --git a/src/components/document-viewer.tsx b/src/components/document-viewer.tsx index a86fbf5..d5cfeac 100644 --- a/src/components/document-viewer.tsx +++ b/src/components/document-viewer.tsx @@ -3,12 +3,11 @@ import { useState, useEffect, useRef } from "react"; import { Button } from "./ui/button"; import { useRouter } from "next/navigation"; -import { getAdvice, getUserData } from "@/app/actions"; +import { getUserData } from "@/app/actions"; import { useQuery, QueryClientProvider, QueryClient, - useQueryClient, } from "@tanstack/react-query"; import type { AdviceAPIType } from "@/types"; import OpenInternalLink from "./internal-link";