├── .nvmrc ├── src ├── pages │ ├── options │ │ ├── index.css │ │ ├── Options.css │ │ ├── Options.tsx │ │ ├── index.html │ │ └── index.tsx │ ├── panel │ │ ├── index.css │ │ ├── Panel.css │ │ ├── Panel.tsx │ │ ├── index.html │ │ └── index.tsx │ ├── content │ │ ├── style.scss │ │ ├── copyToClipboard.ts │ │ ├── injected.ts │ │ ├── permission.ts │ │ ├── index.ts │ │ ├── mainWorld │ │ │ ├── index.ts │ │ │ └── mainWorld.ts │ │ ├── reverseMarkdown.ts │ │ ├── style.global.scss │ │ ├── ripple.ts │ │ ├── getViewportPercentage.ts │ │ ├── attachFile.ts │ │ ├── domOperations.ts │ │ └── getAnnotatedDOM.ts │ ├── newtab │ │ ├── Newtab.scss │ │ ├── index.html │ │ ├── index.css │ │ ├── index.tsx │ │ ├── Newtab.css │ │ └── Newtab.tsx │ ├── devtools │ │ ├── index.ts │ │ └── index.html │ ├── permission │ │ ├── index.html │ │ └── requestPermission.ts │ ├── popup │ │ ├── index.html │ │ ├── index.css │ │ ├── index.tsx │ │ ├── Popup.css │ │ └── Popup.tsx │ ├── sidepanel │ │ ├── index.html │ │ ├── index.tsx │ │ └── index.css │ └── background │ │ └── index.ts ├── vite-env.d.ts ├── assets │ ├── style │ │ └── theme.scss │ └── img │ │ └── logo.svg ├── environment.d.ts ├── helpers │ ├── knowledge │ │ ├── redirects.json │ │ ├── index.ts │ │ └── db.json │ ├── shrinkHTML │ │ ├── tagsSelfClose.ts │ │ └── templatize.test.ts │ ├── rpc │ │ ├── utils.ts │ │ ├── runtimeFunctionStrings.ts │ │ ├── pageRPC.ts │ │ └── performAction.ts │ ├── index.ts │ ├── countTokens.ts │ ├── browserUtils.ts │ ├── buildAnnotatedScreenshots.ts │ ├── dom-agent │ │ ├── availableActions.ts │ │ ├── determineNextAction.ts │ │ └── parseResponse.ts │ ├── vision-agent │ │ ├── parseResponse.ts │ │ ├── determineNavigateAction.ts │ │ └── tools.ts │ ├── utils.ts │ ├── errorChecker.ts │ ├── chromeDebugger.ts │ ├── simplifyDom.ts │ ├── disableExtensions.ts │ └── voiceControl.ts ├── constants.ts ├── shared │ ├── hoc │ │ ├── withSuspense.tsx │ │ └── withErrorBoundary.tsx │ ├── storages │ │ ├── exampleThemeStorage.ts │ │ └── base.ts │ ├── hooks │ │ └── useStorage.tsx │ └── images │ │ └── mergeScreenshots.ts ├── common │ ├── CustomKnowledgeBase │ │ ├── Notes.tsx │ │ ├── DuplicateKnowledgeAlert.tsx │ │ ├── DefaultKnowledge.tsx │ │ ├── index.tsx │ │ ├── NewKnowledgeJson.tsx │ │ └── HostKnowledge.tsx │ ├── AutosizeTextarea.tsx │ ├── TokenCount.tsx │ ├── settings │ │ ├── AgentModeDropdown.tsx │ │ ├── ModelDropdown.tsx │ │ └── SetAPIKey.tsx │ ├── CopyButton.tsx │ ├── TaskStatus.tsx │ ├── RunTaskButton.tsx │ ├── VoiceButton.tsx │ ├── RecommendedTasks.tsx │ ├── App.tsx │ ├── TaskUI.tsx │ └── TaskHistory.tsx ├── state │ ├── ui.ts │ ├── settings.ts │ └── store.ts └── global.d.ts ├── .github ├── CODEOWNERS ├── dependabot.yml ├── workflows │ ├── greetings.yml │ ├── test.yml │ └── build-zip.yml ├── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md └── stale.yml ├── .env.example ├── .eslintignore ├── .npmrc ├── .prettierrc ├── commitlint.config.js ├── .husky ├── pre-commit └── commit-msg ├── public └── icon-128.png ├── media └── web-annotation.png ├── test-utils └── jest.setup.js ├── postcss.config.js ├── utils ├── reload │ ├── constant.ts │ ├── injections │ │ ├── script.ts │ │ └── view.ts │ ├── utils.ts │ ├── interpreter │ │ ├── index.ts │ │ └── types.ts │ ├── rollup.config.mjs │ ├── initReloadClient.ts │ └── initReloadServer.ts ├── plugins │ ├── custom-dynamic-import.ts │ ├── watch-rebuild.ts │ ├── inline-vite-preload-script.ts │ ├── add-hmr.ts │ └── make-manifest.ts ├── manifest-parser │ └── index.ts └── log.ts ├── .prettierignore ├── tailwind.config.js ├── package.lib.json ├── .gitignore ├── tsconfig.json ├── .eslintrc ├── rollup.lib.config.js ├── PULL_REQUEST_TEMPLATE.md ├── manifest.js ├── CONTRIBUTING_KNOWLEDGE.md ├── TROUBLESHOOTING.md ├── vite.config.ts ├── CONTRIBUTING.md ├── package.json └── README.md /.nvmrc: -------------------------------------------------------------------------------- 1 | 22.11.0 2 | -------------------------------------------------------------------------------- /src/pages/options/index.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/pages/panel/index.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @mondaychen -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | VITE_DEBUG_MODE=true 2 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | public-hoist-pattern[]=@testing-library/dom 2 | -------------------------------------------------------------------------------- /src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": true, 3 | "printWidth": 80 4 | } 5 | -------------------------------------------------------------------------------- /src/assets/style/theme.scss: -------------------------------------------------------------------------------- 1 | .crx-class { 2 | color: pink; 3 | } 4 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | export default { extends: ["@commitlint/config-conventional"] }; 2 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npx lint-staged 5 | -------------------------------------------------------------------------------- /public/icon-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/normal-computing/fuji-web/HEAD/public/icon-128.png -------------------------------------------------------------------------------- /src/pages/content/style.scss: -------------------------------------------------------------------------------- 1 | // IMPORTANT: this file is supposed to be loaded into the shadow dom 2 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npm run commitlint ${1} 5 | -------------------------------------------------------------------------------- /media/web-annotation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/normal-computing/fuji-web/HEAD/media/web-annotation.png -------------------------------------------------------------------------------- /test-utils/jest.setup.js: -------------------------------------------------------------------------------- 1 | // Do what you need to set up your test 2 | console.log("setup test: jest.setup.js"); 3 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /src/pages/panel/Panel.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: #242424; 3 | } 4 | 5 | .container { 6 | color: #ffffff; 7 | } 8 | -------------------------------------------------------------------------------- /src/pages/newtab/Newtab.scss: -------------------------------------------------------------------------------- 1 | $myColor: red; 2 | 3 | h1, 4 | h2, 5 | h3, 6 | h4, 7 | h5, 8 | h6 { 9 | color: $myColor; 10 | } 11 | -------------------------------------------------------------------------------- /utils/reload/constant.ts: -------------------------------------------------------------------------------- 1 | export const LOCAL_RELOAD_SOCKET_PORT = 8082; 2 | export const LOCAL_RELOAD_SOCKET_URL = `ws://localhost:${LOCAL_RELOAD_SOCKET_PORT}`; 3 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | .gitignore 4 | .github 5 | .eslintignore 6 | .husky 7 | .nvmrc 8 | .prettierignore 9 | LICENSE 10 | *.md 11 | pnpm-lock.yaml -------------------------------------------------------------------------------- /src/pages/content/copyToClipboard.ts: -------------------------------------------------------------------------------- 1 | // copy provided text to clipboard 2 | export async function copyToClipboard(text: string) { 3 | await navigator.clipboard.writeText(text); 4 | } 5 | -------------------------------------------------------------------------------- /src/pages/content/injected.ts: -------------------------------------------------------------------------------- 1 | // The content script runs inside each page this extension is enabled on 2 | 3 | import { initializeRPC } from "./domOperations"; 4 | 5 | initializeRPC(); 6 | -------------------------------------------------------------------------------- /src/pages/options/Options.css: -------------------------------------------------------------------------------- 1 | .container { 2 | width: 100%; 3 | height: 50vh; 4 | font-size: 2rem; 5 | display: flex; 6 | align-items: center; 7 | justify-content: center; 8 | } 9 | -------------------------------------------------------------------------------- /src/environment.d.ts: -------------------------------------------------------------------------------- 1 | declare global { 2 | namespace NodeJS { 3 | interface ProcessEnv { 4 | __DEV__: string; 5 | __FIREFOX__: string; 6 | } 7 | } 8 | } 9 | 10 | export {}; 11 | -------------------------------------------------------------------------------- /src/helpers/knowledge/redirects.json: -------------------------------------------------------------------------------- 1 | { 2 | "twitter.com": "x.com", 3 | "www.twitter.com": "x.com", 4 | "www.x.com": "x.com", 5 | "www.airbnb.com": "airbnb.com", 6 | "www.amazon.com": "amazon.com" 7 | } 8 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | export default { 3 | content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"], 4 | theme: { 5 | extend: {}, 6 | }, 7 | plugins: [], 8 | }; 9 | -------------------------------------------------------------------------------- /src/pages/options/Options.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import "@pages/options/Options.css"; 3 | 4 | const Options: React.FC = () => { 5 | return
Options
; 6 | }; 7 | 8 | export default Options; 9 | -------------------------------------------------------------------------------- /src/pages/devtools/index.ts: -------------------------------------------------------------------------------- 1 | // try { 2 | // chrome.devtools.panels.create( 3 | // "Dev Tools", 4 | // "icon-34.png", 5 | // "src/pages/panel/index.html", 6 | // ); 7 | // } catch (e) { 8 | // console.error(e); 9 | // } 10 | -------------------------------------------------------------------------------- /package.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "web-wand-lib", 3 | "version": "2.0.3", 4 | "description": "Helper library for Web Wand", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/normal-computing/web-wand" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/pages/devtools/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Devtools 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | /node_modules 3 | 4 | # testing 5 | /coverage 6 | 7 | # build 8 | /dist 9 | /dist-lib 10 | 11 | # etc 12 | .DS_Store 13 | .env.local 14 | .env 15 | .idea 16 | 17 | # compiled 18 | utils/reload/*.js 19 | utils/reload/injections/*.js 20 | public/manifest.json 21 | -------------------------------------------------------------------------------- /src/pages/panel/Panel.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import "@pages/panel/Panel.css"; 3 | 4 | const Panel: React.FC = () => { 5 | return ( 6 |
7 |

