@kudoai/chatgpt.js 3.8.1 → 3.8.2

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.
@@ -1,5 +1,9 @@
1
1
  (async () => {
2
2
 
3
+ // Init SCHEME
4
+ if (window.matchMedia?.('(prefers-color-scheme: dark)').matches)
5
+ document.documentElement.classList.add('dark')
6
+
3
7
  // Import JS resources
4
8
  for (const resource of ['components/icons.js', 'lib/dom.js', 'lib/settings.js'])
5
9
  await import(chrome.runtime.getURL(resource))
@@ -7,7 +11,8 @@
7
11
  // Init ENV context
8
12
  window.env = {
9
13
  site: new URL((await chrome.tabs.query({ active: true, currentWindow: true }))[0].url)
10
- .hostname.split('.').slice(-2, -1)[0] // extract 2nd-level domain
14
+ .hostname.split('.').slice(-2, -1)[0], // extract 2nd-level domain
15
+ menu: { isDark: document.documentElement.classList.contains('dark') }
11
16
  }
12
17
 
13
18
  // Import DATA
@@ -16,39 +21,112 @@
16
21
  // Define FUNCTIONS
17
22
 
18
23
  function createMenuEntry(entryData) {
24
+
25
+ // Assemble elems
19
26
  const entry = {
20
27
  div: dom.create.elem('div', {
21
28
  id: entryData.key, class: 'menu-entry highlight-on-hover', title: entryData.helptip || '' }),
22
- leftElem: dom.create.elem('div', { class: `menu-icon ${ entryData.type || '' }` }),
29
+ leftElem: dom.create.elem('div', { class: `menu-icon ${ entryData.type || '' }`}),
23
30
  label: dom.create.elem('span')
24
31
  }
25
32
  entry.label.textContent = entryData.label
33
+ entry.div.append(entry.leftElem, entry.label)
26
34
  if (entryData.type == 'toggle') { // add track to left, init knob pos
27
35
  entry.leftElem.append(dom.create.elem('span', { class: 'track' }))
28
36
  entry.leftElem.classList.toggle('on', settings.typeIsEnabled(entryData.key))
29
37
  } else { // add symbol to left, append status to right
30
- entry.leftElem.textContent = entryData.symbol || '⚙️'
38
+ entry.leftElem.textContent = entryData.symbol || '⚙️' ; entry.label.style.flexGrow = 1
31
39
  if (entryData.status) entry.label.textContent += ` — ${entryData.status}`
40
+ if (entryData.type == 'link') {
41
+ entry.label.after(entry.rightElem = dom.create.elem('div', { class: 'menu-right-elem' }))
42
+ entry.rightElem.append(icons.create({ key: 'open', size: 17, fill: 'black' }))
43
+ }
44
+ }
45
+ if (entryData.type == 'category')
46
+ entry.div.append(icons.create({ key: 'caretDown', size: 11, class: 'menu-caret menu-right-elem' }))
47
+ else if (entryData.type == 'slider') { // append slider, add listeners, remove .highlight-on-hover
48
+ entry.slider = dom.create.elem('input', { class: 'slider', type: 'range',
49
+ min: entryData.min || 0, max: entryData.max || 100, value: config[entryData.key] })
50
+ if (entryData.step || env.browser.isFF) // use val from entryData or default to 2% in FF for being laggy
51
+ entry.slider.step = entryData.step || ( 0.02 * entry.slider.max - entry.slider.min )
52
+ entry.label.textContent += `: ${entry.slider.value}${ entryData.labelSuffix || '' }`
53
+ entry.slider.style.setProperty('--track-fill-percent', `${ entry.slider.value / entry.slider.max *100 }%`)
54
+ entry.slider.oninput = ({ target: { value }}) => { // update label/color
55
+ settings.save(entryData.key, parseInt(value)) ; sync.configToUI({ updatedKey: entryData.key })
56
+ entry.label.textContent = `${entryData.label}: ${value}${ entryData.labelSuffix || '' }`
57
+ entry.slider.style.setProperty('--track-fill-percent', `${ value / entry.slider.max *100 }%`)
58
+ }
59
+ entry.div.onwheel = event => { // move slider by 2 steps
60
+ entry.slider.value = parseInt(entry.slider.value) -Math.sign(event.deltaY) *2
61
+ entry.slider.dispatchEvent(new Event('input'))
62
+ }
63
+ entry.div.append(entry.slider) ; entry.div.classList.remove('highlight-on-hover')
64
+ }
65
+ if (entryData.dependencies) {
66
+ const toDisable = Object.values(entryData.dependencies).flat().some(dep => !settings.typeIsEnabled(dep))
67
+ Object.assign(entry.div.style, {
68
+ transition: '', minHeight: 'auto', opacity: +!toDisable,
69
+ height: toDisable ? 0 : 'auto', visibility: toDisable ? 'hidden' : 'visible'
70
+ })
32
71
  }
33
- if (entryData.type == 'category') entry.div.append(icons.create('caretDown', { size: 11, class: 'menu-caret' }))
72
+
73
+ // Add click listener
34
74
  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() }
75
+ const now = Date.now()
76
+ const throttleMs = typeof entryData.throttle == 'number' ? entryData.throttle
77
+ : entryData.throttle ? 1500 : 0
78
+ if (throttleMs && now -( entry.div.lastClickTime || 0 ) < throttleMs) return
79
+ entry.div.classList.remove('disabled') ; entry.div.lastClickTime = now
80
+ ;({
81
+ category: () => toggleCategorySettingsVisiblity({ key: entryData.key }),
82
+ toggle: () => {
83
+ entry.leftElem.classList.toggle('on')
84
+ settings.save(entryData.key, !config[entryData.key])
85
+ sync.configToUI({ updatedKey: entryData.key })
86
+ requestAnimationFrame(() => notify(`${entryData.label} ${chrome.i18n.getMessage(`state_${
87
+ settings.typeIsEnabled(entryData.key) ? 'on' : 'off' }`).toUpperCase()}`))
88
+ },
89
+ link: () => { open(entryData.url) ; close() }
90
+ })[entryData.type]()
91
+
92
+ // Throttle re-click
93
+ if (entryData.throttle) {
94
+ entry.div.classList.add('disabled')
95
+ setTimeout(() => entry.div.classList.remove('disabled'), throttleMs)
96
+ }
97
+
98
+ // Enable/disable dependent entries
99
+ for (const [ctrlKey, ctrlData] of Object.entries({ ...settings.categories, ...settings.controls }))
100
+ if (Object.values(ctrlData.dependencies || {}).flat().includes(entryData.key)) {
101
+ const depDiv = document.querySelector(`div#${ctrlKey}`) ; if (!depDiv) continue
102
+ const ctgChildrenDiv = depDiv.closest('.categorized-entries'),
103
+ ctgChildren = ctgChildrenDiv.querySelectorAll('.menu-entry'),
104
+ toDisable = !settings.typeIsEnabled(entryData.key)
105
+ requestAnimationFrame(() => Object.assign(depDiv.closest('.categorized-entries').style, {
106
+ height: `${dom.get.computedHeight(ctgChildren)}px`,
107
+ transition: env.browser.isFF || toDisable ? '' : 'height 0.25s'
108
+ }))
109
+ Object.assign(depDiv.style, {
110
+ transition: toDisable ? '' : 'opacity 0.15s ease-in', height: toDisable ? 0 : 'auto',
111
+ visibility: toDisable ? 'hidden' : 'visible', opacity: +!toDisable
112
+ })
113
+ depDiv.classList.toggle('disabled', toDisable)
114
+ }
42
115
  }
43
- entry.div.append(entry.leftElem, entry.label)
116
+
44
117
  return entry.div
45
118
  }
