commit
bd84b90e5f
3
.gitignore
vendored
3
.gitignore
vendored
@ -42,4 +42,7 @@ keys.json
|
|||||||
|
|
||||||
# typescript
|
# typescript
|
||||||
.tsbuildinfo
|
.tsbuildinfo
|
||||||
|
# WXT
|
||||||
.wxt
|
.wxt
|
||||||
|
# WebStorm
|
||||||
|
.idea
|
13
package.json
13
package.json
@ -5,12 +5,12 @@
|
|||||||
"description": "Use your locally running AI models to assist you in your web browsing.",
|
"description": "Use your locally running AI models to assist you in your web browsing.",
|
||||||
"author": "n4ze3m",
|
"author": "n4ze3m",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "wxt",
|
"dev": "cross-env TARGET=chrome wxt",
|
||||||
"dev:firefox": "wxt -b firefox",
|
"dev:firefox": "cross-env TARGET=firefox wxt -b firefox",
|
||||||
"build": "wxt build",
|
"build": "cross-env TARGET=chrome wxt build",
|
||||||
"build:firefox": "wxt build -b firefox",
|
"build:firefox": "cross-env TARGET=chrome cross-env TARGET=firefox wxt build -b firefox",
|
||||||
"zip": "wxt zip",
|
"zip": "cross-env TARGET=chrome wxt zip",
|
||||||
"zip:firefox": "wxt zip -b firefox",
|
"zip:firefox": "cross-env TARGET=firefox wxt zip -b firefox",
|
||||||
"compile": "tsc --noEmit",
|
"compile": "tsc --noEmit",
|
||||||
"postinstall": "wxt prepare"
|
"postinstall": "wxt prepare"
|
||||||
},
|
},
|
||||||
@ -66,6 +66,7 @@
|
|||||||
"@types/react-syntax-highlighter": "^15.5.11",
|
"@types/react-syntax-highlighter": "^15.5.11",
|
||||||
"@types/turndown": "^5.0.4",
|
"@types/turndown": "^5.0.4",
|
||||||
"autoprefixer": "^10.4.17",
|
"autoprefixer": "^10.4.17",
|
||||||
|
"cross-env": "^7.0.3",
|
||||||
"postcss": "^8.4.33",
|
"postcss": "^8.4.33",
|
||||||
"prettier": "3.2.4",
|
"prettier": "3.2.4",
|
||||||
"tailwindcss": "^3.4.1",
|
"tailwindcss": "^3.4.1",
|
||||||
|
@ -23,7 +23,7 @@ Click the button below to deploy the code to Railway.
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/n4ze3m/page-share-app.git
|
git clone https://github.com/n4ze3m/page-share-app.git
|
||||||
cd page-assist-app
|
cd page-share-app
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Run the server
|
2. Run the server
|
||||||
|
@ -60,7 +60,7 @@ export const ModelsBody = () => {
|
|||||||
|
|
||||||
form.reset()
|
form.reset()
|
||||||
|
|
||||||
chrome.runtime.sendMessage({
|
browser.runtime.sendMessage({
|
||||||
type: "pull_model",
|
type: "pull_model",
|
||||||
modelName
|
modelName
|
||||||
})
|
})
|
||||||
|
@ -11,7 +11,7 @@ export const AboutApp = () => {
|
|||||||
const { data, status } = useQuery({
|
const { data, status } = useQuery({
|
||||||
queryKey: ["fetchOllamURL"],
|
queryKey: ["fetchOllamURL"],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const chromeVersion = chrome.runtime.getManifest().version
|
const chromeVersion = browser.runtime.getManifest().version
|
||||||
try {
|
try {
|
||||||
const url = await getOllamaURL()
|
const url = await getOllamaURL()
|
||||||
const req = await fetch(`${cleanUrl(url)}/api/version`)
|
const req = await fetch(`${cleanUrl(url)}/api/version`)
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
import { getOllamaURL, isOllamaRunning } from "../services/ollama"
|
import { getOllamaURL, isOllamaRunning } from "../services/ollama"
|
||||||
import { Storage } from "@plasmohq/storage"
|
import { browser } from "wxt/browser"
|
||||||
|
import { setBadgeBackgroundColor, setBadgeText, setTitle } from "@/utils/action"
|
||||||
const progressHuman = (completed: number, total: number) => {
|
const progressHuman = (completed: number, total: number) => {
|
||||||
return ((completed / total) * 100).toFixed(0) + "%"
|
return ((completed / total) * 100).toFixed(0) + "%"
|
||||||
}
|
}
|
||||||
|
|
||||||
const clearBadge = () => {
|
const clearBadge = () => {
|
||||||
chrome.action.setBadgeText({ text: "" })
|
setBadgeText({ text: "" })
|
||||||
chrome.action.setTitle({ title: "" })
|
setTitle({ title: "" })
|
||||||
}
|
}
|
||||||
const streamDownload = async (url: string, model: string) => {
|
const streamDownload = async (url: string, model: string) => {
|
||||||
url += "/api/pull"
|
url += "/api/pull"
|
||||||
@ -42,16 +42,16 @@ const streamDownload = async (url: string, model: string) => {
|
|||||||
completed?: number
|
completed?: number
|
||||||
}
|
}
|
||||||
if (json.total && json.completed) {
|
if (json.total && json.completed) {
|
||||||
chrome.action.setBadgeText({
|
setBadgeText({
|
||||||
text: progressHuman(json.completed, json.total)
|
text: progressHuman(json.completed, json.total)
|
||||||
})
|
})
|
||||||
chrome.action.setBadgeBackgroundColor({ color: "#0000FF" })
|
setBadgeBackgroundColor({ color: "#0000FF" })
|
||||||
} else {
|
} else {
|
||||||
chrome.action.setBadgeText({ text: "🏋️♂️" })
|
setBadgeText({ text: "🏋️♂️" })
|
||||||
chrome.action.setBadgeBackgroundColor({ color: "#FFFFFF" })
|
setBadgeBackgroundColor({ color: "#FFFFFF" })
|
||||||
}
|
}
|
||||||
|
|
||||||
chrome.action.setTitle({ title: json.status })
|
setTitle({ title: json.status })
|
||||||
|
|
||||||
if (json.status === "success") {
|
if (json.status === "success") {
|
||||||
isSuccess = true
|
isSuccess = true
|
||||||
@ -62,13 +62,13 @@ const streamDownload = async (url: string, model: string) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isSuccess) {
|
if (isSuccess) {
|
||||||
chrome.action.setBadgeText({ text: "✅" })
|
setBadgeText({ text: "✅" })
|
||||||
chrome.action.setBadgeBackgroundColor({ color: "#00FF00" })
|
setBadgeBackgroundColor({ color: "#00FF00" })
|
||||||
chrome.action.setTitle({ title: "Model pulled successfully" })
|
setTitle({ title: "Model pulled successfully" })
|
||||||
} else {
|
} else {
|
||||||
chrome.action.setBadgeText({ text: "❌" })
|
setBadgeText({ text: "❌" })
|
||||||
chrome.action.setBadgeBackgroundColor({ color: "#FF0000" })
|
setBadgeBackgroundColor({ color: "#FF0000" })
|
||||||
chrome.action.setTitle({ title: "Model pull failed" })
|
setTitle({ title: "Model pull failed" })
|
||||||
}
|
}
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@ -77,29 +77,18 @@ const streamDownload = async (url: string, model: string) => {
|
|||||||
}
|
}
|
||||||
export default defineBackground({
|
export default defineBackground({
|
||||||
main() {
|
main() {
|
||||||
const storage = new Storage()
|
browser.runtime.onMessage.addListener(async (message) => {
|
||||||
|
|
||||||
chrome.runtime.onMessage.addListener(async (message) => {
|
|
||||||
if (message.type === "sidepanel") {
|
if (message.type === "sidepanel") {
|
||||||
chrome.tabs.query(
|
browser.sidebarAction.open()
|
||||||
{ active: true, currentWindow: true },
|
|
||||||
async (tabs) => {
|
|
||||||
const tab = tabs[0]
|
|
||||||
chrome.sidePanel.open({
|
|
||||||
// tabId: tab.id!,
|
|
||||||
windowId: tab.windowId!
|
|
||||||
})
|
|
||||||
}
|
|
||||||
)
|
|
||||||
} else if (message.type === "pull_model") {
|
} else if (message.type === "pull_model") {
|
||||||
const ollamaURL = await getOllamaURL()
|
const ollamaURL = await getOllamaURL()
|
||||||
|
|
||||||
const isRunning = await isOllamaRunning()
|
const isRunning = await isOllamaRunning()
|
||||||
|
|
||||||
if (!isRunning) {
|
if (!isRunning) {
|
||||||
chrome.action.setBadgeText({ text: "E" })
|
setBadgeText({ text: "E" })
|
||||||
chrome.action.setBadgeBackgroundColor({ color: "#FF0000" })
|
setBadgeBackgroundColor({ color: "#FF0000" })
|
||||||
chrome.action.setTitle({ title: "Ollama is not running" })
|
setTitle({ title: "Ollama is not running" })
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
clearBadge()
|
clearBadge()
|
||||||
}, 5000)
|
}, 5000)
|
||||||
@ -109,47 +98,73 @@ export default defineBackground({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
chrome.action.onClicked.addListener((tab) => {
|
if (import.meta.env.BROWSER === "chrome") {
|
||||||
chrome.tabs.create({ url: chrome.runtime.getURL("options.html") })
|
chrome.action.onClicked.addListener((tab) => {
|
||||||
})
|
browser.tabs.create({ url: browser.runtime.getURL("/options.html") })
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
browser.browserAction.onClicked.addListener((tab) => {
|
||||||
|
console.log("browser.browserAction.onClicked.addListener")
|
||||||
|
browser.tabs.create({ url: browser.runtime.getURL("/options.html") })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
chrome.commands.onCommand.addListener((command) => {
|
browser.contextMenus.create({
|
||||||
switch (command) {
|
id: "open-side-panel-pa",
|
||||||
case "execute_side_panel":
|
title: browser.i18n.getMessage("openSidePanelToChat"),
|
||||||
|
contexts: ["all"]
|
||||||
|
})
|
||||||
|
if (import.meta.env.BROWSER === "chrome") {
|
||||||
|
browser.contextMenus.onClicked.addListener((info, tab) => {
|
||||||
|
if (info.menuItemId === "open-side-panel-pa") {
|
||||||
chrome.tabs.query(
|
chrome.tabs.query(
|
||||||
{ active: true, currentWindow: true },
|
{ active: true, currentWindow: true },
|
||||||
async (tabs) => {
|
async (tabs) => {
|
||||||
const tab = tabs[0]
|
const tab = tabs[0]
|
||||||
chrome.sidePanel.open({
|
chrome.sidePanel.open({
|
||||||
windowId: tab.windowId!
|
tabId: tab.id!
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
break
|
}
|
||||||
default:
|
})
|
||||||
break
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
chrome.contextMenus.create({
|
browser.commands.onCommand.addListener((command) => {
|
||||||
id: "open-side-panel-pa",
|
switch (command) {
|
||||||
title: browser.i18n.getMessage("openSidePanelToChat"),
|
case "execute_side_panel":
|
||||||
contexts: ["all"]
|
chrome.tabs.query(
|
||||||
})
|
{ active: true, currentWindow: true },
|
||||||
|
async (tabs) => {
|
||||||
|
const tab = tabs[0]
|
||||||
|
chrome.sidePanel.open({
|
||||||
|
tabId: tab.id!
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
chrome.contextMenus.onClicked.addListener((info, tab) => {
|
if (import.meta.env.BROWSER === "firefox") {
|
||||||
if (info.menuItemId === "open-side-panel-pa") {
|
browser.contextMenus.onClicked.addListener((info, tab) => {
|
||||||
chrome.tabs.query(
|
if (info.menuItemId === "open-side-panel-pa") {
|
||||||
{ active: true, currentWindow: true },
|
browser.sidebarAction.toggle()
|
||||||
async (tabs) => {
|
}
|
||||||
const tab = tabs[0]
|
})
|
||||||
chrome.sidePanel.open({
|
|
||||||
tabId: tab.id!
|
browser.commands.onCommand.addListener((command) => {
|
||||||
})
|
switch (command) {
|
||||||
}
|
case "execute_side_panel":
|
||||||
)
|
browser.sidebarAction.toggle()
|
||||||
}
|
break
|
||||||
})
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
},
|
},
|
||||||
persistent: true
|
persistent: true
|
||||||
})
|
})
|
||||||
|
@ -9,7 +9,7 @@ export default defineContentScript({
|
|||||||
`[Page Assist Extension] Pulling ${modelName} model. For more details, check the extension icon.`
|
`[Page Assist Extension] Pulling ${modelName} model. For more details, check the extension icon.`
|
||||||
)
|
)
|
||||||
|
|
||||||
await chrome.runtime.sendMessage({
|
await browser.runtime.sendMessage({
|
||||||
type: "pull_model",
|
type: "pull_model",
|
||||||
modelName
|
modelName
|
||||||
})
|
})
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
<title>Page Assist - A Web UI for Local AI Models</title>
|
<title>Page Assist - A Web UI for Local AI Models</title>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<meta name="manifest.type" content="browser_action" />
|
<meta name="manifest.type" content="browser_action" />
|
||||||
|
<meta name="manifest.open_at_install" content="false" />
|
||||||
<link href="~/assets/tailwind.css" rel="stylesheet" />
|
<link href="~/assets/tailwind.css" rel="stylesheet" />
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
</head>
|
</head>
|
||||||
|
@ -2,7 +2,6 @@ import { useEffect, useState } from "react"
|
|||||||
import { notification } from "antd"
|
import { notification } from "antd"
|
||||||
import { getVoice, isSSMLEnabled } from "@/services/tts"
|
import { getVoice, isSSMLEnabled } from "@/services/tts"
|
||||||
import { markdownToSSML } from "@/utils/markdown-to-ssml"
|
import { markdownToSSML } from "@/utils/markdown-to-ssml"
|
||||||
|
|
||||||
type VoiceOptions = {
|
type VoiceOptions = {
|
||||||
utterance: string
|
utterance: string
|
||||||
}
|
}
|
||||||
@ -17,16 +16,28 @@ export const useTTS = () => {
|
|||||||
if (isSSML) {
|
if (isSSML) {
|
||||||
utterance = markdownToSSML(utterance)
|
utterance = markdownToSSML(utterance)
|
||||||
}
|
}
|
||||||
chrome.tts.speak(utterance, {
|
if (import.meta.env.BROWSER === "chrome") {
|
||||||
voiceName: voice,
|
chrome.tts.speak(utterance, {
|
||||||
onEvent(event) {
|
voiceName: voice,
|
||||||
if (event.type === "start") {
|
onEvent(event) {
|
||||||
setIsSpeaking(true)
|
if (event.type === "start") {
|
||||||
} else if (event.type === "end") {
|
setIsSpeaking(true)
|
||||||
setIsSpeaking(false)
|
} else if (event.type === "end") {
|
||||||
|
setIsSpeaking(false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// browser tts
|
||||||
|
window.speechSynthesis.speak(new SpeechSynthesisUtterance(utterance))
|
||||||
|
window.speechSynthesis.onvoiceschanged = () => {
|
||||||
|
const voices = window.speechSynthesis.getVoices()
|
||||||
|
const voice = voices.find((v) => v.name === voice)
|
||||||
|
const utter = new SpeechSynthesisUtterance(utterance)
|
||||||
|
utter.voice = voice
|
||||||
|
window.speechSynthesis.speak(utter)
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
notification.error({
|
notification.error({
|
||||||
message: "Error",
|
message: "Error",
|
||||||
@ -36,7 +47,11 @@ export const useTTS = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const cancel = () => {
|
const cancel = () => {
|
||||||
chrome.tts.stop()
|
if (import.meta.env.BROWSER === "chrome") {
|
||||||
|
chrome.tts.stop()
|
||||||
|
} else {
|
||||||
|
window.speechSynthesis.cancel()
|
||||||
|
}
|
||||||
setIsSpeaking(false)
|
setIsSpeaking(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ import {
|
|||||||
isTweet,
|
isTweet,
|
||||||
isTwitterTimeline,
|
isTwitterTimeline,
|
||||||
parseTweet,
|
parseTweet,
|
||||||
parseTwitterTimeline,
|
parseTwitterTimeline
|
||||||
} from "@/parser/twitter"
|
} from "@/parser/twitter"
|
||||||
import { isGoogleDocs, parseGoogleDocs } from "@/parser/google-docs"
|
import { isGoogleDocs, parseGoogleDocs } from "@/parser/google-docs"
|
||||||
import { cleanUnwantedUnicode } from "@/utils/clean"
|
import { cleanUnwantedUnicode } from "@/utils/clean"
|
||||||
@ -24,18 +24,35 @@ const _getHtml = () => {
|
|||||||
|
|
||||||
export const getDataFromCurrentTab = async () => {
|
export const getDataFromCurrentTab = async () => {
|
||||||
const result = new Promise((resolve) => {
|
const result = new Promise((resolve) => {
|
||||||
chrome.tabs.query({ active: true, currentWindow: true }, async (tabs) => {
|
if (import.meta.env.BROWSER === "chrome") {
|
||||||
const tab = tabs[0]
|
chrome.tabs.query({ active: true, currentWindow: true }, async (tabs) => {
|
||||||
|
const tab = tabs[0]
|
||||||
|
|
||||||
const data = await chrome.scripting.executeScript({
|
const data = await chrome.scripting.executeScript({
|
||||||
target: { tabId: tab.id },
|
target: { tabId: tab.id },
|
||||||
func: _getHtml
|
func: _getHtml
|
||||||
|
})
|
||||||
|
|
||||||
|
if (data.length > 0) {
|
||||||
|
resolve(data[0].result)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
} else {
|
||||||
|
browser.tabs
|
||||||
|
.query({ active: true, currentWindow: true })
|
||||||
|
.then(async (tabs) => {
|
||||||
|
const tab = tabs[0]
|
||||||
|
|
||||||
if (data.length > 0) {
|
const data = await browser.scripting.executeScript({
|
||||||
resolve(data[0].result)
|
target: { tabId: tab.id },
|
||||||
}
|
func: _getHtml
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (data.length > 0) {
|
||||||
|
resolve(data[0].result)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}) as Promise<{
|
}) as Promise<{
|
||||||
url: string
|
url: string
|
||||||
content: string
|
content: string
|
||||||
|
@ -1,31 +1,51 @@
|
|||||||
export const chromeRunTime = async function (domain: string) {
|
export const chromeRunTime = async function (domain: string) {
|
||||||
if (typeof chrome !== "undefined" && chrome.runtime && chrome.runtime.id) {
|
if (browser.runtime && browser.runtime.id) {
|
||||||
const url = new URL(domain)
|
if (import.meta.env.BROWSER === "chrome") {
|
||||||
const domains = [url.hostname]
|
const url = new URL(domain)
|
||||||
const rules = [
|
const domains = [url.hostname]
|
||||||
{
|
const rules = [
|
||||||
id: 1,
|
{
|
||||||
priority: 1,
|
id: 1,
|
||||||
condition: {
|
priority: 1,
|
||||||
requestDomains: domains
|
condition: {
|
||||||
},
|
requestDomains: domains
|
||||||
action: {
|
},
|
||||||
type: "modifyHeaders",
|
action: {
|
||||||
requestHeaders: [
|
type: "modifyHeaders",
|
||||||
{
|
requestHeaders: [
|
||||||
header: "Origin",
|
{
|
||||||
operation: "set",
|
header: "Origin",
|
||||||
value: `${url.protocol}//${url.hostname}`
|
operation: "set",
|
||||||
}
|
value: `${url.protocol}//${url.hostname}`
|
||||||
]
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
]
|
||||||
]
|
|
||||||
|
|
||||||
await chrome.declarativeNetRequest.updateDynamicRules({
|
await browser.declarativeNetRequest.updateDynamicRules({
|
||||||
removeRuleIds: rules.map((r) => r.id),
|
removeRuleIds: rules.map((r) => r.id),
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
addRules: rules
|
addRules: rules
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (import.meta.env.BROWSER === "firefox") {
|
||||||
|
const url = new URL(domain)
|
||||||
|
const domains = [`*://${url.hostname}/*`]
|
||||||
|
browser.webRequest.onBeforeSendHeaders.addListener(
|
||||||
|
(details) => {
|
||||||
|
for (let i = 0; i < details.requestHeaders.length; i++) {
|
||||||
|
if (details.requestHeaders[i].name === "Origin") {
|
||||||
|
details.requestHeaders[i].value =
|
||||||
|
`${url.protocol}//${url.hostname}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { requestHeaders: details.requestHeaders }
|
||||||
|
},
|
||||||
|
{ urls: domains },
|
||||||
|
["blocking", "requestHeaders"]
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
|
|
||||||
export const isGoogleDocs = (url: string) => {
|
export const isGoogleDocs = (url: string) => {
|
||||||
const GOOGLE_DOCS_REGEX = /docs\.google\.com\/document/g
|
const GOOGLE_DOCS_REGEX = /docs\.google\.com\/document/g
|
||||||
return GOOGLE_DOCS_REGEX.test(url)
|
return GOOGLE_DOCS_REGEX.test(url)
|
||||||
}
|
}
|
||||||
|
|
||||||
const getGoogleDocs = () => {
|
const getGoogleDocs = () => {
|
||||||
@ -96,19 +95,36 @@ const getGoogleDocs = () => {
|
|||||||
|
|
||||||
export const parseGoogleDocs = async () => {
|
export const parseGoogleDocs = async () => {
|
||||||
const result = new Promise((resolve) => {
|
const result = new Promise((resolve) => {
|
||||||
chrome.tabs.query({ active: true, currentWindow: true }, async (tabs) => {
|
if (import.meta.env.BROWSER === "chrome") {
|
||||||
const tab = tabs[0]
|
chrome.tabs.query({ active: true, currentWindow: true }, async (tabs) => {
|
||||||
|
const tab = tabs[0]
|
||||||
|
|
||||||
const data = await chrome.scripting.executeScript({
|
const data = await chrome.scripting.executeScript({
|
||||||
target: { tabId: tab.id },
|
target: { tabId: tab.id },
|
||||||
world: "MAIN",
|
world: "MAIN",
|
||||||
func: getGoogleDocs
|
func: getGoogleDocs
|
||||||
|
})
|
||||||
|
|
||||||
|
if (data.length > 0) {
|
||||||
|
resolve(data[0].result)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
} else {
|
||||||
|
browser.tabs
|
||||||
|
.query({ active: true, currentWindow: true })
|
||||||
|
.then(async (tabs) => {
|
||||||
|
const tab = tabs[0]
|
||||||
|
|
||||||
if (data.length > 0) {
|
const data = await browser.scripting.executeScript({
|
||||||
resolve(data[0].result)
|
target: { tabId: tab.id },
|
||||||
}
|
func: getGoogleDocs
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (data.length > 0) {
|
||||||
|
resolve(data[0].result)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}) as Promise<{
|
}) as Promise<{
|
||||||
content?: string
|
content?: string
|
||||||
}>
|
}>
|
||||||
|
20
src/services/app.ts
Normal file
20
src/services/app.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { Storage } from "@plasmohq/storage"
|
||||||
|
const storage = new Storage()
|
||||||
|
|
||||||
|
export const isUrlRewriteEnabled = async () => {
|
||||||
|
const enabled = await storage.get("urlRewriteEnabled")
|
||||||
|
return enabled === "true"
|
||||||
|
}
|
||||||
|
|
||||||
|
export const setUrlRewriteEnabled = async (enabled: boolean) => {
|
||||||
|
await storage.set("urlRewriteEnabled", enabled ? "true" : "false")
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getRewriteUrl = async () => {
|
||||||
|
const rewriteUrl = await storage.get("rewriteUrl")
|
||||||
|
return rewriteUrl
|
||||||
|
}
|
||||||
|
|
||||||
|
export const setRewriteUrl = async (url: string) => {
|
||||||
|
await storage.set("rewriteUrl", url)
|
||||||
|
}
|
@ -21,8 +21,16 @@ export const setTTSProvider = async (ttsProvider: string) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const getBrowserTTSVoices = async () => {
|
export const getBrowserTTSVoices = async () => {
|
||||||
const tts = await chrome.tts.getVoices()
|
if (import.meta.env.BROWSER === "chrome") {
|
||||||
return tts
|
const tts = await chrome.tts.getVoices()
|
||||||
|
return tts
|
||||||
|
} else {
|
||||||
|
const tts = await speechSynthesis.getVoices()
|
||||||
|
return tts.map((voice) => ({
|
||||||
|
voiceName: voice.name,
|
||||||
|
lang: voice.lang
|
||||||
|
}))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getVoice = async () => {
|
export const getVoice = async () => {
|
||||||
|
25
src/utils/action.ts
Normal file
25
src/utils/action.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { browser } from "wxt/browser"
|
||||||
|
|
||||||
|
export const setTitle = ({ title }: { title: string }) => {
|
||||||
|
if (import.meta.env.BROWSER === "chrome") {
|
||||||
|
chrome.action.setTitle({ title })
|
||||||
|
} else {
|
||||||
|
browser.browserAction.setTitle({ title })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const setBadgeBackgroundColor = ({ color }: { color: string }) => {
|
||||||
|
if (import.meta.env.BROWSER === "chrome") {
|
||||||
|
chrome.action.setBadgeBackgroundColor({ color })
|
||||||
|
} else {
|
||||||
|
browser.browserAction.setBadgeBackgroundColor({ color })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const setBadgeText = ({ text }: { text: string }) => {
|
||||||
|
if (import.meta.env.BROWSER === "chrome") {
|
||||||
|
chrome.action.setBadgeText({ text })
|
||||||
|
} else {
|
||||||
|
browser.browserAction.setBadgeText({ text })
|
||||||
|
}
|
||||||
|
}
|
@ -2,35 +2,70 @@ import { defineConfig } from "wxt"
|
|||||||
import react from "@vitejs/plugin-react"
|
import react from "@vitejs/plugin-react"
|
||||||
import topLevelAwait from "vite-plugin-top-level-await"
|
import topLevelAwait from "vite-plugin-top-level-await"
|
||||||
|
|
||||||
|
const chromeMV3Permissions = [
|
||||||
|
"storage",
|
||||||
|
"sidePanel",
|
||||||
|
"activeTab",
|
||||||
|
"scripting",
|
||||||
|
"declarativeNetRequest",
|
||||||
|
"action",
|
||||||
|
"unlimitedStorage",
|
||||||
|
"contextMenus",
|
||||||
|
"tts"
|
||||||
|
]
|
||||||
|
|
||||||
|
const firefoxMV2Permissions = [
|
||||||
|
"storage",
|
||||||
|
"activeTab",
|
||||||
|
"scripting",
|
||||||
|
"unlimitedStorage",
|
||||||
|
"contextMenus",
|
||||||
|
"webRequest",
|
||||||
|
"webRequestBlocking",
|
||||||
|
"http://*/*",
|
||||||
|
"https://*/*",
|
||||||
|
"file://*/*"
|
||||||
|
]
|
||||||
|
|
||||||
// See https://wxt.dev/api/config.html
|
// See https://wxt.dev/api/config.html
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
vite: () => ({
|
vite: () => ({
|
||||||
plugins: [react(),
|
plugins: [
|
||||||
|
react(),
|
||||||
topLevelAwait({
|
topLevelAwait({
|
||||||
promiseExportName: '__tla',
|
promiseExportName: "__tla",
|
||||||
promiseImportName: i => `__tla_${i}`,
|
promiseImportName: (i) => `__tla_${i}`
|
||||||
}),
|
})
|
||||||
],
|
],
|
||||||
build: {
|
build: {
|
||||||
rollupOptions: {
|
rollupOptions: {
|
||||||
external: [
|
external: ["langchain", "@langchain/community"]
|
||||||
"langchain",
|
|
||||||
"@langchain/community",
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
entrypointsDir: "entries",
|
entrypointsDir: "entries",
|
||||||
srcDir: "src",
|
srcDir: "src",
|
||||||
outDir: "build",
|
outDir: "build",
|
||||||
|
|
||||||
manifest: {
|
manifest: {
|
||||||
version: "1.1.6",
|
version: "1.1.7",
|
||||||
name: '__MSG_extName__',
|
name: "__MSG_extName__",
|
||||||
description: '__MSG_extDescription__',
|
description: "__MSG_extDescription__",
|
||||||
default_locale: 'en',
|
default_locale: "en",
|
||||||
action: {},
|
action: {},
|
||||||
author: "n4ze3m",
|
author: "n4ze3m",
|
||||||
host_permissions: ["http://*/*", "https://*/*", "file://*/*"],
|
browser_specific_settings:
|
||||||
|
process.env.TARGET === "firefox"
|
||||||
|
? {
|
||||||
|
gecko: {
|
||||||
|
id: "page-assist@n4ze3m"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
host_permissions:
|
||||||
|
process.env.TARGET !== "firefox"
|
||||||
|
? ["http://*/*", "https://*/*", "file://*/*"]
|
||||||
|
: undefined,
|
||||||
commands: {
|
commands: {
|
||||||
_execute_action: {
|
_execute_action: {
|
||||||
suggested_key: {
|
suggested_key: {
|
||||||
@ -44,16 +79,9 @@ export default defineConfig({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
permissions: [
|
permissions:
|
||||||
"storage",
|
process.env.TARGET === "firefox"
|
||||||
"sidePanel",
|
? firefoxMV2Permissions
|
||||||
"activeTab",
|
: chromeMV3Permissions
|
||||||
"scripting",
|
|
||||||
"declarativeNetRequest",
|
|
||||||
"action",
|
|
||||||
"unlimitedStorage",
|
|
||||||
"contextMenus",
|
|
||||||
"tts"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
Loading…
x
Reference in New Issue
Block a user