Dev Tools Panel

8 |
9 | ); 10 | }; 11 | 12 | export default Panel; 13 | -------------------------------------------------------------------------------- /src/pages/permission/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Request Permissions 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/pages/newtab/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | New tab 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/pages/popup/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Popup 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/pages/options/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Options 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/pages/panel/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Devtools Panel 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/pages/sidepanel/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Side Panel 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/helpers/shrinkHTML/tagsSelfClose.ts: -------------------------------------------------------------------------------- 1 | export const tagsSelfClose = (html: string) => { 2 | // Regular expression to match empty elements 3 | const re = /<([a-z]+)([^<]*?)><\/\1>/gi; 4 | 5 | // Replace empty elements with self-closing tags 6 | const newHtml = html.replace(re, "<$1$2 />"); 7 | return newHtml; 8 | }; 9 | -------------------------------------------------------------------------------- /src/helpers/rpc/utils.ts: -------------------------------------------------------------------------------- 1 | import { DomActions } from "./domActions"; 2 | 3 | export async function waitTillHTMLRendered( 4 | tabId: number, 5 | interval = undefined, 6 | timeout = undefined, 7 | ) { 8 | const domActions = new DomActions(tabId); 9 | return await domActions.waitTillHTMLRendered(interval, timeout); 10 | } 11 | -------------------------------------------------------------------------------- /utils/reload/injections/script.ts: -------------------------------------------------------------------------------- 1 | import initReloadClient from "../initReloadClient"; 2 | 3 | export default function addHmrIntoScript(watchPath: string) { 4 | const reload = () => { 5 | chrome.runtime.reload(); 6 | }; 7 | 8 | initReloadClient({ 9 | watchPath, 10 | onUpdate: reload, 11 | onForceReload: reload, 12 | }); 13 | } 14 | -------------------------------------------------------------------------------- /utils/reload/utils.ts: -------------------------------------------------------------------------------- 1 | import { clearTimeout } from "timers"; 2 | 3 | export function debounce( 4 | callback: (...args: A) => void, 5 | delay: number, 6 | ) { 7 | let timer: NodeJS.Timeout; 8 | return function (...args: A) { 9 | clearTimeout(timer); 10 | timer = setTimeout(() => callback(...args), delay); 11 | }; 12 | } 13 | -------------------------------------------------------------------------------- /src/constants.ts: -------------------------------------------------------------------------------- 1 | export const TAXY_ELEMENT_SELECTOR = "data-taxy-node-id"; 2 | export const VISIBLE_TEXT_ATTRIBUTE_NAME = "data-web-wand-visible-text"; 3 | export const ARIA_LABEL_ATTRIBUTE_NAME = "data-web-wand-aria-label"; 4 | export const WEB_WAND_LABEL_ATTRIBUTE_NAME = "data-web-wand-label"; 5 | 6 | // read from env 7 | export const debugMode = import.meta.env.VITE_DEBUG_MODE === "true"; 8 | -------------------------------------------------------------------------------- /src/pages/content/permission.ts: -------------------------------------------------------------------------------- 1 | export const injectMicrophonePermissionIframe = () => { 2 | const iframe = document.createElement("iframe"); 3 | iframe.setAttribute("hidden", "hidden"); 4 | iframe.setAttribute("id", "permissionsIFrame"); 5 | iframe.setAttribute("allow", "microphone"); 6 | iframe.src = chrome.runtime.getURL("/src/pages/permission/index.html"); 7 | document.body.appendChild(iframe); 8 | }; 9 | -------------------------------------------------------------------------------- /src/pages/content/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * DO NOT USE import someModule from '...'; 3 | * 4 | * @issue-url https://github.com/Jonghakseo/chrome-extension-boilerplate-react-vite/issues/160 5 | * 6 | * Chrome extensions don't support modules in content scripts. 7 | * If you want to use other modules in content scripts, you need to import them via these files. 8 | * 9 | */ 10 | import("@pages/content/injected"); 11 | -------------------------------------------------------------------------------- /src/helpers/index.ts: -------------------------------------------------------------------------------- 1 | import { type ToolOperation } from "./vision-agent/tools"; 2 | export { ToolOperation }; 3 | export { DomActions } from "./rpc/domActions"; 4 | export { callRPC, callRPCWithTab } from "./rpc/pageRPC"; 5 | export { attachDebugger, detachDebugger } from "./chromeDebugger"; 6 | import { operateTool, operateToolWithSelector } from "./rpc/performAction"; 7 | export { operateTool, operateToolWithSelector }; 8 | -------------------------------------------------------------------------------- /src/helpers/rpc/runtimeFunctionStrings.ts: -------------------------------------------------------------------------------- 1 | // TypeScript function 2 | function scrollIntoViewFunction() { 3 | // @ts-expect-error this is run in the browser context 4 | this.scrollIntoView({ 5 | block: "center", 6 | inline: "center", 7 | // behavior: 'smooth', 8 | }); 9 | } 10 | // Convert the TypeScript function to a string 11 | export const scrollScriptString = scrollIntoViewFunction.toString(); 12 | -------------------------------------------------------------------------------- /src/pages/content/mainWorld/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * DO NOT USE import someModule from '...'; 3 | * 4 | * @issue-url https://github.com/Jonghakseo/chrome-extension-boilerplate-react-vite/issues/160 5 | * 6 | * Chrome extensions don't support modules in content scripts. 7 | * If you want to use other modules in content scripts, you need to import them via these files. 8 | * 9 | */ 10 | import("@pages/content/mainWorld/mainWorld"); 11 | -------------------------------------------------------------------------------- /src/pages/newtab/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 | -------------------------------------------------------------------------------- /src/helpers/countTokens.ts: -------------------------------------------------------------------------------- 1 | export const countTokens = async (text: string, model_name: string) => { 2 | const response = await fetch("https://tiktoken-api.vercel.app/token_count", { 3 | method: "POST", 4 | headers: { 5 | "Content-Type": "application/json", 6 | }, 7 | body: JSON.stringify({ 8 | text, 9 | model_name, 10 | }), 11 | }); 12 | const data = await response.json(); 13 | return data.token_count; 14 | }; 15 | -------------------------------------------------------------------------------- /src/shared/hoc/withSuspense.tsx: -------------------------------------------------------------------------------- 1 | import { ComponentType, ReactElement, Suspense } from "react"; 2 | 3 | export default function withSuspense>( 4 | Component: ComponentType, 5 | SuspenseComponent: ReactElement, 6 | ) { 7 | return function WithSuspense(props: T) { 8 | return ( 9 | 10 | 11 | 12 | ); 13 | }; 14 | } 15 | -------------------------------------------------------------------------------- /src/common/CustomKnowledgeBase/Notes.tsx: -------------------------------------------------------------------------------- 1 | import { UnorderedList, ListItem } from "@chakra-ui/react"; 2 | 3 | export default function Notes({ notes }: { notes: string[] | undefined }) { 4 | if (!notes || notes.length === 0) { 5 | return null; 6 | } 7 | return ( 8 | 9 | {notes.map((note, index) => ( 10 | {note} 11 | ))} 12 | 13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /src/helpers/browserUtils.ts: -------------------------------------------------------------------------------- 1 | export async function findActiveTab(): Promise { 2 | const currentWindow = await chrome.windows.getCurrent(); 3 | if (!currentWindow || !currentWindow.id) { 4 | throw new Error("Could not find window"); 5 | } 6 | const tabs = await chrome.tabs.query({ 7 | active: true, 8 | windowId: currentWindow.id, 9 | }); 10 | const tab = tabs[0]; 11 | if (tab && tab.id != null) { 12 | return tab; 13 | } 14 | return null; 15 | } 16 | -------------------------------------------------------------------------------- /src/pages/content/reverseMarkdown.ts: -------------------------------------------------------------------------------- 1 | export function getDataFromRenderedMarkdown(selector: string) { 2 | const element = document.querySelector(selector); 3 | if (!element) { 4 | return null; 5 | } 6 | const text = Array.from(element.querySelectorAll("p")) 7 | .map((p) => p.textContent) 8 | .join("\n\n"); 9 | const codeBlocks = Array.from(element.querySelectorAll("pre code")).map( 10 | (code) => code.textContent, 11 | ); 12 | 13 | return { text, codeBlocks }; 14 | } 15 | -------------------------------------------------------------------------------- /src/state/ui.ts: -------------------------------------------------------------------------------- 1 | import { MyStateCreator } from "./store"; 2 | 3 | export type UiSlice = { 4 | instructions: string | null; 5 | actions: { 6 | setInstructions: (instructions: string) => void; 7 | }; 8 | }; 9 | export const createUiSlice: MyStateCreator = (set) => ({ 10 | instructions: null, 11 | actions: { 12 | setInstructions: (instructions) => { 13 | set((state) => { 14 | state.ui.instructions = instructions; 15 | }); 16 | }, 17 | }, 18 | }); 19 | -------------------------------------------------------------------------------- /src/pages/popup/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | width: 300px; 3 | height: 260px; 4 | margin: 0; 5 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", 6 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", 7 | sans-serif; 8 | -webkit-font-smoothing: antialiased; 9 | -moz-osx-font-smoothing: grayscale; 10 | 11 | position: relative; 12 | } 13 | 14 | code { 15 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", 16 | monospace; 17 | } 18 | -------------------------------------------------------------------------------- /utils/reload/interpreter/index.ts: -------------------------------------------------------------------------------- 1 | import type { WebSocketMessage, SerializedMessage } from "./types"; 2 | 3 | export default class MessageInterpreter { 4 | // eslint-disable-next-line @typescript-eslint/no-empty-function 5 | private constructor() {} 6 | 7 | static send(message: WebSocketMessage): SerializedMessage { 8 | return JSON.stringify(message); 9 | } 10 | static receive(serializedMessage: SerializedMessage): WebSocketMessage { 11 | return JSON.parse(serializedMessage); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/pages/sidepanel/index.tsx: -------------------------------------------------------------------------------- 1 | import { createRoot } from "react-dom/client"; 2 | 3 | import App from "@src/common/App"; 4 | 5 | import refreshOnUpdate from "virtual:reload-on-update-in-view"; 6 | 7 | refreshOnUpdate("pages/sidepanel"); 8 | 9 | function init() { 10 | const appContainer = document.querySelector("#app-container"); 11 | if (!appContainer) { 12 | throw new Error("Can not find #app-container"); 13 | } 14 | const root = createRoot(appContainer); 15 | root.render(); 16 | } 17 | 18 | init(); 19 | -------------------------------------------------------------------------------- /src/pages/sidepanel/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | min-height: 100dvh; 3 | margin: 0; 4 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", 5 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", 6 | sans-serif; 7 | -webkit-font-smoothing: antialiased; 8 | -moz-osx-font-smoothing: grayscale; 9 | 10 | position: relative; 11 | background-color: white; 12 | } 13 | 14 | code { 15 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", 16 | monospace; 17 | } 18 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "npm" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "weekly" 12 | -------------------------------------------------------------------------------- /.github/workflows/greetings.yml: -------------------------------------------------------------------------------- 1 | name: Greetings 2 | 3 | on: [pull_request_target, issues] 4 | 5 | jobs: 6 | greeting: 7 | runs-on: ubuntu-latest 8 | permissions: 9 | issues: write 10 | pull-requests: write 11 | steps: 12 | - uses: actions/first-interaction@v1 13 | with: 14 | repo-token: ${{ secrets.GITHUB_TOKEN }} 15 | issue-message: 'Thank you for your contribution. We will check and reply to you as soon as possible.' 16 | pr-message: 'Thank you for your contribution. We will check and reply to you as soon as possible.' 17 | -------------------------------------------------------------------------------- /utils/reload/interpreter/types.ts: -------------------------------------------------------------------------------- 1 | type UpdatePendingMessage = { 2 | type: "wait_update"; 3 | path: string; 4 | }; 5 | type UpdateRequestMessage = { 6 | type: "do_update"; 7 | }; 8 | type UpdateCompleteMessage = { type: "done_update" }; 9 | type BuildCompletionMessage = { type: "build_complete" }; 10 | type ForceReloadMessage = { type: "force_reload" }; 11 | 12 | export type SerializedMessage = string; 13 | export type WebSocketMessage = 14 | | UpdateCompleteMessage 15 | | UpdateRequestMessage 16 | | UpdatePendingMessage 17 | | BuildCompletionMessage 18 | | ForceReloadMessage; 19 | -------------------------------------------------------------------------------- /src/pages/newtab/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { createRoot } from "react-dom/client"; 3 | import Newtab from "@pages/newtab/Newtab"; 4 | import "@pages/newtab/index.css"; 5 | import refreshOnUpdate from "virtual:reload-on-update-in-view"; 6 | 7 | refreshOnUpdate("pages/newtab"); 8 | 9 | function init() { 10 | const appContainer = document.querySelector("#app-container"); 11 | if (!appContainer) { 12 | throw new Error("Can not find #app-container"); 13 | } 14 | const root = createRoot(appContainer); 15 | 16 | root.render(); 17 | } 18 | 19 | init(); 20 | -------------------------------------------------------------------------------- /src/pages/options/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { createRoot } from "react-dom/client"; 3 | import Options from "@pages/options/Options"; 4 | import "@pages/options/index.css"; 5 | import refreshOnUpdate from "virtual:reload-on-update-in-view"; 6 | 7 | refreshOnUpdate("pages/options"); 8 | 9 | function init() { 10 | const appContainer = document.querySelector("#app-container"); 11 | if (!appContainer) { 12 | throw new Error("Can not find #app-container"); 13 | } 14 | const root = createRoot(appContainer); 15 | root.render(); 16 | } 17 | 18 | init(); 19 | -------------------------------------------------------------------------------- /utils/plugins/custom-dynamic-import.ts: -------------------------------------------------------------------------------- 1 | import type { PluginOption } from "vite"; 2 | 3 | export default function customDynamicImport(): PluginOption { 4 | return { 5 | name: "custom-dynamic-import", 6 | renderDynamicImport({ moduleId }) { 7 | if (!moduleId.includes("node_modules") && process.env.__FIREFOX__) { 8 | return { 9 | left: `import(browser.runtime.getURL('./') + `, 10 | right: ".split('../').join(''));", 11 | }; 12 | } 13 | return { 14 | left: "import(", 15 | right: ")", 16 | }; 17 | }, 18 | }; 19 | } 20 | -------------------------------------------------------------------------------- /src/pages/content/style.global.scss: -------------------------------------------------------------------------------- 1 | // IMPORTANT: this file will impact the styles on ALL PAGES 2 | // DO NOT add style names that can easily conflict with other pages 3 | // TODO: import this directly from fuji-web instead of copying 4 | .web-agent-ripple { 5 | position: absolute; 6 | border-radius: 50%; 7 | transform: scale(0); 8 | animation: web-agent-ripple 0.5s ease-out; 9 | background-color: rgba(0, 0, 255, 0.961); 10 | pointer-events: none; 11 | z-index: 999999; 12 | } 13 | 14 | @keyframes web-agent-ripple { 15 | to { 16 | transform: scale(3); 17 | opacity: 0; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/pages/panel/index.tsx: -------------------------------------------------------------------------------- 1 | // import React from "react"; 2 | // import { createRoot } from "react-dom/client"; 3 | // import Panel from "@pages/panel/Panel"; 4 | // import "@pages/panel/index.css"; 5 | // import refreshOnUpdate from "virtual:reload-on-update-in-view"; 6 | 7 | // refreshOnUpdate("pages/panel"); 8 | 9 | // function init() { 10 | // const appContainer = document.querySelector("#app-container"); 11 | // if (!appContainer) { 12 | // throw new Error("Can not find #app-container"); 13 | // } 14 | // const root = createRoot(appContainer); 15 | // root.render(); 16 | // } 17 | 18 | // init(); 19 | -------------------------------------------------------------------------------- /src/pages/popup/index.tsx: -------------------------------------------------------------------------------- 1 | // import React from "react"; 2 | // import { createRoot } from "react-dom/client"; 3 | // import "@pages/popup/index.css"; 4 | // import Popup from "@pages/popup/Popup"; 5 | // import refreshOnUpdate from "virtual:reload-on-update-in-view"; 6 | 7 | // refreshOnUpdate("pages/popup"); 8 | 9 | // function init() { 10 | // const appContainer = document.querySelector("#app-container"); 11 | // if (!appContainer) { 12 | // throw new Error("Can not find #app-container"); 13 | // } 14 | // const root = createRoot(appContainer); 15 | // root.render(); 16 | // } 17 | 18 | // init(); 19 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | 8 | jobs: 9 | test: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v3 14 | 15 | - name: Setup Node.js 16 | uses: actions/setup-node@v3 17 | with: 18 | node-version-file: ".nvmrc" 19 | 20 | - uses: actions/cache@v3 21 | with: 22 | path: node_modules 23 | key: ${{ runner.OS }}-build-${{ hashFiles('**/package-lock.json') }} 24 | 25 | - uses: pnpm/action-setup@v2 26 | 27 | - run: pnpm install --frozen-lockfile 28 | 29 | - run: pnpm test 30 | -------------------------------------------------------------------------------- /src/common/AutosizeTextarea.tsx: -------------------------------------------------------------------------------- 1 | import { Textarea, TextareaProps } from "@chakra-ui/react"; 2 | import ResizeTextarea from "react-textarea-autosize"; 3 | import React from "react"; 4 | 5 | const AutosizeTextarea = React.forwardRef( 6 | (props, ref) => { 7 | return ( 8 |