@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,33 +1,40 @@
1
- // Requires lib/dom.js + app
1
+ // Requires lib/dom.js
2
2
 
3
3
  window.icons = {
4
4
 
5
- create(name, { size = 16, width, height, ...additionalAttrs } = {}) {
6
- const iconData = icons[name],
7
- iconAttrs = { width: width || size, height: height || size, ...additionalAttrs }
8
- if (iconData.type == 'svg') {
9
- const svg = dom.create.svgElem('svg', { viewBox: iconData.viewBox, ...iconAttrs })
10
- iconData.elems.forEach(([tag, attrs]) => svg.append(dom.create.svgElem(tag, attrs)))
11
- return svg
12
- } else // img w/ src
13
- return dom.create.elem('img', { src: iconData.src, ...iconAttrs })
5
+ create({ key, size = 18, width, height, ...otherAttrs }) {
6
+ if (!key) return console.error('Option \'key\' required by icons.create()')
7
+ const icon = {
8
+ data: this[key], attrs: { width: width || size, height: height || size, class: key, ...otherAttrs }}
9
+ if (icon.data?.svg) { // return <svg>
10
+ icon.svg = dom.create.svgElem('svg', { ...icon.data.svg, ...icon.attrs })
11
+ ;(function create(elems) {
12
+ return elems.map(elem => {
13
+ const [tag, attrs] = Object.entries(elem)[0], svgElem = dom.create.svgElem(tag, attrs)
14
+ if (attrs.elems) svgElem.append(...create(attrs.elems)) // recursively create() sub-elems
15
+ return svgElem
16
+ })
17
+ })(icon.data.elems).forEach(elem => icon.svg.append(elem))
18
+ return icon.svg
19
+ } else if (icon.data?.src) // return <img> w/ src
20
+ return dom.create.elem('img', { src: icon.data.src, ...icon.attrs })
21
+ else
22
+ return console.error(`No <svg|src> data found for key ${key}`)
14
23
  },
15
24
 
16
- caretDown: {
17
- type: 'svg', viewBox: '0 0 24 24',
18
- elems: [[ 'path', { d: 'm0 6.4 12 12 12-12z' }]]
25
+ caretDown: { svg: { viewBox: '0 0 24 24' }, elems: [{ path: { d: 'm0 6.4 12 12 12-12z' }}]},
26
+
27
+ open: {
28
+ svg: { viewBox: '0 0 512 512' },
29
+ elems: [{ path: { transform: 'translate(85.333333, 64)',
30
+ d: 'M128,63.999444 L128,106.666444 L42.6666667,106.666667 L42.6666667,320 L256,320 L256,234.666444 L298.666,234.666444 L298.666667,362.666667 L4.26325641e-14,362.666667 L4.26325641e-14,64 L128,63.999444 Z M362.666667,1.42108547e-14 L362.666667,170.666667 L320,170.666667 L320,72.835 L143.084945,249.751611 L112.915055,219.581722 L289.83,42.666 L192,42.6666667 L192,1.42108547e-14 L362.666667,1.42108547e-14 Z' }}]
19
31
  },
20
32
 
21
33
  plus: {
22
- type: 'svg', viewBox: '0 0 1024 1024',
23
- elems: [
24
- [ 'path', { d: 'M899.901 600.38H600.728v299.173c0 74.383-179.503 74.383-179.503 0V600.38H122.051c-74.384 0-74.384-179.503 0-179.503h299.173V121.703c0-74.384 179.503-74.384 179.503 0v299.174H899.9c74.385 0 74.385 179.503.001 179.503z' }]
25
- ]
34
+ svg: { viewBox: '0 0 1024 1024' },
35
+ elems: [{ path: { d: 'M899.901 600.38H600.728v299.173c0 74.383-179.503 74.383-179.503 0V600.38H122.051c-74.384 0-74.384-179.503 0-179.503h299.173V121.703c0-74.384 179.503-74.384 179.503 0v299.174H899.9c74.385 0 74.385 179.503.001 179.503z' }}]
26
36
  },
27
37
 
28
38
  questionMark: {
29
- type: 'png',
30
- get src() { return `${app.urls.assetHost.replace(
31
- '@latest', '@00e245b')}/images/icons/question-mark/icon16.png` }
32
- }
39
+ src: 'https://cdn.jsdelivr.net/gh/KudoAI/chatgpt.js-chrome-starter@8a59f80/images/icons/question-mark/icon16.png' }
33
40
  };
@@ -21,10 +21,13 @@ window.modals = {
21
21
  + `<span style="${labelStyles}">📜 Open source code:</span> `
22
22
  + `<a href="${app.urls.gitHub}" target="_blank" rel="nopener">`
23
23
  + app.urls.gitHub + '</a>\n'
24
+ + `<span style="${labelStyles}">🚀 Latest changes:</span> `
25
+ + `<a href="${app.urls.gitHub}/commits" target="_blank" rel="nopener">`
26
+ + `${app.urls.gitHub}/commits</a>\n`
24
27
  + `<span style="${labelStyles}">⚡ Powered by:</span> `
25
- + `<a href="${app.urls.chatgptJS}" target="_blank" rel="noopener">chatgpt.js</a>`,
28
+ + `<a href="${app.urls.chatgptjs}" target="_blank" rel="noopener">chatgpt.js</a>`,
26
29
  [ function getSupport(){}, function rateUs(){}, function moreAiExtensions(){} ], // button labels
27
- '', 656 // modal width
30
+ '', 747 // modal width
28
31
  )
29
32
 
30
33
  // Format text
@@ -20,7 +20,7 @@
20
20
  alert: () => modals.alert(...['title', 'msg', 'btns', 'checkbox', 'width'].map(arg => options[arg])),
21
21
  showAbout: () => { config.skipAlert = true ; chatgpt.isLoaded().then(() => modals.open('about')) },
22
22
  syncConfigToUI: () => syncConfigToUI(options)
23
- }[action]?.() || console.warn(`Received unsupported action: "${action}"`))
23
+ }[action]?.() || console.warn(`Chome msg listener warning: "${action}"`))
24
24
  })
