@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.
@@ -6,8 +6,8 @@
6
6
 
7
7
  // Init ENV context
8
8
  const env = {
9
- site: /([^.]+)\.[^.]+$/.exec(new URL((await chrome.tabs.query(
10
- { active: true, currentWindow: true }))[0].url).hostname)?.[1]
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
- 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
- }
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
- // 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' })
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
- // 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')
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
- <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,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-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
+ }
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
- 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 }
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-span { cursor: pointer }}
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
- }})).then(() => chrome.tabs.sendMessage(chatgptTab.id, { action: 'showAbout' }))
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.2.21.1
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.7.1/dist/chatgpt.min.js
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