46
119
 
120
+ function depIsEnabled(ctrlKey) {
121
+ const deps = settings.controls[ctrlKey]?.dependencies
122
+ return !deps || Object.values(deps).flat(Infinity).some(depKey => settings.typeIsEnabled(depKey))
123
+ }
124
+
47
125
  function notify(msg, pos = 'bottom-right') { sendMsgToActiveTab('notify', { msg, pos }) }
48
126
 
49
127
  async function sendMsgToActiveTab(action, options) {
50
- const [activeTab] = await chrome.tabs.query({ active: true, currentWindow: true })
51
- return await chrome.tabs.sendMessage(activeTab.id, { action, options })
128
+ const activeTabID = (await chrome.tabs.query({ active: true, currentWindow: true }))[0].id
129
+ return await chrome.tabs.sendMessage(activeTabID, { action, options })
52
130
  }
53
131
 
54
132
  const sync = {
@@ -61,51 +139,70 @@
61
139
  ))})
62
140
 
63
141
  // Menu elems
64
- document.querySelectorAll('.logo, .menu-title, .menu-entry').forEach((elem, idx) => {
65
- elem.style.transition = config.extensionDisabled ? '' : 'opacity 0.15s ease-in'
66
- setTimeout(() => elem.classList.toggle('disabled', config.extensionDisabled),
67
- config.extensionDisabled ? 0 : idx *10) // fade-out abruptly, fade-in staggered
68
- })
142
+ document.querySelectorAll('.logo, .menu-title, .menu-entry, .slider, .categorized-entries')
143
+ .forEach((elem, idx) => {
144
+ if (elem.id && (elem.matches(`#${elem.id}:has(> div.link)`) || elem.id == 'aboutEntry'))
145
+ return // never disable link/About entries
146
+ elem.style.transition = config.extensionDisabled ? '' : 'opacity 0.15s ease-in'
147
+ const toDisable = config.extensionIsDisabled || !depIsEnabled(elem.id)
148
+ if (elem.classList.contains('categorized-entries')) { // fade category strip
149
+ elem.style.transition = toDisable ? 'none' : 'var(--border-transition)'
150
+ elem.style.borderImage = elem.style.borderImage.replace(
151
+ /rgba?\(([\d,\s]+)(?:,\s*[\d.]+)?\)/,
152
+ `rgba($1,${ toDisable ? 0.3 : env.menu.isDark ? 0.5 : 1 })`
153
+ )
154
+ } else // fade entry
155
+ setTimeout(() => elem.classList.toggle('disabled', toDisable),
156
+ toDisable ? 0 : idx *10) // fade-out abruptly, fade-in staggered
157
+ })
69
158
  },
70
159
 
71
160
  configToUI(options) { return sendMsgToActiveTab('syncConfigToUI', options) }
72
161
  }
73
162
 
74
- function toggleCategorySettingsVisiblity(category, { transitions = true, action } = {}) {
163
+ function toggleCategorySettingsVisiblity({ key, transitions = true, action }) {
75
164
  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`,
165
+ ctgDiv = document.getElementById(key),
166
+ caret = ctgDiv.querySelector('.menu-caret'),
167
+ ctgChildrenDiv = ctgDiv.nextSibling,
168
+ ctgChild = ctgChildrenDiv.querySelectorAll('.menu-entry')
169
+ if (action != 'hide' && dom.get.computedHeight(ctgChildrenDiv) == 0) { // show category settings
170
+ ctgDiv.classList.toggle('expanded', true)
171
+ Object.assign(ctgChildrenDiv.style, { height: `${dom.get.computedHeight(ctgChild)}px`,
83
172
  transition: transitions ? 'height 0.25s' : '' })
84
173
  Object.assign(caret.style, { // point it down
85
174
  transform: 'rotate(0deg)', transition: transitions ? 'transform 0.15s ease-out' : '' })
86
- catChild.forEach(row => { // reset styles to support continuous transition on rapid show/hide
175
+ ctgChild.forEach(row => { // reset styles to support continuous transition on rapid show/hide
87
176
  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
177
+ ctgChildrenDiv.offsetHeight // force reflow to insta-apply reset
178
+ ctgChild.forEach((row, idx) => { // fade-in staggered
90
179
  if (transitions) row.style.transition = `opacity ${ transitionDuration /1000 }s ease-in-out`
91
180
  setTimeout(() => row.style.opacity = 1, transitions ? idx * transitionDuration /10 : 0)
92
181
  })
93
- document.querySelectorAll(`.menu-entry:has(.menu-caret):not(#${category})`).forEach(otherCategoryDiv =>
94
- toggleCategorySettingsVisiblity(otherCategoryDiv.id, { action: 'hide' }))
182
+ document.querySelectorAll(`.menu-entry:has(.menu-caret):not(#${key})`).forEach(otherCtgDiv =>
183
+ toggleCategorySettingsVisiblity({ key: otherCtgDiv.id, action: 'hide' }))
95
184
  } else { // hide category settings
96
- categoryDiv.classList.toggle('expanded', false)
97
- Object.assign(catChildrenDiv.style, { height: 0, transition: '' })
185
+ ctgDiv.classList.toggle('expanded', false)
186
+ Object.assign(ctgChildrenDiv.style, { height: 0, transition: '' })
98
187
  Object.assign(caret.style, { transform: 'rotate(-90deg)', transition: '' }) // point it right
99
188
  }
100
189
  }
