├── .editorconfig
├── .eslintignore
├── .eslintrc.js
├── .github
└── FUNDING.yml
├── .gitignore
├── .npmrc
├── .prettierrc
├── .vscode
├── extensions.json
└── settings.json
├── CHANGELOG.md
├── CONTRIBUTIONS.md
├── README.md
├── _docs
├── _todo.md
├── api-documentation.md
├── api.md
├── store-listing
│ ├── README.md
│ ├── de.txt
│ ├── en.txt
│ ├── es.txt
│ ├── fr.txt
│ ├── hi.txt
│ ├── ja.txt
│ ├── pt_PT.txt
│ ├── ru.txt
│ ├── tr.txt
│ └── zh_CN.txt
└── translations.md
├── index.html
├── package.json
├── postcss.config.js
├── profiles
├── README.md
└── default-profiles.json
├── public
├── _readme.md
├── avatar.png
├── donations
│ ├── bitcoin.png
│ ├── ethereum.png
│ ├── gumroad.png
│ └── paypal.png
├── favicon.ico
├── icons
│ ├── favicon-128x128.png
│ ├── favicon-16x16.png
│ ├── favicon-32x32.png
│ └── favicon-96x96.png
├── logo.png
├── logo.svg
├── robot.png
└── socials
│ ├── github.svg
│ ├── linkedin.svg
│ └── twitter.svg
├── quasar.config.js
├── sites-detection
├── README.md
└── sites-detection.ts
├── src-bex
├── _locales
│ ├── de
│ │ └── messages.json
│ ├── en
│ │ └── messages.json
│ ├── es
│ │ └── messages.json
│ ├── fr
│ │ └── messages.json
│ ├── hi
│ │ └── messages.json
│ ├── ja
│ │ └── messages.json
│ ├── pt_AO
│ │ └── messages.json
│ ├── pt_BR
│ │ └── messages.json
│ ├── pt_CV
│ │ └── messages.json
│ ├── pt_GW
│ │ └── messages.json
│ ├── pt_MZ
│ │ └── messages.json
│ ├── pt_PT
│ │ └── messages.json
│ ├── pt_ST
│ │ └── messages.json
│ ├── pt_TL
│ │ └── messages.json
│ ├── ru
│ │ └── messages.json
│ ├── tr
│ │ └── messages.json
│ ├── zh_CN
│ │ └── messages.json
│ └── zh_SG
│ │ └── messages.json
├── assets
│ └── content.css
├── background.ts
├── bex-flag.d.ts
├── content.ts
├── dom.ts
├── icons
│ ├── icon-128x128.png
│ ├── icon-16x16.png
│ ├── icon-48x48.png
│ └── search.svg
├── libs
│ └── sites-detection.ts
├── manifest.json
└── modules
│ ├── contextMenuComponent.ts
│ └── iframeComponent.ts
├── src
├── App.vue
├── assets
│ └── _readme.md
├── boot
│ ├── .gitkeep
│ └── i18next.ts
├── components
│ ├── OpenAISettingsComponent.vue
│ ├── SearchSettingsComponent.vue
│ └── leftmenu
│ │ ├── ConversationsComponent.vue
│ │ ├── MenuLeftComponent.vue
│ │ ├── ProfilesComponent.vue
│ │ └── SettingsComponent.vue
├── css
│ ├── app.scss
│ └── quasar.variables.scss
├── env.d.ts
├── i18n
│ ├── de
│ │ └── index.ts
│ ├── en
│ │ └── index.ts
│ ├── es
│ │ └── index.ts
│ ├── fr
│ │ └── index.ts
│ ├── hi
│ │ └── index.ts
│ ├── ja
│ │ └── index.ts
│ ├── pt
│ │ └── index.ts
│ ├── ru
│ │ └── index.ts
│ ├── tr
│ │ └── index.ts
│ ├── types.ts
│ └── zh
│ │ └── index.ts
├── js
│ ├── api
│ │ └── openai.api.ts
│ ├── controllers
│ │ ├── dbController.ts
│ │ └── speechController.js
│ ├── data
│ │ ├── default-profiles.json
│ │ ├── default-tags.json
│ │ ├── gpt-models.ts
│ │ └── triggerMode.ts
│ └── libs
│ │ ├── fetch-sse.ts
│ │ ├── helpers.ts
│ │ └── types
│ │ ├── IDBConversation.ts
│ │ ├── IDBProfile.ts
│ │ ├── IGPTResponse.ts
│ │ ├── IPTChatRequest.ts
│ │ └── IUserSettings.ts
├── layouts
│ ├── CompanionLayout.vue
│ └── MainLayout.vue
├── pages
│ ├── CompanionPage.vue
│ ├── ConversationsPage.vue
│ ├── ErrorNotFound.vue
│ └── ProfilesPage.vue
├── quasar.d.ts
├── router
│ ├── index.ts
│ └── routes.ts
└── shims-vue.d.ts
├── tsconfig.json
└── yarn.lock
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | /dist
2 | /src-capacitor
3 | /src-cordova
4 | /.quasar
5 | /node_modules
6 | .eslintrc.js
7 | /src-ssr
8 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | // https://eslint.org/docs/user-guide/configuring#configuration-cascading-and-hierarchy
3 | // This option interrupts the configuration hierarchy at this file
4 | // Remove this if you have an higher level ESLint config file (it usually happens into a monorepos)
5 | root: true,
6 |
7 | // https://eslint.vuejs.org/user-guide/#how-to-use-a-custom-parser
8 | // Must use parserOptions instead of "parser" to allow vue-eslint-parser to keep working
9 | // `parser: 'vue-eslint-parser'` is already included with any 'plugin:vue/**' config and should be omitted
10 | parserOptions: {
11 | parser: require.resolve('@typescript-eslint/parser'),
12 | extraFileExtensions: [ '.vue' ]
13 | },
14 |
15 | env: {
16 | browser: true,
17 | es2021: true,
18 | node: true,
19 | 'vue/setup-compiler-macros': true
20 | },
21 |
22 | // Rules order is important, please avoid shuffling them
23 | extends: [
24 | // Base ESLint recommended rules
25 | // 'eslint:recommended',
26 |
27 | // https://github.com/typescript-eslint/typescript-eslint/tree/master/packages/eslint-plugin#usage
28 | // ESLint typescript rules
29 | 'plugin:@typescript-eslint/recommended',
30 |
31 | // Uncomment any of the lines below to choose desired strictness,
32 | // but leave only one uncommented!
33 | // See https://eslint.vuejs.org/rules/#available-rules
34 | 'plugin:vue/vue3-essential', // Priority A: Essential (Error Prevention)
35 | // 'plugin:vue/vue3-strongly-recommended', // Priority B: Strongly Recommended (Improving Readability)
36 | // 'plugin:vue/vue3-recommended', // Priority C: Recommended (Minimizing Arbitrary Choices and Cognitive Overhead)
37 |
38 | // https://github.com/prettier/eslint-config-prettier#installation
39 | // usage with Prettier, provided by 'eslint-config-prettier'.
40 | 'prettier'
41 | ],
42 |
43 | plugins: [
44 | // required to apply rules which need type information
45 | '@typescript-eslint',
46 |
47 | // https://eslint.vuejs.org/user-guide/#why-doesn-t-it-work-on-vue-files
48 | // required to lint *.vue files
49 | 'vue'
50 |
51 | // https://github.com/typescript-eslint/typescript-eslint/issues/389#issuecomment-509292674
52 | // Prettier has not been included as plugin to avoid performance impact
53 | // add it as an extension for your IDE
54 |
55 | ],
56 |
57 | globals: {
58 | ga: 'readonly', // Google Analytics
59 | cordova: 'readonly',
60 | __statics: 'readonly',
61 | __QUASAR_SSR__: 'readonly',
62 | __QUASAR_SSR_SERVER__: 'readonly',
63 | __QUASAR_SSR_CLIENT__: 'readonly',
64 | __QUASAR_SSR_PWA__: 'readonly',
65 | process: 'readonly',
66 | Capacitor: 'readonly',
67 | chrome: 'readonly'
68 | },
69 |
70 | // add your custom rules here
71 | rules: {
72 |
73 | 'prefer-promise-reject-errors': 'off',
74 |
75 | quotes: ['warn', 'single', { avoidEscape: true }],
76 |
77 | // this rule, if on, would require explicit return type on the `render` function
78 | '@typescript-eslint/explicit-function-return-type': 'off',
79 |
80 | // in plain CommonJS modules, you can't use `import foo = require('foo')` to pass this rule, so it has to be disabled
81 | '@typescript-eslint/no-var-requires': 'off',
82 |
83 | // The core 'no-unused-vars' rules (in the eslint:recommended ruleset)
84 | // does not work with type definitions
85 | 'no-unused-vars': 'off',
86 |
87 | // allow debugger during development only
88 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off'
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: [robert-hoffmann]
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .thumbs.db
3 | node_modules
4 |
5 | # Quasar core related directories
6 | .quasar
7 | /dist/bex
8 |
9 | # Cordova related directories and files
10 | /src-cordova/node_modules
11 | /src-cordova/platforms
12 | /src-cordova/plugins
13 | /src-cordova/www
14 |
15 | # Capacitor related directories and files
16 | /src-capacitor/www
17 | /src-capacitor/node_modules
18 |
19 | # BEX related directories and files
20 | /src-bex/www
21 | /src-bex/js/core
22 |
23 | # Log files
24 | npm-debug.log*
25 | yarn-debug.log*
26 | yarn-error.log*
27 |
28 | # Editor directories and files
29 | .idea
30 | *.suo
31 | *.ntvs*
32 | *.njsproj
33 | *.sln
34 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | # pnpm-related options
2 | shamefully-hoist=true
3 | strict-peer-dependencies=false
4 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": true,
3 | "semi": true
4 | }
5 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": [
3 | "dbaeumer.vscode-eslint",
4 | "esbenp.prettier-vscode",
5 | "editorconfig.editorconfig",
6 | "vue.volar",
7 | "bladnman.auto-align",
8 | "gruntfuggly.todo-tree",
9 | "britesnow.vscode-toggle-quotes",
10 | "wix.vscode-import-cost"
11 | ],
12 | "unwantedRecommendations": [
13 | "octref.vetur",
14 | "hookyqr.beautify",
15 | "dbaeumer.jshint",
16 | "ms-vscode.vscode-typescript-tslint-plugin"
17 | ]
18 | }
19 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "editor.bracketPairColorization.enabled": true,
3 | "editor.guides.bracketPairs": true,
4 | "editor.formatOnSave": false,
5 | "editor.defaultFormatter": "esbenp.prettier-vscode",
6 | "editor.codeActionsOnSave": ["source.fixAll.eslint"],
7 | "eslint.validate": ["javascript", "javascriptreact", "typescript", "vue"],
8 | "typescript.tsdk": "node_modules/typescript/lib",
9 | "[vue]": {
10 | "editor.defaultFormatter": "Vue.volar"
11 | },
12 | "[svg]": {
13 | "editor.defaultFormatter": "jock.svg"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## Version: 0.2.5
2 | * Possibility to display Search Companion on other sites as a sidebar (new)
3 | * Added word count to messages in Conversation Manager (new)
4 | * Added copy conversation to clipboard in conversation list (new)
5 | * ALT+C now sends any selected text to the conversation manager (new)
6 | * Now translated into 10 languages (new)
7 | * Added search engine support for: Yahoo, Baidu, Yandex, Naver, SearX (new)
8 | * You can also just activate the sidebar for any unsupported site
9 | * Lots of UI tweaks and code refactorings (enhancement)
10 |
11 | ## Version: 0.2.2
12 | * Fixed a problem where the search companion would not be properly displayed (fix)
13 | * Move notifications to bottom right (enhancement)
14 | * Scroll to bottom after adding user input (fix)
15 |
16 | ## Version: 0.2.1
17 | * Click to edit. You can now change the title of a saved conversation (new)
18 | * Added speech recognition, you can use ctrl+spacebar to toggle it (new)
19 | * Added keyboard shortcut to open conversation manager with alt+c (new)
20 |
21 | ## Version: 0.2.0
22 | * Added support for multiple profiles: prompt engineering (new)
23 | * Fixed scrolling in conversation panel (fix)
24 | * Fixed side menu from disappearing (fix)
25 | * Many small UI enhancements in the conversation manager (new)
26 | * Google changed how user input is set, so using a different detection method (fix)
27 |
28 | ## Version: 0.1.0
29 | * Show correct token pricing after clear (fix)
30 | * Show token length of current input prompt (new)
31 | * Interface language switcher in settings dialog (new)
32 | * Added AbortSignal to fetch request, so that streamed responses can be canceled (new)
33 | * Added full Chat interface with conversation management (new)
34 | ## Version: 0.0.5
35 | * Reinitialize/delete conversation (new)
36 | * Show conversation token length (new)
37 | * Show conversation token pricing (new)
38 | * Translations: EN/FR (new)=
39 | ## Version: 0.0.4
40 | * Toggle API visibility as password (new)
41 | * Support for conversation history in Search Companion (new)
42 | * ScrollToTop on new search (new)
43 | * Remove question mark from search phrase (fix)
44 | ## Version: 0.0.3
45 | * Initial release
46 | * Tested with
47 | * Chrome, Edge, Opera GX
48 | * Google, Bing, DuckDuckGo
49 |
--------------------------------------------------------------------------------
/CONTRIBUTIONS.md:
--------------------------------------------------------------------------------
1 | **You can contribute by**
2 |
3 | * [Translating to other languages](_docs/translations.md)
4 | * [Filing bug issues](https://github.com/robert-hoffmann/PowerToys4OpenAI/issues) or [providing feedback in discussions](https://github.com/robert-hoffmann/PowerToys4OpenAI//discussions)
5 | * Sharing on social media, in newsletters, or other
6 | * Making a donation to support further development
7 |
8 |
9 | *I'm availiable via [Twitter](https://twitter.com/itechnologynet) & [LinkedIn](https://www.linkedin.com/in/hoffmannrobert)*
10 |
11 |
12 | ---
13 | **For the curious**
14 |
15 | *This was made with [VueJS](https://vuejs.org/) & [Quasar](https://quasar.dev/introduction-to-quasar) for the UI, [Dexie](https://dexie.org/) for local database handling, [TypeScript](https://www.typescriptlang.org/) for logic handling, and [VueUse](https://vueuse.org/) for certain features*
16 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # PowerToys for OpenAI™ (donationware)
2 |
3 | ### For the power-user in the office, who just wants to get stuff done
4 | #### Lightning fast
5 | * Everything is self hosted/stored on your machine
6 | * No server needed, and no third party plug-ins
7 | * Automatic updates
8 | ---
9 | - **[Installation & Troubleshooting (Chrome, Edge, Opera)](https://github.com/robert-hoffmann/PowerToys4OpenAI/wiki/Docs)**
10 | - **[Issues](https://github.com/robert-hoffmann/PowerToys4OpenAI/issues)**
11 | - **[RoadMap](https://github.com/robert-hoffmann/PowerToys4OpenAI/wiki/RoadMap)**
12 | ---
13 | Get from Edge Store | Get from Chrome Store
14 | --- | ---
15 | [](https://microsoftedge.microsoft.com/addons/detail/powertoys-for-openai-%E2%84%A2/kjeipegpggpbciapoallgaieajcefolp) | [](https://chrome.google.com/webstore/detail/powertoys-for-openai/haijiigmikhgoflpocajpfldmjcfbdpa)
16 |
17 | ### Click screenshot for quick product walkthrough
18 |
19 |
20 |
21 |
22 |
23 |
24 | |
25 |
26 |
27 |
28 |
29 | |
30 |
31 |
32 |
33 |
34 |
35 |
36 | |
37 |
38 |
39 |
40 |
41 | |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 | ---
52 |
53 | #### Features
54 | * Search Companion is integrated with search engine results
55 | * Conversation Manager is a full chat interface with history management
56 | * Profile manager lets you fine tune the model's response type
57 | * Import/Export profiles in JSON
58 | * See token count
59 | * See conversation pricing
60 | * Copy results to clipboard
61 | * Markdown rendering support
62 | * Speech recognition support
63 | * Add a sidebar to any site
64 | * Fully translated into 10 languages
65 |
66 | ---
67 |
68 | * **[RoadMap](https://github.com/robert-hoffmann/PowerToys4OpenAI/wiki/RoadMap)**
69 | * **[Contribute](CONTRIBUTIONS.md)**
70 |
71 | ---
72 |
73 | #### External links
74 |
75 | * **[Twitter](https://twitter.com/itechnologynet)**
76 | * **[YouTube](https://www.youtube.com/@itechnologynet)**
77 |
--------------------------------------------------------------------------------
/_docs/_todo.md:
--------------------------------------------------------------------------------
1 | # Todo (later)
2 | * Knowledge graph: type question, generate 3 ansers, click an answer, and generate 3 more ..etc
3 | * https://visjs.github.io/vis-network/
4 | * https://twitter.com/hturan/status/1641780868640374784
5 | * implement writing interface like youwrite, with customizable templating language
6 | * use whisper to generate transcripts automatically ?
7 | * you can query them, but probably run into limitations, unless linked to pinecone or something
8 | * add these settings directly to search companion, IE
9 | * change language
10 | * change profile
11 | * OpenAI settings should be linked to profile
12 | * Thus profile should be linked to conversation history
13 | * add tts support (listen)
14 | * link profile to OpenAI settings
15 | * save/update (previous) conversation
16 | * add token price to db & settings (should probably be updated after each [DONE] event)
17 | * make translation loadable on demand insted of embedding them in the code
18 | * Add word count to search companion (..or not)
19 |
20 | # Refactoring
21 | * move (when possible) event handlers, to routes with arguments
22 | * in settings add: (always reply in selected language) toggle
23 | * add a little animation to show you can delete via swipe
24 | * see what error 600 is
25 | * redo openAI web service to handle errors and stuff
26 | - https://platform.openai.com/docs/guides/error-codes/api-errors
27 | * move db intialization directly to dbController ?
28 |
29 | # Todo (0.3.0)
30 | * Fixed and error with some status code not being handled correctly (done)
31 | * Added some language codes for PT and ZH, see how this works out on next app store submission
32 | * Make the text selection with ALT+C more useful
33 | * Sometimes the open sidebar button becomes unresponsive, see why (done, but needs improvement)
34 | * some strange binding error that doubles every time the conversation panel is opened
35 | * RXJS has some problems, better to use an event handler when settings change and broadcast the event
36 | * temporarily fixed by using a throttle()
37 | * Add popup for ratings, with setting to forget or remind
38 | * Sync settings with Search Companion
39 | * Clean up the way showing/hiding certain buttons work in the Search Companion
40 | * Sometimes OpenAI response is unresponsive, see why, and add a timout with error if possible
41 | * exploit system./user type prompts
42 | * verify DB
43 | * verify default profiles file
44 | * there is a problem that if a message is in progress and you click new conversation, that some stuff stays in memory
45 | * Verify if getFullyQualifiedLanguage() really needs to return en-US, etc (if so, update all supported languages)
46 | * https://www.andiamo.co.uk/resources/iso-language-codes/
47 | * Use a timeout when sending a request to detect that something is wrong and do a retry
48 |
49 | # Bugs
50 | * DONE :: input.focus() doesn't work always in ConversationsPage
51 | * Extension does not work in Brave (need to use event system for storage)
52 |
--------------------------------------------------------------------------------
/_docs/api-documentation.md:
--------------------------------------------------------------------------------
1 | # Usage nomenclature
2 |
3 | ## To call a function in a library use the following syntax:
4 |
5 | `bridge.send('destination.function', data)`
6 |
7 | ```js
8 | bridge.send('background.displayNotification', {
9 | title : 'Hello',
10 | message: 'World'
11 | })
12 | .then(response => {
13 | // do something
14 | console.log(response.success);
15 | })
16 | ```
17 |
18 | * To listen for a function in a library use the following syntax:
19 |
20 | (this should be in the appropriate library file)
21 | ```js
22 | bridge.on('background.displayNotification', ({ data, respond }) => {
23 | chrome.notifications.create('', {
24 | title : data.title,
25 | message : data.message,
26 | iconUrl : data.icon ? data.icon : 'www/logo.png',
27 | type : 'basic'
28 | });
29 |
30 | respond({ success: true });
31 | });
32 | ```
33 |
34 | ## To listen to an event use the following syntax:
35 |
36 | `bridge.send('origin::event', data)`
37 |
38 | ```js
39 | bridge.send('companionPage::onMounted', {
40 | title : 'Hello',
41 | message: 'World'
42 | })
43 | .then(response => {
44 | // do something
45 | console.log(response.success);
46 | })
47 | ```
48 |
49 | (this can be anywhere in the project)
50 | ```js
51 | bridge.on('companionPage::onMounted', ({ data, respond }) => {
52 | // do something
53 |
54 | respond({ success: true });
55 | });
56 | ```
57 |
--------------------------------------------------------------------------------
/_docs/api.md:
--------------------------------------------------------------------------------
1 | * https://platform.openai.com/docs/api-reference/completions/create
2 |
3 | ```js
4 | // options
5 | prompt: {
6 | model : 'text-davinci-003',
7 | prompt : 'Say this is a test',
8 | suffix : null,
9 | max_tokens : 16,
10 | top_p : 1,
11 | n : 1,
12 | stream : false,
13 | logprobs : null,
14 | echo : false,
15 | stop : null,
16 | presence_penalty : 0,
17 | frequency_penalty: 0,
18 | best_of : 1,
19 | logit_bias : null,
20 | user: : ''
21 | }
22 | ```
23 |
24 | ```js
25 | // stream: false
26 | response: {
27 | "id" : "cmpl-uqkvlQyYK7bGYrRHQ0eXlWi7",
28 | "object" : "text_completion",
29 | "created": 1589478378,
30 | "model" : "text-davinci-003",
31 | "choices": [
32 | {
33 | "text" : "\n\nThis is indeed a test",
34 | "index" : 0,
35 | "logprobs" : null,
36 | "finish_reason": "length"
37 | }
38 | ],
39 | "usage": {
40 | "prompt_tokens" : 5,
41 | "completion_tokens": 7,
42 | "total_tokens" : 12
43 | }
44 | }
45 | ```
46 |
47 | ```js
48 | conversation {
49 | timestamp: 1213456
50 | sent : false,
51 | prompt : 'hi there',
52 | template : 'prompt engineering'
53 | }
54 | ```
55 |
56 | ```js
57 | userSettings: {
58 | triggerMode: 1,
59 | darkMode : false,
60 | apiKey : 'sk-xxxxxx',
61 | licenseKey : 'sk-xxxxxx'
62 | }
63 | ```
64 |
--------------------------------------------------------------------------------
/_docs/store-listing/README.md:
--------------------------------------------------------------------------------
1 | These are the translations used in the store listing (full description)
2 |
3 | [See here for the translations for the extension (name, title, short description](/_docs_/store-listing)
4 |
--------------------------------------------------------------------------------
/_docs/store-listing/de.txt:
--------------------------------------------------------------------------------
1 | PowerToys for OpenAI ™ bietet über die offizielle OpenAI™ API ChatGPT-Funktionalität an
2 |
3 | Es richtet sich an Power-User im Büro.
4 |
5 | * Der Suchbegleiter ist in die Suchmaschinenergebnisse integriert
6 | * Der Conversational Manager ist eine vollständige Chat-Schnittstelle mit Verlaufverwaltung
7 | * Der Profil-Manager ermöglicht es Ihnen, den Antworttyp des Modells feinabzustimmen
8 | * Sehen Sie Tokenanzahl
9 | * Sehen Sie die Konversationspreise
10 | * Kopieren Sie Ergebnisse in die Zwischenablage
11 | * Unterstützung für Markdown-Rendering
12 | * Unterstützung für Spracherkennung
13 | * ... und vieles mehr ;-)
14 |
15 | Es ist für Geschwindigkeit optimiert, indem es lokal in Ihrem Browser ausgeführt wird
16 | (keine Serverkosten)
17 |
18 | Die Verwendung dieser Erweiterung erfordert das Vorhandensein eines eigenen API-Schlüssels, der direkt von OpenAI durch Registrierung und Besuch des Entwickler-Dashboards bezogen werden kann
19 |
20 | Verwalten Sie Ihre API-Schlüssel, indem Sie besuchen:
21 | https://platform.openai.com/account/api-keys
22 |
23 | Der Preis liegt bei etwa 0,002 USD pro 750 Wörter oder 1 USD für den Inhalt eines Buches mit 1000 Seiten
24 | (für das Gpt-3.5-Turbo-Model)
25 |
26 | -----------
27 | Bleiben Sie dran:
28 | https://twitter.com/itechnologynet
29 |
--------------------------------------------------------------------------------
/_docs/store-listing/en.txt:
--------------------------------------------------------------------------------
1 | PowerToys for OpenAI ™ provides ChatGPT functionality through the official OpenAI™ API
2 |
3 | Is intended for power users in the office
4 |
5 | * Search Companion is integrated with search engine results
6 | * Conversation Manager is a full chat interface with history management
7 | * Profile manager lets you fine tune the model's response type
8 | * See token count
9 | * See conversation pricing
10 | * Copy results to clipboard
11 | * Markdown rendering support
12 | * Speech recognition support
13 | * ... and more to come ;-)
14 |
15 | It's optimized for speed, by running locally in your browser
16 | (no server costs)
17 |
18 | The use of this extension will require having your own API key, which can be directly obtained from OpenAI by registering and visiting their developer dashboard
19 |
20 | Manage your API keys by visiting:
21 | https://platform.openai.com/account/api-keys
22 |
23 | Pricing is around $0.002 per 750 words, or $1 for the equivalent content of a book of 1000 pages
24 | (for the gpt-3.5-turbo model)
25 |
26 | -----------
27 | Stay tuned:
28 | https://twitter.com/itechnologynet
29 |
--------------------------------------------------------------------------------
/_docs/store-listing/es.txt:
--------------------------------------------------------------------------------
1 | PowerToys for OpenAI ™ proporciona la funcionalidad ChatGPT a través de la API oficial de OpenAI ™
2 |
3 | Está destinado a usuarios avanzados en la oficina.
4 |
5 | * El buscador compañero está integrado con los resultados del motor de búsqueda
6 | * El Administrador de conversaciones es una interfaz de chat completa con gestión de historial
7 | * El Administrador de perfiles le permite ajustar el tipo de respuesta del modelo
8 | * Ver conteo de token
9 | * Ver precios de conversación
10 | * Copiar resultados al portapapeles
11 | * Soporte de representación Markdown
12 | * Soporte de reconocimiento de voz
13 | * ... ¡y más por venir! ;-)
14 |
15 | Está optimizado para la velocidad, al ejecutarse localmente en su navegador
16 | (sin costos de servidor)
17 |
18 | El uso de esta extensión requerirá tener su propia clave API, que se puede obtener directamente de OpenAI mediante el registro y la visita del panel de desarrollo del desarrollador
19 |
20 | Administre sus claves API visitando:
21 | https://platform.openai.com/account/api-keys
22 |
23 | El precio es de alrededor de $ 0.002 por 750 palabras, o $ 1 por el contenido equivalente a un libro de 1000 páginas
24 | (para el modelo gpt-3.5-turbo)
25 |
26 | -----------
27 | Manténgase en sintonía:
28 | https://twitter.com/itechnologynet
29 |
--------------------------------------------------------------------------------
/_docs/store-listing/fr.txt:
--------------------------------------------------------------------------------
1 | PowerToys for OpenAI ™ fournit des fonctionnalités de ChatGPT via l'API officielle d'OpenAI™
2 |
3 | Il est destiné aux utilisateurs avancés au bureau
4 |
5 | * Compagnon de recherche est intégré aux résultats des moteurs de recherche
6 | * Le Gestionnaire de Conversation est une interface de chat complète avec une gestion de l'historique
7 | * Le Gestionnaire de Profil vous permet d'affiner le type de réponse du modèle
8 | * Voir le nombre de jetons
9 | * Voir le coût de la conversation
10 | * Copier les résultats dans le presse-papiers
11 | * Support du rendu Markdown
12 | * Prise en charge de la reconnaissance vocale
13 | * ... et plus à venir ;-)
14 |
15 | Il est optimisé pour la vitesse, en s'exécutant localement dans votre navigateur
16 | (sans coût de serveur)
17 |
18 | Pour utiliser cette extension, vous aurez besoin de votre propre clé API qui peut être obtenue directement auprès d'OpenAI en vous inscrivant et en visitant le tableau de bord du développeur
19 |
20 | Gérez vos clés API en visitant:
21 | https://platform.openai.com/account/api-keys
22 |
23 | Le prix est d'environ $0,002 pour 750 mots, ou $1 pour le contenu équivalent d'un livre de 1000 pages
24 | (pour le modèle gpt-3.5-turbo)
25 |
26 | -----------
27 | Restez informé:
28 | https://twitter.com/itechnologynet
29 |
--------------------------------------------------------------------------------
/_docs/store-listing/hi.txt:
--------------------------------------------------------------------------------
1 | पावरटॉय्स फॉर ओपनएआई ™ आधिकारिक ओपनएआई ™ एपीआई के माध्यम से चैटजीपीटी की फ़ंक्शनैलिटी प्रदान करता है
2 |
3 | कार्यालय में पावर यूज़र्स के लिए उद्देश्यित है|
4 |
5 | * सर्च कंपेनियन खोज इंजन परिणामों के साथ एकीकृत है
6 | * कन्वर्सेशन मैनेजर एक पूर्ण चैट इंटरफ़ेस है जिसमें इतिहास प्रबंधन होता है
7 | * प्रोफ़ाइल मैनेजर आपको मॉडल के जवाब के प्रकार को धीमा करने देता है
8 | * टोकन काउंट देखें
9 | * बातचीत मूल्य देखें
10 | * परिणाम को क्लिपबोर्ड में कॉपी करें
11 | * मार्कडाउन रेंडरिंग समर्थन
12 | * वाक्यांश पहचान समर्थन
13 | * ... और और भी आने वाला है ;-)
14 |
15 | यह तेजी से ऑप्टिमाइज़ किया गया है, आपके ब्राउज़र में स्थानीय रूप से चलता है
16 | (कोई सर्वर लागत नहीं)
17 |
18 | एक्सटेंशन का उपयोग करने के लिए आपके पास अपनी खुद की एपीआई कुंजी होनी चाहिए, जो OpenAI से सीधे प्राप्त की जा सकती है, रजिस्टर करके और उनके डेवलपर डैशबोर्ड पर जाकर।
19 |
20 | अपनी एपीआई कुंजियों को विस्तारित करने के लिए अपने खाते पर जाएं:
21 | https://platform.openai.com/account/api-keys
22 |
23 | कीमत लगभग $0.002 प्रति 750 शब्द है, या एक 1000 पृष्ठ की पुस्तक के समकक्ष सामग्री के लिए $1।
24 | (gpt-3.5-turbo मॉडल के लिए)
25 |
26 | -----------
27 | जुड़े रहें:
28 | https://twitter.com/itechnologynet
29 |
--------------------------------------------------------------------------------
/_docs/store-listing/ja.txt:
--------------------------------------------------------------------------------
1 | PowerToys for OpenAI™ は、公式の OpenAI™ API を介して ChatGPT 機能を提供します
2 |
3 | この拡張機能は、オフィスでのパワーユーザーを対象としています
4 |
5 | * 検索コンパニオンは、検索エンジンの検索結果と統合されています
6 | * 会話マネージャーは、履歴管理が可能な完全なチャットインターフェースです
7 | * プロファイルマネージャーを使用すると、モデルの応答タイプを細かく調整できます
8 | * トークン数を確認できます
9 | * 会話の価格を確認できます
10 | * 結果をクリップボードにコピーできます
11 | * Markdown レンダリングサポートがあります
12 | * 音声認識のサポートがあります
13 | * ... さらに多くの機能が追加されます ;-)
14 |
15 | ブラウザでローカルに実行されるため、速度が最適化されています
16 | (サーバー費用はかかりません)
17 |
18 | この拡張機能の使用には、OpenAI から API キーを取得する必要があります。API キーは、開発者ダッシュボードを登録して訪問することで直接取得できます
19 |
20 | API キーの管理方法については、以下を参照してください:
21 | https://platform.openai.com/account/api-keys
22 |
23 | 料金は、750単語あたり約0.002ドル、もしくは1000ページ分のコンテンツに相当する1ドル(gpt-3.5-turboモデルの場合)です
24 |
25 | -----------
26 | アップデートをお見逃しなく:
27 | https://twitter.com/itechnologynet
28 |
--------------------------------------------------------------------------------
/_docs/store-listing/pt_PT.txt:
--------------------------------------------------------------------------------
1 | PowerToys for OpenAI™ fornece funcionalidade ChatGPT através da API oficial da OpenAI™
2 |
3 | É destinado para usuários avançados no escritório
4 |
5 | * Search Companion está integrado aos resultados do motor de busca
6 | * Conversation Manager é uma interface completa de chat com gerenciamento de histórico
7 | * Profile Manager permite ajustar o tipo de resposta do modelo
8 | * Veja a contagem de tokens
9 | * Veja a precificação da conversa
10 | * Copie os resultados para a área de transferência
11 | * Suporte à renderização Markdown
12 | * Suporte a reconhecimento de voz
13 | * ... e mais por vir ;-)
14 |
15 | É otimizado para velocidade, ao ser executado localmente em seu navegador
16 | (sem custos de servidor)
17 |
18 | O uso desta extensão requer ter sua própria chave de API, que pode ser obtida diretamente da OpenAI ao se registrar e visitar o painel de desenvolvedor
19 |
20 | Gerencie suas chaves API visitando:
21 | https://platform.openai.com/account/api-keys
22 |
23 | O preço fica em cerca de $0,002 por 750 palavras, ou $1 pelo conteúdo equivalente a um livro de 1000 páginas
24 | (para o modelo gpt-3.5-turbo)
25 |
26 | -----------
27 | Fique ligado:
28 | https://twitter.com/itechnologynet
29 |
--------------------------------------------------------------------------------
/_docs/store-listing/ru.txt:
--------------------------------------------------------------------------------
1 | PowerToys for OpenAI™ предоставляет функциональность ChatGPT через официальный API OpenAI™
2 |
3 | Предназначен для опытных пользователей
4 |
5 | * Поисковый компаньон интегрирован с результатами поисковых систем
6 | * Менеджер диалогов - это полноценный интерфейс чата с управлением историей
7 | * Менеджер профилей позволяет точно настроить тип ответа модели
8 | * Просмотр количества токенов
9 | * Оценка цены разговора
10 | * Копирование результатов в буфер обмена
11 | * Поддержка рендеринга в формате Markdown
12 | * Поддержка распознавания речи
13 | * ... и многое другое в будущем ;-)
14 |
15 | Оптимизирован для производительности и запускается локально в вашем браузере
16 | (без затрат на сервер)
17 |
18 | Для использования этого расширения необходимо иметь собственный API-ключ, который можно получить непосредственно у OpenAI, зарегистрировавшись и посетив их панель разработчика
19 |
20 | Управление ключами API осуществляется на сайте:
21 | https://platform.openai.com/account/api-keys
22 |
23 | Цена составляет около $0.002 за 750 слов, или $1 за эквивалент содержания книги в 1000 страниц
24 | (для модели gpt-3.5-turbo)
25 |
26 | -----------
27 | Следите за новостями:
28 | https://twitter.com/itechnologynet
29 |
--------------------------------------------------------------------------------
/_docs/store-listing/tr.txt:
--------------------------------------------------------------------------------
1 | PowerToys for OpenAI™, resmi OpenAI™ API'si aracılığıyla ChatGPT işlevselliği sağlar
2 |
3 | Ofiste güç kullanıcıları için tasarlanmıştır:
4 |
5 | * Arama Eşlikçisi arama motoru sonuçlarıyla entegre edilir
6 | * Konuşma Yöneticisi, geçmiş yönetimiyle tam bir sohbet arayüzüdür
7 | * Profiller, modelin yanıt türünü ince ayar yapmanızı sağlar
8 | * Token sayısını görüntüleyin
9 | * Konuşma fiyatlandırmasını görüntüleyin
10 | * Sonuçları panoya kopyalama
11 | * Markdown işaretleme desteği
12 | * Konuşma tanıma desteği
13 | * ve daha fazlası ;-)
14 |
15 | Hız için optimize edilmiştir, tarayıcınızda yerel olarak çalışarak
16 | (sunucu maliyeti yok)
17 |
18 | Bu uzantının kullanımı, OpenAI'den doğrudan API anahtarınızın olması gerektirir, bu anahtar kayıt olmanız ve geliştirici panosunu ziyaret etmenizle doğrudan OpenAI'den elde edilebilir.
19 |
20 | API anahtarlarınızı yönetmek için şurayı ziyaret edin:
21 | https://platform.openai.com/account/api-keys
22 |
23 | Fiyatlandırma, 750 kelime başına yaklaşık 0,002 $ veya 1000 sayfalık bir kitaba eşdeğer olan içerik için 1 $ civarındadır
24 | (gpt-3.5-turbo modeli için)
25 |
26 | -----------
27 | Güncel kalmaya devam edin:
28 | https://twitter.com/itechnologynet
29 |
--------------------------------------------------------------------------------
/_docs/store-listing/zh_CN.txt:
--------------------------------------------------------------------------------
1 | PowerToys for OpenAI ™ 通过官方 OpenAI™ API 提供 ChatGPT 功能
2 |
3 | 旨在为办公室中的高级用户提供服务
4 |
5 | * 搜索助手与搜索引擎结果集成
6 | * 会话管理器为带有历史管理的完整聊天界面
7 | * 配置管理器可让您微调模型的响应类型
8 | * 查看标记计数
9 | * 查看会话定价
10 | * 复制结果到剪贴板
11 | * 支持 Markdown 渲染
12 | * 支持语音识别
13 | * ...等等功能正在增加 ;-)
14 |
15 | 它经过速度优化,在本地浏览器中运行
16 | (无服务器成本)
17 |
18 | 使用此扩展需要拥有自己的 API 密钥,可以通过注册并访问其开发者仪表板从 OpenAI 直接获取
19 |
20 | 通过访问 https://platform.openai.com/account/api-keys 管理您的 API 密钥
21 |
22 | 价格大约为每750个单词0.002美元,或相当于一本1000页书的内容1美元
23 | (针对 gpt-3.5-turbo 模型)
24 |
25 | -----------------
26 | 敬请关注:
27 | https://twitter.com/itechnologynet
28 |
--------------------------------------------------------------------------------
/_docs/translations.md:
--------------------------------------------------------------------------------
1 | ### How to translate (or fix a typo)
2 |
3 | * **i18n/en/index.ts** contains all the english translations
4 | * **i18n/fr/index.ts** contains all the french translations
5 | * **i18n/types.ts** is used in the application to provide **strongly typed** translations, instead of using *magic strings*
6 | * **_docs/store-listing** contains the translations for the app store (full description)
7 | * **src-bex/_locales contains** translations for the for the app store (name, title, short description)
8 |
9 |
10 | #### To add a translation, you must
11 |
12 | * Fork the project
13 | * Duplicate the existing folders or files, for example copy en/* to de/*
14 | * Translate all entries to the new language
15 | * Make a pull request
16 |
17 | _**i18n/types.ts should not need to be touched, unless adding new entries**_
18 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | <%= productName %>
5 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "powerai",
3 | "version": "0.3.0",
4 | "description": "Productivity tools for OpenAI's API",
5 | "productName": "PowerToys for OpenAI ™",
6 | "author": "Robert Hoffmann ",
7 | "private": true,
8 | "baseUrl": "https://api.openai.com",
9 | "scripts": {
10 | "dev": "quasar dev -m bex",
11 | "build": "quasar build -m bex"
12 | },
13 | "dependencies": {
14 | "@quasar/extras": "^1.16.7",
15 | "@vueuse/core": "^10.4.1",
16 | "dexie": "^3.2.4",
17 | "events": "^3.3.0",
18 | "eventsource-parser": "^1.1.1",
19 | "i18next": "^23.5.1",
20 | "i18next-browser-languagedetector": "^7.1.0",
21 | "i18next-vue": "^3.0.0",
22 | "markdown-it": "^13.0.2",
23 | "quasar": "^2.12.7",
24 | "vue": "^3.3.4",
25 | "vue-router": "^4.2.5"
26 | },
27 | "devDependencies": {
28 | "@quasar/app-vite": "^1.6.2",
29 | "@types/markdown-it": "^13.0.2",
30 | "@types/node": "^18.16.0",
31 | "@typescript-eslint/eslint-plugin": "^5.59.0",
32 | "@typescript-eslint/parser": "^5.59.0",
33 | "autoprefixer": "^10.4.16",
34 | "eslint": "^8.40.0",
35 | "eslint-config-prettier": "^8.8.0",
36 | "eslint-plugin-vue": "^9.12.0",
37 | "postcss": "^8.4.31",
38 | "prettier": "^3.0.3",
39 | "typescript": "4.9.5"
40 | },
41 | "engines": {
42 | "node": "^20 || ^18",
43 | "npm": ">= 6.13.4",
44 | "yarn": ">= 1.21.1"
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | // https://github.com/michael-ciniawsky/postcss-load-config
3 |
4 | module.exports = {
5 | plugins: [
6 | // https://github.com/postcss/autoprefixer
7 | require('autoprefixer')({
8 | overrideBrowserslist: [
9 | 'last 4 Chrome versions',
10 | 'last 4 Firefox versions',
11 | 'last 4 Edge versions',
12 | 'last 4 Safari versions',
13 | 'last 4 Android versions',
14 | 'last 4 ChromeAndroid versions',
15 | 'last 4 FirefoxAndroid versions',
16 | 'last 4 iOS versions'
17 | ]
18 | })
19 |
20 | // https://github.com/elchininet/postcss-rtlcss
21 | // If you want to support RTL css, then
22 | // 1. yarn/npm install postcss-rtlcss
23 | // 2. optionally set quasar.config.js > framework > lang to an RTL language
24 | // 3. uncomment the following line:
25 | // require('postcss-rtlcss')
26 | ]
27 | }
28 |
--------------------------------------------------------------------------------
/profiles/README.md:
--------------------------------------------------------------------------------
1 | **These are the default profiles that are loaded when the application is installed**
2 |
3 | There will probaly be a system where profiles can be imported from the web (via JSON) and you can select them directly in the application
4 | Those files will be hosted here
5 |
--------------------------------------------------------------------------------
/public/_readme.md:
--------------------------------------------------------------------------------
1 | ## Accessible via:
2 | * Inside Chrome extension
3 | * www/*
4 | * Inside *.vue files
5 | * /*
6 |
--------------------------------------------------------------------------------
/public/avatar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/robert-hoffmann/PowerToys4OpenAI/e8aa87e64abe4ba9a11b99f984e6dd0429358f90/public/avatar.png
--------------------------------------------------------------------------------
/public/donations/bitcoin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/robert-hoffmann/PowerToys4OpenAI/e8aa87e64abe4ba9a11b99f984e6dd0429358f90/public/donations/bitcoin.png
--------------------------------------------------------------------------------
/public/donations/ethereum.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/robert-hoffmann/PowerToys4OpenAI/e8aa87e64abe4ba9a11b99f984e6dd0429358f90/public/donations/ethereum.png
--------------------------------------------------------------------------------
/public/donations/gumroad.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/robert-hoffmann/PowerToys4OpenAI/e8aa87e64abe4ba9a11b99f984e6dd0429358f90/public/donations/gumroad.png
--------------------------------------------------------------------------------
/public/donations/paypal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/robert-hoffmann/PowerToys4OpenAI/e8aa87e64abe4ba9a11b99f984e6dd0429358f90/public/donations/paypal.png
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/robert-hoffmann/PowerToys4OpenAI/e8aa87e64abe4ba9a11b99f984e6dd0429358f90/public/favicon.ico
--------------------------------------------------------------------------------
/public/icons/favicon-128x128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/robert-hoffmann/PowerToys4OpenAI/e8aa87e64abe4ba9a11b99f984e6dd0429358f90/public/icons/favicon-128x128.png
--------------------------------------------------------------------------------
/public/icons/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/robert-hoffmann/PowerToys4OpenAI/e8aa87e64abe4ba9a11b99f984e6dd0429358f90/public/icons/favicon-16x16.png
--------------------------------------------------------------------------------
/public/icons/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/robert-hoffmann/PowerToys4OpenAI/e8aa87e64abe4ba9a11b99f984e6dd0429358f90/public/icons/favicon-32x32.png
--------------------------------------------------------------------------------
/public/icons/favicon-96x96.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/robert-hoffmann/PowerToys4OpenAI/e8aa87e64abe4ba9a11b99f984e6dd0429358f90/public/icons/favicon-96x96.png
--------------------------------------------------------------------------------
/public/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/robert-hoffmann/PowerToys4OpenAI/e8aa87e64abe4ba9a11b99f984e6dd0429358f90/public/logo.png
--------------------------------------------------------------------------------
/public/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/public/robot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/robert-hoffmann/PowerToys4OpenAI/e8aa87e64abe4ba9a11b99f984e6dd0429358f90/public/robot.png
--------------------------------------------------------------------------------
/public/socials/github.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/public/socials/linkedin.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/public/socials/twitter.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/quasar.config.js:
--------------------------------------------------------------------------------
1 | /* eslint-env node */
2 |
3 | /*
4 | * This file runs in a Node context (it's NOT transpiled by Babel), so use only
5 | * the ES6 features that are supported by your Node version. https://node.green/
6 | */
7 |
8 | // Configuration for your app
9 | // https://v2.quasar.dev/quasar-cli-vite/quasar-config-js
10 |
11 | const { configure } = require('quasar/wrappers');
12 |
13 | module.exports = configure(function (/* ctx */) {
14 | return {
15 | eslint: {
16 | // fix: true,
17 | // include = [],
18 | // exclude = [],
19 | // rawOptions = {},
20 | warnings: true,
21 | errors: true
22 | },
23 |
24 | // https://v2.quasar.dev/quasar-cli-vite/prefetch-feature
25 | // preFetch: true,
26 |
27 | // app boot file (/src/boot)
28 | // --> boot files are part of "main.js"
29 | // https://v2.quasar.dev/quasar-cli-vite/boot-files
30 | boot: [
31 | 'i18next',
32 | ],
33 |
34 | // https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#css
35 | css: [
36 | 'app.scss'
37 | ],
38 |
39 | // https://github.com/quasarframework/quasar/tree/dev/extras
40 | extras: [
41 | // 'ionicons-v4',
42 | // 'mdi-v5',
43 | // 'fontawesome-v6',
44 | // 'eva-icons',
45 | // 'themify',
46 | // 'line-awesome',
47 | // 'roboto-font-latin-ext', // this or either 'roboto-font', NEVER both!
48 |
49 | 'roboto-font', // optional, you are not bound to it
50 | 'material-icons', // optional, you are not bound to it
51 | 'material-icons-outlined',
52 | ],
53 |
54 | // Full list of options: https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#build
55 | build: {
56 | target: {
57 | browser: [ 'es2019', 'edge98', 'chrome97' ],
58 | node: 'node20'
59 | },
60 |
61 | vueRouterMode: 'hash', // available values: 'hash', 'history'
62 | // vueRouterBase,
63 | // vueDevtools,
64 | // vueOptionsAPI: false,
65 |
66 | // rebuildCache: true, // rebuilds Vite/linter/etc cache on startup
67 |
68 | // publicPath: '/',
69 | // analyze: true,
70 | // env: {},
71 | // rawDefine: {}
72 | // ignorePublicFolder: true,
73 | minify: false,
74 | // polyfillModulePreload: true,
75 | // distDir
76 | extendViteConf (viteConf) {
77 | // INFO: https://stackoverflow.com/a/74019163/896849
78 | viteConf.build.rollupOptions = {
79 | output:{
80 | manualChunks(id) {
81 | if (id.includes('node_modules')) {
82 | return id.toString().split('node_modules/')[1].split('/')[0].toString();
83 | }
84 | }
85 | }
86 | };
87 | },
88 | // viteVuePluginOptions: {},
89 | // vitePlugins: []
90 | },
91 |
92 | // Full list of options: https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#devServer
93 | devServer: {
94 | // https: true
95 | open: true // opens browser window automatically
96 | },
97 |
98 | // https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#framework
99 | framework: {
100 | config: {},
101 |
102 | // iconSet: 'material-icons', // Quasar icon set
103 | // lang: 'en-US', // Quasar language pack
104 |
105 | // For special cases outside of where the auto-import strategy can have an impact
106 | // (like functional components as one of the examples),
107 | // you can manually specify Quasar components/directives to be available everywhere:
108 | //
109 | // components: [],
110 | // directives: [],
111 |
112 | // Quasar plugins
113 | plugins: ['Notify']
114 | },
115 |
116 | // animations: 'all', // --- includes all animations
117 | // https://v2.quasar.dev/options/animations
118 | animations: [],
119 |
120 | // https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#sourcefiles
121 | // sourceFiles: {
122 | // rootComponent: 'src/App.vue',
123 | // router: 'src/router/index',
124 | // store: 'src/store/index',
125 | // registerServiceWorker: 'src-pwa/register-service-worker',
126 | // serviceWorker: 'src-pwa/custom-service-worker',
127 | // pwaManifestFile: 'src-pwa/manifest.json',
128 | // electronMain: 'src-electron/electron-main',
129 | // electronPreload: 'src-electron/electron-preload'
130 | // },
131 |
132 | // https://v2.quasar.dev/quasar-cli-vite/developing-ssr/configuring-ssr
133 | ssr: {
134 | // ssrPwaHtmlFilename: 'offline.html', // do NOT use index.html as name!
135 | // will mess up SSR
136 |
137 | // extendSSRWebserverConf (esbuildConf) {},
138 | // extendPackageJson (json) {},
139 |
140 | pwa: false,
141 |
142 | // manualStoreHydration: true,
143 | // manualPostHydrationTrigger: true,
144 |
145 | prodPort: 3000, // The default port that the production server should use
146 | // (gets superseded if process.env.PORT is specified at runtime)
147 |
148 | middlewares: [
149 | 'render' // keep this as last one
150 | ]
151 | },
152 |
153 | // https://v2.quasar.dev/quasar-cli-vite/developing-pwa/configuring-pwa
154 | pwa: {
155 | workboxMode: 'generateSW', // or 'injectManifest'
156 | injectPwaMetaTags: true,
157 | swFilename: 'sw.js',
158 | manifestFilename: 'manifest.json',
159 | useCredentialsForManifestTag: false,
160 | // useFilenameHashes: true,
161 | // extendGenerateSWOptions (cfg) {}
162 | // extendInjectManifestOptions (cfg) {},
163 | // extendManifestJson (json) {}
164 | // extendPWACustomSWConf (esbuildConf) {}
165 | },
166 |
167 | // Full list of options: https://v2.quasar.dev/quasar-cli-vite/developing-cordova-apps/configuring-cordova
168 | cordova: {
169 | // noIosLegacyBuildFlag: true, // uncomment only if you know what you are doing
170 | },
171 |
172 | // Full list of options: https://v2.quasar.dev/quasar-cli-vite/developing-capacitor-apps/configuring-capacitor
173 | capacitor: {
174 | hideSplashscreen: true
175 | },
176 |
177 | // Full list of options: https://v2.quasar.dev/quasar-cli-vite/developing-electron-apps/configuring-electron
178 | electron: {
179 | // extendElectronMainConf (esbuildConf)
180 | // extendElectronPreloadConf (esbuildConf)
181 |
182 | inspectPort: 5858,
183 |
184 | bundler: 'packager', // 'packager' or 'builder'
185 |
186 | packager: {
187 | // https://github.com/electron-userland/electron-packager/blob/master/docs/api.md#options
188 |
189 | // OS X / Mac App Store
190 | // appBundleId: '',
191 | // appCategoryType: '',
192 | // osxSign: '',
193 | // protocol: 'myapp://path',
194 |
195 | // Windows only
196 | // win32metadata: { ... }
197 | },
198 |
199 | builder: {
200 | // https://www.electron.build/configuration/configuration
201 |
202 | appId: 'powerai'
203 | }
204 | },
205 |
206 | // Full list of options: https://v2.quasar.dev/quasar-cli-vite/developing-browser-extensions/configuring-bex
207 | bex: {
208 | contentScripts: [
209 | 'content'
210 | ],
211 |
212 | // extendBexScriptsConf (esbuildConf) {}
213 | // extendBexManifestJson (json) {}
214 | }
215 | }
216 | });
217 |
--------------------------------------------------------------------------------
/sites-detection/README.md:
--------------------------------------------------------------------------------
1 | ### Configuration file for detecting sites
2 |
3 | This file holds all the information needed to inject the Search Companion into a site and *how* it should be injected
4 |
5 | **Walkthrough:**
6 |
7 | - Detect URL to see if we have a match
8 | - Should it be injected as a sidebar or inline
9 | - If inline, see if we have a match to get the user input query
10 | - Look if we have a container to attach to
11 | - If so, get the configuration info to apply for that container
12 | - append / prepend
13 | - any classes to add
14 | - any styles to apply
15 | - this is how we make sure padding, etc is ok
16 |
17 | *Maybe in a future version we can add this directly to the interface*
18 |
--------------------------------------------------------------------------------
/sites-detection/sites-detection.ts:
--------------------------------------------------------------------------------
1 | export interface SearchEngines {
2 | /** Website name */
3 | name: string,
4 |
5 | /** If URL matches, then module will be loaded */
6 | url: RegExp,
7 |
8 | /** Possible target containers */
9 | targets: Configuration[],
10 |
11 | /** If site supports sidebar injection **/
12 | addSidebar: boolean
13 | }[]
14 |
15 | export interface Configuration {
16 | /**
17 | * From where to extract the users search phrase
18 | * @type {query selector}
19 | * */
20 | userInputs: string[],
21 |
22 | /** Array of possible targets of where the module will be attached to in the DOM */
23 | containers: string[],
24 |
25 | /** How to add the module to the selector */
26 | method: 'append' | 'prepend',
27 |
28 | /** Classes to add to the component */
29 | classes?: string[],
30 |
31 | /** Styles to apply to the component */
32 | styles?: {
33 | [key: string]: string
34 | }[]
35 | }
36 |
37 | export const config: SearchEngines[] = [
38 | {
39 | name : 'Google Search',
40 | url : /https?:\/\/.*(google)\.[a-z]{2,3}\/search\?.*(q=.*)/,
41 | addSidebar : false,
42 | targets: [
43 | {
44 | userInputs : [
45 | "input[name='q']",
46 | "textarea[name='q']"
47 | ],
48 | containers: [
49 | '#rhs'
50 | ],
51 | method : 'prepend',
52 | classes : [
53 | 'ptoia-companion-prepend'
54 | ]
55 | },
56 | {
57 | userInputs : [
58 | "input[name='q']",
59 | "textarea[name='q']"
60 | ],
61 | containers: [
62 | '#rcnt'
63 | ],
64 | method : 'append',
65 | classes : [
66 | 'ptoia-companion-sidecontent',
67 | 'append'
68 | ],
69 | styles: [
70 | { marginLeft: '30px' },
71 | { width : '367px' }
72 | ]
73 | }
74 | ],
75 | },
76 | {
77 | name : 'Bing Search',
78 | url : /https?:\/\/.*(bing)\.[a-z]{2,3}\/search\?.*(q=.*)/,
79 | addSidebar : false,
80 | targets: [
81 | {
82 | userInputs : [
83 | "[name='q']"
84 | ],
85 | containers: [
86 | '#b_context'
87 | ],
88 | method : 'prepend',
89 | classes : [
90 | 'ptoia-companion-prepend'
91 | ],
92 | styles: [
93 | { margin: '0px 0px 20px -20px' },
94 | { width : '108%' }
95 | ]
96 | }
97 | ],
98 | },
99 | {
100 | name : 'Yahoo Search',
101 | url : /https?:\/\/.*(search\.yahoo)\.[a-z]{2,3}\/search\?.*(p=.*)/,
102 | addSidebar : false,
103 | targets: [
104 | {
105 | userInputs : [
106 | "[name='p']"
107 | ],
108 | containers: [
109 | '#right',
110 | '.Contents__inner.Contents__inner--sub'
111 | ],
112 | method : 'prepend',
113 | classes : [
114 | 'ptoia-companion-prepend'
115 | ],
116 | styles: [
117 | { margin: '0px 0px 20px 0px' },
118 | { width : '370px' }
119 | ]
120 | },
121 | {
122 | userInputs : [
123 | "[name='p']"
124 | ],
125 | containers: [
126 | '#cols',
127 | '#contents__wrap'
128 | ],
129 | method : 'append',
130 | classes : [
131 | 'ptoia-companion-sidecontent',
132 | 'append'
133 | ],
134 | styles: [
135 | { margin: '0px 0px 20px 0px' },
136 | { width : '370px' }
137 | ]
138 | }
139 | ],
140 | },
141 | {
142 | name : 'DuckDuckGo Search',
143 | url : /https?:\/\/.*(duckduckgo)\.[a-z]{2,3}\/\?.*(q=.*)/,
144 | addSidebar : false,
145 | targets: [
146 | {
147 | userInputs : [
148 | "input[name='q']"
149 | ],
150 | containers: [
151 | '.results--sidebar.js-results-sidebar'
152 | ],
153 | method : 'prepend',
154 | classes : [
155 | 'ptoia-companion-prepend'
156 | ],
157 | styles: [
158 | { margin: '0 0 10px 0' },
159 | { width : '100%' }
160 | ]
161 | },
162 | {
163 | userInputs : [
164 | "input[name='q']"
165 | ],
166 | containers: [
167 | '#links_wrapper'
168 | ],
169 | method : 'append',
170 | classes : [
171 | 'ptoia-companion-sidecontent',
172 | 'append'
173 | ],
174 | styles: [
175 | { margin: '0 0 10px 0' },
176 | { width : '100%' }
177 | ]
178 | }
179 | ],
180 | },
181 | {
182 | name : 'Baidu Search',
183 | url : /https?:\/\/.*(baidu)\.[a-z]{2,3}\/s?.*/,
184 | addSidebar : false,
185 | targets: [
186 | {
187 | userInputs : [
188 | "input[name='wd']"
189 | ],
190 | containers: [
191 | '#content_right'
192 | ],
193 | method : 'prepend',
194 | classes : [
195 | 'ptoia-companion-prepend'
196 | ],
197 | styles: [
198 | { width : '367px' }
199 | ]
200 | },
201 | {
202 | userInputs : [
203 | "input[name='wd']"
204 | ],
205 | containers: [
206 | '#container'
207 | ],
208 | method : 'append',
209 | classes : [
210 | 'ptoia-companion-sidecontent',
211 | 'append'
212 | ],
213 | styles: [
214 | { width : '367px' }
215 | ]
216 | }
217 | ],
218 | },
219 | {
220 | name : 'Yandex Search',
221 | url : /https?:\/\/.*(yandex)\.[a-z]{2,3}\/search\/\?.*(text=.*)/,
222 | addSidebar : false,
223 | targets: [
224 | {
225 | userInputs : [
226 | "input[name='text']"
227 | ],
228 | containers: [
229 | '#search-result-aside'
230 | ],
231 | method : 'prepend',
232 | classes : [
233 | 'ptoia-companion-prepend'
234 | ],
235 | styles: [
236 | { margin: '0 0 10px 0' },
237 | { width : '100%' }
238 | ]
239 | }
240 | ],
241 | },
242 | {
243 | name : 'Naver Search',
244 | url : /https?:\/\/.*(search\.naver)\.[a-z]{2,3}\/search.naver\?.*/,
245 | addSidebar : false,
246 | targets: [
247 | {
248 | userInputs : [
249 | "input[name='query']"
250 | ],
251 | containers: [
252 | '#sub_pack'
253 | ],
254 | method : 'prepend',
255 | classes : [
256 | 'ptoia-companion-prepend'
257 | ],
258 | styles: [
259 | { margin: '0px 0px 15px 16px' },
260 | { width : '400px' }
261 | ]
262 | },
263 | {
264 | userInputs : [
265 | "input[name='query']"
266 | ],
267 | containers: [
268 | '#content'
269 | ],
270 | method : 'append',
271 | classes : [
272 | 'ptoia-companion-sidecontent',
273 | 'append'
274 | ],
275 | styles: [
276 | { margin: '0px 0px 15px 16px' },
277 | { width : '400px' }
278 | ]
279 | }
280 | ],
281 | },
282 | {
283 | name : 'Brave Search',
284 | url : /https?:\/\/.*(search\.brave)\.[a-z]{2,3}\/search\?.*(q=.*)/,
285 | addSidebar : false,
286 | targets: [
287 | {
288 | userInputs : [
289 | "[name='q']"
290 | ],
291 | containers: [
292 | '#side-right'],
293 | method : 'prepend',
294 | styles: [
295 | { margin: '0px 0px 20px 0px' },
296 | { width : '370px' }
297 | ]
298 | }
299 | ],
300 | },
301 | {
302 | name : 'SearX Search',
303 | url : /https?:\/\/.*(searx\.thegpm)\.[a-z]{2,3}\/.*/,
304 | addSidebar : false,
305 | targets: [
306 | {
307 | userInputs : [
308 | "input[name='q']"
309 | ],
310 | containers: [
311 | '#sidebar_results'],
312 | method : 'prepend',
313 | styles: [
314 | { width : '360px' }
315 | ]
316 | }
317 | ],
318 | },
319 | {
320 | name : 'Other Sites',
321 | url : /https?:\/\/(.*)\.[a-z]{2,3}/i,
322 | addSidebar : true,
323 | targets: [
324 | {
325 | userInputs : [],
326 | containers : [
327 | 'body'
328 | ],
329 | method : 'append',
330 | styles: [
331 | { margin : '0px 0px 20px 0px' },
332 | { width : '450px' },
333 | { position : 'absolute' },
334 | { top : '150px' },
335 | { right : '0' },
336 | { zIndex : '9999' },
337 | ]
338 | }
339 | ],
340 | },
341 | ]
342 |
--------------------------------------------------------------------------------
/src-bex/_locales/de/messages.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": {
3 | "message" : "PowerToys for OpenAI ™",
4 | "description" : "name"
5 | },
6 | "shortName": {
7 | "message" : "PowerToys",
8 | "description" : "shortName"
9 | },
10 | "description": {
11 | "message" : "Produktivitätswerkzeuge für OpenAI-APIs",
12 | "description" : "description"
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src-bex/_locales/en/messages.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": {
3 | "message" : "PowerToys for OpenAI ™",
4 | "description" : "name"
5 | },
6 | "shortName": {
7 | "message" : "PowerToys",
8 | "description" : "shortName"
9 | },
10 | "description": {
11 | "message" : "Productivity tools for OpenAI API's",
12 | "description" : "description"
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src-bex/_locales/es/messages.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": {
3 | "message" : "PowerToys for OpenAI ™",
4 | "description" : "name"
5 | },
6 | "shortName": {
7 | "message" : "PowerToys",
8 | "description" : "shortName"
9 | },
10 | "description": {
11 | "message" : "Herramientas de productividad para las API de OpenAI",
12 | "description" : "description"
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src-bex/_locales/fr/messages.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": {
3 | "message" : "PowerToys for OpenAI ™",
4 | "description" : "name"
5 | },
6 | "shortName": {
7 | "message" : "PowerToys",
8 | "description" : "shortName"
9 | },
10 | "description": {
11 | "message" : "Outils de productivité pour les API's d'OpenAI™",
12 | "description" : "description"
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src-bex/_locales/hi/messages.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": {
3 | "message" : "PowerToys for OpenAI ™",
4 | "description" : "name"
5 | },
6 | "shortName": {
7 | "message" : "PowerToys",
8 | "description" : "shortName"
9 | },
10 | "description": {
11 | "message" : "ओपनएआई एपीआई के लिए उत्पादकता टूल्स।",
12 | "description" : "description"
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src-bex/_locales/ja/messages.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": {
3 | "message" : "PowerToys for OpenAI ™",
4 | "description" : "name"
5 | },
6 | "shortName": {
7 | "message" : "PowerToys",
8 | "description" : "shortName"
9 | },
10 | "description": {
11 | "message" : "OpenAI API のための生産性向上ツール",
12 | "description" : "description"
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src-bex/_locales/pt_AO/messages.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": {
3 | "message" : "PowerToys for OpenAI ™",
4 | "description" : "name"
5 | },
6 | "shortName": {
7 | "message" : "PowerToys",
8 | "description" : "shortName"
9 | },
10 | "description": {
11 | "message" : "Ferramentas de produtividade para APIs do OpenAI",
12 | "description" : "description"
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src-bex/_locales/pt_BR/messages.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": {
3 | "message" : "PowerToys for OpenAI ™",
4 | "description" : "name"
5 | },
6 | "shortName": {
7 | "message" : "PowerToys",
8 | "description" : "shortName"
9 | },
10 | "description": {
11 | "message" : "Ferramentas de produtividade para APIs do OpenAI",
12 | "description" : "description"
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src-bex/_locales/pt_CV/messages.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": {
3 | "message" : "PowerToys for OpenAI ™",
4 | "description" : "name"
5 | },
6 | "shortName": {
7 | "message" : "PowerToys",
8 | "description" : "shortName"
9 | },
10 | "description": {
11 | "message" : "Ferramentas de produtividade para APIs do OpenAI",
12 | "description" : "description"
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src-bex/_locales/pt_GW/messages.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": {
3 | "message" : "PowerToys for OpenAI ™",
4 | "description" : "name"
5 | },
6 | "shortName": {
7 | "message" : "PowerToys",
8 | "description" : "shortName"
9 | },
10 | "description": {
11 | "message" : "Ferramentas de produtividade para APIs do OpenAI",
12 | "description" : "description"
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src-bex/_locales/pt_MZ/messages.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": {
3 | "message" : "PowerToys for OpenAI ™",
4 | "description" : "name"
5 | },
6 | "shortName": {
7 | "message" : "PowerToys",
8 | "description" : "shortName"
9 | },
10 | "description": {
11 | "message" : "Ferramentas de produtividade para APIs do OpenAI",
12 | "description" : "description"
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src-bex/_locales/pt_PT/messages.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": {
3 | "message" : "PowerToys for OpenAI ™",
4 | "description" : "name"
5 | },
6 | "shortName": {
7 | "message" : "PowerToys",
8 | "description" : "shortName"
9 | },
10 | "description": {
11 | "message" : "Ferramentas de produtividade para APIs do OpenAI",
12 | "description" : "description"
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src-bex/_locales/pt_ST/messages.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": {
3 | "message" : "PowerToys for OpenAI ™",
4 | "description" : "name"
5 | },
6 | "shortName": {
7 | "message" : "PowerToys",
8 | "description" : "shortName"
9 | },
10 | "description": {
11 | "message" : "Ferramentas de produtividade para APIs do OpenAI",
12 | "description" : "description"
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src-bex/_locales/pt_TL/messages.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": {
3 | "message" : "PowerToys for OpenAI ™",
4 | "description" : "name"
5 | },
6 | "shortName": {
7 | "message" : "PowerToys",
8 | "description" : "shortName"
9 | },
10 | "description": {
11 | "message" : "Ferramentas de produtividade para APIs do OpenAI",
12 | "description" : "description"
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src-bex/_locales/ru/messages.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": {
3 | "message" : "PowerToys for OpenAI ™",
4 | "description" : "name"
5 | },
6 | "shortName": {
7 | "message" : "PowerToys",
8 | "description" : "shortName"
9 | },
10 | "description": {
11 | "message" : "Инструменты для продуктивности с помощью OpenAI API",
12 | "description" : "description"
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src-bex/_locales/tr/messages.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": {
3 | "message" : "PowerToys for OpenAI ™",
4 | "description" : "name"
5 | },
6 | "shortName": {
7 | "message" : "PowerToys",
8 | "description" : "shortName"
9 | },
10 | "description": {
11 | "message" : "OpenAI API'ları için Üretkenlik Araçları",
12 | "description" : "description"
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src-bex/_locales/zh_CN/messages.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": {
3 | "message" : "PowerToys for OpenAI ™",
4 | "description" : "name"
5 | },
6 | "shortName": {
7 | "message" : "PowerToys",
8 | "description" : "shortName"
9 | },
10 | "description": {
11 | "message" : "OpenAI API 的生产力工具",
12 | "description" : "description"
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src-bex/_locales/zh_SG/messages.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": {
3 | "message" : "PowerToys for OpenAI ™",
4 | "description" : "name"
5 | },
6 | "shortName": {
7 | "message" : "PowerToys",
8 | "description" : "shortName"
9 | },
10 | "description": {
11 | "message" : "OpenAI API 的生产力工具",
12 | "description" : "description"
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src-bex/assets/content.css:
--------------------------------------------------------------------------------
1 | /* Global CSS used within your BEX. This is not preprocessed so this has to be pure CSS. */
2 | div#ptoia-companion-container {
3 | border-width : 1px;
4 | border-style : solid;
5 | border-color : initial;
6 | border-image : initial;
7 | border-radius : 8px;
8 | padding : 0;
9 | margin-bottom : 10px;
10 |
11 | color : rgb(0, 0, 0);
12 | background-color : rgb(255, 255, 255);
13 | border-color : rgb(218, 220, 224);
14 | }
15 | div#ptoia-companion-container.ptoia-companion-sidecontent {
16 | margin-left : 30px;
17 | }
18 |
19 | /* #region ---------------- contextMenu ---------------- */
20 | div#ptoia-context-menu {
21 | display : none;
22 |
23 | position : absolute;
24 | z-index : 9999999;
25 | top : 0;
26 | left : 0;
27 |
28 | border: 1px solid black;
29 | }
30 | div.ptoia-context-menu-item {
31 | font-size : 14px;
32 | font-family: 'Roboto';
33 |
34 | display : block;
35 | background-color : white;
36 | color : black;
37 | cursor : pointer;
38 |
39 | padding : 5px;
40 | height : 25px;
41 | width : 250px;
42 | }
43 |
44 | div.ptoia-context-menu-item {
45 | border-top: 1px solid black;
46 | }
47 | div.ptoia-context-menu-item:first-child {
48 | border-top: none;
49 | }
50 |
51 | div.ptoia-context-menu-item:hover {
52 | background-color: lightgray;
53 | }
54 |
55 | .ptoia-sidebar-closed {
56 | width: 10px !important;
57 | }
58 | /* #endregion ------------- contextMenu ---------------- */
59 |
--------------------------------------------------------------------------------
/src-bex/background.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * INFO: This is the background script for the extension.
3 | *
4 | * It's loaded once when the extension is installed.
5 | * It has access to the chrome.* APIs.
6 | *
7 | */
8 | import { bexBackground } from 'quasar/wrappers';
9 | import { BexBridge } from '@quasar/app-vite/types/bex';
10 | import { openAi } from 'src/js/api/openai.api';
11 |
12 | import { db } from 'src/js/controllers/dbController';
13 | import defaultProfiles from 'src/js/data/default-profiles.json';
14 |
15 | // Used to cancel the Fetch request if the user closes the popup.
16 | // https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal
17 | let controller: AbortController | null = null;
18 |
19 | chrome.runtime.onInstalled.addListener((details) => {
20 | // open options on first install
21 | if (details.reason === 'install') {
22 | // open settings
23 | chrome.runtime.openOptionsPage();
24 |
25 | // load defaults
26 | loadDefaults();
27 | }
28 |
29 | // open when updated
30 | if(details.reason === 'update') {
31 | // show changelog
32 | chrome.tabs.create({url: 'https://github.com/robert-hoffmann/PowerToys4OpenAI/blob/main/CHANGELOG.md'});
33 |
34 | // load defaults
35 | loadDefaults();
36 | }
37 | });
38 |
39 | // Opend feedback URL on extension uninstall
40 | chrome.runtime.setUninstallURL('https://airtable.com/shrqYoLYGNPzqUHMD');
41 |
42 | /* INFO: this can reload the extension if there is an update
43 | chrome.runtime.onUpdateAvailable.addListener(() => {
44 | chrome.runtime.reload();
45 | });
46 | */
47 |
48 | chrome.action.onClicked.addListener((/* tab */) => {
49 | // Opens the chat interface in a new tab.
50 | chrome.tabs.create(
51 | {
52 | url: chrome.runtime.getURL('www/index.html#/index'),
53 | },
54 | (/* newTab */) => {
55 | // Tab opened.
56 | }
57 | );
58 | });
59 |
60 | /**
61 | * Insert default settings into the database
62 | */
63 | async function loadDefaults() {
64 | const profiles = await db.getProfiles();
65 |
66 | if (!profiles.length) {
67 | db.insertProfiles(defaultProfiles);
68 | }
69 |
70 | //console.log('background.loadDefaults()', profiles);
71 | }
72 |
73 | /**
74 | * Send userSettings to the content script
75 | * @param bridge
76 | */
77 | async function sendSettingsToContent(bridge: BexBridge) {
78 | const settings = await db.getUserSettings();
79 | //console.log('background.sendSettingsToContent()', settings);
80 | bridge.send('background.ready', {
81 | settings: settings,
82 | });
83 | }
84 |
85 | declare module '@quasar/app-vite' {
86 | interface BexEventMap {
87 | /* eslint-disable @typescript-eslint/no-explicit-any */
88 | log : [{ message: string; data?: any[] }, never];
89 | getTime: [never, number];
90 |
91 | 'storage.get' : [{ key: string | null }, any];
92 | 'storage.set' : [{ key: string; value: any }, any];
93 | 'storage.remove': [{ key: string }, any];
94 | /* eslint-enable @typescript-eslint/no-explicit-any */
95 | }
96 | }
97 |
98 | export default bexBackground((bridge: BexBridge /* , allActiveConnections */) => {
99 | //console.log('LOADING:', Date.now(), 'background.ts::bexBackground');
100 |
101 | //#region Quasar Bridge
102 | bridge.on('log', ({ data, respond }) => {
103 | console.log(`[BEX] ${data.message}`, ...(data.data || []));
104 | respond();
105 | });
106 |
107 | bridge.on('storage.get', ({ data, respond }) => {
108 | const { key } = data;
109 | if (key === null) {
110 | chrome.storage.local.get(null, (items) => {
111 | // Group the values up into an array to take advantage of the bridge's chunk splitting.
112 | respond(Object.values(items));
113 | });
114 | } else {
115 | chrome.storage.local.get([key], (items) => {
116 | respond(items[key]);
117 | });
118 | }
119 | });
120 | // Usage:
121 | // const { data } = await bridge.send('storage.get', { key: 'someKey' })
122 |
123 | bridge.on('storage.set', ({ data, respond }) => {
124 | chrome.storage.local.set({ [data.key]: data.value }, () => {
125 | respond();
126 | });
127 | });
128 | // Usage:
129 | // await bridge.send('storage.set', { key: 'someKey', value: 'someValue' })
130 |
131 | bridge.on('storage.remove', ({ data, respond }) => {
132 | chrome.storage.local.remove(data.key, () => {
133 | respond();
134 | });
135 | });
136 | // Usage:
137 | // await bridge.send('storage.remove', { key: 'someKey' })
138 | //#endregion
139 |
140 | //#region ----------------------- OpenAI -----------------------
141 | /**
142 | * @description Get the answer from OpenAI
143 | */
144 | bridge.on('background.getAnswer', async ({ data /*, respond */ }) => {
145 | console.log('background.getAnswer');
146 |
147 | const session = await openAi.getSession();
148 | if (session.error) {
149 | console.log('session error', session);
150 | bridge.send(data.replyTo, { response: session, context: data.context });
151 | //respond(session);
152 | return;
153 | }
154 |
155 | controller = new AbortController();
156 | await openAi.getAnswerStreamed(
157 | data.messages,
158 | session.content.accessToken,
159 | controller.signal,
160 | (response) => {
161 | //console.log('background.getAnswer response', response);
162 | //respond(response);
163 | bridge.send(data.replyTo, { response: response, context: data.context });
164 | }
165 | );
166 | });
167 |
168 | bridge.on('background.cancelAnswer', async ({ data, respond }) => {
169 | controller?.abort();
170 | respond(data);
171 | });
172 |
173 | bridge.on('background.openTabUrl', ({ data, respond }) => {
174 | chrome.tabs.create(
175 | {
176 | url: chrome.runtime.getURL(`www/index.html#${data.url}`)
177 | },
178 | (/*newTab*/) => {
179 | // Tab opened
180 | // do something with it (cross tab messaging ?)
181 | //console.log('background.openTabUrl', data);
182 |
183 | // TODO: this works, but we need a better system that takes into account the asynchronous nature of this
184 | if (data.content) {
185 | setTimeout(() => {
186 | bridge.send('background.selectedText', data.content);
187 | }, 1000);
188 | }
189 | }
190 | );
191 |
192 | respond(data);
193 | });
194 |
195 | /**
196 | * @description Send a notification to the user via Chrome's built-in notification system
197 | * @param {object} { data.title, data.message, data.icon }
198 | */
199 | bridge.on('background.displayNotification', ({ data }) => {
200 | chrome.notifications.create('', {
201 | title : data.title,
202 | message : data.message,
203 | iconUrl : data.icon ? data.icon : 'www/logo.png',
204 | silent : true,
205 | type : 'basic'
206 | });
207 | });
208 |
209 | // TODO we need a wrapper that can pass trhough background and back to content
210 | // https://quasar.dev/quasar-cli-vite/developing-browser-extensions/bex-communication#communication-rules
211 | /*
212 | bridge.on('background.proxyEvent', ({ data }) => {
213 | bridge.send(data.destination, {
214 | payload: data.payload,
215 | replyTo: data.replyTo
216 | });
217 | });
218 | */
219 |
220 | sendSettingsToContent(bridge);
221 | //#endregion -------------------- OpenAI -----------------------
222 | });
223 |
--------------------------------------------------------------------------------
/src-bex/bex-flag.d.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | // THIS FEATURE-FLAG FILE IS AUTOGENERATED,
3 | // REMOVAL OR CHANGES WILL CAUSE RELATED TYPES TO STOP WORKING
4 | import "quasar/dist/types/feature-flag";
5 |
6 | declare module "quasar/dist/types/feature-flag" {
7 | interface QuasarFeatureFlags {
8 | bex: true;
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src-bex/content.ts:
--------------------------------------------------------------------------------
1 | // Hooks added here have a bridge allowing communication between the BEX Content Script and the Quasar Application.
2 | // More info: https://quasar.dev/quasar-cli/developing-browser-extensions/content-hooks
3 | import { bexContent } from 'quasar/wrappers';
4 | import { BexBridge } from '@quasar/app-vite/types/bex';
5 |
6 | import { IUserSettings } from 'src/js/libs/types/IUserSettings';
7 |
8 | // Import Modules
9 | import iframe from './modules/iframeComponent';
10 | //import contextMenu from './modules/contextMenuComponent';
11 |
12 | /**
13 | * Initialize the content script
14 | */
15 | function main(bridge: BexBridge, settings: IUserSettings) {
16 | /**
17 | * Initialize Modules (loaded above)
18 | */
19 | //contextMenu(bridge);
20 | iframe(bridge, settings);
21 | }
22 |
23 | /**
24 | * This is the main entry point for the BEX Content Script.
25 | */
26 | // BUG: This loads as many times as the page is refreshed !!!
27 | // Warning: This will bind multiple times, fix it, or unbind before binding
28 | export default bexContent(async (bridge: BexBridge) => {
29 | //console.log('LOADING:', Date.now(), 'content.ts::bexContent');
30 |
31 | // INFO: Fires when the background is ready, and sets userSettings from Database
32 | // Seems to still work, even though using once() instead of on() : mayber should use this elsewhere to prevent those double loads ?
33 | bridge.once('background.ready', async ({ data }) => {
34 | //console.log('background.ready in content', data);
35 |
36 | main(bridge, data.settings);
37 | });
38 |
39 | });
40 |
--------------------------------------------------------------------------------
/src-bex/dom.ts:
--------------------------------------------------------------------------------
1 | // https://quasar.dev/quasar-cli-vite/developing-browser-extensions/dom-script
2 | // Hooks added here have a bridge allowing communication between the Web Page and the BEX Content Script.
3 | import { bexDom } from 'quasar/wrappers';
4 | // import { BexBridge } from '@quasar/app-vite/types/bex';
5 |
6 | // Quasar: internal function
7 | export default bexDom((/* bridge: BexBridge */) => {
8 | //console.log('LOADING: dom.ts', bridge);
9 | })
10 |
--------------------------------------------------------------------------------
/src-bex/icons/icon-128x128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/robert-hoffmann/PowerToys4OpenAI/e8aa87e64abe4ba9a11b99f984e6dd0429358f90/src-bex/icons/icon-128x128.png
--------------------------------------------------------------------------------
/src-bex/icons/icon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/robert-hoffmann/PowerToys4OpenAI/e8aa87e64abe4ba9a11b99f984e6dd0429358f90/src-bex/icons/icon-16x16.png
--------------------------------------------------------------------------------
/src-bex/icons/icon-48x48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/robert-hoffmann/PowerToys4OpenAI/e8aa87e64abe4ba9a11b99f984e6dd0429358f90/src-bex/icons/icon-48x48.png
--------------------------------------------------------------------------------
/src-bex/icons/search.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src-bex/libs/sites-detection.ts:
--------------------------------------------------------------------------------
1 | export interface SearchEngines {
2 | /** Website name */
3 | name: string,
4 |
5 | /** If URL matches, then module will be loaded */
6 | url: RegExp,
7 |
8 | /** Possible target containers */
9 | targets: Configuration[],
10 |
11 | /** If site supports sidebar injection **/
12 | addSidebar: boolean
13 | }[]
14 |
15 | export interface Configuration {
16 | /**
17 | * From where to extract the users search phrase
18 | * @type {query selector}
19 | * */
20 | userInputs: string[],
21 |
22 | /** Array of possible targets of where the module will be attached to in the DOM */
23 | containers: string[],
24 |
25 | /** How to add the module to the selector */
26 | method: 'append' | 'prepend',
27 |
28 | /** Classes to add to the component */
29 | classes?: string[],
30 |
31 | /** Styles to apply to the component */
32 | styles?: {
33 | [key: string]: string
34 | }[]
35 | }
36 |
37 | export const config: SearchEngines[] = [
38 | {
39 | name : 'Google Search',
40 | url : /https?:\/\/.*(google)\.[a-z]{2,3}\/search\?.*(q=.*)/,
41 | addSidebar : false,
42 | targets: [
43 | {
44 | userInputs : [
45 | "input[name='q']",
46 | "textarea[name='q']"
47 | ],
48 | containers: [
49 | '#rhs'
50 | ],
51 | method : 'prepend',
52 | classes : [
53 | 'ptoia-companion-prepend'
54 | ]
55 | },
56 | {
57 | userInputs : [
58 | "input[name='q']",
59 | "textarea[name='q']"
60 | ],
61 | containers: [
62 | '#rcnt'
63 | ],
64 | method : 'append',
65 | classes : [
66 | 'ptoia-companion-sidecontent',
67 | 'append'
68 | ],
69 | styles: [
70 | { marginLeft: '30px' },
71 | { width : '367px' }
72 | ]
73 | }
74 | ],
75 | },
76 | {
77 | name : 'Bing Search',
78 | url : /https?:\/\/.*(bing)\.[a-z]{2,3}\/search\?.*(q=.*)/,
79 | addSidebar : false,
80 | targets: [
81 | {
82 | userInputs : [
83 | "[name='q']"
84 | ],
85 | containers: [
86 | '#b_context'
87 | ],
88 | method : 'prepend',
89 | classes : [
90 | 'ptoia-companion-prepend'
91 | ],
92 | styles: [
93 | { margin: '0px 0px 20px -20px' },
94 | { width : '108%' }
95 | ]
96 | }
97 | ],
98 | },
99 | {
100 | name : 'Yahoo Search',
101 | url : /https?:\/\/.*(search\.yahoo)\.[a-z]{2,3}\/search\?.*(p=.*)/,
102 | addSidebar : false,
103 | targets: [
104 | {
105 | userInputs : [
106 | "[name='p']"
107 | ],
108 | containers: [
109 | '#right',
110 | '.Contents__inner.Contents__inner--sub'
111 | ],
112 | method : 'prepend',
113 | classes : [
114 | 'ptoia-companion-prepend'
115 | ],
116 | styles: [
117 | { margin: '0px 0px 20px 0px' },
118 | { width : '370px' }
119 | ]
120 | },
121 | {
122 | userInputs : [
123 | "[name='p']"
124 | ],
125 | containers: [
126 | '#cols',
127 | '#contents__wrap'
128 | ],
129 | method : 'append',
130 | classes : [
131 | 'ptoia-companion-sidecontent',
132 | 'append'
133 | ],
134 | styles: [
135 | { margin: '0px 0px 20px 0px' },
136 | { width : '370px' }
137 | ]
138 | }
139 | ],
140 | },
141 | {
142 | name : 'DuckDuckGo Search',
143 | url : /https?:\/\/.*(duckduckgo)\.[a-z]{2,3}\/\?.*(q=.*)/,
144 | addSidebar : false,
145 | targets: [
146 | {
147 | userInputs : [
148 | "input[name='q']"
149 | ],
150 | containers: [
151 | '.results--sidebar.js-results-sidebar'
152 | ],
153 | method : 'prepend',
154 | classes : [
155 | 'ptoia-companion-prepend'
156 | ],
157 | styles: [
158 | { margin: '0 0 10px 0' },
159 | { width : '100%' }
160 | ]
161 | },
162 | {
163 | userInputs : [
164 | "input[name='q']"
165 | ],
166 | containers: [
167 | '#links_wrapper'
168 | ],
169 | method : 'append',
170 | classes : [
171 | 'ptoia-companion-sidecontent',
172 | 'append'
173 | ],
174 | styles: [
175 | { margin: '0 0 10px 0' },
176 | { width : '100%' }
177 | ]
178 | }
179 | ],
180 | },
181 | {
182 | name : 'Baidu Search',
183 | url : /https?:\/\/.*(baidu)\.[a-z]{2,3}\/s?.*/,
184 | addSidebar : false,
185 | targets: [
186 | {
187 | userInputs : [
188 | "input[name='wd']"
189 | ],
190 | containers: [
191 | '#content_right'
192 | ],
193 | method : 'prepend',
194 | classes : [
195 | 'ptoia-companion-prepend'
196 | ],
197 | styles: [
198 | { width : '367px' }
199 | ]
200 | },
201 | {
202 | userInputs : [
203 | "input[name='wd']"
204 | ],
205 | containers: [
206 | '#container'
207 | ],
208 | method : 'append',
209 | classes : [
210 | 'ptoia-companion-sidecontent',
211 | 'append'
212 | ],
213 | styles: [
214 | { width : '367px' }
215 | ]
216 | }
217 | ],
218 | },
219 | {
220 | name : 'Yandex Search',
221 | url : /https?:\/\/.*(yandex)\.[a-z]{2,3}\/search\/\?.*(text=.*)/,
222 | addSidebar : false,
223 | targets: [
224 | {
225 | userInputs : [
226 | "input[name='text']"
227 | ],
228 | containers: [
229 | '#search-result-aside'
230 | ],
231 | method : 'prepend',
232 | classes : [
233 | 'ptoia-companion-prepend'
234 | ],
235 | styles: [
236 | { margin: '0 0 10px 0' },
237 | { width : '100%' }
238 | ]
239 | }
240 | ],
241 | },
242 | {
243 | name : 'Naver Search',
244 | url : /https?:\/\/.*(search\.naver)\.[a-z]{2,3}\/search.naver\?.*/,
245 | addSidebar : false,
246 | targets: [
247 | {
248 | userInputs : [
249 | "input[name='query']"
250 | ],
251 | containers: [
252 | '#sub_pack'
253 | ],
254 | method : 'prepend',
255 | classes : [
256 | 'ptoia-companion-prepend'
257 | ],
258 | styles: [
259 | { margin: '0px 0px 15px 16px' },
260 | { width : '400px' }
261 | ]
262 | },
263 | {
264 | userInputs : [
265 | "input[name='query']"
266 | ],
267 | containers: [
268 | '#content'
269 | ],
270 | method : 'append',
271 | classes : [
272 | 'ptoia-companion-sidecontent',
273 | 'append'
274 | ],
275 | styles: [
276 | { margin: '0px 0px 15px 16px' },
277 | { width : '400px' }
278 | ]
279 | }
280 | ],
281 | },
282 | {
283 | name : 'Brave Search',
284 | url : /https?:\/\/.*(search\.brave)\.[a-z]{2,3}\/search\?.*(q=.*)/,
285 | addSidebar : false,
286 | targets: [
287 | {
288 | userInputs : [
289 | "[name='q']"
290 | ],
291 | containers: [
292 | '#side-right'],
293 | method : 'prepend',
294 | styles: [
295 | { margin: '0px 0px 20px 0px' },
296 | { width : '370px' }
297 | ]
298 | }
299 | ],
300 | },
301 | {
302 | name : 'SearX Search',
303 | url : /https?:\/\/.*(searx\.thegpm)\.[a-z]{2,3}\/.*/,
304 | addSidebar : false,
305 | targets: [
306 | {
307 | userInputs : [
308 | "input[name='q']"
309 | ],
310 | containers: [
311 | '#sidebar_results'],
312 | method : 'prepend',
313 | styles: [
314 | { width : '360px' }
315 | ]
316 | }
317 | ],
318 | },
319 | {
320 | name : 'Other Sites',
321 | url : /https?:\/\/(.*)\.[a-z]{2,3}/i,
322 | addSidebar : true,
323 | targets: [
324 | {
325 | userInputs : [],
326 | containers : [
327 | 'body'
328 | ],
329 | method : 'append',
330 | styles: [
331 | { margin : '0px 0px 20px 0px' },
332 | { width : '450px' },
333 | { position : 'absolute' },
334 | { top : '150px' },
335 | { right : '0' },
336 | { zIndex : '9999' },
337 | ]
338 | }
339 | ],
340 | },
341 | ]
342 |
--------------------------------------------------------------------------------
/src-bex/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "manifest_version": 3,
3 | "name": "__MSG_name__",
4 | "short_name": "__MSG_shortName__",
5 | "description": "__MSG_description__",
6 | "default_locale": "en",
7 | "icons": {
8 | "16" : "icons/icon-16x16.png",
9 | "48" : "icons/icon-48x48.png",
10 | "128" : "icons/icon-128x128.png"
11 | },
12 |
13 | "permissions": [
14 | "storage",
15 | "notifications"
16 | ],
17 |
18 | "action": {
19 | "default_title": "__MSG_name__"
20 | },
21 |
22 | "options_ui": {
23 | "page" : "www/index.html#/index/settings/search",
24 | "open_in_tab": true
25 | },
26 |
27 | "background": {
28 | "service_worker": "background.js"
29 | },
30 |
31 | "content_scripts": [
32 | {
33 | "matches": [ "*://*/*" ],
34 | "css" : [ "assets/content.css" ],
35 | "js" : [
36 | "content.js"
37 | ],
38 | "run_at": "document_end"
39 | }
40 | ],
41 |
42 | "content_security_policy": {
43 | "extension_pages": "script-src 'self'; object-src 'self';"
44 | },
45 |
46 | "web_accessible_resources": [
47 | {
48 | "resources": [ "*" ],
49 | "matches" : [ "*://*/*" ]
50 | }
51 | ]
52 | }
53 |
--------------------------------------------------------------------------------
/src-bex/modules/contextMenuComponent.ts:
--------------------------------------------------------------------------------
1 | // We create a new folder + file:
2 | // src-bex/dom/detect-quasar.js
3 | // https://quasar.dev/quasar-cli-vite/developing-browser-extensions/dom-script
4 | import { BexBridge } from '@quasar/app-vite/types/bex';
5 |
6 | //#region --------------------------- Behavioral Modules ---------------------------
7 | function main(bridge: BexBridge) {
8 |
9 | function displayContextMenu(e: MouseEvent) {
10 | //console.log('displayContextMenu', e.target);
11 |
12 | // ctrl is not pressed, do not open context menu
13 | if (!e.ctrlKey) {
14 | return;
15 | }
16 | e.preventDefault();
17 |
18 | // get existing context menu
19 | let contextMenu = document.getElementById('ptoia-context-menu');
20 |
21 | // create context menu if it does not exist
22 | if (contextMenu === null) {
23 | contextMenu = document.createElement('div');
24 | contextMenu.id = 'ptoia-context-menu';
25 | contextMenu.classList.add('ptoia-context-menu');
26 |
27 | contextMenu.addEventListener('click', (ev: MouseEvent) => {
28 | //console.log('contextMenu click', ev);
29 |
30 | bridge.send('content::context-action', { clientX: ev.clientX, clientY: ev.clientY })
31 | .then((response) => {
32 | console.log({
33 | file : 'contentClick.ts',
34 | method: {
35 | name: 'content::context-action',
36 | type: 'send'
37 | },
38 | result: response
39 | });
40 | });
41 | });
42 |
43 | contextMenu.addEventListener('click', (ev: MouseEvent) => {
44 | const target = ev.target as HTMLElement;
45 |
46 | target.style.display = 'none';
47 | //console.log('target', target.dataset.action);
48 | getSelectedText(ev);
49 | });
50 |
51 | document.body.appendChild(contextMenu);
52 | }
53 |
54 | function populateContextMenu(menu: HTMLElement) {
55 | menu.innerHTML = '';
56 |
57 | actions.forEach((action) => {
58 | const item = document.createElement('div');
59 | item.dataset.action = action.action;
60 |
61 | item.innerText = action.name;
62 | item.classList.add('ptoia-context-menu-item');
63 |
64 | menu.appendChild(item)
65 | });
66 | }
67 |
68 | // display
69 | populateContextMenu(contextMenu);
70 | contextMenu.style.display = 'block';
71 | contextMenu.style.top = `${e.pageY}px`;
72 | contextMenu.style.left = `${e.pageX}px`;
73 |
74 | }
75 |
76 | function detectKeyDown(e: KeyboardEvent) {
77 | //console.log('detectKeyDown', e);
78 |
79 | if (e.key === 'Escape') {
80 | const contextMenu = document.getElementById('ptoia-context-menu');
81 | if (contextMenu !== null) {
82 | contextMenu.style.display = 'none';
83 | }
84 | }
85 | }
86 |
87 | // may want to use this to manipulmate the DOM
88 | function detectClick(e: MouseEvent) {
89 | const target = e.target as HTMLElement;
90 |
91 | //console.log('detectClick', e.target);
92 |
93 | // dont close if we click on the context menu
94 | if (target.closest('.ptoia-context-menu') !== null) {
95 | return;
96 | }
97 |
98 | const contextMenu = document.getElementById('ptoia-context-menu');
99 | if (contextMenu !== null) {
100 |
101 |
102 | contextMenu.style.display = 'none';
103 | }
104 | }
105 |
106 |
107 | function getSelectedText(e: MouseEvent) {
108 | const selectedText = window.getSelection()?.toString();
109 |
110 | if (!!selectedText) {
111 | //console.log('getSelectedText', selectedText);
112 | }
113 | }
114 |
115 | // bind context
116 | document.addEventListener('contextmenu', (e: MouseEvent) => displayContextMenu(e));
117 | //document.addEventListener('mouseup' , (e: MouseEvent) => getSelectedText(e));
118 | document.addEventListener('click' , (e: MouseEvent) => detectClick(e));
119 | document.addEventListener('keydown', (e: KeyboardEvent) => detectKeyDown(e));
120 | }
121 | //#endregion ------------------------ Behavioral Modules ---------------------------
122 |
123 | const actions = [
124 | {
125 | name : 'getTargetText',
126 | action : 'getTargetText'
127 | },
128 | {
129 | name : 'action2',
130 | action : 'action2'
131 | },
132 | {
133 | name : 'action3',
134 | action : 'action3'
135 | },
136 | {
137 | name : 'action4',
138 | action : 'action4'
139 | }
140 | ];
141 |
142 |
143 | // Export module
144 | export default function contextMenu(bridge: BexBridge) {
145 | main(bridge);
146 | }
147 |
--------------------------------------------------------------------------------
/src-bex/modules/iframeComponent.ts:
--------------------------------------------------------------------------------
1 | // We create a new folder + file:
2 | // src-bex/dom/detect-quasar.js
3 | // https://quasar.dev/quasar-cli-vite/developing-browser-extensions/dom-script
4 | import { BexBridge } from '@quasar/app-vite/types/bex';
5 | import { useDebounceFn } from '@vueuse/core';
6 | import { config, Configuration } from '../libs/sites-detection';
7 | import { IUserSettings } from 'src/js/libs/types/IUserSettings';
8 |
9 | //console.log('iframeComponent.ts');
10 |
11 | const
12 | component = document.createElement('div'),
13 | iFrame = document.createElement('iframe'),
14 | defaulContainerHeight = '56px',
15 | defaulContainerWidth = '370px';
16 |
17 | let selectedText = '';
18 |
19 | /**
20 | * Set the height of our container housing our BEX
21 | * @param height
22 | */
23 | const setContainerHeight = (height: string) => {
24 | component.style.height = `${height}px`;
25 | }
26 |
27 | /**
28 | * The code below will get everything going. Initialize the iFrame with defaults and add it to the page.
29 | * @type {string}
30 | */
31 | component.id = 'ptoia-companion-container';
32 | iFrame.allow = 'clipboard-write';
33 |
34 | // Default settings for container
35 | Object.assign(component.style, {
36 | overflow: 'hidden',
37 | height : defaulContainerHeight,
38 | width : defaulContainerWidth,
39 | });
40 |
41 | // Default settings for Iframe
42 | Object.assign(iFrame.style, {
43 | overflow: 'hidden',
44 | height : '100%',
45 | width : '100%',
46 | border : '0'
47 | });
48 |
49 | /**
50 | * This is a workaround to events triggering multiple times
51 | */
52 | const toggleCompanion = useDebounceFn(() => {
53 | component.classList.toggle('ptoia-sidebar-closed');
54 | }, 250);
55 |
56 | /**
57 | * Inject a module into the DOM
58 | */
59 | function injectContentModule(bridge: BexBridge, configuration: Configuration, asSidebar: boolean) {
60 | //console.log('injectContentModule::content.ts', configuration);
61 |
62 | // INFO: find the element we want to append to
63 | let container: Element | undefined;
64 | configuration.containers.forEach((selector) => {
65 | const element = document.querySelector(selector);
66 |
67 | if (element) {
68 | container = element;
69 | return;
70 | }
71 | });
72 |
73 | if (container) {
74 | // INFO: Wrap in shadow DOM to prevent CSS conflicts
75 | // Also keeps certain sites from blocking iframes
76 | // https://stackoverflow.com/a/67613697/896849
77 |
78 | // BUG: was having random this error randomly
79 | // Shadow root cannot be created on a host which already hosts a shadow tree
80 | // So for now, just wrapping in a try/catch
81 | try {
82 | const shadowDom = component.attachShadow({ mode:'closed' });
83 | shadowDom.appendChild(iFrame);
84 | } catch (e) {
85 | console.error('Error attaching shadow DOM', e);
86 | // could not attach to shadow DOM, so just append it to the container...
87 | component.appendChild(iFrame);
88 | }
89 |
90 | // When the page loads, insert our browser extension app.
91 | iFrame.src = chrome.runtime.getURL('www/index.html#/search-companion');
92 |
93 | // -------------------------------------------------------------------
94 |
95 | // append or prepend the container to the page
96 | container[configuration.method](component);
97 |
98 | // Add any classes to the container if needed
99 | if (configuration.classes) {
100 | configuration.classes.forEach((className) => {
101 | component.classList.add(className);
102 | });
103 | }
104 |
105 | // Apply any styles to the container if needed
106 | if (configuration.styles) {
107 | configuration.styles.forEach((style) => {
108 | //console.log('style', style);
109 |
110 | const styleName : string = Object.entries(style)[0][0];
111 | const styleValue: string = Object.entries(style)[0][1];
112 | component.style[styleName] = styleValue;
113 | });
114 | }
115 |
116 | // if we should display as a sidebar (starts in closed position)
117 | if (asSidebar) {
118 | component.classList.add('ptoia-sidebar-closed');
119 | }
120 |
121 | let prompt = '';
122 | if (configuration.userInputs) {
123 | // Get the user input
124 | configuration.userInputs.forEach((selector) => {
125 | const input = document.querySelector(selector);
126 |
127 | // if we find a match, assign it and break out of the loop
128 | if (input) {
129 | prompt = input?.value || '';
130 | return;
131 | }
132 | });
133 | }
134 |
135 | // INFO: listen for page ready
136 | bridge.on('content.pageReady', () => {
137 | // Set the value of the user input, to the SearchCompanion input field
138 | bridge.send('page.searchCompanion.initialize', {
139 | prompt : prompt || '',
140 | sidebar: asSidebar || false,
141 | });
142 | });
143 | }
144 | }
145 |
146 | //#region --------------------------- Behavioral Modules ---------------------------
147 | function main(bridge: BexBridge, settings: IUserSettings) {
148 | // see if the site we are on is in our list of supported sites
149 | const website = config.find((item) => {
150 | const match = window.location.href.match(item.url);
151 | return match && match[1];
152 | });
153 |
154 | //console.log('website', website);
155 |
156 | // only insert content on supported websites
157 | if (website) {
158 | //console.log('we found a URL match', website);
159 |
160 | // Attach a listener to open the Conversation Manager when the user presses Alt+C
161 | document.addEventListener('keydown', (event: KeyboardEvent) => {
162 | if (event.altKey && event.code === 'KeyC') {
163 | bridge.send('background.openTabUrl',
164 | {
165 | url : '/index',
166 | content: selectedText
167 | }
168 | );
169 | }
170 | });
171 |
172 | // attach an event listener to see if text has been selected
173 | // INFO: ask GPT about: Selection API (and an event listener example)
174 | document.addEventListener('mouseup', (/* event */) => {
175 | selectedText = window.getSelection()?.toString() || '';
176 |
177 | if (selectedText !== '') {
178 | //console.log('Text has been selected: ' + selectedText, event);
179 | // do something with the selected text
180 | }
181 | });
182 |
183 | // loop over all posssible targets
184 | const configuration = website.targets.find((target) => {
185 | // loop over all possible containers
186 | const container = target.containers.find((selector) => {
187 | return document.querySelector(selector);
188 | });
189 |
190 | // if we have a container to attach to for this target, return the target
191 | if (container) {
192 | return target;
193 | }
194 | });
195 |
196 | if (configuration) {
197 | //console.log('website.addSidebar' , website.addSidebar);
198 | //console.log('settings.showSidebar', settings.showSidebar);
199 |
200 | // Dont add sidebar if user has it disabled
201 | if (website.addSidebar == true && settings.showSidebar == false) {
202 | // just exit and add nothing
203 | return;
204 | }
205 |
206 | // inject module if we found a match
207 | injectContentModule(bridge, configuration, website.addSidebar);
208 | }
209 | }
210 | }
211 | //#endregion ------------------------ Behavioral Modules ---------------------------
212 | // Export module
213 | export default function iframe(bridge: BexBridge, settings: IUserSettings) {
214 | //console.log('LOADING:', Date.now(), 'iframeComponent.export');
215 |
216 | main(bridge, settings);
217 |
218 | bridge.on('content.onResize', async ({ data }) => {
219 | //console.log('content.onResize', data);
220 | setContainerHeight(data.height);
221 | });
222 |
223 | // INFO: Event when the sidebar should be shown/hidden
224 | bridge.on('content.toggleCompanion', async () => {
225 | // INFO: This is a workaround to events triggering multiple times
226 | toggleCompanion();
227 | });
228 |
229 | bridge.on('content.scrollToTop', () => {
230 | window.scrollTo(0, 0);
231 | });
232 |
233 | bridge.on('content.copyToClipboard', ({ data }) => {
234 | //console.log('content.copyToClipboard', data);
235 | navigator.clipboard.writeText(data || '');
236 | });
237 | }
238 |
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
53 |
--------------------------------------------------------------------------------
/src/assets/_readme.md:
--------------------------------------------------------------------------------
1 | ## Accessible via:
2 | * Inside Chrome extension
3 | * www/assets/*
4 | * Inside *.vue files
5 | * ~assets/*
6 |
--------------------------------------------------------------------------------
/src/boot/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/robert-hoffmann/PowerToys4OpenAI/e8aa87e64abe4ba9a11b99f984e6dd0429358f90/src/boot/.gitkeep
--------------------------------------------------------------------------------
/src/boot/i18next.ts:
--------------------------------------------------------------------------------
1 | import { boot } from 'quasar/wrappers';
2 |
3 | import i18next from 'i18next';
4 | import I18NextVue from 'i18next-vue';
5 | import LanguageDetector from 'i18next-browser-languagedetector';
6 |
7 | import de from '../i18n/de';
8 | import en from '../i18n/en';
9 | import es from '../i18n/es';
10 | import fr from '../i18n/fr';
11 | import hi from '../i18n/hi';
12 | import ja from '../i18n/ja';
13 | import pt from '../i18n/pt';
14 | import ru from '../i18n/ru';
15 | import tr from '../i18n/tr';
16 | import zh from '../i18n/zh';
17 |
18 | // https://dev.to/adrai/how-to-properly-internationalize-a-vue-application-using-i18next-1doj
19 | // usage in vue file: https://github.com/i18next/i18next-vue
20 | export default boot(({ app }) => {
21 | i18next
22 | // detect user language
23 | // learn more: https://github.com/i18next/i18next-browser-languageDetector
24 | .use(LanguageDetector)
25 | // init i18next
26 | // for all options read: https://www.i18next.com/overview/configuration-options
27 | .init({
28 | debug : false,
29 | fallbackLng: 'en',
30 | resources: {
31 | de: de,
32 | en: en,
33 | es: es,
34 | fr: fr,
35 | hi: hi,
36 | ja: ja,
37 | pt: pt,
38 | ru: ru,
39 | tr: tr,
40 | zh: zh
41 | },
42 | detection: {
43 | order: ['localStorage', 'navigator'],
44 | }
45 | });
46 |
47 | // Set i18n instance on app
48 | app.use(I18NextVue, { i18next })
49 | });
50 |
--------------------------------------------------------------------------------
/src/components/SearchSettingsComponent.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{ $t(translation.global.settings) }}
6 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | {{ $t(translation.searchSettings.interfaceLanguage) }}
22 |
23 |
24 |
25 |
26 |
31 |
32 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 | {{ $t(translation.searchSettings.model) }}
47 |
48 |
49 |
50 |
51 |
52 |
53 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 | {{ $t(translation.searchSettings.apiKeyTitle) }}
68 |
69 |
70 |
71 |
72 |
80 |
81 |
82 |
83 |
84 |
85 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 | {{ $t(translation.searchSettings.showSidebarOnAllSites) }}
106 |
107 |
108 |
124 |
125 |
126 |
127 | {{ $t(translation.searchSettings.triggerModeTitle) }}
128 |
129 |
130 |
131 |
132 |
133 |
138 |
139 |
140 |
141 |
142 |
143 | {{ $t(translation.searchSettings.triggerModeManual) }}
144 | {{ $t(translation.searchSettings.triggerModeManualHint) }}
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 | {{ $t(translation.searchSettings.triggerModeQuestion) }}
154 | {{ $t(translation.searchSettings.triggerModeQuestionHint) }}
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 | {{ $t(translation.searchSettings.triggerModeAuto) }}
164 | {{ $t(translation.searchSettings.triggerModeAutoHint) }}
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
185 |
186 |
274 |
275 |
278 |
--------------------------------------------------------------------------------
/src/components/leftmenu/ConversationsComponent.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{ t(translation.conversations.title) }}
5 |
6 |
7 |
15 |
16 |
17 |
22 |
27 |
28 |
29 |
30 |
31 |
36 |
37 |
38 |
39 |
40 |
43 |
44 | {{ item.title }}
45 | titleChanged(value, initialValue, item)"
50 | >
51 |
60 |
61 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 | {{ item.timestamp }}
74 |
75 |
76 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
102 |
103 |
217 |
218 |
221 |
--------------------------------------------------------------------------------
/src/components/leftmenu/MenuLeftComponent.vue:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
9 |
10 |
11 |
12 |
13 |
14 |
19 |
20 |
26 |
27 |
33 |
34 |
41 |
48 |
55 |
62 |
69 |
76 |
83 |
90 |
97 |
104 |
111 |
112 |
113 |
114 |
115 |
116 |
125 |
126 |
205 |
206 |
215 |
216 |
217 |
--------------------------------------------------------------------------------
/src/components/leftmenu/ProfilesComponent.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{ t(translation.profiles.title) }}
5 |
6 |
7 |
15 |
16 |
17 |
22 |
28 |
29 |
30 |
31 |
32 |
37 |
38 |
39 |
40 |
41 |
44 | {{ item.title }}
45 |
46 | {{ item.description }}
47 |
48 |
49 |
50 |
51 |
52 | $router.push(`/index/profiles/${item.id || 0}`)"
56 | :title="t(translation.global.edit)"
57 | flat
58 | round
59 | outline
60 | />
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
77 |
78 |
181 |
182 |
185 |
--------------------------------------------------------------------------------
/src/components/leftmenu/SettingsComponent.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{ t(translation.global.settings) }}
6 |
7 |
8 |
9 |
10 |
11 | $router.push(item.route)"
15 | >
16 |
17 |
18 |
19 |
20 |
21 | {{ item.title }}
22 | {{ item.caption }}
23 |
24 |
25 |
26 |
27 |
28 |
37 |
38 |
73 |
74 |
77 |
--------------------------------------------------------------------------------
/src/css/app.scss:
--------------------------------------------------------------------------------
1 | // app global css in SCSS form
2 | /* ===== Scrollbar CSS ===== */
3 | /* Firefox */
4 | * {
5 | scrollbar-width: auto;
6 | scrollbar-color: #99141d #ffffff;
7 | }
8 |
9 | /* Chrome, Edge, and Safari */
10 | *::-webkit-scrollbar {
11 | width: 16px;
12 | }
13 |
14 | *::-webkit-scrollbar-track {
15 | background: #ffffff;
16 | }
17 |
18 | *::-webkit-scrollbar-thumb {
19 | background-color: #99141d;
20 | border-radius: 10px;
21 | border: 2px solid #ffffff;
22 | }
23 |
24 | .text-underline {
25 | text-decoration: underline;
26 | }
27 |
--------------------------------------------------------------------------------
/src/css/quasar.variables.scss:
--------------------------------------------------------------------------------
1 | // Quasar SCSS (& Sass) Variables
2 | // --------------------------------------------------
3 | // To customize the look and feel of this app, you can override
4 | // the Sass/SCSS variables found in Quasar's source Sass/SCSS files.
5 |
6 | // Check documentation for full list of Quasar variables
7 |
8 | // Your own variables (that are declared here) and Quasar's own
9 | // ones will be available out of the box in your .vue/.scss/.sass files
10 |
11 | // It's highly recommended to change the default colors
12 | // to match your app's branding.
13 | // Tip: Use the "Theme Builder" on Quasar's documentation website.
14 |
15 | $primary : #ce1925;
16 | $secondary : #99141d;
17 | $accent : #ffb201;
18 |
19 | $dark : #1D1D1D;
20 | $dark-page : #121212;
21 |
22 | $positive : #21BA45;
23 | $negative : #C10015;
24 | $info : #31CCEC;
25 | $warning : #F2C037;
26 |
27 |
28 | // Alexia
29 | $_primary : #e63440;
30 | $_secondary : #99141d;
31 | $_accent : #ffb201;
32 | $_tertiary : #9a3345;
33 |
--------------------------------------------------------------------------------
/src/env.d.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 |
3 | declare namespace NodeJS {
4 | interface ProcessEnv {
5 | NODE_ENV: string;
6 | VUE_ROUTER_MODE: 'hash' | 'history' | 'abstract' | undefined;
7 | VUE_ROUTER_BASE: string | undefined;
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/src/i18n/de/index.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | translation: {
3 | global: {
4 | yes : 'Ja',
5 | no : 'Nein',
6 | save : 'Speichern',
7 | close : 'Schließen',
8 | edit : 'Bearbeiten',
9 | copy : 'Kopieren',
10 | play : 'Abspielen',
11 | record : 'Aufnehmen',
12 | import : 'Importieren',
13 | export : 'Exportieren',
14 | upload : 'Hochladen',
15 | cancel : 'Abbrechen',
16 | delete : 'Löschen',
17 | retry : 'Erneut versuchen',
18 | missing : 'fehlend',
19 | settings : 'Einstellungen',
20 | search : 'Eine Frage stellen',
21 | searchHint : 'Oder ein Thema ausführlich behandeln',
22 | authError : 'Fehler: {{statusCode}}\n\nEs tut uns leid, aber beim Authentifizieren ist ein Fehler aufgetreten.\nBitte überprüfen Sie Ihren API-Schlüssel und Ihr Modell.',
23 | comingSoon : 'Kommt bald',
24 | donate : 'Spenden {{name}}',
25 | donateCopy : '{{name}}-Adresse in die Zwischenablage kopiert',
26 | copiedToClipBoard : 'Inhalt in Zwischenablage kopiert',
27 | },
28 | searchCompanion: {
29 | title : 'Suchbegleiter',
30 | search : 'Suche',
31 | searchHint : 'Vertiefen Sie dieses Thema',
32 | clear : 'Verlauf löschen',
33 | },
34 | conversations: {
35 | me : 'Ich',
36 | welcome : 'Hallo, wie kann ich Ihnen helfen?',
37 | title : 'Konversationen',
38 | newConversation : 'Neues Gespräch',
39 | sttHint : 'Hören aktivieren mit Strg+Leertaste',
40 | sttOn : 'Spracherkennung: Aktiviert',
41 | sttOff : 'Spracherkennung: Deaktiviert',
42 | inputHint : 'Bestätigen Sie mit Strg+Eingabetaste oder verwenden Sie die Spracherkennung mit Strg+Leertaste',
43 | wordCount : 'Wörter',
44 | },
45 | profiles: {
46 | title : 'Profile',
47 | newProfile : 'Neues Profil',
48 | selectProfile : 'Profil auswählen',
49 | selected : 'Aktives Profil: {{name}}',
50 | name : 'Titel',
51 | active : 'Aktiv',
52 | tags : 'Tags',
53 | description : 'Beschreibung',
54 | prompt : 'Systemaufforderung',
55 |
56 | },
57 | settings: {
58 | general : 'Allgemein',
59 | global : 'Globale Einstellungen'
60 | },
61 | searchSettings: {
62 | interfaceLanguage : 'Schnittstellensprache',
63 | showSidebarOnAllSites : 'Seitenleiste auf allen Seiten anzeigen',
64 | triggerModeTitle : 'Auslösemodus',
65 | triggerModeManual : 'Manuell',
66 | triggerModeManualHint : 'Nur bei manueller Suche auslösen',
67 | triggerModeQuestion : 'Frage',
68 | triggerModeQuestionHint : 'Nur bei Endung mit: ? auslösen',
69 | triggerModeAuto : 'Automatisch',
70 | triggerModeAutoHint : 'Systematisch die Suchmaschine abfragen',
71 | apiKeyTitle : 'API-Schlüssel',
72 | apiKeyHint : 'Ihr OpenAI-API-Schlüssel',
73 | model : 'Modell',
74 | }
75 | }
76 | };
77 |
--------------------------------------------------------------------------------
/src/i18n/en/index.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | translation: {
3 | global: {
4 | yes : 'Yes',
5 | no : 'No',
6 | save : 'Save',
7 | close : 'Close',
8 | edit : 'Edit',
9 | copy : 'Copy',
10 | play : 'Play',
11 | record : 'Record',
12 | import : 'Import',
13 | export : 'Export',
14 | upload : 'Upload',
15 | cancel : 'Cancel',
16 | delete : 'Delete',
17 | retry : 'Retry',
18 | missing : 'missing',
19 | settings : 'Settings',
20 | search : 'Ask a question',
21 | searchHint : 'Or expand on a topic',
22 | authError : 'Error: {{statusCode}}\n\nSorry, something went wrong with authentication, please check your API key & model',
23 | comingSoon : 'coming soon',
24 | donate : 'Donate {{name}}',
25 | donateCopy : '{{name}} address copied to clipboard',
26 | copiedToClipBoard : 'Content copied to clipboard',
27 | },
28 | searchCompanion: {
29 | title : 'Search Companion',
30 | search : 'Search',
31 | searchHint : 'Expand on this topic',
32 | clear : 'Clear conversation history',
33 | },
34 | conversations: {
35 | me : 'me',
36 | welcome : 'Hi there, how may i assist you',
37 | title : 'Conversations',
38 | newConversation : 'New conversation',
39 | sttHint : 'Toggle listening with CTRL+SpaceBar',
40 | sttOn : 'Speech Recognition: Activated',
41 | sttOff : 'Speech Recognition: Deactivated',
42 | inputHint : 'You can set focus with CTRL, validate with CTRL+Enter, or use speech recognition with CTRL+SpaceBar',
43 | wordCount : 'Word count',
44 | },
45 | profiles: {
46 | title : 'Profiles',
47 | newProfile : 'New profile',
48 | selectProfile : 'Select a profile',
49 | selected : 'Active Profile: {{name}}',
50 | name : 'Title',
51 | active : 'Active',
52 | tags : 'Tags',
53 | description : 'Description',
54 | prompt : 'System prompt',
55 |
56 | },
57 | settings: {
58 | general : 'General',
59 | global : 'Global settings'
60 | },
61 | searchSettings: {
62 | interfaceLanguage : 'Interface language',
63 | showSidebarOnAllSites : 'Show sidebar on all sites',
64 | triggerModeTitle : 'Trigger Mode',
65 | triggerModeManual : 'Manual',
66 | triggerModeManualHint : 'Only trigger search on manual click',
67 | triggerModeQuestion : 'Question',
68 | triggerModeQuestionHint : 'Only trigger search if ending with: ?',
69 | triggerModeAuto : 'Automatic',
70 | triggerModeAutoHint : 'Systematically query the search engine',
71 | apiKeyTitle : 'API Key',
72 | apiKeyHint : 'Your OpenAI API key',
73 | model : 'Model',
74 | }
75 | }
76 | };
77 |
--------------------------------------------------------------------------------
/src/i18n/es/index.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | translation: {
3 | global: {
4 | yes : 'Sí',
5 | no : 'No',
6 | save : 'Guardar',
7 | close : 'Cerrar',
8 | edit : 'Editar',
9 | copy : 'Copiar',
10 | play : 'Reproducir',
11 | record : 'Grabar',
12 | import : 'Importar',
13 | export : 'Exportar',
14 | upload : 'Cargar',
15 | cancel : 'Cancelar',
16 | delete : 'Eliminar',
17 | retry : 'Reintentar',
18 | missing : 'faltante',
19 | settings : 'Ajustes',
20 | search : 'Hacer una pregunta',
21 | searchHint : 'O expande un tema',
22 | authError : 'Error: {{statusCode}}\noops, algo salió mal con la autenticación, por favor verifica tu clave API y modelo',
23 | comingSoon : 'próximamente disponible',
24 | donate : 'Donar para {{name}}',
25 | donateCopy : 'Dirección {{name}} copiada al portapapeles',
26 | copiedToClipBoard : 'Contenido copiado al portapapeles'
27 | },
28 | searchCompanion: {
29 | title : 'Compañero de búsqueda',
30 | search : 'Buscar',
31 | searchHint : 'Expandir este tema',
32 | clear : 'Borrar historial de conversaciones'
33 | },
34 | conversations: {
35 | me : 'yo',
36 | welcome : 'Hola, ¿cómo puedo ayudarte?',
37 | title : 'Conversaciones',
38 | newConversation : 'Nueva conversación',
39 | sttHint : 'Puedes activarlo/desactivarlo con CTRL+Espacio',
40 | sttOn : 'Reconocimiento de voz: Activado',
41 | sttOff : 'Reconocimiento de voz: Desactivado',
42 | inputHint : 'Puedes validar con CTRL+Intro, o utilizar el reconocimiento de voz con CTRL+Espacio',
43 | wordCount : 'Número de palabras'
44 | },
45 | profiles: {
46 | title : 'Perfiles',
47 | newProfile : 'Nuevo perfil',
48 | selectProfile : 'Seleccionar perfil',
49 | selected : 'Perfil activo: {{name}}',
50 | name : 'Perfiles',
51 | active : 'Activo',
52 | tags : 'Etiquetas',
53 | description : 'Descripción',
54 | prompt : 'Comando de invitación'
55 | },
56 | settings: {
57 | general : 'General',
58 | global : 'Configuraciones globales'
59 | },
60 | searchSettings: {
61 | interfaceLanguage : 'Idioma de la interfaz',
62 | showSidebarOnAllSites : 'Mostrar barra lateral en todos los sitios',
63 | triggerModeTitle : 'Modo de activación',
64 | triggerModeManual : 'Manual',
65 | triggerModeManualHint : 'Activación solo en clic manual',
66 | triggerModeQuestion : 'Pregunta',
67 | triggerModeQuestionHint : 'Activación solo si termina en: ?',
68 | triggerModeAuto : 'Automático',
69 | triggerModeAutoHint : 'Interrogar sistemáticamente el motor de búsqueda',
70 | apiKeyTitle : 'Clave API',
71 | apiKeyHint : 'Tu clave API de OpenAI',
72 | model : 'Modelo'
73 | }
74 | }
75 | };
76 |
--------------------------------------------------------------------------------
/src/i18n/fr/index.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | translation: {
3 | global: {
4 | yes : 'Oui',
5 | no : 'Non',
6 | save : 'Enregistrer',
7 | close : 'Fermer',
8 | edit : 'Modifier',
9 | copy : 'Copier',
10 | play : 'Jouer',
11 | record : 'Enregistrer',
12 | import : 'Importer',
13 | export : 'Exporter',
14 | upload : 'Charger',
15 | cancel : 'Annuler',
16 | delete : 'Supprimer',
17 | retry : 'Ressayer',
18 | missing : 'manquant',
19 | settings : 'Settings',
20 | search : 'Poser une question',
21 | searchHint : 'Ou développez un sujet',
22 | authError : 'Erreur: {{statusCode}}\n\nDésolé, quelque chose s\'est mal passé avec l\'authentification, veuillez vérifier votre clé API & modèle',
23 | comingSoon : 'bientôt disponible',
24 | donate : 'Faire un don de {{name}}',
25 | donateCopy : 'Adresse {{name}} copié dans le presse-papier',
26 | copiedToClipBoard : 'Contenu copié dans le presse-papier',
27 | },
28 | searchCompanion: {
29 | title : 'Compagnon de recherche',
30 | search : 'Chercher',
31 | searchHint : 'Développer ce sujet',
32 | clear : 'Effacer l\'hitorique des conversations',
33 | },
34 | conversations: {
35 | me : 'moi',
36 | welcome : 'Bonjour, comment puis-je vous aider',
37 | title : 'Conversations',
38 | newConversation : 'Nouvelle conversation',
39 | sttHint : 'Vous pouvez l\'activer/désactiver avec CTRL+Espace',
40 | sttOn : 'Reconnaissance Vocale: Activée',
41 | sttOff : 'Reconnaissance Vocale: Désactivée',
42 | inputHint : 'Vous pouvez valider avec CTRL+Entrée, ou utiliser la reconnaissance vocale avec CTRL+Espace',
43 | wordCount : 'Nombre de mots',
44 | },
45 | profiles: {
46 | title : 'Profils',
47 | newProfile : 'Nouveau profil',
48 | selectProfile : 'Sélectionner un profil',
49 | selected : 'Profil Actif: {{name}}',
50 | name : 'Profils',
51 | active : 'Actif',
52 | tags : 'Tags',
53 | description : 'Déscription',
54 | prompt : 'Invité de commande'
55 | },
56 | settings: {
57 | general : 'Général',
58 | global : 'Paramètres globaux'
59 | },
60 | searchSettings: {
61 | interfaceLanguage : 'Langue de l\'interface',
62 | showSidebarOnAllSites : 'Afficher la barre latérale sur tous les sites',
63 | triggerModeTitle : 'Mode de déclenchement',
64 | triggerModeManual : 'Manuel',
65 | triggerModeManualHint : 'Déclenchement uniquement sur clic manuel',
66 | triggerModeQuestion : 'Question',
67 | triggerModeQuestionHint : 'Déclenchement uniquement si se termine par : ?',
68 | triggerModeAuto : 'Automatique',
69 | triggerModeAutoHint : 'Interroger systématiquement le moteur de recherche',
70 | apiKeyTitle : 'Clé API',
71 | apiKeyHint : 'Votre clé API OpenAI',
72 | model : 'Modèle',
73 | }
74 | }
75 | };
76 |
--------------------------------------------------------------------------------
/src/i18n/hi/index.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | translation: {
3 | global: {
4 | yes : 'हाँ',
5 | no : 'नहीं',
6 | save : 'सहेजें',
7 | close : 'बंद करें',
8 | edit : 'संपादित करें',
9 | copy : 'कॉपी करें',
10 | play : 'खेलें',
11 | record : 'रिकॉर्ड करें',
12 | import : 'आयात करें',
13 | export : 'निर्यात करें',
14 | upload : 'अपलोड करें',
15 | cancel : 'रद्द करें',
16 | delete : 'हटाएँ',
17 | retry : 'पुन: प्रयास करें',
18 | missing : 'खोया हुआ',
19 | settings : 'सेटिंग्स',
20 | search : 'सवाल पूछें',
21 | searchHint : 'या एक विषय पर विस्तार करें',
22 | authError : 'त्रुटि: {{statusCode}}\n\nक्षमा करें, प्रमाणीकरण के साथ कुछ गलत हो गया है, कृपया अपनी एपीआई कुंजी और मॉडल जांचें',
23 | comingSoon : 'जल्द ही आ रहा है',
24 | donate : '{{name}} दान करें',
25 | donateCopy : '{{name}} पता क्लिपबोर्ड पर कॉपी हो गया है',
26 | copiedToClipBoard : 'सामग्री क्लिपबोर्ड पर कॉपी हो गई है'
27 | },
28 | searchCompanion: {
29 | title : 'खोज सहयोगी',
30 | search : 'खोजें',
31 | searchHint : 'इस विषय पर विस्तार करें',
32 | clear : 'बातचीत इतिहास साफ करें'
33 | },
34 | conversations: {
35 | me : 'मुझसे',
36 | welcome : 'हाय वहाँ, मैं आपकी केसे सहायता कर सकता हूँ?',
37 | title : 'बातचीतें',
38 | newConversation : 'नई बातचीत',
39 | sttHint : 'CTRL+स्पेसबार के साथ सुनना टॉगल करें',
40 | sttOn : 'वाणी ज्ञान प्राप्ति: सक्रिय',
41 | sttOff : 'वाणी ज्ञान प्राप्ति: निष्क्रिय',
42 | inputHint : 'आप CTRL+Enter के साथ मान्य कर सकते हैं, या CTRL+स्पेसबार के साथ वाणी ज्ञान प्राप्ति का उपयोग कर सकते हैं',
43 | wordCount : 'शब्द गणना'
44 | },
45 | profiles: {
46 | title : 'प्रोफ़ाइल्स',
47 | newProfile : 'नई प्रोफाइल',
48 | selectProfile : 'एक प्रोफाइल चुनें',
49 | selected : 'सक्रिय प्रोफ़ाइल: {{name}}',
50 | name : 'शीर्षक',
51 | active : 'सक्रिय',
52 | tags : 'टैग',
53 | description : 'विवरण',
54 | prompt : 'सिस्टम प्रॉम्प्ट'
55 | },
56 | settings: {
57 | general : 'सामान्य',
58 | global : 'वैश्विक सेटिंग्स'
59 | },
60 | searchSettings: {
61 | interfaceLanguage : 'इंटरफ़ेस भाषा',
62 | showSidebarOnAllSites : 'सभी साइटों पर साइडबार दिखाएं',
63 | triggerModeTitle : 'ट्रिगर मोड',
64 | triggerModeManual : 'मैनुअल',
65 | triggerModeManualHint : 'केवल मैनुअल क्लिक पर खोज ट्रिगर करें',
66 | triggerModeQuestion : 'सवाल',
67 | triggerModeQuestionHint : 'केवल सवाल समाप्त होने पर खोज ट्रिगर करें :?',
68 | triggerModeAuto : 'ऑटोमैटिक',
69 | triggerModeAutoHint : 'सिस्टमैटिकली सवाल इंजन क्वेरी करें',
70 | apiKeyTitle : 'एपीआई कुंजी',
71 | apiKeyHint : 'आपकी ओपनएआई एपीआई कुंजी',
72 | model : 'मॉडल'
73 | }
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/src/i18n/ja/index.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | translation: {
3 | global: {
4 | yes : 'はい',
5 | no : 'いいえ',
6 | save : '保存',
7 | close : '閉じる',
8 | edit : '編集',
9 | copy : '複製',
10 | play : '再生',
11 | record : '録音',
12 | import : 'インポート',
13 | export : 'エクスポート',
14 | upload : 'アップロード',
15 | cancel : 'キャンセル',
16 | delete : '削除',
17 | retry : '再試行',
18 | missing : '見つかりません',
19 | settings : '設定',
20 | search : '質問してください',
21 | searchHint : 'またはトピックを拡大してください',
22 | authError : 'エラー:{{statusCode}}\n\n申し訳ありませんが、認証で問題が発生しました。APIキーとモデルを確認してください',
23 | comingSoon : '近日公開',
24 | donate : '{{name}}を寄付する',
25 | donateCopy : '{{name}}のアドレスがクリップボードにコピーされました',
26 | copiedToClipBoard : 'コンテンツがクリップボードにコピーされました',
27 | },
28 | searchCompanion: {
29 | title : '検索コンパニオン',
30 | search : '検索',
31 | searchHint : 'このトピックを拡大してください',
32 | clear : '会話履歴をクリア',
33 | },
34 | conversations: {
35 | me : '私',
36 | welcome : 'こんにちは、いつでもどうぞ',
37 | title : '会話',
38 | newConversation : '新しい会話',
39 | sttHint : 'CTRL+SpaceBarで音声入力',
40 | sttOn : '音声認識:オン',
41 | sttOff : '音声認識:オフ',
42 | inputHint : 'CTRL+Enterで検索またはCTRL+SpaceBarで音声認識',
43 | wordCount : '単語数',
44 | },
45 | profiles: {
46 | title : 'プロフィール',
47 | newProfile : '新しいプロフィール',
48 | selectProfile : 'プロフィールを選択',
49 | selected : 'アクティブプロファイル:{{name}}',
50 | name : 'タイトル',
51 | active : 'アクティブ',
52 | tags : 'タグ',
53 | description : '説明',
54 | prompt : 'システムプロンプト',
55 |
56 | },
57 | settings: {
58 | general : '一般',
59 | global : 'グローバル設定'
60 | },
61 | searchSettings: {
62 | interfaceLanguage : 'インターフェース言語',
63 | showSidebarOnAllSites : 'すべてのサイトでサイドバーを表示',
64 | triggerModeTitle : 'トリガーモード',
65 | triggerModeManual : 'マニュアル',
66 | triggerModeManualHint : 'マニュアルクリックでのみ検索をトリガーする',
67 | triggerModeQuestion : '質問',
68 | triggerModeQuestionHint : '質問符 (?) で終わる場合にのみ検索をトリガーする',
69 | triggerModeAuto : '自動',
70 | triggerModeAutoHint : '定期的に検索エンジンにクエリを送信する',
71 | apiKeyTitle : 'APIキー',
72 | apiKeyHint : 'あなたのOpenAI APIキー',
73 | model : 'モデル',
74 | }
75 | }
76 | };
77 |
--------------------------------------------------------------------------------
/src/i18n/pt/index.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | translation: {
3 | global: {
4 | yes : 'Sim',
5 | no : 'Não',
6 | save : 'Salvar',
7 | close : 'Fechar',
8 | edit : 'Editar',
9 | copy : 'Copiar',
10 | play : 'Reproduzir',
11 | record : 'Gravar',
12 | import : 'Importar',
13 | export : 'Exportar',
14 | upload : 'Enviar',
15 | cancel : 'Cancelar',
16 | delete : 'Excluir',
17 | retry : 'Tentar Novamente',
18 | missing : 'Perdido',
19 | settings : 'Configurações',
20 | search : 'Pergunte uma questão',
21 | searchHint : 'Ou expanda um tópico',
22 | authError : 'Erro: {{statusCode}}\n\nDesculpe, ocorreu um erro na autenticação, verifique sua chave API e seu modelo',
23 | comingSoon : 'em breve',
24 | donate : 'Doar {{name}}',
25 | donateCopy : 'Endereço {{name}} copiado para área de transferência',
26 | copiedToClipBoard : 'Conteúdo copiado para área de transferência',
27 | },
28 | searchCompanion: {
29 | title : 'Companheiro de Busca',
30 | search : 'Procurar',
31 | searchHint : 'Expandir neste tópico',
32 | clear : 'Limpar histórico de conversas',
33 | },
34 | conversations: {
35 | me : 'eu',
36 | welcome : 'Olá, como posso ajudar?',
37 | title : 'Conversas',
38 | newConversation : 'Nova conversa',
39 | sttHint : 'Alternar escuta com CTRL+Barra de Espaço',
40 | sttOn : 'Reconhecimento de fala: Ativado',
41 | sttOff : 'Reconhecimento de fala: Desativado',
42 | inputHint : 'Você pode validar com CTRL + Enter ou use a fala com CTRL + Barra de Espaço',
43 | wordCount : 'Contagem de Palavras',
44 | },
45 | profiles: {
46 | title : 'Perfis',
47 | newProfile : 'Novo perfil',
48 | selectProfile : 'Selecione um perfil',
49 | selected : 'Perfil Ativo: {{name}}',
50 | name : 'Título',
51 | active : 'Ativo',
52 | tags : 'Tags',
53 | description : 'Descrição',
54 | prompt : 'Solicitação do sistema',
55 |
56 | },
57 | settings: {
58 | general : 'Geral',
59 | global : 'Configurações Globais'
60 | },
61 | searchSettings: {
62 | interfaceLanguage : 'Idioma da interface',
63 | showSidebarOnAllSites : 'Mostrar barra lateral em todos os sites',
64 | triggerModeTitle : 'Modo de Ativação',
65 | triggerModeManual : 'Manual',
66 | triggerModeManualHint : 'Ativar buscas somente ao clicar manualmente',
67 | triggerModeQuestion : 'Pergunta',
68 | triggerModeQuestionHint : 'Ativar buscas somente em perguntas com ponto de interrogação no final',
69 | triggerModeAuto : 'Automático',
70 | triggerModeAutoHint : 'Realizar consulta no motor de busca de maneira sistemática',
71 | apiKeyTitle : 'Chave da API',
72 | apiKeyHint : 'Sua chave de API do OpenAI',
73 | model : 'Modelo',
74 | }
75 | }
76 | };
77 |
--------------------------------------------------------------------------------
/src/i18n/ru/index.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | translation: {
3 | global: {
4 | yes : 'Да',
5 | no : 'Нет',
6 | save : 'Сохранить',
7 | close : 'Закрыть',
8 | edit : 'Править',
9 | play : 'Слушать',
10 | record : 'Запись',
11 | import : 'Импорт',
12 | export : 'Экспорт',
13 | upload : 'Закачка',
14 | cancel : 'Отмена',
15 | delete : 'Удалить',
16 | retry : 'Повторить',
17 | missing : 'отсутствует',
18 | settings : 'Настройки',
19 | search : 'Введи вопрос',
20 | searchHint : 'Или раскрой тему',
21 | authError : 'Ошибка: {{statusCode}}\n\nЧто-то не так с аутентификацией, проверьте ваш API ключ и модел.',
22 | comingSoon : 'скоро',
23 | donate : 'Пожертвовать {{name}}',
24 | donateCopy : '{{name}} адрес скопирован в буфер обмена',
25 | copiedToClipBoard : 'Содержимое скопировано в буфер обмена',
26 | },
27 | searchCompanion: {
28 | title : 'Поисковый компаньон',
29 | login : 'Логин',
30 | search : 'Поиск',
31 | searchHint : 'Развернуть тему',
32 | clear : 'Очистить историю разговоря',
33 | copy : 'Скопировать результат',
34 | },
35 | conversations: {
36 | me : 'Я',
37 | welcome : 'Привет, чем могу вам помочь?',
38 | title : 'Диалоги',
39 | newConversation : 'Новый диалог',
40 | sttHint : 'Переключить диктовку по CTRL+SpaceBar',
41 | sttOn : 'Диктовка: активна',
42 | sttOff : 'Диктовка: неактивна',
43 | inputHint : 'Можно отправить по CTRL+Enter, или воспользоваться диктовкой по CTRL+SpaceBar',
44 | wordCount : 'Всего слов',
45 | },
46 | profiles: {
47 | title : 'Профили',
48 | newProfile : 'Новый профиль',
49 | selectProfile : 'Выбрать профиль',
50 | selected : 'Текущий профиль: {{name}}',
51 | name : 'Подпись',
52 | active : 'Активен',
53 | tags : 'Тэги',
54 | description : 'Описание',
55 | prompt : 'Системная затравка',
56 |
57 | },
58 | settings: {
59 | general : 'Общие',
60 | global : 'Глобальные настройки'
61 | },
62 | searchSettings: {
63 | interfaceLanguage : 'Язык интерфейса',
64 | showSidebarOnAllSites : 'Показывать боковую панель на сайтах',
65 | triggerModeTitle : 'Режим срабатывания',
66 | triggerModeManual : 'По требованию',
67 | triggerModeManualHint : 'Только, когда пользователь самостоятельно отправляет запрос',
68 | triggerModeQuestion : 'По знаку вопроса',
69 | triggerModeQuestionHint : 'Отправляет, если предложение оканчивается на "?"',
70 | triggerModeAuto : 'Автоматически',
71 | triggerModeAutoHint : 'Регулярно опрашивать поиск',
72 | apiKeyTitle : 'API Ключ',
73 | apiKeyHint : 'Ваш ключ OpenAI API',
74 | model : 'Модель',
75 | }
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/src/i18n/tr/index.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | translation: {
3 | global: {
4 | yes : 'Evet',
5 | no : 'Hayır',
6 | save : 'Kaydet',
7 | close : 'Kapat',
8 | edit : 'Düzenle',
9 | copy : 'Kopyala',
10 | play : 'Oynat',
11 | record : 'Kaydet',
12 | import : 'İçe Aktar',
13 | export : 'Dışa Aktar',
14 | upload : 'Yükle',
15 | cancel : 'İptal',
16 | delete : 'Sil',
17 | retry : 'Yeniden Dene',
18 | missing : 'eksik',
19 | settings : 'Ayarlar',
20 | search : 'Soru Sormak İçin Tıklayın',
21 | searchHint : 'Veya bir konuda genişleyin',
22 | authError : 'Hata: {{statusCode}}\n\nÜzgünüz, kimlik doğrulamada bir sorun oluştu, lütfen API anahtarınızı ve modelinizi kontrol edin',
23 | comingSoon : 'Çok Yakında',
24 | donate : '{{name}} Bağış Yap',
25 | donateCopy : '{{name}} bağış adresi panoya kopyalandı',
26 | copiedToClipBoard : 'İçerik panoya kopyalandı',
27 | },
28 | searchCompanion: {
29 | title : 'Arama Asistanı',
30 | search : 'Ara',
31 | searchHint : 'Bu konuda genişleyin',
32 | clear : 'Konuşma geçmişini temizle',
33 | },
34 | conversations: {
35 | me : 'ben',
36 | welcome : 'Merhaba, nasıl yardımcı olabilirim?',
37 | title : 'Konuşmalar',
38 | newConversation : 'Yeni Konuşma',
39 | sttHint : 'CTRL+Boşluk ile dinlemeyi açın/kapatın',
40 | sttOn : 'Konuşma Tanıma: Aktif',
41 | sttOff : 'Konuşma Tanıma: Pasif',
42 | inputHint : 'CTRL+Enter ile doğrulayabilir veya CTRL+Boşluk kullanarak konuşma tanımayı kullanabilirsiniz',
43 | wordCount : 'Kelime Sayısı',
44 | },
45 | profiles: {
46 | title : 'Profil Ayarları',
47 | newProfile : 'Yeni Profil',
48 | selectProfile : 'Bir Profil Seçin',
49 | selected : 'Aktif Profil: {{name}}',
50 | name : 'Başlık',
51 | active : 'Aktif',
52 | tags : 'Etiketler',
53 | description : 'Açıklama',
54 | prompt : 'Sistem İsteği',
55 |
56 | },
57 | settings: {
58 | general : 'Genel',
59 | global : 'Global Ayarlar'
60 | },
61 | searchSettings: {
62 | interfaceLanguage : 'Arayüz Dili',
63 | showSidebarOnAllSites : 'Tüm Sitelerde Kenar Çubuğunu Göster',
64 | triggerModeTitle : 'Tetikleme Modu',
65 | triggerModeManual : 'Manuel',
66 | triggerModeManualHint : 'Sadece manuel tıklamada aramayı tetikler',
67 | triggerModeQuestion : 'Soru',
68 | triggerModeQuestionHint : 'Sadece "?" ile bitenlerde aramayı tetikler',
69 | triggerModeAuto : 'Otomatik',
70 | triggerModeAutoHint : 'Sistemli olarak arama motoruna sorgu gönderir',
71 | apiKeyTitle : 'API Anahtarı',
72 | apiKeyHint : 'OpenAI API anahtarınız',
73 | model : 'Model',
74 | }
75 | }
76 | };
77 |
--------------------------------------------------------------------------------
/src/i18n/types.ts:
--------------------------------------------------------------------------------
1 | export const translation = {
2 | global: {
3 | yes : 'global.yes',
4 | no : 'global.no',
5 | save : 'global.save',
6 | close : 'global.close',
7 | edit : 'global.edit',
8 | copy : 'global.copy',
9 | play : 'global.play',
10 | record : 'global.record',
11 | import : 'global.import',
12 | export : 'global.export',
13 | upload : 'global.upload',
14 | cancel : 'global.cancel',
15 | delete : 'global.delete',
16 | retry : 'global.retry',
17 | missing : 'global.missing',
18 | settings : 'global.settings',
19 | search : 'global.search',
20 | searchHint : 'global.searchHint',
21 | authError : 'global.authError',
22 | comingSoon : 'global.comingSoon',
23 | donate : 'global.donate',
24 | donateCopy : 'global.donateCopy',
25 | copiedToClipBoard : 'global.copiedToClipBoard',
26 | },
27 | searchCompanion: {
28 | title : 'searchCompanion.title',
29 | search : 'searchCompanion.search',
30 | searchHint : 'searchCompanion.searchHint',
31 | clear : 'searchCompanion.clear',
32 | },
33 | conversations: {
34 | me : 'conversations.me',
35 | welcome : 'conversations.welcome',
36 | title : 'conversations.title',
37 | newConversation : 'conversations.newConversation',
38 | sttHint : 'conversations.sttHint',
39 | sttOn : 'conversations.sttOn',
40 | sttOff : 'conversations.sttOff',
41 | inputHint : 'conversations.inputHint',
42 | wordCount : 'conversations.wordCount',
43 | },
44 | profiles: {
45 | title : 'profiles.title',
46 | newProfile : 'profiles.newProfile',
47 | selectProfile : 'profiles.selectProfile',
48 | selected : 'profiles.selected',
49 | name : 'profiles.name',
50 | active : 'profiles.active',
51 | tags : 'profiles.tags',
52 | description : 'profiles.description',
53 | prompt : 'profiles.prompt',
54 | },
55 | settings: {
56 | general : 'settings.general',
57 | global : 'settings.global'
58 | },
59 | searchSettings: {
60 | interfaceLanguage : 'searchSettings.interfaceLanguage',
61 | showSidebarOnAllSites : 'searchSettings.showSidebarOnAllSites',
62 | triggerModeTitle : 'searchSettings.triggerModeTitle',
63 | triggerModeManual : 'searchSettings.triggerModeManual',
64 | triggerModeManualHint : 'searchSettings.triggerModeManualHint',
65 | triggerModeQuestion : 'searchSettings.triggerModeQuestion',
66 | triggerModeQuestionHint : 'searchSettings.triggerModeQuestionHint',
67 | triggerModeAuto : 'searchSettings.triggerModeAuto',
68 | triggerModeAutoHint : 'searchSettings.triggerModeAutoHint',
69 | apiKeyTitle : 'searchSettings.apiKeyTitle',
70 | apiKeyHint : 'searchSettings.apiKeyHint',
71 | model : 'searchSettings.model',
72 | }
73 | };
74 |
--------------------------------------------------------------------------------
/src/i18n/zh/index.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | translation: {
3 | global: {
4 | yes : '是',
5 | no : '不',
6 | save : '保存',
7 | close : '关闭',
8 | edit : '编辑',
9 | copy : '复制',
10 | play : '播放',
11 | record : '录制',
12 | import : '导入',
13 | export : '导出',
14 | upload : '上传',
15 | cancel : '取消',
16 | delete : '删除',
17 | retry : '重试',
18 | missing : '丢失',
19 | settings : '设置',
20 | search : '提出问题',
21 | searchHint : '或扩展某个主题',
22 | authError : '错误:{{statusCode}}\n\n对不起,身份验证出现问题,请检查您的 API 密钥和模型',
23 | comingSoon : '即将上线',
24 | donate : '捐赠 {{name}}',
25 | donateCopy : '{{name}} 地址已复制到剪贴板',
26 | copiedToClipBoard : '内容已复制到剪贴板',
27 | },
28 | searchCompanion: {
29 | title : '搜索陪伴',
30 | search : '搜索',
31 | searchHint : '扩展此主题',
32 | clear : '清除对话历史',
33 | },
34 | conversations: {
35 | me : '我',
36 | welcome : '你好,有什么可以帮助您的',
37 | title : '对话',
38 | newConversation : '新对话',
39 | sttHint : '使用 CTRL+SpaceBar 切换收听',
40 | sttOn : '语音识别:已启用',
41 | sttOff : '语音识别:已禁用',
42 | inputHint : '您可以使用 CTRL+Enter 进行验证,或使用 CTRL+SpaceBar 进行语音识别',
43 | wordCount : '单词统计',
44 | },
45 | profiles: {
46 | title : '个人资料',
47 | newProfile : '新资料',
48 | selectProfile : '选择资料',
49 | selected : '活动资料:{{name}}',
50 | name : '标题',
51 | active : '活动中',
52 | tags : '标签',
53 | description : '描述',
54 | prompt : '系统提示',
55 |
56 | },
57 | settings: {
58 | general : '通用',
59 | global : '全局设置'
60 | },
61 | searchSettings: {
62 | interfaceLanguage : '界面语言',
63 | showSidebarOnAllSites : '在所有网站上显示侧边栏',
64 | triggerModeTitle : '触发模式',
65 | triggerModeManual : '手动',
66 | triggerModeManualHint : '仅在手动单击时触发搜索',
67 | triggerModeQuestion : '问题',
68 | triggerModeQuestionHint : '仅在以问号结尾时触发搜索',
69 | triggerModeAuto : '自动',
70 | triggerModeAutoHint : '系统性地查询搜索引擎',
71 | apiKeyTitle : 'API 密钥',
72 | apiKeyHint : '您的 OpenAI API 密钥',
73 | model : '模型',
74 | }
75 | }
76 | };
77 |
--------------------------------------------------------------------------------
/src/js/api/openai.api.ts:
--------------------------------------------------------------------------------
1 | import { fetchSSE } from '../libs/fetch-sse';
2 | import { IPTResult } from '../libs/types/IGPTResponse';
3 | import { IPTChatMessages } from '../libs/types/IPTChatRequest';
4 | import { db } from '../controllers/dbController';
5 | class OpenAi {
6 | constructor() {
7 | // do something
8 | }
9 |
10 | // TODO: session status can be 200, yet content is empty: handle this case
11 | // https://chat.openai.com/api/auth/session
12 | async getSession() {
13 | //console.log('openai.api.getSession()');
14 |
15 | // try to get settings from database
16 | return await db.getUserSettings().then((data) => {
17 | //console.log('getSession()::getUserSettings()', data);
18 |
19 | if (data.apiKey) {
20 | return {
21 | error : false,
22 | status : 200,
23 | content: {
24 | accessToken: data.apiKey
25 | }
26 | }
27 | }
28 | else {
29 | return {
30 | error : true,
31 | status : 401,
32 | content: {
33 | accessToken: '',
34 | message : 'No API key found'
35 | }
36 | }
37 | }
38 | });
39 | }
40 |
41 | async getAnswerStreamed(
42 | messages : IPTChatMessages[],
43 | accessToken: string,
44 | abortSignal: AbortSignal,
45 | callBack : (response: IPTResult) => void
46 | ) {
47 | console.log('openai.api.getAnswerStreamed()');
48 |
49 | // https://platform.openai.com/docs/api-reference/completions/create
50 | // TODO: add dynamic question prefix/suffix
51 |
52 | const userSettings = await db.getUserSettings();
53 | //console.log('getAnswerStreamed::userSettings', userSettings);
54 |
55 | const api = {
56 | 'model' : userSettings.model?.value || 'gpt-3.5-turbo',
57 | 'messages' : messages,
58 | 'temperature' : 1,
59 | 'stream' : true
60 | }
61 |
62 | //console.log('getAnswerStreamed', api);
63 |
64 | const requestOptions = {
65 | signal : abortSignal,
66 | method : 'POST',
67 | headers: {
68 | 'Content-Type' : 'application/json',
69 | 'Authorization': `Bearer ${accessToken}`
70 | },
71 | body: JSON.stringify(api),
72 | onMessage(message: IPTResult) {
73 | //console.log('onMessage', message);
74 | callBack(message)
75 | },
76 | };
77 |
78 | try {
79 | await fetchSSE('https://api.openai.com/v1/chat/completions', requestOptions);
80 | } catch (error) {
81 | console.log('getAnswerStreamed error', error);
82 | }
83 | }
84 | }
85 |
86 | export const openAi = new OpenAi();
87 |
--------------------------------------------------------------------------------
/src/js/controllers/dbController.ts:
--------------------------------------------------------------------------------
1 | // db.js
2 | import Dexie, { IndexableType } from 'dexie';
3 | import { IUserSettings, } from 'src/js/libs/types/IUserSettings';
4 | import { IDBConversation } from 'src/js/libs/types/IDBConversation';
5 | import { IDBProfile } from 'src/js/libs/types/IDBProfile';
6 |
7 | import { triggerMode } from 'src/js/data/triggerMode';
8 | import { gptModels } from 'src/js/data/gpt-models';
9 |
10 | // INFO: database works in background.js
11 | // You cannot see the data from the serviceworker dev tools panel
12 | // However you can see the data from the content.js dev tools panel
13 | class dbController extends Dexie {
14 | //#region ------------------------ Setup ---------------------
15 | userSettings! : Dexie.Table;
16 | conversations!: Dexie.Table;
17 | profiles!: Dexie.Table;
18 |
19 | constructor() {
20 | super('powertoys.oai');
21 |
22 | // INFO: See indexable types: https://dexie.org/docs/Indexable-Type
23 | this.version(1).stores({
24 | // INFO: Normally only declare items that we want indexed
25 | userSettings : '++id, triggerMode, darkMode, apiKey, licenseKey',
26 | conversations: '++id, timestamp, title, conversation'
27 | });
28 |
29 | this.version(2).stores({
30 | // INFO: Normally only declare items that we want indexed
31 | userSettings : '++id, triggerMode, darkMode, apiKey, licenseKey',
32 | conversations: '++id, timestamp, title, conversation',
33 | profiles : '++id, *tags, selected'
34 | });
35 |
36 | // v0.2.2
37 | this.version(3).stores({
38 | // INFO: Normally only declare items that we want indexed
39 | userSettings : '++id, triggerMode, darkMode, apiKey, licenseKey',
40 | conversations: '++id, timestamp, title, conversation, profileId',
41 | profiles : '++id, *tags, selected'
42 | });
43 | }
44 | //#endregion --------------------- Setup ---------------------
45 |
46 | //#region ------------------------ DB Operations ---------------------
47 | /**
48 | * Delete Database
49 | */
50 | async deleteDatabase() {
51 | return this.delete();
52 | }
53 |
54 | /**
55 | * Delete an entry from the database
56 | * @param {String} table Name of the table
57 | * @param {Mixed} key Key or Composite key
58 | */
59 | async deleteItem(table: string, key: IndexableType) {
60 | this.table(table).delete(key);
61 | }
62 | //#endregion --------------------- DB Operations ---------------------
63 |
64 | //#region ------------------------ User Settings ---------------------
65 | async getUserSettings() : Promise {
66 | const userSettings = await this.userSettings.get({id: 0});
67 |
68 | // set default values for items that may not exist (e.g. after updates)
69 | if (userSettings) {
70 | if (!userSettings.model) {
71 | userSettings.model = gptModels[0];
72 | }
73 |
74 | return userSettings;
75 | }
76 |
77 | // no user settings found, return default values
78 | return {
79 | id : 0,
80 | darkMode : false,
81 | triggerMode: triggerMode.QUESTION,
82 | apiKey : '',
83 | licenseKey : '',
84 | showSidebar: false,
85 | model : gptModels[0]
86 | };
87 | }
88 |
89 | async saveUserSettings(settings: IUserSettings) {
90 | //console.log('db.saveUserSettings()', settings);
91 |
92 | return this.userSettings
93 | //https://stackoverflow.com/questions/57087581/dexiedb-put-method-not-working-when-adding-primary-key-as-argument
94 | .put(settings)
95 | .then(() => {
96 | return settings;
97 | })
98 | .catch(() => {
99 | return false;
100 | });
101 | }
102 | //#endregion --------------------- User Settings ---------------------
103 |
104 |
105 | //#region ------------------------ Conversations ---------------------
106 | async getConversation(conversationId: number) : Promise {
107 | return await this.conversations.get({id: conversationId}) || undefined;
108 | }
109 |
110 | async getConversations() : Promise {
111 | return await this.conversations.orderBy('timestamp').reverse().toArray() ?? [];
112 | }
113 |
114 | async saveConversation(conversation: IDBConversation) : Promise {
115 | //console.log('db.saveConversation()', conversation);
116 |
117 | // https://dexie.org/docs/Table/Table.put()
118 | // https://stackoverflow.com/questions/57087581/dexiedb-put-method-not-working-when-adding-primary-key-as-argument
119 | return this.conversations
120 | .put(conversation)
121 | .then((conversationId: number) => {
122 | //console.log('db::savedConversation', conversation, conversationId);
123 | return conversationId;
124 | })
125 | .catch((error) => {
126 | console.log('db::saveConversationError', error);
127 | return 0;
128 | });
129 | }
130 |
131 | async updateConversation(id: number, changes: {[keyPath: string]: any }) : Promise {
132 | //console.log('db.updateConversation()', key, changes);
133 | return this.conversations
134 | .update(id, changes)
135 | .catch((error) => {
136 | console.log('db::updateConversation', error);
137 | return 0;
138 | });
139 | }
140 |
141 | async deleteConversation(conversationId: number) {
142 | return await this.conversations.delete(conversationId);
143 | }
144 | //#endregion --------------------- Conversations ---------------------
145 |
146 | //#region ------------------------ Profiles ---------------------
147 | async getProfiles() : Promise {
148 | return await this.profiles.orderBy('id').toArray() ?? [];
149 | }
150 |
151 | async getProfile(profileId: number) : Promise {
152 | //console.log('db.getProfile()', profileId);
153 | if (!profileId) {
154 | return 0;
155 | }
156 |
157 | return await this.profiles.get({id: profileId}) || 0;
158 | }
159 |
160 | async saveProfile(profile: IDBProfile) : Promise {
161 | //console.log('db.saveProfile()', profile);
162 |
163 | if (profile.selected) {
164 | // make sure this is the only profile that is selected
165 | await this.profiles.where({ selected: 1 }).modify({ selected: 0 });
166 | }
167 |
168 | // https://dexie.org/docs/Table/Table.put()
169 | // https://stackoverflow.com/questions/57087581/dexiedb-put-method-not-working-when-adding-primary-key-as-argument
170 | return this.profiles
171 | .put(profile)
172 | .then((profileId: number) => {
173 | //console.log('db::savedProfile', profile, profileId);
174 | return profileId;
175 | })
176 | .catch((error) => {
177 | console.log('db::saveProfileError', error);
178 | return 0;
179 | });
180 | }
181 |
182 | async insertProfiles(profiles: IDBProfile[]) : Promise {
183 | //console.log('db.insertProfiles()', profiles);
184 | // https://dexie.org/docs/Table/Table.put()
185 | // https://stackoverflow.com/questions/57087581/dexiedb-put-method-not-working-when-adding-primary-key-as-argument
186 | return this.profiles
187 | .bulkPut(profiles)
188 | .then((profileId: number) => {
189 | //console.log('db::insertProfiles', profiles, profileId);
190 | return profileId;
191 | })
192 | .catch((error) => {
193 | console.log('db::insertProfileError', error);
194 | return 0;
195 | });
196 | }
197 |
198 | async getActiveProfile() {
199 | return await this.profiles.where({ selected: 1 }).first();
200 | }
201 |
202 | async setAsDefaultProfile(profileId: number) {
203 | // set all other profiles to false
204 | await this.profiles.where({ selected: 1 }).modify({ selected: 0 });
205 |
206 | return this.profiles
207 | .update(profileId, { selected: 1 })
208 | .catch((/*error*/) => {
209 | //console.log('db::setAsDefaultProfileError', error);
210 | return 0;
211 | });
212 | }
213 |
214 | async deleteProfile(profileId: number) {
215 | return await this.profiles.delete(profileId);
216 | }
217 | //#endregion --------------------- Profiles ---------------------
218 | }
219 |
220 | // export
221 | export const db = new dbController();
222 |
--------------------------------------------------------------------------------
/src/js/controllers/speechController.js:
--------------------------------------------------------------------------------
1 | export function speechToText(options = { language: 'en-US', callback: any }) {
2 | var started = false;
3 | var speech = function() {
4 | var recognition = new webkitSpeechRecognition();
5 |
6 | function toggleRecognition() {
7 | if (started) {
8 | started = false;
9 | recognition.stop();
10 | //console.log('recognition stop');
11 | }
12 | else {
13 | started = true;
14 | recognition.start();
15 | //console.log('recognition start');
16 | }
17 | }
18 |
19 | // make sure we don't add the handler multiple times
20 | document.removeEventListener('toggle-record', toggleRecognition);
21 | document.addEventListener('toggle-record' , toggleRecognition);
22 |
23 | recognition.continuous = true;
24 | recognition.interimResults = false;
25 | recognition.lang = options.language;
26 |
27 | recognition.onresult = function(event) {
28 | var result = event.results[event.results.length - 1][0].transcript;
29 |
30 | //console.log('recognition onresult', options.target);
31 | options.callback(result);
32 | }
33 | }
34 |
35 | speech();
36 | }
37 |
38 | export function textToSpeech(options = { sentence, language: 'en-US' }) {
39 | // config
40 | let speechSynthesis = {
41 | language: options.language,
42 | enabled : true,
43 | };
44 |
45 | speechSynthesis.msg = new window.SpeechSynthesisUtterance(options.sentence);
46 | speechSynthesis.msg.lang = speechSynthesis.language;
47 |
48 | // speak text
49 | if (speechSynthesis.enabled) {
50 | window.speechSynthesis.speak(speechSynthesis.msg);
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/js/data/default-tags.json:
--------------------------------------------------------------------------------
1 | [
2 | "design",
3 | "development",
4 | "finance",
5 | "management",
6 | "marketing",
7 | "productivity",
8 | "recruitment",
9 | "sales",
10 | "other"
11 | ]
12 |
--------------------------------------------------------------------------------
/src/js/data/gpt-models.ts:
--------------------------------------------------------------------------------
1 | export const gptModels = [
2 | {
3 | label : 'GPT 3.5 (4 096 tokens)',
4 | value : 'gpt-3.5-turbo',
5 | maxTokens: 4096,
6 | pricing: {
7 | prompt : 0.0015,
8 | completion : 0.002
9 | }
10 | },
11 | {
12 | label : 'GPT 3.5 (16 384 tokens)',
13 | value : 'gpt-3.5-turbo-16k',
14 | maxTokens: 16384,
15 | pricing: {
16 | prompt : 0.003,
17 | completion : 0.004
18 | }
19 | },
20 | {
21 | label : 'GPT 4 (8 192 tokens)',
22 | value : 'gpt-4',
23 | maxTokens: 8192,
24 | pricing: {
25 | prompt : 0.03,
26 | completion : 0.06
27 | }
28 | },
29 | {
30 | label : 'GPT 4 (32 768 tokens)',
31 | value : 'gpt-4-32k',
32 | maxTokens: 32768,
33 | pricing: {
34 | prompt : 0.06,
35 | completion : 0.12
36 | }
37 | }
38 | ]
39 |
--------------------------------------------------------------------------------
/src/js/data/triggerMode.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * How the searchbox is triggered
3 | */
4 | export const triggerMode = {
5 | MANUAL : 0,
6 | QUESTION: 1,
7 | AUTO : 2
8 | }
9 |
--------------------------------------------------------------------------------
/src/js/libs/fetch-sse.ts:
--------------------------------------------------------------------------------
1 | import { createParser } from 'eventsource-parser';
2 | import { IPTResult, IOAIStreamedResponse } from './types/IGPTResponse';
3 |
4 | // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function*
5 | async function* streamAsyncIterable(stream: ReadableStream) {
6 | const reader = stream.getReader();
7 | try {
8 | while (true) {
9 | const { done, value } = await reader.read();
10 | if (done) {
11 | return;
12 | }
13 | yield value;
14 | }
15 | }
16 | catch (error) {
17 | console.log('streamAsyncIterable::error', error);
18 | } finally {
19 | reader.releaseLock();
20 | }
21 | }
22 |
23 | export async function fetchSSE(
24 | url : string,
25 | options : RequestInit & { onMessage: (message: IPTResult) => void }
26 | ) {
27 | console.log('fetchSSE');
28 |
29 | const { onMessage, ...requestOptions } = options;
30 | const resp = await fetch(url, requestOptions);
31 |
32 | if (!resp.ok) {
33 | // do something
34 | console.log('fetchSSE::error', resp);
35 | onMessage({
36 | error : true,
37 | status : resp.status,
38 | message : resp.statusText,
39 | response: null
40 | });
41 | }
42 |
43 | const parser = createParser((event) => {
44 | //console.log('parser', event);
45 | if (event.type === 'event') {
46 | if (event.data === '[DONE]') {
47 | // all done !
48 | onMessage({
49 | error : false,
50 | status : 999,
51 | message: '[DONE]',
52 | response: null
53 | });
54 | return;
55 | }
56 |
57 | let data: IOAIStreamedResponse;
58 | try {
59 | data = JSON.parse(event.data);
60 | //console.log('streamed reponse', data);
61 | }
62 | catch (error) {
63 | console.log('Error parsing IOAIStreamedResponse json()', error);
64 | onMessage({
65 | error : true,
66 | status : resp.status,
67 | message : `${error}`,
68 | response: null
69 | });
70 |
71 | return;
72 | }
73 |
74 | onMessage({
75 | error : false,
76 | status : resp.status,
77 | message : null,
78 | response: data,
79 | });
80 | }
81 | });
82 |
83 | for await (const chunk of streamAsyncIterable(resp.body)) {
84 | const str = new TextDecoder().decode(chunk);
85 | parser.feed(str);
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/src/js/libs/helpers.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * See if a string ends with a question mark
3 | * @param question
4 | */
5 | export function endsWithQuestionMark(question: string) : boolean {
6 | return (
7 | question.endsWith('?') || // ASCII
8 | question.endsWith('?') || // Chinese/Japanese
9 | question.endsWith('؟') || // Arabic
10 | question.endsWith('⸮') // Arabic
11 | )
12 | }
13 |
14 | /**
15 | *
16 | * @param str Returns an estimate of the number of tokens used
17 | */
18 | export function getTokenCount(str: string) : number {
19 | if (str.length == 0) return 0;
20 |
21 | // get the average of words and 4 characters
22 | return Math.round((str.split(' ').length + str.length/4) / 2);
23 | }
24 |
25 | /**
26 | * Get the number of words in a string
27 | * @param s
28 | */
29 | export function getWordCount(s: string) {
30 | // Info: try different versions
31 | // https://stackoverflow.com/a/18679657/896849
32 |
33 | s = s.replace(/(^\s*)|(\s*$)/gi,''); // exclude start and end white-space
34 | s = s.replace(/[ ]{2,}/gi,' '); // 2 or more space to 1
35 | s = s.replace(/\n /,'\n'); // exclude newline with a start spacing
36 | return s.split(' ').filter(function(str: string) { return str != ''; }).length;
37 | //return s.split(' ').filter(String).length; // - this can also be used
38 | }
39 |
40 | /**
41 | * Convert 2 letter code to a fully qualified language code
42 | * @param lang
43 | */
44 | export function getFullyQualifiedLanguage(lang: string) {
45 | switch (lang) {
46 | case 'en':
47 | return 'en-US';
48 |
49 | case 'fr':
50 | return 'fr-FR';
51 |
52 | default:
53 | return 'en-US';
54 | }
55 | }
56 |
57 | /**
58 | * Generate a random GUID
59 | */
60 | export function guid() : string {
61 | return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, (c: number) =>
62 | (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
63 | );
64 | }
65 |
66 | /**
67 | *
68 | * @param file Gets the base64 representation of a file
69 | */
70 | export function getBase64(file: File) : Promise {
71 | return new Promise((resolve, reject) => {
72 | const reader = new FileReader();
73 |
74 | reader.onerror = (error) => {
75 | reject(error);
76 | }
77 |
78 | reader.onload = () => {
79 | resolve(reader.result as string);
80 | }
81 |
82 | reader.readAsDataURL(file);
83 | });
84 | }
85 |
86 | /**
87 | * Get the body text from a URL
88 | * @param url
89 | */
90 | export async function getTextFromUrl(url: string) {
91 | return await fetch(url)
92 | .then(response => response.text())
93 | .then(html => {
94 | const parser = new DOMParser();
95 | const doc = parser.parseFromString(html, 'text/html');
96 |
97 | return doc.body.innerText;
98 | })
99 | .catch(error => {
100 | return error;
101 | });
102 | }
103 |
--------------------------------------------------------------------------------
/src/js/libs/types/IDBConversation.ts:
--------------------------------------------------------------------------------
1 | import { IPTChatMessages } from './IPTChatRequest';
2 |
3 | /**
4 | * Conversations as stored in the Database
5 | */
6 | export interface IDBConversation {
7 | id? : number,
8 | timestamp : number,
9 | title : string,
10 | conversation: IPTChatMessages[],
11 | profileId : number
12 | }
13 |
--------------------------------------------------------------------------------
/src/js/libs/types/IDBProfile.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Profiles as stored in the Database
3 | */
4 | export interface IDBProfile {
5 | id? : number,
6 | title : string,
7 | description : string,
8 | tags : string[]
9 | content : string,
10 | icon? : string,
11 | isUserPrompt : number,
12 | selected : number
13 | }
14 |
--------------------------------------------------------------------------------
/src/js/libs/types/IGPTResponse.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Final message sent back to UI backend (internal)
3 | */
4 | export interface IPTResult {
5 | error : boolean,
6 | status : number,
7 | message : string | null,
8 | response : IOAIStreamedResponse | null,
9 | }
10 |
11 | /**
12 | * Streaming response format (from OpenAI)
13 | */
14 | export interface IOAIStreamedResponse {
15 | id : string,
16 | model : string,
17 | created: number,
18 | choices: {
19 | index : number,
20 | delta : IOAIResponseMessage,
21 | finish_reason : null | 'stop',
22 | }[]
23 | }
24 |
25 | export interface IOAIResponseMessage {
26 | role? : 'system' | 'assistant' | 'user',
27 | content?: string,
28 | }
29 |
--------------------------------------------------------------------------------
/src/js/libs/types/IPTChatRequest.ts:
--------------------------------------------------------------------------------
1 | export interface IPTChatRequest {
2 | model : string,
3 | messages : IPTChatMessages,
4 | temperature? : number,
5 | top_p? : number,
6 | n? : number,
7 | stream? : boolean,
8 | stop? : string | string[],
9 | max_tokens? : number,
10 | presence_penalty? : number,
11 | frequency_penalty? : number,
12 | logit_bias? : object,
13 | user? : string
14 | }
15 |
16 | export interface IPTChatMessages {
17 | role : 'system' | 'assistant' | 'user',
18 | content: string
19 | }
20 |
--------------------------------------------------------------------------------
/src/js/libs/types/IUserSettings.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Settings stored in IndexedDB
3 | */
4 | export interface IUserSettings {
5 | id : number,
6 | triggerMode : number,
7 | darkMode : boolean,
8 | apiKey? : string,
9 | licenseKey? : string,
10 | showSidebar?: boolean,
11 | model? : IGPTModel
12 | }
13 |
14 | export interface IGPTModel {
15 | label : string,
16 | value : string,
17 | maxTokens: number,
18 | pricing: {
19 | prompt : number,
20 | completion : number
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/layouts/CompanionLayout.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
12 |
--------------------------------------------------------------------------------
/src/layouts/MainLayout.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
12 |
13 |
14 |
15 | PowerToys for OpenAI ™
16 |
17 |
18 |
19 |
27 |
28 |
36 |
37 |
45 |
46 | -
47 |
48 |
56 |
57 |
58 |
59 |
60 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
81 |
82 |
91 |
92 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
159 |
--------------------------------------------------------------------------------
/src/pages/ErrorNotFound.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 404
6 |
7 |
8 |
9 | Oops. Nothing here...
10 |
11 |
12 |
21 |
22 |
23 |
24 |
25 |
28 |
--------------------------------------------------------------------------------
/src/quasar.d.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 |
3 | // Forces TS to apply `@quasar/app-vite` augmentations of `quasar` package
4 | // Removing this would break `quasar/wrappers` imports as those typings are declared
5 | // into `@quasar/app-vite`
6 | // As a side effect, since `@quasar/app-vite` reference `quasar` to augment it,
7 | // this declaration also apply `quasar` own
8 | // augmentations (eg. adds `$q` into Vue component context)
9 | ///
10 |
--------------------------------------------------------------------------------
/src/router/index.ts:
--------------------------------------------------------------------------------
1 | import { route } from 'quasar/wrappers';
2 | import {
3 | createMemoryHistory,
4 | createRouter,
5 | createWebHashHistory,
6 | createWebHistory,
7 | } from 'vue-router';
8 |
9 | import routes from './routes';
10 |
11 | /*
12 | * If not building with SSR mode, you can
13 | * directly export the Router instantiation;
14 | *
15 | * The function below can be async too; either use
16 | * async/await or return a Promise which resolves
17 | * with the Router instance.
18 | */
19 |
20 | export default route(function (/* { store, ssrContext } */) {
21 | const createHistory = process.env.SERVER
22 | ? createMemoryHistory
23 | : (process.env.VUE_ROUTER_MODE === 'history' ? createWebHistory : createWebHashHistory);
24 |
25 | const Router = createRouter({
26 | scrollBehavior: () => ({ left: 0, top: 0 }),
27 | routes,
28 |
29 | // Leave this as is and make changes in quasar.conf.js instead!
30 | // quasar.conf.js -> build -> vueRouterMode
31 | // quasar.conf.js -> build -> publicPath
32 | history: createHistory(process.env.VUE_ROUTER_BASE),
33 | });
34 |
35 | return Router;
36 | });
37 |
--------------------------------------------------------------------------------
/src/router/routes.ts:
--------------------------------------------------------------------------------
1 | import { RouteRecordRaw } from 'vue-router';
2 |
3 | const routes: RouteRecordRaw[] = [
4 | // Routes for BEX Application
5 | {
6 | path : '/index',
7 | component: () => import('layouts/MainLayout.vue'),
8 | children : [
9 | {
10 | path : '',
11 | component: () => import('src/pages/ConversationsPage.vue'),
12 | props : () => {
13 | return {
14 | settings : null
15 | }
16 | }
17 | },
18 | {
19 | path : '/index/conversations/:conversationId(\\d+)',
20 | component: () => import('src/pages/ConversationsPage.vue'),
21 | props : (route) => {
22 | return {
23 | settings : null,
24 | conversationId : Number.parseInt(route.params.conversationId as string, 10) || 0
25 | }
26 | }
27 | },
28 | {
29 | path : '/index/profiles/:id(\\d+)',
30 | component: () => import('src/pages/ProfilesPage.vue'),
31 | props : (route) => {
32 | return {
33 | id : Number.parseInt(route.params.id as string, 10) || 0
34 | }
35 | }
36 | },
37 | {
38 | // TODO: move this to it's own page
39 | path : '/index/settings/:type?',
40 | component: () => import('src/pages/ConversationsPage.vue'),
41 | props : (route) => {
42 | return {
43 | settings : route.params.type || null
44 | }
45 | }
46 | },
47 | ],
48 | },
49 | // Routes for BEX
50 | {
51 | path : '/search-companion',
52 | component: () => import('src/layouts/CompanionLayout.vue'),
53 | children : [
54 | {
55 | path : '',
56 | component: () => import('src/pages/CompanionPage.vue')
57 | },
58 | ]
59 | },
60 |
61 | // Always leave this as last one,
62 | // but you can also remove it
63 | {
64 | path : '/:catchAll(.*)*',
65 | component : () => import('pages/ErrorNotFound.vue'),
66 | beforeEnter: () => {
67 | // reject the navigation
68 | return true;
69 | },
70 | },
71 | ];
72 |
73 | export default routes;
74 |
--------------------------------------------------------------------------------
/src/shims-vue.d.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 |
3 | ///
4 |
5 | // Mocks all files ending in `.vue` showing them as plain Vue instances
6 | declare module '*.vue' {
7 | import type { DefineComponent } from 'vue';
8 | const component: DefineComponent<{}, {}, any>;
9 | export default component;
10 | }
11 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@quasar/app-vite/tsconfig-preset",
3 | "compilerOptions": {
4 | "baseUrl": "."
5 | }
6 | }
--------------------------------------------------------------------------------