@kudoai/chatgpt.js 3.8.0 → 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,55 +1,132 @@
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))
6
10
 
7
11
  // Init ENV context
8
- const env = {
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
14
- const { app } = await chrome.storage.local.get('app')
15
- icons.import({ app }) // for src's using app.urls.assetHost
19
+ ;({ app: window.app } = await chrome.storage.local.get('app'))
16
20
 
17
21
  // Define FUNCTIONS
18
22
 
19
23
  function createMenuEntry(entryData) {
24
+
25
+ // Assemble elems
20
26
  const entry = {
21
27
  div: dom.create.elem('div', {
22
28
  id: entryData.key, class: 'menu-entry highlight-on-hover', title: entryData.helptip || '' }),
23
- leftElem: dom.create.elem('div', { class: `menu-icon ${ entryData.type || '' }` }),
29
+ leftElem: dom.create.elem('div', { class: `menu-icon ${ entryData.type || '' }`}),
24
30
  label: dom.create.elem('span')
25
31
  }
26
32
  entry.label.textContent = entryData.label
33
+ entry.div.append(entry.leftElem, entry.label)
27
34
  if (entryData.type == 'toggle') { // add track to left, init knob pos
28
35
  entry.leftElem.append(dom.create.elem('span', { class: 'track' }))
29
36
  entry.leftElem.classList.toggle('on', settings.typeIsEnabled(entryData.key))
30
37
  } else { // add symbol to left, append status to right
31
- entry.leftElem.innerText = entryData.symbol || '⚙️'
38
+ entry.leftElem.textContent = entryData.symbol || '⚙️' ; entry.label.style.flexGrow = 1
32
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')
33
64
  }
34
- if (entryData.type == 'category') entry.div.append(icons.create('caretDown', { size: 11, class: 'menu-caret' }))
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
+ })
71
+ }
72
+
73
+ // Add click listener
35
74
  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()}`)
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)
42
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
+ }
43
115
  }
44
- entry.div.append(entry.leftElem, entry.label)
116
+
45
117
  return entry.div
46
118
  }
47
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
+
48
125
  function notify(msg, pos = 'bottom-right') { sendMsgToActiveTab('notify', { msg, pos }) }
49
126
 
50
127
  async function sendMsgToActiveTab(action, options) {
51
- const [activeTab] = await chrome.tabs.query({ active: true, currentWindow: true })
52
- return await chrome.tabs.sendMessage(activeTab.id, { action: action, options: { ...options }})
128
+ const activeTabID = (await chrome.tabs.query({ active: true, currentWindow: true }))[0].id
129
+ return await chrome.tabs.sendMessage(activeTabID, { action, options })
53
130
  }
54
131
 
55
132
  const sync = {
@@ -62,49 +139,70 @@
62
139
  ))})
63
140
 
64
141
  // Menu elems
65
- document.querySelectorAll('.logo, .menu-title, .menu-entry').forEach((elem, idx) => {
66
- elem.style.transition = config.extensionDisabled ? '' : 'opacity 0.15s ease-in'
67
- setTimeout(() => elem.classList.toggle('disabled', config.extensionDisabled),
68
- config.extensionDisabled ? 0 : idx *10) // fade-out abruptly, fade-in staggered
69
- })
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
+ })
70
158
  },
71
159
 
72
160
  configToUI(options) { return sendMsgToActiveTab('syncConfigToUI', options) }
73
161
  }
74
162
 
75
- function toggleCategorySettingsVisiblity(category, { transitions = true, action } = {}) {
163
+ function toggleCategorySettingsVisiblity({ key, transitions = true, action }) {
76
164
  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`,
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
- Object.assign(catChildrenDiv.style, { height: 0, transition: '' })
185
+ ctgDiv.classList.toggle('expanded', false)
186
+ Object.assign(ctgChildrenDiv.style, { height: 0, transition: '' })
97
187
  Object.assign(caret.style, { transform: 'rotate(-90deg)', transition: '' }) // point it right
98
188
  }
99
189
  }
100
190
 
101
191
  // Run MAIN routine
102
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
+
103
201
  // Init MASTER TOGGLE
104
202
  const masterToggle = {
105
203
  div: document.querySelector('.master-toggle'),
106
- switch: dom.create.elem('div', { class: 'toggle menu-icon highlight-on-hover' }),
107
- 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' })
108
206
  }
109
207
  masterToggle.div.append(masterToggle.switch) ; masterToggle.switch.append(masterToggle.track)
110
208
  await settings.load('extensionDisabled') ; masterToggle.switch.classList.toggle('on', !config.extensionDisabled)
@@ -124,7 +222,7 @@
124
222
  // Group controls by category
125
223
  const categorizedCtrls = {}
126
224
  Object.entries(settings.controls).forEach(([key, ctrl]) =>
127
- ( categorizedCtrls[ctrl.category || 'general'] ??= {} )[key] = { ...ctrl, key: key })
225
+ ( categorizedCtrls[ctrl.category || 'general'] ??= {} )[key] = { ...ctrl, key })
128
226
 
129
227
  // Create/append general controls
130
228
  Object.values(categorizedCtrls.general || {}).forEach(ctrl => menuEntriesDiv.append(createMenuEntry(ctrl)))
@@ -132,43 +230,77 @@
132
230
  // Create/append categorized controls
133
231
  Object.entries(categorizedCtrls).forEach(([category, ctrls]) => {
134
232
  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)))
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)))
141
242
  })
142
243
  }
143
244
 
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
- })
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() }
149
262
 
150
- 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 }))
151
268
 
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() }
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
+ }))
156
275
 
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 }))
161
- 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() }
162
291
 
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'))
167
- 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
+ })
168
297
 
169
- // Remove LOADING SPINNER after imgs load
298
+ // REMOVE LOADING spinner after imgs load
170
299
  Promise.all([...document.querySelectorAll('img')].map(img =>
171
300
  img.complete ? Promise.resolve() : new Promise(resolve => img.onload = resolve)
172
- )).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
+ })
173
305
 
174
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,13 +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
  }
86
+ .slider::-webkit-slider-thumb:hover { transform: scaleX(1.325) }
58
87
 
59
88
  /* Toggle elements */
60
89
  .toggle .track {
@@ -67,18 +96,54 @@ header {
67
96
  border: 1px solid black ; border-radius: 50% ; background: white ; transition: transform 0.08s ease-in-out
68
97
  }
69
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%) }}
70
110
 
71
111
  /* Footer */
72
112
  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 }
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 */
144
+ @supports (cursor: pointer) { .highlight-on-hover:hover, .toggle .track, .chatgptjs-logo { cursor: pointer }}
82
145
  @supports (overflow: clip) { body { overflow: clip }}
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 }}
83
149
  @supports (user-select: none) { body, button, input, select, textarea { user-select: none }}
84
- @supports (cursor: pointer) { .highlight-on-hover:hover, .toggle .track, .cjs-logo { cursor: pointer }}
@@ -1,20 +1,18 @@
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@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',
14
13
  support: 'https://github.com/KudoAI/chatgpt.js-chrome-starter/issues'
15
14
  }
16
- }
17
- chrome.storage.local.set({ app }) // save to Chrome storage
15
+ }}) // save to Chrome storage
18
16
 
19
17
  // Launch CHATGPT on install
20
18
  chrome.runtime.onInstalled.addListener(details => {
@@ -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.4.28
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.0/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