101
190
 
102
191
  // Run MAIN routine
103
192
 
193
+ // Append RISING PARTICLES styles
194
+ ['gray', 'white'].forEach(color => document.head.append(
195
+ dom.create.elem('link', { rel: 'stylesheet',
196
+ href: `https://cdn.jsdelivr.net/gh/adamlui/ai-web-extensions@71695ca/assets/styles/rising-particles/dist/${
197
+ color}.min.css`
198
+ })))
199
+ dom.addRisingParticles(document.body, { lightScheme: env.menu.isDark ? 'white' : 'gray' })
200
+
104
201
  // Init MASTER TOGGLE
105
202
  const masterToggle = {
106
203
  div: document.querySelector('.master-toggle'),
107
- switch: dom.create.elem('div', { class: 'toggle menu-icon highlight-on-hover' }),
108
- track: dom.create.elem('span', { class: 'track' })
204
+ switch: dom.create.elem('div', { class: 'toggle menu-icon highlight-on-hover', style: 'height: 26px' }),
205
+ track: dom.create.elem('span', { class: 'track', style: 'position: relative ; top: 7.5px' })
109
206
  }
110
207
  masterToggle.div.append(masterToggle.switch) ; masterToggle.switch.append(masterToggle.track)
111
208
  await settings.load('extensionDisabled') ; masterToggle.switch.classList.toggle('on', !config.extensionDisabled)
@@ -125,7 +222,7 @@
125
222
  // Group controls by category
126
223
  const categorizedCtrls = {}
127
224
  Object.entries(settings.controls).forEach(([key, ctrl]) =>
128
- ( categorizedCtrls[ctrl.category || 'general'] ??= {} )[key] = { ...ctrl, key: key })
225
+ ( categorizedCtrls[ctrl.category || 'general'] ??= {} )[key] = { ...ctrl, key })
129
226
 
130
227
  // Create/append general controls
131
228
  Object.values(categorizedCtrls.general || {}).forEach(ctrl => menuEntriesDiv.append(createMenuEntry(ctrl)))
@@ -133,43 +230,77 @@
133
230
  // Create/append categorized controls
134
231
  Object.entries(categorizedCtrls).forEach(([category, ctrls]) => {
135
232
  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)))
233
+ const ctgData = { ...settings.categories[category], key: category, type: 'category' },
234
+ ctgChildrenDiv = dom.create.elem('div', { class: 'categorized-entries' })
235
+ if (ctgData.color) { // color the stripe
236
+ const [r, g, b] = ctgData.color.match(/\w\w/g).map(v => parseInt(v, 16))
237
+ ctgChildrenDiv.style.borderImage = `linear-gradient(transparent, rgba(${r},${g},${b},${
238
+ env.menu.isDark ? 0.5 : 1 })) 30 100% ${ env.menu.isDark ? '/ 100' : '' }`
239
+ }
240
+ menuEntriesDiv.append(createMenuEntry(ctgData), ctgChildrenDiv)
241
+ Object.values(ctrls).forEach(ctrl => ctgChildrenDiv.append(createMenuEntry(ctrl)))
142
242
  })
143
243
  }
144
244
 
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
- })
245
+ // Create/append ABOUT entry
246
+ const aboutEntry = {
247
+ div: createMenuEntry({ key: 'aboutEntry', symbol: '💡', label: 'About...', helptip: `About ${app.name}` }),
248
+ ticker: {
249
+ xGap: '&emsp;&emsp;&emsp;',
250
+ span: dom.create.elem('span', { class: 'ticker' }), innerDiv: dom.create.elem('div')
251
+ }
252
+ }
253
+ aboutEntry.div.querySelector('div.menu-icon').style.paddingLeft = '10px'
254
+ aboutEntry.div.querySelector('span').style.paddingLeft = '2.5px'
255
+ aboutEntry.ticker.content = `Version: <span class="ticker-em">v${ app.version + aboutEntry.ticker.xGap }</span>`
256
+ + `Powered by <span class="ticker-em">chatgpt.js</span>${aboutEntry.ticker.xGap}`
257
+ for (let i = 0 ; i < 7 ; i++) aboutEntry.ticker.content += aboutEntry.ticker.content // make long af
258
+ aboutEntry.ticker.innerDiv.innerHTML = aboutEntry.ticker.content
259
+ aboutEntry.ticker.span.append(aboutEntry.ticker.innerDiv)
260
+ aboutEntry.div.append(aboutEntry.ticker.span) ; footer.before(aboutEntry.div)
261
+ aboutEntry.div.onclick = () => { chrome.runtime.sendMessage({ action: 'showAbout' }) ; close() }
150
262
 
