Skip to content

Commit 598fd96

Browse files
committed
feat: ocr
1 parent bcc307b commit 598fd96

File tree

5 files changed

+79
-12
lines changed

5 files changed

+79
-12
lines changed

ui/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"version": "2025.09.03.2100",
55
"type": "module",
66
"engines": {
7-
"node": "22.15.0"
7+
"node": "^22.15.0"
88
},
99
"scripts": {
1010
"dev": "./dev_device.sh",
@@ -78,6 +78,7 @@
7878
"prettier": "^3.6.2",
7979
"prettier-plugin-tailwindcss": "^0.6.14",
8080
"tailwindcss": "^4.1.12",
81+
"tesseract.js": "6.0.1",
8182
"typescript": "^5.9.2",
8283
"vite": "^7.1.4",
8384
"vite-tsconfig-paths": "^5.1.4"

ui/src/components/ActionBar.tsx

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { MdOutlineContentPasteGo } from "react-icons/md";
1+
import { MdOutlineContentPasteGo, MdOutlineDocumentScanner } from "react-icons/md";
22
import { LuCable, LuHardDrive, LuMaximize, LuSettings, LuSignal } from "react-icons/lu";
33
import { FaKeyboard } from "react-icons/fa6";
44
import { Popover, PopoverButton, PopoverPanel } from "@headlessui/react";
@@ -19,11 +19,14 @@ import WakeOnLanModal from "@/components/popovers/WakeOnLan/Index";
1919
import MountPopopover from "@/components/popovers/MountPopover";
2020
import ExtensionPopover from "@/components/popovers/ExtensionPopover";
2121
import { useDeviceUiNavigation } from "@/hooks/useAppNavigation";
22+
import OCRModal from "./popovers/OCRModal";
2223

2324
export default function Actionbar({
2425
requestFullscreen,
26+
videoElmRef,
2527
}: {
2628
requestFullscreen: () => Promise<void>;
29+
videoElmRef?: React.RefObject<HTMLVideoElement | null>;
2730
}) {
2831
const { navigateTo } = useDeviceUiNavigation();
2932
const { isVirtualKeyboardEnabled, setVirtualKeyboardEnabled } = useHidStore();
@@ -99,6 +102,36 @@ export default function Actionbar({
99102
}}
100103
</PopoverPanel>
101104
</Popover>
105+
<Popover>
106+
<PopoverButton as={Fragment}>
107+
<Button
108+
size="XS"
109+
theme="light"
110+
text="OCR"
111+
LeadingIcon={MdOutlineDocumentScanner}
112+
onClick={() => {
113+
setDisableVideoFocusTrap(true);
114+
}}
115+
/>
116+
</PopoverButton>
117+
<PopoverPanel
118+
anchor="bottom start"
119+
transition
120+
className={cx(
121+
"z-10 flex w-[420px] origin-top flex-col overflow-visible!",
122+
"flex origin-top flex-col transition duration-300 ease-out data-closed:translate-y-8 data-closed:opacity-0",
123+
)}
124+
>
125+
{({ open }) => {
126+
checkIfStateChanged(open);
127+
return (
128+
<div className="mx-auto w-full max-w-xl">
129+
<OCRModal videoElmRef={videoElmRef} />
130+
</div>
131+
);
132+
}}
133+
</PopoverPanel>
134+
</Popover>
102135
<div className="relative">
103136
<Popover>
104137
<PopoverButton as={Fragment}>

ui/src/components/WebRTCVideo.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -487,7 +487,7 @@ export default function WebRTCVideo() {
487487
disabled={peerConnection?.connectionState !== "connected"}
488488
className="contents"
489489
>
490-
<Actionbar requestFullscreen={requestFullscreen} />
490+
<Actionbar requestFullscreen={requestFullscreen} videoElmRef={videoElm} />
491491
<MacroBar />
492492
</fieldset>
493493
</div>

ui/src/hooks/useOCR.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { type WorkerOptions } from "tesseract.js";
2+
3+
export type ImageLike = string | HTMLImageElement | HTMLCanvasElement | HTMLVideoElement
4+
| CanvasRenderingContext2D | File | Blob | OffscreenCanvas;
5+
6+
// tesseract.js is h
7+
async function ocrImage(
8+
language: string | string[],
9+
image: ImageLike,
10+
options?: Partial<WorkerOptions>,
11+
) {
12+
const { createWorker } = await import('tesseract.js')
13+
const worker = await createWorker(language, undefined, options)
14+
const { data: { text } } = await worker.recognize(image)
15+
await worker.terminate()
16+
return text
17+
}
18+
19+
export default function useOCR() {
20+
return {
21+
ocrImage,
22+
}
23+
}

ui/vite.config.ts

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -31,20 +31,30 @@ export default defineConfig(({ mode, command }) => {
3131
esbuild: {
3232
pure: ["console.debug"],
3333
},
34-
build: { outDir: isCloud ? "dist" : "../static" },
34+
build: {
35+
outDir: isCloud ? "dist" : "../static",
36+
rollupOptions: {
37+
external: ["tesseract.js"],
38+
output: {
39+
paths: {
40+
"tesseract.js": "https://cdn.jsdelivr.net/npm/[email protected]/dist/tesseract.esm.min.js",
41+
},
42+
},
43+
},
44+
},
3545
server: {
3646
host: "0.0.0.0",
3747
https: useSSL,
3848
proxy: JETKVM_PROXY_URL
3949
? {
40-
"/me": JETKVM_PROXY_URL,
41-
"/device": JETKVM_PROXY_URL,
42-
"/webrtc": JETKVM_PROXY_URL,
43-
"/auth": JETKVM_PROXY_URL,
44-
"/storage": JETKVM_PROXY_URL,
45-
"/cloud": JETKVM_PROXY_URL,
46-
"/developer": JETKVM_PROXY_URL,
47-
}
50+
"/me": JETKVM_PROXY_URL,
51+
"/device": JETKVM_PROXY_URL,
52+
"/webrtc": JETKVM_PROXY_URL,
53+
"/auth": JETKVM_PROXY_URL,
54+
"/storage": JETKVM_PROXY_URL,
55+
"/cloud": JETKVM_PROXY_URL,
56+
"/developer": JETKVM_PROXY_URL,
57+
}
4858
: undefined,
4959
},
5060
base: onDevice && command === "build" ? "/static" : "/",

0 commit comments

Comments
 (0)