25
25
 
26
26
  // Init SETTINGS
@@ -36,7 +36,7 @@
36
36
  if (foundState) msg = msg.replace(foundState, '')
37
37
 
38
38
  // Show notification
39
- chatgpt.notify(`${app.symbol} ${msg}`, pos, notifDuration, shadow || env.ui.scheme == 'dark' ? '' : 'shadow')
39
+ chatgpt.notify(`${app.symbol} ${msg}`, pos, notifDuration, shadow || env.ui.scheme == 'light')
40
40
  const notif = document.querySelector('.chatgpt-notif:last-child')
41
41
 
42
42
  // Append styled state word
@@ -81,7 +81,7 @@
81
81
  // Add RISING PARTICLES styles for modals
82
82
  ;['gray', 'white'].forEach(color => document.head.append(
83
83
  dom.create.elem('link', { rel: 'stylesheet',
84
- href: `https://cdn.jsdelivr.net/gh/adamlui/ai-web-extensions@727feff/assets/styles/rising-particles/dist/${
84
+ href: `https://cdn.jsdelivr.net/gh/adamlui/ai-web-extensions@71695ca/assets/styles/rising-particles/dist/${
85
85
  color}.min.css`
86
86
  })))
87
87
 
@@ -25,8 +25,8 @@ const chatgpt = {
25
25
  selectors: {
26
26
  btns: {
27
27
  continue: 'button:has(svg[class*=rotate] > path[d^="M4.47189"])',
28
- createImage: 'button[data-testid="composer-create-image"]',
29
- deepResearch: 'button[data-testid="composer-deep-research"]',
28
+ createImage: 'button[data-testid=composer-create-image]',
29
+ deepResearch: 'button[data-testid=composer-deep-research]',
30
30
  login: 'button[data-testid*=login]',
31
31
  newChat: 'a[href="/"]:has(svg),' // Pencil button (when logged in)
32
32
  + 'button:has([d^="M3.06957"])', // Cycle Arrows button (in temp chat logged out)
@@ -34,10 +34,10 @@ const chatgpt = {
34
34
  // 'Try Again' entry of model selector below msg
35
35
  + 'div[role=menuitem] div:has(svg):has(path[d^="M3.06957"])',
36
36
  scroll: 'button:has(> svg > path[d^="M12 21C11.7348"])',
37
- search: 'button[data-testid="composer-button-search"]',
38
- reason: 'button[data-testid="composer-button-reason"]',
37
+ search: 'button[data-testid=composer-button-search]',
38
+ reason: 'button[data-testid=composer-button-reason]',
39
39
  send: 'button[data-testid=send-button]',
40
- sidebar: 'button[data-testid*=sidebar-button]',
40
+ sidebar: 'div[style*=-sidebar-width] button[data-testid=close-sidebar-button], div[style*=-sidebar-rail-width] button[aria-controls=stage-slideover-sidebar]',
41
41
  stop: 'button[data-testid=stop-button]',
42
42
  upload: 'button:has(> svg > path[d^="M12 3C12.5523"])',
43
43
  voice: 'button[data-testid*=composer-speech-button]'
@@ -49,9 +49,9 @@ const chatgpt = {
49
49
  chatHistory: 'div#history',
50
50
  errors: { toast: 'div.toast-root', txt: 'div[class*=text-error]' },
51
51
  footer: 'div#thread-bottom-container > div:last-of-type > div, span.text-sm.leading-none',
52
- header: 'div#page-header, main div.sticky:first-of-type',
52
+ header: 'header#page-header',
53
53
  links: { newChat: 'nav a[href="/"]', sidebarItem: 'nav a' },
54
- sidebar: 'div.bg-token-sidebar-surface-primary',
54
+ sidebar: 'div#stage-slideover-sidebar, div.bg-token-sidebar-surface-primary',
55
55
  ssgManifest: 'script[src*="_ssgManifest.js"]'
56
56
  },
57
57
 
@@ -69,14 +69,14 @@ const chatgpt = {
69
69
  console.log('\n%c🤖 chatgpt.js personas\n',
70
70
  'font-family: sans-serif ; font-size: xxx-large ; font-weight: bold')
71
71
  for (const prompt of data) // list personas
72
- console.log(`%c${ prompt.title }`, 'font-family: monospace ; font-size: larger ;')
72
+ console.log(`%c${prompt.title}`, 'font-family: monospace ; font-size: larger ;')
73
73
  return resolve()
74
74
  }
75
75
  const selectedPrompt = data.find(obj => obj.title.toLowerCase() == persona.toLowerCase())
76
76
  if (!selectedPrompt)
77
- return reject(`🤖 chatgpt.js >> Persona '${ persona }' was not found!`)
77
+ return reject(`🤖 chatgpt.js >> Persona '${persona}' was not found!`)
78
78
  chatgpt.send(selectedPrompt.prompt, 'click')
79
- console.info(`Loading ${ persona } persona...`)
79
+ console.info(`Loading ${persona} persona...`)
80
80
  chatgpt.isIdle().then(() => console.info('Persona activated!'))
81
81
  return resolve()
82
82
  }
@@ -183,11 +183,11 @@ const chatgpt = {
183
183
  }
184
184
  modalStyle.textContent = ( // update prev/new style contents
185
185
  `.chatgpt-modal { /* vars */
186
- --transition: opacity 0.65s cubic-bezier(.165,.84,.44,1), /* for fade-in */
187
- transform 0.55s cubic-bezier(.165,.84,.44,1) ; /* for move-in */
188
- --bg-transition: background-color 0.25s ease ; /* for bg dim */
189
- --btn-transition: transform 0.1s ease-in-out, box-shadow 0.1s ease-in-out ; /* for smooth zoom */
190
- --btn-shadow: 2px 1px ${ scheme == 'dark' ? '54px #00cfff' : '30px #9cdaff' }}`
186
+ --transition: opacity 0.65s cubic-bezier(.165,.84,.44,1), /* for fade-in */
187
+ transform 0.55s cubic-bezier(.165,.84,.44,1) ; /* for move-in */
188
+ --bg-transition: background-color 0.25s ease ; /* for bg dim */
189
+ --btn-transition: transform 0.1s ease-in-out, box-shadow 0.1s ease-in-out ; /* for smooth zoom */
190
+ --btn-shadow: 2px 1px ${ scheme == 'dark' ? '54px #00cfff' : '30px #9cdaff' }}`
191
191
 
192
192
  + '.no-mobile-tap-outline { outline: none ; -webkit-tap-highlight-color: transparent }'
193
193
 
@@ -197,8 +197,8 @@ const chatgpt = {
197
197
  position: fixed ; top: 0 ; left: 0 ; width: 100% ; height: 100% ; /* expand to full view-port */
198
198
  display: flex ; justify-content: center ; align-items: center ; z-index: 9999 ; /* align */
199
199
  transition: var(--bg-transition) ; /* for bg dim */
200
- -webkit-transition: var(--bg-transition) ; -moz-transition: var(--bg-transition) ;
201
- -o-transition: var(--bg-transition) ; -ms-transition: var(--bg-transition) }`
200
+ -webkit-transition: var(--bg-transition) ; -moz-transition: var(--bg-transition) ;
201
+ -o-transition: var(--bg-transition) ; -ms-transition: var(--bg-transition) }`
202
202
 
203
203
  // Alert styles
204
204
  + `.chatgpt-modal > div {
@@ -212,13 +212,13 @@ const chatgpt = {
212
212
  border: 1px solid ${ scheme == 'dark' ? 'white' : '#b5b5b5' };
213
213
  transform: translateX(-3px) translateY(7px) ; /* offset to move-in from */
214
214
  max-width: 75vw ; word-wrap: break-word ; border-radius: 15px ;
215
- --shadow: 0 30px 60px rgba(0,0,0,0.12) ; box-shadow: var(--shadow) ;
216
- -webkit-box-shadow: var(--shadow) ; -moz-box-shadow: var(--shadow) ;
215
+ --shadow: 0 30px 60px rgba(0,0,0,0.12) ; box-shadow: var(--shadow) ;
216
+ -webkit-box-shadow: var(--shadow) ; -moz-box-shadow: var(--shadow) ;
217
217
  user-select: none ; -webkit-user-select: none ; -moz-user-select: none ;
218
- -o-user-select: none ; -ms-user-select: none ;
218
+ -o-user-select: none ; -ms-user-select: none ;
219
219
  transition: var(--transition) ; /* for fade-in + move-in */
220
- -webkit-transition: var(--transition) ; -moz-transition: var(--transition) ;
221
- -o-transition: var(--transition) ; -ms-transition: var(--transition) }
220
+ -webkit-transition: var(--transition) ; -moz-transition: var(--transition) ;
221
+ -o-transition: var(--transition) ; -ms-transition: var(--transition) }
222
222
  .chatgpt-modal h2 { font-weight: bold ; font-size: 24px ; margin-bottom: 9px }
223
223
  .chatgpt-modal a { color: ${ scheme == 'dark' ? '#00cfff' : '#1e9ebb' }}
224
224
  .chatgpt-modal a:hover { text-decoration: underline }
@@ -238,8 +238,8 @@ const chatgpt = {
238
238
  ${ isMobile ? 'margin-top: 5px ; margin-bottom: 3px ;' : '' }
239
239
  border-radius: 0 ; border: 1px solid ${ scheme == 'dark' ? 'white' : 'black' };
240
240
  transition: var(--btn-transition) ;
241
- -webkit-transition: var(--btn-transition) ; -moz-transition: var(--btn-transition) ;
242
- -o-transition: var(--btn-transition) ; -ms-transition: var(--btn-transition) }
241
+ -webkit-transition: var(--btn-transition) ; -moz-transition: var(--btn-transition) ;
242
+ -o-transition: var(--btn-transition) ; -ms-transition: var(--btn-transition) }
243
243
  .chatgpt-modal button:hover {
244
244
  transform: scale(1.055) ; color: black ;
245
245
  background-color: #${ scheme == 'dark' ? '00cfff' : '9cdaff' };
@@ -422,7 +422,7 @@ const chatgpt = {
422
422
  this.toggle.refreshFrame()
423
423
  document.removeEventListener('visibilitychange', this.toggle.beacons)
424
424
  clearTimeout(this.isActive) ; this.isActive = null
425
- console.log(`↻ ChatGPT >> [${ chatgpt.autoRefresh.nowTimeStamp()}] Auto refresh de-activated`)
425
+ console.log(`↻ ChatGPT >> [${chatgpt.autoRefresh.nowTimeStamp()}] Auto refresh de-activated`)
426
426
  } else
427
427
  console.log(`↻ ChatGPT >> [${chatgpt.autoRefresh.nowTimeStamp()}] Auto refresh already inactive!`)
428
428
  },
@@ -648,7 +648,7 @@ const chatgpt = {
648
648
  year = now.getFullYear(),
649
649
  hour = now.getHours().toString().padStart(2, '0'),
650
650
  minute = now.getMinutes().toString().padStart(2, '0')
651
- filename = `ChatGPT_${ day }-${ month }-${ year }_${ hour }-${ minute }.txt`
651
+ filename = `ChatGPT_${day}-${month}-${year}_${hour}-${minute}.txt`
652
652
 
653
653
  // Create transcript from active chat
654
654
  if (chatToGet == 'active' && /\/\w{8}-\w{4}-\w{4}-\w{4}-\w{12}$/.test(window.location.href)) {
@@ -692,13 +692,13 @@ const chatgpt = {
692
692
  }
693
693
 
694
694
  // Export transcript
695
- console.info(`Exporting transcript as ${ format.toUpperCase() }...`)
695
+ console.info(`Exporting transcript as ${format.toUpperCase()}...`)
696
696
  if (format == 'pdf') { // convert SVGs + launch PDF printer
697
697
 
698
698
  // Convert SVG icons to data URLs for proper PDF rendering
699
699
  transcript = transcript.replace(/<svg.*?<\/svg>/g, (match) => {
700
700
  const dataURL = 'data:image/svg+xml,' + encodeURIComponent(match)
701
- return `<img src="${ dataURL }">`
701
+ return `<img src="${dataURL}">`
702
702
  })
703
703
 
704
704
  // Launch PDF printer
@@ -886,7 +886,7 @@ const chatgpt = {
886
886
  (chatToGet == 'active' && !new RegExp(`\/${re_chatID.source}$`).test(location.href))) {
887
887
  chatToGet = Number.isInteger(chatToGet) ? chatToGet : 0 // preserve index, otherwise get latest
888
888
  if (chatToGet > data.length) // reject if index out-of-bounds
889
- return reject(`🤖 chatgpt.js >> Chat with index ${ chatToGet + 1 }`
889
+ return reject(`🤖 chatgpt.js >> Chat with index ${ chatToGet +1 }`
890
890
  + ` is out of bounds. Only ${data.length} chats exist!`)
891
891
  for (const detail of detailsToGet) detailsToReturn[detail] = data[chatToGet][detail]
892
892
  return resolve(detailsToReturn)
@@ -931,7 +931,7 @@ const chatgpt = {
931
931
  userMessages.sort((a, b) => a.msg.create_time - b.msg.create_time) // sort in chronological order
932
932
 
933
933
  if (parseInt(msgToGet, 10) + 1 > userMessages.length) // reject if index out of bounds
934
- return reject(`🤖 chatgpt.js >> Message/response with index ${ msgToGet + 1 }`
934
+ return reject(`🤖 chatgpt.js >> Message/response with index ${ msgToGet +1 }`
935
935
  + ` is out of bounds. Only ${userMessages.length} messages/responses exist!`)
936
936
 
937
937
  // Fill [chatGPTMessages]
@@ -939,8 +939,9 @@ const chatgpt = {
939
939
  let sub = []
940
940
  for (const key in data) {
941
941
  if (data[key].message != null && data[key].message.author.role == 'assistant'
942
- && data[key].parent == userMessage.id)
942
+ && isUserMsgAncestor(key, userMessage.id)) {
943
943
  sub.push(data[key].message)
944
+ }
944
945
  }
945
946
  sub.sort((a, b) => a.create_time - b.create_time) // sort in chronological order
946
947
  sub = sub.map(x => { // pull out msgs after sorting
@@ -973,6 +974,17 @@ const chatgpt = {
973
974
  return resolve(msgToGet == 'all' ? msgsToReturn // if 'all' passed, return array
974
975
  : msgToGet == 'latest' ? msgsToReturn[msgsToReturn.length - 1] // else if 'latest' passed, return latest
975
976
  : msgsToReturn[msgToGet] ) // else return element of array
977
+
978
+ function isUserMsgAncestor(msgID, targetUserID) {
979
+ let currentID = msgID ; const maxDepth = 10 ; let depth = 0
980
+ while (currentID && depth < maxDepth) {
981
+ const currentMsg = data[currentID]
982
+ if (!currentMsg?.message) return false
983
+ if (currentMsg.id == targetUserID) return true
984
+ currentID = currentMsg.parent ; depth++
985
+ }
986
+ return false
987
+ }
976
988
  }
977
989
  xhr.send()
978
990
  })})}
@@ -1105,7 +1117,7 @@ const chatgpt = {
1105
1117
  const validMethods = ['POST', 'GET']
1106
1118
  method = (method || '').trim().toUpperCase()
1107
1119
  if (!method || !validMethods.includes(method)) // reject if not valid method
1108
- return console.error(`Valid methods are ${ validMethods }`)
1120
+ return console.error(`Valid methods are ${validMethods}`)
1109
1121
  if (!token) return console.error('Please provide a valid access token!')
1110
1122
  if (body && typeof body != 'object') // reject if body is passed but not an object
1111
1123
  return console.error(`Invalid body data type. Got ${ typeof body }, expected object`)
@@ -1126,7 +1138,7 @@ const chatgpt = {
1126
1138
  return reject('🤖 chatgpt.js >> ' + responseData.detail.description)
1127
1139
  else if (xhr.status != 200)
1128
1140
  return reject('🤖 chatgpt.js >> Request failed. Cannot contact custom instructions endpoint.')
1129
- console.info(`Custom instructions successfully contacted with method ${ method }`)
1141
+ console.info(`Custom instructions successfully contacted with method ${method}`)
1130
1142
  return resolve(responseData || {}) // return response data no matter what the method is
1131
1143
  }
1132
1144
  xhr.send(JSON.stringify(body) || '') // if body is passed send it, else just send the request
@@ -1291,7 +1303,12 @@ const chatgpt = {
1291
1303
 
1292
1304
  minify() { chatgpt.code.minify(); },
1293
1305
 
1294
- notify(msg, position, notifDuration, shadow) {
1306
+ notify(...args) {
1307
+ const scheme = chatgpt.isDarkMode() ? 'dark' : 'light'
1308
+ let msg, position, notifDuration, shadow, toast
1309
+ if (typeof args[0] == 'object' && !Array.isArray(args[0]))
1310
+ ({ msg, position, notifDuration, shadow, toast } = args[0])
1311
+ else [msg, position, notifDuration, shadow] = args
1295
1312
  notifDuration = notifDuration ? +notifDuration : 1.75; // sec duration to maintain notification visibility
1296
1313
  const fadeDuration = 0.35, // sec duration of fade-out
1297
1314
  vpYoffset = 23, vpXoffset = 27 // px offset from viewport border
@@ -1325,7 +1342,7 @@ const chatgpt = {
1325
1342
  + (notificationDiv.isRight ? 'Right' : 'Left')
1326
1343
 
1327
1344
  // Create/append/update notification style (if missing or outdated)
1328
- const thisUpdated = 1735767823541 // timestamp of last edit for this file's `notifStyle`
1345
+ const thisUpdated = 1746996635555 // timestamp of last edit for this file's `notifStyle`
1329
1346
  let notifStyle = document.querySelector('#chatgpt-notif-style') // try to select existing style
1330
1347
  if (!notifStyle || parseInt(notifStyle.getAttribute('last-updated'), 10) < thisUpdated) { // if missing or outdated
1331
1348
  if (!notifStyle) { // outright missing, create/id/attr/append it first
@@ -1356,6 +1373,13 @@ const chatgpt = {
1356
1373
  + '45% { opacity: 0.05 ; transform: rotateX(-81deg) }'
1357
1374
  + '100% { opacity: 0 ; transform: rotateX(-180deg) scale(1.15) }}'
1358
1375
  )
1376
+ if (toast) notifStyle.textContent += `
1377
+ div.chatgpt-notif {
1378
+ position: absolute ; left: 50% ; right: 21% !important ; text-align: center ;
1379
+ ${ scheme == 'dark' ? 'border: 2px solid white ;' : '' }
1380
+ margin-${ !notificationDiv.isTop ? 'bottom: 105px' : 'top: 42px' };
1381
+ transform: translate(-50%, -50%) scale(0.6) !important }
1382
+ div.chatgpt-notif > div.notif-close-btn { top: 18px ; right: 7px ; transform: scale(2) }`
1359
1383
  }
1360
1384
 
1361
1385
  // Enqueue notification
@@ -1377,7 +1401,7 @@ const chatgpt = {
1377
1401
  const oldDiv = document.getElementById(divId),
1378
1402
  offsetProp = oldDiv.style.top ? 'top' : 'bottom', // pick property to change
1379
1403
  vOffset = +parseInt(oldDiv.style[offsetProp]) +5 + oldDiv.getBoundingClientRect().height
1380
- oldDiv.style[offsetProp] = `${ vOffset }px` // change prop
1404
+ oldDiv.style[offsetProp] = `${vOffset}px` // change prop
1381
1405
  }
1382
1406
  } catch (err) { console.warn('Failed to re-position notification:', err) }
1383
1407
  }
@@ -1395,7 +1419,7 @@ const chatgpt = {
1395
1419
 
1396
1420
  // Add notification dismissal to timeout schedule + button clicks
1397
1421
  const dismissNotif = () => {
1398
- notificationDiv.style.animation = `notif-zoom-fade-out ${ fadeDuration }s ease-out`;
1422
+ notificationDiv.style.animation = `notif-zoom-fade-out ${fadeDuration}s ease-out`;
1399
1423
  clearTimeout(dismissFuncTID)
1400
1424
  }
1401
1425
  const dismissFuncTID = setTimeout(dismissNotif, hideDelay * 1000) // maintain visibility for `hideDelay` secs, then dismiss
@@ -1426,7 +1450,7 @@ const chatgpt = {
1426
1450
  }
1427
1451
  Object.keys(colors).forEach(elem => { // populate dark scheme colors if missing
1428
1452
  colors[elem][1] = colors[elem][1] ||
1429
- '#' + (Number(`0x1${ colors[elem][0].replace(/^#/, '') }`) ^ 0xFFFFFF)
1453
+ '#' + (Number(`0x1${colors[elem][0].replace(/^#/, '')}`) ^ 0xFFFFFF)
1430
1454
  .toString(16).substring(1).toUpperCase() // convert to hex
1431
1455
  })
1432
1456
 
@@ -1456,7 +1480,7 @@ const chatgpt = {
1456
1480
  : (( Object.keys(this).find(obj => Object.keys(this[obj]).includes(this[functionName[1]].name)) + '.' )
1457
1481
  + this[functionName[1]].name )),
1458
1482
  isAsync = this[functionName[1]]?.constructor.name == 'AsyncFunction'
1459
- console.log('%c>> %c' + ( isChatGptObjParent ? '' : `${ functionName[0] }.%c`) + functionName[1]
1483
+ console.log('%c>> %c' + ( isChatGptObjParent ? '' : `${functionName[0]}.%c`) + functionName[1]
1460
1484
  + ' - https://chatgptjs.org/userguide/' + /(?:.*\.)?(.*)/.exec(rootFunction)[1].toLowerCase() + ( isAsync ? '-async' : '' ) + '\n%c[%c'
1461
1485
  + ((( functionName[0] == 'chatgpt' && functionName[1] == this[functionName[1]].name ) || // parent is chatgpt + names match or
1462
1486
  !isChatGptObjParent) // parent is chatgpt.obj
@@ -1641,14 +1665,14 @@ const chatgpt = {
1641
1665
  const validValues = ['dark', 'light', 'system']
1642
1666
  if (!value) return console.error('Please specify a scheme value!')
1643
1667
  if (!validValues.includes(value))
1644
- return console.error(`Invalid scheme value. Valid values are [${ validValues }]`)
1668
+ return console.error(`Invalid scheme value. Valid values are [${validValues}]`)
1645
1669
 
1646
1670
  // Determine scheme to set
1647
1671
  let schemeToSet = value
1648
1672
  if (value == 'system')
1649
1673
  schemeToSet = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
1650
1674
  localStorage.setItem('theme', value)
1651
- console.info(`Scheme set to ${ value.toUpperCase() }.`)
1675
+ console.info(`Scheme set to ${value.toUpperCase()}.`)
1652
1676
 
1653
1677
  // Toggle scheme if necessary
1654
1678
  if (!document.documentElement.classList.contains(schemeToSet)) this.toggle()
@@ -1666,7 +1690,7 @@ const chatgpt = {
1666
1690
  for (let i = 0; i < arguments.length; i++) if (typeof arguments[i] != 'string')
1667
1691
  return console.error(`Argument ${ i + 1 } must be a string.`)
1668
1692
  chatgpt.send('What is the sentiment of the following text'
1669
- + ( entity ? ` towards the entity ${ entity },` : '')
1693
+ + ( entity ? ` towards the entity ${entity},` : '')
1670
1694
  + ' from strongly negative to strongly positive?\n\n' + text )
1671
1695
  console.info('Analyzing sentiment...')
1672
1696
  await chatgpt.isIdle()
@@ -1687,7 +1711,7 @@ const chatgpt = {
1687
1711
  return new Promise((resolve, reject) => {
1688
1712
  const xhr = new XMLHttpRequest()
1689
1713
  chatgpt.getChatData(chatToGet).then(chat => {
1690
- xhr.open('GET', `${ chatgpt.endpoints.openAI.chat }/${ chat.id }`, true)
1714
+ xhr.open('GET', `${chatgpt.endpoints.openAI.chat}/${chat.id}`, true)
1691
1715
  xhr.setRequestHeader('Content-Type', 'application/json')
1692
1716
  xhr.setRequestHeader('Authorization', 'Bearer ' + token)
1693
1717
  xhr.onload = () => {
@@ -1720,13 +1744,13 @@ const chatgpt = {
1720
1744
  const confirmShareChat = (token, data) => {
1721
1745
  return new Promise((resolve, reject) => {
1722
1746
  const xhr = new XMLHttpRequest()
1723
- xhr.open('PATCH', `${ chatgpt.endpoints.openAI.share }/${ data.share_id }`, true)
1747
+ xhr.open('PATCH', `${chatgpt.endpoints.openAI.share}/${data.share_id}`, true)
1724
1748
  xhr.setRequestHeader('Content-Type', 'application/json')
1725
1749
  xhr.setRequestHeader('Authorization', 'Bearer ' + token)
1726
1750
  xhr.onload = () => {
1727
1751
  if (xhr.status != 200)
1728
1752
  return reject('🤖 chatgpt.js >> Request failed. Cannot share chat.')
1729
- console.info(`Chat shared at '${ data.share_url }'`)
1753
+ console.info(`Chat shared at '${data.share_url}'`)
1730
1754
  return resolve() // the response has nothing useful
1731
1755
  }
1732
1756
  xhr.send(JSON.stringify({ // request body
@@ -1900,7 +1924,7 @@ const chatgpt = {
1900
1924
  for (let i = 0; i < arguments.length; i++) if (typeof arguments[i] != 'string')
1901
1925
  return console.error(`Argument ${ i + 1 } must be a string.`)
1902
1926
  chatgpt.send('Suggest some names. ' + ( details || '' ))
1903
- console.info(`Creating ${ ideaType }...`)
1927
+ console.info(`Creating ${ideaType}...`)
1904
1928
  await chatgpt.isIdle()
1905
1929
  return chatgpt.getChatData('active', 'msg', 'chatgpt', 'latest')
1906
1930
  },
@@ -3,12 +3,14 @@ window.settings = {
3
3
 
4
4
  controls: {
5
5
  // Add settings options as keys, with each key's value being an object that includes:
6
- // - 'type': the control type (e.g. 'toggle', 'link' or 'prompt')
6
+ // - 'type': the control type (e.g. 'toggle', 'slider', 'link' or 'prompt')
7
7
  // - 'label': a descriptive label
8
8
  // - 'defaultVal' (optional): default value of setting (true for toggles if unspecified, false otherwise)
9
9
  // - 'category' (optional): string key from this.categories to group control under
10
10
  // - 'symbol' (optional): for icon display (e.g. ⌚)
11
11
  // - 'helptip' (optional): tooltip to display on hover
12
+ // - 'throttle' (optional): true/false or ms to disable toggles on click (defaults to 1500 if true)
13
+ // - 'dependencies' (optional): array of key names of categories/controls that must also be enabled
12
14
 
13
15
  // NOTE: Controls are displayed in top-to-bottom order (within categories and in top-level)
14
16
  // NOTE: Toggles are disabled by default unless defaultVal is true
@@ -26,6 +28,9 @@ window.settings = {
26
28
  // - 'color' (optional): hex code (w/o #) of color for left-border
27
29
  // - 'helptip' (optional): tooltip to display on hover
28
30
  // - 'autoExpand' (optional): true/false to auto-expand categories on toolbar icon click
31
+ // - 'throttle' (optional): true/false or ms to disable toggles on click (defaults to 1500 if true)
32
+ // - 'throttle' (optional): true/false or ms to disable toggles on click (defaults to 1500 if true)
33
+ // - 'dependencies' (optional): array of key names of categories/controls that must also be enabled
29
34
 
30
35
  // NOTE: Categories are displayed in top-to-bottom order
31
36
 
@@ -43,9 +48,11 @@ window.settings = {
43
48
 
44
49
  load(...keys) {
45
50
  return Promise.all(keys.flat().map(async key => // resolve promise when all keys load
46
- config[key] = (await chrome.storage.local.get(key))[key]
47
- ?? this.controls[key]?.defaultVal ?? this.controls[key]?.type == 'toggle'
48
- ))
51
+ config[key] = (await chrome.storage.local.get(key))[key] ?? initDefaultVal(key)))
52
+ function initDefaultVal(key) {
53
+ const ctrlData = settings.controls?.[key]
54
+ return ctrlData?.defaultVal ?? ( ctrlData?.type == 'slider' ? 100 : ctrlData?.type == 'toggle' )
55
+ }
49
56
  },
50
57
 
51
58
  save(key, val) {
@@ -3,7 +3,7 @@
3
3
  "name": "ChatGPT Extension",
4
4
  "short_name": "ChatGPT 🧩",
5
5
  "description": "A Chromium extension template to start using chatgpt.js like a boss!",
6
- "version": "2025.5.11",
6
+ "version": "2025.7.27",
7
7
  "author": "KudoAI",
8
8
  "homepage_url": "https://github.com/KudoAI/chatgpt.js-chrome-starter",
9
9
  "icons": { "16": "icons/icon16.png", "32": "icons/icon32.png", "64": "icons/icon64.png", "128": "icons/icon128.png" },