151
- sync.fade() // based on master toggle
263
+ // Create/append CHATGPT entry
264
+ const activeTabURL = (await chrome.tabs.query({ active: true, currentWindow: true }))[0].url,
265
+ chatgptURL = chrome.runtime.getManifest().content_scripts[0].matches.map(url => url.replace(/\/\*$/, ''))
266
+ if (!activeTabURL.includes(chatgptURL)) footer.before(createMenuEntry({
267
+ key: 'chatgptEntry', type: 'link', symbol: '🤖', label: 'Open ChatGPT', url: chatgptURL, helptip: chatgptURL }))
152
268
 
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() }
269
+ // Create/append LATEST CHANGES entry
270
+ const latestChangesURL = `${app.urls.github}/commits`
271
+ footer.before(createMenuEntry({
272
+ key: 'latestChangesEntry', type: 'link', symbol: '🚀',
273
+ label: 'Latest Changes...', url: latestChangesURL, helptip: latestChangesURL
274
+ }))
157
275
 
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 }))
162
- aboutSpan.onclick = () => { chrome.runtime.sendMessage({ action: 'showAbout' }) ; close() }
276
+ // Init FOOTER
277
+ const footerElems = { // left-to-right
278
+ chatgptjs: { logo: footer.querySelector('.chatgptjs-logo') },
279
+ about: { span: footer.querySelector('.about-span') },
280
+ moreExt: { span: footer.querySelector('.more-ext-span') }
281
+ }
282
+ footerElems.chatgptjs.logo.src = 'https://cdn.jsdelivr.net/gh/KudoAI/chatgpt.js@858b952'
283
+ + `/assets/images/badges/powered-by-chatgpt.js/${ env.menu.isDark ? 'white' : 'black' }/with-robot/95x19.png`
284
+ footerElems.chatgptjs.logo.onclick = () => { open(app.urls.chatgptjs) ; close() }
285
+ footerElems.about.span.title = `About ${app.name}`
286
+ footerElems.about.span.append(icons.create({ key: 'questionMark', width: 15, height: 13 }))
287
+ footerElems.about.span.onclick = () => { chrome.runtime.sendMessage({ action: 'showAbout' }) ; close() }
288
+ footerElems.moreExt.span.title = 'More AI Extensions'
289
+ footerElems.moreExt.span.append(icons.create({ key: 'plus' }))
290
+ footerElems.moreExt.span.onclick = () => { open(app.urls.relatedExtensions) ; close() }
163
291
 
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'))
168
- moreExtensionsSpan.onclick = () => { open(app.urls.relatedExtensions) ; close() }
292
+ // AUTO-EXPAND categories
293
+ document.querySelectorAll('.menu-entry:has(.menu-caret)').forEach(ctgDiv => {
294
+ if (settings.categories[ctgDiv.id]?.autoExpand)
295
+ toggleCategorySettingsVisiblity({ key: ctgDiv.id, transitions: false })
296
+ })
169
297
 
170
- // Remove LOADING SPINNER after imgs load
298
+ // REMOVE LOADING spinner after imgs load
171
299
  Promise.all([...document.querySelectorAll('img')].map(img =>
172
300
  img.complete ? Promise.resolve() : new Promise(resolve => img.onload = resolve)
173
- )).then(() => document.querySelectorAll('[class^=loading]').forEach(elem => elem.remove()))
301
+ )).then(() => {
302
+ document.querySelectorAll('[class^=loading]').forEach(elem => elem.remove())
303
+ sync.fade() // based on master toggle state
304
+ })
174
305
 
175
306
  })()
@@ -10,16 +10,16 @@
10
10
  </div>
11
11
  <header>
12
12
  <div class="logo">
13
- <img src="https://cdn.jsdelivr.net/gh/KudoAI/chatgpt.js@f0cdfc9/starters/chrome/extension/icons/icon32.png"
13
+ <img src="data:image/svg+xml,%3csvg%20xmlns='http://www.w3.org/2000/svg'%20width='180'%20height='180'%20fill='none'%3e%3cstyle%3e%20:root%20{%20--primary-fill:%20%23000;%20--secondary-fill:%20%23fff;%20}%20@media%20(prefers-color-scheme:%20dark)%20{%20:root%20{%20--primary-fill:%20%23fff;%20--secondary-fill:%20%23000;%20}%20}%20%3c/style%3e%3cg%20clip-path='url(%23a)'%3e%3crect%20width='180'%20height='180'%20fill='var(--primary-fill)'%20rx='90'%20/%3e%3cg%20clip-path='url(%23b)'%3e%3cpath%20fill='var(--secondary-fill)'%20d='M75.91%2073.628V62.232c0-.96.36-1.68%201.199-2.16l22.912-13.194c3.119-1.8%206.838-2.639%2010.676-2.639%2014.394%200%2023.511%2011.157%2023.511%2023.032%200%20.839%200%201.799-.12%202.758l-23.752-13.914c-1.439-.84-2.879-.84-4.318%200L75.91%2073.627Zm53.499%2044.383v-27.23c0-1.68-.72-2.88-2.159-3.719L97.142%2069.55l9.836-5.638c.839-.48%201.559-.48%202.399%200l22.912%2013.195c6.598%203.839%2011.035%2011.995%2011.035%2019.912%200%209.116-5.397%2017.513-13.915%2020.992v.001Zm-60.577-23.99-9.836-5.758c-.84-.48-1.2-1.2-1.2-2.16v-26.39c0-12.834%209.837-22.55%2023.152-22.55%205.039%200%209.716%201.679%2013.676%204.678L70.993%2055.516c-1.44.84-2.16%202.039-2.16%203.719v34.787-.002Zm21.173%2012.234L75.91%2098.339V81.546l14.095-7.917%2014.094%207.917v16.793l-14.094%207.916Zm9.056%2036.467c-5.038%200-9.716-1.68-13.675-4.678l23.631-13.676c1.439-.839%202.159-2.038%202.159-3.718V85.863l9.956%205.757c.84.48%201.2%201.2%201.2%202.16v26.389c0%2012.835-9.957%2022.552-23.27%2022.552v.001Zm-28.43-26.75L47.72%20102.778c-6.599-3.84-11.036-11.996-11.036-19.913%200-9.236%205.518-17.513%2014.034-20.992v27.35c0%201.68.72%202.879%202.16%203.718l29.989%2017.393-9.837%205.638c-.84.48-1.56.48-2.399%200Zm-1.318%2019.673c-13.555%200-23.512-10.196-23.512-22.792%200-.959.12-1.919.24-2.879l23.63%2013.675c1.44.84%202.88.84%204.32%200l30.108-17.392v11.395c0%20.96-.361%201.68-1.2%202.16l-22.912%2013.194c-3.119%201.8-6.837%202.639-10.675%202.639Zm29.748%2014.274c14.515%200%2026.63-10.316%2029.39-23.991%2013.434-3.479%2022.071-16.074%2022.071-28.91%200-8.396-3.598-16.553-10.076-22.43.6-2.52.96-5.039.96-7.557%200-17.153-13.915-29.99-29.989-29.99-3.239%200-6.358.48-9.477%201.56-5.398-5.278-12.835-8.637-20.992-8.637-14.515%200-26.63%2010.316-29.39%2023.991-13.434%203.48-22.07%2016.074-22.07%2028.91%200%208.396%203.598%2016.553%2010.075%2022.431-.6%202.519-.96%205.038-.96%207.556%200%2017.154%2013.915%2029.989%2029.99%2029.989%203.238%200%206.357-.479%209.476-1.559%205.397%205.278%2012.835%208.637%2020.992%208.637Z'%20/%3e%3c/g%3e%3c/g%3e%3cdefs%3e%3cclipPath%20id='a'%3e%3cpath%20d='M0%200h180v180H0z'%20/%3e%3c/clipPath%3e%3cclipPath%20id='b'%3e%3cpath%20d='M29.487%2029.964h121.035v119.954H29.487z'%20/%3e%3c/clipPath%3e%3c/defs%3e%3c/svg%3e"
14
14
  width=26 alt="">
