Skip to content

Commit

Permalink
feat: add whishper integration (#62)
Browse files Browse the repository at this point in the history
Fixes #63 

### TL;DR

### What changed?

### Why?
  • Loading branch information
aamirazad authored Jul 9, 2024
1 parent 9c97bca commit fc45379
Show file tree
Hide file tree
Showing 8 changed files with 367 additions and 6 deletions.
42 changes: 41 additions & 1 deletion src/app/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,18 @@
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";

/*
Clerk helpers
___| | ___ _ __| | __
/ __| |/ _ | '__| |/ /
| (__| | __| | | <
\___|_|\___|_| |_|\_\
*/

export async function setUserProperty<K extends keyof UsersTableType>(
propertyName: K,
value: UsersTableType[K],
Expand Down Expand Up @@ -44,6 +52,15 @@ export async function getUserData() {
return userData;
}

/*
Paperless
| _ \ __ _ _ __ ___ _ __| | ___ ___ ___
| |_) / _` | '_ \ / _ | '__| |/ _ / __/ __|
| __| (_| | |_) | __| | | | __\__ \__ \
|_| \__,_| .__/ \___|_| |_|\___|___|___/
|_|
*/

export async function getPaperlessDocuments(query: string) {
const userData = await getUserData();

Expand All @@ -64,3 +81,26 @@ 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/transcriptions`);

const data = (await response.json()) as WhishperRecordingsType;

console.log(data)

return data;
}
6 changes: 4 additions & 2 deletions src/app/paperless/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ function DocumentsPage() {

const paperlessDocumentMap = PaperlessDocuments.data.results;

if (paperlessDocumentMap.length === 0) {
if (!paperlessDocumentMap ?? paperlessDocumentMap.length === 0) {
return <h1 className="text-2xl font-bold">No results!</h1>;
}

Expand Down Expand Up @@ -165,7 +165,9 @@ export default function PaperlessPage() {
</SignedOut>
<SignedIn>
<div className="flex w-full flex-col gap-8">
<DocumentsSearch />
<div className="flex w-full justify-center">
<DocumentsSearch />
</div>
<div className="w-full">
<QueryClientProvider client={queryClient}>
<DocumentsPage />
Expand Down
69 changes: 69 additions & 0 deletions src/app/settings/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
URL: "",
},
});

if (userData.whishperURL && !isAutofilled) {
form.setValue("URL", userData.whishperURL);
setIsAutofilled(true);
}

async function onSubmit(values: z.infer<typeof formSchema>) {
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 (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="w-64 space-y-4">
<FormField
control={form.control}
name="URL"
render={({ field }) => (
<FormItem>
<FormLabel>Whishper URL</FormLabel>
<FormControl>
<Input type="url" {...field} />
</FormControl>
<FormDescription>Leave empty to disable</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit">Submit</Button>
</form>
</Form>
);
}

interface ProgressIndicatorProps {
activeTab: number;
totalTabs: number;
Expand Down Expand Up @@ -227,6 +291,11 @@ export function Forms() {
setActiveTab={setActiveTab}
userData={userData}
/>,
<WhishperURL
key="paperlessToken"
setActiveTab={setActiveTab}
userData={userData}
/>,
];
return (
<>
Expand Down
203 changes: 203 additions & 0 deletions src/app/whishper/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
"use client";

import { SignedIn, SignedOut } from "@clerk/nextjs";
import { usePathname, useRouter, useSearchParams } from "next/navigation";
import { useForm } from "react-hook-form";
import { array, 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,
QueryClientProvider,
QueryClient,
} from "@tanstack/react-query";
import { getUserData } from "../actions";
import LoadingSpinner from "@/components/loading-spinner";
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();

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;
const lowerCaseQuery = query.toLowerCase();
return data.filter(
(item) =>
item.fileName.toLowerCase().includes(lowerCaseQuery) ||
item.result.text.toLowerCase().includes(lowerCaseQuery),
);
}

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<z.infer<typeof formSchema>>({
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<typeof formSchema>) {
if (values.query)
router.replace(pathname + "?" + createQueryString("query", values.query));
}

return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="w-64 space-y-4">
<FormField
control={form.control}
name="query"
render={({ field }) => (
<FormItem>
<FormLabel>Search for your voice recordings</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit" className="dark:bg-slate-200">
Submit
</Button>
</form>
</Form>
);
}

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 <h1 className="text-2xl font-bold">Start Searching!</h1>;
}

if (!userData.data?.whishperURL) {
return (
<h1 className="text-2xl font-bold">
You need to set your whishper url in{" "}
<Link href="/settings">settings</Link>
</h1>
);
}

if (WhishperRecordings.isLoading || userData.isLoading) {
return <LoadingSpinner>Loading...</LoadingSpinner>;
}

if (!WhishperRecordings.data || WhishperRecordings.error) {
return (
<h1 className="text-2xl font-bold">
Connection failed! Check that the whishper url is set correctly in{" "}
<Link href="/settings">settings</Link>
</h1>
);
}

const WhishperRecordingsMap = WhishperRecordings.data;

if (!WhishperRecordingsMap ?? WhishperRecordingsMap.length === 0) {
return <h1 className="text-2xl font-bold">No results!</h1>;
}

return (
<>
<h1 className="text-2xl font-bold">Search Results</h1>
<ul className="list-disc">
{WhishperRecordingsMap.map((recording, index) => (
<li className="underline" key={index}>
<OpenExternalLInk
className="underline hover:text-slate-300"
href={`${userData.data?.whishperURL}/editor/${recording.id}`}
>
{recording.fileName.split("_WHSHPR_")[1]}
</OpenExternalLInk>
</li>
))}
</ul>
</>
);
}

export default function WhishperPage() {
return (
<main className="">
<div className="flex flex-col items-center justify-center">
<SignedOut>
<div className="text-center text-2xl">
Please <OpenInternalLink href="/sign-in">sign in</OpenInternalLink>
</div>
</SignedOut>
<SignedIn>
<div className="flex w-full flex-col gap-8">
<div className="flex w-full justify-center">
<SearchForm />
</div>
<div className="w-full">
<QueryClientProvider client={queryClient}>
<RecordingsList />
</QueryClientProvider>
</div>
</div>
</SignedIn>
</div>
</main>
);
}
3 changes: 1 addition & 2 deletions src/components/document-viewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
Loading

0 comments on commit fc45379

Please sign in to comment.