From 174944aeb1c07af267fc36a8227334e22a43238e Mon Sep 17 00:00:00 2001 From: Animesh404 Date: Wed, 30 Oct 2024 19:18:27 +0530 Subject: [PATCH 01/10] WIP: change in default parameter --- openadapt/strategies/base.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openadapt/strategies/base.py b/openadapt/strategies/base.py index de114331b..c1fddc0d3 100644 --- a/openadapt/strategies/base.py +++ b/openadapt/strategies/base.py @@ -7,7 +7,7 @@ from oa_pynput import keyboard, mouse import numpy as np -from openadapt import adapters, models, playback, utils +from openadapt import adapters, config, models, playback, utils from openadapt.custom_logger import logger CHECK_ACTION_COMPLETE = False @@ -21,7 +21,7 @@ def __init__( self, recording: models.Recording, max_frame_times: int = MAX_FRAME_TIMES, - include_a11y_data: bool = True, + include_a11y_data: bool = config.RECORD_READ_ACTIVE_ELEMENT_STATE, ) -> None: """Initialize the BaseReplayStrategy. From 2e3788946ba280136ebeab804be508c05eeacd27 Mon Sep 17 00:00:00 2001 From: Animesh404 Date: Mon, 4 Nov 2024 00:11:20 +0530 Subject: [PATCH 02/10] feat: dashboard UI improvement --- openadapt/app/dashboard/app/layout.tsx | 8 +- .../app/dashboard/app/recordings/page.tsx | 4 +- openadapt/app/dashboard/app/routes.ts | 41 +++-- .../app/settings/(api_keys)/form.tsx | 160 +++++++++++++----- .../app/dashboard/app/settings/navbar.tsx | 2 +- .../dashboard/components/Navbar/Navbar.tsx | 54 ++++-- .../app/dashboard/components/Shell/Shell.tsx | 38 ++--- .../components/SimpleTable/SimpleTable.tsx | 8 +- openadapt/app/dashboard/tailwind.config.js | 24 +++ 9 files changed, 244 insertions(+), 95 deletions(-) diff --git a/openadapt/app/dashboard/app/layout.tsx b/openadapt/app/dashboard/app/layout.tsx index 3cd56c206..cf986e1d6 100644 --- a/openadapt/app/dashboard/app/layout.tsx +++ b/openadapt/app/dashboard/app/layout.tsx @@ -4,6 +4,12 @@ import { ColorSchemeScript, MantineProvider } from '@mantine/core' import { Notifications } from '@mantine/notifications'; import { Shell } from '@/components/Shell' import { CSPostHogProvider } from './providers'; +import { Poppins } from 'next/font/google'; + +const poppins = Poppins({ + subsets: ['latin'], + weight: ['400', '700'], + }); export const metadata = { title: 'OpenAdapt.AI', @@ -20,7 +26,7 @@ export default function RootLayout({ - + diff --git a/openadapt/app/dashboard/app/recordings/page.tsx b/openadapt/app/dashboard/app/recordings/page.tsx index 901444f76..52a35dc3f 100644 --- a/openadapt/app/dashboard/app/recordings/page.tsx +++ b/openadapt/app/dashboard/app/recordings/page.tsx @@ -51,12 +51,12 @@ export default function Recordings() { return ( {recordingStatus === RecordingStatus.RECORDING && ( - )} {recordingStatus === RecordingStatus.STOPPED && ( - )} diff --git a/openadapt/app/dashboard/app/routes.ts b/openadapt/app/dashboard/app/routes.ts index b0ef5f0d0..47425035b 100644 --- a/openadapt/app/dashboard/app/routes.ts +++ b/openadapt/app/dashboard/app/routes.ts @@ -1,23 +1,42 @@ -type Route = { - name: string - path: string -} +import { + IconFileText, + IconSettings, + IconEraser, + IconBook + } from '@tabler/icons-react'; + + + export interface Route { + name: string; + path: string; + icon: typeof IconFileText; // Simple type that works for all Tabler icons + active?: boolean; + badge?: string | number; + } export const routes: Route[] = [ { name: 'Recordings', path: '/recordings', - }, - { + icon: IconFileText, + active: true + }, + { name: 'Settings', path: '/settings', - }, - { + icon: IconSettings, + active: false + }, + { name: 'Scrubbing', path: '/scrubbing', - }, - { + icon: IconEraser, + active: false + }, + { name: 'Onboarding', path: '/onboarding', - } + icon: IconBook, + active: false + } ] diff --git a/openadapt/app/dashboard/app/settings/(api_keys)/form.tsx b/openadapt/app/dashboard/app/settings/(api_keys)/form.tsx index 3afafe87f..83129877d 100644 --- a/openadapt/app/dashboard/app/settings/(api_keys)/form.tsx +++ b/openadapt/app/dashboard/app/settings/(api_keys)/form.tsx @@ -1,20 +1,17 @@ 'use client'; -import { Button, Fieldset, Flex, Grid, Stack, TextInput } from '@mantine/core'; +import React, { useEffect } from 'react'; import { useForm } from '@mantine/form'; -import React, { useEffect } from 'react' import { saveSettings } from '../utils'; type Props = { - settings: Record, + settings: Record; } -export const Form = ({ - settings, -}: Props) => { +export const Form = ({ settings }: Props) => { const form = useForm({ initialValues: JSON.parse(JSON.stringify(settings)), - }) + }); useEffect(() => { form.setValues(JSON.parse(JSON.stringify(settings))); @@ -24,41 +21,122 @@ export const Form = ({ function resetForm() { form.reset(); } + + const inputClasses = "w-full px-3 py-2 border border-gray-200 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none transition-all duration-200 bg-white"; + const labelClasses = "block text-sm font-medium text-gray-700 mb-1"; + const fieldsetClasses = "border border-gray-200 rounded-xl p-6 bg-zinc-100 shadow-lg relative mt-2"; + const legendClasses = "absolute -top-3 bg-white px-2 text-sm font-semibold text-gray-800 shadow-lg rounded-lg"; + return ( -
- - -
- - - -
-
- -
- - - -
-
- -
- - - - - -
-
-
- - - + - + +
- ) -} + ); +}; diff --git a/openadapt/app/dashboard/app/settings/navbar.tsx b/openadapt/app/dashboard/app/settings/navbar.tsx index 0080d225b..78002bb1e 100644 --- a/openadapt/app/dashboard/app/settings/navbar.tsx +++ b/openadapt/app/dashboard/app/settings/navbar.tsx @@ -21,7 +21,7 @@ export const Navbar = ({ href={route.path} key={route.path} className={ - (currentRoute === route.path ? 'bg-gray-200' : '') + + (currentRoute === route.path ? 'bg-gray-300 rounded-lg' : '') + ' p-5 no-underline flex items-center gap-x-1 transition hover:gap-x-2 ease-out' } > diff --git a/openadapt/app/dashboard/components/Navbar/Navbar.tsx b/openadapt/app/dashboard/components/Navbar/Navbar.tsx index f75aac37a..29018f5fc 100644 --- a/openadapt/app/dashboard/components/Navbar/Navbar.tsx +++ b/openadapt/app/dashboard/components/Navbar/Navbar.tsx @@ -2,27 +2,57 @@ import { usePathname } from 'next/navigation' import { routes } from '@/app/routes' -import { Stack } from '@mantine/core' import Link from 'next/link' import React from 'react' import { IconChevronRight } from '@tabler/icons-react' export const Navbar = () => { const currentRoute = usePathname() + return ( - +
{routes.map((route) => ( - - {route.name} + +
+ + {route.name} + + + {route.badge && ( + + {route.badge} + + )} +
))} - +
) } diff --git a/openadapt/app/dashboard/components/Shell/Shell.tsx b/openadapt/app/dashboard/components/Shell/Shell.tsx index 9f0523ed2..35f9c5548 100644 --- a/openadapt/app/dashboard/components/Shell/Shell.tsx +++ b/openadapt/app/dashboard/components/Shell/Shell.tsx @@ -4,45 +4,35 @@ import { AppShell, Box, Burger, Image, Text } from '@mantine/core' import React from 'react' import { Navbar } from '../Navbar' import { useDisclosure } from '@mantine/hooks' -import logo from '../../assets/logo.png' +import logo from '../../../assets/logo_inverted.png' type Props = { children: React.ReactNode } export const Shell = ({ children }: Props) => { - const [opened, { toggle }] = useDisclosure() return ( - - - - OpenAdapt - - OpenAdapt.AI - + + + + OpenAdapt + + OpenAdapt.AI + + - - - - {children} + {children} ) } diff --git a/openadapt/app/dashboard/components/SimpleTable/SimpleTable.tsx b/openadapt/app/dashboard/components/SimpleTable/SimpleTable.tsx index b80ae2838..b56329350 100644 --- a/openadapt/app/dashboard/components/SimpleTable/SimpleTable.tsx +++ b/openadapt/app/dashboard/components/SimpleTable/SimpleTable.tsx @@ -20,10 +20,12 @@ export function SimpleTable>({ }: Props) { return ( - - +
{columns.map(({name}) => ( @@ -33,7 +35,7 @@ export function SimpleTable>({ {data.map((row, rowIndex) => ( - + {columns.map(({accessor}, accesorIndex) => ( {typeof accessor === 'string' ? row[accessor] : accessor(row)} diff --git a/openadapt/app/dashboard/tailwind.config.js b/openadapt/app/dashboard/tailwind.config.js index e994c1e45..46b1a1799 100644 --- a/openadapt/app/dashboard/tailwind.config.js +++ b/openadapt/app/dashboard/tailwind.config.js @@ -11,6 +11,30 @@ module.exports = { 'gradient-conic': 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))', }, + fontSize: { + sm: '0.750rem', + base: '1rem', + xl: '1.333rem', + '2xl': '1.777rem', + '3xl': '2.369rem', + '4xl': '3.158rem', + '5xl': '4.210rem', + }, + fontFamily: { + heading: ['var(--font-poppins)'], + body: ['var(--font-poppins)'], + }, + fontWeight: { + normal: '400', + bold: '700', + }, + colors: { + 'text': '#e7e5e4', + 'background': '#00001f', + 'primary': '#2f27ce', + 'secondary': '#6259a1', + 'accent': '#6c78f9', + }, }, }, plugins: [], From c9165416b5fd85a2db99bec784cb883ba39a6381 Mon Sep 17 00:00:00 2001 From: Animesh404 Date: Sat, 9 Nov 2024 13:25:59 +0530 Subject: [PATCH 03/10] feat: table UI --- .../app/dashboard/app/recordings/RawRecordings.tsx | 4 ++-- .../components/SimpleTable/SimpleTable.tsx | 13 +++++++++---- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/openadapt/app/dashboard/app/recordings/RawRecordings.tsx b/openadapt/app/dashboard/app/recordings/RawRecordings.tsx index 17b6c413b..a958bc225 100644 --- a/openadapt/app/dashboard/app/recordings/RawRecordings.tsx +++ b/openadapt/app/dashboard/app/recordings/RawRecordings.tsx @@ -32,9 +32,9 @@ export const RawRecordings = () => { {name: 'ID', accessor: 'id'}, {name: 'Description', accessor: 'task_description'}, {name: 'Start time', accessor: (recording: Recording) => recording.video_start_time ? timeStampToDateString(recording.video_start_time) : 'N/A'}, - {name: 'Timestamp', accessor: (recording: Recording) => recording.timestamp ? timeStampToDateString(recording.timestamp) : 'N/A'}, + // {name: 'Timestamp', accessor: (recording: Recording) => recording.timestamp ? timeStampToDateString(recording.timestamp) : 'N/A'}, {name: 'Monitor Width/Height', accessor: (recording: Recording) => `${recording.monitor_width}/${recording.monitor_height}`}, - {name: 'Double Click Interval Seconds/Pixels', accessor: (recording: Recording) => `${recording.double_click_interval_seconds}/${recording.double_click_distance_pixels}`}, + // {name: 'Double Click Interval Seconds/Pixels', accessor: (recording: Recording) => `${recording.double_click_interval_seconds}/${recording.double_click_distance_pixels}`}, ]} data={recordings} refreshData={fetchRecordings} diff --git a/openadapt/app/dashboard/components/SimpleTable/SimpleTable.tsx b/openadapt/app/dashboard/components/SimpleTable/SimpleTable.tsx index b56329350..147d05cbb 100644 --- a/openadapt/app/dashboard/components/SimpleTable/SimpleTable.tsx +++ b/openadapt/app/dashboard/components/SimpleTable/SimpleTable.tsx @@ -19,23 +19,28 @@ export function SimpleTable>({ onClickRow, }: Props) { return ( - + -
+
{columns.map(({name}) => ( - {name} + {name} ))} {data.map((row, rowIndex) => ( - + {columns.map(({accessor}, accesorIndex) => ( {typeof accessor === 'string' ? row[accessor] : accessor(row)} From 41f252bc2afe94ef3fb66deb847d9dd58f57fa60 Mon Sep 17 00:00:00 2001 From: Animesh404 Date: Sat, 16 Nov 2024 21:53:16 +0530 Subject: [PATCH 04/10] feat: dashboard improvement with animated screenshots --- openadapt/app/dashboard/api/recordings.py | 22 + .../app/recordings/RawRecordings.tsx | 83 ++- .../app/settings/(api_keys)/form.tsx | 2 +- .../components/FramePlayer/FramePlayer.tsx | 120 ++++ .../components/FramePlayer/index.tsx | 1 + openadapt/app/dashboard/next.config.js | 76 +-- openadapt/app/dashboard/package-lock.json | 511 +++++++++++++++++- openadapt/app/dashboard/package.json | 2 + openadapt/app/dashboard/types/recording.ts | 1 + 9 files changed, 766 insertions(+), 52 deletions(-) create mode 100644 openadapt/app/dashboard/components/FramePlayer/FramePlayer.tsx create mode 100644 openadapt/app/dashboard/components/FramePlayer/index.tsx diff --git a/openadapt/app/dashboard/api/recordings.py b/openadapt/app/dashboard/api/recordings.py index 074e99f95..4b58a1d0c 100644 --- a/openadapt/app/dashboard/api/recordings.py +++ b/openadapt/app/dashboard/api/recordings.py @@ -29,6 +29,9 @@ def attach_routes(self) -> APIRouter: self.app.add_api_route("/start", self.start_recording) self.app.add_api_route("/stop", self.stop_recording) self.app.add_api_route("/status", self.recording_status) + self.app.add_api_route( + "/{recording_id}/screenshots", self.get_recording_screenshots + ) self.recording_detail_route() return self.app @@ -63,6 +66,25 @@ def recording_status() -> dict[str, bool]: """Get the recording status.""" return {"recording": cards.is_recording()} + @staticmethod + def get_recording_screenshots(recording_id: int) -> dict[str, list[str]]: + """Get all screenshots for a specific recording.""" + session = crud.get_new_session(read_only=True) + recording = crud.get_recording_by_id(session, recording_id) + action_events = get_events(session, recording) + + screenshots = [] + for action_event in action_events: + try: + image = display_event(action_event) + if image: + screenshots.append(image2utf8(image)) + except Exception as e: + logger.info(f"Failed to display event: {e}") + continue + + return {"screenshots": screenshots} + def recording_detail_route(self) -> None: """Add the recording detail route as a websocket.""" diff --git a/openadapt/app/dashboard/app/recordings/RawRecordings.tsx b/openadapt/app/dashboard/app/recordings/RawRecordings.tsx index a958bc225..710a0942b 100644 --- a/openadapt/app/dashboard/app/recordings/RawRecordings.tsx +++ b/openadapt/app/dashboard/app/recordings/RawRecordings.tsx @@ -1,21 +1,59 @@ import { SimpleTable } from '@/components/SimpleTable'; +import dynamic from 'next/dynamic'; import { Recording } from '@/types/recording'; -import React, { useEffect, useState } from 'react' +import React, { useEffect, useState } from 'react'; import { timeStampToDateString } from '../utils'; import { useRouter } from 'next/navigation'; +const FramePlayer = dynamic<{ recording: Recording; frameRate: number }>( + () => import('@/components/FramePlayer').then((mod) => mod.FramePlayer), + { ssr: false } +); + +async function fetchRecordingWithScreenshots(recordingId: string | number) { + try { + const response = await fetch(`/api/recordings/${recordingId}/screenshots`); + if (!response.ok) { + throw new Error('Failed to fetch screenshots'); + } + const data = await response.json(); + return data.screenshots || []; + } catch (error) { + console.error('Error fetching screenshots:', error); + return []; + } +} + export const RawRecordings = () => { const [recordings, setRecordings] = useState([]); + const [loading, setLoading] = useState(true); const router = useRouter(); - function fetchRecordings() { - fetch('/api/recordings').then(res => { + async function fetchRecordings() { + try { + setLoading(true); + const res = await fetch('/api/recordings'); if (res.ok) { - res.json().then((data) => { - setRecordings(data.recordings); - }); + const data = await res.json(); + + // Fetch screenshots for all recordings in parallel + const recordingsWithScreenshots = await Promise.all( + data.recordings.map(async (rec: Recording) => { + const screenshots = await fetchRecordingWithScreenshots(rec.id); + return { + ...rec, + screenshots + }; + }) + ); + + setRecordings(recordingsWithScreenshots); } - }) + } catch (error) { + console.error('Error fetching recordings:', error); + } finally { + setLoading(false); + } } useEffect(() => { @@ -26,19 +64,38 @@ export const RawRecordings = () => { return () => router.push(`/recordings/detail/?id=${recording.id}`); } + if (loading) { + return
Loading recordings...
; + } + return ( recording.video_start_time ? timeStampToDateString(recording.video_start_time) : 'N/A'}, - // {name: 'Timestamp', accessor: (recording: Recording) => recording.timestamp ? timeStampToDateString(recording.timestamp) : 'N/A'}, - {name: 'Monitor Width/Height', accessor: (recording: Recording) => `${recording.monitor_width}/${recording.monitor_height}`}, - // {name: 'Double Click Interval Seconds/Pixels', accessor: (recording: Recording) => `${recording.double_click_interval_seconds}/${recording.double_click_distance_pixels}`}, + {name: 'Start time', accessor: (recording: Recording) => + recording.video_start_time + ? timeStampToDateString(recording.video_start_time) + : 'N/A' + }, + {name: 'Monitor Width/Height', accessor: (recording: Recording) => + `${recording.monitor_width}/${recording.monitor_height}` + }, + { + name: 'Video', + accessor: (recording: Recording) => ( +
+ +
+ ), + } ]} data={recordings} refreshData={fetchRecordings} onClickRow={onClickRow} /> - ) -} + ); +}; diff --git a/openadapt/app/dashboard/app/settings/(api_keys)/form.tsx b/openadapt/app/dashboard/app/settings/(api_keys)/form.tsx index 83129877d..fdd850ed4 100644 --- a/openadapt/app/dashboard/app/settings/(api_keys)/form.tsx +++ b/openadapt/app/dashboard/app/settings/(api_keys)/form.tsx @@ -25,7 +25,7 @@ export const Form = ({ settings }: Props) => { const inputClasses = "w-full px-3 py-2 border border-gray-200 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none transition-all duration-200 bg-white"; const labelClasses = "block text-sm font-medium text-gray-700 mb-1"; const fieldsetClasses = "border border-gray-200 rounded-xl p-6 bg-zinc-100 shadow-lg relative mt-2"; - const legendClasses = "absolute -top-3 bg-white px-2 text-sm font-semibold text-gray-800 shadow-lg rounded-lg"; + const legendClasses = "absolute -top-3 bg-primary/80 px-2 text-sm font-semibold text-zinc-200 shadow-lg rounded-lg"; return (
diff --git a/openadapt/app/dashboard/components/FramePlayer/FramePlayer.tsx b/openadapt/app/dashboard/components/FramePlayer/FramePlayer.tsx new file mode 100644 index 000000000..6396c0f14 --- /dev/null +++ b/openadapt/app/dashboard/components/FramePlayer/FramePlayer.tsx @@ -0,0 +1,120 @@ +"use client" + +import { Recording } from '@/types/recording'; +import React, { useEffect, useRef, useState } from "react"; +import { Stage, Layer, Image as KonvaImage, Group, Rect } from "react-konva"; +import Konva from "konva"; + +interface FramePlayerProps { + recording: Recording; + frameRate: number; +} + +export const FramePlayer: React.FC = ({ recording, frameRate }) => { + const [currentScreenshotIndex, setCurrentScreenshotIndex] = useState(0); + const [currentImage, setCurrentImage] = useState(null); + const [isHovering, setIsHovering] = useState(false); + const imageRef = useRef(null); + const intervalRef = useRef(null); + + const STAGE_WIDTH = 300; + const STAGE_HEIGHT = 200; + const CORNER_RADIUS = 8; + + // Load initial image + useEffect(() => { + if (!recording?.screenshots?.length) return; + + const img = new window.Image(); + img.src = recording.screenshots[0]; + img.onload = () => { + setCurrentImage(img); + }; + }, [recording]); + + useEffect(() => { + if (!recording?.screenshots?.length ) { + if (isHovering && currentScreenshotIndex !== 0) { + setCurrentScreenshotIndex(0); + } + return; + } + + intervalRef.current = setInterval(() => { + setCurrentScreenshotIndex((prevIndex) => { + return (prevIndex + 1) % recording.screenshots.length; + }); + }, 1000 / frameRate); + + return () => { + if (intervalRef.current) { + clearInterval(intervalRef.current); + } + }; + }, [recording, frameRate, isHovering]); + + // Handle image loading when screenshot index changes + useEffect(() => { + if (!recording?.screenshots?.length) return; + + const img = new window.Image(); + img.src = recording.screenshots[currentScreenshotIndex]; + img.onload = () => { + setCurrentImage(img); + }; + }, [currentScreenshotIndex, recording]); + + if (!recording?.screenshots?.length) { + return ( +
+ No screenshots +
+ ); + } + + return ( +
setIsHovering(true)} + onMouseLeave={() => { + setIsHovering(false); + setCurrentScreenshotIndex(0); + }} + > + + + { + ctx.beginPath(); + ctx.moveTo(CORNER_RADIUS, 0); + ctx.lineTo(STAGE_WIDTH - CORNER_RADIUS, 0); + ctx.quadraticCurveTo(STAGE_WIDTH, 0, STAGE_WIDTH, CORNER_RADIUS); + ctx.lineTo(STAGE_WIDTH, STAGE_HEIGHT - CORNER_RADIUS); + ctx.quadraticCurveTo(STAGE_WIDTH, STAGE_HEIGHT, STAGE_WIDTH - CORNER_RADIUS, STAGE_HEIGHT); + ctx.lineTo(CORNER_RADIUS, STAGE_HEIGHT); + ctx.quadraticCurveTo(0, STAGE_HEIGHT, 0, STAGE_HEIGHT - CORNER_RADIUS); + ctx.lineTo(0, CORNER_RADIUS); + ctx.quadraticCurveTo(0, 0, CORNER_RADIUS, 0); + ctx.closePath(); + }} + > + {currentImage && ( + + )} + + + + {!isHovering && ( +
+ Frame {currentScreenshotIndex + 1}/{recording.screenshots.length} +
+ )} +
+ ); +}; diff --git a/openadapt/app/dashboard/components/FramePlayer/index.tsx b/openadapt/app/dashboard/components/FramePlayer/index.tsx new file mode 100644 index 000000000..45049f9c9 --- /dev/null +++ b/openadapt/app/dashboard/components/FramePlayer/index.tsx @@ -0,0 +1 @@ +export { FramePlayer } from './FramePlayer' diff --git a/openadapt/app/dashboard/next.config.js b/openadapt/app/dashboard/next.config.js index f2e91fdb1..d91416a93 100644 --- a/openadapt/app/dashboard/next.config.js +++ b/openadapt/app/dashboard/next.config.js @@ -1,36 +1,48 @@ /** @type {import('next').NextConfig} */ -// get values from environment variables -const { DASHBOARD_SERVER_PORT } = process.env +// Get values from environment variables +const { DASHBOARD_SERVER_PORT } = process.env; const nextConfig = { - rewrites: async () => { - return [ - { - source: '/', - destination: `/recordings`, - }, - { - source: '/api/:path*', - destination: `http://127.0.0.1:${DASHBOARD_SERVER_PORT}/api/:path*` - }, - { - source: '/docs', - destination: - process.env.NODE_ENV === 'development' - ? `http://127.0.0.1:${DASHBOARD_SERVER_PORT}/docs` - : '/api/docs', - }, - { - source: '/openapi.json', - destination: - process.env.NODE_ENV === 'development' - ? `http://127.0.0.1:${DASHBOARD_SERVER_PORT}/openapi.json` - : '/api/openapi.json', - }, - ] - }, - output: 'export', - reactStrictMode: false, -} + async rewrites() { + return [ + { + source: '/', + destination: `/recordings`, + }, + { + source: '/api/:path*', + destination: `http://127.0.0.1:${DASHBOARD_SERVER_PORT}/api/:path*`, + }, + { + source: '/docs', + destination: + process.env.NODE_ENV === 'development' + ? `http://127.0.0.1:${DASHBOARD_SERVER_PORT}/docs` + : '/api/docs', + }, + { + source: '/openapi.json', + destination: + process.env.NODE_ENV === 'development' + ? `http://127.0.0.1:${DASHBOARD_SERVER_PORT}/openapi.json` + : '/api/openapi.json', + }, + ]; + }, -module.exports = nextConfig + webpack: (config, { isServer }) => { + if (!isServer) { + // Prevent `canvas` from being bundled on the client side + config.resolve.alias['canvas'] = false; + } else { + // Exclude canvas from the server bundle, treating it as an external dependency for Node + config.externals.push({ canvas: 'commonjs canvas' }); + } + return config; + }, + + output: 'export', + reactStrictMode: false, +}; + +module.exports = nextConfig; diff --git a/openadapt/app/dashboard/package-lock.json b/openadapt/app/dashboard/package-lock.json index c939b78fc..959310413 100644 --- a/openadapt/app/dashboard/package-lock.json +++ b/openadapt/app/dashboard/package-lock.json @@ -20,6 +20,7 @@ "@types/react": "18.2.7", "@types/react-dom": "18.2.4", "autoprefixer": "10.4.14", + "canvas": "^2.11.2", "concurrently": "^8.0.1", "eslint": "8.41.0", "eslint-config-next": "^14.1.4", @@ -29,6 +30,7 @@ "posthog-js": "^1.128.3", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-konva": "^18.2.10", "tailwindcss": "3.3.2", "typescript": "5.0.4" }, @@ -425,6 +427,25 @@ "react": "^18.2.0" } }, + "node_modules/@mapbox/node-pre-gyp": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", + "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==", + "dependencies": { + "detect-libc": "^2.0.0", + "https-proxy-agent": "^5.0.0", + "make-dir": "^3.1.0", + "node-fetch": "^2.6.7", + "nopt": "^5.0.0", + "npmlog": "^5.0.1", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.11" + }, + "bin": { + "node-pre-gyp": "bin/node-pre-gyp" + } + }, "node_modules/@next/env": { "version": "14.1.4", "resolved": "https://registry.npmjs.org/@next/env/-/env-14.1.4.tgz", @@ -765,6 +786,14 @@ "@types/react": "*" } }, + "node_modules/@types/react-reconciler": { + "version": "0.28.8", + "resolved": "https://registry.npmjs.org/@types/react-reconciler/-/react-reconciler-0.28.8.tgz", + "integrity": "sha512-SN9c4kxXZonFhbX4hJrZy37yw9e7EIxcpHCxQv5JUS18wDE5ovkQKlqQEkufdJCCMfuI9BnjUJvhYeJ9x5Ra7g==", + "dependencies": { + "@types/react": "*" + } + }, "node_modules/@types/scheduler": { "version": "0.16.3", "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz", @@ -866,6 +895,11 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" + }, "node_modules/acorn": { "version": "8.8.2", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", @@ -885,6 +919,17 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -939,6 +984,24 @@ "node": ">= 8" } }, + "node_modules/aproba": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==" + }, + "node_modules/are-we-there-yet": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", + "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", + "deprecated": "This package is no longer supported.", + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/arg": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", @@ -1339,6 +1402,20 @@ } ] }, + "node_modules/canvas": { + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/canvas/-/canvas-2.11.2.tgz", + "integrity": "sha512-ItanGBMrmRV7Py2Z+Xhs7cT+FNt5K0vPL4p9EZ/UX/Mu7hFbkxSjKF2KVtPwX7UYWp7dRKnrTvReflgrItJbdw==", + "hasInstallScript": true, + "dependencies": { + "@mapbox/node-pre-gyp": "^1.0.0", + "nan": "^2.17.0", + "simple-get": "^3.0.3" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -1391,6 +1468,14 @@ "node": ">= 6" } }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "engines": { + "node": ">=10" + } + }, "node_modules/client-only": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", @@ -1433,6 +1518,14 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, + "node_modules/color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "bin": { + "color-support": "bin.js" + } + }, "node_modules/commander": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", @@ -1486,6 +1579,11 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==" + }, "node_modules/copy-anything": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-3.0.5.tgz", @@ -1613,6 +1711,17 @@ } } }, + "node_modules/decompress-response": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz", + "integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==", + "dependencies": { + "mimic-response": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/deep-equal": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.1.tgz", @@ -1721,6 +1830,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==" + }, + "node_modules/detect-libc": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", + "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", + "engines": { + "node": ">=8" + } + }, "node_modules/detect-node-es": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", @@ -2629,6 +2751,28 @@ "url": "https://www.patreon.com/infusion" } }, + "node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -2680,6 +2824,26 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/gauge": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", + "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", + "deprecated": "This package is no longer supported.", + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.2", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.1", + "object-assign": "^4.1.1", + "signal-exit": "^3.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.2" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", @@ -2921,6 +3085,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==" + }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", @@ -2932,6 +3101,18 @@ "node": ">= 0.4" } }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/human-signals": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-4.3.1.tgz", @@ -3448,6 +3629,17 @@ "set-function-name": "^2.0.1" } }, + "node_modules/its-fine": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/its-fine/-/its-fine-1.2.5.tgz", + "integrity": "sha512-fXtDA0X0t0eBYAGLVM5YsgJGsJ5jEmqZEPrGbzdf5awjv0xE7nqv3TVnvtUF060Tkes15DbDAKW/I48vsb6SyA==", + "dependencies": { + "@types/react-reconciler": "^0.28.0" + }, + "peerDependencies": { + "react": ">=18.0" + } + }, "node_modules/jackspeak": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", @@ -3530,6 +3722,26 @@ "node": ">= 8" } }, + "node_modules/konva": { + "version": "9.3.16", + "resolved": "https://registry.npmjs.org/konva/-/konva-9.3.16.tgz", + "integrity": "sha512-qa47cefGDDHzkToGRGDsy24f/Njrz7EHP56jQ8mlDcjAPO7vkfTDeoBDIfmF7PZtpfzDdooafQmEUJMDU2F7FQ==", + "funding": [ + { + "type": "patreon", + "url": "https://www.patreon.com/lavrton" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/konva" + }, + { + "type": "github", + "url": "https://github.com/sponsors/lavrton" + } + ], + "peer": true + }, "node_modules/language-subtag-registry": { "version": "0.3.22", "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.22.tgz", @@ -3614,6 +3826,28 @@ "node": ">=10" } }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -3650,6 +3884,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/mimic-response": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz", + "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -3677,6 +3922,40 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/moment": { "version": "2.30.1", "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", @@ -3700,6 +3979,11 @@ "thenify-all": "^1.0.0" } }, + "node_modules/nan": { + "version": "2.22.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.22.0.tgz", + "integrity": "sha512-nbajikzWTMwsW+eSsNm3QwlOs7het9gGJU5dDZzRTQGk03vyBOauxgI4VakDzE0PtsGTmXPsXTbbjVhRwR5mpw==" + }, "node_modules/nanoid": { "version": "3.3.6", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", @@ -3794,11 +4078,44 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, "node_modules/node-releases": { "version": "2.0.12", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.12.tgz", "integrity": "sha512-QzsYKWhXTWx8h1kIvqfnC++o0pEmpRQA/aenALsL2F4pqNVr7YzcdMlDij5WBnwftRbJCNJL/O7zdKaxKPHqgQ==" }, + "node_modules/nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -3840,6 +4157,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/npmlog": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", + "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", + "deprecated": "This package is no longer supported.", + "dependencies": { + "are-we-there-yet": "^2.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^3.0.0", + "set-blocking": "^2.0.0" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -4376,9 +4705,9 @@ ] }, "node_modules/react": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", - "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "dependencies": { "loose-envify": "^1.1.0" }, @@ -4403,6 +4732,36 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, + "node_modules/react-konva": { + "version": "18.2.10", + "resolved": "https://registry.npmjs.org/react-konva/-/react-konva-18.2.10.tgz", + "integrity": "sha512-ohcX1BJINL43m4ynjZ24MxFI1syjBdrXhqVxYVDw2rKgr3yuS0x/6m1Y2Z4sl4T/gKhfreBx8KHisd0XC6OT1g==", + "funding": [ + { + "type": "patreon", + "url": "https://www.patreon.com/lavrton" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/konva" + }, + { + "type": "github", + "url": "https://github.com/sponsors/lavrton" + } + ], + "dependencies": { + "@types/react-reconciler": "^0.28.2", + "its-fine": "^1.1.1", + "react-reconciler": "~0.29.0", + "scheduler": "^0.23.0" + }, + "peerDependencies": { + "konva": "^8.0.1 || ^7.2.5 || ^9.0.0", + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, "node_modules/react-number-format": { "version": "5.3.4", "resolved": "https://registry.npmjs.org/react-number-format/-/react-number-format-5.3.4.tgz", @@ -4415,6 +4774,21 @@ "react-dom": "^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0" } }, + "node_modules/react-reconciler": { + "version": "0.29.2", + "resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.29.2.tgz", + "integrity": "sha512-zZQqIiYgDCTP/f1N/mAR10nJGrPD2ZR+jDSEsKWJHYC7Cm2wodlwbR3upZRdC3cjIjSlTLNVyO7Iu0Yy7t2AYg==", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "engines": { + "node": ">=0.10.0" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, "node_modules/react-remove-scroll": { "version": "2.5.9", "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.9.tgz", @@ -4521,6 +4895,19 @@ "pify": "^2.3.0" } }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -4772,6 +5159,25 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/safe-regex-test": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz", @@ -4789,9 +5195,9 @@ } }, "node_modules/scheduler": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", - "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", "dependencies": { "loose-envify": "^1.1.0" } @@ -4810,6 +5216,11 @@ "node": ">=10" } }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" + }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", @@ -4889,6 +5300,35 @@ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/simple-get": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.1.tgz", + "integrity": "sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA==", + "dependencies": { + "decompress-response": "^4.2.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, "node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -4929,6 +5369,14 @@ "node": ">=10.0.0" } }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, "node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -5250,6 +5698,30 @@ "node": ">=6" } }, + "node_modules/tar": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tar/node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "engines": { + "node": ">=8" + } + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -5296,6 +5768,11 @@ "node": ">=8.0" } }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, "node_modules/tree-kill": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", @@ -5589,6 +6066,20 @@ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -5675,6 +6166,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, "node_modules/word-wrap": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", diff --git a/openadapt/app/dashboard/package.json b/openadapt/app/dashboard/package.json index 787ee08b8..30ed659b3 100644 --- a/openadapt/app/dashboard/package.json +++ b/openadapt/app/dashboard/package.json @@ -27,6 +27,7 @@ "@types/react": "18.2.7", "@types/react-dom": "18.2.4", "autoprefixer": "10.4.14", + "canvas": "^2.11.2", "concurrently": "^8.0.1", "eslint": "8.41.0", "eslint-config-next": "^14.1.4", @@ -36,6 +37,7 @@ "posthog-js": "^1.128.3", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-konva": "^18.2.10", "tailwindcss": "3.3.2", "typescript": "5.0.4" }, diff --git a/openadapt/app/dashboard/types/recording.ts b/openadapt/app/dashboard/types/recording.ts index 40aeb84cf..fec7bea7b 100644 --- a/openadapt/app/dashboard/types/recording.ts +++ b/openadapt/app/dashboard/types/recording.ts @@ -9,6 +9,7 @@ export type Recording = { task_description: string; video_start_time: number | null; original_recording_id: number | null; + screenshots: string[]; } export enum RecordingStatus { From 15b2a99eaa308ec4710e43d90b4bba51c0fd7f34 Mon Sep 17 00:00:00 2001 From: Animesh404 Date: Tue, 19 Nov 2024 23:07:42 +0530 Subject: [PATCH 05/10] add group opacity change on hover --- .../app/dashboard/components/SimpleTable/SimpleTable.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openadapt/app/dashboard/components/SimpleTable/SimpleTable.tsx b/openadapt/app/dashboard/components/SimpleTable/SimpleTable.tsx index 147d05cbb..f7b12d08d 100644 --- a/openadapt/app/dashboard/components/SimpleTable/SimpleTable.tsx +++ b/openadapt/app/dashboard/components/SimpleTable/SimpleTable.tsx @@ -38,11 +38,11 @@ export function SimpleTable>({ {data.map((row, rowIndex) => ( - - {columns.map(({accessor}, accesorIndex) => ( - + {columns.map(({accessor, name: columnName}, accesorIndex) => ( + {typeof accessor === 'string' ? row[accessor] : accessor(row)} ))} From e7e42b91f9e28853b5eb5b5c5e8ec812d48ef6ed Mon Sep 17 00:00:00 2001 From: Animesh Kumar Jha <60042503+Animesh404@users.noreply.github.com> Date: Wed, 20 Nov 2024 10:26:25 +0530 Subject: [PATCH 06/10] remove unwanted changes --- openadapt/strategies/base.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openadapt/strategies/base.py b/openadapt/strategies/base.py index c1fddc0d3..de114331b 100644 --- a/openadapt/strategies/base.py +++ b/openadapt/strategies/base.py @@ -7,7 +7,7 @@ from oa_pynput import keyboard, mouse import numpy as np -from openadapt import adapters, config, models, playback, utils +from openadapt import adapters, models, playback, utils from openadapt.custom_logger import logger CHECK_ACTION_COMPLETE = False @@ -21,7 +21,7 @@ def __init__( self, recording: models.Recording, max_frame_times: int = MAX_FRAME_TIMES, - include_a11y_data: bool = config.RECORD_READ_ACTIVE_ELEMENT_STATE, + include_a11y_data: bool = True, ) -> None: """Initialize the BaseReplayStrategy. From e203afbd1240faa2a319b87ef72a54c09b699c20 Mon Sep 17 00:00:00 2001 From: Animesh404 Date: Mon, 20 Jan 2025 01:37:28 +0530 Subject: [PATCH 07/10] fix: css fix for recording details page and bountrycard --- .../components/Onboarding/steps/Tutorial.tsx | 2 +- .../app/dashboard/components/Shell/Shell.tsx | 5 +- openadapt/app/dashboard/next.config.js | 84 +++++++++---------- 3 files changed, 47 insertions(+), 44 deletions(-) diff --git a/openadapt/app/dashboard/components/Onboarding/steps/Tutorial.tsx b/openadapt/app/dashboard/components/Onboarding/steps/Tutorial.tsx index 65c9bbd0e..8638abd38 100644 --- a/openadapt/app/dashboard/components/Onboarding/steps/Tutorial.tsx +++ b/openadapt/app/dashboard/components/Onboarding/steps/Tutorial.tsx @@ -13,7 +13,7 @@ function BountyCard(props: { bounty: Bounty }) { href={props.bounty.task.url} target="_blank" rel="noopener" - className="block group relative h-full rounded-lg border border-gray-400/50 dark:border-indigo-500/50 bg-gradient-to-br from-gray-300/30 via-gray-300/40 to-gray-300/50 dark:from-indigo-600/20 dark:via-indigo-600/30 dark:to-indigo-600/40 md:gap-8 transition-colors hover:border-gray-400 hover:dark:border-indigo-500 hover:bg-gray-300/10 hover:dark:bg-gray-600/5 !no-underline" + className="block group relative h-full rounded-lg bg-background/80 border-gray-400/50 hover:border-indigo-500 hover:bg-background !no-underline" >
diff --git a/openadapt/app/dashboard/components/Shell/Shell.tsx b/openadapt/app/dashboard/components/Shell/Shell.tsx index 35f9c5548..fdf4d9305 100644 --- a/openadapt/app/dashboard/components/Shell/Shell.tsx +++ b/openadapt/app/dashboard/components/Shell/Shell.tsx @@ -3,6 +3,7 @@ import { AppShell, Box, Burger, Image, Text } from '@mantine/core' import React from 'react' import { Navbar } from '../Navbar' +import { usePathname } from 'next/navigation' import { useDisclosure } from '@mantine/hooks' import logo from '../../../assets/logo_inverted.png' @@ -11,6 +12,8 @@ type Props = { } export const Shell = ({ children }: Props) => { + const pathname = usePathname() + const isRecordingDetails = pathname?.includes('/recordings/detail') return ( { - {children} + {children} ) } diff --git a/openadapt/app/dashboard/next.config.js b/openadapt/app/dashboard/next.config.js index d91416a93..f924297d7 100644 --- a/openadapt/app/dashboard/next.config.js +++ b/openadapt/app/dashboard/next.config.js @@ -1,48 +1,48 @@ /** @type {import('next').NextConfig} */ -// Get values from environment variables -const { DASHBOARD_SERVER_PORT } = process.env; +// get values from environment variables +const { DASHBOARD_SERVER_PORT } = process.env const nextConfig = { - async rewrites() { - return [ - { - source: '/', - destination: `/recordings`, - }, - { - source: '/api/:path*', - destination: `http://127.0.0.1:${DASHBOARD_SERVER_PORT}/api/:path*`, - }, - { - source: '/docs', - destination: - process.env.NODE_ENV === 'development' - ? `http://127.0.0.1:${DASHBOARD_SERVER_PORT}/docs` - : '/api/docs', - }, - { - source: '/openapi.json', - destination: - process.env.NODE_ENV === 'development' - ? `http://127.0.0.1:${DASHBOARD_SERVER_PORT}/openapi.json` - : '/api/openapi.json', - }, - ]; - }, + rewrites: async () => { + return [ + { + source: '/', + destination: `/recordings`, + }, + { + source: '/api/:path*', + destination: `http://127.0.0.1:${DASHBOARD_SERVER_PORT}/api/:path*`, + }, + { + source: '/docs', + destination: + process.env.NODE_ENV === 'development' + ? `http://127.0.0.1:${DASHBOARD_SERVER_PORT}/docs` + : '/api/docs', + }, + { + source: '/openapi.json', + destination: + process.env.NODE_ENV === 'development' + ? `http://127.0.0.1:${DASHBOARD_SERVER_PORT}/openapi.json` + : '/api/openapi.json', + }, + ] + }, - webpack: (config, { isServer }) => { - if (!isServer) { - // Prevent `canvas` from being bundled on the client side - config.resolve.alias['canvas'] = false; - } else { - // Exclude canvas from the server bundle, treating it as an external dependency for Node - config.externals.push({ canvas: 'commonjs canvas' }); - } - return config; - }, + webpack: (config, { isServer }) => { + if (!isServer) { + // Prevent `canvas` from being bundled on the client side + config.resolve.alias['canvas'] = false + } else { + // Exclude canvas from the server bundle, treating it as an external dependency for Node + config.externals.push({ canvas: 'commonjs canvas' }) + } + return config + }, - output: 'export', - reactStrictMode: false, -}; + output: 'export', + reactStrictMode: false, +} -module.exports = nextConfig; +module.exports = nextConfig From 06a513864554ab5b618d20eb4cca01c1dfdad781 Mon Sep 17 00:00:00 2001 From: Animesh Kumar Jha <60042503+Animesh404@users.noreply.github.com> Date: Mon, 20 Jan 2025 01:40:05 +0530 Subject: [PATCH 08/10] Update openadapt/app/dashboard/app/layout.tsx Co-authored-by: Richard Abrich --- openadapt/app/dashboard/app/layout.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openadapt/app/dashboard/app/layout.tsx b/openadapt/app/dashboard/app/layout.tsx index cf986e1d6..a029f8bb8 100644 --- a/openadapt/app/dashboard/app/layout.tsx +++ b/openadapt/app/dashboard/app/layout.tsx @@ -9,7 +9,7 @@ import { Poppins } from 'next/font/google'; const poppins = Poppins({ subsets: ['latin'], weight: ['400', '700'], - }); +}); export const metadata = { title: 'OpenAdapt.AI', From b6f5c408395a046cea01500add731e0330db568a Mon Sep 17 00:00:00 2001 From: Animesh404 Date: Mon, 20 Jan 2025 01:42:47 +0530 Subject: [PATCH 09/10] used 4 space indentation --- .../components/FramePlayer/FramePlayer.tsx | 230 ++++++++++-------- 1 file changed, 128 insertions(+), 102 deletions(-) diff --git a/openadapt/app/dashboard/components/FramePlayer/FramePlayer.tsx b/openadapt/app/dashboard/components/FramePlayer/FramePlayer.tsx index 6396c0f14..b036ee6bc 100644 --- a/openadapt/app/dashboard/components/FramePlayer/FramePlayer.tsx +++ b/openadapt/app/dashboard/components/FramePlayer/FramePlayer.tsx @@ -1,120 +1,146 @@ -"use client" +'use client' -import { Recording } from '@/types/recording'; -import React, { useEffect, useRef, useState } from "react"; -import { Stage, Layer, Image as KonvaImage, Group, Rect } from "react-konva"; -import Konva from "konva"; +import { Recording } from '@/types/recording' +import React, { useEffect, useRef, useState } from 'react' +import { Stage, Layer, Image as KonvaImage, Group, Rect } from 'react-konva' +import Konva from 'konva' interface FramePlayerProps { - recording: Recording; - frameRate: number; + recording: Recording + frameRate: number } -export const FramePlayer: React.FC = ({ recording, frameRate }) => { - const [currentScreenshotIndex, setCurrentScreenshotIndex] = useState(0); - const [currentImage, setCurrentImage] = useState(null); - const [isHovering, setIsHovering] = useState(false); - const imageRef = useRef(null); - const intervalRef = useRef(null); +export const FramePlayer: React.FC = ({ + recording, + frameRate, +}) => { + const [currentScreenshotIndex, setCurrentScreenshotIndex] = useState(0) + const [currentImage, setCurrentImage] = useState( + null + ) + const [isHovering, setIsHovering] = useState(false) + const imageRef = useRef(null) + const intervalRef = useRef(null) - const STAGE_WIDTH = 300; - const STAGE_HEIGHT = 200; - const CORNER_RADIUS = 8; + const STAGE_WIDTH = 300 + const STAGE_HEIGHT = 200 + const CORNER_RADIUS = 8 - // Load initial image - useEffect(() => { - if (!recording?.screenshots?.length) return; + // Load initial image + useEffect(() => { + if (!recording?.screenshots?.length) return - const img = new window.Image(); - img.src = recording.screenshots[0]; - img.onload = () => { - setCurrentImage(img); - }; - }, [recording]); + const img = new window.Image() + img.src = recording.screenshots[0] + img.onload = () => { + setCurrentImage(img) + } + }, [recording]) - useEffect(() => { - if (!recording?.screenshots?.length ) { - if (isHovering && currentScreenshotIndex !== 0) { - setCurrentScreenshotIndex(0); - } - return; - } + useEffect(() => { + if (!recording?.screenshots?.length) { + if (isHovering && currentScreenshotIndex !== 0) { + setCurrentScreenshotIndex(0) + } + return + } - intervalRef.current = setInterval(() => { - setCurrentScreenshotIndex((prevIndex) => { - return (prevIndex + 1) % recording.screenshots.length; - }); - }, 1000 / frameRate); + intervalRef.current = setInterval(() => { + setCurrentScreenshotIndex((prevIndex) => { + return (prevIndex + 1) % recording.screenshots.length + }) + }, 1000 / frameRate) - return () => { - if (intervalRef.current) { - clearInterval(intervalRef.current); - } - }; - }, [recording, frameRate, isHovering]); + return () => { + if (intervalRef.current) { + clearInterval(intervalRef.current) + } + } + }, [recording, frameRate, isHovering]) - // Handle image loading when screenshot index changes - useEffect(() => { - if (!recording?.screenshots?.length) return; + // Handle image loading when screenshot index changes + useEffect(() => { + if (!recording?.screenshots?.length) return - const img = new window.Image(); - img.src = recording.screenshots[currentScreenshotIndex]; - img.onload = () => { - setCurrentImage(img); - }; - }, [currentScreenshotIndex, recording]); + const img = new window.Image() + img.src = recording.screenshots[currentScreenshotIndex] + img.onload = () => { + setCurrentImage(img) + } + }, [currentScreenshotIndex, recording]) - if (!recording?.screenshots?.length) { - return ( -
- No screenshots -
- ); - } + if (!recording?.screenshots?.length) { + return ( +
+ No screenshots +
+ ) + } - return ( -
setIsHovering(true)} - onMouseLeave={() => { - setIsHovering(false); - setCurrentScreenshotIndex(0); - }} - > - - - { - ctx.beginPath(); - ctx.moveTo(CORNER_RADIUS, 0); - ctx.lineTo(STAGE_WIDTH - CORNER_RADIUS, 0); - ctx.quadraticCurveTo(STAGE_WIDTH, 0, STAGE_WIDTH, CORNER_RADIUS); - ctx.lineTo(STAGE_WIDTH, STAGE_HEIGHT - CORNER_RADIUS); - ctx.quadraticCurveTo(STAGE_WIDTH, STAGE_HEIGHT, STAGE_WIDTH - CORNER_RADIUS, STAGE_HEIGHT); - ctx.lineTo(CORNER_RADIUS, STAGE_HEIGHT); - ctx.quadraticCurveTo(0, STAGE_HEIGHT, 0, STAGE_HEIGHT - CORNER_RADIUS); - ctx.lineTo(0, CORNER_RADIUS); - ctx.quadraticCurveTo(0, 0, CORNER_RADIUS, 0); - ctx.closePath(); + return ( +
setIsHovering(true)} + onMouseLeave={() => { + setIsHovering(false) + setCurrentScreenshotIndex(0) }} - > - {currentImage && ( - + > + + + { + ctx.beginPath() + ctx.moveTo(CORNER_RADIUS, 0) + ctx.lineTo(STAGE_WIDTH - CORNER_RADIUS, 0) + ctx.quadraticCurveTo( + STAGE_WIDTH, + 0, + STAGE_WIDTH, + CORNER_RADIUS + ) + ctx.lineTo( + STAGE_WIDTH, + STAGE_HEIGHT - CORNER_RADIUS + ) + ctx.quadraticCurveTo( + STAGE_WIDTH, + STAGE_HEIGHT, + STAGE_WIDTH - CORNER_RADIUS, + STAGE_HEIGHT + ) + ctx.lineTo(CORNER_RADIUS, STAGE_HEIGHT) + ctx.quadraticCurveTo( + 0, + STAGE_HEIGHT, + 0, + STAGE_HEIGHT - CORNER_RADIUS + ) + ctx.lineTo(0, CORNER_RADIUS) + ctx.quadraticCurveTo(0, 0, CORNER_RADIUS, 0) + ctx.closePath() + }} + > + {currentImage && ( + + )} + + + + {!isHovering && ( +
+ + Frame {currentScreenshotIndex + 1}/ + {recording.screenshots.length} + +
)} - - - - {!isHovering && ( -
- Frame {currentScreenshotIndex + 1}/{recording.screenshots.length}
- )} -
- ); -}; + ) +} From bd3e9230145a471fa732d60d9d00f160025706a5 Mon Sep 17 00:00:00 2001 From: Animesh404 Date: Mon, 20 Jan 2025 20:24:59 +0530 Subject: [PATCH 10/10] fix: formatting fix via prettier --- openadapt/app/dashboard/app/layout.tsx | 12 +- .../app/recordings/RawRecordings.tsx | 95 +++++++------- openadapt/app/dashboard/app/routes.ts | 41 +++---- .../app/settings/(api_keys)/form.tsx | 60 ++++----- .../app/dashboard/app/settings/navbar.tsx | 18 +-- .../dashboard/components/Navbar/Navbar.tsx | 24 ++-- .../components/Onboarding/steps/Tutorial.tsx | 116 +++++++++++------- .../app/dashboard/components/Shell/Shell.tsx | 24 ++-- .../components/SimpleTable/SimpleTable.tsx | 110 +++++++++++------ openadapt/app/dashboard/tailwind.config.js | 24 ++-- openadapt/app/dashboard/types/recording.ts | 35 +++--- 11 files changed, 318 insertions(+), 241 deletions(-) diff --git a/openadapt/app/dashboard/app/layout.tsx b/openadapt/app/dashboard/app/layout.tsx index a029f8bb8..6b1798e33 100644 --- a/openadapt/app/dashboard/app/layout.tsx +++ b/openadapt/app/dashboard/app/layout.tsx @@ -1,15 +1,15 @@ import './globals.css' import { ColorSchemeScript, MantineProvider } from '@mantine/core' -import { Notifications } from '@mantine/notifications'; +import { Notifications } from '@mantine/notifications' import { Shell } from '@/components/Shell' -import { CSPostHogProvider } from './providers'; -import { Poppins } from 'next/font/google'; +import { CSPostHogProvider } from './providers' +import { Poppins } from 'next/font/google' const poppins = Poppins({ subsets: ['latin'], weight: ['400', '700'], -}); +}) export const metadata = { title: 'OpenAdapt.AI', @@ -29,9 +29,7 @@ export default function RootLayout({ - - {children} - + {children} diff --git a/openadapt/app/dashboard/app/recordings/RawRecordings.tsx b/openadapt/app/dashboard/app/recordings/RawRecordings.tsx index 710a0942b..15be80223 100644 --- a/openadapt/app/dashboard/app/recordings/RawRecordings.tsx +++ b/openadapt/app/dashboard/app/recordings/RawRecordings.tsx @@ -1,101 +1,106 @@ -import { SimpleTable } from '@/components/SimpleTable'; -import dynamic from 'next/dynamic'; -import { Recording } from '@/types/recording'; -import React, { useEffect, useState } from 'react'; -import { timeStampToDateString } from '../utils'; -import { useRouter } from 'next/navigation'; +import { SimpleTable } from '@/components/SimpleTable' +import dynamic from 'next/dynamic' +import { Recording } from '@/types/recording' +import React, { useEffect, useState } from 'react' +import { timeStampToDateString } from '../utils' +import { useRouter } from 'next/navigation' const FramePlayer = dynamic<{ recording: Recording; frameRate: number }>( () => import('@/components/FramePlayer').then((mod) => mod.FramePlayer), { ssr: false } -); +) async function fetchRecordingWithScreenshots(recordingId: string | number) { try { - const response = await fetch(`/api/recordings/${recordingId}/screenshots`); + const response = await fetch( + `/api/recordings/${recordingId}/screenshots` + ) if (!response.ok) { - throw new Error('Failed to fetch screenshots'); + throw new Error('Failed to fetch screenshots') } - const data = await response.json(); - return data.screenshots || []; + const data = await response.json() + return data.screenshots || [] } catch (error) { - console.error('Error fetching screenshots:', error); - return []; + console.error('Error fetching screenshots:', error) + return [] } } export const RawRecordings = () => { - const [recordings, setRecordings] = useState([]); - const [loading, setLoading] = useState(true); - const router = useRouter(); + const [recordings, setRecordings] = useState([]) + const [loading, setLoading] = useState(true) + const router = useRouter() async function fetchRecordings() { try { - setLoading(true); - const res = await fetch('/api/recordings'); + setLoading(true) + const res = await fetch('/api/recordings') if (res.ok) { - const data = await res.json(); + const data = await res.json() // Fetch screenshots for all recordings in parallel const recordingsWithScreenshots = await Promise.all( data.recordings.map(async (rec: Recording) => { - const screenshots = await fetchRecordingWithScreenshots(rec.id); + const screenshots = await fetchRecordingWithScreenshots( + rec.id + ) return { ...rec, - screenshots - }; + screenshots, + } }) - ); + ) - setRecordings(recordingsWithScreenshots); + setRecordings(recordingsWithScreenshots) } } catch (error) { - console.error('Error fetching recordings:', error); + console.error('Error fetching recordings:', error) } finally { - setLoading(false); + setLoading(false) } } useEffect(() => { - fetchRecordings(); - }, []); + fetchRecordings() + }, []) function onClickRow(recording: Recording) { - return () => router.push(`/recordings/detail/?id=${recording.id}`); + return () => router.push(`/recordings/detail/?id=${recording.id}`) } if (loading) { - return
Loading recordings...
; + return
Loading recordings...
} return ( - recording.video_start_time - ? timeStampToDateString(recording.video_start_time) - : 'N/A' + { name: 'ID', accessor: 'id' }, + { name: 'Description', accessor: 'task_description' }, + { + name: 'Start time', + accessor: (recording: Recording) => + recording.video_start_time + ? timeStampToDateString(recording.video_start_time) + : 'N/A', }, - {name: 'Monitor Width/Height', accessor: (recording: Recording) => - `${recording.monitor_width}/${recording.monitor_height}` + { + name: 'Monitor Width/Height', + accessor: (recording: Recording) => + `${recording.monitor_width}/${recording.monitor_height}`, }, { name: 'Video', accessor: (recording: Recording) => (
- +
), - } + }, ]} data={recordings} refreshData={fetchRecordings} onClickRow={onClickRow} /> - ); -}; + ) +} diff --git a/openadapt/app/dashboard/app/routes.ts b/openadapt/app/dashboard/app/routes.ts index 47425035b..2ac2713a1 100644 --- a/openadapt/app/dashboard/app/routes.ts +++ b/openadapt/app/dashboard/app/routes.ts @@ -2,41 +2,40 @@ import { IconFileText, IconSettings, IconEraser, - IconBook - } from '@tabler/icons-react'; + IconBook, +} from '@tabler/icons-react' - - export interface Route { - name: string; - path: string; - icon: typeof IconFileText; // Simple type that works for all Tabler icons - active?: boolean; - badge?: string | number; - } +export interface Route { + name: string + path: string + icon: typeof IconFileText // Simple type that works for all Tabler icons + active?: boolean + badge?: string | number +} export const routes: Route[] = [ { name: 'Recordings', path: '/recordings', icon: IconFileText, - active: true - }, - { + active: true, + }, + { name: 'Settings', path: '/settings', icon: IconSettings, - active: false - }, - { + active: false, + }, + { name: 'Scrubbing', path: '/scrubbing', icon: IconEraser, - active: false - }, - { + active: false, + }, + { name: 'Onboarding', path: '/onboarding', icon: IconBook, - active: false - } + active: false, + }, ] diff --git a/openadapt/app/dashboard/app/settings/(api_keys)/form.tsx b/openadapt/app/dashboard/app/settings/(api_keys)/form.tsx index fdd850ed4..10fb2662e 100644 --- a/openadapt/app/dashboard/app/settings/(api_keys)/form.tsx +++ b/openadapt/app/dashboard/app/settings/(api_keys)/form.tsx @@ -1,41 +1,45 @@ -'use client'; +'use client' -import React, { useEffect } from 'react'; -import { useForm } from '@mantine/form'; -import { saveSettings } from '../utils'; +import React, { useEffect } from 'react' +import { useForm } from '@mantine/form' +import { saveSettings } from '../utils' type Props = { - settings: Record; + settings: Record } export const Form = ({ settings }: Props) => { const form = useForm({ initialValues: JSON.parse(JSON.stringify(settings)), - }); + }) useEffect(() => { - form.setValues(JSON.parse(JSON.stringify(settings))); - form.setInitialValues(JSON.parse(JSON.stringify(settings))); - }, [settings]); + form.setValues(JSON.parse(JSON.stringify(settings))) + form.setInitialValues(JSON.parse(JSON.stringify(settings))) + }, [settings]) function resetForm() { - form.reset(); + form.reset() } - const inputClasses = "w-full px-3 py-2 border border-gray-200 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none transition-all duration-200 bg-white"; - const labelClasses = "block text-sm font-medium text-gray-700 mb-1"; - const fieldsetClasses = "border border-gray-200 rounded-xl p-6 bg-zinc-100 shadow-lg relative mt-2"; - const legendClasses = "absolute -top-3 bg-primary/80 px-2 text-sm font-semibold text-zinc-200 shadow-lg rounded-lg"; + const inputClasses = + 'w-full px-3 py-2 border border-gray-200 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none transition-all duration-200 bg-white' + const labelClasses = 'block text-sm font-medium text-gray-700 mb-1' + const fieldsetClasses = + 'border border-gray-200 rounded-xl p-6 bg-zinc-100 shadow-lg relative mt-2' + const legendClasses = + 'absolute -top-3 bg-primary/80 px-2 text-sm font-semibold text-zinc-200 shadow-lg rounded-lg' return ( - +
{/* Privacy Section */}
- - PRIVACY - + PRIVACY
@@ -55,9 +61,7 @@ export const Form = ({ settings }: Props) => { {/* Segmentation Section */}
- - SEGMENTATION - + SEGMENTATION
@@ -77,9 +83,7 @@ export const Form = ({ settings }: Props) => { {/* Completions Section */}
- - COMPLETIONS - + COMPLETIONS
- ); -}; + ) +} diff --git a/openadapt/app/dashboard/app/settings/navbar.tsx b/openadapt/app/dashboard/app/settings/navbar.tsx index 78002bb1e..e93987a61 100644 --- a/openadapt/app/dashboard/app/settings/navbar.tsx +++ b/openadapt/app/dashboard/app/settings/navbar.tsx @@ -1,18 +1,16 @@ -'use client'; +'use client' -import { Stack } from '@mantine/core'; -import { IconChevronRight } from '@tabler/icons-react'; -import Link from 'next/link'; -import { usePathname } from 'next/navigation'; +import { Stack } from '@mantine/core' +import { IconChevronRight } from '@tabler/icons-react' +import Link from 'next/link' +import { usePathname } from 'next/navigation' import React from 'react' type Props = { routes: { name: string; path: string }[] } -export const Navbar = ({ - routes -}: Props) => { +export const Navbar = ({ routes }: Props) => { const currentRoute = usePathname() return ( @@ -21,7 +19,9 @@ export const Navbar = ({ href={route.path} key={route.path} className={ - (currentRoute === route.path ? 'bg-gray-300 rounded-lg' : '') + + (currentRoute === route.path + ? 'bg-gray-300 rounded-lg' + : '') + ' p-5 no-underline flex items-center gap-x-1 transition hover:gap-x-2 ease-out' } > diff --git a/openadapt/app/dashboard/components/Navbar/Navbar.tsx b/openadapt/app/dashboard/components/Navbar/Navbar.tsx index 29018f5fc..216449407 100644 --- a/openadapt/app/dashboard/components/Navbar/Navbar.tsx +++ b/openadapt/app/dashboard/components/Navbar/Navbar.tsx @@ -16,9 +16,11 @@ export const Navbar = () => {
@@ -26,9 +28,11 @@ export const Navbar = () => { size={20} className={` mr-3 transition-transform duration-200 - ${currentRoute.includes(route.path) - ? 'text-blue-400' - : 'text-gray-400 group-hover:text-white'} + ${ + currentRoute.includes(route.path) + ? 'text-blue-400' + : 'text-gray-400 group-hover:text-white' + } `} stroke={1.5} /> @@ -37,9 +41,11 @@ export const Navbar = () => { size={18} className={` ml-2 transition-transform duration-200 - ${currentRoute.includes(route.path) - ? 'text-blue-400' - : 'text-gray-400 group-hover:text-white'} + ${ + currentRoute.includes(route.path) + ? 'text-blue-400' + : 'text-gray-400 group-hover:text-white' + } group-hover:translate-x-2 `} stroke={1.5} diff --git a/openadapt/app/dashboard/components/Onboarding/steps/Tutorial.tsx b/openadapt/app/dashboard/components/Onboarding/steps/Tutorial.tsx index 8638abd38..5ed25c115 100644 --- a/openadapt/app/dashboard/components/Onboarding/steps/Tutorial.tsx +++ b/openadapt/app/dashboard/components/Onboarding/steps/Tutorial.tsx @@ -1,60 +1,84 @@ -'use client'; +'use client' import { Box, Text } from '@mantine/core' -import { algora, type AlgoraOutput } from '@algora/sdk'; +import { algora, type AlgoraOutput } from '@algora/sdk' import React, { useEffect } from 'react' -import Link from 'next/link'; +import Link from 'next/link' -type Bounty = AlgoraOutput['bounty']['list']['items'][number]; +type Bounty = AlgoraOutput['bounty']['list']['items'][number] function BountyCard(props: { bounty: Bounty }) { - return ( - -
-
- {props.bounty.reward_formatted} -
-
- {props.bounty.task.repo_name}#{props.bounty.task.number} -
-
- {props.bounty.task.title} -
-
- - ); + return ( + +
+
+ {props.bounty.reward_formatted} +
+
+ {props.bounty.task.repo_name}#{props.bounty.task.number} +
+
+ {props.bounty.task.title} +
+
+ + ) } -const featuredBountyId = 'clxi7tk210002l20aqlz58ram'; +const featuredBountyId = 'clxi7tk210002l20aqlz58ram' async function getFeaturedBounty() { - const bounty: Bounty = await algora.bounty.get.query({ id: featuredBountyId }); - return bounty; + const bounty: Bounty = await algora.bounty.get.query({ + id: featuredBountyId, + }) + return bounty } export const Tutorial = () => { - const [featuredBounty, setFeaturedBounty] = React.useState(null); - useEffect(() => { - getFeaturedBounty().then(setFeaturedBounty); - }, []); - return ( - - - Welcome to OpenAdapt! Thank you for joining us on our mission to build open source desktop AI. Your feedback is extremely valuable! - - - To start, please watch the demonstration below. Then try it yourself! If you have any issues, please submit a Github Issue. - - - - If {"you'd"} like to contribute directly to our development, please consider the following open Bounties (no development experience required): - {featuredBounty && } - - - ) + const [featuredBounty, setFeaturedBounty] = React.useState( + null + ) + useEffect(() => { + getFeaturedBounty().then(setFeaturedBounty) + }, []) + return ( + + + Welcome to OpenAdapt! Thank you for joining us on our mission to + build open source desktop AI. Your feedback is extremely + valuable! + + + To start, please watch the demonstration below. Then try it + yourself! If you have any issues, please submit a{' '} + + Github Issue. + + + + + If {"you'd"} like to contribute directly to our development, + please consider the following open Bounties (no development + experience required): + {featuredBounty && } + + + ) } diff --git a/openadapt/app/dashboard/components/Shell/Shell.tsx b/openadapt/app/dashboard/components/Shell/Shell.tsx index fdf4d9305..5dba076c4 100644 --- a/openadapt/app/dashboard/components/Shell/Shell.tsx +++ b/openadapt/app/dashboard/components/Shell/Shell.tsx @@ -16,26 +16,32 @@ export const Shell = ({ children }: Props) => { const isRecordingDetails = pathname?.includes('/recordings/detail') return ( - + - - OpenAdapt.AI - + OpenAdapt.AI - {children} + + {children} + ) } diff --git a/openadapt/app/dashboard/components/SimpleTable/SimpleTable.tsx b/openadapt/app/dashboard/components/SimpleTable/SimpleTable.tsx index f7b12d08d..707cd9a16 100644 --- a/openadapt/app/dashboard/components/SimpleTable/SimpleTable.tsx +++ b/openadapt/app/dashboard/components/SimpleTable/SimpleTable.tsx @@ -1,15 +1,17 @@ -import { Button, Flex, Table } from "@mantine/core" +import { Button, Flex, Table } from '@mantine/core' import { IconRefresh } from '@tabler/icons-react' -import React from "react" +import React from 'react' type Props> = { columns: { - name: string, - accessor: string | ((row: T) => React.ReactNode), - }[]; - data: T[], - refreshData: () => void, - onClickRow: (row: T) => (event: React.MouseEvent) => void, + name: string + accessor: string | ((row: T) => React.ReactNode) + }[] + data: T[] + refreshData: () => void + onClickRow: ( + row: T + ) => (event: React.MouseEvent) => void } export function SimpleTable>({ @@ -18,38 +20,72 @@ export function SimpleTable>({ refreshData, onClickRow, }: Props) { - return ( - - -
} + variant="outline" + onClick={refreshData} + className="hover:bg-accent bg-primary/80 hover:text-zinc-100 text-zinc-200 hover:text-white" + > + Refresh + +
- - - {columns.map(({name}) => ( - {name} - ))} - - - - {data.map((row, rowIndex) => ( - - {columns.map(({accessor, name: columnName}, accesorIndex) => ( - - {typeof accessor === 'string' ? row[accessor] : accessor(row)} - + group" + > + + + {columns.map(({ name }) => ( + + {name} + ))} - ))} - -
-
- ) + + + {data.map((row, rowIndex) => ( + + {columns.map( + ( + { accessor, name: columnName }, + accesorIndex + ) => ( + + {typeof accessor === 'string' + ? row[accessor] + : accessor(row)} + + ) + )} + + ))} + + + + ) } diff --git a/openadapt/app/dashboard/tailwind.config.js b/openadapt/app/dashboard/tailwind.config.js index 46b1a1799..d6557e263 100644 --- a/openadapt/app/dashboard/tailwind.config.js +++ b/openadapt/app/dashboard/tailwind.config.js @@ -19,22 +19,22 @@ module.exports = { '3xl': '2.369rem', '4xl': '3.158rem', '5xl': '4.210rem', - }, - fontFamily: { + }, + fontFamily: { heading: ['var(--font-poppins)'], body: ['var(--font-poppins)'], - }, - fontWeight: { + }, + fontWeight: { normal: '400', bold: '700', - }, - colors: { - 'text': '#e7e5e4', - 'background': '#00001f', - 'primary': '#2f27ce', - 'secondary': '#6259a1', - 'accent': '#6c78f9', - }, + }, + colors: { + text: '#e7e5e4', + background: '#00001f', + primary: '#2f27ce', + secondary: '#6259a1', + accent: '#6c78f9', + }, }, }, plugins: [], diff --git a/openadapt/app/dashboard/types/recording.ts b/openadapt/app/dashboard/types/recording.ts index fec7bea7b..1497b7050 100644 --- a/openadapt/app/dashboard/types/recording.ts +++ b/openadapt/app/dashboard/types/recording.ts @@ -1,15 +1,15 @@ export type Recording = { - id: number; - timestamp: number; - monitor_width: number; - monitor_height: number; - double_click_interval_seconds: number; - double_click_distance_pixels: number; - platform: string; - task_description: string; - video_start_time: number | null; - original_recording_id: number | null; - screenshots: string[]; + id: number + timestamp: number + monitor_width: number + monitor_height: number + double_click_interval_seconds: number + double_click_distance_pixels: number + platform: string + task_description: string + video_start_time: number | null + original_recording_id: number | null + screenshots: string[] } export enum RecordingStatus { @@ -18,12 +18,11 @@ export enum RecordingStatus { UNKNOWN = 'UNKNOWN', } - export type ScrubbedRecording = { - id: number; - recording_id: number; - recording: Pick; - provider: string; - original_recording: Pick; - scrubbed: boolean; + id: number + recording_id: number + recording: Pick + provider: string + original_recording: Pick + scrubbed: boolean }