@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.
@@ -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[class*=sidebar]:has(nav > div#sidebar-header)',
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
  }
@@ -128,15 +128,16 @@ const chatgpt = {
128
128
  drag: {
129
129
  mousedown(event) { // find modal, update styles, attach listeners, init XY offsets
130
130
  if (event.button != 0) return // prevent non-left-click drag
131
- if (getComputedStyle(event.target).cursor == 'pointer') return // prevent drag on interactive elems
131
+ if (!/auto|default/.test(getComputedStyle(event.target).cursor))
132
+ return // prevent drag on interactive elems
132
133
  chatgpt.draggingModal = event.currentTarget
133
134
  event.preventDefault() // prevent sub-elems like icons being draggable
134
135
  Object.assign(chatgpt.draggingModal.style, {
135
136
  transition: '0.1s', willChange: 'transform', transform: 'scale(1.05)' })
136
- document.body.style.cursor = 'grabbing'; // update cursor
137
- [...chatgpt.draggingModal.children] // prevent hover FX if drag lags behind cursor
138
- .forEach(child => child.style.pointerEvents = 'none');
139
- ['mousemove', 'mouseup'].forEach(eventType => // add listeners
137
+ document.body.style.cursor = 'grabbing' // update cursor
138
+ ;[...chatgpt.draggingModal.children] // prevent hover FX if drag lags behind cursor
139
+ .forEach(child => child.style.pointerEvents = 'none')
140
+ ;['mousemove', 'mouseup'].forEach(eventType => // add listeners
140
141
  document.addEventListener(eventType, handlers.drag[eventType]))
141
142
  const draggingModalRect = chatgpt.draggingModal.getBoundingClientRect()
142
143
  handlers.drag.offsetX = event.clientX - draggingModalRect.left +21
@@ -153,10 +154,10 @@ const chatgpt = {
153
154
  mouseup() { // restore styles/pointer events, remove listeners, reset chatgpt.draggingModal
154
155
  Object.assign(chatgpt.draggingModal.style, { // restore styles
155
156
  cursor: 'inherit', transition: 'inherit', willChange: 'auto', transform: 'scale(1)' })
156
- document.body.style.cursor = ''; // restore cursor
157
- [...chatgpt.draggingModal.children] // restore pointer events
158
- .forEach(child => child.style.pointerEvents = '');
159
- ['mousemove', 'mouseup'].forEach(eventType => // remove listeners
157
+ document.body.style.cursor = '' // restore cursor
158
+ ;[...chatgpt.draggingModal.children] // restore pointer events
159
+ .forEach(child => child.style.pointerEvents = '')
160
+ ;['mousemove', 'mouseup'].forEach(eventType => // remove listeners
160
161
  document.removeEventListener(eventType, handlers.drag[eventType]))
161
162
  chatgpt.draggingModal = null
162
163
  }
@@ -180,13 +181,13 @@ const chatgpt = {
180
181
  modalStyle.setAttribute('last-updated', thisUpdated.toString())
181
182
  document.head.append(modalStyle)
182
183
  }
183
- modalStyle.innerText = ( // update prev/new style contents
184
+ modalStyle.textContent = ( // update prev/new style contents
184
185
  `.chatgpt-modal { /* vars */
185
- --transition: opacity 0.65s cubic-bezier(.165,.84,.44,1), /* for fade-in */
186
- transform 0.55s cubic-bezier(.165,.84,.44,1) ; /* for move-in */
187
- --bg-transition: background-color 0.25s ease ; /* for bg dim */
188
- --btn-transition: transform 0.1s ease-in-out, box-shadow 0.1s ease-in-out ; /* for smooth zoom */
189
- --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' }}`
190
191
 
191
192
  + '.no-mobile-tap-outline { outline: none ; -webkit-tap-highlight-color: transparent }'
192
193
 
@@ -196,8 +197,8 @@ const chatgpt = {
196
197
  position: fixed ; top: 0 ; left: 0 ; width: 100% ; height: 100% ; /* expand to full view-port */
197
198
  display: flex ; justify-content: center ; align-items: center ; z-index: 9999 ; /* align */
198
199
  transition: var(--bg-transition) ; /* for bg dim */
199
- -webkit-transition: var(--bg-transition) ; -moz-transition: var(--bg-transition) ;
200
- -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) }`
201
202
 
202
203
  // Alert styles
203
204
  + `.chatgpt-modal > div {
@@ -211,13 +212,13 @@ const chatgpt = {
211
212
  border: 1px solid ${ scheme == 'dark' ? 'white' : '#b5b5b5' };
212
213
  transform: translateX(-3px) translateY(7px) ; /* offset to move-in from */
213
214
  max-width: 75vw ; word-wrap: break-word ; border-radius: 15px ;
214
- --shadow: 0 30px 60px rgba(0,0,0,0.12) ; box-shadow: var(--shadow) ;
215
- -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) ;
216
217
  user-select: none ; -webkit-user-select: none ; -moz-user-select: none ;
217
- -o-user-select: none ; -ms-user-select: none ;
218
+ -o-user-select: none ; -ms-user-select: none ;
218
219
  transition: var(--transition) ; /* for fade-in + move-in */
219
- -webkit-transition: var(--transition) ; -moz-transition: var(--transition) ;
220
- -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) }
221
222
  .chatgpt-modal h2 { font-weight: bold ; font-size: 24px ; margin-bottom: 9px }
222
223
  .chatgpt-modal a { color: ${ scheme == 'dark' ? '#00cfff' : '#1e9ebb' }}
223
224
  .chatgpt-modal a:hover { text-decoration: underline }
@@ -232,13 +233,13 @@ const chatgpt = {
232
233
  display: flex ; justify-content: flex-end ; margin: 20px -5px -3px 0 ;
233
234
  ${ isMobile ? 'flex-direction: column-reverse' : '' }}
234
235
  .chatgpt-modal button {
235
- font-size: 14px ; text-transform: uppercase ;
236
+ font-size: 14px ; text-transform: uppercase ; cursor: crosshair ;
236
237
  margin-left: ${ isMobile ? 0 : 10 }px ; padding: ${ isMobile ? 15 : 8 }px 18px ;
237
238
  ${ isMobile ? 'margin-top: 5px ; margin-bottom: 3px ;' : '' }
238
239
  border-radius: 0 ; border: 1px solid ${ scheme == 'dark' ? 'white' : 'black' };
239
240
  transition: var(--btn-transition) ;
240
- -webkit-transition: var(--btn-transition) ; -moz-transition: var(--btn-transition) ;
241
- -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) }
242
243
  .chatgpt-modal button:hover {
243
244
  transform: scale(1.055) ; color: black ;
244
245
  background-color: #${ scheme == 'dark' ? '00cfff' : '9cdaff' };
@@ -270,7 +271,7 @@ const chatgpt = {
270
271
  }
271
272
 
272
273
  // Insert text into elems
273
- modalTitle.innerText = title || '' ; modalMessage.innerText = msg || '' ; chatgpt.renderHTML(modalMessage)
274
+ modalTitle.textContent = title || '' ; modalMessage.innerText = msg || '' ; chatgpt.renderHTML(modalMessage)
274
275
 
275
276
  // Create/append buttons (if provided) to buttons div
276
277
  const modalButtons = document.createElement('div')
@@ -421,7 +422,7 @@ const chatgpt = {
421
422
  this.toggle.refreshFrame()
422
423
  document.removeEventListener('visibilitychange', this.toggle.beacons)
423
424
  clearTimeout(this.isActive) ; this.isActive = null
424
- console.log(`↻ ChatGPT >> [${ chatgpt.autoRefresh.nowTimeStamp()}] Auto refresh de-activated`)
425
+ console.log(`↻ ChatGPT >> [${chatgpt.autoRefresh.nowTimeStamp()}] Auto refresh de-activated`)
425
426
  } else
426
427
  console.log(`↻ ChatGPT >> [${chatgpt.autoRefresh.nowTimeStamp()}] Auto refresh already inactive!`)
427
428
  },
@@ -647,7 +648,7 @@ const chatgpt = {
647
648
  year = now.getFullYear(),
648
649
  hour = now.getHours().toString().padStart(2, '0'),
649
650
  minute = now.getMinutes().toString().padStart(2, '0')
650
- filename = `ChatGPT_${ day }-${ month }-${ year }_${ hour }-${ minute }.txt`
651
+ filename = `ChatGPT_${day}-${month}-${year}_${hour}-${minute}.txt`
651
652
 
652
653
  // Create transcript from active chat
653
654
  if (chatToGet == 'active' && /\/\w{8}-\w{4}-\w{4}-\w{4}-\w{12}$/.test(window.location.href)) {
@@ -691,13 +692,13 @@ const chatgpt = {
691
692
  }
692
693
 
693
694
  // Export transcript
694
- console.info(`Exporting transcript as ${ format.toUpperCase() }...`)
695
+ console.info(`Exporting transcript as ${format.toUpperCase()}...`)
695
696
  if (format == 'pdf') { // convert SVGs + launch PDF printer
696
697
 
697
698
  // Convert SVG icons to data URLs for proper PDF rendering
698
699
  transcript = transcript.replace(/<svg.*?<\/svg>/g, (match) => {
699
700
  const dataURL = 'data:image/svg+xml,' + encodeURIComponent(match)
700
- return `<img src="${ dataURL }">`
701
+ return `<img src="${dataURL}">`
701
702
  })
702
703
 
703
704
  // Launch PDF printer
@@ -885,7 +886,7 @@ const chatgpt = {
885
886
  (chatToGet == 'active' && !new RegExp(`\/${re_chatID.source}$`).test(location.href))) {
886
887
  chatToGet = Number.isInteger(chatToGet) ? chatToGet : 0 // preserve index, otherwise get latest
887
888
  if (chatToGet > data.length) // reject if index out-of-bounds
888
- return reject(`🤖 chatgpt.js >> Chat with index ${ chatToGet + 1 }`
889
+ return reject(`🤖 chatgpt.js >> Chat with index ${ chatToGet +1 }`
889
890
  + ` is out of bounds. Only ${data.length} chats exist!`)
890
891
  for (const detail of detailsToGet) detailsToReturn[detail] = data[chatToGet][detail]
891
892
  return resolve(detailsToReturn)
@@ -930,7 +931,7 @@ const chatgpt = {
930
931
  userMessages.sort((a, b) => a.msg.create_time - b.msg.create_time) // sort in chronological order
931
932
 
932
933
  if (parseInt(msgToGet, 10) + 1 > userMessages.length) // reject if index out of bounds
933
- return reject(`🤖 chatgpt.js >> Message/response with index ${ msgToGet + 1 }`
934
+ return reject(`🤖 chatgpt.js >> Message/response with index ${ msgToGet +1 }`
934
935
  + ` is out of bounds. Only ${userMessages.length} messages/responses exist!`)
935
936
 
936
937
  // Fill [chatGPTMessages]
@@ -938,8 +939,9 @@ const chatgpt = {
938
939
  let sub = []
939
940
  for (const key in data) {
940
941
  if (data[key].message != null && data[key].message.author.role == 'assistant'
941
- && data[key].parent == userMessage.id)
942
+ && isUserMsgAncestor(key, userMessage.id)) {
942
943
  sub.push(data[key].message)
944
+ }
943
945
  }
944
946
  sub.sort((a, b) => a.create_time - b.create_time) // sort in chronological order
945
947
  sub = sub.map(x => { // pull out msgs after sorting
@@ -972,6 +974,17 @@ const chatgpt = {
972
974
  return resolve(msgToGet == 'all' ? msgsToReturn // if 'all' passed, return array
973
975
  : msgToGet == 'latest' ? msgsToReturn[msgsToReturn.length - 1] // else if 'latest' passed, return latest
974
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
+ }
975
988
  }
976
989
  xhr.send()
977
990
  })})}
@@ -1104,7 +1117,7 @@ const chatgpt = {
1104
1117
  const validMethods = ['POST', 'GET']
1105
1118
  method = (method || '').trim().toUpperCase()
1106
1119
  if (!method || !validMethods.includes(method)) // reject if not valid method
1107
- return console.error(`Valid methods are ${ validMethods }`)
1120
+ return console.error(`Valid methods are ${validMethods}`)
1108
1121
  if (!token) return console.error('Please provide a valid access token!')
1109
1122
  if (body && typeof body != 'object') // reject if body is passed but not an object
1110
1123
  return console.error(`Invalid body data type. Got ${ typeof body }, expected object`)
@@ -1125,7 +1138,7 @@ const chatgpt = {
1125
1138
  return reject('🤖 chatgpt.js >> ' + responseData.detail.description)
1126
1139
  else if (xhr.status != 200)
1127
1140
  return reject('🤖 chatgpt.js >> Request failed. Cannot contact custom instructions endpoint.')
1128
- console.info(`Custom instructions successfully contacted with method ${ method }`)
1141
+ console.info(`Custom instructions successfully contacted with method ${method}`)
1129
1142
  return resolve(responseData || {}) // return response data no matter what the method is
1130
1143
  }
1131
1144
  xhr.send(JSON.stringify(body) || '') // if body is passed send it, else just send the request
@@ -1290,7 +1303,12 @@ const chatgpt = {
1290
1303
 
1291
1304
  minify() { chatgpt.code.minify(); },
1292
1305
 
1293
- 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
1294
1312
  notifDuration = notifDuration ? +notifDuration : 1.75; // sec duration to maintain notification visibility
1295
1313
  const fadeDuration = 0.35, // sec duration of fade-out
1296
1314
  vpYoffset = 23, vpXoffset = 27 // px offset from viewport border
@@ -1299,7 +1317,7 @@ const chatgpt = {
1299
1317
  const notificationDiv = document.createElement('div') // make div
1300
1318
  notificationDiv.id = Math.floor(chatgpt.randomFloat() * 1000000) + Date.now()
1301
1319
  notificationDiv.classList.add('chatgpt-notif')
1302
- notificationDiv.innerText = msg // insert msg
1320
+ notificationDiv.textContent = msg // insert msg
1303
1321
  document.body.append(notificationDiv) // insert into DOM
1304
1322
 
1305
1323
  // Create/append close button
@@ -1324,7 +1342,7 @@ const chatgpt = {
1324
1342
  + (notificationDiv.isRight ? 'Right' : 'Left')
1325
1343
 
1326
1344
  // Create/append/update notification style (if missing or outdated)
1327
- 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`
1328
1346
  let notifStyle = document.querySelector('#chatgpt-notif-style') // try to select existing style
1329
1347
  if (!notifStyle || parseInt(notifStyle.getAttribute('last-updated'), 10) < thisUpdated) { // if missing or outdated
1330
1348
  if (!notifStyle) { // outright missing, create/id/attr/append it first
@@ -1332,7 +1350,7 @@ const chatgpt = {
1332
1350
  notifStyle.setAttribute('last-updated', thisUpdated.toString())
1333
1351
  document.head.append(notifStyle)
1334
1352
  }
1335
- notifStyle.innerText = ( // update prev/new style contents
1353
+ notifStyle.textContent = ( // update prev/new style contents
1336
1354
  '.chatgpt-notif {'
1337
1355
  + 'font-family: -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", Roboto, "PingFang SC",'
1338
1356
  + '"Hiragino Sans GB", "Microsoft YaHei", "Helvetica Neue", sans-serif ;'
@@ -1355,6 +1373,13 @@ const chatgpt = {
1355
1373
  + '45% { opacity: 0.05 ; transform: rotateX(-81deg) }'
1356
1374
  + '100% { opacity: 0 ; transform: rotateX(-180deg) scale(1.15) }}'
1357
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) }`
1358
1383
  }
1359
1384
 
1360
1385
  // Enqueue notification
@@ -1368,7 +1393,7 @@ const chatgpt = {
1368
1393
  notificationDiv.style.right = notificationDiv.isRight ? vpXoffset.toString() + 'px' : ''
1369
1394
  notificationDiv.style.left = !notificationDiv.isRight ? vpXoffset.toString() + 'px' : ''
1370
1395
 
1371
- // Reposition old notifications
1396
+ // Re-position old notifications
1372
1397
  const thisQuadrantQueue = notifyProps.queue[notificationDiv.quadrant]
1373
1398
  if (thisQuadrantQueue.length > 1) {
1374
1399
  try { // to move old notifications
@@ -1376,9 +1401,9 @@ const chatgpt = {
1376
1401
  const oldDiv = document.getElementById(divId),
1377
1402
  offsetProp = oldDiv.style.top ? 'top' : 'bottom', // pick property to change
1378
1403
  vOffset = +parseInt(oldDiv.style[offsetProp]) +5 + oldDiv.getBoundingClientRect().height
1379
- oldDiv.style[offsetProp] = `${ vOffset }px` // change prop
1404
+ oldDiv.style[offsetProp] = `${vOffset}px` // change prop
1380
1405
  }
1381
- } catch (err) {}
1406
+ } catch (err) { console.warn('Failed to re-position notification:', err) }
1382
1407
  }
1383
1408
 
1384
1409
  // Show notification
@@ -1394,7 +1419,7 @@ const chatgpt = {
1394
1419
 
1395
1420
  // Add notification dismissal to timeout schedule + button clicks
1396
1421
  const dismissNotif = () => {
1397
- notificationDiv.style.animation = `notif-zoom-fade-out ${ fadeDuration }s ease-out`;
1422
+ notificationDiv.style.animation = `notif-zoom-fade-out ${fadeDuration}s ease-out`;
1398
1423
  clearTimeout(dismissFuncTID)
1399
1424
  }
1400
1425
  const dismissFuncTID = setTimeout(dismissNotif, hideDelay * 1000) // maintain visibility for `hideDelay` secs, then dismiss
@@ -1425,7 +1450,7 @@ const chatgpt = {
1425
1450
  }
1426
1451
  Object.keys(colors).forEach(elem => { // populate dark scheme colors if missing
1427
1452
  colors[elem][1] = colors[elem][1] ||
1428
- '#' + (Number(`0x1${ colors[elem][0].replace(/^#/, '') }`) ^ 0xFFFFFF)
1453
+ '#' + (Number(`0x1${colors[elem][0].replace(/^#/, '')}`) ^ 0xFFFFFF)
1429
1454
  .toString(16).substring(1).toUpperCase() // convert to hex
1430
1455
  })
1431
1456
 
@@ -1455,7 +1480,7 @@ const chatgpt = {
1455
1480
  : (( Object.keys(this).find(obj => Object.keys(this[obj]).includes(this[functionName[1]].name)) + '.' )
1456
1481
  + this[functionName[1]].name )),
1457
1482
  isAsync = this[functionName[1]]?.constructor.name == 'AsyncFunction'
1458
- console.log('%c>> %c' + ( isChatGptObjParent ? '' : `${ functionName[0] }.%c`) + functionName[1]
1483
+ console.log('%c>> %c' + ( isChatGptObjParent ? '' : `${functionName[0]}.%c`) + functionName[1]
1459
1484
  + ' - https://chatgptjs.org/userguide/' + /(?:.*\.)?(.*)/.exec(rootFunction)[1].toLowerCase() + ( isAsync ? '-async' : '' ) + '\n%c[%c'
1460
1485
  + ((( functionName[0] == 'chatgpt' && functionName[1] == this[functionName[1]].name ) || // parent is chatgpt + names match or
1461
1486
  !isChatGptObjParent) // parent is chatgpt.obj
@@ -1640,14 +1665,14 @@ const chatgpt = {
1640
1665
  const validValues = ['dark', 'light', 'system']
1641
1666
  if (!value) return console.error('Please specify a scheme value!')
1642
1667
  if (!validValues.includes(value))
1643
- return console.error(`Invalid scheme value. Valid values are [${ validValues }]`)
1668
+ return console.error(`Invalid scheme value. Valid values are [${validValues}]`)
1644
1669
 
1645
1670
  // Determine scheme to set
1646
1671
  let schemeToSet = value
1647
1672
  if (value == 'system')
1648
1673
  schemeToSet = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
1649
1674
  localStorage.setItem('theme', value)
1650
- console.info(`Scheme set to ${ value.toUpperCase() }.`)
1675
+ console.info(`Scheme set to ${value.toUpperCase()}.`)
1651
1676
 
1652
1677
  // Toggle scheme if necessary
1653
1678
  if (!document.documentElement.classList.contains(schemeToSet)) this.toggle()
@@ -1665,7 +1690,7 @@ const chatgpt = {
1665
1690
  for (let i = 0; i < arguments.length; i++) if (typeof arguments[i] != 'string')
1666
1691
  return console.error(`Argument ${ i + 1 } must be a string.`)
1667
1692
  chatgpt.send('What is the sentiment of the following text'
1668
- + ( entity ? ` towards the entity ${ entity },` : '')
1693
+ + ( entity ? ` towards the entity ${entity},` : '')
1669
1694
  + ' from strongly negative to strongly positive?\n\n' + text )
1670
1695
  console.info('Analyzing sentiment...')
1671
1696
  await chatgpt.isIdle()
@@ -1686,7 +1711,7 @@ const chatgpt = {
1686
1711
  return new Promise((resolve, reject) => {
1687
1712
  const xhr = new XMLHttpRequest()
1688
1713
  chatgpt.getChatData(chatToGet).then(chat => {
1689
- xhr.open('GET', `${ chatgpt.endpoints.openAI.chat }/${ chat.id }`, true)
1714
+ xhr.open('GET', `${chatgpt.endpoints.openAI.chat}/${chat.id}`, true)
1690
1715
  xhr.setRequestHeader('Content-Type', 'application/json')
1691
1716
  xhr.setRequestHeader('Authorization', 'Bearer ' + token)
1692
1717
  xhr.onload = () => {
@@ -1719,13 +1744,13 @@ const chatgpt = {
1719
1744
  const confirmShareChat = (token, data) => {
1720
1745
  return new Promise((resolve, reject) => {
1721
1746
  const xhr = new XMLHttpRequest()
1722
- xhr.open('PATCH', `${ chatgpt.endpoints.openAI.share }/${ data.share_id }`, true)
1747
+ xhr.open('PATCH', `${chatgpt.endpoints.openAI.share}/${data.share_id}`, true)
1723
1748
  xhr.setRequestHeader('Content-Type', 'application/json')
1724
1749
  xhr.setRequestHeader('Authorization', 'Bearer ' + token)
1725
1750
  xhr.onload = () => {
1726
1751
  if (xhr.status != 200)
1727
1752
  return reject('🤖 chatgpt.js >> Request failed. Cannot share chat.')
1728
- console.info(`Chat shared at '${ data.share_url }'`)
1753
+ console.info(`Chat shared at '${data.share_url}'`)
1729
1754
  return resolve() // the response has nothing useful
1730
1755
  }
1731
1756
  xhr.send(JSON.stringify({ // request body
@@ -1762,8 +1787,7 @@ const chatgpt = {
1762
1787
  activateObserver() {
1763
1788
 
1764
1789
  // Stop the previous observer to preserve resources
1765
- if (this.observer instanceof MutationObserver)
1766
- try { this.observer.disconnect() } catch (e) {}
1790
+ if (this.observer instanceof MutationObserver) this.observer.disconnect()
1767
1791
 
1768
1792
  if (!this.elems.length) return console.error('🤖 chatgpt.js >> No elems to append!')
1769
1793
 
@@ -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
  },
@@ -2,7 +2,6 @@
2
2
  // Source: https://github.com/adamlui/ai-web-extensions/blob/main/assets/lib/dom.js/src/dom.js
3
3
 
4
4
  window.dom = {
5
- import(deps) { Object.assign(this.imports = this.imports || {}, deps) },
6
5
 
7
6
  addRisingParticles(targetNode, { lightScheme = 'gray', darkScheme = 'white' } = {}) {
8
7
  // * Requires https://assets.aiwebextensions.com/styles/rising-particles/dist/<lightScheme>.min.css
@@ -12,11 +11,11 @@ window.dom = {
12
11
  particlesDivsWrapper.style.cssText = (
13
12
  'position: absolute ; top: 0 ; left: 0 ;' // hug targetNode's top-left corner
14
13
  + 'height: 100% ; width: 100% ; border-radius: 15px ; overflow: clip ;' // bound innards exactly by targetNode
15
- + 'z-index: -1' ); // allow interactive elems to be clicked
16
- ['sm', 'med', 'lg'].forEach(particleSize => {
14
+ + 'z-index: -1' ) // allow interactive elems to be clicked
15
+ ;['sm', 'med', 'lg'].forEach(particleSize => {
17
16
  const particlesDiv = document.createElement('div')
18
- particlesDiv.id = this.imports.config?.bgAnimationsDisabled ? `particles-${particleSize}-off`
19
- : `${( this.imports.env?.ui?.scheme || this.imports.env?.ui?.app?.scheme ) == 'dark' ? darkScheme
17
+ particlesDiv.id = config?.bgAnimationsDisabled ? `particles-${particleSize}-off`
18
+ : `${( env?.ui?.scheme || env?.ui?.app?.scheme ) == 'dark' ? darkScheme
20
19
  : lightScheme }-particles-${particleSize}`
21
20
  particlesDivsWrapper.append(particlesDiv)
22
21
  })
@@ -41,8 +40,9 @@ window.dom = {
41
40
 
42
41
  style(content, attrs = {}) {
43
42
  const style = document.createElement('style')
43
+ style.setAttribute('type', 'text/css') // support older browsers
44
44
  for (const attr in attrs) style.setAttribute(attr, attrs[attr])
45
- if (content) style.innerText = content
45
+ if (content) style.textContent = content
46
46
  return style
47
47
  },
48
48
 
@@ -94,17 +94,18 @@ window.dom = {
94
94
  computedWidth(elems) { return this.computedSize(elems, { dimension: 'width' }) }, // including margins
95
95
 
96
96
  loadedElem(selector, { timeout = null } = {}) {
97
- const timeoutPromise = new Promise(resolve =>
98
- timeout ? setTimeout(() => resolve(null), timeout) : undefined)
99
- const isLoadedPromise = new Promise(resolve => {
100
- const elem = document.querySelector(selector)
101
- if (elem) resolve(elem)
102
- else new MutationObserver((_, obs) => {
97
+ const raceEntries = [
98
+ new Promise(resolve => { // when elem loads
103
99
  const elem = document.querySelector(selector)
104
- if (elem) { obs.disconnect() ; resolve(elem) }
105
- }).observe(document.documentElement, { childList: true, subtree: true })
106
- })
107
- return Promise.race([isLoadedPromise, timeoutPromise])
100
+ if (elem) resolve(elem)
101
+ else new MutationObserver((_, obs) => {
102
+ const elem = document.querySelector(selector)
103
+ if (elem) { obs.disconnect() ; resolve(elem) }
104
+ }).observe(document.documentElement, { childList: true, subtree: true })
105
+ })
106
+ ]
107
+ if (timeout) raceEntries.push(new Promise(resolve => setTimeout(() => resolve(null), timeout)))
108
+ return Promise.race(raceEntries)
108
109
  }
109
110
  }
110
111
  };
@@ -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' 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) {
@@ -0,0 +1,6 @@
1
+ window.ui = {
2
+ getScheme() {
3
+ return /\b(light|dark)\b/.exec(document.documentElement.className)?.[1]
4
+ || ( window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light' )
5
+ }
6
+ };
@@ -3,14 +3,14 @@
3
3
  "name": "ChatGPT Extension",
4
4
  "short_name": "ChatGPT 🧩",
5
5
  "description": "A Chromium extension template to start using chatgpt.js like a boss!",
6
- "version": "2025.4.28",
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" },
10
- "permissions": [ "activeTab", "storage" ],
10
+ "permissions": ["activeTab", "storage"],
11
11
  "action": { "default_popup": "popup/index.html" },
12
- "web_accessible_resources": [{ "matches": [ "<all_urls>" ], "resources": [ "components/*", "lib/*" ]}],
13
- "content_scripts": [{ "matches": [ "https://chatgpt.com/*" ], "js": [ "content.js" ] }],
12
+ "web_accessible_resources": [{ "matches": ["<all_urls>"], "resources": ["components/*", "lib/*"] }],
13
+ "content_scripts": [{ "matches": ["https://chatgpt.com/*"], "js": ["content.js"] }],
14
14
  "background": { "service_worker": "service-worker.js" },
15
15
  "minimum_chrome_version": "88"
16
16
  }