15
15
  </div>
16
16
  <div class="menu-title">ChatGPT Extension</div>
17
17
  <div class="master-toggle"></div>
18
18
  </header>
19
19
  <footer>
20
- <span><img class="cjs-logo"></span>
20
+ <span><img class="chatgptjs-logo"></span>
21
21
  <span class="about-span menu-icon highlight-on-hover"></span>
22
- <span class="more-extensions-span menu-icon highlight-on-hover"></span>
22
+ <span class="more-ext-span menu-icon highlight-on-hover"></span>
23
23
  </footer>
24
24
  <script src="controller.js"></script>
25
25
  </body>
@@ -1,20 +1,25 @@
1
1
  /* General */
2
+ :root {
3
+ --entry-highlighted-bg: rgb(100,149,237) ;
4
+ --track-filled-color: #000 ; --track-empty-color: #e0e0e0 ; --thumb-color: #000 ; --thumb-border: #fff
5
+ }
2
6
  html { height: fit-content ; min-height: 89px }
3
7
  body {
4
- width: max-content ; margin: 0 ; font-size: .905rem ;
8
+ width: 268px ; margin: 0 ; font-size: .905rem ;
5
9
  font-family: -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", Roboto,
6
- "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Helvetica Neue", sans-serif
10
+ "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Helvetica Neue", sans-serif ;
11
+ background-image: linear-gradient(180deg, #b6ebff -163px, white 65px)
7
12
  }
8
13
 
9
14
  /* Color/fade mods */
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) }
13
- .disabled { opacity: 0.3 ; pointer-events: none }
15
+ .highlight-on-hover:hover { background: var(--entry-highlighted-bg) }
16
+ .highlight-on-hover:hover span:not(.track), .highlight-on-hover:hover svg, span.menu-icon.highlight-on-hover:hover img {
17
+ filter: invert(1) } /* invert setting labels on hover */
18
+ .disabled { opacity: 0.3 !important ; pointer-events: none }
14
19
 
15
20
  /* Loader */
16
21
  .loading-bg {
17
- background-color: white ; width: 100% ; height: 100% ; position: absolute ; z-index: 1111 ;
22
+ background: white ; width: 100% ; height: 100% ; position: absolute ; z-index: 1111 ;
18
23
  display: inline-grid ; align-content: center ; justify-content: center /* center spinner */
19
24
  }
