Commit 54a246cc authored by Domi's avatar Domi

feat: support more language

parent df56514b
{ {
"tabWidth": 2, "tabWidth": 2,
"semi": false,
"useTabs": false "useTabs": false
} }
# Anything Copilot - Any web page as copilot <div align="center">
<img width="200" src="public/logo.svg" alt="Logo">
<h1>Anything Copilot - Any web page as copilot</h1>
<p>Use the official ChatGPT website or any other webpage for free as your AI copilot, including GPTs, GPT-4, or any new features. </p>
</div>
Use the official ChatGPT website or any other webpage for free as your AI copilot, including GPTs, GPT-4, or any new features. ## Install/Download - Edge Add-ons & Chrome web store
- [Edge Add-ons](https://microsoftedge.microsoft.com/addons/detail/anything-copilot/lbeehbkcmjaopnlccpjcdgamcabhnanl) - [Edge Add-ons - Anything Copilot](https://microsoftedge.microsoft.com/addons/detail/anything-copilot/lbeehbkcmjaopnlccpjcdgamcabhnanl)
- [Chrome web store](https://chromewebstore.google.com/u/1/detail/anything-copilot-any-web/lilckelmopbcffmglfmfhelaajhjpcff) - [Chrome web store - Anything Copilot](https://chromewebstore.google.com/u/1/detail/anything-copilot-any-web/lilckelmopbcffmglfmfhelaajhjpcff)
This template should help get you started developing with Vue 3 in Vite. Open any web page in a Copilot window and seamlessly multitask with other pages or applications.
## Screenshots & Video
<table>
<tr>
<td>
<img src="docs/assets/1280x800_1.png" alt="Anything Copilot screenshot 1" >
</td>
<td>
<img src="docs/assets/1280x800_2.png" alt="Anything Copilot screenshot 1" >
</td>
</tr>
</table>
[📺 Youtube - Anything Copilot demo](https://youtu.be/RnOUGg-WiH0?si=8YHPLGvczmUTsVqU)
## Recommended IDE Setup ## Recommended IDE Setup
...@@ -18,8 +37,8 @@ TypeScript cannot handle type information for `.vue` imports by default, so we r ...@@ -18,8 +37,8 @@ TypeScript cannot handle type information for `.vue` imports by default, so we r
If the standalone TypeScript plugin doesn't feel fast enough to you, Volar has also implemented a [Take Over Mode](https://github.com/johnsoncodehk/volar/discussions/471#discussioncomment-1361669) that is more performant. You can enable it by the following steps: If the standalone TypeScript plugin doesn't feel fast enough to you, Volar has also implemented a [Take Over Mode](https://github.com/johnsoncodehk/volar/discussions/471#discussioncomment-1361669) that is more performant. You can enable it by the following steps:
1. Disable the built-in TypeScript Extension 1. Disable the built-in TypeScript Extension
1) Run `Extensions: Show Built-in Extensions` from VSCode's command palette 1. Run `Extensions: Show Built-in Extensions` from VSCode's command palette
2) Find `TypeScript and JavaScript Language Features`, right click and select `Disable (Workspace)` 2. Find `TypeScript and JavaScript Language Features`, right click and select `Disable (Workspace)`
2. Reload the VSCode window by running `Developer: Reload Window` from the command palette. 2. Reload the VSCode window by running `Developer: Reload Window` from the command palette.
## Customize configuration ## Customize configuration
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<link rel="icon" href="/favicon.ico"> <link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vite App</title> <title>Anything Copilot</title>
</head> </head>
<body> <body>
<div id="app"></div> <div id="app"></div>
......
...@@ -8,7 +8,7 @@ const manifest = { ...@@ -8,7 +8,7 @@ const manifest = {
// short_name: "__MSG_short_name__", // short_name: "__MSG_short_name__",
// no more than 132 characters // no more than 132 characters
description: "__MSG_description__", description: "__MSG_description__",
version: "1.1.1", version: "1.1.2",
action: { action: {
default_icon: { default_icon: {
16: "logo.png", 16: "logo.png",
......
...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
"vue-i18n": "^9.7.0" "vue-i18n": "^9.7.0"
}, },
"devDependencies": { "devDependencies": {
"@intlify/unplugin-vue-i18n": "^1.5.0",
"@tsconfig/node18": "^18.2.2", "@tsconfig/node18": "^18.2.2",
"@types/chrome": "^0.0.251", "@types/chrome": "^0.0.251",
"@types/lodash-es": "^4.17.11", "@types/lodash-es": "^4.17.11",
...@@ -817,6 +818,35 @@ ...@@ -817,6 +818,35 @@
"node": ">=12" "node": ">=12"
} }
}, },
"node_modules/@intlify/bundle-utils": {
"version": "7.4.0",
"resolved": "https://registry.npmjs.org/@intlify/bundle-utils/-/bundle-utils-7.4.0.tgz",
"integrity": "sha512-AQfjBe2HUxzyN8ignIk3WhhSuVcSuirgzOzkd17nb337rCbI4Gv/t1R60UUyIqFoFdviLb/wLcDUzTD/xXjv9w==",
"dev": true,
"dependencies": {
"@intlify/message-compiler": "^9.4.0",
"@intlify/shared": "^9.4.0",
"acorn": "^8.8.2",
"escodegen": "^2.0.0",
"estree-walker": "^2.0.2",
"jsonc-eslint-parser": "^2.3.0",
"magic-string": "^0.30.0",
"mlly": "^1.2.0",
"source-map-js": "^1.0.1",
"yaml-eslint-parser": "^1.2.2"
},
"engines": {
"node": ">= 14.16"
},
"peerDependenciesMeta": {
"petite-vue-i18n": {
"optional": true
},
"vue-i18n": {
"optional": true
}
}
},
"node_modules/@intlify/core-base": { "node_modules/@intlify/core-base": {
"version": "9.7.0", "version": "9.7.0",
"resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-9.7.0.tgz", "resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-9.7.0.tgz",
...@@ -858,6 +888,45 @@ ...@@ -858,6 +888,45 @@
"url": "https://github.com/sponsors/kazupon" "url": "https://github.com/sponsors/kazupon"
} }
}, },
"node_modules/@intlify/unplugin-vue-i18n": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/@intlify/unplugin-vue-i18n/-/unplugin-vue-i18n-1.5.0.tgz",
"integrity": "sha512-jW0MCCdwxybxcwjEfCunAcKjVoxyO3i+cnLL6v+MNGRLUHqrpELF6zQAJUhgAK2afhY7mCliy8RxTFWKdXm26w==",
"dev": true,
"dependencies": {
"@intlify/bundle-utils": "^7.4.0",
"@intlify/shared": "^9.4.0",
"@rollup/pluginutils": "^5.0.2",
"@vue/compiler-sfc": "^3.2.47",
"debug": "^4.3.3",
"fast-glob": "^3.2.12",
"js-yaml": "^4.1.0",
"json5": "^2.2.3",
"pathe": "^1.0.0",
"picocolors": "^1.0.0",
"source-map-js": "^1.0.2",
"unplugin": "^1.1.0"
},
"engines": {
"node": ">= 14.16"
},
"peerDependencies": {
"petite-vue-i18n": "*",
"vue-i18n": "*",
"vue-i18n-bridge": "*"
},
"peerDependenciesMeta": {
"petite-vue-i18n": {
"optional": true
},
"vue-i18n": {
"optional": true
},
"vue-i18n-bridge": {
"optional": true
}
}
},
"node_modules/@jridgewell/gen-mapping": { "node_modules/@jridgewell/gen-mapping": {
"version": "0.3.3", "version": "0.3.3",
"dev": true, "dev": true,
...@@ -935,6 +1004,28 @@ ...@@ -935,6 +1004,28 @@
"node": ">= 8" "node": ">= 8"
} }
}, },
"node_modules/@rollup/pluginutils": {
"version": "5.0.5",
"resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.0.5.tgz",
"integrity": "sha512-6aEYR910NyP73oHiJglti74iRyOwgFU4x3meH/H8OJx6Ry0j6cOVZ5X/wTvub7G7Ao6qaHBEaNsV3GLJkSsF+Q==",
"dev": true,
"dependencies": {
"@types/estree": "^1.0.0",
"estree-walker": "^2.0.2",
"picomatch": "^2.3.1"
},
"engines": {
"node": ">=14.0.0"
},
"peerDependencies": {
"rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0"
},
"peerDependenciesMeta": {
"rollup": {
"optional": true
}
}
},
"node_modules/@tsconfig/node18": { "node_modules/@tsconfig/node18": {
"version": "18.2.2", "version": "18.2.2",
"dev": true, "dev": true,
...@@ -950,6 +1041,12 @@ ...@@ -950,6 +1041,12 @@
"@types/har-format": "*" "@types/har-format": "*"
} }
}, },
"node_modules/@types/estree": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
"integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==",
"dev": true
},
"node_modules/@types/filesystem": { "node_modules/@types/filesystem": {
"version": "0.0.35", "version": "0.0.35",
"resolved": "https://registry.npmjs.org/@types/filesystem/-/filesystem-0.0.35.tgz", "resolved": "https://registry.npmjs.org/@types/filesystem/-/filesystem-0.0.35.tgz",
...@@ -1261,6 +1358,27 @@ ...@@ -1261,6 +1358,27 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/acorn": {
"version": "8.11.2",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz",
"integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==",
"dev": true,
"bin": {
"acorn": "bin/acorn"
},
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/acorn-jsx": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
"integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
"dev": true,
"peerDependencies": {
"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
}
},
"node_modules/ansi-styles": { "node_modules/ansi-styles": {
"version": "3.2.1", "version": "3.2.1",
"dev": true, "dev": true,
...@@ -1297,6 +1415,12 @@ ...@@ -1297,6 +1415,12 @@
"integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==",
"dev": true "dev": true
}, },
"node_modules/argparse": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
"dev": true
},
"node_modules/autoprefixer": { "node_modules/autoprefixer": {
"version": "10.4.16", "version": "10.4.16",
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.16.tgz", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.16.tgz",
...@@ -1689,10 +1813,91 @@ ...@@ -1689,10 +1813,91 @@
"node": ">=0.8.0" "node": ">=0.8.0"
} }
}, },
"node_modules/escodegen": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz",
"integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==",
"dev": true,
"dependencies": {
"esprima": "^4.0.1",
"estraverse": "^5.2.0",
"esutils": "^2.0.2"
},
"bin": {
"escodegen": "bin/escodegen.js",
"esgenerate": "bin/esgenerate.js"
},
"engines": {
"node": ">=6.0"
},
"optionalDependencies": {
"source-map": "~0.6.1"
}
},
"node_modules/eslint-visitor-keys": {
"version": "3.4.3",
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
"integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
"dev": true,
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
},
"funding": {
"url": "https://opencollective.com/eslint"
}
},
"node_modules/espree": {
"version": "9.6.1",
"resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz",
"integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==",
"dev": true,
"dependencies": {
"acorn": "^8.9.0",
"acorn-jsx": "^5.3.2",
"eslint-visitor-keys": "^3.4.1"
},
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
},
"funding": {
"url": "https://opencollective.com/eslint"
}
},
"node_modules/esprima": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
"dev": true,
"bin": {
"esparse": "bin/esparse.js",
"esvalidate": "bin/esvalidate.js"
},
"engines": {
"node": ">=4"
}
},
"node_modules/estraverse": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
"integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
"dev": true,
"engines": {
"node": ">=4.0"
}
},
"node_modules/estree-walker": { "node_modules/estree-walker": {
"version": "2.0.2", "version": "2.0.2",
"license": "MIT" "license": "MIT"
}, },
"node_modules/esutils": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
"integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/fast-glob": { "node_modules/fast-glob": {
"version": "3.3.2", "version": "3.3.2",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz",
...@@ -2003,6 +2208,18 @@ ...@@ -2003,6 +2208,18 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/js-yaml": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
"dev": true,
"dependencies": {
"argparse": "^2.0.1"
},
"bin": {
"js-yaml": "bin/js-yaml.js"
}
},
"node_modules/jsesc": { "node_modules/jsesc": {
"version": "2.5.2", "version": "2.5.2",
"dev": true, "dev": true,
...@@ -2033,6 +2250,63 @@ ...@@ -2033,6 +2250,63 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/jsonc-eslint-parser": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/jsonc-eslint-parser/-/jsonc-eslint-parser-2.4.0.tgz",
"integrity": "sha512-WYDyuc/uFcGp6YtM2H0uKmUwieOuzeE/5YocFJLnLfclZ4inf3mRn8ZVy1s7Hxji7Jxm6Ss8gqpexD/GlKoGgg==",
"dev": true,
"dependencies": {
"acorn": "^8.5.0",
"eslint-visitor-keys": "^3.0.0",
"espree": "^9.0.0",
"semver": "^7.3.5"
},
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
},
"funding": {
"url": "https://github.com/sponsors/ota-meshi"
}
},
"node_modules/jsonc-eslint-parser/node_modules/lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"dev": true,
"dependencies": {
"yallist": "^4.0.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/jsonc-eslint-parser/node_modules/semver": {
"version": "7.5.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
"integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
"dev": true,
"dependencies": {
"lru-cache": "^6.0.0"
},
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/jsonc-eslint-parser/node_modules/yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true
},
"node_modules/jsonc-parser": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz",
"integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==",
"dev": true
},
"node_modules/lilconfig": { "node_modules/lilconfig": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz",
...@@ -2050,6 +2324,12 @@ ...@@ -2050,6 +2324,12 @@
"node": "^12.20.0 || ^14.13.1 || >=16.0.0" "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
} }
}, },
"node_modules/lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"dev": true
},
"node_modules/lodash-es": { "node_modules/lodash-es": {
"version": "4.17.21", "version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
...@@ -2116,6 +2396,18 @@ ...@@ -2116,6 +2396,18 @@
"url": "https://github.com/sponsors/isaacs" "url": "https://github.com/sponsors/isaacs"
} }
}, },
"node_modules/mlly": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/mlly/-/mlly-1.4.2.tgz",
"integrity": "sha512-i/Ykufi2t1EZ6NaPLdfnZk2AX8cs0d+mTzVKuPfqPKPatxLApaBoxJQ9x1/uckXtrS/U5oisPMDkNs0yQTaBRg==",
"dev": true,
"dependencies": {
"acorn": "^8.10.0",
"pathe": "^1.1.1",
"pkg-types": "^1.0.3",
"ufo": "^1.3.0"
}
},
"node_modules/ms": { "node_modules/ms": {
"version": "2.1.2", "version": "2.1.2",
"dev": true, "dev": true,
...@@ -2339,6 +2631,12 @@ ...@@ -2339,6 +2631,12 @@
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
"dev": true "dev": true
}, },
"node_modules/pathe": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.1.tgz",
"integrity": "sha512-d+RQGp0MAYTIaDBIMmOfMwz3E+LOZnxx1HZd5R18mmCZY0QBlK0LDZfPc8FW8Ed2DlvsuE6PRjroDY+wg4+j/Q==",
"dev": true
},
"node_modules/picocolors": { "node_modules/picocolors": {
"version": "1.0.0", "version": "1.0.0",
"license": "ISC" "license": "ISC"
...@@ -2384,6 +2682,17 @@ ...@@ -2384,6 +2682,17 @@
"node": ">= 6" "node": ">= 6"
} }
}, },
"node_modules/pkg-types": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.0.3.tgz",
"integrity": "sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A==",
"dev": true,
"dependencies": {
"jsonc-parser": "^3.2.0",
"mlly": "^1.2.0",
"pathe": "^1.1.0"
}
},
"node_modules/postcss": { "node_modules/postcss": {
"version": "8.4.31", "version": "8.4.31",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
...@@ -2672,6 +2981,16 @@ ...@@ -2672,6 +2981,16 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true,
"optional": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/source-map-js": { "node_modules/source-map-js": {
"version": "1.0.2", "version": "1.0.2",
"license": "BSD-3-Clause", "license": "BSD-3-Clause",
...@@ -2869,11 +3188,29 @@ ...@@ -2869,11 +3188,29 @@
"node": ">=14.17" "node": ">=14.17"
} }
}, },
"node_modules/ufo": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/ufo/-/ufo-1.3.2.tgz",
"integrity": "sha512-o+ORpgGwaYQXgqGDwd+hkS4PuZ3QnmqMMxRuajK/a38L6fTpcE5GPIfrf+L/KemFzfUpeUQc1rRS1iDBozvnFA==",
"dev": true
},
"node_modules/undici-types": { "node_modules/undici-types": {
"version": "5.26.5", "version": "5.26.5",
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/unplugin": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/unplugin/-/unplugin-1.5.1.tgz",
"integrity": "sha512-0QkvG13z6RD+1L1FoibQqnvTwVBXvS4XSPwAyinVgoOCl2jAgwzdUKmEj05o4Lt8xwQI85Hb6mSyYkcAGwZPew==",
"dev": true,
"dependencies": {
"acorn": "^8.11.2",
"chokidar": "^3.5.3",
"webpack-sources": "^3.2.3",
"webpack-virtual-modules": "^0.6.0"
}
},
"node_modules/update-browserslist-db": { "node_modules/update-browserslist-db": {
"version": "1.0.13", "version": "1.0.13",
"dev": true, "dev": true,
...@@ -3066,6 +3403,21 @@ ...@@ -3066,6 +3403,21 @@
"dev": true, "dev": true,
"license": "ISC" "license": "ISC"
}, },
"node_modules/webpack-sources": {
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz",
"integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==",
"dev": true,
"engines": {
"node": ">=10.13.0"
}
},
"node_modules/webpack-virtual-modules": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.1.tgz",
"integrity": "sha512-poXpCylU7ExuvZK8z+On3kX+S8o/2dQ/SVYueKA0D4WEMXROXgY8Ez50/bQEUmvoSMMrWcrJqCHuhAbsiwg7Dg==",
"dev": true
},
"node_modules/which": { "node_modules/which": {
"version": "2.0.2", "version": "2.0.2",
"dev": true, "dev": true,
...@@ -3099,6 +3451,23 @@ ...@@ -3099,6 +3451,23 @@
"engines": { "engines": {
"node": ">= 14" "node": ">= 14"
} }
},
"node_modules/yaml-eslint-parser": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/yaml-eslint-parser/-/yaml-eslint-parser-1.2.2.tgz",
"integrity": "sha512-pEwzfsKbTrB8G3xc/sN7aw1v6A6c/pKxLAkjclnAyo5g5qOh6eL9WGu0o3cSDQZKrTNk4KL4lQSwZW+nBkANEg==",
"dev": true,
"dependencies": {
"eslint-visitor-keys": "^3.0.0",
"lodash": "^4.17.21",
"yaml": "^2.0.0"
},
"engines": {
"node": "^14.17.0 || >=16.0.0"
},
"funding": {
"url": "https://github.com/sponsors/ota-meshi"
}
} }
} }
} }
...@@ -22,6 +22,7 @@ ...@@ -22,6 +22,7 @@
"vue-i18n": "^9.7.0" "vue-i18n": "^9.7.0"
}, },
"devDependencies": { "devDependencies": {
"@intlify/unplugin-vue-i18n": "^1.5.0",
"@tsconfig/node18": "^18.2.2", "@tsconfig/node18": "^18.2.2",
"@types/chrome": "^0.0.251", "@types/chrome": "^0.0.251",
"@types/lodash-es": "^4.17.11", "@types/lodash-es": "^4.17.11",
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<link rel="icon" href="/favicon.ico"> <link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vite App</title> <title>Anything Copilot</title>
</head> </head>
<body> <body>
<div id="app"></div> <div id="app"></div>
......
{
"name": {
"message": "Anything Copilot - የድርሻ ፕሌይን በተጠቀሰው የአንድ ጽ/ቤት"
},
"short_name": {
"message": "Anything Copilot"
},
"description": {
"message": "በቀጥታ ችሎታው ተጠቀሰ ከአስማዋ የ ChatGPT ድርጅትን ለመጠቀም ወደሚከተለው GPTs, GPT-4, ወይም የሞተ አጠቃላይ ድርሻ ለተቀጥለው ተጠቀሰው የ AI ፕሌይን ማጥፋት ይችላሉ።"
},
"toggle_minimize_desc": {
"message": "የርስ/የአጠቃላይ ሪዘምን መቀያ"
}
}
\ No newline at end of file
{
"name": {
"message": "Anything Copilot - أي صفحة ويب كمساعد"
},
"short_name": {
"message": "Anything Copilot"
},
"description": {
"message": "استخدم موقع ChatGPT الرسمي أو أي صفحة ويب أخرى مجانًا كمساعد ذكاء اصطناعي، بما في ذلك GPTs وGPT-4 وأي ميزات جديدة."
},
"toggle_minimize_desc": {
"message": "تبديل عرض/إخفاء نافذة المساعد"
}
}
\ No newline at end of file
{
"name": {
"message": "Anything Copilot - Уеб приложение, AI съпилот"
},
"short_name": {
"message": "Anything Copilot"
},
"description": {
"message": "Използвайте официалния уебсайт на ChatGPT или всяка друга уеб страница безплатно като вашия AI съпилот."
},
"toggle_minimize_desc": {
"message": "Превключване на показването/скриването на прозореца на Съпилот"
}
}
\ No newline at end of file
{
"name": {
"message": "Anything Copilot - ওয়েব অ্যাপ, এআই কো-পাইলট"
},
"short_name": {
"message": "Anything Copilot"
},
"description": {
"message": "অফিসিয়াল চ্যাটজিপিটি ওয়েবসাইট বা অন্য কোনও ওয়েবপেজ বিনামূল্যে আপনার এআই কো-পাইলট হিসাবে ব্যবহার করুন।"
},
"toggle_minimize_desc": {
"message": "কো-পাইলট উইন্ডো দেখার / আড়ালম্বন করার টগল করুন"
}
}
\ No newline at end of file
{
"name": {
"message": "Anything Copilot - Assistent de multitasques"
},
"short_name": {
"message": "Anything Copilot"
},
"description": {
"message": "Utilitza el lloc web oficial de ChatGPT o qualsevol altra pàgina web de franc com el teu copilot d'IA."
},
"toggle_minimize_desc": {
"message": "Commuta ocultar/mostrar finestra del Copilot"
}
}
\ No newline at end of file
{
"name": {
"message": "Anything Copilot - Víceúlohový asistent"
},
"short_name": {
"message": "Anything Copilot"
},
"description": {
"message": "Použijte oficiální webovou stránku ChatGPT nebo jakoukoli jinou webovou stránku jako svého AI spolujezdce."
},
"toggle_minimize_desc": {
"message": "Přepnout zobrazení/skrytí okna Spolujezdce"
}
}
\ No newline at end of file
{
"name": {
"message": "Anything Copilot - Enhver webside som kopilot"
},
"short_name": {
"message": "Anything Copilot"
},
"description": {
"message": "Brug den officielle ChatGPT-hjemmeside eller en anden webside gratis som din AI-kopilot."
},
"toggle_minimize_desc": {
"message": "Skift visning/skjul Copilot-vindue"
}
}
\ No newline at end of file
{
"name": {
"message": "Anything Copilot - Jede Webseite als Copilot"
},
"short_name": {
"message": "Anything Copilot"
},
"description": {
"message": "Verwenden Sie die offizielle ChatGPT-Website oder jede andere Webseite kostenlos als Ihren AI-Copiloten."
},
"toggle_minimize_desc": {
"message": "Copilot-Fenster anzeigen/ausblenden umschalten"
}
}
\ No newline at end of file
{
"name": {
"message": "Anything Copilot - Πολυεργαλείο βοηθός"
},
"short_name": {
"message": "Anything Copilot"
},
"description": {
"message": "Χρησιμοποιήστε τον επίσημο ιστότοπο του ChatGPT ή οποιαδήποτε ιστοσελίδα ως τον AI συνοδηγό σας."
},
"toggle_minimize_desc": {
"message": "Εναλλαγή εμφάνισης/απόκρυψης του παραθύρου του Συνοδηγού"
}
}
\ No newline at end of file
{
"name": {
"message": "Anything Copilot - Any web page as copilot"
},
"short_name": {
"message": "Anything Copilot"
},
"description": {
"message": "Use the official ChatGPT website or any other webpage for free as your AI copilot, including GPTs, GPT-4, or any new features."
},
"toggle_minimize_desc": {
"message": "Toggle show/hide Copilot window"
}
}
\ No newline at end of file
{
"name": {
"message": "Anything Copilot - Any web page as copilot"
},
"short_name": {
"message": "Anything Copilot"
},
"description": {
"message": "Use the official ChatGPT website or any other webpage for free as your AI copilot, including GPTs, GPT-4, or any new features."
},
"toggle_minimize_desc": {
"message": "Toggle show/hide Copilot window"
}
}
\ No newline at end of file
{
"name": {
"message": "Anything Copilot - Any web page as copilot"
},
"short_name": {
"message": "Anything Copilot"
},
"description": {
"message": "Use the official ChatGPT website or any other webpage for free as your AI copilot, including GPTs, GPT-4, or any new features."
},
"toggle_minimize_desc": {
"message": "Toggle show/hide Copilot window"
}
}
\ No newline at end of file
{
"name": {
"message": "Anything Copilot - Asistente multitarea"
},
"short_name": {
"message": "Anything Copilot"
},
"description": {
"message": "Utiliza el sitio web oficial de ChatGPT o cualquier otra página web de forma gratuita como tu copiloto de IA."
},
"toggle_minimize_desc": {
"message": "Alternar mostrar/ocultar ventana de Copiloto"
}
}
\ No newline at end of file
{
"name": {
"message": "Anything Copilot - Asistente de multitarea"
},
"short_name": {
"message": "Anything Copilot"
},
"description": {
"message": "Utiliza el sitio web oficial de ChatGPT o cualquier otra página web de forma gratuita como tu copiloto de IA."
},
"toggle_minimize_desc": {
"message": "Alternar mostrar/ocultar ventana de Copiloto"
}
}
\ No newline at end of file
{
"name": {
"message": "Anything Copilot - Mitmeülesandejuht"
},
"short_name": {
"message": "Anything Copilot"
},
"description": {
"message": "Kasutage ametlikku ChatGPT veebisaiti või mis tahes muud veebilehte oma tehisintellektist kaasjuhina tasuta."
},
"toggle_minimize_desc": {
"message": "Vaheta kaasjuhi akna kuvamist/peitmist"
}
}
\ No newline at end of file
{
"name": {
"message": "Anything Copilot - کمک کننده چند وظیفه"
},
"short_name": {
"message": "Anything Copilot"
},
"description": {
"message": "از وب سایت رسمی چت جی پی تی یا هر صفحه وب دیگر به عنوان همراه راننده خود برای رایگان استفاده کنید."
},
"toggle_minimize_desc": {
"message": "تغییر حالت نمایش / عدم نمایش پنجره همراه راننده"
}
}
\ No newline at end of file
{
"name": {
"message": "Anything Copilot - Monitoimintavastaanottaja"
},
"short_name": {
"message": "Anything Copilot"
},
"description": {
"message": "Käytä virallista ChatGPT-verkkosivustoa tai mitä tahansa muuta verkkosivua ilmaiseksi tekoäly-apukuskina."
},
"toggle_minimize_desc": {
"message": "Vaihda Copilot-ikkunan näyttö/piilotus"
}
}
\ No newline at end of file
{
"name": {
"message": "Anything Copilot - Web app, AI copilot"
},
"short_name": {
"message": "Anything Copilot"
},
"description": {
"message": "Gamitin ang opisyal na website ng ChatGPT o anumang ibang webpage nang libre bilang iyong AI copilot."
},
"toggle_minimize_desc": {
"message": "I-on ang pagpapalit ng pagpapakita/pagtatago ng bintana ng Copilot"
}
}
\ No newline at end of file
{
"name": {
"message": "Anything Copilot - Assistant multitâche"
},
"short_name": {
"message": "Anything Copilot"
},
"description": {
"message": "Utilisez le site Web officiel ChatGPT ou toute autre page Web gratuitement en tant que votre copilote IA."
},
"toggle_minimize_desc": {
"message": "Activer/désactiver la fenêtre du copilote"
}
}
\ No newline at end of file
{
"name": {
"message": "Anything Copilot - વેબ ઍપ, AI કોપાયલોટ"
},
"short_name": {
"message": "Anything Copilot"
},
"description": {
"message": "Use the official ChatGPT website or any webpage as your AI copilot."
},
"toggle_minimize_desc": {
"message": "કોપાયલોટ વિંડો બતાવવા/છુપાવવા ટૉગલ કરો"
}
}
\ No newline at end of file
{
"name": {
"message": "Anything Copilot - כל דף אינטרנט כמסע נהג"
},
"short_name": {
"message": "Anything Copilot"
},
"description": {
"message": "השתמש באתר הרשמי של ChatGPT או בכל דף אינטרנט אחר ללא תשלום כמסע נהג שלך, כולל GPTs, GPT-4 או כל תכונות חדשות."
},
"toggle_minimize_desc": {
"message": "שינוי הצגה/הסתרה של חלון הגיבור המלכודת"
}
}
\ No newline at end of file
{
"name": {
"message": "Anything Copilot - वेब ऐप, AI को-पायलट"
},
"short_name": {
"message": "Anything Copilot"
},
"description": {
"message": "ChatGPT वेबसाइट या किसी अन्य वेब पृष्ठ का प्रयोग निशुल्क रूप से अपने AI को-पायलट के रूप में करें।"
},
"toggle_minimize_desc": {
"message": "को-पायलट विंडो का दिखाएँ/छुपाएँ टॉगल करें"
}
}
\ No newline at end of file
{
"name": {
"message": "Anything Copilot - Web aplikacija, AI suvozač"
},
"short_name": {
"message": "Anything Copilot"
},
"description": {
"message": "Besplatno koristite službenu ChatGPT web stranicu ili bilo koju drugu web stranicu kao svoj AI suvozač."
},
"toggle_minimize_desc": {
"message": "Uključivanje/isključivanje prikaza suvozačevog prozora"
}
}
\ No newline at end of file
{
"name": {
"message": "Anything Copilot - Többfeladatos segítő"
},
"short_name": {
"message": "Anything Copilot"
},
"description": {
"message": "Használd az hivatalos ChatGPT weboldalt vagy bármely más weboldalt ingyen az AI autóvezetőddé."
},
"toggle_minimize_desc": {
"message": "Váltás az Autóvezető ablak megjelenítése/elrejtése között"
}
}
\ No newline at end of file
{
"name": {
"message": "Anything Copilot - Aplikasi web, kopilot AI"
},
"short_name": {
"message": "Anything Copilot"
},
"description": {
"message": "Gunakan situs web resmi ChatGPT atau halaman web lainnya secara gratis sebagai kopilot AI Anda."
},
"toggle_minimize_desc": {
"message": "Aktifkan/Tutup tampilan jendela Kopilot"
}
}
\ No newline at end of file
{
"name": {
"message": "Anything Copilot - App web, copilota AI"
},
"short_name": {
"message": "Anything Copilot"
},
"description": {
"message": "Utilizza il sito web ufficiale di ChatGPT o qualsiasi altra pagina web gratuitamente come tuo copilota AI."
},
"toggle_minimize_desc": {
"message": "Attiva/disattiva visualizzazione/nascondi finestra Copilot"
}
}
\ No newline at end of file
{
"name": {
"message": "Anything Copilot - 無限アプリ、AIアシスタント"
},
"short_name": {
"message": "Anything Copilot"
},
"description": {
"message": "ChatGPTの公式ウェブサイトや他のページを無料でAIアシスタントにする。GPTやGPT4の新機能を即座に体験できる。オープンソースの大規模モデルや文心一言と対話し、Google翻訳やTikTokで短い動画を見ることもできます。"
},
"toggle_minimize_desc": {
"message": "Copilotウィンドウを切り替える"
}
}
\ No newline at end of file
{
"name": {
"message": "Anything Copilot - ವೆಬ್ ಆ್ಯಪ್, ಏಐ ಕೊಪಿಲೋಟ್"
},
"short_name": {
"message": "Anything Copilot"
},
"description": {
"message": "ಆಧಿಕಾರಿಕ ChatGPT ವೆಬ್‌ಸೈಟ್ ಅಥವಾ ಯಾವುದೇ ವೆಬ್‌ಪೇಜ್‌ನೊಂದಿಗೆ ನಿಮ್ಮ ಏಕಾಂತಕ್ಕೆ AI ಸಹಾಯಕ ಹೊಸ್ತಾದ ಹಾಗೆ ಬಳಸಿ."
},
"toggle_minimize_desc": {
"message": "ಕೊಪಿಲೋಟ್ ವಿಂಡೋ ತೋರಿಸು/ಮರೆಮಾಡು ಟಾಗಲ್‌"
}
}
\ No newline at end of file
{
"name": {
"message": "Anything Copilot - 무한 어플리케이션, AI 어시스턴트"
},
"short_name": {
"message": "Anything Copilot"
},
"description": {
"message": "Use the official ChatGPT website or any webpage as your AI copilot."
},
"toggle_minimize_desc": {
"message": "CoPilot 창 표시/숨기기 전환"
}
}
\ No newline at end of file
{
"name": {
"message": "Anything Copilot - Multitasking assistant"
},
"short_name": {
"message": "Anything Copilot"
},
"description": {
"message": "Naudokite oficialią ChatGPT svetainę ar bet kurią svetainę kaip savo AI bendražygį."
},
"toggle_minimize_desc": {
"message": "Perjungti rodyti/paslėpti kolegos langą"
}
}
\ No newline at end of file
{
"name": {
"message": "Anything Copilot - Daudzfunkciju palīgs"
},
"short_name": {
"message": "Anything Copilot"
},
"description": {
"message": "Bezmaksas izmantojiet oficiālo ChatGPT mājaslapu vai jebkuru citu tīmekļa lapu kā savu AI līdzpilotu."
},
"toggle_minimize_desc": {
"message": "Pārslēgt Kopilota loga rādīšanu/paslēpšanu"
}
}
\ No newline at end of file
{
"name": {
"message": "Anything Copilot - Multitasking assistant"
},
"short_name": {
"message": "Anything Copilot"
},
"description": {
"message": "സർക്കാർഗെറ്റ് വെബ് സൈറ്റുകൾ അല്ലെങ്കിൽ മറ്റ് ഏതെങ്കിലും വെബ് പേജ് സൌജന്യമായാൽ നിങ്ങളുടെ ഏ.ഐ. കോ-പയ്ലറ്റായാക്കാം."
},
"toggle_minimize_desc": {
"message": "കോ-പയ്ലറ്റ് വിൻഡോ കാണാൻ/മറയ്ക്കാൻ ടോഗിൾ ചെയ്യുക"
}
}
\ No newline at end of file
{
"name": {
"message": "Anything Copilot - वेब अ‍ैप, AI सहकारीस्वरूपी"
},
"short_name": {
"message": "Anything Copilot"
},
"description": {
"message": "ChatGPT या अधिकृत वेबसाइट किंवा इतर कोणत्याही वेब पृष्ठावर मुफ्त म्हणून आपल्या AI सहकारीस्वरूपी म्हणून वापरा."
},
"toggle_minimize_desc": {
"message": "सहाय्यक विंडो दाखवण्याचे/लपवण्याचे स्विच करा"
}
}
\ No newline at end of file
{
"name": {
"message": "Anything Copilot - Penolong Pelbagai Tugas"
},
"short_name": {
"message": "Anything Copilot"
},
"description": {
"message": "Gunakan laman web rasmi ChatGPT atau laman web lain secara percuma sebagai pembantu pemandu AI anda."
},
"toggle_minimize_desc": {
"message": "Togol papar/sorok tetingkap Pembantu"
}
}
\ No newline at end of file
{
"name": {
"message": "Anything Copilot - Webapp, AI copiloot"
},
"short_name": {
"message": "Anything Copilot"
},
"description": {
"message": "Gebruik de officiële ChatGPT-website of een andere webpagina gratis als uw AI copiloot, inclusief GPT's, GPT-4 of nieuwe functies."
},
"toggle_minimize_desc": {
"message": "Klap het Copilot-venster in/uit"
}
}
\ No newline at end of file
{
"name": {
"message": "Anything Copilot - Multitasking-assistent"
},
"short_name": {
"message": "Anything Copilot"
},
"description": {
"message": "Bruk den offisielle ChatGPT-nettsiden eller en annen nettside gratis som din AI medpilot."
},
"toggle_minimize_desc": {
"message": "Bytt vis/skjul Medpilot-vinduet"
}
}
\ No newline at end of file
{
"name": {
"message": "Anything Copilot - Multitasking assistant"
},
"short_name": {
"message": "Anything Copilot"
},
"description": {
"message": "Używaj oficjalnej strony internetowej ChatGPT lub dowolnej innej strony za darmo jako swojego współpilota AI."
},
"toggle_minimize_desc": {
"message": "Przełącz pokaż/ukryj okno Współpilota"
}
}
\ No newline at end of file
{
"name": {
"message": "Anything Copilot - Assistente multitarefa"
},
"short_name": {
"message": "Anything Copilot"
},
"description": {
"message": "Use o site oficial do ChatGPT ou qualquer outra página da web gratuitamente como seu co-piloto de IA."
},
"toggle_minimize_desc": {
"message": "Alternar mostrar/ocultar janela de Co-piloto"
}
}
\ No newline at end of file
{
"name": {
"message": "Anything Copilot - Assistente de multitarefas"
},
"short_name": {
"message": "Anything Copilot"
},
"description": {
"message": "Use o site oficial do ChatGPT ou qualquer outra página da web gratuitamente como seu copiloto de IA."
},
"toggle_minimize_desc": {
"message": "Alternar mostrar/ocultar janela do Copiloto"
}
}
\ No newline at end of file
{
"name": {
"message": "Anything Copilot - Aplicație web, copilot AI"
},
"short_name": {
"message": "Anything Copilot"
},
"description": {
"message": "Folosiți site-ul oficial ChatGPT sau orice altă pagină web gratuit ca și copilot AI."
},
"toggle_minimize_desc": {
"message": "Comutați între afișarea/ascunderea ferestrei Copilot"
}
}
\ No newline at end of file
{
"name": {
"message": "Anything Copilot - Мультизадачный помощник"
},
"short_name": {
"message": "Anything Copilot"
},
"description": {
"message": "Используйте официальный веб-сайт ChatGPT или любую другую веб-страницу бесплатно в качестве вашего помощника-со-водителя."
},
"toggle_minimize_desc": {
"message": "Переключить показ/скрытие окна помощника-со-водителя"
}
}
\ No newline at end of file
{
"name": {
"message": "Anything Copilot - Multitasking asistent"
},
"short_name": {
"message": "Anything Copilot"
},
"description": {
"message": "Použite oficiálnu webovú stránku ChatGPT alebo akúkoľvek inú webovú stránku zadarmo ako váš AI spolujazdec."
},
"toggle_minimize_desc": {
"message": "Prepnúť zobrazenie/skrytie okna Spolujazdec"
}
}
\ No newline at end of file
{
"name": {
"message": "Anything Copilot - Večopravilni pomočnik"
},
"short_name": {
"message": "Anything Copilot"
},
"description": {
"message": "Brezplačno uporabite uradno spletno mesto ChatGPT ali katero koli drugo spletno stran kot svoj AI sovoznik."
},
"toggle_minimize_desc": {
"message": "Preklopite prikaz/skrivanje okna Sovoznika"
}
}
\ No newline at end of file
{
"name": {
"message": "Anything Copilot - Web aplikacija, AI kopilot"
},
"short_name": {
"message": "Anything Copilot"
},
"description": {
"message": "Besplatno koristite zvaničnu ChatGPT veb stranicu ili bilo koju drugu veb stranicu kao vaš AI kopilot."
},
"toggle_minimize_desc": {
"message": "Prikaži/sakrij prozor kopilota"
}
}
\ No newline at end of file
{
"name": {
"message": "Anything Copilot - Multifunktionsassistent"
},
"short_name": {
"message": "Anything Copilot"
},
"description": {
"message": "Använd den officiella ChatGPT-webbplatsen eller vilken annan webbsida som helst gratis som din AI-co-pilot."
},
"toggle_minimize_desc": {
"message": "Växla visa/dölj co-pilot-fönstret"
}
}
\ No newline at end of file
{
"name": {
"message": "Anything Copilot - Multitasking assistant"
},
"short_name": {
"message": "Anything Copilot"
},
"description": {
"message": "Tumia tovuti rasmi ya ChatGPT au ukurasa wowote mwingine wa wavuti bure kama mpalaji wako wa AI."
},
"toggle_minimize_desc": {
"message": "Badilisha kuonyesha/ficha dirisha la Mpangilio"
}
}
\ No newline at end of file
{
"name": {
"message": "Anything Copilot - பலவின்பமுள்ள உதவி"
},
"short_name": {
"message": "Anything Copilot"
},
"description": {
"message": "உங்கள் அறிவியல் உதவி காப்பாற்ற பதிவாக்க ஆமின் இயந்திரத்தைஅல்லது எங்கேரும் புதிய அம்சங்களை உள்ளதாக்கலாம்."
},
"toggle_minimize_desc": {
"message": "காப்பாற்ற சாளரத்தை முதல்வரிக்கவும்"
}
}
\ No newline at end of file
{
"name": {
"message": "Anything Copilot - వెబ్ యాప్, AI కోపిలాట్"
},
"short_name": {
"message": "Anything Copilot"
},
"description": {
"message": "ఉచితంగా మీరు మీ AI కోపిలాట్కు ఆధికారిక చాట్ జీపీటి వెబ్సైట్ లేదా ఏ మరొక వెబ్ పేజీని ఉపయోగించుకోండి."
},
"toggle_minimize_desc": {
"message": "కోపిలాట్ విండో దాచు/దాగువాడండి టాగిల్ చేయండి"
}
}
\ No newline at end of file
{
"name": {
"message": "Anything Copilot - Multitasking assistant"
},
"short_name": {
"message": "Anything Copilot"
},
"description": {
"message": "ใช้เว็บไซต์ ChatGPT อย่างเป็นทางการหรือหน้าเว็บใดก็ได้ฟรีเป็น Copilot ของ AI ของคุณ เช่น GPTs, GPT-4 หรือคุณสมบัติใหม่ ๆ"
},
"toggle_minimize_desc": {
"message": "สลับแสดง / ซ่อนหน้าต่าง Copilot"
}
}
\ No newline at end of file
{
"name": {
"message": "Anything Copilot - Çoklu Görevli Yardımcı"
},
"short_name": {
"message": "Anything Copilot"
},
"description": {
"message": "Resmi ChatGPT sitesini veya herhangi bir web sayfasını ücretsiz olarak yapay zeka yardımcınız olarak kullanın."
},
"toggle_minimize_desc": {
"message": "Yardımcı pencereyi göster/gizle"
}
}
\ No newline at end of file
{
"name": {
"message": "Anything Copilot - Багатозадачний помічник"
},
"short_name": {
"message": "Anything Copilot"
},
"description": {
"message": "Безкоштовно використовуйте офіційний веб-сайт ChatGPT або будь-яку іншу веб-сторінку як Штучний розум."
},
"toggle_minimize_desc": {
"message": "Перемкнути вікно Співкерманича (показати/приховати)"
}
}
\ No newline at end of file
{
"name": {
"message": "Anything Copilot - Ứng dụng web, phi công AI"
},
"short_name": {
"message": "Anything Copilot"
},
"description": {
"message": "Sử dụng trang web chính thức của ChatGPT hoặc bất kỳ trang web nào khác miễn phí như là phi công AI của bạn."
},
"toggle_minimize_desc": {
"message": "Mở/đóng cửa sổ Copilot"
}
}
\ No newline at end of file
{
"name": {
"message": "Anything Copilot - 無限應用、AI助手"
},
"short_name": {
"message": "Anything Copilot"
},
"description": {
"message": "免費將ChatGPT官網或其他頁面化為您的AI助手,立即體驗GPTs、GPT4及其他新功能。可與開源大模型、文心一言對話,用Google Translate翻譯及抖音刷短視頻。"
},
"toggle_minimize_desc": {
"message": "切換顯示/隱藏Copilot視窗"
}
}
\ No newline at end of file
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<link rel="icon" href="/favicon.ico"> <link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vite App</title> <title>Anything Copilot</title>
<base href="http://localhost:3000"> <base href="http://localhost:3000">
</head> </head>
<body> <body>
......
public/favicon.ico

4.19 KB | W: | H:

public/favicon.ico

1.91 KB | W: | H:

public/favicon.ico
public/favicon.ico
public/favicon.ico
public/favicon.ico
  • 2-up
  • Swipe
  • Onion skin
File deleted
...@@ -39,6 +39,9 @@ ...@@ -39,6 +39,9 @@
--color-heading: var(--vt-c-text-light-1); --color-heading: var(--vt-c-text-light-1);
--color-text: var(--vt-c-text-light-1); --color-text: var(--vt-c-text-light-1);
--bg-rgb: 255, 255, 255;
--fg-rgb: 0, 0, 0;
--section-gap: 160px; --section-gap: 160px;
} }
...@@ -54,6 +57,9 @@ ...@@ -54,6 +57,9 @@
--color-heading: var(--vt-c-text-dark-1); --color-heading: var(--vt-c-text-dark-1);
--color-text: var(--vt-c-text-dark-2); --color-text: var(--vt-c-text-dark-2);
--bg-rgb: 0, 0, 0;
--fg-rgb: 255, 255, 255;
} }
} }
......
@import './base.css'; @import "./base.css";
#app { #app {
margin: 0 auto; margin: 0 auto;
...@@ -8,13 +8,14 @@ ...@@ -8,13 +8,14 @@
a, a,
.green { .green {
text-decoration: none; text-decoration: none;
color: hsla(160, 100%, 37%, 1); /* color: hsla(160, 100%, 37%, 1); */
color: #38bdf8;
transition: 0.4s; transition: 0.4s;
} }
@media (hover: hover) { @media (hover: hover) {
a:hover { a:hover {
background-color: hsla(160, 100%, 37%, 0.2); /* background-color: hsla(160, 100%, 37%, 0.2); */
} }
} }
......
...@@ -81,12 +81,12 @@ type UpdatePipWinOption = { ...@@ -81,12 +81,12 @@ type UpdatePipWinOption = {
windowInfo: Partial<chrome.windows.UpdateInfo>; windowInfo: Partial<chrome.windows.UpdateInfo>;
}; };
async function updatePipWin({ windowId, windowInfo }: UpdatePipWinOption) { async function updateWindow({ windowId, windowInfo }: UpdatePipWinOption) {
await chrome.windows.update(windowId, windowInfo); await chrome.windows.update(windowId, windowInfo);
} }
function handleMessage(message: any, sender: chrome.runtime.MessageSender) { function handleMessage(message: any, sender: chrome.runtime.MessageSender) {
console.log("bg message: ", message, sender); console.log("bg message: ", message, sender, Date.now());
switch (message?.type) { switch (message?.type) {
case MessageType.bgOpenPip: case MessageType.bgOpenPip:
openPipBackground(message.url); openPipBackground(message.url);
...@@ -100,12 +100,11 @@ function handleMessage(message: any, sender: chrome.runtime.MessageSender) { ...@@ -100,12 +100,11 @@ function handleMessage(message: any, sender: chrome.runtime.MessageSender) {
case MessageType.getPipWinInfo: case MessageType.getPipWinInfo:
getPipWindow(sender.tab?.id!, message.options); getPipWindow(sender.tab?.id!, message.options);
break; break;
case MessageType.minimizePipWin: case MessageType.updateWindow:
minimizePip(message.options); updateWindow(message.options);
console.log(message, sender);
break; break;
case MessageType.updatePipWin: case MessageType.removeWindow:
updatePipWin(message.options); chrome.windows.remove(message.options.windowId);
break; break;
} }
} }
......
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed, watch, onMounted } from "vue"; import { ref, computed, watch, onMounted } from "vue"
import IconMinimize from "@/components/icons/IconMinimize.vue"; import IconMinimize from "@/components/icons/IconMinimize.vue"
import IconSplitRight from "@/components/icons/IconSplitscreenRight.vue"; import IconSplitRight from "@/components/icons/IconSplitscreenRight.vue"
import IconClose from "@/components/icons/IconClose.vue"; import IconClose from "@/components/icons/IconClose.vue"
import { MessageType } from "@/types"; import { MessageType } from "@/types"
import { pipWindowInfo, pipWindowRef } from "@/content/store"; import { pipWindow } from "@/store"
import { throttle } from "lodash-es"; import { throttle } from "lodash-es"
import IconRefresh from "./icons/IconRefresh.vue"; import IconRefresh from "./icons/IconRefresh.vue"
import { PipEventName } from "@/types/pip"; import { dispatchContentEvent } from "@/content/event"
import { onUnmounted } from "vue"; import { onUnmounted } from "vue"
import { useI18n } from "@/utils/i18n"
import IconHide from "./icons/IconHide.vue"
const open = ref(false); const open = ref(false)
const timer = ref(0); const pinOpen = ref(false)
const btns = ref<HTMLDivElement>(); const timer = ref(0)
const wrapper = ref<HTMLDivElement>()
const { t } = useI18n()
const pipRight = computed(() => { const isWindows = navigator.userAgentData?.platform == "Windows"
const winInfo = pipWindowInfo.value; const modiferKey = isWindows ? "ctrl" : "⌘"
const { width } = screen;
if (!winInfo) return 0;
if (!winInfo.width || !winInfo.left) return 0;
return width - winInfo.left - winInfo.width;
});
const pipLeft = computed(() => { const pipRect = computed(() => {
const winInfo = pipWindowInfo.value; const winInfo = pipWindow.windowsWindow
return winInfo?.left || 0; const { width } = screen
});
return {
width: winInfo?.width || 0,
height: winInfo?.height || 0,
left: winInfo?.left || 0,
right: width - (winInfo?.left || 0) - (winInfo?.width || 0),
}
})
const updateWindowInfo = throttle(() => { const updateWindowInfo = throttle(() => {
const id = pipWindowInfo.value?.id; const id = pipWindow.windowsWindow?.id
console.log("updateWindowInfo", id); console.log("updateWindowInfo", id)
if (id) { if (id) {
chrome.runtime.sendMessage({ chrome.runtime.sendMessage({
type: MessageType.getPipWinInfo, type: MessageType.getPipWinInfo,
options: { options: {
windowId: id, windowId: id,
}, },
}); })
} }
}, 3000); }, 3000)
const handleClick = (e: MouseEvent) => { const clickMenu = (e: MouseEvent) => {
const target = e.composedPath()[0] as Element; open.value = true
if (btns.value?.contains(target)) { if (e.ctrlKey || e.metaKey) {
return; pinOpen.value = true
} }
open.value = false; }
};
const handleClickAway = (e: MouseEvent) => {
const target = e.composedPath()[0] as Element
if (wrapper.value?.contains(target)) {
return
}
open.value = false
}
watch(open, (value, oldValue, onCleanup) => { watch(open, (value, oldValue, onCleanup) => {
value && updateWindowInfo(); value && updateWindowInfo()
window.addEventListener("click", handleClick); window.addEventListener("click", handleClickAway)
onCleanup(() => { onCleanup(() => {
window.removeEventListener("click", handleClick); window.removeEventListener("click", handleClickAway)
}); })
}); })
const menuPointerEnter = () => { const menuPointerEnter = () => {
clearTimeout(timer.value); clearTimeout(timer.value)
timer.value = setTimeout(() => (open.value = true), 200); timer.value = setTimeout(() => (open.value = true), 200)
}; }
const menuPointerLeave = () => { const menuPointerLeave = () => {
clearTimeout(timer.value); clearTimeout(timer.value)
timer.value = setTimeout(() => (open.value = false), 200); timer.value = setTimeout(() => (open.value = false), 200)
}; }
const btnPointerEnter = () => { const btnPointerEnter = () => {
clearTimeout(timer.value); clearTimeout(timer.value)
}; }
const btnPointerLeave = () => { const btnPointerLeave = () => {
timer.value = setTimeout(() => (open.value = false), 350); if (pinOpen.value) {
}; return
}
timer.value = setTimeout(() => (open.value = false), 350)
}
const minimize = () => { const minimize = () => {
const windowId = pipWindowInfo.value?.id; open.value = false
const windowId = pipWindow.windowsWindow?.id
if (!windowId) { if (!windowId) {
console.log("windowId is not set", pipWindowInfo.value); console.log("windowId is not set", pipWindow.windowsWindow)
return; return
} }
chrome.runtime.sendMessage({ chrome.runtime.sendMessage({
type: MessageType.updatePipWin, type: MessageType.updateWindow,
options: { options: {
windowId: windowId, windowId: windowId,
windowInfo: { windowInfo: {
state: "minimized", state: "minimized",
}, },
}, },
}); })
}; }
const slideOver = () => { const moveAside = (value?: number) => {
const windowId = pipWindowInfo.value?.id; open.value = false
const windowId = pipWindow.windowsWindow?.id
if (!windowId) { if (!windowId) {
console.log("windowId is not set", pipWindowInfo.value); console.log("windowId is not set", pipWindow.windowsWindow)
return; return
} }
console.log("slide over: ", pipRight, pipLeft);
const left = Math.min(Math.max(pipRight.value, 60), screen.width - 60); const left =
pipRect.value.right > pipRect.value.left
? screen.width - 60 - pipRect.value.width
: 60
chrome.runtime.sendMessage({ chrome.runtime.sendMessage({
type: MessageType.updatePipWin, type: MessageType.updateWindow,
options: { options: {
windowId, windowId,
windowInfo: { windowInfo: {
left, left: value || left,
}, },
}, },
}); })
}; }
const close = () => { const close = () => {
const win = window.documentPictureInPicture.window; const win = window.documentPictureInPicture.window
win?.close(); win?.close()
}; }
const refresh = () => { const refresh = () => {
const pipWindow = pipWindowRef.value; open.value = false
if (pipWindow) { const win = pipWindow.window
document.dispatchEvent( if (win) {
new CustomEvent(PipEventName.loadDoc, { dispatchContentEvent({
detail: { url: pipWindow.location.href }, type: "load-doc",
detail: { url: win.location.href },
}) })
);
} }
// const win = window.documentPictureInPicture.window; }
// win?.close();
// document.dispatchEvent(
// new CustomEvent(PipEventName.pip, {
// detail: {
// url: location.href,
// mode: "write-html",
// },
// })
// );
};
const throttledRefresh = throttle(refresh, 3000);
const handleKeydown = (e: KeyboardEvent) => { const handleKeydown = (e: KeyboardEvent) => {
const isWindows = navigator.userAgentData.platform == "Windows"; if (isWindows ? e.ctrlKey : e.metaKey) {
if (e.code == "KeyR" && (isWindows ? e.ctrlKey : e.metaKey)) { if (e.code == "KeyR") {
throttledRefresh(); return refresh()
} }
// if (e.code == "KeyM" && e.metaKey) { if (e.code == "KeyM") {
// e.preventDefault(); return minimize()
// e.stopPropagation() }
// }
}; if (e.code == "ArrowRight") {
return moveAside(screen.width - 60 - pipRect.value.width)
}
if (e.code == "ArrowLeft") {
return moveAside(60)
}
}
}
onMounted(() => { onMounted(() => {
pipWindowRef.value?.addEventListener("keydown", handleKeydown); pipWindow.window?.addEventListener("keydown", handleKeydown)
}); })
onUnmounted(() => { onUnmounted(() => {
pipWindowRef.value?.removeEventListener("keydown", handleKeydown); pipWindow.window?.removeEventListener("keydown", handleKeydown)
}); })
</script> </script>
<template> <template>
<div ref="wrapper">
<button <button
class="multitasking-menu flex gap-1 px-2 py-1.5 z-[9999]" class="multitasking-menu flex gap-1 px-2 py-1.5 z-[9999]"
@click="open = true" @click="clickMenu"
@pointerenter="menuPointerEnter" @pointerenter="menuPointerEnter"
@pointerleave="menuPointerLeave" @pointerleave="menuPointerLeave"
> >
...@@ -171,42 +190,64 @@ onUnmounted(() => { ...@@ -171,42 +190,64 @@ onUnmounted(() => {
<div <div
v-if="open" v-if="open"
ref="btns" class="btn-group flex flex-col px-2 py-2 gap-2 fixed rounded-lg shadow-lg left-6 z-[9999]"
class="btn-group flex px-2 py-1 gap-2 fixed rounded-full left-6 z-[9999]"
@pointerenter="btnPointerEnter" @pointerenter="btnPointerEnter"
@pointerleave="btnPointerLeave" @pointerleave="btnPointerLeave"
@click="open = false"
> >
<div v-if="0">
<input
autofocus
placeholder="search"
class="search text-xs rounded h-6 leading-6 px-2"
/>
</div>
<button <button
class="btn w-7 h-7 flex items-center justify-center rounded" tabindex="1"
class="btn-item"
@click="minimize" @click="minimize"
aria-label="minimize" :aria-label="t('minimize')"
> >
<IconMinimize /> <IconHide class="shrink-0 scale-95" />
<span class="truncate">{{ t("minimize") }}</span>
<span class="shortcut">
<span class="key">{{ modiferKey }}</span> + <span class="key">m</span>
</span>
</button> </button>
<button <button
class="btn w-7 h-7 flex items-center justify-center rounded" class="btn-item"
@click="slideOver" @click="moveAside()"
aria-label="slide over" :aria-label="t('moveAside')"
> >
<IconSplitRight :class="{ 'rotate-180': pipRight < pipLeft }" /> <IconSplitRight
:class="[
'shrink-0 scale-95',
{ 'rotate-180': pipRect.right < pipRect.left },
]"
/>
<span class="truncate">{{ t("moveAside") }}</span>
<span class="shortcut">
<span class="key">{{ modiferKey }}</span> +
<span class="key">{{
pipRect.right < pipRect.left ? "←" : "→"
}}</span>
</span>
</button> </button>
<button <button class="btn-item" @click="close" :aria-label="t('close')">
class="btn w-7 h-7 flex items-center justify-center rounded" <IconClose class="shrink-0" />
@click="close" <span class="truncate">{{ t("close") }}</span>
aria-label="close" <span class="shortcut">
> <span class="key">{{ modiferKey }}</span> + <span class="key">w</span>
<IconClose /> </span>
</button> </button>
<button <button class="btn-item" @click="refresh" :aria-label="t('refresh')">
class="btn w-7 h-7 flex items-center justify-center rounded" <IconRefresh class="shrink-0" />
@click="refresh" <span class="truncate">{{ t("refresh") }}</span>
aria-label="refresh" <span class="shortcut">
> <span class="key">{{ modiferKey }}</span> + <span class="key">r</span>
<IconRefresh /> </span>
</button> </button>
<!-- <button @click="">forward</button> </div>
<button @click="">backward</button> -->
</div> </div>
</template> </template>
...@@ -236,25 +277,51 @@ onUnmounted(() => { ...@@ -236,25 +277,51 @@ onUnmounted(() => {
backdrop-filter: invert(20%); backdrop-filter: invert(20%);
} }
.search {
background: var(--color-background-soft);
border: 1px solid var(--color-border);
}
.btn-group { .btn-group {
backdrop-filter: blur(5px) invert(5%); backdrop-filter: blur(10px) invert(5%);
top: 12px; top: 24px;
border: 1px solid var(--color-border); border: 1px solid var(--color-border);
color: white; color: var(--color-text);
left: 50%; left: 50%;
transform: translateX(-50%); transform: translateX(-50%);
background-color: rgba(60, 60, 60, 0.5); background-color: rgba(var(--bg-rgb), 0.65);
animation: 200ms ease-out fadein; animation: 200ms ease-out fadein;
max-width: 100%;
}
.btn-item {
font-size: 14px;
border-radius: 4px;
display: flex;
align-items: center;
gap: 4px;
padding: 4px 6px;
} }
.btn-group .btn { .btn-item:hover {
background: var(--color-background-mute);
}
.shortcut {
letter-spacing: -0.5px;
font-size: 12px;
line-height: 14px;
margin-left: auto;
text-wrap: nowrap;
} }
.btn-group .btn:hover { .shortcut .key {
background: rgba(30, 30, 30, 0.2); padding: 2px 4px;
border-radius: 4px;
background: var(--color-background-soft);
} }
@keyframes fadein { @keyframes fadein {
from { from {
transform: translateX(-50%) translateY(-50%) scale(50%); transform: translateX(-50%) translateY(-60px) scale(50%);
opacity: 0.2; opacity: 0.2;
} }
to { to {
......
<script setup lang="ts"> <script setup lang="ts">
import { PipEventName } from "@/types/pip"; import { dispatchContentEvent } from "@/content/event"
import { reactive, ref } from "vue"; import { reactive, ref } from "vue"
import IconClose from "./icons/IconClose.vue"; import IconClose from "./icons/IconClose.vue"
import { useI18n } from "@/utils/i18n"
const emit = defineEmits(["close"]); const { t } = useI18n()
const host = ref(location.host); const emit = defineEmits(["close"])
const host = ref(location.host)
function handleClick() { function handleClick() {
document.dispatchEvent( dispatchContentEvent({
new CustomEvent(PipEventName.pip, { type: "pip",
detail: { detail: {
url: location.href, url: location.href,
mode: "write-html", mode: "write-html",
}, },
}) })
); emit("close")
emit("close");
} }
function handleClose(e: MouseEvent) { function handleClose(e: MouseEvent) {
e.stopPropagation(); e.stopPropagation()
emit("close"); emit("close")
} }
</script> </script>
...@@ -58,7 +60,7 @@ function handleClose(e: MouseEvent) { ...@@ -58,7 +60,7 @@ function handleClose(e: MouseEvent) {
class="w-36 h-36 rounded-full animate-ping bg-[var(--color-background-soft)]" class="w-36 h-36 rounded-full animate-ping bg-[var(--color-background-soft)]"
></div> ></div>
<div class="font-bold text-base">Click Here</div> <div class="font-bold text-base">{{ t("clickHere") }}</div>
</div> </div>
</div> </div>
</div> </div>
......
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 127.14 96.36"
fill="currentColor"
color="#5865f2"
>
<path
d="M107.7,8.07A105.15,105.15,0,0,0,81.47,0a72.06,72.06,0,0,0-3.36,6.83A97.68,97.68,0,0,0,49,6.83,72.37,72.37,0,0,0,45.64,0,105.89,105.89,0,0,0,19.39,8.09C2.79,32.65-1.71,56.6.54,80.21h0A105.73,105.73,0,0,0,32.71,96.36,77.7,77.7,0,0,0,39.6,85.25a68.42,68.42,0,0,1-10.85-5.18c.91-.66,1.8-1.34,2.66-2a75.57,75.57,0,0,0,64.32,0c.87.71,1.76,1.39,2.66,2a68.68,68.68,0,0,1-10.87,5.19,77,77,0,0,0,6.89,11.1A105.25,105.25,0,0,0,126.6,80.22h0C129.24,52.84,122.09,29.11,107.7,8.07ZM42.45,65.69C36.18,65.69,31,60,31,53s5-12.74,11.43-12.74S54,46,53.89,53,48.84,65.69,42.45,65.69Zm42.24,0C78.41,65.69,73.25,60,73.25,53s5-12.74,11.44-12.74S96.23,46,96.12,53,91.08,65.69,84.69,65.69Z"
/>
</svg>
</template>
<template>
<svg
width="98"
height="96"
viewBox="0 0 100 100"
xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M48.854 0C21.839 0 0 22 0 49.217c0 21.756 13.993 40.172 33.405 46.69 2.427.49 3.316-1.059 3.316-2.362 0-1.141-.08-5.052-.08-9.127-13.59 2.934-16.42-5.867-16.42-5.867-2.184-5.704-5.42-7.17-5.42-7.17-4.448-3.015.324-3.015.324-3.015 4.934.326 7.523 5.052 7.523 5.052 4.367 7.496 11.404 5.378 14.235 4.074.404-3.178 1.699-5.378 3.074-6.6-10.839-1.141-22.243-5.378-22.243-24.283 0-5.378 1.94-9.778 5.014-13.2-.485-1.222-2.184-6.275.486-13.038 0 0 4.125-1.304 13.426 5.052a46.97 46.97 0 0 1 12.214-1.63c4.125 0 8.33.571 12.213 1.63 9.302-6.356 13.427-5.052 13.427-5.052 2.67 6.763.97 11.816.485 13.038 3.155 3.422 5.015 7.822 5.015 13.2 0 18.905-11.404 23.06-22.324 24.283 1.78 1.548 3.316 4.481 3.316 9.126 0 6.6-.08 11.897-.08 13.526 0 1.304.89 2.853 3.316 2.364 19.412-6.52 33.405-24.935 33.405-46.691C97.707 22 75.788 0 48.854 0z"
/>
</svg>
</template>
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
height="24"
viewBox="0 -960 960 960"
width="24"
fill="currentColor"
>
<path
d="M240-840h440v520L400-40l-50-50q-7-7-11.5-19t-4.5-23v-14l44-174H120q-32 0-56-24t-24-56v-80q0-7 2-15t4-15l120-282q9-20 30-34t44-14Zm360 80H240L120-480v80h360l-54 220 174-174v-406Zm0 406v-406 406Zm80 34v-80h120v-360H680v-80h200v520H680Z"
/>
</svg>
</template>
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
height="24"
viewBox="0 -960 960 960"
width="24"
fill="currentColor"
>
<path
d="M720-120H280v-520l280-280 50 50q7 7 11.5 19t4.5 23v14l-44 174h258q32 0 56 24t24 56v80q0 7-2 15t-4 15L794-168q-9 20-30 34t-44 14Zm-360-80h360l120-280v-80H480l54-220-174 174v406Zm0-406v406-406Zm-80-34v80H160v360h120v80H80v-520h200Z"
/>
</svg>
</template>
<template>
<svg
width="12"
height="12"
viewBox="0 0 1200 1227"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M714.163 519.284L1160.89 0H1055.03L667.137 450.887L357.328 0H0L468.492 681.821L0 1226.37H105.866L515.491 750.218L842.672 1226.37H1200L714.137 519.284H714.163ZM569.165 687.828L521.697 619.934L144.011 79.6944H306.615L611.412 515.685L658.88 583.579L1055.08 1150.3H892.476L569.165 687.854V687.828Z"
/>
</svg>
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted, watch } from "vue"
import { pipWindow } from "@/store"
import IconHide from "@/components/icons/IconHide.vue"
import IconArrowCircleRight from "@/components/icons/IconArrowCircleRight.vue"
import IconClose from "@/components/icons/IconClose.vue"
import { MessageType } from "@/types"
const handleLocalChange = (changes: {
[key: string]: chrome.storage.StorageChange
}) => {
if (changes.pipWindowId) {
pipWindow.id = changes.pipWindowId.newValue
}
}
watch(
() => pipWindow.id,
async (id) => {
if (id) {
const tabs = await chrome.tabs.query({ windowId: id })
console.log("pip window tabs: ", tabs, id)
if (tabs && tabs.length == 1) {
pipWindow.tab = tabs[0]
}
return
}
pipWindow.tab = null
}
)
onMounted(() => {
chrome.storage.local
.get({ pipWindowId: null })
.then(({ pipWindowId: id }) => {
if (id) {
pipWindow.id = id
}
})
chrome.storage.local.onChanged.addListener(handleLocalChange)
})
onUnmounted(() => {
chrome.storage.local.onChanged.removeListener(handleLocalChange)
})
async function handleUpdatePip(state: "normal" | "minimized") {
await chrome.runtime.sendMessage({
type: MessageType.updateWindow,
options: {
windowId: pipWindow.id,
windowInfo: {
state,
},
},
})
}
async function closePip() {
await chrome.runtime.sendMessage({
type: MessageType.updateWindow,
options: {
windowId: pipWindow.id,
windowInfo: {
state: "normal",
},
},
})
await chrome.runtime.sendMessage({
type: MessageType.removeWindow,
options: {
windowId: pipWindow.id,
},
})
}
</script>
<template>
<div v-if="pipWindow.tab && pipWindow.tab.id">
<div class="text-sm flex items-center truncate mt-6">
<span
class="w-4 h-4 inline-block mr-2 rounded"
:style="{
background:
'#8882 center / contain url(' + pipWindow.tab?.favIconUrl + ')',
}"
></span>
<span>{{ pipWindow.tab?.title }}</span>
</div>
<div class="flex gap-2">
<button
class="primary-btn flex items-center mt-2 rounded-lg p-2 px-3"
@click="handleUpdatePip('minimized')"
>
<IconHide />
</button>
<button
class="primary-btn flex items-center mt-2 rounded-lg p-2 px-3"
@click="handleUpdatePip('normal')"
>
<IconArrowCircleRight />
</button>
<button
class="primary-btn flex items-center mt-2 rounded-lg p-2 px-3"
@click="closePip"
>
<IconClose />
</button>
</div>
</div>
</template>
<style scoped>
.primary-btn {
background: var(--color-background-soft);
}
.primary-btn:hover {
background: var(--color-background-mute);
}
</style>
<script setup lang="ts"> <script setup lang="ts">
import { pipLauncher } from "./store"; import { pipLauncher } from "@/store";
import PipLauncher from "@/components/PipLauncher.vue"; import PipLauncher from "@/components/PipLauncher.vue";
import { useI18n } from "vue-i18n";
const { t } = useI18n();
</script> </script>
<template> <template>
......
<script setup lang="ts"> <script setup lang="ts">
import Multitasking from "@/components/Multitasking.vue"; import Multitasking from "@/components/Multitasking.vue"
import PipSplash from "@/components/PipSplash.vue"; import PipSplash from "@/components/PipSplash.vue"
import { pipLoading } from "./store"; import { pipLoading } from "@/store"
import LoadingBar from "@/components/LoadingBar.vue"; import LoadingBar from "@/components/LoadingBar.vue"
</script> </script>
<template> <template>
......
type EventOptions =
| {
type: "pip"
detail: {
url: string
mode: string
}
}
| {
type: "load-doc"
detail: {
url: string
}
}
| {
type: "loaded"
detail: {}
}
type EventType = EventOptions["type"]
function getRealType(type: string) {
return "anything-copilot_" + type
}
export function dispatchContentEvent({ type, detail }: EventOptions) {
const event = new CustomEvent(getRealType(type), { detail })
document.dispatchEvent(event)
}
export function addContentEventListener(
type: EventType,
handler: (e: Event) => void
) {
document.addEventListener(getRealType(type), handler)
}
export function removeContentEventListener(
type: EventType,
handler: (e: Event) => void
) {
document.removeEventListener(getRealType(type), handler)
}
import { mount, waitMountApp } from "./ui"; import { mount, waitMountApp } from "./ui"
import { contentCss, pipLauncher, pipLoading, pipWindow } from "@/store"
import { MessageType } from "@/types"
import Copilot from "./Copilot.vue"
import { waitMessage } from "@/utils/ext"
import { import {
contentCss, dispatchContentEvent,
pipLauncher, addContentEventListener,
pipLoading, removeContentEventListener,
pipWindowInfo, } from "@/content/event"
pipWindowRef, // import { PipEventName } from "@/types/pip"
} from "./store";
import { MessageType } from "@/types";
import Copilot from "./Copilot.vue";
import { waitMessage } from "@/utils/ext";
import { PipEventName } from "@/types/pip";
function handleMessage( function handleMessage(
message: any, message: any,
sender: chrome.runtime.MessageSender, sender: chrome.runtime.MessageSender,
sendResponse: (res: any) => void sendResponse: (res: any) => void
) { ) {
console.log(message, sender); console.log(message, sender)
switch (message?.type) { switch (message?.type) {
case MessageType.pip: case MessageType.pip:
document.dispatchEvent( dispatchContentEvent({
new CustomEvent(PipEventName.pip, { detail: message.options }) type: "pip",
); detail: message.options,
break; })
break
case "content-css": case "content-css":
contentCss.value = message.payload?.value || ""; contentCss.value = message.payload?.value || ""
break; break
case MessageType.pipLaunch: case MessageType.pipLaunch:
pipLauncher.visible = true; pipLauncher.visible = true
break; break
case MessageType.hiContent: case MessageType.hiContent:
chrome.runtime.sendMessage({ chrome.runtime.sendMessage({
type: MessageType.contentHere, type: MessageType.contentHere,
}); })
sendResponse({ type: MessageType.contentHere }); sendResponse({ type: MessageType.contentHere })
break; break
case MessageType.pipWinInfo: case MessageType.pipWinInfo:
pipWindowInfo.value = message.window; pipWindow.windowsWindow = message.window
chrome.storage.local.set({ chrome.storage.local.set({
pipWindowId: message.window.id, pipWindowId: message.window.id,
}); })
break; break
} }
} }
async function handlePipEvent(event: any) { async function handlePipEvent(event: any) {
const pipWindow = await new Promise<Window | null>((r) => { const win = await new Promise<Window | null>((r) => {
const docPip = window.documentPictureInPicture; const docPip = window.documentPictureInPicture
const handleEnter = () => { const handleEnter = () => {
r(docPip.window); r(docPip.window)
docPip?.removeEventListener("enter", handleEnter); docPip?.removeEventListener("enter", handleEnter)
}; }
docPip?.addEventListener("enter", handleEnter); docPip?.addEventListener("enter", handleEnter)
}); })
console.log("content pip event: ", event); console.log("content pip event: ", event)
if (pipWindow) { if (win) {
pipWindowRef.value = pipWindow; pipWindow.window = win
mount(Copilot, pipWindow.document); mount(Copilot, win.document)
await new Promise<void>((r) => { await new Promise<void>((r) => {
document.addEventListener(PipEventName.loaded, (e) => { const handlePipLoaded = (e: Event) => {
console.log("load", e); console.log("load", e)
r(); r()
}); removeContentEventListener("loaded", handlePipLoaded)
}); }
addContentEventListener("loaded", handlePipLoaded)
})
// may be 0 if not wait document is loaded // may be 0 if not wait document is loaded
chrome.runtime.sendMessage({ chrome.runtime.sendMessage({
type: MessageType.getPipWinInfo, type: MessageType.getPipWinInfo,
options: { options: {
width: pipWindow.outerWidth, width: win.outerWidth,
height: pipWindow.outerHeight, height: win.outerHeight,
}, },
}); })
pipWindow.addEventListener("pagehide", () => { win.addEventListener("pagehide", () => {
chrome.storage.local.set({ chrome.storage.local.set({
pipWindowId: undefined, pipWindowId: 0,
}); })
}); })
} }
} }
async function handlePipLoadedEvent(e: Event) { async function handlePipLoadedEvent(e: Event) {
console.log("e: ", e); console.log("e: ", e)
pipLoading.splashScreen = false; pipLoading.splashScreen = false
pipLoading.isLoading = false; pipLoading.isLoading = false
const pipWindow = window.documentPictureInPicture.window; const win = window.documentPictureInPicture.window
if (pipWindow) { if (win) {
mount(Copilot, pipWindow.document); mount(Copilot, win.document)
} }
} }
async function handlePopLoadDocEvent(e: CustomEvent | Event) { async function handlePopLoadDocEvent(e: CustomEvent | Event) {
pipLoading.isLoading = true; pipLoading.isLoading = true
} }
chrome.runtime.onMessage.addListener(handleMessage); chrome.runtime?.onMessage.addListener(handleMessage)
document.addEventListener(PipEventName.pip, handlePipEvent); addContentEventListener("pip", handlePipEvent)
document.addEventListener(PipEventName.loaded, handlePipLoadedEvent); addContentEventListener("loaded", handlePipLoadedEvent)
document.addEventListener(PipEventName.loadDoc, handlePopLoadDocEvent); addContentEventListener("load-doc", handlePopLoadDocEvent)
waitMountApp(); waitMountApp()
// dev
if (location.host == chrome.runtime.id && location.hash == "#copilot") {
mount(Copilot, window.document)
}
import { PipEventName } from "@/types/pip"; import { addContentEventListener } from "./event"
import { copilotNavigateTo, pip } from "./pip"; import { copilotNavigateTo, pip } from "./pip"
function handlePipEvent(event: CustomEvent | Event) { function handlePipEvent(event: CustomEvent | Event) {
console.log(event);
if ("detail" in event) { if ("detail" in event) {
try { pip(event.detail)
pip(event.detail);
} catch (e) {
console.error(e);
}
} }
} }
function handleLoadDocEvent(event: CustomEvent | Event) { function handleLoadDocEvent(event: CustomEvent | Event) {
if ("detail" in event) { if ("detail" in event) {
copilotNavigateTo(event.detail.url); copilotNavigateTo(event.detail.url)
} }
} }
document.addEventListener(PipEventName.pip, handlePipEvent); addContentEventListener('pip', handlePipEvent)
document.addEventListener(PipEventName.loadDoc, handleLoadDocEvent); addContentEventListener('load-doc', handleLoadDocEvent)
window.addEventListener("securitypolicyviolation", (e) => {
console.warn(e);
console.log(e.originalPolicy);
});
import { PipEventName, type PipOptions } from "@/types/pip"; import { type PipOptions } from "@/types/pip"
import { import {
querySome, querySome,
copyStyleSheets, copyStyleSheets,
getDomNonce, getDomNonce,
replaceHtmlNonce, replaceHtmlNonce,
removePrerenderRules, removePrerenderRules,
} from "@/utils/dom"; } from "@/utils/dom"
import { dispatchContentEvent } from "./event"
function fetchDoc(input: URL | RequestInfo, init?: RequestInit) { function fetchDoc(input: URL | RequestInfo, init?: RequestInit) {
const headers = { const headers = {
...@@ -13,172 +14,172 @@ function fetchDoc(input: URL | RequestInfo, init?: RequestInit) { ...@@ -13,172 +14,172 @@ function fetchDoc(input: URL | RequestInfo, init?: RequestInit) {
"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
// "User-Agent": // "User-Agent":
// "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Mobile Safari/537.36", // "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Mobile Safari/537.36",
}; }
return fetch(input, { return fetch(input, {
...init, ...init,
headers: { headers: {
...headers, ...headers,
...init?.headers, ...init?.headers,
}, },
}); })
} }
let initWindow = null as any; let initWindow = null as any
export async function pip(options: PipOptions) { export async function pip(options: PipOptions) {
let { mode, selector, url, isCopyStyle } = options; let { mode, selector, url, isCopyStyle } = options
url = url || location.href; url = url || location.href
let element: Element | null = null; let element: Element | null = null
if (selector) { if (selector) {
mode = "move-element"; mode = "move-element"
element = querySome(selector); element = querySome(selector)
} }
const pipWindow = await window.documentPictureInPicture.requestWindow({ const pipWindow = await window.documentPictureInPicture.requestWindow({
width: 420, width: 420,
height: 800, height: 800,
}); })
initWindow = { ...pipWindow }; initWindow = { ...pipWindow }
// test(pipWindow.document, pipWindow); // test(pipWindow.document, pipWindow);
if (isCopyStyle) { if (isCopyStyle) {
copyStyleSheets(pipWindow, document); copyStyleSheets(pipWindow, document)
} }
if (mode === "iframe") { if (mode === "iframe") {
const iframe = document.createElement("iframe"); const iframe = document.createElement("iframe")
iframe.src = url; iframe.src = url
iframe.id = ""; iframe.id = ""
iframe.setAttribute("style", "width: 100%; height: 100%; border: none;"); iframe.setAttribute("style", "width: 100%; height: 100%; border: none;")
pipWindow.document.body.append(iframe); pipWindow.document.body.append(iframe)
return; return
} }
if (mode === "move-element") { if (mode === "move-element") {
if (element) { if (element) {
pipWindow.document.body.append(element); pipWindow.document.body.append(element)
return; return
} else { } else {
throw Error("selector not found"); throw Error("selector not found")
} }
} }
if (mode === "write-html") { if (mode === "write-html") {
const res = await fetchDoc(url); const res = await fetchDoc(url)
const html = await res.text(); const html = await res.text()
writeHtml(pipWindow, html); writeHtml(pipWindow, html)
navGuard(pipWindow); navGuard(pipWindow)
return; return
} }
} }
export async function copilotNavigateTo(url: string) { export async function copilotNavigateTo(url: string) {
const pipWindow = window.documentPictureInPicture.window; const pipWindow = window.documentPictureInPicture.window
if (!pipWindow) { if (!pipWindow) {
throw Error("pipWindow not found"); throw Error("pipWindow not found")
} }
const res = await fetchDoc(url); const res = await fetchDoc(url)
const html = await res.text(); const html = await res.text()
// resetWindow(pipWindow); // resetWindow(pipWindow);
writeHtml(pipWindow, html); writeHtml(pipWindow, html)
pipWindow.history.replaceState(pipWindow.history.state, "", url); pipWindow.history.replaceState(pipWindow.history.state, "", url)
} }
type ReopenOptions = { type ReopenOptions = {
url: string; url: string
width?: number; width?: number
height?: number; height?: number
}; }
/** Error: requires user activation */ /** Error: requires user activation */
export async function copilotReopen({ url, width, height }: ReopenOptions) { export async function copilotReopen({ url, width, height }: ReopenOptions) {
const p = window.documentPictureInPicture.window; const p = window.documentPictureInPicture.window
let w = width; let w = width
let h = height; let h = height
if (p) { if (p) {
w = w || p.innerWidth; w = w || p.innerWidth
h = h || p.innerHeight; h = h || p.innerHeight
p.close(); p.close()
} }
const pipWindow = await window.documentPictureInPicture.requestWindow({ const pipWindow = await window.documentPictureInPicture.requestWindow({
width: w, width: w,
height: h, height: h,
}); })
const res = await fetchDoc(url); const res = await fetchDoc(url)
const html = await res.text(); const html = await res.text()
writeHtml(pipWindow, html); writeHtml(pipWindow, html)
navGuard(pipWindow); navGuard(pipWindow)
} }
function writeHtml(pipWindow: Window, html: string) { function writeHtml(pipWindow: Window, html: string) {
const nonce = getDomNonce(document); const nonce = getDomNonce(document)
let escaped = replaceHtmlNonce(html, nonce); let escaped = replaceHtmlNonce(html, nonce)
if (window.trustedTypes) { if (window.trustedTypes) {
const escapeHTMLPolicy = window.trustedTypes.createPolicy("escapePolicy", { const escapeHTMLPolicy = window.trustedTypes.createPolicy("escapePolicy", {
createHTML: (string: string) => string, createHTML: (string: string) => string,
}); })
escaped = escapeHTMLPolicy.createHTML(escaped); escaped = escapeHTMLPolicy.createHTML(escaped)
} }
pipWindow.document.open(); pipWindow.document.open()
pipWindow.document.write(escaped); pipWindow.document.write(escaped)
pipWindow.document.close(); pipWindow.document.close()
document.dispatchEvent(new CustomEvent(PipEventName.loaded)); dispatchContentEvent({ type: "loaded", detail: {} })
const base = document.createElement("base"); const base = document.createElement("base")
base.target = "_blank"; base.target = "_blank"
pipWindow.document.head.append(base); pipWindow.document.head.append(base)
removePrerenderRules(pipWindow.document); removePrerenderRules(pipWindow.document)
} }
function navGuard(pipWindow: Window) { function navGuard(pipWindow: Window) {
const handleBeforeUnload = (e: BeforeUnloadEvent) => { const handleBeforeUnload = (e: BeforeUnloadEvent) => {
console.log("before unload: ", e); console.log("before unload: ", e)
e.preventDefault(); e.preventDefault()
e.returnValue = true; e.returnValue = true
}; }
const handleClick = (e: MouseEvent) => { const handleClick = (e: MouseEvent) => {
const target = e.target as Element | null; const target = e.target as Element | null
if (!target) return; if (!target) return
console.log("click ", e); console.log("click ", e)
const anchor = target.closest<HTMLAnchorElement>("a, [href]"); const anchor = target.closest<HTMLAnchorElement>("a, [href]")
if (!anchor) return; if (!anchor) return
const href = anchor.getAttribute("href"); const href = anchor.getAttribute("href")
if (!href) return; if (!href) return
if (href.slice(0, 1) == "#") { if (href.slice(0, 1) == "#") {
e.preventDefault(); e.preventDefault()
pipWindow.location.hash = href; pipWindow.location.hash = href
return; return
} }
console.log(">> href: ", href, e.defaultPrevented); console.log(">> href: ", href, e.defaultPrevented)
if ( if (
href.startsWith(location.origin) || href.startsWith(location.origin) ||
!href.startsWith(location.protocol) !href.startsWith(location.protocol)
) { ) {
if (!e.defaultPrevented) { if (!e.defaultPrevented) {
e.preventDefault(); e.preventDefault()
// copilotReopen({ url: new URL(anchor.href, location.origin).href }); // copilotReopen({ url: new URL(anchor.href, location.origin).href });
copilotNavigateTo(new URL(anchor.href, location.origin).href); copilotNavigateTo(new URL(anchor.href, location.origin).href)
} }
return; return
}
} }
};
pipWindow.addEventListener("beforeunload", handleBeforeUnload); pipWindow.addEventListener("beforeunload", handleBeforeUnload)
pipWindow.addEventListener("click", handleClick); pipWindow.addEventListener("click", handleClick)
} }
function resetWindow(win: Window) { function resetWindow(win: Window) {
...@@ -195,38 +196,38 @@ function resetWindow(win: Window) { ...@@ -195,38 +196,38 @@ function resetWindow(win: Window) {
// } // }
Object.keys(win).forEach((k) => { Object.keys(win).forEach((k) => {
if (k == "location") return; if (k == "location") return
try { try {
win[k as any] = initWindow[k]; win[k as any] = initWindow[k]
} catch (e) { } catch (e) {
console.error(e); console.error(e)
} }
}); })
Object.keys(win.document).forEach((k) => { Object.keys(win.document).forEach((k) => {
if (k == "location") return; if (k == "location") return
try { try {
win.document[k as "body"] = initWindow.document[k]; win.document[k as "body"] = initWindow.document[k]
} catch (e) { } catch (e) {
console.error(e); console.error(e)
} }
}); })
} }
function test(doc: Document, win: Window) { function test(doc: Document, win: Window) {
doc.addEventListener("DOMContentLoaded", (e) => { doc.addEventListener("DOMContentLoaded", (e) => {
console.warn("DOMContentLoaded", e); console.warn("DOMContentLoaded", e)
}); })
win.addEventListener("beforeunload", (e) => { win.addEventListener("beforeunload", (e) => {
console.warn("beforeunload", e); console.warn("beforeunload", e)
}); })
win.addEventListener("load", (e) => { win.addEventListener("load", (e) => {
console.warn("load", e); console.warn("load", e)
}); })
win.addEventListener("unload", (e) => { win.addEventListener("unload", (e) => {
console.warn("unload", e); console.warn("unload", e)
}); })
} }
import { createApp, type Component } from "vue"; import { createApp, type Component } from "vue"
import { createI18n } from "vue-i18n"; import App from "./App.vue"
import App from "./App.vue"; import { MessageType } from "@/types"
import { MessageType } from "@/types"; import { i18n } from "@/utils/i18n"
import "@/assets/main.css"; import "@/assets/main.css"
const isSelf = chrome.runtime.id === location.host; const isSelf = chrome.runtime?.id === location.host
export function mount(App: Component, doc = document) { export function mount(App: Component, doc = document) {
const outter = doc.createElement("anything-copilot"); const outter = doc.createElement("anything-copilot")
const root = isSelf ? outter : outter.attachShadow({ mode: "open" }); const root = isSelf ? outter : outter.attachShadow({ mode: "open" })
const appContainer = doc.createElement("div"); const appContainer = doc.createElement("div")
appContainer.id = "app"; appContainer.id = "app"
const link = doc.createElement("link"); const link = doc.createElement("link")
link.rel = "stylesheet"; link.rel = "stylesheet"
link.href = chrome.runtime.getURL("/index.css"); link.href = chrome.runtime?.getURL("/index.css")
root.append(link); root.append(link)
root.append(appContainer); root.append(appContainer)
doc.documentElement.append(outter); doc.documentElement.append(outter)
const app = createApp(App); const app = createApp(App)
const i18n = createI18n({}); app.use(i18n)
app.use(i18n); app.mount(appContainer)
app.mount(appContainer);
} }
export function mountApp(doc = document) { export function mountApp(doc = document) {
mount(App, doc); mount(App, doc)
chrome.runtime.sendMessage({ type: MessageType.contentMount }); chrome.runtime?.sendMessage({ type: MessageType.contentMount })
} }
export function waitMountApp() { export function waitMountApp() {
if (document.readyState == "interactive") { if (document.readyState == "interactive") {
mountApp(); mountApp()
} else { } else {
const hanldeStateChange = () => { const hanldeStateChange = () => {
if (document.readyState == "interactive") { if (document.readyState == "interactive") {
document.removeEventListener("readystatechange", hanldeStateChange); document.removeEventListener("readystatechange", hanldeStateChange)
mountApp(); mountApp()
} }
}; }
document.addEventListener("readystatechange", hanldeStateChange); document.addEventListener("readystatechange", hanldeStateChange)
} }
} }
{
"openInPip": "በCopilot የምጥ ገጽ ክፍል ክፈት",
"other": "ሌሎች",
"clickHere": "እዚህ ጠቅ ያድርጉ",
"minimize": "ምንጭ",
"moveAside": "ቀስት ለማግኘት",
"close": "ዝጋ",
"refresh": "አስተካክል",
"protectedTabTips": "ይህ ገጽ በመሳሪያ መሳሪያ የተጠበቀ ነው"
}
\ No newline at end of file
{
"openInPip": "افتح في نافذة كوبيلوت",
"other": "آخر",
"clickHere": "انقر هنا",
"minimize": "تصغير",
"moveAside": "تحريك جانباً",
"close": "إغلاق",
"refresh": "تحديث",
"protectedTabTips": "هذه الصفحة محمية بواسطة المتصفح"
}
\ No newline at end of file
{
"openInPip": "Отвори в Copilot прозорец",
"other": "Друго",
"clickHere": "Цъкни тук",
"minimize": "Минимизиране",
"moveAside": "Премести настрана",
"close": "Затвори",
"refresh": "Обнови",
"protectedTabTips": "Тази страница е защитена от браузъра"
}
\ No newline at end of file
{
"openInPip": "Copilot উইন্ডোতে খুলুন",
"other": "অন্যান্য",
"clickHere": "এখানে ক্লিক করুন",
"minimize": "সর্বনিম্ন",
"moveAside": "পাশে সরান",
"close": "বন্ধ করুন",
"refresh": "পুনরায় লোড করুন",
"protectedTabTips": "এই পৃষ্ঠাটি ব্রাউজার দ্বারা সুরক্ষিত"
}
\ No newline at end of file
{
"openInPip": "Obri en finestra del Copilot",
"other": "Altres",
"clickHere": "Clica aquí",
"minimize": "Minimitza",
"moveAside": "Aparta",
"close": "Tanca",
"refresh": "Refresca",
"protectedTabTips": "Aquesta pàgina està protegida pel navegador"
}
\ No newline at end of file
{
"openInPip": "Otevřít v okně Copilot",
"other": "Další",
"clickHere": "Klikněte zde",
"minimize": "Minimalizovat",
"moveAside": "Přesunout stranou",
"close": "Zavřít",
"refresh": "Obnovit",
"protectedTabTips": "Tato stránka je chráněna prohlížečem"
}
\ No newline at end of file
{
"openInPip": "Åbn i Copilot-vindue",
"other": "Andet",
"clickHere": "Klik her",
"minimize": "Minimer",
"moveAside": "Flyt til side",
"close": "Luk",
"refresh": "Opdater",
"protectedTabTips": "Denne side er beskyttet af browseren"
}
\ No newline at end of file
{
"openInPip": "In Copilot-Fenster öffnen",
"other": "Andere",
"clickHere": "Hier klicken",
"minimize": "Minimieren",
"moveAside": "Beiseite bewegen",
"close": "Schließen",
"refresh": "Aktualisieren",
"protectedTabTips": "Diese Seite ist durch den Browser geschützt"
}
\ No newline at end of file
{
"openInPip": "Ανοίξτε στο παράθυρο του Copilot",
"other": "Άλλο",
"clickHere": "Κάντε κλικ εδώ",
"minimize": "Ελαχιστοποίηση",
"moveAside": "Μετακίνηση στην πλευρά",
"close": "Κλείσιμο",
"refresh": "Ανανέωση",
"protectedTabTips": "Αυτή η σελίδα προστατεύεται από τον περιηγητή"
}
\ No newline at end of file
{
"openInPip": "Open in Copilot window",
"other": "Other",
"clickHere": "Click Here",
"minimize": "Minimize",
"moveAside": "Move aside",
"close": "Close",
"refresh": "Refresh",
"protectedTabTips": "This page is protected by browser"
}
\ No newline at end of file
{
"openInPip": "Abrir en ventana de Copilot",
"other": "Otro",
"clickHere": "Haz clic aquí",
"minimize": "Minimizar",
"moveAside": "Mover a un lado",
"close": "Cerrar",
"refresh": "Actualizar",
"protectedTabTips": "Esta página está protegida por el navegador"
}
\ No newline at end of file
{
"openInPip": "Abrir en ventana de Copilot",
"other": "Otro",
"clickHere": "Haz clic aquí",
"minimize": "Minimizar",
"moveAside": "Mover a un lado",
"close": "Cerrar",
"refresh": "Refrescar",
"protectedTabTips": "Esta página está protegida por el navegador"
}
\ No newline at end of file
{
"openInPip": "Avage Copiloti aken",
"other": "Muu",
"clickHere": "Klõpsake siin",
"minimize": "Vähenda",
"moveAside": "Liiguta kõrvale",
"close": "Sulge",
"refresh": "Värskenda",
"protectedTabTips": "See lehekülg on kaitstud brauseri poolt"
}
\ No newline at end of file
{
"openInPip": "باز کردن در پنجره Copilot",
"other": "سایر",
"clickHere": "اینجا کلیک کنید",
"minimize": "کمینه کردن",
"moveAside": "جابجا شدن",
"close": "بستن",
"refresh": "تازه‌سازی",
"protectedTabTips": "این صفحه توسط مرورگر محافظت می‌شود"
}
\ No newline at end of file
{
"openInPip": "Avaa Copilot-ikkunassa",
"other": "Muu",
"clickHere": "Klikkaa tästä",
"minimize": "Pienennä",
"moveAside": "Siirrä sivuun",
"close": "Sulje",
"refresh": "Päivitä",
"protectedTabTips": "Tämä sivu on suojattu selaimen toimesta"
}
\ No newline at end of file
{
"openInPip": "Buksan sa bintana ng Copilot",
"other": "Iba pa",
"clickHere": "I-click Dito",
"minimize": "Ibawas",
"moveAside": "Ilipat sa tabi",
"close": "Isara",
"refresh": "I-refresh",
"protectedTabTips": "Protektado ng browser ang pahinang ito"
}
\ No newline at end of file
{
"openInPip": "Ouvrir dans la fenêtre Copilot",
"other": "Autre",
"clickHere": "Cliquez ici",
"minimize": "Minimiser",
"moveAside": "Déplacer de côté",
"close": "Fermer",
"refresh": "Actualiser",
"protectedTabTips": "Cette page est protégée par le navigateur"
}
\ No newline at end of file
{
"openInPip": "કોપિલોટ વિંડોમાં ખોલો",
"other": "અન્ય",
"clickHere": "અહીં ક્લિક કરો",
"minimize": "ઘટાડો",
"moveAside": "અપર ચાલો",
"close": "બંધ",
"refresh": "રિફ્રેશ",
"protectedTabTips": "આ પાનું બ્રાઉઝર દ્વારા સુરક્ષિત છે"
}
\ No newline at end of file
{
"openInPip": "פתח בחלון Copilot",
"other": "אחר",
"clickHere": "לחץ כאן",
"minimize": "מזער",
"moveAside": "הזז בצד",
"close": "סגור",
"refresh": "רענן",
"protectedTabTips": "דף זה מוגן על ידי הדפדפן"
}
\ No newline at end of file
{
"openInPip": "कोपाइलट विंडो में खोलें",
"other": "अन्य",
"clickHere": "यहां क्लिक करें",
"minimize": "कम करें",
"moveAside": "दूर हटें",
"close": "बंद करें",
"refresh": "ताज़ा करें",
"protectedTabTips": "यह पृष्ठ ब्राउज़र द्वारा सुरक्षित है"
}
\ No newline at end of file
{
"openInPip": "Otvori u CoPilot prozoru",
"other": "Ostalo",
"clickHere": "Kliknite ovdje",
"minimize": "Smanji",
"moveAside": "Pomakni na stranu",
"close": "Zatvori",
"refresh": "Osvježi",
"protectedTabTips": "Ova stranica je zaštićena preglednikom"
}
\ No newline at end of file
{
"openInPip": "Megnyitás a Copilot ablakban",
"other": "Egyéb",
"clickHere": "Kattintson ide",
"minimize": "Kis méret",
"moveAside": "Elmozdít",
"close": "Bezárás",
"refresh": "Frissítés",
"protectedTabTips": "Ezt az oldalt a böngésző védi"
}
\ No newline at end of file
{
"openInPip": "Buka dalam jendela Copilot",
"other": "Lainnya",
"clickHere": "Klik Disini",
"minimize": "Miminimalkan",
"moveAside": "Pindah ke samping",
"close": "Tutup",
"refresh": "Segarkan",
"protectedTabTips": "Halaman ini dilindungi oleh browser"
}
\ No newline at end of file
{
"openInPip": "Apri nella finestra di Copilot",
"other": "Altro",
"clickHere": "Clicca qui",
"minimize": "Minimizza",
"moveAside": "Sposta da parte",
"close": "Chiudi",
"refresh": "Aggiorna",
"protectedTabTips": "Questa pagina è protetta dal browser"
}
\ No newline at end of file
{
"openInPip": "コパイロットウィンドウで開く",
"other": "その他",
"clickHere": "ここをクリック",
"minimize": "最小化",
"moveAside": "一旦寄せる",
"close": "閉じる",
"refresh": "更新",
"protectedTabTips": "このページはブラウザによって保護されています"
}
\ No newline at end of file
{
"openInPip": "ಕೊಪಿಲೋಟ್ ವಿಂಡೋದಲ್ಲಿ ತೆರೆಯಿರಿ",
"other": "ಇತರೆ",
"clickHere": "ಇಲ್ಲಿ ಕ್ಲಿಕ್ ಮಾಡಿ",
"minimize": "ಕುಗ್ಗಿಸಿ",
"moveAside": "ಬೆಳಕು ನೀಡಿ",
"close": "ಮುಚ್ಚಿ",
"refresh": "ರಿಫ್ರೆಶ್",
"protectedTabTips": "ಈ ಪುಟವನ್ನು ಬ್ರೌಸರ್‌ ರಕ್ಷಿಸಿದೆ"
}
\ No newline at end of file
{
"openInPip": "Copilot 창에서 열기",
"other": "기타",
"clickHere": "여기를 클릭하세요",
"minimize": "최소화",
"moveAside": "옆으로 이동",
"close": "닫기",
"refresh": "새로고침",
"protectedTabTips": "이 페이지는 브라우저에 의해 보호됩니다"
}
\ No newline at end of file
{
"openInPip": "Atidaryti „Copilot“ langą",
"other": "Kitas",
"clickHere": "Spustelėkite čia",
"minimize": "Mažinti",
"moveAside": "Nustumti šalin",
"close": "Uždaryti",
"refresh": "Atnaujinti",
"protectedTabTips": "Šis puslapis apsaugotas naršyklės"
}
\ No newline at end of file
{
"openInPip": "Atvērt Copilot logā",
"other": "Cits",
"clickHere": "Noklikšķiniet šeit",
"minimize": "Minimizēt",
"moveAside": "Pagriezt malā",
"close": "Aizvērt",
"refresh": "Atsvaidzināt",
"protectedTabTips": "Šī lapa ir aizsargāta ar pārlūkprogrammu"
}
\ No newline at end of file
{
"openInPip": "കോപിലോറ്റ് ജിപ്പി ജോളം തുറക്കുക",
"other": "മറ്റ്",
"clickHere": "ഇവിടെ ക്ലിക്ക് ചെയ്യുക",
"minimize": "ഇടത്തരിക്കുക",
"moveAside": "വലത്തരിക്കുക",
"close": "അടുത്തവെക്കുക",
"refresh": "പുതുക്കുക",
"protectedTabTips": "ഈ പേജ് ബ്രൗസർ സംരക്ഷിക്കുന്നു"
}
\ No newline at end of file
{
"openInPip": "कोपायलटच्या विंडोमध्ये उघडा",
"other": "इतर",
"clickHere": "येथे क्लिक करा",
"minimize": "कमी करा",
"moveAside": "पाठवा",
"close": "बंद करा",
"refresh": "ताज्या करा",
"protectedTabTips": "हे पृष्ठ ब्राउझरने संरक्षित केले आहे"
}
\ No newline at end of file
{
"openInPip": "Buka dalam tetingkap Copilot",
"other": "Lain-lain",
"clickHere": "Klik di sini",
"minimize": "Kurangkan",
"moveAside": "Bersisih",
"close": "Tutup",
"refresh": "Muat semula",
"protectedTabTips": "Halaman ini dilindungi oleh pelayar"
}
\ No newline at end of file
{
"openInPip": "Open in Copilot-venster",
"other": "Andere",
"clickHere": "Klik hier",
"minimize": "Minimaliseren",
"moveAside": "Opzij zetten",
"close": "Sluiten",
"refresh": "Vernieuwen",
"protectedTabTips": "Deze pagina is beschermd door de browser"
}
\ No newline at end of file
{
"openInPip": "Åpne i Copilot-vindu",
"other": "Annet",
"clickHere": "Klikk her",
"minimize": "Minimer",
"moveAside": "Flytt til side",
"close": "Lukk",
"refresh": "Oppdater",
"protectedTabTips": "Denne siden er beskyttet av nettleseren"
}
\ No newline at end of file
{
"openInPip": "Otwórz w oknie Copilot",
"other": "Inne",
"clickHere": "Kliknij tutaj",
"minimize": "Zminimalizuj",
"moveAside": "Przesuń na bok",
"close": "Zamknij",
"refresh": "Odśwież",
"protectedTabTips": "Ta strona jest chroniona przez przeglądarkę"
}
\ No newline at end of file
{
"openInPip": "Abrir na janela do Copilot",
"other": "Outro",
"clickHere": "Clique aqui",
"minimize": "Minimizar",
"moveAside": "Mover para o lado",
"close": "Fechar",
"refresh": "Atualizar",
"protectedTabTips": "Esta página está protegida pelo navegador"
}
\ No newline at end of file
{
"openInPip": "Abrir em janela do Copilot",
"other": "Outro",
"clickHere": "Clique aqui",
"minimize": "Minimizar",
"moveAside": "Mover para o lado",
"close": "Fechar",
"refresh": "Atualizar",
"protectedTabTips": "Esta página está protegida pelo navegador"
}
\ No newline at end of file
{
"openInPip": "Deschide în fereastra Copilot",
"other": "Altele",
"clickHere": "Click aici",
"minimize": "Minimizează",
"moveAside": "Mută în lateral",
"close": "Închide",
"refresh": "Reîmprospătează",
"protectedTabTips": "Această pagină este protejată de browser"
}
\ No newline at end of file
{
"openInPip": "Открыть в окне Copilot",
"other": "Другое",
"clickHere": "Нажмите здесь",
"minimize": "Минимизировать",
"moveAside": "Убрать в сторону",
"close": "Закрыть",
"refresh": "Обновить",
"protectedTabTips": "Эта страница защищена браузером"
}
\ No newline at end of file
{
"openInPip": "Otvoriť v okne Copilot",
"other": "Iné",
"clickHere": "Kliknite sem",
"minimize": "Minimalizovať",
"moveAside": "Presunúť bokom",
"close": "Zatvoriť",
"refresh": "Obnoviť",
"protectedTabTips": "Táto stránka je chránená prehliadačom"
}
\ No newline at end of file
{
"openInPip": "Odpri v oknu Copilot",
"other": "Drugo",
"clickHere": "Klikni tukaj",
"minimize": "Minimaliziraj",
"moveAside": "Premakni na stran",
"close": "Zapri",
"refresh": "Osveži",
"protectedTabTips": "Ta stran je zaščitena z brskalnikom"
}
\ No newline at end of file
{
"openInPip": "Отвори у прозору Копилота",
"other": "Остало",
"clickHere": "Кликни овде",
"minimize": "Minimiziraj",
"moveAside": "Pomeri na stranu",
"close": "Zatvori",
"refresh": "Osveži",
"protectedTabTips": "Ова страница је заштићена прегледачем"
}
\ No newline at end of file
{
"openInPip": "Öppna i Copilot-fönster",
"other": "Annan",
"clickHere": "Klicka här",
"minimize": "Minimera",
"moveAside": "Flytta åt sidan",
"close": "Stäng",
"refresh": "Uppdatera",
"protectedTabTips": "Denna sida skyddas av webbläsaren"
}
\ No newline at end of file
{
"openInPip": "Fungua kwenye dirisha la Copilot",
"other": "Nyingine",
"clickHere": "Bonyeza Hapa",
"minimize": "Kupunguza",
"moveAside": "Hama kando",
"close": "Funga",
"refresh": "Sasisha",
"protectedTabTips": "Ukurasa huu umelindwa na kivinjari"
}
\ No newline at end of file
{
"openInPip": "கிளிக் மேல் இங்கே திற",
"other": "பிற",
"clickHere": "இங்கே திட்டமிடு",
"minimize": "சுருக்கப்படுதல்",
"moveAside": "பக்கத்திற்கு நீக்கு",
"close": "மூடு",
"refresh": "புதுப்பிக்க",
"protectedTabTips": "இந்த பக்கம் உலாவியால் பாதுகாக்கப்படுகிறது"
}
\ No newline at end of file
{
"openInPip": "కొపీలోట్ విండోలో తెరలంచండి",
"other": "ఇతర",
"clickHere": "ఇక్కడ రాండండి",
"minimize": "తక్కువ చేయడం",
"moveAside": "చేరుకుందండి",
"close": "మూసివేయి",
"refresh": "తాజాకరించండి",
"protectedTabTips": "ఈ పేజీని బ్రౌజర్ రక్షించింది"
}
\ No newline at end of file
{
"openInPip": "เปิดในหน้าต่าง Copilot",
"other": "อื่น ๆ",
"clickHere": "คลิกที่นี่",
"minimize": "ย่อ",
"moveAside": "เลื่อนข้างหลัง",
"close": "ปิด",
"refresh": "รีเฟรช",
"protectedTabTips": "หน้านี้ได้รับการป้องกันโดยเบราว์เซอร์"
}
\ No newline at end of file
{
"openInPip": "Copilot penceresinde aç",
"other": "Diğer",
"clickHere": "Buraya tıklayın",
"minimize": "Küçült",
"moveAside": "Kenara Taşı",
"close": "Kapat",
"refresh": "Yenile",
"protectedTabTips": "Bu sayfa tarayıcı tarafından korunmaktadır"
}
\ No newline at end of file
{
"openInPip": "Відкрити у вікні Copilot",
"other": "Інше",
"clickHere": "Натисніть тут",
"minimize": "Мінімізувати",
"moveAside": "Відкрити сторонній",
"close": "Закрити",
"refresh": "Оновити",
"protectedTabTips": "Ця сторінка захищена браузером"
}
\ No newline at end of file
{
"openInPip": "Mở trong cửa sổ Copilot",
"other": "Khác",
"clickHere": "Nhấp vào đây",
"minimize": "Thu nhỏ",
"moveAside": "Di chuyển sang một bên",
"close": "Đóng",
"refresh": "Làm mới",
"protectedTabTips": "Trang này được bảo vệ bởi trình duyệt"
}
\ No newline at end of file
{
"openInPip": "在 Copilot 窗口中打开",
"other": "其他",
"clickHere": "点击这里",
"minimize": "最小化",
"moveAside": "移到旁边",
"close": "关闭",
"refresh": "刷新",
"protectedTabTips": "当前页面受浏览器保护"
}
\ No newline at end of file
{
"openInPip": "在 Copilot 窗口中打開",
"other": "其他",
"clickHere": "點擊這裡",
"minimize": "最小化",
"moveAside": "移到旁邊",
"close": "關閉",
"refresh": "刷新",
"protectedTabTips": "此頁面受到瀏覽器保護"
}
\ No newline at end of file
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted, onUnmounted, computed, reactive, watch } from "vue"; import { ref, onMounted, onUnmounted, computed, reactive, watch } from "vue"
import { emptyTab, checkContent } from "@/utils/ext"; import { emptyTab, checkContent } from "@/utils/ext"
import { items } from "@/content/store"; import { items, pipWindow } from "@/store"
import IconHide from "@/components/icons/IconHide.vue"; import PipWindowActions from "@/components/popup/PipWindowActions.vue"
import IconArrowCircleRight from "@/components/icons/IconArrowCircleRight.vue"; import IconThumbUp from "@/components/icons/IconThumbUp.vue"
import IconClose from "@/components/icons/IconClose.vue"; import IconThumbDown from "@/components/icons/IconThumbDown.vue"
import IconGithub from "@/components/icons/IconGithub.vue"
const activeTab = ref<chrome.tabs.Tab>(emptyTab); import IconDiscord from "@/components/icons/IconDiscord.vue"
const manifest = reactive(chrome.runtime.getManifest()); import IconXLogo from "@/components/icons/IconXLogo.vue"
const avaiable = ref(false); import { useI18n } from "@/utils/i18n"
import { getStoreUrl } from "@/utils/store"
const pipWindowId = ref(0);
const { t } = useI18n()
const pipWindowInfo = reactive({
windowId: 0, const activeTab = ref<chrome.tabs.Tab>(emptyTab)
isOpen: false, const manifest = reactive(chrome.runtime.getManifest())
favIconUrl: "", const avaiable = ref(false)
title: "",
});
const handleLocalChange = (changes: { const handleLocalChange = (changes: {
[key: string]: chrome.storage.StorageChange; [key: string]: chrome.storage.StorageChange
}) => { }) => {
if (changes.pipWindowId) { if (changes.pipWindowId) {
pipWindowId.value = changes.pipWindowId.newValue; pipWindow.id = changes.pipWindowId.newValue
} }
}; }
watch(pipWindowId, async (id) => {
if (id) {
const tabs = await chrome.tabs.query({ windowId: id });
console.log("pip window tabs: ", tabs);
if (tabs && tabs.length == 1) {
pipWindowInfo.isOpen = true;
pipWindowInfo.favIconUrl = tabs[0].favIconUrl || "";
pipWindowInfo.title = tabs[0].title || "";
}
return;
}
pipWindowInfo.isOpen = false;
});
onMounted(() => { onMounted(() => {
chrome.tabs.query({ active: true, currentWindow: true }).then((tabs) => { chrome.tabs.query({ active: true, currentWindow: true }).then((tabs) => {
console.log(tabs); console.log(tabs)
if (tabs[0]) activeTab.value = tabs[0]; if (tabs[0]) activeTab.value = tabs[0]
checkContent(tabs[0].id!).then((value) => { checkContent(tabs[0].id!).then((value) => {
avaiable.value = value; avaiable.value = value
}); })
}); })
chrome.storage.local chrome.storage.local
.get({ pipWindowId: null }) .get({ pipWindowId: null })
.then(({ pipWindowId: id }) => { .then(({ pipWindowId: id }) => {
if (id) { if (id) {
pipWindowId.value = id; pipWindow.id = id
} }
}); })
chrome.storage.local.onChanged.addListener(handleLocalChange); chrome.storage.local.onChanged.addListener(handleLocalChange)
}); })
onUnmounted(() => { onUnmounted(() => {
chrome.storage.local.onChanged.removeListener(handleLocalChange); chrome.storage.local.onChanged.removeListener(handleLocalChange)
}); })
const host = computed({ const host = computed({
get: () => { get: () => {
if (!activeTab.value.url) return ""; if (!activeTab.value.url) return ""
const u = new URL(activeTab.value.url); const u = new URL(activeTab.value.url)
return u.host; return u.host
}, },
set: () => {}, set: () => {},
}); })
async function handleWriteHtml() { async function handleWriteHtml() {
const tabs = await chrome.tabs.query({ active: true, currentWindow: true }); const tabs = await chrome.tabs.query({ active: true, currentWindow: true })
const tab = tabs[0]; const tab = tabs[0]
if (tab) { if (tab) {
chrome.tabs.sendMessage(tab.id!, { chrome.tabs.sendMessage(tab.id!, {
type: "pip", type: "pip",
...@@ -87,96 +69,87 @@ async function handleWriteHtml() { ...@@ -87,96 +69,87 @@ async function handleWriteHtml() {
url: tab.url, url: tab.url,
mode: "write-html", mode: "write-html",
}, },
}); })
} }
} }
async function handleClickLaunch(url: string) { async function handleClickLaunch(url: string) {
console.log(activeTab.value); console.log(activeTab.value)
chrome.runtime.sendMessage({ chrome.runtime.sendMessage({
type: "bg-pip-launch", type: "bg-pip-launch",
url, url,
}); })
} }
async function handleUpdatePip(state: "normal" | "minimized") { function feedback() {
chrome.windows.update(pipWindowId.value, { open("https://tawk.to/anythingcopilot", "_blank")
state,
});
} }
function fivestar() {
async function closePip() { open(
await chrome.windows.update(pipWindowId.value, { state: "normal" }); getStoreUrl({
chrome.windows.remove(pipWindowId.value); id: chrome.runtime.id,
name: "anything-copilot",
reviews: true,
})
),
"_blank"
} }
</script> </script>
<template> <template>
<main class="w-[300px] p-4 mx-auto"> <main class="w-[300px] p-4 mx-auto">
<div> <div class="flex">
<div class="mr-auto">
<span class="font-bold opacity-50">Anything Copilot</span> <span class="font-bold opacity-50">Anything Copilot</span>
<span class="mx-2 text-sm opacity-50">{{ manifest.version }}</span> <span class="mx-2 text-sm opacity-50">{{ manifest.version }}</span>
</div> </div>
<div class="flex items-center gap-1">
<div v-if="pipWindowInfo.isOpen">
<div class="text-sm flex items-center truncate mt-6">
<span
class="w-4 h-4 inline-block mr-2 rounded"
:style="{
background:
'center / contain url(' + pipWindowInfo.favIconUrl + ')',
}"
></span>
<span>{{ pipWindowInfo.title }}</span>
</div>
<div class="flex gap-2">
<button <button
class="primary-btn flex items-center mt-2 rounded-lg p-2 px-3" class="p-1 rounded-lg hover:animate-ping hover:text-rose-600 hover:bg-rose-600/20"
@click="handleUpdatePip('minimized')" @click="fivestar"
> >
<IconHide /> <IconThumbUp class="w-5 h-5 opacity-60" />
</button>
<button
class="primary-btn flex items-center mt-2 rounded-lg p-2 px-3"
@click="handleUpdatePip('normal')"
>
<IconArrowCircleRight />
</button> </button>
<button <button
class="primary-btn flex items-center mt-2 rounded-lg p-2 px-3" class="p-1 rounded-lg hover:animate-bounce hover:text-blue-600 hover:bg-blue-600/20"
@click="closePip" @click="feedback"
> >
<IconClose /> <IconThumbDown class="w-5 h-5 opacity-60" />
</button> </button>
</div> </div>
</div> </div>
<div class="text-sm truncate mt-6"> <PipWindowActions />
{{ host }}
<span class="text-rose-800" v-if="!avaiable">{{ <div class="flex items-center text-sm mt-6">
"is protected by browser" <span
}}</span> class="w-4 h-4 inline-block mr-2 rounded"
:style="{
background:
'#8882 center / contain url(' + activeTab?.favIconUrl + ')',
}"
></span>
<span class="inline-block truncate">{{ host }}</span>
</div> </div>
<button <button
:class="[ :class="[
'primary-btn w-full flex items-center mt-2 rounded-lg p-2 px-3', 'w-full bg-sky-800 text-white flex items-center mt-2 rounded-lg p-2 px-3',
{ {
'cursor-not-allowed': !avaiable, 'cursor-not-allowed': !avaiable,
}, },
]" ]"
@click="handleWriteHtml" @click="handleWriteHtml"
> >
<span <span class="text-base">{{ t("openInPip") }}</span>
class="w-5 h-5 inline-block mr-3 rounded"
:style="{
background: 'center / contain url(' + activeTab.favIconUrl + ')',
}"
>
</span>
<span class="text-base">Open in Copilot window</span>
</button> </button>
<div class="text-sm mt-6">other</div> <div v-if="!avaiable" class="text-sm leading-4 text-rose-800">
{{ t("protectedTabTips") }}
</div>
<div class="text-sm mt-6">{{ t("other") }}</div>
<button <button
v-for="item of items" v-for="item of items"
class="primary-btn w-full flex items-center mt-3 rounded-lg p-2 px-3" class="primary-btn w-full flex items-center mt-3 rounded-lg p-2 px-3"
...@@ -192,7 +165,32 @@ async function closePip() { ...@@ -192,7 +165,32 @@ async function closePip() {
<span class="text-base">{{ item.title }}</span> <span class="text-base">{{ item.title }}</span>
</button> </button>
<div class="mt-6"></div> <div class="mt-3 flex items-center justify-center opacity-60">
<a
class="px-1.5"
href="https://github.com/baotlake/anything-copilot"
target="_blank"
ref="noreferrer"
>
<IconGithub class="w-[15px] h-[15px] text-[rgb(var(--fg-rgb))]" />
</a>
<a
class="px-1.5"
href="https://discord.gg/aYSxbF8em9"
target="_blank"
ref="noreferrer"
>
<IconDiscord class="w-[16px] h-[16px] text-[rgb(var(--fg-rgb))]" />
</a>
<a
class="px-1.5"
href="https://twitter.com/baotlake"
target="_blank"
ref="noreferrer"
>
<IconXLogo class="w-[12px] h-[12px] text-[rgb(var(--fg-rgb))]" />
</a>
</div>
</main> </main>
</template> </template>
......
import "@/assets/main.css"; import "@/assets/main.css";
import { createApp } from "vue"; import { createApp } from "vue";
import { createI18n } from "vue-i18n";
import Popup from "./Popup.vue"; import Popup from "./Popup.vue";
import { i18n } from "@/utils/i18n";
const i18n = createI18n({});
const app = createApp(Popup); const app = createApp(Popup);
app.use(i18n); app.use(i18n);
app.mount("#app"); app.mount("#app");
\ No newline at end of file
...@@ -29,8 +29,12 @@ export const items = reactive([ ...@@ -29,8 +29,12 @@ export const items = reactive([
}, },
]); ]);
export const pipWindowRef = ref<Window | null>(null); export const pipWindow = reactive({
export const pipWindowInfo = ref<chrome.windows.Window | null>(null); id: 0,
window: null as Window | null,
windowsWindow: null as chrome.windows.Window | null,
tab: null as chrome.tabs.Tab | null,
});
export const pipLoading = reactive({ export const pipLoading = reactive({
isLoading: true, isLoading: true,
......
...@@ -6,8 +6,8 @@ export enum MessageType { ...@@ -6,8 +6,8 @@ export enum MessageType {
bgPipLaunch = "bg-pip-launch", bgPipLaunch = "bg-pip-launch",
pipLaunch = "pip-launch", pipLaunch = "pip-launch",
contentMount = "content-mount", contentMount = "content-mount",
minimizePipWin = "minimize-pip-win",
getPipWinInfo = "get-pip-win-info", getPipWinInfo = "get-pip-win-info",
pipWinInfo = "pip-win-info", pipWinInfo = "pip-win-info",
updatePipWin = "update-pip-win", updateWindow = "update-window",
removeWindow = "remove-window",
} }
...@@ -4,9 +4,3 @@ export type PipOptions = { ...@@ -4,9 +4,3 @@ export type PipOptions = {
mode: "iframe" | "write-html" | "move-element"; mode: "iframe" | "write-html" | "move-element";
isCopyStyle?: boolean; isCopyStyle?: boolean;
}; };
export enum PipEventName {
pip = "anything-copilot-pip",
loaded = "anything-copilot-loaded",
loadDoc = "anything-copilot-load-doc",
}
import { createI18n, useI18n as vueUseI18n } from "vue-i18n"
import messages from "@intlify/unplugin-vue-i18n/messages"
import type EnMessage from "@/locales/en.json"
type MessageSchema = typeof EnMessage
export function getLocale() {
const language = chrome.i18n.getUILanguage()
let code = language
if (["en-AU", "en-GB", "en-US"].includes(code)) {
code = "en"
}
code = code.replace("_", "-")
if (!code) {
code = "en"
}
return code
}
export const i18n = createI18n<[MessageSchema], string>({
legacy: false,
locale: getLocale(),
messages: messages as { [key: string]: MessageSchema },
})
export function useI18n() {
return vueUseI18n<{ message: MessageSchema }>({ useScope: "global" })
}
type Options = {
id: string
name: string
reviews?: boolean
}
export function getChromeWebStoreUrl({ id, name, reviews }: Options) {
const u = new URL("https://chromewebstore.google.com/")
const slug = name.replace(/[\s/]+/g, "-") || "-"
u.pathname = `/detail/${slug}/${id}`
if (reviews) {
u.pathname = u.pathname + "/reviews"
}
return u.href
}
export function getEdgeAddonsUrl({ id, name, reviews }: Options) {
const u = new URL("https://microsoftedge.microsoft.com")
const slug = name.replace(/[\s/]+/g, "-") || "-"
u.pathname = `/addons/detail/${slug}/${id}`
return u.href
}
export function getStoreUrl(options: Options) {
const id = chrome.runtime.id
if (id.startsWith("lilckelmo")) {
return getChromeWebStoreUrl(options)
}
if (id.startsWith("lbeehbkc")) {
return getEdgeAddonsUrl(options)
}
return getEdgeAddonsUrl({
...options,
id: "lbeehbkcmjaopnlccpjcdgamcabhnanl",
})
}
{ {
"extends": "@vue/tsconfig/tsconfig.dom.json", "extends": "@vue/tsconfig/tsconfig.dom.json",
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"], "include": ["env.d.ts", "src/**/*", "src/**/*.vue", "src/**/*.json"],
"exclude": ["src/**/__tests__/*"], "exclude": ["src/**/__tests__/*"],
"compilerOptions": { "compilerOptions": {
"composite": true, "composite": true,
......
import click
from os import path
import json
@click.group()
@click.option('--d', default='src/locales', help="locales directory")
@click.pass_context
def cli(ctx, d):
locales_dir = path.realpath(path.join(d))
ctx.obj['locales_dir'] = locales_dir
@cli.command()
@click.option('--new', default=['en', 'zh-CN'], multiple=True, help="new language")
@click.option('--old', default='zh-TW', help="old language")
@click.option('--o', default='-', help="output")
@click.pass_context
def extract(ctx, new, old, o=''):
locales_dir = ctx.obj['locales_dir']
new_msgs = {
code: json.load(
open(path.join(locales_dir, code + '.json'), 'r', encoding='utf8')
)
for code in new
}
old_msg = json.load(
open(path.join(locales_dir, old + '.json'), 'r', encoding='utf8')
)
new_keys = {
code: [key for key in new_msgs[code].keys() if key not in old_msg.keys()]
for code in new
}
new_data = {
code: {key: new_msgs[code][key] for key in new_keys[code]}
for code in new
}
print(json.dumps(new_data, ensure_ascii=False))
@cli.command()
@click.option('--t', default='', help='')
@click.pass_context
def update(ctx, t):
locales_dir = ctx.obj['locales_dir']
translated_path = path.realpath(t)
def update_msg(code, content):
filename = code.replace('_', '-')
msg_path = path.join(locales_dir, f'{filename}.json')
try:
msg = json.load(open(msg_path, 'r', encoding='utf8'))
json.dump(
{
**msg,
**content,
},
open(msg_path, 'w+', encoding='utf8'),
ensure_ascii=False,
indent=2
)
except Exception as e:
print('code: ', code)
print(e)
if t.endswith('.jl'):
with open(translated_path, 'r', encoding='utf8') as f:
for line in f:
line_data = json.loads(line)
code = line_data['code']
content = line_data['content']
content = json.loads(content) if type(
content) == str else content
update_msg(code, content)
if t.endswith('.json'):
data = json.load(open(translated_path, 'r', encoding='utf8'))
for code in data:
update_msg(code, data[code])
if __name__ == '__main__':
cli(obj={})
// chrome i18n docs
const extLanguages =
"ar\tArabic\nam\tAmharic\nbg\tBulgarian\nbn\tBengali\nca\tCatalan\ncs\tCzech\nda\tDanish\nde\tGerman\nel\tGreek\nen\tEnglish\nen_AU\tEnglish (Australia)\nen_GB\tEnglish (Great Britain)\nen_US\tEnglish (USA)\nes\tSpanish\nes_419\tSpanish (Latin America and Caribbean)\net\tEstonian\nfa\tPersian\nfi\tFinnish\nfil\tFilipino\nfr\tFrench\ngu\tGujarati\nhe\tHebrew\nhi\tHindi\nhr\tCroatian\nhu\tHungarian\nid\tIndonesian\nit\tItalian\nja\tJapanese\nkn\tKannada\nko\tKorean\nlt\tLithuanian\nlv\tLatvian\nml\tMalayalam\nmr\tMarathi\nms\tMalay\nnl\tDutch\nno\tNorwegian\npl\tPolish\npt_BR\tPortuguese (Brazil)\npt_PT\tPortuguese (Portugal)\nro\tRomanian\nru\tRussian\nsk\tSlovak\nsl\tSlovenian\nsr\tSerbian\nsv\tSwedish\nsw\tSwahili\nta\tTamil\nte\tTelugu\nth\tThai\ntr\tTurkish\nuk\tUkrainian\nvi\tVietnamese\nzh_CN\tChinese (China)\nzh_TW\tChinese (Taiwan)"
const languages = Object.fromEntries(
extLanguages.split("\n").map((v) => v.split("\t"))
)
const edgeLanguages = {
...languages,
bn: "Bangla",
en_GB: "English (United Kingdom)",
en_US: "English (United States)",
sw: "Kiswahili",
es_419: "Spanish (Latin America and the Caribbean)",
}
let codeList = Object.keys(languages)
let exclude = ["en", "zh_CN"]
const isEdge = location.host == "partner.microsoft.com"
async function main(data = window.json) {
const faildList = []
for (let code of codeList) {
let key = code
if (["en", "en_AU", "en_GB", "en_US"].includes(code)) {
key = "en"
}
// he -> iw,
const label = isEdge ? edgeLanguages[code] : languages[code]
if (exclude.includes(code)) {
continue
}
item = data[key] || data[key.replace("_", "-")]
log("code: ", code)
try {
await goDetail(code)
await sleep(800)
if (item.desc) {
desc = item.desc
desc = desc.replace(/\s+😎/, "\n\n\n😎")
desc = desc.replace(/\s+😘/, "\n\n😘")
desc = desc.replace(/\s+😊/, "\n\n😊")
desc = desc.replace(/\s+👻/, "\n\n👻")
await inputDesc(desc)
}
if (item.terms && isEdge) {
await inputTerms(item.terms)
}
await saveDraft()
await sleep(300)
await closeDetails()
} catch (err) {
log(err)
faildList.push({ code, label, message: err.message })
try {
await closeDetails()
} catch (err) {
} finally {
continue
}
}
}
log("faildList: ", faildList)
}
async function sleep(timeout) {
return new Promise((r) => setTimeout(r, timeout))
}
function log(...args) {
console.warn(...args)
}
async function click({ target, waitFor }) {
const el = document.querySelector(target)
el.click()
if (!waitFor) {
return
}
let success = false
let count = 0
while (!success) {
const view = document.querySelector(waitFor)
success = !!view
count++
await sleep(150)
if (count % 30 == 0) {
el.click()
}
log("waiting for: ", waitFor)
}
}
async function dispatchInput(input, value) {
input.value = value
input.dispatchEvent(new Event("input", { bubbles: true }))
input.dispatchEvent(new Event("change", { bubbles: true }))
}
async function goDetail(code) {
if (isEdge) {
const label = edgeLanguages[code]
await click({
target: `#extensionListTable #edit-button[aria-label*="${label}"]`,
})
return
}
const tag = code.replace("_", "-")
await click({
target: `ul[aria-label="Language"] li[data-value="${tag}"]`,
})
}
async function inputDesc(desc) {
if (isEdge) {
const textarea = document.querySelector(
'textarea[aria-label*="Description"]'
)
dispatchInput(textarea, desc)
return
}
const textarea = document.querySelector("article section label textarea")
dispatchInput(textarea, desc)
}
async function saveDraft() {
if (!isEdge) {
return
}
await click({
target: ".command-bar-right he-button:nth-child(1)",
waitFor: ".alert.alert-success",
})
}
async function closeDetails() {
if (!isEdge) {
return
}
await click({
target: ".command-bar-right he-button:nth-child(2)",
waitFor: "#extensionListTable",
})
}
import { fileURLToPath, URL } from "node:url"; import { fileURLToPath, URL, resolve as r } from "node:url";
import { dirname, resolve } from "node:path";
import { defineConfig } from "vite"; import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue"; import vue from "@vitejs/plugin-vue";
import vueJsx from "@vitejs/plugin-vue-jsx"; import vueJsx from "@vitejs/plugin-vue-jsx";
import vueI18n from "@intlify/unplugin-vue-i18n/vite";
import manifest from "./manifest"; import manifest from "./manifest";
import makeManifest from "./utils/manifest-plugin"; import makeManifest from "./utils/manifest-plugin";
// https://vitejs.dev/config/ // https://vitejs.dev/config/
export default defineConfig({ export default defineConfig({
plugins: [vue(), vueJsx(), makeManifest(manifest, { isDev: false })], define: {
__INTLIFY_JIT_COMPILATION__: true,
__INTLIFY_DROP_MESSAGE_COMPILER__: true,
},
plugins: [
vue(),
vueJsx(),
vueI18n({
runtimeOnly: true,
include: resolve(
dirname(fileURLToPath(import.meta.url)),
"./src/locales/**"
),
}),
makeManifest(manifest, { isDev: false }),
],
resolve: { resolve: {
alias: { alias: {
"@": fileURLToPath(new URL("./src", import.meta.url)), "@": fileURLToPath(new URL("./src", import.meta.url)),
"vue-i18n": "vue-i18n/dist/vue-i18n.runtime.esm-bundler.js",
}, },
}, },
build: { build: {
......
import { defineConfig } from "vite"; import { defineConfig } from "vite";
import { fileURLToPath, URL } from "node:url"; import { fileURLToPath, URL } from "node:url";
import { dirname, resolve } from "node:path";
import vue from "@vitejs/plugin-vue"; import vue from "@vitejs/plugin-vue";
import vueJsx from "@vitejs/plugin-vue-jsx"; import vueJsx from "@vitejs/plugin-vue-jsx";
import vueI18n from "@intlify/unplugin-vue-i18n/vite";
export default defineConfig({ export default defineConfig({
server: { server: {
port: 3000, port: 3000,
}, },
plugins: [vue(), vueJsx()], define: {
__INTLIFY_JIT_COMPILATION__: true,
__INTLIFY_DROP_MESSAGE_COMPILER__: true,
},
plugins: [
vue(),
vueJsx(),
vueI18n({
runtimeOnly: true,
include: resolve(
dirname(fileURLToPath(import.meta.url)),
"./src/locales/**"
),
}),
],
resolve: { resolve: {
alias: { alias: {
"@": fileURLToPath(new URL("./src", import.meta.url)), "@": fileURLToPath(new URL("./src", import.meta.url)),
"vue-i18n": "vue-i18n/dist/vue-i18n.runtime.esm-bundler.js",
}, },
}, },
build: { build: {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment