├── src
├── types
│ ├── global.d.ts
│ ├── index.tsx
│ ├── solutions.ts
│ └── electron.d.ts
├── components
│ ├── ui
│ │ ├── card.tsx
│ │ ├── dialog.tsx
│ │ └── toast.tsx
│ ├── Queue
│ │ ├── ScreenshotQueue.tsx
│ │ ├── ScreenshotItem.tsx
│ │ └── QueueCommands.tsx
│ ├── Chat
│ │ └── ChatModal.tsx
│ ├── Solutions
│ │ └── SolutionCommands.tsx
│ └── Audio
│ │ └── AudioTranscriber.tsx
├── vite-env.d.ts
├── index.css
├── lib
│ └── utils.ts
├── main.tsx
├── App.tsx
└── _pages
│ └── Queue.tsx
├── renderer
├── src
│ ├── react-app-env.d.ts
│ ├── setupTests.ts
│ ├── App.test.tsx
│ ├── index.css
│ ├── reportWebVitals.ts
│ ├── index.tsx
│ ├── App.tsx
│ ├── App.css
│ └── logo.svg
├── public
│ ├── robots.txt
│ ├── favicon.ico
│ ├── logo192.png
│ ├── logo512.png
│ ├── manifest.json
│ └── index.html
├── .gitignore
├── tsconfig.json
├── package.json
└── README.md
├── bun.lockb
├── assets
├── .DS_Store
└── icons
│ ├── mac
│ └── icon.icns
│ ├── win
│ └── icon.ico
│ └── png
│ └── icon-256x256.png
├── postcss.config.js
├── tsconfig.node.json
├── .gitattributes
├── electron
├── tsconfig.json
├── ipcHandlers.ts
├── shortcuts.ts
├── ScreenshotHelper.ts
├── ProcessingHelper.ts
├── LLMHelper.ts
├── main.ts
├── preload.ts
└── WindowHelper.ts
├── index.html
├── .gitignore copy
├── tsconfig.json
├── worker-script
└── node
│ └── index.js
├── tailwind.config.js
├── README.md
├── dist-electron
├── LLMHelper.js.map
├── ipcHandlers.js.map
├── shortcuts.js.map
├── ipcHandlers.js
├── ProcessingHelper.js.map
├── ScreenshotHelper.js.map
├── main.js.map
├── shortcuts.js
├── preload.js.map
├── ProcessingHelper.js
├── ScreenshotHelper.js
├── preload.js
├── LLMHelper.js
├── main.js
├── WindowHelper.js.map
└── WindowHelper.js
├── .gitignore
└── package.json
/src/types/global.d.ts:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/components/ui/card.tsx:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/renderer/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/bun.lockb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Prat011/free-interview-coder/HEAD/bun.lockb
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
--------------------------------------------------------------------------------
/assets/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Prat011/free-interview-coder/HEAD/assets/.DS_Store
--------------------------------------------------------------------------------
/renderer/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/assets/icons/mac/icon.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Prat011/free-interview-coder/HEAD/assets/icons/mac/icon.icns
--------------------------------------------------------------------------------
/assets/icons/win/icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Prat011/free-interview-coder/HEAD/assets/icons/win/icon.ico
--------------------------------------------------------------------------------
/renderer/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Prat011/free-interview-coder/HEAD/renderer/public/favicon.ico
--------------------------------------------------------------------------------
/renderer/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Prat011/free-interview-coder/HEAD/renderer/public/logo192.png
--------------------------------------------------------------------------------
/renderer/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Prat011/free-interview-coder/HEAD/renderer/public/logo512.png
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/assets/icons/png/icon-256x256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Prat011/free-interview-coder/HEAD/assets/icons/png/icon-256x256.png
--------------------------------------------------------------------------------
/src/lib/utils.ts:
--------------------------------------------------------------------------------
1 | // src/lib/utils.ts
2 |
3 | export function cn(...classes: (string | undefined)[]) {
4 | return classes.filter(Boolean).join(" ")
5 | }
6 |
--------------------------------------------------------------------------------
/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "composite": true,
4 | "skipLibCheck": true,
5 | "module": "ESNext",
6 | "moduleResolution": "bundler",
7 | "allowSyntheticDefaultImports": true
8 | },
9 | "include": ["vite.config.ts"]
10 | }
11 |
--------------------------------------------------------------------------------
/renderer/src/setupTests.ts:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom';
6 |
--------------------------------------------------------------------------------
/src/main.tsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import ReactDOM from "react-dom/client"
3 | import App from "./App"
4 | import "./index.css"
5 |
6 | ReactDOM.createRoot(document.getElementById("root")!).render(
7 |
8 |
9 |
10 | )
11 |
--------------------------------------------------------------------------------
/src/types/index.tsx:
--------------------------------------------------------------------------------
1 | export interface Screenshot {
2 | id: string
3 | path: string
4 | timestamp: number
5 | thumbnail: string // Base64 thumbnail
6 | }
7 |
8 | export interface Solution {
9 | initial_thoughts: string[]
10 | thought_steps: string[]
11 | description: string
12 | code: string
13 | }
14 |
--------------------------------------------------------------------------------
/renderer/src/App.test.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render, screen } from '@testing-library/react';
3 | import App from './App';
4 |
5 | test('renders learn react link', () => {
6 | render( );
7 | const linkElement = screen.getByText(/learn react/i);
8 | expect(linkElement).toBeInTheDocument();
9 | });
10 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | release/Interview[[:space:]]Coder-1.0.0-arm64-mac.zip filter=lfs diff=lfs merge=lfs -text
2 | release/Interview[[:space:]]Coder-1.0.0-arm64.dmg filter=lfs diff=lfs merge=lfs -text
3 | release/mac-arm64/Interview[[:space:]]Coder.app/Contents/Frameworks/Electron[[:space:]]Framework.framework/Versions/A/Electron[[:space:]]Framework filter=lfs diff=lfs merge=lfs -text
4 |
--------------------------------------------------------------------------------
/renderer/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/renderer/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
5 | sans-serif;
6 | -webkit-font-smoothing: antialiased;
7 | -moz-osx-font-smoothing: grayscale;
8 | }
9 |
10 | code {
11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
12 | monospace;
13 | }
14 |
--------------------------------------------------------------------------------
/electron/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ESNext",
4 | "module": "CommonJS",
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "esModuleInterop": true,
8 | "noImplicitAny": true,
9 | "sourceMap": true,
10 | "jsx": "react-jsx",
11 | "baseUrl": ".",
12 | "outDir": "../dist-electron",
13 | "moduleResolution": "node",
14 | "resolveJsonModule": true
15 | },
16 | "include": ["*.ts"]
17 | }
18 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Interview Coder
7 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/renderer/src/reportWebVitals.ts:
--------------------------------------------------------------------------------
1 | import { ReportHandler } from 'web-vitals';
2 |
3 | const reportWebVitals = (onPerfEntry?: ReportHandler) => {
4 | if (onPerfEntry && onPerfEntry instanceof Function) {
5 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
6 | getCLS(onPerfEntry);
7 | getFID(onPerfEntry);
8 | getFCP(onPerfEntry);
9 | getLCP(onPerfEntry);
10 | getTTFB(onPerfEntry);
11 | });
12 | }
13 | };
14 |
15 | export default reportWebVitals;
16 |
--------------------------------------------------------------------------------
/renderer/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "esModuleInterop": true,
8 | "allowSyntheticDefaultImports": true,
9 | "strict": true,
10 | "forceConsistentCasingInFileNames": true,
11 | "noFallthroughCasesInSwitch": true,
12 | "module": "esnext",
13 | "moduleResolution": "node",
14 | "resolveJsonModule": true,
15 | "isolatedModules": true,
16 | "noEmit": true,
17 | "jsx": "react-jsx"
18 | },
19 | "include": ["src"]
20 | }
21 |
--------------------------------------------------------------------------------
/renderer/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "Interview Coder",
3 | "name": "Interview Coder",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/renderer/src/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom/client';
3 | import './index.css';
4 | import App from './App';
5 | import reportWebVitals from './reportWebVitals';
6 |
7 | const root = ReactDOM.createRoot(
8 | document.getElementById('root') as HTMLElement
9 | );
10 | root.render(
11 |
12 |
13 |
14 | );
15 |
16 | // If you want to start measuring performance in your app, pass a function
17 | // to log results (for example: reportWebVitals(console.log))
18 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
19 | reportWebVitals();
20 |
--------------------------------------------------------------------------------
/.gitignore copy:
--------------------------------------------------------------------------------
1 | # Dependencies
2 | node_modules/
3 | /.pnp
4 | .pnp.js
5 |
6 | # Testing
7 | /coverage
8 |
9 | # Production
10 | /build
11 | /dist
12 | /dist-electron
13 | /release
14 |
15 | # Misc
16 | .DS_Store
17 | .env
18 | .env.local
19 | .env.development.local
20 | .env.test.local
21 | .env.production.local
22 |
23 | # Logs
24 | npm-debug.log*
25 | yarn-debug.log*
26 | yarn-error.log*
27 | logs
28 | *.log
29 |
30 | # Editor directories and files
31 | .idea/
32 | .vscode/
33 | *.swp
34 | *.swo
35 | *.swn
36 | *.bak
37 |
38 | # OS generated files
39 | .DS_Store
40 | .DS_Store?
41 | ._*
42 | .Spotlight-V100
43 | .Trashes
44 | ehthumbs.db
45 | Thumbs.db
--------------------------------------------------------------------------------
/renderer/src/App.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import logo from './logo.svg';
3 | import './App.css';
4 |
5 | function App() {
6 | return (
7 |
23 | );
24 | }
25 |
26 | export default App;
27 |
--------------------------------------------------------------------------------
/src/types/solutions.ts:
--------------------------------------------------------------------------------
1 | export interface Solution {
2 | initial_thoughts: string[]
3 | thought_steps: string[]
4 | description: string
5 | code: string
6 | }
7 |
8 | export interface SolutionsResponse {
9 | [key: string]: Solution
10 | }
11 |
12 | export interface ProblemStatementData {
13 | problem_statement: string;
14 | input_format: {
15 | description: string;
16 | parameters: any[];
17 | };
18 | output_format: {
19 | description: string;
20 | type: string;
21 | subtype: string;
22 | };
23 | complexity: {
24 | time: string;
25 | space: string;
26 | };
27 | test_cases: any[];
28 | validation_type: string;
29 | difficulty: string;
30 | }
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ESNext",
4 | "lib": ["DOM", "DOM.Iterable", "ESNext"], // Browser APIs
5 | "module": "ESNext", // For Vite/Modern browsers
6 | "moduleResolution": "bundler", // For Vite
7 | "jsx": "react-jsx", // React specific
8 | "noEmit": true, // Vite handles the building
9 | "allowImportingTsExtensions": true,
10 | "resolveJsonModule": true,
11 | "isolatedModules": true,
12 | "strict": true,
13 | "noUnusedLocals": false,
14 | "noUnusedParameters": true,
15 | "noFallthroughCasesInSwitch": true,
16 | "skipLibCheck": true
17 | },
18 | "include": ["src"], // Only React source files
19 | "references": [{ "path": "./tsconfig.node.json" }]
20 | }
21 |
--------------------------------------------------------------------------------
/renderer/src/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | text-align: center;
3 | }
4 |
5 | .App-logo {
6 | height: 40vmin;
7 | pointer-events: none;
8 | }
9 |
10 | @media (prefers-reduced-motion: no-preference) {
11 | .App-logo {
12 | animation: App-logo-spin infinite 20s linear;
13 | }
14 | }
15 |
16 | .App-header {
17 | background-color: #282c34;
18 | min-height: 100vh;
19 | display: flex;
20 | flex-direction: column;
21 | align-items: center;
22 | justify-content: center;
23 | font-size: calc(10px + 2vmin);
24 | color: white;
25 | }
26 |
27 | .App-link {
28 | color: #61dafb;
29 | }
30 |
31 | @keyframes App-logo-spin {
32 | from {
33 | transform: rotate(0deg);
34 | }
35 | to {
36 | transform: rotate(360deg);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/components/Queue/ScreenshotQueue.tsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import ScreenshotItem from "./ScreenshotItem"
3 |
4 | interface Screenshot {
5 | path: string
6 | preview: string
7 | }
8 |
9 | interface ScreenshotQueueProps {
10 | isLoading: boolean
11 | screenshots: Screenshot[]
12 | onDeleteScreenshot: (index: number) => void
13 | }
14 | const ScreenshotQueue: React.FC = ({
15 | isLoading,
16 | screenshots,
17 | onDeleteScreenshot
18 | }) => {
19 | if (screenshots.length === 0) {
20 | return <>>
21 | }
22 |
23 | const displayScreenshots = screenshots.slice(0, 5)
24 |
25 | return (
26 |
27 | {displayScreenshots.map((screenshot, index) => (
28 |
35 | ))}
36 |
37 | )
38 | }
39 |
40 | export default ScreenshotQueue
41 |
--------------------------------------------------------------------------------
/renderer/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "renderer",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@testing-library/jest-dom": "^5.17.0",
7 | "@testing-library/react": "^13.4.0",
8 | "@testing-library/user-event": "^13.5.0",
9 | "@types/jest": "^27.5.2",
10 | "@types/node": "^16.18.119",
11 | "@types/react": "^18.3.12",
12 | "@types/react-dom": "^18.3.1",
13 | "react": "^18.3.1",
14 | "react-dom": "^18.3.1",
15 | "react-scripts": "5.0.1",
16 | "typescript": "^4.9.5",
17 | "web-vitals": "^2.1.4"
18 | },
19 | "scripts": {
20 | "start": "react-scripts start",
21 | "build": "react-scripts build",
22 | "test": "react-scripts test",
23 | "eject": "react-scripts eject"
24 | },
25 | "eslintConfig": {
26 | "extends": [
27 | "react-app",
28 | "react-app/jest"
29 | ]
30 | },
31 | "browserslist": {
32 | "production": [
33 | ">0.2%",
34 | "not dead",
35 | "not op_mini all"
36 | ],
37 | "development": [
38 | "last 1 chrome version",
39 | "last 1 firefox version",
40 | "last 1 safari version"
41 | ]
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/types/electron.d.ts:
--------------------------------------------------------------------------------
1 | interface ElectronAPI {
2 | updateContentDimensions: (dimensions: {
3 | width: number
4 | height: number
5 | }) => Promise
6 | getScreenshots: () => Promise>
7 | deleteScreenshot: (path: string) => Promise<{ success: boolean; error?: string }>
8 | onScreenshotTaken: (callback: (data: { path: string; preview: string }) => void) => () => void
9 | onSolutionsReady: (callback: (solutions: string) => void) => () => void
10 | onResetView: (callback: () => void) => () => void
11 | onSolutionStart: (callback: () => void) => () => void
12 | onDebugStart: (callback: () => void) => () => void
13 | onDebugSuccess: (callback: (data: any) => void) => () => void
14 | onSolutionError: (callback: (error: string) => void) => () => void
15 | onProcessingNoScreenshots: (callback: () => void) => () => void
16 | onProblemExtracted: (callback: (data: any) => void) => () => void
17 | onSolutionSuccess: (callback: (data: any) => void) => () => void
18 | onUnauthorized: (callback: () => void) => () => void
19 | onDebugError: (callback: (error: string) => void) => () => void
20 | takeScreenshot: () => Promise
21 | moveWindowLeft: () => Promise
22 | moveWindowRight: () => Promise
23 | quitApp: () => Promise
24 | getApiKey: () => Promise
25 | }
26 |
27 | interface Window {
28 | electronAPI: ElectronAPI
29 | }
--------------------------------------------------------------------------------
/worker-script/node/index.js:
--------------------------------------------------------------------------------
1 | const { parentPort } = require('worker_threads');
2 |
3 | // Handle messages from the main thread
4 | parentPort.on('message', async (message) => {
5 | try {
6 | // Process the message based on its type
7 | switch (message.type) {
8 | case 'process':
9 | // Add your processing logic here
10 | const result = await processTask(message.data);
11 | parentPort.postMessage({ type: 'result', data: result });
12 | break;
13 |
14 | default:
15 | parentPort.postMessage({
16 | type: 'error',
17 | error: `Unknown message type: ${message.type}`
18 | });
19 | }
20 | } catch (error) {
21 | parentPort.postMessage({
22 | type: 'error',
23 | error: error.message
24 | });
25 | }
26 | });
27 |
28 | async function processTask(data) {
29 | // Add your task processing logic here
30 | return {
31 | status: 'success',
32 | result: `Processed: ${JSON.stringify(data)}`
33 | };
34 | }
35 |
36 | // Error handling for the worker
37 | process.on('uncaughtException', (error) => {
38 | parentPort.postMessage({
39 | type: 'error',
40 | error: `Uncaught Exception: ${error.message}`
41 | });
42 | });
43 |
44 | process.on('unhandledRejection', (reason) => {
45 | parentPort.postMessage({
46 | type: 'error',
47 | error: `Unhandled Rejection: ${reason}`
48 | });
49 | });
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | export const content = ["./src/**/*.{js,jsx,ts,tsx}", "./public/index.html"]
2 |
3 | module.exports = {
4 | content: ["./src/**/*.{js,jsx,ts,tsx}"],
5 | theme: {
6 | extend: {
7 | fontFamily: {
8 | sans: ["Inter", "system-ui", "sans-serif"]
9 | },
10 | animation: {
11 | in: "in 0.2s ease-out",
12 | out: "out 0.2s ease-in",
13 | pulse: "pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite",
14 | shimmer: "shimmer 2s linear infinite",
15 | "text-gradient-wave": "textGradientWave 2s infinite ease-in-out"
16 | },
17 | keyframes: {
18 | textGradientWave: {
19 | "0%": { backgroundPosition: "0% 50%" },
20 | "100%": { backgroundPosition: "200% 50%" }
21 | },
22 | shimmer: {
23 | "0%": {
24 | backgroundPosition: "200% 0"
25 | },
26 | "100%": {
27 | backgroundPosition: "-200% 0"
28 | }
29 | },
30 | in: {
31 | "0%": { transform: "translateY(100%)", opacity: 0 },
32 | "100%": { transform: "translateY(0)", opacity: 1 }
33 | },
34 | out: {
35 | "0%": { transform: "translateY(0)", opacity: 1 },
36 | "100%": { transform: "translateY(100%)", opacity: 0 }
37 | },
38 | pulse: {
39 | "0%, 100%": {
40 | opacity: 1
41 | },
42 | "50%": {
43 | opacity: 0.5
44 | }
45 | }
46 | }
47 | }
48 | },
49 | plugins: []
50 | }
51 |
--------------------------------------------------------------------------------
/src/components/ui/dialog.tsx:
--------------------------------------------------------------------------------
1 | // src/components/ui/dialog.tsx
2 |
3 | import * as React from "react"
4 | import * as DialogPrimitive from "@radix-ui/react-dialog"
5 | import { cn } from "../../lib/utils"
6 |
7 | const Dialog = DialogPrimitive.Root
8 | const DialogTrigger = DialogPrimitive.Trigger
9 | const DialogPortal = DialogPrimitive.Portal
10 |
11 | const DialogOverlay = React.forwardRef<
12 | React.ElementRef,
13 | React.ComponentPropsWithoutRef
14 | >(({ className, ...props }, ref) => (
15 |
20 | ))
21 | DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
22 |
23 | const DialogContent = React.forwardRef<
24 | React.ElementRef,
25 | React.ComponentPropsWithoutRef
26 | >(({ className, children, ...props }, ref) => (
27 |
28 |
29 |
38 | {children}
39 |
40 |
41 | ))
42 | DialogContent.displayName = DialogPrimitive.Content.displayName
43 |
44 | const DialogClose = DialogPrimitive.Close
45 |
46 | export { Dialog, DialogTrigger, DialogContent, DialogClose }
47 |
--------------------------------------------------------------------------------
/renderer/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | React App
28 |
29 |
30 | You need to enable JavaScript to run this app.
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/src/components/Queue/ScreenshotItem.tsx:
--------------------------------------------------------------------------------
1 | // src/components/ScreenshotItem.tsx
2 | import React from "react"
3 | import { X } from "lucide-react"
4 |
5 | interface Screenshot {
6 | path: string
7 | preview: string
8 | }
9 |
10 | interface ScreenshotItemProps {
11 | screenshot: Screenshot
12 | onDelete: (index: number) => void
13 | index: number
14 | isLoading: boolean
15 | }
16 |
17 | const ScreenshotItem: React.FC = ({
18 | screenshot,
19 | onDelete,
20 | index,
21 | isLoading
22 | }) => {
23 | const handleDelete = async () => {
24 | await onDelete(index)
25 | }
26 |
27 | return (
28 | <>
29 |
32 |
33 | {isLoading && (
34 |
37 | )}
38 |
47 |
48 | {!isLoading && (
49 |
{
51 | e.stopPropagation()
52 | handleDelete()
53 | }}
54 | className="absolute top-2 left-2 p-1 rounded-full bg-black bg-opacity-50 text-white opacity-0 group-hover:opacity-100 transition-opacity duration-300"
55 | aria-label="Delete screenshot"
56 | >
57 |
58 |
59 | )}
60 |
61 | >
62 | )
63 | }
64 |
65 | export default ScreenshotItem
66 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Free Interview Coder
2 |
3 | A desktop application to help with coding interviews.
4 |
5 | ## 🚀 Quick Start Guide
6 |
7 | ### Prerequisites
8 | - Make sure you have Node.js installed on your computer
9 | - Git installed on your computer
10 | - A Gemini API key (get it from [Google AI Studio](https://makersuite.google.com/app/apikey))
11 |
12 | ### Installation Steps
13 |
14 | 1. Clone the repository:
15 | ```bash
16 | git clone [repository-url]
17 | cd interview-coder
18 | ```
19 |
20 | 2. Install dependencies:
21 | ```bash
22 | npm install
23 | ```
24 |
25 | 3. Set up environment variables:
26 | - Create a file named `.env` in the root folder
27 | - Add your Gemini API key:
28 | ```
29 | GEMINI_API_KEY=your_api_key_here
30 | ```
31 | - Save the file
32 |
33 | ### Running the App
34 |
35 | #### Method 1: Development Mode (Recommended for first run)
36 | 1. Open a terminal and run:
37 | ```bash
38 | npm run dev -- --port 5180
39 | ```
40 |
41 | 2. Open another terminal in the same folder and run:
42 | ```bash
43 | NODE_ENV=development npm run electron:dev
44 | ```
45 |
46 | #### Method 2: Production Mode
47 | ```bash
48 | npm run app:build
49 | ```
50 | The built app will be in the `release` folder.
51 |
52 | ### ⚠️ Important Notes
53 |
54 | 1. **Closing the App**:
55 | - Press `Cmd + Q` (Mac) or `Ctrl + Q` (Windows/Linux) to quit
56 | - Or use Activity Monitor/Task Manager to close `Interview Coder`
57 | - The X button currently doesn't work (known issue)
58 |
59 | 2. **If the app doesn't start**:
60 | - Make sure no other app is using port 5180
61 | - Try killing existing processes:
62 | ```bash
63 | # Find processes using port 5180
64 | lsof -i :5180
65 | # Kill them (replace [PID] with the process ID)
66 | kill [PID]
67 | ```
68 |
69 | 3. **Keyboard Shortcuts**:
70 | - `Cmd/Ctrl + B`: Toggle window visibility
71 | - `Cmd/Ctrl + H`: Take screenshot
72 | - `Cmd/Ctrl + Arrow Keys`: Move window
73 |
74 | ### Troubleshooting
75 |
76 | If you see errors:
77 | 1. Delete the `node_modules` folder
78 | 2. Delete `package-lock.json`
79 | 3. Run `npm install` again
80 | 4. Try running the app again using Method 1
81 |
--------------------------------------------------------------------------------
/renderer/README.md:
--------------------------------------------------------------------------------
1 | # Getting Started with Create React App
2 |
3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
4 |
5 | ## Available Scripts
6 |
7 | In the project directory, you can run:
8 |
9 | ### `npm start`
10 |
11 | Runs the app in the development mode.\
12 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
13 |
14 | The page will reload if you make edits.\
15 | You will also see any lint errors in the console.
16 |
17 | ### `npm test`
18 |
19 | Launches the test runner in the interactive watch mode.\
20 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
21 |
22 | ### `npm run build`
23 |
24 | Builds the app for production to the `build` folder.\
25 | It correctly bundles React in production mode and optimizes the build for the best performance.
26 |
27 | The build is minified and the filenames include the hashes.\
28 | Your app is ready to be deployed!
29 |
30 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
31 |
32 | ### `npm run eject`
33 |
34 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!**
35 |
36 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
37 |
38 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
39 |
40 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
41 |
42 | ## Learn More
43 |
44 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
45 |
46 | To learn React, check out the [React documentation](https://reactjs.org/).
47 |
--------------------------------------------------------------------------------
/dist-electron/LLMHelper.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"LLMHelper.js","sourceRoot":"","sources":["../electron/LLMHelper.ts"],"names":[],"mappings":"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"}
--------------------------------------------------------------------------------
/dist-electron/ipcHandlers.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"ipcHandlers.js","sourceRoot":"","sources":["../electron/ipcHandlers.ts"],"names":[],"mappings":";AAAA,iBAAiB;;AAKjB,sDA0EC;AA7ED,uCAAuC;AAGvC,SAAgB,qBAAqB,CAAC,QAAkB;IACtD,kBAAO,CAAC,MAAM,CACZ,2BAA2B,EAC3B,KAAK,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,MAAM,EAAqC,EAAE,EAAE;QACpE,IAAI,KAAK,IAAI,MAAM,EAAE,CAAC;YACpB,QAAQ,CAAC,mBAAmB,CAAC,KAAK,EAAE,MAAM,CAAC,CAAA;QAC7C,CAAC;IACH,CAAC,CACF,CAAA;IAED,kBAAO,CAAC,MAAM,CAAC,mBAAmB,EAAE,KAAK,EAAE,KAAK,EAAE,IAAY,EAAE,EAAE;QAChE,OAAO,QAAQ,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAA;IACxC,CAAC,CAAC,CAAA;IAEF,kBAAO,CAAC,MAAM,CAAC,iBAAiB,EAAE,KAAK,IAAI,EAAE;QAC3C,IAAI,CAAC;YACH,MAAM,cAAc,GAAG,MAAM,QAAQ,CAAC,cAAc,EAAE,CAAA;YACtD,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,eAAe,CAAC,cAAc,CAAC,CAAA;YAC9D,OAAO,EAAE,IAAI,EAAE,cAAc,EAAE,OAAO,EAAE,CAAA;QAC1C,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,0BAA0B,EAAE,KAAK,CAAC,CAAA;YAChD,MAAM,KAAK,CAAA;QACb,CAAC;IACH,CAAC,CAAC,CAAA;IAEF,kBAAO,CAAC,MAAM,CAAC,iBAAiB,EAAE,KAAK,IAAI,EAAE;QAC3C,OAAO,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,QAAQ,CAAC,OAAO,EAAE,EAAE,CAAC,CAAA;QACzC,IAAI,CAAC;YACH,IAAI,QAAQ,GAAG,EAAE,CAAA;YACjB,IAAI,QAAQ,CAAC,OAAO,EAAE,KAAK,OAAO,EAAE,CAAC;gBACnC,QAAQ,GAAG,MAAM,OAAO,CAAC,GAAG,CAC1B,QAAQ,CAAC,kBAAkB,EAAE,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC;oBACjD,IAAI;oBACJ,OAAO,EAAE,MAAM,QAAQ,CAAC,eAAe,CAAC,IAAI,CAAC;iBAC9C,CAAC,CAAC,CACJ,CAAA;YACH,CAAC;iBAAM,CAAC;gBACN,QAAQ,GAAG,MAAM,OAAO,CAAC,GAAG,CAC1B,QAAQ,CAAC,uBAAuB,EAAE,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC;oBACtD,IAAI;oBACJ,OAAO,EAAE,MAAM,QAAQ,CAAC,eAAe,CAAC,IAAI,CAAC;iBAC9C,CAAC,CAAC,CACJ,CAAA;YACH,CAAC;YACD,QAAQ,CAAC,OAAO,CAAC,CAAC,OAAY,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAA;YAC7D,OAAO,QAAQ,CAAA;QACjB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,4BAA4B,EAAE,KAAK,CAAC,CAAA;YAClD,MAAM,KAAK,CAAA;QACb,CAAC;IACH,CAAC,CAAC,CAAA;IAEF,kBAAO,CAAC,MAAM,CAAC,eAAe,EAAE,KAAK,IAAI,EAAE;QACzC,QAAQ,CAAC,gBAAgB,EAAE,CAAA;IAC7B,CAAC,CAAC,CAAA;IAEF,kBAAO,CAAC,MAAM,CAAC,cAAc,EAAE,KAAK,IAAI,EAAE;QACxC,IAAI,CAAC;YACH,QAAQ,CAAC,WAAW,EAAE,CAAA;YACtB,OAAO,CAAC,GAAG,CAAC,sCAAsC,CAAC,CAAA;YACnD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAA;QAC1B,CAAC;QAAC,OAAO,KAAU,EAAE,CAAC;YACpB,OAAO,CAAC,KAAK,CAAC,yBAAyB,EAAE,KAAK,CAAC,CAAA;YAC/C,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,CAAC,OAAO,EAAE,CAAA;QACjD,CAAC;IACH,CAAC,CAAC,CAAA;IAEF,kBAAO,CAAC,MAAM,CAAC,UAAU,EAAE,GAAG,EAAE;QAC9B,cAAG,CAAC,IAAI,EAAE,CAAA;IACZ,CAAC,CAAC,CAAA;IAEF,kBAAO,CAAC,MAAM,CAAC,oBAAoB,EAAE,KAAK,IAAI,EAAE;QAC9C,OAAO,OAAO,CAAC,GAAG,CAAC,cAAc,IAAI,EAAE,CAAA;IACzC,CAAC,CAAC,CAAA;AACJ,CAAC"}
--------------------------------------------------------------------------------
/electron/ipcHandlers.ts:
--------------------------------------------------------------------------------
1 | // ipcHandlers.ts
2 |
3 | import { ipcMain, app } from "electron"
4 | import { AppState } from "./main"
5 |
6 | export function initializeIpcHandlers(appState: AppState): void {
7 | ipcMain.handle(
8 | "update-content-dimensions",
9 | async (event, { width, height }: { width: number; height: number }) => {
10 | if (width && height) {
11 | appState.setWindowDimensions(width, height)
12 | }
13 | }
14 | )
15 |
16 | ipcMain.handle("delete-screenshot", async (event, path: string) => {
17 | return appState.deleteScreenshot(path)
18 | })
19 |
20 | ipcMain.handle("take-screenshot", async () => {
21 | try {
22 | const screenshotPath = await appState.takeScreenshot()
23 | const preview = await appState.getImagePreview(screenshotPath)
24 | return { path: screenshotPath, preview }
25 | } catch (error) {
26 | console.error("Error taking screenshot:", error)
27 | throw error
28 | }
29 | })
30 |
31 | ipcMain.handle("get-screenshots", async () => {
32 | console.log({ view: appState.getView() })
33 | try {
34 | let previews = []
35 | if (appState.getView() === "queue") {
36 | previews = await Promise.all(
37 | appState.getScreenshotQueue().map(async (path) => ({
38 | path,
39 | preview: await appState.getImagePreview(path)
40 | }))
41 | )
42 | } else {
43 | previews = await Promise.all(
44 | appState.getExtraScreenshotQueue().map(async (path) => ({
45 | path,
46 | preview: await appState.getImagePreview(path)
47 | }))
48 | )
49 | }
50 | previews.forEach((preview: any) => console.log(preview.path))
51 | return previews
52 | } catch (error) {
53 | console.error("Error getting screenshots:", error)
54 | throw error
55 | }
56 | })
57 |
58 | ipcMain.handle("toggle-window", async () => {
59 | appState.toggleMainWindow()
60 | })
61 |
62 | ipcMain.handle("reset-queues", async () => {
63 | try {
64 | appState.clearQueues()
65 | console.log("Screenshot queues have been cleared.")
66 | return { success: true }
67 | } catch (error: any) {
68 | console.error("Error resetting queues:", error)
69 | return { success: false, error: error.message }
70 | }
71 | })
72 |
73 | ipcMain.handle("quit-app", () => {
74 | app.quit()
75 | })
76 |
77 | ipcMain.handle("get-gemini-api-key", async () => {
78 | return process.env.GEMINI_API_KEY || ""
79 | })
80 | }
81 |
--------------------------------------------------------------------------------
/renderer/src/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/dist-electron/shortcuts.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"shortcuts.js","sourceRoot":"","sources":["../electron/shortcuts.ts"],"names":[],"mappings":"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"}
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | lerna-debug.log*
8 | .pnpm-debug.log*
9 |
10 | # Diagnostic reports (https://nodejs.org/api/report.html)
11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
12 |
13 | # Runtime data
14 | pids
15 | *.pid
16 | *.seed
17 | *.pid.lock
18 |
19 | # Directory for instrumented libs generated by jscoverage/JSCover
20 | lib-cov
21 |
22 | # Coverage directory used by tools like istanbul
23 | coverage
24 | *.lcov
25 |
26 | # nyc test coverage
27 | .nyc_output
28 |
29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
30 | .grunt
31 |
32 | # Bower dependency directory (https://bower.io/)
33 | bower_components
34 |
35 | # node-waf configuration
36 | .lock-wscript
37 |
38 | # Compiled binary addons (https://nodejs.org/api/addons.html)
39 | build/Release
40 |
41 | # Dependency directories
42 | node_modules/
43 | jspm_packages/
44 |
45 | # Snowpack dependency directory (https://snowpack.dev/)
46 | web_modules/
47 |
48 | # TypeScript cache
49 | *.tsbuildinfo
50 |
51 | # Optional npm cache directory
52 | .npm
53 |
54 | # Optional eslint cache
55 | .eslintcache
56 |
57 | # Optional stylelint cache
58 | .stylelintcache
59 |
60 | # Microbundle cache
61 | .rpt2_cache/
62 | .rts2_cache_cjs/
63 | .rts2_cache_es/
64 | .rts2_cache_umd/
65 |
66 | # Optional REPL history
67 | .node_repl_history
68 |
69 | # Output of 'npm pack'
70 | *.tgz
71 |
72 | # Yarn Integrity file
73 | .yarn-integrity
74 |
75 | # dotenv environment variable files
76 | .env
77 | .env.development.local
78 | .env.test.local
79 | .env.production.local
80 | .env.local
81 |
82 | # parcel-bundler cache (https://parceljs.org/)
83 | .cache
84 | .parcel-cache
85 |
86 | # Next.js build output
87 | .next
88 | out
89 |
90 | # Nuxt.js build / generate output
91 | .nuxt
92 | dist
93 |
94 | # Gatsby files
95 | .cache/
96 | # Comment in the public line in if your project uses Gatsby and not Next.js
97 | # https://nextjs.org/blog/next-9-1#public-directory-support
98 | # public
99 |
100 | # vuepress build output
101 | .vuepress/dist
102 |
103 | # vuepress v2.x temp and cache directory
104 | .temp
105 | .cache
106 |
107 | # vitepress build output
108 | **/.vitepress/dist
109 |
110 | # vitepress cache directory
111 | **/.vitepress/cache
112 |
113 | # Docusaurus cache and generated files
114 | .docusaurus
115 |
116 | # Serverless directories
117 | .serverless/
118 |
119 | # FuseBox cache
120 | .fusebox/
121 |
122 | # DynamoDB Local files
123 | .dynamodb/
124 |
125 | # TernJS port file
126 | .tern-port
127 |
128 | # Stores VSCode versions used for testing VSCode extensions
129 | .vscode-test
130 |
131 | # yarn v2
132 | .yarn/cache
133 | .yarn/unplugged
134 | .yarn/build-state.yml
135 | .yarn/install-state.gz
136 | .pnp.*
137 |
--------------------------------------------------------------------------------
/dist-electron/ipcHandlers.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | // ipcHandlers.ts
3 | Object.defineProperty(exports, "__esModule", { value: true });
4 | exports.initializeIpcHandlers = initializeIpcHandlers;
5 | const electron_1 = require("electron");
6 | function initializeIpcHandlers(appState) {
7 | electron_1.ipcMain.handle("update-content-dimensions", async (event, { width, height }) => {
8 | if (width && height) {
9 | appState.setWindowDimensions(width, height);
10 | }
11 | });
12 | electron_1.ipcMain.handle("delete-screenshot", async (event, path) => {
13 | return appState.deleteScreenshot(path);
14 | });
15 | electron_1.ipcMain.handle("take-screenshot", async () => {
16 | try {
17 | const screenshotPath = await appState.takeScreenshot();
18 | const preview = await appState.getImagePreview(screenshotPath);
19 | return { path: screenshotPath, preview };
20 | }
21 | catch (error) {
22 | console.error("Error taking screenshot:", error);
23 | throw error;
24 | }
25 | });
26 | electron_1.ipcMain.handle("get-screenshots", async () => {
27 | console.log({ view: appState.getView() });
28 | try {
29 | let previews = [];
30 | if (appState.getView() === "queue") {
31 | previews = await Promise.all(appState.getScreenshotQueue().map(async (path) => ({
32 | path,
33 | preview: await appState.getImagePreview(path)
34 | })));
35 | }
36 | else {
37 | previews = await Promise.all(appState.getExtraScreenshotQueue().map(async (path) => ({
38 | path,
39 | preview: await appState.getImagePreview(path)
40 | })));
41 | }
42 | previews.forEach((preview) => console.log(preview.path));
43 | return previews;
44 | }
45 | catch (error) {
46 | console.error("Error getting screenshots:", error);
47 | throw error;
48 | }
49 | });
50 | electron_1.ipcMain.handle("toggle-window", async () => {
51 | appState.toggleMainWindow();
52 | });
53 | electron_1.ipcMain.handle("reset-queues", async () => {
54 | try {
55 | appState.clearQueues();
56 | console.log("Screenshot queues have been cleared.");
57 | return { success: true };
58 | }
59 | catch (error) {
60 | console.error("Error resetting queues:", error);
61 | return { success: false, error: error.message };
62 | }
63 | });
64 | electron_1.ipcMain.handle("quit-app", () => {
65 | electron_1.app.quit();
66 | });
67 | electron_1.ipcMain.handle("get-gemini-api-key", async () => {
68 | return process.env.GEMINI_API_KEY || "";
69 | });
70 | }
71 | //# sourceMappingURL=ipcHandlers.js.map
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "interview-coder",
3 | "version": "1.0.0",
4 | "main": "./dist-electron/main.js",
5 | "scripts": {
6 | "clean": "rimraf dist dist-electron",
7 | "dev": "vite",
8 | "build": "npm run clean && tsc && vite build",
9 | "preview": "vite preview",
10 | "electron:dev": "tsc -p electron/tsconfig.json && electron .",
11 | "app:dev": "concurrently \"vite\" \"wait-on http://localhost:5173 && cross-env electron .\"",
12 | "app:build": "npm run build && electron-builder",
13 | "watch": "tsc -p electron/tsconfig.json --watch"
14 | },
15 | "build": {
16 | "appId": "com.electron.interview-coder",
17 | "productName": "Interview Coder",
18 | "files": [
19 | "dist/**/*",
20 | "dist-electron/**/*",
21 | "package.json"
22 | ],
23 | "directories": {
24 | "output": "release"
25 | },
26 | "mac": {
27 | "category": "public.app-category.utilities",
28 | "target": [
29 | "dmg",
30 | "zip"
31 | ],
32 | "icon": "assets/icons/mac/icon.icns"
33 | },
34 | "win": {
35 | "target": [
36 | "nsis",
37 | "portable"
38 | ],
39 | "icon": "assets/icons/win/icon.ico"
40 | },
41 | "linux": {
42 | "target": [
43 | "AppImage",
44 | "deb"
45 | ],
46 | "icon": "assets/icons/png/icon-256x256.png"
47 | },
48 | "publish": [
49 | {
50 | "provider": "github",
51 | "owner": "ibttf",
52 | "repo": "interview-coder-frontend"
53 | }
54 | ]
55 | },
56 | "keywords": [],
57 | "author": "",
58 | "license": "ISC",
59 | "description": "",
60 | "devDependencies": {
61 | "@types/color": "^4.2.0",
62 | "@types/diff": "^6.0.0",
63 | "@types/electron": "^1.4.38",
64 | "@types/node": "^22.9.0",
65 | "@types/react": "^18.3.12",
66 | "@types/react-dom": "^18.3.1",
67 | "@types/react-syntax-highlighter": "^15.5.13",
68 | "@types/screenshot-desktop": "^1.12.3",
69 | "@types/uuid": "^9.0.8",
70 | "@typescript-eslint/eslint-plugin": "^8.14.0",
71 | "@typescript-eslint/parser": "^8.14.0",
72 | "@vitejs/plugin-react": "^4.3.3",
73 | "autoprefixer": "^10.4.20",
74 | "concurrently": "^9.1.0",
75 | "cross-env": "^7.0.3",
76 | "electron": "^33.2.0",
77 | "electron-builder": "^25.1.8",
78 | "electron-is-dev": "^3.0.1",
79 | "postcss": "^8.4.49",
80 | "rimraf": "^6.0.1",
81 | "tailwindcss": "^3.4.15",
82 | "typescript": "^5.6.3",
83 | "vite": "^5.4.11",
84 | "vite-plugin-electron": "^0.28.8",
85 | "vite-plugin-electron-renderer": "^0.14.6",
86 | "wait-on": "^8.0.1"
87 | },
88 | "dependencies": {
89 | "@google/genai": "^1.28.0",
90 | "@google/generative-ai": "^0.2.1",
91 | "@radix-ui/react-dialog": "^1.1.2",
92 | "@radix-ui/react-toast": "^1.2.2",
93 | "axios": "^1.7.7",
94 | "class-variance-authority": "^0.7.0",
95 | "clsx": "^2.1.1",
96 | "diff": "^7.0.0",
97 | "dotenv": "^17.2.3",
98 | "form-data": "^4.0.1",
99 | "lucide-react": "^0.460.0",
100 | "react": "^18.3.1",
101 | "react-code-blocks": "^0.1.6",
102 | "react-dom": "^18.3.1",
103 | "react-icons": "^5.3.0",
104 | "react-query": "^3.39.3",
105 | "react-syntax-highlighter": "^15.6.1",
106 | "screenshot-desktop": "^1.15.0",
107 | "sharp": "^0.33.5",
108 | "tailwind-merge": "^2.5.4",
109 | "tesseract.js": "^5.0.5",
110 | "uuid": "^11.0.3"
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/electron/shortcuts.ts:
--------------------------------------------------------------------------------
1 | import { globalShortcut, app } from "electron"
2 | import { AppState } from "./main" // Adjust the import path if necessary
3 |
4 | export class ShortcutsHelper {
5 | private appState: AppState
6 |
7 | constructor(appState: AppState) {
8 | this.appState = appState
9 | }
10 |
11 | public registerGlobalShortcuts(): void {
12 | globalShortcut.register("CommandOrControl+H", async () => {
13 | const mainWindow = this.appState.getMainWindow()
14 | if (mainWindow) {
15 | console.log("Taking screenshot...")
16 | try {
17 | const screenshotPath = await this.appState.takeScreenshot()
18 | const preview = await this.appState.getImagePreview(screenshotPath)
19 | mainWindow.webContents.send("screenshot-taken", {
20 | path: screenshotPath,
21 | preview
22 | })
23 | } catch (error) {
24 | console.error("Error capturing screenshot:", error)
25 | }
26 | }
27 | })
28 |
29 | globalShortcut.register("CommandOrControl+Enter", async () => {
30 | await this.appState.processingHelper.processScreenshots()
31 | })
32 |
33 | globalShortcut.register("CommandOrControl+R", () => {
34 | console.log(
35 | "Command + R pressed. Canceling requests and resetting queues..."
36 | )
37 |
38 | // Cancel ongoing API requests
39 | this.appState.processingHelper.cancelOngoingRequests()
40 |
41 | // Clear both screenshot queues
42 | this.appState.clearQueues()
43 |
44 | console.log("Cleared queues.")
45 |
46 | // Update the view state to 'queue'
47 | this.appState.setView("queue")
48 |
49 | // Notify renderer process to switch view to 'queue'
50 | const mainWindow = this.appState.getMainWindow()
51 | if (mainWindow && !mainWindow.isDestroyed()) {
52 | mainWindow.webContents.send("reset-view")
53 | }
54 | })
55 |
56 | // New shortcuts for moving the window
57 | globalShortcut.register("CommandOrControl+Left", () => {
58 | console.log("Command/Ctrl + Left pressed. Moving window left.")
59 | this.appState.moveWindowLeft()
60 | })
61 |
62 | globalShortcut.register("CommandOrControl+Right", () => {
63 | console.log("Command/Ctrl + Right pressed. Moving window right.")
64 | this.appState.moveWindowRight()
65 | })
66 | globalShortcut.register("CommandOrControl+Down", () => {
67 | console.log("Command/Ctrl + down pressed. Moving window down.")
68 | this.appState.moveWindowDown()
69 | })
70 | globalShortcut.register("CommandOrControl+Up", () => {
71 | console.log("Command/Ctrl + Up pressed. Moving window Up.")
72 | this.appState.moveWindowUp()
73 | })
74 |
75 | globalShortcut.register("CommandOrControl+B", () => {
76 | this.appState.toggleMainWindow()
77 | // If window exists and we're showing it, bring it to front
78 | const mainWindow = this.appState.getMainWindow()
79 | if (mainWindow && !this.appState.isVisible()) {
80 | // Force the window to the front on macOS
81 | if (process.platform === "darwin") {
82 | mainWindow.setAlwaysOnTop(true, "normal")
83 | // Reset alwaysOnTop after a brief delay
84 | setTimeout(() => {
85 | if (mainWindow && !mainWindow.isDestroyed()) {
86 | mainWindow.setAlwaysOnTop(true, "floating")
87 | }
88 | }, 100)
89 | }
90 | }
91 | })
92 |
93 | // Unregister shortcuts when quitting
94 | app.on("will-quit", () => {
95 | globalShortcut.unregisterAll()
96 | })
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/dist-electron/ProcessingHelper.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"ProcessingHelper.js","sourceRoot":"","sources":["../electron/ProcessingHelper.ts"],"names":[],"mappings":"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"}
--------------------------------------------------------------------------------
/dist-electron/ScreenshotHelper.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"ScreenshotHelper.js","sourceRoot":"","sources":["../electron/ScreenshotHelper.ts"],"names":[],"mappings":"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"}
--------------------------------------------------------------------------------
/src/components/ui/toast.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as ToastPrimitive from "@radix-ui/react-toast"
3 | import { cn } from "../../lib/utils"
4 | import { X } from "lucide-react"
5 |
6 | const ToastProvider = ToastPrimitive.Provider
7 |
8 | export type ToastMessage = {
9 | title: string
10 | description: string
11 | variant: ToastVariant
12 | }
13 |
14 | const ToastViewport = React.forwardRef<
15 | React.ElementRef,
16 | React.ComponentPropsWithoutRef
17 | >(({ className, ...props }, ref) => (
18 |
26 | ))
27 | ToastViewport.displayName = ToastPrimitive.Viewport.displayName
28 |
29 | type ToastVariant = "neutral" | "success" | "error"
30 |
31 | interface ToastProps
32 | extends React.ComponentPropsWithoutRef {
33 | variant?: ToastVariant
34 | }
35 |
36 | const toastVariants: Record = {
37 | neutral: "bg-yellow-500 text-white",
38 | success: "bg-green-500 text-white",
39 | error: "bg-red-500 text-white"
40 | }
41 |
42 | const Toast = React.forwardRef<
43 | React.ElementRef,
44 | ToastProps
45 | >(({ className, variant = "neutral", ...props }, ref) => (
46 |
55 | ))
56 | Toast.displayName = ToastPrimitive.Root.displayName
57 |
58 | const ToastAction = React.forwardRef<
59 | React.ElementRef,
60 | React.ComponentPropsWithoutRef
61 | >(({ className, ...props }, ref) => (
62 |
67 | ))
68 | ToastAction.displayName = ToastPrimitive.Action.displayName
69 |
70 | const ToastClose = React.forwardRef<
71 | React.ElementRef,
72 | React.ComponentPropsWithoutRef
73 | >(({ className, ...props }, ref) => (
74 |
82 |
83 |
84 | ))
85 | ToastClose.displayName = ToastPrimitive.Close.displayName
86 |
87 | const ToastTitle = React.forwardRef<
88 | React.ElementRef,
89 | React.ComponentPropsWithoutRef
90 | >(({ className, ...props }, ref) => (
91 |
96 | ))
97 | ToastTitle.displayName = ToastPrimitive.Title.displayName
98 |
99 | const ToastDescription = React.forwardRef<
100 | React.ElementRef,
101 | React.ComponentPropsWithoutRef
102 | >(({ className, ...props }, ref) => (
103 |
108 | ))
109 | ToastDescription.displayName = ToastPrimitive.Description.displayName
110 |
111 | export type { ToastProps, ToastVariant }
112 | export {
113 | ToastProvider,
114 | ToastViewport,
115 | Toast,
116 | ToastAction,
117 | ToastClose,
118 | ToastTitle,
119 | ToastDescription
120 | }
121 |
--------------------------------------------------------------------------------
/dist-electron/main.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"main.js","sourceRoot":"","sources":["../electron/main.ts"],"names":[],"mappings":"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"}
--------------------------------------------------------------------------------
/dist-electron/shortcuts.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | Object.defineProperty(exports, "__esModule", { value: true });
3 | exports.ShortcutsHelper = void 0;
4 | const electron_1 = require("electron");
5 | class ShortcutsHelper {
6 | appState;
7 | constructor(appState) {
8 | this.appState = appState;
9 | }
10 | registerGlobalShortcuts() {
11 | electron_1.globalShortcut.register("CommandOrControl+H", async () => {
12 | const mainWindow = this.appState.getMainWindow();
13 | if (mainWindow) {
14 | console.log("Taking screenshot...");
15 | try {
16 | const screenshotPath = await this.appState.takeScreenshot();
17 | const preview = await this.appState.getImagePreview(screenshotPath);
18 | mainWindow.webContents.send("screenshot-taken", {
19 | path: screenshotPath,
20 | preview
21 | });
22 | }
23 | catch (error) {
24 | console.error("Error capturing screenshot:", error);
25 | }
26 | }
27 | });
28 | electron_1.globalShortcut.register("CommandOrControl+Enter", async () => {
29 | await this.appState.processingHelper.processScreenshots();
30 | });
31 | electron_1.globalShortcut.register("CommandOrControl+R", () => {
32 | console.log("Command + R pressed. Canceling requests and resetting queues...");
33 | // Cancel ongoing API requests
34 | this.appState.processingHelper.cancelOngoingRequests();
35 | // Clear both screenshot queues
36 | this.appState.clearQueues();
37 | console.log("Cleared queues.");
38 | // Update the view state to 'queue'
39 | this.appState.setView("queue");
40 | // Notify renderer process to switch view to 'queue'
41 | const mainWindow = this.appState.getMainWindow();
42 | if (mainWindow && !mainWindow.isDestroyed()) {
43 | mainWindow.webContents.send("reset-view");
44 | }
45 | });
46 | // New shortcuts for moving the window
47 | electron_1.globalShortcut.register("CommandOrControl+Left", () => {
48 | console.log("Command/Ctrl + Left pressed. Moving window left.");
49 | this.appState.moveWindowLeft();
50 | });
51 | electron_1.globalShortcut.register("CommandOrControl+Right", () => {
52 | console.log("Command/Ctrl + Right pressed. Moving window right.");
53 | this.appState.moveWindowRight();
54 | });
55 | electron_1.globalShortcut.register("CommandOrControl+Down", () => {
56 | console.log("Command/Ctrl + down pressed. Moving window down.");
57 | this.appState.moveWindowDown();
58 | });
59 | electron_1.globalShortcut.register("CommandOrControl+Up", () => {
60 | console.log("Command/Ctrl + Up pressed. Moving window Up.");
61 | this.appState.moveWindowUp();
62 | });
63 | electron_1.globalShortcut.register("CommandOrControl+B", () => {
64 | this.appState.toggleMainWindow();
65 | // If window exists and we're showing it, bring it to front
66 | const mainWindow = this.appState.getMainWindow();
67 | if (mainWindow && !this.appState.isVisible()) {
68 | // Force the window to the front on macOS
69 | if (process.platform === "darwin") {
70 | mainWindow.setAlwaysOnTop(true, "normal");
71 | // Reset alwaysOnTop after a brief delay
72 | setTimeout(() => {
73 | if (mainWindow && !mainWindow.isDestroyed()) {
74 | mainWindow.setAlwaysOnTop(true, "floating");
75 | }
76 | }, 100);
77 | }
78 | }
79 | });
80 | // Unregister shortcuts when quitting
81 | electron_1.app.on("will-quit", () => {
82 | electron_1.globalShortcut.unregisterAll();
83 | });
84 | }
85 | }
86 | exports.ShortcutsHelper = ShortcutsHelper;
87 | //# sourceMappingURL=shortcuts.js.map
--------------------------------------------------------------------------------
/dist-electron/preload.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"preload.js","sourceRoot":"","sources":["../electron/preload.ts"],"names":[],"mappings":"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"}
--------------------------------------------------------------------------------
/src/App.tsx:
--------------------------------------------------------------------------------
1 | import { ToastProvider } from "./components/ui/toast"
2 | import Queue from "./_pages/Queue"
3 | import { ToastViewport } from "@radix-ui/react-toast"
4 | import { useEffect, useRef, useState } from "react"
5 | import Solutions from "./_pages/Solutions"
6 | import { QueryClient, QueryClientProvider } from "react-query"
7 |
8 | const queryClient = new QueryClient({
9 | defaultOptions: {
10 | queries: {
11 | staleTime: Infinity,
12 | cacheTime: Infinity
13 | }
14 | }
15 | })
16 |
17 | const App: React.FC = () => {
18 | const [view, setView] = useState<"queue" | "solutions" | "debug">("queue")
19 | const [isChatOpen, setIsChatOpen] = useState(false)
20 | const containerRef = useRef(null)
21 |
22 | // Effect for height monitoring
23 | useEffect(() => {
24 | const cleanup = window.electronAPI.onResetView(() => {
25 | console.log("Received 'reset-view' message from main process.")
26 | queryClient.invalidateQueries(["screenshots"])
27 | queryClient.invalidateQueries(["problem_statement"])
28 | queryClient.invalidateQueries(["solution"])
29 | queryClient.invalidateQueries(["new_solution"])
30 | setView("queue")
31 | })
32 |
33 | return () => {
34 | cleanup()
35 | }
36 | }, [])
37 |
38 | useEffect(() => {
39 | if (!containerRef.current) return
40 |
41 | const updateHeight = () => {
42 | if (!containerRef.current) return
43 | const height = containerRef.current.scrollHeight
44 | const width = containerRef.current.scrollWidth
45 | window.electronAPI?.updateContentDimensions({ width, height })
46 | }
47 |
48 | const resizeObserver = new ResizeObserver(() => {
49 | updateHeight()
50 | })
51 |
52 | // Initial height update
53 | updateHeight()
54 |
55 | // Observe for changes
56 | resizeObserver.observe(containerRef.current)
57 |
58 | // Also update height when view changes
59 | const mutationObserver = new MutationObserver(() => {
60 | updateHeight()
61 | })
62 |
63 | mutationObserver.observe(containerRef.current, {
64 | childList: true,
65 | subtree: true,
66 | attributes: true,
67 | characterData: true
68 | })
69 |
70 | return () => {
71 | resizeObserver.disconnect()
72 | mutationObserver.disconnect()
73 | }
74 | }, [view]) // Re-run when view changes
75 |
76 | useEffect(() => {
77 | const cleanupFunctions = [
78 | window.electronAPI.onSolutionStart(() => {
79 | setView("solutions")
80 | console.log("starting processing")
81 | }),
82 |
83 | window.electronAPI.onUnauthorized(() => {
84 | queryClient.removeQueries(["screenshots"])
85 | queryClient.removeQueries(["solution"])
86 | queryClient.removeQueries(["problem_statement"])
87 | setView("queue")
88 | console.log("Unauthorized")
89 | }),
90 | // Update this reset handler
91 | window.electronAPI.onResetView(() => {
92 | console.log("Received 'reset-view' message from main process")
93 |
94 | queryClient.removeQueries(["screenshots"])
95 | queryClient.removeQueries(["solution"])
96 | queryClient.removeQueries(["problem_statement"])
97 | setView("queue")
98 | console.log("View reset to 'queue' via Command+R shortcut")
99 | }),
100 | window.electronAPI.onProblemExtracted((data: any) => {
101 | if (view === "queue") {
102 | console.log("Problem extracted successfully")
103 | queryClient.invalidateQueries(["problem_statement"])
104 | queryClient.setQueryData(["problem_statement"], data)
105 | }
106 | })
107 | ]
108 | return () => cleanupFunctions.forEach((cleanup) => cleanup())
109 | }, [])
110 |
111 | // Handle Command+Enter to open chat
112 | useEffect(() => {
113 | const handleKeyDown = (e: KeyboardEvent) => {
114 | if ((e.metaKey || e.ctrlKey) && e.key === "Enter") {
115 | e.preventDefault()
116 | setIsChatOpen(true)
117 | }
118 | }
119 |
120 | window.addEventListener("keydown", handleKeyDown)
121 | return () => window.removeEventListener("keydown", handleKeyDown)
122 | }, [])
123 |
124 | return (
125 |
126 |
127 |
128 | {view === "queue" ? (
129 |
130 | ) : view === "solutions" ? (
131 |
132 | ) : (
133 | <>>
134 | )}
135 |
136 |
137 |
138 |
139 | )
140 | }
141 |
142 | export default App
143 |
--------------------------------------------------------------------------------
/electron/ScreenshotHelper.ts:
--------------------------------------------------------------------------------
1 | // ScreenshotHelper.ts
2 |
3 | import path from "node:path"
4 | import fs from "node:fs"
5 | import { app } from "electron"
6 | import { v4 as uuidv4 } from "uuid"
7 | import screenshot from "screenshot-desktop"
8 |
9 | export class ScreenshotHelper {
10 | private screenshotQueue: string[] = []
11 | private extraScreenshotQueue: string[] = []
12 | private readonly MAX_SCREENSHOTS = 5
13 |
14 | private readonly screenshotDir: string
15 | private readonly extraScreenshotDir: string
16 |
17 | private view: "queue" | "solutions" = "queue"
18 |
19 | constructor(view: "queue" | "solutions" = "queue") {
20 | this.view = view
21 |
22 | // Initialize directories
23 | this.screenshotDir = path.join(app.getPath("userData"), "screenshots")
24 | this.extraScreenshotDir = path.join(
25 | app.getPath("userData"),
26 | "extra_screenshots"
27 | )
28 |
29 | // Create directories if they don't exist
30 | if (!fs.existsSync(this.screenshotDir)) {
31 | fs.mkdirSync(this.screenshotDir)
32 | }
33 | if (!fs.existsSync(this.extraScreenshotDir)) {
34 | fs.mkdirSync(this.extraScreenshotDir)
35 | }
36 | }
37 |
38 | public getView(): "queue" | "solutions" {
39 | return this.view
40 | }
41 |
42 | public setView(view: "queue" | "solutions"): void {
43 | this.view = view
44 | }
45 |
46 | public getScreenshotQueue(): string[] {
47 | return this.screenshotQueue
48 | }
49 |
50 | public getExtraScreenshotQueue(): string[] {
51 | return this.extraScreenshotQueue
52 | }
53 |
54 | public clearQueues(): void {
55 | // Clear screenshotQueue
56 | this.screenshotQueue.forEach((screenshotPath) => {
57 | fs.unlink(screenshotPath, (err) => {
58 | if (err)
59 | console.error(`Error deleting screenshot at ${screenshotPath}:`, err)
60 | })
61 | })
62 | this.screenshotQueue = []
63 |
64 | // Clear extraScreenshotQueue
65 | this.extraScreenshotQueue.forEach((screenshotPath) => {
66 | fs.unlink(screenshotPath, (err) => {
67 | if (err)
68 | console.error(
69 | `Error deleting extra screenshot at ${screenshotPath}:`,
70 | err
71 | )
72 | })
73 | })
74 | this.extraScreenshotQueue = []
75 | }
76 |
77 | public async takeScreenshot(
78 | hideMainWindow: () => void,
79 | showMainWindow: () => void
80 | ): Promise {
81 | hideMainWindow()
82 | let screenshotPath = ""
83 |
84 | if (this.view === "queue") {
85 | screenshotPath = path.join(this.screenshotDir, `${uuidv4()}.png`)
86 | await screenshot({ filename: screenshotPath })
87 |
88 | this.screenshotQueue.push(screenshotPath)
89 | if (this.screenshotQueue.length > this.MAX_SCREENSHOTS) {
90 | const removedPath = this.screenshotQueue.shift()
91 | if (removedPath) {
92 | try {
93 | await fs.promises.unlink(removedPath)
94 | } catch (error) {
95 | console.error("Error removing old screenshot:", error)
96 | }
97 | }
98 | }
99 | } else {
100 | screenshotPath = path.join(this.extraScreenshotDir, `${uuidv4()}.png`)
101 | await screenshot({ filename: screenshotPath })
102 |
103 | this.extraScreenshotQueue.push(screenshotPath)
104 | if (this.extraScreenshotQueue.length > this.MAX_SCREENSHOTS) {
105 | const removedPath = this.extraScreenshotQueue.shift()
106 | if (removedPath) {
107 | try {
108 | await fs.promises.unlink(removedPath)
109 | } catch (error) {
110 | console.error("Error removing old screenshot:", error)
111 | }
112 | }
113 | }
114 | }
115 |
116 | showMainWindow()
117 | return screenshotPath
118 | }
119 |
120 | public async getImagePreview(filepath: string): Promise {
121 | try {
122 | const data = await fs.promises.readFile(filepath)
123 | return `data:image/png;base64,${data.toString("base64")}`
124 | } catch (error) {
125 | console.error("Error reading image:", error)
126 | throw error
127 | }
128 | }
129 |
130 | public async deleteScreenshot(
131 | path: string
132 | ): Promise<{ success: boolean; error?: string }> {
133 | try {
134 | await fs.promises.unlink(path)
135 | if (this.view === "queue") {
136 | this.screenshotQueue = this.screenshotQueue.filter(
137 | (filePath) => filePath !== path
138 | )
139 | } else {
140 | this.extraScreenshotQueue = this.extraScreenshotQueue.filter(
141 | (filePath) => filePath !== path
142 | )
143 | }
144 | return { success: true }
145 | } catch (error) {
146 | console.error("Error deleting file:", error)
147 | return { success: false, error: error.message }
148 | }
149 | }
150 | }
151 |
--------------------------------------------------------------------------------
/electron/ProcessingHelper.ts:
--------------------------------------------------------------------------------
1 | // ProcessingHelper.ts
2 |
3 | import { AppState } from "./main"
4 | import { LLMHelper } from "./LLMHelper"
5 | import dotenv from "dotenv"
6 |
7 | dotenv.config()
8 |
9 | const isDev = process.env.NODE_ENV === "development"
10 | const isDevTest = process.env.IS_DEV_TEST === "true"
11 | const MOCK_API_WAIT_TIME = Number(process.env.MOCK_API_WAIT_TIME) || 500
12 |
13 | export class ProcessingHelper {
14 | private appState: AppState
15 | private llmHelper: LLMHelper
16 | private currentProcessingAbortController: AbortController | null = null
17 | private currentExtraProcessingAbortController: AbortController | null = null
18 |
19 | constructor(appState: AppState) {
20 | this.appState = appState
21 | const apiKey = process.env.GEMINI_API_KEY
22 | if (!apiKey) {
23 | throw new Error("GEMINI_API_KEY not found in environment variables")
24 | }
25 | this.llmHelper = new LLMHelper(apiKey)
26 | }
27 |
28 | public async processScreenshots(): Promise {
29 | const mainWindow = this.appState.getMainWindow()
30 | if (!mainWindow) return
31 |
32 | const view = this.appState.getView()
33 |
34 | if (view === "queue") {
35 | const screenshotQueue = this.appState.getScreenshotHelper().getScreenshotQueue()
36 | if (screenshotQueue.length === 0) {
37 | mainWindow.webContents.send(this.appState.PROCESSING_EVENTS.NO_SCREENSHOTS)
38 | return
39 | }
40 |
41 | mainWindow.webContents.send(this.appState.PROCESSING_EVENTS.INITIAL_START)
42 | this.appState.setView("solutions")
43 |
44 | this.currentProcessingAbortController = new AbortController()
45 |
46 | try {
47 | // Extract problem information using LLM with vision
48 | const problemInfo = await this.llmHelper.extractProblemFromImages(screenshotQueue)
49 |
50 | // Store problem info
51 | this.appState.setProblemInfo(problemInfo)
52 |
53 | // Send problem extracted event
54 | mainWindow.webContents.send(
55 | this.appState.PROCESSING_EVENTS.PROBLEM_EXTRACTED,
56 | problemInfo
57 | )
58 |
59 | // Generate solution
60 | const solution = await this.llmHelper.generateSolution(problemInfo)
61 |
62 | mainWindow.webContents.send(
63 | this.appState.PROCESSING_EVENTS.SOLUTION_SUCCESS,
64 | solution
65 | )
66 |
67 | } catch (error: any) {
68 | console.error("Processing error:", error)
69 | mainWindow.webContents.send(
70 | this.appState.PROCESSING_EVENTS.INITIAL_SOLUTION_ERROR,
71 | error.message
72 | )
73 | } finally {
74 | this.currentProcessingAbortController = null
75 | }
76 | } else {
77 | // Debug mode
78 | const extraScreenshotQueue = this.appState.getScreenshotHelper().getExtraScreenshotQueue()
79 | if (extraScreenshotQueue.length === 0) {
80 | console.log("No extra screenshots to process")
81 | mainWindow.webContents.send(this.appState.PROCESSING_EVENTS.NO_SCREENSHOTS)
82 | return
83 | }
84 |
85 | mainWindow.webContents.send(this.appState.PROCESSING_EVENTS.DEBUG_START)
86 | this.currentExtraProcessingAbortController = new AbortController()
87 |
88 | try {
89 | // Get problem info and current solution
90 | const problemInfo = this.appState.getProblemInfo()
91 | if (!problemInfo) {
92 | throw new Error("No problem info available")
93 | }
94 |
95 | // Get current solution from state
96 | const currentSolution = await this.llmHelper.generateSolution(problemInfo)
97 | const currentCode = currentSolution.solution.code
98 |
99 | // Debug the solution using vision model
100 | const debugResult = await this.llmHelper.debugSolutionWithImages(
101 | problemInfo,
102 | currentCode,
103 | extraScreenshotQueue
104 | )
105 |
106 | this.appState.setHasDebugged(true)
107 | mainWindow.webContents.send(
108 | this.appState.PROCESSING_EVENTS.DEBUG_SUCCESS,
109 | debugResult
110 | )
111 |
112 | } catch (error: any) {
113 | console.error("Debug processing error:", error)
114 | mainWindow.webContents.send(
115 | this.appState.PROCESSING_EVENTS.DEBUG_ERROR,
116 | error.message
117 | )
118 | } finally {
119 | this.currentExtraProcessingAbortController = null
120 | }
121 | }
122 | }
123 |
124 | public cancelOngoingRequests(): void {
125 | if (this.currentProcessingAbortController) {
126 | this.currentProcessingAbortController.abort()
127 | this.currentProcessingAbortController = null
128 | }
129 |
130 | if (this.currentExtraProcessingAbortController) {
131 | this.currentExtraProcessingAbortController.abort()
132 | this.currentExtraProcessingAbortController = null
133 | }
134 |
135 | this.appState.setHasDebugged(false)
136 | }
137 | }
138 |
--------------------------------------------------------------------------------
/dist-electron/ProcessingHelper.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | // ProcessingHelper.ts
3 | var __importDefault = (this && this.__importDefault) || function (mod) {
4 | return (mod && mod.__esModule) ? mod : { "default": mod };
5 | };
6 | Object.defineProperty(exports, "__esModule", { value: true });
7 | exports.ProcessingHelper = void 0;
8 | const LLMHelper_1 = require("./LLMHelper");
9 | const dotenv_1 = __importDefault(require("dotenv"));
10 | dotenv_1.default.config();
11 | const isDev = process.env.NODE_ENV === "development";
12 | const isDevTest = process.env.IS_DEV_TEST === "true";
13 | const MOCK_API_WAIT_TIME = Number(process.env.MOCK_API_WAIT_TIME) || 500;
14 | class ProcessingHelper {
15 | appState;
16 | llmHelper;
17 | currentProcessingAbortController = null;
18 | currentExtraProcessingAbortController = null;
19 | constructor(appState) {
20 | this.appState = appState;
21 | const apiKey = process.env.GEMINI_API_KEY;
22 | if (!apiKey) {
23 | throw new Error("GEMINI_API_KEY not found in environment variables");
24 | }
25 | this.llmHelper = new LLMHelper_1.LLMHelper(apiKey);
26 | }
27 | async processScreenshots() {
28 | const mainWindow = this.appState.getMainWindow();
29 | if (!mainWindow)
30 | return;
31 | const view = this.appState.getView();
32 | if (view === "queue") {
33 | const screenshotQueue = this.appState.getScreenshotHelper().getScreenshotQueue();
34 | if (screenshotQueue.length === 0) {
35 | mainWindow.webContents.send(this.appState.PROCESSING_EVENTS.NO_SCREENSHOTS);
36 | return;
37 | }
38 | mainWindow.webContents.send(this.appState.PROCESSING_EVENTS.INITIAL_START);
39 | this.appState.setView("solutions");
40 | this.currentProcessingAbortController = new AbortController();
41 | try {
42 | // Extract problem information using LLM with vision
43 | const problemInfo = await this.llmHelper.extractProblemFromImages(screenshotQueue);
44 | // Store problem info
45 | this.appState.setProblemInfo(problemInfo);
46 | // Send problem extracted event
47 | mainWindow.webContents.send(this.appState.PROCESSING_EVENTS.PROBLEM_EXTRACTED, problemInfo);
48 | // Generate solution
49 | const solution = await this.llmHelper.generateSolution(problemInfo);
50 | mainWindow.webContents.send(this.appState.PROCESSING_EVENTS.SOLUTION_SUCCESS, solution);
51 | }
52 | catch (error) {
53 | console.error("Processing error:", error);
54 | mainWindow.webContents.send(this.appState.PROCESSING_EVENTS.INITIAL_SOLUTION_ERROR, error.message);
55 | }
56 | finally {
57 | this.currentProcessingAbortController = null;
58 | }
59 | }
60 | else {
61 | // Debug mode
62 | const extraScreenshotQueue = this.appState.getScreenshotHelper().getExtraScreenshotQueue();
63 | if (extraScreenshotQueue.length === 0) {
64 | console.log("No extra screenshots to process");
65 | mainWindow.webContents.send(this.appState.PROCESSING_EVENTS.NO_SCREENSHOTS);
66 | return;
67 | }
68 | mainWindow.webContents.send(this.appState.PROCESSING_EVENTS.DEBUG_START);
69 | this.currentExtraProcessingAbortController = new AbortController();
70 | try {
71 | // Get problem info and current solution
72 | const problemInfo = this.appState.getProblemInfo();
73 | if (!problemInfo) {
74 | throw new Error("No problem info available");
75 | }
76 | // Get current solution from state
77 | const currentSolution = await this.llmHelper.generateSolution(problemInfo);
78 | const currentCode = currentSolution.solution.code;
79 | // Debug the solution using vision model
80 | const debugResult = await this.llmHelper.debugSolutionWithImages(problemInfo, currentCode, extraScreenshotQueue);
81 | this.appState.setHasDebugged(true);
82 | mainWindow.webContents.send(this.appState.PROCESSING_EVENTS.DEBUG_SUCCESS, debugResult);
83 | }
84 | catch (error) {
85 | console.error("Debug processing error:", error);
86 | mainWindow.webContents.send(this.appState.PROCESSING_EVENTS.DEBUG_ERROR, error.message);
87 | }
88 | finally {
89 | this.currentExtraProcessingAbortController = null;
90 | }
91 | }
92 | }
93 | cancelOngoingRequests() {
94 | if (this.currentProcessingAbortController) {
95 | this.currentProcessingAbortController.abort();
96 | this.currentProcessingAbortController = null;
97 | }
98 | if (this.currentExtraProcessingAbortController) {
99 | this.currentExtraProcessingAbortController.abort();
100 | this.currentExtraProcessingAbortController = null;
101 | }
102 | this.appState.setHasDebugged(false);
103 | }
104 | }
105 | exports.ProcessingHelper = ProcessingHelper;
106 | //# sourceMappingURL=ProcessingHelper.js.map
--------------------------------------------------------------------------------
/electron/LLMHelper.ts:
--------------------------------------------------------------------------------
1 | import { GoogleGenerativeAI, GenerativeModel } from "@google/generative-ai"
2 | import fs from "fs"
3 |
4 | export class LLMHelper {
5 | private model: GenerativeModel
6 |
7 | constructor(apiKey: string) {
8 | const genAI = new GoogleGenerativeAI(apiKey)
9 | this.model = genAI.getGenerativeModel({ model: "gemini-2.0-flash" })
10 | }
11 |
12 | private async fileToGenerativePart(imagePath: string) {
13 | const imageData = await fs.promises.readFile(imagePath)
14 | return {
15 | inlineData: {
16 | data: imageData.toString("base64"),
17 | mimeType: "image/png"
18 | }
19 | }
20 | }
21 |
22 | private cleanJsonResponse(text: string): string {
23 | // Remove markdown code block syntax if present
24 | text = text.replace(/^```(?:json)?\n/, '').replace(/\n```$/, '');
25 | // Remove any leading/trailing whitespace
26 | text = text.trim();
27 | return text;
28 | }
29 |
30 | public async extractProblemFromImages(imagePaths: string[]) {
31 | try {
32 | const imageParts = await Promise.all(imagePaths.map(path => this.fileToGenerativePart(path)))
33 |
34 | const prompt = `You are a coding problem analyzer. Please analyze these images of a coding problem and extract the following information in JSON format:
35 | {
36 | "problem_statement": "The complete problem statement",
37 | "input_format": {
38 | "description": "Description of input format",
39 | "parameters": [{"name": "param name", "type": "param type", "description": "param description"}]
40 | },
41 | "output_format": {
42 | "description": "Description of what should be output",
43 | "type": "The expected type of the output"
44 | },
45 | "constraints": [
46 | {"description": "Each constraint in plain text"}
47 | ],
48 | "test_cases": [
49 | {
50 | "input": "Example input",
51 | "output": "Expected output",
52 | "explanation": "Explanation if provided"
53 | }
54 | ]
55 | }
56 | Important: Return ONLY the JSON object, without any markdown formatting or code blocks.`
57 |
58 | const result = await this.model.generateContent([prompt, ...imageParts])
59 | const response = await result.response
60 | const text = this.cleanJsonResponse(response.text())
61 | return JSON.parse(text)
62 | } catch (error) {
63 | console.error("Error extracting problem from images:", error)
64 | throw error
65 | }
66 | }
67 |
68 | public async generateSolution(problemInfo: any) {
69 | const prompt = `Given this coding problem:
70 | ${JSON.stringify(problemInfo, null, 2)}
71 |
72 | Please provide a solution in the following JSON format:
73 | {
74 | "solution": {
75 | "explanation": "Detailed explanation of the approach",
76 | "complexity": {
77 | "time": "Time complexity",
78 | "space": "Space complexity"
79 | },
80 | "code": "The complete solution code",
81 | "test_results": [
82 | {
83 | "input": "test case input",
84 | "expected": "expected output",
85 | "actual": "actual output",
86 | "passed": true/false
87 | }
88 | ]
89 | }
90 | }
91 | Important: Return ONLY the JSON object, without any markdown formatting or code blocks.`
92 |
93 | const result = await this.model.generateContent(prompt)
94 | const response = await result.response
95 | const text = this.cleanJsonResponse(response.text())
96 | return JSON.parse(text)
97 | }
98 |
99 | public async debugSolutionWithImages(problemInfo: any, currentCode: string, debugImagePaths: string[]) {
100 | try {
101 | const imageParts = await Promise.all(debugImagePaths.map(path => this.fileToGenerativePart(path)))
102 |
103 | const prompt = `You are a coding problem debugger. Given:
104 | 1. The original problem: ${JSON.stringify(problemInfo, null, 2)}
105 | 2. The current solution: ${currentCode}
106 | 3. The debug information in the provided images
107 |
108 | Please analyze the debug information and provide feedback in this JSON format:
109 | {
110 | "analysis": {
111 | "issues_found": [
112 | {
113 | "description": "Description of the issue",
114 | "location": "Where in the code",
115 | "severity": "high/medium/low"
116 | }
117 | ],
118 | "suggested_fixes": [
119 | {
120 | "description": "Description of the fix",
121 | "code_change": "The specific code change needed"
122 | }
123 | ]
124 | },
125 | "improved_solution": {
126 | "code": "The complete improved solution",
127 | "explanation": "Explanation of the changes made"
128 | }
129 | }
130 | Important: Return ONLY the JSON object, without any markdown formatting or code blocks.`
131 |
132 | const result = await this.model.generateContent([prompt, ...imageParts])
133 | const response = await result.response
134 | const text = this.cleanJsonResponse(response.text())
135 | return JSON.parse(text)
136 | } catch (error) {
137 | console.error("Error debugging solution with images:", error)
138 | throw error
139 | }
140 | }
141 | }
--------------------------------------------------------------------------------
/dist-electron/ScreenshotHelper.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | // ScreenshotHelper.ts
3 | var __importDefault = (this && this.__importDefault) || function (mod) {
4 | return (mod && mod.__esModule) ? mod : { "default": mod };
5 | };
6 | Object.defineProperty(exports, "__esModule", { value: true });
7 | exports.ScreenshotHelper = void 0;
8 | const node_path_1 = __importDefault(require("node:path"));
9 | const node_fs_1 = __importDefault(require("node:fs"));
10 | const electron_1 = require("electron");
11 | const uuid_1 = require("uuid");
12 | const screenshot_desktop_1 = __importDefault(require("screenshot-desktop"));
13 | class ScreenshotHelper {
14 | screenshotQueue = [];
15 | extraScreenshotQueue = [];
16 | MAX_SCREENSHOTS = 5;
17 | screenshotDir;
18 | extraScreenshotDir;
19 | view = "queue";
20 | constructor(view = "queue") {
21 | this.view = view;
22 | // Initialize directories
23 | this.screenshotDir = node_path_1.default.join(electron_1.app.getPath("userData"), "screenshots");
24 | this.extraScreenshotDir = node_path_1.default.join(electron_1.app.getPath("userData"), "extra_screenshots");
25 | // Create directories if they don't exist
26 | if (!node_fs_1.default.existsSync(this.screenshotDir)) {
27 | node_fs_1.default.mkdirSync(this.screenshotDir);
28 | }
29 | if (!node_fs_1.default.existsSync(this.extraScreenshotDir)) {
30 | node_fs_1.default.mkdirSync(this.extraScreenshotDir);
31 | }
32 | }
33 | getView() {
34 | return this.view;
35 | }
36 | setView(view) {
37 | this.view = view;
38 | }
39 | getScreenshotQueue() {
40 | return this.screenshotQueue;
41 | }
42 | getExtraScreenshotQueue() {
43 | return this.extraScreenshotQueue;
44 | }
45 | clearQueues() {
46 | // Clear screenshotQueue
47 | this.screenshotQueue.forEach((screenshotPath) => {
48 | node_fs_1.default.unlink(screenshotPath, (err) => {
49 | if (err)
50 | console.error(`Error deleting screenshot at ${screenshotPath}:`, err);
51 | });
52 | });
53 | this.screenshotQueue = [];
54 | // Clear extraScreenshotQueue
55 | this.extraScreenshotQueue.forEach((screenshotPath) => {
56 | node_fs_1.default.unlink(screenshotPath, (err) => {
57 | if (err)
58 | console.error(`Error deleting extra screenshot at ${screenshotPath}:`, err);
59 | });
60 | });
61 | this.extraScreenshotQueue = [];
62 | }
63 | async takeScreenshot(hideMainWindow, showMainWindow) {
64 | hideMainWindow();
65 | let screenshotPath = "";
66 | if (this.view === "queue") {
67 | screenshotPath = node_path_1.default.join(this.screenshotDir, `${(0, uuid_1.v4)()}.png`);
68 | await (0, screenshot_desktop_1.default)({ filename: screenshotPath });
69 | this.screenshotQueue.push(screenshotPath);
70 | if (this.screenshotQueue.length > this.MAX_SCREENSHOTS) {
71 | const removedPath = this.screenshotQueue.shift();
72 | if (removedPath) {
73 | try {
74 | await node_fs_1.default.promises.unlink(removedPath);
75 | }
76 | catch (error) {
77 | console.error("Error removing old screenshot:", error);
78 | }
79 | }
80 | }
81 | }
82 | else {
83 | screenshotPath = node_path_1.default.join(this.extraScreenshotDir, `${(0, uuid_1.v4)()}.png`);
84 | await (0, screenshot_desktop_1.default)({ filename: screenshotPath });
85 | this.extraScreenshotQueue.push(screenshotPath);
86 | if (this.extraScreenshotQueue.length > this.MAX_SCREENSHOTS) {
87 | const removedPath = this.extraScreenshotQueue.shift();
88 | if (removedPath) {
89 | try {
90 | await node_fs_1.default.promises.unlink(removedPath);
91 | }
92 | catch (error) {
93 | console.error("Error removing old screenshot:", error);
94 | }
95 | }
96 | }
97 | }
98 | showMainWindow();
99 | return screenshotPath;
100 | }
101 | async getImagePreview(filepath) {
102 | try {
103 | const data = await node_fs_1.default.promises.readFile(filepath);
104 | return `data:image/png;base64,${data.toString("base64")}`;
105 | }
106 | catch (error) {
107 | console.error("Error reading image:", error);
108 | throw error;
109 | }
110 | }
111 | async deleteScreenshot(path) {
112 | try {
113 | await node_fs_1.default.promises.unlink(path);
114 | if (this.view === "queue") {
115 | this.screenshotQueue = this.screenshotQueue.filter((filePath) => filePath !== path);
116 | }
117 | else {
118 | this.extraScreenshotQueue = this.extraScreenshotQueue.filter((filePath) => filePath !== path);
119 | }
120 | return { success: true };
121 | }
122 | catch (error) {
123 | console.error("Error deleting file:", error);
124 | return { success: false, error: error.message };
125 | }
126 | }
127 | }
128 | exports.ScreenshotHelper = ScreenshotHelper;
129 | //# sourceMappingURL=ScreenshotHelper.js.map
--------------------------------------------------------------------------------
/dist-electron/preload.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | Object.defineProperty(exports, "__esModule", { value: true });
3 | exports.PROCESSING_EVENTS = void 0;
4 | const electron_1 = require("electron");
5 | exports.PROCESSING_EVENTS = {
6 | //global states
7 | UNAUTHORIZED: "procesing-unauthorized",
8 | NO_SCREENSHOTS: "processing-no-screenshots",
9 | //states for generating the initial solution
10 | INITIAL_START: "initial-start",
11 | PROBLEM_EXTRACTED: "problem-extracted",
12 | SOLUTION_SUCCESS: "solution-success",
13 | INITIAL_SOLUTION_ERROR: "solution-error",
14 | //states for processing the debugging
15 | DEBUG_START: "debug-start",
16 | DEBUG_SUCCESS: "debug-success",
17 | DEBUG_ERROR: "debug-error"
18 | };
19 | window.GEMINI_API_KEY = process.env.GEMINI_API_KEY;
20 | // Expose the Electron API to the renderer process
21 | electron_1.contextBridge.exposeInMainWorld("electronAPI", {
22 | updateContentDimensions: (dimensions) => electron_1.ipcRenderer.invoke("update-content-dimensions", dimensions),
23 | takeScreenshot: () => electron_1.ipcRenderer.invoke("take-screenshot"),
24 | getScreenshots: () => electron_1.ipcRenderer.invoke("get-screenshots"),
25 | deleteScreenshot: (path) => electron_1.ipcRenderer.invoke("delete-screenshot", path),
26 | // Event listeners
27 | onScreenshotTaken: (callback) => {
28 | const subscription = (_, data) => callback(data);
29 | electron_1.ipcRenderer.on("screenshot-taken", subscription);
30 | return () => {
31 | electron_1.ipcRenderer.removeListener("screenshot-taken", subscription);
32 | };
33 | },
34 | onSolutionsReady: (callback) => {
35 | const subscription = (_, solutions) => callback(solutions);
36 | electron_1.ipcRenderer.on("solutions-ready", subscription);
37 | return () => {
38 | electron_1.ipcRenderer.removeListener("solutions-ready", subscription);
39 | };
40 | },
41 | onResetView: (callback) => {
42 | const subscription = () => callback();
43 | electron_1.ipcRenderer.on("reset-view", subscription);
44 | return () => {
45 | electron_1.ipcRenderer.removeListener("reset-view", subscription);
46 | };
47 | },
48 | onSolutionStart: (callback) => {
49 | const subscription = () => callback();
50 | electron_1.ipcRenderer.on(exports.PROCESSING_EVENTS.INITIAL_START, subscription);
51 | return () => {
52 | electron_1.ipcRenderer.removeListener(exports.PROCESSING_EVENTS.INITIAL_START, subscription);
53 | };
54 | },
55 | onDebugStart: (callback) => {
56 | const subscription = () => callback();
57 | electron_1.ipcRenderer.on(exports.PROCESSING_EVENTS.DEBUG_START, subscription);
58 | return () => {
59 | electron_1.ipcRenderer.removeListener(exports.PROCESSING_EVENTS.DEBUG_START, subscription);
60 | };
61 | },
62 | onDebugSuccess: (callback) => {
63 | electron_1.ipcRenderer.on("debug-success", (_event, data) => callback(data));
64 | return () => {
65 | electron_1.ipcRenderer.removeListener("debug-success", (_event, data) => callback(data));
66 | };
67 | },
68 | onDebugError: (callback) => {
69 | const subscription = (_, error) => callback(error);
70 | electron_1.ipcRenderer.on(exports.PROCESSING_EVENTS.DEBUG_ERROR, subscription);
71 | return () => {
72 | electron_1.ipcRenderer.removeListener(exports.PROCESSING_EVENTS.DEBUG_ERROR, subscription);
73 | };
74 | },
75 | onSolutionError: (callback) => {
76 | const subscription = (_, error) => callback(error);
77 | electron_1.ipcRenderer.on(exports.PROCESSING_EVENTS.INITIAL_SOLUTION_ERROR, subscription);
78 | return () => {
79 | electron_1.ipcRenderer.removeListener(exports.PROCESSING_EVENTS.INITIAL_SOLUTION_ERROR, subscription);
80 | };
81 | },
82 | onProcessingNoScreenshots: (callback) => {
83 | const subscription = () => callback();
84 | electron_1.ipcRenderer.on(exports.PROCESSING_EVENTS.NO_SCREENSHOTS, subscription);
85 | return () => {
86 | electron_1.ipcRenderer.removeListener(exports.PROCESSING_EVENTS.NO_SCREENSHOTS, subscription);
87 | };
88 | },
89 | onProblemExtracted: (callback) => {
90 | const subscription = (_, data) => callback(data);
91 | electron_1.ipcRenderer.on(exports.PROCESSING_EVENTS.PROBLEM_EXTRACTED, subscription);
92 | return () => {
93 | electron_1.ipcRenderer.removeListener(exports.PROCESSING_EVENTS.PROBLEM_EXTRACTED, subscription);
94 | };
95 | },
96 | onSolutionSuccess: (callback) => {
97 | const subscription = (_, data) => callback(data);
98 | electron_1.ipcRenderer.on(exports.PROCESSING_EVENTS.SOLUTION_SUCCESS, subscription);
99 | return () => {
100 | electron_1.ipcRenderer.removeListener(exports.PROCESSING_EVENTS.SOLUTION_SUCCESS, subscription);
101 | };
102 | },
103 | onUnauthorized: (callback) => {
104 | const subscription = () => callback();
105 | electron_1.ipcRenderer.on(exports.PROCESSING_EVENTS.UNAUTHORIZED, subscription);
106 | return () => {
107 | electron_1.ipcRenderer.removeListener(exports.PROCESSING_EVENTS.UNAUTHORIZED, subscription);
108 | };
109 | },
110 | moveWindowLeft: () => electron_1.ipcRenderer.invoke("move-window-left"),
111 | moveWindowRight: () => electron_1.ipcRenderer.invoke("move-window-right"),
112 | quitApp: () => electron_1.ipcRenderer.invoke("quit-app"),
113 | getApiKey: () => electron_1.ipcRenderer.invoke("get-gemini-api-key")
114 | });
115 | //# sourceMappingURL=preload.js.map
--------------------------------------------------------------------------------
/dist-electron/LLMHelper.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | var __importDefault = (this && this.__importDefault) || function (mod) {
3 | return (mod && mod.__esModule) ? mod : { "default": mod };
4 | };
5 | Object.defineProperty(exports, "__esModule", { value: true });
6 | exports.LLMHelper = void 0;
7 | const generative_ai_1 = require("@google/generative-ai");
8 | const fs_1 = __importDefault(require("fs"));
9 | class LLMHelper {
10 | model;
11 | constructor(apiKey) {
12 | const genAI = new generative_ai_1.GoogleGenerativeAI(apiKey);
13 | this.model = genAI.getGenerativeModel({ model: "gemini-2.0-flash" });
14 | }
15 | async fileToGenerativePart(imagePath) {
16 | const imageData = await fs_1.default.promises.readFile(imagePath);
17 | return {
18 | inlineData: {
19 | data: imageData.toString("base64"),
20 | mimeType: "image/png"
21 | }
22 | };
23 | }
24 | cleanJsonResponse(text) {
25 | // Remove markdown code block syntax if present
26 | text = text.replace(/^```(?:json)?\n/, '').replace(/\n```$/, '');
27 | // Remove any leading/trailing whitespace
28 | text = text.trim();
29 | return text;
30 | }
31 | async extractProblemFromImages(imagePaths) {
32 | try {
33 | const imageParts = await Promise.all(imagePaths.map(path => this.fileToGenerativePart(path)));
34 | const prompt = `You are a coding problem analyzer. Please analyze these images of a coding problem and extract the following information in JSON format:
35 | {
36 | "problem_statement": "The complete problem statement",
37 | "input_format": {
38 | "description": "Description of input format",
39 | "parameters": [{"name": "param name", "type": "param type", "description": "param description"}]
40 | },
41 | "output_format": {
42 | "description": "Description of what should be output",
43 | "type": "The expected type of the output"
44 | },
45 | "constraints": [
46 | {"description": "Each constraint in plain text"}
47 | ],
48 | "test_cases": [
49 | {
50 | "input": "Example input",
51 | "output": "Expected output",
52 | "explanation": "Explanation if provided"
53 | }
54 | ]
55 | }
56 | Important: Return ONLY the JSON object, without any markdown formatting or code blocks.`;
57 | const result = await this.model.generateContent([prompt, ...imageParts]);
58 | const response = await result.response;
59 | const text = this.cleanJsonResponse(response.text());
60 | return JSON.parse(text);
61 | }
62 | catch (error) {
63 | console.error("Error extracting problem from images:", error);
64 | throw error;
65 | }
66 | }
67 | async generateSolution(problemInfo) {
68 | const prompt = `Given this coding problem:
69 | ${JSON.stringify(problemInfo, null, 2)}
70 |
71 | Please provide a solution in the following JSON format:
72 | {
73 | "solution": {
74 | "explanation": "Detailed explanation of the approach",
75 | "complexity": {
76 | "time": "Time complexity",
77 | "space": "Space complexity"
78 | },
79 | "code": "The complete solution code",
80 | "test_results": [
81 | {
82 | "input": "test case input",
83 | "expected": "expected output",
84 | "actual": "actual output",
85 | "passed": true/false
86 | }
87 | ]
88 | }
89 | }
90 | Important: Return ONLY the JSON object, without any markdown formatting or code blocks.`;
91 | const result = await this.model.generateContent(prompt);
92 | const response = await result.response;
93 | const text = this.cleanJsonResponse(response.text());
94 | return JSON.parse(text);
95 | }
96 | async debugSolutionWithImages(problemInfo, currentCode, debugImagePaths) {
97 | try {
98 | const imageParts = await Promise.all(debugImagePaths.map(path => this.fileToGenerativePart(path)));
99 | const prompt = `You are a coding problem debugger. Given:
100 | 1. The original problem: ${JSON.stringify(problemInfo, null, 2)}
101 | 2. The current solution: ${currentCode}
102 | 3. The debug information in the provided images
103 |
104 | Please analyze the debug information and provide feedback in this JSON format:
105 | {
106 | "analysis": {
107 | "issues_found": [
108 | {
109 | "description": "Description of the issue",
110 | "location": "Where in the code",
111 | "severity": "high/medium/low"
112 | }
113 | ],
114 | "suggested_fixes": [
115 | {
116 | "description": "Description of the fix",
117 | "code_change": "The specific code change needed"
118 | }
119 | ]
120 | },
121 | "improved_solution": {
122 | "code": "The complete improved solution",
123 | "explanation": "Explanation of the changes made"
124 | }
125 | }
126 | Important: Return ONLY the JSON object, without any markdown formatting or code blocks.`;
127 | const result = await this.model.generateContent([prompt, ...imageParts]);
128 | const response = await result.response;
129 | const text = this.cleanJsonResponse(response.text());
130 | return JSON.parse(text);
131 | }
132 | catch (error) {
133 | console.error("Error debugging solution with images:", error);
134 | throw error;
135 | }
136 | }
137 | }
138 | exports.LLMHelper = LLMHelper;
139 | //# sourceMappingURL=LLMHelper.js.map
--------------------------------------------------------------------------------
/electron/main.ts:
--------------------------------------------------------------------------------
1 | import { app, BrowserWindow } from "electron"
2 | import { initializeIpcHandlers } from "./ipcHandlers"
3 | import { WindowHelper } from "./WindowHelper"
4 | import { ScreenshotHelper } from "./ScreenshotHelper"
5 | import { ShortcutsHelper } from "./shortcuts"
6 | import { ProcessingHelper } from "./ProcessingHelper"
7 | import * as dotenv from "dotenv"
8 |
9 | // Load environment variables from .env file
10 | dotenv.config()
11 |
12 | export class AppState {
13 | private static instance: AppState | null = null
14 |
15 | private windowHelper: WindowHelper
16 | private screenshotHelper: ScreenshotHelper
17 | public shortcutsHelper: ShortcutsHelper
18 | public processingHelper: ProcessingHelper
19 |
20 | // View management
21 | private view: "queue" | "solutions" = "queue"
22 |
23 | private problemInfo: {
24 | problem_statement: string
25 | input_format: Record
26 | output_format: Record
27 | constraints: Array>
28 | test_cases: Array>
29 | } | null = null // Allow null
30 |
31 | private hasDebugged: boolean = false
32 |
33 | // Processing events
34 | public readonly PROCESSING_EVENTS = {
35 | //global states
36 | UNAUTHORIZED: "procesing-unauthorized",
37 | NO_SCREENSHOTS: "processing-no-screenshots",
38 |
39 | //states for generating the initial solution
40 | INITIAL_START: "initial-start",
41 | PROBLEM_EXTRACTED: "problem-extracted",
42 | SOLUTION_SUCCESS: "solution-success",
43 | INITIAL_SOLUTION_ERROR: "solution-error",
44 |
45 | //states for processing the debugging
46 | DEBUG_START: "debug-start",
47 | DEBUG_SUCCESS: "debug-success",
48 | DEBUG_ERROR: "debug-error"
49 | } as const
50 |
51 | constructor() {
52 | // Initialize WindowHelper with this
53 | this.windowHelper = new WindowHelper(this)
54 |
55 | // Initialize ScreenshotHelper
56 | this.screenshotHelper = new ScreenshotHelper(this.view)
57 |
58 | // Initialize ProcessingHelper
59 | this.processingHelper = new ProcessingHelper(this)
60 |
61 | // Initialize ShortcutsHelper
62 | this.shortcutsHelper = new ShortcutsHelper(this)
63 | }
64 |
65 | public static getInstance(): AppState {
66 | if (!AppState.instance) {
67 | AppState.instance = new AppState()
68 | }
69 | return AppState.instance
70 | }
71 |
72 | // Getters and Setters
73 | public getMainWindow(): BrowserWindow | null {
74 | return this.windowHelper.getMainWindow()
75 | }
76 |
77 | public getView(): "queue" | "solutions" {
78 | return this.view
79 | }
80 |
81 | public setView(view: "queue" | "solutions"): void {
82 | this.view = view
83 | this.screenshotHelper.setView(view)
84 | }
85 |
86 | public isVisible(): boolean {
87 | return this.windowHelper.isVisible()
88 | }
89 |
90 | public getScreenshotHelper(): ScreenshotHelper {
91 | return this.screenshotHelper
92 | }
93 |
94 | public getProblemInfo(): any {
95 | return this.problemInfo
96 | }
97 |
98 | public setProblemInfo(problemInfo: any): void {
99 | this.problemInfo = problemInfo
100 | }
101 |
102 | public getScreenshotQueue(): string[] {
103 | return this.screenshotHelper.getScreenshotQueue()
104 | }
105 |
106 | public getExtraScreenshotQueue(): string[] {
107 | return this.screenshotHelper.getExtraScreenshotQueue()
108 | }
109 |
110 | // Window management methods
111 | public createWindow(): void {
112 | this.windowHelper.createWindow()
113 | }
114 |
115 | public hideMainWindow(): void {
116 | this.windowHelper.hideMainWindow()
117 | }
118 |
119 | public showMainWindow(): void {
120 | this.windowHelper.showMainWindow()
121 | }
122 |
123 | public toggleMainWindow(): void {
124 | console.log(
125 | "Screenshots: ",
126 | this.screenshotHelper.getScreenshotQueue().length,
127 | "Extra screenshots: ",
128 | this.screenshotHelper.getExtraScreenshotQueue().length
129 | )
130 | this.windowHelper.toggleMainWindow()
131 | }
132 |
133 | public setWindowDimensions(width: number, height: number): void {
134 | this.windowHelper.setWindowDimensions(width, height)
135 | }
136 |
137 | public clearQueues(): void {
138 | this.screenshotHelper.clearQueues()
139 |
140 | // Clear problem info
141 | this.problemInfo = null
142 |
143 | // Reset view to initial state
144 | this.setView("queue")
145 | }
146 |
147 | // Screenshot management methods
148 | public async takeScreenshot(): Promise {
149 | if (!this.getMainWindow()) throw new Error("No main window available")
150 |
151 | const screenshotPath = await this.screenshotHelper.takeScreenshot(
152 | () => this.hideMainWindow(),
153 | () => this.showMainWindow()
154 | )
155 |
156 | return screenshotPath
157 | }
158 |
159 | public async getImagePreview(filepath: string): Promise {
160 | return this.screenshotHelper.getImagePreview(filepath)
161 | }
162 |
163 | public async deleteScreenshot(
164 | path: string
165 | ): Promise<{ success: boolean; error?: string }> {
166 | return this.screenshotHelper.deleteScreenshot(path)
167 | }
168 |
169 | // New methods to move the window
170 | public moveWindowLeft(): void {
171 | this.windowHelper.moveWindowLeft()
172 | }
173 |
174 | public moveWindowRight(): void {
175 | this.windowHelper.moveWindowRight()
176 | }
177 | public moveWindowDown(): void {
178 | this.windowHelper.moveWindowDown()
179 | }
180 | public moveWindowUp(): void {
181 | this.windowHelper.moveWindowUp()
182 | }
183 |
184 | public setHasDebugged(value: boolean): void {
185 | this.hasDebugged = value
186 | }
187 |
188 | public getHasDebugged(): boolean {
189 | return this.hasDebugged
190 | }
191 | }
192 |
193 | // Application initialization
194 | async function initializeApp() {
195 | const appState = AppState.getInstance()
196 |
197 | // Initialize IPC handlers before window creation
198 | initializeIpcHandlers(appState)
199 |
200 | app.whenReady().then(() => {
201 | console.log("App is ready")
202 | appState.createWindow()
203 | // Register global shortcuts using ShortcutsHelper
204 | appState.shortcutsHelper.registerGlobalShortcuts()
205 | })
206 |
207 | app.on("activate", () => {
208 | console.log("App activated")
209 | if (appState.getMainWindow() === null) {
210 | appState.createWindow()
211 | }
212 | })
213 |
214 | // Quit when all windows are closed, except on macOS
215 | app.on("window-all-closed", () => {
216 | if (process.platform !== "darwin") {
217 | app.quit()
218 | }
219 | })
220 |
221 | app.dock?.hide() // Hide dock icon (optional)
222 | app.commandLine.appendSwitch("disable-background-timer-throttling")
223 | }
224 |
225 | // Start the application
226 | initializeApp().catch(console.error)
227 |
--------------------------------------------------------------------------------
/electron/preload.ts:
--------------------------------------------------------------------------------
1 | import { contextBridge, ipcRenderer } from "electron"
2 |
3 | // Types for the exposed Electron API
4 | interface ElectronAPI {
5 | updateContentDimensions: (dimensions: {
6 | width: number
7 | height: number
8 | }) => Promise
9 | getScreenshots: () => Promise<{
10 | success: boolean
11 | previews?: Array<{ path: string; preview: string }> | null
12 | error?: string
13 | }>
14 | deleteScreenshot: (
15 | path: string
16 | ) => Promise<{ success: boolean; error?: string }>
17 | onScreenshotTaken: (
18 | callback: (data: { path: string; preview: string }) => void
19 | ) => () => void
20 | onSolutionsReady: (callback: (solutions: string) => void) => () => void
21 | onResetView: (callback: () => void) => () => void
22 | onSolutionStart: (callback: () => void) => () => void
23 | onDebugStart: (callback: () => void) => () => void
24 | onDebugSuccess: (callback: (data: any) => void) => () => void
25 | onSolutionError: (callback: (error: string) => void) => () => void
26 | onProcessingNoScreenshots: (callback: () => void) => () => void
27 | onProblemExtracted: (callback: (data: any) => void) => () => void
28 | onSolutionSuccess: (callback: (data: any) => void) => () => void
29 |
30 | onUnauthorized: (callback: () => void) => () => void
31 | onDebugError: (callback: (error: string) => void) => () => void
32 | takeScreenshot: () => Promise
33 | moveWindowLeft: () => Promise
34 | moveWindowRight: () => Promise
35 | quitApp: () => Promise
36 | getApiKey: () => Promise
37 | }
38 |
39 | export const PROCESSING_EVENTS = {
40 | //global states
41 | UNAUTHORIZED: "procesing-unauthorized",
42 | NO_SCREENSHOTS: "processing-no-screenshots",
43 |
44 | //states for generating the initial solution
45 | INITIAL_START: "initial-start",
46 | PROBLEM_EXTRACTED: "problem-extracted",
47 | SOLUTION_SUCCESS: "solution-success",
48 | INITIAL_SOLUTION_ERROR: "solution-error",
49 |
50 | //states for processing the debugging
51 | DEBUG_START: "debug-start",
52 | DEBUG_SUCCESS: "debug-success",
53 | DEBUG_ERROR: "debug-error"
54 | } as const
55 |
56 | // Synchronously expose the GEMINI_API_KEY to the window object
57 | // This must be done before exposeInMainWorld
58 | ;(window as any).GEMINI_API_KEY = process.env.GEMINI_API_KEY
59 |
60 | // Expose the Electron API to the renderer process
61 | contextBridge.exposeInMainWorld("electronAPI", {
62 | updateContentDimensions: (dimensions: { width: number; height: number }) =>
63 | ipcRenderer.invoke("update-content-dimensions", dimensions),
64 | takeScreenshot: () => ipcRenderer.invoke("take-screenshot"),
65 | getScreenshots: () => ipcRenderer.invoke("get-screenshots"),
66 | deleteScreenshot: (path: string) =>
67 | ipcRenderer.invoke("delete-screenshot", path),
68 |
69 | // Event listeners
70 | onScreenshotTaken: (
71 | callback: (data: { path: string; preview: string }) => void
72 | ) => {
73 | const subscription = (_: any, data: { path: string; preview: string }) =>
74 | callback(data)
75 | ipcRenderer.on("screenshot-taken", subscription)
76 | return () => {
77 | ipcRenderer.removeListener("screenshot-taken", subscription)
78 | }
79 | },
80 | onSolutionsReady: (callback: (solutions: string) => void) => {
81 | const subscription = (_: any, solutions: string) => callback(solutions)
82 | ipcRenderer.on("solutions-ready", subscription)
83 | return () => {
84 | ipcRenderer.removeListener("solutions-ready", subscription)
85 | }
86 | },
87 | onResetView: (callback: () => void) => {
88 | const subscription = () => callback()
89 | ipcRenderer.on("reset-view", subscription)
90 | return () => {
91 | ipcRenderer.removeListener("reset-view", subscription)
92 | }
93 | },
94 | onSolutionStart: (callback: () => void) => {
95 | const subscription = () => callback()
96 | ipcRenderer.on(PROCESSING_EVENTS.INITIAL_START, subscription)
97 | return () => {
98 | ipcRenderer.removeListener(PROCESSING_EVENTS.INITIAL_START, subscription)
99 | }
100 | },
101 | onDebugStart: (callback: () => void) => {
102 | const subscription = () => callback()
103 | ipcRenderer.on(PROCESSING_EVENTS.DEBUG_START, subscription)
104 | return () => {
105 | ipcRenderer.removeListener(PROCESSING_EVENTS.DEBUG_START, subscription)
106 | }
107 | },
108 |
109 | onDebugSuccess: (callback: (data: any) => void) => {
110 | ipcRenderer.on("debug-success", (_event, data) => callback(data))
111 | return () => {
112 | ipcRenderer.removeListener("debug-success", (_event, data) =>
113 | callback(data)
114 | )
115 | }
116 | },
117 | onDebugError: (callback: (error: string) => void) => {
118 | const subscription = (_: any, error: string) => callback(error)
119 | ipcRenderer.on(PROCESSING_EVENTS.DEBUG_ERROR, subscription)
120 | return () => {
121 | ipcRenderer.removeListener(PROCESSING_EVENTS.DEBUG_ERROR, subscription)
122 | }
123 | },
124 | onSolutionError: (callback: (error: string) => void) => {
125 | const subscription = (_: any, error: string) => callback(error)
126 | ipcRenderer.on(PROCESSING_EVENTS.INITIAL_SOLUTION_ERROR, subscription)
127 | return () => {
128 | ipcRenderer.removeListener(
129 | PROCESSING_EVENTS.INITIAL_SOLUTION_ERROR,
130 | subscription
131 | )
132 | }
133 | },
134 | onProcessingNoScreenshots: (callback: () => void) => {
135 | const subscription = () => callback()
136 | ipcRenderer.on(PROCESSING_EVENTS.NO_SCREENSHOTS, subscription)
137 | return () => {
138 | ipcRenderer.removeListener(PROCESSING_EVENTS.NO_SCREENSHOTS, subscription)
139 | }
140 | },
141 |
142 | onProblemExtracted: (callback: (data: any) => void) => {
143 | const subscription = (_: any, data: any) => callback(data)
144 | ipcRenderer.on(PROCESSING_EVENTS.PROBLEM_EXTRACTED, subscription)
145 | return () => {
146 | ipcRenderer.removeListener(
147 | PROCESSING_EVENTS.PROBLEM_EXTRACTED,
148 | subscription
149 | )
150 | }
151 | },
152 | onSolutionSuccess: (callback: (data: any) => void) => {
153 | const subscription = (_: any, data: any) => callback(data)
154 | ipcRenderer.on(PROCESSING_EVENTS.SOLUTION_SUCCESS, subscription)
155 | return () => {
156 | ipcRenderer.removeListener(
157 | PROCESSING_EVENTS.SOLUTION_SUCCESS,
158 | subscription
159 | )
160 | }
161 | },
162 | onUnauthorized: (callback: () => void) => {
163 | const subscription = () => callback()
164 | ipcRenderer.on(PROCESSING_EVENTS.UNAUTHORIZED, subscription)
165 | return () => {
166 | ipcRenderer.removeListener(PROCESSING_EVENTS.UNAUTHORIZED, subscription)
167 | }
168 | },
169 | moveWindowLeft: () => ipcRenderer.invoke("move-window-left"),
170 | moveWindowRight: () => ipcRenderer.invoke("move-window-right"),
171 | quitApp: () => ipcRenderer.invoke("quit-app"),
172 | getApiKey: () => ipcRenderer.invoke("get-gemini-api-key")
173 | } as ElectronAPI)
174 |
--------------------------------------------------------------------------------
/src/components/Chat/ChatModal.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useRef, useEffect } from "react"
2 | import { Send } from "lucide-react"
3 |
4 | interface Message {
5 | id: string
6 | text: string
7 | sender: "user" | "assistant"
8 | timestamp: Date
9 | }
10 |
11 | interface ChatModalProps {
12 | isOpen: boolean
13 | onClose: () => void
14 | }
15 |
16 | const ChatModal: React.FC = ({ isOpen, onClose: _onClose }) => {
17 | const [messages, setMessages] = useState([
18 | {
19 | id: "1",
20 | text: "Hi! I'm your coding assistant. Ask me anything about algorithms, data structures, or coding problems!",
21 | sender: "assistant",
22 | timestamp: new Date()
23 | }
24 | ])
25 | const [inputValue, setInputValue] = useState("")
26 | const [isLoading, setIsLoading] = useState(false)
27 | const messagesEndRef = useRef(null)
28 |
29 | const scrollToBottom = () => {
30 | messagesEndRef.current?.scrollIntoView({ behavior: "smooth" })
31 | }
32 |
33 | useEffect(() => {
34 | scrollToBottom()
35 | }, [messages])
36 |
37 | if (!isOpen) return null
38 |
39 | const handleSendMessage = async () => {
40 | if (!inputValue.trim()) return
41 |
42 | const userText = inputValue
43 |
44 | // Add user message
45 | const userMessage: Message = {
46 | id: Date.now().toString(),
47 | text: userText,
48 | sender: "user",
49 | timestamp: new Date()
50 | }
51 |
52 | setMessages((prev) => [...prev, userMessage])
53 | setInputValue("")
54 | setIsLoading(true)
55 |
56 | try {
57 | // Get API key from window (set by preload script synchronously)
58 | let apiKey = (window as any).GEMINI_API_KEY
59 |
60 | // Fallback: If still not available, request it asynchronously
61 | if (!apiKey && (window as any).electronAPI?.getApiKey) {
62 | apiKey = await (window as any).electronAPI.getApiKey()
63 | }
64 |
65 | if (!apiKey) {
66 | throw new Error("API key not available. Make sure GEMINI_API_KEY is set.")
67 | }
68 |
69 | // Call Gemini API (non-streaming for simplicity)
70 | const response = await fetch(
71 | `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash-exp:generateContent?key=${apiKey}`,
72 | {
73 | method: "POST",
74 | headers: {
75 | "Content-Type": "application/json"
76 | },
77 | body: JSON.stringify({
78 | contents: [
79 | {
80 | parts: [
81 | {
82 | text: `You are a helpful coding assistant. Answer concisely with code examples when relevant.\n\nQuestion: ${userText}`
83 | }
84 | ]
85 | }
86 | ],
87 | generationConfig: {
88 | temperature: 0.7,
89 | topK: 40,
90 | topP: 0.95,
91 | maxOutputTokens: 2048
92 | }
93 | })
94 | }
95 | )
96 |
97 | if (!response.ok) {
98 | throw new Error(`API error: ${response.status}`)
99 | }
100 |
101 | const data = await response.json()
102 | const text = data.candidates?.[0]?.content?.parts?.[0]?.text || "No response received"
103 |
104 | // Create assistant message with response
105 | const assistantMessage: Message = {
106 | id: (Date.now() + 1).toString(),
107 | text: text,
108 | sender: "assistant",
109 | timestamp: new Date()
110 | }
111 | setMessages((prev) => [...prev, assistantMessage])
112 | } catch (error) {
113 | console.error("Chat error:", error)
114 | const errorMessage: Message = {
115 | id: (Date.now() + 1).toString(),
116 | text: `Error: ${error instanceof Error ? error.message : "Failed to get response"}`,
117 | sender: "assistant",
118 | timestamp: new Date()
119 | }
120 | setMessages((prev) => [...prev, errorMessage])
121 | } finally {
122 | setIsLoading(false)
123 | }
124 | }
125 |
126 | const handleKeyDown = (e: React.KeyboardEvent) => {
127 | if (e.key === "Enter" && !e.shiftKey) {
128 | e.preventDefault()
129 | handleSendMessage()
130 | }
131 | }
132 |
133 | return (
134 |
135 |
136 |
137 |
138 | {/* Messages Container */}
139 |
140 | {messages.map((message) => (
141 |
145 |
152 | {message.text || (message.sender === "assistant" && isLoading ? (
153 |
154 |
155 |
156 |
157 |
158 | ) : message.text)}
159 |
160 |
161 | ))}
162 |
163 |
164 |
165 | {/* Input Container */}
166 |
167 | setInputValue(e.target.value)}
171 | onKeyDown={handleKeyDown}
172 | placeholder="Ask me anything..."
173 | className="flex-1 px-3 py-2 bg-white/10 border border-white/20 rounded-lg text-sm text-white/90 placeholder-white/50 focus:outline-none focus:border-white/40 focus:bg-white/15 transition-colors"
174 | disabled={isLoading}
175 | autoFocus
176 | />
177 |
182 |
183 |
184 |
185 |
186 |
187 |
188 | )
189 | }
190 |
191 | export default ChatModal
192 |
--------------------------------------------------------------------------------
/dist-electron/main.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3 | if (k2 === undefined) k2 = k;
4 | var desc = Object.getOwnPropertyDescriptor(m, k);
5 | if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6 | desc = { enumerable: true, get: function() { return m[k]; } };
7 | }
8 | Object.defineProperty(o, k2, desc);
9 | }) : (function(o, m, k, k2) {
10 | if (k2 === undefined) k2 = k;
11 | o[k2] = m[k];
12 | }));
13 | var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14 | Object.defineProperty(o, "default", { enumerable: true, value: v });
15 | }) : function(o, v) {
16 | o["default"] = v;
17 | });
18 | var __importStar = (this && this.__importStar) || function (mod) {
19 | if (mod && mod.__esModule) return mod;
20 | var result = {};
21 | if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22 | __setModuleDefault(result, mod);
23 | return result;
24 | };
25 | Object.defineProperty(exports, "__esModule", { value: true });
26 | exports.AppState = void 0;
27 | const electron_1 = require("electron");
28 | const ipcHandlers_1 = require("./ipcHandlers");
29 | const WindowHelper_1 = require("./WindowHelper");
30 | const ScreenshotHelper_1 = require("./ScreenshotHelper");
31 | const shortcuts_1 = require("./shortcuts");
32 | const ProcessingHelper_1 = require("./ProcessingHelper");
33 | const dotenv = __importStar(require("dotenv"));
34 | // Load environment variables from .env file
35 | dotenv.config();
36 | class AppState {
37 | static instance = null;
38 | windowHelper;
39 | screenshotHelper;
40 | shortcutsHelper;
41 | processingHelper;
42 | // View management
43 | view = "queue";
44 | problemInfo = null; // Allow null
45 | hasDebugged = false;
46 | // Processing events
47 | PROCESSING_EVENTS = {
48 | //global states
49 | UNAUTHORIZED: "procesing-unauthorized",
50 | NO_SCREENSHOTS: "processing-no-screenshots",
51 | //states for generating the initial solution
52 | INITIAL_START: "initial-start",
53 | PROBLEM_EXTRACTED: "problem-extracted",
54 | SOLUTION_SUCCESS: "solution-success",
55 | INITIAL_SOLUTION_ERROR: "solution-error",
56 | //states for processing the debugging
57 | DEBUG_START: "debug-start",
58 | DEBUG_SUCCESS: "debug-success",
59 | DEBUG_ERROR: "debug-error"
60 | };
61 | constructor() {
62 | // Initialize WindowHelper with this
63 | this.windowHelper = new WindowHelper_1.WindowHelper(this);
64 | // Initialize ScreenshotHelper
65 | this.screenshotHelper = new ScreenshotHelper_1.ScreenshotHelper(this.view);
66 | // Initialize ProcessingHelper
67 | this.processingHelper = new ProcessingHelper_1.ProcessingHelper(this);
68 | // Initialize ShortcutsHelper
69 | this.shortcutsHelper = new shortcuts_1.ShortcutsHelper(this);
70 | }
71 | static getInstance() {
72 | if (!AppState.instance) {
73 | AppState.instance = new AppState();
74 | }
75 | return AppState.instance;
76 | }
77 | // Getters and Setters
78 | getMainWindow() {
79 | return this.windowHelper.getMainWindow();
80 | }
81 | getView() {
82 | return this.view;
83 | }
84 | setView(view) {
85 | this.view = view;
86 | this.screenshotHelper.setView(view);
87 | }
88 | isVisible() {
89 | return this.windowHelper.isVisible();
90 | }
91 | getScreenshotHelper() {
92 | return this.screenshotHelper;
93 | }
94 | getProblemInfo() {
95 | return this.problemInfo;
96 | }
97 | setProblemInfo(problemInfo) {
98 | this.problemInfo = problemInfo;
99 | }
100 | getScreenshotQueue() {
101 | return this.screenshotHelper.getScreenshotQueue();
102 | }
103 | getExtraScreenshotQueue() {
104 | return this.screenshotHelper.getExtraScreenshotQueue();
105 | }
106 | // Window management methods
107 | createWindow() {
108 | this.windowHelper.createWindow();
109 | }
110 | hideMainWindow() {
111 | this.windowHelper.hideMainWindow();
112 | }
113 | showMainWindow() {
114 | this.windowHelper.showMainWindow();
115 | }
116 | toggleMainWindow() {
117 | console.log("Screenshots: ", this.screenshotHelper.getScreenshotQueue().length, "Extra screenshots: ", this.screenshotHelper.getExtraScreenshotQueue().length);
118 | this.windowHelper.toggleMainWindow();
119 | }
120 | setWindowDimensions(width, height) {
121 | this.windowHelper.setWindowDimensions(width, height);
122 | }
123 | clearQueues() {
124 | this.screenshotHelper.clearQueues();
125 | // Clear problem info
126 | this.problemInfo = null;
127 | // Reset view to initial state
128 | this.setView("queue");
129 | }
130 | // Screenshot management methods
131 | async takeScreenshot() {
132 | if (!this.getMainWindow())
133 | throw new Error("No main window available");
134 | const screenshotPath = await this.screenshotHelper.takeScreenshot(() => this.hideMainWindow(), () => this.showMainWindow());
135 | return screenshotPath;
136 | }
137 | async getImagePreview(filepath) {
138 | return this.screenshotHelper.getImagePreview(filepath);
139 | }
140 | async deleteScreenshot(path) {
141 | return this.screenshotHelper.deleteScreenshot(path);
142 | }
143 | // New methods to move the window
144 | moveWindowLeft() {
145 | this.windowHelper.moveWindowLeft();
146 | }
147 | moveWindowRight() {
148 | this.windowHelper.moveWindowRight();
149 | }
150 | moveWindowDown() {
151 | this.windowHelper.moveWindowDown();
152 | }
153 | moveWindowUp() {
154 | this.windowHelper.moveWindowUp();
155 | }
156 | setHasDebugged(value) {
157 | this.hasDebugged = value;
158 | }
159 | getHasDebugged() {
160 | return this.hasDebugged;
161 | }
162 | }
163 | exports.AppState = AppState;
164 | // Application initialization
165 | async function initializeApp() {
166 | const appState = AppState.getInstance();
167 | // Initialize IPC handlers before window creation
168 | (0, ipcHandlers_1.initializeIpcHandlers)(appState);
169 | electron_1.app.whenReady().then(() => {
170 | console.log("App is ready");
171 | appState.createWindow();
172 | // Register global shortcuts using ShortcutsHelper
173 | appState.shortcutsHelper.registerGlobalShortcuts();
174 | });
175 | electron_1.app.on("activate", () => {
176 | console.log("App activated");
177 | if (appState.getMainWindow() === null) {
178 | appState.createWindow();
179 | }
180 | });
181 | // Quit when all windows are closed, except on macOS
182 | electron_1.app.on("window-all-closed", () => {
183 | if (process.platform !== "darwin") {
184 | electron_1.app.quit();
185 | }
186 | });
187 | electron_1.app.dock?.hide(); // Hide dock icon (optional)
188 | electron_1.app.commandLine.appendSwitch("disable-background-timer-throttling");
189 | }
190 | // Start the application
191 | initializeApp().catch(console.error);
192 | //# sourceMappingURL=main.js.map
--------------------------------------------------------------------------------
/src/_pages/Queue.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect, useRef } from "react"
2 | import { useQuery } from "react-query"
3 | import ScreenshotQueue from "../components/Queue/ScreenshotQueue"
4 | import {
5 | Toast,
6 | ToastTitle,
7 | ToastDescription,
8 | ToastVariant,
9 | ToastMessage
10 | } from "../components/ui/toast"
11 | import QueueCommands from "../components/Queue/QueueCommands"
12 | import ChatModal from "../components/Chat/ChatModal"
13 | import AudioTranscriber from "../components/Audio/AudioTranscriber"
14 |
15 | interface QueueProps {
16 | setView: React.Dispatch>
17 | setIsChatOpen: React.Dispatch>
18 | isChatOpen: boolean
19 | }
20 |
21 | const Queue: React.FC = ({ setView, setIsChatOpen, isChatOpen }) => {
22 | const [toastOpen, setToastOpen] = useState(false)
23 | const [toastMessage, setToastMessage] = useState({
24 | title: "",
25 | description: "",
26 | variant: "neutral"
27 | })
28 |
29 | const [isTooltipVisible, setIsTooltipVisible] = useState(false)
30 | const [tooltipHeight, setTooltipHeight] = useState(0)
31 | const [position, setPosition] = useState({ x: 0, y: 0 })
32 | const [isDragging, setIsDragging] = useState(false)
33 | const [dragOffset, setDragOffset] = useState({ x: 0, y: 0 })
34 | const contentRef = useRef(null)
35 | const dragHandleRef = useRef(null)
36 |
37 | const { data: screenshots = [], refetch } = useQuery({
38 | queryKey: ["screenshots"],
39 | queryFn: async () => {
40 | try {
41 | const existing = await window.electronAPI.getScreenshots()
42 | return existing
43 | } catch (error) {
44 | console.error("Error loading screenshots:", error)
45 | showToast("Error", "Failed to load existing screenshots", "error")
46 | return []
47 | }
48 | },
49 | staleTime: Infinity,
50 | cacheTime: Infinity,
51 | refetchOnWindowFocus: true,
52 | refetchOnMount: true
53 | })
54 |
55 | const showToast = (
56 | title: string,
57 | description: string,
58 | variant: ToastVariant
59 | ) => {
60 | setToastMessage({ title, description, variant })
61 | setToastOpen(true)
62 | }
63 |
64 | const handleDeleteScreenshot = async (index: number) => {
65 | const screenshotToDelete = screenshots[index]
66 |
67 | try {
68 | const response = await window.electronAPI.deleteScreenshot(
69 | screenshotToDelete.path
70 | )
71 |
72 | if (response.success) {
73 | refetch()
74 | } else {
75 | console.error("Failed to delete screenshot:", response.error)
76 | showToast("Error", "Failed to delete the screenshot file", "error")
77 | }
78 | } catch (error) {
79 | console.error("Error deleting screenshot:", error)
80 | }
81 | }
82 |
83 | useEffect(() => {
84 | const updateDimensions = () => {
85 | if (contentRef.current) {
86 | let contentHeight = contentRef.current.scrollHeight
87 | const contentWidth = contentRef.current.scrollWidth
88 | if (isTooltipVisible) {
89 | contentHeight += tooltipHeight
90 | }
91 | window.electronAPI.updateContentDimensions({
92 | width: contentWidth,
93 | height: contentHeight
94 | })
95 | }
96 | }
97 |
98 | const resizeObserver = new ResizeObserver(updateDimensions)
99 | if (contentRef.current) {
100 | resizeObserver.observe(contentRef.current)
101 | }
102 | updateDimensions()
103 |
104 | const cleanupFunctions = [
105 | window.electronAPI.onScreenshotTaken(() => refetch()),
106 | window.electronAPI.onResetView(() => refetch()),
107 | window.electronAPI.onSolutionError((error: string) => {
108 | showToast(
109 | "Processing Failed",
110 | "There was an error processing your screenshots.",
111 | "error"
112 | )
113 | setView("queue")
114 | console.error("Processing error:", error)
115 | }),
116 | window.electronAPI.onProcessingNoScreenshots(() => {
117 | showToast(
118 | "No Screenshots",
119 | "There are no screenshots to process.",
120 | "neutral"
121 | )
122 | })
123 | ]
124 |
125 | return () => {
126 | resizeObserver.disconnect()
127 | cleanupFunctions.forEach((cleanup) => cleanup())
128 | }
129 | }, [isTooltipVisible, tooltipHeight])
130 |
131 | const handleTooltipVisibilityChange = (visible: boolean, height: number) => {
132 | setIsTooltipVisible(visible)
133 | setTooltipHeight(height)
134 | }
135 |
136 | const handleMouseDown = (e: React.MouseEvent) => {
137 | if (!contentRef.current) return
138 | setIsDragging(true)
139 | const rect = contentRef.current.getBoundingClientRect()
140 | setDragOffset({
141 | x: e.clientX - rect.left,
142 | y: e.clientY - rect.top
143 | })
144 | }
145 |
146 | const handleMouseMove = (e: MouseEvent) => {
147 | if (!isDragging) return
148 | setPosition({
149 | x: e.clientX - dragOffset.x,
150 | y: e.clientY - dragOffset.y
151 | })
152 | }
153 |
154 | const handleMouseUp = () => {
155 | setIsDragging(false)
156 | }
157 |
158 | useEffect(() => {
159 | if (isDragging) {
160 | window.addEventListener('mousemove', handleMouseMove)
161 | window.addEventListener('mouseup', handleMouseUp)
162 | return () => {
163 | window.removeEventListener('mousemove', handleMouseMove)
164 | window.removeEventListener('mouseup', handleMouseUp)
165 | }
166 | }
167 | }, [isDragging, dragOffset])
168 |
169 | return (
170 |
181 |
182 | {/* Drag Handle */}
183 |
190 |
196 | {toastMessage.title}
197 | {toastMessage.description}
198 |
199 |
200 |
201 |
206 |
212 |
213 | {/* Audio Transcriber */}
214 |
{}} />
215 |
216 |
217 |
218 | {isChatOpen &&
setIsChatOpen(false)} />}
219 |
220 | {/* Close chat when clicking outside - escape key */}
221 | {isChatOpen && (
222 | setIsChatOpen(false)}
225 | onKeyDown={(e) => {
226 | if (e.key === "Escape") setIsChatOpen(false)
227 | }}
228 | />
229 | )}
230 |
231 | )
232 | }
233 |
234 | export default Queue
235 |
--------------------------------------------------------------------------------
/dist-electron/WindowHelper.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"WindowHelper.js","sourceRoot":"","sources":["../electron/WindowHelper.ts"],"names":[],"mappings":"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"}
--------------------------------------------------------------------------------
/src/components/Queue/QueueCommands.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect, useRef } from "react"
2 | import { IoLogOutOutline } from "react-icons/io5"
3 | import { MessageCircle } from "lucide-react"
4 |
5 | interface QueueCommandsProps {
6 | onTooltipVisibilityChange: (visible: boolean, height: number) => void
7 | screenshots: Array<{ path: string; preview: string }>
8 | setIsChatOpen: React.Dispatch>
9 | isChatOpen: boolean
10 | }
11 |
12 | const QueueCommands: React.FC = ({
13 | onTooltipVisibilityChange,
14 | screenshots,
15 | setIsChatOpen,
16 | isChatOpen
17 | }) => {
18 | const [isTooltipVisible, setIsTooltipVisible] = useState(false)
19 | const tooltipRef = useRef(null)
20 |
21 | useEffect(() => {
22 | let tooltipHeight = 0
23 | if (tooltipRef.current && isTooltipVisible) {
24 | tooltipHeight = tooltipRef.current.offsetHeight + 10
25 | }
26 | onTooltipVisibilityChange(isTooltipVisible, tooltipHeight)
27 | }, [isTooltipVisible])
28 |
29 | const handleMouseEnter = () => {
30 | setIsTooltipVisible(true)
31 | }
32 |
33 | const handleMouseLeave = () => {
34 | setIsTooltipVisible(false)
35 | }
36 |
37 | return (
38 |
39 |
40 | {/* Show/Hide */}
41 |
42 |
Show/Hide
43 |
44 |
45 | ⌘
46 |
47 |
48 | B
49 |
50 |
51 |
52 |
53 | {/* Screenshot */}
54 |
55 |
56 | {screenshots.length === 0 ? "Take first screenshot" : "Screenshot"}
57 |
58 |
59 |
60 | ⌘
61 |
62 |
63 | H
64 |
65 |
66 |
67 |
68 | {/* Solve Command */}
69 | {screenshots.length > 0 && (
70 |
71 |
Solve
72 |
73 |
74 | ⌘
75 |
76 |
77 | ↵
78 |
79 |
80 |
81 | )}
82 |
83 | {/* Question mark with tooltip */}
84 |
89 |
90 | ?
91 |
92 |
93 | {/* Tooltip Content */}
94 | {isTooltipVisible && (
95 |
99 |
100 |
101 |
Keyboard Shortcuts
102 |
103 | {/* Toggle Command */}
104 |
105 |
106 |
Toggle Window
107 |
108 |
109 | ⌘
110 |
111 |
112 | B
113 |
114 |
115 |
116 |
117 | Show or hide this window.
118 |
119 |
120 | {/* Screenshot Command */}
121 |
122 |
123 |
Take Screenshot
124 |
125 |
126 | ⌘
127 |
128 |
129 | H
130 |
131 |
132 |
133 |
134 | Take a screenshot of the problem description. The tool
135 | will extract and analyze the problem. The 5 latest
136 | screenshots are saved.
137 |
138 |
139 |
140 | {/* Solve Command */}
141 |
142 |
143 |
Solve Problem
144 |
145 |
146 | ⌘
147 |
148 |
149 | ↵
150 |
151 |
152 |
153 |
154 | Generate a solution based on the current problem.
155 |
156 |
157 |
158 |
159 |
160 |
161 | )}
162 |
163 |
164 | {/* Separator */}
165 |
166 |
167 | {/* Chat Button */}
168 |
setIsChatOpen(!isChatOpen)}
176 | >
177 |
178 | Chat
179 |
180 |
181 | {/* Sign Out Button - Moved to end */}
182 |
window.electronAPI.quitApp()}
186 | >
187 |
188 |
189 |
190 |
191 | )
192 | }
193 |
194 | export default QueueCommands
195 |
--------------------------------------------------------------------------------
/electron/WindowHelper.ts:
--------------------------------------------------------------------------------
1 | // electron/WindowHelper.ts
2 |
3 | import { BrowserWindow, screen } from "electron"
4 | import { AppState } from "main"
5 | import path from "node:path"
6 |
7 | const isDev = process.env.NODE_ENV === "development"
8 |
9 | const startUrl = isDev
10 | ? "http://localhost:5180"
11 | : `file://${path.join(__dirname, "../dist/index.html")}`
12 |
13 | export class WindowHelper {
14 | private mainWindow: BrowserWindow | null = null
15 | private isWindowVisible: boolean = false
16 | private windowPosition: { x: number; y: number } | null = null
17 | private windowSize: { width: number; height: number } | null = null
18 | private appState: AppState
19 |
20 | // Initialize with explicit number type and 0 value
21 | private screenWidth: number = 0
22 | private screenHeight: number = 0
23 | private step: number = 0
24 | private currentX: number = 0
25 | private currentY: number = 0
26 |
27 | constructor(appState: AppState) {
28 | this.appState = appState
29 | }
30 |
31 | public setWindowDimensions(width: number, height: number): void {
32 | if (!this.mainWindow || this.mainWindow.isDestroyed()) return
33 |
34 | // Get current window position
35 | const [currentX, currentY] = this.mainWindow.getPosition()
36 |
37 | // Get screen dimensions
38 | const primaryDisplay = screen.getPrimaryDisplay()
39 | const workArea = primaryDisplay.workAreaSize
40 |
41 | // Use 75% width if debugging has occurred, otherwise use 60%
42 | const maxAllowedWidth = Math.floor(
43 | workArea.width * (this.appState.getHasDebugged() ? 0.75 : 0.5)
44 | )
45 |
46 | // Ensure width doesn't exceed max allowed width and height is reasonable
47 | const newWidth = Math.min(width + 32, maxAllowedWidth)
48 | const newHeight = Math.max(Math.ceil(height), 1200) // Ensure minimum height of 1200px
49 |
50 | // Center the window horizontally if it would go off screen
51 | const maxX = workArea.width - newWidth
52 | const newX = Math.min(Math.max(currentX, 0), maxX)
53 |
54 | // Update window bounds
55 | this.mainWindow.setBounds({
56 | x: newX,
57 | y: currentY,
58 | width: newWidth,
59 | height: newHeight
60 | })
61 |
62 | // Update internal state
63 | this.windowPosition = { x: newX, y: currentY }
64 | this.windowSize = { width: newWidth, height: newHeight }
65 | this.currentX = newX
66 | }
67 |
68 | public createWindow(): void {
69 | if (this.mainWindow !== null) return
70 |
71 | const primaryDisplay = screen.getPrimaryDisplay()
72 | const workArea = primaryDisplay.workAreaSize
73 | this.screenWidth = workArea.width
74 | this.screenHeight = workArea.height
75 |
76 | this.step = Math.floor(this.screenWidth / 10) // 10 steps
77 | this.currentX = 0 // Start at the left
78 |
79 | const windowSettings: Electron.BrowserWindowConstructorOptions = {
80 | height: 1200,
81 | minWidth: undefined,
82 | maxWidth: undefined,
83 | x: this.currentX,
84 | y: 0,
85 | webPreferences: {
86 | nodeIntegration: true,
87 | contextIsolation: true,
88 | preload: path.join(__dirname, "preload.js")
89 | },
90 | show: true,
91 | alwaysOnTop: true,
92 | frame: false,
93 | transparent: true,
94 | fullscreenable: false,
95 | hasShadow: false,
96 | backgroundColor: "#00000000",
97 | focusable: true
98 | }
99 |
100 | this.mainWindow = new BrowserWindow(windowSettings)
101 | // this.mainWindow.webContents.openDevTools()
102 | this.mainWindow.setContentProtection(true)
103 |
104 | if (process.platform === "darwin") {
105 | this.mainWindow.setVisibleOnAllWorkspaces(true, {
106 | visibleOnFullScreen: true
107 | })
108 | this.mainWindow.setHiddenInMissionControl(true)
109 | this.mainWindow.setAlwaysOnTop(true, "floating")
110 | }
111 | if (process.platform === "linux") {
112 | // Linux-specific optimizations for stealth overlays
113 | if (this.mainWindow.setHasShadow) {
114 | this.mainWindow.setHasShadow(false)
115 | }
116 | this.mainWindow.setFocusable(false)
117 | }
118 | this.mainWindow.setSkipTaskbar(true)
119 | this.mainWindow.setAlwaysOnTop(true)
120 |
121 | this.mainWindow.loadURL(startUrl).catch((err) => {
122 | console.error("Failed to load URL:", err)
123 | })
124 |
125 | const bounds = this.mainWindow.getBounds()
126 | this.windowPosition = { x: bounds.x, y: bounds.y }
127 | this.windowSize = { width: bounds.width, height: bounds.height }
128 | this.currentX = bounds.x
129 | this.currentY = bounds.y
130 |
131 | this.setupWindowListeners()
132 | this.isWindowVisible = true
133 | }
134 |
135 | private setupWindowListeners(): void {
136 | if (!this.mainWindow) return
137 |
138 | this.mainWindow.on("move", () => {
139 | if (this.mainWindow) {
140 | const bounds = this.mainWindow.getBounds()
141 | this.windowPosition = { x: bounds.x, y: bounds.y }
142 | this.currentX = bounds.x
143 | this.currentY = bounds.y
144 | }
145 | })
146 |
147 | this.mainWindow.on("resize", () => {
148 | if (this.mainWindow) {
149 | const bounds = this.mainWindow.getBounds()
150 | this.windowSize = { width: bounds.width, height: bounds.height }
151 | }
152 | })
153 |
154 | this.mainWindow.on("closed", () => {
155 | this.mainWindow = null
156 | this.isWindowVisible = false
157 | this.windowPosition = null
158 | this.windowSize = null
159 | })
160 | }
161 |
162 | public getMainWindow(): BrowserWindow | null {
163 | return this.mainWindow
164 | }
165 |
166 | public isVisible(): boolean {
167 | return this.isWindowVisible
168 | }
169 |
170 | public hideMainWindow(): void {
171 | if (!this.mainWindow || this.mainWindow.isDestroyed()) {
172 | console.warn("Main window does not exist or is destroyed.")
173 | return
174 | }
175 |
176 | const bounds = this.mainWindow.getBounds()
177 | this.windowPosition = { x: bounds.x, y: bounds.y }
178 | this.windowSize = { width: bounds.width, height: bounds.height }
179 | this.mainWindow.hide()
180 | this.isWindowVisible = false
181 | }
182 |
183 | public showMainWindow(): void {
184 | if (!this.mainWindow || this.mainWindow.isDestroyed()) {
185 | console.warn("Main window does not exist or is destroyed.")
186 | return
187 | }
188 |
189 | if (this.windowPosition && this.windowSize) {
190 | this.mainWindow.setBounds({
191 | x: this.windowPosition.x,
192 | y: this.windowPosition.y,
193 | width: this.windowSize.width,
194 | height: this.windowSize.height
195 | })
196 | }
197 |
198 | this.mainWindow.showInactive()
199 |
200 | this.isWindowVisible = true
201 | }
202 |
203 | public toggleMainWindow(): void {
204 | if (this.isWindowVisible) {
205 | this.hideMainWindow()
206 | } else {
207 | this.showMainWindow()
208 | }
209 | }
210 |
211 | // New methods for window movement
212 | public moveWindowRight(): void {
213 | if (!this.mainWindow) return
214 |
215 | const windowWidth = this.windowSize?.width || 0
216 | const halfWidth = windowWidth / 2
217 |
218 | // Ensure currentX and currentY are numbers
219 | this.currentX = Number(this.currentX) || 0
220 | this.currentY = Number(this.currentY) || 0
221 |
222 | this.currentX = Math.min(
223 | this.screenWidth - halfWidth,
224 | this.currentX + this.step
225 | )
226 | this.mainWindow.setPosition(
227 | Math.round(this.currentX),
228 | Math.round(this.currentY)
229 | )
230 | }
231 |
232 | public moveWindowLeft(): void {
233 | if (!this.mainWindow) return
234 |
235 | const windowWidth = this.windowSize?.width || 0
236 | const halfWidth = windowWidth / 2
237 |
238 | // Ensure currentX and currentY are numbers
239 | this.currentX = Number(this.currentX) || 0
240 | this.currentY = Number(this.currentY) || 0
241 |
242 | this.currentX = Math.max(-halfWidth, this.currentX - this.step)
243 | this.mainWindow.setPosition(
244 | Math.round(this.currentX),
245 | Math.round(this.currentY)
246 | )
247 | }
248 |
249 | public moveWindowDown(): void {
250 | if (!this.mainWindow) return
251 |
252 | const windowHeight = this.windowSize?.height || 0
253 | const halfHeight = windowHeight / 2
254 |
255 | // Ensure currentX and currentY are numbers
256 | this.currentX = Number(this.currentX) || 0
257 | this.currentY = Number(this.currentY) || 0
258 |
259 | this.currentY = Math.min(
260 | this.screenHeight - halfHeight,
261 | this.currentY + this.step
262 | )
263 | this.mainWindow.setPosition(
264 | Math.round(this.currentX),
265 | Math.round(this.currentY)
266 | )
267 | }
268 |
269 | public moveWindowUp(): void {
270 | if (!this.mainWindow) return
271 |
272 | const windowHeight = this.windowSize?.height || 0
273 | const halfHeight = windowHeight / 2
274 |
275 | // Ensure currentX and currentY are numbers
276 | this.currentX = Number(this.currentX) || 0
277 | this.currentY = Number(this.currentY) || 0
278 |
279 | this.currentY = Math.max(-halfHeight, this.currentY - this.step)
280 | this.mainWindow.setPosition(
281 | Math.round(this.currentX),
282 | Math.round(this.currentY)
283 | )
284 | }
285 | }
286 |
--------------------------------------------------------------------------------
/dist-electron/WindowHelper.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | // electron/WindowHelper.ts
3 | var __importDefault = (this && this.__importDefault) || function (mod) {
4 | return (mod && mod.__esModule) ? mod : { "default": mod };
5 | };
6 | Object.defineProperty(exports, "__esModule", { value: true });
7 | exports.WindowHelper = void 0;
8 | const electron_1 = require("electron");
9 | const node_path_1 = __importDefault(require("node:path"));
10 | const isDev = process.env.NODE_ENV === "development";
11 | const startUrl = isDev
12 | ? "http://localhost:5180"
13 | : `file://${node_path_1.default.join(__dirname, "../dist/index.html")}`;
14 | class WindowHelper {
15 | mainWindow = null;
16 | isWindowVisible = false;
17 | windowPosition = null;
18 | windowSize = null;
19 | appState;
20 | // Initialize with explicit number type and 0 value
21 | screenWidth = 0;
22 | screenHeight = 0;
23 | step = 0;
24 | currentX = 0;
25 | currentY = 0;
26 | constructor(appState) {
27 | this.appState = appState;
28 | }
29 | setWindowDimensions(width, height) {
30 | if (!this.mainWindow || this.mainWindow.isDestroyed())
31 | return;
32 | // Get current window position
33 | const [currentX, currentY] = this.mainWindow.getPosition();
34 | // Get screen dimensions
35 | const primaryDisplay = electron_1.screen.getPrimaryDisplay();
36 | const workArea = primaryDisplay.workAreaSize;
37 | // Use 75% width if debugging has occurred, otherwise use 60%
38 | const maxAllowedWidth = Math.floor(workArea.width * (this.appState.getHasDebugged() ? 0.75 : 0.5));
39 | // Ensure width doesn't exceed max allowed width and height is reasonable
40 | const newWidth = Math.min(width + 32, maxAllowedWidth);
41 | const newHeight = Math.max(Math.ceil(height), 1200); // Ensure minimum height of 1200px
42 | // Center the window horizontally if it would go off screen
43 | const maxX = workArea.width - newWidth;
44 | const newX = Math.min(Math.max(currentX, 0), maxX);
45 | // Update window bounds
46 | this.mainWindow.setBounds({
47 | x: newX,
48 | y: currentY,
49 | width: newWidth,
50 | height: newHeight
51 | });
52 | // Update internal state
53 | this.windowPosition = { x: newX, y: currentY };
54 | this.windowSize = { width: newWidth, height: newHeight };
55 | this.currentX = newX;
56 | }
57 | createWindow() {
58 | if (this.mainWindow !== null)
59 | return;
60 | const primaryDisplay = electron_1.screen.getPrimaryDisplay();
61 | const workArea = primaryDisplay.workAreaSize;
62 | this.screenWidth = workArea.width;
63 | this.screenHeight = workArea.height;
64 | this.step = Math.floor(this.screenWidth / 10); // 10 steps
65 | this.currentX = 0; // Start at the left
66 | const windowSettings = {
67 | height: 1200,
68 | minWidth: undefined,
69 | maxWidth: undefined,
70 | x: this.currentX,
71 | y: 0,
72 | webPreferences: {
73 | nodeIntegration: true,
74 | contextIsolation: true,
75 | preload: node_path_1.default.join(__dirname, "preload.js")
76 | },
77 | show: true,
78 | alwaysOnTop: true,
79 | frame: false,
80 | transparent: true,
81 | fullscreenable: false,
82 | hasShadow: false,
83 | backgroundColor: "#00000000",
84 | focusable: true
85 | };
86 | this.mainWindow = new electron_1.BrowserWindow(windowSettings);
87 | // this.mainWindow.webContents.openDevTools()
88 | this.mainWindow.setContentProtection(true);
89 | if (process.platform === "darwin") {
90 | this.mainWindow.setVisibleOnAllWorkspaces(true, {
91 | visibleOnFullScreen: true
92 | });
93 | this.mainWindow.setHiddenInMissionControl(true);
94 | this.mainWindow.setAlwaysOnTop(true, "floating");
95 | }
96 | if (process.platform === "linux") {
97 | // Linux-specific optimizations for stealth overlays
98 | if (this.mainWindow.setHasShadow) {
99 | this.mainWindow.setHasShadow(false);
100 | }
101 | this.mainWindow.setFocusable(false);
102 | }
103 | this.mainWindow.setSkipTaskbar(true);
104 | this.mainWindow.setAlwaysOnTop(true);
105 | this.mainWindow.loadURL(startUrl).catch((err) => {
106 | console.error("Failed to load URL:", err);
107 | });
108 | const bounds = this.mainWindow.getBounds();
109 | this.windowPosition = { x: bounds.x, y: bounds.y };
110 | this.windowSize = { width: bounds.width, height: bounds.height };
111 | this.currentX = bounds.x;
112 | this.currentY = bounds.y;
113 | this.setupWindowListeners();
114 | this.isWindowVisible = true;
115 | }
116 | setupWindowListeners() {
117 | if (!this.mainWindow)
118 | return;
119 | this.mainWindow.on("move", () => {
120 | if (this.mainWindow) {
121 | const bounds = this.mainWindow.getBounds();
122 | this.windowPosition = { x: bounds.x, y: bounds.y };
123 | this.currentX = bounds.x;
124 | this.currentY = bounds.y;
125 | }
126 | });
127 | this.mainWindow.on("resize", () => {
128 | if (this.mainWindow) {
129 | const bounds = this.mainWindow.getBounds();
130 | this.windowSize = { width: bounds.width, height: bounds.height };
131 | }
132 | });
133 | this.mainWindow.on("closed", () => {
134 | this.mainWindow = null;
135 | this.isWindowVisible = false;
136 | this.windowPosition = null;
137 | this.windowSize = null;
138 | });
139 | }
140 | getMainWindow() {
141 | return this.mainWindow;
142 | }
143 | isVisible() {
144 | return this.isWindowVisible;
145 | }
146 | hideMainWindow() {
147 | if (!this.mainWindow || this.mainWindow.isDestroyed()) {
148 | console.warn("Main window does not exist or is destroyed.");
149 | return;
150 | }
151 | const bounds = this.mainWindow.getBounds();
152 | this.windowPosition = { x: bounds.x, y: bounds.y };
153 | this.windowSize = { width: bounds.width, height: bounds.height };
154 | this.mainWindow.hide();
155 | this.isWindowVisible = false;
156 | }
157 | showMainWindow() {
158 | if (!this.mainWindow || this.mainWindow.isDestroyed()) {
159 | console.warn("Main window does not exist or is destroyed.");
160 | return;
161 | }
162 | if (this.windowPosition && this.windowSize) {
163 | this.mainWindow.setBounds({
164 | x: this.windowPosition.x,
165 | y: this.windowPosition.y,
166 | width: this.windowSize.width,
167 | height: this.windowSize.height
168 | });
169 | }
170 | this.mainWindow.showInactive();
171 | this.isWindowVisible = true;
172 | }
173 | toggleMainWindow() {
174 | if (this.isWindowVisible) {
175 | this.hideMainWindow();
176 | }
177 | else {
178 | this.showMainWindow();
179 | }
180 | }
181 | // New methods for window movement
182 | moveWindowRight() {
183 | if (!this.mainWindow)
184 | return;
185 | const windowWidth = this.windowSize?.width || 0;
186 | const halfWidth = windowWidth / 2;
187 | // Ensure currentX and currentY are numbers
188 | this.currentX = Number(this.currentX) || 0;
189 | this.currentY = Number(this.currentY) || 0;
190 | this.currentX = Math.min(this.screenWidth - halfWidth, this.currentX + this.step);
191 | this.mainWindow.setPosition(Math.round(this.currentX), Math.round(this.currentY));
192 | }
193 | moveWindowLeft() {
194 | if (!this.mainWindow)
195 | return;
196 | const windowWidth = this.windowSize?.width || 0;
197 | const halfWidth = windowWidth / 2;
198 | // Ensure currentX and currentY are numbers
199 | this.currentX = Number(this.currentX) || 0;
200 | this.currentY = Number(this.currentY) || 0;
201 | this.currentX = Math.max(-halfWidth, this.currentX - this.step);
202 | this.mainWindow.setPosition(Math.round(this.currentX), Math.round(this.currentY));
203 | }
204 | moveWindowDown() {
205 | if (!this.mainWindow)
206 | return;
207 | const windowHeight = this.windowSize?.height || 0;
208 | const halfHeight = windowHeight / 2;
209 | // Ensure currentX and currentY are numbers
210 | this.currentX = Number(this.currentX) || 0;
211 | this.currentY = Number(this.currentY) || 0;
212 | this.currentY = Math.min(this.screenHeight - halfHeight, this.currentY + this.step);
213 | this.mainWindow.setPosition(Math.round(this.currentX), Math.round(this.currentY));
214 | }
215 | moveWindowUp() {
216 | if (!this.mainWindow)
217 | return;
218 | const windowHeight = this.windowSize?.height || 0;
219 | const halfHeight = windowHeight / 2;
220 | // Ensure currentX and currentY are numbers
221 | this.currentX = Number(this.currentX) || 0;
222 | this.currentY = Number(this.currentY) || 0;
223 | this.currentY = Math.max(-halfHeight, this.currentY - this.step);
224 | this.mainWindow.setPosition(Math.round(this.currentX), Math.round(this.currentY));
225 | }
226 | }
227 | exports.WindowHelper = WindowHelper;
228 | //# sourceMappingURL=WindowHelper.js.map
--------------------------------------------------------------------------------
/src/components/Solutions/SolutionCommands.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect, useRef } from "react"
2 | import { IoLogOutOutline } from "react-icons/io5"
3 |
4 | interface SolutionCommandsProps {
5 | extraScreenshots: any[]
6 | onTooltipVisibilityChange?: (visible: boolean, height: number) => void
7 | }
8 |
9 | const SolutionCommands: React.FC = ({
10 | extraScreenshots,
11 | onTooltipVisibilityChange
12 | }) => {
13 | const [isTooltipVisible, setIsTooltipVisible] = useState(false)
14 | const tooltipRef = useRef(null)
15 |
16 | useEffect(() => {
17 | if (onTooltipVisibilityChange) {
18 | let tooltipHeight = 0
19 | if (tooltipRef.current && isTooltipVisible) {
20 | tooltipHeight = tooltipRef.current.offsetHeight + 10 // Adjust if necessary
21 | }
22 | onTooltipVisibilityChange(isTooltipVisible, tooltipHeight)
23 | }
24 | }, [isTooltipVisible, onTooltipVisibilityChange])
25 |
26 | const handleMouseEnter = () => {
27 | setIsTooltipVisible(true)
28 | }
29 |
30 | const handleMouseLeave = () => {
31 | setIsTooltipVisible(false)
32 | }
33 |
34 | return (
35 |
36 |
37 |
38 | {/* Show/Hide */}
39 |
40 |
Show/Hide
41 |
42 |
43 | ⌘
44 |
45 |
46 | B
47 |
48 |
49 |
50 |
51 | {/* Screenshot */}
52 |
53 |
54 | {extraScreenshots.length === 0
55 | ? "Screenshot your code"
56 | : "Screenshot"}
57 |
58 |
59 |
60 | ⌘
61 |
62 |
63 | H
64 |
65 |
66 |
67 | {extraScreenshots.length > 0 && (
68 |
69 |
Debug
70 |
71 |
72 | ⌘
73 |
74 |
75 | ↵
76 |
77 |
78 |
79 | )}
80 |
81 | {/* Start Over */}
82 |
83 |
Start over
84 |
85 |
86 | ⌘
87 |
88 |
89 | R
90 |
91 |
92 |
93 |
94 | {/* Question Mark with Tooltip */}
95 |
100 | {/* Question mark circle */}
101 |
102 | ?
103 |
104 |
105 | {/* Tooltip Content */}
106 | {isTooltipVisible && (
107 |
112 |
113 | {/* Tooltip content */}
114 |
115 |
116 | Keyboard Shortcuts
117 |
118 |
119 | {/* Toggle Command */}
120 |
121 |
122 |
123 | Toggle Window
124 |
125 |
126 |
127 | ⌘
128 |
129 |
130 | B
131 |
132 |
133 |
134 |
135 | Show or hide this window.
136 |
137 |
138 | {/* Screenshot Command */}
139 |
140 |
141 |
142 | Take Screenshot
143 |
144 |
145 |
146 | ⌘
147 |
148 |
149 | H
150 |
151 |
152 |
153 |
154 | Capture additional parts of the question or your
155 | solution for debugging help. Up to 5 extra screenshots
156 | are saved.
157 |
158 |
159 | {/* Debug Command */}
160 |
161 |
162 |
Debug
163 |
164 |
165 | ⌘
166 |
167 |
168 | ↵
169 |
170 |
171 |
172 |
173 | Generate new solutions based on all previous and newly
174 | added screenshots.
175 |
176 |
177 | {/* Start Over Command */}
178 |
179 |
180 |
Start Over
181 |
182 |
183 | ⌘
184 |
185 |
186 | R
187 |
188 |
189 |
190 |
191 | Start fresh with a new question.
192 |
193 |
194 |
195 |
196 |
197 |
198 | )}
199 |
200 |
201 | {/* Sign Out Button */}
202 |
window.electronAPI.quitApp()}
206 | >
207 |
208 |
209 |
210 |
211 |
212 | )
213 | }
214 |
215 | export default SolutionCommands
216 |
--------------------------------------------------------------------------------
/src/components/Audio/AudioTranscriber.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useRef } from "react"
2 | import { Mic, MicOff, Volume2 } from "lucide-react"
3 | import { GoogleGenAI, Modality } from '@google/genai'
4 |
5 | interface AudioTranscriberProps {
6 | onTranscript: (text: string) => void
7 | }
8 |
9 | const AudioTranscriber: React.FC = ({ onTranscript }) => {
10 | const [isRecording, setIsRecording] = useState(false)
11 | const [transcript, setTranscript] = useState("")
12 | const [error, setError] = useState("")
13 | const [isConnecting, setIsConnecting] = useState(false)
14 | const sessionRef = useRef(null)
15 | const streamRef = useRef(null)
16 | const audioContextRef = useRef(null)
17 | const processorRef = useRef(null)
18 |
19 | const startRecording = async () => {
20 | try {
21 | setError("")
22 | setTranscript("")
23 | setIsConnecting(true)
24 |
25 | // Get API key
26 | let apiKey = (window as any).GEMINI_API_KEY
27 | if (!apiKey && (window as any).electronAPI?.getApiKey) {
28 | apiKey = await (window as any).electronAPI.getApiKey()
29 | }
30 |
31 | if (!apiKey) {
32 | throw new Error("API key not available")
33 | }
34 |
35 | // Initialize Google GenAI
36 | const ai = new GoogleGenAI({ apiKey })
37 |
38 | const config = {
39 | responseModalities: [Modality.TEXT],
40 | inputAudioTranscription: {}
41 | }
42 |
43 | // Connect to Gemini Live
44 | const session = await ai.live.connect({
45 | model: 'gemini-2.0-flash-exp',
46 | callbacks: {
47 | onopen: () => {
48 | setIsConnecting(false)
49 | setIsRecording(true)
50 | },
51 | onmessage: (message: any) => {
52 | // Get input transcription (what user said)
53 | if (message.serverContent?.inputTranscription?.text) {
54 | const text = message.serverContent.inputTranscription.text
55 | setTranscript((prev) => {
56 | const newTranscript = prev + text + " "
57 | onTranscript(newTranscript)
58 | return newTranscript
59 | })
60 | }
61 | },
62 | onerror: (e: any) => {
63 | setError(`Error: ${e.message}`)
64 | stopRecording()
65 | },
66 | onclose: (e: any) => {
67 | if (e.code !== 1000) {
68 | setError(`Connection closed: ${e.reason || "Unknown error"}`)
69 | }
70 | setIsRecording(false)
71 | setIsConnecting(false)
72 | },
73 | },
74 | config: config,
75 | })
76 |
77 | sessionRef.current = session
78 |
79 | // Create audio context first
80 | const audioContext = new AudioContext({ sampleRate: 16000 })
81 | audioContextRef.current = audioContext
82 |
83 | // Create a destination to mix audio streams
84 | const destination = audioContext.createMediaStreamDestination()
85 |
86 | try {
87 | // Request microphone access
88 | const micStream = await navigator.mediaDevices.getUserMedia({
89 | audio: {
90 | echoCancellation: true,
91 | noiseSuppression: true,
92 | autoGainControl: true,
93 | sampleRate: 16000
94 | }
95 | })
96 |
97 | const micSource = audioContext.createMediaStreamSource(micStream)
98 | micSource.connect(destination)
99 |
100 | // Try to get system audio (desktop/tab audio)
101 | try {
102 | const systemStream = await navigator.mediaDevices.getDisplayMedia({
103 | video: false,
104 | audio: {
105 | echoCancellation: false,
106 | noiseSuppression: false,
107 | autoGainControl: false,
108 | sampleRate: 16000
109 | }
110 | })
111 |
112 | const systemSource = audioContext.createMediaStreamSource(systemStream)
113 | systemSource.connect(destination)
114 | } catch (e) {
115 | console.log("System audio not available, using mic only")
116 | }
117 | } catch (e) {
118 | throw new Error("Failed to access audio devices")
119 | }
120 |
121 | // Use the mixed stream
122 | streamRef.current = destination.stream
123 |
124 | const source = audioContext.createMediaStreamSource(destination.stream)
125 | const processor = audioContext.createScriptProcessor(4096, 1, 1)
126 | processorRef.current = processor
127 |
128 | source.connect(processor)
129 | processor.connect(audioContext.destination)
130 |
131 | processor.onaudioprocess = (e) => {
132 | if (session && sessionRef.current) {
133 | const inputData = e.inputBuffer.getChannelData(0)
134 |
135 | // Convert float32 to int16
136 | const int16Data = new Int16Array(inputData.length)
137 | for (let i = 0; i < inputData.length; i++) {
138 | const s = Math.max(-1, Math.min(1, inputData[i]))
139 | int16Data[i] = s < 0 ? s * 0x8000 : s * 0x7FFF
140 | }
141 |
142 | // Convert to base64
143 | const base64Audio = btoa(
144 | String.fromCharCode(...new Uint8Array(int16Data.buffer))
145 | )
146 |
147 | // Send audio to Gemini Live
148 | session.sendRealtimeInput({
149 | audio: {
150 | data: base64Audio,
151 | mimeType: "audio/pcm;rate=16000"
152 | }
153 | })
154 | }
155 | }
156 |
157 | } catch (error: any) {
158 | setError(`Failed to start recording: ${error.message}`)
159 | setIsRecording(false)
160 | setIsConnecting(false)
161 | }
162 | }
163 |
164 | const stopRecording = () => {
165 | // Disconnect audio processor
166 | if (processorRef.current) {
167 | processorRef.current.disconnect()
168 | processorRef.current = null
169 | }
170 |
171 | // Close audio context
172 | if (audioContextRef.current) {
173 | audioContextRef.current.close()
174 | audioContextRef.current = null
175 | }
176 |
177 | // Stop all tracks
178 | if (streamRef.current) {
179 | streamRef.current.getTracks().forEach(track => track.stop())
180 | streamRef.current = null
181 | }
182 |
183 | // Close session
184 | if (sessionRef.current) {
185 | sessionRef.current.close()
186 | sessionRef.current = null
187 | }
188 |
189 | setIsRecording(false)
190 | setIsConnecting(false)
191 | }
192 |
193 | const toggleRecording = () => {
194 | if (isRecording || isConnecting) {
195 | stopRecording()
196 | } else {
197 | startRecording()
198 | }
199 | }
200 |
201 | const clearTranscript = () => {
202 | setTranscript("")
203 | onTranscript("")
204 | }
205 |
206 | const saveTranscript = () => {
207 | if (!transcript) return
208 |
209 | const timestamp = new Date().toISOString().replace(/[:.]/g, '-')
210 | const filename = `transcript_${timestamp}.txt`
211 | const blob = new Blob([transcript], { type: 'text/plain' })
212 | const url = URL.createObjectURL(blob)
213 | const a = document.createElement('a')
214 | a.href = url
215 | a.download = filename
216 | a.click()
217 | URL.revokeObjectURL(url)
218 | }
219 |
220 | return (
221 |
222 | {/* Recording Button */}
223 |
235 | {isRecording ? (
236 | <>
237 |
238 | Stop
239 |
240 | >
241 | ) : isConnecting ? (
242 | <>
243 |
244 | Connecting...
245 | >
246 | ) : (
247 | <>
248 |
249 | Record
250 | >
251 | )}
252 |
253 |
254 | {/* Error Display */}
255 | {error && (
256 |
259 | )}
260 |
261 | {/* Transcript Display */}
262 | {transcript && (
263 |
264 |
265 |
266 | {/* Header */}
267 |
268 |
Transcript
269 |
270 |
275 | Save
276 |
277 |
281 | Clear
282 |
283 |
284 |
285 |
286 | {/* Transcript Content */}
287 |
288 |
289 | {transcript}
290 |
291 |
292 |
293 |
294 |
295 | )}
296 |
297 | )
298 | }
299 |
300 | export default AudioTranscriber
301 |
--------------------------------------------------------------------------------