@kudoai/chatgpt.js 3.7.1 → 3.8.1

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.
@@ -3,22 +3,14 @@
3
3
  "name": "ChatGPT Extension",
4
4
  "short_name": "ChatGPT 🧩",
5
5
  "description": "A Chromium extension template to start using chatgpt.js like a boss!",
6
- "version": "2025.2.21",
6
+ "version": "2025.5.11",
7
7
  "author": "KudoAI",
8
8
  "homepage_url": "https://github.com/KudoAI/chatgpt.js-chrome-starter",
9
- "icons": {
10
- "16": "icons/icon16.png",
11
- "32": "icons/icon32.png",
12
- "64": "icons/icon64.png",
13
- "128": "icons/icon128.png"
14
- },
15
- "permissions": [ "activeTab", "storage" ],
9
+ "icons": { "16": "icons/icon16.png", "32": "icons/icon32.png", "64": "icons/icon64.png", "128": "icons/icon128.png" },
10
+ "permissions": ["activeTab", "storage"],
16
11
  "action": { "default_popup": "popup/index.html" },
17
- "web_accessible_resources": [{
18
- "matches": [ "<all_urls>" ],
19
- "resources": [ "components/modals.js", "lib/chatgpt.js", "lib/dom.js", "lib/settings.js" ]
20
- }],
21
- "content_scripts": [{ "matches": [ "https://chatgpt.com/*" ], "js": [ "content.js" ] }],
12
+ "web_accessible_resources": [{ "matches": ["<all_urls>"], "resources": ["components/*", "lib/*"] }],
13
+ "content_scripts": [{ "matches": ["https://chatgpt.com/*"], "js": ["content.js"] }],
22
14
  "background": { "service_worker": "service-worker.js" },
23
15
  "minimum_chrome_version": "88"
24
16
  }
@@ -5,26 +5,52 @@
5
5
  await import(chrome.runtime.getURL(resource))
6
6
 
7
7
  // Init ENV context