20
25
  .loading-spinner {
@@ -22,13 +27,13 @@ body {
22
27
  animation: loader-move-head-tail 0.8s infinite linear alternate, loader-rotate 1.6s infinite linear
23
28
  }
24
29
  @keyframes loader-move-head-tail {
25
- 0% { clip-path: polygon(50% 50%, 0 0, 50% 0, 50% 0, 50% 0, 50% 0, 50% 0) }
26
- 12.5% { clip-path: polygon(50% 50%, 0 0, 50% 0, 100% 0, 100% 0, 100% 0, 100% 0) }
27
- 25% { clip-path: polygon(50% 50%, 0 0, 50% 0, 100% 0, 100% 100%, 100% 100%, 100% 100%) }
28
- 50% { clip-path: polygon(50% 50%, 0 0, 50% 0, 100% 0, 100% 100%, 50% 100%, 0 100%) }
29
- 62.5% { clip-path: polygon(50% 50%, 100% 0, 100% 0%, 100% 0, 100% 100%, 50% 100%, 0 100%) }
30
- 75% { clip-path: polygon(50% 50%, 100% 100%, 100% 100%, 100% 100%, 100% 100%, 50% 100%, 0 100%) }
31
- 100% { clip-path: polygon(50% 50%, 50% 100%, 50% 100%, 50% 100%, 50% 100%, 50% 100%, 0 100%) }
30
+ 0% { clip-path: polygon(50% 50%, 0 0, 50% 0, 50% 0, 50% 0, 50% 0, 50% 0) }
31
+ 12.5% { clip-path: polygon(50% 50%, 0 0, 50% 0, 100% 0, 100% 0, 100% 0, 100% 0) }
32
+ 25% { clip-path: polygon(50% 50%, 0 0, 50% 0, 100% 0, 100% 100%, 100% 100%, 100% 100%) }
33
+ 50% { clip-path: polygon(50% 50%, 0 0, 50% 0, 100% 0, 100% 100%, 50% 100%, 0 100%) }
34
+ 62.5% { clip-path: polygon(50% 50%, 100% 0, 100% 0%, 100% 0, 100% 100%, 50% 100%, 0 100%) }
35
+ 75% { clip-path: polygon(50% 50%, 100% 100%, 100% 100%, 100% 100%, 100% 100%, 50% 100%, 0 100%) }
36
+ 100% { clip-path: polygon(50% 50%, 50% 100%, 50% 100%, 50% 100%, 50% 100%, 50% 100%, 0 100%) }
32
37
  }
33
38
  @keyframes loader-rotate {
34
39
  0% { transform: scaleY(1) rotate(0deg) } 49.99% { transform: scaleY(1) rotate(135deg) }
@@ -38,8 +43,8 @@ body {
38
43
  /* Header */
39
44
  header {
40
45
  border-bottom: solid 1px lightgrey ; padding: 5px 5px 5px 0 ; margin: 0 ;
41
- min-height: 38px ; display: flex; background: white ; align-items: center }
42
- .logo { margin: 4px 8px 4px 12px ; position: relative ; top: 3px }
46
+ height: 38px ; display: flex ; align-items: center }
47
+ .logo { margin: 0 8px 0 12px ; position: relative ; top: 3px }
43
48
  .menu-title { font-size: 0.85rem ; font-weight: 600 ; padding-right: 3px }
44
49
  .master-toggle { margin-left: auto ; display: flex }
45
50
 
@@ -48,14 +53,37 @@ header {
48
53
  position: relative ; align-items: center ; border-bottom: 1px solid lightgrey ;
49
54
  display: flex ; min-height: 2rem ; padding: 0 14px 0 2px ; white-space: nowrap ; font-size: 91%
50
55
  }
51
- .menu-icon { padding: 8px }
56
+ .menu-icon { padding: 8px ; justify-content: center }
52
57
  .menu-entry > label > .track { transform: scale(0.95) ; top: 1px } /* make child toggles smaller */
58
+ .menu-right-elem { display: flex ; height: 33px ; align-items: center ; padding: 0 11.5px ; margin-right: -14px }
53
59
  .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%
60
+ .menu-right-elem, .menu-caret { filter: drop-shadow(5px 5px 5px rgba(0, 0, 0, 0.1)) }
61
+ .categorized-entries { /* add left-stripe + bg */
62
+ border-left: 5px solid transparent ; height: 0 ;
63
+ overflow: hidden ; /* for y-pos calc */
64
+ border-image: linear-gradient(transparent, rgb(161,161,161)) 30 100% ;
65
+ --border-transition: border-image 0.35s ease-in
66
+ }
67
+ .anchored { pointer-events: none } /* disable collapse */
68
+ .expanded > span { font-weight: 600 ; transform: scale(1.05) ; margin-left: 5px }
69
+
70
+ /* Slider */
71
+ div.menu-entry > input.slider {
72
+ --track-fill-percent: 100% ; width: 100% ; margin: -2px -5px 7px 8px ; padding: 8.5px 3px ; cursor: pointer ;
73
+ -webkit-appearance: none ; appearance: none ; background: none
74
+ }
75
+ .slider::-webkit-slider-runnable-track {
76
+ height: 5px ; border-radius: 10px ;
77
+ background: linear-gradient(to right,
78
+ var(--track-filled-color) 0%, var(--track-filled-color) var(--track-fill-percent),
79
+ var(--track-empty-color) var(--track-fill-percent), var(--track-empty-color) 100%
80
+ )
81
+ }
82
+ .slider::-webkit-slider-thumb {
83
+ -webkit-appearance: none ; width: 12px ; height: 26.5px ; background: var(--thumb-color) ; margin-top: -10.5px ;
84
+ border: 4px solid var(--thumb-border) ; border-radius: 16px ; cursor: ew-resize ; transition: transform 0.05s ease
57
85
  }
58
- .expanded > span { font-weight: 500 ; transform: scale(1.05) ; margin-left: 2px }
86
+ .slider::-webkit-slider-thumb:hover { transform: scaleX(1.325) }
59
87
 
60
88
  /* Toggle elements */
61
89
  .toggle .track {
@@ -68,19 +96,54 @@ header {
68
96
  border: 1px solid black ; border-radius: 50% ; background: white ; transition: transform 0.08s ease-in-out
69
97
  }
70
98
  .toggle.on .track::before { transform: translateX(9px) ; transition: transform 0.15s ease-in-out }
99
+
100
+ /* About entry */
101
+ div#about > div.menu-icon { padding: 8px 10px 8px 11px }
102
+ .ticker {
103
+ align-content: center ; overflow: hidden ; font-size: 10px ; width: 105px ;
104
+ --mask: linear-gradient(to right, transparent, black 20%, black 89%, transparent) ;
105
+ mask-image: var(--mask) ; -webkit-mask-image: var(--mask) /* eslint-disable-line */
106
+ }
107
+ .ticker-em { color: green } .highlight-on-hover:hover .ticker-em { color: #28ee28 }
108
+ .ticker > div { animation: ticker linear 75s infinite }
109
+ @keyframes ticker { 0% { transform: translateX(100%) } 100% { transform: translateX(-2000%) }}
71
110
 
72
111
  /* Footer */
73
112
  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 }
113
+ .chatgptjs-logo { opacity: 0.5 } .chatgptjs-logo:hover { opacity: 1 ; transition: 0.25s }
75
114
  footer > .menu-icon {
76
115
  display: flex ; opacity: 0.7 ; align-items: center ; padding: 8px 5px ;
77
116
  box-sizing: border-box ; height: 96% ; width: 26px
78
117
  }
79
118
  footer img[src*=question-mark] { position: relative ; top: 1px }
80
119
 
120
+ /* Dark styles */
121
+ html.dark {
122
+ --entry-highlighted-bg: white ;
123
+ --track-filled-color: white ; --track-empty-color: #b2b2b2 ; --thumb-color: white ; --thumb-border: black
124
+ }
125
+ html.dark .loading-bg { background: black } html.dark .loading-spinner { border-color: white }
126
+ html.dark body { background-image: linear-gradient(180deg, #99a8a6 -245px, black 185px) }
127
+ html.dark, html.dark header, html.dark footer { color: white } html.dark footer { background: none }
128
+ html.dark div.categorized-entries { border-image: linear-gradient(transparent, rgba(161,161,161,0.5)) 30 100% / 100 }
129
+ html.dark .toggle .track {
130
+ background: var(--track-empty-color) ; border-color: black ; height: 8px ; width: 20px ; transition: none }
131
+ html.dark .toggle.on .track { background: var(--track-filled-color) }
132
+ html.dark .toggle .track::before { top: -3px ; left: -2px ; border: 2px solid black } /* toggle knob */
133
+ html.dark .master-toggle .toggle .track::before { border-color: #444343 } /* master toggle knob */
134
+ html.dark .master-toggle:hover .toggle .track { background: #444343 }
135
+ html.dark .toggle.on .track::before { transform: translateX(11px) }
136
+ html.dark .highlight-on-hover:hover .track { background: black }
137
+ html.dark .menu-icon, html.dark .chatgptjs-logo { opacity: 1 }
138
+ html.dark .menu-icon svg, html.dark .menu-right-elem, html.dark .menu-right-elem > svg { fill: white }
139
+ html.dark .menu-icon img { filter: brightness(0) invert(1) }
140
+ html.dark .menu-icon:hover img { filter: none }
141
+ html.dark .ticker-em { color: #28ee28 } html.dark .highlight-on-hover:hover .ticker-em { color: green }
142
+
81
143
  /* Non-baseline features */
82
- @supports (cursor: pointer) { .highlight-on-hover:hover, .toggle .track, .cjs-logo { cursor: pointer }}
144
+ @supports (cursor: pointer) { .highlight-on-hover:hover, .toggle .track, .chatgptjs-logo { cursor: pointer }}
83
145
  @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 }}
146
+ @supports selector(:has(> input.slider)) { div.menu-entry:has(> input.slider) { flex-wrap: wrap ; padding-top: 2px }}
147
+ @supports selector(:has(> .chatgptjs-logo)) {
148
+ footer > span:has(> .chatgptjs-logo) { display: flex ; align-items: center ; flex-grow: 1 ; padding-left: 2px }}
86
149
  @supports (user-select: none) { body, button, input, select, textarea { user-select: none }}
@@ -6,8 +6,7 @@ chrome.storage.local.set({ 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@latest',
10
- chatgptJS: 'https://chatgptjs.org',
9
+ chatgptjs: 'https://chatgptjs.org',
11
10
  contributors: 'https://docs.chatgptjs.org/#-contributors',
12
11
  gitHub: 'https://github.com/KudoAI/chatgpt.js-chrome-starter',
13
12
  relatedExtensions: 'https://aiwebextensions.com',
@@ -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.5.11
6
+ // @version 2025.7.28
7
7
  // @license MIT
8
- // @icon https://cdn.jsdelivr.net/gh/KudoAI/chatgpt.js@1fc50da/starters/greasemonkey/assets/images/icons/robot/icon48.png
9
- // @icon64 https://cdn.jsdelivr.net/gh/KudoAI/chatgpt.js@1fc50da/starters/greasemonkey/assets/images/icons/robot/icon64.png
8
+ // @icon data:image/svg+xml,%3csvg%20xmlns='http://www.w3.org/2000/svg'%20width='180'%20height='180'%20fill='none'%3e%3cstyle%3e%20:root%20{%20--primary-fill:%20%23000;%20--secondary-fill:%20%23fff;%20}%20@media%20(prefers-color-scheme:%20dark)%20{%20:root%20{%20--primary-fill:%20%23fff;%20--secondary-fill:%20%23000;%20}%20}%20%3c/style%3e%3cg%20clip-path='url(%23a)'%3e%3crect%20width='180'%20height='180'%20fill='var(--primary-fill)'%20rx='90'%20/%3e%3cg%20clip-path='url(%23b)'%3e%3cpath%20fill='var(--secondary-fill)'%20d='M75.91%2073.628V62.232c0-.96.36-1.68%201.199-2.16l22.912-13.194c3.119-1.8%206.838-2.639%2010.676-2.639%2014.394%200%2023.511%2011.157%2023.511%2023.032%200%20.839%200%201.799-.12%202.758l-23.752-13.914c-1.439-.84-2.879-.84-4.318%200L75.91%2073.627Zm53.499%2044.383v-27.23c0-1.68-.72-2.88-2.159-3.719L97.142%2069.55l9.836-5.638c.839-.48%201.559-.48%202.399%200l22.912%2013.195c6.598%203.839%2011.035%2011.995%2011.035%2019.912%200%209.116-5.397%2017.513-13.915%2020.992v.001Zm-60.577-23.99-9.836-5.758c-.84-.48-1.2-1.2-1.2-2.16v-26.39c0-12.834%209.837-22.55%2023.152-22.55%205.039%200%209.716%201.679%2013.676%204.678L70.993%2055.516c-1.44.84-2.16%202.039-2.16%203.719v34.787-.002Zm21.173%2012.234L75.91%2098.339V81.546l14.095-7.917%2014.094%207.917v16.793l-14.094%207.916Zm9.056%2036.467c-5.038%200-9.716-1.68-13.675-4.678l23.631-13.676c1.439-.839%202.159-2.038%202.159-3.718V85.863l9.956%205.757c.84.48%201.2%201.2%201.2%202.16v26.389c0%2012.835-9.957%2022.552-23.27%2022.552v.001Zm-28.43-26.75L47.72%20102.778c-6.599-3.84-11.036-11.996-11.036-19.913%200-9.236%205.518-17.513%2014.034-20.992v27.35c0%201.68.72%202.879%202.16%203.718l29.989%2017.393-9.837%205.638c-.84.48-1.56.48-2.399%200Zm-1.318%2019.673c-13.555%200-23.512-10.196-23.512-22.792%200-.959.12-1.919.24-2.879l23.63%2013.675c1.44.84%202.88.84%204.32%200l30.108-17.392v11.395c0%20.96-.361%201.68-1.2%202.16l-22.912%2013.194c-3.119%201.8-6.837%202.639-10.675%202.639Zm29.748%2014.274c14.515%200%2026.63-10.316%2029.39-23.991%2013.434-3.479%2022.071-16.074%2022.071-28.91%200-8.396-3.598-16.553-10.076-22.43.6-2.52.96-5.039.96-7.557%200-17.153-13.915-29.99-29.989-29.99-3.239%200-6.358.48-9.477%201.56-5.398-5.278-12.835-8.637-20.992-8.637-14.515%200-26.63%2010.316-29.39%2023.991-13.434%203.48-22.07%2016.074-22.07%2028.91%200%208.396%203.598%2016.553%2010.075%2022.431-.6%202.519-.96%205.038-.96%207.556%200%2017.154%2013.915%2029.989%2029.99%2029.989%203.238%200%206.357-.479%209.476-1.559%205.397%205.278%2012.835%208.637%2020.992%208.637Z'%20/%3e%3c/g%3e%3c/g%3e%3cdefs%3e%3cclipPath%20id='a'%3e%3cpath%20d='M0%200h180v180H0z'%20/%3e%3c/clipPath%3e%3cclipPath%20id='b'%3e%3cpath%20d='M29.487%2029.964h121.035v119.954H29.487z'%20/%3e%3c/clipPath%3e%3c/defs%3e%3c/svg%3e
9
+ // @icon64 data:image/svg+xml,%3csvg%20xmlns='http://www.w3.org/2000/svg'%20width='180'%20height='180'%20fill='none'%3e%3cstyle%3e%20:root%20{%20--primary-fill:%20%23000;%20--secondary-fill:%20%23fff;%20}%20@media%20(prefers-color-scheme:%20dark)%20{%20:root%20{%20--primary-fill:%20%23fff;%20--secondary-fill:%20%23000;%20}%20}%20%3c/style%3e%3cg%20clip-path='url(%23a)'%3e%3crect%20width='180'%20height='180'%20fill='var(--primary-fill)'%20rx='90'%20/%3e%3cg%20clip-path='url(%23b)'%3e%3cpath%20fill='var(--secondary-fill)'%20d='M75.91%2073.628V62.232c0-.96.36-1.68%201.199-2.16l22.912-13.194c3.119-1.8%206.838-2.639%2010.676-2.639%2014.394%200%2023.511%2011.157%2023.511%2023.032%200%20.839%200%201.799-.12%202.758l-23.752-13.914c-1.439-.84-2.879-.84-4.318%200L75.91%2073.627Zm53.499%2044.383v-27.23c0-1.68-.72-2.88-2.159-3.719L97.142%2069.55l9.836-5.638c.839-.48%201.559-.48%202.399%200l22.912%2013.195c6.598%203.839%2011.035%2011.995%2011.035%2019.912%200%209.116-5.397%2017.513-13.915%2020.992v.001Zm-60.577-23.99-9.836-5.758c-.84-.48-1.2-1.2-1.2-2.16v-26.39c0-12.834%209.837-22.55%2023.152-22.55%205.039%200%209.716%201.679%2013.676%204.678L70.993%2055.516c-1.44.84-2.16%202.039-2.16%203.719v34.787-.002Zm21.173%2012.234L75.91%2098.339V81.546l14.095-7.917%2014.094%207.917v16.793l-14.094%207.916Zm9.056%2036.467c-5.038%200-9.716-1.68-13.675-4.678l23.631-13.676c1.439-.839%202.159-2.038%202.159-3.718V85.863l9.956%205.757c.84.48%201.2%201.2%201.2%202.16v26.389c0%2012.835-9.957%2022.552-23.27%2022.552v.001Zm-28.43-26.75L47.72%20102.778c-6.599-3.84-11.036-11.996-11.036-19.913%200-9.236%205.518-17.513%2014.034-20.992v27.35c0%201.68.72%202.879%202.16%203.718l29.989%2017.393-9.837%205.638c-.84.48-1.56.48-2.399%200Zm-1.318%2019.673c-13.555%200-23.512-10.196-23.512-22.792%200-.959.12-1.919.24-2.879l23.63%2013.675c1.44.84%202.88.84%204.32%200l30.108-17.392v11.395c0%20.96-.361%201.68-1.2%202.16l-22.912%2013.194c-3.119%201.8-6.837%202.639-10.675%202.639Zm29.748%2014.274c14.515%200%2026.63-10.316%2029.39-23.991%2013.434-3.479%2022.071-16.074%2022.071-28.91%200-8.396-3.598-16.553-10.076-22.43.6-2.52.96-5.039.96-7.557%200-17.153-13.915-29.99-29.989-29.99-3.239%200-6.358.48-9.477%201.56-5.398-5.278-12.835-8.637-20.992-8.637-14.515%200-26.63%2010.316-29.39%2023.991-13.434%203.48-22.07%2016.074-22.07%2028.91%200%208.396%203.598%2016.553%2010.075%2022.431-.6%202.519-.96%205.038-.96%207.556%200%2017.154%2013.915%2029.989%2029.99%2029.989%203.238%200%206.357-.479%209.476-1.559%205.397%205.278%2012.835%208.637%2020.992%208.637Z'%20/%3e%3c/g%3e%3c/g%3e%3cdefs%3e%3cclipPath%20id='a'%3e%3cpath%20d='M0%200h180v180H0z'%20/%3e%3c/clipPath%3e%3cclipPath%20id='b'%3e%3cpath%20d='M29.487%2029.964h121.035v119.954H29.487z'%20/%3e%3c/clipPath%3e%3c/defs%3e%3c/svg%3e
10
10
  // @match *://chatgpt.com/*
11
- // @require https://cdn.jsdelivr.net/npm/@kudoai/chatgpt.js@3.8.1/dist/chatgpt.min.js
11
+ // @require https://cdn.jsdelivr.net/npm/@kudoai/chatgpt.js@3.8.2/dist/chatgpt.min.js
12
12
  // @grant GM_getValue
13
13
  // @grant GM_setValue
14
14
  // @noframes