@kudoai/chatgpt.js 3.7.1 → 3.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +132 -107
- package/chatgpt.js +40 -31
- package/dist/chatgpt.min.js +6 -6
- package/docs/README.md +131 -107
- package/docs/USERGUIDE.md +5 -5
- package/package.json +14 -13
- package/starters/chrome/extension/components/icons.js +8 -2
- package/starters/chrome/extension/components/modals.js +26 -33
- package/starters/chrome/extension/content.js +9 -10
- package/starters/chrome/extension/lib/chatgpt.js +40 -31
- package/starters/chrome/extension/lib/dom.js +4 -3
- package/starters/chrome/extension/lib/settings.js +31 -5
- package/starters/chrome/extension/manifest.json +3 -11
- package/starters/chrome/extension/popup/controller.js +96 -54
- package/starters/chrome/extension/popup/index.html +9 -3
- package/starters/chrome/extension/popup/style.css +17 -9
- package/starters/chrome/extension/service-worker.js +4 -4
- package/starters/greasemonkey/chatgpt.js-greasemonkey-starter.user.js +2 -2
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
|
|
7
7
|
// Init ENV context
|
|
8
8
|
const env = {
|
|
9
|
-
site:
|
|
10
|
-
|
|
9
|
+
site: new URL((await chrome.tabs.query({ active: true, currentWindow: true }))[0].url)
|
|
10
|
+
.hostname.split('.').slice(-2, -1)[0] // extract 2nd-level domain
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
// Import DATA
|
|
@@ -16,6 +16,35 @@
|
|
|
16
16
|
|
|
17
17
|
// Define FUNCTIONS
|
|
18
18
|
|
|
19
|
+
function createMenuEntry(entryData) {
|
|
20
|
+
const entry = {
|
|
21
|
+
div: dom.create.elem('div', {
|
|
22
|
+
id: entryData.key, class: 'menu-entry highlight-on-hover', title: entryData.helptip || '' }),
|
|
23
|
+
leftElem: dom.create.elem('div', { class: `menu-icon ${ entryData.type || '' }` }),
|
|
24
|
+
label: dom.create.elem('span')
|
|
25
|
+
}
|
|
26
|
+
entry.label.textContent = entryData.label
|
|
27
|
+
if (entryData.type == 'toggle') { // add track to left, init knob pos
|
|
28
|
+
entry.leftElem.append(dom.create.elem('span', { class: 'track' }))
|
|
29
|
+
entry.leftElem.classList.toggle('on', settings.typeIsEnabled(entryData.key))
|
|
30
|
+
} else { // add symbol to left, append status to right
|
|
31
|
+
entry.leftElem.innerText = entryData.symbol || '⚙️'
|
|
32
|
+
if (entryData.status) entry.label.textContent += ` — ${entryData.status}`
|
|
33
|
+
}
|
|
34
|
+
if (entryData.type == 'category') entry.div.append(icons.create('caretDown', { size: 11, class: 'menu-caret' }))
|
|
35
|
+
entry.div.onclick = () => {
|
|
36
|
+
if (entryData.type == 'category') toggleCategorySettingsVisiblity(entryData.key)
|
|
37
|
+
else if (entryData.type == 'toggle') {
|
|
38
|
+
entry.leftElem.classList.toggle('on')
|
|
39
|
+
settings.save(entryData.key, !config[entryData.key]) ; sync.configToUI({ updatedKey: entryData.key })
|
|
40
|
+
notify(`${entryData.label} ${chrome.i18n.getMessage(`state_${
|
|
41
|
+
settings.typeIsEnabled(entryData.key) ? 'on' : 'off' }`).toUpperCase()}`)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
entry.div.append(entry.leftElem, entry.label)
|
|
45
|
+
return entry.div
|
|
46
|
+
}
|
|
47
|
+
|
|
19
48
|
function notify(msg, pos = 'bottom-right') { sendMsgToActiveTab('notify', { msg, pos }) }
|
|
20
49
|
|
|
21
50
|
async function sendMsgToActiveTab(action, options) {
|
|
@@ -23,8 +52,6 @@
|
|
|
23
52
|
return await chrome.tabs.sendMessage(activeTab.id, { action: action, options: { ...options }})
|
|
24
53
|
}
|
|
25
54
|
|
|
26
|
-
function settingIsEnabled(key) { return config[key] ^ /disabled|hidden/i.test(key) }
|
|
27
|
-
|
|
28
55
|
const sync = {
|
|
29
56
|
fade() {
|
|
30
57
|
|
|
@@ -45,6 +72,32 @@
|
|
|
45
72
|
configToUI(options) { return sendMsgToActiveTab('syncConfigToUI', options) }
|
|
46
73
|
}
|
|
47
74
|
|
|
75
|
+
function toggleCategorySettingsVisiblity(category, { transitions = true, action } = {}) {
|
|
76
|
+
const transitionDuration = 350, // ms
|
|
77
|
+
categoryDiv = document.getElementById(category),
|
|
78
|
+
caret = categoryDiv.querySelector('.menu-caret'),
|
|
79
|
+
catChildrenDiv = categoryDiv.nextSibling,
|
|
80
|
+
catChild = catChildrenDiv.querySelectorAll('.menu-entry')
|
|
81
|
+
if (action != 'hide' && dom.get.computedHeight(catChildrenDiv) == 0) { // show category settings
|
|
82
|
+
Object.assign(catChildrenDiv.style, { height: `${dom.get.computedHeight(catChild)}px`,
|
|
83
|
+
transition: transitions ? 'height 0.25s' : '' })
|
|
84
|
+
Object.assign(caret.style, { // point it down
|
|
85
|
+
transform: 'rotate(0deg)', transition: transitions ? 'transform 0.15s ease-out' : '' })
|
|
86
|
+
catChild.forEach(row => { // reset styles to support continuous transition on rapid show/hide
|
|
87
|
+
row.style.transition = 'none' ; row.style.opacity = 0 })
|
|
88
|
+
catChildrenDiv.offsetHeight // force reflow to insta-apply reset
|
|
89
|
+
catChild.forEach((row, idx) => { // fade-in staggered
|
|
90
|
+
if (transitions) row.style.transition = `opacity ${ transitionDuration /1000 }s ease-in-out`
|
|
91
|
+
setTimeout(() => row.style.opacity = 1, transitions ? idx * transitionDuration /10 : 0)
|
|
92
|
+
})
|
|
93
|
+
document.querySelectorAll(`.menu-entry:has(.menu-caret):not(#${category})`).forEach(otherCategoryDiv =>
|
|
94
|
+
toggleCategorySettingsVisiblity(otherCategoryDiv.id, { action: 'hide' }))
|
|
95
|
+
} else { // hide category settings
|
|
96
|
+
Object.assign(catChildrenDiv.style, { height: 0, transition: '' })
|
|
97
|
+
Object.assign(caret.style, { transform: 'rotate(-90deg)', transition: '' }) // point it right
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
48
101
|
// Run MAIN routine
|
|
49
102
|
|
|
50
103
|
// Init MASTER TOGGLE
|
|
@@ -63,66 +116,55 @@
|
|
|
63
116
|
}
|
|
64
117
|
|
|
65
118
|
// Create CHILD menu entries on chatgpt.com
|
|
119
|
+
const footer = document.querySelector('footer')
|
|
66
120
|
if (env.site == 'chatgpt') {
|
|
67
|
-
const childEntriesDiv = dom.create.elem('div') ; document.body.append(childEntriesDiv)
|
|
68
121
|
await settings.load(Object.keys(settings.controls))
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
if (
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
entry.div.onclick = () => {
|
|
90
|
-
if (controlType == 'toggle') {
|
|
91
|
-
entry.leftElem.classList.toggle('on')
|
|
92
|
-
settings.save(key, !config[key]) ; sync.configToUI({ updatedKey: key })
|
|
93
|
-
notify(`${settings.controls[key].label} ${ settingIsEnabled(key) ? 'ON' : 'OFF' }`)
|
|
94
|
-
}
|
|
95
|
-
}
|
|
122
|
+
const menuEntriesDiv = dom.create.elem('div') ; footer.before(menuEntriesDiv)
|
|
123
|
+
|
|
124
|
+
// Group controls by category
|
|
125
|
+
const categorizedCtrls = {}
|
|
126
|
+
Object.entries(settings.controls).forEach(([key, ctrl]) =>
|
|
127
|
+
( categorizedCtrls[ctrl.category || 'general'] ??= {} )[key] = { ...ctrl, key: key })
|
|
128
|
+
|
|
129
|
+
// Create/append general controls
|
|
130
|
+
Object.values(categorizedCtrls.general || {}).forEach(ctrl => menuEntriesDiv.append(createMenuEntry(ctrl)))
|
|
131
|
+
|
|
132
|
+
// Create/append categorized controls
|
|
133
|
+
Object.entries(categorizedCtrls).forEach(([category, ctrls]) => {
|
|
134
|
+
if (category == 'general') return
|
|
135
|
+
const catData = { ...settings.categories[category], key: category, type: 'category' },
|
|
136
|
+
catChildrenDiv = dom.create.elem('div', { class: 'categorized-entries' })
|
|
137
|
+
if (catData.color) // color the stripe
|
|
138
|
+
catChildrenDiv.style.borderImage = `linear-gradient(transparent, #${catData.color}) 30 100%`
|
|
139
|
+
menuEntriesDiv.append(createMenuEntry(catData), catChildrenDiv)
|
|
140
|
+
Object.values(ctrls).forEach(ctrl => catChildrenDiv.append(createMenuEntry(ctrl)))
|
|
96
141
|
})
|
|
97
142
|
}
|
|
98
143
|
|
|
144
|
+
// AUTO-EXPAND categories
|
|
145
|
+
document.querySelectorAll('.menu-entry:has(.menu-caret)').forEach(categoryDiv => {
|
|
146
|
+
if (settings.categories[categoryDiv.id]?.autoExpand)
|
|
147
|
+
toggleCategorySettingsVisiblity(categoryDiv.id, { transitions: false })
|
|
148
|
+
})
|
|
149
|
+
|
|
99
150
|
sync.fade() // based on master toggle
|
|
100
151
|
|
|
101
|
-
//
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
// Create/append ABOUT footer button
|
|
112
|
-
const aboutSpan = dom.create.elem('span', {
|
|
113
|
-
title: `About ${app.name}`,
|
|
114
|
-
class: 'menu-icon highlight-on-hover', style: 'right:30px ; padding-top: 2px' })
|
|
115
|
-
const aboutIcon = icons.create('questionMark', { width: 15, height: 13, style: 'margin-bottom: 0.04rem' })
|
|
152
|
+
// Init CHATGPT.JS footer logo/listener
|
|
153
|
+
const cjsLogo = footer.querySelector('.cjs-logo')
|
|
154
|
+
cjsLogo.src = 'https://cdn.jsdelivr.net/gh/KudoAI/chatgpt.js@745f0ca/assets/images/badges/powered-by-chatgpt.js.png'
|
|
155
|
+
cjsLogo.onclick = () => { open(app.urls.chatgptJS) ; close() }
|
|
156
|
+
|
|
157
|
+
// Init ABOUT footer tooltip/icon/listener
|
|
158
|
+
const aboutSpan = footer.querySelector('.about-span')
|
|
159
|
+
aboutSpan.title = `About ${app.name}`
|
|
160
|
+
aboutSpan.append(icons.create('questionMark', { width: 15, height: 13 }))
|
|
116
161
|
aboutSpan.onclick = () => { chrome.runtime.sendMessage({ action: 'showAbout' }) ; close() }
|
|
117
|
-
aboutSpan.append(aboutIcon) ; footer.append(aboutSpan)
|
|
118
162
|
|
|
119
|
-
//
|
|
120
|
-
const moreExtensionsSpan =
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
const moreExtensionsIcon = icons.create('plus')
|
|
163
|
+
// Init MORE EXTENSIONS footer tooltip/icon/listener
|
|
164
|
+
const moreExtensionsSpan = footer.querySelector('.more-extensions-span')
|
|
165
|
+
moreExtensionsSpan.title = 'More AI Extensions'
|
|
166
|
+
moreExtensionsSpan.append(icons.create('plus'))
|
|
124
167
|
moreExtensionsSpan.onclick = () => { open(app.urls.relatedExtensions) ; close() }
|
|
125
|
-
moreExtensionsSpan.append(moreExtensionsIcon) ; footer.append(moreExtensionsSpan)
|
|
126
168
|
|
|
127
169
|
// Remove LOADING SPINNER after imgs load
|
|
128
170
|
Promise.all([...document.querySelectorAll('img')].map(img =>
|
|
@@ -8,13 +8,19 @@
|
|
|
8
8
|
<div class="loading-bg">
|
|
9
9
|
<span class="loading-spinner"></span>
|
|
10
10
|
</div>
|
|
11
|
-
<
|
|
11
|
+
<header>
|
|
12
12
|
<div class="logo">
|
|
13
|
-
<img
|
|
13
|
+
<img src="https://cdn.jsdelivr.net/gh/KudoAI/chatgpt.js@f0cdfc9/starters/chrome/extension/icons/icon32.png"
|
|
14
|
+
width=26 alt="">
|
|
14
15
|
</div>
|
|
15
16
|
<div class="menu-title">ChatGPT Extension</div>
|
|
16
17
|
<div class="master-toggle"></div>
|
|
17
|
-
</
|
|
18
|
+
</header>
|
|
19
|
+
<footer>
|
|
20
|
+
<span><img class="cjs-logo"></span>
|
|
21
|
+
<span class="about-span menu-icon highlight-on-hover"></span>
|
|
22
|
+
<span class="more-extensions-span menu-icon highlight-on-hover"></span>
|
|
23
|
+
</footer>
|
|
18
24
|
<script src="controller.js"></script>
|
|
19
25
|
</body>
|
|
20
26
|
</html>
|
|
@@ -8,6 +8,8 @@ body {
|
|
|
8
8
|
|
|
9
9
|
/* Color/fade mods */
|
|
10
10
|
.highlight-on-hover:hover { background: rgb(100,149,237) }
|
|
11
|
+
.highlight-on-hover:hover span:not(.track), .highlight-on-hover:hover .menu-caret { /* invert setting labels on hover */
|
|
12
|
+
filter: invert(1) }
|
|
11
13
|
.disabled { opacity: 0.3 ; pointer-events: none }
|
|
12
14
|
|
|
13
15
|
/* Loader */
|
|
@@ -34,7 +36,7 @@ body {
|
|
|
34
36
|
}
|
|
35
37
|
|
|
36
38
|
/* Header */
|
|
37
|
-
|
|
39
|
+
header {
|
|
38
40
|
border-bottom: solid 1px lightgrey ; padding: 5px 5px 5px 0 ; margin: 0 ;
|
|
39
41
|
min-height: 38px ; display: flex; background: white ; align-items: center }
|
|
40
42
|
.logo { margin: 4px 8px 4px 12px ; position: relative ; top: 3px }
|
|
@@ -47,9 +49,12 @@ body {
|
|
|
47
49
|
display: flex ; min-height: 2rem ; padding: 0 14px 0 2px ; white-space: nowrap ; font-size: 91%
|
|
48
50
|
}
|
|
49
51
|
.menu-icon { padding: 8px }
|
|
50
|
-
.menu-entry:hover span:not(.track), .menu-entry:hover .caret { filter: invert(1) } /* invert setting labels on hover */
|
|
51
52
|
.menu-entry > label > .track { transform: scale(0.95) ; top: 1px } /* make child toggles smaller */
|
|
52
|
-
.menu-
|
|
53
|
+
.menu-caret { position: absolute ; right: 14px ; transform: rotate(-90deg) }
|
|
54
|
+
.categorized-entries { /* add left-stripe */
|
|
55
|
+
border-left: 4px solid transparent ; height: 0 ; overflow: hidden ;
|
|
56
|
+
border-image: linear-gradient(transparent, rgb(161 161 161)) 30 100%
|
|
57
|
+
}
|
|
53
58
|
|
|
54
59
|
/* Toggle elements */
|
|
55
60
|
.toggle .track {
|
|
@@ -64,13 +69,16 @@ body {
|
|
|
64
69
|
.toggle.on .track::before { transform: translateX(9px) ; transition: transform 0.15s ease-in-out }
|
|
65
70
|
|
|
66
71
|
/* Footer */
|
|
67
|
-
footer {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
.
|
|
71
|
-
|
|
72
|
+
footer { display: flex ; align-items: center ; background: #f5f5f5 ; height: 40px ; padding: 0 7px }
|
|
73
|
+
footer > span:has(> .cjs-logo) { display: flex ; align-items: center ; flex-grow: 1 ; padding-left: 2px }
|
|
74
|
+
.cjs-logo { opacity: 0.5 } .cjs-logo:hover { opacity: 1 ; transition: 0.25s }
|
|
75
|
+
footer > .menu-icon {
|
|
76
|
+
display: flex ; opacity: 0.7 ; align-items: center ; padding: 8px 5px ;
|
|
77
|
+
box-sizing: border-box ; height: 96% ; width: 26px
|
|
78
|
+
}
|
|
79
|
+
footer img[src*=question-mark] { position: relative ; top: 1px }
|
|
72
80
|
|
|
73
81
|
/* Non-baseline features */
|
|
74
82
|
@supports (overflow: clip) { body { overflow: clip }}
|
|
75
83
|
@supports (user-select: none) { body, button, input, select, textarea { user-select: none }}
|
|
76
|
-
@supports (cursor: pointer) { .highlight-on-hover:hover, .toggle .track, .cjs-
|
|
84
|
+
@supports (cursor: pointer) { .highlight-on-hover:hover, .toggle .track, .cjs-logo { cursor: pointer }}
|
|
@@ -6,9 +6,8 @@ const app = {
|
|
|
6
6
|
version: chrome.runtime.getManifest().version, symbol: '🤖', cssPrefix: 'chatgpt-extension',
|
|
7
7
|
author: { name: 'KudoAI', url: 'https://kudoai.com' },
|
|
8
8
|
urls: {
|
|
9
|
-
assetHost: 'https://cdn.jsdelivr.net/gh/KudoAI/chatgpt.js-chrome-starter',
|
|
9
|
+
assetHost: 'https://cdn.jsdelivr.net/gh/KudoAI/chatgpt.js-chrome-starter@latest',
|
|
10
10
|
chatgptJS: 'https://chatgptjs.org',
|
|
11
|
-
cjsAssetHost: 'https://assets.chatgptjs.org',
|
|
12
11
|
contributors: 'https://docs.chatgptjs.org/#-contributors',
|
|
13
12
|
gitHub: 'https://github.com/KudoAI/chatgpt.js-chrome-starter',
|
|
14
13
|
relatedExtensions: 'https://aiwebextensions.com',
|
|
@@ -33,10 +32,11 @@ chrome.runtime.onMessage.addListener(async req => {
|
|
|
33
32
|
const [activeTab] = await chrome.tabs.query({ active: true, currentWindow: true })
|
|
34
33
|
const chatgptTab = new URL(activeTab.url).hostname == 'chatgpt.com' ? activeTab
|
|
35
34
|
: await chrome.tabs.create({ url: chatgptURL })
|
|
36
|
-
if (activeTab != chatgptTab) new Promise(resolve => // after new tab loads
|
|
35
|
+
if (activeTab != chatgptTab) await new Promise(resolve => // after new tab loads
|
|
37
36
|
chrome.tabs.onUpdated.addListener(function loadedListener(tabId, changeInfo) {
|
|
38
37
|
if (tabId == chatgptTab.id && changeInfo.status == 'complete') {
|
|
39
38
|
chrome.tabs.onUpdated.removeListener(loadedListener) ; setTimeout(resolve, 500)
|
|
40
|
-
}}))
|
|
39
|
+
}}))
|
|
40
|
+
chrome.tabs.sendMessage(chatgptTab.id, { action: 'showAbout' })
|
|
41
41
|
}
|
|
42
42
|
})
|
|
@@ -3,12 +3,12 @@
|
|
|
3
3
|
// @description A Greasemonkey template to start using chatgpt.js like a boss
|
|
4
4
|
// @author chatgpt.js
|
|
5
5
|
// @namespace https://chatgpt.js.org
|
|
6
|
-
// @version 2025.
|
|
6
|
+
// @version 2025.4.28
|
|
7
7
|
// @license MIT
|
|
8
8
|
// @icon https://cdn.jsdelivr.net/gh/KudoAI/chatgpt.js@1fc50da/starters/greasemonkey/assets/images/icons/robot/icon48.png
|
|
9
9
|
// @icon64 https://cdn.jsdelivr.net/gh/KudoAI/chatgpt.js@1fc50da/starters/greasemonkey/assets/images/icons/robot/icon64.png
|
|
10
10
|
// @match *://chatgpt.com/*
|
|
11
|
-
// @require https://cdn.jsdelivr.net/npm/@kudoai/chatgpt.js@3.
|
|
11
|
+
// @require https://cdn.jsdelivr.net/npm/@kudoai/chatgpt.js@3.8.0/dist/chatgpt.min.js
|
|
12
12
|
// @grant GM_getValue
|
|
13
13
|
// @grant GM_setValue
|
|
14
14
|
// @noframes
|