8
- const env = {
9
- site: /([^.]+)\.[^.]+$/.exec(new URL((await chrome.tabs.query(
10
- { active: true, currentWindow: true }))[0].url).hostname)?.[1]
8
+ window.env = {
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
14
- const { app } = await chrome.storage.local.get('app')
15
- icons.import({ app }) // for src's using app.urls.assetHost
14
+ ;({ app: window.app } = await chrome.storage.local.get('app'))
16
15
 
17
16
  // Define FUNCTIONS
18
17
 
18
+ function createMenuEntry(entryData) {
19
+ const entry = {
20
+ div: dom.create.elem('div', {
21
+ id: entryData.key, class: 'menu-entry highlight-on-hover', title: entryData.helptip || '' }),
22
+ leftElem: dom.create.elem('div', { class: `menu-icon ${ entryData.type || '' }` }),
23
+ label: dom.create.elem('span')
24
+ }
25
+ entry.label.textContent = entryData.label
26
+ if (entryData.type == 'toggle') { // add track to left, init knob pos
27
+ entry.leftElem.append(dom.create.elem('span', { class: 'track' }))
28
+ entry.leftElem.classList.toggle('on', settings.typeIsEnabled(entryData.key))
29
+ } else { // add symbol to left, append status to right
30
+ entry.leftElem.textContent = entryData.symbol || '⚙️'
31
+ if (entryData.status) entry.label.textContent += ` — ${entryData.status}`
32
+ }
33
+ if (entryData.type == 'category') entry.div.append(icons.create('caretDown', { size: 11, class: 'menu-caret' }))
34
+ entry.div.onclick = () => {
35
+ if (entryData.type == 'category') toggleCategorySettingsVisiblity(entryData.key)
36
+ else if (entryData.type == 'toggle') {
37
+ entry.leftElem.classList.toggle('on')
38
+ settings.save(entryData.key, !config[entryData.key]) ; sync.configToUI({ updatedKey: entryData.key })
39
+ notify(`${entryData.label} ${chrome.i18n.getMessage(`state_${
40
+ settings.typeIsEnabled(entryData.key) ? 'on' : 'off' }`).toUpperCase()}`)
41
+ } else if (entryData.type == 'link') { open(entryData.url) ; close() }
42
+ }
43
+ entry.div.append(entry.leftElem, entry.label)
44
+ return entry.div
45
+ }
46
+
19
47
  function notify(msg, pos = 'bottom-right') { sendMsgToActiveTab('notify', { msg, pos }) }
20
48
 
21
49
  async function sendMsgToActiveTab(action, options) {
22
50
  const [activeTab] = await chrome.tabs.query({ active: true, currentWindow: true })
23
- return await chrome.tabs.sendMessage(activeTab.id, { action: action, options: { ...options }})
51
+ return await chrome.tabs.sendMessage(activeTab.id, { action, options })
24
52
  }
25
53
 
26
- function settingIsEnabled(key) { return config[key] ^ /disabled|hidden/i.test(key) }
27
-
28
54
  const sync = {
29
55
  fade() {
30
56
 
@@ -45,6 +71,34 @@
45
71
  configToUI(options) { return sendMsgToActiveTab('syncConfigToUI', options) }
46
72
  }
47
73
 
74
+ function toggleCategorySettingsVisiblity(category, { transitions = true, action } = {}) {
75
+ const transitionDuration = 350, // ms
76
+ categoryDiv = document.getElementById(category),
77
+ caret = categoryDiv.querySelector('.menu-caret'),
78
+ catChildrenDiv = categoryDiv.nextSibling,
79
+ catChild = catChildrenDiv.querySelectorAll('.menu-entry')
80
+ if (action != 'hide' && dom.get.computedHeight(catChildrenDiv) == 0) { // show category settings
81
+ categoryDiv.classList.toggle('expanded', true)
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
+ categoryDiv.classList.toggle('expanded', false)
97
+ Object.assign(catChildrenDiv.style, { height: 0, transition: '' })
98
+ Object.assign(caret.style, { transform: 'rotate(-90deg)', transition: '' }) // point it right
99
+ }
100
+ }
101
+
48
102
  // Run MAIN routine
49
103
 
50
104
  // Init MASTER TOGGLE
@@ -63,66 +117,55 @@
63
117
  }
64
118
 
65
119
  // Create CHILD menu entries on chatgpt.com
120
+ const footer = document.querySelector('footer')
66
121
  if (env.site == 'chatgpt') {
67
- const childEntriesDiv = dom.create.elem('div') ; document.body.append(childEntriesDiv)
68
122
  await settings.load(Object.keys(settings.controls))
69
- Object.keys(settings.controls).forEach(key => {
70
- const controlType = settings.controls[key].type
71
-
72
- // Init entry's elems
73
- const entry = {
74
- div: dom.create.elem('div', {
75
- class: 'menu-entry highlight-on-hover', title: settings.controls[key].helptip || '' }),
76
- leftElem: dom.create.elem('div', { class: `menu-icon ${ controlType || '' }` }),
77
- label: dom.create.elem('span')
78
- }
79
- entry.label.textContent = settings.controls[key].label
80
- entry.div.append(entry.leftElem, entry.label) ; childEntriesDiv.append(entry.div)
81
- if (controlType == 'toggle') { // add track to left, init knob pos
82
- entry.leftElem.append(dom.create.elem('span', { class: 'track' }))
83
- entry.leftElem.classList.toggle('on', settingIsEnabled(key))
84
- } else { // add symbol to left, append status to right
85
- entry.leftElem.innerText = settings.controls[key].symbol
86
- entry.label.innerText += `— ${settings.controls[key].status}`
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
- }
123
+ const menuEntriesDiv = dom.create.elem('div') ; footer.before(menuEntriesDiv)
124
+
125
+ // Group controls by category
126
+ const categorizedCtrls = {}
127
+ Object.entries(settings.controls).forEach(([key, ctrl]) =>
128
+ ( categorizedCtrls[ctrl.category || 'general'] ??= {} )[key] = { ...ctrl, key: key })
129
+
130
+ // Create/append general controls
131
+ Object.values(categorizedCtrls.general || {}).forEach(ctrl => menuEntriesDiv.append(createMenuEntry(ctrl)))
132
+
133
+ // Create/append categorized controls
134
+ Object.entries(categorizedCtrls).forEach(([category, ctrls]) => {
135
+ if (category == 'general') return
136
+ const catData = { ...settings.categories[category], key: category, type: 'category' },
137
+ catChildrenDiv = dom.create.elem('div', { class: 'categorized-entries' })
138
+ if (catData.color) // color the stripe
139
+ catChildrenDiv.style.borderImage = `linear-gradient(transparent, #${catData.color}) 30 100%`
140
+ menuEntriesDiv.append(createMenuEntry(catData), catChildrenDiv)
141
+ Object.values(ctrls).forEach(ctrl => catChildrenDiv.append(createMenuEntry(ctrl)))
96
142
  })
97
143
  }
98
144
 
145
+ // AUTO-EXPAND categories
146
+ document.querySelectorAll('.menu-entry:has(.menu-caret)').forEach(categoryDiv => {
147
+ if (settings.categories[categoryDiv.id]?.autoExpand)
148
+ toggleCategorySettingsVisiblity(categoryDiv.id, { transitions: false })
149
+ })
150
+
99
151
  sync.fade() // based on master toggle
100
152
 
101
- // Create/append FOOTER container
102
- const footer = dom.create.elem('footer') ; document.body.append(footer)
103
-
104
- // Create/append CHATGPT.JS footer logo
105
- const cjsSpan = dom.create.elem('span', { class: 'cjs-span' })
106
- const cjsLogo = dom.create.elem('img', {
107
- src: `${app.urls.cjsAssetHost}/images/badges/powered-by-chatgpt.js.png?b2a1975` })
108
- cjsSpan.onclick = () => { open(app.urls.chatgptJS) ; close() }
109
- cjsSpan.append(cjsLogo) ; footer.append(cjsSpan)
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' })
153
+ // Init CHATGPT.JS footer logo/listener
154
+ const cjsLogo = footer.querySelector('.cjs-logo')
155
+ cjsLogo.src = 'https://cdn.jsdelivr.net/gh/KudoAI/chatgpt.js@745f0ca/assets/images/badges/powered-by-chatgpt.js.png'
156
+ cjsLogo.onclick = () => { open(app.urls.chatgptJS) ; close() }
157
+
158
+ // Init ABOUT footer tooltip/icon/listener
159
+ const aboutSpan = footer.querySelector('.about-span')
160
+ aboutSpan.title = `About ${app.name}`
161
+ aboutSpan.append(icons.create('questionMark', { width: 15, height: 13 }))
116
162
  aboutSpan.onclick = () => { chrome.runtime.sendMessage({ action: 'showAbout' }) ; close() }
117
- aboutSpan.append(aboutIcon) ; footer.append(aboutSpan)
118
163
 
119
- // Create/append RELATED EXTENSIONS footer button
120
- const moreExtensionsSpan = dom.create.elem('span', {
121
- title: 'More AI Extensions',
122
- class: 'menu-icon highlight-on-hover', style: 'right:2px ; padding-top: 2px' })
123
- const moreExtensionsIcon = icons.create('plus')
164
+ // Init MORE EXTENSIONS footer tooltip/icon/listener
165
+ const moreExtensionsSpan = footer.querySelector('.more-extensions-span')
166
+ moreExtensionsSpan.title = 'More AI Extensions'
167
+ moreExtensionsSpan.append(icons.create('plus'))
124
168
  moreExtensionsSpan.onclick = () => { open(app.urls.relatedExtensions) ; close() }
125
- moreExtensionsSpan.append(moreExtensionsIcon) ; footer.append(moreExtensionsSpan)
126
169
 
127
170
  // Remove LOADING SPINNER after imgs load
128
171
  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
- <div class="menu-header">
11
+ <header>
12
12
  <div class="logo">
13
- <img alt="" width=26 src="https://cdn.jsdelivr.net/gh/KudoAI/chatgpt.js@f0cdfc9/starters/chrome/extension/icons/icon32.png">
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
- </div>
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
- .menu-header {
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,13 @@ 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-prompt { margin-left: 2px } /* align non-toggle items */
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
+ }
58
+ .expanded > span { font-weight: 500 ; transform: scale(1.05) ; margin-left: 2px }
53
59
 
54
60
  /* Toggle elements */
55
61
  .toggle .track {
@@ -64,13 +70,17 @@ body {
64
70
  .toggle.on .track::before { transform: translateX(9px) ; transition: transform 0.15s ease-in-out }
65
71
 
66
72
  /* Footer */
67
- footer {
68
- font-size: 12px ; text-align: center ; color: #999 ; background: #f5f5f5 ; height: 40px ; line-height: 40px }
69
- footer > .menu-icon { position: absolute ; bottom: -10px ; opacity: 0.7 }
70
- .cjs-span { position: absolute ; bottom: -.25rem ; left: 0.7rem }
71
- .cjs-span img { opacity: 0.5 } .cjs-span:hover img { opacity: 1 ; transition: 0.25s }
73
+ footer { display: flex ; align-items: center ; background: #f5f5f5 ; height: 40px ; padding: 0 7px }
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 */
82
+ @supports (cursor: pointer) { .highlight-on-hover:hover, .toggle .track, .cjs-logo { cursor: pointer }}
74
83
  @supports (overflow: clip) { body { overflow: clip }}
84
+ @supports selector(:has(> .cjs-logo)) {
85
+ footer > span:has(> .cjs-logo) { display: flex ; align-items: center ; flex-grow: 1 ; padding-left: 2px }}
75
86
  @supports (user-select: none) { body, button, input, select, textarea { user-select: none }}
76
- @supports (cursor: pointer) { .highlight-on-hover:hover, .toggle .track, .cjs-span { cursor: pointer }}
@@ -1,21 +1,19 @@
1
1
  const chatgptURL = 'https://chatgpt.com'
2
2
 
3
3
  // Init APP data
4
- const app = {
4
+ chrome.storage.local.set({ app: {
5
5
  name: chrome.runtime.getManifest().name,
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',
15
14
  support: 'https://github.com/KudoAI/chatgpt.js-chrome-starter/issues'
16
15
  }
17
- }
18
- chrome.storage.local.set({ app }) // save to Chrome storage
16
+ }}) // save to Chrome storage
19
17
 
20
18
  // Launch CHATGPT on install
21
19
  chrome.runtime.onInstalled.addListener(details => {
@@ -33,10 +31,11 @@ chrome.runtime.onMessage.addListener(async req => {
33
31
  const [activeTab] = await chrome.tabs.query({ active: true, currentWindow: true })
34
32
  const chatgptTab = new URL(activeTab.url).hostname == 'chatgpt.com' ? activeTab
35
33
  : await chrome.tabs.create({ url: chatgptURL })
36
- if (activeTab != chatgptTab) new Promise(resolve => // after new tab loads
34
+ if (activeTab != chatgptTab) await new Promise(resolve => // after new tab loads
37
35
  chrome.tabs.onUpdated.addListener(function loadedListener(tabId, changeInfo) {
38
36
  if (tabId == chatgptTab.id && changeInfo.status == 'complete') {
39
37
  chrome.tabs.onUpdated.removeListener(loadedListener) ; setTimeout(resolve, 500)
40
- }})).then(() => chrome.tabs.sendMessage(chatgptTab.id, { action: 'showAbout' }))
38
+ }}))
39
+ chrome.tabs.sendMessage(chatgptTab.id, { action: 'showAbout' })
41
40
  }
42
41
  })
@@ -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.2.21.1
6
+ // @version 2025.5.11
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.7.1/dist/chatgpt.min.js
11
+ // @require https://cdn.jsdelivr.net/npm/@kudoai/chatgpt.js@3.8.1/dist/chatgpt.min.js
12
12
  // @grant GM_getValue
13
13
  // @grant GM_setValue
14
14
  // @noframes