mrlectus commited on
Commit
0b5c6fd
·
verified ·
1 Parent(s): 0a55035

Upload 60 files

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .env +4 -0
  2. .eslintrc.json +3 -0
  3. .gitignore +40 -0
  4. .gitpod.yml +14 -0
  5. .idea/.gitignore +8 -0
  6. .idea/inspectionProfiles/Project_Default.xml +6 -0
  7. .idea/jsLinters/eslint.xml +6 -0
  8. .idea/modules.xml +8 -0
  9. .idea/prettier.xml +7 -0
  10. .idea/vcs.xml +6 -0
  11. .idea/visio.iml +12 -0
  12. .idea/workspace.xml +64 -0
  13. Dockerfile +69 -0
  14. README.md +36 -12
  15. app/api/image-to-text/route.ts +12 -0
  16. app/api/image/route.ts +25 -0
  17. app/api/route.ts +5 -0
  18. app/api/speech-to-audio/route.ts +16 -0
  19. app/api/text-to-speech-lang/route.ts +10 -0
  20. app/api/text-to-speech/route.ts +15 -0
  21. app/api/translator/route.ts +15 -0
  22. app/constant/index.ts +41 -0
  23. app/favicon.ico +0 -0
  24. app/fonts/GeistMonoVF.woff +0 -0
  25. app/fonts/GeistVF.woff +0 -0
  26. app/globals.css +75 -0
  27. app/layout.tsx +39 -0
  28. app/page.tsx +12 -0
  29. app/providers/asyncprovider.tsx +14 -0
  30. app/providers/wallprovider.tsx +8 -0
  31. app/services/index.ts +99 -0
  32. app/store/index.ts +13 -0
  33. components.json +21 -0
  34. components/AudioPlayback.tsx +36 -0
  35. components/DescriptionDisplay.tsx +19 -0
  36. components/ImageUpload.tsx +130 -0
  37. components/LanguageSelector.tsx +41 -0
  38. components/VisualSearchAssistant.tsx +126 -0
  39. components/footer.tsx +26 -0
  40. components/header.tsx +34 -0
  41. components/ui/button.tsx +57 -0
  42. components/ui/card.tsx +76 -0
  43. components/ui/dialog.tsx +122 -0
  44. components/ui/form.tsx +178 -0
  45. components/ui/input.tsx +22 -0
  46. components/ui/label.tsx +26 -0
  47. components/ui/select.tsx +159 -0
  48. components/ui/toast.tsx +129 -0
  49. components/ui/toaster.tsx +35 -0
  50. hooks/api/index.ts +61 -0
