Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 28 additions & 30 deletions packages/extension/src/background_script.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ const getActiveTabId = (
return true
}

const onConnect = async function (port: chrome.runtime.Port) {
const onConnect = async function(port: chrome.runtime.Port) {
if (port.name !== CONNECTION_APP) return
port.onDisconnect.addListener(() => onDisconnect(port))
const tabId = port.sender?.tab?.id
Expand All @@ -73,7 +73,7 @@ const onConnect = async function (port: chrome.runtime.Port) {
await PageActionBackground.handleSidePanelConnect(port)
}
}
const onDisconnect = async function (port: chrome.runtime.Port) {
const onDisconnect = async function(port: chrome.runtime.Port) {
if (port.name !== CONNECTION_APP) return
if (chrome.runtime.lastError) {
if (
Expand Down Expand Up @@ -170,24 +170,24 @@ const commandFuncs = {

const cmd = isSearch
? {
id: params.id,
title: params.title,
searchUrl: params.searchUrl,
iconUrl: params.iconUrl,
openMode: params.openMode,
openModeSecondary: params.openModeSecondary,
spaceEncoding: params.spaceEncoding,
popupOption: PopupOption,
}
: isPageAction
? {
id: params.id,
title: params.title,
searchUrl: params.searchUrl,
iconUrl: params.iconUrl,
openMode: params.openMode,
openModeSecondary: params.openModeSecondary,
spaceEncoding: params.spaceEncoding,
pageActionOption: params.pageActionOption,
popupOption: PopupOption,
}
: isPageAction
? {
id: params.id,
title: params.title,
iconUrl: params.iconUrl,
openMode: params.openMode,
pageActionOption: params.pageActionOption,
popupOption: PopupOption,
}
: null

if (!cmd) {
Expand Down Expand Up @@ -229,6 +229,7 @@ const commandFuncs = {
) => {
const handleOpenInTab = async () => {
let w: WindowType | undefined
const targetUrl = sender.tab?.url ?? sender.url

const stack = await WindowStackManager.getStack()
for (const layer of stack) {
Expand All @@ -241,7 +242,7 @@ const commandFuncs = {
}
if (!w || w.srcWindowId == null) {
console.warn("window not found", sender.tab?.windowId)
chrome.tabs.create({ url: sender.url })
chrome.tabs.create({ url: targetUrl })
await closeWindow(sender.tab?.windowId as number, "openInTab")
await WindowStackManager.removeWindow(sender.tab?.windowId as number)
response(true)
Expand All @@ -261,10 +262,7 @@ const commandFuncs = {
}

if (targetId) {
chrome.tabs.create({
url: sender.url,
windowId: targetId,
})
chrome.tabs.create({ url: targetUrl, windowId: targetId })
await closeWindow(sender.tab?.windowId as number, "openInTab")
await WindowStackManager.removeWindow(sender.tab?.windowId as number)
response(true)
Expand Down Expand Up @@ -547,17 +545,17 @@ const checkAndPerformLegacyBackup = async () => {
}
}

// Initialize commandIdObj and register listener at top-level
// to ensure they are available when service worker restarts
;(async () => {
try {
await ContextMenu.syncCommandIdObj()
chrome.contextMenus.onClicked.addListener(ContextMenu.onClicked)
} catch (error) {
// Ignore errors during initialization (e.g., in test environment)
console.debug("Failed to initialize context menu listener:", error)
}
})()
// Initialize commandIdObj and register listener at top-level
// to ensure they are available when service worker restarts
; (async () => {
try {
await ContextMenu.syncCommandIdObj()
chrome.contextMenus.onClicked.addListener(ContextMenu.onClicked)
} catch (error) {
// Ignore errors during initialization (e.g., in test environment)
console.debug("Failed to initialize context menu listener:", error)
}
})()

Settings.addChangedListener(() => ContextMenu.init())

Expand Down
2 changes: 1 addition & 1 deletion packages/extension/src/const.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,8 +109,8 @@ export const COMMAND_TYPE_GROUPS = [
titleKey: "commandGroup_webPage_title",
types: [
COMMAND_TYPE.SEARCH,
COMMAND_TYPE.PAGE_ACTION,
COMMAND_TYPE.AI_PROMPT,
COMMAND_TYPE.PAGE_ACTION,
],
},
{
Expand Down
76 changes: 11 additions & 65 deletions packages/extension/src/services/aiPrompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,76 +19,13 @@ export type AiService = {
/** External endpoint URL for AI service config data. */
const AI_SERVICES_URL = `${HUB_URL}/data/ai-services.json`

/**
* List of supported AI services with their DOM selectors.
* Used as fallback when the external fetch fails and no cache is available.
* Selector arrays are tried in order, using the first one that matches.
*/
const AI_SERVICES_FALLBACK: AiService[] = [
{
id: "chatgpt",
name: "ChatGPT",
url: "https://chatgpt.com",
faviconUrl: "https://chatgpt.com/favicon.ico",
inputSelectors: ["#prompt-textarea", "[data-testid='prompt-textarea']"],
submitSelectors: [
"form button.composer-submit-button-color",
"button#composer-submit-button",
"button[data-testid='composer-speech-button']",
],
selectorType: SelectorType.css,
},
{
id: "gemini",
name: "Gemini",
url: "https://gemini.google.com/app",
faviconUrl:
"https://www.gstatic.com/lamda/images/gemini_sparkle_aurora_33f86dc0c0257da337c63.svg",
inputSelectors: [".ql-editor[contenteditable='true']"],
submitSelectors: ["button.send-button", "button mat-icon[fonticon='send']"],
selectorType: SelectorType.css,
},
{
id: "claude",
name: "Claude",
url: "https://claude.ai/new",
faviconUrl: "https://claude.ai/favicon.ico",
inputSelectors: [
"div[contenteditable='true'][aria-label]",
"div[contenteditable='true'].ProseMirror",
],
submitSelectors: [
"#main-content button.Button_claude__c_hZy",
"#main-content button[data-state='closed']",
],
selectorType: SelectorType.css,
},
{
id: "perplexity",
name: "Perplexity",
url: "https://www.perplexity.ai",
faviconUrl: "https://favicon.im/perplexity.ai",
inputSelectors: [
"div#ask-input",
"div[contenteditable='true'][role='textbox']",
],
submitSelectors: [
"button[type='button']:has(use[*|href='#pplx-icon-custom-perplexity-v2v'])",
"button[type='button']:has(use[*|href='#pplx-icon-arrow-up'])",
"button[type='button']:has(use[*|href^='#pplx-icon-arrow-right'])",
"button[aria-label='Submit']",
],
selectorType: SelectorType.css,
},
]

/** Today's date string "YYYY-MM-DD" used as cache TTL key. */
const todayStr = (): string => new Date().toISOString().slice(0, 10)

/**
* Normalize raw JSON data fetched from the external endpoint into AiService[].
* Items that are missing required fields (id, url, inputSelectors, submitSelectors)
* are silently skipped. The external JSON may omit `selectorType`, defaulting to CSS.
* - Items that are missing required fields (id, url, inputSelectors, submitSelectors) are silently skipped.
* - The external JSON may omit `selectorType`, defaulting to `CSS`.
*/
const normalizeServices = (raw: unknown[]): AiService[] => {
const results: AiService[] = []
Expand Down Expand Up @@ -118,6 +55,15 @@ const normalizeServices = (raw: unknown[]): AiService[] => {
return results
}

/**
* List of supported AI services with their DOM selectors.
* Built from the hub's ai-services.json at compile time.
* Used as fallback when the external fetch fails and no cache is available.
* Selector arrays are tried in order, using the first one that matches.
*/
const AI_SERVICES_FALLBACK: AiService[] =
normalizeServices(__AI_SERVICES_JSON__)

/**
* Retrieve AI service definitions.
* Strategy:
Expand Down
3 changes: 2 additions & 1 deletion packages/extension/src/types/vite-env.d.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
/// <reference types="vite/client" />

// eslint-disable-next-line @typescript-eslint/no-empty-object-type
interface ImportMetaEnv {}
interface ImportMetaEnv { }

interface ImportMeta {
readonly env: ImportMetaEnv
}

declare const __APP_VERSION__: string
declare const __APP_NAME__: string
declare const __AI_SERVICES_JSON__: unknown[]
33 changes: 19 additions & 14 deletions packages/extension/vite.config.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import fs from "fs"
import path from "path"
import { defineConfig, loadEnv } from "vite"
import react from "@vitejs/plugin-react"
Expand Down Expand Up @@ -106,6 +107,10 @@ export default defineConfig(({ mode }) => {
define: {
__APP_NAME__: JSON.stringify(packageJson.name),
__APP_VERSION__: JSON.stringify(packageJson.version),
__AI_SERVICES_JSON__: fs.readFileSync(
path.resolve(__dirname, "../hub/public/data/ai-services.json"),
"utf-8",
),
},
resolve: {
alias: {
Expand All @@ -118,20 +123,20 @@ export default defineConfig(({ mode }) => {
pure:
mode === "production"
? [
"console.log",
"console.debug",
"console.info",
"console.trace",
"console.dir",
"console.count",
"console.countReset",
"console.group",
"console.groupCollapsed",
"console.groupEnd",
"console.time",
"console.timeEnd",
"console.timeLog",
]
"console.log",
"console.debug",
"console.info",
"console.trace",
"console.dir",
"console.count",
"console.countReset",
"console.group",
"console.groupCollapsed",
"console.groupEnd",
"console.time",
"console.timeEnd",
"console.timeLog",
]
: [],
},
build: {
Expand Down
5 changes: 5 additions & 0 deletions packages/extension/vitest.config.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import fs from "fs"
import { defineConfig, mergeConfig } from "vitest/config"
import { resolve } from "path"
import packageJson from "./package.json"
Expand All @@ -13,6 +14,10 @@ export default mergeConfig(
define: {
__APP_NAME__: JSON.stringify(packageJson.name),
__APP_VERSION__: JSON.stringify(packageJson.version),
__AI_SERVICES_JSON__: fs.readFileSync(
resolve(__dirname, "../hub/public/data/ai-services.json"),
"utf-8",
),
},
resolve: {
alias: {
Expand Down
11 changes: 3 additions & 8 deletions packages/hub/public/data/ai-services.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,7 @@
"name": "ChatGPT",
"url": "https://chatgpt.com",
"faviconUrl": "https://chatgpt.com/favicon.ico",
"inputSelectors": [
"#prompt-textarea",
"[data-testid='prompt-textarea']"
],
"inputSelectors": ["#prompt-textarea", "[data-testid='prompt-textarea']"],
"submitSelectors": [
"form button.composer-submit-button-color",
"button#composer-submit-button",
Expand All @@ -19,9 +16,7 @@
"name": "Gemini",
"url": "https://gemini.google.com/app",
"faviconUrl": "https://www.gstatic.com/lamda/images/gemini_sparkle_aurora_33f86dc0c0257da337c63.svg",
"inputSelectors": [
".ql-editor[contenteditable='true']"
],
"inputSelectors": [".ql-editor[contenteditable='true']"],
"submitSelectors": [
"button.send-button",
"button mat-icon[fonticon='send']"
Expand All @@ -31,7 +26,7 @@
"id": "claude",
"name": "Claude",
"url": "https://claude.ai/new",
"faviconUrl": "https://claude.ai/favicon.ico",
"faviconUrl": "https://favicon.im/claude.ai",
"inputSelectors": [
"div[contenteditable='true'][aria-label]",
"div[contenteditable='true'].ProseMirror"
Expand Down
Loading