.env ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ AWS_SECRET_ACCESS_KEY=+cGEaSdDgvg/NRNtwVdMarK5RYHMqEwxjvgcOnpD
2
+ AWS_ACCESS_KEY_ID=AKIA4AABJOVKM2P5RNXJ
3
+ AWS_SDK_JS_SUPPRESS_MAINTENANCE_MODE_MESSAGE=1
4
+ AWS_S3_URL=https://lectus-bucket.s3.us-east-1.amazonaws.com/
.eslintrc.json ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ {
2
+ "extends": ["next/core-web-vitals", "next/typescript"]
3
+ }
.gitignore ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2
+
3
+ # dependencies
4
+ /node_modules
5
+ /.pnp
6
+ .pnp.*
7
+ .yarn/*
8
+ !.yarn/patches
9
+ !.yarn/plugins
10
+ !.yarn/releases
11
+ !.yarn/versions
12
+
13
+ # testing
14
+ /coverage
15
+
16
+ # next.js
17
+ /.next/
18
+ /out/
19
+
20
+ # production
21
+ /build
22
+
23
+ # misc
24
+ .DS_Store
25
+ *.pem
26
+
27
+ # debug
28
+ npm-debug.log*
29
+ yarn-debug.log*
30
+ yarn-error.log*
31
+
32
+ # env files (can opt-in for committing if needed)
33
+ .env*
34
+
35
+ # vercel
36
+ .vercel
37
+
38
+ # typescript
39
+ *.tsbuildinfo
40
+ next-env.d.ts
.gitpod.yml ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # This configuration file was automatically generated by Gitpod.
2
+ # Please adjust to your needs (see https://www.gitpod.io/docs/introduction/learn-gitpod/gitpod-yaml)
3
+ # and commit this file to your remote git repository to share the goodness with others.
4
+
5
+ # Learn more from ready-to-use templates: https://www.gitpod.io/docs/introduction/getting-started/quickstart
6
+
7
+ tasks:
8
+ - init: pnpm install && pnpm run build
9
+ command: pnpm run start
10
+
11
+ ports:
12
+ - port: 3000
13
+ onOpen: open-browser
14
+ visibility: public
.idea/.gitignore ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ # Default ignored files
2
+ /shelf/
3
+ /workspace.xml
4
+ # Editor-based HTTP Client requests
5
+ /httpRequests/
6
+ # Datasource local storage ignored files
7
+ /dataSources/
8
+ /dataSources.local.xml
.idea/inspectionProfiles/Project_Default.xml ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ <component name="InspectionProjectProfileManager">
2
+ <profile version="1.0">
3
+ <option name="myName" value="Project Default" />
4
+ <inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
5
+ </profile>
6
+ </component>
.idea/jsLinters/eslint.xml ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="EslintConfiguration">
4
+ <option name="fix-on-save" value="true" />
5
+ </component>
6
+ </project>
.idea/modules.xml ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="ProjectModuleManager">
4
+ <modules>
5
+ <module fileurl="file://$PROJECT_DIR$/.idea/visio.iml" filepath="$PROJECT_DIR$/.idea/visio.iml" />
6
+ </modules>
7
+ </component>
8
+ </project>
.idea/prettier.xml ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="PrettierConfiguration">
4
+ <option name="myConfigurationMode" value="AUTOMATIC" />
5
+ <option name="myRunOnSave" value="true" />
6
+ </component>
7
+ </project>
.idea/vcs.xml ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="VcsDirectoryMappings">
4
+ <mapping directory="" vcs="Git" />
5
+ </component>
6
+ </project>
.idea/visio.iml ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <module type="WEB_MODULE" version="4">
3
+ <component name="NewModuleRootManager">
4
+ <content url="file://$MODULE_DIR$">
5
+ <excludeFolder url="file://$MODULE_DIR$/.tmp" />
6
+ <excludeFolder url="file://$MODULE_DIR$/temp" />
7
+ <excludeFolder url="file://$MODULE_DIR$/tmp" />
8
+ </content>
9
+ <orderEntry type="inheritedJdk" />
10
+ <orderEntry type="sourceFolder" forTests="false" />
11
+ </component>
12
+ </module>
.idea/workspace.xml ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="AutoImportSettings">
4
+ <option name="autoReloadType" value="SELECTIVE" />
5
+ </component>
6
+ <component name="ChangeListManager">
7
+ <list default="true" id="b80f1987-f1a4-4ced-9d91-8a06a8a80780" name="Changes" comment="" />
8
+ <option name="SHOW_DIALOG" value="false" />
9
+ <option name="HIGHLIGHT_CONFLICTS" value="true" />
10
+ <option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
11
+ <option name="LAST_RESOLUTION" value="IGNORE" />
12
+ </component>
13
+ <component name="Git.Settings">
14
+ <option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
15
+ </component>
16
+ <component name="ProjectColorInfo">{
17
+ &quot;associatedIndex&quot;: 3
18
+ }</component>
19
+ <component name="ProjectId" id="2pD9OgrB1ijlK43KuPg9NRcta32" />
20
+ <component name="ProjectViewState">
21
+ <option name="hideEmptyMiddlePackages" value="true" />
22
+ <option name="showLibraryContents" value="true" />
23
+ </component>
24
+ <component name="PropertiesComponent"><![CDATA[{
25
+ "keyToString": {
26
+ "Node.js.index.ts.executor": "Run",
27
+ "RunOnceActivity.ShowReadmeOnStart": "true",
28
+ "RunOnceActivity.git.unshallow": "true",
29
+ "git-widget-placeholder": "main",
30
+ "last_opened_file_path": "/home/brown/Code/visio",
31
+ "node.js.detected.package.eslint": "true",
32
+ "node.js.detected.package.tslint": "true",
33
+ "node.js.selected.package.eslint": "(autodetect)",
34
+ "node.js.selected.package.tslint": "(autodetect)",
35
+ "nodejs_package_manager_path": "pnpm",
36
+ "settings.editor.selected.configurable": "preferences.pluginManager",
37
+ "ts.external.directory.path": "/home/brown/Code/visio/node_modules/typescript/lib",
38
+ "vue.rearranger.settings.migration": "true"
39
+ }
40
+ }]]></component>
41
+ <component name="SharedIndexes">
42
+ <attachedChunks>
43
+ <set>
44
+ <option value="bundled-js-predefined-d6986cc7102b-e768b9ed790e-JavaScript-WS-243.21565.180" />
45
+ </set>
46
+ </attachedChunks>
47
+ </component>
48
+ <component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" />
49
+ <component name="TaskManager">
50
+ <task active="true" id="Default" summary="Default task">
51
+ <changelist id="b80f1987-f1a4-4ced-9d91-8a06a8a80780" name="Changes" comment="" />
52
+ <created>1732289755049</created>
53
+ <option name="number" value="Default" />
54
+ <option name="presentableId" value="Default" />
55
+ <updated>1732289755049</updated>
56
+ <workItem from="1732289756203" duration="1723000" />
57
+ <workItem from="1732292118759" duration="2833000" />
58
+ </task>
59
+ <servers />
60
+ </component>
61
+ <component name="TypeScriptGeneratedFilesManager">
62
+ <option name="version" value="3" />
63
+ </component>
64
+ </project>
Dockerfile ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # syntax=docker/dockerfile:1.4
2
+
3
+ # Adapted from https://github.com/vercel/next.js/blob/e60a1e747c3f521fc24dfd9ee2989e13afeb0a9b/examples/with-docker/Dockerfile
4
+ # For more information, see https://nextjs.org/docs/pages/building-your-application/deploying#docker-image
5
+
6
+ FROM node:18 AS base
7
+
8
+ # Install dependencies only when needed
9
+ FROM base AS deps
10
+ WORKDIR /app
11
+
12
+ # Install dependencies based on the preferred package manager
13
+ COPY --link package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./
14
+ RUN \
15
+ if [ -f yarn.lock ]; then yarn --frozen-lockfile; \
16
+ elif [ -f package-lock.json ]; then npm ci; \
17
+ elif [ -f pnpm-lock.yaml ]; then yarn global add pnpm && pnpm i --frozen-lockfile; \
18
+ else echo "Lockfile not found." && exit 1; \
19
+ fi
20
+
21
+
22
+ # Rebuild the source code only when needed
23
+ FROM base AS builder
24
+ WORKDIR /app
25
+ COPY --from=deps --link /app/node_modules ./node_modules
26
+ COPY --link . .
27
+
28
+ # Next.js collects completely anonymous telemetry data about general usage.
29
+ # Learn more here: https://nextjs.org/telemetry
30
+ # Uncomment the following line in case you want to disable telemetry during the build.
31
+ # ENV NEXT_TELEMETRY_DISABLED 1
32
+
33
+ RUN npm run build
34
+
35
+ # If using yarn comment out above and use below instead
36
+ # RUN yarn build
37
+
38
+ # Production image, copy all the files and run next
39
+ FROM base AS runner
40
+ WORKDIR /app
41
+
42
+ ENV NODE_ENV production
43
+ # Uncomment the following line in case you want to disable telemetry during runtime.
44
+ # ENV NEXT_TELEMETRY_DISABLED 1
45
+
46
+ RUN \
47
+ addgroup --system --gid 1001 nodejs; \
48
+ adduser --system --uid 1001 nextjs
49
+
50
+ COPY --from=builder --link /app/public ./public
51
+
52
+ # Automatically leverage output traces to reduce image size
53
+ # https://nextjs.org/docs/advanced-features/output-file-tracing
54
+ COPY --from=builder --link --chown=1001:1001 /app/.next/standalone ./
55
+ COPY --from=builder --link --chown=1001:1001 /app/.next/static ./.next/static
56
+
57
+ USER nextjs
58
+
59
+ EXPOSE 3000
60
+
61
+ ENV PORT 3000
62
+ ENV HOSTNAME 0.0.0.0
63
+
64
+ # Allow the running process to write model files to the cache folder.
65
+ # NOTE: In practice, you would probably want to pre-download the model files to avoid having to download them on-the-fly.
66
+ RUN mkdir -p /app/node_modules/@xenova/.cache/
67
+ RUN chmod 777 -R /app/node_modules/@xenova/
68
+
69
+ CMD ["node", "server.js"]
README.md CHANGED
@@ -1,12 +1,36 @@
1
- ---
2
- title: Visio
3
- emoji: 😻
4
- colorFrom: pink
5
- colorTo: gray
6
- sdk: docker
7
- pinned: false
8
- license: mit
9
- short_description: 'A '
10
- ---
11
-
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
2
+
3
+ ## Getting Started
4
+
5
+ First, run the development server:
6
+
7
+ ```bash
8
+ npm run dev
9
+ # or
10
+ yarn dev
11
+ # or
12
+ pnpm dev
13
+ # or
14
+ bun dev
15
+ ```
16
+
17
+ Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
18
+
19
+ You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
20
+
21
+ This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
22
+
23
+ ## Learn More
24
+
25
+ To learn more about Next.js, take a look at the following resources:
26
+
27
+ - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
28
+ - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
29
+
30
+ You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
31
+
32
+ ## Deploy on Vercel
33
+
34
+ The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
35
+
36
+ Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
app/api/image-to-text/route.ts ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { pipeline } from "@huggingface/transformers";
2
+ import { NextRequest, NextResponse } from "next/server";
3
+
4
+ export const POST = async (request: NextRequest) => {
5
+ const { url } = await request.json();
6
+ const captioner = await pipeline(
7
+ "image-to-text",
8
+ "Xenova/vit-gpt2-image-captioning",
9
+ );
10
+ const output = await captioner(url);
11
+ return NextResponse.json(output);
12
+ };
app/api/image/route.ts ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { NextRequest, NextResponse } from "next/server";
2
+
3
+ import { ListObjectsCommand, PutObjectCommand } from "@aws-sdk/client-s3";
4
+ import { s3 } from "@/lib/s3";
5
+
6
+ const Bucket = "lectus-bucket";
7
+ export async function GET() {
8
+ const response = await s3.send(new ListObjectsCommand({ Bucket }));
9
+ return NextResponse.json(response?.Contents ?? []);
10
+ }
11
+
12
+ export async function POST(request: NextRequest) {
13
+ try {
14
+ const formData = await request.formData();
15
+ const file = formData.get("image") as File;
16
+ const Body = (await file.arrayBuffer()) as Buffer;
17
+ await s3.send(new PutObjectCommand({ Bucket, Key: file.name, Body }));
18
+
19
+ return NextResponse.json({
20
+ key: process.env.AWS_S3_URL + file.name,
21
+ });
22
+ } catch (e) {
23
+ console.error("error", e);
24
+ }
25
+ }
app/api/route.ts ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ import {NextResponse} from "next/server";
2
+
3
+ export const GET = async () => {
4
+ return NextResponse.json({message: "Welcome to visio"})
5
+ };
app/api/speech-to-audio/route.ts ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import fs from "fs";
2
+ import { NextRequest, NextResponse } from "next/server";
3
+ import { WaveFile } from "wavefile";
4
+ import path from "path";
5
+
6
+ const wav = new WaveFile();
7
+ export const POST = async (request: NextRequest) => {
8
+ const { audio, sampling_rate, name } = await request.json();
9
+ console.log(Object.values(audio));
10
+ wav.fromScratch(1, sampling_rate, "32f", Object.values(audio));
11
+ fs.writeFileSync(
12
+ path.join(process.cwd() + `/public/${name}.wav`),
13
+ wav.toBuffer(),
14
+ );
15
+ return NextResponse.json({ audio: `/${name}.wav` });
16
+ };
app/api/text-to-speech-lang/route.ts ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ import { pipeline } from "@huggingface/transformers";
2
+ import { NextRequest, NextResponse } from "next/server";
3
+
4
+ export const POST = async (request: NextRequest) => {
5
+ const { text, modal } = await request.json();
6
+ const synthesizer = await pipeline("text-to-speech", modal);
7
+ const speaker_embeddings = 'https://huggingface.co/datasets/Xenova/transformers.js-docs/resolve/v3.0.0/speaker_embeddings.bin';
8
+ const out = await synthesizer(text, {speaker_embeddings});
9
+ return NextResponse.json(out);
10
+ };
app/api/text-to-speech/route.ts ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { pipeline } from "@huggingface/transformers";
2
+
3
+ import { NextRequest, NextResponse } from "next/server";
4
+
5
+ export const POST = async (request: NextRequest) => {
6
+ const { text } = await request.json();
7
+ console.log("the text", text);
8
+ const synthesizer = await pipeline("text-to-speech", "Xenova/speecht5_tts");
9
+ const speaker_embeddings =
10
+ "https://huggingface.co/datasets/Xenova/transformers.js-docs/resolve/main/speaker_embeddings.bin";
11
+ const out = await synthesizer(text, {
12
+ speaker_embeddings,
13
+ });
14
+ return NextResponse.json(out);
15
+ };
app/api/translator/route.ts ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import {pipeline} from "@huggingface/transformers";
2
+ import {NextRequest, NextResponse} from "next/server";
3
+
4
+ export const POST = async (request: NextRequest) => {
5
+ const {text, code} = await request.json();
6
+ const translator = await pipeline(
7
+ "translation",
8
+ "Xenova/m2m100_418M",
9
+ );
10
+ const out = await translator(text, {
11
+ src_lang: "en",
12
+ tgt_lang: code,
13
+ } as never);
14
+ return NextResponse.json(out);
15
+ };
app/constant/index.ts ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export const modal = [
2
+ {
3
+ lang: "english",
4
+ model: "Xenova/mms-tts-eng",
5
+ code: "en",
6
+ },
7
+ // {
8
+ // lang: "tamil",
9
+ // model: "Xenova/mms-tts-tam",
10
+ // },
11
+ {
12
+ lang: "russian",
13
+ model: "Xenova/mms-tts-rus",
14
+ code: "ru",
15
+ },
16
+ {
17
+ lang: "hindi",
18
+ model: "Xenova/mms-tts-hin",
19
+ code: "hi",
20
+ },
21
+ {
22
+ lang: "spanish",
23
+ model: "Xenova/mms-tts-spa",
24
+ code: "es",
25
+ },
26
+ {
27
+ lang: "german",
28
+ model: "Xenova/mms-tts-deu",
29
+ code: "de",
30
+ },
31
+ {
32
+ lang: "french",
33
+ model: "Xenova/mms-tts-fra",
34
+ code: "fr",
35
+ },
36
+ {
37
+ lang: "portuguese",
38
+ model: "Xenova/mms-tts-por",
39
+ code: "pt",
40
+ },
41
+ ];
app/favicon.ico ADDED
app/fonts/GeistMonoVF.woff ADDED
Binary file (67.9 kB). View file
 
app/fonts/GeistVF.woff ADDED
Binary file (66.3 kB). View file
 
app/globals.css ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @import url('https://api.mapbox.com/mapbox-gl-js/v2.8.1/mapbox-gl.css');
2
+ @tailwind base;
3
+ @tailwind components;
4
+ @tailwind utilities;
5
+
6
+ body {
7
+ font-family: Arial, Helvetica, sans-serif;
8
+ }
9
+
10
+ @layer base {
11
+ :root {
12
+ --background: 0 0% 100%;
13
+ --foreground: 240 10% 3.9%;
14
+ --card: 0 0% 100%;
15
+ --card-foreground: 240 10% 3.9%;
16
+ --popover: 0 0% 100%;
17
+ --popover-foreground: 240 10% 3.9%;
18
+ --primary: 240 5.9% 10%;
19
+ --primary-foreground: 0 0% 98%;
20
+ --secondary: 240 4.8% 95.9%;
21
+ --secondary-foreground: 240 5.9% 10%;
22
+ --muted: 240 4.8% 95.9%;
23
+ --muted-foreground: 240 3.8% 46.1%;
24
+ --accent: 240 4.8% 95.9%;
25
+ --accent-foreground: 240 5.9% 10%;
26
+ --destructive: 0 84.2% 60.2%;
27
+ --destructive-foreground: 0 0% 98%;
28
+ --border: 240 5.9% 90%;
29
+ --input: 240 5.9% 90%;
30
+ --ring: 240 10% 3.9%;
31
+ --chart-1: 12 76% 61%;
32
+ --chart-2: 173 58% 39%;
33
+ --chart-3: 197 37% 24%;
34
+ --chart-4: 43 74% 66%;
35
+ --chart-5: 27 87% 67%;
36
+ --radius: 0.5rem;
37
+ }
38
+
39
+ .dark {
40
+ --background: 240 10% 3.9%;
41
+ --foreground: 0 0% 98%;
42
+ --card: 240 10% 3.9%;
43
+ --card-foreground: 0 0% 98%;
44
+ --popover: 240 10% 3.9%;
45
+ --popover-foreground: 0 0% 98%;
46
+ --primary: 0 0% 98%;
47
+ --primary-foreground: 240 5.9% 10%;
48
+ --secondary: 240 3.7% 15.9%;
49
+ --secondary-foreground: 0 0% 98%;
50
+ --muted: 240 3.7% 15.9%;
51
+ --muted-foreground: 240 5% 64.9%;
52
+ --accent: 240 3.7% 15.9%;
53
+ --accent-foreground: 0 0% 98%;
54
+ --destructive: 0 62.8% 30.6%;
55
+ --destructive-foreground: 0 0% 98%;
56
+ --border: 240 3.7% 15.9%;
57
+ --input: 240 3.7% 15.9%;
58
+ --ring: 240 4.9% 83.9%;
59
+ --chart-1: 220 70% 50%;
60
+ --chart-2: 160 60% 45%;
61
+ --chart-3: 30 80% 55%;
62
+ --chart-4: 280 65% 60%;
63
+ --chart-5: 340 75% 55%;
64
+ }
65
+ }
66
+
67
+ @layer base {
68
+ * {
69
+ @apply border-border;
70
+ }
71
+
72
+ body {
73
+ @apply bg-background text-foreground;
74
+ }
75
+ }
app/layout.tsx ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import type { Metadata } from "next";
2
+ import localFont from "next/font/local";
3
+ import "./globals.css";
4
+ import { WallProvider } from "./providers/wallprovider";
5
+ import { AsyncProvider } from "./providers/asyncprovider";
6
+
7
+ const geistSans = localFont({
8
+ src: "./fonts/GeistVF.woff",
9
+ variable: "--font-geist-sans",
10
+ weight: "100 900",
11
+ });
12
+ const geistMono = localFont({
13
+ src: "./fonts/GeistMonoVF.woff",
14
+ variable: "--font-geist-mono",
15
+ weight: "100 900",
16
+ });
17
+
18
+ export const metadata: Metadata = {
19
+ title: "Create Next App",
20
+ description: "Generated by create next app",
21
+ };
22
+
23
+ export default function RootLayout({
24
+ children,
25
+ }: Readonly<{
26
+ children: React.ReactNode;
27
+ }>) {
28
+ return (
29
+ <html lang="en">
30
+ <body
31
+ className={`${geistSans.variable} ${geistMono.variable} antialiased`}
32
+ >
33
+ <AsyncProvider>
34
+ <WallProvider>{children}</WallProvider>
35
+ </AsyncProvider>
36
+ </body>
37
+ </html>
38
+ );
39
+ }
app/page.tsx ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import VisualSearchAssistant from "../components/VisualSearchAssistant";
2
+
3
+ export default function Home() {
4
+ return (
5
+ <main className="mx-auto p-4 min-h-screen bg-gradient-to-br from-purple-400 via-pink-500 to-red-500 w-screen">
6
+ <h1 className="text-4xl font-bold mb-6 text-center text-white animate-pulse">
7
+ Visual Search Assistant
8
+ </h1>
9
+ <VisualSearchAssistant />
10
+ </main>
11
+ );
12
+ }
app/providers/asyncprovider.tsx ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+ import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
3
+ import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
4
+ import React from "react";
5
+
6
+ export const AsyncProvider = ({ children }: { children: React.ReactNode }) => {
7
+ const [queryClient] = React.useState(() => new QueryClient());
8
+ return (
9
+ <QueryClientProvider client={queryClient}>
10
+ {children}
11
+ <ReactQueryDevtools initialIsOpen={false} />
12
+ </QueryClientProvider>
13
+ );
14
+ };
app/providers/wallprovider.tsx ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+
3
+ import clsx from "clsx";
4
+ import React from "react";
5
+
6
+ export const WallProvider = ({ children }: { children: React.ReactNode }) => {
7
+ return <div className={clsx("")}>{children}</div>;
8
+ };
app/services/index.ts ADDED
@@ -0,0 +1,99 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export const genTextToSpeech = async ({ text }: { text: string }) => {
2
+ const response = await fetch("/api/text-to-speech", {
3
+ method: "POST",
4
+ body: JSON.stringify({ text }),
5
+ headers: {
6
+ "Content-Type": "application/json",
7
+ },
8
+ });
9
+ if (!response.ok) {
10
+ throw new Error("Network response was not ok");
11
+ }
12
+ const data = await response.json();
13
+ return data as { audio: Float32Array; sampling_rate: number };
14
+ };
15
+
16
+ export const genTextToSpeechLang = async ({
17
+ text,
18
+ modal,
19
+ }: {
20
+ text: string;
21
+ modal: string;
22
+ }) => {
23
+ const response = await fetch("/api/text-to-speech-lang", {
24
+ method: "POST",
25
+ body: JSON.stringify({ text, modal }),
26
+ headers: {
27
+ "Content-Type": "application/json",
28
+ },
29
+ });
30
+ if (!response.ok) {
31
+ throw new Error("Network response was not ok");
32
+ }
33
+ const data = await response.json();
34
+ return data as { audio: Float32Array; sampling_rate: number };
35
+ };
36
+
37
+ export const getWavAudio = async ({
38
+ sampling_rate,
39
+ audio,
40
+ name,
41
+ }: {
42
+ audio: Float32Array;
43
+ sampling_rate: number;
44
+ name: string;
45
+ }) => {
46
+ const response = await fetch("/api/speech-to-audio", {
47
+ method: "POST",
48
+ body: JSON.stringify({ sampling_rate, audio, name }),
49
+ headers: {
50
+ "Content-Type": "application/json",
51
+ },
52
+ });
53
+ if (!response.ok) {
54
+ throw new Error("Network response was not ok");
55
+ }
56
+ const data = await response.json();
57
+ return data as { audio: string };
58
+ };
59
+
60
+ export const uploadImage = async ({ form }: { form: FormData }) => {
61
+ const response = await fetch("/api/image", {
62
+ method: "POST",
63
+ body: form,
64
+ });
65
+ const json = await response.json();
66
+ return json as { key: string };
67
+ };
68
+
69
+ export const imageToText = async ({ url }: { url: string }) => {
70
+ const response = await fetch("/api/image-to-text", {
71
+ method: "POST",
72
+ body: JSON.stringify({ url }),
73
+ headers: {
74
+ "Content-Type": "application/json",
75
+ },
76
+ });
77
+ return (await response.json()) as Array<{ generated_text: string }>;
78
+ };
79
+
80
+ export const getTranslation = async ({
81
+ text,
82
+ code,
83
+ }: {
84
+ text: string;
85
+ code: string;
86
+ }) => {
87
+ const response = await fetch("/api/translator", {
88
+ method: "POST",
89
+ body: JSON.stringify({ text, code }),
90
+ headers: {
91
+ "Content-Type": "application/json",
92
+ },
93
+ });
94
+ if (!response.ok) {
95
+ throw new Error("Network response was not ok");
96
+ }
97
+ const data = await response.json();
98
+ return data as Array<{ translation_text: string }>;
99
+ };
app/store/index.ts ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { create } from "zustand";
2
+
3
+ interface Steps {
4
+ step: number;
5
+ increase: (by: number) => void;
6
+ decrement: (by: number) => void;
7
+ }
8
+
9
+ export const useStepsStore = create<Steps>()((set) => ({
10
+ step: 1,
11
+ increase: (by) => set((state) => ({ step: state.step + by })),
12
+ decrement: (by) => set((state) => ({ step: state.step - by })),
13
+ }));
components.json ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema.json",
3
+ "style": "new-york",
4
+ "rsc": true,
5
+ "tsx": true,
6
+ "tailwind": {
7
+ "config": "tailwind.config.ts",
8
+ "css": "app/globals.css",
9
+ "baseColor": "zinc",
10
+ "cssVariables": true,
11
+ "prefix": ""
12
+ },
13
+ "aliases": {
14
+ "components": "@/components",
15
+ "utils": "@/lib/utils",
16
+ "ui": "@/components/ui",
17
+ "lib": "@/lib",
18
+ "hooks": "@/hooks"
19
+ },
20
+ "iconLibrary": "lucide"
21
+ }
components/AudioPlayback.tsx ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use client'
2
+
3
+ import { Play, Pause, RotateCcw } from 'lucide-react'
4
+ import { Button } from "@/components/ui/button"
5
+ import React from "react";
6
+ import {match} from "ts-pattern"
7
+
8
+ interface AudioPlaybackProps {
9
+ path: string
10
+ }
11
+
12
+ export default function AudioPlayback({ path }: AudioPlaybackProps) {
13
+ const audioRef = React.useRef<HTMLAudioElement| null>(null);
14
+
15
+ return (
16
+ <div className="flex items-center space-x-4">
17
+ <Button
18
+ onClick={async () => await audioRef.current?.play()}
19
+ variant="default"
20
+ size="icon"
21
+ className="w-12 h-12 rounded-full bg-gradient-to-r from-green-400 to-blue-500 hover:from-green-500 hover:to-blue-600 transition-all duration-300"
22
+ >
23
+ {match(audioRef.current?.paused).with(true, () => <Pause/>).otherwise(() => <Play/>)}
24
+ </Button>
25
+ <audio controls src={path} autoPlay={!!path} ref={audioRef} className="hidden"></audio>
26
+ <Button
27
+ variant="outline"
28
+ size="icon"
29
+ className="w-12 h-12 rounded-full transition-all duration-300 hover:bg-gray-200"
30
+ >
31
+ <RotateCcw className="w-6 h-6"/>
32
+ </Button>
33
+ </div>
34
+ )
35
+ }
36
+
components/DescriptionDisplay.tsx ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
2
+
3
+ interface DescriptionDisplayProps {
4
+ description: string
5
+ }
6
+
7
+ export default function DescriptionDisplay({ description }: DescriptionDisplayProps) {
8
+ return (
9
+ <Card className="bg-gradient-to-r from-cyan-500 to-blue-500 text-white shadow-lg transition-all duration-300 hover:shadow-xl">
10
+ <CardHeader>
11
+ <CardTitle className="text-xl font-semibold">Image Description</CardTitle>
12
+ </CardHeader>
13
+ <CardContent>
14
+ <p className="text-lg">{description}</p>
15
+ </CardContent>
16
+ </Card>
17
+ )
18
+ }
19
+
components/ImageUpload.tsx ADDED
@@ -0,0 +1,130 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from "react";
2
+ import {Camera, Upload} from "lucide-react";
3
+ import {Button} from "@/components/ui/button";
4
+
5
+ interface ImageUploadProps {
6
+ onImageUpload: (file: File) => void;
7
+ sideEffect: (text: string | null) => void;
8
+ }
9
+
10
+ export default function ImageUpload({onImageUpload, sideEffect}: ImageUploadProps) {
11
+ const [isDragging, setIsDragging] = React.useState(false);
12
+ const fileInputRef = React.useRef<HTMLInputElement>(null);
13
+ const videoRef = React.useRef<HTMLVideoElement>(null);
14
+ const [isCameraActive, setIsCameraActive] = React.useState(false);
15
+
16
+ const handleDragOver = (e: React.DragEvent<HTMLDivElement>) => {
17
+ e.preventDefault();
18
+ setIsDragging(true);
19
+ };
20
+
21
+ const handleDragLeave = () => {
22
+ setIsDragging(false);
23
+ };
24
+
25
+ const handleDrop = (e: React.DragEvent<HTMLDivElement>) => {
26
+ e.preventDefault();
27
+ setIsDragging(false);
28
+ const file = e.dataTransfer.files[0];
29
+ if (file) {
30
+ onImageUpload(file);
31
+ }
32
+ };
33
+
34
+ const handleFileInput = (e: React.ChangeEvent<HTMLInputElement>) => {
35
+ const file = e.target.files?.[0];
36
+ if (file) {
37
+ onImageUpload(file);
38
+ }
39
+ };
40
+
41
+ const activateCamera = async () => {
42
+ try {
43
+ const stream = await navigator.mediaDevices.getUserMedia({video: true});
44
+ if (videoRef.current) {
45
+ videoRef.current.srcObject = stream;
46
+ setIsCameraActive(true);
47
+ }
48
+ } catch (error) {
49
+ console.error("Error accessing camera:", error);
50
+ }
51
+ };
52
+
53
+ const capturePhoto = () => {
54
+ if (videoRef.current) {
55
+ const canvas = document.createElement("canvas");
56
+ canvas.width = videoRef.current.videoWidth;
57
+ canvas.height = videoRef.current.videoHeight;
58
+ canvas.getContext("2d")?.drawImage(videoRef.current, 0, 0);
59
+ canvas.toBlob((blob) => {
60
+ if (blob) {
61
+ const file = new File([blob], "camera-photo.jpg", {
62
+ type: "image/jpeg",
63
+ });
64
+ onImageUpload(file);
65
+ }
66
+ }, "image/jpeg");
67
+ setIsCameraActive(false);
68
+ }
69
+ };
70
+
71
+ return (
72
+ <div
73
+ className={`border-2 border-dashed rounded-lg p-8 text-center transition-all duration-300 ${
74
+ isDragging
75
+ ? "border-blue-500 bg-blue-50"
76
+ : "border-gray-300 hover:border-blue-300 hover:bg-blue-50"
77
+ }`}
78
+ onDragOver={handleDragOver}
79
+ onDragLeave={handleDragLeave}
80
+ onDrop={handleDrop}
81
+ >
82
+ {isCameraActive ? (
83
+ <div className="space-y-4">
84
+ <video
85
+ ref={videoRef}
86
+ autoPlay
87
+ className="mx-auto mb-4 rounded-lg shadow-md"
88
+ />
89
+ <Button onClick={capturePhoto} variant="default">
90
+ Capture Photo
91
+ </Button>
92
+ </div>
93
+ ) : (
94
+ <>
95
+ <form
96
+ onSubmit={(event) => event.preventDefault()}
97
+ encType="multipart/form-data"
98
+ >
99
+ <p className="mb-4 text-gray-600">
100
+ Drag and drop an image here, or click to select a file
101
+ </p>
102
+ <input
103
+ type="file"
104
+ accept="image/*"
105
+ onChange={(event) => {
106
+ handleFileInput(event);
107
+ sideEffect(null)
108
+ }}
109
+ className="hidden"
110
+ ref={fileInputRef}
111
+ />
112
+ <div className="space-x-4">
113
+ <Button
114
+ onClick={() => fileInputRef.current?.click()}
115
+ variant="outline"
116
+ >
117
+ <Upload className="w-4 h-4 mr-2"/>
118
+ Select Image
119
+ </Button>
120
+ <Button onClick={activateCamera} variant="outline">
121
+ <Camera className="w-4 h-4 mr-2"/>
122
+ Use Camera
123
+ </Button>
124
+ </div>
125
+ </form>
126
+ </>
127
+ )}
128
+ </div>
129
+ );
130
+ }
components/LanguageSelector.tsx ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import {modal} from "@/app/constant";
2
+ import {Select, SelectContent, SelectItem, SelectTrigger, SelectValue,} from "@/components/ui/select";
3
+
4
+ interface LanguageSelectorProps {
5
+ onLanguageChange: (language: string) => void;
6
+ sideEffect: (text: string | null) => void
7
+ }
8
+
9
+ export default function LanguageSelector({
10
+ onLanguageChange,
11
+ sideEffect,
12
+ }: LanguageSelectorProps) {
13
+ return (
14
+ <div className="flex items-center space-x-2">
15
+ <label
16
+ htmlFor="language-select"
17
+ className="text-sm font-medium text-gray-700"
18
+ >
19
+ Select Language:
20
+ </label>
21
+ <Select
22
+ onValueChange={(event) => {
23
+ onLanguageChange(event);
24
+ sideEffect(null)
25
+ }}
26
+ defaultValue="Xenova/mms-tts-eng"
27
+ >
28
+ <SelectTrigger className="w-[180px]" id="language-select">
29
+ <SelectValue placeholder="Select a language"/>
30
+ </SelectTrigger>
31
+ <SelectContent>
32
+ {modal.map((lang) => (
33
+ <SelectItem key={lang.code} value={lang.model}>
34
+ {lang.lang}
35
+ </SelectItem>
36
+ ))}
37
+ </SelectContent>
38
+ </Select>
39
+ </div>
40
+ );
41
+ }
components/VisualSearchAssistant.tsx ADDED
@@ -0,0 +1,126 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+
3
+ import React from "react";
4
+ import ImageUpload from "./ImageUpload";
5
+ import DescriptionDisplay from "./DescriptionDisplay";
6
+ import AudioPlayback from "./AudioPlayback";
7
+ import {Card, CardContent} from "@/components/ui/card";
8
+ import {useGenWav, useImageToText, useTextToSpeachLang, useTranslator, useUploadImage} from "@/hooks/api";
9
+ import LanguageSelector from "./LanguageSelector";
10
+ import {modal} from "@/app/constant";
11
+ import Image from "next/image";
12
+
13
+ export default function VisualSearchAssistant() {
14
+ const [imageUrl, setImageUrl] = React.useState<string | null>(null);
15
+ const [description, setDescription] = React.useState<string>("");
16
+ const [language, setLanguage] = React.useState<string>("Xenova/mms-tts-eng");
17
+ const [path, setPath] = React.useState<string | null>(null);
18
+ const code = modal.find((lang) => lang.model === language)?.code || "en";
19
+ const upload = useUploadImage();
20
+ const iToText = useImageToText();
21
+ const translator = useTranslator();
22
+ const tts = useTextToSpeachLang();
23
+ const wav = useGenWav();
24
+
25
+ const handleImageUpload = async (file: File) => {
26
+ const url = URL.createObjectURL(file);
27
+ setImageUrl(url);
28
+
29
+ const formData = new FormData();
30
+ formData.append("image", file);
31
+
32
+ try {
33
+ upload.mutate(
34
+ {form: formData},
35
+ {
36
+ onSuccess: (data) => {
37
+ iToText.mutate(
38
+ {url: data?.key},
39
+ {
40
+ onSuccess: (data) => {
41
+ translator.mutate(
42
+ {text: data[0].generated_text, code},
43
+ {
44
+ onSuccess: (data) => {
45
+ console.log("data-desc", data);
46
+ setDescription(data[0]?.translation_text);
47
+ console.log("lang", language, data);
48
+ tts.mutate({text: data[0]?.translation_text, modal: language}, {
49
+ onSuccess: (data) => {
50
+ wav.mutate({
51
+ sampling_rate: data.sampling_rate,
52
+ audio: data.audio,
53
+ name: crypto.randomUUID()
54
+ }, {
55
+ onSuccess: (data) => {
56
+ setPath(data.audio);
57
+ }
58
+ })
59
+ }
60
+ })
61
+ },
62
+ },
63
+ );
64
+ },
65
+ },
66
+ );
67
+ },
68
+ },
69
+ );
70
+ } catch (error) {
71
+ console.error("Error processing image:", error);
72
+ setDescription("Failed to process image. Please try again.");
73
+ }
74
+ };
75
+ console.log("upload data", upload?.data);
76
+
77
+ const renderStatusMessage = () => {
78
+ if (upload.isPending) {
79
+ return <p>Please wait while we process your image...</p>;
80
+ }
81
+ if (iToText.isPending) {
82
+ return <p>Processing image and extracting information, please wait...</p>;
83
+ }
84
+ if (translator.isPending) {
85
+ return <p>Wait while we translate your text...</p>;
86
+ }
87
+ if (tts.isPending) {
88
+ return <p>Wait while we process the audio...</p>;
89
+ }
90
+ if (wav.isPending) {
91
+ return <p>Generating audio file, please wait...</p>;
92
+ }
93
+ return null;
94
+ };
95
+
96
+
97
+ return (
98
+ <div className="w-full">
99
+ <Card
100
+ className="bg-white/90 backdrop-blur-sm shadow-xl rounded-xl overflow-hidden transition-all duration-300 hover:shadow-2xl">
101
+ <CardContent className="p-6 space-y-6">
102
+ <LanguageSelector onLanguageChange={setLanguage} sideEffect={setPath}/>
103
+ <ImageUpload sideEffect={setPath} onImageUpload={handleImageUpload}/>
104
+ {imageUrl && (
105
+ <div className="mt-4 flex justify-center">
106
+ <Image
107
+ src={imageUrl}
108
+ alt="Uploaded image"
109
+ width={1000}
110
+ height={1000}
111
+ className="max-w-full max-h-[50vh] object-contain rounded-lg shadow-md transition-all duration-300 hover:scale-105"
112
+ />
113
+ </div>
114
+ )}
115
+ {renderStatusMessage()}
116
+ {description && (
117
+ <div className="space-y-4" key={crypto.randomUUID()}>
118
+ <DescriptionDisplay description={description}/>
119
+ <AudioPlayback path={path!}/>
120
+ </div>
121
+ )}
122
+ </CardContent>
123
+ </Card>
124
+ </div>
125
+ );
126
+ }
components/footer.tsx ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+ import {Button} from "./ui/button";
3
+ import React from "react";
4
+
5
+ export const Footer = () => {
6
+ return (
7
+ <footer>
8
+ <div className="m-3">
9
+ <div className="flex gap-4">
10
+ <Button
11
+ className="rounded-2xl bg-[#C1A616] text-white"
12
+ variant={"outline"}
13
+ >
14
+ Help
15
+ </Button>
16
+ <Button
17
+ className="rounded-2xl border-[#C1A616]"
18
+ variant={"outline"}
19
+ >
20
+ Repeat
21
+ </Button>
22
+ </div>
23
+ </div>
24
+ </footer>
25
+ )
26
+ }
components/header.tsx ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+ import { Volume2, VolumeX } from "lucide-react";
3
+ import { Button } from "./ui/button";
4
+ import React from "react";
5
+
6
+ export const Header = () => {
7
+ const [isMuted, setIsMuted] = React.useState(false);
8
+ const toggleMute = () => {
9
+ setIsMuted(!isMuted);
10
+ };
11
+ return (
12
+ <header>
13
+ <div className="m-10 flex justify-end">
14
+ <div className={`flex items-center rounded-full transition-colors`}>
15
+ <Button
16
+ className={`rounded-l-full p-2 transition-colors `}
17
+ onClick={toggleMute}
18
+ aria-label="Mute"
19
+ >
20
+ <VolumeX className="h-4 w-4" />
21
+ </Button>
22
+ <div className="w-px h-6 bg-zinc-300"></div>
23
+ <Button
24
+ className={`rounded-r-full p-2 transition-colors `}
25
+ onClick={toggleMute}
26
+ aria-label="Unmute"
27
+ >
28
+ <Volume2 className="h-4 w-4" />
29
+ </Button>
30
+ </div>
31
+ </div>
32
+ </header>
33
+ );
34
+ };
components/ui/button.tsx ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import { Slot } from "@radix-ui/react-slot"
3
+ import { cva, type VariantProps } from "class-variance-authority"
4
+
5
+ import { cn } from "@/lib/utils"
6
+
7
+ const buttonVariants = cva(
8
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
9
+ {
10
+ variants: {
11
+ variant: {
12
+ default:
13
+ "bg-primary text-primary-foreground shadow hover:bg-primary/90",
14
+ destructive:
15
+ "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
16
+ outline:
17
+ "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
18
+ secondary:
19
+ "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
20
+ ghost: "hover:bg-accent hover:text-accent-foreground",
21
+ link: "text-primary underline-offset-4 hover:underline",
22
+ },
23
+ size: {
24
+ default: "h-9 px-4 py-2",
25
+ sm: "h-8 rounded-md px-3 text-xs",
26
+ lg: "h-10 rounded-md px-8",
27
+ icon: "h-9 w-9",
28
+ },
29
+ },
30
+ defaultVariants: {
31
+ variant: "default",
32
+ size: "default",
33
+ },
34
+ }
35
+ )
36
+
37
+ export interface ButtonProps
38
+ extends React.ButtonHTMLAttributes<HTMLButtonElement>,
39
+ VariantProps<typeof buttonVariants> {
40
+ asChild?: boolean
41
+ }
42
+
43
+ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
44
+ ({ className, variant, size, asChild = false, ...props }, ref) => {
45
+ const Comp = asChild ? Slot : "button"
46
+ return (
47
+ <Comp
48
+ className={cn(buttonVariants({ variant, size, className }))}
49
+ ref={ref}
50
+ {...props}
51
+ />
52
+ )
53
+ }
54
+ )
55
+ Button.displayName = "Button"
56
+
57
+ export { Button, buttonVariants }
components/ui/card.tsx ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+
3
+ import { cn } from "@/lib/utils"
4
+
5
+ const Card = React.forwardRef<
6
+ HTMLDivElement,
7
+ React.HTMLAttributes<HTMLDivElement>
8
+ >(({ className, ...props }, ref) => (
9
+ <div
10
+ ref={ref}
11
+ className={cn(
12
+ "rounded-xl border bg-card text-card-foreground shadow",
13
+ className
14
+ )}
15
+ {...props}
16
+ />
17
+ ))
18
+ Card.displayName = "Card"
19
+
20
+ const CardHeader = React.forwardRef<
21
+ HTMLDivElement,
22
+ React.HTMLAttributes<HTMLDivElement>
23
+ >(({ className, ...props }, ref) => (
24
+ <div
25
+ ref={ref}
26
+ className={cn("flex flex-col space-y-1.5 p-6", className)}
27
+ {...props}
28
+ />
29
+ ))
30
+ CardHeader.displayName = "CardHeader"
31
+
32
+ const CardTitle = React.forwardRef<
33
+ HTMLDivElement,
34
+ React.HTMLAttributes<HTMLDivElement>
35
+ >(({ className, ...props }, ref) => (
36
+ <div
37
+ ref={ref}
38
+ className={cn("font-semibold leading-none tracking-tight", className)}
39
+ {...props}
40
+ />
41
+ ))
42
+ CardTitle.displayName = "CardTitle"
43
+
44
+ const CardDescription = React.forwardRef<
45
+ HTMLDivElement,
46
+ React.HTMLAttributes<HTMLDivElement>
47
+ >(({ className, ...props }, ref) => (
48
+ <div
49
+ ref={ref}
50
+ className={cn("text-sm text-muted-foreground", className)}
51
+ {...props}
52
+ />
53
+ ))
54
+ CardDescription.displayName = "CardDescription"
55
+
56
+ const CardContent = React.forwardRef<
57
+ HTMLDivElement,
58
+ React.HTMLAttributes<HTMLDivElement>
59
+ >(({ className, ...props }, ref) => (
60
+ <div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
61
+ ))
62
+ CardContent.displayName = "CardContent"
63
+
64
+ const CardFooter = React.forwardRef<
65
+ HTMLDivElement,
66
+ React.HTMLAttributes<HTMLDivElement>
67
+ >(({ className, ...props }, ref) => (
68
+ <div
69
+ ref={ref}
70
+ className={cn("flex items-center p-6 pt-0", className)}
71
+ {...props}
72
+ />
73
+ ))
74
+ CardFooter.displayName = "CardFooter"
75
+
76
+ export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
components/ui/dialog.tsx ADDED
@@ -0,0 +1,122 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as DialogPrimitive from "@radix-ui/react-dialog"
5
+ import { X } from "lucide-react"
6
+
7
+ import { cn } from "@/lib/utils"
8
+
9
+ const Dialog = DialogPrimitive.Root
10
+
11
+ const DialogTrigger = DialogPrimitive.Trigger
12
+
13
+ const DialogPortal = DialogPrimitive.Portal
14
+
15
+ const DialogClose = DialogPrimitive.Close
16
+
17
+ const DialogOverlay = React.forwardRef<
18
+ React.ElementRef<typeof DialogPrimitive.Overlay>,
19
+ React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
20
+ >(({ className, ...props }, ref) => (
21
+ <DialogPrimitive.Overlay
22
+ ref={ref}
23
+ className={cn(
24
+ "fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
25
+ className
26
+ )}
27
+ {...props}
28
+ />
29
+ ))
30
+ DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
31
+
32
+ const DialogContent = React.forwardRef<
33
+ React.ElementRef<typeof DialogPrimitive.Content>,
34
+ React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
35
+ >(({ className, children, ...props }, ref) => (
36
+ <DialogPortal>
37
+ <DialogOverlay />
38
+ <DialogPrimitive.Content
39
+ ref={ref}
40
+ className={cn(
41
+ "fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
42
+ className
43
+ )}
44
+ {...props}
45
+ >
46
+ {children}
47
+ <DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
48
+ <X className="h-4 w-4" />
49
+ <span className="sr-only">Close</span>
50
+ </DialogPrimitive.Close>
51
+ </DialogPrimitive.Content>
52
+ </DialogPortal>
53
+ ))
54
+ DialogContent.displayName = DialogPrimitive.Content.displayName
55
+
56
+ const DialogHeader = ({
57
+ className,
58
+ ...props
59
+ }: React.HTMLAttributes<HTMLDivElement>) => (
60
+ <div
61
+ className={cn(
62
+ "flex flex-col space-y-1.5 text-center sm:text-left",
63
+ className
64
+ )}
65
+ {...props}
66
+ />
67
+ )
68
+ DialogHeader.displayName = "DialogHeader"
69
+
70
+ const DialogFooter = ({
71
+ className,
72
+ ...props
73
+ }: React.HTMLAttributes<HTMLDivElement>) => (
74
+ <div
75
+ className={cn(
76
+ "flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
77
+ className
78
+ )}
79
+ {...props}
80
+ />
81
+ )
82
+ DialogFooter.displayName = "DialogFooter"
83
+
84
+ const DialogTitle = React.forwardRef<
85
+ React.ElementRef<typeof DialogPrimitive.Title>,
86
+ React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
87
+ >(({ className, ...props }, ref) => (
88
+ <DialogPrimitive.Title
89
+ ref={ref}
90
+ className={cn(
91
+ "text-lg font-semibold leading-none tracking-tight",
92
+ className
93
+ )}
94
+ {...props}
95
+ />
96
+ ))
97
+ DialogTitle.displayName = DialogPrimitive.Title.displayName
98
+
99
+ const DialogDescription = React.forwardRef<
100
+ React.ElementRef<typeof DialogPrimitive.Description>,
101
+ React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
102
+ >(({ className, ...props }, ref) => (
103
+ <DialogPrimitive.Description
104
+ ref={ref}
105
+ className={cn("text-sm text-muted-foreground", className)}
106
+ {...props}
107
+ />
108
+ ))
109
+ DialogDescription.displayName = DialogPrimitive.Description.displayName
110
+
111
+ export {
112
+ Dialog,
113
+ DialogPortal,
114
+ DialogOverlay,
115
+ DialogTrigger,
116
+ DialogClose,
117
+ DialogContent,
118
+ DialogHeader,
119
+ DialogFooter,
120
+ DialogTitle,
121
+ DialogDescription,
122
+ }
components/ui/form.tsx ADDED
@@ -0,0 +1,178 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as LabelPrimitive from "@radix-ui/react-label"
5
+ import { Slot } from "@radix-ui/react-slot"
6
+ import {
7
+ Controller,
8
+ ControllerProps,
9
+ FieldPath,
10
+ FieldValues,
11
+ FormProvider,
12
+ useFormContext,
13
+ } from "react-hook-form"
14
+
15
+ import { cn } from "@/lib/utils"
16
+ import { Label } from "@/components/ui/label"
17
+
18
+ const Form = FormProvider
19
+
20
+ type FormFieldContextValue<
21
+ TFieldValues extends FieldValues = FieldValues,
22
+ TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
23
+ > = {
24
+ name: TName
25
+ }
26
+
27
+ const FormFieldContext = React.createContext<FormFieldContextValue>(
28
+ {} as FormFieldContextValue
29
+ )
30
+
31
+ const FormField = <
32
+ TFieldValues extends FieldValues = FieldValues,
33
+ TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
34
+ >({
35
+ ...props
36
+ }: ControllerProps<TFieldValues, TName>) => {
37
+ return (
38
+ <FormFieldContext.Provider value={{ name: props.name }}>
39
+ <Controller {...props} />
40
+ </FormFieldContext.Provider>
41
+ )
42
+ }
43
+
44
+ const useFormField = () => {
45
+ const fieldContext = React.useContext(FormFieldContext)
46
+ const itemContext = React.useContext(FormItemContext)
47
+ const { getFieldState, formState } = useFormContext()
48
+
49
+ const fieldState = getFieldState(fieldContext.name, formState)
50
+
51
+ if (!fieldContext) {
52
+ throw new Error("useFormField should be used within <FormField>")
53
+ }
54
+
55
+ const { id } = itemContext
56
+
57
+ return {
58
+ id,
59
+ name: fieldContext.name,
60
+ formItemId: `${id}-form-item`,
61
+ formDescriptionId: `${id}-form-item-description`,
62
+ formMessageId: `${id}-form-item-message`,
63
+ ...fieldState,
64
+ }
65
+ }
66
+
67
+ type FormItemContextValue = {
68
+ id: string
69
+ }
70
+
71
+ const FormItemContext = React.createContext<FormItemContextValue>(
72
+ {} as FormItemContextValue
73
+ )
74
+
75
+ const FormItem = React.forwardRef<
76
+ HTMLDivElement,
77
+ React.HTMLAttributes<HTMLDivElement>
78
+ >(({ className, ...props }, ref) => {
79
+ const id = React.useId()
80
+
81
+ return (
82
+ <FormItemContext.Provider value={{ id }}>
83
+ <div ref={ref} className={cn("space-y-2", className)} {...props} />
84
+ </FormItemContext.Provider>
85
+ )
86
+ })
87
+ FormItem.displayName = "FormItem"
88
+
89
+ const FormLabel = React.forwardRef<
90
+ React.ElementRef<typeof LabelPrimitive.Root>,
91
+ React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>
92
+ >(({ className, ...props }, ref) => {
93
+ const { error, formItemId } = useFormField()
94
+
95
+ return (
96
+ <Label
97
+ ref={ref}
98
+ className={cn(error && "text-destructive", className)}
99
+ htmlFor={formItemId}
100
+ {...props}
101
+ />
102
+ )
103
+ })
104
+ FormLabel.displayName = "FormLabel"
105
+
106
+ const FormControl = React.forwardRef<
107
+ React.ElementRef<typeof Slot>,
108
+ React.ComponentPropsWithoutRef<typeof Slot>
109
+ >(({ ...props }, ref) => {
110
+ const { error, formItemId, formDescriptionId, formMessageId } = useFormField()
111
+
112
+ return (
113
+ <Slot
114
+ ref={ref}
115
+ id={formItemId}
116
+ aria-describedby={
117
+ !error
118
+ ? `${formDescriptionId}`
119
+ : `${formDescriptionId} ${formMessageId}`
120
+ }
121
+ aria-invalid={!!error}
122
+ {...props}
123
+ />
124
+ )
125
+ })
126
+ FormControl.displayName = "FormControl"
127
+
128
+ const FormDescription = React.forwardRef<
129
+ HTMLParagraphElement,
130
+ React.HTMLAttributes<HTMLParagraphElement>
131
+ >(({ className, ...props }, ref) => {
132
+ const { formDescriptionId } = useFormField()
133
+
134
+ return (
135
+ <p
136
+ ref={ref}
137
+ id={formDescriptionId}
138
+ className={cn("text-[0.8rem] text-muted-foreground", className)}
139
+ {...props}
140
+ />
141
+ )
142
+ })
143
+ FormDescription.displayName = "FormDescription"
144
+
145
+ const FormMessage = React.forwardRef<
146
+ HTMLParagraphElement,
147
+ React.HTMLAttributes<HTMLParagraphElement>
148
+ >(({ className, children, ...props }, ref) => {
149
+ const { error, formMessageId } = useFormField()
150
+ const body = error ? String(error?.message) : children
151
+
152
+ if (!body) {
153
+ return null
154
+ }
155
+
156
+ return (
157
+ <p
158
+ ref={ref}
159
+ id={formMessageId}
160
+ className={cn("text-[0.8rem] font-medium text-destructive", className)}
161
+ {...props}
162
+ >
163
+ {body}
164
+ </p>
165
+ )
166
+ })
167
+ FormMessage.displayName = "FormMessage"
168
+
169
+ export {
170
+ useFormField,
171
+ Form,
172
+ FormItem,
173
+ FormLabel,
174
+ FormControl,
175
+ FormDescription,
176
+ FormMessage,
177
+ FormField,
178
+ }
components/ui/input.tsx ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+
3
+ import { cn } from "@/lib/utils"
4
+
5
+ const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<"input">>(
6
+ ({ className, type, ...props }, ref) => {
7
+ return (
8
+ <input
9
+ type={type}
10
+ className={cn(
11
+ "flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
12
+ className
13
+ )}
14
+ ref={ref}
15
+ {...props}
16
+ />
17
+ )
18
+ }
19
+ )
20
+ Input.displayName = "Input"
21
+
22
+ export { Input }
components/ui/label.tsx ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as LabelPrimitive from "@radix-ui/react-label"
5
+ import { cva, type VariantProps } from "class-variance-authority"
6
+
7
+ import { cn } from "@/lib/utils"
8
+
9
+ const labelVariants = cva(
10
+ "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
11
+ )
12
+
13
+ const Label = React.forwardRef<
14
+ React.ElementRef<typeof LabelPrimitive.Root>,
15
+ React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
16
+ VariantProps<typeof labelVariants>
17
+ >(({ className, ...props }, ref) => (
18
+ <LabelPrimitive.Root
19
+ ref={ref}
20
+ className={cn(labelVariants(), className)}
21
+ {...props}
22
+ />
23
+ ))
24
+ Label.displayName = LabelPrimitive.Root.displayName
25
+
26
+ export { Label }
components/ui/select.tsx ADDED
@@ -0,0 +1,159 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as SelectPrimitive from "@radix-ui/react-select"
5
+ import { Check, ChevronDown, ChevronUp } from "lucide-react"
6
+
7
+ import { cn } from "@/lib/utils"
8
+
9
+ const Select = SelectPrimitive.Root
10
+
11
+ const SelectGroup = SelectPrimitive.Group
12
+
13
+ const SelectValue = SelectPrimitive.Value
14
+
15
+ const SelectTrigger = React.forwardRef<
16
+ React.ElementRef<typeof SelectPrimitive.Trigger>,
17
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
18
+ >(({ className, children, ...props }, ref) => (
19
+ <SelectPrimitive.Trigger
20
+ ref={ref}
21
+ className={cn(
22
+ "flex h-9 w-full items-center justify-between whitespace-nowrap rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
23
+ className
24
+ )}
25
+ {...props}
26
+ >
27
+ {children}
28
+ <SelectPrimitive.Icon asChild>
29
+ <ChevronDown className="h-4 w-4 opacity-50" />
30
+ </SelectPrimitive.Icon>
31
+ </SelectPrimitive.Trigger>
32
+ ))
33
+ SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
34
+
35
+ const SelectScrollUpButton = React.forwardRef<
36
+ React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
37
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
38
+ >(({ className, ...props }, ref) => (
39
+ <SelectPrimitive.ScrollUpButton
40
+ ref={ref}
41
+ className={cn(
42
+ "flex cursor-default items-center justify-center py-1",
43
+ className
44
+ )}
45
+ {...props}
46
+ >
47
+ <ChevronUp className="h-4 w-4" />
48
+ </SelectPrimitive.ScrollUpButton>
49
+ ))
50
+ SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName
51
+
52
+ const SelectScrollDownButton = React.forwardRef<
53
+ React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
54
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
55
+ >(({ className, ...props }, ref) => (
56
+ <SelectPrimitive.ScrollDownButton
57
+ ref={ref}
58
+ className={cn(
59
+ "flex cursor-default items-center justify-center py-1",
60
+ className
61
+ )}
62
+ {...props}
63
+ >
64
+ <ChevronDown className="h-4 w-4" />
65
+ </SelectPrimitive.ScrollDownButton>
66
+ ))
67
+ SelectScrollDownButton.displayName =
68
+ SelectPrimitive.ScrollDownButton.displayName
69
+
70
+ const SelectContent = React.forwardRef<
71
+ React.ElementRef<typeof SelectPrimitive.Content>,
72
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
73
+ >(({ className, children, position = "popper", ...props }, ref) => (
74
+ <SelectPrimitive.Portal>
75
+ <SelectPrimitive.Content
76
+ ref={ref}
77
+ className={cn(
78
+ "relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
79
+ position === "popper" &&
80
+ "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
81
+ className
82
+ )}
83
+ position={position}
84
+ {...props}
85
+ >
86
+ <SelectScrollUpButton />
87
+ <SelectPrimitive.Viewport
88
+ className={cn(
89
+ "p-1",
90
+ position === "popper" &&
91
+ "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"
92
+ )}
93
+ >
94
+ {children}
95
+ </SelectPrimitive.Viewport>
96
+ <SelectScrollDownButton />
97
+ </SelectPrimitive.Content>
98
+ </SelectPrimitive.Portal>
99
+ ))
100
+ SelectContent.displayName = SelectPrimitive.Content.displayName
101
+
102
+ const SelectLabel = React.forwardRef<
103
+ React.ElementRef<typeof SelectPrimitive.Label>,
104
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
105
+ >(({ className, ...props }, ref) => (
106
+ <SelectPrimitive.Label
107
+ ref={ref}
108
+ className={cn("px-2 py-1.5 text-sm font-semibold", className)}
109
+ {...props}
110
+ />
111
+ ))
112
+ SelectLabel.displayName = SelectPrimitive.Label.displayName
113
+
114
+ const SelectItem = React.forwardRef<
115
+ React.ElementRef<typeof SelectPrimitive.Item>,
116
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
117
+ >(({ className, children, ...props }, ref) => (
118
+ <SelectPrimitive.Item
119
+ ref={ref}
120
+ className={cn(
121
+ "relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
122
+ className
123
+ )}
124
+ {...props}
125
+ >
126
+ <span className="absolute right-2 flex h-3.5 w-3.5 items-center justify-center">
127
+ <SelectPrimitive.ItemIndicator>
128
+ <Check className="h-4 w-4" />
129
+ </SelectPrimitive.ItemIndicator>
130
+ </span>
131
+ <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
132
+ </SelectPrimitive.Item>
133
+ ))
134
+ SelectItem.displayName = SelectPrimitive.Item.displayName
135
+
136
+ const SelectSeparator = React.forwardRef<
137
+ React.ElementRef<typeof SelectPrimitive.Separator>,
138
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
139
+ >(({ className, ...props }, ref) => (
140
+ <SelectPrimitive.Separator
141
+ ref={ref}
142
+ className={cn("-mx-1 my-1 h-px bg-muted", className)}
143
+ {...props}
144
+ />
145
+ ))
146
+ SelectSeparator.displayName = SelectPrimitive.Separator.displayName
147
+
148
+ export {
149
+ Select,
150
+ SelectGroup,
151
+ SelectValue,
152
+ SelectTrigger,
153
+ SelectContent,
154
+ SelectLabel,
155
+ SelectItem,
156
+ SelectSeparator,
157
+ SelectScrollUpButton,
158
+ SelectScrollDownButton,
159
+ }
components/ui/toast.tsx ADDED
@@ -0,0 +1,129 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as ToastPrimitives from "@radix-ui/react-toast"
5
+ import { cva, type VariantProps } from "class-variance-authority"
6
+ import { X } from "lucide-react"
7
+
8
+ import { cn } from "@/lib/utils"
9
+
10
+ const ToastProvider = ToastPrimitives.Provider
11
+
12
+ const ToastViewport = React.forwardRef<
13
+ React.ElementRef<typeof ToastPrimitives.Viewport>,
14
+ React.ComponentPropsWithoutRef<typeof ToastPrimitives.Viewport>
15
+ >(({ className, ...props }, ref) => (
16
+ <ToastPrimitives.Viewport
17
+ ref={ref}
18
+ className={cn(
19
+ "fixed top-0 z-[100] flex max-h-screen w-full flex-col-reverse p-4 sm:bottom-0 sm:right-0 sm:top-auto sm:flex-col md:max-w-[420px]",
20
+ className
21
+ )}
22
+ {...props}
23
+ />
24
+ ))
25
+ ToastViewport.displayName = ToastPrimitives.Viewport.displayName
26
+
27
+ const toastVariants = cva(
28
+ "group pointer-events-auto relative flex w-full items-center justify-between space-x-2 overflow-hidden rounded-md border p-4 pr-6 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full",
29
+ {
30
+ variants: {
31
+ variant: {
32
+ default: "border bg-background text-foreground",
33
+ destructive:
34
+ "destructive group border-destructive bg-destructive text-destructive-foreground",
35
+ },
36
+ },
37
+ defaultVariants: {
38
+ variant: "default",
39
+ },
40
+ }
41
+ )
42
+
43
+ const Toast = React.forwardRef<
44
+ React.ElementRef<typeof ToastPrimitives.Root>,
45
+ React.ComponentPropsWithoutRef<typeof ToastPrimitives.Root> &
46
+ VariantProps<typeof toastVariants>
47
+ >(({ className, variant, ...props }, ref) => {
48
+ return (
49
+ <ToastPrimitives.Root
50
+ ref={ref}
51
+ className={cn(toastVariants({ variant }), className)}
52
+ {...props}
53
+ />
54
+ )
55
+ })
56
+ Toast.displayName = ToastPrimitives.Root.displayName
57
+
58
+ const ToastAction = React.forwardRef<
59
+ React.ElementRef<typeof ToastPrimitives.Action>,
60
+ React.ComponentPropsWithoutRef<typeof ToastPrimitives.Action>
61
+ >(({ className, ...props }, ref) => (
62
+ <ToastPrimitives.Action
63
+ ref={ref}
64
+ className={cn(
65
+ "inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium transition-colors hover:bg-secondary focus:outline-none focus:ring-1 focus:ring-ring disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-muted/40 group-[.destructive]:hover:border-destructive/30 group-[.destructive]:hover:bg-destructive group-[.destructive]:hover:text-destructive-foreground group-[.destructive]:focus:ring-destructive",
66
+ className
67
+ )}
68
+ {...props}
69
+ />
70
+ ))
71
+ ToastAction.displayName = ToastPrimitives.Action.displayName
72
+
73
+ const ToastClose = React.forwardRef<
74
+ React.ElementRef<typeof ToastPrimitives.Close>,
75
+ React.ComponentPropsWithoutRef<typeof ToastPrimitives.Close>
76
+ >(({ className, ...props }, ref) => (
77
+ <ToastPrimitives.Close
78
+ ref={ref}
79
+ className={cn(
80
+ "absolute right-1 top-1 rounded-md p-1 text-foreground/50 opacity-0 transition-opacity hover:text-foreground focus:opacity-100 focus:outline-none focus:ring-1 group-hover:opacity-100 group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600",
81
+ className
82
+ )}
83
+ toast-close=""
84
+ {...props}
85
+ >
86
+ <X className="h-4 w-4" />
87
+ </ToastPrimitives.Close>
88
+ ))
89
+ ToastClose.displayName = ToastPrimitives.Close.displayName
90
+
91
+ const ToastTitle = React.forwardRef<
92
+ React.ElementRef<typeof ToastPrimitives.Title>,
93
+ React.ComponentPropsWithoutRef<typeof ToastPrimitives.Title>
94
+ >(({ className, ...props }, ref) => (
95
+ <ToastPrimitives.Title
96
+ ref={ref}
97
+ className={cn("text-sm font-semibold [&+div]:text-xs", className)}
98
+ {...props}
99
+ />
100
+ ))
101
+ ToastTitle.displayName = ToastPrimitives.Title.displayName
102
+
103
+ const ToastDescription = React.forwardRef<
104
+ React.ElementRef<typeof ToastPrimitives.Description>,
105
+ React.ComponentPropsWithoutRef<typeof ToastPrimitives.Description>
106
+ >(({ className, ...props }, ref) => (
107
+ <ToastPrimitives.Description
108
+ ref={ref}
109
+ className={cn("text-sm opacity-90", className)}
110
+ {...props}
111
+ />
112
+ ))
113
+ ToastDescription.displayName = ToastPrimitives.Description.displayName
114
+
115
+ type ToastProps = React.ComponentPropsWithoutRef<typeof Toast>
116
+
117
+ type ToastActionElement = React.ReactElement<typeof ToastAction>
118
+
119
+ export {
120
+ type ToastProps,
121
+ type ToastActionElement,
122
+ ToastProvider,
123
+ ToastViewport,
124
+ Toast,
125
+ ToastTitle,
126
+ ToastDescription,
127
+ ToastClose,
128
+ ToastAction,
129
+ }
components/ui/toaster.tsx ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import { useToast } from "@/hooks/use-toast"
4
+ import {
5
+ Toast,
6
+ ToastClose,
7
+ ToastDescription,
8
+ ToastProvider,
9
+ ToastTitle,
10
+ ToastViewport,
11
+ } from "@/components/ui/toast"
12
+
13
+ export function Toaster() {
14
+ const { toasts } = useToast()
15
+
16
+ return (
17
+ <ToastProvider>
18
+ {toasts.map(function ({ id, title, description, action, ...props }) {
19
+ return (
20
+ <Toast key={id} {...props}>
21
+ <div className="grid gap-1">
22
+ {title && <ToastTitle>{title}</ToastTitle>}
23
+ {description && (
24
+ <ToastDescription>{description}</ToastDescription>
25
+ )}
26
+ </div>
27
+ {action}
28
+ <ToastClose />
29
+ </Toast>
30
+ )
31
+ })}
32
+ <ToastViewport />
33
+ </ToastProvider>
34
+ )
35
+ }
hooks/api/index.ts ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import {
2
+ imageToText,
3
+ genTextToSpeech,
4
+ getWavAudio,
5
+ uploadImage,
6
+ genTextToSpeechLang,
7
+ getTranslation,
8
+ } from "@/app/services";
9
+ import { useMutation} from "@tanstack/react-query";
10
+
11
+ export const useTextToSpeach = () => {
12
+ return useMutation({
13
+ mutationKey: ["text-to-speech"],
14
+ mutationFn: ({ text }: { text: string }) => genTextToSpeech({ text }),
15
+ });
16
+ };
17
+
18
+ export const useTextToSpeachLang = () => {
19
+ return useMutation({
20
+ mutationKey: ["text-to-speech-lang"],
21
+ mutationFn: ({ text, modal }: { text: string; modal: string }) =>
22
+ genTextToSpeechLang({ text, modal }),
23
+ });
24
+ };
25
+
26
+ export const useGenWav = () => {
27
+ return useMutation({
28
+ mutationKey: ["gen-wav"],
29
+ mutationFn: ({
30
+ sampling_rate,
31
+ audio,
32
+ name,
33
+ }: {
34
+ sampling_rate: number;
35
+ audio: Float32Array;
36
+ name: string;
37
+ }) => getWavAudio({ sampling_rate, audio, name }),
38
+ });
39
+ };
40
+
41
+ export const useImageToText = () => {
42
+ return useMutation({
43
+ mutationKey: ["image-to-text"],
44
+ mutationFn: ({ url }: { url: string }) => imageToText({ url }),
45
+ });
46
+ };
47
+
48
+ export const useUploadImage = () => {
49
+ return useMutation({
50
+ mutationKey: ["upload-image"],
51
+ mutationFn: ({ form }: { form: FormData }) => uploadImage({ form }),
52
+ });
53
+ };
54
+
55
+ export const useTranslator = () => {
56
+ return useMutation({
57
+ mutationKey: ["translator"],
58
+ mutationFn: ({ text, code }: { text: string; code: string }) =>
59
+ getTranslation({ text, code }),
60
+ });
61
+ };